Hydra-ai(Tambo)Provider-Managed Skill Tool Calls 抑制方案:在源头抑制,而非下游到处修补 Hydra-aiTamboProvider-Managed Skill Tool Calls 抑制方案在源头抑制而非下游到处修补【免费下载链接】hydra-aiGenerative UI SDK for React项目地址: https://gitcode.com/GitHub_Trending/hy/hydra-ai本文聚焦 Tambohydra-ai后端在 AI SDK 流式管线中处理 Anthropic / OpenAI 等厂商托管 Skill 工具调用的架构决策。核心结论是当厂商内部工具事件code_execution、shell混入用户注册的工具调用流时正确做法是在ai-sdk-client.ts的流式入口处用布尔标志 三个break直接抑制而不是为每个下游消费者添加特判。读完本文你将理解该问题产生的四个具体故障、两种失败方案的教训、最终 O(1) 级修复的源码实现以及未来新增厂商工具时的扩展步骤。背景厂商托管的 Skill 工具事件为何会泄漏Tambo 的架构中Skill技能既可以通过 packages/backend/src/tambo-backend.ts 注入决策循环也可以直接交给大模型厂商在安全容器环境内执行。当 Anthropic / OpenAI 执行 Skill 时它们会通过 AI SDK 的流式管线发出工具调用事件code_execution、shell。这类事件是厂商内部实现细节不应该流入 Tambo 自身的工具调用管线——因为 Tambo 管线语义是用户注册的、需要 Tambo 转发的工具。问题出在从流式管线的角度看这些厂商事件与用户注册的工具调用长得一模一样都是tool-input-start→tool-input-delta→tool-call事件序列纳管它们会立刻引发连锁故障。问题放行厂商工具事件的四个具体故障原文档列出了四个典型症状均可从源码结构得到印证UI 把code_execution当作工具名展示前端拿到TOOL_CALL_*事件后直接渲染toolCallName厂商内部工具名如code_execution会原样出现在界面上。内部路径泄漏/skills/my-skill/SKILL.md这类 Skill 文件路径会作为工具参数暴露给终端用户破坏封装边界。刷新后工具调用消失厂商执行的工具在tool-result时被清空见下文tool-result分支导致页面刷新后工具调用时有时无。同一轮内多个工具调用互相覆盖每条消息只承载一个toolCallRequest多条厂商工具调用会互相覆盖。从源码看handleStreamingResponse中的accumulatedToolCall是一个单一累加器ai-sdk-client.ts每条消息只能 yield 一个tool_calls这正是第 4 点故障的机制根源。尝试过的方案以及为何失败方案一重命名 清理过于脆弱把code_execution改名为skill、清理参数、跨调用累加 Skill 名称。这个方案需要在7 个文件里打特判补丁流式处理器5 个跟踪变量、协调事件发射决策循环用条件展开conditional spreads跨 yield 保留数据工具服务对未知工具提前 returnThreads 服务约 40 行检测块V1 转换跳过 skill tool_use 块可观测性 UI把 skill 卡片重排到文本之前Core 包新增共享常量。文档的结论很直白每个修复都会在下游制造一个新边界用例修补的成本随下游消费者数量线性增长。方案二基于元数据的跟踪仍然过度设计把 Skill 执行记录为metadata._tambo.skillExecutions而不是工具调用。更干净了但仍然要新增LLMStreamItem和DecisionStreamItem接口上的新字段用正则解析提取 Skill 名称Threads 服务中的元数据存储渲染 Skill 徽章的新 UI 组件Core 包中的isSkillToolName辅助函数。文档对此的点评一针见血为了一个没人需要看的厂商工具调用。方案三直接抑制正确答案一个布尔标志 在ai-sdk-client.ts中加三个break。Skill 工具事件对工具调用累加器完全不可见厂商执行完 Skill 后由 LLM 在文本回复里描述结果通过系统提示词强制。最终方案源码详解步骤 1按厂商解析 Skill 工具名AISdkClient顶部维护了一张厂商 → Skill 工具名的映射表ai-sdk-client.ts/** * Provider-specific tool names used for skill execution. * Each provider uses a different tool name for its skill container. * Only the tool name for the active provider is suppressed, so a user * tool named shell on Anthropic (or code_execution on OpenAI) is * not accidentally swallowed. */ const PROVIDER_SKILL_TOOL_NAME: Recordstring, string { anthropic: code_execution, openai: shell, };这段注释本身就是设计的关键约束只抑制当前活跃厂商注入的那个工具名。这样当用户在 Anthropic 上自定义了一个名为shell的工具或在 OpenAI 上自定义了code_execution不会因为名字撞车而被误吞。skillToolName的解析发生在complete()的流式分支ai-sdk-client.tsconst skillToolName params.providerSkills?.skills.length ? PROVIDER_SKILL_TOOL_NAME[providerKey] : undefined;即只有当本次请求确实携带了 providerSkills 时才启用抑制否则skillToolName为undefined所有事件照常处理。步骤 2流式循环中的三个breakhandleStreamingResponse维护一个isProviderSkillTool标志ai-sdk-client.ts在三个事件分支中执行抑制case tool-input-start: { // Only suppress the specific tool name injected by the active // provider for skills. This avoids silently swallowing a user // tool that shares a name with a different providers skill tool // (e.g. user tool shell on Anthropic, or code_execution on OpenAI). isProviderSkillTool !!skillToolName delta.toolName skillToolName; if (isProviderSkillTool) { // Skill tools are fully handled by the provider. Ignore. // Clear accumulated tool state to prevent stale data from a // previous tool call leaking if tool-result doesnt fire. componentTracker undefined; accumulatedToolCall.name undefined; accumulatedToolCall.arguments ; accumulatedToolCall.id undefined; break; } // ... 正常工具处理含 show_component_* 组件跟踪... } case tool-input-delta: if (isProviderSkillTool) break; // ... 正常参数累加 / 组件 JSON 增量解析 ... case tool-call: if (isProviderSkillTool) break; // ... google thoughtSignature 元数据处理 ... // ... 组件 finalize / TOOL_CALL_END 发射 ...三个break分别在开始、增量、完成三个事件上拦截且只在tool-input-start判断标志后续事件靠状态延续。注意tool-input-start分支里还清空了accumulatedToolCall和componentTracker防止上一个工具调用残留的脏数据泄漏例如tool-result未触发的情况。步骤 3tool-result对厂商执行结果的兜底即使有上述拦截厂商执行完成的tool-result事件仍可能出现在流中。源码在tool-result分支做了显式断言与清理ai-sdk-client.tscase tool-result: // Provider-managed tools (e.g. OpenAI shell for skills) return results // inline in the stream. These are handled by the provider, not Tambo. if (!(providerExecuted in delta) || !delta.providerExecuted) { throw new Error( Tool result should not be emitted during streaming, ); } // Clear accumulated tool call so subsequent chunks dont carry // the provider-executed tool as an unresolved client tool call. accumulatedToolCall.name undefined; accumulatedToolCall.arguments ; accumulatedToolCall.id undefined; break;这印证了原文档中厂商执行的工具会在tool-result时被清空的故障描述——厂商工具的结果是内联返回的providerExecuted: true不属于 Tambo 的转发职责。步骤 4文本恢复时复用同一消息 IDSkill 工具执行期间 LLM 暂停生成文本完成后在同一流中继续。text-start分支专门处理这种情况ai-sdk-client.ts如果已有未结束的文本消息且刚刚结束的是一个 Skill 工具调用则复用textMessageId并追加\n\n分隔符客户端收到重复TEXT_MESSAGE_START时只更新流式状态、不新建气泡。// Text resumed after a provider-managed skill tool call. // This branch only triggers for skills because regular tool // calls end the stream entirely (the decision loop restarts // a new complete() call for the next turn). Only provider- // executed tools (skills) return results inline and let the // LLM continue generating text in the same stream.这正是普通工具与 Skill 工具在流式行为上的本质差异普通工具调用会终止本轮流决策循环重新发起complete()而 Skill 工具内联返回结果并让 LLM 在同一流中继续说话。前置Skill 如何被装配进请求抑制方案依赖仅在有 providerSkills 时生效理解装配链路有助于你调试类型定义ProviderSkillConfig与ProviderSkillReference定义在 packages/core/src/skills.tsproviderSkills结构为{ providerName, skills: [{ skillId, version }] }客户端接口LLMClient.complete/StreamingCompleteParams在 llm-client.ts 中声明了providerSkills?: ProviderSkillConfig决策循环runDecisionLoop接收providerSkills参数并透传给llmClient.completedecision-loop-service.ts模型级支持在ensureProviderSkillsForRun上游校验传入的 skills 对该模型一定有效厂商装配mergeProviderSkills是真正的注入点ai-sdk-client.ts——OpenAI 侧添加shell工具containerAuto环境 skillReference列表并切换到responses API模型Anthropic 侧添加codeExecution_20260120()工具并在providerOptions.anthropic.container.skills下注入custom类型的 Skill 引用。两处都会在覆盖同名工具时打印警告。系统提示词让 LLM 自己交代 Skill 做了什么抑制工具事件后用户如何感知 Skill 的执行答案是由 LLM 在文本回复中描述。决策循环系统提示词中新增了### Skills (Internal Implementation Detail)段落decision-loop-prompts.ts### Skills (Internal Implementation Detail) You may have access to skills that run in a secure container environment. These skills are an internal implementation detail and must be treated as opaque. - Do NOT mention skill file names, skill IDs, SKILL.md files, or any internal skill structure in your responses or thinking. - Do NOT describe how skills are loaded, structured, or executed. - When you use a skill, briefly mention which skill you are using by its name (e.g. Using the contenteditable="false">【免费下载链接】hydra-aiGenerative UI SDK for React项目地址: https://gitcode.com/GitHub_Trending/hy/hydra-ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考