🌈 我是“没事学AI”, 欢迎咨询、交流,共同学习:
👁️ 【关注】我们一起挖 AI 的各种门道,看看它还有多少新奇玩法等着咱们发现
👍 【点赞】为这些有用的 AI 知识鼓鼓掌,让更多人知道学 AI 也能这么轻松
🔖 【收藏】把这些 AI 小技巧存起来,啥时候想练手了,翻出来就能用
💬 【评论】说说你学 AI 时的想法和疑问,让大家的思路碰出更多火花
👉 关注获取更多AI技术干货,点赞/收藏备用,欢迎评论区交流学习心得! 🚀
一、LangGraph技术基础与底层原理
1.1 LangGraph核心定位
LangGraph是基于大语言模型(LLM)的工作流编排框架,隶属于LangChain生态,核心定位是解决传统LLM应用中“线性流程无法处理复杂决策逻辑”的问题。其通过有向图(Directed Graph) 结构建模Agent的决策流程,支持循环、分支、条件判断等复杂逻辑,使LLM应用能像“自主思考”一样根据不同输入动态调整执行路径,在智能Agent、多步骤任务自动化、复杂问答系统等场景中具有不可替代的作用。
在整个LLM技术体系中,LangGraph处于“流程控制层”,上接LLM模型(如GPT、Claude)与工具调用模块(如函数调用、数据库查询),下接具体业务场景(如客服、数据分析),负责将离散的技术组件串联成具备动态决策能力的完整应用。
1.2 底层核心原理
LangGraph的底层原理围绕“图结构建模”与“状态管理”展开,核心包含三大组件:
- 节点(Node):流程中的最小执行单元,可对应LLM调用、工具调用、数据处理函数等。例如“调用LLM生成回答”“查询RAG知识库”“判断用户问题是否需要转人工”均为独立节点。
- 边(Edge):定义节点间的流转规则,支持固定流转、条件流转两种模式。固定流转指“节点A执行完成后固定进入节点B”,条件流转指“根据节点A的输出结果判断进入节点B或节点C”。
- 状态(State):贯穿整个工作流的数据载体,存储流程中产生的所有信息(如用户输入、LLM输出、工具返回结果),节点执行时可读取/修改状态,边的流转判断也基于状态内容。
1.3 基础案例:构建简单问答决策流
1.3.1 案例场景
实现一个“用户问答决策流”:接收用户问题后,先判断问题是否属于“技术问题”,若是则调用LLM生成技术回答;若否则返回“非技术问题无法解答”的提示。
1.3.2 环境准备
- 安装依赖包:
# 创建并激活虚拟环境(可选但推荐)
python -m venv langgraph-env
source langgraph-env/bin/activate # macOS Intel架构激活命令
# 安装LangGraph与LangChain核心依赖
pip install langgraph langchain-openai python-dotenv
- 配置环境变量:在项目根目录创建
.env
文件,填入OpenAI API密钥(用于LLM调用):
OPENAI_API_KEY=your-api-key-here
1.3.3 完整实现代码
from dotenv import load_dotenv
from langgraph.graph import Graph, StateGraph
from langgraph.state import State
from langchain_openai import ChatOpenAI
from typing import Literal
# 1. 加载环境变量
load_dotenv()
# 2. 定义状态结构:存储用户问题、判断结果、最终回答
class QAState(State):
user_question: str # 用户输入的问题
is_tech_question: bool | None = None # 是否为技术问题的判断结果
final_answer: str | None = None # 最终返回给用户的回答
# 3. 定义节点函数
def judge_question_type(state: QAState) -> QAState:
"""节点1:判断用户问题是否为技术问题"""
# 调用LLM进行分类判断
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
prompt = f"""
请判断以下用户问题是否属于技术问题(如编程、软件使用、算法等),仅返回"是"或"否":
用户问题:{state.user_question}
"""
response = llm.invoke(prompt).content.strip()
# 更新状态中的判断结果
state.is_tech_question = (response == "是")
return state
def generate_tech_answer(state: QAState) -> QAState:
"""节点2:生成技术问题的回答"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
prompt = f"""
请详细解答以下技术问题:
用户问题:{state.user_question}
要求:回答需技术细节明确,适合有基础的开发者理解。
"""
state.final_answer = llm.invoke(prompt).content
return state
def generate_non_tech_answer(state: QAState) -> QAState:
"""节点3:生成非技术问题的提示"""
state.final_answer = "抱歉,目前仅支持解答技术类问题(如编程、软件使用、算法等),请更换问题类型后重试。"
return state
# 4. 定义边的流转规则
def route_based_on_question_type(state: QAState) -> Literal["generate_tech_answer", "generate_non_tech_answer"]:
"""根据问题类型判断流转方向"""
if state.is_tech_question:
return "generate_tech_answer"
else:
return "generate_non_tech_answer"
# 5. 构建并编译LangGraph
def build_qa_graph() -> Graph:
# 初始化状态图,指定状态类型
graph_builder = StateGraph(QAState)
# 添加节点
graph_builder.add_node("judge_question_type", judge_question_type) # 节点1:判断问题类型
graph_builder.add_node("generate_tech_answer", generate_tech_answer) # 节点2:生成技术回答
graph_builder.add_node("generate_non_tech_answer", generate_non_tech_answer) # 节点3:生成非技术提示
# 设置入口节点(流程起始点)
graph_builder.set_entry_point("judge_question_type")
# 添加边:节点1 -> 分支判断 -> 节点2或节点3
graph_builder.add_conditional_edges(
source="judge_question_type",
condition=route_based_on_question_type # 流转规则函数
)
# 添加边:节点2和节点3 -> 流程结束(exit为LangGraph内置的结束标识)
graph_builder.add_edge("generate_tech_answer", "exit")
graph_builder.add_edge("generate_non_tech_answer", "exit")
# 编译图(生成可执行的Graph对象)
return graph_builder.compile()
# 6. 运行测试
if __name__ == "__main__":
# 构建图
qa_graph = build_qa_graph()
# 测试1:技术问题
tech_input = QAState(user_question="如何用Python实现LangGraph节点间的状态传递?")
tech_result = qa_graph.invoke(tech_input)
print("=== 技术问题测试结果 ===")
print(f"用户问题:{tech_result.user_question}")
print(f"是否技术问题:{tech_result.is_tech_question}")
print(f"最终回答:{tech_result.final_answer}\n")
# 测试2:非技术问题
non_tech_input = QAState(user_question="今天天气怎么样?")
non_tech_result = qa_graph.invoke(non_tech_input)
print("=== 非技术问题测试结果 ===")
print(f"用户问题:{non_tech_result.user_question}")
print(f"是否技术问题:{non_tech_result.is_tech_question}")
print(f"最终回答:{non_tech_result.final_answer}")
1.3.4 代码说明与运行结果
- 状态定义:通过
QAState
类明确流程中需要传递的数据,确保节点间数据交互可追溯。 - 节点逻辑:
judge_question_type
负责分类,generate_tech_answer
/generate_non_tech_answer
负责生成对应内容,职责拆分清晰。 - 流转控制:
route_based_on_question_type
函数通过状态中的is_tech_question
字段动态选择下一个节点,体现LangGraph的核心决策能力。
运行代码后,技术问题会得到LLM生成的详细解答,非技术问题会返回固定提示,符合预期流程。
二、LangGraph构建Agent工作流应用
2.1 Agent工作流中LangGraph的定位
在Agent技术体系中,LangGraph解决了“传统Agent流程固化”的痛点——传统Agent常采用“线性调用工具→生成结果”的模式,无法应对“工具调用失败需重试”“多工具协同(如先查文档再算数据)”“根据中间结果调整策略”等复杂场景。
LangGraph为Agent提供动态流程编排能力,将Agent的“感知(接收输入)→决策(选择工具/动作)→执行(调用工具/生成内容)→反馈(处理工具结果)”全流程建模为图结构,使Agent具备“自主纠错”“多步骤规划”的能力,是构建生产级Agent的核心框架。
2.2 核心模块:Agent工作流的四大核心节点
一个完整的LangGraph Agent工作流通常包含以下节点,节点间通过条件流转形成闭环:
- 输入解析节点:解析用户需求,提取关键信息(如任务目标、约束条件),为后续决策提供基础。
- 工具选择节点:根据输入解析结果,判断是否需要调用工具(如无需调用则直接生成回答),若需调用则选择适配的工具(如代码执行器、数据库查询工具)。
- 工具执行节点:调用选定的工具,获取工具返回结果,并处理异常(如工具调用超时、返回格式错误)。
- 结果生成节点:整合工具返回结果(若有)与LLM逻辑,生成最终回答;若工具结果不完整,可触发“重新选择工具”的循环。
2.3 案例:构建“数据分析Agent”工作流
2.3.1 案例场景
实现一个能自主完成“用户数据分析需求→选择工具(Python代码执行器)→执行数据分析→生成可视化结果与结论”的Agent,若代码执行失败(如语法错误),可自动重试修正。
2.3.2 环境准备
在第一章环境基础上,额外安装代码执行工具依赖:
# 安装代码执行器(支持运行Python代码并返回结果)
pip install langchain-experimental matplotlib pandas
2.3.3 完整实现代码
from dotenv import load_dotenv
from langgraph.graph import StateGraph, Graph
from langgraph.state import State
from langchain_openai import ChatOpenAI
from langchain_experimental.utilities import PythonREPL
from typing import Literal
import matplotlib.pyplot as plt
import pandas as pd
import os
# 1. 加载环境变量
load_dotenv()
# 2. 定义Agent状态:存储完整流程数据
class DataAnalysisState(State):
user需求: str # 用户输入的数据分析需求
解析后需求: str | None = None # 输入解析节点的输出
工具选择结果: Literal["python_repl", "direct_answer"] | None = None # 工具选择结果
生成的代码: str | None = None # 生成的数据分析代码
代码执行结果: str | None = None # 代码执行后的输出(含错误信息)
代码执行成功: bool | None = None # 代码是否执行成功
最终分析结论: str | None = None # 最终返回给用户的结论
# 3. 初始化工具(Python代码执行器)
python_repl = PythonREPL() # 基于langchain-experimental的Python代码执行工具
# 4. 定义节点函数
def parse_user需求(state: DataAnalysisState) -> DataAnalysisState:
"""节点1:解析用户需求,明确数据分析目标与所需步骤"""
llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = f"""
请解析以下用户数据分析需求,输出:
1. 核心分析目标(如计算平均值、绘制趋势图)
2. 所需数据处理步骤(如生成模拟数据、筛选特定字段)
3. 预期输出形式(如数值结果、图表)
用户需求:{state.user需求}
"""
state.解析后需求 = llm.invoke(prompt).content
return state
def select_tool(state: DataAnalysisState) -> DataAnalysisState:
"""节点2:根据解析后的需求选择工具(是否需要Python代码执行)"""
llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = f"""
基于以下解析后的需求,判断是否需要调用Python代码执行器(用于数据分析、绘图):
1. 若需求包含"计算""统计""绘图""分析"等需代码执行的操作,选择工具"python_repl"
2. 若需求仅需文字解释(无数据处理),选择工具"direct_answer"
解析后的需求:{state.解析后需求}
仅返回"python_repl"或"direct_answer"
"""
state.工具选择结果 = llm.invoke(prompt).content.strip()
return state
def generate_analysis_code(state: DataAnalysisState) -> DataAnalysisState:
"""节点3:生成Python数据分析代码(工具为python_repl时执行)"""
llm = ChatOpenAI(model="gpt-4", temperature=0.3)
# 确保生成的代码能在PythonREPL中执行,且图表保存到本地(macOS路径)
prompt = f"""
基于以下解析后的需求,生成可直接执行的Python代码:
1. 若需生成数据,用pandas创建模拟数据;
2. 若需绘图,用matplotlib绘制,图表保存路径为"/tmp/analysis_plot.png"(macOS可访问路径);
3. 代码最后打印关键分析结果;
4. 无需导入已提及的库(pandas、matplotlib.pyplot已导入)。
解析后的需求:{state.解析后需求}
生成的代码需严格符合Python语法,避免语法错误。
"""
state.生成的代码 = llm.invoke(prompt).content
# 清理代码格式(去除可能的markdown代码块标识)
state.生成的代码 = state.生成的代码.replace("```python", "").replace("```", "").strip()
return state
def execute_code(state: DataAnalysisState) -> DataAnalysisState:
"""节点4:执行Python代码,处理执行结果与异常"""
try:
# 执行代码,获取输出
execution_result = python_repl.run(state.生成的代码)
state.代码执行结果 = f"代码执行成功,输出:\n{execution_result}"
state.代码执行成功 = True
except Exception as e:
# 捕获执行错误(如语法错误、运行时错误)
state.代码执行结果 = f"代码执行失败,错误信息:\n{str(e)}"
state.代码执行成功 = False
return state
def correct_code(state: DataAnalysisState) -> DataAnalysisState:
"""节点5:修正执行失败的代码(代码执行失败时触发)"""
llm = ChatOpenAI(model="gpt-4", temperature=0.3)
prompt = f"""
以下Python代码执行失败,错误信息如下,请修正代码:
1. 优先修复语法错误、库调用错误;
2. 确保修正后的代码可直接执行,图表仍保存到"/tmp/analysis_plot.png";
3. 仅返回修正后的代码(无需解释)。
原代码:
{state.生成的代码}
错误信息:
{state.代码执行结果.split("错误信息:")[-1].strip()}
"""
corrected_code = llm.invoke(prompt).content.strip()
state.生成的代码 = corrected_code.replace("```python", "").replace("```", "").strip()
return state
def generate_direct_answer(state: DataAnalysisState) -> DataAnalysisState:
"""节点6:直接生成文字回答(工具为direct_answer时执行)"""
llm = ChatOpenAI(model="gpt-4", temperature=0.7)
prompt = f"""
基于以下解析后的需求,生成文字解释类回答:
1. 回答需逻辑清晰,覆盖需求中的所有要点;
2. 无需涉及代码执行,仅用自然语言解释。
解析后的需求:{state.解析后需求}
"""
state.最终分析结论 = llm.invoke(prompt).content
return state
def generate_final_conclusion(state: DataAnalysisState) -> DataAnalysisState:
"""节点7:整合代码执行结果,生成最终分析结论"""
# 检查是否生成了图表
plot_path = "/tmp/analysis_plot.png"
plot_exists = os.path.exists(plot_path)
llm = ChatOpenAI(model="gpt-4", temperature=0.7)
prompt = f"""
基于以下信息,生成数据分析的最终结论:
1. 先简述用户需求与分析步骤;
2. 再呈现代码执行结果中的关键数据;
3. 若存在图表,说明图表反映的趋势或规律;
4. 最后总结分析结论。
用户需求:{state.user需求}
代码执行结果:{state.代码执行结果}
是否生成图表:{"是" if plot_exists else "否"}
"""
state.最终分析结论 = llm.invoke(prompt).content
return state
# 5. 定义边的流转规则
def route_after_tool_selection(state: DataAnalysisState) -> Literal["generate_analysis_code", "generate_direct_answer"]:
"""根据工具选择结果,决定进入代码生成节点或直接回答节点"""
return state.工具选择结果
def route_after_code_execution(state: DataAnalysisState) -> Literal["correct_code", "generate_final_conclusion"]:
"""根据代码执行结果,决定进入代码修正节点或最终结论生成节点"""
if state.代码执行成功:
return "generate_final_conclusion"
else:
return "correct_code"
# 6. 构建并编译LangGraph
def build_data_analysis_agent() -> Graph:
# 初始化状态图
graph_builder = StateGraph(DataAnalysisState)
# 添加节点
graph_builder.add_node("parse_user需求", parse_user需求) # 节点1:解析用户需求
graph_builder.add_node("select_tool", select_tool) # 节点2:选择工具
graph_builder.add_node("generate_analysis_code", generate_analysis_code) # 节点3:生成分析代码
graph_builder.add_node("execute_code", execute_code) # 节点4:执行代码
graph_builder.add_node("correct_code", correct_code) # 节点5:修正代码
graph_builder.add_node("generate_direct_answer", generate_direct_answer) # 节点6:直接生成回答
graph_builder.add_node("generate_final_conclusion", generate_final_conclusion) # 节点7:生成最终结论
# 设置入口节点
graph_builder.set_entry_point("parse_user需求")
# 定义节点间流转关系
graph_builder.add_edge("parse_user需求", "select_tool") # 解析需求后进入工具选择
# 工具选择后分支:代码生成或直接回答
graph_builder.add_conditional_edges(
source="select_tool",
condition=route_after_tool_selection
)
# 代码生成→执行代码→分支(修正或生成结论)
graph_builder.add_edge("generate_analysis_code", "execute_code")
graph_builder.add_conditional_edges(
source="execute_code",
condition=route_after_code_execution
)
# 代码修正后重新执行
graph_builder.add_edge("correct_code", "execute_code")
# 直接回答/最终结论→流程结束
graph_builder.add_edge("generate_direct_answer", "exit")
graph_builder.add_edge("generate_final_conclusion", "exit")
# 编译图
return graph_builder.compile()
# 7. 运行测试
if __name__ == "__main__":
# 构建Agent工作流
agent = build_data_analysis_agent()
# 测试:用户需求为“生成100个随机数,计算平均值并绘制直方图”
user_input = DataAnalysisState(
user需求="生成100个0-100的随机整数,计算它们的平均值,并用直方图展示分布情况,总结数据特征"
)
# 执行工作流
result = agent.invoke(user_input)
# 打印关键结果
print("=== 数据分析Agent执行结果 ===")
print(f"用户需求:{result.user需求}")
print(f"工具选择:{result.工具选择结果}")
print(f"生成的代码:\n{result.生成的代码}\n")
print(f"代码执行结果:{result.代码执行结果}\n")
print(f"最终分析结论:\n{result.最终分析结论}")
2.3.4 代码说明与运行结果
- 闭环纠错机制:通过
execute_code
→correct_code
→execute_code
的循环,实现代码执行失败后的自动修正,解决了传统Agent“一次调用失败即终止”的问题。 - 工具协同逻辑:根据需求动态选择“代码执行器”或“直接回答”,体现了LangGraph对多工具场景的适配能力。
- 状态流转可视化:运行时可通过
agent.get_graph().draw_mermaid()
生成流程图(需安装mermaid
库),直观展示节点间的条件跳转关系。
运行代码后,Agent会生成符合要求的Python代码(含随机数生成、平均值计算、直方图绘制),若代码存在语法错误(如绘图路径错误),会自动修正并重新执行,最终输出包含数据特征总结的分析结论,同时在/tmp/analysis_plot.png
路径生成可视化图表。
三、LangGraph基于RAG构建智能客服应用
3.1 RAG与LangGraph的结合价值
RAG(检索增强生成)技术通过“检索知识库→增强LLM回答”的模式,解决了LLM“知识时效性不足”“易产生幻觉”的问题,但传统RAG流程(单轮检索→生成)无法处理“多轮对话上下文关联”“复杂问题需多轮检索”等场景。
LangGraph为RAG注入动态流程控制能力,可实现:
- 多轮对话中基于上下文动态调整检索策略(如根据用户追问细化检索关键词);
- 当检索结果不匹配时,自动触发“扩大检索范围”或“询问用户澄清信息”的分支;
- 整合多轮检索结果,生成连贯的最终回答。
在智能客服场景中,这种结合能使系统更精准地理解用户问题(如“我的订单为何未发货”需关联用户历史订单信息),并基于知识库(如售后政策、物流规则)提供一致的回答。
3.2 核心模块:智能客服RAG工作流的关键节点
智能客服RAG工作流包含以下核心节点,通过状态传递实现多轮交互:
- 对话上下文管理节点:存储用户与客服的历史对话,为多轮理解提供上下文。
- 问题解析与检索词生成节点:基于当前问题与上下文,生成检索关键词(如从“查一下那个订单”中提取订单号)。
- 知识库检索节点:调用向量数据库(如Chroma)检索相关文档片段。
- 检索结果评估节点:判断检索结果是否匹配问题(如“无相关结果”“部分匹配”“完全匹配”)。
- 回答生成节点:基于检索结果生成回答;若结果不匹配,生成“澄清问题”的引导语。
3.3 案例:电商智能客服RAG系统
3.3.1 案例场景
构建一个电商智能客服,支持:
- 多轮对话(如用户先问“退货政策”,再追问“拆封后能退吗”);
- 复杂问题拆解(如“我的订单12345没收到,能重新发吗”需先检索订单状态,再查询补发政策);
- 检索结果不足时主动澄清(如“请提供您的订单号,以便查询物流信息”)。
3.3.2 环境准备
在之前环境基础上,安装向量数据库与RAG依赖:
# 安装向量数据库(Chroma)与嵌入模型
pip install chromadb langchain-huggingface sentence-transformers
3.3.3 完整实现代码
from dotenv import load_dotenv
from langgraph.graph import StateGraph, Graph
from langgraph.state import State
from langchain_openai import ChatOpenAI
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from typing import Literal, List, Optional
import os
# 1. 加载环境变量
load_dotenv()
# 2. 初始化知识库(电商客服知识库示例)
def init_knowledge_base():
"""初始化Chroma向量数据库,加载电商客服知识库文档"""
# 示例知识库内容(实际应用中可替换为从文件加载)
docs = [
"退货政策:未拆封商品支持7天无理由退货,拆封后仅质量问题可退,需提供质检报告。",
"物流查询:订单发货后可在APP「我的订单」页面查看物流信息,或联系在线客服提供订单号查询。",
"订单补发:若商品确认丢失,客服核实后24小时内安排补发,运费由商家承担。",
"优惠券使用:优惠券需在下单时手动选择,逾期未使用自动失效,不可折现。"
]
# 分割文档为片段
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20)
splits = text_splitter.split_text("\n".join(docs))
# 初始化嵌入模型(使用开源模型,适配macOS Intel)
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
# 创建向量数据库(存储在本地目录)
persist_directory = "./chroma_db"
vectordb = Chroma.from_texts(
texts=splits,
embedding=embeddings,
persist_directory=persist_directory
)
vectordb.persist() # 持久化到磁盘
return vectordb
# 3. 定义客服对话状态
class SupportState(State):
用户问题: str # 当前用户问题
对话历史: List[str] = [] # 历史对话列表(格式:["用户:xxx", "客服:xxx"])
检索关键词: str | None = None # 生成的检索关键词
检索结果: List[str] = [] # 知识库检索结果
检索相关性: Literal["高", "中", "低"] | None = None # 检索结果相关性
客服回答: str | None = None # 生成的客服回答
是否需要多轮: bool = False # 是否需要继续对话
# 4. 定义节点函数
def manage_context(state: SupportState) -> SupportState:
"""节点1:管理对话上下文,更新历史记录"""
# 将当前用户问题添加到对话历史
state.对话历史.append(f"用户:{state.用户问题}")
return state
def generate_search_terms(state: SupportState) -> SupportState:
"""节点2:基于上下文生成检索关键词"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 整合历史对话生成检索词(如提取订单号、问题核心)
context = "\n".join(state.对话历史)
prompt = f"""
基于以下对话历史,提取当前用户问题的检索关键词(如商品问题、订单号、政策名称):
1. 若涉及订单,优先提取订单号(如12345);
2. 若为政策咨询,提取核心主题(如退货、物流);
3. 输出1-3个关键词,用逗号分隔。
对话历史:{context}
"""
state.检索关键词 = llm.invoke(prompt).content.strip()
return state
def retrieve_from_kb(state: SupportState) -> SupportState:
"""节点3:从知识库检索相关文档"""
# 加载向量数据库
vectordb = init_knowledge_base()
# 执行检索(返回最相关的2个结果)
docs = vectordb.similarity_search(state.检索关键词, k=2)
state.检索结果 = [doc.page_content for doc in docs]
return state
def evaluate_retrieval(state: SupportState) -> SupportState:
"""节点4:评估检索结果相关性"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
prompt = f"""
判断以下检索结果与用户问题的相关性,仅返回"高""中""低":
- 高:结果直接回答问题核心;
- 中:结果部分相关,需结合推理;
- 低:结果无关或未包含关键信息。
用户问题:{state.用户问题}
检索结果:{state.检索结果}
"""
state.检索相关性 = llm.invoke(prompt).content.strip()
return state
def generate_answer(state: SupportState) -> SupportState:
"""节点5:生成客服回答(检索结果相关时)"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
context = "\n".join(state.对话历史)
knowledge = "\n".join(state.检索结果)
prompt = f"""
基于以下知识库内容和对话历史,生成客服回答:
1. 回答需准确引用知识库信息,不编造内容;
2. 语气友好,符合电商客服风格;
3. 若为多轮对话,需关联历史内容。
对话历史:{context}
知识库内容:{knowledge}
"""
answer = llm.invoke(prompt).content
state.客服回答 = answer
state.对话历史.append(f"客服:{answer}")
# 判断是否需要继续对话(如回答中包含引导用户追问的内容)
state.是否需要多轮 = ("还有其他问题" in answer) or ("请告诉我" in answer)
return state
def generate_clarification(state: SupportState) -> SupportState:
"""节点6:生成澄清问题(检索结果无关时)"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
prompt = f"""
由于检索结果不足,生成引导用户提供更多信息的问句,例如:
- 若涉及订单问题:"请提供您的订单号,以便我查询详细信息";
- 若政策咨询不明确:"请问您想了解的是哪种商品的退货政策呢?"
用户当前问题:{state.用户问题}
仅返回问句,无需额外内容。
"""
clarification = llm.invoke(prompt).content
state.客服回答 = clarification
state.对话历史.append(f"客服:{clarification}")
state.是否需要多轮 = True # 需用户进一步输入,继续对话
return state
# 5. 定义边的流转规则
def route_after_retrieval(state: SupportState) -> Literal["generate_answer", "generate_clarification"]:
"""根据检索相关性,决定生成回答或澄清问题"""
if state.检索相关性 in ["高", "中"]:
return "generate_answer"
else:
return "generate_clarification"
def route_after_answer(state: SupportState) -> Literal["exit", "wait_for_user"]:
"""根据是否需要多轮对话,决定结束或等待用户输入"""
if state.是否需要多轮:
return "wait_for_user" # 实际应用中可在此处等待用户新输入
else:
return "exit"
# 6. 构建并编译LangGraph
def build_support_chatbot() -> Graph:
graph_builder = StateGraph(SupportState)
# 添加节点
graph_builder.add_node("manage_context", manage_context) # 上下文管理
graph_builder.add_node("generate_search_terms", generate_search_terms) # 生成检索词
graph_builder.add_node("retrieve_from_kb", retrieve_from_kb) # 知识库检索
graph_builder.add_node("evaluate_retrieval", evaluate_retrieval) # 评估检索结果
graph_builder.add_node("generate_answer", generate_answer) # 生成回答
graph_builder.add_node("generate_clarification", generate_clarification) # 生成澄清
# 设置入口节点
graph_builder.set_entry_point("manage_context")
# 定义流转关系
graph_builder.add_edge("manage_context", "generate_search_terms")
graph_builder.add_edge("generate_search_terms", "retrieve_from_kb")
graph_builder.add_edge("retrieve_from_kb", "evaluate_retrieval")
# 检索评估后分支:生成回答或澄清
graph_builder.add_conditional_edges(
source="evaluate_retrieval",
condition=route_after_retrieval
)
# 回答/澄清后分支:结束或继续
graph_builder.add_conditional_edges(
source="generate_answer",
condition=route_after_answer
)
graph_builder.add_conditional_edges(
source="generate_clarification",
condition=route_after_answer
)
# 编译图
return graph_builder.compile()
# 7. 运行测试(模拟多轮对话)
if __name__ == "__main__":
chatbot = build_support_chatbot()
# 第一轮对话:用户问退货政策
round1_input = SupportState(用户问题="你们的退货政策是什么?")
round1_result = chatbot.invoke(round1_input)
print("=== 第一轮对话结果 ===
用户问题:{round1_result.用户问题}
客服回答:{round1_result.客服回答}\n")
# 第二轮对话:用户追问拆封后是否可退(基于第一轮上下文)
round2_input = SupportState(
用户问题="那拆封后还能退吗?",
对话历史=round1_result.对话历史 # 传递历史对话
)
round2_result = chatbot.invoke(round2_input)
print("\n=== 第二轮对话结果 ===")
print(f"用户问题:{round2_result.用户问题}")
print(f"客服回答:{round2_result.客服回答}\n")
# 第三轮对话:用户问订单相关问题(检索结果不足时的澄清)
round3_input = SupportState(
用户问题="我的订单没收到,能重新发吗?",
对话历史=round2_result.对话历史
)
round3_result = chatbot.invoke(round3_input)
print("\n=== 第三轮对话结果 ===")
print(f"用户问题:{round3_result.用户问题}")
print(f"客服回答:{round3_result.客服回答}")
3.3.4 代码说明与运行结果
- 多轮上下文关联:通过
对话历史
字段在轮次间传递上下文,使客服能理解用户的追问(如“拆封后还能退吗”关联上一轮的“退货政策”)。 - 动态检索优化:
generate_search_terms
节点会基于历史对话生成精准检索词(如第一轮提取“退货政策”,第二轮提取“拆封,退货”),提升检索相关性。 - 检索评估机制:当用户问“订单没收到”时,因未提供订单号,检索结果相关性被判定为“低”,触发
generate_clarification
节点生成澄清问句(如“请提供您的订单号,以便我查询物流信息并核实是否需要补发”)。
运行代码后,三轮对话会依次输出符合逻辑的客服回答:第一轮解释退货政策,第二轮明确拆封后仅质量问题可退,第三轮引导用户提供订单号,完整模拟了电商客服的真实交互流程。