mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-01 13:42:20 -04:00
* feat(functions): add peg-based parsing Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat: support returning toolcalls directly from backends Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore: do run PEG only if backend didn't send deltas Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
108 lines
2.6 KiB
Go
108 lines
2.6 KiB
Go
package functions
|
|
|
|
import (
|
|
"strings"
|
|
|
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
|
"github.com/mudler/xlog"
|
|
)
|
|
|
|
// ToolCallsFromChatDeltas extracts tool calls from C++ autoparser chat deltas.
|
|
// Returns nil if no tool calls are present in the deltas.
|
|
func ToolCallsFromChatDeltas(deltas []*pb.ChatDelta) []FuncCallResults {
|
|
if len(deltas) == 0 {
|
|
xlog.Debug("[ChatDeltas] no chat deltas received from backend")
|
|
return nil
|
|
}
|
|
|
|
// Count what's in the deltas for logging
|
|
totalContentChunks := 0
|
|
totalReasoningChunks := 0
|
|
totalToolCallChunks := 0
|
|
for _, d := range deltas {
|
|
if d.Content != "" {
|
|
totalContentChunks++
|
|
}
|
|
if d.ReasoningContent != "" {
|
|
totalReasoningChunks++
|
|
}
|
|
totalToolCallChunks += len(d.ToolCalls)
|
|
}
|
|
xlog.Debug("[ChatDeltas] received deltas from backend",
|
|
"total_deltas", len(deltas),
|
|
"content_chunks", totalContentChunks,
|
|
"reasoning_chunks", totalReasoningChunks,
|
|
"tool_call_chunks", totalToolCallChunks,
|
|
)
|
|
|
|
type toolCallAccum struct {
|
|
Name string
|
|
Arguments string
|
|
ID string
|
|
}
|
|
byIndex := map[int32]*toolCallAccum{}
|
|
var maxIndex int32 = -1
|
|
|
|
for _, d := range deltas {
|
|
for _, tc := range d.ToolCalls {
|
|
acc, ok := byIndex[tc.Index]
|
|
if !ok {
|
|
acc = &toolCallAccum{}
|
|
byIndex[tc.Index] = acc
|
|
}
|
|
if tc.Name != "" {
|
|
acc.Name = tc.Name
|
|
}
|
|
if tc.Id != "" {
|
|
acc.ID = tc.Id
|
|
}
|
|
acc.Arguments += tc.Arguments
|
|
if tc.Index > maxIndex {
|
|
maxIndex = tc.Index
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(byIndex) == 0 {
|
|
xlog.Debug("[ChatDeltas] deltas present but no tool calls found, falling back to text parsing")
|
|
return nil
|
|
}
|
|
|
|
results := make([]FuncCallResults, 0, len(byIndex))
|
|
for i := int32(0); i <= maxIndex; i++ {
|
|
if acc, ok := byIndex[i]; ok {
|
|
xlog.Debug("[ChatDeltas] extracted tool call",
|
|
"index", i,
|
|
"name", acc.Name,
|
|
"id", acc.ID,
|
|
"args_length", len(acc.Arguments),
|
|
)
|
|
results = append(results, FuncCallResults{
|
|
Name: acc.Name,
|
|
Arguments: acc.Arguments,
|
|
ID: acc.ID,
|
|
})
|
|
}
|
|
}
|
|
xlog.Debug("[ChatDeltas] using C++ autoparser tool calls, skipping Go-side parsing", "count", len(results))
|
|
return results
|
|
}
|
|
|
|
// ContentFromChatDeltas extracts accumulated content text from chat deltas.
|
|
func ContentFromChatDeltas(deltas []*pb.ChatDelta) string {
|
|
var sb strings.Builder
|
|
for _, d := range deltas {
|
|
sb.WriteString(d.Content)
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// ReasoningFromChatDeltas extracts accumulated reasoning text from chat deltas.
|
|
func ReasoningFromChatDeltas(deltas []*pb.ChatDelta) string {
|
|
var sb strings.Builder
|
|
for _, d := range deltas {
|
|
sb.WriteString(d.ReasoningContent)
|
|
}
|
|
return sb.String()
|
|
}
|