跳到主要内容

LLM 提供商

CountBot 的 LLM 提供商子系统负责把不同模型服务统一为一致的流式调用接口。当前实现围绕抽象基类、Provider 工厂、注册表元数据和两套具体 Provider 实现展开。

设计理念

  1. 统一上层接口:业务层只依赖 LLMProvider.chat_stream()
  2. 配置驱动切换:选择 provider 与模型主要依赖配置,而不是修改业务代码。
  3. 元数据集中管理:默认模型、默认 API Base、环境变量映射统一放在注册表里维护。
  4. 兼容多种协议:大多数服务走 OpenAI 兼容接口,Anthropic 协议单独适配。

当前实现结构

当前 provider 模块主要由以下文件组成:

  • backend/modules/providers/base.py
  • backend/modules/providers/registry.py
  • backend/modules/providers/factory.py
  • backend/modules/providers/openai_provider.py
  • backend/modules/providers/anthropic_provider.py
  • backend/modules/providers/tool_parser.py

抽象基类

所有 provider 都继承自 LLMProvider

class LLMProvider(ABC):
def __init__(
self,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
default_model: Optional[str] = None,
timeout: float = 120.0,
max_retries: int = 3,
):
...

@abstractmethod
async def chat_stream(
self,
messages: List[Dict[str, Any]],
tools: Optional[List[Dict[str, Any]]] = None,
model: Optional[str] = None,
max_tokens: int = 4096,
temperature: float = 0.7,
**kwargs: Any,
) -> AsyncIterator[StreamChunk]:
...

@abstractmethod
def get_default_model(self) -> str:
...

流式输出统一使用以下数据结构:

@dataclass
class ToolCall:
id: str
name: str
arguments: Dict[str, Any]

@dataclass
class StreamChunk:
content: Optional[str] = None
tool_call: Optional[ToolCall] = None
finish_reason: Optional[str] = None
usage: Optional[Dict[str, int]] = None
error: Optional[str] = None
reasoning_content: Optional[str] = None

其中 reasoning_content 用来承载思考模型的推理增量,tool_call 用来统一工具调用事件。

Provider 工厂

实例创建由 factory.py 负责:

def create_provider(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
default_model: Optional[str] = None,
timeout: float = 600.0,
max_retries: int = 3,
provider_id: Optional[str] = None,
**kwargs: Any
) -> LLMProvider:
...

当前分流规则如下:

  • anthropic 使用 AnthropicProvider
  • minimaxcustom_anthropic 也走 AnthropicProvider
  • 其他 provider 默认走 OpenAIProvider

创建阶段如果注册表里存在默认值,工厂会自动补齐:

  • default_model
  • api_base

因此应用层通常只需要给出 provider_idapi_key 和必要覆盖项。

Provider 注册表

backend/modules/providers/registry.py 当前维护 22 个 provider:

  • openrouter
  • anthropic
  • openai
  • deepseek
  • moonshot
  • zhipu
  • groq
  • mistral
  • cohere
  • together_ai
  • qwen
  • hunyuan
  • ernie
  • doubao
  • yi
  • baichuan
  • minimax
  • vllm
  • ollama
  • lm_studio
  • custom_openai
  • custom_anthropic

几个当前代码里的重要默认值如下:

Provider默认模型默认 API Base
openaigpt-5.3https://api.openai.com/v1
anthropicclaude-sonnet-4-20250514https://api.anthropic.com
openrouteranthropic/claude-4.5-sonnethttps://openrouter.ai/api/v1
zhipuglm-4.7-flashhttps://open.bigmodel.cn/api/paas/v4
moonshotkimi-k2.5https://api.moonshot.cn/v1
qwenqwen3.5-plushttps://dashscope.aliyuncs.com/compatible-mode/v1

应用默认模型配置来自 backend/modules/config/schema.py

class ModelConfig(BaseModel):
provider: str = "zhipu"
model: str = "glm-5"
temperature: float = 0.7
max_tokens: int = 0

这里有一个值得注意的细节:应用默认 modelglm-5,而注册表里 zhipu 的默认模型仍然是 glm-4.7-flash。这意味着最终使用哪个模型,取决于应用配置是否显式覆盖。

OpenAIProvider

OpenAIProvider 负责大多数 OpenAI 兼容服务:

class OpenAIProvider(LLMProvider):
async def chat_stream(...):
from openai import AsyncOpenAI
...
request_params = {
"model": model,
"messages": messages,
"temperature": temperature,
"stream": True,
}
if max_tokens and max_tokens > 0:
request_params["max_tokens"] = max_tokens
if tools:
request_params["tools"] = tools
request_params["tool_choice"] = "auto"

当前实现特点:

  • 使用官方 openai SDK
  • max_tokens <= 0 时不传该字段
  • 内置指数退避重试
  • 支持普通文本流、工具调用流和 reasoning_content
  • 对常见错误做了用户友好的提示转换

AnthropicProvider

AnthropicProvider 负责 Anthropic 协议以及 Anthropic 兼容接口:

class AnthropicProvider(LLMProvider):
async def chat_stream(...):
...
request_params = self._build_request_params(...)
if self._should_use_sdk():
...
async for chunk in self._chat_stream_via_httpx(...):
yield chunk

和 OpenAI 兼容实现相比,它额外做了几件事:

  • system 消息拆成独立参数
  • 把工具结果消息转换为 Anthropic 的 tool_result
  • 把 OpenAI 风格的工具定义转换为 Anthropic input_schema
  • 官方 anthropic provider 优先走 SDK
  • minimaxcustom_anthropic 等兼容服务可回退到 httpx 直连

这也是当前实现里一个很关键的更新点:AnthropicProvider 不再只是单一路径 SDK 包装,而是同时支持 SDK 和原始 HTTP SSE 流。

配置驱动切换

上层业务代码通常不直接关心具体 provider 类,而是通过配置切换:

{
"model": {
"provider": "openai",
"model": "gpt-5.3",
"temperature": 0.7,
"max_tokens": 0
}
}

这里需要注意:

  • max_tokens = 0 表示“不强制传上限”
  • provider 方法签名里的 4096 是兜底默认值,运行时通常会被配置覆盖
  • provider_id 决定工厂选择哪类 Provider 实现

相关文件

文件说明
backend/modules/providers/base.py抽象基类、ToolCallStreamChunk
backend/modules/providers/registry.pyProvider 元数据注册表
backend/modules/providers/factory.pyProvider 工厂与分流逻辑
backend/modules/providers/openai_provider.pyOpenAI 兼容实现
backend/modules/providers/anthropic_provider.pyAnthropic 与兼容实现
backend/modules/providers/tool_parser.py工具调用解析辅助