恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
OmO(oh-my-openagent)Senpi task 工具:GPT 风格可选字段填充的边界归一化与 QA 证据链
首页
资讯中心
/
OmO(oh-my-openagent)Senpi task 工具:GPT 风格可选字段填充的边界归一化与 QA 证据链
OmO(oh-my-openagent)Senpi task 工具:GPT 风格可选字段填充的边界归一化与 QA 证据链
发布时间:2026/9/18 17:02:04
OmOoh-my-openagentSenpi task 工具GPT 风格可选字段填充的边界归一化与 QA 证据链【免费下载链接】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本文基于仓库中.omo/evidence/20260730-senpi-task-padding/证据集及其 README解析 OmO 项目中 Senpitask工具在模型GPT 风格 provider传入“填充式”可选字段时如何在参数边界做语义归一化避免合法的派生/批量调用被误判为 prompt/tasks 互斥XOR错误。读完后你能掌握该归一化的字段级规则、底层实现链路prepareArguments→ 语义校验 → 执行、配套的 RED/GREEN 证据与真实 e2e 验证方式。1. 背景问题provider 填充的可选字段为什么是故障源Senpi 的task工具参数 schema 中prompt单次派生与tasks批量派生1-16 项是互斥的两种输入形态二者同时有“真实内容”时必须报错。但部分模型GPT 风格 serializer在生成工具调用时会把 schema 里所有可选字段都填上值空字符串、空数组[]、甚至一个tasks: [{ prompt: unused, ... }]的合成占位项。如果这些填充值直接进入校验链路就会出现两类误判顶层被填了prompt: 导致一个本来有意义的批量调用被分类为错误单次派生调用被附带一个占位tasks项触发prompt_and_tasksXOR 错误而不是正常 spawn。证据集 READMEREADME.md明确声明了本 QA 覆盖的四个面单次派生single spawn、批量派生batch spawn、真实的歧义输入genuine ambiguous input即必须继续报错的场景以及模型消费的 task prompt 指导文本。2. 归一化实现normalizeTaskToolArguments修复落在工具参数边界上的一个归一化函数中源码见 argument-normalization.tsconst PROVIDER_PADDING_PROMPTS new Set([unused, placeholder, not used, n/a])PROVIDER_PADDING_PROMPTS识别 provider 常用的占位 prompt 文本。批量/单次判定中若tasks里的每一项 prompt 都是这类占位词或为空则整个tasks字段被丢弃调用降级为单次派生nonBlankText/identifier/stringList把空字符串、纯空白字符串、空数组统一归一为“字段不存在”undefined而非空值。例如category: 、model: 、load_skills: []全部从最终参数对象中移除taskItem逐项过滤tasks缺少非空prompt的项直接丢弃因此tasks: [{ prompt: unused }]这类合成占位项不会存活tools字段有专门注释说明其特殊性父级 kernel-tool 名单必须穿透归一化否则会导致子任务丢失调用方显式要求授予的工具见源码 L96-L98 注释task_summary走clampTaskSummarytask-summary.ts超过TASK_SUMMARY_MAX_LENGTH 80的摘要被强制截断为 80 字符并以...结尾空白摘要被丢弃——归一化在 schema 校验之前运行因此超限值被“钳制”而不是让 spawn 失败。关键的降级判定逻辑源码 L84-L88const tasksAreSinglePadding prompt ! undefined normalizedTasks ! undefined (normalizedTasks.length 0 || normalizedTasks.every(isProviderPaddingTask)) const tasks tasksAreSinglePadding ? undefined : normalizedTasks即只有当顶层 prompt 非空、且tasks要么已被过滤为空、要么每一项都是占位词时才认为这是“provider 填充”把tasks置为undefined真实的内容则原样保留交给后续语义校验。3. 边界接入点prepareArguments在 schema 校验之前归一化函数被直接注册为 Senpi 工具的prepareArguments钩子见 tool.tsexport function createTaskTool(deps: TaskToolDeps): ToolDefinitiontypeof TaskToolParams, TaskToolDetails { const execute buildTaskExecute(deps) return { name: TASK_TOOL_NAME, ... parameters: TaskToolParams, prepareArguments: normalizeTaskToolArguments, execute: (toolCallId, params, signal, onUpdate, ctx) execute(toolCallId, params, signal, onUpdate, ctx), ... } }调用顺序是provider 原始参数 →prepareArguments本归一化→ TypeBox schema 校验 → 语义 XOR 校验 →execute。RED 证据中的结论与此一致“没有prepareArguments捕获到的 provider 形态 prompt/tasks 负载无法在 TypeBox 校验和语义 XOR 校验之前被归一化”red-single.txt。4. 归一化之后语义校验仍然守住“真实歧义”归一化只清除填充值不放松真实歧义的约束。语义层在 validation.ts 中validateBatchShapeL115-L128prompt与tasks同时存在 →prompt_and_tasks“Provide EITHER prompt OR tasks, not both.”两者皆无 →no_prompt_or_taskstasks为空数组 →empty_tasksvalidateTaskTargetL97-L113category与subagent_type必须二选一both_targets/no_target且category与model不可同时出现category_with_model因为 category 路由的模型必须来自 omo.json 的categories.name.modelsresolveRunInBackgroundL133-L151run_in_background是批量级语义顶层与各项取值必须一致不一致报run_in_background_conflict。因此“顶层有真实 prompt、tasks里也有真实 prompt”的输入仍然得到kind: error、error.code prompt_and_tasks——这正是归一化测试中专门钉住的第三类场景。5. 配套测试三个场景钉住三类行为测试文件 argument-normalization.test.ts 通过createTaskTool的真实工具定义调用prepareArguments覆盖GPT 填充的单次派生输入含category: 、model: 、load_skills: []与tasks: [{ prompt: unused, ... }]期望归一化后只剩{ prompt, description, subagent_type, run_in_background, name }五个字段——占位tasks被丢弃空值字段被清除GPT 填充的批量顶层prompt: 、name: 等空值 两个真实tasks项期望resolveSpawnItems返回kind: ok、2 个条目且顶层load_skills: [audit]正确继承到各项目标分别为subagent_type与category两种形态真实歧义真实prompt 真实tasks[0].prompt期望resolveSpawnItems返回kind: error、code: prompt_and_tasks。此外还钉住task_summary的 80 字符强制截断与项级run_in_background镜像值在归一化后保留的行为。6. RED/GREEN 证据链证据集按“隔离分支 先红后绿”的方式记录基线来自origin/dev的915c5e4a9f922099bd15b96fdd4c0b128c70e034、干净状态、Bun 1.4.0baseline.txt。RED 1单次场景运行bun test packages/senpi-task/src/tools/task/argument-normalization.test.ts失败原因是 task 工具尚无prepareArguments兼容实现断言expect(typeof prepareArguments).toBe(function)收到undefined。证据文件特别说明了“为什么这是正确的 RED”失败点就在修复必须新增的生产边界上而非语法、导入、依赖或 fixture 错误red-single.txt。RED 2批量场景单场景转绿后批量测试仍失败——“被 serializer 填充的顶层prompt: 被保留resolveSpawnItems仍把有意义的批量分类为错误”。此时 2 pass / 1 fail把下一处生产改动隔离到“批量调用的语义空值清除”red-batch.txt。GREEN单次场景同一条命令 1 pass / 0 fail捕获的 provider 形态单次调用被归一化为一个干净的单次派生参数对象green-single.txt。7. Prompt 面工具描述与指导的瘦身README 覆盖的第四个方面是“模型消费的 task prompt 指导文本”。验证记录显示green-prompt.txt工具描述是输入形态与目标规则的单一所有者prompt 指导保留继续/控制语义但不重复 category/subagent 选择逻辑隐藏指导保留task_output、task_send、task_cancel、team-mail 语义机器消费后的 prompt 尺寸descriptionChars: 2036、snippetChars: 80、guidelineCount: 3、guidelineChars: 287、usageChars: 583合计 2986 字符相比修复前 4120 字符减少 1134 字符27.5%。记录中明确说明“尺寸对比是调优证据不是测试 oracleoracle 是职责分离与保留的运行时行为”。prompt 面的来源代码为 description.tsTASK_PROMPT_SNIPPET“Spawn one child or fan out a batch; use task_send to continue an existing child.”、TASK_PROMPT_GUIDELINES包含“永远不要与 category 同传 model”“每次派生都传 ≤80 字符的 task_summary”等规则以及buildTaskToolDescription动态注入 omo.json 的 category 列表与已加载 agent 列表其中已包含 “Blank provider padding is normalized automatically; do not add filler values.” 这一面向模型的提示。8. 真实 Senpi e2eprovider 形态调用真的 spawn 出子任务手动 QA 驱动真实的 Senpi task 表面qa-single-task.txtHOMEthrowaway-home \ SENPI_BIN$(command -v senpi) \ TASK_E2E_OUT_DIR.omo/evidence/20260730-senpi-task-padding/live-task-e2e-attempt2 \ node packages/omo-senpi/scripts/qa/task-e2e.mjs具体输入真实 Senpi mock-provider 工具调用携带一个有含义的顶层 prompt、空白可选字符串、空技能数组以及一个合成的tasks:[{prompt:unused,...}]项目标为内置只读exploreagent 加本地 mock 模型。判定条件binary observablePASS 当且仅当后台子任务 id 被创建、子任务完成并可被 revive/peek且输出中不出现 “Provide EITHER prompt OR tasks” 文本。观察结果result: PASStask idst_019fb190spawn_background/unconditional_wake/followup_revive/task_output_peek/jsonl_sequence/extension_suppression全部 PASSXOR error present: false并记录了完整的生命周期签名running/resident → completed/resident → revived → ... → disposed → destroyed。清洗后的原始产物位于 live-task-e2e-attempt2/verdict.json 及同目录日志。e2e 驱动脚本为 task-e2e.mjs。9. 验证门与回归边界verification.txt 记录了完整的验证面聚焦测试bun test packages/senpi-task/src/tools/task127 pass / 0 failpackages/omo-senpi/src/components/task155 pass / 0 fail全仓库bun run typecheck首轮因归一化器中两处 readonly 数组赋值失败修复方式是从stringList()与taskItems()返回可变 schema 数组后重跑通过变更面门bun test packages/senpi-task1039 tests / 0 failures / 3242 expectationsbun run test:senpi插件构建 489 tests通过架构自审变更文件均 250 LOC、无as any/ts-ignore/空 catch、git diff --check干净且未改动 task manager、并发、持久化或生命周期行为对最新 dev 的 post-rebase 验证重新生成冲突的packages/omo-senpi/plugin/extensions/omo.js生成产物而非手工合并tsgo 与test:senpi493 tests通过根套件基线对照task 分支12508 pass / 8 skip / 18 fail而在未改动的origin/dev干净 worktree 上重放为12495 pass / 8 skip / 26 fail——失败集中于 Codex 安装/telemetry 测试族证明根套件失败是基线既有的非本修复引入。10. 证据卫生脱敏原则README 的 “What was omitted” 一节给出了证据集的脱敏约束不存储凭据、原始环境转储、鉴权头或私有模型流量运行时捕获只保留调用形态、task id、终态状态和决定性的错误/成功文本README.md。这与 QA 产物中st_019fb190之类的 task id 加生命周期签名、而不保存完整模型流量的做法一致。小结这套 QA 的核心可复用经验是模型 provider 的工具参数不可全信serializer 会填充分支。OmO 的做法是把“provider 填充归一化”放在prepareArguments边界schema 校验之前用空值清除 占位 prompt 词表unused/placeholder/not used/n/a把填充降级为单次派生同时保留真实歧义的硬错误prompt_and_tasks等错误码再配合 RED→GREEN→live e2e→全仓门的分层证据链保证“provider 形态的调用能 spawn 出子任务而不是返回 XOR 错误”。关键文件索引归一化实现argument-normalization.ts、tool.ts、validation.ts、task-summary.ts测试argument-normalization.test.ts、description.tse2e 驱动task-e2e.mjs证据集.omo/evidence/20260730-senpi-task-padding/【免费下载链接】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),仅供参考