From 27f21d4d13075e33fecc86e88b43fe9b192e4235 Mon Sep 17 00:00:00 2001 From: Piotr Wilkin Date: Fri, 23 Jan 2026 20:18:25 +0100 Subject: [PATCH] Add workaround for templates requiring non-null content --- common/chat.cpp | 13 ++++++++ common/jinja/caps.cpp | 70 ++++++++++++++++++++++++++++++++++++++++--- common/jinja/caps.h | 1 + 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/common/chat.cpp b/common/chat.cpp index 2bf4632669..b0bc187a7d 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -2929,6 +2929,15 @@ static void system_message_not_supported(json & messages) { } } +static void requires_non_null_content(json & messages) { + GGML_ASSERT(messages.is_array()); + for (auto & message : messages) { + if (message.contains("tool_calls") && !messages.contains("content")) { + messages["content"] = ""; + } + } +} + static void func_args_not_string(json & messages) { GGML_ASSERT(messages.is_array()); for (auto & message : messages) { @@ -3034,6 +3043,10 @@ static common_chat_params common_chat_templates_apply_jinja( workaround::system_message_not_supported(params.messages); } + if (!tmpl.original_caps().requires_non_null_content) { + workaround::requires_non_null_content(params.messages); + } + params.extra_context = json::object(); for (auto el : inputs.chat_template_kwargs) { params.extra_context[el.first] = json::parse(el.second); diff --git a/common/jinja/caps.cpp b/common/jinja/caps.cpp index f27490f1fb..ec8509785e 100644 --- a/common/jinja/caps.cpp +++ b/common/jinja/caps.cpp @@ -64,6 +64,7 @@ static void caps_print_stats(value & v, const std::string & path) { std::map caps::to_map() const { return { {"requires_typed_content", requires_typed_content}, + {"requires_non_null_content", requires_non_null_content}, {"supports_tools", supports_tools}, {"supports_tool_calls", supports_tool_calls}, {"supports_parallel_tool_calls", supports_parallel_tool_calls}, @@ -160,7 +161,7 @@ caps caps_get(jinja::program & prog) { {"content", "Assistant message"}, {"tool_calls", json::array({ { - {"id", "call1"}, + {"id", "call0001"}, {"type", "function"}, {"function", { {"name", "tool1"}, @@ -170,10 +171,10 @@ caps caps_get(jinja::program & prog) { }} }, { - {"id", "call2"}, + {"id", "call0002"}, {"type", "function"}, {"function", { - {"name", "tool2"}, + {"name", "tool1"}, {"arguments", { {"arg", "value"} }} @@ -194,7 +195,7 @@ caps caps_get(jinja::program & prog) { {"name", "tool"}, {"type", "function"}, {"function", { - {"name", "tool"}, + {"name", "tool1"}, {"description", "Tool description"}, {"parameters", { {"type", "object"}, @@ -238,6 +239,67 @@ caps caps_get(jinja::program & prog) { } ); + // case: requires non-null content in tool calls + caps_try_execute( + prog, + [&]() { + // messages + return json::array({ + { + {"role", "user"}, + {"content", "User message"}, + }, + { + {"role", "assistant"}, + {"tool_calls", json::array({ + { + {"id", "call0001"}, + {"type", "function"}, + {"function", { + {"name", "tool1"}, + {"arguments", { + {"arg", "value"} + }} + }} + }, + })} + }, + { + {"role", "user"}, + {"content", "User message"}, + }, + }); + }, + [&]() { + // tools + return json::array({ + { + {"name", "tool"}, + {"type", "function"}, + {"function", { + {"name", "tool1"}, + {"description", "Tool description"}, + {"parameters", { + {"type", "object"}, + {"properties", { + {"arg", { + {"type", "string"}, + {"description", "Arg description"}, + }}, + }}, + {"required", json::array({ "arg" })}, + }}, + }}, + }, + }); + }, + [&](bool success, value & messages, value & tools) { + if (!success) { + result.requires_non_null_content = true; + } + } + ); + // case: preserve reasoning content in chat history caps_try_execute( prog, diff --git a/common/jinja/caps.h b/common/jinja/caps.h index 77df117baa..c077ed49e6 100644 --- a/common/jinja/caps.h +++ b/common/jinja/caps.h @@ -15,6 +15,7 @@ struct caps { bool supports_preserve_reasoning = false; // support assistant message with reasoning_content bool requires_typed_content = false; // default: use string content + bool requires_non_null_content = false; // requires "" instead of null for content in tool calls // for reporting on server std::map to_map() const;