├── .github └── workflows │ └── releaser.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── config.yml ├── go.mod ├── go.sum ├── internals ├── config │ └── config.go ├── embeddings │ ├── driver.go │ └── drivers │ │ └── gemini │ │ ├── driver.go │ │ └── init.go ├── http │ ├── embeddings.go │ ├── home.go │ └── vector.go ├── store │ ├── driver.go │ ├── drivers │ │ └── bolt │ │ │ ├── driver.go │ │ │ └── init.go │ └── query.go └── vector │ └── vector.go └── main.go /.github/workflows/releaser.yaml: -------------------------------------------------------------------------------- 1 | env: 2 | CGO_ENABLED: "0" 3 | REGISTRY: ghcr.io 4 | IMAGE_NAME: alash3al/vecdb 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | binary-releaser: 12 | name: Release Go Binary 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | goos: [linux, darwin, windows] 17 | goarch: [amd64, arm64] 18 | steps: 19 | - uses: actions/checkout@v4.1.3 20 | - uses: wangyoucao577/go-release-action@v1.51 21 | with: 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | goos: ${{ matrix.goos }} 24 | goarch: ${{ matrix.goarch }} 25 | goversion: "https://dl.google.com/go/go1.22.0.linux-amd64.tar.gz" 26 | project_path: "." 27 | binary_name: "vecdb" 28 | ldflags: "-s -w" 29 | 30 | docker-releaser: 31 | runs-on: ubuntu-latest 32 | permissions: 33 | contents: read 34 | packages: write 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4.1.3 38 | 39 | - name: Get Latest Tag 40 | id: var_tag 41 | run: echo "name=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT 42 | 43 | - name: Set up Docker Buildx 44 | uses: docker/setup-buildx-action@v3.3.0 45 | 46 | - name: Log in to Github Container registry 47 | uses: docker/login-action@v3 48 | with: 49 | registry: ${{ env.REGISTRY }} 50 | username: ${{ github.actor }} 51 | password: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | - name: Build and push Docker image 54 | uses: docker/build-push-action@v5.3.0 55 | with: 56 | file: Dockerfile 57 | context: . 58 | push: true 59 | tags: | 60 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest 61 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.var_tag.outputs.name }} 62 | cache-from: type=gha 63 | cache-to: type=gha,mode=max -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | .idea -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine As builder 2 | 3 | WORKDIR /vecdb/ 4 | 5 | RUN apk update && apk add git upx 6 | 7 | COPY go.mod go.sum ./ 8 | 9 | RUN go mod download 10 | 11 | COPY . . 12 | 13 | RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o /usr/bin/vecdb . 14 | 15 | RUN upx -9 /usr/bin/vecdb 16 | 17 | FROM alpine 18 | 19 | WORKDIR /vecdb/ 20 | 21 | COPY --from=builder /usr/bin/vecdb /usr/bin/vecdb -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mohammed Al Ashaal 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 | VecDB 2 | ====== 3 | > a very simple vector embedding database, 4 | > you can say that it is a hash-table that let you find items similar to the item you're searching for. 5 | 6 | Why! 7 | ==== 8 | > I'm a databases enthusiast, and this is a for fun and learning project that could be used in production ;). 9 | > 10 | > **P.S**: I like to re-invent the wheel in my free time, because it is my free time! 11 | 12 | Data Model 13 | ========== 14 | > I'm using the `{key => value}` model, 15 | > - `key` should be a unique value that represents the item. 16 | > - `value` should be the vector itself (List of Floats). 17 | 18 | Configurations 19 | ============== 20 | > by default `vecdb` searches for `config.yml` in the current working directory. 21 | > but you can override it using the `--config /path/to/config.yml` flag by providing your own custom file path. 22 | 23 | ```yaml 24 | # http server related configs 25 | server: 26 | # the address to listen on in the form of '[host]:port' 27 | listen: "0.0.0.0:3000" 28 | 29 | # storage related configs 30 | store: 31 | # the driver you want to use 32 | # currently vecdb supports "bolt" which is based on boltdb the in process embedded the database 33 | driver: "bolt" 34 | # the arguments required by the driver 35 | # for bolt, it requires a key called `database` points to the path you want to store the data in. 36 | args: 37 | database: "./vec.db" 38 | 39 | # embeddings related configs 40 | embedder: 41 | # whether to enable the embedder and all endpoints using it or not 42 | enabled: true 43 | # the driver you want to use, currently vecdb supports gemini 44 | driver: gemini 45 | # the arguments required by the driver 46 | # currently gemini driver requires `api_key` and `text_embedding_model` 47 | args: 48 | # by default vecdb will replace anything between ${..} with the actual value from the ENV var 49 | api_key: "${GEMINI_API_KEY}" 50 | text_embedding_model: "text-embedding-004" 51 | ``` 52 | 53 | Components 54 | =========== 55 | - Raw Vectors Layer (low-level) 56 | - send [VectorWriteRequest](#VectorWriteRequest) to `POST /v1/vectors/write` when you have a vector and want to store it somewhere. 57 | - send [VectorSearchRequest](#VectorSearchRequest) to `POST /v1/vectors/search` when you have a vector and want to list all similar vectors' keys/ids ordered by cosine similarity in descending order. 58 | - Embedding Layer (optional) 59 | - send [TextEmbeddingWriteRequest](#TextEmbeddingWriteRequest) to `POST /v1/embeddings/text/write` when you have a text and want `vecdb` to build and store the vector for you using the configured embedder (gemini for now). 60 | - send [TextEmbeddingSearchRequest](#TextEmbeddingSearchRequest) to `POST /v1/embeddings/text/search` when you have a text and want `vecdb` to build a vector and search for similar vectors' keys for you ordered by cosine similarity in descending order. 61 | 62 | Requests 63 | ======== 64 | 65 | ### VectorWriteRequest 66 | ```json5 67 | { 68 | "bucket": "BUCKET_NAME", // consider it a collection or a table 69 | "key": "product-id-1", // should be unique and represents a valid value in your main data store (example: the row id in your mysql/postgres ... etc) 70 | "vector": [1.929292, 0.3848484, -1.9383838383, ... ] // the vector you want to store 71 | } 72 | ``` 73 | 74 | ### VectorSearchRequest 75 | ```json5 76 | { 77 | "bucket": "BUCKET_NAME", // consider it a collection or a table 78 | "vector": [1.929292, 0.3848484, -1.9383838383, ... ], // you will get a list ordered by cosine-similarity in descending order 79 | "min_cosine_similarity": 0.0, // the more you increase, the fewer data you will get 80 | "max_result_count": 10 // max vectors to return (vecdb will first order by cosine similarity then apply the limit) 81 | } 82 | ``` 83 | 84 | ### TextEmbeddingWriteRequest 85 | > if you set `embedder.enabled` to `true`. 86 | 87 | ```json5 88 | { 89 | "bucket": "BUCKET_NAME", // consider it a collection or a table 90 | "key": "product-id-1", // should be unique and represents a valid value in your main data store (example: the row id in your mysql/postgres ... etc) 91 | "content": "This is some text representing the product" // this will be converted to a vector using the configured embedder 92 | } 93 | ``` 94 | 95 | ### TextEmbeddingSearchRequest 96 | > if you set `embedder.enabled` to `true`. 97 | 98 | ```json5 99 | { 100 | "bucket": "BUCKET_NAME", // consider it a collection or a table 101 | "content": "A Product Text", // you will get a list ordered by cosine-similarity in descending order 102 | "min_cosine_similarity": 0.0, // the more you increase, the fewer data you will get 103 | "max_result_count": 10 // max vectors to return (vecdb will first order by cosine similarity then apply the limit) 104 | } 105 | ``` 106 | 107 | Download/Install 108 | ================ 109 | - [Binary](https://github.com/alash3al/vecdb/releases) 110 | - [Docker Image](https://github.com/alash3al/vecdb/pkgs/container/vecdb) -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | server: 2 | listen: ":3000" 3 | 4 | store: 5 | driver: "bolt" 6 | args: 7 | database: "./vec.db" 8 | 9 | embedder: 10 | enabled: true 11 | driver: gemini 12 | args: 13 | api_key: "${GEMINI_API_KEY}" 14 | text_embedding_model: "text-embedding-004" 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alash3al/vecdb 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/go-playground/validator/v10 v10.22.0 7 | github.com/gofiber/fiber/v2 v2.52.5 8 | github.com/google/generative-ai-go v0.17.0 9 | go.etcd.io/bbolt v1.3.10 10 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df 11 | google.golang.org/api v0.190.0 12 | gopkg.in/yaml.v3 v3.0.1 13 | ) 14 | 15 | require ( 16 | cloud.google.com/go v0.115.0 // indirect 17 | cloud.google.com/go/ai v0.8.0 // indirect 18 | cloud.google.com/go/auth v0.7.3 // indirect 19 | cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect 20 | cloud.google.com/go/compute/metadata v0.5.0 // indirect 21 | cloud.google.com/go/longrunning v0.5.7 // indirect 22 | github.com/andybalholm/brotli v1.0.5 // indirect 23 | github.com/felixge/httpsnoop v1.0.4 // indirect 24 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 25 | github.com/go-logr/logr v1.4.2 // indirect 26 | github.com/go-logr/stdr v1.2.2 // indirect 27 | github.com/go-playground/locales v0.14.1 // indirect 28 | github.com/go-playground/universal-translator v0.18.1 // indirect 29 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 30 | github.com/google/s2a-go v0.1.8 // indirect 31 | github.com/google/uuid v1.6.0 // indirect 32 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 33 | github.com/googleapis/gax-go/v2 v2.13.0 // indirect 34 | github.com/klauspost/compress v1.17.0 // indirect 35 | github.com/kr/pretty v0.3.0 // indirect 36 | github.com/leodido/go-urn v1.4.0 // indirect 37 | github.com/mattn/go-colorable v0.1.13 // indirect 38 | github.com/mattn/go-isatty v0.0.20 // indirect 39 | github.com/mattn/go-runewidth v0.0.15 // indirect 40 | github.com/rivo/uniseg v0.2.0 // indirect 41 | github.com/valyala/bytebufferpool v1.0.0 // indirect 42 | github.com/valyala/fasthttp v1.51.0 // indirect 43 | github.com/valyala/tcplisten v1.0.0 // indirect 44 | go.opencensus.io v0.24.0 // indirect 45 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect 46 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect 47 | go.opentelemetry.io/otel v1.26.0 // indirect 48 | go.opentelemetry.io/otel/metric v1.26.0 // indirect 49 | go.opentelemetry.io/otel/trace v1.26.0 // indirect 50 | golang.org/x/crypto v0.25.0 // indirect 51 | golang.org/x/net v0.27.0 // indirect 52 | golang.org/x/oauth2 v0.21.0 // indirect 53 | golang.org/x/sync v0.7.0 // indirect 54 | golang.org/x/sys v0.22.0 // indirect 55 | golang.org/x/text v0.16.0 // indirect 56 | golang.org/x/time v0.5.0 // indirect 57 | google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect 58 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect 59 | google.golang.org/grpc v1.64.1 // indirect 60 | google.golang.org/protobuf v1.34.2 // indirect 61 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 62 | ) 63 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= 3 | cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= 4 | cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= 5 | cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= 6 | cloud.google.com/go/auth v0.7.3 h1:98Vr+5jMaCZ5NZk6e/uBgf60phTk/XN84r8QEWB9yjY= 7 | cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA= 8 | cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= 9 | cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= 10 | cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= 11 | cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= 12 | cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= 13 | cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 16 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 17 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 18 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 19 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 20 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 25 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 26 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 27 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 28 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 29 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 30 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 31 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 32 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 33 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 34 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 35 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 36 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 37 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 38 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 39 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 40 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 41 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 42 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 43 | github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= 44 | github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 45 | github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= 46 | github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= 47 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 48 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 49 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 50 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 51 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 52 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 53 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 54 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 55 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 56 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 57 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 58 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 59 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 60 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 61 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 62 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 63 | github.com/google/generative-ai-go v0.17.0 h1:kUmCXUIwJouD7I7ev3OmxzzQVICyhIWAxaXk2yblCMY= 64 | github.com/google/generative-ai-go v0.17.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= 65 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 66 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 67 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 68 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 69 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 70 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 71 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 72 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 73 | github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= 74 | github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= 75 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 76 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 77 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 78 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= 79 | github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 80 | github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= 81 | github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= 82 | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= 83 | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 84 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 85 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 86 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 87 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 88 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 89 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 90 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 91 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 92 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 93 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 94 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 95 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 96 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 97 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 98 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 99 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 100 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 101 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 102 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 103 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 104 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 105 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 106 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 107 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 108 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 109 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 110 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 111 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 112 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 113 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 114 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 115 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 116 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 117 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 118 | github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= 119 | github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= 120 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 121 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 122 | go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= 123 | go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= 124 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 125 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 126 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= 127 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= 128 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= 129 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= 130 | go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= 131 | go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= 132 | go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= 133 | go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= 134 | go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= 135 | go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= 136 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 137 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 138 | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 139 | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 140 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 141 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= 142 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 143 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 144 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 145 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 146 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 147 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 148 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 149 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 150 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 151 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 152 | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= 153 | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 154 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 155 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 156 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 157 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 158 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 160 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 161 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 162 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 163 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 164 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 167 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 169 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 170 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 171 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 172 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 173 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 174 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 175 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 176 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 177 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 178 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 179 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 180 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 181 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 182 | google.golang.org/api v0.190.0 h1:ASM+IhLY1zljNdLu19W1jTmU6A+gMk6M46Wlur61s+Q= 183 | google.golang.org/api v0.190.0/go.mod h1:QIr6I9iedBLnfqoD6L6Vze1UvS5Hzj5r2aUBOaZnLHo= 184 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 185 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 186 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 187 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 188 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 189 | google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= 190 | google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= 191 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= 192 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 193 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 194 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 195 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 196 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 197 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 198 | google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= 199 | google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 200 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 201 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 202 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 203 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 204 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 205 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 206 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 207 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 208 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 209 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 210 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 211 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 212 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 213 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 214 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 215 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 216 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 217 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 218 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 219 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 220 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 221 | -------------------------------------------------------------------------------- /internals/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "gopkg.in/yaml.v3" 5 | "os" 6 | ) 7 | 8 | type Config struct { 9 | Server struct { 10 | ListenAddr string `yaml:"listen"` 11 | } `yaml:"server"` 12 | 13 | Store struct { 14 | Driver string `yaml:"driver"` 15 | Args map[string]any `yaml:"args"` 16 | } `yaml:"store"` 17 | 18 | Embedder struct { 19 | Enabled bool `yaml:"enabled"` 20 | Driver string `yaml:"driver"` 21 | Args map[string]any `yaml:"args"` 22 | } `yaml:"embedder"` 23 | } 24 | 25 | func NewFromFile(filename string) (*Config, error) { 26 | content, err := os.ReadFile(filename) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | content = []byte(os.ExpandEnv(string(content))) 32 | 33 | var cfg Config 34 | 35 | if err := yaml.Unmarshal(content, &cfg); err != nil { 36 | return nil, err 37 | } 38 | 39 | return &cfg, nil 40 | } 41 | -------------------------------------------------------------------------------- /internals/embeddings/driver.go: -------------------------------------------------------------------------------- 1 | package embeddings 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/alash3al/vecdb/internals/vector" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | driversMap = map[string]Driver{} 12 | driversMapMutex = &sync.RWMutex{} 13 | ) 14 | 15 | type Driver interface { 16 | Open(args map[string]any) error 17 | TextEmbedding(ctx context.Context, content string) (vector.Vec, error) 18 | Close() error 19 | } 20 | 21 | func Register(name string, driver Driver) { 22 | driversMapMutex.Lock() 23 | defer driversMapMutex.Unlock() 24 | 25 | if _, found := driversMap[name]; found { 26 | panic(fmt.Sprintf("specified embeddings driver %s exists", name)) 27 | } 28 | 29 | driversMap[name] = driver 30 | } 31 | 32 | func Open(name string, args map[string]any) (Driver, error) { 33 | driversMapMutex.RLock() 34 | defer driversMapMutex.RUnlock() 35 | 36 | driver, found := driversMap[name] 37 | if !found { 38 | return nil, fmt.Errorf("embeddings driver %s not found", name) 39 | } 40 | 41 | if err := driver.Open(args); err != nil { 42 | return nil, err 43 | } 44 | 45 | return driver, nil 46 | } 47 | -------------------------------------------------------------------------------- /internals/embeddings/drivers/gemini/driver.go: -------------------------------------------------------------------------------- 1 | package gemini 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/alash3al/vecdb/internals/embeddings" 7 | "github.com/alash3al/vecdb/internals/vector" 8 | "github.com/google/generative-ai-go/genai" 9 | "google.golang.org/api/option" 10 | ) 11 | 12 | var _ embeddings.Driver = (*Driver)(nil) 13 | 14 | type Driver struct { 15 | client *genai.Client 16 | textEmbeddingModel string 17 | } 18 | 19 | func (d *Driver) Open(args map[string]any) error { 20 | apiKey, ok := args["api_key"].(string) 21 | if !ok { 22 | return fmt.Errorf("unable to find gemini `args.api_key`") 23 | } 24 | 25 | textEmbeddingModel, ok := args["text_embedding_model"].(string) 26 | if !ok { 27 | return fmt.Errorf("unable to find gemini `args.text_embedding_model`") 28 | } 29 | 30 | ctx := context.Background() 31 | client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | d.client = client 37 | d.textEmbeddingModel = textEmbeddingModel 38 | 39 | return nil 40 | } 41 | 42 | func (d *Driver) TextEmbedding(ctx context.Context, content string) (vector.Vec, error) { 43 | res, err := d.client. 44 | EmbeddingModel(d.textEmbeddingModel). 45 | EmbedContent(ctx, genai.Text(content)) 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | var vec vector.Vec 52 | 53 | for _, f := range res.Embedding.Values { 54 | vec = append(vec, float64(f)) 55 | } 56 | 57 | return vec, nil 58 | } 59 | 60 | func (d *Driver) Close() error { 61 | return d.client.Close() 62 | } 63 | -------------------------------------------------------------------------------- /internals/embeddings/drivers/gemini/init.go: -------------------------------------------------------------------------------- 1 | package gemini 2 | 3 | import "github.com/alash3al/vecdb/internals/embeddings" 4 | 5 | func init() { 6 | embeddings.Register("gemini", &Driver{}) 7 | } 8 | -------------------------------------------------------------------------------- /internals/http/embeddings.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/alash3al/vecdb/internals/embeddings" 5 | "github.com/alash3al/vecdb/internals/store" 6 | "github.com/go-playground/validator/v10" 7 | "github.com/gofiber/fiber/v2" 8 | "net/http" 9 | ) 10 | 11 | func EmbeddingsTextWrite(validate *validator.Validate, embedder embeddings.Driver, db store.Driver) fiber.Handler { 12 | return func(ctx *fiber.Ctx) error { 13 | if embedder == nil { 14 | return ctx.SendStatus(http.StatusNotImplemented) 15 | } 16 | 17 | var input struct { 18 | Bucket string `json:"bucket" validate:"required"` 19 | Key string `json:"key" validate:"required"` 20 | Content string `json:"content" validate:"required"` 21 | } 22 | 23 | if err := ctx.BodyParser(&input); err != nil { 24 | return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{ 25 | "error": err.Error(), 26 | "hint": `seems, you didn't specify a valid json body {"bucket": "", "key": "", "content": ""}`, 27 | }) 28 | } 29 | 30 | if err := validate.Struct(&input); err != nil { 31 | return ctx.Status(http.StatusUnprocessableEntity).JSON(fiber.Map{ 32 | "error": err.Error(), 33 | "hint": `the required JSON body fields are {"bucket": "", "key": "", "content": ""}`, 34 | }) 35 | } 36 | 37 | vec, err := embedder.TextEmbedding(ctx.Context(), input.Content) 38 | if err != nil { 39 | return ctx.Status(http.StatusInternalServerError).JSON(fiber.Map{ 40 | "error": err.Error(), 41 | "hint": `I was unable to perform TextEmbedding(), kindly check the embedder configurations`, 42 | }) 43 | } 44 | 45 | if err := db.Put(input.Bucket, input.Key, vec); err != nil { 46 | return ctx.Status(http.StatusInternalServerError).JSON(fiber.Map{ 47 | "error": err.Error(), 48 | "hint": `I was unable to write to the data store`, 49 | }) 50 | } 51 | 52 | return ctx.SendStatus(http.StatusOK) 53 | } 54 | } 55 | 56 | func EmbeddingsTextSearch(validate *validator.Validate, embedder embeddings.Driver, db store.Driver) fiber.Handler { 57 | return func(ctx *fiber.Ctx) error { 58 | if embedder == nil { 59 | return ctx.SendStatus(http.StatusNotImplemented) 60 | } 61 | 62 | var input struct { 63 | Bucket string `json:"bucket" validate:"required"` 64 | Content string `json:"content" validate:"required"` 65 | MinCosineSimilarity *float64 `json:"min_cosine_similarity" validate:"required"` 66 | MaxResultCount *int64 `json:"max_result_count" validate:"required"` 67 | } 68 | 69 | if err := ctx.BodyParser(&input); err != nil { 70 | return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{ 71 | "error": err.Error(), 72 | "hint": `seems, you didn't specify a valid json body {"bucket": "", "content": "", "min_cosine_similarity": , "max_result_count": }`, 73 | }) 74 | } 75 | 76 | if err := validate.Struct(&input); err != nil { 77 | return ctx.Status(http.StatusUnprocessableEntity).JSON(fiber.Map{ 78 | "error": err.Error(), 79 | "hint": `the required JSON body fields are {"bucket": "", "content": "", "min_cosine_similarity": , "max_result_count": }`, 80 | }) 81 | } 82 | 83 | vec, err := embedder.TextEmbedding(ctx.Context(), input.Content) 84 | if err != nil { 85 | return ctx.Status(http.StatusInternalServerError).JSON(fiber.Map{ 86 | "error": err.Error(), 87 | "hint": `I was unable to perform TextEmbedding(), kindly check the embedder configurations`, 88 | }) 89 | } 90 | 91 | result, err := db.Query(store.VectorQueryInput{ 92 | Bucket: input.Bucket, 93 | Vector: vec, 94 | MinCosineSimilarity: *input.MinCosineSimilarity, 95 | MaxResultCount: *input.MaxResultCount, 96 | }) 97 | 98 | if err != nil { 99 | return ctx.Status(http.StatusInternalServerError).JSON(fiber.Map{ 100 | "error": err.Error(), 101 | "hint": "I was unable to find the data in the data store", 102 | }) 103 | } 104 | 105 | if result.Items == nil { 106 | result.Items = []store.VectorQueryResultItem{} 107 | } 108 | 109 | return ctx.Status(http.StatusOK).JSON(fiber.Map{ 110 | "result": result, 111 | }) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /internals/http/home.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "github.com/gofiber/fiber/v2" 4 | 5 | func Home() fiber.Handler { 6 | return func(ctx *fiber.Ctx) error { 7 | return ctx.JSON(fiber.Map{ 8 | "message": "I'm Live!", 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internals/http/vector.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/alash3al/vecdb/internals/store" 5 | "github.com/alash3al/vecdb/internals/vector" 6 | "github.com/go-playground/validator/v10" 7 | "github.com/gofiber/fiber/v2" 8 | "net/http" 9 | ) 10 | 11 | func VectorWrite(validate *validator.Validate, db store.Driver) fiber.Handler { 12 | return func(ctx *fiber.Ctx) error { 13 | var input struct { 14 | Bucket string `json:"bucket" validate:"required"` 15 | Key string `json:"key" validate:"required"` 16 | Vector vector.Vec `json:"vector" validate:"required"` 17 | } 18 | 19 | if err := ctx.BodyParser(&input); err != nil { 20 | return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{ 21 | "error": err.Error(), 22 | "hint": `seems, you didn't specify a valid json body {"bucket": "", "key": "", "vector": "List"}`, 23 | }) 24 | } 25 | 26 | if err := validate.Struct(&input); err != nil { 27 | return ctx.Status(http.StatusUnprocessableEntity).JSON(fiber.Map{ 28 | "error": err.Error(), 29 | "hint": `the required JSON body fields are {"bucket": "", "key": "", "vector": "List"}`, 30 | }) 31 | } 32 | 33 | if err := db.Put(input.Bucket, input.Key, input.Vector); err != nil { 34 | return ctx.Status(http.StatusInternalServerError).JSON(fiber.Map{ 35 | "error": err.Error(), 36 | "hint": "I was unable to write the data into the data store", 37 | }) 38 | } 39 | 40 | return ctx.SendStatus(http.StatusOK) 41 | } 42 | } 43 | 44 | func VectorSearch(validate *validator.Validate, db store.Driver) fiber.Handler { 45 | return func(ctx *fiber.Ctx) error { 46 | var input struct { 47 | Bucket string `json:"bucket" validate:"required"` 48 | Vector vector.Vec `json:"vector" validate:"required"` 49 | MinCosineSimilarity *float64 `json:"min_cosine_similarity" validate:"required"` 50 | MaxResultCount *int64 `json:"max_result_count" validate:"required"` 51 | } 52 | 53 | if err := ctx.BodyParser(&input); err != nil { 54 | return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{ 55 | "error": err.Error(), 56 | "hint": `seems, you didn't specify a valid json body {"bucket": "", "vector": "List", "min_cosine_similarity": , "max_result_count": }`, 57 | }) 58 | } 59 | 60 | if err := validate.Struct(&input); err != nil { 61 | return ctx.Status(http.StatusUnprocessableEntity).JSON(fiber.Map{ 62 | "error": err.Error(), 63 | "hint": `the required JSON body fields are {"bucket": "", "vector": "List", "min_cosine_similarity": , "max_result_count": }`, 64 | }) 65 | } 66 | 67 | result, err := db.Query(store.VectorQueryInput{ 68 | Bucket: input.Bucket, 69 | Vector: input.Vector, 70 | MinCosineSimilarity: *input.MinCosineSimilarity, 71 | MaxResultCount: *input.MaxResultCount, 72 | }) 73 | 74 | if err != nil { 75 | return ctx.Status(http.StatusInternalServerError).JSON(fiber.Map{ 76 | "error": err.Error(), 77 | "hint": "I was unable to find the data in the data store", 78 | }) 79 | } 80 | 81 | if result.Items == nil { 82 | result.Items = []store.VectorQueryResultItem{} 83 | } 84 | 85 | return ctx.Status(http.StatusOK).JSON(fiber.Map{ 86 | "result": result, 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /internals/store/driver.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "fmt" 5 | "github.com/alash3al/vecdb/internals/vector" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | driversMap = map[string]Driver{} 11 | driversMapMutex = &sync.RWMutex{} 12 | ) 13 | 14 | type Driver interface { 15 | Open(args map[string]any) error 16 | Put(bucket string, key string, vec vector.Vec) error 17 | Delete(bucket string, key string) error 18 | Query(q VectorQueryInput) (*VectorQueryResult, error) 19 | Close() error 20 | } 21 | 22 | func Register(name string, driver Driver) { 23 | driversMapMutex.Lock() 24 | defer driversMapMutex.Unlock() 25 | 26 | if _, found := driversMap[name]; found { 27 | panic(fmt.Sprintf("specified store driver %s exists", name)) 28 | } 29 | 30 | driversMap[name] = driver 31 | } 32 | 33 | func Open(name string, args map[string]any) (Driver, error) { 34 | driversMapMutex.RLock() 35 | defer driversMapMutex.RUnlock() 36 | 37 | driver, found := driversMap[name] 38 | if !found { 39 | return nil, fmt.Errorf("store driver %s not found", name) 40 | } 41 | 42 | if err := driver.Open(args); err != nil { 43 | return nil, err 44 | } 45 | 46 | return driver, nil 47 | } 48 | -------------------------------------------------------------------------------- /internals/store/drivers/bolt/driver.go: -------------------------------------------------------------------------------- 1 | package bolt 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/alash3al/vecdb/internals/store" 8 | "github.com/alash3al/vecdb/internals/vector" 9 | bolt "go.etcd.io/bbolt" 10 | "golang.org/x/exp/slices" 11 | ) 12 | 13 | var _ store.Driver = (*Driver)(nil) 14 | var errStop = errors.New("STOP") 15 | 16 | type Driver struct { 17 | db *bolt.DB 18 | } 19 | 20 | type Value struct { 21 | Vector vector.Vec 22 | Magnitude float64 23 | } 24 | 25 | func (d *Driver) Open(args map[string]any) error { 26 | filename, ok := args["database"].(string) 27 | if !ok { 28 | return fmt.Errorf("unable to find the store `args.database`") 29 | } 30 | 31 | db, err := bolt.Open(filename, 0600, bolt.DefaultOptions) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | d.db = db 37 | 38 | return nil 39 | } 40 | 41 | func (d *Driver) Put(bucket string, key string, vec vector.Vec) error { 42 | keyBytes := []byte(key) 43 | valueBytes, err := json.Marshal(Value{ 44 | Vector: vec, 45 | Magnitude: vec.Magnitude(), 46 | }) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | return d.db.Update(func(tx *bolt.Tx) error { 52 | bucket, err := tx.CreateBucketIfNotExists([]byte(bucket)) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | return bucket.Put(keyBytes, valueBytes) 58 | }) 59 | } 60 | 61 | func (d *Driver) Delete(bucket string, key string) error { 62 | return d.db.Update(func(tx *bolt.Tx) error { 63 | bucket, err := tx.CreateBucketIfNotExists([]byte(bucket)) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | return bucket.Delete([]byte(key)) 69 | }) 70 | } 71 | 72 | func (d *Driver) Query(q store.VectorQueryInput) (*store.VectorQueryResult, error) { 73 | var err error 74 | 75 | result := new(store.VectorQueryResult) 76 | 77 | err = d.db.Update(func(tx *bolt.Tx) error { 78 | _, err := tx.CreateBucketIfNotExists([]byte(q.Bucket)) 79 | 80 | return err 81 | }) 82 | 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | err = d.db.View(func(tx *bolt.Tx) error { 88 | bucket := tx.Bucket([]byte(q.Bucket)) 89 | 90 | if bucket == nil { 91 | return bolt.ErrBucketNotFound 92 | } 93 | 94 | return bucket.ForEach(func(k, v []byte) error { 95 | var currentIterationValue Value 96 | 97 | if err := json.Unmarshal(v, ¤tIterationValue); err != nil { 98 | return err 99 | } 100 | 101 | if cosineSimilarity := currentIterationValue.Vector.CosineSimilarity(q.Vector); cosineSimilarity >= q.MinCosineSimilarity { 102 | result.Items = append(result.Items, store.VectorQueryResultItem{ 103 | Key: string(k), 104 | CosineSimilarity: cosineSimilarity, 105 | }) 106 | } 107 | 108 | return nil 109 | }) 110 | }) 111 | 112 | if err != nil && !errors.Is(err, errStop) { 113 | return nil, err 114 | } 115 | 116 | slices.SortFunc[store.VectorQueryResultItem](result.Items, func(a, b store.VectorQueryResultItem) bool { 117 | return a.CosineSimilarity > b.CosineSimilarity 118 | }) 119 | 120 | if q.MaxResultCount > 0 { 121 | result.Items = result.Items[0:q.MaxResultCount] 122 | } 123 | 124 | return result, nil 125 | } 126 | 127 | func (d *Driver) Close() error { 128 | return d.db.Close() 129 | } 130 | -------------------------------------------------------------------------------- /internals/store/drivers/bolt/init.go: -------------------------------------------------------------------------------- 1 | package bolt 2 | 3 | import "github.com/alash3al/vecdb/internals/store" 4 | 5 | func init() { 6 | store.Register("bolt", &Driver{}) 7 | } 8 | -------------------------------------------------------------------------------- /internals/store/query.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/alash3al/vecdb/internals/vector" 5 | ) 6 | 7 | type VectorQueryInput struct { 8 | Bucket string 9 | Vector vector.Vec 10 | MinCosineSimilarity float64 11 | MaxResultCount int64 12 | } 13 | 14 | type VectorQueryResult struct { 15 | Items []VectorQueryResultItem `json:"items" json:"items,omitempty"` 16 | } 17 | 18 | type VectorQueryResultItem struct { 19 | Key string `json:"key"` 20 | CosineSimilarity float64 `json:"cosine_similarity"` 21 | } 22 | -------------------------------------------------------------------------------- /internals/vector/vector.go: -------------------------------------------------------------------------------- 1 | package vector 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | type Vec []float64 8 | 9 | func (v Vec) DotProduct(b Vec) (result float64) { 10 | if len(v) < 1 { 11 | return 0 12 | } 13 | 14 | if len(v) != len(b) { 15 | return 0 16 | } 17 | 18 | for i := range b { 19 | result += v[i] * b[i] 20 | } 21 | 22 | return result 23 | } 24 | 25 | func (v Vec) Magnitude() float64 { 26 | var sum float64 27 | 28 | for _, n := range v { 29 | sum += n * n 30 | } 31 | 32 | return math.Abs(math.Sqrt(sum)) 33 | } 34 | 35 | func (v Vec) CosineSimilarity(b Vec) float64 { 36 | magV := v.Magnitude() 37 | magB := b.Magnitude() 38 | 39 | if (magV == 0) || (magB == 0) { 40 | return 0 41 | } 42 | 43 | return v.DotProduct(b) / (magV * magB) 44 | } 45 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/alash3al/vecdb/internals/config" 6 | "github.com/alash3al/vecdb/internals/embeddings" 7 | "github.com/alash3al/vecdb/internals/http" 8 | "github.com/alash3al/vecdb/internals/store" 9 | "github.com/go-playground/validator/v10" 10 | "github.com/gofiber/fiber/v2" 11 | "log" 12 | // embedder drivers 13 | _ "github.com/alash3al/vecdb/internals/embeddings/drivers/gemini" 14 | 15 | // store drivers 16 | _ "github.com/alash3al/vecdb/internals/store/drivers/bolt" 17 | ) 18 | 19 | var ( 20 | flagConfigFilename = flag.String("config", "./config.yml", "the configuration filename") 21 | ) 22 | 23 | func main() { 24 | flag.Parse() 25 | 26 | var cfg *config.Config 27 | var db store.Driver 28 | var embedder embeddings.Driver 29 | var err error 30 | 31 | { 32 | cfg, err = config.NewFromFile(*flagConfigFilename) 33 | if err != nil { 34 | log.Fatal(err.Error()) 35 | } 36 | } 37 | 38 | { 39 | db, err = store.Open(cfg.Store.Driver, cfg.Store.Args) 40 | if err != nil { 41 | log.Fatal(err.Error()) 42 | } 43 | } 44 | 45 | if cfg.Embedder.Enabled { 46 | embedder, err = embeddings.Open(cfg.Embedder.Driver, cfg.Embedder.Args) 47 | if err != nil { 48 | log.Fatal(err.Error()) 49 | } 50 | } 51 | 52 | validate := validator.New(validator.WithRequiredStructEnabled()) 53 | 54 | app := fiber.New() 55 | 56 | app.Get("/", http.Home()) 57 | 58 | app.Post("/v1/vectors/write", http.VectorWrite(validate, db)) 59 | app.Post("/v1/vectors/search", http.VectorSearch(validate, db)) 60 | 61 | app.Post("/v1/embeddings/text/write", http.EmbeddingsTextWrite(validate, embedder, db)) 62 | app.Post("/v1/embeddings/text/search", http.EmbeddingsTextSearch(validate, embedder, db)) 63 | 64 | log.Fatal(app.Listen(cfg.Server.ListenAddr)) 65 | } 66 | --------------------------------------------------------------------------------