Le cycle de chat sans état : appeler l’API d’OpenAI manuellement en Python
Créez une conversation à plusieurs tours avec le SDK Python d’OpenAI en gérant vous-même l’historique des messages, et comprenez pourquoi ce même mécanisme sous-tend la mémoire et les agents de LangChain.
Des frameworks comme LangChain donnent l’impression que les modèles de chat possèdent une mémoire, mais l’API sous-jacente ne se souvient de rien. Chaque appel est indépendant, et la « conversation » n’est qu’une liste de messages que votre code reconstruit et renvoie à chaque fois. Écrire manuellement cette boucle une seule fois à l’aide du SDK Python d’OpenAI montre exactement ce que les frameworks d’agent automatisent, pourquoi les coûts en tokens augmentent au cours d’une conversation et quels erreurs il faut anticiper.
Qu’est-ce qu’un point de terminaison de modèle hébergé
L’API d’OpenAI est une structure simple : le fournisseur exécute le modèle sur ses GPU et expose les capacités d’inférence via HTTPS. Vous envoyez du texte, le modèle génère des tokens selon sa boucle habituelle de sélection du prochain token, et vous payez par token dans les deux sens. Trois conséquences en découlent :
- Il est sans état. Rien des demandes précédentes n’est conservé, donc chaque demande doit contenir tout ce que le modèle doit savoir.
N’insérez jamais la clé API dans le code source. Les clés peuvent fuiter via l’historique Git, les captures d’écran et les notebooks partagés, et une clé divulguée signifie que quelqu’un d’autre paiera avec votre compte. Le SDK lit automatiquement OPENAI_API_KEY depuis l’environnement, donc votre script n’a absolument pas besoin de code pour gérer la clé. L’utilisation de l’API est facturée séparément de la souscription ChatGPT, et les nouveaux comptes nécessitent généralement un petit solde prépayé.
Rôles : le format de message partagé
Une requête contient une liste de messages, chacun ayant un rôle :
systemcontient vos instructions, que le modèle prend en compte de manière plus importante.usercontient ce que la personne a écrit.
assistant contient les réponses précédentes du modèle et, dans les agents, ses appels d’outils.Ce format est utilisé dans tout l’écosystème. Claude et Gemini adoptent la même idée avec de légères différences, Ollama l’imite, et SystemMessage, HumanMessage ainsi que AIMessage de LangChain représentent ces rôles sous forme de classes. Le fournisseur transforme la liste en une seule séquence de tokens avant de générer, de sorte que ces rôles constituent en réalité une méthode structurée d’ingénierie de prompts.
Une seule requête
Dans le premier exemple, un client lit la clé depuis l’environnement, envoie une instruction système ainsi qu’une question, puis affiche la réponse accompagnée du nombre de tokens de prompt et de complétion provenant de usage:
from openai import OpenAI
client = OpenAI() # key from env
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a concise "
"Python assistant.",
},
{
"role": "user",
"content": "Why resend the whole "
"chat history each call?",
},
],
temperature=0,
)
print(resp.choices[0].message.content)
u = resp.usage
print(u.prompt_tokens, u.completion_tokens)
gpt-4o-mini est un modèle économique adapté à l’apprentissage ; passer à un modèle plus grand ne nécessite qu’un changement mineur, bien que les noms des modèles et leurs prix puissent varier, il convient donc de consulter la liste actuelle. La valeur temperature=0 minimise le hasard dans l’échantillonnage, ce qui constitue la valeur par défaut idéale pour les systèmes de réponse à des questions ainsi que pour les agents utilisant des outils ultérieurement. Enregistrez usage à chaque appel ; c’est votre indicateur de coût.
Gérer une conversation soi-même
Puisque le serveur oublie tout, c’est votre code qui gère l’historique : après chaque appel, il stocke la réponse, ajoute la question suivante et soumet à nouveau l’ensemble. L’aide ci-dessous effectue cela à l’aide d’une liste msgs au niveau du module, qui commence par un message système :
msgs = [{
"role": "system",
"content": "You are a concise assistant.",
}]
def ask(text: str) -> str:
msgs.append(
{"role": "user", "content": text}
)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=msgs, # full history
temperature=0,
)
reply = resp.choices[0].message.content
msgs.append({
"role": "assistant",
"content": reply,
})
return reply
print(ask("Define a context window."))
print(ask("Now for a five-year-old."))
print(ask("Which answer was shorter?"))
La troisième question illustre bien ce point. Le modèle ne peut comparer que les deux réponses car elles se trouvent toutes deux dans msgs et sont renvoyées. Si l’on supprime la ligne qui ajoute la réponse de l’assistant, celui-ci ne comprend plus ce que vous voulez dire.
Cette fonction ask() réapparaît sous de nombreuses formes. L’application web ChatGPT n’est en réalité qu’une version dotée d’une interface utilisateur. RunnableWithMessageHistory de LangChain représente une version gérée du processus d’ajout et de renvoi des messages. Le boucle interne d’un agent suit le même schéma, avec l’ajout de appels à des outils et de leurs résultats. Notez le coût : trois renvois se transforment en un ou deux, de sorte que le nombre de tokens d’entrée augmente à chaque échange.
Exécution de l’exemple
Installez les dépendances, exportez la clé dans votre shell et exécutez le script. La clé présentée ici est un exemple ; utilisez la vôtre via la shell ou un gestionnaire de secrets, jamais dans un fichier mis en version :
pip install -r requirements.txt
export OPENAI_API_KEY="sk-..."
python examples/part02_chat.py
Le script effectue une seule requête en un seul tour, puis met en œuvre une conversation sur trois tours ; après chaque demande, il indique l’utilisation des tokens ainsi qu’un coût approximatif. Il s’arrête immédiatement en cas d’absence de clé et est délibérément exclu des environnements CI, car il engage de l’argent réel et nécessite une clé valide. Si vous souhaitez des tests pour ce type de code, simulez le client.
Périls à anticiper dès la conception
- Clé manquante : une erreur
AuthenticationErroravec le code HTTP 401, généralement parce que la variable n’a pas été définie dans cet environnement, est mal orthographiée ou contient des espaces superflus. Vérifiez cela au démarrage et échouez rapidement. - Limits de vitesse : une erreur
RateLimitErroravec le code HTTP 429 signifie trop de requêtes ou un solde prépayé épuisé. Les agents en boucle rencontreront ce problème, il faut donc ajouter des tentatives répétées avec un mécanisme de backoff dès maintenant.
Même structure entre les fournisseurs
Claude prend une liste de messages d’utilisateurs et d’assistants, le prompt du système étant déplacé dans un paramètre de niveau supérieur distinct. Gemini utilise le même modèle basé sur une liste de conversations, avec des rôles nommés user et model. Ollama propose une interface compatible avec OpenAI, ce qui permet à ce code de cibler un modèle local en modifiant l’URL de base et le nom du modèle ; consultez appeler Claude, GPT et Gemini via des interfaces compatibles avec OpenAI. C’est cette convergence qui permet à LangChain d’offrir une abstraction unique couvrant de nombreux fournisseurs.
Points clés
- L’API de chat est sans état ; votre code gère et renvoie la conversation.
- La liste de messages étiquetés par rôle constitue en fait une norme commune entre les fournisseurs.
- Enregistrez les données
usageà chaque appel, car le nombre de tokens d’entrée augmente à chaque tour.