Crux
GuidesRetrieval & RAG

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.

PrimitiveJobStart 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.

On this page