Sparsi provides a robust architectural foundation for RAG-driven MCP
servers. It ships an opinionated retrieval layer: a single Retriever
interface, two retrieval operators that thread query and filters through the
workflow, and an EmbeddingClientFactory abstraction for vector-store
backends.
You implement Retriever; Sparsi calls it. The library never sees inside
— your retriever is free to use BM25, embeddings + a vector store, a hosted search API,
or anything else.
type Retriever interface {
Retrieve(ctx context.Context, query string, k int) ([]Document, error)
}
type Document struct {
ID string
Content string
Score float64
Metadata map[string]any
}
// framework-documented metadata keys (use these instead of bare strings)
const (
MetadataSource = "source" // filename or document title
MetadataSourceURL = "source_url" // canonical URL
MetadataHighlights = "highlights" // []string of matched snippets
MetadataUpdatedAt = "updated_at" // time.Time (UTC recommended)
)
Register your Retriever before engine.Run:
library.SetDefaultRetriever(r) for a process-wide default, or
library.RegisterRetriever("acme", r) for an id you select from a vertex
param. Implementations must be safe for concurrent Retrieve calls; graph
execution invokes a single retriever from multiple parallel vertices.
Both ops emit two output wires: Documents (full records) and
Texts (the parallel []string of Document.Content).
Wire Texts straight into AI ops that take *[]string; wire
Documents into downstream ops that need metadata.
Security note: queries and filter values are untrusted — they routinely come from upstream AI ops. Retriever implementations must pass them through their backend's parameterized-query / placeholder / typed-filter API. Never string-concatenate them into SQL, search DSLs, or shell commands.
ValidateCitationsOp — the security boundaryLLMs hallucinate citations. Without enforcement, "Sources: file1.txt, file2.txt" at the
bottom of a response might include filenames the model invented or imported from training
data. ValidateCitationsOp filters raw citations against an allow-list of
identifiers and partitions them into Accepted / Rejected.
// 1. Retrieve.
Vertex("retrieve").Op("RetrieveOp").
Params(map[string]string{"k": "3"}).
Input("Query", "question").
Output("Documents", "documents").
// 2. Derive the allow-list from retrieved docs (NOT the full corpus).
Vertex("retrieved_sources").Op("RetrievedSourcesOp").
Input("Documents", "documents").
Output("Sources", "retrieved_sources").
// 3. AI generates the answer with a "Sources: ..." trailer.
Vertex("answer").Op("AIComputeStringToStringOp").Input("Input", "prompt").
Output("Result", "raw_answer").
// 4. Parse the citations out of the response.
Vertex("parse_citations").Op("ParseCitationsOp").
Input("Raw", "raw_answer").
Output("Sources", "sources").
// 5. Filter against the allow-list. Hallucinations land in Rejected.
Vertex("validate_citations").Op("ValidateCitationsOp").
Input("Raw", "sources").
Input("Allowed", "retrieved_sources").
Output("Accepted", "accepted_sources").
Output("Rejected", "rejected_sources").
Build the allow-list from documents actually retrieved, not from the full corpus.
Otherwise a model that hallucinates the filename of a real-but-unretrieved KB document
slips past the check. Surface Rejected via slog at WARN — it's
signal about model behavior, not a graph failure.
Retrievers that embed text — query embedding for cosine similarity, batch embedding for
index build — call library.ResolveEmbeddingClient(ctx, provider, model) instead
of reading env vars directly. The framework installs credentials on ctx from
the vertex's params, then a registered EmbeddingClientFactory materialises a
client.
type EmbeddingClient interface {
Embed(ctx context.Context, texts []string) ([][]float32, error)
}
type EmbeddingClientFactory interface {
Embedder(ctx context.Context, provider, model, ref string) (EmbeddingClient, error)
}
// the bundled default is gemini-only — register a custom factory for
// any other provider (Voyage, OpenAI, Cohere, Vertex, in-house, ...)
library.SetDefaultEmbeddingClientFactory(myFactory)
library.RegisterEmbeddingClientFactory("voyage-prod", voyageFactory)
The bundled EnvEmbeddingClientFactory only supports provider="gemini"
via GEMINI_API_KEY. This is intentionally asymmetric with AI ops (whose bundled
factory handles both Claude and Gemini) — embedding vendors are fragmented and Sparsi
stays out of opinions about SDKs.