LangGraph
En esta página se explica cómo instrumentar a un LangGraphagente, cómo se identifican los intervalos y cómo se extraen los campos de evaluación. Termina con las mejores prácticas para estructurar un LangGraph agente de modo que pueda evaluarse de forma fiable.
Temas
Instrumente a su agente
Puede instrumentar un LangGraph agente con cualquiera de las dos bibliotecas de instrumentación: OpenTelemetry(opentelemetry-instrumentation-langchain) o OpenInference(openinference-instrumentation-langchain). Amazon Bedrock AgentCore Evaluations es compatible con ambas bibliotecas. Las bibliotecas emiten distintos nombres de ámbito y utilizan distintos atributos de ámbito. El servicio de evaluación extrae los mismos valores de cada uno.
Cuando su agente utiliza la AWS Distro for OpenTelemetry (ADOT), como en Amazon Bedrock AgentCore Runtime, no necesita añadir código de instrumentación explícito. Basta con añadir la biblioteca de instrumentación a las dependencias de su proyecto. ADOT la descubre al inicio y la activa automáticamente.
Añada la biblioteca de instrumentación de la ruta que desee a sus dependencias. Los siguientes ejemplos fijan una versión mínima; usa la última versión disponible a menos que tengas un motivo para marcarla.
ejemplo
- OpenTelemetry
-
NOTA: Utilice la versión 0.55.0 o posterior. La versión 0.55.0 agregó compatibilidad con las nuevas convenciones OpenTelemetry generativas de intervalo de agentes de IA en las que se basa el servicio de evaluación.
Añada opentelemetry-instrumentation-langchain a sus dependencias. El nombre del ámbito emitido es. opentelemetry.instrumentation.langchain
requirements.txt:
opentelemetry-instrumentation-langchain>=0.55.0
pyproject.toml:
[project]
dependencies = [
"opentelemetry-instrumentation-langchain>=0.55.0",
]
- OpenInference
-
Añada openinference-instrumentation-langchain a sus dependencias. El nombre del ámbito emitido esopeninference.instrumentation.langchain.
requirements.txt:
openinference-instrumentation-langchain>=0.1.62
pyproject.toml:
[project]
dependencies = [
"openinference-instrumentation-langchain>=0.1.62",
]
Cómo se identifican los intervalos
El atributo utilizado para clasificar los intervalos difiere entre las dos bibliotecas de instrumentación.
ejemplo
- OpenTelemetry
-
La biblioteca de OpenTelemetry instrumentación clasifica los tramos mediante el traceloop.span.kind atributo y también los establece en las versiones recientes. gen_ai.operation.name
| Tipo de tramo |
Atributo identificativo |
|
Invoca al agente
|
traceloop.span.kind= workflow (también gen_ai.operation.name =invoke_agent)
|
|
Ejecute la herramienta
|
traceloop.span.kind= tool (también gen_ai.operation.name =execute_tool)
|
|
Inferencia
|
gen_ai.operation.name = chat
|
- OpenInference
-
La biblioteca de OpenInference instrumentación clasifica los tramos mediante el atributo. openinference.span.kind
| Tipo de tramo |
Atributo identificativo |
|
Invoca al agente
|
openinference.span.kind= CHAIN o AGENT
|
|
Ejecute la herramienta
|
openinference.span.kind = TOOL
|
|
Inferencia
|
openinference.span.kind = LLM
|
Para el intervalo de agentes de invocación, la entrada y la salida no contienen una lista limpia por mensaje. En cambio, el contenido es el estado del LangChain gráfico serializado: una cadena JSON que contiene el estado completo. La forma exacta de este estado serializado difiere entre las dos bibliotecas de instrumentación. En ambos casos, el servicio lo analiza para encontrar el mensaje del usuario (el mensaje humano) y la respuesta del agente (el mensaje de la IA).
LangGraph también serializa las funciones de los mensajes en más de una forma. Un rol puede aparecer como un valor en minúscula (human,ai,tool) o como un nombre de clase de LangChain mensaje (HumanMessage,,AIMessage). ToolMessage El servicio reconoce ambos formularios.
La ubicación de este contenido depende de cómo se recopiló la telemetría. El atributo de identificación (traceloop.span.kindoopeninference.span.kind) está en el intervalo en ambos casos. Para obtener más información, consulte Intervalos, registros de eventos y señales de telemetría.
Cuando se divide la telemetría, el servicio lee el contenido del registro de eventos correlacionado con cada intervalo:
-
Mensaje del usuario y respuesta del agente: del registro de eventos del intervalo de agentes invocado, en y. body.input body.output
-
Llamada de herramienta: el nombre de la herramienta que aparece en el intervalo de herramientas de ejecución. Los argumentos y el resultado de la herramienta provienen del registro de eventos de ese intervalo, en body.input ybody.output.
Para ver ejemplos, consulte Ejemplos de intervalos con registros de eventos.
Cuando la telemetría no está dividida, el mismo contenido permanece en el tramo que los atributos. Los atributos dependen de la biblioteca de instrumentación:
-
OpenTelemetry:
-
Mensaje del usuario y respuesta del agente: desde gen_ai.task.input y gen_ai.task.output en el intervalo de invocación del agente.
-
Llamada a la herramienta: el nombre de la herramienta desdegen_ai.tool.name, y los argumentos y el resultado de gen_ai.tool.call.arguments ygen_ai.tool.call.result, en el intervalo de la herramienta de ejecución.
-
OpenInference:
-
Mensaje del usuario y respuesta del agente: desde input.value y output.value en el intervalo del agente de invocación.
-
Llamada a la herramienta: el nombre de la herramienta desdetool.name, y los argumentos y el resultado de input.value youtput.value, en el intervalo de la herramienta de ejecución.
Para ver ejemplos, consulte Ejemplos de intervalos sin registros de eventos.
Un ejemplo de tramos con registros de eventos
Cuando se divide la telemetría, el intervalo contiene los atributos de identificación y el contenido reside en un registro de eventos correlacionado. Los siguientes ejemplos provienen de un agente de LangGraph planificación de viajes desplegado en Amazon Bedrock AgentCore Runtime. En cada biblioteca de instrumentación se muestra el mismo agente.
Estos ejemplos no son períodos completos. Muestran datos representativos de una interacción real entre agentes, omitiendo algunos campos y truncando los valores largos para facilitar la lectura.
OpenTelemetry
ejemplo
- Invoke agent span
-
El traceloop.span.kind atributo (workflow) lo identifica como un intervalo de agentes de invocación; las versiones recientes de la biblioteca también establecen el valor =. 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"
}
}
El registro de eventos correlacionados contiene la conversación. El de cada mensaje content es el estado del LangChain gráfico serializado. La entrada envuelve el estado bajo una inputs clave. La salida lo envuelve bajo una outputs clave, con cada mensaje como un objeto LangChain constructor. El mensaje del usuario es el mensaje humano y la respuesta del agente es el mensaje de la IA dentro de ese estado serializado.
{
"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
-
El traceloop.span.kind atributo (tool) lo identifica como un intervalo de herramientas de ejecución; gen_ai.tool.name contiene el nombre de la herramienta y 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"
}
}
El registro de eventos correlacionados contiene la entrada (argumentos) y la salida (resultado, serializado como a) de la herramienta. LangChain ToolMessage
{
"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
En la OpenInference biblioteca, el tipo de intervalo se incluye en el openinference.span.kind atributo y la entrada y la salida del agente se serializan en el registro de eventos correlacionados.
ejemplo
- Invoke agent span
-
El openinference.span.kind atributo (CHAINo AGENT cuando el gráfico se compila con un nombre) lo identifica como un intervalo de agentes de invocación.
{
"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"
}
}
El registro de eventos correlacionados contiene la conversación. El mensaje del usuario es el mensaje de rol humano y la respuesta del agente es el AI-role mensaje de los mensajes serializados.
{
"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
-
El openinference.span.kind atributo (TOOL) lo identifica como un intervalo de herramientas de ejecución; tool.name contiene el nombre de la herramienta.
{
"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"
}
}
El registro de eventos correlacionados contiene la entrada (argumentos) y la salida (resultado, serializado como a) de la herramienta. LangChain ToolMessage
{
"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"
}
]
}
}
}
El ejemplo abarca espacios sin registros de eventos
Cuando la telemetría no está dividida, el mismo contenido permanece en los atributos del tramo y no se genera ningún registro de eventos independiente. Los siguientes ejemplos son de un agente de planificación de LangGraph viajes. En cada biblioteca de instrumentación se muestra el mismo agente.
Estos ejemplos no son períodos completos. Muestran datos representativos de una interacción real entre agentes, omitiendo algunos campos y truncando los valores largos para facilitar la lectura.
OpenTelemetry
ejemplo
- Invoke agent span
-
El gen_ai.task.input atributo contiene la solicitud del usuario y el gen_ai.task.output atributo contiene el estado serializado con la respuesta del agente. Ambos son el estado del LangChain gráfico serializado.
{
"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
-
El gen_ai.tool.call.arguments atributo contiene los argumentos de la herramienta y el gen_ai.tool.call.result atributo contiene el resultado de la herramienta, serializado como a. 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
ejemplo
- Invoke agent span
-
El input.value atributo contiene la solicitud del usuario y el output.value atributo contiene el estado serializado con la respuesta del agente.
{
"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
-
El input.value atributo contiene los argumentos de la herramienta y el output.value atributo contiene el resultado de la herramienta, serializado como a. 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"
}
}
Mejores prácticas para los agentes LangGraph
La forma en que se crea e invoca un LangGraph agente afecta a lo que aparece en su telemetría y, por lo tanto, a la fiabilidad con la que se puede evaluar el agente. Las siguientes prácticas ayudan a garantizar que se puedan recuperar el mensaje del usuario, la respuesta del agente y la actividad de la herramienta.
1. Elija un patrón de construcción de agentes
Existen dos formas comunes de crear un LangGraph agente:
-
Prediseñado create_agent: la forma más rápida de empezar. Produce un único intervalo de agentes de invocación por turno, y la conversación pasa por el ciclo LangGraph de ejecución integrado. Úselo cuando desee un agente estándar de razón-acción sin un flujo de control personalizado.
from langchain.agents import create_agent
agent = create_agent(model=model, tools=[search_flights, book_flight])
-
Personalizado StateGraph: le brinda un control total sobre los nodos, los bordes y el enrutamiento condicional. La ejecución de cada nodo se convierte en su propio intervalo, por lo que las trazas son más granulares. Úselo cuando necesite una orquestación personalizada.
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()
Ambos patrones se evalúan de la misma manera; la diferencia es la granularidad de la traza.
2. Usa mensajes en el estado del gráfico (recomendado)
El servicio de evaluación reconstruye la conversación a partir de los mensajes de entrada y salida del agente. El uso de un messages campo no es obligatorio, pero permite una extracción más fiable. Como costumbreStateGraph, mantén la conversación en un messages campo de tu estado:
-
messagesInclúyelo en tu estado (recomendado). Puedes añadir otros campos personalizados (como user_id metadatos). Cuando messages está presente, la extracción estándar busca directamente el mensaje del usuario y la respuesta del agente. Si no messages está presente, el servicio recurre a reconstruir la conversación a partir de intervalos de inferencia individuales, lo que resulta menos fiable.
-
Añada, no sustituya. Sigue la LangGraph convención de añadir nuevos mensajes a la lista en lugar de sobrescribirlos, de forma que se conserve todo el historial de conversaciones.
-
Usa tipos de LangChain mensajes canónicos (HumanMessage,,,AIMessage). ToolMessage SystemMessage La instrumentación los serializa correctamente y el servicio reconoce sus funciones.
3. Transmita el mensaje del usuario en un formato compatible
Al invocar a un LangGraph agente, se añade el mensaje del usuario al messages estado del gráfico. LangGraph acepta el mensaje en tres formatos intercambiables y AgentCore Evaluations los admite todos. Cada uno produce intervalos y registros de eventos que el servicio puede leer.
-
Tupla: un (role, content) par:
agent.invoke({"messages": [("user", user_message)]}, config=config)
-
LangChain objeto de mensaje: a HumanMessage (u otra clase de mensaje):
from langchain_core.messages import HumanMessage
agent.invoke({"messages": [HumanMessage(content=user_message)]}, config=config)
-
Diccionario: un {"role", "content"} diccionario:
agent.invoke({"messages": [{"role": "user", "content": user_message}]}, config=config)
Los tres formatos tienen el mismo messages estado, por lo que el mensaje del usuario y la respuesta del agente se extraen de forma idéntica, independientemente del que se elija.