openai / openai/openai-openapi
`gpt-5.5` under-reports `usage.output_tokens` for grammar-constrained custom-tool generations
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 2.5k
- Forks
- 527
- Avg merge
- 1h 46m
- Merged PRs (30d)
- 2
Description
Summary
On POST /v1/responses, when the model emits a custom tool call whose output is constrained by a
lark context-free grammar (tools[].type:"custom" + format:{type:"grammar", syntax:"lark"}),
gpt-5.5 sporadically reports a usage.output_tokens that is a small fraction of the tokens it
actually emitted. The deficit also propagates into usage.total_tokens, so affected calls are
under-billed. status is completed and reasoning_tokens is 0, so this is neither
truncation nor reasoning accounting.
- API:
/v1/responses(non-streaming), observed on OpenAI direct (api.openai.com). - Model:
gpt-5.5-2026-04-23(clean repro). Aggregate fleet analysis indicates the issue spans
thegpt-5.xfamily on this code path (gpt-5.5most severe,gpt-5.3-codexmost frequent,
gpt-5.4mild); it does not occur on non-OpenAI models, which lack this tool path. - Trigger: a single large grammar-constrained
apply_patchgeneration (hundreds of lines). - Rate: ~1% extreme (ratio < 0.5), ~5% including mild (ratio < 0.9) under forced large patches.
Evidence (direct api.openai.com, gpt-5.5-2026-04-23, status=completed, reasoning_tokens=0)
We measure: visible_ratio = (output_tokens − reasoning_tokens) / o200k_base_tokens(emitted tool text).
A correct call is ≈ 1.00 (the emitted tool text is essentially all of the visible output).
response id |
reported output_tokens |
actual emitted tokens (o200k_base) | visible_ratio |
total_tokens |
|---|---|---|---|---|
resp_0abcbea5ee8ed031016a3034c7ce1481988703b36797cfb5e8 |
814 | 6,943 (27,763 chars) | 0.117 | 21,701 |
resp_04c402a0b422909a016a30398812488199a7820b9cf725a4c9 |
3,584 | 7,129 (29,282 chars) | 0.503 | 21,887 |
resp_05a681efc8e84b32016a303d63af88819a867161c64e98d854 (control) |
5,935 | 5,924 | 1.002 | 20,680 |
For resp_0abcbea5…: the single emitted apply_patch tool call alone is 6,943 o200k tokens, yet
output_tokens is reported as 814 and total_tokens (21,701 = input 20,887 + output 814) is short by
~6,100 tokens versus the content actually produced. The control call (req-id
req_0122fb83ab304749b2928d7f6ef2db6e) on the identical request shape counts correctly (ratio 1.002).
How to reproduce
A self-contained script is attached (openai_apply_patch_undercount_repro.py, stdlib + tiktoken).
Minimal recipe:
POST /v1/responseswithmodel:"gpt-5.5", a single custom tool:
and{"type":"custom","name":"apply_patch", "format":{"type":"grammar","syntax":"lark","definition":"start: begin_patch hunk+ end_patch\n..."}}tool_choice:{"type":"custom","name":"apply_patch"},reasoning:{"effort":"medium"},
stream:false.- Prompt that forces one large
*** Add Filepatch (e.g. "write a 300+ line Python SQL engine as a
single apply_patch hunk"). - For each response, compare
usage.output_tokens − reasoning_tokensto ano200k_basetoken count
of the emitted tool-callinputtext. Repeat a few dozen times; a fraction come back with
output_tokensfar below the emitted-token count.
The script saves each affected response body as undercount_<request_id>.json and prints its
request id. It runs against OpenAI or Azure OpenAI (api-key or managed identity).
Ruled out (not the cause)
- Truncation —
status == "completed"; the full grammar-valid patch is present inoutput. - Reasoning accounting —
reasoning_tokens == 0on the affected calls. - Client-side counting/deserialization — the under-count is in the raw HTTP response bytes
off the wire; we readusage.output_tokensverbatim, and an independento200k_basecount of the
emitted text is several times larger. - Predicted outputs —
predictionis not set on these requests.
Impact
- Billing:
output_tokensandtotal_tokensare deflated, so affected calls are under-billed. - Downstream tooling: any system relying on
usagefor budgeting, rate accounting, eval token
attribution, or per-call cost is silently skewed on grammar-constrained tool generations.
Ask
Please review the shared JSON response payload(s) (e.g. resp_0abcbea5…, included) and the
attached repro script to confirm the issue. The affected response ids are included should they
help your investigation.
openai_apply_patch_undercount_repro.py
openai_undercount_raw.json
openai_undercount_raw_2.json
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the attached openai_apply_patch_undercount_repro.py script and compare its saved undercount_<request_id>.json responses with the reported usage fields. Review the raw JSON payloads and reproduce the discrepancy across repeated grammar-constrained custom-tool requests. Done means confirming whether the response accounting is incorrect and documenting the evidence for follow-up.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- openapi, python
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100