Refactor Knowledge struct into shared package
Moved the Knowledge struct and related types to the shared package, updating all references across the codebase. This improves modularity and enables better reuse of the Knowledge type across different components.
This commit is contained in:
parent
d2b9eb350e
commit
1c3ea7d1f1
@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/console"
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/console"
|
||||||
|
|
||||||
@ -102,7 +103,7 @@ func main() {
|
|||||||
|
|
||||||
c := console.Agent{
|
c := console.Agent{
|
||||||
Model: m,
|
Model: m,
|
||||||
OnDone: func(ctx context.Context, knowledge agents.Knowledge) error { return nil },
|
OnDone: func(ctx context.Context, knowledge shared.Knowledge) error { return nil },
|
||||||
OnCommandStart: func(ctx context.Context, command string) error {
|
OnCommandStart: func(ctx context.Context, command string) error {
|
||||||
slog.Info("command", "command", command)
|
slog.Info("command", "command", command)
|
||||||
return nil
|
return nil
|
||||||
|
@ -23,12 +23,12 @@ type Agent struct {
|
|||||||
// Model is the chat completion model to use
|
// Model is the chat completion model to use
|
||||||
Model gollm.ChatCompletion
|
Model gollm.ChatCompletion
|
||||||
|
|
||||||
OnLoopComplete func(ctx context.Context, knowledge agents.Knowledge) error
|
OnLoopComplete func(ctx context.Context, knowledge shared.Knowledge) error
|
||||||
|
|
||||||
OnCommandStart func(ctx context.Context, command string) error
|
OnCommandStart func(ctx context.Context, command string) error
|
||||||
OnCommandDone func(ctx context.Context, command string, output string, err error) error
|
OnCommandDone func(ctx context.Context, command string, output string, err error) error
|
||||||
|
|
||||||
OnDone func(ctx context.Context, knowledge agents.Knowledge) error
|
OnDone func(ctx context.Context, knowledge shared.Knowledge) error
|
||||||
|
|
||||||
ContextualInformation []string
|
ContextualInformation []string
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ type Agent struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Knowledge agents.Knowledge
|
Knowledge shared.Knowledge
|
||||||
DataDir string
|
DataDir string
|
||||||
OutputDir string
|
OutputDir string
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
|
|||||||
a.MaxCommands = 20 // Default to 20 commands as per requirements
|
a.MaxCommands = 20 // Default to 20 commands as per requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Knowledge = agents.Knowledge{
|
res.Knowledge = shared.Knowledge{
|
||||||
OriginalQuestions: questions,
|
OriginalQuestions: questions,
|
||||||
RemainingQuestions: questions,
|
RemainingQuestions: questions,
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,13 @@ type ConsoleConfig struct {
|
|||||||
OnCommandDone func(ctx context.Context, command string, output string, err error) error
|
OnCommandDone func(ctx context.Context, command string, output string, err error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Agent) Console(ctx context.Context, questions []string, cfg ConsoleConfig) (Knowledge, string, error) {
|
func (a Agent) Console(ctx context.Context, questions []string, cfg ConsoleConfig) (shared.Knowledge, string, error) {
|
||||||
|
|
||||||
if cfg.MaxCommands <= 0 {
|
if cfg.MaxCommands <= 0 {
|
||||||
cfg.MaxCommands = 10000
|
cfg.MaxCommands = 10000
|
||||||
}
|
}
|
||||||
|
|
||||||
var resKnowledge = Knowledge{
|
var resKnowledge = shared.Knowledge{
|
||||||
OriginalQuestions: questions,
|
OriginalQuestions: questions,
|
||||||
RemainingQuestions: questions,
|
RemainingQuestions: questions,
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtractKnowledge will take a knowledge object and use the gained knowledge to extract the knowledge relevant to the
|
// ExtractKnowledge will take a knowledge object and use the gained knowledge to extract the knowledge relevant to the
|
||||||
@ -16,16 +18,16 @@ import (
|
|||||||
// contextualInformation is any contextual information that should be provided to the model.
|
// contextualInformation is any contextual information that should be provided to the model.
|
||||||
// It will return the knowledge extracted from the sourceData along with any remaining questions.
|
// It will return the knowledge extracted from the sourceData along with any remaining questions.
|
||||||
// This agent call will not use the Agent's system prompts, but will instead form its own. The contextual information will be used.
|
// This agent call will not use the Agent's system prompts, but will instead form its own. The contextual information will be used.
|
||||||
func (a Agent) ExtractKnowledge(ctx context.Context, sourceData string, source string, questions []string) (Knowledge, error) {
|
func (a Agent) ExtractKnowledge(ctx context.Context, sourceData string, source string, questions []string) (shared.Knowledge, error) {
|
||||||
|
|
||||||
var knowledge Knowledge
|
var knowledge shared.Knowledge
|
||||||
fnAnswer := gollm.NewFunction(
|
fnAnswer := gollm.NewFunction(
|
||||||
"learn",
|
"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.`,
|
`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 struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
Info string `description:"The information to learn from the text."`
|
Info string `description:"The information to learn from the text."`
|
||||||
}) (any, error) {
|
}) (any, error) {
|
||||||
knowledge.Knowledge = append(knowledge.Knowledge, TidBit{Info: args.Info, Source: source})
|
knowledge.Knowledge = append(knowledge.Knowledge, shared.TidBit{Info: args.Info, Source: source})
|
||||||
return "", nil
|
return "", nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4,12 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KnowledgeIntegrate will ask the LLM to combine the gained knowledge with the current knowledge, and return the new representation of overall.
|
// KnowledgeIntegrate will ask the LLM to combine the gained knowledge with the current knowledge, and return the new representation of overall.
|
||||||
// If source is not empty, then any new Knowledge will an empty source will be given the source.
|
// If source is not empty, then any new Knowledge will an empty source will be given the source.
|
||||||
// This will override objectives, notes, and remaining questions.
|
// This will override objectives, notes, and remaining questions.
|
||||||
func (a Agent) KnowledgeIntegrate(ctx context.Context, base Knowledge, in ...Knowledge) (Knowledge, error) {
|
func (a Agent) KnowledgeIntegrate(ctx context.Context, base shared.Knowledge, in ...shared.Knowledge) (shared.Knowledge, error) {
|
||||||
// if there are no changes we can just return the knowledge
|
// if there are no changes we can just return the knowledge
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
return base, nil
|
return base, nil
|
||||||
@ -29,7 +31,7 @@ func (a Agent) KnowledgeIntegrate(ctx context.Context, base Knowledge, in ...Kno
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var incoming Knowledge
|
var incoming shared.Knowledge
|
||||||
|
|
||||||
for _, k := range in {
|
for _, k := range in {
|
||||||
incoming.NotesToSelf = append(incoming.NotesToSelf, k.NotesToSelf...)
|
incoming.NotesToSelf = append(incoming.NotesToSelf, k.NotesToSelf...)
|
||||||
@ -43,7 +45,7 @@ func (a Agent) KnowledgeIntegrate(ctx context.Context, base Knowledge, in ...Kno
|
|||||||
baseMsg.Text = "The original knowledge is as follows: " + baseMsg.Text
|
baseMsg.Text = "The original knowledge is as follows: " + baseMsg.Text
|
||||||
incomingMsg.Text = "The new knowledge is as follows: " + incomingMsg.Text
|
incomingMsg.Text = "The new knowledge is as follows: " + incomingMsg.Text
|
||||||
|
|
||||||
var result = Knowledge{
|
var result = shared.Knowledge{
|
||||||
OriginalQuestions: base.OriginalQuestions,
|
OriginalQuestions: base.OriginalQuestions,
|
||||||
Knowledge: append(base.Knowledge, incoming.Knowledge...),
|
Knowledge: append(base.Knowledge, incoming.Knowledge...),
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnswerQuestionWithKnowledge will take a knowledge object and use the gained knowledge to answer a question.
|
// AnswerQuestionWithKnowledge will take a knowledge object and use the gained knowledge to answer a question.
|
||||||
func (a Agent) AnswerQuestionWithKnowledge(ctx context.Context, knowledge Knowledge) (string, error) {
|
func (a Agent) AnswerQuestionWithKnowledge(ctx context.Context, knowledge shared.Knowledge) (string, error) {
|
||||||
originalQuestions := strings.Join(knowledge.OriginalQuestions, "\n")
|
originalQuestions := strings.Join(knowledge.OriginalQuestions, "\n")
|
||||||
infoGained := ""
|
infoGained := ""
|
||||||
|
|
||||||
|
@ -3,19 +3,21 @@ package agents
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/cache"
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/cache"
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor"
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a Agent) ReadPage(ctx context.Context, u *url.URL, questions []string) (Knowledge, error) {
|
func (a Agent) ReadPage(ctx context.Context, u *url.URL, questions []string) (shared.Knowledge, error) {
|
||||||
ar, err := extractArticle(ctx, u)
|
ar, err := extractArticle(ctx, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Knowledge{}, err
|
return shared.Knowledge{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ar.Body == "" {
|
if ar.Body == "" {
|
||||||
return Knowledge{}, fmt.Errorf("could not extract body from page")
|
return shared.Knowledge{}, fmt.Errorf("could not extract body from page")
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.ExtractKnowledge(ctx, ar.Body, u.String(), questions)
|
return a.ExtractKnowledge(ctx, ar.Body, u.String(), questions)
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo"
|
"gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo"
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
func deferClose(c io.Closer) {
|
func deferClose(c io.Closer) {
|
||||||
@ -26,7 +28,7 @@ func deferClose(c io.Closer) {
|
|||||||
type SearchTool struct {
|
type SearchTool struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Function func(ctx context.Context, src *url.URL, questions []string) (Knowledge, error)
|
Function func(ctx context.Context, src *url.URL, questions []string) (shared.Knowledge, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchAndUseTools will search duckduckgo for the given question, and then ask the LLM to select a search result to
|
// SearchAndUseTools will search duckduckgo for the given question, and then ask the LLM to select a search result to
|
||||||
@ -41,8 +43,8 @@ type SearchTool struct {
|
|||||||
// will be combined and returned.
|
// will be combined and returned.
|
||||||
// messages will be appended to all search results. The types of messages that can be appended are both string and
|
// messages will be appended to all search results. The types of messages that can be appended are both string and
|
||||||
// gollm.Message.
|
// gollm.Message.
|
||||||
func (a Agent) SearchAndUseTools(ctx context.Context, searchQuery string, questions []string, loops int, allowConcurrent bool, maxReads int, tools []SearchTool, messages ...any) (Knowledge, error) {
|
func (a Agent) SearchAndUseTools(ctx context.Context, searchQuery string, questions []string, loops int, allowConcurrent bool, maxReads int, tools []SearchTool, messages ...any) (shared.Knowledge, error) {
|
||||||
var knowledge = Knowledge{
|
var knowledge = shared.Knowledge{
|
||||||
OriginalQuestions: questions,
|
OriginalQuestions: questions,
|
||||||
RemainingQuestions: questions,
|
RemainingQuestions: questions,
|
||||||
}
|
}
|
||||||
@ -184,13 +186,13 @@ Use appropriate tools to analyze the search results and determine if they answer
|
|||||||
}
|
}
|
||||||
slog.Info("search results called and executed", "error", err, "results text", results.Text, "results", results.CallResults)
|
slog.Info("search results called and executed", "error", err, "results text", results.Text, "results", results.CallResults)
|
||||||
|
|
||||||
var learned []Knowledge
|
var learned []shared.Knowledge
|
||||||
for _, r := range results.CallResults {
|
for _, r := range results.CallResults {
|
||||||
if r.Error != nil {
|
if r.Error != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if k, ok := r.Result.(Knowledge); ok {
|
if k, ok := r.Result.(shared.Knowledge); ok {
|
||||||
learned = append(learned, k)
|
learned = append(learned, k)
|
||||||
} else {
|
} else {
|
||||||
slog.Error("result is not knowledge", "result", r.Result)
|
slog.Error("result is not knowledge", "result", r.Result)
|
||||||
@ -210,7 +212,7 @@ Use appropriate tools to analyze the search results and determine if they answer
|
|||||||
return knowledge, nil
|
return knowledge, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Agent) SearchAndRead(ctx context.Context, searchQuery string, questions []string, allowConcurrent bool, maxReads int) (Knowledge, error) {
|
func (a Agent) SearchAndRead(ctx context.Context, searchQuery string, questions []string, allowConcurrent bool, maxReads int) (shared.Knowledge, error) {
|
||||||
return a.SearchAndUseTools(ctx, searchQuery, questions, 2, allowConcurrent, maxReads, []SearchTool{
|
return a.SearchAndUseTools(ctx, searchQuery, questions, 2, allowConcurrent, maxReads, []SearchTool{
|
||||||
{
|
{
|
||||||
Name: "readpage",
|
Name: "readpage",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package agents
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
@ -5,8 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ const DefaultPrompt = `Use the provided tools to answer the questions in your cu
|
|||||||
// source is the source of the knowledge, for example a URL.
|
// source is the source of the knowledge, for example a URL.
|
||||||
// Any tool call that returns a Knowledge object will be handled by this function in crafting the final Knowledge object.
|
// Any tool call that returns a Knowledge object will be handled by this function in crafting the final Knowledge object.
|
||||||
// Any other return type will be passed to the resultWorker function, if provided.
|
// Any other return type will be passed to the resultWorker function, if provided.
|
||||||
func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowledge, systemPrompt string, userInput string, source string, history []gollm.Message, resultWorker func(res gollm.ToolCallResponse)) (agents.Knowledge, error) {
|
func (w KnowledgeWorker) Answer(context context.Context, knowledge *Knowledge, systemPrompt string, userInput string, source string, history []gollm.Message, resultWorker func(res gollm.ToolCallResponse)) (Knowledge, error) {
|
||||||
var req gollm.Request
|
var req gollm.Request
|
||||||
|
|
||||||
if systemPrompt != "" {
|
if systemPrompt != "" {
|
||||||
@ -80,7 +78,7 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
|
|||||||
func(ctx *gollm.Context, args struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
NotesToSelf []string `description:"Notes to leave for yourself for later."`
|
NotesToSelf []string `description:"Notes to leave for yourself for later."`
|
||||||
}) (any, error) {
|
}) (any, error) {
|
||||||
return agents.Knowledge{
|
return Knowledge{
|
||||||
NotesToSelf: args.NotesToSelf,
|
NotesToSelf: args.NotesToSelf,
|
||||||
}, nil
|
}, nil
|
||||||
}),
|
}),
|
||||||
@ -90,7 +88,7 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
|
|||||||
func(ctx *gollm.Context, args struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
Objectives []string `description:"The objectives to set for executions going forward."`
|
Objectives []string `description:"The objectives to set for executions going forward."`
|
||||||
}) (any, error) {
|
}) (any, error) {
|
||||||
return agents.Knowledge{
|
return Knowledge{
|
||||||
CurrentObjectives: args.Objectives,
|
CurrentObjectives: args.Objectives,
|
||||||
}, nil
|
}, nil
|
||||||
}),
|
}),
|
||||||
@ -100,13 +98,13 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
|
|||||||
func(ctx *gollm.Context, args struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
Info []string `description:"The information to learn from the input."`
|
Info []string `description:"The information to learn from the input."`
|
||||||
}) (any, error) {
|
}) (any, error) {
|
||||||
var k []agents.TidBit
|
var k []TidBit
|
||||||
|
|
||||||
for _, i := range args.Info {
|
for _, i := range args.Info {
|
||||||
k = append(k, agents.TidBit{Info: i, Source: source})
|
k = append(k, TidBit{Info: i, Source: source})
|
||||||
}
|
}
|
||||||
|
|
||||||
return agents.Knowledge{
|
return Knowledge{
|
||||||
Knowledge: k,
|
Knowledge: k,
|
||||||
}, nil
|
}, nil
|
||||||
})).
|
})).
|
||||||
@ -120,17 +118,17 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
|
|||||||
|
|
||||||
resp, err := w.Model.ChatComplete(context, req)
|
resp, err := w.Model.ChatComplete(context, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agents.Knowledge{}, fmt.Errorf("error calling model: %w", err)
|
return Knowledge{}, fmt.Errorf("error calling model: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Choices) == 0 {
|
if len(resp.Choices) == 0 {
|
||||||
return agents.Knowledge{}, fmt.Errorf("no choices found")
|
return Knowledge{}, fmt.Errorf("no choices found")
|
||||||
}
|
}
|
||||||
|
|
||||||
choice := resp.Choices[0]
|
choice := resp.Choices[0]
|
||||||
|
|
||||||
if len(choice.Calls) == 0 {
|
if len(choice.Calls) == 0 {
|
||||||
return agents.Knowledge{}, fmt.Errorf("no calls found")
|
return Knowledge{}, fmt.Errorf("no calls found")
|
||||||
}
|
}
|
||||||
|
|
||||||
var callNames []string
|
var callNames []string
|
||||||
@ -141,14 +139,14 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
|
|||||||
results, err := w.ToolBox.ExecuteCallbacks(gollm.NewContext(context, req, &choice, nil), choice.Calls, w.OnNewFunction, w.OnFunctionFinished)
|
results, err := w.ToolBox.ExecuteCallbacks(gollm.NewContext(context, req, &choice, nil), choice.Calls, w.OnNewFunction, w.OnFunctionFinished)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agents.Knowledge{}, fmt.Errorf("error executing callbacks: %w", err)
|
return Knowledge{}, fmt.Errorf("error executing callbacks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = agents.Knowledge{}
|
var res = Knowledge{}
|
||||||
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
switch v := r.Result.(type) {
|
switch v := r.Result.(type) {
|
||||||
case agents.Knowledge:
|
case Knowledge:
|
||||||
res = res.Absorb(v)
|
res = res.Absorb(v)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -3,25 +3,28 @@ package agents
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/asticode/go-astisub"
|
|
||||||
"github.com/lrstanley/go-ytdlp"
|
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/asticode/go-astisub"
|
||||||
|
"github.com/lrstanley/go-ytdlp"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ytdlp.MustInstall(context.Background(), nil)
|
ytdlp.MustInstall(context.Background(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions []string) (Knowledge, error) {
|
func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions []string) (shared.Knowledge, error) {
|
||||||
dlp := ytdlp.New()
|
dlp := ytdlp.New()
|
||||||
|
|
||||||
tmpDir, err := os.MkdirTemp("", "mort-ytdlp-")
|
tmpDir, err := os.MkdirTemp("", "mort-ytdlp-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Knowledge{}, fmt.Errorf("error creating temp dir: %w", err)
|
return shared.Knowledge{}, fmt.Errorf("error creating temp dir: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("created temp dir", "path", tmpDir)
|
slog.Info("created temp dir", "path", tmpDir)
|
||||||
@ -40,15 +43,15 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
|
|||||||
|
|
||||||
res, err := dlp.Run(ctx, u.String())
|
res, err := dlp.Run(ctx, u.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Knowledge{}, fmt.Errorf("error running yt-dlp: %w", err)
|
return shared.Knowledge{}, fmt.Errorf("error running yt-dlp: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res == nil {
|
if res == nil {
|
||||||
return Knowledge{}, fmt.Errorf("yt-dlp returned nil")
|
return shared.Knowledge{}, fmt.Errorf("yt-dlp returned nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.ExitCode != 0 {
|
if res.ExitCode != 0 {
|
||||||
return Knowledge{}, fmt.Errorf("yt-dlp exited with code %d", res.ExitCode)
|
return shared.Knowledge{}, fmt.Errorf("yt-dlp exited with code %d", res.ExitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the transcript for this video now _should_ be at tmpDir/subs.en.vtt, however if it's not then just fine any
|
// the transcript for this video now _should_ be at tmpDir/subs.en.vtt, however if it's not then just fine any
|
||||||
@ -60,7 +63,7 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
|
|||||||
vttFile = ""
|
vttFile = ""
|
||||||
files, err := os.ReadDir(tmpDir)
|
files, err := os.ReadDir(tmpDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Knowledge{}, fmt.Errorf("error reading directory: %w", err)
|
return shared.Knowledge{}, fmt.Errorf("error reading directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@ -72,7 +75,7 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
|
|||||||
}
|
}
|
||||||
|
|
||||||
if vttFile == "" {
|
if vttFile == "" {
|
||||||
return Knowledge{}, fmt.Errorf("no vtt file found")
|
return shared.Knowledge{}, fmt.Errorf("no vtt file found")
|
||||||
}
|
}
|
||||||
|
|
||||||
fp, err := os.Open(vttFile)
|
fp, err := os.Open(vttFile)
|
||||||
@ -83,16 +86,16 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
|
|||||||
}
|
}
|
||||||
}(fp)
|
}(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Knowledge{}, fmt.Errorf("error opening vtt file: %w", err)
|
return shared.Knowledge{}, fmt.Errorf("error opening vtt file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subs, err := astisub.ReadFromWebVTT(fp)
|
subs, err := astisub.ReadFromWebVTT(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Knowledge{}, fmt.Errorf("error reading vtt file: %w", err)
|
return shared.Knowledge{}, fmt.Errorf("error reading vtt file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(subs.Items) == 0 {
|
if len(subs.Items) == 0 {
|
||||||
return Knowledge{}, fmt.Errorf("no subtitles found")
|
return shared.Knowledge{}, fmt.Errorf("no subtitles found")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ts string
|
var ts string
|
||||||
|
Loading…
x
Reference in New Issue
Block a user