Commit 3b4d6ab3f for llama.cpp
commit 3b4d6ab3f79651935fba1dd93732f29022a7f5f5
Author: Ramzi <146260554+rmz-24@users.noreply.github.com>
Date: Tue Nov 10 01:44:07 2026 +0100
chat : don't use lazy grammar when response_format is set with tools (#31956)
diff --git a/common/parsers/deepseek.cpp b/common/parsers/deepseek.cpp
index dac180715..e5f10347a 100644
--- a/common/parsers/deepseek.cpp
+++ b/common/parsers/deepseek.cpp
@@ -300,7 +300,7 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
});
if (include_grammar) {
- data.grammar_lazy = has_tools && !require_tools;
+ data.grammar_lazy = !has_response_format && has_tools && !require_tools;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
data.parser.build_grammar(builder, data.grammar_lazy);
});
diff --git a/common/parsers/ministral3.cpp b/common/parsers/ministral3.cpp
index 634163959..c865a3cae 100644
--- a/common/parsers/ministral3.cpp
+++ b/common/parsers/ministral3.cpp
@@ -109,7 +109,7 @@ common_chat_params common_chat_params_init_ministral_3(const common_chat_templat
});
if (include_grammar) {
- data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
+ data.grammar_lazy = !has_response_format && has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
data.parser.build_grammar(builder, data.grammar_lazy);
diff --git a/common/parsers/qwen3-coder.cpp b/common/parsers/qwen3-coder.cpp
index a68a6835e..b8dc02267 100644
--- a/common/parsers/qwen3-coder.cpp
+++ b/common/parsers/qwen3-coder.cpp
@@ -191,7 +191,7 @@ common_chat_params common_chat_params_init_qwen3_coder(const common_chat_templat
});
if (include_grammar) {
- data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
+ data.grammar_lazy = !has_response_format && has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
data.parser.build_grammar(builder, data.grammar_lazy);
diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp
index 60090d5c9..57955b983 100644
--- a/tests/test-chat.cpp
+++ b/tests/test-chat.cpp
@@ -2397,6 +2397,18 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
.expect_content(R"({"amount": 123.45, "date": "2025-12-03"})")
.run();
+ tst.test(
+ "I need to output the invoice details in JSON\n"
+ "</think>\n\n"
+ R"({"amount": 123.45, "date": "2025-12-03"})")
+ .reasoning_format(COMMON_REASONING_FORMAT_AUTO)
+ .enable_thinking(true)
+ .tools({ get_time_tool })
+ .json_schema(invoice_schema)
+ .expect_reasoning("I need to output the invoice details in JSON")
+ .expect_content(R"({"amount": 123.45, "date": "2025-12-03"})")
+ .run();
+
// a tool call ends the prefilled thinking block, with or without a closing </think>
tst.test(
"<tool_call>\n"
@@ -2741,6 +2753,18 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
.expect_content(R"({"amount": 123.45, "date": "2025-12-03"})")
.run();
+ tst.test(
+ "[THINK]I need to output the invoice details in JSON[/THINK]"
+ "```json\n"
+ R"({"amount": 123.45, "date": "2025-12-03"})"
+ "\n```")
+ .reasoning_format(COMMON_REASONING_FORMAT_AUTO)
+ .tools({ special_function_tool })
+ .json_schema(invoice_schema)
+ .expect_reasoning("I need to output the invoice details in JSON")
+ .expect_content(R"({"amount": 123.45, "date": "2025-12-03"})")
+ .run();
+
// fake tool call marker in reasoning
tst.test(
"[THINK]Let me think about [TOOL_CALLS]special_function[ARGS]{\"arg1\":1} and more[/THINK]"
@@ -4326,6 +4350,17 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
.expect(message_assist)
.run();
+ tst.test(
+ "```json\n"
+ R"({"amount": 123.45, "date": "2025-12-03"})"
+ "\n```")
+ .enable_thinking(false)
+ .reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK)
+ .tools({ get_time_tool })
+ .json_schema(invoice_schema)
+ .expect_content(R"({"amount": 123.45, "date": "2025-12-03"})")
+ .run();
+
// Thinking + content
tst.test("I'm\nthinking</think>Hello, world!\nWhat's up?")
.enable_thinking(true)