├── .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 | [][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 | 
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 | 
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 |
--------------------------------------------------------------------------------