You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
stop as a plain string is rejected, and malformed requests get a 500 instead of a 400 #221
Two related gaps on /v1/chat/completions and /v1/completions (main @ 1b6befd, M1 Max):
1. stop as a plain string is rejected
OpenAI documents stop as "string / array / null", and a bare string is what several SDKs and llama.cpp-style clients send. Both request types decode only the array form:
(and L4487 for TextCompletionRequest), so the request fails to decode:
curl -s localhost:5413/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"Count from one to ten in words."}],"max_tokens":40,"stop":"three"}'# → HTTP 500# {"error":{"type":"server_error","code":"internal_error","message":"DecodingError.typeMismatch: Expected value of type Array<Any>. Path: stop. …"}}
2. A request that does not decode is answered with 500, not 400
That 500 is the route's generic catch (L1520-L1536 / L1540-L1552), which treats a DecodingError from the request body the same as an internal failure. OpenAI answers these with 400 invalid_request_error and names the offending param. The distinction matters in practice: the OpenAI SDKs treat a 400 as final but retry a 5xx (openai-python retries twice by default), so a client with a malformed request pays three round trips to learn nothing, and the error tells it the server was at fault. Same story for malformed JSON or a missing messages:
POST /v1/chat/completions {"model":"x"}
→ 500 {"error":{"type":"server_error","code":"internal_error","message":"DecodingError.keyNotFound: Key 'messages' not found …"}}
Suggested fix: accept stop as string or array (same try? decode(String)-then-array pattern MessageContent already uses), and map a DecodingError from the body decode to 400 invalid_request_error with param set from the coding path. Only the request-body decode; everything else on these routes stays a 500. I have both ready with unit tests and contract-script assertions, happy to send a PR.
Thanks for the detailed report. I checked it against main and agree with both points:
stop is decoded only as an array in ChatRequest / TextCompletionRequest, so a bare string (which OpenAI allows) fails to decode.
The /v1/chat/completions route's generic catch turns any error, including a DecodingError from the request body, into a 500 server_error. OpenAI answers these with 400 invalid_request_error, and SDKs retry 5xx but not 400.
I have not run your curl repro myself; this is confirmed from the code.
Your proposed fix looks right: accept stop as string or array, and map only the request-body DecodingError to 400 invalid_request_error with param from the coding path, leaving everything else on these routes as 500. A PR with the unit tests and contract-script assertions would be very welcome.
PR is up: #223. stop now decodes as a string or an array on both request types (null or absent still means no client stops), and only a DecodingError from the request-body decode maps to 400 invalid_request_error with param from the coding path, so stop, messages, messages.0.role and so on get named; everything else on those routes still returns 500 as before.
10 unit tests cover the decoding and the error payload, and the contract script gained 2 assertions: a bare stop string is accepted and honoured and "stop": 42 answers 400 naming stop. Both are red on main and green on the branch, against the CI model.
Activity
solderzzc commentedon Oct 9, 2026
Thanks for the detailed report. I checked it against main and agree with both points:
stopis decoded only as an array inChatRequest/TextCompletionRequest, so a bare string (which OpenAI allows) fails to decode./v1/chat/completionsroute's genericcatchturns any error, including aDecodingErrorfrom the request body, into a 500server_error. OpenAI answers these with 400invalid_request_error, and SDKs retry 5xx but not 400.I have not run your curl repro myself; this is confirmed from the code.
Your proposed fix looks right: accept
stopas string or array, and map only the request-bodyDecodingErrorto 400invalid_request_errorwithparamfrom the coding path, leaving everything else on these routes as 500. A PR with the unit tests and contract-script assertions would be very welcome.HamedShams commentedon Oct 9, 2026
Thanks for confirming @solderzzc
PR is up: #223.
stopnow decodes as a string or an array on both request types (null or absent still means no client stops), and only aDecodingErrorfrom the request-body decode maps to 400invalid_request_errorwithparamfrom the coding path, sostop,messages,messages.0.roleand so on get named; everything else on those routes still returns 500 as before.10 unit tests cover the decoding and the error payload, and the contract script gained 2 assertions: a bare
stopstring is accepted and honoured and"stop": 42answers 400 namingstop. Both are red on main and green on the branch, against the CI model.