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 生态。

相关推荐
彩讯股份30063432 分钟前
彩讯股份与心洲科技签署战略合作协议,共建企业级模型后训练能力
人工智能·科技
迅易科技41 分钟前
从场景验证到Agent上线:迅易 × WorkBuddy如何帮助企业建设AI能力?
人工智能·ai·腾讯云
PNP Robotics1 小时前
多伦多大学机器人峰会|物理AI与具身智能落地新趋势
人工智能·深度学习·机器学习·机器人
GIR1231 小时前
官方出品 | 多通道土壤呼吸测量系统市场现状与十五五规划深度报告:行业分析+趋势预测全收录
大数据·人工智能·机器学习
绿算技术1 小时前
绿算技术亮相第十八届HPC AI中国年会,擘画AI基础设施全栈协同新图景
人工智能
Litluecat1 小时前
2026年7月22日科技热点新闻
人工智能·科技·新闻·每日·速览
To_OC2 小时前
别再傻傻分不清:Workflow 和 Agent 到底不是一回事
人工智能·agent·workflow
触底反弹2 小时前
🔥 2026 大模型选择指南:别再只看 Benchmark 了,这些维度才是关键!
人工智能·面试
神奇霸王龙2 小时前
GB/T 46886 闭环屠夫:5 旗舰多模态 LLM 工业质检实测
人工智能·计算机视觉·ai·开源·ai编程·本地部署
南讯股份Nascent2 小时前
洽洽全域会员项目启动会圆满召开
大数据·人工智能