Repository: ggml-org/llama.cpp
Pull request: Autoparser: True streaming

In the final changes to the autoparser I modified the atomicity constraint to be pretty restrictive due to models such as GLM 4.7-Flash which put arguments directly after the function name, with no markers in between. Now I'm relaxing that constraint so people can look at their favorite assistant building the tool call live ;)

Changed files:
filename: common/chat-auto-parser-generator.cpp
patch: @@ -239,8 +239,6 @@ common_peg_parser analyze_tools::build_tool_parser_tag_json(parser_build_context
         if (!function.close.empty()) {
             func_parser = func_parser + function.close;
         }
-        func_parser = p.atomic(func_parser);
-
         tool_choice |= p.rule("tool-" + name, func_parser);
     });
 
@@ -357,13 +355,31 @@ common_peg_parser analyze_tools::build_tool_parser_tag_tagged(parser_build_conte
 
         // 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_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix))) + call_id.suffix;
+            have_call_id = true;
+            call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix)) + call_id.suffix);
         }
 
-        auto func_parser = p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix) +
-                           call_id_section + p.space() + args_seq;
+        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() && properties.size() > 0) {
+            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));
@@ -377,8 +393,10 @@ common_peg_parser analyze_tools::build_tool_parser_tag_tagged(parser_build_conte
             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);
+        }
 
-        func_parser = p.atomic(func_parser);
         tool_choice |= p.rule("tool-" + name, func_parser);
     });