📖 1. 导读
💡本文是 LangGraph 系列的第 5 讲。如果说第 4 讲让智能体“会行动”(能调用工具),那么第 5 讲要解决的是:让智能体能根据输入与状态,选择不同的执行路径——也就是“会决策”。
这一讲重点讲清两个核心能力:
- 条件边(Conditional Edges):根据条件函数的返回值,动态选择下一步去哪个节点
- 动态路由(Routing):把“选择哪条路径”的逻辑做成一个 Router(规则/模型/混合),实现可扩展的多分支工作流
1.1 本讲你将学会
- ✅ 什么时候该用条件边,什么时候该用多个图/多个子流程
- ✅ 3 种常用路由策略:
- ✅ 如何用
add_conditional_edges()写出可维护的多分支路由 - ✅Prompt Chaining + Routing:先分类再分步处理的链式工作流
- ✅Parallelization + Routing:同一意图下并行多个子任务,再聚合输出
- ✅ 如何把路由做成一个“智能客服分流”式的综合案例
1.2 前置要求
- 🛠️ Python 3.10+
- 推荐安装:
pip install -U langgraph langchain-core - LLM(示例沿用 DeepSeek)
🧠 2. 为什么需要“路由”?
当你的智能体开始变复杂时,你会自然遇到这些问题:
- 用户问题类型很多:价格/退款/技术/知识库/闲聊
- 不同类型的请求,需要不同的提示词、不同的工具、不同的权限
- 你不希望“一条链”里塞所有逻辑,否则会:
**路由(Routing)**的价值:先识别“该走哪条路”,再进入“专用流程”。
官方文档把 Routing 作为一种核心工作流模式:先处理输入,再将其导向上下文相关的任务流(比如把“退款问题”导向退款流程)。
🧱 3. 条件边(Conditional Edges)怎么理解?
在 LangGraph 里,边分两类:
- 普通边:
add_edge("A", "B"),固定从 A 到 B - 条件边:
add_conditional_edges("A", route_fn, mapping),从 A 出发,由route_fn(state)决定下一步去哪里
条件边的关键点是:
- route_fn 必须是纯函数风格:只读 state,返回一个“路由标签/节点名”
- mapping 是约束:把“允许的返回值”映射到“实际节点”
- 可扩展:你可以从 2 分支扩到 10 分支,但不要在
route_fn里写成一坨 if-else(后面会给更好的组织方式)
🧪 4. 实战一:规则路由(最稳定、最推荐做兜底)
适合场景:
- 需求简单、分类明确(关键词/正则/结构化字段)
- 追求稳定性(不希望模型“路由错”)
下面示例:把用户输入分到 3 个分支:math/search/chat。
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
user_input: str
route: str
output: str
def router_rule(state: State) -> dict:
text = state["user_input"].lower()
if any(k in text for k in ["计算", "+", "-", "*", "/", "多少"]):
route = "math"
elif any(k in text for k in ["搜索", "查一下", "资料", "wiki"]):
route = "search"
else:
route = "chat"
return {"route": route}
def node_math(state: State) -> dict:
return {"output": "这里执行计算逻辑(示例略)"}
def node_search(state: State) -> dict:
return {"output": "这里执行搜索逻辑(示例略)"}
def node_chat(state: State) -> dict:
return {"output": "这里执行闲聊逻辑(示例略)"}
def route_decision(state: State) -> str:
# 只返回一个“标签”,让 mapping 决定下一跳
return state["route"]
builder = StateGraph(State)
builder.add_node("router", router_rule)
builder.add_node("math", node_math)
builder.add_node("search", node_search)
builder.add_node("chat", node_chat)
builder.add_edge(START, "router")
builder.add_conditional_edges(
"router",
route_decision,
{
"math": "math",
"search": "search",
"chat": "chat",
},
)
builder.add_edge("math", END)
builder.add_edge("search", END)
builder.add_edge("chat", END)
graph = builder.compile()
print(graph.invoke({"user_input": "帮我计算 12 * 37 + 9", "route": "", "output": ""})["output"])
规则路由的小建议
- ✅把“规则”集中管理:用一个表/字典存关键词,而不是散落在 if-else 里
- ✅ 先做一个规则兜底(比如把高风险/高确定性的分流先锁死)
- ⚠️ 不要用规则承载“语义理解”,那是模型的强项(下一节)
🤖 5. 实战二:LLM 路由(结构化输出 + 条件边)
适合场景:
- 分类靠语义理解(“这个问题更像退款还是技术故障?”)
- 类别会扩展(未来会加更多分支)
核心做法:
- 用一个
router_llm节点:让模型输出一个结构化字段(比如decision) - 用条件边:根据
decision跳到对应节点
下面示例参考官方 Routing 思路(用结构化输出做路由逻辑),改成中文“故事/段子/诗歌”三选一。
from typing import TypedDict
from typing_extensions import Literal
from pydantic import BaseModel, Field
from langgraph.graph import StateGraph, START, END
from langchain_core.messages import SystemMessage, HumanMessage
from langchain.chat_models import init_chat_model
llm = init_chat_model(model="deepseek-chat", model_provider="deepseek")
class Route(BaseModel):
step: Literal["poem", "story", "joke"] = Field(
description="下一步走 poem/story/joke 中的一个"
)
router = llm.with_structured_output(Route)
class State(TypedDict):
input: str
decision: str
output: str
def llm_call_router(state: State) -> dict:
decision = router.invoke(
[
SystemMessage(content="根据用户请求,选择输出 poem / story / joke 中最合适的一种。只返回你的选择。"),
HumanMessage(content=state["input"]),
]
)
return {"decision": decision.step}
def write_story(state: State) -> dict:
msg = llm.invoke(f"写一个关于『{state['input']}』的短故事,100字以内。")
return {"output": msg.content}
def write_joke(state: State) -> dict:
msg = llm.invoke(f"写一个关于『{state['input']}』的段子,尽量好笑。")
return {"output": msg.content}
def write_poem(state: State) -> dict:
msg = llm.invoke(f"写一首关于『{state['input']}』的小诗,4~8行。")
return {"output": msg.content}
def route_decision(state: State) -> str:
return state["decision"]
builder = StateGraph(State)
builder.add_node("router", llm_call_router)
builder.add_node("story", write_story)
builder.add_node("joke", write_joke)
builder.add_node("poem", write_poem)
builder.add_edge(START, "router")
builder.add_conditional_edges(
"router",
route_decision,
{
"story": "story",
"joke": "joke",
"poem": "poem",
},
)
builder.add_edge("story", END)
builder.add_edge("joke", END)
builder.add_edge("poem", END)
graph = builder.compile()
print(graph.invoke({"input": "猫咪", "decision": "", "output": ""})["output"])
LLM 路由的工程注意事项(很重要)
- 一定要结构化输出:不要让模型随便输出文本再做字符串包含判断(不稳定)
- 最好有兜底分支:当模型输出意外值时,统一 fallback 到安全路径
- 把路由粒度控制住:路由只负责“选路”,不要在路由节点里做重活(否则 debug 很痛苦)
🧩 6. 实战三:综合案例——“智能客服”动态分流
这是最常见、最实用的路由案例之一:先识别用户问题属于哪一类,再进入专用流程。
我们把问题分成四类:
pricing:价格/套餐/费用refund:退款/退订/取消tech:技术故障/报错/登录失败other:其他/闲聊
6.1 设计要点
- 路由层:只产出
intent - 业务层:每个分支用不同 system prompt(甚至不同工具/权限)
- 可扩展:未来新增
delivery、invoice只需要加一个节点 + 更新 mapping
from typing import TypedDict
from typing_extensions import Literal
from pydantic import BaseModel, Field
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langchain_core.messages import SystemMessage, HumanMessage
llm = init_chat_model(model="deepseek-chat", model_provider="deepseek")
class Intent(BaseModel):
intent: Literal["pricing", "refund", "tech", "other"] = Field(
description="用户意图分类:pricing/refund/tech/other"
)
intent_router = llm.with_structured_output(Intent)
class State(TypedDict):
user_input: str
intent: str
reply: str
def classify_intent(state: State) -> dict:
result = intent_router.invoke(
[
SystemMessage(
content=(
"你是客服分流路由器。只做分类,不要回答用户问题。\n"
"请把用户输入分类到 pricing/refund/tech/other 四类之一。"
)
),
HumanMessage(content=state["user_input"]),
]
)
return {"intent": result.intent}
def handle_pricing(state: State) -> dict:
msg = llm.invoke(
[
SystemMessage(content="你是价格与套餐客服,回答要清晰、结构化,必要时用表格。"),
HumanMessage(content=state["user_input"]),
]
)
return {"reply": msg.content}
def handle_refund(state: State) -> dict:
msg = llm.invoke(
[
SystemMessage(content="你是退款与退订客服,先给流程步骤,再给注意事项。"),
HumanMessage(content=state["user_input"]),
]
)
return {"reply": msg.content}
def handle_tech(state: State) -> dict:
msg = llm.invoke(
[
SystemMessage(content="你是技术支持,优先问关键排查信息,并给出可执行的排障步骤。"),
HumanMessage(content=state["user_input"]),
]
)
return {"reply": msg.content}
def handle_other(state: State) -> dict:
msg = llm.invoke(
[
SystemMessage(content="你是通用客服,保持友好、简洁。"),
HumanMessage(content=state["user_input"]),
]
)
return {"reply": msg.content}
def route_intent(state: State) -> str:
return state["intent"]
builder = StateGraph(State)
builder.add_node("router", classify_intent)
builder.add_node("pricing", handle_pricing)
builder.add_node("refund", handle_refund)
builder.add_node("tech", handle_tech)
builder.add_node("other", handle_other)
builder.add_edge(START, "router")
builder.add_conditional_edges(
"router",
route_intent,
{
"pricing": "pricing",
"refund": "refund",
"tech": "tech",
"other": "other",
},
)
builder.add_edge("pricing", END)
builder.add_edge("refund", END)
builder.add_edge("tech", END)
builder.add_edge("other", END)
graph = builder.compile()
print(graph.invoke({"user_input": "我想取消订阅,怎么退款?", "intent": "", "reply": ""})["reply"])
🔗 7. 实战四:Prompt Chaining + Routing(先分类再分步处理)
这是生产环境中最常见的组合模式之一:先路由到不同分支,然后在每个分支内进行多步骤的链式处理(每个 LLM 调用处理前一个的输出)。
7.1 适用场景
- 文档翻译:先识别目标语言,再执行“翻译 → 校对 → 润色”的链式流程
- 内容生成:先分类(技术文档/营销文案),再执行“大纲 → 初稿 → 优化”的链式流程
- 质量检查:先分类(代码审查/文档审查),再执行“检查 → 修复 → 验证”的链式流程
7.2 核心设计思路
- 第一层:路由层(识别类型/语言/领域)
- 第二层:链式处理层(每个分支内,多个节点顺序执行,后一个处理前一个的输出)
下面示例:多语言文档翻译系统,先路由到目标语言,然后在每个语言分支内执行“翻译 → 校对 → 润色”三步链式处理。
from typing import TypedDict
from typing_extensions import Literal
from pydantic import BaseModel, Field
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langchain_core.messages import SystemMessage, HumanMessage
llm = init_chat_model(model="deepseek-chat", model_provider="deepseek")
class LanguageRoute(BaseModel):
target_lang: Literal["en", "ja", "ko"] = Field(
description="目标语言:en(英语)/ja(日语)/ko(韩语)"
)
lang_router = llm.with_structured_output(LanguageRoute)
class State(TypedDict):
source_text: str
target_lang: str
translated: str
proofread: str
polished: str
def route_language(state: State) -> dict:
"""路由层:识别目标语言"""
result = lang_router.invoke(
[
SystemMessage(
content=(
"你是语言路由助手。根据用户输入,识别目标翻译语言。\n"
"如果用户明确提到语言(如'翻译成英语'),选择对应语言;\n"
"否则根据上下文推断,默认选择 en。"
)
),
HumanMessage(content=f"用户输入:{state['source_text']}"),
]
)
return {"target_lang": result.target_lang}
def translate_to_en(state: State) -> dict:
"""英语分支:第一步 - 翻译"""
msg = llm.invoke(
[
SystemMessage(content="你是专业的中英翻译,准确传达原文意思。"),
HumanMessage(content=f"请将以下中文翻译成英语:\n{state['source_text']}"),
]
)
return {"translated": msg.content}
def proofread_en(state: State) -> dict:
"""英语分支:第二步 - 校对"""
msg = llm.invoke(
[
SystemMessage(content="你是英语校对专家,检查语法、用词和流畅度。"),
HumanMessage(
content=f"请校对以下英语翻译,修正错误并提升表达:\n{state['translated']}"
),
]
)
return {"proofread": msg.content}
def polish_en(state: State) -> dict:
"""英语分支:第三步 - 润色"""
msg = llm.invoke(
[
SystemMessage(content="你是英语润色专家,让文本更自然、地道。"),
HumanMessage(
content=f"请润色以下英语文本,使其更符合母语者习惯:\n{state['proofread']}"
),
]
)
return {"polished": msg.content}
def translate_to_ja(state: State) -> dict:
"""日语分支:第一步 - 翻译"""
msg = llm.invoke(
[
SystemMessage(content="你是专业的中日翻译,准确传达原文意思。"),
HumanMessage(content=f"请将以下中文翻译成日语:\n{state['source_text']}"),
]
)
return {"translated": msg.content}
def proofread_ja(state: State) -> dict:
"""日语分支:第二步 - 校对"""
msg = llm.invoke(
[
SystemMessage(content="你是日语校对专家,检查语法、用词和敬语使用。"),
HumanMessage(
content=f"请校对以下日语翻译,修正错误并提升表达:\n{state['translated']}"
),
]
)
return {"proofread": msg.content}
def polish_ja(state: State) -> dict:
"""日语分支:第三步 - 润色"""
msg = llm.invoke(
[
SystemMessage(content="你是日语润色专家,让文本更自然、地道。"),
HumanMessage(
content=f"请润色以下日语文本,使其更符合母语者习惯:\n{state['proofread']}"
),
]
)
return {"polished": msg.content}
def translate_to_ko(state: State) -> dict:
"""韩语分支:第一步 - 翻译"""
msg = llm.invoke(
[
SystemMessage(content="你是专业的中韩翻译,准确传达原文意思。"),
HumanMessage(content=f"请将以下中文翻译成韩语:\n{state['source_text']}"),
]
)
return {"translated": msg.content}
def proofread_ko(state: State) -> dict:
"""韩语分支:第二步 - 校对"""
msg = llm.invoke(
[
SystemMessage(content="你是韩语校对专家,检查语法、用词和敬语使用。"),
HumanMessage(
content=f"请校对以下韩语翻译,修正错误并提升表达:\n{state['translated']}"
),
]
)
return {"proofread": msg.content}
def polish_ko(state: State) -> dict:
"""韩语分支:第三步 - 润色"""
msg = llm.invoke(
[
SystemMessage(content="你是韩语润色专家,让文本更自然、地道。"),
HumanMessage(
content=f"请润色以下韩语文本,使其更符合母语者习惯:\n{state['proofread']}"
),
]
)
return {"polished": msg.content}
def route_by_lang(state: State) -> str:
"""路由函数:根据目标语言选择分支"""
return state["target_lang"]
# 构建图
builder = StateGraph(State)
# 路由节点
builder.add_node("router", route_language)
# 英语分支节点(链式)
builder.add_node("translate_en", translate_to_en)
builder.add_node("proofread_en", proofread_en)
builder.add_node("polish_en", polish_en)
# 日语分支节点(链式)
builder.add_node("translate_ja", translate_to_ja)
builder.add_node("proofread_ja", proofread_ja)
builder.add_node("polish_ja", polish_ja)
# 韩语分支节点(链式)
builder.add_node("translate_ko", translate_to_ko)
builder.add_node("proofread_ko", proofread_ko)
builder.add_node("polish_ko", polish_ko)
# 连接:START → router
builder.add_edge(START, "router")
# 路由:router → 各语言分支
builder.add_conditional_edges(
"router",
route_by_lang,
{
"en": "translate_en",
"ja": "translate_ja",
"ko": "translate_ko",
},
)
# 英语分支链式连接
builder.add_edge("translate_en", "proofread_en")
builder.add_edge("proofread_en", "polish_en")
builder.add_edge("polish_en", END)
# 日语分支链式连接
builder.add_edge("translate_ja", "proofread_ja")
builder.add_edge("proofread_ja", "polish_ja")
builder.add_edge("polish_ja", END)
# 韩语分支链式连接
builder.add_edge("translate_ko", "proofread_ko")
builder.add_edge("proofread_ko", "polish_ko")
builder.add_edge("polish_ko", END)
graph = builder.compile()
# 测试
result = graph.invoke({
"source_text": "人工智能正在改变我们的生活方式。",
"target_lang": "",
"translated": "",
"proofread": "",
"polished": "",
})
print(f"最终润色结果:{result['polished']}")
7.3 Prompt Chaining + Routing 的关键要点
- ✅路由层与处理层分离:路由只负责分类,不处理业务逻辑
- ✅每个分支独立链式处理:不同分支可以有不同的处理步骤数
- ✅状态传递清晰:链式节点通过 state 字段传递中间结果(如
translated → proofread → polished) - ✅可扩展性强:新增语言只需添加一组节点和一条路由映射
⚡ 8. 实战五:Parallelization + Routing(同一意图下并行多个子任务)
这是提升效率的经典组合:先路由到不同分支,然后在某个分支内并行执行多个独立子任务,最后聚合结果。
8.1 适用场景
- 内容生成:先分类(技术文档/营销文案),然后并行生成“标题/正文/摘要/关键词”
- 数据分析:先分类(销售数据/用户行为),然后并行执行“统计/可视化/报告生成”
- 多维度评估:先分类(代码审查/文档审查),然后并行检查“性能/安全性/可读性”
8.2 核心设计思路
- 第一层:路由层(识别任务类型)
- 第二层:并行处理层(多个独立节点同时执行,都从 START 或同一节点出发)
- 第三层:聚合层(等待所有并行任务完成,合并结果)
下面示例:智能内容生成系统,先路由到不同类型(技术文档/营销文案),然后在每个类型内并行生成“标题/正文/摘要/关键词”,最后聚合输出。
from typing import TypedDict
from typing_extensions import Literal
from pydantic import BaseModel, Field
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langchain_core.messages import SystemMessage, HumanMessage
llm = init_chat_model(model="deepseek-chat", model_provider="deepseek")
class ContentTypeRoute(BaseModel):
content_type: Literal["tech", "marketing"] = Field(
description="内容类型:tech(技术文档)/marketing(营销文案)"
)
type_router = llm.with_structured_output(ContentTypeRoute)
class State(TypedDict):
topic: str
content_type: str
title: str
body: str
summary: str
keywords: str
final_output: str
def route_content_type(state: State) -> dict:
"""路由层:识别内容类型"""
result = type_router.invoke(
[
SystemMessage(
content=(
"你是内容类型路由器。根据用户主题,判断应该生成技术文档还是营销文案。\n"
"- 技术类主题(如'API使用'、'系统架构')→ tech\n"
"- 营销类主题(如'产品推广'、'品牌宣传')→ marketing"
)
),
HumanMessage(content=f"主题:{state['topic']}"),
]
)
return {"content_type": result.content_type}
# ========== 技术文档分支:并行生成节点 ==========
def generate_tech_title(state: State) -> dict:
"""技术文档:生成标题"""
msg = llm.invoke(
[
SystemMessage(content="你是技术文档专家,生成清晰、专业的标题。"),
HumanMessage(content=f"为主题『{state['topic']}』生成一个技术文档标题。"),
]
)
return {"title": msg.content}
def generate_tech_body(state: State) -> dict:
"""技术文档:生成正文"""
msg = llm.invoke(
[
SystemMessage(content="你是技术文档专家,生成详细、结构化的正文内容。"),
HumanMessage(content=f"为主题『{state['topic']}』生成技术文档正文(500-800字)。"),
]
)
return {"body": msg.content}
def generate_tech_summary(state: State) -> dict:
"""技术文档:生成摘要"""
msg = llm.invoke(
[
SystemMessage(content="你是技术文档专家,生成简洁、准确的摘要。"),
HumanMessage(content=f"为主题『{state['topic']}』生成技术文档摘要(100-150字)。"),
]
)
return {"summary": msg.content}
def generate_tech_keywords(state: State) -> dict:
"""技术文档:生成关键词"""
msg = llm.invoke(
[
SystemMessage(content="你是技术文档专家,提取关键术语和标签。"),
HumanMessage(content=f"为主题『{state['topic']}』提取5-8个技术关键词。"),
]
)
return {"keywords": msg.content}
# ========== 营销文案分支:并行生成节点 ==========
def generate_marketing_title(state: State) -> dict:
"""营销文案:生成标题"""
msg = llm.invoke(
[
SystemMessage(content="你是营销文案专家,生成吸引眼球、有感染力的标题。"),
HumanMessage(content=f"为主题『{state['topic']}』生成一个营销文案标题。"),
]
)
return {"title": msg.content}
def generate_marketing_body(state: State) -> dict:
"""营销文案:生成正文"""
msg = llm.invoke(
[
SystemMessage(content="你是营销文案专家,生成有说服力、情感化的正文内容。"),
HumanMessage(content=f"为主题『{state['topic']}』生成营销文案正文(300-500字)。"),
]
)
return {"body": msg.content}
def generate_marketing_summary(state: State) -> dict:
"""营销文案:生成摘要"""
msg = llm.invoke(
[
SystemMessage(content="你是营销文案专家,生成简洁、有吸引力的摘要。"),
HumanMessage(content=f"为主题『{state['topic']}』生成营销文案摘要(50-100字)。"),
]
)
return {"summary": msg.content}
def generate_marketing_keywords(state: State) -> dict:
"""营销文案:生成关键词"""
msg = llm.invoke(
[
SystemMessage(content="你是营销文案专家,提取营销关键词和标签。"),
HumanMessage(content=f"为主题『{state['topic']}』提取5-8个营销关键词。"),
]
)
return {"keywords": msg.content}
# ========== 聚合节点 ==========
def aggregate_tech_content(state: State) -> dict:
"""技术文档:聚合所有并行结果"""
output = f"# {state['title']}\n\n"
output += f"## 摘要\n{state['summary']}\n\n"
output += f"## 正文\n{state['body']}\n\n"
output += f"## 关键词\n{state['keywords']}"
return {"final_output": output}
def aggregate_marketing_content(state: State) -> dict:
"""营销文案:聚合所有并行结果"""
output = f"【标题】{state['title']}\n\n"
output += f"【摘要】{state['summary']}\n\n"
output += f"【正文】\n{state['body']}\n\n"
output += f"【关键词】{state['keywords']}"
return {"final_output": output}
def route_by_type(state: State) -> str:
"""路由函数:根据内容类型选择分支"""
return state["content_type"]
# 构建图
builder = StateGraph(State)
# 路由节点
builder.add_node("router", route_content_type)
# 技术文档分支:并行节点
builder.add_node("tech_title", generate_tech_title)
builder.add_node("tech_body", generate_tech_body)
builder.add_node("tech_summary", generate_tech_summary)
builder.add_node("tech_keywords", generate_tech_keywords)
builder.add_node("tech_aggregator", aggregate_tech_content)
# 营销文案分支:并行节点
builder.add_node("marketing_title", generate_marketing_title)
builder.add_node("marketing_body", generate_marketing_body)
builder.add_node("marketing_summary", generate_marketing_summary)
builder.add_node("marketing_keywords", generate_marketing_keywords)
builder.add_node("marketing_aggregator", aggregate_marketing_content)
# 连接:START → router
builder.add_edge(START, "router")
# 路由:router → 各内容类型分支的入口节点
builder.add_conditional_edges(
"router",
route_by_type,
{
"tech": "tech_parallel_entry", # 技术文档分支入口
"marketing": "marketing_parallel_entry", # 营销文案分支入口
},
)
# ========== 技术文档分支:真正的并行执行 ==========
def tech_parallel_entry(state: State) -> dict:
"""技术文档分支入口:触发并行任务"""
# 这个节点不做实际工作,只是作为并行任务的起点
# 实际工作中,可以在这里设置并行标志
return {}
# 技术文档分支:4个并行节点都从入口节点出发
builder.add_node("tech_parallel_entry", tech_parallel_entry)
builder.add_edge("tech_parallel_entry", "tech_title")
builder.add_edge("tech_parallel_entry", "tech_body")
builder.add_edge("tech_parallel_entry", "tech_summary")
builder.add_edge("tech_parallel_entry", "tech_keywords")
# 所有并行节点完成后,都指向聚合节点
builder.add_edge("tech_title", "tech_aggregator")
builder.add_edge("tech_body", "tech_aggregator")
builder.add_edge("tech_summary", "tech_aggregator")
builder.add_edge("tech_keywords", "tech_aggregator")
builder.add_edge("tech_aggregator", END)
# ========== 营销文案分支:真正的并行执行 ==========
def marketing_parallel_entry(state: State) -> dict:
"""营销文案分支入口:触发并行任务"""
return {}
# 营销文案分支:4个并行节点都从入口节点出发
builder.add_node("marketing_parallel_entry", marketing_parallel_entry)
builder.add_edge("marketing_parallel_entry", "marketing_title")
builder.add_edge("marketing_parallel_entry", "marketing_body")
builder.add_edge("marketing_parallel_entry", "marketing_summary")
builder.add_edge("marketing_parallel_entry", "marketing_keywords")
# 所有并行节点完成后,都指向聚合节点
builder.add_edge("marketing_title", "marketing_aggregator")
builder.add_edge("marketing_body", "marketing_aggregator")
builder.add_edge("marketing_summary", "marketing_aggregator")
builder.add_edge("marketing_keywords", "marketing_aggregator")
builder.add_edge("marketing_aggregator", END)
graph = builder.compile()
# 测试
result = graph.invoke({
"topic": "如何使用 LangGraph 构建智能体",
"content_type": "",
"title": "",
"body": "",
"summary": "",
"keywords": "",
"final_output": "",
})
print(result["final_output"])
8.3 并行执行的工作原理
在上面的示例中,我们使用了入口节点(tech_parallel_entry/marketing_parallel_entry)来触发并行执行:
- 路由层:
router根据内容类型,路由到对应的入口节点 - 并行层:入口节点同时连接到 4 个并行节点(title/body/summary/keywords)
- 聚合层:所有并行节点完成后,都指向聚合节点;LangGraph 会自动等待所有前置节点完成后再执行聚合节点
关键点:
- ✅ 多个节点从同一个节点出发 → 它们会并行执行
- ✅ 多个节点都指向同一个聚合节点→ LangGraph 会等待所有节点完成后再执行聚合节点
- ✅ 这种模式可以显著提升性能:4 个独立任务并行执行,总时间 ≈ 最慢的那个任务的时间
注意:如果并行节点之间有依赖关系,应该使用链式连接(Prompt Chaining),而不是并行连接。
8.4 Parallelization + Routing 的关键要点
- ✅并行节点独立性:并行执行的节点之间不应该有依赖关系
- ✅聚合节点等待:聚合节点需要等待所有并行任务完成(LangGraph 会自动处理)
- ✅路由后并行:先路由到分支,再在分支内并行,避免跨分支的混乱
- ✅性能提升明显:3-4 个并行任务可以显著缩短总执行时间
🔍 9. 路由设计的最佳实践(强烈建议看)
7.1 把“路由标签”与“节点名”分离
很多人会让route_fn直接返回节点名,这当然能跑,但长期维护会痛。
更推荐:
route_fn返回业务意图标签(pricing/refund/tech)- mapping 决定标签对应哪个节点(未来节点换名字不影响路由逻辑)
7.2 路由要“可观测”
建议把decision/intent放到 state 里(你已经在上面这么做了),这样:
- debug 时能直接看到“路由怎么选的”
- 可以统计每类请求占比(运营/产品很喜欢)
7.3 兜底策略(避免线上事故)
路由输出如果不在 mapping 里,建议统一 fallback:
- 回到
other(低风险回答) - 或走
human_handoff(第 6 讲的人机协作会讲)
7.4 多分支不是越多越好
建议遵循:
- 3~6 个一级分支最舒服
- 更细的分流放在二级路由(路由节点之间也可以再路由)
📌 10. 小结
本讲你已经掌握了让智能体“会决策”的关键能力:
- ✅ 条件边
add_conditional_edges()的用法与心智模型 - ✅ 规则路由(稳定兜底)与 LLM 路由(语义分流)
- ✅Prompt Chaining + Routing:先路由分类,再在每个分支内进行链式处理(翻译→校对→润色)
- ✅Parallelization + Routing:先路由分类,再在分支内并行执行多个独立任务,最后聚合结果
- ✅ 综合案例:智能客服分流工作流
核心模式总结:
| 模式 | 适用场景 | 关键特点 |
|---|---|---|
| 规则路由 | 分类明确、追求稳定 | if/else 判断,最可靠 |
| LLM 路由 | 语义理解、类别扩展 | 结构化输出,最灵活 |
| Prompt Chaining + Routing | 多步骤处理、质量要求高 | 先分类,再链式处理 |
| Parallelization + Routing | 独立子任务、性能要求高 | 先分类,再并行执行 |