跳到主要内容

Spring AI 速查表

本文档提供 Spring AI 常用配置、API 和代码片段的快速参考。

依赖配置

Maven BOM

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

常用起步依赖

依赖说明
spring-ai-openai-spring-boot-starterOpenAI 集成
spring-ai-ollama-spring-boot-starterOllama 本地模型
spring-ai-anthropic-spring-boot-starterAnthropic Claude
spring-ai-azure-openai-spring-boot-starterAzure OpenAI
spring-ai-pgvector-store-spring-boot-starterPGVector 向量存储
spring-ai-redis-store-spring-boot-starterRedis 向量存储
spring-ai-chroma-store-spring-boot-starterChroma 向量存储
spring-ai-milvus-store-spring-boot-starterMilvus 向量存储

模型配置

OpenAI

spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com
chat:
options:
model: gpt-4o
temperature: 0.7
max-tokens: 1000
embedding:
options:
model: text-embedding-3-small
image:
options:
model: dall-e-3
quality: standard
size: 1024x1024

Ollama

spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
model: llama3.2
options:
temperature: 0.7
embedding:
model: nomic-embed-text

Azure OpenAI

spring:
ai:
azure:
openai:
api-key: ${AZURE_OPENAI_API_KEY}
endpoint: ${AZURE_OPENAI_ENDPOINT}
chat:
options:
deployment-name: gpt-4

ChatClient API

基本用法

// 简单对话
String response = chatClient.prompt()
.user("你好")
.call()
.content();

// 系统提示
String response = chatClient.prompt()
.system("你是Java专家")
.user("解释Spring Boot")
.call()
.content();

// 流式输出
Flux<String> stream = chatClient.prompt()
.user("讲个故事")
.stream()
.content();

结构化输出

// 输出为对象
record Person(String name, int age) {}
Person person = chatClient.prompt()
.user("生成人物信息")
.call()
.entity(Person.class);

// 输出为列表
List<Book> books = chatClient.prompt()
.user("推荐5本书")
.call()
.entity(new ParameterizedTypeReference<List<Book>>() {});

模型选项

String response = chatClient.prompt()
.user("问题")
.options(ChatOptionsBuilder.builder()
.withModel("gpt-4o")
.withTemperature(0.8)
.withMaxTokens(500)
.build())
.call()
.content();

提示词模板

基本模板

String template = "告诉我一个关于{topic}的笑话";
PromptTemplate promptTemplate = new PromptTemplate(template);
Prompt prompt = promptTemplate.create(Map.of("topic", "程序员"));

String response = chatClient.prompt()
.prompt(prompt)
.call()
.content();

外部模板文件

# src/main/resources/prompts/system.st
你是一个专业的{role}。
请用{style}风格回答问题。
@Value("classpath:/prompts/system.st")
Resource systemPromptResource;

PromptTemplate template = new PromptTemplate(systemPromptResource);
Prompt prompt = template.create(Map.of(
"role", "Java开发专家",
"style", "简洁专业"
));

Embedding 嵌入

获取嵌入向量

@Autowired
private EmbeddingModel embeddingModel;

// 单个文本
float[] embedding = embeddingModel.embed("文本内容");

// 批量获取
EmbeddingResponse response = embeddingModel.embedForResponse(List.of("文本1", "文本2"));
float[] embedding = response.getResult().getOutput();

相似度计算

// 使用向量存储计算
List<Document> similar = vectorStore.similaritySearch(
SearchRequest.query(text).withTopK(5)
);

// 手动计算余弦相似度
public double cosineSimilarity(float[] vector1, float[] vector2) {
double dotProduct = 0.0, norm1 = 0.0, norm2 = 0.0;
for (int i = 0; i < vector1.length; i++) {
dotProduct += vector1[i] * vector2[i];
norm1 += vector1[i] * vector1[i];
norm2 += vector2[i] * vector2[i];
}
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}

多模态处理

图像输入

// URL 图像
var userMessage = new UserMessage(
"描述这张图片",
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageUrl))
);

// 上传图像
var userMessage = new UserMessage(
"描述这张图片",
List.of(new Media(MimeTypeUtils.IMAGE_JPEG, file.getBytes()))
);

String response = chatClient.prompt()
.messages(userMessage)
.call()
.content();

图像生成

@Autowired
private ImageModel imageModel;

// 简单生成
ImageResponse response = imageModel.call(new ImagePrompt("一只橙色的猫"));
String imageUrl = response.getResult().getOutput().getUrl();

// 带选项生成
ImageOptions options = OpenAiImageOptionsBuilder.builder()
.withModel("dall-e-3")
.withQuality("hd")
.withSize("1024x1024")
.withStyle("vivid")
.build();

ImageResponse response = imageModel.call(new ImagePrompt(prompt, options));

音频处理

语音识别

@Autowired
private AudioTranscriptionModel transcriptionModel;

AudioTranscriptionRequest request = AudioTranscriptionRequest.builder()
.audio(audioResource)
.language("zh")
.build();

String text = transcriptionModel.transcribe(request).getResult().getOutput();

文本转语音

@Autowired
private SpeechModel speechModel;

SpeechPrompt prompt = new SpeechPrompt("要转换的文本");
prompt.getOptions().setVoice("alloy");
prompt.getOptions().setSpeed(1.0);

byte[] audioData = speechModel.call(prompt).getResult().getOutput();

向量存储

添加文档

// 简单文档
Document doc = new Document("文档内容");
vectorStore.add(List.of(doc));

// 带元数据
Document doc = new Document("文档内容", Map.of(
"category", "technical",
"author", "张三"
));
vectorStore.add(List.of(doc));

相似度搜索

// 基本搜索
List<Document> docs = vectorStore.similaritySearch("查询内容");

// 带参数搜索
SearchRequest request = SearchRequest.query("查询内容")
.withTopK(5)
.withSimilarityThreshold(0.7)
.withFilterExpression("category == 'java'");

List<Document> docs = vectorStore.similaritySearch(request);

过滤表达式

// 等于
"category == 'java'"

// 不等于
"category != 'deprecated'"

// 比较
"year >= 2023"

// 组合
"category == 'java' && year >= 2023"

// 或条件
"category == 'java' || category == 'spring'"

// 包含
"category in ['java', 'spring', 'kotlin']"

// 模糊匹配
"title like '%Spring%'"

RAG 实现

使用 QuestionAnswerAdvisor

ChatClient ragClient = ChatClient.builder(chatModel)
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
.build();

String response = ragClient.prompt()
.user("什么是Spring AI?")
.call()
.content();

自定义检索参数

String response = chatClient.prompt()
.user(question)
.advisors(advisor -> advisor
.param(QuestionAnswerAdvisor.SEARCH_TOP_K, 5)
.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "category == 'java'"))
.call()
.content();

Tool Calling

定义工具

@Component
public class MyTools {

@Tool(description = "工具描述")
public String myTool(
@ToolParam(description = "参数描述") String param) {
return "结果";
}
}

使用工具

String response = chatClient.prompt()
.user("问题")
.functions("myTool", "anotherTool")
.call()
.content();

函数式工具

@Bean
@Description("计算器")
public Function<CalcRequest, CalcResponse> calculator() {
return request -> new CalcResponse(compute(request.expression()));
}

对话记忆

配置

@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(new InMemoryChatMemoryRepository())
.maxMessages(20)
.build();
}

@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
return builder
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}

使用

String response = chatClient.prompt()
.user(message)
.advisors(advisor -> advisor
.param(ChatMemory.CONVERSATION_ID, sessionId))
.call()
.content();

可观测性

配置

management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
tracing:
sampling:
probability: 1.0

spring:
ai:
chat:
observations:
include-completion-tokens: true
include-prompt-tokens: true

自定义指标

@Service
public class MetricsService {
private final Counter requestCounter;
private final Timer responseTimer;

public MetricsService(MeterRegistry registry) {
this.requestCounter = Counter.builder("ai.requests")
.register(registry);
this.responseTimer = Timer.builder("ai.response.time")
.register(registry);
}
}

文档处理

文本分割

TokenTextSplitter splitter = new TokenTextSplitter(
512, // 块大小
128, // 重叠大小
5, // 最小块大小
10000, // 最大块大小
true // 保持段落完整
);

List<Document> chunks = splitter.apply(documents);

文档读取

// 读取文件
TikaDocumentReader reader = new TikaDocumentReader(new FileSystemResource("file.pdf"));
List<Document> documents = reader.get();

// 读取文本
TextReader textReader = new TextReader(new FileSystemResource("file.txt"));
List<Document> documents = textReader.get();

常用配置

流式响应

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}

异常处理

@RestControllerAdvice
public class AiExceptionHandler {

@ExceptionHandler(AiClientException.class)
public ResponseEntity<String> handleAiException(AiClientException e) {
return ResponseEntity.internalServerError()
.body("AI服务错误:" + e.getMessage());
}

@ExceptionHandler(RateLimitException.class)
public ResponseEntity<String> handleRateLimit(RateLimitException e) {
return ResponseEntity.status(429)
.body("请求过于频繁,请稍后重试");
}
}

测试

Mock 测试

@SpringBootTest
class AiServiceTest {

@MockBean
private ChatClient chatClient;

@Test
void testChat() {
when(chatClient.prompt()).thenReturn(mockPromptSpec);
// 测试逻辑
}
}

集成测试

@SpringBootTest
class ChatIntegrationTest {

@Autowired
private ChatClient chatClient;

@Test
void testChatResponse() {
String response = chatClient.prompt()
.user("测试问题")
.call()
.content();

assertThat(response).isNotEmpty();
}
}

常见问题

API Key 配置

# 推荐使用环境变量
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}

代理设置

spring:
ai:
openai:
base-url: https://your-proxy.com

Ollama 本地模型

# 启动 Ollama
ollama serve

# 拉取模型
ollama pull llama3.2
ollama pull nomic-embed-text
ollama pull llava # 多模态

参考链接