From 0993a8e865693c8fec335a7d161d0e01b8315091 Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Mon, 11 Nov 2024 00:23:01 -0500 Subject: [PATCH] Fix unmarshalling issues and adjust logging for debugging Modify `FunctionCall` struct to handle arguments as strings. Add debugging logs to facilitate error tracing and improve JSON unmarshalling in various functions. --- anthropic.go | 20 +++++++++++++------- function.go | 11 ++++++----- google.go | 9 ++++++++- openai.go | 12 +++++++++--- toolbox.go | 13 +------------ 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/anthropic.go b/anthropic.go index fb881a0..3ec34f2 100644 --- a/anthropic.go +++ b/anthropic.go @@ -2,6 +2,7 @@ package go_llm import ( "context" + "encoding/json" "fmt" anth "github.com/liushuangls/go-anthropic/v2" "log" @@ -120,13 +121,18 @@ func (a anthropic) responseToLLMResponse(in anth.MessagesResponse) Response { case anth.MessagesContentTypeToolUse: if msg.MessageContentToolUse != nil { - choice.Calls = append(choice.Calls, ToolCall{ - ID: msg.MessageContentToolUse.ID, - FunctionCall: FunctionCall{ - Name: msg.MessageContentToolUse.Name, - Arguments: msg.MessageContentToolUse.Input, - }, - }) + b, e := json.Marshal(msg.MessageContentToolUse.Input) + if e != nil { + log.Println("failed to marshal input", e) + } else { + choice.Calls = append(choice.Calls, ToolCall{ + ID: msg.MessageContentToolUse.ID, + FunctionCall: FunctionCall{ + Name: msg.MessageContentToolUse.Name, + Arguments: string(b), + }, + }) + } } } diff --git a/function.go b/function.go index 173d4d7..a6f7134 100644 --- a/function.go +++ b/function.go @@ -37,16 +37,17 @@ func (f *Function) Execute(ctx context.Context, input string) (string, error) { } // first, we need to parse the input into the struct - p := reflect.New(f.paramType).Elem() + p := reflect.New(f.paramType) + fmt.Println("Function.Execute", f.Name, "input:", input) //m := map[string]any{} - err := json.Unmarshal([]byte(input), p.Addr().Interface()) + err := json.Unmarshal([]byte(input), p.Interface()) if err != nil { - return "", fmt.Errorf("failed to unmarshal input: %w", err) + return "", fmt.Errorf("failed to unmarshal input: %w (input: %s)", err, input) } // now we can call the function exec := func(ctx context.Context) (string, error) { - out := f.fn.Call([]reflect.Value{reflect.ValueOf(ctx), p}) + out := f.fn.Call([]reflect.Value{reflect.ValueOf(ctx), p.Elem()}) if len(out) != 2 { return "", fmt.Errorf("function %s must return two values, got %d", f.Name, len(out)) @@ -87,5 +88,5 @@ func (f *Function) toOpenAIDefinition() jsonschema.Definition { type FunctionCall struct { Name string `json:"name,omitempty"` - Arguments any `json:"arguments,omitempty"` + Arguments string `json:"arguments,omitempty"` } diff --git a/google.go b/google.go index 3829ec2..c1b92cb 100644 --- a/google.go +++ b/google.go @@ -2,6 +2,7 @@ package go_llm import ( "context" + "encoding/json" "fmt" "github.com/google/generative-ai-go/genai" "google.golang.org/api/option" @@ -63,11 +64,17 @@ func (g google) responseToLLMResponse(in *genai.GenerateContentResponse) (Respon choice := ResponseChoice{} choice.Content = v.Name + b, e := json.Marshal(v.Args) + + if e != nil { + return Response{}, fmt.Errorf("error marshalling args: %w", e) + } + call := ToolCall{ ID: v.Name, FunctionCall: FunctionCall{ Name: v.Name, - Arguments: v.Args, + Arguments: string(b), }, } diff --git a/openai.go b/openai.go index d228c57..75894b0 100644 --- a/openai.go +++ b/openai.go @@ -64,9 +64,11 @@ func (o openaiImpl) requestToOpenAIRequest(request Request) oai.ChatCompletionRe Name: tool.Name, Description: tool.Description, Strict: tool.Strict, - Parameters: tool.Parameters, + Parameters: tool.Parameters.Definition(), }, }) + + fmt.Println("tool:", tool.Name, tool.Description, tool.Strict, tool.Parameters.Definition()) } if request.Temperature != nil { @@ -94,8 +96,9 @@ func (o openaiImpl) responseToLLMResponse(response oai.ChatCompletionResponse) R res := Response{} for _, choice := range response.Choices { - var tools []ToolCall + var toolCalls []ToolCall for _, call := range choice.Message.ToolCalls { + fmt.Println("responseToLLMResponse: call:", call.Function.Arguments) toolCall := ToolCall{ ID: call.ID, FunctionCall: FunctionCall{ @@ -104,7 +107,9 @@ func (o openaiImpl) responseToLLMResponse(response oai.ChatCompletionResponse) R }, } - tools = append(tools, toolCall) + fmt.Println("toolCall.FunctionCall.Arguments:", toolCall.FunctionCall.Arguments) + + toolCalls = append(toolCalls, toolCall) } res.Choices = append(res.Choices, ResponseChoice{ @@ -112,6 +117,7 @@ func (o openaiImpl) responseToLLMResponse(response oai.ChatCompletionResponse) R Role: Role(choice.Message.Role), Name: choice.Message.Name, Refusal: choice.Message.Refusal, + Calls: toolCalls, }) } diff --git a/toolbox.go b/toolbox.go index b95fd2d..0cb34a5 100644 --- a/toolbox.go +++ b/toolbox.go @@ -2,11 +2,9 @@ package go_llm import ( "context" - "encoding/json" "errors" "fmt" "github.com/sashabaranov/go-openai" - "log/slog" ) // ToolBox is a collection of tools that OpenAI can use to execute functions. @@ -69,8 +67,6 @@ var ( func (t *ToolBox) ExecuteFunction(ctx context.Context, functionName string, params string) (string, error) { f, ok := t.names[functionName] - slog.Info("functionName", functionName) - if !ok { return "", newError(ErrFunctionNotFound, fmt.Errorf("function \"%s\" not found", functionName)) } @@ -79,12 +75,5 @@ func (t *ToolBox) ExecuteFunction(ctx context.Context, functionName string, para } func (t *ToolBox) Execute(ctx context.Context, toolCall ToolCall) (string, error) { - slog.Info("toolCall", toolCall) - - b, err := json.Marshal(toolCall.FunctionCall.Arguments) - - if err != nil { - return "", fmt.Errorf("failed to marshal arguments: %w", err) - } - return t.ExecuteFunction(ctx, toolCall.ID, string(b)) + return t.ExecuteFunction(ctx, toolCall.FunctionCall.Name, toolCall.FunctionCall.Arguments) }