基于 Spring AI 构建智能餐厅推荐系统:多模型集成的实践指南

本文将教您如何使用 Spring AI 项目构建基于不同聊天模型的应用程序。Spring AI 聊天模型是一个简单易用的接口,允许我们与这些模型进行交互。我们的 Spring Boot 示例应用程序将在 OpenAI、Mistral AI 和 Ollama 提供的三种流行聊天模型之间切换,并展示如何使用 Spring AI 框架实现多轮对话、结构化输出等核心功能。

源代码

如果您想亲自尝试,可以随时查看我的源代码。为此,您必须克隆我的示例 GitHub 仓库。然后,您只需按照我的说明进行操作即可。

项目目标

使用 Spring AI 构建一个智能餐厅推荐系统,实现以下核心功能:

  • 智能推荐:根据用户偏好推荐合适的餐厅
  • 多轮对话:支持上下文对话,提供连贯的用户体验
  • 结构化输出:返回标准化的 JSON 数据格式
  • 多模型支持:支持多个 AI 模型提供商的无缝切换
  • 中文支持:完美支持中文参数和响应
  • 生产就绪:包含完整的配置管理和错误处理

环境搭建

技术栈

  • Java 21:使用最新的 LTS 版本
  • Spring Boot 3.5.6:提供企业级应用框架
  • Spring AI 1.1.0-M2:AI 集成框架
  • Maven:依赖管理和构建工具
  • Lombok:简化 Java 代码

依赖配置

Spring AI 项目仍在积极开发中,当前使用里程碑版本 1.1.0-M2

<repositories>
    <repository>
        <id>central</id>
        <name>Central</name>
        <url>https://repo1.maven.org/maven2/</url>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

然后,我们应该包含指定版本的 Spring AI 项目的 Maven BOM。

<properties>
    <java.version>21</java.version>
    <spring-ai.version>1.1.0-M2</spring-ai.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

由于我们的示例应用暴露了一些 REST 端点,我们应该包含 Spring Boot Web Starter。我们可以包含 Spring Boot Test Starter 来创建一些 JUnit 测试。Spring AI 模块包含在 Maven profiles 部分中。每个聊天模型提供商都有三个不同的配置文件。默认情况下,我们的应用使用 OpenAI,因此它激活了 open-ai 配置文件,该文件包含 spring-ai-starter-model-openai 库。我们应该激活 mistral-ai 配置文件来切换到 Mistral AI。第三个选项是 ollama-ai 配置文件,包含 spring-ai-starter-model-ollama 依赖。这将使在不同聊天模型 AI 提供商之间切换变得轻而易举——我们只需要在 Maven 运行命令中设置配置文件参数。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

<profiles>
    <profile>
        <id>open-ai</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-starter-model-openai</artifactId>
            </dependency>
        </dependencies>
    </profile>
    <profile>
        <id>mistral-ai</id>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-starter-model-mistral-ai</artifactId>
            </dependency>
        </dependencies>
    </profile>
    <profile>
        <id>ollama-ai</id>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-starter-model-ollama</artifactId>
            </dependency>
        </dependencies>
    </profile>
</profiles>

项目配置

环境配置

在开始开发之前,我们需要配置 AI 模型提供商的 API 密钥。

配置 OpenAI

我们必须在 OpenAI 平台门户上拥有一个账户。登录后,访问 API 密钥 页面来生成 API 令牌。

export OPENAI_API_KEY=<YOUR_TOKEN_VALUE>

配置 Mistral AI

Mistral AI 平台门户上创建账户,访问 API 密钥 页面生成令牌。

export MISTRAL_API_KEY=<YOUR_TOKEN_VALUE>

运行和配置 Ollama

Ollama 允许在本地运行大型语言模型。从 下载页面 下载并安装 Ollama,然后运行模型:

ollama run llama3.2

配置兼容 OpenAI 的模型

  1. Groq AI
  1. Docker Model Runner (DMR)
  1. OpenRouter AI
  1. DeepSeek AI
  1. Qwen AI

Spring Boot 配置

1. 通用配置 (application.yml)

spring.ai.openai.api-key: ${OPENAI_API_KEY}
spring.ai.openai.chat.options.model: gpt-5
spring.ai.openai.chat.options.temperature: 1

spring.ai.mistralai.api-key: ${MISTRAL_API_KEY}
spring.ai.ollama.chat.options.model: llama3.2

2. OpenAI 兼容模型配置

Groq AI 配置 (application-groq.yml)

spring.ai.openai.api-key: ${GROQ_API_KEY}
spring.ai.openai.base-url: https://api.groq.com
spring.ai.openai.chat.options.model: llama-3.1-70b-versatile

DeepSeek AI 配置 (application-deepseek.yml)

spring.ai.openai.api-key: ${DEEPSEEK_API_KEY}
spring.ai.openai.base-url: https://api.deepseek.com
spring.ai.openai.chat.options.model: deepseek-chat

OpenRouter AI 配置 (application-openrouter.yml)

spring.ai.openai.api-key: ${OPENROUTER_API_KEY}
spring.ai.openai.base-url: https://openrouter.ai
spring.ai.openai.chat.completions-path: /api/v1/chat/completions
spring.ai.openai.chat.options.model: qwen/qwen3-coder:free

Qwen AI 配置 (application-qwen.yml)

spring.ai.openai.api-key: ${QWEN_API_KEY}
spring.ai.openai.base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
spring.ai.openai.chat.options.model: qwen-plus

Docker Model Runner 配置 (application-dmr.yml)

spring.ai.openai.base-url: http://localhost:12434/engines/
spring.ai.openai.api-key: dummy-key
spring.ai.openai.chat.options.model: llama3.2

3. 环境变量设置

# 选择其中一个模型设置对应的环境变量
export OPENAI_API_KEY=your-openai-api-key
export MISTRAL_API_KEY=your-mistral-api-key
export GROQ_API_KEY=your-groq-api-key
export DEEPSEEK_API_KEY=your-deepseek-api-key
export OPENROUTER_API_KEY=your-openrouter-api-key
export QWEN_API_KEY=your-qwen-api-key

核心功能实现

1. 数据模型设计

首先定义我们的核心数据模型,使用 Lombok 简化代码:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Restaurant {
    private Long id;
    private String name;
    private String cuisine;
    private String location;
    private Double rating;
    private String description;
    private String priceRange;
    private String[] features;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dish {
    private Long id;
    private String name;
    private String description;
    private String cuisine;
    private Double price;
    private String category;
    private List<String> ingredients;
    private String dietaryInfo;
    private Integer calories;
    private String preparationTime;
    private String difficulty;
}

2. REST API 设计

设计 RESTful API 接口,支持中文参数和 JSON 数据交换:

@PostMapping("/dishes/generate")
public ResponseEntity<List<Dish>> generateDishes(@RequestBody Map<String, Object> request) {
    String cuisine = (String) request.get("cuisine");
    Integer countObj = (Integer) request.get("count");
    int count = countObj != null ? countObj : 5;

    // 限制菜品数量在合理范围内
    int validCount = Math.max(1, Math.min(count, 10));

    PromptTemplate template = new PromptTemplate("""
            为{cuisine}菜系生成{count}道特色菜品,包含以下信息:
            - 菜品名称
            - 详细描述
            - 主要食材
            - 价格范围
            - 菜品分类(开胃菜/主菜/甜点等)
            - 饮食信息(素食/无麸质/低卡等)
            - 卡路里
            - 制作时间
            - 难度等级
            
            请返回JSON格式的菜品数据,不要包含解释性文字。
            """);

    Prompt prompt = template.create(Map.of(
            "cuisine", cuisine,
            "count", validCount
    ));

    List<Dish> dishes = chatClient.prompt(prompt)
            .call()
            .entity(new ParameterizedTypeReference<List<Dish>>() {
            });

    return ResponseEntity.ok(dishes);
}

@RestController 类注入自动配置的 ChatClient.Builder 来创建 ChatClient 实例。RestaurantRecommendationController 实现了从 POST /api/restaurants/recommend 端点返回餐厅列表的方法。主要目标是生成一个包含 5 个对象的列表,这些对象具有在 Restaurant 类中定义的字段。

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/restaurants")
public class RestaurantRecommendationController {

    private final ChatClient chatClient;

    @PostMapping("/recommend")
    public ResponseEntity<List<Restaurant>> recommendRestaurants(@RequestBody RecommendationRequest request) {
        PromptTemplate template = new PromptTemplate("""
                根据以下用户偏好推荐5家合适的餐厅:
                位置: {location}
                菜系: {cuisine}
                价格范围: {priceRange}
                饮食限制: {dietaryRestrictions}
                场合: {occasion}
                人数: {groupSize}
                用餐时间: {timeOfDay}
                其他偏好: {preferences}
                
                请返回餐厅列表,包含餐厅名称、菜系、位置、评分、描述、价格范围和特色。
                不要包含任何解释性文字,只返回JSON格式的餐厅数据。
                """);

        Prompt prompt = template.create(Map.of(
                "location", request.getLocation() != null ? request.getLocation() : "北京市",
                "cuisine", request.getCuisine() != null ? request.getCuisine() : "不限",
                "priceRange", request.getPriceRange() != null ? request.getPriceRange() : "不限",
                "dietaryRestrictions", request.getDietaryRestrictions() != null ?
                        String.join(", ", request.getDietaryRestrictions()) : "无",
                "occasion", request.getOccasion() != null ? request.getOccasion() : "日常用餐",
                "groupSize", request.getGroupSize() != null ? request.getGroupSize().toString() : "1-2人",
                "timeOfDay", request.getTimeOfDay() != null ? request.getTimeOfDay() : "午餐",
                "preferences", request.getPreferences() != null ?
                        String.join(", ", request.getPreferences()) : "无"
        ));

        List<Restaurant> restaurants = chatClient.prompt(prompt)
                .call()
                .entity(new ParameterizedTypeReference<List<Restaurant>>() {});

        return ResponseEntity.ok(restaurants);
    }
}

结果并不令人惊讶——系统会返回符合用户偏好的餐厅列表。然而,第二次调用同一个端点时,列表会略有不同。根据我们的提示,聊天客户端应该"返回当前餐厅列表"。所以,我期望得到与之前相同的列表。在这种情况下,问题是聊天客户端不记得之前的对话。

运行应用

# 使用 OpenAI GPT-4o(推荐)
mvn spring-boot:run

# 使用 OpenAI(默认配置)
mvn spring-boot:run

# 使用 Mistral AI
mvn spring-boot:run -Pmistral-ai

# 使用 Ollama
mvn spring-boot:run -Pollama-ai

# 使用 Groq AI
mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=groq"

# 使用 DeepSeek AI
mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=deepseek"

# 使用 Qwen AI
mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=qwen"

# 使用 OpenRouter AI
mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=openrouter"

# 使用 DMR AI
mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=dmr"

测试 API

# 推荐餐厅
curl -X POST http://localhost:8080/api/restaurants/recommend \
  -H "Content-Type: application/json" \
  -d '{
    "location": "北京市",
    "cuisine": "中餐",
    "priceRange": "中等价位",
    "dietaryRestrictions": ["素食"],
    "occasion": "商务聚餐",
    "groupSize": 4,
    "timeOfDay": "晚餐"
  }'

# 生成菜品
curl -X POST "http://localhost:8080/api/restaurants/dishes/generate" \
  -H "Content-Type: application/json" \
  -d '{"cuisine": "中餐", "count": 3}'

# 获取建议
curl -X POST "http://localhost:8080/api/restaurants/advice" \
  -H "Content-Type: application/json" \
  -d '{"query": "适合情侣约会的餐厅"}'

# 多语言聊天
curl -X POST "http://localhost:8080/api/restaurants/chat" \
  -H "Content-Type: application/json" \
  -d '{"message": "推荐一家好吃的川菜馆", "language": "zh"}'

核心技术特性

1. 多轮对话支持

多轮对话是智能应用的核心特性,Spring AI 提供了开箱即用的解决方案:

@Configuration
public class ChatConfig {

    /**
     * 配置聊天记忆
     */
    @Bean
    public ChatMemory chatMemory() {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(new InMemoryChatMemoryRepository())
                .build();
    }

    /**
     * 配置 ChatClient
     * 包含记忆功能和日志记录
     */
    @Bean
    public ChatClient chatClient(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {
        return chatClientBuilder
                .defaultSystem("你是一个专业的餐厅推荐助手。请用中文回答,提供准确、有用的餐厅和菜品推荐。")
                .defaultAdvisors(
                        MessageChatMemoryAdvisor.builder(chatMemory).build(),
                        new SimpleLoggerAdvisor())
                .build();
    }
}
@SpringBootApplication
public class RestaurantApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestaurantApplication.class, args);
    }
}

现在,让我们进行最终测试。应用重启后,我们可以调用生成餐厅列表的端点。然后,我们将调用 GET /api/restaurants/{id}/details 端点来仅显示具有指定 ID 的单个餐厅。最后,我们可以重复调用 POST /api/restaurants/recommend 端点来验证它是否返回相同的列表。

多轮对话测试

让我们测试多轮对话功能:

# 第一轮对话:询问川菜馆推荐
curl -X POST http://localhost:8080/api/restaurants/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "我想在北京找一家川菜馆", "language": "zh"}'

# 第二轮对话:询问价格(AI 能记住之前的推荐)
curl -X POST http://localhost:8080/api/restaurants/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "刚才推荐的餐厅中,哪家最便宜?", "language": "zh"}'

测试结果:AI 能够记住之前的对话内容,提供连贯的回复,准确回答关于之前推荐餐厅的问题。

多轮对话实现原理

  1. 自动记忆管理MessageChatMemoryAdvisor 自动管理对话历史
  2. 内存存储:使用 MessageWindowChatMemory + InMemoryChatMemoryRepository
  3. 透明集成:通过 ChatClientdefaultAdvisors 配置
  4. 上下文保持:每次对话都会包含完整的对话历史

2. 结构化输出支持

Spring AI 提供了多种结构化输出方式,满足不同的开发需求:

1. 直接 .entity() 方法(最简单)

@PostMapping("/restaurant/direct-entity")
public ResponseEntity<Restaurant> getRestaurantDirectEntity(@RequestBody Map<String, Object> request) {
    String cuisine = (String) request.get("cuisine");
    
    PromptTemplate template = new PromptTemplate("""
            推荐1家{cuisine}餐厅,返回JSON格式的餐厅信息。
            包含:name, cuisine, location, rating, description, priceRange, features
            """);
    
    Prompt prompt = template.create(Map.of("cuisine", cuisine));
    
    // 直接使用 .entity() 方法
    Restaurant restaurant = chatClient.prompt(prompt)
            .call()
            .entity(Restaurant.class);
    
    return ResponseEntity.ok(restaurant);
}

2. ParameterizedTypeReference(当前项目使用)

@PostMapping("/restaurants/type-ref")
public ResponseEntity<List<Restaurant>> getRestaurantsWithTypeRef(@RequestBody Map<String, Object> request) {
    String cuisine = (String) request.get("cuisine");
    
    PromptTemplate template = new PromptTemplate("""
            推荐3家{cuisine}餐厅,返回JSON格式的餐厅列表。
            每个餐厅包含:name, cuisine, location, rating, description, priceRange, features
            """);
    
    Prompt prompt = template.create(Map.of("cuisine", cuisine));
    
    // 使用 ParameterizedTypeReference
    List<Restaurant> restaurants = chatClient.prompt(prompt)
            .call()
            .entity(new ParameterizedTypeReference<List<Restaurant>>() {});
    
    return ResponseEntity.ok(restaurants);
}

3. BeanOutputConverter

@PostMapping("/restaurant/bean-converter")
public ResponseEntity<Restaurant> getRestaurantWithBeanConverter(@RequestBody Map<String, Object> request) {
    String cuisine = (String) request.get("cuisine");
    
    PromptTemplate template = new PromptTemplate("""
            推荐1家{cuisine}餐厅,返回JSON格式的餐厅信息。
            包含:name, cuisine, location, rating, description, priceRange, features
            """);
    
    Prompt prompt = template.create(Map.of("cuisine", cuisine));
    
    // 使用 BeanOutputConverter
    BeanOutputConverter<Restaurant> converter = new BeanOutputConverter<>(Restaurant.class);
    
    String response = chatClient.prompt(prompt)
            .call()
            .content();
    
    Restaurant restaurant = converter.convert(response);
    
    return ResponseEntity.ok(restaurant);
}

4. MapOutputConverter

@PostMapping("/restaurant/map-converter")
public ResponseEntity<Map<String, Object>> getRestaurantWithMapConverter(@RequestBody Map<String, Object> request) {
    String cuisine = (String) request.get("cuisine");
    
    PromptTemplate template = new PromptTemplate("""
            推荐1家{cuisine}餐厅,返回JSON格式的餐厅信息。
            包含:name, cuisine, location, rating, description, priceRange, features
            """);
    
    Prompt prompt = template.create(Map.of("cuisine", cuisine));
    
    // 使用 MapOutputConverter
    MapOutputConverter converter = new MapOutputConverter();
    
    String response = chatClient.prompt(prompt)
            .call()
            .content();
    
    @SuppressWarnings("unchecked")
    Map<String, Object> restaurant = (Map<String, Object>) converter.convert(response);
    
    return ResponseEntity.ok(restaurant);
}

5. ListOutputConverter

@PostMapping("/restaurants/list-converter")
public ResponseEntity<List<Map<String, String>>> getRestaurantsWithListConverter(@RequestBody Map<String, Object> request) {
    String cuisine = (String) request.get("cuisine");
    
    PromptTemplate template = new PromptTemplate("""
            推荐3家{cuisine}餐厅,返回JSON格式的餐厅列表。
            每个餐厅包含:name, cuisine, location, rating, description, priceRange, features
            """);
    
    Prompt prompt = template.create(Map.of("cuisine", cuisine));
    
    // 使用 ListOutputConverter
    ListOutputConverter converter = new ListOutputConverter();
    
    String response = chatClient.prompt(prompt)
            .call()
            .content();
    
    @SuppressWarnings("unchecked")
    List<String> restaurantStrings = (List<String>) converter.convert(response);
    
    // 将字符串列表转换为 Map 列表(这里简化处理)
    List<Map<String, String>> restaurants = restaurantStrings.stream()
            .map(str -> Map.of("name", str, "cuisine", cuisine))
            .toList();
    
    return ResponseEntity.ok(restaurants);
}

结构化输出方式对比

输出方式代码简洁度类型安全灵活性推荐场景
.entity()⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐简单对象
ParameterizedTypeReference⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐复杂泛型
BeanOutputConverter⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐自定义转换
MapOutputConverter⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐动态结构
ListOutputConverter⭐⭐⭐⭐⭐⭐⭐⭐⭐简单列表

结构化输出测试

# 测试直接 .entity() 方法
curl -X POST http://localhost:8080/api/structured/restaurant/direct-entity \
  -H "Content-Type: application/json" \
  -d '{"cuisine": "川菜"}'

# 测试 ParameterizedTypeReference
curl -X POST http://localhost:8080/api/structured/restaurants/type-ref \
  -H "Content-Type: application/json" \
  -d '{"cuisine": "川菜"}'

# 测试 BeanOutputConverter
curl -X POST http://localhost:8080/api/structured/restaurant/bean-converter \
  -H "Content-Type: application/json" \
  -d '{"cuisine": "川菜"}'

测试结果:所有结构化输出方式都能正确工作,返回符合预期格式的数据。

功能扩展

基于对当前项目的分析,以下是本项目未涉及但 Spring AI 框架支持的强大功能:

🚀 核心功能扩展

1. 流式响应 (Streaming)

// 当前项目:同步调用
List<Restaurant> restaurants = chatClient.prompt()
    .user("推荐餐厅")
    .call()
    .entity(new ParameterizedTypeReference<List<Restaurant>>() {});

// 未实现:流式响应
Flux<String> stream = chatClient.prompt()
    .user("推荐餐厅")
    .stream()
    .content();

2. 函数调用 (Function Calling)

// 未实现:工具调用
@Bean
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
    return request -> {
        // 调用天气 API
        return weatherService.getWeather(request.getLocation());
    };
}

// 在 ChatClient 中使用
chatClient.prompt()
    .user("北京今天天气如何?")
    .functions("weatherFunction")
    .call();

3. 向量数据库集成 (RAG)

// 未实现:检索增强生成
@Bean
public VectorStore vectorStore() {
    return new PineconeVectorStore(pineconeApiKey, "restaurant-index");
}

// RAG 查询
chatClient.prompt()
    .user("推荐适合商务聚餐的餐厅")
    .advisors(RetrievalAugmentationAdvisor.builder(vectorStore).build())
    .call();

🎨 多模态支持

4. 图像识别和处理

// 未实现:图像输入
@PostMapping("/analyze-image")
public ResponseEntity<String> analyzeImage(@RequestParam MultipartFile image) {
    return chatClient.prompt()
        .user("分析这张餐厅图片")
        .media(image)
        .call()
        .content();
}

5. 语音转文字和文字转语音

// 未实现:语音处理
@PostMapping("/voice-chat")
public ResponseEntity<byte[]> voiceChat(@RequestParam MultipartFile audio) {
    // 语音转文字
    String text = speechToTextService.transcribe(audio);
    
    // AI 处理
    String response = chatClient.prompt()
        .user(text)
        .call()
        .content();
    
    // 文字转语音
    return textToSpeechService.synthesize(response);
}

⚡ 性能优化功能

6. 异步处理

// 未实现:异步调用
@Async
public CompletableFuture<List<Restaurant>> recommendRestaurantsAsync(RecommendationRequest request) {
    return chatClient.prompt()
        .user("推荐餐厅")
        .call()
        .entity(new ParameterizedTypeReference<List<Restaurant>>() {})
        .toFuture();
}

7. 缓存机制

// 未实现:响应缓存
@Cacheable("restaurant-recommendations")
public List<Restaurant> getCachedRecommendations(RecommendationRequest request) {
    return chatClient.prompt()
        .user("推荐餐厅")
        .call()
        .entity(new ParameterizedTypeReference<List<Restaurant>>() {});
}

8. 批量处理

// 未实现:批量处理
public List<List<Restaurant>> batchRecommend(List<RecommendationRequest> requests) {
    return chatClient.batch()
        .prompt("推荐餐厅")
        .call()
        .entity(new ParameterizedTypeReference<List<Restaurant>>() {});
}

🔧 高级管理功能

9. 模型路由和负载均衡

// 未实现:智能路由
@Bean
public ModelRouter modelRouter() {
    return ModelRouter.builder()
        .addRoute("restaurant", "gpt-4o")
        .addRoute("dish", "claude-3")
        .addRoute("chat", "llama-3")
        .build();
}

10. 提示词模板管理

// 未实现:复杂模板
@Bean
public PromptTemplate restaurantTemplate() {
    return new PromptTemplate("""
        你是一个专业的餐厅推荐助手。
        用户偏好:{preferences}
        位置:{location}
        预算:{budget}
        
        {% if occasion == "商务聚餐" %}
        请推荐环境优雅、服务专业的餐厅。
        {% elif occasion == "朋友聚会" %}
        请推荐氛围轻松、菜品丰富的餐厅。
        {% endif %}
        """);
}

🏠 企业级功能

11. 多租户支持

// 未实现:多租户隔离
@Bean
public TenantAwareChatClient tenantAwareChatClient() {
    return TenantAwareChatClient.builder()
        .tenantResolver(tenantResolver)
        .modelProvider(tenantModelProvider)
        .build();
}

12. 安全性和内容过滤

// 未实现:内容安全
@Bean
public ContentFilterAdvisor contentFilterAdvisor() {
    return ContentFilterAdvisor.builder()
        .contentFilter(contentFilter)
        .build();
}

13. 监控和指标

// 未实现:性能监控
@Bean
public MetricsAdvisor metricsAdvisor() {
    return MetricsAdvisor.builder()
        .meterRegistry(meterRegistry)
        .build();
}

💰 成本优化功能

14. 成本控制

// 未实现:成本控制
@Bean
public CostOptimizationAdvisor costOptimizationAdvisor() {
    return CostOptimizationAdvisor.builder()
        .maxCostPerRequest(0.01)
        .fallbackModel("gpt-3.5-turbo")
        .build();
}

15. 模型热更新

// 未实现:动态切换
@PostMapping("/switch-model")
public ResponseEntity<String> switchModel(@RequestParam String modelName) {
    modelManager.switchModel(modelName);
    return ResponseEntity.ok("模型已切换");
}

总结

Spring AI 作为 Spring 生态系统的新成员,为 Java 开发者提供了构建智能化应用的强大工具。随着 AI 技术的不断发展,我们可以期待:

  • 更多模型支持:集成更多 AI 模型提供商
  • 功能增强:更丰富的 AI 功能特性
  • 开发工具:更完善的开发工具和调试支持
  • 企业级功能:更强大的企业级特性和监控支持

参考资源


本文基于 Spring AI 1.1.0-M2 版本编写,随着框架的不断发展,部分 API 可能会有变化。建议参考官方文档获取最新信息。