Files
server-core/app/api/routers/compute_internal.py
T
Pine 8c30e2c45b feat: 算力补贴体系 - usage_records加成本字段+补贴发放接口
- compute_usage_records加4字段(cost_amount/gross_profit/model_cost_ratio/subsidy_eligible)
- 新增alembic迁移0080
- compute_internal新增subsidy-grant接口(增加用户算力余额)
- deduct_by_engine_cost透传成本/毛利字段并保存
2026-09-13 19:04:16 +08:00

102 lines
4.0 KiB
Python

# -*- coding: utf-8 -*-
"""compute 引擎 → server-core 内部回调接口。
compute 扣费完成后异步回调此接口,按引擎实际费用(微元)扣平台来源账本。
鉴权:Authorization: Bearer <COMPUTE_ADMIN_TOKEN>(与引擎侧 PINEAGENTS_INTERNAL_TOKEN 一致)。
幂等:engine_log_id 去重,防网络重试重复扣费。
"""
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, Header, HTTPException
from pydantic import BaseModel
from ... import config
from ...infrastructure.repositories import Database
from ..dependencies import get_db
from ...services.compute_pricing_service import deduct_by_engine_cost
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/compute/internal", tags=["compute-internal"])
class EngineDeductRequest(BaseModel):
username: str
actual_cost_micro: int
engine_log_id: int
model_name: str = ""
token_count: int = 0
cost_micro: int = 0 # 采购成本(微元),补贴体系数据底座
model_cost_ratio: float = 1.0 # 模型成本比例快照
subsidy_eligible: int = 1 # 是否参与补贴
def _verify_internal_token(authorization: str = Header(default="")) -> None:
"""校验 compute 引擎内部回调令牌。"""
token = authorization[7:].strip() if authorization.lower().startswith("bearer ") else authorization.strip()
expected = config.COMPUTE_ADMIN_TOKEN or ""
if not expected or token != expected:
raise HTTPException(status_code=401, detail="invalid internal token")
class SubsidyGrantRequest(BaseModel):
username: str
amount_micro: int
period: str = ""
record_id: int = 0
@router.post("/subsidy-grant", summary="compute 补贴发放(增加用户算力余额)")
async def subsidy_grant(
req: SubsidyGrantRequest,
db: Database = Depends(get_db),
_auth: None = Depends(_verify_internal_token),
):
"""补贴发放:amount_micro(微元)→ 增加用户个人算力余额 + 记录审计。"""
if req.amount_micro <= 0:
return {"ok": True, "amount": 0, "reason": "zero_amount"}
amount_fen = max(0, int(req.amount_micro) // 10000) # 微元→分
if amount_fen <= 0:
return {"ok": True, "amount": 0, "reason": "zero_fen"}
user = await db.users.get_by_username(req.username)
if not user:
return {"ok": False, "amount": 0, "reason": "user_not_found"}
user_id = user["id"]
# 增加个人算力余额
from ...services.compute_pricing_service import adjust_user_quota
await adjust_user_quota(db, user_id, amount_fen, reason=f"subsidy_grant period={req.period} record_id={req.record_id}")
logger.info("[compute-internal] subsidy granted username=%s amount_fen=%s period=%s record_id=%s",
req.username, amount_fen, req.period, req.record_id)
return {"ok": True, "amount": amount_fen, "username": req.username, "period": req.period}
@router.post("/deduct", summary="compute 引擎扣费回调(按实际费用扣平台账本)")
async def engine_deduct(
req: EngineDeductRequest,
db: Database = Depends(get_db),
_auth: None = Depends(_verify_internal_token),
):
"""compute 扣费后回调:actual_cost_micro(微元)→ 扣企业分配/个人余额,engine_log_id 幂等。"""
try:
result = await deduct_by_engine_cost(
db,
username=req.username,
actual_cost_micro=req.actual_cost_micro,
engine_log_id=req.engine_log_id,
model_name=req.model_name,
token_count=req.token_count,
cost_micro=req.cost_micro,
model_cost_ratio=req.model_cost_ratio,
subsidy_eligible=req.subsidy_eligible,
)
return {"ok": result.get("ok", True), **result}
except Exception as exc: # noqa: BLE001
logger.warning("[compute-internal] deduct callback failed username=%s engine_log_id=%s err=%s",
req.username, req.engine_log_id, exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc