编程 Spring AI 2.0 深度解析:Java 开发者终于有了自己的 AI Agent 基础设施

2026-05-13 18:19:28 +0800 CST views 2

Spring AI 2.0 深度解析:Java 开发者终于有了自己的 AI Agent 基础设施

前言

2026年,Spring AI 2.0 正式发布,Java 生态终于迎来了原生 AI 基础框架。

从 2025 年 Spring AI 1.0 GA 正式发布,到 2026 年全面拥抱 Agent 工程化,Spring AI 已经从"概念验证"演化为"生产级框架"。它支持 50+ 模型接入、统一 ChatClient API、完整 Tool Calling 支持、原生 MCP 协议集成,以及让 RAG、记忆、日志、限流这些横切关注点可以像 Servlet Filter 一样组合的 Advisors 机制。

更重要的是,Spring AI 2.0 与 Spring Boot 4.0、Spring Framework 7.0 深度整合,让 Java 开发者第一次拥有了与 Python 开发者使用 LangChain 时同等的 AI 工程化能力——不再需要被迫切换到 Python 生态。

本文将从 Spring AI 2.0 的核心架构出发,深入解析:统一 ChatClient API 设计、Tool Calling 与 Java 方法签名打通、Advisors 机制、RAG 企业知识库实战、MCP 协议集成、以及与 LangChain4j 的深度对比选型。

一、为什么 Spring AI 2.0 是游戏改变者?

1.1 Java 开发者 AI 困境的终结

// 传统方式: 用 Java 调 OpenAI API (繁琐、易错)
public class OldWay {
    public static void main(String[] args) throws Exception {
        // 1. 手动构建 HTTP 请求
        HttpClient client = HttpClient.newHttpClient();
        ObjectMapper mapper = new ObjectMapper();
        
        Map<String, Object> requestBody = Map.of(
            "model", "gpt-4",
            "messages", List.of(
                Map.of("role", "user", "content", "Hello!")
            ),
            "temperature", 0.7
        );
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.openai.com/v1/chat/completions"))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                mapper.writeValueAsString(requestBody)
            ))
            .build();
        
        // 2. 手动解析响应
        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
        
        ChatResponse chatResponse = mapper.readValue(
            response.body(), ChatResponse.class);
        
        System.out.println(chatResponse.getChoices().get(0)
            .getMessage().getContent());
        
        // 问题: 
        // - 没有类型安全
        // - 没有流式支持
        // - 切换模型需要重写代码
        // - RAG、记忆、限流需要自己实现
    }
}

// Spring AI 方式: 声明式调用 (简洁、安全、可测试)
@RestController
public class AiController {
    private final ChatClient chatClient;  // 统一接口
    
    // 所有模型厂商一个写法
    public AiController(ChatClient.Builder builder) {
        this.chatClient = builder.defaultSystem("你是智能助手").build();
    }
    
    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();  // 类型安全
    }
}

1.2 Spring AI 2.0 核心能力矩阵

// Spring AI 2.0 核心能力

public class SpringAI2Capabilities {
    
    // 1. 50+ 模型支持 (OpenAI, Anthropic, Google, Azure, AWS, HuggingFace...)
    // 不需要关心底层差异,API 统一
    
    // 2. 统一 ChatClient API
    // 所有模型厂商一个写法
    
    // 3. 结构化输出 (Structured Output)
    // AI 返回自动映射到 Java 强类型
    
    // 4. Tool Calling / Function Calling
    // AI 调用 Java 方法,参数自动验证
    
    // 5. 流式响应 (Streaming)
    // Server-Sent Events / ReadableStream
    
    // 6. RAG (Retrieval-Augmented Generation)
    // 向量检索 + 生成,文档问答
    
    // 7. 记忆 (Memory)
    // 多轮对话上下文自动管理
    
    // 8. MCP (Model Context Protocol)
    // 与 AI Agent 工具生态深度集成
    
    // 9. Advisors (切面机制)
    // RAG、记忆、日志、限流可组合
}

二、ChatClient 统一 API:所有模型厂商一个写法

2.1 基础调用

// ChatClient 基础用法

// 配置 (application.yml)
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}

@RestController
public class ChatController {
    private final ChatClient chatClient;
    
    public ChatController(ChatClient.Builder builder) {
        // 构建器模式,支持全局默认配置
        this.chatClient = builder
            .defaultSystem("你是一个专业的技术顾问")
            .defaultOptions(
                ChatOptionsBuilder.builder()
                    .temperature(0.7)
                    .maxTokens(2000)
                    .build()
            )
            .build();
    }
    
    // 简单对话
    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }
    
    // 多轮对话 (带记忆)
    @GetMapping("/chat/remember")
    public String chatWithMemory(@RequestParam String message, 
                                  HttpSession session) {
        // 从 session 获取对话历史
        Prompt prompt = (Prompt) session.getAttribute("prompt");
        if (prompt == null) {
            prompt = new Prompt(new UserMessage(message));
        } else {
            prompt = new Prompt(List.of(
                prompt.getInstructions(),
                new UserMessage(message)
            ));
        }
        
        String response = chatClient.prompt(prompt).call().content();
        session.setAttribute("prompt", 
            new Prompt(List.of(
                new UserMessage(message),
                new AssistantMessage(response)
            )));
        
        return response;
    }
}

2.2 模型切换

// 一套代码,切换不同模型

@Service
public class MultiModelService {
    private final ChatClient openAiClient;
    private final ChatClient anthropicClient;
    private final ChatClient localClient;
    
    public MultiModelService(
            @Qualifier("OpenAiChatClient") ChatClient openAiClient,
            @Qualifier("AnthropicChatClient") ChatClient anthropicClient,
            @Qualifier("LocalChatClient") ChatClient localClient) {
        this.openAiClient = openAiClient;
        this.anthropicClient = anthropicClient;
        this.localClient = localClient;
    }
    
    // 根据场景选择模型
    public String generate(String task, String model) {
        ChatClient client = switch (model) {
            case "gpt-4" -> openAiClient;
            case "claude" -> anthropicClient;
            case "llama" -> localClient;  // 本地模型,省钱
            default -> openAiClient;
        };
        
        return client.prompt()
            .user(task)
            .call()
            .content();
    }
    
    // A/B 测试不同模型效果
    public Map<String, String> compareModels(String prompt) {
        return Map.of(
            "gpt-4", openAiClient.prompt().user(prompt).call().content(),
            "claude-3", anthropicClient.prompt().user(prompt).call().content()
        );
    }
}

2.3 流式响应

// 流式响应:实时显示 AI 生成过程

@RestController
public class StreamingController {
    private final ChatClient chatClient;
    
    public StreamingController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    
    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .stream()  // 流式调用
            .content();  // 逐字返回
    }
}

// 前端 SSE 消费示例
/*
const eventSource = new EventSource('/chat/stream?message=Hello');

eventSource.onmessage = (event) => {
    // event.data 包含每个 token
    document.getElementById('output').textContent += event.data;
};

eventSource.onerror = (error) => {
    console.error('Stream error:', error);
    eventSource.close();
};
*/

// WebClient 流式 (适合微服务)
@Service
public class StreamingService {
    private final WebClient webClient = WebClient.create();
    
    public Flux<String> streamChat(String message) {
        return webClient.post()
            .uri("https://api.openai.com/v1/chat/completions")
            .header("Authorization", "Bearer " + apiKey)
            .bodyValue(Map.of(
                "model", "gpt-4",
                "messages", List.of(Map.of("role", "user", "content", message)),
                "stream", true
            ))
            .retrieve()
            .bodyToFlux(String.class);
    }
}

三、结构化输出:AI 返回自动映射到 Java 强类型

3.1 核心设计

// Spring AI 2.0 结构化输出
// AI 返回的 JSON 自动映射到 Java 对象

public class StructuredOutput {
    
    // 定义期望的输出结构
    public record Movie(String title, int year, String genre, double rating) {}
    
    public record SearchResult(String title, String url, String snippet) {}
    
    // 电影推荐 (返回 POJO)
    public Movie recommendMovie(String genre) {
        return chatClient.prompt()
            .user("推荐一部" + genre + "类型的电影,返回标题、年份、类型、评分")
            .call()
            .entity(Movie.class);  // 自动映射
    }
    
    // 批量搜索结果
    public List<SearchResult> search(String query) {
        return chatClient.prompt()
            .user("搜索" + query + ",返回10个结果,包含标题、URL、摘要")
            .call()
            .entity(new ParameterizedTypeReference<List<SearchResult>>() {});
    }
}

3.2 高级用法:复杂结构

// 复杂结构化输出

// 1. 嵌套对象
public record Company(
    String name,
    int foundedYear,
    List<Department> departments
) {}

public record Department(
    String name,
    int employeeCount,
    Manager manager
) {}

public record Manager(String name, String title) {}

// 2. 枚举和约束
public record Order(
    String orderId,
    OrderStatus status,  // 枚举
    List<OrderItem> items,
    @JsonProperty("total_amount") BigDecimal totalAmount
) {}

public enum OrderStatus {
    PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}

public record OrderItem(String productName, int quantity, BigDecimal price) {}

// 3. 带验证的输出
public record UserProfile(
    @NotBlank String name,
    @Email String email,
    @Min(18) @Max(150 int age,
    @Pattern(regexp = "^\\d{10}$") String phone
) {}

// 4. 条件渲染
public record ApiResponse(
    boolean success,
    String message,
    @JsonInclude(NON_NULL) Object data,  // 成功时返回
    @JsonInclude(NON_NULL) String error   // 失败时返回
) {}

// 实战:API 响应解析
public ApiResponse analyzeDocument(String content) {
    String prompt = """
        分析以下文档,返回结构化结果:
        %s
        
        规则:
        - 如果文档包含错误信息,success=false,error=错误描述
        - 如果文档有效,success=true,data=分析结果
        """.formatted(content);
    
    return chatClient.prompt()
        .user(prompt)
        .call()
        .entity(ApiResponse.class);
}

3.3 JSON Schema 控制

// 精确控制输出格式

@Configuration
public class StructuredOutputConfig {
    
    @Bean
    public ChatClient structuredChatClient(ChatClient.Builder builder) {
        return builder
            .defaultOptions(
                ChatOptionsBuilder.builder()
                    // 强制 JSON 输出
                    .responseFormat(new ChatOptions.ResponseFormat(
                        ResponseFormat.Type.JSON,
                        """
                        {
                          "type": "object",
                          "properties": {
                            "title": {"type": "string"},
                            "year": {"type": "integer"},
                            "rating": {"type": "number", "minimum": 0, "maximum": 10},
                            "genres": {
                              "type": "array",
                              "items": {"type": "string"}
                            }
                          },
                          "required": ["title", "year", "rating"]
                        }
                        """
                    ))
                    .build()
            )
            .build();
    }
}

// 使用自定义 Schema
public record BookReview(
    @JsonProperty("book_title") String bookTitle,
    @JsonProperty("review_summary") String reviewSummary,
    @JsonProperty("rating") double rating,
    @JsonProperty("key_takeaways") List<String> keyTakeaways
) {}

public BookReview analyzeBook(String bookText) {
    return structuredChatClient.prompt()
        .user("分析这本书,返回结构化评价:" + bookText)
        .call()
        .entity(BookReview.class);
}

四、Tool Calling:AI 调用 Java 方法

4.1 工具定义

// Spring AI 2.0 Tool Calling

// 1. 简单工具 (无参数)
public class SimpleTools {
    
    @Tool(description = "获取当前天气")
    public String getCurrentWeather() {
        return "北京,晴,25°C";
    }
    
    @Tool(description = "获取当前时间")
    public String getCurrentTime() {
        return LocalDateTime.now().toString();
    }
}

// 2. 带参数的工具
public class ParameterizedTools {
    
    @Tool(description = "搜索书籍")
    public record SearchBooksRequest(
        @ToolParam(description = "搜索关键词") String query,
        @ToolParam(description = "最大返回数量", defaultValue = "10") int limit
    ) {}
    
    @Tool(description = "搜索书籍")
    public List<Book> searchBooks(SearchBooksRequest request) {
        // 调用数据库或外部 API
        return bookRepository.findByTitleContaining(
            request.query(), 
            PageRequest.of(0, request.limit())
        );
    }
    
    @Tool(description = "计算日期差")
    public record DateDiffRequest(
        @ToolParam(description = "开始日期,格式 yyyy-MM-dd") String startDate,
        @ToolParam(description = "结束日期,格式 yyyy-MM-dd") String endDate
    ) {}
    
    @Tool(description = "计算两个日期之间的天数")
    public long calculateDateDiff(DateDiffRequest request) {
        LocalDate start = LocalDate.parse(request.startDate());
        LocalDate end = LocalDate.parse(request.endDate());
        return ChronoUnit.DAYS.between(start, end);
    }
}

4.2 工具注册与调用

// 工具注册

@Configuration
public class ToolConfiguration {
    
    @Bean
    public ChatClient toolChatClient(ChatClient.Builder builder) {
        // 方式1: 使用 @Tool 注解的方法
        // 方式2: 实现 ToolCallback 接口
        // 方式3: 使用 FunctionCallback
        
        return builder
            .defaultTools(new SimpleTools(), new ParameterizedTools())
            // 或
            .defaultTools(
                FunctionCallback.builder()
                    .name("get_weather")
                    .description("获取指定城市的天气")
                    .inputType(GetWeatherRequest.class)
                    .handler(request -> weatherService.getWeather(request.city()))
                    .build()
            )
            .build();
    }
}

// 调用带工具的 ChatClient
@Service
public class AiAssistantService {
    private final ChatClient chatClient;
    
    public AiAssistantService(ChatClient.Builder builder) {
        this.chatClient = builder
            .defaultTools(new DocumentTools(), new DatabaseTools())
            .build();
    }
    
    public String assistant(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .tools()  // 启用工具
            .call()
            .content();
    }
    
    // Agent 循环: AI 调用工具 → 工具返回 → AI 处理 → 继续调用
    public String agentLoop(String initialMessage) {
        String currentMessage = initialMessage;
        int maxIterations = 10;
        
        for (int i = 0; i < maxIterations; i++) {
            // AI 决定是否调用工具
            ChatResponse response = chatClient.prompt()
                .user(currentMessage)
                .tools()
                .call()
                .chatResponse();
            
            // 检查是否有工具调用
            if (response.hasToolCalls()) {
                // 执行工具
                for (ToolCall call : response.toolCalls()) {
                    String toolResult = executeTool(call);
                    currentMessage = "工具 '" + call.name() + "' 返回: " + toolResult;
                }
            } else {
                // AI 直接返回答案
                return response.getResult().getOutput().getContent();
            }
        }
        
        return "Agent 达到最大迭代次数";
    }
}

4.3 完整实战:AI 数据库查询助手

// AI 数据库查询助手

// 工具定义
public class DatabaseTools {
    private final JdbcTemplate jdbcTemplate;
    
    public DatabaseTools(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Tool(description = "执行 SQL 查询并返回结果")
    public record QueryRequest(
        @ToolParam(description = "SQL 查询语句") String sql,
        @ToolParam(description = "最大返回行数", defaultValue = "100") int limit
    ) {}
    
    @Tool(description = "执行只读 SQL 查询")
    public List<Map<String, Object>> executeQuery(QueryRequest request) {
        String sql = request.sql() + " LIMIT " + request.limit();
        
        // 安全检查:只允许 SELECT
        if (!sql.trim().toUpperCase().startsWith("SELECT")) {
            return List.of(Map.of("error", "Only SELECT queries allowed"));
        }
        
        return jdbcTemplate.queryForList(sql);
    }
    
    @Tool(description = "获取数据库表结构信息")
    public record TableSchemaRequest(
        @ToolParam(description = "表名") String tableName
    ) {}
    
    @Tool(description = "获取指定表的列信息")
    public String getTableSchema(TableSchemaRequest request) {
        String sql = """
            SELECT column_name, data_type, is_nullable, column_default
            FROM information_schema.columns
            WHERE table_name = ?
            ORDER BY ordinal_position
            """;
        
        List<Map<String, Object>> columns = jdbcTemplate.queryForList(sql, 
            request.tableName());
        
        return columns.stream()
            .map(c -> c.get("column_name") + " (" + c.get("data_type") + ")")
            .collect(Collectors.joining(", "));
    }
}

// Agent 实现
@Service
public class SqlAgent {
    private final ChatClient chatClient;
    
    public SqlAgent(ChatClient.Builder builder, JdbcTemplate jdbcTemplate) {
        this.chatClient = builder
            .defaultSystem("""
                你是一个数据库查询助手。用户可以用自然语言描述他们想要的数据查询。
                
                工作流程:
                1. 理解用户的查询意图
                2. 询问需要查询哪个表(如果用户没有指定)
                3. 使用 get_table_schema 工具查看表结构
                4. 使用 execute_query 工具执行 SQL
                5. 用用户友好的方式解释结果
                
                安全规则:
                - 只能执行 SELECT 查询
                - 默认返回最多 100 行
                - 解释结果时使用中文
                """)
            .defaultTools(new DatabaseTools(jdbcTemplate))
            .build();
    }
    
    public String query(String naturalLanguage) {
        return chatClient.prompt()
            .user(naturalLanguage)
            .tools()
            .call()
            .content();
    }
}

// 使用
@RestController
public class SqlController {
    private final SqlAgent sqlAgent;
    
    @GetMapping("/query")
    public String query(@RequestParam String question) {
        return sqlAgent.query(question);
    }
}

五、Advisors 机制:AI 领域的 AOP

5.1 什么是 Advisors?

// Spring AI 2.0 Advisors 机制
// 类似 Servlet Filter,但用于 AI 调用链

public class AdvisorsConcept {
    // Advisors 解决的问题:
    // - RAG: 每次请求前注入相关文档
    // - 记忆: 管理多轮对话上下文
    // - 日志: 记录每次 AI 调用
    // - 限流: 控制 API 调用频率
    // - 重试: 自动重试失败的请求
    // - 监控: 收集 AI 性能指标
    
    // 链式组合
    // Request → [限流 Advisor] → [日志 Advisor] → [RAG Advisor] → [记忆 Advisor] → AI
    // Response → [记忆 Advisor] → [监控 Advisor] → Return
}

5.2 内置 Advisors

// 1. 记忆 Advisor (多轮对话)
@Configuration
public class MemoryAdvisorConfig {
    
    @Bean
    public ChatMemory chatMemory() {
        // 方式1: 简单内存存储
        return new InMemoryChatMemory();
        
        // 方式2: 持久化存储
        return new JPAChatMemory(entityManager);
        
        // 方式3: Redis 分布式存储
        return new RedisChatMemory(redisTemplate);
    }
    
    @Bean
    public ChatClient memoryChatClient(
            ChatClient.Builder builder, 
            ChatMemory chatMemory) {
        
        return builder
            .defaultAdvisors(
                new MessageChatMemoryAdvisor(chatMemory, "user-123")
            )
            .build();
    }
}

// 2. RAG Advisor
@Configuration
public class RagAdvisorConfig {
    
    @Bean
    public VectorStore vectorStore(PgVectorStoreProperties properties) {
        return new PgVectorStore(jdbcTemplate, 
            new OpenAiEmbeddingModel());
    }
    
    @Bean
    public ChatClient ragChatClient(
            ChatClient.Builder builder,
            VectorStore vectorStore) {
        
        return builder
            .defaultAdvisors(
                new RagAdvisor(vectorStore, 
                    SearchRequest.defaults()
                        .withTopK(5)
                        .withSimilarityThreshold(0.7)
                )
            )
            .build();
    }
}

// 3. 限流 Advisor
@Configuration
public class RateLimitAdvisorConfig {
    
    @Bean
    public ChatClient rateLimitedChatClient(ChatClient.Builder builder) {
        return builder
            .defaultAdvisors(
                new RateLimitAdvisor(
                    TokenBucketRateLimiter.create(
                        100,  // 每分钟 100 次
                        10    // 突发容量
                    )
                )
            )
            .build();
    }
}

// 4. 日志 Advisor
@Configuration
public class LoggingAdvisorConfig {
    
    @Bean
    public ChatClient loggedChatClient(ChatClient.Builder builder) {
        return builder
            .defaultAdvisors(
                new LoggingAdvisor(LogLevel.DEBUG)
            )
            .build();
    }
}

5.3 自定义 Advisor

// 自定义 Advisor

public class CustomAdvisor implements ChatClientRetryAdvisor {
    
    @Override
    public AdvisedRequest beforeRetries(AdvisedRequest request, int attempt, Exception exception) {
        // 在重试前执行
        log.warn("Retrying request, attempt: {}, error: {}", attempt, exception.getMessage());
        
        // 可以修改请求
        return request;
    }
    
    @Override
    public void afterRetries(AdvisedRequest request, int attempt, Exception finalException) {
        // 所有重试失败后执行
        metrics.record("ai.retry.failed", Map.of(
            "error_type", finalException.getClass().getSimpleName(),
            "attempts", attempt
        ));
        
        // 发送告警
        alertService.send("AI API 重试失败: " + finalException.getMessage());
    }
}

// 使用自定义 Advisor
@Configuration
public class CustomAdvisorConfig {
    
    @Bean
    public ChatClient customChatClient(ChatClient.Builder builder) {
        return builder
            .defaultAdvisors(
                // 链式组合: 先限流,再日志,最后自定义
                new RateLimitAdvisor(tokenBucket),
                new LoggingAdvisor(LogLevel.INFO),
                new CustomAdvisor()
            )
            .build();
    }
}

// Advisor 执行顺序
// 配置时按添加顺序执行
.builder()
    .defaultAdvisors(
        advisor1,  // 第一个执行
        advisor2,  // 第二个执行
        advisor3   // 第三个执行
    )
    .build()

// 执行流程
// Request → advisor1.before() → advisor2.before() → advisor3.before() → AI
// Response → advisor3.after() → advisor2.after() → advisor1.after() → Return

5.4 实战:RAG + 记忆 + 限流组合

// 完整实战:企业知识库 AI 助手

@Service
public class EnterpriseChatService {
    private final ChatClient chatClient;
    
    public EnterpriseChatService(
            ChatClient.Builder builder,
            VectorStore vectorStore,
            ChatMemory chatMemory,
            RateLimiter rateLimiter) {
        
        this.chatClient = builder
            .defaultSystem("""
                你是一个企业知识库助手。你可以根据以下信息回答用户的问题。
                
                规则:
                - 只使用提供的上下文信息回答
                - 如果上下文中没有相关信息,请说明"根据我的知识库,没有相关信息"
                - 回答要专业、简洁、有帮助
                """)
            .defaultAdvisors(
                // 1. 限流:防止 API 滥用
                new RateLimitAdvisor(rateLimiter),
                
                // 2. RAG:注入相关文档
                new RagAdvisor(vectorStore,
                    SearchRequest.defaults()
                        .withTopK(5)
                        .withSimilarityThreshold(0.6)
                ),
                
                // 3. 记忆:管理对话历史
                new MessageChatMemoryAdvisor(chatMemory, "session-123")
            )
            .defaultOptions(
                ChatOptionsBuilder.builder()
                    .temperature(0.3)  // 降低随机性,更准确
                    .maxTokens(2000)
                    .build()
            )
            .build();
    }
    
    public String chat(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    }
    
    // 流式版本
    public Flux<String> chatStream(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .stream()
            .content();
    }
}

// 限流器实现
@Component
public class TokenBucketRateLimiter implements RateLimiter {
    private final Map<String, TokenBucket> buckets = new ConcurrentHashMap<>();
    private final int maxTokens;
    private final Duration refillDuration;
    
    public TokenBucketRateLimiter(int maxTokens, Duration refillDuration) {
        this.maxTokens = maxTokens;
        this.refillDuration = refillDuration;
    }
    
    @Override
    public boolean acquire(String key) {
        TokenBucket bucket = buckets.computeIfAbsent(key, 
            k -> new TokenBucket(maxTokens, refillDuration));
        return bucket.tryAcquire();
    }
    
    private static class TokenBucket {
        private int availableTokens;
        private final int maxTokens;
        private final Duration refillDuration;
        private Instant nextRefill;
        
        TokenBucket(int maxTokens, Duration refillDuration) {
            this.maxTokens = maxTokens;
            this.availableTokens = maxTokens;
            this.refillDuration = refillDuration;
            this.nextRefill = Instant.now();
        }
        
        synchronized boolean tryAcquire() {
            refill();
            if (availableTokens > 0) {
                availableTokens--;
                return true;
            }
            return false;
        }
        
        private void refill() {
            if (Instant.now().isAfter(nextRefill)) {
                availableTokens = maxTokens;
                nextRefill = Instant.now().plus(refillDuration);
            }
        }
    }
}

六、MCP 协议集成

6.1 MCP 简介

// MCP (Model Context Protocol) 集成
// Spring AI 2.0 支持将 MCP Server 作为工具使用

@Configuration
public class McpConfiguration {
    
    @Bean
    public McpClientWrapper mcpChatClient(ChatClient.Builder builder) {
        // 连接 MCP Server
        McpClient mcpClient = McpClient.builder()
            .url("http://localhost:8080/mcp")
            .authentication("bearer", apiKey)
            .timeout(Duration.ofSeconds(30))
            .build();
        
        return new McpClientWrapper(mcpClient, builder);
    }
}

public class McpClientWrapper {
    private final McpClient mcpClient;
    private final ChatClient.Builder chatClientBuilder;
    
    public McpClientWrapper(McpClient mcpClient, ChatClient.Builder builder) {
        this.mcpClient = mcpClient;
        this.chatClientBuilder = builder;
    }
    
    // 获取 MCP Server 工具列表
    public List<ToolMetadata> listTools() {
        return mcpClient.listTools();
    }
    
    // 执行 MCP 工具
    public Object executeTool(String toolName, Map<String, Object> parameters) {
        return mcpClient.callTool(toolName, parameters);
    }
    
    // 创建支持 MCP 工具的 ChatClient
    public ChatClient createChatClient() {
        return chatClientBuilder
            .defaultTools(
                mcpClient.listTools().stream()
                    .map(tool -> FunctionCallback.builder()
                        .name(tool.name())
                        .description(tool.description())
                        .inputType(tool.inputSchema())
                        .handler(params -> mcpClient.callTool(tool.name(), params))
                        .build())
                    .toArray(FunctionCallback[]::new)
            )
            .build();
    }
}

// MCP Server 实现 (Spring AI 自建)
@RestController
@RequestMapping("/mcp")
public class McpServerController {
    
    @GetMapping("/tools")
    public List<ToolDefinition> listTools() {
        return List.of(
            new ToolDefinition(
                "web_search", 
                "搜索互联网",
                Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "query", Map.of("type", "string", "description", "搜索关键词"),
                        "limit", Map.of("type", "integer", "description", "结果数量")
                    ),
                    "required", List.of("query")
                )
            ),
            new ToolDefinition(
                "database_query",
                "执行数据库查询",
                Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "sql", Map.of("type", "string", "description", "SQL 语句")
                    ),
                    "required", List.of("sql")
                )
            )
        );
    }
    
    @PostMapping("/call")
    public Object callTool(@RequestBody ToolCallRequest request) {
        return switch (request.tool()) {
            case "web_search" -> webSearch(request.params());
            case "database_query" -> databaseQuery(request.params());
            default -> Map.of("error", "Unknown tool: " + request.tool());
        };
    }
    
    private Object webSearch(Map<String, Object> params) {
        // 实现搜索逻辑
        return Map.of("results", List.of());
    }
    
    private Object databaseQuery(Map<String, Object> params) {
        // 实现查询逻辑
        return Map.of("rows", List.of());
    }
}

七、与 LangChain4j 的对比选型

7.1 核心对比

// Spring AI 2.0 vs LangChain4j 对比

/*
| 维度               | Spring AI 2.0         | LangChain4j            |
|---------------------|------------------------|------------------------|
| 生态整合            | Spring 生态无缝        | 独立框架,不依赖 Spring |
| 上手难度            | 低 (Spring 开发者)     | 中 (文档详细)           |
| 模型支持            | 50+                    | 50+                    |
| Tool Calling        | 完整                   | 完整                   |
| RAG                 | 原生支持               | 原生支持                |
| MCP                 | 支持                   | 实验性                  |
| 活跃度              | 高 (VMware 支持)       | 高 (社区活跃)           |
| 生产案例            | 多                     | 多                     |
| 适用场景            | Spring Boot 项目       | 任意 Java 项目         |
*/

public class ComparisonGuide {
    
    // 选择 Spring AI 的场景
    public boolean shouldUseSpringAI() {
        return 
            // ✓ 使用 Spring Boot/Cloud
            // ✓ 需要与现有 Spring 组件集成 (Security, Data, etc.)
            // ✓ 团队熟悉 Spring 生态
            // ✓ 需要企业级支持 (VMware)
            // ✓ 项目中有其他 Spring 依赖
    }
    
    // 选择 LangChain4j 的场景
    public boolean shouldUseLangChain4j() {
        return
            // ✓ 没有使用 Spring
            // ✓ 需要更灵活的配置
            // ✓ 需要更好的调试体验
            // ✓ 需要更多 AI 能力示例
    }
    
    // 混合使用 (如果需要)
    public void hybridApproach() {
        // Spring AI 处理基础设施 (HTTP, Security)
        // LangChain4j 处理 AI 逻辑 (Chains, Agents)
        
        // 通过 ChatModel 桥接
        // LangChain4j 可以使用 Spring AI 的 ChatModel
        ChatModel springAiModel = OpenAiChatModel.builder()
            .apiKey(apiKey)
            .build();
        
        // LangChain4j 使用 Spring AI 模型
        AiServices<Assistant> aiServices = AiServices.builder(Assistant.class)
            .chatLanguageModel(springAiModel)
            .build();
    }
}

7.2 迁移指南

// LangChain4j → Spring AI 迁移示例

// LangChain4j 代码
/*
ChatLanguageModel model = OpenAiChatModel.builder()
    .apiKey("sk-xxx")
    .modelName("gpt-4")
    .temperature(0.7)
    .build();

AiServices<Assistant> aiServices = AiServices.builder(Assistant.class)
    .chatLanguageModel(model)
    .chatMessageHistoryFactory(...) // RAG 支持
    .tools(new DatabaseTool(), new WebSearchTool())
    .build();

Assistant assistant = aiServices.build();
String response = assistant.chat("Hello");
*/

// 迁移到 Spring AI 2.0
@Configuration
public class MigrationConfig {
    
    @Bean
    public ChatModel openAiChatModel(
            @Value("${spring.ai.openai.api-key}") String apiKey) {
        return OpenAiChatModel.builder()
            .apiKey(apiKey)
            .defaultOptions(
                ChatOptionsBuilder.builder()
                    .temperature(0.7)
                    .model("gpt-4")
                    .build()
            )
            .build();
    }
    
    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
            .defaultTools(new DatabaseTool(), new WebSearchTool())
            .build();
    }
}

// 使用对比
@Service
public class AssistantService {
    private final ChatClient chatClient;
    
    public AssistantService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    
    public String chat(String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }
}

八、生产环境最佳实践

8.1 配置管理

# application.yml
spring:
  ai:
    # OpenAI 配置
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com/v1
      
    # Anthropic 配置
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      
    # 向量存储配置
    vector:
      pgvector:
        host: ${DB_HOST}
        port: ${DB_PORT}
        database: ${DB_NAME}
        username: ${DB_USER}
        password: ${DB_PASSWORD}

# 配置文件
@Bean
@ConfigurationProperties(prefix = "ai")
public class AiProperties {
    private Map<String, ModelConfig> models = new HashMap<>();
    private RateLimitConfig rateLimit;
    private RetryConfig retry;
    
    public record ModelConfig(
        String provider,
        String apiKey,
        String baseUrl,
        double temperature,
        int maxTokens
    ) {}
    
    public record RateLimitConfig(int requestsPerMinute, int burstCapacity) {}
    public record RetryConfig(int maxAttempts, Duration backoff) {}
}

8.2 监控与可观测性

// AI 调用监控

@Component
public class AiMetricsService {
    
    private final MeterRegistry meterRegistry;
    
    public AiMetricsService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    // 记录 API 调用
    public void recordApiCall(String model, long durationMs, boolean success) {
        meterRegistry.timer("ai.api.call",
            Tags.of(
                "model", model,
                "success", String.valueOf(success)
            )
        ).record(Duration.ofMillis(durationMs));
    }
    
    // 记录 Token 使用
    public void recordTokenUsage(String model, int promptTokens, int completionTokens) {
        meterRegistry.counter("ai.tokens.prompt",
            Tags.of("model", model)
        ).increment(promptTokens);
        
        meterRegistry.counter("ai.tokens.completion",
            Tags.of("model", model)
        ).increment(completionTokens);
    }
}

// 全局异常处理
@RestControllerAdvice
public class AiExceptionHandler {
    
    @ExceptionHandler(RateLimitExceededException.class)
    public ResponseEntity<ErrorResponse> handleRateLimit(RateLimitExceededException ex) {
        return ResponseEntity
            .status(HttpStatus.TOO_MANY_REQUESTS)
            .body(new ErrorResponse(
                "RATE_LIMIT_EXCEEDED",
                "API 调用频率超限,请稍后重试",
                Map.of("retry_after", ex.getRetryAfterSeconds())
            ));
    }
    
    @ExceptionHandler(AiModelException.class)
    public ResponseEntity<ErrorResponse> handleModelError(AiModelException ex) {
        // 记录错误
        log.error("AI Model Error: {}", ex.getMessage(), ex);
        
        return ResponseEntity
            .status(HttpStatus.BAD_GATEWAY)
            .body(new ErrorResponse(
                "AI_MODEL_ERROR",
                "AI 服务暂时不可用,请稍后重试",
                null
            ));
    }
}

九、总结

Spring AI 2.0 的发布,标志着 Java 生态正式进入 AI 工程化时代:

  1. 统一 ChatClient API: 所有模型厂商一个写法,切换成本为零
  2. 结构化输出: AI 返回自动映射到 Java 强类型,类型安全
  3. Tool Calling: AI 调用 Java 方法,参数自动验证
  4. Advisors 机制: RAG、记忆、日志、限流可组合,类似 AOP
  5. MCP 协议: 与 AI Agent 工具生态深度集成
  6. 生产级支持: 监控、限流、重试、容错全支持

对于 Spring 开发者来说,Spring AI 2.0 提供了最低学习曲线的 AI 集成方案——不需要切换语言,不需要学习新框架,只需要在现有的 Spring Boot 项目中添加依赖,就能获得完整的 AI 能力。

对于 Java 生态来说,Spring AI 2.0 补齐了最后一块短板——终于可以与 Python 的 LangChain 在 AI 工程化领域正面竞争了。


参考资料

推荐文章

PHP 如何输出带微秒的时间
2024-11-18 01:58:41 +0800 CST
mysql 计算附近的人
2024-11-18 13:51:11 +0800 CST
Vue3中如何处理SEO优化?
2024-11-17 08:01:47 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
404错误页面的HTML代码
2024-11-19 06:55:51 +0800 CST
JavaScript 实现访问本地文件夹
2024-11-18 23:12:47 +0800 CST
Golang 中你应该知道的 Range 知识
2024-11-19 04:01:21 +0800 CST
程序员茄子在线接单