ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Windmill AI Evals 历史记录与 gitSha 锚定机制深度解析:读懂 `history/*.jsonl` 的每一条基准运行记录

Windmill AI Evals 历史记录与 gitSha 锚定机制深度解析:读懂 `history/*.jsonl` 的每一条基准运行记录 Windmill AI Evals 历史记录与 gitSha 锚定机制深度解析读懂history/*.jsonl的每一条基准运行记录【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill本文围绕 Windmill 开源仓库中 ai_evals/history/README.md 所定义的核心机制展开AI 评测AI Evals每次完整运行的历史记录行是如何锚定到 benchmark 定义提交gitSha上的以及这套设计如何保证历史数据可审计、可回溯、不被无关提交污染。读完本文你将掌握--record命令的触发条件、JSONL 记录行的完整字段语义、gitSha的精确含义以及如何像阅读仓库自带的历史数据ai_evals/history/flow.jsonl、ai_evals/history/cli.jsonl一样去分析你自己的评测历史。一、背景Windmill AI Evals 是什么Windmill 是一个开源开发者平台可将脚本转化为 Webhook、工作流与 UI。为了持续度量其 AI 生成能力cli、flow、script、app、global五种模式仓库在 ai_evals/ 目录内置了一个轻量级基准运行器benchmark runner。根据 ai_evals/README.md每次尝试attempt都走真实生产链路real production path并依次经过真实生产路径执行real production path确定性校验deterministic validationLLM 评判LLM judging。每次完整运行full-suite run会产出三类产物results/下的完整 JSON 摘要、伴随的产物目录artifacts以及——在显式传入--record时——向ai_evals/history/mode.jsonl追加的一条紧凑历史记录。本文的主角正是后者。二、历史记录的核心设计记录锚定到 benchmark 定义提交ai_evals/history/README.md用三句话定义了这个目录的语义这也是整篇文章要展开的骨架Recorded history rows are anchored to the benchmark-definition commit used for the run.That meansgitShapoints to the commit whose prompts, evaluators, and fixtures produced the recorded result. A later commit may only add the new JSONL row to git history without changing the benchmark itself.翻译并拆解其技术含义锚定对象是benchmark 定义提交每一条历史记录中的gitSha指向的是生成该结果的那份 prompts、evaluators 与 fixtures所在的提交而不是追加这条 JSONL 行的提交。两者通常不是同一个提交后续提交可能只是把新的 JSONL 行加入 git 历史A later commit may only add the new JSONL row却完全没有改动 benchmark 本身。此时新记录的gitSha仍指向旧的 benchmark 定义提交。意义这套设计让历史曲线具备严格的控制变量语义——对比两条记录时只要gitSha相同就说明 prompt、校验器、fixture 完全一致指标差异只可能来自模型、随机性或后端环境而gitSha不同则意味着 benchmark 本身发生了迁移跨版本直接对比通过率需要格外谨慎。这个记录行提交 ≠ benchmark 定义提交的区分正是history/目录区别于普通 git 日志的核心价值所在。三、如何产生一条历史记录--record命令历史记录不是每次运行都会写而是由 CLI 的--record开关显式控制。查看 ai_evals/cli/index.ts 中run子命令的定义.option( --record, append a compact summary line to ai_evals/history/mode.jsonl, )在handleRun中--record与 case 筛选是互斥的ai_evals/cli/index.tsif (input.record input.caseIds.length 0) { throw new Error( --record only supports full-suite runs; omit case ids to record history, ); }即--record只支持全量套件运行一旦你同时指定了具体 case idCLI 会直接报错。原因很直观——只有全量运行才具备横向可比性部分 case 的选择性运行写入历史会污染通过率曲线。记录写入发生在每次模型运行完成后ai_evals/cli/index.tsconst resultPath await writeRunResult(result, resolvedOutputPath); const historyPath input.record ? await appendHistoryRecord(result) : null; process.stdout.write(${formatRunSummary(result)}\n); process.stdout.write(Saved: ${resultPath}\n); if (historyPath) { process.stdout.write(Recorded: ${historyPath}\n); }典型用法来自 ai_evals/README.md 的 Commands 一节cd ai_evals bun run cli -- run flow --record若同时想为多个模型顺序跑同一套 case 并各记一行bun run cli -- run flow flow-test0-sum-two-numbers --models haiku,opus,4o注意--models多模型顺序运行时每个模型都会单独构建结果并追加记录。四、记录长什么样真实 JSONL 数据解剖history/目录下每个模式一个 JSONL 文件每行是一条完整运行的紧凑摘要。以 ai_evals/history/flow.jsonl 中 2026-04-15 的一次 claude-opus 运行为例已格式化{ createdAt: 2026-04-15T14:04:19.086Z, gitSha: cc3e17dbc1c204b5d4e30ad449d59e9e7cd0bb89, mode: flow, runs: 2, runModel: anthropic:claude-opus-4-6, judgeModel: claude-sonnet-4-6, caseCount: 13, attemptCount: 26, passedAttempts: 26, passRate: 1, averageDurationMs: 53226.5, averageJudgeScore: 95.8076923076923, averageTokenUsagePerAttempt: { prompt: 136106.3076923077, completion: 2673.5, total: 138779.8076923077 }, failedCaseIds: [], cases: [ { id: flow-test0-sum-two-numbers, attemptCount: 2, passedAttempts: 2, passRate: 1, averageDurationMs: 27188.5, averageJudgeScore: 98, averageTokenUsagePerAttempt: { prompt: 119289, completion: 630.5, total: 119919.5 } } ] }注意此例中gitSha为cc3e17dbc1c204b5d4e30ad449d59e9e7cd0bb89——这条记录锚定的是产生这 13 个 case 的 prompt/校验器/fixture 的那个提交。它既可能是运行时的 HEAD也可能早于实际写入 JSONL 行的提交这正体现了第二节所述的锚定语义。再看 ai_evals/history/cli.jsonl 中的 cli 模式记录可以观察模式差异——cli 模式不报告 token 用量averageTokenUsagePerAttempt: null且runModel使用anthropic:haiku、anthropic:opus这种简写别名{createdAt:2026-04-10T14:28:09.045Z,gitSha:8f8b487be517a0bdd318c36857c1d46d5ab0723a,mode:cli,runs:1,runModel:anthropic:opus,judgeModel:claude-sonnet-4-6,caseCount:6,attemptCount:6,passedAttempts:6,passRate:1,averageDurationMs:24988.833333333332,averageJudgeScore:99.66666666666667,averageTokenUsagePerAttempt:null,failedCaseIds:[],cases:[{id:bun-hello-script,attemptCount:1,passedAttempts:1,passRate:1,averageDurationMs:22034,averageJudgeScore:100,averageTokenUsagePerAttempt:null}, ...]}五、记录字段的源码级语义每条记录由toHistoryRecord()从BenchmarkRunResult转换而来实现在 ai_evals/core/results.ts。逐组解读5.1 运行元数据run metadata字段来源语义createdAtnew Date().toISOString()运行构建结果的 UTC 时间戳gitShagetGitSha()执行git rev-parse HEADbenchmark 定义提交而非记录追加提交见 ai_evals/core/results.tsmode命令行参数cli/flow/script/app/globalruns--runs n每个 case 的重复尝试次数默认 1runModelformatRunModelLabel被测模型别名如anthropic:claude-haiku-4-5-20251001judgeModel默认claude-sonnet-4-6见 ai_evals/core/judge.ts 的DEFAULT_JUDGE_MODEL独立于被测模型的评判模型getGitSha()的实现值得注意——它用execFileSync(git, [rev-parse, HEAD], { cwd: getRepoRoot() })在仓库根目录执行仓库根由 ai_evals/core/cases.ts 的REPO_ROOT解析如果失败例如在非 git 检出中运行则返回null。5.2 套件汇总suite totals字段计算方式caseCount本次运行的 case 数量attemptCount所有 case 的尝试总数 caseCount × runspassedAttempts所有检查全部通过的尝试数passRatepassedAttempts / attemptCountaverageDurationMs所有尝试的平均耗时averagePassedDurationMs仅通过尝试的平均耗时无通过时nullaverageJudgeScore所有含数值 judgeScore 的尝试的平均分averageTokenUsagePerAttempt/averageTokenUsagePerPassedAttempt每次尝试 / 每次通过尝试的平均 prompt/completion/total tokenfailedCaseIds存在失败尝试的 case id 去重列表这里有一个刻意设计README 与 ai_evals/core/results.ts 都明确说明CLI 对外展示的耗时与 token 均值只用通过尝试passed attempts但所有尝试的均值仍完整落盘目的是让失败尝试可审计同时避免失败尝试的异常耗时/耗 token 扭曲成功成本的比较。5.3 每个 case 的指标cases[]cases[]中每个条目对该 case 自身重新聚合字段与套件级一一对应id、attemptCount、passedAttempts、passRate、averageDurationMs、averagePassedDurationMs、averageJudgeScore、averageTokenUsagePerAttempt、averageTokenUsagePerPassedAttempt、averageFinalContextTokensPassed、maxFinalContextTokensPassed。聚合逻辑复用aggregateAttempts()ai_evals/core/results.ts它逐次累加durationMs、finalContextTokens与tokenUsageprompt/completion/total 三通道分别累加。finalContextTokens与累计 prompt token 是互补指标前者是 agentic 循环中最后一次模型请求的输入 token含缓存创建与命中后者是所有迭代输入之和见 ai_evals/core/types.ts 的类型注释。5.4 记录与完整结果的关系历史记录是results/完整 JSON 的紧凑投影完整结果ai_evals/results/时间戳__mode.json含每次 attempt 的全部checks、judgeSummary、错误信息与产物路径ai_evals/core/results.ts历史记录仅保留聚合指标与failedCaseIds去掉了 attempt 级明细。此外历史记录不会携带artifactFiles——toSerializableRunResult()与toHistoryRecord()都将其剥离。因此 JSONL 行非常紧凑适合长年累月追加。六、从源码验证写入链路与测试完整写入链路为bun run cli -- run mode --record └─ runSuite() // ai_evals/core/runSuite.ts 并发执行各 case └─ buildRunResult() // 聚合套件指标 getGitSha() └─ appendHistoryRecord() // resolveHistoryPath() 定位 ai_evals/history/mode.jsonl └─ toHistoryRecord() // 转换为紧凑记录 └─ appendFile(..., JSON.stringify(record) \n)appendHistoryRecord与resolveHistoryPath的实现在 ai_evals/core/results.ts 与 ai_evals/core/results.tsexport async function appendHistoryRecord( result: BenchmarkRunResult, historyPath resolveHistoryPath(result.mode), ): Promisestring { await mkdir(path.dirname(historyPath), { recursive: true }); await appendFile( historyPath, JSON.stringify(toHistoryRecord(result)) \n, utf8, ); return historyPath; } export function resolveHistoryPath(mode: EvalMode): string { return path.join(getAiEvalsRoot(), history, ${mode}.jsonl); }注意这里用的是追加模式appendFile且 JSONL 每行一个 JSON 对象、以换行结尾——这是标准的 append-only 日志格式天然支持用tail、jq -s等工具增量消费。测试验证方面ai_evals/core/results.test.ts 的records passed-attempt metrics in history用例直接在临时目录中调用appendHistoryRecord()断言落盘记录中套件级与 case 级的averageDurationMs、averageTokenUsagePerAttempt、averagePassedDurationMs等字段与预期聚合值一致。同文件前几个用例keeps success cost metrics separate from failed attempts、aggregates final context size over passed attempts only、reports passed averages as unavailable when no attempt passes则验证了通过/全部尝试分开统计、finalContextTokens仅按通过尝试聚合、以及全失败时averagePassedDurationMs为null的行为。七、实战如何分析与消费历史记录7.1 查看已有历史仓库自带四个模式的 JSONL 历史文件ai_evals/history/flow.jsonlai_evals/history/script.jsonlai_evals/history/app.jsonlai_evals/history/cli.jsonlglobal.jsonl由运行bun run cli -- run global --record时创建。7.2 用 jq 按模型对比通过率# 查看 flow 历史中每条记录的模型与通过率 jq -r [.createdAt, .gitSha, .runModel, .passRate, .averageJudgeScore] | tsv ai_evals/history/flow.jsonl # 统计同一 gitSha同一 benchmark 定义下各模型的平均通过率 jq -s group_by(.runModel) | map({runModel: .[0].runModel, passRate: (map(.passRate) | add / length), records: length}) ai_evals/history/flow.jsonl7.3 定位回归由于gitSha锚定 benchmark 定义提交当你要判断某次改动是否导致通过率下降时先看新旧两条记录的gitSha是否相同相同 → benchmark 未变指标差异可归因于模型/环境/随机性不同 → benchmark 已迁移需要结合failedCaseIds与results/完整 JSON 定位具体失败 case而不是直接对比通过率数字。7.4 组合results/深入失败案例历史记录只给了failedCaseIds要看到具体失败的checks、judgeSummary与错误信息需要回到同一时刻的完整结果文件命名格式YYYY-MM-DDTHH-MM-SS.sssZ__mode.json位于 ai_evals/results/ 目录。八、设计权衡与注意事项为什么记录行提交 ≠ benchmark 提交如果gitSha记录的是写入行的提交那么仅仅追加一行 JSONL 就会改变所有历史行的语义使benchmark 是否变化无法从数据中判断。锚定到 benchmark 定义提交让每条记录自带版本指纹这是该设计最核心的取舍。--record只允许全量运行防止部分 case 选择运行混入历史、扭曲套件级通过率。cli 模式的特殊性cli 模式走 Anthropic Agent SDKai_evals/README.md 的 Models 一节只支持 Anthropic 别名且该模式不报告 token 用量真实 cli.jsonl 中averageTokenUsagePerAttempt均为null。passRate 与 judgeScore 是两套信号passRate由确定性校验与 judge 阈值默认 80 分见 ai_evals/core/runSuite.ts共同决定averageJudgeScore则是评判模型打分的均值。从 ai_evals/history/flow.jsonl 可以看到某些 case如flow-test10-while-loop-counterjudgeScore 很高90却未通过说明确定性校验捕获了 judge 不易察觉的结构性缺陷。gitSha可能为null当运行环境不是 git 检出、git rev-parse HEAD失败时记录中的gitSha为null此时锚定语义失效跨记录对比需人工确认 benchmark 是否一致。九、小结ai_evals/history/不是简单的运行日志而是一套以gitSha为锚点的、可审计的基准历史。其核心契约——gitSha指向产生结果的 benchmark 定义提交而非追加记录的提交——保证了历史曲线在 benchmark 不变时可严格对比在 benchmark 迁移时可追溯差异来源。配合--record的 full-suite-only 约束、通过/全部尝试分离的成本统计以及 ai_evals/core/results.ts 中紧凑的toHistoryRecord()序列化这套设计为持续度量 Windmill 的 AI 生成质量提供了可靠的量化基础。想要深入实践可以从 ai_evals/README.md 起步用bun run cli -- run mode --record复现一次完整运行再用jq分析你自己的history/mode.jsonl。【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表