langgraph 是一个用于构建有状态、多参与者 LLM 应用程序的库,用于创建智能体和多智能体工作流。评估 langgraph 图可能具有挑战性,因为单次调用可能涉及多次 LLM 调用,而进行哪些 LLM 调用可能取决于先前调用的输出。在本指南中,我们将重点介绍如何将图和图节点传递给 evaluate() / aevaluate() 的机制。有关构建智能体时的评估技术和最佳实践,请参阅 langgraph 文档。
端到端评估
最常见的评估类型是端到端评估,我们希望评估每个示例输入的最终图输出。定义一个图
让我们先构建一个简单的 ReACT 智能体:from typing import Annotated, Literal, TypedDict
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.prebuilt import ToolNode
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
class State(TypedDict):
# 消息的类型是 "list"。注解中的 'add_messages' 函数
# 定义了如何更新此状态键
# (在这种情况下,它将消息追加到列表中,而不是覆盖它们)
messages: Annotated[list, add_messages]
# 定义智能体要使用的工具
@tool
def search(query: str) -> str:
"""调用以浏览网页。"""
# 这是一个占位符,但不要告诉 LLM 这一点...
if "sf" in query.lower() or "san francisco" in query.lower():
return "It's 60 degrees and foggy."
return "It's 90 degrees and sunny."
tools = [search]
tool_node = ToolNode(tools)
model = init_chat_model("claude-sonnet-4-6").bind_tools(tools)
# 定义决定是否继续的函数
def should_continue(state: State) -> Literal["tools", END]:
messages = state['messages']
last_message = messages[-1]
# 如果 LLM 进行工具调用,则路由到 "tools" 节点
if last_message.tool_calls:
return "tools"
# 否则,我们停止(回复用户)
return END
# 定义调用模型的函数
def call_model(state: State):
messages = state['messages']
response = model.invoke(messages)
# 我们返回一个列表,因为这将被添加到现有列表中
return {"messages": [response]}
# 定义一个新图
workflow = StateGraph(State)
# 定义我们将循环的两个节点
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)
# 将入口点设置为 'agent'
# 这意味着此节点是第一个被调用的节点
workflow.add_edge(START, "agent")
# 我们现在添加一个条件边
workflow.add_conditional_edges(
# 首先,我们定义起始节点。我们使用 'agent'。
# 这意味着这些是在 'agent' 节点被调用后采取的边。
"agent",
# 接下来,我们传入将决定接下来调用哪个节点的函数。
should_continue,
)
# 我们现在添加从 'tools' 到 'agent' 的普通边。
# 这意味着在 'tools' 被调用后,接下来调用 'agent' 节点。
workflow.add_edge("tools", 'agent')
# 最后,我们编译它!
# 这将其编译为 LangChain Runnable,
# 意味着你可以像使用任何其他 runnable 一样使用它。
# 请注意,我们在编译图时(可选地)传递了内存
app = workflow.compile()
创建数据集
让我们创建一个包含问题和预期响应的简单数据集:from langsmith import Client
questions = [
"what's the weather in sf",
"whats the weather in san fran",
"whats the weather in tangier"
]
answers = [
"It's 60 degrees and foggy.",
"It's 60 degrees and foggy.",
"It's 90 degrees and sunny.",
]
ls_client = Client()
dataset = ls_client.create_dataset("weather agent")
ls_client.create_examples(
inputs=[{"question": q} for q in questions],
outputs=[{"answer": a} for a in answers],
dataset_id=dataset.id,
)
创建评估器
以及一个简单的评估器: 需要langsmith>=0.2.0
judge_llm = init_chat_model("gpt-5.4")
async def correct(outputs: dict, reference_outputs: dict) -> bool:
instructions = (
"给定一个实际答案和一个预期答案,判断实际答案是否包含"
"预期答案中的所有信息。如果实际答案包含所有预期信息,"
"则回复 'CORRECT',否则回复 'INCORRECT'。"
"不要在回复中包含任何其他内容。"
)
# 我们的图输出一个 State 字典,在这种情况下意味着
# 我们将有一个 'messages' 键,最后一条消息应该是
# 我们的实际答案。
actual_answer = outputs["messages"][-1].content
expected_answer = reference_outputs["answer"]
user_msg = (
f"ACTUAL ANSWER: {actual_answer}"
f"\n\nEXPECTED ANSWER: {expected_answer}"
)
response = await judge_llm.ainvoke(
[
{"role": "system", "content": instructions},
{"role": "user", "content": user_msg}
]
)
return response.content.upper() == "CORRECT"
运行评估
现在我们可以运行评估并探索结果。我们只需要包装我们的图函数,使其能够以示例中存储的格式接收输入:如果你的所有图节点都定义为同步函数,那么你可以使用
evaluate 或 aevaluate。如果你的任何节点定义为异步,则需要使用 aevaluate。langsmith>=0.2.0
import asyncio
from langsmith import aevaluate
def example_to_state(inputs: dict) -> dict:
return {"messages": [{"role": "user", "content": inputs['question']}]}
# 我们在这里使用 LCEL 声明式语法。
# 请记住,langgraph 图也是 langchain runnables。
target = example_to_state | app
async def main():
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct],
max_concurrency=4, # 可选
experiment_prefix="claude-sonnet-4-6-baseline", # 可选
metadata={ # 可选,用于在 UI 中填充模型/提示/工具列
"models": "google_genai:gemini-3.1-pro-preview",
"tools": [{"name": "search", "description": "Call to surf the web."}],
},
)
print(experiment_results)
asyncio.run(main())
评估中间步骤
通常,评估智能体的最终输出以及它所采取的中间步骤是有价值的。langgraph 的一个优点是,图的输出是一个状态对象,该对象通常已经携带了关于所采取的中间步骤的信息。通常,我们可以通过查看状态中的消息来评估我们感兴趣的任何内容。例如,我们可以查看消息以断言模型在第一步调用了 ‘search’ 工具。
需要 langsmith>=0.2.0
def right_tool(outputs: dict) -> bool:
tool_calls = outputs["messages"][1].tool_calls
return bool(tool_calls and tool_calls[0]["name"] == "search")
async def main():
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct, right_tool],
max_concurrency=4, # 可选
experiment_prefix="claude-sonnet-4-6-baseline", # 可选
metadata={ # 可选,用于在 UI 中填充模型/提示/工具列
"models": "google_genai:gemini-3.1-pro-preview",
"tools": [{"name": "search", "description": "Call to surf the web."}],
},
)
print(experiment_results)
有关可以传递给自定义评估器的更多参数信息,请参阅此操作指南。
from langsmith.schemas import Run, Example
def right_tool_from_run(run: Run, example: Example) -> dict:
# 获取文档和答案
first_model_run = next(run for run in root_run.child_runs if run.name == "agent")
tool_calls = first_model_run.outputs["messages"][-1].tool_calls
right_tool = bool(tool_calls and tool_calls[0]["name"] == "search")
return {"key": "right_tool", "value": right_tool}
async def main():
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct, right_tool_from_run],
max_concurrency=4, # 可选
experiment_prefix="claude-sonnet-4-6-baseline", # 可选
metadata={ # 可选,用于在 UI 中填充模型/提示/工具列
"models": "google_genai:gemini-3.1-pro-preview",
"tools": [{"name": "search", "description": "Call to surf the web."}],
},
)
print(experiment_results)
运行和评估单个节点
有时你想直接评估单个节点以节省时间和成本。langgraph 使这变得容易。在这种情况下,我们甚至可以继续使用我们一直在使用的评估器。
node_target = example_to_state | app.nodes["agent"]
async def main():
node_experiment_results = await aevaluate(
node_target,
data="weather agent",
evaluators=[right_tool_from_run],
max_concurrency=4, # 可选
experiment_prefix="claude-sonnet-4-6-model-node", # 可选
metadata={ # 可选,用于在 UI 中填充模型/提示/工具列
"models": "google_genai:gemini-3.1-pro-preview",
"tools": [{"name": "search", "description": "Call to surf the web."}],
},
)
print(node_experiment_results)
相关内容
参考代码
点击查看合并的代码片段
点击查看合并的代码片段
import asyncio
from typing import Annotated, Literal, TypedDict
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.prebuilt import ToolNode
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from langsmith import Client, aevaluate
# 定义一个图
class State(TypedDict):
# 消息的类型是 "list"。注解中的 'add_messages' 函数
# 定义了如何更新此状态键
# (在这种情况下,它将消息追加到列表中,而不是覆盖它们)
messages: Annotated[list, add_messages]
# 定义智能体要使用的工具
@tool
def search(query: str) -> str:
"""调用以浏览网页。"""
# 这是一个占位符,但不要告诉 LLM 这一点...
if "sf" in query.lower() or "san francisco" in query.lower():
return "It's 60 degrees and foggy."
return "It's 90 degrees and sunny."
tools = [search]
tool_node = ToolNode(tools)
model = init_chat_model("claude-sonnet-4-6").bind_tools(tools)
# 定义决定是否继续的函数
def should_continue(state: State) -> Literal["tools", END]:
messages = state['messages']
last_message = messages[-1]
# 如果 LLM 进行工具调用,则路由到 "tools" 节点
if last_message.tool_calls:
return "tools"
# 否则,我们停止(回复用户)
return END
# 定义调用模型的函数
def call_model(state: State):
messages = state['messages']
response = model.invoke(messages)
# 我们返回一个列表,因为这将被添加到现有列表中
return {"messages": [response]}
# 定义一个新图
workflow = StateGraph(State)
# 定义我们将循环的两个节点
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)
# 将入口点设置为 'agent'
# 这意味着此节点是第一个被调用的节点
workflow.add_edge(START, "agent")
# 我们现在添加一个条件边
workflow.add_conditional_edges(
# 首先,我们定义起始节点。我们使用 'agent'。
# 这意味着这些是在 'agent' 节点被调用后采取的边。
"agent",
# 接下来,我们传入将决定接下来调用哪个节点的函数。
should_continue,
)
# 我们现在添加从 'tools' 到 'agent' 的普通边。
# 这意味着在 'tools' 被调用后,接下来调用 'agent' 节点。
workflow.add_edge("tools", 'agent')
# 最后,我们编译它!
# 这将其编译为 LangChain Runnable,
# 意味着你可以像使用任何其他 runnable 一样使用它。
# 请注意,我们在编译图时(可选地)传递了内存
app = workflow.compile()
questions = [
"what's the weather in sf",
"whats the weather in san fran",
"whats the weather in tangier"
]
answers = [
"It's 60 degrees and foggy.",
"It's 60 degrees and foggy.",
"It's 90 degrees and sunny.",
]
# 创建数据集
ls_client = Client()
dataset = ls_client.create_dataset("weather agent")
ls_client.create_examples(
inputs=[{"question": q} for q in questions],
outputs=[{"answer": a} for a in answers],
dataset_id=dataset.id,
)
# 定义评估器
judge_llm = init_chat_model("gpt-5.4")
async def correct(outputs: dict, reference_outputs: dict) -> bool:
instructions = (
"给定一个实际答案和一个预期答案,判断实际答案是否包含"
"预期答案中的所有信息。如果实际答案包含所有预期信息,"
"则回复 'CORRECT',否则回复 'INCORRECT'。"
"不要在回复中包含任何其他内容。"
)
# 我们的图输出一个 State 字典,在这种情况下意味着
# 我们将有一个 'messages' 键,最后一条消息应该是
# 我们的实际答案。
actual_answer = outputs["messages"][-1].content
expected_answer = reference_outputs["answer"]
user_msg = (
f"ACTUAL ANSWER: {actual_answer}"
f"\n\nEXPECTED ANSWER: {expected_answer}"
)
response = await judge_llm.ainvoke(
[
{"role": "system", "content": instructions},
{"role": "user", "content": user_msg}
]
)
return response.content.upper() == "CORRECT"
def right_tool(outputs: dict) -> bool:
tool_calls = outputs["messages"][1].tool_calls
return bool(tool_calls and tool_calls[0]["name"] == "search")
def example_to_state(inputs: dict) -> dict:
return {"messages": [{"role": "user", "content": inputs['question']}]}
# 我们在这里使用 LCEL 声明式语法。
# 请记住,langgraph 图也是 langchain runnables。
target = example_to_state | app
# 运行评估
async def main():
experiment_results = await aevaluate(
target,
data="weather agent",
evaluators=[correct, right_tool],
max_concurrency=4, # 可选
experiment_prefix="claude-sonnet-4-6-baseline", # 可选
metadata={ # 可选,用于在 UI 中填充模型/提示/工具列
"models": "google_genai:gemini-3.1-pro-preview",
"tools": [{"name": "search", "description": "Call to surf the web."}],
},
)
print(experiment_results)
asyncio.run(main())
连接这些文档 到 Claude、VSCode 等,通过 MCP 获取实时答案。

