Skip to content

fix(aviary): sum token usage across every model call in an episode - #4716

Merged
cmunley1 merged 1 commit into
mainfrom
qiaochuz/nvb-6900411-aviary-usage-sum
Oct 20, 2026
Merged

cmunley1 merged 1 commit into
mainfrom
qiaochuz/nvb-6900411-aviary-usage-sum

Conversation

@qiaochuz-nv

Copy link
Copy Markdown
Contributor

What does this PR do?

Makes aviary_agent and toolsandbox_agent report the token usage of the whole episode. Before this change,
response.usage held only the last model call's counts. Every multi-step rollout therefore under-reported
input_tokens/output_tokens/total_tokens, and so did every aggregate built from them.

Fixes NVBug 6900411.

Closes #4504

Cause

AviaryAgent.responses() loops model call → /step → model call. It reassigns model_response each turn and
returns AviaryNeMoGymResponse.model_validate(model_response.model_dump() | output_overrides). output_overrides
never set usage, so the last call's usage wins. This construction dates from the original integration (#55); #590
moved the overrides into output_overrides and kept model_response.model_dump() as the base. #3805 added a
post-terminal model call (generate_final_response_after_done), and that call was dropped the same way.

toolsandbox_agent (#1949) copied the same loop and has the same defect.

nemo_gym/reward_profile.py copies response.usage into each rollout's numeric fields, so the per-rollout token
counts and the mean/, median/, max/... token metrics were all under-counted. They are real counts from one
call, so nothing looked wrong. The rollout_token_count_mismatch health check would flag the mismatch, but only
when model-call capture is on.

Fix

  • Both agents accumulate accumulate_response_usage(usage, model_response.usage) after each validated model
    response and put "usage": usage in output_overrides. The rest of the returned response (id, status, model)
    still comes from the last call.
  • This is the helper simple_agent and gymnasium_agent already use. A call that fails or fails validation
    breaks before it is counted, as before. A call with no usage (a provider that omits it,
    responses_converter.py leaves it None) is skipped, which is the helper's policy. critpt_agent instead
    reports null when any call lacks usage. I kept the shared behaviour, to match the other loop agents.
  • Tests: usage on both mocked calls of test_responses_multi_step, including cached and reasoning details, and on
    both calls of the parametrized post-terminal test (aviary). A new two-turn test for toolsandbox. Each fails on
    main with the last call's numbers.

Before the fix

Gym b3103ea8a (current main):

responses_api_agents/aviary_agent/.venv/bin/python -m pytest responses_api_agents/aviary_agent/tests/test_app.py
responses_api_agents/toolsandbox_agent/.venv/bin/python -m pytest responses_api_agents/toolsandbox_agent/tests/test_app.py
FAILED test_responses_multi_step
  {'input_tokens': 1818, 'input_tokens_details': {'cached_tokens': 300}, 'output_tokens': 24, ..., 'total_tokens': 1842}
  != {'input_tokens': 2118, 'input_tokens_details': {'cached_tokens': 400}, 'output_tokens': 1424, ..., 'total_tokens': 3542}
FAILED test_responses_generates_final_response_after_environment_done[True-True-False]   (600/8 != 1100/48)
FAILED test_responses_generates_final_response_after_environment_done[True-True-True]
3 failed, 10 passed
FAILED test_responses_sums_usage_across_turns   (toolsandbox: 1818/24/1842 != 2118/1424/3542)

Live, with model-call capture on (QA model_call_capture_agent_backends, aviary_gsm8k, forced multi-call episode,
h100). response.usage equals the last captured call alone:

aviary on the inference provider: 10 model call(s); response.usage.input_tokens vs sum of calls: expected 6895, got 949
aviary on a real vLLM (Qwen3-8B): 2 model call(s); response.usage.input_tokens vs sum of calls: expected 342, got 62
model_call_capture_agent_backends: 0/2 passed
toolsandbox (example task 0, 2 agent calls): response.usage 839/818/1657 vs sum of agent calls 1355/1088/2443

After the fix

Gym e64106a7a (this branch), same commands:

aviary_agent: 13 passed
toolsandbox_agent: 14 passed
aviary on the inference provider: 10 model call(s)  PASS
aviary on a real vLLM (Qwen3-8B): 2 model call(s)   PASS
model_call_capture_agent_backends: 2/2 passed
toolsandbox task 0: response.usage 1493/1068/2561 == sum of agent calls (516+977 in, 409+659 out)
toolsandbox task 1: response.usage 4575/728/5303 == sum of agent calls

Validation

  • gym env test +entrypoint=responses_api_agents/aviary_agent +should_validate_data=true: 13 passed;
    same for toolsandbox_agent: 14 passed. Both new assertions fail on main.
  • pre-commit run --all-files: passed.
  • QA testcase model_call_capture_agent_backends (h100, GPU lane, inference provider + local Qwen3-8B vLLM), with
    a QA-side assertion that response.usage equals the sum of the captured per-call counts: main 0/2 → 2/2.
  • Rollout evidence: the four live aviary rollouts above (provider and vLLM, main and fix). Toolsandbox: its
    registered QA case is blocked on README-model access, so I ran a scratch smoke of the shipped toolsandbox config
    on Nemotron-3-Nano-30B-A3B with capture on, --limit 2. It compared usage with the agent's captured Responses
    calls; the user-simulator's chat calls are excluded. Toolsandbox has no committed integration check beyond its
    unit tests.

Compatibility

  • Token metrics for multi-step aviary and toolsandbox rollouts go up: per-rollout input_tokens/output_tokens/
    total_tokens and their aggregates now cover every model call. Single-step episodes are unchanged. Earlier
    numbers for these environments under-count and are not comparable.
  • This matches the documented contract (rollout-evidence.mdx: usage is the sum over model responses that
    finished). It also removes a false rollout_token_count_mismatch for multi-call rollouts when capture is on.
  • NeMo RL does not read response.usage from Gym rollouts, so training is unaffected.
  • Open PRs feat(aviary): support post-terminal model response #3109 and feat(aviary): consolidate Hypotest and support final assistant responses #3792 also edit aviary_agent/app.py; neither touches usage.

🤖 Generated with Claude Code

AviaryAgent.responses() built its returned response from the last model
call, so response.usage (and the rollout input/output/total token fields
reward_profile copies from it) counted only the final call of a multi-step
episode. Accumulate usage with accumulate_response_usage over every
validated model response, including the post-terminal final-response call.

toolsandbox_agent copied the same loop and had the same defect; apply the
same accumulator there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
@qiaochuz-nv qiaochuz-nv added bug Something isn't working QA_fix Fix implemented by a QA team member area:agent Agent harnesses and Responses API agent behavior needs-review PR is ready for code review and waiting on a reviewer complexity:low Localized change in one scope with a small, straightforward review surface labels Oct 18, 2026
@qiaochuz-nv
qiaochuz-nv requested a review from yaoyu-33 October 18, 2026 07:12
@copy-pr-bot

copy-pr-bot Bot commented Oct 18, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@qiaochuz-nv

Copy link
Copy Markdown
Contributor Author

/ok to test e64106a

@cmunley1
cmunley1 added this pull request to the merge queue Oct 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/39393435484

@cmunley1
cmunley1 removed this pull request from the merge queue due to a manual request Oct 20, 2026
@cmunley1
cmunley1 added this pull request to the merge queue Oct 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/39393828662

Merged via the queue into main with commit 70d62f3 Oct 20, 2026
46 of 51 checks passed
@cmunley1
cmunley1 deleted the qiaochuz/nvb-6900411-aviary-usage-sum branch October 20, 2026 08:46
zyzhou5 pushed a commit to zyzhou5/Gym that referenced this pull request Oct 22, 2026
…ts (NVIDIA-NeMo#4753)

proof_refinement_agent and three conversational_tool_use generation agents
(domain, policy_tool, scenario) returned a rollout response whose usage
came from one model call: the last correction turn, the last domain phase,
the final tools completion, or the one completion chosen as the scenario
artifact. reward_profile copies response.usage into the rollout token
fields, so those under-counted every multi-call rollout.

Sum every policy-model call's usage with accumulate_response_usage, as
NVIDIA-NeMo#4716 does for aviary_agent and toolsandbox_agent. Chat agents convert
each completion's usage with a new chat_completion_usage_to_response_usage
helper, which chat_completion_to_response now uses too. Scenario
generation also counts completions whose text failed to parse;
policy_tool_generation leaves its judge calls out, as rollout usage covers
the policy model.

proof_refinement_agent correction turns also copied max_tokens, which
Responses requests don't have, so the caller's max_output_tokens cap was
dropped on every turn after the first. Copy max_output_tokens instead.

Follow-up to NVBug 6900411.

Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
public — e64106a7 Deployed Oct 18, 2026 by copy-pr-bot[bot] via release / finalize / notify #5721
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Agent harnesses and Responses API agent behavior bug Something isn't working complexity:low Localized change in one scope with a small, straightforward review surface needs-review PR is ready for code review and waiting on a reviewer QA_fix Fix implemented by a QA team member sla:review-overdue Review response is over the one-business-day SLA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NVBug 6900411] Sum token usage across all model calls in aviary_agent rollouts

2 participants