ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Kiln Evals V2 Cleanup 架构解析:V2EvalResult 契约迁移与八项精准修复实战

Kiln Evals V2 Cleanup 架构解析:V2EvalResult 契约迁移与八项精准修复实战
  • 人工智能
  • AI 应用
  • AI 评测
  • RAG
  • 微调
  • 提示工程
  • 开发工具
  • 桌面应用

【免费下载链接】Kiln

Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.

项目地址:https://gitcode.com/gh_mirrors/ki/Kiln
点击查看免费下载

导读

Evals V2 Cleanup 是 Kiln 仓库中一个"小而精"的收尾项目:它把 Evals V2 spec-fidelity review(2026-06-23)中筛出的八项缺陷(编号 D14/D15/D27–D31/D35)集中到一次架构级清理中,其中六项是后端正确性修复、两项是结果展示面的 UI 缺口。本文以 architecture.md 为骨架,结合 functional_spec.md、implementation_plan.md 以及仓库当前源码,逐项拆解这些修复的动机、实现方式与验证门槛。读完本文,你将理解 Kiln V2 评测体系中最关键的内部契约V2EvalResult是如何贯穿 8 个适配器与 5 个调用点的,也能掌握这类"评测基础设施正确性收尾"项目的架构决策与评审流程。

一、项目背景:一次 spec-fidelity review 的八项收尾

Evals V2 Cleanup 的定位十分明确:它是一个holding project(汇总性项目),专门承接 Evals V2 规范一致性评审(spec-fidelity review)中"重要但体量小"的修复项——这些修复不足以各自立项,但全部被标记为ship-blocker(发布阻塞项,其中 D27/D29 由 scosman 于 2026-06-24 从 post-ship 提升为 ship-blocker)。详见 project_overview.md。

八项决策一览:

ID面一句话描述
D14后端(eval_helpers.py)get_tool_calls()必须能读取真实的 OpenAI 格式 trace
D15后端(eval_helpers.py)get_assistant_messages()必须返回list[str]
D27后端(eval.py)expected_tools在保存时必须非空
D28后端(eval.py)ArgMatch的正则表达式在保存时必须可编译
D29后端(eval.py)reference_key在保存时要求min_length=1
D30后端(eval.py)无用模板检查必须是 AST 级别,而非{{表面扫描
D31后端 + 前端在运行结果中暴露 V2llm_judge的推理(reasoning)
D35前端聚合视图在n_excluded > 0时给出警告

项目的关键设计约束有两点:

  1. 并行安全:所有改动都不触碰评测创建容器(eval create container,属于另一个项目 Project 1),因此与创建流程项目完全并行、无分支冲突;唯一的交叉点是eval.py文件本身被两个项目共享,但涉及的是不同的类。
  2. 单一共享脊柱:除了V2EvalResult契约迁移(§1)外,其余各项彼此独立,可任意顺序落地。架构文档因此没有拆分成组件级文档,而是单篇架构文档,并把 UI 设计折叠进来(ui_design.md被跳过)。

决策来源记录在 DECISIONS.md,逐项证据文件为confirm_K.md(D14/D15)、confirm_J.md(D27–D30)、confirm_H.md(D31)、confirm_L.md(D35)。

二、后端脊柱:V2EvalResult契约迁移(§1)

2.1 为什么需要一个模型对象,而不是三元组

迁移前的状况是:每个 V2 适配器都实现evaluate(eval_input) -> tuple[EvalScores, SkippedReason | None, str | None]的三元组返回,而llm_judge适配器在 v2_eval_llm_judge.py 中拿到了run_output却丢弃了run_output.intermediate_outputs——即法官模型(judge model)的思维链(chain-of-thought)没有任何通道可以到达存储层。

架构决策(由 scosman 拍板)是引入一个自文档化的 Pydantic 模型:

class V2EvalResult(BaseModel): """Result of a single V2 eval `evaluate()` call.""" scores: EvalScores = Field(default_factory=dict) skipped_reason: SkippedReason | None = None skipped_detail: str | None = None intermediate_outputs: dict[str, str] | None = None

选模型而非元组的理由(architecture.md §1.4):

  • 自文档化:字段名即契约;
  • 可扩展:后续想加logprobs、usage等字段时,无需重新穿线所有调用点。

这一点在当前源码中已经得到印证:仓库中的 eval.py 里V2EvalResult已经多了一个文档写作时未出现的usage: Usage | None字段("What the judgment itself cost, if it called a model…"),并且由EvalRun.eval_usage持久化——正是"模型而非元组"设计可扩展性的直接证据。

2.2 生产者:8 个适配器的evaluate()签名迁移

抽象签名定义在 base_eval.py:

@abstractmethod async def evaluate(self, eval_input: EvalTaskInput) -> V2EvalResult: ...

各适配器的改写要点:

适配器变更
v2_eval_llm_judge.pyreturn V2EvalResult(scores=scores, intermediate_outputs=run_output.intermediate_outputs)——停止丢弃run_output.intermediate_outputs(原 165 行);skip 路径返回V2EvalResult(skipped_reason=skip, skipped_detail=detail)
v2_eval_code_eval.pyV2EvalResult(scores=...);trust-skip 返回V2EvalResult(skipped_reason=..., skipped_detail=...);intermediate_outputs保持None
v2_eval_tool_call_check.py、v2_eval_exact_match.py、v2_eval_contains.py、v2_eval_pattern_match.py、v2_eval_set_check.py、v2_eval_step_count_check.py机械化改写:把已有的(scores, skip, detail)返回包进V2EvalResult(...)

从当前源码看,v2_eval_llm_judge.py的evaluate已完整落地这一契约(L254-L260):

return V2EvalResult( scores=scores, intermediate_outputs=run_output.intermediate_outputs, # `usage`, not `cumulative_usage`: it already accumulates every call this # judgment made, and it is the one that carries latency. usage=judge_run.usage, )

而 skip 路径(缺引用键、模板渲染失败、引用未知变量等)均返回V2EvalResult(skipped_reason=..., skipped_detail=...),intermediate_outputs为None——与文档"仅 llm_judge 填充 intermediate_outputs,其余 7 个保持 None"的约定一致。

2.3 消费者:5 个调用点

调用点 1:BaseV2EvalBridge.run_eval(base_eval.py)。签名-> tuple[EvalScores, Dict[str,str] | None]不变,但内部改为解构V2EvalResult:

eval_task_input = EvalTaskInput.from_task_run(task_run) result = await self.evaluate(eval_task_input) if result.skipped_reason is not None: raise ValueError( f"V2 eval was skipped ({result.skipped_reason}): {result.skipped_detail}" ) return result.scores, result.intermediate_outputs

之前该路径返回(scores, None),现在 fresh-generation 路径也能携带推理结果。

调用点 2:eval_runner.py的_run_v2_job三路分支。三个 evaluate 分支(from_eval_input、task_run_eval、默认eval_config_eval)现在都解构result.*并构造EvalRun,其中intermediate_outputs是新增的持久化字段。当前实现中,三路分支统一汇聚到 eval_runner.py 的_persist_judgment:

return await self._persist_score( job, scored_run_id=trace.id, scores=result.scores, skipped_reason=result.skipped_reason.value if result.skipped_reason else None, skipped_detail=result.skipped_detail, intermediate_outputs=result.intermediate_outputs, eval_usage=result.usage, )

需要强调的是:这是一次纯内部契约变更,不涉及任何数据模型/存储迁移——EvalRun.intermediate_outputs字段早已存在,类型正是dict[str, str] | None(见 eval.py,字段描述为 "The intermediate outputs of the task (example, eval thinking)")。

调用点 3:eval_api.py的test_v2_eval。读取result.scores/result.skipped_reason/result.skipped_detail/result.intermediate_outputs构建TestV2EvalResponse(§6 详述)。

三、D14 / D15:KilnEvalHelpers的 trace 导航修复(§2)

KilnEvalHelpers(eval_helpers.py)是一个纯标准库工具集("Stdlib only — no Pydantic, no Kiln-model/DB/UI imports"),用户在自己编写的 code-eval 评分器(scorer)中显式导入使用:

from kiln_ai.adapters.eval.eval_helpers import KilnEvalHelpers

官方提供的 code-eval 示例画廊(example gallery)依赖get_tool_calls,因此这两个方法的正确性直接影响官方示例的可用性。

3.1 真实的 trace 格式:工具调用嵌套在 assistant 消息里

Kiln 的 trace 遵循 OpenAI 约定。工具调用不是顶层条目,而是嵌套在role: "assistant"消息内部:

{ "role": "assistant", "content": null, "tool_calls": [ {"id": "call_1", "type": "function", "function": {"name": "search", "arguments": "{\"q\": \"cats\"}"}} ] }

正确的提取逻辑已经存在于 v2_eval_tool_call_check.py 中——D14/D15 的做法就是复用这套逻辑(提取function.name、JSON 解析function.arguments),而不是另起炉灶。

3.2 D14:get_tool_calls修复

修复前(有缺陷):过滤条件为role == "tool_call"或type == "tool_call"——这个形态在真实 trace 中永远不会出现,因此对任何真实工具调用 trace 都会返回[]。测试之所以通过,仅仅是因为 test_code_eval_samples.py 里用了合成的role: "tool_call"trace——这正是"用伪造数据喂测试导致缺陷发货"的典型教训。

修复后:遍历role == "assistant"消息,把每个tool_calls条目展平为:

{"name": <function.name or "">, "arguments": <json.loads(function.arguments) → dict, {} on parse failure>, "id": <tool_call.id or None>}

要点(均来自 architecture.md §2.1 与当前源码 eval_helpers.py):

  • name/arguments两个键是has_tool_call/count_tool_calls消费的,保持这两个方法无需改动即可继续工作;
  • id是新增的,用于对齐 spec §3.1 的{name, arguments, id}契约;
  • arguments永远是dict:JSON 字符串解析失败(JSONDecodeError/TypeError)或解析结果非 dict 时回退为{};
  • None/ 空 trace →[];
  • 对畸形数据永不抛异常:非 dict 的function、缺失的name、无法解析的arguments,都会产出{"name": "", "arguments": {}, "id": ...}。

3.3 D15:get_assistant_messages修复

修复前(类型错误):返回list[dict](完整的 assistant 条目)。

修复后:返回每个role == "assistant"消息的内容字符串,返回类型-> list[str]:

@staticmethod def get_assistant_messages(trace): if not trace: return [] return [ msg["content"] for msg in trace if msg.get("role") == "assistant" and isinstance(msg.get("content"), str) ]

content缺失或非字符串的 assistant 轮次(例如纯工具调用轮次content: null)会被省略。这是一个破坏性签名变更,但符合文档化 spec 契约,且没有任何已发布的示例画廊调用此方法,因此安全。

从当前 test_eval_helpers.py 看,真实格式 fixture(assistant + 嵌套tool_calls)与content: null省略场景的测试均已在库中。

3.4 硬性质量门槛(§2.3 / §2.4)

这是本项目中最特殊的验收要求,且是流程性风险而非代码性风险:批准 D14/D15 之前,必须手动对一条真实的、包含真实工具调用的 Kiln trace(例如从一次实时的工具调用任务运行中捕获)运行这两个方法,确认get_tool_calls返回调用、get_assistant_messages返回内容字符串。Agent 手工编造的 fixture 不足以作为验收依据——因为最初的缺陷恰恰就是因为测试使用了伪造的 trace 形态而混过检查的。文档原文的表述值得记住:

Approval of D14/D15requires a manual runof both methods against areal Kiln trace containing actual tool calls… Agent-authored fixtures areinsufficient.

四、D27–D30:eval.py保存时校验器(§3)

四个修复全部在 V2 的*Properties(或EvalConfig的模板校验器)上加构造时校验,让"脚枪"在保存时失败,而不是在运行时静默出错。它们与 Project 1 的set_check枚举工作共享 eval.py,但触碰的是不同类,冲突风险低。

校验在EvalConfig/*Properties构造时执行,因此同时拦截两条路径:持久化保存路径,以及test_v2_eval的瞬态配置路径(后者也会构建EvalConfig)。每个校验器都必须产出可供创建 UI 保存错误直接展示的用户可读ValueError。

4.1 D28:ArgMatch正则保存时编译

  • 位置:ArgMatch,match_mode == "regex"时。
  • 行为:加@model_validator(mode="after"),对re.compile(str(self.value))调用,re.error时抛出ValueError(f"Invalid regex value '{self.value}': {e}"),镜像已有的PatternMatchProperties.validate_pattern模式。
  • 为什么阴险:运行时re.search被try/except re.error包裹并返回False(见v2_eval_tool_call_check.py)——一个无效正则静默地永远不匹配,用户毫无反馈。

当前源码中ArgMatch的validate_regex(eval.py)与PatternMatchProperties.validate_pattern(L177-L191)均已按此落地。注意exact/contains模式跳过编译(边界情况 §7 明确要求)。

4.2 D30:AST 级"无用模板"检查

  • 位置:EvalConfig.validate_v2_templates_and_expressions的LlmJudgeProperties分支。
  • 现状(有缺陷):表面扫描——if "{{" not in tmpl and "{%" not in tmpl: raise,只能拦住零变量模板。
  • 绕过的漏洞:{{ reference_data.expected_output }}含有{{因而通过检查,但它只引用reference_data——渲染出的法官提示词与模型输出完全无关,这个 eval 毫无意义。
  • 修复:解析模板 AST 并检查顶层变量引用。渲染命名空间恰好是EvalTaskInput.model_dump()={final_message, trace, reference_data, task_input}。拒绝保存,除非至少一个被引用的顶层变量属于{final_message, trace, task_input}(即既不能只引用reference_data,也不能零变量)。
    • 实现:jinja2.meta.find_undeclared_variables(_template_env.parse(prompt_template))返回顶层名称集合;trace[0].content这类子路径仍会浮现顶层trace,因此通过。
    • 错误消息必须解释"模板从不读取模型输出",并给出{{ final_message }}之类的建议。
    • 在此之前保留原有的compile_template_or_raise语法检查调用。

当前实现位于 eval.py,且MEANINGFUL = {"final_message", "trace", "task_input"}的注释明确写着 "reference_data deliberately excluded"——与文档设计完全一致。

4.3 D27:expected_tools非空

  • 位置:ToolCallCheckProperties.expected_tools。
  • 行为:要求非空,Field(min_length=1)(Pydantic v2)或等价的model_validator,空列表抛ValueError。
  • 为什么:空列表在all/ordered/never模式下是空真(vacuous pass),在any模式下则必然失败——一个按模式静默"永远通过/永远失败"的检查。当前实现为expected_tools: list[ToolCallSpec] = Field(min_length=1)(eval.py)。

4.4 D29:reference_key的min_length=1

  • 位置:三处reference_key: str | None字段——ExactMatchProperties、ContainsProperties、SetCheckProperties。
  • 行为:设置时必须非空。None仍然合法(XOR 校验器允许reference_key与expected_value恰好一个为None);只有空字符串是新被拒绝的。用min_length=1加在字段上(保留| None),或使用校验器,以不破坏既有 XOR 语义为准。
  • 为什么:""不是None,因此今天能通过 XOR 检查,然后在运行时永远 skip("" not in reference_data)。

当前三个类均实现为reference_key: str | None = Field(default=None, min_length=1)(eval.py),且各自的 XOR 校验器("Exactly one of … must be set")保持不动——边界情况 §7 明确:None仍合法,仅""被拒。

五、D31:暴露 V2llm_judge推理(§4、§5)

5.1 后端:捕获 + 持久化 + 暴露

D31 的实质性改动在 v2_eval_llm_judge.py:

judge_run, run_output = await adapter.invoke_returning_run_output(rendered_prompt)

invoke_returning_run_output已经产出run_output.intermediate_outputs——SIMPLE_CHAIN_OF_THOUGHT法官会发出{"chain_of_thought": "..."}。现在把它传进V2EvalResult.intermediate_outputs(§2.2 的代码已展示)。skip / 必需变量缺失路径(原 105-107 行)携带None。

后端链路:_run_v2_job的三条 V2 分支统一经_persist_judgment持久化到EvalRun.intermediate_outputs。没有任何其他适配器产生推理;code_eval的 stdout 捕获明确列为范围外(Out of scope)。

5.2 前端:运行结果页的"View reasoning"模态框

  • 线程化 prop:在run_result/+page.svelte的<svelte:component>上加intermediate_outputs={result.intermediate_outputs ?? null}(EvalRun的 TS 类型上intermediate_outputs已存在,V1 路径已在读取它)。
  • 渲染器:llm_judge_result.svelte加 prop 与一个Dialog实例(镜像run_result/+page.svelte中thinking_dialog的既有模式):
export let intermediate_outputs: Record<string, string> | null = null $: reasoning = intermediate_outputs?.reasoning || intermediate_outputs?.chain_of_thought || null let reasoning_dialog: Dialog | null = null

当reasoning存在时,渲染一个小的"View reasoning"按钮(text-xs链接样式,与既有的text-gray-400配置块风格一致),点击调用reasoning_dialog?.show()。<Dialog title="Judge Reasoning">内放<div class="font-light text-sm whitespace-pre-wrap">{reasoning}</div>。

从当前源码 llm_judge_result.svelte 看,这套实现已完整落地(reasoning 提取、两个渲染分支、Dialog bind:this模态)。

明确的设计约束:

  • code_eval_result.svelte及其他无推理的渲染器不做任何改动;
  • 不重新启用 Thinking 列,V1 列的逻辑保持原样;
  • 不采用内联展开/折叠,必须是模态框;
  • 聚合/对比视图不展示推理(它们显示跨多轮的均值,单次推理不属于那里);
  • 推理为空时(法官未产出 chain-of-thought)不显示任何推理入口;
  • skipped 运行时intermediate_outputs为None,渲染器显示既有的 skip 徽章。

5.3 测试面板边界

test_v2_eval会在TestV2EvalResponse上返回intermediate_outputs(后端)。但 Test-Your-Judge 面板的推理 UI 属于 Project 1 的创建容器表面,不在本项范围内——D31 只负责让数据可用。

六、D35:聚合视图的n_excluded(i) 指示器(§7)

当部分数据集用例被跳过/排除时,聚合分数会静默地低估覆盖率。D35 的目标就是让均值不被误读。

6.1 触发与展示

  • 触发条件:某个 run config(或法官对比面上的某个法官)的n_excluded > 0。
  • UI:一个小的 info "(i)" 符号(复用既有的InfoTooltip组件模式,对比视图本已导入),tooltip 文案为"{n_excluded} of {n_used + n_excluded} cases were skipped and are not reflected in this score."。
  • 按严重度着色(skip 比率 =n_excluded / (n_used + n_excluded)):
    • > 0%且≤ 20%→黄色(warning 色);
    • > 20%→红色(error 色)。
  • 与既有的percent_complete < 1.0"不完整"警告(缺跑)相互独立——两者可同时出现,描述的是不同条件。

InfoTooltip的 SVG 用fill="currentColor",因此包裹它的text-warning/text-errorspan 会自动给 (i) 字形上色——无需改动组件本身。

6.2 数据来源与展示面

  • 主面——对比视图(run_config_comparison_table.svelte):n_excluded位于每个ScoreSummary中(results[run_config_id][score_key].n_excluded),且在一个 run config 的各 score key 间恒定(在eval_api.py按 run config 设置)。每行只读一次。当前源码(L40-L48 的excluded_for_run_config辅助函数与 L232-L240 的渲染)已按此实现。
  • 次面——法官对比视图:RunConfigEvalResult.n_excluded(eval_api.py)已暴露;若该表面渲染聚合每运行分数,则应用同样的图标 + tooltip 模式。

6.3 范围决策(pushback)

原始 spec 文案("…required reference data missing"、按SkippedReason细分)暗示要做按原因的分类统计。但聚合 API 只暴露计数(n_excluded),且不同行可能因不同原因 skip。决策:tooltip 只报告计数;按原因的文案不在范围内(单项 skip 原因已在eval_result_scores.svelte中逐条展示)。暴露按原因聚合需要新的 API 工作——明确排除。

数据流要点:这是纯前端改动——数据已经通过 API 与生成的 TS schema 流转到位。

七、API 契约与 schema 再生成(§6)

端点 / 模型变更
TestV2EvalResponse(eval_api.py)增加intermediate_outputs: dict[str, str] | None = None
evaluate()(内部,所有 V2 适配器)返回V2EvalResult而非三元组(§2)

没有新增 HTTP 路由、没有请求形态变更、没有数据模型/存储迁移。唯一的额外工作是:为TestV2EvalResponse新字段重新生成 OpenAPI 客户端并提交,使check_schema通过(脚本位于 app/web_ui/src/lib/generate_schema.sh)。

从当前源码看,TestV2EvalResponse已含intermediate_outputs与score_range_errors、tool_call_log等字段——schema 再生成环节已落地。

八、测试策略与质量门槛(§8、§9)

领域测试
§1V2EvalResult既有 V2 适配器测试改为断言result.scores/result.skipped_reason/result.intermediate_outputs;test_v2_dispatch_and_contract.py覆盖形态;runner 测试断言EvalRun.intermediate_outputs对 llm_judge 持久化、对 code_eval 为None
§2 D14/D15真实格式 fixture(assistant + 嵌套tool_calls);get_tool_calls返回[{name,arguments,id}];get_assistant_messages返回list[str]、省略content:null轮次;批准前手动真实 trace 门槛(§2.3);更新test_eval_helpers.py与test_code_eval_samples.py
§3 D27–D30逐校验器的接受/拒绝:空expected_tools被拒;坏正则ArgMatch被拒;reference_key=""被拒但None被接受;D30 的{{ reference_data.x }}独用模板与纯字面量模板被拒,{{ final_message }}/{{ trace[0].content }}/{{ task_input }}被接受
§4–6 D31llm_judgeevaluate()返回推理;_run_v2_job持久化它;test_v2_eval返回它;web 测试:llm_judge_result.svelte有推理时显示 "View reasoning" → 模态框,无推理时隐藏
§7 D35web 测试:(i) 在n_excluded>0时出现、为 0 时消失;≤20%显示text-warning、>20%显示text-error;与"不完整运行"警告共存

每个阶段 CR(代码评审)之前都要跑完整检查套件:lint / format / type、py + web 测试、schema、build。

边界情况清单(functional_spec.md §7)同样值得直接引用:

  • D14 畸形tool_calls:非 dictfunction、缺name、arguments无法解析 → 该调用仍产出{"name": "", "arguments": {}, "id": …},helper 对坏 trace 永不抛异常;
  • D14/D15 非 assistant / 混合 trace:只考虑role == "assistant"条目,user/tool/system 忽略;
  • D15 content 形态:content: null或列表型 content → 省略(只返回字符串 content);
  • D30 合法边界模板:{{ task_input }}、{{ trace[0].content }}、{% if final_message %}…{% endif %}均通过;仅{{ reference_data.x }}和纯字面量被拒;
  • D28 非 regexArgMatch:exact/contains模式跳过编译;
  • D29Nonevs"":None仍合法(XOR 控制),仅""新被拒;
  • D31 skipped 运行:skipped_reason已设 →intermediate_outputs为None,渲染器显示既有 skip 徽章;
  • D31 空推理:法官无 chain-of-thought → 无 "View reasoning" 入口;
  • D35 零排除 / 严重度边界:n_excluded == 0→ 无图标;比率恰好20%→ 黄;> 20%→ 红;n_used == 0 && n_excluded > 0→ 比率 100% → 红图标(全 skip,均值已为None/"unknown")。

九、执行顺序与风险协调(§9、§10)

执行规划为两个阶段(implementation_plan.md,当前两个阶段均已完成 ✅):

  1. 阶段 1 —— 后端脊柱 + helpers + 校验器:V2EvalResult迁移(§1)+ D14/D15(§2)+ D27–D30(§3)+ D31 后端捕获(§4)。全部在libs/core/ API 层,互相兼容,一个 CR 提交。
  2. 阶段 2 —— 前端 + API 表面:TestV2EvalResponse字段 + schema 再生成(§6)、D31 推理模态框(§5)、D35 (i) 指示器(§7)。一个 CR 提交。

两阶段划分把手动真实 trace 门槛(阶段 1)与 UI 评审(阶段 2)分离,保证每个 CR 可评审。若偏好,单阶段合并也允许——实现计划默认建议两阶段。

风险与协调要点:

  • Project 1 共享eval.py:它的set_check枚举工作与 D27–D30 触碰不同的类/字段,小心 rebase 即可,冲突风险低。V2EvalResult迁移触碰的适配器文件不在 Project 1 范围。
  • evaluate()签名变更面广(8 个适配器 + 5 个调用点)但机械——类型检查器(ty)会标记任何遗漏的调用点,可以依赖它兜底。
  • 手动 trace 门槛是流程风险而非代码风险:绝不能用合成 fixture 批准 D14/D15。

十、总结:这类"评测基础设施收尾"项目的可复用方法论

回顾 Evals V2 Cleanup,能提炼出几条对任何 AI 评测系统都适用的工程原则:

  1. 契约先行、模型优于元组:V2EvalResult把(scores, skip_reason, skip_detail)的隐性三元组升级为自文档化模型,换来的是后续usage等字段零成本扩展——正如仓库源码所示。
  2. 用真实数据形状约束测试:D14/D15 的教训是"合成 fixture 会掩盖真实 trace 形态的缺陷",对应的对策是"批准前必须手动跑真实 trace"这一硬性流程门槛。
  3. 错误要在保存时暴露,而不是运行时静默:D27–D30 的全部动机都是把正则编译错误、空expected_tools、空reference_key、无意义模板这类"脚枪"前移到保存时刻,其中 D30 用jinja2.meta.find_undeclared_variables做 AST 级检查而非{{表面扫描,堵住了reference_data独用模板的旁路。
  4. 聚合视图必须披露数据覆盖度:D35 用分色 (i) 图标提示n_excluded,避免均值被静默误读,同时克制地只报计数、不做按原因聚合。
  5. 小修复也要独立评审与分阶段:即使每项都是"外科手术式"改动,也要通过两阶段 CR、完整检查套件和明确的范围外清单(Out of scope)来控制质量与并行安全。

如果你正在维护自己的评测系统,可以直接参照本项目的做法:先梳理内部的 evaluate 契约与数据流(适配器 → runner → API → 前端渲染器),再对照真实 trace 形状逐个修正数据解析,最后用保存时校验器把配置错误挡在运行之前。

  • 人工智能
  • AI 应用
  • AI 评测
  • RAG
  • 微调
  • 提示工程
  • 开发工具
  • 桌面应用

【免费下载链接】Kiln

Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.

项目地址:https://gitcode.com/gh_mirrors/ki/Kiln
点击查看免费下载

相关推荐

上一篇:Z80开源硅克隆:揭秘4位ALU与16位寄存器堆的终极设计
下一篇:DeepChat窗口状态持久化:electron-window-state实现原理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进