行业资讯

Java 程序员的 AI 进化论 | 用 Java 搭 RAG 知识库,PgVector 实战踩坑

发布时间:2026/8/14 13:15:03
Java 程序员的 AI 进化论 | 用 Java 搭 RAG 知识库,PgVector 实战踩坑 团队知识库搜文档一直用 ElasticSearch 关键词匹配搜怎么申请测试环境返回一堆带测试和环境的无关页面真正那篇操作手册排到第 8 页。上个月我试着用 RAG 重构了搜索效果立竿见影——语义搜数据库连不上怎么办能直接把MySQL 连接超时排查那篇顶上来。今天把整套 Spring Boot PgVector 的实现、踩的坑、还有哪些参数最影响效果讲清楚。说个真实数据重构前工单系统里文档搜不到相关的转人工请求每周 40 条重构后降到 8 条左右。这不是模型多聪明是检索方式从字面对得上变成了意思对得上。一、为什么不用现成大模型直接答先说清楚为什么非得搞 RAG而不是直接把问题丢给 GPT 让它编答案。大模型有两个硬伤训练数据有截止日期你公司内部的文档它根本没见过它还会一本正经地胡说八道编一个不存在的 API 路径你都发现不了。RAG 的思路简单先把知识切成一截一截存进向量库用户提问时把问题也转成向量找最相似的几截文档拼进 Prompt 让大模型基于这些文档回答。这样回答有据可依还能带上出处链接。方案回答质量数据时效是否需要私有数据成本直接问大模型中爱编截止训练日不支持低关键词搜索ES低漏语义实时支持中RAG 向量检索高有依据实时支持中我选 RAG 不是因为它时髦是因为它解决了私有数据 不准编这两个刚需。ES 关键词匹配在语义理解上确实差一截。ES 的问题不是搜不准是它只能做字面匹配。用户搜服务启动报端口被占用ES 只会找含端口占用的文档但团队里那篇叫Address already in use 排查的手册因为没写中文端口被占用五个字死活搜不出来。这种中英文混写、同义表述的内部文档正是 RAG 的强项——它理解的是语义不是关键词。二、整体架构和依赖先说架构四步一条龙文档入库 Markdown/Word → 切分 → Embedding → PgVector 表 用户提问 问题 → Embedding → 相似度检索 topK → 拼 Prompt → 大模型回答入库是一次性批量跑的提问是每次实时走的。注意 Embedding 模型在入库和查询时必须用同一个不然向量空间不一致检索出来的全是噪声。这个坑我在 4.1 细说。整个链路就四步文档切分 → 向量化Embedding→ 存入 PgVector → 查询时相似度检索 拼 Prompt。PgVector 是 PostgreSQL 的向量插件不用单独维护一个向量数据库对我们已经在用 PG 的团队来说零额外运维。2.1 Maven 依赖Spring Boot 3.x PgVector依赖直接看表公众号不渲染 XML就不贴 pom 了依赖groupIdartifactId版本作用Spring Boot Weborg.springframework.bootspring-boot-starter-web3.2.5Web 框架Spring Data JPAorg.springframework.bootspring-boot-starter-data-jpa3.2.5数据访问PgVectororg.springframework.aispring-ai-pgvector-store1.0.0-M6向量存储Spring AIorg.springframework.aispring-ai-openai1.0.0-M6Embedding Chat 模型PostgreSQL Driverorg.postgresqlpostgresql42.7.3数据库驱动2.2 文档切分策略切分是最容易被忽视、但最影响效果的环节。我一开始按固定字数 500 字一刀切结果一篇数据库迁移流程被从中间劈开前半讲备份后半讲回滚检索时经常只召回一半。有人问为什么不直接用 Spring AI 自带的TextReader它能自动按段落切。我试过它对中文支持一般——按换行切遇到一大段没换行的文档就整段塞进去超了向量维度上限直接报错。所以我手写了一个 splitter逻辑透明、参数好调后面改切片策略不用看框架源码。正确做法是用重叠切片overlap每片 400 字、相邻片重叠 80 字保证语义完整package com.example.rag.service; import org.springframework.ai.document.Document; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Map; Component public class DocumentSplitter { // 切片大小与重叠窗口这两个值最影响召回质量 private static final int CHUNK_SIZE 400; private static final int OVERLAP 80; // 按重叠窗口切分避免语义在边界被切断 public ListDocument split(String content, String source) { ListDocument chunks new ArrayList(); int start 0; while (start content.length()) { int end Math.min(start CHUNK_SIZE, content.length()); String text content.substring(start, end); // metadata 带源文件回答时可直接回链 chunks.add(new Document(text, Map.of(source, source))); if (end content.length()) break; start end - OVERLAP; // 关键回退 overlap 长度 } return chunks; } }调完这两个参数召回准确率从 60% 提到 85%。切片太大检索粒度粗太小语义碎片化400/80 是我在这个知识库上试出来的甜点值。三、向量存储和检索3.1 配置 PgVector StoreEmbedding 模型我用的 OpenAI text-embedding-3-small1536 维。PgVector 表结构让 Spring AI 自动建不用手写 DDL。package com.example.rag.config; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.PgVectorStore; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; Configuration public class VectorStoreConfig { // 向量维度必须和 Embedding 模型一致填错建表直接报错 private static final int EMBEDDING_DIM 1536; Bean public VectorStore vectorStore(DataSource dataSource, EmbeddingModel embeddingModel) { return PgVectorStore.builder(new JdbcTemplate(dataSource), embeddingModel) .dimensions(EMBEDDING_DIM) .distanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE) .initializeSchema(true) // 首次启动自动建表生产建议关掉手写迁移 .build(); } }3.2 存入和检索写入就是add查询就是similaritySearchSpring AI 封装得很干净package com.example.rag.service; import com.example.rag.DocumentSplitter; import org.springframework.ai.document.Document; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.stereotype.Service; import java.util.List; Service public class KnowledgeService { private final VectorStore vectorStore; private final DocumentSplitter splitter; public KnowledgeService(VectorStore vectorStore, DocumentSplitter splitter) { this.vectorStore vectorStore; this.splitter splitter; } // 入库切分后批量写入向量库 public void ingest(String content, String source) { ListDocument chunks splitter.split(content, source); vectorStore.add(chunks); } // 检索topK 取最相似的5片similarityThreshold 过滤弱相关 public ListDocument search(String question) { SearchRequest request SearchRequest.builder() .query(question) .topK(5) .similarityThreshold(0.72) // 低于此分数的片段不要避免噪声 .build(); return vectorStore.similaritySearch(request); } }similarityThreshold这个参数我调了很久。设太高0.85漏掉很多相关文档设太低0.6又把无关的塞进来大模型被带偏。0.72 是准确率和小米平衡的点。3.3 拼装 Prompt 调大模型检索到的片段不能直接丢给大模型得拼成一段带格式的 Prompt告诉它只基于以下资料回答资料里没有就明说不知道。package com.example.rag.service; import org.springframework.ai.chat.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.document.Document; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; Service public class RagAnswerService { private final ChatClient chatClient; private final KnowledgeService knowledgeService; public RagAnswerService(ChatClient chatClient, KnowledgeService knowledgeService) { this.chatClient chatClient; this.knowledgeService knowledgeService; } // 检索片段拼成上下文再调大模型生成带出处的回答 public String answer(String question) { ListDocument docs knowledgeService.search(question); String context docs.stream() .map(d - 【 d.getMetadata().get(source) 】/n d.getText()) .collect(Collectors.joining(/n/n)); String promptText 你是基于以下资料的助手只根据资料回答资料中没有的内容回答未找到相关文档。 如果资料不足以回答明确说明。 资料 %s 问题%s .formatted(context, question); return chatClient.prompt(new Prompt(promptText, OpenAiChatOptions.builder().temperature(0.1).build())) .call().content(); } }temperature(0.1)很关键——RAG 场景要的是稳定准确的回答不是创意发挥。我一开始用默认 0.7同一个问题问两次答案不一样评审时被人抓包你怎么前后矛盾。调到 0.1 之后答案基本固定可复现。四、踩坑记录4.1 向量维度对不上启动直接炸第一个坑最折腾。我一开始填的EMBEDDING_DIM 1536但 pom 里 Embedding 模型名写错了实际调的是 text-embedding-ada-002也是 1536 维侥幸没炸后来换成 bge-large 模型是 1024 维建表用 1536、写入用 1024直接报维度不匹配。教训维度必须和模型严格一致改模型时这行常量一定同步改。我后来把维度改成从配置文件读不再硬编码。坑现象解决维度不匹配写入报 dimension mismatch维度从配置读取跟随模型版本切片把表格切断表格跨两片检索只回一半切片前先按段落/标题预分割检索阈值太低相关度低的文档混入答案跑偏similarityThreshold 调到 0.724.2 中文分词切歪了第二个坑更隐蔽。默认切分是按字符数但中文没有空格一篇文档里数据库连接池配置最大连接数被切成数据库连接池配和置最大连接数语义全碎了。召回质量直线下降。解决方法是先按中文标点。/n做一级切分再在每段内做重叠切片保证句子完整性。这个改动让中文问答准确率从 70% 提到 88%。4.3 Embedding 调用超限第三个坑是钱的问题。全量文档 2000 篇批量 EmbeddingOpenAI 限流 3500 次/分钟一股脑并发打过去直接 429。我加了信号量限流每秒最多 50 次跑完 2000 篇大概 40 秒没再触发限流。// 信号量控制并发避免触发 OpenAI 限流 private final Semaphore embedLimit new Semaphore(50); public void ingestBatch(ListDocument chunks) { for (Document doc : chunks) { embedLimit.acquire(); // 拿不到许可就阻塞自然限流 try { vectorStore.add(List.of(doc)); } finally { // 20ms 后释放等效每秒约 50 次 scheduler.schedule(embedLimit::release, 20, TimeUnit.MILLISECONDS); } } }这个写法比sleep 固定间隔优雅——它根据实际处理能力动态调节不会在限流边缘反复横跳。增量同步每天新增几十篇也得走这套限流别以为量少就不限。五、我的使用建议搭完这套 RAG我有几个实在的建议切片策略比模型选择更影响效果。 很多人一上来纠结用哪个 Embedding 模型其实切片方式对召回的影响更大。先把手头的文档按自然段落 重叠窗口切好这步做对普通模型也能有 85% 的准确率。我前后试了 ada-002、text-embedding-3-small、还有本地的 bge-small在切片策略固定的情况下三者准确率差距不到 3 个百分点但价格和速度差了一倍。阈值要基于真实查询调别拍脑袋。similarityThreshold设多少拿你真实的 20 个高频问题跑一遍看召回的文档里相关的占多少再定。我见过有人直接抄教程里的 0.78结果他们文档短、语义密0.78 把一半相关文档挡在门外。生产环境关掉 initializeSchema。 开发图省事开initializeSchema(true)让框架自动建表但生产环境表结构应该走数据库迁移脚本管理不然哪天框架升级改了表结构自动建表可能覆盖你的数据。成本没你想的高。 我实测过2000 篇文档、每篇平均 800 字Embedding 一次批量跑完大概花 ¥12text-embedding-3-small。每次用户提问的 Embedding 是 1536 维的一小段成本忽略不计。大模型调用按 token 算每次回答拼上 5 个片段大概 2000 token 上下文gpt-4o-mini 单次回答成本不到 ¥0.01。换句话说这套系统跑一个月几万次查询API 费用也就一两百块比招个人专门答工单便宜太多。别神话 RAG。 它解决的是检索对了但答案散落的问题解决不了文档里压根没有答案的情况。我加了资料里没有就明说不知道的兜底避免它强行编。实测大概 8% 的提问会触发兜底这些要么是真的没文档要么是文档太旧。这部分得靠知识库运营去补不是技术问题。检查项建议向量维度从配置读取跟随 Embedding 模型版本切片方式先按中文标点分段再重叠切片400/80检索阈值用真实查询调不要抄教程默认值Embedding 限流批量入库加信号量避免 429建表策略生产关 initializeSchema走迁移脚本元数据每片带 source回答可回链出处温度参数RAG 场景 temperature 设 0.1保证可复现兜底逻辑Prompt 明确资料没有就说不知道防编造这套 RAG 我跑了三周团队内部搜索的一次找到率从 45% 提到 82%工单量降了大概三成——大家自己搜得到答案就不来问我们了。API 费用一个月 ¥200 不到ROI 相当可观。你要是也在头疼内部知识检索RAG 值得试但别一上来追求完美先把切片和阈值这两件事做对效果就够惊艳了。说句心里话RAG 是这两年我最推荐 Java 开发者上手的一个 AI 应用场景。它不要求你懂模型训练、不要求 GPU、不需要新中间件PgVector 复用现有 PG 就行纯靠 Spring Boot 那套你已经熟得不能再熟的东西就能搭起来。门槛低、见效快、成本可控比追那些花哨的 Agent 框架实在多了。我们组后续还打算把这套检索能力接到内部 IM 机器人上让同事直接在聊天里问怎么申请权限机器人调 RAG 回答案和文档链接——这套扩展基本是复用现有代码下个迭代就能上。这里给大家精心整理了一份全面的AI大模型学习资源包括AI大模型全套学习路线图从入门到实战、精品AI大模型学习书籍手册、视频教程、实战学习、面试题等资料免费分享扫码免费领取全部内容1. 成长路线图学习规划要学习一门新的技术作为新手一定要先学习成长路线图方向不对努力白费。这里我们为新手和想要进一步提升的专业人士准备了一份详细的学习成长路线图和规划。可以说是最科学最系统的学习成长路线。2. 大模型经典PDF书籍书籍和学习文档资料是学习大模型过程中必不可少的我们精选了一系列深入探讨大模型技术的书籍和学习文档它们由领域内的顶尖专家撰写内容全面、深入、详尽为你学习大模型提供坚实的理论基础。书籍含电子版PDF3. 大模型视频教程对于很多自学或者没有基础的同学来说书籍这些纯文字类的学习教材会觉得比较晦涩难以理解因此我们提供了丰富的大模型视频教程以动态、形象的方式展示技术概念帮助你更快、更轻松地掌握核心知识。4. 2026行业报告行业分析主要包括对不同行业的现状、趋势、问题、机会等进行系统地调研和评估以了解哪些行业更适合引入大模型的技术和应用以及在哪些方面可以发挥大模型的优势。5. 大模型项目实战学以致用当你的理论知识积累到一定程度就需要通过项目实战在实际操作中检验和巩固你所学到的知识同时为你找工作和职业发展打下坚实的基础。6. 大模型面试题面试不仅是技术的较量更需要充分的准备。在你已经掌握了大模型技术之后就需要开始准备面试我们将提供精心整理的大模型面试题库涵盖当前面试中可能遇到的各种技术问题让你在面试中游刃有余。7. 资料领取全套内容免费抱走学 AI 不用再找第二份不管你是 0 基础想入门 AI 大模型还是有基础想冲刺大厂、了解行业趋势这份资料都能满足你现在只需按照提示操作就能免费领取扫码免费领取全部内容