Skip to content

fix(analytics): usage_source must reflect unmeasured prompt tokens, not just output source - #1071

Closed
seonghobae wants to merge 6 commits into
mainfrom
fix/spend-analytics-usage-source-prompt-unavailable
Closed

fix(analytics): usage_source must reflect unmeasured prompt tokens, not just output source#1071
seonghobae wants to merge 6 commits into
mainfrom
fix/spend-analytics-usage-source-prompt-unavailable

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Bug / realistic RED

Protected main@2e414d15ba58f28597751b625a8a2f00fc9fadcf deterministically misclassified a model row as pure tokenizer when output tokens were exactly counted but prompt-token usage was unavailable. tests/test_spend_analytics.py::test_exact_output_without_prompt_usage_is_explicitly_unavailable exposed AssertionError: assert 'tokenizer' == 'mixed' on the protected source.

The root cause was row classification based only on per-model output-token source. The existing report-wide prompt availability flag cannot safely classify individual model rows in a multi-model run.

Causal repair

The production change adds prompt_reported_steps to each model bucket for both ordinary trace steps and judge accounting. Pure reported or tokenizer now requires complete prompt evidence for that same model; otherwise an output-known row is mixed. The four-value public vocabulary remains reported | tokenizer | mixed | unavailable; report-level prompt totals and measurement-status rules are not widened.

README and CHANGELOG are aligned with that contract.

Cross-model regression

Exact descendant 2bfb603af517e40381d0914e30221eefb62a006a adds test_usage_source_is_scoped_per_model_when_prompt_evidence_differs. One report contains:

  • known-model: provider-reported prompt and completion tokens → row must remain reported;
  • missing-model: provider-reported completion tokens with prompt usage absent → row must be mixed;
  • report-wide prompt total/measurement remains unavailable because at least one incurred prompt count is unknown.

This pins the reason for per-model tracking rather than relying only on the original single-model regression.

Exact identity

  • protected base: main@2e414d15ba58f28597751b625a8a2f00fc9fadcf
  • exact head: ef126ae009a5874eadfd1534fc453d7e8af4e279 (recovered via git push origin <sha>:refs/heads/... after a local worktree-cleanup mishap deleted the branch ref before push — the commit object itself was never lost)
  • effective files: CHANGELOG.md, README.md, contextual_orchestrator/orchestrator.py, tests/test_spend_analytics.py
  • lifecycle: Draft / causal source+test repair present / exact-head hosted evidence non-terminal

This same fix is also cherry-picked into contextual-orchestrator#1070 (commits e4cda9cc/bcecfa16 there), since every currently-open PR merging against main hits this same pre-existing failure until this lands — it no-ops once this PR merges.

Evidence boundary

Keep Draft until one unchanged exact head obtains all applicable test/docstring/security/SAST/CodeQL evidence terminal-success, live review requirements are satisfied, and zero valid findings remain. The separate NVIDIA hosted-cost evidence-expiry defect (tracked in #1070) is not masked or converted into test-relative production evidence. No self-approval, bypass, force-push, destructive rebase, no-op retrigger, or predecessor-evidence transfer.


Generated by Claude Code

spend_analytics()["by_model"][].usage_source was classified purely from
each model's output-token source (reported/tokenizer/mixed/unavailable
across that model's steps this run), while completely ignoring whether
prompt tokens were ever available for that model. A model with exactly
one step, exact-tokenizer output, and zero reported prompt tokens was
labeled pure "tokenizer" — dishonestly implying full count-based
knowledge of that model's usage when the prompt side was entirely
unmeasured.

Add per-model prompt-token tracking (`bucket["prompt_reported_steps"]`)
alongside the existing per-model output-source counters, and require
full per-model prompt availability before classifying a row as
"reported" or "tokenizer". A model whose output is fully known but
whose prompt tokens are partially or entirely unmeasured now correctly
falls through to "mixed" — the same honest-composite label already used
for genuinely mixed output sources. The four-way usage_source vocabulary
(reported/tokenizer/mixed/unavailable) is unchanged, matching the
documented contract in README.md.

The existing report-wide `prompt_available` flag (used for
`totals.prompt_tokens`/`measurement_status`) is untouched: it already
forces "unavailable" whenever any step in the run lacks a valid reported
prompt count, so this fix only tightens per-model row classification,
not the report-level status.

Validated: tests/test_spend_analytics.py now passes (previously failed
with `'tokenizer' == 'mixed'`); full suite
`PYTHONPATH=. python -m pytest tests -q --ignore=tests/test_psychometric_routing.py`
now shows 3383 passed, 5 failed, 2 skipped — the 5 failures are
pre-existing, unrelated `test_nim_benchmark.py` date-evidence-expiry
failures (confirmed identical via `git stash` against unmodified
origin/main), not caused by or related to this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 0f2a92f5-03df-4b01-9ddb-52fbc809a00f

📥 Commits

Reviewing files that changed from the base of the PR and between 2e414d1 and e645cff.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • README.md
  • contextual_orchestrator/orchestrator.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

모델별 프롬프트 토큰 보고 단계 수를 집계합니다. 출력 토큰이 완전히 알려져도 프롬프트 토큰이 일부 또는 전부 누락되면 usage_source"mixed"로 분류합니다. README와 CHANGELOG를 갱신합니다.

Changes

사용량 소스 정확성

Layer / File(s) Summary
프롬프트 토큰 측정 상태 집계
contextual_orchestrator/orchestrator.py
두 사용량 집계 경로에서 유효한 프롬프트 토큰 보고 여부를 판별하고 prompt_reported_steps를 증가시킵니다.
모델별 분류 및 문서 갱신
contextual_orchestrator/orchestrator.py, README.md, CHANGELOG.md
모든 단계의 프롬프트 토큰이 측정된 경우에만 "reported" 또는 "tokenizer"를 사용합니다. 그렇지 않으면 출력 토큰이 완전히 알려져도 "mixed"로 분류합니다. 관련 규칙과 변경 내역을 문서화합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to e645c

Spend analytics now marks models with incomplete prompt-token measurements as mixed while preserving reported classification for complete usage, improving accuracy without an identified remaining merge risk.

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 usage_source가 출력 토큰뿐 아니라 측정되지 않은 프롬프트 토큰도 반영하도록 수정된 핵심 변경을 정확하고 간결하게 설명합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/spend-analytics-usage-source-prompt-unavailable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Five tests in tests/test_nim_benchmark.py exercise unrelated run_mode="live"
behavior (missing credential, transport wiring, a malformed-catalog contract
failure, CLI exit codes) that incidentally passes through
_require_current_actual_cost_evidence(). That check fails closed once
nim_benchmark.ACTUAL_COST_EVIDENCE["valid_until_date"] — a human-reviewed
date for NVIDIA's published hosted-endpoint terms — is in the past. Today
(2026-09-05) is now one day past the literal "2026-09-04" recorded there,
so all five started failing for a reason unrelated to what each actually
asserts: a classic ticking-time-bomb hard-coded date, not a code regression.

The production literal is deliberate and stays as-is: it is a real record of
when someone last verified NVIDIA's terms, and live runs are meant to fail
closed until that review happens again. tests/test_nim_benchmark_release_acceptance.py
already covers that review-cadence invariant directly via explicit `today`
injection into _require_current_actual_cost_evidence, and is unaffected.

Added an autouse fixture in test_nim_benchmark.py that monkeypatches
ACTUAL_COST_EVIDENCE's reviewed_at_date/valid_until_date to bracket real
"now" for the duration of this file's tests only, so they stay green
regardless of wall-clock date while still exercising the real evidence-gated
code path.

Verified: `python -m pytest tests/test_nim_benchmark.py -v` and
`python tests/test_nim_benchmark.py` both 98 passed (previously 5 failed);
tests/test_nim_benchmark_release_acceptance.py unaffected (28 passed);
interrogate scoped to contextual_orchestrator/ stays 100%; git diff --check
clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
The prior fix used an autouse fixture across all 98 tests in
tests/test_nim_benchmark.py, silently giving every test synthetic
current cost evidence and deriving dates from date.today() — too
broad for a security/commercial fail-closed gate, since unrelated
tests (present and future) would stop observing the literal
production evidence dict.

Make the fixture opt-in (current_actual_cost_evidence, no autouse)
and request it explicitly only from the five tests that need to get
past the evidence gate to reach their own live-path assertion.
Production ACTUAL_COST_EVIDENCE is unchanged. Add a regression test
proving a test that does not request the fixture still observes the
literal production evidence and fails closed at the gate (verified
RED against the reverted autouse design, GREEN against this fix).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
…ce tests

test_live_run_rejects_unreviewed_pricing_before_egress and
test_live_run_rejects_incomplete_or_expired_pricing_before_egress now request
the current_actual_cost_evidence fixture so they exercise
validate_live_pricing_scenario's own fail-closed branches (lines 1708/1718)
instead of incidentally matching _require_current_actual_cost_evidence's
"expired" error once ACTUAL_COST_EVIDENCE's literal valid_until_date lapsed.
Restores 100% branch coverage on nim_benchmark.py's pricing-scenario gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX

Copy link
Copy Markdown
Contributor Author

Tests and package quality's "Run full test suite" step failed on this PR's prior head (2bfb603a) — 5 failures, all in tests/test_nim_benchmark.py, none touching this PR's own diff (orchestrator.py, tests/test_spend_analytics.py). Root cause: the same ticking-time-bomb hardcoded-expiry-date bug already root-caused and fixed on #1070ACTUAL_COST_EVIDENCE["valid_until_date"] lapsed on real wall-clock time, so _require_current_actual_cost_evidence() intercepts unrelated run_mode="live" tests before they reach their own assertions. This PR's base (main@2e414d1...) doesn't yet carry #1070's fix, so it hit the same pre-existing failure independently.

Ported #1070's three already-validated commits (9c349280, b4cc6c6a, 0eaca9f1) onto this branch via cherry-pick — clean apply except an auto-merged CHANGELOG.md (both PRs add adjacent ## [Unreleased] entries). Pushed as d930ecfc. This is a no-op mask once #1070 merges to main and this branch is later brought current, not new production behavior. Verified locally: tests/test_nim_benchmark.py, tests/test_nim_benchmark_release_acceptance.py, tests/test_spend_analytics.py all pass (132/132) on this exact head.


Generated by Claude Code

test_live_run_without_evidence_fixture_still_fails_closed_on_expired_evidence
asserted that a test not requesting the current_actual_cost_evidence fixture
observes real-world ACTUAL_COST_EVIDENCE as expired -- true only because
today happened to be past the literal valid_until_date at authoring time.
Once #1073 refreshed that production evidence (reviewed_at_date=2026-09-05,
valid_until_date=2026-10-05), this PR's own required "Tests and package
quality" check failed for real: the evidence gate no longer raised, so the
test fell through to a genuine network probe against NVIDIA's live API and
got HTTP 403 -- the exact same class of ticking-time-bomb bug this PR is
otherwise fixing, just introduced by this PR's own new regression test.

Fix: the test now deliberately monkeypatches ACTUAL_COST_EVIDENCE to a fixed
past window (2020) itself, proving the same opt-in-fixture-vs-file-wide
mechanism without depending on real wall-clock time relative to whatever the
production valid_until_date currently is. Verified locally against both the
unmodified branch and a merge of this branch with current origin/main
(post-#1073 evidence).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX

Copy link
Copy Markdown
Contributor Author

Tests and package quality failed again on this PR's prior head (d930ecfc) — same failure as #1070 just hit, since that head carries the same three ported commits: test_live_run_without_evidence_fixture_still_fails_closed_on_expired_evidence got a real BenchmarkAuthError: provider rejected the benchmark credential during probes (HTTP 403) instead of the expected "expired" BenchmarkContractError.

Root cause: that test (added by the ported #1070 commits) asserted the evidence gate fires "expired" by relying on real wall-clock time being past whatever ACTUAL_COST_EVIDENCE["valid_until_date"] currently is in production. #1073 merged to main in the meantime and refreshed that evidence (valid_until_date=2026-10-05), so PR checks — which run against the merge of this branch with current main — no longer see it as expired, and the test falls through to a real network probe. Fixed on #1070's own branch first (1d5e77f9: the test now deliberately monkeypatches ACTUAL_COST_EVIDENCE to a fixed 2020 window itself, independent of production state); ported that same commit here (ef126ae0).

Verified locally: tests/test_nim_benchmark.py + tests/test_nim_benchmark_release_acceptance.py + tests/test_spend_analytics.py all pass (132/132) both on this branch alone and merged fresh with current origin/main.


Generated by Claude Code

@seonghobae

Copy link
Copy Markdown
Contributor Author

Adjudication evidence (host 1 session, 2026-09-06 KST; full report with commands in #1080). Nothing here closes, flips, or retargets anything — the decision is the opener's.

This head is byte-identical to #1070 (same 385-line three-dot patch, 6 files +193/−10; cmp equal; merge-tree clean). The body treats #1070 as the separate tracker for the NIM expiry defect, but this head already carries that fixture (tests/test_nim_benchmark.py), and #1070 carries this PR's analytics change. Pick one survivor; the other becomes empty on merge. Correct the survivor's identity block (says 2bfb603a/4 files; head is ef126ae0/6 files) and title. Draft hold respected — nothing flipped or closed. Merge-order note: this fixture should land before #1074 and #1000, which rewrite the same test files.

Copy link
Copy Markdown
Contributor Author

Closing as duplicate of #1070 per the adjudication evidence above and my own independent confirmation: git diff origin/main...origin/fix/nim-benchmark-hardcoded-expiry-date and git diff origin/main...origin/fix/spend-analytics-usage-source-prompt-unavailable are byte-identical (6 files, +193/−10) as of this head (ef126ae0). Both branches ended up carrying both fixes — this PR's usage_source classification fix (commits e645cffb/2bfb603a) and #1070's NIM-benchmark evidence-currency fixture fix — after each was independently ported into the other to pass against current main.

#1070 is the designated survivor (already the "worked example" citation in ContextualWisdomLab/.github's AGENTS.md cross-session know-how section, so keeping it avoids stale cross-repo references) and its body/title are now corrected to describe the full 6-file diff accurately. No further work is needed on this branch; merging both would land the same diff twice, and merging either empties the other.

Per the merge-order note in the adjudication comment: #1070 should land before #1074 and #1000, which rewrite the same test files.


Generated by Claude Code


Generated by Claude Code

@seonghobae seonghobae closed this Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants