oh-my-claudecode executor 基准实战:为 Express POST /api/products 端点实现输入校验的任务拆解与自动化评测
【免费下载链接】oh-my-claudecodeTeams-first Multi-agent orchestration for Claude Code项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-claudecode
在 oh-my-claudecode 中,benchmarks/executor目录存放着一组用来衡量“执行型 Agent(Executor / Deep Executor)”实现质量的基准任务,而 task-input-validation.md 正是其中最具代表性的一个:它模拟一个真实到可以直接落地的 TypeScript + Express 场景——POST /api/products端点目前“来者不拒”,需要为它补齐字段级输入校验。读完本文,你将掌握该任务的全部需求细节与可运行的参考实现思路,理解它的 ground-truth 打分口径,并能用仓库自带的基准运行器亲手复现一次对执行 Agent 的质量评估。
一、先看清这份文档在仓库中的身份:一个 Executor Benchmark Fixture
首先需要厘清:task-input-validation.md并不是 oh-my-claudecode 某个运行模块的说明文档,而是一份面向执行 Agent 的合成任务规格(task fixture)。它被放在任务类目录 fixtures/tasks/ 下,与task-add-timestamp.md、task-notification-refactor.md并列,每一份都对应一份同名的 ground-truth 标注文件。这种结构决定了它的两个读取姿势:
- 给 Agent 读:它是一张“工单”,要求执行 Agent 基于其中的 Context、既有代码和 Validation Requirements 给出实现方案;
- 给评测器读:它的校验点是可枚举、可判定的,评测器可以依据 ground-truth/task-input-validation.json 中的 6 条关键“发现(findings)”自动判断 Agent 有没有抓到要害。
从 run-benchmark.ts 的源码可以看到,fixture 内容会被直接拼进用户消息:
function buildUserMessage(fixtureContent: string): string { return `Implement the following task. Describe your approach, the files you would modify, and the changes you would make:\n\n${fixtureContent}`; }也就是说,文档正文本身就是在评测中交给模型的原样输入。理解这一点,后续所有讨论都有了坐标系。
二、任务背景与既有代码:文档给出了什么
文档首先交代了背景:POST /api/products当前对任何输入都直接放行、不校验,需要“在创建商品之前加上合理的输入校验”。随后给出了三段伪仓库代码——注意,这三段中的src/前缀是任务设定内的“假设项目”文件,并非本仓库真实源码,读者在实现时应当把它当作一个独立的小型 Node 工程看待。
第一段是商品类型定义:
// src/types/product.ts export interface Product { id: string; name: string; description: string; price: number; category: 'electronics' | 'clothing' | 'food' | 'other'; sku: string; inStock: boolean; createdAt: Date; }第二段是 Express 路由入口,其中POST /api/products直接把req.body交给createProduct,任何异常都吞成笼统的500:
// src/api/routes/products.ts import { Router } from 'express'; import { createProduct } from '../../services/product-service'; const router = Router(); router.post('/products', async (req, res) => { try { const product = await createProduct(req.body); res.status(201).json(product); } catch (err) { res.status(500).json({ error: 'Failed to create product' }); } }); router.get('/products/:id', async (req, res) => { const product = await getProduct(req.params.id); if (!product) return res.status(404).json({ error: 'Product not found' }); res.json(product); }); export default router;第三段是服务层,Partial<Product>让它对所有缺省字段都做“兜底填空”,这正是“无效输入也能被静默接受”的根源:
// src/services/product-service.ts import { Product } from '../types/product'; import { db } from '../database'; import { generateId } from '../utils/id'; export async function createProduct(input: Partial<Product>): Promise<Product> { const product: Product = { id: generateId(), name: input.name || '', description: input.description || '', price: input.price || 0, category: input.category || 'other', sku: input.sku || '', inStock: input.inStock ?? true, createdAt: new Date(), }; await db.products.insert(product); return product; }三段代码相互配合,暴露出三个可被攻击的坏味道:路由层无校验、服务层默认值吞掉脏数据、错误码永远 500。这正是文档要求执行 Agent 去“读代码、找模式、定方案”的素材。
三、Validation Requirements 的字段级拆解:七条需求逐条落地
文档给出的需求清单是整份任务的核心,必须原样保留并逐条展开。任务要求新增校验而不是重写,所有规则都应落在“校验层”这一关注点上。
name: required, string, 1-200 charactersdescription: optional, string, max 2000 charactersprice: required, number, must be >= 0, max 2 decimal placescategory: required, must be one of the valid categoriessku: required, string, must match pattern^[A-Z]{2,4}-\d{4,8}$- Return 400 with descriptive error messages for validation failures
- Do not modify the Product interface or existing GET route
逐条解读其工程含义:
- 规则 1(name):
required意味着空串、纯空白、缺失都要被判为失败;“string”排除了数字与对象;“1-200 characters”给出了边界。注意 TS 的string类型只能约束“编译期类型”,无法阻止42或{ name: 42 }这类运行时脏值,因此必须做运行时检查,typeof value !== 'string'是第一道闸。 - 规则 2(description):唯一可选字段。可选的正确实现是“未提供则跳过,提供了就必须是字符串且不超过 2000 字符”,而不是“默认给空串”——后者会让
description: 123这类错误静默通过,违背校验语义。 - 规则 3(price):最易出边界问题的一条。
>= 0排除负数;“number”排除字符串"19.99";而“max 2 decimal places”在浮点语境下需要专门处理——常见做法是验证Number.isInteger(price * 100),因为19.999 * 100 = 1999.9非整数即判定失败。此外还应考虑NaN、Infinity、非有限数,这些都能绕过>= 0。 - 规则 4(category):枚举校验应与类型定义中
'electronics' | 'clothing' | 'food' | 'other'严格一致。建议定义一个可复用的常量数组,由它推导联合类型,避免校验逻辑与类型定义“手写两份、悄然漂移”。 - 规则 5(sku):正则
^[A-Z]{2,4}-\d{4,8}$的含义是:2~4 个大写英文字母 + 一个连字符 + 4~8 位数字,且必须整体匹配(^、$锚定)。如果业务上允许小写输入,可先toUpperCase()再校验——但是否规范化属于业务决策,任务只要求“必须匹配该模式”,实现时应避免自行放宽。 - 规则 6(400 响应):校验失败要返回 HTTP 400,并给出“能说明哪个字段、为什么失败”的描述性错误。这正是文档第一节路由代码里
catch一律 500 的反面教材——校验错误属于客户端问题,绝不应当被当作服务端故障吞成 500。 - 规则 7(约束红线):不改
Product接口、不动既有GET /products/:id路由。这是一条改动范围约束,等价于要求“最小可行变更”:校验应作为新增逻辑(内联在 POST handler 或独立校验函数/中间件),不得顺手重构既有代码。
四、一份贴合七条需求的参考实现思路
任务没有规定必须用哪个校验库,且从规则 7 的“最小改动”精神看,纯 TypeScript 手写一个校验函数是零依赖、可复制的最稳方案。参考实现可以收敛为一个返回“字段级错误列表”的纯函数,便于路由层一次性收集所有问题:
// src/api/routes/products.ts 中新增的校验函数(示意) const VALID_CATEGORIES = ['electronics', 'clothing', 'food', 'other'] as const; const SKU_PATTERN = /^[A-Z]{2,4}-\d{4,8}$/; interface ValidationResult { valid: boolean; errors: Record<string, string>; } function validateProductInput(body: unknown): ValidationResult { const errors: Record<string, string> = {}; const input = (body ?? {}) as Record<string, unknown>; const name = input.name; if (typeof name !== 'string' || name.trim().length === 0) { errors.name = 'name is required and must be a non-empty string'; } else if (name.length > 200) { errors.name = 'name must be at most 200 characters'; } if (input.description !== undefined) { if (typeof input.description !== 'string') { errors.description = 'description must be a string when provided'; } else if (input.description.length > 2000) { errors.description = 'description must be at most 2000 characters'; } } const price = input.price; if (typeof price !== 'number' || !Number.isFinite(price) || price < 0) { errors.price = 'price is required and must be a non-negative number'; } else if (!Number.isInteger(price * 100)) { errors.price = 'price must have at most 2 decimal places'; } const category = input.category; if (typeof category !== 'string' || !(VALID_CATEGORIES as readonly string[]).includes(category)) { errors.category = 'category must be one of: electronics, clothing, food, other'; } const sku = input.sku; if (typeof sku !== 'string' || !SKU_PATTERN.test(sku)) { errors.sku = 'sku must match the pattern ^[A-Z]{2,4}-\\d{4,8}$'; } return { valid: Object.keys(errors).length === 0, errors }; }随后在 POST handler 里接入,并只在通过校验时才继续调用服务层:
router.post('/products', async (req, res) => { const { valid, errors } = validateProductInput(req.body); if (!valid) { return res.status(400).json({ error: 'Validation failed', details: errors }); } try { const product = await createProduct(req.body); res.status(201).json(product); } catch (err) { res.status(500).json({ error: 'Failed to create product' }); } });这样的设计恰好同时满足规则的六与七:校验失败返回“字段 → 原因”的描述性 400;Product接口、GET路由与createProduct服务函数均保持原样,改动只发生在路由文件内(或抽出一个独立校验模块)。
值得注意的实现边界有四处:
req.body运行时可能不是对象(如空请求体、JSON 数组),校验函数内先(body ?? {})兜底取值,避免直接解构抛错;name的“1 个字符下限”应指非空白内容,因此用name.trim().length === 0判空更符合“有意义的名称”这一业务直觉;sku必须用锚定正则做整体匹配,SKU_PATTERN.test()若正则带g标志还会受lastIndex状态污染,实现中不要复用带g的正则实例;- 小数位校验放在“已是合法有限数”之后,否则
NaN * 100等运算结果会干扰判定分支。
若要进一步验证方案正确性,可在路由测试中用 supertest 等工具对六类非法输入(缺 name、超长 description、负数 price、两位以上小数、非法 category、非法 sku 格式)断言400,再对一条合法 payload 断言201。这类断言对应着 ground-truth 中“CRITICAL/MAJOR”分级背后的验收语义。
五、评测口径:ground-truth 如何定义“这次实现算不算合格”
任务文档本身不携带答案,答案在 task-input-validation.json 中。它把校验要求转译成 6 条带严重级别、带关键词的 findings,供评测器做自动匹配:
| Finding ID | 严重级别 | 含义(对应需求条目) |
|---|---|---|
| IMPL-IV-1 | CRITICAL | name 必须按“必填、string、1-200 字符”校验(需求 1) |
| IMPL-IV-2 | CRITICAL | price 必须按“非负、至多 2 位小数”校验,防止-5、19.999(需求 3) |
| IMPL-IV-3 | CRITICAL | SKU 必须匹配^[A-Z]{2,4}-\d{4,8}$(需求 5) |
| IMPL-IV-4 | MAJOR | category 必须在枚举内(需求 4) |
| IMPL-IV-5 | MAJOR | 校验失败返回带描述的 400 而非 500(需求 6) |
| IMPL-IV-6 | MAJOR | 不得修改 Product 接口与既有 GET 路由,校验只做增量(需求 7) |
同时该文件记录了三个元信息:domain: "task"(任务型)、expectedVerdict: "scoped"、isCleanBaseline: false。结合 agents/executor.md 与 deep-executor.md 中 Investigation Protocol 的任务分级约定(Trivial / Scoped(2-5 个文件、边界清晰)/ Complex),可以推断expectedVerdict: "scoped"表示评测期望执行 Agent 把本任务正确归类为“边界清晰的中等改动”——它涉及路由与可能的校验模块、但范围明确。isCleanBaseline: false则说明该任务故意埋有缺陷/缺口,不是用于误报率测试的“干净基线”。
一个值得指出的细节是:需求 2(description 上限 2000)属于必做项,但 ground-truth 并未为它单列 finding。这提示我们 ground-truth 是一种有取舍的验收采样,它聚焦于最容易做错、代价最高的校验点(必填字段的类型/长度/格式、错误语义、范围红线),而不是穷举每条需求。
六、底层原理:从 Agent 输出到量化分数,链路如何工作
executor 基准复用了 harsh-critic 评分体系。运行一条消息后,得分链路在 shared/parser.ts、shared/scorer.ts 与 harsh-critic/scoring/scorer.ts 中分四步完成:
- 解析(Parse):parseGenericOutput 把 Agent 的 Markdown 输出切成小节,识别
Critical/Major/Minor级别的区块并把列表项提取为 findings;如果整段输出没有严重级别分区,则回退到全文本提取列表项。证据标记hasEvidence由正则判定——命中反引号代码片段或文件:行号形态即视为“带证据”。 - 匹配(Match):每一条 Agent finding 与该 fixture 的 ground truth findings 做关键词重叠匹配。
MIN_KEYWORD_MATCHES = 2是最低命中门槛,而 scorer.ts 实际采用比例策略:所需命中数 =max(2, ceil(关键词数 × 0.4))。严重级别判定放宽到“相邻也算对”(ALLOW_ADJACENT_SEVERITY = true,即 CRITICAL 与 MAJOR 互认),降低措辞带来的抖动。 - 打分(Score):产出 truePositiveRate(检出率)、falseNegativeRate(漏检率)、falsePositiveRate(误报率)、severityAccuracy、missingCoverage、perspectiveCoverage、evidenceRate 以及 pre-commitment / gap-analysis / multi-perspective 三个过程合规布尔值,并按 SCORING_WEIGHTS(TPR 0.25、FNR 0.15、FPR 0.10、missing 0.20、perspective 0.10、evidence 0.10、process 0.10)合成
compositeScore。 - 聚合(Aggregate):同一 Agent 跑完多个 fixture 后取均值;布尔指标用多数票。
task领域的 fixture 在进入评分前还会被 scorer.ts 的CANONICAL_DOMAIN_PROJECTION投影为 canonical 的analysis领域,从而无缝复用 harsh-critic 的 canonical scorer——这解释了为什么一份“改代码的任务”也能用“评审式”的关键词打分框架来度量。
七、亲手复现:运行这条 executor 基准
仓库提供了完整的基准运行器 run-benchmark.ts,用npx tsx即可执行。其 CLI 参数由 shared/runner.ts 的parseCliArgs支持:
--agent <name>/--agents <a,b>:只跑某个 Agent 变体,或指定多个;--fixture <id>:只跑单个 fixture,例如task-input-validation(id 即去掉.md扩展名的文件名);--output-dir <path>:报告输出目录,默认benchmarks/executor/results;--model <model>:覆盖模型,默认claude-opus-4-6;--dry-run:不调用任何 API,仅验证流水线可通。
只针对本文档跑一次新旧对比的命令形如:
npx tsx benchmarks/executor/run-benchmark.ts --fixture task-input-validation运行器默认把新合并的executor与旧的deep-executor两个变体在同一个 fixture 上各跑一遍(从 run-benchmark.ts 头部注释可见这一对比意图)。Agent 系统提示的加载顺序在 runner.ts 中定义:优先取仓库根 agents/executor.md,取不到再回退到 prompts/deep-executor.md。API 调用经由callClaude完成,读取ANTHROPIC_API_KEY或ANTHROPIC_AUTH_TOKEN环境变量,必要时也支持ANTHROPIC_BASE_URL代理;对529/overloaded/rate/500等可重试错误做最多 5 次指数退避重试,max_tokens固定 8192。每条结果会记录 API 延迟、harness 开销与输入/输出 token 数,最终在results目录写入带时间戳的results_*.json、report_*.md以及固定名的results.json、report.md。
--dry-run适合无密钥场景下的自检:它会加载 fixtures、构造 user message、解析 CLI 参数,但在真正发起 API 调用前停住并打印 “Pipeline validated”。
八、这份 fixture 折射出的执行 Agent 行为规范
把任务文档、基准运行器和两份 Agent 提示放在一起,还能读出 oh-my-claudecode 对“合格执行 Agent”的行为期望,这恰恰也是本任务的隐性考点:
- 先分类再动手:deep-executor.md 与 agents/executor.md 的 Investigation Protocol 都要求第一步把任务归入 Trivial / Scoped / Complex,对应本任务的
expectedVerdict: "scoped"; - 保持最小可行变更:两条提示反复强调 “Prefer the smallest viable change” 与 “Do not introduce new abstractions for single-use logic”,对应需求 7 的“不碰 Product 接口与 GET 路由”红线;
- 先探索、匹配既有模式再实现:要求用 Glob/Grep/Read 摸清路由命名、错误处理与返回格式等既有惯例,避免自造风格迥异的新模式——对应本任务中“仿照既有 Express handler 风格接入校验”的期望;
- 以证据收尾:Success Criteria 要求给出“fresh 的 build/test/lsp_diagnostics 输出”作为完成依据,呼应评测中
evidenceRate(CRITICAL/MAJOR finding 是否带文件引用或代码证据)这一指标; - 警惕过程性失分:评分中 0.10 权重的过程合规项会检测 Agent 输出是否包含 pre-commitment(先承诺再调查)、gap analysis(还有什么是缺失的)与多视角分析,因此即便方案正确,输出组织方式也会影响综合分。
若把该任务交给执行 Agent 后出现漏检,可以对照 ground-truth 的 six findings 定位是落在哪一类:漏了 name/price/sku(CRITICAL 三类)通常意味着没有做字段级逐条实现;漏了 400 语义或范围红线(MAJOR 三类)则多半是“只补了逻辑、没纠正错误语义”或“顺手改了不该改的代码”——这恰是本文第一节路由代码中catch → 500与Partial<Product>兜底两个坏味道希望引导 Agent 识别的地方。
综上,task-input-validation.md 既是一份可直接练习的“Express 输入校验实战工单”,也是理解 oh-my-claudecode 执行 Agent 评测体系的上佳切片:从七条需求到参考实现,从 ground-truth 关键词打分到run-benchmark.ts的端到端流水线,它把“代码正确性”量化为可复现的 composite score。读者既可以把这份任务当作 TypeScript/Express 校验编码的演练题,也可以沿着 ground-truth、shared/runner.ts 与 harsh-critic/scoring/scorer.ts 继续深入,理解一套面向 AI Agent 的自动化代码评测是如何设计与落地的。
【免费下载链接】oh-my-claudecodeTeams-first Multi-agent orchestration for Claude Code项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-claudecode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考