LangGraph
Esta página explica como instrumentar um LangGraphagente, como os intervalos são identificados e como os campos de avaliação são extraídos. Ele termina com as melhores práticas para estruturar um LangGraph agente para que ele possa ser avaliado de forma confiável.
Tópicos
Instrumente seu agente
Você pode instrumentar um LangGraph agente com qualquer uma das duas bibliotecas de instrumentação: OpenTelemetry(opentelemetry-instrumentation-langchain) ou OpenInference(openinference-instrumentation-langchain). O Amazon Bedrock AgentCore Evaluations oferece suporte às duas bibliotecas. As bibliotecas emitem nomes de escopo diferentes e usam atributos de extensão diferentes. O serviço de avaliação extrai os mesmos valores de cada um.
Quando seu agente é executado com a AWS Distro for OpenTelemetry (ADOT), como no Amazon Bedrock AgentCore Runtime, você não precisa adicionar código de instrumentação explícito. Adicionar a biblioteca de instrumentação às dependências do seu projeto é suficiente. O ADOT o descobre na inicialização e o ativa automaticamente.
Adicione a biblioteca de instrumentação para o caminho que você deseja para suas dependências. Os exemplos a seguir fixam uma versão mínima; use a versão mais recente disponível, a menos que você tenha um motivo para fixar.
exemplo
- OpenTelemetry
-
NOTA: Use a versão 0.55.0 ou posterior. A versão 0.55.0 adicionou suporte às novas convenções de amplitude do agente OpenTelemetry Generative-AI nas quais o serviço de avaliação depende.
Adicione opentelemetry-instrumentation-langchain às suas dependências. O nome do escopo emitido éopentelemetry.instrumentation.langchain.
requirements.txt:
opentelemetry-instrumentation-langchain>=0.55.0
pyproject.toml:
[project]
dependencies = [
"opentelemetry-instrumentation-langchain>=0.55.0",
]
- OpenInference
-
Adicione openinference-instrumentation-langchain às suas dependências. O nome do escopo emitido éopeninference.instrumentation.langchain.
requirements.txt:
openinference-instrumentation-langchain>=0.1.62
pyproject.toml:
[project]
dependencies = [
"openinference-instrumentation-langchain>=0.1.62",
]
A instrumentação é uma etapa na configuração da observabilidade. Para exportar a telemetria para avaliação, conclua a configuração completa em Configurar observabilidade.
Como os vãos são identificados
O atributo usado para classificar extensões difere entre as duas bibliotecas de instrumentação.
exemplo
- OpenTelemetry
-
A biblioteca de OpenTelemetry instrumentação classifica as extensões usando o traceloop.span.kind atributo, e as versões recentes também são definidas. gen_ai.operation.name
| Tipo de extensão |
Atributo de identificação |
|
Invocar agente
|
traceloop.span.kind= workflow (também gen_ai.operation.name =invoke_agent)
|
|
Ferramenta de execução
|
traceloop.span.kind= tool (também gen_ai.operation.name =execute_tool)
|
|
Inferência
|
gen_ai.operation.name = chat
|
- OpenInference
-
A biblioteca de OpenInference instrumentação classifica as extensões usando o atributo. openinference.span.kind
| Tipo de extensão |
Atributo de identificação |
|
Invocar agente
|
openinference.span.kind= CHAIN ou AGENT
|
|
Ferramenta de execução
|
openinference.span.kind = TOOL
|
|
Inferência
|
openinference.span.kind = LLM
|
Para o intervalo do agente de invocação, a entrada e a saída não contêm uma lista limpa por mensagem. Em vez disso, o conteúdo é o estado do LangChain gráfico serializado: uma string JSON que envolve o estado completo. A forma exata desse estado serializado difere entre as duas bibliotecas de instrumentação. Em ambos os casos, o serviço o analisa para encontrar o prompt do usuário (a mensagem humana) e a resposta do agente (a mensagem de IA).
LangGraph também serializa as funções das mensagens em mais de um formulário. Uma função pode aparecer como um valor minúsculo (human,ai,tool) ou como um nome de classe de LangChain mensagem (HumanMessage,AIMessage,ToolMessage). O serviço reconhece os dois formulários.
A localização desse conteúdo depende de como a telemetria foi coletada. O atributo de identificação (traceloop.span.kindouopeninference.span.kind) está no intervalo em ambos os casos. Para obter mais informações, consulte Espaços, registros de eventos e sinais de telemetria.
Quando a telemetria é dividida, o serviço lê o conteúdo do registro do evento correlacionado a cada período:
-
Solicitação do usuário e resposta do agente: do registro de eventos do invoke agent span, em body.input e. body.output
-
Chamada de ferramenta: o nome da ferramenta a partir da extensão da ferramenta de execução. Os argumentos e o resultado da ferramenta vêm do registro de eventos desse intervalo, em body.input body.output e.
Para ver exemplos, consulte Exemplos de períodos com registros de eventos.
Quando a telemetria não é dividida, o mesmo conteúdo permanece na extensão como atributos. Os atributos dependem da biblioteca de instrumentação:
-
OpenTelemetry:
-
Solicitação do usuário e resposta do agente: de gen_ai.task.input e gen_ai.task.output sobre o período de invocação do agente.
-
Chamada de ferramenta: o nome da ferramenta degen_ai.tool.name, os argumentos e o resultado de gen_ai.tool.call.arguments egen_ai.tool.call.result, na extensão da ferramenta de execução.
-
OpenInference:
-
Solicitação do usuário e resposta do agente: de input.value e output.value sobre o período de invocação do agente.
-
Chamada de ferramenta: o nome da ferramenta detool.name, os argumentos e o resultado de input.value eoutput.value, na extensão da ferramenta de execução.
Por exemplo, consulte Exemplos de períodos sem registros de eventos.
Exemplos de períodos com registros de eventos
Quando a telemetria é dividida, o intervalo carrega os atributos de identificação e o conteúdo fica em um registro de evento correlacionado. Os exemplos a seguir são de um agente de LangGraph planejamento de viagens implantado no Amazon Bedrock Runtime. AgentCore O mesmo agente é mostrado em cada biblioteca de instrumentação.
Esses exemplos não são extensões completas. Eles mostram dados representativos de uma interação real do agente, com alguns campos omitidos e valores longos truncados para facilitar a leitura.
OpenTelemetry
exemplo
- Invoke agent span
-
O traceloop.span.kind atributo (workflow) identifica isso como uma extensão do agente de invocação; versões recentes da biblioteca também gen_ai.operation.name definem =. 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"
}
}
O registro do evento correlacionado carrega a conversa. Cada mensagem content é o estado do LangChain gráfico serializado. A entrada envolve o estado sob uma inputs chave. A saída a envolve em uma outputs chave, com cada mensagem como um objeto LangChain construtor. O prompt do usuário é a mensagem humana e a resposta do agente é a mensagem de IA dentro desse 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
-
O traceloop.span.kind atributo (tool) identifica isso como uma extensão da ferramenta de execução; gen_ai.tool.name contém o nome da ferramenta e 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"
}
}
O registro do evento correlacionado carrega a entrada (argumentos) e a saída da ferramenta (resultado, serializado como a 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
Com a OpenInference biblioteca, o tipo de intervalo é transportado no openinference.span.kind atributo e a entrada e a saída do agente são serializadas no registro do evento correlacionado.
exemplo
- Invoke agent span
-
O openinference.span.kind atributo (CHAIN, ou AGENT quando o gráfico é compilado com um nome) identifica isso como uma extensão do agente de invocação.
{
"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"
}
}
O registro do evento correlacionado carrega a conversa. O prompt do usuário é a mensagem da função humana e a resposta do agente é a AI-role mensagem nas mensagens serializadas.
{
"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
-
O openinference.span.kind atributo (TOOL) identifica isso como uma extensão da ferramenta de execução; tool.name contém o nome da ferramenta.
{
"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"
}
}
O registro do evento correlacionado carrega a entrada (argumentos) e a saída da ferramenta (resultado, serializado como a 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"
}
]
}
}
}
Exemplos de períodos sem registros de eventos
Quando a telemetria não é dividida, o mesmo conteúdo permanece nos atributos de span e nenhum registro de evento separado é produzido. Os exemplos a seguir são de um agente de LangGraph planejamento de viagens. O mesmo agente é mostrado em cada biblioteca de instrumentação.
Esses exemplos não são extensões completas. Eles mostram dados representativos de uma interação real do agente, com alguns campos omitidos e valores longos truncados para facilitar a leitura.
OpenTelemetry
exemplo
- Invoke agent span
-
O gen_ai.task.input atributo contém o prompt do usuário e o gen_ai.task.output atributo mantém o estado serializado com a resposta do agente. Ambos são o estado do 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
-
O gen_ai.tool.call.arguments atributo contém os argumentos da ferramenta e o gen_ai.tool.call.result atributo contém o resultado da ferramenta, 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
exemplo
- Invoke agent span
-
O input.value atributo contém o prompt do usuário e o output.value atributo mantém o estado serializado com a resposta do 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
-
O input.value atributo contém os argumentos da ferramenta e o output.value atributo contém o resultado da ferramenta, 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"
}
}
Melhores práticas para LangGraph agentes
A forma como você cria e invoca um LangGraph agente afeta o que aparece em sua telemetria e, portanto, a confiabilidade com que o agente pode ser avaliado. As práticas a seguir ajudam a garantir que a solicitação do usuário, a resposta do agente e a atividade da ferramenta sejam recuperáveis.
1. Escolha um padrão de construção do agente
Há duas maneiras comuns de criar um LangGraph agente:
-
Pré-construído create_agent: a maneira mais rápida de começar. Ele produz um único intervalo de agente de invocação por turno, com a conversa LangGraph transmitida pelo loop de execução integrado. Use isso quando quiser um agente de ação racional padrão sem fluxo de controle personalizado.
from langchain.agents import create_agent
agent = create_agent(model=model, tools=[search_flights, book_flight])
-
Personalizado StateGraph: oferece controle total sobre nós, bordas e roteamento condicional. A execução de cada nó se torna sua própria extensão, portanto, os rastreamentos são mais granulares. Use isso quando precisar de uma orquestração 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 os padrões são avaliados da mesma forma; a diferença é a granularidade do traço.
2. Use mensagens no estado do seu gráfico (recomendado)
O serviço de avaliação reconstrói a conversa a partir das mensagens de entrada e saída do agente. Usar um messages campo não é obrigatório, mas permite a extração mais confiável. Para fins de personalizaçãoStateGraph, mantenha a conversa em um messages campo em seu estado:
-
Inclua messages em seu estado (recomendado). Você pode adicionar outros campos personalizados (como user_id metadados). Quando messages está presente, a extração padrão encontra diretamente o prompt do usuário e a resposta do agente. Se messages estiver ausente, o serviço volta a reconstruir a conversa a partir de períodos de inferência individuais, o que é menos confiável.
-
Anexe, não substitua. Siga a LangGraph convenção de acrescentar novas mensagens à lista em vez de sobrescrevê-la, para que o histórico completo da conversa seja preservado.
-
Use tipos de LangChain mensagens canônicas (HumanMessage,, AIMessageToolMessage,SystemMessage). A instrumentação os serializa corretamente e o serviço reconhece suas funções.
3. Passe a mensagem do usuário em um formato compatível
Ao invocar um LangGraph agente, você adiciona a mensagem do usuário ao messages estado do gráfico. LangGraph aceita a mensagem em três formatos intercambiáveis, e o AgentCore Evaluations suporta todos eles. Cada um produz intervalos e registros de eventos que o serviço pode ler.
-
Tupla: um (role, content) par:
agent.invoke({"messages": [("user", user_message)]}, config=config)
-
LangChain objeto de mensagem: a HumanMessage (ou outra classe de mensagem):
from langchain_core.messages import HumanMessage
agent.invoke({"messages": [HumanMessage(content=user_message)]}, config=config)
-
Dicionário: um {"role", "content"} dicionário:
agent.invoke({"messages": [{"role": "user", "content": user_message}]}, config=config)
Todos os três formatos resultam no mesmo messages estado, portanto, o prompt do usuário e a resposta do agente são extraídos de forma idêntica, independentemente de qual você escolher.