From 62b8143ad217fc7d3404119a9440be4a18ce31e0 Mon Sep 17 00:00:00 2001 From: "Piotr Wilkin (ilintar)" Date: Sun, 8 Mar 2026 17:14:43 +0100 Subject: [PATCH] Fix structured outputs (#20223) * Fix structured outputs * Update common/chat-auto-parser-generator.cpp Co-authored-by: Aldehir Rojas --------- Co-authored-by: Aldehir Rojas --- common/chat-auto-parser-generator.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/common/chat-auto-parser-generator.cpp b/common/chat-auto-parser-generator.cpp index 01787cc8dc..03dfb8f10f 100644 --- a/common/chat-auto-parser-generator.cpp +++ b/common/chat-auto-parser-generator.cpp @@ -1,6 +1,7 @@ #include "chat-auto-parser.h" #include "chat-peg-parser.h" #include "chat.h" +#include "common.h" #include "json-schema-to-grammar.h" #include "nlohmann/json.hpp" @@ -51,13 +52,15 @@ common_chat_params peg_generator::generate_parser(const common_chat_template & bool has_tools = autoparser.tools.format.mode != tool_format::NONE && inputs.tools.is_array() && !inputs.tools.empty(); std::string trigger_marker = !autoparser.tools.format.section_start.empty() ? autoparser.tools.format.section_start : - autoparser.tools.format.per_call_start; - bool include_grammar = - has_tools && ((inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO && !trigger_marker.empty()) || - inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED); + autoparser.tools.format.per_call_start; + + bool has_response_format = !inputs.json_schema.empty() && inputs.json_schema.is_object(); + bool include_grammar = has_response_format || (has_tools && + ((inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO && !trigger_marker.empty()) || + inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED)); if (include_grammar) { - data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO; + data.grammar_lazy = !has_response_format && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO; data.grammar = build_grammar([&](const common_grammar_builder & builder) { foreach_function(inputs.tools, [&](const json & tool) { const auto & function = tool.at("function"); @@ -68,7 +71,7 @@ common_chat_params peg_generator::generate_parser(const common_chat_template & }); // Set grammar triggers based on tool section markers (fall back to per-call markers) - if (data.grammar_lazy) { // only do triggers on lazy grammar + if (data.grammar_lazy) { data.grammar_triggers = { { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, trigger_marker } }; @@ -104,8 +107,11 @@ common_peg_arena autoparser::build_parser(const templates_params & inputs) const bool has_response_format = inputs.json_schema.is_object() && !inputs.json_schema.empty(); if (has_response_format) { - return ctx.reasoning_parser + p.space() + - p.content(p.schema(p.json(), "response-format", inputs.json_schema)) + p.end(); + auto response_format = p.rule("response-format", p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema))); + return ctx.reasoning_parser + p.space() + p.choice({ + p.literal("```json") + p.space() + response_format + p.space() + p.literal("```"), + response_format + }) + p.end(); } if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE && jinja_caps.supports_tool_calls) {