vproxy / src /aistudio_api /application /api_service_common.py
jlshu's picture
Upload 85 files
95f8c4b verified
Raw History Blame Contribute Delete
3.59 kB
"""Shared helpers for API service handlers."""
from __future__ import annotations
import logging
from fastapi import HTTPException
from aistudio_api.api.response_models import (
HealthResponse,
ModelStatsResponse,
StatsResponse,
StatsTotalsResponse,
)
from aistudio_api.api.state import runtime_state
logger = logging.getLogger("aistudio.server")
MAX_RETRIES = 3
async def try_switch_account() -> bool:
"""尝试切换到下一个可用账号。返回是否成功切换。"""
rotator = runtime_state.rotator
if rotator is None:
return False
next_account = await rotator.get_next_account()
if next_account is None:
return False
account_service = runtime_state.account_service
client = runtime_state.client
if account_service is None or client is None or client._session is None:
return False
result = await account_service.activate_account(
next_account.id,
client._session,
runtime_state.snapshot_cache,
None, # skip lock — caller already holds it
keep_snapshot_cache=False,
)
return result is not None
def require_busy_lock():
busy_lock = runtime_state.busy_lock
if busy_lock is None:
raise HTTPException(
503, detail={"message": "Server not ready", "type": "service_unavailable"}
)
if busy_lock.locked():
raise HTTPException(
503,
detail={
"message": "Server is busy with maximum concurrent requests",
"type": "service_unavailable",
},
headers={"Retry-After": "2"},
)
return busy_lock
async def ensure_active_account(attempt: int) -> None:
if attempt != 0:
return
account_svc = runtime_state.account_service
if account_svc and not account_svc.get_active_account():
await try_switch_account()
def record_rotator_event(event: str) -> None:
rotator = runtime_state.rotator
account_service = runtime_state.account_service
account = account_service.get_active_account() if account_service else None
if not rotator or account is None:
return
if event == "success":
rotator.record_success(account.id)
elif event == "rate_limited":
rotator.record_rate_limited(account.id)
elif event == "error":
rotator.record_error(account.id)
def health_response() -> HealthResponse:
busy_lock = runtime_state.busy_lock
return HealthResponse(status="ok", busy=busy_lock.locked() if busy_lock else False)
def stats_response() -> StatsResponse:
stats = dict(runtime_state.model_stats)
totals = StatsTotalsResponse(
requests=sum(s.requests for s in stats.values()),
success=sum(s.success for s in stats.values()),
rate_limited=sum(s.rate_limited for s in stats.values()),
errors=sum(s.errors for s in stats.values()),
prompt_tokens=sum(s.prompt_tokens for s in stats.values()),
completion_tokens=sum(s.completion_tokens for s in stats.values()),
total_tokens=sum(s.total_tokens for s in stats.values()),
)
models = {
name: ModelStatsResponse(
requests=s.requests,
success=s.success,
rate_limited=s.rate_limited,
errors=s.errors,
prompt_tokens=s.prompt_tokens,
completion_tokens=s.completion_tokens,
total_tokens=s.total_tokens,
last_used=s.last_used,
)
for name, s in stats.items()
}
return StatsResponse(models=models, totals=totals)