feat(comparison): 比价记录累计 LLM token(入/出)并在 admin 展示

server 从 llm_calls[].usage 累加 prompt/completion_tokens 落库,admin 比价记录列表/详情据此展示 token 数与估算成本;配套迁移 comparison_token_cols。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 17:24:54 +08:00
parent e13f0f7658
commit a584e1f59f
4 changed files with 50 additions and 1 deletions
+37
View File
@@ -0,0 +1,37 @@
"""add input_tokens / output_tokens to comparison_record
比价记录增加本次 LLM 累计 token(server 从 llm_calls[].usage 累加),
供 admin 比价记录页展示 token 数与估算成本。
Revision ID: comparison_token_cols
Revises: feedback_review_fields
Create Date: 2026-06-23 00:40:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "comparison_token_cols"
down_revision: Union[str, Sequence[str], None] = "feedback_review_fields"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
"comparison_record",
sa.Column("input_tokens", sa.Integer(), nullable=True),
)
op.add_column(
"comparison_record",
sa.Column("output_tokens", sa.Integer(), nullable=True),
)
def downgrade() -> None:
op.drop_column("comparison_record", "output_tokens")
op.drop_column("comparison_record", "input_tokens")
+2
View File
@@ -33,6 +33,8 @@ class AdminComparisonListItem(BaseModel):
step_count: int | None = None
llm_call_count: int | None = None
retry_count: int | None = None
input_tokens: int | None = None # Σ usage.prompt_tokens(server 派生)
output_tokens: int | None = None # Σ usage.completion_tokens(server 派生)
device_model: str | None = None
rom_vendor: str | None = None
rom_name: str | None = None
+8 -1
View File
@@ -77,8 +77,15 @@ def _backfill_llm_calls(record_id: int, trace_id: str) -> None:
rec.llm_calls = calls
rec.llm_call_count = len(calls)
rec.retry_count = sum(1 for c in calls if c.get("error"))
# token 累加(usage 已被 pricebot llm_client 归一为 prompt/completion_tokens;
# error 的调用 usage 可能为 None,or {} 兜底)
rec.input_tokens = sum((c.get("usage") or {}).get("prompt_tokens") or 0 for c in calls)
rec.output_tokens = sum((c.get("usage") or {}).get("completion_tokens") or 0 for c in calls)
db.commit()
logger.info("backfill llm_calls trace=%s n=%d", trace_id, len(calls))
logger.info(
"backfill llm_calls trace=%s n=%d in_tok=%d out_tok=%d",
trace_id, len(calls), rec.input_tokens, rec.output_tokens,
)
except Exception as e: # noqa: BLE001 best-effort
logger.warning("backfill llm_calls failed trace=%s: %s", trace_id, e)
finally:
+3
View File
@@ -124,6 +124,9 @@ class ComparisonRecord(Base):
step_count: Mapped[int | None] = mapped_column(Integer, nullable=True) # 客户端 agent loop 总步数
llm_call_count: Mapped[int | None] = mapped_column(Integer, nullable=True) # 本次 LLM 调用次数(server 从 llm_calls 算)
retry_count: Mapped[int | None] = mapped_column(Integer, nullable=True) # LLM 失败重试次数(server 从 llm_calls error 算)
# 本次 LLM 累计 token(server 从 llm_calls[].usage 累加;旧记录/未采集为 None)
input_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True) # Σ usage.prompt_tokens
output_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True) # Σ usage.completion_tokens
# 每次 LLM 调用明细 [{scene,model,input_messages,output,usage,latency_ms,error}];
# server 收上报后按 trace_id 同机拉 pricebot 落库(见 compare_record 端点)。旧记录/未采集为 None。
llm_calls: Mapped[list | None] = mapped_column(_JSON, nullable=True)