@dudousxd/nestjs-agent-rag 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/index.cjs +883 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +845 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/chunk.ts","../src/vector-store.ts","../src/filter.ts","../src/memory-vector-store.ts","../src/pg-vector-store.ts","../src/redis-vector-store.ts","../src/embedding-retriever.ts","../src/keyword-retriever.ts","../src/hybrid-retriever.ts","../src/reranking-retriever.ts","../src/filtered-retriever.ts","../src/ingest.ts","../src/retrieval-tool.ts"],"sourcesContent":["export { chunkText, type ChunkOptions } from './chunk.js';\nexport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\nexport { documentIdOf } from './vector-store.js';\nexport { MemoryVectorStore } from './memory-vector-store.js';\nexport {\n PgVectorStore,\n type PgClient,\n type PgVectorStoreOptions,\n} from './pg-vector-store.js';\nexport {\n RedisVectorStore,\n type RedisSearchClient,\n type RedisVectorStoreOptions,\n} from './redis-vector-store.js';\nexport { EmbeddingRetriever } from './embedding-retriever.js';\nexport { KeywordRetriever, type KeywordRetrieverOptions } from './keyword-retriever.js';\nexport { HybridRetriever, type HybridRetrieverOptions } from './hybrid-retriever.js';\nexport { RerankingRetriever, type RerankingRetrieverOptions } from './reranking-retriever.js';\nexport { FilteredRetriever } from './filtered-retriever.js';\nexport {\n chunkDocuments,\n ingestChunks,\n ingestDocuments,\n type ChunkRecord,\n type IngestChunksOptions,\n type IngestDocument,\n type IngestOptions,\n} from './ingest.js';\nexport {\n createRetrievalTool,\n type RetrievalTool,\n type RetrievalToolOptions,\n} from './retrieval-tool.js';\n","export interface ChunkOptions {\n /** Target max characters per chunk. Default 800. */\n chunkSize?: number;\n /** Characters of overlap carried from the end of one chunk into the next. Default 100. */\n overlap?: number;\n}\n\n/**\n * Splits text into overlapping chunks for embedding. Greedy up to `chunkSize`, but prefers to break\n * on a paragraph → sentence → word boundary in the back half of the window (so a chunk rarely cuts\n * mid-sentence), then carries `overlap` characters into the next chunk to preserve context across\n * the seam. Pure and deterministic.\n */\nexport function chunkText(text: string, options: ChunkOptions = {}): string[] {\n const chunkSize = options.chunkSize ?? 800;\n const overlap = options.overlap ?? 100;\n const normalized = text.trim();\n if (normalized.length === 0) {\n return [];\n }\n if (normalized.length <= chunkSize) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n let start = 0;\n while (start < normalized.length) {\n const end = Math.min(start + chunkSize, normalized.length);\n let breakAt = end;\n if (end < normalized.length) {\n const window = normalized.slice(start, end);\n const boundary = Math.max(\n window.lastIndexOf('\\n\\n'),\n window.lastIndexOf('. '),\n window.lastIndexOf(' '),\n );\n // Only honour a boundary in the back half, else a tiny chunk cascades.\n if (boundary > chunkSize / 2) {\n breakAt = start + boundary + 1;\n }\n }\n const chunk = normalized.slice(start, breakAt).trim();\n if (chunk.length > 0) {\n chunks.push(chunk);\n }\n if (breakAt >= normalized.length) {\n break;\n }\n // Step forward with overlap, but always make progress (guards against a stuck boundary).\n start = Math.max(breakAt - overlap, start + 1);\n }\n return chunks;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\n\n/** A stored, embedded chunk. `embedding` length must match the store's configured dimensions. */\nexport interface VectorRecord {\n id: string;\n text: string;\n embedding: number[];\n /** Citation-facing origin (document title, URL, row id). */\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface VectorSearchOptions {\n topK: number;\n /** Exact-match metadata filter (all keys must equal). Passed through from `RetrieveOptions.filter`. */\n filter?: Record<string, unknown>;\n}\n\n/**\n * The write + search side of RAG storage. `@dudousxd/nestjs-agent-rag` ships `MemoryVectorStore`\n * (in-JS cosine, tests + small scale) and `PgVectorStore` (pgvector); any impl works. Pair one with\n * an `EmbeddingProvider` via {@link import('./embedding-retriever.js').EmbeddingRetriever} to get a\n * core `Retriever`.\n */\nexport interface VectorStore {\n upsert(records: VectorRecord[]): Promise<void>;\n search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;\n /**\n * Delete every chunk belonging to a source document — all records whose id is `${documentId}`\n * or `${documentId}#<n>` (the id scheme {@link import('./ingest.js').chunkDocuments} produces).\n * Use it to drop a document from the index, and before re-ingesting one: `upsert` overwrites\n * matching ids but can't remove chunks a shorter new version no longer produces, so a re-ingest\n * without a preceding `remove` leaves the old tail orphaned.\n */\n remove(documentId: string): Promise<void>;\n /**\n * List the distinct source documents currently indexed (chunk ids collapsed back to their document\n * by stripping the trailing `#<n>`), each with a representative chunk's `metadata`, optionally\n * narrowed by a metadata `filter`. The enumeration seam for reconciliation: diff this against your\n * source of truth to find documents to ingest (missing), re-ingest (a stored fingerprint like\n * `size` changed), or {@link VectorStore.remove remove} (orphaned).\n */\n listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;\n}\n\n/** A distinct source document as seen by the index — its id plus a representative chunk's metadata. */\nexport interface IndexedDocument {\n id: string;\n metadata?: Record<string, unknown>;\n}\n\n/** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */\nexport function documentIdOf(chunkId: string): string {\n return chunkId.replace(/#\\d+$/, '');\n}\n","/** Exact-match metadata filter (every key in `filter` must equal the record's). Shared by the\n * in-memory {@link import('./memory-vector-store.js').MemoryVectorStore} and\n * {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport function matchesFilter(\n metadata: Record<string, unknown> | undefined,\n filter: Record<string, unknown>,\n): boolean {\n if (metadata === undefined) {\n return Object.keys(filter).length === 0;\n }\n return Object.entries(filter).every(([key, value]) => metadata[key] === value);\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * An in-process {@link VectorStore} — cosine similarity over a Map, no infra. The reference adapter\n * for tests and small/embedded corpora; for production scale use `PgVectorStore` (or your own).\n */\nexport class MemoryVectorStore implements VectorStore {\n private readonly records = new Map<string, VectorRecord>();\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n for (const id of this.records.keys()) {\n if (documentIdOf(id) === documentId) {\n this.records.delete(id);\n }\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const documents = new Map<string, IndexedDocument>();\n for (const record of this.records.values()) {\n if (filter !== undefined && !matchesFilter(record.metadata, filter)) {\n continue;\n }\n const id = documentIdOf(record.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const scored: Passage[] = [];\n for (const record of this.records.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n scored.push({\n id: record.id,\n text: record.text,\n score: cosineSimilarity(embedding, record.embedding),\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK);\n }\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n const length = Math.min(a.length, b.length);\n for (let index = 0; index < length; index += 1) {\n const valueA = a[index] ?? 0;\n const valueB = b[index] ?? 0;\n dot += valueA * valueB;\n normA += valueA * valueA;\n normB += valueB * valueB;\n }\n const denominator = Math.sqrt(normA) * Math.sqrt(normB);\n return denominator === 0 ? 0 : dot / denominator;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport type {\n IndexedDocument,\n VectorRecord,\n VectorSearchOptions,\n VectorStore,\n} from './vector-store.js';\n\n/**\n * The minimal Postgres surface {@link PgVectorStore} needs — adapt your own `pg` / `postgres.js`\n * client to it, so this package pulls in NO driver (bring your own, like the store adapters take an\n * ORM handle and the Redis transport takes a client). `query` runs parameterized SQL (`$1,$2,…`) and\n * resolves the result rows.\n */\nexport interface PgClient {\n query<Row = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<Row[]>;\n}\n\nexport interface PgVectorStoreOptions {\n /** Table name. Default `agent_rag_chunks`. */\n table?: string;\n /** Embedding width — must match your model (e.g. 1536 for text-embedding-3-small). Default 1536. */\n dimensions?: number;\n}\n\n/**\n * A pgvector-backed {@link VectorStore} — the production reference adapter. Cosine distance via the\n * `<=>` operator over an HNSW index; metadata in a `jsonb` column filtered with `@>`. Call\n * {@link PgVectorStore.ensureSchema} once at boot to create the extension, table, and index.\n */\nexport class PgVectorStore implements VectorStore {\n private readonly table: string;\n private readonly dimensions: number;\n\n constructor(\n private readonly client: PgClient,\n options: PgVectorStoreOptions = {},\n ) {\n this.table = options.table ?? 'agent_rag_chunks';\n this.dimensions = options.dimensions ?? 1536;\n }\n\n /** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */\n async ensureSchema(): Promise<void> {\n await this.client.query('CREATE EXTENSION IF NOT EXISTS vector');\n await this.client.query(\n `CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY,\n text TEXT NOT NULL,\n source TEXT,\n metadata JSONB,\n embedding vector(${this.dimensions}) NOT NULL\n )`,\n );\n await this.client.query(\n `CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx\n ON ${this.table} USING hnsw (embedding vector_cosine_ops)`,\n );\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n await this.client.query(\n `INSERT INTO ${this.table} (id, text, source, metadata, embedding)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (id) DO UPDATE SET\n text = EXCLUDED.text,\n source = EXCLUDED.source,\n metadata = EXCLUDED.metadata,\n embedding = EXCLUDED.embedding`,\n [\n record.id,\n record.text,\n record.source ?? null,\n record.metadata !== undefined ? JSON.stringify(record.metadata) : null,\n toVectorLiteral(record.embedding),\n ],\n );\n }\n }\n\n async remove(documentId: string): Promise<void> {\n await this.client.query(`DELETE FROM ${this.table} WHERE ${DOCUMENT_ID_FROM_CHUNK} = $1`, [\n documentId,\n ]);\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const hasFilter = filter !== undefined && Object.keys(filter).length > 0;\n // DISTINCT ON collapses chunks to one row per document; all chunks share the doc's metadata.\n const rows = await this.client.query<{\n doc_id: string;\n metadata: Record<string, unknown> | null;\n }>(\n `SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata\n FROM ${this.table}\n ${hasFilter ? 'WHERE metadata @> $1::jsonb' : ''}\n ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,\n hasFilter ? [JSON.stringify(filter)] : [],\n );\n return rows.map((row) => ({\n id: row.doc_id,\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const vector = toVectorLiteral(embedding);\n const hasFilter = options.filter !== undefined && Object.keys(options.filter).length > 0;\n const rows = await this.client.query<PgRow>(\n `SELECT id, text, source, metadata, 1 - (embedding <=> $1::vector) AS score\n FROM ${this.table}\n ${hasFilter ? 'WHERE metadata @> $3::jsonb' : ''}\n ORDER BY embedding <=> $1::vector\n LIMIT $2`,\n hasFilter ? [vector, options.topK, JSON.stringify(options.filter)] : [vector, options.topK],\n );\n return rows.map((row) => ({\n id: row.id,\n text: row.text,\n score: Number(row.score),\n ...(row.source !== null ? { source: row.source } : {}),\n ...(row.metadata !== null ? { metadata: row.metadata } : {}),\n }));\n }\n}\n\ninterface PgRow {\n id: string;\n text: string;\n source: string | null;\n metadata: Record<string, unknown> | null;\n score: number | string;\n}\n\n/**\n * SQL that collapses a chunk id (`${documentId}#<n>`) back to its source document id — the pgvector\n * mirror of {@link import('./vector-store.js').documentIdOf}. Shared by `remove` and\n * `listDocuments` so both key on the exact same definition of \"chunk belongs to document\".\n */\nconst DOCUMENT_ID_FROM_CHUNK = \"regexp_replace(id, '#[0-9]+$', '')\";\n\n/** pgvector accepts a `'[1,2,3]'` text literal cast to `vector`. */\nfunction toVectorLiteral(embedding: number[]): string {\n return `[${embedding.join(',')}]`;\n}\n","import type { Passage } from '@dudousxd/nestjs-agent-core';\nimport {\n type IndexedDocument,\n type VectorRecord,\n type VectorSearchOptions,\n type VectorStore,\n documentIdOf,\n} from './vector-store.js';\n\n/**\n * The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.\n * Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so\n * this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the\n * RediSearch module (Redis Stack / Redis 8+).\n */\nexport interface RedisSearchClient {\n sendCommand(args: (string | Buffer)[]): Promise<unknown>;\n}\n\nexport interface RedisVectorStoreOptions {\n /** RediSearch index name. Default `agent_rag_idx`. */\n index?: string;\n /** Key prefix for the per-chunk hashes. Default `agent_rag:`. */\n prefix?: string;\n /** Embedding width — must match your model (e.g. 1536). Default 1536. */\n dimensions?: number;\n /**\n * Metadata keys to index as filterable TAG fields. RediSearch needs filter fields declared up\n * front, so only keys listed here can be passed in `search`'s `filter`. Default none.\n */\n filterableFields?: string[];\n}\n\n/**\n * A RediSearch-backed {@link VectorStore} — HNSW + cosine over Redis, the ecosystem-native option\n * for anyone already running Redis (see `-transport-redis`). Chunks are hashes under `prefix`, the\n * embedding a FLOAT32 vector field; `search` is a KNN query. Call {@link RedisVectorStore.ensureSchema}\n * once at boot to create the index.\n */\nexport class RedisVectorStore implements VectorStore {\n private readonly index: string;\n private readonly prefix: string;\n private readonly dimensions: number;\n private readonly filterableFields: string[];\n\n constructor(\n private readonly client: RedisSearchClient,\n options: RedisVectorStoreOptions = {},\n ) {\n this.index = options.index ?? 'agent_rag_idx';\n this.prefix = options.prefix ?? 'agent_rag:';\n this.dimensions = options.dimensions ?? 1536;\n this.filterableFields = options.filterableFields ?? [];\n }\n\n /** Idempotent: create the HNSW/cosine index if `FT.INFO` says it doesn't exist yet. */\n async ensureSchema(): Promise<void> {\n try {\n await this.client.sendCommand(['FT.INFO', this.index]);\n return;\n } catch {\n // index missing → create it below\n }\n const schema: string[] = ['text', 'TEXT', 'source', 'TAG', 'metadata_json', 'TEXT', 'NOINDEX'];\n for (const field of this.filterableFields) {\n schema.push(`meta_${field}`, 'TAG');\n }\n schema.push(\n 'embedding',\n 'VECTOR',\n 'HNSW',\n '6',\n 'TYPE',\n 'FLOAT32',\n 'DIM',\n String(this.dimensions),\n 'DISTANCE_METRIC',\n 'COSINE',\n );\n await this.client.sendCommand([\n 'FT.CREATE',\n this.index,\n 'ON',\n 'HASH',\n 'PREFIX',\n '1',\n this.prefix,\n 'SCHEMA',\n ...schema,\n ]);\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n const args: (string | Buffer)[] = [\n 'HSET',\n `${this.prefix}${record.id}`,\n 'text',\n record.text,\n 'embedding',\n encodeVector(record.embedding),\n ];\n if (record.source !== undefined) {\n args.push('source', record.source);\n }\n if (record.metadata !== undefined) {\n args.push('metadata_json', JSON.stringify(record.metadata));\n for (const field of this.filterableFields) {\n const value = record.metadata[field];\n if (value !== undefined) {\n args.push(`meta_${field}`, String(value));\n }\n }\n }\n await this.client.sendCommand(args);\n }\n }\n\n async remove(documentId: string): Promise<void> {\n const keys = new Set<string>([`${this.prefix}${documentId}`]);\n const pattern = `${this.prefix}${escapeGlob(documentId)}#*`;\n let cursor = '0';\n do {\n const reply = await this.client.sendCommand([\n 'SCAN',\n cursor,\n 'MATCH',\n pattern,\n 'COUNT',\n '256',\n ]);\n const page = parseScanReply(reply);\n cursor = page.cursor;\n for (const key of page.keys) {\n keys.add(key);\n }\n } while (cursor !== '0');\n if (keys.size > 0) {\n await this.client.sendCommand(['DEL', ...keys]);\n }\n }\n\n async listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]> {\n const query = buildFilter(filter);\n const documents = new Map<string, IndexedDocument>();\n const batchSize = 1000;\n let offset = 0;\n for (;;) {\n // RETURN metadata_json (not NOCONTENT) so we can carry a representative chunk's metadata;\n // parseSearchReply already yields prefix-stripped ids + parsed metadata for both RESP2/RESP3.\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'RETURN',\n '1',\n 'metadata_json',\n 'LIMIT',\n String(offset),\n String(batchSize),\n 'DIALECT',\n '2',\n ]);\n const passages = parseSearchReply(reply, this.prefix);\n for (const passage of passages) {\n const id = documentIdOf(passage.id);\n if (!documents.has(id)) {\n documents.set(id, {\n id,\n ...(passage.metadata !== undefined ? { metadata: passage.metadata } : {}),\n });\n }\n }\n if (passages.length < batchSize) {\n break;\n }\n offset += batchSize;\n }\n return [...documents.values()];\n }\n\n async search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]> {\n const query = `${buildFilter(options.filter)}=>[KNN ${options.topK} @embedding $BLOB AS vector_score]`;\n const reply = await this.client.sendCommand([\n 'FT.SEARCH',\n this.index,\n query,\n 'PARAMS',\n '2',\n 'BLOB',\n encodeVector(embedding),\n 'RETURN',\n '4',\n 'text',\n 'source',\n 'metadata_json',\n 'vector_score',\n 'SORTBY',\n 'vector_score',\n 'ASC',\n 'DIALECT',\n '2',\n 'LIMIT',\n '0',\n String(options.topK),\n ]);\n return parseSearchReply(reply, this.prefix);\n }\n}\n\n/** RediSearch expects a little-endian FLOAT32 buffer for the query/stored vector. */\nfunction encodeVector(embedding: number[]): Buffer {\n return Buffer.from(new Float32Array(embedding).buffer);\n}\n\n/** `*` for no filter, else an AND of TAG clauses like `(@meta_owner:{u1} @meta_tenant:{t1})`. */\nfunction buildFilter(filter?: Record<string, unknown>): string {\n if (filter === undefined || Object.keys(filter).length === 0) {\n return '*';\n }\n const clauses = Object.entries(filter).map(\n ([key, value]) => `@meta_${key}:{${escapeTag(String(value))}}`,\n );\n return `(${clauses.join(' ')})`;\n}\n\n/** Escape RediSearch TAG punctuation so an id/tenant value matches literally. */\nfunction escapeTag(value: string): string {\n return value.replace(/[^a-zA-Z0-9_]/g, (char) => `\\\\${char}`);\n}\n\n/** Escape `SCAN MATCH` glob metacharacters so a document id matches its chunk keys literally. */\nfunction escapeGlob(value: string): string {\n return value.replace(/[*?[\\]\\\\]/g, (char) => `\\\\${char}`);\n}\n\n/**\n * Parse a `SCAN` reply into `{ cursor, keys }`, tolerating both wire shapes: the RESP2 array\n * `[cursor, [key, …]]` and node-redis's RESP3 object `{ cursor, keys }`.\n */\nfunction parseScanReply(reply: unknown): { cursor: string; keys: string[] } {\n if (Array.isArray(reply) && reply.length >= 2) {\n const rawKeys = reply[1];\n return {\n cursor: toStr(reply[0]),\n keys: Array.isArray(rawKeys) ? rawKeys.map(toStr) : [],\n };\n }\n if (typeof reply === 'object' && reply !== null && 'cursor' in reply && 'keys' in reply) {\n const { cursor, keys } = reply as { cursor: unknown; keys: unknown };\n return {\n cursor: toStr(cursor),\n keys: Array.isArray(keys) ? keys.map(toStr) : [],\n };\n }\n return { cursor: '0', keys: [] };\n}\n\nfunction toStr(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);\n}\n\n/**\n * Parse an `FT.SEARCH` reply into passages, tolerating both wire shapes: the RESP2 array\n * `[total, key, [f, v, …], …]` (ioredis / node-redis RESP2) and the RESP3 object\n * `{ results: [{ id, extra_attributes: { … } }] }` (node-redis's default). Distance → similarity\n * via `1 - distance` (COSINE), and the `prefix` is stripped back off the key to recover the chunk id.\n */\nfunction parseSearchReply(reply: unknown, prefix: string): Passage[] {\n if (Array.isArray(reply)) {\n const passages: Passage[] = [];\n for (let index = 1; index + 1 < reply.length; index += 2) {\n const rawFields = reply[index + 1];\n if (Array.isArray(rawFields)) {\n const attrs: Record<string, string> = {};\n for (let field = 0; field + 1 < rawFields.length; field += 2) {\n attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);\n }\n passages.push(toPassage(toStr(reply[index]), attrs, prefix));\n }\n }\n return passages;\n }\n if (typeof reply === 'object' && reply !== null && 'results' in reply) {\n const results = (reply as { results: unknown }).results;\n if (Array.isArray(results)) {\n return results.map((result) => toPassage(readId(result), readAttributes(result), prefix));\n }\n }\n return [];\n}\n\nfunction readId(result: unknown): string {\n if (typeof result === 'object' && result !== null && 'id' in result) {\n return toStr((result as { id: unknown }).id);\n }\n return '';\n}\n\nfunction readAttributes(result: unknown): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (typeof result === 'object' && result !== null && 'extra_attributes' in result) {\n const raw = (result as { extra_attributes: unknown }).extra_attributes;\n if (typeof raw === 'object' && raw !== null) {\n for (const [key, value] of Object.entries(raw)) {\n attrs[key] = toStr(value);\n }\n }\n }\n return attrs;\n}\n\nfunction toPassage(rawId: string, attrs: Record<string, string>, prefix: string): Passage {\n const distance = Number(attrs.vector_score ?? '1');\n const source = attrs.source;\n return {\n id: rawId.startsWith(prefix) ? rawId.slice(prefix.length) : rawId,\n text: attrs.text ?? '',\n score: 1 - distance,\n ...(source !== undefined ? { source } : {}),\n ...parseMetadata(attrs.metadata_json),\n };\n}\n\nfunction parseMetadata(raw: string | undefined): { metadata?: Record<string, unknown> } {\n if (raw === undefined) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed === 'object' && parsed !== null) {\n return { metadata: parsed as Record<string, unknown> };\n }\n } catch {\n // not JSON — no metadata\n }\n return {};\n}\n","import type {\n EmbeddingProvider,\n Passage,\n RetrieveOptions,\n Retriever,\n} from '@dudousxd/nestjs-agent-core';\nimport type { VectorStore } from './vector-store.js';\n\n/**\n * Bridges an {@link EmbeddingProvider} + {@link VectorStore} into the core {@link Retriever} SPI:\n * embed the query, then vector-search. This is what you wire as the agent's retriever (as a tool\n * via `createRetrievalTool`, or into `AgentModule.forRoot({ retrieval })` for inject mode).\n */\nexport class EmbeddingRetriever implements Retriever {\n constructor(\n private readonly embedder: EmbeddingProvider,\n private readonly store: VectorStore,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const [embedding] = await this.embedder.embed([query]);\n if (embedding === undefined) {\n return [];\n }\n return this.store.search(embedding, {\n topK: options.topK ?? 5,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\nimport { matchesFilter } from './filter.js';\nimport type { ChunkRecord } from './ingest.js';\n\nexport interface KeywordRetrieverOptions {\n /** BM25 term-frequency saturation. Default 1.5. */\n k1?: number;\n /** BM25 length normalization, 0 (off) … 1 (full). Default 0.75. */\n b?: number;\n}\n\ninterface IndexedDoc {\n record: ChunkRecord;\n /** term → frequency within this doc. */\n frequencies: Map<string, number>;\n length: number;\n}\n\n/**\n * An in-memory BM25 lexical retriever — the keyword half of hybrid search. Feed it the SAME\n * {@link ChunkRecord}s you upsert into the vector store (from `chunkDocuments`) so their chunk ids\n * line up and `HybridRetriever` can fuse the two rankings. Full BM25: idf, term-frequency saturation\n * (`k1`), and document-length normalization (`b`).\n */\nexport class KeywordRetriever implements Retriever {\n private readonly docs = new Map<string, IndexedDoc>();\n private readonly documentFrequency = new Map<string, number>();\n private totalLength = 0;\n private readonly k1: number;\n private readonly b: number;\n\n constructor(options: KeywordRetrieverOptions = {}) {\n this.k1 = options.k1 ?? 1.5;\n this.b = options.b ?? 0.75;\n }\n\n /** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */\n add(records: ChunkRecord[]): void {\n for (const record of records) {\n this.remove(record.id);\n const tokens = tokenize(record.text);\n const frequencies = new Map<string, number>();\n for (const token of tokens) {\n frequencies.set(token, (frequencies.get(token) ?? 0) + 1);\n }\n for (const term of frequencies.keys()) {\n this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);\n }\n this.docs.set(record.id, { record, frequencies, length: tokens.length });\n this.totalLength += tokens.length;\n }\n }\n\n private remove(id: string): void {\n const existing = this.docs.get(id);\n if (existing === undefined) {\n return;\n }\n for (const term of existing.frequencies.keys()) {\n const next = (this.documentFrequency.get(term) ?? 1) - 1;\n if (next <= 0) {\n this.documentFrequency.delete(term);\n } else {\n this.documentFrequency.set(term, next);\n }\n }\n this.totalLength -= existing.length;\n this.docs.delete(id);\n }\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const total = this.docs.size;\n if (total === 0) {\n return [];\n }\n const averageLength = this.totalLength / total;\n const queryTerms = new Set(tokenize(query));\n const scored: Passage[] = [];\n for (const { record, frequencies, length } of this.docs.values()) {\n if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) {\n continue;\n }\n let score = 0;\n for (const term of queryTerms) {\n const termFrequency = frequencies.get(term);\n if (termFrequency === undefined) {\n continue;\n }\n const df = this.documentFrequency.get(term) ?? 0;\n const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));\n const denominator =\n termFrequency + this.k1 * (1 - this.b + (this.b * length) / averageLength);\n score += idf * ((termFrequency * (this.k1 + 1)) / denominator);\n }\n if (score > 0) {\n scored.push({\n id: record.id,\n text: record.text,\n score,\n ...(record.source !== undefined ? { source: record.source } : {}),\n ...(record.metadata !== undefined ? { metadata: record.metadata } : {}),\n });\n }\n }\n return scored.sort((a, b) => b.score - a.score).slice(0, options.topK ?? 5);\n }\n}\n\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface HybridRetrieverOptions {\n /** RRF constant — larger flattens the rank weighting. Default 60 (the value from the original paper). */\n k?: number;\n /** Candidates pulled from each retriever before fusion. Default 20. */\n fetchTopK?: number;\n /** Per-retriever multipliers on each RRF contribution (same order as `retrievers`). Default all 1. */\n weights?: number[];\n}\n\n/**\n * Fuses several retrievers with Reciprocal Rank Fusion — the robust way to combine, say, dense\n * vector search with lexical BM25. RRF scores each passage by `Σ weight / (k + rank)` across the\n * lists it appears in, so it needs no score normalization between incompatible scales (cosine vs\n * BM25). Deduplicates by passage id, so aligned chunk ids from the two retrievers reinforce.\n */\nexport class HybridRetriever implements Retriever {\n constructor(\n private readonly retrievers: Retriever[],\n private readonly options: HybridRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const k = this.options.k ?? 60;\n const fetchTopK = this.options.fetchTopK ?? 20;\n const weights = this.options.weights;\n const lists = await Promise.all(\n this.retrievers.map((retriever) =>\n retriever.retrieve(query, {\n topK: fetchTopK,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n }),\n ),\n );\n\n const fused = new Map<string, { passage: Passage; score: number }>();\n lists.forEach((list, listIndex) => {\n const weight = weights?.[listIndex] ?? 1;\n list.forEach((passage, rank) => {\n const contribution = weight / (k + rank + 1);\n const existing = fused.get(passage.id);\n if (existing !== undefined) {\n existing.score += contribution;\n } else {\n fused.set(passage.id, { passage, score: contribution });\n }\n });\n });\n\n return [...fused.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, options.topK ?? 5)\n .map((entry) => ({ ...entry.passage, score: entry.score }));\n }\n}\n","import type { Passage, Reranker, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\nexport interface RerankingRetrieverOptions {\n /** How many candidates to pull from the base retriever before reranking. Default 20. */\n fetchTopK?: number;\n}\n\n/**\n * Two-stage retrieval: over-fetch cheap candidates from a base `Retriever`, then reorder them with a\n * stronger {@link Reranker} and keep the top few. The standard precision boost — a fast first stage\n * casts a wide net, a slow accurate second stage sharpens it. Composes over ANY retriever (vector,\n * hybrid, keyword), and is itself a `Retriever`.\n */\nexport class RerankingRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly reranker: Reranker,\n private readonly options: RerankingRetrieverOptions = {},\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n const candidates = await this.base.retrieve(query, {\n topK: this.options.fetchTopK ?? 20,\n ...(options.filter !== undefined ? { filter: options.filter } : {}),\n });\n return this.reranker.rerank(query, candidates, { topK: options.topK ?? 5 });\n }\n}\n","import type { Passage, RetrieveOptions, Retriever } from '@dudousxd/nestjs-agent-core';\n\n/**\n * Wraps a `Retriever` so every `retrieve` call ANDs a fixed metadata `filter` on top of whatever the\n * caller passes — the owner/tenant-scoping primitive. `new FilteredRetriever(base, { ownerId })` only\n * ever returns that owner's passages, so a shared vector store serves per-user / per-tenant RAG\n * without the agent (or a crafted query) reaching across the boundary.\n *\n * The fixed filter takes precedence on key conflicts: a caller can narrow further but cannot widen\n * past the scope. Composes over ANY retriever (vector, hybrid, reranked) and is itself a `Retriever`.\n */\nexport class FilteredRetriever implements Retriever {\n constructor(\n private readonly base: Retriever,\n private readonly filter: Record<string, unknown>,\n ) {}\n\n async retrieve(query: string, options: RetrieveOptions = {}): Promise<Passage[]> {\n return this.base.retrieve(query, {\n ...options,\n filter: { ...options.filter, ...this.filter },\n });\n }\n}\n","import type { EmbeddingProvider } from '@dudousxd/nestjs-agent-core';\nimport { type ChunkOptions, chunkText } from './chunk.js';\nimport type { VectorRecord, VectorStore } from './vector-store.js';\n\n/** A source document to ingest. `id` scopes the chunk ids (`${id}#${n}`); `source` is the citation. */\nexport interface IngestDocument {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A chunked, not-yet-embedded record. Feed the same array to a vector store AND a keyword index so\n * both retrievers key on identical chunk ids (which is what lets hybrid fusion line them up). */\nexport interface ChunkRecord {\n id: string;\n text: string;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface IngestChunksOptions {\n embedder: EmbeddingProvider;\n store: VectorStore;\n}\n\nexport interface IngestOptions extends ChunkOptions, IngestChunksOptions {}\n\n/**\n * Split documents into chunk records. Chunk ids are `${doc.id}#${index}`, so re-chunking the same\n * document produces the same ids (upsert overwrites in place rather than duplicating).\n */\nexport function chunkDocuments(\n documents: IngestDocument[],\n options: ChunkOptions = {},\n): ChunkRecord[] {\n const chunks: ChunkRecord[] = [];\n for (const document of documents) {\n chunkText(document.text, options).forEach((text, index) => {\n chunks.push({\n id: `${document.id}#${index}`,\n text,\n ...(document.source !== undefined ? { source: document.source } : {}),\n ...(document.metadata !== undefined ? { metadata: document.metadata } : {}),\n });\n });\n }\n return chunks;\n}\n\n/** Embed pre-chunked records (one batched `embed`) and upsert them. Returns the record count. */\nexport async function ingestChunks(\n chunks: ChunkRecord[],\n options: IngestChunksOptions,\n): Promise<number> {\n if (chunks.length === 0) {\n return 0;\n }\n const embeddings = await options.embedder.embed(chunks.map((chunk) => chunk.text));\n const records: VectorRecord[] = chunks.map((chunk, index) => ({\n ...chunk,\n embedding: embeddings[index] ?? [],\n }));\n await options.store.upsert(records);\n return records.length;\n}\n\n/** Chunk → embed → upsert in one call. For hybrid search, use {@link chunkDocuments} +\n * {@link ingestChunks} and feed the same chunks to a {@link import('./keyword-retriever.js').KeywordRetriever}. */\nexport async function ingestDocuments(\n documents: IngestDocument[],\n options: IngestOptions,\n): Promise<number> {\n return ingestChunks(chunkDocuments(documents, options), options);\n}\n","import type { Passage, Retriever, ToolHandler, ToolSpec } from '@dudousxd/nestjs-agent-core';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nexport interface RetrievalToolOptions {\n /** Tool name the model sees. Default `search_knowledge`. */\n name?: string;\n description?: string;\n /** Passages per search. Default lets the retriever decide (typically 5). */\n topK?: number;\n}\n\n/** A functional tool (`{ spec, handler }`) — pass it to nestjs `provideAgentTool`. */\nexport interface RetrievalTool {\n spec: ToolSpec;\n handler: ToolHandler;\n}\n\nfunction isQueryInput(value: unknown): value is { query: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'query' in value &&\n typeof (value as Record<'query', unknown>).query === 'string'\n );\n}\n\n/** A hand-rolled Standard Schema for `{ query: string }` — keeps this package validation-lib-free. */\nconst querySchema: StandardSchemaV1<{ query: string }> = {\n '~standard': {\n version: 1,\n vendor: 'nestjs-agent-rag',\n validate(value: unknown) {\n return isQueryInput(value)\n ? { value: { query: value.query } }\n : { issues: [{ message: 'retrieval tool expects { query: string }' }] };\n },\n },\n};\n\n/**\n * Builds the agentic-retrieval tool: a `read`-kind `search_knowledge(query)` the model calls when it\n * decides it needs context. Its output `{ passages }` persists as the tool call's result, so sources\n * flow to the frontend and telescope through the normal tool-call machinery (the citation surface).\n *\n * ```ts\n * providers: [provideAgentTool(createRetrievalTool(retriever))]\n * ```\n */\nexport function createRetrievalTool(\n retriever: Retriever,\n options: RetrievalToolOptions = {},\n): RetrievalTool {\n const topK = options.topK;\n return {\n spec: {\n name: options.name ?? 'search_knowledge',\n kind: 'read',\n description:\n options.description ??\n 'Search the knowledge base for passages relevant to a query. Returns passages with their source for citation.',\n inputSchema: querySchema,\n },\n handler: {\n async execute(input: unknown): Promise<{ passages: Passage[] }> {\n if (!isQueryInput(input)) {\n throw new Error('retrieval tool expects { query: string }');\n }\n const passages = await retriever.retrieve(input.query, topK !== undefined ? { topK } : {});\n return { passages };\n },\n },\n };\n}\n"],"mappings":"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","names":["chunkText","text","options","chunkSize","overlap","normalized","trim","length","chunks","start","end","Math","min","breakAt","window","slice","boundary","max","lastIndexOf","chunk","push","documentIdOf","chunkId","replace","matchesFilter","metadata","filter","undefined","Object","keys","length","entries","every","key","value","MemoryVectorStore","records","Map","upsert","record","set","id","remove","documentId","keys","documentIdOf","delete","listDocuments","filter","documents","values","undefined","matchesFilter","metadata","has","search","embedding","options","scored","push","text","score","cosineSimilarity","source","sort","a","b","slice","topK","dot","normA","normB","length","Math","min","index","valueA","valueB","denominator","sqrt","PgVectorStore","table","dimensions","client","options","ensureSchema","query","upsert","records","record","id","text","source","metadata","undefined","JSON","stringify","toVectorLiteral","embedding","remove","documentId","DOCUMENT_ID_FROM_CHUNK","listDocuments","filter","hasFilter","Object","keys","length","rows","map","row","doc_id","search","vector","topK","score","Number","join","RedisVectorStore","index","prefix","dimensions","filterableFields","client","options","ensureSchema","sendCommand","schema","field","push","String","upsert","records","record","args","id","text","encodeVector","embedding","source","undefined","metadata","JSON","stringify","value","remove","documentId","keys","Set","pattern","escapeGlob","cursor","reply","page","parseScanReply","key","add","size","listDocuments","filter","query","buildFilter","documents","Map","batchSize","offset","passages","parseSearchReply","passage","documentIdOf","has","set","length","values","search","topK","Buffer","from","Float32Array","buffer","Object","clauses","entries","map","escapeTag","join","replace","char","Array","isArray","rawKeys","toStr","isBuffer","toString","rawFields","attrs","toPassage","results","result","readId","readAttributes","raw","extra_attributes","rawId","distance","Number","vector_score","startsWith","slice","score","parseMetadata","metadata_json","parsed","parse","EmbeddingRetriever","embedder","store","retrieve","query","options","embedding","embed","undefined","search","topK","filter","KeywordRetriever","docs","Map","documentFrequency","totalLength","k1","b","options","add","records","record","remove","id","tokens","tokenize","text","frequencies","token","set","get","term","keys","length","existing","undefined","next","delete","retrieve","query","total","size","averageLength","queryTerms","Set","scored","values","filter","matchesFilter","metadata","score","termFrequency","df","idf","Math","log","denominator","push","source","sort","a","slice","topK","toLowerCase","match","HybridRetriever","retrievers","options","retrieve","query","k","fetchTopK","weights","lists","Promise","all","map","retriever","topK","filter","undefined","fused","Map","forEach","list","listIndex","weight","passage","rank","contribution","existing","get","id","score","set","values","sort","a","b","slice","entry","RerankingRetriever","base","reranker","options","retrieve","query","candidates","topK","fetchTopK","filter","undefined","rerank","FilteredRetriever","base","filter","retrieve","query","options","chunkDocuments","documents","options","chunks","document","chunkText","text","forEach","index","push","id","source","undefined","metadata","ingestChunks","length","embeddings","embedder","embed","map","chunk","records","embedding","store","upsert","ingestDocuments","isQueryInput","value","query","querySchema","version","vendor","validate","issues","message","createRetrievalTool","retriever","options","topK","spec","name","kind","description","inputSchema","handler","execute","input","Error","passages","retrieve","undefined"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { Passage, Retriever, EmbeddingProvider, RetrieveOptions, Reranker, ToolSpec, ToolHandler } from '@dudousxd/nestjs-agent-core';
|
|
2
|
+
|
|
3
|
+
interface ChunkOptions {
|
|
4
|
+
/** Target max characters per chunk. Default 800. */
|
|
5
|
+
chunkSize?: number;
|
|
6
|
+
/** Characters of overlap carried from the end of one chunk into the next. Default 100. */
|
|
7
|
+
overlap?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Splits text into overlapping chunks for embedding. Greedy up to `chunkSize`, but prefers to break
|
|
11
|
+
* on a paragraph → sentence → word boundary in the back half of the window (so a chunk rarely cuts
|
|
12
|
+
* mid-sentence), then carries `overlap` characters into the next chunk to preserve context across
|
|
13
|
+
* the seam. Pure and deterministic.
|
|
14
|
+
*/
|
|
15
|
+
declare function chunkText(text: string, options?: ChunkOptions): string[];
|
|
16
|
+
|
|
17
|
+
/** A stored, embedded chunk. `embedding` length must match the store's configured dimensions. */
|
|
18
|
+
interface VectorRecord {
|
|
19
|
+
id: string;
|
|
20
|
+
text: string;
|
|
21
|
+
embedding: number[];
|
|
22
|
+
/** Citation-facing origin (document title, URL, row id). */
|
|
23
|
+
source?: string;
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
interface VectorSearchOptions {
|
|
27
|
+
topK: number;
|
|
28
|
+
/** Exact-match metadata filter (all keys must equal). Passed through from `RetrieveOptions.filter`. */
|
|
29
|
+
filter?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The write + search side of RAG storage. `@dudousxd/nestjs-agent-rag` ships `MemoryVectorStore`
|
|
33
|
+
* (in-JS cosine, tests + small scale) and `PgVectorStore` (pgvector); any impl works. Pair one with
|
|
34
|
+
* an `EmbeddingProvider` via {@link import('./embedding-retriever.js').EmbeddingRetriever} to get a
|
|
35
|
+
* core `Retriever`.
|
|
36
|
+
*/
|
|
37
|
+
interface VectorStore {
|
|
38
|
+
upsert(records: VectorRecord[]): Promise<void>;
|
|
39
|
+
search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Delete every chunk belonging to a source document — all records whose id is `${documentId}`
|
|
42
|
+
* or `${documentId}#<n>` (the id scheme {@link import('./ingest.js').chunkDocuments} produces).
|
|
43
|
+
* Use it to drop a document from the index, and before re-ingesting one: `upsert` overwrites
|
|
44
|
+
* matching ids but can't remove chunks a shorter new version no longer produces, so a re-ingest
|
|
45
|
+
* without a preceding `remove` leaves the old tail orphaned.
|
|
46
|
+
*/
|
|
47
|
+
remove(documentId: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* List the distinct source documents currently indexed (chunk ids collapsed back to their document
|
|
50
|
+
* by stripping the trailing `#<n>`), each with a representative chunk's `metadata`, optionally
|
|
51
|
+
* narrowed by a metadata `filter`. The enumeration seam for reconciliation: diff this against your
|
|
52
|
+
* source of truth to find documents to ingest (missing), re-ingest (a stored fingerprint like
|
|
53
|
+
* `size` changed), or {@link VectorStore.remove remove} (orphaned).
|
|
54
|
+
*/
|
|
55
|
+
listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
|
|
56
|
+
}
|
|
57
|
+
/** A distinct source document as seen by the index — its id plus a representative chunk's metadata. */
|
|
58
|
+
interface IndexedDocument {
|
|
59
|
+
id: string;
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
/** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */
|
|
63
|
+
declare function documentIdOf(chunkId: string): string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* An in-process {@link VectorStore} — cosine similarity over a Map, no infra. The reference adapter
|
|
67
|
+
* for tests and small/embedded corpora; for production scale use `PgVectorStore` (or your own).
|
|
68
|
+
*/
|
|
69
|
+
declare class MemoryVectorStore implements VectorStore {
|
|
70
|
+
private readonly records;
|
|
71
|
+
upsert(records: VectorRecord[]): Promise<void>;
|
|
72
|
+
remove(documentId: string): Promise<void>;
|
|
73
|
+
listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
|
|
74
|
+
search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The minimal Postgres surface {@link PgVectorStore} needs — adapt your own `pg` / `postgres.js`
|
|
79
|
+
* client to it, so this package pulls in NO driver (bring your own, like the store adapters take an
|
|
80
|
+
* ORM handle and the Redis transport takes a client). `query` runs parameterized SQL (`$1,$2,…`) and
|
|
81
|
+
* resolves the result rows.
|
|
82
|
+
*/
|
|
83
|
+
interface PgClient {
|
|
84
|
+
query<Row = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<Row[]>;
|
|
85
|
+
}
|
|
86
|
+
interface PgVectorStoreOptions {
|
|
87
|
+
/** Table name. Default `agent_rag_chunks`. */
|
|
88
|
+
table?: string;
|
|
89
|
+
/** Embedding width — must match your model (e.g. 1536 for text-embedding-3-small). Default 1536. */
|
|
90
|
+
dimensions?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A pgvector-backed {@link VectorStore} — the production reference adapter. Cosine distance via the
|
|
94
|
+
* `<=>` operator over an HNSW index; metadata in a `jsonb` column filtered with `@>`. Call
|
|
95
|
+
* {@link PgVectorStore.ensureSchema} once at boot to create the extension, table, and index.
|
|
96
|
+
*/
|
|
97
|
+
declare class PgVectorStore implements VectorStore {
|
|
98
|
+
private readonly client;
|
|
99
|
+
private readonly table;
|
|
100
|
+
private readonly dimensions;
|
|
101
|
+
constructor(client: PgClient, options?: PgVectorStoreOptions);
|
|
102
|
+
/** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index. */
|
|
103
|
+
ensureSchema(): Promise<void>;
|
|
104
|
+
upsert(records: VectorRecord[]): Promise<void>;
|
|
105
|
+
remove(documentId: string): Promise<void>;
|
|
106
|
+
listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
|
|
107
|
+
search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The minimal Redis surface {@link RedisVectorStore} needs: send a raw command and get the reply.
|
|
112
|
+
* Both `node-redis` (`client.sendCommand(args)`) and `ioredis` (wrap `redis.call`) satisfy it — so
|
|
113
|
+
* this package pulls in NO Redis driver (bring your own, like the other adapters). Requires the
|
|
114
|
+
* RediSearch module (Redis Stack / Redis 8+).
|
|
115
|
+
*/
|
|
116
|
+
interface RedisSearchClient {
|
|
117
|
+
sendCommand(args: (string | Buffer)[]): Promise<unknown>;
|
|
118
|
+
}
|
|
119
|
+
interface RedisVectorStoreOptions {
|
|
120
|
+
/** RediSearch index name. Default `agent_rag_idx`. */
|
|
121
|
+
index?: string;
|
|
122
|
+
/** Key prefix for the per-chunk hashes. Default `agent_rag:`. */
|
|
123
|
+
prefix?: string;
|
|
124
|
+
/** Embedding width — must match your model (e.g. 1536). Default 1536. */
|
|
125
|
+
dimensions?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Metadata keys to index as filterable TAG fields. RediSearch needs filter fields declared up
|
|
128
|
+
* front, so only keys listed here can be passed in `search`'s `filter`. Default none.
|
|
129
|
+
*/
|
|
130
|
+
filterableFields?: string[];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A RediSearch-backed {@link VectorStore} — HNSW + cosine over Redis, the ecosystem-native option
|
|
134
|
+
* for anyone already running Redis (see `-transport-redis`). Chunks are hashes under `prefix`, the
|
|
135
|
+
* embedding a FLOAT32 vector field; `search` is a KNN query. Call {@link RedisVectorStore.ensureSchema}
|
|
136
|
+
* once at boot to create the index.
|
|
137
|
+
*/
|
|
138
|
+
declare class RedisVectorStore implements VectorStore {
|
|
139
|
+
private readonly client;
|
|
140
|
+
private readonly index;
|
|
141
|
+
private readonly prefix;
|
|
142
|
+
private readonly dimensions;
|
|
143
|
+
private readonly filterableFields;
|
|
144
|
+
constructor(client: RedisSearchClient, options?: RedisVectorStoreOptions);
|
|
145
|
+
/** Idempotent: create the HNSW/cosine index if `FT.INFO` says it doesn't exist yet. */
|
|
146
|
+
ensureSchema(): Promise<void>;
|
|
147
|
+
upsert(records: VectorRecord[]): Promise<void>;
|
|
148
|
+
remove(documentId: string): Promise<void>;
|
|
149
|
+
listDocuments(filter?: Record<string, unknown>): Promise<IndexedDocument[]>;
|
|
150
|
+
search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Bridges an {@link EmbeddingProvider} + {@link VectorStore} into the core {@link Retriever} SPI:
|
|
155
|
+
* embed the query, then vector-search. This is what you wire as the agent's retriever (as a tool
|
|
156
|
+
* via `createRetrievalTool`, or into `AgentModule.forRoot({ retrieval })` for inject mode).
|
|
157
|
+
*/
|
|
158
|
+
declare class EmbeddingRetriever implements Retriever {
|
|
159
|
+
private readonly embedder;
|
|
160
|
+
private readonly store;
|
|
161
|
+
constructor(embedder: EmbeddingProvider, store: VectorStore);
|
|
162
|
+
retrieve(query: string, options?: RetrieveOptions): Promise<Passage[]>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** A source document to ingest. `id` scopes the chunk ids (`${id}#${n}`); `source` is the citation. */
|
|
166
|
+
interface IngestDocument {
|
|
167
|
+
id: string;
|
|
168
|
+
text: string;
|
|
169
|
+
source?: string;
|
|
170
|
+
metadata?: Record<string, unknown>;
|
|
171
|
+
}
|
|
172
|
+
/** A chunked, not-yet-embedded record. Feed the same array to a vector store AND a keyword index so
|
|
173
|
+
* both retrievers key on identical chunk ids (which is what lets hybrid fusion line them up). */
|
|
174
|
+
interface ChunkRecord {
|
|
175
|
+
id: string;
|
|
176
|
+
text: string;
|
|
177
|
+
source?: string;
|
|
178
|
+
metadata?: Record<string, unknown>;
|
|
179
|
+
}
|
|
180
|
+
interface IngestChunksOptions {
|
|
181
|
+
embedder: EmbeddingProvider;
|
|
182
|
+
store: VectorStore;
|
|
183
|
+
}
|
|
184
|
+
interface IngestOptions extends ChunkOptions, IngestChunksOptions {
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Split documents into chunk records. Chunk ids are `${doc.id}#${index}`, so re-chunking the same
|
|
188
|
+
* document produces the same ids (upsert overwrites in place rather than duplicating).
|
|
189
|
+
*/
|
|
190
|
+
declare function chunkDocuments(documents: IngestDocument[], options?: ChunkOptions): ChunkRecord[];
|
|
191
|
+
/** Embed pre-chunked records (one batched `embed`) and upsert them. Returns the record count. */
|
|
192
|
+
declare function ingestChunks(chunks: ChunkRecord[], options: IngestChunksOptions): Promise<number>;
|
|
193
|
+
/** Chunk → embed → upsert in one call. For hybrid search, use {@link chunkDocuments} +
|
|
194
|
+
* {@link ingestChunks} and feed the same chunks to a {@link import('./keyword-retriever.js').KeywordRetriever}. */
|
|
195
|
+
declare function ingestDocuments(documents: IngestDocument[], options: IngestOptions): Promise<number>;
|
|
196
|
+
|
|
197
|
+
interface KeywordRetrieverOptions {
|
|
198
|
+
/** BM25 term-frequency saturation. Default 1.5. */
|
|
199
|
+
k1?: number;
|
|
200
|
+
/** BM25 length normalization, 0 (off) … 1 (full). Default 0.75. */
|
|
201
|
+
b?: number;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* An in-memory BM25 lexical retriever — the keyword half of hybrid search. Feed it the SAME
|
|
205
|
+
* {@link ChunkRecord}s you upsert into the vector store (from `chunkDocuments`) so their chunk ids
|
|
206
|
+
* line up and `HybridRetriever` can fuse the two rankings. Full BM25: idf, term-frequency saturation
|
|
207
|
+
* (`k1`), and document-length normalization (`b`).
|
|
208
|
+
*/
|
|
209
|
+
declare class KeywordRetriever implements Retriever {
|
|
210
|
+
private readonly docs;
|
|
211
|
+
private readonly documentFrequency;
|
|
212
|
+
private totalLength;
|
|
213
|
+
private readonly k1;
|
|
214
|
+
private readonly b;
|
|
215
|
+
constructor(options?: KeywordRetrieverOptions);
|
|
216
|
+
/** Index (or re-index) records. Re-adding an existing id replaces its posting and updates stats. */
|
|
217
|
+
add(records: ChunkRecord[]): void;
|
|
218
|
+
private remove;
|
|
219
|
+
retrieve(query: string, options?: RetrieveOptions): Promise<Passage[]>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface HybridRetrieverOptions {
|
|
223
|
+
/** RRF constant — larger flattens the rank weighting. Default 60 (the value from the original paper). */
|
|
224
|
+
k?: number;
|
|
225
|
+
/** Candidates pulled from each retriever before fusion. Default 20. */
|
|
226
|
+
fetchTopK?: number;
|
|
227
|
+
/** Per-retriever multipliers on each RRF contribution (same order as `retrievers`). Default all 1. */
|
|
228
|
+
weights?: number[];
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Fuses several retrievers with Reciprocal Rank Fusion — the robust way to combine, say, dense
|
|
232
|
+
* vector search with lexical BM25. RRF scores each passage by `Σ weight / (k + rank)` across the
|
|
233
|
+
* lists it appears in, so it needs no score normalization between incompatible scales (cosine vs
|
|
234
|
+
* BM25). Deduplicates by passage id, so aligned chunk ids from the two retrievers reinforce.
|
|
235
|
+
*/
|
|
236
|
+
declare class HybridRetriever implements Retriever {
|
|
237
|
+
private readonly retrievers;
|
|
238
|
+
private readonly options;
|
|
239
|
+
constructor(retrievers: Retriever[], options?: HybridRetrieverOptions);
|
|
240
|
+
retrieve(query: string, options?: RetrieveOptions): Promise<Passage[]>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interface RerankingRetrieverOptions {
|
|
244
|
+
/** How many candidates to pull from the base retriever before reranking. Default 20. */
|
|
245
|
+
fetchTopK?: number;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Two-stage retrieval: over-fetch cheap candidates from a base `Retriever`, then reorder them with a
|
|
249
|
+
* stronger {@link Reranker} and keep the top few. The standard precision boost — a fast first stage
|
|
250
|
+
* casts a wide net, a slow accurate second stage sharpens it. Composes over ANY retriever (vector,
|
|
251
|
+
* hybrid, keyword), and is itself a `Retriever`.
|
|
252
|
+
*/
|
|
253
|
+
declare class RerankingRetriever implements Retriever {
|
|
254
|
+
private readonly base;
|
|
255
|
+
private readonly reranker;
|
|
256
|
+
private readonly options;
|
|
257
|
+
constructor(base: Retriever, reranker: Reranker, options?: RerankingRetrieverOptions);
|
|
258
|
+
retrieve(query: string, options?: RetrieveOptions): Promise<Passage[]>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Wraps a `Retriever` so every `retrieve` call ANDs a fixed metadata `filter` on top of whatever the
|
|
263
|
+
* caller passes — the owner/tenant-scoping primitive. `new FilteredRetriever(base, { ownerId })` only
|
|
264
|
+
* ever returns that owner's passages, so a shared vector store serves per-user / per-tenant RAG
|
|
265
|
+
* without the agent (or a crafted query) reaching across the boundary.
|
|
266
|
+
*
|
|
267
|
+
* The fixed filter takes precedence on key conflicts: a caller can narrow further but cannot widen
|
|
268
|
+
* past the scope. Composes over ANY retriever (vector, hybrid, reranked) and is itself a `Retriever`.
|
|
269
|
+
*/
|
|
270
|
+
declare class FilteredRetriever implements Retriever {
|
|
271
|
+
private readonly base;
|
|
272
|
+
private readonly filter;
|
|
273
|
+
constructor(base: Retriever, filter: Record<string, unknown>);
|
|
274
|
+
retrieve(query: string, options?: RetrieveOptions): Promise<Passage[]>;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface RetrievalToolOptions {
|
|
278
|
+
/** Tool name the model sees. Default `search_knowledge`. */
|
|
279
|
+
name?: string;
|
|
280
|
+
description?: string;
|
|
281
|
+
/** Passages per search. Default lets the retriever decide (typically 5). */
|
|
282
|
+
topK?: number;
|
|
283
|
+
}
|
|
284
|
+
/** A functional tool (`{ spec, handler }`) — pass it to nestjs `provideAgentTool`. */
|
|
285
|
+
interface RetrievalTool {
|
|
286
|
+
spec: ToolSpec;
|
|
287
|
+
handler: ToolHandler;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Builds the agentic-retrieval tool: a `read`-kind `search_knowledge(query)` the model calls when it
|
|
291
|
+
* decides it needs context. Its output `{ passages }` persists as the tool call's result, so sources
|
|
292
|
+
* flow to the frontend and telescope through the normal tool-call machinery (the citation surface).
|
|
293
|
+
*
|
|
294
|
+
* ```ts
|
|
295
|
+
* providers: [provideAgentTool(createRetrievalTool(retriever))]
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
declare function createRetrievalTool(retriever: Retriever, options?: RetrievalToolOptions): RetrievalTool;
|
|
299
|
+
|
|
300
|
+
export { type ChunkOptions, type ChunkRecord, EmbeddingRetriever, FilteredRetriever, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, KeywordRetriever, type KeywordRetrieverOptions, MemoryVectorStore, type PgClient, PgVectorStore, type PgVectorStoreOptions, type RedisSearchClient, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalTool, type RetrievalToolOptions, type VectorRecord, type VectorSearchOptions, type VectorStore, chunkDocuments, chunkText, createRetrievalTool, documentIdOf, ingestChunks, ingestDocuments };
|