您的位置:首页 > 手游攻略 > 工具塞满上下文窗口怎么办?深度解析 AI Agent Tool Search 按需加载实现原理

工具塞满上下文窗口怎么办?深度解析 AI Agent Tool Search 按需加载实现原理

作者:互联网  时间: 2026-07-16 18:40:03  

本文将讲解 AI Agent 里的 tool search 机制——当工具数量多到占满上下文窗口时,怎么按需加载模型需要的工具。本文内容覆盖实现原理、匹配算法、缓存影响,并配合 x-code-cli 的源码来讲解。

Tool 和 MCP Tool

LLM 本身不能执行操作

大语言模型(LLM)是纯文本生成器,只能接收文本输入并输出文本应答,无法主动执行外部操作。

AI Agent 之所以能做这些事,是因为模型可以通过工具调用的方式来执行这些操作。例如模型输出指令——“我要调用 readFile 工具,参数是 package.json”——客户端(这里指 MCP 客户端,即实现了 MCP 协议的 AI Agent 应用,如 x-code-cli、Claude Code、Cursor 等)代码将解析这条指令,并执行实际的文件读取操作,再把读到的内容传回给模型。模型拿到文件内容后继续推理,决定下一步做什么。

这些可被模型调用的操作——读文件、执行命令、搜索代码、查数据库、联网搜索——统称为 Tool(工具)。每个工具由三部分组成:一个名字(name)、一段自然语言描述(description,告诉模型这个工具能做什么)、一份 JSON Schema(inputSchema,定义工具接受什么参数)。以 x-code-cli 的 readFile 工具为例:

{"name": "readFile","description": "Read the contents of a file at the specified path.","inputSchema": {"type": "object","properties": {"path": {"type": "string","description": "The absolute path of the file to read"},"offset": {"type": "number","description": "Line number to start reading from (1-based)"},"limit": {"type": "number","description": "Maximum number of lines to read"}},"required": ["path"]}}

这三部分会被序列化后放进每次 API 请求的 tools 数组。模型根据 description 判断什么时候用这个工具,根据 inputSchema 生成合法的参数。一个工具的定义越复杂(参数多、描述长),占用的 token 就越多。

下面是 x-code-cli 里 Agent Loop 的执行流程:

sequenceDiagramautonumberactor U as 用户participant C as 客户端(x-code-cli)participant LLM as LLM APIU->>C: "帮我把 utils.ts 里的 foo 函数重命名为 bar"C->>LLM: 用户消息 + 工具列表(readFile, edit, shell, grep...)LLM-->>C: tool_call: readFile({path: "src/utils.ts"})Note over C: 客户端执行 readFile,读取文件内容C->>LLM: tool_result: "export function foo() { ... }"LLM-->>C: tool_call: edit({path: "src/utils.ts", old: "foo", new: "bar"})Note over C: 客户端执行 edit,替换字符串C->>LLM: tool_result: "文件已修改"LLM-->>C: 文本回复: "已将 foo 重命名为 bar"C->>U: 展示回复

在整个过程中,模型自主决定调用什么工具、传什么参数。客户端只负责执行和传递结果。这个循环可能执行一次,也可能执行很多次,主要取决于任务的复杂度。

工具数量的增长

内置工具

一个 Agent 产品刚上线时,内置工具的数量可能不多。但是随着产品的迭代,内置工具会逐渐增加。例如联网搜索、网页抓取、子 agent 委派、待办管理、后台任务管理等工具。Claude Code、Codex、Cursor 这些主流 AI Agent 产品都经历了这个增长过程,一个成熟的 AI Agent 产品拥有几十个内置工具是很正常的。

MCP 工具

MCP(Model Context Protocol,模型上下文协议)是一套让 Agent 接入外部服务的开放协议。例如接入 GitHub MCP server,Agent 就能创建 issue、合并 PR;接入一个数据库 MCP server,Agent 就能执行 SQL 查询。

各个 MCP Server 会通过 tools/list 接口向客户端注册自身对外开放的全部工具;该接口采用全量返回模式,客户端在建立与某一服务端的连接后,即可一次性获取该服务端完整的工具定义清单。

sequenceDiagramparticipant C as MCP 客户端(AI Agent)participant S1 as MCP Server A(交易所)participant S2 as MCP Server B(数据库)C->>S1: 建立连接C->>S1: tools/listS1-->>C: 返回 400 个工具定义C->>S2: 建立连接C->>S2: tools/listS2-->>C: 返回 15 个工具定义Note over C: 客户端拿到所有 server 的完整工具列表<br/>共 415 个工具,全部放进后续的 LLM 请求

MCP tool 和内置 tool 在结构上没有区别,都是名字 + 描述 + JSON Schema。区别只在于是谁提供的:内置 tool 是产品代码里写死的,MCP tool 是外部 server 动态注册的。

一个 MCP server 提供的工具数量往往远超内置工具。以一个加密货币交易所的 MCP server 为例,它覆盖现货交易、合约交易、资产划转、行情查询、余额查询等操作,一共 400 个工具。如果用户本身又接了一些其他的 MCP server,工具总数很容易超过 400 个。

工具数量增多带来的问题

如果要让大模型调用这些工具,所有工具定义必须随请求一起发给模型。由于大语言模型本身是无状态的,每次发起推理请求时,都需要重新携带完整的工具列表。

一个中等复杂的工具定义大约 150–300 token(名字几个 token、描述一两行、JSON Schema 里的属性名和类型描述是大头)。400 个工具 × 平均 160 token ≈ 64,000 token。用户还没开始提问,光工具定义就占掉了 64k token。

我们来看一下 x-code-cli 里调用 LLM 时的请求结构。下面是 packages/core/src/agent/loop.tsrunTurn 函数的核心代码(简化后):

result = streamText({model, // LLM 模型实例system: cached.system, // System Prompt(系统提示),约 2-5k tokenmessages: cached.messages,// Messages(对话历史 + 用户消息)tools: cached.tools, // Tools(工具定义)← 全量注入时 64k tokenmaxRetries: 3,abortSignal: options.abortSignal,maxOutputTokens: getMaxOutputTokens(options.modelId),providerOptions: mergedProviderOptions,})

一次 API 请求的输入由 systemtoolsmessages 三个字段组成。token 的分布大致是这样的:

一次 API 请求的 Token 构成:┌────────────────────────────────────┐│ system(系统提示)│约 2-5k token├────────────────────────────────────┤│ tools(工具定义) │← 全量注入时:64k token(400 个工具)├────────────────────────────────────┤│ messages(对话历史 + 用户消息)│根据对话长度变化└────────────────────────────────────┘如果模型的上下文窗口是 128k,工具列表占掉了将近一半

关于费用需要说明一点:主流 LLM API 都有前缀缓存(prefix caching)。system + tools 这些每轮不变的前缀部分,第一次请求按全价计费,后续请求命中缓存后按折扣价计费(通常是原价的 10%–50%),所以不是每轮都需要付全价。

但 64k token 的工具列表即使命中缓存,也在持续产生开销:它占用了上下文窗口的物理空间,增加了首字延迟(TTFT),挤压了留给对话历史和用户消息的空间。工具列表越大,能用来做实际对话的窗口就越小。

这就引出了本文的主题——tool search。

Tool Search

tool search 是一个用于“按需加载工具定义”的内置工具。默认情况下,所有工具的完整定义(名字 + 描述 + JSON Schema)都会放进每次请求的 tools 数组里。tool search 改变了这个策略:只在系统提示里放一份工具名字清单,不放完整定义;当模型判断需要调用某个工具时,先通过内置的 toolSearch 工具加载该工具的完整 schema,下一轮再执行调用。

模型仍然知道有哪些工具——系统提示里列出了所有工具的名字。但每个工具的完整定义(描述 + JSON Schema)不会预先放进请求,而是等模型实际要调用时再加载。这样上下文占用就从全量 64k 降到了只加载工具名字清单的 3-5k,再加上按需加载的少数几个工具的 schema。

Deferred 机制

Deferred 直译是“延迟”。在 tool search 的语境下,deferred 工具就是被延迟加载的工具——它的完整定义(描述 + JSON Schema)不随请求发送,只在模型通过 toolSearch 主动请求时才加载进来。与之对应的是直接加载的工具(directly loaded),即每轮请求都带着完整 schema 的核心工具。

下面来看按需加载具体如何实现,主要有以下几个步骤:

第一步:客户端照常从 MCP server 全量拿到工具。 tools/list 接口还是全量返回。客户端启动时从每个 MCP server 拉到所有工具的完整定义——名字、描述、JSON Schema——全部存在本地内存里。这一步和没有 tool search 时完全一样。

第二步:只把工具名字告诉模型。 完整的工具 schema 不放进请求的 tools 数组。工具名字按 server 分组,写在系统提示的 ## Deferred Tools 段里。模型每轮都能看到这份名字清单,知道有哪些工具可用,但看不到每个工具接受什么参数。

下面是 x-code-cli 实际生成的 ## Deferred Tools 段(截取部分):

## Deferred ToolsThe tools below are available but NOT loaded — only their names are listed,with no schema. To use one, first call `toolSearch` (keyword search, or`select:<exact_name>` to load specific tools by name); its schema is thenadded to your tool set and it becomes directly callable on your next step.Core tools (readFile, writeFile, edit, shell, grep, glob, listDir, task)are always loaded — never search for those.### Built-in- webSearch, webFetch, todoWrite, activateSkill### Server: gate-mcp- mcp__gate__cex_spot_get_ticker, mcp__gate__cex_spot_create_order,mcp__gate__cex_spot_get_balance, mcp__gate__cex_futures_get_ticker,... (共 400 个工具)### Server: github- mcp__github__create_issue, mcp__github__list_pull_requests,mcp__github__merge_pull_request, ...

可以看到,这段文字包含两部分信息:一是使用说明(告诉模型怎么通过 toolSearch 加载工具),二是按来源分组的工具名字清单。模型每轮都能看到这份清单,但看不到任何工具的参数定义。

第三步:给模型一个内置的 toolSearch 工具。 当模型需要调用某个 deferred 工具时,它先调用 toolSearch,传入关键词或工具名。客户端在内存里的工具目录上做匹配,把命中的工具加入下一轮请求的 tools 数组。模型在下一轮就能像调任何普通工具一样调用它。

sequenceDiagramparticipant S as MCP Serverparticipant C as 客户端(MCP 客户端)participant LLM as LLMNote over S,C: 启动时C->>S: tools/listS-->>C: 400 个工具的完整定义Note over C: 全部存在内存里Note over C,LLM: 用户提问:&#34;帮我查一下 BTC/USDT 的最新价格&#34;C->>LLM: 核心工具 schema + toolSearch schema + deferred 名字清单Note over LLM: 看到名单里有 cex_spot_get_ticker,<br/>但没有它的 schema,无法直接调用LLM-->>C: toolSearch({query: &#34;select:mcp__gate__cex_spot_get_ticker&#34;})Note over C: 在内存目录里匹配,把这个工具加入 activated 集合C->>LLM: 核心工具 + toolSearch + cex_spot_get_ticker(刚激活的 schema)LLM-->>C: cex_spot_get_ticker({currency_pair: &#34;BTC_USDT&#34;})

要强调的一点:deferred 是客户端做的事。MCP 协议的 tools/list 依旧全量返回,协议本身没有按需发现的能力。deferred 机制完全是客户端在拿到全量工具列表之后,自行决定哪些放进请求、哪些延迟加载——这是客户端的策略,不是协议的功能。

模型只看到名字,怎么找到对的工具?

如果只提供工具名称、不附带对应的 Schema,模型无法明确该工具的入参结构。为了解决该问题,系统设计了多层校验保障机制。

工具名称自身已具备充足的语义描述能力。 mcp__github__create_issuecex_spot_get_ticker 这种名字,模型一看就知道是什么功能。MCP 工具名一般遵循 server__actionserver__resource__verb 的命名规范,名字本身就是一层语义索引。如果工具名全是 tool_001tool_002,这套方案就不成立了。

名单放在模型看得到的地方。 系统提示的 ## Deferred Tools 段按 server 分组列出了所有 deferred 工具名。模型不是盲目搜索——它看到了完整名单,只是没看到每个工具的参数定义。

客户端完成 toolSearch 的匹配运算。 toolSearch 支持两种调用方式:关键词搜索和精确匹配。

方式一:关键词搜索。 模型不确定工具的确切名字时使用。客户端拿 query 参数和每个工具的预处理文本做关键词匹配,按得分排序,返回前几个。

用一个例子来说明。假设 deferred 目录里有这几个工具:

工具目录(每个工具在注册时预处理好搜索文本):工具名搜索文本(名字拆词 + 描述 + schema 属性名)─────────────────────────────────────────────────────────────────────────mcp__gate__cex_spot_get_ticker gate cex spot get ticker 获取现货行情 currency_pairmcp__gate__cex_spot_create_order gate cex spot create order 创建现货订单 currency_pair side amount pricemcp__gate__cex_spot_cancel_order gate cex spot cancel order 取消现货订单 order_idmcp__github__create_issuegithub create issue 创建 issue title body labels

模型传入 toolSearch({query: "spot ticker"}) 后,客户端对每个工具打分:

query = "spot ticker"mcp__gate__cex_spot_get_ticker:"spot" → 名字拆词里有 "spot" → +10"ticker" → 名字拆词里有 "ticker" → +10总分 = 20 ✓mcp__gate__cex_spot_create_order:"spot" → 名字拆词里有 "spot" → +10"ticker" → 名字拆词里没有,搜索文本里也没有 → +0总分 = 10mcp__gate__cex_spot_cancel_order:"spot" → 名字拆词里有 "spot" → +10"ticker" → 名字拆词里没有,搜索文本里也没有 → +0总分 = 10mcp__github__create_issue:"spot" → 都没有 → +0"ticker" → 都没有 → +0总分 = 0结果(按分数降序):1. mcp__gate__cex_spot_get_ticker (20)← 返回2. mcp__gate__cex_spot_create_order (10)← 返回3. mcp__gate__cex_spot_cancel_order (10)← 返回4. mcp__github__create_issue(0) ← 过滤掉

Codex 的搜索逻辑类似,也是给每个工具预先生成搜索文本,但打分用的是 BM25 算法(信息检索领域的经典排序算法,搜索引擎用它来给搜索结果排序)。核心思路一样:拿 query 的关键词去匹配工具的搜索文本,按相关度排序。

两种方式都不需使用 embedding,也不需要调用模型,都是做的字符串匹配——同样的 query、同样的工具集,得到的结果完全一致。

另外,关键词搜索可能会同时命中多个工具。toolSearch 按打分排序取前 N 个(x-code-cli 默认 5 个),所有命中的工具都会被激活——加入 activated 集合,下一轮全部出现在 tools 数组里。模型拿到这几个工具的 schema 后,自己判断该使用哪个,客户端不会进行限制。

比如用户说“帮我下一个限价单买 BTC”,模型调用 toolSearch({query: "spot order"}),可能会同时命中 cex_spot_create_ordercex_spot_get_tickercex_spot_cancel_order 等多个工具。多激活几个工具的代价是每个工具多占一份 schema 的 token,但比全量注入 400 个工具要好得多。而且激活是持久的——一旦激活,后续轮次不用重复搜索同一个工具,可以直接使用。

方式二:select: 精确匹配。 模型直接传 select:工具名,客户端按名字精确查找,不需要打分。

模型怎么知道确切的工具名?因为系统提示的 ## Deferred Tools 段里列出了所有 deferred 工具的完整名字。模型每轮都能看到这份名单,只要它在名单里找到了目标工具名,就可以直接用 select: 加载。

什么时候用哪种方式?这个逻辑写在 toolSearch 工具自身的 description 里,模型在调用前会读到这段说明:

Pass `query` as either:- keywords describing the capability you need(e.g. "search the web", "github create issue")→ returns the best-matching deferred tools, or- "select:<name>,<name>" to load specific tools by theirEXACT name from the Deferred Tools list(prefer this when you already know the name).

也就是说工具描述直接告诉模型:如果已经知道名字,优先用 select:;不确定名字时,用关键词描述能力。 实际使用中,模型大多数情况会走 select:,因为它能直接从名单里看到工具名。关键词搜索是模型对工具名不确定时的备选路径——比如工具名比较长或者有多个相似工具时,模型可能用 "spot order" 这样的关键词让客户端帮它筛选。

两种调用方式对比:关键词搜索(不确定工具名时用):toolSearch({query: "spot ticker"})→ 客户端对所有工具做关键词打分,返回得分最高的几个精确匹配(已知工具名时用,推荐方式):toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"})→ 客户端直接按名字查找,找到就返回,不打分

关键词搜索是一层保底机制。模型大多数情况下能直接从名单里找到目标工具名,走 select: 精确匹配。只有当模型不确定具体用哪个工具时,才会退而使用关键词搜索让客户端帮它筛选。

哪些工具该 defer,哪些直接加载

不是所有的工具都需要 defer,一些核心工具会直接加载,而一些偶尔才用的工具才会被 defer。

直接加载的工具(每轮请求都带完整 schema):

这类工具是 Agent 的基础操作能力,用户随便问个问题都可能用到:

工具功能为什么必须直接加载
readFile读文件几乎所有编码任务都需要先读文件
edit编辑文件写代码的核心操作
writeFile写文件创建新文件
shell执行命令运行测试、安装依赖、git 操作
grep搜索代码定位代码位置
glob查找文件找文件路径
task子 agent委派子任务
toolSearch工具搜索加载 deferred 工具的唯一入口

延迟加载(defer)的工具:

  • 非核心内置工具:webSearch(联网搜索)、webFetch(抓取网页)、todoWrite(TODO 清单)。这些工具不是每个任务都会被用到,所以需要时再加载。
  • 所有 MCP 工具:MCP 工具由外部 server 提供,面向特定场景(交易、GitHub 操作、数据库查询等),不存在“每个任务都会用到”的 MCP 工具。

用一个具体例子来说明分类过程。假设 Agent 注册了以下工具:

分类示例:readFile→ 核心内置工具,每个任务都用→ 直接加载edit→ 核心内置工具,每个任务都用→ 直接加载shell → 核心内置工具,每个任务都用→ 直接加载toolSearch→ 加载入口,必须直接可用 → 直接加载webSearch → 非核心内置,偶尔才用 → deferwebFetch→ 非核心内置,偶尔才用 → defermcp__gate__cex_spot_get_ticker→ MCP 工具→ defermcp__gate__cex_spot_create_order→ MCP 工具→ defermcp__github__create_issue → MCP 工具→ defer...(其余 MCP 工具全部 defer

下面是判断逻辑的流程图:

完整循环:搜索 → 激活 → 调用

我们来看一个具体的示例,用“查 BTC 价格”来演示一遍实际的调用过程:

sequenceDiagramautonumberactor U as 用户participant C as 客户端participant LLM as LLMU->>C: &#34;帮我查一下 BTC 的现货价格&#34;C->>LLM: 用户消息 + 核心工具 + toolSearch + deferred 名字清单Note over LLM: 看到名单里有 cex_spot_get_tickerLLM-->>C: toolSearch({query: &#34;select:mcp__gate__cex_spot_get_ticker&#34;})Note over C: 精确匹配命中,加入 activated 集合C-->>LLM: tool_result: &#34;Loaded 1 tool(s) — now callable directly on your next step&#34;Note over C: 下一轮请求的 tools 数组追加 cex_spot_get_ticker 的完整 schemaC->>LLM: 核心工具 + toolSearch + cex_spot_get_ticker(已激活)LLM-->>C: cex_spot_get_ticker({currency_pair: &#34;BTC_USDT&#34;})Note over C: 执行 MCP 工具调用,拿到价格C->>LLM: 工具执行结果LLM-->>C: &#34;BTC 现货价格是 63,521.30 USDT&#34;C->>U: 展示回复

注意步骤 3 和步骤 5 之间:模型在第一轮调用了 toolSearch 工具,但不能在同一轮里调用 cex_spot_get_ticker——因为这个工具在第一轮的 tools 数组里还不存在。必须等客户端把它的 schema 加进去,下一轮模型才能调用。

这就是 deferred 方案的代价:首次使用某个 deferred 工具时,至少需要 2 轮 API 调用——先搜索,下一轮才能调用。

整个流程里 state 的变化

从启动到多轮对话,各个 state 字段的变化过程:

阶段state 变化
启动catalog = 从所有 MCP server 拿到的 400 个 deferred 工具的完整定义
activated = 空集合
systemPromptCache = 系统提示,包含 ## Deferred Tools 名字清单
baseTools = [readFile, edit, shell, grep, ..., toolSearch]
第 1 轮请求tools = composeTurnTools(baseTools, activated) = baseTools(activated 为空)
用户:“查 BTC 价格”发给 LLM:system + tools(9 个核心工具)+ messages
第 1 轮响应LLM 返回:toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"})
handleToolSearch() 在 catalog 里精确匹配命中
activated = { "mcp__gate__cex_spot_get_ticker" }
返回 tool_result 给模型:Loaded 1 tool(s) — now callable directly on your next step:
- mcp__gate__cex_spot_get_ticker: 获取现货行情
第 2 轮请求tools = [...baseTools, cex_spot_get_ticker 的完整 schema](尾部多了一个工具)
messages 里追加了上一轮的 tool_call + tool_result
发给 LLM:system + tools(10 个工具)+ messages,前缀缓存 miss 一次
第 2 轮响应LLM 返回:cex_spot_get_ticker({currency_pair: "BTC_USDT"})
客户端执行 MCP 工具调用,拿到价格。activated 不变
第 3 轮tools 同第 2 轮(activated 没变,前缀稳定,缓存命中)
LLM 返回文本回复
后续轮次activated 持久保留,不用再搜 cex_spot_get_ticker
如果激活新工具 → activated 增长 → tools 追加 → 再 miss 一次

几个关键点:

  • catalogsystemPromptCache 在启动时固定,整个 session 不变
  • activated 只增不减
  • tools 数组每轮动态合成 = baseTools + activated 对应的 schema
  • 前缀缓存只在 activated 增长的那一轮 miss,之后稳定

工程细节

前面讲的是 tool search 的核心流程。这一节主要讲一下几个实现过程中遇到的问题以及解决方法。

阈值判断:什么时候该启用 defer。 工具少的时候,全量注入更简单,也不需要多一轮往返。x-code-cli 用 DEFERRAL_THRESHOLD_PERCENT 做判断:把所有候选 deferred 工具的 schema 序列化后估算 token 量,如果不超过模型上下文窗口的 10%,就跳过 defer,退回全量加载。

模型兼容性。 tool search 依赖模型主动去调 toolSearch。如果模型不支持或不配合,deferred 工具就永远不会被加载。各家的处理方式不同:

  • Codex 的做法最精确:在 models.json 里按具体模型版本配置 supports_search_tool 布尔字段。只有明确标记为支持的模型才启用 tool search,未知模型默认关闭。
  • Claude Code 判断的是 API 能力而非模型智力:用 modelSupportsToolReference() 检查模型是否支持 tool_reference 协议块。默认排除列表只有 ['haiku'],但可以通过远程配置动态更新,不需要发版。
  • x-code-cli 目前用的是粗粒度的字符串匹配:WEAK_MODEL_PATTERNS = ['haiku', 'nano', 'glm-4v'],模型名包含这些子串就退回全量注入。这个方案的问题是区分不了同一系列的不同版本——比如 Claude 3 Haiku 和 Claude 3.5 Haiku 能力差距很大,但都会被一刀切地禁用。

系统提示的字节稳定性。 LLM API 的前缀缓存依赖请求前缀不变。system + tools 组成的前缀在后续请求中保持一致,就能命中缓存,按折扣价计费。这要求系统提示里的 deferred 名单不能在运行中动态修改。x-code-cli 的做法是在启动时生成 systemPromptCache 并冻结,整个 session 不再改动。

这带来一个副作用:工具激活后,系统提示里的名单仍然写着这个工具是 deferred 状态,但 tools 数组里已经有了它的完整 schema。模型看到的系统提示和实际的工具列表之间存在不一致:系统提示说工具未加载,但工具实际上已经在列表里了。

在实际使用中,这个不一致很少造成问题。强模型(Sonnet、GPT-4o 等级)在收到第一次 toolSearch 的 tool_result 后,会记住工具已经加载,后续直接调用而不会重复搜索。重复搜索只在极端情况下出现——比如对话轮次非常多、早期的 tool_result 被压缩掉了、模型又看到系统提示里写着"未加载"时,才可能再搜一次。x-code-cli 加了一层防御:如果搜索的工具已经在 activated 集合里,返回 Already loaded — call xxx directly now.,避免模型陷入搜索循环。这是防御性代码,不是常规路径。

Claude Code 和 Codex 从架构上避免了这种不一致。Claude Code 靠 Anthropic API 的 tool_reference 机制,激活的工具由服务端展开 schema,客户端不改 tools 数组。Codex 靠 OpenAI Responses API 的 tool_search_output 历史项,同样由服务端处理。两者都不需要在系统提示里维护一份可能过时的状态。x-code-cli 因为要跨多个 provider,没法依赖单一 API 的服务端能力,只能走客户端重注入——但从实际效果看,强模型下这种不一致几乎不会触发重复搜索。

子 agent 不开 defer。 子 agent 的工具集由白名单控制,大多数子 agent 只有几个内置工具。比如 explore 子 agent 只有 readFileglobgreplistDirshell 五个工具,plan 子 agent 更少,只有四个只读工具。工具这么少,加 tool search 没有意义。唯一的例外是 general-purpose 子 agent——它继承了父 session 的 MCP 工具,工具数量可能很多,但即便如此,子 agent 目前也走全量注入,因为子 agent 的生命周期短、轮次少,defer 带来的节省不明显。

总结

tool search 的完整流程回顾:启动时从 MCP server 全量拿到工具定义,按使用频率分成直接加载和 deferred 两类。直接加载的工具,完整 schema 放进每轮请求的 tools 数组;deferred 工具只在系统提示里列名字。模型需要某个 deferred 工具时,调 toolSearch 加载它的 schema,下一轮就能正常调用。

几个主流 AI Agent CLI 的实现对比:

全量注入?动态机制匹配算法
Claude CodeToolSearch 工具关键词打分 + select:
Codextool_search 工具BM25 全文检索
Gemini CLI
x-code-clitoolSearch 工具关键词打分 + select:

Claude Code、Codex、x-code-cli 三家的思路几乎一样,区别在于激活后的 schema 怎么对模型可用——Claude Code 和 Codex 靠各自 API 的服务端能力,x-code-cli 靠客户端重注入(直接把 schema 加进 tools 数组)。Gemini CLI 目前没有内置 tool search。

我的其他 AI Agent 文章

  • 让 AI Agent 系统自己发现 bug、自己提修复 PR:自我进化的 Harness
  • AI Agent 工程师入门指南
  • 如何从零开始实现一个 AI Agent CLI
  • 掘金小册--从零打造一个 AI Agent CLI

参考资料

  • Claude Code 源码
  • Codex 源码
  • x-code-cli 源码
  • MCP 协议规范

最新游戏

更多

Copyright©2010-2019. All rights reserved | 波波三国游戏官网|[email protected]

备案编号:湘ICP备2022015115号-4