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:
Steve Dudenhoeffer 2024-11-11 00:23:00 -05:00
parent a83d5f9822
commit 7a43e3a5c8
5 changed files with 82 additions and 20 deletions

View File

@ -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
View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}