Six metrics. Exact packed search or optional HNSW. Text embedding. Atomic gob/JSON persistence. Zero allocations on distance computation. All pure Go.
package main import "github.com/BackendStack21/go-vector/pkg/vector" func main() { store := vector.NewStore(vector.CosineDistance) store.Add("cat", vector.Vector{1.0, 0.8, 0.1}) store.Add("dog", vector.Vector{0.9, 0.7, 0.1}) store.Add("car", vector.Vector{0.1, 0.0, 0.9}) query := vector.Vector{1.0, 0.9, 0.1} results := store.Search(query, 2) for _, r := range results { fmt.Printf("%s: %.4f\n", r.ID, r.Distance) } // cat: 0.0005 // dog: 0.0026 }
Ten vector operations, six distance metrics, and a production-ready in-memory vector store. All zero-allocation on read paths.
Optional packages add an HNSW approximate index, int8 quantization, and a mutex-wrapped store. The core pkg/vector API stays stdlib-only and backward compatible with v1.3 gob/JSON files.
Dot product, norm, normalize, add, subtract, scale. Full suite of vector operations with zero-overhead []float32.
Cosine, Euclidean, Manhattan, Dot Product, Chebyshev, Hamming. Single-pass computation. Choose lower-is-better or higher-is-better sorting.
Add, upsert, metadata, filters, radius, and top-k. Cloned outputs — no accidental mutation. LockedStore when writers share the index.
Built-in Random Projections embedder (Johnson-Lindenstrauss). Bring your own OpenAI or Ollama adapter via the Embedder interface.
Gob or JSON, path or io.Writer. SaveAtomic for temp+rename. v1.3 files still load. ~60MB per 10K vectors at 1536d.
Standard library only. No CGo, no BLAS, no syscalls, no I/O. Compiles anywhere Go runs.
Pure-Go approximate index in pkg/hnsw. Same Vector type. Exact Store stays brute-force.
pkg/quantize packs vectors to int8 without changing float32 Store precision. Optional sibling, not a silent mode flag.
SearchOpts adds predicates, metadata equality, score thresholds, parallel scan, and ID-only results that skip cloning.
Built-in Random Projections embedder using the Johnson-Lindenstrauss lemma. Deterministic, zero-dependency, ~10µs per document. Or bring your own via the Embedder interface.
Sparse random projection matrix — each vocabulary token contributes to ~33% of output dimensions. Vocabulary built from your corpus. Output always L2-normalized.
rp := vector.NewRandomProjections(256) rp.Fit(corpus) v, _ := rp.Embed("machine learning") // v is a 256-dim normalized Vector
Implement Embedder for a custom backend. Batching is a separate BatchEmbedder so existing implementations keep compiling. Built-in HTTP talks to OpenAI, Ollama, LM Studio, and Azure-style paths.
type Embedder interface { Embed(text string) (Vector, error) Dims() int } e := vector.NewHTTPEmbedder(url, model, 0, vector.WithRetry(3))
Uniform-length stores pack into a row-major array with cached norms. When n outgrows a linear scan, switch to HNSW without changing embedders or Vector.
Sibling package, stdlib plus pkg/vector only. Own gob format. RecallAgainst compares to an exact store. Re-rank candidates with vector.Rerank when you want exact scores on a shortlist.
idx := hnsw.New(384, vector.CosineDistance) idx.Add("doc", vec) hits := idx.Search(query, 10) // optional exact rescore hits = vector.Rerank(query, hits, vector.CosineDistance, 5)
Quantize for memory. Int8Store is a new type so float32 search cannot silently lose precision. Reconstruct or keep a float32 store for exact ranking.
q, scale := quantize.ToInt8(vec) qs := quantize.NewInt8Store(vector.CosineDistance) qs.Add("doc", vec) qs.Search(query, 10)
Gob-encode your entire store to a file. Restore with metric, metadata, and all data intact. JSON export for debugging. v1.3 files still load.
Binary encoding via encoding/gob. SaveAtomic writes a temp file, fsyncs, then renames. Streaming WriteTo / ReadFrom for any io.Writer. Corrupt length mismatches are rejected.
store.SaveAtomic("/data/vectors.db") restored := vector.NewStore(...) restored.Load("/data/vectors.db") // all data + metric restored
Export as indented JSON for debugging, inspection, or cross-language interop. Same roundtrip guarantees.
store.SaveJSON("/data/vectors.json") restored.LoadJSON("/data/vectors.json")
Save and restore the RandomProjections embedder — vocabulary, projection matrix, dimensions. No refitting needed across restarts.
rp.SaveEmbedder("/data/embedder.gob") restored, _ := vector.LoadEmbedder("/data/embedder.gob") // Same text → identical vector
All benchmarks at 1536 dimensions (typical embedding size) on AMD EPYC. Single-pass Cosine and Euclidean — no intermediate vector allocations.
| Operation | Latency | Allocs |
|---|---|---|
| Dot | 7.5 µs | 0 |
| Cosine | 9.4 µs | 0 |
| Euclidean | 8.7 µs | 0 |
| Manhattan | 9.4 µs | 0 |
| Search 100 vectors | 3.2 ms | 63 KB |
| Search 1,000 vectors | 28.5 ms | 74 KB |
| Search 10,000 vectors | 315 ms | 185 KB |
Brute-force search is O(n·d). Suitable for datasets up to ~100K vectors. Use pkg/hnsw beyond that, or SearchIDs when you do not need cloned vectors.
Pure float32 math in the core. No CGo, no syscalls. Query paths return zero/nil instead of panicking. Persistence validates structure before replacing in-memory state.
LockedStore or an external mutex when writers are involved.
One import. Zero-dependency core. Optional HNSW, ONNX, and int8 siblings.
go get github.com/BackendStack21/go-vector
go test github.com/BackendStack21/go-vector/pkg/vector/ -cover