目录

  1. 背景:单体 LLM 的认知边界与 Multi-Agent 的哲学起源
  2. 核心架构模式:拓扑选型与通信设计
  3. LangGraph 技术实现:图、状态与记忆
  4. 行业落地:代码审查流水线的深度解构
  5. 生态演进:协议、治理与可观测性

1. 背景:单体 LLM 的认知边界与 Multi-Agent 的哲学起源

1.1 单体 LLM 的失败模式:不只是工程问题

讨论 Multi-Agent 的必要性,通常从"上下文窗口太小"开始。但这个表述掩盖了一个更根本的事实:即使上下文窗口无限大,单体 LLM 也无法胜任真正复杂的任务。理解这一点,需要从注意力机制的本质说起。

Transformer 的自注意力机制是一种全局信息聚合过程——每个 token 通过 Query-Key-Value 机制与序列中的所有其他 token 计算相关性。这在短序列上工作得很好,因为相关信息的"信号噪声比"较高。但随着序列长度增加,注意力矩阵的维度呈平方增长,而真正相关的信息密度却在下降。模型并不是"忘记"了中间的信息,而是在数学上无法维持对长距离依赖的精准关注——这被称为"Lost in the Middle"效应,多项实证研究表明关键信息处于上下文中段时的提取准确率会下降 20-40%。

更深层的问题在于推理深度的限制。即使是性能最好的 LLM,在多步推理链中也存在明确的错误累积效应:若每一步推理的准确率为 p,则 n 步后的整体准确率约为 (1-p)^n。以 95% 的单步准确率计算,10 步推理后整体准确率跌至约 60%,20 步后只剩约 36%。这不是可以通过扩大上下文解决的问题,而是链式推理架构本身的固有局限。

还有一个常被忽视的"角色冲突"问题:单体 LLM 必须同时扮演规划者和执行者。规划需要高层次的抽象与全局视角,执行需要细节聚焦与精准输出。在认知科学中,这对应于大脑的前额叶(高阶规划)与后部皮质(具体执行)的分工。强迫同一个系统同时承担两种认知模式,不可避免地导致在两者之间反复切换的效率损失。

从计算成本角度,Transformer 的时间复杂度为 O(n²)——将序列长度从 32K 扩展到 128K,推理成本增加 16 倍,而信息质量的提升远不成比例。这在需要批量处理的生产场景中构成难以承受的工程代价。

关键认知:单体 LLM 的瓶颈不是工程限制,而是架构本质。在真正复杂的任务上,不是"做不好",而是"天花板明确"。

1.2 分布式认知:从认知科学到系统设计

Multi-Agent 系统并非凭空而来,它在思想上深度借鉴了认知科学中的分布式认知理论(Distributed Cognition)。

1990 年代,认知科学家 Edwin Hutchins 通过研究海军导航舰桥的工作模式,提出了一个颠覆性的观点:认知不仅仅发生在单个人的大脑中,而是分布在人与人之间、人与工具之间、人与环境之间的交互网络中。一个舰桥的导航任务,是船长、副手、领航员、海图、罗盘这些"认知节点"共同完成的,没有任何一个节点单独具备完成任务的全部能力。

这个洞察直接映射到 Multi-Agent 设计:系统的智能不应该集中于单一模型,而应该分布在多个专能 Agent 及其协作机制之中。每个 Agent 持有局部视图和专业知识,通过结构化的协作协议聚合为超越个体的全局智能。

分布式认知理论还提供了另一个重要启示:认知负荷的分配方式决定了系统的质量上限。当一个专才(Security Agent)专注于安全漏洞分析,它的注意力资源被完全集中在一个认知域上,其分析质量将远超要求全才(单体 LLM)在同等时间内同时处理安全、性能、可维护性的质量。这就是"职责单一化"的认知科学基础,而不仅仅是软件工程原则。

1.3 Multi-Agent 的本质是组织设计问题

理解 Multi-Agent 最重要的思维跨越,是将它从技术问题升维到组织设计问题

当我们设计 Multi-Agent 系统时,我们实际上在做的事情与设计一个人类团队高度同构:定义角色与职责边界,设计信息流转机制,制定决策权限与升级路径,建立质量控制与审查机制,规划异常情况的处理流程。一个优秀的 Multi-Agent 架构师需要同时具备软件工程师和组织设计者的视角。

这个视角带来了一个实践启示:在设计 Multi-Agent 系统时,应当先做"岗位设计",再做"技术实现"。先想清楚"如果这是一个人类团队,我们需要哪些角色,每个角色的输入是什么、输出是什么、决策边界在哪里",然后再将这些组织设计映射到 Agent 的技术实现。

当你从组织设计角度审视代码审查场景时,会天然地得出"需要静态分析专家、业务逻辑审查者、安全专家、以及协调仲裁者"的结论——这正是后续架构设计的出发点。


2. 核心架构模式:拓扑选型与通信设计

2.1 四种拓扑结构的选型决策树

Multi-Agent 系统有四种基本拓扑,每种都有其适用条件和内在权衡。选型决策不应基于偏好,而应基于任务特征的系统性分析。

**Hub-Spoke(中心辐射)**是最常见的起点。一个 Supervisor Agent 作为中枢,负责任务分配与结果仲裁;多个 Worker Agent 各司其职并向 Supervisor 汇报。这种模式的核心优势是控制流清晰——系统行为高度可预测,调试相对容易,人工干预的介入点明确。代价是 Supervisor 成为单点瓶颈,当 Worker 数量增多时,Supervisor 的决策负担呈线性增长,调度延迟也会随之上升。

<ol><li><span><span><code><span><span leaf="">// Hub-Spoke routing logic</span></span></code></span></span></li><li><span><span><code><span><span leaf="">supervisor</span></span><span><span leaf="">.</span></span><span><span leaf="">decide</span></span><span><span leaf="">(</span></span><span><span leaf="">current_state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; completed&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;get_completed_workers</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; remaining&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;available_workers&nbsp;</span></span><span><span leaf="">-</span></span><span><span leaf="">&nbsp;completed</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;task_complete</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">or</span></span><span><span leaf="">&nbsp;remaining</span></span><span><span leaf="">.</span></span><span><span leaf="">empty</span></span><span><span leaf="">():</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;FINISH</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Key decision: which worker next, and why?</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">next</span></span><span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;select_worker_by_priority</span></span><span><span leaf="">(</span></span><span><span leaf="">remaining</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">.</span></span><span><span leaf="">task_profile</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; log_routing_decision</span></span><span><span leaf="">(</span></span><span><span leaf="">next</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;reasoning</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span></span><span><span leaf="">next</span></span></code></span></span></li></ol>

**Pipeline(流水线)**适用于强顺序依赖的场景——前一步的输出是后一步的必要输入,且这种依赖关系是稳定的、可预知的。ETL 数据处理、文档转换流水线、代码编译-测试-发布链路,都是 Pipeline 的典型场景。Pipeline 的最大优势是模块化——每个步骤可以独立优化、独立测试、独立替换,而不影响整体。其核心缺陷是不能并行:总延迟等于各步骤延迟之和,单步失败阻断整条流水线。

**Hierarchical(层级制)**是 Hub-Spoke 的递归扩展,适用于任务规模大到单层 Supervisor 无法有效管理的场景。Orchestrator 负责高层规划,下一层的 Sub-Supervisor 负责各领域的协调,最底层的 Worker 负责具体执行。层级制的优势在于将决策复杂度分散到多个层级,每个决策节点只需关注自己视野内的子问题。其代价是通信链路更长,任务在多个层级间的上下文信息在传递过程中容易产生语义漂移——Orchestrator 表达的高层意图,在经过 Sub-Supervisor 重新解释后,可能到达 Worker 时已经产生了微妙的偏差。

**Peer-to-Peer(去中心化)**让 Agent 之间直接通信,没有中央协调者。这种模式高度灵活,适合强调自主性的研究场景,或任务流程高度动态、无法预先规划的情况。但在工程实践中,去中心化带来的调试难度和循环调用风险,使它在生产环境中很少被采用。

选型决策框架:当面对一个具体任务时,应按以下顺序提问:任务步骤之间是否有严格的顺序依赖?如果是,优先考虑 Pipeline。任务是否可以分解为相互独立的子任务?如果是,考虑 Hub-Spoke。任务规模是否大到一个 Supervisor 无法管理?如果是,升级为 Hierarchical。如果任务流程高度动态且可控性要求低,可以考虑 P2P。绝大多数生产场景最终都落在 Hub-Spoke 或 Hub-Spoke + Pipeline 的混合形式上。

2.2 通信协议的深层对比:从分布式系统视角审视

Multi-Agent 系统中最核心的架构决策之一是通信协议的选择:消息传递(Message Passing)还是共享状态(Shared State)?

表面上看,这是一个实现细节。从分布式系统视角看,这是关于一致性与可用性的根本权衡

消息传递模型将 Agent 视为独立的计算单元,通过消息队列交换信息。这在概念上与 Actor 模型和微服务架构一致:Agent 不需要了解彼此的内部状态,只需能够理解接收到的消息格式。这种高度解耦带来了良好的可扩展性——Agent 可以分布在不同机器甚至不同语言环境中,只要消息协议兼容即可。但代价是调试困难:一个错误可能需要追踪多个 Agent 的消息历史才能定位;此外,并发消息处理需要解决消息排序和幂等性问题。

共享状态模型让所有 Agent 读写同一个状态对象。调试体验极佳——在任何时间点都可以查看完整的系统状态快照,理解"系统现在处于什么阶段、各 Agent 输出了什么"变得直观。代价是 Agent 之间通过状态隐式耦合,并发写入需要解决冲突(通常通过 Reducer 函数实现确定性合并)。

从 CAP 定理的视角分析:消息传递天然倾向 AP(可用性 + 分区容忍),适合分布式部署但需要接受最终一致性;共享状态倾向 CP(一致性 + 分区容忍),适合单进程内的强一致性需求。这并不是说哪种更好,而是要理解在选择通信模型时,同时在选择一致性保证级别。

在 LangGraph 的实践中,共享状态 + TypedDict + Reducer 是面向生产场景的最优选择,原因有三:其一,可观测性卓越——任意节点的状态快照都是人类可读的结构化数据;其二,LangGraph 的 Checkpointing 机制天然基于共享状态序列化,故障恢复和时间旅行调试都依赖于此;其三,Reducer 函数提供了声明式的并发冲突解决方案,无需手动编写锁逻辑。

<ol><li><span><span><code><span><span leaf="">// Reducer-based state merging (no locks needed)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">state&nbsp;</span></span><span><span leaf="">AgentState</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; messages</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">List</span></span><span></span><span><span leaf="">// reducer: append_new (add_messages)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; findings</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">List</span></span><span></span><span><span leaf="">// reducer: concatenate (operator.add)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; agent_outputs</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">Dict</span></span><span></span><span><span leaf="">// reducer: merge_dicts ({**x, **y})</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; confidence</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">Float</span></span><span></span><span><span leaf="">// reducer: max_value (max)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Concurrent writes are safe because reducers are deterministic</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Worker A writes: {findings: [finding1]}</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Worker B writes: {findings: [finding2]}</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Merged result: &nbsp;{findings: [finding1, finding2]} &nbsp;✓</span></span></code></span></span></li></ol>

2.3 Supervisor 模式的核心难题:任务分解粒度

Supervisor 模式在实践中最难处理的问题不是技术实现,而是任务分解粒度的把握。

粒度过粗:一个 Worker 承担过多责任,退化为小型单体系统,Multi-Agent 的优势无法发挥;粒度过细:产生"分解爆炸"——大量细粒度 Worker 之间的协调成本超过了并行计算带来的收益,同时状态复杂度激增。

判断分解粒度是否合适,有一个实用标准:每个 Worker 应该在一次 LLM 调用内完成其核心任务,如果一个 Worker 内部需要多轮 LLM 调用才能完成工作,说明它的边界划定可能过宽,可以进一步分解;如果一个 Worker 的输出几乎总是被 Supervisor 原样转发给下一个 Worker,说明它的任务粒度过细,可以与上下游合并。

另一个常见问题是 Supervisor 的"决策质量":Supervisor 需要根据当前状态动态决定下一步调用哪个 Worker,这本身是一个需要上下文理解和推理的 LLM 调用。随着已完成任务的增多,Supervisor 的决策上下文也在增长,最终可能陷入和单体 LLM 同样的上下文管理困境。缓解策略是为 Supervisor 提供结构化输出约束——不允许 Supervisor 自由生成文本,而是强制其从预定义的有限选项中选择,将决策空间收缩到可管理的范围。

<ol><li><span><span><code><span><span leaf="">// Structured supervisor output prevents routing hallucination</span></span></code></span></span></li><li><span><span><code><span><span leaf="">RoutingDecision</span></span><span><span leaf="">&nbsp;schema</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; next_agent</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">enum</span></span><span><span leaf="">[</span></span><span><span leaf="">researcher</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;coder</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;reviewer</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;FINISH</span></span><span><span leaf="">]</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; reasoning</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">string</span></span><span></span><span><span leaf="">(</span></span><span><span leaf="">max&nbsp;</span></span><span><span leaf="">100</span></span><span><span leaf="">&nbsp;chars</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; confidence</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">float</span></span><span></span><span><span leaf="">(</span></span><span><span leaf="">0.0</span></span><span><span leaf="">-</span></span><span><span leaf="">1.0</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">supervisor</span></span><span><span leaf="">.</span></span><span><span leaf="">route</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; decision&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;llm</span></span><span><span leaf="">.</span></span><span><span leaf="">invoke_with_structured_output</span></span><span><span leaf="">(</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; schema</span></span><span><span leaf="">=</span></span><span><span leaf="">RoutingDecision</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; context</span></span><span><span leaf="">=</span></span><span><span leaf="">build_minimal_context</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">// only essential info</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Validate: reject low-confidence decisions</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;decision</span></span><span><span leaf="">.</span></span><span><span leaf="">confidence&nbsp;</span></span><span><span leaf="">&lt;</span></span><span></span><span><span leaf="">0.7</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;default_next_agent</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">// fallback to heuristic</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;decision</span></span><span><span leaf="">.</span></span><span><span leaf="">next_agent</span></span></code></span></span></li></ol>

2.4 Agent 间信任模型:最小权限原则

一个经常被忽视的设计维度是 Agent 间的信任边界

在 Multi-Agent 系统中,Agent 并非平等的——不同 Agent 应该拥有不同的工具访问权限,访问不同范围的外部系统。这不只是安全上的考量,也是工程上的约束:权限边界清晰的系统更容易审计,更容易排查"是哪个 Agent 执行了这个操作"。

最小权限原则在 Agent 设计中的映射:每个 Agent 只应获得完成其核心任务所必需的最小工具集。一个只负责代码静态分析的 Agent,不应该有写入数据库或调用外部 API 的能力;一个报告生成 Agent,不应该有执行代码的权限。

这个原则在实践中还有另一个维度:Agent 对其他 Agent 输出的信任程度。在 Supervisor 收集多个 Worker 的输出时,应将这些输出视为"待审查的报告"而非"已证实的事实",Supervisor 需要保持判断力,而不是无条件地传递 Worker 的结论。

<ol><li><span><span><code><span><span leaf="">// Tool permission registry: explicit, not implicit</span></span></code></span></span></li><li><span><span><code><span><span leaf="">TOOL_PERMISSIONS&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">{</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">"researcher"</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[</span></span><span><span leaf="">search_docs</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;fetch_web</span></span><span><span leaf="">],</span></span><span></span><span><span leaf="">// read-only external</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">"coder"</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[</span></span><span><span leaf="">run_sandbox</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;search_docs</span></span><span><span leaf="">],</span></span><span></span><span><span leaf="">// execution in sandbox</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">"security_scan"</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[</span></span><span><span leaf="">search_cve_db</span></span><span><span leaf="">],</span></span><span></span><span><span leaf="">// specialized read</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">"reporter"</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[],</span></span><span></span><span><span leaf="">// no tools, synthesis only</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">"supervisor"</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[]</span></span><span></span><span><span leaf="">// no tools, routing only</span></span></code></span></span></li><li><span><span><code><span><span leaf="">}</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Agent factory enforces permissions</span></span></code></span></span></li><li><span><span><code><span><span leaf="">create_agent</span></span><span><span leaf="">(</span></span><span><span leaf="">name</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;prompt</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; allowed_tools&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;TOOL_PERMISSIONS</span></span><span><span leaf="">[</span></span><span><span leaf="">name</span></span><span><span leaf="">]</span></span><span></span><span><span leaf="">// fail loudly if name not in registry</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; llm&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;base_llm</span></span><span><span leaf="">.</span></span><span><span leaf="">bind_tools</span></span><span><span leaf="">(</span></span><span><span leaf="">allowed_tools</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;agent_node</span></span><span><span leaf="">(</span></span><span><span leaf="">llm</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;prompt</span></span><span><span leaf="">)</span></span></code></span></span></li></ol>

3. LangGraph 技术实现:图、状态与记忆

3.1 StateGraph 的本质:状态机思维 vs 数据流思维

LangGraph 的核心抽象是 StateGraph——一个有状态的有向图,其中节点代表计算,边代表控制流,共享状态在整个图的生命周期内持续演化。

理解 LangGraph 的关键是辨析两种思维模式的差异:数据流思维(如 LangChain 的 Chain)和状态机思维(LangGraph 的图)。

数据流思维把计算看作数据的变换管道:输入进入,经过一系列变换,输出流出。这对线性处理非常直观,但无法自然表达循环、条件分支、以及基于状态的回退。状态机思维则不同:系统始终处于某个"状态",外部事件(工具调用结果、LLM 输出)触发状态转移,控制流完全由状态和转移函数决定。这使得循环(Agent 可以重复调用工具直到满足某个条件)、条件分支(基于置信度决定是否需要人工)、以及跨轮次的状态持久化变得自然而然。

为什么用图而不是链,本质上是因为复杂 Agent 系统的控制流是"非确定性的、依赖于运行时状态的"。你无法在设计时知道一个 ReAct Agent 会循环几次,无法知道哪些 Worker 会被调用,无法知道系统会不会触发人工干预。图的条件边(Conditional Edges)正是用来表达这种运行时动态路由的,而链在设计时就固定了执行顺序。

<ol><li><span><span><code><span><span leaf="">// State machine perspective: nodes are states, edges are transitions</span></span></code></span></span></li><li><span><span><code><span><span leaf="">StateGraph</span></span><span><span leaf="">&nbsp;definition</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; nodes</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">name&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;compute_function</span></span><span><span leaf="">}</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; edges</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">fixed</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp; &nbsp; &nbsp; A&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;B &nbsp;</span></span><span><span leaf="">(</span></span><span><span leaf="">always go to B after A</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; conditional</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;A&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;f</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">→</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">B</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;C</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;D</span></span><span><span leaf="">,</span></span><span></span><span><span leaf="">END</span></span><span><span leaf="">}</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// State is shared and mutable across all nodes</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; state_schema</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">TypedDict</span></span><span></span><span><span leaf="">with</span></span><span><span leaf="">&nbsp;reducers</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Example: agent decides whether to call tool or finish</span></span></code></span></span></li><li><span><span><code><span><span leaf="">should_continue</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; last_msg&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">.</span></span><span><span leaf="">messages</span></span><span><span leaf="">[-</span></span><span><span leaf="">1</span></span><span><span leaf="">]</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;last_msg has tool_calls</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span></span><span><span leaf="">"tool_executor"</span></span><span></span><span><span leaf="">// continue working</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">elif</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">.</span></span><span><span leaf="">confidence&nbsp;</span></span><span><span leaf="">&lt;</span></span><span></span><span><span leaf="">0.7</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span></span><span><span leaf="">"fallback_agent"</span></span><span></span><span><span leaf="">// need help</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">else</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span></span><span><span leaf="">END</span></span><span></span><span><span leaf="">// done</span></span></code></span></span></li></ol>

状态中的 Reducer 是另一个重要设计:每个状态字段都有一个合并函数,决定当多个节点同时更新同一字段时如何处理。 add_messages 追加而非覆盖消息历史, operator.add 累积列表, max 保留最大值。Reducer 设计的好坏直接影响系统的并发安全性——正确设计的 Reducer 使并发写入无锁化、确定性化。

3.2 Checkpointing 的深层价值:可审计性与可干预性

大多数介绍 LangGraph Checkpointing 的文章都将其定位为"故障恢复机制"——系统崩溃后可以从中断点继续。这个定位没有错,但低估了 Checkpointing 的真正价值。

Checkpointing 的第一个深层价值是可审计性。在生产环境中,当一个 Multi-Agent 系统产生了错误的输出,定位原因是极其困难的。哪个 Agent 最先产生了错误推断?Supervisor 的哪次路由决策是错的?工具调用的返回值是否被正确处理?没有 Checkpointing,这些问题几乎无法回答。有了 Checkpointing,每个状态快照都是一个调试断点,可以精确还原"系统在每一步知道什么、做了什么决定"。

第二个深层价值是可干预性(Human-in-the-Loop 的技术基础)。LangGraph 的 interrupt() 机制基于 Checkpointing 实现——在特定节点暂停图的执行,将控制权交还给人类,允许人类修改状态后恢复执行。这不仅是"让人类审批",更是"让人类能够精确修改系统的中间状态"。在高风险操作(如生产数据库变更、对外发送通知)前插入 interrupt 节点,是 AI 系统安全设计的重要模式。

第三个价值是时间旅行调试:可以将系统状态回退到任意历史检查点,修改某个节点的输出,然后重新运行后续流程,观察不同输入如何影响最终结果。这对 prompt 优化和系统调试效率的提升是数量级的。

<ol><li><span><span><code><span><span leaf="">// Checkpointing enables three capabilities</span></span></code></span></span></li><li><span><span><code><span><span leaf="">checkpointer&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">SqliteSaver</span></span><span><span leaf="">(</span></span><span><span leaf="">"production.db"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">app&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;graph</span></span><span><span leaf="">.</span></span><span><span leaf="">compile</span></span><span><span leaf="">(</span></span><span><span leaf="">checkpointer</span></span><span><span leaf="">=</span></span><span><span leaf="">checkpointer</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// 1. Fault recovery: same thread_id resumes from last checkpoint</span></span></code></span></span></li><li><span><span><code><span><span leaf="">session&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">"thread_id"</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"pr-review-12345"</span></span><span><span leaf="">}</span></span></code></span></span></li><li><span><span><code><span><span leaf="">result&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;app</span></span><span><span leaf="">.</span></span><span><span leaf="">invoke</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;config</span></span><span><span leaf="">=</span></span><span><span leaf="">session</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">// crash mid-way? resume here</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// 2. Human intervention: pause and inspect</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// When interrupt() is hit, execution pauses</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Human can call app.update_state(session, {override_field: new_value})</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Then resume: app.invoke(None, config=session)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// 3. Time-travel debugging</span></span></code></span></span></li><li><span><span><code><span><span leaf="">history&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;list</span></span><span><span leaf="">(</span></span><span><span leaf="">app</span></span><span><span leaf="">.</span></span><span><span leaf="">get_state_history</span></span><span><span leaf="">(</span></span><span><span leaf="">session</span></span><span><span leaf="">))</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// history[5] is the state after dispatcher ran</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Replay from step 5 with modified dispatcher output:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">app</span></span><span><span leaf="">.</span></span><span><span leaf="">update_state</span></span><span><span leaf="">(</span></span><span><span leaf="">session</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;modified_state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;as_node</span></span><span><span leaf="">=</span></span><span><span leaf="">"dispatcher"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">app</span></span><span><span leaf="">.</span></span><span><span leaf="">invoke</span></span><span><span leaf="">(</span></span><span><span leaf="">None</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;config</span></span><span><span leaf="">=</span></span><span><span leaf="">session</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">// re-run from dispatcher with new data</span></span></code></span></span></li></ol>

3.3 记忆管理的三个层次

Multi-Agent 系统的记忆管理远比"将消息历史放入 prompt"复杂。工程实践中,需要区分三个层次的记忆,每个层次有不同的技术实现路径和适用场景。

工作记忆(Working Memory) 对应 LLM 的上下文窗口。这是 Agent 在当前任务执行期间"能看到的所有东西"——当前轮次的消息历史、工具调用结果、其他 Agent 的输出。工作记忆是临时的,任务结束即消失。工程上的关键挑战是上下文窗口管理:随着 Agent 工作轮次增加,消息历史线性增长,最终超出窗口限制。解决方案通常是滚动窗口(只保留最近 N 条消息)或摘要压缩(将旧消息压缩为摘要后放入系统提示)。两种方案的权衡:滚动窗口简单但可能丢失早期关键信息,摘要压缩保留语义但摘要质量依赖于压缩 LLM 的能力。

情节记忆(Episodic Memory) 是跨对话、跨任务的历史记录——“这个用户上周让我审查的 PR 我发现了哪些问题”。这是 Checkpointing 配合 thread_id 实现的持久化状态。工程上通常通过关系型数据库(如 PostgreSQL)或向量数据库(时序检索)实现。情节记忆的价值在于让 Agent 具备"记住历史、从历史中学习"的能力,但代价是存储开销随时间线性增长,需要设计合理的保留策略(LRU 淘汰、重要性标注、定期归档)。

语义记忆(Semantic Memory) 是领域知识库——不依赖于特定对话历史,而是通用的专业知识(代码审查规则、安全漏洞模式、业务逻辑规范)。技术实现路径是向量数据库 + 语义检索(RAG)。语义记忆的关键工程问题是知识的新鲜度:当业务规则更新时,如何确保 Agent 使用的不是过期知识?这需要设计知识版本管理机制,将知识更新事件同步到向量库。

<ol><li><span><span><code><span><span leaf="">// Three-tier memory architecture</span></span></code></span></span></li><li><span><span><code><span><span leaf="">class</span></span><span></span><span><span leaf="">AgentMemorySystem</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; working_memory</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">ContextWindow</span></span><span></span><span><span leaf="">// ephemeral, current session</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; episodic_memory</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">CheckpointStore</span></span><span></span><span><span leaf="">// persistent, per thread</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; semantic_memory</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">VectorStore</span></span><span></span><span><span leaf="">// shared, domain knowledge</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; query_relevant_context</span></span><span><span leaf="">(</span></span><span><span leaf="">task</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;agent_id</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Retrieve from all three tiers</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; recent_history&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;working_memory</span></span><span><span leaf="">.</span></span><span><span leaf="">last_n</span></span><span><span leaf="">(</span></span><span><span leaf="">messages</span></span><span><span leaf="">=</span></span><span><span leaf="">5</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; past_similar&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;episodic_memory</span></span><span><span leaf="">.</span></span><span><span leaf="">search</span></span><span><span leaf="">(</span></span><span><span leaf="">task</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;top_k</span></span><span><span leaf="">=</span></span><span><span leaf="">3</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; domain_knowledge&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;semantic_memory</span></span><span><span leaf="">.</span></span><span><span leaf="">semantic_search</span></span><span><span leaf="">(</span></span><span><span leaf="">task</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;top_k</span></span><span><span leaf="">=</span></span><span><span leaf="">5</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Compose context with priority: working &gt; episodic &gt; semantic</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;compose_context</span></span><span><span leaf="">(</span></span><span><span leaf="">recent_history</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;past_similar</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;domain_knowledge</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;max_tokens</span></span><span><span leaf="">=</span></span><span><span leaf="">2000</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">// hard budget</span></span></code></span></span></li></ol>

3.4 工具权限隔离的安全考量

让所有 Agent 共享所有工具,是 Multi-Agent 系统中最常见的安全反模式。表面上看,这简化了工具管理;实际上,它同时扩大了每个 Agent 的攻击面,使得一个被 prompt 注入的 Agent 可以调用本不该调用的高危工具。

更微妙的问题是工具调用的不可逆性。许多工具的操作是有副作用的——发送邮件、写入数据库、调用第三方 API。如果 Researcher Agent(本应只做信息收集)意外地被授予了写入权限,一次误操作可能引发难以回滚的后果。

工具隔离的另一个工程价值是调试追溯。当系统发生异常的外部操作时,如果每个 Agent 的工具集是明确的,可以立即缩小排查范围到"哪些 Agent 有这个工具的权限"。如果所有 Agent 共享工具,则需要遍历所有 Agent 的调用历史。

在实践中,工具权限隔离还应考虑工具组合的安全性:单个工具可能无害,但组合使用可能产生不当结果。例如, search_codebase + write_file 的组合,使得 Agent 可以搜索敏感代码然后写出到外部——即使这两个工具单独都是合理的。设计工具集时需要考虑工具组合的语义,而不只是单个工具的权限。


4. 行业落地:代码审查流水线的深度解构

4.1 传统代码审查的本质痛点:注意力资源的不可分割性

在讨论 AI 代码审查系统之前,必须正确诊断传统代码审查的痛点。

常见的表述是"人工审查太慢"。这个诊断是表象,不是本质。等待时间的背后,是人类工程师的注意力资源的稀缺性与不可分割性。一个资深工程师审查一个复杂 PR,需要同时在脑中维护:代码逻辑的执行路径、潜在的安全漏洞模式、性能影响评估、与现有代码库的一致性、以及业务需求的符合程度。在认知科学上,这种多任务并行本身就是次优的——人类的工作记忆容量有限,真正的并行处理只存在于单纯的感知-运动任务,高阶认知任务本质上是串行的。

更深层的问题是审查质量的不可复制性。一个资深安全工程师花两小时审查的代码,其注意力分配模式无法被复制——审查同样代码的另一个人,关注点可能完全不同,遗漏的问题集合也会不同。这种不一致性不是能力问题,而是认知架构的必然结果。

Multi-Agent 系统解决的不是"让 AI 比人类审查得更快",而是解决了"为什么不同维度的审查质量是相互竞争的"这个根本矛盾。当安全审查和逻辑审查由独立的 Agent 并行进行,两者的注意力资源不再相互挤占,各自都可以达到最佳聚焦状态。

这个洞察也说明了为什么不能简单地把代码审查理解为"让 LLM 看一遍 diff"——单体 LLM 的单次调用同样面临注意力分配的问题,它做安全分析的深度和做逻辑分析的深度是相互制约的。

4.2 六 Agent 职责划分的设计原理

流水线由六个 Agent 组成:Dispatcher、Static Analysis、Logic Review、Security Scan、Supervisor 和 Reporter。这个划分不是任意的,而是基于审查维度的认知独立性原则。

Dispatcher 的存在是因为"为三个不同专家准备不同的简报"本身是一个需要理解全局的任务。它需要判断哪些文件对安全敏感、PR 的业务意图是什么、整体复杂度如何——这些判断会影响每个 Review Agent 的关注重点,是整个流水线的元信息层。将 Dispatcher 剥离出来的工程价值在于:它使得 Review Agent 的 prompt 可以更聚焦(“专注于 Dispatcher 告诉你的关注点”),而不需要每个 Review Agent 都从头理解整个 PR。

Static Analysis、Logic Review、Security Scan 三者的分工边界是"认知域的独立性":静态分析关注代码的形式质量(复杂度、命名、文档),不需要理解业务逻辑;逻辑审查关注代码行为的正确性(边界条件、并发安全),不需要懂 OWASP;安全审查关注漏洞模式(注入、越权、敏感数据暴露),有独立于业务逻辑的分析框架。三者之间的信息重叠率低,天然适合并行化。

Supervisor 不是"汇总结果"的简单角色,而是承担了三个重要职责:冲突仲裁(当不同 Agent 给出矛盾意见时)、置信度评估(判断哪些发现足够可信值得上报)、以及升级决策(哪些问题需要人工介入)。将这三个职责合并在一个 Supervisor 中,是因为它们本质上都依赖于对全局信息的综合判断,分拆反而会引入更多协调复杂性。

Reporter 单独存在是因为"为不同受众生成不同形式的报告"是一个需要理解受众的任务。开发者需要具体的修复建议,团队 Lead 需要风险评估摘要,安全团队需要漏洞的技术细节。Reporter 的职责是将 Supervisor 的结构化输出转化为针对特定受众的自然语言报告。

可以合并或拆分吗? Logic Review 和 Security Scan 在理论上存在部分重叠(如并发漏洞既是逻辑问题也可能是安全问题),但拆分的好处大于代价——两者有不同的知识背景要求,合并后的 prompt 会变得过于宽泛,降低专注度。Static Analysis 完全可以用非 LLM 工具(ESLint、SonarQube)替代,引入 LLM-based Static Analysis Agent 主要用于处理那些无法被规则引擎捕获的"语义级"代码质量问题。

4.3 并行与串行的架构决策:依赖关系分析

代码审查流水线中最重要的并行化决策是 Static Analysis、Logic Review、Security Scan 三者的并行执行。这不是性能优化,而是基于依赖关系分析的正确架构选择。

并行的前提条件:三个 Review Agent 的工作是否相互独立?即,A Agent 的输入是否依赖于 B Agent 的输出?答案是:在代码审查场景中,三者都以 PR diff 作为唯一输入,相互之间没有数据依赖。Logic Review 不需要等待 Static Analysis 的结果,Security Scan 不需要等待 Logic Review 的发现。因此,并行化在逻辑上是正确的。

但并行化并不总是适合的。有两种情况下串行优于并行:第一,当下游 Agent 的工作依赖于上游 Agent 的输出时(如 Performance Agent 需要 Architecture Agent 先完成高层架构分析,才能判断性能问题的根因);第二,当并行 Agent 的结果可能互相矛盾且矛盾会影响后续工作时(此时需要先串行运行一个仲裁步骤)。

并行化还带来了Fan-In 同步问题:Supervisor 必须等待所有并行 Agent 完成后才能汇总。LangGraph 通过图的拓扑自动处理这个同步——当多条边指向同一个节点时,该节点在所有前驱节点完成后才会执行。但这意味着整体延迟由最慢的 Agent 决定。在设计中需要为每个并行 Agent 设置独立的超时限制,避免一个慢 Agent 阻塞整个流水线。

<ol><li><span><span><code><span><span leaf="">// Fan-out parallel execution with timeout protection</span></span></code></span></span></li><li><span><span><code><span><span leaf="">graph topology</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; dispatcher&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;static_analysis &nbsp;</span></span><span><span leaf="">(</span></span><span><span leaf="">parallel</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; dispatcher&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;logic_review &nbsp; &nbsp;&nbsp;</span></span><span><span leaf="">(</span></span><span><span leaf="">parallel</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; dispatcher&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;security_scan &nbsp; &nbsp;</span></span><span><span leaf="">(</span></span><span><span leaf="">parallel</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// All three must complete before supervisor runs</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; static_analysis&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;supervisor</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; logic_review &nbsp; &nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;supervisor</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; security_scan &nbsp;&nbsp;</span></span><span><span leaf="">→</span></span><span><span leaf="">&nbsp;supervisor</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Each agent has independent timeout</span></span></code></span></span></li><li><span><span><code><span><span leaf="">wrapped_static_analysis</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">with</span></span><span><span leaf="">&nbsp;timeout</span></span><span><span leaf="">(</span></span><span><span leaf="">seconds</span></span><span><span leaf="">=</span></span><span><span leaf="">30</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;agent</span></span><span><span leaf="">=</span></span><span><span leaf="">"static_analysis"</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; result&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;static_analysis_agent</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;timed_out</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">static_findings</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[],</span></span><span><span leaf="">&nbsp;errors</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[</span></span><span><span leaf="">"static_analysis timeout"</span></span><span><span leaf="">]}</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;result</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Supervisor handles partial results gracefully</span></span></code></span></span></li><li><span><span><code><span><span leaf="">supervisor</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; available_findings&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;collect_non_empty</span></span><span><span leaf="">(</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; state</span></span><span><span leaf="">.</span></span><span><span leaf="">static_findings</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; state</span></span><span><span leaf="">.</span></span><span><span leaf="">logic_findings</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; state</span></span><span><span leaf="">.</span></span><span><span leaf="">security_findings</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; timeout_agents&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;detect_timeouts</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">.</span></span><span><span leaf="">errors</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Degrade gracefully: report with available data + note missing coverage</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;timeout_agents</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; add_caveat</span></span><span><span leaf="">(</span></span><span><span leaf="">"Analysis incomplete: {timeout_agents} timed out"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; aggregate_and_score</span></span><span><span leaf="">(</span></span><span><span leaf="">available_findings</span></span><span><span leaf="">)</span></span></code></span></span></li></ol>

4.4 置信度评分体系:量化不确定性

置信度评分是代码审查 Agent 系统中最精妙也最容易被忽视的设计元素。表面上是一个 0-1 的浮点数,实质上是 Agent 对自身输出可靠性的元认知能力。

为什么需要置信度?LLM 的输出在不同类型的任务上有完全不同的可靠性分布:指出一个明显的 SQL 注入漏洞( WHERE id='{user_input}')是高置信度任务;判断一段复杂的并发代码是否存在竞态条件,是低置信度任务。如果不对这两类输出进行区分,会导致误报率过高——高优先级的报警被大量低质量警告淹没,工程师的审查疲劳使真正重要的问题被忽略。

如何量化置信度?可以从三个维度综合评估:证据强度(是否有确定的代码模式支持,如硬编码密码字符串 vs 可能的逻辑错误)、多 Agent 共识(同一问题被多个 Agent 独立发现,置信度加成)、历史准确率(对于特定类别的问题,该 Agent 的历史假阳性率是多少)。

<ol><li><span><span><code><span><span leaf="">// Confidence calibration model</span></span></code></span></span></li><li><span><span><code><span><span leaf="">compute_confidence</span></span><span><span leaf="">(</span></span><span><span leaf="">finding</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; base_confidence&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;agent_assessed_confidence</span></span><span><span leaf="">(</span></span><span><span leaf="">finding</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Boost if multiple agents confirm the same issue</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; corroborating_agents&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;find_same_issue_in_other_agents</span></span><span><span leaf="">(</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; finding</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">.</span></span><span><span leaf="">all_findings</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; confirmation_boost&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">0.1</span></span><span></span><span><span leaf="">*</span></span><span><span leaf="">&nbsp;len</span></span><span><span leaf="">(</span></span><span><span leaf="">corroborating_agents</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Penalize if similar issues were false positives historically</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; historical_fp_rate&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;lookup_historical_fp_rate</span></span><span><span leaf="">(</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; category</span></span><span><span leaf="">=</span></span><span><span leaf="">finding</span></span><span><span leaf="">.</span></span><span><span leaf="">category</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; pattern</span></span><span><span leaf="">=</span></span><span><span leaf="">finding</span></span><span><span leaf="">.</span></span><span><span leaf="">code_pattern</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; fp_penalty&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;historical_fp_rate&nbsp;</span></span><span><span leaf="">*</span></span><span></span><span><span leaf="">0.3</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;clamp</span></span><span><span leaf="">(</span></span><span><span leaf="">base_confidence&nbsp;</span></span><span><span leaf="">+</span></span><span><span leaf="">&nbsp;confirmation_boost&nbsp;</span></span><span><span leaf="">-</span></span><span><span leaf="">&nbsp;fp_penalty</span></span><span><span leaf="">,</span></span><span></span><span><span leaf="">0.0</span></span><span><span leaf="">,</span></span><span></span><span><span leaf="">1.0</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Confidence threshold drives action</span></span></code></span></span></li><li><span><span><code><span><span leaf="">route_by_confidence</span></span><span><span leaf="">(</span></span><span><span leaf="">finding</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;finding</span></span><span><span leaf="">.</span></span><span><span leaf="">confidence&nbsp;</span></span><span><span leaf="">&gt;=</span></span><span></span><span><span leaf="">0.9</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;AUTO_REPORT &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span></span><span><span leaf="">// high confidence</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">elif</span></span><span><span leaf="">&nbsp;finding</span></span><span><span leaf="">.</span></span><span><span leaf="">confidence&nbsp;</span></span><span><span leaf="">&gt;=</span></span><span></span><span><span leaf="">0.7</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;REPORT_WITH_NOTE &nbsp;&nbsp;</span></span><span><span leaf="">// moderate confidence</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">elif</span></span><span><span leaf="">&nbsp;finding</span></span><span><span leaf="">.</span></span><span><span leaf="">confidence&nbsp;</span></span><span><span leaf="">&gt;=</span></span><span></span><span><span leaf="">0.5</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;QUEUE_FOR_REVIEW &nbsp;&nbsp;</span></span><span><span leaf="">// needs verification</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">else</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;DISCARD &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span></span><span><span leaf="">// too uncertain</span></span></code></span></span></li></ol>

置信度低时的处理是另一个设计重点。置信度低的发现不应该被简单丢弃,而应该触发不同的工作流:要么请求人工确认,要么用更强的 LLM 模型重新分析,要么标注为"待观察"进入人工审查队列。将这些处理路径编码到图的条件边中,是 LangGraph 的典型用法。

4.5 冲突仲裁机制:多源信息融合

当 Logic Review 和 Security Scan 对同一段代码给出矛盾意见时,Supervisor 面临一个典型的多源信息融合问题。

例如:Logic Review 认为"这个加密密钥的硬编码虽然不规范,但在当前业务场景下是可接受的临时方案"(低严重性),而 Security Scan 认为"硬编码加密密钥是严重的安全漏洞,必须立即修复"(高严重性)。这两个判断都有其内在逻辑,但结论相反。

冲突仲裁的核心原则是:在安全问题上,遵循最保守的判断(Security Agent 胜出);在代码质量问题上,遵循最专业的专家(域内专家 Agent 胜出);在业务逻辑问题上,需要引入业务上下文(可能需要人工确认)。这个分层原则不能硬编码,而应该通过 Supervisor 的 prompt 工程传达给模型。

<ol><li><span><span><code><span><span leaf="">// Conflict resolution with domain priority</span></span></code></span></span></li><li><span><span><code><span><span leaf="">Supervisor</span></span><span><span leaf="">.</span></span><span><span leaf="">resolve_conflicts</span></span><span><span leaf="">(</span></span><span><span leaf="">findings</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; conflicts&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;detect_contradictions</span></span><span><span leaf="">(</span></span><span><span leaf="">findings</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">for</span></span><span><span leaf="">&nbsp;conflict&nbsp;</span></span><span><span leaf="">in</span></span><span><span leaf="">&nbsp;conflicts</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;conflict</span></span><span><span leaf="">.</span></span><span><span leaf="">domain&nbsp;</span></span><span><span leaf="">==</span></span><span></span><span><span leaf="">"security"</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Security findings always take precedence - fail safe</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; winner&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;highest_severity_among</span></span><span><span leaf="">(</span></span><span><span leaf="">conflict</span></span><span><span leaf="">.</span></span><span><span leaf="">agents_findings</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolution&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">"security_conservative"</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">elif</span></span><span><span leaf="">&nbsp;conflict</span></span><span><span leaf="">.</span></span><span><span leaf="">domain&nbsp;</span></span><span><span leaf="">==</span></span><span></span><span><span leaf="">"business_logic"</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Logic conflicts need human judgment</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag_for_human_review</span></span><span><span leaf="">(</span></span><span><span leaf="">conflict</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;reason</span></span><span><span leaf="">=</span></span><span><span leaf="">"logic_conflict"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolution&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">"escalate"</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">else</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Other conflicts: use confidence-weighted voting</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; winner&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;max</span></span><span><span leaf="">(</span></span><span><span leaf="">conflict</span></span><span><span leaf="">.</span></span><span><span leaf="">agents_findings</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;key</span></span><span><span leaf="">=</span></span><span><span leaf="">lambda</span></span><span><span leaf="">&nbsp;f</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;f</span></span><span><span leaf="">.</span></span><span><span leaf="">confidence</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolution&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">"confidence_weighted"</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; log_conflict_resolution</span></span><span><span leaf="">(</span></span><span><span leaf="">conflict</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;resolution</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;winner</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;merged_findings_with_resolved_conflicts</span></span></code></span></span></li></ol>

更深层的冲突仲裁问题是元层面的不一致性:两个 Agent 对同一代码的理解可能从根本上不同,而不只是结论不同。这种情况下,最好的处理是让 Supervisor 意识到理解层面的歧义存在,并将这种歧义本身上报给人工审查者,而不是强行选出一个"正确答案"。

4.6 Human-in-the-Loop 的触发策略

Human-in-the-Loop 不是"兜底机制",而是整个系统设计的一个核心组成部分。关键的设计问题是:什么情况下必须触发人工,什么情况下 AI 可以自主决定

这个问题的本质是风险与效率的权衡。人工介入增加了延迟和成本,但降低了错误决策的风险。理想的触发策略应该在两个维度上都做到最优:不漏掉真正需要人工的情况,也不用人工审查不需要的情况。

触发人工的条件可以分为三类:基于绝对规则(无论置信度如何,特定类型的问题必须人工确认,如涉及生产数据库 schema 变更的 PR);基于置信度阈值(综合评分低于某个值,说明系统对这个 PR 的理解不够充分);基于影响范围(PR 涉及的文件数量、代码行数超过阈值,或涉及核心业务路径的文件)。

<ol><li><span><span><code><span><span leaf="">// Human-in-the-loop trigger policy</span></span></code></span></span></li><li><span><span><code><span><span leaf="">evaluate_human_review_need</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; triggers&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">[]</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Absolute rules: always escalate regardless of confidence</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;has_critical_security_finding</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; triggers</span></span><span><span leaf="">.</span></span><span><span leaf="">append</span></span><span><span leaf="">(</span></span><span><span leaf="">"critical_security_vulnerability"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;affects_core_business_path</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">.</span></span><span><span leaf="">changed_files</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; triggers</span></span><span><span leaf="">.</span></span><span><span leaf="">append</span></span><span><span leaf="">(</span></span><span><span leaf="">"core_business_path_modified"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Threshold-based: escalate when system is uncertain</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">.</span></span><span><span leaf="">overall_score&nbsp;</span></span><span><span leaf="">&lt;</span></span><span></span><span><span leaf="">50</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; triggers</span></span><span><span leaf="">.</span></span><span><span leaf="">append</span></span><span><span leaf="">(</span></span><span><span leaf="">f</span></span><span><span leaf="">"low_confidence_score: {state.overall_score}"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;count_unresolved_conflicts</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">&gt;</span></span><span></span><span><span leaf="">2</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; triggers</span></span><span><span leaf="">.</span></span><span><span leaf="">append</span></span><span><span leaf="">(</span></span><span><span leaf="">"multiple_agent_conflicts"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Impact-based: escalate for large changes</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">if</span></span><span><span leaf="">&nbsp;pr_change_size</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">&gt;</span></span><span><span leaf="">&nbsp;LARGE_PR_THRESHOLD</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; triggers</span></span><span><span leaf="">.</span></span><span><span leaf="">append</span></span><span><span leaf="">(</span></span><span><span leaf="">"large_pr_risk"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span></span><span><span leaf="">(</span></span><span><span leaf="">len</span></span><span><span leaf="">(</span></span><span><span leaf="">triggers</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">&gt;</span></span><span></span><span><span leaf="">0</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;triggers</span></span><span><span leaf="">)</span></span></code></span></span></li></ol>

Human-in-the-Loop 的实现需要与工程实际结合。在代码审查场景中,最实用的形式不是"等待人工回复后才生成报告",而是"生成报告的同时,将需要人工关注的部分明确标注,并通知相关专家"。这种非阻塞式的人工介入在工程实践中摩擦更低,也更容易推广。

4.7 系统的失败模式分析

任何生产系统都需要提前分析其失败模式。在代码审查流水线中,各 Agent 的失败风险和影响是不对称的。

Security Scan 是最容易产生错误影响的 Agent。它的假阳性(误报不存在的漏洞)会浪费工程师时间,更危险的是假阴性(漏掉真实漏洞)可能导致安全事故。Security Scan 的 prompt 设计需要平衡误报率和召回率——过于保守的 prompt 会产生大量低置信度警告,过于宽松的 prompt 会漏掉真实问题。在实践中,建议使用两段式策略:第一遍宽松扫描(高召回),第二遍对第一遍的结果进行精确验证(高精确率)。

Supervisor 是最难降级的 Agent。如果 Supervisor 失败(LLM API 超时、输出格式异常),整个流水线都会停顿,因为没有任何下游处理可以在没有聚合结果的情况下进行。Supervisor 的降级策略应该是:维护一个基于规则的备用仲裁逻辑(纯启发式,不依赖 LLM),当主 Supervisor 失败时自动接管,生成质量较低但结构完整的汇总结果。

Dispatcher 的失败影响是局部的——即使 Dispatcher 无法生成优质的审查计划,三个 Review Agent 仍然可以使用通用 prompt 进行审查,只是失去了针对性。这使得 Dispatcher 的降级相对简单:使用预定义的默认审查计划作为 fallback。

<ol><li><span><span><code><span><span leaf="">// Degradation hierarchy</span></span></code></span></span></li><li><span><span><code><span><span leaf="">run_with_degradation</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">try</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; dispatcher_result&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;dispatcher_agent</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">except</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; dispatcher_result&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;DEFAULT_REVIEW_PLAN &nbsp;</span></span><span><span leaf="">// fallback: generic plan</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; log_degradation</span></span><span><span leaf="">(</span></span><span><span leaf="">"dispatcher"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Review agents run regardless (isolated failures)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; static_result &nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;run_with_timeout</span></span><span><span leaf="">(</span></span><span><span leaf="">static_analysis_agent</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;timeout</span></span><span><span leaf="">=</span></span><span><span leaf="">30</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; logic_result &nbsp;&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;run_with_timeout</span></span><span><span leaf="">(</span></span><span><span leaf="">logic_review_agent</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;timeout</span></span><span><span leaf="">=</span></span><span><span leaf="">45</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; security_result&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;run_with_timeout</span></span><span><span leaf="">(</span></span><span><span leaf="">security_scan_agent</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;timeout</span></span><span><span leaf="">=</span></span><span><span leaf="">30</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Supervisor failure is critical - use rule-based fallback</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">try</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; supervisor_result&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;supervisor_agent</span></span><span><span leaf="">(</span></span><span><span leaf="">merged_state</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">except</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; supervisor_result&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;rule_based_supervisor</span></span><span><span leaf="">(</span></span><span><span leaf="">merged_state</span></span><span><span leaf="">)</span></span><span></span><span><span leaf="">// no LLM</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; log_degradation</span></span><span><span leaf="">(</span></span><span><span leaf="">"supervisor"</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;severity</span></span><span><span leaf="">=</span></span><span><span leaf="">"high"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Reporter can always generate something from whatever is available</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;reporter_agent</span></span><span><span leaf="">(</span></span><span><span leaf="">available_results</span></span><span><span leaf="">)</span></span></code></span></span></li></ol>

5. 生态演进:协议、治理与可观测性

5.1 A2A Protocol 的深层意义:Agent 间的合约

2025 年 Google 主导发布的 A2A(Agent-to-Agent)Protocol,从技术上看是一套 HTTP 协议规范,但其深层意义是定义了 Agent 之间的合约

在没有 A2A 之前,不同框架、不同厂商的 Agent 之间协作的唯一方式是"定制集成"——专门为 A 框架的 Agent 编写与 B 框架 Agent 通信的胶水代码。这种方式不可扩展:如果有 10 个 Agent 框架,最坏情况下需要 45 个点对点的集成方案。

A2A 解决这个问题的方式是标准化三个层次:Agent 能力的声明方式(Agent Card,JSON 格式的能力描述)、任务的生命周期管理(Task 对象,包含状态、消息、artifact)、以及传输层协议(HTTP + SSE 用于流式输出)。

Agent Card 是 A2A 中最值得关注的概念。每个 Agent 发布一个 JSON 格式的"服务契约",声明它能做什么、接受什么输入、产生什么输出、如何认证。这使得 Agent 可以被动态发现——一个 Meta-Agent 可以在运行时查询 Agent 注册表,根据任务需求找到最合适的 Agent,而无需在设计时硬编码协作关系。这是迈向 Level 2 自组织 Multi-Agent 系统的关键基础设施。

<ol><li><span><span><code><span><span leaf="">// Agent Card: the contract between agents</span></span></code></span></span></li><li><span><span><code><span><span leaf="">AgentCard</span></span><span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">{</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; name</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"SecurityScanAgent"</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; version</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"1.2.0"</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; description</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"OWASP Top 10 security scanner for code diffs"</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; capabilities</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; input_modalities</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[</span></span><span><span leaf="">"text"</span></span><span><span leaf="">],</span></span><span></span><span><span leaf="">// what it accepts</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; output_modalities</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[</span></span><span><span leaf="">"json"</span></span><span><span leaf="">],</span></span><span></span><span><span leaf="">// what it produces</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; streaming</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">true</span></span><span><span leaf="">,</span></span><span></span><span><span leaf="">// supports incremental output</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; push_notifications</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">false</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">},</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; skills</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[{</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; id</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"scan_diff"</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; input_schema</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">diff</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"string"</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;context</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"object?"</span></span><span><span leaf="">},</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; output_schema</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">findings</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"array[Finding]"</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;score</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"number"</span></span><span><span leaf="">}</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">}],</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; authentication</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">schemes</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">[</span></span><span><span leaf="">"bearer"</span></span><span><span leaf="">]},</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; endpoint</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"https://agents.example.com/security-scan"</span></span></code></span></span></li><li><span><span><code><span><span leaf="">}</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Dynamic agent discovery</span></span></code></span></span></li><li><span><span><code><span><span leaf="">MetaAgent</span></span><span><span leaf="">.</span></span><span><span leaf="">find_agent_for_task</span></span><span><span leaf="">(</span></span><span><span leaf="">task</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; candidates&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;registry</span></span><span><span leaf="">.</span></span><span><span leaf="">search</span></span><span><span leaf="">(</span></span><span><span leaf="">task</span></span><span><span leaf="">.</span></span><span><span leaf="">required_capability</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; scored&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;rank_by_fitness</span></span><span><span leaf="">(</span></span><span><span leaf="">candidates</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;task</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;scored</span></span><span><span leaf="">[</span></span><span><span leaf="">0</span></span><span><span leaf="">]</span></span><span></span><span><span leaf="">// best match</span></span></code></span></span></li></ol>

跨厂商互操作性是 A2A 最大的价值所在,也是最难实现的部分。“合约"的真正难题不在于 JSON 格式的统一,而在于语义层面的对齐:两个不同厂商的 SecurityScanAgent 都声明了相同的能力描述,但它们对"critical severity"的定义可能完全不同。解决这个问题需要的是能力语义的标准化,这是一个比协议设计更长远的工程,涉及行业标准的形成过程。

5.2 MCP 的本质:为什么工具调用需要标准化协议

Anthropic 主导的 MCP(Model Context Protocol)解决的是另一个层面的互操作性问题:Agent 与工具之间,而非 Agent 与 Agent 之间。

在 MCP 之前,每个 LLM 应用需要为每个工具编写专有的集成代码——调用 GitHub API 的封装、调用 Jira API 的封装、调用 Slack API 的封装。这些封装的实现质量参差不齐,维护负担随工具数量线性增长。更重要的是,这些封装都是针对特定应用的,无法在不同 AI 应用间复用。

MCP 定义了三类原语: resources(数据源,如文件系统、数据库)、 tools(可调用函数,有副作用)、 prompts(预定义的提示模板)。任何实现了 MCP Server 接口的工具,可以被任何支持 MCP 客户端的 AI 应用调用。这创造了一个双边市场:工具开发者只需实现一次 MCP Server,就能被所有 AI 应用使用;AI 应用只需集成一次 MCP 客户端,就能调用所有 MCP 工具。

MCP 与 OpenAPI 的本质区别在于设计目标的不同。OpenAPI 是为了人类开发者(或代码生成器)理解和调用 HTTP API 而设计的——它的主要价值是文档化和客户端代码生成。MCP 是为了 LLM 在运行时动态理解和选择工具而设计的——它的主要价值是工具发现和语义理解。MCP Server 的工具描述不只是参数文档,而是为 LLM 理解"这个工具能做什么、应该在什么情况下使用"而优化的自然语言描述。这是两种截然不同的信息消费者(人类 vs LLM),导致了完全不同的设计哲学。

<ol><li><span><span><code><span><span leaf="">// MCP: unified interface for all tools</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Before MCP: N tools × M applications = N×M integrations</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// After MCP: N tools (each with MCP server) + M applications (each with MCP client)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= N + M integrations</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// MCP Server definition (tool side)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">MCP_Server</span></span><span><span leaf="">(</span></span><span><span leaf="">name</span></span><span><span leaf="">=</span></span><span><span leaf="">"github_tools"</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; tools</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">-</span></span><span><span leaf="">&nbsp;name</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;create_pr</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; description</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"Create a pull request in a GitHub repository. Use when..."</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; input_schema</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">repo</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;title</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;body</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;base_branch</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;head_branch</span></span><span><span leaf="">}</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">-</span></span><span><span leaf="">&nbsp;name</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;list_issues</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; description</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"List open issues for a repository. Use when user wants..."</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; input_schema</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">repo</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;labels</span></span><span><span leaf="">?,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">?}</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Tool description is optimized for LLM understanding, not human developers</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">// Agent runtime: discover tools dynamically</span></span></code></span></span></li><li><span><span><code><span><span leaf="">agent</span></span><span><span leaf="">.</span></span><span><span leaf="">available_tools&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;mcp_client</span></span><span><span leaf="">.</span></span><span><span leaf="">list_tools</span></span><span><span leaf="">(</span></span><span><span leaf="">server</span></span><span><span leaf="">=</span></span><span><span leaf="">"github_tools"</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">// Agent can now call any tool without bespoke integration code</span></span></code></span></span></li><li><span><span><code><span><span leaf="">agent</span></span><span><span leaf="">.</span></span><span><span leaf="">call_tool</span></span><span><span leaf="">(</span></span><span><span leaf="">"create_pr"</span></span><span><span leaf="">,</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">repo</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"..."</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;title</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"..."</span></span><span><span leaf="">,</span></span><span></span><span><span leaf="">...})</span></span></code></span></span></li></ol>

MCP 的工程价值还体现在安全边界上。通过 MCP Server 的中间层,可以在工具调用路径上插入权限验证、调用审计、速率限制等控制逻辑,而不需要修改 Agent 代码本身。这使得"谁可以调用什么工具"的策略管理可以集中化、标准化。

5.3 Autonomous System 的边界问题:治理而非技术

随着 Multi-Agent 系统能力的提升,一个核心问题浮现:什么任务应该让 Agent 自主决策,什么必须保留人工控制权

这个问题的本质是治理问题,不是技术问题。技术上,AI 系统今天已经能够自主完成很多复杂任务;问题在于,哪些任务在发生错误时,后果是不可接受的。

一个实用的框架是从两个维度评估:决策的可逆性决策的影响范围。可逆性高(生成草稿、提供建议)+ 影响范围小(只影响一个用户的工作)= 完全可以自主决策;可逆性低(删除数据、发送通知)+ 影响范围大(影响整个团队或用户群)= 必须人工确认。

在代码审查场景中,这意味着:AI 可以自主决定哪些代码问题是低严重性的(可逆:人工可以后续覆盖);但 AI 不应该自主决定阻止一个 PR 合并(不可逆:可能阻塞整个团队的工作流)。这种边界设计不是技术约束,而是组织决策——哪些决策权可以委托给 AI,需要团队基于风险偏好明确划定。

监管合规是另一个维度。在金融、医疗、法律等高监管行业,某些决策在法律上必须由具备资质的人类专业人员做出,AI 只能作为辅助工具。这不是 AI 能力的限制,而是监管框架对"人类责任链"的要求。构建 Multi-Agent 系统时,需要提前识别哪些决策节点存在监管要求,并确保在这些节点上人类决策者的介入是强制性的,而非可选的。

随着系统自主程度的提升,还需要设计明确的能力边界声明机制:系统应该在用户可见的层面清楚说明"我可以自主完成的范围"和"超出这个范围时我会请求人工确认”。这不是技术设计,而是用户信任关系的管理。AI 系统最容易失去信任的时刻,往往不是因为能力不足,而是因为在用户不知情的情况下做了超出预期的决策。

5.4 可观测性的架构设计:为什么 Multi-Agent 的调试难 10 倍

调试单体 LLM 应用相对直观:有一个输入,一个输出,中间可能有几步 Chain。调试 Multi-Agent 系统是另一个量级的挑战,原因在于以下几个本质特征。

涌现行为:Multi-Agent 系统的整体行为不是各 Agent 行为的简单叠加。当 Logic Review 发现了一个问题,这个发现会影响 Supervisor 的路由决策,进而影响 Reporter 的报告重点。系统的最终输出依赖于多个 Agent 的交互模式,而不只是单个 Agent 的输出质量。在没有完整追踪的情况下,很难理解某个特定输出是如何被"协作生成"的。

非确定性放大:单个 LLM 调用存在温度参数引入的非确定性,Multi-Agent 系统中多个 LLM 调用的非确定性相互叠加,使得相同输入产生不同输出的概率大幅上升。这对调试造成了严重困扰——相同的 PR 在不同时刻可能产生不同的审查结论,定位"为什么这次结果和上次不同"需要对比两次完整的执行链路。

状态爆炸:当系统有 6 个 Agent、每个 Agent 有 5 个可能的状态,理论上的系统状态空间是 5^6 = 15,625 种。即使实际中大多数状态不可达,可能的执行路径仍然极其庞大,使得基于测试用例的全覆盖几乎不可能实现。

针对这些挑战,Multi-Agent 系统需要专门的可观测性维度,超越传统的"日志+指标+追踪"三支柱:

Agent-level 追踪不只追踪 LLM 调用,而是追踪 Agent 的决策逻辑——Supervisor 为什么选择了这个路由?置信度评估的依据是什么?这需要在 Agent 代码中埋入结构化的决策日志,而不只是依赖 LLM API 的调用日志。

状态 diff 可视化:每次状态转移都记录"转移前状态 vs 转移后状态的差异",而不只是全量状态快照。这使得在后验分析时能够快速定位是哪个节点引入了错误的状态变化。

跨 Agent 因果链:当最终报告中出现了一个错误结论,能够自动追溯到"这个结论来自哪个 Agent 的哪次 LLM 调用"——这需要在状态中保留每条信息的"来源标注"(provenance tracking)。

<ol><li><span><span><code><span><span leaf="">// Multi-Agent observability: four dimensions</span></span></code></span></span></li><li><span><span><code><span><span leaf="">class</span></span><span></span><span><span leaf="">AgentObserver</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; trace_agent_decision</span></span><span><span leaf="">(</span></span><span><span leaf="">agent</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;decision</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;reasoning</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Not just WHAT was decided, but WHY</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; emit_trace</span></span><span><span leaf="">({</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; agent</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;agent</span></span><span><span leaf="">.</span></span><span><span leaf="">name</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; decision</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;decision</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reasoning</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;reasoning</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state_snapshot</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;hash</span></span><span><span leaf="">(</span></span><span><span leaf="">state</span></span><span><span leaf="">),</span></span><span></span><span><span leaf="">// lightweight, not full state</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; timestamp</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;now</span></span><span><span leaf="">()</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">})</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; trace_state_transition</span></span><span><span leaf="">(</span></span><span><span leaf="">from_state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;to_state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;trigger_node</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Record what changed, not full state</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; diff&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;compute_state_diff</span></span><span><span leaf="">(</span></span><span><span leaf="">from_state</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;to_state</span></span><span><span leaf="">)</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; emit_trace</span></span><span><span leaf="">({</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">"state_transition"</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; node</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;trigger_node</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; added_fields</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;diff</span></span><span><span leaf="">.</span></span><span><span leaf="">added</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; modified_fields</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;diff</span></span><span><span leaf="">.</span></span><span><span leaf="">modified</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; provenance</span></span><span><span leaf="">:</span></span><span></span><span><span leaf="">{</span></span><span><span leaf="">node</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;trigger_node</span></span><span><span leaf="">,</span></span><span><span leaf="">&nbsp;timestamp</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;now</span></span><span><span leaf="">()}</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">})</span></span></code></span></span></li><li><span><span><code></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; trace_causal_chain</span></span><span><span leaf="">(</span></span><span><span leaf="">final_finding</span></span><span><span leaf="">):</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">// Trace backwards: which agent produced this finding?</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; chain&nbsp;</span></span><span><span leaf="">=</span></span><span></span><span><span leaf="">[]</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; current&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;final_finding</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">while</span></span><span><span leaf="">&nbsp;current</span></span><span><span leaf="">.</span></span><span><span leaf="">source</span></span><span><span leaf="">:</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; chain</span></span><span><span leaf="">.</span></span><span><span leaf="">append</span></span><span><span leaf="">({</span></span><span><span leaf="">agent</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;current</span></span><span><span leaf="">.</span></span><span><span leaf="">source</span></span><span><span leaf="">.</span></span><span><span leaf="">agent</span></span><span><span leaf="">,</span></span><span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; input</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;current</span></span><span><span leaf="">.</span></span><span><span leaf="">source</span></span><span><span leaf="">.</span></span><span><span leaf="">input_hash</span></span><span><span leaf="">,</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output</span></span><span><span leaf="">:</span></span><span><span leaf="">&nbsp;current</span></span><span><span leaf="">.</span></span><span><span leaf="">source</span></span><span><span leaf="">.</span></span><span><span leaf="">output_hash</span></span><span><span leaf="">})</span></span></code></span></span></li><li><span><span><code><span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; current&nbsp;</span></span><span><span leaf="">=</span></span><span><span leaf="">&nbsp;current</span></span><span><span leaf="">.</span></span><span><span leaf="">source</span></span></code></span></span></li><li><span><span><code><span></span><span><span leaf="">return</span></span><span><span leaf="">&nbsp;chain &nbsp;</span></span><span><span leaf="">// full provenance chain</span></span></code></span></span></li></ol>

LangSmith 是目前 LangGraph 生态中最成熟的可观测性工具,提供了图执行的可视化追踪、各节点延迟分布、以及基于历史数据的 prompt 性能分析。在生产部署中,LangSmith 与 OpenTelemetry 的集成使得 Agent 系统的追踪数据可以流入企业已有的可观测性基础设施(Datadog、Jaeger 等),避免形成独立的可观测性孤岛。

5.5 主流框架横向对比与演进路径

理解框架选择需要理解各框架背后的核心设计哲学,而不只是功能对比表格。

LangGraph 的设计哲学是"显式控制优先"——将 Agent 的控制流以图的形式显式化,使复杂行为变得可视化、可测试、可审计。这种设计对工程可控性的追求高于灵活性,因此学习曲线陡峭但生产成熟度高。适合对系统行为有精确要求、需要人工干预点、以及需要故障恢复能力的生产场景。

AutoGen 的设计哲学是"对话驱动"——通过多个 Agent 之间的自然语言对话来完成任务。这更接近人类团队的协作方式,在涉及讨论、辩论、迭代改进的任务(如代码生成与审查的双 Agent 循环)上表现自然。代价是控制流难以预测,不适合需要确定性行为的流水线场景。

CrewAI 的定位是快速原型——声明式的角色定义让非专业工程师也能快速搭建 Multi-Agent 应用。但其抽象层次较高,当需要精细控制行为时,CrewAI 的封装反而成为障碍。

框架的演进趋势是向"运行时互操作性"收敛。A2A 协议推动的方向是:无论底层使用什么框架,Agent 都能通过标准协议协作。这意味着框架选择将越来越聚焦于"开发体验和内部架构",而不是"互操作性"——后者将由协议层保证。

Multi-Agent 系统能力的演进路径可以描述为四个阶段。第一阶段(当前主流)是预定义工作流的自动化执行——图结构在设计时确定,Agent 角色固定;第二阶段是动态任务分解——系统可以根据任务特征自动调整 Agent 组合和调用顺序,但 Agent 本身仍然是预定义的;第三阶段是 Agent 自组织——一个 Meta-Agent 可以从 Agent 注册表中动态招募最合适的 Agent 组成团队,这需要 A2A 协议的成熟;第四阶段是自我改进——系统可以分析自身的历史性能,识别工作流中的瓶颈并主动重新设计。当前工业界处于第一到第二阶段之间,第三阶段的基础设施(A2A、MCP)正在成熟,第四阶段仍在研究阶段。


结语

Multi-Agent 系统的价值不在于"让更多 AI 一起工作",而在于用组织设计的思维解决认知架构的局限性。单体 LLM 的注意力机制本质上是串行的、全局的,无法在需要深度并行专业化分析的任务上达到最优质量。Multi-Agent 通过将认知任务分布到多个专能节点,让每个节点都能以最优的注意力配置处理其专属领域,并通过结构化协作协议将分散的局部洞察聚合为全局智能。

LangGraph 提供了构建生产级 Multi-Agent 系统所需的完整基础设施:强类型状态管理使系统行为具有可预测性,图定义使控制流可视化,Checkpointing 使故障恢复和时间旅行调试成为可能,interrupt 机制使 Human-in-the-Loop 成为一等公民。掌握 LangGraph,意味着掌握了将组织设计原则转化为可运行 AI 系统的能力。

代码审查流水线是这些设计原则的一个具体投影。它的架构决策——为什么并行而非串行、为什么六个 Agent 而非更少、为什么需要置信度评分——都有清晰的工程理由和权衡逻辑。这种"理由先于实现"的思维方式,是构建可维护、可演进的 Multi-Agent 系统的基础。

生态层面,A2A 和 MCP 正在解决跨系统、跨厂商的互操作性问题,为下一阶段的 Agent 自组织奠定协议基础。但更深层的挑战——自主决策的边界划定、可观测性架构的设计、以及治理框架的建立——是纯技术以外的命题,需要工程、产品、法务的跨职能协作。

AI 系统不缺乏能力,缺乏的是与其能力相匹配的治理成熟度。这是接下来 Multi-Agent 系统演进最重要的课题。


持续更新中,关注我获取更多干货