当前位置: 首页 > news >正文

101-Spring AI Alibaba RAG 示例

本案例将引导您构建一个基于 Spring AI 的 RAG(检索增强生成)服务器,使用阿里云通义千问模型(OpenAI兼容模式)和 PostgreSQL + pgvector 向量存储。

1. 案例目标

我们将创建一个包含以下核心功能的 Web 应用:

  1. 文档上传与处理:支持 PDF、Word、TXT 等多格式文档的上传和向量化处理。
  2. 文本内容插入:支持直接将文本内容插入到向量数据库中。
  3. 相似性搜索:基于向量存储进行相似性搜索,检索相关文档。
  4. 智能问答:基于知识库内容的智能问答,支持阻塞式和流式两种响应方式。

2. 技术栈与核心依赖

  • Spring Boot 3.4.5
  • Spring AI 1.0.0
  • Java 17
  • PostgreSQL + pgvector
  • 阿里云通义千问(OpenAI兼容模式)

在 pom.xml 中,需要引入以下核心依赖:

<dependencies><!-- Spring Web 用于构建 RESTful API --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.4.0</version></dependency><!-- Spring AI OpenAI Starter --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId></dependency><!-- pgvector 向量存储 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pgvector-store</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-autoconfigure-vector-store-pgvector</artifactId></dependency><!-- PDF 文档读取器 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pdf-document-reader</artifactId></dependency><!-- Tika 文档读取器 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-tika-document-reader</artifactId></dependency>
</dependencies><!-- 依赖管理 -->
<dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>1.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

3. 项目配置

在 src/main/resources/application.yml 文件中,配置应用信息、数据库连接和 AI 模型参数。

server:port: 9000spring:datasource:url: jdbc:postgresql://127.0.0.1:5432/postgresusername: postgrespassword: postgresapplication:name: rag-openai-dashscope-pgvector-exampleservlet:multipart:max-file-size: 10MBmax-request-size: 10MBai:openai:api-key: ${AI_DASHSCOPE_API_KEY}base-url: https://dashscope.aliyuncs.com/compatible-modechat:options:model: qwen-plus-latestembedding:options:model: text-embedding-v3dimensions: 1024vectorstore:pgvector:table-name: mxy_rag_vectorinitialize-schema: truedimensions: 1024index-type: hnsw

重要提示:请将 AI_DASHSCOPE_API_KEY 环境变量设置为你从阿里云获取的有效 API Key。同时确保 PostgreSQL 已安装 pgvector 扩展。

4. 核心代码实现

4.1 主应用类

创建 Spring Boot 主应用类:

package com.alibaba.cloud.ai.example.rag;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class OpenAiDashscopeRagApplication {public static void main(String[] args) {SpringApplication.run(OpenAiDashscopeRagApplication.class, args);}
}

4.2 服务接口

定义知识库服务接口:

package com.alibaba.cloud.ai.example.rag.service;import org.springframework.ai.document.Document;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;import java.util.List;/*** 知识库管理操作的服务接口。*/
public interface KnowledgeBaseService {/*** 将字符串内容插入到指定的向量库中。* @param content 要插入的文本内容*/void insertTextContent(String content);/*** 根据文件类型动态选择Reader加载文件到知识库。* 支持的文件类型:PDF、Word、TXT、Text等** @param file 上传的文件* @return 处理结果信息*/String loadFileByType(MultipartFile file);/*** 基于查询在指定业务类型中搜索相似文档。** @param query 查询字符串* @param topK 返回的相似文档数量* @return 相似文档列表*/List<Document> similaritySearch(String query, int topK);/*** 阻塞式LLM对话接口,根据业务类型获取相关知识库数据进行问答。** @param query 用户查询问题* @param topK 检索的相关文档数量* @return LLM生成的回答*/String chatWithKnowledge(String query, int topK);/*** 流式LLM对话接口,根据业务类型获取相关知识库数据进行问答。** @param query 用户查询问题* @param topK 检索的相关文档数量* @return 流式返回的LLM回答*/Flux<String> chatWithKnowledgeStream(String query,  int topK);
}

4.3 服务实现

实现知识库服务:

package com.alibaba.cloud.ai.example.rag.service.impl;import com.alibaba.cloud.ai.example.rag.service.KnowledgeBaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.stream.Collectors;/*** 知识库服务实现类 */
@Service
public class KnowledgeBaseServiceImpl implements KnowledgeBaseService {private static final Logger logger = LoggerFactory.getLogger(KnowledgeBaseServiceImpl.class);private final VectorStore vectorStore;private final ChatClient chatClient;@Autowiredpublic KnowledgeBaseServiceImpl(VectorStore vectorStore, @Qualifier("openAiChatModel")ChatModel chatModel) {this.vectorStore = vectorStore;this.chatClient = ChatClient.builder(chatModel).defaultAdvisors(new SimpleLoggerAdvisor()).defaultOptions(OpenAiChatOptions.builder().temperature(0.7).build()).build();}/*** 相似性搜索* @param query 查询字符串* @param topK 返回的相似文档数量* @return*/@Overridepublic List<Document> similaritySearch(String query, int topK) {Assert.hasText(query, "查询不能为空");logger.info("执行相似性搜索: query={}, topK={}", query, topK);// 创建业务类型过滤器SearchRequest searchRequest = SearchRequest.builder().query(query).topK(topK).build();List<Document> results = vectorStore.similaritySearch(searchRequest);logger.info("相似性搜索完成,找到 {} 个相关文档", results.size());return results;}/*** 将文本内容插入到向量存储中。** @param content      要插入的文本内容*/@Overridepublic void insertTextContent(String content) {Assert.hasText(content, "文本内容不能为空");logger.info("插入文本内容到向量存储: contentLength={}",  content.length());// 创建文档并设置ID和元数据Document document = new Document(content);// 使用文本分割器处理长文本List<Document> splitDocuments = new TokenTextSplitter().apply(List.of(document));// 添加到向量存储vectorStore.add(splitDocuments);logger.info("文本内容插入完成: 生成文档片段数: {}",  splitDocuments.size());}/*** 根据文件类型加载文件到向量存储中。** @param file         要上传的文件* @return 处理结果消息*/@Overridepublic String loadFileByType(MultipartFile file) {Assert.notNull(file, "文件不能为空");logger.info("开始处理文件上传: fileName={}, fileSize={}", file.getOriginalFilename(),  file.getSize());try {// 创建临时文件Path tempFile = Files.createTempFile("upload_", "_" + file.getOriginalFilename());Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);List<Document> documents;String fileName = file.getOriginalFilename();// 根据文件类型选择合适的文档读取器if (fileName.toLowerCase().endsWith(".pdf")) {// 使用PDF读取器PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(tempFile.toUri().toString());documents = pdfReader.get();logger.info("使用PDF读取器处理文件: {}", fileName);} else {// 使用Tika读取器处理其他类型文件TikaDocumentReader tikaReader = new TikaDocumentReader(tempFile.toUri().toString());documents = tikaReader.get();logger.info("使用Tika读取器处理文件: {}", fileName);}// 添加文档到向量存储vectorStore.add(documents);// 清理临时文件Files.deleteIfExists(tempFile);logger.info("文件处理完成: fileName={}, documentsCount={}", fileName,  documents.size());return String.format("成功处理文件 %s,共生成 %d 个文档片段", fileName, documents.size());} catch (IOException e) {logger.error("文件处理失败: fileName={}, error={}", file.getOriginalFilename(),  e.getMessage(), e);return "文件处理失败: " + e.getMessage();}}/*** {@inheritDoc}*/@Overridepublic String chatWithKnowledge(String query, int topK) {Assert.hasText(query, "查询问题不能为空");logger.info("开始知识库对话,查询: '{}'", query);// 检索相关文档List<Document> relevantDocs = similaritySearch(query, topK);if (relevantDocs.isEmpty()) {logger.warn("未找到与查询相关的文档");return "抱歉,我在知识库中没有找到相关信息来回答您的问题。";}// 构建上下文String context = relevantDocs.stream().map(Document::getText).collect(Collectors.joining("\n\n"));// 构建提示词String prompt = String.format("基于以下知识库内容回答用户问题。如果知识库内容无法回答问题,请明确说明。\n\n" + "知识库内容:\n%s\n\n" + "用户问题:%s\n\n" + "请基于上述知识库内容给出准确、有用的回答:", context, query);// 调用LLM生成回答String answer = chatClient.prompt(prompt).call().content();logger.info("知识库对话完成,查询: '{}'", query);return answer;}/*** {@inheritDoc}*/@Overridepublic Flux<String> chatWithKnowledgeStream(String query, int topK) {Assert.hasText(query, "查询问题不能为空");logger.info("开始流式知识库对话,查询: '{}'", query);try {// 检索相关文档List<Document> relevantDocs = similaritySearch(query, topK);if (relevantDocs.isEmpty()) {logger.warn("未找到与查询相关的文档");return Flux.just("抱歉,我在知识库中没有找到相关信息来回答您的问题。");}// 构建上下文String context = relevantDocs.stream().map(Document::getText).collect(Collectors.joining("\n\n"));// 构建提示词String prompt = String.format("基于以下知识库内容回答用户问题。如果知识库内容无法回答问题,请明确说明。\n\n" + "知识库内容:\n%s\n\n" + "用户问题:%s\n\n" + "请基于上述知识库内容给出准确、有用的回答:", context, query);// 调用LLM生成流式回答return chatClient.prompt(prompt).stream().content();} catch (Exception e) {logger.error("流式知识库对话失败,查询: '{}'", query, e);return Flux.just("对话过程中发生错误: " + e.getMessage());}}
}

4.4 控制器

创建 REST API 控制器:

package com.alibaba.cloud.ai.example.rag.controller;import com.alibaba.cloud.ai.example.rag.service.KnowledgeBaseService;
import org.springframework.ai.document.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;import java.util.List;/*** 知识库管理操作的控制器。* 基于业务类型进行知识库管理,支持广告和AIGC两个业务类型。*/
@RestController
@RequestMapping("/api/v1/knowledge-base")
public class KnowledgeBaseController {private final KnowledgeBaseService knowledgeBaseService;/*** 构造一个新的知识库控制器。** @param knowledgeBaseService 知识库服务实例*/@Autowiredpublic KnowledgeBaseController(KnowledgeBaseService knowledgeBaseService) {this.knowledgeBaseService = knowledgeBaseService;}/*** 将字符串内容插入到向量库中。** @param content 要插入的文本内容* @return 表示成功或失败的响应实体*/@GetMapping("/insert-text")public ResponseEntity<String> insertTextContent(@RequestParam("content") String content) {if (content == null || content.trim().isEmpty()) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("文本内容是必需的");}try {knowledgeBaseService.insertTextContent(content);return ResponseEntity.ok("文本内容已成功插入");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("插入文本内容失败: " + e.getMessage());}}/*** 根据文件类型动态选择Reader加载文件到知识库。* 支持的文件类型:PDF、Word、TXT、Text等** @param file 上传的文件* @return 表示成功或失败的响应实体*/@PostMapping("/upload-file")public ResponseEntity<String> uploadFileByType(@RequestParam("file") MultipartFile file) {if (file.isEmpty()) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("文件为空");}try {String result = knowledgeBaseService.loadFileByType(file);return ResponseEntity.ok(result);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件上传失败: " + e.getMessage());}}/*** 在指定业务类型的知识库中执行相似性搜索。** @param query 搜索查询* @param topK  要检索的相似文档数量(默认为5)* @return 包含相似文档列表或错误消息的响应实体*/@GetMapping("/search")public ResponseEntity<?> similaritySearch(@RequestParam("query") String query,@RequestParam(value = "topK", defaultValue = "5") int topK) {if (query == null || query.trim().isEmpty()) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("查询内容是必需的");}if (topK <= 0) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("topK必须是正整数");}try {List<Document> results = knowledgeBaseService.similaritySearch(query, topK);return ResponseEntity.ok(results);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("相似性搜索过程中发生错误: " + e.getMessage());}}/*** 阻塞式LLM对话接口,根据业务类型获取相关知识库数据进行问答。** @param query 用户查询问题* @param topK  检索的相关文档数量(默认为5)* @return LLM生成的回答*/@GetMapping("/chat")public ResponseEntity<String> chatWithKnowledge(@RequestParam("query") String query,@RequestParam(value = "topK", defaultValue = "5") int topK) {if (query == null || query.trim().isEmpty()) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("查询问题是必需的");}if (topK <= 0) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("topK必须是正整数");}try {String answer = knowledgeBaseService.chatWithKnowledge(query, topK);return ResponseEntity.ok(answer);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("对话过程中发生错误: " + e.getMessage());}}/*** 流式LLM对话接口,根据业务类型获取相关知识库数据进行问答。** @param query 用户查询问题* @param topK  检索的相关文档数量(默认为5)* @return 流式返回的LLM回答*/@GetMapping(value = "/chat-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public ResponseEntity<Flux<String>> chatWithKnowledgeStream(@RequestParam("query") String query,@RequestParam(value = "topK", defaultValue = "5") int topK) {if (query == null || query.trim().isEmpty()) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Flux.just("查询问题是必需的"));}if (topK <= 0) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Flux.just("topK必须是正整数"));}try {Flux<String> answerStream = knowledgeBaseService.chatWithKnowledgeStream(query, topK);return ResponseEntity.ok(answerStream);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Flux.just("流式对话过程中发生错误: " + e.getMessage()));}}
}

5. API接口说明

本应用提供以下 REST API 接口:

5.1 上传文档

支持上传 PDF、Word、TXT 等格式的文档到知识库:

POST /api/v1/knowledge-base/upload-file
Content-Type: multipart/form-datafile=@document.pdf

响应示例

成功处理文件 document.pdf,共生成 42 个文档片段

5.2 插入文本

直接将文本内容插入到向量库中:

POST /api/v1/knowledge-base/insert-text
Content-Type: application/x-www-form-urlencodedcontent=文本内容

响应示例

文本内容已成功插入

5.3 智能问答

基于知识库内容的阻塞式问答:

POST /api/v1/knowledge-base/chat
Content-Type: application/x-www-form-urlencodedquery=你的问题&topK=5

响应示例

根据知识库内容,Spring AI 是一个用于 AI 工程的应用程序框架,它提供了与各种 AI 模型和服务集成的便捷方式...

5.4 流式问答

基于知识库内容的流式问答:

POST /api/v1/knowledge-base/chat-stream
Content-Type: application/x-www-form-urlencodedquery=你的问题&topK=5

响应示例

data: 根据
data: 知识库
data: 内容
data: ,
data: Spring
data: AI
data: 是
data: 一个
data: 用于
data: AI
data: 工程
data: 的
data: 应用程序
data: 框架
data: ...

5.5 相似性搜索

在知识库中搜索相似文档:

GET /api/v1/knowledge-base/search?query=搜索内容&topK=5

响应示例

[{"content": "Spring AI 是一个用于 AI 工程的应用程序框架...","metadata": {...}},{"content": "Spring AI 提供了与各种 AI 模型和服务的集成...","metadata": {...}}
]

6. 运行与测试

6.1 环境准备

  1. 设置 API Key
    # Windows
    set AI_DASHSCOPE_API_KEY=your_api_key# Linux/Mac
    export AI_DASHSCOPE_API_KEY=your_api_key
  2. 确保 PostgreSQL 已安装 pgvector 扩展
    CREATE EXTENSION IF NOT EXISTS vector;
  3. 配置数据库连接:修改 application.yml 中的数据库连接信息。

6.2 启动应用

mvn spring-boot:run

应用启动在 http://localhost:9000

6.3 测试示例

测试 1:上传文档

使用 curl 上传 PDF 文档:

curl -X POST -F "file=@document.pdf" http://localhost:9000/api/v1/knowledge-base/upload-file
测试 2:插入文本

使用 curl 插入文本内容:

curl -X POST -d "content=Spring AI 是一个用于 AI 工程的应用程序框架" http://localhost:9000/api/v1/knowledge-base/insert-text
测试 3:智能问答

使用 curl 进行智能问答:

curl -X POST -d "query=什么是 Spring AI?" http://localhost:9000/api/v1/knowledge-base/chat
测试 4:流式问答

使用 curl 进行流式问答:

curl -X POST -d "query=什么是 Spring AI?" http://localhost:9000/api/v1/knowledge-base/chat-stream
测试 5:相似性搜索

使用 curl 进行相似性搜索:

curl "http://localhost:9000/api/v1/knowledge-base/search?query=Spring AI 框架"

7. 实现思路与扩展建议

7.1 实现思路

本案例的核心思想是"检索增强生成"(Retrieval-Augmented Generation, RAG)。具体实现包括:

  • 文档处理:使用 Spring AI 的文档读取器(PDF、Tika)读取不同格式的文档,并使用文本分割器将长文档分割成适合处理的片段。
  • 向量化存储:使用阿里云的 text-embedding-v3 模型将文本片段转换为向量,并存储在 PostgreSQL + pgvector 中。
  • 相似性搜索:根据用户查询,在向量数据库中检索最相关的文档片段。
  • 增强生成:将检索到的相关文档作为上下文,结合用户问题,生成更加准确和有针对性的回答。

7.2 扩展建议

  • 多业务类型支持:可以扩展系统,支持多个业务类型的知识库,通过元数据区分不同类型的文档。
  • 文档预处理优化:增加文档预处理步骤,如去除噪声、提取关键信息等,提高检索质量。
  • 缓存机制:为频繁查询的问题添加缓存机制,减少向量数据库的查询压力,提高响应速度。
  • 评估与优化:建立评估体系,定期评估 RAG 系统的回答质量,并根据评估结果优化系统参数。
  • 多模态支持:扩展系统以支持图像、音频等多模态内容的处理和检索。
  • 高级检索策略:实现更复杂的检索策略,如混合检索、多阶段检索等,提高检索精度。
http://www.dtcms.com/a/542023.html

相关文章:

  • 免费大空间网站漯河网站建设 千弘网络
  • 一个逆向工具 Ghidra 在 Linux 上的安装和基本使用
  • linux21 线程同步--互斥锁
  • 建设网站的申请信用卡分期付款jsp做的网站代码
  • 致同研究:可变对价的披露示例
  • 做会员卡的网站在线制作海宁市建设局官方网站6
  • 图神经网络入门:用 MLP 作为 Cora 数据集的基线模型
  • 邢台建设银行网站网站挂载
  • 主要的网站开发技术路线网站投放
  • 金昌做网站秦皇岛优化seo
  • 短剧小程序开发的技术新蓝海:交互、社交与AIGC的落地实践
  • 鹤岗做网站怎么把文件发送到网站
  • 2025年--Lc220--589. N 叉树的前序遍历(递归版)-Java版
  • 网站制作好吗上海影视公司
  • 网站公司怎么做的好兴隆大院网站哪个公司做的
  • JS睡眠函数(JS sleep()函数、JS单线程、Event Loop事件循环)假睡眠
  • Windows配置解压版MySQL5(免安装)
  • 营销网站建设阿凡达平面设计主要做什么
  • 有什么好的网站设计思想的博客张掖高端网站建设公司
  • 公司网上注册在哪个网站做产品网站营销推广
  • 网站在线留言的用途建设专业网站哪家技术好
  • 做阀门网站效果怎么样网站建设-部署与发布
  • 达梦数据库笔记--分权
  • 网站建设php书籍免费制作微信小程序的网站
  • 潍城区住房和城乡建设局网站frontpage新建网站
  • 台州企业网站制作公司松江建设投资有限公司网站
  • 如何制作自己的网站教程自建国际网站做电商
  • 队列-概念【Queue1】
  • OCR用于Llamaindex与OCR运用的展望
  • DeepSeek-OCR MoE结构梳理(其它LLM原理类似)