https://blog.langchain.com/evaluating-deep-agents-our-learnings/
过去一个月,LangChain 基于 Deep Agents 框架交付了四个应用:
- DeepAgents CLI: 一个编码 agent
- LangSmith Assist: LangSmith 内置的智能助手
- Personal Email Assistant: 能从用户交互中学习的邮件助手
- Agent Builder: 无代码 agent 搭建平台
在开发和部署这些 agent 的过程中,团队为每个应用都配置了评估系统,积累了不少实战经验。这篇文章会深入探讨评估深度 agent 的几个关键模式:
- 深度 agent 需要为每个测试用例定制测试逻辑 —— 每个案例都有自己独特的成功标准
- 单步运行 特别适合验证特定场景下的决策(还能省 token)
- 完整轮次运行 适合测试 agent 的"最终状态"
- 多轮对话测试 能模拟真实用户交互,但需要保持测试可控
- 环境配置很关键 —— 深度 agent 需要干净、可复现的测试环境
先解释几个后文会用到的概念。
Agent 的运行方式:
- 单步 (Single step): 限制核心 agent 循环只运行一轮,看它会采取什么行动
- 完整轮次 (Full turn): 让 agent 完整处理一次输入,可能包含多次工具调用
- 多轮对话 (Multiple turns): 让 agent 完整运行多次,模拟用户和 agent 之间的多次交互
可以测试的内容:
- 执行轨迹 (Trajectory): agent 调用了哪些工具,传了什么参数
- 最终响应 (Final response): agent 返回给用户的最终回复
- 其他状态 (Other state): agent 运行过程中产生的其他内容(比如文件、制品等)
1 深度 Agent 的每个测试用例都需要定制化逻辑
传统的 LLM 评估流程很直接:
- 构建示例数据集
- 写一个评估器
- 用应用处理数据集生成输出,然后用评估器打分
每个数据点的处理方式完全一样 —— 跑同样的应用逻辑,用同样的评估器打分。
深度 Agent 打破了这个假设。除了最终消息,还需要测试更多内容。每个数据点的"成功标准"可能完全不同,需要针对 agent 的轨迹和状态做特定的断言。
看个例子:
有个日历调度 agent,能记住用户偏好。用户说"记住,不要在早上 9 点前安排会议"。需要验证这个 agent 是否在文件系统中更新了自己的记忆。
为了测试这个场景,可能需要验证:
- agent 是否对
memories.md
文件调用了
edit\_file - agent 是否在最终消息中告知用户记忆已更新
- memories.md 文件是否真的包含了不安排早会的信息。可以:
- 用正则表达式查找"9am"
- 或者用 LLM-as-judge 配合特定成功标准,做更全面的文件更新分析
LangSmith 的 Pytest 和 Vitest 集成支持这种定制化测试。可以针对每个测试用例,对 agent 的轨迹、最终消息和状态做不同的断言。
# 标记为 LangSmith 测试用例
@pytest.mark.langsmith
def test\_remember\_no\_early\_meetings() -> None:
user\_input = "I don't want any meetings scheduled before 9 AM ET"
# 可以把 agent 的输入记录到 LangSmith
t.log\_inputs({"question": user\_input})
response = run\_agent(user\_input)
# 可以把 agent 的输出记录到 LangSmith
t.log\_outputs({"outputs": response})
agent\_tool\_calls = get\_agent\_tool\_calls(response)
# 断言 agent 调用了 edit\_file 工具来更新记忆
assert any([tc["name"] == "edit\_file"and tc["args"]["path"] == "memories.md"for tc in agent\_tool\_calls])
# 用 LLM-as-judge 记录反馈: 最终消息是否确认了记忆更新
communicated\_to\_user = llm\_as\_judge\_A(response)
t.log\_feedback(key="communicated\_to\_user", score=communicated\_to\_user)
# 用 LLM-as-judge 记录反馈: 记忆文件是否包含正确信息
memory\_updated = llm\_as\_judge\_B(response)
t.log\_feedback(key="memory\_updated", score=memory\_updated)
关于如何使用 Pytest 的通用代码示例,可以查看这份文档:
这个 LangSmith 集成会自动把所有测试用例记录到实验中,这样就能查看失败用例的 trace(方便调试),还能跟踪结果的变化趋势。
2 单步评估很实用,效率也高
在深度 Agent 的评估中,大约一半的测试用例都是单步评估,也就是说,在特定的输入消息序列之后,LLM 会立即做什么决策?
这对验证 agent 在特定场景下是否用正确的参数调用了正确的工具特别有用。常见的测试场景包括:
- 是否调用了正确的工具来搜索会议时间?
- 是否检查了正确的目录内容?
- 是否更新了记忆?
回归问题往往出现在单个决策点,而不是整个执行序列。如果用 LangGraph,它的流式能力可以在单次工具调用后中断 agent 来检查输出 —— 这样能在不跑完整个 agent 序列的情况下及早发现问题。
在下面的代码中,手动在 tools 节点前设置了一个断点,这样就能轻松地让 agent 只跑一步。然后就可以检查单步之后的状态并做断言。
@pytest.mark.langsmith
def test\_single\_step() -> None:
state\_before\_tool\_execution = await agent.ainvoke(
inputs,
# interrupt\_before 指定要在哪些节点前停止
# 在 tool 节点前中断,就能检查工具调用参数
interrupt\_before=["tools"]
)
# 可以看到 agent 的消息历史,包括最新的工具调用
print(state\_before\_tool\_execution["messages"])
3 完整轮次运行能看到全貌
把单步评估当作"单元测试",确保 agent 在特定场景下采取预期行动。而完整轮次运行也很有价值 —— 能看到 agent 端到端执行的完整画面。
完整轮次运行可以从多个角度测试 agent 行为:
1) 执行轨迹: 评估完整轨迹的一个常见方法是确保某个特定工具在执行过程中的某个时刻被调用了,但不关心具体时机。在日历调度的例子中,调度器可能需要多次工具调用才能找到适合所有参与者的时间段。
2) 最终响应: 有时候最终输出的质量比 agent 走的具体路径更重要。在编码和研究这类开放式任务中,这种情况更常见。
3) 其他状态: 评估其他状态和评估 agent 的最终响应很相似。有些 agent 不是以聊天格式回复用户,而是创建制品。在 LangGraph 中检查 agent 的状态,就能轻松查看和测试这些制品。
- 对于编码 agent → 读取并测试 agent 写的文件
- 对于研究 agent → 断言 agent 找到了正确的链接或来源
完整轮次运行能看到 agent 执行的全貌。LangSmith 把完整的 agent 轮次以 trace 的形式展示,既能看到延迟和 token 使用量这些高层指标,也能分析每次模型调用或工具调用的具体步骤。
4 多轮对话测试模拟真实用户交互
有些场景需要测试 agent 在多轮对话中的表现,也就是多个连续的用户输入。挑战在于,如果直接硬编码一系列输入,一旦 agent 偏离了预期路径,后续硬编码的用户输入可能就不合理了。
团队的解决方案是在 Pytest 和 Vitest 测试中加入条件逻辑。比如:
- 运行第一轮,然后检查 agent 输出
- 如果输出符合预期,继续下一轮
- 如果不符合预期,提前让测试失败(因为可以灵活地在每步之后加检查,这个方案可行)
这种方法可以运行多轮评估,而不用对 agent 的每个可能分支都建模。如果想单独测试第二轮或第三轮,只需从那个点开始设置测试,配上合适的初始状态就行。
5 搭建合适的评估环境很重要
深度 Agent 是有状态的,专门用来处理复杂的长时任务 —— 通常需要更复杂的环境来做评估。
不像简单的 LLM 评估,环境通常只包含几个无状态工具,深度 Agent 需要为每次评估运行提供一个全新、干净的环境,才能保证结果可复现。
编码 agent 就是个典型例子。Harbor 为 TerminalBench 提供了一个在专用 Docker 容器或沙箱中运行的评估环境。对于 DeepAgents CLI,团队用了更轻量的方案: 为每个测试用例创建一个临时目录,在里面运行 agent。
总结一下: 深度 Agent 评估需要每个测试都能重置的环境 -- 否则评估会变得不稳定,难以复现。
小技巧: Mock API 请求
LangSmith Assist 需要连接真实的 LangSmith API。对真实服务运行评估既慢又贵。更好的办法是把 HTTP 请求记录到文件系统,测试时重放它们。Python 可以用 vcr;JS 的话,可以通过 Hono app 代理 fetch 请求。
Mock 或重放 API 请求能让深度 Agent 评估更快、更容易调试,特别是当 agent 严重依赖外部系统状态时。 (这个很重要,Mock假接口可以帮我快速打通流程,验证输入和输出有效性,这个也是笔者经常调试的一个手段)
添加微信,备注” LLM “进入大模型技术交流群
如果你觉得这篇文章对你有帮助,别忘了点个赞、送个喜欢
/ 作者:ChallengeHub小编
/ 作者:欢迎转载,标注来源即可
