---
title: "Knowledge / RAG"
description: "Configure namespace-bound retrieval and admin-only ingestion with the shipped knowledge factories and source types."
---

Knowledge lets an agent retrieve developer-curated content. The chat handler
receives only a read-only `RetrieverFactory`; ingestion uses a separate
write-capable `KnowledgeStoreFactory` in trusted admin code.

> **Resolve namespaces on the server**
>
> A retriever is constructed bound to server-derived namespaces. Its `query`
> method has no namespace parameter. Never derive namespaces from the request body.

## Enable retrieval

```ts
import { createChatHandler } from '@mordn/chat-widget/server';
import { getDefaultEmbedder }
  from '@mordn/chat-widget/server/knowledge';
import { createKnowledgeDrizzleRetriever }
  from '@mordn/chat-widget/server/knowledge/drizzle';

const embedder = getDefaultEmbedder(); // GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY

export const { GET, POST, DELETE } = createChatHandler({
  getUserId: getChatUserId,
  model: anthropic('claude-sonnet-4-5'),
  store: createDrizzleChatStore(),
  retrieval: {
    store: createKnowledgeDrizzleRetriever({ embedder }),
    resolveNamespaces: (ctx) => [
      'agent:support-bot',
      `user:${ctx.userId}:support-bot`,
    ],
    mode: 'tool',
    topK: 5,
    minScore: 0.2,
    vectorWeight: 1,
    citations: true,
  },
});
```

Use `createEmbedder(model, dimensions?)` only when wrapping an AI SDK embedding
model. The no-argument built-in Gemini path is `getDefaultEmbedder()` (or
`createGeminiEmbedder(options)`). Dimensions must match the vector column.

## Retrieval options


- **`store`** · `RetrieverFactory` · required

  Read-only retriever factory.

- **`resolveNamespaces`** · `(ctx) => string[] \| Promise<string[]>` · required

  Trusted namespace resolver.

- **`mode`** · `'tool' \| 'auto'` · default `tool`

  Expose `searchKnowledge` or retrieve every turn.

- **`topK`** · `number` · default `5`

  Result cap; the default store clamps to 20.

- **`minScore`** · `number` · default `0.2`

  Minimum relevance.

- **`vectorWeight`** · `number` · default `1`

  Vector/lexical blend where supported.

- **`citations`** · `boolean` · default `true`

  Emit source-url parts.

- **`buildQuery`** · `(messages, ctx) => string \| Promise<string>`

  Custom query builder for auto mode.

- **`renderContext`** · `(chunks) => string`

  Custom injected context rendering.


## Ingestion

The default docs-aware pipeline is:

```text
SSRF-guarded loader → HTML-to-Markdown or Markdown passthrough
→ heading-aware chunking → content-hash diff → embedding → idempotent upsert
```

Set `docsMode: false` to use the legacy clean-text/chunk-text path.

Supported sources:

```ts
type IngestSource =
  | { type: 'url'; url: string; title?: string }
  | { type: 'sitemap'; url: string; limit?: number }
  | { type: 'crawl'; url: string; depth?: number; maxPages?: number; sameOriginOnly?: boolean }
  | { type: 'llms'; url: string; limit?: number }
  | { type: 'file'; path?: string; fileKey?: string; filename?: string; mediaType?: string }
  | { type: 'text'; text: string; title?: string };
```

> **Warning**
>
> The built-in `file` loader is a storage-key loader: it calls a configured
> `StorageAdapter`, fetches the signed URL, and decodes the response as text. It
> does not read the local filesystem or parse binary PDFs. Extract local/PDF
> content to text first, or provide a custom ingestion path.

## Programmatic ingest

```ts
import { getDefaultEmbedder, ingest }
  from '@mordn/chat-widget/server/knowledge';
import { createKnowledgeDrizzleStore }
  from '@mordn/chat-widget/server/knowledge/drizzle';

const embedder = getDefaultEmbedder();
const factory = createKnowledgeDrizzleStore({ embedder });

export async function ingestText(namespace: string, text: string) {
  const store = factory(namespace);
  return ingest({
    store,
    namespace,
    sources: [{ type: 'text', text, title: 'Uploaded note' }],
  });
}
```

Use the [CLI](/docs/api/cli) for URL, sitemap, crawl, llms.txt, status, and
evaluation workflows.
