@dudousxd/nestjs-agent-rag 0.11.0 → 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 +7 -3
- package/dist/index.cjs +137 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -7
- package/dist/index.d.ts +56 -7
- package/dist/index.js +133 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -432,6 +432,34 @@ declare class MemoryVectorStore implements VectorStore {
|
|
|
432
432
|
search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Turning a natural-language question into full-text search terms, for the lexical leg of
|
|
437
|
+
* `PgLexicalVectorStore`.
|
|
438
|
+
*
|
|
439
|
+
* With the `simple` text-search configuration (no stemming, no stop words; any language) a question
|
|
440
|
+
* searched as is either matches nothing (every word required) or, as "any word", ranks chunks full
|
|
441
|
+
* of "the / of / which / de / que" first. So the query side drops the stop words of the question's
|
|
442
|
+
* language(s) and keeps the meaningful terms, which the store then ranks by how many of them a chunk
|
|
443
|
+
* holds, rare terms weighing more (an IDF over the matching rows, BM25-style).
|
|
444
|
+
*/
|
|
445
|
+
/**
|
|
446
|
+
* Postgres' Snowball stop word lists (`tsearch_data/{english,portuguese,spanish}.stop`), minus
|
|
447
|
+
* one-letter words (never terms here) and words that carry meaning in a sibling language
|
|
448
|
+
* ("estado", "sentido").
|
|
449
|
+
*/
|
|
450
|
+
declare const DEFAULT_STOP_WORDS: Readonly<Record<string, ReadonlySet<string>>>;
|
|
451
|
+
/**
|
|
452
|
+
* The meaningful terms of a question, in order, deduplicated, at most `max`: the stop words of the
|
|
453
|
+
* language(s) the question is written in are dropped (the language(s) with the most stop words in
|
|
454
|
+
* it, so "sea"/"son" stay terms in an English question although they are Spanish stop words). A
|
|
455
|
+
* question made only of stop words keeps them all (there is nothing else to search for).
|
|
456
|
+
*/
|
|
457
|
+
declare function keywordTerms(query: string, max?: number, stopWords?: Readonly<Record<string, ReadonlySet<string>>>): string[];
|
|
458
|
+
/** Whether the user wrote search syntax (a quoted phrase, `-exclusion`) worth honoring as such. */
|
|
459
|
+
declare function hasSearchSyntax(query: string): boolean;
|
|
460
|
+
/** `'a' | 'b' | …` for `to_tsquery` (terms are letter/digit runs, so quoting is enough). */
|
|
461
|
+
declare function anyTermTsquery(terms: string[]): string;
|
|
462
|
+
|
|
435
463
|
/**
|
|
436
464
|
* The minimal Postgres surface {@link import('./pg-vector-store.js').PgVectorStore} needs — adapt your
|
|
437
465
|
* own `pg` / `postgres.js` client to it, so this package pulls in NO driver (bring your own, like the
|
|
@@ -502,11 +530,22 @@ interface PgFullTextOptions {
|
|
|
502
530
|
*/
|
|
503
531
|
column?: string;
|
|
504
532
|
/**
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
533
|
+
* Search natural-language questions by their meaningful terms (default `true`): the question's
|
|
534
|
+
* stop words are dropped (see {@link PgFullTextOptions.stopWords}) and rows holding any remaining
|
|
535
|
+
* term (at most 24) are ranked by the sum of the matched terms' IDF among those rows (BM25
|
|
536
|
+
* without term frequencies), `ts_rank_cd` breaking ties; a question with explicit search syntax
|
|
537
|
+
* (a quoted phrase, `-word`) is first tried as written. `false`: only `websearch_to_tsquery` on
|
|
538
|
+
* the query as written (every word must appear).
|
|
508
539
|
*/
|
|
509
540
|
anyTermFallback?: boolean;
|
|
541
|
+
/**
|
|
542
|
+
* Stop word lists dropped from questions, by language; only the list(s) with the most words in
|
|
543
|
+
* the question apply, so a word that is a stop word in another language stays a term. Default
|
|
544
|
+
* {@link DEFAULT_STOP_WORDS} (English, Portuguese, Spanish: Postgres' Snowball lists). `false`
|
|
545
|
+
* keeps every word. With a language `config` (`english`) Postgres drops that language's stop
|
|
546
|
+
* words as well.
|
|
547
|
+
*/
|
|
548
|
+
stopWords?: Readonly<Record<string, ReadonlySet<string>>> | false;
|
|
510
549
|
}
|
|
511
550
|
interface PgVectorStoreOptions {
|
|
512
551
|
/** Table name. Default `agent_rag_chunks`. */
|
|
@@ -669,9 +708,10 @@ declare class PgVectorStore implements VectorStore {
|
|
|
669
708
|
* `LexicalRetriever` and `HybridRetriever` work on Postgres exactly as on RediSearch — the dense leg
|
|
670
709
|
* over pgvector, the lexical leg over a `tsvector`, both on the same rows and ids.
|
|
671
710
|
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
711
|
+
* A question is searched by its meaningful terms: its stop words dropped, rows holding any remaining
|
|
712
|
+
* term ranked by IDF-weighted coverage, `ts_rank_cd` (cover density) breaking ties (see
|
|
713
|
+
* {@link PgFullTextOptions.anyTermFallback}); explicit syntax (quoted phrases, `-exclusions`) goes
|
|
714
|
+
* through `websearch_to_tsquery` first.
|
|
675
715
|
*
|
|
676
716
|
* The `tsvector` is either a column you own ({@link PgFullTextOptions.column}) or the expression
|
|
677
717
|
* `to_tsvector(config, text)`, for which {@link PgVectorStore.schemaStatements} adds a GIN
|
|
@@ -682,12 +722,21 @@ declare class PgLexicalVectorStore extends PgVectorStore implements LexicalVecto
|
|
|
682
722
|
private readonly tsConfig;
|
|
683
723
|
private readonly tsColumn;
|
|
684
724
|
private readonly anyTermFallback;
|
|
725
|
+
private readonly stopWords;
|
|
685
726
|
constructor(client: PgClientSource, options?: PgVectorStoreOptions & {
|
|
686
727
|
fullText?: PgFullTextOptions;
|
|
687
728
|
});
|
|
688
729
|
schemaStatements(): string[];
|
|
689
730
|
searchText(query: string, options: VectorSearchOptions): Promise<Passage[]>;
|
|
731
|
+
/** `websearch_to_tsquery` on the query as written: every word, quoted phrases, `-exclusions`. */
|
|
690
732
|
private runTextSearch;
|
|
733
|
+
/**
|
|
734
|
+
* Rows holding any of `terms`, ranked by the sum of the matched terms' IDF among those rows,
|
|
735
|
+
* `ln(1 + (N - df + 0.5) / (df + 0.5))`, then `ts_rank_cd`. Each (row, term) match is evaluated
|
|
736
|
+
* once, and `ts_rank_cd` only for the rows ranked within `topK` (ties included): a question full
|
|
737
|
+
* of common words can match most of the table.
|
|
738
|
+
*/
|
|
739
|
+
private runRankedTermSearch;
|
|
691
740
|
/** Must be character-for-character the index expression, or the planner won't use the index. */
|
|
692
741
|
private tsvector;
|
|
693
742
|
}
|
|
@@ -1476,4 +1525,4 @@ declare class HttpReranker implements Reranker {
|
|
|
1476
1525
|
rerank(query: string, passages: Passage[], options?: RerankOptions): Promise<Passage[]>;
|
|
1477
1526
|
}
|
|
1478
1527
|
|
|
1479
|
-
export { type ChunkOptions, type ChunkRecord, type DescribesRetrieval, type DrizzleLike, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HttpModelError, HttpReranker, type HttpRerankerOptions, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, type InstrumentRetrieverOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, type ListChunksOptions, MemoryVectorStore, type MetadataPatch, type NodePgQueryable, type OpenAiEmbeddingsOptions, type PgClient, type PgClientSource, type PgFullTextOptions, PgLexicalVectorStore, PgVectorStore, type PgVectorStoreOptions, type PostgresJsSql, RAG_DIAGNOSTIC_LIB, RAG_RETRIEVAL_CHANNEL, RAG_RETRIEVAL_EVENT, type RagRetrievalEvent, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalDescriptor, type RetrievalTool, type RetrievalToolOptions, type RetrieverKind, type StoredChunk, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, type VectorStoreKind, applyMetadataPatch, chunkDocuments, chunkIndexOf, chunkText, createRetrievalTool, describeRetrieval, describeSharedSource, describeSource, describesRetrieval, documentIdOf, emitRagRetrieval, ingestChunks, ingestDocuments, instrumentRetriever, isLexicalVectorStore, openAiEmbeddings, toPgClient };
|
|
1528
|
+
export { type ChunkOptions, type ChunkRecord, DEFAULT_STOP_WORDS, type DescribesRetrieval, type DrizzleLike, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HttpModelError, HttpReranker, type HttpRerankerOptions, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, type InstrumentRetrieverOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, type ListChunksOptions, MemoryVectorStore, type MetadataPatch, type NodePgQueryable, type OpenAiEmbeddingsOptions, type PgClient, type PgClientSource, type PgFullTextOptions, PgLexicalVectorStore, PgVectorStore, type PgVectorStoreOptions, type PostgresJsSql, RAG_DIAGNOSTIC_LIB, RAG_RETRIEVAL_CHANNEL, RAG_RETRIEVAL_EVENT, type RagRetrievalEvent, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalDescriptor, type RetrievalTool, type RetrievalToolOptions, type RetrieverKind, type StoredChunk, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, type VectorStoreKind, anyTermTsquery, applyMetadataPatch, chunkDocuments, chunkIndexOf, chunkText, createRetrievalTool, describeRetrieval, describeSharedSource, describeSource, describesRetrieval, documentIdOf, emitRagRetrieval, hasSearchSyntax, ingestChunks, ingestDocuments, instrumentRetriever, isLexicalVectorStore, keywordTerms, openAiEmbeddings, toPgClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -432,6 +432,34 @@ declare class MemoryVectorStore implements VectorStore {
|
|
|
432
432
|
search(embedding: number[], options: VectorSearchOptions): Promise<Passage[]>;
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Turning a natural-language question into full-text search terms, for the lexical leg of
|
|
437
|
+
* `PgLexicalVectorStore`.
|
|
438
|
+
*
|
|
439
|
+
* With the `simple` text-search configuration (no stemming, no stop words; any language) a question
|
|
440
|
+
* searched as is either matches nothing (every word required) or, as "any word", ranks chunks full
|
|
441
|
+
* of "the / of / which / de / que" first. So the query side drops the stop words of the question's
|
|
442
|
+
* language(s) and keeps the meaningful terms, which the store then ranks by how many of them a chunk
|
|
443
|
+
* holds, rare terms weighing more (an IDF over the matching rows, BM25-style).
|
|
444
|
+
*/
|
|
445
|
+
/**
|
|
446
|
+
* Postgres' Snowball stop word lists (`tsearch_data/{english,portuguese,spanish}.stop`), minus
|
|
447
|
+
* one-letter words (never terms here) and words that carry meaning in a sibling language
|
|
448
|
+
* ("estado", "sentido").
|
|
449
|
+
*/
|
|
450
|
+
declare const DEFAULT_STOP_WORDS: Readonly<Record<string, ReadonlySet<string>>>;
|
|
451
|
+
/**
|
|
452
|
+
* The meaningful terms of a question, in order, deduplicated, at most `max`: the stop words of the
|
|
453
|
+
* language(s) the question is written in are dropped (the language(s) with the most stop words in
|
|
454
|
+
* it, so "sea"/"son" stay terms in an English question although they are Spanish stop words). A
|
|
455
|
+
* question made only of stop words keeps them all (there is nothing else to search for).
|
|
456
|
+
*/
|
|
457
|
+
declare function keywordTerms(query: string, max?: number, stopWords?: Readonly<Record<string, ReadonlySet<string>>>): string[];
|
|
458
|
+
/** Whether the user wrote search syntax (a quoted phrase, `-exclusion`) worth honoring as such. */
|
|
459
|
+
declare function hasSearchSyntax(query: string): boolean;
|
|
460
|
+
/** `'a' | 'b' | …` for `to_tsquery` (terms are letter/digit runs, so quoting is enough). */
|
|
461
|
+
declare function anyTermTsquery(terms: string[]): string;
|
|
462
|
+
|
|
435
463
|
/**
|
|
436
464
|
* The minimal Postgres surface {@link import('./pg-vector-store.js').PgVectorStore} needs — adapt your
|
|
437
465
|
* own `pg` / `postgres.js` client to it, so this package pulls in NO driver (bring your own, like the
|
|
@@ -502,11 +530,22 @@ interface PgFullTextOptions {
|
|
|
502
530
|
*/
|
|
503
531
|
column?: string;
|
|
504
532
|
/**
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
533
|
+
* Search natural-language questions by their meaningful terms (default `true`): the question's
|
|
534
|
+
* stop words are dropped (see {@link PgFullTextOptions.stopWords}) and rows holding any remaining
|
|
535
|
+
* term (at most 24) are ranked by the sum of the matched terms' IDF among those rows (BM25
|
|
536
|
+
* without term frequencies), `ts_rank_cd` breaking ties; a question with explicit search syntax
|
|
537
|
+
* (a quoted phrase, `-word`) is first tried as written. `false`: only `websearch_to_tsquery` on
|
|
538
|
+
* the query as written (every word must appear).
|
|
508
539
|
*/
|
|
509
540
|
anyTermFallback?: boolean;
|
|
541
|
+
/**
|
|
542
|
+
* Stop word lists dropped from questions, by language; only the list(s) with the most words in
|
|
543
|
+
* the question apply, so a word that is a stop word in another language stays a term. Default
|
|
544
|
+
* {@link DEFAULT_STOP_WORDS} (English, Portuguese, Spanish: Postgres' Snowball lists). `false`
|
|
545
|
+
* keeps every word. With a language `config` (`english`) Postgres drops that language's stop
|
|
546
|
+
* words as well.
|
|
547
|
+
*/
|
|
548
|
+
stopWords?: Readonly<Record<string, ReadonlySet<string>>> | false;
|
|
510
549
|
}
|
|
511
550
|
interface PgVectorStoreOptions {
|
|
512
551
|
/** Table name. Default `agent_rag_chunks`. */
|
|
@@ -669,9 +708,10 @@ declare class PgVectorStore implements VectorStore {
|
|
|
669
708
|
* `LexicalRetriever` and `HybridRetriever` work on Postgres exactly as on RediSearch — the dense leg
|
|
670
709
|
* over pgvector, the lexical leg over a `tsvector`, both on the same rows and ids.
|
|
671
710
|
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
711
|
+
* A question is searched by its meaningful terms: its stop words dropped, rows holding any remaining
|
|
712
|
+
* term ranked by IDF-weighted coverage, `ts_rank_cd` (cover density) breaking ties (see
|
|
713
|
+
* {@link PgFullTextOptions.anyTermFallback}); explicit syntax (quoted phrases, `-exclusions`) goes
|
|
714
|
+
* through `websearch_to_tsquery` first.
|
|
675
715
|
*
|
|
676
716
|
* The `tsvector` is either a column you own ({@link PgFullTextOptions.column}) or the expression
|
|
677
717
|
* `to_tsvector(config, text)`, for which {@link PgVectorStore.schemaStatements} adds a GIN
|
|
@@ -682,12 +722,21 @@ declare class PgLexicalVectorStore extends PgVectorStore implements LexicalVecto
|
|
|
682
722
|
private readonly tsConfig;
|
|
683
723
|
private readonly tsColumn;
|
|
684
724
|
private readonly anyTermFallback;
|
|
725
|
+
private readonly stopWords;
|
|
685
726
|
constructor(client: PgClientSource, options?: PgVectorStoreOptions & {
|
|
686
727
|
fullText?: PgFullTextOptions;
|
|
687
728
|
});
|
|
688
729
|
schemaStatements(): string[];
|
|
689
730
|
searchText(query: string, options: VectorSearchOptions): Promise<Passage[]>;
|
|
731
|
+
/** `websearch_to_tsquery` on the query as written: every word, quoted phrases, `-exclusions`. */
|
|
690
732
|
private runTextSearch;
|
|
733
|
+
/**
|
|
734
|
+
* Rows holding any of `terms`, ranked by the sum of the matched terms' IDF among those rows,
|
|
735
|
+
* `ln(1 + (N - df + 0.5) / (df + 0.5))`, then `ts_rank_cd`. Each (row, term) match is evaluated
|
|
736
|
+
* once, and `ts_rank_cd` only for the rows ranked within `topK` (ties included): a question full
|
|
737
|
+
* of common words can match most of the table.
|
|
738
|
+
*/
|
|
739
|
+
private runRankedTermSearch;
|
|
691
740
|
/** Must be character-for-character the index expression, or the planner won't use the index. */
|
|
692
741
|
private tsvector;
|
|
693
742
|
}
|
|
@@ -1476,4 +1525,4 @@ declare class HttpReranker implements Reranker {
|
|
|
1476
1525
|
rerank(query: string, passages: Passage[], options?: RerankOptions): Promise<Passage[]>;
|
|
1477
1526
|
}
|
|
1478
1527
|
|
|
1479
|
-
export { type ChunkOptions, type ChunkRecord, type DescribesRetrieval, type DrizzleLike, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HttpModelError, HttpReranker, type HttpRerankerOptions, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, type InstrumentRetrieverOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, type ListChunksOptions, MemoryVectorStore, type MetadataPatch, type NodePgQueryable, type OpenAiEmbeddingsOptions, type PgClient, type PgClientSource, type PgFullTextOptions, PgLexicalVectorStore, PgVectorStore, type PgVectorStoreOptions, type PostgresJsSql, RAG_DIAGNOSTIC_LIB, RAG_RETRIEVAL_CHANNEL, RAG_RETRIEVAL_EVENT, type RagRetrievalEvent, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalDescriptor, type RetrievalTool, type RetrievalToolOptions, type RetrieverKind, type StoredChunk, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, type VectorStoreKind, applyMetadataPatch, chunkDocuments, chunkIndexOf, chunkText, createRetrievalTool, describeRetrieval, describeSharedSource, describeSource, describesRetrieval, documentIdOf, emitRagRetrieval, ingestChunks, ingestDocuments, instrumentRetriever, isLexicalVectorStore, openAiEmbeddings, toPgClient };
|
|
1528
|
+
export { type ChunkOptions, type ChunkRecord, DEFAULT_STOP_WORDS, type DescribesRetrieval, type DrizzleLike, EmbeddingRetriever, type EmbeddingRetrieverOptions, FilteredRetriever, HttpModelError, HttpReranker, type HttpRerankerOptions, HybridRetriever, type HybridRetrieverOptions, type IndexedDocument, type IngestChunksOptions, type IngestDocument, type IngestOptions, type InstrumentRetrieverOptions, KeywordRetriever, type KeywordRetrieverOptions, LexicalRetriever, type LexicalVectorStore, type ListChunksOptions, MemoryVectorStore, type MetadataPatch, type NodePgQueryable, type OpenAiEmbeddingsOptions, type PgClient, type PgClientSource, type PgFullTextOptions, PgLexicalVectorStore, PgVectorStore, type PgVectorStoreOptions, type PostgresJsSql, RAG_DIAGNOSTIC_LIB, RAG_RETRIEVAL_CHANNEL, RAG_RETRIEVAL_EVENT, type RagRetrievalEvent, type RedisSearchClient, RedisVectorSchemaMismatchError, RedisVectorStore, type RedisVectorStoreOptions, RerankingRetriever, type RerankingRetrieverOptions, type RetrievalDescriptor, type RetrievalTool, type RetrievalToolOptions, type RetrieverKind, type StoredChunk, UnsafeRemovalError, type VectorRecord, type VectorSearchOptions, type VectorStore, type VectorStoreKind, anyTermTsquery, applyMetadataPatch, chunkDocuments, chunkIndexOf, chunkText, createRetrievalTool, describeRetrieval, describeSharedSource, describeSource, describesRetrieval, documentIdOf, emitRagRetrieval, hasSearchSyntax, ingestChunks, ingestDocuments, instrumentRetriever, isLexicalVectorStore, keywordTerms, openAiEmbeddings, toPgClient };
|
package/dist/index.js
CHANGED
|
@@ -356,6 +356,81 @@ 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
|
+
|
|
359
434
|
// src/pg-client.ts
|
|
360
435
|
var ADAPTED = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-rag/pg-client");
|
|
361
436
|
function toPgClient(source) {
|
|
@@ -814,6 +889,7 @@ var PgLexicalVectorStore = class extends PgVectorStore {
|
|
|
814
889
|
tsConfig;
|
|
815
890
|
tsColumn;
|
|
816
891
|
anyTermFallback;
|
|
892
|
+
stopWords;
|
|
817
893
|
constructor(client, options = {}) {
|
|
818
894
|
super(client, options);
|
|
819
895
|
const config = options.fullText?.config ?? "simple";
|
|
@@ -827,6 +903,8 @@ var PgLexicalVectorStore = class extends PgVectorStore {
|
|
|
827
903
|
this.tsConfig = config;
|
|
828
904
|
this.tsColumn = column;
|
|
829
905
|
this.anyTermFallback = options.fullText?.anyTermFallback ?? true;
|
|
906
|
+
const stopWords = options.fullText?.stopWords;
|
|
907
|
+
this.stopWords = stopWords === false ? {} : stopWords ?? DEFAULT_STOP_WORDS;
|
|
830
908
|
}
|
|
831
909
|
schemaStatements() {
|
|
832
910
|
const statements = super.schemaStatements();
|
|
@@ -840,19 +918,18 @@ var PgLexicalVectorStore = class extends PgVectorStore {
|
|
|
840
918
|
if (filterMatchesNothing(options.filter) || query.trim() === "") {
|
|
841
919
|
return [];
|
|
842
920
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
...new Set(query.toLowerCase().match(/[\p{L}\p{N}]{2,}/gu) ?? [])
|
|
849
|
-
].slice(0, 24);
|
|
850
|
-
if (terms.length < 2) {
|
|
851
|
-
return all;
|
|
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
|
+
}
|
|
852
926
|
}
|
|
853
|
-
|
|
927
|
+
const terms = keywordTerms(query, 24, this.stopWords);
|
|
928
|
+
return terms.length === 0 ? [] : this.runRankedTermSearch(terms, options);
|
|
854
929
|
}
|
|
855
|
-
|
|
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)`;
|
|
856
933
|
const params = [
|
|
857
934
|
query,
|
|
858
935
|
options.topK
|
|
@@ -868,6 +945,47 @@ var PgLexicalVectorStore = class extends PgVectorStore {
|
|
|
868
945
|
LIMIT $2`, params);
|
|
869
946
|
return rows.map(toPassage);
|
|
870
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),
|
|
958
|
+
options.topK,
|
|
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
|
+
}
|
|
871
989
|
/** Must be character-for-character the index expression, or the planner won't use the index. */
|
|
872
990
|
tsvector() {
|
|
873
991
|
return this.tsColumn ?? `to_tsvector('${this.tsConfig}'::regconfig, text)`;
|
|
@@ -2488,6 +2606,7 @@ function itemsOf(body) {
|
|
|
2488
2606
|
}
|
|
2489
2607
|
__name(itemsOf, "itemsOf");
|
|
2490
2608
|
export {
|
|
2609
|
+
DEFAULT_STOP_WORDS,
|
|
2491
2610
|
EmbeddingRetriever,
|
|
2492
2611
|
FilteredRetriever,
|
|
2493
2612
|
HttpModelError,
|
|
@@ -2505,6 +2624,7 @@ export {
|
|
|
2505
2624
|
RedisVectorStore,
|
|
2506
2625
|
RerankingRetriever,
|
|
2507
2626
|
UnsafeRemovalError,
|
|
2627
|
+
anyTermTsquery,
|
|
2508
2628
|
applyMetadataPatch,
|
|
2509
2629
|
chunkDocuments,
|
|
2510
2630
|
chunkIndexOf,
|
|
@@ -2516,10 +2636,12 @@ export {
|
|
|
2516
2636
|
describesRetrieval,
|
|
2517
2637
|
documentIdOf,
|
|
2518
2638
|
emitRagRetrieval,
|
|
2639
|
+
hasSearchSyntax,
|
|
2519
2640
|
ingestChunks,
|
|
2520
2641
|
ingestDocuments,
|
|
2521
2642
|
instrumentRetriever,
|
|
2522
2643
|
isLexicalVectorStore,
|
|
2644
|
+
keywordTerms,
|
|
2523
2645
|
openAiEmbeddings,
|
|
2524
2646
|
toPgClient
|
|
2525
2647
|
};
|