2026-04-27LangGraph多分支实战:用图结构实现专业化任务并行协作
在大模型应用开发中,我们常常会遇到这样的场景:一个复杂问题需要多个专业化任务协同处理,单独一个节点无法完成所有逻辑,且多个任务之间可以并行执行以提升效率。传统的线性工作流的往往显得笨重,而LangGraph的出现,恰好解决了这一痛点------它以图结构为核心,轻松实现多分支并行、状态管理与结果汇聚,让复杂任务的编排变得直观又高效。
今天就结合一个实际实战案例,聊聊LangGraph多分支的核心特点、实现逻辑,用通俗的语言讲透如何用LangGraph构建多分支协作的智能应用,适合刚接触LangGraph的开发者参考。
一、先明确核心场景:为什么需要多分支?
我们以"户外活动适宜性评估"为具体场景:判断一个城市某一时刻是否适合户外活动,需要两个核心数据支撑------天气数据(温度、天气状况)和时间数据(当前时段、时段类型),这两个数据的获取的是相互独立的,无需等待其中一个完成再执行另一个,且最终的决策必须结合两个数据才能得出,缺一不可。
这种"多任务并行获取、单节点汇聚决策"的场景,正是LangGraph多分支的优势所在。它不像线性工作流那样只能串行执行,而是可以让多个专业化任务同时推进,既提升了执行效率,又能实现"分工明确、协同决策"的逻辑,这也是LangGraph超越传统工作流引擎的核心亮点之一。
二、LangGraph多分支核心特点:分工、并行、汇聚
LangGraph的核心哲学是"将程序逻辑从过程指令转化为状态拓扑空间",而多分支作为其核心能力,主要体现在三个方面,结合我们的实战案例就能轻松理解:
1. 专业化分工:每个分支只做一件事
多分支的核心前提是"职责拆分",避免一个节点承担过多任务,提升代码的可维护性和复用性。在我们的案例中,我们将整个任务拆分为两个专业化分支,每个分支专注于自己的核心职责,不越界、不冗余:
- 分支一:专注获取天气相关数据,仅输出原始天气信息,不做任何决策判断,确保数据的纯粹性;
- 分支二:专注获取时间相关数据,仅输出原始时间信息,同样不参与决策,只负责数据采集。
这种分工模式,不仅让每个分支的逻辑更简洁,也便于后续的扩展------比如后续需要新增"空气质量"分支,只需新增一个专业化节点即可,无需修改原有逻辑,体现了LangGraph模块化扩展的优势。
2. 并行执行:提升效率,无需相互等待
这是LangGraph多分支最实用的特点之一。两个专业化分支的任务是相互独立的,没有依赖关系,因此LangGraph会自动触发两个分支并行执行,而不是串行等待一个分支完成再执行另一个。
举个直观的例子:如果获取天气数据需要1秒,获取时间数据需要1秒,线性执行总共需要2秒,而并行执行只需1秒即可完成两个数据的获取,大幅提升了整体任务的执行效率。LangGraph的异步执行引擎会自动管理并行逻辑,包括同时启动多个节点、等待所有分支返回结果,还能通过内置信号量控制并发度,避免资源过载。
3. 汇聚决策:多分支结果融合,输出最终答案
多分支并行执行后,会产生多个原始数据,这些数据单独存在时没有实际意义------比如只知道天气晴朗,不知道具体时段,无法判断是否适合户外活动;只知道时段合适,不知道天气状况,也无法做出准确决策。
因此,LangGraph多分支的核心闭环是"汇聚节点":它会收集所有分支的输出结果,对数据进行整理、校验,再结合预设的决策逻辑,融合所有分支的数据,输出最终的完整答案。这个汇聚节点是整个多分支流程的"大脑",负责整合所有专业化分支的成果,实现"1+1>2"的协同效果。
三、LangGraph多分支实现方式:4步搭建完整流程
结合我们的实战案例,LangGraph多分支的实现逻辑非常清晰,无需复杂的配置,核心只需4步,就能搭建起"分工-并行-汇聚"的完整工作流,贴合LangGraph"状态驱动"的核心编程模型:
第一步:定义核心节点,明确分支职责
首先需要定义整个图结构的核心节点,每个节点对应一个具体的任务,节点的职责必须清晰、单一。我们的案例中,共定义了4个核心节点,各司其职:
- 入口节点:作为整个流程的启动器,负责接收用户的查询请求,同时触发两个专业化分支的并行执行,相当于多分支的"总开关";
- 两个专业化分支节点:分别负责获取天气、时间数据,输出原始数据,不参与决策;
- 汇聚决策节点:负责收集两个分支的原始数据,校验数据的完整性,再结合决策逻辑,输出最终的户外活动适宜性评估报告。
节点的定义遵循"单一职责原则",这也是LangGraph多分支能够灵活扩展的基础------每个节点都是独立的模块,可单独修改、替换,不影响其他节点的运行。
第二步:搭建图结构,连接分支与汇聚节点
LangGraph的核心是"图结构",节点定义完成后,需要通过"边"将这些节点连接起来,明确流程的执行顺序和关联关系,这也是实现多分支的关键:
- 入口节点与两个分支节点建立连接:确保入口节点启动后,能同时触发两个分支并行执行;
- 两个分支节点与汇聚决策节点建立连接:确保两个分支的输出结果,能准确传递到汇聚节点,供后续决策使用;
- 汇聚决策节点与结束节点连接:确保决策完成后,整个流程正常终止。
这种连接方式,形成了"入口→多分支并行→汇聚→结束"的完整闭环,图结构的可视化也让整个流程的逻辑更加清晰,便于后期排查问题和优化。
第三步:配置分支逻辑,确保数据规范
每个专业化分支节点,都需要配置对应的处理逻辑,确保输出的数据格式统一、规范,便于汇聚节点进行处理。比如:
对于天气分支,我们会规范其输出格式,确保每次返回的数据都包含温度、天气状况、城市等核心信息;对于时间分支,同样规范输出格式,包含小时、分钟、时段类型、城市等信息。
同时,我们还会对分支的输出结果进行简单的清理和标识,避免不同分支的输出数据混淆,确保汇聚节点能准确识别、提取每个分支的数据------这一步看似简单,却能有效避免后续数据提取时出现错误,提升整个流程的稳定性。
第四步:实现汇聚逻辑,完成多分支协同决策
汇聚节点是多分支流程的核心,其核心逻辑分为两步:
- 数据收集与校验:汇聚节点会自动收集两个分支的输出结果,对数据进行校验,判断是否存在缺失------如果某个分支的数据缺失,会提示决策失败,确保最终决策的准确性;
- 多数据融合决策:在确保数据完整的前提下,汇聚节点会结合预设的决策逻辑,同时考虑天气和时间两个因素,进行综合判断,最终输出清晰、易懂的评估报告,包括决策依据、综合结论和具体建议。
这里需要注意的是,汇聚节点的决策逻辑必须依赖所有分支的数据,单一分支的数据无法完成决策------这也体现了多分支协作的意义,通过多个专业化数据的融合,让决策更加全面、合理。
四、实战总结:LangGraph多分支的优势与适用场景
通过这个户外活动评估的实战案例,我们能清晰感受到LangGraph多分支的优势,它不仅解决了传统线性工作流效率低、扩展性差的问题,还带来了更灵活、更易维护的开发体验,其核心优势可总结为三点:
- 效率提升:多分支并行执行,大幅缩短整体任务的执行时间,尤其适合多任务独立的场景;
- 扩展性强:新增分支只需新增节点并建立连接,无需修改原有逻辑,适配复杂场景的迭代需求,这也是LangGraph被广泛应用于企业级场景的重要原因之一;
- 逻辑清晰:图结构的可视化的,让多分支的执行流程、节点关联一目了然,便于开发、调试和后期维护,同时状态驱动的模型也让流程控制更加灵活。
而LangGraph多分支的适用场景,远不止我们案例中的户外活动评估,只要是需要"多任务并行、多数据融合决策"的场景,都可以使用它来实现,比如:
- 客服自动化:将用户请求拆分为"意图识别""信息查询""回复生成"多个分支,并行处理,提升响应速度;
- 多维度数据分析:多个分支分别获取不同维度的数据,汇聚节点进行融合分析,输出综合报告;
- 智能代理开发:多个专业化代理并行工作,汇聚节点整合各代理的结果,完成复杂任务。
五、最后想说的话
LangGraph的多分支能力,本质上是将复杂的任务拆解为多个可并行的专业化子任务,再通过汇聚节点实现协同决策,它让大模型应用的开发从"线性思维"转向"图结构思维",不仅提升了开发效率,也让复杂工作流的设计变得更加直观、灵活。
本文的案例虽然简单,但涵盖了LangGraph多分支的核心实现逻辑------节点定义、图结构搭建、分支配置、汇聚决策,只要掌握了这四个核心步骤,就能轻松应对大多数多分支协作的场景。
如果是刚接触LangGraph,建议从简单的多分支场景入手,熟悉节点、边、状态的核心概念,再逐步尝试更复杂的循环、条件分支等功能,相信你会发现LangGraph在复杂大模型应用开发中的强大之处。
后续我也会分享更多LangGraph的实战技巧,比如条件分支、循环逻辑的实现,感兴趣的可以关注一下~
代码实现:
import json
import re
from typing import List, Dict, Any, Optional
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import MessageGraph, END
def remove_think_tags(text):
"""
移除字符串中<think></think>标签及其内容
Args:
text: 包含think标签的字符串
Returns:
移除think标签及其内容后的字符串
"""
# 支持多行内容和标签可能有的属性
pattern = r'<think[^>]*>.*?</think>'
result = re.sub(pattern, '', text, flags=re.DOTALL)
# 清理空白字符
result = result.strip()
result = re.sub(r'\n{3,}', '\n\n', result) # 限制连续换行
return result
def extract_json_from_llm_response(text: str) -> Optional[Dict[str, Any]]:
"""
从LLM响应中健壮地提取JSON对象
处理多种格式:
1. 纯JSON: {"key": "value"}
2. Markdown代码块: ```json {...} ```
3. 带前缀/后缀的JSON: "Result: {...}"
"""
text = remove_think_tags(text.strip())
# 尝试1:直接解析整个字符串
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# 尝试2:提取Markdown代码块中的JSON
code_block_patterns = [
r'```(?:json)?\s*({.*?})\s*```', # ```json {...} ```
r'```\s*({.*?})\s*```', # ``` {...} ```
r'({.*})' # 直接匹配花括号内容(最后手段)
]
for pattern in code_block_patterns:
match = re.search(pattern, text, re.DOTALL)
if match:
try:
return json.loads(match.group(1))
except json.JSONDecodeError:
continue
# 尝试3:查找第一个{和最后一个}之间的内容(处理嵌套)
start = text.find('{')
end = text.rfind('}')
if start != -1 and end != -1 and start < end:
candidate = text[start:end + 1]
try:
return json.loads(candidate)
except json.JSONDecodeError:
pass
print(f"⚠️ JSON提取失败,原始响应: {text[:100]}...")
return None
# ==================== 1. LLM 配置 ====================
DEEPSEEK_API_KEY = "123" # 替换为实际的 API Key
llm = ChatOpenAI(
api_key=DEEPSEEK_API_KEY,
base_url="http://192.168.0.100:8087/v1",
model="qwen3.5-35b-gptq",
temperature=0.3,
max_tokens=20000,
)
# ==================== 2. 专业化分支节点(只提供原始数据) ====================
def source_node(state: List[Any]) -> List[Any]:
"""
源节点:触发两个专业化分支
返回原始状态,LangGraph 会自动将消息分发到所有出边(实现并行)
"""
print("\n[source_node] 触发天气和时间两个专业化分支")
return state
def weather_branch(state: List[Any]) -> AIMessage:
"""
天气分支:仅返回原始天气数据(无决策能力)
输出示例: {"temperature": 22, "condition": "sunny", "city": "北京"}
"""
print("\n[weather_branch] 专注提取天气数据(无决策能力)")
last_msg = state[-1].content if state else ""
system_prompt = (
"你是一个专业天气数据提取器。严格遵守:\n"
"1. 只提取天气相关数据,不做任何决策或建议\n"
"2. 输出纯JSON,不要任何额外文本或Markdown代码块\n"
"3. JSON格式: {\"temperature\": 整数, \"condition\": \"sunny/cloudy/rainy/snowy\", \"city\": \"城市名\"}"
)
user_prompt = f"用户问题:{last_msg}\n\n请返回该城市的模拟天气数据:"
response = llm.invoke([
SystemMessage(content=system_prompt),
HumanMessage(content=user_prompt)
])
# 清理响应并添加分支标识
cleaned = remove_think_tags(response.content.strip())
return AIMessage(content=f"[WEATHER]{cleaned}")
def time_branch(state: List[Any]) -> AIMessage:
"""
时间分支:仅返回原始时间数据(无决策能力)
输出示例: {"hour": 15, "minute": 30, "period": "afternoon", "city": "北京"}
"""
print("\n[time_branch] 专注提取时间数据(无决策能力)")
last_msg = state[-1].content if state else ""
system_prompt = (
"你是一个专业时间数据提取器。严格遵守:\n"
"1. 只提取当前时间数据,不做任何决策或建议\n"
"2. 输出纯JSON,不要任何额外文本或Markdown代码块\n"
"3. JSON格式: {\"hour\": 0-23, \"minute\": 0-59, \"period\": \"morning/afternoon/evening/night\", \"city\": \"城市名\"}"
)
user_prompt = f"用户问题:{last_msg}\n\n请返回该城市的模拟当前时间:"
response = llm.invoke([
SystemMessage(content=system_prompt),
HumanMessage(content=user_prompt)
])
cleaned = remove_think_tags(response.content.strip())
return AIMessage(content=f"[TIME]{cleaned}")
def decision_sink(state: List[Any]) -> AIMessage:
"""
汇聚决策节点:必须结合天气+时间数据才能做出完整回答
单一分支数据无法回答"是否适合户外活动"这类综合问题
"""
print("\n[decision_sink] 综合天气+时间数据进行智能决策")
# 从state中提取带标识的分支结果(兼容并行执行的顺序不确定性)
weather_data = None
time_data = None
# 从最新消息向前查找(确保获取最新分支输出)
for msg in reversed(state):
if isinstance(msg, AIMessage):
content = msg.content
if "[WEATHER]" in content and weather_data is None:
json_str = content.replace("[WEATHER]", "").strip()
weather_data = extract_json_from_llm_response(json_str)
print(f" ✓ 天气数据提取: {weather_data}")
elif "[TIME]" in content and time_data is None:
json_str = content.replace("[TIME]", "").strip()
time_data = extract_json_from_llm_response(json_str)
print(f" ✓ 时间数据提取: {time_data}")
# 验证必要数据
if not weather_data or not time_data:
missing = []
if not weather_data: missing.append("天气数据")
if not time_data: missing.append("时间数据")
return AIMessage(content=f"❌ 决策失败:缺少{'和'.join(missing)},无法进行综合判断")
# === 核心:必须结合两个分支数据才能做出合理决策 ===
decision = make_outdoor_activity_decision(weather_data, time_data)
return AIMessage(content=decision)
def make_outdoor_activity_decision(weather: Dict[str, Any], time: Dict[str, Any]) -> str:
"""
基于天气+时间的综合决策(必须两个数据源)
单独任一数据都无法安全判断户外活动适宜性
"""
city = weather.get("city", time.get("city", "未知城市"))
temp = weather.get("temperature", "N/A")
condition = weather.get("condition", "unknown")
hour = time.get("hour", -1)
period = time.get("period", "unknown")
# 决策逻辑:必须同时考虑天气和时间
factors = []
recommendations = []
suitability_score = 0
# ===== 时间因素分析(单独无法决策)=====
if hour < 6 or hour > 21:
factors.append(f"⚠️ {period}时段({hour}点):光线不足/温度较低")
suitability_score -= 30
elif 6 <= hour <= 9:
factors.append(f"✅ 早晨{hour}点:适合晨练,空气清新")
suitability_score += 20
elif 15 <= hour <= 17:
factors.append(f"✅ 下午{hour}点:阳光适宜,温度舒适")
suitability_score += 25
else:
factors.append(f"⚪ {period}时段({hour}点):时间中性")
# ===== 天气因素分析(单独无法决策)=====
if condition == "rainy":
factors.append("⚠️ 正在下雨:路面湿滑,易感冒")
suitability_score -= 40
elif condition == "snowy":
factors.append("⚠️ 正在下雪:路面积雪,能见度低")
suitability_score -= 45
elif condition == "sunny":
if isinstance(temp, int) and temp > 32:
factors.append(f"⚠️ 晴天但高温({temp}°C):中暑风险高")
suitability_score -= 25
elif isinstance(temp, int) and 20 <= temp <= 28:
factors.append(f"✅ 晴朗舒适({temp}°C):紫外线适中")
suitability_score += 30
else:
factors.append(f"⚪ 晴天({temp}°C):天气良好")
suitability_score += 15
elif condition == "cloudy":
factors.append(f"✅ 多云({temp}°C):紫外线弱,体感舒适")
suitability_score += 25
# ===== 综合决策(必须两个数据)=====
if suitability_score >= 30:
conclusion = "🟢 非常适合户外活动!"
recommendations = [
"建议进行:慢跑、骑行、公园散步",
"注意:及时补充水分"
]
elif suitability_score >= 10:
conclusion = "🟡 适合户外活动,需注意条件"
recommendations = [
"建议进行:短途散步、户外休闲",
"注意:根据天气准备相应装备(雨具/防晒)"
]
elif suitability_score >= -10:
conclusion = "🟠 谨慎进行户外活动"
recommendations = [
"建议:缩短户外时间,选择遮蔽区域",
"避免:剧烈运动、长时间暴露"
]
else:
conclusion = "🔴 不建议户外活动"
recommendations = [
"建议:室内活动替代",
"原因:天气/时间条件不适宜"
]
# 生成综合报告
report = (
f"📍 {city}户外活动适宜性综合评估\n"
f"{'─' * 40}\n"
f"🌤️ 天气数据: {temp}°C, {condition}\n"
f"⏰ 时间数据: {hour}点 ({period})\n"
f"{'─' * 40}\n"
f"💡 决策依据:\n"
)
for factor in factors:
report += f" • {factor}\n"
report += f"{'─' * 40}\n"
report += f"🎯 综合结论: {conclusion}\n"
report += f"📋 建议:\n"
for rec in recommendations:
report += f" • {rec}\n"
return report
# ==================== 3. 构建专业化多分支图 ====================
graph = MessageGraph()
graph.add_node("source", source_node)
graph.add_node("weather_branch", weather_branch)
graph.add_node("time_branch", time_branch)
graph.add_node("decision_sink", decision_sink)
graph.set_entry_point("source")
graph.add_edge("source", "weather_branch")
graph.add_edge("source", "time_branch")
graph.add_edge("weather_branch", "decision_sink")
graph.add_edge("time_branch", "decision_sink")
graph.add_edge("decision_sink", END)
app = graph.compile()
#画图
print(app.get_graph().draw_ascii())
# ==================== 4. 执行测试 ====================
if __name__ == "__main__":
print("=" * 70)
print("✅ 真实多分支协作测试:需要天气+时间数据共同决策")
print("=" * 70)
test_cases = [
# "北京现在适合户外跑步吗?",
# "上海下午四点去公园散步合适吗?",
# "广州晚上九点适合夜跑吗?",
"成都中午十二点适合户外野餐吗?"
]
for i, query in enumerate(test_cases, 1):
print(f"\n{'=' * 70}")
print(f"_TestCase {i}: {query}_")
print('=' * 70)
inputs = [HumanMessage(content=query)]
for event in app.stream(inputs):
node = list(event.keys())[0]
msg = event[node]
# 打印中间节点输出(简化显示)
if node in ["weather_branch", "time_branch"]:
content_preview = msg.content.replace("[WEATHER]", "").replace("[TIME]", "").strip()
prefix = "🌤️" if node == "weather_branch" else "⏰"
print(f"\n[→ {node}] {prefix} {content_preview[:80]}...")
elif node == "decision_sink":
print(f"\n[→ {node}] 🎯 最终决策报告:\n")
print(msg.content)
结果输出:
+-----------+
| start |
+-----------+
*
*
*
+--------+
| source |
+--------+*
** **
** **
** **
+-------------+ +----------------+
| time_branch | | weather_branch |
+-------------+ +----------------+
** **
** **
** **
+---------------+
| decision_sink |
+---------------+
*
*
*
+---------+
| end |
+---------+
======================================================================
✅ 真实多分支协作测试:需要天气+时间数据共同决策
======================================================================
======================================================================
TestCase 1: 成都中午十二点适合户外野餐吗?
======================================================================
source_node\] 触发天气和时间两个专业化分支 \[weather_branch\] 专注提取天气数据(无决策能力) \[time_branch\] 专注提取时间数据(无决策能力) \[→ weather_branch\] 🌤️ \`\`\`json {"temperature": 25, "condition": "sunny", "city": "成都"} \`\`\`... \[→ time_branch\] ⏰ {"hour": 12, "minute": 0, "period": "afternoon", "city": "成都"}... \[decision_sink\] 综合天气+时间数据进行智能决策 ✓ 时间数据提取: {'hour': 12, 'minute': 0, 'period': 'afternoon', 'city': '成都'} ✓ 天气数据提取: {'temperature': 25, 'condition': 'sunny', 'city': '成都'} \[→ decision_sink\] 🎯 最终决策报告: 📍 成都户外活动适宜性综合评估 ──────────────────────────────────────── 🌤️ 天气数据: 25°C, sunny ⏰ 时间数据: 12点 (afternoon) ──────────────────────────────────────── 💡 决策依据: • ⚪ afternoon时段(12点):时间中性 • ✅ 晴朗舒适(25°C):紫外线适中 ──────────────────────────────────────── 🎯 综合结论: 🟢 非常适合户外活动! 📋 建议: • 建议进行:慢跑、骑行、公园散步 • 注意:及时补充水分 更多学习资料尽在 [老虎网盘资源](http://resources.kittytiger.cn/)