👨 作者简介:大家好,我是唐璜Taro ,全栈 领域创作者
✒️ 个人主页 :唐璜Taro
🚀 支持我:点赞👍+📝 评论 + ⭐️收藏
LangChain + LangGraph 多 Agent 实战:从工具链到工作流编排的万字精华指南
单个 Agent 能回答问题,多个 Agent 协作能解决复杂任务。本文从工具链集成讲起,深入 LangGraph 的状态机设计,最后用三个经典案例带你从零构建多 Agent 系统。

目录
- 第六章:经典案例二------研究报告生成(并行+聚合)
- [第七章:经典案例三------代码审查 Pipeline(串行+反馈循环)](#第七章:经典案例三——代码审查 Pipeline(串行+反馈循环))
- 第八章:生产环境最佳实践
- 第九章:总结与选型建议
第六章:经典案例二------研究报告生成(并行+聚合)
6.1 需求
生成一份完整的研究报告,多个 Agent 并行工作:
- 搜索 Agent × 3:分别从不同角度搜集资料
- 分析 Agent:汇总分析所有资料
- 写作 Agent:撰写报告
- 审校 Agent:检查质量,不合格则退回修改
6.2 完整代码
python
from typing import TypedDict, Annotated, List
from operator import add
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
# ============ 1. 定义状态 ============
class ResearchState(TypedDict):
topic: str # 研究主题
market_data: str # 市场搜索结果
tech_data: str # 技术搜索结果
competitor_data: str # 竞品搜索结果
analysis: str # 分析结论
draft: str # 报告草稿
review_feedback: str # 审校反馈
revision_count: int # 修改次数
final_report: str # 最终报告
messages: Annotated[list, add]
# ============ 2. 定义 LLM ============
llm = ChatOpenAI(model="gpt-4", temperature=0.3)
# ============ 3. 并行搜索节点 ============
def search_market(state: ResearchState) -> dict:
"""市场角度搜索"""
response = llm.invoke([
SystemMessage(content="你是市场研究分析师。从市场规模、增长趋势、用户需求角度分析以下主题。给出具体数据和观点。"),
HumanMessage(content=f"研究主题:{state['topic']}")
])
return {"market_data": response.content}
def search_tech(state: ResearchState) -> dict:
"""技术角度搜索"""
response = llm.invoke([
SystemMessage(content="你是技术研究专家。从技术架构、核心算法、技术栈角度分析以下主题。给出技术细节。"),
HumanMessage(content=f"研究主题:{state['topic']}")
])
return {"tech_data": response.content}
def search_competitor(state: ResearchState) -> dict:
"""竞品角度搜索"""
response = llm.invoke([
SystemMessage(content="你是竞品分析专家。列出主要竞品,分析各自的优劣势、定价策略、市场份额。"),
HumanMessage(content=f"研究主题:{state['topic']}")
])
return {"competitor_data": response.content}
# ============ 4. 分析节点 ============
def analysis_node(state: ResearchState) -> dict:
"""汇总分析"""
response = llm.invoke([
SystemMessage(content="""你是高级分析师。根据以下三个维度的调研数据,提炼关键洞察:
1. 核心发现(3-5条)
2. 市场机会
3. 风险提示
4. 战略建议"""),
HumanMessage(content=f"""
市场调研:{state['market_data']}
技术调研:{state['tech_data']}
竞品调研:{state['competitor_data']}
请输出分析结论。""")
])
return {"analysis": response.content}
# ============ 5. 写作节点 ============
def writing_node(state: ResearchState) -> dict:
"""撰写报告"""
revision_note = ""
if state.get("review_feedback"):
revision_note = f"\n\n审校反馈(请据此修改):{state['review_feedback']}"
response = llm.invoke([
SystemMessage(content="""你是专业报告撰写者。根据分析结论撰写一份结构清晰的研究报告。
报告结构:
1. 摘要(200字)
2. 市场分析
3. 技术分析
4. 竞品对比(表格)
5. 机会与风险
6. 建议与结论
要求:语言专业、数据充分、逻辑清晰。"""),
HumanMessage(content=f"""
分析结论:{state['analysis']}
{revision_note}""")
])
return {"draft": response.content, "revision_count": state.get("revision_count", 0) + 1}
# ============ 6. 审校节点 ============
def review_node(state: ResearchState) -> dict:
"""审校报告"""
response = llm.invoke([
SystemMessage(content="""你是报告审校专家。检查报告质量并给出评分和修改意见。
评分维度(1-10分):
- 完整性:是否覆盖所有必要内容
- 准确性:数据和结论是否合理
- 可读性:结构是否清晰,语言是否专业
如果总分 >= 24,返回 "APPROVED"
如果总分 < 24,返回具体修改意见。"""),
HumanMessage(content=f"请审校以下报告:\n\n{state['draft']}")
])
feedback = response.content
approved = "APPROVED" in feedback
if approved:
return {"final_report": state["draft"], "review_feedback": ""}
return {"review_feedback": feedback}
# ============ 7. 路由函数 ============
def should_continue_writing(state: ResearchState) -> str:
if state.get("final_report"):
return "finish"
if state.get("revision_count", 0) >= 3:
# 最多修改3次,强制通过
return "finish"
return "revise"
# ============ 8. 构建图 ============
graph = StateGraph(ResearchState)
# 添加节点
graph.add_node("search_market", search_market)
graph.add_node("search_tech", search_tech)
graph.add_node("search_competitor", search_competitor)
graph.add_node("analysis", analysis_node)
graph.add_node("writer", writing_node)
graph.add_node("reviewer", review_node)
# 入口:三个搜索并行执行
graph.set_entry_point("search_market")
graph.add_edge("search_market", "analysis")
graph.add_edge("search_tech", "analysis")
graph.add_edge("search_competitor", "analysis")
# 但从入口只能到 search_market,需要额外设置并行
# 实际上 LangGraph 的并行需要通过 Send API 或 fan-out 模式
# 串行链:搜索 → 分析 → 写作 → 审校
graph.add_edge("analysis", "writer")
graph.add_edge("writer", "reviewer")
# 审校后条件分支
graph.add_conditional_edges("reviewer", should_continue_writing, {
"finish": END,
"revise": "writer" # 不合格退回修改
})
# 编译
app = graph.compile()
# ============ 9. 执行 ============
result = app.invoke({
"topic": "2025年中国大模型行业应用市场",
"market_data": "", "tech_data": "", "competitor_data": "",
"analysis": "", "draft": "", "review_feedback": "",
"revision_count": 0, "final_report": "", "messages": []
})
print("=" * 60)
print("最终报告")
print("=" * 60)
print(result["final_report"])
print(f"\n修改次数: {result['revision_count']}")
6.3 并行执行的实现
上面的代码中,三个搜索节点需要并行执行。LangGraph 提供了 Send API 来实现真正的并行:
python
from langgraph.types import Send
def continue_to_searches(state: ResearchState):
"""并行分发到三个搜索节点"""
return [
Send("search_market", state),
Send("search_tech", state),
Send("search_competitor", state)
]
# 入口先到一个分发节点
graph.add_conditional_edges("__start__", continue_to_searches)
Send 会把当前状态复制三份,分别发给三个节点,等它们全部完成后才继续下一步。
6.4 聚合节点的写法
并行节点的结果会自动合并到 State 中(因为用了 Annotated[list, add])。但如果是字符串字段,需要用一个聚合节点来合并:
python
def aggregate_node(state: ResearchState) -> dict:
"""合并三个搜索结果"""
combined = f"""
## 市场调研
{state.get('market_data', '无数据')}
## 技术调研
{state.get('tech_data', '无数据')}
## 竞品调研
{state.get('competitor_data', '无数据')}
"""
return {"analysis": combined}
第七章:经典案例三------代码审查 Pipeline(串行+反馈循环)
7.1 需求
构建一个代码审查流水线:
- 分析 Agent:理解代码功能
- 安全 Agent:检查安全漏洞
- 性能 Agent:检查性能问题
- 规范 Agent:检查代码规范
- 汇总 Agent:生成审查报告
- 如果发现严重问题,自动修复后重新审查
7.2 完整代码
python
from typing import TypedDict, Annotated, List, Optional
from operator import add
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
# ============ 1. 状态定义 ============
class CodeReviewState(TypedDict):
code: str # 待审查代码
language: str # 编程语言
functional_analysis: str # 功能分析
security_issues: str # 安全问题
performance_issues: str # 性能问题
style_issues: str # 规范问题
review_summary: str # 审查汇总
fixed_code: str # 修复后的代码
needs_fix: bool # 是否需要修复
iteration: int # 当前迭代次数
messages: Annotated[list, add]
# ============ 2. LLM ============
llm = ChatOpenAI(model="gpt-4", temperature=0)
# ============ 3. 各审查 Agent ============
def functional_analyzer(state: CodeReviewState) -> dict:
"""功能分析 Agent"""
response = llm.invoke([
SystemMessage(content="""你是代码分析专家。分析代码的功能:
1. 代码做了什么?
2. 主要函数/类的职责
3. 调用关系和数据流
简洁明了,不超过300字。"""),
HumanMessage(content=f"语言:{state['language']}\n代码:\n{state['code']}")
])
return {"functional_analysis": response.content}
def security_reviewer(state: CodeReviewState) -> dict:
"""安全审查 Agent"""
response = llm.invoke([
SystemMessage(content="""你是安全工程师。检查代码中的安全漏洞:
- SQL注入、XSS、CSRF
- 硬编码密钥/密码
- 不安全的加密方式
- 权限校验缺失
- 路径遍历
- 反序列化漏洞
对每个问题给出严重程度(高/中/低)和修复建议。
如果没有问题,返回"未发现安全问题"。"""),
HumanMessage(content=f"语言:{state['language']}\n代码:\n{state['code']}")
])
return {"security_issues": response.content}
def performance_reviewer(state: CodeReviewState) -> dict:
"""性能审查 Agent"""
response = llm.invoke([
SystemMessage(content="""你是性能优化专家。检查代码中的性能问题:
- N+1查询
- 内存泄漏
- 不必要的循环
- 缺少缓存
- 大数据处理不当
- 同步阻塞
对每个问题给出影响程度和优化建议。
如果没有问题,返回"未发现性能问题"。"""),
HumanMessage(content=f"语言:{state['language']}\n代码:\n{state['code']}")
])
return {"performance_issues": response.content}
def style_reviewer(state: CodeReviewState) -> dict:
"""代码规范 Agent"""
response = llm.invoke([
SystemMessage(content="""你是代码规范审查员。检查:
- 命名规范
- 代码结构
- 注释质量
- 错误处理
- 测试覆盖
- DRY/SOLID 原则
给出具体改进建议。如果没有问题,返回"代码规范良好"。"""),
HumanMessage(content=f"语言:{state['language']}\n代码:\n{state['code']}")
])
return {"style_issues": response.content}
# ============ 4. 汇总 Agent ============
def summary_agent(state: CodeReviewState) -> dict:
"""汇总审查结果"""
response = llm.invoke([
SystemMessage(content="""你是技术负责人。根据四位审查员的意见,生成代码审查报告。
报告格式:
## 审查结果
- 总体评分:A/B/C/D
- 严重问题数:X
- 建议改进数:Y
## 问题清单
(按严重程度排序)
## 是否需要修复
如果有高严重度安全问题或性能问题,回答 NEEDS_FIX
否则回答 PASS"""),
HumanMessage(content=f"""
功能分析:{state['functional_analysis']}
安全问题:{state['security_issues']}
性能问题:{state['performance_issues']}
规范问题:{state['style_issues']}""")
])
needs_fix = "NEEDS_FIX" in response.content
return {
"review_summary": response.content,
"needs_fix": needs_fix
}
# ============ 5. 自动修复 Agent ============
def auto_fixer(state: CodeReviewState) -> dict:
"""自动修复 Agent"""
response = llm.invoke([
SystemMessage(content="""你是高级开发工程师。根据审查报告修复代码中的问题。
要求:
1. 只修复严重问题,保持原有功能不变
2. 修复后输出完整代码
3. 在代码顶部添加注释说明修复了什么"""),
HumanMessage(content=f"""
原始代码:
{state['code']}
审查报告:
{state['review_summary']}
请输出修复后的完整代码。""")
])
return {
"fixed_code": response.content,
"code": response.content, # 更新代码用于下一轮审查
"iteration": state.get("iteration", 0) + 1
}
# ============ 6. 路由函数 ============
def needs_fix_or_pass(state: CodeReviewState) -> str:
if state.get("needs_fix") and state.get("iteration", 0) < 3:
return "fix"
return "pass"
# ============ 7. 构建图 ============
graph = StateGraph(CodeReviewState)
# 添加节点
graph.add_node("functional", functional_analyzer)
graph.add_node("security", security_reviewer)
graph.add_node("performance", performance_reviewer)
graph.add_node("style", style_reviewer)
graph.add_node("summary", summary_agent)
graph.add_node("fixer", auto_fixer)
# 入口 → 四个审查并行(简化为串行,实际用 Send 可并行)
graph.set_entry_point("functional")
graph.add_edge("functional", "security")
graph.add_edge("security", "performance")
graph.add_edge("performance", "style")
graph.add_edge("style", "summary")
# 汇总后判断
graph.add_conditional_edges("summary", needs_fix_or_pass, {
"fix": "fixer", # 需要修复 → 自动修复
"pass": END # 通过 → 结束
})
# 修复后重新审查(循环)
graph.add_edge("fixer", "functional")
app = graph.compile()
# ============ 8. 测试 ============
test_code = '''
def login(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
result = db.execute(query)
if result:
session["user"] = username
return {"status": "ok"}
return {"status": "fail"}
'''
result = app.invoke({
"code": test_code,
"language": "Python",
"functional_analysis": "", "security_issues": "",
"performance_issues": "", "style_issues": "",
"review_summary": "", "fixed_code": "",
"needs_fix": False, "iteration": 0, "messages": []
})
print("审查报告:")
print(result["review_summary"])
print("\n修复后代码:")
print(result.get("fixed_code", "无需修复"))
print(f"\n迭代次数:{result['iteration']}")
7.3 执行流程
┌─────────────────────────────────────────┐
│ 第一轮审查 │
│ │
代码 ──▶ 功能分析 → 安全审查 → 性能审查 → 规范审查 → 汇总
│ │
└────────────────────┬────────────────────┘
│
有严重问题?
╱ ╲
是 否
▼ ▼
自动修复 输出报告
│
▼
┌─────────────────────────────────────┐
│ 第二轮审查 │
│ │
修复后代码 ──▶ 功能分析 → 安全审查 → 性能审查 → 规范审查 → 汇总
│ │
└──────────────────┬──────────────────┘
│
仍有问题且 < 3轮?
╱ ╲
是 否
▼ ▼
再次修复 输出最终报告
第八章:生产环境最佳实践
8.1 错误处理与重试
python
from langchain_core.runnables import RunnableLambda
import time
def retry_on_failure(func, max_retries=3, delay=1):
"""带重试的节点包装器"""
def wrapper(state):
for attempt in range(max_retries):
try:
return func(state)
except Exception as e:
if attempt == max_retries - 1:
return {"error": str(e), "messages": [f"节点执行失败: {e}"]}
time.sleep(delay * (attempt + 1))
return wrapper
# 使用
graph.add_node("search", retry_on_failure(search_agent))
8.2 成本控制
LLM 调用按 token 计费。多 Agent 系统的 token 消耗是单 Agent 的 N 倍。控制成本的策略:
python
# 1. 不同 Agent 用不同模型
router_llm = ChatOpenAI(model="gpt-4o-mini") # 路由用小模型(便宜)
analysis_llm = ChatOpenAI(model="gpt-4") # 分析用大模型(准确)
# 2. 限制上下文长度
def trim_messages(messages, max_tokens=4000):
"""只保留最近的消息"""
total = sum(len(m.content) for m in messages)
while total > max_tokens and len(messages) > 1:
removed = messages.pop(0)
total -= len(removed.content)
return messages
# 3. 缓存重复查询
from langchain.globals import set_llm_cache
from langchain_community.cache import SQLiteCache
set_llm_cache(SQLiteCache(database_path=".langchain.db"))
8.3 可观测性
多 Agent 系统的调试比单 Agent 难得多。LangSmith 提供了完整的 trace 能力:
python
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "your-key"
os.environ["LANGCHAIN_PROJECT"] = "multi-agent-demo"
# 每次执行都会自动记录到 LangSmith
# 可以看到每个节点的输入/输出、耗时、token消耗
app.invoke({"task": "分析竞品"})
8.4 测试策略
python
import pytest
def test_router():
"""测试路由是否正确分类"""
state = {"user_query": "我的API调用报错了", "messages": []}
result = router_node(state)
assert result["category"] == "tech"
def test_end_to_end():
"""端到端测试"""
result = app.invoke({
"user_query": "订单ORD001状态",
"messages": [], "category": "", "agent_response": "",
"need_human": False, "final_answer": ""
})
assert "ORD001" in result["final_answer"]
assert result["need_human"] == False
8.5 部署架构
┌─────────────┐
│ API 网关 │
└──────┬──────┘
│
┌──────▼──────┐
│ LangServe │
│ (FastAPI) │
└──────┬──────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Agent 1 │ │ Agent 2 │ │ Agent 3 │
│ (容器) │ │ (容器) │ │ (容器) │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└────────────┼────────────┘
▼
┌──────────────┐
│ Redis / │
│ PostgreSQL │
│ (状态存储) │
└──────────────┘
LangServe 部署示例:
python
# serve.py
from langserve import add_routes
from fastapi import FastAPI
from my_graph import app as graph_app
server = FastAPI(title="多Agent服务")
add_routes(
server,
graph_app,
path="/agent",
enable_feedback_endpoint=True,
playground_type="chat"
)
# uvicorn serve:app --host 0.0.0.0 --port 8000
第九章:总结与选型建议
9.1 什么时候用 LangChain,什么时候用 LangGraph
| 场景 | 选择 | 原因 |
|---|---|---|
| 单轮问答 | LangChain Chain | 最简单,够用 |
| RAG 检索问答 | LangChain RetrievalQA | 成熟方案 |
| 单 Agent + 工具 | LangChain Agent | 工具调用链路完整 |
| 多 Agent 协作 | LangGraph | 需要状态管理和图编排 |
| 有循环的工作流 | LangGraph | Chain 不支持循环 |
| 人机协作流程 | LangGraph | 支持 interrupt/checkpoint |
| 并行处理 | LangGraph | Send API 原生支持 |
9.2 设计多 Agent 系统的原则
- 单一职责:每个 Agent 只做一件事,做好一件事
- 最小权限:每个 Agent 只拿到它需要的工具
- 明确状态:State 定义要清晰,避免字段歧义
- 容错设计:每个节点都要处理异常,不让一个节点的失败拖垮整个流程
- 成本意识:路由用小模型,分析用大模型;缓存重复查询
- 可测试性:每个节点可以独立测试,整个图可以端到端测试
9.3 学习路径建议
入门: LangChain Chain → Prompt → LLM → OutputParser
│
▼
进阶: Tools → Agent → RAG → Memory
│
▼
高级: LangGraph → State → Node → Edge → 条件路由
│
▼
实战: 多Agent系统 → 并行编排 → 人机协作 → 生产部署
9.4 常见陷阱
陷阱一:Agent 数量膨胀。 不是拆得越多越好。3-5 个 Agent 通常就够了,超过 7 个就难以维护。
陷阱二:上下文传递丢失。 每个节点只能看到 State 中的内容。如果重要信息没放进 State,后面的 Agent 就看不到。
陷阱三:死循环。 循环条件一定要有退出机制(最大迭代次数、明确的终止条件)。
陷阱四:忽视延迟。 串行 5 个 Agent,每个调用 LLM 需要 3 秒,用户就要等 15 秒。能并行就并行,能用小模型就用小模型。
写在最后: 多 Agent 不是银弹。很多场景下,一个精心设计的单 Agent + 合适的工具集,比粗糙的多 Agent 系统效果更好。先从单 Agent 做起,当复杂度确实需要拆分时,再引入 LangGraph。工程的本质是权衡,不是堆砌。