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

453 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`