文章目录
-
- Langchain实战案例:构建一个聊天机器人
-
- [1. 核心架构设计](#1. 核心架构设计)
- [2. 基础实现步骤](#2. 基础实现步骤)
- [3. 进阶与生产级优化](#3. 进阶与生产级优化)
- 基础版:带记忆的基础聊天机器人
- [扩展版:带有 RAG 知识库检索的高级聊天机器人](#扩展版:带有 RAG 知识库检索的高级聊天机器人)
- 进阶版:LangGraph构建生产级别的聊天机器人
-
- [1. 核心架构设计:状态与节点](#1. 核心架构设计:状态与节点)
- [2. 工作流编排:边与条件路由](#2. 工作流编排:边与条件路由)
- [3. 生产级必备:状态持久化与记忆](#3. 生产级必备:状态持久化与记忆)
- [4. 人工介入(Human-in-the-Loop)](#4. 人工介入(Human-in-the-Loop))
- [5. 生产环境的工程化保障](#5. 生产环境的工程化保障)
- 进阶版:案例一:LangGraph构建带条件路由的多智能体客服系统
- 进阶版:案例二:LangGraph构建带人工审批的自动化工作流
- 其他技术:流式输出(Streaming)
-
- [1. 核心流式模式选择](#1. 核心流式模式选择)
- [2. 实战代码:打字机效果 + 节点进度提示](#2. 实战代码:打字机效果 + 节点进度提示)
- [3. 前端对接与生产级优化建议](#3. 前端对接与生产级优化建议)
- [其他技术:执行回退(Time Travel)](#其他技术:执行回退(Time Travel))
- [其他技术:LangSmith 监控](#其他技术:LangSmith 监控)
-
- [第一步:获取 LangSmith API Key](#第一步:获取 LangSmith API Key)
- 第二步:配置环境变量
- [第三步:在云端可视化追踪你的 Agent](#第三步:在云端可视化追踪你的 Agent)
- [💡 国内开发者注意事项](#💡 国内开发者注意事项)
Langchain实战案例:构建一个聊天机器人
使用 LangChain 构建聊天机器人,核心在于将我们之前学过的大模型(LLM) 、提示词模板(Prompt) 、**记忆组件(Memory)以及检索增强(RAG)**等组件进行编排。
根据 LangChain 官方教程,构建一个具备上下文理解能力的聊天机器人,通常遵循以下核心架构和步骤:
1. 核心架构设计
一个标准的 LangChain 聊天机器人主要由以下几个模块协同工作:
- 消息与提示词(Messages & Prompts):聊天机器人接收的是带有角色属性(如 System, Human, AI)的消息,而非纯文本。提示词模板负责将这些消息格式化。
- 记忆系统(Memory):用于管理短期记忆(如当前对话的上下文窗口)和长期记忆(如基于向量数据库的历史检索),使机器人具备"记住"前文的能力。
- 检索增强(RAG / Retrieval):如果机器人需要回答特定领域的问题,需要为其添加检索功能,使其能先从外部知识库中查找资料,再生成回答。
- 工具与代理(Tools & Agents):赋予聊天机器人执行外部操作(如联网搜索、调用 API)的能力,使其从"被动问答"变为"主动解决问题"。
2. 基础实现步骤
结合 LangChain 的官方指南,构建一个聊天机器人的标准流程如下:
- 初始化聊天模型:选择支持对话交互的 ChatModel(如 OpenAI、通义千问等),并设定好系统提示词(System Prompt)来定义机器人的性格和行为边界。
- 挂载记忆组件 :在构建对话链(Chain)时,接入
ConversationBufferMemory或滑动窗口记忆等组件。LangChain 会自动将历史消息注入到当前的 Prompt 中。 - 处理长对话历史:当对话轮数过多导致超出模型上下文限制时,可以通过"上下文压缩(Contextual Compression)"或摘要机制,提炼关键信息,防止 Token 溢出。
- 接入外部知识(可选):如果是一个企业客服或文档问答机器人,可以将向量数据库作为检索器(Retriever)挂载到聊天机器人上,实现基于私有数据的精准问答。
3. 进阶与生产级优化
如果你希望构建一个生产级别的聊天机器人,还可以进一步探索:
- 使用 LangGraph 进行高级编排:LangGraph 提供了更强大的状态机机制,适合构建包含复杂条件分支、循环和人工介入(Human-in-the-loop)的高级聊天机器人。
- 流式输出(Streaming):通过 LangChain 的流式传输功能,实现打字机效果,大幅提升用户的交互体验。
- 监控与调试:接入 LangSmith 平台,对聊天机器人的每一轮对话、Token 消耗、检索命中率进行追踪、评估和调试。
如果你已经准备好了,我们可以直接开始写代码。你是想先实现一个带记忆的基础聊天机器人 ,还是想直接挑战带有 RAG 知识库检索的高级聊天机器人?
基础版:带记忆的基础聊天机器人
在 LangChain 当前主推的 LCEL(LangChain Expression Language)体系中,构建带记忆的聊天机器人比早期更加透明和灵活。在新的 Runnable 体系中,Memory 不再是 Chain 的"隐藏参数",而是显式的状态管理。
下面我将为你提供一份完整、可运行的代码,演示如何使用 LCEL 构建一个基础版聊天机器人:
核心代码实现
python
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 1. 初始化大模型与输出解析器
# 确保你已经配置了环境变量 OPENAI_API_KEY
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
parser = StrOutputParser()
# 2. 定义 Prompt 模板(关键:必须显式包含 {history} 变量)
prompt = PromptTemplate(
input_variables=["history", "question"],
template="""你是一个友好且聪明的AI助手。
以下是之前的对话历史:
{history}
当前用户问题:{question}
请基于上下文给出连贯、准确的回答。"""
)
# 3. 使用 LCEL 组合基础链 (Prompt | LLM | Parser)
chain = prompt | llm | parser
# 4. 定义对话历史存储机制
store = {}
def get_session_history(session_id: str):
"""根据 session_id 获取或创建聊天历史"""
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 5. 构建带记忆的 Runnable
chat_chain = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="question", # 对应 prompt 中的用户输入变量
history_messages_key="history", # 对应 prompt 中的历史变量
)
# 6. 测试多轮对话
config = {"configurable": {"session_id": "test-session-001"}}
print("--- 第一轮对话 ---")
response1 = chat_chain.invoke({"question": "你好,我叫小明。"}, config=config)
print("AI:", response1)
print("\n--- 第二轮对话 ---")
response2 = chat_chain.invoke({"question": "你还记得我叫什么名字吗?"}, config=config)
print("AI:", response2)
核心逻辑解析
- LCEL 表达式写法 :通过
prompt | llm | parser这种管道符写法,将提示词、模型和输出解析器串联起来。这比早期的LLMChain更透明、也更可组合。 - 显式状态管理 :历史是否生效,取决于 Prompt 模板中是否显式使用了
{history}占位符。这是很多人第一次使用RunnableWithMessageHistory时最容易忽略的一点。 - Session 隔离 :通过传入
session_id,系统可以支持多个用户的并发对话,且各自的记忆互不干扰。所有数据默认只存在你的本地机器上(内存中)。
💡 进阶演进建议
当前我们使用的是 InMemoryChatMessageHistory,这意味着一旦程序重启,记忆就会丢失。在实际的工程落地中,Memory 的存储是需要可演进的。
当你的应用需要长期保存用户偏好时,你可以将 InMemoryChatMessageHistory 替换为基于 Redis 或本地 SQLite/PostgreSQL 的持久化存储方案(如 Checkpointer),从而实现真正的长期记忆。
基础版跑通后,你的 AI 助手已经具备了"记忆力"。接下来,你想:
- 接入 RAG 知识库:让这个聊天机器人能够查阅你本地的 PDF 或 TXT 文档来回答问题?
- 搭建可视化界面:用 Streamlit 快速给它套上一个类似微信的聊天网页界面?
扩展版:带有 RAG 知识库检索的高级聊天机器人
给聊天机器人接入 RAG(检索增强生成)知识库,是让 AI 从"通用百科"进化为"私人领域专家"的关键一步。
在 LangChain 最新的 LCEL(LangChain Expression Language)体系中,实现这个功能的核心思路是:让大模型在回答前,先去向量数据库中"查资料",并将查到的资料(Context)和用户的历史对话(History)一起打包发给大模型。
下面我为你提供一套完整、可运行的代码,演示如何将多轮对话记忆 与本地文档检索完美结合。
第一步:准备环境依赖
除了基础的 LangChain 和 OpenAI(或你使用的其他模型),你还需要安装文档解析和向量数据库相关的库:
bash
pip install langchain langchain-openai langchain-community pypdf chromadb
第二步:核心代码实现
这段代码将文档加载、向量化、历史记忆和检索问答全部串联在了一起:
python
import os
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
# ================= 1. 构建本地 RAG 知识库 =================
def build_rag_retriever(file_path):
# 根据文件后缀选择加载器
if file_path.endswith(".pdf"):
loader = PyPDFLoader(file_path)
elif file_path.endswith(".txt"):
loader = TextLoader(file_path, encoding="utf-8")
else:
raise ValueError("仅支持 PDF 或 TXT 文件")
documents = loader.load()
# 将长文档切分为小块,保证上下文连贯
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
texts = splitter.split_documents(documents)
# 存入本地 Chroma 向量数据库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(texts, embeddings)
return vectorstore.as_retriever(search_kwargs={"k": 3})
# 替换为你的本地文档路径
retriever = build_rag_retriever("your_document.pdf")
# ================= 2. 构建带记忆与检索的 LCEL 链 =================
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 核心 Prompt:同时包含历史对话和检索到的文档上下文
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的AI助手。请严格根据以下参考文档回答用户问题。如果文档中没有相关信息,请如实告知,不要编造。\n\n参考文档:\n{context}"),
MessagesPlaceholder(variable_name="chat_history"), # 注入历史对话
("human", "{input}")
])
# 使用 LangChain 内置工具,自动将检索结果拼接为字符串
from langchain_core.runnables import RunnablePassthrough
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 组装核心处理链
rag_chain = (
{"context": retriever | format_docs, "input": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# ================= 3. 挂载对话记忆 =================
store = {}
def get_history(session_id):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 最终带记忆的 RAG 链
final_chain = RunnableWithMessageHistory(
rag_chain,
get_history,
input_messages_key="input",
history_messages_key="chat_history",
)
# ================= 4. 开始多轮对话测试 =================
config = {"configurable": {"session_id": "user_001"}}
print("💡 提示:输入 'exit' 退出对话")
while True:
query = input("\n你:")
if query.lower() in ["exit", "q"]:
break
response = final_chain.invoke(query, config=config)
print(f"AI:{response}")
核心逻辑解析
- 检索与生成的解耦 :
{"context": retriever | format_docs, "input": RunnablePassthrough()}这一行是 LCEL 的精髓。它在后台并行处理:把用户问题发给检索器拿回文档,同时把用户原话透传下去。 - 防幻觉机制:在 System Prompt 中明确约束了"严格根据参考文档回答,不要编造",这是 RAG 系统保证回答准确性的底线。
- 记忆与检索的融合 :
MessagesPlaceholder确保了 AI 不仅"懂文档",还"记得你"。比如你可以先问"文档里提到的核心概念是什么?",接着问"你能用通俗的话给我解释一下吗?",AI 能够结合上一轮的上下文和文档内容完美作答。
现在,你的 AI 助手已经具备了"查阅资料"和"记住对话"的双重能力!
接下来,你想如何完善它?
- 搭建可视化界面:用 Streamlit 快速给它套上一个类似微信的网页聊天界面,方便分享和测试?
- 进阶优化:了解如何优化检索质量(比如引入混合检索、重排序 Rerank 机制),让 AI 找资料更准?
进阶版:LangGraph构建生产级别的聊天机器人
从基础的 LCEL 链过渡到 LangGraph,意味着你的 AI 应用从"流水线"进化成了"状态机"。LangGraph 的核心优势在于它能精确控制执行路径、管理持久化状态,并支持条件分支与人工介入(Human-in-the-loop),这正是构建生产级复杂系统的关键。
下面我为你梳理一套构建生产级聊天机器人的核心架构与实战指南:
1. 核心架构设计:状态与节点
在 LangGraph 中,我们不再只是传递字符串,而是维护一个共享的"状态(State)"。
- 定义状态结构 :使用
TypedDict严格声明字段类型。特别是messages字段,必须使用Annotated[list[AnyMessage], operator.add](或内置的add_messages),确保新消息追加到列表而不是覆盖旧消息,防止对话历史丢失。 - 模块化节点(Nodes):将意图识别、RAG 检索、Agent 决策等拆分为独立的函数。每个节点只读取和更新自己关心的状态字段,互不干扰。
2. 工作流编排:边与条件路由
通过图结构(Graph)将节点连接起来,实现复杂的业务逻辑:
- 条件边(Conditional Edges):例如,在"意图分类"节点后,根据用户的意图(如"技术支持"、"账单查询")动态路由到不同的专家 Agent 节点。
- 循环与回退 :当 Agent 的分析结果置信度低于阈值时,可以通过显式的
add_edge构造循环路径,让其回到路由器重新决策,并配合retry_count状态变量防止无限死循环。
3. 生产级必备:状态持久化与记忆
生产环境不能仅依赖内存,必须引入 Checkpointer(检查点机制):
- 会话隔离 :在调用
app.invoke()时传入config={"configurable": {"thread_id": "user-001"}}。相同的thread_id会共享同一段对话历史,实现跨会话的连续性。 - 断点恢复:生产环境中若服务宕机,系统可从数据库(如 PostgresSaver)中读取最近的 checkpoint 恢复执行,无需从头开始。
4. 人工介入(Human-in-the-Loop)
对于敏感操作(如退款审批、发送邮件),AI 不能擅自做主:
- 中断与恢复 :在关键决策节点使用
interrupt()函数。图运行到此处会暂停,并将当前完整状态保存到 Checkpointer 中。 - 等待人类决策 :暂停时长不受限制(几分钟到几天均可)。当人类通过外部 UI 做出"批准"或"拒绝"的决定后,使用
Command(resume="approved")唤醒图,从暂停的位置继续往下走。
5. 生产环境的工程化保障
- 错误处理与降级 :为节点添加装饰器(如
@robust_node_wrapper),捕获异常并在状态中注入错误信息,甚至自动触发转人工审核(requires_human_review)的降级流程。 - 时间旅行(调试利器) :利用
app.get_state_history(thread_id)查看一次运行的完整历史快照。如果对某一步的结果不满意,可以直接回到该步的 checkpoint 修改输入并重新执行,极大降低了复杂 Agent 的调试成本。 - 流式输出 :使用
app.stream()进行单步调试或向前端推送打字机效果,实时观察当前节点名和状态快照,这是排查死循环最有效的手段。
LangGraph 的架构虽然强大,但代码量相对较长。为了让你更直观地理解,我们可以直接上手写代码。你想先看哪个具体场景的实现?
- 带条件路由的多智能体客服系统:演示意图识别 + 动态分发到不同专家 Agent。
- 带人工审批的自动化工作流 :演示
interrupt暂停、等待人类输入并恢复执行的完整闭环。
进阶版:案例一:LangGraph构建带条件路由的多智能体客服系统
构建带条件路由的多智能体客服系统,是 LangGraph 最经典的应用场景之一。它的核心思想是:将复杂的客服任务拆解为多个专职节点(如意图识别、订单专家、物流专家),并通过一个"路由器"根据用户的实时意图,动态决定将任务交给谁处理。
下面为你提供一套完整的实战代码,演示如何实现"意图识别 → 动态路由 → 专家处理"的完整闭环:
核心代码实现
python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage
# ================= 1. 定义共享状态 (State) =================
class CustomerServiceState(TypedDict):
# 使用 add_messages 确保对话历史是追加而不是覆盖
messages: Annotated[list, add_messages]
# 记录识别出的用户意图
intent: str
# ================= 2. 定义各个专职节点 (Nodes) =================
# 意图识别节点
def intent_recognition(state: CustomerServiceState):
# 实际生产中这里会调用 LLM 进行分类
last_msg = state["messages"][-1].content.lower()
if "订单" in last_msg:
intent = "ORDER"
elif "物流" in last_msg or "快递" in last_msg:
intent = "LOGISTICS"
else:
intent = "GENERAL"
return {"intent": intent}
# 订单处理专家节点
def order_agent(state: CustomerServiceState):
response = AIMessage(content="【订单专家】:您好,已为您查询到订单状态,目前正在仓库打包中,预计今天发货。")
return {"messages": [response]}
# 物流处理专家节点
def logistics_agent(state: CustomerServiceState):
response = AIMessage(content="【物流专家】:您好,您的包裹已到达郑州转运中心,预计明天上午为您派送。")
return {"messages": [response]}
# 通用兜底节点
def general_agent(state: CustomerServiceState):
response = AIMessage(content="【通用助手】:您好,请问有什么我可以帮您的吗?")
return {"messages": [response]}
# ================= 3. 定义条件路由逻辑 (Router) =================
def route_to_agent(state: CustomerServiceState):
# 根据状态中的 intent 字段,动态决定下一步走向
if state['intent'] == 'ORDER':
return "order_agent"
elif state['intent'] == 'LOGISTICS':
return "logistics_agent"
else:
return "general_agent"
# ================= 4. 构建并编译工作流图 (Graph) =================
workflow = StateGraph(CustomerServiceState)
# 添加节点
workflow.add_node("intent_recognition", intent_recognition)
workflow.add_node("order_agent", order_agent)
workflow.add_node("logistics_agent", logistics_agent)
workflow.add_node("general_agent", general_agent)
# 设置入口:所有请求先经过意图识别
workflow.add_edge(START, "intent_recognition")
# 设置条件边:根据路由函数的返回值,动态分发到对应的专家节点
workflow.add_conditional_edges(
"intent_recognition",
route_to_agent,
{
"order_agent": "order_agent",
"logistics_agent": "logistics_agent",
"general_agent": "general_agent"
}
)
# 所有专家处理完毕后,结束当前流程
workflow.add_edge("order_agent", END)
workflow.add_edge("logistics_agent", END)
workflow.add_edge("general_agent", END)
# 编译图
app = workflow.compile()
# ================= 5. 运行与测试 =================
test_inputs = [
"帮我查一下我的订单到哪了",
"我的快递怎么还没更新物流信息?",
"你们营业时间是几点到几点?"
]
for user_input in test_inputs:
print(f"👤 用户: {user_input}")
config = {"configurable": {"thread_id": "test-session"}}
result = app.invoke(
{"messages": [HumanMessage(content=user_input)]},
config=config
)
print(f"🤖 AI: {result['messages'][-1].content}\n")
架构亮点解析
- 状态隔离与共享 :
CustomerServiceState是整个图的"中枢神经"。每个节点只修改自己负责的字段(如意图节点只改intent,专家节点只追加messages),互不干扰,极大降低了系统耦合度。 - 动态路由 (
add_conditional_edges) :这是 LangGraph 区别于传统 Chain 的核心。系统不再是一条直线,而是像十字路口,根据实时的intent状态,精准将任务派发给最合适的专家。 - 高度可扩展性 :如果未来需要增加"售后退款专家",你只需新增一个节点函数,并在
route_to_agent中增加一个elif判断,完全不需要修改现有的业务代码。
这套架构已经具备了企业级智能客服的雏形。接下来,你想继续挑战哪个方向?
- 带人工审批的自动化工作流 :当 AI 无法处理时,演示如何使用
interrupt暂停流程,等待人类客服介入并恢复执行。 - 接入真实大模型 :把上面硬编码的
if/else替换为真实的 LLM 意图分类,让路由更聪明。
进阶版:案例二:LangGraph构建带人工审批的自动化工作流
在 LangGraph 中实现"人工介入(Human-in-the-loop)"的核心在于中断(Interrupt)与恢复(Resume)机制。这通常用于处理高风险操作(如退款审批、敏感信息发送)或 AI 遇到无法解决的复杂问题时。
要实现这个闭环,系统需要三个核心要素:
- Checkpointer(检查点):用于在暂停时保存当前状态,以便稍后能"断点续跑"。
- interrupt() 函数:在特定节点主动挂起工作流,并将需要人工审核的数据抛出。
- Command(resume=...):接收人类的决策(如批准或拒绝),并唤醒工作流继续执行。
下面我为你编写一套完整的实战代码,演示当 AI 遇到复杂问题时,如何自动暂停并等待人类客服接管:
核心代码实现
python
import operator
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import HumanMessage, AIMessage
# ================= 1. 定义共享状态 =================
class SupportState(TypedDict):
messages: Annotated[list, add_messages]
resolution: str # 记录最终处理结果
# ================= 2. 定义工作流节点 =================
# AI 自动处理节点
def ai_agent(state: SupportState):
last_msg = state["messages"][-1].content
# 模拟 AI 判断:如果包含"投诉"或"退款",AI 无法处理,需要人工介入
if "投诉" in last_msg or "退款" in last_msg:
return {"messages": [AIMessage(content="检测到复杂售后问题,正在为您转接人工客服...")]}
return {"messages": [AIMessage(content="您好,我是AI助手,已为您解决常规问题。")]}
# 人工审核/介入节点
def human_review(state: SupportState):
# 【核心】调用 interrupt() 暂停流程,并将当前对话抛给前端/人类
# 流程会在这里挂起,直到收到 Command(resume=...)
decision = interrupt({
"prompt": "AI无法处理该问题,请人工客服介入",
"messages": state["messages"]
})
# 恢复执行后,interrupt() 会返回人类的决策
if decision == "approve":
return {"messages": [AIMessage(content="【人工客服】:您好,您的退款申请已批准,款项将在3个工作日内原路返回。")], "resolution": "approved"}
else:
return {"messages": [AIMessage(content="【人工客服】:抱歉,您的退款申请未通过审核。")], "resolution": "rejected"}
# 结束通知节点
def end_node(state: SupportState):
return {"messages": [AIMessage(content="工单已处理完毕,感谢您的反馈。")]}
# ================= 3. 构建并编译工作流 =================
workflow = StateGraph(SupportState)
workflow.add_node("ai_agent", ai_agent)
workflow.add_node("human_review", human_review)
workflow.add_node("end_node", end_node)
workflow.add_edge(START, "ai_agent")
# 条件路由:根据 AI 的回复内容决定是否转人工
def should_transfer_to_human(state: SupportState):
last_msg = state["messages"][-1].content
return "转接人工" in last_msg
workflow.add_conditional_edges(
"ai_agent",
should_transfer_to_human,
{
True: "human_review", # 需要人工介入
False: "end_node" # AI 已解决,直接结束
}
)
workflow.add_edge("human_review", "end_node")
workflow.add_edge("end_node", END)
# 【关键】编译时必须传入 checkpointer,否则无法保存暂停时的状态
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
# ================= 4. 运行与测试 =================
config = {"configurable": {"thread_id": "user-session-001"}}
# 第一阶段:触发 AI 无法处理的场景
print("=== 用户发起复杂请求 ===")
result = app.invoke(
{"messages": [HumanMessage(content="我要投诉!我买的商品坏了,要求立刻退款!")]},
config=config
)
print(f"当前进度: {result['messages'][-1].content}")
# 第二阶段:模拟人类客服查看并做出决策
# 在实际生产中,这里是通过 Web 界面提交表单,然后调用 app.invoke(Command(...))
print("\n=== 人工客服介入并批准退款 ===")
final_result = app.invoke(
Command(resume="approve"), # 传入 "approve" 唤醒 human_review 节点
config=config
)
print(f"最终回复: {final_result['messages'][-1].content}")
架构亮点与生产建议
- 优雅的挂起机制 :
interrupt()并不是让程序死循环等待(这会耗尽服务器资源),而是将当前状态序列化存入 Checkpointer 后直接结束本次请求。当人类做出决策后,系统通过Command(resume=...)重新加载状态并继续执行。 - 状态一致性 :由于使用了
thread_id,无论人类是 1 分钟后审批,还是 3 天后审批,系统都能精准找回当时的对话上下文。 - 生产环境建议 :在真实的业务中,建议将
MemorySaver替换为PostgresSaver或RedisSaver等持久化存储,防止服务器重启导致审批中的工单丢失。同时,审批权限应与业务系统的 RBAC(基于角色的权限控制)打通,确保只有对应权限的客服才能恢复该流程。
至此,你已经掌握了从基础对话、RAG 知识库,到多智能体路由,再到人工介入的完整 LangGraph 生产级架构!
接下来,你想探索哪个方向?
- 流式输出(Streaming):如何配合前端实现"打字机"效果,并实时展示当前正在执行哪个节点?
- 时间旅行(Time Travel):如何利用 LangGraph 的 Checkpoint 历史,回溯到对话的任意一步进行修改和重新执行(超级强大的调试功能)?
其他技术:流式输出(Streaming)
在 LangGraph 中实现流式输出(Streaming),不仅能打造类似 ChatGPT 的"打字机"效果,还能让前端实时感知后台 Agent 的执行进度。这需要通过不同的 stream_mode 来实现。
以下是实现该功能的核心方案与代码示例:
1. 核心流式模式选择
LangGraph 提供了多种流式模式,通常可以组合使用以满足不同的需求:
messages模式:实现 Token 级别的流式输出。它会在 LLM 生成内容的瞬间,将文本片段(chunk)实时推送到前端,非常适合打字机效果。updates模式:在节点(Node)执行结束后触发。它会返回刚刚执行完的节点名称及其产出的状态更新,非常适合用来在前端展示"当前正在执行哪个节点"的进度提示。
2. 实战代码:打字机效果 + 节点进度提示
假设你已经编译好了一个 LangGraph 图 app,可以通过以下代码同时获取文本流和节点状态更新:
python
import asyncio
async def stream_agent():
# 初始化输入和配置
inputs = {"messages": [("user", "帮我查一下订单状态")]}
config = {"configurable": {"thread_id": "session-001"}}
# 同时开启 updates 和 messages 两种流式模式
async for chunk in app.astream(inputs, config, stream_mode=["updates", "messages"]):
# 1. 处理节点状态更新(展示进度)
if isinstance(chunk, dict) and "updates" in str(type(chunk)):
# 提取当前执行的节点名称
for node_name, update_data in chunk.items():
print(f"[系统进度] 正在执行节点: {node_name}...")
# 2. 处理 Token 流(打字机效果)
elif isinstance(chunk, tuple):
message_chunk, metadata = chunk
# 提取模型生成的文本片段并实时输出
if hasattr(message_chunk, "content") and message_chunk.content:
print(message_chunk.content, end="", flush=True)
# 运行异步流式输出
asyncio.run(stream_agent())
3. 前端对接与生产级优化建议
在实际生产环境中,LangGraph 的流式数据需要通过 SSE(Server-Sent Events)或 WebSocket 桥接到前端。为了保证用户体验,建议在前端做好以下处理:
- 增量渲染(Partial Message) :前端不应简单地"收到什么就显示什么",而应维护一个状态机。收到
on_text_delta事件时将 chunk 追加到当前内容;收到on_chain_end事件时,标记该消息为已完成,防止文字抖动或内容回退。 - 背压处理(Backpressure):大模型每秒可能吐出几十个 Token,如果前端处理不过来会导致消息堆积。建议在服务端实现批量打包机制(例如每收集 10 个 delta 或等待 50ms 后一次性推送),以减少网络往返和渲染开销。
- 工具调用提示 :除了文本流,你还可以监听
on_tool_start和on_tool_end事件。当 Agent 调用外部 API 时,前端可以显示"正在查询数据库..."等加载动画,极大提升交互体验。
掌握了流式输出后,你的 Agent 在交互体验上已经非常接近商业级产品了。接下来,你想探索哪个方向?
- 时间旅行(Time Travel):如何利用 Checkpoint 历史,回溯到对话的任意一步,修改状态并重新执行(超级强大的调试与纠错功能)?
- LangSmith 监控:如何将这套系统接入 LangSmith,在云端可视化追踪每一次调用的 Token 消耗、检索命中率和执行路径?
其他技术:执行回退(Time Travel)
LangGraph 的"时间旅行(Time Travel)"是其最强大的特性之一。它基于 Checkpoint(检查点)机制,让你能够像使用 Git 一样管理 Agent 的执行历史。你可以随时回溯到任意历史节点,查看当时的状态,甚至修改状态后从该节点重新执行,探索不同的业务走向。
要实现时间旅行,核心依赖于以下三个 API:
app.get_state_history(config):获取该会话(thread_id)的所有历史 Checkpoint 列表。app.get_state(config):获取某个特定 Checkpoint 的完整状态快照(包括当前消息、下一步要执行的节点等)。app.update_state(config, values, as_node="..."):在指定的历史节点上手动修改状态,并创建一条新的"分叉时间线"。
核心代码演示:查看历史与修改重跑
假设你已经运行了一个带 Checkpointer 的 LangGraph 图,以下是实现时间旅行的代码:
python
from langgraph.checkpoint.memory import MemorySaver
# 假设 app 已经使用 checkpointer 编译完成
# memory = MemorySaver()
# app = graph.compile(checkpointer=memory)
config = {"configurable": {"thread_id": "user-session-001"}}
# ================= 1. 查看完整执行历史 =================
print("=== 获取对话历史快照 ===")
history = list(app.get_state_history(config))
for i, state in enumerate(history):
print(f"Step {i}: 下一步将执行 {state.next}, 消息数量: {len(state.values.get('messages', []))}")
# ================= 2. 回溯到特定历史节点 =================
# 假设我们想回到倒数第二步(索引为 1 的 Checkpoint)
if len(history) > 1:
old_checkpoint_config = history.config
# 查看该历史节点的详细状态
snapshot = app.get_state(old_checkpoint_config)
print(f"\n=== 回溯到 Step 1 的状态 ===")
print(f"当前消息: {snapshot.values['messages'][-1].content}")
print(f"下一步节点: {snapshot.next}")[[source_group_web_1]]
# ================= 3. 修改状态并创建新分支(时间旅行) =================
# 如果我们觉得 AI 在 Step 1 之后的回答不好,可以修改当时的输入或状态,让它重新生成
new_messages = [{"role": "human", "content": "请换一种更幽默的方式重新回答"}]
# 使用 update_state 从该节点重跑
# as_node 指定从哪个节点开始继续执行
new_config = app.update_state(
old_checkpoint_config,
{"messages": new_messages},
as_node="ai_agent" # 假设你要从 ai_agent 节点重新执行
)
print("\n=== 已创建新的时间分支,继续执行 ===")
# 此时图会从修改后的状态继续往下走,且不会影响原来的历史
for event in app.stream(None, new_config, stream_mode="updates"):
print(event)
核心逻辑解析
- 透明的状态管理:LangGraph 的 Checkpointer 会在每个节点执行后自动保存完整的状态快照。你不需要手动拼接历史消息,框架会替你管理。
- 创建分叉时间线 :
update_state并不会覆盖原来的历史记录,而是基于旧的 Checkpoint 创建了一个新的父级引用。这就像在 Git 中基于某个旧 Commit 拉出了一个新分支,非常适合调试失败步骤或探索"如果当时做了不同选择会怎样"的替代路径。 - 人工介入的终极形态 :在生产环境中,如果 AI 写了一段文案或生成了一个退款方案,审核人员可以直接调用
update_state修改初稿字段,然后让图从修改后的状态继续执行,而不是退回让 AI 重新生成,极大节省了 Token 和时间。
至此,LangGraph 的核心高级特性(状态机、人工介入、流式输出、时间旅行)你已经全部掌握!
接下来,为了让你的 Agent 真正具备"生产级"的可观测性,你想探索哪个方向?
- LangSmith 监控:如何将这套系统接入 LangSmith,在云端可视化追踪每一次调用的 Token 消耗、检索命中率和完整执行路径?
- 生产级部署架构:如何将 Checkpointer 替换为 PostgreSQL/Redis,并结合 FastAPI 部署一套支持高并发、多租户的真实后端服务?
其他技术:LangSmith 监控
将 LangGraph 接入 LangSmith 是迈向生产级应用的关键一步。LangSmith 作为 LangChain 生态下的核心可观测性平台,能够对你的 LLM 应用进行全链路的追踪(Tracing)、调试(Debugging)和评估(Evaluation)。
得益于 LangChain 生态的无缝协作,接入 LangSmith 的门槛极低。你完全不需要修改任何现有的业务代码,只需通过配置环境变量即可开启全链路监控。
以下是完整的接入指南:
第一步:获取 LangSmith API Key
- 访问 LangSmith 官方平台注册并登录(支持 GitHub 等快捷登录)。
- 登录后,进入平台「Settings」(设置)页面,找到「API Keys」选项,创建并复制你的个人 API Key。
第二步:配置环境变量
在你的运行环境(如 .env 文件、终端或 Python 代码中)设置以下核心环境变量:
bash
# 1. 填入你刚刚获取的 API Key
export LANGSMITH_API_KEY="lsv2_pt_你的密钥"
# 2. 开启 LangSmith 追踪功能(必须)
export LANGCHAIN_TRACING_V2="true"
# 3. 设置项目名称(可选,用于在 LangSmith 中分类和管理不同的应用)
export LANGCHAIN_PROJECT="my-langgraph-agent"
配置完成后,再次运行你的 LangGraph 代码,所有的执行数据就会自动上报到 LangSmith 平台。
第三步:在云端可视化追踪你的 Agent
登录 LangSmith 控制台,你可以直观地看到以下核心监控数据:
- 执行路径与耗时分布(Waterfall 追踪) :
在 Trace 详情页,你可以看到 Agent 执行的全流程耗时分布。例如,意图识别、RAG 检索、LLM 生成等每个节点的具体耗时,帮助你快速定位性能瓶颈。 - Token 消耗与成本监控 :
平台会记录每一次 LLM 调用的输入、输出、Token 消耗量以及错误信息。这对于控制生产环境的 API 成本至关重要。 - 交互式调试(LangSmith Studio) :
如果你发现某次对话 AI 回答出错,可以直接在 LangSmith 中查看该次运行的详细 Trace。你可以在 Studio 中模拟输入,甚至在任意节点暂停、检查中间状态、修改输入后继续执行,这比单纯看日志排查问题高效得多。
💡 国内开发者注意事项
由于 LangSmith 的服务器部署在国外,国内普通网络环境可能无法直接访问。如果你的团队面临网络限制或私有化部署需求,推荐考虑国内替代方案 Langfuse。
- 优势:Langfuse 是开源的,支持完全免费的私有部署,且功能与 LangSmith 高度对齐。
- 兼容性:LangSmith 的追踪代码几乎可以无缝平移到 Langfuse,学习成本极低。
至此,你已经打通了从代码编写、流式输出、状态调试到云端监控的完整闭环!接下来,你可以选择:
- 生产级部署架构:将内存 Checkpointer 替换为 PostgreSQL/Redis,结合 FastAPI 部署支持高并发的真实后端服务。
- 自动化评估(Evals):利用 LangSmith 构建测试数据集,在每次修改 Prompt 后自动跑分,确保系统不会发生"回归退化"。