UNPKG

@langchain/community

Version:
1 lines • 22.5 kB
{"version":3,"file":"elasticsearch.cjs","names":["config: HybridRetrievalStrategyConfig","VectorStore","embeddings: EmbeddingsInterface","args: ElasticClientArgs","documents: Document[]","options?: { ids?: string[] }","vectors: number[][]","query: string","filter?: ElasticFilter","_callbacks?: Callbacks","query: number[]","k: number","hit: any","Document","queryText: string","queryVector: number[]","params: { ids: string[] }","texts: string[]","metadatas: object[] | object","docs: Document[]","dbConfig: ElasticClientArgs","dimension: number","request: estypes.IndicesCreateRequest","result: ElasticMetadataTerms"],"sources":["../../src/vectorstores/elasticsearch.ts"],"sourcesContent":["import * as uuid from \"uuid\";\nimport { Client, estypes } from \"@elastic/elasticsearch\";\nimport type { EmbeddingsInterface } from \"@langchain/core/embeddings\";\nimport { VectorStore } from \"@langchain/core/vectorstores\";\nimport { Document } from \"@langchain/core/documents\";\nimport type { Callbacks } from \"@langchain/core/callbacks/manager\";\n/**\n * Type representing the k-nearest neighbors (k-NN) engine used in\n * Elasticsearch.\n */\ntype ElasticKnnEngine = \"hnsw\";\n/**\n * Type representing the similarity measure used in Elasticsearch.\n */\ntype ElasticSimilarity = \"l2_norm\" | \"dot_product\" | \"cosine\";\n\n/**\n * Interface defining the options for vector search in Elasticsearch.\n */\ninterface VectorSearchOptions {\n readonly engine?: ElasticKnnEngine;\n readonly similarity?: ElasticSimilarity;\n readonly m?: number;\n readonly efConstruction?: number;\n readonly candidates?: number;\n}\n\n/**\n * Configuration options for hybrid retrieval strategy.\n */\nexport interface HybridRetrievalStrategyConfig {\n rankWindowSize?: number;\n rankConstant?: number;\n textField?: string;\n}\n\n/**\n * Hybrid search strategy combining vector and BM25 search using RRF.\n */\nexport class HybridRetrievalStrategy {\n public readonly rankWindowSize: number;\n public readonly rankConstant: number;\n public readonly textField: string;\n\n constructor(config: HybridRetrievalStrategyConfig = {}) {\n this.rankWindowSize = config.rankWindowSize ?? 100;\n this.rankConstant = config.rankConstant ?? 60;\n this.textField = config.textField ?? \"text\";\n }\n}\n\n/**\n * Interface defining the arguments required to create an Elasticsearch\n * client.\n */\nexport interface ElasticClientArgs {\n readonly client: Client;\n readonly indexName?: string;\n readonly vectorSearchOptions?: VectorSearchOptions;\n readonly strategy?: HybridRetrievalStrategy;\n}\n\n/**\n * Type representing a filter object in Elasticsearch.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ElasticFilter = object | { field: string; operator: string; value: any }[];\n\ntype ElasticMetadataTerms = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n must: { [operator: string]: { [field: string]: any } }[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n must_not: { [operator: string]: { [field: string]: any } }[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n should?: { [operator: string]: { [field: string]: any } }[];\n minimum_should_match?: number;\n};\n\n/**\n * Elasticsearch vector store supporting vector and hybrid search.\n *\n * Hybrid search combines kNN vector search with BM25 full-text search\n * using RRF. Enable by passing a `HybridRetrievalStrategy` to the constructor.\n *\n * @example\n * ```typescript\n * // Vector search (default)\n * const vectorStore = new ElasticVectorSearch(embeddings, { client, indexName });\n *\n * // Hybrid search\n * const hybridStore = new ElasticVectorSearch(embeddings, {\n * client,\n * indexName,\n * strategy: new HybridRetrievalStrategy()\n * });\n * ```\n */\nexport class ElasticVectorSearch extends VectorStore {\n declare FilterType: ElasticFilter;\n\n private readonly client: Client;\n\n private readonly indexName: string;\n\n private readonly engine: ElasticKnnEngine;\n\n private readonly similarity: ElasticSimilarity;\n\n private readonly efConstruction: number;\n\n private readonly m: number;\n\n private readonly candidates: number;\n\n private readonly strategy?: HybridRetrievalStrategy;\n\n private lastQueryText?: string;\n\n _vectorstoreType(): string {\n return \"elasticsearch\";\n }\n\n constructor(embeddings: EmbeddingsInterface, args: ElasticClientArgs) {\n super(embeddings, args);\n\n this.engine = args.vectorSearchOptions?.engine ?? \"hnsw\";\n this.similarity = args.vectorSearchOptions?.similarity ?? \"l2_norm\";\n this.m = args.vectorSearchOptions?.m ?? 16;\n this.efConstruction = args.vectorSearchOptions?.efConstruction ?? 100;\n this.candidates = args.vectorSearchOptions?.candidates ?? 200;\n this.strategy = args.strategy;\n\n const userAgent = this.strategy\n ? \"langchain-js-vs-hybrid/0.0.1\"\n : \"langchain-js-vs/0.0.1\";\n\n this.client = args.client.child({\n headers: { \"user-agent\": userAgent },\n });\n this.indexName = args.indexName ?? \"documents\";\n }\n\n /**\n * Method to add documents to the Elasticsearch database. It first\n * converts the documents to vectors using the embeddings, then adds the\n * vectors to the database.\n * @param documents The documents to add to the database.\n * @param options Optional parameter that can contain the IDs for the documents.\n * @returns A promise that resolves with the IDs of the added documents.\n */\n async addDocuments(documents: Document[], options?: { ids?: string[] }) {\n const texts = documents.map(({ pageContent }) => pageContent);\n return this.addVectors(\n await this.embeddings.embedDocuments(texts),\n documents,\n options\n );\n }\n\n /**\n * Method to add vectors to the Elasticsearch database. It ensures the\n * index exists, then adds the vectors and their corresponding documents\n * to the database.\n * @param vectors The vectors to add to the database.\n * @param documents The documents corresponding to the vectors.\n * @param options Optional parameter that can contain the IDs for the documents.\n * @returns A promise that resolves with the IDs of the added documents.\n */\n async addVectors(\n vectors: number[][],\n documents: Document[],\n options?: { ids?: string[] }\n ) {\n await this.ensureIndexExists(\n vectors[0].length,\n this.engine,\n this.similarity,\n this.efConstruction,\n this.m\n );\n const documentIds =\n options?.ids ?? Array.from({ length: vectors.length }, () => uuid.v4());\n const operations = vectors.flatMap((embedding, idx) => [\n {\n index: {\n _id: documentIds[idx],\n _index: this.indexName,\n },\n },\n {\n embedding,\n metadata: documents[idx].metadata,\n text: documents[idx].pageContent,\n },\n ]);\n const results = await this.client.bulk({ refresh: true, operations });\n if (results.errors) {\n const reasons = results.items.map(\n (result) => result.index?.error?.reason\n );\n throw new Error(`Failed to insert documents:\\n${reasons.join(\"\\n\")}`);\n }\n return documentIds;\n }\n\n async similaritySearch(\n query: string,\n k = 4,\n filter?: ElasticFilter,\n _callbacks?: Callbacks\n ): Promise<Document[]> {\n this.lastQueryText = query;\n return super.similaritySearch(query, k, filter, _callbacks);\n }\n\n /**\n * Method to perform a similarity search in the Elasticsearch database\n * using a vector. It returns the k most similar documents along with\n * their similarity scores.\n * @param query The query vector.\n * @param k The number of most similar documents to return.\n * @param filter Optional filter to apply to the search.\n * @returns A promise that resolves with an array of tuples, where each tuple contains a Document and its similarity score.\n */\n async similaritySearchVectorWithScore(\n query: number[],\n k: number,\n filter?: ElasticFilter\n ): Promise<[Document, number][]> {\n if (this.strategy && this.lastQueryText) {\n return this.hybridSearchVectorWithScore(\n this.lastQueryText,\n query,\n k,\n filter\n );\n }\n\n const result = await this.client.search({\n index: this.indexName,\n size: k,\n knn: {\n field: \"embedding\",\n query_vector: query,\n filter: { bool: this.buildMetadataTerms(filter) },\n k,\n num_candidates: this.candidates,\n },\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.hits.hits.map((hit: any) => [\n new Document({\n pageContent: hit._source.text,\n metadata: hit._source.metadata,\n }),\n hit._score,\n ]);\n }\n\n private async hybridSearchVectorWithScore(\n queryText: string,\n queryVector: number[],\n k: number,\n filter?: ElasticFilter\n ): Promise<[Document, number][]> {\n const metadataTerms = this.buildMetadataTerms(filter);\n const filterClauses =\n metadataTerms.must.length > 0 || metadataTerms.must_not.length > 0\n ? { bool: metadataTerms }\n : undefined;\n\n const result = await this.client.search({\n index: this.indexName,\n size: k,\n retriever: {\n rrf: {\n retrievers: [\n {\n standard: {\n query: {\n match: {\n [this.strategy!.textField]: queryText,\n },\n },\n },\n },\n {\n knn: {\n field: \"embedding\",\n query_vector: queryVector,\n k,\n num_candidates: this.candidates,\n },\n },\n ],\n rank_window_size: this.strategy!.rankWindowSize,\n rank_constant: this.strategy!.rankConstant,\n },\n },\n ...(filterClauses && { query: filterClauses }),\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.hits.hits.map((hit: any) => [\n new Document({\n pageContent: hit._source.text,\n metadata: hit._source.metadata,\n }),\n hit._score,\n ]);\n }\n\n /**\n * Method to delete documents from the Elasticsearch database.\n * @param params Object containing the IDs of the documents to delete.\n * @returns A promise that resolves when the deletion is complete.\n */\n async delete(params: { ids: string[] }): Promise<void> {\n const operations = params.ids.map((id) => ({\n delete: {\n _id: id,\n _index: this.indexName,\n },\n }));\n if (operations.length > 0)\n await this.client.bulk({ refresh: true, operations });\n }\n\n /**\n * Static method to create an ElasticVectorSearch instance from texts. It\n * creates Document instances from the texts and their corresponding\n * metadata, then calls the fromDocuments method to create the\n * ElasticVectorSearch instance.\n * @param texts The texts to create the ElasticVectorSearch instance from.\n * @param metadatas The metadata corresponding to the texts.\n * @param embeddings The embeddings to use for the documents.\n * @param args The arguments to create the Elasticsearch client.\n * @returns A promise that resolves with the created ElasticVectorSearch instance.\n */\n static fromTexts(\n texts: string[],\n metadatas: object[] | object,\n embeddings: EmbeddingsInterface,\n args: ElasticClientArgs\n ): Promise<ElasticVectorSearch> {\n const documents = texts.map((text, idx) => {\n const metadata = Array.isArray(metadatas) ? metadatas[idx] : metadatas;\n return new Document({ pageContent: text, metadata });\n });\n\n return ElasticVectorSearch.fromDocuments(documents, embeddings, args);\n }\n\n /**\n * Static method to create an ElasticVectorSearch instance from Document\n * instances. It adds the documents to the Elasticsearch database, then\n * returns the ElasticVectorSearch instance.\n * @param docs The Document instances to create the ElasticVectorSearch instance from.\n * @param embeddings The embeddings to use for the documents.\n * @param dbConfig The configuration for the Elasticsearch database.\n * @returns A promise that resolves with the created ElasticVectorSearch instance.\n */\n static async fromDocuments(\n docs: Document[],\n embeddings: EmbeddingsInterface,\n dbConfig: ElasticClientArgs\n ): Promise<ElasticVectorSearch> {\n const store = new ElasticVectorSearch(embeddings, dbConfig);\n await store.addDocuments(docs).then(() => store);\n return store;\n }\n\n /**\n * Static method to create an ElasticVectorSearch instance from an\n * existing index in the Elasticsearch database. It checks if the index\n * exists, then returns the ElasticVectorSearch instance if it does.\n * @param embeddings The embeddings to use for the documents.\n * @param dbConfig The configuration for the Elasticsearch database.\n * @returns A promise that resolves with the created ElasticVectorSearch instance if the index exists, otherwise it throws an error.\n */\n static async fromExistingIndex(\n embeddings: EmbeddingsInterface,\n dbConfig: ElasticClientArgs\n ): Promise<ElasticVectorSearch> {\n const store = new ElasticVectorSearch(embeddings, dbConfig);\n const exists = await store.doesIndexExist();\n if (exists) {\n return store;\n }\n throw new Error(`The index ${store.indexName} does not exist.`);\n }\n\n private async ensureIndexExists(\n dimension: number,\n engine = \"hnsw\",\n similarity = \"l2_norm\",\n efConstruction = 100,\n m = 16\n ): Promise<void> {\n const request: estypes.IndicesCreateRequest = {\n index: this.indexName,\n mappings: {\n dynamic_templates: [\n {\n // map all metadata properties to be keyword except loc\n metadata_except_loc: {\n match_mapping_type: \"*\",\n match: \"metadata.*\",\n unmatch: \"metadata.loc\",\n mapping: { type: \"keyword\" },\n },\n },\n ],\n properties: {\n text: { type: \"text\" },\n metadata: {\n type: \"object\",\n properties: {\n loc: { type: \"object\" }, // explicitly define loc as an object\n },\n },\n embedding: {\n type: \"dense_vector\",\n dims: dimension,\n index: true,\n similarity,\n index_options: {\n type: engine,\n m,\n ef_construction: efConstruction,\n },\n },\n },\n },\n };\n\n const indexExists = await this.doesIndexExist();\n if (indexExists) return;\n\n await this.client.indices.create(request);\n }\n\n private buildMetadataTerms(filter?: ElasticFilter): ElasticMetadataTerms {\n if (filter == null) return { must: [], must_not: [] };\n const filters = Array.isArray(filter)\n ? filter\n : Object.entries(filter).map(([key, value]) => ({\n operator: \"term\",\n field: key,\n value,\n }));\n\n const must = [];\n const must_not = [];\n const should = [];\n for (const condition of filters) {\n const metadataField = `metadata.${condition.field}`;\n if (condition.operator === \"exists\") {\n must.push({\n [condition.operator]: {\n field: metadataField,\n },\n });\n } else if (condition.operator === \"not_exists\") {\n must_not.push({\n exists: {\n field: metadataField,\n },\n });\n } else if (condition.operator === \"exclude\") {\n const toExclude = { [metadataField]: condition.value };\n must_not.push({\n ...(Array.isArray(condition.value)\n ? { terms: toExclude }\n : { term: toExclude }),\n });\n } else if (condition.operator === \"or\") {\n should.push({\n term: {\n [metadataField]: condition.value,\n },\n });\n } else {\n must.push({\n [condition.operator]: {\n [metadataField]: condition.value,\n },\n });\n }\n }\n const result: ElasticMetadataTerms = { must, must_not };\n\n if (should.length > 0) {\n result.should = should;\n result.minimum_should_match = 1;\n }\n return result;\n }\n\n /**\n * Method to check if an index exists in the Elasticsearch database.\n * @returns A promise that resolves with a boolean indicating whether the index exists.\n */\n async doesIndexExist(): Promise<boolean> {\n return await this.client.indices.exists({ index: this.indexName });\n }\n\n /**\n * Method to delete an index from the Elasticsearch database if it exists.\n * @returns A promise that resolves when the deletion is complete.\n */\n async deleteIfExists(): Promise<void> {\n const indexExists = await this.doesIndexExist();\n if (!indexExists) return;\n\n await this.client.indices.delete({ index: this.indexName });\n }\n}\n"],"mappings":"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"}