Knowledge / RAG

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

code
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

storeRetrieverFactoryrequired
Read-only retriever factory.
resolveNamespaces(ctx) => string[] | Promise<string[]>required
Trusted namespace resolver.
mode'tool' | 'auto'default: tool
Expose searchKnowledge or retrieve every turn.
topKnumberdefault: 5
Result cap; the default store clamps to 20.
minScorenumberdefault: 0.2
Minimum relevance.
vectorWeightnumberdefault: 1
Vector/lexical blend where supported.
citationsbooleandefault: 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:

code
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:

code
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

code
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 for URL, sitemap, crawl, llms.txt, status, and evaluation workflows.