跳转到内容

LLM Provider 多模型适配层

学习目标

理解 MetaGPT 的 LLM Provider 层如何通过注册表模式实现多模型适配,以及重试、成本和流式传输的实现细节。


项目实践

统一抽象:BaseLLM

所有 LLM 提供商继承 BaseLLM 抽象类:

class BaseLLM:
def __init__(self, llm_config: LLMConfig): ...
async def aask(self, msg, system_msgs=None, stream=True, temperature=None) -> str: ...
async def _achat_completion(self, messages, timeout, temperature) -> ChatCompletion: ...
def get_choice_text(self, rsp: ChatCompletion) -> str: ...
def get_usage(self, messages, response_text) -> dict: ...

公共能力:

  • aask():统一的异步对话接口,支持流式和非流式
  • _calc_usage():计算 token 消耗和成本
  • cost_manager:关联的成本管理器

注册表模式

llm_provider_registry.py 实现了 Provider 注册表:

# 装饰器:将 Provider 类绑定到多个 LLMType
def register_provider(types: list[LLMType]):
def decorator(cls):
for t in types:
LLM_API_REGISTRY[t] = cls
return cls
return decorator
# 工厂函数:根据配置创建实例
def create_llm_instance(config: LLMConfig) -> BaseLLM:
return LLM_API_REGISTRY[config.api_type](config)

OpenAILLM:复用适配

OpenAILLM 通过注册表绑定到 10+ 个 LLMType:

@register_provider([
LLMType.OPENAI,
LLMType.FIREWORKS,
LLMType.OPEN_LLM,
LLMType.MOONSHOT,
LLMType.MISTRAL,
LLMType.YI,
LLMType.OPEN_ROUTER,
LLMType.DEEPSEEK,
LLMType.SILICONFLOW,
LLMType.LLAMA_API,
])
class OpenAILLM(BaseLLM):
def _init_client(self):
self.aclient = AsyncOpenAI(
api_key=self.config.api_key,
base_url=self.config.base_url,
)

所有兼容 OpenAI API 的提供商复用同一个 OpenAILLM 类,只需在配置中指定不同的 base_url

独立 Provider

需要自定义客户端的提供商独立实现:

Provider用途SDK
AzureOpenAILLMAzure OpenAI 服务openai.AsyncAzureOpenAI
AnthropicLLMClaude 系列anthropic.AsyncAnthropic
GoogleGeminiLLMGoogle Geminigoogle.generativeai
OllamaLLM本地 OllamaHTTP API
QianFanLLM百度千帆qianfan SDK
SparkLLM讯飞星火WebSocket
ZhiPuLLM智谱 AIzhipuai SDK
BedrockLLMAWS Bedrockboto3

重试机制

OpenAILLM 使用 Tenacity 实现自动重试:

@retry(
wait=wait_random_exponential(min=1, max=20),
stop=stop_after_attempt(3),
retry=retry_if_exception_type(APIConnectionError),
after=after_log(logger, logger.level("WARNING").name),
reraise=Exception,
)
async def _achat_completion_stream(self, messages, timeout, temperature) -> str:
# 流式补全实现
...
  • 指数退避:1-20 秒随机等待
  • 最多 3 次重试
  • 只重试 APIConnectionError(非业务错误)

成本追踪

每种模型类型关联对应的 CostManager:

def _select_costmanager(self, llm_config: LLMConfig) -> CostManager:
if llm_config.api_type == LLMType.FIREWORKS:
return FireworksCostManager()
elif llm_config.api_type == LLMType.OPEN_LLM:
return TokenCostManager() # 固定 token 计费
else:
return self.cost_manager # 标准计费

CostManager 内部维护:

class CostManager:
total_cost: float = 0.0
max_budget: float = 100.0
# 按模型统计 token 消耗
def record_cost(self, model: str, prompt_tokens: int, completion_tokens: int): ...

配置

~/.metagpt/config2.yaml

llm:
api_type: "openai" # 或 azure / ollama / groq 等
model: "gpt-4-turbo" # 或 gpt-3.5-turbo
base_url: "https://api.openai.com/v1"
api_key: "YOUR_API_KEY"

代码中通过 Config.default() 加载:

class Config(BaseModel):
llm: LLMConfig = Field(default_factory=LLMConfig)
@classmethod
def default(cls) -> "Config":
return cls.model_validate(read_yaml_file(CONFIG_ROOT / "config2.yaml"))

问题与规避

max_tokens 参数名差异

  • OpenAI 新模型使用 max_completion_tokens,旧模型和兼容 API 使用 max_tokens
  • get_max_completion_tokens() 根据模型名判断使用哪个参数
  • 陷阱:自定义 OpenAI 兼容 API 可能不支持 max_completion_tokens

流式传输中断

  • Tenacity 重试只覆盖 APIConnectionError,不覆盖流式传输中的中断
  • 对策:_achat_completion_stream() 捕获 asyncio.TimeoutError 并返回已收到的内容

Rate Limit

  • 不同提供商的 rate limit 策略不同
  • 对策:指数退避重试(1-20s 随机)覆盖大多数 rate limit 场景

设计取舍

注册表 vs 工厂模式

  • 注册表:LLM_API_REGISTRY[api_type] = ProviderClass,查找 O(1)
  • 工厂:if-elif 链或 match-case,维护成本高
  • MetaGPT 选择注册表,新提供商只需添加 @register_provider 装饰器

OpenAI SDK 复用 vs 自定义客户端

  • 兼容 OpenAI API 的提供商(Fireworks、Moonshot、Mistral 等)复用 OpenAILLM
  • 非兼容的提供商(Anthropic、Gemini、Spark 等)需要独立实现
  • 好处:减少重复代码,新增兼容提供商只需配置变更
  • 代价:OpenAILLM 内部需要处理不同提供商的边缘情况

参考来源

  • 源码验证: metagpt/provider/base_llm.py — BaseLLM 抽象类
  • 源码验证: metagpt/provider/openai_api.py:43 — OpenAILLM 注册到 10+ 类型
  • 源码验证: metagpt/provider/llm_provider_registry.py — 注册表和工厂
  • 源码验证: metagpt/config2.py — Config 加载
  • 源码验证: metagpt/utils/cost_manager.py — CostManager 成本追踪