LangGraph
Cette page explique comment instrumenter un LangGraphagent, comment les intervalles sont identifiés et comment les champs d'évaluation sont extraits. Il se termine par les meilleures pratiques pour structurer un LangGraph agent afin qu'il puisse être évalué de manière fiable.
Rubriques
Instrumez votre agent
Vous pouvez instrumenter un LangGraph agent avec l'une des deux bibliothèques d'instrumentation suivantes : OpenTelemetry(opentelemetry-instrumentation-langchain) ou OpenInference(openinference-instrumentation-langchain). Amazon Bedrock AgentCore Evaluations prend en charge les deux bibliothèques. Les bibliothèques émettent des noms de portée différents et utilisent des attributs d'étendue différents. Le service d'évaluation extrait les mêmes valeurs de chacun d'entre eux.
Lorsque votre agent s'exécute avec le AWS Distro for OpenTelemetry (ADOT), par exemple sur Amazon Bedrock AgentCore Runtime, vous n'avez pas besoin d'ajouter de code d'instrumentation explicite. Il suffit d'ajouter la bibliothèque d'instrumentation aux dépendances de votre projet. ADOT le découvre au démarrage et l'active automatiquement.
Ajoutez la bibliothèque d'instrumentation correspondant au chemin que vous souhaitez accéder à vos dépendances. Les exemples suivants épinglent une version minimale ; utilisez la dernière version disponible sauf si vous avez une raison de l'épingler.
Exemple
- OpenTelemetry
-
REMARQUE : Utilisez la version 0.55.0 ou une version ultérieure. La version 0.55.0 a ajouté la prise en charge des nouvelles conventions de durée des agents d' OpenTelemetry IA générative sur lesquelles repose le service d'évaluation.
Ajoutez opentelemetry-instrumentation-langchain à vos dépendances. Le nom de la portée émis estopentelemetry.instrumentation.langchain.
requirements.txt:
opentelemetry-instrumentation-langchain>=0.55.0
pyproject.toml:
[project]
dependencies = [
"opentelemetry-instrumentation-langchain>=0.55.0",
]
- OpenInference
-
Ajoutez openinference-instrumentation-langchain à vos dépendances. Le nom de la portée émis estopeninference.instrumentation.langchain.
requirements.txt:
openinference-instrumentation-langchain>=0.1.62
pyproject.toml:
[project]
dependencies = [
"openinference-instrumentation-langchain>=0.1.62",
]
L'instrumentation est l'une des étapes de la mise en place de l'observabilité. Pour exporter la télémétrie à des fins d'évaluation, effectuez la configuration complète dans Configurer l'observabilité.
Comment les travées sont identifiées
L'attribut utilisé pour classer les intervalles diffère entre les deux bibliothèques d'instrumentation.
Exemple
- OpenTelemetry
-
La bibliothèque OpenTelemetry d'instrumentation classe les travées à l'aide de l'traceloop.span.kindattribut, et les versions récentes sont également définies. gen_ai.operation.name
| Type de travée |
Attribut d'identification |
|
Invoquer l'agent
|
traceloop.span.kind= workflow (également gen_ai.operation.name =invoke_agent)
|
|
Exécuter l'outil
|
traceloop.span.kind= tool (également gen_ai.operation.name =execute_tool)
|
|
Inférence
|
gen_ai.operation.name = chat
|
- OpenInference
-
La bibliothèque OpenInference d'instrumentation classe les travées à l'aide de l'openinference.span.kindattribut.
| Type de travée |
Attribut d'identification |
|
Invoquer l'agent
|
openinference.span.kind= CHAIN ou AGENT
|
|
Exécuter l'outil
|
openinference.span.kind = TOOL
|
|
Inférence
|
openinference.span.kind = LLM
|
En ce qui concerne la durée d'appel de l'agent, l'entrée et la sortie ne contiennent pas de liste propre par message. Le contenu est plutôt l'état du LangChain graphe sérialisé : une chaîne JSON qui enveloppe l'état complet. La forme exacte de cet état sérialisé diffère entre les deux bibliothèques d'instrumentation. Dans les deux cas, le service l'analyse pour trouver l'invite de l'utilisateur (le message humain) et la réponse de l'agent (le message AI).
LangGraph sérialise également les rôles de message sous plusieurs formes. Un rôle peut apparaître sous forme de valeur minuscule (human,ai,tool) ou de nom de classe de LangChain message (HumanMessage,AIMessage,ToolMessage). Le service reconnaît les deux formulaires.
L'emplacement de ce contenu dépend de la manière dont la télémétrie a été collectée. L'attribut d'identification (traceloop.span.kindouopeninference.span.kind) se trouve sur la plage dans les deux cas. Pour plus d'informations, voir Spans, enregistrements d'événements et signaux de télémétrie.
Lorsque la télémétrie est divisée, le service lit le contenu de l'enregistrement d'événements corrélé à chaque intervalle :
-
Demande de l'utilisateur et réponse de l'agent : à partir de l'enregistrement des événements de l'intervalle d'appel de l'agent, dans body.input etbody.output.
-
Appel à l'outil : nom de l'outil issu de la plage d'outils d'exécution. Les arguments et le résultat de l'outil proviennent de l'enregistrement des événements de cette plage, dans body.input etbody.output.
Pour des exemples, voir Exemples de périodes avec enregistrements d'événements.
Lorsque la télémétrie n'est pas divisée, le même contenu reste sur la plage que les attributs. Les attributs dépendent de la bibliothèque d'instrumentation :
-
OpenTelemetry:
-
Demande de l'utilisateur et réponse de l'agent : depuis gen_ai.task.input et gen_ai.task.output pendant la durée d'appel de l'agent.
-
Appel à l'outil : nom de l'outil à partir degen_ai.tool.name, arguments et résultats de gen_ai.tool.call.arguments etgen_ai.tool.call.result, sur la durée d'exécution de l'outil.
-
OpenInference:
-
Demande de l'utilisateur et réponse de l'agent : depuis input.value et output.value pendant la durée d'appel de l'agent.
-
Appel à l'outil : nom de l'outil à partir detool.name, arguments et résultats de input.value etoutput.value, sur la durée d'exécution de l'outil.
Pour des exemples, voir Exemples de périodes sans enregistrement d'événements.
Exemples de périodes avec des enregistrements d'événements
Lorsque la télémétrie est divisée, la plage contient les attributs d'identification et le contenu est enregistré dans un enregistrement d'événements corrélé. Les exemples suivants proviennent d'un agent de LangGraph planification de voyages déployé sur Amazon AgentCore Bedrock Runtime. Le même agent est affiché sous chaque bibliothèque d'instruments.
Ces exemples ne sont pas des étendues complètes. Ils présentent des données représentatives d'une interaction réelle avec un agent, certains champs étant omis et les valeurs longues tronquées pour des raisons de lisibilité.
OpenTelemetry
Exemple
- Invoke agent span
-
L'traceloop.span.kindattribut (workflow) l'identifie comme une durée d'appel de l'agent ; les versions récentes de la bibliothèque ont également pour valeur gen_ai.operation.name =invoke_agent.
{
"traceId": "6a01eef11066751d68f90def0da1f80a",
"spanId": "ba1833fa7f097041",
"parentSpanId": "836a5ccf9a2186cc",
"name": "travel_agent.workflow",
"kind": "INTERNAL",
"scope": {
"name": "opentelemetry.instrumentation.langchain",
"version": "0.60.0"
},
"startTimeUnixNano": 1778511607308521744,
"endTimeUnixNano": 1778511610930280395,
"durationNano": 3621758651,
"attributes": {
"traceloop.span.kind": "workflow",
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.name": "travel_agent",
"gen_ai.provider.name": "langgraph",
"traceloop.workflow.name": "travel_agent",
"session.id": "sea-nyc-trip-2-turns-adot_v17_opentelemetry_0_60_0"
},
"status": {
"code": "OK"
}
}
L'enregistrement d'événements corrélé contient la conversation. Chaque message correspond à l'état du LangChain graphe sérialisé. content L'entrée place l'état sous une inputs clé. La sortie l'enveloppe sous une outputs clé, chaque message étant un objet LangChain constructeur. L'invite de l'utilisateur est le message humain et la réponse de l'agent est le message AI contenu dans cet état sérialisé.
{
"spanId": "ba1833fa7f097041",
"traceId": "6a01eef11066751d68f90def0da1f80a",
"scope": {
"name": "opentelemetry.instrumentation.langchain"
},
"body": {
"input": {
"messages": [
{
"content": "{\"inputs\": {\"messages\": [{\"role\": \"user\", \"content\": \"Hey, how can you help me\"}]}, \"tags\": [], \"metadata\": {\"ls_integration\": \"langchain_create_agent\", \"lc_agent_name\": \"travel_agent\", \"thread_id\": \"sea-nyc-trip-2-turns-adot_v17_opentelemetry_0_60_0\"}, \"kwargs\": {\"name\": \"travel_agent\"}}",
"role": "user"
}
]
},
"output": {
"messages": [
{
"content": "{\"outputs\": {\"messages\": [{\"lc\": 1, \"type\": \"constructor\", \"id\": [\"langchain\", \"schema\", \"messages\", \"HumanMessage\"], \"kwargs\": {\"content\": \"Hey, how can you help me\", \"type\": \"human\", \"id\": \"12345678-1234-1234-1234-123456789012\"}}, {\"lc\": 1, \"type\": \"constructor\", \"id\": [\"langchain\", \"schema\", \"messages\", \"AIMessage\"], \"kwargs\": {\"content\": \"Hello! I'm your travel planning assistant ...\", \"type\": \"ai\"}}]}, \"kwargs\": {\"tags\": []}}",
"role": "assistant"
}
]
}
}
}
- Execute tool span
-
L'traceloop.span.kindattribut (tool) l'identifie comme une plage d'outils d'exécution ; gen_ai.tool.name contient le nom de l'outil et gen_ai.operation.name =execute_tool.
{
"traceId": "6a01eefa5c52f3d86a35038f35f5ba30",
"spanId": "5b332f3cd15ace04",
"parentSpanId": "922a21edc04eba29",
"name": "execute_tool search_flights",
"kind": "INTERNAL",
"scope": {
"name": "opentelemetry.instrumentation.langchain",
"version": "0.60.0"
},
"startTimeUnixNano": 1778511614892698232,
"endTimeUnixNano": 1778511614893399618,
"durationNano": 701386,
"attributes": {
"traceloop.span.kind": "tool",
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.name": "search_flights",
"gen_ai.tool.type": "function",
"gen_ai.tool.description": "Search for available flights between cities.",
"gen_ai.provider.name": "langgraph",
"traceloop.workflow.name": "travel_agent",
"session.id": "sea-nyc-trip-2-turns-adot_v17_opentelemetry_0_60_0"
},
"status": {
"code": "OK"
}
}
L'enregistrement d'événement corrélé contient l'entrée (arguments) et la sortie (résultat, sérialisé sous forme de a LangChain ToolMessage) de l'outil.
{
"spanId": "5b332f3cd15ace04",
"traceId": "6a01eefa5c52f3d86a35038f35f5ba30",
"scope": {
"name": "opentelemetry.instrumentation.langchain"
},
"body": {
"input": {
"messages": [
{ "content": "{\"origin\": \"SEA\", \"destination\": \"NYC\", \"date\": \"2025-03-15\"}" }
]
},
"output": {
"messages": [
{
"role": "tool",
"name": "search_flights",
"content": "{\"origin\": \"SEA\", \"destination\": \"NYC\", \"flights\": [ ... ]}"
}
]
}
}
}
OpenInference
Avec la OpenInference bibliothèque, le type d'intervalle est inclus dans l'openinference.span.kindattribut, et les entrées et sorties de l'agent sont sérialisées dans l'enregistrement d'événements corrélé.
Exemple
- Invoke agent span
-
L'openinference.span.kindattribut (CHAINou AGENT lorsque le graphe est compilé avec un nom) l'identifie comme un span d'agent d'appel.
{
"traceId": "6a387ee61078243c1cc455ed45c6c313",
"spanId": "0a7990d804132a9b",
"parentSpanId": "29ae22014173881c",
"name": "LangGraph",
"kind": "INTERNAL",
"scope": {
"name": "openinference.instrumentation.langchain",
"version": "0.1.66"
},
"startTimeUnixNano": 1782087405949310976,
"endTimeUnixNano": 1782087408945828864,
"durationNano": 2996517888,
"attributes": {
"openinference.span.kind": "CHAIN",
"input.mime_type": "application/json",
"output.mime_type": "application/json",
"llm.input_messages.0.message.role": "user",
"session.id": "sea-nyc-trip-2-turns-oi-0-1-66"
},
"status": {
"code": "OK"
}
}
L'enregistrement d'événements corrélé contient la conversation. L'invite de l'utilisateur est le message de rôle humain et la réponse de l'agent est le AI-role message contenu dans les messages sérialisés.
{
"spanId": "0a7990d804132a9b",
"traceId": "6a387ee61078243c1cc455ed45c6c313",
"scope": {
"name": "openinference.instrumentation.langchain"
},
"body": {
"input": {
"messages": [
{
"role": "user",
"content": "{\"messages\": [{\"role\": \"user\", \"content\": \"Hey, how can you help me\"}]}"
}
]
},
"output": {
"messages": [
{
"content": "{\"messages\": [{\"type\": \"human\", \"data\": {\"content\": \"Hey, how can you help me\", ...}}, {\"type\": \"ai\", \"data\": {\"content\": \"Hello! I'm your travel planning assistant ...\", ...}}]}",
"role": "assistant"
}
]
}
}
}
- Execute tool span
-
L'openinference.span.kindattribut (TOOL) l'identifie comme une plage d'outils d'exécution ; tool.name contient le nom de l'outil.
{
"traceId": "6a387ef07b8f4f3732fab45d3c0b51ff",
"spanId": "ab105c12cc40048f",
"parentSpanId": "9b2d4e72760690b4",
"name": "search_flights",
"kind": "INTERNAL",
"scope": {
"name": "openinference.instrumentation.langchain",
"version": "0.1.66"
},
"startTimeUnixNano": 1782087411724620032,
"endTimeUnixNano": 1782087411725306880,
"durationNano": 686848,
"attributes": {
"openinference.span.kind": "TOOL",
"tool.name": "search_flights",
"tool.description": "Search for available flights between cities.",
"input.mime_type": "application/json",
"output.mime_type": "application/json",
"session.id": "sea-nyc-trip-2-turns-oi-0-1-66"
},
"status": {
"code": "OK"
}
}
L'enregistrement d'événement corrélé contient l'entrée (arguments) et la sortie (résultat, sérialisé sous forme de a LangChain ToolMessage) de l'outil.
{
"spanId": "ab105c12cc40048f",
"traceId": "6a387ef07b8f4f3732fab45d3c0b51ff",
"scope": {
"name": "openinference.instrumentation.langchain"
},
"body": {
"input": {
"messages": [
{
"role": "user",
"content": "{\"origin\": \"SEA\", \"destination\": \"NYC\", \"date\": \"2025-03-15\"}"
}
]
},
"output": {
"messages": [
{
"content": "{\"type\": \"tool\", \"data\": {\"content\": \"{\\\"origin\\\": \\\"SEA\\\", \\\"destination\\\": \\\"NYC\\\", \\\"flights\\\": [ ... ]}\", \"type\": \"tool\", \"name\": \"search_flights\", \"tool_call_id\": \"toolu_bdrk_01LzXXJCfpfuS7Bpf7e1qLMg\", \"status\": \"success\"}}",
"role": "assistant"
}
]
}
}
}
Exemples de périodes sans enregistrement d'événements
Lorsque la télémétrie n'est pas divisée, le même contenu reste dans les attributs span et aucun enregistrement d'événement distinct n'est produit. Les exemples suivants proviennent d'un agent de LangGraph planification de voyages. Le même agent est affiché sous chaque bibliothèque d'instruments.
Ces exemples ne sont pas des étendues complètes. Ils présentent des données représentatives d'une interaction réelle avec un agent, certains champs étant omis et les valeurs longues tronquées pour des raisons de lisibilité.
OpenTelemetry
Exemple
- Invoke agent span
-
L'gen_ai.task.inputattribut contient l'invite de l'utilisateur et l'gen_ai.task.outputattribut contient l'état sérialisé avec la réponse de l'agent. Les deux sont l'état du LangChain graphe sérialisé.
{
"traceId": "6a4de7b85e61747e6b568a1f4768e89d",
"spanId": "31ea3d5882dac680",
"name": "LangGraph.workflow",
"kind": "INTERNAL",
"scope": {
"name": "opentelemetry.instrumentation.langchain",
"version": "0.62.1"
},
"attributes": {
"traceloop.span.kind": "workflow",
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.name": "LangGraph",
"gen_ai.task.input": "{\"inputs\": {\"messages\": [{\"role\": \"user\", \"content\": \"Hey, how can you help me\"}]}, \"tags\": [], \"metadata\": { ... }, \"kwargs\": {\"name\": \"LangGraph\"}}",
"gen_ai.task.output": "{\"outputs\": {\"messages\": [{\"lc\": 1, \"type\": \"constructor\", \"id\": [\"langchain\", \"schema\", \"messages\", \"HumanMessage\"], \"kwargs\": {\"content\": \"Hey, how can you help me\", \"type\": \"human\"}}, {\"lc\": 1, \"type\": \"constructor\", \"id\": [\"langchain\", \"schema\", \"messages\", \"AIMessage\"], \"kwargs\": {\"content\": \"Hello! I'm your travel planning assistant ...\", \"type\": \"ai\"}}]}, \"kwargs\": {\"tags\": []}}",
"session.id": "sea-nyc-trip-2-turns-unified"
},
"status": {
"code": "OK"
}
}
- Execute tool span
-
L'gen_ai.tool.call.argumentsattribut contient les arguments de l'outil et l'gen_ai.tool.call.resultattribut contient le résultat de l'outil, sérialisé sous la forme d'un LangChain ToolMessage.
{
"traceId": "6a4de7c376913db82e6f0f336a16731d",
"spanId": "b64c37adefae74f0",
"name": "execute_tool search_flights",
"kind": "INTERNAL",
"scope": {
"name": "opentelemetry.instrumentation.langchain",
"version": "0.62.1"
},
"attributes": {
"traceloop.span.kind": "tool",
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.name": "search_flights",
"gen_ai.tool.description": "Search for available flights between cities.",
"gen_ai.tool.call.arguments": "{\"input_str\": \"{'origin': 'SEA', 'destination': 'NYC', 'date': '2025-03-15'}\", \"inputs\": {\"origin\": \"SEA\", \"destination\": \"NYC\", \"date\": \"2025-03-15\"}, \"metadata\": { ... }}",
"gen_ai.tool.call.result": "{\"output\": {\"lc\": 1, \"type\": \"constructor\", \"id\": [\"langchain\", \"schema\", \"messages\", \"ToolMessage\"], \"kwargs\": {\"content\": \"{\\\"origin\\\": \\\"SEA\\\", \\\"destination\\\": \\\"NYC\\\", \\\"flights\\\": [ ... ]}\", \"type\": \"tool\", \"name\": \"search_flights\", \"status\": \"success\"}}}",
"session.id": "sea-nyc-trip-2-turns-unified"
},
"status": {
"code": "OK"
}
}
OpenInference
Exemple
- Invoke agent span
-
L'input.valueattribut contient l'invite de l'utilisateur et l'output.valueattribut contient l'état sérialisé avec la réponse de l'agent.
{
"traceId": "6a387ee61078243c1cc455ed45c6c313",
"spanId": "b8c0b67876b78b91",
"name": "LangGraph",
"kind": "INTERNAL",
"scope": {
"name": "openinference.instrumentation.langchain",
"version": "0.1.66"
},
"attributes": {
"openinference.span.kind": "CHAIN",
"input.value": "{\"messages\": [{\"role\": \"user\", \"content\": \"Hey, how can you help me\"}]}",
"output.value": "{\"messages\": [{\"type\": \"human\", \"data\": {\"content\": \"Hey, how can you help me\"}}, {\"type\": \"ai\", \"data\": {\"content\": \"Hello! I'm your travel planning assistant ...\"}}]}",
"session.id": "sea-nyc-trip-2-turns-oi-0-1-66"
},
"status": {
"code": "OK"
}
}
- Execute tool span
-
L'input.valueattribut contient les arguments de l'outil et l'output.valueattribut contient le résultat de l'outil, sérialisé sous la forme d'un LangChain ToolMessage.
{
"traceId": "6a387ef07b8f4f3732fab45d3c0b51ff",
"spanId": "58752612d9b22ae1",
"name": "search_flights",
"kind": "INTERNAL",
"scope": {
"name": "openinference.instrumentation.langchain",
"version": "0.1.66"
},
"attributes": {
"openinference.span.kind": "TOOL",
"tool.name": "search_flights",
"tool.description": "Search for available flights between cities.",
"input.value": "{\"origin\": \"SEA\", \"destination\": \"NYC\", \"date\": \"2025-03-15\"}",
"output.value": "{\"type\": \"tool\", \"data\": {\"content\": \"{\\\"origin\\\": \\\"SEA\\\", \\\"destination\\\": \\\"NYC\\\", \\\"flights\\\": [ ... ]}\", \"name\": \"search_flights\"}}",
"session.id": "sea-nyc-trip-2-turns-oi-0-1-66"
},
"status": {
"code": "OK"
}
}
Bonnes pratiques pour les LangGraph agents
La façon dont vous créez et invoquez un LangGraph agent influe sur ce qui apparaît dans sa télémétrie, et donc sur la fiabilité de l'évaluation de l'agent. Les pratiques suivantes permettent de garantir que l'invite de l'utilisateur, la réponse de l'agent et l'activité de l'outil sont récupérables.
1. Choisissez un modèle de construction de l'agent
Il existe deux méthodes courantes pour créer un LangGraph agent :
-
Préconfiguré create_agent : le moyen le plus rapide de démarrer. Il produit un seul intervalle d'appel par tour, la conversation passant par LangGraph la boucle d'exécution intégrée. Utilisez-le lorsque vous souhaitez un agent raisonné standard sans flux de contrôle personnalisé.
from langchain.agents import create_agent
agent = create_agent(model=model, tools=[search_flights, book_flight])
-
Personnalisé StateGraph : vous donne un contrôle total sur les nœuds, les arêtes et le routage conditionnel. Chaque exécution de nœud devient sa propre étendue, de sorte que les traces sont plus granulaires. Utilisez-le lorsque vous avez besoin d'une orchestration personnalisée.
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
class State(TypedDict):
messages: list
graph = StateGraph(State)
graph.add_node("generate_response", generate_response)
graph.add_node("tools", run_tools)
graph.add_edge(START, "generate_response")
agent = graph.compile()
Les deux modèles sont évalués de la même manière ; la différence réside dans la granularité du tracé.
2. Utiliser des messages dans l'état de votre graphe (recommandé)
Le service d'évaluation reconstruit la conversation à partir des messages d'entrée et de sortie de l'agent. L'utilisation d'un messages champ n'est pas obligatoire, mais elle permet l'extraction la plus fiable possible. Pour une personnalisationStateGraph, conservez la conversation dans un messages champ de votre État :
-
messagesÀ inclure dans votre État (recommandé). Vous pouvez ajouter d'autres champs personnalisés (tels que user_id des métadonnées). Lorsqu'elle messages est présente, l'extraction standard trouve directement l'invite de l'utilisateur et la réponse de l'agent. En cas messages d'absence, le service se contente de reconstruire la conversation à partir de périodes d'inférence individuelles, ce qui est moins fiable.
-
Ajoutez, ne remplacez pas. Suivez la LangGraph convention qui consiste à ajouter de nouveaux messages à la liste plutôt que de les remplacer, afin de préserver l'historique complet des conversations.
-
Utilisez des types de LangChain messages canoniques (HumanMessage,AIMessage,ToolMessage,SystemMessage). L'instrumentation les sérialise correctement et le service reconnaît leurs rôles.
3. Transmettre le message de l'utilisateur dans un format compatible
Lorsque vous appelez un LangGraph agent, vous ajoutez le message utilisateur à l'messagesétat du graphe. LangGraph accepte le message dans trois formats interchangeables, et AgentCore Evaluations les prend tous en charge. Chacun produit des intervalles et des enregistrements d'événements que le service peut lire.
-
Tuple : une (role, content) paire :
agent.invoke({"messages": [("user", user_message)]}, config=config)
-
LangChain objet de message : a HumanMessage (ou autre classe de message) :
from langchain_core.messages import HumanMessage
agent.invoke({"messages": [HumanMessage(content=user_message)]}, config=config)
-
Dictionnaire : un {"role", "content"} dictionnaire :
agent.invoke({"messages": [{"role": "user", "content": user_message}]}, config=config)
Les trois formats aboutissent au même messages état, de sorte que l'invite de l'utilisateur et la réponse de l'agent sont extraites de la même manière, quel que soit le format choisi.