全书目录

第四编 系统层

第 19 章 动作空间与工具接口:Agent 到底能做什么

8 分钟 3,580 字 第 20 / 161 个阅读单元

对 Agent 来说,“能力”这个词太抽象。 真正具体的是动作空间。

动作空间指的不是模型理论上能生成多少种句子,而是系统实际上允许它发起哪些动作、以什么参数发起、动作会产生什么效果、失败后会留下什么状态、以及这些动作在什么边界内可被调用。换句话说,动作空间是 Agent 对外部世界的可操作面。

这一定义一旦成立,很多问题就会立刻清楚起来。

一个系统如果只能回答问题、不能触发外部变化,它更像解释器;一个系统如果可以查数据库、点网页、发邮件、改工单、执行代码、调用支付、创建审批、操作桌面,它才真正进入了行动系统的范围。于是,“Agent 到底能做什么”这个问题,归根到底不是问模型有多聪明,而是问系统把多大的世界控制权交给了它。

所以,动作空间首先决定能力边界,其次决定风险边界。

如果动作空间过窄,系统就只会解释,不会推进任务;如果动作空间过宽,却没有约束、验证和审批,系统就会迅速进入高风险区。很多团队早期只关注第一点,担心 Agent 能做的太少;真正上线以后,往往才发现第二点更要命,因为一旦动作具有副作用,错误就不再停留在文本层,而会变成真实的外部世界变化。

这也是为什么今天主流平台都在把工具调用做成显式原语,而不是让模型自由输出“我现在去做某事”。函数调用、OpenAPI action group、MCP tool、browser automation、computer use、code interpreter、shell execution,本质上都在做同一件事:把原本模糊的“做事能力”压缩成一组受约束的动作接口。

从更早的经典理论看,这并不新鲜。

无论是 STRIPS 里的 operator,还是 PDDL 里的 action,核心都是一样的:动作必须有前置条件、参数、效果和状态更新语义。现代 Agent 的工具接口虽然换成了 JSON Schema、OpenAPI 或平台 SDK,但本质仍是 operator 的工程化版本。一个工具之所以可被系统调用,不是因为模型会说它的名字,而是因为这个动作已经被明确写成契约。

所以,一个成熟的工具接口,至少要把七件事说清楚。

第一,动作名称是什么。

第二,动作目的是什么。

第三,输入参数的结构是什么。

第四,输出结果的结构是什么。

第五,失败信号与错误类型是什么。

第六,权限等级和可调用条件是什么。

第七,副作用与外部后果是什么。

前五项通常比较容易被团队接受,因为它们看起来像技术接口设计;真正最容易被低估的是后两项,尤其是副作用说明。因为在真实生产环境里,一个动作调用从来不只是“取点信息”,很多时候它会真正改变外部世界。

它可能会写数据库,发客户消息,创建工单,提交审批,改配置,触发自动化流程,执行命令,甚至完成支付或发布内容。到了这一步,动作就不再是语言行为,而是外部世界行为。只要团队没有把这一点写进系统设计,风险就会以一种非常隐蔽的方式累积起来:表面上看是模型“输出了一段文本”,实际上系统已经接受那段文本作为操作指令去改变世界了。

因此,动作空间设计的核心,不是把工具接得越多越好,而是把动作定义得越清楚越好。

这里的“清楚”至少包含三层。

第一层,是语义清楚。

Agent 必须知道这个动作究竟是“查询”还是“修改”,是“建议”还是“执行”,是“读操作”还是“写操作”,是“幂等动作”还是“一次性动作”。如果这些语义在接口层不清楚,模型就很难形成稳定的动作选择策略,系统也很难在风险上分级治理。

第二层,是参数清楚。

动作空间的很多错误并不是选错了工具,而是参数填错了、漏填了、单位错了、目标对象错了、依赖顺序错了。正因为如此,结构化输出、严格 schema、参数校验和 tool-result 配对协议才会在各家平台里被反复强调。模型的自由语言能力很强,但越接近执行层,就越不能依赖自由语言来承载动作细节。

第三层,是后果清楚。

一个动作一旦成功,会产生什么状态变化;一旦失败,会留下什么中间状态;如果动作部分成功,系统能否识别;如果动作不可逆,系统是否需要审批;如果动作幂等性不足,重试会不会造成重复副作用。这些问题如果不前置考虑,动作空间就只是“表面可调用”,不是真正可运行。

所以,本章更准确的说法应该是:

text
动作空间不是工具清单,而是系统允许 Agent 影响世界的方式集合。

只要这样理解,很多平台设计就变得非常自然。为什么 function calling 要用 schema 描述参数?因为动作必须可验证。为什么 tool result 必须带着对应的 call id 回填?因为动作不是一句话,而是一次有身份、有结果的执行事件。为什么 computer use 要把截图、鼠标、键盘、命令动作拆开?因为 GUI 世界也必须被离散化成一组可操作原语。为什么有些平台支持 long-running tools、pause、resume、approval?因为很多动作不是瞬时完成,而是带时长、带等待、带人工门控的过程。

从控制与强化学习角度看,动作空间还有另一个常被忽略的维度:层级。

并不是所有动作都应该是原子动作。很多时候,真正有用的不是“向左点击一次”“再输入一个字符”这种极细粒度控制,而是更高层的 option、skill 或子任务,比如“获取客户资料”“完成一次排障检查”“执行一次安全审批”“提交一次代码修改并跑测试”。这是经典层级控制理论早就强调过的事情。Agent 如果被迫只在过低层级上思考,动作序列会变长,错误面会变大,规划成本也会飙升。

于是,好的动作空间往往是分层的。

底层动作解决原子控制问题,中层动作解决领域工具问题,高层动作解决任务步骤问题。很多现代 Agent 系统中的 manager-worker、planner-executor、subagent-handoff,本质上都在做动作空间的层级化重组,而不只是角色分工。

但分层并不自动带来安全,反而会带来新的接口问题。

因为一旦动作开始多步串联,系统就不再只需要考虑“单个工具调得对不对”,而要考虑整条动作轨迹是否正确。一个动作的输出可能变成下一个动作的输入;一个局部参数错误可能在第三步才表现为严重后果;一个中途失败如果没有被显式标记,后面动作可能会在错误状态上继续执行。这就是为什么 NESTFUL、ToolHop、ToolSandbox、AppWorld、WebArena 这类 benchmark 都在强调 trajectory correctness,而不是只看单轮函数调用是否格式正确。

因此,动作空间的真正难点从来不在“是否支持工具”,而在“是否支持正确执行”。

这又把我们带回工具接口设计本身。一个生产级动作接口,不应该只暴露“入口”,还要暴露:

  • 这个动作能否并行执行
  • 这个动作是否需要前置确认
  • 这个动作是否允许自动重试
  • 这个动作失败是否可恢复
  • 这个动作执行过程中是否会产生中间状态
  • 这个动作是否需要沙箱环境
  • 这个动作是否必须在用户上下文权限内执行

如果这些属性不被写入接口层,系统后面就只能靠 prompt 临时猜测,而这恰恰是最不稳定的做法。

再看一个最常见、也最容易被低估的动作:“发送消息”。

从技术上看,它可能只是一个 send_message 工具;从业务后果看,它可能完全不是同一个动作。发一条普通提醒,也许风险很低;发一条对客户作出承诺的消息,风险显著更高;发一条面向公众的公告,又是另一种级别。也就是说,动作空间不能只按技术类型划分,还必须按业务后果分层。否则,同样一个“发送动作”会把低风险通知和高风险承诺混在一起,最终让系统无法治理。

这正是“最小权限”在 Agent 里变得特别重要的原因。

一个成熟系统不应让 Agent 拥有抽象的“发送能力”“写入能力”“执行能力”,而应让它拥有被业务语义切开的、被权限边界限制的具体能力。例如:只能给当前工单相关责任人发内部提醒,不能对外发送;只能创建草稿,不能直接提交;只能生成支付建议,不能调用支付;只能在只读数据库执行查询,不能改表;只能在沙箱里跑代码,不能直接进入生产环境。动作空间越强,这种切分越重要。

于是,动作空间设计最终会落到一个非常工程化的闭环:

先定义动作,再定义 schema;先定义 schema,再定义权限;先定义权限,再定义执行回路;先定义执行回路,再定义失败处理、审批节点、审计记录和补偿路径。没有这个闭环,所谓“工具调用”就只是把风险从语言层搬到了执行层。

所以,第 19 章最终要建立的结论是:

text
Agent 能做什么,不由模型的想象力决定,而由系统为它声明、约束、验证并执行的动作接口决定。

动作空间越清晰,Agent 的能力就越稳定;动作接口越结构化,系统就越容易评测;权限和副作用边界越明确,系统就越安全;层级和协议设计越好,Agent 就越可能在真实环境中完成任务,而不是只在 prompt 里显得很会做事。