feat(orchestrator): W4薄片①——new-api logs.quota权威成本接通(取代客户端估算为权威)
- newapi_cost.py: 直读new-api PG logs(真实倍率), model×token_name聚合/生成裁判分列/平台vs创作者归属/交叉校验 - 实测merge-prod-20: ¥0.59总/¥0.0103次/约¥0.031款(MiniMax-M2.7+v4-flash裁判分列)——确证文本生成成本可忽略 - report.py成本段加权威成本指针; 模型§5客户端估算降fallback,权威数落账 - 评审整改: per-creator计量/支付/订阅推迟(admin无法替他人铸token+支付通道3-5周阻塞) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
11009b4064
commit
1ece4a7c43
@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""new-api 权威成本读取(W4 成本侧① · HJ-NEWAPI-BILL-001 薄片①)。
|
||||
|
||||
定位(评审 v2 整改后的薄片范围):
|
||||
把 W4 的 LLM 成本来源从「客户端 token×假设单价 估算」切到 **new-api `logs.quota` 权威**——
|
||||
logs.quota 已含真实模型倍率(new-api 计费引擎算好的),比客户端估算准。只读、零依赖生成路径、
|
||||
零回归风险;且 new-api 历史日志已在库,无需新跑批即可验证。
|
||||
|
||||
读取路径(评审实测裁决):
|
||||
admin HTTP API(/api/log)鉴权 finicky 且 127.0.0.1:3000 回环超时(status=000,实测);
|
||||
**直读 new-api PostgreSQL**(内网、proven、CLAUDE.md 允许内网口令入库)经 `ssh mini-infra docker exec psql`。
|
||||
连接参数为内网常量(可环境变量覆盖),非公网密钥。
|
||||
|
||||
平台系统 vs 创作者成本归属(薄片②):
|
||||
按 logs.token_name 分组——`--platform-token <name>` 标记平台铺量 token,其余归创作者侧。
|
||||
(per-creator 用户/支付/订阅按评审 v2 推迟到支付通道真实化后,本工具不涉。)
|
||||
|
||||
用法:
|
||||
python3 newapi_cost.py --batch runs/merge-prod-20 # 从批 ledger 推窗口 + 交叉校验 report.json
|
||||
python3 newapi_cost.py --since '2026-06-10T14:40:00+08:00' --until '2026-06-10T15:10:00+08:00'
|
||||
python3 newapi_cost.py --minutes 60 # 最近 60 分钟
|
||||
环境变量:NEWAPI_PG_SSH(默认 mini-infra) / NEWAPI_PG_PW / NEWAPI_USD_CNY(默认 7.2)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# —— new-api 配额→¥ 换算(评审 F3/F4 修正)——
|
||||
# QuotaPerUnit 库内无此 key(实查 options 仅 9 键),用 new-api 编译默认:500000 quota = 1 USD。
|
||||
QUOTA_PER_UNIT = float(os.environ.get("NEWAPI_QUOTA_PER_UNIT", "500000"))
|
||||
# USD→¥ 汇率为标注假设(非 new-api 配置项);售卖锁价时须快照,此处仅成本台账折算用。
|
||||
USD_CNY = float(os.environ.get("NEWAPI_USD_CNY", "7.2"))
|
||||
|
||||
# —— 直读 PG 连接(内网常量,CLAUDE.md 允许;可环境变量覆盖)——
|
||||
PG_SSH = os.environ.get("NEWAPI_PG_SSH", "mini-infra")
|
||||
PG_CONTAINER = os.environ.get("NEWAPI_PG_CONTAINER", "infra-postgres")
|
||||
PG_PW = os.environ.get("NEWAPI_PG_PW", "f6710e2d0294eb1c10e26a805a64bc54")
|
||||
PG_USER = os.environ.get("NEWAPI_PG_USER", "root")
|
||||
PG_DB = os.environ.get("NEWAPI_PG_DB", "new-api")
|
||||
|
||||
|
||||
def _psql(sql: str) -> list[list[str]]:
|
||||
"""经 ssh+docker exec 跑只读 psql,返回 `|` 分隔的行(-tA 无表头无对齐,默认分隔符)。
|
||||
|
||||
注:SQL 内禁用双引号(远程经 sh -c "..." 传递,双引号会破坏分层引用);字段分隔用
|
||||
psql 默认 `|`(弃 -F'\t' 字面 tab——会让远程 argv 解析挂起,实测)。
|
||||
"""
|
||||
inner = (
|
||||
'docker exec -e PGPASSWORD=%s %s psql -U %s -d %s -tA -c "%s"'
|
||||
% (PG_PW, PG_CONTAINER, PG_USER, PG_DB, sql)
|
||||
)
|
||||
out = subprocess.run(
|
||||
["ssh", PG_SSH, inner],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
if out.returncode != 0:
|
||||
raise RuntimeError("psql 读取失败:%s" % (out.stderr.strip()[:300]))
|
||||
rows = []
|
||||
for line in out.stdout.splitlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
rows.append(line.split("|"))
|
||||
return rows
|
||||
|
||||
|
||||
def _iso_to_epoch(iso: str) -> int:
|
||||
"""ISO 时间 → unix 秒(logs.created_at 是 epoch int)。经 PG 自身转,避开本地 tz 依赖。"""
|
||||
r = _psql("select extract(epoch from timestamptz '%s')::bigint" % iso)
|
||||
return int(r[0][0])
|
||||
|
||||
|
||||
def _batch_window(run_dir: str) -> tuple[str, str]:
|
||||
"""从批 ledger.jsonl 的首/末 ts 推窗口(含前后各 2 分钟余量覆盖收尾调用)。"""
|
||||
led = os.path.join(run_dir, "ledger.jsonl")
|
||||
ts = []
|
||||
with open(led, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
try:
|
||||
ts.append(json.loads(line)["ts"])
|
||||
except Exception: # noqa: BLE001 - 跳过非法行
|
||||
continue
|
||||
if not ts:
|
||||
raise RuntimeError("ledger 无可用 ts:%s" % led)
|
||||
return min(ts), max(ts)
|
||||
|
||||
|
||||
def _client_estimate(run_dir: str):
|
||||
"""读批 report.json 的 client-side 估算(tokenUsage),用于交叉校验。缺则 None。"""
|
||||
rep = os.path.join(run_dir, "report.json")
|
||||
if not os.path.exists(rep):
|
||||
return None
|
||||
try:
|
||||
with open(rep, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return (data.get("cost") or {}).get("tokenUsage")
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="new-api 权威成本读取(W4 成本侧①)")
|
||||
ap.add_argument("--batch", help="批 run 目录(含 ledger.jsonl/report.json)→ 推窗口+交叉校验")
|
||||
ap.add_argument("--since", help="ISO 起(与 --until 配对)")
|
||||
ap.add_argument("--until", help="ISO 止")
|
||||
ap.add_argument("--minutes", type=float, help="最近 N 分钟")
|
||||
ap.add_argument("--platform-token", default="",
|
||||
help="标记为平台铺量的 token_name(其余归创作者侧,薄片②归属)")
|
||||
args = ap.parse_args()
|
||||
|
||||
client_est = None
|
||||
if args.batch:
|
||||
since_iso, until_iso = _batch_window(args.batch)
|
||||
client_est = _client_estimate(args.batch)
|
||||
since = _iso_to_epoch(since_iso) - 120
|
||||
until = _iso_to_epoch(until_iso) + 120
|
||||
window_desc = "批 %s 窗口 %s ~ %s(±120s)" % (args.batch, since_iso, until_iso)
|
||||
elif args.since and args.until:
|
||||
since = _iso_to_epoch(args.since)
|
||||
until = _iso_to_epoch(args.until)
|
||||
window_desc = "%s ~ %s" % (args.since, args.until)
|
||||
elif args.minutes:
|
||||
now = _psql("select extract(epoch from now())::bigint")[0][0]
|
||||
until = int(now)
|
||||
since = until - int(args.minutes * 60)
|
||||
window_desc = "最近 %g 分钟" % args.minutes
|
||||
else:
|
||||
ap.error("需 --batch / (--since 且 --until) / --minutes 之一")
|
||||
return 2
|
||||
|
||||
# type=2 = 消费日志(new-api:消费日志 type=2);按 model × token_name 聚合权威 quota
|
||||
sql = (
|
||||
"select model_name, coalesce(token_name,'') tn, count(*), "
|
||||
"coalesce(sum(quota),0), coalesce(sum(prompt_tokens),0), coalesce(sum(completion_tokens),0) "
|
||||
"from logs where type=2 and created_at between %d and %d "
|
||||
"group by model_name, tn order by 4 desc"
|
||||
) % (since, until)
|
||||
rows = _psql(sql)
|
||||
|
||||
yuan_per_quota = USD_CNY / QUOTA_PER_UNIT
|
||||
tot_calls = tot_quota = tot_pt = tot_ct = 0
|
||||
plat = {"calls": 0, "quota": 0}
|
||||
crea = {"calls": 0, "quota": 0}
|
||||
|
||||
print("==== new-api 权威成本(logs.quota,含真实倍率) ====")
|
||||
print("窗口:%s" % window_desc)
|
||||
print("换算:QuotaPerUnit=%g/USD(库内无此 key,编译默认)× USD→¥=%g ⇒ ¥%.6g/quota【FX 为假设】"
|
||||
% (QUOTA_PER_UNIT, USD_CNY, yuan_per_quota))
|
||||
print("--- 按 model × token_name ---")
|
||||
for model, tn, calls, quota, pt, ct in rows:
|
||||
calls, quota, pt, ct = int(calls), int(quota), int(pt), int(ct)
|
||||
tot_calls += calls; tot_quota += quota; tot_pt += pt; tot_ct += ct
|
||||
bucket = plat if (args.platform_token and tn == args.platform_token) else crea
|
||||
bucket["calls"] += calls; bucket["quota"] += quota
|
||||
print(" %-18s / %-16s : %4d 次, %8d quota, ¥%.4f"
|
||||
% (model, tn or "—", calls, quota, quota * yuan_per_quota))
|
||||
|
||||
print("--- 合计 ---")
|
||||
print(" %d 次调用, %d quota, %d prompt + %d completion token, **¥%.4f**"
|
||||
% (tot_calls, tot_quota, tot_pt, tot_ct, tot_quota * yuan_per_quota))
|
||||
if tot_calls:
|
||||
print(" 均 %.0f quota/次 ≈ ¥%.5f/次" % (tot_quota / tot_calls, tot_quota / tot_calls * yuan_per_quota))
|
||||
|
||||
# 薄片②:平台铺量 vs 创作者成本归属
|
||||
if args.platform_token:
|
||||
print("--- 归属(platform-token=%s)---" % args.platform_token)
|
||||
print(" 平台铺量 : %d 次, ¥%.4f" % (plat["calls"], plat["quota"] * yuan_per_quota))
|
||||
print(" 创作者侧 : %d 次, ¥%.4f" % (crea["calls"], crea["quota"] * yuan_per_quota))
|
||||
|
||||
# 交叉校验:客户端估算 vs new-api 权威
|
||||
if client_est:
|
||||
print("--- 交叉校验(client-side 估算 vs new-api 权威)---")
|
||||
ce_cost = client_est.get("estCostYuan")
|
||||
ce_tok = client_est.get("total_tokens")
|
||||
print(" client 估算:¥%s(%s token,单价假设 ¥%s/M)"
|
||||
% (ce_cost, ce_tok, client_est.get("pricePerMTokenYuan")))
|
||||
print(" new-api 权威:¥%.4f(%d quota)" % (tot_quota * yuan_per_quota, tot_quota))
|
||||
print(" → 权威为准;client 估算保留为 new-api 不可达时的兜底/交叉校验。")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -316,6 +316,7 @@ def _render_markdown(rep):
|
||||
% (round(tu["estCostYuan"] / max(1, rep["cost"].get("acceptCount", 1)), 4)))
|
||||
elif tu is not None:
|
||||
lines.append("- token 计量(W4):本批通道未返 usage(calls_counted=0),无 token 数据——见 llm_client 计量覆盖率说明")
|
||||
lines.append("- **权威成本(W4 薄片①)**:上为 client-side 估算(单价假设);**new-api `logs.quota` 权威成本**(含真实倍率+生成/裁判分列)运行 `newapi_cost.py --batch <run_dir>`(HJ-NEWAPI-BILL-001 薄片①)")
|
||||
lines.append("")
|
||||
lines.append("## 9. 金丝雀发布清单(≤10/批)")
|
||||
lines.append("")
|
||||
|
||||
@ -44,7 +44,8 @@
|
||||
结构:单款成本 = LLM 调用(设计 1 + 对抗 1 + 修复 ≤1 + 裁判 1 ≈ 4±1 次【确证:编排器流水线结构】× 每次 ~3-6K token【推断:max_tokens=4096 上限,实际未计量】)+ 失败摊销 ×1.25【确证:accept 80% 口径】+ 存储(MB 级,可忽略)。
|
||||
- 若单价 2 元/百万 token【假设·待网关账单】→ **≈ 0.03-0.08 元/款**;单价 ×10 也 <1 元/款。
|
||||
- **文本生成不是成本瓶颈**。真正未计入、可能咬人的两项:**素材生成 GPU**(ComfyUI 未部署,零数据)与**人审兜底人力**——列为 W4 测量项,本模型不编数。
|
||||
- **✅ token 计量埋点已就位(2026-06-11)**:`orchestrator/llm_client.py` 累加全部 chat_json 调用的 usage(prompt/completion/total + calls_counted 覆盖率),批报告「## 8. 成本」自动渲染总 token / 均 token/次 / 估算成本 / 单游戏成本;单价 `PRICE_PER_MTOKEN_YUAN=2.0` 为**假设占位**(环境变量 `NEWAPI_PRICE_PER_MTOKEN` 可覆盖,真实网关账单到位即改此一处自动校准全部估算)。**真实 token 数下次批跑自动采集**(历史批无 usage 数据,不回填)。单测 3 绿(累加/缺 usage 不臆造/成本估算)。⚠ 早期观察:M2.7 推理型含 reasoning_content,每次 token 可能高于本模型「3-6K」推断——以实采为准。
|
||||
- **✅ token 计量埋点已就位(2026-06-11)**:`orchestrator/llm_client.py` 累加 chat_json 的 usage,批报告「## 8. 成本」渲染;单价 `PRICE_PER_MTOKEN_YUAN=2.0` 为假设占位。**此为客户端估算,已降为 fallback**(见下)。
|
||||
- **✅✅ new-api `logs.quota` 权威成本已接通(2026-06-11,HJ-NEWAPI-BILL-001 薄片①,取代上面的客户端估算为权威源)**:`orchestrator/newapi_cost.py` 直读 new-api PostgreSQL logs(含真实模型倍率,new-api 计费引擎算好),按 model×token_name 聚合、生成/裁判分列、平台铺量 vs 创作者归属、交叉校验客户端估算。**实测权威数(merge-prod-20,20 创意/57 调用/19 accept)= ¥0.59 总、¥0.0103/次、约 ¥0.031/款**(MiniMax-M2.7 53 次 ¥0.58 + deepseek-v4-flash 裁判 4 次 ¥0.005)——**确证文本生成成本可忽略**(与 §3 三线排序结论一致,自有端 IAA 万级 DAU 才回本,近期现金靠 B 端)。换算 `QuotaPerUnit=500000/USD`(new-api 编译默认,库内无此 key)× USD→¥ 假设 7.2。**历史批无需重跑即可读**(new-api logs 已在库)。⚠ 评审整改:per-creator 计量/支付/订阅按 `2026-06-11-newapi计费平面集成-review.md` v2 推迟到支付通道真实化后(admin API 无法替他人铸 token + 支付通道 ICP/进件 3-5 周阻塞)。
|
||||
|
||||
## 6. 口径一致性勘误(随本拍板执行)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user