package functions import ( "strings" "github.com/mudler/LocalAI/pkg/functions/peg" "github.com/mudler/xlog" ) // PEGFormatType identifies the format type for PEG parsing. type PEGFormatType int const ( FormatJSONNative PEGFormatType = iota FormatTagWithJSON // {"key": "val"} FormatTagWithTagged // value ) // ParseFunctionCallPEG attempts to parse tool calls using the PEG parser. // Returns nil if no tool calls were found. func ParseFunctionCallPEG(llmresult string, config FunctionsConfig) []FuncCallResults { xlog.Debug("[PEG] starting PEG tool call parsing") // If auto-detected markers from the C++ backend are available, use them first if config.ToolFormatMarkers != nil { m := config.ToolFormatMarkers xlog.Debug("[PEG] using auto-detected markers from C++ backend", "format_type", m.FormatType, "section_start", m.SectionStart, "section_end", m.SectionEnd, "per_call_start", m.PerCallStart, "per_call_end", m.PerCallEnd, "func_name_prefix", m.FuncNamePrefix, "func_name_suffix", m.FuncNameSuffix, "func_close", m.FuncClose, "arg_name_prefix", m.ArgNamePrefix, "arg_name_suffix", m.ArgNameSuffix, "arg_value_prefix", m.ArgValuePrefix, "arg_value_suffix", m.ArgValueSuffix, "arg_separator", m.ArgSeparator, "name_field", m.NameField, "args_field", m.ArgsField, "id_field", m.IDField, "reasoning_start", m.ReasoningStart, "reasoning_end", m.ReasoningEnd, ) arena := BuildPEGParserFromMarkers(config.ToolFormatMarkers) if arena != nil { results := parsePEG(arena, llmresult) if len(results) > 0 { xlog.Debug("[PEG] markers-based parser matched", "count", len(results)) return results } xlog.Debug("[PEG] markers-based parser found no tool calls") } else { xlog.Debug("[PEG] failed to build parser from markers") } } // If a specific XML format preset is set, use its PEG format if config.XMLFormatPreset != "" { xlog.Debug("[PEG] trying XML format preset", "preset", config.XMLFormatPreset) preset := GetXMLFormatPreset(config.XMLFormatPreset) if preset != nil { pegType := classifyXMLFormat(preset) xlog.Debug("[PEG] classified preset", "preset", config.XMLFormatPreset, "peg_type", pegTypeName(pegType)) arena := BuildPEGParserFromFormat(preset, pegType) if arena != nil { results := parsePEG(arena, llmresult) if len(results) > 0 { xlog.Debug("[PEG] preset parser matched", "preset", config.XMLFormatPreset, "count", len(results)) return results } xlog.Debug("[PEG] preset parser found no tool calls", "preset", config.XMLFormatPreset) } } else { xlog.Debug("[PEG] unknown preset name", "preset", config.XMLFormatPreset) } } // If a custom XML format is set, classify and try it if config.XMLFormat != nil { pegType := classifyXMLFormat(config.XMLFormat) xlog.Debug("[PEG] trying custom XML format", "peg_type", pegTypeName(pegType)) arena := BuildPEGParserFromFormat(config.XMLFormat, pegType) if arena != nil { results := parsePEG(arena, llmresult) if len(results) > 0 { xlog.Debug("[PEG] custom format parser matched", "count", len(results)) return results } xlog.Debug("[PEG] custom format parser found no tool calls") } } // Auto-detect: try all three format types xlog.Debug("[PEG] auto-detecting format across all presets") for _, pegType := range []PEGFormatType{FormatJSONNative, FormatTagWithJSON, FormatTagWithTagged} { for _, preset := range getAllXMLFormats() { classified := classifyXMLFormat(preset.format) if classified != pegType { continue } arena := BuildPEGParserFromFormat(preset.format, pegType) if arena == nil { continue } results := parsePEG(arena, llmresult) if len(results) > 0 { xlog.Debug("[PEG] auto-detect matched", "preset", preset.name, "peg_type", pegTypeName(pegType), "count", len(results)) return results } } } xlog.Debug("[PEG] no tool calls found by any format") return nil } func pegTypeName(t PEGFormatType) string { switch t { case FormatJSONNative: return "json_native" case FormatTagWithJSON: return "tag_with_json" case FormatTagWithTagged: return "tag_with_tagged" default: return "unknown" } } // classifyXMLFormat determines the PEG format type from an XML format config. func classifyXMLFormat(f *XMLToolCallFormat) PEGFormatType { // If there's an explicit function opener like " 200 { inputPreview = inputPreview[:200] + "..." } xlog.Debug("[PEG] parse did not succeed", "result_type", result.Type, "input_preview", inputPreview) return nil } mapper := &peg.ChatPegMapper{} mapper.FromAST(&ctx.Ast, &result) msg := mapper.Result xlog.Debug("[PEG] parse succeeded", "content_len", len(msg.Content), "reasoning_len", len(msg.ReasoningContent), "tool_calls", len(msg.ToolCalls)) if len(msg.ToolCalls) == 0 { return nil } var results []FuncCallResults for _, tc := range msg.ToolCalls { xlog.Debug("[PEG] extracted tool call", "name", tc.Name, "id", tc.ID, "args_len", len(tc.Arguments)) results = append(results, FuncCallResults{ Name: tc.Name, Arguments: tc.Arguments, ID: tc.ID, }) } return results }