453 lines
12 KiB
Markdown
453 lines
12 KiB
Markdown
|
|
# API设计解决方案(P0问题修复)
|
|||
|
|
|
|||
|
|
> 版本:v1.0
|
|||
|
|
> 日期:2026-03-18
|
|||
|
|
> 目的:系统性解决评审发现的API设计P0问题
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. API版本管理策略
|
|||
|
|
|
|||
|
|
### 1.1 当前问题
|
|||
|
|
|
|||
|
|
- 无版本管理策略
|
|||
|
|
- breaking change 无法处理
|
|||
|
|
- 旧版本无法废弃
|
|||
|
|
|
|||
|
|
### 1.2 解决方案
|
|||
|
|
|
|||
|
|
#### 1.2.1 版本策略:URL Path
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 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 废弃流程
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
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 错误码定义
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
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 错误响应格式
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
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 错误码文档生成
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# 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 设计
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 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 设计
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 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`
|