# ragChatbot (/docs/recipes/rag-chatbot)



`ragChatbot` is `simpleChatbot` with a retrieval preamble. It
takes a consumer-supplied `retriever`, calls it once per `ask()`,
prepends the returned chunks to the user message as
`Context:\n[1] ...\n[2] ...`, then delegates to the underlying
`simpleChatbot`. When the retriever returns no chunks or throws,
the message passes through unmodified — the bot stays useful if
retrieval is down.

Best fit: **a knowledge-base assistant** or **docs
assistant**. Reach for this when answers must be grounded in
your documents and you already have a vector store or
full-text index.

## Quickstart [#quickstart]

```ts
import { ragChatbot } from "@pleach/recipes/rag";

const bot = ragChatbot({
  retriever: async (query, { topK = 4 } = {}) => {
    return await myVectorDb.search(query, { topK });
  },
});

console.log(await bot.ask("what's our refund policy?"));
```

The recipe does not bind a vector store. Bring your own —
pgvector, Pinecone, Weaviate, a Postgres FTS index, an
S3-backed JSON snapshot, anything that satisfies the
`Retriever` signature.

## What it does [#what-it-does]

On each `ask(message)`:

1. Invoke `retriever(message, { topK })` once.
2. If the result is a non-empty array, build a context
   preamble of the form
   `Context:\n[1] (id) content\n[2] (id) content\n...`
   and prepend it to the user message.
3. Delegate to the underlying `simpleChatbot.ask()` with the
   augmented message.

If the retriever throws or returns `[]`, step 2 is skipped and
the raw user message is forwarded. The retriever runs exactly
once per turn — no automatic re-query, no streaming retrieval.

## Config reference [#config-reference]

```ts
interface RetrievedChunk {
  /** Stable identifier for citation rendering. */
  id: string;
  /** Raw text content surfaced to the LLM. */
  content: string;
  /** Optional similarity score (recipe is score-agnostic). */
  score?: number;
  /** Free-form metadata — source URL, page number, section. */
  metadata?: Record<string, unknown>;
}

type Retriever = (
  query: string,
  opts?: { topK?: number },
) => Promise<RetrievedChunk[]>;

interface RagChatbotConfig extends SimpleChatbotConfig {
  retriever: Retriever;
  /** Default topK passed to the retriever. Defaults to 4. */
  topK?: number;
}
```

`RagChatbotConfig` extends `SimpleChatbotConfig`, so
`systemPrompt`, `orchestratorConfig`, and all
`CreatePleachRuntimeConfig` fields work identically.

## Common gotchas [#common-gotchas]

* **The retriever is consumer-owned.** The recipe does not
  rate-limit, cache, deduplicate, or retry. If your vector
  store has its own back-pressure or budget rules, enforce
  them inside the retriever.
* **`score` is not used for ordering.** Chunks are inserted
  in the order the retriever returns them. If you want a
  different ranking, sort inside the retriever before
  returning.
* **No automatic citation rendering.** The numbered `[N]`
  preamble lets the LLM cite chunks by index in its reply,
  but the recipe does not parse `[N]` references out of the
  assistant text. Build that surface in your UI layer.
* **Retriever errors are swallowed.** A thrown error falls
  through to the plain-delegation path. If you need
  observability on retrieval failures, log inside the
  retriever or wrap it.

## See also [#see-also]

* [`@pleach/recipes` overview](/docs/recipes-pleach-recipes) —
  every recipe in one page.
* [`simpleChatbot`](/docs/recipes/simple-chatbot) — the base
  this recipe extends.
* [`Internal knowledge agent`](/docs/internal-knowledge-agent)
  — full-system pattern for RAG with audit and lineage.
