关于作者
- 深耕领域:大语言模型开发 / RAG 知识库 / AI Agent 落地 / 模型微调
- 技术栈:Python | RAG (LangChain / Dify + Milvus) | FastAPI + Docker
- 工程能力:专注模型工程化部署、知识库构建与优化,擅长全流程解决方案
「让 AI 交互更智能,让技术落地更高效」
欢迎技术探讨与项目合作,解锁大模型与智能交互的无限可能!
LLM Provider:统一的多模型接入层
通过 Provider Registry + LiteLLM 的组合,nanobot 实现了统一的多模型接入层,支持 10+ LLM 提供者,添加新提供者仅需 2 步。
概述
nanobot 的 LLM Provider 系统采用"注册表 + LiteLLM"的设计模式,为 Agent 提供了统一的多模型接入能力。无论是 OpenRouter、Anthropic、OpenAI,还是本地部署的 vLLM,都可以通过相同的接口调用。本文将深入剖析 LLM Provider 系统的设计与实现。
问题背景
多模型接入的核心挑战
构建一个支持多模型的 AI Agent 需要解决以下问题:
| 挑战 | 描述 | nanobot 的解决方案 |
|---|---|---|
| API 差异 | 不同提供者 API 格式不同 | LiteLLM 统一接口 |
| 认证方式 | API Key 格式和位置不同 | Provider Registry 自动配置 |
| 模型前缀 | LiteLLM 需要特定前缀 | 自动添加/跳过前缀 |
| 网关检测 | 如何识别网关类型 | Key 前缀 + URL 关键字 |
| 参数覆盖 | 某些模型需要特殊参数 | model_overrides 机制 |
Provider 系统设计目标
核心架构
Provider Registry 设计
Provider Registry 是整个系统的核心,定义了所有提供者的元数据:
@dataclass(frozen=True)
class ProviderSpec:
"""One LLM provider's metadata."""
# 身份标识
name: str # 配置字段名,如 "dashscope"
keywords: tuple[str, ...] # 模型名关键字匹配(小写)
env_key: str # LiteLLM 环境变量,如 "DASHSCOPE_API_KEY"
display_name: str = "" # 显示名称
# 模型前缀
litellm_prefix: str = "" # "dashscope" → model 变成 "dashscope/{model}"
skip_prefixes: tuple[str, ...] = () # 已有前缀时跳过
# 额外环境变量
env_extras: tuple[tuple[str, str], ...] = ()
# 网关/本地检测
is_gateway: bool = False # 是否为网关(如 OpenRouter)
is_local: bool = False # 是否为本地部署
detect_by_key_prefix: str = "" # 通过 API Key 前缀检测
detect_by_base_keyword: str = "" # 通过 URL 关键字检测
default_api_base: str = "" # 默认 API Base URL
# 网关行为
strip_model_prefix: bool = False # 重定向前是否剥离前缀
# 模型参数覆盖
model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()
支持的提供者
提供者类型对比
| 类型 | 特点 | 示例 |
|---|---|---|
| Gateway | 可路由任意模型,通过 Key/URL 检测 | OpenRouter、AiHubMix |
| Standard | 特定提供者,通过模型名匹配 | Anthropic、OpenAI、DeepSeek |
| Local | 本地部署,需配置 api_base | vLLM、Ollama |
| Auxiliary | 辅助功能,非主要 LLM | Groq(语音转录) |
Provider Registry 详解
网关类型提供者
# OpenRouter: 全球网关,Key 以 "sk-or-" 开头
ProviderSpec(
name="openrouter",
keywords=("openrouter",),
env_key="OPENROUTER_API_KEY",
display_name="OpenRouter",
litellm_prefix="openrouter",
is_gateway=True,
detect_by_key_prefix="sk-or-",
detect_by_base_keyword="openrouter",
default_api_base="https://openrouter.ai/api/v1",
)
# AiHubMix: OpenAI 兼容网关
ProviderSpec(
name="aihubmix",
keywords=("aihubmix",),
env_key="OPENAI_API_KEY",
display_name="AiHubMix",
litellm_prefix="openai",
is_gateway=True,
detect_by_base_keyword="aihubmix",
strip_model_prefix=True, # 剥离 "anthropic/" 再重新添加
)
标准提供者
# Anthropic: LiteLLM 原生支持,无需前缀
ProviderSpec(
name="anthropic",
keywords=("anthropic", "claude"),
env_key="ANTHROPIC_API_KEY",
display_name="Anthropic",
litellm_prefix="", # 无需前缀
)
# DeepSeek: 需要 "deepseek/" 前缀
ProviderSpec(
name="deepseek",
keywords=("deepseek",),
env_key="DEEPSEEK_API_KEY",
display_name="DeepSeek",
litellm_prefix="deepseek",
skip_prefixes=("deepseek/",), # 避免重复添加
)
# DashScope: Qwen 模型
ProviderSpec(
name="dashscope",
keywords=("qwen", "dashscope"),
env_key="DASHSCOPE_API_KEY",
display_name="DashScope",
litellm_prefix="dashscope",
skip_prefixes=("dashscope/", "openrouter/"),
)
带参数覆盖的提供者
# Moonshot: Kimi 模型,K2.5 强制 temperature >= 1.0
ProviderSpec(
name="moonshot",
keywords=("moonshot", "kimi"),
env_key="MOONSHOT_API_KEY",
display_name="Moonshot",
litellm_prefix="moonshot",
env_extras=(
("MOONSHOT_API_BASE", "{api_base}"),
),
default_api_base="https://api.moonshot.ai/v1",
model_overrides=(
("kimi-k2.5", {"temperature": 1.0}),
),
)
本地部署提供者
# vLLM: 本地 OpenAI 兼容服务器
ProviderSpec(
name="vllm",
keywords=("vllm",),
env_key="HOSTED_VLLM_API_KEY",
display_name="vLLM/Local",
litellm_prefix="hosted_vllm",
is_local=True,
# api_base 必须由用户在配置中提供
)
LiteLLMProvider 实现
初始化流程
class LiteLLMProvider(LLMProvider):
"""LLM provider using LiteLLM for multi-provider support."""
def __init__(
self,
api_key: str | None = None,
api_base: str | None = None,
default_model: str = "anthropic/claude-opus-4-5",
extra_headers: dict[str, str] | None = None,
provider_name: str | None = None,
):
super().__init__(api_key, api_base)
self.default_model = default_model
self.extra_headers = extra_headers or {}
# 检测网关/本地部署
self._gateway = find_gateway(provider_name, api_key, api_base)
# 配置环境变量
if api_key:
self._setup_env(api_key, api_base, default_model)
# 禁用 LiteLLM 日志噪音
litellm.suppress_debug_info = True
litellm.drop_params = True
环境变量配置
def _setup_env(self, api_key: str, api_base: str | None, model: str) -> None:
"""Set environment variables based on detected provider."""
spec = self._gateway or find_by_model(model)
if not spec:
return
# 网关/本地覆盖现有环境变量
if self._gateway:
os.environ[spec.env_key] = api_key
else:
os.environ.setdefault(spec.env_key, api_key)
# 解析 env_extras 占位符
effective_base = api_base or spec.default_api_base
for env_name, env_val in spec.env_extras:
resolved = env_val.replace("{api_key}", api_key)
resolved = resolved.replace("{api_base}", effective_base)
os.environ.setdefault(env_name, resolved)
模型名解析
def _resolve_model(self, model: str) -> str:
"""Resolve model name by applying provider/gateway prefixes."""
if self._gateway:
# 网关模式:应用网关前缀
prefix = self._gateway.litellm_prefix
if self._gateway.strip_model_prefix:
model = model.split("/")[-1]
if prefix and not model.startswith(f"{prefix}/"):
model = f"{prefix}/{model}"
return model
# 标准模式:自动添加提供者前缀
spec = find_by_model(model)
if spec and spec.litellm_prefix:
if not any(model.startswith(s) for s in spec.skip_prefixes):
model = f"{spec.litellm_prefix}/{model}"
return model
参数覆盖
def _apply_model_overrides(self, model: str, kwargs: dict[str, Any]) -> None:
"""Apply model-specific parameter overrides from the registry."""
model_lower = model.lower()
spec = find_by_model(model)
if spec:
for pattern, overrides in spec.model_overrides:
if pattern in model_lower:
kwargs.update(overrides)
return
Chat 调用
async def chat(
self,
messages: list[dict[str, Any]],
tools: list[dict[str, Any]] | None = None,
model: str | None = None,
max_tokens: int = 4096,
temperature: float = 0.7,
) -> LLMResponse:
"""Send a chat completion request via LiteLLM."""
model = self._resolve_model(model or self.default_model)
kwargs: dict[str, Any] = {
"model": model,
"messages": messages,
"max_tokens": max_tokens,
"temperature": temperature,
}
# 应用模型特定覆盖
self._apply_model_overrides(model, kwargs)
# 直接传递 api_key(比环境变量更可靠)
if self.api_key:
kwargs["api_key"] = self.api_key
# 传递 api_base 用于自定义端点
if self.api_base:
kwargs["api_base"] = self.api_base
# 传递额外头部(如 AiHubMix 的 APP-Code)
if self.extra_headers:
kwargs["extra_headers"] = self.extra_headers
if tools:
kwargs["tools"] = tools
kwargs["tool_choice"] = "auto"
try:
response = await acompletion(**kwargs)
return self._parse_response(response)
except Exception as e:
return LLMResponse(
content=f"Error calling LLM: {str(e)}",
finish_reason="error",
)
查找辅助函数
按模型名查找
def find_by_model(model: str) -> ProviderSpec | None:
"""Match a standard provider by model-name keyword."""
model_lower = model.lower()
for spec in PROVIDERS:
if spec.is_gateway or spec.is_local:
continue
if any(kw in model_lower for kw in spec.keywords):
return spec
return None
检测网关
def find_gateway(
provider_name: str | None = None,
api_key: str | None = None,
api_base: str | None = None,
) -> ProviderSpec | None:
"""Detect gateway/local provider.
Priority:
1. provider_name - 配置键直接匹配
2. api_key prefix - 如 "sk-or-" → OpenRouter
3. api_base keyword - 如 URL 中含 "aihubmix"
"""
# 1. 配置键直接匹配
if provider_name:
spec = find_by_name(provider_name)
if spec and (spec.is_gateway or spec.is_local):
return spec
# 2. 自动检测
for spec in PROVIDERS:
if spec.detect_by_key_prefix and api_key and api_key.startswith(spec.detect_by_key_prefix):
return spec
if spec.detect_by_base_keyword and api_base and spec.detect_by_base_keyword in api_base:
return spec
return None
配置示例
OpenRouter(推荐)
{
"providers": {
"openrouter": {
"apiKey": "sk-or-v1-xxx"
}
},
"agents": {
"defaults": {
"model": "anthropic/claude-opus-4-5"
}
}
}
本地 vLLM
{
"providers": {
"vllm": {
"apiKey": "dummy",
"apiBase": "http://localhost:8000/v1"
}
},
"agents": {
"defaults": {
"model": "meta-llama/Llama-3.1-8B-Instruct"
}
}
}
DeepSeek 直连
{
"providers": {
"deepseek": {
"apiKey": "sk-xxx"
}
},
"agents": {
"defaults": {
"model": "deepseek-chat"
}
}
}
添加新提供者
只需 2 步即可添加新的 LLM 提供者:
Step 1: 添加 ProviderSpec
在 nanobot/providers/registry.py 的 PROVIDERS 中添加:
ProviderSpec(
name="myprovider",
keywords=("myprovider", "mymodel"),
env_key="MYPROVIDER_API_KEY",
display_name="My Provider",
litellm_prefix="myprovider",
skip_prefixes=("myprovider/",),
)
Step 2: 添加配置字段
在 nanobot/config/schema.py 的 ProvidersConfig 中添加:
class ProvidersConfig(BaseModel):
# ... 现有字段
myprovider: ProviderConfig = ProviderConfig()
完成!环境变量、模型前缀、配置匹配、状态显示都会自动工作。
设计亮点
| 设计点 | 实现 | 优势 |
|---|---|---|
| 单一数据源 | Provider Registry | 所有元数据集中管理 |
| 自动检测 | Key 前缀 + URL 关键字 | 无需手动指定网关类型 |
| 智能前缀 | litellm_prefix + skip_prefixes | 避免重复添加前缀 |
| 参数覆盖 | model_overrides | 处理模型特殊要求 |
| 占位符解析 | {api_key}, {api_base} | 灵活配置环境变量 |
总结
nanobot 的 LLM Provider 系统通过 Provider Registry + LiteLLM 的组合,实现了统一的多模型接入层:
| 特性 | 实现 |
|---|---|
| 统一接口 | LiteLLM 封装所有提供者差异 |
| 自动配置 | Registry 自动设置环境变量和前缀 |
| 网关检测 | Key 前缀 + URL 关键字自动识别 |
| 易于扩展 | 2 步添加新提供者 |
| 灵活覆盖 | 支持模型级别的参数覆盖 |
这种设计使得 nanobot 能够无缝支持各种 LLM 提供者,从全球网关到本地部署,从商业 API 到开源模型。

250

被折叠的 条评论
为什么被折叠?



