关于 spring-ai
面向 Spring 应用开发者的 Spring AI 参考技能,涵盖聊天、结构化输出、流式响应、函数调用、嵌入和向量存储,可用于构建生成式 AI 功能。
主要功能
- 01ChatClient 聊天调用
- 02结构化输出与流式响应
- 03函数调用
- 04文本嵌入
- 05向量存储与 RAG
适用场景
- 01在 Spring 服务中接入聊天模型
- 02生成结构化的模型响应
- 03实现长响应的流式处理
- 04基于向量存储构建 RAG 查询
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill spring-ai安装后在 Claude Code 中输入/spring-ai确认已加载。
附带文件advanced.md
详细介绍
Spring AI - 快速参考
完整参考:有关图像生成、多模态/视觉、顾问/中间件、测试模式和提示词模板,请参阅 advanced.md。
深入了解:使用
mcp__documentation__fetch_docs,并将技术指定为:spring-ai,获取全面的文档。
依赖项
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
配置
OpenAI
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7
max-tokens: 1000
embedding:
options:
model: text-embedding-3-small
Azure OpenAI
spring:
ai:
azure:
openai:
api-key: ${AZURE_OPENAI_KEY}
endpoint: ${AZURE_OPENAI_ENDPOINT}
chat:
options:
deployment-name: gpt-4o
temperature: 0.7
Ollama(本地)
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3
temperature: 0.7
基本聊天
@Service
@RequiredArgsConstructor
public class ChatService {
private final ChatClient chatClient;
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
// With system prompt
public String chatWithContext(String message) {
return chatClient.prompt()
.system("You are a helpful assistant specialized in Spring Boot.")
.user(message)
.call()
.content();
}
// With parameters
public String chatWithParams(String message, String topic) {
return chatClient.prompt()
.system(s -> s.text("You are an expert in {topic}.")
.param("topic", topic))
.user(message)
.call()
.content();
}
}
ChatClient 构建器
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("You are a helpful AI assistant.")
.defaultOptions(ChatOptionsBuilder.builder()
.withTemperature(0.7)
.withMaxTokens(1000)
.build())
.build();
}
}
结构化输出
public record BookRecommendation(
String title,
String author,
String genre,
String summary,
int rating
) {}
@Service
public class BookService {
private final ChatClient chatClient;
public BookRecommendation getRecommendation(String preferences) {
return chatClient.prompt()
.user("Recommend a book based on: " + preferences)
.call()
.entity(BookRecommendation.class);
}
public List<BookRecommendation> getRecommendations(String preferences, int count) {
return chatClient.prompt()
.user("Recommend " + count + " books based on: " + preferences)
.call()
.entity(new ParameterizedTypeReference<List<BookRecommendation>>() {});
}
}
流式处理
@Service
public class StreamingChatService {
private final ChatClient chatClient;
public Flux<String> streamChat(String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
// WebFlux controller
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamResponse(@RequestParam String message) {
return streamChat(message);
}
}
函数调用
@Configuration
public class FunctionConfig {
@Bean
@Description("Get current weather for a location")
public Function<WeatherRequest, WeatherResponse> currentWeather() {
return request -> weatherService.getWeather(request.location());
}
@Bean
@Description("Search for products by name")
public Function<ProductSearchRequest, List<Product>> searchProducts() {
return request -> productService.search(request.query(), request.maxResults());
}
}
public record WeatherRequest(String location) {}
public record WeatherResponse(String location, double temperature, String conditions) {}
@Service
public class AssistantService {
private final ChatClient chatClient;
public String assistWithFunctions(String message) {
return chatClient.prompt()
.user(message)
.functions("currentWeather", "searchProducts")
.call()
.content();
}
}
嵌入
@Service
@RequiredArgsConstructor
public class EmbeddingService {
private final EmbeddingModel embeddingModel;
public float[] getEmbedding(String text) {
EmbeddingResponse response = embeddingModel.embedForResponse(List.of(text));
return response.getResult().getOutput();
}
public List<float[]> getEmbeddings(List<String> texts) {
EmbeddingResponse response = embeddingModel.embedForResponse(texts);
return response.getResults().stream()
.map(e -> e.getOutput())
.toList();
}
}
向量存储(RAG)
配置
spring:
ai:
vectorstore:
pgvector:
dimensions: 1536
index-type: HNSW
distance-type: COSINE_DISTANCE
RAG 查询
@Service
@RequiredArgsConstructor
public class RagService {
private final VectorStore vectorStore;
private final ChatClient chatClient;
public String queryWithContext(String question) {
// Retrieve relevant documents
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.query(question)
.withTopK(5)
.withSimilarityThreshold(0.7)
);
// Build context
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// Generate response with context
return chatClient.prompt()
.system("""
You are a helpful assistant. Answer questions based on the provided context.
If the answer is not in the context, say "I don't have information about that."
Context:
{context}
""")
.user(question)
.call()
.content();
}
}
QuestionAnswerAdvisor
@Configuration
public class RagConfig {
@Bean
public ChatClient ragChatClient(ChatClient.Builder builder, VectorStore vectorStore) {
return builder
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
.build();
}
}
// Usage is simple - advisor handles RAG automatically
@Service
public class SimpleRagService {
private final ChatClient ragChatClient;
public String answer(String question) {
return ragChatClient.prompt()
.user(question)
.call()
.content();
}
}
最佳实践
| 应做 | 不应做 |
|---|---|
| 使用结构化输出,获得可预测的结果 | 手动解析自由格式文本 |
| 实现完善的错误处理 | 忽略 API 故障 |
| 对较长的响应使用流式处理 | 等待大型内容生成完成 |
| 尽可能缓存嵌入 | 反复重新生成嵌入 |
| 设置合适的令牌上限 | 使用无限令牌 |
生产环境检查清单
- 妥善保护 API 密钥(环境变量)
- 实施速率限制
- 错误处理与重试
- 监控令牌用量
- 在适当情况下缓存响应
- 为向量存储建立正确的索引
- 确保嵌入维度一致
- 防范提示词注入
- 监控成本并设置警报
- 配置备用模型
不应使用此技能的情况
- 直接调用 OpenAI/Anthropic API - 直接使用相应的 SDK
- 训练 ML 模型 - 使用 Python 框架(PyTorch、TensorFlow)
- 非 Spring 应用 - 使用 LangChain 或原生 SDK
- 简单文本生成 - 对于简单用例,这可能过于复杂
反模式
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 硬编码 API 密钥 | 安全风险 | 使用环境变量 |
| 未设置令牌上限 | 成本激增 | 适当设置 max-tokens |
| 长请求使用同步处理 | 线程阻塞 | 使用流式处理 |
| 忽略速率限制 | API 错误、封禁 | 实现带退避的重试 |
| 不缓存嵌入 | 成本高昂 | 在本地缓存嵌入 |
| 存在提示词注入漏洞 | 安全风险 | 清理用户输入 |
快速故障排除
| 问题 | 诊断 | 修复方法 |
|---|---|---|
| API 密钥无效 | 检查错误消息 | 验证 OPENAI_API_KEY 环境变量 |
| 超出速率限制 | 429 错误 | 添加重试逻辑,减少请求 |
| 较长提示词导致超时 | 连接超时 | 使用流式处理,延长超时时间 |
| 嵌入维度不匹配 | 向量存储错误 | 匹配嵌入模型的维度 |
| 结构化输出失败 | JSON 解析错误 | 简化架构,添加示例 |
参考文档
评论
支持 Markdown