Refactor answer logic to improve question handling.

Updated the `Answer` method to better handle multi-step questions and manage retries. Introduced detailed step breakdowns and streamlined the function's structure for clarity and maintainability. Enhanced the default prompt for improved instruction clarity.
This commit is contained in:
Steve Dudenhoeffer 2025-03-17 00:20:07 -04:00
parent 5b338d4129
commit a5a669f9a6

View File

@ -36,7 +36,13 @@ type Question struct {
// Answers is a list of answers to a question // Answers is a list of answers to a question
type Answers []string 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 { 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 // 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]) 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( fnSearch := gollm.NewFunction(
"search", "search",
"Search the web for an answer to a question. You can call this function up to "+fmt.Sprint(o.MaxSearches)+" times.", "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 := q
q2.Question = args.Question q2.Question = args.Question
if o.MaxThinks > 0 {
o.MaxSearches = o.MaxSearches - 1
}
return functionSearch(ctx, q2, args.SearchQuery) return functionSearch(ctx, q2, args.SearchQuery)
}) })
@ -300,6 +311,10 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) {
q2 := q q2 := q
q2.Question = args.Question q2.Question = args.Question
if o.MaxThinks > 0 {
o.MaxThinks = o.MaxThinks - 1
}
return functionThink(ctx, q2) 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 { func(ctx *gollm.Context, args struct {
Answer string `json:"answer" description:"the answer to the question"` Answer string `json:"answer" description:"the answer to the question"`
}) (string, error) { }) (string, error) {
answer = args.Answer
return args.Answer, nil return args.Answer, nil
}) })
@ -395,35 +411,49 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) {
Temperature: &temp, Temperature: &temp,
} }
// 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) {
slog.Info("running answer", "question", q.Question, "req", req)
for _, c := range req.Conversation {
slog.Info("conversation", "conversation", c)
}
for _, m := range req.Messages {
slog.Info("message", "message", m)
}
res, err := q.Model.ChatComplete(ctx, req) res, err := q.Model.ChatComplete(ctx, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(res.Choices) == 0 { if len(res.Choices) == 0 {
return nil, nil return nil, fmt.Errorf("no response choices provided")
} }
if len(res.Choices) > o.MaxSearches { if len(res.Choices) > o.MaxSearches {
res.Choices = res.Choices[:o.MaxSearches] res.Choices = res.Choices[:o.MaxSearches]
} }
var answers []Result var answers []gollm.ToolCallResponse
for _, choice := range res.Choices {
fnChoice := func(choice gollm.ResponseChoice) []Result { choice := res.Choices[0]
var calls []Result var callsOutput = make(chan gollm.ToolCallResponse, len(choice.Calls))
var callsOutput = make(chan Result, len(choice.Calls)) fnCall := func(call gollm.ToolCall) gollm.ToolCallResponse {
fnCall := func(call gollm.ToolCall) Result {
str, err := req.Toolbox.Execute(gollm.NewContext(ctx, req, &choice, &call), call) str, err := req.Toolbox.Execute(gollm.NewContext(ctx, req, &choice, &call), call)
if err != nil { if err != nil {
return Result{ return gollm.ToolCallResponse{
ID: call.ID,
Error: err, Error: err,
} }
} }
return Result{ return gollm.ToolCallResponse{
ID: call.ID,
Result: str, Result: str,
} }
} }
@ -433,7 +463,8 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) {
if o.OnNewFunction != nil { if o.OnNewFunction != nil {
err := o.OnNewFunction(ctx, call.FunctionCall.Name, q.Question, call.FunctionCall.Arguments) err := o.OnNewFunction(ctx, call.FunctionCall.Name, q.Question, call.FunctionCall.Arguments)
if err != nil { if err != nil {
callsOutput <- Result{ callsOutput <- gollm.ToolCallResponse{
ID: call.ID,
Error: err, Error: err,
} }
return return
@ -445,38 +476,40 @@ func (o Options) Answer(ctx context.Context, q Question) (Answers, error) {
for i := 0; i < len(choice.Calls); i++ { for i := 0; i < len(choice.Calls); i++ {
result := <-callsOutput result := <-callsOutput
calls = append(calls, result) answers = append(answers, result)
} }
close(callsOutput) close(callsOutput)
slog.Info("calls", "calls", calls) slog.Info("generating new request", "answers", answers, "choice", choice)
return calls newReq := gollm.NewContext(ctx, req, &choice, nil).ToNewRequest(answers...)
return &newReq, nil
} }
answers = append(answers, fnChoice(choice)...) maxTries := o.MaxTries
for i := 0; i < maxTries; i++ {
newReq, err := runAnswer(o, req)
if err != nil {
return "", err
} }
var errs []error if newReq == nil {
var results []string break
for _, answer := range answers {
if answer.Error != nil {
errs = append(errs, answer.Error)
continue
} }
results = append(results, answer.Result) if answer != "" {
break
} }
if len(errs) > 0 { req = *newReq
return nil, errors.Join(errs...)
} }
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) return DefaultOptions.Answer(ctx, q)
} }