Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions tools/server/server-common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -927,10 +927,15 @@ json oaicompat_chat_params_parse(
if (response_type == "json_object") {
json_schema = json_value(response_format, "schema", json::object());
} else if (response_type == "json_schema") {
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-response_format
// OpenAI expects: response_format.json_schema.schema
auto schema_wrapper = json_value(response_format, "json_schema", json::object());
json_schema = json_value(schema_wrapper, "schema", json::object());
if (!schema_wrapper.contains("schema")) {
throw std::invalid_argument("response_format type \"json_schema\" requires \"json_schema.schema\" to be set");
}
json_schema = schema_wrapper.at("schema");
} else if (!response_type.empty() && response_type != "text") {
throw std::invalid_argument("response_format type must be one of \"text\" or \"json_object\", but got: " + response_type);
throw std::invalid_argument("response_format type must be one of \"text\", \"json_object\", or \"json_schema\", but got: " + response_type);
}
}

Expand Down
31 changes: 31 additions & 0 deletions tools/server/tests/unit/test_chat_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,18 @@ def test_apply_chat_template():
({"type": "json_object", "schema": {"const": "42"}}, 6, "\"42\""),
({"type": "json_object", "schema": {"items": [{"type": "integer"}]}}, 10, "[ -3000 ]"),
({"type": "json_schema", "json_schema": {"schema": {"const": "foooooo"}}}, 10, "\"foooooo\""),
# json_schema with name field (OpenAI-style)
({"type": "json_schema", "json_schema": {"name": "test", "schema": {"const": "bar"}, "strict": True}}, 6, "\"bar\""),
({"type": "json_object"}, 10, "(\\{|John)+"),
({"type": "sound"}, 0, None),
# invalid response format (expected to fail)
({"type": "json_object", "schema": 123}, 0, None),
({"type": "json_object", "schema": {"type": 123}}, 0, None),
({"type": "json_object", "schema": {"type": "hiccup"}}, 0, None),
# json_schema missing required json_schema.schema field (should fail)
({"type": "json_schema", "json_schema": {"name": "test"}}, 0, None),
({"type": "json_schema", "json_schema": {}}, 0, None),
({"type": "json_schema"}, 0, None),
])
def test_completion_with_response_format(response_format: dict, n_predicted: int, re_content: str | None):
global server
Expand All @@ -203,6 +209,31 @@ def test_completion_with_response_format(response_format: dict, n_predicted: int
assert "error" in res.body


@pytest.mark.parametrize("response_format,expected_error_message", [
# json_schema type requires json_schema.schema to be set
({"type": "json_schema", "json_schema": {"name": "test"}}, "json_schema.schema"),
({"type": "json_schema", "json_schema": {}}, "json_schema.schema"),
({"type": "json_schema"}, "json_schema.schema"),
# invalid response_format type should mention valid options
({"type": "invalid_type"}, "json_schema"),
])
def test_response_format_error_messages(response_format: dict, expected_error_message: str):
"""Test that invalid response_format configurations return helpful error messages."""
global server
server.start()
res = server.make_request("POST", "/chat/completions", data={
"max_tokens": 10,
"messages": [
{"role": "user", "content": "test"},
],
"response_format": response_format,
})
assert res.status_code == 400
assert "error" in res.body
assert expected_error_message in res.body["error"]["message"], \
f"Expected '{expected_error_message}' in error message, got: {res.body['error']['message']}"


@pytest.mark.parametrize("jinja,json_schema,n_predicted,re_content", [
(False, {"const": "42"}, 6, "\"42\""),
(True, {"const": "42"}, 6, "\"42\""),
Expand Down