kimi-code 深度掌握系列文章-工具系统:从注册到执行(六)

1. 工具系统概述

ToolManager 是 Agent 与外部世界交互的桥梁。它位于 packages/agent-core/src/agent/tool/,是 Agent 类的核心成员之一。在一个 turn 的 loop 中,LLM 返回 tool_use 停止原因后,loop 会查询 ToolManager 获取可执行的工具集合,完成工具调用的分配与执行。

工具的来源有三种:

来源 ToolSource 说明
内置工具 'builtin' 框架自带的核心工具,如文件读写、Shell 执行、Web 搜索等。由 initializeBuiltinTools() 按依赖注入动态构建
用户自定义工具 'user' 通过 registerUserTool() 注册的外部工具,支持 inlinedeferred 两种披露模式
MCP 工具 'mcp' 通过 Model Context Protocol 从外部 MCP 服务器动态发现和注册的工具,使用 mcp__serverName__toolName 命名空间

无论是哪种来源,所有工具都遵循统一的抽象接口 --- BuiltinTool<Input>ExecutableTool<Input>,这使得 loop 层可以无差别地调度任何工具。

​设计哲学:​ ToolManager 不区分工具的来源 --- 内置、用户、MCP 工具在 loop 层面都是等价的 ExecutableTool 条目。来源标记仅用于 UI 展示和权限策略的差异化处理。

2. Tool 接口设计

2.1 ExecutableTool 核心契约

每个可执行工具必须提供三个基础字段和一个核心方法:

字段/方法 类型 说明
name string 工具的唯一标识名称,LLM 通过此名称发起调用
description string 工具的功能描述,会出现在 system prompt 的工具列表中
parameters Record 参数 的 JSON Schema(draft-7),描述 LLM 应如何构造调用参数
resolveExecution(args) (args: Input) => ToolExecution 核心方法:接收 LLM 传来的参数,返回 ToolExecution 对象

2.2 ToolExecution --- 执行前的准备

resolveExecution() 返回的 ToolExecution 对象是工具执行的关键中介,它包含:

  • accesses 声明该工具需要的资源访问(如文件路径)。权限系统据此做冲突检测和序列化。
  • approvalRule 权限规则的匹配模式,用于权限策略的决策链。
  • display UI 展示信息,如 { kind: 'file_io', operation: 'read', path }
  • execute(context) 实际的异步执行函数,接收 turnIdtoolCallIdsignal 等上下文。

2.3 参数 JSON Schema 的生成

工具使用 Zod schema 定义输入类型,然后通过 toInputJsonSchema() 转换为 LLM 可理解的 JSON Schema:

Typescript 复制代码
import { z } from 'zod';
import { toInputJsonSchema } from '../../support/input-schema';

export const ReadInputSchema = z.object({
  path: z.string().describe('Path to a text file...'),
  line_offset: z.number().int().min(1).optional()
    .describe('The line number to start reading from...'),
  n_lines: z.number().int().positive().optional()
    .describe('The number of lines to read...'),
});

// 转换为 draft-7 JSON Schema,使用 io: 'input' 视图
const parameters: Record<string, unknown> = toInputJsonSchema(ReadInputSchema);

关键细节:toInputJsonSchema 使用 zod 的 io: 'input' 模式确保带 .default() 的字段保持可选。同时自动为所有 object 节点添加 additionalProperties: false,防止拼写错误的参数被静默忽略。

2.4 序列化为 LLM function calling 格式

当 loop 构建 LLM 请求时,ToolManager.loopTools getter 返回当前可用的 ExecutableTool[]。Kosong 层的 generate() 会提取每个工具的 namedescriptionparameters 三个字段,序列化为 LLM provider 所需的 function calling 格式(OpenAI 兼容的 tools 数组)。

3. 内置工具全景

内置工具在 initializeBuiltinTools() 中按依赖注入动态构建。并非所有工具在每个 session 都可用 --- 它们的注册取决于 Agent 的配置和能力(如是否启用 cron、是否有 subagent host、是否支持图片/视频等)。

3.1 文件操作

工具名 类别 核心能力
Read 文件 读取文本文件,支持行偏移、负偏移(从尾部读)、行数限制(最多 1000 行)。自动探测文件类型,对图片/视频提示使用 ReadMediaFile。内部有 100KB 字节上限和 2000 字符/行截断
Write 文件 写入或覆盖文件。通过 Kaos 抽象层执行实际 I/O
Edit 文件 精确字符串替换编辑 --- 使用 old_stringnew_string 模式进行替换。old_string 必须在文件中唯一匹配,支持 replace_all 全局替换
Glob 文件 基于 glob pattern 的文件匹配搜索。按修改时间排序返回结果,支持分页(limit/offset)
Grep 文件 基于ripgrep 的内容搜索。支持正则表达式、文件类型过滤、上下文行(-A/-B/-C)、multiline 模式、大小写不敏感搜索
ReadMediaFile 文件 读取图片和视频文件。支持图片压缩(WebP 解码)、多模态输入。仅当模型支持 image_invideo_in 时注册

3.2 Shell

工具名 类别 核心能力
Bash Shell 执行 shell 命令。Windows 上使用 Git Bash,Linux/macOS 使用系统 bash。• ​沙箱模式 ​:通过 Kaos 抽象层执行,而非直接调用 node:child_process• ​后台执行 ​:run_in_background: true 将命令转为后台任务• ​超时管理 ​:前台默认 60s(最大 5min),后台默认 10min(最大 24h),支持 disable_timeout• ​用户中断 ​:支持通过 AbortController 取消运行中的命令(TUI 中 Esc/Ctrl+C)• ​自动后台 ​:前台命令超时时可自动转为后台(bashAutoBackgroundOnTimeout

​Kaos 抽象层:​ Bash 工具的核心强化在于 Kaos --- 一个跨平台的进程执行抽象。它提供了 execexecWithEnvspawn 等统一接口,使得沙箱模式的实现可以独立于操作系统。非沙箱模式下,Kaos 仍然提供进程生命周期管理(stderr 关闭后 SIGTERM → grace → SIGKILL 两阶段杀死)。

3.3 Web

工具名 类别 核心能力
WebSearch Web 网络搜索。依赖 toolServices.webSearcher 由 provider 注入。 支持多种搜索渠道(Moonshot provider 有专门实现)
FetchURL Web 网页抓取并处理为可读内容。内置 ​15 分钟自我清理缓存​:相同 URL 在 15 分钟内重复请求直接返回缓存结果,减少重复抓取

3.4 协作

工具名 类别 核心能力
Agent 协作 生成子 Agent(subagent)。传入 prompt 和 description,子 Agent 在隔离的上下文中执行任务。支持后台运行和超时控制
AgentSwarm 协作 并行多 Agent 协调。在 swarm 模式下允许多个子 Agent 并行处理任务
AskUserQuestion 协作 向用户提问。仅当 agent.rpc?.requestQuestion 可用时注册
Skill 协作 调用已注册的 Skill。仅当有可调用 Skill 时注册

3.5 状态管理

工具名 类别 核心能力
TodoList 状态 待办事项管理。基于 ToolStore 的键值存储,支持创建、更新、完成、删除任务项
TaskList 后台 列出所有后台任务及其状态(运行中/已完成/已失败)
TaskOutput 后台 获取指定后台任务的输出。支持流式读取和过滤
TaskStop 后台 停止指定的后台任务

3.6 定时任务

工具名 类别 核心能力
CronCreate 定时 创建定时任务。支持一次性(recurring: false)和重复执行。任务持久化到 sessionDir/cron/.json
CronList 定时 列出当前 session 的所有定时任务及其下次执行时间
CronDelete 定时 删除指定的定时任务

​定时任务的生命周期:​ 任务绑定到 session --- 恢复同一 session 时会重新加载已持久化的 cron 任务。停机期间错过的触发会被合并为单次投递(coalescedCount)。任务不会跨 session 继承。所有触发时间都经过 jitter 随机化处理,避免精确时间点的竞态问题。

3.7 规划与目标

工具名 类别 核心能力
EnterPlanMode 规划 进入规划模式。在此模式下 Agent 专注于分析和规划,限制某些工具的执行
ExitPlanMode 规划 退出规划模式,恢复正常执行流程
CreateGoal 目标 创建高层次的执行目标。仅 main agent 可用(agent.type === 'main'
GetGoal 目标 获取目标的状态和进度。仅 main agent 可用
UpdateGoal 目标 更新目标信息。仅 main agent 可用
SetGoalBudget 目标 为目标设置 token 或其他资源预算。仅 main agent 可用

3.8 工具发现 --- select_tools

工具名 类别 核心能力
SelectTools 发现 渐进式工具披露 的核心。LLM 按需加载 MCP 工具和 deferred 模式的用户工具。 工具 schema 从实时注册表读取并作为 role: 'system' 消息注入上下文,下一个 step 即可直接调用。

4. 工具注册与发现

4.1 注册表结构

ToolManager 内部维护了三张核心注册表:

typescript 复制代码
// 内置工具 --- 由 initializeBuiltinTools() 按 Agent 能力动态构建
protected builtinTools: Map<string, BuiltinTool> = new Map();

// 用户自定义工具 --- 通过 registerUserTool() 添加
protected readonly userTools: Map<string, ExecutableTool> = new Map();

// MCP 工具 --- 通过 registerMcpServer() 添加,附带服务器来源信息
protected readonly mcpTools: Map<string, McpToolEntry> = new Map();

// 按服务器名称索引 MCP 工具
protected readonly mcpToolsByServer: Map<string, string[]> = new Map();

此外,还有两个辅助集合用于工具可见性控制:

  • enabledTools​:​ 一个 Set<string>,存储当前启用的内置和用户工具名称。由配置文件的 profile 或 setActiveTools() 管理。
  • mcpAccessPatterns​:​ glob 模式数组(如 mcp__*mcp__github__*),控制哪些 MCP 工具对当前 session 可见。

4.2 工具按类别组织

从模块结构来看,工具按功能类别在源码中组织:

  • tools/builtin/file/ --- 文件操作类(read, write, edit, glob, grep, read-media)
  • tools/builtin/shell/ --- Shell 类(bash)
  • tools/builtin/web/ --- Web 类(web-search, fetch-url)
  • tools/builtin/collaboration/ --- 协作文类(agent, agent-swarm, ask-user, skill-tool)
  • tools/builtin/state/ --- 状态类(todo-list)
  • tools/builtin/planning/ --- 规划类(enter-plan-mode, exit-plan-mode)
  • tools/builtin/goal/ --- 目标类(create-goal, get-goal, update-goal, set-goal-budget)
  • tools/background/ --- 后台任务类(task-list, task-output, task-stop)
  • tools/cron/ --- 定时任务类(cron-create, cron-list, cron-delete)
  • tools/builtin/select-tools.ts --- 工具发现类

4.3 渐进式工具披露(Progressive Disclosure)

当工具集非常大时(特别是连接了多个 MCP 服务器后,可能有上百个工具),将所有工具的完整 schema 放入 system prompt 会显著增加 token 消耗。渐进式工具披露解决了这个问题:

  1. 核心内置工具始终在顶层 tools[]
  2. MCP 工具和标记为 deferred 的用户工具仅以名称列表的形式在 system context 中宣布
  3. LLM 需要时调用 select_tools 按名称加载具体的工具 schema
  4. 加载后的工具成为可调用的执行条目,但其 schema 作为历史消息存储而非重复出现在每个请求的 tools[]
  5. compaction 发生时,已被折叠的 schema 消息被丢弃,LLM 需重新选择仍需要的工具
Typescript 复制代码
// ToolManager.loopTools getter 中的披露逻辑
const disclosure = this.progressiveDisclosure;
const enabledMcpNames = [...this.mcpTools.keys()]
  .filter((name) => this.isMcpToolEnabled(name));

// 披露模式开启时只暴露已加载的动态工具
const loadedSet = disclosure ? this.loadedDynamicToolNames() : undefined;
const mcpNames = loadedSet === undefined
  ? enabledMcpNames
  : enabledMcpNames.filter((name) => loadedSet.has(name));

// select_tools 仅在披露模式下暴露
const selectToolsName = disclosure ? [b.SELECT_TOOLS_TOOL_NAME] : [];

4.4 性能考量

在大型工具集场景下(如连接了 GitHub、Slack、数据库等多个 MCP 服务器),工具 schema 的 prompt token 消耗是一个关键约束。以一个平均包含 500 tokens schema 描述的工具为例,100 个工具将消耗约 50,000 tokens 的 context 空间。渐进式披露将这一成本分散到按需加载的时机,显著降低了每个 turn 的固定开销。

5. MCP 工具集成

5.1 MCP 概述

MCP(Model Context Protocol)是一个开放协议,允许 AI 应用通过标准化接口连接到外部数据源和工具。在 kimi-code 中,MCP 集成由 packages/agent-core/src/mcp/ 模块实现。

5.2 工具命名空间

MCP 工具使用三段式命名解决多服务器间的冲突:

Typescript 复制代码
// 命名格式:mcp__serverName__toolName
// 各部分经过 sanitize 处理(非 ASCII 字符替换为 _,连续 _ 折叠)

export function qualifyMcpToolName(serverName: string, toolName: string): string {
  const full = `mcp__${sanitizeMcpNamePart(serverName)}__${sanitizeMcpNamePart(toolName)}`;
  if (full.length <= 64) return full; // 大多数 LLM provider 限制工具名为 64 字符
  // 超出时使用 FNV-1a 8 字符哈希后缀进行确定性截断
  const hash = stableHash8(full);
  const head = full.slice(0, 64 - hash.length - 1);
  return `${head}_${hash}`;
}

5.3 MCP 客户端架构

  • ​连接管理:​ McpConnectionManager 管理所有 MCP 服务器的生命周期 --- 连接、重连、断开、状态变更通知
  • ​OAuth 认证:​ 当服务器需要认证时,自动注入 mcp__auth__<serverName> 合成工具,引导用户完成 OAuth 流程
  • ​状态监听:​ ToolManager.attachMcpTools() 订阅 McpConnectionManager.onStatusChange,当服务器状态变更时动态注册/注销工具

5.4 MCP 工具的发现与注册

完整的注册流程如下:

kotlin 复制代码
// 当 MCP 服务器连接成功时
private registerConnectedMcpServer(mcp: McpConnectionManager, entry: McpServerEntry): void {
  const resolved = mcp.resolved(entry.name);
  
  // 1. 注册工具的 ExecutableTool 包装
  const result = this.registerMcpServer(
    entry.name,
    resolved.client,
    resolved.tools,
    resolved.enabledNames,  // 配置中指定的工具白名单
  );
  
  // 2. 记录工具发现事件(可观察性)
  this.recordMcpToolsDiscovered(
    entry.name,
    resolved.rawTools,      // 服务器返回的原始工具列表
    resolved.enabledNames,
    result.collisions,      // 命名冲突信息
  );
  
  // 3. 通知 UI 更新
  this.agent.emitEvent({
    type: 'tool.list.updated',
    reason: 'mcp.connected',
    serverName: entry.name,
  });
}

每个 MCP 工具被包装为一个 ExecutableTool,其 execute 闭包内部通过 client.callTool(toolName, args, signal) 调用远程 MCP 服务器:

typescript 复制代码
const wrapped: ExecutableTool = {
  name: qualified,
  description: tool.description,
  parameters: tool.parameters,
  resolveExecution: (args) => ({
    approvalRule: qualified,
    execute: async (context) => {
      const result = await client.callTool(
        tool.name,
        (args ?? {}) as Record<string, unknown>,
        context.signal,
      );
      // 将 MCP 结果转换为 ExecutableToolResult
      return mcpResultToExecutableOutput(result, qualified, {
        originalsDir: this.agent.mediaOriginalsDir,
        maxImageEdgePx: this.agent.imageLimits?.maxEdgePx(),
      });
    },
  }),
};

5.5 命名冲突处理

当多个 MCP 服务器提供同名工具时,系统记录冲突信息(McpToolCollision)并以先注册者为准。冲突信息通过事件和日志传递给用户,帮助诊断配置问题。

5.6 MCP 资源暴露

除了工具,MCP 服务器还可以暴露 Resources --- 静态或动态的数据源。通过 ListMcpResourcesReadMcpResource 两个 deferred tool(按需加载的工具),Agent 可以列出和读取 MCP 资源的内容。

6. 工具执行流程

一个完整的工具调用从 LLM 的 tool_use 响应到结果返回,经历以下阶段:

1

解析与参数提取

loop 从 LLM 响应中提取 ToolCall(包含 namearguments)。参数是 JSON 字符串,由 loop 层的 preflight 进行 JSON 解析。

2

参数验证(Zod Schema)

解析后的参数通过工具的 Zod schema 进行验证。loop 层的 tool-call.ts 在执行前使用 AJV(JSON Schema validator)对参数进行校验。未通过验证的参数会返回错误。

3

权限检查

调用 PermissionManager 的策略链。每个工具通过 resolveExecution() 返回的 approvalRuleaccesses 参与权限决策。策略按优先级依次评估:模式策略(auto/yolo/manual)→ 路径访问策略 → 用户配置规则 → 默认/fallback 策略。

4

并发冲突检测(ToolAccesses)

ToolAccesses 声明了工具所需的资源(如文件路径)。同一批次中,如果两个工具声明了冲突的资源访问(如同一文件的读写),loop 会序列化它们的执行。

5

执行沙箱(Kaos)

工具通过 execute(context) 执行实际操作。文件 I/O 通过 Kaos 抽象层进行,确保跨平台兼容性和沙箱安全。Bash 命令通过 Kaos 的进程执行接口运行。

6

结果格式化

工具返回 ExecutableToolResult,包含 output(LLM 可见的内容)、isError(是否错误)、note(model-only 侧通道元数据)、message(用户可见提示)等字段。

7

执行时间统计

loop 记录每个工具的执行耗时,用于 UI 展示和性能监控。

typescript 复制代码
// ExecutableToolResult 的定义
export interface ExecutableToolSuccessResult {
  readonly output: ExecutableToolOutput;           // LLM 可见的输出
  readonly isError?: false | undefined;
  readonly stopTurn?: boolean | undefined;         // 是否停止当前 turn
  readonly message?: string | undefined;           // 用户侧提示
  readonly note?: string | undefined;              // model-only 元数据
}

export interface ExecutableToolErrorResult {
  readonly output: ExecutableToolOutput;
  readonly isError: true;
  readonly telemetry?: Record<string, unknown>;
}

export type ExecutableToolResult =
  | ExecutableToolSuccessResult
  | ExecutableToolErrorResult;

​result-builder 工具类:​ Bash 等复杂工具使用 ToolResultBuilder 辅助构建结果。它支持流式输出拼接(stdout/stderr 流式更新回调)、结构化渲染(renderAs{Text,Structured,Error}()),以及内容截断保护(默认上限 30,000 字符)。

7. 扩展:添加自定义工具

7.1 通过配置添加用户工具

用户工具通过 ToolManager.registerUserTool() 注册:

kotlin 复制代码
// ToolManager.registerUserTool() 的实现
registerUserTool(input: UserToolRegistration): void {
  const { name, description, parameters } = input;
  const tool: ExecutableTool = {
    name,
    description,
    parameters,
    resolveExecution: (args) => ({
      approvalRule: name,
      execute: async (context) => {
        // 通过 RPC 将调用转发到外部处理
        return this.agent.rpc!.toolCall!(
          {
            turnId: Number(context.turnId),
            toolCallId: context.toolCallId,
            args,
          },
          { signal: context.signal },
        );
      },
    }),
  };
  this.userTools.set(name, tool);
  if (input.disclosure === 'deferred') {
    this.deferredUserTools.add(name); // 渐进式披露
  }
  this.enabledTools.add(name);
}

用户工具支持两种披露模式:

  • inline​(默认):​ 工具始终出现在顶层 tools[] 中,LLM 可以直接调用
  • deferred​:​ 工具仅以名称形式宣布,LLM 需通过 select_tools 按需加载 schema

7.2 通过插件系统添加工具

插件(Plugin)是一种更高级的扩展机制。插件可以在 Agent 初始化时通过 injection/plugin-session-start.ts 注入 session 启动逻辑,其中包括注册自定义工具。插件注册的工具遵循与内置工具相同的 ExecutableTool 接口。

7.3 工具开发最佳实践

原则 说明
幂等性 工具的 execute 应尽可能幂等。LLM 可能因为各种原因重试同一个工具调用
超时处理 长时间运行的操作应支持通过 context.signal(AbortSignal)取消,并在取消后做适当清理
错误处理 返回明确的 ExecutableToolErrorResult 而非抛出未捕获异常。错误信息应包含足够的上下文帮助 LLM 自我修正
描述质量 description 字段应清晰描述工具的功能和使用场景。这是 LLM 决定何时使用该工具的唯一信息来源
参数验证 使用 Zod schema 定义输入,通过 toInputJsonSchema() 生成 JSON Schema。在每个字段上使用 .describe() 提供清晰说明
权限声明 resolveExecution() 中正确声明 accessesapprovalRule,确保权限系统能正确决策
结果结构 输出应结构化且易于 LLM 理解。使用 note 字段传递 model-only 元数据,使用 message 字段传递用户可见的消息
披露策略 不常用的工具建议使用 deferred 模式,减少 prompt token 消耗

总结

kimi-code 的工具系统是一个设计精良的抽象层。它通过统一的 ExecutableTool 接口屏蔽了三种工具来源的差异,通过 Zod schema + JSON Schema 的双重验证保证了参数安全,通过 Kaos 抽象层实现了跨平台的执行隔离,通过渐进式工具披露解决了大量工具时的 prompt token 消耗问题。

ToolManager 不是简单的工具列表管理器 --- 它是工具生命周期(注册、发现、披露、执行、注销)、权限决策的前置协调、MCP 动态集成的统一编排中心。理解它的架构,是深入掌握 kimi-code 关键的一步。

相关推荐
今日无bug1 小时前
用 Prompt 做 NLP 任务开发:几分钟构建一个推理系统
llm·nlp
jsl_jsl_jsl1 小时前
claudecode学习 第 9 章 · Subagents
agent
lbzlbss1 小时前
AI 自动化测试流水线实战(二):Analyst 把 PRD 变 L0/L1 用例套件
agent
鱼日先生1 小时前
Dify 中级实验(04):迭代进阶——如何批量处理数据并守住性能边界?
agent·工作流·dify
circuitsosk2 小时前
不止于API调用:大模型推理加速与云原生服务化部署指南
python·云原生·agent·vllm·推理加速·大模型部署·ensorrt-llm
AINative软件工程2 小时前
LLM Streaming 背压工程实践:慢客户端、断流重连与服务端缓冲区的生产设计
node.js·llm
@Mr_LiuYang3 小时前
《深入理解 AI Agent:设计原理与工程实践 》实验1-2 深度搜索能力
人工智能·agent
苏灿烤鱼3 小时前
今日 GitHub 热门|Agent 记忆重回榜首,+2,690 项目却只排第三
typescript·agent·资讯
苏灿烤鱼3 小时前
GitHub #2 拆解|把工程经验装进 Agent,为什么仍会“静默失效”?
javascript·人工智能·agent