智能体应用允许大语言模型(LLM)自主决定解决问题所需的下一步操作。这种灵活性非常强大,但由于模型的黑箱特性,很难预测对智能体某一部分的微小调整将如何影响其他部分。要构建可用于生产的智能体,全面的测试至关重要。
测试智能体有几种方法:
-
单元测试 使用内存中的模拟对象独立测试智能体中的小而确定性的部分,以便快速、确定性地断言精确行为。
-
集成测试 使用真实的网络调用测试智能体,以确认各组件协同工作、凭证和模式匹配,且延迟在可接受范围内。
由于智能体应用通常将多个组件串联在一起,并且必须处理由LLM非确定性引起的不稳定性,因此它们更倾向于依赖集成测试。
单元测试
模拟聊天模型
对于不需要API调用的逻辑,您可以使用内存中的存根来模拟响应。
LangChain 提供了 GenericFakeChatModel 用于模拟文本响应。它接受一个响应迭代器(AIMessages 或字符串),每次调用返回一个响应。它同时支持常规和流式使用。
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
model = GenericFakeChatModel(messages=iter([
AIMessage(content="", tool_calls=[ToolCall(name="foo", args={"bar": "baz"}, id="call_1")]),
"bar"
]))
model.invoke("hello")
# AIMessage(content='', ..., tool_calls=[{'name': 'foo', 'args': {'bar': 'baz'}, 'id': 'call_1', 'type': 'tool_call'}])
如果再次调用模型,它将返回迭代器中的下一个项目:
model.invoke("hello, again!")
# AIMessage(content='bar', ...)
InMemorySaver 检查点器
为了在测试期间启用持久化,您可以使用 InMemorySaver 检查点器。这允许您模拟多轮对话以测试依赖状态的行为:
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model,
tools=[],
checkpointer=InMemorySaver()
)
# 第一次调用
agent.invoke(HumanMessage(content="我住在澳大利亚悉尼。"))
# 第二次调用:第一条消息已持久化(悉尼位置),因此模型返回 GMT+10 时间
agent.invoke(HumanMessage(content="我的当地时间是几点?"))
集成测试
许多智能体行为只有在使用真实LLM时才会显现,例如智能体决定调用哪个工具、如何格式化响应,或提示词修改是否会影响整个执行轨迹。LangChain 的 agentevals 包提供了专门设计用于使用实时模型测试智能体轨迹的评估器。
AgentEvals 允许您通过执行轨迹匹配或使用LLM裁判轻松评估智能体的轨迹(确切的消息序列,包括工具调用):
轨迹匹配
为给定输入硬编码一个参考轨迹,并通过逐步比较验证运行结果。适用于测试定义明确的工作流,您知道预期的行为。当您对应该调用哪些工具及其顺序有具体期望时使用。此方法是确定性的、快速的且成本效益高,因为它不需要额外的LLM调用。
LLM作为裁判
使用LLM定性验证智能体的执行轨迹。“裁判”LLM根据提示评分标准(可以包括参考轨迹)审查智能体的决策。更灵活,可以评估效率和适当性等细微方面,但需要LLM调用且确定性较低。当您想评估智能体轨迹的整体质量和合理性,而无需严格要求工具调用或顺序时使用。
安装 AgentEvals
或者,直接克隆 AgentEvals 仓库。
轨迹匹配评估器
AgentEvals 提供 create_trajectory_match_evaluator 函数,用于将智能体的轨迹与参考轨迹进行匹配。有四种模式可供选择:
| 模式 | 描述 | 使用场景 |
|---|
strict | 消息和工具调用完全按相同顺序匹配 | 测试特定序列(例如,策略查找在授权之前) |
unordered | 允许相同工具调用以任意顺序出现 | 验证信息检索时顺序无关紧要的情况 |
subset | 智能体仅调用参考轨迹中的工具(无额外工具) | 确保智能体未超出预期范围 |
superset | 智能体至少调用参考轨迹中的工具(允许额外工具) | 验证是否采取了所需的最低限度操作 |
strict 模式确保轨迹包含相同顺序的相同消息和相同工具调用,但允许消息内容有所不同。当您需要强制执行特定操作序列时(例如要求在授权前进行策略查找),这非常有用。from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.match import create_trajectory_match_evaluator
@tool
def get_weather(city: str):
"""获取城市天气信息。"""
return f"It's 75 degrees and sunny in {city}."
agent = create_agent("openai:gpt-4o", tools=[get_weather])
evaluator = create_trajectory_match_evaluator(
trajectory_match_mode="strict",
)
def test_weather_tool_called_strict():
result = agent.invoke({
"messages": [HumanMessage(content="旧金山的天气怎么样?")]
})
reference_trajectory = [
HumanMessage(content="旧金山的天气怎么样?"),
AIMessage(content="", tool_calls=[
{"id": "call_1", "name": "get_weather", "args": {"city": "San Francisco"}}
]),
ToolMessage(content="It's 75 degrees and sunny in San Francisco.", tool_call_id="call_1"),
AIMessage(content="旧金山的天气是75度,晴天。"),
]
evaluation = evaluator(
outputs=result["messages"],
reference_outputs=reference_trajectory
)
# {
# 'key': 'trajectory_strict_match',
# 'score': True,
# 'comment': None,
# }
assert evaluation["score"] is True
unordered 模式允许以任意顺序调用相同的工具,当您想验证特定信息是否被检索但不关心顺序时非常有用。例如,智能体可能需要同时检查城市的天气和活动,但顺序无关紧要。from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.match import create_trajectory_match_evaluator
@tool
def get_weather(city: str):
"""获取城市天气信息。"""
return f"It's 75 degrees and sunny in {city}."
@tool
def get_events(city: str):
"""获取城市中正在发生的活动。"""
return f"今晚在{city}的公园有音乐会。"
agent = create_agent("openai:gpt-4o", tools=[get_weather, get_events])
evaluator = create_trajectory_match_evaluator(
trajectory_match_mode="unordered",
)
def test_multiple_tools_any_order():
result = agent.invoke({
"messages": [HumanMessage(content="今天旧金山有什么活动?")]
})
# 参考轨迹显示工具调用顺序与实际执行顺序不同
reference_trajectory = [
HumanMessage(content="今天旧金山有什么活动?"),
AIMessage(content="", tool_calls=[
{"id": "call_1", "name": "get_events", "args": {"city": "SF"}},
{"id": "call_2", "name": "get_weather", "args": {"city": "SF"}},
]),
ToolMessage(content="今晚在旧金山的公园有音乐会。", tool_call_id="call_1"),
ToolMessage(content="旧金山75度,晴天。", tool_call_id="call_2"),
AIMessage(content="今天在旧金山:75度晴天,晚上公园有音乐会。"),
]
evaluation = evaluator(
outputs=result["messages"],
reference_outputs=reference_trajectory,
)
# {
# 'key': 'trajectory_unordered_match',
# 'score': True,
# }
assert evaluation["score"] is True
superset 和 subset 模式匹配部分轨迹。superset 模式验证智能体至少调用了参考轨迹中的工具,允许额外的工具调用。subset 模式确保智能体未调用参考轨迹之外的任何工具。from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.match import create_trajectory_match_evaluator
@tool
def get_weather(city: str):
"""获取城市天气信息。"""
return f"It's 75 degrees and sunny in {city}."
@tool
def get_detailed_forecast(city: str):
"""获取城市的详细天气预报。"""
return f"{city}的详细预报:整周晴天。"
agent = create_agent("openai:gpt-4o", tools=[get_weather, get_detailed_forecast])
evaluator = create_trajectory_match_evaluator(
trajectory_match_mode="superset",
)
def test_agent_calls_required_tools_plus_extra():
result = agent.invoke({
"messages": [HumanMessage(content="波士顿的天气怎么样?")]
})
# 参考轨迹仅要求 get_weather,但智能体可能会调用其他工具
reference_trajectory = [
HumanMessage(content="波士顿的天气怎么样?"),
AIMessage(content="", tool_calls=[
{"id": "call_1", "name": "get_weather", "args": {"city": "Boston"}},
]),
ToolMessage(content="波士顿75度,晴天。", tool_call_id="call_1"),
AIMessage(content="波士顿的天气是75度,晴天。"),
]
evaluation = evaluator(
outputs=result["messages"],
reference_outputs=reference_trajectory,
)
# {
# 'key': 'trajectory_superset_match',
# 'score': True,
# 'comment': None,
# }
assert evaluation["score"] is True
您还可以设置 tool_args_match_mode 属性和/或 tool_args_match_overrides 来自定义评估器如何考虑实际轨迹与参考轨迹中工具调用的相等性。默认情况下,仅考虑具有相同参数的相同工具的工具调用相等。有关更多详细信息,请访问 仓库。
LLM作为裁判评估器
您还可以使用LLM通过 create_trajectory_llm_as_judge 函数评估智能体的执行路径。与轨迹匹配评估器不同,它不需要参考轨迹,但如果可用,可以提供参考轨迹。
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.llm import create_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT
@tool
def get_weather(city: str):
"""获取城市天气信息。"""
return f"It's 75 degrees and sunny in {city}."
agent = create_agent("openai:gpt-4o", tools=[get_weather])
evaluator = create_trajectory_llm_as_judge(
model="openai:o3-mini",
prompt=TRAJECTORY_ACCURACY_PROMPT,
)
def test_trajectory_quality():
result = agent.invoke({
"messages": [HumanMessage(content="西雅图的天气怎么样?")]
})
evaluation = evaluator(
outputs=result["messages"],
)
# {
# 'key': 'trajectory_accuracy',
# 'score': True,
# 'comment': '提供的智能体轨迹是合理的...'
# }
assert evaluation["score"] is True
如果您有参考轨迹,可以在提示中添加一个额外变量并传入参考轨迹。下面,我们使用预构建的 TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE 提示并配置 reference_outputs 变量:evaluator = create_trajectory_llm_as_judge(
model="openai:o3-mini",
prompt=TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
)
evaluation = judge_with_reference(
outputs=result["messages"],
reference_outputs=reference_trajectory,
)
如需更多关于LLM如何评估轨迹的可配置性,请访问 仓库。
异步支持
所有 agentevals 评估器都支持 Python asyncio。对于使用工厂函数的评估器,可以通过在函数名中 create_ 后添加 async 来获得异步版本。
from agentevals.trajectory.llm import create_async_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT
from agentevals.trajectory.match import create_async_trajectory_match_evaluator
async_judge = create_async_trajectory_llm_as_judge(
model="openai:o3-mini",
prompt=TRAJECTORY_ACCURACY_PROMPT,
)
async_evaluator = create_async_trajectory_match_evaluator(
trajectory_match_mode="strict",
)
async def test_async_evaluation():
result = await agent.ainvoke({
"messages": [HumanMessage(content="天气怎么样?")]
})
evaluation = await async_judge(outputs=result["messages"])
assert evaluation["score"] is True
LangSmith 集成
为了随时间跟踪实验,您可以将评估器结果记录到 LangSmith,这是一个用于构建生产级LLM应用的平台,包括跟踪、评估和实验工具。
首先,通过设置必要的环境变量来设置 LangSmith:
export LANGSMITH_API_KEY="your_langsmith_api_key"
export LANGSMITH_TRACING="true"
LangSmith 提供两种主要的评估运行方法:pytest 集成和 evaluate 函数。
import pytest
from langsmith import testing as t
from agentevals.trajectory.llm import create_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT
trajectory_evaluator = create_trajectory_llm_as_judge(
model="openai:o3-mini",
prompt=TRAJECTORY_ACCURACY_PROMPT,
)
@pytest.mark.langsmith
def test_trajectory_accuracy():
result = agent.invoke({
"messages": [HumanMessage(content="旧金山的天气怎么样?")]
})
reference_trajectory = [
HumanMessage(content="旧金山的天气怎么样?"),
AIMessage(content="", tool_calls=[
{"id": "call_1", "name": "get_weather", "args": {"city": "SF"}},
]),
ToolMessage(content="旧金山75度,晴天。", tool_call_id="call_1"),
AIMessage(content="旧金山的天气是75度,晴天。"),
]
# 将输入、输出和参考输出记录到 LangSmith
t.log_inputs({})
t.log_outputs({"messages": result["messages"]})
t.log_reference_outputs({"messages": reference_trajectory})
trajectory_evaluator(
outputs=result["messages"],
reference_outputs=reference_trajectory
)
使用 pytest 运行评估:pytest test_trajectory.py --langsmith-output
结果将自动记录到 LangSmith。
或者,您可以在 LangSmith 中创建数据集并使用 evaluate 函数:from langsmith import Client
from agentevals.trajectory.llm import create_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT
client = Client()
trajectory_evaluator = create_trajectory_llm_as_judge(
model="openai:o3-mini",
prompt=TRAJECTORY_ACCURACY_PROMPT,
)
def run_agent(inputs):
"""返回轨迹消息的智能体函数。"""
return agent.invoke(inputs)["messages"]
experiment_results = client.evaluate(
run_agent,
data="your_dataset_name",
evaluators=[trajectory_evaluator]
)
结果将自动记录到 LangSmith。
记录和重放 HTTP 调用
调用真实LLM API的集成测试可能很慢且昂贵,尤其是在CI/CD管道中频繁运行时。我们建议使用一个库来记录HTTP请求和响应,然后在后续运行中重放它们,而无需进行实际的网络调用。
您可以使用 vcrpy 来实现这一点。如果您使用 pytest,pytest-recording 插件 提供了一种简单的方法,只需最少的配置即可启用此功能。请求/响应被记录在磁带中,然后在后续运行中用于模拟真实的网络调用。
在 conftest.py 文件中设置过滤掉磁带中的敏感信息:
import pytest
@pytest.fixture(scope="session")
def vcr_config():
return {
"filter_headers": [
("authorization", "XXXX"),
("x-api-key", "XXXX"),
# ... 您想屏蔽的其他标头
],
"filter_query_parameters": [
("api_key", "XXXX"),
("key", "XXXX"),
],
}
然后配置您的项目以识别 vcr 标记:
[pytest]
markers =
vcr: 通过VCR记录/重放HTTP
addopts = --record-mode=once
--record-mode=once 选项在第一次运行时记录HTTP交互,并在后续运行中重放它们。
现在,只需用 vcr 标记装饰您的测试:
@pytest.mark.vcr()
def test_agent_trajectory():
# ...
第一次运行此测试时,您的智能体会进行真实的网络调用,pytest 会在 tests/cassettes 目录中生成一个磁带文件 test_agent_trajectory.yaml。后续运行将使用该磁带模拟真实的网络调用,前提是智能体的请求与上一次运行相比没有变化。如果发生变化,测试将失败,您需要删除磁带并重新运行测试以记录新的交互。
当您修改提示词、添加新工具或更改预期轨迹时,保存的磁带将过时,您现有的测试将失败。您应删除相应的磁带文件并重新运行测试以记录新的交互。