Deep Agents Eval 测试套件全解析:从轨迹断言到外部基准的端到端评测体系
【免费下载链接】deepagentsThe batteries-included agent harness.项目地址: https://gitcode.com/GitHub_Trending/de/deepagents
导读
本文围绕 Deep Agents SDK 评测体系的测试核心——libs/evals/tests/evals/目录展开,系统讲解其目录结构、pytest 基础设施、基于轨迹(trajectory)的"正确性 + 效率"双轨断言模型、LLM-as-judge 自动打分、JSON 效率报告插件,以及 MemoryAgentBench、tau2-bench airline 与 BFCL v3 等外部基准的接入方式。读完本文,你将掌握如何阅读、运行、扩写这套真实调用 LLM 的端到端行为评测套件,并理解每个组件背后的源码实现与数据流。
目录全景:tests/evals 承担什么角色
tests/evals/是 Deep Agents SDK 的行为评测测试套件所在目录。它与传统单元测试不同:每个 eval 都会用一个真实的 LLM 运行一个 agent,捕获完整轨迹(工具调用、文件变更、最终回复),然后从正确性和效率两个维度打分。目录的官方说明见 tests/evals/README.md,其明确列出的组件包括:
| 组件 | 职责 |
|---|---|
conftest.py | pytest 夹具:--model选项、model/model_name夹具、LangSmith 实验元数据 |
utils.py | 核心框架:AgentTrajectory、断言类、TrajectoryScorer、run_agent |
llm_judge.py | 基于 openevals 的 LLM-as-judge 断言 |
pytest_reporter.py | 自定义 pytest 插件,产出效率总结报告 |
fixtures/ | 静态测试数据(如摘要种子消息) |
data/ | 基准样本数据(FRAMES、Nexus、BFCL v3)与 BFCL API 实现 |
memory_agent_bench/ | MemoryAgentBench(ICLR 2026)评测运行器 |
tau2_airline/ | tau2-bench 航空领域评测(来自 Sierra Research 的 tau-bench,MIT License) |
从仓库结构看,该目录与libs/evals/tests/unit_tests/(纯单元测试)并列,体现了"单元测试验证机制、evals 验证端到端行为"的分层设计。根 libs/evals/README.md 将其定位为"面向 Deep Agents SDK 的端到端行为评测套件",并说明每个 eval 都会把完整轨迹与正确性、效率分数同步到 LangSmith。
基础设施层:conftest.py 的 pytest 集成
conftest.py 是整套评测的 pytest 入口,承担三个关键职责。
1. 启动即校验:LangSmith 追踪与模型必须就绪
pytest_configure在会话启动阶段就强制两条前置条件,不满足则直接pytest.exit:
- 必须开启 LangSmith 追踪:
LANGSMITH_TRACING、LANGSMITH_TRACING_V2、LANGCHAIN_TRACING_V2、LANGCHAIN_TRACING任一为"true"即可,否则整套测试被跳过并给出明确报错; - 必须传入
--model:每个 eval 都依赖真实模型,缺失时直接退出。
此外它还注册了三类自定义 marker:
config.addinivalue_line("markers", "eval_category(name): ...") config.addinivalue_line("markers", "eval_tier(name): ...") config.addinivalue_line("markers", "repl(*allowed): ...")其中eval_category用于分组(如memory、tool_use),eval_tier用于区分baseline(回归门槛)与hillclimb(进度追踪)两个层级,repl用于声明可选的 REPL 后端(与--repl quickjs搭配)。
2. 全套 CLI 选项
pytest_addoption暴露了评测专用的命令行参数:
| 选项 | 说明 |
|---|---|
--model | 必填,评测使用的模型标识,如--model claude-sonnet-4-6 |
--eval-category | 可重复,只运行指定类别的 eval,如--eval-category memory --eval-category tool_use |
--eval-category-exclude | 可重复,排除指定类别(优先于 include) |
--eval-tier | 可重复,只运行指定层级的 eval |
--openrouter-provider | 逗号分隔的 OpenRouter 供应商白名单,如MiniMax,Fireworks |
--openrouter-allow-fallbacks | 允许 OpenRouter 在列表供应商不可用时回退,默认严格不回退 |
--openai-reasoning-effort | 仅对 OpenAI 模型生效,取值为minimal/low/medium/high/xhigh |
--repl | 可选 REPL 中间件,当前支持quickjs |
pytest_collection_modifyitems会依据eval_category/eval_tiermarker 对收集到的测试项做过滤,并且如果传入的 include/exclude 值与测试中实际存在的 marker 值对不上,会直接以返回码 1 退出,避免"静默跑空"。
3. 夹具体系
model_name:由pytest_generate_tests把--model参数化到每个测试;model:通过init_chat_model构建真实模型实例。源码中对 OpenRouter 前缀强制设置 120 秒超时(规避 SDK 默认 5 秒读超时导致的 TCP 悬挂),对openai:前缀显式开启use_responses_api,并可叠加reasoning_effort;langsmith_experiment_metadata(session 级):记录model、运行日期与deepagents_version,作为 LangSmith 实验元数据。
核心框架:utils.py 的轨迹与断言模型
utils.py 是整个评测框架的心脏,约 1500 行,定义了从轨迹建模到断言执行的全套机制。
AgentTrajectory 与 AgentStep
@dataclass(frozen=True) class AgentStep: index: int # 从 1 开始计数 action: AIMessage # agent 的输出,可能包含工具调用 observations: list[ToolMessage] @dataclass(frozen=True) class AgentTrajectory: steps: list[AgentStep] files: dict[str, str] @property def answer(self) -> str: ... # 最后一步的文本内容 def pretty(self) -> str: ... # 人类可读的轨迹摘要AgentTrajectory把agent.invoke()的原始结果(messages 列表 + files 通道)规整为结构化数据:AIMessage构成 step、ToolMessage挂到对应 step 的 observations,files则统一转换为dict[str, str](兼容字符串与{content: ...}两种表示)。pretty()生成的格式会被llm_judge.py直接复用作为判官 prompt 的输入。此外_strip_common_zero_width会剔除零宽字符,避免模型插入的隐形 Unicode 破坏字符串比对。
双轨断言体系:正确性硬校验 + 效率软记录
框架定义了两种断言基类:
SuccessAssertion(正确性):违反即通过pytest.fail硬失败测试;EfficiencyAssertion(效率):只记录日志、永不让测试失败。
每个断言都实现check(trajectory) -> bool与describe_failure(trajectory) -> str,失败信息会附带完整轨迹打印,便于定位问题。
正确性断言(SuccessAssertion),工厂函数与字段如下:
| 断言 | 核心字段 | 语义 |
|---|---|---|
FinalTextContains | text,case_insensitive | 最终回复必须包含子串 |
FinalTextExcludes | text,case_insensitive | 最终回复不得包含子串 |
FinalTextContainsAny | texts,case_insensitive | 最终回复至少包含一组等价措辞之一(如 "unknown"/"no data"/"n/a"),用于检验"诚实承认信息缺失"而非幻觉编造 |
FinalTextMinLength | n | 最终回复去除空白后至少 n 字符,用于过滤"凑字数的收尾话术" |
FileEquals | path,content | 轨迹中的文件内容与期望完全一致 |
FileContains/FileExcludes | path,substring | 文件包含/不包含某子串 |
FileAbsent | path | 文件路径必须不存在(比FileExcludes更严格——删除操作会把 key 从 files 通道中整个移除) |
ToolCalled | name,step,args_contains,args_equals | 轨迹中必须存在匹配的工具调用 |
ToolNotCalled | 同上 | 轨迹中不得出现匹配的工具调用(硬失败版,用于拦截"无目标却反射性调用 update_goal"之类的行为) |
工具调用的选择器有两条构造期防线(_validate_tool_call_selector):step必须为正(1-indexed),args_contains与args_equals互斥,避免歧义匹配让ToolNotCalled空转通过。
效率断言(EfficiencyAssertion):
| 断言 | 字段 | 语义 |
|---|---|---|
AgentSteps | n | 轨迹恰好 n 个 agent step |
ToolCallRequests | n | 工具调用请求总数恰好为 n |
MaxToolCallRequests | n | 工具调用请求数至多 n,用于捕获"简单任务却 cargo-cult 规划工具"的退化 |
ToolCall | name,step,args_contains,args_equals | 特定工具调用发生过(软记录) |
TrajectoryScorer:两段式构建器
@dataclass(frozen=True) class TrajectoryScorer: _success: tuple[SuccessAssertion, ...] = () _expectations: tuple[EfficiencyAssertion, ...] = () def success(self, *assertions: SuccessAssertion) -> TrajectoryScorer: ... def expect(self, *, agent_steps=None, tool_call_requests=None, tool_calls=None) -> TrajectoryScorer: ....success()追加硬校验,.expect()追加效率期望,二者都返回新的 scorer(不可变风格)。_assert_expectations在断言执行时:先通过_log_efficiency把实际/期望的agent_steps、tool_call_requests写入 LangSmith feedback(t.log_feedback),再依次执行成功断言,任一不满足即pytest.fail并记录correctness=0。
run_agent / run_agent_async:统一入口
def run_agent(agent, *, query, model, initial_files=None, scorer=None, thread_id=None, eval_metadata=None, extra_state=None) -> AgentTrajectory:run_agent封装了"构造输入 → 记录 LangSmith 输入 → invoke → 记录输出 → 构建轨迹 → 执行断言"的完整链路,支持同步/异步两个版本。extra_state允许注入中间件持有的状态(如{"rubric": "..."}供RubricMiddleware使用),thread_id缺省时自动生成 UUID。每个 eval 测试本质上就是"创建一个 agent、调一次run_agent、挂一个TrajectoryScorer"。
扩展正确性手段:llm_judge.py 的 LLM-as-judge
当字符串断言无法覆盖语义标准时,llm_judge.py 提供了基于 openevals 的LLMJudge断言:
def llm_judge(*criteria: str, judge_model: str = _DEFAULT_JUDGE_MODEL, include_tool_calls: bool = False) -> LLMJudge:关键设计:
- 逐条独立判分:每个 criterion 通过
create_llm_as_judge独立评估,所有 criterion 通过断言才算成功,任一失败即硬失败; - 并发评估:多个 criterion 通过
ThreadPoolExecutor(最多 8 个 worker)并行调用,结果按下标归位,保证失败信息确定有序;单条失败不会短路其余评估; - 两种上下文:
include_tool_calls=False(默认)时判官只看 agent 的文本回复,适合评判"说了什么";True时判官看到完整轨迹(工具调用 + 文本),适合评判"做了什么"(如是否真的写了文件)。判官 prompt 明确提示"工具调用是真实执行过的动作,应视为行为证据"; - 失败诊断:
describe_failure给出逐条判分注释;llm_judge_all_passed聚合分数回写 LangSmith; - 健壮性:轨迹为空、判官调用抛错、openevals 返回结构异常都会给出明确报错。
默认判官模型为claude-sonnet-4-6(见_DEFAULT_JUDGE_MODEL)。
结果落盘:pytest_reporter.py 与效率指标
pytest_reporter.py 是注册在conftest.py中的 pytest 插件(pytest_plugins = ["tests.evals.pytest_reporter"]),在会话结束后汇总并输出 JSON 报告。
核心行为
- 改写 pytest 退出码:
pytest_sessionfinish中,当单个 eval 失败(exitstatus=1)但确有测试运行时,会把会话退出码改写为 0。这是有意为之——评测失败不应中断 CI 流水线,后续聚合/报告步骤必须照常执行;但如果一个测试都没跑(配置错误或收集崩溃),则保留非零退出码"响亮失败"; - 效率数据收集:通过
_evals_utils._on_efficiency_result回调接收每个测试的EfficiencyResult,并补记duration_s与passed; - LangSmith 实验链接:会话开始时若设置了
LANGSMITH_TEST_SUITE,会预创建 LangSmith 实验并在终端输出公开/内部对比链接; - 失败明细:每个失败测试记录
test_name、category、failure_message(超长信息截断到 30k 字符,约 7500 token),供后续--retry-failed复用。
报告中的关键指标
插件计算并通过终端与 JSON 报告输出的指标包括:
| 指标 | 计算方式 | 说明 |
|---|---|---|
correctness | passed / total | 整体正确率 |
category_scores | 按eval_category分组的 passed/total | 分类正确率 |
step_ratio | sum(actual_steps) / sum(expected_steps) | 步数效率比(无期望时为空) |
tool_call_ratio | sum(actual_tool_calls) / sum(expected_tool_calls) | 工具调用效率比 |
solve_rate | passed 且含步数期望与耗时的测试的 expected_steps/duration 均值,失败测试计 0 | 求解速率 |
median_duration_s | 各测试 call 阶段耗时的中位数 | 延迟表现 |
JSON 报告可通过--evals-report-file或环境变量DEEPAGENTS_EVALS_REPORT_FILE指定路径;报告包含created_at、sdk_version、model、计数、上述指标、experiment_urls、experiment_links与failures数组。
数据资产:fixtures/ 与 data/
fixtures/:静态测试数据,例如summarization_seed_messages.json,供摘要类测试复用。data/benchmark_samples/:三个精选外部基准子集的样本——frames_final.json(检索类)、nexus_final.json(推理类)、bfcl_v3_final.json(函数调用类)。external_benchmarks.py 从这些文件各选取 5 个高难度用例并断言数量,构成curated_external_hard子集:- FRAMES(检索/文件回退):
_create_file_backed_agent用固定 system prompt 构造 agent,initial_files注入工作区文件,用"归一化子串存在"断言评分(忽略空白、大小写与引号变体); - Nexus(推理):同样采用文件回退 + 文本评分;
- BFCL v3(有状态工具调用):
data/bfcl_apis/下实现VehicleControlAPI、MessageAPI、TradingBot、TravelAPI、TicketAPI五个有状态 API 类。评测把 API 的公开方法包装为StructuredTool注入 agent,用MemorySaver做多轮对话,最后重放 ground truth 调用到全新实例上,逐属性对比模型实例与真值实例的最终状态——即"状态一致性"评分,比比对调用字符串更严格。
- FRAMES(检索/文件回退):
外部基准接入(一):memory_agent_bench/
memory_agent_bench/ 是 MemoryAgentBench(ICLR 2026)的评测运行器,核心是 configs.py 中的DatasetConfig数据类:
@dataclass(frozen=True) class DatasetConfig: split: str # HuggingFace 数据集 split,如 Conflict_Resolution source: str # 与数据集 metadata.source 匹配的来源标识 chunk_size: int = 4096 # 记忆阶段每个文本块的 token 预算 max_samples: int = 1 # 评估的上下文样本数上限 max_questions: int | None = None # 每个样本的提问数上限配置按四个能力维度组织,每个维度配多档上下文长度或难度:
| 维度 | 配置示例 | 关注点 |
|---|---|---|
Conflict_Resolution(单跳/多跳) | CR_SH_6K~CR_SH_262K、CR_MH_6K~CR_MH_262K | 事实合并与冲突消解 |
Test_Time_Learning(ICL) | TTL_BANKING77、TTL_CLINIC150、TTL_NLU、TTL_TREC_COARSE/FINE、TTL_RECSYS | 测试期学习/上下文内分类 |
Accurate_Retrieval | AR_RULER_QA1/QA2、AR_LONGMEMEVAL、AR_EVENTQA_FULL/64K/128K | 精确检索(含多跳与时间推理) |
Long_Range_Understanding | LRU_INFBENCH_SUM、LRU_DETECTIVE_QA | 长程理解(生成与推理问答) |
ALL_CONFIGS汇总全部 23 个配置;CI_CONFIGS提供面向 CI 的高信号子集(每类 2 个、偏向更难变体,注释中给出了如"SH@64K 在合理上下文长度下梯度良好""MH@6K 是最便宜的近零 canary"等选型理由)。配套的 data_utils.py 负责从 HuggingFace 拉取数据并按配置切分,eval_utils.py 提供评测逻辑,test_memory_agent_bench.py 将其接入 pytest。
外部基准接入(二):tau2_airline/
tau2_airline/ 是 tau2-bench 航空领域的 vendored 实现(源自 Sierra Research 的 tau-bench,MIT License)。目录下的data/db.json、policy.md、tasks.json为领域数据,且按约定必须与上游逐字节一致(不得重新格式化,详见 libs/evals/AGENTS.md)。
其核心是多轮对话编排器 runner.py:run_multi_turn让 deepagents agent 与 LLM 驱动的用户模拟器(UserSimulator)来回对话,最多 30 轮(DEFAULT_MAX_TURNS),记录完整 transcript、工具调用日志与终止原因(user_stop/max_turns):
for turn in range(max_turns): trajectory = run_agent(agent, query=user_msg, model=model, thread_id=thread_id) agent_msg = trajectory.answer ... user_msg = user_sim.respond(agent_msg)配合 domain.py(领域模型与工具)、user_sim.py(用户模拟器)、evaluation.py(结果评分),test_tau2_airline.py 将整套流程接入评测套件。这种"agent 对打 LLM 模拟用户"的范式,能覆盖自由对话式任务,弥补单轮 prompt 评测的不足。
如何运行这套评测
虽然tests/evals本质上是 pytest 测试,仓库推荐通过规范入口deepagents-evals(console script,定义于 libs/evals/pyproject.toml 的[project.scripts])运行,详见 libs/evals/AGENTS.md:
# 单模型单次运行 deepagents-evals run --model claude-opus-4-7 # 按类别与层级过滤,输出 JSON 报告 deepagents-evals run --model openai:gpt-5.5 \ --eval-category memory --eval-tier baseline --report evals_report.json # 多次运行并聚合统计 deepagents-evals trials --model openai:gpt-5.5 --trials 3 # 只重跑上次失败的用例 deepagents-evals trials --model openai:gpt-5.5 --trials 1 \ --retry-failed trial_runs/trials_summary.jsonCLI 还提供list(发现类别/层级/模型/eval)、aggregate(聚合离线报告)、radar(生成雷达图)、catalog/model-groups(检查生成文档)等子命令;DEEPAGENTS_EVALS_MODEL环境变量可省略--model。运行前置条件与 pytest 层一致:必须开启 LangSmith 追踪(LANGSMITH_TRACING=true+LANGSMITH_API_KEY),并配置与所选模型匹配的 provider key。make evals MODEL=...、make evals-trials MODEL=... TRIALS=...仍可用(CI 采用的形式),CLI 是其超集。
CLI 的退出码语义是自动化接入的关键:0表示成功;1表示存在评测失败(由trials_summary.json中聚合的counts.failed.mean > 0判定,而非单次 pytest 退出码——因为 pytest_reporter 会把单次运行的退出码改写成 0);2表示配置错误或生成文档过期;3表示没有可用报告。请勿解析人类可读输出,应以退出码与trials_summary.json驱动自动化。聚合报告的 JSONC 结构(metrics、counts、category_scores、trials各字段及每 trial 的experiment_urls、pytest_returncode)均记录在 libs/evals/AGENTS.md 中,可供读者直接参考对接。
延伸阅读
- libs/evals/tests/evals/README.md——本套件的目录索引与组件清单
- libs/evals/tests/evals/utils.py——轨迹与断言核心框架
- libs/evals/tests/evals/conftest.py——pytest 集成与模型夹具
- libs/evals/tests/evals/pytest_reporter.py——效率报告插件
- libs/evals/tests/evals/external_benchmarks.py——FRAMES/Nexus/BFCL v3 精选子集评测
- libs/evals/AGENTS.md——
deepagents-evalsCLI 用法、退出码与报告 schema - libs/evals/README.md——评测套件总览
- libs/evals/EVAL_CATALOG.md、libs/evals/MODEL_GROUPS.md——完整 eval 清单与模型分组目录
【免费下载链接】deepagentsThe batteries-included agent harness.项目地址: https://gitcode.com/GitHub_Trending/de/deepagents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考