pgvector知识库问答准确率从42%到78%的落地全记录

Feng 83 阅读 AI开发

版本组合先摆出来:Spring AI 1.1.0、Boot 3.5.x、JDK 17、PostgreSQL 16 加 pgvector 0.8.0,嵌入模型用 DashScope 的 text-embedding-v3,输出 1024 维。这套配置照着搭,基本能一次跑通。

全文检索撞不上的那句话

行政部提的需求很朴素:员工老在群里问报销单丢了怎么补,行政每月重复回答十几遍,想做个机器人自动答疑。

第一反应是上 ES。结果搜“报销单丢了怎么补”返回空。文档里明明写着发票遗失后的补录流程,但分词之后拿“报销”“丢失”“补办”去倒排索引里硬撞,撞不上就是撞不上。

RAG 的本质是让模型开卷考试:先从知识库检索相关段落,塞进提示词,再让模型作答。Embedding 给每段文字算一个指纹向量,意思相近的指纹距离更近;向量库按指纹距离做语义检索,可以理解成“按意思查的 ES”。

那为什么不微调?制度文档月月改版,微调一轮的钱够重建知识库十遍,而且每次改版都得重来。RAG 把知识放在外面,文档更新后重新导入即生效,模型参数不用动。

为什么是 pgvector,不是 Milvus、ES 或 Redis

候选有四家。Milvus 适合十亿级场景,但我们的量级不匹配;ES 8.x 要升集群版本,风险太大;Redis 全量文档进内存,成本过高;pgvector 在现有 PostgreSQL 上加个扩展就能用,事务、备份、权限全部复用。

生产库本来就是 PostgreSQL 16,装上 pgvector 0.8.0 扩展、加一个向量列就完成。单次查询实测 50 毫秒以内(百万级文档块)。量级到几十万块时补建 HNSW 索引:

原则是新组件能少则少,每个中间件背后都是一台要维护的机器。能加一列解决的问题,别新起中间件。

依赖、配置与入库流程

依赖三个:spring-ai-starter-model-openai、spring-ai-starter-vector-store-pgvector、spring-ai-tika-document-reader。

配置上有个分工:聊天走 DeepSeek 控制成本,向量走阿里 DashScope 的 text-embedding-v3 满足数据不出境要求,base-url 和 api-key 分别配置。

文档入库用 TokenTextSplitter 分块,每块 800 token,碎块并入邻块,然后 vectorStore.add() 向量化并写入 PostgreSQL。文档改版场景用先删后插,避免新旧版本混检。

问答接口采用 SSE 流式返回,模型每生成一小段就推给前端,用户第一秒就能看到输出。总耗时不变,体验改善明显。

第一版只答对 66%,问题出在两处

测试组用 50 道真题验证,第一版答对 33 道,66%。复盘下来两个核心问题。

分块切断语义。默认固定长度切割导致表格被截断,召回块只有半截文字。解决办法是调整分块参数,800 token 目标、碎块并入邻块,表格密集的文档按章节手动切分。

未设相似度阈值。Spring AI 默认把 topK 块全部送给模型,相关度 0.31 的低质量块混进去,模型直接被带偏。加上 similarityThreshold(0.5) 之后低相关块被过滤,宁缺毋滥。

优化后同一套题答对 39 道,78%;对照组全文检索只有 21 道,42%。剩余 11 道错题里,6 道是口语缩写问题,比如员工问“十三薪”但文档写的是“年终双薪”,5 道是正确段落排名没进 topK,可以靠混合检索加 Rerank 重排解决。

上线前必须补的四件事

超时熔断。Resilience4j 配 8 秒超时加熔断,错误率过半自动熔断,半小时后半开探测。

Token 统计。按接口和部门维度埋点,单次问答约 2800 token,乘上全员使用频次就是真实成本。

权限控制。metadata 挂部门标签,检索时按登录态过滤,薪酬考勤等敏感内容按权限开放。

出处标注。回答末尾附【出处:文档名】,员工可点开原文核对,信任锚点从 AI 转移到出处。

几个容易踩的坑

相似度阈值不是越低越好。设成 0 等于没设,设太高会把正确但表述差异大的段落一起滤掉,0.5 是这套语料下的经验值,换语料要重新标定。

HNSW 索引不是建了就一定快。数据量小的时候顺序扫描反而更稳,几十万块以上再建,建索引期间写入会变慢,最好在低峰期做。

先删后插要包在事务里,否则删除成功、插入失败会出现文档真空期,用户提问直接查不到内容。

流式返回和熔断要一起考虑。SSE 连接建立后超时判定跟普通请求不一样,别让熔断器把正常的长连接误杀。

Feng
这位作者很神秘,还没有填写简介。