03:多 LLM 提供商统一接入:Provider 模式与 LiteLLM 实践

引言

AI 应用面临的一个核心挑战是如何优雅地支持多个 LLM 提供商。CountBot 通过 Provider 抽象模式 + LiteLLM 适配层,实现了对 9+ 种 LLM 提供商的统一接入。本文将深入分析这一设计。

架构设计

三层抽象

markdown 复制代码
AgentLoop
    ↓ 调用统一接口
LLMProvider (抽象基类)
    ↓ 具体实现
LiteLLMProvider
    ↓ 委托
LiteLLM 库 → OpenAI / Anthropic / DeepSeek / Gemini / ...

Provider 抽象基类

python 复制代码
class LLMProvider(ABC):
    def __init__(self, api_key, api_base, default_model, timeout=120.0, max_retries=3):
        self.api_key = api_key
        self.api_base = api_base
        self.default_model = default_model
        self.timeout = timeout
        self.max_retries = max_retries

    @abstractmethod
    async def chat_stream(
        self, messages, tools=None, model=None,
        max_tokens=4096, temperature=0.7, **kwargs
    ) -> AsyncIterator[StreamChunk]:
        pass

关键设计决策:

  • 流式优先chat_stream 返回 AsyncIterator[StreamChunk],而非一次性返回完整响应
  • 统一数据模型StreamChunk 封装了文本内容、工具调用、推理内容、错误信息等所有可能的响应类型

StreamChunk 统一响应模型

python 复制代码
@dataclass
class StreamChunk:
    content: str | None = None              # 文本内容
    tool_call: ToolCall | None = None       # 工具调用
    finish_reason: str | None = None        # 完成原因
    usage: dict[str, int] | None = None     # Token 用量
    error: str | None = None                # 错误信息
    reasoning_content: str | None = None    # 推理内容(思考模型)

    @property
    def is_content(self) -> bool: return self.content is not None
    @property
    def is_tool_call(self) -> bool: return self.tool_call is not None
    @property
    def is_reasoning(self) -> bool: return self.reasoning_content is not None

reasoning_content 字段是为 DeepSeek-R1、Kimi 等"思考模型"设计的,这些模型会在生成最终回答前输出推理过程。

Provider 注册表

python 复制代码
PROVIDER_REGISTRY = {
    "openai": ProviderMetadata(
        id="openai",
        name="OpenAI",
        default_api_base="https://api.openai.com/v1",
        default_model="gpt-4o",
        litellm_prefix="",
        env_key="OPENAI_API_KEY",
    ),
    "anthropic": ProviderMetadata(
        id="anthropic",
        name="Anthropic",
        default_api_base="https://api.anthropic.com",
        default_model="claude-3-5-sonnet-20241022",
        litellm_prefix="anthropic/",
        env_key="ANTHROPIC_API_KEY",
    ),
    "deepseek": ProviderMetadata(
        id="deepseek",
        default_api_base="https://api.deepseek.com",
        default_model="deepseek-chat",
        litellm_prefix="deepseek/",
        env_key="DEEPSEEK_API_KEY",
    ),
    # ... 更多提供商
}

注册表的核心字段:

  • litellm_prefix:LiteLLM 识别模型的前缀,如 anthropic/claude-3-5-sonnet
  • env_key:API Key 对应的环境变量名
  • env_extras:额外的环境变量配置(如 Azure 需要 endpoint 等)
  • model_overrides:特定模型的参数覆盖

LiteLLMProvider 实现细节

环境变量自动配置

python 复制代码
def _configure_litellm(self, api_key, api_base):
    provider_metadata = get_provider_metadata(self.provider_id)
    if provider_metadata:
        if provider_metadata.env_key and api_key:
            os.environ[provider_metadata.env_key] = api_key
        for env_name, env_val in provider_metadata.env_extras:
            resolved = env_val.replace("{api_key}", api_key or "")
            resolved = resolved.replace("{api_base}", effective_base)
            os.environ[env_name] = resolved

这种设计让用户只需在 Web UI 中填写 API Key,系统自动处理环境变量配置。

日志抑制

python 复制代码
def _suppress_litellm_logging(self):
    os.environ["LITELLM_LOG"] = "ERROR"
    litellm.suppress_debug_info = True
    litellm.set_verbose = False
    litellm.drop_params = True
    litellm.telemetry = False

LiteLLM 默认日志非常冗长,CountBot 通过全面抑制确保日志清洁。

模型参数智能适配

不同提供商对参数的支持不同,CountBot 通过 model_overrides 机制处理:

python 复制代码
# 例如 Kimi 模型要求 temperature 必须为 1.0
"kimi": ProviderMetadata(
    model_overrides={"moonshot-v1-auto": {"temperature": 1.0}},
)

工具调用兼容性

不同 LLM 对 Function Calling 的支持程度不同。CountBot 通过 ToolCallParser 提供了降级方案:

python 复制代码
class ToolCallParser:
    # 支持三种格式的工具调用解析
    @classmethod
    def parse(cls, text):
        result = cls._parse_json(text)      # 标准 JSON 格式
        if result: return result
        result = cls._parse_simple(text)     # 简单文本格式
        if result: return result
        result = cls._parse_pure_json(text)  # 纯 JSON 对象
        if result: return result
        return None

这确保了即使 LLM 不支持原生 Function Calling,也能通过文本解析实现工具调用。

切换提供商的零成本

由于统一的抽象层,切换 LLM 提供商只需修改配置:

python 复制代码
# 从 OpenAI 切换到 DeepSeek,只需改配置
config.model.provider = "deepseek"
config.model.model = "deepseek-chat"

AgentLoop、工具系统、渠道系统等所有上层代码完全不需要修改。

总结

CountBot 的多 Provider 架构展示了一个优秀的适配器模式实践:通过抽象基类定义统一接口,通过注册表管理元数据,通过 LiteLLM 处理底层差异。这种设计使得系统可以轻松适应快速变化的 LLM 生态。

相关推荐
一勺菠萝丶几秒前
常见 AI 模型类型整理:大语言模型、聊天模型、推理模型、Embedding 模型到底有什么区别?
人工智能·语言模型·embedding
多年小白2 分钟前
今日A股 拉
大数据·人工智能·深度学习·microsoft·ai
wujian83112 分钟前
怎么把Kimi里的表格完整复制到wps内
人工智能·ai·wps·豆包·deepseek·ai导出鸭
Joy T3 分钟前
【碳金融】欧盟CBAM逻辑与“磐石·禹衡”系统的技术对冲分析
人工智能·重构·cbam·碳排放·碳核算·磐石
字节高级特工4 分钟前
C++11(一) 革新:右值引用与移动语义
java·开发语言·c++·人工智能·后端
DO_Community5 分钟前
Token聚合平台 vs 传统云 vs AI原生云,AI推理应用怎么选?
人工智能·agent·token·ai-native·deepseek
码农小旋风5 分钟前
2026最新国内用户Claude Code 开发配置详细手册
人工智能·chatgpt·claude
byte轻骑兵8 分钟前
【LE Audio】CAP精讲[9]:全流程操盘手,解锁CAP核心交互工序
人工智能·音视频·人机交互·le audio·音视频控制
AI科技星9 分钟前
强哥德巴赫猜想(1+1)终极证明(2026 年5月 21 日)
开发语言·人工智能·算法·计算机视觉·量子计算
枫叶林FYL9 分钟前
【强化学习】5 异构机器人数据集的跨具身离线强化学习:形态感知分组与梯度冲突消解
人工智能·系统架构·机器人