经过一段重要的开发周期,Spring AI 1.0 现已发布,这是一个针对 Java 中人工智能工程的全面解决方案。此次发布包含了许多针对人工智能工程师的全新功能。
Java 和 Spring 正处于人工智能浪潮的前沿。许多公司已经在使用 Spring Boot 运行他们的业务,这使得将人工智能集成到现有系统变得异常简单。你可以轻松地将业务逻辑和数据与人工智能模型连接起来,而无需太多麻烦。
Spring AI 支持多种人工智能模型和技术。图像模型可以根据文本提示生成图像。转录模型可以将音频转换为文本。嵌入模型是将任意数据转换为向量的模型,这些向量是针对语义相似性搜索优化的数据类型。聊天模型想必大家已经很熟悉了!你肯定已经和某个聊天模型有过简短的对话。聊天模型是人工智能领域中最受关注的部分。它们非常出色,可以帮助你校对文档或写诗。(不过别指望它们讲笑话……至少现在还不行。)它们很棒,但也存在一些问题。
(文中配图已获得 Spring AI 团队负责人 Dr. Mark Pollack 的授权)
让我们来探讨一下这些问题及其在 Spring AI 中的解决方案。聊天模型往往容易跑题,你需要给它们一个系统提示来规范它们的整体结构和形式。
人工智能模型没有记忆。你需要帮助它们将来自特定用户的某条消息与其他消息关联起来,为它们提供记忆。
人工智能模型生活在孤立的沙盒中,但如果给予它们访问工具的权限——即它们可以在需要时调用的函数,它们就能发挥出惊人的能力。Spring AI 支持工具调用,允许你告知人工智能模型其环境中可用的工具,然后它可以在需要时请求你调用这些工具。这种多轮交互过程将为你透明处理。
人工智能模型很聪明,但它们并非无所不知!它们不知道你的专有数据库中有什么内容——而且我想你也不希望它们知道!因此,你需要通过填充提示来告知它们的响应,基本上是使用强大的字符串连接操作符将文本放入请求中,模型会在查看问题之前考虑这些内容。如果喜欢,可以将其视为背景信息。
你可以填充大量数据,但并非无限量。那么如何决定应该发送哪些数据,哪些不应该发送呢?使用向量存储来选择相关数据并继续发送。这被称为检索增强生成(Retrieval-Augmented Generation,简称 RAG)。
人工智能聊天模型喜欢聊天,有时甚至会非常自信地编造内容,因此你需要使用评估——用一个模型来验证另一个模型的输出,以确保结果合理。
当然,没有任何人工智能应用是孤立存在的。如今,现代人工智能系统和服务在与其他系统和服务集成时效果最佳。模型上下文协议(Model Context Protocol,简称 MCP)可以让你将人工智能应用与其他基于 MCP 的服务连接起来,无论它们是用什么语言编写的。你可以将所有这些内容组装成驱动实现更大目标的代理工作流。
你可以在熟悉的 Spring Boot 开发范式和抽象基础上完成所有这些操作:Spring Initializr 提供了几乎所有内容的便捷启动依赖项。Spring AI 提供了便捷的 Spring Boot 自动配置,为你提供你所熟悉和期待的约定优于配置(Convention-over-Configuration)设置。Spring AI 还支持通过 Spring Boot 的 Actuator 和 Micrometer 项目进行可观察性操作。它与 GraalVM 和虚拟线程配合良好,让你能够构建快速且高效的人工智能应用,并实现扩展。
这对你们来说意味着什么呢?
这意味着,如果你已经部署了 Oracle 数据库,那么你已经处于这些变革性新范式的有利位置!在本文中,我们将探讨如何开始使用 Oracle 数据库及其向量支持,了解它能带来什么,以及它如何在以人工智能为先、以数据为驱动的 Spring Boot 和 Spring AI 应用中发挥重要作用。
如果你已经安装了 Oracle 数据库,那么可以跳过这一部分。对于其他人,请注意:Oracle 是无论存储格式如何(无论是关系型、文档型(JSON)、图形、空间、时间序列、向量等)的顶级数据库。它提供了比你能想象的更多的功能。你知道吗?Oracle 数据库可以免费运行,不仅用于开发,还可以在生产环境中使用相当规模和大小的部署。它被称为 Oracle Database Free。当前版本具体称为 Oracle Database 23ai Free。它包含了许多 Oracle Database 标准版和企业版的功能,但有一些资源限制,包括 1 个 CPU、2 GB 内存和 12 GB 磁盘上的用户数据。
这已经足够让大多数应用起步了!
其许可非常宽松,允许你在开发、测试和生产环境中免费运行。Oracle Database 23ai Free 可用于 Windows、Linux 和 macOS,支持 Intel 和 ARM 架构。你还可以在容器运行时(如 Docker 和 Podman)中运行它。
你可以从这里获取容器镜像、更多功能详情以及所有其他信息。
让我们看看如何使用 Oracle 数据库和 Spring AI 构建一个以人工智能为先、可扩展、可观察且快速的应用程序。
示例应用
我们将构建一个虚构的狗领养机构,名为“Pooch Palace”。它就像一个可以在线找到和领养狗的收容所!就像大多数收容所一样,人们会想要和某个人交谈,去“面试”这些狗。我们将构建一个服务来促进这一过程,即一个助手。
我们将在应用启动时在关系型数据库中安装一些狗的信息。我们的目标是构建一个助手,帮助我们找到梦寐以求的狗,比如“Prancer”,它被描述为“一种看起来像小精灵的恶魔般的、神经质的、憎恨人类、憎恨动物、憎恨儿童的狗”。这只狗太棒了。你可能听说过它。几年前,当它的主人想为它找一个新家时,它的广告在网上疯传。这是原始帖子,出现在 BuzzFeed News、USA Today 和纽约时报。
我们将构建一个简单的 HTTP 端点,使用 Spring AI 与 LLM(在此示例中,我们将使用 OpenAI,但你可以使用任何你想要的,包括 Ollama、Amazon Bedrock、Google Gemini、HuggingFace 以及许多其他支持 Spring AI 的服务)集成,请求人工智能模型通过分析我们的问题,查看收容所中的狗(以及我们数据库中的狗)来帮助我们找到最适合我们的狗。
首先,前往 Spring Initializr,指定 artifact ID 为 oracle,并为你的 Spring AI 项目添加以下依赖项:Web、Actuator、GraalVM、Devtools、OpenAI、Oracle 向量数据库和 Docker Compose。我们将使用 Java 24 和 Apache Maven,但你可以选择你喜欢的方式。
下载 .zip 文件,用你最喜欢的 IDE 打开它,我们就可以开始了。首先要做的是自定义 docker-compose.yml 文件。它已经生成得可以让 Spring Boot 自动正确连接,但端口不会暴露,因此你无法连接到它。此外,用于连接的用户名和密码也未指定。因此,将你的 docker-compose.yml 修改如下:
services:
oracle:
image: 'gvenzl/oracle-free:latest'
environment:
- 'ORACLE_PASSWORD=secret'
- 'APP_USER=test'
- 'APP_USER_PASSWORD=test'
ports:
- '1521:1521'
这将为开发者提供默认的用户名和密码,并将端口导出,以便你可以从客户端或 IDE 客户端连接到这个关系型数据库。
在 application.properties
中添加以下内容,我们将逐步解释这些更改:
spring.application.name=oracle
# 可观察性
management.endpoints.web.exposure.include=*
# 扩展性
spring.threads.virtual.enabled=true
# Docker
spring.docker.compose.lifecycle-management=start_only
# SQL
spring.sql.init.mode=always
spring.datasource.password=test
spring.datasource.username=test
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/FREEPDB1
# 人工智能
spring.ai.vectorstore.oracle.remove-existing-vector-store-table=true
spring.ai.vectorstore.oracle.initialize-schema=true
spring.ai.vectorstore.oracle.dimensions=1536
spring.ai.vectorstore.oracle.index-type=ivf
spring.ai.vectorstore.oracle.distance-type=cosine
我们在文档中用注释标记了某些部分。在“扩展性”部分,我们启用了虚拟线程,稍后会讨论这一点。
“可观察性”部分告诉 Spring Boot Actuator(它会显示有关我们应用的信息)展示所有端点。在生产环境中,你可能不希望在没有锁定这些端点的情况下这么做,但在演示中,了解当前可见的内容肯定是有用的。
在“Docker”部分,我们告诉 Spring Boot 的 Docker Compose 支持不要在每次启动时自动停止并重新启动 Docker 镜像。相反,它只会启动镜像(如果尚未运行)。
在“SQL”部分,我们指定了连接到 Oracle 数据库的详细信息。即使你没有指定这些值,开发过程中仍然可以保持高效,但如果你希望用你最喜欢的数据库工具连接到数据库,这些值会很方便。
如果你希望通过命令行连接到这个数据库,安装 Oracle SQLcl 命令行工具,然后运行以下命令:
sql test/test@localhost:1521/FREEPDB1
spring.sql.init.mode=always
属性非常有趣,因为它告诉 Spring Boot
自动运行 src/main/resources
文件夹中的 schema.sql
和 data.sql
,以初始化我们的数据库。这将方便我们确保示例应用具有有效的架构和数据(我们稍后会查看这些内容)。
在“人工智能”部分,我们配置了如何让 Spring AI 使用 Oracle 数据库作为向量存储,以支持稍后的语义相似性搜索。
这里有一个你没有看到的属性:你的 OpenAI 账户凭证。Spring AI 提供了 Spring Boot 风格的自动配置,并将自动连接到你指定的模型,前提是你提供了凭证。这个应用使用了 OpenAI,因此你需要指定另一个属性(在注册免费账户并获取 API 密钥后):spring.ai.openai.api-key=<YOUR_KEY>。
或者,你也可以使用 Oracle 提供的出色人工智能模型!Spring AI 支持它们,尽管我们可能会在后续文章中更深入地探讨。
现在,让我们为数据库填充有效的架构和数据。创建 src/main/resources/schema.sql
:
create table if not exists dog
(
id integer primary key,
name varchar(255) not null,
owner varchar(255) null,
description varchar(255) not null
);
创建 src/main/resources/data.sql
:
delete from dog;
insert into dog (id, name, description)
values (87, ‘Bailey’, ‘A tan Dachshund known for being playful.’);
insert into dog (id, name, description)
values (89, ‘Charlie’, ‘A black Bulldog known for being curious.’);
insert into dog (id, name, description)
values (67, ‘Cooper’, ‘A tan Boxer known for being affectionate.’);
insert into dog (id, name, description)
values (73, ‘Max’, ‘A brindle Dachshund known for being energetic.’);
insert into dog (id, name, description)
values (3, ‘Buddy’, ‘A Poodle known for being calm.’);
insert into dog (id, name, description)
values (93, ‘Duke’, ‘A white German Shepherd known for being friendly.’);
insert into dog (id, name, description)
values (63, ‘Jasper’, ‘A grey Shih Tzu known for being protective.’);
insert into dog (id, name, description)
values (69, ‘Toby’, ‘A grey Doberman known for being playful.’);
insert into dog (id, name, description)
values (101, ‘Nala’, ‘A spotted German Shepherd known for being loyal.’);
insert into dog (id, name, description)
values (61, ‘Penny’, ‘A white Great Dane known for being protective.’);
insert into dog (id, name, description)
values (1, ‘Bella’, ‘A golden Poodle known for being calm.’);
insert into dog (id, name, description)
values (91, ‘Willow’, ‘A brindle Great Dane known for being calm.’);
insert into dog (id, name, description)
values (5, ‘Daisy’, ‘A spotted Poodle known for being affectionate.’);
insert into dog (id, name, description)
values (95, ‘Mia’, ‘A grey Great Dane known for being loyal.’);
insert into dog (id, name, description)
values (71, ‘Molly’, ‘A golden Chihuahua known for being curious.’);
insert into dog (id, name, description)
values (65, ‘Ruby’, ‘A white Great Dane known for being protective.’);
insert into dog (id, name, description)
values (45, ‘Prancer’, ‘A demonic, neurotic, man hating, animal hating, children hating dogs that look like gremlins.’);
(现在是一个很好的时机提醒你,这个示例的代码也可以在网上找到,你可以直接复制粘贴到你的应用中!)
通过这些更改——.SQL 文件、compose.yml 文件和 application.properties——你已经拥有一个有效(尽管是空的)应用。你可以在命令行上运行应用:
./mvnw spring-boot:run
或者直接在你的 IDE 中运行主类。
跳转到应用的主类 OracleApplication.java。这是一个标准且空的 Spring Boot 应用。
我们将与数据库(Oracle)进行交互,以存储狗的记录。让我们构建一个数据访问层——一个实体和一个仓库
interface DogRepository extends ListCrudRepository<Dog, Integer> {}
record Dog(@Id int id, String name, String owner, String description) {}
第一个定义提供了一个方便的数据访问层。你可以将 DogRepository 注入到代码中,并用它来(例如)从 dog 表中查找所有 Dog 对象。
我们将构建一个简单的 HTTP 端点(一个 Spring MVC 控制器),接收用户的请求,然后通过方便的 Spring AI ChatClient 将它们转发到自动配置的聊天模型。
以下是我们的控制器代码。我们将在示例后解释它的作用
@Controller
@ResponseBody
class DogAssistantController {
private final ChatClient ai;
DogAssistantController(ChatClient.Builder ai, VectorStore vectorStore, ChatMemory chatMemory) {var systemPrompt = “””
You are an AI powered assistant to help people adopt a dog from the adoption
agency named Pooch Palace with locations in Antwerp, Seoul, Tokyo, Singapore, Paris,
Mumbai, New Delhi, Barcelona, San Francisco, and London. Information about the dogs available
will be presented below. If there is no information, then return a polite response suggesting we
don‘t have any dogs available.
“””;
var ragAdvisor = new QuestionAnswerAdvisor(vectorStore);
var memoryAdvisor = new PromptChatMemoryAdvisor(chatMemory);
this.ai = ai
.defaultSystem(systemPrompt)
.defaultAdvisors(ragAdvisor, memoryAdvisor)
.build();
}
@GetMapping(“/{user}/inquire”)
String inquire(@PathVariable String user, @RequestParam String question) {
return this.ai.prompt()
.advisors(a -> a.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, user))
.user(question)
.call()
.content();
}
}
这是一个简单的 Spring MVC 控制器,暴露了一个端点 /inquire
,并期望一个名为 question 的请求参数。为了完成工作,它将使用 Spring AI 的 ChatClient
。我们通过将构建器注入到构造函数中,然后在构建的实例上配置一些有趣的默认值,这些默认值将应用于通过这个 ChatClient
处理的所有请求。请记住,在同一个应用中创建许多具有不同用途的不同 ChatClient
是非常简单的。
我们首先配置了一个系统提示(system prompt)。系统提示告诉 LLM(大型语言模型)它应该如何表现;它为 LLM 框定了问题并赋予了它一个角色。在这里,我们指示 LLM 成为一个名为“Pooch Palace”虚构狗领养机构的助手。
我们还配置了一个 QuestionAnswerAdvisor
。QuestionAnswerAdvisor
是一个拦截器,它知道如何查询向量数据库(配置的 Oracle 数据库),并获取可能与我们的请求相关的任何数据,然后将其放入请求中,再将请求发送到后端的 LLM。顺便说一下,你需要手动将以下依赖项添加到 pom.xml
中:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
你需要配置两个 Bean:
@Bean
JdbcChatMemoryRepository jdbcChatMemoryRepository(DataSource dataSource) {
return JdbcChatMemoryRepository.builder().jdbcTemplate(new JdbcTemplate(dataSource)).build();
}
@BeanMessageWindowChatMemory jdbcChatMemory(JdbcChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build();
}
这些 Bean 依赖于数据库中有一个关系表来存储对话内容。在 schema.sql 的末尾添加以下内容:
CREATE TABLE if not exists ai_chat_memory
(
conversation_id VARCHAR2(36 CHAR) NOT NULL,
content CLOB NOT NULL,
type VARCHAR2(10 CHAR) NOT NULL,
"timestamp" TIMESTAMP NOT NULL,
CONSTRAINT ai
_chat_memory_type_chk CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
);
CREATE INDEX if not exists ai_chat_memory_conversation_id_timestamp_idxON ai_chat_memory(conversation_id, “timestamp”);
如果你查看控制器方法 inquire,你会发现我们使用用户名作为存储对话的键。你不会意外地获取到别人的对话,反之亦然。这个例子有点简单,因为用户名是任意的,来自 URL 的路径变量。实际上,给这个应用添加 Spring Security 并使用当前认证用户的名字作为键是非常简单的。
如果你现在运行一个请求,你什么也得不到。我们虽然在 dog 表中有狗的信息,但可能有数万亿条记录!我们不想在每次 LLM 请求中发送所有数据,而只发送那些可能匹配的数据。因此,我们将它们存储在 Oracle 数据库的向量类型中。QuestionAnswerAdvisor 会知道如何查询这些向量数据,以找到概念上相似的数据,并仅将查询结果的子集与问题一起发送给模型。以下是初始化向量数据的方法:
@Bean
ApplicationRunner dogumentInitializer(JdbcClient db, VectorStore vectorStore, DogRepository repository) {
return args -> repository.findAll().forEach(dog -> {
var dogument = new Document("id: %s, name: %s, description: %s".formatted(
dog.id(), dog.name(), dog.description()
));
vectorStore.add(List.of(dogument));
});
}
这个定义注册了一个 ApplicationRunner
对象,Spring Boot 会在应用启动时调用它。在其中,我们注入了 DogRepository
和 VectorStore
,遍历数据库中的所有狗,并将它们转换为 Spring AI 的 Document
对象。这里没有固定的模式,只要确保每个文档的字符串格式一致即可。
重新启动应用,让我们一起测试一下!我们使用 http
命令行工具(你可以使用 curl
或其他任何你喜欢的 HTTP 客户端)。
http --form POST :8080/jlong/inquire question=="do you have any neurotic dogs?"
在一次运行中,我们得到了以下响应:
是的,我们有一只神经质的狗可供领养。认识一下 Prancer,这是一只恶魔般的、神经质的狗,它有点脾气问题——它倾向于憎恨人类、憎恨动物、憎恨儿童,而且看起来像个小精灵。如果你感兴趣,请告诉我!
起来像个小精灵。如果你感兴趣,请告诉我!
(你的响应可能会有所不同。)
它成功了!我们有了一个方便的自然语言助手,帮助人们找到他们梦寐以求的“Prancer”!
从这里开始,你可以做很多事情。你可以使用 Spring AI 的工具调用支持,让 LLM 调用你代码中的业务逻辑,并安排领养狗的时间。
你可以将业务逻辑集中化,并通过 MCP 协议导出以供消费。
你可以将整个系统变成一个代理,设定目标并让 LLM 的输出驱动代码的执行。
可能性是无穷无尽的!
面向生产的 AI 应用
现在,是时候将目光转向生产环境了。
安全性
使用 Spring Security 锁定这个 Web 应用非常简单。你甚至可以使用经过身份验证的 Principal#getName 作为对话 ID。那么数据库中存储的数据,比如对话记录呢?你有几个选择。Oracle 数据库支持静态加密。在 Oracle 数据库中,这个功能被称为 TDE(透明数据加密)。基本上,当你读取或写入值时,它们会安全地存储在磁盘上。代码无需任何更改。
扩展性
我们希望这个代码能够扩展。请记住,每次你向 LLM(或许多关系型数据库)发起 HTTP 请求时,你都在进行阻塞式 I/O。I/O 会占用一个线程,直到 I/O 完成之前,这个线程都无法被系统中的其他需求使用。这是一种对线程的浪费。线程不应该只是闲置等待。Java 21 提供了虚拟线程,对于足够依赖 I/O 的服务来说,它可以显著提高扩展性。这就是我们在 application.properties 文件中设置 spring.threads.virtual.enabled=true 的原因。
GraalVM 原生镜像
GraalVM 是一个 AOT(Ahead-Of-Time)编译器,由 Oracle 领导开发,你可以通过 GraalVM 社区版开源项目或功能强大的(且免费的)Oracle GraalVM 发行版来使用它。
如果你已经将其设置为你的 SDK,你可以轻松地将这个 Spring AI 应用程序转换为特定于操作系统和架构的原生镜像:
./mvnw -DskipTests -Pnative native:compile
在大多数机器上,这需要大约一分钟的时间。完成后,你可以轻松运行二进制文件:
./target/oracle
这个程序的启动速度将比在 JVM 上快得多。你可能需要注释掉我们之前创建的 ApplicationRunner
,因为它会在启动时进行 I/O 操作,显著延迟启动时间。在我的机器上,它启动时间不到十分之一秒。
更好的是,你会发现这个应用程序占用的内存只是在 JVM 上运行时的一小部分。
这很好,你可能会说,但我需要将它部署到我的云平台(当然是 Oracle Cloud)上,这意味着需要将其打包成 Docker 镜像。很简单!
./mvnw -DskipTests -Pnative spring-boot:build-image
这可能还需要一分钟左右的时间。完成后,你会看到打印出生成的 Docker 镜像的名称。
你可以运行它,记得覆盖它在主机上引用的主机名和端口。
docker run -e SPRING_AI_OPENAI_API_KEY=$SPRING_AI_OPENAI_API_KEY \
-e SPRING_DATASOURCE_URL=jdbc:oracle:thin:@host.docker.internal:1521/FREEPDB1 docker.io/library/oracle:0.0.1-SNAPSHOT
太棒了!
我们在 macOS 上运行,令人惊讶的是,这个应用程序在 macOS 虚拟机中模拟 Linux 时,运行速度甚至比直接在 macOS 上更快!太神奇了!
可观察性
这个应用程序如此出色,我相信它很快就会登上头条新闻,就像 Prancer 一样。当这种情况发生时,你最好密切关注你的系统资源,尤其是令牌数量。所有对 LLM 的请求都有成本——至少是复杂性,如果不是金钱的话。幸运的是,Spring AI 为你提供了支持。向模型发起一些请求,然后访问 Spring Boot Actuator 的指标端点(由 Micrometer.io 提供支持):http://localhost:8080/actuator/metrics,你会看到与令牌消耗相关的指标。太棒了!你可以使用 Micrometer 将这些指标转发到你选择的时序数据库中,以获得一个统一的仪表板。
Oracle Cloud (OCI)
你已经拥有一个智能、可扩展且高效的程序,但应该在哪里运行它呢?Oracle Cloud (OCI) 是一个不错的选择!Oracle OCI 是 Oracle 的云基础设施,类似于 AWS、Azure 或 Google Cloud,提供基础设施和平台服务,用于运行应用程序、存储数据和构建云原生解决方案。你可以以多种方式在其中运行你的新 Docker 镜像。OCI 提供了适用于 Intel 和 ARM 架构的计算实例。你可以通过注册 Always Free Tier 轻松开始,它将为你提供计算实例、Oracle 自治数据库、负载均衡器和一些其他资源的免费使用权。
Oracle 自治数据库
如果你认为在 OCI 上运行你的 Spring Boot 应用程序是一个不错的选择,那么你一定会喜欢他们运行 Oracle 数据库本身的方式!Oracle 自治数据库就是我们刚刚提到的 Oracle 数据库,完全由他们管理。你可以享受所有的优势,而无需承担任何管理的负担。而且,如果你觉得这还不够,你甚至可以在 Microsoft Azure、Google Cloud Platform 以及 Amazon Web Services (AWS) 上运行它。
下一步
你刚刚快速构建了一个生产级、支持人工智能的 Spring AI 和 Oracle 数据库驱动的应用程序。我们才刚刚开始探索它的潜力!立即访问 Spring Initializr,了解 Spring AI 1.0,并深入了解 Oracle 数据库及其向量支持。
想了解更多资讯
扫码关注👇
了解更多考试相关
扫码添加上智启元官方客服微信👇