Add pre-run token usage estimation - #341
Open
Aaron Aspinwall (AaronAspinwall123) wants to merge 6 commits into
Open
Add pre-run token usage estimation#341Aaron Aspinwall (AaronAspinwall123) wants to merge 6 commits into
Aaron Aspinwall (AaronAspinwall123) wants to merge 6 commits into
Conversation
Add stage-aware local token estimation with resume, force, tool, conversation, and judge accounting. Persist provider usage coverage and estimate accuracy while avoiding misleading comparisons for partial or incomplete runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64b5af06-d5d7-45c4-a340-3173531f4ec7
Aaron Aspinwall (AaronAspinwall123)
requested review from
Chang Liu (changliu2),
Jake Present (jakepresent) and
tangym
as code owners
September 1, 2026 23:39
Read token telemetry from run metrics and surface the pre-run estimate, likely range, provider-reported usage, stage breakdown, and comparison status on run pages and HTML exports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64b5af06-d5d7-45c4-a340-3173531f4ec7
Aaron Aspinwall (AaronAspinwall123)
requested a review
from sooyeonni (sooyeonni)
as a code owner
September 2, 2026 01:52
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64b5af06-d5d7-45c4-a340-3173531f4ec7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64b5af06-d5d7-45c4-a340-3173531f4ec7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64b5af06-d5d7-45c4-a340-3173531f4ec7
Chang Liu (changliu2)
requested changes
Sep 4, 2026
Chang Liu (changliu2)
left a comment
Collaborator
There was a problem hiding this comment.
Requesting changes on exact head 23d1080 for four token-accounting correctness issues:
- A no-op resume erases the completed run's existing token telemetry. The new
stage_usage or token_estimatecondition rewritesmetrics.jsoneven when no stage recorded new usage; a zero-pending-call estimate therefore replaces prior totals and stage data with zeros. Preserve the existing metrics when no new usage was recorded, or store invocation and cumulative totals separately. - Viewer estimation and execution use different results roots when
ARTIFACTS_ROOTis customized. The temporary estimate config receivesartifacts_root/results_dir, butwriteRunConfigFiles()serializes the original config and the spawned runner receives no equivalent overrides. The viewer reserves and monitors the custom root while ASSERT writes under<MEASUREMENTS_ROOT>\artifacts\results. Apply the same resolved roots to both estimation and submission. pipeline.inference.max_tool_callshas no effect on estimates. The simulated-tools example produces the identical inference estimate for limits1and10- 675 calls and 1,152,200 tokens - whileHostedSessioncontinues target/resolver calls up to the configured cap. Model the configured loop, accumulated context, simulator calls, and resulting judge transcript, or make the upper bound reflect that cap.- Opaque-target caveats disappear when tracked usage is zero.
_log_token_estimate()returns before printing notes at zero tokens, and viewer normalization drops a zero-valued estimate entirely, hiding the fact that callable/endpoint/sandbox target-internal usage was excluded. Preserve and render note-only estimates.
Count tool-call payloads separately from final answers, retain tool history, and share simulator prompts and transcript rendering with execution. Calibrate output assumptions without treating completion limits as expected lengths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64b5af06-d5d7-45c4-a340-3173531f4ec7
Chang Liu (changliu2)
requested changes
Sep 8, 2026
Chang Liu (changliu2)
left a comment
Collaborator
There was a problem hiding this comment.
Re-reviewed exact head 45afbc41. The new projection work improves representative tool payloads, context accumulation, and calibration, but none of the four prior blockers is resolved:
- A no-op resume still rewrites
metrics.jsonwhenever the zero-valuedtoken_estimatedictionary exists, replacing completed-run telemetry with zero usage (assert_ai/runner.py:1186-1198). - Viewer estimation still injects custom
artifacts_root/results_dironly into its temporary estimate config;writeRunConfigFiles()persists the original config for execution, so custom-root estimates and spawned runs can target different trees (viewer/src/lib/server/run-spawn.ts:699,1031-1033). max_tool_callsstill does not affect projections. Prompt and scenario estimates explicitly assume one tool round trip and exclude additional calls, while runtime may loop to the configured cap (assert_ai/core/token_estimator.py:1193-1196,1263-1267).- Note-only opaque-target caveats still disappear: the runner returns before logging notes at zero tokens, completed-run normalization drops a zero-valued estimate before reading notes, and the new-run preview neither types nor renders notes (
assert_ai/runner.py:548-551;viewer/src/lib/server/data.ts:174-188;viewer/src/routes/new/+page.svelte:47-53,1672-1676).
All checks are green, but these cases remain uncovered. Keeping changes requested.
Chang Liu (changliu2)
requested changes
Sep 8, 2026
Chang Liu (changliu2)
left a comment
Collaborator
There was a problem hiding this comment.
Re-reviewed exact head 45afbc4. The new commit improves per-round tool payload, history, and transcript accounting, but none of the four requested blockers is resolved:
runner.pyis unchanged: a no-op resume still rewrites an existingmetrics.jsonfrom a truthy zero-valued estimate and emptystage_usage, replacing prior telemetry with zero totals.viewer/src/lib/server/run-spawn.tsis unchanged: customARTIFACTS_ROOTvalues are still injected only into the estimate config, not the submitted run config, so the viewer and runner can use different result trees.pipeline.inference.max_tool_callsstill has no effect. On the current head, the simulated-tools example returns the same inference estimate for limits1and10: 675 calls and 1,369,075 tokens. The projection still models one round trip while runtime can continue to the configured cap.- Zero-token opaque-target caveats are still suppressed: run logging returns before notes, and viewer normalization drops the zero-valued estimate.
Please keep this in changes-requested state until all four are addressed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Summary
metrics.jsononly when the run and telemetry are completeHow token estimation works
This is a local, deterministic, stage-aware forecast, not an LLM prediction or a dry-run against the target. We tokenize what is already known and use conservative, schema-shaped projections for content that has not been generated yet. The CLI and viewer use the same implementation in
assert_ai/core/token_estimator.py, with read-only run planning inassert_ai/runner.py.1. Plan only the work that will run
Resolve the config and overrides, then apply the runtime's stage selection, artifact-cache, resume, and forced-stage rules. Reusable generation artifacts and compatible completed inference/judgments are excluded; forced or invalidated work is included. Existing test cases, taxonomies, and transcripts are used when applicable. For a first run, representative generated structures stand in for artifacts that do not exist yet. Estimation does not call a model, execute tools, or create run artifacts.
2. Count known inputs and project stage outputs
Known messages, system prompts, tool schemas, and structured-response schemas are counted locally with LiteLLM's model-aware tokenizer. Provider/model names are mapped to supported tokenizer families; unsupported names or tokenizer failures use a roughly four-characters-per-token fallback. This is token accounting, not dollar-cost estimation.
systematize)test_set)inference)max_turns, including tester requests, target requests, and tool round trips. Project response lengths using the rules below.judge)judge.n.3. Use high-side completion assumptions, not the full budget
When a target output limit
Bis present, final-answer projections are:Without a target limit, the prompt/scenario baselines are 512/384 tokens. Tester messages use 55 tokens, capped by the tester limit; simulated tool results use 90 tokens. For example, a target limit of 256 projects a 224-token final answer, while a limit of 900 projects a 675-token prompt answer. These are deliberately conservative heuristics, calibrated against the saved measurements below, not learned predictions of the exact answer length.
4. Carry projected content into later requests
Each projected tester/target message is appended to conversation history before estimating the next request. Earlier output therefore also becomes input on later turns and in the judge transcript; usage is not just a single-call estimate multiplied by turn count.
Tool-enabled targets assume one tool round trip per target turn: count a schema-shaped tool-call payload separately from one final answer, add the tool result, and retain that history in later target requests and judge input. Simulated tools add their own model call, using the same prompt builder as execution, including the conversation and previous tool results. Projected judge XML uses the runtime formatter and its message-length truncation.
5. Aggregate and report uncertainty
For larger case/job lists, count up to 24 evenly spaced profiles/jobs per group and scale their mean usage to the full count. Sum projected input and output tokens across pending calls and stages. The usual displayed range is 65%-135% of the point estimate: a heuristic planning band, not a statistically fitted confidence interval or guaranteed upper bound. High-side bias comes from the output assumptions, not a blanket multiplier on every known input.
Retries, extra tool calls, provider-added web-search context, and hidden provider overhead are not included. Model calls inside opaque callable, connector, endpoint, or sandbox targets are explicitly excluded; owned tester/judge calls can still be estimated. Notes surface these limitations and any unavailable stage estimate.
6. Compare with provider usage after completion
Keep the pre-run forecast and aggregate provider-reported input/output usage separately. CLI and viewer show the signed difference
(actual - estimate) / estimateonly when execution and tracked-call telemetry are complete; incomplete totals are labeled reported/partial rather than presented as a reliable accuracy result. The paired calibration section below explains how the latest algorithm was replayed against the same saved usage without fresh model calls.Viewer
The Summary & submit step now estimates usage before the evaluation starts. The compact row shows the conservative total, likely range, and tracked call count. It uses the same normalized payload as submission, makes no provider calls, and does not reserve a run directory. Submission waits while the estimate is loading; superseded or abandoned requests terminate their estimator process and clean up the temporary config.
Completed runs now use a single compact Token usage row instead of the previous three-card layout. It shows:
Older token metrics without the newer accuracy
statusfield remain readable. Runs without token telemetry show a small unavailable state rather than a large empty card.CLI
assert-ai estimate --config PATHexposes the same read-only estimate without starting a run;--output jsonis used by the viewer endpoint.assert-ai runprints the estimate before uncached stages execute and reports provider usage plus estimate accuracy after completion. Zero tracked-token estimates still emit explicit text and preserve notes for opaque callable, endpoint, connector, or sandbox usage.Validation
npm run check(0 errors; 6 existing warnings in unrelated files)npm run build/api/runs/estimatecheck: 83,242 estimated tokens, likely 54,107-112,377, 28 tracked calls, and no suite directory createdAccuracy review: estimate-only replay
The revised estimator is closer on all 12 original scenarios, while keeping 12/12 point estimates above saved actual usage and 12/12 actual totals inside the displayed likely range. Both CLI and viewer receive these changes through the shared estimator.
Calculation changes
Paired results
Method: estimate-only replay against saved provider usage; no fresh model calls. Used the same 12
azure/gpt-5.4-miniscenarios, fixture files, and original per-case overrides. The previous estimator reproduced every original total exactly before changes; fixture SHA-256 fingerprints remained unchanged. Ground truth is provider-reported token usage, not evaluation scores. Prediction runs created no run artifacts; calibration data stays local and uncommitted.Updated overestimate is
(estimate - actual) / actual. Standard MAPE usesabs(estimate - actual) / actual; the original PR metric instead divides by the estimate. Weighted error issum(abs(estimate - actual)) / sum(actual). Standard MAPE improved by about 23% relative.This is the same small calibration set, not independent evidence of accuracy on unseen workloads. Point estimates remain planning heuristics, not guaranteed upper bounds. Additional tool calls, retries, unusually long outputs, hidden provider overhead, and opaque target internals remain limitations.
Regression results
264 passed, 6 skipped. Re-ran the same model-client, estimator, runner-usage, stage-filter, artifact-cache, CLI, viewer-spawn, and viewer-token-usage selectors, plus existing runtime-mode/inference-stage coverage for the shared simulator prompt builder. Added deterministic regressions for completion-budget headroom, judge contract scaling/caps, tool payload accounting, accumulated tool history, simulator substitutions, and runtime XML escaping/truncation.
Original Azure calibration (before accuracy review)
Ran 12 fresh
azure/gpt-5.4-minievaluations after the conservative tuning, covering prompt length, requested output length, batching, one-to-four-turn conversations, long conversational context, a simulated tool loop, and a generated end-to-end pipeline. Generated calibration artifacts remain local and are not committed.Difference is
(actual - estimate) / estimate; negative values mean the estimate was conservative.The high-side bias is intentional: compared with the earlier tuning, mean absolute error increases from 14.0% to 20.0%, while underestimates drop from 2/12 runs to 0/12. A separate CLI/viewer demo estimated 4,453 tokens (likely 2,894-6,012) and reported 3,913 actual tokens, 12.1% below the estimate.