Skip to content

fix(sglang): _build_client_args clobbers user's extra_body instead of merging#1925

Merged
RobinPicard merged 1 commit into
dottxt-ai:mainfrom
chuenchen309:fix/sglang-extra-body-merge
Jul 20, 2026
Merged

fix(sglang): _build_client_args clobbers user's extra_body instead of merging#1925
RobinPicard merged 1 commit into
dottxt-ai:mainfrom
chuenchen309:fix/sglang-extra-body-merge

Conversation

@chuenchen309

Copy link
Copy Markdown
Contributor

Problem

SGLang._build_client_args (and the duplicated AsyncSGLang._build_client_args) builds the client kwargs with a shallow inference_kwargs.update(output_type_args). When output_type is Regex or CFG, format_output_type returns {"extra_body": {...}} for the structured-output payload. The blanket .update() then replaces the user's own extra_body= kwarg wholesale instead of merging into it — any keys the caller passed in extra_body are silently dropped.

The sibling vllm.py::_build_client_args already handles this correctly: it pops the user's extra_body, merges the structured-output payload into it, and reassigns. sglang.py never got the same treatment.

Fix

In both SGLang._build_client_args and AsyncSGLang._build_client_args: when output_type_args contains an extra_body key, merge it into the user's existing extra_body dict instead of letting .update() clobber it. Other top-level keys (e.g. response_format for JsonSchema output types) are unaffected and still applied normally via the existing .update() path.

Testing

  • Added test_sglang_build_client_args_merges_user_extra_body (parametrized over SGLang/AsyncSGLang) asserting both the user's key and the structured-output payload survive in the merged extra_body.
  • Confirmed red→green: reverting only src/outlines/models/sglang.py (not the test file) reproduces the failure (KeyError: 'user_key') on both parametrizations; reapplying the fix passes.
  • Full tests/models/test_sglang.py suite: 21 passed.
  • ruff check and pre-commit run --files src/outlines/models/sglang.py tests/models/test_sglang.py (mypy + ruff + hygiene hooks) all pass.

AI-Generated disclosure

Found via an AI-assisted code review pass (Claude Code) across outlines/src/outlines/models/. I personally reproduced the clobbering with a minimal repro, verified the fix against both the extra_body path (CFG/regex) and the response_format path (JsonSchema) to confirm no regression, and ran the full test suite before submitting.

… merging

`inference_kwargs.update(output_type_args)` is a shallow dict update.
When output_type is Regex/CFG, `format_output_type` returns
`{"extra_body": {...}}`, so this replaces the user's own `extra_body=`
kwarg wholesale instead of merging into it -- a user-supplied
`extra_body={"user_key": ...}` is silently dropped entirely. The
sibling `vllm.py`'s `_build_client_args` already handles this
correctly (pop the user's extra_body, merge, reassign).

Fix both the sync `SGLang._build_client_args` and the duplicated
async `AsyncSGLang._build_client_args`: when `output_type_args`
contains an `extra_body` key, merge it into the user's existing
`extra_body` instead of letting the blanket `.update()` clobber it.
Other top-level keys (e.g. `response_format` for JsonSchema output
types) are unaffected and still applied normally.

AI-Generated disclosure: found via an AI-assisted code review pass
(Claude Code) across outlines/src/outlines/models/; I personally
reproduced the clobbering with a minimal repro, verified the fix
against both the extra_body path (CFG/regex) and the response_format
path (JsonSchema) to confirm no regression, and ran the full
tests/models/test_sglang.py suite (21 tests) before submitting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ErenAta16

Copy link
Copy Markdown
Contributor

Verified the clobber: inference_kwargs.update(output_type_args) replaces the whole extra_body value on a matching key rather than merging nested dicts, so a user-supplied extra_body was silently discarded whenever structured output also needed one. The fix pops both sides, merges into the user's dict first, then puts the merged result back before the general update, so extra_body doesn't get processed twice or re-clobbered by the trailing inference_kwargs.update(output_type_args). Applied identically and correctly to both the sync and async client-arg builders. Test checks both the user key and the structured-output key survive together. LGTM.

@ErenAta16

Copy link
Copy Markdown
Contributor

Related, filed separately since it's a distinct bug from what this PR fixes: #1931. The extra_body = inference_kwargs.pop("extra_body", {}) pattern this PR keeps (and vllm.py also has) returns the caller's own dict object when the key is present, not a copy, so .update() mutates it in place. If a caller reuses one extra_body dict across multiple calls (structured, then output_type=None), the later call silently inherits the earlier one's structured_outputs key. Doesn't affect the clobbering fix in this PR, just flagging since it's the same function and the same underlying pop-without-copy habit. Might be worth a one-line dict(...) wrap here while it's already being touched, or as a follow-up.

@chuenchen309

Copy link
Copy Markdown
Contributor Author

Thanks for independently verifying this, and for splitting #1931 out rather than folding it in here — you're right that the aliasing is a distinct bug from the clobber. pop("extra_body", {}) handing back the caller's own object is a different failure mode with a different blast radius (it survives past the call), so it deserves its own fix and its own test.

@ErenAta16

Copy link
Copy Markdown
Contributor

Makes sense, thanks for confirming. Filed #1931 with the full repro for anyone who picks it up next.

@RobinPicard RobinPicard left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks!

@github-actions

Copy link
Copy Markdown

📚 Documentation preview: https://dottxt-ai.github.io/outlines/pr-preview/pr-1925/

Preview updates automatically with each commit.

@RobinPicard
RobinPicard merged commit 2c86869 into dottxt-ai:main Jul 20, 2026
5 of 6 checks passed
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.

3 participants