@@ -22,7 +22,7 @@ namespace Microsoft.SemanticKernel.Connectors.Weaviate;
22
22
/// </summary>
23
23
/// <typeparam name="TRecord">The data model to use for adding, updating and retrieving data from storage.</typeparam>
24
24
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
25
- public sealed class WeaviateVectorStoreRecordCollection < TRecord > : IVectorStoreRecordCollection < Guid , TRecord >
25
+ public sealed class WeaviateVectorStoreRecordCollection < TRecord > : IVectorStoreRecordCollection < Guid , TRecord > , IKeywordVectorizedHybridSearch < TRecord >
26
26
#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
27
27
{
28
28
/// <summary>The name of this database for telemetry purposes.</summary>
@@ -86,6 +86,9 @@ public sealed class WeaviateVectorStoreRecordCollection<TRecord> : IVectorStoreR
86
86
/// <summary>The default options for vector search.</summary>
87
87
private static readonly VectorSearchOptions s_defaultVectorSearchOptions = new ( ) ;
88
88
89
+ /// <summary>The default options for hybrid vector search.</summary>
90
+ private static readonly KeywordVectorizedHybridSearchOptions s_defaultKeywordVectorizedHybridSearchOptions = new ( ) ;
91
+
89
92
/// <summary><see cref="HttpClient"/> that is used to interact with Weaviate API.</summary>
90
93
private readonly HttpClient _httpClient ;
91
94
@@ -343,39 +346,63 @@ public async Task<VectorSearchResults<TRecord>> VectorizedSearchAsync<TVector>(
343
346
{
344
347
const string OperationName = "VectorSearch" ;
345
348
346
- Verify . NotNull ( vector ) ;
349
+ VerifyVectorParam ( vector ) ;
347
350
348
- var vectorType = vector . GetType ( ) ;
351
+ var searchOptions = options ?? s_defaultVectorSearchOptions ;
352
+ var vectorProperty = this . _propertyReader . GetVectorPropertyOrFirst ( searchOptions . VectorPropertyName ) ;
349
353
350
- if ( ! s_supportedVectorTypes . Contains ( vectorType ) )
351
- {
352
- throw new NotSupportedException (
353
- $ "The provided vector type { vectorType . FullName } is not supported by the Azure CosmosDB NoSQL connector. " +
354
- $ "Supported types are: { string . Join ( ", " , s_supportedVectorTypes . Select ( l => l . FullName ) ) } ") ;
355
- }
354
+ var vectorPropertyName = this . _propertyReader . GetJsonPropertyName ( vectorProperty . DataModelPropertyName ) ;
355
+ var fields = this . _propertyReader . DataPropertyJsonNames ;
356
356
357
- var searchOptions = options ?? s_defaultVectorSearchOptions ;
358
- var vectorProperty = this . GetVectorPropertyForSearch ( searchOptions . VectorPropertyName ) ;
357
+ var query = WeaviateVectorStoreRecordCollectionQueryBuilder . BuildSearchQuery (
358
+ vector ,
359
+ this . CollectionName ,
360
+ vectorPropertyName ,
361
+ this . _propertyReader . KeyPropertyName ,
362
+ s_jsonSerializerOptions ,
363
+ searchOptions ,
364
+ this . _propertyReader . JsonPropertyNamesMap ,
365
+ this . _propertyReader . VectorPropertyJsonNames ,
366
+ this . _propertyReader . DataPropertyJsonNames ) ;
359
367
360
- if ( vectorProperty is null )
361
- {
362
- throw new InvalidOperationException ( "The collection does not have any vector properties, so vector search is not possible." ) ;
363
- }
368
+ return await this . ExecuteQueryAsync ( query , searchOptions . IncludeVectors , WeaviateConstants . ScorePropertyName , OperationName , cancellationToken ) . ConfigureAwait ( false ) ;
369
+ }
370
+
371
+ /// <inheritdoc />
372
+ public async Task < VectorSearchResults < TRecord > > KeywordVectorizedHybridSearch < TVector > ( TVector vector , ICollection < string > keywords , KeywordVectorizedHybridSearchOptions ? options = null , CancellationToken cancellationToken = default )
373
+ {
374
+ const string OperationName = "HybridSearch" ;
375
+
376
+ VerifyVectorParam ( vector ) ;
377
+
378
+ var searchOptions = options ?? s_defaultKeywordVectorizedHybridSearchOptions ;
379
+ var vectorProperty = this . _propertyReader . GetVectorPropertyOrFirst ( searchOptions . VectorPropertyName ) ;
380
+ var textDataProperty = this . _propertyReader . GetFullTextDataPropertyOrSingle ( searchOptions . FullTextPropertyName ) ;
364
381
365
382
var vectorPropertyName = this . _propertyReader . GetJsonPropertyName ( vectorProperty . DataModelPropertyName ) ;
383
+ var textDataPropertyName = this . _propertyReader . GetJsonPropertyName ( textDataProperty . DataModelPropertyName ) ;
366
384
var fields = this . _propertyReader . DataPropertyJsonNames ;
367
385
368
- var query = WeaviateVectorStoreRecordCollectionQueryBuilder . BuildSearchQuery (
386
+ var query = WeaviateVectorStoreRecordCollectionQueryBuilder . BuildHybridSearchQuery (
369
387
vector ,
388
+ string . Join ( " " , keywords ) ,
370
389
this . CollectionName ,
371
390
vectorPropertyName ,
372
391
this . _propertyReader . KeyPropertyName ,
392
+ textDataPropertyName ,
373
393
s_jsonSerializerOptions ,
374
394
searchOptions ,
375
395
this . _propertyReader . JsonPropertyNamesMap ,
376
396
this . _propertyReader . VectorPropertyJsonNames ,
377
397
this . _propertyReader . DataPropertyJsonNames ) ;
378
398
399
+ return await this . ExecuteQueryAsync ( query , searchOptions . IncludeVectors , WeaviateConstants . HybridScorePropertyName , OperationName , cancellationToken ) . ConfigureAwait ( false ) ;
400
+ }
401
+
402
+ #region private
403
+
404
+ private async Task < VectorSearchResults < TRecord > > ExecuteQueryAsync ( string query , bool includeVectors , string scorePropertyName , string operationName , CancellationToken cancellationToken )
405
+ {
379
406
using var request = new WeaviateVectorSearchRequest ( query ) . Build ( ) ;
380
407
381
408
var ( responseModel , content ) = await this . ExecuteRequestWithResponseContentAsync < WeaviateVectorSearchResponse > ( request , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -388,28 +415,26 @@ public async Task<VectorSearchResults<TRecord>> VectorizedSearchAsync<TVector>(
388
415
{
389
416
VectorStoreType = DatabaseName ,
390
417
CollectionName = this . CollectionName ,
391
- OperationName = OperationName
418
+ OperationName = operationName
392
419
} ;
393
420
}
394
421
395
422
var mappedResults = collectionResults . Where ( x => x is not null ) . Select ( result =>
396
423
{
397
- var ( storageModel , score ) = WeaviateVectorStoreCollectionSearchMapping . MapSearchResult ( result ! ) ;
424
+ var ( storageModel , score ) = WeaviateVectorStoreCollectionSearchMapping . MapSearchResult ( result ! , scorePropertyName ) ;
398
425
399
426
var record = VectorStoreErrorHandler . RunModelConversion (
400
427
DatabaseName ,
401
428
this . CollectionName ,
402
- OperationName ,
403
- ( ) => this . _mapper . MapFromStorageToDataModel ( storageModel , new ( ) { IncludeVectors = searchOptions . IncludeVectors } ) ) ;
429
+ operationName ,
430
+ ( ) => this . _mapper . MapFromStorageToDataModel ( storageModel , new ( ) { IncludeVectors = includeVectors } ) ) ;
404
431
405
432
return new VectorSearchResult < TRecord > ( record , score ) ;
406
433
} ) ;
407
434
408
435
return new VectorSearchResults < TRecord > ( mappedResults . ToAsyncEnumerable ( ) ) ;
409
436
}
410
437
411
- #region private
412
-
413
438
private Task < HttpResponseMessage > ExecuteRequestAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
414
439
{
415
440
request . RequestUri = new Uri ( this . _endpoint , request . RequestUri ! ) ;
@@ -469,33 +494,6 @@ private async Task<T> RunOperationAsync<T>(string operationName, Func<Task<T>> o
469
494
}
470
495
}
471
496
472
- /// <summary>
473
- /// Get vector property to use for a search by using the storage name for the field name from options
474
- /// if available, and falling back to the first vector property in <typeparamref name="TRecord"/> if not.
475
- /// </summary>
476
- /// <param name="vectorFieldName">The vector field name.</param>
477
- /// <exception cref="InvalidOperationException">Thrown if the provided field name is not a valid field name.</exception>
478
- private VectorStoreRecordVectorProperty ? GetVectorPropertyForSearch ( string ? vectorFieldName )
479
- {
480
- // If vector property name is provided in options, try to find it in schema or throw an exception.
481
- if ( ! string . IsNullOrWhiteSpace ( vectorFieldName ) )
482
- {
483
- // Check vector properties by data model property name.
484
- var vectorProperty = this . _propertyReader . VectorProperties
485
- . FirstOrDefault ( l => l . DataModelPropertyName . Equals ( vectorFieldName , StringComparison . Ordinal ) ) ;
486
-
487
- if ( vectorProperty is not null )
488
- {
489
- return vectorProperty ;
490
- }
491
-
492
- throw new InvalidOperationException ( $ "The { typeof ( TRecord ) . FullName } type does not have a vector property named '{ vectorFieldName } '.") ;
493
- }
494
-
495
- // If vector property is not provided in options, return first vector property from schema.
496
- return this . _propertyReader . VectorProperty ;
497
- }
498
-
499
497
/// <summary>
500
498
/// Returns custom mapper, generic data model mapper or default record mapper.
501
499
/// </summary>
@@ -528,5 +526,19 @@ private IVectorStoreRecordMapper<TRecord, JsonObject> InitializeMapper()
528
526
s_jsonSerializerOptions ) ;
529
527
}
530
528
529
+ private static void VerifyVectorParam < TVector > ( TVector vector )
530
+ {
531
+ Verify . NotNull ( vector ) ;
532
+
533
+ var vectorType = vector . GetType ( ) ;
534
+
535
+ if ( ! s_supportedVectorTypes . Contains ( vectorType ) )
536
+ {
537
+ throw new NotSupportedException (
538
+ $ "The provided vector type { vectorType . FullName } is not supported by the Weaviate connector. " +
539
+ $ "Supported types are: { string . Join ( ", " , s_supportedVectorTypes . Select ( l => l . FullName ) ) } ") ;
540
+ }
541
+ }
542
+
531
543
#endregion
532
544
}
0 commit comments