ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Windmill AI Evals:面向生产级 AI 生成模式的基准测试框架完全指南

Windmill AI Evals:面向生产级 AI 生成模式的基准测试框架完全指南 Windmill AI Evals面向生产级 AI 生成模式的基准测试框架完全指南【免费下载链接】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/windmillWindmill 的ai_evals是一个专为 Windmill AI 生成模式打造的轻量级基准测试运行器benchmark runner覆盖cli、flow、script、app、global五种生成模式。它每次都针对当前 checkout 中的**生产级 prompt、工具与引导guidance**运行真实链路而不是测试简化后的替身。本文将从安装、命令行使用、Case 编写规范、确定性校验与 LLM 评判机制、后端冒烟验证到结果与历史记录的完整流程结合仓库源码逐层展开帮助你用它度量、回归和比较不同 LLM 在 Windmill 各 AI 场景下的真实表现。一次尝试的三层流水线真实路径、确定性验证、LLM 评判ai_evals的设计哲学是真实优先。每一次 attempt尝试都依次执行三个步骤见 ai_evals/README.md走真实的 production path运行真实的模型代理、工具与前端对话循环而不是 mock 掉核心逻辑确定性验证deterministic validation用可复现的规则检查生成产物——结构、语法、引用是否有效、是否复用了工作区已有资源等LLM 评判LLM judging由一个独立的评判模型默认claude-sonnet-4-6基于 checklist 与期望产物对输出的语义完成度打分。这套流水线在 runSuite.ts 中实现每个 case 先loadInitial加载初始状态、loadExpected加载期望产物然后执行modeRunner.run(prompt, initial, context)随后依次追加run succeeded、validate(...)确定性检查、validateToolExpectations工具调用期望、validateAssistantExpectations助手文本期望若 runner 提供了backendValidate还会追加后端冒烟验证最后只要 model 与 case 都允许就调用 LLM judge 打分。一个 attempt 通过passed当且仅当全部 check 通过包括judge score threshold默认阈值 80。安装两种依赖缺一不可cd ai_evals bun install前端模式flow、script、app、global还会复用生产前端聊天代码通过 Vitest bridge因此还需要安装前端依赖cd frontend bun install运行时依赖非常克制见 ai_evals/package.jsonanthropic-ai/claude-agent-sdkCLI 模式、anthropic-ai/sdkjudge、commanderCLI 解析、openaiOpenAI 系前端模型、yamlcase 解析。CLI 入口为 ai_evals/cli/index.ts通过bun run cli -- ...调用其中cli模式的 runner 是懒加载的避免非 CLI 模式引入 wmill CLI 工具链及其 JSR 依赖。命令行速查models / cases / run公开 CLI 面只有三个子命令实现在 cli/index.ts 中models列出当前可用的模型别名cases [mode]列出某个模式或全部模式下的 case 清单run mode [caseIds...]运行基准测试可按 case id 精筛也可跑全量。常用示例cd ai_evals bun run cli -- models # 列出模型别名 bun run cli -- cases # 列出全部模式的 case bun run cli -- cases flow # 只列出 flow 模式的 case bun run cli -- run flow # 跑 flow 全量 bun run cli -- run flow flow-test4-order-processing-loop --model opus # 指定单个模型 bun run cli -- run flow flow-test0-sum-two-numbers --models haiku,opus,4o # 顺序跑多个模型 bun run cli -- run flow flow-test0-sum-two-numbers --runs 3 --verbose # 重复 3 次并流式输出 bun run cli -- run flow --record # 跑全量并把历史写入 JSONL GEMINI_API_KEY... bun run cli -- run app app-test1-counter-create --model gemini-3-flash-preview WMILL_AI_EVAL_BACKEND_URLhttp://127.0.0.1:8000 bun run cli -- run flow --backend-validation preview bun run cli -- run global global-test1-script-create bun run cli -- run cli bun-hello-scriptrun子命令的完整选项含源码中的默认值与约束见 cli/index.ts选项含义约束与默认值--runs n每个 case 重复执行的次数必须为正整数默认1--output path自定义结果 JSON 路径仅支持单模型运行多模型时禁用--model alias选择被测模型与--models互斥--models a,b,c顺序跑多个模型别名逗号分隔自动去重最后输出模型汇总对比--verbose流式输出前端运行的助手输出通过 progress 事件逐 chunk 推送--skip-judge跳过 LLM judge 打分适合只做确定性验证--execution-only只要求模型/代理/前端循环能跑完跳过校验器、工具期望、后端产物验证与 judge--record追加一行紧凑摘要到ai_evals/history/mode.jsonl仅支持全量跑传 case id 会报错--backend-validation mode后端冒烟验证仅off/preview且只支持script与flow另外两条运行期约束值得一提前端模式启动前会调用assertWindmillBackendReachable检查后端可达性与登录失败会提前给出搭建指引见 cli/index.ts多模型跑完后会输出Model summary逐模型列出 pass rate 与通过尝试的平均耗时方便横向对比。模型别名体系一个别名映射到底层 providerai_evals用别名alias屏蔽底层 provider 差异完整清单定义在 ai_evals/core/models.ts以bun run cli -- models输出为准。当前内置别名haikuClaude Haiku 4.5、sonnetClaude Sonnet 4.5、opusClaude Opus 4.64oGPT-4o、gpt-5.5GPT-5.5gemini-3-flash-preview、gemini-3.1-pro-previewdeepseek-v4-flash、deepseek-v4-pro每个别名还接受多种拼写如gpt-4o、gpt-55、claude-opus-4.6、claude-haiku-4.5解析时会统一小写化后匹配 id 与 aliases 列表。有几个模式相关的硬约束前端模式flow、script、app、global可用 Anthropic、OpenAI、Gemini、DeepSeek 四家后端的别名cli模式始终走 Anthropic agent SDK只接受 Anthropic 别名judge 模型独立于被测模型默认claude-sonnet-4-6可通过--skip-judge只做确定性验证。Case 格式一个 YAML 文件描述一个验收任务每个模式的 case 集中放在一个 YAML 文件中ai_evals/cases/ 下的flow.yaml、script.yaml、app.yaml、global.yaml、cli.yaml由 core/cases.ts 加载——文件必须是 YAML 列表其中initial/expected相对路径会被解析为仓库根目录下的绝对路径。最小形态- id: flow-test0-sum-two-numbers prompt: |- Create a flow that takes two numbers, a and b, and returns their sum. initial: ai_evals/fixtures/... expected: ai_evals/fixtures/...可选字段对应 core/types.ts 的EvalCaseinitial起始状态 fixtureexpected期望产物 fixturevalidate额外的确定性校验规则toolExpect工具调用期望requiredToolsUsed、requiredToolsAnyOf、forbiddenToolsUsed、toolCallArgs等cliExpectCLI 模式的技能/命令/工作区校验assistantExpect助手文本期望对global这类交付物是草稿的模式尤为重要——judge 看不到助手说了什么只能靠这里断言judgeChecklist传给 judge 的显式验收标准skipJudge跳过该 case 的 judgeruntime运行期配置如backendPreview真实后端预览参数、sessionChat、planMode、maxTurns、appContext等。flow 模式的 validate 能力FlowValidationSpeccore/types.ts可表达输入 schema 形状schemaRequiredPaths、schemaAnyOf顶层步骤topLevelStepIds、exactTopLevelStepIds、topLevelStepOrder、topLevelStepTypes、topLevelStepTypeCountsAtLeast模块/代码/输入特征moduleRules中的hasStopAfterIf、immediateChildStepIds、requiredInputTransformsmoduleFieldRules中的字段取值results.*引用有效性resolveResultsRefs特殊模块与挂起步骤requireSpecialModules、requireSuspendSteps。例如真实的 flow.yaml 中flow-test13-prefer-existing-workspace-flow用exactTopLevelStepIds限定唯一顶层步骤、topLevelStepTypes断言其类型为flow、moduleRules.requiredInputTransforms断言输入变换表达式为flow_input.a/flow_input.b——从源码结构看这套规则是为了强约束复用工作区已有子流程而非内联逻辑的行为。app 模式的 validate 能力AppValidationSpeccore/types.ts可表达必需前端文件路径requiredFrontendPaths与文件内容片段requiredFrontendFileContent后端可运行项requiredBackendRunnableKeys、requiredBackendRunnableTypes、requiredBackendRunnableContent、backendRunnableCountAtLeastdatatable 数量与具体表datatableCountAtLeast、datatableTableCountAtLeast、datatableTableCountExactly、requiredDatatables工具使用与禁止内容requiredToolsUsed、forbiddenAppContent。App 的通用确定性检查core/validators.ts还包括必须有/index.tsx前端入口、前端文件非空、前后端无语法错误、后端内联脚本必须有入口、前端引用的后端引用必须可解析以及与 initial 不同。global 模式的 validate 能力GlobalValidationSpeccore/types.ts聚焦草稿draft级要求draftCountAtLeast/draftCountExactly、requiredDrafts可限定草稿 type/path/language、summary 与 value 的包含/排除片段、triggerKind、forbiddenDrafts。所有 global 输出必须都是草稿isDraft true脚本草稿还会做入口导出与语法检查。fixtureinitial 里的高级种子Global 的 initial fixture 可以做三件很真实的事种子liveEditorDrafts含type、storagePath、effectivePath、value模拟当前打开的 script/flow/raw app 编辑器从而测试引用 this / current 的 prompt种子会话artifacts与previewTabs{ name, versions: [{content, note?}], role?, approvedVersion? }按最旧在前排列配合runtime.sessionChat: true使用previewTabs中的 tab 由生产 tab 模型驱动open_preview、get_preview_status、close_page会真实打开/报告/关闭它们version是用户在产物版本选择器里钉住的版本只有get_preview_status会报告种子workspace.variables{ path, value, is_secret, description?, labels?, ws_specific? }mock 忠实复刻真实get_variable的默认解密语义——只有调用方显式传decryptSecret: false时才隐藏 secret 的 value而 chat 读取路径正是传decryptSecret: false所以 case 可以验证模型不会编造没见过的值。README 还给出一个泄密检测技巧种一个醒目的 secret现有 fixture 用sk_live_do_not_leak_me再用valueExcludes断言抓到即判泄露。toolExpect 的高级规则toolExpect.toolCallArgscore/types.ts为每个工具参数提供多种断言语义stringStartsWithAnyOf全调用普遍性——对该工具的每次记录调用字段都必须以这些前缀之一开头stringEqualsAnyOf全调用普遍性 精确匹配用于$res:f/a/b这类前缀会被_backup后缀污染的引用stringIncludesAnyOf存在性——至少一次调用包含子串即可适合 SQL 中变更语句与验证 SELECT 混用的场景nonEmpty每次调用该字段都非空fieldMustBeAbsent没有任何一次记录调用传过该字段——显式null也算传了。这是为部分更新工具设计的模型提供了它本不该读到的字段本身就是失败典型如对 secret 变量调用write_variable.value。此外toolCallArgsSameCall要求多个字段约束发生在同一次调用上例如一次调用同时带 label 与 worker 过滤打开 Runs 页而不是两次调用各带一个requiredToolsAnyOf则允许多工具满足同一意图时的任选路径如read_app_file或search_app均可。datatable 种子与内存 SQL 引擎Global及 flow的 initial fixture 可以种子workspace.datatables{ datatable_name, schemas: { schema: { table: { columns, rows? } } } }让list_datatables、get_datatable_table_schema、exec_datatable_sql在 eval 期间返回种子数据。SQL 跑在 datatableSqlEngine.ts 这个小型内存引擎上而非真实数据库但写入在一个 case 内是有状态的CREATE/DROP/INSERT/UPDATE/DELETE会就地修改种子 datatable后续list_datatables、get_datatable_table_schema、SELECT、information_schema都能看到——这正是防止模型反复查询验证写入而陷入循环的关键。该引擎是尽力而为SELECT返回引用或第一个表的全部行、无 WHERE/投影/joinUPDATE/DELETE 的 WHERE 仅支持col value的 AND 组合解析不了就当成功 no-op。因此 datatable case 应该通过工具使用与 SQL 参数断言requiredToolsUsed、stringIncludesAnyOf来验证而不是断言精确返回行。空/缺省datatables种子会让list_datatables返回[]这正是未配置 datatable 就阻止执行这类阻断 case 依赖的行为。App fixture 还可以在 fixture 根目录放一个可选的datatables.jsonflow 的 initial fixture 则可以包含一个benchmark 工作区目录现有脚本与流程让真实的search_workspace、get_runnable_details工具在 eval 中发现可复用资源——flow case 中复用已有脚本/子流程的测试就是靠这个实现的。后端冒烟验证--backend-validation preview当启用--backend-validation preview或设置WMILL_AI_EVAL_BACKEND_VALIDATIONpreview时core/backendValidation.tsscripteval 会在一个隔离的临时工作区跑真实的脚本预览floweval 只对定义了runtime.backendPreview的 case 跑真实流程预览比如flow-test0-sum-two-numbers就配了args: {a: 4, b: 5}flow case 若带initial.workspacefixture会先把这些脚本/流程种子进预览工作区再预览设置了WMILL_AI_EVAL_BACKEND_WORKSPACE时会创建/复用该工作区作为专用测试工作区每次预览前清空f/evals/*下的受管 eval 资源再重新种子当前 case 的 fixture这对 CE 低工作区配额环境尤其有用。支持的后端环境变量WMILL_AI_EVAL_BACKEND_VALIDATIONpreviewWMILL_AI_EVAL_BACKEND_URLhttp://127.0.0.1:8000WMILL_AI_EVAL_BACKEND_EMAILadminwindmill.devWMILL_AI_EVAL_BACKEND_PASSWORDchangemeWMILL_AI_EVAL_BACKEND_WORKSPACEintegration-tests复用已有工作区WMILL_AI_EVAL_BACKEND_POLL_INTERVAL_MS默认 2000、WMILL_AI_EVAL_BACKEND_MAX_WAIT_MS默认 120000——后两个是源码中的轮询与等待上限参数前端模式需要可达的 Windmill 后端模型请求经由工作区 AI 代理/api/w/{workspace}/ai/proxy发出。启动时ai_evals会检查解析出的后端 URL后端不可达或登录失败会提前失败并给出搭建指引。前端模式的运行流程为创建临时后端工作区或设置WMILL_AI_EVAL_BACKEND_WORKSPACE时创建/复用指定工作区、在f/evals/ai/provider下 upsert provider 资源、请求统一走/api/w/{workspace}/ai/proxy。结果与产物从 JSON 摘要到逐 attempt 的评审每次运行都会产出一份摘要 JSON位于ai_evals/results/如2026-04-09T09-40-33.051Z__flow.json生成的产物放在同名的兄弟目录如2026-04-09T09-40-33.051Z__flow/。各模式的典型产物见 core/results.ts 的writeRunArtifacts按caseId/attempt-N/组织flowflow.jsonscriptscript.json 生成的脚本文件appapp.json 前端/后端文件globalglobal-drafts.jsoncliassistant-output.txt、trace.json、wmill-invocations.jsonl 生成的工作区文件后端验证过的 attempt 还会带backend-preview.json若使用--recordCLI 会向ai_evals/history/{flow,script,app,global,cli}.jsonl追加一行紧凑 JSON包含运行元数据createdAt、gitSha、mode、runModel、judgeModel套件汇总caseCount、attemptCount、passedAttempts、passRate、averageDurationMs、averagePassedDurationMs、averageJudgeScoretoken 用量averageTokenUsagePerAttempt、averageTokenUsagePerPassedAttempt以及averageFinalContextTokensPassed、maxFinalContextTokensPassed逐 case 指标cases[]与failedCaseIds。一个重要的统计口径README 与 results.ts 双重确认CLI 头部的耗时与 token 平均值只统计通过的 attempt但全量 attempt 的均值仍会记录——这样失败尝试既保持可审计又不会扭曲成功成本对比。摘要输出还会列出前 10 条失败明细caseId attempt N: 未通过的检查名。目录布局与测试双轨ai_evals的目录职责README Layout 一节cases/每个模式一个 YAML 文件fixtures/initial 与 expected fixturesfrontend 下按模式再分子目录如frontend/flow/expected/里的test0_sum_two_numbers.jsoncore/共享的加载、模型解析、校验、评判与结果写入modes/每个模式一个 runnerhistory/run --record写入的 pass-rate 历史每模式一个 JSONLresults/本地基准输出与产物adapters/CLI 运行时适配器与前端Vitest bridge适配器。单元测试跑在两条独立轨道上bun test adapters/跑纯 TypeScript 测试bun run test:frontend-graph跑*.vitest.ts文件通过cd ../frontend vitest run --project server驱动见 package.json专门覆盖依赖前端代码Svelte runes、SvelteKit 别名而 bun 无法直接加载的适配器。core/下每个模块几乎都配有*.test.tscases.test.ts、validators.test.ts、judge相关、runSuite.test.ts、results.test.ts等adapters/frontend/下还有datatableSqlEngine.test.ts、mockBackend*.test.ts、windmillBackend.test.ts等为 case 编写者提供了丰富的断言语义参考。各模式要点与注意事项前端模式复用生产前端聊天代码通过 Vitest bridge 把生产 chat 代码接进 eval--verbose时可实时流式看到助手输出与工具调用progress 事件含assistant-chunk、tool-call等见 core/types.tsGlobal 模式评估生产的 global AI 工具并校验最终的 AI draft storeCLI 模式创建隔离工作区、把当前 checkout 的引导guidance写进去然后基准测试真实的 skills /AGENTS.md流程同时记录结构化 trace调用的 skills、工具调用、提出的wmill命令、尝试执行的wmill命令cliExpect可据此断言requiredSkillsBeforeFirstMutation、orderedProposedCommands、forbiddenExecutedCommands、workspaceUnchanged等行为环境变量WMILL_AI_EVAL_DISABLE_ACTIVE_EDITOR_CONTEXT1可以回退到旧行为live editor 只能通过list_workspace_items发现确定性校验器应该聚焦真实正确性约束而不是某一种精确实现形态——这是 README 反复强调的编写原则也是避免 eval 退化成背答案的关键。结语ai_evals的价值在于它把生产 prompt 真实工具链 确定性校验 LLM 评判组合成了一个可重复、可审计、可对比的闭环用--models一键横向对比多家模型用--record沉淀 pass-rate 历史追踪回归用validate/toolExpect/judgeChecklist三层约束把好不好翻译成可执行的检查项。无论你是 Windmill 的贡献者、自托管用户还是想评估自己 prompt 质量的开发者都可以从 ai_evals/cases/ 的现有 case 入手按本文的字段规范添加自己的验收场景让每一次 AI 能力升级都有数据可依。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

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