Introduction
Composable RAG pipeline engine for Go.
Weave is a Go library that handles the full document lifecycle for Retrieval-Augmented Generation: ingest documents, chunk text, generate embeddings, store vectors, retrieve relevant context, and assemble prompts.
Weave is a library — not a service. You bring your own database, vector store, embedder, and HTTP server. Weave provides the RAG plumbing.
What it does
- Document ingestion — Load, chunk, embed, and store documents in a single
engine.Ingestcall. Supports single and batch ingestion with configurable concurrency. - Semantic retrieval — Retrieve the most relevant chunks via cosine similarity, MMR (Maximal Marginal Relevance), or hybrid search across one or multiple collections.
- Collection management — Organize documents into named collections with per-collection embedding models, chunking strategies, and custom metadata.
- Pluggable components — Every subsystem (Loader, Chunker, Embedder, Retriever, VectorStore, MetadataStore) is a Go interface. Swap any component with one line.
- Multi-tenant isolation — Every collection, document, and chunk is scoped to a tenant via context. Cross-tenant queries are structurally impossible.
- Extension system — Hook into 14 lifecycle events (ingestion, retrieval, collection, reindex) for metrics, audit trails, or custom processing.
- Context assembly — Build LLM prompts from retrieved chunks with token budgeting and citation tracking.
- Pluggable stores — In-memory (testing), PostgreSQL (bun ORM), SQLite, and pgvector. All implement the same composite store interfaces.
- Forge integration — Drop-in
forge.Extensionwith DI-injectedEngineand auto-registered HTTP routes. - HTTP API — 12 endpoints for collections, documents, retrieval, and hybrid search.
Design philosophy
Library, not service. Weave is a set of Go packages you import. You control main, the database connection, and the process lifecycle.
Interfaces over implementations. Every subsystem defines a Go interface. Swap any storage or embedding backend with a single type change.
Composable stores. Each subsystem (metadata, vector) has its own store interface. Pass the same backend value wherever a store is needed.
Tenant-scoped by design. weave.WithTenant and weave.WithApp inject context that is enforced at every layer — no cross-tenant data is accessible regardless of what the caller passes.
TypeID everywhere. All entities use type-prefixed, K-sortable, UUIDv7-based identifiers. Passing a doc_ ID where a col_ ID is expected is a compile error.
Quick look
package main
import (
"context"
"fmt"
"log"
"github.com/xraph/weave"
memstore "github.com/xraph/weave/store/memory"
memvec "github.com/xraph/weave/vectorstore/memory"
)
func main() {
ctx := context.Background()
ctx = weave.WithTenant(ctx, "tenant-1")
ctx = weave.WithApp(ctx, "myapp")
engine, err := weave.NewEngine(
weave.WithStore(memstore.New()),
weave.WithVectorStore(memvec.New()),
weave.WithEmbedder(myEmbedder),
)
if err != nil {
log.Fatal(err)
}
// Create a collection.
col, _ := engine.CreateCollection(ctx, weave.CreateCollectionInput{
Name: "product-docs",
ChunkSize: 512,
ChunkOverlap: 50,
})
// Ingest a document.
doc, _ := engine.Ingest(ctx, col.ID, weave.IngestInput{
Title: "Return Policy",
Content: "Our return policy allows returns within 30 days...",
Source: "faq.md",
})
fmt.Printf("ingested doc=%s state=%s chunks=%d\n",
doc.ID, doc.State, doc.ChunkCount)
// Retrieve relevant chunks.
results, _ := engine.Retrieve(ctx, col.ID, &weave.RetrieveInput{
Query: "How long do I have to return an item?",
TopK: 3,
})
for _, r := range results {
fmt.Printf("[%.2f] %s\n", r.Score, r.Content[:60])
}
}