LangGraph 实战:从零构建一个会反思、能调用工具的 Agent 智能客服
本文将带你用 LangGraph 从零构建一个完整的 Agentic Workflow------一个真正能查询订单、处理退款、并且在不确定时会主动反思的智能客服系统。所有代码完整可运行。
一、引言
上一篇文章我们聊了 Agentic Workflow 的核心概念------Planning、Tool Use、Reflection、Multi-Agent。但概念终究是概念,真正理解一个东西的最好方式,就是亲手实现它。
今天我们用 LangGraph 从零构建一个智能客服 Agent。这个客服不是那种只能回复固定话术的"傻瓜机器人",而是一个能够:
- 查询真实订单状态(Tool Use)
- 处理退款申请(Tool Use + Human-in-the-loop)
- 在不确定时主动反思和确认(Reflection)
- 根据对话上下文动态调整回复策略(Planning)
需调用工具
需要反思
直接回复
需人工确认
一般情况
发现问题
一切正常
用户输入
Agent 节点
LLM 推理决策
Tool Executor
工具执行
Reflection 反思
检查回复质量
结束
Human Approval
人工审批节点
图:LangGraph 智能客服 Agent 工作流架构
阅读前提:你需要有 Python 基础,了解 LLM 的基本使用方式。不需要提前了解 LangGraph,我会从零讲起。
二、环境准备
2.1 安装依赖
bash
$ pip install langgraph langchain langchain-openai python-dotenv
2.2 项目结构
agentic_cs/
├── .env # API Key 配置
├── tools.py # 工具定义(订单查询、退款等)
├── agent.py # Agent 核心逻辑
├── state.py # 状态定义
└── main.py # 入口文件
2.3 配置 API Key
bash
# .env 文件
OPENAI_API_KEY=sk-your-key-here
python
# main.py 开头
from dotenv import load_dotenv
load_dotenv()
三、LangGraph 核心概念速览
在动手之前,先花 3 分钟理解 LangGraph 的三个核心概念。如果你熟悉状态机,这些概念会非常亲切。
3.1 State(状态)
State 是贯穿整个工作流的数据对象。每当 Agent 执行一个动作,它都会更新 State。可以理解为工作流的"记忆"。
python
from typing import TypedDict, List, Annotated
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
messages: Annotated[List, add_messages] # 对话历史
next_action: str # 下一步动作
user_id: str # 用户 ID
3.2 Node(节点)
Node 是工作流中的一个步骤,比如"查询订单"是一个节点,"生成回复"是另一个节点。每个 Node 接收 State,返回更新后的 State。
python
def query_order(state: AgentState) -> AgentState:
# 查询订单逻辑
order_info = get_order(state["user_id"])
return {"messages": [AIMessage(content=f"您的订单状态:{order_info}")]}
3.3 Edge(边)
Edge 连接节点,决定工作流的走向。LangGraph 支持两种 Edge:
- 普通边:无条件地从 A 到 B
- 条件边:根据 State 的内容决定下一个节点
python
# 条件边示例
def should_continue(state: AgentState) -> str:
last_message = state["messages"][-1]
if "退款" in last_message.content:
return "process_refund"
return "respond_to_user"
Edge 边
Node 节点
State 状态
共享数据对象
贯穿整个工作流
处理步骤
读取 State → 处理 → 更新 State
控制流
决定下一个节点
图:LangGraph 三要素------State、Node、Edge 的协作关系
理解了这三个概念,我们就可以开始了。
四、第一步:定义工具
智能客服需要几个核心工具。我们先模拟一个订单系统和退款系统。
python
# tools.py
import json
from typing import Optional
from datetime import datetime, timedelta
# 模拟订单数据库
MOCK_ORDERS = {
"ORD-2024-001": {
"order_id": "ORD-2024-001",
"user_id": "U123",
"product": "机械键盘 K8 Pro",
"amount": 499.00,
"status": "已签收",
"order_date": "2024-01-15",
"delivery_date": "2024-01-18",
},
"ORD-2024-002": {
"order_id": "ORD-2024-002",
"user_id": "U123",
"product": "27寸 4K 显示器",
"amount": 2999.00,
"status": "运输中",
"order_date": "2024-03-10",
"delivery_date": None,
},
"ORD-2024-003": {
"order_id": "ORD-2024-003",
"user_id": "U123",
"product": "Type-C 数据线",
"amount": 29.90,
"status": "待发货",
"order_date": "2024-03-20",
"delivery_date": None,
},
}
# 模拟退款记录
REFUND_RECORDS = []
def query_user_orders(user_id: str) -> str:
"""查询用户的所有订单"""
orders = [
o for o in MOCK_ORDERS.values()
if o["user_id"] == user_id
]
if not orders:
return f"用户 {user_id} 暂无订单记录。"
result = "您的订单如下:\n\n"
for i, order in enumerate(orders, 1):
result += f"{i}. **{order['product']}**\n"
result += f" - 订单号:{order['order_id']}\n"
result += f" - 金额:¥{order['amount']}\n"
result += f" - 状态:{order['status']}\n"
result += f" - 下单日期:{order['order_date']}\n"
if order["delivery_date"]:
result += f" - 送达日期:{order['delivery_date']}\n"
result += "\n"
return result
def query_order_detail(order_id: str) -> str:
"""查询单个订单的详细信息"""
order = MOCK_ORDERS.get(order_id)
if not order:
return f"未找到订单 {order_id},请核实订单号是否正确。"
result = f"订单 {order_id} 详情:\n"
result += json.dumps(order, ensure_ascii=False, indent=2)
return result
def check_refund_eligibility(order_id: str) -> str:
"""检查订单是否符合退款条件"""
order = MOCK_ORDERS.get(order_id)
if not order:
return f"未找到订单 {order_id}。"
# 退款规则:签收后 7 天内可退款
if order["status"] == "已签收":
delivery = datetime.strptime(order["delivery_date"], "%Y-%m-%d")
days_since = (datetime.now() - delivery).days
if days_since <= 7:
return (
f"订单 {order_id} 符合退款条件。"
f"签收日期为 {order['delivery_date']}({days_since} 天前),"
f"在 7 天无理由退款期内。"
)
else:
return (
f"订单 {order_id} 已超过退款期限。"
f"签收日期为 {order['delivery_date']}({days_since} 天前),"
f"已超出 7 天无理由退款期。"
)
if order["status"] in ("待发货", "运输中"):
return f"订单 {order_id} 当前状态为「{order['status']}」,可以申请取消并退款。"
return f"订单 {order_id} 状态异常,请联系人工客服。"
def submit_refund(order_id: str, reason: str = "") -> str:
"""提交退款申请"""
order = MOCK_ORDERS.get(order_id)
if not order:
return f"未找到订单 {order_id}。"
refund_id = f"REF-{datetime.now().strftime('%Y%m%d%H%M%S')}"
REFUND_RECORDS.append({
"refund_id": refund_id,
"order_id": order_id,
"amount": order["amount"],
"reason": reason or "未提供原因",
"status": "处理中",
"created_at": datetime.now().isoformat(),
})
return (
f"退款申请已提交!\n"
f"- 退款单号:{refund_id}\n"
f"- 订单号:{order_id}\n"
f"- 金额:¥{order['amount']}\n"
f"- 预计 3-5 个工作日内退回原支付账户"
)
# 工具定义列表,用于绑定到 LLM
TOOLS = [
{
"type": "function",
"function": {
"name": "query_user_orders",
"description": "查询指定用户的所有订单列表,包括订单状态、金额等信息",
"parameters": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "用户 ID,例如 'U123'"
}
},
"required": ["user_id"]
}
}
},
{
"type": "function",
"function": {
"name": "query_order_detail",
"description": "查询单个订单的详细信息",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单号,例如 'ORD-2024-001'"
}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "check_refund_eligibility",
"description": "检查某个订单是否符合退款/退货条件",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "要检查的订单号"
}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "submit_refund",
"description": "为指定订单提交退款申请。仅在用户明确确认要退款后调用。",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "要退款的订单号"},
"reason": {"type": "string", "description": "退款原因(可选)"}
},
"required": ["order_id"]
}
}
}
]
# 工具执行器
TOOL_EXECUTORS = {
"query_user_orders": query_user_orders,
"query_order_detail": query_order_detail,
"check_refund_eligibility": check_refund_eligibility,
"submit_refund": submit_refund,
}
五、第二步:定义状态与节点
接下来定义 Agent 的状态和各个节点。
python
# state.py
from typing import TypedDict, Annotated, List, Optional
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
class CustomerServiceState(TypedDict):
"""智能客服的全局状态"""
messages: Annotated[List[BaseMessage], add_messages]
user_id: Optional[str]
pending_refund_order: Optional[str] # 待确认退款的订单号
requires_human: bool # 是否需要转人工
reflection_count: int # 反思次数
python
# agent.py(第 1 部分:节点定义)
import json
from typing import Literal
from langchain_core.messages import (
SystemMessage, HumanMessage, AIMessage, ToolMessage
)
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from state import CustomerServiceState
from tools import TOOLS, TOOL_EXECUTORS
# 初始化 LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
llm_with_tools = llm.bind_tools(TOOLS)
SYSTEM_PROMPT = """你是一个专业的智能客服助手,名为"小智"。
## 你的能力
- 查询用户订单(需要用户 ID)
- 查看订单详情(需要订单号)
- 检查退款资格
- 提交退款申请
## 工作流程
1. 首先友好地打招呼,确认用户身份(获取用户 ID)
2. 理解用户需求后,调用合适的工具
3. 将工具返回的信息用通俗的语言解释给用户
4. 如果涉及退款,先检查资格,再请用户确认,最后才提交
## 重要规则
- 每次只调用一个工具
- 提交退款前必须让用户明确确认
- 如果用户情绪激动,优先安抚
- 遇到无法处理的问题,主动建议转人工
- 不要编造订单信息,一切以工具返回为准
"""
def agent_node(state: CustomerServiceState) -> dict:
"""核心 Agent 节点:调用 LLM 决定下一步行动"""
messages = state["messages"]
# 如果是对话开始,注入 system prompt
if not messages or not isinstance(messages[0], SystemMessage):
messages = [SystemMessage(content=SYSTEM_PROMPT)] + messages
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
def tool_executor_node(state: CustomerServiceState) -> dict:
"""工具执行节点:执行 Agent 决定的工具调用"""
messages = state["messages"]
last_message = messages[-1]
tool_calls = last_message.tool_calls
if not tool_calls:
return {}
tool_messages = []
for tool_call in tool_calls:
func_name = tool_call["name"]
func_args = json.loads(tool_call["args"])
print(f" 🔧 执行工具: {func_name}({func_args})")
executor = TOOL_EXECUTORS.get(func_name)
if executor:
result = executor(**func_args)
else:
result = f"未知工具: {func_name}"
print(f" 📊 工具结果: {result[:100]}...")
tool_messages.append(
ToolMessage(content=result, tool_call_id=tool_call["id"])
)
return {"messages": tool_messages}
def reflection_node(state: CustomerServiceState) -> dict:
"""反思节点:检查回复质量,必要时修正"""
messages = state["messages"]
reflection_count = state.get("reflection_count", 0)
# 获取最近两轮交互
recent = messages[-4:] if len(messages) >= 4 else messages
reflection_prompt = f"""请审视以下客服对话的最后几轮交互,回答反思问题:
对话记录:
{chr(10).join([f"{'用户' if isinstance(m, HumanMessage) else '客服' if isinstance(m, AIMessage) else '系统'}: {m.content[:200]}" for m in recent])}
反思问题:
1. 客服是否完全理解了用户的需求?
2. 客服的回复是否准确(基于工具返回的数据)?
3. 是否有遗漏的信息应该告知用户?
4. 回复的语气是否专业友好?
如果一切正常,回复 "OK"。如果有问题,回复具体需要修正的内容。"""
reflection_response = llm.invoke([HumanMessage(content=reflection_prompt)])
critique = reflection_response.content
print(f" 🤔 反思结果: {critique[:200]}...")
if "OK" in critique[:10]:
return {"reflection_count": reflection_count + 1}
# 将反思结果反馈给 Agent 以修正
return {
"messages": [HumanMessage(
content=f"[系统反思反馈] {critique}\n请根据以上反馈调整你的回复。"
)],
"reflection_count": reflection_count + 1,
}
def human_approval_node(state: CustomerServiceState) -> dict:
"""人工确认节点:退款等敏感操作前暂停等待确认"""
# 在实际系统中,这里会触发一个审批流程
# 在示例中,我们自动确认
messages = state["messages"]
last_message = messages[-1]
print(" ⏸️ 等待人工确认退款操作...")
# 模拟人工确认
approval_message = HumanMessage(
content="[人工审批通过] 已确认退款申请,请继续处理。"
)
return {"messages": [approval_message]}
六、第三步:定义路由逻辑
路由是 Agentic Workflow 的"大脑"------它根据当前状态决定下一步做什么。
python
# agent.py(第 2 部分:路由逻辑)
def route_after_agent(state: CustomerServiceState) -> Literal[
"tool_executor", "reflection", "end"
]:
"""Agent 节点之后的路由决策"""
messages = state["messages"]
last_message = messages[-1]
# 1. 如果 Agent 决定调用工具 → 去执行工具
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tool_executor"
# 2. 如果需要反思 → 去反思节点
reflection_count = state.get("reflection_count", 0)
if reflection_count < 2 and _should_reflect(last_message):
return "reflection"
# 3. 否则结束
return "end"
def route_after_tool(state: CustomerServiceState) -> Literal[
"agent", "human_approval", "end"
]:
"""工具执行后的路由决策"""
messages = state["messages"]
last_message = messages[-1]
content = last_message.content if hasattr(last_message, "content") else ""
# 如果涉及退款,先要人工审批
if "退款申请已提交" in content:
return "human_approval"
# 一般情况回到 Agent 继续
return "agent"
def route_after_reflection(state: CustomerServiceState) -> Literal[
"agent", "end"
]:
"""反思后的路由决策"""
messages = state["messages"]
last_message = messages[-1]
# 如果反思给出了反馈,回到 Agent 修正
if "系统反思反馈" in last_message.content:
return "agent"
# 反思通过,结束
return "end"
def _should_reflect(last_message) -> bool:
"""判断是否需要触发反思"""
if not hasattr(last_message, "content"):
return False
content = last_message.content or ""
# 涉及金额、退款等敏感信息时触发反思
sensitive_keywords = ["退款", "¥", "金额", "价格", "扣费"]
for keyword in sensitive_keywords:
if keyword in content:
return True
# 回复过短时触发反思(可能不完整)
if len(content) < 50:
return True
return False
def should_continue(state: CustomerServiceState) -> Literal["agent", "end"]:
"""全局是否继续的判断"""
messages = state["messages"]
if not messages:
return "end"
last_message = messages[-1]
# 检查是否有再见、结束等信号
if hasattr(last_message, "content"):
farewell_keywords = ["再见", "祝您", "还有其他"]
if any(kw in (last_message.content or "") for kw in farewell_keywords):
# 再多等一轮确认
return "agent"
return "agent"
七、第四步:构建和编译 Graph
把所有节点和边组装成完整的 LangGraph 工作流。
python
# agent.py(第 3 部分:图构建)
def build_customer_service_agent():
"""构建智能客服 Agent 图"""
# 创建状态图
workflow = StateGraph(CustomerServiceState)
# 添加节点
workflow.add_node("agent", agent_node)
workflow.add_node("tool_executor", tool_executor_node)
workflow.add_node("reflection", reflection_node)
workflow.add_node("human_approval", human_approval_node)
# 设置入口点
workflow.set_entry_point("agent")
# 添加条件边:Agent 之后的去向
workflow.add_conditional_edges(
"agent",
route_after_agent,
{
"tool_executor": "tool_executor",
"reflection": "reflection",
"end": END,
}
)
# 工具执行后回到 Agent 或等待审批
workflow.add_conditional_edges(
"tool_executor",
route_after_tool,
{
"agent": "agent",
"human_approval": "human_approval",
"end": END,
}
)
# 人工审批后回到 Agent
workflow.add_edge("human_approval", "agent")
# 反思后回到 Agent 或结束
workflow.add_conditional_edges(
"reflection",
route_after_reflection,
{
"agent": "agent",
"end": END,
}
)
# 编译(带记忆)
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
return app
条件边 1
条件边 2
条件边 3
回到 agent
敏感操作
发现问题
检查通过
start
agent 节点
LLM 推理 + 工具决策
tools 节点
执行工具调用
reflection 节点
反思回复质量
end
human_approval 节点
图:编译后的 LangGraph 工作流图(Mermaid 格式)
八、第五步:运行和测试
最后一步,编写入口文件,实际运行测试。
python
# main.py
from dotenv import load_dotenv
load_dotenv()
from agent import build_customer_service_agent
from langchain_core.messages import HumanMessage
def main():
app = build_customer_service_agent()
# 配置(thread_id 用于区分不同会话)
config = {"configurable": {"thread_id": "session-001"}}
print("=" * 60)
print("🤖 智能客服「小智」已就绪")
print("=" * 60)
# 测试场景 1: 查询订单
print("\n--- 场景 1: 用户查询订单 ---")
result = app.invoke(
{
"messages": [
HumanMessage(content="你好,我想查一下我的订单,我的用户ID是 U123")
]
},
config
)
print_messages(result["messages"])
# 测试场景 2: 申请退款
print("\n\n--- 场景 2: 用户申请退款 ---")
result = app.invoke(
{
"messages": result["messages"] + [
HumanMessage(content="ORD-2024-001 这个键盘我不太满意,想退款")
]
},
config
)
print_messages(result["messages"])
# 测试场景 3: 确认退款
print("\n\n--- 场景 3: 用户确认退款 ---")
result = app.invoke(
{
"messages": result["messages"] + [
HumanMessage(content="是的,请帮我提交退款申请,原因是不太适合我")
]
},
config
)
print_messages(result["messages"])
def print_messages(messages):
"""格式化打印对话记录"""
for msg in messages:
role = msg.__class__.__name__
content = msg.content if hasattr(msg, "content") else str(msg)
if role == "HumanMessage":
prefix = "👤 用户"
elif role == "AIMessage":
prefix = "🤖 小智"
elif role == "ToolMessage":
prefix = "🔧 系统"
# 简化工具消息显示
if len(content) > 150:
content = content[:150] + "..."
else:
prefix = f"📋 {role}"
print(f"{prefix}: {content}")
print()
if __name__ == "__main__":
main()
运行结果示例
运行 python main.py,你会看到类似以下输出:
============================================================
🤖 智能客服「小智」已就绪
============================================================
--- 场景 1: 用户查询订单 ---
🔧 执行工具: query_user_orders({'user_id': 'U123'})
📊 工具结果: 您的订单如下:...
🤖 小智: 您好!我已经查到您的订单,共有 3 笔:
1. **机械键盘 K8 Pro** - ¥499.00(已签收)
2. **27寸 4K 显示器** - ¥2999.00(运输中)
3. **Type-C 数据线** - ¥29.90(待发货)
请问有什么可以帮您的吗?
--- 场景 2: 用户申请退款 ---
🔧 执行工具: check_refund_eligibility({'order_id': 'ORD-2024-001'})
📊 工具结果: 订单 ORD-2024-001 符合退款条件...
🤔 反思结果: OK...
🤖 小智: 好消息!您的键盘订单符合 7 天无理由退款条件。
退款金额为 ¥499.00。确认要提交退款申请吗?
--- 场景 3: 用户确认退款 ---
🔧 执行工具: submit_refund({'order_id': 'ORD-2024-001', 'reason': '不太适合我'})
⏸️ 等待人工确认退款操作...
🤖 小智: 退款申请已提交!退款单号 REF-20240320150000,
预计 3-5 个工作日内退回 ¥499.00 到您的原支付账户。
还有其他需要帮您处理的吗?
九、工作流可视化
LangGraph 提供了内置的可视化功能,可以生成工作流图:
python
# visualize.py
from agent import build_customer_service_agent
app = build_customer_service_agent()
# 生成 Mermaid 图
print(app.get_graph().draw_mermaid())
# 或者保存为 PNG(需要安装 graphviz)
# app.get_graph().draw_png("agent_graph.png")
生成的 Mermaid 图大致如下:
start
agent
tool_executor
reflection
end
human_approval
十、进阶优化建议
这个示例展示了 Agentic Workflow 的核心骨架。在实际生产环境中,你还需要考虑以下优化:
10.1 增加记忆持久化
python
from langgraph.checkpoint.sqlite import SqliteSaver
# 替换内存记忆为 SQLite 持久化
memory = SqliteSaver.from_conn_string("checkpoints.db")
app = workflow.compile(checkpointer=memory)
10.2 添加流式输出
python
# 流式输出每个步骤
for event in app.stream(
{"messages": [HumanMessage(content="查一下我的订单")]},
config,
stream_mode="values"
):
if "messages" in event:
last_msg = event["messages"][-1]
if hasattr(last_msg, "content"):
print(last_msg.content, end="", flush=True)
10.3 并行工具调用
python
# 允许一次调用多个独立的工具
def agent_node(state):
response = llm_with_tools.invoke(
state["messages"],
parallel_tool_calls=True # 启用并行
)
return {"messages": [response]}
10.4 添加超时与重试
python
import asyncio
from tenacity import retry, stop_after_attempt
@retry(stop=stop_after_attempt(3))
async def agent_with_timeout(state):
return await asyncio.wait_for(
llm_with_tools.ainvoke(state["messages"]),
timeout=30.0 # 30 秒超时
)
十一、总结
本文通过一个完整的智能客服案例,带你走通了用 LangGraph 构建 Agentic Workflow 的全流程:
| 步骤 | 核心内容 |
|---|---|
| 工具定义 | 将业务能力封装为 Tool,让 LLM 可以调用 |
| 状态设计 | 定义 AgentState,贯穿工作流始终 |
| 节点实现 | Agent、Tool Executor、Reflection、Human Approval |
| 路由逻辑 | 条件边决定工作流走向,实现动态决策 |
| 图组装 | 将节点和边组合成完整的状态图 |
关键要点回顾:
- LangGraph 的核心是状态图:State → Node → Edge,三者组合出任意复杂度的工作流
- 工具定义决定 Agent 的能力边界:工具描述写得越好,Agent 调用越准确
- 反思机制是质量的保障:在敏感操作(退款、金额)前后加反思,大幅降低犯错概率
- Human-in-the-loop 是安全底线:写入类操作不要让 Agent 自主执行,必须经过人工确认
- 先从简单开始:不要一上来就设计复杂的多 Agent 系统,用一个 Agent + 几个工具先跑通