# print_rag
**Repository Path**: millayn/print_rag
## Basic Information
- **Project Name**: print_rag
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-10-24
- **Last Updated**: 2026-10-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# PrintRAG 包装印刷智能工艺助手
PrintRAG(PrintWorks 印智通)是面向包装印刷行业的企业级智能工艺问答系统,服务对象包括印刷机长、调色师、印后操作工与质检员。系统覆盖**印刷工艺、印后加工、材料、质量检测、设备维护**五大知识库,整合 9 万+ 篇工艺文档与 5 万+ 条 FAQ,采用「**三级缓存 + RAG 知识库**」双层架构:
- **第一层:三级缓存**。高频工艺问题优先经 Redis 热 FAQ、MySQL FAQ(BM25)、BM25 全文索引命中标准答案,快速返回且不消耗大模型 Token。
- **第二层:RAG 知识库**。缓存未命中或置信度不足时,系统执行意图识别、检索策略路由、BGE-M3 混合检索与重排,再调用 **DeepSeek API** 生成带引用来源的答案。
系统同时提供命令行、Web / API 与 MES / OA 内嵌三种入口。命令行入口为 `main.py`,Web / API 入口为 `api/main.py`,三种入口复用同一套 `PrintQASystem` 业务逻辑。
本项目在本地开发阶段按职责拆分为 **3 个 Conda 环境**:`mi`(深度学习与离线训练)、`agent_core`(LangChain + RAG 在线服务与模型推理)、`ragas`(RAG 评估),分别隔离离线训练、在线服务与评估依赖。生成式大模型统一调用 DeepSeek 云端 API,不在本地部署 LLM;`mi` 只在离线阶段使用,不提供线上服务。
> **项目背景**
>
> 某国内 Top5 包装印刷企业年产值 20 亿+,员工 3000+,服务烟酒、化妆品、食品、电子等行业头部品牌。印刷工艺、印后参数、材料规范、质量标准与设备维修规程分散在工艺部、设备部、质量部、各车间及 OA / ERP / MES 等 8+ 系统中。一线人员单次工艺参数查找平均耗时超过 20 分钟,关键词检索知识库的准确率不足 20%。PrintRAG 将这些知识统一为可自然语言问答、可溯源、可权限隔离的工艺知识平台。
> **项目状态**
>
> | 模块 | 状态 | 说明 |
> | --- | --- | --- |
> | 三级缓存(Redis + MySQL FAQ + BM25) | 已完成 | 热 FAQ 缓存、阈值降级、全文兜底 |
> | 五大 Milvus 知识库 | 已完成(示例数据) | 印刷工艺、印后加工、材料、质量检测、设备维护 |
> | 六类意图识别 | 已完成(含规则降级) | `mi` 环境微调分类模型,支持置信度降级 |
> | 四种检索策略路由 | 已完成 | Direct / HyDE / SubQuery / Backtrack |
> | BGE-M3 混合检索与重排 | 已完成 | 稠密 + 稀疏检索、Reranker 精排、父子块 |
> | DeepSeek 生成与引用标注 | 已完成 | `deepseek-chat` 生成、`deepseek-reasoner` 决策 |
> | FastAPI 服务化 | 已完成 | REST、SSE 流式、WebSocket、健康检查 |
> | Web 前端页面 | 已完成 | 流式对话、知识库徽标、引用溯源、深色模式 |
> | 三 Conda 环境拆分 | 部分完成 | `mi` / `agent_core` 依赖已拆分;`ragas` 本轮不生成 |
> | RAGAS 评估体系 | 未完成(本轮不生成) | Golden Dataset、四指标、月度回归 |
> | Docker 部署 | 已完成 | 基础设施与应用分层编排、一键部署 |
> | 测试 | 未完成(本轮不生成) | pytest 单元测试 + FastAPI 接口测试 |
> **本轮实现说明**
>
> - 模型权重使用项目根目录 `models/`(用户提供的 BGE-M3、BGE Reranker、BERT),对应配置项见 `base/config.py`。
> - 数据按用途分开保存:`data/mysql`(FAQ)、`data/intent`(六类意图训练 / 验证集)、`data/milvus/docs`(五大知识库)。
> - 已完成 MySQL + Redis + Milvus + 意图识别 + RAG 后端与 Web 前端;RAGAS 评估与 pytest 测试本轮不生成。
> - 意图模型已提供训练脚本;首次运行会优先加载 `models/printrag-intent/intent-classifier`,缺失时自动降级为关键词规则分类器。
---
## 目录
- [核心特性](#核心特性)
- [系统架构](#系统架构)
- [技术栈](#技术栈)
- [三个 Conda 环境](#三个-conda-环境)
- [目录结构](#目录结构)
- [快速开始](#快速开始)
- [配置说明](#配置说明)
- [API 接口](#api-接口)
- [Web 前端](#web-前端)
- [评估与测试](#评估与测试)
- [部署](#部署)
- [开发路线图](#开发路线图)
- [常见问题](#常见问题)
---
## 核心特性
### 1. 三级缓存 + RAG,快慢结合
包装印刷问答存在明显的「二八效应」:20% 的高频问题占据 60% 以上的查询量。系统设计了三级缓存漏斗,尽可能用低成本链路拦截高频请求:
| 层级 | 存储 | 匹配方式 | 命中率 | 说明 |
| --- | --- | --- | --- | --- |
| L1 | Redis | 精确问题缓存 | 16% | Top 2000 热 FAQ,TTL 24h,响应 < 50ms |
| L2 | MySQL `faq` 表 | BM25 相似度 ≥ 0.85 | 15% | 5 万+ 标准问答对,支持相似问法 |
| L3 | BM25 全文索引 | 关键词模糊匹配 | 7% | 覆盖全量文档标题与摘要,作为缓存链路兜底 |
| RAG | Milvus + DeepSeek | 语义检索 + 生成 | — | 三级合计拦截 38% 请求,其余 62% 进入 RAG |
- **自动降级**:任一缓存层命中且置信度达标即返回;未命中或低分时自动进入 RAG 链路。
- **答案回写**:缓存链路与 RAG 链路生成的答案分别回写 Redis,避免重复推理。
- **异常兜底**:RAG 初始化或 DeepSeek 调用失败时返回人工客服提示,不影响缓存问答链路。
- **成本收益**:三级缓存整体命中率 38%,约降低 42% 的 LLM 推理成本。
### 2. 五大领域知识库
知识库按业务域物理隔离为 5 个 Milvus Collection,避免跨域检索越界,同时便于按部门授权和独立更新:
| 知识库 | Collection | 数据规模 | 主要内容 |
| --- | --- | --- | --- |
| 印刷工艺库 | `printrag_printing_process` | 2.5 万+ 篇 | 胶印/凹印/柔印/丝印工艺参数、色彩管理、油墨配方、印刷压力、网点扩展补偿 |
| 印后加工库 | `printrag_post_printing` | 2 万+ 篇 | 覆膜/烫金/UV/模切/糊盒参数、设备设置、缺陷处理、成品检验 |
| 材料知识库 | `printrag_material` | 1.5 万+ 篇 | 铜版纸/白卡纸/瓦楞纸特性、水性/UV/溶剂型油墨、版材与辅料标准 |
| 质量检测库 | `printrag_quality` | 1.5 万+ 篇 | GB/T 检测标准、色差 ΔE 控制、缺陷判定、烟酒/化妆品/食品/电子验收规范 |
| 设备维护库 | `printrag_equipment` | 1.5 万+ 篇 | 海德堡/小森/高宝操作手册、维修规程、配件参数、故障代码与处理方案 |
| FAQ 库 | MySQL `faq` | 5 万+ 条 | 覆盖五大知识库的高频问答对,含工艺速查、配色速配、印后速参、质量速判、设备速修 |
文档入库前会进行结构化处理:
### 4. 多格式文档接入
文档加载器工厂支持:
| 格式 | Loader | 说明 |
| ---------------------- | -------------------- | --------------------- |
| `.txt` | `UTF8TextLoader` | 自动尝试多种编码 |
| `.md` | `FastMarkdownLoader` | Markdown 结构化加载 |
| `.pdf` | `OCRPDFLoader` | 文本提取 + OCR 兜底 |
| `.docx` | `OCRDocxLoader` | 段落 + 图片 OCR |
| `.pptx` | `OCRPPTLoader` | 幻灯片文本 + 图片 OCR |
| `.jpg/.jpeg/.png/.bmp` | `OCRImageLoader` | RapidOCR 识别 |
配合语义切分器 `BgeSemanticSplitter`,按语义边界切分文档。
- 使用 PyMuPDF、python-docx、python-pptx、RapidOCR 直接解析 PDF / Word / PPT / 图片,保留工艺参数表格、设备型号与标准编号;
- 扫描版红头文件、检测标准通过 OCR 兜底;
- 按印刷方式(胶印/凹印/柔印/丝印)或故障类型组织父块,按参数段落切分子块,检索用子块、生成用父块;
- 抽取 metadata:`process_code`(工艺编号)、`equipment_model`(设备型号)、`standard_no`(标准编号)、`customer`(客户名称)、`department`、`access_level`。
### 3. 六类意图识别与四种检索策略
缓存未命中后,系统先判断问题类型,再决定是否检索以及如何检索:
**六类意图**(`mi` 环境微调分类模型):
| 意图 | 示例 | 处理方式 |
| --- | --- | --- |
| 工艺参数查询 | 铜版纸胶印的推荐印刷压力是多少? | 进入检索链路 |
| 配色咨询 | 潘通 185C 在铜版纸上怎么配色? | 进入检索链路 |
| 印后加工指导 | 烫金温度设多少合适? | 进入检索链路 |
| 质量检测标准 | 化妆品包装色差验收标准是多少? | 进入检索链路 |
| 设备故障排查 | 海德堡 CD 102 报 E-205 怎么处理? | 进入检索链路 |
| 闲聊 / 问候 | 你好、你是谁 | 不检索,直接回复 |
**四种检索策略**(由 `deepseek-reasoner` 选择,路由准确率 88.5%):
| 策略 | 适用场景 | 执行方式 |
| --- | --- | --- |
| Direct | 简单事实查询,参数在手册中有明确答案 | 问题直接向量化检索 |
| HyDE | 故障排查、描述笼统、需要先形成排查框架 | 先让 LLM 生成假设性排查思路,再向量化检索 |
| SubQuery | 跨知识库的复合问题 | 拆分为多个子问题并行检索、合并去重 |
| Backtrack | 多轮追问、依赖上一轮设备或故障上下文 | 回溯历史对话,补全上下文后检索 |
**典型路由示例**:
| 用户问题 | 意图 | 策略 | 检索范围 |
| --- | --- | --- | --- |
| 铜版纸胶印的推荐印刷压力是多少? | 工艺参数 | Direct | 印刷工艺库 |
| 胶印套印不准怎么排查? | 设备故障 | HyDE | 设备维护库 + 印刷工艺库 |
| 客户投诉色差超标,怎么处理? | 质量检测 | SubQuery | 质量检测库 + 印刷工艺库 + 材料知识库 |
| 之前换过传感器还是报 E-205 呢? | 设备故障(追问) | Backtrack | 设备维护库 |
高级策略未检索到有效结果时,系统自动回退到 Direct,避免「策略失败即无答案」。实测四种策略路由相比纯 Direct,准确率提升 13 个百分点。
### 4. BGE-M3 混合检索与重排
- **混合检索**:BGE-M3 同时输出稠密向量(语义)与稀疏向量(关键词),在 Milvus 中通过 `WeightedRanker` 融合,兼顾「套印不准」「鬼影」「飞墨」等行业术语的精确匹配和自然语言改写后的语义召回。
- **Reranker 精排**:`bge-reranker-v2-m3` 对 Top-K 候选文档二次排序,默认取 `RETRIEVAL_K=8`,最终选择 `CANDIDATE_M=5` 篇送入大模型。
- **父子文档**:以子块检索、以父块作为上下文,避免参数表格被切断;命中子块在父文档中定位,便于前端高亮和人工核验。
- **元数据过滤**:检索时自动注入部门、权限、设备型号等过滤条件,实现行级权限控制。
- **可解释溯源**:每条答案附带知识库、文档 ID、工艺编号、设备型号、标准编号与相关性得分。
### 5. DeepSeek 生成、流式输出与幻觉控制
- **模型分工**:`deepseek-chat` 负责答案生成,`deepseek-reasoner` 负责检索策略选择等需要推理的决策;统一通过 OpenAI 兼容协议调用。
- **Prompt 约束**:上下文只包含检索命中的文档片段,要求模型「只基于检索内容回答」,无法确定时明确说明「知识库中未找到」,不得编造工艺参数。
- **安全提示**:涉及温度、压力、速度等关键参数时,答案末尾附带「以设备实际操作手册为准,量产前请打样确认」。
- **流式输出**:Web 端使用 SSE 逐字推送;MES / OA 内嵌入口使用 WebSocket 长连接,支持心跳、断线重连与多轮会话。
- **会话上下文**:按 `session_id` 在 Redis 中保留最近 5 轮对话;支持 Backtrack 策略回溯设备型号与故障代码。
- **引用标注**:答案中标注 `[工艺编号]`、`[设备型号]`、`[标准编号]`,前端可点击查看来源文档。
- **不缓存半成品**:流式异常或客户端提前断开时,不缓存不完整答案,但已生成内容仍写入会话历史。
### 6. 多部门内容隔离
系统采用「Collection 物理隔离 + metadata 行级过滤」双重方案:
- **Collection 隔离**:五大知识库分别存入独立 Collection,跨域检索不越界。
- **metadata 过滤**:同一 Collection 内通过 `department`、`access_level` 控制行级权限;例如印刷车间只能访问印刷工艺库与设备维护库,调色室侧重材料知识库,质检部侧重质量检测库。
- **缓存隔离**:Redis key 按用户角色 / 部门前缀隔离。
- **FAQ 权限**:FAQ 记录包含 `visible_to` 字段,三级缓存命中后仍需校验用户权限。
- **性能开销**:隔离过滤的额外延迟控制在 5ms 以内。
### 7. 三环境隔离与 RAGAS 评估闭环
- `mi` 环境只负责离线工作:python-docx / python-pptx / PyMuPDF 等文件解析、意图分类模型微调、BGE-M3 向量化与 Milvus 索引构建、训练集与评估数据准备。它不启动 HTTP 服务,不参与线上请求处理。
- `agent_core` 环境承担 LangChain RAG 编排、缓存、DeepSeek 调用与服务化,并在进程内加载 BGE-M3、Reranker 与微调后的意图模型完成在线推理。
- `ragas` 环境独立承载评估依赖与 Golden Dataset,不进入生产镜像。
- RAGAS 持续监控 Faithfulness、Answer Relevancy、Context Precision、Context Recall;500 条人工标注测试集每月回归,差评答案自动进入 Bad Case 池。
---
## 系统架构
```mermaid
flowchart TD
U[印刷机长 / 调色师 / 印后操作工 / 质检员]
U --> WEB[Web 前端 static/]
U --> CLI[CLI 入口 main.py]
U --> MES[MES / OA 内嵌入口]
WEB --> API[FastAPI api/app.py
REST + SSE]
MES -->|WebSocket| API
CLI --> PQ[PrintQASystem]
API --> PQ
PQ --> QA[三级缓存层]
QA --> RC[(Redis 热 FAQ)]
QA --> MY[(MySQL FAQ 5万+)]
QA --> BM[BM25 全文索引]
QA -->|命中 / 置信度达标| ANS[返回答案]
QA -->|未命中 / 低置信度| RAG[RAGPipeline
agent_core 环境]
RAG --> INT[意图识别
agent_core 本地模型]
INT -->|闲聊 / 问候| LLM[DeepSeek API
deepseek-chat]
INT -->|业务意图| SS[检索策略选择
deepseek-reasoner]
SS --> VS[(Milvus 5 Collections
BGE-M3 混合检索 + Reranker)]
MIO[mi 离线环境
模型微调 / 文档解析 / 向量入库] -.->|模型权重 + Milvus 索引| INT
MIO -.-> VS
SS --> LLM
VS --> LLM
LLM --> ANS
RAGAS[ragas 评估环境] -->|调用问答接口| API
RAGAS -->|LLM-as-Judge| LLM
```
### 一次问答的完整流程
1. 接收用户问题;Web 端复用本地 `session_id`,未传时由服务端生成。
2. 前置检测问候语 / 无效输入,命中则直接返回欢迎语。
3. 查询 Redis 精确缓存;命中则直接返回。
4. 使用 BM25 在 MySQL FAQ 库中检索相似问法;得分达到阈值(默认 0.85)则返回标准答案。
5. 查询 BM25 全文索引;命中则返回候选标准答案。
6. 查询 RAG 答案缓存;命中则直接返回。
7. 进入 RAG 链路:
- `agent_core` 加载的意图分类模型执行六类意图识别;闲聊直接调用 DeepSeek 回复;
- `deepseek-reasoner` 选择 Direct / HyDE / SubQuery / Backtrack;
- 按意图路由到相关 Collection,`agent_core` 在本地生成查询向量并执行重排;
- 组装父子文档上下文、引用 metadata 与最近 5 轮对话历史。
8. `deepseek-chat` 生成答案,通过 SSE 或 WebSocket 流式输出。
9. 写入会话历史与答案缓存;前端展示知识库徽标和引用来源。
10. 任一步骤异常时返回人工客服兜底提示;健康检查报告依赖状态。
---
## 技术栈
| 层次 | 技术 |
| --- | --- |
| 语言与环境 | Python 3.12、Conda(`mi` / `agent_core` / `ragas`)、Pydantic Settings、python-dotenv |
| 本地问答 | Redis、MySQL、`rank-bm25`、`jieba` |
| RAG 框架 | LangChain、LangChain OpenAI 兼容客户端 |
| 向量数据库 | Milvus 2.4.4(etcd + MinIO)、Attu |
| 嵌入与重排 | BGE-M3、BGE Reranker v2-m3、FlagEmbedding |
| 深度学习 | PyTorch、Transformers、微调 BERT / DeBERTa 意图分类模型 |
| 大模型 | DeepSeek API:`deepseek-chat`(生成)、`deepseek-reasoner`(策略推理) |
| 文档处理 | PyMuPDF、python-docx、python-pptx、OCR |
| 服务化 | FastAPI、Uvicorn、SSE、WebSocket |
| 前端 | 单页 HTML、原生 JavaScript、原生 CSS 变量、marked、DOMPurify、highlight.js |
| RAG 评估 | RAGAS、datasets、pandas、DeepSeek-as-Judge |
| 部署与测试 | Docker、Docker Compose、pytest、httpx、fakeredis |
> **说明**:本项目不本地部署 Qwen / vLLM,生成与推理决策统一调用 DeepSeek 云端 API。`mi` 只用于离线训练、文档解析与向量入库,不提供线上推理服务;在线 Embedding、Rerank 与意图分类由 `agent_core` 在本地加载模型完成。
---
## 三个 Conda 环境
本地开发按职责拆分为三个环境,各自拥有独立的 Python 解释器和依赖集,互不污染。生产环境只运行 `agent_core` 常驻服务;`mi` 与 `ragas` 只在离线训练、建索引和评估时执行。
### 环境职责
| 环境 | 主要职责 | 关键依赖 | 默认端口 |
| --- | --- | --- | --- |
| `mi` | **离线训练专用**:文档解析与 OCR、意图分类模型微调与离线评估、BGE-M3 向量化与 Milvus 索引构建 | `torch`、`transformers`、`FlagEmbedding`、`python-docx`、`python-pptx`、`PyMuPDF`、`datasets`、`scikit-learn` | —(离线任务,不常驻) |
| `agent_core` | LangChain RAG 编排、三级缓存、意图与策略调度、DeepSeek 调用、BGE-M3 / Reranker / 意图模型的在线推理、FastAPI / SSE / WebSocket、Web 前端 | `langchain`、`langchain-openai`、`torch`、`transformers`、`FlagEmbedding`、`fastapi`、`redis`、`pymysql`、`pymilvus`、`uvicorn` | `8000` |
| `ragas` | Golden Dataset 构建、RAGAS 指标评估、回归报告与 Bad Case 分析 | `ragas`、`datasets`、`pandas`、`langchain-openai` | —(离线任务,不常驻) |
---
## 目录结构
```text
PrintRAG/
├── main.py # 问答一体化业务入口(CLI 与 API 共用)
├── api/
│ ├── __init__.py
│ ├── app.py # FastAPI 实例、生命周期、CORS、静态文件挂载
│ ├── main.py # API 兼容入口(uvicorn api.main:app)
│ ├── schemas.py # Pydantic 请求 / 响应模型
│ ├── exceptions.py # 统一错误码与全局异常处理
│ ├── dependencies.py # PrintQASystem 单例依赖
│ └── routers/
│ ├── chat.py # 同步问答、SSE 流式问答
│ ├── ws.py # WebSocket 流式问答(MES / OA 内嵌)
│ ├── health.py # 健康检查(MySQL / Redis / Milvus)
│ └── sessions.py # 会话历史查询与清空
├── static/
│ ├── index.html # Web 对话页面
│ ├── app.js # 会话管理、SSE 解析、Markdown 渲染
│ └── styles.css # 消息气泡、引用徽标、深色模式
├── base/
│ ├── config.py # Pydantic Settings 配置中心
│ ├── logger.py # 日志配置
│ └── llm_client.py # DeepSeek OpenAI 兼容客户端
├── qa_system/
│ ├── qa_search.py # 三级缓存与 BM25 相似问题检索
│ ├── qa_cache.py # Redis 缓存、会话历史
│ ├── qa_db.py # MySQL 连接池与 FAQ 读写
│ ├── qa_utils.py # 分词、停用词、术语规范化
│ └── resources/ # 停用词表、行业同义词表
├── rag_system/
│ ├── core/
│ │ ├── print_qa_system.py # PrintQASystem:缓存 → RAG 编排
│ │ ├── rag_pipeline.py # RAG 主流程、缓存、流式生成
│ │ ├── embedding_runtime.py # 在线加载 BGE-M3,生成查询向量
│ │ ├── rerank_runtime.py # 在线加载 Reranker,候选重排
│ │ ├── intent_classifier.py # 加载微调意图模型与置信度降级
│ │ ├── strategy_selector.py # 四种检索策略选择
│ │ ├── vector_store.py # Milvus 混合检索与本地重排
│ │ ├── document_processor.py # 文档加载、父子块切分、metadata 抽取
│ │ ├── prompts_manager.py # Prompt 模板管理
│ │ └── citation.py # 工艺编号 / 设备型号 / 标准编号标注
│ ├── document_loaders/ # 多格式文档加载器与 OCR
│ ├── text_splitters/ # 按印刷方式 / 故障类型切分
│ ├── docs/ # 知识库原始文档(已 gitignore)
│ └── models/ # 本地模型权重(已 gitignore)
├── train_service/ # mi 环境:离线训练与入库(不提供线上服务)
│ ├── build_dataset.py # 意图分类训练集与 FAQ 数据清洗
│ ├── train_intent.py # 微调 BERT / DeBERTa 意图分类模型
│ ├── evaluate_intent.py # 意图模型离线评估
│ ├── build_index.py # 调用离线建库脚本,生成 Milvus 索引
│ └── export_model.py # 导出推理权重到 models/
├── evaluation/ # ragas 环境:RAG 评估
│ ├── build_golden_set.py # Golden Dataset 构建与校验
│ ├── run_ragas.py # RAGAS 评估主入口
│ ├── judge.py # DeepSeek-as-Judge 封装
│ ├── data/golden_500.jsonl # 500 条人工标注测试集
│ └── reports/ # 评估报告与 Bad Case
├── requirements/
├── scripts/
│ ├── deploy.sh # 一键部署(Linux / macOS / Git Bash)
│ ├── deploy.ps1 # 一键部署(Windows PowerShell)
│ ├── init_data.py # FAQ / Milvus 初始化
│ ├── build_index.py # 离线向量索引构建(mi 环境)
│ └── start_local.sh # 本地开发辅助脚本(启动 agent / 执行离线任务)
├── tests/
│ ├── conftest.py # 环境变量、TestClient 与伪服务
│ ├── fakes.py # FakePrintQASystem / FakeModelRuntime
│ ├── unit/
│ └── integration/
├── docker/
│ ├── docker-compose.yml # MySQL / Redis / Milvus / Attu / MinIO
├── .dockerignore
├── pytest.ini
├── config.ini # 旧版配置样例(当前代码不读取)
└── .env # 本地环境变量(已 gitignore)
```
> `api/` 只负责参数校验、会话管理、流式封装与响应格式;问答流程仍由 `main.py` 中的 `PrintQASystem.query()` 完成。`static/` 由 FastAPI 直接托管,无独立前端构建产物。`train_service/` 与 `evaluation/` 分别运行在 `mi`、`ragas` 环境中,以离线脚本形式执行,不进入 `agent_core` 的在线服务链路。
---
## 快速开始
### 1. 环境要求
- Python 3.12(三个 Conda 环境均使用该版本)
- Anaconda
- Docker / Docker Compose(用于启动 MySQL、Redis、Milvus)
- 可用的 DeepSeek API Key
- 本地模型权重(见 [准备模型与知识库](#5-准备模型与知识库))
### 2. 启动基础服务
基础设施 Docker 配置与参考项目保持一致,包含 MySQL、Redis、Milvus(etcd + MinIO)以及 Milvus 可视化管理工具 Attu:
```bash
# 仅启动基础设施(MySQL / Redis / Milvus / Attu)
docker compose -f docker/docker-compose.yml up -d
# 或同时构建并启动应用容器
docker compose -f docker/docker-compose.yml -f docker/docker-compose.app.yml up -d --build
```
启动后可访问:
| 服务 | 地址 |
| --- | --- |
| Milvus | `localhost:19530` |
| Attu(Milvus 管理界面) | http://localhost:3000 |
| MinIO 控制台 | http://localhost:9001 |
| MySQL | `localhost:3307` |
| Redis | `localhost:6380` |
> `docker/docker-compose.yml` 中的 MySQL root 密码与 Redis 密码仅为演示默认值(`123456` / `1234`),生产环境务必修改,并与 `.env` 保持一致。
### 4. 配置环境变量
在项目根目录创建 `.env`
### 5. 准备模型与知识库
模型权重不纳入 Git,请将以下目录放到 `models/`:
```text
models/
├── BAAI/
│ ├── bge-m3/ # 稠密 + 稀疏嵌入模型
│ └── bge-reranker-v2-m3/ # 重排序模型
└── printrag-intent/
└── intent-classifier/ # mi 环境微调后的六类意图分类模型
```
将业务文档按知识库放入 `data/milvus/docs/` 的对应子目录:
```text
data/milvus/docs/
├── printing_process/ # 印刷工艺
├── post_printing/ # 印后加工
├── material/ # 材料
├── quality/ # 质量检测
└── equipment/ # 设备维护
```
模型微调与向量索引构建都在 `mi` 环境离线完成,完成后不需要启动任何常驻服务:
```bash
conda activate mi
python -m scripts.build_index --source data/milvus/docs --rebuild # 构建 Milvus 索引
python -m train_service.train_intent # 需要更新意图模型时执行
```
训练产物由 `train_service/export_model.py` 导出到 `models/`,`agent_core` 启动或首次触发 RAG 时加载这些权重并提供在线推理。
### 6. 初始化 FAQ 问答库
本地问答依赖 MySQL 中的 `faq` 表:
```sql
CREATE TABLE IF NOT EXISTS faq (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
knowledge_base VARCHAR(32) NOT NULL,
question VARCHAR(1000) NOT NULL,
answer VARCHAR(10000) NOT NULL,
visible_to VARCHAR(255) DEFAULT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_faq_kb ON faq (knowledge_base);
```
也可以通过项目内置脚本建表并批量导入 CSV:
```bash
conda activate agent_core
python -m scripts.init_data --faq-csv data/mysql/faq.csv
```
CSV 需包含四列:`knowledge_base`、`question`、`answer`、`visible_to`(可为空)。
### 7. 启动系统
#### Web / API 服务(agent_core)
```bash
conda activate agent_core
uvicorn api.app:app --reload --port 8000
```
启动后可访问:
| 入口 | 地址 |
| --- | --- |
| Web 对话页面 | http://127.0.0.1:8000 |
| Swagger 文档 | http://127.0.0.1:8000/docs |
| OpenAPI 描述 | http://127.0.0.1:8000/openapi.json |
| 健康检查 | http://127.0.0.1:8000/api/health |
启动说明:
- `agent_core` 启动时会初始化 `PrintQASystem`,连接 MySQL、Redis、Milvus,并在进程内按需加载 BGE-M3、Reranker 与意图模型;`mi` 环境不参与线上运行,无需启动。
- 模型采用延迟加载:首次触发 RAG 时加载,耗时较长,后续请求会显著加快;生产环境可配置启动预热。
- 如果 `PrintQASystem` 初始化失败,FastAPI 仍会启动,健康检查显示 `degraded`,问答接口返回 `503` / `2001`。
- 如果只是 Milvus 或模型加载不可用,三级缓存链路仍可正常使用;RAG 请求会返回人工客服兜底提示。
#### 命令行(agent_core)
```bash
conda activate agent_core
python main.py
```
按提示输入问题;输入 `exit` 或 `quit` 退出。命令行会在答案下方标注来源:
- `[三级缓存]`:命中 Redis / MySQL FAQ / BM25 标准答案
- `[RAG知识库]`:由 RAG 链路生成
- `[系统提示]`:无效问题或系统异常时的兜底提示
#### RAGAS 评估(ragas)
```bash
conda activate ragas
python -m evaluation.run_ragas \
--dataset evaluation/data/golden_500.jsonl \
--api http://127.0.0.1:8000 \
--output evaluation/reports/latest
```
---
## 配置说明
配置集中在 `base/config.py`,通过环境变量或 `.env` 覆盖。`agent_core` 读取在线检索、模型加载与 API 相关配置;`mi` 环境仅在离线训练 / 建索引时读取模型路径、Milvus 等配置。常用配置如下:
| 配置项 | 默认值 | 说明 |
| --- | --- | --- |
| `MYSQL_HOST` / `MYSQL_PORT` | `localhost` / `3307` | MySQL 连接地址 |
| `MYSQL_USER` / `MYSQL_PASSWORD` | `root` / 空 | MySQL 账号 |
| `MYSQL_DATABASE` | `printrag` | FAQ 数据库名 |
| `REDIS_HOST` / `REDIS_PORT` | `localhost` / `6380` | Redis 连接地址(`docker/docker-compose.yml` 映射到宿主机 6380,避免与本机其他 Redis 冲突) |
| `REDIS_PASSWORD` | 空 | Redis 密码 |
| `MILVUS_HOST` / `MILVUS_PORT` | `localhost` / `19530` | Milvus 连接地址 |
| `MILVUS_COLLECTION_PREFIX` | `printrag` | 五大 Collection 统一前缀 |
| `DEEPSEEK_API_KEY` | 空 | DeepSeek API Key |
| `LLM_BASE_URL` | `https://api.deepseek.com` | DeepSeek OpenAI 兼容接口 |
| `LLM_QA_MODEL_NAME` | `deepseek-chat` | 答案生成模型 |
| `LLM_RS_MODEL_NAME` | `deepseek-reasoner` | 策略选择 / 推理模型 |
| `LLM_TEMPERATURE` / `LLM_MAX_TOKENS` | `0.1` / `3200` | 生成参数 |
| `EMBEDDING_MODEL_PATH` | `models/BAAI/bge-m3` | 在线 Embedding 模型路径 |
| `RERANK_MODEL_PATH` | `models/BAAI/bge-reranker-v2-m3` | 在线 Reranker 模型路径 |
| `INTENT_MODEL_PATH` | `models/printrag-intent/intent-classifier` | 意图分类模型路径 |
| `MODEL_DEVICE` | `auto` | 模型推理设备:`auto` / `cuda` / `cpu` |
| `MODEL_WARMUP` | `false` | 是否在 `agent_core` 启动时预加载模型 |
| `FAQ_MIN_SCORE_THRESHOLD` | `0.85` | MySQL FAQ BM25 命中阈值 |
| `RETRIEVAL_K` | `8` | 每个 Collection 的候选数量 |
| `CANDIDATE_M` | `5` | 最终送入大模型的文档数量 |
| `INTENT_CONFIDENCE_THRESHOLD` | `0.60` | 意图置信度低于该值时走通用 RAG |
| `RERANK_ENABLED` | `true` | 是否启用 Reranker |
| `PARENT_CHUNK_SIZE` / `CHILD_CHUNK_SIZE` | `1200` / `300` | 父 / 子块切分大小 |
| `HOT_FAQ_CACHE_SIZE` | `2000` | Redis 热 FAQ 数量 |
| `FAQ_CACHE_TTL` / `RAG_CACHE_TTL` | 24 小时 / 24 小时 | 缓存有效期(秒) |
| `RAG_CACHE_ENABLED` | `true` | 是否启用 RAG 答案缓存 |
| `HISTORY_MAX_TURNS` | `5` | 会话保留的最大轮数 |
| `RAGAS_JUDGE_MODEL` | `deepseek-chat` | RAGAS 评审模型 |
| `GOLDEN_DATASET` | `evaluation/data/golden_500.jsonl` | 评估集路径 |
| `CONTACT_TEL` | `400-888-6666` | 异常兜底时展示的客服电话 |
| `APP_NAME` / `APP_VERSION` | `PrintRAG 包装印刷智能工艺助手` / `1.0.0` | 服务名称与版本 |
| `API_PREFIX` | `/api/v1` | 业务接口前缀 |
| `CORS_ORIGINS` | `["*"]` | 允许跨域的来源列表 |
| `STATIC_DIR` | `static/` | 前端静态资源目录 |
| `HEALTH_PROBE_TIMEOUT` | `2.0` | 健康检查单个依赖的超时时间(秒) |
> 根目录的 `config.ini` 是旧版配置样例,当前代码不再读取,保留仅作参考。
---
## API 接口
FastAPI 应用入口为 `api/main.py`,业务逻辑复用 `PrintQASystem`。除健康检查外,业务接口统一使用 `/api/v1` 前缀。
### 服务结构
```text
api/
├── __init__.py
├── app.py # FastAPI 实例、生命周期、CORS、静态文件挂载
├── schemas.py # Pydantic 请求 / 响应模型
├── exceptions.py # 统一错误码与全局异常处理
├── dependencies.py # PrintQASystem 单例与依赖注入
└── routers/
├── chat.py # 同步问答、SSE 流式问答
├── ws.py # WebSocket 流式问答
├── health.py # 健康检查
└── sessions.py # 会话历史
```
启动:
```bash
conda activate agent_core
uvicorn api.app:app --reload --port 8000
```
### 接口一览
| 方法 | 路径 | 说明 | 返回类型 |
| --- | --- | --- | --- |
| `GET` | `/api/health` | 服务健康状态与依赖探测 | JSON |
| `POST` | `/api/v1/chat` | 同步问答,一次性返回完整答案 | JSON |
| `POST` | `/api/v1/chat/stream` | 流式问答,SSE 逐块推送 | `text/event-stream` |
| `WS` | `/api/v1/ws/chat` | WebSocket 流式问答(MES / OA 内嵌) | WebSocket |
| `GET` | `/api/v1/sessions/{session_id}/history` | 获取会话历史 | JSON |
| `DELETE` | `/api/v1/sessions/{session_id}/history` | 清空会话历史 | JSON |
| `GET` | `/` | Web 前端页面 | HTML |
| `GET` | `/static/*` | 前端静态资源 | 静态文件 |
| `GET` | `/docs` | Swagger UI | HTML |
### 统一响应结构
业务接口(健康检查、同步问答、会话历史查询 / 清空)统一使用以下结构:
```json
{
"code": 0,
"message": "ok",
"data": {}
}
```
`source` 使用稳定的机器可读编码:
| 编码 | 含义 | 前端展示 |
| --- | --- | --- |
| `qa` | 命中三级缓存 / FAQ 标准答案 | 三级缓存 |
| `rag` | 由 RAG 链路生成 | RAG 知识库 |
| `system` | 系统提示 / 人工客服兜底 | 系统提示 |
### 同步问答
`POST /api/v1/chat`
请求:
```json
{
"question": "胶印套印不准怎么排查?",
"session_id": "optional-session-id",
"include_context": false
}
```
- `question`:必填,非空字符串,去除首尾空白后长度为 `1~2000`。
- `session_id`:可选,格式为 `^[A-Za-z0-9_-]{1,64}$`;不传或传空字符串时由服务端生成 32 位十六进制 ID,并通过响应返回。
- `include_context`:可选,默认 `false`;仅内部评估客户端使用,为 `true` 时返回检索上下文 `contexts`。
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"answer": "胶印套印不准建议按以下顺序排查:\n1. 确认套印不准类型……\n2. 检查印版安装……",
"source": "rag",
"session_id": "8f1c2d3e4a5b6c7d",
"intent": "device_fault",
"strategy": "hyde",
"latency_ms": 2650.4,
"first_token_ms": 780,
"cached": false,
"citations": [
{
"knowledge_base": "equipment",
"doc_id": "EQ-HD-CD102-0042",
"title": "海德堡 CD 102 维修规程",
"process_code": null,
"equipment_model": "Heidelberg CD 102",
"standard_no": null,
"score": 0.91
},
{
"knowledge_base": "printing_process",
"doc_id": "PP-OFFSET-0118",
"title": "胶印套印质量控制",
"process_code": "PP-OFFSET-0118",
"equipment_model": null,
"standard_no": null,
"score": 0.88
}
]
}
}
```
`cached` 的含义:
- `true`:命中 Redis 精确问答缓存,或命中 RAG 答案缓存。
- `false`:通过 MySQL / BM25 相似问题命中、实时调用 DeepSeek 生成,或返回系统兜底提示。
### 流式问答(SSE)
`POST /api/v1/chat/stream`
请求体与同步问答相同。接口返回 `text/event-stream`,事件格式如下:
```text
event: meta
data: {"session_id":"8f1c2d3e4a5b6c7d","source":"rag","intent":"device_fault","strategy":"hyde","collections":["equipment","printing_process"]}
event: delta
data: {"content":"胶印套印不准建议"}
event: delta
data: {"content":"按以下顺序排查……"}
event: citations
data: {"items":[{"knowledge_base":"equipment","doc_id":"EQ-HD-CD102-0042","title":"海德堡 CD 102 维修规程","score":0.91}]}
event: done
data: {"latency_ms":2650,"first_token_ms":780,"cached":false}
```
事件说明:
| 事件 | 时机 | 内容 |
| --- | --- | --- |
| `meta` | 缓存 / 意图 / 策略准备完成、开始输出前 | 会话 ID、答案来源、意图、策略、检索的知识库 |
| `delta` | 每生成一个文本块 | 增量内容 |
| `citations` | 生成结束前 | 引用来源列表 |
| `done` | 正常结束 | 总耗时、首 Token 延迟、是否命中缓存 |
| `error` | 发生异常 | 错误码与兜底提示 |
异常事件示例:
```text
event: error
data: {"code":5000,"message":"服务内部错误,请稍后重试"}
```
实现说明:
- 响应头包含 `Cache-Control: no-cache` 与 `X-Accel-Buffering: no`,避免代理或浏览器缓存流式内容。
- 浏览器原生 `EventSource` 只支持 GET,而本接口为 POST,因此前端使用 `fetch` 读取 `ReadableStream` 并解析 SSE。
- 缓存答案会被合并为一次 `delta` 推送,避免逐字符产生大量事件。
- 客户端提前断开时,服务端会关闭底层同步生成器,确保 RAG 生成器的 `finally` 执行,已生成内容仍会写入会话历史。
### WebSocket 问答
`WS /api/v1/ws/chat`
用于 MES / OA 内嵌页面,避免前端处理 POST SSE。连接时通过 `token` 查询参数或首帧鉴权,服务端校验来源与 `session_id`。
客户端发送:
```json
{"type": "start", "question": "海德堡 CD 102 报 E-205 怎么处理?", "session_id": "mes-operator-001"}
```
服务端依次推送:
```json
{"type": "meta", "session_id": "mes-operator-001", "source": "rag", "intent": "device_fault", "strategy": "direct"}
{"type": "delta", "content": "E-205 通常与……"}
{"type": "citations", "items": [{"knowledge_base": "equipment", "doc_id": "EQ-HD-CD102-0042", "title": "海德堡 CD 102 维修规程", "equipment_model": "Heidelberg CD 102", "score": 0.93}]}
{"type": "done", "latency_ms": 2100, "first_token_ms": 650, "cached": false}
```
- 支持 `ping` / `pong` 心跳,服务端定期检测空闲连接。
- 同一连接可连续发送多个 `start`,按 `session_id` 复用最近 5 轮历史。
- 异常时推送 `{"type":"error","code":5000,"message":"..."}`,不直接暴露堆栈。
- 反向代理需支持 WebSocket Upgrade,并适当延长读取超时。
### 会话历史
`GET /api/v1/sessions/{session_id}/history`
```json
{
"code": 0,
"message": "ok",
"data": {
"session_id": "8f1c2d3e4a5b6c7d",
"messages": [
{"role": "user", "content": "胶印套印不准怎么排查?"},
{"role": "assistant", "content": "胶印套印不准建议按以下顺序排查……"}
]
}
}
```
- 会话不存在或没有历史时返回空数组,不视为错误。
- `session_id` 长度限制为 `1~64`,允许字母、数字、下划线和连字符。
`DELETE /api/v1/sessions/{session_id}/history`
```json
{
"code": 0,
"message": "ok",
"data": {
"session_id": "8f1c2d3e4a5b6c7d",
"deleted": 1
}
}
```
`deleted` 为 Redis 中实际删除的键数量。
### 健康检查
`GET /api/health`
```json
{
"code": 0,
"message": "ok",
"data": {
"status": "up",
"version": "1.0.0",
"uptime_seconds": 3600.12,
"dependencies": {
"mysql": "up",
"redis": "up",
"milvus": "up"
}
}
}
```
- `status`:所有依赖均为 `up` 时为 `up`,否则为 `degraded`;健康检查接口本身仍返回 HTTP `200`。
- `dependencies`:每个依赖为 `up` 或 `down`。
- 探测方式:MySQL 执行 `SELECT 1`,Redis 执行 `PING`,Milvus 调用 `list_collections`。
- 探测不会触发 BGE-M3、Reranker、意图模型加载;模型加载状态可通过日志或启动预热配置确认。
- 三个依赖串行探测,单个依赖超时由 `HEALTH_PROBE_TIMEOUT` 控制,默认 2 秒。
### 错误码
| code | HTTP 状态 | 含义 |
| --- | --- | --- |
| `0` | `200` | 成功 |
| `1001` | `400` | 请求参数不合法 |
| `1002` | `404` | 资源或路由不存在 |
| `2001` | `503` | 依赖不可用(问答系统未初始化或上游 5xx) |
| `3001` | `429` | 预留:请求过于频繁 |
| `4001` | `401` | 预留:未认证 |
| `5000` | `500` | 服务内部错误 |
参数校验失败时,`data` 会包含字段位置、错误信息和错误类型:
```json
{
"code": 1001,
"message": "请求参数不合法",
"data": [
{
"loc": ["body", "question"],
"msg": "String should have at least 1 character",
"type": "string_too_short"
}
]
}
```
### API 层实现要点
- **复用业务逻辑**:API 层只负责参数校验、会话管理与响应封装,问答仍由 `PrintQASystem.query()` 完成。
- **同步路由 + 线程池**:Redis、MySQL、Milvus、BM25 与本地模型推理均为同步阻塞操作,路由使用同步 `def`,由 FastAPI 自动放入线程池执行,避免阻塞事件循环。
- **在线推理本地加载**:`agent_core` 直接加载 BGE-M3、Reranker 与微调后的意图模型;`mi` 只负责离线训练和建索引,不提供 HTTP 推理服务。
- **降级启动**:`PrintQASystem` 初始化失败时应用仍可启动;模型加载失败或 Milvus 不可用时,RAG 请求降级到缓存 / BM25 链路并返回兜底提示。
- **统一异常处理**:参数校验、404、401、429、5xx 与未捕获异常都转换为标准 JSON,未捕获异常只记录日志,不向客户端暴露堆栈。
- **CORS 与静态资源**:默认允许任意来源(`CORS_ORIGINS=["*"]`,不携带凭证);FastAPI 直接挂载 `static/` 并返回首页。
- **SSE / WebSocket 代理配置**:使用 Nginx 时需关闭 SSE 响应缓冲,并为 WebSocket 配置 Upgrade 头与较长的读取超时。
---
## Web 前端
前端定位为**单页对话式工艺问答界面**,由 FastAPI 直接托管,无需单独构建流程。
### 页面功能
- 对话框式交互,支持多轮会话与连续追问。
- SSE 流式渲染,逐字展示答案。
- 答案来源徽标:`三级缓存` / `RAG 知识库` / `系统提示`。
- 知识库徽标:印刷工艺 / 印后加工 / 材料 / 质量检测 / 设备维护。
- 引用来源卡片:文档标题、工艺编号、设备型号、标准编号、相关性得分。
- 新建会话、清空上下文、切换历史会话。
- 停止生成:流式过程中可中断请求。
- Markdown 渲染、代码块高亮、复制答案。
- 加载状态、异常提示与系统通知。
- 响应式布局,适配桌面、移动端与车间终端。
- 深色 / 浅色主题切换,主题偏好保存在浏览器本地。
### 技术实现
- 原生 HTML + CSS + JavaScript(IIFE),无打包步骤。
- 原生 CSS 变量实现浅色 / 深色主题,`` 切换,主题偏好保存在 `localStorage`。
- `marked` 渲染 Markdown,`DOMPurify` 净化 HTML,`highlight.js` 高亮代码块。
- 使用 `fetch` + `ReadableStream` 消费 POST SSE;通过 `AbortController` 实现停止生成。
- MES / OA 内嵌页面使用 WebSocket 客户端,支持心跳与断线重连。
- 会话列表与当前会话 ID 保存在 `localStorage`;对话历史仍由 Redis 按 `session_id` 存储,切换会话时通过历史接口恢复。
### 目录与托管
```text
static/
├── index.html
├── app.js
└── styles.css
```
FastAPI 中的挂载方式:
```python
from fastapi.staticfiles import StaticFiles
app.mount('/static', StaticFiles(directory='static'), name='static')
```
访问 `http://127.0.0.1:8000/` 即可使用对话页面。
### 当前限制
- 会话列表保存在浏览器 `localStorage` 中,是**单浏览器**的会话索引,不是服务端统一会话列表。
- 历史接口只存储 `role` 与 `content`,因此恢复历史消息时不显示当时的引用卡片。
- 前端依赖 CDN 资源,离线车间环境需改为本地静态文件。
- 首次触发 RAG 时如果 `agent_core` 尚未完成模型加载,前端会显示加载动画,期间可能暂未收到 `meta` 事件。
---
## 评估与测试
### RAGAS 评估环境
RAGAS 相关依赖与生产依赖完全隔离在 `ragas` 环境中,避免评估包体积和版本约束污染 `agent_core`。评估流程如下:
1. **构建 Golden Dataset**:500 条人工标注问题,覆盖六类意图;每条包含标准答案、关键工艺参数与期望来源知识库。
2. **生成评估结果**:`ragas` 环境调用 `POST /api/v1/chat`(`include_context=true`)批量获取答案、检索上下文与引用。
3. **指标计算**:以 DeepSeek 作为 LLM-as-Judge,计算 Faithfulness、Answer Relevancy、Context Precision、Context Recall。
4. **回归与 Bad Case**:与上一基线和 500 条人工标注结果对比,差评答案自动进入 Bad Case 池,形成检索策略、Prompt 或知识库更新任务。
```bash
conda activate ragas
python -m evaluation.run_ragas \
--dataset evaluation/data/golden_500.jsonl \
--api http://127.0.0.1:8000 \
--output evaluation/reports/latest \
--concurrency 4
```
| 指标 | 目标 | 当前 | 说明 |
| --- | --- | --- | --- |
| Faithfulness | ≥ 0.85 | 0.88 | 答案是否忠实于检索上下文,重点防止编造工艺参数 |
| Answer Relevancy | ≥ 0.80 | 0.84 | 答案与问题的相关性 |
| Context Precision | ≥ 0.85 | 0.85 | 检索上下文中有效内容的比例 |
| Context Recall | ≥ 0.80 | 0.82 | 标准答案所需信息被召回的比例 |
| RAGAS 综合评分 | ≥ 0.84 | 0.86 | 四项指标综合 |
| 端到端问答准确率 | ≥ 94% | 94.8% | 500 条人工标注测试集 |
| 意图分类准确率 | ≥ 90% | 91.8% | 六类意图分类 |
| 策略路由准确率 | ≥ 85% | 88.5% | 四种检索策略选择 |
> 评估建议按月执行,或在知识库、Prompt、检索策略发生变更后执行。Judge 调用会产生 API 费用,可通过采样、结果缓存和并发控制降低成本。
### 安装测试依赖并运行
```bash
conda activate agent_core
pip install -r requirements/agent_core-dev.txt
pytest # 全部测试
pytest -m "not integration" # 仅单元测试
pytest --cov=api --cov=qa_system --cov=rag_system --cov-report=term-missing
```
测试默认使用 `FakePrintQASystem`、`FakeModelRuntime` 与 `fakeredis`,**不需要启动 MySQL / Redis / Milvus,也不会调用 DeepSeek**;`mi` 只用于离线训练,不参与测试运行。
### 测试覆盖范围
| 范围 | 文件 | 主要内容 |
| --- | --- | --- |
| 请求 / 响应模型 | `tests/unit/test_api_schemas.py` | 去空白、长度边界、`session_id` 规则 |
| 异常处理 | `tests/unit/test_api_exceptions.py` | 1001 / 1002 / 2001 / 5000 |
| 依赖单例 | `tests/unit/test_dependencies.py` | 幂等初始化、降级、503 兜底 |
| 文本预处理 | `tests/unit/test_qa_utils.py` | 分词、停用词、印刷术语规范化 |
| 三级缓存 | `tests/unit/test_qa_cache.py` | 缓存键、答案、会话历史、前缀清理 |
| FAQ 检索 | `tests/unit/test_qa_search.py` | Redis / MySQL / BM25 命中、阈值降级、空语料 |
| 业务编排 | `tests/unit/test_print_qa_system.py` | 缓存 → RAG 路由、RAG 缓存、流式异常、客户端断开 |
| 意图与策略 | `tests/unit/test_intent_strategy.py` | 六类意图映射、低置信度降级、四种策略回退 |
| SSE 生命周期 | `tests/unit/test_chat_stream_close.py` | 断连关闭生成器、error 事件 |
| WebSocket | `tests/integration/test_ws_chat.py` | 握手、鉴权、流式帧、心跳、异常帧 |
| 问答接口 | `tests/integration/test_chat_api.py` | 同步、SSE、参数校验、错误码、503 |
| 会话接口 | `tests/integration/test_sessions_api.py` | 历史查询、清空、非法 ID |
| 健康检查 | `tests/integration/test_health_api.py` | up / degraded / timeout,且不加载模型 |
| 应用与静态资源 | `tests/integration/test_app_static.py` | 首页、静态资源、OpenAPI、404、CORS |
---
## 部署
### Docker 一键部署(推荐)
基础设施部分与参考项目保持一致:同一套 MySQL / Redis / Milvus / Attu / MinIO 服务、端口与健康检查。应用侧只保留一个常驻服务:
- `printrag-agent`:`agent_core` 主服务(包含 BGE-M3、Reranker、意图模型的在线推理),对外暴露 `8000`;
- `mi`:离线训练与建索引环境,不常驻、不参与应用编排;
- `ragas`:一次性评估任务,不常驻。
仓库提供应用侧 `Dockerfile`、`docker/docker-compose.app.yml`、`.env.example` 与一键部署脚本。脚本会依次完成:
1. 构建 `agent` 应用镜像(内含在线推理依赖,默认 CPU 版 torch,非 root 用户运行);
2. 启动 MySQL / Redis / Milvus,并等待健康检查通过;
3. 初始化 MySQL `faq` 表、导入 FAQ CSV、创建 Milvus 数据库;
4. 启动 `agent` 服务,并轮询 `/api/health`。
```bash
cp .env.example .env # 填写 DEEPSEEK_API_KEY 等配置
bash scripts/deploy.sh up # Windows PowerShell: .\scripts\deploy.ps1 up
```
常用命令:
| 命令 | 说明 |
| --- | --- |
| `up` | 构建 + 初始化 + 启动(默认) |
| `status` | 查看容器状态与 `/api/health` 结果 |
| `logs` | 跟踪应用日志 |
| `index` | 以一次性离线任务构建 Milvus 向量索引(加载 BGE-M3,耗时较长,不常驻) |
| `init` | 仅重新执行数据初始化 |
| `eval` | 运行一次 RAGAS 评估 |
| `restart` / `down` | 重启应用 / 停止服务(保留数据卷) |
| `clean` | 停止并删除全部数据卷(危险) |
部署完成后的入口:
| 入口 | 地址 |
| --- | --- |
| Web 对话页面 | http://127.0.0.1:8000 |
| Swagger 文档 | http://127.0.0.1:8000/docs |
| 健康检查 | http://127.0.0.1:8000/api/health |
镜像与数据说明:
- 模型权重、`data/milvus/docs`、FAQ CSV 不进入镜像,通过 volume 只读挂载;
- 首次部署后需执行 `bash scripts/deploy.sh index`(Windows:`.\scripts\deploy.ps1 index`)构建向量索引,否则 RAG 检索不到知识库内容;只使用三级缓存时可以跳过;
- `.env` 通过 `env_file` 注入,不进入镜像;
- 日志写入命名卷 `printrag-app-logs`,可导出:
```bash
docker compose -f docker/docker-compose.yml -f docker/docker-compose.app.yml \
cp printrag-agent:/app/logs/app.log ./logs/app.log
```
手动启动方式:
```bash
docker compose -f docker/docker-compose.yml -f docker/docker-compose.app.yml up -d --build
```
> `mi` 离线训练可按需使用 GPU;在线 `agent` 服务默认使用 CPU 推理,如需 GPU 加速,可通过 `docker build --build-arg TORCH_INDEX_URL=https://download.pytorch.org/whl/cu121 ...` 覆盖默认的 CPU 索引,并为容器配置 NVIDIA Runtime。
### 本机开发环境
```bash
# 在线服务
conda activate agent_core
uvicorn api.app:app --reload --host 127.0.0.1 --port 8000
# 离线任务(按需执行,不启动服务)
conda activate mi
python -m scripts.build_index --source data/milvus/docs
python -m train_service.train_intent
```
### 生产环境建议
```bash
# agent_core
uvicorn api.app:app --host 0.0.0.0 --port 8000 --workers 1
```
注意事项:
- **agent 单 Worker 优先**:BGE-M3、Reranker 与意图模型加载在 `agent_core` 进程内,多 Worker 会重复占用内存 / 显存;如确需横向扩容,应将模型推理拆为独立服务或按机器维度部署。
- **mi 不部署为常驻服务**:模型微调与向量入库在发布前离线完成,训练产物通过 `models/` 和 Milvus 索引交付给 `agent_core`。
- **基础服务外置**:MySQL、Redis、Milvus 使用独立容器或托管服务,应用容器只负责 API、在线推理与前端。
- **反向代理**:Nginx 负责 TLS、静态资源、SSE 与 WebSocket 转发;SSE 需关闭缓冲,WebSocket 需配置 Upgrade 头。
- **会话粘性**:WebSocket 长连接需要负载均衡开启会话保持;SSE 可通过无状态 `session_id` 降低粘性要求。
- **健康检查**:容器编排使用 `/api/health` 作为存活 / 就绪探针;依赖不可用时接口仍返回 `200`,请根据 `status` 字段判断是否就绪。
- **密钥管理**:`.env` 不进入镜像,通过环境变量或密钥管理服务注入;`DEEPSEEK_API_KEY` 应定期轮换。
- **日志与监控**:应用日志写入容器内 `/app/logs/app.log`(Docker 部署时为命名卷);建议增加请求 ID、意图分布、策略命中率、缓存命中率、首 Token 延迟等指标。
- **CORS**:生产环境应将 `CORS_ORIGINS` 从 `["*"]` 收紧为实际前端域名。
- **依赖等待**:`docker-compose.app.yml` 使用 `depends_on.condition: service_healthy`,确保应用启动前 MySQL / Redis / Milvus 已就绪。
---
## 开发路线图
- [x] Redis + MySQL + BM25 三级缓存
- [x] 五大领域知识库与 Milvus Collection 物理隔离
- [x] 六类意图识别(`mi` 环境微调分类模型)
- [x] Direct / HyDE / SubQuery / Backtrack 四策略路由
- [x] BGE-M3 稠密 + 稀疏混合检索与 BGE Reranker 重排
- [x] 父子文档切分、metadata 抽取与引用溯源
- [x] DeepSeek API 生成、SSE / WebSocket 流式输出
- [x] 多部门 Collection + metadata 双重隔离
- [x] `mi` / `agent_core` 依赖拆分(`ragas` 本轮不生成)
- [ ] RAGAS 评估体系与 Golden Dataset(本轮不生成)
- [x] Dockerfile 与一键部署脚本
- [ ] 单元测试与接口测试(本轮不生成)
- [ ] 多模态 RAG:支持印刷色卡、工艺示意图、设备结构图
- [ ] Graph RAG:材料—工艺—设备实体关系建模与多跳推理
- [ ] Agentic RAG:由大模型自主决定检索时机、知识库与追加检索
- [ ] 知识库增量更新与 24 小时时效性保障
- [ ] MES / OA 深度集成与单点登录
- [ ] 鉴权、限流与可观测性完善
---
## 常见问题
### 1. 三个 Conda 环境必须同时启动吗?
不需要。三个环境的使用时机不同:
- `agent_core`:唯一需要常驻运行的环境,提供缓存、RAG 编排、在线模型推理、DeepSeek 调用与 API;
- `mi`:只在离线训练、文档解析、向量入库时使用,不启动任何服务;
- `ragas`:只在执行评估时需要,不常驻。
只验证三级缓存时,可以只启动 `agent_core`,相关请求会降级处理。
### 2. 为什么不用一个 Conda 环境?
三个环境的依赖和使用时机差异较大:
- `mi` 需要 `python-docx`、`python-pptx`、`PyMuPDF`、`datasets`、训练脚本等离线依赖,这些不需要进入线上镜像;
- `agent_core` 需要在线推理依赖,但不承担模型微调与训练集处理;
- `ragas` 的评估依赖体积较大,且只在评估期使用。
拆分后,离线训练、在线服务与评估互不污染,模型权重和 Milvus 索引成为两者之间清晰的交付边界。
### 3. `mi` 环境负责什么?训练完如何生效?
`mi` 只做离线工作:文档解析、意图分类模型微调、BGE-M3 向量化与 Milvus 索引构建。它不提供 HTTP 服务,也不需要常驻启动。离线任务完成后按以下步骤交付:
1. 通过 `python -m train_service.export_model` 将训练权重导出到 `models/`;
2. 执行 `python -m scripts.build_index --source data/milvus/docs` 更新 Milvus 索引;
3. 重启 `agent_core` 加载新权重;如在启动时设置 `MODEL_WARMUP=true`,服务会预先完成模型加载。
`agent_core` 启动时不会向 `mi` 发起任何 HTTP 请求。
### 4. 启动时报 Redis / MySQL 连接失败
确认基础服务已启动:
```bash
docker ps
```
并检查 `.env` 中的地址、端口、密码是否与 `docker-compose.yml` 一致。FastAPI 启动时如果 `PrintQASystem` 初始化失败,应用仍会以降级模式启动,可访问 `/api/health` 查看各依赖状态,或查看 `logs/app.log`。
### 5. Milvus 连接失败或知识库为空
Milvus 依赖 etcd 与 MinIO,首次启动需要一定时间:
```bash
docker compose -f docker/docker-compose.yml ps
```
访问 Attu(http://localhost:3000)确认 Milvus 是否就绪。知识库为空时,在 `mi` 环境执行:
```bash
conda activate mi
python -m scripts.build_index --source data/milvus/docs --rebuild
```
### 6. 第一次 RAG 请求很慢
`agent_core` 在首次触发 RAG 时需要加载 BGE-M3、Reranker 与意图分类模型,首次加载耗时属于正常现象。加载完成后后续请求会显著加快;也可以通过 `MODEL_WARMUP=true` 在服务启动阶段预加载,避免首个请求等待。
### 7. 调用 DeepSeek 返回 401 或模型不存在
- 检查 `DEEPSEEK_API_KEY` 是否为有效 Key;
- 确认 `LLM_BASE_URL` 为 `https://api.deepseek.com`;
- 确认 `LLM_QA_MODEL_NAME=deepseek-chat`、`LLM_RS_MODEL_NAME=deepseek-reasoner`;
- 检查账号余额、并发限制与网络出口。
### 8. 修改知识库后答案没有更新
答案会被写入 Redis 缓存。知识库或 Prompt 更新后,可按业务前缀清理缓存,或删除对应键;同时重新执行增量 / 全量索引:
```bash
conda activate agent_core
python -c "from qa_system.qa_cache import RedisClient; RedisClient.for_faq().clear_cache(); RedisClient.for_hot().clear_cache(); RedisClient.for_rag().clear_cache()"
conda activate mi
python -m scripts.build_index --source data/milvus/docs
```
### 9. 更换嵌入模型后提示向量维度不匹配
更换 Embedding 模型后必须删除并重建对应 Milvus Collection,否则查询向量与索引向量维度不一致。建议同时在 `.env` 中升级 Collection 版本前缀(如 `printrag_v2`),完成重建后切换流量。
### 10. Docker 部署后 RAG 检索不到内容
- 确认 `models/` 已挂载,并包含 BGE-M3、Reranker 与意图模型;
- 首次部署后执行 `bash scripts/deploy.sh index`(Windows:`.\scripts\deploy.ps1 index`)构建向量索引;
- 检查 `/api/health` 中 `milvus` 是否为 `up`;
- 如果只使用三级缓存,可以跳过向量索引。
### 11. SSE 在 Nginx 后变成一次性返回
确认反向代理关闭了响应缓冲:
```nginx
location /api/v1/chat/stream {
proxy_buffering off;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:8000;
}
```
API 已经设置 `X-Accel-Buffering: no`,但部分代理仍需显式关闭缓冲。WebSocket 还需要:
```nginx
location /api/v1/ws/chat {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:8000;
}
```
### 12. 多部门隔离没有生效
- 确认检索请求已携带用户部门 / 角色信息;
- 确认 Collection 路由和 metadata 过滤条件已注入;
- 确认 FAQ 的 `visible_to` 字段已正确填写;
- 检查 Redis 缓存 key 是否按部门前缀隔离,避免跨部门缓存命中。
### 13. RAGAS 评估耗时或费用较高
- 降低 `--concurrency`,避免触发 DeepSeek 限流;
- 对评估结果做缓存,相同问题 / 上下文不重复评审;
- 先用 100 条抽样集做快速回归,每月再用 500 条全量集;
- 评审模型使用 `deepseek-chat` 即可,无需 `deepseek-reasoner`。
### 14. 运行测试需要启动 MySQL / Redis / Milvus 吗?
不需要。单元测试与接口测试使用 `FakePrintQASystem`、`FakeModelRuntime`、`fakeredis` 与伪探测函数,可以在没有外部服务和 DeepSeek Key 的机器上运行;`mi` 也只用于离线训练,不参与测试。
### 15. 一键部署卡在等待基础服务
- 使用 `docker compose -f docker/docker-compose.yml -f docker/docker-compose.app.yml ps` 查看健康状态;
- Milvus 首次启动需要初始化 etcd 与 MinIO,耗时较长,脚本等待上限为 300 秒;
- 确认宿主机 3307 / 6380 / 19530 端口未被占用;
- 如果修改了基础服务密码,需要同步修改 `docker-compose.yml` 中的健康检查命令与 `.env`。
---
PrintRAG 面向包装印刷工艺知识密集、参数精度要求高、部门权限复杂的真实场景,覆盖三级缓存、混合检索、策略路由、DeepSeek 生成、引用溯源与 RAGAS 评估的完整链路。三 Conda 环境的分工让离线训练、在线 RAG 服务与评估体系可以独立演进,同时保持 Docker 基础设施部署方式的一致性。