mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-17 21:40:07 -04:00
The ToProto conversion was dropping tool_call_id and reasoning_content even though both proto and Go fields existed, breaking multi-turn tool calling and reasoning passthrough to backends.
95 lines
2.8 KiB
Go
95 lines
2.8 KiB
Go
package schema
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/mudler/xlog"
|
|
|
|
"github.com/mudler/LocalAI/pkg/grpc/proto"
|
|
)
|
|
|
|
type Message struct {
|
|
// The message role
|
|
Role string `json:"role,omitempty" yaml:"role"`
|
|
|
|
// The message name (used for tools calls)
|
|
Name string `json:"name,omitempty" yaml:"name"`
|
|
|
|
// The message content
|
|
Content any `json:"content" yaml:"content"`
|
|
|
|
StringContent string `json:"string_content,omitempty" yaml:"string_content,omitempty"`
|
|
StringImages []string `json:"string_images,omitempty" yaml:"string_images,omitempty"`
|
|
StringVideos []string `json:"string_videos,omitempty" yaml:"string_videos,omitempty"`
|
|
StringAudios []string `json:"string_audios,omitempty" yaml:"string_audios,omitempty"`
|
|
|
|
// A result of a function call
|
|
FunctionCall any `json:"function_call,omitempty" yaml:"function_call,omitempty"`
|
|
|
|
ToolCalls []ToolCall `json:"tool_calls,omitempty" yaml:"tool_call,omitempty"`
|
|
|
|
ToolCallID string `json:"tool_call_id,omitempty" yaml:"tool_call_id,omitempty"`
|
|
|
|
// Reasoning content extracted from <thinking>...</thinking> tags
|
|
Reasoning *string `json:"reasoning,omitempty" yaml:"reasoning,omitempty"`
|
|
}
|
|
|
|
type ToolCall struct {
|
|
Index int `json:"index"`
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
FunctionCall FunctionCall `json:"function"`
|
|
}
|
|
|
|
type FunctionCall struct {
|
|
Name string `json:"name,omitempty"`
|
|
Arguments string `json:"arguments"`
|
|
}
|
|
|
|
type Messages []Message
|
|
|
|
// MessagesToProto converts schema.Message slice to proto.Message slice
|
|
// It handles content conversion, tool_calls serialization, and optional fields
|
|
func (messages Messages) ToProto() []*proto.Message {
|
|
protoMessages := make([]*proto.Message, len(messages))
|
|
for i, message := range messages {
|
|
protoMessages[i] = &proto.Message{
|
|
Role: message.Role,
|
|
Name: message.Name, // needed by function calls
|
|
}
|
|
|
|
switch ct := message.Content.(type) {
|
|
case string:
|
|
protoMessages[i].Content = ct
|
|
case []any:
|
|
// If using the tokenizer template, in case of multimodal we want to keep the multimodal content as and return only strings here
|
|
data, _ := json.Marshal(ct)
|
|
resultData := []struct {
|
|
Text string `json:"text"`
|
|
}{}
|
|
json.Unmarshal(data, &resultData)
|
|
for _, r := range resultData {
|
|
protoMessages[i].Content += r.Text
|
|
}
|
|
}
|
|
|
|
// Serialize tool_calls to JSON string if present
|
|
if len(message.ToolCalls) > 0 {
|
|
toolCallsJSON, err := json.Marshal(message.ToolCalls)
|
|
if err != nil {
|
|
xlog.Warn("failed to marshal tool_calls to JSON", "error", err)
|
|
} else {
|
|
protoMessages[i].ToolCalls = string(toolCallsJSON)
|
|
}
|
|
}
|
|
|
|
if message.ToolCallID != "" {
|
|
protoMessages[i].ToolCallId = message.ToolCallID
|
|
}
|
|
if message.Reasoning != nil {
|
|
protoMessages[i].ReasoningContent = *message.Reasoning
|
|
}
|
|
}
|
|
return protoMessages
|
|
}
|