|
8 | 8 | #include "nlohmann/json.hpp" |
9 | 9 | #include "peg-parser.h" |
10 | 10 |
|
11 | | -#include <algorithm> |
12 | 11 | #include <stdexcept> |
13 | 12 | #include <string> |
14 | 13 |
|
15 | 14 | using json = nlohmann::ordered_json; |
16 | 15 |
|
17 | | -namespace { |
18 | | - |
19 | | -// Gemma4-specific PEG builder extending the standard chat builder. |
20 | | -// Adds value type parsers that use <|\"|> as string delimiters |
21 | | -// instead of JSON's double quotes, and disables json-to-schema |
22 | | -// conversion for these types. |
23 | | -class common_peg_gemma4_builder { |
24 | | - common_chat_peg_builder & p_; |
25 | | - static constexpr const char * QUOTE = "<|\"|>"; |
26 | | - |
27 | | -public: |
28 | | - explicit common_peg_gemma4_builder(common_chat_peg_builder & p) : p_(p) {} |
29 | | - |
30 | | - common_peg_parser gemma4_string() { |
31 | | - return p_.rule("gemma4-string", [&]() { |
32 | | - return p_.literal(QUOTE) + p_.until(QUOTE) + p_.literal(QUOTE); |
33 | | - }); |
34 | | - } |
35 | | - |
36 | | - common_peg_parser gemma4_number() { |
37 | | - return p_.rule("gemma4-number", [&]() { |
38 | | - auto digit1_9 = p_.chars("[1-9]", 1, 1); |
39 | | - auto digits = p_.chars("[0-9]"); |
40 | | - auto int_part = p_.choice({p_.literal("0"), p_.sequence({digit1_9, p_.chars("[0-9]", 0, -1)})}); |
41 | | - auto frac = p_.sequence({p_.literal("."), digits}); |
42 | | - auto exp = p_.sequence({p_.choice({p_.literal("e"), p_.literal("E")}), |
43 | | - p_.optional(p_.chars("[+-]", 1, 1)), digits}); |
44 | | - auto not_number_continuation = p_.negate(p_.chars("[0-9.eE+-]", 1, 1)); |
45 | | - return p_.sequence({p_.optional(p_.literal("-")), int_part, p_.optional(frac), |
46 | | - p_.optional(exp), not_number_continuation}); |
47 | | - }); |
48 | | - } |
49 | | - |
50 | | - common_peg_parser gemma4_bool() { |
51 | | - return p_.rule("gemma4-bool", [&]() { |
52 | | - return p_.choice({p_.literal("true"), p_.literal("false")}); |
53 | | - }); |
54 | | - } |
55 | | - |
56 | | - common_peg_parser gemma4_null() { |
57 | | - return p_.rule("gemma4-null", [&]() { |
58 | | - return p_.literal("null"); |
59 | | - }); |
60 | | - } |
61 | | - |
62 | | - common_peg_parser gemma4_dict() { |
63 | | - return p_.rule("gemma4-dict", [&]() { |
64 | | - auto ws = p_.space(); |
65 | | - auto key = p_.until(":"); |
66 | | - auto member = p_.sequence({key, p_.literal(":"), ws, gemma4_value()}); |
67 | | - auto members = p_.sequence({member, p_.zero_or_more(p_.sequence({p_.literal(","), ws, member}))}); |
68 | | - return p_.sequence({ |
69 | | - p_.literal("{"), ws, |
70 | | - p_.choice({p_.literal("}"), p_.sequence({members, ws, p_.literal("}")})}) |
71 | | - }); |
72 | | - }); |
73 | | - } |
74 | | - |
75 | | - common_peg_parser gemma4_array() { |
76 | | - return p_.rule("gemma4-array", [&]() { |
77 | | - auto ws = p_.space(); |
78 | | - auto elements = p_.sequence({gemma4_value(), p_.zero_or_more(p_.sequence({p_.literal(","), ws, gemma4_value()}))}); |
79 | | - return p_.sequence({ |
80 | | - p_.literal("["), ws, |
81 | | - p_.choice({p_.literal("]"), p_.sequence({elements, ws, p_.literal("]")})}) |
82 | | - }); |
83 | | - }); |
84 | | - } |
85 | | - |
86 | | - common_peg_parser gemma4_value() { |
87 | | - return p_.rule("gemma4-value", [&]() { |
88 | | - return p_.choice({gemma4_string(), gemma4_dict(), gemma4_array(), |
89 | | - gemma4_number(), gemma4_bool(), gemma4_null()}); |
90 | | - }); |
91 | | - } |
92 | | - |
93 | | - // Select the appropriate value parser based on JSON schema type. |
94 | | - // Does NOT use schema() - the gemma4 types are pure PEG without |
95 | | - // JSON schema metadata, so GBNF is generated directly from the |
96 | | - // PEG structure. |
97 | | - common_peg_parser gemma4_value_for_type(const json & schema) { |
98 | | - if (!schema.contains("type") || !schema.at("type").is_string()) { |
99 | | - return gemma4_value(); |
100 | | - } |
101 | | - std::string type = schema.at("type").get<std::string>(); |
102 | | - if (type == "string") { return gemma4_string(); } |
103 | | - if (type == "number") { return gemma4_number(); } |
104 | | - if (type == "integer") { return gemma4_number(); } |
105 | | - if (type == "boolean") { return gemma4_bool(); } |
106 | | - if (type == "object") { return gemma4_dict(); } |
107 | | - if (type == "array") { return gemma4_array(); } |
108 | | - return gemma4_value(); |
109 | | - } |
110 | | -}; |
111 | | - |
112 | | -} // anonymous namespace |
113 | | - |
114 | 16 | // Helper to iterate over tools/functions |
115 | 17 | static void foreach_function(const json & tools, const std::function<void(const json &)> & fn) { |
116 | 18 | for (const auto & tool : tools) { |
@@ -142,9 +44,7 @@ common_chat_params peg_generator::generate_parser(const common_chat_template & |
142 | 44 | // Create the result structure |
143 | 45 | common_chat_params data; |
144 | 46 | data.prompt = common_chat_template_direct_apply(tmpl, inputs); |
145 | | - data.format = (autoparser.tools.format.mode == tool_format::TAG_WITH_GEMMA4_DICT) |
146 | | - ? COMMON_CHAT_FORMAT_PEG_GEMMA4 |
147 | | - : COMMON_CHAT_FORMAT_PEG_NATIVE; |
| 47 | + data.format = COMMON_CHAT_FORMAT_PEG_NATIVE; |
148 | 48 | data.preserved_tokens = autoparser.preserved_tokens; |
149 | 49 |
|
150 | 50 | auto parser = autoparser.build_parser(inputs); |
@@ -271,8 +171,6 @@ common_peg_parser analyze_tools::build_parser(parser_build_context & ctx) const |
271 | 171 | return build_tool_parser_tag_json(ctx); |
272 | 172 | case tool_format::TAG_WITH_TAGGED: |
273 | 173 | return build_tool_parser_tag_tagged(ctx); |
274 | | - case tool_format::TAG_WITH_GEMMA4_DICT: |
275 | | - return build_tool_parser_tag_gemma4_dict(ctx); |
276 | 174 | default: |
277 | 175 | LOG_ERR("[ERROR] Template seems to support tool calls, but failed to determine tool format. Tool calling will not work properly. " |
278 | 176 | "Check for a fixed template for your model in the models/templates directory of your llama.cpp installation or " |
@@ -586,145 +484,4 @@ common_peg_parser analyze_tools::build_tool_parser_tag_tagged(parser_build_conte |
586 | 484 | p.end(); |
587 | 485 | } |
588 | 486 |
|
589 | | -common_peg_parser analyze_tools::build_tool_parser_tag_gemma4_dict(parser_build_context & ctx) const { |
590 | | - auto & p = ctx.p; |
591 | | - const auto & inputs = ctx.inputs; |
592 | | - bool force_tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED; |
593 | | - |
594 | | - common_peg_gemma4_builder g4(p); |
595 | | - static const std::string QUOTE = "<|\"|>"; |
596 | | - |
597 | | - common_peg_parser tool_choice = p.choice(); |
598 | | - |
599 | | - foreach_function(inputs.tools, [&](const json & tool) { |
600 | | - const auto & func = tool.at("function"); |
601 | | - std::string name = func.at("name"); |
602 | | - const auto & params = func.at("parameters"); |
603 | | - |
604 | | - if (!params.contains("properties") || !params.at("properties").is_object()) { |
605 | | - auto func_parser = p.atomic( |
606 | | - p.tool_open(p.literal(function.name_prefix) + p.tool_name(p.literal(name)) + p.literal("{")) + |
607 | | - p.tool_args(p.eps()) + |
608 | | - p.tool_close(p.literal("}"))); |
609 | | - tool_choice |= p.rule("tool-" + name, func_parser); |
610 | | - return; |
611 | | - } |
612 | | - |
613 | | - const auto & properties = params.at("properties"); |
614 | | - std::set<std::string> required; |
615 | | - if (params.contains("required") && params.at("required").is_array()) { |
616 | | - params.at("required").get_to(required); |
617 | | - } |
618 | | - |
619 | | - // Build per-argument parsers, sorted alphabetically (matching template's dictsort) |
620 | | - struct arg_entry { |
621 | | - std::string param_name; |
622 | | - common_peg_parser parser; |
623 | | - }; |
624 | | - std::vector<arg_entry> arg_entries; |
625 | | - |
626 | | - for (const auto & [param_name, param_schema] : properties.items()) { |
627 | | - std::string type = "object"; |
628 | | - if (param_schema.contains("type")) { |
629 | | - const auto & type_v = param_schema.at("type"); |
630 | | - if (type_v.is_string()) { |
631 | | - type_v.get_to(type); |
632 | | - } else if (type_v.is_array()) { |
633 | | - // Handle nullable types like ["string", "null"] |
634 | | - for (const auto & t : type_v) { |
635 | | - if (t.is_string() && t.get<std::string>() != "null") { |
636 | | - type = t.get<std::string>(); |
637 | | - break; |
638 | | - } |
639 | | - } |
640 | | - } |
641 | | - } |
642 | | - // Infer string type from enum values when type is unspecified |
643 | | - if (type == "object" && param_schema.contains("enum")) { |
644 | | - const auto & enum_vals = param_schema.at("enum"); |
645 | | - if (enum_vals.is_array()) { |
646 | | - for (const auto & v : enum_vals) { |
647 | | - if (v.is_string()) { |
648 | | - type = "string"; |
649 | | - break; |
650 | | - } |
651 | | - } |
652 | | - } |
653 | | - } |
654 | | - |
655 | | - common_peg_parser value_parser = p.eps(); |
656 | | - if (type == "string") { |
657 | | - // String values are delimited by <|"|>...<|"|> |
658 | | - value_parser = |
659 | | - p.literal(QUOTE) + |
660 | | - p.tool_arg_string_value(p.schema(p.until(QUOTE), |
661 | | - "tool-" + name + "-arg-" + param_name + "-schema", param_schema, true)) + |
662 | | - p.literal(QUOTE); |
663 | | - } else if (type == "number" || type == "integer") { |
664 | | - value_parser = p.tool_arg_value(g4.gemma4_number()); |
665 | | - } else if (type == "boolean") { |
666 | | - value_parser = p.tool_arg_value(g4.gemma4_bool()); |
667 | | - } else if (type == "null") { |
668 | | - value_parser = p.tool_arg_value(g4.gemma4_null()); |
669 | | - } else if (type == "object") { |
670 | | - value_parser = p.tool_arg_value(g4.gemma4_dict()); |
671 | | - } else if (type == "array") { |
672 | | - value_parser = p.tool_arg_value(g4.gemma4_array()); |
673 | | - } else { |
674 | | - value_parser = p.tool_arg_value(g4.gemma4_value()); |
675 | | - } |
676 | | - |
677 | | - auto arg = p.tool_arg( |
678 | | - p.tool_arg_open(p.tool_arg_name(p.literal(param_name)) + p.literal(":")) + |
679 | | - value_parser + |
680 | | - p.tool_arg_close(p.eps())); |
681 | | - |
682 | | - arg_entries.push_back({param_name, p.rule("tool-" + name + "-arg-" + param_name, arg)}); |
683 | | - } |
684 | | - |
685 | | - // Sort alphabetically to match Jinja's dictsort |
686 | | - std::sort(arg_entries.begin(), arg_entries.end(), [](const auto & a, const auto & b) { |
687 | | - return a.param_name < b.param_name; |
688 | | - }); |
689 | | - |
690 | | - // Build arg sequence: any arg, then zero-or-more comma-separated additional args |
691 | | - common_peg_parser args_seq = p.eps(); |
692 | | - if (!arg_entries.empty()) { |
693 | | - common_peg_parser any_arg = p.choice(); |
694 | | - for (auto & entry : arg_entries) { |
695 | | - any_arg |= entry.parser; |
696 | | - } |
697 | | - args_seq = p.optional( |
698 | | - any_arg + p.repeat(p.literal(",") + any_arg, 0, (int) arg_entries.size() - 1)); |
699 | | - } |
700 | | - |
701 | | - // Full parser: call:name{args} |
702 | | - auto func_parser = p.atomic( |
703 | | - p.tool_open(p.literal(function.name_prefix) + p.tool_name(p.literal(name)) + p.literal("{")) + |
704 | | - p.tool_args(args_seq) + |
705 | | - p.tool_close(p.literal("}"))); |
706 | | - |
707 | | - tool_choice |= p.rule("tool-" + name, func_parser); |
708 | | - }); |
709 | | - |
710 | | - // Wrap each call in <|tool_call>...</tool_call|> |
711 | | - auto wrapped_call = p.literal(format.per_call_start) + tool_choice + p.literal(format.per_call_end); |
712 | | - |
713 | | - common_peg_parser tool_calls = p.eps(); |
714 | | - if (inputs.parallel_tool_calls) { |
715 | | - tool_calls = p.trigger_rule("tool-call", wrapped_call + p.zero_or_more(p.space() + wrapped_call)); |
716 | | - } else { |
717 | | - tool_calls = p.trigger_rule("tool-call", wrapped_call); |
718 | | - } |
719 | | - |
720 | | - if (!force_tools) { |
721 | | - tool_calls = p.optional(tool_calls); |
722 | | - } |
723 | | - |
724 | | - auto content_before_tools = p.until_one_of({ format.per_call_start, ctx.reasoning->start }); |
725 | | - return ctx.reasoning_parser + |
726 | | - (force_tools ? p.eps() : p.optional(p.content(content_before_tools) + p.optional(ctx.reasoning_parser))) + |
727 | | - tool_calls + p.end(); |
728 | | -} |
729 | | - |
730 | 487 | } // namespace autoparser |
0 commit comments