Files
lijiaoqiao/docs/api_solution_v1_2026-03-18.md
2026-03-26 20:06:14 +08:00

12 KiB
Raw Blame History

API设计解决方案P0问题修复

版本v1.0 日期2026-03-18 目的系统性解决评审发现的API设计P0问题


1. API版本管理策略

1.1 当前问题

  • 无版本管理策略
  • breaking change 无法处理
  • 旧版本无法废弃

1.2 解决方案

1.2.1 版本策略URL Path

# API 版本配置
API_VERSION_CONFIG = {
    'v1': {
        'status': 'deprecated',
        'sunset_date': '2027-06-01',  # 废弃日期
        'migration_guide': '/docs/v1-migration',
        'features': ['basic_chat', 'embeddings']
    },
    'v2': {
        'status': 'active',
        'features': ['basic_chat', 'embeddings', 'streaming', 'tools']
    },
    'v3': {
        'status': 'beta',
        'features': ['basic_chat', 'embeddings', 'streaming', 'tools', 'batch']
    }
}

# 版本检查中间件
class APIVersionMiddleware:
    def process_request(self, request, handler):
        # 1. 提取版本
        path_parts = request.path.split('/')
        version = path_parts[1] if len(path_parts) > 1 else 'v1'

        # 2. 验证版本存在
        if version not in API_VERSION_CONFIG:
            return ErrorResponse(
                status=404,
                error={
                    'code': 'API_VERSION_NOT_FOUND',
                    'message': f'API version {version} not found',
                    'available_versions': list(API_VERSION_CONFIG.keys())
                }
            )

        # 3. 检查废弃状态
        config = API_VERSION_CONFIG[version]
        if config['status'] == 'deprecated':
            # 添加废弃警告头
            request.headers['Deprecation'] = f'="{config["sunset_date"]}"'
            request.headers['Link'] = f'<{config["migration_guide"]}>; rel="migration"'

        # 4. 存储版本信息
        request.api_version = version

        return handler(request)

1.2.2 废弃流程

class APIDeprecationManager:
    def __init__(self):
        self.timeline = {
            'v1': {
                'announced': '2026-03-01',
                'deprecated': '2026-06-01',
                'sunset': '2027-06-01',
                'migration_guide': '/docs/v1-migration'
            }
        }

    def handle_request(self, request):
        """处理废弃版本请求"""
        version = request.api_version
        config = API_VERSION_CONFIG[version]

        if config['status'] == 'deprecated':
            # 1. 添加警告响应头
            response.headers['Deprecation'] = 'true'
            response.headers['Sunset'] = config['sunset_date']

            # 2. 记录废弃版本使用
            metrics.increment('api.deprecated_version.used', tags={
                'version': version
            })

        return response

    def get_migration_guide(self, from_version, to_version):
        """获取迁移指南"""
        return {
            'from': from_version,
            'to': to_version,
            'breaking_changes': [
                {
                    'endpoint': '/v1/chat/completions',
                    'change': 'Response format changed',
                    'migration': 'Use response_format v2 compatibility mode'
                }
            ],
            'tools': [
                {
                    'name': 'Migration SDK',
                    'description': 'Auto-convert requests to new format',
                    'install': 'pip install lgw-migration'
                }
            ]
        }

2. 完整错误码体系

2.1 当前问题

  • 只有HTTP状态码
  • 无业务错误码
  • 错误信息不完整

2.2 解决方案

2.2.1 错误码定义

from enum import Enum

class ErrorCode(Enum):
    # 认证授权 (AUTH_*)
    AUTH_INVALID_TOKEN = ('AUTH_001', 'Invalid or expired token', 401, False)
    AUTH_INSUFFICIENT_PERMISSION = ('AUTH_002', 'Insufficient permissions', 403, False)
    AUTH_MFA_REQUIRED = ('AUTH_003', 'MFA verification required', 403, False)

    # 计费 (BILLING_*)
    BILLING_INSUFFICIENT_BALANCE = ('BILLING_001', 'Insufficient balance', 402, False)
    BILLING_CHARGE_FAILED = ('BILLING_002', 'Charge failed', 500, True)
    BILLING_REFUND_FAILED = ('BILLING_003', 'Refund failed', 500, True)
    BILLING_DISCREPANCY = ('BILLING_004', 'Billing discrepancy detected', 500, True)

    # 路由 (ROUTER_*)
    ROUTER_NO_PROVIDER_AVAILABLE = ('ROUTER_001', 'No provider available', 503, True)
    ROUTER_ALL_PROVIDERS_FAILED = ('ROUTER_002', 'All providers failed', 503, True)
    ROUTER_TIMEOUT = ('ROUTER_003', 'Request timeout', 504, True)

    # 供应商 (PROVIDER_*)
    PROVIDER_INVALID_KEY = ('PROVIDER_001', 'Invalid API key', 401, False)
    PROVIDER_RATE_LIMIT = ('PROVIDER_002', 'Rate limit exceeded', 429, False)
    PROVIDER_QUOTA_EXCEEDED = ('PROVIDER_003', 'Quota exceeded', 402, False)
    PROVIDER_MODEL_NOT_FOUND = ('PROVIDER_004', 'Model not found', 404, False)
    PROVIDER_ERROR = ('PROVIDER_005', 'Provider error', 502, True)

    # 限流 (RATE_LIMIT_*)
    RATE_LIMIT_EXCEEDED = ('RATE_LIMIT_001', 'Rate limit exceeded', 429, False)
    RATE_LIMIT_TOKEN_EXCEEDED = ('RATE_LIMIT_002', 'Token limit exceeded', 429, False)
    RATE_LIMIT_BURST_EXCEEDED = ('RATE_LIMIT_003', 'Burst limit exceeded', 429, False)

    # 通用 (COMMON_*)
    COMMON_INVALID_REQUEST = ('COMMON_001', 'Invalid request', 400, False)
    COMMON_RESOURCE_NOT_FOUND = ('COMMON_002', 'Resource not found', 404, False)
    COMMON_INTERNAL_ERROR = ('COMMON_003', 'Internal error', 500, True)
    COMMON_SERVICE_UNAVAILABLE = ('COMMON_004', 'Service unavailable', 503, True)

    def __init__(self, code, message, status_code, retryable):
        self.code = code
        self.message = message
        self.status_code = status_code
        self.retryable = retryable

2.2.2 错误响应格式

class ErrorResponse:
    def __init__(
        self,
        error_code: ErrorCode,
        message: str = None,
        details: dict = None,
        request_id: str = None,
        doc_url: str = None
    ):
        self.error = {
            'code': error_code.code,
            'message': message or error_code.message,
            'details': details or {},
            'request_id': request_id,
            'doc_url': doc_url or f'/docs/errors/{error_code.code.lower()}',
            'retryable': error_code.retryable
        }

    def to_dict(self):
        return self.error

    def to_json(self):
        return json.dumps(self.error)

# 使用示例
raise ErrorResponse(
    error_code=ErrorCode.BILLING_INSUFFICIENT_BALANCE,
    details={
        'required': 100.00,
        'available': 50.00,
        'currency': 'USD',
        'top_up_url': '/api/v1/billing/top-up'
    },
    request_id=get_request_id()
)

2.2.3 错误码文档生成

# openapi.yaml 部分
components:
  ErrorCode:
    type: object
    properties:
      code:
        type: string
        example: BILLING_001
      message:
        type: string
        example: Insufficient balance
      details:
        type: object
      request_id:
        type: string
      doc_url:
        type: string
      retryable:
        type: boolean

  errors:
    BILLING_INSUFFICIENT_BALANCE:
      status: 402
      message: "余额不足"
      details:
        required:
          type: number
          description: "所需金额"
        available:
          type: number
          description: "可用余额"
        top_up_url:
          type: string
          description: "充值链接"
      retryable: false

3. SDK 规划

3.1 当前问题

  • 无官方SDK
  • 开发者体验差

3.2 解决方案

3.2.1 SDK 路线图

Phase 1 (S1): 兼容层
├── Python SDK (OpenAI兼容)
├── Node.js SDK (OpenAI兼容)
└── 透明迁移工具

Phase 2 (S2): 自有SDK
├── Python SDK (自有API)
├── Node.js SDK (自有API)
└── Go SDK

Phase 3 (S3): 高级功能
├── 重试中间件
├── 缓存中间件
├── 指标中间件
└── 框架集成 (LangChain, LlamaIndex)

3.2.2 Python SDK 设计

# lgw-sdk-python
class LLMGateway:
    """LLM Gateway Python SDK"""

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.lgateway.com",
        timeout: float = 60.0,
        max_retries: int = 3
    ):
        self.api_key = api_key
        self.base_url = base_url
        self.timeout = timeout
        self.max_retries = max_retries
        self._session = requests.Session()

        # 默认配置
        self.default_headers = {
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        }

    def chat.completions(
        self,
        model: str,
        messages: List[Dict],
        **kwargs
    ) -> ChatCompletion:
        """聊天完成"""
        response = self._request(
            method='POST',
            path='/v1/chat/completions',
            json={
                'model': model,
                'messages': messages,
                **kwargs
            }
        )
        return ChatCompletion(**response)

    def _request(self, method, path, **kwargs):
        """发送请求(带重试)"""
        url = f"{self.base_url}{path}"
        headers = {**self.default_headers, **kwargs.pop('headers', {})}

        for attempt in range(self.max_retries):
            try:
                response = self._session.request(
                    method=method,
                    url=url,
                    headers=headers,
                    timeout=self.timeout,
                    **kwargs
                )
                response.raise_for_status()
                return response.json()

            except requests.exceptions.RequestException as e:
                if attempt == self.max_retries - 1:
                    raise
                # 指数退避
                time.sleep(2 ** attempt)

# 使用示例
client = LLMGateway(api_key="lgw-xxx")
response = client.chat.completions(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello"}]
)
print(response.choices[0].message.content)

3.2.3 Node.js SDK 设计

// lgw-sdk-node
export class LLMGateway {
  private apiKey: string;
  private baseURL: string;
  private maxRetries: number;

  constructor(config: LLMGatewayConfig) {
    this.apiKey = config.apiKey;
    this.baseURL = config.baseURL || 'https://api.lgateway.com';
    this.maxRetries = config.maxRetries || 3;
  }

  async chat.completions(
    params: ChatCompletionParams
  ): Promise<ChatCompletion> {
    const response = await this.request(
      'POST',
      '/v1/chat/completions',
      params
    );
    return response as ChatCompletion;
  }

  private async request<T>(
    method: string,
    path: string,
    body?: any,
    retries: number = 0
  ): Promise<T> {
    try {
      const response = await fetch(`${this.baseURL}${path}`, {
        method,
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
        },
        body: body ? JSON.stringify(body) : undefined,
      });

      if (!response.ok) {
        throw new LLMGatewayError(await response.json());
      }

      return response.json();
    } catch (error) {
      if (retries < this.maxRetries) {
        await this.sleep(Math.pow(2, retries));
        return this.request(method, path, body, retries + 1);
      }
      throw error;
    }
  }
}

4. 实施计划

4.1 任务分解

任务 负责人 截止 依赖
API版本管理中间件 架构 S0-M1 -
错误码体系定义 后端 S0-M1 -
错误响应格式统一 后端 S0-M1 -
Python SDK开发 前端 S1 -
Node.js SDK开发 前端 S1 -

4.2 验证标准

  • API版本可管理、可废弃
  • 所有错误都有完整错误码
  • SDK可通过pip/npm安装

文档状态API设计解决方案 关联文档

  • llm_gateway_prd_v0_2026-03-16.md