package reader import ( "context" "fmt" "net/url" "strings" "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared" "gitea.stevedudenhoeffer.com/steve/answer/pkg/cache" "gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor" gollm "gitea.stevedudenhoeffer.com/steve/go-llm" ) type article struct { URL string Title string Body string } func extractArticle(ctx context.Context, c cache.Cache, u *url.URL) (res article, err error) { defer func() { e := recover() if e != nil { if e, ok := e.(error); ok { err = fmt.Errorf("panic: %w", e) } else { err = fmt.Errorf("panic: %v", e) } } }() extractors := extractor.MultiExtractor( extractor.CacheExtractor{ Cache: c, Tag: "goose", Extractor: extractor.GooseExtractor{}, }, extractor.CacheExtractor{ Cache: c, Tag: "playwright", Extractor: extractor.PlaywrightExtractor{}, }, ) a, err := extractors.Extract(ctx, u.String()) if err != nil { return article{ URL: "", Title: "", Body: "", }, err } return article{ URL: a.URL, Title: a.Title, Body: a.Body, }, nil } type Response struct { Knowledge []string Remaining string } type Learn struct { Info string `description:"The information to learn from the text."` } func doesTextAnswerQuestion(ctx context.Context, question string, text string, source string, a Agent) (shared.Knowledge, error) { var knowledge shared.Knowledge fnAnswer := gollm.NewFunction( "learn", `Use learn to pass some relevant information to the model. The model will use this information to answer the question. Use it to learn relevant information from the text. Keep these concise and relevant to the question.`, func(ctx *gollm.Context, args Learn) (string, error) { knowledge.Knowledge = append(knowledge.Knowledge, shared.TidBit{Info: args.Info, Source: source}) return "", nil }) fnNoAnswer := gollm.NewFunction( "finished", "Indicate that the text does not answer the question.", func(ctx *gollm.Context, args struct { Remaining string `description:"After all the knowledge has been learned, this is the parts of the question that are not answered, if any. Leave this blank if the text fully answers the question."` }) (string, error) { knowledge.RemainingQuestions = []string{args.Remaining} return "", nil }) req := gollm.Request{ Messages: []gollm.Message{ { Role: gollm.RoleSystem, Text: `Evaluate the given text to see if you can answer any information from it relevant to the question that the user asks. Use the "learn" function to pass relevant information to the model. You can use the "learn" function multiple times to pass multiple pieces of relevant information to the model. If the text does not answer the question or you are done using "learn" to pass on knowledge then use the "finished" function and indicate the parts of the question that are not answered by anything learned. You can call "learn" multiple times before calling "finished".`, }, { Role: gollm.RoleSystem, Text: "The text to evaluate: " + text, }, }, Toolbox: gollm.NewToolBox(fnAnswer, fnNoAnswer), } if len(a.ContextualInformation) > 0 { req.Messages = append(req.Messages, gollm.Message{ Role: gollm.RoleSystem, Text: "Some contextual information you should be aware of: " + strings.Join(a.ContextualInformation, "\n"), }) } req.Messages = append(req.Messages, gollm.Message{ Role: gollm.RoleUser, Text: "My question to learn from the text is: " + question, }) resp, err := a.Model.ChatComplete(ctx, req) if err != nil { return knowledge, err } if len(resp.Choices) == 0 { return knowledge, nil } choice := resp.Choices[0] if len(choice.Calls) == 0 { return knowledge, nil } _, err = req.Toolbox.ExecuteCallbacks(gollm.NewContext(ctx, req, &choice, nil), choice.Calls, nil, nil) return knowledge, err }