The same seven-vertex graph as rag-bm25, with the
retriever swapped from BM25 to Gemini embeddings + cosine similarity. The retriever calls
library.ResolveEmbeddingClient — never reads GEMINI_API_KEY on
its own — so a registered EmbeddingClientFactory determines where the
credential actually comes from.
What this example teaches
library.Retriever that builds and queries an in-memory vector index at startuplibrary.ResolveEmbeddingClient(ctx, provider, model) instead of reading env vars directlyEmbeddingClientFactory decouples the retriever from credential sourcinggemini-embedding-001 vectorsSwap the retriever, leave the graph alone. The downstream prompt assembly, citation
parsing, and citation validation work the same regardless of how documents were ranked.
That's the value of the Retriever abstraction.
At startup, the example embeds every .txt
passage with library.ResolveEmbeddingClient(ctx, "gemini", "gemini-embedding-001")
and stores vectors in memory. Each request embeds the query the same way, then ranks the
corpus by cosine similarity. The retriever never touches GEMINI_API_KEY
directly — credentials flow through the registered factory.
func (r *EmbedRetriever) Retrieve(ctx context.Context, q string, k int) ([]library.Document, error) {
client, err := library.ResolveEmbeddingClient(ctx, "gemini", "gemini-embedding-001")
if err != nil {
return nil, err
}
vecs, err := client.Embed(ctx, []string{q})
if err != nil { return nil, err }
return r.topKByCosine(vecs[0], k), nil
}
The bundled EnvEmbeddingClientFactory
supports provider="gemini" out of the box via GEMINI_API_KEY.
To plug in Voyage, OpenAI, Cohere, or anything else, register your own factory:
// uses the bundled gemini-only factory — no extra wiring
docs, _ := loadKB("testdata/kb")
retriever, _ := NewEmbedRetriever(context.Background(), docs)
library.SetDefaultRetriever(retriever)
// to use a different embedding provider:
// library.SetDefaultEmbeddingClientFactory(&VoyageFactory{...})
// or library.RegisterEmbeddingClientFactory("voyage-prod", ...)
// and set client_factory_id on the RetrieveOp vertex.
export CLAUDE_API_KEY=<your key>
export GEMINI_API_KEY=<your key> # for embeddings
go run ./examples/rag-gemini-embed --question "How is data encrypted at rest?"
The same binary is dual-mode. Pass --mcp and instead of running once and exiting it speaks the Model Context Protocol over stdin/stdout, exposing the entire workflow as a single MCP tool, answer_from_kb. The Go SDK derives the tool's input schema from the workflow's UserInput struct and validates every tools/call request against it. The KB is embedded once at server startup, so every subsequent tool call reuses the in-memory Gemini vectors.
# Speak MCP over stdin/stdout instead of running once
go run ./examples/rag-gemini-embed --mcp
Register it with any MCP client by pointing the client at that command:
{
"mcpServers": {
"rag-gemini-embed": {
"command": "go",
"args": ["run", "./examples/rag-gemini-embed", "--mcp"]
}
}
}