RAG 检索增强生成系统升级实录——从哈希伪向量到 ONNX 真实嵌入
背景
为「作业批改系统」的 OneBot QQ Bot 接入 RAG(检索增强生成),让学生能通过 QQ 直接向知识库文档提问。插件侧配置已开启,但后端 RAG 接口返回异常。
时间线
| 时间 | 事件 |
|---|---|
| 10:09 | 发现 RAG 接口返回 {"enhancedMessage":"????","hasContext":false} |
| 11:00 | 定位根因:SIMILARITY_THRESHOLD = 0.75 对哈希向量过高 |
| 11:13 | 实现 RRF 双路混合检索 |
| 11:27 | 修复 SQL 拼接空格/LIMIT 类型匹配 |
| 11:32 | 修复 LIMIT 参数类型 |
| 11:50 | Gateway 重启导致 OneBot 断开 |
| 14:59 | 升级到 ONNX 真实嵌入模型 bge-small-zh-v1.5 |
| 15:27 | 模型下载完成,512 维向量就绪 |
| 15:31 | pgvector 列维度不匹配报错,改表后修复 |
| 15:40 | 检索精度被 C 语言文档淹没,Top-K 从 3 提到 6 |
问题一:RAG 返回空结果
现象
调用 POST /api/onebot/rag 返回:
1 | {"enhancedMessage":"????","hasContext":false} |
根因
DocumentServiceImpl.java 中设置了 SIMILARITY_THRESHOLD = 0.75,但 EmbeddingService 使用的是哈希词频稀疏向量:
1 | // 旧 EmbeddingService - 伪向量 |
这种向量的余弦相似度通常在 0.05 ~ 0.3 之间,0.75 的阈值导致 100% 结果被过滤。
修复
去掉硬阈值,改用 RRF 双路融合排序(见下文)。
问题二:RRF 混合检索实现
方案
采用业界通用的 Reciprocal Rank Fusion(RRF)双路召回:
1 | 查询 |
RRF 公式
1 | RRF_score(doc) = Σ 1 / (k + rank_i) |
某 chunk 在语义路排第 1、关键词路排第 5:
1 | RRF = 1/(60+1) + 1/(60+5) = 0.0164 + 0.0154 = 0.0318 |
问题三:SQL 拼接坑
坑 1:Java Text Block 吃掉行尾空格
1 | sql.append(""" |
修:Java 15+ 用 \s 显式保留空格:
1 | sql.append(""" |
坑 2:PostgreSQL LIMIT 类型
1 | List<String> params = new ArrayList<>(); |
修:改成 List<Object>,直接传 int:
1 | List<Object> params = new ArrayList<>(); |
问题四:哈希向量升级到 ONNX 真实嵌入
方案
- 模型:BAAI/bge-small-zh-v1.5(中文优化,512 维)
- 引擎:DJL ONNX Runtime(纯 CPU,无需 PyTorch)
- 下载:hf-mirror.com(国内镜像),~90MB
- 本地路径:
%USERPROFILE%\.djl\models\BAAI_bge-small-zh-v1.5\
BGE Query 指令前缀:
1 | // Query 侧:加指令前缀 |
这是 BGE 官方推荐的最佳实践,前缀能提升检索精度 5-10%。
问题五:pgvector 维度不匹配
现象
1 | expected 384 dimensions, not 512 |
修复
1 | ALTER TABLE document_chunk ALTER COLUMN embedding_vec TYPE vector(512); |
注意:旧文档的哈希向量(384 维)与 ONNX 向量(512 维)语义空间完全不同,需删除旧文档并重新上传。
问题六:检索精度被噪音淹没
数据库中有 94 个 C 语言文档 chunk + 6 个仪表盘 chunk,比例 15:1。即使 ONNX 模型精度足够,Top-K=3 时,仪表盘的有效数据被 C 语言 chunk 挤出。
1 | // OnebotRagController.java |
最终架构
1 | QQ 消息 |
经验教训
- Java Text Block 的
\s陷阱——行尾空格默认被吃掉,SQL 拼接务必用\s或改用普通字符串 - PostgreSQL
LIMIT ?参数必须是 int——String.valueOf()会导致类型错误 - BGE 模型 query/document 侧需要不同处理——query 加指令前缀,document 不加
- 知识库噪音比嵌入精度更致命——94 个不相关 chunk 足以淹没 6 个相关 chunk,管好入库质量比调参更重要
- Gateway 重启 = OneBot 重连——确保 Napcat 进程在 Gateway 之前启动,或配置自动拉起
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 LKL-ZREO!
评论






