Spring AI深度解析(1/50):ChatClient抽象层如何实现跨模型兼容
二、实现剖析:DeepSeekChatClient源码实现。三、通义千问的特殊处理:阿里云签名机制。一、接口抽象:统一国内模型的调用范式。• 部分厂商要求数据出境审查(需配置。• 通义千问需要处理阿里云V3签名。四、动态切换配置示例。五、扩展开发注意事项。
·
一、接口抽象:统一国内模型的调用范式
对比国内主流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()
);
}
}
五、扩展开发注意事项
-
国产模型特殊处理:
• 通义千问需要处理阿里云V3签名• 部分厂商要求数据出境审查(需配置
spring.ai.deepseek.data-region=cn) -
流式响应差异:
// 通义千问的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); } -
异常代码映射:
try { // 调用国产模型API... } catch (HttpClientErrorException e) { if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) { throw new AiAuthenticationException("API密钥无效"); } else if (e.getRawStatusCode() == 451) { throw new AiContentFilterException("内容合规性检查未通过"); } }
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)