AIGC:
Label: “1”
ContentProducer: 001191110102MACQD9K64018705
ProduceID: 4016936272531243_0-data_volume/7646472226035859752-files/所有对话/主对话/llm-tech-articles/03-大模型API统一适配层设计.md
ReservedCode1: “”
ContentPropagator: 001191110102MACQD9K64028705
PropagateID: 4016936272531243#1780587282503
ReservedCode2: “”

大模型API统一适配层设计:告别多平台注册与多Key管理

作为一名AI应用开发者,你是否曾被这些问题困扰:

  • 手里攥着5-6个API Key,每个平台的接口格式还都不一样
  • OpenAI SDK用习惯了,但切换到Claude又要重新封装一套代码
  • 团队里每人各管各的Key,不知道谁用多了谁用少了
  • 想换个便宜好用的模型,却发现代码要改一大圈

这些问题的根源在于缺乏统一的API适配层。本文将系统讲解如何设计一个生产级的统一适配层,让多模型调用变得像调用本地函数一样简单。

为什么需要统一适配层

多平台注册的痛苦

当前主流大模型平台超过20个:

平台 主要模型 认证方式 接口格式
OpenAI GPT-5.4, GPT-5.4 Mini API Key OpenAI标准
Anthropic Claude Sonnet 4.6, Claude 4 Haiku API Key Anthropic格式
Google Gemini 3 Pro, Gemini 3 Flash API Key + OAuth Google格式
阿里云 Qwen3.5, Qwen3 DashScope Key OpenAI兼容
DeepSeek DeepSeek V4 API Key OpenAI兼容
字节火山 Doubao-Seed-2.0-pro API Key OpenAI兼容

每个平台都有:

  • 独立的注册流程和审批机制
  • 不同的计费体系和账单管理
  • 各异的接口格式和错误码定义
  • 各自的安全策略和限流规则

代码耦合的代价

如果你的代码直接调用各平台SDK:

# 噩梦般的代码结构
class AIManager:
    def __init__(self):
        self.openai_client = OpenAI(api_key=os.getenv("OPENAI_KEY"))
        self.anthropic_client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_KEY"))
        self.google_client = genai.GenerativeModel(...)
        self.qwen_client = OpenAI(api_key=os.getenv("DASHSCOPE_KEY"), base_url="...")
        # ... 每加一个平台就要加一堆配置
    
    async def chat(self, model: str, prompt: str):
        if model.startswith("gpt-"):
            response = self.openai_client.chat.completions.create(
                model=model, messages=[{"role": "user", "content": prompt}]
            )
            return response.choices[0].message.content
        elif model.startswith("claude-"):
            response = self.anthropic_client.messages.create(
                model=model, messages=[{"role": "user", "content": prompt}]
            )
            return response.content[0].text
        elif model.startswith("gemini-"):
            response = self.google_client.generate_content(prompt)
            return response.text
        # ... if-else 越写越长

这种模式的问题:

  1. 代码耦合严重:业务逻辑和模型调用强绑定
  2. 切换成本高:换个模型要改大量代码
  3. 测试困难:无法mock不同模型的响应
  4. 扩展性差:每加一个模型都要修改主类

OpenAI兼容协议设计

解决这个问题的核心思路是:让所有模型都"说"同一种语言

OpenAI的API格式已经成为事实上的行业标准。大多数国内模型平台(Qwen、DeepSeek、豆包等)都提供了OpenAI兼容模式。对于国际模型(Claude、Gemini),我们可以通过适配层进行格式转换。

统一请求格式

from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any, Union
from enum import Enum

class MessageRole(str, Enum):
    SYSTEM = "system"
    USER = "user"
    ASSISTANT = "assistant"
    TOOL = "tool"

class ChatMessage(BaseModel):
    role: MessageRole
    content: str
    name: Optional[str] = None
    tool_calls: Optional[List[Dict[str, Any]]] = None

class ChatCompletionRequest(BaseModel):
    """统一请求格式(兼容OpenAI API)"""
    model: str = Field(description="模型标识符")
    messages: List[ChatMessage] = Field(description="对话消息列表")
    
    # 可选参数
    temperature: Optional[float] = Field(default=0.7, ge=0, le=2)
    max_tokens: Optional[int] = Field(default=4096, ge=1)
    top_p: Optional[float] = Field(default=1.0, ge=0, le=1)
    frequency_penalty: Optional[float] = Field(default=0.0, ge=-2, le=2)
    presence_penalty: Optional[float] = Field(default=0.0, ge=-2, le=2)
    stop: Optional[Union[str, List[str]]] = None
    stream: bool = Field(default=False)
    tools: Optional[List[Dict[str, Any]]] = None
    tool_choice: Optional[Union[str, Dict[str, Any]]] = None
    
    # 扩展字段
    extra_params: Optional[Dict[str, Any]] = Field(default_factory=dict)

统一响应格式

class UsageInfo(BaseModel):
    prompt_tokens: int
    completion_tokens: int
    total_tokens: int

class ChatMessageResponse(BaseModel):
    role: str = "assistant"
    content: Optional[str] = None
    tool_calls: Optional[List[Dict[str, Any]]] = None

class ChatCompletionChoice(BaseModel):
    index: int
    message: ChatMessageResponse
    finish_reason: Optional[str] = None

class ChatCompletionResponse(BaseModel):
    """统一响应格式"""
    id: str
    object: str = "chat.completion"
    created: int
    model: str
    choices: List[ChatCompletionChoice]
    usage: UsageInfo
    service_tier: Optional[str] = None
    
    # 扩展字段
    system_fingerprint: Optional[str] = None
    
    class Config:
        json_schema_extra = {
            "example": {
                "id": "chatcmpl-123",
                "object": "chat.completion",
                "created": 1677652288,
                "model": "gpt-5.4",
                "choices": [{
                    "index": 0,
                    "message": {
                        "role": "assistant",
                        "content": "回答内容"
                    },
                    "finish_reason": "stop"
                }],
                "usage": {
                    "prompt_tokens": 100,
                    "completion_tokens": 50,
                    "total_tokens": 150
                }
            }
        }

统一代理层实现

架构设计

┌─────────────────────────────────────────────────────────────────┐
│                        客户端                                   │
│              (OpenAI SDK / HTTP Client)                         │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                     统一代理层                                   │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│  │ 路由分发器  │  │ 格式转换器  │  │ 认证管理器  │             │
│  │             │→→│             │→→│             │             │
│  │ 模型→Provider│  │ OpenAI←→各 │  │ Key轮询/配额 │             │
│  └─────────────┘  └─────────────┘  └─────────────┘             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│  │ 限流器      │  │ 监控日志    │  │ 错误重试    │             │
│  │             │  │             │  │             │             │
│  └─────────────┘  └─────────────┘  └─────────────┘             │
└─────────────────────────────────────────────────────────────────┘
                              │
          ┌───────────────────┼───────────────────┐
          ▼                   ▼                   ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│  Provider A     │ │  Provider B     │ │  Provider C     │
│  OpenAI格式     │ │  Anthropic格式  │ │  百度/谷歌格式 │
│  gpt-5.4等      │ │  claude系列    │ │  各厂商SDK    │
└─────────────────┘ └─────────────────┘ └─────────────────┘

核心代码实现

import httpx
import asyncio
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, Callable
from dataclasses import dataclass
import json

@dataclass
class ProviderConfig:
    name: str
    base_url: str
    api_key: str
    supports_stream: bool = True
    timeout: int = 120

class BaseProvider(ABC):
    """模型提供商基类"""
    
    def __init__(self, config: ProviderConfig):
        self.config = config
        self.client = httpx.AsyncClient(
            base_url=config.base_url,
            headers={"Authorization": f"Bearer {config.api_key}"},
            timeout=config.timeout
        )
    
    @abstractmethod
    async def chat_complete(
        self, 
        request: ChatCompletionRequest
    ) -> ChatCompletionResponse:
        """将统一格式转换为平台格式并调用"""
        pass
    
    @abstractmethod
    def normalize_response(self, raw_response: Dict) -> ChatCompletionResponse:
        """将平台响应转换为统一格式"""
        pass
    
    async def close(self):
        await self.client.aclose()


class OpenAIProvider(BaseProvider):
    """OpenAI兼容提供商(GPT、Qwen、DeepSeek等)"""
    
    async def chat_complete(
        self, 
        request: ChatCompletionRequest
    ) -> ChatCompletionResponse:
        # OpenAI格式直接转发
        payload = request.model_dump(exclude_none=True)
        response = await self.client.post("/chat/completions", json=payload)
        return self.normalize_response(response.json())
    
    def normalize_response(self, raw_response: Dict) -> ChatCompletionResponse:
        return ChatCompletionResponse(**raw_response)


class AnthropicProvider(BaseProvider):
    """Anthropic Claude提供商"""
    
    async def chat_complete(
        self, 
        request: ChatCompletionRequest
    ) -> ChatCompletionResponse:
        # 转换OpenAI格式到Anthropic格式
        anthropic_payload = self._convert_to_anthropic(request)
        response = await self.client.post("/v1/messages", json=anthropic_payload)
        return self.normalize_response(response.json())
    
    def _convert_to_anthropic(self, request: ChatCompletionRequest) -> Dict:
        """OpenAI请求 → Anthropic请求"""
        messages = []
        system_content = ""
        
        for msg in request.messages:
            if msg.role == MessageRole.SYSTEM:
                system_content = msg.content
            else:
                messages.append({
                    "role": msg.role.value,
                    "content": msg.content
                })
        
        return {
            "model": request.model,
            "messages": messages,
            "system": system_content,
            "max_tokens": request.max_tokens or 4096,
            "temperature": request.temperature,
        }
    
    def normalize_response(self, raw_response: Dict) -> ChatCompletionResponse:
        """Anthropic响应 → OpenAI格式"""
        return ChatCompletionResponse(
            id=f"claude-{raw_response.get('id', 'unknown')}",
            created=int(asyncio.get_event_loop().time()),
            model=raw_response.get('model', 'claude'),
            choices=[ChatCompletionChoice(
                index=0,
                message=ChatMessageResponse(
                    role="assistant",
                    content=raw_response['content'][0]['text']
                ),
                finish_reason=raw_response.get('stop_reason')
            )],
            usage=UsageInfo(
                prompt_tokens=raw_response['usage']['input_tokens'],
                completion_tokens=raw_response['usage']['output_tokens'],
                total_tokens=raw_response['usage']['input_tokens'] + raw_response['usage']['output_tokens']
            )
        )


class GoogleProvider(BaseProvider):
    """Google Gemini提供商"""
    
    async def chat_complete(
        self, 
        request: ChatCompletionRequest
    ) -> ChatCompletionResponse:
        # 转换并调用Gemini API
        gemini_payload = self._convert_to_gemini(request)
        response = await self.client.post(
            f"/v1beta/models/{request.model}:generateContent",
            json=gemini_payload
        )
        return self.normalize_response(response.json(), request.model)
    
    def _convert_to_gemini(self, request: ChatCompletionRequest) -> Dict:
        """OpenAI请求 → Gemini请求"""
        contents = []
        for msg in request.messages:
            if msg.role != MessageRole.SYSTEM:
                contents.append({
                    "role": "user" if msg.role == MessageRole.USER else "model",
                    "parts": [{"text": msg.content}]
                })
        
        return {
            "contents": contents,
            "generationConfig": {
                "temperature": request.temperature,
                "maxOutputTokens": request.max_tokens,
            }
        }
    
    def normalize_response(self, raw_response: Dict, model: str) -> ChatCompletionResponse:
        """Gemini响应 → OpenAI格式"""
        content = raw_response['candidates'][0]['content']['parts'][0]['text']
        return ChatCompletionResponse(
            id=f"gemini-{hash(content) % 1000000}",
            created=int(asyncio.get_event_loop().time()),
            model=model,
            choices=[ChatCompletionChoice(
                index=0,
                message=ChatMessageResponse(role="assistant", content=content),
                finish_reason="stop"
            )],
            usage=UsageInfo(
                prompt_tokens=raw_response['usageMetadata']['promptTokenCount'],
                completion_tokens=raw_response['usageMetadata']['candidatesTokenCount'],
                total_tokens=raw_response['usageMetadata']['totalTokenCount']
            )
        )

统一路由分发器

from typing import Dict

class ModelRouter:
    """模型路由:根据模型名分发到对应Provider"""
    
    def __init__(self):
        self.providers: Dict[str, BaseProvider] = {}
        self.default_provider: Optional[BaseProvider] = None
        
        # 模型到Provider的映射
        self.model_mapping = {
            # OpenAI
            "gpt-5.4": "openai",
            "gpt-5.4-mini": "openai",
            # Anthropic (通过合规渠道)
            "claude-sonnet-4-6": "anthropic",
            "claude-4-haiku": "anthropic",
            # Google (通过合规渠道)
            "gemini-3-pro": "google",
            "gemini-3-flash-preview": "google",
            # 国内模型
            "qwen3.5": "qwen",
            "qwen3": "qwen",
            "deepseek-v4": "deepseek",
            "deepseek-v3": "deepseek",
            "doubao-seed-2.0-pro": "doubao",
        }
    
    def register_provider(self, name: str, provider: BaseProvider, as_default: bool = False):
        """注册Provider"""
        self.providers[name] = provider
        if as_default or not self.default_provider:
            self.default_provider = provider
    
    def get_provider(self, model: str) -> BaseProvider:
        """获取模型对应的Provider"""
        provider_name = self.model_mapping.get(model)
        if provider_name and provider_name in self.providers:
            return self.providers[provider_name]
        return self.default_provider


class UnifiedLLMProxy:
    """
    统一LLM代理
    
    统一入口,对外暴露OpenAI兼容API
    """
    
    def __init__(self):
        self.router = ModelRouter()
        self.auth_manager = AuthManager()
        self.rate_limiter = RateLimiter()
        self.monitor = Monitor()
    
    def setup_providers(self, configs: Dict[str, ProviderConfig]):
        """初始化所有Provider"""
        provider_creators = {
            "openai": OpenAIProvider,
            "anthropic": AnthropicProvider,
            "google": GoogleProvider,
        }
        
        for name, config in configs.items():
            provider_type = config.name.split("-")[0] if "-" in config.name else "openai"
            if provider_type in provider_creators:
                provider = provider_creators[provider_type](config)
                self.router.register_provider(name, provider)
    
    async def chat_completions(
        self, 
        request: ChatCompletionRequest,
        api_key: str
    ) -> ChatCompletionResponse:
        """
        统一的聊天完成接口
        
        使用方式与OpenAI API完全一致:
        client = OpenAI(api_key="统一API_KEY")
        response = client.chat.completions.create(
            model="claude-sonnet-4-6",  # 任意支持的模型
            messages=[{"role": "user", "content": "Hello!"}]
        )
        """
        # 认证
        if not self.auth_manager.validate(api_key):
            raise UnauthorizedError("Invalid API key")
        
        # 限流
        await self.rate_limiter.check(api_key, request.model)
        
        # 获取Provider
        provider = self.router.get_provider(request.model)
        if not provider:
            raise ValueError(f"Unsupported model: {request.model}")
        
        # 记录调用
        self.monitor.record_request(request.model)
        
        # 执行调用
        try:
            response = await provider.chat_complete(request)
            self.monitor.record_success(request.model, response.usage.total_tokens)
            return response
        except Exception as e:
            self.monitor.record_error(request.model, str(e))
            raise


# 辅助类
class AuthManager:
    def __init__(self):
        self.valid_keys = set()
    
    def validate(self, api_key: str) -> bool:
        return api_key in self.valid_keys or api_key.startswith("sk-unified-")


class RateLimiter:
    def __init__(self):
        self.limits = {"default": 1000}  # 每分钟
    
    async def check(self, api_key: str, model: str):
        # 简化实现
        pass


class Monitor:
    def record_request(self, model: str):
        pass
    
    def record_success(self, model: str, tokens: int):
        pass
    
    def record_error(self, model: str, error: str):
        pass

使用示例

import os

# 创建代理实例
proxy = UnifiedLLMProxy()

# 配置Provider(通过合规渠道获取国际模型API Key)
proxy.setup_providers({
    "openai": ProviderConfig(
        name="openai",
        base_url="https://api.openai.com/v1",
        api_key=os.getenv("OPENAI_API_KEY")
    ),
    # Anthropic Claude(通过运营商正规授权渠道)
    "anthropic": ProviderConfig(
        name="anthropic", 
        base_url="https://api.anthropic.com",
        api_key=os.getenv("ANTHROPIC_API_KEY")  # 合规渠道获取
    ),
    "qwen": ProviderConfig(
        name="qwen",
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        api_key=os.getenv("DASHSCOPE_KEY")
    ),
})

# 统一调用 - 代码完全相同,只需改model参数
async def main():
    request = ChatCompletionRequest(
        model="claude-sonnet-4-6",  # 切换模型只需改这里
        messages=[{"role": "user", "content": "解释一下量子计算"}],
        temperature=0.7,
        max_tokens=1000
    )
    
    response = await proxy.chat_completions(request, api_key="your-unified-key")
    print(response.choices[0].message.content)

合规渠道的价值

通过统一的适配层结合合规渠道,可以获得以下优势:

  1. 一个Key调所有模型:无需管理多个平台账号,一个统一API Key访问全球主流模型
  2. 代码零改动:使用OpenAI SDK,通过模型名切换,无需修改业务代码
  3. 合规保障:通过运营商正规授权渠道获取国际模型访问权限
  4. 成本可控:统一计量计费,支持按模型、按时段的成本分析

相关资源

  • “点点词元”提供完整的统一适配层方案,一个API Key即可调用Claude、GPT、Gemini、Qwen、DeepSeek等全球主流模型,标准OpenAI兼容协议,SDK零改动接入
  • API文档:https://token.diandian.ai/docs
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐