From 92db5a0802a43fd72a16b87740bb7666e8263b0f Mon Sep 17 00:00:00 2001 From: Pine Date: Tue, 8 Sep 2026 18:17:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=90=E8=90=A5=E7=AB=AF=E6=94=B9?= =?UTF-8?q?=E4=BB=B7=E5=90=8C=E6=AD=A5compute=E6=9C=8D=E5=8A=A1models?= =?UTF-8?q?=E8=A1=A8=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=AE=9E=E9=99=85=E8=AE=A1?= =?UTF-8?q?=E8=B4=B9=E4=BB=8D=E7=94=A8=E6=97=A7=E4=BB=B7=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/routers/rbac_compute_pricing.py | 63 +++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/app/api/routers/rbac_compute_pricing.py b/app/api/routers/rbac_compute_pricing.py index 8f503ed..0af898a 100644 --- a/app/api/routers/rbac_compute_pricing.py +++ b/app/api/routers/rbac_compute_pricing.py @@ -15,6 +15,7 @@ import uuid from datetime import datetime from typing import Optional +import httpx from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel from sqlalchemy import select, and_ @@ -22,6 +23,7 @@ from sqlalchemy import select, and_ from ..dependencies import get_db, get_current_user from ...rbac import write_audit from ...domain.rules import role_allowed +from ...config import COMPUTE_BASE_URL, COMPUTE_ADMIN_TOKEN, COMPUTE_TIMEOUT from ...infrastructure.models import ( ParkTenant, ParkCompany, CompanyMember, User, TenantDiscount, TenantUserDiscount, @@ -219,11 +221,66 @@ async def admin_update_standard_price( user: dict = Depends(get_current_user), ): check_role(user, ["operator"]) - STANDARD_PRICES[model] = max(1, int(body.price_per_1k)) + new_price = max(1, int(body.price_per_1k)) + STANDARD_PRICES[model] = new_price + + # 同步更新 compute 服务 models 表价格(实际计费以此为准) + # 单位转换:分/1000token → 元/百万token = price * 10 + compute_price_yuan_per_m = round(new_price * 10, 4) + synced = await _sync_compute_model_price(model, compute_price_yuan_per_m) + await write_audit(db, action="compute.standard_price_update", resource="standard_price", - resource_id=model, detail=f"price={body.price_per_1k}", + resource_id=model, detail=f"price={new_price}, compute_synced={synced}", user=user, request=request) - return {"ok": True, "model": model, "price_per_1k": STANDARD_PRICES[model]} + return {"ok": True, "model": model, "price_per_1k": STANDARD_PRICES[model], + "compute_synced": synced} + + +async def _sync_compute_model_price(model_name: str, price_yuan_per_m: float) -> bool: + """同步价格到 compute 服务 models 表(input_price = output_price = 统一价)。 + + 返回是否同步成功;失败不影响 server-core 内存价格更新,仅记录日志。 + """ + if not COMPUTE_BASE_URL or not COMPUTE_ADMIN_TOKEN: + logger.warning("compute 服务地址或 token 未配置,跳过价格同步") + return False + headers = {"Authorization": f"Bearer {COMPUTE_ADMIN_TOKEN}", + "Content-Type": "application/json"} + try: + async with httpx.AsyncClient(timeout=COMPUTE_TIMEOUT) as client: + # 1. 查模型列表,按 model_name 匹配 id + resp = await client.get(f"{COMPUTE_BASE_URL}/admin/models", + params={"page_size": 200}, headers=headers) + if resp.status_code != 200: + logger.warning(f"compute 模型列表查询失败: {resp.status_code}") + return False + data = resp.json() + items = data.get("data", {}).get("items", data.get("data", [])) + if isinstance(items, dict): + items = items.get("items", []) + target = None + for m in items: + if m.get("model_name") == model_name or m.get("name") == model_name: + target = m + break + if target is None: + logger.info(f"compute 服务中未找到模型 {model_name},跳过同步") + return False + # 2. 更新价格 + mid = target.get("id") + resp = await client.put(f"{COMPUTE_BASE_URL}/admin/models", + json={"id": mid, + "input_price": price_yuan_per_m, + "output_price": price_yuan_per_m}, + headers=headers) + if resp.status_code == 200: + logger.info(f"compute 模型 {model_name}(id={mid}) 价格已同步为 {price_yuan_per_m} 元/百万token") + return True + logger.warning(f"compute 模型价格更新失败: {resp.status_code} {resp.text}") + return False + except Exception as e: + logger.warning(f"compute 价格同步异常: {e}") + return False # ═══════════════════════════════════════════════════════════════════