Skip to content
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion libs/langchain-community/src/vectorstores/elasticsearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Client, estypes } from "@elastic/elasticsearch";
import type { EmbeddingsInterface } from "@langchain/core/embeddings";
import { VectorStore } from "@langchain/core/vectorstores";
import { Document } from "@langchain/core/documents";
import type { Callbacks } from "@langchain/core/callbacks/manager";
/**
* Type representing the k-nearest neighbors (k-NN) engine used in
* Elasticsearch.
Expand All @@ -24,6 +25,36 @@ interface VectorSearchOptions {
readonly candidates?: number;
}

/**
* Configuration options for hybrid retrieval strategy.
*/
export interface HybridRetrievalStrategyConfig {
rankWindowSize?: number;
rankConstant?: number;
textField?: string;
/**
* For Elasticsearch 9.x, set to `false` to include vectors in responses.
*/
excludeSourceVectors?: boolean;
}

/**
* Hybrid search strategy combining vector and BM25 search using RRF.
*/
export class HybridRetrievalStrategy {
public readonly rankWindowSize: number;
public readonly rankConstant: number;
public readonly textField: string;
public readonly excludeSourceVectors?: boolean;

constructor(config: HybridRetrievalStrategyConfig = {}) {
this.rankWindowSize = config.rankWindowSize ?? 100;
this.rankConstant = config.rankConstant ?? 60;
this.textField = config.textField ?? "text";
this.excludeSourceVectors = config.excludeSourceVectors;
}
}

/**
* Interface defining the arguments required to create an Elasticsearch
* client.
Expand All @@ -32,6 +63,7 @@ export interface ElasticClientArgs {
readonly client: Client;
readonly indexName?: string;
readonly vectorSearchOptions?: VectorSearchOptions;
readonly strategy?: HybridRetrievalStrategy;
}

/**
Expand Down Expand Up @@ -73,6 +105,10 @@ export class ElasticVectorSearch extends VectorStore {

private readonly candidates: number;

private readonly strategy?: HybridRetrievalStrategy;

private lastQueryText?: string;

_vectorstoreType(): string {
return "elasticsearch";
}
Expand All @@ -85,9 +121,14 @@ export class ElasticVectorSearch extends VectorStore {
this.m = args.vectorSearchOptions?.m ?? 16;
this.efConstruction = args.vectorSearchOptions?.efConstruction ?? 100;
this.candidates = args.vectorSearchOptions?.candidates ?? 200;
this.strategy = args.strategy;

const userAgent = this.strategy
? "langchain-js-vs-hybrid/0.0.1"
: "langchain-js-vs/0.0.1";

this.client = args.client.child({
headers: { "user-agent": "langchain-js-vs/0.0.1" },
headers: { "user-agent": userAgent },
});
this.indexName = args.indexName ?? "documents";
}
Expand Down Expand Up @@ -155,6 +196,16 @@ export class ElasticVectorSearch extends VectorStore {
return documentIds;
}

async similaritySearch(
Copy link
Author

@margaretjgu margaretjgu Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default value inferred from the VectorStore class that we extend from

async similaritySearch(
query: string,
k = 4,
filter: this["FilterType"] | undefined = undefined,
_callbacks: Callbacks | undefined = undefined // implement passing to embedQuery later
): Promise<DocumentInterface[]> {
const results = await this.similaritySearchVectorWithScore(
await this.embeddings.embedQuery(query),
k,
filter
);

query: string,
k = 4,
filter?: ElasticFilter,
_callbacks?: Callbacks
): Promise<Document[]> {
this.lastQueryText = query;
return super.similaritySearch(query, k, filter, _callbacks);
}

/**
* Method to perform a similarity search in the Elasticsearch database
* using a vector. It returns the k most similar documents along with
Expand Down Expand Up @@ -315,6 +366,12 @@ export class ElasticVectorSearch extends VectorStore {
},
};

if (this.strategy?.excludeSourceVectors !== undefined) {
request.settings = {
"index.mapping.exclude_source_vectors": this.strategy.excludeSourceVectors,
};
}

const indexExists = await this.doesIndexExist();
if (indexExists) return;

Expand Down