Skip to content
Ziwen
Go back

动态工具加载:四种 Agent Harness 的协议与实现

目录

动态工具加载由 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注册、搜索和激活工具,维护会话状态,路由工具调用
模型 APIKimi、OpenAI Responses、Anthropic Messages把工具 schema、搜索结果和调用结果编码进模型上下文
执行环境本地函数、Extension、MCP Server、provider server tool接收参数,执行操作,返回结果

一次动态调用经过以下链路:

用户提出任务
  → Agent 框架搜索 registry
  → provider adapter 生成模型 API 请求
  → 模型生成工具调用
  → Agent 框架把调用路由到执行环境
  → 执行结果进入下一轮模型请求

四个框架解决的是同一类问题:完整工具集持续增长,每一轮实际需要的工具很少。它们的主要差异是搜索发生在哪里、状态由谁维护,以及新增 schema 通过哪种 API 结构进入上下文。

toolstool_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 的 Calculatortool_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 会生成状态为 completedtool_search_calltool_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 CodeMCP tools模型调用 select_tools,框架更新已选集合Kimi messages[n].toolsMCP Server
Pi任意已注册 Extension toolExtension 搜索并修改 active setadapter 按 provider 转换Extension 的 execute()
CodexMCP、App 和 dynamic toolsCodex 用 BM25 搜索 deferred registryOpenAI Responses Tool SearchCodex handler、客户端或 MCP Server
Claude CodeMCP toolsToolSearch 搜索并加载匹配 schemaAnthropic defer_loading + tool_referenceClaude 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输出
supportsToolSearchopenai-responsesopenai-codex-responsestool_search_call + tool_search_output
supportsToolReferencesanthropic-messagesdefer_loading + tool_reference
deferredToolsMode: "kimi"Kimi OpenAI-compatible Chat Completionsmessages[n].tools
fallback其他 provider下一轮完整顶层 tools

Pi 把这些 capability 保存为内部兼容性元数据,并按 provider、API route 和 model id 配置。以所检查的 Pi commit 为例,openai/gpt-5.5 标记了 supportsToolSearchgpt-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 在本地索引中搜索,生成状态为 completedtool_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_loadingtool_referenceENABLE_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 或 ExtensionAgent 进程或宿主应用
stdio MCP toolAgent 启动的 MCP Server 子进程
HTTP/SSE MCP tool远程 MCP Server
provider server toolOpenAI、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。

参考资料


Share this post:

Previous Post
从召回到生成:一篇讲清 RAG 检索链路
Next Post
Pi 与 Kimi code Hanress 分析