-> Refactor autoparser analyzer structure
-> Fix content truncation -> Fix errors in capability detection due to non-empty assistant message -> Add missing debug prints for Jinja
This commit is contained in:
parent
822fd2bee9
commit
28fcef67c0
|
|
@ -30,8 +30,8 @@ common_chat_params universal_peg_generator::generate_parser(const common_chat_te
|
|||
const struct templates_params & inputs,
|
||||
const diff_analysis_result & analysis) {
|
||||
// Check for thinking forced open
|
||||
bool thinking_forced_open = (analysis.reasoning == reasoning_mode::FORCED_OPEN);
|
||||
bool thinking_forced_closed = (analysis.reasoning == reasoning_mode::FORCED_CLOSED);
|
||||
bool thinking_forced_open = (analysis.reasoning.mode == reasoning_mode::FORCED_OPEN);
|
||||
bool thinking_forced_closed = (analysis.reasoning.mode == reasoning_mode::FORCED_CLOSED);
|
||||
|
||||
// Build the parser using the analysis results
|
||||
auto parser = build_parser(analysis, inputs, thinking_forced_open, thinking_forced_closed);
|
||||
|
|
@ -44,7 +44,7 @@ common_chat_params universal_peg_generator::generate_parser(const common_chat_te
|
|||
data.parser = parser.save();
|
||||
|
||||
// Build grammar if tools are present
|
||||
bool has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
bool has_tools = analysis.tools.format.mode != tool_format::NONE && inputs.tools.is_array() && !inputs.tools.empty();
|
||||
bool include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
|
||||
|
||||
if (include_grammar) {
|
||||
|
|
@ -60,9 +60,9 @@ common_chat_params universal_peg_generator::generate_parser(const common_chat_te
|
|||
});
|
||||
|
||||
// Set grammar triggers based on tool section markers (fall back to per-call markers)
|
||||
std::string trigger_marker = !analysis.markers.tool_section_start.empty()
|
||||
? analysis.markers.tool_section_start
|
||||
: analysis.markers.per_call_start;
|
||||
std::string trigger_marker = !analysis.tools.format.section_start.empty()
|
||||
? analysis.tools.format.section_start
|
||||
: analysis.tools.format.per_call_start;
|
||||
if (!trigger_marker.empty()) {
|
||||
data.grammar_triggers = {
|
||||
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, trigger_marker }
|
||||
|
|
@ -79,26 +79,24 @@ common_peg_arena universal_peg_generator::build_parser(const diff_analysis_resul
|
|||
bool thinking_forced_closed) {
|
||||
return build_chat_peg_unified_parser([&](common_chat_peg_unified_builder & p) {
|
||||
p.set_allow_python_dict_format(true);
|
||||
const auto & m = analysis.markers;
|
||||
|
||||
common_peg_parser reasoning = p.eps();
|
||||
bool extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
|
||||
bool enable_thinking = inputs.enable_thinking;
|
||||
|
||||
if (extract_reasoning && enable_thinking && analysis.reasoning != reasoning_mode::NONE) {
|
||||
if (extract_reasoning && enable_thinking && analysis.reasoning.mode != reasoning_mode::NONE) {
|
||||
if (thinking_forced_open || thinking_forced_closed) {
|
||||
// Thinking is forced open OR forced closed with enable_thinking=true
|
||||
// In both cases, expect only the closing tag (opening was in template)
|
||||
reasoning = p.reasoning(p.until(m.reasoning_end)) + m.reasoning_end;
|
||||
} else if (analysis.reasoning == reasoning_mode::TAG_BASED ||
|
||||
analysis.reasoning == reasoning_mode::TOOLS_ONLY) {
|
||||
reasoning = p.reasoning(p.until(analysis.reasoning.end)) + analysis.reasoning.end;
|
||||
} else if (analysis.reasoning.mode == reasoning_mode::TAG_BASED ||
|
||||
analysis.reasoning.mode == reasoning_mode::TOOLS_ONLY) {
|
||||
// Standard tag-based reasoning OR tools-only mode (reasoning appears with tools)
|
||||
// Both use the same tag-based pattern if markers are available
|
||||
if (!m.reasoning_start.empty() && !m.reasoning_end.empty()) {
|
||||
reasoning = p.optional(m.reasoning_start + p.reasoning(p.until(m.reasoning_end)) + m.reasoning_end);
|
||||
if (!analysis.reasoning.start.empty() && !analysis.reasoning.end.empty()) {
|
||||
reasoning = p.optional(analysis.reasoning.start + p.reasoning(p.until(analysis.reasoning.end)) + analysis.reasoning.end);
|
||||
}
|
||||
} else if (analysis.reasoning == reasoning_mode::DELIMITER) {
|
||||
reasoning = p.optional(p.reasoning(p.until(m.reasoning_end)) + m.reasoning_end);
|
||||
} else if (analysis.reasoning.mode == reasoning_mode::DELIMITER) {
|
||||
reasoning = p.optional(p.reasoning(p.until(analysis.reasoning.end)) + analysis.reasoning.end);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,19 +107,19 @@ common_peg_arena universal_peg_generator::build_parser(const diff_analysis_resul
|
|||
return reasoning + p.space() + p.content(p.schema(p.json(), "response-format", inputs.json_schema)) + p.end();
|
||||
}
|
||||
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE && analysis.supports_tools) {
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE && analysis.jinja_caps.supports_tool_calls) {
|
||||
return build_tool_parser(p, analysis, inputs, reasoning);
|
||||
}
|
||||
|
||||
if (analysis.content == content_mode::ALWAYS_WRAPPED &&
|
||||
!m.content_start.empty() && !m.content_end.empty()) {
|
||||
if (analysis.content.mode == content_mode::ALWAYS_WRAPPED &&
|
||||
!analysis.content.start.empty() && !analysis.content.end.empty()) {
|
||||
|
||||
bool extracting_reasoning = extract_reasoning && enable_thinking && analysis.reasoning != reasoning_mode::NONE;
|
||||
bool extracting_reasoning = extract_reasoning && enable_thinking && analysis.reasoning.mode != reasoning_mode::NONE;
|
||||
|
||||
if (extracting_reasoning) {
|
||||
return reasoning + m.content_start + p.content(p.until(m.content_end)) + m.content_end + p.end();
|
||||
return reasoning + analysis.content.start + p.content(p.until(analysis.content.end)) + analysis.content.end + p.end();
|
||||
}
|
||||
return p.content(p.until(m.content_start)) + m.content_start + p.content(p.until(m.content_end)) + m.content_end + p.end();
|
||||
return p.content(p.until(analysis.content.start)) + analysis.content.start + p.content(p.until(analysis.content.end)) + analysis.content.end + p.end();
|
||||
}
|
||||
return reasoning + p.content(p.rest()) + p.end();
|
||||
});
|
||||
|
|
@ -133,7 +131,7 @@ common_peg_parser universal_peg_generator::build_tool_parser(
|
|||
const templates_params & inputs,
|
||||
const common_peg_parser & reasoning) {
|
||||
|
||||
switch (analysis.tools) {
|
||||
switch (analysis.tools.format.mode) {
|
||||
case tool_format::JSON_NATIVE:
|
||||
return build_tool_parser_json_native(p, analysis, inputs, reasoning);
|
||||
case tool_format::TAG_WITH_JSON:
|
||||
|
|
@ -151,42 +149,40 @@ common_peg_parser universal_peg_generator::build_tool_parser_json_native(
|
|||
const templates_params & inputs,
|
||||
const common_peg_parser & reasoning) {
|
||||
|
||||
const auto & m = analysis.markers;
|
||||
|
||||
// Build effective field names with dot notation if function_field is set
|
||||
std::string name_field = analysis.name_field;
|
||||
std::string args_field = analysis.args_field;
|
||||
std::string name_field = analysis.tools.format.name_field;
|
||||
std::string args_field = analysis.tools.format.args_field;
|
||||
|
||||
if (!analysis.function_field.empty() &&
|
||||
analysis.function_field != "function" &&
|
||||
if (!analysis.tools.format.function_field.empty() &&
|
||||
analysis.tools.format.function_field != "function" &&
|
||||
name_field.find('.') == std::string::npos) {
|
||||
name_field = analysis.function_field + "." + name_field;
|
||||
args_field = analysis.function_field + "." + args_field;
|
||||
name_field = analysis.tools.format.function_field + "." + name_field;
|
||||
args_field = analysis.tools.format.function_field + "." + args_field;
|
||||
}
|
||||
|
||||
auto tools_parser = p.standard_json_tools(
|
||||
m.tool_section_start,
|
||||
m.tool_section_end,
|
||||
analysis.tools.format.section_start,
|
||||
analysis.tools.format.section_end,
|
||||
inputs.tools,
|
||||
inputs.parallel_tool_calls,
|
||||
inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED,
|
||||
name_field,
|
||||
args_field,
|
||||
analysis.tools_array_wrapped,
|
||||
analysis.fun_name_is_key,
|
||||
analysis.id_field,
|
||||
analysis.gen_id_field,
|
||||
analysis.parameter_order
|
||||
analysis.tools.format.tools_array_wrapped,
|
||||
analysis.tools.format.fun_name_is_key,
|
||||
analysis.tools.format.id_field,
|
||||
analysis.tools.format.gen_id_field,
|
||||
analysis.tools.format.parameter_order
|
||||
);
|
||||
|
||||
// Handle content wrappers if present
|
||||
if (analysis.content == content_mode::ALWAYS_WRAPPED &&
|
||||
!m.content_start.empty() && !m.content_end.empty()) {
|
||||
auto wrapped_content = p.optional(m.content_start + p.content(p.until(m.content_end)) + m.content_end);
|
||||
if (analysis.content.mode == content_mode::ALWAYS_WRAPPED &&
|
||||
!analysis.content.start.empty() && !analysis.content.end.empty()) {
|
||||
auto wrapped_content = p.optional(analysis.content.start + p.content(p.until(analysis.content.end)) + analysis.content.end);
|
||||
return reasoning + wrapped_content + tools_parser + p.end();
|
||||
}
|
||||
|
||||
auto content_before_tools = m.tool_section_start.empty() ? p.eps() : p.until(m.tool_section_start);
|
||||
auto content_before_tools = analysis.tools.format.section_start.empty() ? p.eps() : p.until(analysis.tools.format.section_start);
|
||||
return reasoning + p.optional(p.content(content_before_tools)) + tools_parser + p.end();
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +192,6 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_json(
|
|||
const templates_params & inputs,
|
||||
const common_peg_parser & reasoning) {
|
||||
|
||||
const auto & m = analysis.markers;
|
||||
common_peg_parser tool_choice = p.choice();
|
||||
|
||||
foreach_function(inputs.tools, [&](const json & tool) {
|
||||
|
|
@ -206,17 +201,17 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_json(
|
|||
|
||||
// Build call_id parser based on position (if supported)
|
||||
common_peg_parser call_id_section = p.eps();
|
||||
if (analysis.call_id_pos == call_id_position::BETWEEN_FUNC_AND_ARGS &&
|
||||
!m.call_id_prefix.empty() && !m.call_id_suffix.empty()) {
|
||||
call_id_section = p.optional(m.call_id_prefix + p.tool_id(p.until(m.call_id_suffix))) + m.call_id_suffix;
|
||||
if (analysis.tools.call_id.pos == call_id_position::BETWEEN_FUNC_AND_ARGS &&
|
||||
!analysis.tools.call_id.prefix.empty() && !analysis.tools.call_id.suffix.empty()) {
|
||||
call_id_section = p.optional(analysis.tools.call_id.prefix + p.tool_id(p.until(analysis.tools.call_id.suffix))) + analysis.tools.call_id.suffix;
|
||||
}
|
||||
|
||||
auto func_parser = p.tool_open(m.func_name_prefix + p.tool_name(p.literal(name)) + m.func_name_suffix) +
|
||||
auto func_parser = p.tool_open(analysis.tools.function.name_prefix + p.tool_name(p.literal(name)) + analysis.tools.function.name_suffix) +
|
||||
call_id_section +
|
||||
p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema));
|
||||
|
||||
if (!m.func_close.empty()) {
|
||||
func_parser = func_parser + m.func_close;
|
||||
if (!analysis.tools.function.close.empty()) {
|
||||
func_parser = func_parser + analysis.tools.function.close;
|
||||
}
|
||||
|
||||
tool_choice |= p.rule("tool-" + name, func_parser);
|
||||
|
|
@ -226,30 +221,26 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_json(
|
|||
|
||||
common_peg_parser tool_calls = p.eps();
|
||||
|
||||
if (!m.per_call_start.empty()) {
|
||||
auto wrapped_call = m.per_call_start + tool_choice + m.per_call_end;
|
||||
if (!analysis.tools.format.per_call_start.empty()) {
|
||||
auto wrapped_call = analysis.tools.format.per_call_start + tool_choice + analysis.tools.format.per_call_end;
|
||||
if (inputs.parallel_tool_calls) {
|
||||
tool_calls = p.trigger_rule("tool-call",
|
||||
wrapped_call + p.zero_or_more(p.space() + wrapped_call));
|
||||
} else {
|
||||
tool_calls = p.trigger_rule("tool-call", wrapped_call);
|
||||
}
|
||||
if (!m.tool_section_start.empty()) {
|
||||
tool_calls = p.trigger_rule("tool-calls", p.literal(m.tool_section_start) + p.space() +
|
||||
tool_calls + p.space() + (m.tool_section_end.empty() ? p.end() : p.literal(m.tool_section_end)));
|
||||
if (!analysis.tools.format.section_start.empty()) {
|
||||
tool_calls = p.trigger_rule("tool-calls", p.literal(analysis.tools.format.section_start) + p.space() +
|
||||
tool_calls + p.space() + (analysis.tools.format.section_end.empty() ? p.end() : p.literal(analysis.tools.format.section_end)));
|
||||
}
|
||||
} else {
|
||||
std::string separator = m.call_separator;
|
||||
if (separator.empty()) {
|
||||
separator = ", "; // Default
|
||||
}
|
||||
|
||||
std::string separator = ", "; // Default
|
||||
if (inputs.parallel_tool_calls) {
|
||||
tool_calls = p.trigger_rule("tool-call",
|
||||
m.tool_section_start + tool_choice + p.zero_or_more(separator + tool_choice) + m.tool_section_end);
|
||||
analysis.tools.format.section_start + tool_choice + p.zero_or_more(separator + tool_choice) + analysis.tools.format.section_end);
|
||||
} else {
|
||||
tool_calls = p.trigger_rule("tool-call",
|
||||
m.tool_section_start + tool_choice + m.tool_section_end);
|
||||
analysis.tools.format.section_start + tool_choice + analysis.tools.format.section_end);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,7 +248,7 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_json(
|
|||
tool_calls = p.optional(tool_calls);
|
||||
}
|
||||
|
||||
std::string trigger_marker = !m.tool_section_start.empty() ? m.tool_section_start : m.per_call_start;
|
||||
std::string trigger_marker = !analysis.tools.format.section_start.empty() ? analysis.tools.format.section_start : analysis.tools.format.per_call_start;
|
||||
auto content_before_tools = trigger_marker.empty() ? p.eps() : p.until(trigger_marker);
|
||||
return reasoning + p.optional(p.content(content_before_tools)) + tool_calls + p.end();
|
||||
}
|
||||
|
|
@ -268,7 +259,6 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_tagged(
|
|||
const templates_params & inputs,
|
||||
const common_peg_parser & reasoning) {
|
||||
|
||||
const auto & m = analysis.markers;
|
||||
common_peg_parser tool_choice = p.choice();
|
||||
|
||||
foreach_function(inputs.tools, [&](const json & tool) {
|
||||
|
|
@ -293,13 +283,13 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_tagged(
|
|||
auto type = param_schema.value("type", "object");
|
||||
|
||||
auto arg = p.tool_arg(
|
||||
p.tool_arg_open(m.arg_name_prefix + p.tool_arg_name(p.literal(param_name)) + m.arg_name_suffix) + m.arg_value_prefix +
|
||||
p.tool_arg_open(analysis.tools.arguments.name_prefix + p.tool_arg_name(p.literal(param_name)) + analysis.tools.arguments.name_suffix) + analysis.tools.arguments.value_prefix +
|
||||
(type == "string" ?
|
||||
p.tool_arg_string_value(p.schema(p.until(m.arg_value_suffix),
|
||||
p.tool_arg_string_value(p.schema(p.until(analysis.tools.arguments.value_suffix),
|
||||
"tool-" + name + "-arg-" + param_name + "-schema", param_schema, true)) :
|
||||
p.tool_arg_json_value(p.schema(p.json(),
|
||||
"tool-" + name + "-arg-" + param_name + "-schema", param_schema)) + p.space()) +
|
||||
p.tool_arg_close(p.literal(m.arg_value_suffix))
|
||||
p.tool_arg_close(p.literal(analysis.tools.arguments.value_suffix))
|
||||
);
|
||||
|
||||
if (is_required) {
|
||||
|
|
@ -320,23 +310,23 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_tagged(
|
|||
|
||||
// Build call_id parser based on position (if supported)
|
||||
common_peg_parser call_id_section = p.eps();
|
||||
if (analysis.call_id_pos == call_id_position::BETWEEN_FUNC_AND_ARGS &&
|
||||
!m.call_id_prefix.empty() && !m.call_id_suffix.empty()) {
|
||||
call_id_section = p.optional(m.call_id_prefix + p.tool_id(p.until(m.call_id_suffix))) + m.call_id_suffix;
|
||||
if (analysis.tools.call_id.pos == call_id_position::BETWEEN_FUNC_AND_ARGS &&
|
||||
!analysis.tools.call_id.prefix.empty() && !analysis.tools.call_id.suffix.empty()) {
|
||||
call_id_section = p.optional(analysis.tools.call_id.prefix + p.tool_id(p.until(analysis.tools.call_id.suffix))) + analysis.tools.call_id.suffix;
|
||||
}
|
||||
|
||||
auto func_parser = p.tool_open(m.func_name_prefix + p.tool_name(p.literal(name)) + m.func_name_suffix) +
|
||||
auto func_parser = p.tool_open(analysis.tools.function.name_prefix + p.tool_name(p.literal(name)) + analysis.tools.function.name_suffix) +
|
||||
call_id_section +
|
||||
p.space() + args_seq;
|
||||
|
||||
if (!m.func_close.empty()) {
|
||||
func_parser = func_parser + p.space() + p.tool_close(p.literal(m.func_close));
|
||||
} else if (!m.per_call_end.empty()) {
|
||||
if (!analysis.tools.function.close.empty()) {
|
||||
func_parser = func_parser + p.space() + p.tool_close(p.literal(analysis.tools.function.close));
|
||||
} else if (!analysis.tools.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(m.per_call_end)));
|
||||
func_parser = func_parser + p.tool_close(p.peek(p.literal(analysis.tools.format.per_call_end)));
|
||||
} else {
|
||||
func_parser = func_parser + p.tool_close(p.space()); // force this to process tool closing callbacks in mapper
|
||||
}
|
||||
|
|
@ -348,29 +338,26 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_tagged(
|
|||
|
||||
common_peg_parser tool_calls = p.eps();
|
||||
|
||||
if (!m.per_call_start.empty()) {
|
||||
auto wrapped_call = m.per_call_start + p.space() + tool_choice + p.space() + m.per_call_end;
|
||||
if (!analysis.tools.format.per_call_start.empty()) {
|
||||
auto wrapped_call = analysis.tools.format.per_call_start + p.space() + tool_choice + p.space() + analysis.tools.format.per_call_end;
|
||||
if (inputs.parallel_tool_calls) {
|
||||
tool_calls = p.trigger_rule("tool-call", wrapped_call + p.zero_or_more(p.space() + wrapped_call));
|
||||
} else {
|
||||
tool_calls = p.trigger_rule("tool-call", wrapped_call);
|
||||
}
|
||||
if (!m.tool_section_start.empty()) {
|
||||
tool_calls = p.trigger_rule("tool-calls", p.literal(m.tool_section_start) + p.space() +
|
||||
tool_calls + p.space() + (m.tool_section_end.empty() ? p.end() : p.literal(m.tool_section_end)));
|
||||
if (!analysis.tools.format.section_start.empty()) {
|
||||
tool_calls = p.trigger_rule("tool-calls", p.literal(analysis.tools.format.section_start) + p.space() +
|
||||
tool_calls + p.space() + (analysis.tools.format.section_end.empty() ? p.end() : p.literal(analysis.tools.format.section_end)));
|
||||
}
|
||||
} else {
|
||||
std::string separator = m.call_separator;
|
||||
if (separator.empty()) {
|
||||
separator = ", "; // Default
|
||||
}
|
||||
std::string separator = ", "; // Default
|
||||
|
||||
if (inputs.parallel_tool_calls) {
|
||||
tool_calls = p.trigger_rule("tool-call",
|
||||
m.tool_section_start + p.space() + tool_choice + p.zero_or_more(separator + tool_choice) + p.space() + m.tool_section_end);
|
||||
analysis.tools.format.section_start + p.space() + tool_choice + p.zero_or_more(separator + tool_choice) + p.space() + analysis.tools.format.section_end);
|
||||
} else {
|
||||
tool_calls = p.trigger_rule("tool-call",
|
||||
m.tool_section_start + p.space() + tool_choice + p.space() + m.tool_section_end);
|
||||
analysis.tools.format.section_start + p.space() + tool_choice + p.space() + analysis.tools.format.section_end);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -378,7 +365,7 @@ common_peg_parser universal_peg_generator::build_tool_parser_tag_tagged(
|
|||
tool_calls = p.optional(tool_calls);
|
||||
}
|
||||
|
||||
std::string trigger_marker = !m.tool_section_start.empty() ? m.tool_section_start : m.per_call_start;
|
||||
std::string trigger_marker = !analysis.tools.format.section_start.empty() ? analysis.tools.format.section_start : analysis.tools.format.per_call_start;
|
||||
auto content_before_tools = trigger_marker.empty() ? p.eps() : p.until(trigger_marker);
|
||||
return reasoning + p.optional(p.content(content_before_tools)) + tool_calls + p.end();
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "chat.h"
|
||||
#include "jinja/caps.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
|
@ -15,11 +16,11 @@ using json = nlohmann::ordered_json;
|
|||
// Parameters for template application
|
||||
// ============================================================================
|
||||
struct template_params {
|
||||
json messages;
|
||||
json tools;
|
||||
bool add_generation_prompt = false;
|
||||
bool enable_thinking = true;
|
||||
std::optional<json> extra_context = std::nullopt;
|
||||
json messages;
|
||||
json tools;
|
||||
bool add_generation_prompt = false;
|
||||
bool enable_thinking = true;
|
||||
std::optional<json> extra_context = std::nullopt;
|
||||
};
|
||||
|
||||
struct diff_split {
|
||||
|
|
@ -35,9 +36,9 @@ struct diff_split {
|
|||
|
||||
// Result of compare_variants containing diff and original outputs
|
||||
struct compare_variants_result {
|
||||
diff_split diff;
|
||||
std::string output_A;
|
||||
std::string output_B;
|
||||
diff_split diff;
|
||||
std::string output_A;
|
||||
std::string output_B;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -77,29 +78,23 @@ struct marker_registry {
|
|||
std::string arg_separator; // e.g., "", "\n", ","
|
||||
|
||||
// === Call ID markers (for non-JSON formats with tool call IDs) ===
|
||||
std::string call_id_prefix; // e.g., "[CALL_ID]" (marker before call ID value)
|
||||
std::string call_id_suffix; // e.g., "" (marker after call ID value, before next section)
|
||||
|
||||
// === Special markers ===
|
||||
std::string code_block_marker; // e.g., "Action:" (for markdown code block format)
|
||||
std::string code_block_language; // e.g., "json"
|
||||
std::string function_namespace; // e.g., "functions." (for prefixed-indexed format)
|
||||
std::string call_id_prefix; // e.g., "[CALL_ID]" (marker before call ID value)
|
||||
std::string call_id_suffix; // e.g., "" (marker after call ID value, before next section)
|
||||
};
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Analysis Result Enums
|
||||
// ============================================================================
|
||||
|
||||
// Reasoning handling mode (derived from R1-R3 comparisons)
|
||||
enum class reasoning_mode {
|
||||
NONE, // No reasoning markers detected
|
||||
TAG_BASED, // Standard tag-based: <think>...</think>
|
||||
DELIMITER, // Delimiter-based: [BEGIN FINAL RESPONSE] (reasoning ends at delimiter)
|
||||
FORCED_OPEN, // Template ends with open reasoning tag (empty start, non-empty end)
|
||||
FORCED_CLOSED,// Template ends with open reasoning tag on enabled thinking but
|
||||
// with both opened and closed tag for disabled thinking
|
||||
TOOLS_ONLY // Only reason on tool calls, not on normal content
|
||||
NONE, // No reasoning markers detected
|
||||
TAG_BASED, // Standard tag-based: <think>...</think>
|
||||
DELIMITER, // Delimiter-based: [BEGIN FINAL RESPONSE] (reasoning ends at delimiter)
|
||||
FORCED_OPEN, // Template ends with open reasoning tag (empty start, non-empty end)
|
||||
FORCED_CLOSED, // Template ends with open reasoning tag on enabled thinking but
|
||||
// with both opened and closed tag for disabled thinking
|
||||
TOOLS_ONLY // Only reason on tool calls, not on normal content
|
||||
};
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os, const reasoning_mode & mode) {
|
||||
|
|
@ -143,10 +138,10 @@ inline std::ostream & operator<<(std::ostream & os, const content_mode & mode) {
|
|||
|
||||
// Call ID position in tool calls (for non-JSON formats)
|
||||
enum class call_id_position {
|
||||
NONE, // No call ID support detected
|
||||
PRE_FUNC_NAME, // Call ID before function name: [CALL_ID]id[FUNC]name{args}
|
||||
BETWEEN_FUNC_AND_ARGS, // Call ID between function and args: [FUNC]name[CALL_ID]id{args}
|
||||
POST_ARGS, // Call ID after arguments: [FUNC]name{args}[CALL_ID]id
|
||||
NONE, // No call ID support detected
|
||||
PRE_FUNC_NAME, // Call ID before function name: [CALL_ID]id[FUNC]name{args}
|
||||
BETWEEN_FUNC_AND_ARGS, // Call ID between function and args: [FUNC]name[CALL_ID]id{args}
|
||||
POST_ARGS, // Call ID after arguments: [FUNC]name{args}[CALL_ID]id
|
||||
};
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os, const call_id_position & pos) {
|
||||
|
|
@ -166,10 +161,10 @@ inline std::ostream & operator<<(std::ostream & os, const call_id_position & pos
|
|||
|
||||
// Tool call format classification (derived from T1-T5, A1-A3 comparisons)
|
||||
enum class tool_format {
|
||||
NONE, // No tool support detected
|
||||
JSON_NATIVE, // Pure JSON: {"name": "X", "arguments": {...}}
|
||||
TAG_WITH_JSON, // Tag-based with JSON args: <function=X>{...}</function>
|
||||
TAG_WITH_TAGGED, // Tag-based with tagged args: <param=key>value</param>
|
||||
NONE, // No tool support detected
|
||||
JSON_NATIVE, // Pure JSON: {"name": "X", "arguments": {...}}
|
||||
TAG_WITH_JSON, // Tag-based with JSON args: <function=X>{...}</function>
|
||||
TAG_WITH_TAGGED, // Tag-based with tagged args: <param=key>value</param>
|
||||
};
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os, const tool_format & format) {
|
||||
|
|
@ -187,33 +182,77 @@ inline std::ostream & operator<<(std::ostream & os, const tool_format & format)
|
|||
}
|
||||
}
|
||||
|
||||
struct reasoning_analysis {
|
||||
reasoning_mode mode = reasoning_mode::NONE;
|
||||
|
||||
std::string start; // e.g., "<think>", "[THINK]", "<|START_THINKING|>", ""
|
||||
std::string end; // e.g., "</think>", "[BEGIN FINAL RESPONSE]", "<|END_THINKING|>"
|
||||
};
|
||||
|
||||
struct content_analysis {
|
||||
content_mode mode = content_mode::PLAIN;
|
||||
|
||||
std::string start; // e.g., "<response>", ">>>all\n", ""
|
||||
std::string end; // e.g., "</response>", ""
|
||||
|
||||
bool requires_nonnull_content = false;
|
||||
};
|
||||
|
||||
struct tool_format_analysis {
|
||||
tool_format mode = tool_format::NONE;
|
||||
|
||||
std::string section_start; // e.g., "<tool_call>", "[TOOL_CALLS]", ""
|
||||
std::string section_end; // e.g., "</tool_call>", ""
|
||||
std::string per_call_start; // e.g., "<|tool_call_begin|>", "" (for multi-call templates)
|
||||
std::string per_call_end; // e.g., "<|tool_call_end|>", ""
|
||||
|
||||
bool fun_name_is_key = false; // In JSON format function name is JSON key, i.e. { "<funname>": { ... arguments ... } }
|
||||
bool tools_array_wrapped = false; // Tool calls wrapped in JSON array [...]
|
||||
|
||||
std::string function_field = "function";
|
||||
std::string name_field = "name";
|
||||
std::string args_field = "arguments";
|
||||
std::string id_field;
|
||||
std::string gen_id_field;
|
||||
std::vector<std::string> parameter_order;
|
||||
};
|
||||
|
||||
struct tool_function_analysis {
|
||||
std::string name_prefix; // e.g., "<function=", "\"name\": \"", "functions."
|
||||
std::string name_suffix; // e.g., ">", "\"", ":0"
|
||||
std::string close; // e.g., "</function>", "" (for tag-based)
|
||||
};
|
||||
|
||||
struct tool_arguments_analysis {
|
||||
std::string start; // e.g., "<|tool_call_argument_begin|>", "<args>"
|
||||
std::string end; // e.g., "<|tool_call_argument_end|>", "</args>"
|
||||
std::string name_prefix; // e.g., "<param=", "<arg_key>", "\""
|
||||
std::string name_suffix; // e.g., ">", "</arg_key>", "\":"
|
||||
std::string value_prefix; // e.g., "", "<arg_value>", ""
|
||||
std::string value_suffix; // e.g., "</param>", "</arg_value>", ""
|
||||
std::string separator; // e.g., "", "\n", ","
|
||||
};
|
||||
|
||||
struct tool_id_analysis {
|
||||
call_id_position pos = call_id_position::NONE;
|
||||
|
||||
std::string prefix; // e.g., "[CALL_ID]" (marker before call ID value)
|
||||
std::string suffix; // e.g., "" (marker after call ID value, before next section)
|
||||
};
|
||||
|
||||
struct tool_analysis {
|
||||
tool_format_analysis format;
|
||||
tool_function_analysis function;
|
||||
tool_arguments_analysis arguments;
|
||||
tool_id_analysis call_id;
|
||||
};
|
||||
|
||||
// Complete result of differential analysis
|
||||
struct diff_analysis_result {
|
||||
// Classification results
|
||||
reasoning_mode reasoning = reasoning_mode::NONE;
|
||||
content_mode content = content_mode::PLAIN;
|
||||
tool_format tools = tool_format::NONE;
|
||||
|
||||
// All extracted markers
|
||||
marker_registry markers;
|
||||
|
||||
// JSON field names (for JSON-based formats)
|
||||
bool fun_name_is_key = false;
|
||||
std::string function_field = "function";
|
||||
std::string name_field = "name";
|
||||
std::string args_field = "arguments";
|
||||
std::string id_field;
|
||||
std::string gen_id_field;
|
||||
std::vector<std::string> parameter_order;
|
||||
|
||||
// Call ID position (for non-JSON formats)
|
||||
call_id_position call_id_pos = call_id_position::NONE;
|
||||
|
||||
// Flags
|
||||
bool supports_tools = false;
|
||||
bool supports_parallel_calls = false;
|
||||
bool requires_nonnull_content = false;
|
||||
bool tools_array_wrapped = false; // Tool calls wrapped in JSON array [...]
|
||||
jinja::caps jinja_caps;
|
||||
reasoning_analysis reasoning;
|
||||
content_analysis content;
|
||||
tool_analysis tools;
|
||||
|
||||
// Preserved tokens for tokenizer (union of all non-empty markers)
|
||||
std::vector<std::string> preserved_tokens;
|
||||
|
|
@ -227,94 +266,102 @@ class differential_analyzer {
|
|||
static diff_analysis_result analyze(const common_chat_template & tmpl);
|
||||
|
||||
// Phase-specific analysis (can be called individually for testing)
|
||||
static void analyze_reasoning(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
static void analyze_content(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
static void analyze_tools(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
static void analyze_arguments(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
static reasoning_analysis analyze_reasoning(const common_chat_template & tmpl, bool supports_tools);
|
||||
static content_analysis analyze_content(const common_chat_template & tmpl, const reasoning_analysis & reasoning);
|
||||
static tool_analysis analyze_tools(const common_chat_template & tmpl,
|
||||
const jinja::caps & caps,
|
||||
const reasoning_analysis & reasoning);
|
||||
|
||||
// Factorized differential comparison function (public for testing)
|
||||
// Takes base params and a single modifier lambda to create variant B
|
||||
// Returns compare_variants_result containing diff and both outputs, or std::nullopt on failure
|
||||
static std::optional<compare_variants_result> compare_variants(
|
||||
const common_chat_template & tmpl,
|
||||
const template_params & params_A,
|
||||
const std::function<void(template_params &)> & params_modifier);
|
||||
const common_chat_template & tmpl,
|
||||
const template_params & params_A,
|
||||
const std::function<void(template_params &)> & params_modifier);
|
||||
|
||||
private:
|
||||
// Comparison helpers (implement the comparison matrix from the plan)
|
||||
|
||||
// R1: Extract reasoning markers by comparing with/without reasoning_content
|
||||
static void compare_reasoning_presence(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// 1. Reasoning analysis:
|
||||
// Look for reasoning markers in rendered content
|
||||
static void compare_reasoning_presence(const common_chat_template & tmpl, reasoning_analysis & reasoning);
|
||||
|
||||
// R2: Detect forced-open reasoning by comparing enable_thinking=false vs true
|
||||
static void compare_thinking_enabled(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// Compare generation prompt with enable_thinking=true vs false
|
||||
static void compare_thinking_enabled(const common_chat_template & tmpl, reasoning_analysis & reasoning);
|
||||
|
||||
// R3: Detect reasoning scope (content-only vs with tools)
|
||||
static void compare_reasoning_scope(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// Check if reasoning is always possible or only in tool calls
|
||||
static void compare_reasoning_scope(const common_chat_template & tmpl, reasoning_analysis & reasoning);
|
||||
|
||||
// C1: Extract content markers by comparing different content values
|
||||
static void compare_content_values(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// 2. Content (fully inside analyze_content mentioned above)
|
||||
|
||||
// T1: Analyze the tool calls
|
||||
static void analyze_tool_calls(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// 3. Tool calls
|
||||
// a. format
|
||||
// Extract tool calling 'haystack' for further analysis and delegate further analysis based on format
|
||||
static tool_format_analysis analyze_tool_calls(const common_chat_template & tmpl,
|
||||
const reasoning_analysis & reasoning);
|
||||
|
||||
// Analyzes a tool call section to determine the format used (pure JSON, function name markers, or full markers)
|
||||
static void analyze_tool_call_format(const std::string & haystack,
|
||||
const std::string & fun_name_needle,
|
||||
const std::string & arg_name_needle,
|
||||
diff_analysis_result & result);
|
||||
// Analyze format based on position of function and argument name in needle
|
||||
static tool_format_analysis analyze_tool_call_format(const std::string & haystack,
|
||||
const std::string & fun_name_needle,
|
||||
const std::string & arg_name_needle,
|
||||
const reasoning_analysis & reasoning);
|
||||
|
||||
// Helper functions to handle the two branches of analyze_tool_call_format
|
||||
// Analyze specifics of JSON native format (entire tool call is a JSON object)
|
||||
static void analyze_tool_call_format_json_native(const std::string & clean_haystack,
|
||||
const std::string & fun_name_needle,
|
||||
const std::string & arg_name_needle,
|
||||
diff_analysis_result & result);
|
||||
tool_format_analysis & format);
|
||||
|
||||
// Analyze specifics of non-JSON native format (tags for function name or for function name and arguments)
|
||||
static void analyze_tool_call_format_non_json(const std::string & clean_haystack,
|
||||
const std::string & fun_name_needle,
|
||||
diff_analysis_result & result);
|
||||
tool_format_analysis & format);
|
||||
|
||||
// T2: Check if markers are per call or per section
|
||||
static void check_per_call_markers(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// Check for and extract specific per-call markers for non-native-JSON templates with parallel call support
|
||||
static void check_per_call_markers(const common_chat_template & tmpl, tool_format_analysis & result);
|
||||
|
||||
// T3: Extract call separator; also outputs second_call_content for per-call detection
|
||||
static void extract_call_separator(const common_chat_template & tmpl, diff_analysis_result & result,
|
||||
std::string & second_call_content);
|
||||
// Logic below is only for non-JSON-native tool calling formats
|
||||
// 3. b. function name
|
||||
// Extract function name markers
|
||||
static tool_function_analysis extract_function_markers(const common_chat_template & tmpl,
|
||||
const tool_format_analysis & analysis);
|
||||
|
||||
// T4: Analyze function name format and extract markers
|
||||
static void extract_function_markers(const common_chat_template & tmpl,
|
||||
diff_analysis_result & result);
|
||||
// 4. c. function arguments
|
||||
// Delegates to separate functions for: separator analysis, argument name analysis, argument value analysis
|
||||
static tool_arguments_analysis analyze_arguments(const common_chat_template & tmpl,
|
||||
const tool_analysis & analysis);
|
||||
|
||||
// T5: Extract argument separator
|
||||
static void extract_argument_separator(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// Extract argument name markers
|
||||
static void extract_argument_name_markers(const common_chat_template & tmpl,
|
||||
tool_arguments_analysis & args_analysis);
|
||||
|
||||
// T6: Extract args container markers
|
||||
static void extract_args_markers(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// Extract argument value markers
|
||||
static void extract_argument_value_markers(const common_chat_template & tmpl,
|
||||
const tool_analysis & analysis,
|
||||
tool_arguments_analysis & args_analysis);
|
||||
|
||||
// A1: Extract argument name markers
|
||||
static void extract_argument_name_markers(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// Extract argument separator, if specified (eg. <arg=foo>...</arg><sep><arg=bar>...</arg>)
|
||||
static void extract_argument_separator(const common_chat_template & tmpl,
|
||||
tool_arguments_analysis & args_analysis);
|
||||
|
||||
// A2: Extract argument value markers
|
||||
static void extract_argument_value_markers(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// Extract argument wrapper markers, if present (eg. '<args><arg=foo>...</arg><arg=bar>...</arg></args>')
|
||||
static void extract_args_markers(const common_chat_template & tmpl,
|
||||
const tool_analysis & analysis,
|
||||
tool_arguments_analysis & args_analysis);
|
||||
|
||||
// T7: Extract call ID markers (for non-JSON formats)
|
||||
static void extract_call_id_markers(const common_chat_template & tmpl, diff_analysis_result & result);
|
||||
// 4. d. function call id
|
||||
// Extract call ID markers, if present
|
||||
static tool_id_analysis extract_call_id_markers(const common_chat_template & tmpl,
|
||||
tool_format_analysis & analysis);
|
||||
|
||||
// Classify tool format based on extracted markers
|
||||
static void classify_tool_format(diff_analysis_result & result);
|
||||
|
||||
// Classification helpers
|
||||
// Collect tokens from entire analysis to preserve
|
||||
static void collect_preserved_tokens(diff_analysis_result & result);
|
||||
|
||||
// Utility: Apply template with given parameters
|
||||
static std::string apply_template(const common_chat_template & tmpl,
|
||||
const template_params & params);
|
||||
static std::string apply_template(const common_chat_template & tmpl, const template_params & params);
|
||||
};
|
||||
|
||||
enum segment_type {
|
||||
TEXT,
|
||||
MARKER
|
||||
};
|
||||
enum segment_type { TEXT, MARKER };
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os, const segment_type & type) {
|
||||
switch (type) {
|
||||
|
|
@ -329,7 +376,7 @@ inline std::ostream & operator<<(std::ostream & os, const segment_type & type) {
|
|||
|
||||
struct segment {
|
||||
segment_type type;
|
||||
std::string value;
|
||||
std::string value;
|
||||
|
||||
segment(segment_type type, std::string value) : type(type), value(std::move(value)) {}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -167,13 +167,13 @@ void common_chat_peg_mapper::map(const common_peg_ast_node & node) {
|
|||
bool is_content = node.tag == common_chat_peg_builder::CONTENT;
|
||||
|
||||
if (is_reasoning) { // GPT OSS can have more than 1 reasoning block, so concatenate here
|
||||
result.reasoning_content += std::string(trim_trailing_space(node.text));
|
||||
result.reasoning_content += std::string(node.text);
|
||||
}
|
||||
|
||||
if (is_content) {
|
||||
// Concatenate content from multiple content nodes (e.g., when reasoning markers
|
||||
// are preserved before content markers in reasoning_format=NONE mode)
|
||||
result.content += std::string(trim_trailing_space(node.text));
|
||||
result.content += std::string(node.text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ bool common_chat_templates_support_enable_thinking(const common_chat_templates *
|
|||
? *chat_templates->template_tool_use
|
||||
: *chat_templates->template_default;
|
||||
diff_analysis_result result = differential_analyzer::analyze(tmpl);
|
||||
detect |= result.reasoning != reasoning_mode::NONE;
|
||||
detect |= result.reasoning.mode != reasoning_mode::NONE;
|
||||
return detect;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ static void caps_try_execute(jinja::program & prog,
|
|||
jinja::runtime runtime(ctx);
|
||||
auto results = runtime.execute(prog);
|
||||
auto parts = jinja::runtime::gather_string_parts(results);
|
||||
std::string result = parts->as_string().str();
|
||||
result = parts->as_string().str();
|
||||
success = true;
|
||||
} catch (const std::exception & e) {
|
||||
JJ_DEBUG("Exception during execution: %s", e.what());
|
||||
|
|
@ -95,6 +95,8 @@ caps caps_get(jinja::program & prog) {
|
|||
return v->stats.ops.find(op_name) != v->stats.ops.end();
|
||||
};
|
||||
|
||||
JJ_DEBUG("%s\n", ">>> Running capability check: typed content");
|
||||
|
||||
// case: typed content support
|
||||
caps_try_execute(
|
||||
prog,
|
||||
|
|
@ -125,6 +127,7 @@ caps caps_get(jinja::program & prog) {
|
|||
}
|
||||
);
|
||||
|
||||
JJ_DEBUG("%s\n", ">>> Running capability check: system prompt");
|
||||
|
||||
// case: system prompt support
|
||||
caps_try_execute(
|
||||
|
|
@ -155,6 +158,8 @@ caps caps_get(jinja::program & prog) {
|
|||
}
|
||||
);
|
||||
|
||||
JJ_DEBUG("%s\n", ">>> Running capability check: tool support");
|
||||
|
||||
// case: tools support
|
||||
caps_try_execute(
|
||||
prog,
|
||||
|
|
@ -167,7 +172,7 @@ caps caps_get(jinja::program & prog) {
|
|||
},
|
||||
{
|
||||
{"role", "assistant"},
|
||||
{"content", "Assistant message"},
|
||||
{"content", ""}, // Some templates expect content to be empty with tool calls
|
||||
{"tool_calls", json::array({
|
||||
{
|
||||
{"id", "call00001"},
|
||||
|
|
@ -260,6 +265,8 @@ caps caps_get(jinja::program & prog) {
|
|||
}
|
||||
);
|
||||
|
||||
JJ_DEBUG("%s\n", ">>> Running capability check: preserve reasoning");
|
||||
|
||||
// case: preserve reasoning content in chat history
|
||||
caps_try_execute(
|
||||
prog,
|
||||
|
|
|
|||
|
|
@ -114,8 +114,10 @@ value binary_expression::execute_impl(context & ctx) {
|
|||
|
||||
// Logical operators
|
||||
if (op.value == "and") {
|
||||
JJ_DEBUG("Executing logical test: %s AND %s", left->type().c_str(), right->type().c_str());
|
||||
return left_val->as_bool() ? right->execute(ctx) : std::move(left_val);
|
||||
} else if (op.value == "or") {
|
||||
JJ_DEBUG("Executing logical test: %s OR %s", left->type().c_str(), right->type().c_str());
|
||||
return left_val->as_bool() ? std::move(left_val) : right->execute(ctx);
|
||||
}
|
||||
|
||||
|
|
@ -835,7 +837,7 @@ value call_expression::execute_impl(context & ctx) {
|
|||
for (auto & arg_stmt : this->args) {
|
||||
auto arg_val = arg_stmt->execute(ctx);
|
||||
JJ_DEBUG(" Argument type: %s", arg_val->type().c_str());
|
||||
args.push_back(std::move(arg_val));
|
||||
args.push_back(arg_val);
|
||||
}
|
||||
// execute callee
|
||||
value callee_val = callee->execute(ctx);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
{%- set ns.is_first = false -%}
|
||||
{%- set ns.is_last_user = true -%}{{'<|User|>' + message['content']}}
|
||||
{%- endif -%}
|
||||
{%- if message['role'] == 'assistant' and message['tool_calls'] is defined and message['tool_calls'] is not none -%}
|
||||
{%- if message['role'] == 'assistant' and message['tool_calls'] -%}
|
||||
{%- if ns.is_last_user -%}{{'<|Assistant|></think>'}}
|
||||
{%- endif -%}
|
||||
{%- set ns.is_last_user = false -%}
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
{%- set ns.is_tool = false -%}
|
||||
{%- for tool in message['tool_calls'] -%}
|
||||
{%- if not ns.is_first -%}
|
||||
{%- if message['content'] is none -%}{{'<|tool▁calls▁begin|><|tool▁call▁begin|>'+ tool['function']['name'] + '<|tool▁sep|>' + tool['function']['arguments'] + '<|tool▁call▁end|>'}}
|
||||
{%- if not message['content'] -%}{{'<|tool▁calls▁begin|><|tool▁call▁begin|>'+ tool['function']['name'] + '<|tool▁sep|>' + tool['function']['arguments'] + '<|tool▁call▁end|>'}}
|
||||
{%- else -%}{{message['content'] + '<|tool▁calls▁begin|><|tool▁call▁begin|>' + tool['function']['name'] + '<|tool▁sep|>' + tool['function']['arguments'] + '<|tool▁call▁end|>'}}
|
||||
{%- endif -%}
|
||||
{%- set ns.is_first = true -%}
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
{%- endif -%}
|
||||
{%- endfor -%}{{'<|tool▁calls▁end|><|end▁of▁sentence|>'}}
|
||||
{%- endif -%}
|
||||
{%- if message['role'] == 'assistant' and (message['tool_calls'] is not defined or message['tool_calls'] is none) -%}
|
||||
{%- if message['role'] == 'assistant' and not message['tool_calls'] -%}
|
||||
{%- if ns.is_last_user -%}{{'<|Assistant|>'}}
|
||||
{%- if message['prefix'] is defined and message['prefix'] and thinking -%}{{'<think>'}}
|
||||
{%- else -%}{{'</think>'}}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ static void test_nemotron_tool_format(testing & t);
|
|||
|
||||
// CohereForAI template analysis tests
|
||||
static void test_cohere_reasoning_detection(testing & t);
|
||||
static void test_cohere_tool_format(testing & t);
|
||||
static void test_cohere_analysis(testing & t);
|
||||
|
||||
// Marker separation
|
||||
|
|
@ -1283,18 +1282,18 @@ static void test_nemotron_reasoning_detection(testing & t) {
|
|||
auto analysis = differential_analyzer::analyze(tmpl);
|
||||
|
||||
// Check reasoning markers
|
||||
t.assert_equal("reasoning_start should be '<think>'", "<think>", analysis.markers.reasoning_start);
|
||||
t.assert_equal("reasoning_end should be '</think>'", "</think>", analysis.markers.reasoning_end);
|
||||
t.assert_equal("reasoning_start should be '<think>'", "<think>", analysis.reasoning.start);
|
||||
t.assert_equal("reasoning_end should be '</think>'", "</think>", analysis.reasoning.end);
|
||||
|
||||
// Check reasoning mode detection
|
||||
// Nemotron uses forced closed reasoning with add_generation_prompt
|
||||
t.assert_equal("reasoning should be FORCED_CLOSED", reasoning_mode::FORCED_CLOSED, analysis.reasoning);
|
||||
t.assert_equal("reasoning should be FORCED_CLOSED", reasoning_mode::FORCED_CLOSED, analysis.reasoning.mode);
|
||||
|
||||
// Make sure reasoning markers don't spill over to content markers
|
||||
t.assert_equal("content start should be empty", "", analysis.markers.content_start);
|
||||
t.assert_equal("content end should be empty", "", analysis.markers.content_end);
|
||||
t.assert_equal("content start should be empty", "", analysis.content.start);
|
||||
t.assert_equal("content end should be empty", "", analysis.content.end);
|
||||
|
||||
t.assert_equal("content should be PLAIN", content_mode::PLAIN, analysis.content);
|
||||
t.assert_equal("content should be PLAIN", content_mode::PLAIN, analysis.content.mode);
|
||||
}
|
||||
|
||||
static void test_nemotron_tool_format(testing & t) {
|
||||
|
|
@ -1304,27 +1303,27 @@ static void test_nemotron_tool_format(testing & t) {
|
|||
auto analysis = differential_analyzer::analyze(tmpl);
|
||||
|
||||
// Check tool markers - Nemotron uses per-call wrapping (each call individually wrapped)
|
||||
t.assert_equal("tool_section_start should be empty (per-call format)", "", analysis.markers.tool_section_start);
|
||||
t.assert_equal("tool_section_end should be empty (per-call format)", "", analysis.markers.tool_section_end);
|
||||
t.assert_equal("per_call_start should be '<tool_call>\\n'", "<tool_call>\n", analysis.markers.per_call_start);
|
||||
t.assert_equal("per_call_end should be '</tool_call>'", "</tool_call>", analysis.markers.per_call_end);
|
||||
t.assert_true("should support parallel calls", analysis.supports_parallel_calls);
|
||||
t.assert_equal("tool_section_start should be empty (per-call format)", "", analysis.tools.format.section_start);
|
||||
t.assert_equal("tool_section_end should be empty (per-call format)", "", analysis.tools.format.section_end);
|
||||
t.assert_equal("per_call_start should be '<tool_call>\\n'", "<tool_call>\n", analysis.tools.format.per_call_start);
|
||||
t.assert_equal("per_call_end should be '</tool_call>'", "</tool_call>", analysis.tools.format.per_call_end);
|
||||
t.assert_true("should support parallel calls", analysis.jinja_caps.supports_parallel_tool_calls);
|
||||
|
||||
// Check function markers
|
||||
t.assert_equal("func_name_prefix should be '<function='", "<function=", analysis.markers.func_name_prefix);
|
||||
t.assert_equal("func_name_suffix should be '>\\n'", ">\n", analysis.markers.func_name_suffix);
|
||||
t.assert_equal("func_close should be '</function>\\n'", "</function>\n", analysis.markers.func_close);
|
||||
t.assert_equal("func_name_prefix should be '<function='", "<function=", analysis.tools.function.name_prefix);
|
||||
t.assert_equal("func_name_suffix should be '>\\n'", ">\n", analysis.tools.function.name_suffix);
|
||||
t.assert_equal("func_close should be '</function>\\n'", "</function>\n", analysis.tools.function.close);
|
||||
|
||||
// Check argument markers (note: markers retain trailing newlines for proper parsing)
|
||||
t.assert_equal("arg_name_prefix should be '<parameter='", "<parameter=", analysis.markers.arg_name_prefix);
|
||||
t.assert_equal("arg_name_suffix should be '>\\n'", ">\n", analysis.markers.arg_name_suffix);
|
||||
t.assert_equal("arg_value_suffix should be '</parameter>\\n'", "</parameter>\n", analysis.markers.arg_value_suffix);
|
||||
t.assert_equal("arg_name_prefix should be '<parameter='", "<parameter=", analysis.tools.arguments.name_prefix);
|
||||
t.assert_equal("arg_name_suffix should be '>\\n'", ">\n", analysis.tools.arguments.name_suffix);
|
||||
t.assert_equal("arg_value_suffix should be '</parameter>\\n'", "</parameter>\n", analysis.tools.arguments.value_suffix);
|
||||
|
||||
// Check format classification
|
||||
t.assert_true("tool format should be TAG_WITH_TAGGED", analysis.tools == tool_format::TAG_WITH_TAGGED);
|
||||
t.assert_true("tool format should be TAG_WITH_TAGGED", analysis.tools.format.mode == tool_format::TAG_WITH_TAGGED);
|
||||
|
||||
// Verify tool support
|
||||
t.assert_true("should support tools", analysis.supports_tools);
|
||||
t.assert_true("should support tools", analysis.jinja_caps.supports_tools);
|
||||
}
|
||||
|
||||
static common_chat_template load_cohere_template(testing & t) {
|
||||
|
|
@ -1333,7 +1332,6 @@ static common_chat_template load_cohere_template(testing & t) {
|
|||
|
||||
static void test_cohere_analysis(testing & t) {
|
||||
t.test("Cohere reasoning detection", test_cohere_reasoning_detection);
|
||||
t.test("Cohere tool format", test_cohere_tool_format);
|
||||
}
|
||||
|
||||
static void test_cohere_reasoning_detection(testing & t) {
|
||||
|
|
@ -1343,64 +1341,64 @@ static void test_cohere_reasoning_detection(testing & t) {
|
|||
auto analysis = differential_analyzer::analyze(tmpl);
|
||||
|
||||
// Check reasoning markers - Cohere uses special token format
|
||||
t.assert_equal("reasoning_start should be '<|START_THINKING|>'", "<|START_THINKING|>", analysis.markers.reasoning_start);
|
||||
t.assert_equal("reasoning_end should be '<|END_THINKING|>'", "<|END_THINKING|>", analysis.markers.reasoning_end);
|
||||
t.assert_equal("reasoning_start should be '<|START_THINKING|>'", "<|START_THINKING|>", analysis.reasoning.start);
|
||||
t.assert_equal("reasoning_end should be '<|END_THINKING|>'", "<|END_THINKING|>", analysis.reasoning.end);
|
||||
|
||||
// Check reasoning mode - Cohere only shows reasoning with tool calls (TOOLS_ONLY)
|
||||
t.assert_equal("reasoning should be TOOLS_ONLY", reasoning_mode::TOOLS_ONLY, analysis.reasoning);
|
||||
t.assert_equal("reasoning should be TOOLS_ONLY", reasoning_mode::TOOLS_ONLY, analysis.reasoning.mode);
|
||||
|
||||
// Check content markers - Cohere wraps all content with START/END_RESPONSE
|
||||
t.assert_equal("content_start should be '<|START_RESPONSE|>'", "<|START_RESPONSE|>", analysis.markers.content_start);
|
||||
t.assert_equal("content_end should be '<|END_RESPONSE|>'", "<|END_RESPONSE|>", analysis.markers.content_end);
|
||||
t.assert_equal("content_start should be '<|START_RESPONSE|>'", "<|START_RESPONSE|>", analysis.content.start);
|
||||
t.assert_equal("content_end should be '<|END_RESPONSE|>'", "<|END_RESPONSE|>", analysis.content.end);
|
||||
|
||||
// Content is always wrapped (both with and without tools)
|
||||
t.assert_equal("content should be ALWAYS_WRAPPED", content_mode::ALWAYS_WRAPPED, analysis.content);
|
||||
t.assert_equal("content should be ALWAYS_WRAPPED", content_mode::ALWAYS_WRAPPED, analysis.content.mode);
|
||||
}
|
||||
|
||||
static void test_cohere_tool_format(testing & t) {
|
||||
static void test_tool_format_cohere(testing & t) {
|
||||
common_chat_template tmpl = load_cohere_template(t);
|
||||
|
||||
// Run differential analysis
|
||||
auto analysis = differential_analyzer::analyze(tmpl);
|
||||
|
||||
// Check tool section markers - Cohere uses ACTION markers
|
||||
t.assert_equal("tool_section_start should be '<|START_ACTION|>'", "<|START_ACTION|>", analysis.markers.tool_section_start);
|
||||
t.assert_equal("tool_section_end should be '<|END_ACTION|>'", "<|END_ACTION|>", analysis.markers.tool_section_end);
|
||||
t.assert_equal("tool_section_start should be '<|START_ACTION|>'", "<|START_ACTION|>", analysis.tools.format.section_start);
|
||||
t.assert_equal("tool_section_end should be '<|END_ACTION|>'", "<|END_ACTION|>", analysis.tools.format.section_end);
|
||||
|
||||
// JSON_NATIVE format has no per-call markers
|
||||
t.assert_equal("per_call_start should be empty", "", analysis.markers.per_call_start);
|
||||
t.assert_equal("per_call_end should be empty", "", analysis.markers.per_call_end);
|
||||
t.assert_equal("per_call_start should be empty", "", analysis.tools.format.per_call_start);
|
||||
t.assert_equal("per_call_end should be empty", "", analysis.tools.format.per_call_end);
|
||||
|
||||
// JSON_NATIVE format has empty function markers (no XML-style markers)
|
||||
t.assert_equal("func_name_prefix should be empty", "", analysis.markers.func_name_prefix);
|
||||
t.assert_equal("func_name_suffix should be empty", "", analysis.markers.func_name_suffix);
|
||||
t.assert_equal("func_close should be empty", "", analysis.markers.func_close);
|
||||
t.assert_equal("func_name_prefix should be empty", "", analysis.tools.function.name_prefix);
|
||||
t.assert_equal("func_name_suffix should be empty", "", analysis.tools.function.name_suffix);
|
||||
t.assert_equal("func_close should be empty", "", analysis.tools.function.close);
|
||||
|
||||
// JSON_NATIVE format has empty args markers
|
||||
t.assert_equal("args_start should be empty", "", analysis.markers.args_start);
|
||||
t.assert_equal("args_end should be empty", "", analysis.markers.args_end);
|
||||
t.assert_equal("args_start should be empty", "", analysis.tools.arguments.start);
|
||||
t.assert_equal("args_end should be empty", "", analysis.tools.arguments.end);
|
||||
|
||||
// JSON_NATIVE format has empty argument markers
|
||||
t.assert_equal("arg_name_prefix should be empty", "", analysis.markers.arg_name_prefix);
|
||||
t.assert_equal("arg_name_suffix should be empty", "", analysis.markers.arg_name_suffix);
|
||||
t.assert_equal("arg_value_prefix should be empty", "", analysis.markers.arg_value_prefix);
|
||||
t.assert_equal("arg_value_suffix should be empty", "", analysis.markers.arg_value_suffix);
|
||||
t.assert_equal("arg_separator should be empty", "", analysis.markers.arg_separator);
|
||||
t.assert_equal("arg_name_prefix should be empty", "", analysis.tools.arguments.name_prefix);
|
||||
t.assert_equal("arg_name_suffix should be empty", "", analysis.tools.arguments.name_suffix);
|
||||
t.assert_equal("arg_value_prefix should be empty", "", analysis.tools.arguments.value_prefix);
|
||||
t.assert_equal("arg_value_suffix should be empty", "", analysis.tools.arguments.value_suffix);
|
||||
t.assert_equal("arg_separator should be empty", "", analysis.tools.arguments.separator);
|
||||
|
||||
// Check JSON field names - Cohere uses non-standard names
|
||||
t.assert_equal("name_field should be 'tool_name'", "tool_name", analysis.name_field);
|
||||
t.assert_equal("args_field should be 'parameters'", "parameters", analysis.args_field);
|
||||
t.assert_equal("name_field should be 'tool_name'", "tool_name", analysis.tools.format.name_field);
|
||||
t.assert_equal("args_field should be 'parameters'", "parameters", analysis.tools.format.args_field);
|
||||
// This isn't a real tool call id field, i.e. with the OpenAI tool call ID format
|
||||
t.assert_equal("id_field should be 'tool_call_id'", "", analysis.id_field);
|
||||
t.assert_equal("id_field should be 'tool_call_id'", "", analysis.tools.format.id_field);
|
||||
|
||||
// Check format classification
|
||||
t.assert_equal("tool format should be JSON_NATIVE", tool_format::JSON_NATIVE, analysis.tools);
|
||||
t.assert_equal("tool format should be JSON_NATIVE", tool_format::JSON_NATIVE, analysis.tools.format.mode);
|
||||
|
||||
// Check flags
|
||||
t.assert_true("should support tools", analysis.supports_tools);
|
||||
t.assert_true("should support parallel calls", analysis.supports_parallel_calls);
|
||||
t.assert_true("should not require nonnull content", !analysis.requires_nonnull_content);
|
||||
t.assert_true("tools_array_wrapped should be true", analysis.tools_array_wrapped);
|
||||
t.assert_true("should support tools", analysis.jinja_caps.supports_tools);
|
||||
t.assert_true("should support parallel calls", analysis.jinja_caps.supports_parallel_tool_calls);
|
||||
t.assert_true("should not require nonnull content", !analysis.content.requires_nonnull_content);
|
||||
t.assert_true("tools_array_wrapped should be true", analysis.tools.format.tools_array_wrapped);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ static void test_example_qwen3_non_coder(testing & t) {
|
|||
auto mapper = common_chat_peg_unified_mapper(msg);
|
||||
mapper.from_ast(ctx.ast, result);
|
||||
|
||||
t.assert_equal("content", "I need to get the weather.", msg.content);
|
||||
t.assert_equal("content", "I need to get the weather.\n", msg.content);
|
||||
t.assert_equal("reasoning", "", msg.reasoning_content);
|
||||
t.assert_equal("tool calls count", 1u, msg.tool_calls.size());
|
||||
if (!msg.tool_calls.empty()) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ static std::string HELP = R"(
|
|||
Usage: test-chat-template [OPTIONS] PATH_TO_TEMPLATE
|
||||
Options:
|
||||
-h, --help Show this help message and exit.
|
||||
--with-tools Add a tool and a tool call to the default JSON input
|
||||
--json <path> Path to the JSON input file.
|
||||
--stop-on-first-fail Stop testing on the first failure (default: false).
|
||||
--no-common Use direct Jinja engine instead of common chat templates (default: use common).
|
||||
|
|
@ -57,12 +58,65 @@ static std::string DEFAULT_JSON = R"({
|
|||
"add_generation_prompt": true
|
||||
})";
|
||||
|
||||
static std::string DEFAULT_JSON_WITH_TOOLS = R"({
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello, how are you?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "I am fine, thank you!"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Call a tool!"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call00001",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "test",
|
||||
"arguments": { "arg": "hello" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "test",
|
||||
"description": "Test",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"arg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["arg"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"bos_token": "<s>",
|
||||
"eos_token": "</s>",
|
||||
"add_generation_prompt": true
|
||||
})";
|
||||
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
std::vector<std::string> args(argv, argv + argc);
|
||||
|
||||
std::string tmpl_path;
|
||||
std::string json_path;
|
||||
std::string output_path;
|
||||
std::string & json_to_use = DEFAULT_JSON;
|
||||
bool stop_on_first_fail = false;
|
||||
bool use_common = true;
|
||||
|
||||
|
|
@ -74,6 +128,8 @@ int main(int argc, char ** argv) {
|
|||
if (args[i] == "--json" && i + 1 < args.size()) {
|
||||
json_path = args[i + 1];
|
||||
i++;
|
||||
} else if (args[i] == "--with-tools") {
|
||||
json_to_use = DEFAULT_JSON_WITH_TOOLS;
|
||||
} else if (args[i] == "--stop-on-first-fail") {
|
||||
stop_on_first_fail = true;
|
||||
} else if (args[i] == "--output" && i + 1 < args.size()) {
|
||||
|
|
@ -106,7 +162,7 @@ int main(int argc, char ** argv) {
|
|||
std::istreambuf_iterator<char>());
|
||||
input_json = json::parse(content);
|
||||
} else {
|
||||
input_json = json::parse(DEFAULT_JSON);
|
||||
input_json = json::parse(json_to_use);
|
||||
}
|
||||
|
||||
std::filesystem::path p(tmpl_path);
|
||||
|
|
|
|||
|
|
@ -419,33 +419,33 @@ int main(int argc, char ** argv) {
|
|||
LOG_ERR("\n=== Differential Analysis Results ===\n");
|
||||
|
||||
LOG_ERR("\n--- Reasoning & Content Structure ---\n");
|
||||
LOG_ERR("reasoning_mode: %s\n", mode_to_str(analysis.reasoning).c_str());
|
||||
LOG_ERR("reasoning_start: '%s'\n", analysis.markers.reasoning_start.c_str());
|
||||
LOG_ERR("reasoning_end: '%s'\n", analysis.markers.reasoning_end.c_str());
|
||||
LOG_ERR("content_mode: %s\n", mode_to_str(analysis.content).c_str());
|
||||
LOG_ERR("content_start: '%s'\n", analysis.markers.content_start.c_str());
|
||||
LOG_ERR("content_end: '%s'\n", analysis.markers.content_end.c_str());
|
||||
LOG_ERR("reasoning_mode: %s\n", mode_to_str(analysis.reasoning.mode).c_str());
|
||||
LOG_ERR("reasoning_start: '%s'\n", analysis.reasoning.start.c_str());
|
||||
LOG_ERR("reasoning_end: '%s'\n", analysis.reasoning.end.c_str());
|
||||
LOG_ERR("content_mode: %s\n", mode_to_str(analysis.content.mode).c_str());
|
||||
LOG_ERR("content_start: '%s'\n", analysis.content.start.c_str());
|
||||
LOG_ERR("content_end: '%s'\n", analysis.content.end.c_str());
|
||||
|
||||
LOG_ERR("\n--- Tool Call Structure ---\n");
|
||||
LOG_ERR("tool_mode: %s\n", mode_to_str(analysis.tools).c_str());
|
||||
LOG_ERR("supports_tools: %s\n", analysis.supports_tools ? "true" : "false");
|
||||
LOG_ERR("supports_parallel_calls: %s\n", analysis.supports_parallel_calls ? "true" : "false");
|
||||
LOG_ERR("tool_section_start: '%s'\n", analysis.markers.tool_section_start.c_str());
|
||||
LOG_ERR("tool_section_end: '%s'\n", analysis.markers.tool_section_end.c_str());
|
||||
LOG_ERR("per_call_start: '%s'\n", analysis.markers.per_call_start.c_str());
|
||||
LOG_ERR("per_call_end: '%s'\n", analysis.markers.per_call_end.c_str());
|
||||
LOG_ERR("func_name_prefix: '%s'\n", analysis.markers.func_name_prefix.c_str());
|
||||
LOG_ERR("func_name_suffix: '%s'\n", analysis.markers.func_name_suffix.c_str());
|
||||
LOG_ERR("func_close: '%s'\n", analysis.markers.func_close.c_str());
|
||||
LOG_ERR("arg_name_prefix: '%s'\n", analysis.markers.arg_name_prefix.c_str());
|
||||
LOG_ERR("arg_name_suffix: '%s'\n", analysis.markers.arg_name_suffix.c_str());
|
||||
LOG_ERR("arg_value_prefix: '%s'\n", analysis.markers.arg_value_prefix.c_str());
|
||||
LOG_ERR("arg_value_suffix: '%s'\n", analysis.markers.arg_value_suffix.c_str());
|
||||
LOG_ERR("name_field: '%s'\n", analysis.name_field.c_str());
|
||||
LOG_ERR("args_field: '%s'\n", analysis.args_field.c_str());
|
||||
LOG_ERR("id_field: '%s'\n", analysis.id_field.c_str());
|
||||
LOG_ERR("gen_id_field: '%s'\n", analysis.gen_id_field.c_str());
|
||||
LOG_ERR("parameter_order: '%s'\n", std::accumulate(analysis.parameter_order.begin(), analysis.parameter_order.end(),
|
||||
LOG_ERR("tool_mode: %s\n", mode_to_str(analysis.tools.format.mode).c_str());
|
||||
LOG_ERR("supports_tools: %s\n", analysis.jinja_caps.supports_tools ? "true" : "false");
|
||||
LOG_ERR("supports_parallel_calls: %s\n", analysis.jinja_caps.supports_parallel_tool_calls ? "true" : "false");
|
||||
LOG_ERR("tool_section_start: '%s'\n", analysis.tools.format.section_start.c_str());
|
||||
LOG_ERR("tool_section_end: '%s'\n", analysis.tools.format.section_end.c_str());
|
||||
LOG_ERR("per_call_start: '%s'\n", analysis.tools.format.per_call_start.c_str());
|
||||
LOG_ERR("per_call_end: '%s'\n", analysis.tools.format.per_call_end.c_str());
|
||||
LOG_ERR("func_name_prefix: '%s'\n", analysis.tools.function.name_prefix.c_str());
|
||||
LOG_ERR("func_name_suffix: '%s'\n", analysis.tools.function.name_suffix.c_str());
|
||||
LOG_ERR("func_close: '%s'\n", analysis.tools.function.close.c_str());
|
||||
LOG_ERR("arg_name_prefix: '%s'\n", analysis.tools.arguments.name_prefix.c_str());
|
||||
LOG_ERR("arg_name_suffix: '%s'\n", analysis.tools.arguments.name_suffix.c_str());
|
||||
LOG_ERR("arg_value_prefix: '%s'\n", analysis.tools.arguments.value_prefix.c_str());
|
||||
LOG_ERR("arg_value_suffix: '%s'\n", analysis.tools.arguments.value_suffix.c_str());
|
||||
LOG_ERR("name_field: '%s'\n", analysis.tools.format.name_field.c_str());
|
||||
LOG_ERR("args_field: '%s'\n", analysis.tools.format.args_field.c_str());
|
||||
LOG_ERR("id_field: '%s'\n", analysis.tools.format.id_field.c_str());
|
||||
LOG_ERR("gen_id_field: '%s'\n", analysis.tools.format.gen_id_field.c_str());
|
||||
LOG_ERR("parameter_order: '%s'\n", std::accumulate(analysis.tools.format.parameter_order.begin(), analysis.tools.format.parameter_order.end(),
|
||||
std::string(""), [] (const std::string & a, const std::string & b) { return a.empty() ? b : a + ", " + b; }
|
||||
).c_str());
|
||||
|
||||
|
|
@ -470,11 +470,13 @@ int main(int argc, char ** argv) {
|
|||
LOG_ERR(" '%s'\n", token.c_str());
|
||||
}
|
||||
|
||||
LOG_ERR("\n=== Verifying created grammar ===\n");
|
||||
auto * grammar = llama_grammar_init_impl(nullptr, parser_data.grammar.c_str(), "root",
|
||||
parser_data.grammar_lazy, nullptr, 0, nullptr, 0);
|
||||
if (grammar != nullptr) {
|
||||
LOG_ERR("\n=== Grammar successfully created ===\n");
|
||||
if (!parser_data.grammar.empty()) {
|
||||
LOG_ERR("\n=== Verifying created grammar ===\n");
|
||||
auto * grammar = llama_grammar_init_impl(nullptr, parser_data.grammar.c_str(), "root",
|
||||
parser_data.grammar_lazy, nullptr, 0, nullptr, 0);
|
||||
if (grammar != nullptr) {
|
||||
LOG_ERR("\n=== Grammar successfully created ===\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception & e) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue