从零搭建RAG知识库问答系统

作者:袖梨 2026-09-14

企业制度、产品手册和内部代码通常不在大模型的训练数据中,直接提问容易得到缺少依据甚至错误的回答。RAG 通过先检索知识库、再结合命中文档生成答案,为私有知识问答提供了一条可落地的路径。下面将以 Spring AI 为基础,从最小示例开始拆解入库、检索、重排与生成等关键环节。

用 Spring AI 从零搭一个「会查资料再回答」的知识库问答系统:从原理到代码,从跑通到上线。


阅读地图(先花 3 分钟看这里)

这篇文章解决什么问题

你想让大模型回答「内部知识」——公司的制度、手册、产品文档、代码库。但大模型没学过这些,直接问它就会一本正经地瞎编

RAG(Retrieval-Augmented Generation,检索增强生成)就是干这个的:先查资料,再照着资料答。这篇文章教你把这件事用 Spring AI 完整落地——从「为什么」到「怎么写代码」,再到「怎么把它调到生产可用」。

写给谁

  • 有 Java / Spring Boot 基础,想快速上 RAG 的工程师;
  • 已经跑过 demo、但「检索不准 / 有幻觉 / 不知道从哪调优」的同学;
  • 想系统理解 RAG 每个环节(分片、索引、召回、重排、生成)在干什么的人。

不需要你懂机器学习,只要会写 Java 就行。

一条主线 + 一个贯穿案例

整篇文章走一条主线——Advanced RAG 的完整链路

离线入库:文档加载 → 解析 → 分片 → 向量化 → 建索引
在线问答:提问 → 改写 → 向量化 → 召回 → 重排 → 拼 prompt → 生成

所有代码都围绕同一个例子(后文反复用到):

知识库里有一条「退货正策」:「7 天无理由退货:签收后 7 天内、未使用可申请,审核通过后 3~5 天退款」。用户问:「这东西咋退啊?」

从入库到回答,你会看到这条知识怎么被切块、向量化、命中、重排、喂给模型,最后给出带溯源的回答。


快速上手:15 分钟跑通最小闭环

目标:先看到结果,再回头理解原理。 这里用内存向量库 + 云端大模型(qwen-max),不装本地模型、不碰数据库,配好 API Key 就能跑通。

1. 准备环境(只要一个 API Key)

# ① 云端大模型:阿里百炼开通 qwen-max,创建 API Key(推荐)
export OPENAI_API_KEY=sk-xxxx

# ② 生产要用的 pgvector(这一步可以先跳过,等跑通再装)
docker run -d --name pgvector -e POSTGRES_PASSWORD=postgres -p 5432:5432 pgvector/pgvector:pg16
docker exec -it pgvector psql -U postgres -c "CREATE EXTENSION vector;"

没有 API Key、想离线跑?也可以用本地 Ollama(ollama pull qwen2.5:1.5b + ollama pull nomic-embed-text),再把第 3 节的配置换回 Ollama 即可。

完整环境说明见附录 A。

2. 最小闭环代码(一个文件搞定)

新建一个 Spring Boot 项目,pom 里只加三个依赖(spring-ai-starter-model-openaispring-ai-ragspring-ai-advisors-vector-store,完整 pom 见附录 A),然后写这个 CommandLineRunner

@SpringBootApplication
public class QuickStartApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuickStartApplication.class, args);
    }

    @Bean
    CommandLineRunner run(EmbeddingModel embeddingModel, ChatModel ch@tModel) {
        return args -> {
            // ① 离线入库:内存向量库(重启即丢,只用来跑通)
            VectorStore store = SimpleVectorStore.builder(embeddingModel).build();

            // 一条退货正策,切块入库
            Document doc = new Document(
                    "7 天无理由退货:签收后 7 天内、未使用可申请,审核通过后 3~5 天退款。");
            List<Document> chunks = TokenTextSplitter.builder()
                    .withChunkSize(800)
                    .withMinChunkSizeChars(350)
                    .build()
                    .apply(List.of(doc));
            store.add(chunks);

            // ② 在线问答:挂一个朴素 RAG Advisor
            QuestionAnswerAdvisor qa = QuestionAnswerAdvisor.builder(store)
                    .searchRequest(SearchRequest.builder()
                            .topK(5)
                            .similarityThreshold(0.45)
                            .build())
                    .build();
            ChatClient client = ChatClient.builder(ch@tModel)
                    .defaultAdvisors(qa)
                    .build();

            // ③ 提问
            String answer = client.prompt().user("这东西咋退啊?").call().content();
            System.out.println("回答:" + answer);
        };
    }
}

3. 配置(application.yml)

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      ch@t:
        options:
          model: qwen-max
      embedding:
        options:
          model: qwen3.7-text-embedding   # 1024 维
          dimensions: 1024

4. 运行,预期输出

回答:根据退货正策,商品在签收后 7 天内、未使用的情况下可以申请无理由退货,
审核通过后 3~5 天退款。

看到这个,你就已经跑通了 RAG 的完整链路:入库(切块 → 向量化 → 存)→ 召回(向量相似度命中「退货正策」)→ 生成(照资料答)。

5. 跑通之后

这段代码是「朴素 RAG」——黑盒、不可控、只够演示。后面 10 章就是把它一步步拆开、逐个环节优化成生产可用的 Advanced RAG:

你现在要解决的问题去哪看
文档是扫描件/图片,读不进来第 2 章(OCR)
切块太糙、语义断裂第 3 章
换中文强 Embedding、上 pgvector第 4 章
口语化「咋退」查不到第 5 章(查询改写)
召回太糙、想要精排第 6 章
模型瞎编、要溯源第 7 章
效果还是差、要系统调优第 8 章

第一部分 · 理解 RAG

第 1 章 为什么要 RAG

本章解决什么问题:让你在动手前,搞懂 RAG 是什么、什么时候该用它、以及整套系统长什么样。读完这章,你能画出 RAG 的全景图,并判断「我的场景到底该不该上 RAG」。

1.1 大模型的三个天生短板

大模型很强,但有三件事它天生做不好:

  • 幻觉:一本正经地瞎编——它是「概率生成」,不是「查数据库」。
  • 知识过期:训练截止后发生的事(新正策、新产品)一概不知。
  • 私有知识缺失:企业内部的制度、手册、代码不在公开语料里。

这三件事有一个共同解法:别让模型凭空答,先给它查好资料。这就是 RAG。

1.2 RAG 是什么

RAG = Retrieval(检索)+ Augmented(增强)+ Generation(生成):先查资料,再照着资料答。

用户提问 → 检索知识库(找资料)→ 资料塞进提示词 → LLM 照资料答

一句话:把「查资料」和「照着答」接起来,补齐大模型「不知道私有知识」的短板。

1.3 系统全景:两条线

RAG 分离线在线两条线,先分清这两条线,后面所有章节都挂在上面:

【离线 · 入库线】(一次性,知识更新时重跑)
文档加载 → 解析/清洗 → 分片 → 向量化 → 建索引 → 存入向量库

【在线 · 查询线】(每次提问实时执行)
用户提问 → 查询改写 → 向量化 → 召回 → 重排 → 拼 prompt → LLM 生成 → 回答

各阶段做什么、关键点在哪:

阶段做什么关键点
文档加载读入 PDF/Word/HTML/Markdown格式多样
解析/清洗提取纯文本,去页眉页脚图片会丢、表格易碎
分片长文档切成小块块大小决定检索精度
向量化文本转成高维向量Embedding 模型决定上限
建索引向量入库 + 建索引HNSW / IVFFLAT
查询改写口语化提问改成书面检索式「咋退」→「如何退货」
召回取 topK 最相关片段阈值 / topK 调参
重排用 reranker 精排候选粗召回 + 精排
生成拼 prompt + 照资料答防幻觉

1.4 核心概念与术语

概念一句话理解
Embedding把文本变成一串数字(向量),语义近的向量距离近
向量库每条记录存「向量 + 原始文本 + 元数据」三样东西,向量只用来检索
Chunk(分片)文档切成的小块,检索的基本单位
召回从库里捞出最相关的 topK 块
Rerank(重排)对召回结果重新打分排序
Context(上下文)拼进提示词的那段资料
幻觉模型没有依据地瞎编
溯源(citation)回答时指出依据哪份文档

完整术语表见附录 D。

新手误区:向量库不是只存向量。 入库时每条记录同时保存三样东西——向量 embedding(只用于相似度检索)、原始文本、metadata 元数据(来源、时间等)。检索命中后,取出来喂给 LLM 的是原始文本,不是那串向量——向量只是「找」的索引,文本才是「答」的原料。

1.5 什么时候该用 RAG

判断「要不要上 RAG」,看三件事:有哪些典型场景你的场景适不适合问题有多复杂(单跳还是多跳)

典型场景

场景例子知识来源
知识库问答「报销流程是什么」制度、Wiki
智能客服「怎么退货」话术、正策
企业助手「入职要办啥」手册、SOP
文档/合同助手「合同违约条款」合同、法务文档
垂直行业「这药禁忌」「这法条解读」药典、法典、研报
代码助手「鉴权逻辑在哪」代码、技术文档
Agent 增强给 Agent 提供事实依据各类知识源

适不适合

  • 适合:私有知识 / 实时更新 / 要溯源(满足其一即可)。
  • 不适合:纯创造性 / 无知识库 / 强确定性——这些该走工具调用或规则引擎。

单跳 vs 多跳(判断你的问题复杂度):

问题特征例子技术
单跳、关键词明确「P0 故障怎么处理」纯向量 / 混合检索
单跳、语义口语化「咋退」「咋报销」纯向量 + 查询改写
精确编号/型号订单号、SKU混合检索(向量 + BM25)
多跳推理「A 和 B 什么关系」GraphRAG / 图检索(进阶)

单跳 = 答案就在一条知识里,检索一次命中即可答;多跳 = 答案分散在几条知识里,要「查 A → 根据 A 再查 B → 拼起来」才能答,向量检索搞不定,需图检索(GraphRAG)或 Agent 多轮检索。普通知识库问答,先「纯向量 + 查询改写 + 重排」就够(见第 8 章)。

1.6 技术路线对比:RAG / 微调 / 长上下文

先给结论:「灌知识」用 RAG,微调现在基本不干这事了——它的用途已经变成「改输出风格/格式、适配领域术语、把强模型蒸馏到小模型降成本」,用它灌知识更新慢、成本高、易过拟合。

维度RAG微调长上下文
原理检索外部知识注入 prompt私有数据再训练资料全塞进上下文
知识更新秒级慢(要重训)秒级
可溯源
适用私有知识、实时、要溯源改风格/格式、降本单篇长文精读

1.7 落地四关

RAG 落地最容易卡在这四件事,后面章节分别解决:

痛点表现对策对应章节
文档解析PDF 乱码/扫描件、表格拆散、图片丢失换解析器、OCR、图片转文字摘要第 2 章
颗粒度块太大噪声多、太小语义断裂块 300~1000 token、父子分块第 3 章
检索准确率查不到 / 查不准阈值、查询改写、混合检索、重排第 5、6 章
提问模糊性口语化、指代不明,和文档用词对不上查询改写、多轮记忆第 5、7 章

1.8 大模型选型与评估

这里指 ch@t 生成模型;embedding 选型见第 4 章、rerank 见第 6 章。

选型主要看三个能力

能力说明
逻辑推理能力能否从多段资料里综合、推理出结论
指令遵循能力能否严格照 prompt 约束执行(如「资料没有就说不知道」)
防幻觉能力没有依据时会不会硬编

评估标准:用「忠实度(有无幻觉)、检索命中率、延迟、成本」量化,先搭评测集再测(第 8 章)。

优先选这三项都强的模型(Qwen / DeepSeek 等),再按成本、延迟收敛。

1.9 进阶方向

演进路线

Naive RAG → Advanced RAG(预检索/检索/后检索三段优化)→ Modular RAG → 前沿

本文主线是 Advanced RAG:预检索(查询改写)、检索(混合)、后检索(重排)。

Modular RAG(模块化):把检索、改写、重排、生成等环节拆成可插拔模块,自由增删/替换/编排——Spring AI 的 RetrievalAugmentationAdvisor 四段式就是它的落地(第 7 章)。

前沿方向简单了解即可:

方向一句话解决什么
GraphRAG把知识建成「实体 + 关系」的图再检索多跳推理(「A 和 B 什么关系」)
Agentic RAG让 Agent 自主决定何时检索、检索什么复杂任务、多轮检索
多模态 RAG文档里的图片/表格也参与检索图、表信息不丢失

本章要点回顾

  • RAG 补上大模型三个短板(幻觉 / 知识过期 / 私有知识缺失):先查资料,再照着资料答
  • 两条线:离线入库(加载 → 解析 → 分片 → 向量化 → 建索引)、在线查询(改写 → 向量化 → 召回 → 重排 → 生成)。
  • 决策:私有知识 / 实时更新 / 要溯源 → 用 RAG;「灌知识」用 RAG、别用微调。
  • 落地四关:文档解析、颗粒度、检索准确率、提问模糊性,分别对应第 2~7 章。
  • 主线:Advanced RAG = 预检索(查询改写)+ 检索(混合)+ 后检索(重排)。

第二部分 · 实战主线

第 2~6 章为导读:每章点明「解决什么问题、关键坑在哪」,完整实操与术语(混合检索、BM25、Reranker 等)在对应掘金专栏里;可直接落地的代码集中在第 7 章和附录 B。

第 2 章 文档加载与解析

本章解决什么问题:离线入库的第一步——把各种格式的文档(PDF/Word/HTML/Markdown,以及扫描件)读进来、洗干净,变成第 3 章能直接切块的干净纯文本。没有这一步,后面分片、索引全是白搭。

详细内容见专栏:RAG检索增强生成:文档加载解析 · RAG检索增强生成:文本清洗


第 3 章 分片(Chunking)

本章解决什么问题:文档太长,整篇塞不进模型、也查不准。本章教你怎么把文档切成合适的小块——块太大/太小的后果、怎么选参数、有哪些策略、以及不同文档类型怎么切才切得好。

详细内容见专栏:RAG检索增强生成:文本分片


第 4 章 索引与 Embedding

本章解决什么问题:把第 3 章切好的文本块变成「机器能算相似度」的向量,并组织成能快速检索的索引。核心是选对 Embedding 模型(决定 RAG 上限)和配好向量库(维度对齐是头号坑)。

详细内容见专栏:RAG检索增强生成:向量化 & 索引


第 5 章 召回(Retrieval)

本章解决什么问题:用户问「这东西咋退」,怎么从向量库里捞出真正相关的那几条。本章覆盖三种召回方式、召回质量指标、topK/阈值调参,以及最重要的「查询侧优化」(查询改写)。

详细内容见专栏:RAG检索增强生成:召回


第 6 章 重排(Reranking)

本章解决什么问题:向量召回是「粗筛」,排序不够准。本章用更准的 reranker 模型对候选精排,让真正相关的排到最前面。

详细内容见专栏:RAG检索增强生成:重排(Reranking)


第 7 章 生成(Generation)

本章解决什么问题:把重排后的资料拼进 prompt,让模型照着资料答、没有依据就拒答,并带出溯源。核心是防幻觉和模块化组装。

7.1 生成 = 拼 context + 让模型照资料答

[系统提示:基于资料答] + [资料:重排后的文档] + [问题] → LLM → 回答

7.2 Prompt 模板(两个占位符)

请基于以下资料回答,资料没有的就说「不知道」,不要编造。
问题:{query}
资料:{question_answer_context}
// 自定义模板必须含 {query} 和 {question_answer_context}
QuestionAnswerAdvisor qa = QuestionAnswerAdvisor.builder(vectorStore)
        .promptTemplate(customTemplate).build();

7.3 防幻觉三板斧

手段做法
空上下文拒答allowEmptyContext(false),没资料就说不知道
提示词强制模板写死「资料没有就说不确定」
降温度temperature 调到 0.2~0.3,回答更保守

7.4 引用溯源(citation)

入库时把来源写进 metadata(source),回答时带出来。注意 QuestionAnswerAdvisor 默认只注入正文、不注入 metadata,溯源要用模块化 RAG(7.7)手动拿 docssource

7.5 多轮 RAG(指代消解)

追问「那它的价格呢」要结合上文。做法:ChatClient 同时挂「记忆 + RAG」两个 Advisor:

ChatClient.builder(ch@tModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(ch@tMemory).build(),  // 记忆带历史
        qaAdvisor)                                             // RAG
    .build();

历史会帮消解「它」指什么;配查询改写效果更好;记忆会涨 token,需窗口截断或摘要。

7.6 上下文 token 控制

手段说明
控 topK / 重排取前 N源头限制注入条数
截断每篇只取前 N token
摘要压缩context 先总结再注入

7.7 朴素 vs 模块化 RAG

  • 朴素QuestionAnswerAdvisor):黑盒,代码少,环节不可控。
  • 模块化RetrievalAugmentationAdvisor):四段可插拔。
RetrievalAugmentationAdvisor.builder()
    .queryTransformers(rewrite)                // ①查询改写(可选)
    .documentRetriever(                        // ②检索(必填)
        VectorStoreDocumentRetriever.builder().vectorStore(vs).topK(10).similarityThreshold(0.35).build())
    .documentPostProcessors(rerankPostProcessor)  // ③后处理 Rerank(可选)
    .queryAugmenter(                           // ④增强(空上下文防幻觉)
        ContextualQueryAugmenter.builder().allowEmptyContext(false).build())
    .build();

四段只有「检索」必填,其余按需加(改写/重排都多一次模型调用,有成本)。


第三部分 · 优化与上线

第 8 章 检索优化

本章解决什么问题:效果不满意时,八成问题在检索侧。本章给出一份按性价比排序的优化清单、问题诊断表,以及「先有度量再优化」的评测体系。

8.1 优化清单(按性价比排序)

顺序手段成本说明
1调阈值/topK先打印 score 分布再定
2查询改写一次 LLM口语化命中率提升最明显
3元数据过滤缩小范围、减少误召回
4混合检索解决编号/型号查不准
5重排一次 rerank精排、压缩候选
6换更强 Embedding重建索引Embedding 不行上面全白搭

先便宜后贵:1→3→2→5→4→6,别一上来就换模型、上混合检索。

8.2 问题诊断表

症状根因对策
检索不到(召回低)相关文档没搜出降阈值 / 查询改写 / 换 Embedding / 混合检索
检索不准(精准低)搜出一堆无关升阈值 / 过滤 / 重排
幻觉资料没有却乱编空上下文拒答 / 提示词强制 / 降温度

排查顺序:先看检索(命中、score),再看生成(照不照资料答)。

8.3 评测体系(先有度量,再谈优化)

评测集:准备「问题 → 标准答案 → 应命中文档」三元组,作回归测试。

RAGAS 三指标

指标含义查什么
Faithfulness答案是否忠于上下文(有没有编)幻觉
Answer Relevancy答案是否切题答非所问
Context Relevancy检索上下文是否相关检索准不准

三者对应 8.2 三类问题:Context Relevancy 查检索、Faithfulness 查幻觉、Answer Relevancy 查切题。RAGAS 是 Python 库,Java 侧无同级框架,生产用 Python 离线跑或「LLM as Judge」自打分。

8.4 全链路日志调试

三层:底层 DEBUG 日志 → 自定义 Advisor 拦截 → 直接打印检索结果。

logging.level:
  [email protected]: DEBUG
  org.springframework.ai.vectorstore: DEBUG
// 最直观:直接打印检索结果看 score
for (Document d : vectorStore.similaritySearch(
        SearchRequest.builder().query(q).topK(10).build())) {
    System.out.println(d.getScore() + " / " + d.getText());
}

排查顺序:先打印检索结果(命中/分数)→ 再看 prompt 拼接 → 最后看生成。


第 9 章 商业化落地

本章解决什么问题:从「能跑」到「能上线转前」。三阶段推进、算账、选型、工程质量四件事、数据治理,以及一个贯穿全书的电商客服案例。

9.1 三阶段推进

阶段目标退出标准
PoC证明检索+生成能答对最小数据跑通,命中率达预期
试点验证真实稳定性小流量观察命中率/成本/反馈
全量规模化运营接评测、坚控、运维体系

9.2 成本与定价

单次问答成本 ≈ 生成 token × 生成单价 + 检索成本(极小)+ 摊薄的入库成本。生成是大头,和 context 长度、模型档次强相关——所以控 topK、压缩 context 不只是质量问题是成本问题。

定价:按 token / 按调用 / 订阅 / SaaS 多租户。 降本:模型分级(简单问题用小模型)、缓存、本地开发云生产、提示词瘦身、上下文压缩。

9.3 选型清单

组件建议
ch@t生产云(质量稳),开发本地
embedding中文选 bge / qwen3.7(第 4 章)
rerank云 API 最省事(第 6 章)
向量库有 PG 用 pgvector,大规模用 Milvus

9.4 工程质量四件事

关注点做法
Prompt 注入分隔符隔离用户输入,内容安全组件兜底
数据隔离按租户/conversationId 隔离,身份走服务端注入
权限溯源检索按租户过滤,回答带 source
降级兜底检索空/超时/异常有兜底话术或转人工

9.5 数据治理

入库去重(稳定 id、先删后加)、过期清理(按 updated_at)、权限隔离、版本管理。

9.6 落地案例(电商智能客服)

目标:客服机器人答「退货/配送/发票」等制度问题,降低人工坐席
架构:ChatClient 挂 记忆→RAG(改写→检索→重排→增强)→日志;业务表+向量表共用 PG+pgvector
模型:ch@t=qwen-max、embedding=qwen3.7(1024)、rerank=qwen3-rerank
节奏:PoC 灌 4 条知识验证「咋退」→试点接真实日志→全量加评测+坚控+转人工

这个案例贯穿全书各章节;附录 B 是可跑通的朴素闭环 Demo,进阶环节(查询改写 / 重排 / 溯源)见第 7 章。


第 10 章 问题排查

本章解决什么问题:出了问题,按「数据解析 → 分片 → 向量化 → 召回 → 生成 → 工程化」全链路逐段定位,每段给你「症状 → 根因 → 解法」。

10.1 按阶段定位

数据解析(10.2) → 分片(10.3) → 向量化/索引(10.4) → 召回(10.5) → 生成(10.6) → 工程化(10.7)

10.2 数据与解析

问题解法
PDF 乱码/扫描件扫描件 OCR,换解析器
表格整表解析保表头
图片信息丢失提取图片 → 调多模态模型生成文字描述 → 当普通文本入库
Tika 提取不干净解析后清洗页眉页脚/广告

图片是最隐蔽的坑:Tika 只提取文字,图片直接跳过且不报错。带业务信息的图片必须「转文字摘要」入库;完整多模态 RAG(检索出原图传给 VL)成本高,生产少用。

10.3 分片问题

语义断裂 → 调块大小/重叠;表格切碎 → 整表;标题丢失 → contextual headers;代码切碎 → 按函数切。(详见第 3 章)

10.4 向量化 / 索引问题

问题解法
维度不匹配(静默失败/报错)对齐 dimensions(第 4 章)
Embedding 不匹配领域(中文/术语召回差)换中文强/领域 Embedding
索引类型选错(慢/召回低)HNSW 默认,大规模/省内存用 IVFFLAT
数据漂移(知识更新库没跟上)建立「先删后加 + 定期清理」机制

10.5 召回问题(汇总)

检索不到 / 检索不准——最高频,根因对策见第 8.2 诊断表,此处不展开。

10.6 生成问题

问题解法
幻觉空上下文拒答 + 提示词强制 + 降温度(第 7 章)
答非所问回到检索侧优化(第 8 章)
上下文过长控 topK、重排取前 N、压缩
引用错误metadata 带稳定 source

10.7 工程化问题

问题解法
成本失控模型分级、缓存、压缩 context
延迟高控 topK、模型分级、异步化
并发撑不住限流、连接池、降级
多租户串号按租户/会话隔离,身份服务端注入
数据安全权限过滤、输入隔离、内容安全

10.8 高频坑速查表

完整版见附录 C。


附录

附录 A:环境搭建

环境准备

需要四样:JDK 21+、Maven 3.6+、PostgreSQL(pgvector)、模型(本地 Ollama 或云端 API Key)。

① PostgreSQL + pgvector(Docker 一键)

docker run -d --name pgvector 
  -e POSTGRES_PASSWORD=postgres 
  -p 5432:5432 
  pgvector/pgvector:pg16

# 启用向量扩展
docker exec -it pgvector psql -U postgres -c "CREATE EXTENSION vector;"

pgvector 是「PostgreSQL + vector 扩展」,不装扩展就没有向量能力。

② 本地 Ollama(免 Key,先跑通)

ollama pull qwen2.5:1.5b       # 聊天模型(约 1GB;更小试 0.5b,更好用 3b)
ollama pull nomic-embed-text   # Embedding 模型(768 维)

③ 云端百炼(可选):创建 API Key,设环境变量 OPENAI_API_KEY

核心依赖(pom.xml)

<properties>
    <spring-boot.version>3.5.0</spring-boot.version>
    <spring-ai.version>1.1.2</spring-ai.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 本地模型 Ollama(免 Key) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-ollama</artifactId>
    </dependency>
    <!-- 云端模型 OpenAI 兼容端点(阿里百炼 Qwen) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
    <!-- 向量库 pgvector -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- 文档解析(底层 Apache Tika) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-tika-document-reader</artifactId>
    </dependency>
    <!-- RAG 模块(查询改写 / 模块化 Advisor) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-rag</artifactId>
    </dependency>
    <!-- 朴素 RAG 的 QuestionAnswerAdvisor -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-advisors-vector-store</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

关键配置(application.yml)

spring:
  ai:
    # 同时引入 Ollama 和 OpenAI 时,用这个开关决定激活哪个
    model:
      ch@t: ollama          # 改 openai 即切云端
      embedding: ollama

    ollama:
      base-url: http://localhost:11434
      ch@t:
        options:
          model: qwen2.5:1.5b
      embedding:
        options:
          model: nomic-embed-text   # 768 维

    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      ch@t:
        options:
          model: qwen-max
      embedding:
        options:
          model: qwen3.7-text-embedding   # 1024 维(可调 256~2560)
          dimensions: 1024

    vectorstore:
      pgvector:
        initialize-schema: true
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 768          # 与激活的 Embedding 模型维度一致

  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
    password: postgres

⚠️ dimensions 必须和 Embedding 模型维度一致:用 nomic-embed-text 填 768,用 qwen3.7-text-embedding 填 1024(上面激活 Ollama,所以是 768)。


附录 B:完整最小 Demo

一个可跑通的分层项目:把「离线入库」和「在线问答」拆成两个 Service,支持 pgvector 持久化 + 模块化 RAG。可直接 copy,配好环境就能跑。

B.1 项目结构

rag-demo/
├── pom.xml                                  # 依赖(见附录 A)
├── data/
│   └── return-policy.md                     # 示例知识文档
└── src/main/
    ├── java/com/example/rag/
    │   ├── RagApplication.java              # 启动类 + CommandLineRunner 演示
    │   ├── IngestService.java               # 离线入库
    │   └── QueryService.java                # 在线问答
    └── resources/
        └── application.yml                  # 配置(见附录 A)

B.2 示例知识文档(data/return-policy.md)

# 退货正策

## 无理由退货条件

7 天无理由退货:签收后 7 天内、未使用可申请,审核通过后 3~5 天退款。

## 退货流程

进入「我的订单」→ 选择订单 → 申请退货 → 填写原因 → 等待审核 → 寄回商品 → 确认收货 → 退款到账。

B.3 RagApplication.java

package com.example.rag;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class RagApplication {

    public static void main(String[] args) {
        SpringApplication.run(RagApplication.class, args);
    }

    /** 演示入口:入库一条正策 → 提一个问题 → 打印回答 */
    @Bean
    CommandLineRunner demo(IngestService ingestService, QueryService queryService) {
        return args -> {
            ingestService.ingest("data/return-policy.md");
            String answer = queryService.ask("这东西咋退啊?");
            System.out.println("回答:" + answer);
        };
    }
}

B.4 IngestService.java

package com.example.rag;

import java.util.List;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;

@Service
public class IngestService {

    private final VectorStore vectorStore;

    public IngestService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    /** 离线入库:读 → 切 → 存(写入时自动向量化) */
    public void ingest(String filePath) {
        List<Document> docs = new TikaDocumentReader("file:" + filePath).get();
        TokenTextSplitter splitter = TokenTextSplitter.builder()
                .withChunkSize(500)
                .withMinChunkSizeChars(200)
                .withPunctuationMarks(List.of('。', '?', '!', ';'))  // 中文标点
                .build();
        vectorStore.add(splitter.apply(docs));
    }
}

B.5 QueryService.java

package com.example.rag;

import [email protected];
import [email protected];
import [email protected];
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;

@Service
public class QueryService {

    private final ChatClient ch@tClient;

    public QueryService(ChatModel ch@tModel, VectorStore vectorStore) {
        // 朴素 RAG:黑盒、代码少,先跑通
        QuestionAnswerAdvisor qa = QuestionAnswerAdvisor.builder(vectorStore)
                .searchRequest(SearchRequest.builder()
                        .topK(5)
                        .similarityThreshold(0.45)
                        .build())
                .build();
        this.ch@tClient = ChatClient.builder(ch@tModel)
                .defaultAdvisors(qa)
                .build();
    }

    /** 在线问答:召回 + 生成 */
    public String ask(String question) {
        return [email protected]()
                .user(question)
                .call()
                .content();
    }
}

B.6 运行与预期

# 1. 装环境(附录 A):Ollama + pgvector
# 2. 启动
mvn spring-boot:run

# 3. 预期输出
回答:根据退货正策,签收后 7 天内、未使用可申请退货,审核通过后 3~5 天退款。

附录 C:高频坑速查表

症状阶段根因解决章节
查不到任何文档召回阈值过高降阈值、打印 score5/8
查到一堆无关召回阈值过低/未过滤升阈值、过滤、重排5/6/8
编号/型号查不准召回纯向量不抓精确词混合检索5
口语化不命中召回query 与文档用词不一致查询改写5
答案缺上下文分片块太小/断裂父子分块、调块大小3
表格答错分片表格被切碎整表切3
图片信息丢解析解析器只取文字图片转文字摘要2
检索报错/静默失败索引维度不匹配对齐 dimensions4
中文召回差索引Embedding 不匹配换中文强 Embedding4
模型瞎编生成空上下文硬答拒答+提示词+降温度7
成本暴涨工程化context 过长/模型贵压缩、分级、缓存9
串号工程化隔离键写死按租户/会话隔离9

附录 D:术语表

一句话
RAG检索增强生成:先查资料再照着答
Embedding把文本变成向量,语义近的向量距离近
Embedding 模型负责文本 → 向量的模型,决定 RAG 上限
向量库存「向量 + 原文 + 元数据」,向量只用来检索
Chunk(分片)文档切的小块,检索基本单位
Chunk size / overlap块大小 / 相邻块重叠,分片两参数
召回(Retrieval)从库里捞出最相关 topK 块
Recall / Precision召回率 / 准确率,检索评测指标
Hit Rate / MRR / nDCG命中率 / 首个相关排名倒数均值 / 排序质量
topK每次召回取前 K 条
similarityThreshold相似度阈值,低于则丢弃
Rerank(重排)用 reranker 对候选重新打分排序
Reranker交叉编码器模型,比 embedding 准但慢
BM25关键词相关性算法,擅长精确词匹配
RRF倒数排名融合,合并多路检索结果
HyDE先让 LLM 生成「假设答案」再拿去检索
查询改写LLM 把口语改写成书面检索式
Context(上下文)拼进提示词的那段资料
幻觉模型没依据地瞎编
溯源(citation)回答时指出依据哪份文档
HNSW / IVFFLAT向量索引类型:分层图 / 倒排+平坦
余弦距离衡量向量方向是否一致,文本语义默认
pgvectorPostgreSQL 的向量扩展插件
GraphRAG知识建图(实体+关系)再检索,解多跳推理
Agentic RAGAgent 自主决定何时检索、检索什么
多模态 RAG图片/表格也参与检索
RAGASRAG 评测框架(Faithfulness / Relevancy 等)
Naive RAG朴素 RAG:检索 + 生成直连,黑盒
Advanced RAG预检索/检索/后检索三段优化
Modular RAG环节拆成可插拔模块,自由编排

相关文章

精彩推荐