@devilsdev/rag-pipeline-utils 2.6.2 → 2.7.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/BENCHMARKS.md +7 -1
- package/CHANGELOG.md +121 -0
- package/README.md +35 -24
- package/dist/index.cjs +4164 -1825
- package/dist/index.d.ts +265 -256
- package/dist/index.mjs +1 -1
- package/dist/types/cache/memory-cache.d.ts +4 -1
- package/dist/types/cache/redis-cache.d.ts +4 -1
- package/dist/types/cli/services/instrument.d.ts +14 -0
- package/dist/types/cli/services/rag-service.d.ts +140 -0
- package/dist/types/cli/shared/config-context.d.ts +9 -0
- package/dist/types/config/component-factory.d.ts +57 -0
- package/dist/types/config/enhanced-ragrc-schema.d.ts +648 -0
- package/dist/types/config/load-config.d.ts +18 -0
- package/dist/types/config/normalize-config.d.ts +9 -0
- package/dist/types/core/observability/event-logger.d.ts +211 -0
- package/dist/types/core/observability/metrics.d.ts +585 -0
- package/dist/types/core/performance/parallel-processor.d.ts +92 -0
- package/dist/types/core/plugin-registry.d.ts +5 -0
- package/dist/types/dag/core/execution-scheduler.d.ts +3 -1
- package/dist/types/dag/dag-engine.d.ts +15 -2
- package/dist/types/dev/dev-server.d.ts +136 -0
- package/dist/types/dev/hot-reload.d.ts +226 -0
- package/dist/types/ecosystem/plugin-analytics-dashboard.d.ts +141 -0
- package/dist/types/ecosystem/plugin-hub.d.ts +161 -0
- package/dist/types/ecosystem/plugin-installer.d.ts +40 -0
- package/dist/types/ecosystem/plugin-probe.d.ts +21 -0
- package/dist/types/ecosystem/plugin-scanner.d.ts +30 -0
- package/dist/types/ecosystem/tar-extract.d.ts +24 -0
- package/dist/types/enterprise/audit-logging.d.ts +217 -0
- package/dist/types/evaluate/compare.d.ts +140 -0
- package/dist/types/evaluate/dataset.d.ts +43 -0
- package/dist/types/evaluate/evaluator.d.ts +14 -0
- package/dist/types/evaluate/report.d.ts +56 -0
- package/dist/types/evaluate/synthetic.d.ts +64 -0
- package/dist/types/loader/csv-loader.d.ts +10 -0
- package/dist/types/loader/document-reader.d.ts +18 -0
- package/dist/types/loader/html-loader.d.ts +21 -0
- package/dist/types/loader/markdown-loader.d.ts +21 -0
- package/dist/types/retrieval/bm25-search.d.ts +4 -0
- package/dist/types/retrieval/file-vector-store.d.ts +4 -1
- package/dist/types/retrieval/hybrid-retriever.d.ts +4 -0
- package/dist/types/retrieval/pgvector-retriever.d.ts +123 -0
- package/dist/types/retrieval/qdrant-retriever.d.ts +106 -0
- package/dist/types/security/validator.d.ts +11 -1
- package/dist/types/testing.d.ts +2 -0
- package/dist/types/utils/batch-processor.d.ts +205 -0
- package/dist/types/utils/connection-pool.d.ts +158 -0
- package/dist/types/utils/error-formatter.d.ts +312 -0
- package/dist/types/utils/logger.d.ts +29 -4
- package/dist/types/utils/rate-limiter.d.ts +13 -8
- package/dist/types/utils/retry.d.ts +10 -0
- package/package.json +12 -2
- package/src/cache/memory-cache.js +4 -1
- package/src/cache/redis-cache.js +4 -1
- package/src/cli/enhanced-cli-commands.js +79 -0
- package/src/cli/handlers/ai-handler.js +2 -2
- package/src/cli/handlers/eval-handler.js +201 -0
- package/src/cli/handlers/generate-handler.js +103 -0
- package/src/cli/services/benchmark.js +2 -2
- package/src/cli/services/rag-service.js +59 -10
- package/src/cli/shared/cli-utils.js +40 -1
- package/src/config/component-factory.js +32 -0
- package/src/connectors/local-embedder.js +20 -13
- package/src/core/observability/event-logger.js +135 -1
- package/src/core/observability/metrics.js +223 -5
- package/src/core/performance/parallel-processor.js +2 -0
- package/src/core/plugin-registry.js +5 -1
- package/src/dag/core/execution-scheduler.js +2 -0
- package/src/dag/dag-engine.js +13 -0
- package/src/dev/dev-server.js +6 -0
- package/src/ecosystem/plugin-hub.js +4 -0
- package/src/enterprise/audit-logging.js +5 -0
- package/src/evaluate/compare.js +395 -0
- package/src/evaluate/dataset.js +207 -0
- package/src/evaluate/report.js +350 -0
- package/src/evaluate/synthetic.js +246 -0
- package/src/index.js +17 -0
- package/src/retrieval/bm25-search.js +4 -0
- package/src/retrieval/file-vector-store.js +4 -1
- package/src/retrieval/hybrid-retriever.js +4 -0
- package/src/retrieval/pgvector-retriever.js +420 -0
- package/src/retrieval/qdrant-retriever.js +361 -0
- package/src/security/validator.js +8 -0
- package/src/testing.js +2 -0
- package/src/utils/batch-processor.js +7 -3
- package/src/utils/error-formatter.js +48 -20
- package/src/utils/logger.js +31 -1
- package/src/utils/rate-limiter.js +9 -4
- package/src/utils/retry.js +10 -1
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Retriever on PostgreSQL with the pgvector extension.
|
|
5
|
+
*
|
|
6
|
+
* Pass a `pg` Pool or Client you manage (`pool`), or a `connectionString`
|
|
7
|
+
* and this class creates a Pool with the optional `pg` package. Documents
|
|
8
|
+
* live in one table: id (text, primary key), content, metadata (jsonb) and
|
|
9
|
+
* embedding (vector). Similarity is cosine; `score` is 1 - cosine distance.
|
|
10
|
+
*
|
|
11
|
+
* The database can change without this process knowing, so the retriever
|
|
12
|
+
* has no cache identity unless you give it a `cacheKey`; see getCacheKey.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { throwIfAborted, raceAbort } = require('../connectors/connector-utils');
|
|
16
|
+
|
|
17
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
18
|
+
const DEFAULT_TABLE = 'rag_documents';
|
|
19
|
+
// pgvector's default hnsw.ef_search
|
|
20
|
+
const PG_DEFAULT_EF_SEARCH = 40;
|
|
21
|
+
// 4 parameters per row; pg allows 65535 per statement.
|
|
22
|
+
const MAX_BATCH = 1000;
|
|
23
|
+
|
|
24
|
+
/** Quotes a table name, optionally schema-qualified, after validating it. */
|
|
25
|
+
function quoteTable(name) {
|
|
26
|
+
const parts = String(name).split('.');
|
|
27
|
+
if (parts.length > 2 || !parts.every((p) => IDENTIFIER.test(p))) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`PgVectorRetriever: invalid table name "${name}" (letters, digits and underscores; optionally schema.table)`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return parts.map((p) => `"${p}"`).join('.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function vectorLiteral(vector, id) {
|
|
36
|
+
if (!Array.isArray(vector) || vector.length === 0) {
|
|
37
|
+
throw new Error(`PgVectorRetriever: document "${id}" has no vector`);
|
|
38
|
+
}
|
|
39
|
+
for (const x of vector) {
|
|
40
|
+
if (typeof x !== 'number' || !Number.isFinite(x)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`PgVectorRetriever: document "${id}" has a vector with a non-finite value`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return `[${vector.join(',')}]`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Turns a pg error into a message that says what to do. */
|
|
50
|
+
function explain(err, table) {
|
|
51
|
+
const code = err && err.code;
|
|
52
|
+
if (code === '42P01') {
|
|
53
|
+
return new Error(
|
|
54
|
+
`PgVectorRetriever: table ${table} does not exist. Store documents first (rag-pipeline ingest), or create it.`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (
|
|
58
|
+
code === '42501' ||
|
|
59
|
+
/permission denied/i.test(String(err && err.message))
|
|
60
|
+
) {
|
|
61
|
+
return new Error(
|
|
62
|
+
'PgVectorRetriever: permission denied. Run `CREATE EXTENSION IF NOT EXISTS vector;` as a database owner, ' +
|
|
63
|
+
`and grant this user access to ${table}. (${err.message})`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (
|
|
67
|
+
/different vector dimensions|expected \d+ dimensions/i.test(
|
|
68
|
+
String(err && err.message),
|
|
69
|
+
)
|
|
70
|
+
) {
|
|
71
|
+
return new Error(
|
|
72
|
+
`PgVectorRetriever: ${err.message}. The table holds vectors from another embedder; use the same embedder or a new table.`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return err;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Retriever on PostgreSQL with the pgvector extension. Creates the
|
|
80
|
+
* extension, table and index on first use; similarity is cosine.
|
|
81
|
+
*/
|
|
82
|
+
class PgVectorRetriever {
|
|
83
|
+
/**
|
|
84
|
+
* @param {Object} options
|
|
85
|
+
* @param {{ query: Function, connect?: Function }} [options.pool] - A pg Pool or Client you manage
|
|
86
|
+
* @param {string} [options.connectionString] - Used to create a Pool when no `pool` is given
|
|
87
|
+
* @param {string} [options.table='rag_documents'] - Table name, optionally `schema.table`
|
|
88
|
+
* @param {number} [options.dimensions] - Vector size for a new table; defaults to the first stored vector's
|
|
89
|
+
* @param {boolean} [options.createTable=true] - Create the extension, table and index on first store
|
|
90
|
+
* @param {'hnsw'|'none'} [options.index='hnsw'] - Index created with the table
|
|
91
|
+
* @param {number} [options.efSearch] - HNSW candidate list size per query (pgvector's default is 40). Higher finds more of the true nearest neighbours, more slowly; a query always uses at least its topK
|
|
92
|
+
* @param {string} [options.cacheKey] - Cache identity; change it when the table changes (see getCacheKey)
|
|
93
|
+
*/
|
|
94
|
+
constructor(options = {}) {
|
|
95
|
+
const {
|
|
96
|
+
pool,
|
|
97
|
+
connectionString,
|
|
98
|
+
table = DEFAULT_TABLE,
|
|
99
|
+
dimensions,
|
|
100
|
+
createTable = true,
|
|
101
|
+
index = 'hnsw',
|
|
102
|
+
efSearch,
|
|
103
|
+
cacheKey,
|
|
104
|
+
} = options;
|
|
105
|
+
if (!pool && !connectionString) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
'PgVectorRetriever needs a pg `pool` (or Client) or a `connectionString`',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (pool && typeof pool.query !== 'function') {
|
|
111
|
+
throw new Error('PgVectorRetriever: `pool` must have a query() method');
|
|
112
|
+
}
|
|
113
|
+
if (!['hnsw', 'none'].includes(index)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`PgVectorRetriever: unknown index "${index}" (use "hnsw" or "none")`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
dimensions !== undefined &&
|
|
120
|
+
!(Number.isInteger(dimensions) && dimensions > 0)
|
|
121
|
+
) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
'PgVectorRetriever: `dimensions` must be a positive integer',
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
efSearch !== undefined &&
|
|
128
|
+
!(Number.isInteger(efSearch) && efSearch > 0 && efSearch <= 1000)
|
|
129
|
+
) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
'PgVectorRetriever: `efSearch` must be an integer from 1 to 1000',
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
this.efSearch = efSearch;
|
|
135
|
+
this.table = quoteTable(table);
|
|
136
|
+
this.tableName = table;
|
|
137
|
+
this.dimensions = dimensions;
|
|
138
|
+
this.createTable = createTable;
|
|
139
|
+
this.index = index;
|
|
140
|
+
this.cacheKey = cacheKey;
|
|
141
|
+
this._pool = pool || null;
|
|
142
|
+
this._connectionString = connectionString;
|
|
143
|
+
this._ownsPool = !pool;
|
|
144
|
+
this._ready = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The pg Pool or Client in use; creates the Pool on first use. */
|
|
148
|
+
_db() {
|
|
149
|
+
if (!this._pool) {
|
|
150
|
+
let pg;
|
|
151
|
+
try {
|
|
152
|
+
pg = require('pg'); // eslint-disable-line global-require
|
|
153
|
+
} catch {
|
|
154
|
+
throw new Error(
|
|
155
|
+
'PgVectorRetriever with a connectionString needs the "pg" package: npm install pg',
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
// allowExitOnIdle: an idle pool must not keep the CLI or a script alive.
|
|
159
|
+
this._pool = new pg.Pool({
|
|
160
|
+
connectionString: this._connectionString,
|
|
161
|
+
allowExitOnIdle: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return this._pool;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async _query(text, params, signal) {
|
|
168
|
+
throwIfAborted(signal);
|
|
169
|
+
try {
|
|
170
|
+
return await raceAbort(this._db().query(text, params), signal);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
throw explain(err, this.table);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Runs one query with `SET LOCAL hnsw.ef_search`, in its own transaction. */
|
|
177
|
+
async _queryWithEfSearch(text, params, ef, signal) {
|
|
178
|
+
throwIfAborted(signal);
|
|
179
|
+
const db = this._db();
|
|
180
|
+
const pooled =
|
|
181
|
+
typeof db.connect === 'function' &&
|
|
182
|
+
typeof db.end === 'function' &&
|
|
183
|
+
db.totalCount !== undefined;
|
|
184
|
+
const client = pooled ? await db.connect() : db;
|
|
185
|
+
try {
|
|
186
|
+
await client.query('BEGIN');
|
|
187
|
+
// ef is an integer checked above; SET takes no bind parameters
|
|
188
|
+
await client.query(`SET LOCAL hnsw.ef_search = ${Math.min(ef, 1000)}`);
|
|
189
|
+
const result = await client.query(text, params);
|
|
190
|
+
await client.query('COMMIT');
|
|
191
|
+
return result;
|
|
192
|
+
} catch (err) {
|
|
193
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
194
|
+
throw explain(err, this.table);
|
|
195
|
+
} finally {
|
|
196
|
+
if (pooled) client.release();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Creates the extension, table and index once, sized to `dimensions`. */
|
|
201
|
+
_ensureTable(dimensions) {
|
|
202
|
+
if (!this.createTable) return Promise.resolve();
|
|
203
|
+
if (!this._ready) {
|
|
204
|
+
const size = this.dimensions || dimensions;
|
|
205
|
+
const indexName = `"${this.tableName.split('.').pop()}_embedding_hnsw"`;
|
|
206
|
+
this._ready = (async () => {
|
|
207
|
+
await this._query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
208
|
+
await this._query(
|
|
209
|
+
`CREATE TABLE IF NOT EXISTS ${this.table} (` +
|
|
210
|
+
'id text PRIMARY KEY, ' +
|
|
211
|
+
'content text NOT NULL DEFAULT \'\', ' +
|
|
212
|
+
'metadata jsonb NOT NULL DEFAULT \'{}\'::jsonb, ' +
|
|
213
|
+
`embedding vector(${size}) NOT NULL)`,
|
|
214
|
+
);
|
|
215
|
+
if (this.index === 'hnsw') {
|
|
216
|
+
await this._query(
|
|
217
|
+
`CREATE INDEX IF NOT EXISTS ${indexName} ON ${this.table} USING hnsw (embedding vector_cosine_ops)`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
})().catch((err) => {
|
|
221
|
+
this._ready = null;
|
|
222
|
+
throw err;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return this._ready;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Cache identity for createRagPipeline({ cache }). Other processes can
|
|
230
|
+
* write to the table without this one knowing, so there is none unless
|
|
231
|
+
* you set `cacheKey`: then cached retrieval results are reused until you
|
|
232
|
+
* change the key or their time to live ends.
|
|
233
|
+
* @param {'retriever'} role
|
|
234
|
+
* @returns {string|null}
|
|
235
|
+
*/
|
|
236
|
+
getCacheKey(role) {
|
|
237
|
+
if (role !== 'retriever' || !this.cacheKey) return null;
|
|
238
|
+
return `pgvector|${this.tableName}|${this.cacheKey}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Inserts or replaces documents, in one transaction: a failed batch
|
|
243
|
+
* changes nothing.
|
|
244
|
+
* @param {Array<{ id: string, vector: number[], content?: string, metadata?: Object }>} docs
|
|
245
|
+
* @returns {Promise<void>}
|
|
246
|
+
*/
|
|
247
|
+
async store(docs) {
|
|
248
|
+
if (!Array.isArray(docs))
|
|
249
|
+
throw new Error('PgVectorRetriever.store() takes an array');
|
|
250
|
+
if (docs.length === 0) return;
|
|
251
|
+
let dimensions = null;
|
|
252
|
+
const rows = docs.map((doc) => {
|
|
253
|
+
const id = doc && doc.id;
|
|
254
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
'PgVectorRetriever.store(): every document needs a string id',
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const literal = vectorLiteral(doc.vector, id);
|
|
260
|
+
if (dimensions === null) dimensions = doc.vector.length;
|
|
261
|
+
else if (doc.vector.length !== dimensions) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`PgVectorRetriever.store(): document "${id}" has ${doc.vector.length} dimensions, the batch has ${dimensions}`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return [
|
|
267
|
+
id,
|
|
268
|
+
doc.content || '',
|
|
269
|
+
JSON.stringify(doc.metadata || {}),
|
|
270
|
+
literal,
|
|
271
|
+
];
|
|
272
|
+
});
|
|
273
|
+
await this._ensureTable(dimensions);
|
|
274
|
+
|
|
275
|
+
const db = this._db();
|
|
276
|
+
const pooled =
|
|
277
|
+
typeof db.connect === 'function' &&
|
|
278
|
+
typeof db.end === 'function' &&
|
|
279
|
+
db.totalCount !== undefined;
|
|
280
|
+
const client = pooled ? await db.connect() : db;
|
|
281
|
+
try {
|
|
282
|
+
await client.query('BEGIN');
|
|
283
|
+
for (let i = 0; i < rows.length; i += MAX_BATCH) {
|
|
284
|
+
const batch = rows.slice(i, i + MAX_BATCH);
|
|
285
|
+
const values = batch
|
|
286
|
+
.map(
|
|
287
|
+
(_, j) =>
|
|
288
|
+
`($${j * 4 + 1}, $${j * 4 + 2}, $${j * 4 + 3}::jsonb, $${j * 4 + 4}::vector)`,
|
|
289
|
+
)
|
|
290
|
+
.join(', ');
|
|
291
|
+
await client.query(
|
|
292
|
+
`INSERT INTO ${this.table} (id, content, metadata, embedding) VALUES ${values} ` +
|
|
293
|
+
'ON CONFLICT (id) DO UPDATE SET content = EXCLUDED.content, ' +
|
|
294
|
+
'metadata = EXCLUDED.metadata, embedding = EXCLUDED.embedding',
|
|
295
|
+
batch.flat(),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
await client.query('COMMIT');
|
|
299
|
+
} catch (err) {
|
|
300
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
301
|
+
throw explain(err, this.table);
|
|
302
|
+
} finally {
|
|
303
|
+
if (pooled) client.release();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The documents most similar to a query vector.
|
|
309
|
+
* @param {number[]|{ queryVector: number[], topK?: number, signal?: AbortSignal }} request
|
|
310
|
+
* @param {number} [k=10] - Results when `request` is a vector
|
|
311
|
+
* @returns {Promise<Array<{ id: string, score: number, content: string, metadata: Object }>>}
|
|
312
|
+
*/
|
|
313
|
+
async retrieve(request, k = 10) {
|
|
314
|
+
const {
|
|
315
|
+
queryVector,
|
|
316
|
+
topK = k,
|
|
317
|
+
signal,
|
|
318
|
+
} = Array.isArray(request) ? { queryVector: request } : request || {};
|
|
319
|
+
if (!Number.isInteger(topK) || topK < 1) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
'PgVectorRetriever.retrieve(): topK must be a positive integer',
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
const literal = vectorLiteral(queryVector, 'query');
|
|
325
|
+
const sql =
|
|
326
|
+
`SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score FROM ${this.table} ` +
|
|
327
|
+
'ORDER BY embedding <=> $1::vector LIMIT $2';
|
|
328
|
+
// An HNSW scan returns at most ef_search rows, so a topK above it
|
|
329
|
+
// would come back short.
|
|
330
|
+
const ef = Math.max(this.efSearch || 0, topK);
|
|
331
|
+
const { rows } =
|
|
332
|
+
ef > PG_DEFAULT_EF_SEARCH || this.efSearch
|
|
333
|
+
? await this._queryWithEfSearch(sql, [literal, topK], ef, signal)
|
|
334
|
+
: await this._query(sql, [literal, topK], signal);
|
|
335
|
+
throwIfAborted(signal);
|
|
336
|
+
return rows.map((r) => ({
|
|
337
|
+
id: r.id,
|
|
338
|
+
score: Number(r.score),
|
|
339
|
+
content: r.content,
|
|
340
|
+
metadata: r.metadata || {},
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Deletes the chunks of one source (`metadata.source`), for re-ingesting.
|
|
346
|
+
* @param {string} source
|
|
347
|
+
* @returns {Promise<number>} Rows deleted
|
|
348
|
+
*/
|
|
349
|
+
async removeBySource(source) {
|
|
350
|
+
const { rowCount } = await this._query(
|
|
351
|
+
`DELETE FROM ${this.table} WHERE metadata->>'source' = $1`,
|
|
352
|
+
[String(source)],
|
|
353
|
+
).catch((err) => {
|
|
354
|
+
if (/does not exist/.test(err.message)) return { rowCount: 0 };
|
|
355
|
+
throw err;
|
|
356
|
+
});
|
|
357
|
+
return rowCount || 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Deletes documents by id.
|
|
362
|
+
* @param {string[]} ids
|
|
363
|
+
* @returns {Promise<number>} Rows deleted
|
|
364
|
+
*/
|
|
365
|
+
async delete(ids) {
|
|
366
|
+
const { rowCount } = await this._query(
|
|
367
|
+
`DELETE FROM ${this.table} WHERE id = ANY($1::text[])`,
|
|
368
|
+
[ids.map(String)],
|
|
369
|
+
);
|
|
370
|
+
return rowCount || 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Deletes every document; the table stays. */
|
|
374
|
+
async clear() {
|
|
375
|
+
await this._query(`DELETE FROM ${this.table}`).catch((err) => {
|
|
376
|
+
if (!/does not exist/.test(err.message)) throw err;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** @returns {Promise<number>} */
|
|
381
|
+
async size() {
|
|
382
|
+
try {
|
|
383
|
+
const { rows } = await this._query(
|
|
384
|
+
`SELECT count(*)::int AS n FROM ${this.table}`,
|
|
385
|
+
);
|
|
386
|
+
return rows[0].n;
|
|
387
|
+
} catch (err) {
|
|
388
|
+
if (/does not exist/.test(err.message)) return 0;
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Every document, without vectors, in id order.
|
|
395
|
+
* @param {{ limit?: number }} [options]
|
|
396
|
+
* @returns {Promise<Array<{ id: string, content: string, metadata: Object }>>}
|
|
397
|
+
*/
|
|
398
|
+
async list({ limit } = {}) {
|
|
399
|
+
const { rows } = await this._query(
|
|
400
|
+
`SELECT id, content, metadata FROM ${this.table} ORDER BY id${limit ? ' LIMIT $1' : ''}`,
|
|
401
|
+
limit ? [limit] : [],
|
|
402
|
+
);
|
|
403
|
+
return rows.map((r) => ({
|
|
404
|
+
id: r.id,
|
|
405
|
+
content: r.content,
|
|
406
|
+
metadata: r.metadata || {},
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Closes the Pool this retriever created; a pool you passed in stays open. */
|
|
411
|
+
async close() {
|
|
412
|
+
if (this._ownsPool && this._pool) {
|
|
413
|
+
const pool = this._pool;
|
|
414
|
+
this._pool = null;
|
|
415
|
+
await pool.end();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
module.exports = { PgVectorRetriever, quoteTable };
|