从零构建一个智能客服 AI Agent:LangGraph + Chroma RAG + DeepSeek 实战
本文用一个完整项目,手把手拆解 AI Agent 的核心模式:ReAct 循环、Tool-Use、RAG 检索、多轮记忆,以及背后的设计取舍。
一、为什么做这个项目?
面试 AI Agent 开发岗时,有一道出现频率极高的问题:
"设计一个 AI 客服 Agent,说说你的架构。"
这道题考的不是你会调 API,而是你对 Agent 系统的整体设计能力:
- 怎么让 LLM 理解用户意图?
- 怎么接入外部数据(FAQ、订单系统)?
- 多轮对话的记忆怎么管理?
- 出错了怎么办?
与其背八股,不如直接写一个能跑的项目。本文记录的就是这样一个实战——用 LangGraph 编排 Agent,用 Chroma 做 RAG,用 DeepSeek 做推理引擎。
最终代码在 GitHub 上:github.com/cuzz123/cus…
二、技术选型与为什么
| 选型 | 原因 |
|---|---|
| LangGraph(而非 LangChain Agent) | LangChain Agent 是黑盒,LangGraph 的 StateGraph 让我能显式控制每一步——意图判断、工具调用、循环决策。面试时可以说 "我手写了 StateGraph 的 Node 和 Edge,而不是调了一个封装好的 AgentExecutor"。 |
| Chroma(而非 FAISS / Pinecone) | 本地部署,零成本,和 LangChain 集成好。Chroma 内置 ONNX embedding 模型,不需要额外装 PyTorch 或 sentence-transformers,依赖极轻。 |
| DeepSeek(而非 OpenAI) | 价格便宜(约 1/10),中文理解强,兼容 OpenAI 接口格式,切换成本低。 |
三、架构设计:ReAct 循环
整个 Agent 跑在一个 ReAct(Reasoning + Acting)循环 里:
用户输入
│
▼
┌─────────────────────┐
│ Agent Node │ ← LLM 判断:直接回复?还是调工具?
│ (DeepSeek) │
└────────┬────────────┘
│
┌────┴────┐
▼ ▼
有工具调用 直接回复
│ │
▼ ▼
┌────────┐ 输出结果
│ Tools │
│ Node │ ← 执行 FAQ/订单/天气 查询
└───┬────┘
│
└──→ 回到 Agent Node,再次判断
关键设计决策
1. 为什么要循环?
一次工具调用的结果可能触发另一个工具。比如用户问 "帮我查一下我的订单物流"——先要查出订单号,再查物流,再回答。循环让 Agent 能连续调用多个工具。
2. 为什么用 StateGraph 而不是 Sequential Chain?
StateGraph 的节点之间可以有条件跳转(conditional edges),不是死板的线性流程。我的代码里:
def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
"""检查最后一条消息是否有工具调用"""
last_msg = state["messages"][-1]
if last_msg.tool_calls:
return "tools" # 去执行工具
return END # 直接回复用户
这 vs 传统的 if-else 路由:StateGraph 是图结构,天然支持复杂的分支和循环,而 if-else 只能处理简单的线性分支。
四、三个 Tool 的实现
1. FAQ 检索(RAG)
这是最核心的一个 Tool。用户问 "怎么退货?"、"保修期多久?" 这类问题时,需要从知识库里检索答案。
流程:
FAQ 文档 → 分块 → Embedding → Chroma 向量库 → 语义检索 → 返回 Top-K
代码实现(简化版):
def _build_faq_db():
"""构建 Chroma 向量库"""
ef = embedding_functions.DefaultEmbeddingFunction()
client = chromadb.PersistentClient(path="data/chroma_db")
# 按问题-答案对分块
collection.add(documents=chunks, ids=chunk_ids)
return collection
def faq_search(query: str) -> str:
collection = _build_faq_db()
results = collection.query(query_texts=[query], n_results=3)
return format_results(results)
设计取舍:
| 选项 | 选了什么 | 为什么 |
|---|---|---|
| 分块策略 | 按 FAQ 的标题层级分块(200 chars) | 问题-答案对天然是一个完整语义单元 |
| Embedding | Chroma 内置 ONNX(all-MiniLM-L6-v2) | 轻量,不依赖 PyTorch,~70MB |
| K 值 | Top-3 | 太多会混杂不相关内容,太少可能漏掉 |
| 重排序 | V1 不做 | 简单场景 Top-3 够用,V2 可加 Cohere Rerank |
2. 订单查询
模拟调用后端订单系统的 API。实际生产环境就是调一个内部 HTTP 接口:
def query_order(order_id: str) -> str:
"""查询订单状态、物流、预计送达"""
orders = _load_orders() # 实际环境: requests.get("/api/orders/{id}")
# ... 格式化返回
3. 天气查询
同样模拟第三方 API 调用。展示了 Agent 如何接入外部服务。
五、多轮对话记忆
LangGraph 内置了 MemorySaver(Checkpointer),它记录每次 invoke 的消息历史到内存中:
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
# 每次调用带上 thread_id
result = app.invoke(
{"messages": [("human", user_input)]},
config={"configurable": {"thread_id": "session_001"}},
)
为什么用 Checkpointer 而不是 ConversationBufferMemory?
- Checkpointer 是 LangGraph 原生的,和 StateGraph 的图结构深度集成
- 它是增量存储的,每次只保存状态变更,而不是整个消息列表
- 恢复对话时只需要 thread_id,不需要手动维护 memory 对象
局限(V2 改进方向): 目前是进程内内存,重启就丢了。生产环境应换成 PostgresCheckpointer 或 Redis 实现持久化。
六、兜底与容错
Agent 不能保证永远正确,但可以保证出错了也不崩。我做了三层兜底:
| 层 | 保护什么 | 实现 |
|---|---|---|
| LLM 调用失败 | API 超时、网络异常 | try/except → 返回降级回复 |
| Tool 执行异常 | 查询出错、数据格式异常 | 每个 Tool 内部独立 try/except |
| 无匹配结果 | FAQ 没找到答案 | 返回友好提示,建议转人工 |
错误不是 bug,是功能。 面试官考你"Agent 出错怎么处理"时,能说出这三层兜底,比只说"try catch"高两个档次。
七、可以怎么给面试官讲这个项目?(面试话术)
如果面试官问:"说说你做过的这个项目"
30 秒 elevator pitch:
"我用 LangGraph 搭了一个智能客服 Agent,核心是一个 ReAct 循环:LLM 判断意图 → 调用 Tool → 收集结果 → 再判断。支持 RAG 搜索 FAQ、查询订单和天气,有多轮记忆和三层兜底机制。代码开源在 GitHub,500 多行 Python。"
如果追问:"为什么这么设计?"
- "选 LangGraph 而不是 LangChain Agent,是因为我需要控制每一步的决策,而不是黑盒调用。StateGraph 让我能画出明确的 Node 和 Edge。"
- "RAG 部分用 Chroma 做向量检索,而不是简单的关键词匹配,因为用户问法多样——'退货流程' 和 '怎么把东西退回去' 是同一个意思,语义检索才能覆盖。"
- "三层容错是为了应对生产环境的不可靠——LLM 可能超时、数据库可能连不上、搜索可能没结果,每一层都有预案。"
如果被问"哪里可以改进?"
- "当前记忆是内存级别的,重启丢失。V2 可以接 PostgresCheckpointer 做持久化。"
- "没有做 Agent 的 Observability。可以集成 LangSmith 做 Trace,追踪每次 LLM 调用的耗时和 Token 消耗。"
- "没有流式响应。可以用 .stream() 让用户看到逐字输出,体验更好。"
- "没有加 Rate Limit 和 Cost 控制。DeepSeek 虽然便宜,但死循环会浪费钱,可以用最大迭代次数兜底。"
八、项目链接
GitHub:github.com/cuzz123/cus…
有什么问题或建议,欢迎在评论区讨论!
转行小贴士: 这个项目是为转行做AI Agent 开发的伙伴准备的第一个实战项目。按计划这套做完还会有 MCP Server 和多 Agent 协作系统,保持关注。