Agent系统的责任链设计:从事故中学习

1. 问题背景:一次人类决策失误的启示

2026年6月1日,弗吉尼亚州I-95公路上发生严重大巴事故,司机面临过失杀人指控。这场悲剧的核心是:在复杂动态环境中,一个决策者的瞬间判断失误,导致不可逆的后果。

作为Agent开发者,我们每天都在构建类似“决策者”——AI Agent。它们被赋予工具调用、任务规划、甚至自主执行的权力。但如果我们设计的Agent在一次错误调用中删除了客户数据库,或者给出错误的诊疗建议,谁来承担责任?更重要的是——我们能否在系统层面,让这种错误可预防、可追溯、可恢复

本文不讨论法律责任,而是聚焦工程实践:如何为Agent系统构建一套“责任链”机制,确保每步决策都有记录、每次失败都有回退、每个异常都有人(或系统)知晓。

2. Agent架构的四个脆弱环节

任何Agent系统(无论是ReAct、Plan-and-Execute还是Multi-Agent)都包含四个核心模块,每个模块都可能成为事故源头:

  • 规划(Planning):将用户目标拆解成子步骤。失败点:步骤遗漏、依赖顺序错误。
  • 工具调用(Tool Use):执行具体操作(API、数据库、文件系统)。失败点:参数错误、权限不足、结果不可靠。
  • 记忆(Memory):维持上下文和中间结果。失败点:信息丢失、混淆不同任务。
  • 执行(Execution):按计划顺序执行子任务。失败点:死循环、超时不终止、并发冲突。

I-95事故中的司机——某种程度上就是Agent的“执行器”。他可能因为疲劳、环境突发(比如其他车辆变道)或判断失误而犯错。Agent系统如果没有在架构层面内置容错与审计,遇到类似情况只会更糟糕:不会疲劳,但可能因幻觉而调用错误工具,且不会主动上报异常。

3. 责任链的核心流程图

下面我用Mermaid描述一个带有完整责任链的Agent执行流程。请重点关注步骤间的校验与上报节点

mermaid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
flowchart TD
    A[用户输入] --> B[规划阶段]
    B --> C[前置条件校验]
    C -- 通过 --> D[工具调用]
    C -- 不通过 --> E[错误上报并进入人工审批]
    D --> F[结果验证]
    F -- 有效 --> G[更新记忆]
    F -- 无效/异常 --> H[重试策略]
    H -- 重试次数 < 阈值 --> D
    H -- 重试次数 >= 阈值 --> I[全局异常记录]
    I --> J[触发通知/人工介入]
    G --> K[继续下一步]
    K --> L[循环直到完成或终止]
    L -- 完成 --> M[结果输出+审计摘要]
    L -- 终止 --> N[部分结果+失败原因日志]

这张图告诉我们:每个工具调用之前要校验前提,之后要验证结果。失败的尝试不应默默重试,而应记录完整的上下文(输入、输出、时间戳、所属任务ID),达到阈值后必须上报。

4. 关键实现细节与踩坑记录

4.1 前置校验:防呆设计

很多Agent框架只校验工具参数格式,不校验业务语义。例如一个“发送邮件”工具,参数正确但收件人被AI幻觉成错误地址,系统不会发现。我在实际项目中踩过坑:Agent生成了正确的SQL语句,但误将表名拼写成同义词(存在但数据不同)。

解决方案:对关键工具添加“语义校验器”——将输入传递给一个专门的验证Agent(或调用简单的规则引擎)做二次确认。例如:

python
1 2 3 4 5 6 7
def semantic_check(tool_name: str, arguments: dict, context: str) -> bool:
    # 用轻量LLM或固定规则检查参数是否符合上下文
    if tool_name == "send_email":
        expected_recipients = extract_expected_recipients(context)
        if arguments["to"] not in expected_recipients:
            return False
    return True

注意:校验本身也会增加延迟和成本。我的建议是对高危险工具(删除、转账、修改配置)开启语义校验,对只读/低风险工具关闭

4.2 审计日志:每一笔账都要记

事故调查时,最关键的是“事发前5分钟发生了什么”。Agent执行亦然。设计一个结构化的审计日志,至少包含:

  • task_id:整个任务的唯一标识
  • step_id:当前步骤序号
  • tool_name:调用的工具
  • input:实际传递的参数(JSON序列化)
  • output:工具返回的结果(截断至5KB)
  • status:success / retry / failed
  • timestamp:UTC时间戳
  • exception:如有异常,记录完整堆栈
  • decision:这个步骤的决策依据(可选,从LLM思维链中提取)

这样的日志可以直接存入数据库或对象存储。我见过最好的实践是每次工具调用都写一条独立记录,不依赖主流程状态——即使主进程崩溃,已完成调用的日志也保留下来。

4.3 失败重试策略:不是所有错误都该重试

Agent常犯的错误之一是“无差别重试”。例如API返回429(限流),重试是合理的;返回404(资源不存在),重试毫无意义。

我的做法是:

python
1 2 3 4 5 6
RETRY_MAP = {
    "rate_limit": (3, 2.0),      # (最大重试次数, 退避秒数)
    "timeout": (2, 5.0),
    "server_error": (1, 0.0),
    "client_error_4xx": (0, 0.0)  # 不重试,直接上报
}

此外,每次重试前应更新记忆,告诉Agent“当前步骤正在重试,之前尝试的输入与错误信息”,帮助LLM调整后续调用。否则Agent重试时可能使用完全相同的参数,白白浪费资源。

4.4 人工介入接口:让系统承认“我不确定”

Agent决策有时超过置信阈值或碰到未定义情况时,应该优雅地暂停并请求人类决策者介入。这类似于事故中的“呼叫支援”。

实现上,可以设计一个HumanInTheLoop通道:

python
1 2 3 4 5 6
async def request_human_approval(step_info: dict) -> dict:
    # 将步骤信息推送到消息队列或WebSocket
    await notify_human({"task_id": step_info["task_id"], "question": step_info["question"]})
    # 阻塞等待回复(或超时超时默认拒绝)
    response = await wait_for_human(step_info["task_id"], timeout=120)
    return response

在实际系统里,我通常会设置“超时自动拒绝”并记录日志,因为等待过久会导致整个任务阻塞。

5. 简化版动手实现:带审计日志的Agent执行器

下面我提供一个最小可运行示例(假设使用OpenAI API与一个虚构工具)。重点展示如何集成日志记录与异常上报:

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 57 58 59 60
import json
import datetime

class AgentWithAudit:
    def __init__(self, llm, tools):
        self.llm = llm
        self.tools = {t.name: t for t in tools}
        self.audit_log = []

    async def execute(self, user_input: str):
        plan = await self.llm.plan(user_input)
        task_id = f"task_{datetime.datetime.utcnow().timestamp()}"
        for step in plan.steps:
            tool = self.tools.get(step.tool_name)
            if not tool:
                self._log_entry(task_id, step, "error", f"Tool {step.tool_name} not found")
                raise ValueError(f"Unknown tool: {step.tool_name}")

            # 前置校验
            if not await self._semantic_check(step.tool_name, step.args, plan.context):
                self._log_entry(task_id, step, "blocked", "Semantic check failed")
                # 进入人工审批
                approved = await self._request_human_approval(step)
                if not approved:
                    self._log_entry(task_id, step, "rejected", "Human rejected")
                    return

            # 执行并重试逻辑
            retries = 0
            max_retries = 2
            while retries <= max_retries:
                try:
                    result = await tool.call(**step.args, timeout=10)
                    # 结果验证
                    if not self._validate_result(tool, result):
                        raise ValueError("Result validation failed")
                    self._log_entry(task_id, step, "success", result)
                    break
                except Exception as e:
                    retries += 1
                    if retries > max_retries:
                        self._log_entry(task_id, step, "failed", str(e))
                        await self._notify_human(step, e)
                        break
                    else:
                        self._log_entry(task_id, step, "retry", f"Attempt {retries}: {e}")
                        await asyncio.sleep(2 ** retries)

        return self._build_report(task_id)

    def _log_entry(self, task_id, step, status, detail):
        self.audit_log.append({
            "task_id": task_id,
            "step_id": step.id,
            "tool": step.tool_name,
            "input": step.args,
            "status": status,
            "detail": str(detail)[:500],
            "timestamp": datetime.datetime.utcnow().isoformat()
        })

这段代码并不完美,但体现了核心思想:每一步都有记录;错误不会无声消失;高风险操作有二次确认。

Agent audit log flow with human intervention

6. 对开发者的建议

  • 不要相信Agent的自信:LLM会自信地犯错。在关键路径上增加语义校验,即使牺牲一点速度。
  • 日志不只是为了调试,更是为了追责:把审计日志当成核心功能来设计,而不是事后补丁。
  • 拥抱“失败上报”文化:Agent系统里“重试-成功”不是最优秀的状态,“失败-上报-人工介入”才是更健壮的。
  • 模拟事故演练:像消防演习一样,定期向Agent注入异常输入,观察你的审计链是否完整记录了所有环节。

我曾在自己的Agent框架里关闭了语义校验以追求速度,结果一次演示中Agent把“钉钉通知”发到了竞争对手的群里。那次教训后,我再也不敢忽略责任链。

回到文章开头的大巴事故。人类司机有疲劳、有盲区,AI Agent有幻觉、有上下文丢失。两者都需要系统级的保护机制。今天你为Agent多写一行日志校验,明天就少一次线上事故。

注:文中所有代码为概念示例,实际生产需要添加更完善的并发控制、超时和安全性检查。