@@ -2,9 +2,13 @@ import { QdrantClient } from "@qdrant/js-client-rest";
2
2
import type { Schemas as QdrantSchemas } from "@qdrant/js-client-rest" ;
3
3
import { v4 as uuid } from "uuid" ;
4
4
import type { EmbeddingsInterface } from "@langchain/core/embeddings" ;
5
- import { VectorStore } from "@langchain/core/vectorstores" ;
5
+ import {
6
+ type MaxMarginalRelevanceSearchOptions ,
7
+ VectorStore ,
8
+ } from "@langchain/core/vectorstores" ;
6
9
import { Document } from "@langchain/core/documents" ;
7
10
import { getEnvironmentVariable } from "@langchain/core/utils/env" ;
11
+ import { maximalMarginalRelevance } from "@langchain/core/utils/math" ;
8
12
9
13
const CONTENT_KEY = "content" ;
10
14
const METADATA_KEY = "metadata" ;
@@ -194,6 +198,8 @@ export class QdrantVectorStore extends VectorStore {
194
198
vector : query ,
195
199
limit : k ,
196
200
filter,
201
+ with_payload : [ this . metadataPayloadKey , this . contentPayloadKey ] ,
202
+ with_vector : false ,
197
203
} ) ;
198
204
199
205
const result : [ Document , number ] [ ] = (
@@ -210,6 +216,63 @@ export class QdrantVectorStore extends VectorStore {
210
216
return result ;
211
217
}
212
218
219
+ /**
220
+ * Return documents selected using the maximal marginal relevance.
221
+ * Maximal marginal relevance optimizes for similarity to the query AND diversity
222
+ * among selected documents.
223
+ *
224
+ * @param {string } query - Text to look up documents similar to.
225
+ * @param {number } options.k - Number of documents to return.
226
+ * @param {number } options.fetchK - Number of documents to fetch before passing to the MMR algorithm. Defaults to 20.
227
+ * @param {number } options.lambda - Number between 0 and 1 that determines the degree of diversity among the results,
228
+ * where 0 corresponds to maximum diversity and 1 to minimum diversity.
229
+ * @param {this["FilterType"] } options.filter - Optional filter to apply to the search results.
230
+ *
231
+ * @returns {Promise<Document[]> } - List of documents selected by maximal marginal relevance.
232
+ */
233
+ async maxMarginalRelevanceSearch (
234
+ query : string ,
235
+ options : MaxMarginalRelevanceSearchOptions < this[ "FilterType" ] >
236
+ ) : Promise < Document [ ] > {
237
+ if ( ! query ) {
238
+ return [ ] ;
239
+ }
240
+
241
+ const queryEmbedding = await this . embeddings . embedQuery ( query ) ;
242
+
243
+ await this . ensureCollection ( ) ;
244
+
245
+ const results = await this . client . search ( this . collectionName , {
246
+ vector : queryEmbedding ,
247
+ limit : options ?. fetchK ?? 20 ,
248
+ filter : options ?. filter ,
249
+ with_payload : [ this . metadataPayloadKey , this . contentPayloadKey ] ,
250
+ with_vector : true ,
251
+ } ) ;
252
+
253
+ const embeddingList = results . map ( ( res ) => res . vector ) as number [ ] [ ] ;
254
+
255
+ const mmrIndexes = maximalMarginalRelevance (
256
+ queryEmbedding ,
257
+ embeddingList ,
258
+ options ?. lambda ,
259
+ options . k
260
+ ) ;
261
+
262
+ const topMmrMatches = mmrIndexes . map ( ( idx ) => results [ idx ] ) ;
263
+
264
+ const result = ( topMmrMatches as QdrantSearchResponse [ ] ) . map (
265
+ ( res ) =>
266
+ new Document ( {
267
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
268
+ metadata : res . payload [ this . metadataPayloadKey ] as Record < string , any > ,
269
+ pageContent : res . payload [ this . contentPayloadKey ] as string ,
270
+ } )
271
+ ) ;
272
+
273
+ return result ;
274
+ }
275
+
213
276
/**
214
277
* Method to ensure the existence of a collection in the Qdrant database.
215
278
* If the collection does not exist, it is created.
0 commit comments