├── .gitignore ├── LICENSE ├── README.md ├── bot.go ├── bot_test.go ├── cmd └── gptbot │ ├── README.md │ ├── endpoint.go │ ├── gradio │ ├── gradio.png │ ├── requirements.txt │ └── ui.py │ ├── http.go │ ├── http_client.go │ ├── main.go │ ├── oas2.go │ ├── service.go │ ├── swagger.yaml │ └── wikipedia_gpt3.txt ├── docs └── architecture.png ├── encoder.go ├── engine.go ├── entity.go ├── example_test.go ├── feeder.go ├── feeder_test.go ├── go.mod ├── go.sum ├── milvus ├── README.md ├── docker-compose.yml ├── milvus.go └── milvus_test.go ├── preprocessor.go ├── preprocessor_test.go ├── testdata └── olympics_sections.json ├── vectorstore.go └── vectorstore_test.go /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-aie/gptbot/914b2b830c70606bcc2e536d7a457a1b881b6103/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Luo Peng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPTBot 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/go-aie/gptbot/vulndb.svg)][1] 4 | 5 | Question Answering Bot powered by [OpenAI GPT models][2]. 6 | 7 | 8 | ## Installation 9 | 10 | ```bash 11 | $ go get -u github.com/go-aie/gptbot 12 | ``` 13 | 14 | 15 | ## Quick Start 16 | 17 | ```go 18 | func main() { 19 | ctx := context.Background() 20 | apiKey := os.Getenv("OPENAI_API_KEY") 21 | encoder := gptbot.NewOpenAIEncoder(apiKey, "") 22 | store := gptbot.NewLocalVectorStore() 23 | 24 | // Feed documents into the vector store. 25 | feeder := gptbot.NewFeeder(&gptbot.FeederConfig{ 26 | Encoder: encoder, 27 | Updater: store, 28 | }) 29 | err := feeder.Feed(ctx, &gptbot.Document{ 30 | ID: "1", 31 | Text: "Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt.", 32 | }) 33 | if err != nil { 34 | fmt.Printf("err: %v", err) 35 | return 36 | } 37 | 38 | // Chat with the bot to get answers. 39 | bot := gptbot.NewBot(&gptbot.BotConfig{ 40 | APIKey: apiKey, 41 | Encoder: encoder, 42 | Querier: store, 43 | }) 44 | 45 | question := "When was GPT-3 released?" 46 | answer, _, err := bot.Chat(ctx, question) 47 | if err != nil { 48 | fmt.Printf("err: %v", err) 49 | return 50 | } 51 | fmt.Printf("Q: %s\n", question) 52 | fmt.Printf("A: %s\n", answer) 53 | 54 | // Output: 55 | // 56 | // Q: When was GPT-3 released? 57 | // A: GPT-3 was released in 2020. 58 | } 59 | ``` 60 | 61 | **NOTE**: 62 | - The above example uses a local vector store. If you have a larger dataset, please consider using a vector search engine (e.g. [Milvus](milvus)). 63 | - With the help of [GPTBot Server](cmd/gptbot), you can even upload documents as files and then start chatting via HTTP! 64 | 65 | 66 | ## Design 67 | 68 | GPTBot is an implementation of the method demonstrated in [Question Answering using Embeddings][3]. 69 | 70 | ![architecture](docs/architecture.png) 71 | 72 | 73 | ## Core Concepts 74 | 75 | 76 | | Concepts | Description | Built-in Support | 77 | |--------------|---------------------------------------------------------|-----------------------------------------------------------| 78 | | Preprocessor | Preprocess the documents by splitting them into chunks. | ✅[customizable]
[Preprocessor][4] | 79 | | Encoder | Creates an embedding vector for each chunk. | ✅[customizable]
[OpenAIEncoder][5] | 80 | | VectorStore | Stores and queries document chunk embeddings. | ✅[customizable]
[LocalVectorStore][6]
[Milvus][7] | 81 | | Feeder | Feeds the documents into the vector store. | / | 82 | | Bot | Question answering bot to chat with. | / | 83 | 84 | 85 | ## License 86 | 87 | [MIT](LICENSE) 88 | 89 | 90 | [1]: https://pkg.go.dev/github.com/go-aie/gptbot 91 | [2]: https://platform.openai.com/docs/models 92 | [3]: https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_embeddings.ipynb 93 | [4]: https://pkg.go.dev/github.com/go-aie/gptbot#Preprocessor 94 | [5]: https://pkg.go.dev/github.com/go-aie/gptbot#OpenAIEncoder 95 | [6]: https://pkg.go.dev/github.com/go-aie/gptbot#LocalVectorStore 96 | [7]: https://pkg.go.dev/github.com/go-aie/gptbot/milvus#Milvus 97 | -------------------------------------------------------------------------------- /bot.go: -------------------------------------------------------------------------------- 1 | package gptbot 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "strings" 7 | "text/template" 8 | ) 9 | 10 | // Turn represents a round of dialogue. 11 | type Turn struct { 12 | Question string `json:"question,omitempty"` 13 | Answer string `json:"answer,omitempty"` 14 | } 15 | 16 | type BotConfig struct { 17 | // APIKey is the LLM Platform's APIKey. 18 | // This field is required. 19 | APIKey string 20 | 21 | // Engine is the LLM Platform api implementation 22 | // Defaults to OpenAI's chat api which using gpt-3.5-turbo model 23 | Engine Engine 24 | 25 | // Encoder is an Embedding Encoder, which will encode the user's question into a vector for similarity search. 26 | // This field is required. 27 | Encoder Encoder 28 | 29 | // Querier is a search engine, which is capable of doing the similarity search. 30 | // This field is required. 31 | Querier Querier 32 | 33 | // Model is the ID of OpenAI's model to use for chat. 34 | // Defaults to "gpt-3.5-turbo". 35 | Model ModelType 36 | 37 | // TopK specifies how many candidate similarities will be selected to construct the prompt. 38 | // Defaults to 3. 39 | TopK int 40 | 41 | // Temperature specifies the sampling temperature to use, between 0 and 1. 42 | // Higher values like 0.8 will make the output more random, while lower values 43 | // like 0.2 will make it more focused and deterministic. Defaults to 0.7. 44 | // 45 | // Note that in multi-turn mode, Temperature only applies to the backend 46 | // system, and the temperature of the frontend agent is always 0 since we 47 | // want its behaviour to be as deterministic as possible. 48 | Temperature float64 49 | 50 | // MaxTokens is the maximum number of tokens to generate in the chat. 51 | // Defaults to 256. 52 | MaxTokens int 53 | 54 | // PromptTmpl specifies a custom prompt template for single-turn mode. 55 | // Defaults to DefaultPromptTmpl. 56 | PromptTmpl string 57 | 58 | // MultiTurnPromptTmpl specifies a custom prompt template for multi-turn mode. 59 | // Defaults to DefaultMultiTurnPromptTmpl. 60 | // 61 | // Prompt-based question answering bot essentially operates in single-turn mode, 62 | // since the quality of each answer largely depends on the associated prompt context 63 | // (i.e. the most similar document chunks), which all depends on the corresponding 64 | // question rather than the conversation history. 65 | // 66 | // As a workaround, we try to achieve the effect of multi-turn mode by adding an 67 | // extra frontend agent, who can respond directly to the user for casual greetings, 68 | // and can refine incomplete questions according to the conversation history 69 | // before consulting the backend system (i.e. the single-turn Question Answering Bot). 70 | MultiTurnPromptTmpl string 71 | } 72 | 73 | func (cfg *BotConfig) init() { 74 | if cfg.Model == "" { 75 | cfg.Model = GPT3Dot5Turbo 76 | } 77 | if cfg.TopK == 0 { 78 | cfg.TopK = 3 79 | } 80 | if cfg.Temperature == 0 { 81 | cfg.Temperature = 0.7 82 | } 83 | if cfg.MaxTokens == 0 { 84 | cfg.MaxTokens = 256 85 | } 86 | if cfg.PromptTmpl == "" { 87 | cfg.PromptTmpl = DefaultPromptTmpl 88 | } 89 | if cfg.MultiTurnPromptTmpl == "" { 90 | cfg.MultiTurnPromptTmpl = DefaultMultiTurnPromptTmpl 91 | } 92 | if cfg.Engine == nil { 93 | cfg.Engine = NewOpenAIChatEngine(cfg.APIKey, cfg.Model) 94 | } 95 | } 96 | 97 | type EngineMessage struct { 98 | Role string `json:"role,omitempty"` 99 | Content string `json:"content,omitempty"` 100 | } 101 | 102 | type EngineRequest struct { 103 | Messages []*EngineMessage `json:"messages,omitempty"` 104 | Temperature float64 `json:"temperature,omitempty"` 105 | MaxTokens int `json:"max_tokens,omitempty"` 106 | } 107 | 108 | type EngineResponse struct { 109 | Text string `json:"text,omitempty"` 110 | } 111 | 112 | type Engine interface { 113 | Infer(context.Context, *EngineRequest) (*EngineResponse, error) 114 | } 115 | 116 | type Encoder interface { 117 | Encode(cxt context.Context, text string) (Embedding, error) 118 | EncodeBatch(cxt context.Context, texts []string) ([]Embedding, error) 119 | } 120 | 121 | type Querier interface { 122 | Query(ctx context.Context, embedding Embedding, corpusID string, topK int) ([]*Similarity, error) 123 | } 124 | 125 | type Bot struct { 126 | cfg *BotConfig 127 | } 128 | 129 | // NewBot support single and multi-turn chat request 130 | func NewBot(cfg *BotConfig) *Bot { 131 | cfg.init() 132 | bot := &Bot{cfg: cfg} 133 | 134 | return bot 135 | } 136 | 137 | // Chat answers the given question in single-turn mode by default. If ChatHistory with non-empty history 138 | // is specified, multi-turn mode will be enabled. See BotConfig.MultiTurnPromptTmpl for more details. 139 | func (b *Bot) Chat(ctx context.Context, question string, options ...ChatOption) (answer string, debug *Debug, err error) { 140 | opts := new(chatOptions) 141 | for _, option := range options { 142 | option(opts) 143 | } 144 | 145 | if opts.Debug { 146 | debug = new(Debug) 147 | ctx = newContext(ctx, debug) 148 | } 149 | 150 | if len(opts.History) > 0 { 151 | answer, err = b.multiTurnChat(ctx, question, opts) 152 | return 153 | } 154 | 155 | answer, err = b.singleTurnChat(ctx, question, opts) 156 | return 157 | } 158 | 159 | func (b *Bot) multiTurnChat(ctx context.Context, question string, opts *chatOptions) (string, error) { 160 | prefix := "QUERY:" 161 | 162 | t := PromptTemplate(b.cfg.MultiTurnPromptTmpl) 163 | prompt, err := t.Render(struct { 164 | Turns []*Turn 165 | Question string 166 | Prefix string 167 | }{ 168 | Turns: opts.History, 169 | Question: question, 170 | Prefix: prefix, 171 | }) 172 | if err != nil { 173 | return "", err 174 | } 175 | 176 | // Here we set temperature to 0 since we want the output to be focused and deterministic. 177 | refinedQuestionOrReply, err := b.chat(ctx, prompt, 0) 178 | if err != nil { 179 | return "", err 180 | } 181 | 182 | // Save the reply of the frontend agent for debugging purposes. 183 | if debug, ok := fromContext(ctx); ok { 184 | debug.FrontendReply = refinedQuestionOrReply 185 | } 186 | 187 | if strings.HasPrefix(refinedQuestionOrReply, prefix) { 188 | return b.singleTurnChat(ctx, refinedQuestionOrReply[len(prefix):], opts) 189 | } else { 190 | return refinedQuestionOrReply, nil 191 | } 192 | } 193 | 194 | func (b *Bot) singleTurnChat(ctx context.Context, question string, opts *chatOptions) (string, error) { 195 | prompt, err := b.cfg.constructPrompt(ctx, question, opts) 196 | if err != nil { 197 | return "", err 198 | } 199 | 200 | // Save the prompt of the backend system for debugging purposes. 201 | if debug, ok := fromContext(ctx); ok { 202 | debug.BackendPrompt = prompt 203 | } 204 | 205 | return b.chat(ctx, prompt, b.cfg.Temperature) 206 | } 207 | 208 | func (b *Bot) chat(ctx context.Context, prompt string, temperature float64) (string, error) { 209 | req := &EngineRequest{ 210 | Messages: []*EngineMessage{{Role: "user", Content: prompt}}, 211 | Temperature: temperature, 212 | MaxTokens: b.cfg.MaxTokens, 213 | } 214 | resp, err := b.cfg.Engine.Infer(ctx, req) 215 | if err != nil { 216 | return "", err 217 | } 218 | 219 | return resp.Text, nil 220 | } 221 | 222 | func (b *BotConfig) constructPrompt(ctx context.Context, question string, opts *chatOptions) (string, error) { 223 | emb, err := b.Encoder.Encode(ctx, question) 224 | if err != nil { 225 | return "", err 226 | } 227 | 228 | similarities, err := b.Querier.Query(ctx, emb, opts.CorpusID, b.TopK) 229 | if err != nil { 230 | return "", err 231 | } 232 | 233 | var texts []string 234 | for _, s := range similarities { 235 | texts = append(texts, s.Text) 236 | } 237 | 238 | p := PromptTemplate(b.PromptTmpl) 239 | return p.Render(PromptData{ 240 | Question: question, 241 | Sections: texts, 242 | }) 243 | } 244 | 245 | type chatOptions struct { 246 | Debug bool 247 | CorpusID string 248 | History []*Turn 249 | } 250 | 251 | type ChatOption func(opts *chatOptions) 252 | 253 | func ChatDebug(debug bool) ChatOption { 254 | return func(opts *chatOptions) { opts.Debug = debug } 255 | } 256 | 257 | func ChatHistory(history ...*Turn) ChatOption { 258 | return func(opts *chatOptions) { opts.History = history } 259 | } 260 | 261 | func ChatCorpusID(corpusID string) ChatOption { 262 | return func(opts *chatOptions) { opts.CorpusID = corpusID } 263 | } 264 | 265 | type PromptData struct { 266 | Question string 267 | Sections []string 268 | } 269 | 270 | type PromptTemplate string 271 | 272 | func (p PromptTemplate) Render(data any) (string, error) { 273 | tmpl, err := template.New("").Parse(string(p)) 274 | if err != nil { 275 | return "", err 276 | } 277 | 278 | var buf bytes.Buffer 279 | if err := tmpl.Execute(&buf, data); err != nil { 280 | return "", err 281 | } 282 | 283 | return buf.String(), nil 284 | } 285 | 286 | const ( 287 | DefaultPromptTmpl = ` 288 | Answer the question as truthfully as possible using the provided context, and if the answer is not contained within the text below, say "I don't know." 289 | 290 | Context: 291 | 292 | {{range .Sections -}} 293 | * {{.}} 294 | {{- end}} 295 | 296 | Q: {{.Question}} 297 | A: 298 | ` 299 | 300 | DefaultMultiTurnPromptTmpl = `You are an Agent who communicates with the User, with a System available for answering queries. Your responsibilities include: 301 | 1. For greetings and pleasantries, respond directly to the User; 302 | 2. For other questions, if you cannot understand them, ask the User directly; otherwise, be sure to begin with "{{$.Prefix}}" when querying the System. 303 | 304 | Example 1: 305 | User: What is GPT-3? 306 | Agent: {{$.Prefix}} What is GPT-3? 307 | 308 | Example 2: 309 | User: How many parameters does it use? 310 | Agent: Sorry, I don't quite understand what you mean. 311 | 312 | Example 3: 313 | User: What is GPT-3? 314 | Agent: GPT-3 is an AI model. 315 | User: How many parameters does it use? 316 | Agent: {{$.Prefix}} How many parameters does GPT-3 use? 317 | 318 | Conversation: 319 | {{- range $.Turns}} 320 | User: {{.Question}} 321 | Agent: {{.Answer}} 322 | {{- end}} 323 | User: {{$.Question}} 324 | Agent: 325 | ` 326 | ) 327 | 328 | type Debug struct { 329 | FrontendReply string `json:"frontend_reply,omitempty"` 330 | BackendPrompt string `json:"backend_prompt,omitempty"` 331 | } 332 | 333 | type contextKeyT string 334 | 335 | var contextKey = contextKeyT("github.com/go-aie/gptbot/bot.Debug") 336 | 337 | // NewContext returns a copy of the parent context 338 | // and associates it with a Debug. 339 | func newContext(ctx context.Context, d *Debug) context.Context { 340 | return context.WithValue(ctx, contextKey, d) 341 | } 342 | 343 | // FromContext returns the Debug bound to the context, if any. 344 | func fromContext(ctx context.Context) (d *Debug, ok bool) { 345 | d, ok = ctx.Value(contextKey).(*Debug) 346 | return 347 | } 348 | -------------------------------------------------------------------------------- /bot_test.go: -------------------------------------------------------------------------------- 1 | package gptbot_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/go-aie/gptbot" 10 | ) 11 | 12 | func TestBot_Chat(t *testing.T) { 13 | ctx := context.Background() 14 | 15 | store := gptbot.NewLocalVectorStore() 16 | if err := store.LoadJSON(ctx, "testdata/olympics_sections.json"); err != nil { 17 | t.Fatalf("err: %v\n", err) 18 | } 19 | 20 | apiKey := os.Getenv("OPENAI_API_KEY") 21 | bot := gptbot.NewBot(&gptbot.BotConfig{ 22 | APIKey: apiKey, 23 | Encoder: gptbot.NewOpenAIEncoder(apiKey, ""), 24 | Querier: store, 25 | }) 26 | 27 | question := "Who won the 2020 Summer Olympics men's high jump?" 28 | answer, _, err := bot.Chat(ctx, question) 29 | if err != nil { 30 | t.Fatalf("err: %v\n", err) 31 | } 32 | 33 | if !strings.Contains(answer, "Gianmarco Tamberi") || !strings.Contains(answer, "Mutaz Essa Barshim") { 34 | t.Errorf("unexpected answer: %s\n", answer) 35 | } 36 | } 37 | 38 | func TestBot_ChatWithHistory(t *testing.T) { 39 | ctx := context.Background() 40 | var history []*gptbot.Turn 41 | 42 | store := gptbot.NewLocalVectorStore() 43 | if err := store.LoadJSON(ctx, "testdata/olympics_sections.json"); err != nil { 44 | t.Fatalf("err: %v\n", err) 45 | } 46 | 47 | apiKey := os.Getenv("OPENAI_API_KEY") 48 | bot := gptbot.NewBot(&gptbot.BotConfig{ 49 | APIKey: apiKey, 50 | Encoder: gptbot.NewOpenAIEncoder(apiKey, ""), 51 | Querier: store, 52 | }) 53 | 54 | question := "Who won the 2020 Summer Olympics men's high jump?" 55 | answer, _, err := bot.Chat(ctx, question, gptbot.ChatHistory(history...)) 56 | if err != nil { 57 | t.Fatalf("err: %v\n", err) 58 | } 59 | history = append(history, &gptbot.Turn{ 60 | Question: question, 61 | Answer: answer, 62 | }) 63 | 64 | if !strings.Contains(answer, "Gianmarco Tamberi") || !strings.Contains(answer, "Mutaz Essa Barshim") { 65 | t.Errorf("unexpected answer: %s\n", answer) 66 | } 67 | 68 | question = "Did they agree to share the gold medal?" // In multi-turn mode, here "they" will be inferred to the names of the winners. 69 | answer, _, err = bot.Chat(ctx, question, gptbot.ChatHistory(history...)) 70 | if err != nil { 71 | t.Fatalf("err: %v\n", err) 72 | } 73 | 74 | if !strings.Contains(answer, "Yes") || !strings.Contains(answer, "agreed to share the gold medal") { 75 | t.Errorf("unexpected answer: %s\n", answer) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cmd/gptbot/README.md: -------------------------------------------------------------------------------- 1 | # GPTBot Server 2 | 3 | 4 | ## Prerequisites 5 | 6 | Install and run the Milvus server (see [instructions](../../milvus)). 7 | 8 | ## Start GPTBot Server 9 | 10 | ```bash 11 | $ go build 12 | $ ./gptbot 13 | 2023/03/25 22:48:13 transport=HTTP addr=:8080 14 | ``` 15 | 16 | ## Using cURL 17 | 18 | Upload a document file: 19 | 20 | ```bash 21 | $ curl -F file=@wikipedia_gpt3.txt http://localhost:8080/upload 22 | ``` 23 | 24 | Chat with the bot: 25 | 26 | ```bash 27 | $ curl -H 'Content-Type: application/json' http://localhost:8080/chat -d '{"question": "When was GPT-3 introduced in the paper?"}' 28 | ``` 29 | 30 | ## Using Gradio 31 | 32 | Install dependencies: 33 | 34 | ```bash 35 | $ cd gradio 36 | $ python3 -m venv venv 37 | $ source venv/bin/activate 38 | $ pip3 install -r requirements.txt 39 | ``` 40 | 41 | Run the UI: 42 | 43 | ```bash 44 | $ python3 ui.py 45 | ``` 46 | 47 | ![gradio](gradio/gradio.png) 48 | -------------------------------------------------------------------------------- /cmd/gptbot/endpoint.go: -------------------------------------------------------------------------------- 1 | // Code generated by kun; DO NOT EDIT. 2 | // github.com/RussellLuo/kun 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/RussellLuo/kun/pkg/httpcodec" 10 | "github.com/RussellLuo/kun/pkg/httpoption" 11 | "github.com/RussellLuo/validating/v3" 12 | "github.com/go-aie/gptbot" 13 | "github.com/go-kit/kit/endpoint" 14 | ) 15 | 16 | type ChatRequest struct { 17 | CorpusID string `json:"corpus_id"` 18 | Question string `json:"question"` 19 | InDebug bool `json:"in_debug"` 20 | History []*gptbot.Turn `json:"history"` 21 | } 22 | 23 | // ValidateChatRequest creates a validator for ChatRequest. 24 | func ValidateChatRequest(newSchema func(*ChatRequest) validating.Schema) httpoption.Validator { 25 | return httpoption.FuncValidator(func(value interface{}) error { 26 | req := value.(*ChatRequest) 27 | return httpoption.Validate(newSchema(req)) 28 | }) 29 | } 30 | 31 | type ChatResponse struct { 32 | Answer string `json:"answer"` 33 | Debug *gptbot.Debug `json:"debug"` 34 | Err error `json:"-"` 35 | } 36 | 37 | func (r *ChatResponse) Body() interface{} { return r } 38 | 39 | // Failed implements endpoint.Failer. 40 | func (r *ChatResponse) Failed() error { return r.Err } 41 | 42 | // MakeEndpointOfChat creates the endpoint for s.Chat. 43 | func MakeEndpointOfChat(s Service) endpoint.Endpoint { 44 | return func(ctx context.Context, request interface{}) (interface{}, error) { 45 | req := request.(*ChatRequest) 46 | answer, debug, err := s.Chat( 47 | ctx, 48 | req.CorpusID, 49 | req.Question, 50 | req.InDebug, 51 | req.History, 52 | ) 53 | return &ChatResponse{ 54 | Answer: answer, 55 | Debug: debug, 56 | Err: err, 57 | }, nil 58 | } 59 | } 60 | 61 | type CreateDocumentsRequest struct { 62 | Documents []*gptbot.Document `json:"documents"` 63 | } 64 | 65 | // ValidateCreateDocumentsRequest creates a validator for CreateDocumentsRequest. 66 | func ValidateCreateDocumentsRequest(newSchema func(*CreateDocumentsRequest) validating.Schema) httpoption.Validator { 67 | return httpoption.FuncValidator(func(value interface{}) error { 68 | req := value.(*CreateDocumentsRequest) 69 | return httpoption.Validate(newSchema(req)) 70 | }) 71 | } 72 | 73 | type CreateDocumentsResponse struct { 74 | Err error `json:"-"` 75 | } 76 | 77 | func (r *CreateDocumentsResponse) Body() interface{} { return r } 78 | 79 | // Failed implements endpoint.Failer. 80 | func (r *CreateDocumentsResponse) Failed() error { return r.Err } 81 | 82 | // MakeEndpointOfCreateDocuments creates the endpoint for s.CreateDocuments. 83 | func MakeEndpointOfCreateDocuments(s Service) endpoint.Endpoint { 84 | return func(ctx context.Context, request interface{}) (interface{}, error) { 85 | req := request.(*CreateDocumentsRequest) 86 | err := s.CreateDocuments( 87 | ctx, 88 | req.Documents, 89 | ) 90 | return &CreateDocumentsResponse{ 91 | Err: err, 92 | }, nil 93 | } 94 | } 95 | 96 | type DebugSplitDocumentRequest struct { 97 | Doc *gptbot.Document `json:"doc"` 98 | } 99 | 100 | // ValidateDebugSplitDocumentRequest creates a validator for DebugSplitDocumentRequest. 101 | func ValidateDebugSplitDocumentRequest(newSchema func(*DebugSplitDocumentRequest) validating.Schema) httpoption.Validator { 102 | return httpoption.FuncValidator(func(value interface{}) error { 103 | req := value.(*DebugSplitDocumentRequest) 104 | return httpoption.Validate(newSchema(req)) 105 | }) 106 | } 107 | 108 | type DebugSplitDocumentResponse struct { 109 | Texts []string `json:"texts"` 110 | Err error `json:"-"` 111 | } 112 | 113 | func (r *DebugSplitDocumentResponse) Body() interface{} { return r } 114 | 115 | // Failed implements endpoint.Failer. 116 | func (r *DebugSplitDocumentResponse) Failed() error { return r.Err } 117 | 118 | // MakeEndpointOfDebugSplitDocument creates the endpoint for s.DebugSplitDocument. 119 | func MakeEndpointOfDebugSplitDocument(s Service) endpoint.Endpoint { 120 | return func(ctx context.Context, request interface{}) (interface{}, error) { 121 | req := request.(*DebugSplitDocumentRequest) 122 | texts, err := s.DebugSplitDocument( 123 | ctx, 124 | req.Doc, 125 | ) 126 | return &DebugSplitDocumentResponse{ 127 | Texts: texts, 128 | Err: err, 129 | }, nil 130 | } 131 | } 132 | 133 | type DeleteDocumentsRequest struct { 134 | DocumentIds []string `json:"document_ids"` 135 | } 136 | 137 | // ValidateDeleteDocumentsRequest creates a validator for DeleteDocumentsRequest. 138 | func ValidateDeleteDocumentsRequest(newSchema func(*DeleteDocumentsRequest) validating.Schema) httpoption.Validator { 139 | return httpoption.FuncValidator(func(value interface{}) error { 140 | req := value.(*DeleteDocumentsRequest) 141 | return httpoption.Validate(newSchema(req)) 142 | }) 143 | } 144 | 145 | type DeleteDocumentsResponse struct { 146 | Err error `json:"-"` 147 | } 148 | 149 | func (r *DeleteDocumentsResponse) Body() interface{} { return r } 150 | 151 | // Failed implements endpoint.Failer. 152 | func (r *DeleteDocumentsResponse) Failed() error { return r.Err } 153 | 154 | // MakeEndpointOfDeleteDocuments creates the endpoint for s.DeleteDocuments. 155 | func MakeEndpointOfDeleteDocuments(s Service) endpoint.Endpoint { 156 | return func(ctx context.Context, request interface{}) (interface{}, error) { 157 | req := request.(*DeleteDocumentsRequest) 158 | err := s.DeleteDocuments( 159 | ctx, 160 | req.DocumentIds, 161 | ) 162 | return &DeleteDocumentsResponse{ 163 | Err: err, 164 | }, nil 165 | } 166 | } 167 | 168 | type UploadFileRequest struct { 169 | CorpusID string `json:"corpus_id"` 170 | File *httpcodec.FormFile `json:"file"` 171 | } 172 | 173 | // ValidateUploadFileRequest creates a validator for UploadFileRequest. 174 | func ValidateUploadFileRequest(newSchema func(*UploadFileRequest) validating.Schema) httpoption.Validator { 175 | return httpoption.FuncValidator(func(value interface{}) error { 176 | req := value.(*UploadFileRequest) 177 | return httpoption.Validate(newSchema(req)) 178 | }) 179 | } 180 | 181 | type UploadFileResponse struct { 182 | Err error `json:"-"` 183 | } 184 | 185 | func (r *UploadFileResponse) Body() interface{} { return r } 186 | 187 | // Failed implements endpoint.Failer. 188 | func (r *UploadFileResponse) Failed() error { return r.Err } 189 | 190 | // MakeEndpointOfUploadFile creates the endpoint for s.UploadFile. 191 | func MakeEndpointOfUploadFile(s Service) endpoint.Endpoint { 192 | return func(ctx context.Context, request interface{}) (interface{}, error) { 193 | req := request.(*UploadFileRequest) 194 | err := s.UploadFile( 195 | ctx, 196 | req.CorpusID, 197 | req.File, 198 | ) 199 | return &UploadFileResponse{ 200 | Err: err, 201 | }, nil 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /cmd/gptbot/gradio/gradio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-aie/gptbot/914b2b830c70606bcc2e536d7a457a1b881b6103/cmd/gptbot/gradio/gradio.png -------------------------------------------------------------------------------- /cmd/gptbot/gradio/requirements.txt: -------------------------------------------------------------------------------- 1 | gradio 2 | requests 3 | -------------------------------------------------------------------------------- /cmd/gptbot/gradio/ui.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import gradio as gr 4 | import requests 5 | 6 | 7 | URL = 'http://127.0.0.1:8080' 8 | CORPUS_ID = os.getenv('CORPUS_ID', '') 9 | 10 | 11 | def handle_error(resp): 12 | if resp.status_code != 200: 13 | raise gr.Error(resp.json()['error']['message']) 14 | 15 | 16 | def create(text): 17 | resp = requests.post(URL+'/upsert', json=dict(documents=[dict(text=text,metadata={'corpus_id':CORPUS_ID})])) 18 | handle_error(resp) 19 | return 'Created successfully!' 20 | 21 | 22 | def split_text(text): 23 | resp = requests.post(URL+'/debug/split', json=dict(doc=dict(text=text,metadata={'corpus_id':CORPUS_ID}))) 24 | handle_error(resp) 25 | return resp.json() 26 | 27 | 28 | def split_file(file): 29 | with open(file.name) as f: 30 | return split_text(f.read()) 31 | 32 | 33 | def upload(file): 34 | with open(file.name) as f: 35 | resp = requests.post(URL+'/upload', data=dict(corpus_id=CORPUS_ID), files=dict(file=f)) 36 | handle_error(resp) 37 | return 'Uploaded successfully!' 38 | 39 | 40 | def clear(): 41 | resp = requests.post(URL+'/delete', json=dict(document_ids=[])) 42 | handle_error(resp) 43 | return 'Cleared successfully!' 44 | 45 | 46 | def chat(question, history): 47 | turns = [dict(question=h[0], answer=h[1]) for h in history] 48 | resp = requests.post(URL+'/chat', json=dict(question=question, in_debug=True, corpus_id=CORPUS_ID, history=turns)) 49 | handle_error(resp) 50 | json = resp.json() 51 | return json['answer'], json['debug'].get('backend_prompt', '') 52 | 53 | 54 | with gr.Blocks() as demo: 55 | with gr.Tab('Chat Bot'): 56 | chatbot = gr.Chatbot() 57 | msg = gr.Textbox(label='Input') 58 | with gr.Accordion('Debug', open=False): 59 | prompt = gr.Textbox(label='Prompt') 60 | 61 | def user(msg, history): 62 | question = msg 63 | return '', history + [[question, None]] 64 | 65 | def bot(history): 66 | question = history[-1][0] 67 | answer, prompt = chat(question, history[:-1]) 68 | history[-1][1] = answer 69 | return history, prompt 70 | 71 | msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then( 72 | bot, [chatbot], [chatbot, prompt] 73 | ) 74 | 75 | with gr.Tab('Knowledge Base'): 76 | status = gr.Textbox(label='Status Bar') 77 | btn = gr.Button(value="Clear All") 78 | btn.click(clear, inputs=None, outputs=[status]) 79 | 80 | with gr.Tab('Document Text'): 81 | text = gr.Textbox(label='Document Text', lines=8) 82 | btn = gr.Button(value="Create") 83 | btn.click(create, inputs=[text], outputs=[status]) 84 | 85 | with gr.Accordion('Debug', open=False): 86 | btn = gr.Button(value="Split") 87 | json = gr.JSON() 88 | btn.click(split_text, inputs=[text], outputs=[json]) 89 | 90 | with gr.Tab('Document File'): 91 | file = gr.File(label='Document File') 92 | btn = gr.Button(value="Upload") 93 | btn.click(upload, inputs=[file], outputs=[status]) 94 | 95 | with gr.Accordion('Debug', open=False): 96 | btn = gr.Button(value="Split") 97 | json = gr.JSON() 98 | btn.click(split_file, inputs=[file], outputs=[json]) 99 | 100 | 101 | if __name__ == '__main__': 102 | demo.launch(share=True) 103 | -------------------------------------------------------------------------------- /cmd/gptbot/http.go: -------------------------------------------------------------------------------- 1 | // Code generated by kun; DO NOT EDIT. 2 | // github.com/RussellLuo/kun 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | 10 | "github.com/RussellLuo/kun/pkg/httpcodec" 11 | "github.com/RussellLuo/kun/pkg/httpoption" 12 | "github.com/RussellLuo/kun/pkg/oas2" 13 | "github.com/go-chi/chi" 14 | kithttp "github.com/go-kit/kit/transport/http" 15 | ) 16 | 17 | func NewHTTPRouter(svc Service, codecs httpcodec.Codecs, opts ...httpoption.Option) chi.Router { 18 | r := chi.NewRouter() 19 | options := httpoption.NewOptions(opts...) 20 | 21 | r.Method("GET", "/api", oas2.Handler(OASv2APIDoc, options.ResponseSchema())) 22 | 23 | var codec httpcodec.Codec 24 | var validator httpoption.Validator 25 | var kitOptions []kithttp.ServerOption 26 | 27 | codec = codecs.EncodeDecoder("Chat") 28 | validator = options.RequestValidator("Chat") 29 | r.Method( 30 | "POST", "/chat", 31 | kithttp.NewServer( 32 | MakeEndpointOfChat(svc), 33 | decodeChatRequest(codec, validator), 34 | httpcodec.MakeResponseEncoder(codec, 200), 35 | append(kitOptions, 36 | kithttp.ServerErrorEncoder(httpcodec.MakeErrorEncoder(codec)), 37 | )..., 38 | ), 39 | ) 40 | 41 | codec = codecs.EncodeDecoder("CreateDocuments") 42 | validator = options.RequestValidator("CreateDocuments") 43 | r.Method( 44 | "POST", "/upsert", 45 | kithttp.NewServer( 46 | MakeEndpointOfCreateDocuments(svc), 47 | decodeCreateDocumentsRequest(codec, validator), 48 | httpcodec.MakeResponseEncoder(codec, 200), 49 | append(kitOptions, 50 | kithttp.ServerErrorEncoder(httpcodec.MakeErrorEncoder(codec)), 51 | )..., 52 | ), 53 | ) 54 | 55 | codec = codecs.EncodeDecoder("DebugSplitDocument") 56 | validator = options.RequestValidator("DebugSplitDocument") 57 | r.Method( 58 | "POST", "/debug/split", 59 | kithttp.NewServer( 60 | MakeEndpointOfDebugSplitDocument(svc), 61 | decodeDebugSplitDocumentRequest(codec, validator), 62 | httpcodec.MakeResponseEncoder(codec, 200), 63 | append(kitOptions, 64 | kithttp.ServerErrorEncoder(httpcodec.MakeErrorEncoder(codec)), 65 | )..., 66 | ), 67 | ) 68 | 69 | codec = codecs.EncodeDecoder("DeleteDocuments") 70 | validator = options.RequestValidator("DeleteDocuments") 71 | r.Method( 72 | "POST", "/delete", 73 | kithttp.NewServer( 74 | MakeEndpointOfDeleteDocuments(svc), 75 | decodeDeleteDocumentsRequest(codec, validator), 76 | httpcodec.MakeResponseEncoder(codec, 200), 77 | append(kitOptions, 78 | kithttp.ServerErrorEncoder(httpcodec.MakeErrorEncoder(codec)), 79 | )..., 80 | ), 81 | ) 82 | 83 | codec = codecs.EncodeDecoder("UploadFile") 84 | validator = options.RequestValidator("UploadFile") 85 | r.Method( 86 | "POST", "/upload", 87 | kithttp.NewServer( 88 | MakeEndpointOfUploadFile(svc), 89 | decodeUploadFileRequest(codec, validator), 90 | httpcodec.MakeResponseEncoder(codec, 200), 91 | append(kitOptions, 92 | kithttp.ServerErrorEncoder(httpcodec.MakeErrorEncoder(codec)), 93 | )..., 94 | ), 95 | ) 96 | 97 | return r 98 | } 99 | 100 | func decodeChatRequest(codec httpcodec.Codec, validator httpoption.Validator) kithttp.DecodeRequestFunc { 101 | return func(_ context.Context, r *http.Request) (interface{}, error) { 102 | var _req ChatRequest 103 | 104 | if err := codec.DecodeRequestBody(r, &_req); err != nil { 105 | return nil, err 106 | } 107 | 108 | if err := validator.Validate(&_req); err != nil { 109 | return nil, err 110 | } 111 | 112 | return &_req, nil 113 | } 114 | } 115 | 116 | func decodeCreateDocumentsRequest(codec httpcodec.Codec, validator httpoption.Validator) kithttp.DecodeRequestFunc { 117 | return func(_ context.Context, r *http.Request) (interface{}, error) { 118 | var _req CreateDocumentsRequest 119 | 120 | if err := codec.DecodeRequestBody(r, &_req); err != nil { 121 | return nil, err 122 | } 123 | 124 | if err := validator.Validate(&_req); err != nil { 125 | return nil, err 126 | } 127 | 128 | return &_req, nil 129 | } 130 | } 131 | 132 | func decodeDebugSplitDocumentRequest(codec httpcodec.Codec, validator httpoption.Validator) kithttp.DecodeRequestFunc { 133 | return func(_ context.Context, r *http.Request) (interface{}, error) { 134 | var _req DebugSplitDocumentRequest 135 | 136 | if err := codec.DecodeRequestBody(r, &_req); err != nil { 137 | return nil, err 138 | } 139 | 140 | if err := validator.Validate(&_req); err != nil { 141 | return nil, err 142 | } 143 | 144 | return &_req, nil 145 | } 146 | } 147 | 148 | func decodeDeleteDocumentsRequest(codec httpcodec.Codec, validator httpoption.Validator) kithttp.DecodeRequestFunc { 149 | return func(_ context.Context, r *http.Request) (interface{}, error) { 150 | var _req DeleteDocumentsRequest 151 | 152 | if err := codec.DecodeRequestBody(r, &_req); err != nil { 153 | return nil, err 154 | } 155 | 156 | if err := validator.Validate(&_req); err != nil { 157 | return nil, err 158 | } 159 | 160 | return &_req, nil 161 | } 162 | } 163 | 164 | func decodeUploadFileRequest(codec httpcodec.Codec, validator httpoption.Validator) kithttp.DecodeRequestFunc { 165 | return func(_ context.Context, r *http.Request) (interface{}, error) { 166 | var _req UploadFileRequest 167 | 168 | if err := codec.DecodeRequestBody(r, &_req); err != nil { 169 | return nil, err 170 | } 171 | 172 | if err := validator.Validate(&_req); err != nil { 173 | return nil, err 174 | } 175 | 176 | return &_req, nil 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /cmd/gptbot/http_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by kun; DO NOT EDIT. 2 | // github.com/RussellLuo/kun 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | 12 | "github.com/RussellLuo/kun/pkg/httpcodec" 13 | "github.com/go-aie/gptbot" 14 | ) 15 | 16 | type HTTPClient struct { 17 | codecs httpcodec.Codecs 18 | httpClient *http.Client 19 | scheme string 20 | host string 21 | pathPrefix string 22 | } 23 | 24 | func NewHTTPClient(codecs httpcodec.Codecs, httpClient *http.Client, baseURL string) (*HTTPClient, error) { 25 | u, err := url.Parse(baseURL) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return &HTTPClient{ 30 | codecs: codecs, 31 | httpClient: httpClient, 32 | scheme: u.Scheme, 33 | host: u.Host, 34 | pathPrefix: strings.TrimSuffix(u.Path, "/"), 35 | }, nil 36 | } 37 | 38 | func (c *HTTPClient) Chat(ctx context.Context, corpusID string, question string, inDebug bool, history []*gptbot.Turn) (answer string, debug *gptbot.Debug, err error) { 39 | codec := c.codecs.EncodeDecoder("Chat") 40 | 41 | path := "/chat" 42 | u := &url.URL{ 43 | Scheme: c.scheme, 44 | Host: c.host, 45 | Path: c.pathPrefix + path, 46 | } 47 | 48 | reqBody := struct { 49 | CorpusID string `json:"corpus_id"` 50 | Question string `json:"question"` 51 | InDebug bool `json:"in_debug"` 52 | History []*gptbot.Turn `json:"history"` 53 | }{ 54 | CorpusID: corpusID, 55 | Question: question, 56 | InDebug: inDebug, 57 | History: history, 58 | } 59 | reqBodyReader, headers, err := codec.EncodeRequestBody(&reqBody) 60 | if err != nil { 61 | return "", nil, err 62 | } 63 | 64 | _req, err := http.NewRequestWithContext(ctx, "POST", u.String(), reqBodyReader) 65 | if err != nil { 66 | return "", nil, err 67 | } 68 | 69 | for k, v := range headers { 70 | _req.Header.Set(k, v) 71 | } 72 | 73 | _resp, err := c.httpClient.Do(_req) 74 | if err != nil { 75 | return "", nil, err 76 | } 77 | defer _resp.Body.Close() 78 | 79 | if _resp.StatusCode < http.StatusOK || _resp.StatusCode > http.StatusNoContent { 80 | var respErr error 81 | err := codec.DecodeFailureResponse(_resp.Body, &respErr) 82 | if err == nil { 83 | err = respErr 84 | } 85 | return "", nil, err 86 | } 87 | 88 | respBody := &ChatResponse{} 89 | err = codec.DecodeSuccessResponse(_resp.Body, respBody.Body()) 90 | if err != nil { 91 | return "", nil, err 92 | } 93 | return respBody.Answer, respBody.Debug, nil 94 | } 95 | 96 | func (c *HTTPClient) CreateDocuments(ctx context.Context, documents []*gptbot.Document) (err error) { 97 | codec := c.codecs.EncodeDecoder("CreateDocuments") 98 | 99 | path := "/upsert" 100 | u := &url.URL{ 101 | Scheme: c.scheme, 102 | Host: c.host, 103 | Path: c.pathPrefix + path, 104 | } 105 | 106 | reqBody := struct { 107 | Documents []*gptbot.Document `json:"documents"` 108 | }{ 109 | Documents: documents, 110 | } 111 | reqBodyReader, headers, err := codec.EncodeRequestBody(&reqBody) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | _req, err := http.NewRequestWithContext(ctx, "POST", u.String(), reqBodyReader) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | for k, v := range headers { 122 | _req.Header.Set(k, v) 123 | } 124 | 125 | _resp, err := c.httpClient.Do(_req) 126 | if err != nil { 127 | return err 128 | } 129 | defer _resp.Body.Close() 130 | 131 | if _resp.StatusCode < http.StatusOK || _resp.StatusCode > http.StatusNoContent { 132 | var respErr error 133 | err := codec.DecodeFailureResponse(_resp.Body, &respErr) 134 | if err == nil { 135 | err = respErr 136 | } 137 | return err 138 | } 139 | 140 | return nil 141 | } 142 | 143 | func (c *HTTPClient) DebugSplitDocument(ctx context.Context, doc *gptbot.Document) (texts []string, err error) { 144 | codec := c.codecs.EncodeDecoder("DebugSplitDocument") 145 | 146 | path := "/debug/split" 147 | u := &url.URL{ 148 | Scheme: c.scheme, 149 | Host: c.host, 150 | Path: c.pathPrefix + path, 151 | } 152 | 153 | reqBody := struct { 154 | Doc *gptbot.Document `json:"doc"` 155 | }{ 156 | Doc: doc, 157 | } 158 | reqBodyReader, headers, err := codec.EncodeRequestBody(&reqBody) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | _req, err := http.NewRequestWithContext(ctx, "POST", u.String(), reqBodyReader) 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | for k, v := range headers { 169 | _req.Header.Set(k, v) 170 | } 171 | 172 | _resp, err := c.httpClient.Do(_req) 173 | if err != nil { 174 | return nil, err 175 | } 176 | defer _resp.Body.Close() 177 | 178 | if _resp.StatusCode < http.StatusOK || _resp.StatusCode > http.StatusNoContent { 179 | var respErr error 180 | err := codec.DecodeFailureResponse(_resp.Body, &respErr) 181 | if err == nil { 182 | err = respErr 183 | } 184 | return nil, err 185 | } 186 | 187 | respBody := &DebugSplitDocumentResponse{} 188 | err = codec.DecodeSuccessResponse(_resp.Body, respBody.Body()) 189 | if err != nil { 190 | return nil, err 191 | } 192 | return respBody.Texts, nil 193 | } 194 | 195 | func (c *HTTPClient) DeleteDocuments(ctx context.Context, documentIds []string) (err error) { 196 | codec := c.codecs.EncodeDecoder("DeleteDocuments") 197 | 198 | path := "/delete" 199 | u := &url.URL{ 200 | Scheme: c.scheme, 201 | Host: c.host, 202 | Path: c.pathPrefix + path, 203 | } 204 | 205 | reqBody := struct { 206 | DocumentIds []string `json:"document_ids"` 207 | }{ 208 | DocumentIds: documentIds, 209 | } 210 | reqBodyReader, headers, err := codec.EncodeRequestBody(&reqBody) 211 | if err != nil { 212 | return err 213 | } 214 | 215 | _req, err := http.NewRequestWithContext(ctx, "POST", u.String(), reqBodyReader) 216 | if err != nil { 217 | return err 218 | } 219 | 220 | for k, v := range headers { 221 | _req.Header.Set(k, v) 222 | } 223 | 224 | _resp, err := c.httpClient.Do(_req) 225 | if err != nil { 226 | return err 227 | } 228 | defer _resp.Body.Close() 229 | 230 | if _resp.StatusCode < http.StatusOK || _resp.StatusCode > http.StatusNoContent { 231 | var respErr error 232 | err := codec.DecodeFailureResponse(_resp.Body, &respErr) 233 | if err == nil { 234 | err = respErr 235 | } 236 | return err 237 | } 238 | 239 | return nil 240 | } 241 | 242 | func (c *HTTPClient) UploadFile(ctx context.Context, corpusID string, file *httpcodec.FormFile) (err error) { 243 | codec := c.codecs.EncodeDecoder("UploadFile") 244 | 245 | path := "/upload" 246 | u := &url.URL{ 247 | Scheme: c.scheme, 248 | Host: c.host, 249 | Path: c.pathPrefix + path, 250 | } 251 | 252 | reqBody := struct { 253 | CorpusID string `json:"corpus_id"` 254 | File *httpcodec.FormFile `json:"file"` 255 | }{ 256 | CorpusID: corpusID, 257 | File: file, 258 | } 259 | reqBodyReader, headers, err := codec.EncodeRequestBody(&reqBody) 260 | if err != nil { 261 | return err 262 | } 263 | 264 | _req, err := http.NewRequestWithContext(ctx, "POST", u.String(), reqBodyReader) 265 | if err != nil { 266 | return err 267 | } 268 | 269 | for k, v := range headers { 270 | _req.Header.Set(k, v) 271 | } 272 | 273 | _resp, err := c.httpClient.Do(_req) 274 | if err != nil { 275 | return err 276 | } 277 | defer _resp.Body.Close() 278 | 279 | if _resp.StatusCode < http.StatusOK || _resp.StatusCode > http.StatusNoContent { 280 | var respErr error 281 | err := codec.DecodeFailureResponse(_resp.Body, &respErr) 282 | if err == nil { 283 | err = respErr 284 | } 285 | return err 286 | } 287 | 288 | return nil 289 | } 290 | -------------------------------------------------------------------------------- /cmd/gptbot/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "github.com/RussellLuo/kun/pkg/httpcodec" 12 | "github.com/go-aie/gptbot" 13 | "github.com/go-aie/gptbot/milvus" 14 | ) 15 | 16 | func main() { 17 | apiKey := os.Getenv("OPENAI_API_KEY") 18 | encoder := gptbot.NewOpenAIEncoder(apiKey, "") 19 | store, err := milvus.NewMilvus(&milvus.Config{ 20 | CollectionName: "gptbot", 21 | }) 22 | if err != nil { 23 | log.Fatalf("err: %v", err) 24 | } 25 | 26 | feeder := gptbot.NewFeeder(&gptbot.FeederConfig{ 27 | Encoder: encoder, 28 | Updater: store, 29 | Preprocessor: gptbot.NewPreprocessor(&gptbot.PreprocessorConfig{ 30 | ChunkTokenNum: 300, 31 | PunctuationMarks: []rune{'.', '?', '!', '\n'}, // common English sentence pattern 32 | }), 33 | }) 34 | 35 | bot := gptbot.NewBot(&gptbot.BotConfig{ 36 | APIKey: apiKey, 37 | Encoder: encoder, 38 | Querier: store, 39 | // Engine: gptbot.NewOpenAICompletionEngine(apiKey, gptbot.TextDavinci003), 40 | }) 41 | 42 | svc := NewGPTBot(feeder, store, bot) 43 | r := NewHTTPRouter(svc, httpcodec.NewDefaultCodecs(nil, 44 | httpcodec.Op("UploadFile", httpcodec.NewMultipartForm(0)), 45 | )) 46 | 47 | errs := make(chan error, 2) 48 | go func() { 49 | log.Printf("transport=HTTP addr=%s\n", ":8080") 50 | errs <- http.ListenAndServe(":8080", r) 51 | }() 52 | go func() { 53 | c := make(chan os.Signal, 1) 54 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 55 | errs <- fmt.Errorf("%s", <-c) 56 | }() 57 | 58 | log.Printf("terminated, err:%v", <-errs) 59 | } 60 | -------------------------------------------------------------------------------- /cmd/gptbot/oas2.go: -------------------------------------------------------------------------------- 1 | // Code generated by kun; DO NOT EDIT. 2 | // github.com/RussellLuo/kun 3 | 4 | package main 5 | 6 | import ( 7 | "reflect" 8 | 9 | "github.com/RussellLuo/kun/pkg/httpcodec" 10 | "github.com/RussellLuo/kun/pkg/oas2" 11 | "github.com/go-aie/gptbot" 12 | ) 13 | 14 | var ( 15 | base = `swagger: "2.0" 16 | info: 17 | title: "GPTBot-API" 18 | version: "1.0.0" 19 | description: "Hi here! This is the API documentation for GPTBot.\n//" 20 | license: 21 | name: "MIT" 22 | host: "example.com" 23 | basePath: "/" 24 | schemes: 25 | - "https" 26 | consumes: 27 | - "application/json" 28 | produces: 29 | - "application/json" 30 | ` 31 | 32 | paths = ` 33 | paths: 34 | /chat: 35 | post: 36 | description: "Chat sends question to the bot for an answer. If inDebug (i.e. the debug mode)\nis enabled, non-nil debug (i.e. the debugging information) will be returned." 37 | summary: "Chat sends question to the bot for an answer. If inDebug (i.e. the debug mode)\nis enabled, non-nil debug (i.e. the debugging information) will be returned." 38 | operationId: "Chat" 39 | parameters: 40 | - name: body 41 | in: body 42 | schema: 43 | $ref: "#/definitions/ChatRequestBody" 44 | %s 45 | /upsert: 46 | post: 47 | description: "CreateDocuments feeds documents into the vector store." 48 | summary: "CreateDocuments feeds documents into the vector store." 49 | operationId: "CreateDocuments" 50 | parameters: 51 | - name: body 52 | in: body 53 | schema: 54 | $ref: "#/definitions/CreateDocumentsRequestBody" 55 | %s 56 | /debug/split: 57 | post: 58 | description: "DebugSplitDocument splits a document into texts. It's mainly used for debugging purposes." 59 | summary: "DebugSplitDocument splits a document into texts. It's mainly used for debugging purposes." 60 | operationId: "DebugSplitDocument" 61 | parameters: 62 | - name: body 63 | in: body 64 | schema: 65 | $ref: "#/definitions/DebugSplitDocumentRequestBody" 66 | %s 67 | /delete: 68 | post: 69 | description: "DeleteDocuments deletes the specified documents from the vector store." 70 | summary: "DeleteDocuments deletes the specified documents from the vector store." 71 | operationId: "DeleteDocuments" 72 | parameters: 73 | - name: body 74 | in: body 75 | schema: 76 | $ref: "#/definitions/DeleteDocumentsRequestBody" 77 | %s 78 | /upload: 79 | post: 80 | description: "UploadFile uploads a file and then feeds the text into the vector store." 81 | summary: "UploadFile uploads a file and then feeds the text into the vector store." 82 | operationId: "UploadFile" 83 | parameters: 84 | - name: body 85 | in: body 86 | schema: 87 | $ref: "#/definitions/UploadFileRequestBody" 88 | %s 89 | ` 90 | ) 91 | 92 | func getResponses(schema oas2.Schema) []oas2.OASResponses { 93 | return []oas2.OASResponses{ 94 | oas2.GetOASResponses(schema, "Chat", 200, &ChatResponse{}), 95 | oas2.GetOASResponses(schema, "CreateDocuments", 200, &CreateDocumentsResponse{}), 96 | oas2.GetOASResponses(schema, "DebugSplitDocument", 200, &DebugSplitDocumentResponse{}), 97 | oas2.GetOASResponses(schema, "DeleteDocuments", 200, &DeleteDocumentsResponse{}), 98 | oas2.GetOASResponses(schema, "UploadFile", 200, &UploadFileResponse{}), 99 | } 100 | } 101 | 102 | func getDefinitions(schema oas2.Schema) map[string]oas2.Definition { 103 | defs := make(map[string]oas2.Definition) 104 | 105 | oas2.AddDefinition(defs, "ChatRequestBody", reflect.ValueOf(&struct { 106 | CorpusID string `json:"corpus_id"` 107 | Question string `json:"question"` 108 | InDebug bool `json:"in_debug"` 109 | History []*gptbot.Turn `json:"history"` 110 | }{})) 111 | oas2.AddResponseDefinitions(defs, schema, "Chat", 200, (&ChatResponse{}).Body()) 112 | 113 | oas2.AddDefinition(defs, "CreateDocumentsRequestBody", reflect.ValueOf(&struct { 114 | Documents []*gptbot.Document `json:"documents"` 115 | }{})) 116 | oas2.AddResponseDefinitions(defs, schema, "CreateDocuments", 200, (&CreateDocumentsResponse{}).Body()) 117 | 118 | oas2.AddDefinition(defs, "DebugSplitDocumentRequestBody", reflect.ValueOf(&struct { 119 | Doc *gptbot.Document `json:"doc"` 120 | }{})) 121 | oas2.AddResponseDefinitions(defs, schema, "DebugSplitDocument", 200, (&DebugSplitDocumentResponse{}).Body()) 122 | 123 | oas2.AddDefinition(defs, "DeleteDocumentsRequestBody", reflect.ValueOf(&struct { 124 | DocumentIds []string `json:"document_ids"` 125 | }{})) 126 | oas2.AddResponseDefinitions(defs, schema, "DeleteDocuments", 200, (&DeleteDocumentsResponse{}).Body()) 127 | 128 | oas2.AddDefinition(defs, "UploadFileRequestBody", reflect.ValueOf(&struct { 129 | CorpusID string `json:"corpus_id"` 130 | File *httpcodec.FormFile `json:"file"` 131 | }{})) 132 | oas2.AddResponseDefinitions(defs, schema, "UploadFile", 200, (&UploadFileResponse{}).Body()) 133 | 134 | return defs 135 | } 136 | 137 | func OASv2APIDoc(schema oas2.Schema) string { 138 | resps := getResponses(schema) 139 | paths := oas2.GenPaths(resps, paths) 140 | 141 | defs := getDefinitions(schema) 142 | definitions := oas2.GenDefinitions(defs) 143 | 144 | return base + paths + definitions 145 | } 146 | -------------------------------------------------------------------------------- /cmd/gptbot/service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | 7 | "github.com/RussellLuo/kun/pkg/httpcodec" 8 | "github.com/go-aie/gptbot" 9 | "github.com/go-aie/gptbot/milvus" 10 | "github.com/google/uuid" 11 | ) 12 | 13 | //go:generate kungen ./service.go Service 14 | 15 | // Hi here! This is the API documentation for GPTBot. 16 | // 17 | //kun:oas title=GPTBot-API 18 | //kun:oas version=1.0.0 19 | type Service interface { 20 | // CreateDocuments feeds documents into the vector store. 21 | //kun:op POST /upsert 22 | CreateDocuments(ctx context.Context, documents []*gptbot.Document) error 23 | 24 | // UploadFile uploads a file and then feeds the text into the vector store. 25 | //kun:op POST /upload 26 | UploadFile(ctx context.Context, corpusID string, file *httpcodec.FormFile) (err error) 27 | 28 | // DeleteDocuments deletes the specified documents from the vector store. 29 | //kun:op POST /delete 30 | DeleteDocuments(ctx context.Context, documentIds []string) error 31 | 32 | // Chat sends question to the bot for an answer. If inDebug (i.e. the debug mode) 33 | // is enabled, non-nil debug (i.e. the debugging information) will be returned. 34 | //kun:op POST /chat 35 | Chat(ctx context.Context, corpusID, question string, inDebug bool, history []*gptbot.Turn) (answer string, debug *gptbot.Debug, err error) 36 | 37 | // DebugSplitDocument splits a document into texts. It's mainly used for debugging purposes. 38 | //kun:op POST /debug/split 39 | DebugSplitDocument(ctx context.Context, doc *gptbot.Document) (texts []string, err error) 40 | } 41 | 42 | type GPTBot struct { 43 | feeder *gptbot.Feeder 44 | store *milvus.Milvus 45 | bot *gptbot.Bot 46 | } 47 | 48 | func NewGPTBot(feeder *gptbot.Feeder, store *milvus.Milvus, bot *gptbot.Bot) *GPTBot { 49 | return &GPTBot{ 50 | feeder: feeder, 51 | store: store, 52 | bot: bot, 53 | } 54 | } 55 | 56 | func (b *GPTBot) CreateDocuments(ctx context.Context, docs []*gptbot.Document) error { 57 | return b.feeder.Feed(ctx, docs...) 58 | } 59 | 60 | func (b *GPTBot) UploadFile(ctx context.Context, corpusID string, file *httpcodec.FormFile) (err error) { 61 | defer file.File.Close() 62 | data, err := io.ReadAll(file.File) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | doc := &gptbot.Document{ 68 | ID: uuid.New().String(), 69 | Text: string(data), 70 | Metadata: gptbot.Metadata{ 71 | CorpusID: corpusID, 72 | }, 73 | } 74 | return b.feeder.Feed(ctx, doc) 75 | } 76 | 77 | func (b *GPTBot) DeleteDocuments(ctx context.Context, docIDs []string) error { 78 | return b.store.Delete(ctx, docIDs...) 79 | } 80 | 81 | func (b *GPTBot) Chat(ctx context.Context, corpusID, question string, inDebug bool, history []*gptbot.Turn) (answer string, debug *gptbot.Debug, err error) { 82 | return b.bot.Chat(ctx, question, gptbot.ChatCorpusID(corpusID), gptbot.ChatDebug(inDebug), gptbot.ChatHistory(history...)) 83 | } 84 | 85 | func (b *GPTBot) DebugSplitDocument(ctx context.Context, doc *gptbot.Document) (texts []string, err error) { 86 | if doc.ID == "" { 87 | doc.ID = uuid.New().String() 88 | } 89 | 90 | p := b.feeder.Preprocessor() 91 | chunkMap, err := p.Preprocess(doc) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | for _, c := range chunkMap[doc.ID] { 97 | texts = append(texts, c.Text) 98 | } 99 | return texts, nil 100 | } 101 | -------------------------------------------------------------------------------- /cmd/gptbot/swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: "GPTBot-API" 4 | version: "1.0.0" 5 | description: "Hi here! This is the API documentation for GPTBot.\n//" 6 | license: 7 | name: "MIT" 8 | host: "example.com" 9 | basePath: "/" 10 | schemes: 11 | - "https" 12 | consumes: 13 | - "application/json" 14 | produces: 15 | - "application/json" 16 | 17 | paths: 18 | /chat: 19 | post: 20 | description: "Chat sends question to the bot for an answer." 21 | operationId: "Chat" 22 | parameters: 23 | - name: body 24 | in: body 25 | schema: 26 | $ref: "#/definitions/ChatRequestBody" 27 | 28 | produces: 29 | - application/json; charset=utf-8 30 | responses: 31 | 200: 32 | description: "" 33 | schema: 34 | $ref: "#/definitions/ChatResponse" 35 | 36 | /upsert: 37 | post: 38 | description: "CreateDocuments feeds documents into the vector store." 39 | operationId: "CreateDocuments" 40 | parameters: 41 | - name: body 42 | in: body 43 | schema: 44 | $ref: "#/definitions/CreateDocumentsRequestBody" 45 | 46 | produces: 47 | - application/json; charset=utf-8 48 | responses: 49 | 200: 50 | description: "" 51 | schema: 52 | $ref: "#/definitions/CreateDocumentsResponse" 53 | 54 | /debug/chat: 55 | post: 56 | description: "DebugChat sends question to the bot for an answer as well as some debugging information." 57 | operationId: "DebugChat" 58 | parameters: 59 | - name: body 60 | in: body 61 | schema: 62 | $ref: "#/definitions/DebugChatRequestBody" 63 | 64 | produces: 65 | - application/json; charset=utf-8 66 | responses: 67 | 200: 68 | description: "" 69 | schema: 70 | $ref: "#/definitions/DebugChatResponse" 71 | 72 | /debug/split: 73 | post: 74 | description: "DebugSplitDocument splits a document into texts. It's mainly used for debugging purposes." 75 | operationId: "DebugSplitDocument" 76 | parameters: 77 | - name: body 78 | in: body 79 | schema: 80 | $ref: "#/definitions/DebugSplitDocumentRequestBody" 81 | 82 | produces: 83 | - application/json; charset=utf-8 84 | responses: 85 | 200: 86 | description: "" 87 | schema: 88 | $ref: "#/definitions/DebugSplitDocumentResponse" 89 | 90 | /delete: 91 | post: 92 | description: "DeleteDocuments deletes the specified documents from the vector store." 93 | operationId: "DeleteDocuments" 94 | parameters: 95 | - name: body 96 | in: body 97 | schema: 98 | $ref: "#/definitions/DeleteDocumentsRequestBody" 99 | 100 | produces: 101 | - application/json; charset=utf-8 102 | responses: 103 | 200: 104 | description: "" 105 | schema: 106 | $ref: "#/definitions/DeleteDocumentsResponse" 107 | 108 | /upload: 109 | post: 110 | description: "UploadFile uploads a file and then feeds the text into the vector store." 111 | operationId: "UploadFile" 112 | parameters: 113 | - name: body 114 | in: body 115 | schema: 116 | $ref: "#/definitions/UploadFileRequestBody" 117 | 118 | produces: 119 | - application/json; charset=utf-8 120 | responses: 121 | 200: 122 | description: "" 123 | schema: 124 | $ref: "#/definitions/UploadFileResponse" 125 | 126 | 127 | definitions: 128 | ChatRequestBody: 129 | type: object 130 | properties: 131 | question: 132 | type: string 133 | history: 134 | type: array 135 | items: 136 | $ref: "#/definitions/Turn" 137 | ChatResponse: 138 | type: object 139 | properties: 140 | answer: 141 | type: string 142 | CreateDocumentsRequestBody: 143 | type: object 144 | properties: 145 | documents: 146 | type: array 147 | items: 148 | $ref: "#/definitions/Document" 149 | CreateDocumentsResponse: 150 | type: object 151 | Debug: 152 | type: object 153 | properties: 154 | frontend_reply: 155 | type: string 156 | backend_prompt: 157 | type: string 158 | DebugChatRequestBody: 159 | type: object 160 | properties: 161 | question: 162 | type: string 163 | history: 164 | type: array 165 | items: 166 | $ref: "#/definitions/Turn" 167 | DebugChatResponse: 168 | type: object 169 | properties: 170 | answer: 171 | type: string 172 | debug: 173 | $ref: "#/definitions/Debug" 174 | DebugSplitDocumentRequestBody: 175 | type: object 176 | properties: 177 | doc: 178 | $ref: "#/definitions/Document" 179 | DebugSplitDocumentResponse: 180 | type: object 181 | properties: 182 | texts: 183 | type: array 184 | items: 185 | type: string 186 | DeleteDocumentsRequestBody: 187 | type: object 188 | properties: 189 | document_ids: 190 | type: array 191 | items: 192 | type: string 193 | DeleteDocumentsResponse: 194 | type: object 195 | Document: 196 | type: object 197 | properties: 198 | id: 199 | type: string 200 | text: 201 | type: string 202 | metadata: 203 | $ref: "#/definitions/Metadata" 204 | Metadata: 205 | type: object 206 | Turn: 207 | type: object 208 | properties: 209 | question: 210 | type: string 211 | answer: 212 | type: string 213 | UploadFileRequestBody: 214 | type: object 215 | properties: 216 | file: 217 | type: string 218 | format: binary 219 | UploadFileResponse: 220 | type: object 221 | -------------------------------------------------------------------------------- /cmd/gptbot/wikipedia_gpt3.txt: -------------------------------------------------------------------------------- 1 | Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt. 2 | 3 | The architecture is a decoder-only transformer network with a 2048-token-long context and then-unprecedented size of 175 billion parameters, requiring 800GB to store. The model was trained using generative pre-training; it is trained to predict what the next token is based on previous tokens. The model demonstrated strong zero-shot and few-shot learning on many tasks.[2] 4 | 5 | It is the third-generation language prediction model in the GPT series, successor to GPT-2 created by OpenAI, a San Francisco-based artificial intelligence research laboratory.[3] GPT-3, which was introduced in May 2020, and was in beta testing as of July 2020,[4] is part of a trend in natural language processing (NLP) systems of pre-trained language representations.[1] 6 | 7 | The quality of the text generated by GPT-3 is so high that it can be difficult to determine whether or not it was written by a human, which has both benefits and risks.[5] Thirty-one OpenAI researchers and engineers presented the original May 28, 2020 paper introducing GPT-3. In their paper, they warned of GPT-3's potential dangers and called for research to mitigate risk.[1]: 34  David Chalmers, an Australian philosopher, described GPT-3 as "one of the most interesting and important AI systems ever produced."[6] An April 2022 review in The New York Times described GPT-3's capabilities as being able to write original prose with fluency equivalent to that of a human.[7] 8 | 9 | Microsoft announced on September 22, 2020, that it had licensed "exclusive" use of GPT-3; others can still use the public API to receive output, but only Microsoft has access to GPT-3's underlying model.[8] 10 | -------------------------------------------------------------------------------- /docs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-aie/gptbot/914b2b830c70606bcc2e536d7a457a1b881b6103/docs/architecture.png -------------------------------------------------------------------------------- /encoder.go: -------------------------------------------------------------------------------- 1 | package gptbot 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/rakyll/openai-go" 7 | "github.com/rakyll/openai-go/embedding" 8 | ) 9 | 10 | type OpenAIEncoder struct { 11 | client *embedding.Client 12 | } 13 | 14 | func NewOpenAIEncoder(apiKey string, model string) *OpenAIEncoder { 15 | if model == "" { 16 | model = "text-embedding-ada-002" 17 | } 18 | s := openai.NewSession(apiKey) 19 | 20 | return &OpenAIEncoder{ 21 | client: embedding.NewClient(s, model), 22 | } 23 | } 24 | 25 | func (e *OpenAIEncoder) Encode(ctx context.Context, text string) (Embedding, error) { 26 | embeddings, err := e.EncodeBatch(ctx, []string{text}) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return embeddings[0], nil 31 | } 32 | 33 | func (e *OpenAIEncoder) EncodeBatch(ctx context.Context, texts []string) ([]Embedding, error) { 34 | resp, err := e.client.Create(ctx, &embedding.CreateParams{ 35 | Input: texts, 36 | }) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | var embeddings []Embedding 42 | for _, data := range resp.Data { 43 | embeddings = append(embeddings, data.Embedding) 44 | } 45 | return embeddings, nil 46 | } 47 | -------------------------------------------------------------------------------- /engine.go: -------------------------------------------------------------------------------- 1 | package gptbot 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/rakyll/openai-go" 7 | "github.com/rakyll/openai-go/chat" 8 | "github.com/rakyll/openai-go/completion" 9 | ) 10 | 11 | type ModelType string 12 | 13 | const ( 14 | // GPT-4 15 | GPT4 ModelType = "gpt-4" 16 | GPT40613 ModelType = "gpt-4-0613" 17 | GPT432K ModelType = "gpt-4-32k" 18 | GPT432K0613 ModelType = "gpt-4-32k-0613" 19 | GPT40314 ModelType = "gpt-4-0314" 20 | GPT432K0314 ModelType = "gpt-4-32k-0314" 21 | 22 | // GPT-3.5 23 | GPT3Dot5Turbo ModelType = "gpt-3.5-turbo" 24 | GPT3Dot5Turbo16K ModelType = "gpt-3.5-turbo-16k" 25 | GPT3Dot5Turbo0613 ModelType = "gpt-3.5-turbo-0613" 26 | GPT3Dot5Turbo16K0613 ModelType = "gpt-3.5-turbo-16k-0613" 27 | TextDavinci003 ModelType = "text-davinci-003" 28 | TextDavinci002 ModelType = "text-davinci-002" 29 | 30 | // GPT-3 (not recommend) 31 | TextAda001 ModelType = "text-ada-001" 32 | TextCurie001 ModelType = "text-curie-001" 33 | TextBabbage001 ModelType = "text-babbage-001" 34 | ) 35 | 36 | // OpenAIChatEngine is an engine powered by OpenAI's Chat API /v1/chat/completions. 37 | // 38 | // See https://platform.openai.com/docs/models/model-endpoint-compatibility for 39 | // the supported models. 40 | type OpenAIChatEngine struct { 41 | Client *chat.Client 42 | } 43 | 44 | func NewOpenAIChatEngine(apiKey string, model ModelType) *OpenAIChatEngine { 45 | return &OpenAIChatEngine{ 46 | Client: chat.NewClient(openai.NewSession(apiKey), string(model)), 47 | } 48 | } 49 | 50 | func (e *OpenAIChatEngine) Infer(ctx context.Context, req *EngineRequest) (*EngineResponse, error) { 51 | resp, err := e.Client.CreateCompletion(ctx, &chat.CreateCompletionParams{ 52 | Messages: []*chat.Message{ 53 | { 54 | Role: req.Messages[0].Role, 55 | Content: req.Messages[0].Content, 56 | }, 57 | }, 58 | Temperature: req.Temperature, 59 | MaxTokens: req.MaxTokens, 60 | }) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return &EngineResponse{ 66 | Text: resp.Choices[0].Message.Content, 67 | }, nil 68 | } 69 | 70 | // OpenAICompletionEngine is an engine powered by OpenAI's Completion API /v1/completions. 71 | // 72 | // See https://platform.openai.com/docs/models/model-endpoint-compatibility for 73 | // the supported models. 74 | type OpenAICompletionEngine struct { 75 | Client *completion.Client 76 | } 77 | 78 | func NewOpenAICompletionEngine(apiKey string, model ModelType) *OpenAICompletionEngine { 79 | return &OpenAICompletionEngine{ 80 | Client: completion.NewClient(openai.NewSession(apiKey), string(model)), 81 | } 82 | } 83 | 84 | func (e *OpenAICompletionEngine) Infer(ctx context.Context, req *EngineRequest) (*EngineResponse, error) { 85 | resp, err := e.Client.Create(ctx, &completion.CreateParams{ 86 | Prompt: []string{req.Messages[0].Content}, 87 | Temperature: req.Temperature, 88 | MaxTokens: req.MaxTokens, 89 | }) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | return &EngineResponse{ 95 | Text: resp.Choices[0].Text, 96 | }, nil 97 | } 98 | -------------------------------------------------------------------------------- /entity.go: -------------------------------------------------------------------------------- 1 | package gptbot 2 | 3 | type Metadata struct { 4 | CorpusID string `json:"corpus_id,omitempty"` 5 | } 6 | 7 | type Document struct { 8 | ID string `json:"id,omitempty"` 9 | Text string `json:"text,omitempty"` 10 | Metadata Metadata `json:"metadata,omitempty"` 11 | } 12 | 13 | type Embedding []float64 14 | 15 | type Chunk struct { 16 | ID string `json:"id,omitempty"` 17 | Text string `json:"text,omitempty"` 18 | DocumentID string `json:"document_id,omitempty"` 19 | Metadata Metadata `json:"metadata,omitempty"` 20 | Embedding Embedding `json:"embedding,omitempty"` 21 | } 22 | 23 | type Similarity struct { 24 | *Chunk 25 | 26 | Score float64 `json:"score,omitempty"` 27 | } 28 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package gptbot_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/go-aie/gptbot" 9 | ) 10 | 11 | func Example() { 12 | ctx := context.Background() 13 | apiKey := os.Getenv("OPENAI_API_KEY") 14 | encoder := gptbot.NewOpenAIEncoder(apiKey, "") 15 | store := gptbot.NewLocalVectorStore() 16 | 17 | // Feed documents into the vector store. 18 | feeder := gptbot.NewFeeder(&gptbot.FeederConfig{ 19 | Encoder: encoder, 20 | Updater: store, 21 | }) 22 | err := feeder.Feed(ctx, &gptbot.Document{ 23 | ID: "1", 24 | Text: "Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt.\n\nThe architecture is a decoder-only transformer network with a 2048-token-long context and then-unprecedented size of 175 billion parameters, requiring 800GB to store. The model was trained using generative pre-training; it is trained to predict what the next token is based on previous tokens. The model demonstrated strong zero-shot and few-shot learning on many tasks.[2]", 25 | }) 26 | if err != nil { 27 | fmt.Printf("err: %v", err) 28 | return 29 | } 30 | 31 | // Chat with the bot to get answers. 32 | bot := gptbot.NewBot(&gptbot.BotConfig{ 33 | APIKey: apiKey, 34 | Encoder: encoder, 35 | Querier: store, 36 | }) 37 | 38 | question := "When was GPT-3 released?" 39 | answer, _, err := bot.Chat(ctx, question) 40 | if err != nil { 41 | fmt.Printf("err: %v", err) 42 | return 43 | } 44 | fmt.Printf("Q: %s\n", question) 45 | fmt.Printf("A: %s\n", answer) 46 | 47 | question = "How many parameters does GPT-3 use?" 48 | answer, _, err = bot.Chat(ctx, question) 49 | if err != nil { 50 | fmt.Printf("err: %v", err) 51 | return 52 | } 53 | fmt.Printf("Q: %s\n", question) 54 | fmt.Printf("A: %s\n", answer) 55 | 56 | // Output: 57 | // 58 | // Q: When was GPT-3 released? 59 | // A: GPT-3 was released in 2020. 60 | // Q: How many parameters does GPT-3 use? 61 | // A: GPT-3 uses 175 billion parameters. 62 | } 63 | 64 | func Example_multiTurn() { 65 | ctx := context.Background() 66 | apiKey := os.Getenv("OPENAI_API_KEY") 67 | encoder := gptbot.NewOpenAIEncoder(apiKey, "") 68 | store := gptbot.NewLocalVectorStore() 69 | 70 | // Feed documents into the vector store. 71 | feeder := gptbot.NewFeeder(&gptbot.FeederConfig{ 72 | Encoder: encoder, 73 | Updater: store, 74 | }) 75 | err := feeder.Feed(ctx, &gptbot.Document{ 76 | ID: "1", 77 | Text: "Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt.\n\nThe architecture is a decoder-only transformer network with a 2048-token-long context and then-unprecedented size of 175 billion parameters, requiring 800GB to store. The model was trained using generative pre-training; it is trained to predict what the next token is based on previous tokens. The model demonstrated strong zero-shot and few-shot learning on many tasks.[2]", 78 | }) 79 | if err != nil { 80 | fmt.Printf("err: %v", err) 81 | return 82 | } 83 | 84 | // Chat with the bot to get answers. 85 | bot := gptbot.NewBot(&gptbot.BotConfig{ 86 | APIKey: apiKey, 87 | Encoder: encoder, 88 | Querier: store, 89 | }) 90 | 91 | var history []*gptbot.Turn 92 | 93 | question := "When was GPT-3 released?" 94 | answer, _, err := bot.Chat(ctx, question, gptbot.ChatHistory(history...)) 95 | if err != nil { 96 | fmt.Printf("err: %v", err) 97 | return 98 | } 99 | fmt.Printf("Q: %s\n", question) 100 | fmt.Printf("A: %s\n", answer) 101 | 102 | // Save the conversation history. 103 | history = append(history, &gptbot.Turn{ 104 | Question: question, 105 | Answer: answer, 106 | }) 107 | 108 | question = "How many parameters does it use?" // In multi-turn mode, here "it" will be inferred to "GPT-3". 109 | answer, _, err = bot.Chat(ctx, question, gptbot.ChatHistory(history...)) 110 | if err != nil { 111 | fmt.Printf("err: %v", err) 112 | return 113 | } 114 | fmt.Printf("Q: %s\n", question) 115 | fmt.Printf("A: %s\n", answer) 116 | 117 | // Output: 118 | // 119 | // Q: When was GPT-3 released? 120 | // A: GPT-3 was released in 2020. 121 | // Q: How many parameters does it use? 122 | // A: GPT-3 uses 175 billion parameters. 123 | } 124 | -------------------------------------------------------------------------------- /feeder.go: -------------------------------------------------------------------------------- 1 | package gptbot 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type XPreprocessor interface { 8 | Preprocess(docs ...*Document) (map[string][]*Chunk, error) 9 | } 10 | 11 | type Updater interface { 12 | Insert(ctx context.Context, chunks map[string][]*Chunk) error 13 | Delete(ctx context.Context, documentIDs ...string) error 14 | } 15 | 16 | type FeederConfig struct { 17 | // Encoder is the embedding encoder. 18 | // This field is required. 19 | Encoder Encoder 20 | 21 | // Updater is the vector store for inserting/deleting chunks. 22 | // This field is required. 23 | Updater Updater 24 | 25 | // Defaults to NewPreprocessor(...). 26 | Preprocessor XPreprocessor 27 | 28 | // BatchSize is the number of chunks to encode/upsert at a time. 29 | // Defaults to 100. 30 | BatchSize int 31 | } 32 | 33 | func (cfg *FeederConfig) init() *FeederConfig { 34 | if cfg.Preprocessor == nil { 35 | cfg.Preprocessor = NewPreprocessor(&PreprocessorConfig{}) 36 | } 37 | if cfg.BatchSize == 0 { 38 | cfg.BatchSize = 100 39 | } 40 | return cfg 41 | } 42 | 43 | type Feeder struct { 44 | cfg *FeederConfig 45 | } 46 | 47 | func NewFeeder(cfg *FeederConfig) *Feeder { 48 | return &Feeder{ 49 | cfg: cfg.init(), 50 | } 51 | } 52 | 53 | func (f *Feeder) Preprocessor() XPreprocessor { 54 | return f.cfg.Preprocessor 55 | } 56 | 57 | func (f *Feeder) Feed(ctx context.Context, docs ...*Document) error { 58 | chunks, err := f.cfg.Preprocessor.Preprocess(docs...) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | // Delete old chunks belonging to the given document IDs. 64 | var docIDs []string 65 | for docID := range chunks { 66 | docIDs = append(docIDs, docID) 67 | } 68 | if err := f.cfg.Updater.Delete(ctx, docIDs...); err != nil { 69 | return err 70 | } 71 | 72 | // Insert new chunks. 73 | for batch := range genBatches(chunks, f.cfg.BatchSize) { 74 | if err := f.encode(ctx, batch); err != nil { 75 | return err 76 | } 77 | if err := f.insert(ctx, batch); err != nil { 78 | return err 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (f *Feeder) encode(ctx context.Context, batch []*Chunk) error { 86 | var texts []string 87 | for _, chunk := range batch { 88 | texts = append(texts, chunk.Text) 89 | } 90 | 91 | embeddings, err := f.cfg.Encoder.EncodeBatch(ctx, texts) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | for i, chunk := range batch { 97 | chunk.Embedding = embeddings[i] 98 | } 99 | 100 | return nil 101 | } 102 | 103 | func (f *Feeder) insert(ctx context.Context, batch []*Chunk) error { 104 | chunkMap := make(map[string][]*Chunk) 105 | for _, chunk := range batch { 106 | chunkMap[chunk.DocumentID] = append(chunkMap[chunk.DocumentID], chunk) 107 | } 108 | return f.cfg.Updater.Insert(ctx, chunkMap) 109 | } 110 | 111 | func genBatches(chunks map[string][]*Chunk, size int) <-chan []*Chunk { 112 | ch := make(chan []*Chunk) 113 | 114 | go func() { 115 | var batch []*Chunk 116 | 117 | for _, chunkList := range chunks { 118 | for _, chunk := range chunkList { 119 | batch = append(batch, chunk) 120 | 121 | if len(batch) == size { 122 | // Reach the batch size, copy and send all the buffered chunks. 123 | temp := make([]*Chunk, size) 124 | copy(temp, batch) 125 | ch <- temp 126 | 127 | // Clear the buffer. 128 | batch = batch[:0] 129 | } 130 | } 131 | } 132 | 133 | // Send all the remaining chunks, if any. 134 | if len(batch) > 0 { 135 | ch <- batch 136 | } 137 | 138 | close(ch) 139 | }() 140 | 141 | return ch 142 | } 143 | -------------------------------------------------------------------------------- /feeder_test.go: -------------------------------------------------------------------------------- 1 | package gptbot_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/go-aie/gptbot" 9 | "github.com/google/go-cmp/cmp" 10 | ) 11 | 12 | func TestFeeder_Feed(t *testing.T) { 13 | apiKey := os.Getenv("OPENAI_API_KEY") 14 | encoder := gptbot.NewOpenAIEncoder(apiKey, "") 15 | 16 | store := gptbot.NewLocalVectorStore() 17 | f := gptbot.NewFeeder(&gptbot.FeederConfig{ 18 | Encoder: encoder, 19 | Updater: store, 20 | Preprocessor: gptbot.NewPreprocessor(&gptbot.PreprocessorConfig{ 21 | ChunkTokenNum: 150, 22 | MinChunkCharNum: 50, 23 | }), 24 | BatchSize: 2, 25 | }) 26 | 27 | tests := []struct { 28 | in []*gptbot.Document 29 | want map[string][]*gptbot.Chunk 30 | }{ 31 | { 32 | in: []*gptbot.Document{ 33 | { 34 | ID: "1", 35 | Text: "Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt.\n\nThe architecture is a decoder-only transformer network with a 2048-token-long context and then-unprecedented size of 175 billion parameters, requiring 800GB to store. The model was trained using generative pre-training; it is trained to predict what the next token is based on previous tokens. The model demonstrated strong zero-shot and few-shot learning on many tasks.[2]", 36 | }, 37 | { 38 | ID: "2", 39 | Text: "生成型预训练变换模型 3 (英语:Generative Pre-trained Transformer 3,简称 GPT-3)是一个自回归语言模型,目的是为了使用深度学习生成人类可以理解的自然语言[1]。GPT-3是由在旧金山的人工智能公司OpenAI训练与开发,模型设计基于谷歌开发的 Transformer 语言模型。GPT-3的神经网络包含1750亿个参数,需要800GB来存储, 为有史以来参数最多的神经网络模型[2]。该模型在许多任务上展示了强大的零样本和少样本的能力。[3]", 40 | }, 41 | }, 42 | want: map[string][]*gptbot.Chunk{ 43 | "1": { 44 | { 45 | ID: "1_0", 46 | Text: "Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt. The architecture is a decoder-only transformer network with a 2048-token-long context and then-unprecedented size of 175 billion parameters, requiring 800GB to store. The model was trained using generative pre-training; it is trained to predict what the next token is based on previous tokens. The model demonstrated strong zero-shot and few-shot learning on many tasks.", 47 | DocumentID: "1", 48 | }, 49 | }, 50 | "2": { 51 | { 52 | ID: "2_0", 53 | Text: "生成型预训练变换模型 3 (英语:Generative Pre-trained Transformer 3,简称 GPT-3)是一个自回归语言模型,目的是为了使用深度学习生成人类可以理解的自然语言[1]。", 54 | DocumentID: "2", 55 | }, 56 | { 57 | ID: "2_1", 58 | Text: "GPT-3是由在旧金山的人工智能公司OpenAI训练与开发,模型设计基于谷歌开发的 Transformer 语言模型。", 59 | DocumentID: "2", 60 | }, 61 | { 62 | ID: "2_2", 63 | Text: "GPT-3的神经网络包含1750亿个参数,需要800GB来存储, 为有史以来参数最多的神经网络模型[2]。该模型在许多任务上展示了强大的零样本和少样本的能力。", 64 | DocumentID: "2", 65 | }, 66 | }, 67 | }, 68 | }, 69 | } 70 | for _, tt := range tests { 71 | if err := f.Feed(context.Background(), tt.in...); err != nil { 72 | t.Errorf("err: %v\n", err) 73 | } 74 | 75 | got := store.GetAllData(context.Background()) 76 | 77 | // For simplicity, clear the Embedding field. 78 | for _, cs := range got { 79 | for _, c := range cs { 80 | c.Embedding = nil 81 | } 82 | } 83 | if !cmp.Equal(got, tt.want) { 84 | diff := cmp.Diff(got, tt.want) 85 | t.Errorf("Want - Got: %s", diff) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-aie/gptbot 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/RussellLuo/kun v0.4.5 7 | github.com/RussellLuo/validating/v3 v3.0.0-beta.1 8 | github.com/go-aie/xslices v0.0.0-20230221025134-e24f453f38b6 9 | github.com/go-chi/chi v4.1.2+incompatible 10 | github.com/go-kit/kit v0.10.0 11 | github.com/google/go-cmp v0.5.8 12 | github.com/google/uuid v1.1.2 13 | github.com/milvus-io/milvus-sdk-go/v2 v2.2.1 14 | github.com/rakyll/openai-go v1.0.7 15 | github.com/samber/go-gpt-3-encoder v0.3.1 16 | golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb 17 | gonum.org/v1/gonum v0.12.0 18 | ) 19 | 20 | require ( 21 | github.com/dlclark/regexp2 v1.7.0 // indirect 22 | github.com/go-logfmt/logfmt v0.5.0 // indirect 23 | github.com/golang/protobuf v1.5.2 // indirect 24 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect 25 | github.com/milvus-io/milvus-proto/go-api v0.0.0-20230301092744-7efc6eec15fd // indirect 26 | github.com/samber/lo v1.37.0 // indirect 27 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 28 | golang.org/x/sys v0.1.0 // indirect 29 | golang.org/x/text v0.3.7 // indirect 30 | google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29 // indirect 31 | google.golang.org/grpc v1.48.0 // indirect 32 | google.golang.org/protobuf v1.28.1 // indirect 33 | gopkg.in/yaml.v2 v2.4.0 // indirect 34 | sigs.k8s.io/yaml v1.3.0 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 5 | github.com/RussellLuo/kun v0.4.5 h1:006wh9AhIqf/IDijB0TXju8FrlWI0UAaObRPvKZKXFM= 6 | github.com/RussellLuo/kun v0.4.5/go.mod h1:ITHYogvZMuRxT3CWEZQ7d+B6KU0wZJgVnjY74RfoUjE= 7 | github.com/RussellLuo/validating/v3 v3.0.0-beta.1 h1:uvS1bibGqNFbL9sdT+T63A88vOq3UlVVYuqw1rZynF4= 8 | github.com/RussellLuo/validating/v3 v3.0.0-beta.1/go.mod h1:aXLMAOUVm1Abr2yLXA8g49WVSI6RiiCwn0TXv2iToU0= 9 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 10 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 11 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= 12 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= 13 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 14 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 15 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 16 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 17 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 18 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 19 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 20 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 21 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 22 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 23 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= 24 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= 25 | github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 26 | github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= 27 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 28 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 29 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 30 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 31 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= 32 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 33 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 34 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 35 | github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 36 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 37 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 38 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 39 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 40 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 41 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 42 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 43 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 44 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= 45 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 46 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 47 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 48 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 49 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 50 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 51 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 52 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 53 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 54 | github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= 55 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 56 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 57 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 58 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 59 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 60 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 61 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 62 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 63 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 64 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 65 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 66 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 67 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 68 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 69 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= 70 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= 71 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 72 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 73 | github.com/go-aie/xslices v0.0.0-20230221025134-e24f453f38b6 h1:tNgrG4FQyW28Cfe2VMDpUfGnTLnEQlmMzKJCG92Safs= 74 | github.com/go-aie/xslices v0.0.0-20230221025134-e24f453f38b6/go.mod h1:4X94BrIAuQRal3BQlni9H0QkNb8+hSgqi4vCmdvSJIA= 75 | github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= 76 | github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 77 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 78 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 79 | github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= 80 | github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= 81 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 82 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 83 | github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= 84 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 85 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 86 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 87 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 88 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= 89 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 90 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 91 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 92 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 93 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 94 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 95 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 96 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 97 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 98 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 99 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 100 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 101 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 102 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 103 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 104 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 105 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 106 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 107 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 108 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 109 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 110 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 111 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 112 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 113 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 114 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 115 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 116 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 117 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 118 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 119 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 120 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 121 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 122 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 123 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 124 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 125 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 126 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 127 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 128 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 129 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 130 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 131 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 132 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 133 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 134 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 135 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= 136 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 137 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 138 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 139 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 140 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= 141 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 142 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 143 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 144 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 145 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 146 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 147 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 148 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 149 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 150 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 151 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 152 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 153 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 154 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 155 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 156 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 157 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 158 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 159 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 160 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 161 | github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= 162 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 163 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 164 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 165 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 166 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 167 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 168 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 169 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 170 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 171 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 172 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 173 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 174 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 175 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 176 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 177 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 178 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 179 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 180 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 181 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= 182 | github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= 183 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= 184 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 185 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 186 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 187 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 188 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 189 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 190 | github.com/milvus-io/milvus-proto/go-api v0.0.0-20230301092744-7efc6eec15fd h1:9ilgTEqZSdEPbJKSrRGB1TIHTaF7DqVDIwn8/azcaBk= 191 | github.com/milvus-io/milvus-proto/go-api v0.0.0-20230301092744-7efc6eec15fd/go.mod h1:148qnlmZ0Fdm1Fq+Mj/OW2uDoEP25g3mjh0vMGtkgmk= 192 | github.com/milvus-io/milvus-sdk-go/v2 v2.2.1 h1:6p/lrxZCGkw5S2p5GPWy/BUmO6mVUNfrczv98uAnhoU= 193 | github.com/milvus-io/milvus-sdk-go/v2 v2.2.1/go.mod h1:tzSlTIYTqe23QBymMFOKU13nEh+n+sTJzRvpkq2fqsk= 194 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 195 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 196 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 197 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 198 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 202 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 203 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 204 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 205 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 206 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= 207 | github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= 208 | github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= 209 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= 210 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 211 | github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 212 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 213 | github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= 214 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 215 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 216 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 217 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 218 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 219 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= 220 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= 221 | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= 222 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 223 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 224 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= 225 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 226 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 227 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 228 | github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= 229 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 230 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 231 | github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= 232 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= 233 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 234 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 235 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 236 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 237 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 238 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 239 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 240 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 241 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 242 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 243 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= 244 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 245 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 246 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 247 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 248 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 249 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 250 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 251 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= 252 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 253 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 254 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 255 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 256 | github.com/rakyll/openai-go v1.0.7 h1:efM5cMYj75ebvKyy2a1pbQOMrtZtxArmdlIRfMH3mUs= 257 | github.com/rakyll/openai-go v1.0.7/go.mod h1:hQpeaAVYbRXtWjFUew82LmbEIASmjihU3aI5NfJdqxA= 258 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 259 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 260 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 261 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 262 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 263 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 264 | github.com/samber/go-gpt-3-encoder v0.3.1 h1:YWb9GsGYUgSX/wPtsEHjyNGRQXsQ9vDCg9SU2x9uMeU= 265 | github.com/samber/go-gpt-3-encoder v0.3.1/go.mod h1:27nvdvk9ZtALyNtgs9JsPCMYja0Eleow/XzgjqwRtLU= 266 | github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= 267 | github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= 268 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 269 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 270 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 271 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 272 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 273 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 274 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 275 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 276 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 277 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 278 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 279 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 280 | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 281 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 282 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 283 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 284 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 285 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 286 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 287 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 288 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 289 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 290 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 291 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 292 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 293 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 294 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 295 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 296 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 297 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 298 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= 299 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 300 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 301 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 302 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 303 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 304 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 305 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 306 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 307 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 308 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 309 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 310 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 311 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 312 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 313 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 314 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 315 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 316 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 317 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 318 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 319 | golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= 320 | golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 321 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 322 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 323 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 324 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 325 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 326 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 327 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 328 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 329 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 330 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 331 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 332 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 333 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 334 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 335 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 336 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 337 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 338 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 339 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 340 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 341 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 342 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 343 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 344 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 345 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 346 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 347 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 348 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 349 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 350 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= 351 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 352 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 353 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 354 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 355 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 356 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 357 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 358 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 359 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 360 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 361 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 362 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 363 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 364 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 365 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 366 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 367 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 368 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 369 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 370 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 371 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 376 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 377 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 378 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 379 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 380 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 381 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 382 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 383 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 384 | golang.org/x/sys v0.1.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 388 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 389 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 390 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 391 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 392 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 393 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 394 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 395 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 396 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 397 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 398 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 399 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 400 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 401 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 402 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 403 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 404 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 405 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 406 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 407 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 408 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 409 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 410 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 411 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 412 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 413 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 414 | gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= 415 | gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= 416 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 417 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 418 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 419 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 420 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 421 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 422 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 423 | google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 424 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 425 | google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 426 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 427 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 428 | google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29 h1:DJUvgAPiJWeMBiT+RzBVcJGQN7bAEWS5UEoMshES9xs= 429 | google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= 430 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 431 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 432 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= 433 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 434 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 435 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 436 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 437 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 438 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 439 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 440 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 441 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 442 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 443 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 444 | google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 445 | google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= 446 | google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 447 | google.golang.org/grpc/examples v0.0.0-20220617181431-3e7b97febc7f h1:rqzndB2lIQGivcXdTuY3Y9NBvr70X+y77woofSRluec= 448 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 449 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 450 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 451 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 452 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 453 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 454 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 455 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 456 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 457 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 458 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 459 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 460 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 461 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 462 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 463 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 464 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 465 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 466 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 467 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 468 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 469 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 470 | gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= 471 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 472 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 473 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 474 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 475 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 476 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 477 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 478 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 479 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 480 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 481 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 482 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 483 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 484 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 485 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 486 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 487 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 488 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 489 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= 490 | -------------------------------------------------------------------------------- /milvus/README.md: -------------------------------------------------------------------------------- 1 | # Milvus 2 | 3 | Using [Milvus][1] as the vector store. 4 | 5 | 6 | ## Installation 7 | 8 | For how to install Milvus in different scenarios, see [Official Documents][2]. 9 | 10 | For testing purpose, here we choose to [Install Milvus Standalone with Docker Compose][3]. 11 | 12 | ```bash 13 | $ wget https://github.com/milvus-io/milvus/releases/download/v2.2.4/milvus-standalone-docker-compose.yml -O docker-compose.yml 14 | $ sudo docker compose up -d 15 | ``` 16 | 17 | ## Testing 18 | 19 | ```bash 20 | $ go test -v -race 21 | === RUN TestMilvus_Query 22 | --- PASS: TestMilvus_Query (7.34s) 23 | PASS 24 | ok github.com/go-aie/gptbot/milvus 7.866s 25 | ``` 26 | 27 | 28 | [1]: https://milvus.io/ 29 | [2]: https://milvus.io/docs/install_standalone-operator.md 30 | [3]: https://milvus.io/docs/install_standalone-docker.md -------------------------------------------------------------------------------- /milvus/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | etcd: 5 | container_name: milvus-etcd 6 | image: quay.io/coreos/etcd:v3.5.5 7 | environment: 8 | - ETCD_AUTO_COMPACTION_MODE=revision 9 | - ETCD_AUTO_COMPACTION_RETENTION=1000 10 | - ETCD_QUOTA_BACKEND_BYTES=4294967296 11 | - ETCD_SNAPSHOT_COUNT=50000 12 | volumes: 13 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd 14 | command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd 15 | 16 | minio: 17 | container_name: milvus-minio 18 | image: minio/minio:RELEASE.2022-03-17T06-34-49Z 19 | environment: 20 | MINIO_ACCESS_KEY: minioadmin 21 | MINIO_SECRET_KEY: minioadmin 22 | ports: 23 | - "9001:9001" 24 | - "9000:9000" 25 | volumes: 26 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data 27 | command: minio server /minio_data --console-address ":9001" 28 | healthcheck: 29 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] 30 | interval: 30s 31 | timeout: 20s 32 | retries: 3 33 | 34 | standalone: 35 | container_name: milvus-standalone 36 | image: milvusdb/milvus:v2.2.4 37 | command: ["milvus", "run", "standalone"] 38 | environment: 39 | ETCD_ENDPOINTS: etcd:2379 40 | MINIO_ADDRESS: minio:9000 41 | volumes: 42 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus 43 | ports: 44 | - "19530:19530" 45 | - "9091:9091" 46 | depends_on: 47 | - "etcd" 48 | - "minio" 49 | 50 | networks: 51 | default: 52 | name: milvus 53 | -------------------------------------------------------------------------------- /milvus/milvus.go: -------------------------------------------------------------------------------- 1 | package milvus 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "github.com/go-aie/gptbot" 11 | "github.com/go-aie/xslices" 12 | "github.com/milvus-io/milvus-sdk-go/v2/client" 13 | "github.com/milvus-io/milvus-sdk-go/v2/entity" 14 | ) 15 | 16 | const ( 17 | pkName, idName, textName, documentIDName, corpusIDName, embeddingName = "pk", "id", "text", "document_id", "corpus_id", "embedding" 18 | ) 19 | 20 | type Config struct { 21 | // CollectionName is the collection name. 22 | // This field is required. 23 | CollectionName string 24 | 25 | // CreateNew specifies whether to overwrite if the collection already exists. 26 | CreateNew bool 27 | 28 | // Addr is the address of the Milvus server. 29 | // Defaults to "localhost:19530". 30 | Addr string 31 | 32 | // Dim is the embedding dimension. 33 | // Defaults to 1536 (the dimension generated by OpenAI's Embedding API). 34 | Dim int 35 | } 36 | 37 | func (cfg *Config) init() { 38 | if cfg.Addr == "" { 39 | cfg.Addr = "localhost:19530" 40 | } 41 | if cfg.Dim == 0 { 42 | cfg.Dim = 1536 43 | } 44 | } 45 | 46 | type Milvus struct { 47 | client client.Client 48 | cfg *Config 49 | } 50 | 51 | func NewMilvus(cfg *Config) (*Milvus, error) { 52 | cfg.init() 53 | ctx := context.Background() 54 | 55 | c, err := client.NewGrpcClient(ctx, cfg.Addr) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | m := &Milvus{ 61 | client: c, 62 | cfg: cfg, 63 | } 64 | 65 | if err := m.createAndLoadCollection(ctx, cfg.CreateNew); err != nil { 66 | return nil, err 67 | } 68 | 69 | return m, nil 70 | } 71 | 72 | func (m *Milvus) LoadJSON(ctx context.Context, filename string) error { 73 | data, err := os.ReadFile(filename) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | var chunks []*gptbot.Chunk 79 | if err := json.Unmarshal(data, &chunks); err != nil { 80 | return err 81 | } 82 | 83 | chunkMap := make(map[string][]*gptbot.Chunk) 84 | for _, chunk := range chunks { 85 | chunkMap[chunk.DocumentID] = append(chunkMap[chunk.DocumentID], chunk) 86 | } 87 | 88 | return m.Insert(ctx, chunkMap) 89 | } 90 | 91 | func (m *Milvus) Insert(ctx context.Context, chunks map[string][]*gptbot.Chunk) error { 92 | var idList []string 93 | var textList []string 94 | var documentIDList []string 95 | var embeddingList [][]float32 96 | var corpusIDList []string 97 | for _, chunkList := range chunks { 98 | for _, chunk := range chunkList { 99 | idList = append(idList, chunk.ID) 100 | textList = append(textList, chunk.Text) 101 | documentIDList = append(documentIDList, chunk.DocumentID) 102 | corpusIDList = append(corpusIDList, chunk.Metadata.CorpusID) 103 | embeddingList = append(embeddingList, xslices.Float64ToNumber[float32](chunk.Embedding)) 104 | } 105 | } 106 | 107 | idCol := entity.NewColumnVarChar(idName, idList) 108 | textCol := entity.NewColumnVarChar(textName, textList) 109 | documentIDCol := entity.NewColumnVarChar(documentIDName, documentIDList) 110 | corpusIDCol := entity.NewColumnVarChar(corpusIDName, corpusIDList) 111 | embeddingCol := entity.NewColumnFloatVector(embeddingName, m.cfg.Dim, embeddingList) 112 | 113 | _, err := m.client.Insert(ctx, m.cfg.CollectionName, "", idCol, textCol, documentIDCol, corpusIDCol, embeddingCol) 114 | return err 115 | } 116 | 117 | // Query searches similarities of the given embedding with default consistency level. 118 | func (m *Milvus) Query(ctx context.Context, embedding gptbot.Embedding, corpusID string, topK int) ([]*gptbot.Similarity, error) { 119 | float32Emb := xslices.Float64ToNumber[float32](embedding) 120 | expr := "" 121 | if corpusID != "" { 122 | expr = fmt.Sprintf(`corpus_id == "%s"`, corpusID) 123 | } 124 | 125 | vec2search := []entity.Vector{ 126 | entity.FloatVector(float32Emb), 127 | } 128 | 129 | param, _ := entity.NewIndexFlatSearchParam() 130 | result, err := m.client.Search( 131 | ctx, 132 | m.cfg.CollectionName, 133 | nil, 134 | expr, 135 | []string{idName, textName, documentIDName, corpusIDName}, 136 | vec2search, 137 | embeddingName, 138 | entity.L2, 139 | topK, 140 | param, 141 | ) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | if len(result) == 0 { 147 | return nil, nil 148 | } 149 | 150 | return constructSimilaritiesFromResult(&result[0]) 151 | } 152 | 153 | // Delete deletes the chunks belonging to the given documentIDs. 154 | // As a special case, empty documentIDs means deleting all chunks. 155 | func (m *Milvus) Delete(ctx context.Context, documentIDs ...string) error { 156 | // To delete all chunks, we drop the old collection and create a new one. 157 | if len(documentIDs) == 0 { 158 | if err := m.client.ReleaseCollection(ctx, m.cfg.CollectionName); err != nil { 159 | return err 160 | } 161 | if err := m.client.DropCollection(ctx, m.cfg.CollectionName); err != nil { 162 | return err 163 | } 164 | return m.createAndLoadCollection(ctx, true) 165 | } 166 | 167 | expr := fmt.Sprintf(`document_id in ["%s"]`, strings.Join(documentIDs, `", "`)) 168 | result, err := m.client.Query(ctx, m.cfg.CollectionName, nil, expr, []string{pkName}) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | var pkCol *entity.ColumnInt64 174 | for _, field := range result { 175 | if field.Name() == pkName { 176 | if c, ok := field.(*entity.ColumnInt64); ok { 177 | pkCol = c 178 | } 179 | } 180 | } 181 | 182 | if len(pkCol.Data()) == 0 { 183 | return nil 184 | } 185 | return m.client.DeleteByPks(ctx, m.cfg.CollectionName, "", pkCol) 186 | } 187 | 188 | func (m *Milvus) createAndLoadCollection(ctx context.Context, createNew bool) error { 189 | if err := m.createCollection(ctx, createNew); err != nil { 190 | return err 191 | } 192 | return m.client.LoadCollection(ctx, m.cfg.CollectionName, false) 193 | } 194 | 195 | func (m *Milvus) createCollection(ctx context.Context, createNew bool) error { 196 | has, err := m.client.HasCollection(ctx, m.cfg.CollectionName) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | if has && !createNew { 202 | return nil 203 | } 204 | 205 | if has { 206 | _ = m.client.DropCollection(ctx, m.cfg.CollectionName) 207 | } 208 | 209 | // The collection does not exist, so we need to create one. 210 | 211 | schema := &entity.Schema{ 212 | CollectionName: m.cfg.CollectionName, 213 | AutoID: true, 214 | Fields: []*entity.Field{ 215 | { 216 | Name: pkName, 217 | DataType: entity.FieldTypeInt64, 218 | PrimaryKey: true, 219 | AutoID: true, 220 | }, 221 | { 222 | Name: idName, 223 | DataType: entity.FieldTypeVarChar, 224 | TypeParams: map[string]string{ 225 | entity.TypeParamMaxLength: fmt.Sprintf("%d", 65535), 226 | }, 227 | }, 228 | { 229 | Name: textName, 230 | DataType: entity.FieldTypeVarChar, 231 | TypeParams: map[string]string{ 232 | entity.TypeParamMaxLength: fmt.Sprintf("%d", 65535), 233 | }, 234 | }, 235 | { 236 | Name: documentIDName, 237 | DataType: entity.FieldTypeVarChar, 238 | TypeParams: map[string]string{ 239 | entity.TypeParamMaxLength: fmt.Sprintf("%d", 65535), 240 | }, 241 | }, 242 | { 243 | Name: corpusIDName, 244 | DataType: entity.FieldTypeVarChar, 245 | TypeParams: map[string]string{ 246 | entity.TypeParamMaxLength: fmt.Sprintf("%d", 65535), 247 | }, 248 | }, 249 | { 250 | Name: embeddingName, 251 | DataType: entity.FieldTypeFloatVector, 252 | TypeParams: map[string]string{ 253 | entity.TypeParamDim: fmt.Sprintf("%d", m.cfg.Dim), 254 | }, 255 | }, 256 | }, 257 | } 258 | 259 | // Create collection with consistency level, which serves as the default search/query consistency level. 260 | if err := m.client.CreateCollection(ctx, schema, 2, client.WithConsistencyLevel(entity.ClBounded)); err != nil { 261 | return err 262 | } 263 | 264 | // Create index "IVF_FLAT". 265 | idx, err := entity.NewIndexIvfFlat(entity.L2, 128) 266 | if err != nil { 267 | return err 268 | } 269 | return m.client.CreateIndex(ctx, m.cfg.CollectionName, embeddingName, idx, false) 270 | } 271 | 272 | func constructSimilaritiesFromResult(result *client.SearchResult) ([]*gptbot.Similarity, error) { 273 | var idCol *entity.ColumnVarChar 274 | var textCol *entity.ColumnVarChar 275 | var documentIDCol *entity.ColumnVarChar 276 | var corpusIDCol *entity.ColumnVarChar 277 | 278 | for _, field := range result.Fields { 279 | switch field.Name() { 280 | case idName: 281 | if c, ok := field.(*entity.ColumnVarChar); ok { 282 | idCol = c 283 | } 284 | case textName: 285 | if c, ok := field.(*entity.ColumnVarChar); ok { 286 | textCol = c 287 | } 288 | case documentIDName: 289 | if c, ok := field.(*entity.ColumnVarChar); ok { 290 | documentIDCol = c 291 | } 292 | case corpusIDName: 293 | if c, ok := field.(*entity.ColumnVarChar); ok { 294 | corpusIDCol = c 295 | } 296 | } 297 | } 298 | 299 | var similarities []*gptbot.Similarity 300 | for i := 0; i < result.ResultCount; i++ { 301 | id, err := idCol.ValueByIdx(i) 302 | if err != nil { 303 | return nil, err 304 | } 305 | text, err := textCol.ValueByIdx(i) 306 | if err != nil { 307 | return nil, err 308 | } 309 | documentID, err := documentIDCol.ValueByIdx(i) 310 | if err != nil { 311 | return nil, err 312 | } 313 | 314 | corpusID, err := corpusIDCol.ValueByIdx(i) 315 | if err != nil { 316 | return nil, err 317 | } 318 | 319 | similarities = append(similarities, &gptbot.Similarity{ 320 | Chunk: &gptbot.Chunk{ 321 | ID: id, 322 | Text: text, 323 | DocumentID: documentID, 324 | Metadata: gptbot.Metadata{ 325 | CorpusID: corpusID, 326 | }, 327 | }, 328 | Score: float64(result.Scores[i]), 329 | }) 330 | } 331 | 332 | return similarities, nil 333 | } 334 | -------------------------------------------------------------------------------- /milvus/milvus_test.go: -------------------------------------------------------------------------------- 1 | package milvus_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/go-aie/gptbot" 9 | "github.com/go-aie/gptbot/milvus" 10 | "github.com/google/go-cmp/cmp" 11 | ) 12 | 13 | func TestMilvus_Query(t *testing.T) { 14 | apiKey := os.Getenv("OPENAI_API_KEY") 15 | encoder := gptbot.NewOpenAIEncoder(apiKey, "") 16 | 17 | store, err := milvus.NewMilvus(&milvus.Config{ 18 | CollectionName: "olympics_knowledge", 19 | }) 20 | if err != nil { 21 | t.Fatalf("err: %v\n", err) 22 | } 23 | 24 | if err := store.LoadJSON(context.Background(), "../testdata/olympics_sections.json"); err != nil { 25 | t.Fatalf("err: %v\n", err) 26 | } 27 | 28 | tests := []struct { 29 | in string 30 | want []*gptbot.Similarity 31 | }{ 32 | { 33 | in: "Who won the 2020 Summer Olympics men's high jump?", 34 | want: []*gptbot.Similarity{ 35 | { 36 | Chunk: &gptbot.Chunk{ 37 | ID: "Summary", 38 | DocumentID: "Athletics at the 2020 Summer Olympics - Men's long jump", 39 | Metadata: gptbot.Metadata{ 40 | CorpusID: "olympic:2020", 41 | }, 42 | }, 43 | }, 44 | { 45 | Chunk: &gptbot.Chunk{ 46 | ID: "Summary", 47 | DocumentID: "Athletics at the 2020 Summer Olympics - Men's triple jump", 48 | Metadata: gptbot.Metadata{ 49 | CorpusID: "olympic:2020", 50 | }, 51 | }, 52 | }, 53 | { 54 | Chunk: &gptbot.Chunk{ 55 | ID: "Summary", 56 | DocumentID: "Athletics at the 2020 Summer Olympics - Men's high jump", 57 | Metadata: gptbot.Metadata{ 58 | CorpusID: "olympic:2020", 59 | }, 60 | }, 61 | }, 62 | }, 63 | }, 64 | } 65 | for _, tt := range tests { 66 | embedding, err := encoder.Encode(context.Background(), tt.in) 67 | if err != nil { 68 | t.Errorf("err: %v\n", err) 69 | } 70 | 71 | got, err := store.Query(context.Background(), embedding, "olympic:2020", 3) 72 | if err != nil { 73 | t.Errorf("err: %v\n", err) 74 | } 75 | 76 | // For simplicity, clear fields Text, Embedding and Score. 77 | for _, s := range got { 78 | s.Text = "" 79 | s.Embedding = nil 80 | s.Score = 0 81 | } 82 | 83 | if !cmp.Equal(got, tt.want) { 84 | diff := cmp.Diff(got, tt.want) 85 | t.Errorf("Want - Got: %s", diff) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /preprocessor.go: -------------------------------------------------------------------------------- 1 | package gptbot 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode" 7 | "unicode/utf8" 8 | 9 | "github.com/go-aie/xslices" 10 | "github.com/google/uuid" 11 | tokenizer "github.com/samber/go-gpt-3-encoder" 12 | ) 13 | 14 | type PreprocessorConfig struct { 15 | // ChunkTokenNum is the number of tokens for each text chunk. 16 | // Defaults to 200. 17 | ChunkTokenNum int 18 | 19 | // MinChunkCharNum is the minimum number of characters for each text chunk. 20 | // Defaults to 350. 21 | MinChunkCharNum int 22 | 23 | // MinChunkLenToEmbed is the minimum length in characters. 24 | // Chunks with shorter length will be discarded. 25 | // 26 | // Defaults to 5. 27 | MinChunkLenToEmbed int 28 | 29 | // MaxChunkNum is the maximum number of chunks to generate from a text. 30 | // Defaults to 10000. 31 | MaxChunkNum int 32 | 33 | // PunctuationMarks is the sentence separators. 34 | // Defaults to []rune{'.', '?', '!', '。', '?', '!', '\n'} 35 | PunctuationMarks []rune 36 | } 37 | 38 | func (cfg *PreprocessorConfig) init() *PreprocessorConfig { 39 | if cfg.ChunkTokenNum == 0 { 40 | cfg.ChunkTokenNum = 200 41 | } 42 | if cfg.MinChunkCharNum == 0 { 43 | cfg.MinChunkCharNum = 350 44 | } 45 | if cfg.MinChunkLenToEmbed == 0 { 46 | cfg.MinChunkLenToEmbed = 5 47 | } 48 | if cfg.MaxChunkNum == 0 { 49 | cfg.MaxChunkNum = 10000 50 | } 51 | if len(cfg.PunctuationMarks) == 0 { 52 | cfg.PunctuationMarks = []rune{'.', '?', '!', '。', '?', '!', '\n'} 53 | } 54 | return cfg 55 | } 56 | 57 | // Preprocessor splits a list of documents into chunks. 58 | type Preprocessor struct { 59 | encoder *dummyTokenizer 60 | cfg *PreprocessorConfig 61 | } 62 | 63 | func NewPreprocessor(cfg *PreprocessorConfig) *Preprocessor { 64 | return &Preprocessor{ 65 | encoder: newDummyTokenizer(), 66 | cfg: cfg.init(), 67 | } 68 | } 69 | 70 | func (p *Preprocessor) Preprocess(docs ...*Document) (map[string][]*Chunk, error) { 71 | chunkMap := make(map[string][]*Chunk) 72 | 73 | for _, doc := range docs { 74 | docID := doc.ID 75 | meta := doc.Metadata 76 | if docID == "" { 77 | docID = uuid.New().String() 78 | } 79 | 80 | textChunks, err := p.split(doc.Text) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | for i, textChunk := range textChunks { 86 | chunkMap[docID] = append(chunkMap[docID], &Chunk{ 87 | ID: fmt.Sprintf("%s_%d", docID, i), 88 | Text: textChunk, 89 | DocumentID: docID, 90 | Metadata: meta, 91 | }) 92 | } 93 | } 94 | 95 | return chunkMap, nil 96 | } 97 | 98 | // split converts the text into chunks. 99 | // 100 | // The splitting algorithm is borrowed from https://github.com/openai/chatgpt-retrieval-plugin/blob/88d983585816b7f298edb0cabf7502c5ccff370d/services/chunks.py#L22-L96. 101 | func (p *Preprocessor) split(text string) ([]string, error) { 102 | if text == "" || strings.TrimSpace(text) == "" { 103 | return nil, nil 104 | } 105 | 106 | // Convert the document text into runes. 107 | runes := []rune(text) 108 | 109 | var chunks []string 110 | 111 | var i int 112 | var chunkNum int 113 | 114 | for i < len(runes) && chunkNum < p.cfg.MaxChunkNum { 115 | // Take the first ChunkTokenNum tokens as a chunk. 116 | chunkRunes, err := p.encoder.Encode(runes[i:], p.cfg.ChunkTokenNum) 117 | if err != nil { 118 | return nil, nil 119 | } 120 | 121 | // Skip the chunk if it is empty or whitespace. 122 | chunkText := string(chunkRunes) 123 | if chunkText == "" || strings.TrimSpace(chunkText) == "" { 124 | i += len(chunkRunes) 125 | continue 126 | } 127 | 128 | // Find the last period or punctuation mark in the chunk. 129 | // Note that here we count the index in runes. 130 | var lastPuncIdx = -1 131 | for _, punc := range p.cfg.PunctuationMarks { 132 | lastPuncIdx = xslices.Max(lastPuncIdx, lastRuneIndex(chunkText, punc)) 133 | } 134 | 135 | if lastPuncIdx != -1 && lastPuncIdx > p.cfg.MinChunkCharNum { 136 | if chunkRunes[lastPuncIdx] == '.' && lastPuncIdx+1 < len(chunkRunes) { 137 | // given the dot cases of `equivalent to 66.2 nautical miles` or `http://example.com/download.html` 138 | // roughly split by: dot mark must followed by space char 139 | if unicode.IsSpace(chunkRunes[lastPuncIdx+1]) { 140 | chunkText = string([]rune(chunkText)[:lastPuncIdx+1]) 141 | } 142 | } else { 143 | // Truncate the chunk text at the punctuation mark. 144 | chunkText = string([]rune(chunkText)[:lastPuncIdx+1]) 145 | } 146 | } 147 | 148 | trimmedChunkText := strings.TrimSpace(strings.ReplaceAll(chunkText, "\n", " ")) 149 | if utf8.RuneCountInString(trimmedChunkText) > p.cfg.MinChunkLenToEmbed { 150 | chunks = append(chunks, trimmedChunkText) 151 | } 152 | 153 | i += utf8.RuneCountInString(chunkText) 154 | chunkNum += 1 155 | } 156 | 157 | // Handle the remaining runes. 158 | if i < len(runes) { 159 | remainingText := string(runes[i:]) 160 | trimmedRemainingText := strings.TrimSpace(strings.ReplaceAll(remainingText, "\n", " ")) 161 | if utf8.RuneCountInString(trimmedRemainingText) > p.cfg.MinChunkLenToEmbed { 162 | chunks = append(chunks, trimmedRemainingText) 163 | } 164 | } 165 | 166 | return chunks, nil 167 | } 168 | 169 | func lastRuneIndex(s string, r rune) int { 170 | runes := []rune(s) 171 | for i := len(runes) - 1; i >= 0; i-- { 172 | if runes[i] == r { 173 | return i 174 | } 175 | } 176 | return -1 177 | } 178 | 179 | // dummyTokenizer tokenizes any given string at the rune level, but counts the 180 | // number of tokens as correctly as possible by using go-gpt-3-encoder. 181 | // 182 | // The reason why we do not use go-gpt-3-encoder directly is that it can not 183 | // handle Chinese characters properly. 184 | type dummyTokenizer struct { 185 | encoder *tokenizer.Encoder 186 | } 187 | 188 | func newDummyTokenizer() *dummyTokenizer { 189 | encoder, err := tokenizer.NewEncoder() 190 | if err != nil { 191 | // We assume that there's no error. 192 | panic(err) 193 | } 194 | return &dummyTokenizer{encoder: encoder} 195 | } 196 | 197 | // Encode iterates through runes and returns a slice of the leading runes, which 198 | // consume at most tokenNum number of tokens. 199 | func (t *dummyTokenizer) Encode(runes []rune, tokenNum int) ([]rune, error) { 200 | b := strings.Builder{} 201 | for i, r := range runes { 202 | _, _ = b.WriteRune(r) 203 | tokens, err := t.encoder.Encode(b.String()) 204 | if err != nil { 205 | return nil, err 206 | } 207 | if len(tokens) > tokenNum { 208 | return runes[:i], nil 209 | } 210 | } 211 | return runes, nil 212 | } 213 | -------------------------------------------------------------------------------- /preprocessor_test.go: -------------------------------------------------------------------------------- 1 | package gptbot_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-aie/gptbot" 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestPreprocessor_Preprocess(t *testing.T) { 11 | p := gptbot.NewPreprocessor(&gptbot.PreprocessorConfig{ 12 | ChunkTokenNum: 150, 13 | MinChunkCharNum: 40, 14 | PunctuationMarks: []rune{'.', '。', '?', '!', '\n'}, 15 | }) 16 | 17 | tests := []struct { 18 | in []*gptbot.Document 19 | want map[string][]*gptbot.Chunk 20 | }{ 21 | { 22 | in: []*gptbot.Document{ 23 | { 24 | ID: "1", 25 | Text: "Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt.\n\nThe architecture is a decoder-only transformer network with a 2048-token-long context and then-unprecedented size of 175 billion parameters, requiring 800GB to store. The model was trained using generative pre-training; it is trained to predict what the next token is based on previous tokens. The model demonstrated strong zero-shot and few-shot learning on many tasks.[2]", 26 | }, 27 | { 28 | ID: "2", 29 | Text: "生成型预训练变换模型 3 (英语:Generative Pre-trained Transformer 3,简称 GPT-3)是一个自回归语言模型,目的是为了使用深度学习生成人类可以理解的自然语言[1]。GPT-3是由在旧金山的人工智能公司OpenAI训练与开发,模型设计基于谷歌开发的 Transformer 语言模型。GPT-3的神经网络包含1750亿个参数,需要800GB来存储, 为有史以来参数最多的神经网络模型[2]。该模型在许多任务上展示了强大的零样本和少样本的能力。[3]", 30 | }, 31 | { 32 | ID: "3", 33 | Text: "可以在官网地址下载,地址为:https://www.kuaifan.co/download.html 快帆支持安卓、苹果iOS、Windows电脑PC、苹果Mac/苹果电脑、AndroidTV等各端下载", 34 | }, 35 | { 36 | ID: "4", 37 | Text: "but he did not realize that this was expressed in the Arabic mile (about 1,830 meters or 1.14 mi) rather than the shorter Roman mile (about 1,480 m) with which he was familiar", 38 | }, 39 | }, 40 | want: map[string][]*gptbot.Chunk{ 41 | "1": { 42 | { 43 | ID: "1_0", 44 | Text: "Generative Pre-trained Transformer 3 (GPT-3) is an autoregressive language model released in 2020 that uses deep learning to produce human-like text. Given an initial text as prompt, it will produce text that continues the prompt. The architecture is a decoder-only transformer network with a 2048-token-long context and then-unprecedented size of 175 billion parameters, requiring 800GB to store. The model was trained using generative pre-training; it is trained to predict what the next token is based on previous tokens. The model demonstrated strong zero-shot and few-shot learning on many tasks.[2]", 45 | DocumentID: "1", 46 | }, 47 | }, 48 | "2": { 49 | { 50 | ID: "2_0", 51 | Text: "生成型预训练变换模型 3 (英语:Generative Pre-trained Transformer 3,简称 GPT-3)是一个自回归语言模型,目的是为了使用深度学习生成人类可以理解的自然语言[1]。", 52 | DocumentID: "2", 53 | }, 54 | { 55 | ID: "2_1", 56 | Text: "GPT-3是由在旧金山的人工智能公司OpenAI训练与开发,模型设计基于谷歌开发的 Transformer 语言模型。", 57 | DocumentID: "2", 58 | }, 59 | { 60 | ID: "2_2", 61 | Text: "GPT-3的神经网络包含1750亿个参数,需要800GB来存储, 为有史以来参数最多的神经网络模型[2]。该模型在许多任务上展示了强大的零样本和少样本的能力。", 62 | DocumentID: "2", 63 | }, 64 | }, 65 | "3": { 66 | { 67 | ID: "3_0", 68 | Text: "可以在官网地址下载,地址为:https://www.kuaifan.co/download.html 快帆支持安卓、苹果iOS、Windows电脑PC、苹果Mac/苹果电脑、AndroidTV等各端下载", 69 | DocumentID: "3", 70 | }, 71 | }, 72 | "4": { 73 | { 74 | ID: "4_0", 75 | Text: "but he did not realize that this was expressed in the Arabic mile (about 1,830 meters or 1.14 mi) rather than the shorter Roman mile (about 1,480 m) with which he was familiar", 76 | DocumentID: "4", 77 | }, 78 | }, 79 | }, 80 | }, 81 | } 82 | for _, tt := range tests { 83 | got, err := p.Preprocess(tt.in...) 84 | if err != nil { 85 | t.Errorf("err: %v\n", err) 86 | } 87 | 88 | if !cmp.Equal(got, tt.want) { 89 | diff := cmp.Diff(got, tt.want) 90 | t.Errorf("Want - Got: %s", diff) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /vectorstore.go: -------------------------------------------------------------------------------- 1 | package gptbot 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "os" 7 | 8 | "golang.org/x/exp/maps" 9 | "golang.org/x/exp/slices" 10 | "gonum.org/v1/gonum/mat" 11 | ) 12 | 13 | type LocalVectorStore struct { 14 | chunks map[string][]*Chunk 15 | } 16 | 17 | func NewLocalVectorStore() *LocalVectorStore { 18 | return &LocalVectorStore{ 19 | chunks: make(map[string][]*Chunk), 20 | } 21 | } 22 | 23 | // LoadJSON will deserialize from disk into a `LocalVectorStore` based on the provided filename. 24 | func (vs *LocalVectorStore) LoadJSON(ctx context.Context, filename string) error { 25 | data, err := os.ReadFile(filename) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | var chunks []*Chunk 31 | if err := json.Unmarshal(data, &chunks); err != nil { 32 | return err 33 | } 34 | 35 | chunkMap := make(map[string][]*Chunk) 36 | for _, chunk := range chunks { 37 | chunkMap[chunk.DocumentID] = append(chunkMap[chunk.DocumentID], chunk) 38 | } 39 | 40 | return vs.Insert(ctx, chunkMap) 41 | } 42 | 43 | // StoreJSON will serialize the `LocalVectorStore` to disk based on the provided filename. 44 | func (vs *LocalVectorStore) StoreJSON(filename string) error { 45 | var chunks []*Chunk 46 | 47 | for _, chunk := range vs.chunks { 48 | chunks = append(chunks, chunk...) 49 | } 50 | 51 | b, err := json.Marshal(chunks) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | err = os.WriteFile(filename, b, 0666) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | 64 | // GetAllData returns all the internal data. It is mainly used for testing purpose. 65 | func (vs *LocalVectorStore) GetAllData(ctx context.Context) map[string][]*Chunk { 66 | return vs.chunks 67 | } 68 | 69 | func (vs *LocalVectorStore) Insert(ctx context.Context, chunks map[string][]*Chunk) error { 70 | for documentID, chunkList := range chunks { 71 | vs.chunks[documentID] = append(vs.chunks[documentID], chunkList...) 72 | } 73 | return nil 74 | } 75 | 76 | func (vs *LocalVectorStore) Query(ctx context.Context, embedding Embedding, corpusID string, topK int) ([]*Similarity, error) { 77 | if topK <= 0 { 78 | return nil, nil 79 | } 80 | 81 | target := mat.NewVecDense(len(embedding), embedding) 82 | 83 | var similarities []*Similarity 84 | for _, chunks := range vs.chunks { 85 | for _, chunk := range chunks { 86 | candidate := mat.NewVecDense(len(chunk.Embedding), chunk.Embedding) 87 | score := mat.Dot(target, candidate) 88 | similarities = append(similarities, &Similarity{ 89 | Chunk: chunk, 90 | Score: score, 91 | }) 92 | } 93 | } 94 | 95 | // Sort similarities by score in descending order. 96 | slices.SortStableFunc(similarities, func(a, b *Similarity) bool { 97 | return a.Score > b.Score 98 | }) 99 | 100 | if len(similarities) <= topK { 101 | return similarities, nil 102 | } 103 | return similarities[:topK], nil 104 | } 105 | 106 | // Delete deletes the chunks belonging to the given documentIDs. 107 | // As a special case, empty documentIDs means deleting all chunks. 108 | func (vs *LocalVectorStore) Delete(ctx context.Context, documentIDs ...string) error { 109 | if len(documentIDs) == 0 { 110 | maps.Clear(vs.chunks) 111 | } 112 | for _, documentID := range documentIDs { 113 | delete(vs.chunks, documentID) 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /vectorstore_test.go: -------------------------------------------------------------------------------- 1 | package gptbot_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/go-aie/gptbot" 9 | "github.com/google/go-cmp/cmp" 10 | ) 11 | 12 | func TestLocalVectorStore_Query(t *testing.T) { 13 | apiKey := os.Getenv("OPENAI_API_KEY") 14 | encoder := gptbot.NewOpenAIEncoder(apiKey, "") 15 | 16 | store := gptbot.NewLocalVectorStore() 17 | if err := store.LoadJSON(context.Background(), "testdata/olympics_sections.json"); err != nil { 18 | t.Fatalf("err: %v\n", err) 19 | } 20 | 21 | tests := []struct { 22 | in string 23 | want []*gptbot.Similarity 24 | }{ 25 | { 26 | in: "Who won the 2020 Summer Olympics men's high jump?", 27 | want: []*gptbot.Similarity{ 28 | { 29 | Chunk: &gptbot.Chunk{ 30 | ID: "Summary", 31 | DocumentID: "Athletics at the 2020 Summer Olympics - Men's long jump", 32 | }, 33 | }, 34 | { 35 | Chunk: &gptbot.Chunk{ 36 | ID: "Summary", 37 | DocumentID: "Athletics at the 2020 Summer Olympics - Men's triple jump", 38 | }, 39 | }, 40 | { 41 | Chunk: &gptbot.Chunk{ 42 | ID: "Summary", 43 | DocumentID: "Athletics at the 2020 Summer Olympics - Men's high jump", 44 | }, 45 | }, 46 | }, 47 | }, 48 | } 49 | for _, tt := range tests { 50 | embedding, err := encoder.Encode(context.Background(), tt.in) 51 | if err != nil { 52 | t.Errorf("err: %v\n", err) 53 | } 54 | 55 | got, err := store.Query(context.Background(), embedding, "", 3) 56 | if err != nil { 57 | t.Errorf("err: %v\n", err) 58 | } 59 | 60 | // For simplicity, clear fields Text, Embedding and Score. 61 | for _, s := range got { 62 | s.Text = "" 63 | s.Embedding = nil 64 | s.Score = 0 65 | } 66 | 67 | if !cmp.Equal(got, tt.want) { 68 | diff := cmp.Diff(got, tt.want) 69 | t.Errorf("Want - Got: %s", diff) 70 | } 71 | } 72 | } 73 | 74 | func TestLocalVectorStore_LoadJSON(t *testing.T) { 75 | filename, cleanup := createTemp(t, []byte(`[{"id":"id_1","text":"text_1","document_id":"doc_id_1"}]`)) 76 | defer cleanup() 77 | 78 | store := gptbot.NewLocalVectorStore() 79 | if err := store.LoadJSON(context.Background(), filename); err != nil { 80 | t.Fatalf("err: %v\n", err) 81 | } 82 | 83 | got := store.GetAllData(context.Background()) 84 | want := map[string][]*gptbot.Chunk{ 85 | "doc_id_1": { 86 | { 87 | ID: "id_1", 88 | Text: "text_1", 89 | DocumentID: "doc_id_1", 90 | }, 91 | }, 92 | } 93 | 94 | if !cmp.Equal(got, want) { 95 | diff := cmp.Diff(got, want) 96 | t.Errorf("Want - Got: %s", diff) 97 | } 98 | } 99 | 100 | func TestLocalVectorStore_StoreJSON(t *testing.T) { 101 | store := gptbot.NewLocalVectorStore() 102 | _ = store.Insert(context.Background(), map[string][]*gptbot.Chunk{ 103 | "doc_id_1": { 104 | { 105 | ID: "id_1", 106 | Text: "text_1", 107 | DocumentID: "doc_id_1", 108 | }, 109 | }, 110 | }) 111 | 112 | filename, cleanup := createTemp(t, []byte("")) 113 | defer cleanup() 114 | 115 | if err := store.StoreJSON(filename); err != nil { 116 | t.Fatalf("err: %v\n", err) 117 | } 118 | 119 | got, err := os.ReadFile(filename) 120 | if err != nil { 121 | t.Fatalf("err: %v\n", err) 122 | } 123 | 124 | want := []byte(`[{"id":"id_1","text":"text_1","document_id":"doc_id_1","metadata":{}}]`) 125 | 126 | if !cmp.Equal(got, want) { 127 | diff := cmp.Diff(got, want) 128 | t.Errorf("Want - Got: %s", diff) 129 | } 130 | } 131 | 132 | func createTemp(t *testing.T, content []byte) (string, func()) { 133 | f, err := os.CreateTemp("", "test") 134 | if err != nil { 135 | t.Fatalf("err: %v\n", err) 136 | } 137 | 138 | filename := f.Name() 139 | cleanup := func() { 140 | _ = os.Remove(filename) 141 | } 142 | 143 | if _, err := f.Write(content); err != nil { 144 | cleanup() 145 | t.Fatalf("err: %v\n", err) 146 | } 147 | if err := f.Close(); err != nil { 148 | cleanup() 149 | t.Fatalf("err: %v\n", err) 150 | } 151 | 152 | return filename, cleanup 153 | } 154 | --------------------------------------------------------------------------------