feat(opc): 用户端算力中心(供应商自动对接 PineAgents)
- 新增 /opc/compute/*(require_roles opc_member): base(基础 API 地址)/models(可用模型)/prices(价目)/tokens(我的令牌 列表/创建/删除) - pineagents_catalog: 供应商自动对接 PineAgents 的模型与价目目录(与 /v1 中继模型一致) - compute_client: 新增 issue_user_token/list_user_tokens/delete_token(按用户名挂引擎用户) - 令牌创建经引擎签 PAT, 用户可自服务管理; 引擎数据经 server-core 代理, 前端不直连
This commit is contained in:
@@ -14,6 +14,7 @@ from ..schemas.opc import BidRequest, ProfileUpdate, FinanceRecordCreate
|
||||
from ...rbac import require_roles, write_audit
|
||||
from ...infrastructure.repositories import Database, new_id, utcnow_iso
|
||||
from ...infrastructure.models import FinanceRecord
|
||||
from ...services import compute_client, pineagents_catalog
|
||||
|
||||
router = APIRouter(prefix="/opc", tags=["opc"])
|
||||
|
||||
@@ -212,3 +213,59 @@ async def opc_affairs(
|
||||
_u: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
return await db.portal_pages.get("opc", "affairs") or {"items": []}
|
||||
|
||||
|
||||
# ── 算力中心(用户端,供应商自动对接 PineAgents,令牌自服务)─────────────────
|
||||
@router.get("/compute/base", summary="算力基础 API 地址")
|
||||
async def opc_compute_base(
|
||||
request: Request,
|
||||
user: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
"""用户接入地址:经 server-core /v1 中继(OpenAI 兼容)。"""
|
||||
base = request.base_url.rstrip("/")
|
||||
return {
|
||||
"base_url": f"{base}/v1",
|
||||
"relay_path": "/v1/chat/completions",
|
||||
"models_path": "/v1/models",
|
||||
"username": user.get("username"),
|
||||
"hint": "将 base_url 与下方令牌填入 OpenAI 兼容客户端(如 request_url/base)即可调用本平台模型",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/compute/models", summary="可用模型(PineAgents 自动对接)")
|
||||
async def opc_compute_models(_u: dict = Depends(require_roles("opc_member"))):
|
||||
return {"items": pineagents_catalog.models()}
|
||||
|
||||
|
||||
@router.get("/compute/prices", summary="模型价目")
|
||||
async def opc_compute_prices(_u: dict = Depends(require_roles("opc_member"))):
|
||||
return {"items": pineagents_catalog.prices()}
|
||||
|
||||
|
||||
@router.get("/compute/tokens", summary="我的算力令牌")
|
||||
async def opc_compute_tokens(user: dict = Depends(require_roles("opc_member"))):
|
||||
"""列出当前用户的引擎消费令牌。"""
|
||||
try:
|
||||
data = await compute_client.list_user_tokens(user.get("username"))
|
||||
except compute_client.ComputeError as exc:
|
||||
raise HTTPException(status_code=502, detail=f"算力引擎对接失败: {exc}") from exc
|
||||
return {"items": data.get("items", [])}
|
||||
|
||||
|
||||
@router.post("/compute/tokens", summary="创建算力令牌")
|
||||
async def opc_compute_tokens_create(user: dict = Depends(require_roles("opc_member"))):
|
||||
"""为当前用户签发一枚消费令牌(供应商自动对接 PineAgents)。"""
|
||||
try:
|
||||
token = await compute_client.issue_user_token(user.get("username"), name="OPC 端令牌")
|
||||
except compute_client.ComputeError as exc:
|
||||
raise HTTPException(status_code=502, detail=f"算力引擎对接失败: {exc}") from exc
|
||||
return token
|
||||
|
||||
|
||||
@router.delete("/compute/tokens/{token_id}", summary="删除算力令牌")
|
||||
async def opc_compute_tokens_delete(token_id: int, user: dict = Depends(require_roles("opc_member"))):
|
||||
try:
|
||||
await compute_client.delete_token(token_id)
|
||||
except compute_client.ComputeError as exc:
|
||||
raise HTTPException(status_code=502, detail=f"算力引擎对接失败: {exc}") from exc
|
||||
return {"ok": True, "token_id": token_id}
|
||||
|
||||
@@ -110,6 +110,30 @@ async def get_logs(user_pat: str) -> dict:
|
||||
return await _request(method="GET", path="/api/log/self", headers=headers)
|
||||
|
||||
|
||||
async def issue_user_token(username: str, name: str = "") -> dict:
|
||||
"""为某引擎用户签发消费令牌,返回 {id, key, ...}。"""
|
||||
data = await _request(
|
||||
method="POST", path="/api/token", headers=_admin_headers(),
|
||||
json={"username": username, "name": name},
|
||||
)
|
||||
return data.get("data") or {}
|
||||
|
||||
|
||||
async def list_user_tokens(username: str) -> dict:
|
||||
"""列出某引擎用户的消费令牌(按用户名挂到引擎用户)。"""
|
||||
data = await _request(
|
||||
method="GET", path=f"/api/token?username={username}", headers=_admin_headers(),
|
||||
)
|
||||
return (data.get("data") or {}).get("items") or []
|
||||
|
||||
|
||||
async def delete_token(token_id: int) -> dict:
|
||||
"""删除引擎令牌。"""
|
||||
return await _request(
|
||||
method="DELETE", path=f"/api/token/{token_id}", headers=_admin_headers(),
|
||||
)
|
||||
|
||||
|
||||
async def sync_user_enabled(username: str, enabled: bool) -> dict:
|
||||
"""按 username 同步引擎用户启用/禁用(平台用户生命周期自动同步)。"""
|
||||
return await _request(
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""PineAgents 算力目录:用户端算力中心「自动对接的供应商」模型清单与价目。
|
||||
|
||||
供应商自动对接 PineAgents —— 用户无需配置上游,直接看到本平台经 server-core /v1 中继
|
||||
可调用的模型与价格。模型列表与 relay.py 的 _ENGINE_MODEL_IDS 保持一致;价格为 元/百万token。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
# 模型目录(id = OpenAI 兼容模型名,供 /v1/chat/completions 使用)
|
||||
MODELS: list[dict] = [
|
||||
{"id": "deepseek-chat", "name": "DeepSeek Chat", "company": "深度求索", "tags": ["对话", "通用"]},
|
||||
{"id": "deepseek-reasoner", "name": "DeepSeek Reasoner", "company": "深度求索", "tags": ["推理", "深度思考"]},
|
||||
{"id": "deepseek-v4-flash", "name": "DeepSeek V4 Flash", "company": "深度求索", "tags": ["快速", "低延迟"]},
|
||||
{"id": "deepseek-v4-pro", "name": "DeepSeek V4 Pro", "company": "深度求索", "tags": ["旗舰", "高性能"]},
|
||||
]
|
||||
|
||||
# 价目:元 / 百万token(输入 / 输出)
|
||||
PRICES: dict[str, dict] = {
|
||||
"deepseek-chat": {"input": 0.5, "output": 1.5, "unit": "/百万tokens"},
|
||||
"deepseek-reasoner": {"input": 1.0, "output": 2.5, "unit": "/百万tokens"},
|
||||
"deepseek-v4-flash": {"input": 0.3, "output": 1.0, "unit": "/百万tokens"},
|
||||
"deepseek-v4-pro": {"input": 2.0, "output": 6.0, "unit": "/百万tokens"},
|
||||
}
|
||||
|
||||
|
||||
def models() -> list[dict]:
|
||||
"""可用模型清单(加入价格为可选)。"""
|
||||
out = []
|
||||
for m in MODELS:
|
||||
price = PRICES.get(m["id"], {})
|
||||
out.append({**m, "price": price})
|
||||
return out
|
||||
|
||||
|
||||
def prices() -> list[dict]:
|
||||
return [{"id": m["id"], "name": m["name"], **PRICES.get(m["id"], {})} for m in MODELS]
|
||||
Reference in New Issue
Block a user