[v0.5.0] benchmark tokens — Claude token-count integration after client rewrap (live-phase-gated)#101
Merged
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #63.
Closes #89.
Summary
Adds the Claude token-count integration (issue #89, work package 6 of the
v0.5.0 benchmark): a guarded Anthropic count-tokens caller, envelope capture
after client-side rewrap with
approximation: truemarking, and the runnerclient_wrapped_tokens/raw_payload_tokens.benchmarks/adapters/guard.py: registers"anthropic": "ANTHROPIC_API_KEY"in
PROVIDER_API_KEY_ENVso the count-tokens caller reuses the existingBENCHMARK_LIVE_PROVIDERS_ENABLED+ per-provider-key guard (same guard as[v0.5.0] benchmark adapters — define OpenAI/Google model matrix #85), rather than inventing a second gate.
benchmarks/adapters/claude_tokens.py(new):LiveClaudeTokenCountercalls
require_live_environment("anthropic")on everycount()call,then POSTs to the Anthropic count-tokens endpoint via
urllib.requestonly -- no SDK, no new dependency (binding pre-flight amendment,
2026-07-08).
build_client_wrapped_envelopeimplements methodology"Token Measurement" steps 1-3.
FakeTokenCounteris the only counter anytest in this repo uses.
benchmarks/runner.py:_execute_cellfills each cell's token record viaa new
_build_token_recordhelper. Outside a maintainer-run live phase(every CI/unit-test run), it falls back to the same honest
Noneplaceholder as before. When the guard passes, it fills
client_wrapped_tokens/raw_payload_tokensunder the exportedMETHODOLOGY_TOKEN_LABELconstant.benchmarks/report.py: additive change --CellRecordgainstoken_approximation, and_basic_statsexcludesapproximation: truerecords from the headline-eligible client-wrapped-token median in both
REPORT.mdand the README-safe summary, while still surfacing theexcluded count (never silently dropped).
docs/benchmarks/model-matrix.yml: fixes a comment that claimed nothingunder
benchmarks/adapters/calls the count-tokens API -- this module nowdoes, strictly behind the guard.
D6b merged-test sanction (used, scoped exactly as granted)
Registering
"anthropic"inPROVIDER_API_KEY_ENVinvalidated the mergedtest asserting
"anthropic"is an unknown provider(
tests/benchmarks/test_adapters.py). Per the issue's 2026-07-08 pre-flightamendment, re-pointed that one assertion at
"not-a-registered-provider".Nothing else about that test (or any other merged test) changed.
Dependency rule (binding pre-flight amendment, honored)
No SDK, no optional extra, no dev/benchmark dependency group was added.
pyproject.tomlanduv.lockare absent from this diff -- proof below.(empty output: neither file appears in the diff)
Full diff stat:
Why this triggered supervisor review
Total diff is 915 insertions + 30 deletions = 945 changed lines, over
the 500-line threshold (precedent: #97 at 1510 insertions, #98 at 915 + 36 =
951). This PR is opened per the pipeline's supervisor-review protocol and
will not be merged by this worker. Labeled
supervisor-review.The size is mostly new, additive test coverage
(
test_claude_tokens.pyalone is 239 lines) plus one new, self-containedmodule (
claude_tokens.py, 277 lines) -- there is no single file here thatis large or hard to review in isolation.
No other supervisor-review trigger applies on its own merits: no new
third-party dependency, no
pyproject.tomlchange, no async introduced intoa previously-sync path. Runtime network access is the one other
pipeline-flagged category and is addressed below.
Runtime network access -- proof of structural incapability without the gate
LiveClaudeTokenCounter.count()callsrequire_live_environment("anthropic")as its first line, every call, regardless of caller. Tests prove this two
ways:
test_live_claude_token_counter_refuses_without_config_and_never_touches_networkmonkeypatches
urllib.request.urlopento raiseAssertionErrorif evercalled, then asserts
count()raisesLiveProviderDisabledError--i.e. the guard trips before any network call is even attempted.
test_token_record_is_the_honest_placeholder_when_live_guard_is_disabledproves the runner's default per-cell path (no env vars set, as in every
CI run) never reaches
LiveClaudeTokenCounterat all.CI never sets
BENCHMARK_LIVE_PROVIDERS_ENABLEDorANTHROPIC_API_KEY, sothis path is never exercised there. Every test that exercises the "guard
passes" branch substitutes a fake counter
(
benchmarks.adapters.claude_tokens.FakeTokenCounter) forLiveClaudeTokenCounter-- none call the real API."Why this approach" (unprescribed design calls)
its exact wrapped message envelope" must be marked
approximation: true.I read that as: cells produced by
MockOpenAIAdapter/MockGoogleAdapter(and their live-stub counterparts) are approximationsby construction, because those adapters never call a real
openai-python/google-genaiclient -- there is no real wrappedrequest body to recover, only a synthetic mock payload. Cells produced by
the no-MCP baseline or the offline
python_docs_mcp_adapterretrievaladapter hand this module real prompt/tool-call data directly (no
intermediary provider SDK claiming to have "wrapped" it), so this
module's own fixed, documented wrapping of that data is exact, not a
guess.
build_client_wrapped_envelopetakes an optionalprovider_mock_payloadparam to make this switch explicit and testablerather than inferring it from adapter identity implicitly. Note: the
runner's per-cell dispatch does not currently invoke the OpenAI/Google
adapters at all (out of scope per the [v0.5.0] benchmark adapter — python-docs-mcp-server tool runner (offline stdio) #86 composition decision), so in
today's runner this branch is exercised by direct unit tests
(
test_claude_tokens.py) rather than byrun_benchmark-- the mechanismis in place and tested for when that wiring lands.
count_cell_tokensraises afterthe guard passes (e.g. a live-phase network error),
_build_token_recordcatches it and records that cell's token status as
"failed"rather thanletting the exception propagate. A token-counting problem must never
crash the whole benchmark run or mask that cell's actual
answer/latency/scoring results, which is the same failure-isolation
philosophy
_execute_cellalready applies to adapter dispatch.token_labelfield. Added"token_label": METHODOLOGY_TOKEN_LABELto every token record (placeholder and counted) so the constant is
visible at the per-cell artifact level, not only where the report
generator renders it -- directly implements "labeled ... via the
exported constant" from the acceptance criteria.
_fmt_tokensexclusion surfacing. Rather than silently droppingapproximation: truecounts from the headline median,_fmt_tokensgained an
approximatedcount parameter so both report tables show e.g."100, 1 approximation:true excluded" -- consistent with the existing
placeholder-count precedent (
", N placeholder") already in thatfunction.
Acceptance criteria
wrapping used by the benchmark client (methodology steps 1-3); where
a client cannot expose its exact wrapped envelope, the record is
marked
approximation: trueand the report generator excludes itfrom headline-eligible tables.
require_live_environment()(same guard as [v0.5.0] benchmark adapters — define OpenAI/Google model matrix #85) plus an Anthropic key env var; CI and unit tests
never call it -- tests use a fake counter.
client_wrapped_tokensandraw_payload_tokens(raw payload counted separately as diagnosticdata), labeled
Claude Tokens (Normalized Payload)via the exportedconstant -- never confusable with provider billing tokens.
2026-07-08 pre-flight amendment, which tightened this from
"optional extra allowed" to "no dependency at all." No dependency was
added;
pyproject.toml/uv.lockare absent from the diff (provenabove).
tests/benchmarks/) cover fake-counterrecords, approximation marking, guard refusal without env config, and
serialization-latency capture alongside counts (decision 5.8).
Refs #63, neverCloses #63.Validation gate (full output)
uv lock --checkwas not run: no dependency/extras change is proposed inthis PR.
(The gate above was re-run in full, all 7 commands, after the CodeRabbit
fix in 417b330 -- output shown is from that latest run.)
CodeRabbit triage
tests/benchmarks/test_claude_tokens.pyL150-167: the two remaining guard-refusal tests (refuses_with_flag_but_no_key,refuses_with_key_but_no_flag) did not stuburlopen, unlike the no-config test at L135-148; a guard-order regression could have let them reach the network instead of failing deterministicallyurllib.request.urlopento raiseAssertionError("network reached")before exercising the refusal -- the same fail-closed pattern as the existing test. Change is confined to this PR's own new test file (no merged test touched, no assertion weakened). Replied on the review thread.No other CodeRabbit findings at time of writing.
Open / deferred items
None in scope. Spending API credits and any live benchmark run remain
maintainer-only, per the issue's scope boundaries.