From 7a43e3a5c8aa4df17b37da36af0b69f97df1ded9 Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Mon, 11 Nov 2024 00:23:00 -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. --- cmd/answer.go | 12 +++++++++ go.mod | 3 +++ pkg/answer/answer.go | 56 ++++++++++++++++++++++++++++++++++-------- pkg/cache/directory.go | 16 ++++++++++-- pkg/search/google.go | 15 ++++++----- 5 files changed, 82 insertions(+), 20 deletions(-) diff --git a/cmd/answer.go b/cmd/answer.go index 2f69fe5..3db35d9 100644 --- a/cmd/answer.go +++ b/cmd/answer.go @@ -6,6 +6,7 @@ import ( "answer/pkg/search" "context" gollm "gitea.stevedudenhoeffer.com/steve/go-llm" + "github.com/joho/godotenv" "github.com/urfave/cli" "log/slog" "os" @@ -36,6 +37,12 @@ func main() { Description: "", Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "env-file", + Value: ".env", + Usage: "file to read environment variables from", + }, + &cli.StringFlag{ Name: "model", Value: "openai/gpt-4o", @@ -66,6 +73,11 @@ func main() { if c.NArg() == 0 { return cli.ShowAppHelp(c) } + + if c.String("env-file") != "" { + _ = godotenv.Load(c.String("env-file")) + } + var question answer.Question question.Question = strings.Join(c.Args(), " ") diff --git a/go.mod b/go.mod index 447f57d..c714d5d 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,12 @@ go 1.23.2 replace gitea.stevedudenhoeffer.com/steve/go-llm => ../go-llm +replace github.com/rocketlaunchr/google-search => github.com/chrisjoyce911/google-search v0.0.0-20230910003754-e501aedf805a + require ( gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20241031152103-f603010dee49 github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275 + github.com/joho/godotenv v1.5.1 github.com/playwright-community/playwright-go v0.4702.0 github.com/rocketlaunchr/google-search v1.1.6 github.com/urfave/cli v1.22.16 diff --git a/pkg/answer/answer.go b/pkg/answer/answer.go index 4b73064..899e801 100644 --- a/pkg/answer/answer.go +++ b/pkg/answer/answer.go @@ -44,6 +44,11 @@ type Options struct { // MaxTries is the absolute maximum number of pages to try to get an answer from. For instance, if MaxSearches is 5 and // 5 pages are tried and no answers are found, the function will return ErrMaxTries. MaxTries int + + // OnNewFunction is a callback that, if non-nil, will be called when a new function is called by the LLM. + // The "answer" and "no_answer" functions are not included in this callback. + // Return an error to stop the function from being called. + OnNewFunction func(ctx context.Context, funcName string, question string, parameter string) error } var DefaultOptions = Options{ @@ -152,7 +157,9 @@ func doesTextAnswerQuestion(ctx context.Context, q Question, text string) (strin fnNoAnswer := gollm.NewFunction( "no_answer", "Indicate that the text does not answer the question.", - func(ctx context.Context, args struct{}) (string, error) { + func(ctx context.Context, args struct { + Ignored string `description:"ignored, just here to make sure the function is called. Fill with anything."` + }) (string, error) { return "", nil }) @@ -192,6 +199,8 @@ func doesTextAnswerQuestion(ctx context.Context, q Question, text string) (strin } func functionSearch(ctx context.Context, q Question, searchTerm string) (string, error) { + + slog.Info("searching", "search", searchTerm, "question", q) res, err := q.Search.Search(ctx, searchTerm) if err != nil { return "", err @@ -207,6 +216,8 @@ func functionSearch(ctx context.Context, q Question, searchTerm string) (string, if trimmed == "" { } + + slog.Info("extracting article", "url", trimmed) u, err := url.Parse(trimmed) if err != nil { continue @@ -218,6 +229,8 @@ func functionSearch(ctx context.Context, q Question, searchTerm string) (string, continue } + slog.Info("extracted article", "url", a.URL, "title", a.Title, "body", a.Body) + if a.Title != "" && a.Body != "" { answer, err := doesTextAnswerQuestion(ctx, q, a.Body) @@ -296,7 +309,7 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { "think", "Think about a question. This is useful for breaking down complex questions into smaller parts that are easier to answer.", func(ctx context.Context, args struct { - Question string `description:"the question to think about"` + Question string `json:"question" description:"the question to think about"` }) (string, error) { q2 := q q2.Question = args.Question @@ -308,7 +321,7 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { "answer", "You definitively answer a question, if you call this it means you know the answer and do not need to search for it or use any other function to find it", func(ctx context.Context, args struct { - Answer string `description:"the answer to the question"` + Answer string `json:"answer" description:"the answer to the question"` }) (string, error) { return args.Answer, nil }) @@ -354,11 +367,9 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { res.Choices = res.Choices[:o.MaxSearches] } - var answers Answers - choicesOutput := make(chan string, len(res.Choices)) - + var answers []Result for _, choice := range res.Choices { - fnChoice := func(choice gollm.ResponseChoice) string { + fnChoice := func(choice gollm.ResponseChoice) []Result { var calls []Result var callsOutput = make(chan Result, len(choice.Calls)) fnCall := func(call gollm.ToolCall) Result { @@ -377,6 +388,15 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { for _, call := range choice.Calls { go func(call gollm.ToolCall) { + if o.OnNewFunction != nil { + err := o.OnNewFunction(ctx, call.FunctionCall.Name, q.Question, call.FunctionCall.Arguments) + if err != nil { + callsOutput <- Result{ + Error: err, + } + return + } + } callsOutput <- fnCall(call) }(call) } @@ -388,13 +408,29 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { close(callsOutput) + return calls } - answers = append(answers, fnChoice(choice)) - + answers = append(answers, fnChoice(choice)...) } - return answers, nil + var errs []error + var results []string + + for _, answer := range answers { + if answer.Error != nil { + errs = append(errs, answer.Error) + continue + } + + results = append(results, answer.Result) + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + + return results, nil } diff --git a/pkg/cache/directory.go b/pkg/cache/directory.go index 8cbb32d..ad23438 100644 --- a/pkg/cache/directory.go +++ b/pkg/cache/directory.go @@ -72,6 +72,18 @@ func (d *Directory) AutoCleanupRoutine(ctx context.Context) error { } } +func (d *Directory) openFileForWriting(key string) (*os.File, error) { + path := d.GetPath(key) + + fp, err := os.Create(path) + + if err != nil { + return nil, err + } + + return fp, nil +} + func (d *Directory) openFile(key string) (*os.File, error) { path := d.GetPath(key) @@ -91,7 +103,7 @@ func (d *Directory) Set(key string, value io.Reader) error { d.lock.Lock() defer d.lock.Unlock() - fp, err := d.openFile(key) + fp, err := d.openFileForWriting(key) if err != nil { return err } @@ -109,7 +121,7 @@ func (d *Directory) SetJSON(key string, value any) error { d.lock.Lock() defer d.lock.Unlock() - fp, err := d.openFile(key) + fp, err := d.openFileForWriting(key) if err != nil { return err } diff --git a/pkg/search/google.go b/pkg/search/google.go index cdaf0d7..8a97c34 100644 --- a/pkg/search/google.go +++ b/pkg/search/google.go @@ -4,6 +4,7 @@ import ( "answer/pkg/cache" "context" googlesearch "github.com/rocketlaunchr/google-search" + "log/slog" "sort" ) @@ -20,21 +21,19 @@ func (g Google) Search(ctx context.Context, search string) ([]Result, error) { err := g.Cache.GetJSON(key, &res) + slog.Info("searching", "search", search, "results", res, "err", err) + if err == nil { return res, nil } results, err := googlesearch.Search(ctx, search, googlesearch.SearchOptions{ - CountryCode: "", - LanguageCode: "", - Limit: 0, - Start: 0, - UserAgent: "", - OverLimit: false, - ProxyAddr: "", - FollowNextPage: false, + + UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36", }) + slog.Info("searched", "search", search, "results", results, "err", err) + if err != nil { return nil, err }