ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

oh-my-openagent 团队创建工具的参数冲突诊断:从 Kimi 重试死循环到 inline_spec 权威优先级设计

2026/9/19 6:30:36 拓冰建站 浏览量
oh-my-openagent 团队创建工具的参数冲突诊断:从 Kimi 重试死循环到 inline_spec 权威优先级设计 oh-my-openagent 团队创建工具的参数冲突诊断从 Kimi 重试死循环到 inline_spec 权威优先级设计【免费下载链接】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/20260804-team-create-recovery/diagnosis.md 为骨架完整还原 oh-my-openagent 中team_create工具在特定模型路径Kimi 系列上连续触发invalid_arguments的故障现场、根因分析、修复决策以及当前源码中对应的优先级实现与测试契约。读完本文你将掌握双可选参数工具 schema 的prose-only XOR 约束为何会在真实模型上失效、根级oneOf/anyOf为何在兼容归一化路径下不可靠以及以更丰富参数为权威、避免模型驱动重试循环这一可复用的容错设计模式。恢复会话诊断一次被完整留档的故障复盘诊断文档位于 .omo/evidence/20260804-team-create-recovery/diagnosis.md属于仓库内.omo/evidence/下的会话取证材料记录了2026-08-04 前后team_create工具在两段独立会话中反复失败的完整证据链包括会话 ID、转录路径、调用序列、根因推导、修复决策与历史提交。它没有包含任何原始凭证、Provider 密钥或认证材料是一份纯技术性的可追溯复盘。主会话Primary session的故障现场Session ID019fcb34-df92-78b4-af23-747951793586Transcript诊断文档记录的本地路径/Users/yeongyu/.pi/agent/sessions/--Users-yeongyu-sionicai-kimiblog-kimik3ultrafast--/2026-08-04T05-17-47-794Z_019fcb34-df92-78b4-af23-747951793586.jsonlProvider / model / APIapitopia/kimi-k3-ultrafast-unlocked/openai-completions恢复出的工具调用序列如下Tool callArgument shapeResultteam_create:23team_nameinline_specinvalid_argumentsteam_create:24team_nameonlynamed spec not foundteam_create:25team_nameinline_specinvalid_argumentsteam_create:26team_nameinline_specinvalid_argumentsteam_create:27team_nameinline_specinvalid_argumentsteam_create:28team_nameinline_specinvalid_argumentsteam_create:29team_nameinline_specinvalid_argumentsteam_create:30team_nameinline_specinvalid_arguments这段序列有两个值得注意的细节推理与输出脱节在:25到:30的调用之前助手assistant的推理过程明确表示将移除team_name但实际发出的工具参数仍然同时保留了team_name和inline_spec两个字段。也就是说模型知道规则、甚至复述了规则却在生成工具参数时一再违反规则。isError: false每一次无效结果都以isError: false返回。这意味着错误不是以硬失败的形式呈现给模型的而是以工具正常返回、内部携带invalid_arguments结果的形式呈现的——这在一定程度上削弱了模型对问题严重性的感知也是它敢于反复重试同一参数形状的诱因之一。独立复现Independent recurrenceSession ID019fc0fb-d81b-7d40-9e58-c1ae3130f858Transcript/Users/yeongyu/.pi/agent/sessions/--Users-yeongyu-Documents--/2026-08-02T05-39-18-171Z_019fc0fb-d81b-7d40-9e58-c1ae3130f858.jsonl同一模型/Provider 路径apitopia/kimi-k3-ultrafast-unlocked。双字段调用在team_create:39、:46、:47、:49再次出现。两次会话、同一模型路径、同样的双字段症状说明这不是单次偶然的采样抖动而是该模型路径在给定工具 schema 下具有系统性的参数生成倾向必须从工具设计层面解决而不是指望模型自我纠错。根因prose-only 的 XOR 约束诊断文档将根因归纳为四条层层递进schema 层面没有真正的互斥约束。TeamCreateParams暴露了两个可选的兄弟字段team_name与inline_spec二选一XOR的规则只存在于两处字段的 prose 描述文本以及运行时校验逻辑。schema 本身允许两个字段同时出现。模型反复违反 prose 规则。Kimi 即使在推理中正确复述了规则输出时仍同时携带两个字段——这正是把约束写在自然语言描述里的脆弱性描述可以被理解却无法约束输出分布。根级oneOf/anyOf方案不可靠。诊断文档明确指出Senpi 的 Moonshot 兼容性归一化会扁平化根对象联合root object unions且只保留所有分支共有的 requirements因此即便为参数加一个根级oneOf/anyOf表达互斥也会在兼容归一化后失去互斥语义该归一化实现在诊断文档所述的兄弟 Senpi 仓库packages/ai/src/utils/tool-schema-compat.ts中服务于openai-completions这一受影响 Provider 路径。把结果标记为 error 治标不治本。故障中模型已经读到了文本错误invalid_arguments及其原因却依然重复同样的形状——说明错误信息本身不是瓶颈继续在错误呈现方式上做文章无法解决首次失败只会让会话在错误与重试之间多消耗几轮。综合来看问题本质是一个由描述文本而非 schema 结构承载的互斥约束遇到一个对该描述不够敏感、但输出足够固执的模型路径就必然演变成一次性的重复失败循环。修复决策让 inline_spec 成为权威基于上述根因诊断文档给出的修复决策非常干脆只要inline_spec存在就把它视为权威authoritative仅当没有提供 inline spec 时才使用team_name。更丰富的内联负载可以在第一次过度指定的调用上就直接执行而不是进入模型驱动的重试循环。这一决策的工程含义是接受模型可能同时给两个字段这一事实把工具的输入面做得宽容用确定性的优先级规则替代期望模型遵守互斥的不可靠假设把失败概率从每次调用都可能失败降到几乎零从而切断重试循环节省 token 与延迟。源码验证lifecycle.ts 中的优先级实现修复后的行为可以在 packages/senpi-task/src/tools/team/lifecycle.ts 中逐行确认。参数 schema描述文本直接写明优先级TeamCreateParams用 TypeBox 定义了两个可选字段lifecycle.ts L48-L57export const TeamCreateParams Type.Object({ team_name: Type.Optional( Type.String({ description: Named team spec (project .omo/teams or omo.json) to create. Ignored when inline_spec is also provided. }), ), inline_spec: Type.Optional( Type.Union([InlineTeamSpecSchema, Type.String({ description: The same spec as a JSON string; parsed automatically. Passing the object form is preferred. })], { description: Inline team spec, e.g. { name, members: [{ name, category|subagent_type, prompt? }] }. A JSON string of the same object is also accepted and parsed automatically. Takes precedence when team_name is also provided., }), ), })两处描述文本已不再是模糊的二选一而是明确的优先级声明team_name被描述为当同时提供inline_spec时被忽略Ignored when inline_spec is also providedinline_spec被描述为当同时提供team_name时优先Takes precedence when team_name is also provided。同时inline_spec支持对象与 JSON 字符串两种形态字符串会自动解析coerceInlineSpeclifecycle.ts L95-L106解析失败返回带具体 JSON 错误细节的invalid_arguments。执行逻辑确定性优先级分支runTeamCreate是核心执行函数lifecycle.ts L108-L151其关键逻辑是export async function runTeamCreate(service: TeamToolsService, params: TeamCreateInput): PromiseToolExecutionResultTeamCreateDetails { const hasName params.team_name ! undefined params.team_name.length 0 const hasInline params.inline_spec ! undefined if (!hasName !hasInline) { return toolErrorResult(Provide team_name or inline_spec., { kind: invalid_arguments, reason: provide team_name or inline_spec }) } let inlineSpec: unknown if (hasInline) { const coerced coerceInlineSpec(params.inline_spec) if (!coerced.ok) { return toolErrorResult(coerced.reason, { kind: invalid_arguments, reason: coerced.reason }) } inlineSpec coerced.spec } try { const result await service.createTeam( hasInline ? { inlineSpec } : { teamName: params.team_name }, ) // ... } catch (error) { if (error instanceof SenpiTeamSpecError) return toolErrorResult(error.message, { kind: spec_error, code: error.code, reason: error.message }) if (error instanceof SenpiTeamRuntimeError) return toolErrorResult(error.message, { kind: runtime_error, code: error.code, reason: error.message }) throw error } }要点只有两个字段都为空的极端情况!hasName !hasInline才返回invalid_arguments只要inline_spec在场就只把inlineSpec传给服务层team_name被无条件丢弃——这与诊断文档的修复决策完全一致。结果用kind判别联合lifecycle.ts L77-L81区分created/invalid_arguments/spec_error/runtime_error输入畸形 →invalid_argumentsspec 不合法如命名团队不存在、成员引用已退役的 agent→spec_error携带codespawn/边界类失败 →runtime_error。工具描述lifecycle.ts L87-L91同样将优先级写进了给模型看的引导语Passinline_specfor an ad hoc team orteam_namefor a named spec;inline_spectakes precedence when both are provided.内联 spec 的成员 schemaInlineTeamSpecSchema与InlineTeamSpecMemberSchemalifecycle.ts L15-L46定义了内联团队的合法形状供模型与运行时共同参考团队级name可选缺省时派生内联名与members数组或单个成员对象均可单个对象会被自动包裹成数组。成员级name团队内唯一、小写词干归一化、kindcategory/subagent_type/agent其中agent是subagent_type的别名省略时按字段推断、category按类别路由、subagent_type按 agent 定义运行、prompt成员指令必须用英文书写、task_summary单行摘要最长 80 字符超长会被强制截断用于任务 footer/widget UI。两个 schema 都保留了additionalProperties: true的宽容度且注释明确当前会话恒为 lead不要声明 lead 成员。服务层解析resolveTeamSpec 的归一化与注册表加载工具层的优先级分支只是第一道闸门真正的 spec 解析在服务层完成。见 packages/omo-senpi/src/components/task/team-service-support.ts 的resolveTeamSpecL47-L80export async function resolveTeamSpec( input: { readonly teamName?: string; readonly inlineSpec?: unknown }, ports: SenpiTeamMemberPorts, projectRoot: string, omoTeams: Recordstring, unknown | undefined, ): PromiseResolvedTeamSpec { if (input.inlineSpec ! undefined) { const name inlineTeamName(input.inlineSpec) const spec normalizeSenpiTeamSpec(input.inlineSpec, name) validateSenpiTeamMembers(spec, ports) return { spec, source: omo-json } } const teamName input.teamName if (teamName undefined) throw new SenpiTeamSpecError(no team_name or inline_spec provided, INVALID_SPEC, unknown) const registry await loadTeamRegistry({ projectRoot, ports, ...(omoTeams ! undefined ? { omoTeams } : {}) }) const entry registry.teams.find((candidate) candidate.name teamName) if (entry ! undefined) return { spec: entry.spec, source: entry.source } // ... 未找到时抛出带可用团队清单的 INVALID_SPEC }从源码结构看这条路径清晰地划分了两条分支inline 分支权威路径input.inlineSpec ! undefined时直接走normalizeSenpiTeamSpec归一化含 JSON 字符串强转、单成员包裹、lead 字段与保留名校验见 packages/senpi-task/src/team/normalize.ts与validateSenpiTeamMembers成员校验source 记录为omo-json。named 分支从项目.omo/teams目录与omo.json声明的注册表加载未找到时抛出INVALID_SPEC错误消息会附上已声明团队清单与加载失败的团队列表帮助模型在下一次调用中纠正名称测试 team-service.test.ts L254 即断言了这类含declared-a/declared-b的错误信息。此外inlineTeamNameL33-L39会在内联 spec 缺省name时回退到inline-team作为团队名。服务入口createTeam位于 packages/omo-senpi/src/components/task/team-service.tsL136-L151它要求当前会话必须是 lead随后调用resolveTeamSpec并把解析结果交给底层createTeam拉起成员后台任务在 packages/omo-senpi/src/components/task/index.ts 中createTeam还被包装了一层leadPollers.kick()L300-L307用于唤醒空闲的 lead 轮询器——注释明确团队只能通过本会话自己的team_create出现因此正是这个调用负责唤醒轮询器。测试固化的优先级契约修复决策不仅落在实现里也被测试锁定为不可回退的契约。packages/senpi-task/src/tools/team/lifecycle-precedence.test.ts 全文只有一个用例恰好就是对本次故障场景的回归验证test(#given both team_name and inline_spec #when team_create runs #then inline_spec is authoritative, async () { // given const inlineSpec { name: inline-team, members: [] } const service createFakeTeamService({ createTeam: async () fakeCreateResult() }) // when const result await runTeamCreate(service, { team_name: stale-named-team, inline_spec: inlineSpec }) // then expect(result.details.kind).toBe(created) expect(service.calls).toEqual([{ method: createTeam, args: [{ inlineSpec }] }]) })它断言了最关键的一条当调用同时携带team_name: stale-named-team与inline_spec时服务层只收到{ inlineSpec }——team_name确实被忽略而不是被合并或报错。这正是第一次过度指定调用即成功的直接证据。相关测试还覆盖了TeamCreateParams不再暴露lead_session_idlifecycle.test.ts L11与诊断记录中移除模型提供的 lead-session override的演进相呼应。历史演进与可复用的工程启示诊断文档记录了该路径的三次关键演进0be02d59f389引入 Senpiteam_create的运行时 XOR 校验即prose 运行时约束的起点也是本次故障的伏笔。1b580615ad0e移除模型提供的 lead-session overrideteam_create不再允许调用方指定 leadlead 恒为当前会话schema 中也不再出现该字段。a8654d385a7d新增 JSON 字符串形态的inline_spec支持coerceInlineSpec的JSON.parse路径对象形态仍为首选。纵观整个修复可以提炼出三条可复用于任何 Agent 工具设计的经验不要让 schema 描述承担互斥约束。当两个参数在语义上互斥时优先用结构表达如判别联合、必填规则或在兼容归一化不可行的前提下用确定的优先级规则把多给变成无害。本案例中inline_spec更丰富、信息量更大因此被选为权威。警惕模型复述规则却不遵守规则。从诊断记录看Kimi 能在推理中正确重述 prose 规则却在输出层持续违反——说明文本引导对某些模型路径只是表面合规。工具设计必须假设输入可能同时携带所有可选字段。错误呈现方式解决不了已理解的重复错误。isError: false的软错误与文本错误信息都没有阻止重试因为瓶颈不在模型是否看懂错误而在模型下一次调用会不会生成同样形状。把失败面从输入约束改为服务端宽容才是切断重试循环的根本手段。对于 oh-my-openagent 的使用者而言这意味着在team_create上同时传入team_name与inline_spec不再是错误inline_spec会以权威身份直接执行而想要复现或验证本次修复可以阅读 .omo/evidence/20260804-team-create-recovery/diagnosis.md 的完整取证对照 lifecycle-precedence.test.ts 的回归用例并沿 lifecycle.ts → team-service-support.ts → team-service.ts 的调用链逐层核实。【免费下载链接】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),仅供参考