GNL
Docs menu
Core · Free@gnldev/rag

RAG pipeline — chunking, pgvector, GraphRAG

Deterministic chunking, a durable pgvector store, and similarity-graph (GraphRAG) retrieval; run inside createRagTool it's journaled → exactly-once / replayable RAG.

What it's for / when to use it#

@gnldev/ragturns a knowledge base into a tool the agent can call. The critical difference: when the RAG tool is used inside runDurable its result is journaled → on resume/replay the same documents come back, no new embed/query call is made (exactly-once RAG) — a retrieval that already ran is replayed from the journal rather than re-issued.

The pipeline has three parts: (1) chunking — splits raw text/documents into chunks; (2) vector store — in-memory, durable Postgres/pgvector, or a similarity graph (GraphRAG); (3) createRagTool — packages embed + query + optional rerank into a single tool.

Chunking — chunkText / chunkDocuments#

chunkText splits raw text into chunks; three strategies: recursive (default — splits by preferring paragraph → line → sentence → word boundaries), markdown (sections by heading hierarchy; adds a heading breadcrumb metadata field to every chunk), and character (fixed window). Consecutive chunks overlap via overlap (context continuity).

chunkDocuments splits VectorDocs and produces a list ready for indexDocuments. The chunk id is <docId>#<i> deterministic, so re-indexing upserts into the same ids instead of producing duplicates. A document that fits in a single chunk passes through unsplit (no pointless #0 derivation).

chunkDocuments → indexDocuments
import { chunkDocuments, indexDocuments, InMemoryVectorStore } from '@gnldev/rag';

const chunks = chunkDocuments(docs, {
  size: 1200, overlap: 120, strategy: 'markdown',
});
// chunks[i].id === 'guide#3', metadata: { source: 'guide', chunk: 3, heading: 'Kurulum > Docker' }

const store = new InMemoryVectorStore();
await indexDocuments(store, chunks, embed);

Durable store — PostgresVectorStore (pgvector)#

PostgresVectorStoreand InMemoryVectorStore expose the same interface — Postgres backs it durably with pgvector: it takes an injectable pool, lazy-loads the pg driver, and supports HNSW or ivfflat indexes. This means the knowledge base persists across restarts in real customer projects.

PostgresVectorStore
import { PostgresVectorStore, createRagTool } from '@gnldev/rag';
import { Pool } from 'pg';

const store = new PostgresVectorStore({
  pool: new Pool({ connectionString: process.env.DATABASE_URL }),
  dimension: 1536,
  index: 'hnsw',
});
await store.upsert(items); // the same signature as InMemory

GraphRAG — indirect-relevance retrieval#

GraphRagdoes retrieval over a similarity graph between chunks (the graph-retrieval pattern, over the same journal as everything else GraphRAG ). It catches what plain vector search misses: chunks that aren't directly similar to the query but are strongly connected to chunks that are (indirect relevance) also join the results. Since it implements the VectorStore interface, it's a drop-in for createRagTool.

Parameters: threshold (edge threshold, default 0.75), hops (neighbor-expansion depth, default 1; 0 = plain vector search), decay (per-hop score decay, default 0.7), and seeds (number of direct results seeding the expansion, default 4). If a node is reached by more than one path, the highest score is kept; ties break deterministically by id.

GraphRag — the graph as a query layer
import { GraphRag, createRagTool } from '@gnldev/rag';

const graph = new GraphRag({ threshold: 0.75, hops: 1, decay: 0.7, seeds: 4 });
await graph.upsert(items); // edges are built incrementally

const ragTool = createRagTool({ store: graph, embed, topK: 6 });

createRagTool — deterministic / exactly-once#

createRagTool packages embed + store.query + optional rerankinto a single tool. It works with any VectorStore (InMemory / Postgres / GraphRag). Because runDurable journals the durableTool result, resume returns the same documents; no new embed/query/rerank call runs.

Guarantee
RAG being deterministic and exactly-once is unique to GNL: the same-context guarantee across replays makes debugging and evals reproducible — frameworks that embed live can't offer this consistency.

API reference#

fnchunkText

(text, opts?) → Chunk[]; recursive/markdown/character strategy, size/overlap, markdown heading breadcrumb.

fnchunkDocuments

(docs, opts?) → VectorDoc[]; deterministic <docId>#<i> chunk id → re-indexing upserts.

classPostgresVectorStore

Durable pgvector store; injectable pool, lazy pg, HNSW/ivfflat — same interface as InMemory.

classGraphRag

Similarity-graph VectorStore; indirect-relevance retrieval via threshold/hops/decay/seeds, drop-in for createRagTool.

fncreateRagTool

({ store, embed, topK?, rerank?, ... }) → tool; journaled inside durable = exactly-once/replayable RAG.

fnInMemoryVectorStore / indexDocuments

In-memory store + bulk index helper (test/prototype).