☰
Kiln Evals V2 前端与 API 表面清理:Phase 2 的 reasoning 展示与 n_excluded 告警实施指南
2026/10/15 2:24:08 网站建设 项目流程
  • 人工智能
  • 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
点击查看免费下载

本指南围绕 Kiln 仓库evals_v2_cleanup项目 Phase 2 的完整实施脉络展开,核心解决两类"结果/视图表面"问题:将 V2llm_judge评测的链式推理(chain-of-thought)从后端一路透传到运行结果页并以模态框展示(决策 D31),以及在 run-config 对比视图与 judge 对比视图上,当数据集样本被跳过(n_excluded > 0)时给出带严重度分级的提示(决策 D35)。读完本文,你将掌握TestV2EvalResponse.intermediate_outputs的 OpenAPI 重新生成流程、V2 结果渲染器与推理模态框的完整接线方式,以及聚合视图n_excluded指示器的数据读取、颜色阈值与测试覆盖策略,并可通过仓库中的源码与测试用例逐一验证每项实现。

一、Phase 2 是什么:三件事,一个共同目标

Phase 2 是specs/projects/evals_v2_cleanup项目("重要但小"的 Evals V2 spec-fidelity 修复集合)的执行批次之一,定位在 project_overview.md 中描述为ship-blocker工作。它与创建容器(create container)互不冲突,只处理结果/视图表面:

  1. 重新生成 OpenAPI 客户端,让TestV2EvalResponse.intermediate_outputs出现在前端 TS 类型中;
  2. D31 UI:把intermediate_outputs注入 V2 结果渲染器(run_result/+page.svelte),并在llm_judge_result.svelte中新增 "View reasoning" 链接与模态框;
  3. D35 UI:在 run-config 对比视图(run_config_comparison_table.svelte)的 Status 单元格旁新增n_excluded信息指示器,并在 judge 对比视图(compare/+page.svelte)应用相同模式。

三者的共同目标是"让已经存在于 API 中的数据(judge 的推理、跳过的样本数)真正可见",前端补全视图缺口,后端仅做字段透传,不引入新的 HTTP 路由、不改请求结构、不做数据模型迁移(functional_spec.md §6 明确了这一 API 契约边界)。

二、Step 1:重新生成 OpenAPI schema,让后端新字段流入 TS 类型

后端TestV2EvalResponse已定义intermediate_outputs: dict[str, str] | None = None字段,位于 eval_api.py 的TestV2EvalResponse模型(scores、skipped_reason、skipped_detail、score_range_errors之后新增)。它是test_v2_eval接口的响应模型:当测试运行一个 V2 评测配置时,返回各分数、跳过原因与详情,以及本次运行的中间输出(对llm_judge而言即推理文本)。

要让前端 TypeScript 代码访问该字段,Phase 2 规定通过 HooksMCP 工具链完成两步:

  • mcp__HooksMCP__generate_schema:根据后端 Pydantic 模型重新生成 OpenAPI schema 与对应的 TS 客户端类型;
  • mcp__HooksMCP__check_schema:校验 schema 与 TS 类型是否与后端一致、未漂移。

仓库中对应的可执行脚本位于 app/web_ui/src/lib/check_schema.sh 与 app/web_ui/src/lib/generate_schema.sh,以及生成流程脚本 app/web_ui/src/lib/openapi_schema.sh。实际生成产物为 app/web_ui/api_schema.d.ts(前端类型声明)。凡改动后端 Pydantic 模型后,都必须重跑该流程,否则 TS 类型将缺少新字段,svelte-check或构建阶段会报错——这正是 Phase 2 把"重新生成 schema"列为 Step 1 的原因。

三、Step 2:D31 前端接线 —— 把intermediate_outputs传入 V2 结果渲染器

数据源头在run_result页面。查看 run_result/+page.svelte/specs/[project_id]/[task_id]/[spec_id]/[eval_id]/[eval_config_id]/[run_config_id]/run_result/+page.svelte#L545-L556),V2 配置的结果单元格通过<svelte:component>动态渲染对应类型的渲染器组件:

{#if is_v2_config && v2_result_component} <td> <svelte:component this={v2_result_component} scores={result.eval_run.scores} skipped_reason={result.eval_run.skipped_reason ?? null} skipped_detail={result.eval_run.skipped_detail ?? null} eval_config={results.eval_config} intermediate_outputs={result.eval_run.intermediate_outputs ?? null} /> </td>

关键点:

  • v2_result_component通过getV2EvalTypeMetadata(v2type).resultRendererComponent解析(该页第 345-350 行的派生逻辑),llm_judge类型对应 llm_judge_result.svelte;
  • intermediate_outputs取自result.eval_run.intermediate_outputs,取不到时兜底为null;
  • 表格表头逻辑保持不变:V2 配置渲染 "Result" 列而非逐 score 列,且不再显示 V1 的 "Thinking" 列({#if !is_v2_config}<th>Thinking</th>{/if})——这正是 D31 要补的缺口:V2 的llm_judge推理此前被完全隐藏。

后端支撑(functional_spec.md §1):V2 适配器统一返回类型化对象V2EvalResult(scores/skipped_reason/skipped_detail/intermediate_outputs),llm_judge适配器从 judge run 的intermediate_outputs(如{"chain_of_thought": "..."})填充该字段,其余 7 个 V2 适配器(code_eval等)保持None。eval_runner.py的_run_v2_job在三个分支(from_eval_input/task_run_eval/eval_config_eval)把result.intermediate_outputs写入EvalRun.intermediate_outputs持久化。EvalRun.intermediate_outputs是既有字段(dict[str, str] | None),无需数据模型迁移。

四、Step 3:D31 UI ——llm_judge_result.svelte的 "View reasoning" 模态框

llm_judge_result.svelte 的最终实现严格遵循 Phase 2 的四点要求,值得逐条对照:

1) 新增 prop:

export let intermediate_outputs: Record<string, string> | null = null

2) 派生 reasoning:同时兼容reasoning与chain_of_thought两种键名,任一存在即视为有推理:

$: reasoning = intermediate_outputs?.reasoning || intermediate_outputs?.chain_of_thought || null

3) 复用 Dialog:引入Dialog组件并声明let reasoning_dialog: Dialog | null = null。模态框标题固定为 "Judge Reasoning",正文以whitespace-pre-wrap保留换行:

<Dialog bind:this={reasoning_dialog} title="Judge Reasoning"> <div class="font-light text-sm whitespace-pre-wrap"> {reasoning} </div> </Dialog>

这与页面级thinking_dialog(V1 的 "Thinking Output" 模态框)属于同一Dialog组件族,符合 functional_spec.md §4.2 "复用既有 Dialog 模式"的设计约束——不是内联展开/折叠。

4) 条件渲染链接:当reasoning存在时,渲染text-xs样式的 "View reasoning" 链接,与既有的text-gray-400配置信息块风格一致。实现里有两个分支:eval_config存在时挂在配置块内部({#if props}分支,位于 Model/G-Eval 信息之后),否则走独立的兜底分支({:else if reasoning})——后者覆盖eval_config为空的回退场景:

{#if reasoning} <div> <button class="text-xs link link-hover text-gray-400" on:click={() => reasoning_dialog?.show()} > View reasoning </button> </div> {/if}

值得注意的设计边界:渲染器只按类型决定是否展示推理入口——code_eval等无推理的 V2 类型渲染器不渲染任何推理入口;V1 "Thinking" 列行为不变;跳过(skipped)的运行intermediate_outputs为None,渲染器只显示既有跳过徽章。这些边界在 functional_spec.md §7 的 Edge cases 中逐一列明。

五、Step 4:D35 UI ——run_config_comparison_table.svelte的n_excluded指示器

对比视图用于并排比较多个 run-config 的平均分,是 D35 的主战场:当部分数据集样本被跳过(如缺少 reference data、工具未命中)时,平均分会静默低估覆盖率,因此需要显式告警。

5.1 数据读取:excluded_for_run_config辅助函数

run_config_comparison_table.svelte 实现了文档要求的辅助函数:

function excluded_for_run_config( summary: EvalResultSummary | null, rc_id: string | null, output_scores: { name: string }[], ): { n_excluded: number; n_used: number } { if (!summary || !rc_id) return { n_excluded: 0, n_used: 0 } const per_score = summary.results?.["" + rc_id] if (!per_score) return { n_excluded: 0, n_used: 0 } for (const s of output_scores) { const ss = per_score[string_to_json_key(s.name)] if (ss) return { n_excluded: ss.n_excluded ?? 0, n_used: ss.n_used ?? 0 } } return { n_excluded: 0, n_used: 0 } }

其核心逻辑是:从EvalResultSummary.results[run_config_id]中,遍历该 eval 声明的 output scores,取第一个可用ScoreSummary的n_excluded/n_used。这样做的依据(见 functional_spec.md §5.2):n_excluded是按 run-config 维度在 eval_api.py 聚合时写入的,对同一 run-config 的各 score key 取值一致,因此"读一次即可"。

ScoreSummary模型本身位于 eval_api.py,其中n_used描述"含全部期望分数且未跳过的 EvalRun 数",n_excluded描述"因skipped_reason被排除的 EvalRun 数";聚合函数score_summary_from_values(eval_api.py)在values为空(全部被排除)时返回mean_score=None, n_used=0, n_excluded=n_excluded——即"全部跳过"场景在 API 层已有明确表达。

5.2 渲染与颜色阈值

在每行 Status 单元格(<td>)内,当n_excluded > 0时渲染一个InfoTooltip,包裹在着色<span>中,并计算跳过比例:

{#if excluded.n_excluded > 0} {@const ratio = excluded.n_excluded / (excluded.n_used + excluded.n_excluded)} <div class="mt-1"> <span class={ratio > 0.2 ? "text-error" : "text-warning"}> <InfoTooltip symbol="info" position="top" tooltip_text={`${excluded.n_excluded} of ${excluded.n_used + excluded.n_excluded} cases were skipped and are not reflected in this score.`} /> </span> </div> {/if}

对照 functional_spec.md §5.1 的严重度约定,边界行为如下:

跳过比例(n_excluded / (n_used + n_excluded))样式含义
> 0%且≤ 20%text-warning(黄色)少量跳过,需知悉
> 20%text-error(红色)大量跳过,分数可信度低
恰为20%text-warning(黄色)严格按ratio > 0.2判断,取黄色
n_used == 0 && n_excluded > 0text-error(红色)比例 100%,全部跳过,mean 为None/"unknown"
n_excluded == 0不渲染无指示器

指示器与既有percent_complete < 1.0的 incomplete 告警共存:两者描述不同条件(前者=部分运行缺失,后者=样本被跳过),且页面级 incomplete 告警位于表格上方的Warning组件(第 133-146 行),行级(i)指示器位于对应行的 Status 单元格,互不遮蔽。

5.3 工具提示文案的范围决策

tooltip 只报计数("{n_excluded} of {n_used + n_excluded} cases were skipped..."),不提供按SkippedReason拆分的明细。这是 functional_spec.md §5.3 的明确决策:聚合 API 只暴露计数而非 per-reason 统计,且不同行可能因不同原因跳过;单条样本的跳过原因已由eval_result_scores.svelte逐条展示,per-reason 聚合需新增 API 工作,明确排除在范围外。

六、Step 5:D35 UI —— judge 对比视图compare/+page.svelte

judge 对比视图位于 compare/+page.svelte/specs/[project_id]/[task_id]/compare/+page.svelte)。文档要求:该视图使用EvalConfigResult,其n_excluded位于 eval-config 级别、ScoreSummary位于每个 score key 下,因此对展示聚合分数的 section header 或 per-score 行应用相同的指示器模式。

最终实现采用与run_config_comparison_table完全一致的数据读取策略——getModelExcluded(第 599-622 行/specs/[project_id]/[task_id]/compare/+page.svelte#L599-L622))从eval_config_result.results中取第一个可用ScoreSummary的n_excluded/n_used,代码注释明确说明"与 run_config_comparison_table 保持数据源一致"。渲染处(第 1095-1111 行/specs/[project_id]/[task_id]/compare/+page.svelte#L1095-L1111))出现两种使用场景:

  • incomplete 分支内:当某行percentComplete < 1.0且n_excluded > 0时,在 "Eval Incomplete" 警示旁叠加同款(i)指示器(同样按ratio > 0.2取text-error/text-warning),tooltip 文案与主视图一致;
  • 正常聚合分数行:{#if excluded_info && excluded_info.n_excluded > 0}分支下渲染同款InfoTooltip(第 1186-1199 行/specs/[project_id]/[task_id]/compare/+page.svelte#L1186-L1199))。

InfoTooltip组件位于 app/web_ui/src/lib/ui/info_tooltip.svelte,symbol="info"即渲染 info-circle 字形——两处视图复用同一 UI 原语,保证视觉一致性。

七、Step 6:测试覆盖 —— 行为即契约

Phase 2 要求为两处 UI 补齐单元测试,仓库中的测试实现与文档列出的用例一一对应。

7.1llm_judge_result.test.ts

llm_judge_result.test.ts 覆盖:

  • "shows View reasoning link when reasoning is present via reasoning key":传入intermediate_outputs: { reasoning: "..." },断言文本包含 "View reasoning";
  • "shows View reasoning link when reasoning is present via chain_of_thought key":验证chain_of_thought键同样触发;
  • "hides View reasoning when intermediate_outputs is null"与"hides View reasoning when intermediate_outputs has no reasoning keys":验证null或无关键(如{ some_other_key: "value" })时不渲染;
  • "opens dialog when View reasoning is clicked":模拟点击后断言模态框内容出现;
  • "shows View reasoning even without eval_config (fallback path)":验证eval_config缺失时的兜底渲染分支。

这组测试精确锁定了"两个键名 + 一个兜底分支"的渲染契约,防止后续改动破坏兼容性。

7.2run_config_comparison_table.test.ts

run_config_comparison_table.test.ts 通过makeSummary(rc_id, n_excluded, n_used, percent_complete)构造各种数据组合,覆盖:

  • "shows info indicator when n_excluded > 0"(3 / 10);
  • "hides info indicator when n_excluded == 0"(0 / 10,断言text-warning button, .text-error button数量为 0);
  • "shows text-warning when ratio <= 0.2"(2 / 10= 20%,黄色且无红色指示器,锁定边界取黄色);
  • "shows text-error when ratio > 0.2"(3 / 10= 30%,红色);
  • "coexists with incomplete-runs warning"(n_excluded=3, n_used=7, percent_complete=0.7,同时断言 "70% Complete" 与指示器同时出现);
  • "renders correct tooltip text with counts":3 / 10时断言 tooltip 文本为 "3 of 13 cases were skipped and are not reflected in this score."——精确锁定计数文案。

八、Step 7:最终检查与质量门禁

Phase 2 的收尾步骤是运行 HooksMCP 全套检查:lint、format、type-check、build、测试、schema 校验。结合 functional_spec.md §9 的质量验收标准,D31/D35 的端到端验收要点为:

  • D31:跑一次批量llm_judgeeval,确认intermediate_outputs被持久化;run-result 页能通过模态框打开推理;code_eval不显示推理入口;test_v2_eval返回该字段;重新生成的 TS 客户端已提交。
  • D35:n_excluded > 0时对比视图出现指示器 + tooltip,0时不出现,且与 incomplete 告警正确共存。

九、补充:为什么这套实现是"最小且正确"的

回到 project_overview.md 的定位,Phase 2 严格遵守两条原则:

  1. 非规定性(non-prescriptive):D31 文档明确"找到正确的展示方式,不要过度工程化",最终选择"模态框而非内联展开、行级链接而非新增列",与 V1 Thinking 列的交互模式保持一致;
  2. 数据已存在,只做表面补齐:intermediate_outputs(eval_api.py)与n_excluded早在 API 层就绪,本阶段不新增任何聚合 API 或存储字段——functional_spec.md §5.2 明确 "Data already flows through the API and generated TS schema — frontend-only change"。

这也解释了 Phase 2 为何把 OpenAPI 重新生成放在 Step 1:后端契约已定(TestV2EvalResponse.intermediate_outputs、V2EvalResult内部契约),前端要做的只是把既有数据接上视图,而让 TS 类型跟上后端,是这一切的前置条件。对于希望复刻这套"视图表面清理"模式的开发者,可以按"确认 API 已暴露 → 重新生成 schema → 接线渲染器 → 条件渲染 + 复用既有 UI 原语 → 补齐行为测试 → 跑全量检查"的流水线推进,每一步在 Kiln 仓库中都有对应的源码与测试锚点可查。

  • 人工智能
  • 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
点击查看免费下载

相关推荐

上一篇:Chrome MCP Server WebSocket通信完整指南:实时数据传输的终极实现方案
下一篇:gitignore.io模板生态系统:社区贡献与审核流程

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询