LangGraph 实战:用状态图编排多智能体协作

在构建 AI 应用时,很多人最早接触的是单链式 Agent,但当任务变复杂,就会遇到LangGraph 这类”图编排”框架。它把多智能体协作拆解成一个有向状态图:每个节点是一个函数或大模型调用,边决定下一步去哪,条件边还能让流程自动循环、分支或暂停等待人工。相比把全部逻辑写进一条 ReAct 链,用图来描述可以让”自我纠错、人工审核、多角色分工”变得清晰且可维护。下面我从概念到代码,带你把 LangGraph 真正用起来。

一、为什么需要图编排

我们之前在《手写 ReAct Agent》里用”思考-行动-观察”循环解决过工具调用问题。那种单链结构胜在简单,但一旦需要”生成→校验→不达标则重生成””先调研再撰写””关键步骤人工确认”,硬编码的循环就会迅速失控。LangGraph 的核心价值,正是把这些控制流显式建模成图。

图由三类要素组成:State(在节点间传递的数据)、Node(一个处理步骤,可以是普通函数,也可以是一个封装好的 Agent)、Edge(流转方向,含普通边与按条件分支的 Conditional Edge)。当流程需要记住历史、支持中断恢复时,再挂一个 Checkpointer 即可。

举个直观的例子:把一次”写周报”的任务想成图,比把它塞进一个超长提示词要清楚得多——收集数据、起草、主管审核、退回修改,每一步都是图上的一个节点,箭头写明谁接谁。一旦某一步要插入”人工确认”,只需在对应节点前加一条中断边,而不必重写整段逻辑。

要素作用
State在节点间传递、累积的数据(如对话消息列表)
Node一个处理步骤:普通函数或预置 Agent
Edge控制流转方向,连接两个节点
Conditional Edge按返回值分支或循环,实现自我纠错
Checkpointer持久化状态,支持断点续跑与人工介入

二、最小可运行:带自我纠错的图

先看一个能体现”图优势”的例子:生成答案后交给校验节点,不达标就回到生成节点重来,达标才结束。这种”生成-校验”循环用单链要写一堆 if/while,用图只需一条条件边。

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    question: str
    answer: str
    attempts: int

def generate(state: State) -> dict:
    # 调用 LLM 生成答案(此处用伪代码表示实际调用)
    reply = llm(state["question"])
    return {"answer": reply, "attempts": state["attempts"] + 1}

def validate(state: State) -> str:
    # 校验答案是否合规,返回分支名
    return "ok" if check(state["answer"]) else "retry"

builder = StateGraph(State)
builder.add_node("generate", generate)
builder.add_node("validate", validate)
builder.add_edge(START, "generate")
builder.add_edge("generate", "validate")
builder.add_conditional_edges(
    "validate", validate,
    {"ok": END, "retry": "generate"}   # 不达标则回到 generate
)
graph = builder.compile()

注意 validate 返回的是字符串分支名,add_conditional_edges 据此决定走向。正是这一行,让你无需手写 while 循环就能表达”最多重试 N 次”的控制流——只要在 generate 里对 attempts 做上限判断即可。

三、多智能体协作:Supervisor 模式

真实业务里,一个 Agent 往往干不完所有事。更常见的做法是” supervisor(主管)+ 多个 worker(专员)”:主管读上下文决定下一步交给谁,各专员执行完再把结果交回主管,直到可以收尾。下面用 create_react_agent 快速封装两个专员。

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.prebuilt import create_react_agent

# 两个专员:一个负责联网调研,一个负责成稿
research = create_react_agent(model, tools=[web_search],
                              prompt="你是调研助手,只做资料收集")
writer = create_react_agent(model, tools=[],
                            prompt="你是撰写助手,负责把资料整理成稿")

def supervisor(state: MessagesState) -> str:
    last = state["messages"][-1].content
    if "需要调研" in last:   return "research"
    if "可以成稿" in last:   return "writer"
    return "finish"

g = StateGraph(MessagesState)
g.add_node("supervisor", supervisor)
g.add_node("research", research)
g.add_node("writer", writer)
g.add_edge(START, "supervisor")
g.add_conditional_edges("supervisor", lambda s: s["next"],
                        {"research": "research", "writer": "writer", "finish": END})
g.add_edge("research", "supervisor")
g.add_edge("writer", "supervisor")
app = g.compile()

这里每个专员本身也是带工具的 Agent(Function Calling 在底层驱动),而 LangGraph 负责”谁先谁后、何时汇合”。比起把多角色逻辑揉进一个超长 prompt,图结构让职责边界和流转一目了然。

主管模式的妙处在于可扩展:你随时能再挂一个”审核专员”,让成稿在对外发布前过一遍事实核查;节点之间是松耦合的,替换或升级某一个专员并不会牵动整条链路。这也是它区别于”把所有指令写进一个系统提示词”的根本原因——能力边界被显式拆分成了可测试的单元。

四、持久化与人工介入

复杂流程常常要”跑一半暂停,等人确认再继续”。LangGraph 用 Checkpointer 把每一步状态存下来,配合 interrupt_before 就能在指定节点前中断。这是单链脚本最难补的能力。

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()
app = graph.compile(checkpointer=memory,
                    interrupt_before=["validate"])  # 校验前暂停等人审

cfg = {"configurable": {"thread_id": "task-1"}}
app.invoke({"question": "写一份迁移方案"}, cfg)
# 此时流程停在 validate 前,可人工查看中间结果
app.invoke(None, cfg)   # 人工确认后继续

thread_id 把整条对话/任务状态串起来,刷新页面、服务重启都不会丢上下文。做客服、审批、长流程自动化时,这个能力几乎是刚需。

五、接上统一网关与对外服务

生产环境里,模型调用最好走一层网关,方便换模型、做配额和审计。我们之前搭过 One API 统一网关,只要把 base_url 指过去即可,代码无需改动模型名以外的任何逻辑。

import os
os.environ["OPENAI_API_KEY"] = "sk-xxxx"            # 网关颁发的密钥
os.environ["OPENAI_BASE_URL"] = "https://gw.example.com/v1"

from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="qwen2.5-72b-instruct", temperature=0)

如果想把编排好的 Agent 暴露成 HTTP 接口,套一层 FastAPI 就能对外提供服务,再用 Nginx 反向代理 收口域名与证书。本地开发用 VS Code 远程连容器,体验最顺。

from fastapi import FastAPI
api = FastAPI()

@api.post("/agent/run")
async def run(payload: dict):
    result = app.invoke({"question": payload["q"]})
    return {"answer": result["answer"]}

六、LangGraph 与 Dify、单链 Agent 怎么选

方式适合场景灵活性上手成本
单链 Agent(ReAct)简单问答、单次工具调用低低
LangGraph 图编排多步、需循环/分支/人工介入高中
Dify 可视化业务快速搭建、少写代码中低

如果团队不想写代码、要快速搭一个知识库问答,Dify + Ollama 本地 RAG 更合适;如果要精确控制每一步、做多智能体编排和人工审批,LangGraph 是更稳的选择;二者甚至能共存——Dify 负责某个业务节点,LangGraph 负责把多个节点串起来。

七、常见坑与排障

1. State 字段忘了用 Annotated 累积。 消息类状态要用 Annotated[list, operator.add],否则节点返回会覆盖而不是追加历史,导致上下文丢失。

2. 条件边返回值与映射不一致。 add_conditional_edges 的映射字典必须覆盖函数所有可能返回值,漏掉一个就会在运行时报”no route”错误。

3. 忘了传 thread_id。 用了 Checkpointer 却没在调用时带 configurable.thread_id,状态不会持久化,中断恢复也就失效。

4. 循环没有出口。 条件边指向自身节点时务必设置上限或终止条件,否则会出现无限循环把额度刷光。

八、小结

LangGraph 把”智能体如何协作”从隐含在 prompt 和循环里的黑盒,变成一张可读、可调试、可中断的状态图。它不一定是每个项目的首选——简单任务用单链或 Dify 更快——但一旦你的 AI 应用需要多角色分工、自我纠错或人工把关,图编排就是那把最合适的锤子。

落地建议很明确:先用最小图验证”生成-校验”闭环,再叠 supervisor 做多角色分工,最后用 checkpointer 补上中断恢复。模型层统一走网关、接入层用 FastAPI 加反向代理收口,整套链路就能从本地原型平滑过渡到线上服务。记住,工具是为问题服务的——只有当任务真正需要多步编排与人工把关时,才值得引入图的复杂度;否则,一条 ReAct 链或一个 Dify 工作流反而更轻、更省心。

上一篇 direnv 实战:进入目录自动加载项目环境变量
下一篇 MLA 多头潜在注意力实战:大模型如何压缩 KV 缓存
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]