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应用开发框架。它具有以下优势:

  1. 易于上手:如果你熟悉Spring生态,使用Spring AI几乎没有学习成本
  2. 功能完整:支持聊天、嵌入、函数调用、RAG等主流AI功能
  3. 扩展性好:可以轻松集成多种AI服务和向量数据库
  4. 生产就绪:提供了完整的监控、日志、错误处理机制

当然,也有一些需要注意的地方:

  1. 依赖外部服务:需要稳定的网络连接和API服务
  2. 成本控制:需要合理设计缓存和限流机制
  3. 数据安全:处理敏感数据时需要特别注意

总的来说,Spring AI 1.0为Java开发者提供了一个excellent的AI应用开发解决方案。如果你正在考虑开发AI应用,我强烈推荐尝试Spring AI。它能让你专注于业务逻辑,而不用担心底层的AI模型集成问题。

希望这篇文章能帮助你快速上手Spring AI 1.0。如果你有任何问题,欢迎在评论区讨论。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注