现象
主 agent 注册了若干外部工具,子 agent 只注册了一个普通工具。
主 agent 通过 AgentTool 调子 agent 时,子 agent 明明只配置了自己的工具,却在模型请求上下文里看到了主 agent 的外部工具。
根因
AgentTool 创建子 invocation 时使用了:
而 Invocation.Clone 会复制父 invocation 的:
父 run 中由 AG-UI 注入的外部工具会进入:
子 invocation 继承后,LLM flow 又会把这些外部工具追加到子 agent 的模型请求里:
AG-UI input.Tools
-> RunOptions.ExternalTools
-> AgentTool parentInv.Clone(...)
-> child invocation.RunOptions.ExternalTools
-> LLM flow append tools
-> 子 agent 看到外部工具
影响范围
父 agent 静态注册工具不受影响,子 invocation 切到子 agent 后,基础工具面来自子 agent 自己的 Tools()。问题范围集中在父 invocation RunOptions 继承下来的工具字段。
受影响字段如下。
| 字段 |
影响 |
ExternalTools |
外部工具会进入子 invocation,是本次问题的主链路 |
AdditionalTools |
run 级别追加的外部工具也会进入子 invocation |
ExternalToolNames |
外部工具的派生状态会进入子 invocation |
ToolFilter |
父工具过滤器会进入子 invocation,并在子流程工具过滤阶段被使用 |
源码确认的路径如下。
| 路径 |
行为 |
普通 AgentTool |
会继承父 RunOptions,当前没有清理外部工具字段,是本次问题触发点 |
DynamicTool |
会通过 Clone 派生子 invocation,随后清理 AdditionalTools、ExternalTools、ExternalToolNames、ToolFilter |
Explorer |
会继承父 invocation,随后清理 ExternalTools、ExternalToolNames、ToolFilter,保留 AdditionalTools |
| Graph AgentNode |
会继承父 ExternalTools,测试明确要求 parent external tools 先于 node-scoped external tools 出现在子 invocation |
| Team |
会通过 Clone 派生成员 invocation,继承父 RunOptions |
| ChainAgent / CycleAgent / ParallelAgent |
会通过 Clone 派生子 agent invocation,继承父 RunOptions |
transfer_to_agent |
会通过 Clone 派生目标 agent invocation,继承父 RunOptions |
结论
普通 AgentTool 子调用 clone 了父 invocation 的 RunOptions,但没有清理外部工具面,导致父 run 注入的外部工具暴露给子 agent。
修复建议
方案一 修改默认行为
普通 AgentTool 创建子 invocation 时默认清理父 invocation 继承下来的外部工具字段。
RunOptions.AdditionalTools = nil
RunOptions.ExternalTools = nil
RunOptions.ExternalToolNames = nil
RunOptions.ToolFilter = nil
这个方案可以直接修复所有普通 AgentTool 调用场景,更符合语义直觉。语义上把子 agent 作为独立工具边界,子 agent 默认只看到自己定义的工具。
方案二 新增 option
保持当前默认行为,新增一个 AgentTool option 显式控制外部工具隔离,形式类似已有的 WithPinModel(true)。
示例命名如下。
WithPinExternalTools(true)
开启后清理父 invocation 继承下来的外部工具字段。
RunOptions.AdditionalTools = nil
RunOptions.ExternalTools = nil
RunOptions.ExternalToolNames = nil
RunOptions.ToolFilter = nil
这个方案兼容性更稳,调用方可以逐个 AgentTool 启用隔离。
回归测试
至少补三类测试:
- 父 invocation 有
ExternalTools,子 agent 模型请求不应看到。
- 父 invocation 有
AdditionalTools,子 agent 模型请求不应看到。
- 父 invocation 有
ToolFilter,不应误过滤子 agent 自身工具。
现象
主 agent 注册了若干外部工具,子 agent 只注册了一个普通工具。
主 agent 通过
AgentTool调子 agent 时,子 agent 明明只配置了自己的工具,却在模型请求上下文里看到了主 agent 的外部工具。根因
AgentTool创建子 invocation 时使用了:而
Invocation.Clone会复制父 invocation 的:父 run 中由 AG-UI 注入的外部工具会进入:
子 invocation 继承后,LLM flow 又会把这些外部工具追加到子 agent 的模型请求里:
影响范围
父 agent 静态注册工具不受影响,子 invocation 切到子 agent 后,基础工具面来自子 agent 自己的
Tools()。问题范围集中在父 invocationRunOptions继承下来的工具字段。受影响字段如下。
ExternalToolsAdditionalToolsExternalToolNamesToolFilter源码确认的路径如下。
AgentToolRunOptions,当前没有清理外部工具字段,是本次问题触发点DynamicToolClone派生子 invocation,随后清理AdditionalTools、ExternalTools、ExternalToolNames、ToolFilterExplorerExternalTools、ExternalToolNames、ToolFilter,保留AdditionalToolsExternalTools,测试明确要求 parent external tools 先于 node-scoped external tools 出现在子 invocationClone派生成员 invocation,继承父RunOptionsClone派生子 agent invocation,继承父RunOptionstransfer_to_agentClone派生目标 agent invocation,继承父RunOptions结论
普通
AgentTool子调用 clone 了父 invocation 的RunOptions,但没有清理外部工具面,导致父 run 注入的外部工具暴露给子 agent。修复建议
方案一 修改默认行为
普通
AgentTool创建子 invocation 时默认清理父 invocation 继承下来的外部工具字段。这个方案可以直接修复所有普通
AgentTool调用场景,更符合语义直觉。语义上把子 agent 作为独立工具边界,子 agent 默认只看到自己定义的工具。方案二 新增 option
保持当前默认行为,新增一个
AgentTooloption 显式控制外部工具隔离,形式类似已有的WithPinModel(true)。示例命名如下。
开启后清理父 invocation 继承下来的外部工具字段。
这个方案兼容性更稳,调用方可以逐个
AgentTool启用隔离。回归测试
至少补三类测试:
ExternalTools,子 agent 模型请求不应看到。AdditionalTools,子 agent 模型请求不应看到。ToolFilter,不应误过滤子 agent 自身工具。