b90539a013
- 登录:/auth/send-code·phone-login·wx-login·wx-phone(复用 RBAC 签发) - 算力:services/compute_client(loopback :3000)+ /admin/compute/ping·provision - /v1 relay 改走 compute-engine(前端不直连引擎) - 模型:User 补 wx_openid/phone;仓储 async create+新方法 - 契约:api/schemas(auth/compute)Pydantic
107 lines
3.9 KiB
Python
107 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""算力引擎(compute-engine / new-api)客户端封装。
|
|
|
|
云超服算力统一经此服务消费引擎(loopback :3000,仅服务),前端不直连引擎:
|
|
模型中继 ``/v1/*`` + 管理 API ``/api/*``(建号 / 发 PAT / 额度 / 流水)。
|
|
|
|
- 引擎仅作模型计量与额度/令牌/流水,不承担身份(身份在 server-core)。
|
|
- 管理调用带引擎管理令牌(``COMPUTE_ADMIN_TOKEN``);用户侧余额/流水用其 PAT。
|
|
|
|
路径:引擎管理路由起点见 compute-engine ``controller/user.go``、``controller/log.go``、
|
|
``router/api-router.go`` 的 admin 段;以下默认路径按设计 §4.3 拟定,若与引擎实际
|
|
路由不符,仅需调整对应 ``_path`` 常量,不影响上层调用。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import httpx
|
|
|
|
from .. import config
|
|
|
|
|
|
class ComputeError(Exception):
|
|
"""算力引擎调用异常(网络 / 非 2xx / 引擎未就绪等)。"""
|
|
|
|
|
|
def _base() -> str:
|
|
return config.COMPUTE_BASE_URL.rstrip("/")
|
|
|
|
|
|
def _admin_headers() -> dict[str, str]:
|
|
return {"Authorization": f"Bearer {config.COMPUTE_ADMIN_TOKEN}"}
|
|
|
|
|
|
async def _request(*, method: str, path: str, headers: dict[str, str] | None = None,
|
|
json: dict | None = None) -> dict:
|
|
"""带超时/重试的引擎请求;2xx 返回 JSON,否则抛 ComputeError。"""
|
|
url = f"{_base()}{path}"
|
|
last: Exception | None = None
|
|
for _ in range(max(1, config.COMPUTE_RETRIES + 1)):
|
|
try:
|
|
async with httpx.AsyncClient(timeout=config.COMPUTE_TIMEOUT) as client:
|
|
resp = await client.request(
|
|
method, url, json=json, headers=headers,
|
|
)
|
|
if resp.status_code == 200:
|
|
try:
|
|
return resp.json() if resp.content else {}
|
|
except Exception: # noqa: BLE001
|
|
return {}
|
|
if resp.status_code < 500:
|
|
raise ComputeError(
|
|
f"compute-engine {resp.status_code}: {resp.text[:200]}"
|
|
)
|
|
last = ComputeError(f"compute-engine {resp.status_code}: {resp.text[:200]}")
|
|
except httpx.HTTPError as exc:
|
|
last = ComputeError(f"compute-engine request failed: {exc}")
|
|
except ComputeError as exc:
|
|
last = exc
|
|
if " 4" in str(exc) or " 3" in str(exc):
|
|
break
|
|
await asyncio.sleep(0.2)
|
|
raise last or ComputeError("compute-engine unavailable")
|
|
|
|
|
|
async def ping() -> dict:
|
|
"""引擎连通性健康检查。"""
|
|
try:
|
|
return await _request(method="GET", path="/api/status", headers=_admin_headers())
|
|
except ComputeError as exc:
|
|
return {"status": "error", "detail": str(exc)}
|
|
|
|
|
|
async def create_user(username: str) -> dict:
|
|
"""在引擎建号(username = 云超服 uid)。"""
|
|
return await _request(
|
|
method="POST", path="/api/user", headers=_admin_headers(),
|
|
json={"username": username},
|
|
)
|
|
|
|
|
|
async def issue_pat(username: str) -> str:
|
|
"""为引擎用户签发 personal access token(下发前端注入 provider)。"""
|
|
data = await _request(
|
|
method="POST", path="/api/token", headers=_admin_headers(),
|
|
json={"username": username},
|
|
)
|
|
return str(data.get("key") or data.get("token") or "")
|
|
|
|
|
|
async def get_balance(user_pat: str) -> dict:
|
|
"""用户余额(需该用户 PAT)。"""
|
|
headers = {"Authorization": f"Bearer {user_pat}"}
|
|
return await _request(method="GET", path="/api/user/self", headers=headers)
|
|
|
|
|
|
async def get_month_usage(user_pat: str) -> dict:
|
|
"""本月消耗(按日额度)。"""
|
|
headers = {"Authorization": f"Bearer {user_pat}"}
|
|
return await _request(method="GET", path="/api/data/self", headers=headers)
|
|
|
|
|
|
async def get_logs(user_pat: str) -> dict:
|
|
"""用户 token 流水。"""
|
|
headers = {"Authorization": f"Bearer {user_pat}"}
|
|
return await _request(method="GET", path="/api/log/self", headers=headers)
|