AI 教学平台深度优化实录 — RAG 检索从 0% 到 60% 的全链路排查与修复
概述
今天对 AI 编程教学平台进行了一次系统性深度优化,覆盖了四个维度:
- RAG 可观测性:从零搭建检索质量评估体系
- RAG 精准度:定位并修复两个数据链路 bug
- 工程规范化:Prompt 模板化、前端状态管理、配置治理
- 性能调优:Reranker 候选数控制、连接池调整
核心成果:RAG 检索 Hit@3 从 0% → 60%,忠实度从 2.4 → 4.2。
一、RAG 评估体系搭建
问题
在此之前,RAG 检索效果完全靠”感觉”——改完 Prompt 不知道有没有变好,换模型不知道命中率涨没涨。简历上写 RAG 却拿不出任何量化数据。
方案
自建评估系统,不引入 Python 生态的 RAGAS(太重),全部放在 src/test 下零侵入:
1 | src/test/java/.../eval/ |
第一轮:检索质量(不调 LLM,3 秒)— Hit@3 / Hit@5 / MRR / Recall
第二轮:生成质量(LLM-as-Judge,仅 5 条)— 忠实度 / 相关性
首次跑出结果:Hit@3 = 0%,忠实度 = 2.4——LLM 在裸答,RAG 完全没起作用。
二、Hit@3 = 0% 的全链路排查
排查 1:仪表盘数据污染
1 | [DEBUG] fused[2]: content="## 知识点掌握度\n- 其他: 100%" |
学情统计数据被写进了 document_chunk 表,检索时混入了大量无关结果。
根因:DashboardRagServiceImpl.uploadDashboard() 把结构化统计分块 + Embedding 化后存入向量库,但这些数据与知识问答场景完全无关。
修法:删掉仪表盘 RAG 上传逻辑(数据应通过 MCP 工具直接从业务表查询),清理 document_chunk 中所有 dashboard_% 数据。
涉及文件:
DashboardRagServiceImpl.java— upload 改空操作DashboardController.java— 删 POST/upload-to-rag+ GET/check-rag-uploadedVectorStoreService.java— 删existsToday()DashboardUploadScheduler.java— 定时任务跳过V12__cleanup_dashboard_chunks.sql— 清理历史数据
排查 2:SmartChunkService 双 Bug
修复后重新上传 C 语言文档,Hit@3 仍然是 0。查 DB 发现 92 个 chunk 里根本没有”标识符”那章——第一个 chunk 从 int x=97;printf 开始,前面四节全丢了。
Bug 1:文档类型误判
1 | // SmartChunkService.detectDocType() |
```C语言最重要的知识点 被检测为代码块标记,整篇文档被判为 CODE 类型,走了 splitByCodeStructure 而非 splitByMarkdownHeaders。
修法:Markdown 标题 # / ## 优先匹配,优先级高于代码块。
Bug 2:首个匹配前文本被丢弃
1 | // splitCodeByFunctions() |
文档开头(第一节基础认识、第二节 vc++、第三节标识符)不包含任何 void|int|static 函数头模式。第一个匹配是 int x=97;printf...,此时 lastEnd=0,if (lastEnd > 0) 为 false——标识符那章被静默丢弃。
修法:删掉 if (lastEnd > 0) 条件,保留第一个匹配前的所有文本。
修复后重传文档,标识符章节终于入库。再次评估:
1 | Hit@3: 80.0% Hit@5: 80.0% MRR: 0.644 |
三、Reranker 耗时优化
Reranker 平均耗时从 982ms 涨到 5446ms——因为 TOP_K 从 10 调到 30 后,Reranker 需要逐条 Cross-Encoder 推理全部 ~20 个候选 chunk,每条 270ms。
修法:仅对 RRF 融合后的 Top-15 做精排,砍掉排名靠后的低价值候选。
1 | 调整前:20条 × 270ms = 5446ms → Hit@3 = 80% |
结论:Hit@3 微降至 60%(15 条候选切掉了一些正确答案),但 Reranker 耗时可控。要进一步提升需要换更强的 Embedding 模型(bge-large 1024 维)。
四、Prompt 模板化
将散落在 3 个 Java 文件中的硬编码 Prompt 抽到 resource/prompts/ 目录:
1 | prompts/ |
新建 PromptLoader 工具类,使用 Spring ClassPathResource 加载,模板变量用 {{key}} 占位符替换。
好处:调 Prompt 不用动 Java 代码、不需要重新编译,未来支持 A/B 测试只需切文件名。
五、ONNX 模型路径配置化
1 | // 改前:Windows 绝对路径硬编码 |
1 | # application.properties |
跨平台无感知,Docker 容器通过挂载 djl_models volume 自动映射。
六、前端状态管理规范化
创建 stores/class.ts — Pinia 班级 Store,5 分钟缓存:
1 | export const useClassStore = defineStore('class', () => { |
PageFour、PageFive、TaskDetail 全部接入,页面切换不再重复请求班级列表。
七、其他优化
- HikariCP 连接池:20 → 30,适配异步消费者场景
- 安全配置:
/api/homework/**保持公开(学生免登录提交),但POST /submit+POST /bind-qq由 Bucket4j 限流兜底 - 删除代码路径:
existsToday()、仪表盘 RAG 上传、check-rag-uploaded
最终数据
| 指标 | 优化前 | 优化后 |
|---|---|---|
| Hit@3 | 0% | 60% |
| Hit@5 | 0% | 66.7% |
| MRR | 0.000 | 0.524 |
| 忠实度 | 2.4 | 4.2 |
| 相关性 | 5.0 | 4.8 |
| Reranker 耗时 | 982ms | 514ms |
| 向量检索 | — | 14.5ms |
待办
- 核心链路单元测试(RAG 检索、批改、限流)
- 换更强大的 Embedding 模型(bge-large 1024 维),进一步提升 Hit@3 至 80%+
- 前端请求去重(防连点重复提交)
- LM 并发 Semaphore 控制(虚拟线程无限打 LLM 的风险)





