Retrieval & RAG
Define knowledge bases, retrieve scored evidence, compose retrieval recipes, and validate grounded citations.
Your model cannot answer questions about your own docs: it has never seen them, and pasting everything into the prompt does not scale. Retrieval fixes that by indexing your knowledge once and turning each question into prompt-ready, scored evidence.
Minimal RAG
import { prompt } from "@use-crux/core";
import { embedding } from "@use-crux/openai";
import { knowledgeBase } from "@use-crux/core/retrieval";
import { inMemoryStorage } from "@use-crux/core/storage";
import { z } from "zod";
const docs = knowledgeBase({
id: "docs",
storage: inMemoryStorage(),
embeddings: embedding({
name: "docs",
model: "text-embedding-3-small",
}),
});
await docs.index([
{
namespace: "docs",
sourceId: "refunds.md",
content: "Refunds are available within 30 days of purchase.",
metadata: { section: "policy" },
},
]);
export const answerDocs = prompt({
id: "answer-docs",
use: [
docs.grounding({
query: ({ input }) => input.question as string,
citations: { required: true, quotes: "required" },
}),
],
input: z.object({ question: z.string() }),
system: "Answer only from the retrieved docs.",
prompt: ({ input }) => input.question,
});When To Drop Down A Layer
The beta API is built around one progressive path:
knowledgeBase() -> retriever() -> retrievalRecipe() -> grounding()Each layer exposes the layer below it. Start with a knowledge base for normal RAG, drop down to a retriever for custom search, use a recipe when one search pass is not enough, and use grounding when an answer must cite allowed evidence.
| Primitive | Job | Start here |
|---|---|---|
knowledgeBase() | Own indexing, storage, embeddings, lifecycle, retrieval, tools, and scoping. | Querying |
retriever() | Query indexed knowledge or a custom backend and return scored hits. | Querying |
retrievalRecipe() | Compose named query fanout, retrieval, reranking, parent expansion, and compression. | Recipes |
grounding() | Inject evidence, expose search tools, and validate citations. | Grounding |
corpus() / indexer() | Build lower-level write paths when you need direct control. | Indexing |
Inspect And Replace Layers
A knowledge base is a facade, not a closed abstraction:
const inspection = docs.inspect();
const direct = docs.retriever({ limit: 6 });
const recipe = docs.recipe({
id: "docs-answer",
steps: [
retrieve({ limit: 20 }),
rerank({ engine: judgeReranker({ name: "docs-judge", model }), topK: 8 }),
],
});
const tools = docs.tools({ prefix: "docs", include: ["search", "getSource"] });You can replace any layer:
import { retriever } from "@use-crux/core/retrieval";
const searchApi = retriever({
id: "search-api",
namespace: "docs",
async retrieve(query, options) {
const results = await internalSearch.search({
query,
limit: options.limit ?? 5,
filter: options.filter,
});
return results.map((result) => ({
namespace: "docs",
source: { id: result.documentId, url: result.url },
chunkId: result.sectionId,
content: result.text,
metadata: result.metadata,
score: result.score,
}));
},
});Recipes For Stronger Retrieval
Use a named recipe when a raw retriever needs broader recall, model-backed ranking, parent context, or prompt-budget compression:
import {
compressToBudget,
fanout,
retrievalRecipe,
retrieve,
rerank,
} from "@use-crux/core/retrieval";
const docsAnswer = retrievalRecipe({
id: "docs-answer",
retriever: docs.retriever(),
model,
steps: [
fanout({ maxQueries: 4 }),
retrieve({ limit: 30 }),
rerank({ engine: judgeReranker({ name: "docs-judge", model }), topK: 8 }),
compressToBudget({ tokens: 3500 }),
],
});
const hits = await docsAnswer.retrieve("How do enterprise refunds work?");
const { trace } = await docsAnswer.retrieveWithTrace(
"How do enterprise refunds work?",
);Recipes also support federation:
const answer = retrievalRecipe({
id: "support-and-docs",
retriever: [
{ retriever: docs.retriever(), weight: 1 },
{ retriever: supportTickets, weight: 0.5 },
],
onSourceError: "skip-with-warning",
steps: [
retrieve({ limit: 20 }),
rerank({ engine: judgeReranker({ name: "docs-judge", model }), topK: 8 }),
],
});Tenancy And Lifecycle
Use scopes when one knowledge base definition serves multiple namespaces. Isolation is structural: scoped handles bind storage keys to the namespace instead of relying on a runtime filter.
const tenantDocs = docs.scope({ namespace: `tenant:${tenantId}` });
await tenantDocs.reindex(sourceDocuments);
await tenantDocs.remove("old-policy.md");
const status = tenantDocs.inspect().lifecycle;Removed sources become unretrievable immediately. If inactive vector rows are retained by policy or adapter limits, inspect() reports that retention.
Evals
Test the callable production generate.task() or stream.task() that uses
your retriever. Cases stay typed from the prompt input, and checks can assert
on structured answers and citations. See the Evals guide.
Related
Embeddings
Dense, sparse, hybrid, preprocessing, truncation, retries, rate limits, and cache.
Ingestion
Text, files, URLs, built-in parsers, OCR hooks, and custom loaders.
Indexing
Lower-level indexing, transforms, chunking, cache, and dry runs.
Chunkers
Structured, plain text, parent-child, semantic, and custom chunking strategies.
Querying
Knowledge bases, direct retrievers, search modes, tools, and custom backends.
Recipes
Named query fanout, retrieve, rerank, parent expansion, compression, and federation.
Grounding
When retrieval must become cited, validated evidence.
Corpus Sync
Repeated indexing jobs, stale deletion, dry runs, source ledgers, and cache control.