首页
/ openinterpreter 中 tool_search 工具描述模板:Apps/Connectors 工具发现的 BM25 实现解析

openinterpreter 中 tool_search 工具描述模板:Apps/Connectors 工具发现的 BM25 实现解析

2026-09-06 19:58:00作者:彭桢灵Jeremy

本文围绕 tool_description.md 这份 tool_search 工具描述模板展开:先逐句解读模板本身的提示词设计与 {{app_descriptions}} 占位符机制,再深入 ToolSearchHandlercreate_tool_search_tool 的源码实现,说明描述文本如何被动态渲染、BM25 检索引擎如何构建、搜索结果又如何转化为下一次模型调用可用的工具规格。读完本文,你可以完整理解本项目"延迟加载工具 + 关键词检索发现"这一工具发现机制从提示词到执行链路的落地方式。

1. 模板定位:一段注入给模型的"工具发现说明书"

模板文件 codex-rs/core/templates/search_tool/tool_description.md 全文如下:

# Apps (Connectors) tool discovery

Searches over apps/connectors tool metadata with BM25 and exposes matching tools for the next model call.

You have access to all the tools of the following apps/connectors:
{{app_descriptions}}
Some of the tools may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required tools and load them for the apps mentioned above. For the apps mentioned above, always use `tool_search` instead of `list_mcp_resources` or `list_mcp_resource_templates` for tool discovery.

它不是给人看的说明文档,而是作为 tool_search 工具的 description 文本注入到模型上下文中的一段提示词。逐句拆解其设计意图:

  • 标题 # Apps (Connectors) tool discovery:限定这份描述服务于 Apps/Connectors(应用/连接器)场景下的工具发现,与 MCP 通用工具发现相区分。
  • 第一句功能声明:明确说明该工具"基于 BM25 对工具元数据做检索,并把命中的工具暴露给下一次模型调用"。这一句同时交代了检索算法(BM25)与结果用途(供下一轮调用加载使用),让模型知道调用它不会立即执行工具,而是"解锁"工具。
  • {{app_descriptions}} 占位符:运行时会被当前已启用的应用/连接器清单及描述替换,告诉模型"这些应用的工具你都可能用到"。
  • "部分工具可能没有提前提供给你":解释了延迟加载(deferred loading)策略——工具元数据不会一次性全部塞进上下文,而是按需检索加载,以此控制 token 开销。
  • "对上述应用始终用 tool_search 而不是 list_mcp_resources / list_mcp_resource_templates 做工具发现":这是一条硬性路由规则,避免模型绕过检索直接枚举 MCP 资源,保证发现路径统一走 BM25。

与 MCP 通用场景对应的描述由 Rust 代码动态生成,见 tool_search_spec.rs

let description = format!(
    "# Tool discovery\n\nSearches over deferred tool metadata with BM25 and exposes matching tools for the next model call.{source_section}Some of the tools may not have been provided to you upfront, and you should use this tool (`{TOOL_SEARCH_TOOL_NAME}`) to search for the required tools. For MCP tool discovery, always use `{TOOL_SEARCH_TOOL_NAME}` instead of `list_mcp_resources` or `list_mcp_resource_templates`."
);

两者结构几乎同构:都是"功能声明 + 来源清单 + 延迟加载提示 + 路由规则"四段式。从源码结构看,模板文件承载的是"来源为 Apps/Connectors"的变体,而 Rust 侧 create_tool_search_tool 负责通用 MCP 场景及来源清单的渲染,两者共享同一套提示词骨架。

同目录下还有一份姊妹模板 request_plugin_install_description.md,定义 request_plugin_install 工具的使用约束:仅当用户明确要求某个已知未安装的插件/连接器、且 tool_search 不可用或已尝试但未命中时才允许调用,并给出完整五步工作流(先检查活跃工具列表 → 对照已知清单精确匹配 → 插件优先于连接器 → 携带 tool_type/action_type/tool_id/suggest_reason 参数发起安装请求 → 安装完成后决定继续搜索还是放弃)。它与 tool_search 构成互补关系:tool_search 解决"已启用来源里找不到工具",request_plugin_install 解决"工具根本还没安装"。

2. 工具 Schema:query 必填、limit 默认 8

tool_search 的参数定义同样在 create_tool_search_tool 中:

let properties = BTreeMap::from([
    ("query".to_string(),
     JsonSchema::string(Some("Search query for deferred tools.".to_string()))),
    ("limit".to_string(),
     JsonSchema::number(Some(format!(
         "Maximum number of tools to return. Defaults to {default_limit}."
     )))),
]);
  • query(string,必填):检索词,对应 JSON Schema 的 required: ["query"]
  • limit(number,可选):返回工具数量上限,默认值来自常量 TOOL_SEARCH_DEFAULT_LIMIT,其值为 8
pub const TOOL_SEARCH_TOOL_NAME: &str = "tool_search";
pub const TOOL_SEARCH_DEFAULT_LIMIT: usize = 8;

参数在运行时的校验逻辑见 ToolSearchHandler::handle_call

  • query 去除首尾空白后为空 → 返回 RespondToModel("query must not be empty") 错误,让模型自行修正;
  • limit == 0 → 返回 RespondToModel("limit must be greater than zero")
  • 无匹配来源时直接返回空的 ToolSearchOutput,不报错。

这种"把错误回传给模型而不是崩溃"的设计,使模型可以在同一会话内自我修正参数。

3. BM25 检索引擎:从 search_text 到 SearchEngine

tool_description.md 中"with BM25"这句声明的实现在 ToolSearchHandler

pub(crate) fn new(
    search_infos: Vec<ToolSearchInfo>,
    source_listing: ToolSearchSourceListing,
) -> Self {
    let search_source_infos = search_infos
        .iter()
        .filter_map(|search_info| search_info.source_info.clone())
        .collect::<Vec<_>>();
    let spec = create_tool_search_tool(
        &search_source_infos,
        TOOL_SEARCH_DEFAULT_LIMIT,
        source_listing,
    );
    let documents: Vec<Document<usize>> = search_infos
        .iter()
        .map(|search_info| search_info.entry.search_text.clone())
        .enumerate()
        .map(|(idx, search_text)| Document::new(idx, search_text))
        .collect();
    let search_engine =
        SearchEngineBuilder::<usize>::with_documents(Language::English, documents).build();
    // ...
}

关键点:

  1. 每个可检索工具(ToolSearchInfo)都携带一段 search_text 字段,它即是被索引的"文档"。BM25 引擎以英文语言配置构建(Language::English),文档 ID 就是该工具在 search_infos 数组中的下标;
  2. 检索时 search 方法search_engine.search(query, limit) 拿到按相关性排序的文档 ID,再映射回 ToolSearchEntry 中的 outputLoadableToolSpec),最后经 coalesce_loadable_tool_specs 合并——同一命名空间(如 mcp__calendar)下命中的多个工具会被归并为一个 LoadableToolSpec::Namespace,减少重复的工具声明。单元测试 mixed_search_results_coalesce_mcp_namespaces 验证了 MCP 工具与动态命名空间工具混合命中时的归并结果,并确认每个工具都带 defer_loading: Some(true) 标记。

4. 来源清单渲染:去重、512KB 预算与按字符边界截断

模板里的 {{app_descriptions}} 对应的来源清单渲染逻辑在 tool_search_spec.rs

这套预算控制保证了无论接入了多少应用/连接器,注入模型上下文的那段 description 都不会失控增长——这正是"延迟加载 + 按需检索"策略能成立的前提之一。

5. 暴露控制与缓存:何时模型才能看到 tool_search

tool_search 是否对模型可见由配置开关控制。mcp_tool_exposure.rs 中的逻辑根据 search_tool_enabled 布尔值决定工具暴露策略(search_tool_enabledtrue 时走"经检索发现"的暴露路径),其配套单测文件 mcp_tool_exposure_test.rs 覆盖了 apps_enabledsearch_tool_enabled 四种组合下的暴露结果。

性能侧,ToolSearchHandlerCacheMutex<Option<Arc<ToolSearchHandler>>> 缓存已构建的检索器:当新的 search_infossource_listing 与缓存完全一致时直接复用(避免每轮对话重建 BM25 索引),任一字段变化则重建。单测 cache_reuses_handler_for_identical_search_infos_and_rebuilds_for_changes 验证了"相同输入复用同一 Arc、修改一条 search_text 即触发重建"的行为。端到端集成测试位于 tests/suite/search_tool.rstests/suite/request_plugin_install.rs,后者同时验证了"必须先耗尽 tool_search 才允许请求安装"的约束。

6. 小结:一条完整的工具发现链路

把模板与源码串起来,tool_search 的完整链路是:

  1. 描述注入:模板/代码生成的 description(含来源清单与 BM25 声明)进入模型上下文,模型据此知道"哪些应用的工具需要检索才可用";
  2. 参数校验query 非空、limit > 0(默认 8),错误以可回传模型的方式返回;
  3. BM25 检索:对每个工具的 search_text 建立英文 BM25 索引,按相关性取前 limit 个;
  4. 规格归并:命中工具按命名空间合并为 LoadableToolSpec,携带 defer_loading 标记暴露给下一轮调用;
  5. 兜底协同:检索不到时,若用户显式要求某个已知插件/连接器,走姊妹模板约束下的 request_plugin_install 安装流程。

这一机制的本质,是用"提示词声明发现规则 + 客户端 BM25 检索 + 延迟加载标记"三层配合,让模型在工具数量膨胀时无需背负全部工具 schema 的上下文成本,同时保留确定性的发现路径。若要进一步阅读,建议从 tool_search_spec.rstool_search.rs 两个文件及其内嵌测试入手,再对照 codex-rs/core/templates/search_tool/ 下的两份模板,即可完整还原该功能的提示词面与实现面。

登录后查看全文
热门项目推荐
相关项目推荐