跳转至

Agent 系统里有多个模块都需要访问 LLM 客户端(如 openai.AsyncOpenAI),如何设计才能避免重复创建连接池,同时保证可测试性?

这个问题本质上是两个需求的平衡:

既要全局共享一个连接池避免重复创建,又要方便替换以便测试。

我会用依赖注入 + 模块级单例来解决。”


❌ 先看坏味道

多个模块各自实例化客户端:

# agent_a.py
client = AsyncOpenAI()  # 创建一个连接池

# agent_b.py
client = AsyncOpenAI()  # 又创建一个连接池

🔴 问题:

  • 连接池被复制多份,资源浪费;

  • 测试时你没法统一 mock,得去改每个模块的源码或者做猴子补丁,非常难看。


✅ 正确设计:单例创建,依赖注入

把客户端的创建和使用分开:

# llm_client.py —— 全局只建一次
import openai
from functools import lru_cache

@lru_cache(maxsize=1)
def get_llm_client() -> openai.AsyncOpenAI:
    return openai.AsyncOpenAI()

然后每个模块不自己创建,而是通过参数接收:

# agent_a.py
class AgentA:
    def __init__(self, llm: AsyncOpenAI):
        self.llm = llm

组装时注入同一个实例:

# main.py
client = get_llm_client()
agent_a = AgentA(client)
agent_b = AgentB(client)

📊 直观示意图:

image.png

🧪 可测试性怎么来的?

测试时,你根本不用走真实网络:

from unittest.mock import AsyncMock

mock_client = AsyncMock()
agent = AgentA(mock_client)
# 直接断言行为,完全隔离

因为模块依赖于接口(AsyncOpenAI 本身就是个协议),而非具体创建方式,替换起来没有任何负担。


🎯 如果需要更复杂的生命周期管理

可以用上下文管理器或依赖注入容器,但核心原则不变:

  • ✅ 创建集中:单例或工厂负责生成客户端。

  • ✅ 使用注入:模块只声明“我需要一个 LLM 客户端”,不关心它从哪来。

  • ✅ 测试替换:注入 mock 即可,框架完全不受影响。

📌 一句话总结

“单例保连接池,注入保可测性。创建归创建,使用归使用,边界划清楚,两端都舒服。”