LangChain-ReAct-Agent 智能客服系统 · 项目技术文档

资源下载链接:LangChain ReAct Agent 智能客服系统(扫地机器人垂直场景)

说明:本文档描述项目当前整体现状,涵盖架构、模块、存储、配置、部署、接口与扩展方式。


1. 项目概述

1.1 项目定位

基于 LangChain + LangGraph 构建的生产级 ReAct 智能客服 Agent,以"扫地机器人产品咨询"为示例垂直场景,支持三类核心任务:

任务类型 说明 依赖工具
知识库问答 故障咨询、操作说明、保养方法等产品问题 rag_summarize(RAG 检索增强)
工具辅助问答 天气查询、用户定位等外部信息补充 get_weather / get_user_location
个性化报告生成 按用户 + 月份生成使用分析报告 fill_context_for_report + fetch_external_data

1.2 核心能力

  • ReAct 多工具智能体 :基于 LangGraph create_agent 高层 API,模型自主决策"思考 → 工具调用 → 观察 → 再思考",支持动态提示词切换;
  • RAG 两阶段检索:ES 向量 + BM25 混合检索(RRF 融合)粗召回 → Rerank 精排 + 阈值过滤 → LLM 生成,附参考资料来源(metadata 透传);
  • Redis 持久化会话:多用户、多会话长期对话,7 天 TTL,LIST 原子追加防并发丢消息;
  • RESTful 流式后端 :FastAPI + SSE(text/event-stream)流式返回,支持第三方系统对接与独立部署;
  • Agent 高可用防护:迭代轮数熔断、原地打转检测、图级 recursion_limit 硬上限、工具超时兜底、全链路降级;
  • LLM 自动化评测:测试用例 + LLM 裁判打分,输出任务成功率 / 工具调用准确率 / 幻觉率 / 平均分。

1.3 技术栈

技术选型
Agent 框架 LangChain 1.2.x、LangGraph(create_agent 高层 API、stream_mode="values"
大模型 阿里云百炼 DashScope:qwen3-max(对话)、text-embedding-v4(向量)、qwen3-rerank(精排 API)
服务框架 FastAPI 0.141.x + uvicorn + SSE
会话存储 Redis(LIST 结构 + pipeline 原子操作)
向量检索 Elasticsearch 8.x(生产主后端:向量 KNN + BM25 混合检索)
降级向量库 Chroma(ES 故障 / 非 ES 后端时自动降级)
前端(可选) Streamlit(app_streamlit.py,本地调试界面)
评测 自研 eval/eval_runner.py(LLM 裁判)

2. 系统架构

2.1 总体架构

#mermaid-svg-iOY9LndcZXAEol4S{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iOY9LndcZXAEol4S .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iOY9LndcZXAEol4S .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iOY9LndcZXAEol4S .error-icon{fill:#552222;}#mermaid-svg-iOY9LndcZXAEol4S .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iOY9LndcZXAEol4S .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iOY9LndcZXAEol4S .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iOY9LndcZXAEol4S .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iOY9LndcZXAEol4S .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iOY9LndcZXAEol4S .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iOY9LndcZXAEol4S .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iOY9LndcZXAEol4S .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iOY9LndcZXAEol4S .marker.cross{stroke:#333333;}#mermaid-svg-iOY9LndcZXAEol4S svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iOY9LndcZXAEol4S p{margin:0;}#mermaid-svg-iOY9LndcZXAEol4S .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iOY9LndcZXAEol4S .cluster-label text{fill:#333;}#mermaid-svg-iOY9LndcZXAEol4S .cluster-label span{color:#333;}#mermaid-svg-iOY9LndcZXAEol4S .cluster-label span p{background-color:transparent;}#mermaid-svg-iOY9LndcZXAEol4S .label text,#mermaid-svg-iOY9LndcZXAEol4S span{fill:#333;color:#333;}#mermaid-svg-iOY9LndcZXAEol4S .node rect,#mermaid-svg-iOY9LndcZXAEol4S .node circle,#mermaid-svg-iOY9LndcZXAEol4S .node ellipse,#mermaid-svg-iOY9LndcZXAEol4S .node polygon,#mermaid-svg-iOY9LndcZXAEol4S .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iOY9LndcZXAEol4S .rough-node .label text,#mermaid-svg-iOY9LndcZXAEol4S .node .label text,#mermaid-svg-iOY9LndcZXAEol4S .image-shape .label,#mermaid-svg-iOY9LndcZXAEol4S .icon-shape .label{text-anchor:middle;}#mermaid-svg-iOY9LndcZXAEol4S .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iOY9LndcZXAEol4S .rough-node .label,#mermaid-svg-iOY9LndcZXAEol4S .node .label,#mermaid-svg-iOY9LndcZXAEol4S .image-shape .label,#mermaid-svg-iOY9LndcZXAEol4S .icon-shape .label{text-align:center;}#mermaid-svg-iOY9LndcZXAEol4S .node.clickable{cursor:pointer;}#mermaid-svg-iOY9LndcZXAEol4S .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iOY9LndcZXAEol4S .arrowheadPath{fill:#333333;}#mermaid-svg-iOY9LndcZXAEol4S .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iOY9LndcZXAEol4S .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iOY9LndcZXAEol4S .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iOY9LndcZXAEol4S .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iOY9LndcZXAEol4S .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iOY9LndcZXAEol4S .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iOY9LndcZXAEol4S .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iOY9LndcZXAEol4S .cluster text{fill:#333;}#mermaid-svg-iOY9LndcZXAEol4S .cluster span{color:#333;}#mermaid-svg-iOY9LndcZXAEol4S div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iOY9LndcZXAEol4S .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iOY9LndcZXAEol4S rect.text{fill:none;stroke-width:0;}#mermaid-svg-iOY9LndcZXAEol4S .icon-shape,#mermaid-svg-iOY9LndcZXAEol4S .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iOY9LndcZXAEol4S .icon-shape p,#mermaid-svg-iOY9LndcZXAEol4S .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iOY9LndcZXAEol4S .icon-shape .label rect,#mermaid-svg-iOY9LndcZXAEol4S .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iOY9LndcZXAEol4S .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iOY9LndcZXAEol4S .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iOY9LndcZXAEol4S :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础设施
RAG 层 rag/
Agent 层 agent/
API 服务层 api/main.py
客户端
Streamlit 前端 / 第三方系统
POST /api/v1/chat/stream (SSE)
GET /api/v1/chat/history
GET /health
CORS 白名单 + 入参校验
ReactAgent

create_agent 高层 API
中间件 middleware.py

monitor_tool / 动态提示词
工具集 agent_tools.py

rag_summarize / fetch_external_data ...
工具超时装饰器

tool_timeout
VectorStoreService

hybrid_search 混合检索
RerankService

精排 + 阈值过滤
RagSummarizeService

LCEL 链路组装
Redis

chat:msg:{sid} / chat:ctx:{sid}
Elasticsearch

rag_knowledge 索引
Chroma

降级向量库
DashScope API

qwen3-max / embedding / rerank

2.2 对话请求时序(SSE 流式)

ES / Rerank / LLM Redis ReactAgent api/main.py 客户端 ES / Rerank / LLM Redis ReactAgent api/main.py 客户端 #mermaid-svg-Bbe5LmwRuhbmurRv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bbe5LmwRuhbmurRv .error-icon{fill:#552222;}#mermaid-svg-Bbe5LmwRuhbmurRv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bbe5LmwRuhbmurRv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bbe5LmwRuhbmurRv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bbe5LmwRuhbmurRv .marker.cross{stroke:#333333;}#mermaid-svg-Bbe5LmwRuhbmurRv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bbe5LmwRuhbmurRv p{margin:0;}#mermaid-svg-Bbe5LmwRuhbmurRv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Bbe5LmwRuhbmurRv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Bbe5LmwRuhbmurRv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Bbe5LmwRuhbmurRv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Bbe5LmwRuhbmurRv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Bbe5LmwRuhbmurRv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Bbe5LmwRuhbmurRv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Bbe5LmwRuhbmurRv .sequenceNumber{fill:white;}#mermaid-svg-Bbe5LmwRuhbmurRv #sequencenumber{fill:#333;}#mermaid-svg-Bbe5LmwRuhbmurRv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Bbe5LmwRuhbmurRv .messageText{fill:#333;stroke:none;}#mermaid-svg-Bbe5LmwRuhbmurRv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Bbe5LmwRuhbmurRv .labelText,#mermaid-svg-Bbe5LmwRuhbmurRv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Bbe5LmwRuhbmurRv .loopText,#mermaid-svg-Bbe5LmwRuhbmurRv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Bbe5LmwRuhbmurRv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Bbe5LmwRuhbmurRv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Bbe5LmwRuhbmurRv .noteText,#mermaid-svg-Bbe5LmwRuhbmurRv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Bbe5LmwRuhbmurRv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Bbe5LmwRuhbmurRv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Bbe5LmwRuhbmurRv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Bbe5LmwRuhbmurRv .actorPopupMenu{position:absolute;}#mermaid-svg-Bbe5LmwRuhbmurRv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Bbe5LmwRuhbmurRv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Bbe5LmwRuhbmurRv .actor-man circle,#mermaid-svg-Bbe5LmwRuhbmurRv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Bbe5LmwRuhbmurRv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 每帧 state POST /api/v1/chat/stream 入参校验(query 非空/长度/mode 枚举) get_msg(session_id) 单次读历史 set_context({report})(失败仅告警) execute_stream_with_raw(query, history_msg) 图执行(模型/工具节点) yield (state, out_text) 增量提取 reasoning(仅新增消息) SSE data:{type:"reasoning"} 熔断判断(轮数/重复调用) SSE data:{type:"final"} data:DONE append_turn(RPUSH+LTRIM+EXPIRE 原子落库)

2.3 RAG 检索链路

#mermaid-svg-7QLvAy9oq3KWmlHb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7QLvAy9oq3KWmlHb .error-icon{fill:#552222;}#mermaid-svg-7QLvAy9oq3KWmlHb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7QLvAy9oq3KWmlHb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7QLvAy9oq3KWmlHb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7QLvAy9oq3KWmlHb .marker.cross{stroke:#333333;}#mermaid-svg-7QLvAy9oq3KWmlHb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7QLvAy9oq3KWmlHb p{margin:0;}#mermaid-svg-7QLvAy9oq3KWmlHb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7QLvAy9oq3KWmlHb .cluster-label text{fill:#333;}#mermaid-svg-7QLvAy9oq3KWmlHb .cluster-label span{color:#333;}#mermaid-svg-7QLvAy9oq3KWmlHb .cluster-label span p{background-color:transparent;}#mermaid-svg-7QLvAy9oq3KWmlHb .label text,#mermaid-svg-7QLvAy9oq3KWmlHb span{fill:#333;color:#333;}#mermaid-svg-7QLvAy9oq3KWmlHb .node rect,#mermaid-svg-7QLvAy9oq3KWmlHb .node circle,#mermaid-svg-7QLvAy9oq3KWmlHb .node ellipse,#mermaid-svg-7QLvAy9oq3KWmlHb .node polygon,#mermaid-svg-7QLvAy9oq3KWmlHb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7QLvAy9oq3KWmlHb .rough-node .label text,#mermaid-svg-7QLvAy9oq3KWmlHb .node .label text,#mermaid-svg-7QLvAy9oq3KWmlHb .image-shape .label,#mermaid-svg-7QLvAy9oq3KWmlHb .icon-shape .label{text-anchor:middle;}#mermaid-svg-7QLvAy9oq3KWmlHb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7QLvAy9oq3KWmlHb .rough-node .label,#mermaid-svg-7QLvAy9oq3KWmlHb .node .label,#mermaid-svg-7QLvAy9oq3KWmlHb .image-shape .label,#mermaid-svg-7QLvAy9oq3KWmlHb .icon-shape .label{text-align:center;}#mermaid-svg-7QLvAy9oq3KWmlHb .node.clickable{cursor:pointer;}#mermaid-svg-7QLvAy9oq3KWmlHb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7QLvAy9oq3KWmlHb .arrowheadPath{fill:#333333;}#mermaid-svg-7QLvAy9oq3KWmlHb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7QLvAy9oq3KWmlHb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7QLvAy9oq3KWmlHb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7QLvAy9oq3KWmlHb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7QLvAy9oq3KWmlHb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7QLvAy9oq3KWmlHb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7QLvAy9oq3KWmlHb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7QLvAy9oq3KWmlHb .cluster text{fill:#333;}#mermaid-svg-7QLvAy9oq3KWmlHb .cluster span{color:#333;}#mermaid-svg-7QLvAy9oq3KWmlHb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7QLvAy9oq3KWmlHb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7QLvAy9oq3KWmlHb rect.text{fill:none;stroke-width:0;}#mermaid-svg-7QLvAy9oq3KWmlHb .icon-shape,#mermaid-svg-7QLvAy9oq3KWmlHb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7QLvAy9oq3KWmlHb .icon-shape p,#mermaid-svg-7QLvAy9oq3KWmlHb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7QLvAy9oq3KWmlHb .icon-shape .label rect,#mermaid-svg-7QLvAy9oq3KWmlHb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7QLvAy9oq3KWmlHb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7QLvAy9oq3KWmlHb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7QLvAy9oq3KWmlHb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ES 异常自动降级
用户问题
hybrid_search 混合检索
ES 纯向量检索 top_k*2
ES 原生 BM25 检索 top_k*2
RRF 倒数排序融合

score = Σ 1/(rank+60)
截取 top_k 候选
Rerank 精排打分
阈值过滤(≥0.3)
截断 top_n=5
LLM 生成回答

(参考资料+元数据入上下文)
Chroma 纯向量检索


3. 目录结构

复制代码
LangChain-ReAct-Agent/
├── api/
│   └── main.py                 # FastAPI 服务入口:SSE 流式接口 / 历史 / 健康检查 / CORS / 入参校验
├── agent/
│   ├── react_agent.py          # ReAct 智能体主逻辑:create_agent、流式执行、熔断防护、历史窗口截断
│   ├── tools/
│   │   ├── agent_tools.py      # 7 个业务工具(知识库问答/天气/定位/用户ID/月份/外部数据/报告信号)
│   │   ├── middleware.py       # Agent 中间件:工具监控 / 模型前日志 / 报告提示词动态切换
│   │   └── timeout_decorator.py# 工具超时装饰器(全局线程池 + 超时标记)
├── rag/
│   ├── vector_store.py         # 向量存储服务:ES(生产) / Chroma(降级)、混合检索、RRF、知识库加载
│   ├── rag_service.py          # RAG 业务服务(单例):两阶段检索 + LCEL 生成链路
│   └── rerank_service.py       # 精排服务:local(BGE-Reranker) / api(qwen3-rerank) 双模式、重试降级
├── model/
│   └── factory.py              # 模型工厂:ChatTongyi / DashScopeEmbeddings(重试配置化)
├── utils/
│   ├── config_handler.py       # YAML 配置统一加载
│   ├── session_store.py        # Redis 会话存储:LIST 原子追加、旧数据迁移、连接加固
│   ├── logger_handler.py       # 日志管理
│   ├── file_handler.py         # 文件解析(pdf/txt)与 MD5 工具
│   ├── path_tool.py            # 项目路径工具
│   └── prompt_loader.py        # 提示词加载
├── eval/
│   ├── eval_runner.py          # LLM 自动化评测:用例执行 + 裁判打分 + 汇总指标
│   ├── judge_prompt.txt        # 裁判评分提示词
│   ├── testset/agent_testset.json  # 评测用例集
│   └── output/                 # 评测报告输出目录
├── prompts/
│   ├── main_prompt.txt         # 默认系统提示词(知识库问答模式)
│   ├── rag_summarize.txt       # RAG 总结提示词(回答必须基于参考资料)
│   └── report_prompt.txt       # 报告生成提示词(报告模式动态切换)
├── config/                     # YAML 配置:agent / model / vector_store / es / redis / chroma / prompts
├── data/                       # 知识库文档(txt/pdf)+ external/records.csv 用户月度数据
├── chroma_db/                  # Chroma 持久化目录(降级向量库)
├── logs/                       # 运行日志输出目录
├── app_streamlit.py            # Streamlit 本地调试界面(可选)
├── requirements.txt            # Python 依赖清单
└── docs/                       # 技术文档

4. 模块详解

4.1 API 服务层(api/main.py

  • 全局单例 Agentagent = ReactAgent(),进程内唯一实例,复用模型与工具注册。
  • SSE 流式接口 POST /api/v1/chat/stream
    • 入参校验 → 读取历史(单次)→ Agent 流式执行 → reasoning/final 事件 → 原子落库;
    • 流内异常兜底 :生成器内部逐段 try/except(FastAPI 外层 try 覆盖不到惰性生成器内部),任何异常都保证客户端收到 final + [DONE]
    • 历史单次读history_msg 参数传入 Agent,消除"API 层与 Agent 内部各读一次 Redis"的双读不一致;
    • 空答案治理:空回答兜底为友好文案,且不把空回答写入历史。
  • reasoning 增量推送stream_mode="values" 每帧携带完整消息列表,_extract_reasoning_parts(messages, start_index) 只处理新增区间;AI 消息带工具调用时只推「调用工具 参数=...」摘要,tool 内部返回不进 reasoning,避免刷屏。
  • CORS 白名单 :环境变量 ALLOW_ORIGINS(逗号分隔),默认 http://localhost:8501;不采用 ["*"] + credentials 组合。
  • 入参校验 :query 非空、≤2000 字符;mode 仅 chat / report;非法抛 HTTPException(400)
  • 其他接口GET /api/v1/chat/history(按 session_id 取历史)、GET /health(健康检查)。

4.2 Agent 层(agent/react_agent.py

  • 智能体构建create_agent(model=chat_model, system_prompt=..., tools=[7个工具], middleware=[...]) 高层 API,由 LangGraph 编排 agent 图。

  • 流式执行agent.stream(..., stream_mode="values", config={"recursion_limit": max_recursion_limit})values 模式每帧返回完整状态字典 {"messages": [...]}

    • execute_stream(query, session_id):返回文本片段(供 Streamlit/兼容旧接口);
    • execute_stream_with_raw(query, session_id, report, history_msg):yield (state, out_text) 二元组,供 API 层做 reasoning 提取;历史优先用调用方传入的 history_msg
  • 多层防护

    防护 参数 机制
    工具迭代轮熔断 max_tool_iter=8 统计携带 tool_calls 的 AI 消息数
    总思考轮熔断 max_total_think_iter=12 统计全部新生成 AI 消息数(防纯思考死循环)
    原地打转检测 max_dup_tool_call=2 同「工具名+参数签名」重复≥2 次熔断(sort_keys=True 序列化)
    图级硬上限 max_recursion_limit = max_total_think_iter*3+10 LangGraph 节点级 recursion_limit,消费端 break 后后台仍会执行,此上限兜底终止
    生成器显式关闭 finally: gen.close() 熔断/异常/正常结束都终止底层流(传递 GeneratorExit)
  • 历史窗口truncate_conversation_history(history_msg, max_turn=8),一轮 = user+assistant 两条,截断至最近 8 轮。

4.3 工具层(agent/tools/

工具 入参 说明
rag_summarize query 两阶段 RAG 知识库问答(rag_summarize_with_rerank
get_weather city 天气信息(demo 固定文本)
get_user_location 用户所在城市(demo 随机返回)
get_user_id 默认用户 ID(配置 default_user_id,确定性)
get_current_month 报告统计月份(配置 report_default_month,确定性)
fetch_external_data user_id, month 读取 records.csv 用户月度数据,返回格式化文本
fill_context_for_report 报告模式信号:触发中间件切换报告提示词
  • 超时装饰器 @tool_timeout(timeout_sec=8) :所有工具统一 8 秒超时。实现为模块级全局线程池(max_workers=8),超时立即返回 {"__tool_timeout__": True, "msg": ...} 标记,不等待挂死线程结束;线程池复用避免每次调用新建池的开销。
  • 中间件(middleware.py
    • @wrap_tool_call monitor_tool:工具执行监控日志、超时标记转文本、异常转 observation 返回给 LLM(不静默吞错)、fill_context_for_reportruntime.context["report"]=True
    • @before_model log_before_model:模型调用前日志;
    • @dynamic_prompt report_prompt_switch:根据 runtime.context["report"] 动态切换 报告/默认 提示词。

4.4 RAG 层(rag/

VectorStoreService(vector_store.py

  • 双后端:config/vector_store.ymlvector_backend 选择 elasticsearch(生产)/ chroma
  • ES 初始化:实例化 ElasticsearchStore(字段 vector / text)、动态探测 embedding 维度、IK 分词器(ik_max_word 索引 / ik_smart 搜索)、recreate=False 默认不删已有索引;初始化整体 try/except,失败自动降级 Chroma,不阻断服务启动
  • 混合检索 hybrid_search(query, top_k):纯向量检索 top_k*2 + 原生 ES BM25 top_k*2 → 手动 RRF 融合(score = Σ 1/(rank+60))→ 截取 top_k运行期异常降级 Chroma 纯向量检索(绝不再次调用已异常的 ES)。
  • 知识库加载 load_document():按文件类型解析(txt/pdf)→ 递归字符切分(chunk_size=200, overlap=20)→ 写入当前后端;MD5 去重 (ES 用 md5_es.txt、Chroma 用 md5.txt,两套记录隔离)。
  • Chroma 惰性加载:@property chroma_db 首次访问才创建,ES 后端启动不强制加载。

RagSummarizeService(rag_service.py

  • 单例模式,全局只实例化一次。
  • 两阶段检索 retrieve_with_rerank(query)hybrid_search 粗召回(top_k=12)→ rerank_service.filter 精排 → 返回 [(text, metadata)]元数据全程透传,供展示引用来源)。
  • LCEL 生成链路:prompt_template | print_prompt | chat_model | StrOutputParser();无匹配资料时上下文置为"知识库中没有找到和该问题相关的参考资料"。
  • 提供传统单阶段 rag_summarize(retriever 纯向量)与推荐的两阶段 rag_summarize_with_rerank 两套接口。

BgeRerankService(rerank_service.py

  • 双模式:rerank_mode=api(dashscope qwen3-rerank 云端)/ local(BAAI/bge-reranker-large 本地模型,modelscope 下载 + transformers 推理,支持 cuda/cpu)。
  • 惰性加载:_ensure_loaded() 加锁 + 二次检查,首次 filter 才加载,避免 import 即阻塞。
  • API 重试:瞬时失败按 rerank_api_max_retries 重试(日志记录第 n/m 次),重试全部失败才降级(返回占位分数 1.0 的原始文档)。
  • filter(query, docs_with_meta):打分 → 阈值过滤(≥0.3)→ 截取 top_n=5;本地模型不可用时直接返回粗召回前 top_n。

4.5 模型层(model/factory.py

  • ChatModelFactoryChatTongyi(model=qwen3-max, max_retries=chat_model_max_retries)
  • EmbeddingsFactoryDashScopeEmbeddings(model=text-embedding-v4, max_retries=embedding_model_max_retries)
  • 全局单例:chat_model / embed_model 模块级实例化一次;
  • 重试次数全部配置化(config/model.yml),瞬时网络抖动/限流自动重试,不引入额外组件。

4.6 会话存储层(utils/session_store.py

  • Key 设计chat:msg:{sid}(LIST,元素为 JSON 的 {role, content})、chat:ctx:{sid}(会话上下文,report 标记)。
  • 原子追加 append_turn(sid, user_content, assistant_content) :pipeline 一次性完成 RPUSH(追加 user+assistant 两条)+ LTRIM(-200, -1)(限条数)+ EXPIRE(7 天 TTL);RPUSH 由 Redis 单线程执行,天然串行化,并发追加不互相覆盖
  • 旧数据兼容get_msgTYPE 判断;string 类型(旧数据)读出后自动迁移为 LIST------先 DELETE 再 RPUSH(真实 Redis 对 string key 执行 RPUSH 报 WRONGTYPE)。
  • 连接加固socket_connect_timeout=2 / socket_timeout=3 / socket_keepalive / health_check_interval=30 / Retry(ExponentialBackoff, retries=2);所有方法异常兜底(读取失败返回空列表,不阻断主链路)。

4.7 评测层(eval/eval_runner.py

  • 用例执行:逐条用例调用 agent.execute_stream_with_raw,提取工具调用记录(精简 JSON,避免消息对象序列化超长)。
  • LLM 裁判:手工拼接评分 prompt(规避模板渲染 bug),裁判 LLM 输出 JSON(task_success / tool_call_correct / hallucination_exist / answer_score / comment),带代码块提取与解析容错。
  • 汇总指标:任务成功率、工具调用准确率、幻觉率、回答平均分;报告输出至 eval/output/eval_report_{ts}.json
  • 健壮性:单条用例异常跳过不中断整体;裁判调用异常返回全失败默认结果。

5. 存储设计

5.1 Redis(会话存储)

Key 类型 内容 TTL
chat:msg:{sid} LIST 对话消息({role, content} JSON),最多 200 条 FIFO 7 天
chat:ctx:{sid} STRING 会话上下文 JSON(如 {"report": true}) 7 天

写入路径:append_turn(pipeline 原子) / set_context(SETEX);读取路径:get_msg(含旧数据迁移) / get_context(默认 {"report": false});清空:clear_all(sid)

5.2 Elasticsearch(生产向量库)

  • 索引名 rag_knowledge;mapping 字段:
    • text:text 类型,ik_max_word 分析器(索引)/ ik_smart(搜索),存储文档正文;
    • vector:dense_vector,维度由 embedding 模型动态探测,similarity=cosine;
    • metadata:object 类型,透传文档元数据。
  • 检索能力:向量 KNN(similarity_search(search_type="vector"))+ 原生 BM25(match 查询 + ik_max_word)+ RRF 融合。
  • 维护命令:python -m rag.vector_store(加载知识库 / 调试混合检索);reset_knowledge_store()(危险操作:重建索引 + 清空 md5 记录,重新全量导入)。

5.3 Chroma(降级向量库)

  • collection_name=agent,持久化目录 chroma_db/;ES 故障或 vector_backend=chroma 时使用;
  • MD5 去重记录独立文件 md5.txt(与 ES 的 md5_es.txt 隔离)。

6. 配置说明

6.1 config/vector_store.yml

配置项 默认值 说明
vector_backend elasticsearch 向量后端:elasticsearch / chroma
k 5 retriever 单路返回条数
retrieve_top_k 12 两阶段粗召回数量(需 > rerank top_n)
allow_knowledge_file_type txt, pdf 知识库允许的文件类型
chunk_size / chunk_overlap 200 / 20 文档切分参数
separators 句号/问号/感叹号等 切分分隔符

6.2 config/es.yml

配置项 默认值 说明
es_host http://127.0.0.1:9200 ES 服务地址
es_index_name rag_knowledge 索引名
md5_hex_es_store md5_es.txt ES 知识库 MD5 记录文件

6.3 config/model.yml

配置项 默认值 说明
chat_model_name qwen3-max 对话模型
embedding_model_name text-embedding-v4 向量模型
rerank_mode api 精排模式:api / local
rerank_api_model qwen3-rerank 云端精排模型
rerank_model_repo / local_model_dir BAAI/bge-reranker-large 本地精排模型
rerank_score_threshold 0.3 精排相关性阈值
rerank_top_n 5 精排后返回条数
chat_model_max_retries 2 对话 LLM 失败重试次数
embedding_model_max_retries 2 向量化失败重试次数
rerank_api_max_retries 2 云端精排失败重试次数

6.4 config/agent.yml

配置项 默认值 说明
external_data_path data/external/records.csv 用户月度数据文件
report_default_month 2025-12 报告统计基准月份(demo 数据集;生产改动态)
default_user_id 1001 默认用户 ID(demo 固定;生产绑定登录态)

6.5 config/redis.yml / config/chroma.yml / config/prompts.yml

  • redis.yml:host / port / password(连接参数在 session_store.py 另有超时与重试加固);
  • chroma.yml:collection_name=agent、persist_directory=chroma_db、data_path=data、md5_hex_store=md5.txt;
  • prompts.yml:三个提示词文件路径(main / rag_summarize / report)。

6.6 环境变量

变量 说明
DASHSCOPE_API_KEY 阿里云百炼 API Key(对话/向量/精排共用)
ALLOW_ORIGINS CORS 白名单(逗号分隔),默认 http://localhost:8501

7. 部署运维

7.1 环境依赖

  • Python 3.10+(本项目实测 3.12.10)
  • 外部服务:Redis、Elasticsearch(8.x,需安装 IK 分词插件)、Chroma(可选,自动降级用)
  • 依赖安装:pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 模型访问:配置 DASHSCOPE_API_KEY(对话/向量/精排全走云端,无需本地模型;local 精排模式才需要下载 BGE 模型)

7.2 启动步骤

bash 复制代码
# 1. 确保 Redis / ES 已启动
# 2. 首次使用:导入知识库(ES 后端)
python -m rag.vector_store
# 3. 启动后端服务
python -m api.main          # uvicorn api.main:app --host 0.0.0.0 --port 8000
# 4.(可选)启动 Streamlit 本地调试界面
streamlit run app_streamlit.py

7.3 健康检查与验证

bash 复制代码
curl http://localhost:8000/health                      # {"code":200,"msg":"service running"}
curl -N -X POST http://localhost:8000/api/v1/chat/stream \
  -H "Content-Type: application/json" \
  -d '{"session_id":"test1","query":"充电底座指示灯闪烁是什么原因?","mode":"chat"}'
# 返回 SSE:data:{"type":"reasoning",...} → data:{"type":"final",...} → data:[DONE]
curl "http://localhost:8000/api/v1/chat/history?session_id=test1"   # 查看已落库历史

7.4 日志与故障排查

日志统一走 utils/logger_handler.py,输出至 logs/ 目录。关键日志关键字速查:

场景 日志关键字
ES 初始化失败降级 [ES初始化]ES 不可用,自动降级使用 Chroma 向量检索
混合检索降级 [hybrid_search]降级执行Chroma纯向量检索
Rerank 降级 rerank api重试...仍失败,执行降级
工具执行 [tool monitor] 执行工具:{name} / 传入参数 / 返回结果
工具超时 [tool monitor] 【工具执行超时】
熔断 ⚠️智能体触发熔断 / ⚠️检测到原地打转
Agent 异常 [ReactAgent 顶层运行异常]
SSE 兜底 [SSE]Agent 流式执行异常 / [SSE]历史落库失败
Redis 异常 [RedisSession.*]
知识库加载 [加载知识库] / [ES初始化]索引...已存在

常见问题速查:

现象 排查方向
流中断无提示 [SSE]Agent 流式执行异常 / [ReactAgent 顶层运行异常];确认 Nginx 未缓冲 event-stream
ES 挂了服务是否可用 服务不崩,自动降级 Chroma;查 [ES初始化] / [hybrid_search] 日志确认降级生效
并发丢消息 Redis 已改 LIST 原子追加;确认新代码走 append_turn 而非 save_msg 覆盖写
报告内容漂移 确认 get_user_id / get_current_month 走配置值(确定性)
回答与历史对不上 确认 API 层单次读历史并传 history_msg(无双读)
工具卡死不返回 tool_timeout 超时标记日志;挂死线程受全局池 max_workers=8 约束

8. API 参考

8.1 POST /api/v1/chat/stream

SSE 流式对话接口。

请求体(JSON):

字段 类型 必填 说明
session_id string 会话 ID;不传则服务端生成 uuid
query string 用户问题,strip 后非空且 ≤2000 字符
mode string chat(默认)/ report

响应Content-Type: text/event-stream,事件序列:

复制代码
data:{"type":"reasoning","content":"🔍 Agent思考过程:  \n..."}
data:{"type":"final","content":"最终回答..."}
data:[DONE]
事件 说明
reasoning 思考过程增量(含工具调用摘要「调用工具 参数=...」)
final 最终回答(空答案有兜底文案;异常时有"服务暂时异常"提示)
[DONE] 流结束标记

错误码:400(入参非法:query 为空/超长、mode 非法)。

8.2 GET /api/v1/chat/history?session_id=xxx

返回 {"history": [{"role":"user","content":"..."}, {"role":"assistant","content":"..."}]}

8.3 GET /health

返回 {"code":200,"msg":"service running"},供负载均衡/探活使用。


9. 扩展指南

9.1 新增一个业务工具

  1. agent/tools/agent_tools.py 定义工具(@tool(description=...) + @tool_timeout(timeout_sec=8)),返回字符串或超时标记 dict
  2. agent/react_agent.pycreate_agent(tools=[...]) 列表中加入该工具;
  3. 如需特殊行为(如报告信号、结果转换),在 middleware.pymonitor_tool 中按 tool_name 分支处理;
  4. 在评测用例集 eval/testset/agent_testset.json 补充用例(expect_tool_list 指向新工具)。

9.2 新增知识库文档

  1. 将 txt/pdf 放入 data/ 目录;
  2. 运行 python -m rag.vector_store:MD5 去重自动跳过已入库文件,新文件切分后写入当前后端(ES/Chroma);
  3. 若需全量重建 ES 知识库:调用 reset_knowledge_store()(会清空索引,谨慎操作)。

9.3 切换模型 / 精排模式

  • 对话/向量模型:改 config/model.ymlchat_model_name / embedding_model_name(及 *_max_retries);
  • 精排:rerank_mode: api|local;api 需 DASHSCOPE_API_KEY,local 需下载 BAAI/bge-reranker-large(自动经 modelscope 下载至 local_model_dir);
  • 重试与阈值调参:rerank_api_max_retries / rerank_score_threshold / rerank_top_n

9.4 新增 HTTP 接口

api/main.py 追加 FastAPI 路由;流式接口请遵循"生成器内部逐段 try/except + 结尾 DONE"范式,保证异常可见、流不悬挂。

9.5 新增评测用例

eval/testset/agent_testset.json 添加 {case_id, case_type(qa/report), user_query, expect_tool_list, expect_key_points, forbidden_points};运行 python -m eval.eval_runner,报告输出到 eval/output/

9.6 前端接入

  • Streamlit:app_streamlit.py(本地调试);
  • 第三方系统:直接调用 /api/v1/chat/stream,按 reasoning/final/[DONE] 事件渲染;跨域需在服务端 ALLOW_ORIGINS 加入前端域名。
相关推荐
Csvn2 小时前
第 21 章 排错与调试实战
人工智能·aigc·agent
LiCoMi2 小时前
AI Agent 开发学习路线-第五课
agent·ai编程
tachibana23 小时前
什么是 Function Calling ?
数据库·人工智能·ai·llm·agent
全栈弄潮儿²⁰²⁴3 小时前
AI Agent 开发实战(7):如何接入搜索和数据库工具?
数据库·人工智能·ai·chatgpt·oracle·agent·ai编程
prog_61033 小时前
【笔记】用cursor手搓cursor(十)
人工智能·笔记·大语言模型·agent
徐龙4 小时前
给大模型装一双手:一个"能自己开 Chrome 把票买完"的 Agent,是怎么设计出来的
agent
leeyi5 小时前
命令行里的平台:df_cli 都会什么(第108篇)
后端·aigc·agent
jsjzsl26 小时前
独立自由度框架下位置自由度的本体论特征、物理现象与新技术路径
python·flask·fastapi
染指11106 小时前
111.Agent-LangChain核心组件-Tools工具
人工智能·langchain·agents