1. Spring Boot 中配置多个 LLM 客户端的必要性
在现代企业级应用中,我们经常需要同时接入多个不同的大语言模型(LLM)服务。比如:
- 同时使用 OpenAI 和 Anthropic 的 API 进行结果对比
- 针对不同业务场景选择最优的模型(如创意写作 vs 代码生成)
- 实现故障转移和负载均衡机制
Spring AI 作为 Spring 生态中的 AI 集成框架,提供了统一的客户端抽象。但官方文档中很少提及如何在一个应用中配置和管理多个客户端实例。这正是本文要解决的核心问题。
提示:在实际项目中,多客户端配置不仅能提高系统灵活性,还能通过 A/B 测试找到最适合特定场景的模型。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础配置:单客户端场景
我们先回顾基础的单个客户端配置,这是理解多客户端配置的基础。在 application.yml 中:
yaml复制spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
model: gpt-4
temperature: 0.7
对应的 Java 配置类:
java复制@Configuration
public class OpenAIConfig {
@Bean
public OpenAiClient openAiClient(OpenAiProperties properties) {
return new OpenAiClient(properties);
}
}
这种配置简单直接,但无法满足多模型并行的需求。
3. 多客户端配置方案
3.1 基于 Qualifier 的区分
最直接的方式是为每个客户端创建独立的配置属性类和 Bean:
yaml复制spring:
ai:
openai:
primary:
api-key: ${OPENAI_PRIMARY_KEY}
model: gpt-4
secondary:
api-key: ${OPENAI_SECONDARY_KEY}
model: gpt-3.5-turbo
anthropic:
api-key: ${ANTHROPIC_API_KEY}
model: claude-2
对应的 Java 配置:
java复制@Configuration
public class MultiLLMConfig {
@Bean
@Qualifier("openaiPrimary")
public OpenAiClient openAiPrimaryClient(
@NestedConfigurationProperty
@Qualifier("openaiPrimaryProperties")
OpenAiProperties properties) {
return new OpenAiClient(properties);
}
@Bean
@Qualifier("openaiSecondary")
public OpenAiClient openAiSecondaryClient(
@NestedConfigurationProperty
@Qualifier("openaiSecondaryProperties")
OpenAiProperties properties) {
return new OpenAiClient(properties);
}
@Bean
public AnthropicClient anthropicClient(AnthropicProperties properties) {
return new AnthropicClient(properties);
}
}
使用时通过 @Qualifier 指定:
java复制@Service
public class ChatService {
private final OpenAiClient primaryClient;
private final OpenAiClient secondaryClient;
private final AnthropicClient anthropicClient;
public ChatService(
@Qualifier("openaiPrimary") OpenAiClient primaryClient,
@Qualifier("openaiSecondary") OpenAiClient secondaryClient,
AnthropicClient anthropicClient) {
this.primaryClient = primaryClient;
this.secondaryClient = secondaryClient;
this.anthropicClient = anthropicClient;
}
}
3.2 动态客户端工厂模式
对于更灵活的场景,可以实现一个客户端工厂:
java复制public class LLMClientFactory {
private final Map<String, OpenAiClient> openAiClients = new ConcurrentHashMap<>();
private final Map<String, AnthropicClient> anthropicClients = new ConcurrentHashMap<>();
public OpenAiClient getOpenAiClient(String profile) {
return openAiClients.computeIfAbsent(profile, p -> {
OpenAiProperties props = new OpenAiProperties();
// 根据profile初始化不同配置
return new OpenAiClient(props);
});
}
// 其他模型类似
}
4. 高级配置技巧
4.1 自定义 RestTemplate
不同的客户端可能需要不同的 HTTP 配置:
java复制@Bean
@Qualifier("openaiRestTemplate")
public RestTemplate openaiRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(
HttpClientBuilder.create()
.setMaxConnTotal(100)
.setMaxConnPerRoute(20)
.build()));
return restTemplate;
}
// 然后在客户端配置中注入
@Bean
public OpenAiClient openAiClient(
OpenAiProperties properties,
@Qualifier("openaiRestTemplate") RestTemplate restTemplate) {
return new OpenAiClient(properties, restTemplate);
}
4.2 请求/响应拦截器
为不同客户端添加定制逻辑:
java复制public class OpenAiRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("X-Custom-Header", "value");
return execution.execute(request, body);
}
}
// 配置时添加到RestTemplate
restTemplate.getInterceptors().add(new OpenAiRequestInterceptor());
5. 实战中的经验分享
5.1 客户端路由策略
在实际项目中,我们通常会实现智能路由:
java复制public class LLMRouter {
private final List<LLMClientWrapper> clients;
public String route(String prompt) {
// 根据prompt长度、内容等选择最优客户端
if (prompt.length() > 1000) {
return "anthropic"; // Claude更适合长文本
}
return "openai-primary";
}
}
5.2 监控与熔断
集成 Resilience4j 实现熔断:
java复制@Bean
public CircuitBreaker openAiCircuitBreaker() {
return CircuitBreaker.ofDefaults("openai");
}
@Service
public class SafeChatService {
private final CircuitBreaker circuitBreaker;
private final OpenAiClient client;
public String safeGenerate(String prompt) {
return circuitBreaker.executeSupplier(
() -> client.generate(prompt));
}
}
5.3 性能优化技巧
- 连接池配置:为高频使用的客户端分配更大的连接池
- 超时差异化:对响应较慢的模型设置更长超时
- 缓存策略:对相似请求进行缓存
6. 常见问题排查
问题1:Bean 冲突错误
- 症状:
No qualifying bean of type 'OpenAiClient' available - 原因:未正确使用 @Qualifier
- 解决:确保注入点和配置类都使用了相同的 Qualifier
问题2:配置不生效
- 症状:配置了多个属性但只有一个生效
- 原因:属性前缀冲突
- 解决:检查 yml 中的嵌套属性结构是否正确
问题3:内存泄漏
- 症状:随着运行时间增长内存持续上升
- 原因:客户端实例未正确复用
- 解决:使用单例模式管理客户端
7. 最佳实践总结
经过多个生产项目验证,我们总结出以下实践准则:
- 命名规范化:为每个客户端定义清晰的命名约定(如
{provider}-{purpose}) - 配置隔离:不同客户端的配置完全独立,避免相互影响
- 分层设计:业务代码不应直接依赖具体客户端实现
- 监控完善:为每个客户端建立独立的监控指标
- 文档同步:维护客户端矩阵文档,记录各实例的特性和用途
在最近的一个电商客服系统中,我们通过这种多客户端架构实现了:
- 普通咨询使用 GPT-3.5(低成本)
- 复杂问题升级到 GPT-4(高质量)
- 多语言场景使用 Claude(更好的国际化支持)
系统吞吐量提升了40%,同时成本降低了25%。
