第 14 / 15 章 约 6 分钟
输出校验与动作执行
校验结构和引用,为外部动作增加执行边界。
内容同步:2026年10月25日 · 文档版本 8
这个模块在 Agent 里的位置
模型输出不是终点。这个模块决定:
- 结果是否符合结构。
- 引用是否真实。
- 是否有权限执行。
- 是否需要人审。
- 外部动作是否幂等。
- 是否有终态和审计。
它是业务正确性的最后一道闸门。
先记住几个概念
| 概念 | 含义 | 为什么重要 |
|---|---|---|
| Proposal | 模型生成的结构化建议 | 不直接执行 |
| Validator | schema、引用、权限、业务规则校验 | 防止错误进入执行 |
| Human approval | 人工审批 | 高风险动作的确认 |
| Action executor | 真正执行外部动作的组件 | 与模型解耦 |
| Action key | 幂等键 | 防止重复执行 |
| Terminal result | 成功 / 失败 / 取消等终态 | 防止任务假成功 |
从一个真实问题开始:页面显示已取消,邮件还是发出去了
一个分析 Agent 生成结论:
供应商 A 风险上升,建议通知负责人。
系统直接根据这段自然语言发送了邮件。
后来用户在页面上点了取消,但后台没有阻断 pending action。几分钟后,用户又收到一封“任务完成”的邮件。
排查发现:
- 模型输出是自由文本,不是结构化 proposal。
- 没有校验收件人是否在当前租户内。
- 没有幂等 key。
- 取消只改了 UI 状态,没有阻断 action。
- 动作没有 terminal result 和审计。
这类问题的根因不是“模型不稳定”,而是输出和执行之间缺少契约。
模型只生成 proposal,不直接执行
正确链路:
模型输出
-> 结构化 proposal
-> schema / 引用 / 权限校验
-> 自动策略或人工审批
-> action executor
-> terminal result
-> audit
模型可以建议“发通知”,但不能自己决定:
- 发给谁。
- 是否有权限。
- 是否要人审。
- 是否重复执行。
这些必须由服务端策略控制。
一个合格的 proposal
{
"entities": [
{
"entityId": "entity-9182",
"riskScore": 87.4,
"evidenceRefs": ["win-1001", "win-1002"]
}
],
"proposedAction": {
"type": "send_notification",
"recipient": "owner:9182",
"riskLevel": "high",
"message": "高风险实体风险上升"
}
}
执行前至少校验:
- evidenceRefs 是否存在。
- recipient 是否属于当前租户。
- riskLevel 是否符合策略。
- actionType 是否允许。
- actionKey 是否已经执行。
输出校验分五层
| 层 | 校验内容 | 失败处理 |
|---|---|---|
| Schema | 字段、类型、枚举、数量 | 拒绝或让模型修正 |
| 引用 | evidence / source 是否存在 | 拒绝 |
| 权限 | 是否允许操作目标 | 拒绝并记录 |
| 业务规则 | 风险等级、收件人、金额、频率 | 拒绝或要求补充 |
| 幂等 | action key 是否已执行 | 返回已有结果 |
只做 schema 校验是不够的。模型完全可以输出一个结构正确但引用伪造的 JSON。
数据库设计
create table agent_action_proposals (
id bigint primary key,
run_id bigint not null,
tenant_id bigint not null,
action_type text not null,
payload jsonb not null,
risk_level text not null,
status text not null,
created_at timestamptz not null
);
create table agent_action_executions (
id bigint primary key,
proposal_id bigint not null,
action_key text not null,
status text not null,
result jsonb,
created_at timestamptz not null,
unique (proposal_id, action_key)
);
设计要点:
- proposal 和 execution 分表。
- action_key 做幂等。
- 执行结果必须有终态。
- 人审结果和拒绝原因写入审计。
幂等 key 怎么设计
一个可用的 action key:
notify:run-9f27:owner:9182
含义:
- 动作类型。
- 所属 run。
- 目标对象。
如果重试、worker 恢复或网络超时,只要 action key 相同,就不应重复发送。
取消必须阻断 pending action
用户取消 run 时,系统必须:
- 停止模型。
- 停止工具。
- 停止子任务。
- 阻断所有 pending action。
如果 action 已经在执行中,要尽量 abort;如果不能 abort,必须记录终态并告知用户。
不能出现:
UI: cancelled
Action: succeeded
Audit: no result
终态必须可观测
一个执行记录示例:
{
"proposalId": "proposal-551",
"runId": "run-9f27",
"actionType": "send_notification",
"payload": {
"recipient": "owner:9182",
"message": "高风险实体风险上升"
},
"validation": {
"schemaValid": true,
"evidenceRefsValid": true,
"permissionValid": true,
"requiresHumanApproval": true
},
"execution": {
"actionKey": "notify:run-9f27:owner:9182",
"status": "succeeded",
"terminalResult": {
"deliveredAt": "2026-09-24T10:02:00Z"
}
}
}
这能回答:
- 是谁提议的。
- 是否通过校验。
- 是否需要人审。
- 是否执行。
- 是否成功。
- 何时完成。
常见工程错误
| 错误 | 实际后果 | 正确做法 |
|---|---|---|
| 自由文本触发动作 | 不可审计、不可解析 | 结构化 proposal |
| 只做 schema 校验 | 引用可能伪造 | evidence 校验 |
| 无权限校验 | 越权操作 | 服务端 permission policy |
| 无幂等 key | 重复发通知 / 写数据 | action key |
| 无终态 | 任务假成功 | terminal result |
| 取消不阻断 action | 用户已取消但副作用发生 | block pending action |
| 人审结果不入审计 | 无法追溯 | 记录审批和拒绝原因 |
上线前检查
- 输出有 schema。
- 引用可校验。
- 权限在服务端判断。
- 高风险动作有人审。
- 执行幂等。
- 有 terminal status。
- 审计完整。
- run 取消时阻断 pending action。