flowchart LR
A[Appel entrant] --> B[Prompt + contexte brut]
B --> C[LLM OpenAI]
C --> D[Réponse vocale]
Mon agent vocal était nul (et je ne savais pas pourquoi)
Je bosse sur un agent vocal connecté à des outils métier : planning, historique utilisateur, géolocalisation. Le principe : un LLM (OpenAI Realtime API) qui gère la conversation au téléphone et qui doit prendre des décisions en temps réel. Vérifier des disponibilités, calculer un trajet, confirmer un rendez-vous.
Techniquement, ça marchait. L’agent parlait, répondait, consultait les données. Mais en pratique, c’était médiocre. Un utilisateur rappelait, l’agent repartait de zéro. Je balançais du contexte brut dans le prompt, le modèle noyait l’info utile dans le bruit. Et surtout, je n’arrivais pas à comprendre pourquoi il prenait telle décision plutôt qu’une autre. C’était une boîte noire.
J’empilais des appels API sans architecture. Je ne comprenais pas la mécanique des agents : le cycle de décision, le routing, le retrieval. Je savais que ça existait, mais je n’avais pas de modèle mental pour organiser tout ça.
C’est dans ce contexte que j’ai suivi la formation LangChain: Develop AI Agents with LangChain & LangGraph d’Eden Marco. 19h de contenu, code accessible commit par commit sur GitHub.

Ce qui suit n’est pas un résumé du cours. C’est ce qui a concrètement changé ma façon de construire cet agent.
Le déclic : comprendre que l’agent est une boucle
Le truc qui m’a débloqué, c’est de comprendre le pattern ReAct (Reasoning + Acting). Avant, mon architecture était linéaire :
Le LLM recevait tout en une fois et devait se débrouiller. Pas de boucle de décision. Pas de tools.
Avec ReAct, le LLM ne génère plus une réponse directe. Il raisonne sur ce qu’il doit faire, agit via un tool, observe le résultat, et décide s’il a assez d’information pour répondre. Sinon il recommence.
flowchart TD
A[Question utilisateur] --> B[LLM raisonne]
B --> C{Besoin d'un tool ?}
C -->|Oui| D[Appel tool]
D --> E[Observation du résultat]
E --> B
C -->|Non| F[Réponse finale]
style B fill:#f9f,stroke:#333
style D fill:#bbf,stroke:#333
style F fill:#bfb,stroke:#333
Dit comme ça, c’est simple. Mais quand tu ouvres LangSmith et que tu vois les 5 tool calls enchaînés avec leurs arguments, tu comprends enfin ce qui se passe entre la question de l’utilisateur et la réponse de l’agent. C’est une boucle while, pas de la magie.
Le cours montre aussi l’évolution des agents LangChain, du ReAct prompt-based (fragile, parsing texte) au function calling natif, puis à LangGraph et create_agent(). Ce qui m’a été utile : comprendre que le ReAct d’origine est important pour l’intuition, mais qu’en production c’est LangGraph qui tient la route.
Mon pipeline RAG était cassé et je ne le savais pas
C’est la partie du cours qui m’a le plus servi. Le cours montre l’indexation de 6 500 documents dans Pinecone. Eden Marco retire volontairement le retry sur le modèle d’embeddings et relance. Résultat : erreur 429, rate limiting. Des batches entiers échouent silencieusement.
Le symptôme, c’est pas un crash. C’est un index à moitié rempli qui produit des réponses incohérentes. Tu cherches le bug côté prompt alors qu’il est côté ingestion.
sequenceDiagram
participant App as Pipeline
participant Emb as Modèle Embeddings
participant Vec as Pinecone
App->>Emb: Batch 1 (500 docs)
Emb-->>App: Vecteurs OK
App->>Vec: Upsert batch 1
App->>Emb: Batch 2 (500 docs)
Emb-->>App: Vecteurs OK
App->>Vec: Upsert batch 2
App->>Emb: Batch 3 (500 docs)
Emb--xApp: 429 Rate Limit
Note over App: Sans retry : batch perdu<br/>Index incomplet
En voyant ça, j’ai immédiatement reconnu mon problème. Mon pipeline d’ingestion envoyait tout d’un bloc, sans retry, sans validation du nombre de batches réussis. Je suis passé à du batching asynchrone avec backoff et vérification. La qualité des réponses de mon agent a changé du jour au lendemain.
L’autre concept utile : le routing adaptatif. Toutes les questions ne relèvent pas du même pipeline. “Quel est l’historique de ce client ?” c’est du vector store. “Est-ce qu’il y a un créneau demain à 8h ?” c’est une API temps réel. Le cours implémente ça avec un LLM router et du structured output Pydantic :
class RouteQuery(BaseModel):
datasource: Literal["vectorstore", "web_search"] = Field(
...,
description="Route vers vectorstore ou web_search"
)
llm_router = llm.with_structured_output(RouteQuery)Un piège que le cours montre en live : le LLM peut décider de ne pas remplir certains champs du schéma Pydantic. Le parser crash, l’agent s’arrête. Aucune des solutions (prompt plus directif, champ optionnel, chaîne découpée) n’est dans la doc officielle.
La leçon la plus contre-intuitive : les docstrings comptent plus que le code
Quand tu décores une fonction avec @tool, LangChain extrait le nom, les types, et la docstring, puis transmet tout au LLM via function calling. Le LLM utilise ces métadonnées pour décider s’il appelle le tool et avec quels arguments.
J’ai passé 45 minutes à debugger un tool que l’agent n’appelait jamais. Je cherchais un bug dans le code. Le problème : la docstring disait "Searches the internet". Trop vague. Le LLM ne comprenait pas quand l’utiliser.
Le cours compare un tool custom avec le tool officiel Tavily, qui a des arguments riches (include_domains, search_depth) et des descriptions précises. La différence de qualité dans les tool calls est immédiate.
Ce que j’ai changé dans mon agent :
- Des descriptions explicites sur chaque tool, avec des exemples d’usage dans la docstring
- Des types précis (
Literal,Optional, contraintes) au lieu destrpartout - Des tools spécialisés par domaine au lieu d’un tool fourre-tout
C’est exactement comme une API REST : le nom, les types, la description forment un contrat. Sauf qu’ici le consommateur est un LLM. Et un LLM ne lève pas d’exception quand le contrat est vague, il improvise.
LangSmith : je ne debugguerai plus jamais sans
Avant LangSmith, mon agent était une boîte noire. Question dedans, réponse dehors, mystère entre les deux.
Avec le tracing, je vois chaque itération de la boucle ReAct, chaque tool call avec ses arguments, le prompt exact envoyé à chaque étape, le temps et les tokens consommés.
Exemple concret : mon agent appelait 5 fois le même tool avec des variations mineures de la query. Sans LangSmith, j’aurais jamais trouvé que le problème venait de la docstring ambiguë qui poussait le LLM à reformuler sa recherche au lieu de passer au tool suivant.
C’est comme les logs structurés pour les microservices. Techniquement optionnel, en pratique indispensable.
En vrac : ce que le cours couvre aussi
MCP (Model Context Protocol) : un standard pour connecter des agents à des services externes via un protocole commun. Au lieu d’écrire un tool custom par service, le serveur MCP expose ses capacités et le client les découvre. C’est encore jeune, mais pour mon agent ça pourrait remplacer mes intégrations custom.
Interchangeabilité des modèles : changer de LLM tient en une ligne avec LangChain. Mais le cours montre honnêtement que les modèles légers (Gemma 1B via Ollama) ne suivent pas les instructions complexes. Pour du function calling fiable, il faut rester sur GPT-5, Claude ou Gemini.
LCEL : la syntaxe pipe prompt | llm | parser est élégante mais opaque. L’instructeur lui-même admet que c’est “la chose la plus difficile à comprendre dans LangChain”. Sans LangSmith pour voir ce qui passe d’un maillon à l’autre, c’est du débugage à l’aveugle.
Les vrais pièges (que j’aurais aimé connaître avant)
- Rate limiting silencieux sur les embeddings : les batches échouent, l’index est incomplet, les réponses sont incohérentes. Pas de crash, pas de warning. Tu peux tourner en rond pendant des heures.
- Structured output incomplet : le LLM peut décider de ne pas remplir un champ requis Pydantic. Le parser crash sans fallback. Il faut anticiper.
- Docstrings vagues = agent perdu : tu cherches un bug côté code alors que le problème est côté description du tool. C’est le piège le plus vicieux parce que rien ne plante, l’agent fait juste n’importe quoi.
- Versioning LangChain : les providers sont découplés en packages séparés. Bon pour la maintenance, pénible quand une mise à jour casse la compatibilité.
Ce qui a concrètement changé
Avant cette formation, j’utilisais l’API OpenAI comme un tuyau : données dedans, réponse dehors. Maintenant j’ai un cadre.
Le RAG structuré (batching, retry, routing adaptatif) a directement amélioré la qualité des réponses de mon agent vocal. Le pattern ReAct m’a donné une architecture pour la logique de décision. Les docstrings précises sur les tools ont résolu des bugs que je ne comprenais pas. Et LangSmith m’a donné la visibilité qui me manquait pour itérer.
Ce qui rend cette formation utile, c’est qu’Eden Marco filme sans filet. Le rate limiting qui crash, le Pydantic parser qui échoue, le modèle léger qui ignore les instructions : tout est montré en live. C’est dans ces moments-là qu’on apprend, pas dans les slides.
Si tu utilises déjà des LLMs via API et que tu veux comprendre comment structurer tes agents au-delà du prompt engineering, c’est un bon point de départ.

LangChain: Develop AI Agents with LangChain & LangGraph sur Udemy