AbsoluteJS

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.

1
loadLoad the raw input
A file, directory, URL, or in-memory upload is read into bytes plus metadata (path, content type, base metadata).
2
extractExtract text documents
The first matching extractor turns bytes into one or more text documents. PDFs, office files, EPUBs, emails, and archives ship built in; OCR and media transcription are opt-in.
createRAGFileExtractorRegistry([...])
3
chunkChunk each document
Each document is normalized and split into RAGDocumentChunk values with deterministic chunk ids, per the resolved chunking options.
prepareRAGDocuments({ defaultChunking, documents })
4
collectionEmbed at upsert time
Chunks without a precomputed embedding are embedded by the collection provider (kind: "passage"); precomputed vectors are dimension-validated and reused.
await collection.ingest({ chunks })
5
storeUpsert into the store
The vector store receives one upsert({ chunks }) call: in-memory, SQLite, Postgres, Pinecone, or your own RAGVectorStore.

#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.

FunctionInputWhat it does
loadRAGDocumentFileRAGDocumentFileInputReads one file from disk and extracts it into a single document.
loadRAGDocumentsFromDirectoryRAGDirectoryIngestInputWalks a directory (recursive by default) and extracts every matching file.
loadRAGDocumentFromURLRAGDocumentUrlInputFetches one URL and extracts the response body.
loadRAGDocumentsFromURLsRAGDocumentUrlIngestInputFetches a batch of URLs with shared base metadata and chunking.
loadRAGDocumentsFromUploadsRAGDocumentUploadIngestInputExtracts in-memory uploads (utf8 or base64 content) without touching disk.
prepareRAGDocument / prepareRAGDocumentsRAGIngestDocumentNormalizes and chunks already-extracted text into RAGPreparedDocument values.
buildRAGUpsertInputFrom*Documents | Directory | URLs | UploadsOne-call load + prepare + flatten into { chunks } ready for collection.ingest().
TS
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);
Directory extension filtering
Without 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).

OptionDefaultBehavior
strategy'paragraphs'defaultAlso 'sentences', 'fixed', and 'source_aware' (structure-aware splitting for extracted sheets, slides, and segments).
maxChunkLength900defaultUpper bound per chunk in characters (clamped to a floor of 120).
chunkOverlap120defaultCharacters shared between adjacent chunks; clamped to maxChunkLength - 1.
minChunkLength80defaultFragments 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.

ExtractorHandlesNotes
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().pdfNative text-layer extraction. Throws on scanned, image-only PDFs (pair with the OCR extractor).
createOfficeDocumentExtractor().docx .xlsx .pptx .odt .ods .odpSummary document plus per-sheet and per-slide documents with source-native metadata.
createLegacyDocumentExtractor().rtf .doc .xls .ppt .msgLegacy binary office formats via printable-string extraction; RTF is stripped.
createEPUBExtractor().epubUnzips the container and extracts chapter text.
createEmailExtractor().eml .emlx .mbox .mbxSingle messages and whole mailboxes, split into per-message documents.
createRAGArchiveFileExtractor(expander).zip .tar .gz .tgz .bz2 .xzExpands archives (zip / tar / gzip built in) and recursively extracts each entry.
createRAGImageOCRExtractor(provider).png .jpg .jpeg .webp .tiff .bmp .gif .heicOpt-in. Runs an RAGOCRProvider over images and records confidence metadata.
createRAGPDFOCRExtractor({ provider }).pdfOpt-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.
TS
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.

FactoryDefault modelNotes
anthropicOCRclaude-3-5-sonnet-latestOCR via the Anthropic Messages API; apiKey required.
openaiOCRgpt-4.1-miniOCR via the OpenAI Responses API; apiKey required.
geminiOCRgemini-2.5-flashOCR via the Gemini API; apiKey required.
ollamaOCRllavaLocal OCR against http://127.0.0.1:11434 by default; no API key.
openaiCompatibleOCRgpt-4.1-miniAny OpenAI-compatible endpoint; baseUrl becomes required.
openaiTranscribergpt-4o-mini-transcribeAudio transcription via /v1/audio/transcriptions with verbose segments.
ollamaTranscriberqwen2.5vlLocal transcription through Ollama; no API key.
openaiCompatibleTranscribergpt-4o-mini-transcribeAny 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.

Precomputed embeddings are first-class
Batch-embed offline, attach the vectors to your chunks, and ingest with zero embedding calls — a dimension mismatch throws instead of silently corrupting the index.