edurag项目讲解

项目背景

随着在线教育的快速发展,学生在学习过程中面临知识点分散、答疑响应不及时等问题。传统的 FAQ

系统仅能处理规则化问答,无法应对复杂、开放式的学科知识咨询;而通用大语言模型虽然具备较强的语义理解能力,但缺乏对特定教学领域知识的覆盖,容易产生幻觉或答案不准确。

为破解这一难题,EduRag 构建了一套融合标量检索、向量检索与大语言模型(LLM)的智能问答系统。系统以 "标量精确匹配优先,RAG 语义理解兜底" 的级联架构为核心思路,整合 MySQL + Redis + Milvus

三大存储引擎,实现对教育教学场景中高并发、多学科、多轮次问答的高效支撑。

系统架构

系统采用四层架构设计:

<table>

<tr><th>层级</th><th>技术组件</th><th>职责</th></tr>

<tr><td><b>接入层</b></td><td>FastAPI + WebSocket</td><td>提供 REST API 和流式 WebSocket 双通道,支持跨域请求与多会话管理</td></tr>

<tr><td><b>路由层</b></td><td>BERT 中文分类器 + 策略选择器</td><td>将用户查询分类为"通用对话"或"专业咨询",对专业问题自动选择四种检索增强策略之一</td></tr>

<tr><td><b>检索层</b></td><td>MySQL(BM25) + Milvus(混合检索) + Redis(缓存)</td><td>先经 Redis 缓存的 BM25 关键词检索快速匹配 FAQ 库,未命中则进入 Milvus 混合语义检索(稠密向量 +

稀疏向量),再经 BGE-Reranker 二次排序</td></tr>

<tr><td><b>生成层</b></td><td>通义千问(qwen3-max)via DashScope</td><td>结合检索上下文、对话历史与 RAG Prompt 模板,流式生成高质量答案</td></tr>

</table>

核心创新点

  1. 级联检索策略:BM25 关键词检索(快速命中 FAQ)→ Milvus 混合语义检索(稠密 + 稀疏向量)→ BGE-Reranker 重排序,兼顾速度与精度。高频问题实现毫秒级响应,复杂问题实现深度语义匹配。

  2. 四种智能检索策略:LLM 驱动的策略选择器,根据查询意图自动切换------直接检索、HyDE(假设文档嵌入)、子查询分解、回溯问题简化,显著提升不同场景下的检索召回率。

  3. 父子块存储架构:子块粒度(300 字符)用于向量匹配,父块粒度(1200 字符)用于 LLM 上下文生成,在保持检索精度的同时提供充足的语义背景。

  4. 全链路中文优化:基于微调 BERT-base-chinese 的查询意图分类器、jieba 分词增强的 BM25、中文递归语义分块器,全链路针对中文教学场景深度定制。

  5. 集成 RAGAS 评估体系:覆盖忠实度、答案相关性、上下文精确度与召回率四大指标,支持持续迭代优化。

应用场景

系统面向 IT 培训机构的教学答疑场景,覆盖 AI、Java、测试、运维、大数据等学科。学生可通过 Web 前端提交自然语言问题,系统自动区分"行政信息查询"(如费用、课程安排,由 MySQL

精确匹配快速应答)和"学科知识咨询"(如算法原理、框架对比,由 RAG 流水线结合知识库深度回答),支持多轮对话记忆与流式逐字输出,兼顾教学场景的高准确率与良好的交互体验。

以下是各模块功能解析:

1.mysql+redis查询模块

该模块主要为了实现高速查询(redis)和高相似度问答对查询(mysql+BM25);初始化redis和mysql客户端后,将MySQL中的原始query和tokenizer处理后的加载到redis中(作为val,对应key分别为origin_q和tokenizer_q),同时初始化BM25搜索模型;

关于BM25,他需要提前经过训练才能使用,本质上他是tf-idf的改良版本,添加了词频饱和度和文本长度惩罚机制(分别见超参数k,b,控制词频饱和的平滑程度/长文本惩罚力度),需要在训练集上提前计算出idf相关部分的统计参数;无论是BM25还是tf-idf,最终算出来的都是一个具体的相似度得分(由句中的若干token相似度得分相加得到),这里为了检索到高相似度的语句,对mysql中的所有问答对的query相似度得做softmax,并设定阈值;潜在问题,当两个句子的token组合相似而排序不同时,句意不同但BM25得分相近,因此softmax概率被挤占。

基于词频/相似度的查询功能实现逻辑:

对于拿到的query,(原则上先查询已有问答对,但初次使用时不存在已记录的问答对)先到redis中查找是否存在相同的origin_q,若有则直接访问MySQL获取响应的answer,并将其存为问答对,下次查询相同query时直接从redis中返回答案;

如果对于query在缓存问答对和origin_q中都未能命中,则在tokenizer_q中执行bm25检索,返回高于或等于设定阈值的结果,这里的结果指的是与输入query高度匹配的tokenizer_q对应的答案,同样将结果存入redis中。如果未找到高于阈值的结果,进入向量检索流程

2.milvus查询模块

该模块用于在MySQL和redis无查询结果的情况下,需要通过向量查询来补充检索内容;milvus模块大体上分为两个部分:离线构建知识库,在线查询;

离线构建知识库,需要将文本进行切块,向量化,并按照索引方式存入向量数据库中;这里的向量化模型使用的是bge-m3(dim=1024),分词器使用jieba;这里需要讲一下文本切块的细节,项目中使用了父子块的形式进行切分,首先需要先分别定义父块和子块的分割器(在chunk_size和overlap参数上区别),对原文本进行父块切分后,遍历父块列表进行子块的切分;

向量数据库中应当包含以下主要字段:id序号,子块内容,唯一标识(哈希),子块向量(稀疏向量/稠密向量),原数据(父块id/父块内容/文件来源/对应页码等);其中原数据在进行子块切分的过程中添加到实体entity中。

父子分块作用:仅将子块进行向量化并参与匹配,提升匹配精度;通过检索到的子块召回对饮的父块,保证上下文内容连续/完整,丰富语义;

检索阶段:

将输入的query进行向量化(必须使用和构建数据库时相同的embedding模型,bge-m3),对向量数据库中的子向量进行检索(这里需要指定metric-type,需要和构建索引时的参数保持一致),参数设置方面传入limit=k以及检索字段,这里使用bge-reranker进行混合检索,分别检索稠密向量和稀疏向量;具体代码操作中需要分别设置两个向量检索器,再通过.hybird_search()方法传入两个检索器执行混合检索;

混合检索得到的k个子文档,抽取父文档并执行去重,得到最后的候选文档(M个);原则上设置M远小于k,因为检索得到的若干个子块可能归属同一个父块,这里按照1:5设置M:k。最终返回父文档列表

3.意图识别与query改写

向量查询结果最终会作为附加上下文给到LLM进行回复生成,在此之前需要判断用户的query是否需要执行向量检索,对于通用性的问题,可以直接投喂LLM生成回复,专业问题执行rag流程,目的是为了提效;这里使用bert-base-chinese做二分类识别,训练集5000条数据,准确率能做到90+;

query改写目的是提升返回答案的质量,在用户提问模糊或冗余的情况下保证问答质量,具体分为以下四种:

hyde_prompt:要求模型基于用户视角以及给定的query生成一个或几个假设答案;

目的:基于检索结果和假设答案进行最终结果的生成,保证回答不偏移(模型能够参考假设答案)

subquery_prompt:对于较长的query,将其拆分成多个子查询,以列表的形式返回,最终遍历列表进行检索;

目的:提升检索精度,缓解模型压力

backtracking_prompt:回溯,对于复杂query或信息冗余的query,进行简单化;

目的:提升检索精度和查询效率

rag_prompt:最终传入LLM的提示词模板,需要填充query与检索得到的context

关于如何在不同场景下使用不同的prompt,这里使用LLM进行策略识别,针对用户query选择策略:直接检索,假设问题检索(hyde_),子查询检索(subquery_),回溯检索(backtracking_);

拓展补充:

数据读取模块loader:

对于输入文件夹路径,递归遍历文件夹及其中的文件,按照后缀进行解析读取;

文件类型涵盖以下类型:

.md .pdf .docx .txt .ppt .png .jpg 同时保留通用文档解析器;由于文档中可能包含文字图片表格等内容,这里使用多模态大模型来完成解析任务(使用paddleocr)。

具体流程:

对于.md文档的读取流程:保留文字和公式,读取表格内容,对于较小的表格直接保留,对于较大的表格(超过1000行)保留表头并切分为多个表格;对于图片的处理,根据业务需求决定使用ocr直接提取文字或使用多模态模型识别/总结内容;

对于.pdf文档的读取流程:按照page逐页处理,先读取原生的文本内容,再提取文档中的图片内容,根据图片大小阈值判断是否调用ocr或多模态模型进行解析,最终整合文本和图片内容;

对于.docx文档的读取流程:按照page逐页读取,直接提取文本段落内容和表格内容,其中表格内容按照单元格逐个进行提取;对于嵌入且超过设定阈值的图片进行提取

对于.pptx文档的读取流程:逐张幻灯片 (slide) 处理。将幻灯片上的形状 (shape) 按视觉顺序(top, left 坐标)排序。提取文本框 (shape.has_text_frame) 的文本。提取表格 (shape.has_table) 内所有单元格的文本。如果形状是图片 (shape.shape_type == 13),提取图片数据 (shape.image.blob),执行 OCR。如果形状是组合 (shape.shape_type == 6),递归调用 extract_text 处理其包含的子形状。

对于图像类型的文档:直接调用ocr进行提取,并将结果整合为一个字符串返回。

指标评估RAGAS:

包含四个维度的评估指标:

  1. Faithfulness(忠实度):答案是否忠于上下文。
  2. Answer Relevancy(答案相关性):答案与问题的匹配程度。
  3. Context Relevancy(上下文相关性):上下文是否与问题相关。
  4. Context Recall(上下文召回率):上下文是否包含所有必要信息。

忠实度计算:

采用**基于声明(Claim-based)**的验证方式:

Step 1 :将生成的答案拆分为若干个独立的事实陈述(claims)。 Step 2 :对每个 claim,判断它是否能从上下文中推断出来( entailment )。 Step 3

Faithfulness=能被上下文支持的 Claim 数量​/答案中总 Claim 数量

答案相关性计算:

采用逆向问题生成(Reverse Question Generation)

Step 1 :基于生成的答案,让 LLM 生成 n 个"这个答案可能回答的问题(反推生成若干问题)"。 Step 2 :计算每个生成问题与原始问题 的语义相似度(通常用嵌入向量的余弦相似度)。 Step 3

Answer Relevancy=1/n n∑i=1 ​cos_sim(Qoriginal​,Qgeneratedi​​)

上下文相关性计算:

Step 1 :将检索到的上下文按句子切分。 Step 2 :对每个句子,判断它是否与问题相关(Relevant / Irrelevant)。 Step 3

Context Relevancy=相关句子数​/上下文总句子数

上下文召回率计算:

Step 1 :需要有一个标准答案(Ground Truth) 作为参照。 Step 2 :将标准答案拆分为若干个独立的事实陈述(claims)。 Step 3 :检查每个 claim 是否能在检索到的上下文中找到支持。 Step 4

Context Recall=能在上下文中找到支持的 Claim 数量​/标准答案中的总 Claim 数量

指标 评估对象 是否需要 Ground Truth 核心问法 高分代表
Faithfulness 生成答案 ❌ 不需要 "答案有没有胡说?" 无幻觉
Answer Relevancy 生成答案 ❌ 不需要 "答案有没有答非所问?" 切题
Context Relevancy 检索上下文 ❌ 不需要 "上下文里有多少垃圾?" 检索精准
Context Recall 检索上下文 需要 "上下文漏没漏关键信息?" 检索全面