从模型输出“我想调用工具”,到运行时安全、可恢复、可审计地执行动作:理解 Agent 工程的协议层
| 阶段 | 系统真正处理的对象 | 关键问题 | 工程边界 |
|---|---|---|---|
| 模型决策 | 模型输出的工具名、参数草案、调用理由 | 这是建议还是命令? | 模型只能提出意图,不能直接拥有执行权 |
| 协议校验 | Schema、类型、枚举、必填字段、权限策略 | 参数是否可解析、合法、完整? | 校验失败应进入修复、重问或拒绝,不应硬执行 |
| 执行路由 | 工具注册表、租户权限、环境、凭据、限流 | 该由哪个工具、哪个账号、哪个环境执行? | 执行身份必须来自系统授权,而不是来自模型文本 |
| 状态推进 | 任务状态机、步骤日志、检查点、重试计数 | 这一步成功、失败、待确认还是可重试? | 长任务不能只靠对话历史隐式记状态 |
| 结果回灌 | 工具返回、错误码、结构化观察、脱敏结果 | 模型下一步能看到什么? | 工具结果要最小暴露、可引用、可追踪 |
| 审计治理 | Trace、审批记录、风险标签、成本和责任边界 | 事后能否还原为什么执行? | 没有审计的自治执行不适合进入生产 |
Tool Calling 不是“模型调用函数”,而是“模型生成一个可校验的调用意图,运行时决定是否、如何、由谁执行”。这条线一旦模糊,模型幻觉、提示注入、权限越界和副作用失控就会混在一起。
| 对象 | 可以由模型生成吗? | 必须由系统决定吗? | 原因 |
|---|---|---|---|
| 工具名 | 可以建议 | 必须从注册表匹配 | 防止模型编造不存在或越权工具 |
| 参数值 | 可以生成 | 必须校验和规范化 | 自然语言生成容易漏字段、类型漂移或注入 payload |
| 执行凭据 | 不应该 | 必须 | 凭据属于运行时和权限系统,不属于上下文文本 |
| 是否高风险 | 可以辅助判断 | 必须按策略判定 | 风险等级不能完全交给概率模型 |
| 是否提交写入 | 可以提出 | 必须由策略、状态机或人类确认 | 副作用动作需要责任边界和审计链 |
{
"name": "create_support_ticket",
"description": "为已认证用户创建客服工单。只在用户明确要求提交时使用。",
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["user_id", "category", "summary", "priority"],
"properties": {
"user_id": { "type": "string", "description": "系统认证后的用户 ID,不从用户文本猜测" },
"category": { "type": "string", "enum": ["billing", "bug", "account", "other"] },
"summary": { "type": "string", "minLength": 8, "maxLength": 200 },
"priority": { "type": "string", "enum": ["low", "normal", "urgent"] },
"idempotency_key": { "type": "string", "description": "由运行时生成,用于避免重复创建" }
}
},
"risk": "write",
"requires_confirmation": true
}| Schema 元素 | 作用 | 常见坏味道 | 更稳妥的做法 |
|---|---|---|---|
| name | 工具唯一标识,供模型选择和运行时路由 | 名字过泛,如 execute、do_task | 用动词 + 业务对象,如 search_docs、update_invoice_status |
| description | 告诉模型何时使用、何时不要使用 | 只写功能,不写边界和禁用条件 | 包含触发条件、风险提示、典型输入和反例 |
| parameters | 约束参数结构、类型、枚举和必填项 | 大量自由文本字段,缺少 enum 和范围 | 把业务约束变成可校验字段,减少自然语言猜测 |
| additionalProperties | 控制模型是否能塞入未知字段 | 默认放开,导致隐藏注入和字段漂移 | 生产写入工具通常关闭未知字段 |
| risk metadata | 标记读、写、外发、执行、敏感等级 | 风险只写在文档里,运行时不可读 | 把风险作为工具注册表的一等字段 |
| confirmation policy | 声明是否需要人工确认或二次校验 | 靠模型自己判断要不要确认 | 由策略引擎结合用户、工具、参数和环境决定 |
对模型来说,Schema 是“如何表达动作”的语言;对运行时来说,Schema 是“哪些动作允许被解析和执行”的合同。好的工具 Schema 会降低模型出错率,也会降低系统被注入和误调用的风险。
| 问题 | 表现 | 处理策略 | 不要做什么 |
|---|---|---|---|
| 字段缺失 | 缺少 user_id、date_range、target_id 等关键字段 | 向用户澄清、从系统上下文补齐、或拒绝执行 | 让模型凭空猜 ID、日期或权限对象 |
| 类型漂移 | 数字变字符串、数组变文本、枚举值拼错 | Schema 强校验、规范化、有限自动修复 | 直接把未校验参数传给 API |
| 歧义参数 | “最近”“那个客户”“高优先级”没有确定对象 | 引入解析层或让用户确认候选对象 | 用第一个搜索结果当真实目标 |
| Prompt 注入 | 参数里夹带“忽略前面规则”“导出全部数据” | 字段级过滤、权限校验、工具结果隔离 | 把外部文本当系统指令拼回上下文 |
| 结构化输出破损 | JSON 不闭合、混入解释文本、漏字段 | 使用原生结构化输出、重试修复、降级到人工 | 用脆弱正则从长文本里硬抠动作 |
| 路由维度 | 它决定什么 | 工程实践 |
|---|---|---|
| 任务类型 | 搜索、读取、写入、代码执行、浏览器操作还是数据库查询 | 按能力域拆工具组,避免一个万能工具承担所有动作 |
| 用户与租户 | 用户能看什么、改什么、代表谁执行 | 工具调用前做服务端权限校验,不信任模型声明 |
| 环境 | 生产、沙箱、预览、只读副本、测试数据 | 高风险动作先跑 dry-run 或预览环境 |
| 成本与延迟 | 调用昂贵工具、外部 API 或长时间任务是否值得 | 设置预算、超时、批量限制和降级路径 |
| 数据敏感级别 | 返回结果是否脱敏、裁剪或禁止进入模型上下文 | 把数据分类和脱敏放在工具包装层 |
| 风险策略 | 是否需要确认、审批、双人复核或禁止自治 | 策略引擎读取工具元数据、参数和会话风险 |
| 状态 | 进入条件 | 允许动作 | 退出条件 |
|---|---|---|---|
| Planning | 用户目标和上下文已收集 | 模型规划、工具候选、澄清问题 | 生成可校验的下一步动作 |
| Ready | 工具名存在、参数通过结构校验 | 业务校验、风险打标、生成 dry-run | 通过策略,或进入确认 / 拒绝 |
| Waiting | 需要人类确认、外部依赖或配额释放 | 展示影响范围、收集批准、取消任务 | 批准、拒绝、超时或转人工 |
| Running | 执行权已授予 | 调用工具、写检查点、更新进度 | 成功、失败、超时或被取消 |
| Observing | 工具返回结构化结果 | 脱敏、摘要、决定下一步或结束 | 进入下一轮 Planning,或进入终态 |
| Failed | 不可恢复错误、策略拒绝、重试耗尽 | 记录原因、补偿、转人工 | 人工接管或新任务重开 |
Agent 一旦涉及多步工具调用,就不再是“多轮聊天”。状态机能回答当前卡在哪里、是否能重试、是否能取消、是否需要补偿、是否还允许模型继续规划。
| 失败类型 | 例子 | 能否重试 | 处理方式 |
|---|---|---|---|
| 模型输出失败 | 工具选错、参数 JSON 破损、遗漏字段 | 通常可以 | 带错误信息重试、缩小工具集、要求澄清 |
| 校验失败 | 枚举非法、对象不存在、权限不足 | 视情况 | 规范化、重新生成、让用户选择对象或拒绝 |
| 工具瞬时失败 | 网络超时、429、外部服务 5xx | 可以 | 指数退避、限次重试、Fallback、保留检查点 |
| 业务冲突 | 库存变化、版本冲突、重复提交 | 通常不可盲重试 | 重新读取状态、比较版本、请求用户确认 |
| 副作用不确定 | 请求超时但对方可能已经写入成功 | 不能简单重试 | 查询幂等键、对账、进入人工或补偿流程 |
| 策略拒绝 | 越权、敏感数据、危险命令 | 不应重试同一动作 | 拒绝、解释边界、提供安全替代路径 |
| 主题 | Agent 场景里的问题 | 工程策略 |
|---|---|---|
| 并发 | 多个 Agent 或用户同时修改同一对象 | 乐观锁、版本号、资源锁、队列串行化 |
| 事务 | 跨多个工具的动作无法天然原子提交 | Saga、阶段提交、预留 / 确认两段式、事务边界显式化 |
| 补偿 | 前几步成功,后一步失败,系统处于半完成状态 | 为每个副作用动作设计撤销、冲正、取消或人工处理路径 |
| 取消 | 用户中途停止任务,但工具已在执行 | 区分取消规划、取消排队、取消执行和执行后补偿 |
| 一致性 | 模型上下文里的状态落后于真实系统 | 写入后重新读取真实状态,再让模型继续判断 |
| 风险等级 | 典型工具 | 自治策略 | 确认方式 |
|---|---|---|---|
| 只读低风险 | 搜索文档、读取公开配置、查询个人待办 | 可自动执行 | 记录 trace 即可 |
| 只读敏感 | 读取客户信息、财务数据、内部文档 | 按权限和最小必要暴露 | 可能需要理由、脱敏或审批 |
| 可回滚写入 | 创建草稿、标记状态、生成工单 | 可在规则范围内半自动 | 提交前展示摘要和影响范围 |
| 外发动作 | 发邮件、发消息、提交表单、公开发布 | 默认需要确认 | 用户确认接收方、正文、附件和发送身份 |
| 不可逆高风险 | 付款、删除、权限变更、生产发版 | 不应完全自治 | 强确认、审批流、双人复核或禁止 |
好的确认不是弹一个“是否继续”。它应该展示工具名、目标对象、关键参数、预计副作用、失败后的恢复方式、执行身份和审计编号。人类需要批准的是一个具体、可理解、可追责的动作。
| 工具类型 | 最小安全配置 | 生产增强配置 |
|---|---|---|
| 浏览器 | 域名白名单、禁自动提交、禁下载执行文件 | 步骤截图、DOM diff、表单提交确认、敏感页面隔离 |
| 代码 | 临时目录、资源限制、禁危险系统路径 | 容器隔离、网络策略、依赖锁定、产物扫描 |
| 数据库 | 只读账号、查询超时、结果行数限制 | 语义层、字段脱敏、审批写入、审计查询计划 |
| 业务 API | 服务端鉴权、参数校验、限流 | dry-run、幂等键、风险评分、补偿动作 |
| 记录对象 | 应该包含 | 用来回答的问题 |
|---|---|---|
| 模型调用 | 模型、prompt 版本、工具列表、temperature、token、延迟 | 为什么模型选择了这个工具?成本和延迟在哪里? |
| 工具意图 | tool_name、arguments、生成轮次、校验错误 | 模型生成的动作是否符合协议? |
| 策略裁决 | 权限结果、风险等级、确认要求、拒绝原因 | 系统为什么允许或拒绝执行? |
| 执行结果 | request_id、idempotency_key、状态码、返回摘要、异常 | 工具是否真的执行成功?是否可以重试? |
| 人工介入 | 审批人、审批时间、确认内容、修改参数 | 哪个人批准了哪个具体动作? |
| 状态变迁 | 任务状态、检查点、重试次数、补偿记录 | 长任务在哪里失败,能否恢复? |
| 生命周期节点 | 必留证据字段 | 判断重点 |
|---|---|---|
| 意图生成 | tool_call_id、tool_name、arguments_raw、model_call_id、request_id、生成轮次 | 模型只是提出动作,不等于获得执行权 |
| 参数校验 | tool_schema_id、schema_version、字段校验结果、normalized_args_hash、validation_error | 区分结构错误、业务错误、注入风险和歧义对象 |
| 权限裁决 | actor_id、tenant_id、policy_version、permission_decision_id、allow / deny、确认要求 | 执行身份来自系统授权,不来自模型文本 |
| 审批确认 | approval_id、approver_id、approval_snapshot、risk_tier、human_gate_required | 人类批准的是哪个具体工具、目标对象、参数和副作用范围? |
| 执行与重试 | execution_id、request_id、idempotency_key、attempt、状态码、超时 / 异常 | 可重试必须依赖幂等键和真实状态查询 |
| 回滚与补偿 | compensation_id、rollback_action_id、补偿结果、人工接管记录、最终资源状态 | 副作用不确定时先对账,再决定重试、补偿或转人工 |
| Trace 串联 | trace_id、父子 span、cost_ms、latency_ms、返回摘要、redaction_status | 事后能还原为什么选它、谁允许、做了什么、如何恢复 |
| 回归闭环 | incident_id、eval_case_id、regression_slice、release_gate_id、fixed_tool_version | 坏例是否进入工具选择、参数校验、权限、幂等和补偿回归集? |
| 事故现象 | 先查证据 | 常见根因 | 沉淀动作 |
|---|---|---|---|
| 重复提交、重复扣款或重复发信 | idempotency_key、execution_id、attempt、result_status、最终资源状态 | 超时后盲重试,或 Agent 重新执行整段计划但没有查重 | 补幂等键和对账查询,把重复副作用样本加入 regression_slice |
| 模型调用了不该可见的工具 | tool_schema_id、tool_visibility、actor_id、tenant_id、permission_decision_id | 工具注册表全量暴露,或权限裁决只在模型提示词里表达 | 按任务和角色动态裁剪工具,发布前跑工具可见性门禁 |
| 参数里夹带注入或越权对象 | arguments_raw、normalized_args_hash、validation_error、blocked_span | 自由文本参数太宽,外部内容被塞进可执行字段 | 收紧 schema、字段级过滤、把注入样本加入工具安全评测 |
| 确认后执行对象和预览不一致 | approval_snapshot、normalized_args_hash、execution_id、resource_version | 确认后参数被模型或系统改写,或真实对象版本发生变化 | 审批快照绑定参数哈希和资源版本,执行前做版本校验 |
| 多工具任务半完成 | trace_id、state_checkpoint_id、compensation_id、rollback_action_id | 跨工具流程缺事务边界和补偿动作,失败后只靠对话历史恢复 | 把流程升级为状态机,补 Saga / compensation,并沉淀 runbook |
| 工具返回文本诱导下一步越权 | tool_observation_id、redaction_status、source_untrusted_flag、next_tool_call_id | 工具 observation 没有低信任标记,被模型当成新指令 | 工具返回分区、脱敏和降权,和指令层级证据包交叉回放 |
只记录模型输入输出,只能解释“它说了什么”;只记录业务 API 日志,只能解释“系统做了什么”。Agent 审计必须把两者串起来,才能解释“为什么从这句话走到了这个外部动作”。
| 形态 | 核心接口 | 适合任务 | 缺口 |
|---|---|---|---|
| 单次 Chat Completion | messages → text | 问答、摘要、改写、分类 | 没有执行协议,输出多为文本 |
| 结构化输出 | messages + schema → JSON | 抽取、路由、配置生成 | 只解决格式,不解决动作权限和状态 |
| Function / Tool Calling | messages + tools → tool_calls | 读取实时状态、调用业务 API | 需要外部运行时执行、校验、重试 |
| Agent Loop | plan → act → observe → replan | 多步任务、探索式操作、复杂工作流 | 需要状态机、预算、终止条件和恢复 |
| Agent Runtime | task + policy + tools + state + trace | 生产级任务执行 | 需要平台化治理、审计和人类介入 |
Tool Calling 会从 API 参数生成走向执行协议: 工具注册、策略、状态机、幂等、确认和审计会成为 Agent 平台的基础层。
结构化输出与工具协议继续合流: 模型输出会越来越像受 schema 约束的中间表示,而不是一段自由文本;特殊 token、chat template 和 JSON 边界可继续看 Tokenizer / 结构化输出边界。
Human-in-the-Loop 会成为默认能力: 高风险动作将更常见地进入预览、确认、审批和接管流程。
跨工具事务与补偿: 多工具、多系统、多步骤 Agent 的真实难点会越来越像分布式工作流。
工具安全评测: 未来评测不只看回答质量,还会看模型是否误调工具、越权调用、重复执行和正确停止。
把浏览器、代码和数据库工具过早交给自治 Agent: 这些工具的攻击面和副作用都很大,必须先有沙箱、权限和审计。
用 Prompt 代替权限系统: “请不要删除数据”不是权限控制,真正的边界必须在运行时实现。
Trace 缺失: 没有跨模型和工具的 Trace,Agent 一旦出错就很难定位责任和恢复现场。