Skip to content

Commit 10e8b8b

Browse files
committed
Commit 1: 被动评分核心
feat: add passive benchmark auto-tagging to daily analysis pipeline What: - Add BenchmarkTagger class to src/services/model_benchmark.py: get_current_model_id() reads the active model from Config.litellm_model or AgentResult.model; enrich_context_snapshot() injects {"model_id", "benchmark":true, "benchmark_meta":{latency_ms, total_tokens, prompt_tokens, completion_tokens, estimated_cost_usd}} into any existing context_snapshot dict - Inject auto-tagging into StockAnalysisPipeline in both code paths: Non-agent path (analyzer.analyze): capture latency via time.perf_counter(), tag context_snapshot before db.save_analysis_history() Agent path (executor.run): capture latency + full token usage from AgentResult.stats, tag initial_context before save - Add Config fields benchmark_auto_tag (default true) and benchmark_auto_report (default true), parsed from BENCHMARK_AUTO_TAG and BENCHMARK_AUTO_REPORT env vars - Add auto benchmark report generation to main.py: after backtest completes, if benchmark_auto_report is enabled, automatically call ModelBenchmarkService.generate_report() and log the accuracy leaderboard and performance leaderboard, saving to reports/benchmark_*.txt - Sync .env.example with the two new config entries Why: - Users should not need to explicitly invoke the benchmark CLI (python -m src.services.model_benchmark) to compare model accuracy. Instead, every normal daily analysis run automatically tags the context_snapshot with model_id and performance metadata, so that after the backtest evaluation window passes, a cross-model comparison report is generated without any extra user action - The system supports multiple models; users may switch models between runs. Passive tagging accumulates labeled data over time, enabling statistically meaningful comparisons without dedicated benchmark runs - Two-path coverage (agent and non-agent) ensures no analysis is missed Verification: - Syntax: all 4 .py files pass py_compile - flake8: 0 critical errors (E9,F63,F7,F82) - BenchmarkTagger.get_current_model_id(): correctly resolves agent vs non-agent model (tested with MockConfig) - BenchmarkTagger.enrich_context_snapshot(): Non-agent path: injects model_id + benchmark flag, tokens=0 ✓ Agent path: injects full tokens (8500 total, 5100 prompt, 3400 completion) + cost ($0.0663 for claude-sonnet-4-6 at 5k+3.4k tokens) ✓ Idempotency: already-tagged snapshots are not overwritten ✓ - Config.benchmark_auto_tag and benchmark_auto_report default to true ✓ - AST verification: BenchmarkTagger imported in both pipeline code paths ✓ - AST verification: ModelBenchmarkService + format_benchmark_report imported in main.py auto-report block ✓ - .env.example: both BENCHMARK_AUTO_TAG and BENCHMARK_AUTO_REPORT added ✓ Not verified (requires live environment): - End-to-end main.py run with real API calls and database - Auto-report query joining backtest_results with benchmark-tagged analysis_history rows - Non-agent path real-world latency measurement accuracy Risk: - Non-agent path tokens are always 0 (traditional analyzer.analyze() does not expose token usage). Performance leaderboard will be incomplete for non-agent runs, but accuracy scoring is unaffected (it only depends on direction_correct from backtest, not on benchmark_meta) - context_snapshot grows ~200 bytes per record (benchmark_meta JSON). Negligible at scale (200KB per 1000 records) - Lazy import of BenchmarkTagger inside the save block avoids pulling model_benchmark dependencies at module load time; a failed import is caught and logged at debug level without blocking the analysis save - Auto-report text is verbose (~50 lines). Users who find this noisy can set BENCHMARK_AUTO_REPORT=false Rollback: - Set BENCHMARK_AUTO_TAG=false and BENCHMARK_AUTO_REPORT=false in .env to disable all passive benchmarking without code changes - Revert the 6 changed files to completely remove the feature - Existing benchmark fields in context_snapshot are inert: no query depends on them, they are only consumed by ModelBenchmarkService .generate_report() which checks for "benchmark":true --- Commit 2: 设计文档更新(被动评分章节) docs: add passive benchmarking section to design-model-benchmark.html What: - Add "Phase 0: Passive Benchmarking (Zero-Click Background Collection)" section explaining the auto-tag → passive accumulation → auto-report flow - Document the two new .env config entries (BENCHMARK_AUTO_TAG, BENCHMARK_AUTO_REPORT) - Explain agent vs non-agent path coverage differences - Clarify relationship between explicit benchmark CLI and passive tagging - Add BenchmarkTagger to the Dependencies list in meta-box Why: - AGENTS.md rule: new features must include updated design documentation - Users and maintainers need to understand that benchmarking now happens automatically without explicit CLI invocation Verification: - All 4 semantic markers present: Passive Benchmarking, BenchmarkTagger, BENCHMARK_AUTO_TAG, BENCHMARK_AUTO_REPORT, Zero-Click Background Collection - HTML structural integrity maintained Risk: None (docs only) Rollback: Revert design doc to previous version
1 parent 893cdf8 commit 10e8b8b

6 files changed

Lines changed: 265 additions & 1 deletion

File tree

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,13 @@ BACKTEST_ENGINE_VERSION=v1
569569
# 中性区间阈值(%),例如 2 表示 -2%~+2% 视为震荡
570570
BACKTEST_NEUTRAL_BAND_PCT=2.0
571571

572+
# === 模型基准测试自动标注 ===
573+
# 是否在每次分析时自动标注 model_id + 性能数据到 context_snapshot(true/false)
574+
# 开启后,回测评估可自动产出跨模型对比报告,无需手动运行 benchmark CLI
575+
BENCHMARK_AUTO_TAG=true
576+
# 是否在回测完成后自动生成跨模型对比报告(true/false)
577+
BENCHMARK_AUTO_REPORT=true
578+
572579
# === 定时任务配置 ===
573580
# 是否启用定时任务(true/false)
574581
SCHEDULE_ENABLED=false

docs/design-model-benchmark.html

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ <h1>Design: Model Benchmark</h1>
163163
<dt>Status</dt><dd>Implemented</dd>
164164
<dt>Module</dt><dd><code>src/services/model_benchmark.py</code></dd>
165165
<dt>Date</dt><dd>2026-05-11</dd>
166-
<dt>Dependencies</dt><dd><code>BacktestEngine</code>, <code>BacktestService</code>, <code>AnalysisHistory</code>, <code>AgentFactory</code></dd>
166+
<dt>Dependencies</dt><dd><code>BacktestEngine</code>, <code>BacktestService</code>, <code>AnalysisHistory</code>, <code>AgentFactory</code>, <code>BenchmarkTagger</code></dd>
167167
<dt>CLI Entry</dt><dd><code>python -m src.services.model_benchmark [--analyze|--evaluate|--report|--full] [--parallel] [--debug] [--max-parallel N]</code></dd>
168168
</dl>
169169

@@ -366,6 +366,39 @@ <h3>All-in-One</h3>
366366
<pre><code>python -m src.services.model_benchmark --full --stocks AAPL,TSLA</code></pre>
367367
<p>Runs all three phases sequentially. Useful for immediate testing when eval window is 0 (same-day analysis + evaluation).</p>
368368

369+
<h3>Phase 0: Passive Benchmarking (Zero-Click Background Collection)</h3>
370+
371+
<div class="callout callout-tip">
372+
<div class="callout-title">核心设计理念:零操作、全自动</div>
373+
<p>用户<strong>无需</strong>显式调用 benchmark CLI。每次通过 <code>main.py</code> 正常分析股票时,系统自动在 <code>context_snapshot</code> 中标注当前使用的模型 ID 和性能数据(延迟 / Token / 成本估算),回测评估后自动产出跨模型对比报告。</p>
374+
</div>
375+
376+
<p><strong>工作原理:</strong></p>
377+
<ol>
378+
<li><strong>自动标注(BenchmarkTagger)</strong>:在 <code>StockAnalysisPipeline</code> 的每次分析执行前后,自动捕获 LLM 调用耗时(<code>time.perf_counter()</code>)、Token 用量(从 <code>AgentResult.stats</code> 提取)、模型 ID(从 <code>Config.litellm_model</code><code>AgentResult.model</code> 读取),写入 <code>context_snapshot.benchmark_meta</code></li>
379+
<li><strong>被动积累</strong>:用户在日常使用中切换不同模型(今天用 Claude、明天用 Gemini),每条分析记录自动标注对应模型。日积月累,自然形成多模型对比数据集</li>
380+
<li><strong>自动报告</strong>:回测完成后,若 <code>BENCHMARK_AUTO_REPORT=true</code>,自动调用 <code>ModelBenchmarkService.generate_report()</code>,JOIN 查询所有带 <code>"benchmark": true</code> 标记的回测结果,产出模型排名报告并保存到 <code>reports/benchmark_*.txt</code></li>
381+
</ol>
382+
383+
<p><strong>配置项(.env):</strong></p>
384+
<pre><code># 是否自动为每次分析标注模型+性能数据(默认 true)
385+
BENCHMARK_AUTO_TAG=true
386+
# 是否在回测完成后自动生成跨模型对比报告(默认 true)
387+
BENCHMARK_AUTO_REPORT=true</code></pre>
388+
389+
<p><strong>Agent 与非 Agent 路径均覆盖:</strong></p>
390+
<ul>
391+
<li><strong>非 Agent 路径</strong><code>analyzer.analyze()</code>):标注 LLM 耗时;Token 数据通常不可用(传统路径不暴露 usage),<code>benchmark_meta.total_tokens=0</code></li>
392+
<li><strong>Agent 路径</strong><code>executor.run()</code>):标注 LLM 耗时 + 完整 Token 数据 + 成本估算,性能对比数据最完整</li>
393+
</ul>
394+
395+
<p><strong>与显式 benchmark CLI 的关系:</strong></p>
396+
<ul>
397+
<li>显式 benchmark CLI(<code>python -m src.services.model_benchmark --analyze ...</code>)仍然保留,用于<strong>主动</strong>对比多个模型(在单次运行中同时调用多个模型分析同一只股票)</li>
398+
<li>被动标注(<code>BenchmarkTagger</code>)是<strong>互补</strong>机制,在日常使用中零成本收集数据,无需额外操作</li>
399+
<li>两种路径产出的 <code>context_snapshot</code> 格式完全一致,共用同一套 <code>generate_report()</code> 查询逻辑</li>
400+
</ul>
401+
369402
<!-- ═══════════════════════════════════════════════════════════════ -->
370403
<h2 id="data-model">5. Data Model &amp; Storage</h2>
371404

main.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,34 @@ def run_full_analysis(
742742
f"自动回测完成: processed={stats.get('processed')} saved={stats.get('saved')} "
743743
f"completed={stats.get('completed')} insufficient={stats.get('insufficient')} errors={stats.get('errors')}"
744744
)
745+
746+
# === Auto benchmark report: 回测后自动产出跨模型对比 ===
747+
if getattr(config, 'benchmark_auto_report', True):
748+
try:
749+
from src.services.model_benchmark import ModelBenchmarkService, format_benchmark_report
750+
751+
logger.info("正在生成自动模型基准测试报告...")
752+
bench_svc = ModelBenchmarkService()
753+
bench_report = bench_svc.generate_report(
754+
eval_window_days=getattr(config, 'backtest_eval_window_days', 10),
755+
)
756+
if bench_report.models:
757+
bench_text = format_benchmark_report(bench_report)
758+
logger.info("自动模型基准测试报告:\n%s", bench_text)
759+
# 可选:保存到 reports/ 目录
760+
try:
761+
from pathlib import Path
762+
reports_dir = Path("reports")
763+
reports_dir.mkdir(exist_ok=True)
764+
report_path = reports_dir / f"benchmark_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
765+
report_path.write_text(bench_text, encoding="utf-8")
766+
logger.info("模型基准测试报告已保存: %s", report_path)
767+
except Exception as _save_exc:
768+
logger.debug("保存 benchmark 报告失败: %s", _save_exc)
769+
else:
770+
logger.info("暂无 benchmark 标注数据,跳过自动模型基准测试报告")
771+
except Exception as _benchmark_exc:
772+
logger.warning("自动模型基准测试报告生成失败(已忽略): %s", _benchmark_exc)
745773
except Exception as e:
746774
logger.warning(f"自动回测失败(已忽略): {e}")
747775

src/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,12 @@ class Config:
833833
backtest_min_age_days: int = 14
834834
backtest_engine_version: str = "v1"
835835
backtest_neutral_band_pct: float = 2.0
836+
837+
# === 模型基准测试自动标注 ===
838+
# 开启后,每次分析自动在 context_snapshot 中标注 model_id + 性能数据(延迟/Token/成本),
839+
# 使得回测评估后可以自动产出跨模型对比报告,无需手动运行 benchmark CLI。
840+
benchmark_auto_tag: bool = True
841+
benchmark_auto_report: bool = True
836842

837843
# === 日志配置 ===
838844
log_dir: str = "./logs" # 日志文件目录
@@ -1597,6 +1603,8 @@ def _load_from_env(cls) -> 'Config':
15971603
backtest_eval_window_days=parse_env_int(os.getenv('BACKTEST_EVAL_WINDOW_DAYS'), 10, field_name='BACKTEST_EVAL_WINDOW_DAYS', minimum=1),
15981604
backtest_min_age_days=parse_env_int(os.getenv('BACKTEST_MIN_AGE_DAYS'), 14, field_name='BACKTEST_MIN_AGE_DAYS', minimum=1),
15991605
backtest_engine_version=os.getenv('BACKTEST_ENGINE_VERSION', 'v1'),
1606+
benchmark_auto_tag=os.getenv('BENCHMARK_AUTO_TAG', 'true').lower() == 'true',
1607+
benchmark_auto_report=os.getenv('BENCHMARK_AUTO_REPORT', 'true').lower() == 'true',
16001608
backtest_neutral_band_pct=parse_env_float(
16011609
os.getenv('BACKTEST_NEUTRAL_BAND_PCT'),
16021610
2.0,

src/core/pipeline.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,12 +491,14 @@ def _on_llm_stream(chars_received: int) -> None:
491491
)
492492

493493
self._emit_progress(64, f"{stock_name}:正在请求 LLM 生成报告")
494+
_benchmark_t0 = time.perf_counter()
494495
result = self.analyzer.analyze(
495496
enhanced_context,
496497
news_context=news_context,
497498
progress_callback=self._emit_progress,
498499
stream_progress_callback=_on_llm_stream,
499500
)
501+
_benchmark_latency_ms = (time.perf_counter() - _benchmark_t0) * 1000
500502

501503
# Step 7.5a: 技术评分兜底 — LLM 未输出评分时使用 trend_result.signal_score
502504
if result and result.sentiment_score == 50 and trend_result is not None:
@@ -543,6 +545,17 @@ def _on_llm_stream(chars_received: int) -> None:
543545
realtime_quote=realtime_quote,
544546
chip_data=chip_data
545547
)
548+
# === Benchmark auto-tag: 每次分析自动标注模型+性能数据 ===
549+
if getattr(self.config, 'benchmark_auto_tag', True):
550+
try:
551+
from src.services.model_benchmark import BenchmarkTagger
552+
context_snapshot = BenchmarkTagger.enrich_context_snapshot(
553+
existing_snapshot=context_snapshot,
554+
model_id=BenchmarkTagger.get_current_model_id(self.config),
555+
latency_ms=_benchmark_latency_ms,
556+
)
557+
except Exception as _benchmark_exc:
558+
logger.debug("Benchmark auto-tag skipped: %s", _benchmark_exc)
546559
self.db.save_analysis_history(
547560
result=result,
548561
query_id=query_id,
@@ -897,7 +910,9 @@ def _analyze_with_agent(
897910
message = f"Analyze stock {code} ({stock_name}) and return the full decision dashboard JSON in English."
898911
else:
899912
message = f"请分析股票 {code} ({stock_name}),并生成决策仪表盘报告。"
913+
_benchmark_t0 = time.perf_counter()
900914
agent_result = executor.run(message, context=initial_context)
915+
_benchmark_latency_ms = (time.perf_counter() - _benchmark_t0) * 1000
901916

902917
# 转换为 AnalysisResult
903918
result = self._agent_result_to_analysis_result(
@@ -963,6 +978,22 @@ def _analyze_with_agent(
963978
if result and result.success:
964979
try:
965980
initial_context["stock_name"] = resolved_stock_name
981+
# === Benchmark auto-tag: Agent 路径自动标注模型+性能数据 ===
982+
if getattr(self.config, 'benchmark_auto_tag', True):
983+
try:
984+
from src.services.model_benchmark import BenchmarkTagger
985+
model_id = (
986+
agent_result.model
987+
or BenchmarkTagger.get_current_model_id(self.config)
988+
)
989+
initial_context = BenchmarkTagger.enrich_context_snapshot(
990+
existing_snapshot=initial_context,
991+
model_id=model_id,
992+
latency_ms=_benchmark_latency_ms,
993+
agent_result=agent_result,
994+
)
995+
except Exception as _benchmark_exc:
996+
logger.debug("Benchmark auto-tag skipped (agent): %s", _benchmark_exc)
966997
self.db.save_analysis_history(
967998
result=result,
968999
query_id=query_id,

src/services/model_benchmark.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,163 @@ def _extract_model_info(snapshot_json: Optional[str]) -> Tuple[Optional[str], Op
10421042
return None, None
10431043

10441044

1045+
# ---------------------------------------------------------------------------
1046+
# BenchmarkTagger — passive / auto-tag every analysis for background scoring
1047+
# ---------------------------------------------------------------------------
1048+
1049+
class BenchmarkTagger:
1050+
"""
1051+
被动评分标注器:在正常使用 main.py 运行分析时,自动为每一条分析结果标注
1052+
model_id + 性能元数据(延迟 / Token / 成本估算),使得后续回测评估后可以
1053+
自动产出跨模型对比报告,无需手动运行 benchmark CLI。
1054+
1055+
=== 使用方式(由 pipeline 自动调用,用户无需操作) ===
1056+
1057+
from src.services.model_benchmark import BenchmarkTagger
1058+
1059+
# 在每次分析执行前后:
1060+
t0 = time.perf_counter()
1061+
result = run_analysis(...)
1062+
latency_ms = (time.perf_counter() - t0) * 1000
1063+
1064+
# 为 context_snapshot 注入 benchmark 标签:
1065+
enriched = BenchmarkTagger.enrich_context_snapshot(
1066+
existing_snapshot=original_context_snapshot,
1067+
model_id=config.litellm_model,
1068+
latency_ms=latency_ms,
1069+
agent_result=agent_result, # Agent 路径传入,非 Agent 路径为 None
1070+
)
1071+
1072+
db.save_analysis_history(..., context_snapshot=enriched, ...)
1073+
1074+
=== 存储格式(context_snapshot 内) ===
1075+
1076+
{
1077+
...原有字段...,
1078+
"model_id": "anthropic/claude-sonnet-4-6",
1079+
"benchmark": true,
1080+
"benchmark_meta": {
1081+
"latency_ms": 3421.5,
1082+
"total_tokens": 8500,
1083+
"prompt_tokens": 5100,
1084+
"completion_tokens": 3400,
1085+
"estimated_cost_usd": 0.0663
1086+
}
1087+
}
1088+
1089+
只有含有 "benchmark": true 标记的分析记录,才会被 generate_report()
1090+
JOIN 查询纳入跨模型对比。
1091+
"""
1092+
1093+
@staticmethod
1094+
def get_current_model_id(config) -> str:
1095+
"""
1096+
获取当前正在使用的模型 ID。
1097+
1098+
优先级:
1099+
1. Agent 模式下的 AGENT_LITELLM_MODEL(显式配置)
1100+
2. Agent 模式下的 LITELLM_MODEL(fallback 继承)
1101+
3. 非 Agent 模式下的 LITELLM_MODEL
1102+
4. 环境变量 LITELLM_MODEL 兜底
1103+
"""
1104+
# Check agent-specific model first
1105+
if getattr(config, 'agent_mode', False) or getattr(config, 'agent_skills', None):
1106+
agent_model = getattr(config, 'agent_litellm_model', '') or ''
1107+
if agent_model:
1108+
return agent_model
1109+
# Fall back to primary litellm model
1110+
primary = getattr(config, 'litellm_model', '') or ''
1111+
if primary:
1112+
return primary
1113+
# Last resort: env var
1114+
return os.environ.get('LITELLM_MODEL', 'unknown')
1115+
1116+
@staticmethod
1117+
def enrich_context_snapshot(
1118+
existing_snapshot: Optional[Dict[str, Any]],
1119+
model_id: str,
1120+
latency_ms: float,
1121+
agent_result: Any = None,
1122+
) -> Dict[str, Any]:
1123+
"""
1124+
为 context_snapshot 注入 benchmark 标注字段。
1125+
1126+
Args:
1127+
existing_snapshot: 原始 context_snapshot dict(可能为 None)
1128+
model_id: 当前使用的模型 ID(如 "anthropic/claude-sonnet-4-6")
1129+
latency_ms: 分析耗时(毫秒)
1130+
agent_result: AgentResult 对象(Agent 路径传入,用于提取 Token 用量)
1131+
1132+
Returns:
1133+
注入 benchmark 字段后的新 dict(不修改原对象)
1134+
"""
1135+
snapshot = dict(existing_snapshot) if existing_snapshot else {}
1136+
1137+
# 如果已有 benchmark 标记(例如 benchmark CLI 手动调用),保留不覆盖
1138+
# 但兼容日常路径未打标的情况
1139+
if snapshot.get("benchmark") and snapshot.get("model_id"):
1140+
# Already tagged, update perf meta only if missing
1141+
if not snapshot.get("benchmark_meta"):
1142+
snapshot["benchmark_meta"] = BenchmarkTagger._build_benchmark_meta(
1143+
latency_ms, agent_result
1144+
)
1145+
return snapshot
1146+
1147+
# Extract token usage from AgentResult (agent path)
1148+
prompt_tokens = 0
1149+
completion_tokens = 0
1150+
total_tokens = 0
1151+
if agent_result is not None:
1152+
total_tokens = getattr(agent_result, 'total_tokens', 0) or 0
1153+
stats = getattr(agent_result, 'stats', None)
1154+
if stats:
1155+
prompt_tokens = getattr(stats, 'total_prompt_tokens', 0) or 0
1156+
completion_tokens = getattr(stats, 'total_completion_tokens', 0) or 0
1157+
# 如果没有细分数据但有总量,估算 6:4 拆分
1158+
if not prompt_tokens and not completion_tokens and total_tokens:
1159+
prompt_tokens = int(total_tokens * 0.6)
1160+
completion_tokens = total_tokens - prompt_tokens
1161+
1162+
cost = estimate_cost(model_id, prompt_tokens, completion_tokens)
1163+
1164+
snapshot["model_id"] = model_id
1165+
snapshot["benchmark"] = True
1166+
snapshot["benchmark_meta"] = {
1167+
"latency_ms": round(latency_ms, 1),
1168+
"total_tokens": total_tokens,
1169+
"prompt_tokens": prompt_tokens,
1170+
"completion_tokens": completion_tokens,
1171+
"estimated_cost_usd": cost,
1172+
}
1173+
return snapshot
1174+
1175+
@staticmethod
1176+
def _build_benchmark_meta(
1177+
latency_ms: float,
1178+
agent_result: Any = None,
1179+
) -> Dict[str, Any]:
1180+
"""构建 benchmark_meta 子字典(内部复用)。"""
1181+
total_tokens = 0
1182+
prompt_tokens = 0
1183+
completion_tokens = 0
1184+
if agent_result is not None:
1185+
total_tokens = getattr(agent_result, 'total_tokens', 0) or 0
1186+
stats = getattr(agent_result, 'stats', None)
1187+
if stats:
1188+
prompt_tokens = getattr(stats, 'total_prompt_tokens', 0) or 0
1189+
completion_tokens = getattr(stats, 'total_completion_tokens', 0) or 0
1190+
if not prompt_tokens and not completion_tokens and total_tokens:
1191+
prompt_tokens = int(total_tokens * 0.6)
1192+
completion_tokens = total_tokens - prompt_tokens
1193+
return {
1194+
"latency_ms": round(latency_ms, 1),
1195+
"total_tokens": total_tokens,
1196+
"prompt_tokens": prompt_tokens,
1197+
"completion_tokens": completion_tokens,
1198+
"estimated_cost_usd": 0.0, # model_id unknown at this point
1199+
}
1200+
1201+
10451202
# ---------------------------------------------------------------------------
10461203
# Report formatting (updated with performance section)
10471204
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)