* Fix call ID detection (Mistral parser mostly) + atomicity for tag-json parsers
* Rename
* Update common/chat-auto-parser-generator.cpp
Co-authored-by: Sigbjørn Skjæret <redacted>
---------
Co-authored-by: Sigbjørn Skjæret <redacted>
#include "json-schema-to-grammar.h"
#include "log.h"
#include "nlohmann/json.hpp"
+#include "peg-parser.h"
#include <algorithm>
#include <stdexcept>
p.end();
}
+common_peg_parser analyze_tools::build_func_parser(common_chat_peg_builder & p, const std::string & name,
+ const common_peg_parser & call_id_section, bool have_call_id,
+ const common_peg_parser & args,
+ std::optional<common_peg_parser> atomic_peek) const {
+ auto open = p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix);
+ bool matched_atomic = false;
+ common_peg_parser func_parser = p.eps();
+
+ if (!function.name_suffix.empty()) {
+ func_parser = open + call_id_section + p.space() + args;
+ matched_atomic = true;
+ } else if (have_call_id) {
+ func_parser = p.atomic(open + call_id_section) + p.space() + args;
+ matched_atomic = true;
+ } else if (atomic_peek.has_value()) {
+ func_parser = p.atomic(open + call_id_section + p.space() + *atomic_peek) + args;
+ matched_atomic = true;
+ } else {
+ func_parser = open + call_id_section + p.space() + args;
+ }
+
+ if (!function.close.empty()) {
+ func_parser = func_parser + p.space() + p.tool_close(p.literal(function.close));
+ } else if (!format.per_call_end.empty()) {
+ // When there's no func_close but there is a per_call_end marker, use peek() to ensure
+ // we only emit tool_close when we can actually see the closing marker. This prevents
+ // premature closing during partial parsing when we've seen e.g. "</" which could be
+ // either "</tool_call>" (end) or "<arg_key>" prefix that failed to match.
+ func_parser = func_parser + p.tool_close(p.peek(p.literal(format.per_call_end)));
+ } else {
+ func_parser = func_parser + p.tool_close(p.space()); // force this to process tool closing callbacks in mapper
+ }
+ if (!matched_atomic) {
+ func_parser = p.atomic(func_parser);
+ }
+ return func_parser;
+}
+
common_peg_parser analyze_tools::build_tool_parser_tag_json(parser_build_context & ctx) const {
auto & p = ctx.p;
const auto & inputs = ctx.inputs;
const auto & schema = func.contains("parameters") ? func.at("parameters") : json::object();
// Build call_id parser based on position (if supported)
+ bool have_call_id = false;
common_peg_parser call_id_section = p.eps();
if (call_id.pos == call_id_position::BETWEEN_FUNC_AND_ARGS && !call_id.prefix.empty() &&
- !call_id.suffix.empty()) {
- call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix))) + call_id.suffix;
+ (!call_id.suffix.empty() || !arguments.start.empty())) {
+ if (!call_id.suffix.empty()) {
+ call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix))) + call_id.suffix;
+ } else {
+ call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(arguments.start)));
+ }
+ have_call_id = true;
}
-
- auto func_parser = p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix) +
- call_id_section + p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema));
- if (!function.close.empty()) {
- func_parser = func_parser + function.close;
+ auto args_parser = p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema));
+ if (!arguments.start.empty()) {
+ args_parser = p.literal(arguments.start) + args_parser;
+ }
+ if (!arguments.end.empty()) {
+ args_parser = args_parser + p.literal(arguments.end);
}
+
+ auto atomic_peek = !arguments.start.empty() ? std::optional(p.peek(p.literal(arguments.start))) : std::nullopt;
+ auto func_parser = build_func_parser(p, name, call_id_section, have_call_id, args_parser, atomic_peek);
tool_choice |= p.rule("tool-" + name, func_parser);
});
args_seq = args_seq + p.repeat(p.space() + any_opt, 0, (int) optional_parsers.size());
}
+ if (!arguments.start.empty()) {
+ args_seq = p.literal(arguments.start) + args_seq;
+ }
+ if (!arguments.end.empty()) {
+ args_seq = args_seq + p.literal(arguments.end);
+ }
+
// Build call_id parser based on position (if supported)
common_peg_parser call_id_section = p.eps();
bool have_call_id = false;
if (call_id.pos == call_id_position::BETWEEN_FUNC_AND_ARGS && !call_id.prefix.empty() &&
- !call_id.suffix.empty()) {
+ (!call_id.suffix.empty() || !arguments.start.empty())) {
have_call_id = true;
- call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix)) + call_id.suffix);
- }
-
- bool matched_atomic = false;
- common_peg_parser func_parser = p.eps();
- if (!function.name_suffix.empty()) {
- func_parser = p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix) +
- call_id_section + p.space() + args_seq;
- matched_atomic = true;
- } else if (have_call_id) {
- func_parser = p.atomic(p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix) +
- call_id_section) + p.space() + args_seq;
- matched_atomic = true;
- } else if (!arguments.name_prefix.empty() && !required_parsers.empty()) {
- // Only peek for an arg tag when there are required args that must follow.
- // When all args are optional, the model may emit no arg tags at all (#20650).
- func_parser = p.atomic(p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix) +
- call_id_section + p.space() + p.peek(p.literal(arguments.name_prefix))) + args_seq;
- matched_atomic = true;
- } else {
- func_parser = p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix) +
- call_id_section + p.space() + args_seq;
- }
-
- if (!function.close.empty()) {
- func_parser = func_parser + p.space() + p.tool_close(p.literal(function.close));
- } else if (!format.per_call_end.empty()) {
- // When there's no func_close but there is a per_call_end marker, use peek() to ensure
- // we only emit tool_close when we can actually see the closing marker. This prevents
- // premature closing during partial parsing when we've seen e.g. "</" which could be
- // either "</tool_call>" (end) or "<arg_key>" prefix that failed to match.
- func_parser = func_parser + p.tool_close(p.peek(p.literal(format.per_call_end)));
- } else {
- func_parser =
- func_parser + p.tool_close(p.space()); // force this to process tool closing callbacks in mapper
- }
- if (!matched_atomic) {
- func_parser = p.atomic(func_parser);
+ if (!call_id.suffix.empty()) {
+ call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix)) + call_id.suffix);
+ } else {
+ call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(arguments.start)));
+ }
}
+ // Only peek for an arg tag when there are required args that must follow.
+ // When all args are optional, the model may emit no arg tags at all (#20650).
+ auto atomic_peek = (!arguments.name_prefix.empty() && !required_parsers.empty()) ?
+ std::optional(p.peek(p.literal(arguments.name_prefix))) : std::nullopt;
+ auto func_parser = build_func_parser(p, name, call_id_section, have_call_id, args_seq, atomic_peek);
tool_choice |= p.rule("tool-" + name, func_parser);
});
common_peg_parser build_tool_parser_json_native(parser_build_context & ctx) const;
common_peg_parser build_tool_parser_tag_json(parser_build_context & ctx) const;
common_peg_parser build_tool_parser_tag_tagged(parser_build_context & ctx) const;
+
+ // Shared helper: builds func_parser from open+call_id+args, handling atomic wrapping and close.
+ // atomic_peek: if present, used as the peek expression in the third atomicity branch.
+ common_peg_parser build_func_parser(common_chat_peg_builder & p, const std::string & name,
+ const common_peg_parser & call_id_section, bool have_call_id,
+ const common_peg_parser & args,
+ std::optional<common_peg_parser> atomic_peek) const;
common_peg_parser build_tool_parser_tag_gemma4_dict(parser_build_context & ctx) const;
};
static const std::string USER_MSG = "U_USER_MSG Hello END_U";
static const std::string ASSISTANT_MSG = "A_ASST_MSG I can help END_A";
static const std::string THINKING_CONTENT = "REASON_PART I am thinking END_R";
+static const std::string CALL_ID_001 = "call00001";
+static const std::string CALL_ID_002 = "call00002";
+static const std::string CALL_ID_999 = "call99999";
static std::vector<std::function<void(const common_chat_template & tmpl, autoparser &)>> workarounds(
{ // Old reasoning Qwen templates - they don't really display reasoning content, but we still want to
analysis.tools.function.name_prefix = "<|tool▁sep|>";
analysis.tools.format.per_call_end = "<|tool▁call▁end|>";
analysis.tools.function.close = "```";
+ LOG_DBG(ANSI_ORANGE "[Patch: DeepSeek-R1-Distill-Qwen]\n" ANSI_RESET);
}
}
});
{ "content", USER_MSG }
};
-static json build_tool_call(const std::string & name, const json & args, const std::string & id = "call00001") {
+static json build_tool_call(const std::string & name, const json & args, const std::string & id = CALL_ID_001) {
return json{
{ "id", id },
{ "type", "function" },
};
}
-static json first_tool_call_zero_args = build_tool_call(FUN_FIRST, json::object(), "call00001");
-static json first_tool_call_one_arg = build_tool_call(FUN_FIRST, {{ ARG_FIRST, "XXXX" }}, "call00001");
-static json first_tool_call_one_arg_other_val = build_tool_call(FUN_FIRST, {{ ARG_FIRST, "YYYY" }}, "call00001");
-static json first_tool_call_other_arg = build_tool_call(FUN_FIRST, {{ ARG_SECOND, "YYYY" }}, "call00001");
+static json first_tool_call_zero_args = build_tool_call(FUN_FIRST, json::object(), CALL_ID_001);
+static json first_tool_call_one_arg = build_tool_call(FUN_FIRST, {{ ARG_FIRST, "XXXX" }}, CALL_ID_001);
+static json first_tool_call_one_arg_other_val = build_tool_call(FUN_FIRST, {{ ARG_FIRST, "YYYY" }}, CALL_ID_001);
+static json first_tool_call_other_arg = build_tool_call(FUN_FIRST, {{ ARG_SECOND, "YYYY" }}, CALL_ID_001);
static json first_tool_call =
- build_tool_call(FUN_FIRST, json{{ ARG_FIRST, "XXXX" }, { ARG_SECOND, "YYYY" }}, "call00001");
+ build_tool_call(FUN_FIRST, json{{ ARG_FIRST, "XXXX" }, { ARG_SECOND, "YYYY" }}, CALL_ID_001);
static json second_tool_call =
- build_tool_call(FUN_SECOND, json{ { ARG_FIRST, "XXXX" }, { ARG_SECOND, "YYYY" }}, "call00002");
+ build_tool_call(FUN_SECOND, json{ { ARG_FIRST, "XXXX" }, { ARG_SECOND, "YYYY" }}, CALL_ID_002);
static json first_tool_call_alt_id =
- build_tool_call(FUN_FIRST, json{{ ARG_FIRST, "XXXX" }, { ARG_SECOND, "YYYY" }}, "call99999");
+ build_tool_call(FUN_FIRST, json{{ ARG_FIRST, "XXXX" }, { ARG_SECOND, "YYYY" }}, CALL_ID_999);
template <typename T>
static std::string mode_to_str(T mode) {
LOG_DBG("func_name_prefix: '%s'\n", tools.function.name_prefix.c_str());
LOG_DBG("func_name_suffix: '%s'\n", tools.function.name_suffix.c_str());
LOG_DBG("func_close: '%s'\n", tools.function.close.c_str());
+ LOG_DBG("call_id_prefix: '%s'\n", tools.call_id.prefix.c_str());
+ LOG_DBG("call_id_suffix: '%s'\n", tools.call_id.suffix.c_str());
+ LOG_DBG("call_id_pos: '%s'\n", mode_to_str(tools.call_id.pos).c_str());
+ LOG_DBG("args_start: '%s'\n", tools.arguments.start.c_str());
+ LOG_DBG("args_end: '%s'\n", tools.arguments.end.c_str());
LOG_DBG("arg_name_prefix: '%s'\n", tools.arguments.name_prefix.c_str());
LOG_DBG("arg_name_suffix: '%s'\n", tools.arguments.name_suffix.c_str());
LOG_DBG("arg_value_prefix: '%s'\n", tools.arguments.value_prefix.c_str());
if (caps.supports_parallel_tool_calls) {
check_per_call_markers();
}
+ LOG_DBG(ANSI_ORANGE "Phase 3a: Function call analysis\n" ANSI_RESET);
extract_function_markers();
+ LOG_DBG(ANSI_ORANGE "Phase 3b: Argument analysis\n" ANSI_RESET);
if (format.mode == tool_format::TAG_WITH_TAGGED) {
analyze_arguments();
}
extract_argument_separator();
extract_args_markers();
+ LOG_DBG(ANSI_ORANGE "Phase 3c: Call id analysis\n" ANSI_RESET);
extract_call_id_markers();
}
}
}
void analyze_tools::analyze_arguments() {
- LOG_DBG(ANSI_ORANGE "Phase 4: Argument analysis\n" ANSI_RESET);
-
extract_argument_name_markers();
extract_argument_value_markers();
}
const auto & diff = comparison->diff;
- if (format.mode != tool_format::JSON_NATIVE) {
+ if (format.mode == tool_format::JSON_NATIVE) {
std::string prefix_marker = !format.section_start.empty() ? format.section_start : format.per_call_start;
std::string suffix_marker = !format.section_end.empty() ? format.section_end : format.per_call_end;
// these might happen earlier in the tools section as an example or somewhere else, so we need to find the closest ones
if (find_fun != std::string::npos) {
args_start = args_start.substr(find_fun + FUN_FIRST.size(), args_start.size() - find_fun - FUN_FIRST.size());
}
+ size_t find_call_id = args_start.find(CALL_ID_001);
+ if (find_call_id != std::string::npos) {
+ args_start = args_start.substr(find_call_id + CALL_ID_001.size(), args_start.size() - find_call_id - CALL_ID_001.size());
+ }
arguments.start = args_start;
arguments.end = args_end;
}
return;
}
- std::string id_value_1 = "call00001";
- std::string id_value_2 = "call99999";
+ std::string id_value_1 = CALL_ID_001;
+ std::string id_value_2 = CALL_ID_999;
size_t common_id_prefix_len = 0;
for (size_t i = 0; i < std::min(id_value_1.length(), id_value_2.length()); i++) {
call_id.suffix = find_first_marker(before_func);
}
+ if (call_id.prefix == arguments.end) {
+ call_id.prefix = "";
+ }
+
+ if (call_id.suffix == arguments.start) {
+ call_id.suffix = "";
+ }
+
// When call_id is detected, per_call_end may have been incorrectly set to include
// the call_id_suffix and sample args. Clear it if it starts with call_id_suffix.
if (call_id.pos != call_id_position::NONE && !call_id.suffix.empty() &&
return ctx;
}
-static std::optional<common_chat_params> try_specialized_template(
+std::optional<common_chat_params> common_chat_try_specialized_template(
const common_chat_template & tmpl,
const std::string & src,
const autoparser::generation_params & params) {
return data;
}
- if (auto result = try_specialized_template(tmpl, src, params)) {
+ if (auto result = common_chat_try_specialized_template(tmpl, src, params)) {
result->generation_prompt = params.generation_prompt;
return *result;
}
std::string common_chat_template_direct_apply(
const common_chat_template & tmpl,
const autoparser::generation_params & inputs);
+
+std::optional<common_chat_params> common_chat_try_specialized_template(
+ const common_chat_template & tmpl,
+ const std::string & src,
+ const autoparser::generation_params & params);
// #20424 introduced effective_input = generation_prompt + input, but the throw
// uses input.substr(result.end) where result.end is in effective_input space.
{
- auto tmpls = common_chat_templates_ptr(
- common_chat_templates_init(nullptr, read_file("models/templates/GLM-4.7-Flash.jinja")));
+ if (!g_template_filter.empty() && std::string("models/templates/GLM-4.7-Flash.jinja").find(g_template_filter) != std::string::npos) {
+ auto tmpls = common_chat_templates_ptr(
+ common_chat_templates_init(nullptr, read_file("models/templates/GLM-4.7-Flash.jinja")));
- static common_chat_tool weather_tool{
- "get_weather", "Get weather",
- R"({"type":"object","properties":{"city":{"type":"string"}},"required":["city"]})",
- };
-
- common_chat_templates_inputs inputs;
- inputs.tools = { weather_tool };
- inputs.enable_thinking = true;
- inputs.reasoning_format = COMMON_REASONING_FORMAT_AUTO;
- inputs.add_generation_prompt = true;
- inputs.use_jinja = true;
- common_chat_msg msg;
- msg.role = "user";
- msg.content = "get_weather";
- inputs.messages = { msg };
+ static common_chat_tool weather_tool{
+ "get_weather", "Get weather",
+ R"({"type":"object","properties":{"city":{"type":"string"}},"required":["city"]})",
+ };
- auto params = common_chat_templates_apply(tmpls.get(), inputs);
- common_peg_arena arena;
- arena.load(params.parser);
- common_chat_parser_params pp(params);
-
- // generation_prompt is non-empty for thinking models, so result.end
- // will be offset by generation_prompt.size() into effective_input space.
- assert(!pp.generation_prompt.empty());
-
- std::string bad_input =
- "Thinking.\n"
- "</think>"
- "<tool_call>get_weather"
- "<arg_key>city</arg_key><arg_value>Tokyo</arg_value>"
- "</tool_call>\n";
-
- bool got_runtime_error = false;
- bool got_out_of_range = false;
- std::string error_msg;
- try {
- common_chat_peg_parse(arena, bad_input, /*is_partial=*/false, pp);
- } catch (const std::out_of_range & e) {
- got_out_of_range = true;
- error_msg = e.what();
- } catch (const std::runtime_error & e) {
- got_runtime_error = true;
- error_msg = e.what();
+ common_chat_templates_inputs inputs;
+ inputs.tools = { weather_tool };
+ inputs.enable_thinking = true;
+ inputs.reasoning_format = COMMON_REASONING_FORMAT_AUTO;
+ inputs.add_generation_prompt = true;
+ inputs.use_jinja = true;
+ common_chat_msg msg;
+ msg.role = "user";
+ msg.content = "get_weather";
+ inputs.messages = { msg };
+
+ auto params = common_chat_templates_apply(tmpls.get(), inputs);
+ common_peg_arena arena;
+ arena.load(params.parser);
+ common_chat_parser_params pp(params);
+
+ // generation_prompt is non-empty for thinking models, so result.end
+ // will be offset by generation_prompt.size() into effective_input space.
+ assert(!pp.generation_prompt.empty());
+
+ std::string bad_input =
+ "Thinking.\n"
+ "</think>"
+ "<tool_call>get_weather"
+ "<arg_key>city</arg_key><arg_value>Tokyo</arg_value>"
+ "</tool_call>\n";
+
+ bool got_runtime_error = false;
+ bool got_out_of_range = false;
+ std::string error_msg;
+ try {
+ common_chat_peg_parse(arena, bad_input, /*is_partial=*/false, pp);
+ } catch (const std::out_of_range & e) {
+ got_out_of_range = true;
+ error_msg = e.what();
+ } catch (const std::runtime_error & e) {
+ got_runtime_error = true;
+ error_msg = e.what();
+ }
+ GGML_ASSERT(!got_out_of_range && "throw path crashed with out_of_range (input.substr in effective_input space)");
+ GGML_ASSERT(got_runtime_error && "throw path should produce std::runtime_error with parse position");
}
- GGML_ASSERT(!got_out_of_range && "throw path crashed with out_of_range (input.substr in effective_input space)");
- GGML_ASSERT(got_runtime_error && "throw path should produce std::runtime_error with parse position");
}
// Kimi-K2-Thinking tests - custom parser
.expect(message_assist_call_id)
.expect_reconstruction()
.run();
+
+ tst.test("[TOOL_CALLS]special_function[CALL_ID]000000001[ARGS]{\"arg1\": 1}"
+ "[TOOL_CALLS]special_function_with_opt[CALL_ID]000000002[ARGS]{\"arg1\": 1, \"arg2\": 2}")
+ .parallel_tool_calls(true)
+ .tools({
+ special_function_tool, special_function_tool_with_optional_param
+ })
+ .expect_tool_calls({
+ { "special_function", R"({"arg1": 1})", "000000001" },
+ { "special_function_with_opt", R"({"arg1": 1, "arg2": 2})", "000000002" },
+ })
+ .expect_reconstruction()
+ .run();
+
+
}
// Devstral
{
#include "gguf.h"
#include "jinja/runtime.h"
#include "log.h"
+#include "nlohmann/json.hpp"
+#include "peg-parser.h"
#include <fstream>
#include <numeric>
+#include <optional>
#include <sstream>
#include <string>
-#include "nlohmann/json.hpp"
-#include "peg-parser.h"
-
using json = nlohmann::ordered_json;
enum class output_mode {
};
struct debug_options {
- std::string template_path;
- bool with_tools = true;
- bool generation_prompt = true;
- bool enable_reasoning = true;
- bool debug_jinja = false;
- bool force_tool_call = false;
- output_mode mode = output_mode::BOTH;
- input_message_type input_message = input_message_type::NONE;
+ std::string template_path;
+ bool with_tools = true;
+ bool generation_prompt = true;
+ bool enable_reasoning = true;
+ bool debug_jinja = false;
+ bool force_tool_call = false;
+ output_mode mode = output_mode::BOTH;
+ input_message_type input_message = input_message_type::NONE;
};
static std::string read_file(const std::string & path) {
json final_messages = messages;
if (add_generation_prompt && !messages.empty() && messages.back().value("role", "") == "assistant") {
final_messages.push_back(json{
- { "role", "user" },
+ { "role", "user" },
{ "content", "Now please continue with another response." }
});
}
const json & tools,
bool add_generation_prompt,
bool enable_thinking,
- input_message_type message_type) {
+ input_message_type message_type) {
json user_msg = build_user_message();
auto render_if = [&](input_message_type type, const std::string & name, const json & assistant_msg) {
}
}
+static autoparser::generation_params prepare_params(const debug_options & opts, const json & tools) {
+ autoparser::generation_params params;
+ params.messages = json::array({ build_user_message() });
+ params.reasoning_format = opts.enable_reasoning ? COMMON_REASONING_FORMAT_DEEPSEEK : COMMON_REASONING_FORMAT_NONE;
+ params.enable_thinking = opts.enable_reasoning;
+ params.add_generation_prompt = opts.generation_prompt;
+
+ if (opts.with_tools) {
+ params.tools = tools;
+ params.tool_choice = opts.force_tool_call ? COMMON_CHAT_TOOL_CHOICE_REQUIRED : COMMON_CHAT_TOOL_CHOICE_AUTO;
+ } else {
+ params.tools = json();
+ params.tool_choice = COMMON_CHAT_TOOL_CHOICE_NONE;
+ }
+ params.parallel_tool_calls = false;
+ return params;
+}
+
int main(int argc, char ** argv) {
// Set log level to most verbose to capture all debug output
common_log_set_verbosity_thold(99);
try {
common_chat_template chat_template(template_source, "", "");
- // Build tools definition
json tools = opts.with_tools ? build_tools_definition() : json();
- // Render template scenarios if requested
- if (opts.input_message != input_message_type::NONE &&
- (opts.mode == output_mode::TEMPLATE || opts.mode == output_mode::BOTH)) {
+ autoparser::generation_params params = prepare_params(opts, tools);
+ common_chat_params parser_data;
+ if (std::optional<common_chat_params> spec_tmpl =
+ common_chat_try_specialized_template(chat_template, template_source, params)) {
LOG_ERR("\n");
- LOG_ERR("================================================================================\n");
- LOG_ERR(" TEMPLATE RENDERING OUTPUT\n");
- LOG_ERR("================================================================================\n");
+ LOG_ERR("This template uses a specialized parser, analysis results will not be available.");
+ parser_data = *spec_tmpl;
+ } else {
+ // Render template scenarios if requested
+ if (opts.input_message != input_message_type::NONE &&
+ (opts.mode == output_mode::TEMPLATE || opts.mode == output_mode::BOTH)) {
+ LOG_ERR("\n");
+ LOG_ERR("================================================================================\n");
+ LOG_ERR(" TEMPLATE RENDERING OUTPUT\n");
+ LOG_ERR("================================================================================\n");
+
+ render_all_scenarios(chat_template, tools, opts.generation_prompt, opts.enable_reasoning,
+ opts.input_message);
+ }
- render_all_scenarios(chat_template, tools, opts.generation_prompt, opts.enable_reasoning,
- opts.input_message);
- }
+ // Output analysis if requested
+ if (opts.mode == output_mode::ANALYSIS || opts.mode == output_mode::BOTH) {
+ LOG_ERR("\n");
+ LOG_ERR("================================================================================\n");
+ LOG_ERR(" TEMPLATE ANALYSIS\n");
+ LOG_ERR("================================================================================\n");
- // Output analysis if requested
- if (opts.mode == output_mode::ANALYSIS || opts.mode == output_mode::BOTH) {
- LOG_ERR("\n");
- LOG_ERR("================================================================================\n");
- LOG_ERR(" TEMPLATE ANALYSIS\n");
- LOG_ERR("================================================================================\n");
-
- autoparser::autoparser analysis;
- analysis.analyze_template(chat_template);
-
- // Generate Parser
- autoparser::generation_params params;
- params.messages = json::array({ build_user_message() });
- params.reasoning_format =
- opts.enable_reasoning ? COMMON_REASONING_FORMAT_DEEPSEEK : COMMON_REASONING_FORMAT_NONE;
- params.enable_thinking = opts.enable_reasoning;
- params.add_generation_prompt = opts.generation_prompt;
-
- if (opts.with_tools) {
- params.tools = tools;
- params.tool_choice = opts.force_tool_call ? COMMON_CHAT_TOOL_CHOICE_REQUIRED : COMMON_CHAT_TOOL_CHOICE_AUTO;
- } else {
- params.tools = json();
- params.tool_choice = COMMON_CHAT_TOOL_CHOICE_NONE;
- }
- params.parallel_tool_calls = false;
+ autoparser::autoparser analysis;
+ analysis.analyze_template(chat_template);
- auto parser_data = autoparser::peg_generator::generate_parser(chat_template, params, analysis);
+ // Generate Parser
+ parser_data = autoparser::peg_generator::generate_parser(chat_template, params, analysis);
+ }
LOG_ERR("\n=== Generated Parser ===\n");
common_peg_arena arena;