一、Spring AI 概述
Spring AI 是 Spring 官方推出的 AI 应用开发框架,旨在简化 Java 开发者构建 AI 驱动应用的过程。它遵循 Spring 一贯的设计哲学:约定优于配置 、面向接口编程 、依赖注入,为开发者提供了一套统一、可扩展的 API,屏蔽了不同 AI 模型提供商之间的差异。
Spring AI 的核心价值在于:
- 提供统一的编程模型,支持多种 AI 模型提供商(OpenAI、Anthropic、Google、阿里云、字节跳动等)
- 提供丰富的抽象层,将 AI 能力与 Spring 生态无缝集成
- 支持结构化输出、函数调用、RAG(检索增强生成)等高级特性
- 提供可观测性、重试、限流等企业级功能
二、Spring AI 核心架构详解
Spring AI 采用分层架构设计,从下到上分为模型层、核心抽象层、服务层和集成层。

2.1 模型层
模型层是 Spring AI 的最底层,负责与具体的 AI 模型提供商进行通信。它包含了各个模型提供商的客户端实现,将原生 API 转换为 Spring AI 的统一抽象。
主要组件:
- 聊天模型客户端(ChatModel)
- 嵌入模型客户端(EmbeddingModel)
- 图像生成模型客户端(ImageModel)
- 语音转文本模型客户端(SpeechModel)
- 文本转语音模型客户端(TextToSpeechModel)
2.2 核心抽象层
核心抽象层是 Spring AI 的灵魂,定义了一套与具体模型提供商无关的接口和抽象类。这一层确保了开发者可以在不修改业务代码的情况下,轻松切换不同的 AI 模型提供商。
核心抽象:
Model:所有 AI 模型的根接口ChatModel:聊天模型的抽象EmbeddingModel:嵌入模型的抽象Prompt:AI 输入的抽象ChatResponse:AI 输出的抽象OutputParser:输出解析器的抽象FunctionCallback:函数调用的抽象
2.3 服务层
服务层基于核心抽象层,提供了更高层次的服务和功能。它封装了常见的 AI 应用场景,简化了开发者的使用。
主要服务:
ChatClient:统一的聊天客户端VectorStore:向量存储服务RetrievalAugmentor:检索增强生成服务FunctionCallingService:函数调用服务
2.4 集成层
集成层负责将 Spring AI 与 Spring 生态和其他第三方框架进行集成。
主要集成:
- Spring Boot 自动配置
- Spring Web 集成
- Spring Data 集成
- Spring Security 集成
- Spring Cloud 集成
- Swagger/OpenAPI 集成
三、Spring AI 核心抽象模型深度解析
Spring AI 的核心设计理念是抽象与解耦。通过定义清晰的接口和抽象类,Spring AI 将 AI 能力的使用与具体实现分离开来,使得代码更加灵活、可扩展和可测试。
3.1 核心抽象模型概览

3.2 Model 接口
Model 是所有 AI 模型的根接口,定义了 AI 模型最基本的能力:接收一个 Prompt 并返回一个 ChatResponse。
java
package org.springframework.ai.model;
import org.springframework.ai.prompt.Prompt;
import org.springframework.ai.model.ChatResponse;
/**
* 所有AI模型的根接口
* @author Spring AI Team
*/
public interface Model {
/**
* 调用AI模型
* @param prompt 输入提示
* @return 模型响应
*/
ChatResponse call(Prompt prompt);
}
Model 接口的设计非常简洁,只包含一个 call 方法。这种设计使得所有 AI 模型都具有统一的调用方式,极大地提高了代码的一致性和可维护性。
3.3 Prompt 抽象
Prompt 是 AI 输入的抽象,它包含了发送给 AI 模型的所有信息。一个 Prompt 由多个 Message 组成,每个 Message 代表一次对话中的一条消息。
typescript
package org.springframework.ai.prompt;
import org.springframework.ai.model.Message;
import java.util.List;
import java.util.Map;
/**
* AI输入提示的抽象
* @author Spring AI Team
*/
public class Prompt {
private final List<Message> messages;
private final Map<String, Object> options;
public Prompt(List<Message> messages) {
this(messages, Map.of());
}
public Prompt(List<Message> messages, Map<String, Object> options) {
this.messages = messages;
this.options = options;
}
public List<Message> getMessages() {
return messages;
}
public Map<String, Object> getOptions() {
return options;
}
}
Message 接口定义了消息的基本属性:内容和角色。Spring AI 支持四种消息角色:
USER:用户消息ASSISTANT:助手消息SYSTEM:系统消息FUNCTION:函数调用结果消息
3.4 ChatResponse 抽象
ChatResponse 是 AI 输出的抽象,它包含了 AI 模型返回的所有信息。一个 ChatResponse 由多个 Generation 组成,每个 Generation 代表模型生成的一个候选结果。
typescript
package org.springframework.ai.model;
import java.util.List;
import java.util.Map;
/**
* AI聊天响应的抽象
* @author Spring AI Team
*/
public class ChatResponse {
private final List<Generation> generations;
private final Map<String, Object> metadata;
public ChatResponse(List<Generation> generations) {
this(generations, Map.of());
}
public ChatResponse(List<Generation> generations, Map<String, Object> metadata) {
this.generations = generations;
this.metadata = metadata;
}
public List<Generation> getGenerations() {
return generations;
}
public Map<String, Object> getMetadata() {
return metadata;
}
public Generation getResult() {
return generations.get(0);
}
}
Generation 类包含了生成的消息和相关的元数据,如完成原因、使用的令牌数等。
3.5 OutputParser 接口
OutputParser 是输出解析器的抽象,负责将 AI 模型返回的字符串转换为结构化的数据。这是 Spring AI 中非常重要的一个组件,它解决了 AI 模型输出格式不固定的问题。
java
package org.springframework.ai.parser;
/**
* 输出解析器接口
* @param <T> 解析后的类型
* @author Spring AI Team
*/
public interface OutputParser<T> {
/**
* 解析AI模型的输出
* @param text AI模型返回的字符串
* @return 解析后的结构化数据
*/
T parse(String text);
/**
* 获取格式说明,用于提示AI模型按照指定格式输出
* @return 格式说明字符串
*/
String getFormat();
}
四、ChatClient:统一的 AI 交互入口
ChatClient 是 Spring AI 提供的统一聊天客户端,它封装了 ChatModel 的底层细节,提供了更加流畅和易用的 API。ChatClient 采用构建者模式设计,支持链式调用,极大地简化了 AI 交互的代码编写。
4.1 ChatClient 的设计理念
ChatClient 的设计理念是简单、灵活、可扩展。它提供了多种调用方式,满足不同场景的需求:
- 同步调用:适用于简单的问答场景
- 异步调用:适用于需要非阻塞处理的场景
- 流式调用:适用于需要实时展示结果的场景
同时,ChatClient 支持丰富的配置选项,可以在调用时动态调整模型参数,如温度、最大令牌数等。
4.2 ChatClient 的核心 API
arduino
package org.springframework.ai.chat.client;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Map;
import java.util.function.Consumer;
/**
* 统一的聊天客户端接口
* @author Spring AI Team
*/
public interface ChatClient {
/**
* 创建一个新的ChatClient构建器
* @param chatModel 聊天模型
* @return ChatClient构建器
*/
static Builder builder(ChatModel chatModel) {
return new DefaultChatClientBuilder(chatModel);
}
/**
* 创建一个新的请求规范
* @return 请求规范
*/
RequestSpec prompt();
/**
* ChatClient构建器
*/
interface Builder {
Builder defaultSystem(String text);
Builder defaultSystem(String text, Map<String, Object> variables);
Builder defaultOptions(Consumer<ChatOptions.Builder> optionsConsumer);
ChatClient build();
}
/**
* 请求规范
*/
interface RequestSpec {
RequestSpec system(String text);
RequestSpec system(String text, Map<String, Object> variables);
RequestSpec user(String text);
RequestSpec user(String text, Map<String, Object> variables);
RequestSpec assistant(String text);
RequestSpec options(Consumer<ChatOptions.Builder> optionsConsumer);
ResponseSpec call();
StreamResponseSpec stream();
}
/**
* 同步响应规范
*/
interface ResponseSpec {
String content();
<T> T entity(Class<T> type);
ChatResponse chatResponse();
}
/**
* 流式响应规范
*/
interface StreamResponseSpec {
Flux<String> content();
Flux<ChatResponse> chatResponse();
}
}
4.3 ChatClient 的使用示例
首先,我们需要在 pom.xml 中添加必要的依赖:
xml
<?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.2.5</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>spring-ai-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-ai-demo</name>
<description>Spring AI Demo Project</description>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M1</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-dashscope-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.52</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
然后,在 application.yml 中配置阿里云通义千问的 API Key:
yaml
spring:
ai:
dashscope:
api-key: your-api-key-here
chat:
options:
model: qwen-turbo
temperature: 0.7
max-tokens: 1000
server:
port: 8080
springdoc:
swagger-ui:
path: /swagger-ui.html
api-docs:
path: /v3/api-docs
接下来,创建一个配置类来配置 ChatClient:
kotlin
package com.jam.demo.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Spring AI配置类
* @author ken
*/
@Configuration
public class SpringAiConfig {
/**
* 配置ChatClient
* @param dashScopeChatModel 通义千问聊天模型
* @return ChatClient实例
*/
@Bean
public ChatClient chatClient(DashScopeChatModel dashScopeChatModel) {
return ChatClient.builder(dashScopeChatModel)
.defaultSystem("你是一个专业的Java技术助手,回答问题要准确、简洁、专业。")
.build();
}
}
创建一个简单的控制器来演示 ChatClient 的使用:
kotlin
package com.jam.demo.controller;
import com.jam.demo.dto.ChatRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* 聊天控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/chat")
@RequiredArgsConstructor
@Tag(name = "聊天接口", description = "AI聊天相关接口")
public class ChatController {
private final ChatClient chatClient;
/**
* 同步聊天接口
* @param request 聊天请求
* @return AI响应
*/
@PostMapping("/sync")
@Operation(summary = "同步聊天", description = "发送消息并等待AI完整响应")
public ResponseEntity<String> syncChat(@RequestBody ChatRequest request) {
String response = chatClient.prompt()
.user(request.getMessage())
.call()
.content();
return ResponseEntity.ok(response);
}
/**
* 流式聊天接口
* @param request 聊天请求
* @return AI流式响应
*/
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@Operation(summary = "流式聊天", description = "发送消息并以SSE方式接收AI实时响应")
public Flux<String> streamChat(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.getMessage())
.stream()
.content();
}
}
创建对应的 DTO 类:
kotlin
package com.jam.demo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 聊天请求DTO
* @author ken
*/
@Data
@Schema(description = "聊天请求")
public class ChatRequest {
@Schema(description = "用户消息", example = "什么是Spring AI?")
private String message;
}
最后,创建 Spring Boot 启动类:
typescript
package com.jam.demo;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring AI Demo启动类
* @author ken
*/
@SpringBootApplication
@OpenAPIDefinition(
info = @Info(
title = "Spring AI Demo API",
version = "1.0",
description = "Spring AI演示项目API文档"
)
)
public class SpringAiDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiDemoApplication.class, args);
}
}
4.4 ChatClient 的高级用法
ChatClient 支持很多高级特性,如动态调整模型参数、传递变量、多轮对话等。
动态调整模型参数
scss
String response = chatClient.prompt()
.user("写一首关于春天的诗")
.options(options -> options
.temperature(0.9)
.maxTokens(500))
.call()
.content();
传递变量
javascript
String response = chatClient.prompt()
.user("请用{language}语言解释什么是{concept}",
Map.of("language", "中文", "concept", "依赖注入"))
.call()
.content();
多轮对话
scss
// 第一轮对话
String response1 = chatClient.prompt()
.user("什么是Spring Boot?")
.call()
.content();
// 第二轮对话(需要包含历史消息)
String response2 = chatClient.prompt()
.user("什么是Spring Boot?")
.assistant(response1)
.user("它和Spring Framework有什么区别?")
.call()
.content();
五、Prompt:AI 交互的语言桥梁
Prompt 是人类与 AI 模型沟通的语言,它直接决定了 AI 模型输出的质量。Spring AI 提供了强大的 Prompt 管理能力,支持模板化、变量替换、多消息组合等功能。
5.1 Prompt 的设计理念
Spring AI 的 Prompt 设计遵循以下原则:
- 结构化:将 Prompt 分解为多个消息,每个消息有明确的角色
- 可复用:支持模板化,将通用的 Prompt 逻辑提取为模板
- 可扩展:支持自定义消息类型和模板引擎
- 类型安全:提供类型安全的变量替换机制
5.2 Prompt 模板
Spring AI 内置了基于 Spring Expression Language (SpEL) 的模板引擎,支持变量替换、条件判断、循环等功能。
基本模板使用
typescript
package com.jam.demo.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.prompt.PromptTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* Prompt服务
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PromptService {
private final ChatClient chatClient;
/**
* 使用模板生成解释
* @param concept 要解释的概念
* @param language 解释使用的语言
* @return 解释结果
*/
public String explainConcept(String concept, String language) {
PromptTemplate template = new PromptTemplate(
"请用{language}语言详细解释什么是{concept}。" +
"要求:1. 定义清晰准确;2. 举一个简单的例子;3. 说明它的主要用途。"
);
String promptText = template.render(Map.of(
"concept", concept,
"language", language
));
return chatClient.prompt()
.user(promptText)
.call()
.content();
}
}
条件判断
arduino
PromptTemplate template = new PromptTemplate(
"请写一篇关于{topic}的文章。" +
"#if(${includeCode})" +
"文章中需要包含一个简单的代码示例。" +
"#end"
);
String promptText = template.render(Map.of(
"topic", "Java线程池",
"includeCode", true
));
循环
dart
PromptTemplate template = new PromptTemplate(
"请列出以下技术的主要特点:" +
"#foreach($tech in $technologies)" +
"- $tech" +
"#end"
);
String promptText = template.render(Map.of(
"technologies", List.of("Spring Boot", "Spring Cloud", "Spring AI")
));
5.3 外部 Prompt 模板文件
对于复杂的 Prompt,建议将其保存为外部文件,便于管理和维护。Spring AI 支持从类路径加载 Prompt 模板文件。
创建 src/main/resources/prompts/explain-concept.st 文件:
markdown
请用{language}语言详细解释什么是{concept}。
要求:
1. 定义清晰准确
2. 举一个简单的例子
3. 说明它的主要用途
4. 字数控制在300字以内
然后在代码中加载并使用:
arduino
import org.springframework.core.io.ClassPathResource;
import org.springframework.ai.prompt.PromptTemplate;
public String explainConceptFromFile(String concept, String language) {
PromptTemplate template = new PromptTemplate(new ClassPathResource("prompts/explain-concept.st"));
String promptText = template.render(Map.of(
"concept", concept,
"language", language
));
return chatClient.prompt()
.user(promptText)
.call()
.content();
}
5.4 Prompt 工程最佳实践
- 明确角色:在系统提示中明确 AI 的角色和任务
- 清晰指令:给出清晰、具体的指令,避免模糊不清的表述
- 格式要求:明确指定输出格式,便于后续处理
- 示例引导:提供示例,让 AI 更好地理解你的需求
- 分步思考:对于复杂问题,引导 AI 分步思考
- 限制输出:限制输出的长度和内容,避免无关信息
六、Model:模型能力的抽象与适配
Model 是 Spring AI 对 AI 模型能力的抽象,它定义了 AI 模型应该具备的基本接口。Spring AI 提供了多种 Model 实现,支持不同类型的 AI 模型和不同的模型提供商。
6.1 Model 的设计理念
Spring AI 的 Model 设计遵循以下原则:
- 统一接口:所有同类型的模型都实现相同的接口
- 最小接口:接口只定义最基本的方法,保持简洁
- 可扩展:通过选项机制支持模型的特定功能
- 适配模式:使用适配器模式将原生 API 转换为统一接口
6.2 ChatModel 详解
ChatModel 是聊天模型的抽象,它定义了聊天模型应该具备的基本能力:同步调用和流式调用。
java
package org.springframework.ai.chat.model;
import org.springframework.ai.model.Model;
import org.springframework.ai.prompt.Prompt;
import reactor.core.publisher.Flux;
/**
* 聊天模型接口
* @author Spring AI Team
*/
public interface ChatModel extends Model {
/**
* 同步调用聊天模型
* @param prompt 输入提示
* @return 聊天响应
*/
@Override
ChatResponse call(Prompt prompt);
/**
* 流式调用聊天模型
* @param prompt 输入提示
* @return 流式聊天响应
*/
Flux<ChatResponse> stream(Prompt prompt);
}
6.3 模型选项
Spring AI 通过 ChatOptions 类来配置模型的参数。不同的模型提供商可能有不同的参数,但 Spring AI 定义了一组通用的参数,确保在不同模型之间的兼容性。
通用参数:
model:模型名称temperature:温度,控制输出的随机性maxTokens:最大生成令牌数topP:核采样参数frequencyPenalty:频率惩罚presencePenalty:存在惩罚
scss
import org.springframework.ai.chat.model.ChatOptions;
ChatOptions options = ChatOptions.builder()
.model("qwen-turbo")
.temperature(0.7)
.maxTokens(1000)
.topP(0.9)
.build();
6.4 多模型支持
Spring AI 支持多种模型提供商,你可以在同一个应用中同时使用多个模型。
首先,添加不同模型提供商的依赖:
xml
<!-- 字节跳动豆包 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-doubao-spring-boot-starter</artifactId>
</dependency>
<!-- OpenAI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
然后,在配置文件中配置多个模型:
yaml
spring:
ai:
dashscope:
api-key: your-dashscope-api-key
chat:
options:
model: qwen-turbo
doubao:
api-key: your-doubao-api-key
chat:
options:
model: doubao-pro
openai:
api-key: your-openai-api-key
chat:
options:
model: gpt-3.5-turbo
最后,在代码中使用不同的模型:
typescript
package com.jam.demo.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.doubao.chat.DoubaoChatModel;
import org.springframework.ai.openai.chat.OpenAiChatModel;
import org.springframework.stereotype.Service;
/**
* 多模型服务
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MultiModelService {
private final DashScopeChatModel dashScopeChatModel;
private final DoubaoChatModel doubaoChatModel;
private final OpenAiChatModel openAiChatModel;
/**
* 使用通义千问聊天
* @param message 用户消息
* @return AI响应
*/
public String chatWithQwen(String message) {
return ChatClient.create(dashScopeChatModel)
.prompt()
.user(message)
.call()
.content();
}
/**
* 使用豆包聊天
* @param message 用户消息
* @return AI响应
*/
public String chatWithDoubao(String message) {
return ChatClient.create(doubaoChatModel)
.prompt()
.user(message)
.call()
.content();
}
/**
* 使用GPT-3.5聊天
* @param message 用户消息
* @return AI响应
*/
public String chatWithGpt35(String message) {
return ChatClient.create(openAiChatModel)
.prompt()
.user(message)
.call()
.content();
}
}
七、OutputParser:结构化输出的关键
OutputParser 是 Spring AI 中非常重要的一个组件,它负责将 AI 模型返回的非结构化字符串转换为结构化的数据。这使得开发者可以像调用普通方法一样调用 AI 模型,获取类型安全的返回值。
7.1 OutputParser 的设计理念
Spring AI 的 OutputParser 设计遵循以下原则:
- 类型安全:提供类型安全的解析结果
- 自动格式提示:自动生成格式说明,提示 AI 模型按照指定格式输出
- 容错处理:提供容错机制,处理 AI 模型输出格式不规范的情况
- 可扩展:支持自定义输出解析器
7.2 内置 OutputParser
Spring AI 提供了多种内置的 OutputParser,满足常见的需求:
BeanOutputParser:将输出解析为 Java BeanListOutputParser:将输出解析为列表MapOutputParser:将输出解析为 MapBooleanOutputParser:将输出解析为布尔值IntegerOutputParser:将输出解析为整数DoubleOutputParser:将输出解析为浮点数
BeanOutputParser 使用示例
首先,定义一个 Java Bean:
arduino
package com.jam.demo.dto;
import lombok.Data;
/**
* 书籍信息DTO
* @author ken
*/
@Data
public class Book {
private String title;
private String author;
private String isbn;
private double price;
private String description;
}
然后,使用 BeanOutputParser 解析 AI 输出:
typescript
package com.jam.demo.service;
import com.jam.demo.dto.Book;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.parser.BeanOutputParser;
import org.springframework.stereotype.Service;
/**
* 输出解析服务
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OutputParserService {
private final ChatClient chatClient;
/**
* 获取书籍信息
* @param bookName 书籍名称
* @return 书籍信息
*/
public Book getBookInfo(String bookName) {
BeanOutputParser<Book> parser = new BeanOutputParser<>(Book.class);
String response = chatClient.prompt()
.user("请提供关于《{bookName}》的详细信息。{format}",
Map.of("bookName", bookName, "format", parser.getFormat()))
.call()
.content();
return parser.parse(response);
}
}
ListOutputParser 使用示例
typescript
import org.springframework.ai.parser.ListOutputParser;
/**
* 获取技术栈列表
* @param domain 领域
* @return 技术栈列表
*/
public List<String> getTechStack(String domain) {
ListOutputParser parser = new ListOutputParser();
String response = chatClient.prompt()
.user("请列出{domain}领域常用的10个技术栈。{format}",
Map.of("domain", domain, "format", parser.getFormat()))
.call()
.content();
return parser.parse(response);
}
7.3 ChatClient 集成 OutputParser
ChatClient 提供了 entity 方法,可以直接将 AI 输出解析为指定类型的对象,无需手动创建 OutputParser。
typescript
/**
* 使用ChatClient直接解析为Java Bean
* @param bookName 书籍名称
* @return 书籍信息
*/
public Book getBookInfoWithChatClient(String bookName) {
return chatClient.prompt()
.user("请提供关于《{bookName}》的详细信息。", Map.of("bookName", bookName))
.call()
.entity(Book.class);
}
7.4 自定义 OutputParser
如果内置的 OutputParser 不能满足你的需求,你可以自定义 OutputParser。
typescript
package com.jam.demo.parser;
import com.alibaba.fastjson2.JSON;
import org.springframework.ai.parser.OutputParser;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* 日期输出解析器
* @author ken
*/
public class LocalDateOutputParser implements OutputParser<LocalDate> {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public LocalDate parse(String text) {
if (!StringUtils.hasText(text)) {
return null;
}
// 尝试直接解析
try {
return LocalDate.parse(text.trim(), FORMATTER);
} catch (Exception e) {
// 尝试从JSON中提取
try {
String dateStr = JSON.parseObject(text).getString("date");
return LocalDate.parse(dateStr.trim(), FORMATTER);
} catch (Exception ex) {
throw new IllegalArgumentException("无法解析日期: " + text, ex);
}
}
}
@Override
public String getFormat() {
return "请按照yyyy-MM-dd格式输出日期。如果需要返回多个信息,请将日期放在date字段中,使用JSON格式输出。";
}
}
使用自定义 OutputParser:
typescript
/**
* 获取节日日期
* @param festival 节日名称
* @return 节日日期
*/
public LocalDate getFestivalDate(String festival) {
LocalDateOutputParser parser = new LocalDateOutputParser();
String response = chatClient.prompt()
.user("请告诉我{festival}是哪一天?{format}",
Map.of("festival", festival, "format", parser.getFormat()))
.call()
.content();
return parser.parse(response);
}
八、综合实战案例
下面我们将通过一个完整的案例,展示如何使用 Spring AI 构建一个智能问答系统,支持结构化输出和多轮对话。
8.1 需求分析
我们要构建一个智能图书问答系统,具备以下功能:
- 根据书名查询书籍基本信息
- 根据书名推荐相似书籍
- 根据作者查询其所有作品
- 多轮对话功能
8.2 数据库设计
创建书籍信息表:
sql
CREATE TABLE book (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL COMMENT '书名',
author VARCHAR(255) NOT NULL COMMENT '作者',
isbn VARCHAR(20) UNIQUE COMMENT 'ISBN',
price DECIMAL(10,2) COMMENT '价格',
description TEXT COMMENT '简介',
publish_date DATE COMMENT '出版日期',
category VARCHAR(100) COMMENT '分类',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='书籍信息表';
插入测试数据:
sql
INSERT INTO book (title, author, isbn, price, description, publish_date, category) VALUES
('Java编程思想', 'Bruce Eckel', '9787111213826', 108.00, 'Java领域经典著作,全面介绍了Java语言的特性和编程思想。', '2007-06-01', '计算机/编程'),
('深入理解Java虚拟机', '周志明', '9787111641247', 129.00, '深入解析Java虚拟机的工作原理和实现细节。', '2019-12-01', '计算机/编程'),
('Spring实战', 'Craig Walls', '9787115547620', 89.00, '全面介绍Spring框架的核心概念和使用方法。', '2020-07-01', '计算机/编程'),
('Spring Boot实战', '丁雪丰', '9787115453686', 69.00, '详细讲解Spring Boot的使用和最佳实践。', '2016-05-01', '计算机/编程'),
('设计模式:可复用面向对象软件的基础', 'Erich Gamma', '9787111094043', 59.00, '设计模式领域的经典著作,介绍了23种设计模式。', '2000-09-01', '计算机/编程');
8.3 实体类和 Mapper
创建书籍实体类:
kotlin
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 书籍实体类
* @author ken
*/
@Data
@TableName("book")
public class Book {
@TableId(type = IdType.AUTO)
private Long id;
private String title;
private String author;
private String isbn;
private BigDecimal price;
private String description;
private LocalDate publishDate;
private String category;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
创建 Mapper 接口:
java
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.Book;
import org.apache.ibatis.annotations.Mapper;
/**
* 书籍Mapper
* @author ken
*/
@Mapper
public interface BookMapper extends BaseMapper<Book> {
}
8.4 服务层实现
创建智能图书服务:
typescript
package com.jam.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.dto.BookRecommendation;
import com.jam.demo.entity.Book;
import com.jam.demo.mapper.BookMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
/**
* 智能图书服务
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SmartBookService {
private final ChatClient chatClient;
private final BookMapper bookMapper;
/**
* 根据书名查询书籍信息
* @param title 书名
* @return 书籍信息
*/
public Book getBookByTitle(String title) {
if (!StringUtils.hasText(title)) {
return null;
}
LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>();
wrapper.like(Book::getTitle, title);
List<Book> books = bookMapper.selectList(wrapper);
if (CollectionUtils.isEmpty(books)) {
// 数据库中没有,使用AI生成
return chatClient.prompt()
.user("请提供关于《{title}》的详细信息。", Map.of("title", title))
.call()
.entity(Book.class);
}
return books.get(0);
}
/**
* 推荐相似书籍
* @param title 书名
* @return 推荐书籍列表
*/
public List<BookRecommendation> recommendSimilarBooks(String title) {
Book book = getBookByTitle(title);
if (book == null) {
return List.of();
}
return chatClient.prompt()
.user("根据以下书籍信息,推荐5本相似的书籍:\n" +
"书名:{title}\n" +
"作者:{author}\n" +
"分类:{category}\n" +
"简介:{description}\n" +
"请按照以下格式返回:\n" +
"1. 书名 - 作者 - 推荐理由\n" +
"2. 书名 - 作者 - 推荐理由\n" +
"...",
Map.of(
"title", book.getTitle(),
"author", book.getAuthor(),
"category", book.getCategory(),
"description", book.getDescription()
))
.call()
.entity(new org.springframework.core.ParameterizedTypeReference<List<BookRecommendation>>() {});
}
/**
* 根据作者查询作品
* @param author 作者
* @return 作品列表
*/
public List<String> getBooksByAuthor(String author) {
if (!StringUtils.hasText(author)) {
return List.of();
}
LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Book::getAuthor, author);
List<Book> books = bookMapper.selectList(wrapper);
if (CollectionUtils.isEmpty(books)) {
// 数据库中没有,使用AI生成
return chatClient.prompt()
.user("请列出{author}的主要作品。", Map.of("author", author))
.call()
.entity(new org.springframework.core.ParameterizedTypeReference<List<String>>() {});
}
return books.stream()
.map(Book::getTitle)
.toList();
}
}
创建推荐书籍 DTO:
typescript
package com.jam.demo.dto;
import lombok.Data;
/**
* 书籍推荐DTO
* @author ken
*/
@Data
public class BookRecommendation {
private String title;
private String author;
private String reason;
}
8.5 控制器实现
kotlin
package com.jam.demo.controller;
import com.jam.demo.dto.BookRecommendation;
import com.jam.demo.entity.Book;
import com.jam.demo.service.SmartBookService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 智能图书控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/book")
@RequiredArgsConstructor
@Tag(name = "智能图书接口", description = "智能图书问答相关接口")
public class SmartBookController {
private final SmartBookService smartBookService;
/**
* 根据书名查询书籍信息
* @param title 书名
* @return 书籍信息
*/
@GetMapping("/info")
@Operation(summary = "查询书籍信息", description = "根据书名查询书籍的详细信息")
public ResponseEntity<Book> getBookInfo(@RequestParam String title) {
Book book = smartBookService.getBookByTitle(title);
return ResponseEntity.ok(book);
}
/**
* 推荐相似书籍
* @param title 书名
* @return 推荐书籍列表
*/
@GetMapping("/recommend")
@Operation(summary = "推荐相似书籍", description = "根据书名推荐相似的书籍")
public ResponseEntity<List<BookRecommendation>> recommendBooks(@RequestParam String title) {
List<BookRecommendation> recommendations = smartBookService.recommendSimilarBooks(title);
return ResponseEntity.ok(recommendations);
}
/**
* 根据作者查询作品
* @param author 作者
* @return 作品列表
*/
@GetMapping("/author")
@Operation(summary = "查询作者作品", description = "根据作者查询其主要作品")
public ResponseEntity<List<String>> getBooksByAuthor(@RequestParam String author) {
List<String> books = smartBookService.getBooksByAuthor(author);
return ResponseEntity.ok(books);
}
}
九、最佳实践与常见陷阱
9.1 最佳实践
- 使用 ChatClient 而非直接使用 ChatModel :
ChatClient提供了更加流畅和易用的 API,并且内置了很多有用的功能 - 将 Prompt 模板化:将通用的 Prompt 逻辑提取为模板,便于管理和维护
- 使用结构化输出 :尽可能使用
OutputParser将 AI 输出转换为结构化数据,提高代码的可维护性 - 合理设置模型参数:根据不同的场景调整模型参数,如温度、最大令牌数等
- 添加错误处理:AI 模型调用可能会失败,需要添加适当的错误处理逻辑
- 添加限流和重试:为了防止 API 调用超限,添加限流和重试机制
- 记录日志:记录 AI 模型的输入和输出,便于问题排查和调试
9.2 常见陷阱
- Prompt 不清晰:模糊不清的 Prompt 会导致 AI 输出质量下降
- 过度依赖 AI:不要将所有逻辑都交给 AI 处理,关键逻辑应该由代码实现
- 忽略安全问题:AI 模型可能会生成有害内容,需要添加内容安全检查
- 没有处理格式错误:AI 模型可能不会严格按照指定格式输出,需要添加容错处理
- 没有考虑成本:AI 模型调用是有成本的,需要合理控制调用次数和令牌数
- 硬编码 API Key:不要将 API Key 硬编码在代码中,应该使用配置文件或环境变量
十、总结与展望
Spring AI 为 Java 开发者提供了一套强大而灵活的 AI 应用开发框架。它通过清晰的抽象和统一的 API,极大地降低了 AI 应用开发的门槛。本文详细解析了 Spring AI 的核心架构、核心抽象模型以及四大核心组件的设计理念和使用方法,并通过一个完整的实战案例展示了如何使用 Spring AI 构建智能应用。