@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.cjs
CHANGED
|
@@ -21,12 +21,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
DEFAULT_STOP_WORDS: () => DEFAULT_STOP_WORDS,
|
|
24
25
|
EmbeddingRetriever: () => EmbeddingRetriever,
|
|
25
26
|
FilteredRetriever: () => FilteredRetriever,
|
|
27
|
+
HttpModelError: () => HttpModelError,
|
|
28
|
+
HttpReranker: () => HttpReranker,
|
|
26
29
|
HybridRetriever: () => HybridRetriever,
|
|
27
30
|
KeywordRetriever: () => KeywordRetriever,
|
|
28
31
|
LexicalRetriever: () => LexicalRetriever,
|
|
29
32
|
MemoryVectorStore: () => MemoryVectorStore,
|
|
33
|
+
PgLexicalVectorStore: () => PgLexicalVectorStore,
|
|
30
34
|
PgVectorStore: () => PgVectorStore,
|
|
31
35
|
RAG_DIAGNOSTIC_LIB: () => RAG_DIAGNOSTIC_LIB,
|
|
32
36
|
RAG_RETRIEVAL_CHANNEL: () => RAG_RETRIEVAL_CHANNEL,
|
|
@@ -35,6 +39,7 @@ __export(index_exports, {
|
|
|
35
39
|
RedisVectorStore: () => RedisVectorStore,
|
|
36
40
|
RerankingRetriever: () => RerankingRetriever,
|
|
37
41
|
UnsafeRemovalError: () => UnsafeRemovalError,
|
|
42
|
+
anyTermTsquery: () => anyTermTsquery,
|
|
38
43
|
applyMetadataPatch: () => applyMetadataPatch,
|
|
39
44
|
chunkDocuments: () => chunkDocuments,
|
|
40
45
|
chunkIndexOf: () => chunkIndexOf,
|
|
@@ -46,10 +51,14 @@ __export(index_exports, {
|
|
|
46
51
|
describesRetrieval: () => describesRetrieval,
|
|
47
52
|
documentIdOf: () => documentIdOf,
|
|
48
53
|
emitRagRetrieval: () => emitRagRetrieval,
|
|
54
|
+
hasSearchSyntax: () => hasSearchSyntax,
|
|
49
55
|
ingestChunks: () => ingestChunks,
|
|
50
56
|
ingestDocuments: () => ingestDocuments,
|
|
51
57
|
instrumentRetriever: () => instrumentRetriever,
|
|
52
|
-
isLexicalVectorStore: () => isLexicalVectorStore
|
|
58
|
+
isLexicalVectorStore: () => isLexicalVectorStore,
|
|
59
|
+
keywordTerms: () => keywordTerms,
|
|
60
|
+
openAiEmbeddings: () => openAiEmbeddings,
|
|
61
|
+
toPgClient: () => toPgClient
|
|
53
62
|
});
|
|
54
63
|
module.exports = __toCommonJS(index_exports);
|
|
55
64
|
|
|
@@ -408,6 +417,181 @@ function cosineSimilarity(a, b) {
|
|
|
408
417
|
}
|
|
409
418
|
__name(cosineSimilarity, "cosineSimilarity");
|
|
410
419
|
|
|
420
|
+
// src/lexical-query.ts
|
|
421
|
+
var words = /* @__PURE__ */ __name((s) => new Set(s.split(/\s+/).filter(Boolean)), "words");
|
|
422
|
+
var DEFAULT_STOP_WORDS = {
|
|
423
|
+
english: words(`
|
|
424
|
+
me my myself we our ours ourselves you your yours yourself yourselves he him his himself she
|
|
425
|
+
her hers herself it its itself they them their theirs themselves what which who whom this that
|
|
426
|
+
these those am is are was were be been being have has had having do does did doing an the and
|
|
427
|
+
but if or because as until while of at by for with about against between into through during
|
|
428
|
+
before after above below to from up down in out on off over under again further then once here
|
|
429
|
+
there when where why how all any both each few more most other some such no nor not only own
|
|
430
|
+
same so than too very can will just don should now`),
|
|
431
|
+
portuguese: words(`
|
|
432
|
+
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
|
|
433
|
+
quando muito nos j\xE1 eu tamb\xE9m s\xF3 pelo pela at\xE9 isso ela entre depois sem mesmo aos seus quem
|
|
434
|
+
nas me esse eles voc\xEA essa num nem suas meu \xE0s minha numa pelos elas qual n\xF3s lhe deles essas
|
|
435
|
+
esses pelas este dele tu te voc\xEAs vos lhes meus minhas teu tua teus tuas nosso nossa nossos
|
|
436
|
+
nossas dela delas esta estes estas aquele aquela aqueles aquelas isto aquilo estou est\xE1 estamos
|
|
437
|
+
est\xE3o estive esteve estivemos estiveram estava est\xE1vamos estavam estivera estiv\xE9ramos esteja
|
|
438
|
+
estejamos estejam estivesse estiv\xE9ssemos estivessem estiver estivermos estiverem hei h\xE1 havemos
|
|
439
|
+
h\xE3o houve houvemos houveram houvera houv\xE9ramos haja hajamos hajam houvesse houv\xE9ssemos
|
|
440
|
+
houvessem houver houvermos houverem houverei houver\xE1 houveremos houver\xE3o houveria houver\xEDamos
|
|
441
|
+
houveriam sou somos s\xE3o era \xE9ramos eram fui foi fomos foram fora f\xF4ramos seja sejamos sejam
|
|
442
|
+
fosse f\xF4ssemos fossem for formos forem serei ser\xE1 seremos ser\xE3o seria ser\xEDamos seriam tenho tem
|
|
443
|
+
temos t\xE9m tinha t\xEDnhamos tinham tive teve tivemos tiveram tivera tiv\xE9ramos tenha tenhamos
|
|
444
|
+
tenham tivesse tiv\xE9ssemos tivessem tiver tivermos tiverem terei ter\xE1 teremos ter\xE3o teria
|
|
445
|
+
ter\xEDamos teriam`),
|
|
446
|
+
spanish: words(`
|
|
447
|
+
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
|
|
448
|
+
porque esta entre cuando muy sin sobre tambi\xE9n me hasta hay donde quien desde todo nos durante
|
|
449
|
+
todos uno les ni contra otros ese eso ante ellos esto m\xED antes algunos qu\xE9 unos yo otro otras
|
|
450
|
+
otra \xE9l tanto esa estos mucho quienes nada muchos cual poco ella estar estas algunas algo
|
|
451
|
+
nosotros mi mis t\xFA te ti tu tus ellas nosotras vosotros vosotras os m\xEDo m\xEDa m\xEDos m\xEDas tuyo
|
|
452
|
+
tuya tuyos tuyas suyo suya suyos suyas nuestro nuestra nuestros nuestras vuestro vuestra
|
|
453
|
+
vuestros vuestras esos esas estoy est\xE1s est\xE1 estamos est\xE1is est\xE1n est\xE9 est\xE9s estemos est\xE9is
|
|
454
|
+
est\xE9n estar\xE9 estar\xE1s estar\xE1 estaremos estar\xE9is estar\xE1n estar\xEDa estar\xEDas estar\xEDamos estar\xEDais
|
|
455
|
+
estar\xEDan estaba estabas est\xE1bamos estabais estaban estuve estuviste estuvo estuvimos
|
|
456
|
+
estuvisteis estuvieron estuviera estuvieras estuvi\xE9ramos estuvierais estuvieran estuviese
|
|
457
|
+
estuvieses estuvi\xE9semos estuvieseis estuviesen estando estad he has ha hemos hab\xE9is han haya
|
|
458
|
+
hayas hayamos hay\xE1is hayan habr\xE9 habr\xE1s habr\xE1 habremos habr\xE9is habr\xE1n habr\xEDa habr\xEDas habr\xEDamos
|
|
459
|
+
habr\xEDais habr\xEDan hab\xEDa hab\xEDas hab\xEDamos hab\xEDais hab\xEDan hube hubiste hubo hubimos hubisteis
|
|
460
|
+
hubieron hubiera hubieras hubi\xE9ramos hubierais hubieran hubiese hubieses hubi\xE9semos hubieseis
|
|
461
|
+
hubiesen habiendo habido habida habidos habidas soy eres es somos sois son sea seas seamos
|
|
462
|
+
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
|
|
463
|
+
\xE9ramos erais eran fui fuiste fue fuimos fuisteis fueron fuera fueras fu\xE9ramos fuerais fueran
|
|
464
|
+
fuese fueses fu\xE9semos fueseis fuesen tengo tienes tiene tenemos ten\xE9is tienen tenga tengas
|
|
465
|
+
tengamos teng\xE1is tengan tendr\xE9 tendr\xE1s tendr\xE1 tendremos tendr\xE9is tendr\xE1n tendr\xEDa tendr\xEDas
|
|
466
|
+
tendr\xEDamos tendr\xEDais tendr\xEDan ten\xEDa ten\xEDas ten\xEDamos ten\xEDais ten\xEDan tuve tuviste tuvo tuvimos
|
|
467
|
+
tuvisteis tuvieron tuviera tuvieras tuvi\xE9ramos tuvierais tuvieran tuviese tuvieses tuvi\xE9semos
|
|
468
|
+
tuvieseis tuviesen teniendo tenido tenida tenidos tenidas tened`)
|
|
469
|
+
};
|
|
470
|
+
var TOKEN = /[\p{L}\p{N}]{2,}/gu;
|
|
471
|
+
function keywordTerms(query, max = 24, stopWords = DEFAULT_STOP_WORDS) {
|
|
472
|
+
const tokens = [
|
|
473
|
+
...new Set(query.toLowerCase().normalize("NFC").match(TOKEN) ?? [])
|
|
474
|
+
];
|
|
475
|
+
const counts = Object.entries(stopWords).map(([, stop2]) => [
|
|
476
|
+
stop2,
|
|
477
|
+
tokens.filter((t) => stop2.has(t)).length
|
|
478
|
+
]);
|
|
479
|
+
const best = Math.max(0, ...counts.map(([, n]) => n));
|
|
480
|
+
if (best === 0) return tokens.slice(0, max);
|
|
481
|
+
const stop = counts.filter(([, n]) => n === best).map(([s]) => s);
|
|
482
|
+
const terms = tokens.filter((t) => !stop.some((s) => s.has(t)));
|
|
483
|
+
return (terms.length > 0 ? terms : tokens).slice(0, max);
|
|
484
|
+
}
|
|
485
|
+
__name(keywordTerms, "keywordTerms");
|
|
486
|
+
function hasSearchSyntax(query) {
|
|
487
|
+
return /"[^"]+"/.test(query) || /(^|\s)-[\p{L}\p{N}]/u.test(query);
|
|
488
|
+
}
|
|
489
|
+
__name(hasSearchSyntax, "hasSearchSyntax");
|
|
490
|
+
function anyTermTsquery(terms) {
|
|
491
|
+
return terms.map((t) => `'${t.replace(/'/g, "")}'`).join(" | ");
|
|
492
|
+
}
|
|
493
|
+
__name(anyTermTsquery, "anyTermTsquery");
|
|
494
|
+
|
|
495
|
+
// src/pg-client.ts
|
|
496
|
+
var ADAPTED = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-rag/pg-client");
|
|
497
|
+
function toPgClient(source) {
|
|
498
|
+
if (source[ADAPTED]) {
|
|
499
|
+
return source;
|
|
500
|
+
}
|
|
501
|
+
if (isDrizzle(source)) {
|
|
502
|
+
return toPgClient(source.$client);
|
|
503
|
+
}
|
|
504
|
+
if (typeof source.unsafe === "function") {
|
|
505
|
+
return fromPostgresJs(source);
|
|
506
|
+
}
|
|
507
|
+
return fromQueryable(source);
|
|
508
|
+
}
|
|
509
|
+
__name(toPgClient, "toPgClient");
|
|
510
|
+
function isDrizzle(source) {
|
|
511
|
+
const client = source.$client;
|
|
512
|
+
return client !== null && client !== void 0 && typeof client === "object";
|
|
513
|
+
}
|
|
514
|
+
__name(isDrizzle, "isDrizzle");
|
|
515
|
+
function mark(client) {
|
|
516
|
+
Object.defineProperty(client, ADAPTED, {
|
|
517
|
+
value: true
|
|
518
|
+
});
|
|
519
|
+
return client;
|
|
520
|
+
}
|
|
521
|
+
__name(mark, "mark");
|
|
522
|
+
function rowsOf(result) {
|
|
523
|
+
if (Array.isArray(result)) {
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
const rows = result?.rows;
|
|
527
|
+
return Array.isArray(rows) ? rows : [];
|
|
528
|
+
}
|
|
529
|
+
__name(rowsOf, "rowsOf");
|
|
530
|
+
function fromPostgresJs(sql) {
|
|
531
|
+
const client = {
|
|
532
|
+
query: /* @__PURE__ */ __name(async (text, params = []) => (
|
|
533
|
+
// postgres.js resolves a RowList (an Array subclass); copy it to a plain array.
|
|
534
|
+
[
|
|
535
|
+
...rowsOf(await sql.unsafe(text, params))
|
|
536
|
+
]
|
|
537
|
+
), "query")
|
|
538
|
+
};
|
|
539
|
+
if (typeof sql.begin === "function") {
|
|
540
|
+
const begin = sql.begin.bind(sql);
|
|
541
|
+
client.transaction = (work) => Promise.resolve(begin((tx) => work(fromPostgresJs(tx))));
|
|
542
|
+
}
|
|
543
|
+
return mark(client);
|
|
544
|
+
}
|
|
545
|
+
__name(fromPostgresJs, "fromPostgresJs");
|
|
546
|
+
function fromQueryable(source) {
|
|
547
|
+
const client = {
|
|
548
|
+
query: /* @__PURE__ */ __name(async (text, params) => rowsOf(await source.query(text, params)), "query")
|
|
549
|
+
};
|
|
550
|
+
if (typeof source.transaction === "function") {
|
|
551
|
+
const transaction = source.transaction.bind(source);
|
|
552
|
+
client.transaction = (work) => transaction((tx) => work(toPgClient(tx)));
|
|
553
|
+
} else if (isNodePgPool(source)) {
|
|
554
|
+
client.transaction = async (work) => {
|
|
555
|
+
const connection = await source.connect();
|
|
556
|
+
let broken = false;
|
|
557
|
+
try {
|
|
558
|
+
return await runInTransaction(connection, work, () => {
|
|
559
|
+
broken = true;
|
|
560
|
+
});
|
|
561
|
+
} finally {
|
|
562
|
+
connection.release(broken ? new Error("ROLLBACK failed") : void 0);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
} else if (isNodePgClient(source)) {
|
|
566
|
+
client.transaction = (work) => runInTransaction(source, work);
|
|
567
|
+
}
|
|
568
|
+
return mark(client);
|
|
569
|
+
}
|
|
570
|
+
__name(fromQueryable, "fromQueryable");
|
|
571
|
+
async function runInTransaction(connection, work, onRollbackFailed) {
|
|
572
|
+
const tx = mark({
|
|
573
|
+
query: /* @__PURE__ */ __name(async (text, params) => rowsOf(await connection.query(text, params)), "query")
|
|
574
|
+
});
|
|
575
|
+
await connection.query("BEGIN");
|
|
576
|
+
try {
|
|
577
|
+
const result = await work(tx);
|
|
578
|
+
await connection.query("COMMIT");
|
|
579
|
+
return result;
|
|
580
|
+
} catch (error) {
|
|
581
|
+
await connection.query("ROLLBACK").catch(() => onRollbackFailed?.());
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
__name(runInTransaction, "runInTransaction");
|
|
586
|
+
function isNodePgPool(source) {
|
|
587
|
+
return typeof source.connect === "function" && typeof source.totalCount === "number";
|
|
588
|
+
}
|
|
589
|
+
__name(isNodePgPool, "isNodePgPool");
|
|
590
|
+
function isNodePgClient(source) {
|
|
591
|
+
return typeof source.release === "function" || typeof source.processID === "number" || source.connectionParameters !== void 0;
|
|
592
|
+
}
|
|
593
|
+
__name(isNodePgClient, "isNodePgClient");
|
|
594
|
+
|
|
411
595
|
// src/pg-vector-store.ts
|
|
412
596
|
var NUL_BYTE = String.fromCharCode(0);
|
|
413
597
|
function stripNulBytes(value) {
|
|
@@ -436,11 +620,37 @@ var PgVectorStore = class {
|
|
|
436
620
|
}
|
|
437
621
|
client;
|
|
438
622
|
table;
|
|
623
|
+
/** The single width (fixed-dimension table), or `undefined` for a mixed-dimension table. */
|
|
439
624
|
dimensions;
|
|
625
|
+
/** The widths with a partial HNSW index, for a mixed-dimension table. */
|
|
626
|
+
indexedDimensions;
|
|
627
|
+
nullableEmbeddings;
|
|
628
|
+
iterativeScan;
|
|
629
|
+
efSearch;
|
|
630
|
+
upsertBatchSize;
|
|
631
|
+
iterativeScanSupport;
|
|
440
632
|
constructor(client, options = {}) {
|
|
441
|
-
this.client = client;
|
|
633
|
+
this.client = toPgClient(client);
|
|
442
634
|
this.table = options.table ?? "agent_rag_chunks";
|
|
443
|
-
|
|
635
|
+
const dimensions = options.dimensions ?? 1536;
|
|
636
|
+
if (typeof dimensions === "number") {
|
|
637
|
+
this.dimensions = dimensions;
|
|
638
|
+
this.indexedDimensions = [];
|
|
639
|
+
} else {
|
|
640
|
+
for (const width of dimensions) {
|
|
641
|
+
if (!Number.isInteger(width) || width <= 0) {
|
|
642
|
+
throw new Error(`PgVectorStore: invalid embedding width ${String(width)}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
this.dimensions = void 0;
|
|
646
|
+
this.indexedDimensions = [
|
|
647
|
+
...new Set(dimensions)
|
|
648
|
+
];
|
|
649
|
+
}
|
|
650
|
+
this.nullableEmbeddings = options.nullableEmbeddings ?? false;
|
|
651
|
+
this.iterativeScan = options.iterativeScan;
|
|
652
|
+
this.efSearch = options.efSearch;
|
|
653
|
+
this.upsertBatchSize = Math.max(1, Math.floor(options.upsertBatchSize ?? 100));
|
|
444
654
|
}
|
|
445
655
|
/** Telemetry self-description — the table is the namespace a retrieval was served from. */
|
|
446
656
|
describeRetrieval() {
|
|
@@ -449,38 +659,77 @@ var PgVectorStore = class {
|
|
|
449
659
|
collection: this.table
|
|
450
660
|
};
|
|
451
661
|
}
|
|
452
|
-
/**
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
662
|
+
/**
|
|
663
|
+
* The idempotent DDL {@link PgVectorStore.ensureSchema} runs, in order — for a migration instead
|
|
664
|
+
* of a boot-time side effect. On a populated table, run the index statements `CONCURRENTLY` there.
|
|
665
|
+
*/
|
|
666
|
+
schemaStatements() {
|
|
667
|
+
const mixed = this.dimensions === void 0;
|
|
668
|
+
const column = mixed ? "vector" : `vector(${this.dimensions})`;
|
|
669
|
+
const statements = [
|
|
670
|
+
"CREATE EXTENSION IF NOT EXISTS vector",
|
|
671
|
+
`CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
456
672
|
id TEXT PRIMARY KEY,
|
|
457
673
|
text TEXT NOT NULL,
|
|
458
674
|
source TEXT,
|
|
459
675
|
metadata JSONB,
|
|
460
|
-
embedding
|
|
461
|
-
)`
|
|
462
|
-
|
|
676
|
+
embedding ${column}${this.nullableEmbeddings ? "" : " NOT NULL"}
|
|
677
|
+
)`
|
|
678
|
+
];
|
|
679
|
+
if (mixed) {
|
|
680
|
+
for (const width of this.indexedDimensions) {
|
|
681
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${this.table}_embedding_${width}_idx
|
|
682
|
+
ON ${this.table} USING hnsw ((embedding::vector(${width})) vector_cosine_ops)
|
|
683
|
+
WHERE vector_dims(embedding) = ${width}`);
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${this.table}_embedding_idx
|
|
463
687
|
ON ${this.table} USING hnsw (embedding vector_cosine_ops)`);
|
|
688
|
+
}
|
|
689
|
+
return statements;
|
|
690
|
+
}
|
|
691
|
+
/** Idempotent DDL: the `vector` extension, the chunks table, and the cosine HNSW index(es). */
|
|
692
|
+
async ensureSchema() {
|
|
693
|
+
for (const statement of this.schemaStatements()) {
|
|
694
|
+
await this.client.query(statement);
|
|
695
|
+
}
|
|
464
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Multi-row `INSERT … ON CONFLICT (id) DO UPDATE`, {@link PgVectorStoreOptions.upsertBatchSize}
|
|
699
|
+
* rows per statement. An empty `embedding` is written as `NULL` (see
|
|
700
|
+
* {@link PgVectorStoreOptions.nullableEmbeddings}).
|
|
701
|
+
*/
|
|
465
702
|
async upsert(records) {
|
|
703
|
+
const byId = /* @__PURE__ */ new Map();
|
|
466
704
|
for (const record of records) {
|
|
467
705
|
const id = stripNulBytes(record.id);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
706
|
+
byId.delete(id);
|
|
707
|
+
byId.set(id, {
|
|
708
|
+
...record,
|
|
709
|
+
id
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
const unique = [
|
|
713
|
+
...byId.values()
|
|
714
|
+
];
|
|
715
|
+
for (let start = 0; start < unique.length; start += this.upsertBatchSize) {
|
|
716
|
+
const batch = unique.slice(start, start + this.upsertBatchSize);
|
|
717
|
+
const params = [];
|
|
718
|
+
const rows = batch.map((record) => {
|
|
719
|
+
const text = stripNulBytes(record.text);
|
|
720
|
+
const source = record.source !== void 0 ? stripNulBytes(record.source) : void 0;
|
|
721
|
+
const metadata = record.metadata !== void 0 ? stripNulBytes(record.metadata) : void 0;
|
|
722
|
+
const base = params.length;
|
|
723
|
+
params.push(record.id, text, source ?? null, metadata !== void 0 ? JSON.stringify(metadata) : null, record.embedding.length > 0 ? toVectorLiteral(record.embedding) : null);
|
|
724
|
+
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}::jsonb, $${base + 5}::vector)`;
|
|
725
|
+
});
|
|
471
726
|
await this.client.query(`INSERT INTO ${this.table} (id, text, source, metadata, embedding)
|
|
472
|
-
VALUES (
|
|
727
|
+
VALUES ${rows.join(", ")}
|
|
473
728
|
ON CONFLICT (id) DO UPDATE SET
|
|
474
729
|
text = EXCLUDED.text,
|
|
475
730
|
source = EXCLUDED.source,
|
|
476
731
|
metadata = EXCLUDED.metadata,
|
|
477
|
-
embedding = EXCLUDED.embedding`,
|
|
478
|
-
id,
|
|
479
|
-
text,
|
|
480
|
-
source ?? null,
|
|
481
|
-
metadata !== void 0 ? JSON.stringify(metadata) : null,
|
|
482
|
-
toVectorLiteral(record.embedding)
|
|
483
|
-
]);
|
|
732
|
+
embedding = EXCLUDED.embedding`, params);
|
|
484
733
|
}
|
|
485
734
|
}
|
|
486
735
|
async remove(documentId) {
|
|
@@ -516,11 +765,12 @@ var PgVectorStore = class {
|
|
|
516
765
|
return rows.length;
|
|
517
766
|
}
|
|
518
767
|
async listDocuments(filter) {
|
|
519
|
-
const
|
|
768
|
+
const params = [];
|
|
769
|
+
const where = this.where(filter, params);
|
|
520
770
|
const rows = await this.client.query(`SELECT DISTINCT ON (${DOCUMENT_ID_FROM_CHUNK}) ${DOCUMENT_ID_FROM_CHUNK} AS doc_id, metadata
|
|
521
771
|
FROM ${this.table}
|
|
522
|
-
${where
|
|
523
|
-
ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`,
|
|
772
|
+
${where}
|
|
773
|
+
ORDER BY ${DOCUMENT_ID_FROM_CHUNK}`, params);
|
|
524
774
|
return rows.map((row) => ({
|
|
525
775
|
id: row.doc_id,
|
|
526
776
|
...row.metadata !== null ? {
|
|
@@ -571,11 +821,12 @@ var PgVectorStore = class {
|
|
|
571
821
|
if (filterMatchesNothing(filter)) {
|
|
572
822
|
return [];
|
|
573
823
|
}
|
|
574
|
-
const
|
|
824
|
+
const params = [];
|
|
825
|
+
const where = this.where(filter, params);
|
|
575
826
|
const rows = await this.client.query(`SELECT DISTINCT ${DOCUMENT_ID_FROM_CHUNK} AS doc_id
|
|
576
827
|
FROM ${this.table}
|
|
577
|
-
${where
|
|
578
|
-
ORDER BY doc_id`,
|
|
828
|
+
${where}
|
|
829
|
+
ORDER BY doc_id`, params);
|
|
579
830
|
return rows.map((row) => row.doc_id);
|
|
580
831
|
}
|
|
581
832
|
/** N documents in one statement — the set-based form of {@link PgVectorStore.remove}. */
|
|
@@ -599,81 +850,250 @@ var PgVectorStore = class {
|
|
|
599
850
|
if (filterMatchesNothing(filter)) {
|
|
600
851
|
return 0;
|
|
601
852
|
}
|
|
602
|
-
const
|
|
603
|
-
const
|
|
853
|
+
const params = [];
|
|
854
|
+
const where = this.where(filter, params);
|
|
855
|
+
const rows = await this.client.query(`DELETE FROM ${this.table} ${where} RETURNING id`, params);
|
|
604
856
|
return rows.length;
|
|
605
857
|
}
|
|
606
858
|
async countChunks(filter) {
|
|
607
859
|
if (filterMatchesNothing(filter)) {
|
|
608
860
|
return 0;
|
|
609
861
|
}
|
|
610
|
-
const
|
|
611
|
-
const
|
|
862
|
+
const params = [];
|
|
863
|
+
const where = this.where(filter, params);
|
|
864
|
+
const rows = await this.client.query(`SELECT count(*)::int AS count FROM ${this.table} ${where}`, params);
|
|
612
865
|
return Number(rows[0]?.count ?? 0);
|
|
613
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Cosine similarity, nearest first. On a mixed-dimension table only vectors as wide as the query
|
|
869
|
+
* are compared (through that width's partial index when it has one); chunks without an embedding
|
|
870
|
+
* are never returned.
|
|
871
|
+
*/
|
|
614
872
|
async search(embedding, options) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
873
|
+
if (embedding.length === 0) {
|
|
874
|
+
return [];
|
|
875
|
+
}
|
|
876
|
+
const params = [
|
|
877
|
+
toVectorLiteral(embedding),
|
|
878
|
+
options.topK
|
|
879
|
+
];
|
|
880
|
+
const conditions = this.whereConditions(options.filter, params);
|
|
881
|
+
let vectorType = "vector";
|
|
882
|
+
if (this.dimensions === void 0) {
|
|
883
|
+
const width = embedding.length;
|
|
884
|
+
if (this.indexedDimensions.includes(width)) {
|
|
885
|
+
vectorType = `vector(${width})`;
|
|
886
|
+
}
|
|
887
|
+
conditions.push("embedding IS NOT NULL", `vector_dims(embedding) = ${width}`);
|
|
888
|
+
} else if (this.nullableEmbeddings) {
|
|
889
|
+
conditions.push("embedding IS NOT NULL");
|
|
890
|
+
}
|
|
891
|
+
const column = vectorType === "vector" ? "embedding" : `(embedding::${vectorType})`;
|
|
892
|
+
const sql = `SELECT id, text, source, metadata, 1 - (${column} <=> $1::${vectorType}) AS score
|
|
618
893
|
FROM ${this.table}
|
|
619
|
-
${
|
|
620
|
-
ORDER BY
|
|
621
|
-
LIMIT $2
|
|
622
|
-
|
|
894
|
+
${conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
895
|
+
ORDER BY ${column} <=> $1::${vectorType}
|
|
896
|
+
LIMIT $2`;
|
|
897
|
+
const rows = await this.withSearchSettings((client) => client.query(sql, params));
|
|
898
|
+
return rows.map(toPassage);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* The SQL conditions (ANDed) a metadata `filter` compiles to, appending their bindings to `params`
|
|
902
|
+
* (placeholders are numbered after what `params` already holds). Every statement that takes a
|
|
903
|
+
* filter goes through here, so a subclass that maps the filter onto its own columns — a tenant id,
|
|
904
|
+
* an access list — overrides this one method. Return `['false']` to deny.
|
|
905
|
+
*/
|
|
906
|
+
whereConditions(filter, params) {
|
|
907
|
+
return buildWhere(filter, params);
|
|
908
|
+
}
|
|
909
|
+
where(filter, params) {
|
|
910
|
+
const conditions = this.whereConditions(filter, params);
|
|
911
|
+
return conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
912
|
+
}
|
|
913
|
+
/** Run a read with the configured HNSW settings `SET LOCAL` in one transaction, when possible. */
|
|
914
|
+
async withSearchSettings(read) {
|
|
915
|
+
const transaction = this.client.transaction?.bind(this.client);
|
|
916
|
+
if (transaction === void 0) {
|
|
917
|
+
return read(this.client);
|
|
918
|
+
}
|
|
919
|
+
const iterative = this.iterativeScan !== void 0 && await this.supportsIterativeScan();
|
|
920
|
+
if (!iterative && this.efSearch === void 0) {
|
|
921
|
+
return read(this.client);
|
|
922
|
+
}
|
|
923
|
+
return transaction(async (tx) => {
|
|
924
|
+
if (iterative) {
|
|
925
|
+
await tx.query(`SELECT set_config('hnsw.iterative_scan', $1, true)`, [
|
|
926
|
+
this.iterativeScan
|
|
927
|
+
]);
|
|
928
|
+
}
|
|
929
|
+
if (this.efSearch !== void 0) {
|
|
930
|
+
await tx.query(`SELECT set_config('hnsw.ef_search', $1, true)`, [
|
|
931
|
+
String(this.efSearch)
|
|
932
|
+
]);
|
|
933
|
+
}
|
|
934
|
+
return read(tx);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
/** `hnsw.iterative_scan` exists from pgvector 0.8.0; setting it on an older one is an error. */
|
|
938
|
+
supportsIterativeScan() {
|
|
939
|
+
this.iterativeScanSupport ??= this.client.query(`SELECT extversion FROM pg_extension WHERE extname = 'vector'`).then((rows) => {
|
|
940
|
+
const [major = 0, minor = 0] = (rows[0]?.extversion ?? "0.0").split(".").map(Number);
|
|
941
|
+
return major > 0 || minor >= 8;
|
|
942
|
+
}).catch(() => false);
|
|
943
|
+
return this.iterativeScanSupport;
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
var PgLexicalVectorStore = class extends PgVectorStore {
|
|
947
|
+
static {
|
|
948
|
+
__name(this, "PgLexicalVectorStore");
|
|
949
|
+
}
|
|
950
|
+
tsConfig;
|
|
951
|
+
tsColumn;
|
|
952
|
+
anyTermFallback;
|
|
953
|
+
stopWords;
|
|
954
|
+
constructor(client, options = {}) {
|
|
955
|
+
super(client, options);
|
|
956
|
+
const config = options.fullText?.config ?? "simple";
|
|
957
|
+
if (!SQL_IDENTIFIER.test(config)) {
|
|
958
|
+
throw new Error(`PgLexicalVectorStore: invalid text search configuration "${config}"`);
|
|
959
|
+
}
|
|
960
|
+
const column = options.fullText?.column;
|
|
961
|
+
if (column !== void 0 && !SQL_IDENTIFIER.test(column)) {
|
|
962
|
+
throw new Error(`PgLexicalVectorStore: invalid tsvector column "${column}"`);
|
|
963
|
+
}
|
|
964
|
+
this.tsConfig = config;
|
|
965
|
+
this.tsColumn = column;
|
|
966
|
+
this.anyTermFallback = options.fullText?.anyTermFallback ?? true;
|
|
967
|
+
const stopWords = options.fullText?.stopWords;
|
|
968
|
+
this.stopWords = stopWords === false ? {} : stopWords ?? DEFAULT_STOP_WORDS;
|
|
969
|
+
}
|
|
970
|
+
schemaStatements() {
|
|
971
|
+
const statements = super.schemaStatements();
|
|
972
|
+
if (this.tsColumn === void 0) {
|
|
973
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${this.table}_text_tsv_idx
|
|
974
|
+
ON ${this.table} USING gin (${this.tsvector()})`);
|
|
975
|
+
}
|
|
976
|
+
return statements;
|
|
977
|
+
}
|
|
978
|
+
async searchText(query, options) {
|
|
979
|
+
if (filterMatchesNothing(options.filter) || query.trim() === "") {
|
|
980
|
+
return [];
|
|
981
|
+
}
|
|
982
|
+
if (!this.anyTermFallback || hasSearchSyntax(query)) {
|
|
983
|
+
const strict = await this.runTextSearch(query, options);
|
|
984
|
+
if (strict.length > 0 || !this.anyTermFallback) {
|
|
985
|
+
return strict;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const terms = keywordTerms(query, 24, this.stopWords);
|
|
989
|
+
return terms.length === 0 ? [] : this.runRankedTermSearch(terms, options);
|
|
990
|
+
}
|
|
991
|
+
/** `websearch_to_tsquery` on the query as written: every word, quoted phrases, `-exclusions`. */
|
|
992
|
+
async runTextSearch(query, options) {
|
|
993
|
+
const tsquery = `websearch_to_tsquery('${this.tsConfig}', $1)`;
|
|
994
|
+
const params = [
|
|
995
|
+
query,
|
|
996
|
+
options.topK
|
|
997
|
+
];
|
|
998
|
+
const conditions = [
|
|
999
|
+
`${this.tsvector()} @@ ${tsquery}`,
|
|
1000
|
+
...this.whereConditions(options.filter, params)
|
|
1001
|
+
];
|
|
1002
|
+
const rows = await this.client.query(`SELECT id, text, source, metadata, ts_rank_cd(${this.tsvector()}, ${tsquery}) AS score
|
|
1003
|
+
FROM ${this.table}
|
|
1004
|
+
WHERE ${conditions.join(" AND ")}
|
|
1005
|
+
ORDER BY score DESC, id
|
|
1006
|
+
LIMIT $2`, params);
|
|
1007
|
+
return rows.map(toPassage);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Rows holding any of `terms`, ranked by the sum of the matched terms' IDF among those rows,
|
|
1011
|
+
* `ln(1 + (N - df + 0.5) / (df + 0.5))`, then `ts_rank_cd`. Each (row, term) match is evaluated
|
|
1012
|
+
* once, and `ts_rank_cd` only for the rows ranked within `topK` (ties included): a question full
|
|
1013
|
+
* of common words can match most of the table.
|
|
1014
|
+
*/
|
|
1015
|
+
async runRankedTermSearch(terms, options) {
|
|
1016
|
+
const any = `to_tsquery('${this.tsConfig}', $1)`;
|
|
1017
|
+
const params = [
|
|
1018
|
+
anyTermTsquery(terms),
|
|
623
1019
|
options.topK,
|
|
624
|
-
|
|
625
|
-
]
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
1020
|
+
terms
|
|
1021
|
+
];
|
|
1022
|
+
const conditions = [
|
|
1023
|
+
`${this.tsvector()} @@ ${any}`,
|
|
1024
|
+
...this.whereConditions(options.filter, params)
|
|
1025
|
+
];
|
|
1026
|
+
const rows = await this.client.query(`WITH __terms AS (
|
|
1027
|
+
SELECT DISTINCT to_tsquery('${this.tsConfig}', quote_literal(t)) AS q
|
|
1028
|
+
FROM unnest($3::text[]) AS t
|
|
1029
|
+
), __cand AS MATERIALIZED (
|
|
1030
|
+
SELECT id, text, source, metadata, ${this.tsvector()} AS __tsv
|
|
1031
|
+
FROM ${this.table}
|
|
1032
|
+
WHERE ${conditions.join(" AND ")}
|
|
1033
|
+
), __hits AS MATERIALIZED (
|
|
1034
|
+
SELECT __cand.id AS __id, __terms.q FROM __cand JOIN __terms ON __cand.__tsv @@ __terms.q
|
|
1035
|
+
), __idf AS (
|
|
1036
|
+
SELECT q, ln(1 + ((SELECT count(*) FROM __cand) - count(*) + 0.5) / (count(*) + 0.5)) AS w
|
|
1037
|
+
FROM __hits GROUP BY q
|
|
1038
|
+
), __scored AS (
|
|
1039
|
+
SELECT __hits.__id, sum(__idf.w) AS s, rank() OVER (ORDER BY sum(__idf.w) DESC) AS r
|
|
1040
|
+
FROM __hits JOIN __idf USING (q) GROUP BY __hits.__id
|
|
1041
|
+
)
|
|
1042
|
+
SELECT __cand.id, __cand.text, __cand.source, __cand.metadata,
|
|
1043
|
+
__scored.s + ts_rank_cd(__cand.__tsv, ${any}, 32) AS score
|
|
1044
|
+
FROM __scored JOIN __cand ON __cand.id = __scored.__id
|
|
1045
|
+
WHERE __scored.r <= $2
|
|
1046
|
+
ORDER BY __scored.s DESC, ts_rank_cd(__cand.__tsv, ${any}, 32) DESC, __cand.id
|
|
1047
|
+
LIMIT $2`, params);
|
|
1048
|
+
return rows.map(toPassage);
|
|
1049
|
+
}
|
|
1050
|
+
/** Must be character-for-character the index expression, or the planner won't use the index. */
|
|
1051
|
+
tsvector() {
|
|
1052
|
+
return this.tsColumn ?? `to_tsvector('${this.tsConfig}'::regconfig, text)`;
|
|
637
1053
|
}
|
|
638
1054
|
};
|
|
639
|
-
|
|
1055
|
+
var SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/;
|
|
1056
|
+
function toPassage(row) {
|
|
1057
|
+
return {
|
|
1058
|
+
id: row.id,
|
|
1059
|
+
text: row.text,
|
|
1060
|
+
score: Number(row.score),
|
|
1061
|
+
...row.source !== null ? {
|
|
1062
|
+
source: row.source
|
|
1063
|
+
} : {},
|
|
1064
|
+
...row.metadata !== null ? {
|
|
1065
|
+
metadata: row.metadata
|
|
1066
|
+
} : {}
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
__name(toPassage, "toPassage");
|
|
1070
|
+
function buildWhere(filter, params) {
|
|
640
1071
|
if (filter === void 0 || Object.keys(filter).length === 0) {
|
|
641
|
-
return
|
|
642
|
-
sql: "",
|
|
643
|
-
params: []
|
|
644
|
-
};
|
|
1072
|
+
return [];
|
|
645
1073
|
}
|
|
646
1074
|
const stripped = stripNulBytes(filter);
|
|
647
1075
|
const clauses = [];
|
|
648
|
-
const params = [];
|
|
649
1076
|
const scalar = {};
|
|
650
|
-
let index = startIndex;
|
|
651
1077
|
for (const [key, value] of Object.entries(stripped)) {
|
|
652
1078
|
if (Array.isArray(value)) {
|
|
653
1079
|
if (value.length === 0) {
|
|
654
1080
|
clauses.push("false");
|
|
655
1081
|
continue;
|
|
656
1082
|
}
|
|
657
|
-
const keyParam = `$${index}`;
|
|
658
1083
|
params.push(key);
|
|
659
|
-
|
|
660
|
-
const arrParam = `$${index}`;
|
|
1084
|
+
const keyParam = `$${params.length}`;
|
|
661
1085
|
params.push(value.map(String));
|
|
662
|
-
|
|
1086
|
+
const arrParam = `$${params.length}`;
|
|
663
1087
|
clauses.push(`(CASE WHEN jsonb_typeof(metadata->${keyParam}) = 'array' THEN metadata->${keyParam} ELSE jsonb_build_array(metadata->${keyParam}) END) ?| ${arrParam}::text[]`);
|
|
664
1088
|
} else {
|
|
665
1089
|
scalar[key] = value;
|
|
666
1090
|
}
|
|
667
1091
|
}
|
|
668
1092
|
if (Object.keys(scalar).length > 0) {
|
|
669
|
-
clauses.push(`metadata @> $${index}::jsonb`);
|
|
670
1093
|
params.push(JSON.stringify(scalar));
|
|
671
|
-
|
|
1094
|
+
clauses.push(`metadata @> $${params.length}::jsonb`);
|
|
672
1095
|
}
|
|
673
|
-
return
|
|
674
|
-
sql: `WHERE ${clauses.join(" AND ")}`,
|
|
675
|
-
params
|
|
676
|
-
};
|
|
1096
|
+
return clauses;
|
|
677
1097
|
}
|
|
678
1098
|
__name(buildWhere, "buildWhere");
|
|
679
1099
|
var DOCUMENT_ID_FROM_CHUNK = "regexp_replace(id, '#[0-9]+$', '')";
|
|
@@ -1483,7 +1903,7 @@ function parseSearchReply(reply, prefix, scoreSource = "vector") {
|
|
|
1483
1903
|
attrs[toStr(rawFields[field])] = toStr(rawFields[field + 1]);
|
|
1484
1904
|
}
|
|
1485
1905
|
const inlineScore = scoreSource === "lexical" ? toStr(reply[index + 1]) : void 0;
|
|
1486
|
-
passages.push(
|
|
1906
|
+
passages.push(toPassage2(toStr(reply[index]), attrs, prefix, scoreSource, inlineScore));
|
|
1487
1907
|
}
|
|
1488
1908
|
}
|
|
1489
1909
|
return passages;
|
|
@@ -1491,7 +1911,7 @@ function parseSearchReply(reply, prefix, scoreSource = "vector") {
|
|
|
1491
1911
|
if (typeof reply === "object" && reply !== null && "results" in reply) {
|
|
1492
1912
|
const results = reply.results;
|
|
1493
1913
|
if (Array.isArray(results)) {
|
|
1494
|
-
return results.map((result) =>
|
|
1914
|
+
return results.map((result) => toPassage2(readId(result), readAttributes(result), prefix, scoreSource, scoreSource === "lexical" ? readScore(result) : void 0));
|
|
1495
1915
|
}
|
|
1496
1916
|
}
|
|
1497
1917
|
return [];
|
|
@@ -1524,7 +1944,7 @@ function readAttributes(result) {
|
|
|
1524
1944
|
return attrs;
|
|
1525
1945
|
}
|
|
1526
1946
|
__name(readAttributes, "readAttributes");
|
|
1527
|
-
function
|
|
1947
|
+
function toPassage2(rawId, attrs, prefix, scoreSource = "vector", inlineScore) {
|
|
1528
1948
|
const score = scoreSource === "lexical" ? Number(inlineScore ?? "0") : 1 - Number(attrs.vector_score ?? "1");
|
|
1529
1949
|
const source = attrs.source;
|
|
1530
1950
|
return {
|
|
@@ -1537,7 +1957,7 @@ function toPassage(rawId, attrs, prefix, scoreSource = "vector", inlineScore) {
|
|
|
1537
1957
|
...parseMetadata(attrs.metadata_json)
|
|
1538
1958
|
};
|
|
1539
1959
|
}
|
|
1540
|
-
__name(
|
|
1960
|
+
__name(toPassage2, "toPassage");
|
|
1541
1961
|
function parseMetadata(raw) {
|
|
1542
1962
|
if (raw === void 0) {
|
|
1543
1963
|
return {};
|
|
@@ -2082,14 +2502,182 @@ function createRetrievalTool(retriever, options = {}) {
|
|
|
2082
2502
|
};
|
|
2083
2503
|
}
|
|
2084
2504
|
__name(createRetrievalTool, "createRetrievalTool");
|
|
2505
|
+
|
|
2506
|
+
// src/openai-embeddings.ts
|
|
2507
|
+
var HttpModelError = class extends Error {
|
|
2508
|
+
static {
|
|
2509
|
+
__name(this, "HttpModelError");
|
|
2510
|
+
}
|
|
2511
|
+
status;
|
|
2512
|
+
constructor(status, message) {
|
|
2513
|
+
super(message), this.status = status;
|
|
2514
|
+
this.name = "HttpModelError";
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
function openAiEmbeddings(options) {
|
|
2518
|
+
const url = `${(options.baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/embeddings`;
|
|
2519
|
+
const batchSize = Math.max(1, Math.floor(options.batchSize ?? 64));
|
|
2520
|
+
const maxInputChars = options.maxInputChars ?? 24e3;
|
|
2521
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
2522
|
+
const doFetch = options.fetch ?? fetch;
|
|
2523
|
+
return {
|
|
2524
|
+
async embed(texts) {
|
|
2525
|
+
const vectors = [];
|
|
2526
|
+
for (let start = 0; start < texts.length; start += batchSize) {
|
|
2527
|
+
const input = texts.slice(start, start + batchSize).map((text) => (text.length > maxInputChars ? text.slice(0, maxInputChars) : text) || " ");
|
|
2528
|
+
const response = await doFetch(url, {
|
|
2529
|
+
method: "POST",
|
|
2530
|
+
headers: {
|
|
2531
|
+
"content-type": "application/json",
|
|
2532
|
+
...options.apiKey ? {
|
|
2533
|
+
authorization: `Bearer ${options.apiKey}`
|
|
2534
|
+
} : {},
|
|
2535
|
+
...options.headers
|
|
2536
|
+
},
|
|
2537
|
+
body: JSON.stringify({
|
|
2538
|
+
model: options.model,
|
|
2539
|
+
input,
|
|
2540
|
+
...options.dimensions !== void 0 ? {
|
|
2541
|
+
dimensions: options.dimensions
|
|
2542
|
+
} : {}
|
|
2543
|
+
}),
|
|
2544
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
2545
|
+
});
|
|
2546
|
+
const body = await response.text();
|
|
2547
|
+
if (!response.ok) {
|
|
2548
|
+
throw new HttpModelError(response.status, `Embeddings request failed (${response.status}): ${errorMessageOf(body)}`);
|
|
2549
|
+
}
|
|
2550
|
+
const json = parseJson(body);
|
|
2551
|
+
const items = [
|
|
2552
|
+
...json?.data ?? []
|
|
2553
|
+
].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
2554
|
+
if (items.length !== input.length || !items.every((item) => Array.isArray(item.embedding))) {
|
|
2555
|
+
throw new HttpModelError(502, `Embeddings returned ${items.length} vectors for ${input.length} inputs`);
|
|
2556
|
+
}
|
|
2557
|
+
for (const item of items) {
|
|
2558
|
+
vectors.push(item.embedding);
|
|
2559
|
+
}
|
|
2560
|
+
const tokens = json?.usage?.total_tokens ?? json?.usage?.prompt_tokens;
|
|
2561
|
+
if (typeof tokens === "number" && Number.isFinite(tokens)) {
|
|
2562
|
+
options.onUsage?.(tokens);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
return vectors;
|
|
2566
|
+
}
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
__name(openAiEmbeddings, "openAiEmbeddings");
|
|
2570
|
+
function parseJson(body) {
|
|
2571
|
+
try {
|
|
2572
|
+
return JSON.parse(body);
|
|
2573
|
+
} catch {
|
|
2574
|
+
return null;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
__name(parseJson, "parseJson");
|
|
2578
|
+
function errorMessageOf(body) {
|
|
2579
|
+
const json = parseJson(body);
|
|
2580
|
+
const message = typeof json?.error === "string" ? json.error : json?.error?.message ?? json?.message ?? void 0;
|
|
2581
|
+
return typeof message === "string" ? message : body.slice(0, 300);
|
|
2582
|
+
}
|
|
2583
|
+
__name(errorMessageOf, "errorMessageOf");
|
|
2584
|
+
|
|
2585
|
+
// src/http-reranker.ts
|
|
2586
|
+
var HttpReranker = class {
|
|
2587
|
+
static {
|
|
2588
|
+
__name(this, "HttpReranker");
|
|
2589
|
+
}
|
|
2590
|
+
options;
|
|
2591
|
+
format;
|
|
2592
|
+
doFetch;
|
|
2593
|
+
constructor(options) {
|
|
2594
|
+
this.options = options;
|
|
2595
|
+
this.format = options.format ?? "auto";
|
|
2596
|
+
this.doFetch = options.fetch ?? fetch;
|
|
2597
|
+
}
|
|
2598
|
+
async rerank(query, passages, options = {}) {
|
|
2599
|
+
if (passages.length === 0) {
|
|
2600
|
+
return [];
|
|
2601
|
+
}
|
|
2602
|
+
const topK = options.topK ?? passages.length;
|
|
2603
|
+
const documents = passages.map((passage) => passage.text);
|
|
2604
|
+
const response = await this.doFetch(this.options.url, {
|
|
2605
|
+
method: "POST",
|
|
2606
|
+
headers: {
|
|
2607
|
+
"content-type": "application/json",
|
|
2608
|
+
...this.options.apiKey ? {
|
|
2609
|
+
authorization: `Bearer ${this.options.apiKey}`
|
|
2610
|
+
} : {},
|
|
2611
|
+
...this.options.headers
|
|
2612
|
+
},
|
|
2613
|
+
body: JSON.stringify({
|
|
2614
|
+
...this.options.model !== void 0 ? {
|
|
2615
|
+
model: this.options.model
|
|
2616
|
+
} : {},
|
|
2617
|
+
query,
|
|
2618
|
+
...this.format !== "tei" ? {
|
|
2619
|
+
documents,
|
|
2620
|
+
top_n: topK
|
|
2621
|
+
} : {},
|
|
2622
|
+
...this.format !== "cohere" ? {
|
|
2623
|
+
texts: documents
|
|
2624
|
+
} : {}
|
|
2625
|
+
}),
|
|
2626
|
+
signal: AbortSignal.timeout(this.options.timeoutMs ?? 3e4)
|
|
2627
|
+
});
|
|
2628
|
+
const body = await response.text();
|
|
2629
|
+
if (!response.ok) {
|
|
2630
|
+
throw new HttpModelError(response.status, `Rerank request failed (${response.status}): ${errorMessageOf(body)}`);
|
|
2631
|
+
}
|
|
2632
|
+
const items = itemsOf(body);
|
|
2633
|
+
if (items === void 0) {
|
|
2634
|
+
throw new HttpModelError(502, "Rerank response has no results");
|
|
2635
|
+
}
|
|
2636
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2637
|
+
const reranked = [];
|
|
2638
|
+
for (const item of items) {
|
|
2639
|
+
const index = item.index;
|
|
2640
|
+
const passage = typeof index === "number" ? passages[index] : void 0;
|
|
2641
|
+
if (passage === void 0 || seen.has(index)) {
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
seen.add(index);
|
|
2645
|
+
reranked.push({
|
|
2646
|
+
...passage,
|
|
2647
|
+
score: Number(item.relevance_score ?? item.score ?? 0)
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
reranked.sort((a, b) => b.score - a.score);
|
|
2651
|
+
return reranked.slice(0, topK);
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2654
|
+
function itemsOf(body) {
|
|
2655
|
+
let json;
|
|
2656
|
+
try {
|
|
2657
|
+
json = JSON.parse(body);
|
|
2658
|
+
} catch {
|
|
2659
|
+
return void 0;
|
|
2660
|
+
}
|
|
2661
|
+
if (Array.isArray(json)) {
|
|
2662
|
+
return json;
|
|
2663
|
+
}
|
|
2664
|
+
const wrapped = json;
|
|
2665
|
+
const items = wrapped?.results ?? wrapped?.data;
|
|
2666
|
+
return Array.isArray(items) ? items : void 0;
|
|
2667
|
+
}
|
|
2668
|
+
__name(itemsOf, "itemsOf");
|
|
2085
2669
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2086
2670
|
0 && (module.exports = {
|
|
2671
|
+
DEFAULT_STOP_WORDS,
|
|
2087
2672
|
EmbeddingRetriever,
|
|
2088
2673
|
FilteredRetriever,
|
|
2674
|
+
HttpModelError,
|
|
2675
|
+
HttpReranker,
|
|
2089
2676
|
HybridRetriever,
|
|
2090
2677
|
KeywordRetriever,
|
|
2091
2678
|
LexicalRetriever,
|
|
2092
2679
|
MemoryVectorStore,
|
|
2680
|
+
PgLexicalVectorStore,
|
|
2093
2681
|
PgVectorStore,
|
|
2094
2682
|
RAG_DIAGNOSTIC_LIB,
|
|
2095
2683
|
RAG_RETRIEVAL_CHANNEL,
|
|
@@ -2098,6 +2686,7 @@ __name(createRetrievalTool, "createRetrievalTool");
|
|
|
2098
2686
|
RedisVectorStore,
|
|
2099
2687
|
RerankingRetriever,
|
|
2100
2688
|
UnsafeRemovalError,
|
|
2689
|
+
anyTermTsquery,
|
|
2101
2690
|
applyMetadataPatch,
|
|
2102
2691
|
chunkDocuments,
|
|
2103
2692
|
chunkIndexOf,
|
|
@@ -2109,9 +2698,13 @@ __name(createRetrievalTool, "createRetrievalTool");
|
|
|
2109
2698
|
describesRetrieval,
|
|
2110
2699
|
documentIdOf,
|
|
2111
2700
|
emitRagRetrieval,
|
|
2701
|
+
hasSearchSyntax,
|
|
2112
2702
|
ingestChunks,
|
|
2113
2703
|
ingestDocuments,
|
|
2114
2704
|
instrumentRetriever,
|
|
2115
|
-
isLexicalVectorStore
|
|
2705
|
+
isLexicalVectorStore,
|
|
2706
|
+
keywordTerms,
|
|
2707
|
+
openAiEmbeddings,
|
|
2708
|
+
toPgClient
|
|
2116
2709
|
});
|
|
2117
2710
|
//# sourceMappingURL=index.cjs.map
|