
omo-senpi 遥测并行度指标中的 eval 三桶分类器分桶契约、工具名匹配与 TDD 验证实践【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent导读本文围绕 oh-my-openagent 仓库中omo-senpi包的遥测管线telemetry-parallel-latency功能展开深度讲解其核心模块之一——eval 三桶分类器eval-classifier.ts它把每一组并发工具调用wave精确划分为non_eval/eval_only/mixed三个互斥桶并保证只有non_eval桶参与并行度与节省时间time-savings聚合。文章完整继承任务文档 task-3.md 中的契约、RED/GREEN 测试捕获、变异证明与对抗性用例并结合 eval-classifier.ts 与 eval-classifier.test.ts 源码逐行拆解实现。读完你既能复现这套 TDD 变异测试流程也能理解为什么 eval 调用必须隔离成独立桶、而不是被过滤掉再合并这一度量诚实性问题的完整答案。背景并行度遥测管线中分类器的位置telemetry-parallel-latency是一条完整的遥测子管线它从工具执行事件中度量并行工具调用节省了多少墙钟时间。整条管线由多个职责单一的新增模块组成见 f1.md 的逐 Todo 审计wave-assemblerwave-assembler.ts——把带起止时间的工具调用按时间区间重叠关系装配成并发波wave并受MAX_TRACKED_CALLS 2000上限保护savings-mathsavings-math.ts——基于波跨度Σdᵢ − span计算建模节省链式波不会被误判为并行eval-classifier本文主角——在聚合之前先把每个波按工具名归类隔离 eval 类调用omo-native-parallelomo-native-parallel.ts——事件订阅注册表把tool_execution_start/end配对并缓冲omo-native-parallel-summaryomo-native-parallel-summary.ts——在session_shutdown时汇总并发射parallelism_summary事件。其中eval-classifier 是整个指标可信度的守门员并行度聚合是否被 eval 调用污染完全取决于它如何分类。任务文档task-3.md记录的就是这个模块从契约、TDD、变异测试到对抗性验证的完整研发证据。分桶契约一个 wave 恰好落进一个桶契约非常简单且严格每个 wave 必须被分到恰好一个桶由 eval-classifier.ts 中的WaveBucket类型定义export type WaveBucket eval_only | non_eval | mixed三种桶的含义桶判定条件例子non_eval波内没有任何调用是 eval/代码执行类工具[bash, read, grep]eval_only波内每一个调用都是 eval[eval]、[eval, eval]mixed两者同时存在[bash, eval]分桶的实现位于classifyWaveBucketeval-classifier.tsexport function classifyWaveBucket(wave: ClassifiableWave): WaveBucket { let evalCalls 0 let nonEvalCalls 0 for (const toolName of wave.toolNames) { if (isEvalToolName(toolName)) evalCalls 1 else nonEvalCalls 1 } if (evalCalls 0) return non_eval return nonEvalCalls 0 ? eval_only : mixed }逻辑是两次遍历计数没有任何 eval 调用 →non_eval有 eval 但全是 eval →eval_only其余 →mixed。为什么 mixed 不能拆开再折叠这是本模块最重要的设计决策也是模块头注释eval-classifier.ts直接写明的Eval waves are isolated into their own buckets instead of being filtered out. Dropping the eval call from a mixed wave and recomputing the remainder shrinks the wave span and inflates apparent savings (measured: 1.20s reported as 0.70s), somixedwaves are recorded separately and never folded intonon_eval.朴素做法是把 mixed 波里的 eval 调用删掉把剩下的普通调用重新折叠回non_eval统计。但删除调用会缩小波的跨度span和最大并发数从而凭空夸大并行节省。文档记录了实测失真一个真实节省 1.20s 的波经过朴素过滤后被报告为 0.70s。因此契约明确禁止这条路径——mixed 波既不剥离 eval 调用也不折叠回non_eval而是单独计数。这条禁止项在实现层面体现为summarizeWaveBuckets中eval_only与mixed分支在进入non_eval累加块之前就continueeval-classifier.ts代码里不存在先移除再重算的路径F1 审计的 MUST-NOT 项。eval_only 的独立核算eval_only波被完全排除在并行度指标之外——它们只在各自独立的字段里报告数量与总时长不进入任何并行度聚合。见WaveBucketSummary类型eval-classifier.tsexport type WaveBucketSummary { readonly nonEval: NonEvalWaveCounters readonly evalOnlyWaves: number readonly evalOnlyDurationMs: number readonly mixedWaves: number readonly evalOuterJoinedCalls: number readonly mixedNonEvalJoinedCalls: number }其中evalOnlyWaves数量、evalOnlyDurationMs总时长注意durationOf用Number.isFinite守护见 eval-classifier.ts非法spanMs被吸收为 0 而不抛错是 eval 域自身的核算字段evalOuterJoinedCalls与mixedNonEvalJoinedCalls则分别记录 eval 调用与 mixed 波内普通调用对外层调用总数的贡献但同样不混入nonEval。工具名匹配复用既有归一化/后缀匹配eval 工具检测复用omo-native-tools.ts中现成的归一化与后缀匹配器。任务文档指明其位置为omo-native-tools.ts:182-190实际源码位于 omo-native-tools.tsfunction matchesToolName(toolName: string, expected: string): boolean { const normalized normalizeToolName(toolName) const suffix normalizeToolName(expected) return normalized suffix || normalized.endsWith(_${suffix}) || normalized.endsWith(:${suffix}) || normalized.endsWith(/${suffix}) } function normalizeToolName(toolName: string): string { return toolName.trim().toLowerCase().replaceAll(-, _) }匹配流程是小写化 → 去首尾空白 → 把-换成_然后做精确相等 或 以_/://为前缀分隔符的后缀匹配。eval-classifier.ts中的同名实现eval-classifier.ts逻辑与此完全一致。F1 审计特别指出分类器复制了这段匹配器而非 import 它两份实现经 diff 确认逻辑等价同一归一化、同四种匹配形式匹配契约被测试用例 (d) 证明因而作为 DRY 关注点移交 F2 而非验收失败见 f1.md。匹配目标列表eval-classifier.tsconst EVAL_TOOL_NAMES [eval, codemode, code_mode] as const由此下面这些拼写都会被识别为 evaleval、codemode、mcp:eval、code-mode、EVAL、eval带空格、server/eval、tool_eval、code_mode测试用例 (d) 逐一断言见 eval-classifier.test.ts。code_mode 与 code-mode 的归一化关系code_mode出现在列表里不是范围扩张F1 明确判定normalizeToolName会把code-mode归一化为code_mode因此它俩是同一个标识符家族——如果列表里只有codemodecode-mode这种拼写就匹配不上。F1 审计原文code_modeinEVAL_TOOL_NAMESis not scope expansion:normalizeToolNamemapscode-mode→code_mode, so it is the same identifier family the plan named, not a new tool.f1.md刻意排除 lnln历史遗留的旧别名被故意排除在列表之外——它存在误报风险。测试用例 (d) 的负例明确断言evaluate_foo、evaluate、ln、bash、read、codemodel、eval_helper、空串、纯空白都不命中eval-classifier.test.ts。特别是evaluate_foo不命中正是因为匹配器是后缀分隔符匹配而非朴素的includes子串匹配——变异探针 N4 把后缀匹配换成朴素includes后用例 (d) 立即失败见下文的变异测试。只读工具名不读源码/参数/结果整个模块的输入面只有toolNames: readonly string[]和spanMsClassifiableWaveeval-classifier.ts——不读 cell 源码、不读参数、不读结果从类型层面就杜绝了隐私泄漏F1 的 MUST-NOT 项与对抗性验证中的 privacy 判定。源码级实现解析聚合逻辑summarizeWaveBucketseval-classifier.ts是主入口对每个波先分桶然后按桶分流——eval_only累加evalOnlyWaves、evalOnlyDurationMs、evalOuterJoinedCallscontinuemixed累加mixedWaves遍历工具名分别累加evalOuterJoinedCalls与mixedNonEvalJoinedCallscontinuenon_eval进入唯一的并行度累加路径——wavesTotal、joinedCalls、wavesMultisize 1 时与直方图计数。直方图编码波大小直方图是无标签的位置编码字符串桶上限定义eval-classifier.tsconst WAVE_SIZE_BUCKET_MAXIMA [1, 2, 3, 4, 8, 16, 32] as const const HISTOGRAM_BUCKET_COUNT WAVE_SIZE_BUCKET_MAXIMA.length 1即 8 个桶size ≤ 1、≤ 2、≤ 3、≤ 4、≤ 8、≤ 16、≤ 32 各占一桶超 32 的落入最后一个溢出桶waveSizeBucketIndex对超上限返回HISTOGRAM_BUCKET_COUNT - 1eval-classifier.ts。编码结果如1:1:1:0:0:0:0:0——纯数字以:分隔不含任何标签测试断言not.toContain()eval-classifier.test.ts8 个桶的位置即语义。这个无标签格式是为了在 schema 中作为单个 string 属性传输长度边界见下文直方图溢出边界。无状态纯函数模块只导出纯函数isEvalToolName、classifyWaveBucket、summarizeWaveBuckets没有任何模块级可变状态直方图数组每次调用重新分配new Arraynumber(8).fill(0)eval-classifier.ts。纯同步、无共享可变状态因此并发/交错风险被结构性排除对同一输入连续调用两次结果深相等空输入得到全零直方图测试用例repeated summaries断言eval-classifier.test.ts。TDD 实践RED → GREENRED 原样捕获测试先于生产模块编写。模块文件尚不存在时运行测试得到原样失败捕获文档 task-3.md 记录的 verbatim 输出$ bun test packages/omo-senpi/src/components/telemetry/eval-classifier.test.ts bun test v1.4.0-canary.1 (b58cd4685) # Unhandled error between tests error: Cannot find module ./eval-classifier from .../eval-classifier.test.ts 0 pass 1 fail 1 error Ran 1 test across 1 file. [212.00ms]这是货真价实的第一轮 RED——测试文件先于任何实现字节存在无需 stashF1 的证据完备性审计确认task-3 的 RED 是 genuine first-run见 f1.md。GREEN 验证命令实现完成后两命令验证$ bun test packages/omo-senpi/src/components/telemetry/eval-classifier.test.ts 10 pass 0 fail 48 expect() calls Ran 10 tests across 1 file. [323.00ms] $ bun run --cwd packages/omo-senpi typecheck $ tsgo --noEmit -p tsconfig.json (exit 0, no diagnostics)必测用例清单 (a)-(f)文档要求的强制用例在 eval-classifier.test.ts 中全部落地(a)[bash,read,grep]→non_eval(b)[eval]→eval_only含[eval,eval](c)[bash,eval]→mixed(d)拼写变体eval、codemode、mcp:eval、code-mode、EVAL、eval、server/eval、tool_eval、code_mode命中evaluate_foo、evaluate、ln、bash、read、codemodel、eval_helper、空串、空白不命中(e)mixed 波绝不泄漏进non_eval聚合——含 mixed 波时nonEval.wavesTotal 1、mixedWaves 1eval-classifier.test.ts(f)waves_total/waves_multi/joined_calls/wave_size_histogram四项只聚合non_eval域——用被污染的输入7 个波含 2 个 eval_only、2 个 mixed与纯 non_eval 对照组逐字段比对再钉死绝对值3 / 2 / 6 / 1:1:1:0:0:0:0:0eval-classifier.test.ts。测试既断言与独立构造的对照组相等又断言硬编码绝对值无法通过从被测输出反推期望而自证成功。变异测试守卫非平凡变异的失败捕获为了证明测试不是同义反复tautological作者临时把summarizeWaveBuckets变异为mixed 波落入 non_eval 计数器并把 eval 调用过滤掉正是被禁止的朴素过滤得到 3 条失败verbatim 捕获task-3.mdExpected: 1 / Received: 2 # case (e): mixed 泄漏进 non_eval totals Expected: 3 / Received: 5 # case (f): polluted 与 control 不等 Expected: 1 / Received: 2 # eval_only 核算被污染 7 pass 3 fail变异从变异前副本还原后重跑10 pass / 0 fail。独立复核的 7 个变异探针独立的对抗性复核verify-t1-t3.md对 todo 3 给出confirmed结论并在/tmp草稿副本上打了 7 个变异7/7 全部至少杀死一条断言探针变异结果N1mixed被当作non_eval头号禁止折叠5 fail用例 (c)、(e)、(f)N2mixed 计入 non_eval 聚合且剥离 eval 调用禁止的过滤后重算3 fail(e)、(f)N3eval_only落入 non_eval 聚合2 fail(f)N4后缀匹配换成朴素includes制造evaluate_foo误报1 fail(d)N5从 eval 名单删除code_mode1 fail(d) 的code-mode变体N6直方图带标签编码b01:b12:...3 fail位置编码断言N7wavesMulti对每个波都 1不管大小1 fail(f)复核还确认N2 独立复现了与原文档相同的 3 条失败及相同的 expected/received 数值evaluate_foo不命中、直方图 ≤39 字符且无标签等全部证据数字均可复现见 verify-t1-t3.md 的 Evidence-file reproduction 表。手工 QA 与二元可观察量一次性脚本/tmp/task3-qa.ts跑完即删对一组真实混合波做了端到端分类并打印桶汇总与 non_eval 计数器原样输出per-wave classification non_eval span 820ms [bash, read, grep] non_eval span 310ms [read, read] non_eval span 120ms [edit] eval_only span 4400ms [eval] eval_only span 2100ms [mcp:eval, codemode] mixed span 1200ms [bash, eval] mixed span 1900ms [read, grep, code-mode] non_eval span 640ms [evaluate_foo, bash] bucket summary eval_only waves : 2 eval_only duration ms: 6500 mixed waves : 2 non_eval-only counters (parallelism aggregates) non_eval_waves_total : 4 non_eval_waves_multi : 3 non_eval_joined_calls : 8 non_eval_wave_size_histogram: 1:2:1:0:0:0:0:0 control: counters computed from non_eval waves alone are identical - PASS二元可观察量8 个波进 → 4 个non_eval 2 个eval_only 2 个mixed出non_eval_waves_total 4、non_eval_joined_calls 8只统计四个 non_eval 波3212 个调用2 个 eval_only 与 2 个 mixed 波被排除在所有并行度计数器之外、同时以各自的数量与时长单独报告。对照组从纯 non_eval 波重算计数器并断言逐字节一致 → PASS。注意[evaluate_foo, bash]保持non_eval再次确认无 eval 误报。对抗性类别盘点畸形输入空波、空/纯空白工具名、unicode코드、全角同形字UFF25/FF41/FF4C/FF56、混合大小写EVAL、带填充空格eval——全部不抛错、不错分空波分类为non_eval贡献 0 个 joined calls且不进入任何直方图桶全角不会被小写化为 ASCIIeval正确保持为non_eval测试断言 eval-classifier.test.ts。F2 代码质量评审专门确认这是故意的同形字误报夹具而非 emojif2.mdspanMs: NaN被Number.isFinite守护吸收evalOnlyDurationMs0直方图完好。一个被记录的边界缺口summarizeWaveBuckets([{toolNames: null, spanMs: NaN}])会抛TypeError。但该路径只有违反模块自身 TypeScript 契约才能触达其唯一预期的生产者是 todo 1 的类型化PairedToolCall[]计划与验收标准均不要求在这一内部接缝防御记为 note 而非阻塞verify-t1-t3.md。误导性成功输出变异证明已覆盖把mixed折叠进non_eval会让用例 (e) 和 (f) 失败因此这些守卫不可能同义反复地通过。同时 (f) 的对照组由独立字面量数组构造而非从被污染结果反推杜绝了自我验证。陈旧状态与并发模块只导出纯函数直方图每次调用分配——对相同输入汇总两次深相等空输入得到全零直方图。纯同步、无共享可变状态并发/交错风险被结构性排除。隐私API 面只有toolNames与spanMs穿过不接收、不存储任何 args、results 或 cell 源码。直方图溢出边界8 个位置桶配合 plan 的MAX_TRACKED_CALLS 2000上界每个桶最多 4 位数字8×4 7 个冒号 39 字符远低于 64 字符截断上限且编码无标签。F1 补充验证了最坏情况8 × 2000用:连接为 39 字符并断言toHaveLength(bucketCount * 4 7)且toBeLessThanOrEqual(64)见 f1.md 中 Todo 5 的行。与 parallelism_summary 的集成分类器不是孤岛它在 omo-native-parallel-summary.ts 的session_shutdown处理器中被调用——只有被分类为non_eval的波进入nonEvalWaves并参与节省求和eval_only/mixed各自独立核算F1 审计 Todo 6 (e)。发射出的parallelism_summary事件属性注册在 product-identity.ts 中所有派生自工具调用的计数一律带non_eval_前缀non_eval_waves_total、non_eval_waves_multi、non_eval_joined_calls、non_eval_wave_size_histogram、non_eval_saved_round_trips并带诚实命名modeled_wallclock_saved_ms、upper_bound_saved_ms、measured_turn_duration_ms_total加上eval_only_waves、eval_only_duration_ms、mixed_waves等 eval 域字段以及dropped_calls、incomplete_calls、clock_anomalies质量计数器。schema 文档同步再生成于 senpi-telemetry.md并由schema-doc.test.ts字节级比对把关。命令速查在omo-senpi包所在工作区中bun test packages/omo-senpi/src/components/telemetry/eval-classifier.test.ts # 分类器单测10 pass / 0 fail bun test packages/omo-senpi/src/components/telemetry/ # 全遥测套件基线 136 pass / 0 fail bun run --cwd packages/omo-senpi typecheck # tsgo --noEmit -p tsconfig.jsonexit 0任务工作流TDD为先写测试 → 跑 RED模块缺失报错→ 实现 → 跑 GREEN → 变异验证守卫非平凡 → 手工 QA 脚本验证二元可观察量 → 清理临时文件本任务中/tmp/task3-qa.ts、/tmp/task3-red.txt、/tmp/task3-mutation.txt、/tmp/task3-qa-out.txt、/tmp/eval-classifier.orig.ts均已在收尾时删除不留任何工作区外残留。小结eval-classifier.ts用 91 行纯函数F2 度量见 f2.md解决了遥测度量中的一个真实可信度问题eval 类调用必须与普通工具调用隔离核算否则删掉再折叠会系统性夸大并行节省。它通过三桶契约、复用既有归一化/后缀匹配、ln等误报源的刻意排除以及只读工具名的隐私边界把聚合域的纯净性钉死在类型与测试两个层面10 条测试 7 个变异探针全部杀死断言独立复核给出confirmed结论。对想在自己的遥测或可观测性管线中做域隔离聚合的开发者这是一份可完整复现的参考实现。【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考