目录
1. LangGraph工作流编排
在上篇中,我们通过 create_agent 快速构建了具备检索和工具调用能力的Agent。你可能已经注意到,Agent内部的执行路径并非线性的------它可能多次调用工具、反复调用模型,甚至陷入循环。这就是为什么从LangChain 1.x开始,Agent底层已全面迁移至LangGraph。
LangGraph的核心价值:让开发者精确控制Agent的每一步执行,在保持自主性的同时大幅提升稳定性。
1.1 LangGraph三要素
LangGraph基于图(Graph)来编排AI工作流,其核心抽象仅三个:
| 要素 | 类比 | 职责 |
|---|---|---|
| State(状态) | 共享工作笔记本 | 定义所有节点共享的数据结构 |
| Node(节点) | 执行单元 | 处理State并返回更新 |
| Edge(边) | 控制流 | 决定执行顺序(普通边/条件边) |
基础示例------简单的笑话生成器:
python
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class JokeState(TypedDict):
topic: str
joke: str
def generate_joke(state: JokeState):
msg = llm.invoke(f"写一个关于 {state['topic']} 的笑话")
return {"joke": msg.content}
graph = (
StateGraph(JokeState)
.add_node("generate", generate_joke)
.add_edge(START, "generate")
.add_edge("generate", END)
.compile()
)
1.2 七种核心编排模式
LangGraph将常见的AI工作流归纳为七种模式,覆盖了从简单到复杂的全部场景:
模式一:顺序执行(Prompt Chaining)
前一个LLM的输出作为后一个LLM的输入,类似流水线。可以在节点间加入验证门控------质量不达标时退回重做。
典型场景:写作流程(大纲→初稿→润色)、翻译+校对
模式二:并行执行(Parallelization)
多个节点同时执行,有两种实现方式:
- 任务拆分:将一个大任务拆为多个子任务并行处理,最后汇总
- 冗余投票:多个LLM运行相同任务,合并优化结果(提高准确率)
python
# 三个节点从START并行出发
builder.add_edge(START, "joke")
builder.add_edge(START, "story")
builder.add_edge(START, "poem")
# 汇聚到聚合节点
builder.add_edge("joke", "aggregator")
builder.add_edge("story", "aggregator")
builder.add_edge("poem", "aggregator")
模式三:路由(Routing)
根据输入内容将请求分发到不同的处理节点,是实现Agent决策的核心机制。
典型场景:智能客服(售前/退款/退货路由)、按领域分类的知识问答
模式四:编排器-工作者(Orchestrator-Worker)
编排器(Orchestrator)动态将复杂任务拆解为多个子任务,通过 Send API 分发给工作节点(Worker)并行执行,最后合成器汇总结果。
与普通并行的本质区别:子任务数量是动态的、不确定的。
python
from langgraph.types import Send
def orchestrator(state: State):
"""为每个章节创建一个Worker"""
return [Send("worker", {"section": section}) for section in state["sections"]]
典型场景:深度研究报告生成、批量文档处理
模式五:评估-优化循环(Evaluator-Optimizer)
生成器产出内容,评估器打分给出反馈,不达标则带着反馈重返生成器。形成"生成-评估-反馈-优化"的闭环。
与普通循环的区别:自带终止条件,仅在质量不达标时重复。
典型场景:文案优化、代码审查、翻译质量提升
模式六:子图嵌套(Subgraphs)
将复杂图拆分为多层子图,降低单个图的复杂度:
| 方式 | 说明 |
|---|---|
| 直接嵌入 | 子图作为父图节点,State自动映射 |
| 包装调用 | 在普通节点内手动调用子图,灵活控制State映射 |
python
parent_graph.add_node("analysis_subgraph", compiled_subgraph)
典型场景:大规模工作流的模块化拆分、团队协作开发
模式选择指南
| 业务需求 | 推荐模式 |
|---|---|
| 步骤确定,前后依赖 | Prompt Chaining |
| 不同领域的分支处理 | Routing |
| 多个独立任务需要加速 | Parallelization |
| 复杂任务动态拆解 | Orchestrator-Worker |
| 需要迭代优化质量 | Evaluator-Optimizer |
| 流程过长需要拆解 | Subgraphs |
1.3 Think In LangGraph:客户支持Agent完整案例
上篇我们学会了用 create_agent 快速构建Agent,但面对分支路径、人工审核、多步操作等复杂场景时,我们需要LangGraph的精细控制。本节从零构建一个自动处理客服邮件的Agent,展示完整的LangGraph思维框架。
需求分析
假设我们需要处理的邮件场景:
| 场景 | 类型 | 处理策略 |
|---|---|---|
| 密码重置 | 简单问题(question) | 查询文档 → 自动回复 |
| PDF导出崩溃 | Bug报告(bug) | 创建工单 → 人工审核 |
| 重复扣款 | 紧急账单(billing) | 直接人工处理 |
| 暗黑模式功能 | 功能请求(feature) | 查文档 → 标记功能 |
| API间歇性504 | 复杂技术(complex) | 直接人工处理 |
核心观察:不同的问题走不同的处理路径------这正是需要**图(Graph)**而非链(Chain)的原因。
第一阶段:设计工作流
首先梳理业务流程,确定节点:
| 节点 | 职责 | 类型 | 决策? |
|---|---|---|---|
| 读取邮件 | 解析邮件内容和发件人 | Data节点 | 否 |
| 意图识别 | LLM分类意图+紧急度,决定下一步路径 | LLM节点 | 是 |
| 文档搜索 | 搜索知识库 | Data节点 | 否 |
| BUG跟踪 | 创建/更新Bug工单 | Action节点 | 否 |
| 生成草稿 | LLM生成回复草稿 | LLM节点 | 是 |
| 人工审核 | 暂停等待人工审批 | Human节点 | 否 |
| 发送 | 发送邮件回复 | Action节点 | 否 |
第二阶段:设计State
State是所有节点共享的工作笔记本 。判断字段是否入State的原则:如果下游节点需要用它做决策或处理,就入State;如果能从其他字段推导出来,就不存。
python
from typing import TypedDict, Literal
class EmailClassification(TypedDict):
intent: Literal["question", "bug", "billing", "feature", "complex"]
urgency: Literal["low", "medium", "high", "critical"]
topic: str
summary: str
class EmailAgentState(TypedDict):
email_content: str
sender_email: str
email_id: str
classification: EmailClassification | None
search_results: list[str] | None
customer_history: dict | None
draft_response: str | None
第三阶段:构建Node
在LangGraph中,Node内部通过 Command 声明路由,避免了复杂的外部条件边:
python
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, RetryPolicy
# 意图识别节点------内部决策路由
def classify_intent(state: EmailAgentState) -> Command[Literal["search_documentation", "bug_tracking", "draft_response"]]:
classification = classifier_llm.invoke(f"""
分析这封客户邮件并分类:
邮件: {state['email_content']}
发件人: {state['sender_email']}
提供 intent(question/bug/billing/feature/complex)、urgency、topic、summary。
""")
if classification['intent'] in ['question', 'feature']:
goto = "search_documentation"
elif classification['intent'] == 'bug':
goto = "bug_tracking"
else: # billing / complex → 生成草稿(后续判断是否人工审核)
goto = "draft_response"
return Command(update={"classification": classification}, goto=goto)
# 生成草稿节点------判断是否需要人工审核
def draft_response(state: EmailAgentState) -> Command[Literal["human_review", "send_reply"]]:
# ... 调用LLM生成草稿 ...
needs_review = (
classification.get('urgency') in ['high', 'critical'] or
classification.get('intent') in ['complex', 'billing']
)
goto = "human_review" if needs_review else "send_reply"
return Command(update={"draft_response": response.content}, goto=goto)
异常处理策略------不同节点类型有不同的容错方案:
| 错误类型 | 谁来修复 | 策略 |
|---|---|---|
| 网络超时、限流 | 系统自修复 | 重试(RetryPolicy) |
| 工具执行失败 | LLM | 错误信息写入State,重新调用LLM处理 |
| 输入信息缺失 | 用户 | interrupt() 暂停,人工修复 |
| 重试耗尽 | 开发者 | error_handler 兜底 |
第四阶段:构建图
由于节点内部已通过 Command 声明路由,图的边只需定义固定路径:
python
workflow = StateGraph(EmailAgentState)
workflow.add_node("read_email", read_email)
workflow.add_node("classify_intent", classify_intent)
workflow.add_node("search_documentation", search_documentation,
retry=RetryPolicy(max_attempts=3))
workflow.add_node("bug_tracking", bug_tracking)
workflow.add_node("draft_response", draft_response)
workflow.add_node("human_review", human_review)
workflow.add_node("send_reply", send_reply)
# 最小边原则------只定义固定路径
workflow.add_edge(START, "read_email")
workflow.add_edge("read_email", "classify_intent")
workflow.add_edge("human_review", "send_reply")
workflow.add_edge("send_reply", END)
app = workflow.compile(checkpointer=InMemorySaver())
设计哲学小结:LangGraph的"最小边原则"------尽可能在Node内部用Command做路由决策,Graph层面只保留固定的串行边。这使图的拓扑结构极度简洁,将复杂的路由逻辑留给LLM去处理。
2. 多智能体协作(Multi-Agent)
在上篇中,我们构建的单Agent配合适量工具和精心设计的提示词,足以解决大部分问题。但当工具数量膨胀到10+、涉及多个领域时,单Agent的上下文窗口将不堪重负。此时就需要引入多Agent协作。
2.1 何时使用多Agent
当出现以下信号时,可考虑引入多Agent:
- 上下文膨胀:工具数量过多(如10+),单Agent上下文窗口难以容纳
- 职能分离:不同团队维护不同Agent(如机票组、酒店组)
- 效率需求:多个独立子任务需要并行执行
2.2 常见协作模式
| 模式 | 原理 | 特点 |
|---|---|---|
| Subagents | 主Agent将子Agent作为Tool协调调用 | 支持分布式开发、并行、多跳 |
| Handoffs | 随任务执行切换Agent,各Agent可直接与用户交互 | 灵活切换,适合多轮交互 |
| Skills | 单Agent按需加载不同技能/知识 | 上下文可控,避免膨胀 |
| Router | 路由Agent分类请求后导向专用Agent | 天然并行,适合分流 |
这些模式并非互斥,实际开发中可灵活组合。
2.3 实战案例:婚礼策划多Agent
以婚礼策划为例,展示Subagents模式的具体实现。
需求梳理
婚礼策划Agent需完成三项核心任务:机票搜索(Travel)、场地搜索(Venue)、歌单策划(Playlist)。
选择多Agent而非单Agent的决策依据:
- 工具过多:航班+场地+音乐数据库,上下文难以承受
- 可并行:三个任务彼此独立,可同时执行提高效率
- 便于扩展:未来新增"餐饮""摄影"等Agent不影响现有逻辑
实现方案
Step 1:定义三个子Agent
python
# Travel Agent(调用MCP工具查询航班)
travel_agent = create_agent(
model="deepseek-chat",
tools=[flights_tool, time_tool],
system_prompt="你是旅行规划专家..."
)
# Venue Agent(基于Tavily搜索场地)
venue_agent = create_agent(
model="deepseek-chat",
tools=[web_search],
system_prompt="你是婚礼场地专家..."
)
# Playlist Agent(查询SQLite数据库)
playlist_agent = create_agent(
model="deepseek-chat",
tools=[query_playlist_db],
system_prompt="你是音乐策划专家..."
)
Step 2:定义主Agent(Coordinator)
主Agent通过Tool调用子Agent,实现协调和汇总:
python
from langchain.agents import AgentState
class WeddingState(AgentState):
origin: str
destination: str
guest_count: str
genre: str
# 将子Agent封装为Tool
@tool
async def search_flights(runtime: ToolRuntime) -> str:
"""搜索前往目的地的航班"""
origin = runtime.state["origin"]
destination = runtime.state["destination"]
response = await travel_agent.ainvoke({
"messages": [HumanMessage(content=f"从{origin}到{destination}的航班")]
})
return response['messages'][-1].content
@tool
def search_venues(runtime: ToolRuntime) -> str:
"""搜索婚礼场地"""
query = f"在{ runtime.state['destination'] }适合{ runtime.state['guest_count'] }人的婚礼场地"
response = venue_agent.invoke({"messages": [HumanMessage(content=query)]})
return response['messages'][-1].content
# 主Agent------婚礼策划协调者
coordinator = create_agent(
model="deepseek-chat",
tools=[search_flights, search_venues, suggest_playlist, update_state],
state_schema=WeddingState,
system_prompt="你是婚礼策划协调员..."
)
Step 3:执行验证
运行后,主Agent先通过对话收集婚礼需求,更新State,然后并行调用三个子Agent:
用户:我来自伦敦,想在巴黎举办100人的婚礼,爵士风格
1. update_state → origin=伦敦, destination=巴黎, guest_count=100, genre=爵士
2. 并行调用:
→ search_flights(查询伦敦→巴黎航班)
→ search_venues(搜索巴黎婚礼场地)
→ suggest_playlist(策划爵士歌单)
3. 汇总输出完整的婚礼策划方案
三个子Agent互不依赖,可并行执行,这是Subagents模式相较于串行处理的核心效率优势。
3. MCP模型上下文协议
多Agent协同需要接入多种外部服务------航班查询、时间获取、数据库操作等。在传统模式下,每接入一个服务都需要手动编写Tool代码,不仅工作量大,而且难以复用。MCP(Model Context Protocol)正是为解决这一问题而生。
3.1 什么是MCP
MCP是由Anthropic推出的开放标准,可理解为 "AI世界的USB接口协议"。
在没有MCP的时代,每次接入外部服务都需要手动定义Tool,存在两个根本问题:
- 复用性差:不同Agent有相同的工具需求,每次都重复定义
- 协议不统一:千家服务接口各异,集成成本极高
MCP的解决思路是标准化:
- 外部服务遵循MCP协议提供接口(MCP Server)
- AI应用(MCP Host)通过MCP Client统一对接,无需手动定义Tool
核心概念:
| 概念 | 职责 | 类比 |
|---|---|---|
| MCP Server | 提供MCP服务的应用(远程/本地) | USB设备 |
| MCP Client | 连接Server,获取Tool信息供Host使用 | USB驱动 |
| MCP Host | 协调多个Client的AI应用(如LangChain Agent) | 操作系统 |
通信方式:
| 方式 | 延迟 | 适用场景 |
|---|---|---|
| stdio | 无网络延迟 | 本地MCP服务器 |
| SSE(HTTP) | 有网络延迟 | 远程MCP服务 |
3.2 连接外部MCP服务
LangChain通过 MultiServerMCPClient 统一管理多个MCP连接:
Time MCP(stdio方式------本地进程通信):
python
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"time": {
"transport": "stdio",
"command": "uvx",
"args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
}
})
tools = await client.get_tools()
# tools 包含: get_current_time, convert_time
agent = create_agent("deepseek-chat", tools)
response = await agent.ainvoke({
"messages": [HumanMessage("现在几点了?")]
})
Kiwi MCP(SSE方式------远程HTTP通信):
python
client = MultiServerMCPClient({
"kiwi-com-flight-search": {
"transport": "http",
"url": "https://mcp.kiwi.com"
}
})
tools = await client.get_tools()
agent = create_agent(
model="deepseek-chat",
tools=tools,
system_prompt="You are a travel agent. If the user uses Chinese, use zh-cn as locale."
)
说明 :Kiwi擅长国际航班,国内航班可选用 VariFlight(飞友AI) 等MCP服务。
3.3 自定义MCP服务
团队内部也可以将自己的服务封装为MCP Server。使用FastMCP框架可以快速创建:
python
from fastmcp import FastMCP
mcp = FastMCP("Math")
@mcp.tool()
def add(a: float, b: float) -> float:
"""Add two numbers"""
return a + b
@mcp.tool()
def multiply(a: float, b: float) -> float:
"""Multiply two numbers"""
return a * b
if __name__ == "__main__":
mcp.run(transport="stdio")
LangChain侧连接自定义MCP:
python
client = MultiServerMCPClient({
"math": {
"transport": "stdio",
"command": "python",
"args": ["math_mcp_server.py"]
}
})
tools = await client.get_tools()
agent = create_agent("deepseek-chat", tools,
system_prompt="使用数学工具回答问题。")
response = await agent.ainvoke({
"messages": [HumanMessage("467和529的平方根之和是多少?")]
})
MCP使用流程小结:
配置MultiServerMCPClient → 调用get_tools()获取工具 → 创建Agent并调用
自定义MCP Server可提供三类资源:
| 装饰器 | 用途 | 推荐场景 |
|---|---|---|
@mcp.tool() |
工具 | 必备,实现核心功能 |
@mcp.resource() |
静态资源 | 可选,类似知识库 |
@mcp.prompt() |
预设提示词 | 可选,方便下游使用 |
4. 实战案例:AI私厨
现在,让我们通过一个完整的实战案例,将上、下篇的所有知识串联起来。
4.1 项目概述
AI私厨是一个基于LangChain多模态AI的食谱推荐应用:
- 用户上传冰箱/食材照片
- AI自动识别图片中的食材
- 根据食材搜索相关食谱
- 多维度评估排序(营养、难度、口味)
- 输出结构化的食谱推荐报告
4.2 技术选型
| 组件 | 选型 | 说明 |
|---|---|---|
| 多模态模型 | qwen3.5-plus | 支持图片理解 |
| 搜索工具 | TavilySearch | Web搜索引擎 |
| 记忆持久化 | SQLite + SqliteSaver | 对话历史管理与中断恢复 |
| 后端框架 | FastAPI | 提供RESTful API和文件上传 |
| 文件存储 | 阿里云OSS | 图片上传和URL获取 |
4.3 核心代码
python
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from langchain.agents import create_agent
from langgraph.checkpoint.sqlite import SqliteSaver
# 1. 初始化搜索工具
web_search = TavilySearch(max_results=5, topic="general")
# 2. 初始化多模态模型
model = init_chat_model(
model="qwen3.5-plus",
model_provider="openai",
base_url=os.getenv("DASHSCOPE_BASE_URL"),
api_key=os.getenv("DASHSCOPE_API_KEY")
)
# 3. 初始化记忆
checkpointer = SqliteSaver(
sqlite3.connect("db/personal_chief.db", check_same_thread=False)
)
checkpointer.setup()
# 4. 系统提示词
system_prompt = """你是一名私人厨师。收到食材照片或清单后:
1. 识别和评估食材
2. 调用web_search搜索对应食谱
3. 从营养和难度两个维度打分排序
4. 输出结构化的食谱推荐报告(含步骤、配料、预估时间)"""
# 5. 创建Agent
agent = create_agent(
model=model,
tools=[web_search],
checkpointer=checkpointer,
system_prompt=system_prompt
)
4.4 多模态消息处理
python
from langchain.messages import HumanMessage
# 推荐使用OSS URL而非base64,避免请求体过大
message = HumanMessage(content=[
{"type": "image", "url": oss_image_url},
{"type": "text", "text": "帮我看看这些食材能做些什么?"}
])
response = agent.invoke(
{"messages": [message]},
config={"configurable": {"thread_id": "chef-001"}}
)
4.5 部署方案
方案一------LangGraph CLI部署:
创建 langgraph.json 配置文件,执行 langgraph dev 一键启动,自带RESTful API和可视化调试界面。
方案二------自定义FastAPI服务:
提供更灵活的控制,包含对话接口、会话管理接口和文件上传接口,适合与现有系统集成。
5. 总结
5.1 技术栈全景图
LangChain生态全景
├── LangChain Core(核心框架)
│ ├── 模型调用(ChatModels + with_structured_output)
│ ├── 工具系统(Tools + MCP)
│ ├── 消息系统(Messages + 多模态)
│ ├── 记忆管理(Checkpointer)
│ └── 中间件(Middleware + HITL)
│
├── RAG(检索增强生成)
│ ├── 知识库构建(加载 → 切分 → 向量化 → 存储)
│ ├── 检索优化(HyDE / Multi-Hop / 多路召回 + RRF)
│ ├── 重排序(Cross-Encoder / MMR)
│ └── 评估体系(RAGAS:Faithfulness / Precision / Recall)
│
├── LangGraph(工作流编排)
│ ├── 三要素(State / Node / Edge)
│ ├── 七种编排模式(Chaining / Parallel / Routing 等)
│ └── 最小边原则(Command内部路由)
│
├── Multi-Agent(多智能体协作)
│ ├── Subagents / Handoffs / Skills / Router
│ └── 实战:婚礼策划协调者模式
│
└── 工具链
├── LangSmith(调试 / 监控 / 部署)
├── MCP协议(Time / Kiwi / 自定义Math Server)
├── FastAPI(后端服务)
└── LangGraph CLI(一键部署)
5.2 实践
-
从简单开始,渐进式演化:优先使用单Agent + 适量Tool。仅当上下文膨胀或需要函数分离时,才引入多Agent或LangGraph。过度设计是AI应用工程化的常见陷阱。
-
善用中间件构建企业级能力:PII脱敏是数据合规的硬性要求;模型降级是生产环境可用性的保证;HITL(人工审核)在高风险场景中不可或缺。
-
RAG优化要体系化:Demo级RAG只需"加载→切分→检索"三步,但工业级RAG需要查询优化(重写/HyDE)、多路召回(稠密+稀疏)、RRF融合和Cross-Encoder重排序的完整链路。
-
多模态用URL而非base64:大尺寸图片的base64编码会占用大量Token,显著增加成本和延迟。优先使用对象存储URL。
-
尽早接入LangSmith:从开发第一天就启用链路追踪。当Agent行为不符合预期时,LangSmith的执行详情页是debug的首要入口。
-
选择正确的编排模式:根据业务特性选择LangGraph编排模式。大多数场景下,传统Chain + Routing就足够,不必追求Evaluator-Optimizer等高级模式。
-
用MCP拥抱标准化:新项目优先考虑MCP协议接入外部服务,而非手动编写Tool。这既降低维护成本,也为未来的可移植性做好准备。
LangChain生态仍在快速演进,以下方向值得持续关注:
-
Agentic RAG的深化:从"检索→生成"的固定模式,演进为Agent自主决定检索时机、检索策略和结果利用方式,使RAG系统真正具备"思考能力"。
-
MCP协议的生态化:随着越来越多服务商提供MCP接口,"AI应用连接一切"的开发成本将大幅降低,标准化的Tool市场正在形成。
-
Multi-Agent编排的成熟:从手动编写协调逻辑,演进为框架级的多Agent编排方案,支持Agent间更灵活的消息传递和任务协调。
-
生产级可观测性:LangSmith等平台将持续深化,从Trace级别演进为融合Metrics、Logging、Alerting的完整可观测性体系。