从零搭建电商智能导购 Agent:LangGraph 编排 + 三层记忆 + 熔断降级 + 生产部署全链路实战
本文记录了一个完整的电商消费决策 Agent 从骨架到生产部署的全过程,涵盖 LangGraph 图编排、三层记忆体系、MCP 工具调用、熔断降级、人工审批(HITL)、nginx 反向代理等环节,并附上全程踩坑记录。代码已跑通,生产环境一键部署。
📌 目录
- 一、项目背景与目标
- 二、技术栈总览
- 三、整体架构设计
- 四、核心模块实现
- [4.1 LangGraph 图编排](#4.1 LangGraph 图编排)
- [4.2 三层记忆体系](#4.2 三层记忆体系)
- [4.3 MCP 工具层](#4.3 MCP 工具层)
- [4.4 韧性设计:熔断 / 重试 / 死信](#4.4 韧性设计:熔断 / 重试 / 死信)
- [4.5 HITL 人工审批](#4.5 HITL 人工审批)
- 五、生产部署实战
- 六、踩坑记录(重点)
- 七、效果展示
- 八、总结与展望
一、项目背景与目标
做一个能帮用户找商品、比价、查风险、盯价的智能导购 Agent,核心诉求:
- 自然语言交互:用户说"帮我找个 2000 以内的降噪耳机",Agent 自动搜货、比价、评估风险。
- 有记忆:同一会话连续对话能感知上下文;跨会话能记住用户偏好("只买索尼")。
- 扛得住故障:外部工具挂了不能整条链路崩,要能降级、重试、进死信。
- 人工可干预:下单这种关键操作要等人审批。
- 可生产部署 :一条
docker compose up跑全套,HTTPS 对外。
二、技术栈总览
| 层级 | 技术选型 |
|---|---|
| Agent 编排 | LangGraph(StateGraph + interrupt 做 HITL) |
| 后端 | FastAPI + SSE 流式输出 |
| 工作记忆 | Redis |
| 情景记忆 | PostgreSQL |
| 长期画像 | ChromaDB(向量检索) |
| 工具层 | MCP(独立 HTTP 服务,契约驱动) |
| 韧性 | 三态熔断器 + 指数退避重试 + 死信表 |
| 前端 | React + Vite + TypeScript |
| 部署 | Docker Compose + nginx 反向代理 + HTTPS |
| 检查点 | PostgreSQL(AsyncPostgresSaver,支持断点恢复) |
三、整体架构设计
用户 → nginx(80/443) → frontend(/)
→ backend(/api/) → LangGraph 图
├─ supervisor(意图路由,LLM + 关键词兜底)
├─ search(MCP 商品搜索)
├─ compare(MCP 比价)
├─ risk(MCP 风险评估)
├─ prepare_order → approval(interrupt 挂起等人)
├─ archive(归档入库)
└─ monitor(盯价调度)
记忆层:Redis(工作记忆) ←→ PG(情景记忆) ←→ ChromaDB(长期画像)
韧性层:熔断器 + 重试 + 死信,串在每个节点的工具调用前
LangGraph 的图结构是核心,所有业务节点都是图上的一个 node,supervisor 根据意图决定走哪条边。
四、核心模块实现
4.1 LangGraph 图编排
用 StateGraph 定义状态机,interrupt() 实现 HITL 挂起。
State 定义:
python
class ShoppingState(TypedDict):
query: dict # {raw, category, budget, constraints, order_requested}
intent: str
product_ids: list[str]
comparison_table: list
risk_report: dict
selected_product: dict
completed_nodes: list[str]
memory_context: dict # 三层记忆组装后的上下文
Supervisor 意图路由(LLM 优先,关键词兜底):
python
VALID_INTENTS = {"search", "compare", "risk_check", "archive", "monitor", "unknown"}
async def run(state: ShoppingState) -> dict:
text = (state.get("query") or {}).get("raw", "")
# LLM 解析失败自动走关键词兜底,主流程永远不会因 LLM 挂掉而中断
parsed = await _llm_route(state, text) or _keyword_route(text)
return {
"intent": parsed["intent"],
"next_node": parsed["intent"], # intent 直接当节点名
"query": {...},
"completed_nodes": ["supervisor"],
}
关键设计 :next_node = intent,六个意图对应六个分支节点,图的边天然对齐意图枚举,加新意图只需加节点 + 加边。
4.2 三层记忆体系
| 记忆类型 | 存储 | 用途 | 生命周期 |
|---|---|---|---|
| 工作记忆 | Redis | 当前会话上下文快照 | 会话级 |
| 情景记忆 | PostgreSQL | 会话结构化摘要 | 长期 |
| 长期画像 | ChromaDB | 用户偏好向量检索 | 长期 |
记忆组装(Token Budget 控制):
python
def build_memory_context(history, summaries, persona_docs):
# 总预算 4000 token,画像最多占 60%,超了按行截断
budget = {"total": 4000, "persona_max": 2400}
...
新会话开头:检索最近 5 条情景摘要 + 向量召回 Top6 画像,拼进 memory_context 给 Supervisor 当上下文。
去重与并发控制:
- Redis Set 存消息 hash,TTL 7 天,重复消息直接回
{"duplicate": true} - 情景记忆写入前
setnx分布式锁,防止并发写同一会话摘要
4.3 MCP 工具层
把所有外部电商 API 封装成独立 MCP 服务,契约驱动:
POST /tools/{name}
Headers: X-Correlation-ID(原样回显)
三个工具:product_search / price_query / review_risk。
后端用 tool_gateway 统一调用,带语义断言 + 重试 + 降级:
python
data, err = await call_tool_resilient(
"product_search",
{"category": "降噪耳机", "budget": 2000},
node_id="search",
degrade=MOCK, # 工具全挂时降级 mock
assert_fn=lambda d: (len(d["product_ids"]) >= 1, "空结果"),
)
断言失败自动重试,重试耗尽进死信 + 返回降级结果,图继续往下跑。
4.4 韧性设计:熔断 / 重试 / 死信
每个节点调外部工具前先过熔断器:
python
if not breaker.allow("search"):
return {"product_ids": [], "mock": True} # OPEN 状态直接降级
三态转换:CLOSED → 连续失败达阈值 → OPEN → 冷却时间到 → HALF_OPEN(放一个探测请求)→ 成功回 CLOSED / 失败回 OPEN。
重试 :@retry(max_attempts=3, base_delay=...) 指数退避。
死信 :耗尽重试后写入 dead_letter 表,带 retry_history,可人工排查重放。
4.5 HITL 人工审批
LangGraph 的 interrupt() 原生支持:
python
def approval_node(state):
decision = interrupt(
{"product": state["selected_product"], "amount": ...},
resumable=True,
)
if decision["approved"]:
return {"order_status": "confirmed"}
return {"order_status": "rejected"}
图跑到 interrupt() 自动挂起,前端收到 __interrupt__ 帧弹审批框,用户点通过/驳回后 POST /api/approval/{thread_id},图从断点续跑。
断点恢复 :checkpoint 存 PostgreSQL,用同一 thread_id 重启进程后能从挂起点继续,中间产出不丢。
五、生产部署实战
一条命令起全套:
bash
docker compose -p shopping-agent -f docker-compose.yml -f docker-compose.prod.yml up -d
nginx 配置要点:
- 独占 80/443,HTTP 301 跳 HTTPS
/→ frontend,/api/+/health→ backend- SSE 关 buffering:
proxy_buffering off+ 1h 超时 limit_req10r/s 突发 20(生产限流)- 动态 upstream 解析 :
resolver 127.0.0.11+ 变量式proxy_pass http://$backend$request_uri,后端晚启动不会把 nginx 拖崩
安全:
- Bearer Token 认证(
API_TOKEN环境变量,不配=开发模式放行) - CORS 收紧(
CORS_ORIGINS环境变量) - 滑动窗口限流(
RATE_LIMIT_PER_MIN) - 密码/Token 全走
.env,仓库只留.env.example
内部端口隔离 :postgres/redis/chroma/mcp/backend/frontend 的端口在生产覆盖文件里 ports: !reset [] 全清空,只让 nginx 对外。
六、踩坑记录(重点)
坑 1:ChromaDB 嵌入模型冷启动卡死 ⭐
首个查画像的请求触发从 S3 懒下载 all-MiniLM-L6-v2(79MB),本机到 s3.amazonaws.com 只有 ~50KB/s,要等 20 分钟,SSE 流直接卡死无输出。
修法:
dockerfile
# Dockerfile 构建期预下载,生产镜像自带模型
RUN mkdir -p /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2 && \
curl -fsSL "https://chroma-onnx-models.s3.amazonaws.com/all-MiniLM-L6-v2/onnx.tar.gz" \
-o /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx.tar.gz && \
tar -xzf ... -C /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/
同时 docker-compose 给 backend 挂 chromacache 命名卷,容器重建不丢缓存。
坑 2:checkpointer 必须用 AsyncPostgresSaver
同步版 PostgresSaver 的异步方法直接抛 NotImplementedError,而整条 API 是 astream/aget_state 异步链路。换成 AsyncPostgresSaver + psycopg_pool 的 AsyncConnectionPool。
坑 3:Windows 启动必须用 python run.py,别用 uvicorn
psycopg 异步模式要求 SelectorEventLoop,Windows 默认是 ProactorEventLoop,且新版 uvicorn 自建循环不理会全局 policy。run.py 里手动建循环再 serve。
坑 4:compose 必须带 -p shopping-agent
中文目录名(电商)导致 compose 项目名为空直接报错,必须显式指定 project name。
坑 5:nginx 静态 upstream 拖崩自己
用静态 upstream backend { server backend:8000; } 时,如果后端晚启动,nginx 解析失败直接 emerg 循环重启。改 resolver 127.0.0.11 + 变量式 proxy_pass 动态解析。
坑 6:测试里写死 session_id 必须 FakeRedis 隔离
联调残留的 wm:seen:{sid} 去重集合 TTL 7 天,直连真 Redis 再跑测试会命中 duplicate 帧。测试统一用 FakeRedis。
七、效果展示
发一条「帮我找个耳机」,SSE 流返回:
json
// supervisor
{"intent": "search", "next_node": "search", "query": {"category": "耳机", ...}}
// search - 5 个真实商品
{"product_ids": ["xiaomi-buds-4-pro", "huawei-freebuds-pro-3", "airpods-pro-2-usbc", "bose-qc45-ii", "sony-wh1000xm5"]}
// compare - 15 条比价,自动选最低价
{"selected_product": {"product_id": "xiaomi-buds-4-pro", "platform": "拼多多", "price": 799, "price_verified": true}}
// risk - 5 份风险报告全评估
{"risk_report": {"xiaomi-buds-4-pro": {"negative_rate": 0.035, "shop_score": 4.6, "risk_assessed": true}, ...}}
生产环境验证:HTTPS 全链路通,内部端口宿主机不可达,SSE 经 nginx 流式正常。
八、总结与展望
已完成:LangGraph 编排骨架、三层记忆读写、MCP 工具层、熔断/重试/死信、HITL 审批、断点恢复、生产部署、前后端测试(后端 92 条全过覆盖率 89%,前端 12 条全过)。
下一步:
- LLM prompt 优化(Supervisor 意图解析 + 会话摘要),目前是草稿版
- 评测集扩充到 200 条
- checkpoint 版本迁移
- MCP 接真实电商 API(目前是 mock JSON 数据源,只换数据层,契约不动)
这个项目的核心思路是:把不确定性(LLM、外部工具)用确定性的工程手段兜住------熔断器、重试、死信、降级、关键词兜底,让 Agent 在任何环节出问题都能 graceful degradation,而不是直接崩。
如果觉得有帮助,欢迎点赞收藏 👍 有问题评论区交流。