├── Makefile ├── README.md ├── cmd ├── main.go └── setup │ ├── ConfigHandler.go │ ├── client.go │ ├── echo.go │ └── logrus.go ├── go.mod ├── go.sum ├── internal ├── api │ ├── handlers.go │ ├── health.go │ └── routes.go ├── config │ ├── Environment.go │ ├── GlobalConfig.go │ ├── LoadConfig.go │ └── OpenAIEnv.go └── pkg │ ├── client │ ├── OpenAIClient.go │ ├── interface.go │ └── option.go │ ├── constants │ └── constants.go │ ├── process │ └── postprocessor.go │ └── store │ └── base.go ├── pkg ├── client │ ├── chat.go │ ├── chatgpt.go │ ├── completion.go │ ├── edit.go │ ├── embedding.go │ ├── model.go │ ├── prompt.go │ └── reversed_python │ │ ├── Chatbot.py │ │ ├── ChatbotRunner.py │ │ └── requirements.txt └── errors │ ├── ApiError.go │ └── LoadConfigError.go └── test └── client_test.go /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | export ENV=dev 3 | go run ./cmd/main.go 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang Boilerplate for OpenAI Service 2 | ## Description 3 | * This repository is for the golang-based server boilerplate that wraps OpenAI Service with PostgreSQL and go-chi. 4 | * The codebase uses the [Reverse Engineered ChatGPT API](https://github.com/acheong08/ChatGPT) by [Antonio Cheong](https://github.com/acheong08) and it is also largely influenced by [go-gpt3](https://github.com/PullRequestInc/go-gpt3) from [PullRequest Inc](https://github.com/PullRequestInc). 5 | * It currently lacks the support for GPT-4 yet whereby please feel free to send the pull request on the repository. 6 | 7 | ## Environment Variables 8 | * Please make sure that the environment variable has been defined in the `./config.yaml` like the following: 9 | ```yaml 10 | Environment: DEV 11 | OpenAIEnv: 12 | API_KEY: "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // if you are going to use the official API 13 | ACCESS_TOKEN: "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // if you are going to use the reverse-engineered API 14 | ``` -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "chatgpt-service/cmd/setup" 5 | "chatgpt-service/internal/config" 6 | "fmt" 7 | "github.com/sirupsen/logrus" 8 | "os" 9 | "sync" 10 | ) 11 | 12 | func main() { 13 | var wg sync.WaitGroup 14 | wg.Add(1) 15 | errCh := make(chan error) 16 | 17 | go func() { 18 | port := 8080 19 | // load configuration 20 | env := os.Getenv("ENV") 21 | cfg, err := config.LoadConfig(config.DefaultConfigPath, env) 22 | if err != nil { 23 | errCh <- err 24 | } 25 | // setup database 26 | db := setup.InitializeDatabase(cfg) 27 | // setup openai client 28 | oc, err := setup.NewOpenAIClient(cfg) 29 | if err != nil { 30 | errCh <- err 31 | } 32 | // setup echo server 33 | err, e := setup.InitializeEcho(cfg, *oc, *db) 34 | if err != nil { 35 | logrus.WithFields(logrus.Fields{ 36 | "service": "chatgpt-service", 37 | "component": "main", 38 | }).WithError(err).Error("ChatGPT Service API server running failed") 39 | errCh <- err 40 | } 41 | fmt.Println("ChatGPT Service Server is now running at the port :" + fmt.Sprint(port)) 42 | logrus.WithFields(logrus.Fields{ 43 | "service": "chatgpt-service", 44 | "component": "main", 45 | "port": port, 46 | }).Info("ChatGPT Service API server running") 47 | err = e.Start(":" + fmt.Sprint(port)) 48 | if err != nil { 49 | logrus.WithFields(logrus.Fields{ 50 | "service": "chatgpt-service", 51 | "component": "main", 52 | }).WithError(err).Error("ChatGPT Service API server running failed") 53 | errCh <- err 54 | } 55 | }() 56 | 57 | for { 58 | select { 59 | case err := <-errCh: 60 | logrus.WithFields(logrus.Fields{ 61 | "service": "chatgpt-service", 62 | "component": "main", 63 | }).WithError(err).Error("An error occurred") 64 | wg.Done() 65 | os.Exit(1) 66 | } 67 | } 68 | 69 | wg.Wait() 70 | } 71 | -------------------------------------------------------------------------------- /cmd/setup/ConfigHandler.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "chatgpt-service/internal/config" 5 | "chatgpt-service/internal/pkg/client" 6 | "github.com/labstack/echo/v4" 7 | ) 8 | 9 | func ConfigHandler(e *echo.Echo, cfg config.GlobalConfig, oc *client.OpenAIClientInterface) { 10 | e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { 11 | return func(ctx echo.Context) error { 12 | ctx.Set(config.GlobalConfigKey, cfg) 13 | ctx.Set(client.OpenAIClientKey, oc) 14 | return next(ctx) 15 | } 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/setup/client.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "chatgpt-service/internal/config" 5 | "chatgpt-service/internal/pkg/client" 6 | "chatgpt-service/internal/pkg/constants" 7 | "chatgpt-service/internal/pkg/store" 8 | "github.com/go-pg/pg" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | // InitializeDatabase TODO: refactor to configuration environment variable 14 | func InitializeDatabase(_ *config.GlobalConfig) *store.Database { 15 | endpoint := "" 16 | port := "5432" 17 | user := "" 18 | password := "" 19 | databaseName := "postgres" 20 | 21 | db := pg.Connect(&pg.Options{ 22 | Addr: endpoint + ":" + port, 23 | User: user, 24 | Password: password, 25 | Database: databaseName, 26 | }) 27 | 28 | return &store.Database{DB: db} 29 | } 30 | 31 | func NewOpenAIClient(cfg *config.GlobalConfig, options ...client.ClientOption) (*client.OpenAIClient, error) { 32 | httpClient := &http.Client{ 33 | Timeout: time.Duration(constants.DefaultTimeoutSeconds * time.Second), 34 | } 35 | cl := &client.OpenAIClient{ 36 | UserAgent: constants.DefaultUserAgent, 37 | ApiKey: cfg.OpenAIEnv.API_KEY, 38 | AccessToken: cfg.OpenAIEnv.ACCESS_TOKEN, 39 | BaseURL: constants.DefaultBaseURL, 40 | HttpClient: httpClient, 41 | DefaultEngine: constants.DefaultEngine, 42 | IdOrg: constants.DefaultClientName, 43 | } 44 | for _, clientOption := range options { 45 | err := clientOption(cl) 46 | if err != nil { 47 | return nil, err 48 | } 49 | } 50 | return cl, nil 51 | } 52 | -------------------------------------------------------------------------------- /cmd/setup/echo.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "chatgpt-service/internal/api" 5 | "chatgpt-service/internal/config" 6 | "chatgpt-service/internal/pkg/client" 7 | "chatgpt-service/internal/pkg/store" 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echo/v4/middleware" 10 | ) 11 | 12 | func InitializeEcho(cfg *config.GlobalConfig, oc client.OpenAIClient, db store.Database) (error, *echo.Echo) { 13 | e := echo.New() 14 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 15 | AllowOrigins: []string{"*"}, 16 | AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept}, 17 | AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE}, 18 | AllowCredentials: true, 19 | })) 20 | //ConfigHandler(e, *cfg, oc) 21 | err := api.SetupRoutes(e, *cfg, oc, db) 22 | if err != nil { 23 | return err, nil 24 | } 25 | 26 | return nil, e 27 | } 28 | -------------------------------------------------------------------------------- /cmd/setup/logrus.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "gopkg.in/natefinch/lumberjack.v2" 6 | "os" 7 | ) 8 | 9 | func ConfigureLogrus() { 10 | env := os.Getenv("ENV") 11 | lum := &lumberjack.Logger{ 12 | Filename: "log/server-" + env + ".log", 13 | MaxSize: 500, 14 | MaxBackups: 3, 15 | MaxAge: 28, 16 | Compress: true, 17 | } 18 | 19 | logrus.SetOutput(lum) 20 | logrus.SetFormatter(&logrus.TextFormatter{ 21 | FullTimestamp: true, 22 | ForceQuote: true, 23 | }) 24 | logrus.SetReportCaller(true) 25 | logrus.SetLevel(logrus.DebugLevel) 26 | logrus.Info("logrus & lumberjack activated") 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module chatgpt-service 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/go-pg/pg v8.0.7+incompatible 7 | github.com/knadh/koanf v1.4.5 8 | github.com/labstack/echo/v4 v4.10.2 9 | github.com/labstack/gommon v0.4.0 10 | github.com/pkg/errors v0.9.1 11 | github.com/sirupsen/logrus v1.9.0 12 | github.com/stretchr/testify v1.8.1 13 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/fsnotify/fsnotify v1.4.9 // indirect 19 | github.com/go-chi/cors v1.2.1 // indirect 20 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 21 | github.com/jinzhu/inflection v1.0.0 // indirect 22 | github.com/mattn/go-colorable v0.1.13 // indirect 23 | github.com/mattn/go-isatty v0.0.17 // indirect 24 | github.com/mitchellh/copystructure v1.2.0 // indirect 25 | github.com/mitchellh/mapstructure v1.5.0 // indirect 26 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 27 | github.com/onsi/ginkgo v1.16.5 // indirect 28 | github.com/onsi/gomega v1.27.2 // indirect 29 | github.com/pmezard/go-difflib v1.0.0 // indirect 30 | github.com/valyala/bytebufferpool v1.0.0 // indirect 31 | github.com/valyala/fasttemplate v1.2.2 // indirect 32 | golang.org/x/crypto v0.6.0 // indirect 33 | golang.org/x/net v0.7.0 // indirect 34 | golang.org/x/sys v0.5.0 // indirect 35 | golang.org/x/text v0.7.0 // indirect 36 | golang.org/x/time v0.3.0 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | mellium.im/sasl v0.3.1 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /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 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 7 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 8 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 9 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 10 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 11 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 12 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 13 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 14 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 15 | github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= 16 | github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= 17 | github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= 18 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= 19 | github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= 20 | github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= 21 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= 22 | github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= 23 | github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= 24 | github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= 25 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 26 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 27 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 28 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 29 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 30 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 31 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 32 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 33 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 34 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 35 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 36 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 38 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 39 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 40 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 41 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 42 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 43 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 44 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 45 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 46 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 47 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 48 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 49 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 50 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 51 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 52 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 53 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 54 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 55 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 56 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 57 | github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= 58 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 59 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 60 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 61 | github.com/go-pg/pg v8.0.7+incompatible h1:ty/sXL1OZLo+47KK9N8llRcmbA9tZasqbQ/OO4ld53g= 62 | github.com/go-pg/pg v8.0.7+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA= 63 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 64 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 65 | github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 66 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 67 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 68 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 69 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 70 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 71 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 72 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 73 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 74 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 75 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 76 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 77 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 78 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 79 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 80 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 81 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 82 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 83 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 84 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 85 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 86 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 87 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 88 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 89 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 90 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 91 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 92 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 93 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 94 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 95 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 96 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 97 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 98 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 99 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 100 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 101 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 102 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 103 | github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= 104 | github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= 105 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 106 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 107 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 108 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= 109 | github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 110 | github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 111 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 112 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 113 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 114 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 115 | github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= 116 | github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 117 | github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 118 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 119 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 120 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 121 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 122 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 123 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 124 | github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 125 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 126 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 127 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 128 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 129 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 130 | github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= 131 | github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= 132 | github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= 133 | github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= 134 | github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= 135 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 136 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 137 | github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= 138 | github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= 139 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 140 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 141 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 142 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 143 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 144 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 145 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 146 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 147 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 148 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 149 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 150 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 151 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 152 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 153 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 154 | github.com/knadh/koanf v1.4.5 h1:yKWFswTrqFc0u7jBAoERUz30+N1b1yPXU01gAPr8IrY= 155 | github.com/knadh/koanf v1.4.5/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= 156 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 157 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 158 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 159 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 160 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 161 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 162 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 163 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 164 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 165 | github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA= 166 | github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= 167 | github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= 168 | github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= 169 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 170 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 171 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 172 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 173 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 174 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 175 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 176 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 177 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 178 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 179 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 180 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 181 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 182 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 183 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 184 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 185 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 186 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 187 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 188 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 189 | github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= 190 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 191 | github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 192 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 193 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 194 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 195 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 196 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 197 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 198 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 199 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 200 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 201 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 202 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 203 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 204 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 205 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 206 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 207 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 208 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 209 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 210 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 211 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 212 | github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= 213 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 214 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 215 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 216 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 217 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 218 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 219 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 220 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 221 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 222 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 223 | github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY= 224 | github.com/onsi/gomega v1.27.2/go.mod h1:5mR3phAHpkAVIDkHEUBY6HGVsU+cpcEscrGPB4oPlZI= 225 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 226 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 227 | github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= 228 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 229 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 230 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 231 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 232 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 233 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 234 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 235 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 236 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 237 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 238 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 239 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 240 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 241 | github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 242 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 243 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 244 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 245 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 246 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 247 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 248 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 249 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 250 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 251 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 252 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 253 | github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= 254 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 255 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 256 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 257 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 258 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 259 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 260 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 261 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 262 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 263 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 264 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 265 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 266 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 267 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 268 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 269 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 270 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 271 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 272 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 273 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 274 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 275 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 276 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 277 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 278 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 279 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 280 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 281 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 282 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 283 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 284 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 285 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 286 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 287 | go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= 288 | go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= 289 | go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= 290 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 291 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 292 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 293 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 294 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 295 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 296 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 297 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 298 | golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= 299 | golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 300 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 301 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 302 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 303 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 304 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 305 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 306 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 307 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 308 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 309 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 310 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 311 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 312 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 313 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 314 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 315 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 316 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 317 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 318 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 319 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 320 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 321 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 322 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 323 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 324 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 325 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 326 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 327 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 328 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 329 | golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= 330 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 331 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 332 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 333 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 334 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 335 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 336 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 337 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 338 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 339 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 340 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 341 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 342 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 343 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 344 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 345 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 346 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 347 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 348 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 349 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 350 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 351 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 352 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 353 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 354 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 355 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 356 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 357 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 358 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 359 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 360 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 361 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 362 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 363 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 364 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 365 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 366 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 367 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 368 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 369 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 370 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 371 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 376 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 377 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 378 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 379 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 380 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 381 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 382 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 383 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 384 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 385 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 386 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 387 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 388 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 389 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 390 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 391 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 392 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 393 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 394 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 395 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 396 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 397 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 398 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 399 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 400 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 401 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 402 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 403 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 404 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 405 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 406 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 407 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 408 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 409 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 410 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 411 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 412 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 413 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 414 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 415 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 416 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 417 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 418 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 419 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 420 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 421 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 422 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 423 | google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 424 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 425 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 426 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 427 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 428 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 429 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 430 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 431 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 432 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 433 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 434 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 435 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 436 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 437 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 438 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 439 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 440 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 441 | gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= 442 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 443 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 444 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 445 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 446 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 447 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 448 | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 449 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 450 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 451 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 452 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 453 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 454 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 455 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 456 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 457 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 458 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 459 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 460 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 461 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 462 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 463 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 464 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 465 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 466 | mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= 467 | mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= 468 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 469 | -------------------------------------------------------------------------------- /internal/api/handlers.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "chatgpt-service/internal/config" 5 | "chatgpt-service/internal/pkg/client" 6 | "chatgpt-service/internal/pkg/store" 7 | cif "chatgpt-service/pkg/client" 8 | "github.com/labstack/echo/v4" 9 | "os/exec" 10 | ) 11 | 12 | type Handler struct { 13 | oc *client.OpenAIClient 14 | // TODO: remove echo.Context to have different context for each request 15 | ectx *echo.Context 16 | // TODO: remove handler - database mapping connection 17 | db *store.Database 18 | } 19 | 20 | func NewHandler(c echo.Context, cfg config.GlobalConfig, oc *client.OpenAIClient, db *store.Database) (*Handler, error) { 21 | return &Handler{ 22 | oc: oc, 23 | ectx: &c, 24 | db: db, 25 | }, nil 26 | } 27 | 28 | func (hd *Handler) ListModels(_ echo.Context) error { 29 | res, err := hd.oc.ListModels((*hd.ectx).Request().Context()) 30 | if err != nil { 31 | return err 32 | } 33 | return (*hd.ectx).JSON(200, res) 34 | } 35 | 36 | func (hd *Handler) RetrieveModel(_ echo.Context) error { 37 | res, err := hd.oc.RetrieveModel((*hd.ectx).Request().Context(), (*hd.ectx).Param(cif.ModelIdParamKey)) 38 | if err != nil { 39 | return err 40 | } 41 | return (*hd.ectx).JSON(200, res) 42 | } 43 | 44 | func (hd *Handler) CreateChatCompletion(_ echo.Context) error { 45 | var cr cif.ChatCompletionRequest 46 | if err := (*hd.ectx).Bind(&cr); err != nil { 47 | return (*hd.ectx).JSON(400, err.Error()) 48 | } 49 | res, err := hd.oc.CreateNewChatCompletion((*hd.ectx).Request().Context(), cr) 50 | if err != nil { 51 | return (*hd.ectx).JSON(503, err.Error()) 52 | } 53 | return (*hd.ectx).JSON(200, res) 54 | } 55 | 56 | func (hd *Handler) CreateCompletion(_ echo.Context) error { 57 | var cr cif.CompletionRequest 58 | if err := (*hd.ectx).Bind(&cr); err != nil { 59 | return err 60 | } 61 | res, err := hd.oc.CreateCompletion((*hd.ectx).Request().Context(), cr) 62 | if err != nil { 63 | return err 64 | } 65 | return (*hd.ectx).JSON(200, res) 66 | } 67 | 68 | func (hd *Handler) CreateCompletionStream(_ echo.Context) error { 69 | var cr cif.CompletionRequest 70 | if err := (*hd.ectx).Bind(&cr); err != nil { 71 | (*hd.ectx).Error(err) 72 | return err 73 | } 74 | 75 | // Set up SSE 76 | (*hd.ectx).Response().Header().Set(echo.HeaderContentType, "text/event-stream") 77 | (*hd.ectx).Response().Header().Set(echo.HeaderCacheControl, "no-cache") 78 | (*hd.ectx).Response().Header().Set(echo.HeaderConnection, "keep-alive") 79 | 80 | // Create a channel to receive new responses from the CompletionStream function 81 | respCh := make(chan cif.CompletionResponse) 82 | // A goroutine is started to run the CompletionStream function and send the received responses to the channel. 83 | go func() { 84 | err := hd.oc.CompletionStream((*hd.ectx).Request().Context(), cr, func(resp *cif.CompletionResponse) { 85 | respCh <- *resp 86 | }) 87 | if err != nil { 88 | (*hd.ectx).Error(err) 89 | } 90 | close(respCh) 91 | }() 92 | 93 | // In the for-loop, the code continuously reads from the response channel 94 | // and sends updates to the client via SSE by writing to the response and flushing it 95 | // Continuously read from the response channel and send updates to the client via SSE 96 | _, err := (*hd.ectx).Response().Write([]byte("event: start")) 97 | if err != nil { 98 | (*hd.ectx).Error(err) 99 | return err 100 | } 101 | for { 102 | select { 103 | case resp, ok := <-respCh: 104 | if !ok { 105 | // Channel closed, done streaming 106 | _, err := (*hd.ectx).Response().Write([]byte("event: end")) 107 | if err != nil { 108 | return err 109 | } 110 | (*hd.ectx).Response().Flush() 111 | return nil 112 | } 113 | // Use SSE to stream updates to the client 114 | write, err := (*hd.ectx).Response().Write([]byte("data: " + resp.Choices[0].Text + "\n")) 115 | if err != nil { 116 | return err 117 | } 118 | (*hd.ectx).Response().Flush() 119 | if write == 0 { 120 | return nil 121 | } 122 | case <-(*hd.ectx).Request().Context().Done(): 123 | // Request cancelled, done streaming 124 | write, err := (*hd.ectx).Response().Write([]byte("event: end")) 125 | if err != nil { 126 | return err 127 | } 128 | if write == 0 { 129 | return nil 130 | } 131 | (*hd.ectx).Response().Flush() 132 | return nil 133 | } 134 | } 135 | } 136 | 137 | func (hd *Handler) RunGptPythonClient(_ echo.Context) error { 138 | accessToken, err := (*hd.oc).GetAccessToken() 139 | if err != nil { 140 | return err 141 | } 142 | 143 | var promptRaw cif.GPTPromptRequest 144 | if err := (*hd.ectx).Bind(&promptRaw); err != nil { 145 | return (*hd.ectx).JSON(400, err.Error()) 146 | } 147 | // TODO: temporarily 148 | prompt, err := cif.CreatePrompt(promptRaw) 149 | if err != nil { 150 | return err 151 | } 152 | promptInString := prompt.String() 153 | 154 | result, err := exec.Command("python", "../pkg/client/ChatbotRunner.py", accessToken, promptInString).Output() 155 | if err != nil { 156 | return (*hd.ectx).JSON(500, err.Error()) 157 | } 158 | 159 | responseBody := cif.GPTPromptSuccessfulResponse{ 160 | Result: string(result), 161 | } 162 | 163 | return (*hd.ectx).JSON(200, responseBody) 164 | } 165 | -------------------------------------------------------------------------------- /internal/api/health.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "net/http" 6 | ) 7 | 8 | const HealthEndpoint = "/health" 9 | 10 | type Health struct { 11 | Name string `json:"name"` 12 | Description string `json:"description"` 13 | } 14 | 15 | func HealthCheck(c echo.Context) error { 16 | return c.JSON(http.StatusOK, Health{ 17 | Name: "Chatshire EthDenver Backend", 18 | Description: "Chatshire Backend API for EthDenver Hackathon PoC Stage", 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/api/routes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "chatgpt-service/internal/config" 5 | gclient "chatgpt-service/internal/pkg/client" 6 | "chatgpt-service/internal/pkg/store" 7 | "chatgpt-service/pkg/client" 8 | "github.com/labstack/echo/v4" 9 | ) 10 | 11 | func SetupRoutes(e *echo.Echo, cfg config.GlobalConfig, oc gclient.OpenAIClient, db store.Database) error { 12 | hd, err := NewHandler(e.AcquireContext(), cfg, &oc, &db) 13 | if err != nil { 14 | return err 15 | } 16 | //e.Use(HttpRequestLogHandler) -- FIX THE ERROR 17 | e.GET(HealthEndpoint, HealthCheck) 18 | e.GET(client.GetAllModels, hd.ListModels) 19 | e.GET(client.RetrieveModels, hd.RetrieveModel) 20 | e.POST(client.CreateCompletionEndpoint, hd.CreateCompletion) 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /internal/config/Environment.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Environment string 4 | 5 | const ( 6 | DevEnvironment Environment = "dev" 7 | StagingEnvironment Environment = "staging" 8 | ProdEnvironment Environment = "prod" 9 | ) 10 | -------------------------------------------------------------------------------- /internal/config/GlobalConfig.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const GlobalConfigKey = "globalconfig" 4 | const DefaultConfigPath = "./config.yaml" 5 | const TestConfigPath = "../config.yaml" 6 | 7 | type GlobalConfig struct { 8 | Environment Environment `koanf:"Environment" envDefault:"dev"` 9 | OpenAIEnv OpenAIENV 10 | } 11 | 12 | func (g *GlobalConfig) IsDev() bool { 13 | return g.Environment == DevEnvironment 14 | } 15 | 16 | func (g *GlobalConfig) IsStaging() bool { 17 | return g.Environment == StagingEnvironment 18 | } 19 | 20 | func (g *GlobalConfig) IsProd() bool { 21 | return g.Environment == ProdEnvironment 22 | } 23 | -------------------------------------------------------------------------------- /internal/config/LoadConfig.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | setup "chatgpt-service/pkg/errors" 5 | "fmt" 6 | "github.com/knadh/koanf" 7 | "github.com/knadh/koanf/parsers/yaml" 8 | "github.com/knadh/koanf/providers/file" 9 | "github.com/labstack/gommon/log" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | func LoadConfig(filePath string, env string) (*GlobalConfig, error) { 14 | k := koanf.New(".") 15 | 16 | if err := k.Load(file.Provider(filePath), yaml.Parser()); err != nil { 17 | log.Printf("could not load config file: %v", err) 18 | return nil, fmt.Errorf("error loading config file: %v", err) 19 | } 20 | 21 | var cfg GlobalConfig 22 | if err := k.Unmarshal(fmt.Sprintf("%s", env), &cfg); err != nil { 23 | log.Printf("could not unmarshal config file: %v", err) 24 | return nil, fmt.Errorf("error unmarshaling config file: %v", err) 25 | } 26 | cfg.OpenAIEnv.ParseEnv(k, env) 27 | 28 | if cfg.Environment == "" { 29 | logrus.WithFields(logrus.Fields{ 30 | "component": "setup", 31 | "env": env, 32 | }).Error(logrus.ErrorLevel, "Failed to load config") 33 | return nil, setup.LoadConfigError() 34 | } 35 | 36 | return &cfg, nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/config/OpenAIEnv.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/knadh/koanf" 6 | ) 7 | 8 | type OpenAIENV struct { 9 | API_KEY string `koanf:"%s.OpenAIEnv.API_KEY" envDefault:""` 10 | ACCESS_TOKEN string `koanf:"%s.OpenAIEnv.ACCESS_TOKEN" envDefault:""` 11 | } 12 | 13 | func (oaenv *OpenAIENV) ParseEnv(k *koanf.Koanf, env string) { 14 | oaenv.API_KEY = k.String(fmt.Sprintf("%s.OpenAIEnv.API_KEY", env)) 15 | oaenv.ACCESS_TOKEN = k.String(fmt.Sprintf("%s.OpenAIEnv.ACCESS_TOKEN", env)) 16 | } 17 | -------------------------------------------------------------------------------- /internal/pkg/client/OpenAIClient.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "chatgpt-service/pkg/client" 7 | cerror "chatgpt-service/pkg/errors" 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | "github.com/labstack/echo/v4" 12 | "github.com/pkg/errors" 13 | "github.com/sirupsen/logrus" 14 | "io" 15 | "io/ioutil" 16 | "net/http" 17 | "strings" 18 | ) 19 | 20 | const OpenAIClientKey = "OpenAIClient" 21 | const GPTGenerateQueryEndpoint = "/gpt/generate" 22 | 23 | type OpenAIClient struct { 24 | BaseURL string 25 | ApiKey string 26 | AccessToken string 27 | UserAgent string 28 | HttpClient *http.Client 29 | DefaultEngine string 30 | IdOrg string 31 | } 32 | 33 | func (oc *OpenAIClient) JSONBodyReader(body interface{}) (io.Reader, error) { 34 | if body == nil { 35 | return bytes.NewBuffer(nil), nil 36 | } 37 | 38 | raw, err := json.Marshal(body) 39 | if err != nil { 40 | return nil, errors.New("failed to encode body: " + err.Error()) 41 | } 42 | 43 | // Remove `OriginalPrompt` field if it exists 44 | var objMap map[string]interface{} 45 | if err := json.Unmarshal(raw, &objMap); err != nil { 46 | return nil, errors.New("failed to decode body: " + err.Error()) 47 | } 48 | delete(objMap, "original_prompt") 49 | filteredRaw, err := json.Marshal(objMap) 50 | if err != nil { 51 | return nil, errors.New("failed to re-encode body: " + err.Error()) 52 | } 53 | 54 | return bytes.NewBuffer(filteredRaw), nil 55 | } 56 | 57 | func (oc *OpenAIClient) NewRequestBuilder(ctx context.Context, method string, path string, payload interface{}) (*http.Request, error) { 58 | br, err := oc.JSONBodyReader(payload) 59 | if err != nil { 60 | return nil, err 61 | } 62 | url := oc.BaseURL + path // link to openai.com 63 | req, err := http.NewRequestWithContext(ctx, method, url, br) 64 | if err != nil { 65 | return nil, err 66 | } 67 | if len(oc.IdOrg) > 0 { 68 | req.Header.Set("user", oc.IdOrg) 69 | } 70 | req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 71 | req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", oc.ApiKey)) 72 | return req, nil 73 | } 74 | 75 | func (oc *OpenAIClient) ExecuteRequest(req *http.Request) (*http.Response, error) { 76 | resp, err := oc.HttpClient.Do(req) 77 | if err != nil { 78 | return nil, err 79 | } 80 | if err := oc.CheckRequestSucceed(resp); err != nil { 81 | return nil, err 82 | } 83 | return resp, nil 84 | } 85 | 86 | func (oc *OpenAIClient) CheckRequestSucceed(resp *http.Response) error { 87 | if resp.StatusCode >= 200 && resp.StatusCode < 300 { 88 | return nil 89 | } 90 | defer resp.Body.Close() 91 | data, err := ioutil.ReadAll(resp.Body) 92 | if err != nil { 93 | return fmt.Errorf("failed to read from body: %w", err) 94 | } 95 | var result cerror.APIErrorResponse 96 | if err := json.Unmarshal(data, &result); err != nil { 97 | // if we can't decode the json error then create an unexpected error 98 | apiError := cerror.APIError{ 99 | StatusCode: resp.StatusCode, 100 | Type: "Unexpected", 101 | Message: string(data), 102 | } 103 | return apiError 104 | } 105 | result.Error.StatusCode = resp.StatusCode 106 | return result.Error 107 | } 108 | 109 | func (oc *OpenAIClient) getResponseObject(rsp *http.Response, v interface{}) error { 110 | defer rsp.Body.Close() 111 | if err := json.NewDecoder(rsp.Body).Decode(v); err != nil { 112 | return fmt.Errorf("invalid json response: %w", err) 113 | } 114 | return nil 115 | } 116 | 117 | func (oc *OpenAIClient) ListModels(ctx context.Context) (*client.ListModelsResponse, error) { 118 | endPoint := client.ModelEndPoint 119 | req, err := oc.NewRequestBuilder(ctx, http.MethodGet, endPoint, nil) 120 | if err != nil { 121 | return nil, err 122 | } 123 | resp, err := oc.ExecuteRequest(req) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | output := new(client.ListModelsResponse) 129 | if err := oc.getResponseObject(resp, output); err != nil { 130 | return nil, err 131 | } 132 | return output, nil 133 | } 134 | 135 | func (oc OpenAIClient) RetrieveModel(ctx context.Context, engine string) (*client.ModelObject, error) { 136 | req, err := oc.NewRequestBuilder(ctx, http.MethodGet, client.ModelEndPoint+"/"+engine, nil) 137 | if err != nil { 138 | return nil, err 139 | } 140 | resp, err := oc.ExecuteRequest(req) 141 | if err != nil { 142 | return nil, err 143 | } 144 | output := new(client.ModelObject) 145 | if err := oc.getResponseObject(resp, output); err != nil { 146 | return nil, err 147 | } 148 | return output, nil 149 | } 150 | 151 | func (oc OpenAIClient) CreateNewChatCompletion(ctx context.Context, request client.ChatCompletionRequest) (*client.ChatCompletionResponse, error) { 152 | req, err := oc.NewRequestBuilder(ctx, http.MethodPost, client.OpenAIChatCompletionEndPoint, request) 153 | if err != nil { 154 | return nil, err 155 | } 156 | resp, err := oc.ExecuteRequest(req) 157 | if err != nil { 158 | return nil, err 159 | } 160 | output := new(client.ChatCompletionResponse) 161 | if err := oc.getResponseObject(resp, output); err != nil { 162 | return nil, err 163 | } 164 | return output, nil 165 | } 166 | 167 | func (oc OpenAIClient) CreateCompletion(ctx context.Context, request client.CompletionRequest) (*client.CompletionResponse, error) { 168 | return oc.CreateCompletionWithEngine(ctx, oc.DefaultEngine, request) 169 | } 170 | 171 | func (oc OpenAIClient) CompletionStream(ctx context.Context, request client.CompletionRequest, onData func(response *client.CompletionResponse)) error { 172 | return oc.CompletionStreamWithEngine(ctx, oc.DefaultEngine, request, onData) 173 | } 174 | 175 | func (oc OpenAIClient) CreateCompletionWithEngine(ctx context.Context, _ string, request client.CompletionRequest) (*client.CompletionResponse, error) { 176 | req, err := oc.NewRequestBuilder(ctx, http.MethodPost, client.OpenAICompletionEndPoint, request) 177 | if err != nil { 178 | return nil, err 179 | } 180 | resp, err := oc.ExecuteRequest(req) 181 | if err != nil { 182 | return nil, err 183 | } 184 | output := new(client.CompletionResponse) 185 | if err := oc.getResponseObject(resp, output); err != nil { 186 | return nil, err 187 | } 188 | return output, nil 189 | } 190 | 191 | func (oc OpenAIClient) CompletionStreamWithEngine(ctx context.Context, engine string, request client.CompletionRequest, onData func(response *client.CompletionResponse)) error { 192 | var dataPrefix = string([]byte("data: ")) 193 | var doneSequence = string([]byte("[DONE]")) 194 | 195 | if !*request.Stream { 196 | return errors.New("stream option is false") 197 | } 198 | 199 | req, err := oc.NewRequestBuilder(ctx, http.MethodPost, client.OpenAICompletionEndPoint, request) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | resp, err := oc.ExecuteRequest(req) 205 | if err != nil { 206 | return err 207 | } 208 | 209 | // Create a new channel to handle errors 210 | errCh := make(chan error) 211 | defer func(Body io.ReadCloser) { 212 | err := Body.Close() 213 | if err != nil { 214 | logrus.WithFields(logrus.Fields{ 215 | "error": err, 216 | }).Error("failed to close response body") 217 | // Send the error to the channel 218 | errCh <- err 219 | } 220 | // Close the channel 221 | close(errCh) 222 | }(resp.Body) 223 | 224 | // Create a new scanner to read the response body 225 | scanner := bufio.NewScanner(resp.Body) 226 | 227 | for scanner.Scan() { 228 | // Moved the select statement inside the for loop to handle errors and context cancellation at every iteration. 229 | select { 230 | case <-ctx.Done(): 231 | return ctx.Err() 232 | case err := <-errCh: 233 | return err 234 | default: 235 | line := scanner.Text() 236 | if !strings.HasPrefix(line, dataPrefix) { 237 | continue 238 | } 239 | line = strings.TrimPrefix(line, dataPrefix) 240 | if strings.HasPrefix(line, doneSequence) { 241 | break 242 | } 243 | output := new(client.CompletionResponse) 244 | if err := json.Unmarshal([]byte(line), output); err != nil { 245 | return errors.New("invalid json stream data: " + err.Error()) 246 | } 247 | onData(output) 248 | } 249 | } 250 | 251 | if err := scanner.Err(); err != nil { 252 | logrus.WithFields(logrus.Fields{ 253 | "error": err, 254 | }).Error("failed to scan response body") 255 | } 256 | 257 | return nil 258 | } 259 | 260 | func (oc OpenAIClient) Edits(ctx context.Context, request client.EditsRequest) (*client.EditsResponse, error) { 261 | //TODO implement me 262 | panic("implement me") 263 | } 264 | 265 | func (oc OpenAIClient) Embeddings(ctx context.Context, request client.EmbeddingsRequest) (*client.EmbeddingsResponse, error) { 266 | //TODO implement me 267 | panic("implement me") 268 | } 269 | 270 | func (oc OpenAIClient) GetAccessToken() (string, error) { 271 | if oc.AccessToken == "" { 272 | return "", errors.New("access token is empty") 273 | } 274 | return oc.AccessToken, nil 275 | } 276 | -------------------------------------------------------------------------------- /internal/pkg/client/interface.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "chatgpt-service/pkg/client" 5 | "context" 6 | "github.com/labstack/echo/v4" 7 | ) 8 | 9 | type EchoHttpMethodFunc func(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route 10 | 11 | // OpenAIClientInterface is an API client to communicate with the OpenAI gpt-3 APIs 12 | // https://github.com/PullRequestInc/go-gpt3/blob/283ab6b3e423c5567217fbe4e49950614ddd04c9/gpt3.go 13 | type OpenAIClientInterface interface { 14 | // ListModels Lists the currently available models 15 | // and provides basic information about each one such as the owner and availability. 16 | // curl https://api.openai.com/v1/models -H 'Authorization: Bearer YOUR_API_KEY' 17 | ListModels(ctx context.Context) (*client.ListModelsResponse, error) 18 | 19 | // RetrieveModel retrieves a client instance, providing basic information about the client such 20 | // as the owner and availability. 21 | RetrieveModel(ctx context.Context, engine string) (*client.ModelObject, error) 22 | 23 | // Completion creates a completion with the default client. This is the main endpoint of the API 24 | // which auto-completes based on the given prompt. 25 | CreateCompletion(ctx context.Context, request client.CompletionRequest) (*client.CompletionResponse, error) 26 | 27 | // CompletionStream creates a completion with the default client and streams the results through 28 | // multiple calls to onData. 29 | CompletionStream(ctx context.Context, request client.CompletionRequest, onData func(response *client.CompletionResponse)) error 30 | 31 | // CompletionWithEngine is the same as Completion except allows overriding the default client on the client 32 | CreateCompletionWithEngine(ctx context.Context, engine string, request client.CompletionRequest) (*client.CompletionResponse, error) 33 | 34 | // CompletionStreamWithEngine is the same as CompletionStream except allows overriding the default client on the client 35 | CompletionStreamWithEngine(ctx context.Context, engine string, request client.CompletionRequest, onData func(response *client.CompletionResponse)) error 36 | 37 | // Given a prompt and an instruction, the client will return an edited version of the prompt. 38 | Edits(ctx context.Context, request client.EditsRequest) (*client.EditsResponse, error) 39 | 40 | // Returns an embedding using the provided request. 41 | Embeddings(ctx context.Context, request client.EmbeddingsRequest) (*client.EmbeddingsResponse, error) 42 | } 43 | -------------------------------------------------------------------------------- /internal/pkg/client/option.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // ClientOption are options that can be passed when creating a new client 4 | type ClientOption func(*OpenAIClient) error 5 | 6 | // WithOrg is a client option that allows you to override the organization ID 7 | func WithOrg(id string) ClientOption { 8 | return func(c *OpenAIClient) error { 9 | c.IdOrg = id 10 | return nil 11 | } 12 | } 13 | 14 | // WithDefaultEngine is a client option that allows you to override the default client of the client 15 | func WithDefaultEngine(engine string) ClientOption { 16 | return func(c *OpenAIClient) error { 17 | c.DefaultEngine = engine 18 | return nil 19 | } 20 | } 21 | 22 | // WithUserAgent is a client option that allows you to override the default user agent of the client 23 | func WithUserAgent(userAgent string) ClientOption { 24 | return func(c *OpenAIClient) error { 25 | c.UserAgent = userAgent 26 | return nil 27 | } 28 | } 29 | 30 | // WithBaseURL is a client option that allows you to override the default base url of the client. 31 | // The default base url is "https://api.openai.com/v1" 32 | func WithBaseURL(baseURL string) ClientOption { 33 | return func(c *OpenAIClient) error { 34 | c.BaseURL = baseURL 35 | return nil 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/pkg/constants/constants.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | import "fmt" 4 | 5 | // GPT 3.5 model 6 | const ( 7 | Gpt35EngineTurbo = "gpt-3.5-turbo" 8 | Gpt35EngineTurbo0301 = "gpt-3.5-turbo-0301" 9 | TextDavinci003Engine = "text-davinci-003" 10 | ) 11 | 12 | // Engine Types 13 | const ( 14 | TextAda001Engine = "text-ada-001" 15 | TextBabbage001Engine = "text-babbage-001" 16 | TextCurie001Engine = "text-curie-001" 17 | TextDavinci001Engine = "text-davinci-001" 18 | TextDavinci002Engine = "text-davinci-002" 19 | AdaEngine = "ada" 20 | BabbageEngine = "babbage" 21 | CurieEngine = "curie" 22 | DavinciEngine = "davinci" 23 | ) 24 | 25 | const ( 26 | DefaultEngine = Gpt35EngineTurbo 27 | DefaultRoleName = "User" 28 | // DefaultClientName TODO: load UserName to environment variable 29 | DefaultClientName = "mentat-analysis" 30 | ) 31 | 32 | type EmbeddingEngine string 33 | 34 | // TODO: add more engines released on the late 2022 35 | const ( 36 | TextSimilarityAda001 = "text-similarity-ada-001" 37 | TextSimilarityBabbage001 = "text-similarity-babbage-001" 38 | TextSimilarityCurie001 = "text-similarity-curie-001" 39 | TextSimilarityDavinci001 = "text-similarity-davinci-001" 40 | TextSearchAdaDoc001 = "text-search-ada-doc-001" 41 | TextSearchAdaQuery001 = "text-search-ada-query-001" 42 | TextSearchBabbageDoc001 = "text-search-babbage-doc-001" 43 | TextSearchBabbageQuery001 = "text-search-babbage-query-001" 44 | TextSearchCurieDoc001 = "text-search-curie-doc-001" 45 | TextSearchCurieQuery001 = "text-search-curie-query-001" 46 | TextSearchDavinciDoc001 = "text-search-davinci-doc-001" 47 | TextSearchDavinciQuery001 = "text-search-davinci-query-001" 48 | CodeSearchAdaCode001 = "code-search-ada-code-001" 49 | CodeSearchAdaText001 = "code-search-ada-text-001" 50 | CodeSearchBabbageCode001 = "code-search-babbage-code-001" 51 | CodeSearchBabbageText001 = "code-search-babbage-text-001" 52 | TextEmbeddingAda002 = "text-embedding-ada-002" 53 | ) 54 | 55 | const ( 56 | DefaultBaseURL = "https://api.openai.com/v1" 57 | DefaultUserAgent = "mentat" // go-gpt3 58 | DefaultTimeoutSeconds = 300 59 | ) 60 | 61 | func getEngineURL(engine string) string { 62 | return fmt.Sprintf("%s/engines/%s/completions", DefaultBaseURL, engine) 63 | } 64 | -------------------------------------------------------------------------------- /internal/pkg/process/postprocessor.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func TwoDimensionalInterfacesToJSONString(interfaces [][]interface{}) (string, error) { 10 | // Create a slice of maps that corresponds to the two-dimensional slice of interface{} values 11 | var data []map[string]interface{} 12 | for _, row := range interfaces { 13 | item := make(map[string]interface{}) 14 | for i, value := range row { 15 | key := fmt.Sprintf("results%d", i+1) 16 | item[key] = value 17 | } 18 | data = append(data, item) 19 | } 20 | 21 | // Encode the slice of maps as a JSON-encoded byte slice 22 | resultJSON, err := json.Marshal(data) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | return string(resultJSON), nil 28 | } 29 | 30 | func TwoDimensionalInterfacesToJSONBytes(interfaces [][]interface{}) ([]byte, error) { 31 | // Create a slice of maps that corresponds to the two-dimensional slice of interface{} values 32 | var data []map[string]interface{} 33 | for _, row := range interfaces { 34 | item := make(map[string]interface{}) 35 | for i, value := range row { 36 | key := fmt.Sprintf("results%d", i+1) 37 | item[key] = value 38 | } 39 | data = append(data, item) 40 | } 41 | 42 | // Encode the slice of maps as a JSON-encoded byte slice 43 | resultJSON, err := json.Marshal(data) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return resultJSON, nil 49 | } 50 | 51 | func ConvertNewLineToSpace(input string) string { 52 | return strings.ReplaceAll(input, "\n", " ") 53 | } 54 | -------------------------------------------------------------------------------- /internal/pkg/store/base.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/go-pg/pg" 5 | ) 6 | 7 | type Database struct { 8 | *pg.DB 9 | } 10 | 11 | func (db *Database) Error() string { 12 | //TODO implement me 13 | panic("implement me") 14 | } 15 | 16 | func (db *Database) Heartbeat() error { 17 | _, err := db.Exec("SELECT 1") 18 | if err != nil { 19 | return err 20 | } 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /pkg/client/chat.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "chatgpt-service/internal/pkg/constants" 5 | "chatgpt-service/internal/pkg/process" 6 | ) 7 | 8 | const OpenAIChatCompletionEndPoint = "/chat/completions" 9 | 10 | type ChatCompletionRequest struct { 11 | Model *string `json:"model"` 12 | Messages []Message `json:"messages"` 13 | MaxTokens *int `json:"max_tokens"` 14 | Temperature *int `json:"temperature"` 15 | Stream *bool `json:"stream"` 16 | TopP *int `json:"top_p"` 17 | N *int `json:"n"` 18 | Stop interface{} `json:"stop"` 19 | PresencePenalty float32 `json:"presence_penalty"` 20 | FrequencyPenalty float32 `json:"frequency_penalty"` 21 | User *string `json:"user"` 22 | OriginalPrompt *string `json:"original_prompt"` 23 | } 24 | 25 | type Message struct { 26 | Role string `json:"role"` 27 | Content string `json:"content"` 28 | } 29 | 30 | func NewChatCompletionRequest(content string, maxTokens int, model *string, stream *bool, role *string) (*ChatCompletionRequest, error) { 31 | promptRaw := GPTPromptRequest{ 32 | Prompt: content, 33 | } 34 | prompt, err := CreatePrompt(promptRaw) 35 | if err != nil { 36 | return nil, err 37 | } 38 | promptInString := prompt.String() 39 | 40 | if role == nil { 41 | role = new(string) 42 | *role = "user" 43 | } 44 | cr := &ChatCompletionRequest{ 45 | Model: new(string), 46 | Messages: []Message{ 47 | { 48 | Role: *role, 49 | Content: promptInString, 50 | }, 51 | }, 52 | MaxTokens: new(int), 53 | Temperature: new(int), 54 | Stream: new(bool), 55 | TopP: new(int), 56 | N: new(int), 57 | Stop: nil, 58 | PresencePenalty: 0.0, 59 | FrequencyPenalty: 0.0, 60 | User: new(string), 61 | OriginalPrompt: new(string), 62 | } 63 | if model != nil { 64 | cr.Model = model 65 | } else { 66 | *cr.Model = constants.Gpt35EngineTurbo 67 | } 68 | if stream != nil { 69 | cr.Stream = stream 70 | } else { 71 | *cr.Stream = false 72 | } 73 | *cr.MaxTokens = maxTokens 74 | *cr.Temperature = 0.0 75 | *cr.TopP = 1.0 76 | *cr.N = 1 77 | *cr.User = constants.DefaultClientName 78 | *cr.OriginalPrompt = content 79 | 80 | return cr, nil 81 | } 82 | 83 | type ChatCompletionResponse struct { 84 | Id string `json:"id"` 85 | Object string `json:"object"` 86 | Created int `json:"created"` 87 | Model string `json:"model"` 88 | Usage Usage `json:"usage"` 89 | Choices []Choice `json:"choices"` 90 | } 91 | 92 | type Usage struct { 93 | PromptTokens int `json:"prompt_tokens"` 94 | CompletionTokens int `json:"completion_tokens"` 95 | TotalTokens int `json:"total_tokens"` 96 | } 97 | 98 | type Choice struct { 99 | Message struct { 100 | Role string `json:"role"` 101 | Content string `json:"content"` 102 | } `json:"message"` 103 | FinishReason string `json:"finish_reason"` 104 | Index int `json:"index"` 105 | } 106 | 107 | // GetContent TODO: temporarily only return the first choice 108 | func (c *ChatCompletionRequest) GetContent() string { 109 | return process.ConvertNewLineToSpace(c.Messages[0].Content) 110 | } 111 | 112 | // GetContent TODO: temporarily only return the first choice 113 | func (c *ChatCompletionResponse) GetContent() string { 114 | return process.ConvertNewLineToSpace(c.Choices[0].Message.Content) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/client/chatgpt.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | type GPTPromptRequest struct { 4 | Prompt string `json:"prompt"` 5 | } 6 | 7 | type GPTPromptSuccessfulResponse struct { 8 | Result string `json:"result"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/client/completion.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "chatgpt-service/internal/pkg/constants" 5 | ) 6 | 7 | const OpenAICompletionEndPoint = "/completions" 8 | const CreateCompletionEndpoint = OpenAICompletionEndPoint + "/create" 9 | 10 | // CompletionRequest is a request for the completions API 11 | // https://github.com/PullRequestInc/go-gpt3/blob/283ab6b3e423c5567217fbe4e49950614ddd04c9/models.go#L36 12 | type CompletionRequest struct { 13 | Model *string `json:"model"` 14 | // A list of string prompts to use. 15 | // TODO there are other prompt types here for using token integers that we could add support for. 16 | Prompt string `json:"prompt"` 17 | // How many tokens to complete up to. Max of 512 18 | MaxTokens *int `json:"max_tokens,omitempty"` 19 | // Sampling temperature to use 20 | Temperature *float32 `json:"temperature,omitempty"` 21 | // Whether to stream back results or not. Don't set this value in the request yourself 22 | // as it will be overriden depending on if you use CompletionStream or CreateCompletion methods. 23 | Stream *bool `json:"stream,omitempty"` 24 | // Alternative to temperature for nucleus sampling 25 | TopP *float32 `json:"top_p,omitempty"` 26 | // How many choice to create for each prompt 27 | N *int `json:"n"` 28 | // Include the probabilities of most likely tokens 29 | LogProbs *int `json:"logprobs"` 30 | // Echo back the prompt in addition to the completion 31 | Echo bool `json:"echo"` 32 | User *string `json:"user"` 33 | // Up to 4 sequences where the API will stop generating tokens. Response will not contain the stop sequence. 34 | Stop []string `json:"stop,omitempty"` 35 | // PresencePenalty number between 0 and 1 that penalizes tokens that have already appeared in the text so far. 36 | PresencePenalty float32 `json:"presence_penalty"` 37 | // FrequencyPenalty number between 0 and 1 that penalizes tokens on existing frequency in the text so far. 38 | FrequencyPenalty float32 `json:"frequency_penalty"` 39 | } 40 | 41 | func NewCompletionRequest(prompt string, maxTokens int, model *string, stream *bool, temperature *float32) *CompletionRequest { 42 | cr := &CompletionRequest{ 43 | Model: new(string), 44 | Prompt: prompt, 45 | MaxTokens: new(int), 46 | Temperature: new(float32), 47 | Stream: new(bool), 48 | TopP: new(float32), 49 | N: new(int), 50 | LogProbs: new(int), 51 | Echo: false, 52 | Stop: nil, 53 | PresencePenalty: 0.0, 54 | FrequencyPenalty: 0.0, 55 | User: new(string), 56 | } 57 | if model != nil { 58 | cr.Model = model 59 | } else { 60 | *cr.Model = constants.TextDavinci003Engine 61 | } 62 | if stream != nil { 63 | cr.Stream = stream 64 | } else { 65 | *cr.Stream = false 66 | } 67 | if temperature != nil { 68 | cr.Temperature = temperature 69 | } else { 70 | *cr.Temperature = 0.0 71 | } 72 | *cr.MaxTokens = maxTokens 73 | *cr.TopP = 1.0 74 | *cr.N = 1 75 | *cr.LogProbs = 0 76 | *cr.User = constants.DefaultClientName 77 | return cr 78 | } 79 | 80 | // LogprobResult represents logprob result of Choice 81 | type LogprobResult struct { 82 | Tokens []string `json:"tokens"` 83 | TokenLogprobs []float32 `json:"token_logprobs"` 84 | TopLogprobs []map[string]float32 `json:"top_logprobs"` 85 | TextOffset []int `json:"text_offset"` 86 | } 87 | 88 | type CompletionResponseChoice struct { 89 | Text string `json:"text"` 90 | Index int `json:"index"` 91 | LogProbs LogprobResult `json:"logprobs"` 92 | FinishReason string `json:"finish_reason"` 93 | } 94 | 95 | type CompletionResponseUsage struct { 96 | PromptTokens int `json:"prompt_tokens"` 97 | CompletionTokens int `json:"completion_tokens"` 98 | TotalTokens int `json:"total_tokens"` 99 | } 100 | 101 | // CompletionResponse is the full response from a request to the completions API 102 | type CompletionResponse struct { 103 | ID string `json:"id"` 104 | Object string `json:"object"` 105 | Created int `json:"created"` 106 | Model string `json:"model"` 107 | Choices []CompletionResponseChoice `json:"choices"` 108 | Usage CompletionResponseUsage `json:"usage"` 109 | } 110 | -------------------------------------------------------------------------------- /pkg/client/edit.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // EditsRequest is a request for the edits API 4 | type EditsRequest struct { 5 | // ID of the model to use. You can use the List models API to see all of your available models, or see our Model overview for descriptions of them. 6 | Model string `json:"model"` 7 | // The input text to use as a starting point for the edit. 8 | Input string `json:"input"` 9 | // The instruction that tells the model how to edit the prompt. 10 | Instruction string `json:"instruction"` 11 | // Sampling temperature to use 12 | Temperature *float32 `json:"temperature,omitempty"` 13 | // Alternative to temperature for nucleus sampling 14 | TopP *float32 `json:"top_p,omitempty"` 15 | // How many edits to generate for the input and instruction. Defaults to 1 16 | N *int `json:"n"` 17 | } 18 | 19 | // EditsResponseChoice is one of the choices returned in the response to the Edits API 20 | type EditsResponseChoice struct { 21 | Text string `json:"text"` 22 | Index int `json:"index"` 23 | } 24 | 25 | // EditsResponseUsage is a structure used in the response from a request to the edits API 26 | type EditsResponseUsage struct { 27 | PromptTokens int `json:"prompt_tokens"` 28 | CompletionTokens int `json:"completion_tokens"` 29 | TotalTokens int `json:"total_tokens"` 30 | } 31 | 32 | // EditsResponse is the full response from a request to the edits API 33 | type EditsResponse struct { 34 | Object string `json:"object"` 35 | Created int `json:"created"` 36 | Choices []EditsResponseChoice `json:"choices"` 37 | Usage EditsResponseUsage `json:"usage"` 38 | } 39 | -------------------------------------------------------------------------------- /pkg/client/embedding.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // EmbeddingsRequest is a request for the Embeddings API 4 | type EmbeddingsRequest struct { 5 | // Input text to get embeddings for, encoded as a string or array of tokens. To get embeddings 6 | // for multiple inputs in a single request, pass an array of strings or array of token arrays. 7 | // Each input must not exceed 2048 tokens in length. 8 | Input []string `json:"input"` 9 | // ID of the model to use 10 | Model string `json:"model"` 11 | // The request user is an optional parameter meant to be used to trace abusive requests 12 | // back to the originating user. OpenAI states: 13 | // "The [user] IDs should be a string that uniquely identifies each user. We recommend hashing 14 | // their username or email address, in order to avoid sending us any identifying information. 15 | // If you offer a preview of your product to non-logged in users, you can send a session ID 16 | // instead." 17 | User string `json:"user,omitempty"` 18 | } 19 | 20 | // The inner result of a create embeddings request, containing the embeddings for a single input. 21 | type EmbeddingsResult struct { 22 | // The type of object returned (e.g., "list", "object") 23 | Object string `json:"object"` 24 | // The embedding data for the input 25 | Embedding []float64 `json:"embedding"` 26 | Index int `json:"index"` 27 | } 28 | 29 | // The usage stats for an embeddings response 30 | type EmbeddingsUsage struct { 31 | // The number of tokens used by the prompt 32 | PromptTokens int `json:"prompt_tokens"` 33 | // The total tokens used 34 | TotalTokens int `json:"total_tokens"` 35 | } 36 | 37 | // EmbeddingsResponse is the response from a create embeddings request. 38 | // https://beta.openai.com/docs/api-reference/embeddings/create 39 | type EmbeddingsResponse struct { 40 | Object string `json:"object"` 41 | Data []EmbeddingsResult `json:"data"` 42 | Usage EmbeddingsUsage `json:"usage"` 43 | } 44 | -------------------------------------------------------------------------------- /pkg/client/model.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // ModelObject contained in an client reponse 4 | /* 5 | curl https://api.openai.com/v1/models \ 6 | -H 'Authorization: Bearer YOUR_API_KEY' 7 | 8 | { 9 | "object": "list", 10 | "data": [ 11 | { 12 | "id": "babbage", 13 | "object": "client", 14 | "created": 1649358449, 15 | "owned_by": "openai", 16 | "permission": [ 17 | { 18 | "id": "modelperm-49FUp5v084tBB49tC4z8LPH5", 19 | "object": "model_permission", 20 | "created": 1669085501, 21 | "allow_create_engine": false, 22 | "allow_sampling": true, 23 | "allow_logprobs": true, 24 | "allow_search_indices": false, 25 | "allow_view": true, 26 | "allow_fine_tuning": false, 27 | "organization": "*", 28 | "group": null, 29 | "is_blocking": false 30 | } 31 | ], 32 | "root": "babbage", 33 | "parent": null 34 | }, 35 | { 36 | "id": "ada", 37 | "object": "client", 38 | "created": 1649357491, 39 | "owned_by": "openai", 40 | "permission": [ 41 | { 42 | "id": "modelperm-xTOEYvDZGN7UDnQ65VpzRRHz", 43 | "object": "model_permission", 44 | "created": 1669087301, 45 | "allow_create_engine": false, 46 | "allow_sampling": true, 47 | "allow_logprobs": true, 48 | "allow_search_indices": false, 49 | "allow_view": true, 50 | "allow_fine_tuning": false, 51 | "organization": "*", 52 | "group": null, 53 | "is_blocking": false 54 | } 55 | ], 56 | "root": "ada", 57 | "parent": null 58 | }, 59 | { 60 | "id": "davinci", 61 | "object": "client", 62 | "created": 1649359874, 63 | "owned_by": "openai", 64 | "permission": [ 65 | { 66 | "id": "modelperm-U6ZwlyAd0LyMk4rcMdz33Yc3", 67 | "object": "model_permission", 68 | "created": 1669066355, 69 | "allow_create_engine": false, 70 | "allow_sampling": true, 71 | "allow_logprobs": true, 72 | "allow_search_indices": false, 73 | "allow_view": true, 74 | "allow_fine_tuning": false, 75 | "organization": "*", 76 | "group": null, 77 | "is_blocking": false 78 | } 79 | ], 80 | "root": "davinci", 81 | "parent": null 82 | }, 83 | 84 | */ 85 | 86 | const ModelEndPoint = "/models" 87 | const GetAllModels = ModelEndPoint + "/all" 88 | const RetrieveModels = ModelEndPoint + "/retrieve" 89 | const ModelIdParamKey = "model_id" 90 | 91 | type PermissionInModelObject struct { 92 | ID string `json:"id"` 93 | Object string `json:"object"` 94 | Created int `json:"created"` 95 | AllowCreateEngine bool `json:"allow_create_engine"` 96 | AllowSampling bool `json:"allow_sampling"` 97 | AllowLogprobs bool `json:"allow_logprobs"` 98 | AllowSearchIndices bool `json:"allow_search_indices"` 99 | AllowView bool `json:"allow_view"` 100 | AllowFineTuning bool `json:"allow_fine_tuning"` 101 | Organization string `json:"organization"` 102 | Group interface{} `json:"group"` 103 | IsBlocking bool `json:"is_blocking"` 104 | } 105 | 106 | type ModelObject struct { 107 | ID string `json:"id"` 108 | Object string `json:"object"` 109 | OwnedBy string `json:"owned_by"` 110 | Permission []PermissionInModelObject `json:"permission"` 111 | Root string `json:"root"` 112 | Parent interface{} `json:"parent"` 113 | } 114 | 115 | type ListModelsResponse struct { 116 | Data []ModelObject `json:"data"` 117 | Object string `json:"object"` 118 | } 119 | -------------------------------------------------------------------------------- /pkg/client/prompt.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "chatgpt-service/internal/pkg/engine" 5 | "fmt" 6 | ) 7 | 8 | type Prompt struct { 9 | message string 10 | schema engine.Schema 11 | } 12 | 13 | func (p *Prompt) String() string { 14 | message := p.message + "\n" + p.schema.String() 15 | return message 16 | } 17 | 18 | // TODO: only single prompt at the moment 19 | func CreatePrompt(promptRaw GPTPromptRequest) (*Prompt, error) { 20 | prompt := promptRaw.Prompt 21 | message := "I want to you to act like the expert who are good at writing SQL query." + "\n" 22 | message += fmt.Sprintf("Given the table below, please write the SQL query that can get the info about %s", prompt) + "\n" 23 | message += "GIVE A SQL QUERY ONLY **without** any further responses/explanations." + "\n" 24 | message += "If you are not 100% certain to get the valid information from the database table below, respond \"NO\" without further responses/explanations.\n" 25 | message += "To query the database, you need to use the database information below" 26 | schema := engine.CreateEthereumCoreTransactionSchema() 27 | return &Prompt{message: message, schema: *schema}, nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/client/reversed_python/Chatbot.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple wrapper for the official ChatGPT API 3 | Reference: https://github.com/acheong08/ChatGPT/blob/84c7cfd9e4/src/revChatGPT/V3.py 4 | """ 5 | import argparse 6 | import json 7 | import os 8 | import sys 9 | from importlib.resources import path 10 | from pathlib import Path 11 | from typing import AsyncGenerator 12 | from typing import NoReturn 13 | 14 | import httpx 15 | import requests 16 | import tiktoken 17 | 18 | from . import typings as t 19 | from .utils import create_completer 20 | from .utils import create_keybindings 21 | from .utils import create_session 22 | from .utils import get_filtered_keys_from_object 23 | from .utils import get_input 24 | 25 | 26 | class Chatbot: 27 | """ 28 | Official ChatGPT API 29 | """ 30 | 31 | def __init__( 32 | self, 33 | api_key: str, 34 | engine: str = os.environ.get("GPT_ENGINE") or "gpt-3.5-turbo", 35 | proxy: str = None, 36 | timeout: float = None, 37 | max_tokens: int = None, 38 | temperature: float = 0.5, 39 | top_p: float = 1.0, 40 | presence_penalty: float = 0.0, 41 | frequency_penalty: float = 0.0, 42 | reply_count: int = 1, 43 | system_prompt: str = "You are ChatGPT, a large language model trained by OpenAI. Respond conversationally", 44 | ) -> None: 45 | """ 46 | Initialize Chatbot with API key (from https://platform.openai.com/account/api-keys) 47 | """ 48 | self.engine: str = engine 49 | self.api_key: str = api_key 50 | self.system_prompt: str = system_prompt 51 | self.max_tokens: int = max_tokens or ( 52 | 31000 if engine == "gpt-4-32k" else 7000 if engine == "gpt-4" else 4000 53 | ) 54 | self.truncate_limit: int = ( 55 | 30500 if engine == "gpt-4-32k" else 6500 if engine == "gpt-4" else 3500 56 | ) 57 | self.temperature: float = temperature 58 | self.top_p: float = top_p 59 | self.presence_penalty: float = presence_penalty 60 | self.frequency_penalty: float = frequency_penalty 61 | self.reply_count: int = reply_count 62 | self.timeout: float = timeout 63 | self.proxy = proxy 64 | self.session = requests.Session() 65 | self.session.proxies.update( 66 | { 67 | "http": proxy, 68 | "https": proxy, 69 | }, 70 | ) 71 | if proxy := ( 72 | proxy or os.environ.get("all_proxy") or os.environ.get("ALL_PROXY") or None 73 | ): 74 | if "socks5h" not in proxy: 75 | self.aclient = httpx.AsyncClient( 76 | follow_redirects=True, 77 | proxies=proxy, 78 | timeout=timeout, 79 | ) 80 | else: 81 | self.aclient = httpx.AsyncClient( 82 | follow_redirects=True, 83 | proxies=proxy, 84 | timeout=timeout, 85 | ) 86 | 87 | self.conversation: dict[str, list[dict]] = { 88 | "default": [ 89 | { 90 | "role": "system", 91 | "content": system_prompt, 92 | }, 93 | ], 94 | } 95 | 96 | if self.get_token_count("default") > self.max_tokens: 97 | raise t.ActionRefuseError("System prompt is too long") 98 | 99 | def add_to_conversation( 100 | self, 101 | message: str, 102 | role: str, 103 | convo_id: str = "default", 104 | ) -> None: 105 | """ 106 | Add a message to the conversation 107 | """ 108 | self.conversation[convo_id].append({"role": role, "content": message}) 109 | 110 | def __truncate_conversation(self, convo_id: str = "default") -> None: 111 | """ 112 | Truncate the conversation 113 | """ 114 | while True: 115 | if ( 116 | self.get_token_count(convo_id) > self.truncate_limit 117 | and len(self.conversation[convo_id]) > 1 118 | ): 119 | # Don't remove the first message 120 | self.conversation[convo_id].pop(1) 121 | else: 122 | break 123 | 124 | # https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb 125 | def get_token_count(self, convo_id: str = "default") -> int: 126 | """ 127 | Get token count 128 | """ 129 | if self.engine not in [ 130 | "gpt-3.5-turbo", 131 | "gpt-3.5-turbo-0301", 132 | "gpt-4", 133 | "gpt-4-0314", 134 | "gpt-4-32k", 135 | "gpt-4-32k-0314", 136 | ]: 137 | raise NotImplementedError("Unsupported engine {self.engine}") 138 | 139 | tiktoken.model.MODEL_TO_ENCODING["gpt-4"] = "cl100k_base" 140 | 141 | encoding = tiktoken.encoding_for_model(self.engine) 142 | 143 | num_tokens = 0 144 | for message in self.conversation[convo_id]: 145 | # every message follows {role/name}\n{content}\n 146 | num_tokens += 5 147 | for key, value in message.items(): 148 | num_tokens += len(encoding.encode(value)) 149 | if key == "name": # if there's a name, the role is omitted 150 | num_tokens += 5 # role is always required and always 1 token 151 | num_tokens += 5 # every reply is primed with assistant 152 | return num_tokens 153 | 154 | def get_max_tokens(self, convo_id: str) -> int: 155 | """ 156 | Get max tokens 157 | """ 158 | return self.max_tokens - self.get_token_count(convo_id) 159 | 160 | def ask_stream( 161 | self, 162 | prompt: str, 163 | role: str = "user", 164 | convo_id: str = "default", 165 | **kwargs, 166 | ): 167 | """ 168 | Ask a question 169 | """ 170 | # Make conversation if it doesn't exist 171 | if convo_id not in self.conversation: 172 | self.reset(convo_id=convo_id, system_prompt=self.system_prompt) 173 | self.add_to_conversation(prompt, "user", convo_id=convo_id) 174 | self.__truncate_conversation(convo_id=convo_id) 175 | # Get response 176 | response = self.session.post( 177 | os.environ.get("API_URL") or "https://api.openai.com/v1/chat/completions", 178 | headers={"Authorization": f"Bearer {kwargs.get('api_key', self.api_key)}"}, 179 | json={ 180 | "model": self.engine, 181 | "messages": self.conversation[convo_id], 182 | "stream": True, 183 | # kwargs 184 | "temperature": kwargs.get("temperature", self.temperature), 185 | "top_p": kwargs.get("top_p", self.top_p), 186 | "presence_penalty": kwargs.get( 187 | "presence_penalty", 188 | self.presence_penalty, 189 | ), 190 | "frequency_penalty": kwargs.get( 191 | "frequency_penalty", 192 | self.frequency_penalty, 193 | ), 194 | "n": kwargs.get("n", self.reply_count), 195 | "user": role, 196 | "max_tokens": self.get_max_tokens(convo_id=convo_id), 197 | }, 198 | timeout=kwargs.get("timeout", self.timeout), 199 | stream=True, 200 | ) 201 | if response.status_code != 200: 202 | raise t.APIConnectionError( 203 | f"{response.status_code} {response.reason} {response.text}", 204 | ) 205 | response_role: str = None 206 | full_response: str = "" 207 | for line in response.iter_lines(): 208 | if not line: 209 | continue 210 | # Remove "data: " 211 | line = line.decode("utf-8")[6:] 212 | if line == "[DONE]": 213 | break 214 | resp: dict = json.loads(line) 215 | choices = resp.get("choices") 216 | if not choices: 217 | continue 218 | delta = choices[0].get("delta") 219 | if not delta: 220 | continue 221 | if "role" in delta: 222 | response_role = delta["role"] 223 | if "content" in delta: 224 | content = delta["content"] 225 | full_response += content 226 | yield content 227 | self.add_to_conversation(full_response, response_role, convo_id=convo_id) 228 | 229 | async def ask_stream_async( 230 | self, 231 | prompt: str, 232 | role: str = "user", 233 | convo_id: str = "default", 234 | **kwargs, 235 | ) -> AsyncGenerator[str, None]: 236 | """ 237 | Ask a question 238 | """ 239 | # Make conversation if it doesn't exist 240 | if convo_id not in self.conversation: 241 | self.reset(convo_id=convo_id, system_prompt=self.system_prompt) 242 | self.add_to_conversation(prompt, "user", convo_id=convo_id) 243 | self.__truncate_conversation(convo_id=convo_id) 244 | # Get response 245 | async with self.aclient.stream( 246 | "post", 247 | os.environ.get("API_URL") or "https://api.openai.com/v1/chat/completions", 248 | headers={"Authorization": f"Bearer {kwargs.get('api_key', self.api_key)}"}, 249 | json={ 250 | "model": self.engine, 251 | "messages": self.conversation[convo_id], 252 | "stream": True, 253 | # kwargs 254 | "temperature": kwargs.get("temperature", self.temperature), 255 | "top_p": kwargs.get("top_p", self.top_p), 256 | "presence_penalty": kwargs.get( 257 | "presence_penalty", 258 | self.presence_penalty, 259 | ), 260 | "frequency_penalty": kwargs.get( 261 | "frequency_penalty", 262 | self.frequency_penalty, 263 | ), 264 | "n": kwargs.get("n", self.reply_count), 265 | "user": role, 266 | "max_tokens": self.get_max_tokens(convo_id=convo_id), 267 | }, 268 | timeout=kwargs.get("timeout", self.timeout), 269 | ) as response: 270 | if response.status_code != 200: 271 | await response.aread() 272 | raise t.APIConnectionError( 273 | f"{response.status_code} {response.reason_phrase} {response.text}", 274 | ) 275 | 276 | response_role: str = "" 277 | full_response: str = "" 278 | async for line in response.aiter_lines(): 279 | line = line.strip() 280 | if not line: 281 | continue 282 | # Remove "data: " 283 | line = line[6:] 284 | if line == "[DONE]": 285 | break 286 | resp: dict = json.loads(line) 287 | choices = resp.get("choices") 288 | if not choices: 289 | continue 290 | delta: dict[str, str] = choices[0].get("delta") 291 | if not delta: 292 | continue 293 | if "role" in delta: 294 | response_role = delta["role"] 295 | if "content" in delta: 296 | content: str = delta["content"] 297 | full_response += content 298 | yield content 299 | self.add_to_conversation(full_response, response_role, convo_id=convo_id) 300 | 301 | async def ask_async( 302 | self, 303 | prompt: str, 304 | role: str = "user", 305 | convo_id: str = "default", 306 | **kwargs, 307 | ) -> str: 308 | """ 309 | Non-streaming ask 310 | """ 311 | response = self.ask_stream_async( 312 | prompt=prompt, 313 | role=role, 314 | convo_id=convo_id, 315 | **kwargs, 316 | ) 317 | full_response: str = "".join([r async for r in response]) 318 | return full_response 319 | 320 | def ask( 321 | self, 322 | prompt: str, 323 | role: str = "user", 324 | convo_id: str = "default", 325 | **kwargs, 326 | ) -> str: 327 | """ 328 | Non-streaming ask 329 | """ 330 | response = self.ask_stream( 331 | prompt=prompt, 332 | role=role, 333 | convo_id=convo_id, 334 | **kwargs, 335 | ) 336 | full_response: str = "".join(response) 337 | return full_response 338 | 339 | def rollback(self, n: int = 1, convo_id: str = "default") -> None: 340 | """ 341 | Rollback the conversation 342 | """ 343 | for _ in range(n): 344 | self.conversation[convo_id].pop() 345 | 346 | def reset(self, convo_id: str = "default", system_prompt: str = None) -> None: 347 | """ 348 | Reset the conversation 349 | """ 350 | self.conversation[convo_id] = [ 351 | {"role": "system", "content": system_prompt or self.system_prompt}, 352 | ] 353 | 354 | def save(self, file: str, *keys: str) -> None: 355 | """ 356 | Save the Chatbot configuration to a JSON file 357 | """ 358 | with open(file, "w", encoding="utf-8") as f: 359 | data = { 360 | key: self.__dict__[key] 361 | for key in get_filtered_keys_from_object(self, *keys) 362 | } 363 | # saves session.proxies dict as session 364 | # leave this here for compatibility 365 | data["session"] = data["proxy"] 366 | del data["aclient"] 367 | json.dump( 368 | data, 369 | f, 370 | indent=2, 371 | ) 372 | 373 | def load(self, file: str, *keys_: str) -> None: 374 | """ 375 | Load the Chatbot configuration from a JSON file 376 | """ 377 | with open(file, encoding="utf-8") as f: 378 | # load json, if session is in keys, load proxies 379 | loaded_config = json.load(f) 380 | keys = get_filtered_keys_from_object(self, *keys_) 381 | 382 | if ( 383 | "session" in keys 384 | and loaded_config["session"] 385 | or "proxy" in keys 386 | and loaded_config["proxy"] 387 | ): 388 | self.proxy = loaded_config.get("session", loaded_config["proxy"]) 389 | self.session = httpx.Client( 390 | follow_redirects=True, 391 | proxies=self.proxy, 392 | timeout=self.timeout, 393 | cookies=self.session.cookies, 394 | headers=self.session.headers, 395 | ) 396 | self.aclient = httpx.AsyncClient( 397 | follow_redirects=True, 398 | proxies=self.proxy, 399 | timeout=self.timeout, 400 | cookies=self.session.cookies, 401 | headers=self.session.headers, 402 | ) 403 | if "session" in keys: 404 | keys.remove("session") 405 | if "aclient" in keys: 406 | keys.remove("aclient") 407 | self.__dict__.update({key: loaded_config[key] for key in keys}) 408 | 409 | 410 | class ChatbotCLI(Chatbot): 411 | """ 412 | Command Line Interface for Chatbot 413 | """ 414 | 415 | def print_config(self, convo_id: str = "default") -> None: 416 | """ 417 | Prints the current configuration 418 | """ 419 | print( 420 | f""" 421 | ChatGPT Configuration: 422 | Conversation ID: {convo_id} 423 | Messages: {len(self.conversation[convo_id])} 424 | Tokens used: {( num_tokens := self.get_token_count(convo_id) )} / {self.max_tokens} 425 | Cost: {"${:.5f}".format(( num_tokens / 1000 ) * 0.002)} 426 | Engine: {self.engine} 427 | Temperature: {self.temperature} 428 | Top_p: {self.top_p} 429 | Reply count: {self.reply_count} 430 | """, 431 | ) 432 | 433 | def print_help(self) -> None: 434 | """ 435 | Prints the help message 436 | """ 437 | print( 438 | """ 439 | Commands: 440 | !help Display this message 441 | !rollback n Rollback the conversation by n messages 442 | !save file [keys] Save the Chatbot configuration to a JSON file 443 | !load file [keys] Load the Chatbot configuration from a JSON file 444 | !reset Reset the conversation 445 | !exit Quit chat 446 | 447 | Config Commands: 448 | !config Display the current config 449 | !temperature n Set the temperature to n 450 | !top_p n Set the top_p to n 451 | !reply_count n Set the reply_count to n 452 | !engine engine Sets the chat model to engine 453 | 454 | Examples: 455 | !save c.json Saves all ChatbotGPT class variables to c.json 456 | !save c.json engine top_p Saves only temperature and top_p to c.json 457 | !load c.json not engine Loads all but engine from c.json 458 | !load c.json session Loads session proxies from c.json 459 | 460 | 461 | """, 462 | ) 463 | 464 | def handle_commands(self, prompt: str, convo_id: str = "default") -> bool: 465 | """ 466 | Handle chatbot commands 467 | """ 468 | command, *value = prompt.split(" ") 469 | if command == "!help": 470 | self.print_help() 471 | elif command == "!exit": 472 | sys.exit() 473 | elif command == "!reset": 474 | self.reset(convo_id=convo_id) 475 | print("\nConversation has been reset") 476 | elif command == "!config": 477 | self.print_config(convo_id=convo_id) 478 | elif command == "!rollback": 479 | self.rollback(int(value[0]), convo_id=convo_id) 480 | print(f"\nRolled back by {value[0]} messages") 481 | elif command == "!save": 482 | self.save(*value) 483 | print( 484 | f"Saved {', '.join(value[1:]) if len(value) > 1 else 'all'} keys to {value[0]}", 485 | ) 486 | elif command == "!load": 487 | self.load(*value) 488 | print( 489 | f"Loaded {', '.join(value[1:]) if len(value) > 1 else 'all'} keys from {value[0]}", 490 | ) 491 | elif command == "!temperature": 492 | self.temperature = float(value[0]) 493 | print(f"\nTemperature set to {value[0]}") 494 | elif command == "!top_p": 495 | self.top_p = float(value[0]) 496 | print(f"\nTop_p set to {value[0]}") 497 | elif command == "!reply_count": 498 | self.reply_count = int(value[0]) 499 | print(f"\nReply count set to {value[0]}") 500 | elif command == "!engine": 501 | if len(value) > 0: 502 | self.engine = value[0] 503 | print(f"\nEngine set to {self.engine}") 504 | else: 505 | return False 506 | 507 | return True 508 | 509 | 510 | def main() -> NoReturn: 511 | """ 512 | Main function 513 | """ 514 | print( 515 | """ 516 | ChatGPT - Official ChatGPT API 517 | Repo: github.com/acheong08/ChatGPT 518 | """, 519 | ) 520 | print("Type '!help' to show a full list of commands") 521 | print("Press Esc followed by Enter or Alt+Enter to send a message.\n") 522 | 523 | # Get API key from command line 524 | parser = argparse.ArgumentParser() 525 | parser.add_argument( 526 | "--api_key", 527 | type=str, 528 | required="--config" not in sys.argv, 529 | help="OpenAI API key", 530 | ) 531 | parser.add_argument( 532 | "--temperature", 533 | type=float, 534 | default=0.5, 535 | help="Temperature for response", 536 | ) 537 | parser.add_argument( 538 | "--no_stream", 539 | action="store_true", 540 | help="Disable streaming", 541 | ) 542 | parser.add_argument( 543 | "--base_prompt", 544 | type=str, 545 | default="You are ChatGPT, a large language model trained by OpenAI. Respond conversationally", 546 | help="Base prompt for chatbot", 547 | ) 548 | parser.add_argument( 549 | "--proxy", 550 | type=str, 551 | default=None, 552 | help="Proxy address", 553 | ) 554 | parser.add_argument( 555 | "--top_p", 556 | type=float, 557 | default=1, 558 | help="Top p for response", 559 | ) 560 | parser.add_argument( 561 | "--reply_count", 562 | type=int, 563 | default=1, 564 | help="Number of replies for each prompt", 565 | ) 566 | parser.add_argument( 567 | "--enable_internet", 568 | action="store_true", 569 | help="Allow ChatGPT to search the internet", 570 | ) 571 | parser.add_argument( 572 | "--config", 573 | type=str, 574 | default=False, 575 | help="Path to V3 config json file", 576 | ) 577 | parser.add_argument( 578 | "--submit_key", 579 | type=str, 580 | default=None, 581 | help="Custom submit key for chatbot. For more information on keys, see README", 582 | ) 583 | parser.add_argument( 584 | "--model", 585 | type=str, 586 | default="gpt-3.5-turbo", 587 | choices=["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"], 588 | ) 589 | 590 | args, _ = parser.parse_known_args() 591 | 592 | # Initialize chatbot 593 | if config := args.config or os.environ.get("GPT_CONFIG_PATH"): 594 | chatbot = ChatbotCLI(args.api_key) 595 | try: 596 | chatbot.load(config) 597 | except Exception as err: 598 | print(f"Error: {args.config} could not be loaded") 599 | raise err 600 | else: 601 | chatbot = ChatbotCLI( 602 | api_key=args.api_key, 603 | system_prompt=args.base_prompt, 604 | proxy=args.proxy, 605 | temperature=args.temperature, 606 | top_p=args.top_p, 607 | reply_count=args.reply_count, 608 | engine=args.model, 609 | ) 610 | # Check if internet is enabled 611 | if args.enable_internet: 612 | config = path("revChatGPT", "config").__str__() 613 | chatbot.load(Path(config, "enable_internet.json"), "conversation") 614 | 615 | session = create_session() 616 | completer = create_completer( 617 | [ 618 | "!help", 619 | "!exit", 620 | "!reset", 621 | "!rollback", 622 | "!config", 623 | "!engine", 624 | "!temperture", 625 | "!top_p", 626 | "!reply_count", 627 | "!save", 628 | "!load", 629 | ], 630 | ) 631 | key_bindings = create_keybindings() 632 | if args.submit_key: 633 | key_bindings = create_keybindings(args.submit_key) 634 | # Start chat 635 | while True: 636 | print() 637 | try: 638 | print("User: ") 639 | prompt = get_input( 640 | session=session, 641 | completer=completer, 642 | key_bindings=key_bindings, 643 | ) 644 | except KeyboardInterrupt: 645 | print("\nExiting...") 646 | sys.exit() 647 | if prompt.startswith("!"): 648 | try: 649 | chatbot.handle_commands(prompt) 650 | except Exception as err: 651 | print(f"Error: {err}") 652 | continue 653 | print() 654 | print("ChatGPT: ", flush=True) 655 | if args.enable_internet: 656 | query = chatbot.ask( 657 | f'This is a prompt from a user to a chatbot: "{prompt}". Respond with "none" if it is directed at the chatbot or cannot be answered by an internet search. Otherwise, respond with a possible search query to a search engine. Do not write any additional text. Make it as minimal as possible', 658 | convo_id="search", 659 | temperature=0.0, 660 | ).strip() 661 | print("Searching for: ", query, "") 662 | # Get search results 663 | search_results = '{"results": "No search results"}' 664 | if query != "none": 665 | resp = requests.post( 666 | url="https://ddg-api.herokuapp.com/search", 667 | json={"query": query, "limit": 3}, 668 | timeout=10, 669 | ) 670 | resp.encoding = "utf-8" if resp.encoding is None else resp.encoding 671 | search_results = resp.text 672 | print(json.dumps(json.loads(search_results), indent=4)) 673 | chatbot.add_to_conversation( 674 | f"Search results:{search_results}", 675 | "system", 676 | convo_id="default", 677 | ) 678 | if args.no_stream: 679 | print(chatbot.ask(prompt, "user", convo_id="default")) 680 | else: 681 | for query in chatbot.ask_stream(prompt): 682 | print(query, end="", flush=True) 683 | elif args.no_stream: 684 | print(chatbot.ask(prompt, "user")) 685 | else: 686 | for query in chatbot.ask_stream(prompt): 687 | print(query, end="", flush=True) 688 | print() 689 | 690 | 691 | if __name__ == "__main__": 692 | try: 693 | main() 694 | except Exception as exc: 695 | raise t.CLIError("Command line program unknown error") from exc 696 | except KeyboardInterrupt: 697 | print("\nExiting...") 698 | sys.exit() -------------------------------------------------------------------------------- /pkg/client/reversed_python/ChatbotRunner.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from Chatbot import Chatbot 4 | 5 | 6 | def main(access_token, prompt): 7 | chatbot = Chatbot(config={ 8 | "access_token": access_token 9 | }) 10 | 11 | response = "" 12 | 13 | for data in chatbot.ask(prompt): 14 | response = data["message"] 15 | 16 | print(response) 17 | 18 | return response 19 | 20 | 21 | if __name__ == '__main__': 22 | parser = argparse.ArgumentParser() 23 | parser.add_argument("access_token", help="access token for the chatbot") 24 | parser.add_argument("prompt", help="prompt to pass to the chatbot") 25 | args = parser.parse_args() 26 | 27 | response = main(args.access_token, args.prompt) 28 | -------------------------------------------------------------------------------- /pkg/client/reversed_python/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | httpx 3 | OpenAIAuth -------------------------------------------------------------------------------- /pkg/errors/ApiError.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "fmt" 4 | 5 | // APIError represents an error that occured on an API 6 | type APIError struct { 7 | StatusCode int `json:"status_code"` 8 | Message string `json:"message"` 9 | Type string `json:"type"` 10 | } 11 | 12 | func (e APIError) Error() string { 13 | return fmt.Sprintf("[%d:%s] %s", e.StatusCode, e.Type, e.Message) 14 | } 15 | 16 | type APIErrorResponse struct { 17 | Error APIError `json:"error"` 18 | } 19 | -------------------------------------------------------------------------------- /pkg/errors/LoadConfigError.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "github.com/pkg/errors" 4 | 5 | const LoadConfigErrorString = "failed to load config: environment string is empty" 6 | 7 | func LoadConfigError() error { 8 | return errors.New(LoadConfigErrorString) 9 | } 10 | -------------------------------------------------------------------------------- /test/client_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "chatgpt-service/cmd/setup" 7 | "chatgpt-service/internal/api" 8 | "chatgpt-service/internal/config" 9 | cpkg "chatgpt-service/internal/pkg/client" 10 | "chatgpt-service/internal/pkg/constants" 11 | "chatgpt-service/pkg/client" 12 | "encoding/json" 13 | "fmt" 14 | "github.com/labstack/echo/v4" 15 | "github.com/stretchr/testify/assert" 16 | "io" 17 | "net/http" 18 | "net/http/httptest" 19 | "testing" 20 | ) 21 | 22 | func setupTest(t *testing.T, method string, endpoint string, bodyRaw *[]byte, paramStr *string) (error, echo.Context, *api.Handler) { 23 | cfg, err := config.LoadConfig(config.TestConfigPath, "dev") 24 | if err != nil { 25 | t.Errorf("could not load config: %v", err) 26 | } 27 | db := setup.InitializeDatabase(cfg) 28 | oc, err := setup.NewOpenAIClient(cfg) 29 | if err != nil { 30 | t.Errorf("could not create openai client: %v", err) 31 | } 32 | e := echo.New() 33 | var body io.Reader 34 | if bodyRaw == nil { 35 | body = nil 36 | } else { 37 | body = bytes.NewBuffer(*bodyRaw) 38 | } 39 | if bodyRaw != nil { 40 | reqRaw := httptest.NewRequest(method, endpoint, body) 41 | reqRaw.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 42 | ectx := e.NewContext(reqRaw, httptest.NewRecorder()) 43 | ectx.Set(cpkg.OpenAIClientKey, oc) 44 | hd, err := api.NewHandler(ectx, *cfg, oc, db) 45 | if err != nil { 46 | t.Errorf("could not create handler: %v", err) 47 | } 48 | return nil, ectx, hd 49 | } 50 | // path parameter BindPathParams 51 | if paramStr != nil { 52 | reqRaw := httptest.NewRequest(method, endpoint, nil) 53 | reqRaw.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 54 | ectx := e.NewContext(reqRaw, httptest.NewRecorder()) 55 | ectx.SetParamNames("token") 56 | ectx.SetParamValues(*paramStr) 57 | ectx.Set(cpkg.OpenAIClientKey, oc) 58 | hd, err := api.NewHandler(ectx, *cfg, oc, db) 59 | if err != nil { 60 | t.Errorf("could not create handler: %v", err) 61 | } 62 | return nil, ectx, hd 63 | } 64 | return fmt.Errorf("could not create new http test handler"), nil, nil 65 | } 66 | 67 | func setupTestSSE(t *testing.T, method string, endpoint string, bodyRaw *[]byte) (error, echo.Context, *api.Handler) { 68 | cfg, err := config.LoadConfig(config.TestConfigPath, "dev") 69 | if err != nil { 70 | t.Errorf("could not load config: %v", err) 71 | } 72 | db := setup.InitializeDatabase(cfg) 73 | oc, err := setup.NewOpenAIClient(cfg) 74 | if err != nil { 75 | t.Errorf("could not create openai client: %v", err) 76 | } 77 | e := echo.New() 78 | var body io.Reader 79 | if bodyRaw == nil { 80 | body = nil 81 | } else { 82 | body = bytes.NewBuffer(*bodyRaw) 83 | } 84 | reqRaw := httptest.NewRequest(method, endpoint, body) 85 | reqRaw.Header.Set(echo.HeaderCacheControl, "no-cache") 86 | reqRaw.Header.Set(echo.HeaderAccept, "text/event-stream") 87 | reqRaw.Header.Set(echo.HeaderConnection, "keep-alive") 88 | reqRaw.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 89 | ectx := e.NewContext(reqRaw, httptest.NewRecorder()) 90 | ectx.Set(cpkg.OpenAIClientKey, oc) 91 | hd, err := api.NewHandler(ectx, *cfg, oc, db) 92 | if err != nil { 93 | t.Errorf("could not create handler: %v", err) 94 | } 95 | return nil, ectx, hd 96 | } 97 | 98 | func TestListModels(t *testing.T) { 99 | // given 100 | err, ectx, hd := setupTest(t, http.MethodGet, client.GetAllModels, nil, nil) 101 | if err != nil { 102 | t.Fatalf("could not create handler: %v", err) 103 | } 104 | 105 | // when 106 | err = hd.ListModels(ectx) 107 | 108 | // then 109 | if err != nil { 110 | t.Fatalf("could not list models: %v", err) 111 | } 112 | res := ectx.Response() 113 | if res.Status != http.StatusOK { 114 | t.Fatalf("expected status OK but got %v", res.Status) 115 | } 116 | body := res.Writer.(*httptest.ResponseRecorder).Body 117 | var listModelsResponse client.ListModelsResponse 118 | if err = json.Unmarshal(body.Bytes(), &listModelsResponse); err != nil { 119 | t.Fatalf("could not unmarshal response: %v", err) 120 | } 121 | if len(listModelsResponse.Data) == 0 { 122 | t.Fatalf("expected at least one model but got %v", len(listModelsResponse.Data)) 123 | } 124 | } 125 | 126 | func TestRetrieveModel(t *testing.T) { 127 | // given 128 | err, ectx, hd := setupTest(t, http.MethodGet, client.RetrieveModels, nil, nil) 129 | EXAMPLE_MODEL_ID := constants.TextDavinci003Engine 130 | ectx.SetParamNames(client.ModelIdParamKey) 131 | ectx.SetParamValues(EXAMPLE_MODEL_ID) 132 | 133 | // when 134 | err = hd.RetrieveModel(ectx) 135 | 136 | // then 137 | if err != nil { 138 | t.Errorf("could not retrieve model: %v", err) 139 | } 140 | res := ectx.Response() 141 | if res.Status != http.StatusOK { 142 | t.Errorf("expected status OK but got %v", res.Status) 143 | } 144 | body := res.Writer.(*httptest.ResponseRecorder).Body 145 | var retrievedModelObject client.ModelObject 146 | if err = json.Unmarshal(body.Bytes(), &retrievedModelObject); err != nil { 147 | t.Errorf("could not unmarshal response: %v", err) 148 | } 149 | if retrievedModelObject.ID != "text-davinci-003" { 150 | t.Errorf("expected model with id text-davinci-003 but got %v", retrievedModelObject.ID) 151 | } 152 | } 153 | 154 | func TestCreateCompletion(t *testing.T) { 155 | // given 156 | bodyTest := client.NewCompletionRequest("this is a test", 3, nil, nil, nil) 157 | bodyRaw, err := json.Marshal(bodyTest) 158 | if err != nil { 159 | t.Errorf("could not marshal request body: %v", err) 160 | } 161 | err, ectx, hd := setupTest(t, http.MethodPost, client.CreateCompletionEndpoint, &bodyRaw, nil) 162 | if err != nil { 163 | t.Errorf("could not setup test: %v", err) 164 | } 165 | 166 | // when 167 | err = hd.CreateCompletion(ectx) 168 | 169 | // then 170 | if err != nil { 171 | t.Errorf("could not create completion: %v", err) 172 | } 173 | res := ectx.Response() 174 | if res.Status != http.StatusOK { 175 | t.Errorf("expected status OK but got %v", res.Status) 176 | } 177 | bodyVerify := res.Writer.(*httptest.ResponseRecorder).Body 178 | var completionResponse client.CompletionResponse 179 | if err = json.Unmarshal(bodyVerify.Bytes(), &completionResponse); err != nil { 180 | t.Errorf("could not unmarshal response: %v", err) 181 | } 182 | if len(completionResponse.Choices) == 0 { 183 | t.Errorf("expected at least one completion but got %v", len(completionResponse.Choices)) 184 | } 185 | } 186 | 187 | func TestCreateCompletionStreamTrue(t *testing.T) { 188 | // given 189 | stream := new(bool) 190 | *stream = true 191 | bodyTest := client.NewCompletionRequest("one thing that you should know about golang", 20, nil, stream, nil) 192 | bodyRaw, err := json.Marshal(bodyTest) 193 | if err != nil { 194 | t.Errorf("could not marshal request body: %v", err) 195 | } 196 | err, ectx, hd := setupTestSSE(t, http.MethodPost, client.CreateCompletionEndpoint, &bodyRaw) 197 | if err != nil { 198 | t.Errorf("could not setup test: %v", err) 199 | } 200 | 201 | // when 202 | err = hd.CreateCompletionStream(ectx) 203 | 204 | // then 205 | if err != nil { 206 | t.Errorf("could not create completion: %v", err) 207 | } 208 | res := ectx.Response() 209 | if res.Status != http.StatusOK { 210 | t.Errorf("expected status OK but got %v", res.Status) 211 | } 212 | if res.Header().Get(echo.HeaderContentType) != "text/event-stream" { 213 | t.Errorf("expected content type text/event-stream but got %v", res.Header().Get(echo.HeaderContentType)) 214 | } 215 | var rawString string 216 | reader := bufio.NewReader(res.Writer.(*httptest.ResponseRecorder).Body) 217 | for { 218 | line, err := reader.ReadBytes('\n') 219 | if err != nil { 220 | if err == io.EOF { 221 | break 222 | } 223 | } 224 | rawString += string(line) 225 | } 226 | fmt.Println("rawString", rawString) 227 | assert.NotEmpty(t, rawString) 228 | } 229 | --------------------------------------------------------------------------------