Ingestion & Chunking
Everything between a raw file and an upsert-ready chunk. @absolutejs/rag ships loaders for files, directories, URLs, and uploads; an extractor chain covering PDF, office, EPUB, email, archives, OCR, and media transcripts; and a chunking pipeline with per-document overrides, profile registries, and sane built-in defaults (900 / 120 / 80, paragraphs).
#Pipeline
Ingestion is a pure data pipeline — no network calls until the final embed step, and no store writes until the final upsert. Every stage is exported on its own, so you can enter at any point: hand raw text to prepareRAGDocuments, or let buildRAGUpsertInputFromDirectory run the whole chain in one call.
createRAGFileExtractorRegistry([...])prepareRAGDocuments({ defaultChunking, documents })await collection.ingest({ chunks })#Loaders & Builders
load* functions read and extract, prepare* functions chunk, and buildRAGUpsertInputFrom* functions do both and flatten the result to { chunks: RAGDocumentChunk[] } — the exact shape collection.ingest() takes.
| Function | Input | What it does |
|---|---|---|
loadRAGDocumentFile | RAGDocumentFileInput | Reads one file from disk and extracts it into a single document. |
loadRAGDocumentsFromDirectory | RAGDirectoryIngestInput | Walks a directory (recursive by default) and extracts every matching file. |
loadRAGDocumentFromURL | RAGDocumentUrlInput | Fetches one URL and extracts the response body. |
loadRAGDocumentsFromURLs | RAGDocumentUrlIngestInput | Fetches a batch of URLs with shared base metadata and chunking. |
loadRAGDocumentsFromUploads | RAGDocumentUploadIngestInput | Extracts in-memory uploads (utf8 or base64 content) without touching disk. |
prepareRAGDocument / prepareRAGDocuments | RAGIngestDocument | Normalizes and chunks already-extracted text into RAGPreparedDocument values. |
buildRAGUpsertInputFrom* | Documents | Directory | URLs | Uploads | One-call load + prepare + flatten into { chunks } ready for collection.ingest(). |
import {
buildRAGUpsertInputFromDirectory,
createInMemoryRAGStore,
createRAGCollection,
openaiEmbeddings
} from '@absolutejs/rag';
const collection = createRAGCollection({
embedding: openaiEmbeddings({
apiKey: process.env.OPENAI_API_KEY ?? '',
defaultModel: 'text-embedding-3-small'
}),
store: createInMemoryRAGStore()
});
// Walks ./docs (recursive by default), picks an extractor per file,
// chunks every document, and flattens to upsert-ready chunks.
const upsert = await buildRAGUpsertInputFromDirectory({
baseMetadata: { team: 'platform' },
directory: './docs',
includeExtensions: ['.md', '.mdx', '.pdf']
});
// Chunks without a precomputed embedding are embedded by the
// collection's provider at upsert time (kind: 'passage').
await collection.ingest(upsert);includeExtensions, directory walks use a built-in allowlist (.txt, .md, .mdx, .html, .json, .csv, .xml, .yaml, .pdf, .eml, .mbox, ...). Passing custom extractors or an extractorRegistry without includeExtensions disables the filter so your extractors see every file.#Chunking
RAGChunkingOptions resolve per field, most specific first: the document's own chunking, then a matching registry profile, then the defaultChunking you passed, then the built-ins. Documents that already fit inside maxChunkLength become a single chunk (except under source_aware).
| Option | Default | Behavior |
|---|---|---|
strategy | 'paragraphs'default | Also 'sentences', 'fixed', and 'source_aware' (structure-aware splitting for extracted sheets, slides, and segments). |
maxChunkLength | 900default | Upper bound per chunk in characters (clamped to a floor of 120). |
chunkOverlap | 120default | Characters shared between adjacent chunks; clamped to maxChunkLength - 1. |
minChunkLength | 80default | Fragments shorter than this merge into a neighbor instead of standing alone. |
createRAGChunkingRegistry([...]) registers profiles that match on formats, sources, documentIds, or sourceNativeKinds with an optional priority — one place to say "markdown gets 1200-char source-aware chunks, transcripts get 500".
#File Extractors
An extractor is { name, supports, extract } over raw bytes. The default chain runs office → mailbox → legacy → EPUB → email → archive → PDF → text, first match wins. Register your own with createRAGFileExtractorRegistry, matching on extensions, content types, formats, or a custom match function.
| Extractor | Handles | Notes |
|---|---|---|
createTextFileExtractor() | .txt .md .mdx .html .json .csv .xml .yaml .ts .tsx ... | UTF-8 text and structured-text formats; format inferred from content type or filename. |
createPDFFileExtractor() | Native text-layer extraction. Throws on scanned, image-only PDFs (pair with the OCR extractor). | |
createOfficeDocumentExtractor() | .docx .xlsx .pptx .odt .ods .odp | Summary document plus per-sheet and per-slide documents with source-native metadata. |
createLegacyDocumentExtractor() | .rtf .doc .xls .ppt .msg | Legacy binary office formats via printable-string extraction; RTF is stripped. |
createEPUBExtractor() | .epub | Unzips the container and extracts chapter text. |
createEmailExtractor() | .eml .emlx .mbox .mbx | Single messages and whole mailboxes, split into per-message documents. |
createRAGArchiveFileExtractor(expander) | .zip .tar .gz .tgz .bz2 .xz | Expands archives (zip / tar / gzip built in) and recursively extracts each entry. |
createRAGImageOCRExtractor(provider) | .png .jpg .jpeg .webp .tiff .bmp .gif .heic | Opt-in. Runs an RAGOCRProvider over images and records confidence metadata. |
createRAGPDFOCRExtractor({ provider }) | Opt-in. Native text first; OCR fallback below minExtractedTextLength (default 80) or always with alwaysOCR. | |
createRAGMediaFileExtractor(transcriber) | .mp3 .wav .m4a .flac .ogg .mp4 .mov .mkv .webm ... | Opt-in. Transcribes audio / video into per-segment documents with timing metadata. |
import {
createRAGChunkingRegistry,
createRAGFileExtractorRegistry,
createRAGImageOCRExtractor,
createRAGPDFOCRExtractor,
loadRAGDocumentsFromDirectory,
openaiOCR,
prepareRAGDocuments
} from '@absolutejs/rag';
// OCR and media extractors are opt-in : they need a provider, so they
// are not part of the built-in extractor chain.
const ocr = openaiOCR({ apiKey: process.env.OPENAI_API_KEY ?? '' });
const extractorRegistry = createRAGFileExtractorRegistry([
{
extensions: ['.png', '.jpg', '.webp'],
extractor: createRAGImageOCRExtractor(ocr)
},
{
extensions: ['.pdf'],
// Uses the native text layer when it yields >= 80 characters;
// falls back to OCR for scanned pages (or set alwaysOCR: true).
extractor: createRAGPDFOCRExtractor({ provider: ocr })
}
]);
// Chunking profiles match on format / source / document id; anything
// unmatched falls back to defaultChunking, then the built-in
// 900 / 120 / 80 paragraphs defaults.
const chunkingRegistry = createRAGChunkingRegistry([
{
formats: ['markdown'],
profile: { maxChunkLength: 1200, strategy: 'source_aware' }
}
]);
const loaded = await loadRAGDocumentsFromDirectory({
chunkingRegistry,
defaultChunking: { chunkOverlap: 120, maxChunkLength: 900 },
directory: './scans',
extractorRegistry
});
const prepared = prepareRAGDocuments(loaded);#OCR & Transcription Providers
OCR extractors take an RAGOCRProvider ({ name, extractText }) and media extractors take an RAGMediaTranscriber ({ name, transcribe }). Ready-made providers ship for the common APIs — or wrap your own with createRAGOCRProvider / createRAGMediaTranscriber.
| Factory | Default model | Notes |
|---|---|---|
anthropicOCR | claude-3-5-sonnet-latest | OCR via the Anthropic Messages API; apiKey required. |
openaiOCR | gpt-4.1-mini | OCR via the OpenAI Responses API; apiKey required. |
geminiOCR | gemini-2.5-flash | OCR via the Gemini API; apiKey required. |
ollamaOCR | llava | Local OCR against http://127.0.0.1:11434 by default; no API key. |
openaiCompatibleOCR | gpt-4.1-mini | Any OpenAI-compatible endpoint; baseUrl becomes required. |
openaiTranscriber | gpt-4o-mini-transcribe | Audio transcription via /v1/audio/transcriptions with verbose segments. |
ollamaTranscriber | qwen2.5vl | Local transcription through Ollama; no API key. |
openaiCompatibleTranscriber | gpt-4o-mini-transcribe | Any OpenAI-compatible transcription endpoint; baseUrl required. |
#Embedding at Upsert
Ingestion itself never embeds. Chunks flow to the collection as plain text, and collection.ingest() embeds anything missing an embedding with the collection's provider (kind 'passage'). Chunks that arrive with a precomputed vector — or per-chunk embeddingVariants — are validated against the expected dimensions via validateRAGEmbeddingDimensions and reused as-is.