动态工具加载由 Agent 框架调度,由模型 API 表达,最终由客户端或 MCP Server 执行。Kimi Code、Pi、Codex 和 Claude Code 位于框架层;Kimi API、OpenAI Responses API 和 Anthropic Messages API 位于协议层。

图:Kimi 用工具箱比喻动态工具加载。图片引用自 Kimi API 文档。
| 层级 | 代表对象 | 职责 |
|---|---|---|
| Agent 框架 | Kimi Code、Pi、Codex、Claude Code | 注册、搜索和激活工具,维护会话状态,路由工具调用 |
| 模型 API | Kimi、OpenAI Responses、Anthropic Messages | 把工具 schema、搜索结果和调用结果编码进模型上下文 |
| 执行环境 | 本地函数、Extension、MCP Server、provider server tool | 接收参数,执行操作,返回结果 |
一次动态调用经过以下链路:
用户提出任务
→ Agent 框架搜索 registry
→ provider adapter 生成模型 API 请求
→ 模型生成工具调用
→ Agent 框架把调用路由到执行环境
→ 执行结果进入下一轮模型请求
四个框架解决的是同一类问题:完整工具集持续增长,每一轮实际需要的工具很少。它们的主要差异是搜索发生在哪里、状态由谁维护,以及新增 schema 通过哪种 API 结构进入上下文。
tools 和 tool_choice
API 请求用结构化字段声明可调用函数,system prompt 负责说明使用规则。以 OpenAI-compatible Chat Completions 为例,工具定义位于请求顶层:
{
"model": "...",
"messages": [...],
"tools": [
{
"type": "function",
"function": {
"name": "search_tools",
"description": "Search available tools",
"parameters": {...}
}
}
],
"tool_choice": "required"
}
tools 回答“模型能调用什么”,tool_choice 回答“这一轮是否必须调用”。auto 允许模型自行决定,none 禁止调用,required 要求至少调用一个当前可见的工具。
required 的作用范围是当前所有可见工具。若请求同时声明多个工具,模型可以选择其中任意一个。应用可以只暴露 search_tools,也可以通过 tool choice 明确指定它。Kimi 的最佳实践采用首轮检索,再把 tool_choice 切回 auto。
工具注册与模型可见性
Agent 可以在 registry 保存完整工具集,每次请求只暴露其中一部分。动态加载因此包含两层状态:
- registry 保存所有可执行工具;
- active set 保存当前对模型可见的工具。
典型流程是:
注册全部工具
→ 首轮只暴露 search_tools 和核心工具
→ 模型调用 search_tools
→ 应用从 registry 选择匹配工具
→ 框架更新 active set
→ provider adapter 注入新增 schema
→ 模型直接调用新工具
搜索算法位于应用层,可以使用字符串匹配、BM25、向量检索或远程 catalog。协议负责把搜索结果转换成模型可调用的工具。
三种 API 协议和一种兼容路径
假设 search_tools 刚找到 Calculator。Kimi、OpenAI 和 Anthropic 分别提供一种动态表达;其他 provider 可以在下一轮更新普通顶层 tools。四条路径都让 Calculator 进入模型上下文,完整 schema 的位置各不相同。
| 方式 | 请求顶层 tools | 动态加载记录 |
|---|---|---|
| Kimi K3 API | 保持核心工具 | system.tools = [Calculator] |
| OpenAI Responses API(client) | 保持核心工具 | tool_search_output.tools = [Calculator] |
| Anthropic Messages API | 始终包含带 defer_loading 的 Calculator | tool_reference = Calculator |
| 通用 fallback | 下一轮加入普通 Calculator | 无专用记录 |
Kimi K3 API:把 schema 插进 message
应用完成搜索后,在下一次请求的消息历史中加入:
{
"role": "system",
"tools": [
{
"type": "function",
"function": {
"name": "Calculator",
"description": "Evaluate an expression",
"parameters": {...}
}
}
]
}
Kimi 复用顶层 tools 的 schema,并把定义插入 messages。Calculator 从这个 message 开始生效。应用负责搜索,Kimi API 负责识别 message-level tools。
OpenAI Responses API:用 Tool Search 记录加载结果
OpenAI 把工具定义放进 tool_search_output:
{
"type": "tool_search_output",
"execution": "client",
"call_id": "call_123",
"status": "completed",
"tools": [
{
"type": "function",
"name": "Calculator",
"parameters": {...},
"defer_loading": true
}
]
}
OpenAI 支持 hosted 和 client-executed Tool Search。Hosted 模式由 OpenAI 搜索预先声明的 deferred tools。Client-executed 模式由客户端搜索,再返回 tool_search_output。
Pi 使用 client-executed 的表示。Extension 已经完成搜索,所以 Pi 会生成状态为 completed 的 tool_search_call 和 tool_search_output,把结果追加到历史末尾。
Anthropic Messages API:顶层保存 schema,message 放引用
Anthropic Messages 需要同时使用两个位置。客户端在顶层 tools 发送完整定义,并给待加载工具加上 defer_loading: true:
{
"tools": [
{
"name": "search_tools",
"input_schema": {...}
},
{
"name": "Calculator",
"description": "Evaluate an expression",
"input_schema": {...},
"defer_loading": true
}
]
}
HTTP 请求和模型初始上下文包含的内容不同:
HTTP 请求:
search_tools + Calculator 完整定义
模型初始上下文:
search_tools
Anthropic API 收到 Calculator 的完整 schema,但暂缓把它放进模型的初始工具区。搜索命中后,对话中出现:
{
"type": "tool_reference",
"tool_name": "Calculator"
}
tool_reference 指定加载位置。Anthropic API 根据名称找到顶层 tools 中的 deferred definition,再把完整 schema 展开到这个位置。模型从这里开始调用 Calculator。
顶层 tools 保存完整定义
→ defer_loading 暂缓进入模型上下文
→ 搜索命中 Calculator
→ 对话中加入 tool_reference
→ API 在该位置展开完整 schema
→ 模型调用 Calculator
Anthropic 官方 Tool Search 可以用 regex 或 BM25 在服务端搜索。Pi 采用客户端搜索:Extension 先找到 Calculator,Pi 再把 defer_loading definition 和 tool_reference 交给 Anthropic API。两条路径使用相同的加载语义,搜索发生的位置不同。
可以把两个字段记成:
defer_loading = 先保存,暂缓展示
tool_reference = 从这里开始展示
通用 fallback:直接更新顶层 tools
通用 fallback 在下一轮发送完整 active set:
{
"tools": [{ "name": "search_tools" }, { "name": "Calculator" }]
}
这条路径只更新顶层工具区。模型从新请求的 tools 看到 Calculator。实现简单,顶层工具变化可能影响 provider 的 prompt cache。
四个框架如何实现动态加载
四个框架都维护完整工具集,但延迟对象、搜索位置和 provider 绑定程度不同。
| 框架 | 延迟加载的对象 | 搜索与激活 | API 表达 | 工具执行者 |
|---|---|---|---|---|
| Kimi Code | MCP tools | 模型调用 select_tools,框架更新已选集合 | Kimi messages[n].tools | MCP Server |
| Pi | 任意已注册 Extension tool | Extension 搜索并修改 active set | adapter 按 provider 转换 | Extension 的 execute() |
| Codex | MCP、App 和 dynamic tools | Codex 用 BM25 搜索 deferred registry | OpenAI Responses Tool Search | Codex handler、客户端或 MCP Server |
| Claude Code | MCP tools | ToolSearch 搜索并加载匹配 schema | Anthropic defer_loading + tool_reference | Claude Code 或 MCP Server |
Kimi Code:按需加载 MCP 工具
Kimi Code 在模型声明 dynamically_loaded_tools、支持 tool_use,并且 tool-select 开关启用时使用动态路径;其余配置回到普通顶层 tools。
动态路径把待发现的 MCP schema 留在 registry。稳定的顶层 tools 包含内置工具和 select_tools。MCP 连接变化通过 <tools_added> 和 <tools_removed> 告诉模型。模型用精确名称调用 select_tools,框架再追加一个携带完整 schema 的 system message。Loop 在下一 step 重新读取工具表,新工具随即生效。
Kimi Code 优先处理 MCP tools,因为 MCP 服务器经常带来几十或上百个工具。K3 的 messages[n].tools 同样可以承载普通 function tool。
Kimi 官方目前为 kimi-k3 开放动态声明。把相同 message 发给 kimi-k2.6 会得到 tokenization failed。兼容性同时涉及 HTTP API、chat template 和模型输入编码。K3 的请求路径能够把 message-level tools 编码成模型输入,K2.6 的路径会在编码阶段失败。
Pi:统一状态,按 provider 转换
Pi 给 Extension 提供统一接口。Extension 先用 pi.registerTool() 注册工具,再在执行过程中调用 pi.setActiveTools()。Pi 比较执行前后的 active set;纯增量变化会被记录为 addedToolNames。provider adapter 随后生成 Kimi message、OpenAI search output 或 Anthropic reference。
Pi 根据模型配置选择 provider 表示:
| Pi capability | 适用 API | 输出 |
|---|---|---|
supportsToolSearch | openai-responses、openai-codex-responses | tool_search_call + tool_search_output |
supportsToolReferences | anthropic-messages | defer_loading + tool_reference |
deferredToolsMode: "kimi" | Kimi OpenAI-compatible Chat Completions | messages[n].tools |
| fallback | 其他 provider | 下一轮完整顶层 tools |
Pi 把这些 capability 保存为内部兼容性元数据,并按 provider、API route 和 model id 配置。以所检查的 Pi commit 为例,openai/gpt-5.5 标记了 supportsToolSearch,gpt-5.5-pro 走 fallback。能力判断以完整配置为准。
Extension 负责实现搜索。示例中的 search_tools 是普通工具,可以通过关键词选择 Calculator,也可以连接任意检索系统。Pi 负责记录 active set 变化并按 provider 翻译。这个分层让 Pi 可以动态加载任意 Extension tool。
Codex:在本地搜索 deferred registry
Codex 把待发现工具标记为 deferred exposure,并从工具名称、描述、参数名称和参数描述构建 BM25 索引。只要 registry 中存在 deferred tool,Codex 就向模型暴露一个 client-executed tool_search。
模型返回 tool_search_call 后,Codex 在本地索引中搜索,生成状态为 completed 的 tool_search_output,其中包含命中工具的完整 schema。后续调用再由 registry 中对应的 handler 处理。MCP tool 会被路由到 MCP Server;App 和 dynamic tool 可以交给 Codex runtime 或外部客户端执行。
这条路径把工具发现放在 Codex 进程,把上下文表示交给 OpenAI Responses API。Codex 的公开源码同时覆盖 deferred function、namespace 和 MCP tool。
Claude Code:默认延迟加载 MCP tools
Claude Code 默认启用 MCP Tool Search。启动时进入上下文的是工具名称和 MCP Server instructions,完整 schema 在搜索命中后加载。命中的工具会在后续轮次保持可用;会话压缩移除较早的加载记录后,框架可以再次搜索。
底层协议使用 Anthropic Messages API 的 defer_loading 和 tool_reference。ENABLE_TOOL_SEARCH=true 始终延迟加载,auto 在全部工具 schema 超过上下文窗口 10% 时启用,auto:N 可以自定义阈值,false 会预先加载全部工具。alwaysLoad 可以让指定 MCP Server 的工具持续可见。
Claude Code 会在第三方 ANTHROPIC_BASE_URL 等兼容性不足的路径上默认关闭 Tool Search,因为代理还需要正确转发 beta header 和 tool_reference block。Claude Agent SDK 将相同机制用于 remote MCP 和 custom SDK MCP tools。
Kimi Code 和 Claude Code 围绕自家模型 API 优化 MCP 工具池。Pi 用 capability adapter 支持多种 provider。Codex 绑定 OpenAI Responses API,同时把 MCP、App 和客户端 dynamic tools 放进统一的 deferred registry。
工具执行
模型 API 负责返回工具名称和参数。Agent 框架完成参数校验、权限判断和调用路由,真正的业务操作发生在对应执行环境。
| 工具类型 | 执行位置 |
|---|---|
| 本地 function 或 Extension | Agent 进程或宿主应用 |
| stdio MCP tool | Agent 启动的 MCP Server 子进程 |
| HTTP/SSE MCP tool | 远程 MCP Server |
| provider server tool | OpenAI、Anthropic 或其他 provider 的基础设施 |
| Codex App Server dynamic tool | 接入 App Server 的客户端 |
开发者需要提供可运行的函数或 MCP Server,并处理参数验证、认证授权、超时、重试、幂等性和错误返回。框架可以统一调用循环与权限界面,工具自身仍然承担业务正确性和安全边界。
动态加载与缓存
动态工具首先减少 token,但更重要的收益是保持前缀稳定。长对话中,system prompt、核心工具和早期消息占据大部分输入。若每发现一个工具就改写顶层工具数组,provider 可能无法复用已经计算过的前缀。
各 provider 使用不同的缓存规则。普通 function calling 会把顶层 tools 放进模型前缀;Pi 的 fallback 修改 active set 后,当前请求通常需要建立新缓存。工具列表稳定后,后续请求可以命中新前缀。
OpenAI Tool Search、Anthropic tool_reference 和 Kimi messages[n].tools 都把新定义放到已有上下文之后。前面的长前缀保持不变,新增部分只从加载点开始参与计算。如果回头修改或删除较早的动态声明,缓存也会从那个位置之后失效。
OpenAI Tool Search 会在上下文末尾加载工具。Anthropic 在计算 cache key 前排除 deferred tools。Kimi 文档说明,追加 message-level tool 不影响此前的 cached prefix,顶层 global tools 也不影响它的 cache hit。跨模型框架仍需按 provider 配置 capability 和 fallback。
结论
动态工具加载的关键是分开维护完整 registry 和模型当前可见的工具。Agent 框架决定何时搜索、加载和执行,模型 API 决定新增 schema 如何进入上下文,执行环境负责业务操作。单一 provider 可以直接使用原生动态协议;多 provider 框架还需要 capability 判断和通用 fallback。
参考资料
- Kimi K3 API Tool Calling Best Practices
- Kimi Dynamically Loaded Tools
- OpenAI Tool Search
- OpenAI Prompt Caching
- Anthropic Tool Search Tool
- Anthropic Tool Reference
- Claude Code MCP Tool Search
- Claude Agent SDK Tool Search
- Kimi Code
select_tools实现 - Pi Dynamic Tool Loading
- Pi OpenAI Responses 转换
- Codex Responses 工具转换
- Codex Tool Search 实现