Conversation Multi-Tour
Au-delà de l’appel unique
Dans une application réelle, l’utilisateur ne pose pas une seule question puis disparaît. Il enchaîne les demandes, revient sur un sujet, pose des questions de suivi. Le function calling doit fonctionner dans ce contexte de conversation multi-tour — où le modèle peut appeler des fonctions à chaque étape du dialogue.
Le principe : maintenir l’historique
La clé d’une conversation multi-tour est de conserver tous les messages dans une liste qui grandit à chaque échange. Le modèle a besoin de tout le contexte pour comprendre les références implicites (« et pour celui-là ? », « compare avec le précédent »).
import os
import json
from mistralai import Mistral
from functools import partial
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
model = "mistral-large-latest"
# Historique de la conversation
messages = [
{
"role": "system",
"content": "Vous êtes un assistant de suivi de paiements. "
"Répondez en français de manière concise."
}
]
Boucle de conversation complète
Voici une boucle de conversation qui gère les appels de fonctions à chaque tour :
def process_tool_calls(response, messages, available_functions, tools):
"""Traite les tool_calls et retourne la réponse finale."""
message = response.choices[0].message
messages.append(message)
# Exécuter chaque tool_call
for tool_call in message.tool_calls:
function_name = tool_call.function.name
function_params = json.loads(tool_call.function.arguments)
if function_name in available_functions:
result = available_functions[function_name](**function_params)
else:
result = json.dumps({"error": f"Fonction {function_name} inconnue"})
messages.append({
"role": "tool",
"name": function_name,
"content": result,
"tool_call_id": tool_call.id
})
# Rappeler le modèle avec les résultats
return client.chat.complete(
model=model, messages=messages, tools=tools
)
def chat(user_input: str, messages: list, tools: list,
available_functions: dict) -> str:
"""Gère un tour de conversation complet."""
messages.append({"role": "user", "content": user_input})
response = client.chat.complete(
model=model, messages=messages, tools=tools
)
message = response.choices[0].message
# Boucle tant que le modèle veut appeler des fonctions
while message.tool_calls and len(message.tool_calls) > 0:
response = process_tool_calls(
response, messages, available_functions, tools
)
message = response.choices[0].message
# Le modèle a fini — il répond en texte
messages.append(message)
return message.content
Exemple de conversation multi-tour
Voyons comment cette boucle fonctionne en pratique :
# Tour 1
reponse = chat("Quel est le statut du paiement T1001 ?",
messages, tools, available_functions)
print(f"Assistant : {reponse}")
# → "Le paiement T1001 a le statut : payé."
# Tour 2 — question de suivi
reponse = chat("Et quand a-t-il été effectué ?",
messages, tools, available_functions)
print(f"Assistant : {reponse}")
# → "Le paiement T1001 a été effectué le 15 mars 2026."
# Tour 3 — nouvelle transaction
reponse = chat("Compare avec le paiement T1003.",
messages, tools, available_functions)
print(f"Assistant : {reponse}")
# → "Le paiement T1003 est en attente, contrairement au T1001 qui est payé."
# Tour 4 — question sans fonction
reponse = chat("Merci, c'est tout pour aujourd'hui.",
messages, tools, available_functions)
print(f"Assistant : {reponse}")
# → "Je vous en prie ! N'hésitez pas si vous avez d'autres questions."
Remarquez au tour 2 : l’utilisateur dit « quand a-t-il été effectué ? » sans préciser la transaction. Le modèle comprend grâce au contexte qu’il s’agit de T1001 et appelle retrieve_payment_date avec le bon identifiant.
Au tour 3, le modèle peut décider d’appeler plusieurs fonctions (statut + date de T1003) pour comparer avec les résultats précédents de T1001.
Chaîner les appels séquentiels
Parfois, le modèle a besoin du résultat d’une première fonction pour appeler la suivante. La boucle while dans la fonction chat() gère ce cas automatiquement :
# L'utilisateur demande quelque chose qui nécessite 2 appels séquentiels
reponse = chat(
"Montre-moi le statut et la date du paiement T1002.",
messages, tools, available_functions
)
# Le modèle peut :
# 1. Appeler retrieve_payment_status(T1002)
# 2. Recevoir le résultat
# 3. Appeler retrieve_payment_date(T1002)
# 4. Recevoir le résultat
# 5. Formuler la réponse finale avec les deux informations
Le pattern de flux séquentiel ressemble à :
user → assistant(tool_call_1) → tool(result_1) → assistant(tool_call_2) → tool(result_2) → assistant(réponse)
Gérer la taille de l’historique
En production, une conversation longue peut devenir coûteuse en tokens. Voici quelques stratégies :
def trim_messages(messages: list, max_messages: int = 20) -> list:
"""Conserve le message système et les N derniers messages."""
system_messages = [m for m in messages if hasattr(m, "role")
and m.get("role") == "system"
or (isinstance(m, dict) and m.get("role") == "system")]
non_system = [m for m in messages if m not in system_messages]
if len(non_system) > max_messages:
non_system = non_system[-max_messages:]
return system_messages + non_system
Attention : ne coupez jamais un tool_call sans son message tool correspondant. Le modèle attend toujours les paires complètes.
Points clés à retenir
- Maintenez un historique complet des messages pour que le modèle comprenne le contexte
- Utilisez une boucle while pour gérer les appels de fonctions séquentiels
- Le modèle comprend les références implicites (« celui-là », « le précédent ») grâce au contexte
- En production, limitez la taille de l’historique tout en gardant les paires tool_call/tool intactes
- La fonction
chat()présentée ici est un pattern réutilisable pour toute application de function calling