Agent 框架里 LLM 客户端适合用单例吗?有什么替代方案?
关于 Agent 框架里 LLM 客户端能不能用单例,我的观点是:
可以直接用,但不推荐把它做成“传统意义上的单例”,更好的思路是“统一管理、按需使用、共享底层连接”。
下面我按这个逻辑展开,配上图标对比来看。
🔍 LLM 客户端到底长什么样?¶
一个典型的 LLM 客户端封装了:
-
配置:API Key、Base URL、模型名、温度等参数
-
HTTP 连接层:OkHttp / Apache HttpClient 的实例,管理连接池、超时
-
调用方法:
chat(prompt)、stream()等
在 Agent 框架中,可能同时需要调用多个模型,比如:
-
一个负责思考的工具模型(高温度)
-
一个负责精确执行的模型(低温度、JSON 模式)
-
甚至不同云厂商的模型(OpenAI + Claude)
✅ 单例的优点(为什么有人会用)¶
❌ 单例在 Agent 场景下的致命问题¶
但 Agent 框架的特点是多模型、多配置、动态切换,单例会暴露几个硬伤:
-
❌ 无法同时持有多种配置 你想用一个
getInstance()同时返回 GPT-4 和 GPT-3.5?不行,要么得在方法里传参数破坏设计,要么就得维护多个全局实例,那还叫单例吗? -
❌ 温度、超时等参数僵化 单例构造时定死了参数,但 Agent 的某个步骤可能需要临时调高温度以增加创造性,单例根本没法灵活应变。
-
❌ 测试和 Mock 困难 单例是全局状态,单元测试时很难替换成 Mock 对象,会导致多个测试之间互相影响。
-
❌ 违反单一职责 Agent 中每个工具链可能只需要一个特定的 LLM 能力,强行共用一个大而全的单例会让依赖关系混乱。
🛠️ 替代方案(重点来了)¶
1️⃣ 依赖注入 + 容器管理的 Bean(推荐)¶
不是在代码里写死单例,而是让框架容器(如 Spring)把 LLM 客户端管理成单例 Bean。
同时支持按 Qualifier 注入不同配置的客户端。
@Configuration
public class LLMConfig {
@Bean
@Qualifier("creative")
public LLMClient creativeClient() {
return new LLMClient(apiKey, model, 0.9); // 高温度
}
@Bean
@Qualifier("precise")
public LLMClient preciseClient() {
return new LLMClient(apiKey, model, 0.1); // 低温度
}
}
使用时通过注入拿到自己需要的实例,底层 HTTP 客户端可以统一复用。
✅ 优点:灵活、可测试、多实例并存,但管理权在容器,保持了资源的可控性。
2️⃣ 多例客户端 + 静态共享底层连接池¶
客户端本身不是单例,每次可以 new 出不同配置的 LLMClient,但它们共享同一个全局的 HTTP 连接池(如 OkHttpClient 实例)。
public class LLMClient {
private static final OkHttpClient SHARED_HTTP = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(5, 1, TimeUnit.MINUTES))
.build();
private final String model;
private final double temperature;
public LLMClient(String apiKey, String model, double temp) {
this.model = model;
this.temperature = temp;
// 使用共享的 httpClient 进行实际调用
}
}
✅ 既保留了多实例灵活配置的优点,又避免了重复创建连接池开销。
3️⃣ 工厂模式按需创建¶
把客户端创建逻辑收口到一个工厂类,Agent 按需索取。
工厂内部可以缓存实例(类似有限数量的“多例”),但对外隐藏细节。
4️⃣ 对象池化(高吞吐场景)¶
如果需要频繁创建/销毁客户端(比如每次请求不同参数),可以配合 GenericObjectPool 管理一组预热的客户端,避免反复握手。
不过 LLM 调用本身是慢请求,池化带来的收益有限,一般不是首选。
📌 总结¶
-
单例能用,但约束太大,不适合 Agent 动态多变的场景。
-
推荐方案:依赖注入管理多个具名实例,共享底层 OkHttp 连接池,既保证了灵活配置,又复用了网络开销。
-
面试中如果提到这一点,再加上工厂模式或容器管理的细节,会显得对架构设计理解比较深。
这样讲,有没有把权衡和替代方案说清楚?如果需要,我可以再展开聊聊在 LangChain 这类框架里具体怎么落地。