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.
This commit is contained in:
		@@ -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(), " ")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								pkg/cache/directory.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								pkg/cache/directory.go
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user