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

相关推荐
机器人零零壹34 分钟前
技术白皮书:iRobotCAM如何解决机器人激光切割应用中的核心难点,从CAD离线编程到双目视觉定位与轨迹自适应提升复杂空间曲线切割的精度、效率与柔性化水平
人工智能·算法
电子元件小说家2 小时前
2026年四季度电感市场观察:AI算力与汽车电子驱动下的选型与验证建议智能硬件
人工智能·汽车
网易CodeWave-小码哥2 小时前
支持多模块 Java 微服务导入|CodeWave SDD V4.5.10 上线
人工智能
AI你一生一世2 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
belldeep2 小时前
python:genesis-world, Genesis-Embodied-AI
人工智能·python·ai·genesis
怕浪猫2 小时前
小程序成长计划上新:10 亿 Token 现在能调 DeepSeek-v4.1-flash了
人工智能·aigc·ai编程
ss2732 小时前
为什么PostgreSQL适合AI场景?
数据库·人工智能·postgresql
七牛云行业应用3 小时前
DeepSeek Super-agent 与 Grok Bot:开源 Agent 架构和聊天助手有何区别
人工智能·ai·架构·ai编程
维克兜率天3 小时前
【维克】趋势型CTA:期货市场的“追涨杀跌“
人工智能·python
u1301303 小时前
GitHub 热榜项目:日榜(2026-11-03)
人工智能·github