一、接口抽象:统一国内模型的调用范式
对比国内主流AI服务的API差异:

服务商 端点URL 认证方式 响应结构特征
DeepSeek /v1/chat/completions Bearer Token 类OpenAI兼容格式
通义千问 /api/v1/services/aigc/text-generation/generation AK/SK签名 阿里云标准JSON格式

Spring AI适配方案:

// DeepSeek请求转换器
public class DeepSeekRequestConverter implements RequestConverter<DeepSeekRequest> {
    public DeepSeekRequest convert(ChatRequest request) {
        return DeepSeekRequest.builder()
            .model("deepseek-chat")
            .messages(request.getMessages().stream()
                .map(m -> new DeepSeekMessage(m.getRole(), m.getContent()))
                .toList())
            .temperature(0.7)
            .build();
    }
}

// 通义千问请求转换器(阿里云签名机制)
public class QwenRequestConverter implements RequestConverter<QwenRequest> {
    public QwenRequest convert(ChatRequest request) {
        return QwenRequest.builder()
            .model("qwen-max")
            .input(new QwenInput(request.getPrompt()))
            .parameters(new QwenParams(0.8, 1024))
            .build();
    }
}

二、实现剖析:DeepSeekChatClient源码实现

核心实现流程:

@startuml
participant ChatController
participant DeepSeekChatClient
participant SignatureUtil
participant HttpClients
database DeepSeekAPI

ChatController -> DeepSeekChatClient: call("如何学习AI?")
DeepSeekChatClient -> DeepSeekChatClient: 构建请求头
DeepSeekChatClient -> SignatureUtil: 生成Authorization
SignatureUtil --> DeepSeekChatClient: Bearer {token}
DeepSeekChatClient -> HttpClients: 发送POST请求
HttpClients -> DeepSeekAPI: /v1/chat/completions
DeepSeekAPI --> HttpClients: 返回JSON响应
HttpClients --> DeepSeekChatClient: 解析结果
DeepSeekChatClient -> ChatController: 返回标准化ChatResponse
@enduml

完整实现类:

public class DeepSeekChatClient extends AbstractChatClient {
    private final RestTemplate restTemplate;
    private final String apiKey;
    
    @Override
    public ChatResponse chat(ChatRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + apiKey);
        headers.setContentType(MediaType.APPLICATION_JSON);

        DeepSeekRequest dsRequest = new DeepSeekRequest(
            "deepseek-chat",
            convertMessages(request.getMessages()),
            0.7
        );

        HttpEntity<DeepSeekRequest> entity = new HttpEntity<>(dsRequest, headers);
        
        ResponseEntity<DeepSeekResponse> response = restTemplate.postForEntity(
            "https://api.deepseek.com/v1/chat/completions",
            entity,
            DeepSeekResponse.class
        );

        return convertResponse(response.getBody());
    }

    // 响应体转换逻辑
    private ChatResponse convertResponse(DeepSeekResponse response) {
        return ChatResponse.builder()
            .content(response.getChoices().get(0).getMessage().getContent())
            .usage(new Usage(
                response.getUsage().getPromptTokens(),
                response.getUsage().getCompletionTokens()))
            .build();
    }

    // 请求体定义
    @Builder
    @Jacksonized
    private static class DeepSeekRequest {
        private String model;
        private List<DeepSeekMessage> messages;
        private double temperature;
    }

    @Value
    private static class DeepSeekMessage {
        private String role;
        private String content;
    }
}

三、通义千问的特殊处理:阿里云签名机制

// 通义千问的签名生成器
public class QwenSignatureUtil {
    public static String generateSignature(
        String accessKeySecret, 
        String method, 
        String path,
        Map<String, String> headers) {
        
        String canonicalizedHeaders = headers.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .map(e -> e.getKey() + ":" + e.getValue())
            .collect(Collectors.joining("\n"));

        String stringToSign = method + "\n" 
            + path + "\n"
            + canonicalizedHeaders;

        Mac hmac = Mac.getInstance("HmacSHA256");
        hmac.init(new SecretKeySpec(accessKeySecret.getBytes(), "HmacSHA256"));
        byte[] signData = hmac.doFinal(stringToSign.getBytes());
        return Base64.getEncoder().encodeToString(signData);
    }
}

// 在QwenChatClient中应用签名
public class QwenChatClient implements ChatClient {
    public ChatResponse chat(ChatRequest request) {
        String path = "/api/v1/services/aigc/text-generation/generation";
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Acs-Action", "GenerateText");
        headers.put("X-Acs-Version", "2023-08-01");
        
        String signature = QwenSignatureUtil.generateSignature(
            config.getAccessKeySecret(), 
            "POST",
            path,
            headers
        );
        
        headers.put("Authorization", "ACS3-HMAC-SHA256 " 
            + "Credential=" + config.getAccessKeyId()
            + ",Signature=" + signature);
        
        // 发送请求...
    }
}

四、动态切换配置示例

spring:
  ai:
    provider: deepseek  # 可选 qwen
    deepseek:
      base-url: https://api.deepseek.com
      api-key: ${DEEPSEEK_KEY}
    qwen:
      access-key-id: ${ALIYUN_ACCESS_KEY}
      access-key-secret: ${ALIYUN_SECRET}
      region-id: cn-hangzhou
@Configuration
public class ChatClientConfiguration {

    @Bean
    @ConditionalOnProperty(prefix = "spring.ai", name = "provider", havingValue = "deepseek")
    public ChatClient deepSeekChatClient(
        @Autowired DeepSeekProperties properties,
        RestTemplateBuilder restTemplateBuilder) {
        
        return new DeepSeekChatClient(
            restTemplateBuilder.rootUri(properties.getBaseUrl())
                .defaultHeader("Authorization", "Bearer " + properties.getApiKey())
                .build()
        );
    }

    @Bean
    @ConditionalOnProperty(prefix = "spring.ai", name = "provider", havingValue = "qwen")
    public ChatClient qwenChatClient(QwenProperties properties) {
        return new QwenChatClient(
            properties.getAccessKeyId(),
            properties.getAccessKeySecret(),
            properties.getRegionId()
        );
    }
}

五、扩展开发注意事项

  1. 国产模型特殊处理:
    • 通义千问需要处理阿里云V3签名

    • 部分厂商要求数据出境审查(需配置spring.ai.deepseek.data-region=cn

  2. 流式响应差异:

    // 通义千问的SSE流式实现
    public Flux<String> stream(ChatRequest request) {
        return webClient.post()
            .uri("/api/v1/services/aigc/text-generation/generation/stream")
            .header("X-Acs-Request-Id", UUID.randomUUID().toString())
            .bodyValue(buildQwenRequest(request))
            .retrieve()
            .bodyToFlux(String.class)
            .map(this::extractContent);
    }
    
  3. 异常代码映射:

    try {
        // 调用国产模型API...
    } catch (HttpClientErrorException e) {
        if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
            throw new AiAuthenticationException("API密钥无效");
        } else if (e.getRawStatusCode() == 451) {
            throw new AiContentFilterException("内容合规性检查未通过");
        }
    }
    

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐