将 Spring AI 和 Elasticsearch 作为矢量数据库
了解如何使用 Spring AI 和 Elasticsearch 构建生产可用的 RAG 应用,并借助向量数据库将 LLM 与您的自有数据集成。
Spring AI现已全面上市,其首个稳定版本 1.0已在Maven Central 上供您下载。让我们立即使用它来构建一个完整的人工智能应用程序,使用你最喜欢的LLM和我们最喜欢的向量数据库。或者直接进入资源库,进行最终应用。
什么是春季人工智能?
Spring AI 1.0 是 Java 中人工智能工程的全面解决方案,在经历了人工智能领域快速发展的重要开发阶段后,现已面世。该版本为人工智能工程师提供了许多重要的新功能。
Java 和 Spring 在人工智能浪潮中处于有利位置。很多公司都在 Spring Boot 上运行他们的产品,这使得将人工智能插入到他们已经在做的事情中变得非常容易。您基本上可以将您的业务逻辑和数据直接与这些人工智能模型连接起来,而不会有太多麻烦。

Spring AI 为各种人工智能模型和技术提供支持,例如
图像模型:根据文字提示生成图像。
转录模型:获取音频源并将其转换为文本。
嵌入模型: 将任意数据转换为矢量,矢量是为语义相似性搜索而优化的数据类型。
聊天模式: 这些 应该很熟悉!毫无疑问,你甚至在某个地方与他们进行过简短的交谈。
在人工智能领域,聊天模型似乎最受关注,这也是理所当然的,因为它们非常棒!你可以让他们帮你修改文件或写诗。(只是先别让他们讲笑话......)。它们很棒,但也存在一些问题。
应对人工智能挑战的春季人工智能解决方案

(图片由 Spring AI 团队负责人 Mark Pollack 博士授权使用)
让我们来看看 Spring AI 中的一些问题及其解决方案。
问题 | 解决方案 | |
|---|---|---|
一致性 | 聊天模式思想开放,容易分心 | 您可以给它们一个系统提示,以规范它们的整体形状和结构 |
内存 | 人工智能模型没有内存,因此无法将某个用户的一条信息与另一条信息关联起来 | 您可以为他们提供一个记忆系统,存储对话的相关部分 |
隔离 | 人工智能模型生活在与世隔绝的小沙箱中,但如果你能让它们使用一些工具--在它们认为必要时可以调用的功能,它们就能做出非常了不起的事情 | Spring AI 支持工具调用,让你可以告诉 AI 模型其环境中的工具,然后它可以要求你调用这些工具。这种多轮互动都是透明处理的 |
私人数据 | 人工智能模型很聪明,但并非无所不知!他们不知道你专有数据库中的内容,我们认为你也不希望他们知道! | 您需要通过填充提示来告知他们的回复--基本上就是在模型查看所提问题之前,使用强大的字符串连接运算符将文本放入请求中。背景资料,如果你愿意的话。如何决定哪些应该发送,哪些不应该发送?使用矢量存储,只选择相关数据并将其继续发送。这被称为检索增强生成,或 RAG |
幻觉 | 人工智能聊天模型喜欢,嗯,聊天!有时,他们自信到可以胡编乱造 | 您需要使用评估--使用一个模型来验证另一个模型的输出--来确认合理的结果 |
当然,任何人工智能应用都不是一座孤岛。如今,现代人工智能系统和服务在与其他系统和服务集成时效果最佳。通过模型上下文协议 (MCP),可以将人工智能应用程序与其他基于 MCP 的服务连接起来,无论这些服务是用什么语言编写的。您可以将所有这些集合到代理 工作流程中,以实现更大的目标。
最棒的是什么?您可以在熟悉的成语和抽象基础上完成所有这些工作,Spring Boot 开发人员都会期待这些成语和抽象:Spring Initializr上提供了方便的启动依赖项,基本上所有东西都可以使用。
Spring AI 提供方便的 Spring Boot 自动配置,为您提供您所熟知和期待的 "惯例重于配置 "设置。Spring AI 通过 Spring Boot 的 Actuator 和 Micrometer 项目支持可观察性。它还能很好地与 GraalVM 和虚拟线程配合使用,让您可以构建超快、高效的人工智能应用程序,并进行扩展。
为什么选择 Elasticsearch
Elasticsearch 是一个全文搜索引擎,这一点你可能知道。那么,我们为什么要在这个项目中使用它呢?嗯,这也是一家矢量商店!数据与全文并存,相当不错。其他显著优势
设置超级简单
开源
可横向扩展
您企业的大部分自由格式数据可能已经存在于 Elasticsearch 集群中
具有完整的搜索引擎功能
综上所述,Elasticsearch 符合优秀矢量存储的所有条件,因此让我们对其进行设置,然后开始构建我们的应用程序!
Elasticsearch 入门
我们需要 Elasticsearch 和 Kibana,这是用户界面控制台,用于与数据库中的数据交互。
借助 Docker 镜像和Elastic.co 主页,您可以在本地机器上尝试一切。访问该网站,向下滚动找到curl 命令,运行该命令并将其直接导入 shell:
curl -fsSL https://elastic.co/start-local | sh
______ _ _ _
| ____| | | | (_)
| |__ | | __ _ ___| |_ _ ___
| __| | |/ _` / __| __| |/ __|
| |____| | (_| \__ \ |_| | (__
|______|_|\__,_|___/\__|_|\___|
-------------------------------------------------
🚀 Run Elasticsearch and Kibana for local testing
-------------------------------------------------
ℹ️ Do not use this script in a production environment
⌛️ Setting up Elasticsearch and Kibana v9.0.0...
- Generated random passwords
- Created the elastic-start-local folder containing the files:
- .env, with settings
- docker-compose.yml, for Docker services
- start/stop/uninstall commands
- Running docker compose up --wait
[+] Running 25/26
✔ kibana_settings Pulled 16.7s
✔ kibana Pulled 26.8s
✔ elasticsearch Pulled 17.4s
[+] Running 6/6
✔ Network elastic-start-local_default Created 0.0s
✔ Volume "elastic-start-local_dev-elasticsearch" Created 0.0s
✔ Volume "elastic-start-local_dev-kibana" Created 0.0s
✔ Container es-local-dev Healthy 12.9s
✔ Container kibana_settings Exited 11.9s
✔ Container kibana-local-dev Healthy 21.8s
🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
Username: elastic
Password: w1GB15uQ
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: SERqaGlKWUJLNVJDODc1UGxjLWE6WFdxSTNvMU5SbVc5NDlKMEhpMzJmZw==
Learn more at https://github.com/elastic/start-local
➜ ~这只需为 Elasticsearch 和 Kibana 提取和配置 Docker 映像,几分钟后,它们就会在本地计算机上运行,并提供完整的连接凭据。
您还可以使用两个不同的 URL 与 Elasticsearch 实例交互。按提示操作,将浏览器指向http://localhost:5601。
请注意打印在控制台上的用户名elastic 和密码:登录时需要使用这两个用户名和密码(在上面的输出示例中,它们分别是elastic 和w1GB15uQ )。
整合应用程序
转到Spring Initializr页面并生成一个新的 Spring AI 项目,其中包含以下依赖项:
Elasticsearch Vector StoreSpring Boot ActuatorGraalVMOpenAIWeb
确保选择最新、最好的 Java 版本(最好是 Java 24(截至本文撰写之时)或更高版本)和您所选择的构建工具。本例中我们使用 Apache Maven。
单击Generate ,然后解压缩项目并将其导入您选择的集成开发环境。(我们使用的是 IntelliJ IDEA)。
首先:让我们为 Spring Boot 应用程序指定连接细节。在application.properties, 中写下以下内容:
spring.elasticsearch.uris=http://localhost:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=w1GB15uQ我们还将利用 Spring AI 的向量存储功能来初始化 Elasticsearch 端所需的数据结构,请具体说明:
spring.ai.vectorstore.elasticsearch.initialize-schema=true在本演示中,我们将使用OpenAI,特别是嵌入模型和聊天模型 (只要Spring AI 支持,您可以随意使用自己喜欢的服务)。
在将数据存储到 Elasticsearch 之前,我们需要嵌入模型来创建数据的嵌入。为了让 OpenAI 正常工作,我们需要指定API key :
spring.ai.openai.api-key=...您可以将其定义为一个环境变量,如SPRING_AI_OPENAI_API_KEY ,以避免在源代码中存储凭据。
我们将上传文件,因此请务必自定义可上传至 servlet 容器的数据量:
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB我们快到了在开始编写代码之前,让我们先预览一下它是如何工作的。
在我们的机器上,我们下载了以下文件(一个棋盘游戏的规则列表),将其重命名为test.pdf 并放入~/Downloads/test.pdf.
文件将被发送到/rag/ingest 端点(根据本地设置替换相应路径):
http --form POST http://localhost:8080/rag/ingest path@/Users/jlong/Downloads/test.pdf这可能需要几秒钟...
在幕后,数据被发送到 OpenAI,OpenAI 正在创建数据嵌入;然后,数据(包括向量和原始文本)被写入 Elasticsearch。
这些数据以及其中的所有嵌入式数据,就是神奇之处。然后,我们就可以使用VectorStore 界面查询 Elasticsearch。
整个流程是这样的
HTTP 客户端会将您选择的 PDF 上传到 Spring 应用程序。
Spring AI 负责从我们的 PDF 中提取文本,并将每页分成 800 个字符的小块。
OpenAI 会为每个块生成向量表示。
然后,分块文本和嵌入都会存储在 Elasticsearch 中。

最后,我们将发出一个查询:
http :8080/rag/query question=="where do you place the reward card after obtaining it?"我们会得到相关的答案:
After obtaining a Reward card, you place it facedown under the Hero card of the hero who received it.
Found at page: 28 of the manual不错!这一切是如何运作的?
HTTP 客户端将问题提交给 Spring 应用程序。
Spring AI 从 OpenAI 获取问题的向量表示。
有了这种嵌入,它就能在存储的 Elasticsearch 块中搜索相似文档,并检索出最相似的文档。
然后,Spring AI 将问题和检索到的上下文发送给 OpenAI,以生成 LLM 答案。
最后,它会返回生成的答案和检索到的上下文的引用。

让我们深入 Java 代码,看看它到底是如何工作的。
首先是Main类:它是 Spring Boot 应用程序的标准主类。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}没什么好看的。继续...
接下来是基本 HTTP 控制器:
@RestController
class RagController {
private final RagService rag;
RagController(RagService rag) {
this.rag = rag;
}
@PostMapping("/rag/ingest")
ResponseEntity<?> ingestPDF(@RequestBody MultipartFile path) {
rag.ingest(path.getResource());
return ResponseEntity.ok().body("Done!");
}
@GetMapping("/rag/query")
ResponseEntity<?> query(@RequestParam String question) {
String response = rag.directRag(question);
return ResponseEntity.ok().body(response);
}
}控制器只是调用我们构建的一项服务,以处理文件摄取并将其写入 Elasticsearch 向量存储,然后方便对同一向量存储进行查询。
让我们来看看服务:
@Service
class RagService {
private final ElasticsearchVectorStore vectorStore;
private final ChatClient ai;
RagService(ElasticsearchVectorStore vectorStore, ChatClient.Builder clientBuilder) {
this.vectorStore = vectorStore;
this.ai = clientBuilder.build();
}
void ingest(Resource path) {
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(path);
List<Document> batch = new TokenTextSplitter().apply(pdfReader.read());
vectorStore.add(batch);
}
// TBD
}这段代码将处理所有的输入:给定一个 Spring FrameworkResource ,它是一个围绕字节的容器,我们使用 Spring AI 的PagePdfDocumentReader 读取 PDF 数据(假定是一个.PDF 文件--在接受任意输入之前,请确保您验证了这一点!),然后使用 Spring AI 的TokenTextSplitter 对其进行标记化,最后将生成的List<Document>s 添加到VectorStore 的实现ElasticsearchVectorStore 中。
您可以使用 Kibana 进行确认:向/rag/ingest 端点发送文件后,打开浏览器localhost:5601 ,然后在左侧菜单中导航至Dev Tools 。在这里,你可以发出查询,与 Elasticsearch 实例中的数据交互。

发出这样的查询

现在,有趣的事情来了:我们怎样才能在响应用户查询时将数据返回?
下面是查询的初步实现,方法名为directRag 。
String directRag(String question) {
// Query the vector store for documents related to the question
List<Document> vectorStoreResult =
vectorStore.doSimilaritySearch(SearchRequest.builder().query(question).topK(5)
.similarityThreshold(0.7).build());
// Merging the documents into a single string
String documents = vectorStoreResult.stream()
.map(Document::getText)
.collect(Collectors.joining(System.lineSeparator()));
// Exit if the vector search didn't find any results
if (documents.isEmpty()) {
return "No relevant context found. Please change your question.";
}
// Setting the prompt with the context
String 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 the
question in the QUESTION section.
If unsure, simply state that you don't know.
DOCUMENTS:
""" + documents
+ """
QUESTION:
""" + question;
// Calling the chat model with the question
String response = ai
.prompt()
.user(prompt)
.call()
.content();
return response +
System.lineSeparator() +
"Found at page: " +
// Retrieving the first ranked page number from the document metadata
vectorStoreResult.getFirst().getMetadata().get(PagePdfDocumentReader.METADATA_START_PAGE_NUMBER) +
" of the manual";
}代码非常简单,但让我们把它分成多个步骤:
使用
VectorStore进行相似性搜索。给定所有结果后,获取底层 Spring AI
Documents 并提取其文本,将所有结果串联成一个结果。将
VectorStore的结果发送给模型,同时发送一个提示,指示模型如何处理这些结果以及用户提出的问题。等待回复并返回。
这就是RAG--检索增强生成。这就是我们使用矢量存储的数据为模型的处理和分析提供信息的理念。既然你已经知道怎么做了,那就希望你永远都不必这么做!反正不是这样的:Spring AI顾问将进一步简化这一过程。
除了在应用程序和矢量存储之间提供一个抽象层外,Advisors 还允许你对给定模型的请求进行预处理和后处理。在构建过程中添加以下依赖项
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>在类中添加另一个名为advisedRag(String question) 的方法:
String advisedRag(String question) {
return this.ai
.prompt()
.user(question)
.advisors(new QuestionAnswerAdvisor(vectorStore))
.call()
.content();
}所有 RAG 模式逻辑都封装在QuestionAnswerAdvisor 中。其他一切都与向ChatModel 提出的任何请求一样!不错!
你可以从 GitHub 获取完整的代码。
结论
在这个演示中,我们使用了 Docker 镜像,并在本地机器上完成了所有操作,但我们的目标是构建可投入生产的人工智能系统和服务。为了实现这一目标,您可以做几件事。
首先,你可以添加Spring Boot Actuator来监控令牌的消耗。代币代表了特定请求对模型造成的复杂性成本(有时是美元和美分)。
您已经在类路径上安装了 Spring Boot Actuator,因此只需指定以下属性即可显示所有指标(由宏伟的Micrometer.io项目捕获):
management.endpoints.web.exposure.include=*重新启动应用程序。进行查询,然后转到: http://localhost:8080/actuator/metrics。搜索 "token",就能看到应用程序使用的令牌信息。请务必留意这一点。当然,您也可以使用 Micrometer与 Elasticsearch 的集成来推送这些指标,并让 Elasticsearch 作为您选择的时间序列数据库!
因此,您应该考虑到,每次我们向 Elasticsearch 等数据存储、OpenAI 或其他网络服务发出请求时,我们都在进行 IO,而且 IO 经常会阻塞执行它的线程。Java 21 及更高版本配备了非阻塞虚拟线程,大大提高了可扩展性。使用
spring.threads.virtual.enabled=true最后,您还需要将应用程序和数据托管在一个能够使其蓬勃发展和扩展的地方。我们相信,您可能已经考虑好在哪里运行应用程序了,但在哪里托管您的数据呢?我们可以推荐弹性云吗?它安全、私密、可扩展,功能齐全。我们最喜欢的部分?如果你愿意,还可以购买无服务器版,在无服务器版中,Elastic 会为你佩戴传呼机,而不是你!
常见问题
Spring AI 是什么?
Spring AI 是一种面向 Java 的 AI 工程解决方案,可用于根据文本提示生成图像、将音频转写为文本、将各类数据向量化,并构建聊天模型。
使用 Spring AI 与 Elasticsearch 的好处是什么?
将 Spring AI 与 Elasticsearch 结合使用的主要优势在于,可以利用 Elasticsearch 作为高性能向量存储来构建 AI 应用。这样可以实现混合搜索(结合语义与传统关键词匹配),从而为 LLM 提供最相关的上下文。




