feat(v2): T7 网关扩展 + T9 token 计量与账单(/chat v2,/chat/legacy,/runs,/review,/metrics)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""v2 统计聚合器(V2Stats)—— token 计量与账单(纯标准库)。
|
||||
|
||||
T9:每请求 API token 记账;聚合快路径命中率、回合数分布、熔断次数、累计 token/成本。
|
||||
配合 /metrics 对外透出(论文 E1 token 经济学数据来源之一)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class V2Stats:
|
||||
"""线程安全的 v2 运行统计。"""
|
||||
|
||||
def __init__(self):
|
||||
self._lock = threading.Lock()
|
||||
self._total = 0
|
||||
self._fast_path = 0
|
||||
self._breach = 0
|
||||
self._by_status: Dict[str, int] = {}
|
||||
self._rounds: List[int] = []
|
||||
self._api_input_tokens = 0
|
||||
self._api_output_tokens = 0
|
||||
self._api_cost_usd = 0.0
|
||||
self._recent: List[Dict[str, Any]] = []
|
||||
|
||||
def record(self, result) -> None:
|
||||
"""记录一次 PipelineResult。"""
|
||||
with self._lock:
|
||||
self._total += 1
|
||||
if getattr(result, "fast_path", False):
|
||||
self._fast_path += 1
|
||||
status = getattr(result, "status", "?")
|
||||
self._by_status[status] = self._by_status.get(status, 0) + 1
|
||||
self._rounds.append(getattr(result, "rounds_used", 0))
|
||||
if "breach" in " ".join(getattr(result, "route", [])):
|
||||
self._breach += 1
|
||||
self._api_input_tokens += getattr(result, "api_input_tokens", 0)
|
||||
self._api_output_tokens += getattr(result, "api_output_tokens", 0)
|
||||
self._api_cost_usd += getattr(result, "cost_est", 0.0)
|
||||
self._recent.append({
|
||||
"request_id": getattr(result, "request_id", ""),
|
||||
"status": status,
|
||||
"fast_path": getattr(result, "fast_path", False),
|
||||
"api_input_tokens": getattr(result, "api_input_tokens", 0),
|
||||
"api_output_tokens": getattr(result, "api_output_tokens", 0),
|
||||
"rounds_used": getattr(result, "rounds_used", 0),
|
||||
})
|
||||
if len(self._recent) > 200:
|
||||
self._recent = self._recent[-200:]
|
||||
|
||||
def summary(self) -> Dict[str, Any]:
|
||||
with self._lock:
|
||||
total = self._total
|
||||
rounds = self._rounds
|
||||
return {
|
||||
"total_requests": total,
|
||||
"fast_path_rate": round(self._fast_path / total, 4) if total else 0.0,
|
||||
"status_distribution": dict(self._by_status),
|
||||
"breach_count": self._breach,
|
||||
"rounds_used": {
|
||||
"avg": round(sum(rounds) / len(rounds), 2) if rounds else 0.0,
|
||||
"max": max(rounds) if rounds else 0,
|
||||
"distribution": _histogram(rounds),
|
||||
},
|
||||
"api_tokens": {
|
||||
"input": self._api_input_tokens,
|
||||
"output": self._api_output_tokens,
|
||||
"total": self._api_input_tokens + self._api_output_tokens,
|
||||
"cost_est_usd": round(self._api_cost_usd, 6),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _histogram(values: List[int], max_bucket: int = 10) -> Dict[str, int]:
|
||||
out: Dict[str, int] = {}
|
||||
for v in values:
|
||||
key = str(v) if v <= max_bucket else f">{max_bucket}"
|
||||
out[key] = out.get(key, 0) + 1
|
||||
return out
|
||||
+17
-2
@@ -154,9 +154,14 @@ class WorkerLoop:
|
||||
|
||||
def build_worker(cfg: Dict[str, Any], kb: Any = None,
|
||||
generate: Optional[Callable[[str], Awaitable[str]]] = None) -> WorkerLoop:
|
||||
"""cfg 为 config.worker 段。generate 缺省时用 llama-server 端点客户端(惰性)。"""
|
||||
"""cfg 为 config.worker 段。generate 缺省时按 backend 选择:
|
||||
mock(零运行时演示)| llama_server(真实本地模型,惰性连接)。"""
|
||||
backend = cfg.get("backend", "llama_server")
|
||||
if generate is None:
|
||||
generate = _make_llama_generate(cfg)
|
||||
if backend == "mock":
|
||||
generate = _mock_generate()
|
||||
else:
|
||||
generate = _make_llama_generate(cfg)
|
||||
verifier = Verifier(code_timeout_s=float(cfg.get("code_timeout_s", 10)))
|
||||
return WorkerLoop(
|
||||
generate=generate,
|
||||
@@ -167,6 +172,16 @@ def build_worker(cfg: Dict[str, Any], kb: Any = None,
|
||||
)
|
||||
|
||||
|
||||
def _mock_generate() -> Callable[[str], Awaitable[str]]:
|
||||
"""零运行时 mock 生成器:返回一段确定性文本(演示/测试,不连真实模型)。"""
|
||||
|
||||
async def _gen(prompt: str) -> str:
|
||||
return ("(mock worker)以下是对当前步骤的实现说明:"
|
||||
"步骤已完成,内容足够长且非占位,可供接地验证通过。")
|
||||
|
||||
return _gen
|
||||
|
||||
|
||||
def _make_llama_generate(cfg: Dict[str, Any]) -> Callable[[str], Awaitable[str]]:
|
||||
"""返回调用本地 llama-server(OpenAI 兼容 /v1/chat/completions)的生成器。"""
|
||||
base_url = cfg.get("base_url", f"http://127.0.0.1:{cfg.get('port', 8901)}/v1")
|
||||
|
||||
Reference in New Issue
Block a user