Spring AI 是一个旨在简化 Java 应用集成大语言模型(LLM)的框架,其设计核心在于抽象化模块化。它通过定义清晰的接口和提供丰富的实现,使开发者能够以统一的方式与不同的 AI 模型和服务进行交互,极大地提升了开发效率和代码的可维护性。

其核心架构与组件设计遵循分层思想,主要包括:

  • 抽象层 (Abstraction Layer):定义了与 AI 模型交互的核心接口,是业务代码直接依赖的部分。
  • 实现层 (Implementation Layer):包含了对接各种具体 AI 服务(如 OpenAI, DeepSeek)的代码。
  • 应用层 (Application Layer):由开发者编写的业务逻辑,通过调用抽象层的接口来使用 AI 能力。

一、 核心组件与源码解析

以下是 Spring AI 的核心组件及其在源码中的体现:

1. ChatClient 接口:统一的对话入口

这是 Spring AI 中最核心的接口之一,定义了与大语言模型进行对话的基本行为。它有两个主要实现:

  • 同步调用ChatClient 接口,定义了 call() 方法,用于发送请求并同步等待响应。
  • 流式调用StreamingChatClient 接口,定义了 stream() 方法,返回一个 Flux<ChatResponse>,用于处理流式响应(如 ChatGPT 的逐字输出效果)。

源码解析

  • 接口定义ChatClient 接口继承自 ModelClient<Prompt, ChatResponse>,明确了其处理 Prompt 请求并返回 ChatResponse 响应的职责。
  • 默认方法:它提供了一个便捷的 call(String message) 默认方法,可以快速地将一个字符串消息包装成 Prompt 并发起调用。

2. Prompt 与 Message:请求的构建

Prompt 类是发送给 AI 模型的请求对象,它封装了对话的核心内容。一个 Prompt 包含一个或多个 Message 对象和一个 ChatOptions 对象。

源码解析

  • Message 层次结构Message 是一个接口,代表对话中的一条消息。框架提供了多种实现,如 UserMessage(用户输入)、AssistantMessage(助手回复)、SystemMessage(系统指令)等,覆盖了对话所需的全部角色。
  • ChatOptions:这个类封装了影响模型生成行为的各种参数,如 temperature(创造力)、maxTokens(最大输出长度)等。

3. ChatResponse 与 Generation:响应的封装

ChatResponse 类是 AI 模型返回的响应对象,它包含了模型生成的内容和相关元数据。

源码解析

  • Generation 对象ChatResponse 的核心是 Generation 对象,它代表了模型生成的一条具体结果。Generation 包含一个 AiMessage(通常是 AssistantMessage)和一个 ChatGenerationMetadata 对象。
  • 元数据 (Metadata)ChatGenerationMetadata 是一个关键的元数据容器,它存储了模型响应的附加信息,如 finishReason(停止原因)。这个字段对于判断模型是否需要调用工具(tool_calls)至关重要。

4. 模型实现类:对接具体 AI 服务

这是框架的实现层,为不同的 AI 服务提供商提供了具体的 ChatModel 实现。

源码解析 (以 OpenAI 为例)

  • 实现类OpenAiChatModel 类是 ChatModel 接口的一个具体实现,专门用于与 OpenAI 的 API 进行交互。
  • 核心方法:它实现了 call()stream() 方法。在 call() 方法中,它会将传入的 Prompt 对象转换为 OpenAI API 所需的 ChatCompletionRequest 请求体,然后通过 RestClient 发送 HTTP 请求,并将响应转换为 ChatResponse 对象。
  • 辅助组件:该类内部使用了 RetryTemplate(用于失败重试)、ToolCallingManager(用于管理工具调用)等组件,体现了框架的健壮性和扩展性。

5. EmbeddingModel 与 VectorStore:向量处理与存储

除了对话,Spring AI 也支持处理文本嵌入(Embeddings),这是实现语义搜索、RAG(检索增强生成)等高级功能的基础。

源码解析

  • EmbeddingModel:这是一个接口,定义了生成文本嵌入向量的方法。框架为 OpenAI、DeepSeek 等提供了具体实现,如 OpenAiEmbeddingModel
  • VectorStore:这是一个抽象类,定义了向量存储和检索的基本操作,如 add()(添加文档)、similaritySearch()(相似性搜索)。框架提供了基于 Milvus、Chroma、pgvector 等多种数据库的实现。VectorStore 会使用 EmbeddingModel 来为文档生成嵌入向量并存入数据库。

6. 自动配置 (Auto Configuration):开箱即用的魔法

为了让开发者能够“开箱即用”,Spring AI 大量使用了 Spring Boot 的自动配置机制。

源码解析 (以 pgvector 为例)

  • 条件装配:在 ChromaVectorStoreAutoConfiguration 这样的配置类中,使用了 @ConditionalOnClass@ConditionalOnProperty 等注解。这意味着只有当 classpath 中存在特定的类(如 EmbeddingModelRestClient)并且配置文件中启用了特定属性时,相关的 VectorStore Bean 才会被创建。
  • 属性绑定:使用 @EnableConfigurationProperties 注解将配置文件(如 application.properties)中的属性自动绑定到配置类中,使得配置非常灵活和集中。

二、 核心工作流程总结

从源码层面看,一次典型的 AI 调用流程如下:

  1. 请求构建:开发者通过 ChatClientcall()stream() 方法,传入一个 Prompt 对象。
  2. 模型选择:Spring Boot 的自动配置机制根据应用配置,实例化了具体的 ChatModel 实现(如 OpenAiChatModel),并将其注入到 ChatClient 中。
  3. 请求执行ChatClient 调用 ChatModelcall()stream() 方法。具体的模型实现类会将 Prompt 转换为对应服务商的 API 请求格式,并通过 HTTP 客户端发送。
  4. 响应处理:模型实现类接收到 HTTP 响应后,会将其解析并封装成统一的 ChatResponse 对象返回给 ChatClient
  5. 结果返回ChatClientChatResponse 返回给开发者。开发者可以从 ChatResponse 中提取出 Generation,进而获取模型生成的文本内容和元数据。

三、Spring AI 中工具调用(Tool Calling)和 RAG(检索增强生成)的实现原理

Spring AI 中工具调用(Tool Calling)和 RAG(检索增强生成)的实现,都体现了其设计精巧、模块化的核心思想。它们并非单一组件,而是多个模块协同工作的结果。

1. 工具调用(Tool Calling)的实现原理

工具调用的实现核心是 “代理模式”“递归调用” 的结合,其流程如下:

1) 定义工具 (Tool)
开发者首先需要将外部功能(如查询天气、计算)封装成 Tool 对象。这通常通过实现 FunctionCallback 接口或使用其构建器来完成,需要提供函数名、描述、参数定义和实际的执行逻辑。

2) 发起调用与模型响应
当通过 ChatClient 发起调用时,框架会自动将注册的工具信息(名称、参数、描述)以特定格式(如 JSON Schema)注入到发送给大模型的 Prompt 中。模型在理解用户意图后,如果需要使用工具,会返回一个特殊的响应,其中包含 finish_reason(如 tool_calls)以及要调用的工具名称和参数。

3) 核心处理循环
这是框架自动处理工具调用的关键,发生在 OpenAiChatModel 等具体实现类的 internalCall 方法中,流程如下:

  • 判断响应:框架检查模型返回的 Generation 对象,判断其是否为工具调用(通过 finishReason 和是否存在 toolCalls)。
  • 执行工具:如果是工具调用,框架会遍历所有 toolCalls,根据名称找到对应的 FunctionCallback 实现,并通过反射机制执行其业务逻辑,获取工具执行结果。
  • 构建新 Prompt:框架将原始对话历史、上一轮的 AI 回复(包含工具调用指令)以及本轮工具执行的结果,组合成一个新的 Prompt 对象。
  • 递归调用:框架使用这个新的 Prompt 递归地调用 internalCall 方法,将工具执行的结果“告知”给大模型。模型会根据这些新信息,决定是继续调用其他工具,还是生成最终的用户可读回复。

4) 控制开关:proxyToolCalls
这是一个重要的配置选项,它决定了上述自动处理循环是否生效。

  • proxyToolCalls = false (默认):框架会自动执行上述完整的工具调用、结果处理和递归调用流程。开发者最终得到的是模型基于工具结果生成的最终答案。
  • proxyToolCalls = true:框架会“代理”模式,即不自动处理工具调用。它会将模型返回的原始 ChatResponse(其中包含 ToolCall 信息)直接返回给开发者。开发者需要自己解析 ToolCall,执行工具逻辑,并构建新的 Prompt 进行下一次调用。这提供了更高的灵活性,适用于需要自定义工具调用流程的复杂场景。

2. RAG(检索增强生成)的实现原理

RAG 的实现是一个典型的 ETL(抽取、转换、加载)流程,结合了向量存储和检索技术,主要分为数据准备和查询增强两个阶段。

1) 数据准备阶段(构建知识库)

  • 文档加载 (Extract):使用自定义的 DocumentLoader 从各种数据源(如文件系统、数据库、网页)加载原始文本数据,转换为 Document 对象。Document 对象不仅包含文本内容,还可以携带元数据(metadata),如来源 URL、作者、章节标题等。
  • 文档切分 (Transform):由于大模型有上下文长度限制,需要将长文档切分成较小的、语义连贯的块(chunks)。Spring AI 提供了如 TokenTextSplitter 等工具来实现智能切分。
  • 向量化与存储 (Load):这是 RAG 的核心。框架使用 EmbeddingModel(如 OpenAiEmbeddingModel)将每个文档块转换为一个高维向量(embedding)。这个向量能够捕捉文本的语义信息。然后,这些向量连同其对应的原始文本和元数据,被存储到 VectorStore 中。Spring AI 支持多种向量数据库(如 pgvector, Milvus, Chroma),通过统一的接口屏蔽了底层差异。

2) 查询增强阶段(回答问题)

  • 用户查询:用户向 ChatClient 提出一个问题。
  • 检索相关文档:在将用户问题发送给大模型之前,框架会先使用 EmbeddingModel 将问题转换为向量。然后,在 VectorStore 中执行相似度搜索(similarity search),找出与问题向量最接近的几个文档块。
  • 构建增强 Prompt:框架将检索到的相关文档块的文本内容,以及一个预设的系统指令(system prompt),与用户的问题组合在一起,构建一个新的、信息更丰富的 Prompt。这个系统指令通常会告诉模型:“请基于以下参考资料回答用户问题。如果资料不足以回答问题,请明确告知用户。”
  • 生成最终答案:这个增强后的 Prompt 被发送给大模型。模型在生成回复时,不仅基于其内部知识,还会重点参考和整合检索到的参考资料,从而生成更准确、更相关、更可信的答案。

总而言之,Spring AI 通过提供 FunctionCallbackEmbeddingModelVectorStore 等核心抽象和实现,将复杂的工具调用和 RAG 流程进行了高度封装和自动化。开发者只需关注业务逻辑的实现,而无需过多关心底层的网络请求、数据转换和流程控制,极大地提升了开发效率和应用的健壮性。


四、使用的设计模式

在分析 Spring AI 的核心组件后,可以清晰地看到它巧妙地运用了多种经典的设计模式,这些模式是其实现高内聚、低耦合和高度可扩展架构的关键。除了之前提到的抽象工厂模式,主要还应用了以下几种:

1. 策略模式 (Strategy Pattern)

这是 Spring AI 中应用最广泛的设计模式之一,核心思想是定义一系列算法,并将每个算法封装起来,使它们可以相互替换

  • 应用场景:AI 模型(ChatModel, EmbeddingModel)和向量存储(VectorStore)的实现。
  • 模式解析
    • 接口定义策略ChatModel 接口本身就是一个策略接口,它声明了 call()stream() 等方法。
    • 具体策略实现OpenAiChatModel, DeepSeekChatModel 等都是实现了 ChatModel 接口的具体策略类。每个类都封装了与特定 AI 服务商 API 交互的具体算法。
    • 上下文使用策略ChatClient 作为上下文,持有一个 ChatModel 的引用。在运行时,根据配置(如通过 Spring 的依赖注入),ChatClient 会使用具体的 OpenAiChatModelDeepSeekChatModel 等策略来执行实际的 AI 调用。
  • 优点高度解耦。业务代码(ChatClient)只依赖于抽象的 ChatModel 接口,无需关心具体是哪家 AI 服务的实现。这使得新增一个 AI 服务商(如阿里云通义千问)只需新增一个策略类,而无需修改现有业务逻辑,完美符合开闭原则

2. 模板方法模式 (Template Method Pattern)

该模式定义了一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变算法结构即可重定义算法的某些特定步骤。

  • 应用场景:各种 ChatModelEmbeddingModel 的实现基类。
  • 模式解析
    • 抽象类定义骨架:框架内部可能提供一个抽象基类,如 AbstractChatModel,它实现了 ChatModel 接口。在这个基类中,它可能定义了 call() 方法的通用流程:如记录请求日志 -> 执行前置处理 -> 发送 HTTP 请求 -> 处理响应 -> 记录响应日志 -> 返回结果
    • 抽象方法延迟实现:其中,“发送 HTTP 请求”和“处理响应”这两个核心步骤在基类中被声明为抽象方法(例如 doCall(Prompt prompt))。
    • 子类实现特定步骤:具体的模型类(如 OpenAiChatModel)继承自 AbstractChatModel,并只需要实现 doCall() 方法,编写与 OpenAI API 交互的特定代码即可。它无需关心日志记录、重试等通用逻辑。
  • 优点代码复用和流程统一。避免了在每个具体的模型实现类中都重复编写日志、重试、异常处理等横切关注点的代码,保证了核心调用流程的一致性。

3. 适配器模式 (Adapter Pattern)

该模式将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。

  • 应用场景:将第三方 AI 服务的 API 响应适配到 Spring AI 的统一模型。
  • 模式解析
    • 目标接口:Spring AI 定义了自己的响应模型,如 ChatResponseGeneration
    • 被适配对象:第三方服务(如 OpenAI)返回的是其自身的 JSON 响应结构,例如 OpenAI 的 ChatCompletionResponse
    • 适配器实现:在 OpenAiChatModel 类中,当接收到 ChatCompletionResponse 对象后,会有一个转换过程(例如,调用一个 ResponseMapper 或直接在代码中进行属性映射),将其“适配”成 Spring AI 的 ChatResponse 对象。
  • 优点兼容性。使得 Spring AI 的核心抽象层可以与任何第三方服务集成,无论其内部 API 设计如何,只需提供一个适配器即可,进一步增强了框架的扩展性。

4. 观察者模式 (Observer Pattern)

该模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,会通知所有观察者,使它们能够自动更新。

  • 应用场景:AI 调用过程中的事件监听,例如回调函数(Callbacks)和监听器(Listeners)。
  • 模式解析
    • 主题(Subject):框架内部可能有一个事件发布器,用于在 AI 调用的关键节点(如请求开始、响应结束、发生错误)发布事件。
    • 观察者(Observer):开发者可以注册自己的监听器(Listener)或回调函数(Callback),这些监听器/回调函数实现了特定的接口(如 ApplicationListener<AiCallStartedEvent>)。
    • 通知与更新:当 AI 调用开始时,框架发布 AiCallStartedEvent 事件,所有监听该事件的观察者就会收到通知,并执行自定义逻辑(如记录日志、统计耗时)。
  • 优点可扩展性和关注点分离。允许开发者在不侵入核心调用逻辑的情况下,对 AI 调用过程进行监控、日志记录、性能分析等操作。

5. 代理模式 (Proxy Pattern)

该模式为其他对象提供一种代理以控制对这个对象的访问。

  • 应用场景ChatClient 接口的设计。
  • 模式解析
    • 真实主题(Real Subject):真正执行 AI 调用的是具体的 ChatModel 实现类(如 OpenAiChatModel)。
    • 代理(Proxy)ChatClient 接口及其实现类扮演了代理的角色。业务代码不直接调用 ChatModel,而是通过 ChatClient 这个代理层进行访问。
    • 控制访问ChatClient 代理层可以在调用 ChatModel 之前和之后添加额外的逻辑,例如参数校验、结果缓存、统一异常处理等。
  • 优点增强和控制。在不改变 ChatModel 核心逻辑的前提下,通过代理层可以方便地为 AI 调用添加横切功能。

总结

Spring AI 通过组合运用这些设计模式,构建了一个优雅且强大的框架:

  • 策略模式:实现了算法与使用的分离,是支持多模型的核心。
  • 模板方法模式:封装了不变的流程,将变化的部分延迟到子类,提高了代码复用率。
  • 适配器模式:解决了与第三方服务集成时的接口不匹配问题。
  • 观察者模式:提供了非侵入式的扩展点,方便进行监控和日志记录。
  • 代理模式:为真实对象提供了访问控制和功能增强的入口。

这些设计模式共同作用,使得 Spring AI 具备了高度的灵活性、可扩展性和可维护性,让 Java 开发者能够轻松地将 AI 能力集成到自己的应用中。


五、实战案例

下面是一个完整的 Spring Boot 应用示例,它演示了如何使用 Spring AI 的 ChatClient 和工具调用(Tool Calling)功能,让 AI 模型在回答问题时自动查询天气。

这个示例将带你完成以下核心步骤:

  1. 项目搭建与依赖配置:创建一个 Spring Boot 项目并引入必要的依赖。
  2. 工具定义:将一个天气查询功能封装成 AI 模型可以调用的工具。
  3. 业务逻辑编写:通过 ChatClient 发起对话,并观察 AI 模型如何自动决定是否调用工具。
  4. 运行与验证:启动应用并测试工具调用的效果。

1. 项目搭建与依赖配置 (Maven)

首先,创建一个新的 Spring Boot 项目。在你的 pom.xml 文件中,确保引入了以下关键依赖。请注意,由于 Spring AI 仍处于里程碑(Milestone)阶段,你需要额外配置 Spring 的里程碑仓库。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.5.0</version> <!-- 使用与 Spring AI 兼容的 Boot 版本 -->
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>spring-ai-tool-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-ai-tool-demo</name>
	<description>Demo project for Spring AI Tool Calling</description>
	<url/>
	<licenses>
		<license/>
	</licenses>
	<developers>
		<developer/>
	</developers>
	<scm>
		<connection/>
		<developerConnection/>
		<tag/>
		<url/>
	</scm>
	<properties>
		<java.version>17</java.version>
		<spring-ai.version>1.0.0-M4</spring-ai.version> <!-- 使用最新的 Spring AI 版本 -->
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- 引入 Spring AI 的 OpenAI 模块,用于调用 OpenAI 或兼容 API -->
		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-starter-model-openai</artifactId>
		</dependency>
		<!-- 引入 Spring AI 的工具模块,包含 FunctionCallback 等核心类 -->
		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-tool-support</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<!-- 配置 Spring Milestone 仓库,因为 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>
	</repositories>
</project>

2. 定义工具 (Tool)

接下来,我们需要将一个具体的功能(例如查询天气)定义为 AI 模型可以理解和调用的工具。这通过实现 FunctionCallback 接口或使用其构建器来完成。

// 文件: src/main/java/com/example/springaitool/WeatherService.java
package com.example.springaitool;

import org.springframework.ai.tool.FunctionCallback;
import org.springframework.ai.tool.Tool;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 天气服务工具类,实现了 FunctionCallback 接口,可以被 AI 模型调用。
 */
@Component
public class WeatherService implements FunctionCallback {

    // 模拟的天气数据
    private static final Map<String, String> WEATHER_DATA = Map.of(
            "北京", "晴,25℃,空气质量优",
            "上海", "多云,28℃,空气质量良",
            "广州", "雷阵雨,30℃,空气质量中"
    );

    @Override
    public String call(String toolCallMessage) {
        // 1. 解析工具调用的参数。实际项目中这里可能是 JSON 字符串,需要解析。
        // 为简化示例,我们假设传入的是纯城市名。
        String city = toolCallMessage.trim();

        // 2. 执行工具逻辑,查询天气
        String weatherInfo = WEATHER_DATA.getOrDefault(city, "未找到该城市的天气信息");

        // 3. 返回工具执行结果
        return weatherInfo;
    }

    @Override
    public Tool getDefinition() {
        // 定义工具的元信息,包括名称、描述和参数
        return Tool.builder()
                .name("get_weather")
                .description("查询指定城市的天气情况")
                .parameters(Map.of(
                        "type", "object",
                        "properties", Map.of(
                                "city", Map.of(
                                        "type", "string",
                                        "description", "要查询天气的城市名称"
                                )
                        ),
                        "required", new String[]{"city"}
                ))
                .build();
    }
}

3. 编写业务逻辑 (Controller)

现在,我们创建一个 Web 控制器,通过 ChatClient 发起对话,并将我们定义的工具注册进去。

// 文件: src/main/java/com/example/springaitool/AiController.java
package com.example.springaitool;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.chat.prompt.UserPromptTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class AiController {

    private final ChatClient chatClient;
    private final WeatherService weatherService;

    // 通过构造函数注入 ChatClient 和 WeatherService
    public AiController(ChatClient.Builder builder, WeatherService weatherService) {
        // 构建 ChatClient,并注册我们的天气工具
        this.chatClient = builder
                .defaultTools(weatherService) // 将工具注册到客户端
                .build();
        this.weatherService = weatherService;
    }

    /**
     * 同步调用示例
     */
    @GetMapping("/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "你好") String message) {
        // 创建一个用户消息的 Prompt
        Prompt prompt = new Prompt(message);
        
        // 发起同步调用,并获取响应内容
        return chatClient.call(prompt).getResult().getOutput().getContent();
    }

    /**
     * 流式调用示例 (Server-Sent Events)
     */
    @GetMapping(value = "/chat/stream", produces = "text/event-stream")
    public Flux<String> chatStream(@RequestParam(value = "message", defaultValue = "你好") String message) {
        Prompt prompt = new Prompt(message);
        
        // 发起流式调用,返回一个 Flux<String>,可以逐块处理响应
        return chatClient.stream(prompt).map(ChatResponse::getContent);
    }
}

4. 配置与运行

application.propertiesapplication.yml 文件中,配置你的 AI 模型服务信息。这里以 OpenAI 为例:

# application.properties
spring.ai.openai.api-key=sk-你的-openai-api-key
spring.ai.openai.base-url=https://api.openai.com/v1
spring.ai.openai.chat.options.model=gpt-4o-mini

运行应用:

  1. 启动你的 Spring Boot 应用。
  2. 打开浏览器或使用 curl、Postman 等工具,访问 http://localhost:8080/chat?message=北京今天天气怎么样?

观察结果:
你会看到 AI 模型并没有直接回答问题,而是返回了一个工具调用的指令,其中指定了要调用 get_weather 工具,并传入了参数 {"city": "北京"}。这正是我们之前定义的工具。框架会自动捕获这个指令,执行 WeatherService.call() 方法,并将结果返回给模型,最终由模型生成一个自然语言的回答。


这个完整的示例清晰地展示了 Spring AI 是如何通过 ChatClientFunctionCallback 等核心组件,实现强大且易于使用的工具调用功能的。


六、RAG 的实战示例

下面是一个使用 Spring Boot 和 pgvector 构建本地知识库问答系统的完整 RAG 实战示例。

这个示例将带你完成以下核心步骤:

  1. 环境准备与项目搭建:配置 PostgreSQL 和 pgvector 扩展,并创建 Spring Boot 项目。
  2. 数据准备与向量化:编写代码加载本地文档(如 PDF、TXT),进行切分并存储到向量数据库。
  3. RAG 问答接口开发:通过 ChatClient 实现一个接口,在生成回答前自动检索知识库并增强 Prompt。
  4. 运行与验证:启动应用并测试基于本地知识的问答效果。

1. 环境准备与项目搭建

前提条件:确保你已安装并运行了 PostgreSQL 数据库。

步骤一:在 PostgreSQL 中启用 pgvector 扩展

  1. 连接到你的 PostgreSQL 数据库(例如 mydb)。
  2. 执行以下 SQL 命令来创建 pgvector 扩展:
    CREATE EXTENSION IF NOT EXISTS vector;
    
  3. 验证扩展是否创建成功:
    SELECT * FROM pg_extension WHERE extname = 'vector';
    

步骤二:创建 Spring Boot 项目并配置依赖

在你的 pom.xml 文件中,确保引入了以下关键依赖:

<!-- 引入 Spring AI 的核心 starter,包含 ChatClient 等基础组件 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter</artifactId>
</dependency>
<!-- 引入 Spring AI 的 OpenAI starter,用于调用 OpenAI 的 Embedding 和 Chat 模型 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 引入 Spring AI 的 Data JPA Vector Store starter,用于将向量数据存储到 PostgreSQL -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-data-jpa</artifactId>
</dependency>
<!-- 引入 Spring Boot 的 Data JPA 和 PostgreSQL 驱动 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
<!-- 引入 Spring Boot 的 Web starter,用于创建 REST API -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

步骤三:配置 application.properties

# application.properties

# 配置数据源,连接到你的 PostgreSQL 数据库
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=your_username
spring.datasource.password=your_password

# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

# 配置 OpenAI 的 API Key
spring.ai.openai.api-key=sk-你的-openai-api-key

# 配置默认的 Chat Model (可选,如果不设置则使用 OpenAI 的默认模型)
spring.ai.openai.chat.options.model=gpt-4o-mini

# 配置默认的 Embedding Model (可选,如果不设置则使用 OpenAI 的默认 embedding 模型)
spring.ai.openai.embedding.options.model=text-embedding-3-small

2. 数据准备与向量化 (构建知识库)

步骤一:准备文档
将你想要纳入知识库的文档(例如 knowledge.txt)放在 src/main/resources/documents/ 目录下。

步骤二:编写文档加载与向量化代码

// 文件: src/main/java/com/example/springairag/VectorizationService.java
package com.example.springairag;

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;

@Service
public class VectorizationService {

    private final VectorStore vectorStore;

    @Autowired
    public VectorizationService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    /**
     * 从 classpath 加载文本文件,切分文档,并将 Document 对象存储到 VectorStore 中
     */
    public void loadAndStoreDocuments() throws IOException {
        // 1. 加载文档
        ClassPathResource resource = new ClassPathResource("documents/knowledge.txt");
        TextReader textReader = new TextReader(resource);
        Document document = textReader.read();

        // 2. 切分文档
        TokenTextSplitter textSplitter = new TokenTextSplitter();
        List<Document> documentChunks = textSplitter.apply(document);

        // 3. 将文档块存储到向量数据库
        vectorStore.add(documentChunks);
    }
}

步骤三:创建初始化 Bean,在应用启动时自动构建知识库

// 文件: src/main/java/com/example/springairag/AppInitializer.java
package com.example.springairag;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class AppInitializer implements CommandLineRunner {

    private final VectorizationService vectorizationService;

    @Autowired
    public AppInitializer(VectorizationService vectorizationService) {
        this.vectorizationService = vectorizationService;
    }

    @Override
    public void run(String... args) throws Exception {
        vectorizationService.loadAndStoreDocuments();
        System.out.println("知识库构建完成!");
    }
}

3. RAG 问答接口开发

// 文件: src/main/java/com/example/springairag/AiController.java
package com.example.springairag;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class AiController {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public AiController(ChatClient chatClient, VectorStore vectorStore) {
        this.chatClient = chatClient;
        this.vectorStore = vectorStore;
    }

    /**
     * RAG 问答接口
     */
    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        // 1. 从向量数据库中检索与问题最相关的文档块
        List<Document> relevantDocuments = vectorStore.similaritySearch(question);

        // 2. 构建增强后的 Prompt
        // 将检索到的文档内容拼接起来,作为上下文
        String context = relevantDocuments.stream()
                .map(Document::getContent)
                .reduce("", (a, b) -> a + "\n\n" + b);

        // 构建最终的 Prompt,告诉模型基于给定的上下文回答问题
        String prompt = String.format(
                "你是一个问答机器人。请根据以下参考资料回答用户的问题。如果参考资料不足以回答问题,请明确告知用户。\n\n" +
                        "参考资料:\n%s\n\n" +
                        "用户问题:%s",
                context, question);

        // 3. 调用大模型生成最终答案
        return chatClient.call(prompt).getResult().getOutput().getContent();
    }
}

4. 运行与验证

步骤一:启动应用
运行你的 Spring Boot 应用。在启动过程中,AppInitializer 会自动执行,加载并切分 knowledge.txt 文件,然后使用 OpenAI 的 Embedding 模型将其向量化并存储到 PostgreSQL 的 pgvector 扩展中。

步骤二:测试接口
启动成功后,打开浏览器或使用 curl、Postman 等工具,访问 http://localhost:8080/ask?question=你的问题

观察结果:
当你提出一个问题时,/ask 接口会首先在向量数据库中检索与问题最相关的知识片段,然后将这些片段作为上下文,连同问题本身一起发送给大模型。大模型会基于这个增强的上下文生成最终的回答,从而实现对本地知识的准确问答。


这个完整的示例展示了如何利用 Spring AI 的模块化设计,快速构建一个功能强大的 RAG 应用。从文档加载、切分、向量化存储到最终的检索和生成,每一步都清晰且易于扩展。

希望这两个实战示例能帮助你更好地理解和应用 Spring AI!


七、部署和性能调优指南

在完成了功能开发后,部署和性能调优是确保 Spring AI 应用在生产环境中稳定、高效运行的关键一步。一个好的生产级应用,不仅在于功能实现,更在于其稳定性、可观测性和成本效益。

下面为您整理了一份 Spring AI 生产部署与性能调优指南,涵盖了您提到的模型缓存、连接池配置等核心方面。


1. 核心组件性能调优

连接池与超时配置 (以 OpenAI 为例)

AI 模型服务通常是远程调用,网络延迟和连接管理至关重要。Spring AI 基于 Project Reactor 构建,底层使用 Reactor Netty 的 HttpClient,因此可以通过标准的 Spring Boot 配置来优化连接池和超时。

# application.yml

# 配置底层 HttpClient 的连接池
spring:
  ai:
    openai:
      # 连接超时(毫秒),默认 5000
      connect-timeout: 5000
      # 读取超时(毫秒),默认 30000
      read-timeout: 30000
      # 写入超时(毫秒),默认 30000
      write-timeout: 30000

# 配置 Reactor Netty 的连接池参数
reactor:
  netty:
    http:
      client:
        # 连接池最大连接数,默认 200
        max-connections: 200
        # 每个远程主机的最大连接数,默认 200
        max-connections-per-host: 200
        # 连接池中连接的最大存活时间(毫秒),-1 表示无限制
        max-life-time: -1
        # 连接池中连接的最大空闲时间(毫秒),-1 表示无限制
        max-idle-time: 60000

调优建议

  • 高并发场景:如果您的应用并发量很高,可以适当增加 max-connectionsmax-connections-per-host
  • 网络不稳定环境:可以适当增加超时时间 (read-timeout, write-timeout),防止因短暂网络波动导致请求失败。
  • 资源受限环境:如果服务器资源有限,可以适当调低连接池大小,防止过多连接耗尽系统资源。

模型响应缓存 (Caching)

对于重复性问题或基于固定知识库的查询,缓存可以显著降低 API 调用成本并提高响应速度。

方案一:本地缓存 (如 Caffeine)

适用于单机部署,缓存命中率高的场景。

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
    
  2. 配置与使用

    @Configuration
    @EnableCaching
    public class CacheConfig {
        // 配置 Caffeine 缓存
    }
    
    @Service
    public class AiService {
        // 使用 @Cacheable 注解缓存方法结果
        @Cacheable(value = "aiResponses", key = "#prompt")
        public String getCachedResponse(String prompt) {
            // 调用 ChatClient 的代码
        }
    }
    

方案二:分布式缓存 (如 Redis)

适用于集群部署,需要共享缓存的场景。

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置与使用

    // 配置 RedisCacheManager (略)
    
    @Service
    public class AiService {
        @Cacheable(value = "aiResponses", key = "#prompt")
        public String getCachedResponse(String prompt) {
            // 调用 ChatClient 的代码
        }
    }
    

调优建议

  • 缓存粒度:可以基于完整的 Prompt、用户 ID + Prompt 或者业务上下文来设计缓存 Key。
  • 缓存失效:合理设置缓存的 TTL (Time To Live),特别是当您的知识库或业务数据频繁更新时。

高级功能:Prompt Caching

值得注意的是,Spring AI 从 2025.1.0-M1 版本开始,已支持 Anthropic ClaudeAWS BedrockPrompt Caching 功能。该功能允许模型缓存部分 Prompt 的计算结果,对于重复使用的系统提示词 (System Prompt) 或长文档分析等场景,可以显著降低成本和延迟。这需要在代码中进行特定配置以启用。


2. 生产环境稳定性与可观测性

限流、熔断与重试 (Resilience4j)

AI 服务的 API 可能会因过载或故障而不可用。为了防止级联故障,必须实施服务保护机制。

  1. 添加依赖

    <dependency>
        <groupId>io.github.resilience4j</groupId>
        <artifactId>resilience4j-spring-boot2</artifactId>
    </dependency>
    
  2. 配置与使用

    # application.yml
    resilience4j:
      circuitbreaker:
        instances:
          aiService:
            # 熔断器配置
            failureRateThreshold: 50
            waitDurationInOpenState: 5000
            ringBufferSizeInHalfOpenState: 3
            ringBufferSizeInClosedState: 10
      ratelimiter:
        instances:
          aiService:
            # 限流器配置
            limitForPeriod: 100
            limitRefreshPeriod: 1s
      retry:
        instances:
          aiService:
            # 重试配置
            maxAttempts: 3
            waitDuration: 100ms
    
    @Service
    public class AiService {
        @CircuitBreaker(name = "aiService")
        @RateLimiter(name = "aiService")
        @Retry(name = "aiService")
        public String safeCall(String prompt) {
            // 调用 ChatClient 的代码
        }
    }
    

调优建议

  • 熔断:当 AI 服务错误率过高时,快速失败并返回友好提示,避免线程池耗尽。
  • 限流:保护您的应用不被突发流量打垮,也防止因用户滥用导致 API 费用激增。
  • 重试:对于网络抖动等瞬时故障,进行有限次数的重试可以有效提高成功率。

监控与告警 (Prometheus + Grafana)

为了让系统“可观测”,需要建立完善的监控体系。

  • 集成 Micrometer:Spring Boot Actuator 和 Micrometer 是事实上的标准,可以轻松收集应用的性能指标(如方法调用耗时、调用次数、错误率等)。
  • 对接 Prometheus:将 Micrometer 收集的指标暴露给 Prometheus 进行存储和查询。
  • 配置 Grafana 面板:使用 Grafana 可视化 Prometheus 中的数据,创建包含以下关键指标的仪表盘:
    1. 模型性能:平均/最大响应延迟、请求吞吐量 (RPS)、API 调用错误率。
    2. 资源使用:JVM 内存、CPU 利用率。
    3. 业务指标:Token 消耗量(成本)、缓存命中率、RAG 检索召回率。
  • 设置告警规则:在 Prometheus 中设置告警规则,例如当熔断器打开、限流触发、错误率超过阈值时,通过邮件、企业微信或钉钉通知到负责人。

3. 部署与资源配置

JVM 参数调优

AI 应用通常是计算密集型和内存密集型的。建议为应用分配足够的堆内存,并选择合适的垃圾回收器。

  • 堆内存:根据应用规模和并发量,通常建议至少 -Xmx4g。对于处理大文档或高并发的场景,可能需要 -Xmx8g 或更高。
  • GC 选择:对于大内存应用,建议使用 G1 GC (-XX:+UseG1GC),它在大堆内存下能提供更好的停顿时间控制。

资源配置建议

场景 CPU 核心 内存 存储 GPU
开发/测试 4 16GB SSD
中小规模生产 8-16 32-64GB NVMe SSD 可选,用于本地模型
大规模生产 32+ 128GB+ 分布式存储 推荐,用于加速本地模型

希望这份指南能帮助您将 Spring AI 应用成功部署到生产环境。如果需要针对特定云服务商(如阿里云、AWS)的部署细节,或者更深入的 JVM 调优方案,以后可以继续探讨。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐