Aller au contenu principal

Structure de Chat IA Extensible

Pour une application de chat extensible où les utilisateurs peuvent choisir différents modèles d'IA (par exemple, Gemini, OpenAI et d'autres à l'avenir), une approche structurée utilisant les Classes de Base Abstraites (CBA) et le Patron de Fabrique est très efficace. Cela permet de définir une interface commune pour tous les fournisseurs d'IA et d'en intégrer facilement de nouveaux sans modifier la logique principale existante.

Concepts Clés

  1. Classe de Base Abstraite (CBA) : Définit les méthodes communes que tous les fournisseurs d'IA doivent implémenter. Cela assure une interface cohérente.
  2. Implémentations Concrètes : Classes pour chaque fournisseur d'IA spécifique (par exemple, GeminiAIProvider, OpenAIAIProvider) qui héritent de la CBA et implémentent ses méthodes.
  3. Patron de Fabrique : Un mécanisme pour créer des instances des fournisseurs d'IA basées sur un type donné (par exemple, "gemini", "openai") sans exposer la logique de création au client. Cela découple le client des classes de fournisseurs concrètes.
  4. Gestion de la Configuration : Gestion centralisée des clés API et des noms de modèles par défaut, idéalement chargés à partir de variables d'environnement pour la sécurité.

Structure du Code

Voici la structure de fichiers et le contenu recommandés :

project_root/
├── config.py
├── ai_providers/
│ ├── __init__.py
│ ├── abstract_ai_provider.py
│ ├── gemini_provider.py
│ └── openai_provider.py
├── ai_factory.py
└── main.py

config.py

Ce fichier gère le chargement de la configuration, en particulier des clés API sensibles, de préférence à partir des variables d'environnement.

import os

class Config:
"""Gère la configuration de l'application, y compris les clés API et les valeurs par défaut des modèles."""

# Configuration OpenAI
OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "your_openai_api_key_here")
OPENAI_DEFAULT_MODEL: str = "gpt-3.5-turbo" # Ou "gpt-4"

# Configuration Gemini
GEMINI_API_KEY: str = os.getenv("GEMINI_API_KEY", "your_gemini_api_key_here")
GEMINI_DEFAULT_MODEL: str = "gemini-pro"

# Ajoutez ici la configuration pour d'autres fournisseurs d'IA
# EXAMPLE_API_KEY: str = os.getenv("EXAMPLE_API_KEY", "your_example_api_key_here")
# EXAMPLE_DEFAULT_MODEL: str = "example-model"

# Exemple de configuration des variables d'environnement (par exemple, dans votre shell ou un fichier .env)
# export OPENAI_API_KEY="sk-..."
# export GEMINI_API_KEY="AIza..."

ai_providers/abstract_ai_provider.py

Définit l'interface commune pour tous les fournisseurs d'IA.

from abc import ABC, abstractmethod
from typing import List, Dict

class AbstractAIProvider(ABC):
"""Classe de Base Abstraite pour tous les fournisseurs de chat IA."""

def __init__(self, api_key: str, model_name: str):
self._api_key = api_key
self._model_name = model_name

@abstractmethod
def generate_response(self, messages: List[Dict]) -> str:
"""
Génère une réponse du modèle d'IA basée sur les messages fournis.

Arguments :
messages: Une liste de dictionnaires de messages, typiquement au format
`[{"role": "user", "content": "Hello!"}, {"role": "assistant", "content": "Hi!"}]`.
Le format exact peut varier légèrement selon le fournisseur, donc les implémentations concrètes
doivent gérer la conversion si nécessaire.

Retourne :
La réponse de l'IA sous forme de chaîne de caractères.
"""
pass

@abstractmethod
def get_model_name(self) -> str:
"""Retourne le nom du modèle d'IA utilisé."""
pass

# Ajoutez d'autres méthodes communes si nécessaire, par exemple, get_token_count, get_available_models

ai_providers/openai_provider.py

Implémente AbstractAIProvider pour l'API d'OpenAI. (Installez openai avec pip install openai)

from openai import OpenAI
from typing import List, Dict
from .abstract_ai_provider import AbstractAIProvider

class OpenAIAIProvider(AbstractAIProvider):
"""Implémentation concrète pour les modèles de chat OpenAI."""

def __init__(self, api_key: str, model_name: str):
super().__init__(api_key, model_name)
self._client = OpenAI(api_key=self._api_key)

def generate_response(self, messages: List[Dict]) -> str:
"""
Génère une réponse en utilisant l'API de complétion de chat d'OpenAI.
Le format des 'messages' est directement compatible avec l'API d'OpenAI.
"""
try:
chat_completion = self._client.chat.completions.create(
model=self._model_name,
messages=messages,
temperature=0.7, # Paramètre d'exemple
)
return chat_completion.choices[0].message.content
except Exception as e:
print(f"Erreur lors de l'appel à l'API OpenAI : {e}")
return "Désolé, je n'ai pas pu obtenir de réponse d'OpenAI."

def get_model_name(self) -> str:
return self._model_name


ai_providers/gemini_provider.py

Implémente AbstractAIProvider pour l'API de Google Gemini. (Installez google-generativeai avec pip install google-generativeai)

import google.generativeai as genai
from typing import List, Dict
from .abstract_ai_provider import AbstractAIProvider

class GeminiAIProvider(AbstractAIProvider):
"""Implémentation concrète pour les modèles de chat Google Gemini."""

def __init__(self, api_key: str, model_name: str):
super().__init__(api_key, model_name)
genai.configure(api_key=self._api_key)
self._model = genai.GenerativeModel(self._model_name)

def generate_response(self, messages: List[Dict]) -> str:
"""
Génère une réponse en utilisant l'API de chat de Google Gemini.
Note : La méthode `start_chat` de Gemini attend des messages dans un format légèrement différent
(par exemple, pas de 'rôle' pour l'utilisateur/modèle dans l'historique après le prompt initial).
Cet exemple simplifie pour plus de clarté, en supposant un nouveau chat par appel ou
une fonction de conversion. Pour un chat continu approprié, vous géreriez une `chat_session`.
"""
try:
# Par souci de simplicité, conversion des messages de type OpenAI au format de Gemini
# pour un seul tour. Pour les conversations à plusieurs tours, vous géreriez une session de chat.
gemini_messages = []
for msg in messages:
role_map = {"user": "user", "assistant": "model"}
gemini_messages.append({"role": role_map.get(msg["role"], "user"), "parts": [msg["content"]]})

# Pour une seule requête, le dernier message est le prompt.
# Pour un chat continu, utilisez `start_chat` et `send_message`.
response = self._model.generate_content(gemini_messages[-1]["parts"][0]) # Prend le dernier message de l'utilisateur
return response.text
except Exception as e:
print(f"Erreur lors de l'appel à l'API Gemini : {e}")
return "Désolé, je n'ai pas pu obtenir de réponse de Gemini."

def get_model_name(self) -> str:
return self._model_name


ai_factory.py

La fabrique qui fournit des instances de fournisseurs d'IA basées sur un identifiant de chaîne de caractères.

from typing import Type
from ai_providers.abstract_ai_provider import AbstractAIProvider
from ai_providers.openai_provider import OpenAIAIProvider
from ai_providers.gemini_provider import GeminiAIProvider
from config import Config

class AIProviderFactory:
"""Fabrique pour la création d'instances de fournisseurs d'IA."""

_providers: dict[str, Type[AbstractAIProvider]] = {
"openai": OpenAIAIProvider,
"gemini": GeminiAIProvider,
# Ajoutez de nouveaux fournisseurs d'IA ici
# "example_ai": ExampleAIProvider,
}

@staticmethod
def get_provider(provider_name: str) -> AbstractAIProvider:
"""
Retourne une instance du fournisseur d'IA spécifié.

Arguments :
provider_name: Le nom du fournisseur d'IA (par exemple, "openai", "gemini").

Retourne :
Une instance d'AbstractAIProvider.

Lève :
ValueError: Si le nom du fournisseur n'est pas reconnu.
"""
provider_class = AIProviderFactory._providers.get(provider_name.lower())

if not provider_class:
raise ValueError(f"Fournisseur d'IA inconnu : {provider_name}. Fournisseurs disponibles : {list(AIProviderFactory._providers.keys())}")

# Récupère la clé API et le nom du modèle de la configuration en fonction de `provider_name`
api_key = getattr(Config, f"{provider_name.upper()}_API_KEY")
model_name = getattr(Config, f"{provider_name.upper()}_DEFAULT_MODEL")

return provider_class(api_key=api_key, model_name=model_name)


main.py

La logique principale de l'application, démontrant comment utiliser la fabrique pour interagir avec différents modèles d'IA.

from ai_factory import AIProviderFactory
from typing import List, Dict

def chat_interface():
"""Interface de chat en ligne de commande simple."""
print("Bienvenue dans le Chatbot Multi-IA !")
print("Choisissez votre fournisseur d'IA (par exemple, openai, gemini) :")

provider_choice = input("> ").strip().lower()

try:
ai_provider = AIProviderFactory.get_provider(provider_choice)
print(f"Utilisation de {ai_provider.get_model_name()} via le fournisseur {provider_choice}.")
except ValueError as e:
print(e)
return

messages: List[Dict] = []

print("\nCommencez à chatter ! Tapez 'quit' ou 'exit' pour terminer.")
while True:
user_input = input("Vous : ").strip()

if user_input.lower() in ["quit", "exit"]:
print("Au revoir !")
break

messages.append({"role": "user", "content": user_input})

print(f"IA ({ai_provider.get_model_name()}) : Réflexion en cours...", end="\r")
ai_response = ai_provider.generate_response(messages)
print(f"IA ({ai_provider.get_model_name()}) : {ai_response}")

messages.append({"role": "assistant", "content": ai_response})

# Gardez la liste des messages courte à des fins de démonstration, ou implémentez une gestion de l'historique
if len(messages) > 10:
messages = messages[-8:] # Garde les 4 dernières tours (utilisateur + assistant)

if __name__ == "__main__":
# Définissez les variables d'environnement pour les clés API avant l'exécution, par exemple :
# export OPENAI_API_KEY="sk-..."
# export GEMINI_API_KEY="AIza..."
#
# Ou modifiez config.py directement pour les tests (non recommandé pour la production)

chat_interface()

Comment Ajouter une Nouvelle API d'IA (par exemple, ExampleAIProvider)

  1. Installer le SDK : Installez le SDK Python pour votre nouvelle IA (par exemple, pip install example_sdk).

  2. Mettre à jour config.py : Ajoutez les configurations de la clé API et du nom du modèle.

    # config.py
    class Config:
    # ... configuration existante
    EXAMPLE_API_KEY: str = os.getenv("EXAMPLE_API_KEY", "your_example_api_key_here")
    EXAMPLE_DEFAULT_MODEL: str = "example-model-v1"
  3. Créer ai_providers/example_ai_provider.py :

    • Créez un nouveau fichier example_ai_provider.py dans le répertoire ai_providers.
    • Importez AbstractAIProvider.
    • Créez une classe ExampleAIProvider qui hérite de AbstractAIProvider.
    • Implémentez les méthodes __init__ et generate_response conformément au SDK de la nouvelle API. N'oubliez pas de convertir les messages au format attendu par l'API si nécessaire.
    • Implémentez get_model_name.
    # ai_providers/example_ai_provider.py
    # from example_sdk import ExampleClient # Import SDK hypothétique
    from typing import List, Dict
    from .abstract_ai_provider import AbstractAIProvider

    class ExampleAIProvider(AbstractAIProvider):
    def __init__(self, api_key: str, model_name: str):
    super().__init__(api_key, model_name)
    # self._client = ExampleClient(api_key=self._api_key) # Initialise le client SDK

    def generate_response(self, messages: List[Dict]) -> str:
    # Convertit les messages au format attendu par le SDK d'ExampleAIProvider
    # Par souci de simplicité, supposons qu'il prenne le dernier message utilisateur sous forme de chaîne de caractères
    last_user_message = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "")
    if not last_user_message:
    return "Aucun message utilisateur fourni."

    try:
    # response = self._client.generate(model=self._model_name, prompt=last_user_message)
    # return response.text # Ou ce que le SDK renvoie
    return f"Réponse d'ExampleAI utilisant le modèle '{self._model_name}' : {last_user_message.upper()} (simulé)"
    except Exception as e:
    print(f"Erreur lors de l'appel à l'API ExampleAI : {e}")
    return "Désolé, je n'ai pas pu obtenir de réponse d'ExampleAI."

    def get_model_name(self) -> str:
    return self._model_name
  4. Mettre à jour ai_factory.py :

    • Importez votre nouvelle classe de fournisseur.
    • Ajoutez-la au dictionnaire _providers.
    # ai_factory.py
    # ... importations existantes
    from ai_providers.example_ai_provider import ExampleAIProvider # Nouvelle importation

    class AIProviderFactory:
    _providers: dict[str, Type[AbstractAIProvider]] = {
    "openai": OpenAIAIProvider,
    "gemini": GeminiAIProvider,
    "example_ai": ExampleAIProvider, # Ajouter le nouveau fournisseur ici
    }
    # ... reste de la classe

Maintenant, lorsque vous exécutez main.py, vous pouvez taper example_ai comme choix, et le système instanciera et utilisera votre nouveau fournisseur d'IA sans aucune modification de main.py lui-même.