e529112a90
## 问题 比价记录进入中途退出后未触发 LLM 成本回填,周期补偿也未扫描 cancelled,导致实际已有 LLM 调用的记录长期显示成本、LLM、TOKEN 为空。 ## 修改 - finalize 落库后立即追加 LLM 成本回填 - 周期补偿范围加入 cancelled - 保持无有效调用和全调用失败记录不伪造成本 - 增加即时回填和周期补偿回归测试 ## 验证 - ruff 检查通过 - 相关测试 28 项通过 - 全仓 626 项通过;主干既有失败已在未修改的 origin/main 复现 --------- Co-authored-by: unknown <798648091@qq.com> Reviewed-on: #191 Co-authored-by: linkeyu <linkeyu@wonderable.ai> Co-committed-by: linkeyu <linkeyu@wonderable.ai>
166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
from app.core.rewards import CN_TZ
|
|
from app.db.session import SessionLocal
|
|
from app.models.app_config import AppConfig
|
|
from app.models.comparison import ComparisonRecord
|
|
from app.services import comparison_llm_backfill
|
|
|
|
|
|
def _record(
|
|
trace_id: str,
|
|
*,
|
|
status: str = "success",
|
|
created_at: datetime | None = None,
|
|
) -> int:
|
|
with SessionLocal() as db:
|
|
rec = ComparisonRecord(
|
|
trace_id=trace_id,
|
|
status=status,
|
|
created_at=created_at or datetime.now(),
|
|
)
|
|
db.add(rec)
|
|
db.commit()
|
|
return rec.id
|
|
|
|
|
|
def _delete(record_id: int) -> None:
|
|
with SessionLocal() as db:
|
|
rec = db.get(ComparisonRecord, record_id)
|
|
if rec is not None:
|
|
db.delete(rec)
|
|
db.commit()
|
|
|
|
|
|
def test_backfill_retries_then_persists_cost(monkeypatch):
|
|
record_id = _record("llm-retry-1")
|
|
calls = [
|
|
{
|
|
"model": "unknown-model",
|
|
"error": None,
|
|
"usage": {"prompt_tokens": 1000, "completion_tokens": 500},
|
|
}
|
|
]
|
|
responses = iter([[], calls])
|
|
monkeypatch.setattr(
|
|
comparison_llm_backfill,
|
|
"fetch_llm_calls",
|
|
lambda trace_id: next(responses),
|
|
)
|
|
sleeps: list[float] = []
|
|
monkeypatch.setattr(comparison_llm_backfill.time, "sleep", sleeps.append)
|
|
monkeypatch.setattr(
|
|
comparison_llm_backfill.settings, "INTERNAL_API_SECRET", "test-secret"
|
|
)
|
|
|
|
try:
|
|
assert comparison_llm_backfill.backfill_comparison_llm_cost(
|
|
record_id, "llm-retry-1", attempts=2, retry_delays=(0.25,)
|
|
)
|
|
assert sleeps == [0.25]
|
|
with SessionLocal() as db:
|
|
rec = db.get(ComparisonRecord, record_id)
|
|
assert rec.input_tokens == 1000
|
|
assert rec.output_tokens == 500
|
|
assert rec.llm_cost_yuan is not None
|
|
assert rec.llm_calls == calls
|
|
finally:
|
|
_delete(record_id)
|
|
|
|
|
|
def test_repair_batch_only_targets_terminal_missing_rows(monkeypatch):
|
|
missing_id = _record("llm-repair-missing")
|
|
cancelled_id = _record("llm-repair-cancelled", status="cancelled")
|
|
running_id = _record("llm-repair-running", status="running")
|
|
calls = [
|
|
{
|
|
"model": "unknown-model",
|
|
"error": None,
|
|
"usage": {"prompt_tokens": 100, "completion_tokens": 20},
|
|
}
|
|
]
|
|
seen: list[str] = []
|
|
|
|
def fetch(trace_id: str) -> list[dict]:
|
|
seen.append(trace_id)
|
|
return calls
|
|
|
|
monkeypatch.setattr(comparison_llm_backfill, "fetch_llm_calls", fetch)
|
|
monkeypatch.setattr(
|
|
comparison_llm_backfill.settings, "INTERNAL_API_SECRET", "test-secret"
|
|
)
|
|
try:
|
|
result = comparison_llm_backfill.repair_missing_comparison_llm_costs(
|
|
limit=10, lookback_days=1
|
|
)
|
|
assert result["repaired"] >= 1
|
|
assert "llm-repair-missing" in seen
|
|
assert "llm-repair-cancelled" in seen
|
|
assert "llm-repair-running" not in seen
|
|
with SessionLocal() as db:
|
|
assert db.get(ComparisonRecord, missing_id).llm_cost_yuan is not None
|
|
assert db.get(ComparisonRecord, cancelled_id).llm_cost_yuan is not None
|
|
assert db.get(ComparisonRecord, running_id).llm_cost_yuan is None
|
|
finally:
|
|
_delete(missing_id)
|
|
_delete(cancelled_id)
|
|
_delete(running_id)
|
|
|
|
|
|
def test_repair_excludes_records_before_current_price_config(monkeypatch):
|
|
price_changed_at = datetime.now(UTC) - timedelta(hours=1)
|
|
before_change = (price_changed_at - timedelta(minutes=30)).astimezone(CN_TZ)
|
|
after_change = (price_changed_at + timedelta(minutes=30)).astimezone(CN_TZ)
|
|
before_id = _record(
|
|
"llm-before-price-change",
|
|
created_at=before_change.replace(tzinfo=None),
|
|
)
|
|
after_id = _record(
|
|
"llm-after-price-change",
|
|
created_at=after_change.replace(tzinfo=None),
|
|
)
|
|
with SessionLocal() as db:
|
|
existing = db.get(AppConfig, "llm_token_price")
|
|
if existing is not None:
|
|
db.delete(existing)
|
|
db.flush()
|
|
db.add(
|
|
AppConfig(
|
|
key="llm_token_price",
|
|
value={"default": {"input_per_1m": 1, "output_per_1m": 1}},
|
|
updated_at=price_changed_at,
|
|
)
|
|
)
|
|
db.commit()
|
|
|
|
seen: list[str] = []
|
|
|
|
def backfill(record_id: int, trace_id: str, **kwargs) -> bool:
|
|
seen.append(trace_id)
|
|
return True
|
|
|
|
monkeypatch.setattr(
|
|
comparison_llm_backfill,
|
|
"backfill_comparison_llm_cost",
|
|
backfill,
|
|
)
|
|
try:
|
|
result = comparison_llm_backfill.repair_missing_comparison_llm_costs(
|
|
limit=10_000,
|
|
lookback_days=1,
|
|
)
|
|
assert "llm-after-price-change" in seen
|
|
assert "llm-before-price-change" not in seen
|
|
assert result["repaired"] == len(seen)
|
|
assert result["unresolved"] == 0
|
|
finally:
|
|
_delete(before_id)
|
|
_delete(after_id)
|
|
with SessionLocal() as db:
|
|
config = db.get(AppConfig, "llm_token_price")
|
|
if config is not None:
|
|
db.delete(config)
|
|
db.commit()
|