从巴士事故看Agent的安全执行机制

问题背景:一次本可避免的“连环撞”

2026年6月,美国弗吉尼亚州一名旅游巴士司机因未能提前减速,追尾多辆车辆,导致5人死亡。事后,司机被加控过失杀人罪。这起事故的根源非常经典:驾驶员对前方交通状况的感知滞后,并且没有触发应急反应——哪怕有0.5秒的提前减速,后果都会完全不同。

这件事对AI Agent开发者意味着什么?Agent 在真实世界执行多步骤任务时,也会面临类似的“感知—规划—执行”链条断裂。如果 Agent 调用的工具(比如发送订单、控制机械臂、修改数据库)没有安全监督层,一旦某个中间步骤的条件发生变化(环境变了、依赖的服务超时),仍按原计划执行,就可能引发“业务连环撞”:重复扣款、机器碰撞、数据污染。

我的观点: 大多数 Agent 框架(如 LangChain、AutoGPT)把精力花在“如何拆解任务”上,却严重低估了“如何安全地不执行错误任务”。真正的生产级 Agent 必须内置一个独立于规划器的安全监督模块,就像汽车的碰撞预警系统。

Agent 架构拆解:加入安全监督层

典型的 Agent 架构(规划/工具/记忆/执行)通常长这样:

text
1
用户请求 → 规划器(LLM) → 工具调用 → 执行结果 → 更新记忆 → 循环

这个流程缺少两个关键能力:

  1. 环境感知再确认:每次调用工具前,是否重新读取当前环境状态?大部分 Agent 只依赖记忆中的历史信息,而环境可能已经改变。
  2. 危险条件预检:是否存在预定义的“不可执行”条件(比如余额过低、速度过快、存在冲突)?

因此,我建议将架构调整为:

text
1 2 3 4
用户请求 → 规划器(LLM) → 预检安全监督 → 工具调用 → 后检安全监督 → 更新记忆 → 循环
                ↑      环境感知模块(独立刷新) ←        |
                |                                    |
                +— 失败时触发回滚/重规划 ————————+

Agent safety architecture with environmental sensing and rollback loop

安全监督层是一个无状态的规则引擎(或小型分类器),它在执行每一步之前检查当前环境与规划步骤的假设是否仍然匹配。例如:

  • 速度阈值:如果当前速度超过安全值,拒绝“加速”指令
  • 幂等性检查:如果该操作已经是“已完成”状态,拒绝重复执行
  • 依赖预检:目标服务的健康状态是否正常

核心流程图与伪代码

下面给出一个简化但可运行的 Python 伪代码,模拟一个“驾驶Agent”的安全执行流程。这个 Agent 的任务是“从A点开到B点”,每一步规划器给出动作(加速、减速、保持),安全监督层负责检查。

python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
import time
from dataclasses import dataclass

@dataclass
class Environment:
    speed: float = 0.0
    distance_to_obstacle: float = 100.0  # 米

def check_safety(env: Environment, action: str) -> bool:
    """
    安全预检:根据当前环境判断动作是否危险
    """
    if action == "accelerate":
        if env.speed > 80:  # 限速80
            return False
        if env.distance_to_obstacle < 50:  # 距离太近
            return False
    elif action == "brake":
        if env.speed < 5:  # 速度已经很低,不需要急刹
            return False
    return True

def execute_safe_agent():
    env = Environment()
    planner_actions = ["accelerate", "accelerate", "keep", "brake", "accelerate"]
    
    for i, action in enumerate(planner_actions):
        print(f"Step {i}: 环境速度={env.speed:.1f}, 障碍物距离={env.distance_to_obstacle:.1f}m, 计划动作={action}")
        
        # 安全预检
        if not check_safety(env, action):
            print(f"⚠️ 安全监督拒绝动作 '{action}',触发回滚至上一步安全状态")
            # 回滚:重置为上一步的safe状态(这里简单跳过并减速)
            env.speed = max(0, env.speed - 10)  # 强制减速
            continue
        
        # 执行(模拟一次调用修改环境)
        if action == "accelerate":
            env.speed += 10
        elif action == "brake":
            env.speed -= 20
        # 模拟环境动态变化:前方突然出现障碍物
        env.distance_to_obstacle -= 15
        
        print(f"✓ 执行后:速度={env.speed:.1f}, 障碍物距离={env.distance_to_obstacle:.1f}m")
        time.sleep(0.1)

if __name__ == "__main__":
    execute_safe_agent()

Python code execution flow with safety check and rollback

运行后你会看到,当规划器要求“加速”但距离障碍物不足50米时,安全监督会拒绝并执行回滚(减速)。真实Agent可以扩展回滚到上一步的完整状态快照,甚至通知规划器重新规划。

关键实现细节和踩坑记录

1. 回滚的副作用问题

回滚不是简单的“撤销操作”——很多工具调用有不可逆的副作用(比如发送邮件、注册账号)。解决方案: 回滚时执行补偿动作(compensating action),而不是完全逆操作。例如:

  • 转账失败:发起撤销转账的请求(而不是直接扣回)
  • 创建订单:标记订单为失败,而不是删除订单行

2. 环境感知的刷新频率

Agent 无法全局实时感知,只能通过查询获取环境快照。踩坑经验: 不要依赖规划时获取的快照,步骤之间可能已经过时。建议在每次安全预检前执行一次轻量的环境查询(例如通过 API 获取当前状态)。但也要注意频率限制,避免阻塞。

3. 安全监督本身的副作用

安全监督不应该修改环境,它只应返回 yes/no。但有时“拒绝动作”本身就是一种副作用(比如拒绝后需要记录日志、触发告警)。最佳实践: 安全监督是纯函数,所有的副作用(包括日志、告警)由调用层处理。

4. 规划器与安全监督的冲突处理

当安全监督连续拒绝动作,规划器可能陷入死循环。需要在回滚逻辑中加入重试次数上限,超过后调用 fallback(比如完全停止并通知管理员)。

简化版动手实现:一个“安全管家”Agent

读者可以直接运行以下脚本,模拟一个更完整的场景:Agent 需要从数据库读取用户余额,然后执行扣款操作。安全监督在每一步检查余额是否足够、操作是否重复。

python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
import json
from typing import Dict, Any

# 模拟数据库
DB = {"user_balance": 100, "last_action": None}

def get_environment() -> Dict[str, Any]:
    # 这里实际中可能是异步查询
    return dict(DB)

def update_db(key: str, value: Any):
    DB[key] = value

def is_action_safe(env: Dict, action: str, params: Dict) -> bool:
    if action == "deduct":
        amount = params.get("amount", 0)
        if env["user_balance"] < amount:
            print("余额不足,拒绝扣款")
            return False
        if env["last_action"] == "deduct_success":
            print("上一次操作已经是成功扣款,拒绝重复")
            return False
    return True

def compensate(action: str, params: Dict):
    # 补偿动作示例:退款
    if action == "deduct":
        amount = params.get("amount", 0)
        update_db("user_balance", DB["user_balance"] + amount)
        print(f"补偿:退还 {amount}")

def run_safe_agent():
    # 规划器给出的步骤(人工模拟)
    plan = [
        ("deduct", {"amount": 50}),
        ("deduct", {"amount": 80}),  # 第二步会余额不足
        ("deduct", {"amount": 30}),  # 第三步虽然余额够了,但前面有回滚,需要特别注意幂等
    ]
    for i, (action, params) in enumerate(plan):
        env = get_environment()
        print(f"\nStep {i}: 当前余额 {env['user_balance']},意图:{action} {params}")
        if is_action_safe(env, action, params):
            # 执行
            if action == "deduct":
                old_balance = env["user_balance"]
                update_db("user_balance", old_balance - params["amount"])
                update_db("last_action", "deduct_success")
                print(f"成功扣款,余额变为 {DB['user_balance']}")
        else:
            # 回滚:还原到执行前的状态(这里利用快照,实际更复杂)
            compensate(action, params)
            print(f"回滚完成,余额变为 {DB['user_balance']}")
            # 通知规划器重新规划(略)

if __name__ == "__main__":
    run_safe_agent()

运行输出示例:

text
1 2 3 4 5 6 7 8 9 10
Step 0: 当前余额 100,意图:deduct {'amount': 50}
成功扣款,余额变为 50

Step 1: 当前余额 50,意图:deduct {'amount': 80}
余额不足,拒绝扣款
补偿:退还 80
回滚完成,余额变为 50

Step 2: 当前余额 50,意图:deduct {'amount': 30}
成功扣款,余额变为 20

注意到步骤2虽然余额足够,但安全监督检查了“last_action”并非重复,所以正常执行。

总结(不是废话:给开发者的行动清单)

  • 下一次设计Agent时,先定义安全边界,再写规划逻辑
  • 为每个工具调用实现至少一个预检函数补偿动作
  • 不要信任规划器的输出,就像不要信任长途司机(即使有经验)
  • 回滚不是万能的,但对于可逆操作(如财务、状态更新),它是最后的防线

你不需要等到Agent失控再去补救。从现在开始,在你的Agent循环里加两行安全检查,也许就能避免一场“数字车祸”。