LangChain与LangGraph多Agent实战:从工具链到工作流编排(下)

👨 作者简介:大家好,我是唐璜Taro ,全栈 领域创作者

✒️ 个人主页 :唐璜Taro

🚀 支持我:点赞👍+📝 评论 + ⭐️收藏


LangChain + LangGraph 多 Agent 实战:从工具链到工作流编排的万字精华指南

单个 Agent 能回答问题,多个 Agent 协作能解决复杂任务。本文从工具链集成讲起,深入 LangGraph 的状态机设计,最后用三个经典案例带你从零构建多 Agent 系统。


目录


第六章:经典案例二------研究报告生成(并行+聚合)

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 需求

构建一个代码审查流水线:

  1. 分析 Agent:理解代码功能
  2. 安全 Agent:检查安全漏洞
  3. 性能 Agent:检查性能问题
  4. 规范 Agent:检查代码规范
  5. 汇总 Agent:生成审查报告
  6. 如果发现严重问题,自动修复后重新审查

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 系统的原则

  1. 单一职责:每个 Agent 只做一件事,做好一件事
  2. 最小权限:每个 Agent 只拿到它需要的工具
  3. 明确状态:State 定义要清晰,避免字段歧义
  4. 容错设计:每个节点都要处理异常,不让一个节点的失败拖垮整个流程
  5. 成本意识:路由用小模型,分析用大模型;缓存重复查询
  6. 可测试性:每个节点可以独立测试,整个图可以端到端测试

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。工程的本质是权衡,不是堆砌。

相关推荐
swipe1 天前
从本地开发到生产部署:用 Docker Compose 跑通 NestJS、MySQL 与 Milvus
后端·langchain·llm
yangshicong1 天前
第11章:结构化输出与数据提取 —— 让 AI 直接返回你想要的数据格式
数据库·人工智能·redis·python·langchain·ai编程
Restart-AHTCM1 天前
AI时代大前端Agent开发LangChain.js
typescript·langchain·memory·rag·tools
打小就很皮...1 天前
基于 Python + LangChain + React 的 AI 流式对话与历史存储实战(拓展图片上传)
langchain·react·sse·图片解析
打小就很皮...1 天前
基于 Python + LangChain + React 的 AI 流式对话与历史存储实战
人工智能·langchain·flask·react·sse
AI技术控1 天前
NeuroH-TGL 论文解读:面向脑疾病诊断的神经异质性引导时序图学习方法
人工智能·语言模型·自然语言处理·langchain·nlp
完成大叔1 天前
从脚本到Agent:工具模式下的智能价值
人工智能·langchain
糖果店的幽灵1 天前
Part 2: Models(模型)
microsoft·langchain
Fanxt_Ja1 天前
Langchain+Tavily对接大模型实现联网搜索
python·langchain·deepseek·tavily