LLM 提供商
CountBot 的 LLM 提供商子系统负责把不同模型服务统一为一致的流式调用接口。当前实现围绕抽象基类、Provider 工厂、注册表元数据和两套具体 Provider 实现展开。
设计理念
- 统一上层接口:业务层只依赖
LLMProvider.chat_stream()。 - 配置驱动切换:选择 provider 与模型主要依赖配置,而不是修改业务代码。
- 元数据集中管理:默认模型、默认 API Base、环境变量映射统一放在注册表里维护。
- 兼容多种协议:大多数服务走 OpenAI 兼容接口,Anthropic 协议单独适配。
当前实现结构
当前 provider 模块主要由以下文件组成:
backend/modules/providers/base.pybackend/modules/providers/registry.pybackend/modules/providers/factory.pybackend/modules/providers/openai_provider.pybackend/modules/providers/anthropic_provider.pybackend/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使用AnthropicProviderminimax、custom_anthropic也走AnthropicProvider- 其他 provider 默认走
OpenAIProvider
创建阶段如果注册表里存在默认值,工厂会自动补齐:
default_modelapi_base
因此应用层通常只需要给出 provider_id、api_key 和必要覆盖项。
Provider 注册表
backend/modules/providers/registry.py 当前维护 22 个 provider:
openrouteranthropicopenaideepseekmoonshotzhipugroqmistralcoheretogether_aiqwenhunyuanerniedoubaoyibaichuanminimaxvllmollamalm_studiocustom_openaicustom_anthropic
几个当前代码里的重要默认值如下:
| Provider | 默认模型 | 默认 API Base |
|---|---|---|
openai | gpt-5.3 | https://api.openai.com/v1 |
anthropic | claude-sonnet-4-20250514 | https://api.anthropic.com |
openrouter | anthropic/claude-4.5-sonnet | https://openrouter.ai/api/v1 |
zhipu | glm-4.7-flash | https://open.bigmodel.cn/api/paas/v4 |
moonshot | kimi-k2.5 | https://api.moonshot.cn/v1 |
qwen | qwen3.5-plus | https://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
这里有一个值得注意的细节:应用默认 model 是 glm-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"
当前实现特点:
- 使用官方
openaiSDK 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 - 官方
anthropicprovider 优先走 SDK minimax、custom_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 | 抽象基类、ToolCall、StreamChunk |
backend/modules/providers/registry.py | Provider 元数据注册表 |
backend/modules/providers/factory.py | Provider 工厂与分流逻辑 |
backend/modules/providers/openai_provider.py | OpenAI 兼容实现 |
backend/modules/providers/anthropic_provider.py | Anthropic 与兼容实现 |
backend/modules/providers/tool_parser.py | 工具调用解析辅助 |