Skip to content

第10章 工具系统

"工具安全的最难问题不是'如何阻止恶意调用',而是'如何区分一次危险但正确的操作和一次安全但错误的操作'——前者应该放行,后者应该拦截,而 LLM 给你的信号往往不足以可靠区分两者。"

本章要点

  • 理解 Agent 工具的核心悖论:能力越强,风险越大
  • 掌握三层工具抽象与七层策略防御管线
  • 深入浏览器自动化、进程执行、Web 搜索等核心工具实现
  • 理解工具系统与上下文引擎的协同关系

前九章,我们构建了 OpenClaw 的完整骨架:Gateway 调度全局、Provider 连接模型、Session 管理记忆、Agent 编排决策、通道对接用户、插件开放生态。骨架已成,但 Agent 仍然只是一个"能说会道的嘴"——它能生成文字,却无法触碰真实世界。

工具系统,就是给 Agent 装上的双手。

10.1 Agent 工具的核心悖论

什么区分了 AI Agent 和聊天机器人?答案看似简单:行动的能力。聊天机器人生成"我可以帮你搜索"的文本;Agent 真正打开浏览器、输入查询、返回结果。一个是纸上谈兵,一个是真刀真枪。

但这种简单性掩盖了一个困扰所有 Agent 框架的设计难题:如何让 AI 安全地可扩展地智能地调用系统能力?

考虑最朴素的方案:将每个操作系统能力暴露为 LLM 可调用的函数。这在演示中行得通,但在生产环境中必然崩溃。没有权限控制,Agent 可能因误解指令而执行 rm -rf /;没有路由智能,用户要搜索它却去生成图片;没有并发管理,并行工具调用耗尽系统资源。能力越大,责任越大——也越需要精密的管控。

好的工具系统不是给 Agent 更多的刀——而是一套完整的刀具管理体系:哪些刀放在台面上,哪些锁在柜子里,谁有钥匙,什么情况下可以用。

🔥 深度洞察:工具是 Agent 的"宪法修正案"

从政治学的视角看工具系统,你会获得一个全新的理解。LLM 的基础能力(文本生成)就像宪法原文——庄严但抽象。每一个工具的添加,就像一条宪法修正案——它赋予 Agent 新的权力(执行命令、浏览网页、操作设备),但同时必须有对应的约束条款。美国宪法第二修正案赋予公民持枪权,但配套了无数的联邦和州法律来规范这一权力的行使。OpenClaw 的工具系统遵循完全相同的逻辑:exec 工具赋予 Agent 执行 Shell 命令的能力(强大的"武器"),但七层策略管线确保这一能力在严格约束下行使。没有约束的能力不是能力,是隐患。

10.1.1 如果没有工具系统会怎样?

让我们做一个思想实验。假设 OpenClaw 没有专门的工具系统,而是像早期的 LangChain 那样,让开发者自己将函数注册到 LLM 的工具列表中。会发生什么?

第一个问题是安全真空。 没有统一的策略层,每个工具自行决定"谁能调用我"。浏览器工具可能有 URL 白名单,但 exec 工具完全不设防。安全变成了一个个孤岛——有的工具很安全,有的工具裸奔,整体安全性取决于最弱的那一环。

第二个问题是配置爆炸。 运营者需要为每个工具单独配置权限、超时、资源限制。10 个工具需要 10 套配置。50 个工具需要 50 套。配置的复杂度与工具数量线性增长,维护成本很快超过工具本身的价值。

第三个问题是上下文浪费。 没有统一的输出格式控制,每个工具返回自己认为合理的格式——浏览器返回完整 HTML(50K token),搜索返回原始 JSON(30K token),一次工具调用就可能耗尽整个上下文窗口。Agent 变成了只能使用一次工具的"一次性工人"。

OpenClaw 的工具系统正是对这三个问题的系统性回应。它不是简单的函数注册表,而是一个完整的工具治理框架——在安全、配置和效率三个维度上提供统一的解决方案。

10.1.2 工具系统的设计目标

在深入实现之前,让我们明确工具系统要解决的核心需求:

  1. 安全隔离:不同利益相关者(平台运营者、Agent 开发者、模型提供商)能独立控制工具访问,且安全策略可组合。
  2. 上下文效率:工具的输出必须经过压缩和格式化,使之适合有限的上下文窗口。
  3. 配置简洁:运营者能用一行配置(如 tools.profile: coding)获得一组合理的工具默认值。
  4. 扩展透明:添加新工具不需要修改安全系统;修改安全策略不需要理解工具实现。
  5. 优雅降级:工具不可用时(如浏览器未安装),系统平滑降级而非崩溃。

这五个目标之间存在张力。例如,安全隔离要求细粒度控制,但配置简洁要求粗粒度抽象。工具系统的架构设计本质上是在这些张力之间寻找平衡。

关键概念:工具策略管线(Tool Policy Pipeline) 工具策略管线是 OpenClaw 工具安全的核心机制——在任何工具进入 LLM 的可用工具列表之前,它必须通过多达七个独立的过滤阶段(Agent 级、Owner-Only、通道级、Provider 级、沙箱级等)。每个阶段可以独立允许或拒绝工具,且修改安全策略不需要修改工具实现,反之亦然。

10.2 工具架构:三层抽象

工具系统不是一个扁平的函数列表。它在三个层次上运作,每层解决不同类别的问题。

第一层:工具目录src/agents/tool-catalog.ts)。所有工具的主注册表——文件操作(readwriteedit)、运行时操作(execprocess)、Web 操作(browserweb_searchweb_fetch)、媒体操作(imagettspdf)和会话操作(sessions_spawnsessions_yield)。每个工具属于一个分区(section),并声明参与哪些配置文件(profile)。

第二层:工具策略管线src/agents/tool-policy-pipeline.ts)。安全性的核心。在任何工具进入 LLM 的意识之前,它通过多达七个独立过滤阶段——配置文件策略、提供商策略、全局策略、Agent 级策略和群组策略(策略管线的详细架构如图 6-3 所示,安全模型详见第13章)。每个阶段可以允许、拒绝或对任何工具保持沉默。管线遵循"最后一个明确意见胜出"的语义。

第三层:工具执行(各 src/ 模块)。每个工具的实际实现——用于浏览器自动化的 Playwright、用于命令执行的 Node.js child_process、用于网页搜索的 HTTP 客户端等。

图 10-1:工具系统三层架构

这种分离至关重要。目录是静态且全面的。管线是动态且安全感知的。执行层是模块化且可独立测试的。改变安全策略永远不需要修改工具实现,添加新工具永远不需要理解策略系统。

⚠️ 注意exec 工具的默认安全模式为 "allowlist"——只有在白名单中的命令才会被自动执行。如果你需要 Agent 执行任意命令(如开发环境),可以将安全模式设置为 "full",但务必确保 Agent 仅面向受信任的用户。在面向公众的 Agent 上使用 "full" 模式是严重的安全风险。

10.2.1 为什么是三层而不是两层?

一个自然的疑问:为什么不把目录和策略合并为一层?答案在于关注点的独立演化速度

工具目录的变更频率较低——当 OpenClaw 添加新工具(如 canvasfeishu_doc)时才变化,这可能是几周一次。策略管线的变更频率中等——运营者调整安全配置时变化,可能是每天。工具执行的变更频率最高——Bug 修复、性能优化、API 适配,几乎每次提交都涉及。

如果目录和策略耦合,每次添加新工具都要重新审视安全策略的组合逻辑。如果策略和执行耦合,每次修改浏览器自动化的细节都可能意外改变安全行为。三层分离让每一层按自己的节奏演化,互不干扰。

10.2.2 工具目录的数据模型

让我们看看工具目录的核心数据结构(src/agents/tool-catalog.ts):

typescript
// 工具的核心定义——每个工具是一个静态声明
type CoreToolDefinition = {
  id: string;              // 工具唯一标识
  label: string;           // 显示名称
  description: string;     // 功能描述
  sectionId: string;       // 所属分区(fs/runtime/web/media/...)
  profiles: ToolProfileId[];  // 属于哪些配置文件
  includeInOpenClawGroup?: boolean;  // 是否归入 OpenClaw 工具组
};

这个定义有几个微妙的设计决策:

sectionId 的分区设计。11 个分区(fsruntimewebmemorysessionsuimessagingautomationnodesagentsmedia)不是按技术实现分的,而是按用户心理模型分的。运营者不会想"我要允许使用 Playwright 的工具",而是"我要允许 Web 相关的工具"。分区对齐了运营者的思维方式,让配置更直觉。

profiles 的多重归属。一个工具可以属于多个配置文件。例如 web_search 同时属于 codingfull 配置文件。这意味着切换配置文件不需要手动调整个别工具——配置文件是工具子集的有名字的快捷方式。

includeInOpenClawGroup 的插件边界标识。这个布尔值标记哪些工具在策略过滤中应该被视为"核心 OpenClaw 工具"而非"插件提供的工具"。这影响当 allowlist 中出现未知条目时的降级行为——如果 allowlist 仅包含插件工具名,系统会剥离该 allowlist(让所有核心工具保持可用),而非错误地禁用所有核心工具。

10.3 策略管线:七层防御

如果你只能理解整个工具系统的一个机制,那应该是策略管线。这是 OpenClaw 安全态势的执行点——它的设计揭示了关于 Agent 安全的深刻洞察。

10.3.1 问题的本质

不同利益相关者需要不同级别的工具控制。平台运营者想全局禁止 sessions_spawn(防止远程代码执行)。Agent 配置想将某个 Agent 限制为只读工具(数据分析场景)。模型提供商策略想根据模型能力限制工具(某些模型不支持工具调用)。群组管理员想在特定 Discord 群里禁用 exec(安全考虑)。

传统的做法是把所有这些规则塞进一个巨大的 if-else 树。但这种方案有致命缺陷:规则之间会产生意外的交互效应。当运营者的全局禁止和 Agent 的局部允许冲突时,谁赢?当模型提供商策略和群组策略都对同一个工具有意见时,优先级怎么定?

10.3.2 管线方案

OpenClaw 的回答是:独立过滤阶段的有序管线,每个阶段有自己的作用域和真实来源:

typescript
// src/agents/tool-policy-pipeline.ts(简化)
const stages = [
  { policy: profilePolicy,         label: "tools.profile" },
  { policy: providerProfilePolicy, label: "tools.byProvider.profile" },
  { policy: globalPolicy,          label: "tools.allow" },
  { policy: globalProviderPolicy,  label: "tools.byProvider.allow" },
  { policy: agentPolicy,           label: "agents.X.tools.allow" },
  { policy: agentProviderPolicy,   label: "agents.X.tools.byProvider.allow" },
  { policy: groupPolicy,           label: "group tools.allow" },
];

管线的执行语义简洁但强大:每个阶段独立决定允许或拒绝某些工具。阶段按顺序执行,后面的阶段可以覆盖前面的决策。通过所有七个阶段的工具对 LLM 可用。在任何阶段被阻止的工具从 LLM 的工具目录中移除——模型甚至不知道它的存在

这最后一点至关重要。被策略拒绝的工具不是"显示为不可用",而是从 LLM 的世界中彻底消失。LLM 不能请求使用它不知道存在的工具,这从根本上消除了模型试图绕过工具限制的可能性。

10.3.3 为什么是七层而不是三层?

七层看起来过度设计了?让我们通过一个具体场景理解为什么每一层都不可或缺。

想象这个场景:运营者部署了一个 OpenClaw 实例,同时服务于三个 Agent——coder(编码助手)、researcher(研究助手)和 support(客户支持)。

  • 配置文件层coder 使用 coding 配置文件(包含 execbrowser),support 使用 messaging 配置文件(只有 messagetts)。这一层决定了每个 Agent 的"能力天花板"。
  • 提供商配置文件层researcher 使用 DeepSeek,它不支持工具调用。这一层确保不向不支持的模型发送工具定义。
  • 全局策略层:运营者全局禁止 sessions_spawn,因为这是远程代码执行向量。这一层实施"绝对不可逾越的红线"。
  • 全局提供商策略层:对所有使用 OpenAI 的 Agent,禁止 browser(因为 OpenAI 的使用条款限制)。
  • Agent 策略层coder 只允许在 ~/projects/ 下操作文件。这一层实施每个 Agent 的最小权限原则。
  • Agent 提供商策略层coder 在使用低端模型时禁止 process(防止长时间运行的命令浪费 token)。
  • 群组策略层:在 Discord 的 #general 频道中禁用 exec(公共频道安全考虑),但在 #dev 频道中允许。

七层,每一层回答一个独立的问题,没有任何两层的关注点完全重叠。这就是为什么不能简化到三层——那样必然会把不同关注点混在一起,导致配置变得混乱。

10.3.4 未知 Allowlist 条目的智能处理

管线中有一个容易被忽视但极其重要的细节:当 allowlist 包含未知的工具名时会怎样?

typescript
// src/agents/tool-policy-pipeline.ts — 未知 allowlist 处理
const resolved = stripPluginOnlyAllowlist(policy, pluginGroups, coreToolNames);
if (resolved.unknownAllowlist.length > 0) {
  // 区分两种情况:
  // 1. 已知核心工具但当前不可用(受限于运行时/提供商/模型/配置)
  // 2. 完全未知的条目(可能是拼写错误或未启用的插件)
  const gatedCoreEntries = resolved.unknownAllowlist.filter(isKnownCoreToolId);
  const otherEntries = resolved.unknownAllowlist.filter(e => !isKnownCoreToolId(e));
  // ...
}

基于 VitePress 构建