diff --git a/pkg/answer/answer.go b/pkg/answer/answer.go index 00586d1..7243838 100644 --- a/pkg/answer/answer.go +++ b/pkg/answer/answer.go @@ -36,7 +36,13 @@ type Question struct { // Answers is a list of answers to a question type Answers []string -const DefaultPrompt = "You are being asked to answer a question. You must respond with a function. You can answer it if you know the answer, or if some functions exist you can use those to help you find the answer." +const DefaultPrompt = `You are being asked to answer a question. +You must respond with a function. +You can answer it if you know the answer, or if some functions exist you can use those to help you find the answer. +You can break up the question into multiple steps, and will learn from questions. Such as, if you need to compute the +exact square root of the powerball jackpot, you could search for the current jackpot and then when search finishes +you could execute calculate sqrt(that value), and respond with that. +` type Options struct { // MaxSearches is the maximum possible number of searches to execute for this question. If this is set to 5, the function could @@ -277,7 +283,9 @@ func functionThink(ctx *gollm.Context, q Question) (string, error) { return req.Toolbox.Execute(ctx, res.Choices[0].Calls[0]) } -func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { +func (o Options) Answer(ctx context.Context, q Question) (string, error) { + var answer string + fnSearch := gollm.NewFunction( "search", "Search the web for an answer to a question. You can call this function up to "+fmt.Sprint(o.MaxSearches)+" times.", @@ -288,6 +296,9 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { q2 := q q2.Question = args.Question + if o.MaxThinks > 0 { + o.MaxSearches = o.MaxSearches - 1 + } return functionSearch(ctx, q2, args.SearchQuery) }) @@ -300,6 +311,10 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { q2 := q q2.Question = args.Question + if o.MaxThinks > 0 { + o.MaxThinks = o.MaxThinks - 1 + } + return functionThink(ctx, q2) }) @@ -309,6 +324,7 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { func(ctx *gollm.Context, args struct { Answer string `json:"answer" description:"the answer to the question"` }) (string, error) { + answer = args.Answer return args.Answer, nil }) @@ -395,88 +411,105 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) { Temperature: &temp, } - res, err := q.Model.ChatComplete(ctx, req) + // runAnswer will run the question and try to find the answer. It will return the next request to ask, if needed + // or any error encountered. + runAnswer := func(o Options, req gollm.Request) (*gollm.Request, error) { - if err != nil { - return nil, err - } + slog.Info("running answer", "question", q.Question, "req", req) - if len(res.Choices) == 0 { - return nil, nil - } + for _, c := range req.Conversation { + slog.Info("conversation", "conversation", c) + } - if len(res.Choices) > o.MaxSearches { - res.Choices = res.Choices[:o.MaxSearches] - } + for _, m := range req.Messages { + slog.Info("message", "message", m) + } - var answers []Result - for _, choice := range res.Choices { - fnChoice := func(choice gollm.ResponseChoice) []Result { - var calls []Result - var callsOutput = make(chan Result, len(choice.Calls)) - fnCall := func(call gollm.ToolCall) Result { - str, err := req.Toolbox.Execute(gollm.NewContext(ctx, req, &choice, &call), call) + res, err := q.Model.ChatComplete(ctx, req) - if err != nil { - return Result{ - Error: err, - } - } + if err != nil { + return nil, err + } + if len(res.Choices) == 0 { + return nil, fmt.Errorf("no response choices provided") + } - return Result{ - Result: str, + if len(res.Choices) > o.MaxSearches { + res.Choices = res.Choices[:o.MaxSearches] + } + + var answers []gollm.ToolCallResponse + + choice := res.Choices[0] + var callsOutput = make(chan gollm.ToolCallResponse, len(choice.Calls)) + fnCall := func(call gollm.ToolCall) gollm.ToolCallResponse { + str, err := req.Toolbox.Execute(gollm.NewContext(ctx, req, &choice, &call), call) + + if err != nil { + return gollm.ToolCallResponse{ + ID: call.ID, + Error: err, } } - 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 + return gollm.ToolCallResponse{ + ID: call.ID, + Result: str, + } + } + + 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 <- gollm.ToolCallResponse{ + ID: call.ID, + Error: err, } + return } - callsOutput <- fnCall(call) - }(call) - } - - for i := 0; i < len(choice.Calls); i++ { - result := <-callsOutput - calls = append(calls, result) - } - - close(callsOutput) - - slog.Info("calls", "calls", calls) - return calls + } + callsOutput <- fnCall(call) + }(call) } - answers = append(answers, fnChoice(choice)...) - } - - var errs []error - var results []string - - for _, answer := range answers { - if answer.Error != nil { - errs = append(errs, answer.Error) - continue + for i := 0; i < len(choice.Calls); i++ { + result := <-callsOutput + answers = append(answers, result) } - results = append(results, answer.Result) + close(callsOutput) + + slog.Info("generating new request", "answers", answers, "choice", choice) + newReq := gollm.NewContext(ctx, req, &choice, nil).ToNewRequest(answers...) + + return &newReq, nil } - if len(errs) > 0 { - return nil, errors.Join(errs...) + maxTries := o.MaxTries + + for i := 0; i < maxTries; i++ { + newReq, err := runAnswer(o, req) + + if err != nil { + return "", err + } + + if newReq == nil { + break + } + + if answer != "" { + break + } + + req = *newReq } - return results, nil - + return answer, nil } -func Answer(ctx context.Context, q Question) (Answers, error) { +func Answer(ctx context.Context, q Question) (string, error) { return DefaultOptions.Answer(ctx, q) }