AB
AiBoss站
教程

构建 Agent 控制平面:让工具调用从「能执行」变成「被授权」

教程

构建 Agent 控制平面:让工具调用从「能执行」变成「被授权」

Agent 能把模型输出变成动作,却常常答不上一个更关键的问题:这个动作到底被允许发生吗?本文拆解规划平面、控制平面、执行与观测平面三层结构,给出九步落地方法:收窄动作契约、分离身份与委派、把授权做成确定性决策、按后果分级、把审批绑定到规范化动作、防御提示注入、限定执行权限、独立验证结果、用账本记录决策轨迹。

大多数 Agent 技术栈已经非常擅长把模型输出变成一次动作:模型选对了工具、参数 JSON 语法合法、接口返回 HTTP 200、追踪面板上那个工具调用 span 是绿色的。但系统仍然可能取消了错误的账户。问题不在于「这个调用格式对不对」,而在于「这个动作到底有没有被授权发生」。

Schema 校验只告诉你输入是否成形;认证只告诉你请求是谁发来的;工具定义只告诉模型它可以请求什么。这些都不能证明:当前主体是否有权取消那个特定的订阅、那个特定的账户,也不能证明取消真的生效了。本文要讲的,就是如何为 Agent 系统搭一层控制平面,把「能力」和「权限」分开处理。这是一篇偏架构与工程实践的参考型教程,适合正在把 Agent 从演示推向生产、需要处理高风险动作的工程师与架构师阅读。

准备工作

控制平面不是一段提示词能解决的事,它需要一些前置条件先到位,否则后面九步都落不了地。

  • 一份动作清单。把系统当前所有会对外产生副作用的调用列出来,标注它改的是什么资源、影响范围有多大、能不能撤销。没有这份清单,后面的分级无从谈起。
  • 一个策略决策点(PDP)。可以是一个独立的策略服务,也可以是嵌在网关里的策略引擎,但必须是模型之外、模型改不动的组件。策略的输入是结构化的动作提案,输出是 allow / deny / approval_required 三态之一。
  • 一个执行代理(broker)。它是唯一持有高权限凭据的组件,规划层拿不到这些凭据。所有对外的写操作都从它这里出去。
  • 一套身份体系。至少要能区分三类身份:人类用户、Agent 自身、以及「人类委派给 Agent」的临时授权。三者不能混用同一个 token。
  • 一个不可篡改的账本。用于记录决策与结果的迁移,而不是事后从聊天记录里反推。聊天记录是可变的,不能当审计日志。
  • 一个验证通道。能够读取权威的后置条件或回执,用来判断动作到底有没有生效。

这些组件不一定一开始就齐全,但至少要清楚缺哪一块,以及缺的那块会让哪一类动作失去保护。

操作步骤

第一步:把动作契约收窄并且强类型化

核心原则是:不要因为模型会用,就暴露 http_request、run_shell、裸 SQL 或者不受限的浏览器这类通用工具。应该暴露的是有明确边界的业务级动词,例如 create_draft_invoice、queue_refund_review、send_approved_notice、revoke_session。

工具 schema 在这里的角色是接口契约:它减少自由形式的歧义,但它本身不授权任何动作。一个干净的 schema 应该同时声明以下字段:

字段作用
action_id 与 schema_version唯一标识动作及其契约版本,便于策略按版本匹配
side_effect_class取值如 read_only、draft、reversible_write、external、irreversible
required_scopes 与 allowed_environments声明所需权限范围与允许运行的环境
risk_tier 与 approval_mode风险等级与对应的审批方式
idempotency_requirement是否要求幂等键,以及幂等键的生成规则
verification_method动作完成后如何验证生效
data_classification涉及的数据分级与出站数据规则

主流模型厂商都支持结构化的工具输入与函数 schema,但它们的客户端执行模型也说明了同一件事:模型只是请求调用,真正执行工具的是应用侧。控制平面要利用的正是这个缝隙。

第二步:把认证、委派、审批拆成三件事

常见做法是用户登录一次,然后把用户的 token 直接交给 Agent,之后假定所有工具调用都已获授权。这会把一次临时请求变成环境权限(ambient authority),也让审计变得困难:到底是人做的、Agent 替人做的,还是某个共享系统身份做的?

正确做法是给 Agent 独立的、可归因的身份,而不是让它长期以通用服务账号运行,或者借用人类会话。需要把三个概念分开实现:

  • Agent 身份:每个 Agent 有独立身份,动作可归因到具体实例。
  • 人类身份:发起请求的真实用户。
  • 委派关系:人类把有限权限授予 Agent 的这段关系,包含范围与有效期。

OAuth 2.0 的设计思路在这里有参考价值:它通过授权层提供有限访问,而不是让第三方持有资源所有者的密码;访问令牌本身就承载了范围、有效期等属性。但要注意,OAuth 解决的是委派词汇的问题,它并不等于 Agent 治理——策略执行、验证、监控、撤销仍然要由控制平面自己完成。

第三步:让授权成为一个确定性决策

任何模型输出都不应该直接抵达有副作用的对外接口。正确的链路是:

  1. 模型产出结构化的 ActionProposal。
  2. 动作网关校验这个提案。
  3. 策略决策点返回 allow、deny 或 approval_required。
  4. 由执行代理(而不是规划层)拿到受限的、短期的执行权限去执行被允许的动作。

这条链路里有几个不能妥协的点:

  • 策略检查发生在 LLM 之外。
  • 决策做出之前,执行代理不得执行。
  • 如果需要审批,审批必须绑定到即将派发的那个规范化摘要上。
  • 派发返回 200 不等于成功,输出不能自动判定为成功。
  • 账本围绕「决策」与「结果」的状态迁移来写,而不是事后从聊天记录重建。

零信任架构的思路在这里同样适用:授权是动态的,策略决策可以综合考虑请求本身、身份与属性、资源要求以及上下文信号,而不是把一次登录当作永久信任。

第四步:按后果给动作分级

不应该把整个 Agent 笼统地归类为「全自主」或「人在回路」。这个分类属于单个动作和数据流,而不是整个 Agent。同一个 Agent 可以自动读取受限知识库、在预发环境创建草稿,同时在修改生产访问权限前要求双人复核。

分级模型可以按自己的场景调整,但升级规则值得固定下来:

  • 目标身份是推断出来的而不是用户明确选择的,升一级。
  • 动作受到不可信内容实质影响的,升一级。
  • 没有幂等机制、也没有权威验证路径的,升一级。
  • 范围变成跨租户、批量、对外、不可逆、涉及重大金额、涉及法律后果或生产关键的,升一级。
  • 永远不要因为模型表达了高置信度就自动降级。

动作是否由模型发起并不重要,重要的是这个动作的后果是否具有实质性。

第五步:把审批绑定到规范化动作

这一步必须具体,不能含糊。举例来说,用户写「请处理一下这个账单问题」,Agent 随后自行选择了收款方、取消原因、订阅金额和退款选项——这不是对实际效果的授权。

审批人必须看到即将派发的效果的规范化表示,而不是模型的自然语言意图。并且,一旦动作发生变化,原审批必须失效。这意味着审批记录需要包含以下字段:

字段说明
动作名称与 schema 版本明确审批针对的是哪个契约版本
租户、用户/主体、Agent 运行实例、执行者身份四类身份都要留痕
解析后的目标资源与关键参数不是模型原话,而是解析后的实际值
风险等级与要求审批的策略版本说明为什么需要这次审批
规范化动作与资源版本的摘要/哈希审批与派发必须比对同一个摘要
审批人身份、角色、决定、时间、有效期、理由完整的审批元数据
一次性 nonce/state防止审批被静默重放

这对界面也有要求:任何变更都要用业务语言描述清楚;界面需要突出收款方、目标、金额、范围、数据类型、环境和不可逆性;审批人必须能在有意义的决策边界上拒绝或修改,而不是在提供方已经接受请求之后才介入。

第六步:防御提示注入

不可信的网页、邮件、检索到的文档以及工具输出,不会因为模型能读它们就自动升级为指令。它们应被显式归类为不可信数据;必要时,先在一个无工具、无密钥的隔离步骤里处理,提取出事实之后,再交给有权限的 Agent 使用。

提示注入、用户意图歧义、上下文过期、凭据范围过宽,这四类问题都会把一个「合法的工具调用」变成错误的现实效果。OWASP 在 Agent 系统风险清单里明确列出了过度自主、高影响动作滥用、审批操纵、工具滥用、数据外泄和级联失败等条目。工程上的应对不能是「在提示词里再加一条指令」,而必须是当后果足够重要时仍然保持确定性的控制平面。

第七步:只发放窄范围、短时效的执行权限

执行代理不应该持有长期的高权限凭据。它应该按次换取窄范围、短时效的执行权限:只覆盖这一次动作、这一个资源、这一段时间。动作完成或超时后,权限立即失效。

这样做的直接好处是:即使某次决策被绕过,攻击者拿到的也只是一次性的、范围极窄的凭据,而不是可以横向移动的长期密钥。

第八步:独立验证结果

执行代理调用受限适配器或隔离 worker,资源服务或提供方执行动作,可能返回失败,也可能返回不确定的结果。此时必须由验证器读取权威的后置条件或回执,并把状态明确区分为:

  • pending:已派发,尚未确认。
  • unknown:结果不确定,需要人工或后续任务介入。
  • failed:明确失败。
  • verified:已通过权威来源验证生效。

把这四种状态混为一谈,是很多「看起来成功」的事故的根源。

第九步:用账本记录决策与效果轨迹

审计账本与遥测系统要保留的是决策—效果的完整轨迹,而不是把可变的聊天记录当作审计日志。账本应围绕状态迁移来写:提案产生、策略判定、审批发生、权限发放、派发、验证结果。每一步都有时间戳和主体身份,事后可以完整还原「为什么这个动作被允许」。

一个完整示例

把上面的步骤串起来,看一个取消订阅的场景。用户消息是:「请把那个不再使用的账户的订阅取消掉。」

规划平面:模型选择 cancel_subscription,从更早的对话里提取出一个账户 ID,产出结构化的动作提案:

{
  "action_id": "cancel_subscription",
  "schema_version": "3",
  "side_effect_class": "irreversible",
  "risk_tier": "high",
  "approval_mode": "explicit",
  "idempotency_requirement": "required",
  "verification_method": "provider_receipt",
  "target": { "account_id": "acct_8812" },
  "reason": "user_requested_unused_account"
}

控制平面:动作网关校验提案,策略决策点检查发起主体是否有权取消该租户下的这个账户。这里有一个关键判断——目标账户 ID 是从历史消息里推断出来的,而不是用户明确选择的。按照第四步的升级规则,这会把风险等级再抬一级,策略返回 approval_required。

系统生成规范化摘要,把目标账户、取消原因、生效时间等关键参数固定下来,交给审批人。审批人看到的是业务语言描述的实际效果,而不是模型的原话。审批通过后,记录里带上审批人身份、时间、有效期、一次性 nonce,以及被审批的那个摘要哈希。

执行与观测平面:执行代理按次换取窄范围、短时效的执行权限,调用受限适配器。提供方返回回执。验证器读取回执,确认该账户的订阅状态确实变为已取消,把状态从 pending 迁移到 verified。整个过程写入账本。

对比一下没有控制平面的版本:模型选对了工具、JSON 合法、接口返回 200、追踪面板全绿——但取消的可能是另一个账户。差别不在于模型能力,而在于有没有一层独立于模型的授权与验证。

注意事项

  • 审批不是普遍适用的。控制平面可以对低风险、只读、范围窄的动作直接授权;对后果重大但可逆的动作要求用户确认;对关键动作直接拒绝,让 Agent 去准备材料,交给人工操作手册执行。不要把所有动作都塞进同一个审批流程。
  • 审批必须绑定到同一个摘要。如果审批时看到的参数和派发时的参数不是同一个规范化摘要,审批就形同虚设。动作一旦变化,原审批立即失效。
  • 不要用模型置信度做降级依据。模型说「我很确定」不构成降低管控级别的理由。
  • 聊天记录不是审计日志。它是可变的,不能用来重建决策过程。账本要围绕决策与结果的状态迁移来写。
  • 200 不等于成功。派发返回成功只说明请求被接受,真正的生效与否要靠独立的验证通道确认。
  • 不可信内容不因可读而可信。网页、邮件、检索文档、工具输出都应显式标记为不可信数据,必要时先在无工具、无密钥的隔离环境里处理。
  • 身份不能混用。Agent 身份、人类身份、委派关系要分开实现,避免把临时请求变成环境权限。
  • 本文涉及的风险分级、审批模式、字段设计属于工程实践建议,具体实现方式与各平台的能力、配额、版本相关,请以相关产品官网的当前信息为准。本文不构成安全合规或法律层面的专业意见。