LangGraph 多智能体实战:从零搭建 Multi-Agent 协作系统

前言

单智能体(Single Agent)在处理复杂任务时存在明显瓶颈:上下文窗口有限、工具集过大导致决策混乱、无法并行处理子任务。

LangGraph 通过图结构来编排多个智能体的协作流程,支持循环、条件分支和人工介入,是当前 Multi-Agent 系统最成熟的工程化方案之一。

本文将通过完整代码示例,带你从零搭建一个可运行的 Multi-Agent 协作系统。


一、核心概念

1.1 为什么需要多智能体?

假设你要做一个市场调研报告任务,单 Agent 的处理方式是:

复制代码
用户 → 单 Agent → 搜索 → 分析 → 写报告 → 输出
         ↑ 所有能力堆在一个 Agent 里
         ↑ 上下文越长越容易出错

多智能体方式:

复制代码
用户
 ↓
Planner Agent(分解任务)
 ├── Researcher Agent(信息搜集)  → 并行
 ├── Analyst Agent(数据分析)     → 并行
 └── Writer Agent(报告撰写)
 ↓
最终输出

多智能体的三个核心优势:

  • 专业化:每个 Agent 只做一件事,工具集小,决策更精准
  • 并行化:独立子任务可以同时执行,缩短总耗时
  • 可控性:每个环节可单独调试、审计和干预

1.2 LangGraph 的核心抽象

复制代码
LangGraph = StateGraph(状态图) + Nodes(节点) + Edges(边)
概念 含义 类比
State 在整个图中传递的共享状态 流水线上的物料
Node 一个处理步骤(通常是调用 LLM 或一个函数) 流水线上的工位
Edge 节点之间的流转路径 传送带
Conditional Edge 根据条件动态决定下一步走向 分流器

二、环境准备

2.1 安装依赖

bash 复制代码
pip install langgraph langchain-openai langchain python-dotenv

2.2 配置 API Key

python 复制代码
# .env 文件
OPENAI_API_KEY=sk-your-key-here
# 或使用兼容 OpenAI 格式的其他模型
# OPENAI_BASE_URL=https://your-proxy.com/v1

三、实战一:基础多智能体流水线

3.1 需求说明

构建一个博客文章生成系统,由三个专业 Agent 协作完成:

复制代码
输入: 文章主题
  ↓
[Researcher]  → 生成关键词 + 模拟搜索结果
  ↓
[Writer]      → 根据研究资料撰写文章
  ↓
[Editor]      → 审查并修改文章
  ↓
输出: 最终文章

3.2 定义共享状态

python 复制代码
# state.py
from typing import TypedDict, List, Optional
from langgraph.graph import START, StateGraph, END

class BlogState(TypedDict):
    """博客文章生成流程的共享状态"""
    
    # 输入
    topic: str                    # 文章主题
    
    # Researcher 产出
    keywords: List[str]           # 研究关键词
    research_notes: str          # 研究笔记
    
    # Writer 产出
    draft: str                   # 文章草稿
    
    # Editor 产出
    edited_article: str          # 编辑后的文章
    review_comments: List[str]   # 审稿意见
    
    # 流程控制
    needs_revision: bool         # 是否需要重新修改
    revision_count: int           # 已修改次数

3.3 实现三个 Agent 节点

python 复制代码
# agents.py
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
import os

# 初始化模型(可替换为任意兼容的 LLM)
llm = ChatOpenAI(
    model="gpt-4o-mini",          # 根据需求选择模型
    temperature=0.7,
    max_retries=2
)

# ─── Researcher Agent ────────────────────────────

RESEARCHER_SYSTEM = """你是一位专业的技术研究员。
你的任务是根据文章主题,生成相关的研究关键词,并撰写研究笔记。
研究笔记应包含:核心概念解释、技术要点、常见问题和最佳实践。
输出格式:
关键词:xxx, yyy, zzz
---
研究笔记:
(详细内容)
"""

def researcher_node(state: BlogState) -> BlogState:
    """研究节点:根据主题生成关键词和研究笔记"""
    messages = [
        SystemMessage(content=RESEARCHER_SYSTEM),
        HumanMessage(content=f"请研究以下主题,生成关键词和研究笔记:\n\n{state['topic']}")
    ]
    
    response = llm.invoke(messages)
    content = response.content
    
    # 解析返回内容(简单解析,生产环境建议用结构化输出)
    lines = content.split('\n')
    keywords = []
    notes_lines = []
    in_notes = False
    
    for line in lines:
        if line.startswith('关键词') or line.startswith('Keywords'):
            kw_part = line.split(':')[-1] if ':' in line else line.split(':')[-1]
            keywords = [k.strip() for k in kw_part.split(',') if k.strip()]
            keywords += [k.strip() for k in kw_part.split(',') if k.strip()]
        elif '研究笔记' in line or '笔记' in line:
            in_notes = True
        elif in_notes:
            notes_lines.append(line)
    
    return {
        **state,
        "keywords": keywords[:5],  # 最多保留5个关键词
        "research_notes": '\n'.join(notes_lines).strip()
    }

# ─── Writer Agent ────────────────────────────────

WRITER_SYSTEM = """你是一位资深的技术博客作家。
根据提供的研究笔记和关键词,撰写一篇结构清晰、深入浅出的技术博客。
要求:
1. 字数 800-1500 字
2. 包含代码示例(如适用)
3. 语言通俗易懂,面向有一定基础的开发者
4. 结构:引言 → 核心概念 → 实战示例 → 总结
"""

def writer_node(state: BlogState) -> BlogState:
    """写作节点:根据研究笔记撰写文章草稿"""
    context = f"""
主题:{state['topic']}
关键词:{', '.join(state['keywords'])}
研究笔记:
{state['research_notes']}
"""
    messages = [
        SystemMessage(content=WRITER_SYSTEM),
        HumanMessage(content=context)
    ]
    
    response = llm.invoke(messages)
    
    return {
        **state,
        "draft": response.content
    }

# ─── Editor Agent ────────────────────────────────

EDITOR_SYSTEM = """你是一位严格的技术内容编辑。
审查文章草稿,检查以下方面:
1. 技术准确性
2. 逻辑连贯性
3. 代码示例正确性
4. 语言表达清晰度

如果文章质量合格,直接输出文章内容。
如果需要修改,先输出审稿意见(以"审稿意见:"开头),然后输出修改后的文章(以"修改后文章:"开头)。
最多允许修改2次,如果超过仍然不合格,直接输出当前版本。
"""

def editor_node(state: BlogState) -> BlogState:
    """编辑节点:审查并修改文章"""
    revision_count = state.get("revision_count", 0)
    
    # 超过2次修改直接通过
    if revision_count >= 2:
        return {
            **state,
            "edited_article": state["draft"],
            "needs_revision": False,
            "revision_count": revision_count
        }
    
    context = f"""
主题:{state['topic']}
文章草稿:
{state['draft']}
"""
    messages = [
        SystemMessage(content=EDITOR_SYSTEM),
        HumanMessage(content=context)
    ]
    
    response = llm.invoke(messages)
    content = response.content
    
    # 判断是否需要通过审稿
    needs_revision = "审稿意见" in content and "修改后文章" in content
    review_comments = []
    edited = state["draft"]
    
    if needs_revision:
        # 解析审稿意见
        if "审稿意见:" in content:
            comments_part = content.split("审稿意见:")[-1].split("修改后文章:")[0]
            review_comments = [c.strip() for c in comments_part.split('\n') if c.strip()]
        # 解析修改后的文章
        if "修改后文章:" in content:
            edited = content.split("修改后文章:")[-1].strip()
    else:
        edited = content  # 直接通过,内容即为最终文章
    
    return {
        **state,
        "edited_article": edited,
        "review_comments": review_comments,
        "needs_revision": needs_revision and revision_count < 2,
        "revision_count": revision_count + (1 if needs_revision else 0)
    }

3.4 组装流程图

python 复制代码
# graph.py
from graph import BlogState
from agents import researcher_node, writer_node, editor_node

def build_blog_graph():
    """构建博客文章生成的 StateGraph"""
    graph = StateGraph(BlogState)
    
    # 添加节点
    graph.add_node("researcher", researcher_node)
    graph.add_node("writer", writer_node)
    graph.add_node("editor", editor_node)
    
    # 添加边(定义流转顺序)
    graph.add_edge(START, "researcher")       # 起点 → researcher
    graph.add_edge("researcher", "writer")    # researcher → writer
    graph.add_edge("writer", "editor")        # writer → editor
    
    # 条件边:编辑后是否需要修改?
    def should_revise(state: BlogState) -> str:
        if state.get("needs_revision", False):
            return "writer"   # 回到 writer 修改
        return END             # 结束
    
    graph.add_conditional_edges("editor", should_revise)
    
    return graph.compile()

# 运行示例
if __name__ == "__main__":
    app = build_blog_graph()
    
    result = app.invoke({
        "topic": "LangGraph 多智能体系统实战",
        "keywords": [],
        "research_notes": "",
        "draft": "",
        "edited_article": "",
        "review_comments": [],
        "needs_revision": False,
        "revision_count": 0
    })
    
    print("=" * 60)
    print("最终文章:")
    print("=" * 60)
    print(result["edited_article"])
    print("=" * 60)
    print(f"修改次数: {result['revision_count']}")
    if result["review_comments"]:
        print(f"审稿意见: {result['review_comments']}")

四、实战二:带并行处理的多智能体系统

4.1 需求说明

构建一个代码审查系统,同时启动多个专业审查 Agent 并行工作:

复制代码
输入: 代码文件内容
  ↓
[调度器] 分发代码给多个审查 Agent(并行)
  ├── Security Reviewer   → 安全检查
  ├── Performance Reviewer → 性能分析
  ├── Style Reviewer      → 代码规范
  └── Logic Reviewer      → 逻辑正确性
  ↓
[Aggregator] 汇总所有审查意见
  ↓
输出: 综合审查报告

4.2 实现并行处理

LangGraph 的 send API 支持将一个状态同时发送给多个节点

python 复制代码
# code_review_graph.py
from typing import TypedDict, List
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)

class ReviewState(TypedDict):
    code: str                          # 待审查的代码
    filename: str                      # 文件名
    security_review: str               # 安全审查结果
    performance_review: str            # 性能审查结果
    style_review: str                  # 规范审查结果
    logic_review: str                  # 逻辑审查结果
    final_report: str                  # 最终汇总报告

# ─── 各审查 Agent ────────────────────────────────

def security_reviewer(state: ReviewState) -> dict:
    prompt = f"""你是安全专家。审查以下代码的安全问题:
- SQL 注入、XSS、命令注入
- 敏感信息泄露
- 不安全的依赖或配置
- 权限校验缺失

代码文件:{state['filename']}
代码内容:

{state['code']}

复制代码
只输出审查意见,格式:
[安全] 问题等级(High/Medium/Low): 具体描述"""
    
    response = llm.invoke(prompt)
    return {"security_review": response.content}

def performance_reviewer(state: ReviewState) -> dict:
    prompt = f"""你是性能优化专家。审查以下代码的性能问题:
- 时间复杂度/空间复杂度
- 数据库查询优化
- 内存泄漏风险
- 并发安全问题

代码内容:

{state['code']}

复制代码
只输出审查意见,格式:
[性能] 问题等级(High/Medium/Low): 具体描述"""
    
    response = llm.invoke(prompt)
    return {"performance_review": response.content}

def style_reviewer(state: ReviewState) -> dict:
    prompt = f"""你是代码规范专家。审查以下代码的规范问题:
- 命名规范(变量、函数、类名)
- 代码格式(缩进、空行、注释)
- 最佳实践遵循情况
- 可读性和可维护性

代码内容:

{state['code']}

复制代码
只输出审查意见,格式:
[规范] 问题等级(High/Medium/Low): 具体描述"""
    
    response = llm.invoke(prompt)
    return {"style_review": response.content}

def logic_reviewer(state: ReviewState) -> dict:
    prompt = f"""你是逻辑审查专家。审查以下代码的逻辑正确性:
- 边界条件处理
- 空值/异常处理
- 业务逻辑正确性
- 算法实现是否正确

代码内容:

{state['code']}

复制代码
只输出审查意见,格式:
[逻辑] 问题等级(High/Medium/Low): 具体描述"""
    
    response = llm.invoke(prompt)
    return {"logic_review": response.content}

# ─── 汇总 Agent ──────────────────────────────────

def aggregator_node(state: ReviewState) -> dict:
    prompt = f"""你是代码审查汇总专家。
根据以下四位审查专家的意见,生成一份综合审查报告。

## 安全审查
{state.get('security_review', '未提供')}

## 性能审查
{state.get('performance_review', '未提供')}

## 规范审查
{state.get('style_review', '未提供')}

## 逻辑审查
{state.get('logic_review', '未提供')}

生成格式:
# 代码审查报告 - {state['filename']}

## 总体评估
(给出 Overall 评级:通过/需要修改/重大问题)

## 详细意见
(按 High → Medium → Low 排序汇总所有问题)

## 修改建议
(给出优先级排序的修改建议列表)
"""
    
    response = llm.invoke(prompt)
    return {"final_report": response.content}

# ─── 构建并行图 ──────────────────────────────────

def build_review_graph():
    graph = StateGraph(ReviewState)
    
    # 添加所有节点
    graph.add_node("security", security_reviewer)
    graph.add_node("performance", performance_reviewer)
    graph.add_node("style", style_reviewer)
    graph.add_node("logic", logic_reviewer)
    graph.add_node("aggregator", aggregator_node)
    
    # START 同时发送到四个审查节点(并行!)
    graph.add_edge(START, "security")
    graph.add_edge(START, "performance")
    graph.add_edge(START, "style")
    graph.add_edge(START, "logic")
    
    # 所有审查节点完成后,进入汇总节点
    graph.add_edge("security", "aggregator")
    graph.add_edge("performance", "aggregator")
    graph.add_edge("style", "aggregator")
    graph.add_edge("logic", "aggregator")
    
    # 汇总完成后结束
    graph.add_edge("aggregator", END)
    
    return graph.compile()

# 运行示例
if __name__ == "__main__":
    # 读取示例代码
    with open("example_code.py", "r", encoding="utf-8") as f:
        code_content = f.read()
    
    app = build_review_graph()
    result = app.invoke({
        "code": code_content,
        "filename": "example_code.py",
        "security_review": "",
        "performance_review": "",
        "style_review": "",
        "logic_review": "",
        "final_report": ""
    })
    
    print(result["final_report"])

4.3 关键技巧:用 Send API 实现动态并行

上面的实现中四个审查节点是固定的。如果审查维度是动态生成 的(比如根据代码类型决定需要哪些审查),可以用 Send API:

python 复制代码
from langgraph.types import Send

def dispatch_reviewers(state: ReviewState) -> List[Send]:
    """动态决定需要启动哪些审查 Agent"""
    # 根据代码内容动态决定审查维度
    dispatches = []
    
    # 总是做安全和逻辑审查
    dispatches.append(Send("security", state))
    dispatches.append(Send("logic", state))
    
    # 如果是 Python 代码,加性能和规范审查
    if state["filename"].endswith(".py"):
        dispatches.append(Send("performance", state))
        dispatches.append(Send("style", state))
    
    # 如果是前端代码,加可访问性审查
    if any(state["filename"].endswith(ext) for ext in [".js", ".jsx", ".tsx", ".vue"]):
        dispatches.append(Send("accessibility", state))  # 需提前定义此节点
    
    return dispatches

# 在图构建中使用:
# graph.add_conditional_edges(START, dispatch_reviewers)

五、生产实践要点

5.1 状态持久化(Checkpointer)

生产环境中,流程可能因为各种原因中断(API 限流、服务重启等)。LangGraph 内置了 Checkpointer 机制,支持从中断点恢复:

python 复制代码
from langgraph.checkpoint.sqlite import SqliteSaver

# 使用 SQLite 持久化状态
with SqliteSaver.from_conn_string("checkpoints.db") as saver:
    app = build_blog_graph()
    # 每次执行都会自动保存状态到数据库
    result = app.invoke(initial_state, config={"configurable": {"thread_id": "session-001"}})

# 中断后恢复:
# 只需要用相同的 thread_id 重新 invoke,会自动从断点继续

5.2 人工介入(Human-in-the-Loop)

某些关键决策需要人工确认,LangGraph 支持在流程中暂停等待人工输入

python 复制代码
from langgraph.types import interrupt

def human_approval_node(state: BlogState) -> dict:
    """需要人工审批是否发布文章"""
    approval = interrupt({
        "question": "是否发布这篇文章?",
        "article": state["edited_article"]
    })
    # approval 是人工通过 UI 或其他方式返回的结果
    if approval.get("approved"):
        return {**state, "published": True}
    else:
        return {**state, "published": False, "revision_instructions": approval.get("feedback", "")}

# 在图中添加此节点,并在需要人工介入的位置插入
# graph.add_node("human_approval", human_approval_node)

5.3 错误处理与重试

python 复制代码
import time
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=30)
)
def safe_llm_call(messages):
    """带重试的 LLM 调用"""
    try:
        return llm.invoke(messages)
    except Exception as e:
        print(f"LLM 调用失败: {e},正在重试...")
        raise

def researcher_node_with_retry(state: BlogState) -> BlogState:
    """带错误处理的 Researcher 节点"""
    try:
        response = safe_llm_call([
            SystemMessage(content=RESEARCHER_SYSTEM),
            HumanMessage(content=f"研究主题:{state['topic']}")
        ])
        # ... 解析 response ...
        return {**state, "research_notes": "..."}
    except Exception as e:
        # 出错时返回错误状态,让图有机会处理
        return {**state, "error": str(e), "research_notes": "研究失败,请检查配置。"}

5.4 流式输出(提升用户体验)

长流程的任务,用户等待体验很差。LangGraph 支持流式返回每个节点的结果

python 复制代码
app = build_blog_graph()

# 流式执行
for chunk in app.stream(initial_state, stream_mode="updates"):
    for node_name, node_output in chunk.items():
        print(f"[{node_name}] 完成")
        if node_name == "writer":
            print(f"草稿预览: {node_output['draft'][:200]}...")
        elif node_name == "editor":
            print(f"审稿完成,修改次数: {node_output['revision_count']}")

# 或者只流式返回最终输出(token 级别)
for token in app.stream(initial_state, stream_mode="messages"):
    print(token.content, end="", flush=True)

六、与其他 Multi-Agent 框架对比

特性 LangGraph AutoGen CrewAI 原生 Function Calling
图结构编排 ✅ 原生支持 ❌ 基于对话 ✅ 基于任务序列 ❌ 无编排
循环/条件分支 ✅ 完整支持 ⚠️ 有限支持 ⚠️ 有限支持 ❌ 不支持
状态管理 ✅ TypedDict 强类型 ⚠️ 隐式传递 ⚠️ 隐式传递 ❌ 无状态
人工介入 ✅ interrupt API ❌ 不支持 ❌ 不支持 ❌ 不支持
持久化 ✅ Checkpointer ⚠️ 需自行实现 ⚠️ 需自行实现 ❌ 不支持
学习曲线 中等 最低
适用场景 复杂流程编排 对话式协作 角色扮演任务 简单工具调用

选择建议:

  • 需要精细控制流程 (循环、条件、人工介入)→ LangGraph
  • 快速原型、对话式多 Agent → AutoGen
  • 角色扮演式任务分配 → CrewAI
  • 简单工具调用 → Function Calling

七、常见问题 FAQ

Q: LangGraph 和 LangChain 是什么关系?

LangGraph 是 LangChain 生态的一部分,但可以独立使用。LangChain 提供模型调用和工具封装,LangGraph 负责流程编排。两者配合最好,但 LangGraph 也支持直接使用任意 LLM 调用库。

Q: 图中节点之间如何共享数据?

通过共享的 State 对象。每个节点接收 State,返回需要更新的字段。LangGraph 会自动合并返回值到 State 中。

Q: 如何处理节点执行失败?

推荐做法:① 节点内部捕获异常,返回错误状态字段;② 在图中增加错误处理的专用节点;③ 用 try/except 包裹整个 app.invoke() 调用。

Q: 支持异步执行吗?

支持。所有节点函数都可以是 async def,调用时用 await app.ainvoke()。并行节点会自动并发执行。

Q: 如何调试 LangGraph 流程?

推荐使用 LangSmith(LangChain 官方可观测性平台)进行追踪。或者设置 verbose=True 查看每个节点的输入输出。也可以把中间状态打印出来:

python 复制代码
for event in app.stream(state, stream_mode="debug"):
    print(event)  # 打印每个节点的详细执行信息

总结

LangGraph 的核心价值在于把 Multi-Agent 协作从"黑盒"变成了"白盒"------你可以精确控制每个 Agent 的行为、它们之间的数据流转、以及整个流程的走向。

三个关键点:

  1. State 设计是核心 ------ 合理设计共享状态,让数据流转清晰可追踪
  2. 节点要单一职责 ------ 每个 Agent 只做一件事,工具集越小越精准
  3. 生产环境必做三件事 ------ 状态持久化、错误处理、关键节点人工介入

Multi-Agent 不是银弹,对于简单任务,单 Agent + Function Calling 更高效。但当任务复杂度达到需要分解、需要专业化、需要可控性这个阈值时,LangGraph 是目前最成熟的工程化选择。


如有问题欢迎评论区讨论。

相关推荐
Yeats_Liao2 小时前
物联网接入层技术剖析(三):epoll在JVM中的映射
java·linux·jvm·人工智能·物联网
97zz2 小时前
Claude+deepseek-v4pro+cc switch+VSCode AI编程配置教程(Java开发专属)
java·vscode·ai编程
菜菜小狗的学习笔记2 小时前
八股(九)杂七杂八
java·后端·spring
逍遥德2 小时前
Java编程高频的“技术点”-01:自定义全局异常处理器
java·开发语言·spring boot·后端
潜创微科技2 小时前
IT68353:双DP 1.4 + HDMI 2.0 + USB‑C 三合一转 HDMI 2.0 单芯片KVM切换方案
c语言·开发语言
羑悻的小杀马特2 小时前
工业时序数据选型的几点思考:从存储成本与查询延迟说起
数据库·人工智能
YsyaaabB2 小时前
ACM 模式通用代码模板
java·c++·python·算法
IT界的老黄牛3 小时前
从 MQ 积压追到事件总线:诊断 4K 线程吃光 7G 内存的实战
java·运维·rocketmq
我命由我123453 小时前
C++ - 面向对象 - 析构函数
android·c语言·开发语言·c++·visualstudio·visual studio·android runtime