diff --git a/python/sglang/srt/entrypoints/openai/protocol.py b/python/sglang/srt/entrypoints/openai/protocol.py index e473976a8..b39ad2427 100644 --- a/python/sglang/srt/entrypoints/openai/protocol.py +++ b/python/sglang/srt/entrypoints/openai/protocol.py @@ -53,6 +53,7 @@ from pydantic import ( BaseModel, ConfigDict, Field, + StrictBool, field_serializer, field_validator, model_serializer, @@ -219,7 +220,10 @@ class JsonSchemaResponseFormat(BaseModel): description: Optional[str] = None # use alias to workaround pydantic conflict schema_: Optional[Dict[str, object]] = Field(alias="schema", default=None) - strict: Optional[bool] = None + # The OpenAI wire contract accepts JSON booleans only; StrictBool rejects + # the values lax pydantic would coerce ("yes", "on", 0, 1, ...), matching + # OpenAI's 422 behavior. Omitted (None) keeps its meaning. + strict: Optional[StrictBool] = None class ResponseFormat(BaseModel): diff --git a/test/registered/unit/entrypoints/openai/test_protocol.py b/test/registered/unit/entrypoints/openai/test_protocol.py index 245cdc3b6..eb3d60dbf 100644 --- a/test/registered/unit/entrypoints/openai/test_protocol.py +++ b/test/registered/unit/entrypoints/openai/test_protocol.py @@ -114,6 +114,42 @@ class TestCompletionRequest(unittest.TestCase): class TestChatCompletionRequest(unittest.TestCase): """Test ChatCompletionRequest protocol model""" + def test_json_schema_strict_requires_json_boolean(self): + base_request = { + "model": "test-model", + "messages": [{"role": "user", "content": "Hello"}], + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "answer", + "schema": {"type": "object"}, + }, + }, + } + + for strict in (True, False, None): + with self.subTest(strict=strict): + response_format = dict(base_request["response_format"]) + response_format["json_schema"] = { + **response_format["json_schema"], + "strict": strict, + } + request = ChatCompletionRequest.model_validate( + {**base_request, "response_format": response_format} + ) + self.assertIs(request.response_format.json_schema.strict, strict) + + for strict in ("yes", "false", 0, 1): + with self.subTest(strict=strict), self.assertRaises(ValidationError): + response_format = dict(base_request["response_format"]) + response_format["json_schema"] = { + **response_format["json_schema"], + "strict": strict, + } + ChatCompletionRequest.model_validate( + {**base_request, "response_format": response_format} + ) + def test_basic_chat_completion_request(self): """Test basic chat completion request""" messages = [{"role": "user", "content": "Hello"}]