@dudousxd/nestjs-agent-rag 0.10.3 → 0.12.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/README.md +94 -14
- package/dist/index.cjs +666 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +336 -23
- package/dist/index.d.ts +336 -23
- package/dist/index.js +657 -73
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/index.js
CHANGED
|
@@ -356,6 +356,181 @@ function cosineSimilarity(a, b) {
|
|
|
356
356
|
}
|
|
357
357
|
__name(cosineSimilarity, "cosineSimilarity");
|
|
358
358
|
|
|
359
|
+
// src/lexical-query.ts
|
|
360
|
+
var words = /* @__PURE__ */ __name((s) => new Set(s.split(/\s+/).filter(Boolean)), "words");
|
|
361
|
+
var DEFAULT_STOP_WORDS = {
|
|
362
|
+
english: words(`
|
|
363
|
+
me my myself we our ours ourselves you your yours yourself yourselves he him his himself she
|
|
364
|
+
her hers herself it its itself they them their theirs themselves what which who whom this that
|
|
365
|
+
these those am is are was were be been being have has had having do does did doing an the and
|
|
366
|
+
but if or because as until while of at by for with about against between into through during
|
|
367
|
+
before after above below to from up down in out on off over under again further then once here
|
|
368
|
+
there when where why how all any both each few more most other some such no nor not only own
|
|
369
|
+
same so than too very can will just don should now`),
|
|
370
|
+
portuguese: words(`
|
|
371
|
+
de que do da em um para com n\xE3o uma os no se na por mais as dos como mas ao ele das seu sua ou
|
|
372
|
+
quando muito nos j\xE1 eu tamb\xE9m s\xF3 pelo pela at\xE9 isso ela entre depois sem mesmo aos seus quem
|
|
373
|
+
nas me esse eles voc\xEA essa num nem suas meu \xE0s minha numa pelos elas qual n\xF3s lhe deles essas
|
|
374
|
+
esses pelas este dele tu te voc\xEAs vos lhes meus minhas teu tua teus tuas nosso nossa nossos
|
|
375
|
+
nossas dela delas esta estes estas aquele aquela aqueles aquelas isto aquilo estou est\xE1 estamos
|
|
376
|
+
est\xE3o estive esteve estivemos estiveram estava est\xE1vamos estavam estivera estiv\xE9ramos esteja
|
|
377
|
+
estejamos estejam estivesse estiv\xE9ssemos estivessem estiver estivermos estiverem hei h\xE1 havemos
|
|
378
|
+
h\xE3o houve houvemos houveram houvera houv\xE9ramos haja hajamos hajam houvesse houv\xE9ssemos
|
|
379
|
+
houvessem houver houvermos houverem houverei houver\xE1 houveremos houver\xE3o houveria houver\xEDamos
|
|
380
|
+
houveriam sou somos s\xE3o era \xE9ramos eram fui foi fomos foram fora f\xF4ramos seja sejamos sejam
|
|
381
|
+
fosse f\xF4ssemos fossem for formos forem serei ser\xE1 seremos ser\xE3o seria ser\xEDamos seriam tenho tem
|
|
382
|
+
temos t\xE9m tinha t\xEDnhamos tinham tive teve tivemos tiveram tivera tiv\xE9ramos tenha tenhamos
|
|
383
|
+
tenham tivesse tiv\xE9ssemos tivessem tiver tivermos tiverem terei ter\xE1 teremos ter\xE3o teria
|
|
384
|
+
ter\xEDamos teriam`),
|
|
385
|
+
spanish: words(`
|
|
386
|
+
de la que el en los del se las por un para con no una su al lo como m\xE1s pero sus le ya este s\xED
|
|
387
|
+
porque esta entre cuando muy sin sobre tambi\xE9n me hasta hay donde quien desde todo nos durante
|
|
388
|
+
todos uno les ni contra otros ese eso ante ellos esto m\xED antes algunos qu\xE9 unos yo otro otras
|
|
389
|
+
otra \xE9l tanto esa estos mucho quienes nada muchos cual poco ella estar estas algunas algo
|
|
390
|
+
nosotros mi mis t\xFA te ti tu tus ellas nosotras vosotros vosotras os m\xEDo m\xEDa m\xEDos m\xEDas tuyo
|
|
391
|
+
tuya tuyos tuyas suyo suya suyos suyas nuestro nuestra nuestros nuestras vuestro vuestra
|
|
392
|
+
vuestros vuestras esos esas estoy est\xE1s est\xE1 estamos est\xE1is est\xE1n est\xE9 est\xE9s estemos est\xE9is
|
|
393
|
+
est\xE9n estar\xE9 estar\xE1s estar\xE1 estaremos estar\xE9is estar\xE1n estar\xEDa estar\xEDas estar\xEDamos estar\xEDais
|
|
394
|
+
estar\xEDan estaba estabas est\xE1bamos estabais estaban estuve estuviste estuvo estuvimos
|
|
395
|
+
estuvisteis estuvieron estuviera estuvieras estuvi\xE9ramos estuvierais estuvieran estuviese
|
|
396
|
+
estuvieses estuvi\xE9semos estuvieseis estuviesen estando estad he has ha hemos hab\xE9is han haya
|
|
397
|
+
hayas hayamos hay\xE1is hayan habr\xE9 habr\xE1s habr\xE1 habremos habr\xE9is habr\xE1n habr\xEDa habr\xEDas habr\xEDamos
|
|
398
|
+
habr\xEDais habr\xEDan hab\xEDa hab\xEDas hab\xEDamos hab\xEDais hab\xEDan hube hubiste hubo hubimos hubisteis
|
|
399
|
+
hubieron hubiera hubieras hubi\xE9ramos hubierais hubieran hubiese hubieses hubi\xE9semos hubieseis
|
|
400
|
+
hubiesen habiendo habido habida habidos habidas soy eres es somos sois son sea seas seamos
|
|
401
|
+
se\xE1is sean ser\xE9 ser\xE1s ser\xE1 seremos ser\xE9is ser\xE1n ser\xEDa ser\xEDas ser\xEDamos ser\xEDais ser\xEDan era eras
|
|
402
|
+
\xE9ramos erais eran fui fuiste fue fuimos fuisteis fueron fuera fueras fu\xE9ramos fuerais fueran
|
|
403
|
+
fuese fueses fu\xE9semos fueseis fuesen tengo tienes tiene tenemos ten\xE9is tienen tenga tengas
|
|
404
|
+
tengamos teng\xE1is tengan tendr\xE9 tendr\xE1s tendr\xE1 tendremos tendr\xE9is tendr\xE1n tendr\xEDa tendr\xEDas
|
|
405
|
+
tendr\xEDamos tendr\xEDais tendr\xEDan ten\xEDa ten\xEDas ten\xEDamos ten\xEDais ten\xEDan tuve tuviste tuvo tuvimos
|
|
406
|
+
tuvisteis tuvieron tuviera tuvieras tuvi\xE9ramos tuvierais tuvieran tuviese tuvieses tuvi\xE9semos
|
|
407
|
+
tuvieseis tuviesen teniendo tenido tenida tenidos tenidas tened`)
|
|
408
|
+
};
|
|
409
|
+
var TOKEN = /[\p{L}\p{N}]{2,}/gu;
|
|
410
|
+
function keywordTerms(query, max = 24, stopWords = DEFAULT_STOP_WORDS) {
|
|
411
|
+
const tokens = [
|
|
412
|
+
...new Set(query.toLowerCase().normalize("NFC").match(TOKEN) ?? [])
|
|
413
|
+
];
|
|
414
|
+
const counts = Object.entries(stopWords).map(([, stop2]) => [
|
|
415
|
+
stop2,
|
|
416
|
+
tokens.filter((t) => stop2.has(t)).length
|
|
417
|
+
]);
|
|
418
|
+
const best = Math.max(0, ...counts.map(([, n]) => n));
|
|
419
|
+
if (best === 0) return tokens.slice(0, max);
|
|
420
|
+
const stop = counts.filter(([, n]) => n === best).map(([s]) => s);
|
|
421
|
+
const terms = tokens.filter((t) => !stop.some((s) => s.has(t)));
|
|
422
|
+
return (terms.length > 0 ? terms : tokens).slice(0, max);
|
|
423
|
+
}
|
|
424
|
+
__name(keywordTerms, "keywordTerms");
|
|
425
|
+
function hasSearchSyntax(query) {
|
|
426
|
+
return /"[^"]+"/.test(query) || /(^|\s)-[\p{L}\p{N}]/u.test(query);
|
|
427
|
+
}
|
|
428
|
+
__name(hasSearchSyntax, "hasSearchSyntax");
|
|
429
|
+
function anyTermTsquery(terms) {
|
|
430
|
+
return terms.map((t) => `'${t.replace(/'/g, "")}'`).join(" | ");
|
|
431
|
+
}
|
|
432
|
+
__name(anyTermTsquery, "anyTermTsquery");
|
|
433
|
+
|
|
434
|
+
// src/pg-client.ts
|
|
435
|
+
var ADAPTED = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-rag/pg-client");
|
|
436
|
+
function toPgClient(source) {
|
|
437
|
+
if (source[ADAPTED]) {
|
|
438
|
+
return source;
|
|
439
|
+
}
|
|
440
|
+
if (isDrizzle(source)) {
|
|
441
|
+
return toPgClient(source.$client);
|
|
442
|
+
}
|
|
443
|
+
if (typeof source.unsafe === "function") {
|
|
444
|
+
return fromPostgresJs(source);
|
|
445
|
+
}
|
|
446
|
+
return fromQueryable(source);
|
|
447
|
+
}
|
|
448
|
+
__name(toPgClient, "toPgClient");
|
|
449
|
+
function isDrizzle(source) {
|
|
450
|
+
const client = source.$client;
|
|
451
|
+
return client !== null && client !== void 0 && typeof client === "object";
|
|
452
|
+
}
|
|
453
|
+
__name(isDrizzle, "isDrizzle");
|
|
454
|
+
function mark(client) {
|
|
455
|
+
Object.defineProperty(client, ADAPTED, {
|
|
456
|
+
value: true
|
|
457
|
+
});
|
|
458
|
+
return client;
|
|
459
|
+
}
|
|
460
|
+
__name(mark, "mark");
|
|
461
|
+
function rowsOf(result) {
|
|
462
|
+
if (Array.isArray(result)) {
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
const rows = result?.rows;
|
|
466
|
+
return Array.isArray(rows) ? rows : [];
|
|
467
|
+
}
|
|
468
|
+
__name(rowsOf, "rowsOf");
|
|
469
|
+
function fromPostgresJs(sql) {
|
|
470
|
+
const client = {
|
|
471
|
+
query: /* @__PURE__ */ __name(async (text, params = []) => (
|
|
472
|
+
// postgres.js resolves a RowList (an Array subclass); copy it to a plain array.
|
|
473
|
+
[
|
|
474
|
+
...rowsOf(await sql.unsafe(text, params))
|
|
475
|
+
]
|
|
476
|
+
), "query")
|
|
477
|
+
};
|
|
478
|
+
if (typeof sql.begin === "function") {
|
|
479
|
+
const begin = sql.begin.bind(sql);
|
|
480
|
+
client.transaction = (work) => Promise.resolve(begin((tx) => work(fromPostgresJs(tx))));
|
|
481
|
+
}
|
|
482
|
+
return mark(client);
|
|
483
|
+
}
|
|
484
|
+
__name(fromPostgresJs, "fromPostgresJs");
|
|
485
|
+
function fromQueryable(source) {
|
|
486
|
+
const client = {
|
|
487
|
+
query: /* @__PURE__ */ __name(async (text, params) => rowsOf(await source.query(text, params)), "query")
|
|
488
|
+
};
|
|
489
|
+
if (typeof source.transaction === "function") {
|
|
490
|
+
const transaction = source.transaction.bind(source);
|
|
491
|
+
client.transaction = (work) => transaction((tx) => work(toPgClient(tx)));
|
|
492
|
+
} else if (isNodePgPool(source)) {
|
|
493
|
+
client.transaction = async (work) => {
|
|
494
|
+
const connection = await source.connect();
|
|
495
|
+
let broken = false;
|
|
496
|
+
try {
|
|
497
|
+
return await runInTransaction(connection, work, () => {
|
|
498
|
+
broken = true;
|
|
499
|
+
});
|
|
500
|
+
} finally {
|
|
501
|
+
connection.release(broken ? new Error("ROLLBACK failed") : void 0);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
} else if (isNodePgClient(source)) {
|
|
505
|
+
client.transaction = (work) => runInTransaction(source, work);
|
|
506
|
+
}
|
|
507
|
+
return mark(client);
|
|
508
|
+
}
|
|
509
|
+
__name(fromQueryable, "fromQueryable");
|
|
510
|
+
async function runInTransaction(connection, work, onRollbackFailed) {
|
|
511
|
+
const tx = mark({
|
|
512
|
+
query: /* @__PURE__ */ __name(async (text, params) => rowsOf(await connection.query(text, params)), "query")
|
|
513
|
+
});
|
|
514
|
+
await connection.query("BEGIN");
|
|
515
|
+
try {
|
|
516
|
+
const result = await work(tx);
|
|
517
|
+
await connection.query("COMMIT");
|
|
518
|
+
return result;
|
|
519
|
+
} catch (error) {
|
|
520
|
+
await connection.query("ROLLBACK").catch(() => onRollbackFailed?.());
|
|
521
|
+
throw error;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
__name(runInTransaction, "runInTransaction");
|
|
525
|
+
function isNodePgPool(source) {
|
|
526
|
+
return typeof source.connect === "function" && typeof source.totalCount === "number";
|
|
527
|
+
}
|
|
528
|
+
__name(isNodePgPool, "isNodePgPool");
|
|
529
|
+
function isNodePgClient(source) {
|
|
530
|
+
return typeof source.release === "function" || typeof source.processID === "number" || source.connectionParameters !== void 0;
|
|
531
|
+
}
|
|
532
|
+
__name(isNodePgClient, "isNodePgClient");
|
|
533
|
+
|
|
359
534
|
// src/pg-vector-store.ts
|
|
360
535
|
var NUL_BYTE = String.fromCharCode(0);
|
|
361
536
|
function stripNulBytes(value) {
|
|
@@ -384,11 +559,37 @@ var PgVectorStore = class {
|
|
|
384
559
|
}
|
|
385
560
|
client;
|
|
386
561
|
table;
|
|
562
|
+
/** The single width (fixed-dimension table), or `undefined` for a mixed-dimension table. */
|
|
387
563
|
dimensions;
|
|
564
|
+
/** The widths with a partial HNSW index, for a mixed-dimension table. */
|
|
565
|
+
indexedDimensions;
|
|
566
|
+
nullableEmbeddings;
|
|
567
|
+
iterativeScan;
|
|
568
|
+
efSearch;
|
|
569
|
+
upsertBatchSize;
|
|
570
|
+
iterativeScanSupport;
|
|
388
571
|
constructor(client, options = {}) {
|
|
389
|
-
this.client = client;
|
|
572
|
+
this.client = toPgClient(client);
|
|
390
573
|
this.table = options.table ?? "agent_rag_chunks";
|
|
391
|
-
|
|
574
|
+
const dimensions = options.dimensions ?? 1536;
|
|
575
|
+
if (typeof dimensions === "number") {
|
|
576
|
+
this.dimensions = dimensions;
|
|
577
|
+
this.indexedDimensions = [];
|
|
578
|
+
} else {
|
|
579
|
+
for (const width of dimensions) {
|
|
580
|
+
if (!Number.isInteger(width) || width <= 0) {
|
|
581
|
+
throw new Error(`PgVectorStore: invalid embedding width ${String(width)}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
this.dimensions = void 0;
|
|
585
|
+
this.indexedDimensions = [
|
|
586
|
+
...new Set(dimensions)
|
|
587
|
+
];
|
|
588
|
+
}
|
|
589
|
+
this.nullableEmbeddings = options.nullableEmbeddings ?? false;
|
|
590
|
+
this.iterativeScan = options.iterativeScan;
|
|
591
|
+
this.efSearch = options.efSearch;
|
|
592
|
+
this.upsertBatchSize = Math.max(1, Math.floor(options.upsertBatchSize ?? 100));
|
|
392
593
|
}
|
|
393
594
|
/** Telemetry self-description — the table is the namespace a retrieval was served from. */
|
|
394
595
|
describeRetrieval() {
|
|
@@ -397,38 +598,77 @@ var PgVectorStore = class {
|
|
|
397
598
|
collection: this.table
|
|
398
599
|
};
|
|
399
600
|
}
|
|
400
|
-
/**
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
601
|
+
/**
|
|
602
|
+
* The idempotent DDL {@link PgVectorStore.ensureSchema} runs, in order — for a migration instead
|
|
603
|
+
* of a boot-time side effect. On a populated table, run the index statements `CONCURRENTLY` there.
|
|
604
|
+
*/
|
|
605
|
+
schemaStatements() {
|
|
606
|
+
const mixed = this.dimensions === void 0;
|
|
607
|
+
const column = mixed ? "vector" : `vector(${this.dimensions})`;
|
|
608
|
+
const statements = [
|
|
609
|
+
"CREATE EXTENSION IF NOT EXISTS vector",
|
|
610
|
+
`CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
404
611
|
id TEXT PRIMARY KEY,
|
|
405
612
|
text TEXT NOT NULL,
|
|
406
613
|
source TEXT,
|
|
407
614
|
metadata JSONB,
|
|
408
|
-
embedding
|
|
409
|
-
)`
|
|
410
|
-
|
|
615
|
+
embedding ${column}${this.nullableEmbeddings ? "" : " NOT NULL"}
|
|
616
|
+
)`
|
|
617
|
+
];
|
|
618
|
+
if (mixed) {
|
|
619
|
+
for (const width of this.indexedDimensions) {
|
|
620
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${this.table}_embedding_${width}_idx
|
|
621
|
+
ON ${this.table} USING hnsw ((embedding::vector(${width})) vector_cosine_ops)
|
|
622
|
+
WHERE vector_dims(embedding) = ${width}`);
|
|
623
|
+
}
|
|
624
|
+
} else {
|
|
625
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx
|
|
411
626
|
ON ${this.table} USING hnsw (embedding vector_cosine_ops)`);
|
|
627
|
+
}
|
|
628
|
+
return statements;
|
|
629
|
+
}
|
|
630
|
+
/** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index(es). */
|
|
631
|
+
async ensureSchema() {
|
|
632
|
+
for (const statement of this.schemaStatements()) {
|
|
633
|
+
await this.client.query(statement);
|
|
634
|
+
}
|
|
412
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* Multi-row `INSERT … ON CONFLICT (id) DO UPDATE`, {@link PgVectorStoreOptions.upsertBatchSize}
|
|
638
|
+
* rows per statement. An empty `embedding` is written as `NULL` (see
|
|
639
|
+
* {@link PgVectorStoreOptions.nullableEmbeddings}).
|
|
640
|
+
*/
|
|
413
641
|
async upsert(records) {
|
|
642
|
+
const byId = /* @__PURE__ */ new Map();
|
|
414
643
|
for (const record of records) {
|
|
415
644
|
const id = stripNulBytes(record.id);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
645
|
+
byId.delete(id);
|
|
646
|
+
byId.set(id, {
|
|
647
|
+
...record,
|
|
648
|
+
id
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
const unique = [
|
|
652
|
+
...byId.values()
|
|
653
|
+
];
|
|
654
|
+
for (let start = 0; start < unique.length; start += this.upsertBatchSize) {
|
|
655
|
+
const batch = unique.slice(start, start + this.upsertBatchSize);
|
|
656
|
+
const params = [];
|
|
657
|
+
const rows = batch.map((record) => {
|
|
658
|
+
const text = stripNulBytes(record.text);
|
|
659
|
+
const source = record.source !== void 0 ? stripNulBytes(record.source) : void 0;
|
|
660
|
+
const metadata = record.metadata !== void 0 ? stripNulBytes(record.metadata) : void 0;
|
|
661
|
+
const base = params.length;
|
|
662
|
+
params.push(record.id, text, source ?? null, metadata !== void 0 ? JSON.stringify(metadata) : null, record.embedding.length > 0 ? toVectorLiteral(record.embedding) : null);
|
|
663
|
+
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}::jsonb, $${base + 5}::vector)`;
|
|
664
|
+
});
|
|
419
665
|
await this.client.query(`INSERT INTO ${this.table} (id, text, source, metadata, embedding)
|
|
420
|
-
VALUES (
|
|
666
|
+
VALUES ${rows.join(", ")}
|
|
421
667
|
ON CONFLICT (id) DO UPDATE SET
|
|
422
668
|
text = EXCLUDED.text,
|
|
423
669
|
source = EXCLUDED.source,
|
|
424
670
|
metadata = EXCLUDED.metadata,
|
|
425
|
-
embedding = EXCLUDED.embedding`,
|
|
426
|
-
id,
|
|
427
|
-
text,
|
|
428
|
-
source ?? null,
|
|
429
|
-
metadata !== void 0 ? JSON.stringify(metadata) : null,
|
|
430
|
-
toVectorLiteral(record.embedding)
|
|
431
|
-
]);
|
|
671
|
+
embedding = EXCLUDED.embedding`, params);
|
|
432
672
|
}
|
|
433
673
|
}
|
|
434
674
|
async remove(documentId) {
|
|
@@ -464,11 +704,12 @@ var PgVectorStore = class {
|
|
|
464
704
|
return rows.length;
|
|
465
705
|
}
|
|
466
706
|
async listDocuments(filter) {
|
|
467
|
-
const
|
|
707
|
+
const params = [];
|
|
708
|
+
const where = this.where(filter, params);
|
|
468
709
|
const rows = await this.client.query(`SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata
|
|
469
710
|
FROM ${this.table}
|
|
470
|
-
${where
|
|
471
|
-
ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,
|
|
711
|
+
${where}
|
|
712
|
+
ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, params);
|
|
472
713
|
return rows.map((row) => ({
|
|
473
714
|
id: row.doc_id,
|
|
474
715
|
...row.metadata !== null ? {
|
|
@@ -519,11 +760,12 @@ var PgVectorStore = class {
|
|
|
519
760
|
if (filterMatchesNothing(filter)) {
|
|
520
761
|
return [];
|
|
521
762
|
}
|
|
522
|
-
const
|
|
763
|
+
const params = [];
|
|
764
|
+
const where = this.where(filter, params);
|
|
523
765
|
const rows = await this.client.query(`SELECT DISTINCT ${DOCUMENT_ID_FROM_CHUNK} AS doc_id
|
|
524
766
|
FROM ${this.table}
|
|
525
|
-
${where
|
|
526
|
-
ORDER BY doc_id`,
|
|
767
|
+
${where}
|
|
768
|
+
ORDER BY doc_id`, params);
|
|
527
769
|
return rows.map((row) => row.doc_id);
|
|
528
770
|
}
|
|
529
771
|
/** N documents in one statement — the set-based form of {@link PgVectorStore.remove}. */
|
|
@@ -547,81 +789,250 @@ var PgVectorStore = class {
|
|
|
547
789
|
if (filterMatchesNothing(filter)) {
|
|
548
790
|
return 0;
|
|
549
791
|
}
|
|
550
|
-
const
|
|
551
|
-
const
|
|
792
|
+
const params = [];
|
|
793
|
+
const where = this.where(filter, params);
|
|
794
|
+
const rows = await this.client.query(`DELETE FROM ${this.table} ${where} RETURNING id`, params);
|
|
552
795
|
return rows.length;
|
|
553
796
|
}
|
|
554
797
|
async countChunks(filter) {
|
|
555
798
|
if (filterMatchesNothing(filter)) {
|
|
556
799
|
return 0;
|
|
557
800
|
}
|
|
558
|
-
const
|
|
559
|
-
const
|
|
801
|
+
const params = [];
|
|
802
|
+
const where = this.where(filter, params);
|
|
803
|
+
const rows = await this.client.query(`SELECT count(*)::int AS count FROM ${this.table} ${where}`, params);
|
|
560
804
|
return Number(rows[0]?.count ?? 0);
|
|
561
805
|
}
|
|
806
|
+
/**
|
|
807
|
+
* Cosine similarity, nearest first. On a mixed-dimension table only vectors as wide as the query
|
|
808
|
+
* are compared (through that width's partial index when it has one); chunks without an embedding
|
|
809
|
+
* are never returned.
|
|
810
|
+
*/
|
|
562
811
|
async search(embedding, options) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
812
|
+
if (embedding.length === 0) {
|
|
813
|
+
return [];
|
|
814
|
+
}
|
|
815
|
+
const params = [
|
|
816
|
+
toVectorLiteral(embedding),
|
|
817
|
+
options.topK
|
|
818
|
+
];
|
|
819
|
+
const conditions = this.whereConditions(options.filter, params);
|
|
820
|
+
let vectorType = "vector";
|
|
821
|
+
if (this.dimensions === void 0) {
|
|
822
|
+
const width = embedding.length;
|
|
823
|
+
if (this.indexedDimensions.includes(width)) {
|
|
824
|
+
vectorType = `vector(${width})`;
|
|
825
|
+
}
|
|
826
|
+
conditions.push("embedding IS NOT NULL", `vector_dims(embedding) = ${width}`);
|
|
827
|
+
} else if (this.nullableEmbeddings) {
|
|
828
|
+
conditions.push("embedding IS NOT NULL");
|
|
829
|
+
}
|
|
830
|
+
const column = vectorType === "vector" ? "embedding" : `(embedding::${vectorType})`;
|
|
831
|
+
const sql = `SELECT id, text, source, metadata, 1 - (${column} <=> $1::${vectorType}) AS score
|
|
566
832
|
FROM ${this.table}
|
|
567
|
-
${
|
|
568
|
-
ORDER BY
|
|
569
|
-
LIMIT $2
|
|
570
|
-
|
|
833
|
+
${conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
834
|
+
ORDER BY ${column} <=> $1::${vectorType}
|
|
835
|
+
LIMIT $2`;
|
|
836
|
+
const rows = await this.withSearchSettings((client) => client.query(sql, params));
|
|
837
|
+
return rows.map(toPassage);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* The SQL conditions (ANDed) a metadata `filter` compiles to, appending their bindings to `params`
|
|
841
|
+
* (placeholders are numbered after what `params` already holds). Every statement that takes a
|
|
842
|
+
* filter goes through here, so a subclass that maps the filter onto its own columns — a tenant id,
|
|
843
|
+
* an access list — overrides this one method. Return `['false']` to deny.
|
|
844
|
+
*/
|
|
845
|
+
whereConditions(filter, params) {
|
|
846
|
+
return buildWhere(filter, params);
|
|
847
|
+
}
|
|
848
|
+
where(filter, params) {
|
|
849
|
+
const conditions = this.whereConditions(filter, params);
|
|
850
|
+
return conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
851
|
+
}
|
|
852
|
+
/** Run a read with the configured HNSW settings `SET LOCAL` in one transaction, when possible. */
|
|
853
|
+
async withSearchSettings(read) {
|
|
854
|
+
const transaction = this.client.transaction?.bind(this.client);
|
|
855
|
+
if (transaction === void 0) {
|
|
856
|
+
return read(this.client);
|
|
857
|
+
}
|
|
858
|
+
const iterative = this.iterativeScan !== void 0 && await this.supportsIterativeScan();
|
|
859
|
+
if (!iterative && this.efSearch === void 0) {
|
|
860
|
+
return read(this.client);
|
|
861
|
+
}
|
|
862
|
+
return transaction(async (tx) => {
|
|
863
|
+
if (iterative) {
|
|
864
|
+
await tx.query(`SELECT set_config('hnsw.iterative_scan', $1, true)`, [
|
|
865
|
+
this.iterativeScan
|
|
866
|
+
]);
|
|
867
|
+
}
|
|
868
|
+
if (this.efSearch !== void 0) {
|
|
869
|
+
await tx.query(`SELECT set_config('hnsw.ef_search', $1, true)`, [
|
|
870
|
+
String(this.efSearch)
|
|
871
|
+
]);
|
|
872
|
+
}
|
|
873
|
+
return read(tx);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/** `hnsw.iterative_scan` exists from pgvector 0.8.0; setting it on an older one is an error. */
|
|
877
|
+
supportsIterativeScan() {
|
|
878
|
+
this.iterativeScanSupport ??= this.client.query(`SELECT extversion FROM pg_extension WHERE extname = 'vector'`).then((rows) => {
|
|
879
|
+
const [major = 0, minor = 0] = (rows[0]?.extversion ?? "0.0").split(".").map(Number);
|
|
880
|
+
return major > 0 || minor >= 8;
|
|
881
|
+
}).catch(() => false);
|
|
882
|
+
return this.iterativeScanSupport;
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
var PgLexicalVectorStore = class extends PgVectorStore {
|
|
886
|
+
static {
|
|
887
|
+
__name(this, "PgLexicalVectorStore");
|
|
888
|
+
}
|
|
889
|
+
tsConfig;
|
|
890
|
+
tsColumn;
|
|
891
|
+
anyTermFallback;
|
|
892
|
+
stopWords;
|
|
893
|
+
constructor(client, options = {}) {
|
|
894
|
+
super(client, options);
|
|
895
|
+
const config = options.fullText?.config ?? "simple";
|
|
896
|
+
if (!SQL_IDENTIFIER.test(config)) {
|
|
897
|
+
throw new Error(`PgLexicalVectorStore: invalid text search configuration "${config}"`);
|
|
898
|
+
}
|
|
899
|
+
const column = options.fullText?.column;
|
|
900
|
+
if (column !== void 0 && !SQL_IDENTIFIER.test(column)) {
|
|
901
|
+
throw new Error(`PgLexicalVectorStore: invalid tsvector column "${column}"`);
|
|
902
|
+
}
|
|
903
|
+
this.tsConfig = config;
|
|
904
|
+
this.tsColumn = column;
|
|
905
|
+
this.anyTermFallback = options.fullText?.anyTermFallback ?? true;
|
|
906
|
+
const stopWords = options.fullText?.stopWords;
|
|
907
|
+
this.stopWords = stopWords === false ? {} : stopWords ?? DEFAULT_STOP_WORDS;
|
|
908
|
+
}
|
|
909
|
+
schemaStatements() {
|
|
910
|
+
const statements = super.schemaStatements();
|
|
911
|
+
if (this.tsColumn === void 0) {
|
|
912
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${this.table}_text_tsv_idx
|
|
913
|
+
ON ${this.table} USING gin (${this.tsvector()})`);
|
|
914
|
+
}
|
|
915
|
+
return statements;
|
|
916
|
+
}
|
|
917
|
+
async searchText(query, options) {
|
|
918
|
+
if (filterMatchesNothing(options.filter) || query.trim() === "") {
|
|
919
|
+
return [];
|
|
920
|
+
}
|
|
921
|
+
if (!this.anyTermFallback || hasSearchSyntax(query)) {
|
|
922
|
+
const strict = await this.runTextSearch(query, options);
|
|
923
|
+
if (strict.length > 0 || !this.anyTermFallback) {
|
|
924
|
+
return strict;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
const terms = keywordTerms(query, 24, this.stopWords);
|
|
928
|
+
return terms.length === 0 ? [] : this.runRankedTermSearch(terms, options);
|
|
929
|
+
}
|
|
930
|
+
/** `websearch_to_tsquery` on the query as written: every word, quoted phrases, `-exclusions`. */
|
|
931
|
+
async runTextSearch(query, options) {
|
|
932
|
+
const tsquery = `websearch_to_tsquery('${this.tsConfig}', $1)`;
|
|
933
|
+
const params = [
|
|
934
|
+
query,
|
|
935
|
+
options.topK
|
|
936
|
+
];
|
|
937
|
+
const conditions = [
|
|
938
|
+
`${this.tsvector()} @@ ${tsquery}`,
|
|
939
|
+
...this.whereConditions(options.filter, params)
|
|
940
|
+
];
|
|
941
|
+
const rows = await this.client.query(`SELECT id, text, source, metadata, ts_rank_cd(${this.tsvector()}, ${tsquery}) AS score
|
|
942
|
+
FROM ${this.table}
|
|
943
|
+
WHERE ${conditions.join(" AND ")}
|
|
944
|
+
ORDER BY score DESC, id
|
|
945
|
+
LIMIT $2`, params);
|
|
946
|
+
return rows.map(toPassage);
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Rows holding any of `terms`, ranked by the sum of the matched terms' IDF among those rows,
|
|
950
|
+
* `ln(1 + (N - df + 0.5) / (df + 0.5))`, then `ts_rank_cd`. Each (row, term) match is evaluated
|
|
951
|
+
* once, and `ts_rank_cd` only for the rows ranked within `topK` (ties included): a question full
|
|
952
|
+
* of common words can match most of the table.
|
|
953
|
+
*/
|
|
954
|
+
async runRankedTermSearch(terms, options) {
|
|
955
|
+
const any = `to_tsquery('${this.tsConfig}', $1)`;
|
|
956
|
+
const params = [
|
|
957
|
+
anyTermTsquery(terms),
|
|
571
958
|
options.topK,
|
|
572
|
-
|
|
573
|
-
]
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
959
|
+
terms
|
|
960
|
+
];
|
|
961
|
+
const conditions = [
|
|
962
|
+
`${this.tsvector()} @@ ${any}`,
|
|
963
|
+
...this.whereConditions(options.filter, params)
|
|
964
|
+
];
|
|
965
|
+
const rows = await this.client.query(`WITH __terms AS (
|
|
966
|
+
SELECT DISTINCT to_tsquery('${this.tsConfig}', quote_literal(t)) AS q
|
|
967
|
+
FROM unnest($3::text[]) AS t
|
|
968
|
+
), __cand AS MATERIALIZED (
|
|
969
|
+
SELECT id, text, source, metadata, ${this.tsvector()} AS __tsv
|
|
970
|
+
FROM ${this.table}
|
|
971
|
+
WHERE ${conditions.join(" AND ")}
|
|
972
|
+
), __hits AS MATERIALIZED (
|
|
973
|
+
SELECT __cand.id AS __id, __terms.q FROM __cand JOIN __terms ON __cand.__tsv @@ __terms.q
|
|
974
|
+
), __idf AS (
|
|
975
|
+
SELECT q, ln(1 + ((SELECT count(*) FROM __cand) - count(*) + 0.5) / (count(*) + 0.5)) AS w
|
|
976
|
+
FROM __hits GROUP BY q
|
|
977
|
+
), __scored AS (
|
|
978
|
+
SELECT __hits.__id, sum(__idf.w) AS s, rank() OVER (ORDER BY sum(__idf.w) DESC) AS r
|
|
979
|
+
FROM __hits JOIN __idf USING (q) GROUP BY __hits.__id
|
|
980
|
+
)
|
|
981
|
+
SELECT __cand.id, __cand.text, __cand.source, __cand.metadata,
|
|
982
|
+
__scored.s + ts_rank_cd(__cand.__tsv, ${any}, 32) AS score
|
|
983
|
+
FROM __scored JOIN __cand ON __cand.id = __scored.__id
|
|
984
|
+
WHERE __scored.r <= $2
|
|
985
|
+
ORDER BY __scored.s DESC, ts_rank_cd(__cand.__tsv, ${any}, 32) DESC, __cand.id
|
|
986
|
+
LIMIT $2`, params);
|
|
987
|
+
return rows.map(toPassage);
|
|
988
|
+
}
|
|
989
|
+
/** Must be character-for-character the index expression, or the planner won't use the index. */
|
|
990
|
+
tsvector() {
|
|
991
|
+
return this.tsColumn ?? `to_tsvector('${this.tsConfig}'::regconfig, text)`;
|
|
585
992
|
}
|
|
586
993
|
};
|
|
587
|
-
|
|
994
|
+
var SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/;
|
|
995
|
+
function toPassage(row) {
|
|
996
|
+
return {
|
|
997
|
+
id: row.id,
|
|
998
|
+
text: row.text,
|
|
999
|
+
score: Number(row.score),
|
|
1000
|
+
...row.source !== null ? {
|
|
1001
|
+
source: row.source
|
|
1002
|
+
} : {},
|
|
1003
|
+
...row.metadata !== null ? {
|
|
1004
|
+
metadata: row.metadata
|
|
1005
|
+
} : {}
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
__name(toPassage, "toPassage");
|
|
1009
|
+
function buildWhere(filter, params) {
|
|
588
1010
|
if (filter === void 0 || Object.keys(filter).length === 0) {
|
|
589
|
-
return
|
|
590
|
-
sql: "",
|
|
591
|
-
params: []
|
|
592
|
-
};
|
|
1011
|
+
return [];
|
|
593
1012
|
}
|
|
594
1013
|
const stripped = stripNulBytes(filter);
|
|
595
1014
|
const clauses = [];
|
|
596
|
-
const params = [];
|
|
597
1015
|
const scalar = {};
|
|
598
|
-
let index = startIndex;
|
|
599
1016
|
for (const [key, value] of Object.entries(stripped)) {
|
|
600
1017
|
if (Array.isArray(value)) {
|
|
601
1018
|
if (value.length === 0) {
|
|
602
1019
|
clauses.push("false");
|
|
603
1020
|
continue;
|
|
604
1021
|
}
|
|
605
|
-
const keyParam = `$${index}`;
|
|
606
1022
|
params.push(key);
|
|
607
|
-
|
|
608
|
-
const arrParam = `$${index}`;
|
|
1023
|
+
const keyParam = `$${params.length}`;
|
|
609
1024
|
params.push(value.map(String));
|
|
610
|
-
|
|
1025
|
+
const arrParam = `$${params.length}`;
|
|
611
1026
|
clauses.push(`(CASE WHEN jsonb_typeof(metadata->${keyParam}) = 'array' THEN metadata->${keyParam} ELSE jsonb_build_array(metadata->${keyParam}) END) ?| ${arrParam}::text[]`);
|
|
612
1027
|
} else {
|
|
613
1028
|
scalar[key] = value;
|
|
614
1029
|
}
|
|
615
1030
|
}
|
|
616
1031
|
if (Object.keys(scalar).length > 0) {
|
|
617
|
-
clauses.push(`metadata @> $${index}::jsonb`);
|
|
618
1032
|
params.push(JSON.stringify(scalar));
|
|
619
|
-
|
|
1033
|
+
clauses.push(`metadata @> $${params.length}::jsonb`);
|
|
620
1034
|
}
|
|
621
|
-
return
|
|
622
|
-
sql: `WHERE ${clauses.join(" AND ")}`,
|
|
623
|
-
params
|
|
624
|
-
};
|
|
1035
|
+
return clauses;
|
|
625
1036
|
}
|
|
626
1037
|
__name(buildWhere, "buildWhere");
|
|
627
1038
|
var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
|
|
@@ -1431,7 +1842,7 @@ function parseSearchReply(reply, prefix, scoreSource = "vector") {
|
|
|
1431
1842
|
attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);
|
|
1432
1843
|
}
|
|
1433
1844
|
const inlineScore = scoreSource === "lexical" ? toStr(reply[index + 1]) : void 0;
|
|
1434
|
-
passages.push(
|
|
1845
|
+
passages.push(toPassage2(toStr(reply[index]), attrs, prefix, scoreSource, inlineScore));
|
|
1435
1846
|
}
|
|
1436
1847
|
}
|
|
1437
1848
|
return passages;
|
|
@@ -1439,7 +1850,7 @@ function parseSearchReply(reply, prefix, scoreSource = "vector") {
|
|
|
1439
1850
|
if (typeof reply === "object" && reply !== null && "results" in reply) {
|
|
1440
1851
|
const results = reply.results;
|
|
1441
1852
|
if (Array.isArray(results)) {
|
|
1442
|
-
return results.map((result) =>
|
|
1853
|
+
return results.map((result) => toPassage2(readId(result), readAttributes(result), prefix, scoreSource, scoreSource === "lexical" ? readScore(result) : void 0));
|
|
1443
1854
|
}
|
|
1444
1855
|
}
|
|
1445
1856
|
return [];
|
|
@@ -1472,7 +1883,7 @@ function readAttributes(result) {
|
|
|
1472
1883
|
return attrs;
|
|
1473
1884
|
}
|
|
1474
1885
|
__name(readAttributes, "readAttributes");
|
|
1475
|
-
function
|
|
1886
|
+
function toPassage2(rawId, attrs, prefix, scoreSource = "vector", inlineScore) {
|
|
1476
1887
|
const score = scoreSource === "lexical" ? Number(inlineScore ?? "0") : 1 - Number(attrs.vector_score ?? "1");
|
|
1477
1888
|
const source = attrs.source;
|
|
1478
1889
|
return {
|
|
@@ -1485,7 +1896,7 @@ function toPassage(rawId, attrs, prefix, scoreSource = "vector", inlineScore) {
|
|
|
1485
1896
|
...parseMetadata(attrs.metadata_json)
|
|
1486
1897
|
};
|
|
1487
1898
|
}
|
|
1488
|
-
__name(
|
|
1899
|
+
__name(toPassage2, "toPassage");
|
|
1489
1900
|
function parseMetadata(raw) {
|
|
1490
1901
|
if (raw === void 0) {
|
|
1491
1902
|
return {};
|
|
@@ -1878,7 +2289,7 @@ async function ingestDocuments(documents, options) {
|
|
|
1878
2289
|
__name(ingestDocuments, "ingestDocuments");
|
|
1879
2290
|
|
|
1880
2291
|
// src/retrieval-telemetry.ts
|
|
1881
|
-
import { AsyncLocalStorage } from "
|
|
2292
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1882
2293
|
import { channelName, emit, getChannel } from "@dudousxd/nestjs-diagnostics";
|
|
1883
2294
|
var RAG_DIAGNOSTIC_LIB = "rag";
|
|
1884
2295
|
var RAG_RETRIEVAL_EVENT = "retrieval";
|
|
@@ -2030,13 +2441,181 @@ function createRetrievalTool(retriever, options = {}) {
|
|
|
2030
2441
|
};
|
|
2031
2442
|
}
|
|
2032
2443
|
__name(createRetrievalTool, "createRetrievalTool");
|
|
2444
|
+
|
|
2445
|
+
// src/openai-embeddings.ts
|
|
2446
|
+
var HttpModelError = class extends Error {
|
|
2447
|
+
static {
|
|
2448
|
+
__name(this, "HttpModelError");
|
|
2449
|
+
}
|
|
2450
|
+
status;
|
|
2451
|
+
constructor(status, message) {
|
|
2452
|
+
super(message), this.status = status;
|
|
2453
|
+
this.name = "HttpModelError";
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
function openAiEmbeddings(options) {
|
|
2457
|
+
const url = `${(options.baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/embeddings`;
|
|
2458
|
+
const batchSize = Math.max(1, Math.floor(options.batchSize ?? 64));
|
|
2459
|
+
const maxInputChars = options.maxInputChars ?? 24e3;
|
|
2460
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
2461
|
+
const doFetch = options.fetch ?? fetch;
|
|
2462
|
+
return {
|
|
2463
|
+
async embed(texts) {
|
|
2464
|
+
const vectors = [];
|
|
2465
|
+
for (let start = 0; start < texts.length; start += batchSize) {
|
|
2466
|
+
const input = texts.slice(start, start + batchSize).map((text) => (text.length > maxInputChars ? text.slice(0, maxInputChars) : text) || " ");
|
|
2467
|
+
const response = await doFetch(url, {
|
|
2468
|
+
method: "POST",
|
|
2469
|
+
headers: {
|
|
2470
|
+
"content-type": "application/json",
|
|
2471
|
+
...options.apiKey ? {
|
|
2472
|
+
authorization: `Bearer ${options.apiKey}`
|
|
2473
|
+
} : {},
|
|
2474
|
+
...options.headers
|
|
2475
|
+
},
|
|
2476
|
+
body: JSON.stringify({
|
|
2477
|
+
model: options.model,
|
|
2478
|
+
input,
|
|
2479
|
+
...options.dimensions !== void 0 ? {
|
|
2480
|
+
dimensions: options.dimensions
|
|
2481
|
+
} : {}
|
|
2482
|
+
}),
|
|
2483
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
2484
|
+
});
|
|
2485
|
+
const body = await response.text();
|
|
2486
|
+
if (!response.ok) {
|
|
2487
|
+
throw new HttpModelError(response.status, `Embeddings request failed (${response.status}): ${errorMessageOf(body)}`);
|
|
2488
|
+
}
|
|
2489
|
+
const json = parseJson(body);
|
|
2490
|
+
const items = [
|
|
2491
|
+
...json?.data ?? []
|
|
2492
|
+
].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
2493
|
+
if (items.length !== input.length || !items.every((item) => Array.isArray(item.embedding))) {
|
|
2494
|
+
throw new HttpModelError(502, `Embeddings returned ${items.length} vectors for ${input.length} inputs`);
|
|
2495
|
+
}
|
|
2496
|
+
for (const item of items) {
|
|
2497
|
+
vectors.push(item.embedding);
|
|
2498
|
+
}
|
|
2499
|
+
const tokens = json?.usage?.total_tokens ?? json?.usage?.prompt_tokens;
|
|
2500
|
+
if (typeof tokens === "number" && Number.isFinite(tokens)) {
|
|
2501
|
+
options.onUsage?.(tokens);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return vectors;
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
__name(openAiEmbeddings, "openAiEmbeddings");
|
|
2509
|
+
function parseJson(body) {
|
|
2510
|
+
try {
|
|
2511
|
+
return JSON.parse(body);
|
|
2512
|
+
} catch {
|
|
2513
|
+
return null;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
__name(parseJson, "parseJson");
|
|
2517
|
+
function errorMessageOf(body) {
|
|
2518
|
+
const json = parseJson(body);
|
|
2519
|
+
const message = typeof json?.error === "string" ? json.error : json?.error?.message ?? json?.message ?? void 0;
|
|
2520
|
+
return typeof message === "string" ? message : body.slice(0, 300);
|
|
2521
|
+
}
|
|
2522
|
+
__name(errorMessageOf, "errorMessageOf");
|
|
2523
|
+
|
|
2524
|
+
// src/http-reranker.ts
|
|
2525
|
+
var HttpReranker = class {
|
|
2526
|
+
static {
|
|
2527
|
+
__name(this, "HttpReranker");
|
|
2528
|
+
}
|
|
2529
|
+
options;
|
|
2530
|
+
format;
|
|
2531
|
+
doFetch;
|
|
2532
|
+
constructor(options) {
|
|
2533
|
+
this.options = options;
|
|
2534
|
+
this.format = options.format ?? "auto";
|
|
2535
|
+
this.doFetch = options.fetch ?? fetch;
|
|
2536
|
+
}
|
|
2537
|
+
async rerank(query, passages, options = {}) {
|
|
2538
|
+
if (passages.length === 0) {
|
|
2539
|
+
return [];
|
|
2540
|
+
}
|
|
2541
|
+
const topK = options.topK ?? passages.length;
|
|
2542
|
+
const documents = passages.map((passage) => passage.text);
|
|
2543
|
+
const response = await this.doFetch(this.options.url, {
|
|
2544
|
+
method: "POST",
|
|
2545
|
+
headers: {
|
|
2546
|
+
"content-type": "application/json",
|
|
2547
|
+
...this.options.apiKey ? {
|
|
2548
|
+
authorization: `Bearer ${this.options.apiKey}`
|
|
2549
|
+
} : {},
|
|
2550
|
+
...this.options.headers
|
|
2551
|
+
},
|
|
2552
|
+
body: JSON.stringify({
|
|
2553
|
+
...this.options.model !== void 0 ? {
|
|
2554
|
+
model: this.options.model
|
|
2555
|
+
} : {},
|
|
2556
|
+
query,
|
|
2557
|
+
...this.format !== "tei" ? {
|
|
2558
|
+
documents,
|
|
2559
|
+
top_n: topK
|
|
2560
|
+
} : {},
|
|
2561
|
+
...this.format !== "cohere" ? {
|
|
2562
|
+
texts: documents
|
|
2563
|
+
} : {}
|
|
2564
|
+
}),
|
|
2565
|
+
signal: AbortSignal.timeout(this.options.timeoutMs ?? 3e4)
|
|
2566
|
+
});
|
|
2567
|
+
const body = await response.text();
|
|
2568
|
+
if (!response.ok) {
|
|
2569
|
+
throw new HttpModelError(response.status, `Rerank request failed (${response.status}): ${errorMessageOf(body)}`);
|
|
2570
|
+
}
|
|
2571
|
+
const items = itemsOf(body);
|
|
2572
|
+
if (items === void 0) {
|
|
2573
|
+
throw new HttpModelError(502, "Rerank response has no results");
|
|
2574
|
+
}
|
|
2575
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2576
|
+
const reranked = [];
|
|
2577
|
+
for (const item of items) {
|
|
2578
|
+
const index = item.index;
|
|
2579
|
+
const passage = typeof index === "number" ? passages[index] : void 0;
|
|
2580
|
+
if (passage === void 0 || seen.has(index)) {
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
seen.add(index);
|
|
2584
|
+
reranked.push({
|
|
2585
|
+
...passage,
|
|
2586
|
+
score: Number(item.relevance_score ?? item.score ?? 0)
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
reranked.sort((a, b) => b.score - a.score);
|
|
2590
|
+
return reranked.slice(0, topK);
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
function itemsOf(body) {
|
|
2594
|
+
let json;
|
|
2595
|
+
try {
|
|
2596
|
+
json = JSON.parse(body);
|
|
2597
|
+
} catch {
|
|
2598
|
+
return void 0;
|
|
2599
|
+
}
|
|
2600
|
+
if (Array.isArray(json)) {
|
|
2601
|
+
return json;
|
|
2602
|
+
}
|
|
2603
|
+
const wrapped = json;
|
|
2604
|
+
const items = wrapped?.results ?? wrapped?.data;
|
|
2605
|
+
return Array.isArray(items) ? items : void 0;
|
|
2606
|
+
}
|
|
2607
|
+
__name(itemsOf, "itemsOf");
|
|
2033
2608
|
export {
|
|
2609
|
+
DEFAULT_STOP_WORDS,
|
|
2034
2610
|
EmbeddingRetriever,
|
|
2035
2611
|
FilteredRetriever,
|
|
2612
|
+
HttpModelError,
|
|
2613
|
+
HttpReranker,
|
|
2036
2614
|
HybridRetriever,
|
|
2037
2615
|
KeywordRetriever,
|
|
2038
2616
|
LexicalRetriever,
|
|
2039
2617
|
MemoryVectorStore,
|
|
2618
|
+
PgLexicalVectorStore,
|
|
2040
2619
|
PgVectorStore,
|
|
2041
2620
|
RAG_DIAGNOSTIC_LIB,
|
|
2042
2621
|
RAG_RETRIEVAL_CHANNEL,
|
|
@@ -2045,6 +2624,7 @@ export {
|
|
|
2045
2624
|
RedisVectorStore,
|
|
2046
2625
|
RerankingRetriever,
|
|
2047
2626
|
UnsafeRemovalError,
|
|
2627
|
+
anyTermTsquery,
|
|
2048
2628
|
applyMetadataPatch,
|
|
2049
2629
|
chunkDocuments,
|
|
2050
2630
|
chunkIndexOf,
|
|
@@ -2056,9 +2636,13 @@ export {
|
|
|
2056
2636
|
describesRetrieval,
|
|
2057
2637
|
documentIdOf,
|
|
2058
2638
|
emitRagRetrieval,
|
|
2639
|
+
hasSearchSyntax,
|
|
2059
2640
|
ingestChunks,
|
|
2060
2641
|
ingestDocuments,
|
|
2061
2642
|
instrumentRetriever,
|
|
2062
|
-
isLexicalVectorStore
|
|
2643
|
+
isLexicalVectorStore,
|
|
2644
|
+
keywordTerms,
|
|
2645
|
+
openAiEmbeddings,
|
|
2646
|
+
toPgClient
|
|
2063
2647
|
};
|
|
2064
2648
|
//# sourceMappingURL=index.js.map
|