diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp index f54a58f9b67c..d62e2c9cb483 100644 --- a/tests/test-chat.cpp +++ b/tests/test-chat.cpp @@ -1974,6 +1974,41 @@ static void test_convert_responses_to_chatcmpl() { assert_equals(false, result.contains("tools")); } + + // Test JSON schema conversion + { + json input = json::parse(R"({ + "input": "Hello", + "model": "test-model", + "text": { + "format": { + "type": "json_schema", + "name": "TestOutput", + "schema": { + "type": "object", + "properties": { + "message": {"type": "string"} + }, + "required": ["message"], + "additionalProperties": false + }, + "strict": true + } + } + })"); + + json result = server_chat_convert_responses_to_chatcmpl(input); + + assert_equals(false, result.contains("text")); + assert_equals(true, result.contains("response_format")); + const auto & response_format = result.at("response_format"); + assert_equals(std::string("json_schema"), response_format.at("type").get()); + const auto & json_schema = response_format.at("json_schema"); + assert_equals(std::string("TestOutput"), json_schema.at("name").get()); + assert_equals(true, json_schema.at("strict").get()); + assert_equals(std::string("object"), json_schema.at("schema").at("type").get()); + assert_equals(false, json_schema.contains("type")); + } } // Shared LFM2 parser cases - all variants use one output format and parser diff --git a/tools/server/server-chat.cpp b/tools/server/server-chat.cpp index 0322e54ccea8..a7e3aa55fd78 100644 --- a/tools/server/server-chat.cpp +++ b/tools/server/server-chat.cpp @@ -292,6 +292,34 @@ json server_chat_convert_responses_to_chatcmpl(const json & response_body) { chatcmpl_body.erase("reasoning"); } + if (response_body.contains("text")) { + const json & text = response_body.at("text"); + if (!text.is_object()) { + throw std::invalid_argument("'text' must be an object"); + } + + chatcmpl_body.erase("text"); + if (text.contains("format")) { + const json & format = text.at("format"); + if (!format.is_object()) { + throw std::invalid_argument("'text.format' must be an object"); + } + + const std::string type = json_value(format, "type", std::string()); + if (type == "json_schema") { + chatcmpl_body["response_format"] = { + {"type", "json_schema"}, + {"json_schema", format}, + }; + chatcmpl_body["response_format"]["json_schema"].erase("type"); + } else if (type == "json_object" || type == "text") { + chatcmpl_body["response_format"] = format; + } else if (!type.empty()) { + throw std::invalid_argument("'text.format.type' must be one of 'text', 'json_object', or 'json_schema'"); + } + } + } + return chatcmpl_body; }