DeerFlow skill-creator 的 Grader Agent:一套面向技能评测的证据式评分协议
【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow
DeerFlow 仓库内置的skill-creator公共技能提供了一条"起草技能 → 运行测试 → 量化评分 → 迭代改进"的完整评测闭环,其中skills/public/skill-creator/agents/grader.md定义了闭环里的评分器(Grader Agent):它负责逐条断言判分、抽取并核验执行输出中的隐含声明、并把执行指标与耗时数据汇总成结构化的grading.json。读完本文,你将掌握 Grader 的完整工作流程、PASS/FAIL 判定标准、grading.json的每一类字段语义,以及下游的aggregate_benchmark.py与 eval viewer 是如何消费这份评分产物的。
Grader 在技能评测流水线中的位置
skill-creator的核心循环由 SKILL.md 定义:确定意图并起草 SKILL.md → 并行 spawn "带技能"与"基线"两类子代理执行测试用例 → 打分(Grade)→ 聚合基准(Aggregate)→ 启动评测查看器(viewer)收集人工反馈 → 根据反馈改写技能并进入下一轮迭代。
Grader 正是在"打分"这一步被调度的。SKILL.md 的 Step 4 明确要求:
- 为每次运行 spawn 一个 grader 子代理(或内联评分),该子代理读取 agents/grader.md,把每条断言(assertion)对照输出逐一评估;
- 结果保存到每个运行目录(run directory)下的
grading.json; grading.json的expectations数组必须使用text、passed、evidence三个字段名(而不是name/met/details之类的变体)——评测查看器依赖这些精确的字段名。
skill-creator的技能目录结构如下,grader 所在的agents/目录存放的是各专职子代理的指令文档:
skill-creator/ ├── SKILL.md ├── agents/ │ ├── analyzer.md # 事后分析器:分析盲评中赢家为何胜出 │ ├── comparator.md # 盲测对比器:对两份输出做匿名 A/B 比较 │ └── grader.md # 评分器:对照断言给输出判分 ├── references/ │ └── schemas.md # evals.json / grading.json 等 JSON 结构定义 ├── scripts/ │ └── aggregate_benchmark.py # 聚合 grading.json 生成 benchmark 统计 └── eval-viewer/ └── generate_review.py # 生成人工评审 HTML,读取 grading.jsonGrader 与 analyzer.md、comparator.md 是三种不同角色:后两者用于可选的盲测 A/B 对比场景,而 grader 是每轮迭代必跑的量化评分环节。
角色定义与输入参数
grader.md 开篇即定义了 Grader 的双重职责:
The Grader reviews a transcript and output files, then determines whether each expectation passes or fails. Provide clear evidence for each judgment.
You have two jobs: grade the outputs, and critique the evals themselves.
第一份工作是给输出判分,第二份工作是反向批判评测集本身。文档特别强调:一个薄弱断言上的满分比没有分更糟——它制造虚假的信心。当发现某条断言"过于容易满足"(trivially satisfied),或某个重要结果根本没有任何断言覆盖时,Grader 必须指出来。这一设计使评分产物不仅是"成绩单",还是评测集自身的改进信号。
Grader 从 prompt 中接收三个输入参数:
| 参数 | 类型 | 说明 |
|---|---|---|
expectations | 字符串列表 | 待评估的断言集合(来自 schemas.md 中evals.json的expectations字段) |
transcript_path | 路径 | 执行过程转录文件(markdown),记录了任务 prompt、执行步骤与最终结果 |
outputs_dir | 目录 | 执行器(executor)产出的输出文件目录 |
八步评分流程
Grader 的 Process 部分给出八个步骤,按顺序执行。下面逐步说明,并标注每一步的产物落点。
Step 1:通读转录
完整读取transcript_path指向的转录文件,记录三件事:eval prompt(原始任务)、执行步骤、最终结果;同时识别转录中记载的任何问题或错误。转录是"执行器声称做了什么"的唯一过程证据,但 Grader 并不止步于此——后面会看到它对转录保持有保留的信任。
Step 2:检查输出文件
- 列出
outputs_dir中的全部文件; - 读取/检查每条断言相关的文件。文档明确要求:如果输出不是纯文本,使用 prompt 中提供的检查工具直接查看真实文件,不要只依赖转录里对产物的转述;
- 记录内容、结构与质量。
这是"证据式评分"的关键设计:断言是否成立,最终由产物文件本身裁决,而非由执行器的自述裁决。
Step 3:逐条评估断言
对每一条 expectation 执行三步:
- 搜寻证据——在转录和输出文件中查找支持或反驳该断言的内容;
- 判定结论:
- PASS:有明确证据证明断言为真,且证据反映的是真正的任务完成,而非表面合规(not just surface-level compliance);
- FAIL:没有证据、证据与断言矛盾、或证据是表面的——典型例子是"文件名正确但内容为空或错误";
- 引用证据——直接引用具体文本或描述发现的内容,写入
evidence字段。
Step 4:抽取并核验隐含声明(Claims)
在预定义断言之外,Grader 还要从转录和输出中主动抽取隐含声明并逐一核验:
- 事实声明(factual),如"该表单有 12 个字段"——可对照输出文件核实;
- 过程声明(process),如"使用 pypdf 填写了表单"——可从转录中核实;
- 质量声明(quality),如"所有字段都正确填写了"——需评估该声明是否站得住脚。
对于无法用现有信息核验的声明,必须显式标记(verified: false或标注为 unverifiable)。这一步的价值在于捕获预定义断言可能漏掉的缺口——执行器输出中"说了但没人查"的内容,正是最容易藏问题的地方。
Step 5:读取执行器备注
如果{outputs_dir}/user_notes.md存在:
- 读取并记录执行器标记的不确定项与问题;
- 把相关关切纳入评分输出(对应
grading.json的user_notes_summary字段); - 注意:即使所有断言都通过,执行器的自述也可能暴露问题。
Step 6:批判评测集(Critique the Evals)
完成判分后,评估 eval 本身是否可改进。文档对"何时才值得提建议"设定了高门槛——只在存在明确缺口时提出,并给出了"有区分度"(discriminating)断言的定义:断言应在技能真正成功时通过、在未成功时失败。值得提出的建议包括三类:
- 某条断言通过了,但对一个明显错误的输出同样会通过(例如只查文件是否存在、不查内容);
- 观察到一个重要结果(无论好坏),但没有任何断言覆盖它;
- 某条断言在现有输出下根本无法核验。
原文的标准是:"The goal is to flag things the eval author would say 'good catch' about, not to nitpick every assertion."(目标是标出评测作者会说"抓得好"的问题,而不是对每条断言吹毛求疵。)这些建议最终落到eval_feedback字段,形成对评测集的反馈回路。
Step 7:写出评分结果
结果写入{outputs_dir}/../grading.json——即outputs目录的同级目录。在 skill-creator 的工作区布局中,运行目录形如<workspace>/iteration-<N>/eval-<ID>/with_skill/,其下outputs/存产物,因此grading.json实际落在with_skill/grading.json,与 SKILL.md "Save results tograding.jsonin each run directory" 的表述一致。
Step 8:读取执行器指标与耗时
- 若
{outputs_dir}/metrics.json存在,读取并纳入评分输出(对应execution_metrics); - 若
{outputs_dir}/../timing.json存在,读取并纳入耗时数据(对应timing)。
两个文件都是"可选项":缺失时相应字段留空即可,评分本身不被阻断。metrics.json由执行器写入,包含每次工具调用计数、total_steps、output_chars(输出文件总字符数,作为 token 的代理指标)、transcript_chars等;timing.json则由主代理在子代理任务完成时从通知里抓取total_tokens与duration_ms后落盘——SKILL.md 特别警告这是唯一的采集窗口,错过通知就无法事后恢复。
判定标准:举证责任在断言一侧
文档的 Grading Criteria 把 PASS/FAIL 边界写成了清单式规则:
判 PASS,当且仅当:
- 转录或输出明确证明断言为真;
- 能引用具体证据;
- 证据反映的是实质内容,而非表面合规(例如:文件存在且包含正确内容,而不仅仅是文件名对)。
判 FAIL,当:
- 未找到断言的任何证据;
- 证据与断言矛盾;
- 断言无法用现有信息核验;
- 证据是表面的——断言在技术上被满足,但底层任务结果是错的或不完整的;
- 输出看起来是碰巧满足断言的,而非真正完成了工作(by coincidence rather than by actually doing the work)。
两条兜底规则:
- 存疑时,通过断言的举证责任在断言一方("The burden of proof to pass is on the expectation");
- 没有部分分(No partial credit):每条断言只有 pass 或 fail,不存在折中。
这一"从严判定"取向与 Step 6 的"批判评测集"互为表里:对弱断言严格判 FAIL,再借eval_feedback把它改强,整个评测集的区分度会随迭代不断提升。
输出格式:grading.json 完整结构与字段说明
Grader 的最终产物是一份 JSON 文件。文档给出的完整示例如下(与 references/schemas.md 中的grading.json定义一致):
{ "expectations": [ { "text": "The output includes the name 'John Smith'", "passed": true, "evidence": "Found in transcript Step 3: 'Extracted names: John Smith, Sarah Johnson'" }, { "text": "The spreadsheet has a SUM formula in cell B10", "passed": false, "evidence": "No spreadsheet was created. The output was a text file." }, { "text": "The assistant used the skill's OCR script", "passed": true, "evidence": "Transcript Step 2 shows: 'Tool: Bash - python ocr_script.py image.png'" } ], "summary": { "passed": 2, "failed": 1, "total": 3, "pass_rate": 0.67 }, "execution_metrics": { "tool_calls": { "Read": 5, "Write": 2, "Bash": 8 }, "total_tool_calls": 15, "total_steps": 6, "errors_encountered": 0, "output_chars": 12450, "transcript_chars": 3200 }, "timing": { "executor_duration_seconds": 165.0, "grader_duration_seconds": 26.0, "total_duration_seconds": 191.0 }, "claims": [ { "claim": "The form has 12 fillable fields", "type": "factual", "verified": true, "evidence": "Counted 12 fields in field_info.json" }, { "claim": "All required fields were populated", "type": "quality", "verified": false, "evidence": "Reference section was left blank despite data being available" } ], "user_notes_summary": { "uncertainties": ["Used 2023 data, may be stale"], "needs_review": [], "workarounds": ["Fell back to text overlay for non-fillable fields"] }, "eval_feedback": { "suggestions": [ { "assertion": "The output includes the name 'John Smith'", "reason": "A hallucinated document that mentions the name would also pass — consider checking it appears as the primary contact with matching phone and email from the input" }, { "reason": "No assertion checks whether the extracted phone numbers match the input — I observed incorrect numbers in the output that went uncaught" } ], "overall": "Assertions check presence but not correctness. Consider adding content verification." } }各字段语义(继承自文档的 Field Descriptions 一节):
| 字段 | 说明 |
|---|---|
expectations[] | 逐条判分结果。text为原始断言文本;passed为布尔值;evidence为支撑判定的直接引文或描述 |
summary | 汇总统计:passed/failed/total计数与pass_rate(0.0–1.0 的比例) |
execution_metrics | 从执行器的metrics.json复制(若存在)。output_chars是输出文件总字符数(token 的代理);transcript_chars是转录字符数 |
timing | 墙钟耗时,来自timing.json(若存在)。executor_duration_seconds为执行器子代理耗时;total_duration_seconds为整次运行总耗时 |
claims[] | 从输出中抽取并核验的隐含声明。type取factual/process/quality;verified表示声明是否成立;evidence给出支持或反驳证据 |
user_notes_summary | 执行器标记的问题汇总:uncertainties(执行器不确定的事)、needs_review(需人工关注的项)、workarounds(技能未按预期工作、被迫绕行的地方) |
eval_feedback | (可选)对评测集的改进建议。suggestions每条含reason,可附带其关联的assertion;overall为总评,无问题时可写 "No suggestions, evals look solid" |
其中eval_feedback.suggestions的示例很有代表性:第一条指出"输出包含姓名 John Smith"这条断言可以被一份"恰好提到该名字"的幻觉文档满足,建议改为校验该名字作为主要联系人出现且电话、邮箱与输入匹配;第二条指出没有任何断言检查抽取出的电话号码是否与输入一致,而 Grader 实际观察到了未被捕获的错误号码。这正是"判分 + 批判 eval"双职责的具体落地。
下游消费链:grading.json 如何被仓库代码读取
grading.json的字段名不是随意约定,仓库中两处代码对其有硬依赖,可以从源码确认这份协议的下游消费方式。
1. 基准聚合脚本。scripts/aggregate_benchmark.py 从每个 run 目录读取grading.json并生成benchmark.json/benchmark.md,其读取逻辑与 grader.md 的字段一一对应:
- 从
summary取pass_rate、passed、failed、total(见load_run_results中对grading.get("summary", {})的取值); - 耗时优先取
grading.json内timing.total_duration_seconds,为 0 时回退到同目录的timing.json,并同时取其中的total_tokens——这正好印证了 Step 8 "先读 grading.json,再读 sibling timing.json" 的兜底关系; - 从
execution_metrics取total_tool_calls、errors_encountered,并以output_chars作为 token 的兜底估计值; - 把
user_notes_summary的三个列表(uncertainties/needs_review/workarounds)合并为 benchmark 中的notes字段; - 对
expectations逐条校验必须含text与passed字段,缺失时打印 Warning——即 SKILL.md 强调"不要用name/met/details变体"的机器侧原因。
脚本支持两种目录布局(eval-*/<config>/run-*/grading.json的直接工作区布局,或带runs/子目录的旧布局),配置目录名动态发现而非硬编码,因此with_skill/without_skill或new_skill/old_skill等命名都能被聚合。
2. 评测查看器。eval-viewer/generate_review.py 在构建每个 run 的展示数据时,会依次尝试run_dir / "grading.json"和run_dir.parent / "grading.json"两个候选位置加载评分(即运行目录内或其上级目录),把解析结果挂到该 run 的grading字段上。这正是 Step 7 把结果放在outputs_dir同级(即 run 目录内)的展示层收益:viewer 的 "Formal Grades" 折叠区可以展示每条断言的 pass/fail。
3. 迭代对比。进入第 2 轮迭代后,generate_review.py通过--previous-workspace参数把上一迭代的grading.json一并载入,形成 "Previous Output / Previous Feedback" 的对照视图;SKILL.md 要求聚合时把每个 with-skill 版本排在其基线对应项之前,便于在 benchmark 视图中直接对比。
行为准则
文档结尾的 Guidelines 对评分行为本身提出六条约束:
- 客观(Be objective):结论基于证据而非假设;
- 具体(Be specific):引用支撑结论的原文;
- 彻底(Be thorough):转录与输出文件都要检查;
- 一致(Be consistent):对每条断言应用同一标准;
- 解释失败(Explain failures):说清楚为什么证据不足;
- 不给部分分(No partial credit):每条断言只有二元结论。
小结
grader.md 定义的评分协议有三个值得复用的设计要点:其一,产物优先于自述——断言由输出文件本身裁决,转录只作过程证据;其二,从严判定——举证责任在断言一侧,表面合规与碰巧通过一律判 FAIL;其三,评分即反馈——claims捕获断言未覆盖的隐含声明,eval_feedback反向指出断言的盲区,使grading.json同时充当"成绩单"与"评测集改进清单"。在 DeerFlow 的skill-creator工作流中,这份产物是整条量化闭环的枢纽:聚合脚本、评测查看器和下一轮迭代比较都直接建立在它的字段协议之上。
【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考