使用 Spring AI + Elasticsearch 让 RAG 变得简单

作者:来自 Elastic Laura Trotta

使用私人数据定制你的人工智能聊天机器人体验。

Spring AI 最近将 Elasticsearch 添加为向量存储,Elastic 团队为其提供了优化。我们很高兴展示使用 Spring AI 和 Elasticsearch 向量数据库(vector database)在 Java 中构建完整的 RAG 应用程序是多么简单和直观。开发人员现在可以将 Spring 的模块化功能与 Elasticsearch 的高级检索和 AI 工具结合使用,并快速构建用于企业用例的 Spring Boot 应用程序。

在此博客中,我们将使用 Spring AI 构建检索增强生成 (RAG) Java 应用程序,使用新的 Elasticsearch 向量存储集成进行文档存储和检索。你将学习如何配置 Maven 项目、设置所有必要的依赖项以及将 Elasticsearch 集成为向量存储。我们还将指导你阅读和标记 PDF 文档、将其发送到 Elasticsearch 以及使用 AI 模型对其进行查询以提供准确且上下文相关的信息。让我们开始吧!

免责声明

spring-ai-elasticsearch 工件仍处于技术预览阶段,仅在 Spring Milestones 存储库中可用。因此,在正式发布之前,我们不建议在任何生产环境中使用提供的代码。

先决条件

  • Elasticsearch 版本 >= 8.14.0
  • Java 版本 >= 17
  • SpringAI 支持的任何 LLM(完整列表)

用例:Runewars

Runewars 是一款小型游戏,其 40 页手册中解释了一套相当复杂的规则,如果在距离上一场比赛过去几年后再玩这款游戏,就意味着会忘记大部分规则。让我们尝试向 ChatGPT(版本 GPT-4o)询问一些复习内容:

这不仅是一般性的,而且是错误的:奖励卡必须对其他玩家隐藏。很明显,它不知道这个游戏的规则,所以让我们用规则(rules)来增强模型吧!

演示目标

拥有一个能够回答与 Runewars 规则相关的问题的 AI 聊天模型,并提供找到该信息的手册页和响应。用于完成所有这些工作的代码可在 Github 上找到。

项目配置

我们将使用 Apache Maven 作为构建工具创建一个新的 Java 项目,因此让我们相应地设置 POM,从添加 Milestones 和 Snapshot Spring 存储库开始,如 Spring AI 入门中所述:

  <repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository></repositories>

我们还需要导入 Spring AI bom:

<dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>1.0.0-M3</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

我们将依靠 Spring boot 自动配置来设置我们所需要的 bean:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-spring-boot-autoconfigure</artifactId><version>1.0.0-SNAPSHOT</version>
</dependency>

现在介绍 Elasticsearch 和嵌入式模型(例如 OpenAI)的具体模块:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-elasticsearch-store</artifactId><version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai</artifactId><version>1.0.0-SNAPSHOT</version>
</dependency>

最后,Spring 还提供了一个用于获取游戏手册的 PDF 阅读器:

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

完整的 POM 可在此处找到。

Beans

运行应用程序所需的所有 Spring bean 都可以自动装配,因为在这种情况下,我们不需要任何需要自己创建 bean 的特定配置。我们唯一要做的就是向 application.properties 文件提供必要的信息:

spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.chat.client.enabled=truespring.elasticsearch.uris=${ES_SERVER_URL}
spring.elasticsearch.username=${ES_USERNAME}
spring.elasticsearch.password=${ES_PASSWORD}
spring.ai.vectorstore.elasticsearch.initialize-schema=true

如果正确设置了这些属性,Spring 框架将自动选择向量存储和嵌入/聊天模型类的正确实现。如果你使用不同的 LLM 执行此操作,请务必使用以下方法配置适当的向量维度:spring.ai.vectorstore.elasticsearch.dimensions。例如,OpenAI 的向量维度为 1536,这是默认值,因此我们不需要设置该属性。

有关所有可能的配置参数的更多信息,请参阅官方 Elasticsearch Vector Store 文档。

服务

首先,创建一个新的服务类(Service),其中向量存储和聊天客户端 bean 将自动装配:

@Service
public class RagService {private ElasticsearchVectorStore vectorStore;private ChatClient chatClient;public RagService(ElasticsearchVectorStore vectorStore, ChatClient.Builder clientBuilder) {this.vectorStore = vectorStore;this.chatClient = clientBuilder.build();}
}

它将有两个方法:

  • 一种是从给定路径读取 PDF 文件,将其转换为 SpringAI 文档格式并将其发送到 Elasticsearch。
  • 另一种是查询 Elasticsearch 中与问题相关的文档,然后将这些文档提供给 LLM,以便其给出准确的答复。

内容提取

让我们从第一个开始:

public void ingestPDF(String path) {// Spring AI utility class to read a PDF file page by pagePagePdfDocumentReader pdfReader = new PagePdfDocumentReader(path);List<Document> docbatch = pdfReader.read();// Sending batch of documents to vector store// applying tokenizerdocbatch = new TokenTextSplitter().apply(docbatch);vectorStore.doAdd(docbatch);
}

请注意,在发送到向量存储之前,这批文档如何经过拆分过程:这称为 “标记化(tokenization)”,这意味着文本被分成更小的标记,LLM 可以更有效地对其进行分类和管理。SpringAI 提供了 TokenTextSplitter,可以对其进行自定义以调整块的大小和所需的块数;在这种情况下,默认配置就足够了,因此我们的页面将被分成 800 个字符长的块。

这似乎太简单了,我们只是将字符串发送到数据库吗?与任何与 Spring 相关的事情一样,底层发生了很多事情,隐藏在高层次的抽象之下:文档被发送到嵌入模型进行嵌入,或转换为内容的数字表示,称为向量。文档及其相应的嵌入被索引到 Elasticsearch 向量数据库中,该数据库经过优化,可在提取和查询时处理此类数据。

查询

第二种方法将实现用户与聊天客户端的交互:

public String queryLLM(String question) {// Querying the vector store for documents related to the questionList<Document> vectorStoreResult =vectorStore.doSimilaritySearch(SearchRequest.query(question).withTopK(5).withSimilarityThreshold(0.0));// Merging the documents into a single stringString documents = vectorStoreResult.stream().map(Document::getContent).collect(Collectors.joining(System.lineSeparator()));// Setting the prompt with the contextString prompt = """You're assisting with providing the rules of the tabletop game Runewars.Use the information from the DOCUMENTS section to provide accurate answers to thequestion in the QUESTION section.If unsure, simply state that you don't know.DOCUMENTS:""" + documents+ """QUESTION:""" + question;// Calling the chat model with the questionString response = chatClient.prompt().user(prompt).call().content();return response +System.lineSeparator() +"Found at page: " +// Retrieving the first ranked page number from the document metadatavectorStoreResult.get(0).getMetadata().get(PagePdfDocumentReader.METADATA_START_PAGE_NUMBER) +" of the manual";
}

问题首先被发送到 Elasticsearch 向量存储,以便它可以回复它认为与查询更相关的文档。它是如何做到的?正如调用的方法所说,通过执行相似性搜索(similarity search),或者更详细地说,KNN 搜索:简而言之,将文档的嵌入与问题(也已嵌入)进行比较,并返回被认为更接近的嵌入。

在这种情况下,我们希望答案准确,这意味着我们不希望出现幻觉,这就是为什么 withSimilarityThreshold 参数设置为 0。此外,考虑到数据的性质(手册),我们知道不会有太多重复,所以我们希望在不超过 5 个不同的页面中找到我们想要的内容,因此 withTopK 参数设置为 5。

控制器

测试 Spring 服务最简单的方法是构建一个调用它的基本 RestController:

@RestController
@RequestMapping("rag")
public class RagController {private final RagService ragService;@Autowiredpublic RagController(RagService ragService) {this.ragService = ragService;}@PostMapping("/ingestPdf")public ResponseEntity ingestPDF(String path) {try {ragService.ingestPDF(path);return ResponseEntity.ok().body("Done!");} catch (Exception e) {System.out.println(e.getMessage());return ResponseEntity.internalServerError().build();}}@PostMapping("/query")public ResponseEntity query(String question) {try {String response = ragService.queryLLM(question);return ResponseEntity.ok().body(response);} catch (Exception e) {System.out.println(e.getMessage());return ResponseEntity.internalServerError().build();}}
}

运行 Elasticsearch

连接到 Elasticsearch Cloud 实例是测试应用程序的最快方法,但如果你无法访问它,也没关系!你可以使用 start-local 开始使用 Elasticsearch 的本地实例,这是一个利用 Docker 快速配置和运行服务器和 Kibana 实例的脚本。

curl -fsSL https://elastic.co/start-local | sh

运行应用程序

代码写完了!让我们在熟悉的 8080 端口上启动应用程序,并使用 curl 调用它(懒惰才是这里的关键主题):

curl -XPOST "http://localhost:8080/rag/ingestPdf" --header "Content-Type: text/plain" --data "where-you-downloaded-the-pdf"

请记住,嵌入是一项昂贵的操作,使用功能较弱的 LLM 意味着此调用可能需要一段时间才能完成。

最后,我们一开始提出的问题:

curl -XPOST "http://localhost:8080/rag/query" --header "Content-Type: text/plain" --data "where do you place the reward card after obtaining it?"
In Runewars, after a hero receives a Reward card, the controlling player draws the top card from the Reward deck, looks at it, and places it facedown under the Hero card of the hero who received it. 
The player does not flip the Reward card faceup until they wish to use its ability. Found at page 27 of the manual.

很棒,不是吗?聊天机器人精通 Runewars 的复杂规则,随时准备回答我们的所有问题。

奖励:Ollama

得益于 SpringAI 的抽象,我们可以通过更改几行配置代码轻松使用另一种语言模型。让我们从 POM 依赖项开始,用 Ollama 的本地实例替换 OpenAI:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama</artifactId><version>1.0.0-SNAPSHOT</version>
</dependency>

然后是 application.properties 中的属性:

spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.init.pull-model-strategy=always
spring.ai.chat.client.enabled=truespring.elasticsearch.uris=${ES_SERVER_URL}
spring.elasticsearch.username=${ES_USERNAME}
spring.elasticsearch.password=${ES_PASSWORD}
spring.ai.vectorstore.elasticsearch.initialize-schema=true
spring.ai.vectorstore.elasticsearch.dimensions=1024

pull-model-strategy 属性将方便地为你提取默认模型,因此如果你已完全配置好所有内容,请确保将其设置为 never 以禁用它。还要记得检查正确的向量维度,例如,对于 mxbai-embed-large(默认的 Ollama 嵌入模型),它是 1024。

就是这样,其他一切都保持不变!当然,更改嵌入模型意味着也必须更改 Elasticsearch 索引,因为旧嵌入将与新嵌入不兼容。

结论

按照这些步骤,你应该能够设置一个功能齐全的 RAG 应用程序,其复杂程度与基于 Spring 的基本 CRUD 应用程序相同。完整代码可在此处找到。如有任何问题或疑问,请通过我们的讨论页面与我们联系。

Elasticsearch 包含许多新功能,可帮助你为你的用例构建最佳搜索解决方案。深入了解我们的示例笔记本以了解更多信息,开始免费云试用,或立即在你的本地机器上试用 Elastic。

原文:RAG made easy with Spring AI + Elasticsearch - Search Labs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/480384.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言:深入理解指针

一.内存和地址 我们知道计算机上CPU&#xff08;中央处理器&#xff09;在处理数据的时候&#xff0c;需要的数据是在内存中读取的&#xff0c;处理后的数据也会放回内存中&#xff0c;那我们买电脑的时候&#xff0c;电脑上内存是 8GB/16GB/32GB 等&#xff0c;那这些内存空间…

Spring Boot整合EasyExcel

文章目录 EasyExcel简介Spring Boot整合EasyExcel一、单sheet写操作二、多sheet写数据三、读操作 EasyExcel简介 1、EasyExcel 是一个基于 Java 的简单、省内存的读写 Excel 的开源项目。在尽可能节约内存的情况下支持读写百 M 的 Excel&#xff08;没有一次性将数据读取到内存…

Windsurf可以上传图片开发UI了

背景 曾经羡慕Cursor的“画图”开发功能&#xff0c;这不Windsurf安排上了。 Upload Images to Cascade Cascade now supports uploading images on premium models Ask Cascade to build or tweak UI from on image upload New keybindings Keybindings to navigate betwe…

Linux中使用ping提示“未知的名称或服务”

Linux中使用ping提示“未知的名称或服务” 问题&#xff1a;在linux系统中使用ping、telnet命令提示“未知的名称或服务”或 bad address。以centos系统为例&#xff1a; 问题原因&#xff1a; 1、未安装ping服务 2、操作系统未设置DNS&#xff08;尝试ping IP地址&#xff0…

【C++】深入解析 using namespace std 语句

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;什么是 std&#xff1f;&#x1f4af;using namespace std; 的作用&#x1f4af;为什么需要 std 命名空间&#xff1f;&#x1f4af;using namespace std; 的优缺点优点缺点…

Android音频框架总结

1、AudioFlinger&#xff1a;接收多个APP的数据&#xff0c;合并下发&#xff1b;是策略的执行者&#xff0c;例如具体如何与音频设备通信&#xff0c;如何维护现有系统中的音频设备&#xff0c;以及多个音频流的混音如何处理等等都得由它来完 成。 AudioFlinger主要包含3个主…

Jenkins Nginx Vue项目自动化部署

目录 一、环境准备 1.1 Jenkins搭建 1.2 NVM和Nodejs安装 1.3 Nginx安装 二、Jenkins配置 2.1 相关插件安装 2.2 全局工具安装 2.3 环境变量配置 2.4 邮箱配置&#xff08;构建后发送邮件&#xff09; 2.5 任务配置 三、Nginx配置 3.1 配置路由转发 四、部署项目 …

BASLER工业相机维修不能触发拍照如何处理解决这个问题

BASLER工业相机维修不能触发拍照如何处理解决这个问题&#xff1f;最近遇到挺多工业相机维修咨询这个不能触发拍照的案例&#xff0c;所以今天优米佳维修的技术就抽空整理了这篇关于BASLER相机不能触发拍照的处理方法分享给大家。 当碰到巴斯勒工业相机不能触发拍照的问题&…

68000汇编实战01-编程基础

文章目录 简介产生背景应用领域 语言学习EASy68K帮助文档IDE使用 编程语言commentslabels开始标签指令标签位置标签 opcode 操作码常用操作码数据传送算术运算逻辑运算控制流分支跳转地址跳转子程序跳转 位操作比较堆栈操作 IO操作码其他操作码 directives 指令DC指令EQU 指令S…

wsl2的Ubuntu18.04安装ros和anaconda

参考&#xff1a;超详细 WSL2 安装 ros 和 anaconda_wsl2安装anaconda-CSDN博客 一.安装ros 1. 更换系统源 输入 wget http://fishros.com/install -O fishros && . fishros 和上面的链接一样&#xff0c;依次输入5-2-1 2. 安装ros 输入 wget http://fishros.c…

如何为 ext2/ext3/ext4 文件系统的 /dev/centos/root 增加 800G 空间

如何为 ext2/ext3/ext4 文件系统的 /dev/centos/root 增加 800G 空间 一、引言二、检查当前磁盘和分区状态1. 使用 `df` 命令检查磁盘使用情况2. 使用 `lsblk` 命令查看分区结构3. 使用 `fdisk` 或 `parted` 命令查看详细的分区信息三、扩展逻辑卷(如果使用 LVM)1. 检查 LVM …

【Linux打怪升级记 | 报错02】-bash: 警告:setlocale: LC_TIME: 无法改变区域选项 (zh_CN.UTF-8)

&#x1f5fa;️博客地图 &#x1f4cd;1、报错发现 &#x1f4cd;2、原因分析 &#x1f4cd;3、解决办法 &#x1f4cd;4、测试结果 1、报错发现 装好了CentOS操作系统&#xff0c;使用ssh远程登陆CentOS&#xff0c;出现如下告警信息&#xff1a; bash: 警告:setlocale…

【数据结构】双向链表、单向循环链表、双向循环链表、栈、链栈

目录 一、双向链表 定义类和封装函数以及测试样例如下&#xff1a; 注意事项&#xff1a; 二、循环链表 单循环列表的类和函数封装如下&#xff1a; 注意事项&#xff1a; 三、双向循环链表 结点类和双循环链表的定义部分 函数封装之判空和尾插 双循环链表遍历 双循…

week 6 - SQL Select II

Overview 1. Joins 包括交叉连接&#xff08;Cross&#xff09;、内连接&#xff08;Inner&#xff09;、自然连接&#xff08;Natural&#xff09;、外连接&#xff08;Outer&#xff09; 2. ORDER BY to produce ordered output 3. 聚合函数&#xff08;Aggregate Functio…

systemverilog约束中:=和:/的区别

“x dist { [100:102] : 1, 200 : 2, 300 : 5}” 意味着其值等于100或101或102或200或300其中之一&#xff0c; 其权重比例为1:1:1:2:5 “x dist { [100:102] :/ 1, 200 : 2, 300 : 5}” 意味着等于100&#xff0c;101&#xff0c;102或200&#xff0c;或300其…

[Python/网络安全] Git漏洞之Githack工具基本安装及使用详析

前言 本文仅分享Githack工具基本安装及使用相关知识&#xff0c;不承担任何法律责任。 Git是一个非常流行的开源分布式版本控制系统&#xff0c;它被广泛用于协同开发和代码管理。许多网站和应用程序都使用Git作为其代码管理系统&#xff0c;并将其部署到生产环境中以维护其代…

NFT Insider #157:The Sandbox 开启新一期 VoxEdit 比赛

市场数据 加密艺术及收藏品新闻 Artnames 项目上线&#xff0c;将用户姓名转化为个性化 NFT 艺术品 由知名数字艺术家 Arrotu 发起的生成艺术项目「Artnames」正式上线&#xff0c;利用区块链技术将用户姓名转化为独一无二的 NFT 艺术品。该项目于 11 月 14 日启动&#xff0…

计算机是如何工作的

1. 冯诺依曼体系 CPU 中央处理器: 进行算术运算和逻辑判断 存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储) 输入设备: 用户给计算机发号施令的设备 输出设备: 计算机个用户汇报结果的设备 1&#xff09;针对存储空间&#xff1a; 硬盘 > 内存 >> CPU …

简单好用的折线图绘制!

折线图的概念及作用&#xff1a; 折线图&#xff08;Line Chart&#xff09;是一种常见的图表类型&#xff0c;用于展示数据的变化趋势或时间序列数据。它通过一系列的数据点&#xff08;通常表示为坐标系中的点&#xff09;与这些点之间的线段相连&#xff0c;直观地展示变量…

【拥抱AI】Milvus 如何处理 TB 级别的大规模向量数据?

处理 TB 级别的大规模向量数据是 Milvus 的核心优势之一。Milvus 通过分布式架构、高效的索引算法和优化的数据管理策略来实现这一目标。下面将详细介绍 Milvus 如何处理 TB 级别向量数据的流程&#xff0c;包括插入代码示例、指令以及流程图。 1. 分布式架构 Milvus 使用分…