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