answer/pkg/agents/reader/extractor.go
Steve Dudenhoeffer 693ac4e6a7 Add core implementation for AI-powered question answering
Introduce multiple agents, tools, and utilities for processing, extracting, and answering user-provided questions using LLMs and external data. Key features include knowledge processing, question splitting, search term generation, and contextual knowledge handling.
2025-03-21 11:10:48 -04:00

143 lines
3.8 KiB
Go

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
}