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
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
storeRetrieverFactoryrequiredresolveNamespaces(ctx) => string[] | Promise<string[]>requiredmode'tool' | 'auto'default: toolsearchKnowledge or retrieve every turn.topKnumberdefault: 5minScorenumberdefault: 0.2vectorWeightnumberdefault: 1citationsbooleandefault: truebuildQuery(messages, ctx) => string | Promise<string>renderContext(chunks) => stringIngestion
The default docs-aware pipeline is:
SSRF-guarded loader → HTML-to-Markdown or Markdown passthrough
→ heading-aware chunking → content-hash diff → embedding → idempotent upsertSet docsMode: false to use the legacy clean-text/chunk-text path.
Supported sources:
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
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.