feat: 添加访问令牌管理功能并升级至 0.2.4

- 新增 Management Token(访问令牌)功能,支持创建、更新、删除和管理
- 前端添加访问令牌管理页面,支持普通用户和管理员
- 后端实现完整的令牌生命周期管理 API
- 添加数据库迁移脚本创建 management_tokens 表
- Nginx 配置添加 gzip 压缩,优化响应传输
- Dialog 组件添加 persistent 属性,防止意外关闭
- 为管理后台 API 添加详细的中文文档注释
- 简化多处类型注解,统一代码风格
This commit is contained in:
fawney19
2026-01-07 14:55:07 +08:00
parent f6a6410626
commit 0061fc04b7
59 changed files with 6265 additions and 648 deletions

View File

@@ -6,6 +6,7 @@ from .adaptive import router as adaptive_router
from .api_keys import router as api_keys_router
from .endpoints import router as endpoints_router
from .ldap import router as ldap_router
from .management_tokens import router as management_tokens_router
from .models import router as models_router
from .monitoring import router as monitoring_router
from .provider_query import router as provider_query_router
@@ -30,5 +31,6 @@ router.include_router(models_router)
router.include_router(security_router)
router.include_router(provider_query_router)
router.include_router(ldap_router)
router.include_router(management_tokens_router)
__all__ = ["router"]

View File

@@ -73,7 +73,26 @@ async def list_standalone_api_keys(
is_active: Optional[bool] = None,
db: Session = Depends(get_db),
):
"""列出所有独立余额API Keys"""
"""
列出所有独立余额 API Keys
获取系统中所有独立余额 API Key 的列表。独立余额 Key 不关联用户配额,
有独立的余额限制,主要用于给非注册用户使用。
**查询参数**:
- `skip`: 跳过的记录数(分页偏移量),默认 0
- `limit`: 返回的记录数(分页限制),默认 100最大 500
- `is_active`: 可选根据启用状态筛选true/false
**返回字段**:
- `api_keys`: API Key 列表,包含 id, name, key_display, is_active, current_balance_usd,
balance_used_usd, total_requests, total_cost_usd, rate_limit, allowed_providers,
allowed_api_formats, allowed_models, last_used_at, expires_at, created_at, updated_at,
auto_delete_on_expiry 等字段
- `total`: 符合条件的总记录数
- `limit`: 当前分页限制
- `skip`: 当前分页偏移量
"""
adapter = AdminListStandaloneKeysAdapter(skip=skip, limit=limit, is_active=is_active)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -84,7 +103,35 @@ async def create_standalone_api_key(
key_data: CreateApiKeyRequest,
db: Session = Depends(get_db),
):
"""创建独立余额API Key必须设置余额限制"""
"""
创建独立余额 API Key
创建一个新的独立余额 API Key。独立余额 Key 必须设置初始余额限制。
**请求体字段**:
- `name`: API Key 的名称
- `initial_balance_usd`: 必需,初始余额(美元),必须大于 0
- `allowed_providers`: 可选,允许使用的提供商列表
- `allowed_api_formats`: 可选,允许使用的 API 格式列表
- `allowed_models`: 可选,允许使用的模型列表
- `rate_limit`: 可选,速率限制配置(请求数/秒)
- `expire_days`: 可选,过期天数(兼容旧版)
- `expires_at`: 可选过期时间ISO 格式或 YYYY-MM-DD 格式,优先级高于 expire_days
- `auto_delete_on_expiry`: 可选,过期后是否自动删除
**返回字段**:
- `id`: API Key ID
- `key`: 完整的 API Key仅在创建时返回一次
- `name`: API Key 名称
- `key_display`: 脱敏显示的 Key
- `is_standalone`: 是否为独立余额 Key始终为 true
- `current_balance_usd`: 当前余额
- `balance_used_usd`: 已使用余额
- `rate_limit`: 速率限制配置
- `expires_at`: 过期时间
- `created_at`: 创建时间
- `message`: 提示信息
"""
adapter = AdminCreateStandaloneKeyAdapter(key_data=key_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -93,20 +140,72 @@ async def create_standalone_api_key(
async def update_api_key(
key_id: str, request: Request, key_data: CreateApiKeyRequest, db: Session = Depends(get_db)
):
"""更新独立余额Key可修改名称、过期时间、余额限制等"""
"""
更新独立余额 API Key
更新指定 ID 的独立余额 API Key 的配置信息。
**路径参数**:
- `key_id`: API Key ID
**请求体字段**:
- `name`: 可选API Key 的名称
- `rate_limit`: 可选速率限制配置null 表示无限制)
- `allowed_providers`: 可选,允许使用的提供商列表
- `allowed_api_formats`: 可选,允许使用的 API 格式列表
- `allowed_models`: 可选,允许使用的模型列表
- `expire_days`: 可选,过期天数(兼容旧版)
- `expires_at`: 可选过期时间ISO 格式或 YYYY-MM-DD 格式,优先级高于 expire_daysnull 或空字符串表示永不过期)
- `auto_delete_on_expiry`: 可选,过期后是否自动删除
**返回字段**:
- `id`: API Key ID
- `name`: API Key 名称
- `key_display`: 脱敏显示的 Key
- `is_active`: 是否启用
- `current_balance_usd`: 当前余额
- `balance_used_usd`: 已使用余额
- `rate_limit`: 速率限制配置
- `expires_at`: 过期时间
- `updated_at`: 更新时间
- `message`: 提示信息
"""
adapter = AdminUpdateApiKeyAdapter(key_id=key_id, key_data=key_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/{key_id}")
async def toggle_api_key(key_id: str, request: Request, db: Session = Depends(get_db)):
"""Toggle API key active status (PATCH with is_active in body)"""
"""
切换 API Key 启用状态
切换指定 API Key 的启用/禁用状态。
**路径参数**:
- `key_id`: API Key ID
**返回字段**:
- `id`: API Key ID
- `is_active`: 新的启用状态
- `message`: 提示信息
"""
adapter = AdminToggleApiKeyAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{key_id}")
async def delete_api_key(key_id: str, request: Request, db: Session = Depends(get_db)):
"""
删除 API Key
删除指定的 API Key。此操作不可逆。
**路径参数**:
- `key_id`: API Key ID
**返回字段**:
- `message`: 提示信息
"""
adapter = AdminDeleteApiKeyAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -117,7 +216,24 @@ async def add_balance_to_key(
request: Request,
db: Session = Depends(get_db),
):
"""Adjust balance for standalone API key (positive to add, negative to deduct)"""
"""
调整独立余额 API Key 的余额
为指定的独立余额 API Key 增加或扣除余额。
**路径参数**:
- `key_id`: API Key ID
**请求体字段**:
- `amount_usd`: 调整金额(美元),正数为充值,负数为扣除
**返回字段**:
- `id`: API Key ID
- `name`: API Key 名称
- `current_balance_usd`: 调整后的当前余额
- `balance_used_usd`: 已使用余额
- `message`: 提示信息
"""
# 从请求体获取调整金额
body = await request.json()
amount_usd = body.get("amount_usd")
@@ -162,7 +278,24 @@ async def get_api_key_detail(
include_key: bool = Query(False, description="Include full decrypted key in response"),
db: Session = Depends(get_db),
):
"""Get API key detail, optionally include full key"""
"""
获取 API Key 详情
获取指定 API Key 的详细信息。可选择是否返回完整的解密密钥。
**路径参数**:
- `key_id`: API Key ID
**查询参数**:
- `include_key`: 是否包含完整的解密密钥,默认 false
**返回字段**:
- 当 include_key=false 时返回基本信息id, user_id, name, key_display, is_active,
is_standalone, current_balance_usd, balance_used_usd, total_requests, total_cost_usd,
rate_limit, allowed_providers, allowed_api_formats, allowed_models, last_used_at,
expires_at, created_at, updated_at
- 当 include_key=true 时返回完整密钥key
"""
if include_key:
adapter = AdminGetFullKeyAdapter(key_id=key_id)
else:

View File

@@ -7,7 +7,7 @@ from .health import router as health_router
from .keys import router as keys_router
from .routes import router as routes_router
router = APIRouter(prefix="/api/admin/endpoints", tags=["Endpoint Management"])
router = APIRouter(prefix="/api/admin/endpoints", tags=["Admin - Endpoints"])
# Endpoint CRUD
router.include_router(routes_router)

View File

@@ -29,7 +29,19 @@ async def get_endpoint_concurrency(
request: Request,
db: Session = Depends(get_db),
) -> ConcurrencyStatusResponse:
"""获取 Endpoint 当前并发状态"""
"""
获取 Endpoint 当前并发状态
查询指定 Endpoint 的实时并发使用情况,包括当前并发数和最大并发限制。
**路径参数**:
- `endpoint_id`: Endpoint ID
**返回字段**:
- `endpoint_id`: Endpoint ID
- `endpoint_current_concurrency`: 当前并发数
- `endpoint_max_concurrent`: 最大并发限制
"""
adapter = AdminEndpointConcurrencyAdapter(endpoint_id=endpoint_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -40,7 +52,19 @@ async def get_key_concurrency(
request: Request,
db: Session = Depends(get_db),
) -> ConcurrencyStatusResponse:
"""获取 Key 当前并发状态"""
"""
获取 Key 当前并发状态
查询指定 API Key 的实时并发使用情况,包括当前并发数和最大并发限制。
**路径参数**:
- `key_id`: API Key ID
**返回字段**:
- `key_id`: API Key ID
- `key_current_concurrency`: 当前并发数
- `key_max_concurrent`: 最大并发限制
"""
adapter = AdminKeyConcurrencyAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -51,7 +75,19 @@ async def reset_concurrency(
http_request: Request,
db: Session = Depends(get_db),
) -> dict:
"""Reset concurrency counters (admin function, use with caution)"""
"""
重置并发计数器
重置指定 Endpoint 或 Key 的并发计数器,用于解决计数不准确的问题。
管理员功能,请谨慎使用。
**请求体字段**:
- `endpoint_id`: Endpoint ID可选
- `key_id`: API Key ID可选
**返回字段**:
- `message`: 操作结果消息
"""
adapter = AdminResetConcurrencyAdapter(endpoint_id=request.endpoint_id, key_id=request.key_id)
return await pipeline.run(adapter=adapter, http_request=http_request, db=db, mode=adapter.mode)

View File

@@ -36,7 +36,20 @@ async def get_health_summary(
request: Request,
db: Session = Depends(get_db),
) -> HealthSummaryResponse:
"""获取健康状态摘要"""
"""
获取健康状态摘要
获取系统整体健康状态摘要,包括所有 Provider、Endpoint 和 Key 的健康状态统计。
**返回字段**:
- `total_providers`: Provider 总数
- `active_providers`: 活跃 Provider 数量
- `total_endpoints`: Endpoint 总数
- `active_endpoints`: 活跃 Endpoint 数量
- `total_keys`: Key 总数
- `active_keys`: 活跃 Key 数量
- `circuit_breaker_open_keys`: 熔断的 Key 数量
"""
adapter = AdminHealthSummaryAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -50,9 +63,21 @@ async def get_endpoint_health_status(
"""
获取端点健康状态(简化视图,与用户端点统一)
获取按 API 格式聚合的端点健康状态时间线,基于 Usage 表统计,
返回 50 个时间段的聚合状态,适用于快速查看整体健康趋势。
与 /health/api-formats 的区别:
- /health/status: 返回聚合的时间线状态50个时间段基于 Usage 表
- /health/api-formats: 返回详细的事件列表,基于 RequestCandidate 表
**查询参数**:
- `lookback_hours`: 回溯的小时数1-72默认 6
**返回字段**:
- `api_format`: API 格式名称
- `timeline`: 时间线数据50个时间段
- `time_range_start`: 时间范围起始
- `time_range_end`: 时间范围结束
"""
adapter = AdminEndpointHealthStatusAdapter(lookback_hours=lookback_hours)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -65,7 +90,33 @@ async def get_api_format_health_monitor(
per_format_limit: int = Query(60, ge=10, le=200, description="每个 API 格式的事件数量"),
db: Session = Depends(get_db),
) -> ApiFormatHealthMonitorResponse:
"""获取按 API 格式聚合的健康监控时间线(详细事件列表)"""
"""
获取按 API 格式聚合的健康监控时间线(详细事件列表)
获取每个 API 格式的详细健康监控数据,包括请求事件列表、成功率统计、
时间线数据等,基于 RequestCandidate 表查询,适用于详细分析。
**查询参数**:
- `lookback_hours`: 回溯的小时数1-72默认 6
- `per_format_limit`: 每个 API 格式返回的事件数量10-200默认 60
**返回字段**:
- `generated_at`: 数据生成时间
- `formats`: API 格式健康监控数据列表
- `api_format`: API 格式名称
- `total_attempts`: 总请求数
- `success_count`: 成功请求数
- `failed_count`: 失败请求数
- `skipped_count`: 跳过请求数
- `success_rate`: 成功率
- `provider_count`: Provider 数量
- `key_count`: Key 数量
- `last_event_at`: 最后事件时间
- `events`: 事件列表
- `timeline`: 时间线数据
- `time_range_start`: 时间范围起始
- `time_range_end`: 时间范围结束
"""
adapter = AdminApiFormatHealthMonitorAdapter(
lookback_hours=lookback_hours,
per_format_limit=per_format_limit,
@@ -79,7 +130,26 @@ async def get_key_health(
request: Request,
db: Session = Depends(get_db),
) -> HealthStatusResponse:
"""获取 Key 健康状态"""
"""
获取 Key 健康状态
获取指定 API Key 的健康状态详情,包括健康分数、连续失败次数、
熔断器状态等信息。
**路径参数**:
- `key_id`: API Key ID
**返回字段**:
- `key_id`: API Key ID
- `key_health_score`: 健康分数0.0-1.0
- `key_consecutive_failures`: 连续失败次数
- `key_last_failure_at`: 最后失败时间
- `key_is_active`: 是否活跃
- `key_statistics`: 统计信息
- `circuit_breaker_open`: 熔断器是否打开
- `circuit_breaker_open_at`: 熔断器打开时间
- `next_probe_at`: 下次探测时间
"""
adapter = AdminKeyHealthAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -91,13 +161,20 @@ async def recover_key_health(
db: Session = Depends(get_db),
) -> dict:
"""
Recover key health status
恢复 Key 健康状态
Resets health_score to 1.0, closes circuit breaker,
cancels auto-disable, and resets all failure counts.
手动恢复指定 Key 的健康状态,将健康分数重置为 1.0,关闭熔断器,
取消自动禁用,并重置所有失败计数。
Parameters:
- key_id: Key ID (path parameter)
**路径参数**:
- `key_id`: API Key ID
**返回字段**:
- `message`: 操作结果消息
- `details`: 详细信息
- `health_score`: 健康分数
- `circuit_breaker_open`: 熔断器状态
- `is_active`: 是否活跃
"""
adapter = AdminRecoverKeyHealthAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -109,12 +186,21 @@ async def recover_all_keys_health(
db: Session = Depends(get_db),
) -> dict:
"""
Batch recover all circuit-broken keys
批量恢复所有熔断 Key 的健康状态
Finds all keys with circuit_breaker_open=True and:
1. Resets health_score to 1.0
2. Closes circuit breaker
3. Resets failure counts
查找所有处于熔断状态的 Keycircuit_breaker_open=True
并批量执行以下操作:
1. 将健康分数重置为 1.0
2. 关闭熔断器
3. 重置失败计数
**返回字段**:
- `message`: 操作结果消息
- `recovered_count`: 恢复的 Key 数量
- `recovered_keys`: 恢复的 Key 列表
- `key_id`: Key ID
- `key_name`: Key 名称
- `endpoint_id`: Endpoint ID
"""
adapter = AdminRecoverAllKeysHealthAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -37,7 +37,33 @@ async def list_endpoint_keys(
limit: int = Query(100, ge=1, le=1000, description="返回的最大记录数"),
db: Session = Depends(get_db),
) -> List[EndpointAPIKeyResponse]:
"""获取 Endpoint 的所有 Keys"""
"""
获取 Endpoint 的所有 Keys
获取指定 Endpoint 下的所有 API Key 列表,包括 Key 的配置、统计信息等。
结果按优先级和创建时间排序。
**路径参数**:
- `endpoint_id`: Endpoint ID
**查询参数**:
- `skip`: 跳过的记录数,用于分页(默认 0
- `limit`: 返回的最大记录数1-1000默认 100
**返回字段**:
- `id`: Key ID
- `name`: Key 名称
- `api_key_masked`: 脱敏后的 API Key
- `internal_priority`: 内部优先级
- `global_priority`: 全局优先级
- `rate_multiplier`: 速率倍数
- `max_concurrent`: 最大并发数null 表示自适应模式)
- `is_adaptive`: 是否为自适应并发模式
- `effective_limit`: 有效并发限制
- `success_rate`: 成功率
- `avg_response_time_ms`: 平均响应时间(毫秒)
- 其他配置和统计字段
"""
adapter = AdminListEndpointKeysAdapter(
endpoint_id=endpoint_id,
skip=skip,
@@ -53,7 +79,32 @@ async def add_endpoint_key(
request: Request,
db: Session = Depends(get_db),
) -> EndpointAPIKeyResponse:
"""为 Endpoint 添加 Key"""
"""
为 Endpoint 添加 Key
为指定 Endpoint 添加新的 API Key支持配置并发限制、速率倍数、
优先级、配额限制、能力限制等。
**路径参数**:
- `endpoint_id`: Endpoint ID
**请求体字段**:
- `endpoint_id`: Endpoint ID必须与路径参数一致
- `api_key`: API Key 原文(将被加密存储)
- `name`: Key 名称
- `note`: 备注(可选)
- `rate_multiplier`: 速率倍数(默认 1.0
- `internal_priority`: 内部优先级(默认 100
- `max_concurrent`: 最大并发数null 表示自适应模式)
- `rate_limit`: 每分钟请求限制(可选)
- `daily_limit`: 每日请求限制(可选)
- `monthly_limit`: 每月请求限制(可选)
- `allowed_models`: 允许的模型列表(可选)
- `capabilities`: 能力配置(可选)
**返回字段**:
- 包含完整的 Key 信息,其中 `api_key_plain` 为原文(仅在创建时返回)
"""
adapter = AdminCreateEndpointKeyAdapter(endpoint_id=endpoint_id, key_data=key_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -65,7 +116,32 @@ async def update_endpoint_key(
request: Request,
db: Session = Depends(get_db),
) -> EndpointAPIKeyResponse:
"""更新 Endpoint Key"""
"""
更新 Endpoint Key
更新指定 Key 的配置,支持修改并发限制、速率倍数、优先级、
配额限制、能力限制等。支持部分更新。
**路径参数**:
- `key_id`: Key ID
**请求体字段**(均为可选):
- `api_key`: 新的 API Key 原文
- `name`: Key 名称
- `note`: 备注
- `rate_multiplier`: 速率倍数
- `internal_priority`: 内部优先级
- `max_concurrent`: 最大并发数(设置为 null 可切换到自适应模式)
- `rate_limit`: 每分钟请求限制
- `daily_limit`: 每日请求限制
- `monthly_limit`: 每月请求限制
- `allowed_models`: 允许的模型列表
- `capabilities`: 能力配置
- `is_active`: 是否活跃
**返回字段**:
- 包含更新后的完整 Key 信息
"""
adapter = AdminUpdateEndpointKeyAdapter(key_id=key_id, key_data=key_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -75,7 +151,31 @@ async def get_keys_grouped_by_format(
request: Request,
db: Session = Depends(get_db),
) -> dict:
"""获取按 API 格式分组的所有 Keys用于全局优先级管理"""
"""
获取按 API 格式分组的所有 Keys
获取所有活跃的 Key按 API 格式分组返回,用于全局优先级管理。
每个 Key 包含基本信息、健康度指标、能力标签等。
**返回字段**:
- 返回一个字典,键为 API 格式,值为该格式下的 Key 列表
- 每个 Key 包含:
- `id`: Key ID
- `name`: Key 名称
- `api_key_masked`: 脱敏后的 API Key
- `internal_priority`: 内部优先级
- `global_priority`: 全局优先级
- `rate_multiplier`: 速率倍数
- `is_active`: 是否活跃
- `circuit_breaker_open`: 熔断器状态
- `provider_name`: Provider 名称
- `endpoint_base_url`: Endpoint 基础 URL
- `api_format`: API 格式
- `capabilities`: 能力简称列表
- `success_rate`: 成功率
- `avg_response_time_ms`: 平均响应时间
- `request_count`: 请求总数
"""
adapter = AdminGetKeysGroupedByFormatAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -86,7 +186,18 @@ async def reveal_endpoint_key(
request: Request,
db: Session = Depends(get_db),
) -> dict:
"""获取完整的 API Key用于查看和复制"""
"""
获取完整的 API Key
解密并返回指定 Key 的完整原文,用于查看和复制。
此操作会被记录到审计日志。
**路径参数**:
- `key_id`: Key ID
**返回字段**:
- `api_key`: 完整的 API Key 原文
"""
adapter = AdminRevealEndpointKeyAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -97,7 +208,17 @@ async def delete_endpoint_key(
request: Request,
db: Session = Depends(get_db),
) -> dict:
"""删除 Endpoint Key"""
"""
删除 Endpoint Key
删除指定的 API Key。此操作不可逆请谨慎使用。
**路径参数**:
- `key_id`: Key ID
**返回字段**:
- `message`: 操作结果消息
"""
adapter = AdminDeleteEndpointKeyAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -109,7 +230,24 @@ async def batch_update_key_priority(
priority_data: BatchUpdateKeyPriorityRequest,
db: Session = Depends(get_db),
) -> dict:
"""批量更新 Endpoint 下 Keys 的优先级(用于拖动排序)"""
"""
批量更新 Endpoint 下 Keys 的优先级
批量更新指定 Endpoint 下多个 Key 的内部优先级,用于拖动排序。
所有 Key 必须属于指定的 Endpoint。
**路径参数**:
- `endpoint_id`: Endpoint ID
**请求体字段**:
- `priorities`: 优先级列表
- `key_id`: Key ID
- `internal_priority`: 新的内部优先级
**返回字段**:
- `message`: 操作结果消息
- `updated_count`: 实际更新的 Key 数量
"""
adapter = AdminBatchUpdateKeyPriorityAdapter(endpoint_id=endpoint_id, priority_data=priority_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -45,7 +45,36 @@ async def list_provider_endpoints(
limit: int = Query(100, ge=1, le=1000, description="返回的最大记录数"),
db: Session = Depends(get_db),
) -> List[ProviderEndpointResponse]:
"""获取指定 Provider 的所有 Endpoints"""
"""
获取指定 Provider 的所有 Endpoints
获取指定 Provider 下的所有 Endpoint 列表,包括配置、统计信息等。
结果按创建时间倒序排列。
**路径参数**:
- `provider_id`: Provider ID
**查询参数**:
- `skip`: 跳过的记录数,用于分页(默认 0
- `limit`: 返回的最大记录数1-1000默认 100
**返回字段**:
- `id`: Endpoint ID
- `provider_id`: Provider ID
- `provider_name`: Provider 名称
- `api_format`: API 格式
- `base_url`: 基础 URL
- `custom_path`: 自定义路径
- `timeout`: 超时时间(秒)
- `max_retries`: 最大重试次数
- `max_concurrent`: 最大并发数
- `rate_limit`: 速率限制
- `is_active`: 是否活跃
- `total_keys`: Key 总数
- `active_keys`: 活跃 Key 数量
- `proxy`: 代理配置(密码已脱敏)
- 其他配置字段
"""
adapter = AdminListProviderEndpointsAdapter(
provider_id=provider_id,
skip=skip,
@@ -61,7 +90,31 @@ async def create_provider_endpoint(
request: Request,
db: Session = Depends(get_db),
) -> ProviderEndpointResponse:
"""为 Provider 创建新的 Endpoint"""
"""
为 Provider 创建新的 Endpoint
为指定 Provider 创建新的 Endpoint每个 Provider 的每种 API 格式
只能创建一个 Endpoint。
**路径参数**:
- `provider_id`: Provider ID
**请求体字段**:
- `provider_id`: Provider ID必须与路径参数一致
- `api_format`: API 格式(如 claude、openai、gemini 等)
- `base_url`: 基础 URL
- `custom_path`: 自定义路径(可选)
- `headers`: 自定义请求头(可选)
- `timeout`: 超时时间(秒,默认 300
- `max_retries`: 最大重试次数(默认 2
- `max_concurrent`: 最大并发数(可选)
- `rate_limit`: 速率限制(可选)
- `config`: 额外配置(可选)
- `proxy`: 代理配置(可选)
**返回字段**:
- 包含完整的 Endpoint 信息
"""
adapter = AdminCreateProviderEndpointAdapter(
provider_id=provider_id,
endpoint_data=endpoint_data,
@@ -75,7 +128,31 @@ async def get_endpoint(
request: Request,
db: Session = Depends(get_db),
) -> ProviderEndpointResponse:
"""获取 Endpoint 详情"""
"""
获取 Endpoint 详情
获取指定 Endpoint 的详细信息,包括配置、统计信息等。
**路径参数**:
- `endpoint_id`: Endpoint ID
**返回字段**:
- `id`: Endpoint ID
- `provider_id`: Provider ID
- `provider_name`: Provider 名称
- `api_format`: API 格式
- `base_url`: 基础 URL
- `custom_path`: 自定义路径
- `timeout`: 超时时间(秒)
- `max_retries`: 最大重试次数
- `max_concurrent`: 最大并发数
- `rate_limit`: 速率限制
- `is_active`: 是否活跃
- `total_keys`: Key 总数
- `active_keys`: 活跃 Key 数量
- `proxy`: 代理配置(密码已脱敏)
- 其他配置字段
"""
adapter = AdminGetProviderEndpointAdapter(endpoint_id=endpoint_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -87,7 +164,29 @@ async def update_endpoint(
request: Request,
db: Session = Depends(get_db),
) -> ProviderEndpointResponse:
"""更新 Endpoint"""
"""
更新 Endpoint
更新指定 Endpoint 的配置。支持部分更新。
**路径参数**:
- `endpoint_id`: Endpoint ID
**请求体字段**(均为可选):
- `base_url`: 基础 URL
- `custom_path`: 自定义路径
- `headers`: 自定义请求头
- `timeout`: 超时时间(秒)
- `max_retries`: 最大重试次数
- `max_concurrent`: 最大并发数
- `rate_limit`: 速率限制
- `is_active`: 是否活跃
- `config`: 额外配置
- `proxy`: 代理配置(设置为 null 可清除代理)
**返回字段**:
- 包含更新后的完整 Endpoint 信息
"""
adapter = AdminUpdateProviderEndpointAdapter(
endpoint_id=endpoint_id,
endpoint_data=endpoint_data,
@@ -101,7 +200,19 @@ async def delete_endpoint(
request: Request,
db: Session = Depends(get_db),
) -> dict:
"""删除 Endpoint级联删除所有关联的 Keys"""
"""
删除 Endpoint
删除指定的 Endpoint同时级联删除所有关联的 API Keys。
此操作不可逆,请谨慎使用。
**路径参数**:
- `endpoint_id`: Endpoint ID
**返回字段**:
- `message`: 操作结果消息
- `deleted_keys_count`: 同时删除的 Key 数量
"""
adapter = AdminDeleteProviderEndpointAdapter(endpoint_id=endpoint_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -166,21 +166,95 @@ class LDAPConfigTest(BaseModel):
@router.get("/config")
async def get_ldap_config(request: Request, db: Session = Depends(get_db)) -> Any:
"""获取LDAP配置管理员"""
"""
获取 LDAP 配置
获取系统当前的 LDAP 认证配置信息,用于管理界面显示和编辑。
密码字段不会返回原文,仅返回是否已设置的标志。
**返回字段**:
- `server_url`: LDAP 服务器地址ldap://ldap.example.com:389
- `bind_dn`: 绑定 DNcn=admin,dc=example,dc=com
- `base_dn`: 搜索基准 DNou=users,dc=example,dc=com
- `has_bind_password`: 是否已设置绑定密码(布尔值)
- `user_search_filter`: 用户搜索过滤器(默认:(uid={username})
- `username_attr`: 用户名属性默认uid
- `email_attr`: 邮箱属性默认mail
- `display_name_attr`: 显示名称属性默认cn
- `is_enabled`: 是否启用 LDAP 认证
- `is_exclusive`: 是否仅允许 LDAP 登录(独占模式)
- `use_starttls`: 是否使用 STARTTLS 加密连接
- `connect_timeout`: 连接超时时间1-60
"""
adapter = AdminGetLDAPConfigAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/config")
async def update_ldap_config(request: Request, db: Session = Depends(get_db)) -> Any:
"""更新LDAP配置管理员"""
"""
更新 LDAP 配置
更新系统的 LDAP 认证配置。支持完整配置更新,包括连接参数、
搜索过滤器、属性映射等。提供多重安全校验,防止误锁定管理员。
**请求体字段**:
- `server_url`: LDAP 服务器地址必填1-255字符
- `bind_dn`: 绑定 DN必填1-255字符
- `bind_password`: 绑定密码(可选,设为空字符串可清除密码)
- `base_dn`: 搜索基准 DN必填1-255字符
- `user_search_filter`: 用户搜索过滤器(必须包含 {username} 占位符,默认:(uid={username})
- `username_attr`: 用户名属性默认uid
- `email_attr`: 邮箱属性默认mail
- `display_name_attr`: 显示名称属性默认cn
- `is_enabled`: 是否启用 LDAP 认证
- `is_exclusive`: 是否仅允许 LDAP 登录(需先启用 LDAP
- `use_starttls`: 是否使用 STARTTLS 加密连接
- `connect_timeout`: 连接超时时间1-60默认 10
**安全校验**:
- 启用 LDAP 时必须设置有效的绑定密码
- 启用独占模式前会检查是否有至少 1 个有效的本地管理员账户
- 独占模式要求先启用 LDAP 认证
- 搜索过滤器必须包含 {username} 占位符且括号匹配
- 搜索过滤器嵌套层数不超过 5 层,长度不超过 200 字符
**返回字段**:
- `message`: 操作结果消息
"""
adapter = AdminUpdateLDAPConfigAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/test")
async def test_ldap_connection(request: Request, db: Session = Depends(get_db)) -> Any:
"""测试LDAP连接管理员"""
"""
测试 LDAP 连接
在保存配置前测试 LDAP 服务器连接是否正常。支持使用已保存的配置,
也支持通过请求体覆盖任意配置项进行临时测试,而不影响已保存的配置。
**请求体字段**(均为可选,用于临时覆盖):
- `server_url`: LDAP 服务器地址(覆盖已保存的配置)
- `bind_dn`: 绑定 DN覆盖已保存的配置
- `bind_password`: 绑定密码(覆盖已保存的密码)
- `base_dn`: 搜索基准 DN覆盖已保存的配置
- `user_search_filter`: 用户搜索过滤器(覆盖已保存的配置)
- `username_attr`: 用户名属性(覆盖已保存的配置)
- `email_attr`: 邮箱属性(覆盖已保存的配置)
- `display_name_attr`: 显示名称属性(覆盖已保存的配置)
- `use_starttls`: 是否使用 STARTTLS覆盖已保存的配置
- `connect_timeout`: 连接超时时间(覆盖已保存的配置)
**测试逻辑**:
- 未提供的字段使用已保存的配置值
- `bind_password` 优先使用请求体中的值,否则使用已保存的加密密码
- 测试时会尝试连接 LDAP 服务器并验证绑定 DN
**返回字段**:
- `success`: 测试是否成功(布尔值)
- `message`: 测试结果消息(成功或失败原因)
"""
adapter = AdminTestLDAPConnectionAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -0,0 +1,10 @@
"""Management Token 管理员路由模块"""
from fastapi import APIRouter
from .routes import router as management_tokens_router
router = APIRouter()
router.include_router(management_tokens_router)
__all__ = ["router"]

View File

@@ -0,0 +1,300 @@
"""管理员 Management Token 管理端点"""
from dataclasses import dataclass
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session
from src.api.base.admin_adapter import AdminApiAdapter
from src.api.base.context import ApiRequestContext
from src.api.base.pipeline import ApiRequestPipeline
from src.core.exceptions import NotFoundException
from src.database import get_db
from src.models.database import AuditEventType, ManagementToken, User
from src.services.management_token import ManagementTokenService, token_to_dict
router = APIRouter(prefix="/api/admin/management-tokens", tags=["Admin - Management Tokens"])
pipeline = ApiRequestPipeline()
# ============== 安全基类 ==============
class AdminManagementTokenApiAdapter(AdminApiAdapter):
"""管理员 Management Token 管理 API 的基类
安全限制:禁止使用 Management Token 调用这些接口。
"""
def authorize(self, context: ApiRequestContext) -> None:
# 先调用父类的认证和权限检查
super().authorize(context)
# 禁止使用 Management Token 调用 management-tokens 相关接口
if context.management_token is not None:
raise HTTPException(
status_code=403,
detail="不允许使用 Management Token 管理其他 Token请使用 Web 界面或 JWT 认证",
)
# ============== 路由 ==============
@router.get("")
async def list_all_management_tokens(
request: Request,
user_id: Optional[str] = Query(None, description="筛选用户 ID"),
is_active: Optional[bool] = Query(None, description="筛选激活状态"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
db: Session = Depends(get_db),
):
"""列出所有 Management Tokens管理员
管理员查看所有用户的 Management Tokens支持筛选和分页。
**查询参数**
- user_id (Optional[str]): 筛选指定用户 ID 的 tokens
- is_active (Optional[bool]): 筛选激活状态true/false
- skip (int): 分页偏移量,默认 0
- limit (int): 每页数量,范围 1-100默认 50
**返回字段**
- items (List[dict]): Token 列表
- id (str): Token ID
- user_id (str): 所属用户 ID
- user (dict): 用户信息(包含 id, username, email 等)
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值(不返回明文)
- is_active (bool): 是否激活
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间ISO 8601 格式)
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
- total (int): 总数量
- skip (int): 当前偏移量
- limit (int): 当前每页数量
"""
adapter = AdminListManagementTokensAdapter(
user_id=user_id, is_active=is_active, skip=skip, limit=limit
)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/{token_id}")
async def get_management_token(
token_id: str,
request: Request,
db: Session = Depends(get_db),
):
"""获取 Management Token 详情(管理员)
管理员查看任意 Management Token 的详细信息。
**路径参数**
- token_id (str): Token ID
**返回字段**
- id (str): Token ID
- user_id (str): 所属用户 ID
- user (dict): 用户信息(包含 id, username, email 等)
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值(不返回明文)
- is_active (bool): 是否激活
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间ISO 8601 格式)
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
"""
adapter = AdminGetManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{token_id}")
async def delete_management_token(
token_id: str, request: Request, db: Session = Depends(get_db)
):
"""删除任意 Management Token管理员
管理员可以删除任意用户的 Management Token。
**路径参数**
- token_id (str): 要删除的 Token ID
**返回字段**
- message (str): 操作结果消息
"""
adapter = AdminDeleteManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/{token_id}/status")
async def toggle_management_token(
token_id: str, request: Request, db: Session = Depends(get_db)
):
"""切换任意 Management Token 状态(管理员)
管理员可以启用/禁用任意用户的 Management Token。
**路径参数**
- token_id (str): Token ID
**返回字段**
- message (str): 操作结果消息("Token 已启用""Token 已禁用"
- data (dict): 更新后的 Token 信息
- id (str): Token ID
- user_id (str): 所属用户 ID
- user (dict): 用户信息
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值
- is_active (bool): 是否激活(已切换后的状态)
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
"""
adapter = AdminToggleManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
# ============== 适配器 ==============
@dataclass
class AdminListManagementTokensAdapter(AdminManagementTokenApiAdapter):
"""列出所有 Management Tokens"""
name: str = "admin_list_management_tokens"
user_id: Optional[str] = None
is_active: Optional[bool] = None
skip: int = 0
limit: int = 50
async def handle(self, context: ApiRequestContext):
# 构建查询
query = context.db.query(ManagementToken)
if self.user_id:
query = query.filter(ManagementToken.user_id == self.user_id)
if self.is_active is not None:
query = query.filter(ManagementToken.is_active == self.is_active)
total = query.count()
tokens = (
query.order_by(ManagementToken.created_at.desc())
.offset(self.skip)
.limit(self.limit)
.all()
)
# 预加载用户信息
user_ids = list(set(t.user_id for t in tokens))
users = {u.id: u for u in context.db.query(User).filter(User.id.in_(user_ids)).all()}
for token in tokens:
token.user = users.get(token.user_id)
return JSONResponse(
content={
"items": [token_to_dict(t, include_user=True) for t in tokens],
"total": total,
"skip": self.skip,
"limit": self.limit,
}
)
@dataclass
class AdminGetManagementTokenAdapter(AdminManagementTokenApiAdapter):
"""获取 Management Token 详情"""
name: str = "admin_get_management_token"
token_id: str = ""
async def handle(self, context: ApiRequestContext):
token = ManagementTokenService.get_token_by_id(
db=context.db, token_id=self.token_id
)
if not token:
raise NotFoundException("Management Token 不存在")
# 加载用户信息
token.user = context.db.query(User).filter(User.id == token.user_id).first()
return JSONResponse(content=token_to_dict(token, include_user=True))
@dataclass
class AdminDeleteManagementTokenAdapter(AdminManagementTokenApiAdapter):
"""删除 Management Token"""
name: str = "admin_delete_management_token"
token_id: str = ""
audit_success_event = AuditEventType.MANAGEMENT_TOKEN_DELETED
async def handle(self, context: ApiRequestContext):
# 先获取 token 信息用于审计
token = ManagementTokenService.get_token_by_id(
db=context.db, token_id=self.token_id
)
if not token:
raise NotFoundException("Management Token 不存在")
context.add_audit_metadata(
token_id=token.id,
token_name=token.name,
owner_user_id=token.user_id,
)
success = ManagementTokenService.delete_token(
db=context.db, token_id=self.token_id
)
if not success:
raise NotFoundException("Management Token 不存在")
return JSONResponse(content={"message": "删除成功"})
@dataclass
class AdminToggleManagementTokenAdapter(AdminManagementTokenApiAdapter):
"""切换 Management Token 状态"""
name: str = "admin_toggle_management_token"
token_id: str = ""
audit_success_event = AuditEventType.MANAGEMENT_TOKEN_UPDATED
async def handle(self, context: ApiRequestContext):
token = ManagementTokenService.toggle_status(
db=context.db, token_id=self.token_id
)
if not token:
raise NotFoundException("Management Token 不存在")
# 加载用户信息
token.user = context.db.query(User).filter(User.id == token.user_id).first()
context.add_audit_metadata(
token_id=token.id,
token_name=token.name,
owner_user_id=token.user_id,
is_active=token.is_active,
)
return JSONResponse(
content={
"message": f"Token 已{'启用' if token.is_active else '禁用'}",
"data": token_to_dict(token, include_user=True),
}
)

View File

@@ -8,7 +8,7 @@ from .catalog import router as catalog_router
from .external import router as external_router
from .global_models import router as global_models_router
router = APIRouter(prefix="/api/admin/models", tags=["Admin - Model Management"])
router = APIRouter(prefix="/api/admin/models", tags=["Admin - Models"])
# 挂载子路由
router.include_router(catalog_router)

View File

@@ -31,6 +31,22 @@ async def get_model_catalog(
request: Request,
db: Session = Depends(get_db),
) -> ModelCatalogResponse:
"""
获取统一模型目录
基于 GlobalModel 聚合所有活跃模型及其关联提供商的信息,返回完整的模型目录视图。
**返回字段**:
- `models`: 模型列表,每个模型包含:
- `global_model_name`: GlobalModel 名称
- `display_name`: 显示名称
- `description`: 模型描述
- `providers`: 提供商列表,包含提供商名称、价格、能力等详细信息
- `price_range`: 价格区间(基于 GlobalModel 第一阶梯价格)
- `total_providers`: 关联提供商数量
- `capabilities`: 模型能力标志(视觉、函数调用、流式输出)
- `total`: 模型总数
"""
adapter = AdminGetModelCatalogAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -82,9 +82,21 @@ def _mark_official_providers(data: dict[str, Any]) -> dict[str, Any]:
@router.get("/external")
async def get_external_models(_: User = Depends(require_admin)) -> JSONResponse:
"""
获取 models.dev 的模型数据(代理请求,解决跨域问题)
数据缓存 15 分钟(使用 Redis多 worker 共享)
每个提供商会标记 official 字段,前端可据此过滤
获取外部模型数据
从 models.dev 获取第三方模型数据,用于导入新模型或参考定价信息。
该接口作为代理请求解决跨域问题,并提供缓存优化。
**功能特性**:
- 代理 models.dev API解决前端跨域问题
- 使用 Redis 缓存 15 分钟,多 worker 共享缓存
- 自动标记官方提供商official 字段),前端可据此过滤第三方转售商
**返回字段**:
- 键为提供商 ID"anthropic""openai"
- 值为提供商详细信息,包含:
- `official`: 是否为官方提供商true/false
- 其他 models.dev 提供的原始字段(模型列表、定价等)
"""
# 检查缓存
cached = await _get_cached_data()
@@ -130,7 +142,16 @@ async def get_external_models(_: User = Depends(require_admin)) -> JSONResponse:
@router.delete("/external/cache")
async def clear_external_models_cache(_: User = Depends(require_admin)) -> dict:
"""清除 models.dev 缓存"""
"""
清除外部模型数据缓存
手动清除 models.dev 的 Redis 缓存,强制下次请求重新获取最新数据。
通常用于需要立即更新外部模型数据的场景。
**返回字段**:
- `cleared`: 是否成功清除缓存true/false
- `message`: 提示信息(仅在 Redis 未启用时返回)
"""
redis = await get_redis_client()
if redis is None:
return {"cleared": False, "message": "Redis 未启用"}

View File

@@ -40,7 +40,27 @@ async def list_global_models(
search: Optional[str] = Query(None),
db: Session = Depends(get_db),
) -> GlobalModelListResponse:
"""获取 GlobalModel 列表"""
"""
获取 GlobalModel 列表
查询系统中的全局模型列表,支持分页、过滤和搜索功能。
**查询参数**:
- `skip`: 跳过记录数,用于分页(默认 0
- `limit`: 返回记录数,用于分页(默认 100最大 1000
- `is_active`: 过滤活跃状态true/false/nullnull 表示不过滤)
- `search`: 搜索关键词,支持按名称或显示名称模糊搜索
**返回字段**:
- `models`: GlobalModel 列表,每个包含:
- `id`: GlobalModel ID
- `name`: 模型名称(唯一)
- `display_name`: 显示名称
- `is_active`: 是否活跃
- `provider_count`: 关联提供商数量
- 定价和能力配置等其他字段
- `total`: 返回的模型总数
"""
adapter = AdminListGlobalModelsAdapter(
skip=skip,
limit=limit,
@@ -56,7 +76,21 @@ async def get_global_model(
global_model_id: str,
db: Session = Depends(get_db),
) -> GlobalModelWithStats:
"""获取单个 GlobalModel 详情(含统计信息)"""
"""
获取单个 GlobalModel 详情
查询指定 GlobalModel 的详细信息,包含关联的提供商和价格统计数据。
**路径参数**:
- `global_model_id`: GlobalModel ID
**返回字段**:
- 基础字段:`id`, `name`, `display_name`, `is_active` 等
- 统计字段:
- `total_models`: 关联的 Model 实现数量
- `total_providers`: 关联的提供商数量
- `price_range`: 价格区间统计(最低/最高输入输出价格)
"""
adapter = AdminGetGlobalModelAdapter(global_model_id=global_model_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -67,7 +101,24 @@ async def create_global_model(
payload: GlobalModelCreate,
db: Session = Depends(get_db),
) -> GlobalModelResponse:
"""创建 GlobalModel"""
"""
创建 GlobalModel
创建一个新的全局模型定义,作为多个提供商实现的统一抽象。
**请求体字段**:
- `name`: 模型名称(唯一标识,如 "claude-3-5-sonnet-20241022"
- `display_name`: 显示名称(如 "Claude 3.5 Sonnet"
- `is_active`: 是否活跃(默认 true
- `default_price_per_request`: 默认按次计费价格(可选)
- `default_tiered_pricing`: 默认阶梯定价配置(包含多个价格阶梯)
- `supported_capabilities`: 支持的能力标志vision、function_calling、streaming
- `config`: 额外配置JSON 格式,如 description、context_window 等)
**返回字段**:
- `id`: 创建的 GlobalModel ID
- 其他请求体中的所有字段
"""
adapter = AdminCreateGlobalModelAdapter(payload=payload)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -79,7 +130,26 @@ async def update_global_model(
payload: GlobalModelUpdate,
db: Session = Depends(get_db),
) -> GlobalModelResponse:
"""更新 GlobalModel"""
"""
更新 GlobalModel
更新指定 GlobalModel 的配置信息,支持部分字段更新。
更新后会自动失效相关缓存。
**路径参数**:
- `global_model_id`: GlobalModel ID
**请求体字段**(均为可选):
- `display_name`: 显示名称
- `is_active`: 是否活跃
- `default_price_per_request`: 默认按次计费价格
- `default_tiered_pricing`: 默认阶梯定价配置
- `supported_capabilities`: 支持的能力标志
- `config`: 额外配置
**返回字段**:
- 更新后的完整 GlobalModel 信息
"""
adapter = AdminUpdateGlobalModelAdapter(global_model_id=global_model_id, payload=payload)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -90,7 +160,18 @@ async def delete_global_model(
global_model_id: str,
db: Session = Depends(get_db),
):
"""删除 GlobalModel级联删除所有关联的 Provider 模型实现)"""
"""
删除 GlobalModel
删除指定的 GlobalModel会级联删除所有关联的 Provider 模型实现。
删除后会自动失效相关缓存。
**路径参数**:
- `global_model_id`: GlobalModel ID
**返回**:
- 成功删除返回 204 状态码,无响应体
"""
adapter = AdminDeleteGlobalModelAdapter(global_model_id=global_model_id)
await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
return None
@@ -105,7 +186,29 @@ async def batch_assign_to_providers(
payload: BatchAssignToProvidersRequest,
db: Session = Depends(get_db),
) -> BatchAssignToProvidersResponse:
"""批量为多个 Provider 添加 GlobalModel 实现"""
"""
批量为提供商添加模型实现
为指定的 GlobalModel 批量创建多个 Provider 的模型实现Model 记录)。
用于快速将一个统一模型分配给多个提供商。
**路径参数**:
- `global_model_id`: GlobalModel ID
**请求体字段**:
- `provider_ids`: 提供商 ID 列表
- `create_models`: Model 创建配置列表,每个包含:
- `provider_id`: 提供商 ID
- `provider_model_name`: 提供商侧的模型名称(如 "claude-3-5-sonnet-20241022"
- 其他可选字段(价格覆盖、能力覆盖等)
**返回字段**:
- `success`: 成功创建的 Model 列表
- `errors`: 失败的提供商及错误信息列表
- `total_requested`: 请求处理的总数
- `total_success`: 成功创建的数量
- `total_errors`: 失败的数量
"""
adapter = AdminBatchAssignToProvidersAdapter(global_model_id=global_model_id, payload=payload)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -116,7 +219,27 @@ async def get_global_model_providers(
global_model_id: str,
db: Session = Depends(get_db),
) -> GlobalModelProvidersResponse:
"""获取 GlobalModel 的所有关联提供商(包括非活跃的)"""
"""
获取 GlobalModel 的关联提供商
查询指定 GlobalModel 的所有关联提供商及其模型实现详情,包括非活跃的提供商。
用于查看某个统一模型在各个提供商上的具体配置。
**路径参数**:
- `global_model_id`: GlobalModel ID
**返回字段**:
- `providers`: 提供商列表,每个包含:
- `provider_id`: 提供商 ID
- `provider_name`: 提供商名称
- `provider_display_name`: 提供商显示名称
- `model_id`: Model 实现 ID
- `target_model`: 提供商侧的模型名称
- 价格信息input_price_per_1m、output_price_per_1m 等)
- 能力标志supports_vision、supports_function_calling、supports_streaming
- `is_active`: 是否活跃
- `total`: 关联提供商总数
"""
adapter = AdminGetGlobalModelProvidersAdapter(global_model_id=global_model_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -39,6 +39,34 @@ async def get_audit_logs(
offset: int = Query(0, description="偏移量"),
db: Session = Depends(get_db),
):
"""
获取审计日志
获取系统审计日志列表,支持按用户、事件类型、时间范围筛选。需要管理员权限。
**查询参数**:
- `user_id`: 可选,用户 ID 筛选UUID 格式)
- `event_type`: 可选,事件类型筛选
- `days`: 查询最近多少天的日志,默认 7 天
- `limit`: 返回数量限制,默认 100
- `offset`: 分页偏移量,默认 0
**返回字段**:
- `items`: 审计日志列表,每条日志包含:
- `id`: 日志 ID
- `event_type`: 事件类型
- `user_id`: 用户 ID
- `user_email`: 用户邮箱
- `user_username`: 用户名
- `description`: 事件描述
- `ip_address`: IP 地址
- `status_code`: HTTP 状态码
- `error_message`: 错误信息
- `metadata`: 事件元数据
- `created_at`: 创建时间
- `meta`: 分页元数据total, limit, offset, count
- `filters`: 筛选条件
"""
adapter = AdminGetAuditLogsAdapter(
user_id=user_id,
event_type=event_type,
@@ -51,6 +79,19 @@ async def get_audit_logs(
@router.get("/system-status")
async def get_system_status(request: Request, db: Session = Depends(get_db)):
"""
获取系统状态
获取系统当前的运行状态和关键指标。需要管理员权限。
**返回字段**:
- `timestamp`: 当前时间戳
- `users`: 用户统计total: 总用户数, active: 活跃用户数)
- `providers`: 提供商统计total: 总提供商数, active: 活跃提供商数)
- `api_keys`: API Key 统计total: 总数, active: 活跃数)
- `today_stats`: 今日统计requests: 请求数, tokens: token 数, cost_usd: 成本)
- `recent_errors`: 最近 1 小时内的错误数
"""
adapter = AdminSystemStatusAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -61,6 +102,26 @@ async def get_suspicious_activities(
hours: int = Query(24, description="时间范围(小时)"),
db: Session = Depends(get_db),
):
"""
获取可疑活动记录
获取系统检测到的可疑活动记录。需要管理员权限。
**查询参数**:
- `hours`: 时间范围(小时),默认 24 小时
**返回字段**:
- `activities`: 可疑活动列表,每条记录包含:
- `id`: 记录 ID
- `event_type`: 事件类型
- `user_id`: 用户 ID
- `description`: 事件描述
- `ip_address`: IP 地址
- `metadata`: 事件元数据
- `created_at`: 创建时间
- `count`: 活动总数
- `time_range_hours`: 查询的时间范围(小时)
"""
adapter = AdminSuspiciousActivitiesAdapter(hours=hours)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -72,19 +133,56 @@ async def analyze_user_behavior(
days: int = Query(30, description="分析天数"),
db: Session = Depends(get_db),
):
"""
分析用户行为
分析指定用户的行为模式和使用情况。需要管理员权限。
**路径参数**:
- `user_id`: 用户 ID
**查询参数**:
- `days`: 分析最近多少天的数据,默认 30 天
**返回字段**:
- 用户行为分析结果,包括活动频率、使用模式、异常行为等
"""
adapter = AdminUserBehaviorAdapter(user_id=user_id, days=days)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/resilience-status")
async def get_resilience_status(request: Request, db: Session = Depends(get_db)):
"""
获取韧性系统状态
获取系统韧性管理的当前状态,包括错误统计、熔断器状态等。需要管理员权限。
**返回字段**:
- `timestamp`: 当前时间戳
- `health_score`: 健康评分0-100
- `status`: 系统状态healthy: 健康degraded: 降级critical: 严重)
- `error_statistics`: 错误统计信息
- `recent_errors`: 最近的错误列表(最多 10 条)
- `recommendations`: 系统建议
"""
adapter = AdminResilienceStatusAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/resilience/error-stats")
async def reset_error_stats(request: Request, db: Session = Depends(get_db)):
"""Reset resilience error statistics"""
"""
重置错误统计
重置韧性系统的错误统计数据。需要管理员权限。
**返回字段**:
- `message`: 操作结果信息
- `previous_stats`: 重置前的统计数据
- `reset_by`: 执行重置的管理员邮箱
- `reset_at`: 重置时间
"""
adapter = AdminResetErrorStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -95,6 +193,18 @@ async def get_circuit_history(
limit: int = Query(50, ge=1, le=200),
db: Session = Depends(get_db),
):
"""
获取熔断器历史记录
获取熔断器的状态变更历史记录。需要管理员权限。
**查询参数**:
- `limit`: 返回数量限制,默认 50最大 200
**返回字段**:
- `items`: 熔断器历史记录列表
- `count`: 记录总数
"""
adapter = AdminCircuitHistoryAdapter(limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -117,12 +117,21 @@ async def get_cache_stats(
"""
获取缓存亲和性统计信息
返回:
- 缓存命中率
- 缓存用户数
- Provider切换次数
- Key切换次数
- 缓存预留配置
获取缓存调度器的运行统计数据,包括命中率、切换次数、调度器配置等。
用于监控缓存亲和性功能的运行状态和性能指标。
**返回字段**:
- `status`: 状态ok
- `data`: 统计数据对象
- `scheduler`: 调度器名称cache_aware 或 random
- `total_affinities`: 总缓存亲和性数量
- `cache_hit_rate`: 缓存命中率0.0-1.0
- `provider_switches`: Provider 切换次数
- `key_switches`: Key 切换次数
- `cache_hits`: 缓存命中次数
- `cache_misses`: 缓存未命中次数
- `scheduler_metrics`: 调度器详细指标
- `affinity_stats`: 亲和性统计数据
"""
adapter = AdminCacheStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -137,16 +146,33 @@ async def get_user_affinity(
"""
查询指定用户的所有缓存亲和性
参数:
- user_identifier: 用户标识符,支持以下格式:
* 用户名 (username),如: yuanhonghu
* 邮箱 (email),如: user@example.com
* 用户UUID (user_id),如: 550e8400-e29b-41d4-a716-446655440000
* API Key ID如: 660e8400-e29b-41d4-a716-446655440000
根据用户标识符查询该用户在各个端点上的缓存亲和性记录。
支持多种标识符格式的自动识别和解析。
返回:
- 用户信息
- 所有端点的缓存亲和性列表(每个端点一条记录)
**路径参数**:
- `user_identifier`: 用户标识符,支持以下格式:
- 用户名usernameyuanhonghu
- 邮箱emailuser@example.com
- 用户 UUIDuser_id550e8400-e29b-41d4-a716-446655440000
- API Key ID660e8400-e29b-41d4-a716-446655440000
**返回字段**:
- `status`: 状态ok 或 not_found
- `message`: 提示消息(当无缓存时)
- `user_info`: 用户信息
- `user_id`: 用户 ID
- `username`: 用户名
- `email`: 邮箱
- `affinities`: 缓存亲和性列表
- `provider_id`: Provider ID
- `endpoint_id`: Endpoint ID
- `key_id`: Key ID
- `api_format`: API 格式
- `model_name`: 模型名称global_model_id
- `created_at`: 创建时间
- `expire_at`: 过期时间
- `request_count`: 请求计数
- `total_endpoints`: 缓存的端点数量
"""
adapter = AdminGetUserAffinityAdapter(user_identifier=user_identifier)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -161,10 +187,50 @@ async def list_affinities(
db: Session = Depends(get_db),
) -> Any:
"""
获取所有缓存亲和性列表,可选按关键词过滤
获取所有缓存亲和性列表
参数:
- keyword: 可选,支持用户名/邮箱/User ID/API Key ID 或模糊匹配
查询系统中所有的缓存亲和性记录,支持按关键词过滤和分页。
返回详细的用户、Provider、Endpoint、Key 信息。
**查询参数**:
- `keyword`: 可选,支持以下过滤方式(可选)
- 用户名/邮箱/User ID/API Key ID精确匹配
- 任意字段的模糊匹配affinity_key、user_id、username、email、provider_id、key_id
- `limit`: 返回数量限制1-1000默认 100
- `offset`: 偏移量(用于分页,默认 0
**返回字段**:
- `status`: 状态ok
- `data`: 分页数据对象
- `items`: 缓存亲和性列表
- `affinity_key`: API Key ID用于缓存键
- `user_api_key_name`: 用户 API Key 名称
- `user_api_key_prefix`: 脱敏后的用户 API Key
- `is_standalone`: 是否为独立 API Key
- `user_id`: 用户 ID
- `username`: 用户名
- `email`: 邮箱
- `provider_id`: Provider ID
- `provider_name`: Provider 显示名称
- `endpoint_id`: Endpoint ID
- `endpoint_api_format`: Endpoint API 格式
- `endpoint_url`: Endpoint 基础 URL
- `key_id`: Key ID
- `key_name`: Key 名称
- `key_prefix`: 脱敏后的 Provider Key
- `rate_multiplier`: 速率倍数
- `global_model_id`: GlobalModel ID
- `model_name`: 模型名称
- `model_display_name`: 模型显示名称
- `api_format`: API 格式
- `created_at`: 创建时间
- `expire_at`: 过期时间
- `request_count`: 请求计数
- `meta`: 分页元数据
- `count`: 总数量
- `limit`: 每页数量
- `offset`: 当前偏移量
- `matched_user_id`: 匹配到的用户 ID当关键词为用户标识时
"""
adapter = AdminListAffinitiesAdapter(keyword=keyword, limit=limit, offset=offset)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -177,10 +243,27 @@ async def clear_user_cache(
db: Session = Depends(get_db),
) -> Any:
"""
Clear cache affinity for a specific user
清除指定用户的缓存亲和性
Parameters:
- user_identifier: User identifier (username, email, user_id, or API Key ID)
清除指定用户或 API Key 的所有缓存亲和性记录。
支持按用户维度或单个 API Key 维度清除。
**路径参数**:
- `user_identifier`: 用户标识符,支持以下格式:
- 用户名username
- 邮箱email
- 用户 UUIDuser_id
- API Key ID清除该 API Key 的缓存)
**返回字段**:
- `status`: 状态ok
- `message`: 操作结果消息
- `user_info`: 用户信息
- `user_id`: 用户 ID
- `username`: 用户名
- `email`: 邮箱
- `api_key_id`: API Key ID当清除单个 API Key 时)
- `api_key_name`: API Key 名称(当清除单个 API Key 时)
"""
adapter = AdminClearUserCacheAdapter(user_identifier=user_identifier)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -196,13 +279,23 @@ async def clear_single_affinity(
db: Session = Depends(get_db),
) -> Any:
"""
Clear a single cache affinity entry
清除单条缓存亲和性记录
Parameters:
- affinity_key: API Key ID
- endpoint_id: Endpoint ID
- model_id: Model ID (GlobalModel ID)
- api_format: API format (claude/openai)
根据精确的缓存键affinity_key + endpoint_id + model_id + api_format
清除单条缓存亲和性记录。用于精确控制缓存清除。
**路径参数**:
- `affinity_key`: API Key ID用于缓存的键
- `endpoint_id`: Endpoint ID
- `model_id`: GlobalModel ID
- `api_format`: API 格式claude、openai、gemini
**返回字段**:
- `status`: 状态ok
- `message`: 操作结果消息
- `affinity_key`: API Key ID
- `endpoint_id`: Endpoint ID
- `model_id`: GlobalModel ID
"""
adapter = AdminClearSingleAffinityAdapter(
affinity_key=affinity_key, endpoint_id=endpoint_id, model_id=model_id, api_format=api_format
@@ -216,9 +309,17 @@ async def clear_all_cache(
db: Session = Depends(get_db),
) -> Any:
"""
Clear all cache affinities
清除所有缓存亲和性
Warning: This affects all users, use with caution
清除系统中所有用户的缓存亲和性记录。此操作会影响所有用户,
下次请求时将重新建立缓存亲和性。请谨慎使用。
**警告**: 此操作影响所有用户,使用前请确认
**返回字段**:
- `status`: 状态ok
- `message`: 操作结果消息
- `count`: 清除的缓存数量
"""
adapter = AdminClearAllCacheAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -231,10 +332,19 @@ async def clear_provider_cache(
db: Session = Depends(get_db),
) -> Any:
"""
Clear cache affinities for a specific provider
清除指定 Provider 的缓存亲和性
Parameters:
- provider_id: Provider ID
清除与指定 Provider 相关的所有缓存亲和性记录。
当 Provider 配置变更或下线时使用。
**路径参数**:
- `provider_id`: Provider ID
**返回字段**:
- `status`: 状态ok
- `message`: 操作结果消息
- `provider_id`: Provider ID
- `count`: 清除的缓存数量
"""
adapter = AdminClearProviderCacheAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -248,9 +358,25 @@ async def get_cache_config(
"""
获取缓存相关配置
返回:
- 缓存TTL
- 缓存预留比例
获取缓存亲和性功能的配置参数,包括缓存 TTL、预留比例、
动态预留机制配置等。
**返回字段**:
- `status`: 状态ok
- `data`: 配置数据
- `cache_ttl_seconds`: 缓存亲和性有效期(秒)
- `cache_reservation_ratio`: 静态预留比例(已被动态预留替代)
- `dynamic_reservation`: 动态预留机制配置
- `enabled`: 是否启用
- `config`: 配置参数
- `probe_phase_requests`: 探测阶段请求数阈值
- `probe_reservation`: 探测阶段预留比例
- `stable_min_reservation`: 稳定阶段最小预留比例
- `stable_max_reservation`: 稳定阶段最大预留比例
- `low_load_threshold`: 低负载阈值
- `high_load_threshold`: 高负载阈值
- `description`: 各参数说明
- `description`: 配置说明
"""
adapter = AdminCacheConfigAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -262,7 +388,31 @@ async def get_cache_metrics(
db: Session = Depends(get_db),
) -> Any:
"""
Prometheus 文本格式暴露缓存调度指标,方便接入 Grafana。
获取缓存调度指标(Prometheus 格式)
以 Prometheus 文本格式输出缓存调度器的监控指标,
方便接入 Prometheus/Grafana 等监控系统。
**返回格式**: Prometheus 文本格式Content-Type: text/plain
**指标列表**:
- `cache_scheduler_total_batches`: 总批次数
- `cache_scheduler_last_batch_size`: 最后一批候选数
- `cache_scheduler_total_candidates`: 总候选数
- `cache_scheduler_last_candidate_count`: 最后一批候选计数
- `cache_scheduler_cache_hits`: 缓存命中次数
- `cache_scheduler_cache_misses`: 缓存未命中次数
- `cache_scheduler_cache_hit_rate`: 缓存命中率
- `cache_scheduler_concurrency_denied`: 并发拒绝次数
- `cache_scheduler_avg_candidates_per_batch`: 平均每批候选数
- `cache_affinity_total`: 总缓存亲和性数量
- `cache_affinity_hits`: 亲和性命中次数
- `cache_affinity_misses`: 亲和性未命中次数
- `cache_affinity_hit_rate`: 亲和性命中率
- `cache_affinity_invalidations`: 亲和性失效次数
- `cache_affinity_provider_switches`: Provider 切换次数
- `cache_affinity_key_switches`: Key 切换次数
- `cache_scheduler_info`: 调度器信息label: scheduler
"""
adapter = AdminCacheMetricsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -998,10 +1148,39 @@ async def get_model_mapping_cache_stats(
"""
获取模型映射缓存统计信息
返回:
- 缓存键数量
- 缓存 TTL 配置
- 各类型缓存数量
获取模型解析缓存的详细统计信息,包括各类型缓存键数量、
映射关系列表、Provider 级别的模型映射缓存等。
**返回字段**:
- `status`: 状态ok
- `data`: 统计数据
- `available`: Redis 是否可用
- `message`: 提示消息(当 Redis 未启用时)
- `ttl_seconds`: 缓存 TTL
- `total_keys`: 总缓存键数量
- `breakdown`: 各类型缓存键数量分解
- `model_by_id`: Model ID 缓存数量
- `model_by_provider_global`: Provider-GlobalModel 缓存数量
- `global_model_by_id`: GlobalModel ID 缓存数量
- `global_model_by_name`: GlobalModel 名称缓存数量
- `global_model_resolve`: GlobalModel 解析缓存数量
- `mappings`: 模型映射列表(最多 100 条)
- `mapping_name`: 映射名称(别名)
- `global_model_name`: GlobalModel 名称
- `global_model_display_name`: GlobalModel 显示名称
- `providers`: 使用该映射的 Provider 列表
- `ttl`: 缓存剩余 TTL
- `provider_model_mappings`: Provider 级别的模型映射(最多 100 条)
- `provider_id`: Provider ID
- `provider_name`: Provider 名称
- `global_model_id`: GlobalModel ID
- `global_model_name`: GlobalModel 名称
- `global_model_display_name`: GlobalModel 显示名称
- `provider_model_name`: Provider 侧的模型名称
- `aliases`: 别名列表
- `ttl`: 缓存剩余 TTL
- `hit_count`: 缓存命中次数
- `unmapped`: 未映射或无效的缓存条目
"""
adapter = AdminModelMappingCacheStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -1015,7 +1194,15 @@ async def clear_all_model_mapping_cache(
"""
清除所有模型映射缓存
警告: 这会影响所有模型解析,请谨慎使用
清除系统中所有模型映射缓存,包括 Model、GlobalModel、
模型解析等所有相关缓存。下次请求时将重新从数据库查询。
**警告**: 此操作会影响所有模型解析,请谨慎使用
**返回字段**:
- `status`: 状态ok
- `message`: 操作结果消息
- `deleted_count`: 删除的缓存键数量
"""
adapter = AdminClearAllModelMappingCacheAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -1030,8 +1217,17 @@ async def clear_model_mapping_cache_by_name(
"""
清除指定模型名称的映射缓存
参数:
- model_name: 模型名称(可以是 GlobalModel.name 或映射名称)
根据模型名称清除相关的映射缓存,包括 resolve 缓存和 name 缓存。
用于更新单个模型的配置后刷新缓存。
**路径参数**:
- `model_name`: 模型名称(可以是 GlobalModel.name 或映射名称)
**返回字段**:
- `status`: 状态ok
- `message`: 操作结果消息
- `model_name`: 模型名称
- `deleted_keys`: 删除的缓存键列表
"""
adapter = AdminClearModelMappingCacheByNameAdapter(model_name=model_name)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -1047,9 +1243,19 @@ async def clear_provider_model_mapping_cache(
"""
清除指定 Provider 和 GlobalModel 的模型映射缓存
参数:
- provider_id: Provider ID
- global_model_id: GlobalModel ID
清除特定 Provider 和 GlobalModel 组合的映射缓存及其命中次数统计。
用于 Provider 模型配置更新后刷新缓存。
**路径参数**:
- `provider_id`: Provider ID
- `global_model_id`: GlobalModel ID
**返回字段**:
- `status`: 状态ok
- `message`: 操作结果消息
- `provider_id`: Provider ID
- `global_model_id`: GlobalModel ID
- `deleted_keys`: 删除的缓存键列表
"""
adapter = AdminClearProviderModelMappingCacheAdapter(
provider_id=provider_id, global_model_id=global_model_id

View File

@@ -71,7 +71,47 @@ async def get_request_trace(
request: Request,
db: Session = Depends(get_db),
):
"""获取特定请求的完整追踪信息"""
"""
获取请求的完整追踪信息
获取指定请求的完整链路追踪信息包括所有候选candidates的执行情况。
**路径参数**:
- `request_id`: 请求 ID
**返回字段**:
- `request_id`: 请求 ID
- `total_candidates`: 候选总数
- `final_status`: 最终状态success: 成功failed: 失败streaming: 流式传输中pending: 等待中)
- `total_latency_ms`: 总延迟(毫秒)
- `candidates`: 候选列表,每个候选包含:
- `id`: 候选 ID
- `request_id`: 请求 ID
- `candidate_index`: 候选索引
- `retry_index`: 重试序号
- `provider_id`: 提供商 ID
- `provider_name`: 提供商名称
- `provider_website`: 提供商官网
- `endpoint_id`: 端点 ID
- `endpoint_name`: 端点名称API 格式)
- `key_id`: 密钥 ID
- `key_name`: 密钥名称
- `key_preview`: 密钥脱敏预览
- `key_capabilities`: 密钥支持的能力
- `required_capabilities`: 请求需要的能力标签
- `status`: 状态pending, success, failed, skipped
- `skip_reason`: 跳过原因
- `is_cached`: 是否缓存命中
- `status_code`: HTTP 状态码
- `error_type`: 错误类型
- `error_message`: 错误信息
- `latency_ms`: 延迟(毫秒)
- `concurrent_requests`: 并发请求数
- `extra_data`: 额外数据
- `created_at`: 创建时间
- `started_at`: 开始时间
- `finished_at`: 完成时间
"""
adapter = AdminGetRequestTraceAdapter(request_id=request_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -85,9 +125,23 @@ async def get_provider_failure_rate(
db: Session = Depends(get_db),
):
"""
获取某个 Provider 的失败率统计
获取提供商的失败率统计
需要管理员权限
获取指定提供商最近的失败率统计信息。需要管理员权限
**路径参数**:
- `provider_id`: 提供商 ID
**查询参数**:
- `limit`: 统计最近的尝试数量,默认 100最大 1000
**返回字段**:
- `provider_id`: 提供商 ID
- `total_attempts`: 总尝试次数
- `success_count`: 成功次数
- `failed_count`: 失败次数
- `failure_rate`: 失败率(百分比)
- `avg_latency_ms`: 平均延迟(毫秒)
"""
adapter = AdminProviderFailureRateAdapter(provider_id=provider_id, limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -39,6 +39,31 @@ async def update_provider_billing(
request: Request,
db: Session = Depends(get_db),
):
"""
更新提供商计费配置
更新指定提供商的计费策略、配额设置和优先级配置。
**路径参数**:
- `provider_id`: 提供商 ID
**请求体字段**:
- `billing_type`: 计费类型pay_as_you_go、subscription、prepaid、monthly_quota
- `monthly_quota_usd`: 月度配额(美元),可选
- `quota_reset_day`: 配额重置周期天数1-365默认 30
- `quota_last_reset_at`: 当前周期开始时间,可选(设置后会自动同步该周期内的历史使用量)
- `quota_expires_at`: 配额过期时间,可选
- `rpm_limit`: 每分钟请求数限制,可选
- `provider_priority`: 提供商优先级0-200默认 100
**返回字段**:
- `message`: 操作结果信息
- `provider`: 更新后的提供商信息
- `id`: 提供商 ID
- `name`: 提供商名称
- `billing_type`: 计费类型
- `provider_priority`: 提供商优先级
"""
adapter = AdminProviderBillingAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -50,6 +75,39 @@ async def get_provider_stats(
hours: int = 24,
db: Session = Depends(get_db),
):
"""
获取提供商统计数据
获取指定提供商的计费信息、RPM 使用情况和使用统计数据。
**路径参数**:
- `provider_id`: 提供商 ID
**查询参数**:
- `hours`: 统计时间范围(小时),默认 24
**返回字段**:
- `provider_id`: 提供商 ID
- `provider_name`: 提供商名称
- `period_hours`: 统计时间范围
- `billing_info`: 计费信息
- `billing_type`: 计费类型
- `monthly_quota_usd`: 月度配额
- `monthly_used_usd`: 月度已使用
- `quota_remaining_usd`: 剩余配额
- `quota_expires_at`: 配额过期时间
- `rpm_info`: RPM 信息
- `rpm_limit`: RPM 限制
- `rpm_used`: 已使用 RPM
- `rpm_reset_at`: RPM 重置时间
- `usage_stats`: 使用统计
- `total_requests`: 总请求数
- `successful_requests`: 成功请求数
- `failed_requests`: 失败请求数
- `success_rate`: 成功率
- `avg_response_time_ms`: 平均响应时间(毫秒)
- `total_cost_usd`: 总成本(美元)
"""
adapter = AdminProviderStatsAdapter(provider_id=provider_id, hours=hours)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -67,6 +125,20 @@ async def reset_provider_quota(
@router.get("/strategies")
async def list_available_strategies(request: Request, db: Session = Depends(get_db)):
"""
获取可用负载均衡策略列表
列出系统中所有已注册的负载均衡策略插件。
**返回字段**:
- `strategies`: 策略列表
- `name`: 策略名称
- `priority`: 策略优先级
- `version`: 策略版本
- `description`: 策略描述
- `author`: 策略作者
- `total`: 策略总数
"""
adapter = AdminListStrategiesAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -49,7 +49,36 @@ async def list_provider_models(
limit: int = 100,
db: Session = Depends(get_db),
) -> List[ModelResponse]:
"""获取提供商的所有模型(管理员)"""
"""
获取提供商的所有模型
获取指定提供商的模型列表,支持分页和状态过滤。
**路径参数**:
- `provider_id`: 提供商 ID
**查询参数**:
- `is_active`: 可选的活跃状态过滤true 仅返回活跃模型false 返回禁用模型,不传则返回全部
- `skip`: 跳过的记录数,默认为 0
- `limit`: 返回的最大记录数,默认为 100
**返回字段**(数组,每项包含):
- `id`: 模型 ID
- `provider_id`: 提供商 ID
- `global_model_id`: 全局模型 ID
- `provider_model_name`: 提供商模型名称
- `is_active`: 是否启用
- `input_price_per_1m`: 输入价格(每百万 token
- `output_price_per_1m`: 输出价格(每百万 token
- `cache_creation_price_per_1m`: 缓存创建价格(每百万 token
- `cache_read_price_per_1m`: 缓存读取价格(每百万 token
- `price_per_request`: 每次请求价格
- `supports_vision`: 是否支持视觉
- `supports_function_calling`: 是否支持函数调用
- `supports_streaming`: 是否支持流式输出
- `created_at`: 创建时间
- `updated_at`: 更新时间
"""
adapter = AdminListProviderModelsAdapter(
provider_id=provider_id,
is_active=is_active,
@@ -66,7 +95,29 @@ async def create_provider_model(
request: Request,
db: Session = Depends(get_db),
) -> ModelResponse:
"""创建模型(管理员)"""
"""
创建模型
为指定提供商创建一个新的模型配置。
**路径参数**:
- `provider_id`: 提供商 ID
**请求体字段**:
- `provider_model_name`: 提供商模型名称(必填)
- `global_model_id`: 全局模型 ID可选关联到全局模型
- `is_active`: 是否启用(默认 true
- `input_price_per_1m`: 输入价格(每百万 token可选
- `output_price_per_1m`: 输出价格(每百万 token可选
- `cache_creation_price_per_1m`: 缓存创建价格(每百万 token可选
- `cache_read_price_per_1m`: 缓存读取价格(每百万 token可选
- `price_per_request`: 每次请求价格(可选)
- `supports_vision`: 是否支持视觉(可选)
- `supports_function_calling`: 是否支持函数调用(可选)
- `supports_streaming`: 是否支持流式输出(可选)
**返回字段**: 返回创建的模型详细信息(与 GET 单个模型接口返回格式相同)
"""
adapter = AdminCreateProviderModelAdapter(provider_id=provider_id, model_data=model_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -78,7 +129,32 @@ async def get_provider_model(
request: Request,
db: Session = Depends(get_db),
) -> ModelResponse:
"""获取模型详情(管理员)"""
"""
获取模型详情
获取指定模型的详细配置信息。
**路径参数**:
- `provider_id`: 提供商 ID
- `model_id`: 模型 ID
**返回字段**:
- `id`: 模型 ID
- `provider_id`: 提供商 ID
- `global_model_id`: 全局模型 ID
- `provider_model_name`: 提供商模型名称
- `is_active`: 是否启用
- `input_price_per_1m`: 输入价格(每百万 token
- `output_price_per_1m`: 输出价格(每百万 token
- `cache_creation_price_per_1m`: 缓存创建价格(每百万 token
- `cache_read_price_per_1m`: 缓存读取价格(每百万 token
- `price_per_request`: 每次请求价格
- `supports_vision`: 是否支持视觉
- `supports_function_calling`: 是否支持函数调用
- `supports_streaming`: 是否支持流式输出
- `created_at`: 创建时间
- `updated_at`: 更新时间
"""
adapter = AdminGetProviderModelAdapter(provider_id=provider_id, model_id=model_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -91,7 +167,30 @@ async def update_provider_model(
request: Request,
db: Session = Depends(get_db),
) -> ModelResponse:
"""更新模型(管理员)"""
"""
更新模型配置
更新指定模型的配置信息。只需传入需要更新的字段,未传入的字段保持不变。
**路径参数**:
- `provider_id`: 提供商 ID
- `model_id`: 模型 ID
**请求体字段**(所有字段可选):
- `provider_model_name`: 提供商模型名称
- `global_model_id`: 全局模型 ID
- `is_active`: 是否启用
- `input_price_per_1m`: 输入价格(每百万 token
- `output_price_per_1m`: 输出价格(每百万 token
- `cache_creation_price_per_1m`: 缓存创建价格(每百万 token
- `cache_read_price_per_1m`: 缓存读取价格(每百万 token
- `price_per_request`: 每次请求价格
- `supports_vision`: 是否支持视觉
- `supports_function_calling`: 是否支持函数调用
- `supports_streaming`: 是否支持流式输出
**返回字段**: 返回更新后的模型详细信息(与 GET 单个模型接口返回格式相同)
"""
adapter = AdminUpdateProviderModelAdapter(
provider_id=provider_id,
model_id=model_id,
@@ -107,7 +206,18 @@ async def delete_provider_model(
request: Request,
db: Session = Depends(get_db),
):
"""删除模型(管理员)"""
"""
删除模型
删除指定的模型配置。注意:此操作不可逆。
**路径参数**:
- `provider_id`: 提供商 ID
- `model_id`: 模型 ID
**返回字段**:
- `message`: 删除成功提示信息
"""
adapter = AdminDeleteProviderModelAdapter(provider_id=provider_id, model_id=model_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -119,7 +229,29 @@ async def batch_create_provider_models(
request: Request,
db: Session = Depends(get_db),
) -> List[ModelResponse]:
"""批量创建模型(管理员)"""
"""
批量创建模型
为指定提供商批量创建多个模型配置。
**路径参数**:
- `provider_id`: 提供商 ID
**请求体**: 模型数据数组,每项包含:
- `provider_model_name`: 提供商模型名称(必填)
- `global_model_id`: 全局模型 ID可选
- `is_active`: 是否启用(默认 true
- `input_price_per_1m`: 输入价格(每百万 token可选
- `output_price_per_1m`: 输出价格(每百万 token可选
- `cache_creation_price_per_1m`: 缓存创建价格(每百万 token可选
- `cache_read_price_per_1m`: 缓存读取价格(每百万 token可选
- `price_per_request`: 每次请求价格(可选)
- `supports_vision`: 是否支持视觉(可选)
- `supports_function_calling`: 是否支持函数调用(可选)
- `supports_streaming`: 是否支持流式输出(可选)
**返回字段**: 返回创建的模型列表(与 GET 模型列表接口返回格式相同)
"""
adapter = AdminBatchCreateModelsAdapter(provider_id=provider_id, models_data=models_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -134,10 +266,23 @@ async def get_provider_available_source_models(
db: Session = Depends(get_db),
):
"""
获取该 Provider 支持的所有统一模型名source_model
获取提供商支持的可用源模型
包括:
1. 直连模型Model.provider_model_name 直接作为统一模型名)
获取该提供商支持的所有统一模型名source_model包含价格和能力信息。
**路径参数**:
- `provider_id`: 提供商 ID
**返回字段**:
- `models`: 可用源模型数组,每项包含:
- `global_model_name`: 全局模型名称
- `display_name`: 显示名称
- `provider_model_name`: 提供商模型名称
- `model_id`: 模型 ID
- `price`: 价格信息(包含 input_price_per_1m, output_price_per_1m, cache_creation_price_per_1m, cache_read_price_per_1m, price_per_request
- `capabilities`: 能力信息(包含 supports_vision, supports_function_calling, supports_streaming
- `is_active`: 是否启用
- `total`: 总数
"""
adapter = AdminGetProviderAvailableSourceModelsAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -153,7 +298,27 @@ async def batch_assign_global_models_to_provider(
request: Request,
db: Session = Depends(get_db),
) -> BatchAssignModelsToProviderResponse:
"""批量为 Provider 关联 GlobalModels自动继承价格和能力配置"""
"""
批量关联全局模型
批量为提供商关联全局模型,自动继承全局模型的价格和能力配置。
**路径参数**:
- `provider_id`: 提供商 ID
**请求体字段**:
- `global_model_ids`: 全局模型 ID 数组(必填)
**返回字段**:
- `success`: 成功关联的模型数组,每项包含:
- `global_model_id`: 全局模型 ID
- `global_model_name`: 全局模型名称
- `model_id`: 新创建的模型 ID
- `errors`: 失败的模型数组,每项包含:
- `global_model_id`: 全局模型 ID
- `global_model_name`: 全局模型名称(如果可用)
- `error`: 错误信息
"""
adapter = AdminBatchAssignModelsToProviderAdapter(
provider_id=provider_id, payload=payload
)
@@ -173,10 +338,30 @@ async def import_models_from_upstream(
"""
从上游提供商导入模型
流程:
从上游提供商导入模型列表。如果全局模型不存在,将自动创建。
**流程说明**:
1. 根据 model_ids 检查全局模型是否存在(按 name 匹配)
2. 如不存在,自动创建新的 GlobalModel使用默认配置
2. 如不存在,自动创建新的 GlobalModel使用默认免费配置)
3. 创建 Model 关联到当前 Provider
4. 如模型已关联,则记录到成功列表中
**路径参数**:
- `provider_id`: 提供商 ID
**请求体字段**:
- `model_ids`: 模型 ID 数组(必填,每个 ID 长度 1-100 字符)
**返回字段**:
- `success`: 成功导入的模型数组,每项包含:
- `model_id`: 模型 ID
- `global_model_id`: 全局模型 ID
- `global_model_name`: 全局模型名称
- `provider_model_id`: 提供商模型 ID
- `created_global_model`: 是否新创建了全局模型
- `errors`: 失败的模型数组,每项包含:
- `model_id`: 模型 ID
- `error`: 错误信息
"""
adapter = AdminImportFromUpstreamAdapter(provider_id=provider_id, payload=payload)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -27,24 +27,116 @@ async def list_providers(
is_active: Optional[bool] = None,
db: Session = Depends(get_db),
):
"""
获取提供商列表
获取所有提供商的基本信息列表,支持分页和状态过滤。
**查询参数**:
- `skip`: 跳过的记录数,用于分页,默认为 0
- `limit`: 返回的最大记录数,范围 1-500默认为 100
- `is_active`: 可选的活跃状态过滤true 仅返回活跃提供商false 返回禁用提供商,不传则返回全部
**返回字段**:
- `id`: 提供商 ID
- `name`: 提供商名称(唯一标识)
- `display_name`: 显示名称
- `api_format`: API 格式(如 claude、openai、gemini 等)
- `base_url`: API 基础 URL
- `api_key`: API 密钥(脱敏显示)
- `priority`: 优先级
- `is_active`: 是否活跃
- `created_at`: 创建时间
- `updated_at`: 更新时间
"""
adapter = AdminListProvidersAdapter(skip=skip, limit=limit, is_active=is_active)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/")
async def create_provider(request: Request, db: Session = Depends(get_db)):
"""
创建新提供商
创建一个新的 AI 模型提供商配置。
**请求体字段**:
- `name`: 提供商名称(必填,唯一,用于系统标识)
- `display_name`: 显示名称(必填)
- `description`: 描述信息(可选)
- `website`: 官网地址(可选)
- `billing_type`: 计费类型可选pay_as_you_go/subscription/prepaid默认 pay_as_you_go
- `monthly_quota_usd`: 月度配额(美元)(可选)
- `quota_reset_day`: 配额重置日期1-31可选
- `quota_last_reset_at`: 上次配额重置时间(可选)
- `quota_expires_at`: 配额过期时间(可选)
- `rpm_limit`: 每分钟请求数限制(可选)
- `provider_priority`: 提供商优先级(数字越小优先级越高,默认 100
- `is_active`: 是否启用(默认 true
- `rate_limit`: 速率限制配置(可选)
- `concurrent_limit`: 并发限制(可选)
- `config`: 额外配置信息JSON可选
**返回字段**:
- `id`: 新创建的提供商 ID
- `name`: 提供商名称
- `display_name`: 显示名称
- `message`: 成功提示信息
"""
adapter = AdminCreateProviderAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/{provider_id}")
async def update_provider(provider_id: str, request: Request, db: Session = Depends(get_db)):
"""
更新提供商配置
更新指定提供商的配置信息。只需传入需要更新的字段,未传入的字段保持不变。
**路径参数**:
- `provider_id`: 提供商 ID
**请求体字段**(所有字段可选):
- `name`: 提供商名称
- `display_name`: 显示名称
- `description`: 描述信息
- `website`: 官网地址
- `billing_type`: 计费类型pay_as_you_go/subscription/prepaid
- `monthly_quota_usd`: 月度配额(美元)
- `quota_reset_day`: 配额重置日期1-31
- `quota_last_reset_at`: 上次配额重置时间
- `quota_expires_at`: 配额过期时间
- `rpm_limit`: 每分钟请求数限制
- `provider_priority`: 提供商优先级
- `is_active`: 是否启用
- `rate_limit`: 速率限制配置
- `concurrent_limit`: 并发限制
- `config`: 额外配置信息JSON
**返回字段**:
- `id`: 提供商 ID
- `name`: 提供商名称
- `is_active`: 是否启用
- `message`: 成功提示信息
"""
adapter = AdminUpdateProviderAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{provider_id}")
async def delete_provider(provider_id: str, request: Request, db: Session = Depends(get_db)):
"""
删除提供商
删除指定的提供商。注意:此操作会级联删除关联的端点、密钥和模型配置。
**路径参数**:
- `provider_id`: 提供商 ID
**返回字段**:
- `message`: 删除成功提示信息
"""
adapter = AdminDeleteProviderAdapter(provider_id=provider_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -40,7 +40,41 @@ async def get_providers_summary(
request: Request,
db: Session = Depends(get_db),
) -> List[ProviderWithEndpointsSummary]:
"""获取所有 Providers 的摘要信息(包含 Endpoints 和 Keys 统计)"""
"""
获取所有提供商摘要信息
获取所有提供商的详细摘要信息,包含端点、密钥、模型统计和健康状态。
**返回字段**(数组,每项包含):
- `id`: 提供商 ID
- `name`: 提供商名称
- `display_name`: 显示名称
- `description`: 描述信息
- `website`: 官网地址
- `provider_priority`: 优先级
- `is_active`: 是否启用
- `billing_type`: 计费类型
- `monthly_quota_usd`: 月度配额(美元)
- `monthly_used_usd`: 本月已使用金额(美元)
- `quota_reset_day`: 配额重置日期
- `quota_last_reset_at`: 上次配额重置时间
- `quota_expires_at`: 配额过期时间
- `rpm_limit`: RPM 限制
- `rpm_used`: 已使用 RPM
- `rpm_reset_at`: RPM 重置时间
- `total_endpoints`: 端点总数
- `active_endpoints`: 活跃端点数
- `total_keys`: 密钥总数
- `active_keys`: 活跃密钥数
- `total_models`: 模型总数
- `active_models`: 活跃模型数
- `avg_health_score`: 平均健康分数0-1
- `unhealthy_endpoints`: 不健康端点数(健康分数 < 0.5
- `api_formats`: 支持的 API 格式列表
- `endpoint_health_details`: 端点健康详情(包含 api_format, health_score, is_active, active_keys
- `created_at`: 创建时间
- `updated_at`: 更新时间
"""
adapter = AdminProviderSummaryAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -51,7 +85,44 @@ async def get_provider_summary(
request: Request,
db: Session = Depends(get_db),
) -> ProviderWithEndpointsSummary:
"""获取单个 Provider 的摘要信息(包含 Endpoints 和 Keys 统计)"""
"""
获取单个提供商摘要信息
获取指定提供商的详细摘要信息,包含端点、密钥、模型统计和健康状态。
**路径参数**:
- `provider_id`: 提供商 ID
**返回字段**:
- `id`: 提供商 ID
- `name`: 提供商名称
- `display_name`: 显示名称
- `description`: 描述信息
- `website`: 官网地址
- `provider_priority`: 优先级
- `is_active`: 是否启用
- `billing_type`: 计费类型
- `monthly_quota_usd`: 月度配额(美元)
- `monthly_used_usd`: 本月已使用金额(美元)
- `quota_reset_day`: 配额重置日期
- `quota_last_reset_at`: 上次配额重置时间
- `quota_expires_at`: 配额过期时间
- `rpm_limit`: RPM 限制
- `rpm_used`: 已使用 RPM
- `rpm_reset_at`: RPM 重置时间
- `total_endpoints`: 端点总数
- `active_endpoints`: 活跃端点数
- `total_keys`: 密钥总数
- `active_keys`: 活跃密钥数
- `total_models`: 模型总数
- `active_models`: 活跃模型数
- `avg_health_score`: 平均健康分数0-1
- `unhealthy_endpoints`: 不健康端点数(健康分数 < 0.5
- `api_formats`: 支持的 API 格式列表
- `endpoint_health_details`: 端点健康详情(包含 api_format, health_score, is_active, active_keys
- `created_at`: 创建时间
- `updated_at`: 更新时间
"""
provider = db.query(Provider).filter(Provider.id == provider_id).first()
if not provider:
raise NotFoundException(f"Provider {provider_id} not found")
@@ -67,7 +138,34 @@ async def get_provider_health_monitor(
per_endpoint_limit: int = Query(48, ge=10, le=200, description="每个端点的事件数量"),
db: Session = Depends(get_db),
) -> ProviderEndpointHealthMonitorResponse:
"""获取 Provider 下所有端点的健康监控时间线"""
"""
获取提供商健康监控数据
获取指定提供商下所有端点的健康监控时间线,包含请求成功率、延迟、错误信息等。
**路径参数**:
- `provider_id`: 提供商 ID
**查询参数**:
- `lookback_hours`: 回溯的小时数,范围 1-72默认为 6
- `per_endpoint_limit`: 每个端点返回的事件数量,范围 10-200默认为 48
**返回字段**:
- `provider_id`: 提供商 ID
- `provider_name`: 提供商名称
- `generated_at`: 生成时间
- `endpoints`: 端点健康监控数据数组,每项包含:
- `endpoint_id`: 端点 ID
- `api_format`: API 格式
- `is_active`: 是否活跃
- `total_attempts`: 总请求次数
- `success_count`: 成功次数
- `failed_count`: 失败次数
- `skipped_count`: 跳过次数
- `success_rate`: 成功率0-1
- `last_event_at`: 最后事件时间
- `events`: 事件详情数组(包含 timestamp, status, status_code, latency_ms, error_type, error_message
"""
adapter = AdminProviderHealthMonitorAdapter(
provider_id=provider_id,
@@ -84,7 +182,29 @@ async def update_provider_settings(
request: Request,
db: Session = Depends(get_db),
) -> ProviderWithEndpointsSummary:
"""更新 Provider 基础配置display_name, description, priority, weight 等)"""
"""
更新提供商基础配置
更新提供商的基础配置信息,如显示名称、描述、优先级等。只需传入需要更新的字段。
**路径参数**:
- `provider_id`: 提供商 ID
**请求体字段**(所有字段可选):
- `display_name`: 显示名称
- `description`: 描述信息
- `website`: 官网地址
- `provider_priority`: 优先级
- `is_active`: 是否启用
- `billing_type`: 计费类型
- `monthly_quota_usd`: 月度配额(美元)
- `quota_reset_day`: 配额重置日期
- `quota_last_reset_at`: 上次配额重置时间
- `quota_expires_at`: 配额过期时间
- `rpm_limit`: RPM 限制
**返回字段**: 返回更新后的提供商摘要信息(与 GET /summary 接口返回格式相同)
"""
adapter = AdminUpdateProviderSettingsAdapter(provider_id=provider_id, update_data=update_data)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -18,7 +18,7 @@ from src.core.logger import logger
from src.database import get_db
from src.services.rate_limit.ip_limiter import IPRateLimiter
router = APIRouter(prefix="/api/admin/security/ip", tags=["IP Security"])
router = APIRouter(prefix="/api/admin/security/ip", tags=["Admin - Security"])
pipeline = ApiRequestPipeline()
@@ -56,42 +56,110 @@ class RemoveIPFromWhitelistRequest(BaseModel):
@router.post("/blacklist")
async def add_to_blacklist(request: Request, db: Session = Depends(get_db)):
"""Add IP to blacklist"""
"""
添加 IP 到黑名单
将指定 IP 地址添加到黑名单,被加入黑名单的 IP 将无法访问系统。需要管理员权限。
**请求体字段**:
- `ip_address`: IP 地址
- `reason`: 加入黑名单的原因
- `ttl`: 可选,过期时间(秒),不指定表示永久
**返回字段**:
- `success`: 是否成功
- `message`: 操作结果信息
- `reason`: 加入黑名单的原因
- `ttl`: 过期时间(秒或"永久"
"""
adapter = AddToBlacklistAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.ADMIN)
@router.delete("/blacklist/{ip_address}")
async def remove_from_blacklist(ip_address: str, request: Request, db: Session = Depends(get_db)):
"""Remove IP from blacklist"""
"""
从黑名单移除 IP
将指定 IP 地址从黑名单中移除。需要管理员权限。
**路径参数**:
- `ip_address`: IP 地址
**返回字段**:
- `success`: 是否成功
- `message`: 操作结果信息
"""
adapter = RemoveFromBlacklistAdapter(ip_address=ip_address)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.ADMIN)
@router.get("/blacklist/stats")
async def get_blacklist_stats(request: Request, db: Session = Depends(get_db)):
"""Get blacklist statistics"""
"""
获取黑名单统计信息
获取黑名单的统计信息和列表。需要管理员权限。
**返回字段**:
- `total`: 黑名单总数
- `items`: 黑名单列表,每个项包含:
- `ip`: IP 地址
- `reason`: 加入原因
- `added_at`: 添加时间
- `ttl`: 剩余有效时间(秒)
"""
adapter = GetBlacklistStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.ADMIN)
@router.post("/whitelist")
async def add_to_whitelist(request: Request, db: Session = Depends(get_db)):
"""Add IP to whitelist"""
"""
添加 IP 到白名单
将指定 IP 地址或 CIDR 网段添加到白名单,白名单中的 IP 将跳过速率限制检查。需要管理员权限。
**请求体字段**:
- `ip_address`: IP 地址或 CIDR 格式(如 192.168.1.0/24
**返回字段**:
- `success`: 是否成功
- `message`: 操作结果信息
"""
adapter = AddToWhitelistAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.ADMIN)
@router.delete("/whitelist/{ip_address}")
async def remove_from_whitelist(ip_address: str, request: Request, db: Session = Depends(get_db)):
"""Remove IP from whitelist"""
"""
从白名单移除 IP
将指定 IP 地址从白名单中移除。需要管理员权限。
**路径参数**:
- `ip_address`: IP 地址
**返回字段**:
- `success`: 是否成功
- `message`: 操作结果信息
"""
adapter = RemoveFromWhitelistAdapter(ip_address=ip_address)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.ADMIN)
@router.get("/whitelist")
async def get_whitelist(request: Request, db: Session = Depends(get_db)):
"""Get whitelist"""
"""
获取白名单
获取当前的 IP 白名单列表。需要管理员权限。
**返回字段**:
- `whitelist`: 白名单 IP 地址列表
- `total`: 白名单总数
"""
adapter = GetWhitelistAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.ADMIN)

View File

@@ -44,7 +44,14 @@ def _get_version_from_git() -> str | None:
@router.get("/version")
async def get_system_version():
"""获取系统版本信息"""
"""
获取系统版本信息
获取当前系统的版本号。优先从 git describe 获取,回退到静态版本文件。
**返回字段**:
- `version`: 版本号字符串
"""
# 优先从 git 获取
version = _get_version_from_git()
if version:
@@ -64,7 +71,16 @@ pipeline = ApiRequestPipeline()
@router.get("/settings")
async def get_system_settings(request: Request, db: Session = Depends(get_db)):
"""获取系统设置(管理员)"""
"""
获取系统设置
获取系统的全局设置信息。需要管理员权限。
**返回字段**:
- `default_provider`: 默认提供商名称
- `default_model`: 默认模型名称
- `enable_usage_tracking`: 是否启用使用情况追踪
"""
adapter = AdminGetSystemSettingsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -72,7 +88,19 @@ async def get_system_settings(request: Request, db: Session = Depends(get_db)):
@router.put("/settings")
async def update_system_settings(http_request: Request, db: Session = Depends(get_db)):
"""更新系统设置(管理员)"""
"""
更新系统设置
更新系统的全局设置。需要管理员权限。
**请求体字段**:
- `default_provider`: 可选,默认提供商名称(空字符串表示清除设置)
- `default_model`: 可选,默认模型名称(空字符串表示清除设置)
- `enable_usage_tracking`: 可选,是否启用使用情况追踪
**返回字段**:
- `message`: 操作结果信息
"""
adapter = AdminUpdateSystemSettingsAdapter()
return await pipeline.run(adapter=adapter, http_request=http_request, db=db, mode=adapter.mode)
@@ -80,7 +108,14 @@ async def update_system_settings(http_request: Request, db: Session = Depends(ge
@router.get("/configs")
async def get_all_system_configs(request: Request, db: Session = Depends(get_db)):
"""获取所有系统配置(管理员)"""
"""
获取所有系统配置
获取系统中所有的配置项。需要管理员权限。
**返回字段**:
- 配置项的键值对字典
"""
adapter = AdminGetAllConfigsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -88,7 +123,19 @@ async def get_all_system_configs(request: Request, db: Session = Depends(get_db)
@router.get("/configs/{key}")
async def get_system_config(key: str, request: Request, db: Session = Depends(get_db)):
"""获取特定系统配置(管理员)"""
"""
获取特定系统配置
获取指定配置项的值。需要管理员权限。
**路径参数**:
- `key`: 配置项键名
**返回字段**:
- `key`: 配置项键名
- `value`: 配置项的值(敏感配置项不返回实际值)
- `is_set`: 可选,对于敏感配置项,指示是否已设置
"""
adapter = AdminGetSystemConfigAdapter(key=key)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -100,7 +147,24 @@ async def set_system_config(
request: Request,
db: Session = Depends(get_db),
):
"""设置系统配置(管理员)"""
"""
设置系统配置
设置或更新指定配置项的值。需要管理员权限。
**路径参数**:
- `key`: 配置项键名
**请求体字段**:
- `value`: 配置项的值
- `description`: 可选,配置项描述
**返回字段**:
- `key`: 配置项键名
- `value`: 配置项的值(敏感配置项显示为 ********
- `description`: 配置项描述
- `updated_at`: 更新时间
"""
adapter = AdminSetSystemConfigAdapter(key=key)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -108,7 +172,17 @@ async def set_system_config(
@router.delete("/configs/{key}")
async def delete_system_config(key: str, request: Request, db: Session = Depends(get_db)):
"""删除系统配置(管理员)"""
"""
删除系统配置
删除指定的配置项。需要管理员权限。
**路径参数**:
- `key`: 配置项键名
**返回字段**:
- `message`: 操作结果信息
"""
adapter = AdminDeleteSystemConfigAdapter(key=key)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -116,20 +190,54 @@ async def delete_system_config(key: str, request: Request, db: Session = Depends
@router.get("/stats")
async def get_system_stats(request: Request, db: Session = Depends(get_db)):
"""
获取系统统计信息
获取系统的整体统计数据。需要管理员权限。
**返回字段**:
- `users`: 用户统计total: 总用户数, active: 活跃用户数)
- `providers`: 提供商统计total: 总提供商数, active: 活跃提供商数)
- `api_keys`: API Key 总数
- `requests`: 请求总数
"""
adapter = AdminSystemStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/cleanup")
async def trigger_cleanup(request: Request, db: Session = Depends(get_db)):
"""Manually trigger usage record cleanup task"""
"""
手动触发清理任务
手动触发使用记录清理任务,清理过期的请求/响应数据。需要管理员权限。
**返回字段**:
- `message`: 操作结果信息
- `stats`: 清理统计信息
- `total_records`: 总记录数统计before, after, deleted
- `body_fields`: 请求/响应体字段清理统计before, after, cleaned
- `header_fields`: 请求/响应头字段清理统计before, after, cleaned
- `timestamp`: 清理完成时间
"""
adapter = AdminTriggerCleanupAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/api-formats")
async def get_api_formats(request: Request, db: Session = Depends(get_db)):
"""获取所有可用的API格式列表"""
"""
获取所有可用的 API 格式列表
获取系统支持的所有 API 格式及其元数据。需要管理员权限。
**返回字段**:
- `formats`: API 格式列表,每个格式包含:
- `value`: 格式值
- `label`: 显示名称
- `default_path`: 默认路径
- `aliases`: 别名列表
"""
adapter = AdminGetApiFormatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -39,12 +39,21 @@ async def get_usage_aggregation(
db: Session = Depends(get_db),
):
"""
Get usage aggregation by specified dimension.
获取使用情况聚合统计
- group_by=model: Aggregate by model
- group_by=user: Aggregate by user
- group_by=provider: Aggregate by provider
- group_by=api_format: Aggregate by API format
按指定维度聚合使用情况统计数据。
**查询参数**:
- `group_by`: 必需聚合维度可选值model按模型、user按用户、provider按提供商、api_format按 API 格式)
- `start_date`: 可选开始日期ISO 格式)
- `end_date`: 可选结束日期ISO 格式)
- `limit`: 返回数量限制,默认 20最大 100
**返回字段**:
- 按模型聚合时model, request_count, total_tokens, total_cost, actual_cost
- 按用户聚合时user_id, email, username, request_count, total_tokens, total_cost
- 按提供商聚合时provider_id, provider, request_count, total_tokens, total_cost, actual_cost, avg_response_time_ms, success_rate, error_count
- 按 API 格式聚合时api_format, request_count, total_tokens, total_cost, actual_cost, avg_response_time_ms
"""
if group_by == "model":
adapter = AdminUsageByModelAdapter(start_date=start_date, end_date=end_date, limit=limit)
@@ -69,6 +78,25 @@ async def get_usage_stats(
end_date: Optional[datetime] = None,
db: Session = Depends(get_db),
):
"""
获取使用情况总体统计
获取指定时间范围内的使用情况总体统计数据。
**查询参数**:
- `start_date`: 可选开始日期ISO 格式)
- `end_date`: 可选结束日期ISO 格式)
**返回字段**:
- `total_requests`: 总请求数
- `total_tokens`: 总 token 数
- `total_cost`: 总成本(美元)
- `total_actual_cost`: 实际总成本(美元)
- `avg_response_time`: 平均响应时间(秒)
- `error_count`: 错误请求数
- `error_rate`: 错误率(百分比)
- `cache_stats`: 缓存统计信息cache_creation_tokens, cache_read_tokens, cache_creation_cost, cache_read_cost
"""
adapter = AdminUsageStatsAdapter(start_date=start_date, end_date=end_date)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -79,9 +107,12 @@ async def get_activity_heatmap(
db: Session = Depends(get_db),
):
"""
Get activity heatmap data for the past 365 days.
获取活动热力图数据
This endpoint is cached for 5 minutes to reduce database load.
获取过去 365 天的活动热力图数据。此接口缓存 5 分钟以减少数据库负载。
**返回字段**:
- 按日期聚合的请求数、token 数、成本等统计数据
"""
adapter = AdminActivityHeatmapAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -102,6 +133,33 @@ async def get_usage_records(
offset: int = Query(0, ge=0),
db: Session = Depends(get_db),
):
"""
获取使用记录列表
获取详细的使用记录列表,支持多种筛选条件。
**查询参数**:
- `start_date`: 可选开始日期ISO 格式)
- `end_date`: 可选结束日期ISO 格式)
- `search`: 可选,通用搜索关键词(支持用户名、密钥名、模型名、提供商名模糊搜索,多个关键词用空格分隔)
- `user_id`: 可选,用户 ID 筛选
- `username`: 可选,用户名模糊搜索
- `model`: 可选,模型名模糊搜索
- `provider`: 可选,提供商名称搜索
- `status`: 可选状态筛选stream: 流式请求standard: 标准请求error: 错误请求pending: 等待中streaming: 流式中completed: 已完成failed: 失败active: 活跃请求)
- `limit`: 返回数量限制,默认 100最大 500
- `offset`: 分页偏移量,默认 0
**返回字段**:
- `records`: 使用记录列表,包含 id, user_id, user_email, username, api_key, provider, model, target_model,
input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, total_tokens,
cost, actual_cost, rate_multiplier, response_time_ms, first_byte_time_ms, created_at, is_stream,
input_price_per_1m, output_price_per_1m, cache_creation_price_per_1m, cache_read_price_per_1m,
status_code, error_message, status, has_fallback, api_format, api_key_name, request_metadata
- `total`: 符合条件的总记录数
- `limit`: 当前分页限制
- `offset`: 当前分页偏移量
"""
adapter = AdminUsageRecordsAdapter(
start_date=start_date,
end_date=end_date,
@@ -124,10 +182,19 @@ async def get_active_requests(
db: Session = Depends(get_db),
):
"""
获取活跃请求的状态(轻量级接口,用于前端轮询)
获取活跃请求的状态
获取当前活跃pending/streaming 状态)请求的状态信息。这是一个轻量级接口,适合前端轮询。
**查询参数**:
- `ids`: 可选,逗号分隔的请求 ID 列表,用于查询特定请求的状态
**行为说明**:
- 如果提供 ids 参数,只返回这些 ID 对应请求的最新状态
- 如果不提供 ids返回所有 pending/streaming 状态的请求
**返回字段**:
- `requests`: 活跃请求列表,包含请求状态信息
"""
adapter = AdminActiveRequestsAdapter(ids=ids)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -142,9 +209,48 @@ async def get_usage_detail(
db: Session = Depends(get_db),
):
"""
Get detailed information of a specific usage record.
获取使用记录详情
Includes request/response headers and body.
获取指定使用记录的详细信息,包括请求/响应的头部和正文。
**路径参数**:
- `usage_id`: 使用记录 ID
**返回字段**:
- `id`: 记录 ID
- `request_id`: 请求 ID
- `user`: 用户信息id, username, email
- `api_key`: API Key 信息id, name, display
- `provider`: 提供商名称
- `api_format`: API 格式
- `model`: 请求的模型名称
- `target_model`: 映射后的目标模型名称
- `tokens`: Token 统计input, output, total
- `cost`: 成本统计input, output, total
- `cache_creation_input_tokens`: 缓存创建输入 token 数
- `cache_read_input_tokens`: 缓存读取输入 token 数
- `cache_creation_cost`: 缓存创建成本
- `cache_read_cost`: 缓存读取成本
- `request_cost`: 请求成本
- `input_price_per_1m`: 输入价格(每百万 token
- `output_price_per_1m`: 输出价格(每百万 token
- `cache_creation_price_per_1m`: 缓存创建价格(每百万 token
- `cache_read_price_per_1m`: 缓存读取价格(每百万 token
- `price_per_request`: 每请求价格
- `request_type`: 请求类型
- `is_stream`: 是否为流式请求
- `status_code`: HTTP 状态码
- `error_message`: 错误信息
- `response_time_ms`: 响应时间(毫秒)
- `first_byte_time_ms`: 首字节时间TTFB毫秒
- `created_at`: 创建时间
- `request_headers`: 请求头
- `request_body`: 请求体
- `provider_request_headers`: 提供商请求头
- `response_headers`: 响应头
- `response_body`: 响应体
- `metadata`: 提供商响应元数据
- `tiered_pricing`: 阶梯计费信息(如适用)
"""
adapter = AdminUsageDetailAdapter(usage_id=usage_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -26,6 +26,18 @@ pipeline = ApiRequestPipeline()
# 管理员端点
@router.post("")
async def create_user_endpoint(request: Request, db: Session = Depends(get_db)):
"""
创建用户
创建新用户账号(管理员专用)。
**请求体**:
- `email`: 邮箱地址
- `username`: 用户名
- `password`: 密码
- `role`: 角色user/admin
- `quota_usd`: 配额USD
"""
adapter = AdminCreateUserAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -33,18 +45,33 @@ async def create_user_endpoint(request: Request, db: Session = Depends(get_db)):
@router.get("")
async def list_users(
request: Request,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
role: Optional[str] = None,
is_active: Optional[bool] = None,
skip: int = Query(0, ge=0, description="跳过记录数"),
limit: int = Query(100, ge=1, le=1000, description="返回记录数"),
role: Optional[str] = Query(None, description="按角色筛选user/admin"),
is_active: Optional[bool] = Query(None, description="按状态筛选"),
db: Session = Depends(get_db),
):
"""
获取用户列表
分页获取用户列表,支持按角色和状态筛选。
**返回字段**: id, email, username, role, quota_usd, used_usd, is_active, created_at 等
"""
adapter = AdminListUsersAdapter(skip=skip, limit=limit, role=role, is_active=is_active)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/{user_id}")
async def get_user(user_id: str, request: Request, db: Session = Depends(get_db)): # UUID
async def get_user(user_id: str, request: Request, db: Session = Depends(get_db)):
"""
获取用户详情
获取指定用户的详细信息。
**路径参数**:
- `user_id`: 用户 ID (UUID)
"""
adapter = AdminGetUserAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -55,19 +82,51 @@ async def update_user(
request: Request,
db: Session = Depends(get_db),
):
"""
更新用户信息
更新指定用户的信息,包括角色、配额、权限等。
**路径参数**:
- `user_id`: 用户 ID (UUID)
**请求体** (均为可选):
- `email`: 邮箱地址
- `username`: 用户名
- `role`: 角色
- `quota_usd`: 配额
- `is_active`: 是否启用
- `allowed_providers`: 允许的提供商列表
- `allowed_models`: 允许的模型列表
"""
adapter = AdminUpdateUserAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{user_id}")
async def delete_user(user_id: str, request: Request, db: Session = Depends(get_db)): # UUID
async def delete_user(user_id: str, request: Request, db: Session = Depends(get_db)):
"""
删除用户
永久删除指定用户。不能删除最后一个管理员账户。
**路径参数**:
- `user_id`: 用户 ID (UUID)
"""
adapter = AdminDeleteUserAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/{user_id}/quota")
async def reset_user_quota(user_id: str, request: Request, db: Session = Depends(get_db)):
"""Reset user quota (set used_usd to 0)"""
"""
重置用户配额
将用户的已用配额used_usd重置为 0。
**路径参数**:
- `user_id`: 用户 ID (UUID)
"""
adapter = AdminResetUserQuotaAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -76,10 +135,17 @@ async def reset_user_quota(user_id: str, request: Request, db: Session = Depends
async def get_user_api_keys(
user_id: str,
request: Request,
is_active: Optional[bool] = None,
is_active: Optional[bool] = Query(None, description="按状态筛选"),
db: Session = Depends(get_db),
):
"""获取用户的所有API Keys不包括独立Keys"""
"""
获取用户的 API 密钥列表
获取指定用户的所有 API 密钥(不包括独立密钥)。
**路径参数**:
- `user_id`: 用户 ID (UUID)
"""
adapter = AdminGetUserKeysAdapter(user_id=user_id, is_active=is_active)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -90,7 +156,23 @@ async def create_user_api_key(
request: Request,
db: Session = Depends(get_db),
):
"""为用户创建API Key"""
"""
为用户创建 API 密钥
为指定用户创建新的 API 密钥。
**路径参数**:
- `user_id`: 用户 ID (UUID)
**请求体**:
- `name`: 密钥名称
- `allowed_providers`: 允许的提供商(可选)
- `allowed_models`: 允许的模型(可选)
- `rate_limit`: 速率限制(可选)
- `expire_days`: 过期天数(可选)
**返回**: 包含完整密钥值的响应(仅此一次显示)
"""
adapter = AdminCreateUserKeyAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -102,7 +184,15 @@ async def delete_user_api_key(
request: Request,
db: Session = Depends(get_db),
):
"""删除用户的API Key"""
"""
删除用户的 API 密钥
删除指定用户的指定 API 密钥。
**路径参数**:
- `user_id`: 用户 ID (UUID)
- `key_id`: 密钥 ID
"""
adapter = AdminDeleteUserKeyAdapter(user_id=user_id, key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -35,7 +35,32 @@ async def list_announcements(
offset: int = Query(0, description="偏移量"),
db: Session = Depends(get_db),
):
"""获取公告列表(包含已读状态)"""
"""
获取公告列表
获取公告列表,支持分页和筛选。如果用户已登录,返回包含已读状态。
**查询参数**:
- `active_only`: 是否只返回有效公告,默认 true
- `limit`: 返回数量限制,默认 50
- `offset`: 分页偏移量,默认 0
**返回字段**:
- `items`: 公告列表,每条公告包含:
- `id`: 公告 ID
- `title`: 标题
- `content`: 内容
- `type`: 类型info/warning/error/success
- `priority`: 优先级
- `is_pinned`: 是否置顶
- `is_read`: 是否已读(仅登录用户)
- `author`: 作者信息
- `start_time`: 生效开始时间
- `end_time`: 生效结束时间
- `created_at`: 创建时间
- `total`: 总数
- `unread_count`: 未读数量(仅登录用户)
"""
adapter = ListAnnouncementsAdapter(active_only=active_only, limit=limit, offset=offset)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -45,7 +70,16 @@ async def get_active_announcements(
request: Request,
db: Session = Depends(get_db),
):
"""获取当前有效的公告(首页展示)"""
"""
获取当前有效的公告
获取当前时间范围内有效的公告列表,用于首页展示。
**返回字段**:
- `items`: 有效公告列表
- `total`: 有效公告总数
- `unread_count`: 未读数量(仅登录用户)
"""
adapter = GetActiveAnnouncementsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -56,7 +90,27 @@ async def get_announcement(
request: Request,
db: Session = Depends(get_db),
):
"""获取单个公告详情"""
"""
获取单个公告详情
获取指定公告的详细信息。
**路径参数**:
- `announcement_id`: 公告 IDUUID
**返回字段**:
- `id`: 公告 ID
- `title`: 标题
- `content`: 内容
- `type`: 类型info/warning/error/success
- `priority`: 优先级
- `is_pinned`: 是否置顶
- `author`: 作者信息id, username
- `start_time`: 生效开始时间
- `end_time`: 生效结束时间
- `created_at`: 创建时间
- `updated_at`: 更新时间
"""
adapter = GetAnnouncementAdapter(announcement_id=announcement_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -67,7 +121,17 @@ async def mark_announcement_as_read(
request: Request,
db: Session = Depends(get_db),
):
"""Mark announcement as read"""
"""
标记公告为已读
将指定公告标记为当前用户已读。需要登录。
**路径参数**:
- `announcement_id`: 公告 IDUUID
**返回字段**:
- `message`: 操作结果信息
"""
adapter = MarkAnnouncementReadAdapter(announcement_id=announcement_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -80,7 +144,25 @@ async def create_announcement(
request: Request,
db: Session = Depends(get_db),
):
"""创建公告(管理员权限)"""
"""
创建公告
创建新的系统公告。需要管理员权限。
**请求体字段**:
- `title`: 公告标题(必填)
- `content`: 公告内容(必填)
- `type`: 公告类型info/warning/error/success默认 info
- `priority`: 优先级0-100默认 0
- `is_pinned`: 是否置顶,默认 false
- `start_time`: 生效开始时间(可选)
- `end_time`: 生效结束时间(可选)
**返回字段**:
- `id`: 新创建的公告 ID
- `title`: 公告标题
- `message`: 操作结果信息
"""
adapter = CreateAnnouncementAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -91,7 +173,27 @@ async def update_announcement(
request: Request,
db: Session = Depends(get_db),
):
"""更新公告(管理员权限)"""
"""
更新公告
更新指定公告的信息。需要管理员权限。
**路径参数**:
- `announcement_id`: 公告 IDUUID
**请求体字段(均为可选)**:
- `title`: 公告标题
- `content`: 公告内容
- `type`: 公告类型info/warning/error/success
- `priority`: 优先级0-100
- `is_active`: 是否启用
- `is_pinned`: 是否置顶
- `start_time`: 生效开始时间
- `end_time`: 生效结束时间
**返回字段**:
- `message`: 操作结果信息
"""
adapter = UpdateAnnouncementAdapter(announcement_id=announcement_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -102,7 +204,17 @@ async def delete_announcement(
request: Request,
db: Session = Depends(get_db),
):
"""删除公告(管理员权限)"""
"""
删除公告
删除指定的公告。需要管理员权限。
**路径参数**:
- `announcement_id`: 公告 IDUUID
**返回字段**:
- `message`: 操作结果信息
"""
adapter = DeleteAnnouncementAdapter(announcement_id=announcement_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -115,7 +227,14 @@ async def get_my_unread_announcement_count(
request: Request,
db: Session = Depends(get_db),
):
"""获取我的未读公告数量"""
"""
获取我的未读公告数量
获取当前用户的未读公告数量。需要登录。
**返回字段**:
- `unread_count`: 未读公告数量
"""
adapter = UnreadAnnouncementCountAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -95,72 +95,142 @@ pipeline = ApiRequestPipeline()
# API端点
@router.get("/registration-settings", response_model=RegistrationSettingsResponse)
async def registration_settings(request: Request, db: Session = Depends(get_db)):
"""公开获取注册相关配置"""
"""
获取注册相关配置
返回系统注册配置,包括是否开放注册、是否需要邮箱验证等。
此接口为公开接口,无需认证。
"""
adapter = AuthRegistrationSettingsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/settings")
async def auth_settings(request: Request, db: Session = Depends(get_db)):
"""公开获取认证设置(用于前端判断显示哪些登录选项)"""
"""
获取认证设置
返回系统支持的认证方式如本地认证、LDAP 认证等。
前端据此判断显示哪些登录选项。此接口为公开接口,无需认证。
"""
adapter = AuthSettingsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/login", response_model=LoginResponse)
async def login(request: Request, db: Session = Depends(get_db)):
"""
用户登录
使用邮箱和密码登录,成功后返回 JWT access_token 和 refresh_token。
- **access_token**: 用于后续 API 调用,有效期 24 小时
- **refresh_token**: 用于刷新 access_token
速率限制: 5次/分钟/IP
"""
adapter = AuthLoginAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/refresh", response_model=RefreshTokenResponse)
async def refresh_token(request: Request, db: Session = Depends(get_db)):
"""
刷新访问令牌
使用 refresh_token 获取新的 access_token 和 refresh_token。
原 refresh_token 刷新后失效。
"""
adapter = AuthRefreshAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/register", response_model=RegisterResponse)
async def register(request: Request, db: Session = Depends(get_db)):
"""
用户注册
创建新用户账号。需要系统开放注册功能。
如果系统开启了邮箱验证,需先通过 /send-verification-code 和 /verify-email 完成邮箱验证。
速率限制: 3次/分钟/IP
"""
adapter = AuthRegisterAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/me")
async def get_current_user_info(request: Request, db: Session = Depends(get_db)):
"""
获取当前用户信息
返回当前登录用户的基本信息,包括邮箱、用户名、角色、配额等。
需要 Bearer Token 认证。
"""
adapter = AuthCurrentUserAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/password")
async def change_password(request: Request, db: Session = Depends(get_db)):
"""Change current user's password"""
"""
修改密码
修改当前用户的登录密码,需提供旧密码验证。
密码长度至少 6 位。
"""
adapter = AuthChangePasswordAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/logout", response_model=LogoutResponse)
async def logout(request: Request, db: Session = Depends(get_db)):
"""
用户登出
将当前 Token 加入黑名单,使其失效。
"""
adapter = AuthLogoutAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/send-verification-code", response_model=SendVerificationCodeResponse)
async def send_verification_code(request: Request, db: Session = Depends(get_db)):
"""发送邮箱验证码"""
"""
发送邮箱验证码
向指定邮箱发送验证码,用于注册前的邮箱验证。
验证码有效期 5 分钟,同一邮箱 60 秒内只能发送一次。
速率限制: 3次/分钟/IP
"""
adapter = AuthSendVerificationCodeAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/verify-email", response_model=VerifyEmailResponse)
async def verify_email(request: Request, db: Session = Depends(get_db)):
"""验证邮箱验证码"""
"""
验证邮箱验证码
验证邮箱收到的验证码是否正确。
验证成功后,邮箱会被标记为已验证状态,可用于注册。
速率限制: 10次/分钟/IP
"""
adapter = AuthVerifyEmailAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/verification-status", response_model=VerificationStatusResponse)
async def verification_status(request: Request, db: Session = Depends(get_db)):
"""查询邮箱验证状态"""
"""
查询邮箱验证状态
查询指定邮箱的验证状态,包括是否有待验证的验证码、是否已验证等。
速率限制: 20次/分钟/IP
"""
adapter = AuthVerificationStatusAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -15,6 +15,7 @@ class ApiMode(str, Enum):
ADMIN = "admin"
USER = "user" # JWT 认证的普通用户(不要求管理员权限)
PUBLIC = "public"
MANAGEMENT = "management" # Management Token 认证
class ApiAdapter(ABC):

View File

@@ -10,7 +10,7 @@ from fastapi import HTTPException, Request
from sqlalchemy.orm import Session
from src.core.logger import logger
from src.models.database import ApiKey, User
from src.models.database import ApiKey, ManagementToken, User
from src.utils.request_utils import get_client_ip
@@ -38,6 +38,9 @@ class ApiRequestContext:
# URL 路径参数(如 Gemini API 的 /v1beta/models/{model}:generateContent
path_params: Dict[str, Any] = field(default_factory=dict)
# Management Token用于管理 API 认证)
management_token: Optional[ManagementToken] = None
# 供适配器扩展的状态存储
extra: Dict[str, Any] = field(default_factory=dict)
audit_metadata: Dict[str, Any] = field(default_factory=dict)

View File

@@ -2,19 +2,23 @@ from __future__ import annotations
import time
from enum import Enum
from typing import Any, Optional, Tuple
from typing import TYPE_CHECKING, Any, Optional, Tuple
from fastapi import HTTPException, Request
from sqlalchemy.orm import Session
from src.config.settings import config
from src.core.enums import UserRole
from src.core.exceptions import QuotaExceededException
from src.core.logger import logger
from src.models.database import ApiKey, AuditEventType, User, UserRole
from src.models.database import ApiKey, AuditEventType, User
from src.services.auth.service import AuthService
from src.services.system.audit import AuditService
from src.services.usage.service import UsageService
if TYPE_CHECKING:
from src.models.database import ManagementToken
from .adapter import ApiAdapter, ApiMode
from .context import ApiRequestContext
@@ -47,17 +51,22 @@ class ApiRequestPipeline:
logger.debug(f"[Pipeline] Running with mode={mode}, adapter={adapter.__class__.__name__}, "
f"adapter.mode={adapter.mode}, path={http_request.url.path}")
if mode == ApiMode.ADMIN:
user = await self._authenticate_admin(http_request, db)
user, management_token = await self._authenticate_admin(http_request, db)
api_key = None
elif mode == ApiMode.USER:
user = await self._authenticate_user(http_request, db)
user, management_token = await self._authenticate_user(http_request, db)
api_key = None
elif mode == ApiMode.PUBLIC:
user = None
api_key = None
management_token = None
elif mode == ApiMode.MANAGEMENT:
user, management_token = await self._authenticate_management(http_request, db)
api_key = None
else:
logger.debug("[Pipeline] 调用 _authenticate_client")
user, api_key = self._authenticate_client(http_request, db, adapter)
management_token = None
logger.debug(f"[Pipeline] 认证完成 | user={user.username if user else None}")
raw_body = None
@@ -90,6 +99,9 @@ class ApiRequestPipeline:
api_format_hint=api_format_hint,
path_params=path_params,
)
# 存储 management_token 到 context用于权限检查
if management_token:
context.management_token = management_token
logger.debug(f"[Pipeline] Context构建完成 | adapter={adapter.name} | request_id={context.request_id}")
if mode != ApiMode.ADMIN and user:
@@ -177,12 +189,41 @@ class ApiRequestPipeline:
return user, api_key
async def _authenticate_admin(self, request: Request, db: Session) -> User:
async def _authenticate_admin(
self, request: Request, db: Session
) -> Tuple[User, Optional["ManagementToken"]]:
"""管理员认证,支持 JWT 和 Management Token 两种方式"""
from src.models.database import ManagementToken
from src.utils.request_utils import get_client_ip
authorization = request.headers.get("authorization")
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="缺少管理员凭证")
token = authorization[7:].strip()
# 检查是否为 Management Tokenae_ 前缀)
if token.startswith(ManagementToken.TOKEN_PREFIX):
client_ip = get_client_ip(request)
result = await self.auth_service.authenticate_management_token(db, token, client_ip)
if not result:
raise HTTPException(status_code=401, detail="无效或过期的 Management Token")
user, management_token = result
# 检查管理员权限
if user.role != UserRole.ADMIN:
logger.warning(f"非管理员尝试通过 Management Token 访问管理端点: {user.email}")
raise HTTPException(status_code=403, detail="需要管理员权限")
# 存储到 request.state
request.state.user_id = user.id
request.state.management_token_id = management_token.id
return user, management_token
# JWT 认证
try:
payload = await self.auth_service.verify_token(token, token_type="access")
except HTTPException:
@@ -200,16 +241,43 @@ class ApiRequestPipeline:
if not user or not user.is_active:
raise HTTPException(status_code=403, detail="用户不存在或已禁用")
request.state.user_id = user.id
return user
# 检查管理员权限
if user.role != UserRole.ADMIN:
logger.warning(f"非管理员尝试通过 JWT 访问管理端点: {user.email}")
raise HTTPException(status_code=403, detail="需要管理员权限")
request.state.user_id = user.id
return user, None
async def _authenticate_user(
self, request: Request, db: Session
) -> Tuple[User, Optional["ManagementToken"]]:
"""用户认证,支持 JWT 和 Management Token 两种方式"""
from src.models.database import ManagementToken
from src.utils.request_utils import get_client_ip
async def _authenticate_user(self, request: Request, db: Session) -> User:
"""JWT 认证普通用户(不要求管理员权限)"""
authorization = request.headers.get("authorization")
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="缺少用户凭证")
token = authorization[7:].strip()
# 检查是否为 Management Tokenae_ 前缀)
if token.startswith(ManagementToken.TOKEN_PREFIX):
client_ip = get_client_ip(request)
result = await self.auth_service.authenticate_management_token(db, token, client_ip)
if not result:
raise HTTPException(status_code=401, detail="无效或过期的 Management Token")
user, management_token = result
request.state.user_id = user.id
request.state.management_token_id = management_token.id
return user, management_token
# JWT 认证
try:
payload = await self.auth_service.verify_token(token, token_type="access")
except HTTPException:
@@ -222,13 +290,47 @@ class ApiRequestPipeline:
if not user_id:
raise HTTPException(status_code=401, detail="无效的用户令牌")
# 直接查询数据库,确保返回的是当前 Session 绑定的对象
user = db.query(User).filter(User.id == user_id).first()
if not user or not user.is_active:
raise HTTPException(status_code=403, detail="用户不存在或已禁用")
request.state.user_id = user.id
return user
return user, None
async def _authenticate_management(
self, request: Request, db: Session
) -> Tuple[User, "ManagementToken"]:
"""Management Token 认证"""
from src.models.database import ManagementToken
from src.utils.request_utils import get_client_ip
authorization = request.headers.get("authorization")
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="缺少 Management Token")
token = authorization[7:].strip()
# 检查是否为 Management Token 格式
if not token.startswith(ManagementToken.TOKEN_PREFIX):
raise HTTPException(
status_code=401,
detail=f"无效的 Token 格式,需要 Management Token ({ManagementToken.TOKEN_PREFIX}xxx)",
)
client_ip = get_client_ip(request)
result = await self.auth_service.authenticate_management_token(db, token, client_ip)
if not result:
raise HTTPException(status_code=401, detail="无效或过期的 Management Token")
user, management_token = result
# 存储到 request.state
request.state.user_id = user.id
request.state.management_token_id = management_token.id
return user, management_token
def _calculate_quota_remaining(self, user: Optional[User]) -> Optional[float]:
if not user:

View File

@@ -45,6 +45,29 @@ def format_tokens(num: int) -> str:
@router.get("/stats")
async def get_dashboard_stats(request: Request, db: Session = Depends(get_db)):
"""
获取仪表盘统计数据
根据用户角色返回不同的统计数据。管理员可以看到全局数据,普通用户只能看到自己的数据。
**返回字段(管理员)**:
- `stats`: 统计卡片数组包含总请求、总费用、总Token、总缓存等信息
- `today`: 今日统计requests, cost, actual_cost, tokens, cache_creation_tokens, cache_read_tokens
- `api_keys`: API Key 统计total, active
- `tokens`: 本月 Token 统计
- `token_breakdown`: Token 详细分类input, output, cache_creation, cache_read
- `system_health`: 系统健康指标avg_response_time, error_rate, error_requests, fallback_count, total_requests
- `cost_stats`: 成本统计total_cost, total_actual_cost, cost_savings
- `cache_stats`: 缓存统计信息
- `users`: 用户统计total, active
**返回字段(普通用户)**:
- `stats`: 统计卡片数组,包含 API 密钥、本月请求、配额使用、总Token 等信息
- `today`: 今日统计
- `token_breakdown`: Token 详细分类
- `cache_stats`: 缓存统计信息
- `monthly_cost`: 本月费用
"""
adapter = DashboardStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -55,6 +78,23 @@ async def get_recent_requests(
limit: int = Query(10, ge=1, le=100),
db: Session = Depends(get_db),
):
"""
获取最近请求列表
获取最近的 API 请求记录。管理员可以看到所有用户的请求,普通用户只能看到自己的请求。
**查询参数**:
- `limit`: 返回记录数,默认 10最大 100
**返回字段**:
- `requests`: 请求列表,每条记录包含:
- `id`: 请求 ID
- `user`: 用户名
- `model`: 使用的模型
- `tokens`: Token 数量
- `time`: 请求时间HH:MM 格式)
- `is_stream`: 是否为流式请求
"""
adapter = DashboardRecentRequestsAdapter(limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -65,6 +105,17 @@ async def get_recent_requests(
@router.get("/provider-status")
async def get_provider_status(request: Request, db: Session = Depends(get_db)):
"""
获取提供商状态
获取所有活跃提供商的状态和最近 24 小时的请求统计。
**返回字段**:
- `providers`: 提供商列表,每个提供商包含:
- `name`: 提供商名称
- `status`: 状态active/inactive
- `requests`: 最近 24 小时的请求数
"""
adapter = DashboardProviderStatusAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -75,6 +126,28 @@ async def get_daily_stats(
days: int = Query(7, ge=1, le=30),
db: Session = Depends(get_db),
):
"""
获取每日统计数据
获取指定天数的每日使用统计数据,用于生成图表。
**查询参数**:
- `days`: 统计天数,默认 7 天,最大 30 天
**返回字段**:
- `daily_stats`: 每日统计数组,每天包含:
- `date`: 日期ISO 格式)
- `requests`: 请求数
- `tokens`: Token 数量
- `cost`: 费用USD
- `avg_response_time`: 平均响应时间(秒)
- `unique_models`: 使用的模型数量(仅管理员)
- `unique_providers`: 使用的提供商数量(仅管理员)
- `fallback_count`: 故障转移次数(仅管理员)
- `model_breakdown`: 按模型分解的统计(仅管理员)
- `model_summary`: 模型使用汇总,按费用排序
- `period`: 统计周期信息start_date, end_date, days
"""
adapter = DashboardDailyStatsAdapter(days=days)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -28,12 +28,48 @@ async def get_my_audit_logs(
offset: int = Query(0, ge=0, description="偏移量"),
db: Session = Depends(get_db),
):
"""
获取我的审计日志
获取当前用户的审计日志记录。需要登录。
**查询参数**:
- `event_type`: 可选,事件类型筛选
- `days`: 查询最近多少天的日志,默认 30 天
- `limit`: 返回数量限制,默认 50
- `offset`: 分页偏移量,默认 0
**返回字段**:
- `items`: 审计日志列表,每条日志包含:
- `id`: 日志 ID
- `event_type`: 事件类型
- `description`: 事件描述
- `ip_address`: IP 地址
- `status_code`: HTTP 状态码
- `created_at`: 创建时间
- `meta`: 分页元数据total, limit, offset, count
- `filters`: 筛选条件
"""
adapter = UserAuditLogsAdapter(event_type=event_type, days=days, limit=limit, offset=offset)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/rate-limit-status")
async def get_rate_limit_status(request: Request, db: Session = Depends(get_db)):
"""
获取速率限制状态
获取当前用户所有活跃 API Key 的速率限制状态。需要登录。
**返回字段**:
- `user_id`: 用户 ID
- `api_keys`: API Key 限流状态列表,每个包含:
- `api_key_name`: API Key 名称
- `limit`: 速率限制上限
- `remaining`: 剩余可用次数
- `reset_time`: 限制重置时间
- `window`: 时间窗口
"""
adapter = UserRateLimitStatusAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)

View File

@@ -13,12 +13,26 @@ from src.core.key_capabilities import (
)
from src.database import get_db
router = APIRouter(prefix="/api/capabilities", tags=["Capabilities"])
router = APIRouter(prefix="/api/capabilities", tags=["System Catalog"])
@router.get("")
async def list_capabilities():
"""获取所有能力定义"""
"""
获取所有能力定义
返回系统中定义的所有能力capabilities包括用户可配置和系统内部使用的能力。
能力用于描述模型支持的功能特性,如视觉输入、函数调用、流式输出等。
**返回字段**
- capabilities: 能力列表,每个能力包含:
- name: 能力的唯一标识符(如 vision、function_calling
- display_name: 能力的显示名称(如"视觉输入""函数调用"
- short_name: 能力的简短名称(如"视觉""函数"
- description: 能力的详细描述
- match_mode: 匹配模式exact 精确匹配fuzzy 模糊匹配prefix 前缀匹配等)
- config_mode: 配置模式user_configurable 用户可配置system_only 仅系统使用)
"""
return {
"capabilities": [
{
@@ -36,7 +50,21 @@ async def list_capabilities():
@router.get("/user-configurable")
async def list_user_configurable_capabilities():
"""获取用户可配置的能力列表(用于前端展示配置选项)"""
"""
获取用户可配置的能力列表
返回允许用户在 API Key 中配置的能力列表,用于前端展示配置选项。
用户可以通过配置这些能力来限制或指定 API Key 可以访问的模型功能。
**返回字段**
- capabilities: 用户可配置的能力列表,每个能力包含:
- name: 能力的唯一标识符
- display_name: 能力的显示名称
- short_name: 能力的简短名称
- description: 能力的详细描述
- match_mode: 匹配模式exact、fuzzy、prefix 等)
- config_mode: 配置模式(此接口返回的都是 user_configurable
"""
return {
"capabilities": [
{
@@ -60,11 +88,24 @@ async def get_model_supported_capabilities(
"""
获取指定模型支持的能力列表
Args:
model_name: 模型名称(如 claude-sonnet-4-20250514必须是 GlobalModel.name
根据全局模型名称GlobalModel.name查询该模型支持的能力
并返回每个能力的详细定义。只查询活跃的全局模型。
Returns:
模型支持的能力列表,以及每个能力的详细定义
**路径参数**
- model_name: 全局模型名称(如 claude-sonnet-4-20250514必须是 GlobalModel.name
**返回字段**
- model: 查询的模型名称
- global_model_id: 全局模型的 UUID
- global_model_name: 全局模型的标准名称
- supported_capabilities: 该模型支持的能力名称列表
- capability_details: 支持的能力详细信息列表,每个能力包含:
- name: 能力标识符
- display_name: 能力显示名称
- description: 能力描述
- match_mode: 匹配模式
- config_mode: 配置模式
- error: 错误信息(仅在模型不存在时返回)
"""
from src.models.database import GlobalModel

View File

@@ -37,7 +37,7 @@ from src.models.endpoint_models import (
)
from src.services.health.endpoint import EndpointHealthService
router = APIRouter(prefix="/api/public", tags=["Public Catalog"])
router = APIRouter(prefix="/api/public", tags=["System Catalog"])
pipeline = ApiRequestPipeline()
@@ -49,7 +49,29 @@ async def get_public_providers(
limit: int = Query(100, description="返回记录数限制"),
db: Session = Depends(get_db),
):
"""获取提供商列表(用户视图)。"""
"""
获取提供商列表(用户视图)
返回系统中可用的提供商列表,包含提供商的基本信息和统计数据。
默认只返回活跃的提供商。
**查询参数**
- is_active: 可选过滤活跃状态。None 表示只返回活跃提供商True 返回活跃False 返回非活跃
- skip: 跳过的记录数,用于分页,默认 0
- limit: 返回记录数限制,默认 100最大 100
**返回字段**
- id: 提供商唯一标识符
- name: 提供商名称(英文标识)
- display_name: 提供商显示名称
- description: 提供商描述信息
- is_active: 是否活跃
- provider_priority: 提供商优先级
- models_count: 该提供商下的模型总数
- active_models_count: 该提供商下活跃的模型数
- endpoints_count: 该提供商下的端点总数
- active_endpoints_count: 该提供商下活跃的端点数
"""
adapter = PublicProvidersAdapter(is_active=is_active, skip=skip, limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.PUBLIC)
@@ -64,6 +86,37 @@ async def get_public_models(
limit: int = Query(100, description="返回记录数限制"),
db: Session = Depends(get_db),
):
"""
获取模型列表(用户视图)
返回系统中可用的模型列表,包含模型的详细信息和定价。
默认只返回活跃提供商下的活跃模型。
**查询参数**
- provider_id: 可选,按提供商 ID 过滤,只返回该提供商下的模型
- is_active: 可选,过滤活跃状态(当前未使用,始终返回活跃模型)
- skip: 跳过的记录数,用于分页,默认 0
- limit: 返回记录数限制,默认 100最大 100
**返回字段**
- id: 模型唯一标识符
- provider_id: 所属提供商 ID
- provider_name: 提供商名称
- provider_display_name: 提供商显示名称
- name: 模型统一名称(优先使用 GlobalModel 名称)
- display_name: 模型显示名称
- description: 模型描述信息
- tags: 模型标签(当前为 null
- icon_url: 模型图标 URL
- input_price_per_1m: 输入价格(每 100 万 token
- output_price_per_1m: 输出价格(每 100 万 token
- cache_creation_price_per_1m: 缓存创建价格(每 100 万 token
- cache_read_price_per_1m: 缓存读取价格(每 100 万 token
- supports_vision: 是否支持视觉输入
- supports_function_calling: 是否支持函数调用
- supports_streaming: 是否支持流式输出
- is_active: 是否活跃
"""
adapter = PublicModelsAdapter(
provider_id=provider_id, is_active=is_active, skip=skip, limit=limit
)
@@ -72,6 +125,19 @@ async def get_public_models(
@router.get("/stats", response_model=ProviderStatsResponse)
async def get_public_stats(request: Request, db: Session = Depends(get_db)):
"""
获取系统统计信息
返回系统的整体统计数据,包括提供商数量、模型数量和支持的 API 格式。
只统计活跃的提供商和模型。
**返回字段**
- total_providers: 活跃提供商总数
- active_providers: 活跃提供商数量(与 total_providers 相同)
- total_models: 活跃模型总数
- active_models: 活跃模型数量(与 total_models 相同)
- supported_formats: 支持的 API 格式列表(如 claude、openai、gemini 等)
"""
adapter = PublicStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.PUBLIC)
@@ -84,6 +150,37 @@ async def search_models(
limit: int = Query(20, description="返回记录数限制"),
db: Session = Depends(get_db),
):
"""
搜索模型
根据关键词搜索模型,支持按模型名称、显示名称等字段进行模糊匹配。
只返回活跃提供商下的活跃模型。
**查询参数**
- q: 必填,搜索关键词,支持模糊匹配模型的 provider_model_name、GlobalModel.name 或 GlobalModel.display_name
- provider_id: 可选,按提供商 ID 过滤,只在该提供商下搜索
- limit: 返回记录数限制,默认 20最大值取决于系统配置
**返回字段**
返回符合条件的模型列表,字段与 /api/public/models 接口相同:
- id: 模型唯一标识符
- provider_id: 所属提供商 ID
- provider_name: 提供商名称
- provider_display_name: 提供商显示名称
- name: 模型统一名称
- display_name: 模型显示名称
- description: 模型描述
- tags: 模型标签
- icon_url: 模型图标 URL
- input_price_per_1m: 输入价格(每 100 万 token
- output_price_per_1m: 输出价格(每 100 万 token
- cache_creation_price_per_1m: 缓存创建价格(每 100 万 token
- cache_read_price_per_1m: 缓存读取价格(每 100 万 token
- supports_vision: 是否支持视觉
- supports_function_calling: 是否支持函数调用
- supports_streaming: 是否支持流式输出
- is_active: 是否活跃
"""
adapter = PublicSearchModelsAdapter(query=q, provider_id=provider_id, limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.PUBLIC)
@@ -95,7 +192,37 @@ async def get_public_api_format_health(
per_format_limit: int = Query(100, ge=10, le=500, description="每个格式的事件数限制"),
db: Session = Depends(get_db),
):
"""获取各 API 格式的健康监控数据(公开版,不含敏感信息)"""
"""
获取各 API 格式的健康监控数据
返回系统中各 API 格式(如 Claude、OpenAI、Gemini的健康状态和历史事件。
公开版本,不包含敏感信息(如 provider_id、key_id 等)。
**查询参数**
- lookback_hours: 回溯的时间范围(小时),默认 6 小时,范围 1-1687 天)
- per_format_limit: 每个 API 格式返回的历史事件数量上限,默认 100范围 10-500
**返回字段**
- generated_at: 响应生成时间
- formats: API 格式健康监控数据列表,每个格式包含:
- api_format: API 格式名称(如 claude、openai、gemini
- api_path: 本站入口路径
- total_attempts: 总请求尝试次数
- success_count: 成功次数
- failed_count: 失败次数
- skipped_count: 跳过次数
- success_rate: 成功率success / (success + failed)
- last_event_at: 最后事件时间
- events: 历史事件列表,按时间倒序,每个事件包含:
- timestamp: 事件时间
- status: 状态success、failed、skipped
- status_code: HTTP 状态码
- latency_ms: 延迟(毫秒)
- error_type: 错误类型(如果失败)
- timeline: 时间线数据,用于展示请求量趋势
- time_range_start: 时间范围起始
- time_range_end: 时间范围结束
"""
adapter = PublicApiFormatHealthMonitorAdapter(
lookback_hours=lookback_hours,
per_format_limit=per_format_limit,
@@ -112,7 +239,30 @@ async def get_public_global_models(
search: Optional[str] = Query(None, description="搜索关键词"),
db: Session = Depends(get_db),
):
"""获取 GlobalModel 列表(用户视图,只读)"""
"""
获取全局模型GlobalModel列表
返回系统定义的全局模型列表,用于统一不同提供商的模型标识。
默认只返回活跃的全局模型。
**查询参数**
- skip: 跳过的记录数,用于分页,默认 0最小 0
- limit: 返回记录数限制,默认 100范围 1-1000
- is_active: 可选过滤活跃状态。None 表示只返回活跃模型True 返回活跃False 返回非活跃
- search: 可选搜索关键词支持模糊匹配模型名称name和显示名称display_name
**返回字段**
- models: 全局模型列表,每个模型包含:
- id: 全局模型唯一标识符UUID
- name: 模型名称(统一标识符)
- display_name: 模型显示名称
- is_active: 是否活跃
- default_price_per_request: 默认的按请求计价配置
- default_tiered_pricing: 默认的阶梯定价配置
- supported_capabilities: 支持的能力列表(如 vision、function_calling 等)
- config: 模型配置信息(如 description、icon_url 等)
- total: 符合条件的模型总数
"""
adapter = PublicGlobalModelsAdapter(
skip=skip,
limit=limit,

View File

@@ -29,7 +29,27 @@ async def create_message(
http_request: Request,
db: Session = Depends(get_db),
):
"""统一入口:根据 x-app 自动在标准/Claude Code 之间切换。"""
"""
Claude Messages API
兼容 Anthropic Claude Messages API 格式的代理接口。
根据请求头 `x-app` 自动在标准 API 和 Claude Code CLI 模式之间切换。
**认证方式**: x-api-key 请求头
**请求格式**:
```json
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}
```
**必需请求头**:
- `x-api-key`: API 密钥
- `anthropic-version`: API 版本(如 2023-06-01
"""
adapter = build_claude_adapter(http_request.headers.get("x-app", ""))
return await pipeline.run(
adapter=adapter,
@@ -45,6 +65,13 @@ async def count_tokens(
http_request: Request,
db: Session = Depends(get_db),
):
"""
Claude Token Count API
计算消息的 Token 数量,用于预估请求成本。
**认证方式**: x-api-key 请求头
"""
adapter = ClaudeTokenCountAdapter()
return await pipeline.run(
adapter=adapter,

View File

@@ -56,9 +56,23 @@ async def generate_content(
db: Session = Depends(get_db),
):
"""
Gemini generateContent 端点
Gemini generateContent API
非流式生成内容请求
兼容 Google Gemini API 格式的代理接口(非流式)。
**认证方式**:
- `x-goog-api-key` 请求头,或
- `?key=` URL 参数
**请求格式**:
```json
{
"contents": [{"parts": [{"text": "Hello"}]}]
}
```
**路径参数**:
- `model`: 模型名称,如 gemini-2.0-flash
"""
# 根据 user-agent 或 x-app header 选择适配器
if _is_cli_request(http_request):
@@ -84,9 +98,16 @@ async def stream_generate_content(
db: Session = Depends(get_db),
):
"""
Gemini streamGenerateContent 端点
Gemini streamGenerateContent API
流式生成内容请求
兼容 Google Gemini API 格式的代理接口(流式)。
**认证方式**:
- `x-goog-api-key` 请求头,或
- `?key=` URL 参数
**路径参数**:
- `model`: 模型名称,如 gemini-2.0-flash
注意: Gemini API 通过 URL 端点区分流式/非流式,不需要在请求体中添加 stream 字段
"""
@@ -114,7 +135,11 @@ async def generate_content_v1(
http_request: Request,
db: Session = Depends(get_db),
):
"""v1 兼容端点"""
"""
Gemini generateContent API (v1 兼容)
v1 版本 API 端点,兼容部分使用旧版路径的 SDK。
"""
return await generate_content(model, http_request, db)
@@ -124,5 +149,9 @@ async def stream_generate_content_v1(
http_request: Request,
db: Session = Depends(get_db),
):
"""v1 兼容端点"""
"""
Gemini streamGenerateContent API (v1 兼容)
v1 版本流式 API 端点,兼容部分使用旧版路径的 SDK。
"""
return await stream_generate_content(model, http_request, db)

View File

@@ -27,7 +27,7 @@ from src.database import get_db
from src.models.database import ApiKey, User
from src.services.auth.service import AuthService
router = APIRouter(tags=["Models API"])
router = APIRouter(tags=["System Catalog"])
# 各格式对应的 API 格式列表
# 注意: CLI 格式是透传格式Models API 只返回非 CLI 格式的端点支持的模型
@@ -395,11 +395,65 @@ async def list_models(
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""
List models - 根据请求头认证方式返回对应格式
列出可用模型(统一端点)
- x-api-key -> Claude 格式
- x-goog-api-key 或 ?key= -> Gemini 格式
- Authorization: Bearer -> OpenAI 格式
根据请求头中的认证方式自动检测 API 格式,并返回相应格式的模型列表。
此接口兼容 Claude、OpenAI 和 Gemini 三种 API 格式
**格式检测规则**
- x-api-key + anthropic-version → Claude 格式
- x-goog-api-key 或 ?key= → Gemini 格式
- Authorization: Bearer → OpenAI 格式(默认)
**查询参数**
Claude 格式:
- before_id: 返回此 ID 之前的结果,用于向前分页
- after_id: 返回此 ID 之后的结果,用于向后分页
- limit: 返回数量限制,默认 20范围 1-1000
Gemini 格式:
- pageSize: 每页数量,默认 50范围 1-1000
- pageToken: 分页 token用于获取下一页
**返回字段**
Claude 格式:
- data: 模型列表,每个模型包含:
- id: 模型标识符
- type: "model"
- display_name: 显示名称
- created_at: 创建时间ISO 8601 格式)
- has_more: 是否有更多结果
- first_id: 当前页第一个模型 ID
- last_id: 当前页最后一个模型 ID
OpenAI 格式:
- object: "list"
- data: 模型列表,每个模型包含:
- id: 模型标识符
- object: "model"
- created: Unix 时间戳
- owned_by: 提供商名称
Gemini 格式:
- models: 模型列表,每个模型包含:
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
- nextPageToken: 下一页的 token如果有更多结果
**错误响应**
401: API Key 无效或未提供(格式根据检测到的 API 格式返回)
"""
api_format, api_key = _detect_api_format_and_key(request)
logger.info(f"[Models] GET /v1/models | format={api_format}")
@@ -440,7 +494,50 @@ async def retrieve_model(
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""
Retrieve model - 根据请求头认证方式返回对应格式
获取单个模型详情(统一端点)
根据请求头中的认证方式自动检测 API 格式,并返回相应格式的模型详情。
此接口兼容 Claude、OpenAI 和 Gemini 三种 API 格式。
**格式检测规则**
- x-api-key + anthropic-version → Claude 格式
- x-goog-api-key 或 ?key= → Gemini 格式
- Authorization: Bearer → OpenAI 格式(默认)
**路径参数**
- model_id: 模型标识符Gemini 格式支持 models/ 前缀,会自动移除)
**返回字段**
Claude 格式:
- id: 模型标识符
- type: "model"
- display_name: 显示名称
- created_at: 创建时间ISO 8601 格式)
OpenAI 格式:
- id: 模型标识符
- object: "model"
- created: Unix 时间戳
- owned_by: 提供商名称
Gemini 格式:
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
**错误响应**
401: API Key 无效或未提供
404: 模型不存在或不可访问
"""
api_format, api_key = _detect_api_format_and_key(request)
@@ -486,7 +583,35 @@ async def list_models_gemini(
page_token: Optional[str] = Query(None, alias="pageToken"),
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""List models (Gemini v1beta 端点)"""
"""
列出可用模型Gemini v1beta 专用端点)
Gemini API 的专用模型列表端点,使用 x-goog-api-key 或 ?key= 参数进行认证。
返回 Gemini 格式的模型列表。
**查询参数**
- pageSize: 每页数量,默认 50范围 1-1000
- pageToken: 分页 token用于获取下一页
**返回字段**
- models: 模型列表,每个模型包含:
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法列表
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
- nextPageToken: 下一页的 token如果有更多结果
**错误响应**
401: API Key 无效或未提供
"""
logger.info("[Models] GET /v1beta/models | format=gemini")
# 从 x-goog-api-key 或 ?key= 提取 API Key
@@ -525,7 +650,33 @@ async def get_model_gemini(
model_name: str,
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""Get model (Gemini v1beta 端点)"""
"""
获取单个模型详情Gemini v1beta 专用端点)
Gemini API 的专用模型详情端点,使用 x-goog-api-key 或 ?key= 参数进行认证。
返回 Gemini 格式的模型详情。
**路径参数**
- model_name: 模型名称或资源路径(支持 models/ 前缀,会自动移除)
**返回字段**
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法列表
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
**错误响应**
401: API Key 无效或未提供
404: 模型不存在或不可访问
"""
# 移除 "models/" 前缀(如果有)
model_id = model_name[7:] if model_name.startswith("models/") else model_name
logger.info(f"[Models] GET /v1beta/models/{model_id} | format=gemini")

View File

@@ -27,6 +27,24 @@ async def create_chat_completion(
http_request: Request,
db: Session = Depends(get_db),
):
"""
OpenAI Chat Completions API
兼容 OpenAI Chat Completions API 格式的代理接口。
**认证方式**: Bearer TokenAPI Key 或 JWT Token
**请求格式**:
```json
{
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}],
"stream": false
}
```
**支持的参数**: model, messages, stream, temperature, max_tokens 等标准 OpenAI 参数
"""
adapter = OpenAIChatAdapter()
return await pipeline.run(
adapter=adapter,
@@ -42,6 +60,13 @@ async def create_responses(
http_request: Request,
db: Session = Depends(get_db),
):
"""
OpenAI Responses API (CLI)
兼容 OpenAI Codex CLI 使用的 Responses API 格式,请求透传到上游。
**认证方式**: Bearer TokenAPI Key 或 JWT Token
"""
adapter = OpenAICliAdapter()
return await pipeline.run(
adapter=adapter,

View File

@@ -2,9 +2,11 @@
from fastapi import APIRouter
from .management_tokens import router as management_tokens_router
from .routes import router as me_router
router = APIRouter()
router.include_router(me_router)
router.include_router(management_tokens_router)
__all__ = ["router"]

View File

@@ -0,0 +1,577 @@
"""用户 Management Token 管理端点"""
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, field_validator
from sqlalchemy.orm import Session
from src.api.base.authenticated_adapter import AuthenticatedApiAdapter
from src.api.base.context import ApiRequestContext
from src.api.base.pipeline import ApiRequestPipeline
from src.core.exceptions import InvalidRequestException, NotFoundException
from src.database import get_db
from src.models.database import AuditEventType
from src.services.management_token import (
ManagementTokenService,
parse_expires_at,
token_to_dict,
validate_ip_list,
)
router = APIRouter(prefix="/api/me/management-tokens", tags=["Management Tokens"])
pipeline = ApiRequestPipeline()
# ============== 安全基类 ==============
class ManagementTokenApiAdapter(AuthenticatedApiAdapter):
"""Management Token 管理 API 的基类
安全限制:禁止使用 Management Token 调用这些接口,
防止用户通过已有的 Token 再创建/修改/删除其他 Token。
"""
def authorize(self, context: ApiRequestContext):
# 先调用父类的认证检查
super().authorize(context)
# 禁止使用 Management Token 调用 management-tokens 相关接口
if context.management_token is not None:
raise HTTPException(
status_code=403,
detail="不允许使用 Management Token 管理其他 Token请使用 Web 界面或 JWT 认证",
)
# ============== 请求/响应模型 ==============
class CreateManagementTokenRequest(BaseModel):
"""创建 Management Token 请求"""
name: str = Field(..., min_length=1, max_length=100, description="Token 名称")
description: Optional[str] = Field(None, max_length=500, description="描述")
allowed_ips: Optional[list[str]] = Field(None, description="IP 白名单")
expires_at: Optional[datetime] = Field(None, description="过期时间")
@field_validator("allowed_ips")
@classmethod
def validate_allowed_ips(cls, v: Optional[list[str]]) -> Optional[list[str]]:
return validate_ip_list(v)
@field_validator("expires_at", mode="before")
@classmethod
def parse_expires(cls, v):
return parse_expires_at(v)
class UpdateManagementTokenRequest(BaseModel):
"""更新 Management Token 请求
对于 allowed_ips 和 expires_at 字段:
- 未提供(字段不在请求中): 不修改
- 显式设为 null: 清空该字段
- 提供有效值: 更新为新值
"""
model_config = {"extra": "allow"} # 允许额外字段以便检测哪些字段被显式提供
name: Optional[str] = Field(None, min_length=1, max_length=100)
description: Optional[str] = Field(None, max_length=500)
allowed_ips: Optional[list[str]] = None
expires_at: Optional[datetime] = None
# 用于追踪哪些字段被显式提供(包括显式设为 null 的情况)
_provided_fields: set[str] = set()
def __init__(self, **data):
# 记录实际传入的字段(包括值为 None 的)
provided = set(data.keys())
super().__init__(**data)
object.__setattr__(self, "_provided_fields", provided)
def is_field_provided(self, field_name: str) -> bool:
"""检查字段是否被显式提供(区分未提供和显式设为 null"""
return field_name in self._provided_fields
@field_validator("allowed_ips")
@classmethod
def validate_allowed_ips(cls, v: Optional[list[str]]) -> Optional[list[str]]:
# 如果是 None表示要清空直接返回
if v is None:
return None
return validate_ip_list(v)
@field_validator("expires_at", mode="before")
@classmethod
def parse_expires(cls, v):
# 如果是 None 或空字符串,表示要清空
if v is None or (isinstance(v, str) and not v.strip()):
return None
return parse_expires_at(v)
# ============== 路由 ==============
@router.get("")
async def list_my_management_tokens(
request: Request,
is_active: Optional[bool] = Query(None, description="筛选激活状态"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
db: Session = Depends(get_db),
):
"""列出当前用户的 Management Tokens
获取当前登录用户创建的所有 Management Tokens支持按激活状态筛选和分页。
**查询参数**
- is_active (Optional[bool]): 筛选激活状态true/false不传则返回全部
- skip (int): 分页偏移量,默认 0
- limit (int): 每页数量,范围 1-100默认 50
**返回字段**
- items (List[dict]): Token 列表
- id (str): Token ID
- user_id (str): 所属用户 ID
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值(不返回明文)
- is_active (bool): 是否激活
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间ISO 8601 格式)
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
- total (int): 总数量
- skip (int): 当前偏移量
- limit (int): 当前每页数量
- quota (dict): 配额信息
- used (int): 已使用数量
- max (int): 最大允许数量
"""
adapter = ListMyManagementTokensAdapter(is_active=is_active, skip=skip, limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("")
async def create_my_management_token(request: Request, db: Session = Depends(get_db)):
"""创建 Management Token
为当前用户创建一个新的 Management Token。
**请求体字段**
- name (str): Token 名称,必填,长度 1-100
- description (Optional[str]): 描述,可选,最大长度 500
- allowed_ips (Optional[List[str]]): IP 白名单,可选,支持 IPv4/IPv6 和 CIDR 格式
- expires_at (Optional[datetime]): 过期时间,可选,支持 ISO 8601 格式字符串或 datetime 对象
**返回字段**
- message (str): 操作结果消息
- token (str): 生成的 Token 明文(仅在创建时返回一次,请妥善保存)
- data (dict): Token 信息
- id (str): Token ID
- user_id (str): 所属用户 ID
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值
- is_active (bool): 是否激活(新创建默认为 true
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
"""
adapter = CreateMyManagementTokenAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/{token_id}")
async def get_my_management_token(
token_id: str,
request: Request,
db: Session = Depends(get_db),
):
"""获取 Management Token 详情
获取当前用户指定 Token 的详细信息。
**路径参数**
- token_id (str): Token ID
**返回字段**
- id (str): Token ID
- user_id (str): 所属用户 ID
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值(不返回明文)
- is_active (bool): 是否激活
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间ISO 8601 格式)
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
"""
adapter = GetMyManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/{token_id}")
async def update_my_management_token(
token_id: str, request: Request, db: Session = Depends(get_db)
):
"""更新 Management Token
更新当前用户指定 Token 的信息。支持部分字段更新。
**路径参数**
- token_id (str): Token ID
**请求体字段**(所有字段均可选)
- name (Optional[str]): Token 名称,长度 1-100
- description (Optional[str]): 描述,最大长度 500传空字符串或 null 可清空
- allowed_ips (Optional[List[str]]): IP 白名单,传 null 可清空
- expires_at (Optional[datetime]): 过期时间,传 null 可清空
注意:未提供的字段不会被修改,显式传 null 表示清空该字段。
**返回字段**
- message (str): 操作结果消息
- data (dict): 更新后的 Token 信息
- id (str): Token ID
- user_id (str): 所属用户 ID
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值
- is_active (bool): 是否激活
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
"""
adapter = UpdateMyManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{token_id}")
async def delete_my_management_token(
token_id: str, request: Request, db: Session = Depends(get_db)
):
"""删除 Management Token
删除当前用户指定的 Token。
**路径参数**
- token_id (str): 要删除的 Token ID
**返回字段**
- message (str): 操作结果消息
"""
adapter = DeleteMyManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/{token_id}/status")
async def toggle_my_management_token(
token_id: str, request: Request, db: Session = Depends(get_db)
):
"""切换 Management Token 状态
启用或禁用当前用户指定的 Token。
**路径参数**
- token_id (str): Token ID
**返回字段**
- message (str): 操作结果消息("Token 已启用""Token 已禁用"
- data (dict): 更新后的 Token 信息
- id (str): Token ID
- user_id (str): 所属用户 ID
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): Token 哈希值
- is_active (bool): 是否激活(已切换后的状态)
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间
- last_used_at (Optional[str]): 最后使用时间
- created_at (str): 创建时间
- updated_at (str): 更新时间
"""
adapter = ToggleMyManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/{token_id}/regenerate")
async def regenerate_my_management_token(
token_id: str, request: Request, db: Session = Depends(get_db)
):
"""重新生成 Management Token
重新生成当前用户指定 Token 的值,旧 Token 将立即失效。
**路径参数**
- token_id (str): Token ID
**返回字段**
- message (str): 操作结果消息
- token (str): 新生成的 Token 明文(仅在重新生成时返回一次,请妥善保存)
- data (dict): Token 信息
- id (str): Token ID
- user_id (str): 所属用户 ID
- name (str): Token 名称
- description (Optional[str]): 描述
- token_hash (str): 新的 Token 哈希值
- is_active (bool): 是否激活
- allowed_ips (Optional[List[str]]): IP 白名单
- expires_at (Optional[str]): 过期时间
- last_used_at (Optional[str]): 最后使用时间(重置为 null
- created_at (str): 创建时间
- updated_at (str): 更新时间
"""
adapter = RegenerateMyManagementTokenAdapter(token_id=token_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
# ============== 适配器 ==============
@dataclass
class ListMyManagementTokensAdapter(ManagementTokenApiAdapter):
"""列出用户的 Management Tokens"""
name: str = "list_my_management_tokens"
is_active: Optional[bool] = None
skip: int = 0
limit: int = 50
async def handle(self, context: ApiRequestContext):
from src.config.settings import config
tokens, total = ManagementTokenService.list_tokens(
db=context.db,
user_id=context.user.id,
is_active=self.is_active,
skip=self.skip,
limit=self.limit,
)
# 获取用户 Token 总数(用于配额显示)
max_tokens = config.management_token_max_per_user
return JSONResponse(
content={
"items": [token_to_dict(t) for t in tokens],
"total": total,
"skip": self.skip,
"limit": self.limit,
"quota": {
"used": total,
"max": max_tokens,
},
}
)
@dataclass
class CreateMyManagementTokenAdapter(ManagementTokenApiAdapter):
"""创建 Management Token"""
name: str = "create_my_management_token"
audit_success_event = AuditEventType.MANAGEMENT_TOKEN_CREATED
async def handle(self, context: ApiRequestContext):
body = context.ensure_json_body()
try:
req = CreateManagementTokenRequest(**body)
except Exception as e:
raise InvalidRequestException(str(e))
try:
token, raw_token = ManagementTokenService.create_token(
db=context.db,
user_id=context.user.id,
name=req.name,
description=req.description,
allowed_ips=req.allowed_ips,
expires_at=req.expires_at,
)
except ValueError as e:
raise InvalidRequestException(str(e))
context.add_audit_metadata(token_id=token.id, token_name=token.name)
return JSONResponse(
status_code=201,
content={
"message": "Management Token 创建成功",
"token": raw_token, # 仅在创建时返回一次
"data": token_to_dict(token),
},
)
@dataclass
class GetMyManagementTokenAdapter(ManagementTokenApiAdapter):
"""获取 Management Token 详情"""
name: str = "get_my_management_token"
token_id: str = ""
async def handle(self, context: ApiRequestContext):
token = ManagementTokenService.get_token_by_id(
db=context.db, token_id=self.token_id, user_id=context.user.id
)
if not token:
raise NotFoundException("Management Token 不存在")
return JSONResponse(content=token_to_dict(token))
@dataclass
class UpdateMyManagementTokenAdapter(ManagementTokenApiAdapter):
"""更新 Management Token"""
name: str = "update_my_management_token"
token_id: str = ""
audit_success_event = AuditEventType.MANAGEMENT_TOKEN_UPDATED
async def handle(self, context: ApiRequestContext):
body = context.ensure_json_body()
try:
req = UpdateManagementTokenRequest(**body)
except Exception as e:
raise InvalidRequestException(str(e))
# 构建更新参数,只包含显式提供的字段
update_kwargs: dict = {
"db": context.db,
"token_id": self.token_id,
"user_id": context.user.id,
}
# 对于普通字段,只有提供了才更新
if req.is_field_provided("name"):
update_kwargs["name"] = req.name
if req.is_field_provided("description"):
update_kwargs["description"] = req.description
update_kwargs["clear_description"] = req.description is None or req.description == ""
# 对于可清空字段,需要传递特殊标记
if req.is_field_provided("allowed_ips"):
update_kwargs["allowed_ips"] = req.allowed_ips
update_kwargs["clear_allowed_ips"] = req.allowed_ips is None
if req.is_field_provided("expires_at"):
update_kwargs["expires_at"] = req.expires_at
update_kwargs["clear_expires_at"] = req.expires_at is None
try:
token = ManagementTokenService.update_token(**update_kwargs)
except ValueError as e:
raise InvalidRequestException(str(e))
if not token:
raise NotFoundException("Management Token 不存在")
context.add_audit_metadata(token_id=token.id, token_name=token.name)
return JSONResponse(
content={"message": "更新成功", "data": token_to_dict(token)}
)
@dataclass
class DeleteMyManagementTokenAdapter(ManagementTokenApiAdapter):
"""删除 Management Token"""
name: str = "delete_my_management_token"
token_id: str = ""
audit_success_event = AuditEventType.MANAGEMENT_TOKEN_DELETED
async def handle(self, context: ApiRequestContext):
# 先获取 token 信息用于审计
token = ManagementTokenService.get_token_by_id(
db=context.db, token_id=self.token_id, user_id=context.user.id
)
if not token:
raise NotFoundException("Management Token 不存在")
context.add_audit_metadata(token_id=token.id, token_name=token.name)
success = ManagementTokenService.delete_token(
db=context.db, token_id=self.token_id, user_id=context.user.id
)
if not success:
raise NotFoundException("Management Token 不存在")
return JSONResponse(content={"message": "删除成功"})
@dataclass
class ToggleMyManagementTokenAdapter(ManagementTokenApiAdapter):
"""切换 Management Token 状态"""
name: str = "toggle_my_management_token"
token_id: str = ""
audit_success_event = AuditEventType.MANAGEMENT_TOKEN_UPDATED
async def handle(self, context: ApiRequestContext):
token = ManagementTokenService.toggle_status(
db=context.db, token_id=self.token_id, user_id=context.user.id
)
if not token:
raise NotFoundException("Management Token 不存在")
context.add_audit_metadata(
token_id=token.id, token_name=token.name, is_active=token.is_active
)
return JSONResponse(
content={
"message": f"Token 已{'启用' if token.is_active else '禁用'}",
"data": token_to_dict(token),
}
)
@dataclass
class RegenerateMyManagementTokenAdapter(ManagementTokenApiAdapter):
"""重新生成 Management Token"""
name: str = "regenerate_my_management_token"
token_id: str = ""
audit_success_event = AuditEventType.MANAGEMENT_TOKEN_UPDATED
async def handle(self, context: ApiRequestContext):
token, raw_token, old_token_hash = ManagementTokenService.regenerate_token(
db=context.db, token_id=self.token_id, user_id=context.user.id
)
if not token:
raise NotFoundException("Management Token 不存在")
context.add_audit_metadata(
token_id=token.id,
token_name=token.name,
regenerated=True,
)
return JSONResponse(
content={
"message": "Token 已重新生成",
"token": raw_token, # 仅在重新生成时返回一次
"data": token_to_dict(token),
}
)

View File

@@ -35,20 +35,43 @@ pipeline = ApiRequestPipeline()
@router.get("")
async def get_my_profile(request: Request, db: Session = Depends(get_db)):
"""获取当前用户完整信息(包含偏好设置)"""
"""
获取当前用户信息
返回当前登录用户的完整信息,包括基本信息和偏好设置。
**返回字段**: id, email, username, role, is_active, quota_usd, used_usd, preferences 等
"""
adapter = MeProfileAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("")
async def update_my_profile(request: Request, db: Session = Depends(get_db)):
"""
更新个人信息
更新当前用户的邮箱或用户名。
**请求体**:
- `email`: 新邮箱地址(可选)
- `username`: 新用户名(可选)
"""
adapter = UpdateProfileAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/password")
async def change_my_password(request: Request, db: Session = Depends(get_db)):
"""Change current user's password"""
"""
修改密码
修改当前用户的登录密码。
**请求体**:
- `old_password`: 当前密码
- `new_password`: 新密码(至少 6 位)
"""
adapter = ChangePasswordAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -58,12 +81,30 @@ async def change_my_password(request: Request, db: Session = Depends(get_db)):
@router.get("/api-keys")
async def list_my_api_keys(request: Request, db: Session = Depends(get_db)):
"""
获取 API 密钥列表
返回当前用户的所有 API 密钥,包含使用统计信息。
密钥值仅显示前后几位,完整密钥需通过详情接口获取。
**返回字段**: id, name, key_display, is_active, total_requests, total_cost_usd, last_used_at 等
"""
adapter = ListMyApiKeysAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/api-keys")
async def create_my_api_key(request: Request, db: Session = Depends(get_db)):
"""
创建 API 密钥
为当前用户创建新的 API 密钥。创建成功后会返回完整的密钥值,请妥善保存。
**请求体**:
- `name`: 密钥名称
**返回**: 包含完整密钥值的响应(仅此一次显示完整密钥)
"""
adapter = CreateMyApiKeyAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -72,10 +113,20 @@ async def create_my_api_key(request: Request, db: Session = Depends(get_db)):
async def get_my_api_key(
key_id: str,
request: Request,
include_key: bool = Query(False, description="Include full decrypted key in response"),
include_key: bool = Query(False, description="是否返回完整密钥"),
db: Session = Depends(get_db),
):
"""Get API key detail, optionally include full key"""
"""
获取 API 密钥详情
获取指定 API 密钥的详细信息。
**路径参数**:
- `key_id`: 密钥 ID
**查询参数**:
- `include_key`: 设为 true 时返回完整解密后的密钥值
"""
if include_key:
adapter = GetMyFullKeyAdapter(key_id=key_id)
else:
@@ -85,13 +136,28 @@ async def get_my_api_key(
@router.delete("/api-keys/{key_id}")
async def delete_my_api_key(key_id: str, request: Request, db: Session = Depends(get_db)):
"""
删除 API 密钥
永久删除指定的 API 密钥,删除后无法恢复。
**路径参数**:
- `key_id`: 密钥 ID
"""
adapter = DeleteMyApiKeyAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/api-keys/{key_id}")
async def toggle_my_api_key(key_id: str, request: Request, db: Session = Depends(get_db)):
"""Toggle API key active status"""
"""
切换 API 密钥状态
启用或禁用指定的 API 密钥。禁用后该密钥将无法用于 API 调用。
**路径参数**:
- `key_id`: 密钥 ID
"""
adapter = ToggleMyApiKeyAdapter(key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -102,13 +168,27 @@ async def toggle_my_api_key(key_id: str, request: Request, db: Session = Depends
@router.get("/usage")
async def get_my_usage(
request: Request,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
search: Optional[str] = None, # 通用搜索:密钥名、模型名
start_date: Optional[datetime] = Query(None, description="开始时间ISO 格式)"),
end_date: Optional[datetime] = Query(None, description="结束时间ISO 格式)"),
search: Optional[str] = Query(None, description="搜索关键词(密钥名、模型名)"),
limit: int = Query(100, ge=1, le=200, description="每页记录数默认100最大200"),
offset: int = Query(0, ge=0, le=2000, description="偏移量用于分页最大2000"),
db: Session = Depends(get_db),
):
"""
获取使用统计
获取当前用户的 API 使用统计数据,包括总量汇总、按模型/提供商分组统计及详细记录。
**返回字段**:
- `total_requests`: 总请求数
- `total_tokens`: 总 Token 数
- `total_cost`: 总成本USD
- `summary_by_model`: 按模型分组统计
- `summary_by_provider`: 按提供商分组统计
- `records`: 详细使用记录列表
- `pagination`: 分页信息
"""
adapter = GetUsageAdapter(
start_date=start_date, end_date=end_date, search=search, limit=limit, offset=offset
)
@@ -118,10 +198,17 @@ async def get_my_usage(
@router.get("/usage/active")
async def get_my_active_requests(
request: Request,
ids: Optional[str] = Query(None, description="Comma-separated request IDs to query"),
ids: Optional[str] = Query(None, description="请求 ID 列表,逗号分隔"),
db: Session = Depends(get_db),
):
"""获取用户活跃请求状态(用于轮询更新)"""
"""
获取活跃请求状态
查询正在进行中的请求状态,用于前端轮询更新流式请求的进度。
**查询参数**:
- `ids`: 要查询的请求 ID 列表,逗号分隔
"""
adapter = GetActiveRequestsAdapter(ids=ids)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -133,7 +220,13 @@ async def get_my_interval_timeline(
limit: int = Query(5000, ge=100, le=20000, description="最大返回数据点数量"),
db: Session = Depends(get_db),
):
"""获取当前用户的请求间隔时间线数据,用于散点图展示"""
"""
获取请求间隔时间线
获取请求间隔时间线数据,用于散点图展示请求分布情况。
**返回**: 包含时间戳和间隔时间的数据点列表
"""
adapter = GetMyIntervalTimelineAdapter(hours=hours, limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -144,9 +237,12 @@ async def get_my_activity_heatmap(
db: Session = Depends(get_db),
):
"""
Get user's activity heatmap data for the past 365 days.
获取活动热力图数据
This endpoint is cached for 5 minutes to reduce database load.
获取过去 365 天的活动热力图数据,用于展示每日使用频率。
此接口有 5 分钟缓存。
**返回**: 包含日期和请求数量的数据列表
"""
adapter = GetMyActivityHeatmapAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -154,13 +250,26 @@ async def get_my_activity_heatmap(
@router.get("/providers")
async def list_available_providers(request: Request, db: Session = Depends(get_db)):
"""
获取可用提供商列表
获取当前用户可用的所有提供商及其模型信息。
**返回字段**: id, name, display_name, endpoints, models 等
"""
adapter = ListAvailableProvidersAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/endpoint-status")
async def get_endpoint_status(request: Request, db: Session = Depends(get_db)):
"""获取端点状态(简化版,不包含敏感信息)"""
"""
获取端点健康状态
获取各 API 格式端点的健康状态(简化版,不包含敏感信息)。
**返回**: 按 API 格式分组的端点健康状态
"""
adapter = GetEndpointStatusAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -177,6 +286,17 @@ async def update_api_key_providers(
request: Request,
db: Session = Depends(get_db),
):
"""
更新 API 密钥可用提供商
设置指定 API 密钥可以使用哪些提供商。未设置时使用用户默认权限。
**路径参数**:
- `api_key_id`: API 密钥 ID
**请求体**:
- `allowed_providers`: 允许的提供商 ID 列表
"""
adapter = UpdateApiKeyProvidersAdapter(api_key_id=api_key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -187,7 +307,17 @@ async def update_api_key_capabilities(
request: Request,
db: Session = Depends(get_db),
):
"""更新 API Key 的强制能力配置"""
"""
更新 API 密钥能力配置
设置指定 API 密钥的强制能力配置(如是否启用代码执行等)。
**路径参数**:
- `api_key_id`: API 密钥 ID
**请求体**:
- `force_capabilities`: 能力配置字典,如 `{"code_execution": true}`
"""
adapter = UpdateApiKeyCapabilitiesAdapter(api_key_id=api_key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -197,26 +327,59 @@ async def update_api_key_capabilities(
@router.get("/preferences")
async def get_my_preferences(request: Request, db: Session = Depends(get_db)):
"""
获取偏好设置
获取当前用户的偏好设置,包括主题、语言、通知配置等。
**返回字段**: avatar_url, bio, theme, language, timezone, notifications 等
"""
adapter = GetPreferencesAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/preferences")
async def update_my_preferences(request: Request, db: Session = Depends(get_db)):
"""
更新偏好设置
更新当前用户的偏好设置。
**请求体**:
- `theme`: 主题light/dark
- `language`: 语言
- `timezone`: 时区
- `email_notifications`: 邮件通知开关
- `usage_alerts`: 用量告警开关
- 等
"""
adapter = UpdatePreferencesAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/model-capabilities")
async def get_model_capability_settings(request: Request, db: Session = Depends(get_db)):
"""获取用户的模型能力配置"""
"""
获取模型能力配置
获取用户针对各模型的能力配置(如是否启用特定功能)。
**返回**: model_capability_settings 字典
"""
adapter = GetModelCapabilitySettingsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/model-capabilities")
async def update_model_capability_settings(request: Request, db: Session = Depends(get_db)):
"""更新用户的模型能力配置"""
"""
更新模型能力配置
更新用户针对各模型的能力配置。
**请求体**:
- `model_capability_settings`: 模型能力配置字典,格式为 `{"model_name": {"capability": true}}`
"""
adapter = UpdateModelCapabilitySettingsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -225,11 +388,15 @@ async def update_model_capability_settings(request: Request, db: Session = Depen
class MeProfileAdapter(AuthenticatedApiAdapter):
"""获取当前用户信息的适配器"""
async def handle(self, context): # type: ignore[override]
return PreferenceService.get_user_with_preferences(context.db, context.user.id)
class UpdateProfileAdapter(AuthenticatedApiAdapter):
"""更新用户个人信息的适配器"""
async def handle(self, context): # type: ignore[override]
db = context.db
user = context.user
@@ -265,6 +432,8 @@ class UpdateProfileAdapter(AuthenticatedApiAdapter):
class ChangePasswordAdapter(AuthenticatedApiAdapter):
"""修改用户密码的适配器"""
async def handle(self, context): # type: ignore[override]
db = context.db
user = context.user
@@ -290,6 +459,8 @@ class ChangePasswordAdapter(AuthenticatedApiAdapter):
class ListMyApiKeysAdapter(AuthenticatedApiAdapter):
"""获取用户 API 密钥列表的适配器"""
async def handle(self, context): # type: ignore[override]
db = context.db
user = context.user
@@ -359,6 +530,8 @@ class ListMyApiKeysAdapter(AuthenticatedApiAdapter):
class CreateMyApiKeyAdapter(AuthenticatedApiAdapter):
"""创建 API 密钥的适配器"""
async def handle(self, context): # type: ignore[override]
payload = context.ensure_json_body()
try:
@@ -388,6 +561,8 @@ class CreateMyApiKeyAdapter(AuthenticatedApiAdapter):
@dataclass
class GetMyFullKeyAdapter(AuthenticatedApiAdapter):
"""获取 API 密钥完整密钥值的适配器"""
key_id: str
async def handle(self, context): # type: ignore[override]
@@ -420,7 +595,8 @@ class GetMyFullKeyAdapter(AuthenticatedApiAdapter):
@dataclass
class GetMyApiKeyDetailAdapter(AuthenticatedApiAdapter):
"""Get API key detail without full key"""
"""获取 API 密钥详情的适配器(不包含完整密钥值)"""
key_id: str
async def handle(self, context): # type: ignore[override]
@@ -449,6 +625,8 @@ class GetMyApiKeyDetailAdapter(AuthenticatedApiAdapter):
@dataclass
class DeleteMyApiKeyAdapter(AuthenticatedApiAdapter):
"""删除 API 密钥的适配器"""
key_id: str
async def handle(self, context): # type: ignore[override]
@@ -466,6 +644,8 @@ class DeleteMyApiKeyAdapter(AuthenticatedApiAdapter):
@dataclass
class ToggleMyApiKeyAdapter(AuthenticatedApiAdapter):
"""切换 API 密钥启用/禁用状态的适配器"""
key_id: str
async def handle(self, context): # type: ignore[override]
@@ -488,6 +668,8 @@ class ToggleMyApiKeyAdapter(AuthenticatedApiAdapter):
@dataclass
class GetUsageAdapter(AuthenticatedApiAdapter):
"""获取用户使用统计的适配器"""
start_date: Optional[datetime]
end_date: Optional[datetime]
search: Optional[str] = None
@@ -766,7 +948,7 @@ class GetMyIntervalTimelineAdapter(AuthenticatedApiAdapter):
class GetMyActivityHeatmapAdapter(AuthenticatedApiAdapter):
"""Activity heatmap adapter with Redis caching for user."""
"""获取用户活动热力图数据的适配器(带 Redis 缓存)"""
async def handle(self, context): # type: ignore[override]
user = context.user
@@ -780,6 +962,8 @@ class GetMyActivityHeatmapAdapter(AuthenticatedApiAdapter):
class ListAvailableProvidersAdapter(AuthenticatedApiAdapter):
"""获取可用提供商列表的适配器"""
async def handle(self, context): # type: ignore[override]
from sqlalchemy.orm import selectinload
@@ -851,6 +1035,8 @@ class ListAvailableProvidersAdapter(AuthenticatedApiAdapter):
@dataclass
class UpdateApiKeyProvidersAdapter(AuthenticatedApiAdapter):
"""更新 API 密钥可用提供商的适配器"""
api_key_id: str
async def handle(self, context): # type: ignore[override]
@@ -962,6 +1148,8 @@ class UpdateApiKeyCapabilitiesAdapter(AuthenticatedApiAdapter):
class GetPreferencesAdapter(AuthenticatedApiAdapter):
"""获取用户偏好设置的适配器"""
async def handle(self, context): # type: ignore[override]
preferences = PreferenceService.get_or_create_preferences(context.db, context.user.id)
return {
@@ -983,6 +1171,8 @@ class GetPreferencesAdapter(AuthenticatedApiAdapter):
class UpdatePreferencesAdapter(AuthenticatedApiAdapter):
"""更新用户偏好设置的适配器"""
async def handle(self, context): # type: ignore[override]
payload = context.ensure_json_body()
try: