Files
projectAIpopular/gateway/proxy/billing.py
T
tzt e00e2cd2b1 feat(proxy): 架构与算法优化二轮——sense 运行时单例激活 T-X3、打码密钥泄漏修复、账本热路径 2.4x
- gateway/proxy/routes.py:sense 运行时(SenseStore+Grader+晋升表)按配置单例化,
  原每请求重建导致:每请求跑全量 DDL、重读工件文件,且 T-X3 60s 决策缓存随
  Grader 丢弃、命中率恒为 0(本轮最大收益,激活既有已测组件);
  T1 审计抽样修复原 NameError×3 被 except 吞掉(load_config/get_review/request_id
  均未定义、§9.4 从未入队)——request_id 上提、review 队列经 review_getter 注入
  (api.py 反向依赖解除)、sample_rate 走 load_config
- gateway/model_pool.py:新增 usable_entries() 未打码内通道;routes 的降级链/
  预算降档/能力位重定向/档位映射四处改走该通道——修复打码 api_key 流入上游
  Bearer 头导致带密钥条目重定向必 401 的隐性缺陷(HTTP 管理面仍用打码 list())
- gateway/proxy/ledger.py + billing.py:共享持久连接(原每操作新建,全链路
  每请求 3-5 次 connect)+ 日重置 UPDATE 每实例每日一次短路(原每请求全表扫描
  抢写锁);check_and_count A/B 0.75→0.31 ms/op(2.4x);新增 usage_stats()
  SQL 聚合,/admin/stats 从 list_usage(limit=50 万) Python 四遍扫描改为下推聚合
  (微基准 ~28x,随流水线性扩大)
- gateway/sense/grader.py:决策留痕 insert_decision 移入 asyncio.to_thread
  (原同步 sqlite 写直接跑在事件循环线程,高并发阻塞网关;与观察写批量队列同等保护)
- 全量 474 项两轮复核全绿(基线 474)
2026-09-18 23:59:44 +08:00

230 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""计费两阶段(D-P11):预扣-结算-退款,作为 Ledger 的 mixin。
拆分为独立模块的工程原因:ledger.py 承载 DDL/CRUD/查询;金额敏感的
两阶段语义集中在此,便于评审与测试聚焦。
并发正确性:全局锁内 check-then-act(单写者模型)+ 余额原子防线
(语句条件 balance_milli >= ?,受影响行数为 0 即不足)。
"""
from __future__ import annotations
import threading
import time
from typing import Any, Dict, List, Optional
from gateway.proxy.ledgerutil import _today
# 预扣:余额原子防线;日上限判定在锁内 Python 侧(单写者模型下等价安全)
# 占位顺序:est, spent_total, today, sid, est
_SQL_HOLD = """UPDATE students SET
balance_milli = balance_milli - ?,
spent_today_milli = ?, spent_date = ?
WHERE id = ? AND balance_milli >= ?"""
# 预算四档阈值(T-X1,基点 bp:1% = 100bp;整数比较,杜绝浮点边界漂移)
BUDGET_OPTIMIZE_BP = 8000 # >= 80% 建议降一档
BUDGET_CHEAP_BP = 9500 # >= 95% 强制最低档
_SQL_HOLD_MARK = """INSERT INTO usage_ledger
(request_id, ts, key_id, model, bucket, charged_milli, status)
VALUES (?, ?, ?, ?, ?, ?, 'holding')"""
_SQL_INSUFFICIENT = """INSERT OR REPLACE INTO usage_ledger
(request_id, ts, key_id, model, bucket, charged_milli, status)
VALUES (?, ?, ?, ?, ?, 0, 'insufficient')"""
_SQL_SETTLE_REFUND = """UPDATE students SET
balance_milli = balance_milli + ?,
spent_today_milli = MAX(0, spent_today_milli - ?)
WHERE id = ?"""
_SQL_SETTLE_USAGE = """UPDATE usage_ledger SET
in_miss_tok = ?, in_hit_tok = ?, out_tok = ?, gateway_cached = ?,
upstream_cost_milli = ?, charged_milli = ?, margin_milli = ? - ?,
ttfb_ms = ?, total_ms = ?, status = ?
WHERE request_id = ?"""
class BillingMixin:
"""try_hold / settle / void(由 Ledger 继承;依赖 _lock/_connect)。"""
_lock: threading.Lock
def _connect(self): # 由 Ledger 提供
raise NotImplementedError
def try_hold(self, request_id: str, key_id: int, student_id: int,
model: str, bucket: str, est_milli: int, ts: float) -> bool:
"""预扣:锁内读现状 -> 日上限判定 -> 余额原子扣减 -> 写 holding 流水。
受影响行数为 0 即不足(余额不够)-> False402);
request_id 幂等:重复 hold 直接返回 True(不重复扣)。
"""
today = _today(ts)
with self._lock, self._connect() as conn:
dup = conn.execute(
"SELECT 1 FROM usage_ledger WHERE request_id = ?",
(request_id,)).fetchone()
if dup:
return True
stu = conn.execute(
"SELECT balance_milli, daily_cap_milli, spent_today_milli, spent_date"
" FROM students WHERE id = ?", (student_id,)).fetchone()
if stu is None:
return False
spent_base = stu["spent_today_milli"] if stu["spent_date"] == today else 0
if stu["daily_cap_milli"] > 0 and spent_base + est_milli > stu["daily_cap_milli"]:
conn.execute(_SQL_INSUFFICIENT,
(request_id, int(ts), key_id, model, bucket))
return False
cur = conn.execute(_SQL_HOLD, (
est_milli, spent_base + est_milli, today, student_id, est_milli))
if cur.rowcount == 0:
conn.execute(_SQL_INSUFFICIENT,
(request_id, int(ts), key_id, model, bucket))
return False
conn.execute(_SQL_HOLD_MARK,
(request_id, int(ts), key_id, model, bucket, est_milli))
return True
def settle(self, request_id: str, actual_milli: int, *,
in_miss_tok: int = 0, in_hit_tok: int = 0, out_tok: int = 0,
gateway_cached: int = 0, upstream_cost_milli: int = 0,
ttfb_ms: Optional[int] = None, total_ms: Optional[int] = None,
status: str = "ok") -> bool:
"""结算:按真实值更新流水并回补(预扣额 − 实际额)差额。"""
with self._lock, self._connect() as conn:
row = conn.execute(
"SELECT key_id, charged_milli FROM usage_ledger WHERE request_id = ?",
(request_id,)).fetchone()
if row is None:
return False
key_id = row["key_id"]
est = row["charged_milli"]
student = conn.execute(
"SELECT student_id FROM proxy_keys WHERE id = ?", (key_id,)).fetchone()
if student is None:
return False
refund = est - actual_milli
if refund != 0:
conn.execute(_SQL_SETTLE_REFUND,
(refund, refund, student["student_id"]))
conn.execute(_SQL_SETTLE_USAGE, (
in_miss_tok, in_hit_tok, out_tok, gateway_cached,
upstream_cost_milli, actual_milli,
actual_milli, upstream_cost_milli, ttfb_ms, total_ms,
status, request_id))
return True
def void(self, request_id: str, status: str = "aborted") -> bool:
"""全额退款(上游失败);流水保留审计。"""
with self._lock, self._connect() as conn:
row = conn.execute(
"SELECT key_id, charged_milli, status FROM usage_ledger"
" WHERE request_id = ?", (request_id,)).fetchone()
if row is None or row["status"] not in ("holding", "ok"):
return False
est = row["charged_milli"]
student = conn.execute(
"SELECT student_id FROM proxy_keys WHERE id = ?",
(row["key_id"],)).fetchone()
if student is not None and est > 0:
conn.execute(_SQL_SETTLE_REFUND,
(est, est, student["student_id"]))
conn.execute(
"UPDATE usage_ledger SET charged_milli = 0, status = ?"
" WHERE request_id = ?", (status, request_id))
return True
# ---------- 预算四档(T-X1 ----------
def budget_mode(self, student_id: int, est_milli: int, ts: float) -> str:
"""预算档位判定:normal / optimize / cheap / block。
以「当日已 spent + 本次预估」占日上限的比例判定(基点 bp,全整数运算,
无浮点边界漂移;黄金用例锁死):
< 8000bp80% -> normal(现行行为)
>= 8000bp80% -> optimize(调用方应降一档模型)
>= 9500bp95% -> cheap(调用方应强制最低档)
> 10000bp100% -> blocktry_hold 的硬拒绝语义兜底)
daily_cap_milli <= 0 视为不限额 -> 恒 normal;学生不存在 -> normal
(try_hold 才是扣费与拒绝的唯一权威,本方法只做档位建议)。
"""
today = _today(ts)
with self._lock, self._connect() as conn:
stu = conn.execute(
"SELECT daily_cap_milli, spent_today_milli, spent_date"
" FROM students WHERE id = ?", (student_id,)).fetchone()
if stu is None:
return "normal"
cap = int(stu["daily_cap_milli"] or 0)
if cap <= 0:
return "normal"
spent = int(stu["spent_today_milli"] or 0) \
if stu["spent_date"] == today else 0
projected = spent + max(0, int(est_milli))
projected_bp = projected * 10000 // cap
if projected_bp > 10000:
return "block"
if projected_bp >= BUDGET_CHEAP_BP:
return "cheap"
if projected_bp >= BUDGET_OPTIMIZE_BP:
return "optimize"
return "normal"
# ---------- 流水查询 ----------
def get_usage(self, request_id: str) -> Optional[Dict[str, Any]]:
with self._lock, self._connect() as conn:
row = conn.execute(
"SELECT * FROM usage_ledger WHERE request_id = ?",
(request_id,)).fetchone()
return dict(row) if row else None
def usage_stats(self) -> Dict[str, Any]:
"""全局流水聚合(§5.2 看板口径,2026-09 优化:SQL 下推替代全量拉取)。
返回 {requests, gateway_cached, in_hit_tok, in_miss_tok, revenue_milli,
cost_milli, by_bucket}by_bucket 键为 bucket 名。
"""
with self._lock, self._connect() as conn:
row = conn.execute(
"SELECT COUNT(*) AS requests,"
" COALESCE(SUM(gateway_cached), 0) AS gateway_cached,"
" COALESCE(SUM(in_hit_tok), 0) AS in_hit_tok,"
" COALESCE(SUM(in_miss_tok), 0) AS in_miss_tok,"
" COALESCE(SUM(charged_milli), 0) AS revenue_milli,"
" COALESCE(SUM(upstream_cost_milli), 0) AS cost_milli"
" FROM usage_ledger").fetchone()
buckets = conn.execute(
"SELECT bucket, COUNT(*) AS requests,"
" COALESCE(SUM(charged_milli), 0) AS revenue_milli,"
" COALESCE(SUM(upstream_cost_milli), 0) AS cost_milli"
" FROM usage_ledger GROUP BY bucket").fetchall()
return {
"requests": row["requests"],
"gateway_cached": row["gateway_cached"],
"in_hit_tok": row["in_hit_tok"],
"in_miss_tok": row["in_miss_tok"],
"revenue_milli": row["revenue_milli"],
"cost_milli": row["cost_milli"],
"by_bucket": {r["bucket"]: {"requests": r["requests"],
"revenue_milli": r["revenue_milli"],
"cost_milli": r["cost_milli"]}
for r in buckets},
}
def list_usage(self, student_id: Optional[int] = None,
limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
"""流水分页(可按学生过滤,经其名下 key);按学生过滤走联表常量语句。"""
if student_id is None:
with self._lock, self._connect() as conn:
rows = conn.execute(
"SELECT * FROM usage_ledger ORDER BY ts DESC LIMIT ? OFFSET ?",
(limit, offset)).fetchall()
return [dict(r) for r in rows]
with self._lock, self._connect() as conn:
rows = conn.execute(
"SELECT u.* FROM usage_ledger u JOIN proxy_keys k ON k.id = u.key_id"
" WHERE k.student_id = ? ORDER BY u.ts DESC LIMIT ? OFFSET ?",
(student_id, limit, offset)).fetchall()
return [dict(r) for r in rows]