@@ -166,6 +166,8 @@ common_peg_parser analyze_tools::build_parser(parser_build_context & ctx) const
166166 return build_tool_parser_tag_json (ctx);
167167 case tool_format::TAG_WITH_TAGGED :
168168 return build_tool_parser_tag_tagged (ctx);
169+ case tool_format::TAG_WITH_GEMMA4_DICT :
170+ return build_tool_parser_tag_gemma4_dict (ctx);
169171 default :
170172 LOG_ERR (" [ERROR] Template seems to support tool calls, but failed to determine tool format. Tool calling will not work properly. "
171173 " Check for a fixed template for your model in the models/templates directory of your llama.cpp installation or "
@@ -437,4 +439,113 @@ common_peg_parser analyze_tools::build_tool_parser_tag_tagged(parser_build_conte
437439 p.end ();
438440}
439441
442+ common_peg_parser analyze_tools::build_tool_parser_tag_gemma4_dict (parser_build_context & ctx) const {
443+ auto & p = ctx.p ;
444+ const auto & inputs = ctx.inputs ;
445+ bool force_tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ;
446+
447+ // The Gemma4 string quote token used in place of JSON "
448+ static const std::string QUOTE = " <|\" |>" ;
449+
450+ common_peg_parser tool_choice = p.choice ();
451+
452+ foreach_function (inputs.tools , [&](const json & tool) {
453+ const auto & func = tool.at (" function" );
454+ std::string name = func.at (" name" );
455+ const auto & params = func.at (" parameters" );
456+
457+ if (!params.contains (" properties" ) || !params.at (" properties" ).is_object ()) {
458+ // No arguments - just match the function name with empty braces
459+ auto func_parser = p.atomic (
460+ p.tool_open (p.literal (function.name_prefix ) + p.tool_name (p.literal (name)) + p.literal (" {" )) +
461+ p.tool_args (p.eps ()) +
462+ p.tool_close (p.literal (" }" )));
463+ tool_choice |= p.rule (" tool-" + name, func_parser);
464+ return ;
465+ }
466+
467+ const auto & properties = params.at (" properties" );
468+ std::set<std::string> required;
469+ if (params.contains (" required" ) && params.at (" required" ).is_array ()) {
470+ params.at (" required" ).get_to (required);
471+ }
472+
473+ // Build per-argument parsers, sorted alphabetically (matching template's dictsort)
474+ struct arg_entry {
475+ std::string param_name;
476+ common_peg_parser parser;
477+ };
478+ std::vector<arg_entry> arg_entries;
479+
480+ for (const auto & [param_name, param_schema] : properties.items ()) {
481+ std::string type = " object" ;
482+ auto type_v = param_schema.contains (" type" ) ? param_schema.at (" type" ) : json::object ();
483+ if (type_v.is_string ()) type_v.get_to (type);
484+
485+ common_peg_parser value_parser = p.eps ();
486+ if (type == " string" ) {
487+ // String values are delimited by <|"|>...<|"|>
488+ value_parser =
489+ p.literal (QUOTE ) +
490+ p.tool_arg_string_value (p.schema (p.until (QUOTE ),
491+ " tool-" + name + " -arg-" + param_name + " -schema" , param_schema, true )) +
492+ p.literal (QUOTE );
493+ } else {
494+ // Numbers, booleans: raw text up to the next comma or closing brace
495+ value_parser = p.tool_arg_value (p.until_one_of ({" ," , " }" }));
496+ }
497+
498+ auto arg = p.tool_arg (
499+ p.tool_arg_open (p.tool_arg_name (p.literal (param_name)) + p.literal (" :" )) +
500+ value_parser +
501+ p.tool_arg_close (p.eps ()));
502+
503+ arg_entries.push_back ({param_name, p.rule (" tool-" + name + " -arg-" + param_name, arg)});
504+ }
505+
506+ // Sort alphabetically to match Jinja's dictsort
507+ std::sort (arg_entries.begin (), arg_entries.end (), [](const auto & a, const auto & b) {
508+ return a.param_name < b.param_name ;
509+ });
510+
511+ // Build arg sequence: any arg, then zero-or-more comma-separated additional args
512+ common_peg_parser args_seq = p.eps ();
513+ if (!arg_entries.empty ()) {
514+ common_peg_parser any_arg = p.choice ();
515+ for (auto & entry : arg_entries) {
516+ any_arg |= entry.parser ;
517+ }
518+ args_seq = p.optional (
519+ any_arg + p.repeat (p.literal (" ," ) + any_arg, 0 , (int ) arg_entries.size () - 1 ));
520+ }
521+
522+ // Full parser: call:name{args}
523+ auto func_parser = p.atomic (
524+ p.tool_open (p.literal (function.name_prefix ) + p.tool_name (p.literal (name)) + p.literal (" {" )) +
525+ p.tool_args (args_seq) +
526+ p.tool_close (p.literal (" }" )));
527+
528+ tool_choice |= p.rule (" tool-" + name, func_parser);
529+ });
530+
531+ // Wrap each call in <|tool_call>...</tool_call|>
532+ auto wrapped_call = p.literal (format.per_call_start ) + tool_choice + p.literal (format.per_call_end );
533+
534+ common_peg_parser tool_calls = p.eps ();
535+ if (inputs.parallel_tool_calls ) {
536+ tool_calls = p.trigger_rule (" tool-call" , wrapped_call + p.zero_or_more (p.space () + wrapped_call));
537+ } else {
538+ tool_calls = p.trigger_rule (" tool-call" , wrapped_call);
539+ }
540+
541+ if (!force_tools) {
542+ tool_calls = p.optional (tool_calls);
543+ }
544+
545+ auto content_before_tools = p.until (format.per_call_start );
546+ return ctx.reasoning_parser +
547+ (force_tools ? p.eps () : p.optional (p.content (content_before_tools))) +
548+ tool_calls + p.end ();
549+ }
550+
440551} // namespace autoparser
0 commit comments