fix: Codex 刷新限额功能优化

- 修复并发协程中直接操作 SQLAlchemy Session 的问题,改为收集后统一更新
- 抽取硬编码模型名为 CODEX_QUOTA_REFRESH_MODEL 常量
- 前端刷新限额前添加确认对话框,提示会产生 API 调用费用
- 修正 loguru 日志格式为 {} 占位符风格
This commit is contained in:
fawney19
2026-02-05 18:05:46 +08:00
parent 93090e227f
commit a9b24c9161
2 changed files with 46 additions and 12 deletions

View File

@@ -869,6 +869,9 @@ class AdminCreateProviderKeyAdapter(AdminApiAdapter):
# ========== Codex Quota Refresh API ==========
# Codex 限额刷新测试请求使用的模型(选择最小/最便宜的模型)
CODEX_QUOTA_REFRESH_MODEL = "gpt-5.1-codex-mini"
@router.post("/providers/{provider_id}/refresh-quota")
async def refresh_provider_quota(
@@ -906,7 +909,10 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
import httpx
from src.api.handlers.base.request_builder import get_provider_auth
from src.services.provider.metadata_collectors import MetadataCollectorRegistry, ensure_collectors_registered
from src.services.provider.metadata_collectors import (
MetadataCollectorRegistry,
ensure_collectors_registered,
)
from src.services.provider.transport import build_provider_url
from src.utils.ssl_utils import get_ssl_context
@@ -933,7 +939,13 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
)
if not keys:
return {"success": 0, "failed": 0, "total": 0, "results": [], "message": "没有活跃的 Key"}
return {
"success": 0,
"failed": 0,
"total": 0,
"results": [],
"message": "没有活跃的 Key",
}
# 获取 openai:cli 端点
endpoint = None
@@ -949,6 +961,9 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
success_count = 0
failed_count = 0
# 用于收集需要更新的 key 元数据(避免在并发任务中直接操作 db session
metadata_updates: dict[str, dict] = {} # key_id -> metadata
# 单个 Key 刷新函数
async def refresh_single_key(key: ProviderAPIKey) -> dict:
try:
@@ -972,7 +987,7 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
# 发送最小的测试请求,使用 Codex Responses API 格式
test_body = {
"model": "gpt-5.1-codex-mini",
"model": CODEX_QUOTA_REFRESH_MODEL,
"input": [
{
"type": "message",
@@ -985,9 +1000,7 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
"store": False,
}
async with httpx.AsyncClient(
timeout=30.0, verify=get_ssl_context()
) as client:
async with httpx.AsyncClient(timeout=30.0, verify=get_ssl_context()) as client:
response = await client.post(url, json=test_body, headers=headers)
# 解析响应头中的限额信息
@@ -995,9 +1008,8 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
metadata = MetadataCollectorRegistry.collect("codex", response_headers)
if metadata:
# 更新数据库中的元数据
key.upstream_metadata = metadata
db.add(key)
# 收集元数据,稍后统一更新数据库
metadata_updates[key.id] = metadata
return {
"key_id": key.id,
"key_name": key.name,
@@ -1015,7 +1027,7 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
}
except Exception as e:
logger.error(f"刷新 Key {key.id} 限额失败: {e}")
logger.error("刷新 Key {} 限额失败: {}", key.id, e)
return {
"key_id": key.id,
"key_name": key.name,
@@ -1038,12 +1050,22 @@ class AdminRefreshProviderQuotaAdapter(AdminApiAdapter):
else:
failed_count += 1
# 统一更新数据库(避免在并发任务中操作 session
if metadata_updates:
for key in keys:
if key.id in metadata_updates:
key.upstream_metadata = metadata_updates[key.id]
db.add(key)
# 提交数据库更改
db.commit()
logger.info(
f"[QUOTA_REFRESH] Provider {self.provider_id}: "
f"成功 {success_count}/{len(keys)}, 失败 {failed_count}"
"[QUOTA_REFRESH] Provider {}: 成功 {}/{}, 失败 {}",
self.provider_id,
success_count,
len(keys),
failed_count,
)
return {