一、项目整体理解这是一个生产级的多层 RAG 问答系统核心特点多级检索降级Redis 缓存 → BM25 关键词检索 → Milvus 向量检索层层递进多轮对话支持MySQL 存储会话历史自动保留最近5轮流式输出最终答案逐字返回用户体验好模块化设计文档加载、文本切分、向量存储、检索策略各司其职二、配置与基础模块Config 类设计配置类的核心是多层 fallback 机制self.MYSQL_HOST self.config.get(mysql, host, fallbacklocalhost)配置文件缺失时使用默认值不会因为配置缺失而崩溃使用eval处理列表配置如valid_sources但需注意安全风险路径自动推导无论从哪里导入都能找到config.ini关键配置参数配置项值说明MySQL port33060非默认端口注意parent_chunk_size1200父块较大保留完整上下文child_chunk_size300子块小检索精准candidate_m2最终只取2个文档给 LLMLogger 设计if not logger.handlers: # 避免重复添加防止多次导入时添加多个 handler避免日志重复打印。三、文档处理模块父子文档切分策略这是整个项目的核心设计之一 原始文档 (3000字) │ ▼ parent_splitter (chunk_size1200) ┌─────────────────────────────────────────────────────────────┐ │ Parent Chunk 0 (1200字) parent_id: doc_0_parent_0 │ │ │ │ │ ▼ child_splitter (chunk_size300) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │ │ Child 0_0 │ │ Child 0_1 │ │ Child 0_2 │ │ Child 0_3 │ │ │ │ parent_id │ │ parent_id │ │ parent_id │ │ parent_id │ │ │ │ parent_ │ │ parent_ │ │ parent_ │ │ parent_ │ │ │ │ content │ │ content │ │ content │ │ content │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ └────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ 设计思想子块入库用于检索粒度小匹配精准父块输出作为 LLM 上下文内容完整语义连贯子块中直接存储parent_content避免向量库二次查库中文优化切分器self._separators [ \n\n, # 段落分隔 \n, # 换行 。||, # 中文句尾标点正则支持多匹配 \.\s|\!\s|\?\s, # 英文句尾空格 |;\s, # 分号 |,\s # 逗号 ]相比 LangChain 原生切分器这个版本对中文更友好能够按语义边界进行切分。文档加载器总结加载器文字表格图片OCR特殊处理WordLoader✅✅✅内嵌图片OCRPDFLoader✅❌✅旋转校正、阈值过滤PPTLoader✅✅✅组合递归、形状排序图片加载器❌❌✅纯OCRPDF 图片过滤逻辑只提取占据页面 60% 以上面积的大图小图直接丢弃。这是一个性能优先的设计决策。四、向量存储模块MilvusSchema 设计schema.add_field(id, VARCHAR, is_primaryTrue) # MD5 哈希作为主键 schema.add_field(text, VARCHAR) # 子块文本内容 schema.add_field(dense_vector, FLOAT_VECTOR, dim1024) # 稠密向量 schema.add_field(sparse_vector, SPARSE_FLOAT_VECTOR) # 稀疏向量 schema.add_field(parent_id, VARCHAR) # 父块 ID溯源 schema.add_field(parent_content, VARCHAR) # 父块内容避免二次查询 schema.add_field(source, VARCHAR) # 学科过滤字段混合检索原理稠密向量由 BGE-M3 模型生成捕获语义相似性稀疏向量也是由 BGE-M3 生成是学习出来的关键词权重分布# 稀疏向量示例概念示意 sparse_vector { token_id_1: 0.85, # 人工智能 的权重 token_id_2: 0.62, # 课程 的权重 token_id_3: 0.31, # 介绍 的权重 }两阶段排序阶段方法目的第一阶段WeightedRanker 加权合并快速筛选从百万级降到百级第二阶段BGE-Reranker 重排序精排从百级降到最终 K 个为什么需要两步加权合并速度快毫秒级适合初筛Reranker精度高秒级适合精排索引类型选择索引类型适用场景配置IVF_FLAT稠密向量追求精度nlist128SPARSE_INVERTED_INDEX稀疏向量专用drop_ratio_build0.2加权排序权重ranker WeightedRanker(1.0, 0.7) # 稠密:稀疏 1.0:0.7教育问答中语义理解比关键词匹配更重要所以稠密向量权重更高。多路召回的本质用户 Query → 稠密向量检索 稀疏向量检索 → 加权合并 → Rerank 精排 → 最终结果你的理解多路召回指的是 bge-m3 的稠密向量和稀疏向量分别做近似值得分然后使用 rerank 来做统一排序修正rerank 是在加权合并之后做的不是替代加权合并。加权合并是快速初筛rerank 是精排。五、查询分类器BERT 微调核心流程检查训练好的模型路径存在则加载不存在则用基座模型初始化从基座模型加载分词器训练过程加载数据文件区分特征值和标签值8:2 原则划分训练集和测试集数据预处理分词器将文本 ID 化标签数字化0/1Dataset 封装继承torch.utils.data.DatasetTrainingArguments 配置训练参数Trainer 训练 保存模型评估打印分类报告和混淆矩阵预测输入 query → 分词 → 模型 → logits → argmax → 返回类别标签映射self.label_map {通用知识: 0, 专业咨询: 1}训练参数说明参数值说明num_train_epochs3训练轮次per_device_batch_size8批次大小warmup_steps20学习率预热步数weight_decay0.01权重衰减fp16False禁用混合精度CPU 训练六、策略选择器LLM 路由四种检索策略策略适用场景示例直接检索意图明确问具体信息AI 学科学费是多少子查询检索涉及多个实体/方面比较 Milvus 和 Zilliz Cloud 的优缺点HyDE抽象直接检索效果差人工智能在教育领域的应用回溯问题检索复杂需简化100亿条数据存 Milvus 可以吗设计思路用 LLM 做策略选择而不是硬编码规则将 4 种策略 示例拼成 Prompt让 LLM 判断当前 query 属于哪种策略返回策略名称字符串优点灵活新增策略只需改 Prompt缺点不确定性增加一次 LLM 调用七、BM25 检索模块核心流程从 Redis 获取缓存answer:{query}命中则直接返回未命中则进行 BM25 打分query 分词bm25.get_scores()计算原始分数_softmax()归一化到 [0,1]argmax()取最高分索引阈值判断threshold0.85best_score 0.85MySQL 查答案 → 缓存到 Redis → 返回best_score 0.85返回(None, True)→ 降级到 RAGSoftmax 归一化def _softmax(self, scores): exp_scores np.exp(scores - np.max(scores)) # 减去最大值防止溢出 return exp_scores / exp_scores.sum()为什么需要BM25 原始分数范围很大几到几百归一化到 [0,1] 后可以用统一的阈值0.85返回值含义返回值含义answer, False找到答案不需要 RAGNone, True未找到需要降级到 RAGNone, False无效查询八、RAG 主流程完整调用链路用户 Query │ ▼ 1. 获取历史对话MySQLsession_id 隔离最近5轮 │ ▼ 2. BERT 分类器判断 ├── 通用知识 → 直接 LLM 回答 └── 专业咨询 → 进入 RAG 流程 │ ▼ 3. 策略选择器LLM 路由→ 选择检索策略 │ ├── 直接检索原始 query ├── 回溯问题检索LLM 简化问题 ├── 子查询检索LLM 拆分成多个子查询 └── HyDELLM 生成假设答案 │ ▼ 4. Milvus 混合检索 重排序 → 返回父块 │ ▼ 5. 构建 Promptcontext history query │ ▼ 6. LLM 流式生成最终答案 │ ▼ 7. 更新 MySQL 对话历史保留最近5轮删除旧记录流式输出设计def generate_answer(self, query, source_filterNone, historyNone): # ... 检索逻辑 ... # 最终答案使用流式输出 for token in self.llm(prompt_input): yield token返回格式(content, is_complete)is_completeFalse流式输出中is_completeTrue输出完成九、会话管理会话表结构CREATE TABLE conversations ( id INT AUTO_INCREMENT PRIMARY KEY, session_id VARCHAR(36) NOT NULL, question TEXT NOT NULL, answer TEXT NOT NULL, timestamp DATETIME NOT NULL, INDEX idx_session_id (session_id) );历史获取逻辑# SQL 查询最新5条最新在前 SELECT question, answer FROM conversations WHERE session_id %s ORDER BY timestamp DESC LIMIT 5 # 反转恢复时间正序 history history[::-1] # 格式化供 LLM 理解 history_text \n.join([fQ:{h[question]}\nA:{h[answer]} for h in history])历史更新与清理-- 插入新记录 INSERT INTO conversations (session_id, question, answer, timestamp) VALUES (%s, %s, %s, NOW()) -- 删除超出5轮的旧记录嵌套子查询绕过 MySQL 限制 DELETE FROM conversations WHERE session_id %s AND id NOT IN ( SELECT id FROM ( SELECT id FROM conversations WHERE session_id %s ORDER BY timestamp DESC LIMIT 5 ) AS sub )十、集成问答系统初始化组件self.logger logger self.config Config() self.mysql_client MySQLClient() self.redis_client RedisClient() self.bm25_search BM25Search(self.redis_client, self.mysql_client) self.vector_store VectorStore() self.rag_system RAGSystem(self.vector_store, self.call_dashscope) self.init_conversation_table() # 创建 conversations 表三级检索降级优先级方法触发条件速度1Redis 缓存answer:{query}存在最快2BM25 MySQL相似度 ≥ 0.85快3RAG (Milvus)BM25 未命中慢但全面流式返回格式# 命中 BM25 yield answer, True # RAG 流式 for token in rag_system.generate_answer(...): yield token, False yield , True # 未找到 yield 未找到答案, True十一、架构设计亮点总结1. 模块化设计每个组件职责单一可独立替换和升级工厂模式 策略模式处理多种文档格式2. 检索策略稠密向量语义 稀疏向量关键词互补优势两阶段排序加权合并快速初筛 Cross-Encoder 精排LLM 路由智能选择检索策略3. 工程实现父子文档策略子块精准检索 父块完整上下文多格式文档加载PDF/Word/PPT/图片统一接口智能 OCR只对大图进行 OCR避免性能浪费流式输出逐字返回用户体验好自动历史清理只保留最近5轮控制上下文长度两级缓存Redis 缓存答案和 BM25 索引4. 用户体验多会话支持每个 session_id 独立历史学科过滤可按学科限定检索范围历史追溯所有问答都有记录十二、数据流总结data/*.pdf/.docx/.pptx │ ▼ 文档加载器OCRPDFLoader/OCRDOCLoader/OCRPPTLoader Document 对象 │ ▼ document_processor.py父子切分 Child Chunks子块入库父块内容存 metadata │ ▼ vector_store.pyBGE-M3 嵌入 Milvus 存储 Milvus 向量库 │ ▼ new_main.py用户查询入口 │ ├── Redis 缓存命中 → 直接返回 │ ├── BM25 命中≥0.85→ MySQL 查答案 → 缓存 → 返回 │ └── BM25 未命中 → RAG 流程 │ ├── QueryClassifierBERT→ 分类 ├── StrategySelectorLLM→ 选策略 ├── VectorStoreMilvus→ 混合检索 重排序 └── call_llm_streamOllama→ 流式输出十三、核心设计决策记录为什么要用父子文档子块检索粒度小匹配精准父块输出内容完整语义连贯子块中直接存储parent_content避免向量库二次查库为什么要用混合检索稠密向量理解语义汽车和轿车在语义空间中是接近的稀疏向量匹配关键词BGE-M3 学习出来的权重分布两者互补覆盖更多检索场景为什么要用两阶段排序加权合并速度快毫秒级适合从百万级降到百级Reranker精度高秒级适合从百级降到最终 K 个不能在百万级文档上直接跑 Reranker太慢为什么要用 BERT 分类器快速识别通用问题如今天天气怎么样避免不必要的 RAG 调用节省成本和时间轻量级CPU 即可运行为什么要用 LLM 做策略选择灵活新增策略只需改 Prompt智能利用 LLM 的语义理解能力低成本只需一次小模型调用为什么要做三级检索降级级别方法成本速度1Redis 缓存极低最快2BM25 MySQL低快3Milvus RAG高慢常见问题直接命中缓存或 BM25复杂问题才走 RAG平衡成本和效果。十四、运行与部署启动命令# 命令行模式 python new_main.py # API 服务模式 python app.py # WebSocket HTTP 服务 python app.py # 端口 8001环境要求Python 3.10MySQL 8.0Redis 7.0Milvus 2.4Ollama本地 LLM依赖安装pip install -r requirements.txt配置文件config.ini需配置MySQL、Redis、Milvus、LLM API、检索参数、应用参数十五、总结这个 RAG 项目从文档加载到向量检索从 BM25 降级到多轮对话从流式输出到 API 封装覆盖了一个完整问答系统的所有环节。核心收获RAG 不是单一技术是系统工程需要综合考虑文档处理、向量检索、模型选型、系统架构、用户体验等多个维度分层设计很重要三级检索降级缓存 → BM25 → RAG在保证效果的同时控制了成本父子文档是精妙设计子块精准检索 父块完整上下文兼顾精度和完整性模块化让系统可扩展每个组件职责单一可独立替换和升级用户体验不容忽视流式输出、多会话支持、历史记忆这些细节决定了系统的可用性文档整理完成记录了今天所有的核心理解和技术要点。