openinterpreter 中 tool_search 工具描述模板:Apps/Connectors 工具发现的 BM25 实现解析
2026/9/7 5:16:44 网站建设 项目流程

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

【免费下载链接】openinterpreterA coding agent for open models like Kimi K3 and GLM 5.3项目地址: https://gitcode.com/GitHub_Trending/op/openinterpreter

本文围绕 tool_description.md 这份tool_search工具描述模板展开:先逐句解读模板本身的提示词设计与{{app_descriptions}}占位符机制,再深入 ToolSearchHandler 与 create_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:

  • 按名称去重:以BTreeMap汇总来源名与描述,重复名称保留先出现的描述(测试 create_tool_search_tool_deduplicates_and_renders_enabled_sources 验证了"同名来源只取首个非空描述、输出保持字典序"的行为);
  • 512KB 描述预算:常量MAX_TOOL_SEARCH_SOURCE_DESCRIPTION_BYTES = 512 * 1024,渲染前先扣除来源名等固定开销,再逐个来源分配描述字节数;超预算的来源被跳过,且截断借助 take_bytes_at_char_boundary 在多字节字符边界处安全切割(测试 create_tool_search_tool_bounds_aggregate_source_descriptions 用 2 万个 🦀 字符构造了极端用例);
  • Omit 模式ToolSearchSourceListing::Omit时完全不渲染来源清单(当世界状态已在别处宣告了这些来源时启用),测试 create_tool_search_tool_omits_sources_when_world_state_advertises_them 断言此时描述中不再出现来源名。

这套预算控制保证了无论接入了多少应用/连接器,注入模型上下文的那段 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四种组合下的暴露结果。

性能侧,ToolSearchHandlerCache 用Mutex<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.rs 与 tests/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.rs 与 tool_search.rs 两个文件及其内嵌测试入手,再对照 codex-rs/core/templates/search_tool/ 下的两份模板,即可完整还原该功能的提示词面与实现面。

【免费下载链接】openinterpreterA coding agent for open models like Kimi K3 and GLM 5.3项目地址: https://gitcode.com/GitHub_Trending/op/openinterpreter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询