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 工程化时代:
- 统一 ChatClient API: 所有模型厂商一个写法,切换成本为零
- 结构化输出: AI 返回自动映射到 Java 强类型,类型安全
- Tool Calling: AI 调用 Java 方法,参数自动验证
- Advisors 机制: RAG、记忆、日志、限流可组合,类似 AOP
- MCP 协议: 与 AI Agent 工具生态深度集成
- 生产级支持: 监控、限流、重试、容错全支持
对于 Spring 开发者来说,Spring AI 2.0 提供了最低学习曲线的 AI 集成方案——不需要切换语言,不需要学习新框架,只需要在现有的 Spring Boot 项目中添加依赖,就能获得完整的 AI 能力。
对于 Java 生态来说,Spring AI 2.0 补齐了最后一块短板——终于可以与 Python 的 LangChain 在 AI 工程化领域正面竞争了。
参考资料: