chore: initial public snapshot for github upload
This commit is contained in:
452
docs/api_solution_v1_2026-03-18.md
Normal file
452
docs/api_solution_v1_2026-03-18.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user