逐任务真实成本:上游用量事件 + 价目表计价
此前每任务只记一条 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
@@ -374,6 +374,75 @@ class WorkerTestCase(unittest.TestCase):
|
||||
self.assertEqual(results[0]["status"], "failed")
|
||||
self.assertIn("不支持 goal", results[0]["error"])
|
||||
|
||||
# -- 成本:真实用量 --------------------------------------------------
|
||||
def test_cost_actual_from_usage(self):
|
||||
"""执行器带回用量时记 actual:token 全是实测值,usd 按
|
||||
价目表算((1000-400)*2 + 400*0.10 + 100*10 = 2240 微美元)。"""
|
||||
_insert_task(self.db_path, "A")
|
||||
usage = {"input_tokens": 1000, "cached_input_tokens": 400,
|
||||
"cache_write_input_tokens": 0, "output_tokens": 100,
|
||||
"reasoning_output_tokens": 20, "total_tokens": 1100,
|
||||
"completions": 1}
|
||||
|
||||
def runner(cmd):
|
||||
return SimpleNamespace(
|
||||
returncode=0,
|
||||
stdout=json.dumps({"status": "completed", "text": "done",
|
||||
"usage": usage}),
|
||||
stderr="")
|
||||
|
||||
worker = self._worker(runner=runner, model="gpt-6.1-sol")
|
||||
results = worker.run_all()
|
||||
self.assertEqual([r["status"] for r in results], ["completed"])
|
||||
from cloud.cost import CostStore
|
||||
report = CostStore(self.db_path).report_by_task("A")
|
||||
self.assertEqual(report["actual_tokens"], 1100)
|
||||
self.assertEqual(report["cached_input_tokens"], 400)
|
||||
self.assertEqual(report["estimated_tokens"], 0)
|
||||
self.assertEqual(report["usd_micros"], 2240)
|
||||
self.assertEqual(report["unpriced_actual_tokens"], 0)
|
||||
|
||||
def test_cost_actual_unpriced_model(self):
|
||||
"""无价模型:token 照样记 actual,usd 记 None、报表里
|
||||
进 unpriced_actual_tokens,绝不按 0 美元处理。"""
|
||||
_insert_task(self.db_path, "A")
|
||||
usage = {"input_tokens": 1000, "cached_input_tokens": 0,
|
||||
"cache_write_input_tokens": 0, "output_tokens": 100,
|
||||
"reasoning_output_tokens": 0, "total_tokens": 1100,
|
||||
"completions": 1}
|
||||
|
||||
def runner(cmd):
|
||||
return SimpleNamespace(
|
||||
returncode=0,
|
||||
stdout=json.dumps({"status": "completed", "text": "done",
|
||||
"usage": usage}),
|
||||
stderr="")
|
||||
|
||||
worker = self._worker(runner=runner, model="gpt-5.6-sol")
|
||||
results = worker.run_all()
|
||||
self.assertEqual([r["status"] for r in results], ["completed"])
|
||||
from cloud.cost import CostStore
|
||||
report = CostStore(self.db_path).report_by_task("A")
|
||||
self.assertEqual(report["actual_tokens"], 1100)
|
||||
self.assertEqual(report["usd_micros"], 0)
|
||||
self.assertEqual(report["unpriced_actual_tokens"], 1100)
|
||||
|
||||
def test_cost_estimated_when_no_usage(self):
|
||||
"""执行器没带回用量时回落字符估算记 estimated(旧行为)。"""
|
||||
_insert_task(self.db_path, "A")
|
||||
|
||||
def runner(cmd):
|
||||
return SimpleNamespace(returncode=0, stdout="plain text",
|
||||
stderr="")
|
||||
|
||||
worker = self._worker(runner=runner, model="gpt-6.1-sol")
|
||||
results = worker.run_all()
|
||||
self.assertEqual([r["status"] for r in results], ["completed"])
|
||||
from cloud.cost import CostStore
|
||||
report = CostStore(self.db_path).report_by_task("A")
|
||||
self.assertEqual(report["actual_tokens"], 0)
|
||||
self.assertGreater(report["estimated_tokens"], 0)
|
||||
|
||||
# -- 取消 ------------------------------------------------------------
|
||||
def test_cancel_queued_task_marks_interrupted(self):
|
||||
_insert_task(self.db_path, "A")
|
||||
|
||||
Reference in new issue
Block a user