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
}