Skip to content

LangGraph Agent 编排

你有没有遇到过这种情况:一个任务交给 AI,第一版跑出来的结果还不错,改了两轮需求之后,整个流程变得像一团线。Prompt 越来越长,中间产物不知道存在哪,出错后只能从头再来。

很多 Agent 项目就是这样从“单次模型调用”滑向“多阶段工作流”的。一旦你开始处理需求解析、资料召回、结构规划、产物生成、审稿、改写、交付检查,单纯靠 if-else 串联已经很难维护了。

1. 学习目标

读完这篇,我们会把 LangGraph 放在 Agent 系统的整体位置上看。重点理解四件事:为什么复杂任务需要一张图;节点之间应该用什么方式交接;条件路由和人工确认点怎么设计;哪些任务必须阻塞用户,哪些可以放到后台。

2. 为什么 Agent 系统需要一张图

前面几篇文章已经拆过资料记忆、上下文调度、Prompt 分层、任务状态和混合检索。单个模块能跑通之后,新的问题会冒出来:这些模块按什么顺序执行,哪些步骤可以并行,哪些结果必须人工确认,失败后从哪个中间状态恢复。

一个典型任务很少只有“输入 -> 生成 -> 输出”。用户提交目标后,系统至少要处理八类动作:

  1. 判断对象、场景、范围和交付格式。
  2. 召回项目文档、历史产物、官方资料和审稿规则。
  3. 生成结构,并标记资料缺口。
  4. 生成产物。
  5. 审稿,检查事实、结构、语气和引用。
  6. 根据审稿意见改写。
  7. 做格式、链接和交付前检查。
  8. 写回可复用资料、审稿结论和 trace。

这些步骤之间有依赖,也有分支。资料不足时要追问或补检索;事实冲突时要人工确认;审稿失败时要回到改写;格式检查失败时只修局部。

用一串 if-else 可以做出早期原型,但后续会遇到三个维护问题:

  • 中间结果难以复用。
  • 失败位置难以定位。
  • 新增 Agent 后容易影响既有路径。

LangGraph 适合承载这类流程,因为它把执行步骤、共享 State、条件路由、持久化和 interrupt 机制放到同一个图模型里。对 Agent 系统来说,这张图就是运行时契约:每个 Agent 只关心自己读写哪些字段,路由函数根据结构化结果决定下一步。

LangGraph 把执行步骤建模为图:节点是 Python 函数(可以只包含普通代码,也可以调用 LLM),边是节点之间的流转,条件边是根据当前 State 动态选择下一个节点的路由函数。它底层用消息传递(message passing)驱动执行,节点完成后把状态更新沿边发送给下一个节点,整个执行过程按离散的“super-step”推进。没有依赖关系的节点会在同一个 super-step 里并行运行,所以图模型天然支持并行分支,而不需要额外写并发控制代码。

3. 一条任务请求的主路径

先看一条最小可用的同步主路径:

txt
接收任务
  -> 解析任务目标
  -> 资料召回
  -> 结构规划
  -> 产物生成
  -> 审稿
  -> 是否通过?
      ├── 否 -> 改写 -> 再审稿
      └── 是 -> 交付检查 -> 返回结果

这条路径里,每个节点只承担一类责任,便于替换、重试和观测。

节点输入输出失败处理
解析任务目标用户需求、项目配置对象、场景、范围、交付格式缺少关键字段时返回澄清问题
资料召回任务目标、检索策略资料卡片、来源、可信度标记资料不足,不直接编造
结构规划任务目标、资料卡片任务结构、每部分要回答的问题冲突资料进入人工确认
产物生成结构、资料、风格规则产物版本保留低置信度标记
审稿产物、资料、检查清单问题清单、阻塞级别P0/P1 问题阻塞交付
改写产物、审稿意见修订产物保留修订说明
交付检查修订产物、格式规则可交付版本格式失败只回到局部修正

这张表需要早于代码出现,因为它定义的是图的边界。后面无论使用 LangGraph、Temporal,还是自研 workflow runner,都要先确认每个节点的输入输出契约。没有这份契约,Agent 数量增加以后,系统很容易变成“节点名称很多,但每个节点都在读写全部上下文”。

4. State 是 Agent 之间的公共契约

LangGraph 使用共享 State 让节点交接数据。Agent 系统的 State 可以按用途拆成几组:任务目标、资料来源、结构、产物版本、审稿问题和观测字段。

typescript
type AgentTaskState = {
  taskId: string;
  stage:
    | "intake"
    | "research"
    | "plan"
    | "execute"
    | "review"
    | "rewrite"
    | "delivery_check"
    | "done"
    | "blocked";
  goal: {
    topic: string;
    audience?: string;
    channel?: string;
    format?: string;
  };
  sources: Array<{
    id: string;
    title: string;
    url?: string;
    reliability: "official" | "project" | "secondary" | "unknown";
    usedInSections: string[];
  }>;
  plan?: Array<{
    heading: string;
    question: string;
    sourceIds: string[];
  }>;
  product?: string;
  reviewFindings: Array<{
    severity: "P0" | "P1" | "P2";
    message: string;
    targetSection?: string;
    resolved: boolean;
  }>;
  traceId: string;
};

节点应该围绕 State 字段编程。资料召回节点只负责写 sources,结构规划节点只读取 goalsources,审稿节点只读取 productsources 和检查清单。这样拆分以后,替换资料检索实现时,不需要改执行节点;替换审稿策略时,也不需要改结构规划节点。

State 设计时还要区分“给模型看的上下文”和“给系统看的控制字段”。产物草稿、资料卡片和风格规则可以进入模型上下文;stagetraceIdreviewFindings[].severityresolved 这类字段更适合作为路由、恢复和排障依据。把两类信息混在一段自然语言里,后续恢复任务时会缺少稳定判断点。

LangGraph 通过 reducer 合并各个节点返回的部分 State 更新。默认行为是覆盖,但对于列表字段(如 sourcesreviewFindings),通常需要追加语义,可以用 operator.addadd_messages 做 reducer,避免并行节点写同一份列表时互相覆盖。如果图的内部节点需要传递一些不需要暴露给调用方的字段,可以定义 private State channel;也可以为图显式声明 input/output schema,让外部只看到必要的字段。这些机制共同保证:节点之间只通过约定好的字段通信,新增或替换节点时不会污染全局 State。

5. 条件路由和人工确认点

Agent 任务里最重要的分支通常来自质量门禁:

  • 资料不足:回到资料召回,或向用户追问。
  • 来源冲突:进入人工确认点。
  • 结构缺少主问题:回到结构规划。
  • 审稿有 P0/P1 问题:回到改写。
  • 只有格式问题:进入格式修正,不重写产物。

用 LangGraph 表达时,条件路由应该尽量依赖结构化字段,避免让模型用一段自然语言决定下一步。例如审稿节点输出 blocked: trueblockingReasons: ['source_conflict'],路由函数再决定进入 humanReviewNode

人工确认点也应该是图的一部分。适合人工确认的地方包括:

  • 用户需求不完整。
  • 官方资料之间存在冲突。
  • 产物涉及法律、医疗、金融等高风险判断。
  • 交付前需要确认项目规范或敏感表述。

这些确认点不应该只散落在前端弹窗里,而要写回 State。任务暂停、恢复和排障时,系统需要知道它停在这里的原因、等待谁确认、确认后应该回到哪一个节点。LangGraph 的 interrupt 机制可以用来表达这种暂停点;业务层还需要把确认原因、候选选项和用户选择持久化下来。

实现动态路由时,LangGraph 提供条件边(add_conditional_edges)和 Command 两种选择。条件边只负责决定下一步;如果节点想同时更新 State 并指定下一步,可以返回 Command(update=..., goto=...)。需要人工确认时,节点里调用 interrupt(value) 暂停执行,并把确认请求持久化到 checkpointer;用户确认后通过 Command(resume=...) 恢复,执行会从暂停点继续。要注意:同一个节点的静态边和动态路由不要混用,否则两条路径可能同时执行,导致行为难以预料。

多 Agent 系统里常见的组织方式有两种:Supervisor 模式(一个路由节点统一调度多个子节点)和 Swarm 模式(节点之间可直接交接)。路由集中时更容易追踪和扩展;节点直接交接时延迟更低,但路由逻辑会分散。LangGraph 的低层图模型都能表达这两种模式,选择取决于你的团队更在意可追踪性还是端到端延迟。

在 Agent 项目里,人工确认点不宜过多。适合阻塞主路径的,通常是会影响事实安全、项目风险或产物方向的判断。普通语气微调、标题备选、段落顺序建议,可以交给审稿 Agent 或后台评估,不必每次都打断用户。

6. 同步路径和后台路径

Agent 系统里有些动作必须影响本次返回,有些动作可以后台完成。

路径任务是否阻塞用户
同步路径需求解析、资料召回、结构规划、产物生成、审稿、交付检查阻塞
人工确认路径资料冲突确认、交付前确认阻塞到用户决策
后台路径资料索引更新、审稿结论沉淀、指标聚合、trace 持久化不阻塞

这一区分会影响等待时间和交付稳定性。产物返回前必须完成基本事实检查;新的审稿规则写入长期知识库、资料索引刷新、指标聚合,可以放到后台。后台任务失败时要记录 trace、进入重试队列或待处理列表,不应该让用户已经收到的产物回滚。

这一区分还会直接影响持久化策略。LangGraph 通过 checkpointer(例如 PostgresSaver)在 super-step 边界保存 State。对于需要跨多次请求、等待用户确认或可能长时间运行的任务,持久化 checkpointer 是必需的;否则进程重启后任务状态会丢失,interrupt 也无法恢复。后台任务即使失败,也能从最后一个 committed checkpoint 重新调度,而不会让已经交付的产物回滚。

更稳妥的做法,是在 State 里显式记录后台任务的派生状态,例如 backgroundJobs: [{ type, status, retryCount }]。这样主路径可以先完成交付,运维和产品侧仍然能看到哪些资料写回失败、哪些审稿结论没有进入知识库。

7. 可观测性从图节点开始

图编排天然适合记录 trace。每个节点都可以对应一个 span,记录输入摘要、输出摘要、工具调用、耗时、失败原因和降级状态。

Agent 任务至少要记录这些字段:

  • taskIdtraceIdstage
  • 使用了哪些资料来源
  • 每个 Agent 的输入摘要和输出版本
  • 工具调用耗时与失败原因
  • 审稿问题数量和严重级别
  • 人工确认点的进入原因
  • 最终是否交付、是否降级交付

有了这些字段,用户反馈“这个结果像拼接”时,排查可以从 trace 开始:资料召回是否太宽,结构是否没有合并重复观点,审稿是否没有阻塞结构问题,改写是否只做了表面润色。

可观测性也会反过来影响图设计。如果一个节点里同时做资料召回、结构规划和产物生成,trace 只能告诉我们“执行节点失败”。拆成独立节点以后,失败原因会更接近可行动的修复项:检索策略要收窄、结构聚合要增强,或者审稿门禁要提高阻塞级别。

trace 不仅可以离线排查,也可以实时流给用户。LangGraph 的 astream_events(version='v2') 会输出 on_chat_model_streamon_tool_start/on_tool_endon_chain_start/on_chain_end 等事件,前端可以根据这些事件展示进度、工具调用和当前节点,而不是让用户面对一个长时间空白等待最终产物。

8. 总结

在 Agent 项目里,LangGraph 的主要价值是把任务执行过程从一次模型调用,组织成可恢复、可审计、可插入人工判断的工作流。

学习这部分时,可以先抓住三件事:

  1. State 字段是否表达了任务需要长期保留的中间结果。
  2. 节点是否有清晰的输入输出和失败处理。
  3. 路由条件是否来自可检查字段,而不是一段无法复盘的自然语言判断。

这三件事成立后,多智能体系统才有继续扩展的基础。后续增加事实核查 Agent、风格改写 Agent、交付检查 Agent,主要是在图里新增节点和路由,而不是让一个大 Prompt 继续膨胀。

参考资料

基于 MIT 协议开源