Compare commits

...

6 Commits

Author SHA1 Message Date
1c3ea7d1f1 Refactor Knowledge struct into shared package
Moved the Knowledge struct and related types to the shared package, updating all references across the codebase. This improves modularity and enables better reuse of the Knowledge type across different components.
2025-05-03 22:09:02 -04:00
d2b9eb350e Refactor console agents for improved modularity and configuration
Consolidated and refactored console agent logic into a streamlined structure with better configuration capabilities via `ConsoleConfig`. Improved code reusability and readability by converting nested structures and functions, and introduced more modular execution types for handling commands and history tracking.
2025-05-03 22:06:32 -04:00
4c5922d571 Merge remote-tracking branch 'origin/main' 2025-05-03 21:52:04 -04:00
7834943cb6 Update go-llm module version in go.mod and go.sum 2025-05-03 21:51:54 -04:00
8ae16cb633 Update go-llm module version in go.mod and go.sum 2025-05-03 05:55:09 -04:00
24f248d900 Update go-llm module version in go.mod and go.sum 2025-05-03 05:36:37 -04:00
14 changed files with 322 additions and 267 deletions

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents" "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/console" "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/console"
@@ -102,7 +103,7 @@ func main() {
c := console.Agent{ c := console.Agent{
Model: m, Model: m,
OnDone: func(ctx context.Context, knowledge agents.Knowledge) error { return nil }, OnDone: func(ctx context.Context, knowledge shared.Knowledge) error { return nil },
OnCommandStart: func(ctx context.Context, command string) error { OnCommandStart: func(ctx context.Context, command string) error {
slog.Info("command", "command", command) slog.Info("command", "command", command)
return nil return nil

27
go.mod
View File

@@ -8,14 +8,14 @@ replace github.com/rocketlaunchr/google-search => github.com/chrisjoyce911/googl
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-20250412190744-39ffb8223775 gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250502021123-e9baf7910e00
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/asticode/go-astisub v0.34.0 github.com/asticode/go-astisub v0.34.0
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/docker/docker v28.0.2+incompatible
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/lrstanley/go-ytdlp v0.0.0-20250401014907-da1707e4fb85 github.com/lrstanley/go-ytdlp v0.0.0-20250501010938-80d02fe36936
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/playwright-community/playwright-go v0.5101.0 github.com/playwright-community/playwright-go v0.5101.0
github.com/rocketlaunchr/google-search v1.1.6 github.com/rocketlaunchr/google-search v1.1.6
@@ -24,12 +24,12 @@ require (
) )
require ( require (
cloud.google.com/go v0.120.0 // indirect cloud.google.com/go v0.121.0 // indirect
cloud.google.com/go/ai v0.10.1 // indirect cloud.google.com/go/ai v0.11.0 // indirect
cloud.google.com/go/auth v0.15.0 // indirect cloud.google.com/go/auth v0.16.1 // indirect
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.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.2.0 // indirect github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/PuerkitoBio/goquery v1.10.3 // indirect github.com/PuerkitoBio/goquery v1.10.3 // indirect
@@ -42,7 +42,7 @@ require (
github.com/asticode/go-astits v1.13.0 // indirect github.com/asticode/go-astits v1.13.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/log v0.1.0 // 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.7 // 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/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
@@ -63,7 +63,7 @@ require (
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
github.com/google/generative-ai-go v0.19.0 // indirect github.com/google/generative-ai-go v0.20.1 // indirect
github.com/google/s2a-go v0.1.9 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
@@ -76,13 +76,12 @@ require (
github.com/moby/term v0.5.2 // indirect github.com/moby/term v0.5.2 // indirect
github.com/morikuni/aec v1.0.0 // 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/openai/openai-go v0.1.0-beta.9 // indirect github.com/openai/openai-go v0.1.0-beta.10 // indirect
github.com/opencontainers/go-digest v1.0.0 // 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
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/sashabaranov/go-openai v1.38.1 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/temoto/robotstxt v1.1.2 // indirect github.com/temoto/robotstxt v1.1.2 // indirect
github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect
@@ -103,11 +102,11 @@ require (
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.11.0 // indirect
google.golang.org/api v0.228.0 // indirect google.golang.org/api v0.231.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-20250409194420-de1ac958c67a // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.71.1 // indirect google.golang.org/grpc v1.72.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gotest.tools/v3 v3.5.2 // indirect gotest.tools/v3 v3.5.2 // indirect
) )

86
go.sum
View File

@@ -1,28 +1,20 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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.121.0 h1:pgfwva8nGw7vivjZiRfrmglGWiCJBP+0OmDpenG/Fwg=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q=
cloud.google.com/go/ai v0.10.1 h1:EU93KqYmMeOKgaBXAz2DshH2C/BzAT1P+iJORksLIic= cloud.google.com/go/ai v0.11.0 h1:e0uK//+NCv70DBz8qJ0zRW90OP5kQjt27CjbHl3SG2A=
cloud.google.com/go/ai v0.10.1/go.mod h1:sWWHZvmJ83BjuxAQtYEiA0SFTpijtbH+SXWFO14ri5A= cloud.google.com/go/ai v0.11.0/go.mod h1:bmTa0Ir0VJzDEv841PbAlQlJ/Jxv8Ab5L6LR7Z1Q/QE=
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= 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/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 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= 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.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
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 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-extractor v0.0.0-20250318064250-39453288ce2a/go.mod h1:fzvvUfN8ej2u1ruCsABG+D+2dAPfOklInS4b1pvog1M=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250407055702-5ba0d5df7e96 h1:kT0pwH+q9i4TcFSRems8UFgaKCO94bCzLCf0IgAj6qw= gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250502021123-e9baf7910e00 h1:yPVQZG4xdENkWZi/9OLQcFSQb603ftWUTRct51Q64xc=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250407055702-5ba0d5df7e96/go.mod h1:Puz2eDyIwyQLKFt20BU9eRrfkUpBFo+ZX+PtTI64XSo= gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250502021123-e9baf7910e00/go.mod h1:RPbuI2VSwQJArwr4tdqmu+fEKlhpro5Cqtq6aC4Cp1w=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250408003321-2ae583e9f360 h1:eZ8CZ1o4ZaciaDL0B/6tYwIERFZ94tNQtG7NKdb7cEQ=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250408003321-2ae583e9f360/go.mod h1:Puz2eDyIwyQLKFt20BU9eRrfkUpBFo+ZX+PtTI64XSo=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250412062040-3093b988f80a h1:SH2fWzDtv2KeWiwW+wtPr4NLI3CwdCSoYKn3FCRDdZc=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250412062040-3093b988f80a/go.mod h1:RPbuI2VSwQJArwr4tdqmu+fEKlhpro5Cqtq6aC4Cp1w=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250412074148-916f07be1840 h1:Yf33CXaCYwBG6AQxQAiK5MrdCQrRrf+Y0tzSfuXPb30=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250412074148-916f07be1840/go.mod h1:RPbuI2VSwQJArwr4tdqmu+fEKlhpro5Cqtq6aC4Cp1w=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250412190744-39ffb8223775 h1:KF6HdT7A5fqDnEWRjoYCm2mm5booxd+YD6j0wJkh+GU=
gitea.stevedudenhoeffer.com/steve/go-llm v0.0.0-20250412190744-39ffb8223775/go.mod h1:RPbuI2VSwQJArwr4tdqmu+fEKlhpro5Cqtq6aC4Cp1w=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 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/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 v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -31,15 +23,11 @@ github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d h1:dxGZ0drmrUfNO
github.com/Edw590/go-wolfram v0.0.0-20241010091529-fb9031908c5d/go.mod h1:ubjYqrt3dF4G+YVEDQr+qa2aveeMzt27o/GOH2hswPo= 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 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs= github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= 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.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= 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/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275 h1:Kuhf+w+ilOGoXaR4O4nZ6Dp+ZS83LdANUjwyMXsPGX4= github.com/advancedlogic/GoOse v0.0.0-20231203033844-ae6b36caf275 h1:Kuhf+w+ilOGoXaR4O4nZ6Dp+ZS83LdANUjwyMXsPGX4=
@@ -83,15 +71,13 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/chrisjoyce911/google-search v0.0.0-20230910003754-e501aedf805a h1:OZQiBySVd55npXVsIKnJT6q+9A1tPiXhGnFlc+q0YqQ= 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/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 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.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -159,8 +145,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/golang/protobuf v1.5.3/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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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.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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -192,8 +178,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/liushuangls/go-anthropic/v2 v2.15.0 h1:zpplg7BRV/9FlMmeMPI0eDwhViB0l9SkNrF8ErYlRoQ= github.com/liushuangls/go-anthropic/v2 v2.15.0 h1:zpplg7BRV/9FlMmeMPI0eDwhViB0l9SkNrF8ErYlRoQ=
github.com/liushuangls/go-anthropic/v2 v2.15.0/go.mod h1:kq2yW3JVy1/rph8u5KzX7F3q95CEpCT2RXp/2nfCmb4= github.com/liushuangls/go-anthropic/v2 v2.15.0/go.mod h1:kq2yW3JVy1/rph8u5KzX7F3q95CEpCT2RXp/2nfCmb4=
github.com/lrstanley/go-ytdlp v0.0.0-20250401014907-da1707e4fb85 h1:fgU9HcQ95uG9vqkYP/YW/H6DhwsmkXHriuMM27bwpYU= github.com/lrstanley/go-ytdlp v0.0.0-20250501010938-80d02fe36936 h1:hYa4l1wvSl9OHHgfNemq8I/L1iyWl2KGVp43GMwTGzQ=
github.com/lrstanley/go-ytdlp v0.0.0-20250401014907-da1707e4fb85/go.mod h1:HpxGaeaOpXVUPxUUmj8Izr3helrDGN90haPtmpY5xzA= github.com/lrstanley/go-ytdlp v0.0.0-20250501010938-80d02fe36936/go.mod h1:HpxGaeaOpXVUPxUUmj8Izr3helrDGN90haPtmpY5xzA=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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.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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
@@ -210,12 +196,8 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 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 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/openai/openai-go v0.1.0-beta.6 h1:JquYDpprfrGnlKvQQg+apy9dQ8R9mIrm+wNvAPp6jCQ= github.com/openai/openai-go v0.1.0-beta.10 h1:CknhGXe8aXQMRuqg255PFnWzgRY9nEryMxoNIBBM9tU=
github.com/openai/openai-go v0.1.0-beta.6/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/openai/openai-go v0.1.0-beta.10/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/openai/openai-go v0.1.0-beta.7 h1:ykC09BCIgdXL69wE/8NUjL2rCdAbo9kL3AjnGR6H91o=
github.com/openai/openai-go v0.1.0-beta.7/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/openai/openai-go v0.1.0-beta.9 h1:ABpubc5yU/3ejee2GgRrbFta81SG/d7bQbB8mIdP0Xo=
github.com/openai/openai-go v0.1.0-beta.9/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 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/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 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -224,8 +206,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
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/playwright-community/playwright-go v0.5101.0 h1:gVCMZThDO76LJ/aCI27lpB8hEAWhZszeS0YB+oTxJp0= github.com/playwright-community/playwright-go v0.5101.0 h1:gVCMZThDO76LJ/aCI27lpB8hEAWhZszeS0YB+oTxJp0=
github.com/playwright-community/playwright-go v0.5101.0/go.mod h1:kBNWs/w2aJ2ZUp1wEOOFLXgOqvppFngM5OS+qyhl+ZM= github.com/playwright-community/playwright-go v0.5101.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -235,15 +215,11 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.2.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 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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-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 h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 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/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 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -359,8 +335,6 @@ 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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -447,8 +421,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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.231.0 h1:LbUD5FUl0C4qwia2bjXhCMH65yz1MLPzA/0OYEsYY7Q=
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= google.golang.org/api v0.231.0/go.mod h1:H52180fPI/QQlUc0F4xWfGZILdv09GCWKt2bcsn164A=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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.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.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@@ -458,23 +432,15 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20250404141209-ee84b53bf3d0 h1:Qbb5RVn5xzI4naMJSpJ7lhvmos6UwZkbekd5Uz7rt9E= google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:0PeQib/pH3nB/5pEmFeVQJotzGohV0dq4Vcp09H5yhE=
google.golang.org/genproto/googleapis/api v0.0.0-20250404141209-ee84b53bf3d0/go.mod h1:6T35kB3IPpdw7Wul09by0G/JuOuIFkXV6OOvt8IZeT8= google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/api v0.0.0-20250407143221-ac9807e6c755 h1:AMLTAunltONNuzWgVPZXrjLWtXpsG6A3yLLPEoJ/IjU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/api v0.0.0-20250407143221-ac9807e6c755/go.mod h1:2R6XrVC8Oc08GlNh8ujEpc7HkLiEZ16QeY7FxIs20ac= google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a h1:OQ7sHVzkx6L57dQpzUS4ckfWJ51KDH74XHTDe23xWAs=
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a/go.mod h1:2R6XrVC8Oc08GlNh8ujEpc7HkLiEZ16QeY7FxIs20ac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0 h1:0K7wTWyzxZ7J+L47+LbFogJW1nn/gnnMCN0vGXNYtTI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250407143221-ac9807e6c755 h1:TwXJCGVREgQ/cl18iY0Z4wJCTL/GmW+Um2oSwZiZPnc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250407143221-ac9807e6c755/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a h1:GIqLhp/cYUkuGuiT+vJk8vhOP86L4+SP5j8yXgeVpvI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 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-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 v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@@ -12,21 +12,23 @@ import (
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client" "github.com/docker/docker/client"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents" "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared" "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
) )
type Agent struct { type Agent struct {
agents.Agent
// Model is the chat completion model to use // Model is the chat completion model to use
Model gollm.ChatCompletion Model gollm.ChatCompletion
OnLoopComplete func(ctx context.Context, knowledge agents.Knowledge) error OnLoopComplete func(ctx context.Context, knowledge shared.Knowledge) error
OnCommandStart func(ctx context.Context, command string) error OnCommandStart func(ctx context.Context, command string) error
OnCommandDone func(ctx context.Context, command string, output string, err error) error OnCommandDone func(ctx context.Context, command string, output string, err error) error
OnDone func(ctx context.Context, knowledge agents.Knowledge) error OnDone func(ctx context.Context, knowledge shared.Knowledge) error
ContextualInformation []string ContextualInformation []string
@@ -34,8 +36,9 @@ type Agent struct {
} }
type Response struct { type Response struct {
Knowledge agents.Knowledge Knowledge shared.Knowledge
Directory string DataDir string
OutputDir string
} }
// Answer will give the model access to an ubuntu console with python and pip installed, and then ask the model to // Answer will give the model access to an ubuntu console with python and pip installed, and then ask the model to
@@ -43,25 +46,36 @@ type Response struct {
func (a Agent) Answer(ctx context.Context, questions []string) (Response, error) { func (a Agent) Answer(ctx context.Context, questions []string) (Response, error) {
var res Response var res Response
a.Agent = agents.NewAgent(a.Model, gollm.NewToolBox()).WithMaxCalls(200)
if a.MaxCommands <= 0 { if a.MaxCommands <= 0 {
a.MaxCommands = 10000 a.MaxCommands = 20 // Default to 20 commands as per requirements
} }
res.Knowledge = agents.Knowledge{ res.Knowledge = shared.Knowledge{
OriginalQuestions: questions, OriginalQuestions: questions,
RemainingQuestions: questions, RemainingQuestions: questions,
} }
// create a temporary scratch directory // create temporary directories for data and output
dir, err := os.MkdirTemp("", "console-") dataDir, err := os.MkdirTemp("", "console-data-")
if err != nil { if err != nil {
return res, err return res, err
} }
res.Directory = dir outputDir, err := os.MkdirTemp("", "console-output-")
if err != nil {
os.RemoveAll(dataDir)
return res, err
}
res.DataDir = dataDir
res.OutputDir = outputDir
cl, err := client.NewClientWithOpts(client.FromEnv) cl, err := client.NewClientWithOpts(client.FromEnv)
if err != nil { if err != nil {
os.RemoveAll(dataDir)
os.RemoveAll(outputDir)
return res, err return res, err
} }
defer cl.Close() defer cl.Close()
@@ -69,8 +83,13 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
mounts := []mount.Mount{ mounts := []mount.Mount{
{ {
Type: mount.TypeBind, Type: mount.TypeBind,
Source: dir, Source: dataDir,
Target: "/home/user", Target: "/data",
},
{
Type: mount.TypeBind,
Source: outputDir,
Target: "/output",
}, },
} }
@@ -79,25 +98,29 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
Image: "ubuntu:latest", Image: "ubuntu:latest",
Cmd: []string{"tail", "-f", "/dev/null"}, Cmd: []string{"tail", "-f", "/dev/null"},
Tty: true, Tty: true,
WorkingDir: "/home/user", WorkingDir: "/data",
}, },
HostConfig: &container.HostConfig{ HostConfig: &container.HostConfig{
AutoRemove: true, AutoRemove: true,
Mounts: mounts, Mounts: mounts,
}, },
Name: filepath.Base(dir), Name: filepath.Base(dataDir),
}) })
if err != nil { if err != nil {
os.RemoveAll(dataDir)
os.RemoveAll(outputDir)
return res, fmt.Errorf("failed to create container: %w", err) return res, fmt.Errorf("failed to create container: %w", err)
} }
defer func() { defer func() {
_ = c.Close(ctx) _ = c.Close(ctx)
}() }()
slog.Info("starting container", "dir", dir, "container", fmt.Sprintf("%+v", c)) slog.Info("starting container", "dataDir", dataDir, "outputDir", outputDir, "container", fmt.Sprintf("%+v", c))
err = c.Start(ctx) err = c.Start(ctx)
if err != nil { if err != nil {
os.RemoveAll(dataDir)
os.RemoveAll(outputDir)
return res, fmt.Errorf("failed to start container: %w", err) return res, fmt.Errorf("failed to start container: %w", err)
} }
@@ -106,12 +129,17 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
var history executions var history executions
var keepGoing = true var keepGoing = true
opwd, epwd := c.Execute(ctx, "ls -al /home") // Initial setup - install basic tools
setupCmd, setupErr := c.Sudo(ctx, "apt-get update && apt-get install -y curl wget git python3 python3-pip")
if setupErr == nil {
history = append(history, execution{
Command: "sudo apt-get update && apt-get install -y curl wget git python3 python3-pip",
Output: setupCmd,
WhatILearned: []string{"Basic tools installed: curl, wget, git, python3, pip"},
})
}
fmt.Println(opwd) tools := map[string]gollm.Function{
slog.Info("pwd", "pwd", opwd, "epwd", epwd)
tools := map[string]*gollm.Function{
"exit": gollm.NewFunction( "exit": gollm.NewFunction(
"exit", "exit",
"exit the container", "exit the container",
@@ -122,18 +150,24 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
return "exiting", nil return "exiting", nil
}), }),
"write": gollm.NewFunction( "write_data": gollm.NewFunction(
"write", "write_data",
"write a file in the /root directory", "write a file in the /data directory",
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Filename string `description:"The name of the file to write"` Filename string `description:"The name of the file to write"`
Content string `description:"The content of the file to write"` Content string `description:"The content of the file to write"`
}) (any, error) { }) (any, error) {
target, err := SafeJoinPath(dir, args.Filename) target, err := SafeJoinPath(dataDir, args.Filename)
if err != nil { if err != nil {
return "", err return "", err
} }
// Ensure directory exists
dir := filepath.Dir(target)
if err := os.MkdirAll(dir, 0755); err != nil {
return "", err
}
f, err := os.Create(target) f, err := os.Create(target)
if err != nil { if err != nil {
return "", err return "", err
@@ -145,16 +179,67 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
return "", err return "", err
} }
return "wrote file", nil return "wrote file to /data/" + args.Filename, nil
}), }),
"read": gollm.NewFunction( "write_output": gollm.NewFunction(
"read", "write_output",
"read a file in the /root directory", "write a file in the /output directory (files here will be available after the agent completes)",
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(outputDir, args.Filename)
if err != nil {
return "", err
}
// Ensure directory exists
dir := filepath.Dir(target)
if err := os.MkdirAll(dir, 0755); 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 to /output/" + args.Filename, nil
}),
"read_data": gollm.NewFunction(
"read_data",
"read a file from the /data directory",
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Filename string `description:"The name of the file to read"` Filename string `description:"The name of the file to read"`
}) (any, error) { }) (any, error) {
target, err := SafeJoinPath(dir, args.Filename) target, err := SafeJoinPath(dataDir, args.Filename)
if err != nil {
return "", err
}
b, err := os.ReadFile(target)
if err != nil {
return "", err
}
return string(b), nil
}),
"read_output": gollm.NewFunction(
"read_output",
"read a file from the /output directory",
func(ctx *gollm.Context, args struct {
Filename string `description:"The name of the file to read"`
}) (any, error) {
target, err := SafeJoinPath(outputDir, args.Filename)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -172,9 +257,11 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
"execute a command in the container", "execute a command in the container",
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Command string `description:"The command to execute"` Command string `description:"The command to execute"`
Learn []string `description:"What you learned from this command (optional)"`
ToLearn []string `description:"What you still need to learn (optional)"`
}) (any, error) { }) (any, error) {
if len(history) >= a.MaxCommands { if len(history) >= a.MaxCommands {
return "too many commands", nil return "Command limit reached. You've used all " + fmt.Sprintf("%d", a.MaxCommands) + " allowed commands.", nil
} }
if a.OnCommandStart != nil { if a.OnCommandStart != nil {
@@ -202,69 +289,77 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
history = append(history, execution{ history = append(history, execution{
Command: args.Command, Command: args.Command,
Output: res, Output: res,
WhatILearned: args.Learn,
WhatIStillNeedToLearn: args.ToLearn,
}) })
return res, nil return res, nil
}), }),
"sudo": gollm.NewFunction( "summarize_knowledge": gollm.NewFunction(
"sudo", "summarize_knowledge",
"execute a command in the container", "summarize what you've learned so far",
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct{}) (any, error) {
Command string `description:"The command to execute"` var learned []string
}) (any, error) { var toLearn []string
if len(history) >= a.MaxCommands {
return "too many commands", nil for _, exec := range history {
learned = append(learned, exec.WhatILearned...)
toLearn = append(toLearn, exec.WhatIStillNeedToLearn...)
} }
if a.OnCommandStart != nil { summary := "Knowledge Summary:\n"
err := a.OnCommandStart(ctx, args.Command) if len(learned) > 0 {
if err != nil { summary += "What I've learned:\n- " + strings.Join(learned, "\n- ") + "\n\n"
return "", err } else {
} summary += "I haven't learned anything specific yet.\n\n"
} }
res, err := c.Sudo(ctx, args.Command) if len(toLearn) > 0 {
summary += "What I still need to learn:\n- " + strings.Join(toLearn, "\n- ")
if a.OnCommandDone != nil { } else {
err = a.OnCommandDone(ctx, args.Command, res, nil) summary += "I don't have any specific learning goals at the moment."
if err != nil {
return "", err
}
} }
if err != nil { return summary, 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++ { 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.` systemPrompt := `You are now in a shell in a container of the ubuntu:latest image to answer a question asked by the user.
You have full control over a bash shell inside this Docker container.
Important directories:
- /data: A temporary directory with the lifespan of your processing. Use this for working files.
- /output: Files placed here will be returned to the caller after you're done. Use this for final results.
You can execute up to ` + fmt.Sprintf("%d", a.MaxCommands) + ` commands total. You're currently on command ` + fmt.Sprintf("%d", len(history)+1) + ` of ` + fmt.Sprintf("%d", a.MaxCommands) + `.
For each command, you should:
1. Think about what you need to learn
2. Execute the command using the "execute" function
3. Analyze the output and record what you learned
4. Plan your next command based on this knowledge
You can write files directly to /data or /output using the write_data and write_output functions.
When you are done, use "exit" to exit the container.`
var toolbox []gollm.Function
// Add all tools
toolbox = append(toolbox,
tools["exit"],
tools["write_data"],
tools["write_output"],
tools["read_data"],
tools["read_output"],
tools["summarize_knowledge"],
)
// Only add execute if we haven't reached the command limit
if len(history) < a.MaxCommands { if len(history) < a.MaxCommands {
systemPrompt += `You can run any command you like to get to the needed results.` toolbox = append(toolbox, tools["execute"])
}
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{ kw := shared.KnowledgeWorker{
@@ -291,7 +386,7 @@ Respond with any number of commands to answer the question, they will be execute
slog.Info("answered question and learned nothing") slog.Info("answered question and learned nothing")
} }
res.Knowledge, err = agents.KnowledgeIntegrate(ctx, a.Model, res.Knowledge, r) res.Knowledge, err = a.KnowledgeIntegrate(ctx, res.Knowledge, r)
if err != nil { if err != nil {
return res, fmt.Errorf("error integrating knowledge: %w", err) return res, fmt.Errorf("error integrating knowledge: %w", err)
} }

View File

@@ -1,4 +1,4 @@
package console package agents
import ( import (
"context" "context"
@@ -8,46 +8,29 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents" "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/console"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared" "gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
) )
type Agent struct { type ConsoleConfig struct {
// Model is the chat completion model to use // MaxCommands is how many total commands this console can run.
Model gollm.ChatCompletion MaxCommands int `json:"max_commands"`
OnLoopComplete func(ctx context.Context, knowledge agents.Knowledge) error
OnCommandStart func(ctx context.Context, command string) error OnCommandStart func(ctx context.Context, command string) error
OnCommandDone func(ctx context.Context, command string, output string, err error) error OnCommandDone func(ctx context.Context, command string, output string, err error) error
OnDone func(ctx context.Context, knowledge agents.Knowledge) error
ContextualInformation []string
MaxCommands int
} }
type Response struct { func (a Agent) Console(ctx context.Context, questions []string, cfg ConsoleConfig) (shared.Knowledge, string, error) {
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 if cfg.MaxCommands <= 0 {
// do what is necessary to answer the question. cfg.MaxCommands = 10000
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{ var resKnowledge = shared.Knowledge{
OriginalQuestions: questions, OriginalQuestions: questions,
RemainingQuestions: questions, RemainingQuestions: questions,
} }
@@ -55,14 +38,14 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
// create a temporary scratch directory // create a temporary scratch directory
dir, err := os.MkdirTemp("", "console-") dir, err := os.MkdirTemp("", "console-")
if err != nil { if err != nil {
return res, err return resKnowledge, "", err
} }
res.Directory = dir var resDirectory = dir
cl, err := client.NewClientWithOpts(client.FromEnv) cl, err := client.NewClientWithOpts(client.FromEnv)
if err != nil { if err != nil {
return res, err return resKnowledge, resDirectory, err
} }
defer cl.Close() defer cl.Close()
@@ -74,7 +57,7 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
}, },
} }
c, err := CreateContainer(ctx, cl, ContainerConfig{ c, err := console.CreateContainer(ctx, cl, console.ContainerConfig{
Config: &container.Config{ Config: &container.Config{
Image: "ubuntu:latest", Image: "ubuntu:latest",
Cmd: []string{"tail", "-f", "/dev/null"}, Cmd: []string{"tail", "-f", "/dev/null"},
@@ -89,7 +72,7 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
}) })
if err != nil { if err != nil {
return res, fmt.Errorf("failed to create container: %w", err) return resKnowledge, resDirectory, fmt.Errorf("failed to create container: %w", err)
} }
defer func() { defer func() {
_ = c.Close(ctx) _ = c.Close(ctx)
@@ -98,12 +81,12 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
slog.Info("starting container", "dir", dir, "container", fmt.Sprintf("%+v", c)) slog.Info("starting container", "dir", dir, "container", fmt.Sprintf("%+v", c))
err = c.Start(ctx) err = c.Start(ctx)
if err != nil { if err != nil {
return res, fmt.Errorf("failed to start container: %w", err) return resKnowledge, resDirectory, fmt.Errorf("failed to start container: %w", err)
} }
// Run the model // Run the model
var history executions var history console.Executions
var keepGoing = true var keepGoing = true
opwd, epwd := c.Execute(ctx, "ls -al /home") opwd, epwd := c.Execute(ctx, "ls -al /home")
@@ -111,7 +94,7 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
fmt.Println(opwd) fmt.Println(opwd)
slog.Info("pwd", "pwd", opwd, "epwd", epwd) slog.Info("pwd", "pwd", opwd, "epwd", epwd)
tools := map[string]*gollm.Function{ tools := map[string]gollm.Function{
"exit": gollm.NewFunction( "exit": gollm.NewFunction(
"exit", "exit",
"exit the container", "exit the container",
@@ -129,7 +112,7 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
Filename string `description:"The name of the file to write"` Filename string `description:"The name of the file to write"`
Content string `description:"The content of the file to write"` Content string `description:"The content of the file to write"`
}) (any, error) { }) (any, error) {
target, err := SafeJoinPath(dir, args.Filename) target, err := console.SafeJoinPath(dir, args.Filename)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -154,7 +137,7 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Filename string `description:"The name of the file to read"` Filename string `description:"The name of the file to read"`
}) (any, error) { }) (any, error) {
target, err := SafeJoinPath(dir, args.Filename) target, err := console.SafeJoinPath(dir, args.Filename)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -173,12 +156,12 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Command string `description:"The command to execute"` Command string `description:"The command to execute"`
}) (any, error) { }) (any, error) {
if len(history) >= a.MaxCommands { if len(history) >= cfg.MaxCommands {
return "too many commands", nil return "too many commands", nil
} }
if a.OnCommandStart != nil { if cfg.OnCommandStart != nil {
err := a.OnCommandStart(ctx, args.Command) err := cfg.OnCommandStart(ctx, args.Command)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -192,14 +175,14 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
res, err = c.Execute(ctx, args.Command) res, err = c.Execute(ctx, args.Command)
} }
if a.OnCommandDone != nil { if cfg.OnCommandDone != nil {
err = a.OnCommandDone(ctx, args.Command, res, nil) err = cfg.OnCommandDone(ctx, args.Command, res, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
} }
history = append(history, execution{ history = append(history, console.Execution{
Command: args.Command, Command: args.Command,
Output: res, Output: res,
}) })
@@ -213,12 +196,12 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Command string `description:"The command to execute"` Command string `description:"The command to execute"`
}) (any, error) { }) (any, error) {
if len(history) >= a.MaxCommands { if len(history) >= cfg.MaxCommands {
return "too many commands", nil return "too many commands", nil
} }
if a.OnCommandStart != nil { if cfg.OnCommandStart != nil {
err := a.OnCommandStart(ctx, args.Command) err := cfg.OnCommandStart(ctx, args.Command)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -226,8 +209,8 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
res, err := c.Sudo(ctx, args.Command) res, err := c.Sudo(ctx, args.Command)
if a.OnCommandDone != nil { if cfg.OnCommandDone != nil {
err = a.OnCommandDone(ctx, args.Command, res, nil) err = cfg.OnCommandDone(ctx, args.Command, res, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -237,7 +220,7 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
res = "error executing: " + err.Error() res = "error executing: " + err.Error()
} }
history = append(history, execution{ history = append(history, console.Execution{
Command: "sudo " + args.Command, Command: "sudo " + args.Command,
Output: res, Output: res,
}) })
@@ -246,11 +229,11 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
}), }),
} }
for i := 0; i < a.MaxCommands && len(history) < a.MaxCommands && keepGoing; i++ { for i := 0; i < cfg.MaxCommands && len(history) < cfg.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.` 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 { if len(history) < cfg.MaxCommands {
systemPrompt += `You can run any command you like to get to the needed results.` systemPrompt += `You can run any command you like to get to the needed results.`
} }
@@ -258,19 +241,19 @@ func (a Agent) Answer(ctx context.Context, questions []string) (Response, error)
When you are done, please use "exit" to exit the container. 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.` Respond with any number of commands to answer the question, they will be executed in order.`
var toolbox []*gollm.Function var toolbox []gollm.Function
// add unrestricted tools // add unrestricted tools
toolbox = append(toolbox, tools["exit"], tools["write"], tools["read"]) toolbox = append(toolbox, tools["exit"], tools["write"], tools["read"])
if len(history) < a.MaxCommands { if len(history) < cfg.MaxCommands {
toolbox = append(toolbox, tools["execute"], tools["sudo"]) toolbox = append(toolbox, tools["execute"], tools["sudo"])
} }
kw := shared.KnowledgeWorker{ kw := shared.KnowledgeWorker{
Model: a.Model, Model: a.model,
ToolBox: gollm.NewToolBox(toolbox...), ToolBox: gollm.NewToolBox(toolbox...),
ContextualInformation: a.ContextualInformation, ContextualInformation: a.contextualInformation,
OnNewFunction: func(ctx context.Context, funcName string, args string) (any, error) { OnNewFunction: func(ctx context.Context, funcName string, args string) (any, error) {
slog.Info("new function called", "function name", funcName, "args", args) slog.Info("new function called", "function name", funcName, "args", args)
return nil, nil return nil, nil
@@ -278,11 +261,11 @@ Respond with any number of commands to answer the question, they will be execute
} }
slog.Info("answering question", "question", questions[0]) slog.Info("answering question", "question", questions[0])
r, err := kw.Answer(ctx, &res.Knowledge, systemPrompt, "", "", history.ToGeneralButLastMessageHistory(), func(res gollm.ToolCallResponse) { r, err := kw.Answer(ctx, &resKnowledge, systemPrompt, "", "", history.ToGeneralButLastMessageHistory(), func(res gollm.ToolCallResponse) {
}) })
if err != nil { if err != nil {
return res, fmt.Errorf("error answering question: %w", err) return resKnowledge, resDirectory, fmt.Errorf("error answering question: %w", err)
} }
if len(r.Knowledge) > 0 { if len(r.Knowledge) > 0 {
@@ -291,12 +274,12 @@ Respond with any number of commands to answer the question, they will be execute
slog.Info("answered question and learned nothing") slog.Info("answered question and learned nothing")
} }
res.Knowledge, err = agents.KnowledgeIntegrate(ctx, a.Model, res.Knowledge, r) resKnowledge, err = a.KnowledgeIntegrate(ctx, resKnowledge, r)
if err != nil { if err != nil {
return res, fmt.Errorf("error integrating knowledge: %w", err) return resKnowledge, resDirectory, fmt.Errorf("error integrating knowledge: %w", err)
} }
slog.Info("knowledge integrated", "question", questions[0], "knowledge", res.Knowledge) slog.Info("knowledge integrated", "question", questions[0], "knowledge", resKnowledge)
} }
return res, nil return resKnowledge, resDirectory, nil
} }

View File

@@ -6,7 +6,7 @@ import (
gollm "gitea.stevedudenhoeffer.com/steve/go-llm" gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
) )
type execution struct { type Execution struct {
Command string Command string
Output string Output string
WhatILearned []string WhatILearned []string
@@ -16,7 +16,7 @@ type execution struct {
const kMaxLenCommandSummary = 200 const kMaxLenCommandSummary = 200
const kMaxLenCommandOutputSummary = 200 const kMaxLenCommandOutputSummary = 200
func (e execution) ToGeneralMessageHistory() gollm.Message { func (e Execution) ToGeneralMessageHistory() gollm.Message {
if len(e.Command) > kMaxLenCommandSummary { if len(e.Command) > kMaxLenCommandSummary {
e.Command = e.Command[:kMaxLenCommandSummary] + "... (truncated)" e.Command = e.Command[:kMaxLenCommandSummary] + "... (truncated)"
} }
@@ -33,7 +33,7 @@ func (e execution) ToGeneralMessageHistory() gollm.Message {
} }
} }
func (e execution) ToDetailedMessageHistory() gollm.Message { func (e Execution) ToDetailedMessageHistory() gollm.Message {
prompt := "$ " prompt := "$ "
if strings.HasPrefix(e.Command, "sudo ") { if strings.HasPrefix(e.Command, "sudo ") {
prompt = "# " prompt = "# "
@@ -60,9 +60,9 @@ func (e execution) ToDetailedMessageHistory() gollm.Message {
} }
} }
type executions []execution type Executions []Execution
func (e executions) ToGeneralMessageHistory() []gollm.Message { func (e Executions) ToGeneralMessageHistory() []gollm.Message {
var messages []gollm.Message var messages []gollm.Message
for _, v := range e { for _, v := range e {
@@ -72,7 +72,7 @@ func (e executions) ToGeneralMessageHistory() []gollm.Message {
return messages return messages
} }
func (e executions) ToGeneralButLastMessageHistory() []gollm.Message { func (e Executions) ToGeneralButLastMessageHistory() []gollm.Message {
var messages []gollm.Message var messages []gollm.Message
for i, v := range e { for i, v := range e {
@@ -86,7 +86,7 @@ func (e executions) ToGeneralButLastMessageHistory() []gollm.Message {
return messages return messages
} }
func (e executions) ToDetailedMessageHistory() []gollm.Message { func (e Executions) ToDetailedMessageHistory() []gollm.Message {
var messages []gollm.Message var messages []gollm.Message
for _, v := range e { for _, v := range e {

View File

@@ -5,6 +5,8 @@ import (
"strings" "strings"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm" gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
) )
// ExtractKnowledge will take a knowledge object and use the gained knowledge to extract the knowledge relevant to the // ExtractKnowledge will take a knowledge object and use the gained knowledge to extract the knowledge relevant to the
@@ -16,16 +18,16 @@ import (
// contextualInformation is any contextual information that should be provided to the model. // contextualInformation is any contextual information that should be provided to the model.
// It will return the knowledge extracted from the sourceData along with any remaining questions. // It will return the knowledge extracted from the sourceData along with any remaining questions.
// This agent call will not use the Agent's system prompts, but will instead form its own. The contextual information will be used. // This agent call will not use the Agent's system prompts, but will instead form its own. The contextual information will be used.
func (a Agent) ExtractKnowledge(ctx context.Context, sourceData string, source string, questions []string) (Knowledge, error) { func (a Agent) ExtractKnowledge(ctx context.Context, sourceData string, source string, questions []string) (shared.Knowledge, error) {
var knowledge Knowledge var knowledge shared.Knowledge
fnAnswer := gollm.NewFunction( fnAnswer := gollm.NewFunction(
"learn", "learn",
`Use learn to pass some relevant information to the model. The model will use this information to answer the question. Use it to learn relevant information from the text. Keep these concise and relevant to the question.`, `Use learn to pass some relevant information to the model. The model will use this information to answer the question. Use it to learn relevant information from the text. Keep these concise and relevant to the question.`,
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Info string `description:"The information to learn from the text."` Info string `description:"The information to learn from the text."`
}) (any, error) { }) (any, error) {
knowledge.Knowledge = append(knowledge.Knowledge, TidBit{Info: args.Info, Source: source}) knowledge.Knowledge = append(knowledge.Knowledge, shared.TidBit{Info: args.Info, Source: source})
return "", nil return "", nil
}) })

View File

@@ -4,12 +4,14 @@ import (
"context" "context"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm" gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
) )
// KnowledgeIntegrate will ask the LLM to combine the gained knowledge with the current knowledge, and return the new representation of overall. // KnowledgeIntegrate will ask the LLM to combine the gained knowledge with the current knowledge, and return the new representation of overall.
// If source is not empty, then any new Knowledge will an empty source will be given the source. // If source is not empty, then any new Knowledge will an empty source will be given the source.
// This will override objectives, notes, and remaining questions. // This will override objectives, notes, and remaining questions.
func (a Agent) KnowledgeIntegrate(ctx context.Context, base Knowledge, in ...Knowledge) (Knowledge, error) { func (a Agent) KnowledgeIntegrate(ctx context.Context, base shared.Knowledge, in ...shared.Knowledge) (shared.Knowledge, error) {
// if there are no changes we can just return the knowledge // if there are no changes we can just return the knowledge
if len(in) == 0 { if len(in) == 0 {
return base, nil return base, nil
@@ -29,7 +31,7 @@ func (a Agent) KnowledgeIntegrate(ctx context.Context, base Knowledge, in ...Kno
} }
} }
var incoming Knowledge var incoming shared.Knowledge
for _, k := range in { for _, k := range in {
incoming.NotesToSelf = append(incoming.NotesToSelf, k.NotesToSelf...) incoming.NotesToSelf = append(incoming.NotesToSelf, k.NotesToSelf...)
@@ -43,7 +45,7 @@ func (a Agent) KnowledgeIntegrate(ctx context.Context, base Knowledge, in ...Kno
baseMsg.Text = "The original knowledge is as follows: " + baseMsg.Text baseMsg.Text = "The original knowledge is as follows: " + baseMsg.Text
incomingMsg.Text = "The new knowledge is as follows: " + incomingMsg.Text incomingMsg.Text = "The new knowledge is as follows: " + incomingMsg.Text
var result = Knowledge{ var result = shared.Knowledge{
OriginalQuestions: base.OriginalQuestions, OriginalQuestions: base.OriginalQuestions,
Knowledge: append(base.Knowledge, incoming.Knowledge...), Knowledge: append(base.Knowledge, incoming.Knowledge...),
} }

View File

@@ -8,10 +8,12 @@ import (
"strings" "strings"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm" gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
) )
// AnswerQuestionWithKnowledge will take a knowledge object and use the gained knowledge to answer a question. // AnswerQuestionWithKnowledge will take a knowledge object and use the gained knowledge to answer a question.
func (a Agent) AnswerQuestionWithKnowledge(ctx context.Context, knowledge Knowledge) (string, error) { func (a Agent) AnswerQuestionWithKnowledge(ctx context.Context, knowledge shared.Knowledge) (string, error) {
originalQuestions := strings.Join(knowledge.OriginalQuestions, "\n") originalQuestions := strings.Join(knowledge.OriginalQuestions, "\n")
infoGained := "" infoGained := ""

View File

@@ -3,19 +3,21 @@ package agents
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/cache" "gitea.stevedudenhoeffer.com/steve/answer/pkg/cache"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor" "gitea.stevedudenhoeffer.com/steve/answer/pkg/extractor"
"net/url"
) )
func (a Agent) ReadPage(ctx context.Context, u *url.URL, questions []string) (Knowledge, error) { func (a Agent) ReadPage(ctx context.Context, u *url.URL, questions []string) (shared.Knowledge, error) {
ar, err := extractArticle(ctx, u) ar, err := extractArticle(ctx, u)
if err != nil { if err != nil {
return Knowledge{}, err return shared.Knowledge{}, err
} }
if ar.Body == "" { if ar.Body == "" {
return Knowledge{}, fmt.Errorf("could not extract body from page") return shared.Knowledge{}, fmt.Errorf("could not extract body from page")
} }
return a.ExtractKnowledge(ctx, ar.Body, u.String(), questions) return a.ExtractKnowledge(ctx, ar.Body, u.String(), questions)

View File

@@ -14,6 +14,8 @@ import (
"gitea.stevedudenhoeffer.com/steve/go-extractor" "gitea.stevedudenhoeffer.com/steve/go-extractor"
"gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo" "gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm" gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
) )
func deferClose(c io.Closer) { func deferClose(c io.Closer) {
@@ -26,7 +28,7 @@ func deferClose(c io.Closer) {
type SearchTool struct { type SearchTool struct {
Name string Name string
Description string Description string
Function func(ctx context.Context, src *url.URL, questions []string) (Knowledge, error) Function func(ctx context.Context, src *url.URL, questions []string) (shared.Knowledge, error)
} }
// SearchAndUseTools will search duckduckgo for the given question, and then ask the LLM to select a search result to // SearchAndUseTools will search duckduckgo for the given question, and then ask the LLM to select a search result to
@@ -41,8 +43,8 @@ type SearchTool struct {
// will be combined and returned. // will be combined and returned.
// messages will be appended to all search results. The types of messages that can be appended are both string and // messages will be appended to all search results. The types of messages that can be appended are both string and
// gollm.Message. // gollm.Message.
func (a Agent) SearchAndUseTools(ctx context.Context, searchQuery string, questions []string, loops int, allowConcurrent bool, maxReads int, tools []SearchTool, messages ...any) (Knowledge, error) { func (a Agent) SearchAndUseTools(ctx context.Context, searchQuery string, questions []string, loops int, allowConcurrent bool, maxReads int, tools []SearchTool, messages ...any) (shared.Knowledge, error) {
var knowledge = Knowledge{ var knowledge = shared.Knowledge{
OriginalQuestions: questions, OriginalQuestions: questions,
RemainingQuestions: questions, RemainingQuestions: questions,
} }
@@ -184,13 +186,13 @@ Use appropriate tools to analyze the search results and determine if they answer
} }
slog.Info("search results called and executed", "error", err, "results text", results.Text, "results", results.CallResults) slog.Info("search results called and executed", "error", err, "results text", results.Text, "results", results.CallResults)
var learned []Knowledge var learned []shared.Knowledge
for _, r := range results.CallResults { for _, r := range results.CallResults {
if r.Error != nil { if r.Error != nil {
continue continue
} }
if k, ok := r.Result.(Knowledge); ok { if k, ok := r.Result.(shared.Knowledge); ok {
learned = append(learned, k) learned = append(learned, k)
} else { } else {
slog.Error("result is not knowledge", "result", r.Result) slog.Error("result is not knowledge", "result", r.Result)
@@ -210,7 +212,7 @@ Use appropriate tools to analyze the search results and determine if they answer
return knowledge, nil return knowledge, nil
} }
func (a Agent) SearchAndRead(ctx context.Context, searchQuery string, questions []string, allowConcurrent bool, maxReads int) (Knowledge, error) { func (a Agent) SearchAndRead(ctx context.Context, searchQuery string, questions []string, allowConcurrent bool, maxReads int) (shared.Knowledge, error) {
return a.SearchAndUseTools(ctx, searchQuery, questions, 2, allowConcurrent, maxReads, []SearchTool{ return a.SearchAndUseTools(ctx, searchQuery, questions, 2, allowConcurrent, maxReads, []SearchTool{
{ {
Name: "readpage", Name: "readpage",

View File

@@ -1,4 +1,4 @@
package agents package shared
import ( import (
"strings" "strings"

View File

@@ -5,14 +5,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents"
gollm "gitea.stevedudenhoeffer.com/steve/go-llm" gollm "gitea.stevedudenhoeffer.com/steve/go-llm"
) )
type KnowledgeWorker struct { type KnowledgeWorker struct {
Model gollm.ChatCompletion Model gollm.ChatCompletion
ToolBox *gollm.ToolBox ToolBox gollm.ToolBox
ContextualInformation []string ContextualInformation []string
OnNewFunction func(ctx context.Context, funcName string, args string) (any, error) 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 OnFunctionFinished func(ctx context.Context, funcName string, args string, result any, err error, newFunctionResult any) error
@@ -27,7 +25,7 @@ const DefaultPrompt = `Use the provided tools to answer the questions in your cu
// source is the source of the knowledge, for example a URL. // source is the source of the knowledge, for example a URL.
// Any tool call that returns a Knowledge object will be handled by this function in crafting the final Knowledge object. // Any tool call that returns a Knowledge object will be handled by this function in crafting the final Knowledge object.
// Any other return type will be passed to the resultWorker function, if provided. // Any other return type will be passed to the resultWorker function, if provided.
func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowledge, systemPrompt string, userInput string, source string, history []gollm.Message, resultWorker func(res gollm.ToolCallResponse)) (agents.Knowledge, error) { func (w KnowledgeWorker) Answer(context context.Context, knowledge *Knowledge, systemPrompt string, userInput string, source string, history []gollm.Message, resultWorker func(res gollm.ToolCallResponse)) (Knowledge, error) {
var req gollm.Request var req gollm.Request
if systemPrompt != "" { if systemPrompt != "" {
@@ -80,7 +78,7 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
NotesToSelf []string `description:"Notes to leave for yourself for later."` NotesToSelf []string `description:"Notes to leave for yourself for later."`
}) (any, error) { }) (any, error) {
return agents.Knowledge{ return Knowledge{
NotesToSelf: args.NotesToSelf, NotesToSelf: args.NotesToSelf,
}, nil }, nil
}), }),
@@ -90,7 +88,7 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Objectives []string `description:"The objectives to set for executions going forward."` Objectives []string `description:"The objectives to set for executions going forward."`
}) (any, error) { }) (any, error) {
return agents.Knowledge{ return Knowledge{
CurrentObjectives: args.Objectives, CurrentObjectives: args.Objectives,
}, nil }, nil
}), }),
@@ -100,13 +98,13 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
func(ctx *gollm.Context, args struct { func(ctx *gollm.Context, args struct {
Info []string `description:"The information to learn from the input."` Info []string `description:"The information to learn from the input."`
}) (any, error) { }) (any, error) {
var k []agents.TidBit var k []TidBit
for _, i := range args.Info { for _, i := range args.Info {
k = append(k, agents.TidBit{Info: i, Source: source}) k = append(k, TidBit{Info: i, Source: source})
} }
return agents.Knowledge{ return Knowledge{
Knowledge: k, Knowledge: k,
}, nil }, nil
})). })).
@@ -120,17 +118,17 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
resp, err := w.Model.ChatComplete(context, req) resp, err := w.Model.ChatComplete(context, req)
if err != nil { if err != nil {
return agents.Knowledge{}, fmt.Errorf("error calling model: %w", err) return Knowledge{}, fmt.Errorf("error calling model: %w", err)
} }
if len(resp.Choices) == 0 { if len(resp.Choices) == 0 {
return agents.Knowledge{}, fmt.Errorf("no choices found") return Knowledge{}, fmt.Errorf("no choices found")
} }
choice := resp.Choices[0] choice := resp.Choices[0]
if len(choice.Calls) == 0 { if len(choice.Calls) == 0 {
return agents.Knowledge{}, fmt.Errorf("no calls found") return Knowledge{}, fmt.Errorf("no calls found")
} }
var callNames []string var callNames []string
@@ -141,14 +139,14 @@ func (w KnowledgeWorker) Answer(context context.Context, knowledge *agents.Knowl
results, err := w.ToolBox.ExecuteCallbacks(gollm.NewContext(context, req, &choice, nil), choice.Calls, w.OnNewFunction, w.OnFunctionFinished) results, err := w.ToolBox.ExecuteCallbacks(gollm.NewContext(context, req, &choice, nil), choice.Calls, w.OnNewFunction, w.OnFunctionFinished)
if err != nil { if err != nil {
return agents.Knowledge{}, fmt.Errorf("error executing callbacks: %w", err) return Knowledge{}, fmt.Errorf("error executing callbacks: %w", err)
} }
var res = agents.Knowledge{} var res = Knowledge{}
for _, r := range results { for _, r := range results {
switch v := r.Result.(type) { switch v := r.Result.(type) {
case agents.Knowledge: case Knowledge:
res = res.Absorb(v) res = res.Absorb(v)
default: default:

View File

@@ -3,25 +3,28 @@ package agents
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/asticode/go-astisub"
"github.com/lrstanley/go-ytdlp"
"io" "io"
"log/slog" "log/slog"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"github.com/asticode/go-astisub"
"github.com/lrstanley/go-ytdlp"
"gitea.stevedudenhoeffer.com/steve/answer/pkg/agents/shared"
) )
func init() { func init() {
ytdlp.MustInstall(context.Background(), nil) ytdlp.MustInstall(context.Background(), nil)
} }
func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions []string) (Knowledge, error) { func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions []string) (shared.Knowledge, error) {
dlp := ytdlp.New() dlp := ytdlp.New()
tmpDir, err := os.MkdirTemp("", "mort-ytdlp-") tmpDir, err := os.MkdirTemp("", "mort-ytdlp-")
if err != nil { if err != nil {
return Knowledge{}, fmt.Errorf("error creating temp dir: %w", err) return shared.Knowledge{}, fmt.Errorf("error creating temp dir: %w", err)
} }
slog.Info("created temp dir", "path", tmpDir) slog.Info("created temp dir", "path", tmpDir)
@@ -40,15 +43,15 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
res, err := dlp.Run(ctx, u.String()) res, err := dlp.Run(ctx, u.String())
if err != nil { if err != nil {
return Knowledge{}, fmt.Errorf("error running yt-dlp: %w", err) return shared.Knowledge{}, fmt.Errorf("error running yt-dlp: %w", err)
} }
if res == nil { if res == nil {
return Knowledge{}, fmt.Errorf("yt-dlp returned nil") return shared.Knowledge{}, fmt.Errorf("yt-dlp returned nil")
} }
if res.ExitCode != 0 { if res.ExitCode != 0 {
return Knowledge{}, fmt.Errorf("yt-dlp exited with code %d", res.ExitCode) return shared.Knowledge{}, fmt.Errorf("yt-dlp exited with code %d", res.ExitCode)
} }
// the transcript for this video now _should_ be at tmpDir/subs.en.vtt, however if it's not then just fine any // the transcript for this video now _should_ be at tmpDir/subs.en.vtt, however if it's not then just fine any
@@ -60,7 +63,7 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
vttFile = "" vttFile = ""
files, err := os.ReadDir(tmpDir) files, err := os.ReadDir(tmpDir)
if err != nil { if err != nil {
return Knowledge{}, fmt.Errorf("error reading directory: %w", err) return shared.Knowledge{}, fmt.Errorf("error reading directory: %w", err)
} }
for _, file := range files { for _, file := range files {
@@ -72,7 +75,7 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
} }
if vttFile == "" { if vttFile == "" {
return Knowledge{}, fmt.Errorf("no vtt file found") return shared.Knowledge{}, fmt.Errorf("no vtt file found")
} }
fp, err := os.Open(vttFile) fp, err := os.Open(vttFile)
@@ -83,16 +86,16 @@ func (a Agent) ReadYouTubeTranscript(ctx context.Context, u *url.URL, questions
} }
}(fp) }(fp)
if err != nil { if err != nil {
return Knowledge{}, fmt.Errorf("error opening vtt file: %w", err) return shared.Knowledge{}, fmt.Errorf("error opening vtt file: %w", err)
} }
subs, err := astisub.ReadFromWebVTT(fp) subs, err := astisub.ReadFromWebVTT(fp)
if err != nil { if err != nil {
return Knowledge{}, fmt.Errorf("error reading vtt file: %w", err) return shared.Knowledge{}, fmt.Errorf("error reading vtt file: %w", err)
} }
if len(subs.Items) == 0 { if len(subs.Items) == 0 {
return Knowledge{}, fmt.Errorf("no subtitles found") return shared.Knowledge{}, fmt.Errorf("no subtitles found")
} }
var ts string var ts string