Skip to content

fix(api): harden /v1/completions thinking_budget (validation, stream parity, tokenizer detection) on top of #1844#1821

Merged
jundot merged 7 commits into
jundot:mainfrom
efortin:fix/completions-thinking-budget
Jun 14, 2026
Merged

fix(api): harden /v1/completions thinking_budget (validation, stream parity, tokenizer detection) on top of #1844#1821
jundot merged 7 commits into
jundot:mainfrom
efortin:fix/completions-thinking-budget

Conversation

@efortin

@efortin efortin commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #1844. That PR already forwards thinking_budget on /v1/completions; this one hardens the remaining edges around that path.

Raw completions can use a thinking budget when the prompt already opens a thinking block, for example by ending with <think>\n. The remaining issues were: negative budgets were accepted, streaming completions could leak the scheduler's synthetic <think>\n opener, and the stream strip guard needed to mirror the scheduler's token-level detection instead of relying on raw text.

Changes

  • Reject negative thinking_budget values on chat and completions.
  • Strip the synthetic <think>\n opener from streamed raw completions so streaming and non-streaming responses match.
  • Detect open thinking prompts from tokenizer ids, reusing the prompt ids already computed for context validation.
  • Keep the detector aligned with scheduler behavior, including disabled-thinking prompts and multi-token </think> markers.

Tests

Adds coverage for completions parsing/forwarding, negative-budget validation, stream prefix stripping, tokenizer-backed prompt detection, prompt-id reuse, disabled-thinking patterns, and the structural wiring guards for both completion paths.

Validation: targeted thinking-budget tests pass; broader completion or thinking or budget selection was re-run during review.

@efortin efortin marked this pull request as draft June 11, 2026 14:56
@efortin efortin marked this pull request as ready for review June 11, 2026 16:00
@efortin efortin force-pushed the fix/completions-thinking-budget branch from d6b4ac2 to 161eb75 Compare June 12, 2026 10:22
efortin and others added 6 commits June 13, 2026 12:35
The thinking_budget extension is accepted on /v1/chat/completions and
mapped from budget_tokens on /v1/messages, but /v1/completions silently
dropped it: CompletionRequest had no such field and neither completion
path passed it to the engine. Raw completions are a natural fit for the
budget — a prompt ending with an open think tag (e.g. "<think>\n")
already passes the scheduler's needs_think_prefix gate.

Add the field to CompletionRequest and thread it through the streaming
and non-streaming completion paths via the existing
_resolve_thinking_budget helper, so request-level and model-settings
budgets behave exactly like chat.
…ith non-stream

Two review findings on the completions budget path:

- thinking_budget accepted any int; a negative value has no semantics
  anywhere in the enforcement chain and was silently treated as exhausted.
  Reject it at the API boundary (ge=0) on both OpenAI surfaces.

- When a raw prompt opens a thinking block, the scheduler prepends its
  synthetic think opener to the first streamed chunk (chat streams rely on
  it to rebuild the reasoning block), so /v1/completions stream returned a
  marker the non-streaming path never exposes. Strip it once at the
  completions presentation layer: raw completions are a pure continuation
  of the prompt on both paths.

Also replace the substring-count wiring guard with structural AST checks
(handler -> engine call -> thinking_budget keyword), which survive
reformatting and wrappers.
jundot#1844 (merged upstream) forwards thinking_budget on /v1/completions via a
`**gen_kwargs` dict-unpack instead of an inline
`thinking_budget=_resolve_thinking_budget(...)` keyword. After rebasing
this branch on top of it, the source-level wiring guard only recognized
the inline form and failed. Teach the AST check to also accept the
dict-unpack pattern: the handler sets `gen_kwargs["thinking_budget"]`
from the resolved value, then unpacks the dict into the engine call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@efortin efortin force-pushed the fix/completions-thinking-budget branch from 161eb75 to a033d37 Compare June 13, 2026 10:40
@efortin efortin changed the title feat(api): support thinking_budget on /v1/completions fix(api): harden /v1/completions thinking_budget (validation, stream parity, tokenizer detection) on top of #1844 Jun 13, 2026
@jundot

jundot commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Thanks for the follow-up. I checked the completions wiring and the stream/non-stream parity path against the scheduler's think-prefix detection, and the tokenizer-backed guard looks correct. The targeted thinking-budget tests and the broader completion/thinking/budget selection pass locally. This looks good to me, and I'm going to merge it.

@jundot jundot merged commit 9b8cda5 into jundot:main Jun 14, 2026
4 checks passed
@efortin efortin deleted the fix/completions-thinking-budget branch June 14, 2026 09:21
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