\n\n\n\n Mon parcours dans la création d'une IA conversationnelle avec des SDKs - AgntBox Mon parcours dans la création d'une IA conversationnelle avec des SDKs - AgntBox \n

Mon parcours dans la création d’une IA conversationnelle avec des SDKs

📖 12 min read2,394 wordsUpdated Mar 26, 2026

Bonjour à tous, c’est Nina, de retour de mon laboratoire numérique (qui, soyons honnêtes, est surtout ma table de cuisine avec beaucoup de café tiède). Aujourd’hui, je veux parler de quelque chose qui fait du bruit dans mes canaux Slack et hante mes sessions de codage tard dans la nuit : le monde parfois frustrant, souvent brillant des SDK d’IA, en particulier lorsque vous essayez de construire un agent conversationnel qui ressent vraiment comme une conversation, et pas juste un bot FAQ glorifié.

Ma passion particulière ces derniers temps a été d’intégrer l’API Assistants d’OpenAI. Maintenant, je sais ce que vous pensez, « Nina, API Assistants ? C’est de la vieille news ! » Et oui, vous n’avez pas tort. Ça fait un moment qu’elle est sortie. Mais écoutez-moi : je ne suis pas là pour vous donner un tutoriel de base sur « comment appeler client.beta.assistants.create() ». Nous avons tous vu ça. Mon objectif aujourd’hui est de parler d’un problème particulier et épineux que j’ai rencontré et de la façon dont j’ai lutté avec le SDK pour le faire fonctionner comme je le voulais : gérer des conversations multi-tours et conversationnelles avec des outils personnalisés sans perdre la tête (ou le contexte de l’utilisateur). Pensez au-delà des simples questions-réponses. Pensez à des flux de travail complexes, des questions de suivi, et des appels d’outils dynamiques basés sur les intentions d’utilisateur évolutives.

L’état de ma santé mentale : pourquoi l’API Assistants (reste) importante

Avant l’API Assistants, construire quoi que ce soit de « stateful » avec les modèles d’OpenAI ressemblait souvent à être jongleur avec trop de balles en l’air. Vous étiez responsable de la gestion de l’historique des messages, de l’ingénierie des demandes pour le contexte, et d’orchestrer les appels d’outils tout seul. C’était… épuisant. L’API Assistants promettait de nous décharger d’une grande partie de ce poids en gérant les fils, les messages et même l’orchestration des outils. Et pour des cas simples, elle fonctionne à merveille.

Mon projet actuel consiste à construire un assistant personnel de finance « intelligent ». Pas seulement un qui vous dit votre solde, mais un qui peut vous aider à planifier des budgets, suggérer des stratégies d’investissement en fonction de votre tolérance au risque, et même simuler des scénarios financiers futurs. Cela nécessite beaucoup d’aller-retour, de se souvenir des déclarations précédentes (« Je veux économiser pour une maison », « Mon revenu est X », « Que se passerait-il si j’investissais dans Y ? »), et surtout, d’appeler des fonctions spécifiques (comme récupérer des données boursières en temps réel ou exécuter un algorithme de projection de budget) au bon moment.

Ma première tentative était un glorieux désastre. Je transmettais tout l’historique des messages en va-et-vient, essayant de compresser des instructions complexes dans la demande système, et vérifiant manuellement si un appel d’outil était nécessaire. Ça fonctionnait, mais c’était fragile, lent et un véritable cauchemar à déboguer. C’est alors que j’ai décidé de vraiment m’appuyer sur l’API Assistants et son SDK.

L’étreinte du SDK : fils, messages et l’insaisissable `run`

Le cœur de l’API Assistants est le concept de `Threads` et `Messages`. Vous créez un `Thread` pour une conversation, y ajoutez des `Messages`, puis « exécutez » l’`Assistant` sur ce `Thread`. L’`Assistant` traite ensuite les messages, décide s’il doit appeler un outil, et génère une réponse. Assez simple, non ?

Voici où ça devient intéressant. Mon assistant financier doit faire des choses comme :

  • Suivre des objectifs : « Je veux économiser 50 000 $ pour un acompte. »
  • Recueillir des informations : « Quel est votre revenu mensuel actuel ? »
  • Exécuter des calculs : « En fonction de cela, combien de temps prendra-t-il si j’économise 1 000 $ par mois ? »
  • Fournir des conseils : « Envisagez de diversifier votre portefeuille avec des fonds indiciels à faible coût. »

Chacune de ces actions nécessite souvent un appel d’outil personnalisé. Par exemple, `calculate_savings_timeline(goal_amount, monthly_income, monthly_savings)`. Le défi n’est pas seulement de définir l’outil ; c’est d’amener l’Assistant à :

  1. Reconnaître qu’il a besoin de l’outil.
  2. Identifier tous les paramètres nécessaires (même s’ils sont répartis sur plusieurs messages utilisateur).
  3. Demander à l’utilisateur les paramètres manquants de manière élégante.
  4. Exécuter l’outil et incorporer les résultats.

Le problème des paramètres : quand l’Assistant demande plus

Disons qu’un utilisateur dit : « Combien de temps me faudra-t-il pour économiser pour une maison ? » Mon outil `calculate_savings_timeline` nécessite `goal_amount`, `monthly_income`, et `monthly_savings`. L’Assistant, étant intelligent, réalisera souvent qu’il lui manque des informations. Le SDK, via l’objet `run`, vous indiquera qu’il est `requires_action`.

Mon approche initiale consistait à attendre le statut `requires_action` et à présenter ensuite une question générique « Quelle information avez-vous besoin ? » à l’utilisateur. C’était maladroit. L’utilisateur ne savait souvent pas quels paramètres l’outil nécessitait. Une meilleure approche, sur laquelle je suis finalement tombée d’accord, consistait à inspecter les `tool_calls` dans le statut `requires_action`.


def submit_tool_outputs(client, thread_id, run_id, tool_outputs):
 return client.beta.threads.runs.submit_tool_outputs(
 thread_id=thread_id,
 run_id=run_id,
 tool_outputs=tool_outputs
 )

# ... à l'intérieur de votre boucle de conversation ...

if run.status == 'requires_action':
 tool_outputs = []
 missing_params = {}
 for tool_call in run.required_action.submit_tool_outputs.tool_calls:
 function_name = tool_call.function.name
 arguments = json.loads(tool_call.function.arguments)

 # Voici le truc : vérifier si un paramètre requis est manquant
 # Cela suppose que vos outils sont définis avec un schéma
 # Pour simplifier, disons que nous savons que 'monthly_savings' est toujours nécessaire
 if function_name == "calculate_savings_timeline" and "monthly_savings" not in arguments:
 missing_params['monthly_savings'] = 'Combien prévoyez-vous d\'économiser chaque mois ?'
 
 # ... vérification de paramètres plus sophistiquée ici ...

 if missing_params:
 # Informer l'utilisateur de ce qui manque
 user_prompt = "J'ai besoin d'un peu plus d'informations pour vous aider. "
 for param, question in missing_params.items():
 user_prompt += f"{question} "
 return {"status": "waiting_for_user_input", "prompt": user_prompt}
 else:
 # Tous les paramètres sont présents, exécuter l'outil
 output = execute_tool_function(function_name, arguments) # Votre fonction pour exécuter la logique réelle de l'outil
 tool_outputs.append({
 "tool_call_id": tool_call.id,
 "output": json.dumps(output)
 })
 
 if tool_outputs:
 run = submit_tool_outputs(client, thread.id, run.id, tool_outputs)
 # Continuer à interroger l'exécution jusqu'à ce qu'elle soit terminée ou nécessite une nouvelle action
 else:
 # Ce cas gère si nous avons détecté des paramètres manquants et demandé à l'utilisateur
 pass

Ce code est une version simplifiée, mais l’idée principale est d’intercepter `requires_action`, de voir ce que l’Assistant veut faire, et s’il lui manque des arguments, de demander à l’utilisateur spécifiquement de les fournir. Cela rend la conversation beaucoup plus naturelle, comme un humain posant des questions de clarification, plutôt qu’une réponse générique « erreur ».

L’insaisissable état : suivre le fil des échanges

Un des plus gros casse-têtes était de s’assurer que si un utilisateur fournissait une pièce d’information (« Mon revenu est de 5 000 $ par mois »), l’Assistant s’en souviendrait pour les appels d’outils suivants au cours de la même conversation, même si cela n’était pas immédiatement utilisé. L’API Assistants gère cela assez bien au sein d’un `Thread` en conservant l’historique des messages. Cependant, parfois, vous souhaitez le guider explicitement ou lui donner des informations d’arrière-plan qui ne font pas partie d’un message direct de l’utilisateur.

Je me suis retrouvé à utiliser `metadata` sur les objets `Thread` et `Message` plus que je ne le pensais au départ. Par exemple, si l’utilisateur précise sa tolérance au risque, je pourrais stocker cela dans les métadonnées du thread. Bien que l’Assistant ne « lise » pas directement ces métadonnées dans son processus de raisonnement, elles sont inestimables pour ma logique d’application lorsque j’ai besoin de pré-remplir des arguments pour des appels d’outils ou de prendre des décisions sur les outils qui sont même pertinents.

Un autre modèle que j’ai adopté a été d’injecter des messages « de type système » dans le fil si je voulais rappeler explicitement à l’Assistant certains faits dérivés d’un appel d’outil ou d’un processus interne. Par exemple, après que `calculate_savings_timeline` a retourne qu’il faudrait 10 ans, je pourrais ajouter un message comme : `{“role”: “user”, “content”: “Le plan d’épargne de l’utilisateur indique un délai de 10 ans pour atteindre son objectif.”}`. Cela ne provient pas directement de l’utilisateur, mais cela aide à renforcer le contexte pour les réponses suivantes de l’Assistant.

Gestion des sorties d’outils et des actions suivantes

Lorsqu’un outil s’exécute, vous obtenez une sortie. L’Assistant prend ensuite cette sortie et génère une réponse. Mais que se passe-t-il si la sortie de l’outil appelle un nouvel ensemble de questions ou un nouvel appel d’outil ? Par exemple, l’outil `calculate_savings_timeline` pourrait retourner que l’objectif est inatteignable avec les économies actuelles. L’Assistant devrait idéalement suggérer : « Peut-être devrions-nous envisager des moyens d’augmenter vos économies mensuelles ou de réduire le montant de votre objectif. » Ce n’est pas seulement une question de sortie de texte ; il s’agit de l’enchaînement d’étapes logiques.

Le SDK de l’API Assistants gère cela de manière magnifique en maintenant l’objet `run` actif. Après avoir `submit_tool_outputs`, le `run` revient souvent à `in_progress` et peut ensuite générer un nouveau statut `requires_action` (pour un autre appel d’outil) ou `completed`. Ma boucle principale pour gérer le flux de conversation ressemble à quelque chose comme ceci :


def get_assistant_response(client, thread_id, assistant_id, user_message):
 client.beta.threads.messages.create(
 thread_id=thread_id,
 role="user",
 content=user_message
 )

 run = client.beta.threads.runs.create(
 thread_id=thread_id,
 assistant_id=assistant_id
 )

 while run.status in ['queued', 'in_progress', 'cancelling']:
 time.sleep(1) # Soyez un bon citoyen, ne surchargez pas l'API
 run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)

 if run.status == 'completed':
 messages = client.beta.threads.messages.list(thread_id=thread_id, order="desc")
 for msg in messages.data:
 if msg.role == "assistant":
 # Trouvez le dernier message de l'assistant, souvent le premier
 return msg.content[0].text.value # Supposons que le contenu soit du texte
 return "Pas de réponse de l'assistant."

 elif run.status == 'requires_action':
 tool_outputs = []
 for tool_call in run.required_action.submit_tool_outputs.tool_calls:
 function_name = tool_call.function.name
 arguments = json.loads(tool_call.function.arguments)
 
 # C'est ici que votre logique personnalisée pour gérer les paramètres manquants ou exécuter des outils entre en jeu
 # Pour cet exemple simplifié, supposons que tous les paramètres soient présents et que nous exécutons
 print(f"L'assistant veut appeler {function_name} avec les args : {arguments}")
 
 output = execute_tool_function(function_name, arguments) # Votre logique d'exécution des outils
 tool_outputs.append({
 "tool_call_id": tool_call.id,
 "output": json.dumps(output)
 })
 
 # Soumettez les résultats des outils et continuez l'exécution
 run = client.beta.threads.runs.submit_tool_outputs(
 thread_id=thread_id,
 run_id=run.id,
 tool_outputs=tool_outputs
 )
 # Appelez récursivement cette fonction pour traiter l'exécution mise à jour
 # Faites attention à la profondeur de récursion en production, vous pourriez avoir besoin d'une boucle
 return get_assistant_response(client, thread_id, assistant_id, "") # Passez un message vide
 
 elif run.status == 'failed':
 return f"L'exécution de l'assistant a échoué : {run.last_error.message}"
 
 else:
 return f"Statut d'exécution inattendu : {run.status}"

Cette fonction `get_assistant_response`, bien que simplifiée, montre la boucle principale. L’essentiel est que `requires_action` n’est pas une impasse. C’est une opportunité pour votre application d’intervenir, d’exécuter l’outil demandé, puis de renvoyer les résultats à l’assistant pour continuer son raisonnement. Ce retour d’information en boucle fermée est ce qui rend possibles de véritables conversations dynamiques et avec état.

Prendre en compte pour votre prochain projet d’assistant IA

  1. Acceptez le cycle de vie de `run.status` : Ne vérifiez pas seulement `completed`. `requires_action` est votre ami, pas une erreur. Construisez des gestionnaires solides pour chaque statut pertinent.
  2. Inspectez `tool_calls` pour les paramètres manquants : Au lieu de requêtes génériques, plongez dans `run.required_action.submit_tool_outputs.tool_calls` pour comprendre *exactement* ce dont l’assistant a besoin. Cela élève votre expérience utilisateur.
  3. Utilisation stratégique de messages de type système : Si votre logique d’application interne dérive de nouveaux faits ou a besoin de souligner un certain contexte, envisagez d’injecter des messages « utilisateur » qui représentent ces faits. L’assistant les traitera comme partie de l’historique de la conversation.
  4. Métadonnées pour l’état de l’application : Bien que l’assistant ne puisse pas directement « lire » les métadonnées des fils ou des messages pour son raisonnement, c’est un endroit puissant pour que votre application stocke et récupère l’état pertinent à la conversation. Pensez aux préférences utilisateur, aux variables de session actuelles, etc.
  5. Tester les cas extrêmes pour l’orchestration des outils : Que se passe-t-il si un outil échoue ? Que se passe-t-il si l’utilisateur change d’avis au milieu de l’invocation d’un outil ? Concevez votre `execute_tool_function` et la gestion des erreurs au sein de la boucle `requires_action` avec soin.

Construire de véritables agents IA conversationnels intelligents ne concerne pas seulement le choix du dernier modèle. Il s’agit de manier habilement les SDK qu’ils fournissent pour gérer les complexités de l’interaction humaine. L’API Assistants d’OpenAI, avec un peu d’ingénierie réfléchie autour de sa gestion d’état et de l’orchestration des outils, peut vraiment élever votre expérience de conversation au-delà du simple échange de répliques. C’est encore un voyage, et j’apprends toujours de nouvelles astuces, mais j’espère que ces informations vous permettront d’économiser quelques nuits tardives et des cafés froids !

Articles connexes

🕒 Published:

🧰
Written by Jake Chen

Software reviewer and AI tool expert. Independently tests and benchmarks AI products. No sponsored reviews — ever.

Learn more →
Browse Topics: AI & Automation | Comparisons | Dev Tools | Infrastructure | Security & Monitoring
Scroll to Top