【Spring AI 实战】四、OpenAI / Anthropic / Azure——多模型适配与自动配置原理

【Spring AI 实战】四、OpenAI / Anthropic / Azure------多模型适配与自动配置原理

大家好,我是冰点,今天我们继续聊SpringAI的基本用法和特性

所属阶段:第一阶段·核心基础

前置知识:建议先完成第一至三篇,理解 ChatClient、Prompt 和 Advisor 的基础能力。

适用版本 :本文重点讲多模型适配思路;若使用 Spring AI 1.0 正式版,依赖写法优先使用各模型对应的 *-spring-boot-starter

本文导航

  • [1. Spring AI 自动配置机制](#1. Spring AI 自动配置机制)
  • [2. OpenAI 适配器详解](#2. OpenAI 适配器详解)
  • [3. Anthropic Claude 适配器](#3. Anthropic Claude 适配器)
  • [4. Azure OpenAI 适配器](#4. Azure OpenAI 适配器)
  • [5. 多模型切换与策略模式](#5. 多模型切换与策略模式)
  • [6. 生产级实践:A/B 测试与模型热切换](#6. 生产级实践:A/B 测试与模型热切换)
  • [7. 安全配置:API Key 与密钥管理](#7. 安全配置:API Key 与密钥管理)
  • [8. 完整配置示例](#8. 完整配置示例)
  • [9. 小结](#9. 小结)

1. Spring AI 自动配置机制

1.1 自动装配原理

Spring AI 的自动配置依赖于 Spring Boot 的 @ConditionalOnClass@ConditionalOnProperty 机制。只需要引入对应的模型依赖,Spring AI 就会自动创建相应的 Bean

java 复制代码
// spring-ai-openai-autoconfigure 中的核心自动配置类
@Configuration
@ConditionalOnClass(OpenAiApi.class)           // 类路径中存在 OpenAiApi 才生效
@ConditionalOnProperty(                      // 配置文件中 spring.ai.openai.api-key 存在才生效
    prefix = "spring.ai.openai",
    name = "api-key"
)
@EnableConfigurationProperties(OpenAiProperties.class)
public class OpenAiAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean                  // 没有手动定义时才自动创建
    public OpenAiChatModel openAiChatModel(
            OpenAiApi openAiApi,
            OpenAiProperties properties) {
        return new OpenAiChatModel(openAiApi, properties.getChat().getOptions());
    }

    @Bean
    @ConditionalOnMissingBean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder.build();
    }
}

这意味着:只需要在 pom.xml 中加一个依赖,在 application.yml 中写几行配置,就能直接注入 ChatClient 使用。

1.2 配置属性绑定

所有模型配置都绑定到一个 Properties 类:

java 复制代码
// OpenAI 配置属性
@ConfigurationProperties(prefix = "spring.ai.openai")
public class OpenAiProperties {
    private String apiKey;
    private String baseUrl = "https://api.openai.com";
    private String orgId;
    private Chat chat = new Chat();

    public static class Chat {
        private String model = "gpt-4o-mini";
        private Double temperature = 0.7;
        private Integer maxTokens = 2048;
        // ... 更多配置
    }
}

1.3 配置优先级

优先级 来源 说明
1 代码级 options() ChatOptionsBuilder 在调用时指定(最高)
2 @Bean 级默认值 ChatClient.Builder.defaultOptions()
3 application.yml 配置文件中的 spring.ai.*
4 硬编码默认值 Properties 类中的字段默认值
java 复制代码
// 优先级 1:每次调用时指定(最高优先级)
chatClient.prompt()
    .user("xxx")
    .options(ChatOptionsBuilder.builder()
        .withModel("gpt-4o")
        .withTemperature(0.0)
        .build())
    .call();

// 优先级 2:Bean 定义时设置默认值
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
    return builder
        .defaultOptions(
            ChatOptionsBuilder.builder()
                .withModel("gpt-4o-mini")
                .withTemperature(0.7)
                .build()
        )
        .build();
}

2. OpenAI 适配器详解

2.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

2.2 完整配置

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com
      org-id: ${OPENAI_ORG_ID}
      chat:
        options:
          model: gpt-4o-mini
          temperature: 0.7
          max-tokens: 2000
          top-p: 1.0
          frequency-penalty: 0.0
          presence-penalty: 0.0

2.3 支持的模型

模型 特点 适用场景 价格
gpt-4o 最强能力,多模态 复杂推理、代码生成
gpt-4o-mini 性价比最优 日常对话、简单任务
gpt-4-turbo 大上下文窗口 长文档分析
gpt-3.5-turbo 低成本 简单问答

2.4 OpenAI 原生 JSON Schema 约束

java 复制代码
// 使用 JSON Schema 约束输出格式(OpenAI 原生支持)
public MovieReview review(String movie) {
    OpenAiChatOptions options = OpenAiChatOptions.builder()
        .withModel("gpt-4o-mini")
        .withResponseFormat(Map.of(
            "type", "json_object",
            "schema", Map.of(
                "type", "object",
                "properties", Map.of(
                    "score", Map.of("type", "integer", "description", "评分1-10"),
                    "pros", Map.of("type", "array", "items", Map.of("type", "string")),
                    "cons", Map.of("type", "array", "items", Map.of("type", "string")),
                    "summary", Map.of("type", "string")
                ),
                "required", List.of("score", "pros", "cons", "summary")
            )
        ))
        .build();

    String result = chatModel.call(
        new Prompt(new UserMessage("用 JSON 格式评价电影《" + movie + "》"), options)
    ).getResult().getOutput().getContent();

    return new ObjectMapper().readValue(result, MovieReview.class);
}

3. Anthropic Claude 适配器

3.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
</dependency>

3.2 完整配置

yaml 复制代码
spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      base-url: https://api.anthropic.com
      chat:
        options:
          model: claude-3-5-sonnet-20241022
          max-tokens: 4096
          temperature: 0.7

3.3 Claude Extended Thinking 思考模式

Claude 的独特优势在于 Extended Thinking(思考模式):

java 复制代码
// Claude 思考模式:模型先内部推理,再给出最终答案
public String askWithThinking(String question) {
    AnthropicChatOptions options = AnthropicChatOptions.builder()
        .withModel("claude-3-5-sonnet-20241022")
        .withMaxTokens(4096)
        .withTemperature(0.7)
        .withThinking(
            AnthropicChatOptions.Thinking.builder()
                .withEnabled(true)
                .withBudgetTokens(2048)
                .build()
        )
        .build();

    ChatResponse response = chatClient.prompt()
        .user(question)
        .options(options)
        .call()
        .chatResponse();

    // 获取最终答案
    String answer = response.getResult().getOutput().getContent();

    // 如果开启了思考模式,可以获取思考内容
    var metadata = response.getMetadata();
    if (metadata.containsKey("thinking")) {
        log.info("Claude 思考过程: {}", metadata.get("thinking"));
    }

    return answer;
}

3.4 Claude vs OpenAI 选型建议

维度 OpenAI GPT-4o Claude 3.5 Sonnet
编程能力 最强 略弱
长文本理解 最强(200K context)
指令遵循 最强
创意写作 最强
成本 较高 较低
思考模式 无原生支持 Extended Thinking
数据安全 注意合规 更适合合规场景

4. Azure OpenAI 适配器

4.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
</dependency>

4.2 完整配置

yaml 复制代码
spring:
  ai:
    azure:
      openai:
        api-key: ${AZURE_OPENAI_API_KEY}
        endpoint: ${AZURE_OPENAI_ENDPOINT}   # 形如 https://xxx.openai.azure.com
        chat:
          deployment-name: gpt-4o-mini       # Azure 部署名称(不是模型名)
          options:
            api-version: 2024-06-01
            temperature: 0.7
            max-tokens: 2000

4.3 Azure vs OpenAI 关键差异

差异点 OpenAI Azure OpenAI
认证方式 API Key API Key + Endpoint
模型指定 直接写模型名 写部署名称
网络要求 直连境外 可走内网/合规
数据用途 受 OpenAI 政策 企业数据不回传
API 版本 自动 需指定 api-version
java 复制代码
// Azure 的 endpoint 必须包含 /openai/deployments/ 路径
String azureEndpoint = "https://my-resource.openai.azure.com/openai/deployments/";

AzureOpenAiChatOptions options = AzureOpenAiChatOptions.builder()
    .withDeploymentName("gpt-4o-mini")    // Azure 部署名称
    .withApiVersion("2024-06-01")          // API 版本
    .withTemperature(0.7)
    .build();

5. 多模型切换与策略模式

5.1 多模型注入

在同一个应用中,可以同时注入多个模型:

java 复制代码
@Configuration
public class MultiModelConfig {

    @Bean
    @Primary
    public ChatModel openAiModel(
            @Autowired(required = false) OpenAiChatModel m) { return m; }

    @Bean
    public ChatModel anthropicModel(
            @Autowired(required = false) AnthropicChatModel m) { return m; }

    @Bean
    public ChatModel azureModel(
            @Autowired(required = false) AzureOpenAiChatModel m) { return m; }
}

5.2 模型路由服务

java 复制代码
@Service
@RequiredArgsConstructor
public class ModelRouter {

    private final Map<String, ChatModel> chatModels;

    /**
     * 按名称路由到指定模型
     */
    public String routeTo(String modelName, String prompt) {
        ChatModel model = chatModels.get(modelName.toLowerCase());
        if (model == null) {
            throw new IllegalArgumentException("不支持的模型: " + modelName);
        }
        return model.call(new Prompt(new UserMessage(prompt)))
                .getResult().getOutput().getContent();
    }

    /**
     * 根据任务类型自动选择模型
     */
    public String route(TaskType type, String prompt) {
        String model = switch (type) {
            case CODE_GENERATION -> "openaichatmodel";
            case LONG_TEXT_SUMMARY -> "anthropicchatmodel";
            case SIMPLE_QA -> "azureopenaichatmodel";
        };
        return routeTo(model, prompt);
    }

    public enum TaskType {
        CODE_GENERATION,
        LONG_TEXT_SUMMARY,
        SIMPLE_QA
    }
}

5.3 统一业务接口(策略模式)

java 复制代码
public interface AIService {
    String chat(String prompt);
}

@Service
@Primary
@RequiredArgsConstructor
class OpenAiServiceImpl implements AIService {
    private final OpenAiChatModel model;
    public String chat(String prompt) {
        return model.call(new Prompt(new UserMessage(prompt)))
                .getResult().getOutput().getContent();
    }
}

@Service
@RequiredArgsConstructor
class ClaudeServiceImpl implements AIService {
    private final AnthropicChatModel model;
    public String chat(String prompt) {
        return model.call(new Prompt(new UserMessage(prompt)))
                .getResult().getOutput().getContent();
    }
}

// 业务层注入 @Primary 的实现
@Service
@RequiredArgsConstructor
class BusinessService {
    private final AIService aiService;  // 自动注入默认实现
}

6. 生产级实践:A/B 测试与模型热切换

6.1 权重路由(Weighted Routing)

java 复制代码
@Service
public class WeightedModelRouter {

    private final Map<String, ChatModel> models;
    private final Random random = new Random();

    // 各模型权重(和为 100)
    private final Map<String, Integer> weights = Map.of(
        "openaichatmodel", 60,    // 60% GPT-4o
        "anthropicchatmodel", 30,  // 30% Claude
        "azureopenaichatmodel", 10  // 10% Azure
    );

    public String weightedChat(String prompt) {
        int r = random.nextInt(100);
        int cumulative = 0;
        String selected = "openaichatmodel";

        for (Map.Entry<String, Integer> entry : weights.entrySet()) {
            cumulative += entry.getValue();
            if (r < cumulative) {
                selected = entry.getKey();
                break;
            }
        }

        log.info("路由到模型: {},随机数: {}", selected, r);
        return models.get(selected)
                .call(new Prompt(new UserMessage(prompt)))
                .getResult().getOutput().getContent();
    }
}

6.2 模型热切换(无需重启)

通过 Spring Cloud @RefreshScope + 配置中心实现:

java 复制代码
@RefreshScope
@ConfigurationProperties(prefix = "ai.models")
public class ModelWeightsProperties {
    private Map<String, Integer> weights = new LinkedHashMap<>();
    public Map<String, Integer> getWeights() { return weights; }
    public void setWeights(Map<String, Integer> weights) { this.weights = weights; }
}

@Service
@RefreshScope
@RequiredArgsConstructor
class DynamicModelRouter {
    private final Map<String, ChatModel> models;
    private final ConfigurableApplicationContext context;

    public String chat(String prompt) {
        // 每次从最新配置读取权重
        ModelWeightsProperties props = context.getBean(ModelWeightsProperties.class);
        String selected = selectModel(props.getWeights());
        return models.get(selected)
                .call(new Prompt(new UserMessage(prompt)))
                .getResult().getOutput().getContent();
    }
}

在 Nacos/Apollo 控制台修改以下配置,模型权重立即生效,无需重启:

yaml 复制代码
ai:
  models:
    weights:
      openaichatmodel: 70
      anthropicchatmodel: 30

6.3 模型对比监控

java 复制代码
@Service
@RequiredArgsConstructor
class ModelComparisonService {
    private final Map<String, ChatModel> models;

    /**
     * 同一问题同时请求多个模型,对比输出与耗时
     */
    public Map<String, ModelResult> compare(String prompt) {
        Map<String, ModelResult> results = new ConcurrentHashMap<>();

        models.forEach((name, model) -> {
            long start = System.currentTimeMillis();
            String response = model.call(new Prompt(new UserMessage(prompt)))
                    .getResult().getOutput().getContent();
            long cost = System.currentTimeMillis() - start;

            results.put(name, new ModelResult(response, cost));
            log.info("模型 {} - 耗时: {}ms, 输出长度: {}",
                name, cost, response.length());
        });

        return results;
    }

    public record ModelResult(String response, long costMs) {}
}

7. 安全配置:API Key 与密钥管理

7.1 禁止的做法

yaml 复制代码
# ❌ 绝对禁止:硬编码 API Key
spring:
  ai:
    openai:
      api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

7.2 环境变量(开发环境)

bash 复制代码
# .env / .bashrc / .zshrc
export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
export ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxx
yaml 复制代码
# application.yml
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}

7.3 配置中心加密(生产环境)

yaml 复制代码
# application-prod.yml(配置中心中存储加密后的 key)
spring:
  ai:
    openai:
      api-key: ${encrypted:xxxxxx}  # 配置中心加密存储

7.4 HashiCorp Vault(高安全场景)

java 复制代码
@Configuration
class VaultConfig {
    @Bean
    public String openAiApiKey(VaultTemplate vaultTemplate) {
        VaultResponse response = vaultTemplate.read("secret/openai");
        return response.getData().get("api-key").toString();
    }

    @Bean
    public OpenAiChatModel openAiChatModel(String openAiApiKey) {
        return OpenAiChatModel.builder()
            .apiKey(openAiApiKey)
            .build();
    }
}

8. 完整配置示例

8.1 application.yml(多模型配置)

yaml 复制代码
spring:
  application:
    name: spring-ai-multi-model-demo

  # ============ OpenAI ============
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: ${OPENAI_BASE_URL:https://api.openai.com}
      chat:
        options:
          model: gpt-4o-mini
          temperature: 0.7
          max-tokens: 2000

    # ============ Anthropic(可选)============
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      base-url: ${ANTHROPIC_BASE_URL:https://api.anthropic.com}
      chat:
        options:
          model: claude-3-5-sonnet-20241022
          max-tokens: 4096
          temperature: 0.7

    # ============ Azure OpenAI(可选)============
    azure:
      openai:
        api-key: ${AZURE_OPENAI_API_KEY}
        endpoint: ${AZURE_OPENAI_ENDPOINT}
        chat:
          deployment-name: ${AZURE_DEPLOYMENT_NAME:gpt-4o-mini}
          options:
            api-version: 2024-06-01
            temperature: 0.7

8.2 多模型配置类

java 复制代码
@Configuration
@EnableConfigurationProperties({
    OpenAiProperties.class,
    AnthropicProperties.class,
    AzureOpenAiProperties.class
})
public class MultiModelAutoConfig {

    @Bean
    @Primary
    public ChatModel defaultChatModel(OpenAiChatModel m) { return m; }

    @Bean
    public ChatModel anthropicChatModel(AnthropicChatModel m) { return m; }

    @Bean
    public ChatModel azureChatModel(AzureOpenAiChatModel m) { return m; }
}

9. 小结

本文深入讲解了 Spring AI 的多模型适配体系:

  1. 自动配置机制 :依赖引入即生效,Properties 类 + @ConditionalOnProperty 实现零代码配置
  2. 三大主流模型:OpenAI(编程最强)、Anthropic Claude(长文本+思考模式)、Azure OpenAI(企业合规)
  3. 配置优先级 :调用时 options() > Bean 默认 > yml > 硬编码默认值
  4. 多模型路由:Map 注入 + 策略模式 + 统一业务接口
  5. 生产级实践:权重分流、A/B 测试、热切换、Vault 密钥管理

阶段一「Spring AI 核心基础」到此完结,覆盖了架构设计、ChatClient 实战、Prompt 工程与 Advisors、多模型适配四大主题。

下阶段预告:【Spring AI 实战】五、RAG 核心原理:为什么需要检索增强生成?------进入 RAG 阶段,我们将从向量 Embedding 开始,深入讲解文档处理、向量数据库、RAG 流程优化,以及如何构建一个完整的企业知识库问答系统。


📌 系列导航

📎 示例说明:本文聚焦多模型抽象与配置思路,RAG 主线从第五篇开始。

相关推荐
Cobyte2 小时前
从网关的角度理解并实现一个 Mini OpenClaw
后端·aigc·ai编程
马丁玩编程2 小时前
从程序员到AI工程师:距离有多远?附全套学习路线图
后端·程序员·aigc
rannn_1112 小时前
【Redis|高级篇2】多级缓存|JVM进程缓存、Lua语法、多级缓存实现(OpenResty)、缓存同步(Canal)
java·redis·分布式·后端·缓存·lua·openresty
Ava的硅谷新视界2 小时前
SQLite WAL 模式踩坑笔记:高并发读写下的几个细节
开发语言·后端·编程
不光头强7 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp10 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多11 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
小小李程序员11 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai