跳到主要内容

04 · 工具与 agent 能力

这章讲 Chatbox 从“聊天框”升级成“agent”的那一层:它能给模型挂哪些工具、按什么规则挂、以及模型不支持原生工具时怎么办。

4.1 它要解决的小问题

现代模型能调工具(function calling):模型说“我要 web_search('xxx')”,程序执行后把结果喂回去,模型再继续。Chatbox 要做的是:

  • 根据这个模型支不支持工具、会话开没开某功能,动态决定给它哪些工具;
  • 把每个工具的“使用说明”拼进 system prompt;
  • 给老模型(不支持工具)准备降级方案

4.2 工具的全家福

工具集给模型的能力门控条件定义文件
web_search / parse_link联网搜索、抓取网页正文会话开联网 + 模型支持 web-browsingtoolsets/web-search.ts
read_file / search_file_content读取/搜索用户上传的大文件有内联文件/链接 + 模型支持 read-filetoolsets/file.ts
sandbox_bash/read/write/edit/grep/ls/find在沙箱里跑 shell、改文件沙箱开启(第6章)toolsets/sandbox.ts
query_knowledge_base 等向量检索知识库选了知识库 + 模型支持 knowledge-basetoolsets/knowledge-base.ts
session-attachment-rag检索本会话上传的超大附件附件标记为 session-retrievaltoolsets/session-attachment-rag.ts
MCP 工具外部 MCP server 暴露的任意工具server 运行中packages/mcp/controller.ts
load_skill / execute_skill_script加载技能说明、跑技能脚本会话启用了某些 skilltools-builder.ts

4.3 主线:buildToolsForSession

所有拼装集中在一个函数 buildToolsForSession(model, options)(session/tools-builder.ts:77)。它返回 { tools, instructions }——前者给 streamText,后者拼进 system prompt。

核心是逐个能力门控(tools-builder.ts:83-92):

// 示意,非源码:工具不是全开,而是“模型支持 + 会话开启”才给
const needFileToolSet = hasInlineFileOrLink && model.isSupportToolUse('read-file')
const kbSupported = knowledgeBase && model.isSupportToolUse('knowledge-base')
const webSupported = webBrowsing && model.isSupportToolUse('web-browsing')

注意 isSupportToolUse 带了作用域参数('read-file'/'web-browsing'/'knowledge-base')。这让供应商可以更细地声明“我支持工具,但不支持某类”(src/shared/models/types.ts:16,各子类如 deepseek.ts:65openai-compatible.ts:42 重写)。

MCP 工具是无条件并入的(只要 server 在跑)(tools-builder.ts:129-131):

let tools: ToolSet = { ...mcpController.getAvailableTools() }

4.4 一个工具长什么样:web_search

工具就是 AI SDK 的 tool({ description, inputSchema, execute })。看 web_search(toolsets/web-search.ts:25):

export const webSearchTool = tool({
description: 'Search the web … Use short, concise queries (English preferred).',
inputSchema: z.object({ query: z.string().describe('the search query') }),
execute: async (input, { abortSignal }) =>
await webSearchExecutor({ query: input.query }, { abortSignal }),
})

两个细节:

  • description 即 prompt:它直接告诉模型“能答就别搜”,这是降低无谓工具调用的廉价手段。
  • parse_link 抓正文有计费/授权分支:Chatbox AI 内建路径要 license key,第三方(如 Tavily)走各自 provider,内容截断到默认 12000 字(web-search.ts:36-103)。

4.5 文件工具:大文件的“预览 + 按需读取”

read_file/search_file_content(toolsets/file.ts)解决“文件太大塞不进上下文”的问题。配套约定写在工具说明里(file.ts:40-61):

  • 文件 ≤500 行 → 全文已内联在 <FILE_CONTENT>,别调工具,直接读。
  • 文件 >500 行 → 只内联前 100 行预览 + 一个 <TRUNCATED> 标记,要更多内容才调 read_file(带行号,像 cat -n)。

read_file 默认返回 200 行、上限 500 行,超长行截到 2000 字符加 ...(file.ts:6-20)。这套“预览 + 工具补读”的协议在第5章(上下文构建)有对应实现。

4.6 精华:不支持工具的模型怎么办(prompt 工程降级)

不是所有模型都支持 function calling。Chatbox 的兜底叫 legacy tool fallback(session/legacy-tool-fallback.ts:12)。

思路:程序先替模型把活干了,再把结果伪装成一次“已完成的工具调用”塞进上下文。 例如模型不支持知识库工具,就先用 prompt 工程让模型生成一个检索 query,程序去检索,把结果构造成一个 state: 'result'tool-call 片段(legacy-tool-fallback.ts:36-46):

// 示意,非源码:把“程序代跑的检索”伪装成一次工具调用结果
fallbackToolCallPart = {
type: 'tool-call', state: 'result',
toolName: 'query_knowledge_base',
result: searchResults,
}

这条 fallback 片段会作为流处理器的初始 state注入(回到 orchestration.ts:219),于是 UI 上看起来和原生工具调用一模一样。这让“弱模型”也能享受 RAG / 联网。

4.7 MCP:把外部工具接进来

MCP(Model Context Protocol,模型上下文协议——一种让外部进程向模型暴露工具的标准)在 Chatbox 里由 mcpController(packages/mcp/controller.ts:117)管理。两种传输:

  • stdio:本地子进程,经 IPC 桥到 main 进程跑(浏览器侧不能 spawn 进程,见 ipc-stdio-transport.ts)。
  • http:优先 Streamable HTTP,失败回退到 SSE(controller.ts:34-60)。

工具名会被规范化成 mcp__<server>__<tool>(controller.ts:222-228),且执行被包了一层 try/catch 返回而非抛出(controller.ts:204-214)——注释明说“返回而非抛出,否则会导致流程中断”,即一个 MCP 工具失败不该让整轮生成崩掉。

4.8 Skills:渐进式加载的技能

Skills 是“一段可被加载的指令 + 可选脚本”。拼装时(tools-builder.ts:159-213):先把启用技能的 name + description<available_skills> XML 注入 system prompt;若模型支持工具,再给两个工具 load_skill(按名加载完整指令正文)和 execute_skill_script(跑技能目录里的脚本)。

这是 Anthropic “渐进式披露”思路的实现:先只给目录,模型要用哪个才加载全文,省 token。技能本体(文件/脚本)在 main 进程,经 skillsController 的 IPC 调用(packages/skills/controller.ts)。

4.9 边界

  • 工具门控强依赖 capabilities 元数据;自定义供应商若勾错能力,会出现“给了工具但模型不会用”或反之。
  • legacy fallback 只覆盖 知识库 / 联网两类,不覆盖 file/sandbox/MCP。

4.10 代码地图

主题文件符号
工具拼装总入口src/renderer/stores/session/tools-builder.tsbuildToolsForSession
能力门控判定src/shared/models/types.tsisSupportToolUse(ToolUseScope)
联网工具src/renderer/packages/model-calls/toolsets/web-search.tswebSearchTool / parseLinkTool
文件工具src/renderer/packages/model-calls/toolsets/file.tsreadFileTool / searchFileTool
沙箱工具src/renderer/packages/model-calls/toolsets/sandbox.tssandbox_bash
知识库工具src/renderer/packages/model-calls/toolsets/knowledge-base.tsqueryKnowledgeBaseTool
老模型降级src/renderer/stores/session/legacy-tool-fallback.tsapplyLegacyToolFallback
MCP 控制器src/renderer/packages/mcp/controller.tsmcpController / normalizeToolName
技能控制器src/renderer/packages/skills/controller.tsskillsController