openFunctions RAG is an advanced module for semantic document retrieval. It is built from the same runtime ideas as the rest of the framework:
- ingest documents
- chunk them semantically
- embed the chunks
- store vectors in pgvector
- search by cosine similarity
- optionally expose retrieval back to the AI as tools
RAG is not the core primitive of the framework. It is a tool-wrapped subsystem built on top of the core runtime.
createRAG() returns an object with:
addDocument(content, metadata?)search(query, limit?)listDocuments()deleteDocument(id)createTools()close()
The default storage shape is:
${tablePrefix}_documentsfor source documents${tablePrefix}_chunksfor chunk text plus vector embeddings
createRAG() bootstraps Postgres for vector search:
- creates a connection pool
- enables the
vectorextension - creates the documents table
- creates the chunks table with
embedding vector(dimensions) - creates an HNSW index using
vector_cosine_ops
Why HNSW:
- it works immediately, even with very small datasets
- it supports fast approximate nearest-neighbor search
- it avoids the training requirement of IVFFlat
The current index uses cosine distance and these parameters:
m = 16ef_construction = 64
Chunking is sentence-aware rather than fixed-width.
chunkText(text, maxChars, overlapChars):
- splits on sentence boundaries
- accumulates sentences until the current chunk would exceed
maxChars - emits the chunk
- carries forward a sentence-level overlap up to
overlapChars
This preserves meaning better than arbitrary character slicing and reduces boundary loss when related information spans adjacent chunks.
Default settings:
chunkSize = 1000chunkOverlap = 200
RAG supports two embedding backends behind one wrapper.
Gemini uses gemini-embedding-2-preview through the Gemini API.
Important behaviors:
- requires
GEMINI_API_KEY - supports
RETRIEVAL_DOCUMENTandRETRIEVAL_QUERY - supports configurable dimensionality via
outputDimensionality - practical options are
768,1536, or3072
The task-type split matters for Gemini because document and query embeddings are optimized differently.
OpenAI uses text-embedding-3-small.
Important behaviors:
- requires
OPENAI_API_KEY - uses configurable
dimensions - supports up to
1536 - does not distinguish document vs query task types at the API layer
The internal embed() helper routes to Gemini or OpenAI based on embeddingProvider, so chunking, storage, and search code stay provider-agnostic.
addDocument() is designed as an atomic ingest pipeline.
High-level flow:
- assign a document id
- chunk the content semantically
- generate embeddings for every chunk before starting the write transaction
BEGIN- insert the source document
- insert each chunk plus its vector embedding
COMMIT- on failure,
ROLLBACK
Why embeddings are generated before the transaction:
- API failures do not leave partial database state behind
- the transaction is only used for database writes
Why the write is transactional:
- a failed chunk insert should not leave a half-indexed document
- documents and chunks stay consistent
search(query, limit):
- embeds the query
- compares it against stored chunk embeddings with
embedding <=> $1::vector - orders by ascending cosine distance
- returns ranked
RAGSearchResult[]
Each result includes:
contentdistancedocumentIdchunkIndex
This is the retrieval surface you inject into prompts or hand back to calling code.
rag.createTools() returns two standard framework tools:
add_documentsearch_documents
That matters because RAG does not need a parallel execution model. It reuses the existing one.
Example:
import { registry, createRAG } from "./framework/index.js";
const rag = await createRAG({ embeddingProvider: "gemini" });
registry.registerAll(rag.createTools());Once registered, any adapter, workflow, or agent can use the RAG system through the normal tool path.
RAG is a good example of the framework’s architecture:
- storage and retrieval logic live in a subsystem
- that subsystem exposes a small API
- then it wraps itself back into tools
- the rest of the framework consumes those tools without special cases
That same pattern also appears in memory tools, delegation tools, and structured-output extraction.
Use RAG when:
- documents are too large to fit into prompts
- retrieval should be based on meaning rather than keywords
- multiple agents or tools need shared access to a document corpus
Do not use RAG when:
- you only need normal app persistence
- a small fact store or thread history is enough
- you can solve the problem with direct tool calls and known inputs
In those cases, use createStore(), createPgStore(), or the memory helpers instead.