feat(compute): 归户闭环——relay 透传用户PAT + 用户用量/余额接口
- relay /v1 转发优先透传客户端 Authorization(用户算力PAT→compute归户), 无则回落 COMPUTE_RELAY_TOKEN(匿名) - compute_client: user_balance/user_usage(取该用户一枚PAT鉴权查 /api/user/self|self/usage) - rbac_opc 新增 /opc/compute/usage(本月按模型) + /opc/compute/balance
This commit is contained in:
@@ -244,6 +244,24 @@ async def opc_compute_prices(_u: dict = Depends(require_roles("opc_member"))):
|
||||
return {"items": await compute_catalog.prices()}
|
||||
|
||||
|
||||
@router.get("/compute/usage", summary="我的用量(本月按模型)")
|
||||
async def opc_compute_usage(user: dict = Depends(require_roles("opc_member"))):
|
||||
try:
|
||||
data = await compute_client.user_usage(user.get("username"))
|
||||
except compute_client.ComputeError as exc:
|
||||
raise HTTPException(status_code=502, detail=f"算力引擎对接失败: {exc}") from exc
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/compute/balance", summary="我的算力余额")
|
||||
async def opc_compute_balance(user: dict = Depends(require_roles("opc_member"))):
|
||||
try:
|
||||
data = await compute_client.user_balance(user.get("username"))
|
||||
except compute_client.ComputeError as exc:
|
||||
raise HTTPException(status_code=502, detail=f"算力引擎对接失败: {exc}") from exc
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/compute/tokens", summary="我的算力令牌")
|
||||
async def opc_compute_tokens(user: dict = Depends(require_roles("opc_member"))):
|
||||
"""列出当前用户的引擎消费令牌。"""
|
||||
|
||||
@@ -22,9 +22,13 @@ from ...services import compute_catalog
|
||||
router = APIRouter(prefix="/v1", tags=["relay"])
|
||||
|
||||
|
||||
def _auth_headers() -> dict[str, str]:
|
||||
# /v1 转发用引擎能识别的真实消费令牌(PINEAGENTS_COMPUTE_RELAY_TOKEN),
|
||||
# 与管理的 COMPUTE_ADMIN_TOKEN(内部合成 root) 区分;空令牌不下发,避免 `Bearer ` 非法被 httpx 拒绝。
|
||||
def _auth_headers(request: Request | None = None) -> dict[str, str]:
|
||||
# 优先透传客户端 Authorization(用户算力 PAT → compute 归户计量)。
|
||||
# 无则回落服务端消费令牌(COMPUTE_RELAY_TOKEN)→ compute 计匿名账。
|
||||
if request is not None:
|
||||
client_auth = request.headers.get("Authorization", "")
|
||||
if client_auth:
|
||||
return {"Authorization": client_auth}
|
||||
token = config.COMPUTE_RELAY_TOKEN or config.COMPUTE_ADMIN_TOKEN
|
||||
if not token:
|
||||
return {}
|
||||
@@ -41,7 +45,7 @@ async def relay_chat_completions(request: Request):
|
||||
|
||||
chat_url = f"{config.COMPUTE_BASE_URL.rstrip('/')}/v1/chat/completions"
|
||||
stream = bool(body.get("stream", False))
|
||||
headers = {**_auth_headers(), "Content-Type": "application/json"}
|
||||
headers = {**_auth_headers(request), "Content-Type": "application/json"}
|
||||
|
||||
client = httpx.AsyncClient(timeout=None)
|
||||
upstream_request = client.build_request("POST", chat_url, json=body, headers=headers)
|
||||
|
||||
Reference in New Issue
Block a user