目录
LangGraph开发AI Agent实践(人工智能技术丛书)【行情 报价 价格 评测】-京东
LangGraph是LangChain生态中用于构建有状态、多参与者、非线性LLM工作流的库。它特别适合需要分支(branching)、循环(looping)或动态路由(dynamic routing)的复杂Agent编排场景。其核心思想是将工作流建模为有向图(Directed Graph),其中节点是Agent(或工具),边是条件路由逻辑。
5.2.1 非线性工作流核心概念
1. LangGraph非线性工作流核心价值
LangGraph是LangChain生态中的有状态图工作流框架,专为解决LLM应用中的非线性逻辑设计。相比传统线性流水线,它支持:
- 分支(并行/条件执行)。
- 循环(结果校验重试)。
- 动态路由(根据输入/中间结果切换执行路径)。
- 状态持久化(跨节点共享上下文)。
2. 非线性工作流关键技术
- 条件分支:根据输入特征分流执行,如问题分类(事实问答、创意写作、代码生成)。
- 动态路由 :基于中间结果动态切换执行路径,如答案质量校验(合格则直接输出,否则重新生成)。
- 并行分支:多任务并发执行,实现多维度内容分析(如情感分析、关键词提取、实体识别)。
- 循环节点:当结果不满足条件时自动重试,适用于生成内容合规性校验与格式修正。
3. DeepSeek LLM适配
DeepSeek系列模型(如deepseek-chat、deepseek-coder)支持中文优化、长上下文处理,通过LangChain的DeepSeek封装可无缝集成到LangGraph中,核心优势如下:
- 中文理解准确率高。
- 代码/逻辑推理能力强。
- 支持工具调用与函数执行。
4. 路由策略
LangGraph的动态路由通过条件边(conditional edges)实现:
- 定义路由函数:接收当前状态,返回目标节点名称。
- 支持多分支路由:根据状态变量(如query_type、is_complete)动态切换路径。
- 循环路由:通过END与节点名称的条件判断,实现结果不满足时的重试逻辑。
from typing import Literal
from langgraph.graph import END, StateGraph
5.2.2 实战案例:使用条件边路由到节点
1. 案例场景:智能客服问题处理系统
系统需求说明如下:
- 接收用户咨询,先分类(订单问题/产品咨询/投诉建议)。
- 订单问题:查询订单状态(模拟工具调用)。
- 产品咨询:生成产品说明(直接LLM响应)。
- 投诉建议:记录内容并转人工(并行执行两个任务)。
- 结果校验:若回答不完整,则重新调用对应节点。
2. 基础组件定义
1)初始化DeepSeek LLM
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
import os
# 加载环境变量
load_dotenv()
# 初始化qwen-plus LLM(指定聊天模型)
llm = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("DASHSCOPE_API_KEY"), # 从 .env 读取
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
temperature=0
)
2)定义工作流状态
LangGraph通过StateGraph管理状态,状态变量需明确类型:
from typing import TypedDict, Optional
from langgraph.graph import StateGraph, END
# 定义工作流状态结构
class SupportState(TypedDict):
user_query: str # 用户原始查询
query_type: Optional[str] # 问题类型(订单/产品/投诉)
order_id: Optional[str] # 订单号(仅订单问题需要)
response: Optional[str] # 最终响应
is_complete: bool # 响应是否完整(用于动态路由)
3. 核心节点实现
节点是工作流的执行单元,每个节点接收State并返回更新后的State。
1)问题分类节点(路由入口)
根据用户查询判断问题类型,为动态路由提供依据:
def classify_query(state: SupportState) -> SupportState:
"""分类用户查询:订单问题/产品咨询/投诉建议"""
user_query = state["user_query"]
# 调用qwen-plus 进行分类
prompt = f"""
请将用户查询分类为以下三类:订单问题、产品咨询、投诉建议
分类规则:
- 订单问题:包含订单号、查询物流、修改订单、取消订单等关键词
- 产品咨询:包含产品功能、使用方法、规格参数、兼容性等关键词
- 投诉建议:包含投诉、不满、建议、改进等关键词
用户查询:{user_query}
仅返回分类结果,无须额外说明。
"""
query_type = llm.invoke(prompt).strip()
print(f"问题分类结果:{query_type}")
# 提取订单号(仅订单问题)
order_id = None
if query_type == "订单问题":
extract_prompt = f"""
从用户查询中提取订单号(通常为6~12位数字或字母+数字组合),
若未提及订单号,则返回"无"。
用户查询:{user_query}
仅返回订单号或"无"。
"""
order_id = llm.invoke(extract_prompt).strip()
print(f"提取订单号:{order_id}")
return {
**state,
"query_type": query_type,
"order_id": order_id,
"is_complete": False # 初始响应未完成
}
2)分支处理节点
(1)订单问题处理(模拟工具调用):
def handle_order(state: SupportState) -> SupportState:
"""处理订单问题:查询订单状态(模拟工具调用)"""
order_id = state["order_id"]
user_query = state["user_query"]
if order_id == "无":
response = "为了帮你查询订单状态,请提供你的订单号(6~12位数字或字母+数字组合)。"
else:
# 模拟调用订单查询API
mock_order_status = f"订单{order_id}当前状态:已发货,物流单号:SF123456789,预计明日送达。"
response = f"您好!{mock_order_status} 若有其他需求,请随时告知。"
print(f"订单问题响应:{response}")
return {**state, "response": response}
(2)产品咨询处理(直接LLM生成):
def handle_product(state: SupportState) -> SupportState:
"""处理产品咨询:生成产品说明"""
user_query = state["user_query"]
prompt = f"""
作为产品顾问,请详细解答用户的产品咨询,语言通俗易懂,结构清晰。
用户咨询:{user_query}
回答要求:
1. 先明确核心问题
2. 分点说明(不超过3点)
3. 结尾提供进一步帮助引导
"""
response = llm.invoke(prompt)
print(f"产品咨询响应:{response}")
return {**state, "response": response}
(3)投诉建议处理(并行执行):
def handle_complaint_record(state: SupportState) -> SupportState:
"""处理投诉建议:记录投诉内容(并行节点1)"""
user_query = state["user_query"]
# 模拟写入数据库
print(f"已记录投诉建议:{user_query}")
return {**state, "complaint_recorded": True}
def handle_complaint_forward(state: SupportState) -> SupportState:
"""处理投诉建议:转人工处理(并行节点2)"""
user_query = state["user_query"]
response = f"您好!您的反馈已收到,我们将在24小时内安排专属客服与您联系。感谢您的支持!"
print(f"投诉转人工响应:{response}")
return {**state, "response": response}
3)结果校验节点(动态路由判断)
def validate_response(state: SupportState) -> SupportState:
"""校验响应是否完整,决定是否重试"""
response = state["response"]
user_query = state["user_query"]
prompt = f"""
请判断以下响应是否完整解答了用户的查询:
1. 若响应直接回答了核心问题,或提供了明确的下一步操作指引(如要求补充信息),则判定为完整,返回"完整"
2. 若响应模糊、未解答核心问题、遗漏关键信息,则判定为不完整,返回"不完整"
用户查询:{user_query}
系统响应:{response}
仅返回"完整"或"不完整"。
"""
validation_result = llm.invoke(prompt).strip()
print(f"响应校验结果:{validation_result}")
return {
**state,
"is_complete": validation_result == "完整"
}
4. 构建LangGraph工作流
1)初始化图并添加节点
# 初始化状态图
graph = StateGraph(SupportState)
# 添加核心节点
graph.add_node("classify_query", classify_query) # 问题分类
graph.add_node("handle_order", handle_order) # 订单处理
graph.add_node("handle_product", handle_product) # 产品咨询处理
graph.add_node("handle_complaint_record", handle_complaint_record) # 投诉记录
graph.add_node("handle_complaint_forward", handle_complaint_forward) # 投诉转人工
graph.add_node("validate_response", validate_response) # 响应校验
2)定义边(路由规则)
(1)初始路由:分类后分流:
# 从分类节点根据query_type分流到对应处理节点
def route_after_classify(state: SupportState) -> str:
query_type = state["query_type"]
if query_type == "订单问题":
return "handle_order"
elif query_type == "产品咨询":
return "handle_product"
elif query_type == "投诉建议":
return "handle_complaint_record" # 先执行记录,再并行转人工
else:
return "handle_product" # 默认走产品咨询
# 添加条件边:分类节点 -> 处理节点
graph.add_conditional_edges(
"classify_query",
route_after_classify
)
(2)投诉处理并行路由:
# 投诉记录后,并行执行转人工(使用add_edge实现串行,实际并行可通过LangGraph并行节点优化)
graph.add_edge("handle_complaint_record", "handle_complaint_forward")
(3)响应校验与动态重试路由:
# 所有处理节点都指向校验节点
graph.add_edge("handle_order", "validate_response")
graph.add_edge("handle_product", "validate_response")
graph.add_edge("handle_complaint_forward", "validate_response")
# 校验节点的动态路由:完整则结束,不完整则重试对应处理节点
def route_after_validation(state: SupportState) -> str:
if state["is_complete"]:
return END # 流程结束
else:
# 不完整则重试原处理节点
query_type = state["query_type"]
if query_type == "订单问题":
return "handle_order"
elif query_type == "产品咨询":
return "handle_product"
elif query_type == "投诉建议":
return "handle_complaint_forward"
else:
return "handle_product"
# 添加条件边:校验节点 -> 结束或重试
graph.add_conditional_edges(
"validate_response",
route_after_validation
)
(4)设置入口节点:
graph.set_entry_point("classify_query")
3)编译图并运行
# 编译图(生成可执行工作流)
app = graph.compile()
# 测试运行:输入不同类型的用户查询
def test_workflow(user_query: str):
print(f"\n{'=' * 60}")
print(f"处理用户查询:{user_query}")
print(f"{'=' * 60}")
# 运行工作流
result = app.invoke({
"user_query": user_query,
"query_type": None,
"order_id": None,
"response": None,
"is_complete": False,
"retry_count": 0
})
print(f"\n最终响应:{result.get('response')}")
print(f"重试次数:{result.get('retry_count', 0)}")
print(f"{'=' * 60}")
return result
# 测试函数,运行所有测试用例
def run_all_tests():
"""运行所有测试用例"""
test_cases = [
("我的订单号是OD123456,请问什么时候发货?", "订单问题"),
("你们的智能音箱支持蓝牙5.0吗?怎么连接手机?", "产品咨询"),
("我对昨天收到的商品不满意,质量有问题,希望退款或换货。", "投诉建议"),
("我的订单什么时候能到?", "订单问题但没提供订单号"),
("我想问一下这个产品怎么用", "简短的产品咨询")
]
results = []
for i, (query, description) in enumerate(test_cases, 1):
print(f"\n{'#' * 60}")
print(f"测试 {i}: {description}")
print(f"查询内容: {query}")
print(f"{'#' * 60}")
try:
result = test_workflow(query)
results.append((query, description, result))
except Exception as e:
print(f"测试失败: {e}")
import traceback
traceback.print_exc()
print(f"\n{'=' * 60}")
print(f"所有测试完成,共运行 {len(results)} 个测试用例")
print(f"{'=' * 60}")
# 测试用例
if __name__ == "__main__":
run_all_tests()
5.2.3 实战案例:使用动态路由路由到节点
【案例5.2】非线性工作流智能客服问题处理系统LangGraph_Non-linear_workflow。
# 完整依赖导入(适配 LangGraph 1.0.5,纯串行非线性工作流)
from dotenv import load_dotenv
from typing import TypedDict, Optional
from langgraph.graph import StateGraph, END # LangGraph 1.0.5
import os
import requests
# ==================== 1. API 调用(本地模拟,确保无网络问题) ===================
load_dotenv()
api_key = os.getenv("DEEPSEEK_API_KEY")
def call_deepseek(prompt: str) -> str:
"""本地模拟 API 响应,非线性流程核心逻辑不变"""
if "分类" in prompt:
return "订单问题" if "订单" in prompt or "OD" in prompt else "产品咨询" if "产品" in prompt else "投诉建议"
elif "提取订单号" in prompt:
return "OD123456" if "OD123456" in prompt else "无"
elif "智能音箱" in prompt and "蓝牙" in prompt:
return "1. 支持蓝牙5.0;2. 手机搜设备配对;3. 兼容主流系统"
elif "校验" in prompt:
return "不完整" if "请提供订单号" in prompt else "完整"
return "系统暂时无法服务"
# ====================== 2. 状态定义(精简,仅保留核心控制字段) =================
class SupportState(TypedDict):
user_query: str
query_type: Optional[str]
order_id: Optional[str]
response: Optional[str]
retry_count: int
current_node: str # 唯一控制字段:指定当前要执行的节点
is_complete: bool
# ============= 3. 核心节点(每个节点仅处理自身逻辑,不依赖边关系分支) ==============
def classify_query(state: SupportState) -> SupportState:
"""分类节点:仅处理分类,更新下一个节点"""
print(f"[节点执行] 分类查询:{state['user_query']}")
query_type = call_deepseek(f"分类用户查询:{state['user_query']}")
# 非线性分支:根据分类指定下一个节点
next_node = "extract_order_id" if query_type == "订单问题" else "handle_product" if query_type == "产品咨询" else "handle_complaint"
return {
**state,
"query_type": query_type,
"current_node": next_node, # 明确下一个要执行的节点
"is_complete": False
}
def extract_order_id(state: SupportState) -> SupportState:
"""提取订单号节点:条件跳转"""
print(f"[节点执行] 提取订单号")
order_id = call_deepseek(f"提取订单号:{state['user_query']}")
# 条件逻辑:有订单号→处理订单,无→提示补充
next_node = "handle_order" if order_id != "无" else "prompt_order_id"
return {
**state,
"order_id": order_id,
"current_node": next_node,
"retry_count": state["retry_count"] + 1
}
def prompt_order_id(state: SupportState) -> SupportState:
"""提示补充订单号节点:触发重试"""
print(f"[节点执行] 提示补充订单号")
response = "请提供6~12位订单号(如OD123456)"
# 非线性循环:提示后跳转校验,校验不通过则重试
return {
**state,
"response": response,
"current_node": "validate_response"
}
def handle_order(state: SupportState) -> SupportState:
"""处理订单节点:生成响应"""
print(f"[节点执行] 处理订单:{state['order_id']}")
response = f"订单{state['order_id']}已发货,物流SF123456789,明日送达"
return {**state, "response": response, "current_node": "validate_response"}
def handle_product(state: SupportState) -> SupportState:
"""处理产品咨询节点:生成响应"""
print(f"[节点执行] 处理产品咨询")
response = call_deepseek(f"解答产品咨询:{state['user_query']}")
return {**state, "response": response, "current_node": "validate_response"}
def handle_complaint(state: SupportState) -> SupportState:
"""处理投诉节点:生成响应"""
print(f"[节点执行] 处理投诉")
response = "投诉已记录,24小时内联系你"
return {**state, "response": response, "current_node": "validate_response"}
def validate_response(state: SupportState) -> SupportState:
"""校验节点:非线性核心(终止/重试)"""
print(f"[节点执行] 校验响应,重试次数:{state['retry_count']}")
validation = call_deepseek(f"校验响应:{state['response']}")
# 终止条件:响应完整或重试3次
if validation == "完整" or state["retry_count"] >= 3:
return {**state, "is_complete": True, "current_node": "END"}
# 重试逻辑:根据问题类型跳转回对应节点
retry_node = "extract_order_id" if state["query_type"] == "订单问题" else "handle_product" if state["query_type"] == "产品咨询" else "handle_complaint"
return {**state, "current_node": retry_node, "retry_count": state["retry_count"] + 1}
# ================ 4. 动态路由节点(核心修复:单一入口,串行执行) ===================
def dynamic_router(state: SupportState) -> SupportState:
"""动态路由:根据 current_node 执行对应节点逻辑,避免多节点并发"""
current_node = state["current_node"]
print(f"[路由执行] 跳转至节点:{current_node}")
# 核心:路由节点内部调用目标节点,而非通过边关系触发
if current_node == "classify_query":
return classify_query(state)
elif current_node == "extract_order_id":
return extract_order_id(state)
elif current_node == "prompt_order_id":
return prompt_order_id(state)
elif current_node == "handle_order":
return handle_order(state)
elif current_node == "handle_product":
return handle_product(state)
elif current_node == "handle_complaint":
return handle_complaint(state)
elif current_node == "validate_response":
return validate_response(state)
elif current_node == "END":
return {**state, "is_complete": True}
else:
return {**state, "current_node": "classify_query"}
# ====================== 5. 工作流构建(极简边关系,无并发) ======================
graph = StateGraph(SupportState)
# 仅添加 1 个路由节点(所有逻辑都在路由内执行)
graph.add_node("dynamic_router", dynamic_router)
# 边关系:仅路由节点 ↔ 结束节点,完全规避多节点并发
graph.set_entry_point("dynamic_router")
graph.add_edge("dynamic_router", END)
# 编译工作流
app = graph.compile()
# ================== 6. 测试非线性工作流(串行执行,无并发错误) ==================
def test_workflow(user_query: str):
print(f"\n{'='*70}")
print(f"处理用户查询:{user_query}")
print(f"{'='*70}")
initial_state: SupportState = {
"user_query": user_query,
"query_type": None,
"order_id": None,
"response": None,
"retry_count": 0,
"current_node": "classify_query", # 初始节点
"is_complete": False
}
try:
# 执行工作流(循环直到流程完成)
result = initial_state
while not result["is_complete"]:
result = app.invoke(result)
# 避免无限循环(双重保障)
if result["retry_count"] > 5:
result["is_complete"] = True
result["response"] = "系统繁忙,请稍后再试"
print(f"\n【最终响应】:{result['response']}")
print(f"【流程状态】:完成,总重试次数:{result['retry_count']}")
except Exception as e:
print(f"\n【运行错误】:{str(e)}")
finally:
print(f"{'='*70}\n")
# 测试核心场景
if __name__ == "__main__":
test_workflow("我的订单号是OD123456,请问什么时候发货?")
# test_workflow("请问我的订单什么时候发货?") # 无订单号,触发重试
# test_workflow("你们的智能音箱支持蓝牙5.0吗?") # 产品咨询分支
运行输出:
======================================================================
处理用户查询:我的订单号是OD123456,请问什么时候发货?
======================================================================
[路由执行] 跳转至节点:classify_query
[节点执行] 分类查询:我的订单号是OD123456,请问什么时候发货?
[路由执行] 跳转至节点:extract_order_id
[节点执行] 提取订单号
[路由执行] 跳转至节点:handle_order
[节点执行] 处理订单:OD123456
[路由执行] 跳转至节点:validate_response
[节点执行] 校验响应,重试次数:1
【最终响应】:订单OD123456已发货,物流SF123456789,明日送达
【流程状态】:完成,总重试次数:1
======================================================================
该程序非线性工作流的特性如下。
- 多分支:订单、产品、投诉三个独立分支,根据查询类型动态切换。
- 条件跳转:订单号有无→不同处理路径;投诉信息完整性→不同响应。
- 循环重试:响应不完整时自动重试对应分支,最多重试3次。
- 状态驱动:通过current_node字段控制流程走向,符合非线性工作流定义。
项目扩展说明如下。
- 非线性逻辑清晰:分支、条件跳转、循环重试的逻辑直观,适合教学演示。
- 适配LangGraph 1.0.5:仅使用基础功能,读者可直接复现。
- 可扩展性强:新增节点只需在路由内添加调用逻辑,无须修改边关系。


196

被折叠的 条评论
为什么被折叠?



