Consolidated a bunch of reused code to agents
This commit is contained in:
parent
5407c1a7cc
commit
5d2c350acf
@ -7,14 +7,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
knowledge2 "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/searcher"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -102,18 +96,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
question := strings.Join(c.Args(), " ")
|
question := strings.Join(c.Args(), " ")
|
||||||
|
|
||||||
search := searcher.Agent{
|
agent := agents.NewAgent(m, nil).WithMaxCalls(200)
|
||||||
Model: m,
|
|
||||||
|
|
||||||
OnDone: func(ctx context.Context, knowledge shared.Knowledge) error {
|
knowledge, err := agent.SearchAndRead(ctx, question, []string{question}, true, 10)
|
||||||
slog.Info("done", "knowledge", knowledge)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
MaxReads: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
processor := knowledge2.KnowledgeProcessor{Model: m}
|
|
||||||
knowledge, err := search.Search(ctx, question, question)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -121,9 +106,12 @@ func main() {
|
|||||||
|
|
||||||
slog.Info("knowledge", "knowledge", knowledge)
|
slog.Info("knowledge", "knowledge", knowledge)
|
||||||
|
|
||||||
sum, err := processor.Process(ctx, knowledge)
|
res, err := agent.AnswerQuestionWithKnowledge(ctx, knowledge)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println(sum)
|
fmt.Println(res)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
151
cmd/console/cmd.go
Normal file
151
cmd/console/cmd.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/console"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getKey(key string, env string) string {
|
||||||
|
if key != "" {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Getenv(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Usage: go run cmd/answer.go question...
|
||||||
|
// - flags:
|
||||||
|
// --model=[model string such as openai/gpt-4o, anthropic/claude..., google/gemini-1.5. Default: openai/gpt-4o]
|
||||||
|
// --search-provider=[search provider string such as google, duckduckgo. Default: google]
|
||||||
|
// --cache-provider=[cache provider string such as memory, redis, file. Default: memory]
|
||||||
|
|
||||||
|
var app = cli.App{
|
||||||
|
Name: "console",
|
||||||
|
Usage: "has an llm control a console for you",
|
||||||
|
Version: "0.1",
|
||||||
|
Description: "",
|
||||||
|
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "env-file",
|
||||||
|
Value: ".env",
|
||||||
|
Usage: "file to read environment variables from",
|
||||||
|
},
|
||||||
|
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "model",
|
||||||
|
Value: "openai/gpt-4o-mini",
|
||||||
|
Usage: "model to use for answering the question, syntax: provider/model such as openai/gpt-4o",
|
||||||
|
},
|
||||||
|
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "llm-key",
|
||||||
|
Value: "",
|
||||||
|
Usage: "key for the llm model (if empty, will use env var of PROVIDER_API_KEY, such as OPENAI_API_KEY)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
// if there is no question to answer, print usage
|
||||||
|
if ctx.NArg() == 0 {
|
||||||
|
return cli.ShowAppHelp(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.String("env-file") != "" {
|
||||||
|
_ = godotenv.Load(ctx.String("env-file"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var llm gollm.LLM
|
||||||
|
|
||||||
|
model := ctx.String("model")
|
||||||
|
|
||||||
|
a := strings.Split(model, "/")
|
||||||
|
|
||||||
|
if len(a) != 2 {
|
||||||
|
panic("invalid model, expected: provider/model (such as openai/gpt-4o)")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch a[0] {
|
||||||
|
case "openai":
|
||||||
|
llm = gollm.OpenAI(getKey(ctx.String("llm-key"), "OPENAI_API_KEY"))
|
||||||
|
|
||||||
|
case "anthropic":
|
||||||
|
llm = gollm.Anthropic(getKey(ctx.String("llm-key"), "ANTHROPI_API_KEY"))
|
||||||
|
|
||||||
|
case "google":
|
||||||
|
llm = gollm.Google(getKey(ctx.String("llm-key"), "GOOGLE_API_KEY"))
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unknown model provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := llm.ModelVersion(a[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
question := strings.Join(ctx.Args(), " ")
|
||||||
|
|
||||||
|
c := console.Agent{
|
||||||
|
Model: m,
|
||||||
|
OnDone: func(ctx context.Context, knowledge agents.Knowledge) error { return nil },
|
||||||
|
OnCommandStart: func(ctx context.Context, command string) error {
|
||||||
|
slog.Info("command", "command", command)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
OnCommandDone: func(ctx context.Context, command string, output string, err error) error {
|
||||||
|
slog.Info("command done", "command", command, "output", output, "err", err)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ContextualInformation: []string{
|
||||||
|
fmt.Sprintf("The current time is %s", time.Now().Format(time.RFC3339)),
|
||||||
|
},
|
||||||
|
MaxCommands: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.Answer(context.Background(), []string{question})
|
||||||
|
|
||||||
|
if res.Directory != "" {
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(res.Directory)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Results: ", fmt.Sprintf("%+v", res))
|
||||||
|
|
||||||
|
answer, err := agents.AnswerQuestionWithKnowledge(context.Background(), res.Knowledge, m, []string{
|
||||||
|
fmt.Sprintf("The current time is %s", time.Now().Format(time.RFC3339)),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Answer: ", answer)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Error: ", err)
|
||||||
|
}
|
||||||
|
}
|
118
cmd/steps/cmd.go
Normal file
118
cmd/steps/cmd.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getKey(key string, env string) string {
|
||||||
|
if key != "" {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Getenv(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Usage: go run cmd/answer.go question...
|
||||||
|
// - flags:
|
||||||
|
// --model=[model string such as openai/gpt-4o, anthropic/claude..., google/gemini-1.5. Default: openai/gpt-4o]
|
||||||
|
// --search-provider=[search provider string such as google, duckduckgo. Default: google]
|
||||||
|
// --cache-provider=[cache provider string such as memory, redis, file. Default: memory]
|
||||||
|
|
||||||
|
var app = cli.App{
|
||||||
|
Name: "console",
|
||||||
|
Usage: "has an llm control a console for you",
|
||||||
|
Version: "0.1",
|
||||||
|
Description: "",
|
||||||
|
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "env-file",
|
||||||
|
Value: ".env",
|
||||||
|
Usage: "file to read environment variables from",
|
||||||
|
},
|
||||||
|
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "model",
|
||||||
|
Value: "openai/gpt-4o-mini",
|
||||||
|
Usage: "model to use for answering the question, syntax: provider/model such as openai/gpt-4o",
|
||||||
|
},
|
||||||
|
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "llm-key",
|
||||||
|
Value: "",
|
||||||
|
Usage: "key for the llm model (if empty, will use env var of PROVIDER_API_KEY, such as OPENAI_API_KEY)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
// if there is no question to answer, print usage
|
||||||
|
if ctx.NArg() == 0 {
|
||||||
|
return cli.ShowAppHelp(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.String("env-file") != "" {
|
||||||
|
_ = godotenv.Load(ctx.String("env-file"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var llm gollm.LLM
|
||||||
|
|
||||||
|
model := ctx.String("model")
|
||||||
|
|
||||||
|
a := strings.Split(model, "/")
|
||||||
|
|
||||||
|
if len(a) != 2 {
|
||||||
|
panic("invalid model, expected: provider/model (such as openai/gpt-4o)")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch a[0] {
|
||||||
|
case "openai":
|
||||||
|
llm = gollm.OpenAI(getKey(ctx.String("llm-key"), "OPENAI_API_KEY"))
|
||||||
|
|
||||||
|
case "anthropic":
|
||||||
|
llm = gollm.Anthropic(getKey(ctx.String("llm-key"), "ANTHROPI_API_KEY"))
|
||||||
|
|
||||||
|
case "google":
|
||||||
|
llm = gollm.Google(getKey(ctx.String("llm-key"), "GOOGLE_API_KEY"))
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unknown model provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := llm.ModelVersion(a[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
question := strings.Join(ctx.Args(), " ")
|
||||||
|
|
||||||
|
ag := agents.NewAgent(m, nil)
|
||||||
|
|
||||||
|
steps, err := ag.SplitQuestion(context.Background(), question)
|
||||||
|
|
||||||
|
fmt.Println("Input question: ", question)
|
||||||
|
fmt.Println("Steps: ")
|
||||||
|
for i, s := range steps {
|
||||||
|
fmt.Println(" - Step ", i+1, ": ", s)
|
||||||
|
}
|
||||||
|
fmt.Println("Error: ", err)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Error: ", err)
|
||||||
|
}
|
||||||
|
}
|
30
go.mod
30
go.mod
@ -1,22 +1,24 @@
|
|||||||
module gitea.stevedudenhoeffer.com/steve/answer
|
module gitea.stevedudenhoeffer.com/steve/answer
|
||||||
|
|
||||||
go 1.23.2
|
go 1.24.1
|
||||||
|
|
||||||
replace github.com/rocketlaunchr/google-search => github.com/chrisjoyce911/google-search v0.0.0-20230910003754-e501aedf805a
|
replace github.com/rocketlaunchr/google-search => github.com/chrisjoyce911/google-search v0.0.0-20230910003754-e501aedf805a
|
||||||
|
|
||||||
//replace gitea.stevedudenhoeffer.com/steve/go-llm => ../go-llm
|
// replace gitea.stevedudenhoeffer.com/steve/go-llm => ../go-llm
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.stevedudenhoeffer.com/steve/go-extractor v0.0.0-20250318064250-39453288ce2a
|
gitea.stevedudenhoeffer.com/steve/go-extractor v0.0.0-20250318064250-39453288ce2a
|
||||||
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250321150932-5ba42056adfc
|
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250326035309-82feb7d8b415
|
||||||
github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d
|
github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d
|
||||||
github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275
|
github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
github.com/docker/docker v28.0.2+incompatible
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
github.com/playwright-community/playwright-go v0.5001.0
|
github.com/playwright-community/playwright-go v0.5001.0
|
||||||
github.com/rocketlaunchr/google-search v1.1.6
|
github.com/rocketlaunchr/google-search v1.1.6
|
||||||
github.com/urfave/cli v1.22.16
|
github.com/urfave/cli v1.22.16
|
||||||
go.starlark.net v0.0.0-20250225190231-0d3f41d403af
|
go.starlark.net v0.0.0-20250318223901-d9371fef63fe
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -26,14 +28,19 @@ require (
|
|||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||||
cloud.google.com/go/longrunning v0.6.6 // indirect
|
cloud.google.com/go/longrunning v0.6.6 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/PuerkitoBio/goquery v1.10.2 // indirect
|
github.com/PuerkitoBio/goquery v1.10.2 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/antchfx/htmlquery v1.3.4 // indirect
|
github.com/antchfx/htmlquery v1.3.4 // indirect
|
||||||
github.com/antchfx/xmlquery v1.4.4 // indirect
|
github.com/antchfx/xmlquery v1.4.4 // indirect
|
||||||
github.com/antchfx/xpath v1.3.3 // indirect
|
github.com/antchfx/xpath v1.3.3 // indirect
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
|
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/fatih/set v0.2.1 // indirect
|
github.com/fatih/set v0.2.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573 // indirect
|
github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573 // indirect
|
||||||
@ -46,6 +53,7 @@ require (
|
|||||||
github.com/go-stack/stack v1.8.1 // indirect
|
github.com/go-stack/stack v1.8.1 // indirect
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
github.com/gocolly/colly/v2 v2.1.0 // indirect
|
github.com/gocolly/colly/v2 v2.1.0 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
@ -58,7 +66,11 @@ require (
|
|||||||
github.com/kennygrant/sanitize v1.2.4 // indirect
|
github.com/kennygrant/sanitize v1.2.4 // indirect
|
||||||
github.com/liushuangls/go-anthropic/v2 v2.14.1 // indirect
|
github.com/liushuangls/go-anthropic/v2 v2.14.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/term v0.5.2 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
@ -70,6 +82,7 @@ require (
|
|||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
@ -80,10 +93,11 @@ require (
|
|||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
google.golang.org/api v0.227.0 // indirect
|
google.golang.org/api v0.228.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||||
google.golang.org/grpc v1.71.0 // indirect
|
google.golang.org/grpc v1.71.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
|
gotest.tools/v3 v3.5.2 // indirect
|
||||||
)
|
)
|
||||||
|
447
go.sum
Normal file
447
go.sum
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
|
||||||
|
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
|
||||||
|
cloud.google.com/go/ai v0.10.1 h1:EU93KqYmMeOKgaBXAz2DshH2C/BzAT1P+iJORksLIic=
|
||||||
|
cloud.google.com/go/ai v0.10.1/go.mod h1:sWWHZvmJ83BjuxAQtYEiA0SFTpijtbH+SXWFO14ri5A=
|
||||||
|
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||||
|
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||||
|
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||||
|
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||||
|
cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw=
|
||||||
|
cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw=
|
||||||
|
gitea.stevedudenhoeffer.com/steve/go-extractor v0.0.0-20250318064250-39453288ce2a h1:LZriHuPVjdus7Haz+qEFYgr+g/eOdmeAvlbgk67DDHA=
|
||||||
|
gitea.stevedudenhoeffer.com/steve/go-extractor v0.0.0-20250318064250-39453288ce2a/go.mod h1:fzvvUfN8ej2u1ruCsABG+D+2dAPfOklInS4b1pvog1M=
|
||||||
|
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250321150932-5ba42056adfc h1:t37fsWEfZu5DjCrJgzRT3i8iglB1a/nRrWFj6e/KzoU=
|
||||||
|
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250321150932-5ba42056adfc/go.mod h1:LitFWQ+Q5db6zo6K2mzqfFvz/8EM/vUNAaG+TaSGZf0=
|
||||||
|
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250326035309-82feb7d8b415 h1:LT6sPXU/mZaTmFHMOS90UtfmGaaSD8CaOt40q0komlE=
|
||||||
|
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250326035309-82feb7d8b415/go.mod h1:LitFWQ+Q5db6zo6K2mzqfFvz/8EM/vUNAaG+TaSGZf0=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d h1:dxGZ0drmrUfNOQ93n9kAWkxOXK4bQHRUaFhRzGySTU4=
|
||||||
|
github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d/go.mod h1:ubjYqrt3dF4G+YVEDQr+qa2aveeMzt27o/GOH2hswPo=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
|
||||||
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||||
|
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
|
||||||
|
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
|
||||||
|
github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275 h1:Kuhf+w+ilOGoXaR4O4nZ6Dp+ZS83LdANUjwyMXsPGX4=
|
||||||
|
github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275/go.mod h1:98NztIIMIntZGtQVIs8H85Q5b88fTbwWFbLz/lM9/xU=
|
||||||
|
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
|
||||||
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
|
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||||
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
|
github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0=
|
||||||
|
github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8=
|
||||||
|
github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ=
|
||||||
|
github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM=
|
||||||
|
github.com/antchfx/xmlquery v1.2.4/go.mod h1:KQQuESaxSlqugE2ZBcM/qn+ebIpt+d+4Xx7YcSGAIrM=
|
||||||
|
github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA=
|
||||||
|
github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg=
|
||||||
|
github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc=
|
||||||
|
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||||
|
github.com/antchfx/xpath v1.1.8/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||||
|
github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||||
|
github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||||
|
github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs=
|
||||||
|
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||||
|
github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
||||||
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||||
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chrisjoyce911/google-search v0.0.0-20230910003754-e501aedf805a h1:OZQiBySVd55npXVsIKnJT6q+9A1tPiXhGnFlc+q0YqQ=
|
||||||
|
github.com/chrisjoyce911/google-search v0.0.0-20230910003754-e501aedf805a/go.mod h1:fk5J/qPpaRDjLWdFxT+dmuiqG7kxXArC7K8A+gj88Nk=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ=
|
||||||
|
github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8=
|
||||||
|
github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
||||||
|
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573 h1:u8AQ9bPa9oC+8/A/jlWouakhIvkFfuxgIIRjiy8av7I=
|
||||||
|
github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573/go.mod h1:eBvb3i++NHDH4Ugo9qCvMw8t0mTSctaEa5blJbWcNxs=
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
|
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziHZixGO5ZBS73cKqVzZipfrLmO1w=
|
||||||
|
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
|
||||||
|
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612 h1:BYLNYdZaepitbZreRIa9xeCQZocWmy/wj4cGIH0qyw0=
|
||||||
|
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612/go.mod h1:wgqthQa8SAYs0yyljVeCOQlZ027VW5CmLsbi9jWC08c=
|
||||||
|
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||||
|
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||||
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
|
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
|
||||||
|
github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoGs=
|
||||||
|
github.com/gocolly/colly/v2 v2.1.0/go.mod h1:I2MuhsLjQ+Ex+IzK3afNS8/1qP3AedHOusRPcRdC5o0=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
||||||
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
|
||||||
|
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||||
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||||
|
github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg=
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
|
||||||
|
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/liushuangls/go-anthropic/v2 v2.14.1 h1:t07ckMN7qLkI4yIPJMPNjkwyLV6SEou6UHT/a4rpIHY=
|
||||||
|
github.com/liushuangls/go-anthropic/v2 v2.14.1/go.mod h1:HQ3//ql9jcgP6zpL5R11OkHijWuYVH1iwJSSF0x+Jlk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||||
|
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/playwright-community/playwright-go v0.5001.0 h1:EY3oB+rU9cUp6CLHguWE8VMZTwAg+83Yyb7dQqEmGLg=
|
||||||
|
github.com/playwright-community/playwright-go v0.5001.0/go.mod h1:kBNWs/w2aJ2ZUp1wEOOFLXgOqvppFngM5OS+qyhl+ZM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||||
|
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
|
github.com/sashabaranov/go-openai v1.38.1 h1:TtZabbFQZa1nEni/IhVtDF/WQjVqDgd+cWR5OeddzF8=
|
||||||
|
github.com/sashabaranov/go-openai v1.38.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||||
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||||
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||||
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
||||||
|
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
|
||||||
|
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
||||||
|
github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
|
||||||
|
github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||||
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||||
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||||
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||||
|
go.starlark.net v0.0.0-20250318223901-d9371fef63fe h1:Wf00k2WTLCW/L1/+gA1gxfTcU4yI+nK4YRTjumYezD8=
|
||||||
|
go.starlark.net v0.0.0-20250318223901-d9371fef63fe/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||||
|
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
|
||||||
|
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||||
|
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||||
|
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
238
pkg/agents/agent.go
Normal file
238
pkg/agents/agent.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Agent is essentially the bones of a chat agent. It has a model and a toolbox, and can be used to call the model
|
||||||
|
// with messages and execute the resulting calls.
|
||||||
|
// The agent will keep track of how many calls it has made to the model, and any agents which inherit from this
|
||||||
|
// one (e.g.: all made from sharing pointers or from the With... helpers) will share the same call count.
|
||||||
|
// This package contains a number of helper functions to make it easier to create and use agents.
|
||||||
|
type Agent struct {
|
||||||
|
model gollm.ChatCompletion
|
||||||
|
toolbox *gollm.ToolBox
|
||||||
|
systemPrompt string
|
||||||
|
contextualInformation []string
|
||||||
|
systemPromptSuffix string
|
||||||
|
maxCalls *int32
|
||||||
|
|
||||||
|
calls *atomic.Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAgent creates a new agent struct with the given model and toolbox.
|
||||||
|
// Any inherited agents (e.g.: all made from sharing pointers or from the With... helpers) from this one will
|
||||||
|
// share the same call count.
|
||||||
|
func NewAgent(model gollm.ChatCompletion, toolbox *gollm.ToolBox) Agent {
|
||||||
|
return Agent{
|
||||||
|
model: model,
|
||||||
|
toolbox: toolbox,
|
||||||
|
calls: &atomic.Int32{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) Calls() int32 {
|
||||||
|
return a.calls.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) WithModel(model gollm.ChatCompletion) Agent {
|
||||||
|
a.model = model
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) WithToolbox(toolbox *gollm.ToolBox) Agent {
|
||||||
|
a.toolbox = toolbox
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) WithSystemPrompt(systemPrompt string) Agent {
|
||||||
|
a.systemPrompt = systemPrompt
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) WithContextualInformation(contextualInformation []string) Agent {
|
||||||
|
a.contextualInformation = append(a.contextualInformation, contextualInformation...)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) WithSystemPromptSuffix(systemPromptSuffix string) Agent {
|
||||||
|
a.systemPromptSuffix = systemPromptSuffix
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) WithMaxCalls(maxCalls int32) Agent {
|
||||||
|
a.maxCalls = &maxCalls
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) _readAnyMessages(messages ...any) ([]gollm.Message, error) {
|
||||||
|
var res []gollm.Message
|
||||||
|
|
||||||
|
for _, msg := range messages {
|
||||||
|
switch v := msg.(type) {
|
||||||
|
case gollm.Message:
|
||||||
|
res = append(res, v)
|
||||||
|
case string:
|
||||||
|
res = append(res, gollm.Message{
|
||||||
|
Role: gollm.RoleUser,
|
||||||
|
Text: v,
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
return res, fmt.Errorf("unknown type %T used as message", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRequest will convert the current agent configuration into a gollm.Request. Any messages passed in will be added
|
||||||
|
// to the request at the end. messages can be either a gollm.Message or a string. All string entries will be added as
|
||||||
|
// simple user messages.
|
||||||
|
func (a Agent) ToRequest(messages ...any) (gollm.Request, error) {
|
||||||
|
sysPrompt := a.systemPrompt
|
||||||
|
|
||||||
|
if len(a.contextualInformation) > 0 {
|
||||||
|
if len(sysPrompt) > 0 {
|
||||||
|
sysPrompt += "\n\n"
|
||||||
|
}
|
||||||
|
sysPrompt += fmt.Sprintf(" Contextual information you should be aware of: %v", a.contextualInformation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.systemPromptSuffix) > 0 {
|
||||||
|
if len(sysPrompt) > 0 {
|
||||||
|
sysPrompt += "\n\n"
|
||||||
|
}
|
||||||
|
sysPrompt += a.systemPromptSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
req := gollm.Request{
|
||||||
|
Toolbox: a.toolbox,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sysPrompt) > 0 {
|
||||||
|
req.Messages = append(req.Messages, gollm.Message{
|
||||||
|
Role: gollm.RoleSystem,
|
||||||
|
Text: sysPrompt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, err := a._readAnyMessages(messages...)
|
||||||
|
if err != nil {
|
||||||
|
return req, fmt.Errorf("failed to read messages: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Messages = append(req.Messages, msgs...)
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallModel calls the model with the given messages and returns the raw response.
|
||||||
|
// note that the msgs can be either a gollm.Message or a string. All string entries will be added as simple
|
||||||
|
// user messages.
|
||||||
|
func (a Agent) CallModel(ctx context.Context, msgs ...any) (gollm.Response, error) {
|
||||||
|
calls := a.calls.Add(1)
|
||||||
|
if a.maxCalls != nil && calls > *a.maxCalls {
|
||||||
|
return gollm.Response{}, fmt.Errorf("max model calls exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := a.ToRequest(msgs...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return gollm.Response{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.model.ChatComplete(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallResults struct {
|
||||||
|
ID string
|
||||||
|
Function string
|
||||||
|
Arguments string
|
||||||
|
Result any
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
type CallAndExecuteResults struct {
|
||||||
|
Text string
|
||||||
|
CallResults []CallResults
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallAndExecute calls the model with the given messages and executes the resulting calls in serial order. The results
|
||||||
|
// are returned in the same order as the calls.
|
||||||
|
func (a Agent) CallAndExecute(ctx context.Context, msgs ...any) (CallAndExecuteResults, error) {
|
||||||
|
return a._callAndExecuteParallel(ctx, false, msgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallAndExecuteParallel will call the model with the given messages and all the tool calls in the response will be
|
||||||
|
// executed in parallel. The results will be returned in the same order as the calls.
|
||||||
|
func (a Agent) CallAndExecuteParallel(ctx context.Context, msgs ...any) (CallAndExecuteResults, error) {
|
||||||
|
return a._callAndExecuteParallel(ctx, true, msgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) _callAndExecuteParallel(ctx context.Context, parallel bool, msgs ...any) (CallAndExecuteResults, error) {
|
||||||
|
calls := a.calls.Add(1)
|
||||||
|
if a.maxCalls != nil && calls > *a.maxCalls {
|
||||||
|
return CallAndExecuteResults{}, fmt.Errorf("max model calls exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := a.ToRequest(msgs...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return CallAndExecuteResults{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := a.model.ChatComplete(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return CallAndExecuteResults{}, fmt.Errorf("error calling model: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Choices) == 0 {
|
||||||
|
return CallAndExecuteResults{}, fmt.Errorf("no choices found")
|
||||||
|
}
|
||||||
|
|
||||||
|
choice := response.Choices[0]
|
||||||
|
|
||||||
|
var res = CallAndExecuteResults{
|
||||||
|
Text: choice.Content,
|
||||||
|
CallResults: make([]CallResults, len(choice.Calls)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if parallel {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for i, call := range choice.Calls {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var callRes = CallResults{
|
||||||
|
ID: call.ID,
|
||||||
|
Function: call.FunctionCall.Name,
|
||||||
|
Arguments: call.FunctionCall.Arguments,
|
||||||
|
}
|
||||||
|
|
||||||
|
callRes.Result, callRes.Error = req.Toolbox.Execute(gollm.NewContext(ctx, req, &choice, &call), call)
|
||||||
|
|
||||||
|
res.CallResults[i] = callRes
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
} else {
|
||||||
|
for i, call := range choice.Calls {
|
||||||
|
var callRes = CallResults{
|
||||||
|
ID: call.ID,
|
||||||
|
Function: call.FunctionCall.Name,
|
||||||
|
Arguments: call.FunctionCall.Arguments,
|
||||||
|
}
|
||||||
|
|
||||||
|
callRes.Result, callRes.Error = req.Toolbox.Execute(gollm.NewContext(ctx, req, &choice, &call), call)
|
||||||
|
|
||||||
|
res.CallResults[i] = callRes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
3
pkg/agents/consensus.go
Normal file
3
pkg/agents/consensus.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
// TODO: Consensus system?
|
302
pkg/agents/console-new/agent.go
Normal file
302
pkg/agents/console-new/agent.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
package console_new
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/mount"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Agent struct {
|
||||||
|
// Model is the chat completion model to use
|
||||||
|
Model gollm.ChatCompletion
|
||||||
|
|
||||||
|
OnLoopComplete func(ctx context.Context, knowledge agents.Knowledge) error
|
||||||
|
|
||||||
|
OnCommandStart func(ctx context.Context, command string) error
|
||||||
|
OnCommandDone func(ctx context.Context, command string, output string, err error) error
|
||||||
|
|
||||||
|
OnDone func(ctx context.Context, knowledge agents.Knowledge) error
|
||||||
|
|
||||||
|
ContextualInformation []string
|
||||||
|
|
||||||
|
MaxCommands int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Knowledge agents.Knowledge
|
||||||
|
Directory string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer will give the model access to an ubuntu console with python and pip installed, and then ask the model to
|
||||||
|
// do what is necessary to answer the question.
|
||||||
|
func (a Agent) Answer(ctx context.Context, questions []string) (Response, error) {
|
||||||
|
var res Response
|
||||||
|
|
||||||
|
if a.MaxCommands <= 0 {
|
||||||
|
a.MaxCommands = 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Knowledge = agents.Knowledge{
|
||||||
|
OriginalQuestions: questions,
|
||||||
|
RemainingQuestions: questions,
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a temporary scratch directory
|
||||||
|
dir, err := os.MkdirTemp("", "console-")
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Directory = dir
|
||||||
|
|
||||||
|
cl, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
mounts := []mount.Mount{
|
||||||
|
{
|
||||||
|
Type: mount.TypeBind,
|
||||||
|
Source: dir,
|
||||||
|
Target: "/home/user",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := CreateContainer(ctx, cl, ContainerConfig{
|
||||||
|
Config: &container.Config{
|
||||||
|
Image: "ubuntu:latest",
|
||||||
|
Cmd: []string{"tail", "-f", "/dev/null"},
|
||||||
|
Tty: true,
|
||||||
|
WorkingDir: "/home/user",
|
||||||
|
},
|
||||||
|
HostConfig: &container.HostConfig{
|
||||||
|
AutoRemove: true,
|
||||||
|
Mounts: mounts,
|
||||||
|
},
|
||||||
|
Name: filepath.Base(dir),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to create container: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = c.Close(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
slog.Info("starting container", "dir", dir, "container", fmt.Sprintf("%+v", c))
|
||||||
|
err = c.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to start container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the model
|
||||||
|
|
||||||
|
var history executions
|
||||||
|
var keepGoing = true
|
||||||
|
|
||||||
|
opwd, epwd := c.Execute(ctx, "ls -al /home")
|
||||||
|
|
||||||
|
fmt.Println(opwd)
|
||||||
|
slog.Info("pwd", "pwd", opwd, "epwd", epwd)
|
||||||
|
|
||||||
|
tools := map[string]*gollm.Function{
|
||||||
|
"exit": gollm.NewFunction(
|
||||||
|
"exit",
|
||||||
|
"exit the container",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
RemainingQuestions []string `description:"any remaining questions that remain unanswered"`
|
||||||
|
}) (any, error) {
|
||||||
|
keepGoing = false
|
||||||
|
return "exiting", nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"write": gollm.NewFunction(
|
||||||
|
"write",
|
||||||
|
"write a file in the /root directory",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Filename string `description:"The name of the file to write"`
|
||||||
|
Content string `description:"The content of the file to write"`
|
||||||
|
}) (any, error) {
|
||||||
|
target, err := SafeJoinPath(dir, args.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.WriteString(args.Content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "wrote file", nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"read": gollm.NewFunction(
|
||||||
|
"read",
|
||||||
|
"read a file in the /root directory",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Filename string `description:"The name of the file to read"`
|
||||||
|
}) (any, error) {
|
||||||
|
target, err := SafeJoinPath(dir, args.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(target)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"execute": gollm.NewFunction(
|
||||||
|
"execute",
|
||||||
|
"execute a command in the container",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Command string `description:"The command to execute"`
|
||||||
|
}) (any, error) {
|
||||||
|
if len(history) >= a.MaxCommands {
|
||||||
|
return "too many commands", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.OnCommandStart != nil {
|
||||||
|
err := a.OnCommandStart(ctx, args.Command)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res string
|
||||||
|
// if the command starts with sudo then we need to use the sudo function
|
||||||
|
if strings.HasPrefix(args.Command, "sudo ") {
|
||||||
|
res, err = c.Sudo(ctx, args.Command[5:])
|
||||||
|
} else {
|
||||||
|
res, err = c.Execute(ctx, args.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.OnCommandDone != nil {
|
||||||
|
err = a.OnCommandDone(ctx, args.Command, res, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history = append(history, execution{
|
||||||
|
Command: args.Command,
|
||||||
|
Output: res,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"sudo": gollm.NewFunction(
|
||||||
|
"sudo",
|
||||||
|
"execute a command in the container",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Command string `description:"The command to execute"`
|
||||||
|
}) (any, error) {
|
||||||
|
if len(history) >= a.MaxCommands {
|
||||||
|
return "too many commands", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.OnCommandStart != nil {
|
||||||
|
err := a.OnCommandStart(ctx, args.Command)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.Sudo(ctx, args.Command)
|
||||||
|
|
||||||
|
if a.OnCommandDone != nil {
|
||||||
|
err = a.OnCommandDone(ctx, args.Command, res, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
res = "error executing: " + err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
history = append(history, execution{
|
||||||
|
Command: "sudo " + args.Command,
|
||||||
|
Output: res,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < a.MaxCommands && len(history) < a.MaxCommands && keepGoing; i++ {
|
||||||
|
|
||||||
|
systemPrompt := `You are now in a shell in a container of the ubuntu:latest image to answer a question asked by the user, it is very basic install of ubuntu, simple things (like python) are not preinstalled but can be installed via apt. You will be run multiple times and gain knowledge throughout the process.`
|
||||||
|
|
||||||
|
if len(history) < a.MaxCommands {
|
||||||
|
systemPrompt += `You can run any command you like to get to the needed results.`
|
||||||
|
}
|
||||||
|
|
||||||
|
systemPrompt += `Alternatively, you can use the tool "write" to write a file in the home directory, and also the tool "read" to read a file in the home directory.
|
||||||
|
When you are done, please use "exit" to exit the container.
|
||||||
|
Respond with any number of commands to answer the question, they will be executed in order.`
|
||||||
|
|
||||||
|
var toolbox []*gollm.Function
|
||||||
|
|
||||||
|
// add unrestricted tools
|
||||||
|
toolbox = append(toolbox, tools["exit"], tools["write"], tools["read"])
|
||||||
|
|
||||||
|
if len(history) < a.MaxCommands {
|
||||||
|
toolbox = append(toolbox, tools["execute"], tools["sudo"])
|
||||||
|
}
|
||||||
|
|
||||||
|
kw := shared.KnowledgeWorker{
|
||||||
|
Model: a.Model,
|
||||||
|
ToolBox: gollm.NewToolBox(toolbox...),
|
||||||
|
ContextualInformation: a.ContextualInformation,
|
||||||
|
OnNewFunction: func(ctx context.Context, funcName string, args string) (any, error) {
|
||||||
|
slog.Info("new function called", "function name", funcName, "args", args)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("answering question", "question", questions[0])
|
||||||
|
r, err := kw.Answer(ctx, &res.Knowledge, systemPrompt, "", "", history.ToGeneralButLastMessageHistory(), func(res gollm.ToolCallResponse) {
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("error answering question: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Knowledge) > 0 {
|
||||||
|
slog.Info("answered question and learned", "knowledge", r.Knowledge)
|
||||||
|
} else {
|
||||||
|
slog.Info("answered question and learned nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Knowledge, err = agents.KnowledgeIntegrate(ctx, a.Model, res.Knowledge, r)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("error integrating knowledge: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("knowledge integrated", "question", questions[0], "knowledge", res.Knowledge)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
115
pkg/agents/console-new/container.go
Normal file
115
pkg/agents/console-new/container.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package console_new
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
client *client.Client
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) Start(ctx context.Context) error {
|
||||||
|
return c.client.ContainerStart(ctx, c.id, container.StartOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) Close(ctx context.Context) error {
|
||||||
|
timeout := 10
|
||||||
|
if err := c.client.ContainerStop(ctx, c.id, container.StopOptions{
|
||||||
|
Timeout: &timeout,
|
||||||
|
}); err != nil {
|
||||||
|
// If stop fails, force kill
|
||||||
|
if err := c.client.ContainerKill(ctx, c.id, "SIGKILL"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove container and volumes
|
||||||
|
removeOptions := container.RemoveOptions{
|
||||||
|
RemoveVolumes: true,
|
||||||
|
Force: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.client.ContainerRemove(ctx, c.id, removeOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) execUserCmd(ctx context.Context, user string, cmd string) (string, error) {
|
||||||
|
slog.Info("executing command", "user", user, "cmd", cmd)
|
||||||
|
var cmdStr = []string{"/bin/bash", "-c"}
|
||||||
|
|
||||||
|
cmdStr = append(cmdStr, cmd)
|
||||||
|
|
||||||
|
slog.Info("executing command", "user", user, "cmd", fmt.Sprintf("%#v", cmdStr))
|
||||||
|
exec, err := c.client.ContainerExecCreate(ctx, c.id, container.ExecOptions{
|
||||||
|
Cmd: cmdStr,
|
||||||
|
User: user,
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Close()
|
||||||
|
|
||||||
|
output, err := io.ReadAll(resp.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for command to finish and get exit code
|
||||||
|
inspectResp, err := c.client.ContainerExecInspect(ctx, exec.ID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("command finished", "output", string(output), "err", err, "resp", inspectResp)
|
||||||
|
|
||||||
|
if inspectResp.ExitCode != 0 {
|
||||||
|
return string(output), fmt.Errorf("command exited with code %d", inspectResp.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
func (c Container) Execute(ctx context.Context, cmd string) (string, error) {
|
||||||
|
return c.execUserCmd(ctx, "", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) ExecuteAs(ctx context.Context, user string, cmd string) (string, error) {
|
||||||
|
return c.execUserCmd(ctx, user, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) Sudo(ctx context.Context, cmd string) (string, error) {
|
||||||
|
return c.execUserCmd(ctx, "root", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerConfig struct {
|
||||||
|
Config *container.Config
|
||||||
|
HostConfig *container.HostConfig
|
||||||
|
NetConfig *network.NetworkingConfig
|
||||||
|
Platform *v1.Platform
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateContainer(ctx context.Context, cl *client.Client, cfg ContainerConfig) (*Container, error) {
|
||||||
|
resp, err := cl.ContainerCreate(ctx, cfg.Config, cfg.HostConfig, cfg.NetConfig, cfg.Platform, cfg.Name)
|
||||||
|
|
||||||
|
slog.Info("creating container", "resp", resp, "err", err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Container{client: cl, id: resp.ID}, nil
|
||||||
|
}
|
97
pkg/agents/console-new/execution.go
Normal file
97
pkg/agents/console-new/execution.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package console_new
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type execution struct {
|
||||||
|
Command string
|
||||||
|
Output string
|
||||||
|
WhatILearned []string
|
||||||
|
WhatIStillNeedToLearn []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const kMaxLenCommandSummary = 200
|
||||||
|
const kMaxLenCommandOutputSummary = 200
|
||||||
|
|
||||||
|
func (e execution) ToGeneralMessageHistory() gollm.Message {
|
||||||
|
if len(e.Command) > kMaxLenCommandSummary {
|
||||||
|
e.Command = e.Command[:kMaxLenCommandSummary] + "... (truncated)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Output) > kMaxLenCommandOutputSummary {
|
||||||
|
e.Output = e.Output[:kMaxLenCommandOutputSummary] + "... (truncated)"
|
||||||
|
}
|
||||||
|
|
||||||
|
text := "# " + e.Command + "\n" + e.Output
|
||||||
|
|
||||||
|
return gollm.Message{
|
||||||
|
Role: gollm.RoleUser,
|
||||||
|
Text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e execution) ToDetailedMessageHistory() gollm.Message {
|
||||||
|
prompt := "$ "
|
||||||
|
if strings.HasPrefix(e.Command, "sudo ") {
|
||||||
|
prompt = "# "
|
||||||
|
e.Command = e.Command[5:]
|
||||||
|
}
|
||||||
|
|
||||||
|
text := prompt + strings.TrimSpace(e.Command) + "\n" + e.Output
|
||||||
|
|
||||||
|
if len(e.WhatILearned) > 0 {
|
||||||
|
text += "\n\nWhat I learned:\n" + strings.Join(e.WhatILearned, "\n")
|
||||||
|
} else {
|
||||||
|
text += "\n\nI didn't learn anything new."
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.WhatIStillNeedToLearn) > 0 {
|
||||||
|
text += "\n\nWhat I still need to learn:\n" + strings.Join(e.WhatIStillNeedToLearn, "\n")
|
||||||
|
} else {
|
||||||
|
text += "\n\nI don't need to learn anything else."
|
||||||
|
}
|
||||||
|
|
||||||
|
return gollm.Message{
|
||||||
|
Role: gollm.RoleUser,
|
||||||
|
Text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type executions []execution
|
||||||
|
|
||||||
|
func (e executions) ToGeneralMessageHistory() []gollm.Message {
|
||||||
|
var messages []gollm.Message
|
||||||
|
|
||||||
|
for _, v := range e {
|
||||||
|
messages = append(messages, v.ToGeneralMessageHistory())
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e executions) ToGeneralButLastMessageHistory() []gollm.Message {
|
||||||
|
var messages []gollm.Message
|
||||||
|
|
||||||
|
for i, v := range e {
|
||||||
|
if i == len(e)-1 {
|
||||||
|
messages = append(messages, v.ToDetailedMessageHistory())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = append(messages, v.ToGeneralMessageHistory())
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
func (e executions) ToDetailedMessageHistory() []gollm.Message {
|
||||||
|
var messages []gollm.Message
|
||||||
|
|
||||||
|
for _, v := range e {
|
||||||
|
messages = append(messages, v.ToDetailedMessageHistory())
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
23
pkg/agents/console-new/util.go
Normal file
23
pkg/agents/console-new/util.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package console_new
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SafeJoinPath(tempDir, fileName string) (string, error) {
|
||||||
|
// Clean both paths
|
||||||
|
tempDir = filepath.Clean(tempDir)
|
||||||
|
fileName = filepath.Clean(fileName)
|
||||||
|
|
||||||
|
// Join paths and clean result
|
||||||
|
fullPath := filepath.Clean(filepath.Join(tempDir, fileName))
|
||||||
|
|
||||||
|
// Verify the path is still within tempDir
|
||||||
|
if !strings.HasPrefix(fullPath, tempDir+string(filepath.Separator)) {
|
||||||
|
return "", fmt.Errorf("invalid path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath, nil
|
||||||
|
}
|
302
pkg/agents/console/agent.go
Normal file
302
pkg/agents/console/agent.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/mount"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Agent struct {
|
||||||
|
// Model is the chat completion model to use
|
||||||
|
Model gollm.ChatCompletion
|
||||||
|
|
||||||
|
OnLoopComplete func(ctx context.Context, knowledge agents.Knowledge) error
|
||||||
|
|
||||||
|
OnCommandStart func(ctx context.Context, command string) error
|
||||||
|
OnCommandDone func(ctx context.Context, command string, output string, err error) error
|
||||||
|
|
||||||
|
OnDone func(ctx context.Context, knowledge agents.Knowledge) error
|
||||||
|
|
||||||
|
ContextualInformation []string
|
||||||
|
|
||||||
|
MaxCommands int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Knowledge agents.Knowledge
|
||||||
|
Directory string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer will give the model access to an ubuntu console with python and pip installed, and then ask the model to
|
||||||
|
// do what is necessary to answer the question.
|
||||||
|
func (a Agent) Answer(ctx context.Context, questions []string) (Response, error) {
|
||||||
|
var res Response
|
||||||
|
|
||||||
|
if a.MaxCommands <= 0 {
|
||||||
|
a.MaxCommands = 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Knowledge = agents.Knowledge{
|
||||||
|
OriginalQuestions: questions,
|
||||||
|
RemainingQuestions: questions,
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a temporary scratch directory
|
||||||
|
dir, err := os.MkdirTemp("", "console-")
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Directory = dir
|
||||||
|
|
||||||
|
cl, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
mounts := []mount.Mount{
|
||||||
|
{
|
||||||
|
Type: mount.TypeBind,
|
||||||
|
Source: dir,
|
||||||
|
Target: "/home/user",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := CreateContainer(ctx, cl, ContainerConfig{
|
||||||
|
Config: &container.Config{
|
||||||
|
Image: "ubuntu:latest",
|
||||||
|
Cmd: []string{"tail", "-f", "/dev/null"},
|
||||||
|
Tty: true,
|
||||||
|
WorkingDir: "/home/user",
|
||||||
|
},
|
||||||
|
HostConfig: &container.HostConfig{
|
||||||
|
AutoRemove: true,
|
||||||
|
Mounts: mounts,
|
||||||
|
},
|
||||||
|
Name: filepath.Base(dir),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to create container: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = c.Close(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
slog.Info("starting container", "dir", dir, "container", fmt.Sprintf("%+v", c))
|
||||||
|
err = c.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to start container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the model
|
||||||
|
|
||||||
|
var history executions
|
||||||
|
var keepGoing = true
|
||||||
|
|
||||||
|
opwd, epwd := c.Execute(ctx, "ls -al /home")
|
||||||
|
|
||||||
|
fmt.Println(opwd)
|
||||||
|
slog.Info("pwd", "pwd", opwd, "epwd", epwd)
|
||||||
|
|
||||||
|
tools := map[string]*gollm.Function{
|
||||||
|
"exit": gollm.NewFunction(
|
||||||
|
"exit",
|
||||||
|
"exit the container",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
RemainingQuestions []string `description:"any remaining questions that remain unanswered"`
|
||||||
|
}) (any, error) {
|
||||||
|
keepGoing = false
|
||||||
|
return "exiting", nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"write": gollm.NewFunction(
|
||||||
|
"write",
|
||||||
|
"write a file in the /root directory",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Filename string `description:"The name of the file to write"`
|
||||||
|
Content string `description:"The content of the file to write"`
|
||||||
|
}) (any, error) {
|
||||||
|
target, err := SafeJoinPath(dir, args.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.WriteString(args.Content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "wrote file", nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"read": gollm.NewFunction(
|
||||||
|
"read",
|
||||||
|
"read a file in the /root directory",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Filename string `description:"The name of the file to read"`
|
||||||
|
}) (any, error) {
|
||||||
|
target, err := SafeJoinPath(dir, args.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(target)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"execute": gollm.NewFunction(
|
||||||
|
"execute",
|
||||||
|
"execute a command in the container",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Command string `description:"The command to execute"`
|
||||||
|
}) (any, error) {
|
||||||
|
if len(history) >= a.MaxCommands {
|
||||||
|
return "too many commands", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.OnCommandStart != nil {
|
||||||
|
err := a.OnCommandStart(ctx, args.Command)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res string
|
||||||
|
// if the command starts with sudo then we need to use the sudo function
|
||||||
|
if strings.HasPrefix(args.Command, "sudo ") {
|
||||||
|
res, err = c.Sudo(ctx, args.Command[5:])
|
||||||
|
} else {
|
||||||
|
res, err = c.Execute(ctx, args.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.OnCommandDone != nil {
|
||||||
|
err = a.OnCommandDone(ctx, args.Command, res, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history = append(history, execution{
|
||||||
|
Command: args.Command,
|
||||||
|
Output: res,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}),
|
||||||
|
|
||||||
|
"sudo": gollm.NewFunction(
|
||||||
|
"sudo",
|
||||||
|
"execute a command in the container",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Command string `description:"The command to execute"`
|
||||||
|
}) (any, error) {
|
||||||
|
if len(history) >= a.MaxCommands {
|
||||||
|
return "too many commands", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.OnCommandStart != nil {
|
||||||
|
err := a.OnCommandStart(ctx, args.Command)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.Sudo(ctx, args.Command)
|
||||||
|
|
||||||
|
if a.OnCommandDone != nil {
|
||||||
|
err = a.OnCommandDone(ctx, args.Command, res, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
res = "error executing: " + err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
history = append(history, execution{
|
||||||
|
Command: "sudo " + args.Command,
|
||||||
|
Output: res,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < a.MaxCommands && len(history) < a.MaxCommands && keepGoing; i++ {
|
||||||
|
|
||||||
|
systemPrompt := `You are now in a shell in a container of the ubuntu:latest image to answer a question asked by the user, it is very basic install of ubuntu, simple things (like python) are not preinstalled but can be installed via apt. You will be run multiple times and gain knowledge throughout the process.`
|
||||||
|
|
||||||
|
if len(history) < a.MaxCommands {
|
||||||
|
systemPrompt += `You can run any command you like to get to the needed results.`
|
||||||
|
}
|
||||||
|
|
||||||
|
systemPrompt += `Alternatively, you can use the tool "write" to write a file in the home directory, and also the tool "read" to read a file in the home directory.
|
||||||
|
When you are done, please use "exit" to exit the container.
|
||||||
|
Respond with any number of commands to answer the question, they will be executed in order.`
|
||||||
|
|
||||||
|
var toolbox []*gollm.Function
|
||||||
|
|
||||||
|
// add unrestricted tools
|
||||||
|
toolbox = append(toolbox, tools["exit"], tools["write"], tools["read"])
|
||||||
|
|
||||||
|
if len(history) < a.MaxCommands {
|
||||||
|
toolbox = append(toolbox, tools["execute"], tools["sudo"])
|
||||||
|
}
|
||||||
|
|
||||||
|
kw := shared.KnowledgeWorker{
|
||||||
|
Model: a.Model,
|
||||||
|
ToolBox: gollm.NewToolBox(toolbox...),
|
||||||
|
ContextualInformation: a.ContextualInformation,
|
||||||
|
OnNewFunction: func(ctx context.Context, funcName string, args string) (any, error) {
|
||||||
|
slog.Info("new function called", "function name", funcName, "args", args)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("answering question", "question", questions[0])
|
||||||
|
r, err := kw.Answer(ctx, &res.Knowledge, systemPrompt, "", "", history.ToGeneralButLastMessageHistory(), func(res gollm.ToolCallResponse) {
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("error answering question: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Knowledge) > 0 {
|
||||||
|
slog.Info("answered question and learned", "knowledge", r.Knowledge)
|
||||||
|
} else {
|
||||||
|
slog.Info("answered question and learned nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Knowledge, err = agents.KnowledgeIntegrate(ctx, a.Model, res.Knowledge, r)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("error integrating knowledge: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("knowledge integrated", "question", questions[0], "knowledge", res.Knowledge)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
115
pkg/agents/console/container.go
Normal file
115
pkg/agents/console/container.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
client *client.Client
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) Start(ctx context.Context) error {
|
||||||
|
return c.client.ContainerStart(ctx, c.id, container.StartOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) Close(ctx context.Context) error {
|
||||||
|
timeout := 10
|
||||||
|
if err := c.client.ContainerStop(ctx, c.id, container.StopOptions{
|
||||||
|
Timeout: &timeout,
|
||||||
|
}); err != nil {
|
||||||
|
// If stop fails, force kill
|
||||||
|
if err := c.client.ContainerKill(ctx, c.id, "SIGKILL"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove container and volumes
|
||||||
|
removeOptions := container.RemoveOptions{
|
||||||
|
RemoveVolumes: true,
|
||||||
|
Force: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.client.ContainerRemove(ctx, c.id, removeOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) execUserCmd(ctx context.Context, user string, cmd string) (string, error) {
|
||||||
|
slog.Info("executing command", "user", user, "cmd", cmd)
|
||||||
|
var cmdStr = []string{"/bin/bash", "-c"}
|
||||||
|
|
||||||
|
cmdStr = append(cmdStr, cmd)
|
||||||
|
|
||||||
|
slog.Info("executing command", "user", user, "cmd", fmt.Sprintf("%#v", cmdStr))
|
||||||
|
exec, err := c.client.ContainerExecCreate(ctx, c.id, container.ExecOptions{
|
||||||
|
Cmd: cmdStr,
|
||||||
|
User: user,
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Close()
|
||||||
|
|
||||||
|
output, err := io.ReadAll(resp.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for command to finish and get exit code
|
||||||
|
inspectResp, err := c.client.ContainerExecInspect(ctx, exec.ID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("command finished", "output", string(output), "err", err, "resp", inspectResp)
|
||||||
|
|
||||||
|
if inspectResp.ExitCode != 0 {
|
||||||
|
return string(output), fmt.Errorf("command exited with code %d", inspectResp.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
func (c Container) Execute(ctx context.Context, cmd string) (string, error) {
|
||||||
|
return c.execUserCmd(ctx, "", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) ExecuteAs(ctx context.Context, user string, cmd string) (string, error) {
|
||||||
|
return c.execUserCmd(ctx, user, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Container) Sudo(ctx context.Context, cmd string) (string, error) {
|
||||||
|
return c.execUserCmd(ctx, "root", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerConfig struct {
|
||||||
|
Config *container.Config
|
||||||
|
HostConfig *container.HostConfig
|
||||||
|
NetConfig *network.NetworkingConfig
|
||||||
|
Platform *v1.Platform
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateContainer(ctx context.Context, cl *client.Client, cfg ContainerConfig) (*Container, error) {
|
||||||
|
resp, err := cl.ContainerCreate(ctx, cfg.Config, cfg.HostConfig, cfg.NetConfig, cfg.Platform, cfg.Name)
|
||||||
|
|
||||||
|
slog.Info("creating container", "resp", resp, "err", err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Container{client: cl, id: resp.ID}, nil
|
||||||
|
}
|
97
pkg/agents/console/execution.go
Normal file
97
pkg/agents/console/execution.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type execution struct {
|
||||||
|
Command string
|
||||||
|
Output string
|
||||||
|
WhatILearned []string
|
||||||
|
WhatIStillNeedToLearn []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const kMaxLenCommandSummary = 200
|
||||||
|
const kMaxLenCommandOutputSummary = 200
|
||||||
|
|
||||||
|
func (e execution) ToGeneralMessageHistory() gollm.Message {
|
||||||
|
if len(e.Command) > kMaxLenCommandSummary {
|
||||||
|
e.Command = e.Command[:kMaxLenCommandSummary] + "... (truncated)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Output) > kMaxLenCommandOutputSummary {
|
||||||
|
e.Output = e.Output[:kMaxLenCommandOutputSummary] + "... (truncated)"
|
||||||
|
}
|
||||||
|
|
||||||
|
text := "# " + e.Command + "\n" + e.Output
|
||||||
|
|
||||||
|
return gollm.Message{
|
||||||
|
Role: gollm.RoleUser,
|
||||||
|
Text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e execution) ToDetailedMessageHistory() gollm.Message {
|
||||||
|
prompt := "$ "
|
||||||
|
if strings.HasPrefix(e.Command, "sudo ") {
|
||||||
|
prompt = "# "
|
||||||
|
e.Command = e.Command[5:]
|
||||||
|
}
|
||||||
|
|
||||||
|
text := prompt + strings.TrimSpace(e.Command) + "\n" + e.Output
|
||||||
|
|
||||||
|
if len(e.WhatILearned) > 0 {
|
||||||
|
text += "\n\nWhat I learned:\n" + strings.Join(e.WhatILearned, "\n")
|
||||||
|
} else {
|
||||||
|
text += "\n\nI didn't learn anything new."
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.WhatIStillNeedToLearn) > 0 {
|
||||||
|
text += "\n\nWhat I still need to learn:\n" + strings.Join(e.WhatIStillNeedToLearn, "\n")
|
||||||
|
} else {
|
||||||
|
text += "\n\nI don't need to learn anything else."
|
||||||
|
}
|
||||||
|
|
||||||
|
return gollm.Message{
|
||||||
|
Role: gollm.RoleUser,
|
||||||
|
Text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type executions []execution
|
||||||
|
|
||||||
|
func (e executions) ToGeneralMessageHistory() []gollm.Message {
|
||||||
|
var messages []gollm.Message
|
||||||
|
|
||||||
|
for _, v := range e {
|
||||||
|
messages = append(messages, v.ToGeneralMessageHistory())
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e executions) ToGeneralButLastMessageHistory() []gollm.Message {
|
||||||
|
var messages []gollm.Message
|
||||||
|
|
||||||
|
for i, v := range e {
|
||||||
|
if i == len(e)-1 {
|
||||||
|
messages = append(messages, v.ToDetailedMessageHistory())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = append(messages, v.ToGeneralMessageHistory())
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
func (e executions) ToDetailedMessageHistory() []gollm.Message {
|
||||||
|
var messages []gollm.Message
|
||||||
|
|
||||||
|
for _, v := range e {
|
||||||
|
messages = append(messages, v.ToDetailedMessageHistory())
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
23
pkg/agents/console/util.go
Normal file
23
pkg/agents/console/util.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SafeJoinPath(tempDir, fileName string) (string, error) {
|
||||||
|
// Clean both paths
|
||||||
|
tempDir = filepath.Clean(tempDir)
|
||||||
|
fileName = filepath.Clean(fileName)
|
||||||
|
|
||||||
|
// Join paths and clean result
|
||||||
|
fullPath := filepath.Clean(filepath.Join(tempDir, fileName))
|
||||||
|
|
||||||
|
// Verify the path is still within tempDir
|
||||||
|
if !strings.HasPrefix(fullPath, tempDir+string(filepath.Separator)) {
|
||||||
|
return "", fmt.Errorf("invalid path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath, nil
|
||||||
|
}
|
59
pkg/agents/extract_knowledge.go
Normal file
59
pkg/agents/extract_knowledge.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtractKnowledge will take a knowledge object and use the gained knowledge to extract the knowledge relevant to the
|
||||||
|
// questions provided.
|
||||||
|
// sourceData is the raw text to analyze for the knowledge.
|
||||||
|
// source is the source of the information, such as a URL.
|
||||||
|
// questions are the questions that the knowledge is trying to answer.
|
||||||
|
// model is the chat completion model to use.
|
||||||
|
// 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.
|
||||||
|
// 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) {
|
||||||
|
|
||||||
|
var knowledge 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 struct {
|
||||||
|
Info string `description:"The information to learn from the text."`
|
||||||
|
}) (any, error) {
|
||||||
|
knowledge.Knowledge = append(knowledge.Knowledge, 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."`
|
||||||
|
}) (any, error) {
|
||||||
|
knowledge.RemainingQuestions = []string{args.Remaining}
|
||||||
|
return "", nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Overwrite this agent's system prompts with the ones needed for this function.
|
||||||
|
|
||||||
|
var questionPrompt = "The questions you are trying to answer using the text are:\n" + strings.Join(questions, "\n")
|
||||||
|
if len(questions) == 1 {
|
||||||
|
questionPrompt = "The question you are trying to answer using the text is: " + questions[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := a.
|
||||||
|
WithSystemPrompt(`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".`).
|
||||||
|
WithSystemPromptSuffix(``).
|
||||||
|
WithToolbox(gollm.NewToolBox(fnAnswer, fnNoAnswer)).
|
||||||
|
CallAndExecute(ctx, "The text for you to evaluate is: "+sourceData, questionPrompt)
|
||||||
|
|
||||||
|
return knowledge, err
|
||||||
|
}
|
108
pkg/agents/knowledge.go
Normal file
108
pkg/agents/knowledge.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TidBit is a small piece of information that the AI has learned.
|
||||||
|
type TidBit struct {
|
||||||
|
Info string `json:"info"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Knowledge struct {
|
||||||
|
// OriginalQuestions are the questions that was asked first to the AI before any processing was done.
|
||||||
|
OriginalQuestions []string `json:"originalQuestions"`
|
||||||
|
|
||||||
|
// RemainingQuestions is the questions that are left to find answers for.
|
||||||
|
RemainingQuestions []string `json:"remainingQuestions"`
|
||||||
|
|
||||||
|
// NotesToSelf are notes that the AI has made for itself.
|
||||||
|
NotesToSelf []string `json:"notesToSelf"`
|
||||||
|
|
||||||
|
// CurrentObjectives are the objectives that the AI is currently working on.
|
||||||
|
CurrentObjectives []string `json:"currentObjectives"`
|
||||||
|
|
||||||
|
// Knowledge are the tidbits of information that the AI has learned.
|
||||||
|
Knowledge []TidBit `json:"knowledge"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Knowledge) ToSystemMessage() gollm.Message {
|
||||||
|
var sources = map[string][]string{}
|
||||||
|
|
||||||
|
for _, t := range k.Knowledge {
|
||||||
|
sources[t.Source] = append(sources[t.Source], t.Info)
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
if len(k.OriginalQuestions) > 0 {
|
||||||
|
msg += "Original questions asked:\n - " + strings.Join(k.OriginalQuestions, "\n - ") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(k.NotesToSelf) > 0 {
|
||||||
|
msg += "Notes to self:\n - " + strings.Join(k.NotesToSelf, "\n - ") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sources) > 0 {
|
||||||
|
msg += "Learned information:\n"
|
||||||
|
for source, info := range sources {
|
||||||
|
if source == "" {
|
||||||
|
source = "(unsourced)"
|
||||||
|
}
|
||||||
|
msg += " - From " + source + ":\n - " + strings.Join(info, "\n - ") + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(k.CurrentObjectives) > 0 {
|
||||||
|
msg += "Current objectives:\n - " + strings.Join(k.CurrentObjectives, "\n - ") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(k.RemainingQuestions) > 0 {
|
||||||
|
msg += "Remaining questions:\n - " + strings.Join(k.RemainingQuestions, "\n - ") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return gollm.Message{
|
||||||
|
Role: gollm.RoleSystem,
|
||||||
|
Text: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMessage converts the knowledge to a message that can be sent to the LLM.
|
||||||
|
func (k Knowledge) ToMessage() string {
|
||||||
|
var learned []string
|
||||||
|
for _, t := range k.Knowledge {
|
||||||
|
learned = append(learned, t.Info)
|
||||||
|
}
|
||||||
|
return "Original questions asked:\n" + strings.Join(k.OriginalQuestions, "\n") + "\n" +
|
||||||
|
"Learned information:\n" + strings.Join(learned, "\n") + "\n" +
|
||||||
|
"Remaining questions:\n" + strings.Join(k.RemainingQuestions, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absorb adds the knowledge from the other knowledge objects to this one.
|
||||||
|
// The OriginalQuestions returned will be from value of k's OriginalQuestions.
|
||||||
|
// If any absorbed knowledge has any "CurrentObjectives" set they will explicitly overwrite the current objectives in
|
||||||
|
// aggregate, all CurrentObjectives from all absorbed knowledge will be used.
|
||||||
|
// Any new Knowledge or NotesToSelf will be appended to the current Knowledge and NotesToSelf.
|
||||||
|
func (k Knowledge) Absorb(o ...Knowledge) Knowledge {
|
||||||
|
res := k
|
||||||
|
|
||||||
|
var newObjectives []string
|
||||||
|
|
||||||
|
for _, ok := range o {
|
||||||
|
if len(ok.CurrentObjectives) > 0 {
|
||||||
|
newObjectives = append(newObjectives, ok.CurrentObjectives...)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.NotesToSelf = append(res.NotesToSelf, ok.NotesToSelf...)
|
||||||
|
res.Knowledge = append(res.Knowledge, ok.Knowledge...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newObjectives) > 0 {
|
||||||
|
res.CurrentObjectives = newObjectives
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
86
pkg/agents/knowledge_integrate.go
Normal file
86
pkg/agents/knowledge_integrate.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// This will override objectives, notes, and remaining questions.
|
||||||
|
func (a Agent) KnowledgeIntegrate(ctx context.Context, base Knowledge, in ...Knowledge) (Knowledge, error) {
|
||||||
|
// if there are no changes we can just return the knowledge
|
||||||
|
if len(in) == 0 {
|
||||||
|
return base, nil
|
||||||
|
} else {
|
||||||
|
// if there are no entries in Knowledge.Knowledge, Knowledge.CurrentObjectives, or Knowledge.NotesToSelf then
|
||||||
|
// we can just exit out
|
||||||
|
needToRun := false
|
||||||
|
for _, k := range in {
|
||||||
|
if len(k.Knowledge) > 0 || len(k.CurrentObjectives) > 0 || len(k.NotesToSelf) > 0 {
|
||||||
|
needToRun = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !needToRun {
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var incoming Knowledge
|
||||||
|
|
||||||
|
for _, k := range in {
|
||||||
|
incoming.NotesToSelf = append(incoming.NotesToSelf, k.NotesToSelf...)
|
||||||
|
incoming.CurrentObjectives = append(incoming.CurrentObjectives, k.CurrentObjectives...)
|
||||||
|
incoming.Knowledge = append(incoming.Knowledge, k.Knowledge...)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseMsg := base.ToSystemMessage()
|
||||||
|
incomingMsg := incoming.ToSystemMessage()
|
||||||
|
|
||||||
|
baseMsg.Text = "The original knowledge is as follows: " + baseMsg.Text
|
||||||
|
incomingMsg.Text = "The new knowledge is as follows: " + incomingMsg.Text
|
||||||
|
|
||||||
|
var result = Knowledge{
|
||||||
|
OriginalQuestions: base.OriginalQuestions,
|
||||||
|
Knowledge: append(base.Knowledge, incoming.Knowledge...),
|
||||||
|
}
|
||||||
|
tools := gollm.NewToolBox(
|
||||||
|
gollm.NewFunction(
|
||||||
|
"remaining_questions",
|
||||||
|
"Use the remaining_questions function to indicate what questions remain unanswered. Call this exactly one time. If your current questions are repetitive or not useful, then you can call this function to edit or remove them. If you have new questions, then you can call this function to add them. What is set here will be the final set of questions.",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
RemainingQuestions []string `description:"The questions that remain unanswered."`
|
||||||
|
}) (any, error) {
|
||||||
|
result.RemainingQuestions = append(result.RemainingQuestions, args.RemainingQuestions...)
|
||||||
|
return "ok", nil
|
||||||
|
}),
|
||||||
|
gollm.NewFunction(
|
||||||
|
"notes_to_self",
|
||||||
|
"Use the notes_to_self function to leave or edit notes for yourself. Call this exactly one time. If your current notes are repetitive or not useful, then you can call this function to edit or remove them. What is set here will be the final set",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Notes []string `description:"The notes to leave for yourself."`
|
||||||
|
}) (any, error) {
|
||||||
|
result.NotesToSelf = append(result.NotesToSelf, args.Notes...)
|
||||||
|
return "ok", nil
|
||||||
|
}),
|
||||||
|
gollm.NewFunction(
|
||||||
|
"new_objectives",
|
||||||
|
"Use the new_objectives function to set new objectives for the LLM to work on. Call this exactly one time. If your current objectives are repetitive or not useful, then you can call this function to edit or remove them. What is set here will be the final set of objectives.",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Objectives []string `description:"The objectives to set for the LLM to work on."`
|
||||||
|
}) (any, error) {
|
||||||
|
result.CurrentObjectives = append(result.CurrentObjectives, args.Objectives...)
|
||||||
|
return "ok", nil
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := a.WithSystemPrompt(`You are combining knowledge that has been observed in two states, the original knowledge is what was known before an action was taken, and the new knowledge is what was learned after an action was taken. If the new knowledge answers or opens any new questions, notes, or objectives you should add, edit, or remove them now. The original knowledge is as follows: ` + baseMsg.Text + ` The new knowledge is as follows: ` + incomingMsg.Text).
|
||||||
|
WithSystemPromptSuffix(``).
|
||||||
|
WithToolbox(tools).
|
||||||
|
CallAndExecute(ctx)
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
|
||||||
|
}
|
@ -7,18 +7,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KnowledgeProcessor struct {
|
// AnswerQuestionWithKnowledge will take a knowledge object and use the gained knowledge to answer a question.
|
||||||
Model gollm.ChatCompletion
|
func (a Agent) AnswerQuestionWithKnowledge(ctx context.Context, knowledge Knowledge) (string, error) {
|
||||||
ContextualInformation []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process takes a knowledge object and processes it into a response string.
|
|
||||||
func (a KnowledgeProcessor) Process(ctx context.Context, knowledge shared.Knowledge) (string, error) {
|
|
||||||
originalQuestions := strings.Join(knowledge.OriginalQuestions, "\n")
|
originalQuestions := strings.Join(knowledge.OriginalQuestions, "\n")
|
||||||
infoGained := ""
|
infoGained := ""
|
||||||
|
|
||||||
@ -60,25 +53,12 @@ Here is the knowledge I have gathered from ` + fmt.Sprint(len(sources)) + ` sour
|
|||||||
|
|
||||||
systemPrompt += "\n\nUsing the sources, write an answer to the original question. Note any information that wasn't able to be answered."
|
systemPrompt += "\n\nUsing the sources, write an answer to the original question. Note any information that wasn't able to be answered."
|
||||||
|
|
||||||
req := gollm.Request{
|
res, err := a.WithSystemPrompt(systemPrompt).
|
||||||
Messages: []gollm.Message{
|
WithSystemPromptSuffix(``).
|
||||||
{
|
WithToolbox(nil).
|
||||||
Role: gollm.RoleSystem,
|
CallAndExecute(ctx)
|
||||||
Text: systemPrompt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := a.Model.ChatComplete(ctx, req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to chat complete: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
systemPrompt = `I am trying to source an analysis of information I have gathered.
|
systemPrompt = `I am trying to source an analysis of information I have gathered.
|
||||||
@ -118,27 +98,15 @@ The moon is 4.5 billion years old [1,2], 238,855 miles away from the Earth [3],
|
|||||||
}
|
}
|
||||||
|
|
||||||
summarizedData := `Here is the I need you to source with citations:
|
summarizedData := `Here is the I need you to source with citations:
|
||||||
` + resp.Choices[0].Content
|
` + res.Text
|
||||||
req = gollm.Request{
|
|
||||||
Messages: []gollm.Message{
|
res, err = a.WithSystemPrompt(systemPrompt).
|
||||||
{
|
WithSystemPromptSuffix(``).
|
||||||
Role: gollm.RoleSystem,
|
WithToolbox(nil).
|
||||||
Text: systemPrompt,
|
CallAndExecute(ctx, gollm.Message{Role: gollm.RoleSystem, Text: providedIntel}, summarizedData)
|
||||||
},
|
|
||||||
{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: providedIntel,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Role: gollm.RoleUser,
|
|
||||||
Text: summarizedData,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err = a.Model.ChatComplete(ctx, req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to chat complete: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// now go through the response and find all citations
|
// now go through the response and find all citations
|
||||||
@ -148,7 +116,7 @@ The moon is 4.5 billion years old [1,2], 238,855 miles away from the Earth [3],
|
|||||||
re := regexp.MustCompile(`\[([\d,\s]+)]`)
|
re := regexp.MustCompile(`\[([\d,\s]+)]`)
|
||||||
|
|
||||||
// find all the citations
|
// find all the citations
|
||||||
citations := re.FindAllString(resp.Choices[0].Content, -1)
|
citations := re.FindAllString(res.Text, -1)
|
||||||
|
|
||||||
// now we need to find the sources
|
// now we need to find the sources
|
||||||
lookup := map[int][]string{}
|
lookup := map[int][]string{}
|
||||||
@ -168,19 +136,19 @@ The moon is 4.5 billion years old [1,2], 238,855 miles away from the Earth [3],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := resp.Choices[0].Content
|
text := res.Text
|
||||||
|
|
||||||
if len(lookup) > 0 {
|
if len(lookup) > 0 {
|
||||||
res += "\n\nHere are the sources for the information provided:\n"
|
text += "\n\nHere are the sources for the information provided:\n"
|
||||||
|
|
||||||
for i := 1; i <= len(sources); i++ {
|
for i := 1; i <= len(sources); i++ {
|
||||||
if _, ok := lookup[i]; !ok {
|
if _, ok := lookup[i]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
res += "[" + fmt.Sprint(i) + "] <" + lookup[i][0] + ">\n"
|
text += "[" + fmt.Sprint(i) + "] <" + lookup[i][0] + ">\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return text, nil
|
||||||
}
|
}
|
||||||
|
@ -2,65 +2,31 @@ package agents
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QuestionSplitter struct {
|
// SplitQuestion is a utility function that will ask the LLM to split a question into sub-questions.
|
||||||
Model gollm.ChatCompletion
|
// It is a utility function and as such it does not use the agent's set system prompts, it will however use any
|
||||||
ContextualInfo []string
|
// contextual information that it has.
|
||||||
}
|
func (a Agent) SplitQuestion(ctx context.Context, question string) ([]string, error) {
|
||||||
|
|
||||||
func (q QuestionSplitter) SplitQuestion(ctx context.Context, question string) ([]string, error) {
|
|
||||||
var res []string
|
var res []string
|
||||||
|
fnQuestions := gollm.NewFunction(
|
||||||
req := gollm.Request{
|
"questions",
|
||||||
Toolbox: gollm.NewToolBox(
|
"split the provided question by the user into sub-questions",
|
||||||
gollm.NewFunction(
|
func(ctx *gollm.Context, args struct {
|
||||||
"questions",
|
Questions []string `description:"The questions to evaluate"`
|
||||||
"split the provided question by the user into sub-questions",
|
}) (any, error) {
|
||||||
func(ctx *gollm.Context, args struct {
|
res = args.Questions
|
||||||
Questions []string `description:"The questions to evaluate"`
|
return "", nil
|
||||||
}) (string, error) {
|
|
||||||
res = args.Questions
|
|
||||||
return "", nil
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Messages: []gollm.Message{
|
|
||||||
{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: `The user is going to ask you a question, if the question would be better answered split into multiple questions, please do so.
|
|
||||||
Respond using the "questions" function.
|
|
||||||
If the question is fine as is, respond with the original question passed to the "questions" function.`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(q.ContextualInfo) > 0 {
|
|
||||||
req.Messages = append(req.Messages, gollm.Message{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: "Some contextual information you should be aware of: " + strings.Join(q.ContextualInfo, "\n"),
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
req.Messages = append(req.Messages, gollm.Message{
|
_, err := a.WithSystemPrompt(`The user is going to ask you a question, if the question would be better answered split into multiple questions, please do so.
|
||||||
Role: gollm.RoleUser,
|
Respond using the "questions" function.
|
||||||
Text: question,
|
If the question is fine as is, respond with the original question passed to the "questions" function.`).
|
||||||
})
|
WithSystemPromptSuffix(``).
|
||||||
|
WithToolbox(gollm.NewToolBox(fnQuestions)).
|
||||||
|
CallAndExecute(ctx, question)
|
||||||
|
|
||||||
resp, err := q.Model.ChatComplete(ctx, req)
|
return res, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resp.Choices) == 0 {
|
|
||||||
return nil, fmt.Errorf("no choices found")
|
|
||||||
}
|
|
||||||
|
|
||||||
choice := resp.Choices[0]
|
|
||||||
|
|
||||||
_, _ = req.Toolbox.ExecuteCallbacks(gollm.NewContext(ctx, req, &choice, nil), choice.Calls, nil, nil)
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
72
pkg/agents/read_page.go
Normal file
72
pkg/agents/read_page.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/cache"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a Agent) ReadPage(ctx context.Context, u *url.URL, questions []string) (Knowledge, error) {
|
||||||
|
ar, err := extractArticle(ctx, u)
|
||||||
|
if err != nil {
|
||||||
|
return Knowledge{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ar.Body == "" {
|
||||||
|
return Knowledge{}, fmt.Errorf("could not extract body from page")
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.ExtractKnowledge(ctx, ar.Body, u.String(), questions)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type article struct {
|
||||||
|
URL string
|
||||||
|
Title string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractArticle(ctx context.Context, 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: cache.Nop{},
|
||||||
|
Tag: "goose",
|
||||||
|
Extractor: extractor.GooseExtractor{},
|
||||||
|
},
|
||||||
|
extractor.CacheExtractor{
|
||||||
|
Cache: cache.Nop{},
|
||||||
|
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
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
package reader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/cache"
|
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Agent struct {
|
|
||||||
// Model is the chat completion model to use
|
|
||||||
Model gollm.ChatCompletion
|
|
||||||
|
|
||||||
// OnNewFunction is called when a new function is created
|
|
||||||
OnNewFunction func(ctx context.Context, funcName string, question string, parameter string) (any, error)
|
|
||||||
|
|
||||||
// OnFunctionFinished is called when a function is finished
|
|
||||||
OnFunctionFinished func(ctx context.Context, funcName string, question string, parameter string, result string, err error, newFunctionResult any) error
|
|
||||||
|
|
||||||
Cache cache.Cache
|
|
||||||
|
|
||||||
ContextualInformation []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read will try to read the source and return the answer if possible.
|
|
||||||
func (a Agent) Read(ctx context.Context, question string, source *url.URL) (shared.Knowledge, error) {
|
|
||||||
if a.Cache == nil {
|
|
||||||
a.Cache = cache.Nop{}
|
|
||||||
}
|
|
||||||
|
|
||||||
ar, err := extractArticle(ctx, a.Cache, source)
|
|
||||||
if err != nil {
|
|
||||||
return shared.Knowledge{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ar.Body == "" {
|
|
||||||
return shared.Knowledge{}, fmt.Errorf("could not extract body from page")
|
|
||||||
}
|
|
||||||
|
|
||||||
return doesTextAnswerQuestion(ctx, question, ar.Body, source.String(), a)
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
package agents
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RemainingQuestions struct {
|
|
||||||
Model gollm.ChatCompletion
|
|
||||||
ContextualInformation []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process takes a knowledge object and processes it into a response string.
|
|
||||||
func (a RemainingQuestions) Process(ctx context.Context, knowledge shared.Knowledge) ([]string, error) {
|
|
||||||
originalQuestions := strings.Join(knowledge.OriginalQuestions, "\n")
|
|
||||||
infoGained := ""
|
|
||||||
|
|
||||||
// group all the gained knowledge by source
|
|
||||||
var m = map[string][]string{}
|
|
||||||
for _, k := range knowledge.Knowledge {
|
|
||||||
m[k.Source] = append(m[k.Source], k.Info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now order them in a list so they can be referenced by index
|
|
||||||
type source struct {
|
|
||||||
source string
|
|
||||||
info []string
|
|
||||||
}
|
|
||||||
|
|
||||||
var sources []source
|
|
||||||
for k, v := range m {
|
|
||||||
sources = append(sources, source{
|
|
||||||
source: k,
|
|
||||||
info: v,
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(infoGained) > 0 {
|
|
||||||
infoGained += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
infoGained += strings.Join(v, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
systemPrompt := `I am trying to answer a question, and I gathered some knowledge in an attempt to do so. Here is what I am trying to answer:
|
|
||||||
` + originalQuestions + `
|
|
||||||
|
|
||||||
Here is the knowledge I have gathered from ` + fmt.Sprint(len(sources)) + ` sources:
|
|
||||||
` + infoGained
|
|
||||||
|
|
||||||
systemPrompt += "\n\nUsing the information gathered, have all of the questions been answered? If not, what questions remain? Use the function 'remaining_questions' to answer this question with 0 or more remaining questions."
|
|
||||||
|
|
||||||
var res []string
|
|
||||||
req := gollm.Request{
|
|
||||||
Messages: []gollm.Message{
|
|
||||||
{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: systemPrompt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Toolbox: gollm.NewToolBox(
|
|
||||||
gollm.NewFunction(
|
|
||||||
"remaining_questions",
|
|
||||||
"Given the information learned above, the following questions remain unanswered",
|
|
||||||
func(ctx *gollm.Context, args struct {
|
|
||||||
RemainingQuestions []string `description:"The questions that remain unanswered, if any"`
|
|
||||||
}) (string, error) {
|
|
||||||
res = append(res, args.RemainingQuestions...)
|
|
||||||
return "ok", nil
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := a.Model.ChatComplete(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to chat complete: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resp.Choices) == 0 {
|
|
||||||
return nil, fmt.Errorf("no choices returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
choice := resp.Choices[0]
|
|
||||||
|
|
||||||
if len(choice.Calls) == 0 {
|
|
||||||
return nil, fmt.Errorf("no calls returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = req.Toolbox.ExecuteCallbacks(gollm.NewContext(ctx, req, &choice, nil), choice.Calls, nil, nil)
|
|
||||||
return res, err
|
|
||||||
}
|
|
216
pkg/agents/search.go
Normal file
216
pkg/agents/search.go
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo"
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deferClose(c io.Closer) {
|
||||||
|
if c != nil {
|
||||||
|
_ = c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchTool struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Function func(ctx context.Context, src *url.URL, questions []string) (Knowledge, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchAndUseTools will search duckduckgo for the given question, and then ask the LLM to select a search result to
|
||||||
|
// analyze. The LLM will be given a list of tools to use to analyze the search result, and then the LLM will be asked to
|
||||||
|
// determine if the search results answers the question.
|
||||||
|
// If the context contains a "browser" key that is an extractor.Browser, it will use that browser to search, otherwise a
|
||||||
|
// new one will be created and used for the life of this search and then closed.
|
||||||
|
// searchQuery is the question to search for.
|
||||||
|
// questions is the list of questions that the LLM is trying to answer with the search results.
|
||||||
|
// loops is the number of times to ask the LLM to analyze results if there are remaining questions before giving up.
|
||||||
|
// readers is a list of functions that will be used to read the search results. Any knowledge gained from these readers
|
||||||
|
// 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
|
||||||
|
// 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) {
|
||||||
|
var knowledge = Knowledge{
|
||||||
|
OriginalQuestions: questions,
|
||||||
|
RemainingQuestions: questions,
|
||||||
|
}
|
||||||
|
|
||||||
|
browser, ok := ctx.Value("browser").(extractor.Browser)
|
||||||
|
if !ok {
|
||||||
|
b, err := extractor.NewPlayWrightBrowser(extractor.PlayWrightBrowserOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return knowledge, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer deferClose(browser)
|
||||||
|
ctx = context.WithValue(ctx, "browser", b)
|
||||||
|
browser = b
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := duckduckgo.Config{
|
||||||
|
SafeSearch: duckduckgo.SafeSearchOff,
|
||||||
|
Region: "us-en",
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := cfg.OpenSearch(ctx, browser, searchQuery)
|
||||||
|
defer deferClose(page)
|
||||||
|
if err != nil {
|
||||||
|
return knowledge, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchResults []duckduckgo.Result
|
||||||
|
|
||||||
|
// filterResults will remove any search results that are in oldSearchResults, or are empty
|
||||||
|
filterResults := func(in []duckduckgo.Result) []duckduckgo.Result {
|
||||||
|
var res []duckduckgo.Result
|
||||||
|
for _, r := range in {
|
||||||
|
if r.URL == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxReads == 0 {
|
||||||
|
maxReads = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
var lock sync.Mutex
|
||||||
|
var analyzed []int
|
||||||
|
var converted []*gollm.Function
|
||||||
|
|
||||||
|
for _, t := range tools {
|
||||||
|
fn := gollm.NewFunction(t.Name, t.Description,
|
||||||
|
func(c *gollm.Context, arg struct {
|
||||||
|
Num int `description:"The # of search result to analyze."`
|
||||||
|
}) (any, error) {
|
||||||
|
i := arg.Num - 1
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
analyzed = append(analyzed, i)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if i < 0 || i >= len(searchResults) {
|
||||||
|
return nil, fmt.Errorf("index out of range: expect 1-%d", len(searchResults))
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(searchResults[i].URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing url: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Function(c.Context, u, questions)
|
||||||
|
})
|
||||||
|
converted = append(converted, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < loops; i++ {
|
||||||
|
// if any search results have already been analyzed, remove them
|
||||||
|
// but to make this easier, sort the list of analyzed results descending so they can be removed in order
|
||||||
|
// without changing the indexes of the remaining results
|
||||||
|
|
||||||
|
// but first remove any duplicates
|
||||||
|
var unique = map[int]struct{}{}
|
||||||
|
for _, v := range analyzed {
|
||||||
|
unique[v] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzed = analyzed[:0]
|
||||||
|
for k, _ := range unique {
|
||||||
|
analyzed = append(analyzed, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(analyzed)
|
||||||
|
for j := len(analyzed) - 1; j >= 0; j-- {
|
||||||
|
searchResults = append(searchResults[:analyzed[j]], searchResults[analyzed[j]+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any search results that have already been analyzed
|
||||||
|
analyzed = analyzed[:0]
|
||||||
|
|
||||||
|
_ = page.LoadMore()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
searchResults = filterResults(page.GetResults())
|
||||||
|
|
||||||
|
a = a.WithSystemPrompt(`You are searching DuckDuckGo for the answer to the question that will be posed by the user. The search results will be provided in system messages in the format of: #. "https://url.here" - "Title of Page" - "Description here". For instance:
|
||||||
|
1. "https://example.com" - "Example Title" - "This is an example description."
|
||||||
|
2. "https://example2.com" - "Example Title 2" - "This is an example description 2."
|
||||||
|
|
||||||
|
Use appropriate tools to analyze the search results and determine if they answer the question.`).
|
||||||
|
WithSystemPromptSuffix(``).
|
||||||
|
WithToolbox(gollm.NewToolBox(converted...))
|
||||||
|
|
||||||
|
var searches = make([]string, len(searchResults))
|
||||||
|
for i, r := range searchResults {
|
||||||
|
searches[i] = fmt.Sprintf("%d. %q - %q - %q", i+1, r.URL, r.Title, r.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(searches) > 0 {
|
||||||
|
messages = append(messages, "The search results are:\n"+strings.Join(searches, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var results CallAndExecuteResults
|
||||||
|
if allowConcurrent {
|
||||||
|
results, err = a.CallAndExecuteParallel(ctx, messages...)
|
||||||
|
} else {
|
||||||
|
results, err = a.CallAndExecute(ctx, messages...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return knowledge, fmt.Errorf("error executing search function: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var learned []Knowledge
|
||||||
|
for _, r := range results.CallResults {
|
||||||
|
if r.Error != nil {
|
||||||
|
slog.Error("error executing search function", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if k, ok := r.Result.(Knowledge); ok {
|
||||||
|
learned = append(learned, k)
|
||||||
|
} else {
|
||||||
|
slog.Error("result is not knowledge", "result", r.Result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
knowledge, err = a.KnowledgeIntegrate(ctx, knowledge, learned...)
|
||||||
|
if err != nil {
|
||||||
|
return knowledge, fmt.Errorf("error integrating knowledge: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(knowledge.RemainingQuestions) == 0 {
|
||||||
|
return knowledge, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return knowledge, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Agent) SearchAndRead(ctx context.Context, searchQuery string, questions []string, allowConcurrent bool, maxReads int) (Knowledge, error) {
|
||||||
|
return a.SearchAndUseTools(ctx, searchQuery, questions, 2, allowConcurrent, maxReads, []SearchTool{
|
||||||
|
{
|
||||||
|
Name: "ReadPage",
|
||||||
|
Description: "Read the search result and see if it answers the question. Try to avoid using this on low quality or spammy sites. You can use this function" + fmt.Sprint(maxReads) + " times, but do not call it multiple times on the same result.",
|
||||||
|
Function: a.ReadPage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -2,64 +2,53 @@ package agents
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"errors"
|
||||||
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SearchTerms struct {
|
var ErrNoSearchTerms = errors.New("no search terms")
|
||||||
Model gollm.ChatCompletion
|
|
||||||
Context []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchTerms will create search terms for the given question.
|
// GenerateSearchTerms will create search terms for the given question.
|
||||||
// alreadySearched is a list of search terms that have already been used, and should not be used again.
|
// alreadySearched is a list of search terms that have already been used, and should not be used again.
|
||||||
func (q SearchTerms) SearchTerms(ctx context.Context, question string, alreadySearched []string) (string, error) {
|
func (a Agent) GenerateSearchTerms(ctx context.Context, question string, alreadySearched []string) (string, error) {
|
||||||
var res string
|
var res string
|
||||||
|
var cantFind bool
|
||||||
req := gollm.Request{
|
fnSearch := gollm.NewFunction(
|
||||||
Toolbox: gollm.NewToolBox(
|
"search_terms",
|
||||||
gollm.NewFunction(
|
"search DuckDuckGo with these search terms for the given question",
|
||||||
"search_terms",
|
func(ctx *gollm.Context, args struct {
|
||||||
"search DuckDuckGo with these search terms for the given question",
|
SearchTerms string `description:"The search terms to use for the search"`
|
||||||
func(ctx *gollm.Context, args struct {
|
}) (any, error) {
|
||||||
SearchTerms string `description:"The search terms to use for the search"`
|
res = args.SearchTerms
|
||||||
}) (string, error) {
|
return "", nil
|
||||||
res = args.SearchTerms
|
|
||||||
return "", nil
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Messages: []gollm.Message{
|
|
||||||
{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: `You are to generate search terms for a question using DuckDuckGo. The question will be provided by the user.`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(alreadySearched) > 0 {
|
|
||||||
req.Messages = append(req.Messages, gollm.Message{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: fmt.Sprintf("The following search terms have already been used: %v", alreadySearched),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fnCantThinkOfAny := gollm.NewFunction(
|
||||||
|
"cant_think_of_any",
|
||||||
|
"tell the user that you cannot think of any search terms for the given question",
|
||||||
|
func(ctx *gollm.Context, args struct{}) (any, error) {
|
||||||
|
cantFind = true
|
||||||
|
return "", nil
|
||||||
|
})
|
||||||
|
|
||||||
|
var suffix string
|
||||||
|
if len(alreadySearched) > 0 {
|
||||||
|
suffix = "The following search terms have already been used, please avoid them: " + strings.Join(alreadySearched, ", ") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Messages = append(req.Messages, gollm.Message{
|
_, err := a.WithSystemPrompt(`You are to generate search terms for a question using DuckDuckGo. The question will be provided by the user.`).
|
||||||
Role: gollm.RoleUser,
|
WithSystemPromptSuffix(suffix).
|
||||||
Text: fmt.Sprintf("The question is: %s", question),
|
WithToolbox(gollm.NewToolBox(fnSearch, fnCantThinkOfAny)).
|
||||||
})
|
CallAndExecute(ctx, question)
|
||||||
|
|
||||||
resp, err := q.Model.ChatComplete(ctx, req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Choices) == 0 {
|
if cantFind {
|
||||||
return "", fmt.Errorf("no choices found")
|
return "cannot think of any search terms", ErrNoSearchTerms
|
||||||
}
|
}
|
||||||
|
|
||||||
choice := resp.Choices[0]
|
|
||||||
|
|
||||||
_, _ = req.Toolbox.ExecuteCallbacks(gollm.NewContext(ctx, req, &choice, nil), choice.Calls, nil, nil)
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -1,258 +0,0 @@
|
|||||||
package searcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/reader"
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo"
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
// Answer is the answer to the question that was asked.
|
|
||||||
Answer string
|
|
||||||
|
|
||||||
// Sources is a list of sources that were used to find the answer.
|
|
||||||
Sources []string
|
|
||||||
|
|
||||||
// Remaining is the remaining part(s) of the question that was not answered.
|
|
||||||
Remaining string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Agent struct {
|
|
||||||
// Model is the chat completion model to use
|
|
||||||
Model gollm.ChatCompletion
|
|
||||||
|
|
||||||
OnDone func(ctx context.Context, knowledge shared.Knowledge) error
|
|
||||||
|
|
||||||
// MaxReads is the maximum number of pages that can be read by the agent. Unlimited if <= 0.
|
|
||||||
MaxReads int
|
|
||||||
|
|
||||||
ContextualInformation []string
|
|
||||||
|
|
||||||
AllowConcurrent bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search will search duckduckgo for the given question, and then read the results to figure out the answer.
|
|
||||||
// searchQuery is the query that you want to search for, e.g. "what is the capital of France site:reddit.com"
|
|
||||||
// question is the question that you are trying to answer when reading the search results.
|
|
||||||
// If the context contains a "browser" key that is an extractor.Browser, it will use that browser to search, otherwise a
|
|
||||||
// new one will be created and used for the life of this search and then closed.
|
|
||||||
func (a Agent) Search(ctx context.Context, searchQuery string, question string) (shared.Knowledge, error) {
|
|
||||||
var knowledge = shared.Knowledge{
|
|
||||||
OriginalQuestions: []string{question},
|
|
||||||
RemainingQuestions: []string{question},
|
|
||||||
}
|
|
||||||
|
|
||||||
browser, ok := ctx.Value("browser").(extractor.Browser)
|
|
||||||
if !ok {
|
|
||||||
b, err := extractor.NewPlayWrightBrowser(extractor.PlayWrightBrowserOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return knowledge, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer deferClose(browser)
|
|
||||||
ctx = context.WithValue(ctx, "browser", b)
|
|
||||||
browser = b
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := duckduckgo.Config{
|
|
||||||
SafeSearch: duckduckgo.SafeSearchOff,
|
|
||||||
Region: "us-en",
|
|
||||||
}
|
|
||||||
|
|
||||||
page, err := cfg.OpenSearch(ctx, browser, searchQuery)
|
|
||||||
defer deferClose(page)
|
|
||||||
if err != nil {
|
|
||||||
return knowledge, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var searchResults []duckduckgo.Result
|
|
||||||
|
|
||||||
// filterResults will remove any search results that are in oldSearchResults, or are empty
|
|
||||||
filterResults := func(in []duckduckgo.Result) []duckduckgo.Result {
|
|
||||||
var res []duckduckgo.Result
|
|
||||||
for _, r := range in {
|
|
||||||
if r.URL == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res = append(res, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = page.LoadMore()
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
|
|
||||||
searchResults = filterResults(page.GetResults())
|
|
||||||
|
|
||||||
var toRead = make(chan int, a.MaxReads)
|
|
||||||
|
|
||||||
fnReadSearchResult := gollm.NewFunction("read",
|
|
||||||
"read the search result and see if it answers the question",
|
|
||||||
func(c *gollm.Context, arg struct {
|
|
||||||
Num int `description:"The # of the search result to read."`
|
|
||||||
}) (string, error) {
|
|
||||||
toRead <- arg.Num - 1
|
|
||||||
return "ok", nil
|
|
||||||
})
|
|
||||||
|
|
||||||
readSource := func(ctx context.Context, src duckduckgo.Result) (shared.Knowledge, error) {
|
|
||||||
r := reader.Agent{
|
|
||||||
Model: a.Model,
|
|
||||||
ContextualInformation: a.ContextualInformation,
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(src.URL)
|
|
||||||
if err != nil {
|
|
||||||
return shared.Knowledge{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("reading search result", "url", u)
|
|
||||||
response, err := r.Read(ctx, question, u)
|
|
||||||
if err != nil {
|
|
||||||
return shared.Knowledge{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tools := gollm.NewToolBox(fnReadSearchResult)
|
|
||||||
var req = gollm.Request{
|
|
||||||
Toolbox: tools,
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Messages = append(req.Messages, gollm.Message{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: `You are searching DuckDuckGo for the answer to the question that will be posed by the user. The search results will be provided in system messages in the format of: #. "https://url.here" - "Title of Page" - "Description here". For instance:
|
|
||||||
1. "https://example.com" - "Example Title" - "This is an example description."
|
|
||||||
2. "https://example2.com" - "Example Title 2" - "This is an example description 2."`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if a.MaxReads == 0 {
|
|
||||||
a.MaxReads = 100
|
|
||||||
}
|
|
||||||
req.Messages = append(req.Messages, gollm.Message{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: fmt.Sprintf(`You can read a search result by using the function "read_search_result" with the # of the page to read, it will attempt to read the page, and then an LLM will read the page and see if it answers the question.
|
|
||||||
can call read_search_result multiple times, up to %d times. All sources you read will be evaulated to see if they answer the question in full or at least in part.`, a.MaxReads),
|
|
||||||
})
|
|
||||||
|
|
||||||
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"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
searches := ""
|
|
||||||
for i, r := range searchResults {
|
|
||||||
if i > 0 {
|
|
||||||
searches += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
searches += fmt.Sprintf("%d. %q - %q - %q", i+1, r.URL, r.Title, r.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Messages = append(req.Messages, gollm.Message{
|
|
||||||
Role: gollm.RoleSystem,
|
|
||||||
Text: "Search results are:\n" + searches,
|
|
||||||
})
|
|
||||||
|
|
||||||
results, err := a.Model.ChatComplete(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return knowledge, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(results.Choices) == 0 {
|
|
||||||
return knowledge, fmt.Errorf("no choices were returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
choice := results.Choices[0]
|
|
||||||
|
|
||||||
// enforce the maximum number of reads
|
|
||||||
calls := choice.Calls
|
|
||||||
if len(calls) > a.MaxReads {
|
|
||||||
slog.Warn("too many calls, trimming to max", "len", len(calls), "max", a.MaxReads)
|
|
||||||
calls = calls[:a.MaxReads]
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tools.ExecuteCallbacks(gollm.NewContext(ctx, req, &choice, nil), choice.Calls, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return knowledge, err
|
|
||||||
}
|
|
||||||
|
|
||||||
close(toRead)
|
|
||||||
|
|
||||||
// make sure there are no duplicates
|
|
||||||
var uniques = map[int]struct{}{}
|
|
||||||
|
|
||||||
for i := range toRead {
|
|
||||||
uniques[i] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sources []duckduckgo.Result
|
|
||||||
|
|
||||||
for k := range uniques {
|
|
||||||
if k < 0 || k >= len(searchResults) {
|
|
||||||
slog.Warn("search result index out of range", "index", k, "len", len(searchResults))
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sources = append(sources, searchResults[k])
|
|
||||||
}
|
|
||||||
|
|
||||||
type result struct {
|
|
||||||
Knowledge shared.Knowledge
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var gainedKnowledge = make(chan result, len(sources))
|
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
for _, v := range sources {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
res, err := readSource(ctx, v)
|
|
||||||
slog.Info("read search result", "url", v.URL, "err", err)
|
|
||||||
gainedKnowledge <- result{Knowledge: res, Err: err}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("reading search results", "len", len(sources))
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
close(gainedKnowledge)
|
|
||||||
|
|
||||||
slog.Info("done reading search results", "len", len(gainedKnowledge))
|
|
||||||
|
|
||||||
for r := range gainedKnowledge {
|
|
||||||
if r.Err != nil {
|
|
||||||
slog.Info("error reading search result", "err", r.Err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
knowledge.Knowledge = append(knowledge.Knowledge, r.Knowledge.Knowledge...)
|
|
||||||
knowledge.RemainingQuestions = append(knowledge.RemainingQuestions, r.Knowledge.RemainingQuestions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.OnDone != nil {
|
|
||||||
err := a.OnDone(ctx, knowledge)
|
|
||||||
if err != nil {
|
|
||||||
return knowledge, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return knowledge, nil
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package searcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo"
|
|
||||||
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func deferClose(closer io.Closer) {
|
|
||||||
if closer != nil {
|
|
||||||
_ = closer.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type searchResult struct {
|
|
||||||
Answer string `json:"answer"`
|
|
||||||
Sources []string `json:"sources"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func fnSearch(ctx *gollm.Context, args struct {
|
|
||||||
Query string `description:"The search query to perform on duckduckgo"`
|
|
||||||
Question string `description:"The question(s) you are trying to answer when you read the search results. e.g: "`
|
|
||||||
}) (string, error) {
|
|
||||||
browser, ok := ctx.Value("browser").(extractor.Browser)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("browser not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := duckduckgo.Config{
|
|
||||||
SafeSearch: duckduckgo.SafeSearchOff,
|
|
||||||
Region: "us-en",
|
|
||||||
}
|
|
||||||
|
|
||||||
page, err := cfg.OpenSearch(ctx, browser, args.Query)
|
|
||||||
defer deferClose(page)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to search: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package shared
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TidBit is a small piece of information that the AI has learned.
|
|
||||||
type TidBit struct {
|
|
||||||
Info string
|
|
||||||
Source string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Knowledge struct {
|
|
||||||
// OriginalQuestions are the questions that was asked first to the AI before any processing was done.
|
|
||||||
OriginalQuestions []string
|
|
||||||
|
|
||||||
// RemainingQuestions is the questions that are left to find answers for.
|
|
||||||
RemainingQuestions []string
|
|
||||||
|
|
||||||
// Knowledge are the tidbits of information that the AI has learned.
|
|
||||||
Knowledge []TidBit
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToMessage converts the knowledge to a message that can be sent to the LLM.
|
|
||||||
func (k Knowledge) ToMessage() string {
|
|
||||||
var learned []string
|
|
||||||
for _, t := range k.Knowledge {
|
|
||||||
learned = append(learned, t.Info)
|
|
||||||
}
|
|
||||||
return "Original questions asked:\n" + strings.Join(k.OriginalQuestions, "\n") + "\n" +
|
|
||||||
"Learned information:\n" + strings.Join(learned, "\n") + "\n" +
|
|
||||||
"Remaining questions:\n" + strings.Join(k.RemainingQuestions, "\n")
|
|
||||||
}
|
|
162
pkg/agents/shared/knowledgeworker.go
Normal file
162
pkg/agents/shared/knowledgeworker.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KnowledgeWorker struct {
|
||||||
|
Model gollm.ChatCompletion
|
||||||
|
ToolBox *gollm.ToolBox
|
||||||
|
ContextualInformation []string
|
||||||
|
OnNewFunction func(ctx context.Context, funcName string, args string) (any, error)
|
||||||
|
OnFunctionFinished func(ctx context.Context, funcName string, args string, result any, err error, newFunctionResult any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultPrompt = `Use the provided tools to answer the questions in your current knowledge.`
|
||||||
|
|
||||||
|
// Answer will try to answer the remaining questions in the knowledge object, while providing the LLM with a couple
|
||||||
|
// extra state update objects to manage Knowledge.CurrentObjectives and Knowledge.NotesToSelf.
|
||||||
|
// systemPrompt is the main prompt to tell the LLM what to do.
|
||||||
|
// userInput is the input that the LLM is trying to learn answers from.
|
||||||
|
// 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 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) {
|
||||||
|
var req gollm.Request
|
||||||
|
|
||||||
|
if systemPrompt != "" {
|
||||||
|
req.Messages = append(req.Messages, gollm.Message{
|
||||||
|
Role: gollm.RoleSystem,
|
||||||
|
Text: systemPrompt,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
req.Messages = append(req.Messages, gollm.Message{
|
||||||
|
Role: gollm.RoleSystem,
|
||||||
|
Text: DefaultPrompt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
k := knowledge.ToSystemMessage()
|
||||||
|
if k.Text != "" {
|
||||||
|
req.Messages = append(req.Messages, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.ContextualInformation) > 0 {
|
||||||
|
req.Messages = append(req.Messages, gollm.Message{
|
||||||
|
Role: gollm.RoleSystem,
|
||||||
|
Text: "Contextual Information: " + strings.Join(w.ContextualInformation, ", "),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(history) > 0 {
|
||||||
|
req.Messages = append(req.Messages, history...)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Messages = append(req.Messages, gollm.Message{
|
||||||
|
Role: gollm.RoleSystem,
|
||||||
|
Text: `Feel free to call "learn", "notes_to_self", or "new_objectives" to leave notes for yourself or set new objectives for the LLM to work on, all can be called multiple times.`,
|
||||||
|
})
|
||||||
|
|
||||||
|
lastMsg := "Please try to batch all of your calls, such as if there are things you are learning and notes you are setting, try to do a call for each in one response."
|
||||||
|
if userInput != "" {
|
||||||
|
lastMsg += "\nNext input you are trying to use function calls to answer your objectives from is: " + userInput
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Messages = append(req.Messages, gollm.Message{
|
||||||
|
Role: gollm.RoleUser,
|
||||||
|
Text: lastMsg,
|
||||||
|
})
|
||||||
|
|
||||||
|
req.Toolbox = w.ToolBox.
|
||||||
|
WithFunction(*gollm.NewFunction(
|
||||||
|
"notes_to_self",
|
||||||
|
"leave future executions of the LLM a note or two, can be called many times",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
NotesToSelf []string `description:"Notes to leave for yourself for later."`
|
||||||
|
}) (any, error) {
|
||||||
|
return agents.Knowledge{
|
||||||
|
NotesToSelf: args.NotesToSelf,
|
||||||
|
}, nil
|
||||||
|
})).
|
||||||
|
WithFunction(*gollm.NewFunction(
|
||||||
|
"new_objectives",
|
||||||
|
"Set new objectives for the LLM to work on, can be called many times. If no new objectives are set, the LLM will continue to work on the current objectives.",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Objectives []string `description:"The objectives to set for executions going forward."`
|
||||||
|
}) (any, error) {
|
||||||
|
return agents.Knowledge{
|
||||||
|
CurrentObjectives: args.Objectives,
|
||||||
|
}, nil
|
||||||
|
})).
|
||||||
|
WithFunction(*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. Can be called many times.`,
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Info []string `description:"The information to learn from the input."`
|
||||||
|
}) (any, error) {
|
||||||
|
var k []agents.TidBit
|
||||||
|
|
||||||
|
for _, i := range args.Info {
|
||||||
|
k = append(k, agents.TidBit{Info: i, Source: source})
|
||||||
|
}
|
||||||
|
|
||||||
|
return agents.Knowledge{
|
||||||
|
Knowledge: k,
|
||||||
|
}, nil
|
||||||
|
})).
|
||||||
|
WithRequireTool(true)
|
||||||
|
|
||||||
|
for _, m := range req.Messages {
|
||||||
|
fmt.Println("Role: ", m.Role, "Text: ", m.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Calling...")
|
||||||
|
|
||||||
|
resp, err := w.Model.ChatComplete(context, req)
|
||||||
|
if err != nil {
|
||||||
|
return agents.Knowledge{}, fmt.Errorf("error calling model: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Choices) == 0 {
|
||||||
|
return agents.Knowledge{}, fmt.Errorf("no choices found")
|
||||||
|
}
|
||||||
|
|
||||||
|
choice := resp.Choices[0]
|
||||||
|
|
||||||
|
if len(choice.Calls) == 0 {
|
||||||
|
return agents.Knowledge{}, fmt.Errorf("no calls found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var callNames []string
|
||||||
|
|
||||||
|
for _, c := range choice.Calls {
|
||||||
|
callNames = append(callNames, c.FunctionCall.Name)
|
||||||
|
}
|
||||||
|
results, err := w.ToolBox.ExecuteCallbacks(gollm.NewContext(context, req, &choice, nil), choice.Calls, w.OnNewFunction, w.OnFunctionFinished)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return agents.Knowledge{}, fmt.Errorf("error executing callbacks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = agents.Knowledge{}
|
||||||
|
|
||||||
|
for _, r := range results {
|
||||||
|
switch v := r.Result.(type) {
|
||||||
|
case agents.Knowledge:
|
||||||
|
res = res.Absorb(v)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if resultWorker != nil {
|
||||||
|
resultWorker(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
|
||||||
|
}
|
29
pkg/agents/steps.go
Normal file
29
pkg/agents/steps.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package agents
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Steps is a utility function that will ask the LLM to split a goal into smaller steps.
|
||||||
|
// It is a utility function and as such it does not use the agent's set system prompts, it will however use any
|
||||||
|
// contextual information that it has.
|
||||||
|
func (a Agent) Steps(ctx context.Context, goal string) ([]string, error) {
|
||||||
|
var res []string
|
||||||
|
fnSteps := gollm.NewFunction(
|
||||||
|
"steps",
|
||||||
|
"split the provided goal by the user into sub-steps",
|
||||||
|
func(ctx *gollm.Context, args struct {
|
||||||
|
Steps []string `description:"The steps that should be taken to achieve the goal"`
|
||||||
|
}) (any, error) {
|
||||||
|
res = append(res, args.Steps...)
|
||||||
|
return "", nil
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := a.WithSystemPrompt(`The user is going to mention a goal to you, and you are to respond using only one call of the "steps" function. You should provide the "steps" function with steps that should be taken to achieve the goal the user requests.`).
|
||||||
|
WithSystemPromptSuffix(``).
|
||||||
|
WithToolbox(gollm.NewToolBox(fnSteps)).
|
||||||
|
CallAndExecute(ctx, goal)
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
@ -13,7 +13,7 @@ var Calculator = gollm.NewFunction(
|
|||||||
"A starlark calculator",
|
"A starlark calculator",
|
||||||
func(ctx *gollm.Context, args struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
Expression string `description:"The expression to evaluate using starlark"`
|
Expression string `description:"The expression to evaluate using starlark"`
|
||||||
}) (string, error) {
|
}) (any, error) {
|
||||||
val, err := starlark.EvalOptions(&syntax.FileOptions{},
|
val, err := starlark.EvalOptions(&syntax.FileOptions{},
|
||||||
&starlark.Thread{Name: "main"},
|
&starlark.Thread{Name: "main"},
|
||||||
"input",
|
"input",
|
||||||
|
@ -19,7 +19,7 @@ func CreateWolframFunctions(appId string) WolframFunctions {
|
|||||||
"Query the Wolfram Alpha API",
|
"Query the Wolfram Alpha API",
|
||||||
func(ctx *gollm.Context, args struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
Question string `description:"The question to ask Wolfram|Alpha"`
|
Question string `description:"The question to ask Wolfram|Alpha"`
|
||||||
}) (string, error) {
|
}) (any, error) {
|
||||||
return client.GetShortAnswerQuery(args.Question, wolfram.Imperial, 10)
|
return client.GetShortAnswerQuery(args.Question, wolfram.Imperial, 10)
|
||||||
}),
|
}),
|
||||||
Metric: gollm.NewFunction(
|
Metric: gollm.NewFunction(
|
||||||
@ -27,7 +27,7 @@ func CreateWolframFunctions(appId string) WolframFunctions {
|
|||||||
"Query the Wolfram Alpha API",
|
"Query the Wolfram Alpha API",
|
||||||
func(ctx *gollm.Context, args struct {
|
func(ctx *gollm.Context, args struct {
|
||||||
Question string `description:"The question to ask Wolfram|Alpha"`
|
Question string `description:"The question to ask Wolfram|Alpha"`
|
||||||
}) (string, error) {
|
}) (any, error) {
|
||||||
return client.GetShortAnswerQuery(args.Question, wolfram.Metric, 10)
|
return client.GetShortAnswerQuery(args.Question, wolfram.Metric, 10)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user