LLM Provider:统一的多模型接入层

CoPaw

CoPaw

AI应用
Qwen
Qwen3

内置vllm部署的Qwen3-4B-Instruct-2507模型,agentscope开源的类似openclaw个人助手。

玄同 765

大语言模型 (LLM) 开发工程师 | 中国传媒大学 · 数字媒体技术(智能交互与游戏设计)

CSDN · 个人主页 | GitHub · Follow


关于作者

  • 深耕领域:大语言模型开发 / 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 系统设计目标

Design Goals

Unified Interface

Easy Extension

Auto Detection

LiteLLM Integration

Standard Response

2 Steps to Add

Registry Pattern

Key Prefix

URL Keyword

核心架构

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]], ...] = ()

支持的提供者

Auxiliary

Groq - Voice

Local Deployment

vLLM

Standard Providers

Anthropic

OpenAI

DeepSeek

Gemini

Zhipu AI

DashScope/Qwen

Moonshot/Kimi

MiniMax

Gateways - Route Any Model

OpenRouter

AiHubMix

提供者类型对比

类型特点示例
Gateway可路由任意模型,通过 Key/URL 检测OpenRouter、AiHubMix
Standard特定提供者,通过模型名匹配Anthropic、OpenAI、DeepSeek
Local本地部署,需配置 api_basevLLM、Ollama
Auxiliary辅助功能,非主要 LLMGroq(语音转录)

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.pyPROVIDERS 中添加:

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.pyProvidersConfig 中添加:

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 到开源模型。


您可能感兴趣的与本文相关的镜像

CoPaw

CoPaw

AI应用
Qwen
Qwen3

内置vllm部署的Qwen3-4B-Instruct-2507模型,agentscope开源的类似openclaw个人助手。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值