View a markdown version of this page

LangGraph - Amazon Bedrock AgentCore

LangGraph

本页说明了如何对LangGraph代理进行仪器测量、如何识别跨度以及如何提取评估字段。最后,它提供了构建 LangGraph 代理的最佳实践,以便对其进行可靠评估。

主题

对你的代理进行仪器

您可以使用两个仪器库中的任何一个来检测 LangGraph 代理:OpenTelemetry(opentelemetry-instrumentation-langchain) 或 OpenInference(openinference-instrumentation-langchain)。Amazon Bedrock AgentCore 评估支持这两个库。这些库发出不同的作用域名称并使用不同的跨度属性。评估服务从每个值中提取相同的值。

当您的代理与 AWS Distro for OpenTelemetry (ADOT) 一起 AgentCore 运行时,例如在 Amazon Bedrock Runtime 上,您无需添加显式的检测代码。将仪器库添加到项目的依赖项中就足够了。ADOT 会在启动时发现它并自动将其激活。

为你想要的依赖关系路径添加仪器库。以下示例固定了最低版本;除非您有理由固定,否则请使用最新的可用版本。

OpenTelemetry

注意:使用版本0.55.0或更高版本。版本 0.55.0 增加了对评估服务所依赖的较新的 OpenTelemetry 生成 AI 代理跨度约定的支持。

opentelemetry-instrumentation-langchain 添加到依赖项。发出的作用域名称是。opentelemetry.instrumentation.langchain

requirements.txt:

opentelemetry-instrumentation-langchain>=0.55.0

pyproject.toml:

[project] dependencies = [ "opentelemetry-instrumentation-langchain>=0.55.0", ]
OpenInference

openinference-instrumentation-langchain 添加到依赖项。发出的作用域名称是。openinference.instrumentation.langchain

requirements.txt:

openinference-instrumentation-langchain>=0.1.62

pyproject.toml:

[project] dependencies = [ "openinference-instrumentation-langchain>=0.1.62", ]
注意

仪器化是设置可观测性的一个步骤。要导出遥测以进行评估,请在设置可观测性中完成完整设置。

如何识别跨度

用于对跨度进行分类的属性在两个仪器库之间有所不同。

OpenTelemetry

OpenTelemetry 仪器库使用traceloop.span.kind属性对跨度进行分类,最新版本也进行了设置。gen_ai.operation.name

跨度类型 识别属性

调用代理

traceloop.span.kind=workflow(也是 gen_ai.operation.name =invoke_agent

执行工具

traceloop.span.kind=tool(也是 gen_ai.operation.name =execute_tool

推理

gen_ai.operation.name = chat

OpenInference

OpenInference 仪器库使用属性对跨度进行分类。openinference.span.kind

跨度类型 识别属性

调用代理

openinference.span.kind= CHAINAGENT

执行工具

openinference.span.kind = TOOL

推理

openinference.span.kind = LLM

如何提取评估字段

对于调用代理跨度,输入和输出不包含干净的每条消息列表。相反,内容是序列化的 LangChain 图形状态:包装完整状态的 JSON 字符串。两个仪器库之间这种序列化状态的确切形状不同。在这两种情况下,服务都会对其进行解析以找到用户提示(人类消息)和代理响应(AI 消息)。

LangGraph 还会以多种形式序列化消息角色。角色可以以小写值 (humanaitool) 或 LangChain 消息类名 (, HumanMessageAIMessage,ToolMessage) 的形式出现。该服务可以识别两种形式。

这些内容的位置取决于遥测数据的收集方式。在这两种情况下,标识属性(traceloop.span.kindopeninference.span.kind)都在跨度上。有关更多信息,请参阅跨度、事件记录和遥测信号。

来自事件记录

拆分遥测时,该服务会从与每个跨度相关的事件记录中读取内容:

  • 用户提示代理响应:来自调用代理跨度的事件记录,位于body.input和中body.output

  • 工具调用:执行工具跨度中的工具名称。工具参数和结果来自该跨度的事件记录,位于body.input和中body.output

有关示例,请参阅包含事件记录的跨度示例。

来自跨度属性

如果未拆分遥测,则相同的内容将作为属性保留在跨度上。这些属性取决于仪器库:

  • OpenTelemetry:

    • 用户提示代理响应:从gen_ai.task.inputgen_ai.task.output在调用代理跨度上。

    • 工具调用:执行工具跨度上的工具名称gen_ai.tool.call.resultgen_ai.tool.call.arguments以及来自和的参数和结果。gen_ai.tool.name

  • OpenInference:

    • 用户提示代理响应:从input.valueoutput.value在调用代理跨度上。

    • 工具调用:执行工具跨度上的工具名称output.valueinput.value以及来自和的参数和结果。tool.name

有关示例,请参阅没有事件记录的跨度示例。

包含事件记录的示例

拆分遥测时,跨度带有识别属性,内容存在于相关的事件记录中。以下示例来自部署在 Amazon Bedro LangGraph AgentCore ck Runtime 上的旅行计划代理。每个仪器库下都显示相同的代理。

注意

这些示例不是完整的跨度。它们显示来自真实代理互动的代表性数据,为了便于阅读,省略了一些字段,长值被截断。

OpenTelemetry

Invoke agent span

traceloop.span.kind属性 (workflow) 将其标识为调用代理跨度;最近的库版本也将其设置为 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" } }

关联的事件记录包含对话。每条消息content都是序列化的 LangChain 图形状态。输入将状态封装在按inputs键下。输出将其封装在outputs密钥下,每条消息都作为 LangChain 构造函数对象。用户提示是人类消息,代理响应是处于该序列化状态的 AI 消息。

{ "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

traceloop.span.kind属性 (tool) 将其标识为执行工具跨度;gen_ai.tool.name保存工具名称和 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" } }

关联的事件记录包含工具输入(参数)和输出(结果,序列化为 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

在 OpenInference 库中,跨度类型包含在openinference.span.kind属性中,代理输入和输出在相关的事件记录中序列化。

Invoke agent span

openinference.span.kind属性(CHAIN或使用名称编译图表AGENT时)将其标识为调用代理跨度。

{ "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" } }

关联的事件记录包含对话。用户提示是人类角色消息,代理响应是序列化 AI-role 消息中的消息。

{ "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

openinference.span.kind属性 (TOOL) 将其标识为执行工具跨度;tool.name保存工具名称。

{ "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" } }

关联的事件记录包含工具输入(参数)和输出(结果,序列化为 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" } ] } } }

没有事件记录的示例跨度

如果不拆分遥测,则相同的内容将保留在跨度属性上,并且不会生成单独的事件记录。以下示例来自 LangGraph 旅行计划代理商。每个仪器库下都显示相同的代理。

注意

这些示例不是完整的跨度。它们显示来自真实代理互动的代表性数据,为了便于阅读,省略了一些字段,长值被截断。

OpenTelemetry

Invoke agent span

gen_ai.task.input属性保存用户提示,该gen_ai.task.output属性保存代理响应的序列化状态。两者都是序列化的 LangChain 图形状态。

{ "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

gen_ai.tool.call.arguments属性保存工具参数,该gen_ai.tool.call.result属性保存工具结果,序列化为 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

Invoke agent span

input.value属性保存用户提示,该output.value属性保存代理响应的序列化状态。

{ "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

input.value属性保存工具参数,该output.value属性保存工具结果,序列化为 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" } }

LangGraph 代理的最佳实践

构建和调用 LangGraph 代理的方式会影响其遥测中显示的内容,从而影响对代理进行评估的可靠性。以下做法有助于确保用户提示、代理响应和工具活动可恢复。

1. 选择代理构建模式

生成 LangGraph 代理有两种常用方法:

  • 预构建 create_agent:最快的入门方式。它每回合生成一个调用代理跨度,对话通过内置 LangGraph的执行循环。如果您想要没有自定义控制流的标准推理行为代理,请使用此选项。

    from langchain.agents import create_agent agent = create_agent(model=model, tools=[search_flights, book_flight])
  • 自定义 StateGraph:让您可以完全控制节点、边缘和条件路由。每个节点的执行都会变成自己的跨度,因此跟踪更加精细。当您需要自定义编排时,请使用此选项。

    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()

两种模式的评估方式相同;区别在于轨迹的粒度。

2. 在图表状态下使用消息(推荐)

评估服务根据代理的输入和输出消息重建会话。使用messages字段不是强制性的,但它可以实现最可靠的提取。对于习惯StateGraph,请将对话保留在您所在州的某个messages字段中:

  • 包含messages在您所在的州中(推荐)。您可以添加其他自定义字段(例如user_id或元数据)。当messages存在时,标准提取会直接找到用户提示和代理响应。如果不存在,messages则该服务将退回到根据个人推理跨度重建对话,这就不那么可靠了。

  • 追加,不要替换。遵循将新消息添加到列表而不是覆盖列表的 LangGraph 惯例,这样可以保留完整的对话历史记录。

  • 使用规范 LangChain 消息类型HumanMessage、、AIMessageToolMessageSystemMessage)。仪器会正确序列化它们,并且服务会识别它们的角色。

3. 以支持的格式传递用户消息

调用 LangGraph 代理时,会将用户消息添加到图表的messages状态。 LangGraph 接受三种可互换格式的消息, AgentCore 评估支持所有格式。每个都生成服务可以读取的跨度和事件记录。

  • 元组:一(role, content)对:

    agent.invoke({"messages": [("user", user_message)]}, config=config)
  • LangChain 消息对象:aHumanMessage(或其他消息类):

    from langchain_core.messages import HumanMessage agent.invoke({"messages": [HumanMessage(content=user_message)]}, config=config)
  • 字典:一本{"role", "content"}字典:

    agent.invoke({"messages": [{"role": "user", "content": user_message}]}, config=config)

这三种格式的结果都是一样messages的,因此无论您选择哪种格式,用户提示和代理响应的提取方式都是一样的。