13.04概念更新于 2026年9月17日

程序校验优先

LLM 决策 → 程序校验 → 权限检查 → 人工审批 → 执行。

13.04 · 课时讲解

程序校验优先

让 LLM 负责提议,让程序负责把关。

1 / 11

一句话理解:让 LLM 负责“提议”,让程序负责“把关”——模型输出的每一个动作都先变成结构化提案,经过程序校验、权限检查、必要的人工审批,最后才由程序去执行。

Agent 最迷人的地方是它能根据情况做判断;最危险的地方也是它能根据情况做判断。模型可能算错金额、看错订单状态、被一封邮件带偏,也可能在长任务里忘了一开始说好的限制。

解决办法不是让模型“更小心”,而是 改变谁有最终决定权。在一个设计良好的 Agent 系统里,模型从来不直接操作真实世界,它只是提交一份提案;真正决定执行与否的,是一串不会疲劳、不会被说服的程序检查。

五步管道

一个写操作从提议到生效,要依次经过五步:

五步管道:LLM 决策生成提案,依次经过程序校验、权限检查、人工审批,最后执行与验证;前三道关卡失败时分别拒绝或驳回 模型 1 LLM 决策 生成结构化提案 代码 2 程序校验 格式与业务规则 代码 + 权限系统 3 权限检查 谁能对谁做什么 人 4 人工审批 按风险级别触发 代码 5 执行与验证 幂等执行、回读 拒绝并返回原因 Agent 可修正 拒绝并记录 直接结束 驳回 或修改后执行 修正后重提 顺序很重要:程序能判断的,不要留给人判断
图 1 · 五步管道:模型只提议,程序、权限系统和人依次把关,最后由程序执行

每一步的职责不同:

步骤 回答的问题 由谁执行 失败时
LLM 决策 应该做什么?为什么? 模型 —
程序校验 这个提案本身合法吗?符合业务规则吗? 代码 返回具体错误,让 Agent 修正
权限检查 当前用户、当前 Agent,有权做这件事吗? 代码 + 权限系统 直接拒绝,记录事件
人工审批 这件事值得冒这个风险吗? 人 驳回、修改或转交
执行与验证 真的执行成功了吗?结果和预期一致吗? 代码 进入错误恢复流程

顺序很重要:程序能判断的,不要留给人判断。审批人的注意力非常宝贵,不应该浪费在“金额是不是负数”“订单是否存在”这种问题上。

第一步:把决策变成结构化提案

模型不应该输出“我决定给这位客户退 380 元”这样的自然语言让下游去猜,而应该输出一份结构化提案。结构化输出的方法见 结构化输出。

{
  "action": "refund",
  "order_id": "SO-20260903-88231",
  "amount": 380.00,
  "currency": "CNY",
  "reason_code": "DUPLICATE_CHARGE",
  "evidence": ["pay_7781023", "pay_7781188"],
  "customer_message_draft": "您好,经核查您 9 月 3 日的订单被重复扣款……"
}

几个设计细节:

  • 用枚举代替自由文本:reason_code 只能是预定义的几种原因,程序才能针对每种原因写规则。
  • 证据用 ID 引用:evidence 放的是支付流水号,程序可以回查,而不是模型复述的“我看到有两笔扣款”。
  • 不让模型填它不该决定的字段:操作人、租户、审批人这些信息由程序从会话上下文注入,而不是由模型生成,见 安全的工具设计。

提案里的字段各有来源,校验时一律以系统数据为准:

结构化提案的字段来源:业务字段由模型生成,操作人、租户、审批人和幂等键由程序注入,程序校验时回查系统数据 LLM 生成 action · order_id · amount reason_code(枚举) evidence(支付流水 ID) customer_message_draft 程序从会话上下文注入 操作人 · 租户 · 审批人 幂等键 结构化提案 两部分合并 程序校验 格式 + 业务规则 通过 → 权限检查 失败 → 返回具体错误 系统数据 订单、支付流水、退款记录 回查
图 2 · 提案字段的来源:模型只填它该决定的字段,校验一律以系统数据为准

第二步:程序校验

校验分为两类:格式校验和业务规则校验。

def validate_refund(p, db):
    errors = []

    # 格式校验
    if p.amount <= 0:
        errors.append("金额必须大于 0")
    if p.reason_code not in REFUND_REASONS:
        errors.append(f"未知的退款原因:{p.reason_code}")

    # 业务规则校验:一切以系统数据为准,不信任提案里的描述
    order = db.get_order(p.order_id)
    if order is None:
        return errors + ["订单不存在"]
    if order.status not in ("PAID", "DELIVERED", "EXCEPTION"):
        errors.append(f"订单状态为 {order.status},不允许退款")
    if p.amount + order.refunded_amount > order.paid_amount:
        errors.append("累计退款金额将超过实付金额")

    if p.reason_code == "DUPLICATE_CHARGE":
        charges = [db.get_payment(e) for e in p.evidence]
        if len(charges) < 2 or any(c is None or c.order_id != p.order_id for c in charges):
            errors.append("重复扣款的证据不足:需要同一订单的至少两条支付记录")

    if db.has_pending_refund(p.order_id):
        errors.append("该订单已有处理中的退款,疑似重复提交")

    return errors

注意两点:

  1. 校验依据的是系统数据,而不是提案里的说法。模型说“有两笔扣款”,程序就去查这两笔扣款是否真实存在、是否属于这个订单。
  2. 错误信息要具体,并回传给 Agent。“累计退款金额将超过实付金额”能让 Agent 修正提案,而一个笼统的“校验失败”只会让它反复重试。

第三步:权限检查

校验回答“这件事合不合理”,权限回答“你有没有资格做”。两者必须分开:

  • 当前发起任务的用户,是否有权对这个客户、这个订单执行退款?(例如华东区客服不能处理华南区订单)
  • 这个 Agent 实例的令牌,作用域是否包含退款?单笔上限是多少?
  • 这个订单属于哪个租户?是否与当前会话的租户一致?

权限检查必须在 服务端、执行动作的系统里 做,而不是在 Agent 的提示词里做。OWASP 在“过度代理”风险的防护建议中也强调:授权应在下游系统中执行,而不是依赖 LLM 判断是否允许。详见 最小权限与凭据管理。

第四、五步:审批与执行

通过校验和权限检查的提案,再根据 操作分级 决定是否需要人工审批。审批通过后,执行环节也有讲究:

  • 幂等执行:为每个提案生成唯一的幂等键,网络重试或 Agent 重复提交时不会退两次款。
  • 执行的是被批准的那一份:审批人看到的提案和最终执行的参数必须一致,中间不允许 Agent 再修改。
  • 执行后回读:退款接口返回成功后,回查退款单状态和订单的累计退款金额,确认真的生效,见 各类动作的验证方式。

为什么“告诉模型规则”不够

一个真实而有启发的例子来自 Claude Code 的官方文档。它的 auto 模式会把用户在对话中说的边界(比如“先别推送”“等我审核后再部署”)当作拦截信号。但文档特别提醒:这些边界并不是存储下来的规则,每次检查时从对话记录里重新读取;如果上下文压缩删掉了那条消息,边界就可能丢失。需要硬性保证时,应该添加一条拒绝规则。

这正是本课的核心:写在上下文里的约束是“软”的,写在程序里的规则是“硬”的。重要的约束,一定要有一份硬的版本。

常见误区

  • “模型已经很准了,校验是多余的。” 准确率 99% 意味着每一百次资金操作就有一次出错。校验的成本远低于一次错误退款。
  • “校验失败就直接报错结束。” 好的校验会返回具体原因,让 Agent 有机会自己修正,很多问题不需要人介入。
  • “权限检查放在工具描述里说明就行。” 工具描述是给模型看的,权限检查必须由执行动作的系统强制完成。

设计要点

  • Agent 的每个写操作,是否都以结构化提案的形式输出?
  • 提案中哪些字段由模型生成,哪些由程序注入?
  • 业务规则校验是否基于系统数据回查,而非提案中的描述?
  • 校验失败的错误信息是否足够具体,能让 Agent 修正?
  • 权限检查是否在执行动作的服务端完成,并且独立于 Agent?
  • 执行是否幂等?审批通过的参数与实际执行的参数是否保证一致?

练习

为「销售分析 Agent」的“调整产品折扣”动作设计完整管道,用 60 分钟完成:

  1. 定义提案的 JSON 结构,标出哪些字段由程序注入。
  2. 写出至少 6 条校验规则(格式 3 条、业务规则 3 条),例如折扣不能低于成本价。
  3. 写出权限检查条件和需要人工审批的触发条件。
  4. 描述执行后如何验证折扣真的生效。

下一步

程序校验防得住“模型犯错”,但还有一种更隐蔽的威胁:模型被外部内容“说服”去做坏事。下一课「Prompt 注入」专门讲这种攻击。

试试:上下文、MCP、沙箱、Prompt 注入、外链