我第一次尝试用 LangChain 构建客服机器人时,遇到最大的坑就是"对话状态丢失"——用户问完地址,问优惠码,再问退货政策,机器人每次都像失忆了一样从头开始。这就是为什么我后来转向了 LangGraph,它用状态机的方式管理对话流程,让多轮对话变得清晰可控。今天这篇文章,我会从零开始,手把手教你用 LangGraph + HolySheheep AI 构建一个真正能用的客服 Agent。
一、先搞懂什么是"状态管理",为什么要用它
普通聊天机器人的逻辑是:用户发一条 → AI 回复一条 → 结束。没有记忆。而真实客服场景是这样的:
用户: 我想买个手机
客服: 好的,我们有这几款热门机型...
用户: 256G的有哪些?
客服: 这三款都是256G的...
用户: 那256G的最便宜的多少钱?
客服: 是XXX品牌的,售价XXX元...
用户: 可以用优惠码吗?
客服: 可以的,我们目前有以下优惠...
看,这种连续追问就叫多轮对话。LangGraph 的核心思想是:把对话拆成一个个"节点"(Node),用"状态"(State)在节点间传递信息。就像工厂流水线,每个工位都能看到前面的加工结果。
二、环境准备:安装依赖
我建议用 Python 3.10+,先建个虚拟环境(这步很重要,避免依赖冲突):
mkdir customer-service-bot
cd customer-service-bot
python -m venv venv
Windows
venv\Scripts\activate
Mac/Linux
source venv/bin/activate
安装核心依赖
pip install langgraph langchain-core langchain-community
pip install openai # HolySheep API 兼容 OpenAI SDK
注册 HolySheep AI 后,在控制台获取 API Key(格式像 sk-holysheep-xxxxxx)。HolySheheep 的优势是国内直连延迟<50ms,比调 OpenAI 官方快 5-10 倍,而且汇率是 ¥1=$1,比官方的 ¥7.3=$1 便宜 85% 以上。
三、用 LangGraph 构建客服 Agent(完整代码)
3.1 定义对话状态
首先定义一个状态类,包含对话历史和上下文信息:
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
import operator
class CustomerServiceState(TypedDict):
"""客服 Agent 的状态结构"""
messages: Annotated[Sequence[BaseMessage], operator.add] # 对话历史
current_intent: str | None # 当前意图识别结果
user_info: dict # 用户信息(姓名、订单号等)
context: dict # 额外上下文(当前商品、会话阶段等)
def create_initial_state():
"""创建初始状态"""
return {
"messages": [],
"current_intent": None,
"user_info": {},
"context": {
"stage": "greeting", # greeting -> inquiry -> resolution -> closing
"current_product": None,
"pending_action": None
}
}
3.2 接入 HolySheep API 实现 LLM 调用
这里我用 HolySheep 的 GPT-4.1 模型,价格是 $8/MTok 输出,汇率 ¥1=$1,换算下来非常划算。接入方式完全兼容 OpenAI SDK,只需改 base_url 和 api_key:
from openai import OpenAI
class HolySheepLLM:
"""HolySheep AI LLM 封装"""
def __init__(self, api_key: str):
self.client = OpenAI(
api_key=api_key,
base_url="https://api.holysheep.ai/v1" # 必须是这个地址
)
def invoke(self, prompt: str, system_prompt: str = "") -> str:
"""
调用 LLM 生成回复
参数:
prompt: 用户输入
system_prompt: 系统提示词
返回:
LLM 生成的文本回复
"""
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat.completions.create(
model="gpt-4.1", # HolySheep 支持的模型
messages=messages,
temperature=0.7,
max_tokens=500
)
return response.choices[0].message.content
使用示例
llm = HolySheepLLM(api_key="YOUR_HOLYSHEEP_API_KEY") # 换成你的真实 Key
response = llm.invoke("你好,我想咨询退货流程")
print(response)
3.3 定义意图识别节点
客服 Agent 的核心是判断用户想干什么。我定义了几种常见意图:
INTENT_PROMPT = """你是一个电商客服机器人,你需要判断用户消息的意图。
可能的意图类型:
- greeting: 问候/打招呼
- product_inquiry: 产品咨询
- order_status: 订单查询
- refund: 退货/退款
- discount: 优惠/折扣
- complaint: 投诉
- farewell: 道别/结束
- other: 其他问题
请只输出意图类型,不要输出其他内容。
用户消息:{user_message}"""
INTENT_KEYWORDS = {
"greeting": ["你好", "hi", "hello", "在吗", "在不在"],
"product_inquiry": ["这个", "那个", "多少钱", "规格", "参数", "有货吗"],
"order_status": ["订单", "发货", "物流", "到了吗", "什么时候到"],
"refund": ["退货", "退款", "换货", "不满意", "不要了"],
"discount": ["优惠", "打折", "降价", "便宜", "优惠码", "券"],
"complaint": ["投诉", "差评", "质量问题", "坏了", "投诉"],
"farewell": ["谢谢", "拜拜", "再见", "好的", "知道了"]
}
def classify_intent(state: CustomerServiceState) -> CustomerServiceState:
"""意图识别节点 - 判断用户想干什么"""
user_message = state["messages"][-1].content
# 先用关键词快速匹配
for intent, keywords in INTENT_KEYWORDS.items():
if any(kw in user_message for kw in keywords):
state["current_intent"] = intent
state["context"]["stage"] = "inquiry" if intent != "greeting" else "greeting"
return state
# 关键词匹配失败,用 LLM 判断
llm = HolySheepLLM(api_key="YOUR_HOLYSHEEP_API_KEY")
prompt = INTENT_PROMPT.format(user_message=user_message)
intent = llm.invoke(prompt).strip().lower()
state["current_intent"] = intent if intent in INTENT_KEYWORDS else "other"
return state
3.4 定义回复生成节点
根据意图生成专业回复,这是最核心的节点:
SYSTEM_PROMPT = """你是一个专业的电商客服,代表店铺与用户沟通。
回复要求:
1. 语气友好、专业、有耐心
2. 简洁明了,不要啰嗦
3. 如果需要用户提供信息,主动询问
4. 涉及到订单号、金额等重要信息时,提醒用户核对
对话历史:
{history}
当前意图:{intent}
用户信息:{user_info}
上下文:{context}
用户最新消息:{user_message}
请生成回复:"""
def generate_response(state: CustomerServiceState) -> CustomerServiceState:
"""生成回复节点 - 根据意图生成专业客服回复"""
user_message = state["messages"][-1].content
intent = state.get("current_intent", "other")
user_info = state.get("user_info", {})
context = state.get("context", {})
# 构造提示词
history_text = "\n".join([
f"{'用户' if isinstance(m, HumanMessage) else '客服'}: {m.content}"
for m in state["messages"][:-1]
])
prompt = SYSTEM_PROMPT.format(
history=history_text or "(无历史对话)",
intent=intent,
user_info=user_info,
context=context,
user_message=user_message
)
# 调用 HolySheep API
llm = HolySheepLLM(api_key="YOUR_HOLYSHEEP_API_KEY")
response_text = llm.invoke(prompt, system_prompt="你是一个专业的电商客服。")
# 将 AI 回复加入消息历史
ai_message = AIMessage(content=response_text)
state["messages"] = state["messages"] + [ai_message]
return state
3.5 搭建状态图(Graph)
现在把节点串起来,形成完整的状态图:
from langgraph.graph import StateGraph, END
def should_continue(state: CustomerServiceState) -> str:
"""判断是否继续对话"""
last_message = state["messages"][-1].content if state["messages"] else ""
# 检测结束意图
farewell_keywords = ["谢谢", "好的", "拜拜", "再见", "没问题了", "知道了"]
if any(kw in last_message for kw in farewell_keywords):
return "end"
# 最多对话 20 轮
if len(state["messages"]) >= 40: # 20 轮 = 40 条消息
return "end"
return "continue"
def route_after_intent(state: CustomerServiceState) -> str:
"""意图识别后的路由"""
return "generate_response"
创建状态图
workflow = StateGraph(CustomerServiceState)
添加节点
workflow.add_node("classify_intent", classify_intent)
workflow.add_node("generate_response", generate_response)
设置入口点和边
workflow.set_entry_point("classify_intent")
workflow.add_edge("classify_intent", "generate_response")
添加条件边:generate_response 之后判断是否继续
workflow.add_conditional_edges(
"generate_response",
should_continue,
{
"continue": "classify_intent", # 继续下一轮
"end": END
}
)
编译图
customer_service_graph = workflow.compile()
def chat(message: str, state: dict = None):
"""对话入口函数"""
if state is None:
state = create_initial_state()
# 添加用户消息
human_message = HumanMessage(content=message)
state["messages"] = state["messages"] + [human_message]
# 运行状态图
result = customer_service_graph.invoke(state)
return result
3.6 运行测试
现在来测试一下完整的对话流程:
# 初始化对话状态
session_state = None
第一轮:打招呼
print("=" * 50)
print("用户: 你好,我想问一下手机")
session_state = chat("你好,我想问一下手机", session_state)
print("客服:", session_state["messages"][-1].content)
print(f"当前意图: {session_state['current_intent']}")
第二轮:追问价格
print("\n" + "=" * 50)
print("用户: 256G的多少钱")
session_state = chat("256G的多少钱", session_state)
print("客服:", session_state["messages"][-1].content)
第三轮:问优惠
print("\n" + "=" * 50)
print("用户: 可以用优惠码吗")
session_state = chat("可以用优惠码吗", session_state)
print("客服:", session_state["messages"][-1].content)
第四轮:结束对话
print("\n" + "=" * 50)
print("用户: 谢谢,知道了")
session_state = chat("谢谢,知道了", session_state)
print("客服:", session_state["messages"][-1].content)
四、HolySheep API 价格对比(帮你们省钱)
我用 HolySheep AI 做过详细对比,2026 年主流模型价格:
- GPT-4.1: $8/MTok(输出) · HolySheep 汇率 ¥1=$1,实际约 ¥8/MTok
- Claude Sonnet 4.5: $15/MTok · HolySheep 汇率后约 ¥15/MTok
- Gemini 2.5 Flash: $2.50/MTok · 性价比极高,适合客服场景
- DeepSeek V3.2: $0.42/MTok · 最低价,适合简单咨询
我自己的经验是:客服 Agent 用 Gemini 2.5 Flash 完全够用,响应快、成本低、效果好。一条典型客服对话(输入+输出约 2000 tokens),成本不到 ¥0.005,换算成人民币几乎可以忽略不计。
五、部署到 Web 服务
最后一步,把 Agent 部署成 Web API,方便前端调用。我用 FastAPI:
# pip install fastapi uvicorn
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uuid
app = FastAPI(title="客服 Agent API")
存储会话状态(生产环境建议用 Redis)
sessions = {}
class ChatRequest(BaseModel):
session_id: str | None = None
message: str
class ChatResponse(BaseModel):
session_id: str
response: str
history: list
@app.post("/chat", response_model=ChatResponse)
async def chat_endpoint(request: ChatRequest):
"""对话接口"""
# 获取或创建会话
if request.session_id and request.session_id in sessions:
session_state = sessions[request.session_id]
else:
session_id = request.session_id or str(uuid.uuid4())
session_state = None
# 调用 Agent
try:
result = chat(request.message, session_state)
sessions[session_id] = result
return ChatResponse(
session_id=session_id,
response=result["messages"][-1].content,
history=[
{"role": "user" if isinstance(m, HumanMessage) else "assistant",
"content": m.content}
for m in result["messages"]
]
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health():
return {"status": "ok"}
启动服务: uvicorn main:app --host 0.0.0.0 --port 8000
常见报错排查
报错1: "AuthenticationError: Incorrect API key provided"
这是最常见的错误,意味着 API Key 格式不对或已过期。
# ❌ 错误写法
client = OpenAI(api_key="sk-holysheep-xxx", base_url="https://api.holysheep.ai/v1")
✅ 正确写法
client = OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY", # 替换成真实 Key,从控制台复制
base_url="https://api.holysheep.ai/v1"
)
检查 Key 是否正确
print(client.api_key) # 确认输出的是你的真实 Key,不是占位符
报错2: "RateLimitError: Rate limit reached"
请求太快被限流了。解决方案是加延迟和重试机制:
import time
from openai import RateLimitError
def call_with_retry(client, model, messages, max_retries=3):
"""带重试的 API 调用"""
for i in range(max_retries):
try:
response = client.chat.completions.create(
model=model,
messages=messages
)
return response
except RateLimitError:
if i < max_retries - 1:
wait_time = 2 ** i # 指数退避: 1s, 2s, 4s
print(f"触发限流,等待 {wait_time}s 后重试...")
time.sleep(wait_time)
else:
raise Exception("API 调用失败,已达到最大重试次数")
使用示例
result = call_with_retry(client, "gpt-4.1", messages)
报错3: "State schema missing required key: 'messages'"
状态字典缺少必要字段。确保状态初始化时包含所有必需字段:
# ❌ 错误:状态不完整
bad_state = {
"messages": [HumanMessage(content="你好")]
# 缺少 current_intent, user_info, context
}
✅ 正确:使用初始化工厂函数
good_state = create_initial_state()
good_state["messages"] = [HumanMessage(content="你好")]
或者手动补全所有字段
full_state = {
"messages": [HumanMessage(content="你好")],
"current_intent": None,
"user_info": {},
"context": {
"stage": "greeting",
"current_product": None,
"pending_action": None
}
}
报错4: "Invalid URL: Cannot connect to endpoint"
base_url 配置错误,常见于复制代码时遗漏或拼写错误。
# ❌ 常见错误写法
base_url = "https://api.holysheep.ai/v1/" # 多了尾部斜杠(部分版本有问题)
base_url = "https://api.holysheep.ai/" # 缺少 /v1 路径
base_url = "https://api.holysheep.com/v1" # 拼写错误
✅ 正确写法
base_url = "https://api.holysheep.ai/v1" # 无尾部斜杠
报错5: "Context window exceeded"
对话历史太长,超出了模型上下文限制。
def trim_messages(messages: list, max_messages: int = 20) -> list:
"""裁剪消息历史,保留最近 N 条"""
if len(messages) <= max_messages:
return messages
# 保留系统消息(如果有)和最近的 max_messages 条
system_messages = [m for m in messages if hasattr(m, 'content') and
getattr(m, 'content', '').startswith('你是一个')]
recent_messages = messages[-max_messages:]
if system_messages:
return system_messages + recent_messages
return recent_messages
在生成回复前调用
state["messages"] = trim_messages(state["messages"], max_messages=20)
六、我的实战经验总结
我在生产环境跑这个客服 Agent 大半年,有几点心得:
- 状态要持久化:生产环境用 Redis 存会话状态,别存内存里,重启就丢了。
- 意图识别要降级:LLM 判断意图偶尔会抽风,加关键词兜底能救一命。
- 回复要加置信度:不确定的问题(涉及退款金额、订单状态)一定要人工复核。
- 监控调用量:用 HolySheep 的仪表盘看用量,设置预算告警。
调试的时候,我习惯在每轮对话后打印 state,清楚看到意图识别对不对、上下文传没传对。LangGraph 的优势就是这种透明性,不像传统 LangChain 那样黑盒。