前言:本文围绕 RAG 知识库,分析了 AI 知识问答需求及 RAG 技术催生原因,介绍 RAG 核心流程、技术,及 Spring AI + 本地知识库的实战步骤,实现 AI 基于私有知识库精准问答。

一  AI 知识问答需求分析

AI 知识问答应用场景

随着 AI 技术的快速发展,越来越多的公司开始利用 AI 重构传统业务,打造全新的用户体验和商业价值。其中,AI 知识问答是一个典型应用场景,广泛运用到教育、电商等行业,比如:

  • 教育场景:AI 针对学生的薄弱环节提供个性化辅导
  • 电商场景:AI 根据用户肤质推荐适合的护肤方案

其中,知识的来源可能来源于网络,也可能是自己公司私有的数据,从而让AI提供更精准的服务。

如何让 AI 获取知识?

在实现这个需求前,我们需要思考一个关键问题:知识从哪里获取呢?

首先 AI 原本就拥有一些通用的知识,对于不会的知识,还可以利用互联网搜索。但是这些都是从网络获取的、公开的知识。对于企业来说,数据是命脉,也是自己独特的价值,随着业务的发展,企业肯定会积累一波自己的知识库。如果让 AI 能够利用这些知识库进行问答,效果可能会更好,而且更加个性化。

如果不给 AI 提供特定领域的知识库,AI 可能会面临这些问题:

  1. 知识有限:AI 不知道你的最新的算法内容
  2. 编故事:当 AI 不知道答案时,它可能会 “自圆其说” 编造内容
  3. 无法个性化:不了解你的特色服务和回答风格

那么如何让 AI 利用自己的知识库进行问答呢?这就需要用到 AI 主流的技术 —— RAG

二  RAG 概念

什么是 RAG?

RAG(检索增强生成)是一种结合信息检索技术和 AI 内容生成的混合架构,可以解决大模型的知识时效性限制和幻觉问题。作用就是让 AI 回答问题前先查一查特定的知识库来获取知识,确保回答是基于真实资料而不是凭空想象。

可以简单了解下 RAG 传统 AI 模型的区别:

特性 传统大语言模型 RAG 增强模型
知识时效性 受训练数据截止日期限制 可接入最新知识库
领域专业性 泛化知识,专业深度有限 可接入专业领域知识
响应准确性 可能产生 “幻觉” 基于检索的事实依据
可控性 依赖原始训练 可通过知识库定制输出
资源消耗 较高(需要大模型参数) 模型可更小,结合外部知识

RAG 工作流程

RAG 技术实现主要包含以下 4 个核心步骤,让我们逐步学习:

  • 文档收集和切割
  • 向量转换和存储
  • 文档过滤和检索
  • 查询增强和关联
1 文档收集和切割

文档收集:从各种来源(网页、PDF、数据库等)收集原始文档

文档预处理:清洗、标准化文本格式

文档切割:将长文档分割成适当大小的片段

  • 基于固定大小(如 512 个 token)
  • 基于语义边界(如段落、章节)
  • 基于递归分割策略(如递归字符 n-gram 切割)

2 向量转换和存储

向量转换:使用 Embedding 模型将文本块转换为高维向量表示,可以捕获到文本的语义特征

向量存储:将生成的向量和对应文本存入向量数据库,支持高效的相似性搜索

3 文档过滤和检索

查询处理:将用户问题也转换为向量表示

过滤机制:基于元数据、关键词或自定义规则进行过滤

相似度搜索:在向量数据库中查找与问题向量最相似的文档块

上下文组装:将检索到的多个文档块组装成连贯上下文

4 查询增强和关联

提示词组装:将检索到的相关文档与用户问题组合成增强提示

上下文融合:大模型基于增强提示生成回答

源引用:在回答中添加信息来源引用

后处理:格式化、摘要或其他处理以优化最终输出

完整工作流程

分别理解上述 4 个步骤后,我们可以将它们组合起来,形成完整的 RAG 检索增强生成工作流程:

上述工作流程中涉及了很多技术名词,让我们分别进行解释。

RAG 相关技术

Embedding 模型

Embedding 嵌入是将高维离散数据(如文字、图片)转换为低维连续向量的过程。这些向量能在数学空间中表示原始数据的语义特征,使计算机能够理解数据间的相似性。

Embedding 模型是执行这种转换算法的机器学习模型,如 Word2Vec(文本)、ResNet(图像)等。不同的 Embedding 模型产生的向量表示和维度数不同,一般维度越高表达能力更强,可以捕获更丰富的语义信息和更细微的差别,但同样占用更多存储空间。

向量数据库

向量数据库是专门存储和检索向量数据的数据库系统。通过高效索引算法实现快速相似性搜索,支持 K 近邻查询等操作。

注意,并不是只有向量数据库才能存储向量数据,只不过与传统数据库不同,向量数据库优化了高维向量的存储和检索。

AI 的流行带火了一波向量数据库和向量存储,比如 Milvus、Pinecone 等。此外,一些传统数据库也可以通过安装插件实现向量存储和检索,比如 PGVector、Redis Stack 的 RediSearch 等。

召回

召回是信息检索中的第一阶段,目标是从大规模数据集中快速筛选出可能相关的候选项子集。强调速度和广度,而非精确度。

精排和 Rank 模型

精排(精确排序)是搜索 / 推荐系统的最后阶段,使用计算复杂度更高的算法,考虑更多特征和业务规则,对少量候选项进行更复杂、精细的排序。

比如,短视频推荐先通过召回获取数万个可能相关视频,再通过粗排缩减至数百条,最后精排阶段会考虑用户最近的互动、视频热度、内容多样性等复杂因素,确定最终展示的 10 个视频及顺序。

Rank 模型负责对召回阶段筛选出的候选集进行精确排序,考虑多种特征评估相关性。

现代 Rank 模型通常基于深度学习,如 BERT、LambdaMART 等,综合考虑查询与候选项的相关性、用户历史行为等因素。举个例子,电商推荐系统会根据商品特征、用户偏好、点击率等给每个候选商品打分并排序。

混合检索策略

混合检索策略结合多种检索方法的优势,提高搜索效果。常见组合包括关键词检索、语义检索、知识图谱等。

比如在 AI 大模型开发平台 Dify 中,就为用户提供了 “基于全文检索的关键词搜索 + 基于向量检索的语义检索” 的混合检索策略,用户还可以自己设置不同检索方式的权重。

了解了 RAG 概念后,我们来学习如何利用编程开发实现 RAG。想要在程序中让 AI 使用知识库,首先建议利用一个 AI 开发框架,比如 Spring AI;然后可以通过 2 种模式进行开发 —— 基于本地知识库或云知识库服务实现 RAG。下面分别讲解这 2 种模式。

三  Spring AI + 本地知识库

对比维度 标准 RAG 开发步骤 简化版 RAG 开发步骤
核心环节 1. 文档收集和切割
2. 向量转换和存储
3. 切片过滤和检索
4. 查询增强和关联
1. 文档准备
2. 文档读取
3. 向量转换和存储
4. 查询增强

1 文档准备

首先准备用于给 AI 知识库提供知识的文档,推荐 Markdown 格式,尽量结构化。

2 文档读取

首先,我们要对自己准备好的知识库文档进行处理,然后保存到向量数据库中。(ETL)

ETL 的 3 大核心组件,按照顺序执行:

  • DocumentReader:读取文档,得到文档列表
  • DocumentTransformer:转换文档,得到处理后的文档列表
  • DocumentWriter:将文档列表保存到存储中(可以是向量数据库,也可以是其他存储)

1)引入依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-markdown-document-reader</artifactId>
    <version>1.0.0-M6</version>
</dependency>

2)在根目录下新建 rag 包,编写文档加载器类 AiCodeAppDocumentLoader,负责读取所有 Markdown 文档并转换为 Document 列表。代码如下:

@Component
@Slf4j
public class AiCodeDocumentLoader {

public final ResourcePatternResolver resourcePatternResolver;

public AiCodeDocumentLoader(ResourcePatternResolver resourcePatternResolver) {
    this.resourcePatternResolver = resourcePatternResolver;
}

public List<Document> loadDocuments() {
    List<Document> allDocuments = new ArrayList<>();
    try {
        // 这里可以修改为你要加载的多个 Markdown 文件的路径模式
        Resource[] resources = resourcePatternResolver.getResources("classpath:document/*.md");
        for (Resource resource : resources) {
            String fileName = resource.getFilename();
            MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
                    .withHorizontalRuleCreateDocument(true)
                    .withIncludeCodeBlock(false)
                    .withIncludeBlockquote(false)
                    .withAdditionalMetadata("filename", fileName)
                    .build();
            MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config);
            allDocuments.addAll(reader.get());
        }
    } catch (IOException e) {
        log.error("Markdown 文档加载失败", e);
    }
    return allDocuments;
}

上述代码中,我们通过 MarkdownDocumentReaderConfig 文档加载配置来指定读取文档的细节,比如是否读取代码块、引用块等。特别需要注意的是,我们还指定了额外的元信息配置,提取文档的文件名作为文档的元信息,可以便于后续知识库实现更精确的检索。

3 向量转换和存储

我们先使用 Spring AI 内置的、基于内存读写的向量数据库 SimpleVectorStore 来保存文档。

简单了解下源码,在将文档写入到数据库前,会先调用 Embedding 大模型将文档转换为向量,实际保存到数据库中的是向量类型的数据。

在 rag 包下新建 AiCodeVectorStoreConfig 类,实现初始化向量数据库并且保存文档的方法。

@Configuration
public class AiCodeVectorStoreConfig {

    @Resource
    private AiCodeDocumentLoader aiCodeDocumentLoader;
    
    @Bean
    VectorStore aiCodeVectorStore(EmbeddingModel dashscopeEmbeddingModel){
        SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel)
                .build();
        // 加载文档
        List<Document> documents = aiCodeDocumentLoader.loadDocuments();
        simpleVectorStore.add(documents);
        return simpleVectorStore;
    }
}

4 查询增强

Spring AI 的 Advisor 特性(如 QuestionAnswerAdvisor)提供开箱即用的 RAG 功能,其查询增强原理为:向量数据库存储 AI 模型未知数据,用户提问时,Advisor 会查询该数据库获取相关文档,附加到用户问题中作为上下文,辅助 AI 生成回答。

1).引入依赖:

<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

2).选用 QuestionAnswerAdvisor 问答拦截器新增和 RAG 知识库进行对话的方法。代码如下:

@Resource
private VectorStore vectorStore;

public String doChatWithRag(String message, String chatId) {
    ChatResponse chatResponse = chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
            // 开启日志,便于观察效果
            .advisors(new MyLoggerAdvisor())
            // 应用知识库问答
            .advisors(new QuestionAnswerAdvisor(vectorStore))
            .call()
            .chatResponse();
    String content = chatResponse.getResult().getOutput().getText();
    log.info("content: {}", content);
    return content;
}

测试

编写单元测试代码,故意提问一个文档内有回答的问题:

@Test
void doChatWithRag() {
    String chatId = UUID.randomUUID().toString();
    String message = "算法刚入门者该如何学习算法?";
    String answer =  aiCodeApp.doChatWithRag(message, chatId);
    Assertions.assertNotNull(answer);
}

运行程序后,通过 Debug 可发现:

  • 加载的文档被自动按照小标题拆分,并补充了 metadata 元信息;
  • 根据用户问题检索到相关文档切片,每个切片有对应的分数和元信息;

学习算法是一个循序渐进的过程,我来为你总结一个系统的学习路径:

1. **入门基础**
- 先掌握基本概念:什么是算法、时间/空间复杂度分析
- 从简单算法入手:排序(冒泡、选择)、查找(线性、二分)
- 推荐经典入门问题:斐波那契数列、素数判断、汉诺塔等

2. **系统学习**
- 按类别构建知识体系:
  • 排序算法(快排/归并)
  • 搜索算法(DFS/BFS)
  • 动态规划
  • 贪心算法
  • 图论算法
- 每种类型建议先理解原理,再看Java实现

3. **实践提升**
- 刷题平台推荐(LeetCode/牛客)
- 从简单难度循序渐进
- 每道题要分析时间/空间复杂度
- 定期复习错题

**示例:二分查找Java实现**
```java
public int binarySearch(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while(left <= right) {
        int mid = left + (right - left)/2; // 防止溢出
        if(nums[mid] == target) return mid;
        else if(nums[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1; // 未找到
}
// 时间复杂度:O(log n)
// 空间复杂度:O(1)
```

需要任何具体算法的Java实现或讲解,可以随时告诉我你想了解的算法类型。
学习算法是一个循序渐进的过程,我来为你总结一个系统的学习路径:

1. **入门基础**
- 先掌握基本概念:什么是算法、时间/空间复杂度分析
- 从简单算法入手:排序(冒泡、选择)、查找(线性、二分)
- 推荐经典入门问题:斐波那契数列、素数判断、汉诺塔等

2. **系统学习**
- 按类别构建知识体系:
  • 排序算法(快排/归并)
  • 搜索算法(DFS/BFS)
  • 动态规划
  • 贪心算法
  • 图论算法
- 每种类型建议先理解原理,再看Java实现

3. **实践提升**
- 刷题平台推荐(LeetCode/牛客)
- 从简单难度循序渐进
- 每道题要分析时间/空间复杂度
- 定期复习错题

**示例:二分查找Java实现**
```java
public int binarySearch(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while(left <= right) {
        int mid = left + (right - left)/2; // 防止溢出
        if(nums[mid] == target) return mid;
        else if(nums[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1; // 未找到
}
// 时间复杂度:O(log n)
// 空间复杂度:O(1)
```

需要任何具体算法的Java实现或讲解,可以随时告诉我你想了解的算法类型。

四  Spring AI + 云知识库服务

在上一小节中,我们؜؜؜文档读取、文档加载、向量数据库是在本⁠⁠⁠地通过编程的方式实现的。其实还有另外‏‏‏一种模式,直接使用别人提供的云知识库‌‌‌服务来简化 RAG 的开发。但缺点是‏‏‏额外的费用、以及数据隐私问题。

很多 AI 大模型应用开发平台都提供了云知识库服务,这里我们还是选择 阿里云百炼,因为 Spring AI Alibaba 可以和它轻松集成,简化 RAG 开发。

1 准备云知识库

可؜؜؜以利用云知识库完⁠⁠成⁠文档读取、文档‏‏处理‏、文档加载、‌‌保存到‌向量数据库‏‏、知识库管‏理等操作。

1)在 应用数据 模块中,上传原始文档数据到平台,由平台来帮忙解析文档中的内容和结构:

2)进入阿里云百炼平台的 知识库,创建一个知识库,选择推荐配置即可:

3)导入数据到知识库中,先选择要导入的数据:

导入数据时؜,؜؜可以设置数据预处⁠理规则,⁠⁠智能切分‏档为文档切片(‏‏一部‌分文文档):

创建好知识库后,进入知识库查看文档和切片:

如果你觉得智能切分得到的切片不合理,可以手动编辑切片内容:

2 RAG 开发

Spring AI A؜؜؜libaba 利用了 Spring AI 提⁠⁠⁠供的文档检索特性(DocumentRetri‏‏‏ever),自定义了一套文档检索的方法,使得‌‌‌程序会调用阿里灵积大模型 API 来从云知识‏‏‏库中检索文档,而不是从内存中检索。

使用下列代码就可以创建一个文档检索器并发起查询:

// 调用大模型的 API
var dashScopeApi = new DashScopeApi("DASHSCOPE_API_KEY");
// 创建文档检索器
DocumentRetriever retriever = new DashScopeDocumentRetriever(dashScopeApi,
        DashScopeDocumentRetrieverOptions.builder()
                .withIndexName("你的知识库名称")
                .build());

// 测试从云知识库中查询
List<Document> documentList = retriever.retrieve(new Query("如何学习算法?"));

如何使用这个文档检索器,让 AI 从云知识库查询文档呢?

这就需要使用 Spring AI 提供的另一个 RAG Advisor —— RetrievalAugmentationAdvisor 检索增强顾问,可以绑定文档检索器、查询转换器和查询增强器,更灵活地构造查询。

示例代码如؜؜؜下:

Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
        .queryTransformers(RewriteQueryTransformer.builder()
                .chatClientBuilder(chatClientBuilder.build().mutate())
                .build())
        .documentRetriever(VectorStoreDocumentRetriever.builder()
                .similarityThreshold(0.50)
                .vectorStore(vectorStore)
                .build())
        .build();

String answer = chatClient.prompt()
        .advisors(retrievalAugmentationAdvisor)
        .user(question)
        .call()
        .content();

1)回归到؜؜؜我们的项目中,先⁠⁠编⁠写一个配置类,‏‏用于‏初始化基于云‌‌知识库‌的检索增强‏‏顾问 B‏ean:

@Configuration
@Slf4j
class LoveAppRagCloudAdvisorConfig {

    @Value("${spring.ai.dashscope.api-key}")
    private String dashScopeApiKey;

    @Bean
    public Advisor loveAppRagCloudAdvisor() {
        DashScopeApi dashScopeApi = new DashScopeApi(dashScopeApiKey);
        final String KNOWLEDGE_INDEX = "恋爱大师";
        DocumentRetriever documentRetriever = new DashScopeDocumentRetriever(dashScopeApi,
                DashScopeDocumentRetrieverOptions.builder()
                        .withIndexName(KNOWLEDGE_INDEX)
                        .build());
        return RetrievalAugmentationAdvisor.builder()
                .documentRetriever(documentRetriever)
                .build();
    }
}

2)然后在 LoveApp 中使用该 Advisor:

@Resource
private Advisor loveAppRagCloudAdvisor;

public String doChatWithRag(String message, String chatId) {
    ChatResponse chatResponse = chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
            // 开启日志,便于观察效果
            .advisors(new MyLoggerAdvisor())
            // 应用增强检索服务(云知识库服务)
            .advisors(loveAppRagCloudAdvisor)
            .call()
            .chatResponse();
    String content = chatResponse.getResult().getOutput().getText();
    log.info("content: {}", content);
    return content;
}

3)通过 Deb⁠⁠u⁠g 查看请求,‏‏能发‏现检索到了多‌‌个文档‌切片,每个‏‏切片有对‏应的元信息:

查看请求,؜؜发؜现用户提示词被⁠改⁠写,查询到⁠的关‏联文‏档已经作为上‌下文‏拼‌接到了用户‏提示词中:

查看响应结果,成功包含了知识库里的内容:


至此,我们就学؜؜؜完了 RAG 知识库的基本⁠⁠⁠开发。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐