fix(serve): a non-object json_schema answered 500 "engine failed" - #1587
Merged
JustVugg merged 1 commit intoSep 17, 2026
Merged
Conversation
generation_options read `(response_format.get("json_schema") or {})
.get("schema")`. A truthy json_schema that is not an object (a JSON
string, a list, a number) raised AttributeError, which do_POST turns
into HTTP 500 "The colibri engine failed to process the request." on
both /v1/chat/completions and /v1/completions. OpenAI SDKs retry a 5xx,
and the message blames the engine for a malformed request.
Take .get only from a dict, so the existing 400
"`response_format.json_schema.schema` must be an object." covers it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
JustVugg
pushed a commit
that referenced
this pull request
Sep 18, 2026
…iled"
generation_options() already refuses a `tool_choice` function object that
carries no name, with a 400. It never got the chance: the line above reads
`(choice.get("function") or {}).get("name")`, so writing the name where the
object goes -- {"type": "function", "function": "search"} instead of
{"function": {"name": "search"}} -- raises AttributeError, and do_POST's
catch-all answers HTTP 500 "The colibri engine failed to process the request."
OpenAI SDKs retry a 5xx, against an engine that was never asked anything.
The five renderers that read the forced tool (GLM-5.2, GLM-5.3, Kimi, DeepSeek
V4 and V4.1) carry the same expression, and on /v1/chat/completions the prompt
is rendered before generation_options runs, so they crash first; /v1/completions
reaches the validator directly and crashed there. Both endpoints, every arch.
Read the member, then check it -- the same shape as the json_schema fix
(#1587) -- through one helper, so the existing 400 covers all six call sites.
The legacy {"type": "function", "name": ...} spelling and a well-formed
function object are unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Symptom
A request whose
response_format.json_schemais not an object gets HTTP 500 on/v1/chat/completionsand/v1/completions. Examples: a JSON-encoded string, a list, or a number. The body blames the engine. The request below is one of those payloads, and the response and log line were captured from a realcoli serveon the tiny Qwen3.6 fixture:The request is malformed, so the answer should be a 400 that names the parameter. OpenAI SDKs retry a 5xx by default, so the malformed request is also resent before the client sees the error.
I found this by sending about 70 malformed payloads to a real
coli serve(tiny Qwen3.6 fixture). This was the only one that produced a 5xx. Every other malformed field, including the neighbouringjson_schema: {},response_format: "json"and an empty GBNF grammar, already gets a 400.Root cause
generation_optionsdoes:or {}covers a missing or falsyjson_schema, but not a truthy one that is not a dict. The resultingAttributeErrorreachesdo_POST's catch-all, which answers 500engine_error.Fix
Call
.get("schema")only whenjson_schemais a dict. Anything else reaches the existing 400: "response_format.json_schema.schemamust be an object." (paramresponse_format). Valid schemas are forwarded exactly as before.Tests
test_openai_server.TemplateTest.test_validates_generation_limitsgains subtests forjson_schemaas a string, a list, a number andtrue. Each must raiseAPIErrorwith status 400 and paramresponse_format.Fail-before on unfixed
dev:Verification
tests.test_openai_server: 171 tests OK (1 skipped), in the cp949 locale and withPYTHONUTF8=1.openai_server(18 modules;test_fp8_repack_full_familyleft out because it needs torch here): 375 tests OK, 26 skipped.coli serveon the tiny Qwen3.6 fixture (tools/make_qwen36_tiny.py+convert_qwen36.py). The same malformed-payload run now answers 400 forjson_schemaas a string or a list on/v1/chat/completions, and as a string on/v1/completions. Across all 62 cases of the first run: 52 × 400, 10 × 200, no 5xx, and norequest failedline in the server log.🤖 Generated with Claude Code