Spring AI 1.0实战教程:Java开发者的AI应用开发指南
作为一名Java开发者,我一直在关注Spring生态系统的发展。当Spring AI 1.0正式版发布时,我第一时间进行了深入研究和实践。这篇文章记录了我在使用Spring AI 1.0开发AI应用过程中的经验和心得,希望能帮助同样想要快速上手Spring AI的开发者们。
Spring AI 1.0概述
Spring AI是Spring团队推出的AI应用开发框架,它将Spring的设计理念和编程模型扩展到AI领域。经过一年多的开发和完善,Spring AI 1.0终于在2024年正式发布,为Java开发者提供了一个完整的AI应用开发解决方案。
核心特性
在我的实际使用中,发现Spring AI 1.0主要具备以下特性:
- 多模型支持:集成OpenAI、Azure OpenAI、Anthropic、Google等主流AI服务
- 统一抽象:提供一致的API接口,屏蔽不同AI服务的差异
- Spring Boot集成:完美融入Spring Boot生态
- 向量数据库支持:内置对Redis、Chroma、Pinecone等向量数据库的支持
- RAG功能:原生支持检索增强生成(Retrieval-Augmented Generation)
快速开始
1. 环境准备
首先,我在项目中添加了Spring AI的依赖:
<dependencies>
<!-- Spring AI Core -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>1.0.0</version>
</dependency>
<!-- OpenAI集成 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. 配置文件
在application.yml中配置OpenAI的API信息:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com
chat:
options:
model: gpt-3.5-turbo
temperature: 0.7
我建议将API密钥设置为环境变量,避免在代码中硬编码。
3. 第一个AI应用
创建一个简单的聊天服务:
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatModel chatModel;
public ChatController(ChatModel chatModel) {
this.chatModel = chatModel;
}
@PostMapping("/simple")
public String simpleChat(@RequestBody String message) {
ChatResponse response = chatModel.call(
new Prompt(message)
);
return response.getResult().getOutput().getContent();
}
@PostMapping("/stream")
public Flux<String> streamChat(@RequestBody String message) {
return chatModel.stream(new Prompt(message))
.map(response -> response.getResult().getOutput().getContent());
}
}
这个例子展示了Spring AI最基本的用法。我发现它的API设计非常直观,符合Spring的编程风格。
核心功能实战
1. 对话管理
在实际项目中,我们通常需要管理对话上下文。我实现了一个带有会话记忆的聊天服务:
@Service
public class ConversationService {
private final ChatModel chatModel;
private final Map<String, List<Message>> conversations = new ConcurrentHashMap<>();
public ConversationService(ChatModel chatModel) {
this.chatModel = chatModel;
}
public String chat(String sessionId, String userMessage) {
List<Message> history = conversations.computeIfAbsent(sessionId, k -> new ArrayList<>());
// 添加用户消息
history.add(new UserMessage(userMessage));
// 保持对话历史在合理范围内
if (history.size() > 20) {
history = history.subList(history.size() - 20, history.size());
conversations.put(sessionId, history);
}
// 调用AI模型
Prompt prompt = new Prompt(history);
ChatResponse response = chatModel.call(prompt);
String aiResponse = response.getResult().getOutput().getContent();
// 添加AI回复到历史记录
history.add(new AssistantMessage(aiResponse));
return aiResponse;
}
public void clearConversation(String sessionId) {
conversations.remove(sessionId);
}
}
2. 函数调用(Function Calling)
Spring AI 1.0支持函数调用功能,这让我能够让AI调用我定义的Java方法。以下是我实现的一个天气查询函数:
@Component
public class WeatherFunction implements Function<WeatherFunction.Request, WeatherFunction.Response> {
@Override
public Response apply(Request request) {
// 模拟天气API调用
return new Response(request.location(), getCurrentWeather(request.location()));
}
private String getCurrentWeather(String location) {
// 这里应该调用真实的天气API
return String.format("%s当前天气:晴朗,温度25°C", location);
}
public record Request(String location) {}
public record Response(String location, String weather) {}
}
在控制器中使用函数调用:
@RestController
public class FunctionCallController {
private final ChatModel chatModel;
private final WeatherFunction weatherFunction;
public FunctionCallController(ChatModel chatModel, WeatherFunction weatherFunction) {
this.chatModel = chatModel;
this.weatherFunction = weatherFunction;
}
@PostMapping("/chat-with-functions")
public String chatWithFunctions(@RequestBody String message) {
UserMessage userMessage = new UserMessage(message);
ChatResponse response = chatModel.call(new Prompt(
List.of(userMessage),
OpenAiChatOptions.builder()
.withFunction("getCurrentWeather", "获取指定城市的当前天气", weatherFunction)
.build()
));
return response.getResult().getOutput().getContent();
}
}
3. 向量嵌入和相似性搜索
我在项目中实现了一个文档搜索功能,使用向量嵌入来查找相关内容:
@Service
public class DocumentSearchService {
private final EmbeddingModel embeddingModel;
private final VectorStore vectorStore;
public DocumentSearchService(EmbeddingModel embeddingModel, VectorStore vectorStore) {
this.embeddingModel = embeddingModel;
this.vectorStore = vectorStore;
}
public void addDocument(String id, String content, Map<String, Object> metadata) {
List<Float> embedding = embeddingModel.embed(content);
Document document = new Document(id, content, metadata);
document.setEmbedding(embedding);
vectorStore.add(List.of(document));
}
public List<Document> searchSimilarDocuments(String query, int topK) {
List<Float> queryEmbedding = embeddingModel.embed(query);
return vectorStore.similaritySearch(
SearchRequest.query(query)
.withTopK(topK)
.withSimilarityThreshold(0.7)
);
}
}
4. RAG应用实现
基于向量搜索,我实现了一个RAG(检索增强生成)应用:
@Service
public class RAGService {
private final ChatModel chatModel;
private final DocumentSearchService documentSearchService;
public RAGService(ChatModel chatModel, DocumentSearchService documentSearchService) {
this.chatModel = chatModel;
this.documentSearchService = documentSearchService;
}
public String ragChat(String question) {
// 1. 检索相关文档
List<Document> relevantDocs = documentSearchService.searchSimilarDocuments(question, 3);
// 2. 构建上下文
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// 3. 构建提示词
String prompt = String.format("""
基于以下上下文信息回答问题。如果上下文中没有相关信息,请说明无法从提供的信息中找到答案。
上下文:
%s
问题:%s
回答:
""", context, question);
// 4. 调用AI模型生成回答
ChatResponse response = chatModel.call(new Prompt(prompt));
return response.getResult().getOutput().getContent();
}
}
多模型支持
Spring AI 1.0的一个亮点是支持多种AI模型。我在项目中同时集成了OpenAI和Anthropic:
@Configuration
public class AIModelConfig {
@Bean
@Primary
public ChatModel openAiChatModel() {
return new OpenAiChatModel(openAiApi(), OpenAiChatOptions.builder()
.withModel("gpt-3.5-turbo")
.withTemperature(0.7F)
.build());
}
@Bean
public ChatModel anthropicChatModel() {
return new AnthropicChatModel(anthropicApi(), AnthropicChatOptions.builder()
.withModel("claude-3-sonnet-20240229")
.withTemperature(0.7F)
.build());
}
}
通过这种配置,我可以在不同场景下使用不同的模型:
@Service
public class MultiModelService {
private final ChatModel openAiModel;
private final ChatModel anthropicModel;
public MultiModelService(@Qualifier("openAiChatModel") ChatModel openAiModel,
@Qualifier("anthropicChatModel") ChatModel anthropicModel) {
this.openAiModel = openAiModel;
this.anthropicModel = anthropicModel;
}
public String getResponse(String message, String modelType) {
ChatModel selectedModel = "anthropic".equals(modelType) ? anthropicModel : openAiModel;
return selectedModel.call(new Prompt(message))
.getResult().getOutput().getContent();
}
}
实际应用案例
我用Spring AI 1.0开发了一个智能客服系统,集成了以下功能:
@RestController
@RequestMapping("/api/customer-service")
public class CustomerServiceController {
private final ConversationService conversationService;
private final RAGService ragService;
private final OrderService orderService;
public CustomerServiceController(ConversationService conversationService,
RAGService ragService,
OrderService orderService) {
this.conversationService = conversationService;
this.ragService = ragService;
this.orderService = orderService;
}
@PostMapping("/chat")
public CustomerServiceResponse chat(@RequestBody CustomerServiceRequest request) {
String sessionId = request.getSessionId();
String message = request.getMessage();
// 判断是否是订单查询
if (isOrderQuery(message)) {
String orderInfo = orderService.queryOrder(extractOrderNumber(message));
return new CustomerServiceResponse(orderInfo, "order_query");
}
// 判断是否需要知识库搜索
if (needsKnowledgeBase(message)) {
String ragResponse = ragService.ragChat(message);
return new CustomerServiceResponse(ragResponse, "knowledge_base");
}
// 普通对话
String response = conversationService.chat(sessionId, message);
return new CustomerServiceResponse(response, "conversation");
}
private boolean isOrderQuery(String message) {
return message.contains("订单") || message.matches(".*\\b\\d{10,}\\b.*");
}
private boolean needsKnowledgeBase(String message) {
String[] keywords = {"使用方法", "功能介绍", "操作指南", "帮助"};
return Arrays.stream(keywords).anyMatch(message::contains);
}
private String extractOrderNumber(String message) {
Pattern pattern = Pattern.compile("\\b\\d{10,}\\b");
Matcher matcher = pattern.matcher(message);
return matcher.find() ? matcher.group() : null;
}
}
性能优化经验
在使用Spring AI 1.0的过程中,我总结了几个性能优化的经验:
1. 连接池配置
@Configuration
public class OpenAiConfig {
@Bean
public OpenAiApi openAiApi(@Value("${spring.ai.openai.api-key}") String apiKey) {
return new OpenAiApi(apiKey, OpenAiApi.builder()
.withBaseUrl("https://api.openai.com")
.withHttpClient(createHttpClient())
.build());
}
private OkHttpClient createHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
.build();
}
}
2. 缓存机制
@Service
public class CachedEmbeddingService {
private final EmbeddingModel embeddingModel;
private final RedisTemplate<String, List<Float>> redisTemplate;
public CachedEmbeddingService(EmbeddingModel embeddingModel,
RedisTemplate<String, List<Float>> redisTemplate) {
this.embeddingModel = embeddingModel;
this.redisTemplate = redisTemplate;
}
@Cacheable(value = "embeddings", key = "#text")
public List<Float> getEmbedding(String text) {
String cacheKey = "embedding:" + DigestUtils.md5DigestAsHex(text.getBytes());
List<Float> cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
List<Float> embedding = embeddingModel.embed(text);
redisTemplate.opsForValue().set(cacheKey, embedding, Duration.ofHours(24));
return embedding;
}
}
3. 异步处理
@Service
public class AsyncAIService {
private final ChatModel chatModel;
public AsyncAIService(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Async
public CompletableFuture<String> generateResponseAsync(String message) {
try {
ChatResponse response = chatModel.call(new Prompt(message));
return CompletableFuture.completedFuture(
response.getResult().getOutput().getContent()
);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}
测试和调试
我在开发过程中建立了完整的测试体系:
@SpringBootTest
class SpringAiApplicationTests {
@Autowired
private ChatModel chatModel;
@Autowired
private EmbeddingModel embeddingModel;
@Test
void testBasicChat() {
String response = chatModel.call(new Prompt("Hello, how are you?"))
.getResult().getOutput().getContent();
assertThat(response).isNotEmpty();
System.out.println("AI Response: " + response);
}
@Test
void testEmbedding() {
List<Float> embedding = embeddingModel.embed("This is a test sentence.");
assertThat(embedding).isNotEmpty();
assertThat(embedding.size()).isGreaterThan(0);
System.out.println("Embedding dimension: " + embedding.size());
}
@Test
void testFunctionCalling() {
WeatherFunction weatherFunction = new WeatherFunction();
ChatResponse response = chatModel.call(new Prompt(
List.of(new UserMessage("北京今天天气怎么样?")),
OpenAiChatOptions.builder()
.withFunction("getCurrentWeather", "获取天气信息", weatherFunction)
.build()
));
assertThat(response.getResult().getOutput().getContent()).isNotEmpty();
}
}
遇到的问题和解决方案
1. API调用超时
问题:在处理长文本时经常遇到超时问题。
解决方案:调整超时配置并实现重试机制:
@Component
public class ResilientChatModel {
private final ChatModel chatModel;
public ResilientChatModel(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000))
public String callWithRetry(String message) {
try {
return chatModel.call(new Prompt(message))
.getResult().getOutput().getContent();
} catch (Exception e) {
System.err.println("API调用失败,正在重试...");
throw e;
}
}
}
2. 中文处理问题
问题:在处理中文文本时,token计算不准确。
解决方案:使用tiktoken库进行准确的token计算:
@Component
public class TokenCounter {
private final Encoding encoding;
public TokenCounter() {
this.encoding = Encodings.newDefaultEncodingRegistry().getEncoding(EncodingType.CL100K_BASE);
}
public int countTokens(String text) {
return encoding.encode(text).size();
}
public boolean exceedsLimit(String text, int limit) {
return countTokens(text) > limit;
}
}
生产环境部署
在生产环境中,我配置了以下监控和日志:
@Component
public class AIMetrics {
private final MeterRegistry meterRegistry;
private final Counter apiCallCounter;
private final Timer responseTimer;
public AIMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.apiCallCounter = Counter.builder("ai.api.calls")
.description("AI API调用次数")
.register(meterRegistry);
this.responseTimer = Timer.builder("ai.response.time")
.description("AI响应时间")
.register(meterRegistry);
}
public String callWithMetrics(ChatModel chatModel, String message) {
apiCallCounter.increment();
return responseTimer.recordCallable(() -> {
return chatModel.call(new Prompt(message))
.getResult().getOutput().getContent();
});
}
}
总结
经过几个月的实际使用,我认为Spring AI 1.0是一个非常成熟的AI应用开发框架。它具有以下优势:
- 易于上手:如果你熟悉Spring生态,使用Spring AI几乎没有学习成本
- 功能完整:支持聊天、嵌入、函数调用、RAG等主流AI功能
- 扩展性好:可以轻松集成多种AI服务和向量数据库
- 生产就绪:提供了完整的监控、日志、错误处理机制
当然,也有一些需要注意的地方:
- 依赖外部服务:需要稳定的网络连接和API服务
- 成本控制:需要合理设计缓存和限流机制
- 数据安全:处理敏感数据时需要特别注意
总的来说,Spring AI 1.0为Java开发者提供了一个excellent的AI应用开发解决方案。如果你正在考虑开发AI应用,我强烈推荐尝试Spring AI。它能让你专注于业务逻辑,而不用担心底层的AI模型集成问题。
希望这篇文章能帮助你快速上手Spring AI 1.0。如果你有任何问题,欢迎在评论区讨论。
