├── .env.example ├── .github └── workflows │ └── go.yml ├── .gitignore ├── README.md ├── go.mod ├── go.sum ├── init.go ├── llm ├── llm.go └── openai │ └── chatgpt.go ├── plugin.go ├── plugin_manager.go ├── plugin_manager_test.go └── plugins ├── agicn_search ├── agicn_search.go └── agicn_search_test.go ├── calculator └── calculator.go ├── google ├── .env.example ├── google.go ├── google_test.go └── option.go ├── stablediffusion ├── stablediffusion.go ├── stablediffusion_test.go └── test1.jpg ├── weather └── weather.go └── wikipedia.go └── wikipedia.go /.env.example: -------------------------------------------------------------------------------- 1 | # OpenAI Token 2 | 3 | OPENAI_TOKEN=xxx 4 | # OPENAI_MODEL default is gpt 3.5 5 | # OPENAI_MODEL = "gpt-4" 6 | 7 | # --- FOR Plugins --- 8 | 9 | ## Google Search 10 | GOOGLE_ENGINE_ID = "" 11 | GOOGLE_TOKEN = "" 12 | 13 | # Stable Diffusion 14 | SD_ADDR = "127.0.0.1:19000" -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Check out code into the Go module directory 15 | uses: actions/checkout@v3 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v4 19 | with: 20 | go-version: 1.20.3 21 | 22 | - name: Go version 23 | run: go version 24 | 25 | - name: Get dependencies 26 | run: go mod download 27 | 28 | - name: Run tests 29 | run: go test -v ./... 30 | env: 31 | OPENAI_TOKEN: '${{ secrets.OPENAI_TOKEN }}' 32 | 33 | - name: Notify reviewer if tests pass 34 | if: ${{ success() }} 35 | run: echo "Tests passed. Notify the reviewer." 36 | # 此处可以添加通知 reviewer 的具体操作,例如发送邮件、发送 Slack 消息等。 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | .env 24 | .vscode 25 | .idea 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLM Plugin 2 | 3 | LLM Plugin system. 4 | 5 | ## 1. Plugins 6 | 7 | ### 2.1 Google Search 8 | 9 | Get Google Search token: [https://docs.chatkit.app/tools/google-search.html](https://docs.chatkit.app/tools/google-search.html) 10 | 11 | ### 2.2 Stable Diffusion 12 | 13 | Generate photo by stable diffusion plugin, like: 14 | 15 | ![girl](./plugins/stablediffusion/test1.jpg) 16 | 17 | ## 2. TESTING 18 | 19 | 1. OpenAI: 20 | ```bash 21 | cp .env.example .env 22 | ``` 23 | 24 | 2. Google: 25 | ```bash 26 | cd plugins/google 27 | 28 | cp .env.example .env 29 | ``` 30 | 31 | 32 | Run test: 33 | 34 | ```bash 35 | go test -v ./... 36 | ``` 37 | 38 | 39 | ## 3. RELEASE 40 | 41 | ### v0.1.0 42 | 43 | 1. init project. 44 | 2. support plugin: Google for search, calculator for mathematical calculations. 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/agi-cn/llmplugin 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/joho/godotenv v1.5.1 7 | github.com/mnogu/go-calculator v0.0.1 8 | github.com/pkg/errors v0.9.1 9 | github.com/sashabaranov/go-openai v1.9.0 10 | github.com/sirupsen/logrus v1.9.0 11 | github.com/stretchr/testify v1.8.4 12 | google.golang.org/api v0.120.0 13 | ) 14 | 15 | require ( 16 | cloud.google.com/go/compute v1.19.1 // indirect 17 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 20 | github.com/golang/protobuf v1.5.3 // indirect 21 | github.com/google/s2a-go v0.1.2 // indirect 22 | github.com/google/uuid v1.3.0 // indirect 23 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 24 | github.com/googleapis/gax-go/v2 v2.8.0 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | go.opencensus.io v0.24.0 // indirect 27 | golang.org/x/crypto v0.8.0 // indirect 28 | golang.org/x/net v0.9.0 // indirect 29 | golang.org/x/oauth2 v0.7.0 // indirect 30 | golang.org/x/sys v0.7.0 // indirect 31 | golang.org/x/text v0.9.0 // indirect 32 | google.golang.org/appengine v1.6.7 // indirect 33 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 34 | google.golang.org/grpc v1.54.0 // indirect 35 | google.golang.org/protobuf v1.30.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= 4 | cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= 5 | cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= 6 | cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 7 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 8 | cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= 9 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 10 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 11 | github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= 12 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 13 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 14 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 15 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 16 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 17 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 18 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 19 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 20 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 25 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 26 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 27 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 28 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 29 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 30 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 31 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 32 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 33 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 34 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 35 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 36 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 37 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 38 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 39 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 40 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 41 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 42 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 43 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 44 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 45 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 46 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 47 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 48 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 49 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 50 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 51 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 52 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 53 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 54 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 55 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 57 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 58 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 59 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 60 | github.com/google/s2a-go v0.1.2 h1:WVtYAYuYxKeYajAmThMRYWP6K3wXkcqbGHeUgeubUHY= 61 | github.com/google/s2a-go v0.1.2/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= 62 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 63 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 64 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 65 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= 66 | github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= 67 | github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= 68 | github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= 69 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 70 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 71 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 72 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 73 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 74 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 75 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 76 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 77 | github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 78 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 79 | github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= 80 | github.com/mnogu/go-calculator v0.0.1 h1:paStmrI700koHPtMGNQoAlbQNV1OWjwmSTzeAGq/t+k= 81 | github.com/mnogu/go-calculator v0.0.1/go.mod h1:ZZaGIAlygAGgwJq2XSwV/uTrnlmEvvAIM5g4kQK/dqk= 82 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 83 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 84 | github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= 85 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 86 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 87 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 88 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 89 | github.com/sashabaranov/go-openai v1.9.0 h1:NoiO++IISxxJ1pRc0n7uZvMGMake0G+FJ1XPwXtprsA= 90 | github.com/sashabaranov/go-openai v1.9.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= 91 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 92 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 93 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 94 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 95 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 96 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 97 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 98 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 99 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 100 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 101 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 102 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 103 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 104 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 105 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 106 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 107 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 108 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 109 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 110 | golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= 111 | golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= 112 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 113 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 114 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 115 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 116 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 117 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 118 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 119 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 120 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 121 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 122 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 123 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 124 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 125 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 126 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 127 | golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= 128 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 129 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 130 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 131 | golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= 132 | golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 133 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 134 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 135 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 136 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 139 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 140 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 141 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 142 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 143 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 144 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 146 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 151 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 152 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 155 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 156 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 157 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= 158 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 159 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 160 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 161 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 162 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 163 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 164 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 165 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 166 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 167 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 168 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 169 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 170 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 171 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 172 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 173 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 174 | google.golang.org/api v0.120.0 h1:TTmhTei0mkR+kiBSW2UzZmAbkTaBfUUzfchyXnzG9Hs= 175 | google.golang.org/api v0.120.0/go.mod h1:CrSvlNEFCFLae9ZUtL1z+61+rEBD7J/aCYwVYKZoWFU= 176 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 177 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 178 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 179 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 180 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 181 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 182 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 183 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 184 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= 185 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= 186 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 187 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 188 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 189 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 190 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 191 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 192 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 193 | google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 194 | google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= 195 | google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= 196 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 197 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 198 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 199 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 200 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 201 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 202 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 203 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 204 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 205 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 206 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 207 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 208 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 209 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 210 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 211 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 212 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 213 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 214 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 215 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 216 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 217 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 218 | -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package llmplugin 2 | 3 | import "github.com/sirupsen/logrus" 4 | 5 | func init() { 6 | 7 | logrus.SetLevel(logrus.DebugLevel) 8 | } 9 | -------------------------------------------------------------------------------- /llm/llm.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sashabaranov/go-openai" 7 | ) 8 | 9 | type Role string 10 | 11 | const ( 12 | RoleUser Role = openai.ChatMessageRoleUser 13 | RoleAssistant Role = openai.ChatMessageRoleAssistant 14 | RoleSystem Role = openai.ChatMessageRoleSystem 15 | ) 16 | 17 | func (r Role) String() string { 18 | return string(r) 19 | } 20 | 21 | type LlmMessage struct { 22 | Role Role 23 | Content string 24 | } 25 | 26 | type LlmAnswer struct { 27 | Role string 28 | Content string 29 | } 30 | 31 | type LLMer interface { 32 | Chat(ctx context.Context, messages []LlmMessage) (*LlmAnswer, error) 33 | } 34 | 35 | type Summarizer interface { 36 | Summary(ctx context.Context, content string) (string, error) 37 | } 38 | -------------------------------------------------------------------------------- /llm/openai/chatgpt.go: -------------------------------------------------------------------------------- 1 | package openai 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/agi-cn/llmplugin/llm" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sashabaranov/go-openai" 10 | ) 11 | 12 | type ChatGPT struct { 13 | model string 14 | client *openai.Client 15 | } 16 | 17 | type Option func(c *ChatGPT) 18 | 19 | func WithModel(model string) Option { 20 | return func(c *ChatGPT) { 21 | c.model = model 22 | } 23 | } 24 | 25 | func NewChatGPT(token string, opts ...Option) *ChatGPT { 26 | 27 | client := openai.NewClient(token) 28 | 29 | chatgpt := &ChatGPT{ 30 | model: openai.GPT3Dot5Turbo, 31 | client: client, 32 | } 33 | 34 | for _, opt := range opts { 35 | opt(chatgpt) 36 | } 37 | 38 | return chatgpt 39 | } 40 | 41 | func (c ChatGPT) Summary(ctx context.Context, content string) (string, error) { 42 | 43 | messages := []llm.LlmMessage{ 44 | { 45 | Role: llm.RoleUser, 46 | Content: content, 47 | }, 48 | } 49 | 50 | answer, err := c.Chat(ctx, messages) 51 | if err != nil { 52 | return "", err 53 | } 54 | 55 | return answer.Content, nil 56 | } 57 | 58 | func (c ChatGPT) Chat(ctx context.Context, messages []llm.LlmMessage) (*llm.LlmAnswer, error) { 59 | 60 | chatGPTMessages := c.makeChatGPTMessage(messages) 61 | 62 | return c.send(ctx, chatGPTMessages) 63 | 64 | } 65 | 66 | func (c ChatGPT) makeChatGPTMessage(messages []llm.LlmMessage) []openai.ChatCompletionMessage { 67 | 68 | chatGPTMessages := make([]openai.ChatCompletionMessage, 0, len(messages)) 69 | for _, m := range messages { 70 | chatGPTMessages = append(chatGPTMessages, openai.ChatCompletionMessage{ 71 | Role: m.Role.String(), 72 | Content: m.Content, 73 | }) 74 | } 75 | 76 | return chatGPTMessages 77 | } 78 | 79 | func (c ChatGPT) send(ctx context.Context, messages []openai.ChatCompletionMessage) (*llm.LlmAnswer, error) { 80 | 81 | resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ 82 | Model: c.model, 83 | Messages: messages, 84 | }) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if choices := resp.Choices; len(choices) == 0 { 90 | return nil, errors.New("got empty ChatGPT response") 91 | } 92 | 93 | answer := c.convertLlmAnswer(resp) 94 | return answer, nil 95 | } 96 | 97 | func (c ChatGPT) convertLlmAnswer(openaiResp openai.ChatCompletionResponse) *llm.LlmAnswer { 98 | 99 | choices := openaiResp.Choices[0] 100 | 101 | return &llm.LlmAnswer{ 102 | Role: choices.Message.Role, 103 | Content: choices.Message.Content, 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /plugin.go: -------------------------------------------------------------------------------- 1 | package llmplugin 2 | 3 | import "context" 4 | 5 | type Plugin interface { 6 | Do(ctx context.Context, query string) (answer string, err error) 7 | 8 | GetName() string 9 | GetInputExample() string 10 | GetDesc() string 11 | } 12 | 13 | var _ Plugin = (*SimplePlugin)(nil) 14 | 15 | type SimplePlugin struct { 16 | // Name of plugin. 17 | Name string 18 | 19 | // InputExample is the example of input. 20 | InputExample string 21 | 22 | // Desc is the description of plugin for LLM to understand what is the plugin and what for. 23 | Desc string 24 | 25 | // DoFunc is the handle function of plugin. 26 | DoFunc func(ctx context.Context, query string) (answer string, err error) 27 | } 28 | 29 | func (p SimplePlugin) GetName() string { 30 | return p.Name 31 | } 32 | 33 | func (p SimplePlugin) GetInputExample() string { 34 | return p.InputExample 35 | } 36 | 37 | func (p SimplePlugin) GetDesc() string { 38 | return p.Desc 39 | } 40 | 41 | func (p SimplePlugin) Do(ctx context.Context, query string) (answer string, err error) { 42 | return p.DoFunc(ctx, query) 43 | } 44 | -------------------------------------------------------------------------------- /plugin_manager.go: -------------------------------------------------------------------------------- 1 | package llmplugin 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/agi-cn/llmplugin/llm" 9 | 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type PluginContext struct { 14 | Plugin 15 | 16 | // Input for handle function of plugin. 17 | Input string 18 | } 19 | 20 | type PluginManager struct { 21 | llmer llm.LLMer 22 | 23 | // plugins 24 | plugins map[string]Plugin 25 | } 26 | 27 | type PluginManagerOpt func(manager *PluginManager) 28 | 29 | // WithPlugin enable one plugin. 30 | func WithPlugin(p Plugin) PluginManagerOpt { 31 | 32 | return func(manager *PluginManager) { 33 | name := strings.ToLower(p.GetName()) 34 | if _, ok := manager.plugins[name]; !ok { 35 | manager.plugins[name] = p 36 | } 37 | } 38 | } 39 | 40 | // WithPlugins enable multiple plugins. 41 | func WithPlugins(plugins []Plugin) PluginManagerOpt { 42 | 43 | return func(manager *PluginManager) { 44 | 45 | for _, p := range plugins { 46 | opt := WithPlugin(p) 47 | opt(manager) 48 | } 49 | } 50 | } 51 | 52 | // NewPluginManager create plugin manager. 53 | func NewPluginManager(llmer llm.LLMer, opts ...PluginManagerOpt) *PluginManager { 54 | 55 | manager := &PluginManager{ 56 | llmer: llmer, 57 | plugins: make(map[string]Plugin, 4), 58 | } 59 | 60 | for _, opt := range opts { 61 | opt(manager) 62 | } 63 | 64 | return manager 65 | } 66 | 67 | // Select to choice some plugin to finish the task. 68 | func (m *PluginManager) Select(ctx context.Context, query string) ([]PluginContext, error) { 69 | 70 | answer, err := m.chatWithLlm(ctx, query) 71 | if err != nil { 72 | logrus.Errorf("chat with llm error: %v", err) 73 | return nil, err 74 | } 75 | 76 | pluginCtxs := m.choicePlugins(answer) 77 | 78 | // for debug 79 | for _, c := range pluginCtxs { 80 | logrus.Debugf("query: %s choice plugins: %s input: %s", query, c.GetName(), c.Input) 81 | } 82 | 83 | return pluginCtxs, nil 84 | } 85 | 86 | func (m *PluginManager) makePrompt(query string) string { 87 | 88 | tools := m.makeTaskList() 89 | 90 | prompt := fmt.Sprintf(`You will performs one task based on the following object: 91 | %s 92 | 93 | You can call one or multiple of the following functions in triple backticks: 94 | ''' 95 | %s 96 | ''' 97 | 98 | In each response, you must start with a function call like Tool name and args, split by ':',like: 99 | Google: query 100 | Weather: 101 | 102 | Don't explain why you use a tool. If you cannot figure out the answer, you say 'I don’t know'. 103 | 104 | Select only the corresponding tool and do not return any results.`, 105 | 106 | query, 107 | tools, 108 | ) 109 | 110 | return prompt 111 | } 112 | 113 | func (m *PluginManager) makeTaskList() string { 114 | 115 | lines := make([]string, 0, len(m.plugins)) 116 | 117 | for _, p := range m.plugins { 118 | 119 | line := fmt.Sprintf( 120 | `- %s, Input Example: %s, It works as: %s`, 121 | p.GetName(), 122 | p.GetInputExample(), 123 | p.GetDesc(), 124 | ) 125 | 126 | lines = append(lines, line) 127 | } 128 | 129 | return strings.Join(lines, "\n") 130 | } 131 | 132 | func (m *PluginManager) chatWithLlm(ctx context.Context, query string) (string, error) { 133 | prompt := m.makePrompt(query) 134 | 135 | messages := []llm.LlmMessage{ 136 | { 137 | Role: llm.RoleSystem, 138 | Content: "You are an helpful and kind assistant to answer questions that can use tools to interact with real world and get access to the latest information.", 139 | }, 140 | { 141 | Role: llm.RoleUser, 142 | Content: prompt, 143 | }, 144 | } 145 | 146 | answer, err := m.llmer.Chat(ctx, messages) 147 | if err != nil { 148 | return "", err 149 | } 150 | 151 | // logrus.Debugf("query: %s\n answer: %+v", query, answer) 152 | 153 | return answer.Content, nil 154 | } 155 | 156 | func (m *PluginManager) choicePlugins(answer string) []PluginContext { 157 | 158 | lines := strings.Split(answer, "\n") 159 | 160 | pluginContexts := make([]PluginContext, 0, len(lines)) 161 | 162 | for _, line := range lines { 163 | logrus.Debugf("select one line: %s", line) 164 | 165 | if line == `I don’t know.` { 166 | continue 167 | } 168 | 169 | // Split by space 170 | // IF only ONE column, it's function name without args. 171 | // IF TWO column, it's function name with args. 172 | 173 | ss := strings.Split(line, ":") 174 | if len(ss) == 0 { 175 | logrus.Warnf("answer line invalid: %s", line) 176 | continue 177 | } 178 | 179 | name := strings.TrimSpace(strings.ToLower(ss[0])) 180 | var input string 181 | if len(ss) == 2 { 182 | input = strings.TrimSpace(ss[1]) 183 | } 184 | 185 | if p, ok := m.plugins[name]; ok { 186 | 187 | logrus.Debugf("choice one plug with args: plugin=%v args=%v", name, input) 188 | 189 | pluginCtx := PluginContext{ 190 | Plugin: p, 191 | Input: input, 192 | } 193 | 194 | pluginContexts = append(pluginContexts, pluginCtx) 195 | } 196 | } 197 | 198 | return pluginContexts 199 | } 200 | -------------------------------------------------------------------------------- /plugin_manager_test.go: -------------------------------------------------------------------------------- 1 | package llmplugin 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/agi-cn/llmplugin/llm" 9 | "github.com/agi-cn/llmplugin/llm/openai" 10 | "github.com/agi-cn/llmplugin/plugins/calculator" 11 | "github.com/agi-cn/llmplugin/plugins/google" 12 | "github.com/agi-cn/llmplugin/plugins/stablediffusion" 13 | "github.com/joho/godotenv" 14 | 15 | "github.com/stretchr/testify/assert" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | func TestManagerSelectPlugin(t *testing.T) { 20 | manager := newChatGPTManager() 21 | 22 | t.Run("Digital Computing", func(t *testing.T) { 23 | pluginCtxs, err := manager.Select(context.Background(), "10 add 20 equals ?") 24 | require.NoError(t, err) 25 | 26 | require.Equal(t, 1, len(pluginCtxs)) 27 | require.True(t, includePlugin(pluginCtxs, "Calculator")) 28 | 29 | choices := pluginCtxs[0] 30 | 31 | answer, err := choices.Plugin.Do(context.Background(), choices.Input) 32 | require.NoError(t, err) 33 | 34 | assert.Equal(t, "30", answer) 35 | }) 36 | 37 | t.Run("Query Weather", func(t *testing.T) { 38 | choices, err := manager.Select(context.Background(), "How is the weather today?") 39 | assert.NoError(t, err) 40 | 41 | assert.NotEmpty(t, choices) 42 | assert.True(t, includePlugin(choices, "Weather")) 43 | }) 44 | 45 | t.Run("Stable Diffusion", func(t *testing.T) { 46 | choices, err := manager.Select(context.Background(), "Draw a girl image") 47 | assert.NoError(t, err) 48 | 49 | assert.NotEmpty(t, choices) 50 | assert.True(t, includePlugin(choices, "StableDiffusion")) 51 | }) 52 | 53 | t.Run("Google", func(t *testing.T) { 54 | 55 | choices, err := manager.Select(context.Background(), "NBA 总决赛现在如何?") 56 | assert.NoError(t, err) 57 | 58 | assert.NotEmpty(t, choices) 59 | assert.True(t, includePlugin(choices, "Google")) 60 | }) 61 | } 62 | 63 | func TestManagerSelectPlugin_WithoutChoice(t *testing.T) { 64 | manager := newChatGPTManager() 65 | 66 | t.Run("Quick Sort Source Code", func(t *testing.T) { 67 | choices, err := manager.Select(context.Background(), "quick sort source code in python") 68 | assert.NoError(t, err) 69 | 70 | assert.Empty(t, choices) 71 | }) 72 | 73 | } 74 | 75 | func includePlugin(pluginCtxs []PluginContext, target string) bool { 76 | for _, p := range pluginCtxs { 77 | if p.GetName() == target { 78 | return true 79 | } 80 | } 81 | 82 | return false 83 | } 84 | 85 | func TestChoicePlugins(t *testing.T) { 86 | 87 | plugins := newPlugins() 88 | manager := NewPluginManager(nil, WithPlugins(plugins)) 89 | 90 | t.Run("Choice Calculator", func(t *testing.T) { 91 | 92 | answer := "Calculator: 1+4" 93 | got := manager.choicePlugins(answer) 94 | 95 | assert.True(t, 96 | includePlugin(got, "Calculator")) 97 | }) 98 | 99 | t.Run("Choice Weather", func(t *testing.T) { 100 | answer := "Weather: " 101 | got := manager.choicePlugins(answer) 102 | 103 | assert.True(t, 104 | includePlugin(got, "Weather")) 105 | 106 | }) 107 | 108 | t.Run("Choice Google", func(t *testing.T) { 109 | answer := `Google: 今天NBA比赛赛程表` 110 | got := manager.choicePlugins(answer) 111 | 112 | assert.True(t, 113 | includePlugin(got, "Google")) 114 | }) 115 | 116 | } 117 | 118 | func newChatGPTManager() *PluginManager { 119 | _ = godotenv.Load() // ignore if file not exists 120 | 121 | var llmer llm.LLMer 122 | { 123 | token := os.Getenv("OPENAI_TOKEN") 124 | if len(token) == 0 { 125 | panic("empty openai token: set os env: OPENAI_TOKEN") 126 | } 127 | llmer = openai.NewChatGPT(token, openai.WithModel("gpt-4")) 128 | } 129 | 130 | plugins := newPlugins() 131 | 132 | return NewPluginManager(llmer, WithPlugins(plugins)) 133 | } 134 | 135 | func newPlugins() []Plugin { 136 | 137 | var ( 138 | googleEngineID = os.Getenv("GOOGLE_ENGINE_ID") 139 | googleToken = os.Getenv("GOOGLE_TOKEN") 140 | ) 141 | 142 | plugins := []Plugin{ 143 | &SimplePlugin{ 144 | Name: "Weather", 145 | InputExample: ``, 146 | Desc: "Can check the weather forecast", 147 | DoFunc: func(ctx context.Context, query string) (answer string, err error) { 148 | answer = "Call Weather Plugin" 149 | return 150 | }, 151 | }, 152 | 153 | calculator.NewCalculator(), 154 | 155 | google.NewGoogle(googleEngineID, googleToken), 156 | } 157 | 158 | { // stable diffusion 159 | var sdAddr = os.Getenv("SD_ADDR") 160 | if len(sdAddr) != 0 { 161 | plugins = append(plugins, 162 | stablediffusion.NewStableDiffusion(sdAddr), 163 | ) 164 | } 165 | 166 | } 167 | 168 | return plugins 169 | } 170 | -------------------------------------------------------------------------------- /plugins/agicn_search/agicn_search.go: -------------------------------------------------------------------------------- 1 | package agicn_search 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "net/url" 9 | ) 10 | 11 | const ( 12 | pluginName = "AgicnSearch" 13 | pluginInputExample = "Who is Google boss?" 14 | pluginDesc = `Search something by query input.` 15 | 16 | baseURL = "https://agicn-ducksearch.vercel.app/search" 17 | ) 18 | 19 | type searchResponse struct { 20 | Title string `json:"title"` 21 | Href string `json:"href"` 22 | Body string `json:"body"` 23 | } 24 | 25 | type AgicnSearch struct { 26 | client *http.Client 27 | } 28 | 29 | func NewAgicnSearch() *AgicnSearch { 30 | c := &http.Client{} 31 | return &AgicnSearch{c} 32 | } 33 | 34 | func (s AgicnSearch) Do(ctx context.Context, query string) (answer string, err error) { 35 | searchResults, err := s.doHTTPRequest(ctx, query) 36 | if err != nil { 37 | return "", err 38 | } 39 | 40 | answer = s.makeAnswer(searchResults) 41 | return answer, nil 42 | } 43 | 44 | func (s AgicnSearch) doHTTPRequest(ctx context.Context, query string) ([]searchResponse, error) { 45 | params := url.Values{} 46 | params.Add("q", query) 47 | 48 | url := fmt.Sprintf("%s?%s", baseURL, params.Encode()) 49 | 50 | req, err := http.NewRequest(http.MethodGet, url, nil) 51 | if err != nil { 52 | return nil, err 53 | } 54 | req = req.WithContext(ctx) 55 | 56 | resp, err := s.client.Do(req) 57 | if err != nil { 58 | return nil, err 59 | } 60 | defer resp.Body.Close() 61 | 62 | var searchResults []searchResponse 63 | if err := json.NewDecoder(resp.Body).Decode(&searchResults); err != nil { 64 | return nil, err 65 | } 66 | 67 | return searchResults, nil 68 | } 69 | 70 | func (s AgicnSearch) makeAnswer(searchResults []searchResponse) string { 71 | 72 | if len(searchResults) == 0 { 73 | return "" 74 | } 75 | 76 | result := searchResults[0] 77 | 78 | return fmt.Sprintf("%s\n%s\n%s", result.Title, result.Body, result.Href) 79 | } 80 | 81 | func (s AgicnSearch) GetName() string { 82 | 83 | return pluginName 84 | } 85 | 86 | func (s AgicnSearch) GetInputExample() string { 87 | return pluginInputExample 88 | } 89 | 90 | func (s AgicnSearch) GetDesc() string { 91 | return pluginDesc 92 | } 93 | -------------------------------------------------------------------------------- /plugins/agicn_search/agicn_search_test.go: -------------------------------------------------------------------------------- 1 | package agicn_search 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAgicnSearch(t *testing.T) { 11 | 12 | // TODO(zy): fix agi.cn search 13 | t.Skip("agi.cn search not valid NOW") 14 | 15 | ts := []struct { 16 | testname string 17 | query string 18 | }{ 19 | { 20 | "Search in english", 21 | "NBA schedule today", 22 | }, 23 | { 24 | "Search in chinese", 25 | "今天nba有哪些比赛", 26 | }, 27 | } 28 | 29 | s := NewAgicnSearch() 30 | 31 | for _, tc := range ts { 32 | 33 | t.Run(tc.testname, func(t *testing.T) { 34 | 35 | answer, err := s.Do(context.Background(), tc.query) 36 | assert.NoError(t, err) 37 | 38 | assert.NotEmpty(t, answer) 39 | 40 | t.Logf("query=%s\nresult:%s", tc.query, answer) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugins/calculator/calculator.go: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/mnogu/go-calculator" 8 | ) 9 | 10 | const ( 11 | pluginName = `Calculator` 12 | pluginDesc = `A calculator, capable of performing mathematical calculations, where the input is a description of a mathematical expression and the return is the result of the calculation. For example: the input is: one plus two, the return is three.` 13 | pluginInputExample = `1+2` 14 | ) 15 | 16 | type Calculator struct{} 17 | 18 | func NewCalculator() *Calculator { 19 | 20 | return &Calculator{} 21 | } 22 | 23 | func (c Calculator) GetInputExample() string { 24 | return pluginInputExample 25 | } 26 | 27 | func (Calculator) Do(ctx context.Context, query string) (answer string, err error) { 28 | 29 | result, err := calculator.Calculate(query) 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | return fmt.Sprintf("%v", result), nil 35 | } 36 | 37 | func (c Calculator) GetName() string { 38 | return pluginName 39 | } 40 | 41 | func (c Calculator) GetDesc() string { 42 | return pluginDesc 43 | } 44 | -------------------------------------------------------------------------------- /plugins/google/.env.example: -------------------------------------------------------------------------------- 1 | 2 | # OpenAI for summary 3 | OPENAI_TOKEN="sk-abc" 4 | # OPENAI_MODEL default is gpt 3.5 5 | # OPENAI_MODEL = "gpt-4" 6 | 7 | 8 | # Google 9 | GOOGLE_ENGINE_ID=xxx 10 | GOOGLE_TOKEN=abc -------------------------------------------------------------------------------- /plugins/google/google.go: -------------------------------------------------------------------------------- 1 | // Package google is Google Search Plugin 2 | // Get a Google Serach API key according to the Instruction. 3 | // https://stackoverflow.com/questions/37083058/programmatically-searching-google-in-python-using-custom-search 4 | 5 | package google 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "github.com/agi-cn/llmplugin/llm" 13 | "github.com/pkg/errors" 14 | "google.golang.org/api/customsearch/v1" 15 | "google.golang.org/api/option" 16 | ) 17 | 18 | const ( 19 | pluginName = "Google" 20 | 21 | pluginInputExample = "Who is Google boss?" 22 | 23 | pluginDesc = `Search something on the Internet by query input. You can search online to get the information you need, especially to get valid real-time information.` 24 | ) 25 | 26 | type Google struct { 27 | customSearchID string 28 | apiToken string 29 | 30 | summarizer llm.Summarizer 31 | } 32 | 33 | func NewGoogle(customSearchID, apiToken string, options ...Option) *Google { 34 | g := &Google{ 35 | customSearchID: customSearchID, 36 | apiToken: apiToken, 37 | } 38 | 39 | for _, o := range options { 40 | o(g) 41 | } 42 | 43 | return g 44 | } 45 | 46 | func (g Google) Do(ctx context.Context, query string) (answer string, err error) { 47 | 48 | results, err := g.doSearch(ctx, query) 49 | if err != nil { 50 | return "", err 51 | } 52 | 53 | return g.makeResult(ctx, query, results) 54 | } 55 | 56 | func (g Google) doSearch(ctx context.Context, query string) (*customsearch.Search, error) { 57 | client, err := customsearch.NewService(ctx, option.WithAPIKey(g.apiToken)) 58 | if err != nil { 59 | return nil, errors.Wrap(err, "new google service failed") 60 | } 61 | 62 | results, err := client.Cse.List().Q(query).Cx(g.customSearchID).Do() 63 | if err != nil { 64 | return nil, errors.Wrap(err, "google search failed") 65 | } 66 | 67 | return results, nil 68 | } 69 | 70 | func (g Google) makeResult(ctx context.Context, query string, results *customsearch.Search) (string, error) { 71 | 72 | items := results.Items 73 | if len(items) == 0 { 74 | return "Google don't known", nil 75 | } 76 | 77 | if g.summarizer == nil { 78 | return g.makeRawResult(ctx, items) 79 | } else { 80 | return g.makeResultBySummary(ctx, query, items) 81 | } 82 | } 83 | 84 | func (g Google) makeRawResult(ctx context.Context, items []*customsearch.Result) (string, error) { 85 | // Only return top1 result without llm summary 86 | item := items[0] 87 | 88 | content := fmt.Sprintf("%s\n%s\n%s", 89 | item.Title, 90 | item.Snippet, 91 | item.Link, 92 | ) 93 | return content, nil 94 | } 95 | 96 | func (g Google) makeResultBySummary(ctx context.Context, query string, items []*customsearch.Result) (string, error) { 97 | 98 | prompt := `User query: %s. 99 | 100 | Here is the google search result, you task is the following, 101 | 102 | 1. If there is a suspicion of advertising, then simply ignore the corresponding search results. 103 | 2. Select the top 3 most relevant search results to the user's query. 104 | 3. Summarize all the search results into one paragraph using Chinese. 105 | 4. In the corresponding results, give the relevant citation link. 106 | 5. Your job is to just summarize the search results, don't explain why you did it. If you ignore certain results, don't summarize those ignored results. 107 | 108 | 109 | Summarize the following text delimited by triple Triple dashes. Each line below is a google search result and the corresponding link. 110 | 111 | --- 112 | %s 113 | --- 114 | ` 115 | 116 | if len(items) > 10 { 117 | items = items[:10] 118 | } 119 | lines := make([]string, 0, len(items)) 120 | for i, item := range items { 121 | number := i + 1 122 | 123 | line := fmt.Sprintf( 124 | "%d. %s: %s (%s)", 125 | number, 126 | item.Title, 127 | item.Snippet, 128 | item.Link, 129 | ) 130 | 131 | lines = append(lines, line) 132 | } 133 | 134 | allResult := strings.Join(lines, "\n") 135 | content := fmt.Sprintf(prompt, query, allResult) 136 | 137 | return g.summarizer.Summary(ctx, content) 138 | } 139 | 140 | func (g Google) GetName() string { 141 | return pluginName 142 | } 143 | 144 | func (g Google) GetInputExample() string { 145 | return pluginInputExample 146 | } 147 | 148 | func (g Google) GetDesc() string { 149 | return pluginDesc 150 | } 151 | -------------------------------------------------------------------------------- /plugins/google/google_test.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/agi-cn/llmplugin/llm/openai" 9 | "github.com/joho/godotenv" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestGoogleWithoutSummary(t *testing.T) { 14 | 15 | _ = godotenv.Load() // ignore if file not exists 16 | 17 | var ( 18 | apiToken = os.Getenv("GOOGLE_TOKEN") 19 | engineID = os.Getenv("GOOGLE_ENGINE_ID") 20 | ) 21 | 22 | if apiToken == "" || engineID == "" { 23 | t.Skip("missing google env: GOOGLE_TOKEN or GOOGLE_ENGINE_ID. SKIP!") 24 | } 25 | 26 | g := NewGoogle(engineID, apiToken) 27 | 28 | answer, err := g.Do(context.Background(), "Who is Google Boss?") 29 | assert.NoError(t, err) 30 | 31 | assert.NotEmpty(t, answer) 32 | 33 | t.Logf("got answer: %v", answer) 34 | } 35 | 36 | func TestGoogleWithSummary(t *testing.T) { 37 | 38 | _ = godotenv.Load() // ignore if file not exists 39 | 40 | var ( 41 | apiToken = os.Getenv("GOOGLE_TOKEN") 42 | engineID = os.Getenv("GOOGLE_ENGINE_ID") 43 | 44 | openaiToken = os.Getenv("OPENAI_TOKEN") 45 | ) 46 | 47 | if apiToken == "" || engineID == "" || openaiToken == "" { 48 | t.Skip("missing google env: GOOGLE_TOKEN or GOOGLE_ENGINE_ID. SKIP!") 49 | } 50 | 51 | chatgpt := openai.NewChatGPT(openaiToken) 52 | g := NewGoogle(engineID, apiToken, WithSummarizer(chatgpt)) 53 | 54 | answer, err := g.Do(context.Background(), "今年欧冠决赛是什么时候?") 55 | assert.NoError(t, err) 56 | 57 | assert.NotEmpty(t, answer) 58 | 59 | t.Logf("got answer: %v", answer) 60 | } 61 | -------------------------------------------------------------------------------- /plugins/google/option.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | import "github.com/agi-cn/llmplugin/llm" 4 | 5 | type Option func(g *Google) 6 | 7 | // WithSummarizer 总结内容 8 | func WithSummarizer(summarizer llm.Summarizer) Option { 9 | 10 | return func(g *Google) { 11 | g.summarizer = summarizer 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugins/stablediffusion/stablediffusion.go: -------------------------------------------------------------------------------- 1 | package stablediffusion 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "net/http" 9 | ) 10 | 11 | const ( 12 | pluginName = "StableDiffusion" 13 | pluginInputExample = "A beautiful girl" 14 | pluginDesc = `Stable diffusion is text-to-image model capable of generating images given any text input` 15 | ) 16 | 17 | type StableDiffusion struct { 18 | sdAddr string 19 | 20 | client *http.Client 21 | } 22 | 23 | func NewStableDiffusion(sdAddr string) *StableDiffusion { 24 | if len(sdAddr) == 0 { 25 | panic("stable diffusion address is empty") 26 | } 27 | 28 | return &StableDiffusion{ 29 | sdAddr: sdAddr, 30 | client: &http.Client{}, 31 | } 32 | } 33 | 34 | func (s *StableDiffusion) Do(ctx context.Context, query string) (answer string, err error) { 35 | req, err := s.newRequest(ctx, query) 36 | if err != nil { 37 | return "", err 38 | } 39 | 40 | resp, err := s.client.Do(req) 41 | if err != nil { 42 | return "", err 43 | } 44 | defer resp.Body.Close() 45 | 46 | var sdResp struct { 47 | Result bool `json:"result"` 48 | Images []string `json:"images"` // base64 49 | } 50 | 51 | if err := json.NewDecoder(resp.Body).Decode(&sdResp); err != nil { 52 | return "", err 53 | } 54 | 55 | if len(sdResp.Images) == 0 { 56 | return "", nil 57 | } 58 | 59 | return sdResp.Images[0], nil 60 | } 61 | 62 | func (s *StableDiffusion) newRequest(ctx context.Context, query string) (*http.Request, error) { 63 | url := fmt.Sprintf("http://%v/sd", s.sdAddr) 64 | 65 | body := map[string]interface{}{ 66 | "text": query, 67 | } 68 | data, err := json.Marshal(body) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data)) 74 | if err != nil { 75 | return nil, err 76 | } 77 | req = req.WithContext(ctx) 78 | 79 | return req, nil 80 | } 81 | 82 | func (StableDiffusion) GetName() string { 83 | return pluginName 84 | } 85 | 86 | func (StableDiffusion) GetInputExample() string { 87 | return pluginInputExample 88 | } 89 | 90 | func (StableDiffusion) GetDesc() string { 91 | return pluginDesc 92 | } 93 | -------------------------------------------------------------------------------- /plugins/stablediffusion/stablediffusion_test.go: -------------------------------------------------------------------------------- 1 | package stablediffusion 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestStableDiffusion(t *testing.T) { 14 | 15 | sd := NewStableDiffusion("127.0.0.1:19000") 16 | 17 | answer, err := sd.Do(context.Background(), "a girl play golf") 18 | require.NoError(t, err) 19 | 20 | assert.NotEmpty(t, answer) 21 | 22 | data, err := base64.StdEncoding.DecodeString(answer) 23 | assert.NoError(t, err) 24 | 25 | assert.NoError(t, 26 | ioutil.WriteFile("1.jpg", data, 0644)) 27 | } 28 | -------------------------------------------------------------------------------- /plugins/stablediffusion/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agi-cn/llmplugin/6dcca60cb239d95c1c92d43197d011f9e434093e/plugins/stablediffusion/test1.jpg -------------------------------------------------------------------------------- /plugins/weather/weather.go: -------------------------------------------------------------------------------- 1 | package weather 2 | 3 | 4 | type Weather struct{ 5 | Name string 6 | } -------------------------------------------------------------------------------- /plugins/wikipedia.go/wikipedia.go: -------------------------------------------------------------------------------- 1 | package wikipedia 2 | --------------------------------------------------------------------------------