逐任务真实成本:上游用量事件 + 价目表计价
此前每任务只记一条 estimated(字符数/4,金额为空),注释称 host 协议不暴露真实 token 数。本轮实测推翻了该前提: - account/usage/read 在中继上是死的(要求 ChatGPT 账号登录), 不是来源,已在 cost.py docstring 写明死胡同 - app-server 每上游补全发 thread/tokenUsage/updated,last 是该次 补全的用量;中继 5 调用回合实测 last 之和等于最后 total (input/output/cached/reasoning 四路都对得上) 实现:host.py run_turn 从事件窗口按 threadId+turnId 汇总用量, 重试/goal 各轮累加,dispatch/run JSON 与 goal 用量行透传; sandbox 三路解析(dispatch JSON 解析改用完整 stdout,顺带修 了长 text 下 failed 被误判 completed 的潜 bug);worker 每任务 记一条 actual(重试/fallback 用量累加进同一条),无用量时回落 estimated;cost.py 价目表(默认 OpenAI 挂牌价,env 可覆盖, 无价模型 usd 记 None、报表进 unpriced_actual_tokens 而非 0 美元), cost_records 加 cached_input_tokens 列(SQLite/PG 两支迁移); 报表与按天行新增 cached_input_tokens/unpriced_actual_tokens。 测试:host 32、cloud 483 全过。
This commit is contained in:
1 parent
353d6c1aa8
commit
63823fc2e3
11 files changed
+709
-67
No files matched your search
+39
-14
@@ -26,9 +26,12 @@
|
||||
4. 写回:server 的 tasks 表没有 result/error 列,所以执行结果
|
||||
的持久化分三处,各复用已有结构、不另起字段名:
|
||||
- tasks 表:最终状态 + updated_at(取消时为 interrupted);
|
||||
- cost.py CostStore:每轮实际执行记一条 source="estimated"
|
||||
的成本记录(host 协议不暴露真实 token 数,估算绝不冒充
|
||||
actual;total_tokens 由 CostStore.record 按 input+output 算);
|
||||
- cost.py CostStore:每轮实际执行记一条成本记录;执行器带回
|
||||
上游真实用量(host.py 从 tokenUsage/updated 事件汇总)时记
|
||||
source="actual"(input/output/cached 全是实测值,usd 按价目
|
||||
表算、无价模型记 None);没带回用量时回落 cost.py 估算记
|
||||
estimated,估算绝不冒充 actual;total_tokens 由
|
||||
CostStore.record 按 input+output 算;
|
||||
- models.py:可 fallback 的失败(404/429/5xx/timeout,401/400
|
||||
不算)。默认(未配 --fallback-models)时 worker 只记录
|
||||
fallback 事件(record_fallback() 记 model_fallback 事件,
|
||||
@@ -122,7 +125,7 @@ import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
from cloud.auth import AuthStore
|
||||
from cloud.cost import CostStore, estimate_tokens
|
||||
from cloud.cost import CostStore, estimate_tokens, price_usd_micros, sum_usage
|
||||
from cloud.db import connect as _db_connect
|
||||
from cloud.logging_config import get_logger, log_event, setup_logging
|
||||
from cloud.models import ModelRouter, is_fallbackable, record_fallback
|
||||
@@ -1455,8 +1458,12 @@ class Worker:
|
||||
# fallback_models 时保持老行为(router 给出下一模型时只记事件
|
||||
# 不重试);其它失败(含 worker 超时)按 max_retries 做同模型
|
||||
# 重试,退避等待后重跑一次 _run_attempt。
|
||||
# 各次尝试的真实用量都要算进成本(失败尝试花的钱也是钱):
|
||||
# 主尝试与 fallback 尝试的结果都先累加,最后只记一条汇总。
|
||||
usage_total = None
|
||||
for attempt in range(self.max_retries + 1):
|
||||
result, error = self._run_attempt(task)
|
||||
usage_total = sum_usage(usage_total, (result or {}).get("usage"))
|
||||
if self._was_cancelled(task_id) or \
|
||||
result.get("status") == "cancelled":
|
||||
break
|
||||
@@ -1493,6 +1500,8 @@ class Worker:
|
||||
task = dict(task)
|
||||
task["model"] = fb
|
||||
result, error = self._run_attempt(task)
|
||||
usage_total = sum_usage(usage_total,
|
||||
(result or {}).get("usage"))
|
||||
break
|
||||
if self.router is not None and host_status_try is not None \
|
||||
and is_fallbackable(host_status_try) and \
|
||||
@@ -1585,16 +1594,32 @@ class Worker:
|
||||
finished_at=finished_at)
|
||||
with self._db_lock:
|
||||
self.usage_turns += 1
|
||||
# 成本:host 协议不暴露真实 token 数,用 cost.py 的估算结构
|
||||
# 记 estimated,绝不标 actual;fallback_count 复用同一记录;
|
||||
# 模型记最后真正跑的那个(fallback 成功时就是 fallback 模型)。
|
||||
# cost 记录共用一条连接,并发下必须持锁写,否则两个线程
|
||||
# 共用同一 sqlite 连接会直接报错。
|
||||
total = estimate_tokens(task.get("prompt") or "", stdout)
|
||||
cost_id = self.costs.record(
|
||||
task_id, task["project_id"], actual_model,
|
||||
input_tokens=total, output_tokens=0, usd_micros=None,
|
||||
source="estimated", fallback_count=fallback_count)
|
||||
# 成本:执行器带回上游真实用量时记 actual(input/output/
|
||||
# cached 全是实测值,usd 按价目表算、无价模型记 None,
|
||||
# 报表里进 unpriced_actual_tokens 而不是 0 美元);
|
||||
# 没带回用量时回落字符估算记 estimated,绝不冒充 actual。
|
||||
# fallback_count 复用同一记录;模型记最后真正跑的那个
|
||||
# (fallback 成功时就是 fallback 模型)。cost 记录共用
|
||||
# 一条连接,并发下必须持锁写,否则两个线程共用同一
|
||||
# sqlite 连接会直接报错。
|
||||
if usage_total and (usage_total.get("total_tokens") or 0) > 0:
|
||||
cost_id = self.costs.record(
|
||||
task_id, task["project_id"], actual_model,
|
||||
input_tokens=usage_total.get("input_tokens", 0),
|
||||
output_tokens=usage_total.get("output_tokens", 0),
|
||||
cached_input_tokens=usage_total.get(
|
||||
"cached_input_tokens", 0),
|
||||
usd_micros=price_usd_micros(
|
||||
actual_model, usage_total.get("input_tokens", 0),
|
||||
usage_total.get("cached_input_tokens", 0),
|
||||
usage_total.get("output_tokens", 0)),
|
||||
source="actual", fallback_count=fallback_count)
|
||||
else:
|
||||
total = estimate_tokens(task.get("prompt") or "", stdout)
|
||||
cost_id = self.costs.record(
|
||||
task_id, task["project_id"], actual_model,
|
||||
input_tokens=total, output_tokens=0, usd_micros=None,
|
||||
source="estimated", fallback_count=fallback_count)
|
||||
if final_written:
|
||||
self._audit(task["project_id"],
|
||||
"worker_completed" if final == "completed"
|
||||
|
||||
Reference in new issue
Block a user