diff --git a/.gitignore b/.gitignore index 27c374c0..e0d117d5 100644 --- a/.gitignore +++ b/.gitignore @@ -484,3 +484,5 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp + +.serena diff --git a/docs/VECTOR_API_CHANGELOG.md b/docs/VECTOR_API_CHANGELOG.md new file mode 100644 index 00000000..372295f4 --- /dev/null +++ b/docs/VECTOR_API_CHANGELOG.md @@ -0,0 +1,380 @@ +# Vector Search API Changelog + +## Version TBD - API Consolidation + +### Summary + +Consolidated vector search API from 35+ overloads to ~20 core overloads with extensive implicit conversions. + +### Changes + +#### New Types + +- `VectorSearchInput` - Central type for all vector search inputs with extensive implicit conversions +- `VectorSearchInput.Builder` - Lambda builder for complex multi-target scenarios +- `VectorSearchInput.FactoryFn` - Delegate for creating VectorSearchInput via lambda expressions with implicit conversion +- `HybridVectorInput` - Discriminated union for hybrid search vector inputs (VectorSearchInput, NearTextInput, or NearVectorInput) +- `HybridVectorInput.FactoryFn` - Delegate for creating HybridVectorInput via lambda builder with `.NearVector()` or `.NearText()` methods +- `NearVectorInput` - Wrapper for vector input with optional thresholds (replaces `HybridNearVector`) +- `NearVectorInput.FactoryFn` - Delegate for creating NearVectorInput via lambda builder with target vector configuration +- `NearTextInput` - Server-side vectorization with target vectors (replaces `HybridNearText`) +- `NearTextInput.FactoryFn` - Delegate for creating NearTextInput via lambda builder with target vector configuration +- `TargetVectors` - Static factory methods for target vector configuration +- `TargetVectorsBuilder` - Lambda builder for target vectors + +#### Removed Types + +- `IHybridVectorInput` - Marker interface removed in favor of `HybridVectorInput` +- `INearVectorInput` - Marker interface removed +- `HybridNearVector` - Renamed to `NearVectorInput` +- `HybridNearText` - Renamed to `NearTextInput` + +#### Overload Reduction + +| Client | Method | Before | After | +|--------|--------|--------|-------| +| QueryClient | NearVector | 10+ | 4 | +| QueryClient | NearText | 2 | 4 | +| QueryClient | NearMedia | 2 | 2 | +| QueryClient | Hybrid | 4+ | 9 | +| GenerateClient | NearVector | 6+ | 4 | +| GenerateClient | NearText | 2 | 4 | +| GenerateClient | NearMedia | 2 | 2 | +| GenerateClient | Hybrid | 4+ | 8 | +| AggregateClient | NearVector | 6+ | 4 | +| AggregateClient | NearText | 2 | 4 | +| AggregateClient | NearMedia | 2 | 2 | +| AggregateClient | Hybrid | 3+ | 4 | +| TypedQueryClient | NearText | 2 | 4 | +| TypedQueryClient | NearMedia | 2 | 2 | +| TypedGenerateClient | NearText | 2 | 4 | +| TypedGenerateClient | NearMedia | 2 | 2 | + +#### API Changes + +1. **HybridVectorInput discriminated union for vectors parameter** + - Before: `Hybrid(string? query, IHybridVectorInput? vectors, ...)` + - After: `Hybrid(string? query, HybridVectorInput? vectors, ...)` - accepts VectorSearchInput, NearTextInput, or NearVectorInput via implicit conversions + +2. **VectorSearchInput replaces multiple input types** + - Before: Separate overloads for `float[]`, `Vector`, `Vectors`, tuple enumerables + - After: Single `VectorSearchInput` with implicit conversions from all types + +3. **Builder replaces separate overloads** + - Before: `VectorSearchInputBuilder` as standalone class + - After: `VectorSearchInput.Builder` as nested class + +4. **FactoryFn delegate enables lambda syntax** + - New: `VectorSearchInput.FactoryFn` delegate with implicit conversion to `VectorSearchInput` + - Allows: `vectors: b => b.TargetVectorsSum(("title", vec1), ("desc", vec2))` syntax + +5. **Targets embedded in VectorSearchInput** + - Before: Separate `targets` parameter on some methods + - After: Targets configured via `VectorSearchInput.Builder` methods (Sum, ManualWeights, etc.) + +6. **TargetVectors uses static factory methods** + - Before: `new SimpleTargetVectors(["title", "description"])` + - After: `TargetVectors.Sum("title", "description")` or `new[] { "title", "description" }` (implicit) + +7. **NearTextInput includes TargetVectors** + - Before: Separate `targetVector` parameter on Hybrid methods + - After: `NearTextInput.TargetVectors` property is single source of truth + +8. **Convenience overloads for text-only Hybrid search** + - New: Overloads without `vectors` parameter for all Hybrid methods + - These delegate to main Hybrid method with `vectors: null` + - Simplifies pure text search: `Hybrid("query")` instead of `Hybrid("query", vectors: null)` + +9. **Convenience overloads for NearText target vectors** + - New: Overloads accepting `TargetVectors?` for all NearText methods (QueryClient, GenerateClient, AggregateClient, TypedQueryClient, TypedGenerateClient) + - Allows passing string arrays directly: `targets: new[] { "vec1", "vec2" }` + - Allows static factory methods: `targetVectors: TargetVectors.Sum("vec1", "vec2")` + - Lambda builder syntax still available: `targetVectors: tv => tv.TargetVectorsSum("vec1", "vec2")` + - Matches pattern already established by NearVector methods with VectorSearchInput + +10. **NearVectorInput FactoryFn constructor for consistency** + - New: Constructor accepting `VectorSearchInput.FactoryFn` for lambda builder syntax + - Enables: `new NearVectorInput(v => v.TargetVectorsSum(("title", vec1), ("desc", vec2)))` + - Matches pattern established by NearTextInput + +11. **HybridVectorInput lambda builder for unified target vector syntax** + - New: `HybridVectorInput.FactoryFn` delegate enables lambda builder pattern for Hybrid search + - Syntax: `v => v.NearVector(certainty: 0.8).TargetVectorsManualWeights(("title", 1.2, vec1), ("desc", 0.8, vec2))` + - Syntax: `v => v.NearText(["query"]).TargetVectorsManualWeights(("title", 1.2), ("desc", 0.8))` + - Eliminates need to construct `NearVectorInput` or `NearTextInput` explicitly + - Available across all clients: QueryClient, GenerateClient, AggregateClient, TypedQueryClient + - Unifies target vector configuration directly within the Hybrid method call + +12. **NearMedia lambda builder pattern for unified media search** + - **BREAKING CHANGE**: Old NearImage and NearMedia methods removed entirely + - New: Single `NearMedia` method using lambda builder pattern with `NearMediaInput.FactoryFn` + - Syntax: `m => m.Image(imageBytes).TargetVectorsSum("title", "description")` + - Syntax: `m => m.Video(videoBytes, certainty: 0.8f).TargetVectorsManualWeights(("visual", 1.2), ("audio", 0.8))` + - Supports all media types: Image, Video, Audio, Thermal, Depth, IMU + - Optional target vectors via implicit conversion: `m => m.Image(imageBytes)` works without `.Build()` + - Available across all clients: QueryClient, GenerateClient, AggregateClient, TypedQueryClient, TypedGenerateClient + - Certainty and distance configured in builder (not method parameters) for consistency with NearText/NearVector patterns + +#### Migration from Old NearMedia API + +**Simple image search:** + +```csharp +// Before (removed) +await collection.Query.NearImage(imageBytes); + +// After (required) +await collection.Query.NearMedia(m => m.Image(imageBytes)); +``` + +**With certainty and target vectors:** + +```csharp +// Before (removed) +await collection.Query.NearImage(imageBytes, certainty: 0.8, targetVectors: t => t.TargetVectorsSum("v1", "v2")); + +// After (required) +await collection.Query.NearMedia(m => m.Image(imageBytes, certainty: 0.8f).TargetVectorsSum("v1", "v2")); +``` + +**Media type specification:** + +```csharp +// Before (removed) +await collection.Query.NearMedia(videoBytes, NearMediaType.Video, distance: 0.3); + +// After (required) +await collection.Query.NearMedia(m => m.Video(videoBytes, distance: 0.3f)); +``` + +**All media types:** + +```csharp +// All supported via unified lambda builder pattern +await collection.Query.NearMedia(m => m.Image(imageBytes)); +await collection.Query.NearMedia(m => m.Video(videoBytes)); +await collection.Query.NearMedia(m => m.Audio(audioBytes)); +await collection.Query.NearMedia(m => m.Thermal(thermalBytes)); +await collection.Query.NearMedia(m => m.Depth(depthBytes)); +await collection.Query.NearMedia(m => m.IMU(imuBytes)); +``` + +### Migration Examples + + +**Simple hybrid search:** + +```csharp +// Before +await collection.Query.Hybrid("search query"); + +// After (implicit conversion from string) +await collection.Query.Hybrid("search query"); +``` + +**Hybrid with vectors:** + +```csharp +// Before +await collection.Query.Hybrid("search query", vectors: new[] { 1f, 2f, 3f }); + +// After (tuple implicit conversion) +await collection.Query.Hybrid(("search query", new[] { 1f, 2f, 3f })); +``` + +**Hybrid with target vectors:** + +```csharp +// Before +await collection.Query.Hybrid( + "search query", + vectors: new HybridNearText("banana"), + targetVector: ["title", "description"] // separate parameter +); + +// After (targets inside NearTextInput) +await collection.Query.Hybrid( + new NearTextInput( + "banana", + TargetVectors: TargetVectors.Sum("title", "description") + ) +); + +// Or with implicit string array conversion +await collection.Query.Hybrid( + new NearTextInput( + "banana", + TargetVectors: new[] { "title", "description" } + ) +); +``` + +**Lambda builder for vectors:** + +```csharp +await collection.Query.Hybrid( + "search query", + v => v.TargetVectorsSum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ) +); +``` + +**Target vectors with weights:** + +```csharp +// Use static factory methods +var targetVectors = TargetVectors.ManualWeights(("title", 1.2), ("desc", 0.8)); +await collection.Query.Hybrid( + new NearTextInput("banana", TargetVectors: targets) +); + +// Or with RelativeScore +var targetVectors = TargetVectors.RelativeScore(("title", 0.7), ("desc", 0.3)); +``` + +**NearText with target vectors:** + +```csharp +// Before: lambda builder required +await collection.Query.NearText( + "search query", + targetVectors: tv => tv.TargetVectorsSum("title", "description") +); + +// After: multiple options available +// Option 1: String array (simplest) +await collection.Query.NearText( + "search query", + targets: new[] { "title", "description" } +); + +// Option 2: Static factory method +await collection.Query.NearText( + "search query", + targetVectors: TargetVectors.Sum("title", "description") +); + +// Option 3: Lambda builder (still works) +await collection.Query.NearText( + "search query", + targetVectors: tv => tv.TargetVectorsSum("title", "description") +); +``` + +**Simple vector search (no changes needed):** + +```csharp +// Works unchanged via implicit conversion +await collection.Query.NearVector(new[] { 1f, 2f, 3f }); +``` + +**NearVectorInput with lambda builder:** + +```csharp +// Before: only accepts VectorSearchInput directly +new NearVectorInput(new[] { 1f, 2f, 3f }); + +// After: also accepts lambda builder +new NearVectorInput( + v => v.TargetVectorsSum(("title", vec1), ("desc", vec2)), + Certainty: 0.8f +); +``` + +**Named vector with target:** + +```csharp +// Before +await collection.Query.NearVector(new[] { 1f, 2f, 3f }, targetVector: "title"); + +// After +await collection.Query.NearVector(v => v.Single("title", new[] { 1f, 2f, 3f })); +``` + +**Multi-target with weights:** + +```csharp +await collection.Query.NearVector( + v => v.TargetVectorsManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("desc", 0.8, new[] { 3f, 4f }) + ) +); +``` + +**Hybrid with NearVector and target vectors (NEW unified syntax):** + +```csharp +// Before: construct NearVectorInput explicitly +await collection.Query.Hybrid( + "test", + new NearVectorInput( + VectorSearchInput.Combine( + TargetVectors.ManualWeights(("title", 1.2), ("desc", 0.8)), + ("title", new[] { 1f, 2f }), + ("desc", new[] { 3f, 4f }) + ), + Certainty: 0.8f + ) +); + +// After: lambda builder unifies configuration +await collection.Query.Hybrid( + "test", + v => v.NearVector(certainty: 0.8f) + .ManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("desc", 0.8, new[] { 3f, 4f }) + ) +); +``` + +**Hybrid with NearText and target vectors (NEW unified syntax):** + +```csharp +// Before: construct NearTextInput explicitly +await collection.Query.Hybrid( + "test", + new NearTextInput( + ["concept1", "concept2"], + TargetVectors: TargetVectors.ManualWeights(("title", 1.2), ("desc", 0.8)) + ) +); + +// After: lambda builder unifies configuration +await collection.Query.Hybrid( + "test", + v => v.NearText(["concept1", "concept2"]) + .ManualWeights(("title", 1.2), ("desc", 0.8)) +); +``` + +**Hybrid with NearText including Move parameters:** + +```csharp +await collection.Query.Hybrid( + "test", + v => v.NearText( + ["concept"], + certainty: 0.7f, + moveTo: new Move("positive", 0.5f), + moveAway: new Move("negative", 0.3f) + ) + .Sum("title", "description") +); +``` + +### Breaking Changes + +- `IHybridVectorInput` and `INearVectorInput` interfaces removed +- `HybridNearVector` renamed to `NearVectorInput` +- `HybridNearText` renamed to `NearTextInput` +- `Hybrid` method `vectors` parameter now uses `HybridVectorInput?` discriminated union type +- `SimpleTargetVectors` and `WeightedTargetVectors` constructors are now internal +- `VectorSearchInput.Builder` constructor is now internal (use `FactoryFn` lambda syntax instead) +- `targetVector` parameter removed from Hybrid methods - use `NearTextInput.TargetVectors` instead +- Removed tuple enumerable overloads (`IEnumerable<(string, Vector)>`) +- `targetVector` string parameter removed from NearVector methods (use builder instead) +- Overload resolution may change in edge cases with implicit conversions diff --git a/docs/VECTOR_API_OVERVIEW.md b/docs/VECTOR_API_OVERVIEW.md new file mode 100644 index 00000000..e2406864 --- /dev/null +++ b/docs/VECTOR_API_OVERVIEW.md @@ -0,0 +1,861 @@ +# Vector Search API Reference + +Reference documentation for the vector search API in the Weaviate C# client. + +## API Overview + +| Client | Method | Description | +|-----------------|------------|----------------------------------------------| +| QueryClient | NearVector | Vector similarity search | +| QueryClient | NearText | Text-based semantic search | +| QueryClient | NearMedia | Media-based search (Image/Video/Audio/etc) | +| QueryClient | Hybrid | Combined keyword + vector search | +| GenerateClient | NearVector | Vector search with generative AI | +| GenerateClient | NearText | Text search with generative AI | +| GenerateClient | NearMedia | Media search with generative AI | +| GenerateClient | Hybrid | Hybrid search with generative AI | +| AggregateClient | NearVector | Vector search with aggregation | +| AggregateClient | NearText | Text search with aggregation | +| AggregateClient | NearMedia | Media search with aggregation | +| AggregateClient | Hybrid | Hybrid search with aggregation | + +--- + +## QueryClient.NearVector + +Vector similarity search using vector embeddings. + +### Method Signatures + +```csharp +// Direct vector input +Task NearVector( + VectorSearchInput vectors, + ... +) + +// With GroupBy +Task NearVector( + VectorSearchInput vectors, + GroupByRequest groupBy, + ... +) + +// Lambda builder +Task NearVector( + Func vectorsBuilder, + ... +) + +// Lambda builder with GroupBy +Task NearVector( + Func vectorsBuilder, + GroupByRequest groupBy, + ... +) +``` + +### Examples + +```csharp +// Simple float array (implicit conversion) +await collection.Query.NearVector(new[] { 1f, 2f, 3f }); + +// Named vectors for multi-vector collections +await collection.Query.NearVector( + new Vectors { + { "title", new[] { 1f, 2f } }, + { "description", new[] { 3f, 4f } } + } +); + +// Lambda builder - Sum combination +await collection.Query.NearVector( + v => v.TargetVectorsSum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ) +); + +// Lambda builder - ManualWeights +await collection.Query.NearVector( + v => v.TargetVectorsManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("description", 0.8, new[] { 3f, 4f }) + ) +); + +// Lambda builder - Average +await collection.Query.NearVector( + v => v.TargetVectorsAverage( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ) +); + +// Lambda builder - Minimum +await collection.Query.NearVector( + v => v.TargetVectorsMinimum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ) +); + +// Lambda builder - RelativeScore +await collection.Query.NearVector( + v => v.TargetVectorsRelativeScore( + ("title", 0.7, new[] { 1f, 2f }), + ("description", 0.3, new[] { 3f, 4f }) + ) +); + +// Multi-vector (ColBERT-style) +await collection.Query.NearVector( + ("colbert", new[,] { + { 1f, 2f }, + { 3f, 4f } + }) +); + +// With certainty threshold +await collection.Query.NearVector( + new[] { 1f, 2f, 3f }, + certainty: 0.7f +); + +// With distance threshold +await collection.Query.NearVector( + new[] { 1f, 2f, 3f }, + distance: 0.3f +); + +// With GroupBy +await collection.Query.NearVector( + new[] { 1f, 2f, 3f }, + new GroupByRequest("category", objectsPerGroup: 3) +); +``` + +--- + +## QueryClient.NearText + +Text-based semantic search with server-side vectorization. + +### Method Signatures + +```csharp +// Simple text search +Task NearText( + AutoArray text, + ... +) + +// With GroupBy +Task NearText( + AutoArray text, + GroupByRequest groupBy, + ... +) + +// Lambda builder for NearTextInput +Task NearText( + NearTextInput.FactoryFn query, + ... +) + +// Lambda builder with GroupBy +Task NearText( + NearTextInput.FactoryFn query, + GroupByRequest groupBy, + ... +) + +// Extension: NearTextInput record +Task NearText( + NearTextInput input, + ... +) +``` + +### Examples + +```csharp +// Simple text search +await collection.Query.NearText("banana"); + +// Multiple search terms +await collection.Query.NearText(new[] { "banana", "tropical", "fruit" }); + +// With certainty +await collection.Query.NearText( + "banana", + certainty: 0.7f +); + +// With move parameters +await collection.Query.NearText( + "banana", + moveTo: new Move("fruit", 0.5f), + moveAway: new Move("vegetable", 0.3f) +); + +// Lambda builder - with target vectors (Sum) +await collection.Query.NearText( + q => q(["banana"], certainty: 0.7f) + .TargetVectorsSum("title", "description") +); + +// Lambda builder - with target vectors (Average) +await collection.Query.NearText( + q => q(["tropical", "fruit"]) + .TargetVectorsAverage("title", "description", "category") +); + +// Lambda builder - with target vectors (ManualWeights) +await collection.Query.NearText( + q => q(["search query"]) + .TargetVectorsManualWeights(("title", 0.8), ("description", 0.2)) +); + +// Lambda builder - with move parameters and targets +await collection.Query.NearText( + q => q(["banana"], moveTo: new Move("fruit", 0.5f)) + .TargetVectorsSum("title", "description") +); + +// Using NearTextInput record +await collection.Query.NearText( + new NearTextInput( + "banana", + TargetVectors: TargetVectors.Sum("title", "description"), + Certainty: 0.7f + ) +); + +// With GroupBy +await collection.Query.NearText( + "banana", + new GroupByRequest("category", objectsPerGroup: 3) +); + +// Lambda builder with GroupBy +await collection.Query.NearText( + q => q(["banana"]).TargetVectorsSum("title", "description"), + groupBy: new GroupByRequest("category", objectsPerGroup: 3) +); +``` + +--- + +## QueryClient.NearMedia + +Media-based semantic search supporting Image, Video, Audio, Thermal, Depth, and IMU data. + +### Method Signatures + +```csharp +// Lambda builder (recommended) +Task NearMedia( + NearMediaInput.FactoryFn query, + ... +) + +// Lambda builder with GroupBy (recommended) +Task NearMedia( + NearMediaInput.FactoryFn query, + GroupByRequest groupBy, + ... +) + +// Direct byte[] input (alternative for non-lambda preference) +Task NearMedia( + byte[] media, + NearMediaType mediaType, + double? certainty = null, + double? distance = null, + TargetVectors.FactoryFn? targets = null, + ... +) + +// Direct byte[] input with GroupBy (alternative) +Task NearMedia( + byte[] media, + NearMediaType mediaType, + GroupByRequest groupBy, + double? certainty = null, + double? distance = null, + TargetVectors.FactoryFn? targets = null, + ... +) +``` + +### Examples + +#### Lambda Builder Approach (Recommended) + +```csharp +// Simple image search (no target vectors) +await collection.Query.NearMedia(m => m.Image(imageBytes)); + +// Video search +await collection.Query.NearMedia(m => m.Video(videoBytes)); + +// Audio search +await collection.Query.NearMedia(m => m.Audio(audioBytes)); + +// Thermal search +await collection.Query.NearMedia(m => m.Thermal(thermalBytes)); + +// Depth search +await collection.Query.NearMedia(m => m.Depth(depthBytes)); + +// IMU search +await collection.Query.NearMedia(m => m.IMU(imuBytes)); + +// With certainty +await collection.Query.NearMedia(m => m.Image(imageBytes, certainty: 0.8f)); + +// With distance +await collection.Query.NearMedia(m => m.Image(imageBytes, distance: 0.3f)); + +// With target vectors - Sum +await collection.Query.NearMedia( + m => m.Image(imageBytes).TargetVectorsSum("title", "description") +); + +// With target vectors - Average +await collection.Query.NearMedia( + m => m.Video(videoBytes, certainty: 0.7f).TargetVectorsAverage("visual", "audio", "metadata") +); + +// With target vectors - ManualWeights +await collection.Query.NearMedia( + m => m.Audio(audioBytes, distance: 0.3f) + .TargetVectorsManualWeights(("title", 1.2), ("description", 0.8)) +); + +// With target vectors - Minimum +await collection.Query.NearMedia( + m => m.Image(imageBytes).TargetVectorsMinimum("v1", "v2", "v3") +); + +// With target vectors - RelativeScore +await collection.Query.NearMedia( + m => m.Video(videoBytes) + .TargetVectorsRelativeScore(("visual", 0.7), ("audio", 0.3)) +); + +// With GroupBy +await collection.Query.NearMedia( + m => m.Image(imageBytes).TargetVectorsSum("visual", "semantic"), + groupBy: new GroupByRequest("category", objectsPerGroup: 5) +); + +// With filters and other parameters +await collection.Query.NearMedia( + m => m.Image(imageBytes, certainty: 0.8f).TargetVectorsSum("v1", "v2"), + filters: Filter.ByProperty("status").Equal("active"), + limit: 10, + offset: 0, + autoLimit: 3, + returnMetadata: new MetadataQuery { Distance = true, Certainty = true } +); +``` + +#### Alternative: Direct byte[] Approach + +For those who prefer not to use lambdas, you can use the direct byte[] overloads: + +```csharp +// Simple search without target vectors +await collection.Query.NearMedia( + imageBytes, + NearMediaType.Image, + certainty: 0.8 +); + +// With target vectors using TargetVectors.FactoryFn +await collection.Query.NearMedia( + imageBytes, + NearMediaType.Image, + certainty: 0.8, + targetVectors: t => t.TargetVectorsSum("title", "description") +); + +// Video with distance threshold +await collection.Query.NearMedia( + videoBytes, + NearMediaType.Video, + distance: 0.3 +); + +// With GroupBy +await collection.Query.NearMedia( + imageBytes, + NearMediaType.Image, + groupBy: new GroupByRequest("category", objectsPerGroup: 5), + certainty: 0.8 +); +``` + +--- + +## QueryClient.Hybrid + +Combined keyword (BM25) and vector search with configurable fusion. + +### Method Signatures + +```csharp +// Text-only hybrid search +Task Hybrid( + string query, + ... +) + +// With vectors +Task Hybrid( + string? query, + HybridVectorInput? vectors, + ... +) + +// Lambda builder for vectors (extension) +Task Hybrid( + string? query, + HybridVectorInput.FactoryFn? vectors, + ... +) +``` + +### Examples + +```csharp +// Text-only search +await collection.Query.Hybrid("search query"); + +// With alpha parameter (0 = keyword only, 1 = vector only) +await collection.Query.Hybrid( + "search query", + alpha: 0.5f +); + +// With fusion type +await collection.Query.Hybrid( + "search query", + fusionType: HybridFusion.RelativeScore +); + +// Text + simple vector +await collection.Query.Hybrid( + "search query", + new[] { 1f, 2f, 3f } +); + +// Text + named vectors +await collection.Query.Hybrid( + "search query", + new Vectors { + { "title", new[] { 1f, 2f } }, + { "description", new[] { 3f, 4f } } + } +); + +// Lambda builder - NearVector with Sum +await collection.Query.Hybrid( + "search query", + v => v.NearVector().TargetVectorsSum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ) +); + +// Lambda builder - NearVector with ManualWeights +await collection.Query.Hybrid( + "search query", + v => v.NearVector(certainty: 0.7f).TargetVectorsManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("description", 0.8, new[] { 3f, 4f }) + ) +); + +// NearText with server-side vectorization (implicit conversion to HybridVectorInput) +await collection.Query.Hybrid( + query: null, // Keyword search query (null for vector-only search) + vectors: new NearTextInput("banana") // Implicitly converts to HybridVectorInput +); + +// NearText with target vectors +await collection.Query.Hybrid( + new NearTextInput( + "banana", + TargetVectors: TargetVectors.Sum("title", "description") + ) +); + +// Lambda builder - NearText with targets +await collection.Query.Hybrid( + query: null, + vectors: v => v.NearText(["banana"], certainty: 0.7f).Sum("title", "description") +); + +// NearVector with NearVectorInput record (with certainty) +await collection.Query.Hybrid( + query: null, + vectors: new NearVectorInput( + new[] { 1f, 2f, 3f }, + Certainty: 0.8f + ) +); + +// NearVector with multi-target vectors using NearVectorInput +await collection.Query.Hybrid( + query: "search query", + vectors: new NearVectorInput( + VectorSearchInput.Combine( + TargetVectors.ManualWeights(("title", 0.7), ("description", 0.3)), + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ), + Distance: 0.5f + ) +); + +// Multi-vector (ColBERT) +await collection.Query.Hybrid( + "search query", + ("colbert", new[,] { + { 1f, 2f }, + { 3f, 4f } + }) +); + +// With GroupBy +await collection.Query.Hybrid( + "search query", + new GroupByRequest("category", objectsPerGroup: 3) +); +``` + +--- + +## GenerateClient Methods + +Same signatures as QueryClient but with additional generative parameters: `singlePrompt` and `groupedTask`. + +### Examples + +```csharp +// NearVector with generation +await collection.Generate.NearVector( + new[] { 1f, 2f, 3f }, + singlePrompt: "Summarize this item" +); + +// NearText with generation +await collection.Generate.NearText( + "banana", + groupedTask: new GroupedTask("Compare all results") +); + +// NearMedia with generation +await collection.Generate.NearMedia( + m => m.Image(imageBytes), + singlePrompt: "Describe this image" +); + +// NearMedia with target vectors and generation +await collection.Generate.NearMedia( + m => m.Video(videoBytes, certainty: 0.8f).Sum("visual", "audio"), + groupedTask: new GroupedTask("Summarize all videos") +); + +// Hybrid with generation +await collection.Generate.Hybrid( + "search query", + v => v.NearVector().Sum( + ("title", new[] { 1f, 2f }), + ("desc", new[] { 3f, 4f }) + ), + singlePrompt: "Summarize", + groupedTask: new GroupedTask("Compare all") +); +``` + +--- + +## AggregateClient Methods + +Same signatures as QueryClient but return aggregation results and support `returnMetrics` parameter. + +### Examples + +```csharp +// NearVector with aggregation +await collection.Aggregate.NearVector( + new[] { 1f, 2f, 3f }, + returnMetrics: [Metrics.ForProperty("price").Number(mean: true, sum: true)] +); + +// NearText with aggregation +await collection.Aggregate.NearText( + "banana", + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)] +); + +// NearMedia with aggregation +await collection.Aggregate.NearMedia( + m => m.Image(imageBytes).Sum("visual", "semantic"), + returnMetrics: [Metrics.ForProperty("category").Text(count: true)] +); + +// Hybrid with aggregation +await collection.Aggregate.Hybrid( + "search query", + new[] { 1f, 2f, 3f }, + returnMetrics: [Metrics.ForProperty("quantity").Number(sum: true)] +); +``` + +--- + +## Core Types + +### VectorSearchInput + +Central type for vector search inputs with collection initializer syntax and many implicit conversions. + +**Key implicit conversions:** +- `float[]`, `double[]` → single unnamed vector +- `Vectors` → named vectors collection +- `(string, float[])` → single named vector +- `(string, float[,])` → single named multi-vector (ColBERT) +- `Dictionary` → multiple named vectors + +**Examples:** + +```csharp +// Simple array +VectorSearchInput input = new[] { 1f, 2f, 3f }; + +// Named vectors +VectorSearchInput input = new Vectors { + { "title", new[] { 1f, 2f } }, + { "description", new[] { 3f, 4f } } +}; + +// Tuple +VectorSearchInput input = ("title", new[] { 1f, 2f }); +``` + +### VectorSearchInput.Builder + +Builder for multi-target combinations via lambda syntax. + +**Methods:** +- `Sum(params (string, Vector)[] targets)` - Sum vectors +- `Average(params (string, Vector)[] targets)` - Average vectors +- `Minimum(params (string, Vector)[] targets)` - Minimum values +- `ManualWeights(params (string, double, Vector)[] targets)` - Custom weights +- `RelativeScore(params (string, double, Vector)[] targets)` - Relative score weights + +**Usage:** + +```csharp +v => v.Sum(("title", vec1), ("desc", vec2)) +v => v.ManualWeights(("title", 1.5, vec1), ("desc", 0.5, vec2)) +``` + +--- + +### NearTextInput + +Text search configuration with optional target vectors and move parameters. + +**Properties:** +- `Query` - Text query (string or string[]) +- `TargetVectors` - Target vectors for multi-vector collections +- `Certainty` - Minimum certainty (0-1) +- `Distance` - Maximum distance +- `MoveTo` / `MoveAway` - Concept movement + +**Creation:** + +```csharp +// Simple +new NearTextInput("banana") + +// With targets +new NearTextInput( + "banana", + TargetVectors: TargetVectors.Sum("title", "description") +) + +// Lambda builder +q => q(["banana"], certainty: 0.7f) + .Sum("title", "description") +``` + +--- + +### NearMediaInput + +Input for near-media searches supporting multiple media types with optional target vectors. + +**Properties:** +- `Media` - Media content as byte array +- `Type` - Media type (Image, Video, Audio, Thermal, Depth, IMU) +- `TargetVectors` - Target vectors for multi-vector collections +- `Certainty` - Minimum certainty (0-1) +- `Distance` - Maximum distance + +**Lambda Builder (via FactoryFn):** + +```csharp +// Simple media without targets +m => m.Image(imageBytes) +m => m.Video(videoBytes) +m => m.Audio(audioBytes) + +// With certainty/distance +m => m.Image(imageBytes, certainty: 0.8f) +m => m.Video(videoBytes, distance: 0.3f) + +// With target vectors - Sum +m => m.Image(imageBytes).Sum("visual", "semantic") + +// With target vectors - ManualWeights +m => m.Audio(audioBytes, certainty: 0.7f) + .ManualWeights(("title", 1.2), ("description", 0.8)) + +// With target vectors - Average +m => m.Video(videoBytes).Average("visual", "audio", "metadata") + +// With target vectors - Minimum +m => m.Image(imageBytes).Minimum("v1", "v2", "v3") + +// With target vectors - RelativeScore +m => m.Thermal(thermalBytes) + .RelativeScore(("thermal", 0.7), ("visual", 0.3)) +``` + +**Direct Construction:** + +```csharp +// Simple +new NearMediaInput(imageBytes, NearMediaType.Image) + +// With targets +new NearMediaInput( + imageBytes, + NearMediaType.Image, + TargetVectors: TargetVectors.Sum("visual", "semantic"), + Certainty: 0.8f +) +``` + +--- + +### HybridVectorInput + +Discriminated union for hybrid search vectors. Can be `VectorSearchInput`, `NearTextInput`, or `NearVectorInput`. + +**Lambda Builder (via FactoryFn):** + +```csharp +// NearVector +v => v.NearVector().Sum( + ("title", new[] { 1f, 2f }), + ("desc", new[] { 3f, 4f }) +) + +// NearVector with certainty +v => v.NearVector(certainty: 0.7f).ManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("desc", 0.8, new[] { 3f, 4f }) +) + +// NearText +v => v.NearText() + .Query(["banana"], certainty: 0.7f) + .Sum("title", "description") +``` + +--- + +### TargetVectors + +Specifies which named vectors to target and how to combine them. + +**Factory Methods:** + +```csharp +TargetVectors.Sum("title", "description") +TargetVectors.Average("title", "description") +TargetVectors.Minimum("title", "description") +TargetVectors.ManualWeights(("title", 1.5), ("desc", 0.5)) +TargetVectors.RelativeScore(("title", 0.7), ("desc", 0.3)) + +// Implicit from array +TargetVectors targets = new[] { "title", "description" }; +``` + +--- + +## Best Practices + +1. **Use implicit conversions** for simple cases: + ```csharp + await collection.Query.NearVector(new[] { 1f, 2f, 3f }); + ``` + +2. **Use lambda builders** for multi-target with combinations: + ```csharp + await collection.Query.NearVector( + v => v.Sum(("title", vec1), ("desc", vec2)) + ); + ``` + +3. **For NearText with targets**, use lambda builder: + ```csharp + await collection.Query.NearText( + q => q(["banana"]).Sum("title", "description") + ); + ``` + +4. **For NearMedia**, use lambda builder for concise syntax: + ```csharp + // Recommended + await collection.Query.NearMedia(m => m.Image(imageBytes).Sum("v1", "v2")); + + // Alternative (for non-lambda preference) + await collection.Query.NearMedia( + imageBytes, + NearMediaType.Image, + targets: t => t.Sum("v1", "v2") + ); + ``` + +5. **Target vector combinations**: Choose the right method for your use case + - `Sum()` - Add all vectors (default, best for most cases) + - `Average()` - Average all vectors (normalized sum) + - `ManualWeights()` - Custom weights per vector + - `Minimum()` - Minimum value across vectors + - `RelativeScore()` - Weighted combination based on scores + +4. **For Hybrid with complex vectors**, use HybridVectorInput lambda: + ```csharp + await collection.Query.Hybrid( + "search text", + v => v.NearVector().ManualWeights( + ("title", 1.2, vec1), + ("desc", 0.8, vec2) + ) + ); + ``` + +5. **Set appropriate thresholds**: + - `certainty`: 0.7-0.9 for high-quality results + - `distance`: 0.1-0.3 for similar items (lower is closer) + - `alpha`: 0.5 for balanced hybrid, 0.75 for more vector influence diff --git a/src/Example/TraditionalExample.cs b/src/Example/TraditionalExample.cs index 32e90d4c..fe552b64 100644 --- a/src/Example/TraditionalExample.cs +++ b/src/Example/TraditionalExample.cs @@ -110,7 +110,7 @@ public static async Task Run() { // Batch Insertion Demo var requests = cats.Select(c => - BatchInsertRequest.Create(c.Data, vectors: new Vectors { { "default", c.Vector } }) + BatchInsertRequest.Create(c.Data, vectors: ("default", c.Vector)) ); var batchInsertions = await collection.Data.InsertMany(requests); @@ -140,7 +140,7 @@ public static async Task Run() } result = await collection.Query.FetchObjects(limit: 5); - retrieved = result.Objects.ToList(); + retrieved = [.. result.Objects]; Console.WriteLine("Cats retrieved: " + retrieved.Count); firstObj = retrieved.First(); @@ -167,8 +167,10 @@ public static async Task Run() Console.WriteLine("Querying Neighboring Cats: [20,21,22]"); + float[] vector1 = [20f, 21f, 22f]; + var queryNearVector = await collection.Query.NearVector( - vector: new[] { 20f, 21f, 22f }, + vectors: vector1, distance: 0.5f, limit: 5, returnProperties: ["name", "breed", "color", "counter"], diff --git a/src/Weaviate.Client.Analyzers.Tests/HybridSearchNullParametersAnalyzerTests.cs b/src/Weaviate.Client.Analyzers.Tests/HybridSearchNullParametersAnalyzerTests.cs new file mode 100644 index 00000000..157f6bef --- /dev/null +++ b/src/Weaviate.Client.Analyzers.Tests/HybridSearchNullParametersAnalyzerTests.cs @@ -0,0 +1,446 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Weaviate.Client.Analyzers.Tests; + +public class HybridSearchNullParametersAnalyzerTests +{ + private const string ClientStubs = + @" +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Weaviate.Client.Models +{ + public class HybridVectorInput { } + public class HybridFusion { } + public class BM25Operator { } + public class Filter { } + public class Rerank { } + public class AutoArray { } + public class QueryReference { } + public class MetadataQuery { } + public class VectorQuery { } + public class WeaviateResult { } + public class GroupByResult { } + public class GroupByRequest { } + public class AggregateResult { } + public class AggregateGroupByResult { } + public class GenerativeWeaviateResult { } + public class GenerativeGroupByResult { } + public class SinglePrompt { } + public class GroupedTask { } + public class GenerativeProvider { } + public class Aggregate + { + public class GroupBy { } + public class Metric { } + } +} + +namespace Weaviate.Client +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Weaviate.Client.Models; + + public class QueryClient + { + public async Task Hybrid( + string? query = null, + HybridVectorInput? vectors = null, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + if (query is null && vectors is null) + { + throw new ArgumentException(""At least one of 'query' or 'vectors' must be provided for hybrid search.""); + } + return await Task.FromResult(new WeaviateResult()); + } + + public async Task Hybrid( + GroupByRequest groupBy, + string? query = null, + HybridVectorInput? vectors = null, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + if (query is null && vectors is null) + { + throw new ArgumentException(""At least one of 'query' or 'vectors' must be provided for hybrid search.""); + } + return await Task.FromResult(new GroupByResult()); + } + } + + public class GenerateClient + { + public async Task Hybrid( + string? query = null, + HybridVectorInput? vectors = null, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + if (query is null && vectors is null) + { + throw new ArgumentException(""At least one of 'query' or 'vectors' must be provided for hybrid search.""); + } + return await Task.FromResult(new GenerativeWeaviateResult()); + } + } + + public class AggregateClient + { + public async Task Hybrid( + string? query = null, + HybridVectorInput? vectors = null, + float alpha = 0.7f, + string[]? queryProperties = null, + uint? objectLimit = null, + BM25Operator? bm25Operator = null, + Filter? filters = null, + float? maxVectorDistance = null, + bool totalCount = true, + IEnumerable? returnMetrics = null, + CancellationToken cancellationToken = default + ) + { + if (query is null && vectors is null) + { + throw new ArgumentException(""At least one of 'query' or 'vectors' must be provided for hybrid search.""); + } + return await Task.FromResult(new AggregateResult()); + } + } +} + +namespace Weaviate.Client.Typed +{ + using System.Threading; + using System.Threading.Tasks; + using Weaviate.Client.Models; + + public class WeaviateObject { } + + public class TypedQueryClient + { + public async Task Hybrid( + string? query = null, + HybridVectorInput? vectors = null, + CancellationToken cancellationToken = default + ) + { + if (query is null && vectors is null) + { + throw new System.ArgumentException(""At least one of 'query' or 'vectors' must be provided for hybrid search.""); + } + return await Task.FromResult(new WeaviateResult()); + } + } + + public class TypedGenerateClient + { + public async Task Hybrid( + string? query = null, + HybridVectorInput? vectors = null, + CancellationToken cancellationToken = default + ) + { + if (query is null && vectors is null) + { + throw new System.ArgumentException(""At least one of 'query' or 'vectors' must be provided for hybrid search.""); + } + return await Task.FromResult(new GenerativeWeaviateResult()); + } + } +} +"; + + [Fact] + public async Task ExplicitNullArguments_ReportsDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.QueryClient(); + await client.{|#0:Hybrid|}((string)null, (Weaviate.Client.Models.HybridVectorInput)null); + } +}"; + + var expected = VerifyCS + .Diagnostic(HybridSearchNullParametersAnalyzer.DiagnosticId) + .WithLocation(0); + + await VerifyCS.VerifyAnalyzerAsync(testCode, expected); + } + + [Fact] + public async Task NoArguments_ReportsDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.QueryClient(); + await client.{|#0:Hybrid|}(); + } +}"; + + var expected = VerifyCS + .Diagnostic(HybridSearchNullParametersAnalyzer.DiagnosticId) + .WithLocation(0); + + await VerifyCS.VerifyAnalyzerAsync(testCode, expected); + } + + [Fact] + public async Task QueryOnlyProvided_NoDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.QueryClient(); + await client.Hybrid(""search query""); + } +}"; + + await VerifyCS.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task VectorsOnlyProvided_NoDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.QueryClient(); + var vectors = new Weaviate.Client.Models.HybridVectorInput(); + await client.Hybrid(null, vectors); + } +}"; + + await VerifyCS.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task BothQueryAndVectorsProvided_NoDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.QueryClient(); + var vectors = new Weaviate.Client.Models.HybridVectorInput(); + await client.Hybrid(""search query"", vectors); + } +}"; + + await VerifyCS.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task NamedArgumentsWithBothNull_ReportsDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.QueryClient(); + await client.{|#0:Hybrid|}(query: null, vectors: null); + } +}"; + + var expected = VerifyCS + .Diagnostic(HybridSearchNullParametersAnalyzer.DiagnosticId) + .WithLocation(0); + + await VerifyCS.VerifyAnalyzerAsync(testCode, expected); + } + + [Fact] + public async Task GenerateClient_BothNull_ReportsDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.GenerateClient(); + await client.{|#0:Hybrid|}(); + } +}"; + + var expected = VerifyCS + .Diagnostic(HybridSearchNullParametersAnalyzer.DiagnosticId) + .WithLocation(0); + + await VerifyCS.VerifyAnalyzerAsync(testCode, expected); + } + + [Fact] + public async Task AggregateClient_BothNull_ReportsDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.AggregateClient(); + await client.{|#0:Hybrid|}(); + } +}"; + + var expected = VerifyCS + .Diagnostic(HybridSearchNullParametersAnalyzer.DiagnosticId) + .WithLocation(0); + + await VerifyCS.VerifyAnalyzerAsync(testCode, expected); + } + + [Fact] + public async Task TypedQueryClient_BothNull_ReportsDiagnostic() + { + var testCode = + ClientStubs + + @" +class MyType { } + +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.Typed.TypedQueryClient(); + await client.{|#0:Hybrid|}(); + } +}"; + + var expected = VerifyCS + .Diagnostic(HybridSearchNullParametersAnalyzer.DiagnosticId) + .WithLocation(0); + + await VerifyCS.VerifyAnalyzerAsync(testCode, expected); + } + + [Fact] + public async Task TypedGenerateClient_BothNull_ReportsDiagnostic() + { + var testCode = + ClientStubs + + @" +class MyType { } + +class TestClass +{ + async Task TestMethod() + { + var client = new Weaviate.Client.Typed.TypedGenerateClient(); + await client.{|#0:Hybrid|}(); + } +}"; + + var expected = VerifyCS + .Diagnostic(HybridSearchNullParametersAnalyzer.DiagnosticId) + .WithLocation(0); + + await VerifyCS.VerifyAnalyzerAsync(testCode, expected); + } + + [Fact] + public async Task NonHybridMethod_NoDiagnostic() + { + var testCode = + ClientStubs + + @" +class TestClass +{ + void SomeMethod(string? query = null, object? vectors = null) + { + } + + void TestMethod() + { + SomeMethod(); + } +}"; + + await VerifyCS.VerifyAnalyzerAsync(testCode); + } +} diff --git a/src/Weaviate.Client.Analyzers.Tests/Weaviate.Client.Analyzers.Tests.csproj b/src/Weaviate.Client.Analyzers.Tests/Weaviate.Client.Analyzers.Tests.csproj index 0dcf3ca8..778c8e31 100644 --- a/src/Weaviate.Client.Analyzers.Tests/Weaviate.Client.Analyzers.Tests.csproj +++ b/src/Weaviate.Client.Analyzers.Tests/Weaviate.Client.Analyzers.Tests.csproj @@ -1,6 +1,6 @@ - net8.0;net9.0 + net9.0 latest enable enable diff --git a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md index 50eab87e..edacf875 100644 --- a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md +++ b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md @@ -7,3 +7,4 @@ Rule ID | Category | Severity | Notes WEAVIATE001 | Usage | Warning | AutoArrayUsageAnalyzer - AutoArray should only be used as method parameter WEAVIATE002 | Usage | Error | VectorizerFactoryAnalyzer - Missing property initialization WEAVIATE003 | Usage | Error | VectorizerFactoryAnalyzer - Missing field in Weights calculation +WEAVIATE004 | Usage | Error | Hybrid search requires at least one of 'query' or 'vectors' parameters diff --git a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md index fb2ed19f..41541c87 100644 --- a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md @@ -2,4 +2,4 @@ ### New Rules Rule ID | Category | Severity | Notes -------------|----------|----------|----------------------------------------------------------------- +------------|----------|----------|----------------------------------------------------- diff --git a/src/Weaviate.Client.Analyzers/HybridSearchNullParametersAnalyzer.cs b/src/Weaviate.Client.Analyzers/HybridSearchNullParametersAnalyzer.cs new file mode 100644 index 00000000..c6591f94 --- /dev/null +++ b/src/Weaviate.Client.Analyzers/HybridSearchNullParametersAnalyzer.cs @@ -0,0 +1,220 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Weaviate.Client.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class HybridSearchNullParametersAnalyzer : DiagnosticAnalyzer +{ + public const string DiagnosticId = "WEAVIATE004"; + private const string Category = "Usage"; + + private static readonly LocalizableString Title = + "Hybrid search requires at least one of 'query' or 'vectors' parameters"; + private static readonly LocalizableString MessageFormat = + "At least one of 'query' or 'vectors' must be provided for hybrid search. Both parameters cannot be null."; + private static readonly LocalizableString Description = + "Hybrid search methods require either a keyword query (for BM25 search) or vector input (for vector search), or both. Calling Hybrid() with both parameters as null will throw an ArgumentException at runtime."; + + private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId, + Title, + MessageFormat, + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: Description + ); + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + // Register for invocation expressions (method calls) + context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression); + } + + private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) + { + var invocation = (InvocationExpressionSyntax)context.Node; + + // Get the method symbol + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); + if (symbolInfo.Symbol is not IMethodSymbol methodSymbol) + return; + + // Check if the method is named "Hybrid" + if (methodSymbol.Name != "Hybrid") + return; + + // Check if the containing type is one of the Weaviate client types + if (!IsWeaviateClientType(methodSymbol.ContainingType)) + return; + + // Analyze the arguments + var argumentList = invocation.ArgumentList; + if (argumentList == null) + return; + + // Find the query and vectors arguments + bool queryIsNull = false; + bool vectorsIsNull = false; + + foreach (var argument in argumentList.Arguments) + { + // Get the parameter this argument is for + var parameter = DetermineParameter(argument, methodSymbol, context.SemanticModel); + if (parameter == null) + continue; + + var parameterName = parameter.Name; + + // Check if this is the query or vectors parameter + if (parameterName == "query") + { + queryIsNull = IsNullLiteral(argument.Expression); + } + else if (parameterName == "vectors") + { + vectorsIsNull = IsNullLiteral(argument.Expression); + } + } + + // If both arguments weren't explicitly provided, check if they have default values + // If a parameter wasn't found in the arguments, it's using its default value + var queryParam = methodSymbol.Parameters.FirstOrDefault(p => p.Name == "query"); + var vectorsParam = methodSymbol.Parameters.FirstOrDefault(p => p.Name == "vectors"); + + // If query parameter wasn't in the argument list and has a default value of null + if ( + queryParam != null + && !argumentList.Arguments.Any(a => + DetermineParameter(a, methodSymbol, context.SemanticModel)?.Name == "query" + ) + ) + { + if (queryParam.HasExplicitDefaultValue && queryParam.ExplicitDefaultValue == null) + { + queryIsNull = true; + } + } + + // If vectors parameter wasn't in the argument list and has a default value of null + if ( + vectorsParam != null + && !argumentList.Arguments.Any(a => + DetermineParameter(a, methodSymbol, context.SemanticModel)?.Name == "vectors" + ) + ) + { + if (vectorsParam.HasExplicitDefaultValue && vectorsParam.ExplicitDefaultValue == null) + { + vectorsIsNull = true; + } + } + + // Report diagnostic if both are null + if (queryIsNull && vectorsIsNull) + { + // Get the location of the method name, not the entire invocation + var location = invocation.Expression is MemberAccessExpressionSyntax memberAccess + ? memberAccess.Name.GetLocation() + : invocation.Expression.GetLocation(); + + var diagnostic = Diagnostic.Create(Rule, location); + context.ReportDiagnostic(diagnostic); + } + } + + private static bool IsWeaviateClientType(INamedTypeSymbol typeSymbol) + { + if (typeSymbol == null) + return false; + + var typeName = typeSymbol.Name; + var namespaceName = typeSymbol.ContainingNamespace.ToDisplayString(); + + // Check for QueryClient, GenerateClient, AggregateClient + if (namespaceName == "Weaviate.Client") + { + return typeName == "QueryClient" + || typeName == "GenerateClient" + || typeName == "AggregateClient"; + } + + // Check for TypedQueryClient, TypedGenerateClient + if (namespaceName == "Weaviate.Client.Typed") + { + if (typeSymbol.IsGenericType) + { + var constructedFrom = typeSymbol.ConstructedFrom; + return constructedFrom.Name == "TypedQueryClient" + || constructedFrom.Name == "TypedGenerateClient"; + } + } + + return false; + } + + private static IParameterSymbol? DetermineParameter( + ArgumentSyntax argument, + IMethodSymbol methodSymbol, + SemanticModel semanticModel + ) + { + // If the argument has a name colon (e.g., query: "test"), use that + if (argument.NameColon != null) + { + var parameterName = argument.NameColon.Name.Identifier.Text; + return methodSymbol.Parameters.FirstOrDefault(p => p.Name == parameterName); + } + + // Otherwise, determine by position + var argumentList = argument.Parent as ArgumentListSyntax; + if (argumentList == null) + return null; + + var index = argumentList.Arguments.IndexOf(argument); + if (index < 0 || index >= methodSymbol.Parameters.Length) + return null; + + return methodSymbol.Parameters[index]; + } + + private static bool IsNullLiteral(ExpressionSyntax expression) + { + // Check for literal null + if (expression is LiteralExpressionSyntax literal) + { + return literal.Kind() == SyntaxKind.NullLiteralExpression; + } + + // Check for default(T) where T is a reference type + if (expression is DefaultExpressionSyntax) + { + return true; + } + + // Check for cast expressions like (string)null + if (expression is CastExpressionSyntax castExpression) + { + return IsNullLiteral(castExpression.Expression); + } + + // Check for parenthesized expressions like (null) + if (expression is ParenthesizedExpressionSyntax parenthesized) + { + return IsNullLiteral(parenthesized.Expression); + } + + return false; + } +} diff --git a/src/Weaviate.Client.Tests/GlobalSuppressions.cs b/src/Weaviate.Client.Tests/GlobalSuppressions.cs new file mode 100644 index 00000000..a6db1b92 --- /dev/null +++ b/src/Weaviate.Client.Tests/GlobalSuppressions.cs @@ -0,0 +1,14 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "Performance", + "CA1861:Avoid constant arrays as arguments", + Justification = "", + Scope = "namespace", + Target = "~N:Weaviate.Client.Tests" +)] diff --git a/src/Weaviate.Client.Tests/Integration/Datasets/Collections.cs b/src/Weaviate.Client.Tests/Integration/Datasets/Collections.cs index 48708f63..ddad8090 100644 --- a/src/Weaviate.Client.Tests/Integration/Datasets/Collections.cs +++ b/src/Weaviate.Client.Tests/Integration/Datasets/Collections.cs @@ -82,10 +82,10 @@ public class DatasetCollectionCreateAndExport : TheoryData Name = "CreationAndExport", Description = "My own description too", Properties = [_nameProperty], - VectorConfig = new VectorConfig( + VectorConfig = Configure.Vector( "default", - new Vectorizer.SelfProvided(), - vectorIndexConfig: _vectorIndexConfigHNSW_base + v => v.SelfProvided(), + index: _vectorIndexConfigHNSW_base ), InvertedIndexConfig = InvertedIndexConfig.Default, ReplicationConfig = ReplicationConfig.Default, diff --git a/src/Weaviate.Client.Tests/Integration/Datasets/Datasets.cs b/src/Weaviate.Client.Tests/Integration/Datasets/Datasets.cs index 7a5a60e5..de176e4a 100644 --- a/src/Weaviate.Client.Tests/Integration/Datasets/Datasets.cs +++ b/src/Weaviate.Client.Tests/Integration/Datasets/Datasets.cs @@ -202,7 +202,7 @@ public class DatasetFilteringReferences : TheoryData Filter.Reference("ref").Property("name").HasLength().IsLessThan(6), 0 ), - ["RefIDEquals"] = (Filter.Reference("ref").ID.IsEqual(_reusableUuids[1]), 1), + ["RefIDEquals"] = (Filter.Reference("ref").UUID.IsEqual(_reusableUuids[1]), 1), ["IndirectSelfRefLengthLessThan6"] = ( Filter .Reference("ref2") @@ -223,9 +223,9 @@ public class DatasetFilterByID : TheoryData public static Dictionary Cases => new() { - ["IdEquals"] = Filter.ID.IsEqual(_reusableUuids[0]), - ["IdContainsAny"] = Filter.ID.ContainsAny([_reusableUuids[0]]), - ["IdNotEqual"] = Filter.ID.IsNotEqual(_reusableUuids[1]), + ["IdEquals"] = Filter.UUID.IsEqual(_reusableUuids[0]), + ["IdContainsAny"] = Filter.UUID.ContainsAny([_reusableUuids[0]]), + ["IdNotEqual"] = Filter.UUID.IsNotEqual(_reusableUuids[1]), ["IdWithProperty(_id)Equal"] = Filter.Property("_id").IsEqual(_reusableUuids[0]), }; @@ -287,11 +287,11 @@ IEnumerable data [ BatchInsertRequest.Create( new { Name = "some name" }, - vectors: Vector.Create(1, 2, 3) + vectors: new int[] { 1, 2, 3 } ), BatchInsertRequest.Create( new { Name = "some other name" }, - id: _reusableUuids[0] + uuid: _reusableUuids[0] ), ], ] @@ -334,19 +334,19 @@ IEnumerable data [ BatchInsertRequest.Create( new { Name = "Name 1" }, - id: _reusableUuids[0] + uuid: _reusableUuids[0] ), BatchInsertRequest.Create( new { Name = "Name 2" }, - id: _reusableUuids[1] + uuid: _reusableUuids[1] ), BatchInsertRequest.Create( new { Name = "Name 3" }, - id: _reusableUuids[2] + uuid: _reusableUuids[2] ), BatchInsertRequest.Create( new { Name = "Name 4" }, - id: _reusableUuids[3] + uuid: _reusableUuids[3] ), ], [ @@ -366,19 +366,19 @@ IEnumerable data [ BatchInsertRequest.Create( new { Name = "Name 1" }, - id: _reusableUuids[0] + uuid: _reusableUuids[0] ), BatchInsertRequest.Create( new { Name = "Name 2" }, - id: _reusableUuids[1] + uuid: _reusableUuids[1] ), BatchInsertRequest.Create( new { Name = "Name 3" }, - id: _reusableUuids[2] + uuid: _reusableUuids[2] ), BatchInsertRequest.Create( new { Name = "Name 4" }, - id: _reusableUuids[3] + uuid: _reusableUuids[3] ), ], [ @@ -398,19 +398,19 @@ IEnumerable data [ BatchInsertRequest.Create( new { Name = "Name 1" }, - id: _reusableUuids[0] + uuid: _reusableUuids[0] ), BatchInsertRequest.Create( new { Name = "Name 2" }, - id: _reusableUuids[1] + uuid: _reusableUuids[1] ), BatchInsertRequest.Create( new { Name = "Name 3" }, - id: _reusableUuids[2] + uuid: _reusableUuids[2] ), BatchInsertRequest.Create( new { Name = "Name 4" }, - id: _reusableUuids[3] + uuid: _reusableUuids[3] ), ], [ diff --git a/src/Weaviate.Client.Tests/Integration/TestAuth.cs b/src/Weaviate.Client.Tests/Integration/TestAuth.cs index a84e10c9..5b185bbc 100644 --- a/src/Weaviate.Client.Tests/Integration/TestAuth.cs +++ b/src/Weaviate.Client.Tests/Integration/TestAuth.cs @@ -1,3 +1,5 @@ +// Some tests in this file require internal access to OAuthTokenService. +#if ENABLE_INTERNAL_TESTS namespace Weaviate.Client.Tests.Integration; using System.Net.Http; @@ -224,3 +226,4 @@ await Connect.Local( }); } } +#endif diff --git a/src/Weaviate.Client.Tests/Integration/TestBatch.cs b/src/Weaviate.Client.Tests/Integration/TestBatch.cs index 4cc73df3..97df2f7f 100644 --- a/src/Weaviate.Client.Tests/Integration/TestBatch.cs +++ b/src/Weaviate.Client.Tests/Integration/TestBatch.cs @@ -56,7 +56,7 @@ public async Task Test_Batch_ReferenceAddMany() // Setup referenced collection ("To") var refCollection = await CollectionFactory( name: "To", - vectorConfig: new VectorConfig("default"), + vectorConfig: Configure.Vector(v => v.SelfProvided()), properties: [Property.Int("number")] ); int numObjects = 10; @@ -69,14 +69,14 @@ public async Task Test_Batch_ReferenceAddMany() cancellationToken: TestContext.Current.CancellationToken ); - Guid[] uuidsTo = [.. refInsertResult.Select(r => r.ID!.Value)]; + Guid[] uuidsTo = [.. refInsertResult.Select(r => r.UUID!.Value)]; // Setup main collection ("From") with a reference property var collection = await CollectionFactory( name: "From", properties: Property.Int("num"), references: new Reference("ref", refCollection.Name), - vectorConfig: new VectorConfig("default") + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); // Insert objects into the main collection and get their UUIDs @@ -85,7 +85,7 @@ public async Task Test_Batch_ReferenceAddMany() cancellationToken: TestContext.Current.CancellationToken ); - Guid[] uuidsFrom = [.. fromInsertResult.Select(r => r.ID!.Value)]; + Guid[] uuidsFrom = [.. fromInsertResult.Select(r => r.UUID!.Value)]; // First batch: each "From" object references the "To" object with the same index var batchReturn1 = await collection.Data.ReferenceAddMany( diff --git a/src/Weaviate.Client.Tests/Integration/TestBatchDelete.cs b/src/Weaviate.Client.Tests/Integration/TestBatchDelete.cs index ca65a115..9a2da74d 100644 --- a/src/Weaviate.Client.Tests/Integration/TestBatchDelete.cs +++ b/src/Weaviate.Client.Tests/Integration/TestBatchDelete.cs @@ -10,7 +10,7 @@ public async Task Test_Delete_Many_Return() { var collection = await CollectionFactory( properties: [Property.Text("Name")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); await collection.Data.InsertMany( @@ -34,7 +34,7 @@ public async Task Test_Delete_Many_Or() { var collection = await CollectionFactory( properties: [Property.Text("Name"), Property.Int("Age")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); await collection.Data.InsertMany( @@ -70,7 +70,7 @@ public async Task Test_Delete_Many_And() { var collection = await CollectionFactory( properties: [Property.Text("Name"), Property.Int("Age")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); await collection.Data.InsertMany( @@ -106,7 +106,7 @@ await collection.Data.DeleteMany( public async Task Test_Dry_Run(bool dryRun) { var collection = await CollectionFactory( - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); var uuid1 = await collection.Data.Insert( @@ -119,7 +119,7 @@ public async Task Test_Dry_Run(bool dryRun) ); var result = await collection.Data.DeleteMany( - where: Filter.ID.IsEqual(uuid1), + where: Filter.UUID.IsEqual(uuid1), dryRun: dryRun, cancellationToken: TestContext.Current.CancellationToken ); @@ -164,7 +164,7 @@ await collection.Query.FetchObjectByID( public async Task Test_Verbosity(bool verbose) { var collection = await CollectionFactory( - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); var uuid1 = await collection.Data.Insert( @@ -177,7 +177,7 @@ public async Task Test_Verbosity(bool verbose) ); var result = await collection.Data.DeleteMany( - where: Filter.ID.IsEqual(uuid1), + where: Filter.UUID.IsEqual(uuid1), verbose: verbose, dryRun: false, cancellationToken: TestContext.Current.CancellationToken diff --git a/src/Weaviate.Client.Tests/Integration/TestCollections.cs b/src/Weaviate.Client.Tests/Integration/TestCollections.cs index b2652145..3d108ea2 100644 --- a/src/Weaviate.Client.Tests/Integration/TestCollections.cs +++ b/src/Weaviate.Client.Tests/Integration/TestCollections.cs @@ -69,7 +69,7 @@ public async Task Test_Collections_Exists() { var collection = await CollectionFactory( properties: [Property.Text("Name")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); bool exists = await _weaviate.Collections.Exists( @@ -91,6 +91,8 @@ public async Task Test_Collections_Not_Exists() Assert.False(exists); } + // This test requires internal access to CollectionConfig.FromCollectionCreate +#if ENABLE_INTERNAL_TESTS [Theory] [ClassData(typeof(DatasetCollectionCreateAndExport))] public async Task Test_Collections_Export_Cases(string key) @@ -110,6 +112,7 @@ public async Task Test_Collections_Export_Cases(string key) Assert.Equal(expected, export); } +#endif [Fact] public async Task Collection_Creates_And_Retrieves_Reranker_Config() @@ -144,11 +147,10 @@ public async Task Collection_Creates_And_Retrieves_Generative_Config() // Arrange var collectionClient = await CollectionFactory( properties: [Property.Text("Name")], - generativeConfig: new GenerativeConfig.Custom - { - Type = "generative-dummy", - Config = new { ConfigOption = "ConfigValue" }, - } + generativeConfig: Configure.Generative.Custom( + "generative-dummy", + new { ConfigOption = "ConfigValue" } + ) ); // Act @@ -169,7 +171,7 @@ public async Task Test_Collections_Export() name: "MyOwnSuffix", description: "My own description too", properties: [Property.Text("Name")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); var export = await _weaviate.Collections.Export( @@ -297,9 +299,9 @@ public async Task Test_Collections_Export_NonDefaultValues_Sharding() properties: [Property.Text("Name"), Property.Int("SomeNumber")], references: null, collectionNamePartSeparator: "", - vectorConfig: new VectorConfig( + vectorConfig: Configure.Vector( "nondefault", - new Vectorizer.Text2VecTransformers() { VectorizeCollectionName = false } + v => v.Text2VecTransformers(vectorizeCollectionName: false) ), invertedIndexConfig: new() { @@ -438,12 +440,9 @@ public async Task Test_Collections_Export_NonDefaultValues_Sharding() Assert.False(config?.Skip); Assert.Equal(1000000000000L, config?.VectorCacheMaxObjects); - // Obsolete properties should be null/empty for new VectorConfig usage -#pragma warning disable CS0618 // Type or member is obsolete Assert.Null(export.VectorIndexConfig); Assert.Null(export.VectorIndexType); Assert.Equal("", export.Vectorizer); -#pragma warning restore CS0618 // Type or member is obsolete } [Fact] @@ -455,9 +454,9 @@ public async Task Test_Collections_Export_NonDefaultValues_MultiTenacy() properties: [Property.Text("Name"), Property.Int("SomeNumber")], references: null, collectionNamePartSeparator: "", - vectorConfig: new VectorConfig( + vectorConfig: Configure.Vector( "nondefault", - new Vectorizer.Text2VecTransformers() { VectorizeCollectionName = false } + v => v.Text2VecTransformers(vectorizeCollectionName: false) ), multiTenancyConfig: new() { @@ -627,7 +626,7 @@ await collection.Config.AddVector( public static IEnumerable GenerativeConfigData() { yield return null; - yield return new GenerativeConfig.Anyscale(); + yield return Configure.Generative.Anyscale(); } public static IEnumerable VectorizerConfigData() diff --git a/src/Weaviate.Client.Tests/Integration/TestFilters.cs b/src/Weaviate.Client.Tests/Integration/TestFilters.cs index e5f407c0..a868ffc4 100644 --- a/src/Weaviate.Client.Tests/Integration/TestFilters.cs +++ b/src/Weaviate.Client.Tests/Integration/TestFilters.cs @@ -430,7 +430,7 @@ public async Task FilterArrayTypes(string key) // Arrange var collection = await CollectionFactory( - vectorConfig: new VectorConfig("default"), + vectorConfig: Configure.Vector(v => v.SelfProvided()), properties: [ Property.TextArray("texts"), @@ -491,7 +491,7 @@ public async Task FilterContains(string test) // Arrange var collection = await CollectionFactory( - vectorConfig: new VectorConfig("default"), + vectorConfig: Configure.Vector(v => v.SelfProvided()), properties: [ Property.Text("text"), @@ -672,7 +672,7 @@ public async Task Filter_Not_ContainsAny_Integration() ); // Act - var filter = Filter.Not(Filter.ID.ContainsAny(new[] { uuid1, uuid2 })); + var filter = Filter.Not(Filter.UUID.ContainsAny(new[] { uuid1, uuid2 })); var objects = await collection.Query.FetchObjects( filters: filter, cancellationToken: TestContext.Current.CancellationToken diff --git a/src/Weaviate.Client.Tests/Integration/TestIterator.cs b/src/Weaviate.Client.Tests/Integration/TestIterator.cs index e9475478..52d3ad5e 100644 --- a/src/Weaviate.Client.Tests/Integration/TestIterator.cs +++ b/src/Weaviate.Client.Tests/Integration/TestIterator.cs @@ -9,7 +9,7 @@ public async Task Test_Iterator() { var collection = await CollectionFactory( properties: [Property.Text("name")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); await collection.Data.InsertMany( @@ -63,7 +63,7 @@ public async Task Test_Iterator_Arguments( { var collection = await CollectionFactory( properties: [Property.Int("data"), Property.Text("text")], - vectorConfig: new VectorConfig("default", new Vectorizer.Text2VecTransformers()) + vectorConfig: Configure.Vector(v => v.Text2VecTransformers()) ); // Insert test data @@ -168,7 +168,7 @@ public async Task Test_Iterator_With_Default_Generic() { var collection = await CollectionFactory( properties: [Property.Text("this"), Property.Text("that")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); var insertData = Enumerable @@ -216,7 +216,7 @@ public async Task Test_Iterator_Basic(uint count) { var collection = await CollectionFactory( properties: [Property.Int("data")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); if (count > 0) @@ -262,7 +262,7 @@ public async Task Test_Iterator_With_After() { var collection = await CollectionFactory( properties: [Property.Int("data")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); var insertData = Enumerable diff --git a/src/Weaviate.Client.Tests/Integration/TestLegacyCollection.cs b/src/Weaviate.Client.Tests/Integration/TestLegacyCollection.cs index 9e6cc463..445a828e 100644 --- a/src/Weaviate.Client.Tests/Integration/TestLegacyCollection.cs +++ b/src/Weaviate.Client.Tests/Integration/TestLegacyCollection.cs @@ -1,3 +1,5 @@ +// These tests require internal access to ToDto() and WeaviateRestClient. +#if ENABLE_INTERNAL_TESTS using System.Text.Json; using Weaviate.Client.Models; @@ -175,7 +177,7 @@ public async Task Test_Legacy_Collection_Create_From_Json() Assert.NotNull(obj1); Assert.Equal("Item1", obj1.Properties["name"]); Assert.True(obj1.Vectors.ContainsKey("default")); - Assert.Equal(4, obj1.Vectors["default"].Dimensions); + Assert.Equal((1, 4), obj1.Vectors["default"].Dimensions); float[] obj1Vector = obj1.Vectors["default"]; Assert.Equal(vector1, obj1Vector); @@ -508,3 +510,4 @@ await collectionClient.Config.AddVector( ); } } +#endif diff --git a/src/Weaviate.Client.Tests/Integration/TestMultiVector.cs b/src/Weaviate.Client.Tests/Integration/TestMultiVector.cs index b36c293b..b833baf8 100644 --- a/src/Weaviate.Client.Tests/Integration/TestMultiVector.cs +++ b/src/Weaviate.Client.Tests/Integration/TestMultiVector.cs @@ -161,32 +161,13 @@ public async Task Test_MultiVector_SelfProvided() Assert.Equal(1UL, await collection.Count(TestContext.Current.CancellationToken)); var objs = await collection.Query.NearVector( - new[] { 1f, 2f }, - targetVector: ["regular"], + ("regular", new[] { 1f, 2f }), cancellationToken: TestContext.Current.CancellationToken ); Assert.Single(objs); objs = await collection.Query.NearVector( - new[,] - { - { 1f, 2f }, - { 3f, 4f }, - }, - targetVector: ["colbert"], - cancellationToken: TestContext.Current.CancellationToken - ); - Assert.Single(objs); - - // Vector Lists not supported - // objs = await collection.Query.NearVector( - // VectorData.Create("regular", new[] { new[] { 1f, 2f }, new[] { 2f, 1f } }), - // targetVector: "regular" - // ); - // Assert.Single(objs); - - objs = await collection.Query.NearVector( - Vector.Create( + ( "colbert", new[,] { @@ -194,56 +175,76 @@ public async Task Test_MultiVector_SelfProvided() { 3f, 4f }, } ), - targetVector: ["colbert"], + cancellationToken: TestContext.Current.CancellationToken + ); + Assert.Single(objs); + + objs = await collection.Query.NearVector( + vectors: [("regular", new[] { 1f, 2f }), ("regular", new[] { 2f, 1f })], + cancellationToken: TestContext.Current.CancellationToken + ); + Assert.Single(objs); + + objs = await collection.Query.NearVector( + vectors: + [ + ( + "colbert", + new[,] + { + { 1f, 2f }, + { 3f, 4f }, + } + ), + ], cancellationToken: TestContext.Current.CancellationToken ); Assert.Single(objs); objs = await collection.Query.Hybrid( query: null, - vectors: Vector.Create(1f, 2f), - targetVector: ["regular"], + vectors: ("regular", new[] { 1f, 2f }), cancellationToken: TestContext.Current.CancellationToken ); Assert.Single(objs); objs = await collection.Query.Hybrid( query: null, - vectors: Vectors.Create( - "default", - new[,] - { - { 1f, 2f }, - { 3f, 4f }, - } + vectors: new NearVectorInput( + Vector: + [ + ( + "colbert", + new[,] + { + { 1f, 2f }, + { 3f, 4f }, + } + ), + ] ), - targetVector: ["colbert"], cancellationToken: TestContext.Current.CancellationToken ); Assert.Single(objs); objs = await collection.Query.Hybrid( query: null, - vectors: Vectors.Create( - "colbert", - new[,] - { - { 1f, 2f }, - { 3f, 4f }, - } + vectors: new NearVectorInput( + Vector: + [ + ( + "colbert", + new[,] + { + { 1f, 2f }, + { 3f, 4f }, + } + ), + ] ), - targetVector: ["colbert"], cancellationToken: TestContext.Current.CancellationToken ); Assert.Single(objs); - - // Vector Lists not supported (yet, but coming soon) - // objs = await collection.Query.Hybrid( - // query: null, - // vector: Vectors.Create("colbert", new[] { new[] { 1f, 2f }, new[] { 3f, 4f } }), - // targetVector: "colbert" - // ); - // Assert.Single(objs); } [Fact] @@ -335,8 +336,8 @@ public async Task Test_Collection_Fetch_And_Insert_With_Multivector() Assert.NotNull(obj1); Assert.Equal("Item1", obj1.Properties["name"]); Assert.True(obj1.Vectors.ContainsKey("default")); - Assert.Equal(4, obj1.Vectors["default"].Dimensions); - Assert.Equal(3, obj1.Vectors["default"].Count); + Assert.Equal((4, 3), obj1.Vectors["default"].Dimensions); + Assert.Equal(12, obj1.Vectors["default"].Count); Assert.True(obj1.Vectors["default"].IsMultiVector); float[,] obj1Vector = obj1.Vectors["default"]; Assert.Equal(vector1, obj1Vector); diff --git a/src/Weaviate.Client.Tests/Integration/TestNamedVectorMultiTarget.cs b/src/Weaviate.Client.Tests/Integration/TestNamedVectorMultiTarget.cs index 17ef0a32..7715234b 100644 --- a/src/Weaviate.Client.Tests/Integration/TestNamedVectorMultiTarget.cs +++ b/src/Weaviate.Client.Tests/Integration/TestNamedVectorMultiTarget.cs @@ -46,9 +46,13 @@ public async Task Test_NamedVector_MultiTargetVectorPerTarget() cancellationToken: TestContext.Current.CancellationToken ); + var vectorsObj = new Vectors + { + { "first", new[] { 1f, 0f } }, + { "second", new[] { 1f, 0f, 0f } }, + }; var objs = await collection.Query.NearVector( - new Vectors { { "first", new[] { 1f, 0f } }, { "second", new[] { 1f, 0f, 0f } } }, - targetVector: ["first", "second"], + vectorsObj, cancellationToken: TestContext.Current.CancellationToken ); var ids = objs.Select(o => o.UUID!.Value).OrderBy(x => x).ToList(); @@ -56,48 +60,29 @@ public async Task Test_NamedVector_MultiTargetVectorPerTarget() Assert.Equal(expected, ids); } - public static TheoryData MultiInputCombinations => + public static TheoryData MultiInputCombinations => new( - ( - new NearVectorInput - { - { "first", new[] { 0f, 1f } }, - { "second", new float[] { 1f, 0f, 0f }, new float[] { 0f, 0f, 1f } }, - }, - new[] { "first", "second" } - ), - ( - new NearVectorInput - { - { "first", new[] { 0f, 1f }, new[] { 0f, 1f } }, - { "second", new[] { 1f, 0f, 0f } }, - }, - new[] { "first", "second" } - ), - ( - new NearVectorInput - { - { "first", new float[] { 0f, 1f }, new float[] { 0f, 1f } }, - { "second", new float[] { 1f, 0f, 0f }, new float[] { 0f, 0f, 1f } }, - }, - new[] { "first", "second" } - ), - ( - new NearVectorInput - { - { "first", new float[] { 0f, 1f }, new float[] { 0f, 1f } }, - { "second", new float[] { 1f, 0f, 0f }, new float[] { 0f, 0f, 1f } }, - }, - new[] { "second", "first" } - ) + [ + ("first", new[] { 0f, 1f }), + ("second", new[] { 1f, 0f, 0f }), + ("second", new[] { 0f, 0f, 1f }), + ], + [ + ("first", new[] { 0f, 1f }), + ("first", new[] { 0f, 1f }), + ("second", new[] { 1f, 0f, 0f }), + ], + [ + ("first", new[] { 0f, 1f }), + ("first", new[] { 0f, 1f }), + ("second", new[] { 1f, 0f, 0f }), + ("second", new[] { 0f, 0f, 1f }), + ] ); [Theory] [MemberData(nameof(MultiInputCombinations))] - public async Task Test_SameTargetVector_MultipleInputCombinations( - NearVectorInput nearVector, - string[] targetVector - ) + public async Task Test_SameTargetVector_MultipleInputCombinations(VectorSearchInput nearVector) { RequireVersion("1.27.0"); @@ -130,8 +115,7 @@ string[] targetVector ); var objs = await collection.Query.NearVector( - nearVector, - targetVector: targetVector, + vectors: nearVector, cancellationToken: TestContext.Current.CancellationToken ); var ids = objs.Select(o => o.UUID!.Value).OrderBy(x => x).ToList(); @@ -144,64 +128,67 @@ string[] targetVector { new object[] { - new Vectors - { - { "first", new[] { 0f, 1f } }, + new NearVectorInput( + Vector: new Vectors { - "second", - new float[,] + { "first", new[] { 0f, 1f } }, { - { 1f, 0f, 0f }, - { 0f, 0f, 1f }, - } - }, - }, - new[] { "first", "second" }, + "second", + new float[,] + { + { 1f, 0f, 0f }, + { 0f, 0f, 1f }, + } + }, + } + ), }, new object[] { - new Vectors - { + new NearVectorInput( + Vector: new Vectors { - "first", - new float[,] { - { 0f, 1f }, - { 0f, 1f }, - } - }, - { "second", new[] { 1f, 0f, 0f } }, - }, - new[] { "first", "second" }, + "first", + new float[,] + { + { 0f, 1f }, + { 0f, 1f }, + } + }, + { "second", new[] { 1f, 0f, 0f } }, + } + ), }, new object[] { - new Vectors - { + new NearVectorInput( + Vector: new Vectors { - "first", - new float[,] { - { 0f, 1f }, - { 0f, 1f }, - } - }, - { - "second", - new float[,] + "first", + new float[,] + { + { 0f, 1f }, + { 0f, 1f }, + } + }, { - { 1f, 0f, 0f }, - { 0f, 0f, 1f }, - } - }, - }, - new[] { "first", "second" }, + "second", + new float[,] + { + { 1f, 0f, 0f }, + { 0f, 0f, 1f }, + } + }, + } + ), }, // The following are equivalent to above, but mimic the Python HybridVector.near_vector usage new object[] { - new HybridNearVector( - new Vectors + new NearVectorInput( + Vector: new Vectors { { "first", new[] { 0f, 1f } }, { @@ -216,12 +203,11 @@ string[] targetVector Certainty: null, Distance: null ), - new[] { "first", "second" }, }, new object[] { - new HybridNearVector( - new Vectors + new NearVectorInput( + Vector: new Vectors { { "first", @@ -234,12 +220,11 @@ string[] targetVector { "second", new[] { 1f, 0f, 0f } }, } ), - new[] { "first", "second" }, }, new object[] { - new HybridNearVector( - new Vectors + new NearVectorInput( + Vector: new Vectors { { "first", @@ -259,15 +244,13 @@ string[] targetVector }, } ), - new[] { "first", "second" }, }, }; [Theory] [MemberData(nameof(HybridMultiInputCombinations))] public async Task Test_SameTargetVector_MultipleInputCombinations_Hybrid( - IHybridVectorInput nearVector, - string[] targetVector + HybridVectorInput nearVector ) { RequireVersion("1.27.0"); @@ -303,7 +286,6 @@ string[] targetVector var objs = await collection.Query.Hybrid( query: null, vectors: nearVector, - targetVector: targetVector, returnMetadata: MetadataOptions.All, cancellationToken: TestContext.Current.CancellationToken ); @@ -312,35 +294,66 @@ string[] targetVector Assert.Equal(expected, ids); } - public static IEnumerable MultiTargetVectorsWithDistances => - new List + /// + /// Ported from Python: test_same_target_vector_multiple_input + /// Tests multiple vectors for the same target with Sum combination and distance verification. + /// + public static TheoryData< + VectorSearchInput.FactoryFn, + float[] + > MultiTargetVectorsWithDistances => + new() { - new object[] { TargetVectors.Sum(["first", "second"]), new float[] { 1.0f, 3.0f } }, - new object[] + // Sum combination: first has 1 vector, second has 2 vectors { - TargetVectors.ManualWeights(("first", 1.0f), ("second", [1.0f, 1.0f])), - new float[] { 1.0f, 3.0f }, + tv => + tv.TargetVectorsSum( + ("first", new[] { 0f, 1f }), + ("second", new[] { 1f, 0f, 0f }), + ("second", new[] { 0f, 0f, 1f }) + ), + new[] { 1.0f, 3.0f } }, - new object[] + // ManualWeights: weight 1 for each vector + { + tv => + tv.TargetVectorsManualWeights( + ("first", 1.0, new[] { 0f, 1f }), + ("second", 1.0, new[] { 1f, 0f, 0f }), + ("second", 1.0, new[] { 0f, 0f, 1f }) + ), + new[] { 1.0f, 3.0f } + }, + // ManualWeights: different weights (1 for first, 1 and 2 for second) { - TargetVectors.ManualWeights(("first", 1.0f), ("second", [1.0f, 2.0f])), - new float[] { 2.0f, 4.0f }, + tv => + tv.TargetVectorsManualWeights( + ("first", 1.0, new[] { 0f, 1f }), + ("second", 1.0, new[] { 1f, 0f, 0f }), + ("second", 2.0, new[] { 0f, 0f, 1f }) + ), + new[] { 2.0f, 4.0f } }, - new object[] + // ManualWeights: same weights but different order (second before first) { - TargetVectors.ManualWeights(("second", [1.0f, 2.0f]), ("first", 1.0f)), - new float[] { 2.0f, 4.0f }, + tv => + tv.TargetVectorsManualWeights( + ("second", 1.0, new[] { 1f, 0f, 0f }), + ("second", 2.0, new[] { 0f, 0f, 1f }), + ("first", 1.0, new[] { 0f, 1f }) + ), + new[] { 2.0f, 4.0f } }, }; [Theory] [MemberData(nameof(MultiTargetVectorsWithDistances))] public async Task Test_SameTargetVector_MultipleInput( - TargetVectors targetVector, + VectorSearchInput.FactoryFn nearVectorInput, float[] expectedDistances ) { - RequireVersion("1.26.0"); + RequireVersion("1.27.0"); var collection = await CollectionFactory( properties: Array.Empty(), @@ -351,72 +364,64 @@ float[] expectedDistances } ); - var inserts = ( - await collection.Data.InsertMany( - [ - new BatchInsertRequest( - Data: new { }, - Vectors: new Vectors - { - { "first", new[] { 1f, 0f } }, - { "second", new[] { 0f, 1f, 0f } }, - } - ), - new BatchInsertRequest( - Data: new { }, - Vectors: new Vectors - { - { "first", new[] { 0f, 1f } }, - { "second", new[] { 1f, 0f, 0f } }, - } - ), - ], - cancellationToken: TestContext.Current.CancellationToken - ) - ).ToList(); - - var results = ( - await collection.Query.FetchObjects( - returnMetadata: MetadataOptions.All, - cancellationToken: TestContext.Current.CancellationToken - ) - ).ToList(); - - var uuid1 = results[0].UUID!.Value; - var uuid2 = results[1].UUID!.Value; - - var objs = await collection.Query.NearVector( - new NearVectorInput + var uuid1 = await collection.Data.Insert( + new { }, + vectors: new Vectors + { + { "first", new[] { 1f, 0f } }, + { "second", new[] { 0f, 1f, 0f } }, + }, + cancellationToken: TestContext.Current.CancellationToken + ); + var uuid2 = await collection.Data.Insert( + new { }, + vectors: new Vectors { { "first", new[] { 0f, 1f } }, - { "second", new[] { 1f, 0f, 0f }, new[] { 0f, 0f, 1f } }, + { "second", new[] { 1f, 0f, 0f } }, }, - targetVector: targetVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + var objs = await collection.Query.NearVector( + nearVectorInput, returnMetadata: MetadataOptions.All, cancellationToken: TestContext.Current.CancellationToken ); - var ids = objs.Select(o => o.UUID!.Value).OrderBy(x => x).ToList(); - var expected = new[] { uuid1, uuid2 }.OrderBy(x => x).ToList(); - Assert.Equal(expected, ids); - Assert.Equal(expectedDistances.Length, objs.Count()); + + // uuid2 should be first (closer match) + Assert.Equal(2, objs.Count()); + Assert.Equal(uuid2, objs.ElementAt(0).UUID); + Assert.Equal(uuid1, objs.ElementAt(1).UUID); Assert.Equal(expectedDistances[0], objs.ElementAt(0).Metadata.Distance); Assert.Equal(expectedDistances[1], objs.ElementAt(1).Metadata.Distance); } - public static IEnumerable MultiTargetVectors => - new List + /// + /// Ported from Python: test_named_vector_multi_target + /// Tests various combination methods (Sum, Average, Minimum, ManualWeights, RelativeScore). + /// Uses VectorSearchInput.Combine to combine TargetVectors with shared vector data. + /// + private static readonly Vectors SharedQueryVectors = new() + { + { "first", new[] { 1f, 0f, 0f } }, + { "second", new[] { 1f, 0f, 0f } }, + }; + + public static TheoryData MultiTargetVectors => + new() { - new object[] { (TargetVectors)new[] { "first", "second" } }, - new object[] { TargetVectors.Sum(new[] { "first", "second" }) }, - new object[] { TargetVectors.Minimum(new[] { "first", "second" }) }, - new object[] { TargetVectors.Average(new[] { "first", "second" }) }, - new object[] { TargetVectors.ManualWeights(("first", 1.2), ("second", 0.7)) }, - new object[] { TargetVectors.RelativeScore(("first", 1.2), ("second", 0.7)) }, + { new[] { "first", "second" }, "Targets" }, + { TargetVectors.Sum("first", "second"), "Sum" }, + { TargetVectors.Minimum("first", "second"), "Minimum" }, + { TargetVectors.Average("first", "second"), "Average" }, + { TargetVectors.ManualWeights(("first", 1.2), ("second", 0.7)), "ManualWeights" }, + { TargetVectors.RelativeScore(("first", 1.2), ("second", 0.7)), "RelativeScore" }, }; [Theory] [MemberData(nameof(MultiTargetVectors))] - public async Task Test_NamedVector_MultiTarget(string[] targetVector) + public async Task Test_NamedVector_MultiTarget(TargetVectors targets, string combinationName) { RequireVersion("1.26.0"); @@ -429,6 +434,7 @@ public async Task Test_NamedVector_MultiTarget(string[] targetVector) } ); + // Both vectors are 3D so we can query with a single 3D vector var uuid1 = await collection.Data.Insert( new { }, vectors: new Vectors @@ -448,13 +454,17 @@ public async Task Test_NamedVector_MultiTarget(string[] targetVector) cancellationToken: TestContext.Current.CancellationToken ); + // Query using VectorSearchInput.Combine to pair targets with shared vectors var objs = await collection.Query.NearVector( - new[] { 1f, 0f, 0f }, - targetVector: targetVector, + VectorSearchInput.Combine(targets, SharedQueryVectors), cancellationToken: TestContext.Current.CancellationToken ); + var ids = objs.Select(o => o.UUID!.Value).OrderBy(x => x).ToList(); var expected = new[] { uuid1, uuid2 }.OrderBy(x => x).ToList(); - Assert.Equal(expected, ids); + Assert.True( + expected.SequenceEqual(ids), + $"Expected both objects to be returned with combination method: {combinationName}" + ); } } diff --git a/src/Weaviate.Client.Tests/Integration/TestNearQueries.cs b/src/Weaviate.Client.Tests/Integration/TestNearQueries.cs index 79bbfb88..5d1113ea 100644 --- a/src/Weaviate.Client.Tests/Integration/TestNearQueries.cs +++ b/src/Weaviate.Client.Tests/Integration/TestNearQueries.cs @@ -11,7 +11,7 @@ public async Task NearObject_Returns_All_Objects_With_Metadata() "", "Test collection description", [Property.Text("Name")], - vectorConfig: new VectorConfig("default", new Vectorizer.Text2VecTransformers { }) + vectorConfig: Configure.Vector(v => v.Text2VecTransformers()) ); var uuidBanana = await collectionClient.Data.Insert( @@ -71,7 +71,7 @@ public async Task NearObject_Limit_Returns_Correct_Objects() "", "Test collection description", [Property.Text("Name")], - vectorConfig: new VectorConfig("default", new Vectorizer.Text2VecTransformers { }) + vectorConfig: Configure.Vector(v => v.Text2VecTransformers()) ); var uuidBanana = await collectionClient.Data.Insert( @@ -117,7 +117,7 @@ public async Task NearObject_Offset_Returns_Correct_Objects() "", "Test collection description", [Property.Text("Name")], - vectorConfig: new VectorConfig("default", new Vectorizer.Text2VecTransformers { }) + vectorConfig: Configure.Vector(v => v.Text2VecTransformers()) ); var uuidBanana = await collectionClient.Data.Insert( @@ -162,7 +162,7 @@ public async Task NearObject_GroupBy_Returns_Correct_Groups() "", "Test collection description", [Property.Text("Name"), Property.Int("Count")], - vectorConfig: new VectorConfig("default", new Vectorizer.Text2VecTransformers { }) + vectorConfig: Configure.Vector(v => v.Text2VecTransformers()) ); var uuidBanana1 = await collectionClient.Data.Insert( diff --git a/src/Weaviate.Client.Tests/Integration/TestNearText.cs b/src/Weaviate.Client.Tests/Integration/TestNearText.cs index 1342fd9a..83966270 100644 --- a/src/Weaviate.Client.Tests/Integration/TestNearText.cs +++ b/src/Weaviate.Client.Tests/Integration/TestNearText.cs @@ -6,16 +6,16 @@ namespace Weaviate.Client.Tests.Integration; public partial class SearchTests : IntegrationTests { [Fact] - public async Task NearTextSearch() + public async Task Test_Search_NearText() { // Arrange var collectionClient = await CollectionFactory( null, "Test collection description", [Property.Text("value")], - vectorConfig: new VectorConfig( - "default", - new Vectorizer.Text2VecTransformers() { SourceProperties = ["value"] } + vectorConfig: Configure.Vector( + v => v.Text2VecTransformers(), + sourceProperties: ["value"] ) ); @@ -34,8 +34,8 @@ public async Task NearTextSearch() // Act var retriever = await collectionClient.Query.NearText( "cake", - moveTo: new Move(1.0f, objects: [guids[0]]), - moveAway: new Move(0.5f, concepts: concepts), + moveTo: new Move(guids[0], 1.0f), + moveAway: new Move(concepts, 0.5f), returnProperties: ["value"], includeVectors: "default", cancellationToken: TestContext.Current.CancellationToken @@ -59,7 +59,7 @@ public async Task Test_Search_NearText_GroupBy() "", "Test collection description", [Property.Text("value")], - vectorConfig: new VectorConfig("default", new Vectorizer.Text2VecTransformers()) + vectorConfig: Configure.Vector(v => v.Text2VecTransformers()) ); string[] values = ["Apple", "Mountain climbing", "apple cake", "cake"]; @@ -115,4 +115,63 @@ public async Task Test_Search_NearText_GroupBy() Assert.Equal(guids[3], obj.UUID); Assert.Contains("default", obj.Vectors.Keys); } + + [Fact] + public async Task Test_Search_NearText_MultipleTargetVectors() + { + // Arrange + var collectionClient = await CollectionFactory( + null, + "Test collection with multiple vectors", + [Property.Text("title"), Property.Text("description")], + vectorConfig: + [ + Configure.Vector( + "title", + v => v.Text2VecTransformers(), + sourceProperties: ["title"] + ), + Configure.Vector( + "description", + v => v.Text2VecTransformers(), + sourceProperties: ["description"] + ), + ] + ); + + // Insert test data with distinct title and description content + var data = new[] + { + new { Title = "Fruit recipes", Description = "How to bake delicious cakes" }, + new { Title = "Baking guide", Description = "Mountain climbing safety tips" }, + new { Title = "Apple desserts", Description = "Easy fruit-based recipes" }, + new { Title = "Outdoor adventures", Description = "Hiking and climbing guide" }, + }; + + var tasks = data.Select(d => + collectionClient.Data.Insert( + d, + cancellationToken: TestContext.Current.CancellationToken + ) + ); + Guid[] guids = await Task.WhenAll(tasks); + + // Act - Search for "cake" across both title and description vectors using TargetVectorsSum combination + var retriever = await collectionClient.Query.NearText( + q => q("cake").TargetVectorsSum("title", "description"), + returnProperties: ["title", "description"], + includeVectors: true, + cancellationToken: TestContext.Current.CancellationToken + ); + var retrieved = retriever.Objects.ToList(); + + // Assert + Assert.NotNull(retrieved); + Assert.True(retrieved.Count > 0, "Should return at least one result"); + + // The first result should be the one with "cake" in description + Assert.Equal(guids[0].ToString(), retrieved[0].UUID.ToString()); + Assert.Contains("title", retrieved[0].Vectors.Keys); + Assert.Contains("description", retrieved[0].Vectors.Keys); + } } diff --git a/src/Weaviate.Client.Tests/Integration/TestPagination.cs b/src/Weaviate.Client.Tests/Integration/TestPagination.cs index 6fbac55c..6b06da32 100644 --- a/src/Weaviate.Client.Tests/Integration/TestPagination.cs +++ b/src/Weaviate.Client.Tests/Integration/TestPagination.cs @@ -26,7 +26,7 @@ public async Task FetchObjects_AfterCursor_Paginates() // Sanity: all inserts succeeded and we have stable IDs Assert.Equal(5, insert.Count); - var allIds = insert.Select(r => r.ID!.Value).ToArray(); + var allIds = insert.Select(r => r.UUID!.Value).ToArray(); // Act: fetch first page var page1 = await collection.Query.FetchObjects( diff --git a/src/Weaviate.Client.Tests/Integration/TestProperties.cs b/src/Weaviate.Client.Tests/Integration/TestProperties.cs index bea2788c..357c6216 100644 --- a/src/Weaviate.Client.Tests/Integration/TestProperties.cs +++ b/src/Weaviate.Client.Tests/Integration/TestProperties.cs @@ -64,6 +64,7 @@ public static IEnumerable PropertyTestData() "testGeo", new GeoCoordinate(12.345f, 67.89f), }; +#if ENABLE_INTERNAL_TESTS yield return new object[] { new[] { Property.PhoneNumber("testPhone") }, @@ -80,6 +81,7 @@ public static IEnumerable PropertyTestData() Valid = false, }, }; +#endif yield return new object[] { new[] { Property.TextArray("testTextArray") }, @@ -188,7 +190,7 @@ await cb.Data.InsertMany( ) ) .First() - .ID!.Value; + .UUID!.Value; var retrieved = await c.Query.FetchObjectByID( id, @@ -320,7 +322,9 @@ public async Task AllPropertiesSaveRetrieve() TestUuid = Guid.NewGuid(), TestUuidArray = new[] { Guid.NewGuid(), Guid.NewGuid() }, TestGeo = new GeoCoordinate(12.345f, 67.890f), +#if ENABLE_INTERNAL_TESTS TestPhone = new PhoneNumber("+1 555-123-4567") { DefaultCountry = "US" }, +#endif TestObject = new TestNestedProperties { TestText = "nestedText", @@ -360,6 +364,7 @@ public async Task AllPropertiesSaveRetrieve() var concreteObj = obj?.As(); +#if ENABLE_INTERNAL_TESTS testData.TestPhone = new PhoneNumber(testData.TestPhone.Input) { DefaultCountry = testData.TestPhone.DefaultCountry, @@ -370,6 +375,7 @@ public async Task AllPropertiesSaveRetrieve() NationalFormatted = "(555) 123-4567", Valid = false, }; +#endif Assert.Equivalent(testData, concreteObj); } @@ -401,7 +407,7 @@ public async Task Test_BatchInsertBlob_WithArrays() { // Blobs must be explicitly requested in returnProperties var obj = await c.Query.FetchObjectByID( - r.ID!.Value, + r.UUID!.Value, returnProperties: "testBlob", cancellationToken: TestContext.Current.CancellationToken ); @@ -561,7 +567,7 @@ public async Task Test_BatchInsert_WithArrays() foreach (var r in response) { var obj = await c.Query.FetchObjectByID( - r.ID!.Value, + r.UUID!.Value, cancellationToken: TestContext.Current.CancellationToken ); @@ -820,7 +826,7 @@ public async Task Test_FloatingPoint_Precision_BatchInsert() foreach (var r in response) { var obj = await c.Query.FetchObjectByID( - r.ID!.Value, + r.UUID!.Value, cancellationToken: TestContext.Current.CancellationToken ); Assert.NotNull(obj); diff --git a/src/Weaviate.Client.Tests/Integration/TestQueries.cs b/src/Weaviate.Client.Tests/Integration/TestQueries.cs index bbc67494..88cde42f 100644 --- a/src/Weaviate.Client.Tests/Integration/TestQueries.cs +++ b/src/Weaviate.Client.Tests/Integration/TestQueries.cs @@ -18,7 +18,7 @@ public async Task Test_Sorting(string propertyName) // Arrange var collection = await this.CollectionFactory( properties: props, - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()) + vectorConfig: Configure.Vector(v => v.SelfProvided()) ); var testData = new[] @@ -75,12 +75,11 @@ public async Task Test_BM25_Generate_And_GroupBy_With_Everything() // Arrange var collection = await this.CollectionFactory( properties: [Property.Text("text"), Property.Text("content")], - vectorConfig: new VectorConfig("default", new Vectorizer.SelfProvided()), - generativeConfig: new GenerativeConfig.Custom - { - Type = "generative-dummy", - Config = new { ConfigOption = "ConfigValue" }, - } + vectorConfig: Configure.Vector(v => v.SelfProvided()), + generativeConfig: Configure.Generative.Custom( + "generative-dummy", + new { ConfigOption = "ConfigValue" } + ) ); var testData = new[] @@ -141,11 +140,10 @@ public async Task Test_Collection_Generative_FetchObjects() var collection = await CollectionFactory( properties: [Property.Text("text")], vectorConfig: Configure.Vector(t => t.SelfProvided()), - generativeConfig: new GenerativeConfig.Custom - { - Type = "generative-dummy", - Config = new { ConfigOption = "ConfigValue" }, - } + generativeConfig: Configure.Generative.Custom( + "generative-dummy", + new { ConfigOption = "ConfigValue" } + ) ); // Insert data @@ -226,19 +224,18 @@ public async Task Test_Generate_By_Ids(Guid[] ids, int expectedLen, HashSet t.SelfProvided()), properties: [Property.Text("text")], - generativeConfig: new GenerativeConfig.Custom - { - Type = "generative-dummy", - Config = new { ConfigOption = "ConfigValue" }, - } + generativeConfig: Configure.Generative.Custom( + "generative-dummy", + new { ConfigOption = "ConfigValue" } + ) ); var result = await collection.Data.InsertMany( new[] { - BatchInsertRequest.Create(new { text = "John Doe" }, id: _reusableUuids[0]), - BatchInsertRequest.Create(new { text = "Jane Doe" }, id: _reusableUuids[1]), - BatchInsertRequest.Create(new { text = "J. Doe" }, id: _reusableUuids[2]), + BatchInsertRequest.Create(new { text = "John Doe" }, uuid: _reusableUuids[0]), + BatchInsertRequest.Create(new { text = "Jane Doe" }, uuid: _reusableUuids[1]), + BatchInsertRequest.Create(new { text = "J. Doe" }, uuid: _reusableUuids[2]), }, TestContext.Current.CancellationToken ); diff --git a/src/Weaviate.Client.Tests/Integration/TestSearchHybrid.cs b/src/Weaviate.Client.Tests/Integration/TestSearchHybrid.cs index 5cc2d0a5..8f2ef711 100644 --- a/src/Weaviate.Client.Tests/Integration/TestSearchHybrid.cs +++ b/src/Weaviate.Client.Tests/Integration/TestSearchHybrid.cs @@ -35,8 +35,9 @@ await collection.Data.Insert( var objs = ( await collection.Query.Hybrid( - alpha: 0, query: "name", + vectors: null, + alpha: 0, fusionType: fusionType, includeVectors: true, cancellationToken: TestContext.Current.CancellationToken @@ -47,10 +48,10 @@ await collection.Query.Hybrid( objs = ( await collection.Query.Hybrid( - alpha: 1, query: "name", + vectors: new Vectors(objs.First().Vectors["default"]), + alpha: 1, fusionType: fusionType, - vectors: objs.First().Vectors["default"], cancellationToken: TestContext.Current.CancellationToken ) ).Objects; @@ -82,9 +83,10 @@ await collection.Data.Insert( var objs = ( await collection.Query.Hybrid( - alpha: 0, query: "name", + vectors: null, groupBy: new GroupByRequest("name") { ObjectsPerGroup = 1, NumberOfGroups = 2 }, + alpha: 0, includeVectors: true, cancellationToken: TestContext.Current.CancellationToken ) @@ -157,6 +159,7 @@ public async Task Test_Hybrid_Limit(uint limit) var objs = ( await collection.Query.Hybrid( query: "test", + vectors: null, alpha: 0, limit: limit, cancellationToken: TestContext.Current.CancellationToken @@ -189,6 +192,7 @@ public async Task Test_Hybrid_Offset(uint offset, int expected) var objs = ( await collection.Query.Hybrid( query: "test", + vectors: null, alpha: 0, offset: offset, cancellationToken: TestContext.Current.CancellationToken @@ -217,6 +221,7 @@ public async Task Test_Hybrid_Alpha() var hybridRes = ( await collection.Query.Hybrid( query: "fruit", + vectors: null, alpha: 0, cancellationToken: TestContext.Current.CancellationToken ) @@ -233,6 +238,7 @@ await collection.Query.BM25( hybridRes = ( await collection.Query.Hybrid( query: "fruit", + vectors: null, alpha: 1, cancellationToken: TestContext.Current.CancellationToken ) @@ -280,7 +286,7 @@ await collection.Data.Insert( var hybridObjs = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearVector(obj.Vectors["default"]), + vectors: new NearVectorInput(Vector: obj.Vectors["default"]), cancellationToken: TestContext.Current.CancellationToken ) ).Objects; @@ -290,7 +296,7 @@ await collection.Query.Hybrid( var nearVec = ( await collection.Query.NearVector( - vector: obj.Vectors["default"], + obj.Vectors["default"], returnMetadata: MetadataOptions.Distance, cancellationToken: TestContext.Current.CancellationToken ) @@ -300,7 +306,7 @@ await collection.Query.NearVector( var hybridObjs2 = await collection.Query.Hybrid( query: null, - vectors: new HybridNearVector( + vectors: new NearVectorInput( obj.Vectors["default"], Certainty: null, Distance: Convert.ToSingle(nearVec.First().Metadata.Distance!.Value + 0.001) @@ -351,8 +357,7 @@ await collection.Data.Insert( var hybridObjs = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearVector(obj.Vectors["text"]), - targetVector: ["text"], + vectors: new NearVectorInput(Vector: obj.Vectors["text"]), cancellationToken: TestContext.Current.CancellationToken ) ).Objects; @@ -362,8 +367,7 @@ await collection.Query.Hybrid( var nearVec = ( await collection.Query.NearVector( - vector: obj.Vectors["text"], - targetVector: ["text"], + obj.Vectors["text"], returnMetadata: MetadataOptions.Distance, cancellationToken: TestContext.Current.CancellationToken ) @@ -374,12 +378,11 @@ await collection.Query.NearVector( var hybridObjs2 = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearVector( + vectors: new NearVectorInput( obj.Vectors["text"], Certainty: null, Distance: Convert.ToSingle(nearVec.First().Metadata.Distance!.Value + 0.001) ), - targetVector: ["text"], returnMetadata: MetadataOptions.All, cancellationToken: TestContext.Current.CancellationToken ) @@ -415,7 +418,7 @@ await collection.Data.Insert( var hybridObjs = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearText("banana pudding"), + vectors: new NearTextInput(Query: "banana pudding"), cancellationToken: TestContext.Current.CancellationToken ) ).Objects; @@ -426,7 +429,7 @@ await collection.Query.Hybrid( var hybridObjs2 = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearText( + vectors: new NearTextInput( "banana", Certainty: null, Distance: null, @@ -471,8 +474,10 @@ await collection.Data.Insert( var hybridObjs = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearText("banana pudding"), - targetVector: ["text"], + vectors: new NearTextInput( + Query: "banana pudding", + TargetVectors: new[] { "text" } + ), cancellationToken: TestContext.Current.CancellationToken ) ).Objects; @@ -483,14 +488,14 @@ await collection.Query.Hybrid( var hybridObjs2 = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearText( + vectors: new NearTextInput( "banana", Certainty: null, Distance: null, MoveTo: new Move(force: 0.1f, concepts: ["pudding"]), - MoveAway: new Move(force: 0.1f, concepts: ["smoothie"]) + MoveAway: new Move(force: 0.1f, concepts: ["smoothie"]), + TargetVectors: new[] { "text" } ), - targetVector: ["text"], returnMetadata: MetadataOptions.All, cancellationToken: TestContext.Current.CancellationToken ) @@ -539,8 +544,7 @@ public async Task Test_Vector_Per_Target() var objs = ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearVector(vector), - targetVector: ["first", "second"], + vectors: new NearVectorInput(Vector: vector), cancellationToken: TestContext.Current.CancellationToken ) ).ToList(); @@ -554,8 +558,7 @@ await collection.Query.Hybrid( .. ( await collection.Query.Hybrid( query: null, - vectors: new HybridNearVector(vector, Certainty: null, Distance: 0.1f), - targetVector: new[] { "first", "second" }, + vectors: new NearVectorInput(Vector: vector, Certainty: null, Distance: 0.1f), cancellationToken: TestContext.Current.CancellationToken ) ).Objects, @@ -565,72 +568,66 @@ await collection.Query.Hybrid( Assert.Equal(uuid1, objs[0].UUID); } - public static TheoryData< - IHybridVectorInput, - string[] + public static IEnumerable< + TheoryDataRow > SameTargetVectorMultipleInputCombinationsData => - new( - ( - new NearVectorInput - { - { "first", new float[] { 0, 1 } }, - { "second", new float[] { 1, 0, 0 }, new float[] { 0, 0, 1 } }, - }, - new[] { "first", "second" } - ), - ( - new NearVectorInput - { - { "first", new float[] { 0, 1 }, new float[] { 0, 1 } }, - { "second", new float[] { 1, 0, 0 } }, - }, - new[] { "first", "second" } - ), - ( - new NearVectorInput - { - { "first", new float[] { 0, 1 }, new float[] { 0, 1 } }, - { "second", new float[] { 1, 0, 0 }, new float[] { 0, 0, 1 } }, - }, - new[] { "first", "second" } - ), - ( - new HybridNearVector( - new NearVectorInput + [ + .. new List + { + new NearVectorInput( + Vector: new VectorSearchInput { { "first", new float[] { 0, 1 } }, - { "second", new float[] { 1, 0, 0 }, new float[] { 0, 0, 1 } }, + { "second", new float[] { 1, 0, 0 } }, + { "second", new float[] { 0, 0, 1 } }, } ), - new[] { "first", "second" } - ), - ( - new HybridNearVector( - new NearVectorInput + new NearVectorInput( + Vector: new VectorSearchInput + { + { "first", new float[] { 0, 1 } }, + { "first", new float[] { 0, 1 } }, + { "second", new float[] { 1, 0, 0 } }, + } + ), + new NearVectorInput( + Vector: new VectorSearchInput + { + { "first", new float[] { 0, 1 } }, + { "first", new float[] { 0, 1 } }, + { "second", new float[] { 1, 0, 0 } }, + { "second", new float[] { 0, 0, 1 } }, + } + ), + new NearVectorInput( + Vector: new VectorSearchInput + { + { "first", new float[] { 0, 1 } }, + { "second", new float[] { 1, 0, 0 } }, + { "second", new float[] { 0, 0, 1 } }, + } + ), + new NearVectorInput( + Vector: new VectorSearchInput { { "first", new float[] { 0, 1 }, new float[] { 0, 1 } }, { "second", new float[] { 1, 0, 0 } }, } ), - new[] { "first", "second" } - ), - ( - new HybridNearVector( - new NearVectorInput + new NearVectorInput( + Vector: new VectorSearchInput { { "first", new float[] { 0, 1 }, new float[] { 0, 1 } }, { "second", new float[] { 1, 0, 0 }, new float[] { 0, 0, 1 } }, } ), - new[] { "first", "second" } - ) - ); + }, + ]; [Theory] [MemberData(nameof(SameTargetVectorMultipleInputCombinationsData))] public async Task Test_Same_Target_Vector_Multiple_Input_Combinations( - IHybridVectorInput nearVector, - string[] targetVector + HybridVectorInput nearVector ) { var collection = await CollectionFactory( @@ -665,7 +662,6 @@ string[] targetVector await collection.Query.Hybrid( query: null, vectors: nearVector, - targetVector: targetVector, returnMetadata: MetadataOptions.All, cancellationToken: TestContext.Current.CancellationToken ) @@ -703,7 +699,7 @@ await collection.Data.Insert( var objs = ( await collection.Query.Hybrid( "name", - vectors: Vector.Create(1f, 0f, 0f), + vectors: new float[] { 1f, 0f, 0f }, alpha: 0.7f, cancellationToken: TestContext.Current.CancellationToken ) @@ -716,7 +712,7 @@ await collection.Query.Hybrid( .. ( await collection.Query.Hybrid( "name", - vectors: Vectors.Create(1f, 0f, 0f), + vectors: new float[] { 1f, 0f, 0f }, maxVectorDistance: 0.1f, alpha: 0.7f, cancellationToken: TestContext.Current.CancellationToken @@ -759,6 +755,7 @@ public async Task Test_Hybrid_BM25_Operators() var objs = ( await collection.Query.Hybrid( "banana two", + vectors: null, alpha: 0.0f, bm25Operator: new BM25Operator.Or(MinimumMatch: 1), cancellationToken: TestContext.Current.CancellationToken @@ -808,9 +805,8 @@ await collection.Data.Insert( var res = await collection.Aggregate.Hybrid( query: "banana", - vectors: new[] { 1f, 0f, 0f, 0f }, + vectors: ("default", new[] { 1f, 0f, 0f, 0f }), maxVectorDistance: 0.5f, - targetVector: "default", returnMetrics: [Metrics.ForProperty("name").Text(count: true)], cancellationToken: TestContext.Current.CancellationToken ); diff --git a/src/Weaviate.Client.Tests/Integration/TestSingleTargetRef.cs b/src/Weaviate.Client.Tests/Integration/TestSingleTargetRef.cs index 80fbb6fa..67af61c3 100644 --- a/src/Weaviate.Client.Tests/Integration/TestSingleTargetRef.cs +++ b/src/Weaviate.Client.Tests/Integration/TestSingleTargetRef.cs @@ -5,8 +5,8 @@ namespace Weaviate.Client.Tests.Integration; [Collection("ReferenceTests")] public partial class ReferenceTests : IntegrationTests { - readonly Guid TO_UUID = new Guid("8ad0d33c-8db1-4437-87f3-72161ca2a51a"); - readonly Guid TO_UUID2 = new Guid("577887c1-4c6b-5594-aa62-f0c17883d9cf"); + readonly Guid _to_UUID = new("8ad0d33c-8db1-4437-87f3-72161ca2a51a"); + readonly Guid _to_UUID2 = new("577887c1-4c6b-5594-aa62-f0c17883d9cf"); [Fact] public async Task Test_SingleTargetReferenceOps() @@ -256,7 +256,7 @@ public async Task Test_SingleTargetReference_Complex() Property.Int("movie_id"), ], references: new Reference("forMovie", TargetCollection: movies.Name), - vectorConfig: new VectorConfig("default", new Vectorizer.Text2VecTransformers()) + vectorConfig: Configure.Vector(v => v.Text2VecTransformers()) ); var moviesData = new[] @@ -348,7 +348,7 @@ public async Task Test_SingleTargetReference_Complex() Martin Scorsese’s Goodfellas is without question one of the finest gangster movies ever made, a benchmark even. It’s that rare occasion for a genre film of this type where everything artistically comes together as one. Direction, script, editing, photography, driving soundtrack and crucially an ensemble cast firing on all cylinders. It’s grade “A” film making that marked a return to form for Scorsese whilst simultaneously showing the director at the summit of his directing abilities. -The story itself, based on Nicholas Pileggi’s non-fiction book Wiseguy, pulls absolutely no punches in its stark realisation of the Mafia lifestyle. It’s often brutal, yet funny, unflinching yet stylish, but ultimately from first frame to last it holds the attention, toying with all the human emotions during the journey, tingling the senses of those who were by 1990 fed up of popcorn movie fodder. +The story itself, based on Nicholas Pileggi’s non-fiction book Wiseguy, pulls absolutely no punches in its stark realisation of the Mafia lifestyle. It’s often brutal, yet funny, unflinching yet stylish, but ultimately from first frame to last it holds the attention, toying with all the human emotions during the journey, tingling the senses of those who were by 1990 fed up of popcorn movie fodder. It’s not romanticism here, if anything it’s a debunking of the Mafia myth, but even as the blood flows and the dialogue crackles with electricity, it always remains icy cool, brought to us by a man who had is eyes and ears open while growing up in Queens, New York in the 40s and 50s. Eccellente! 9/10", rating = (double?)9.0, @@ -378,7 +378,7 @@ public async Task Test_SingleTargetReference_Complex() new { author_username = "Ruuz", - content = @"Doesn't really work if you actually spend the time to bother thinking about it, but so long as you don't _Home Alone_ is a pretty good time. There's really no likeable character, and it's honestly pretty mean spirited, but sometimes that's what you might need to defrag over Christmas. + content = @"Doesn't really work if you actually spend the time to bother thinking about it, but so long as you don't _Home Alone_ is a pretty good time. There's really no likeable character, and it's honestly pretty mean spirited, but sometimes that's what you might need to defrag over Christmas. _Final rating:★★★ - I liked it. Would personally recommend you give it a go._", rating = (double?)6.0, @@ -428,13 +428,13 @@ await reviews.Data.Insert( // Act var fun = await reviews.Query.NearText( - "Fun for the whole family", + (AutoArray)"Fun for the whole family", limit: 2, cancellationToken: TestContext.Current.CancellationToken ); var disappointed = await reviews.Query.NearText( - "Disappointed by this movie", + (AutoArray)"Disappointed by this movie", limit: 2, returnReferences: [new QueryReference("forMovie", ["title"])], cancellationToken: TestContext.Current.CancellationToken diff --git a/src/Weaviate.Client.Tests/Integration/TestTenant.cs b/src/Weaviate.Client.Tests/Integration/TestTenant.cs index 43a6fe88..e21471ff 100644 --- a/src/Weaviate.Client.Tests/Integration/TestTenant.cs +++ b/src/Weaviate.Client.Tests/Integration/TestTenant.cs @@ -134,11 +134,11 @@ await tenant1Collection.Data.InsertMany( Assert.Equal(0, result.Count(r => r.Error != null)); var obj1 = await tenant1Collection.Query.FetchObjectByID( - result[0].ID!.Value, + result[0].UUID!.Value, cancellationToken: TestContext.Current.CancellationToken ); var obj2 = await tenant1Collection.Query.FetchObjectByID( - result[1].ID!.Value, + result[1].UUID!.Value, cancellationToken: TestContext.Current.CancellationToken ); @@ -149,13 +149,13 @@ await tenant1Collection.Data.InsertMany( Assert.Null( await tenant2Collection.Query.FetchObjectByID( - result[0].ID!.Value, + result[0].UUID!.Value, cancellationToken: TestContext.Current.CancellationToken ) ); Assert.Null( await tenant2Collection.Query.FetchObjectByID( - result[1].ID!.Value, + result[1].UUID!.Value, cancellationToken: TestContext.Current.CancellationToken ) ); diff --git a/src/Weaviate.Client.Tests/Integration/TypedClientIntegrationTests.cs b/src/Weaviate.Client.Tests/Integration/TypedClientIntegrationTests.cs index 077e9177..e182ed77 100644 --- a/src/Weaviate.Client.Tests/Integration/TypedClientIntegrationTests.cs +++ b/src/Weaviate.Client.Tests/Integration/TypedClientIntegrationTests.cs @@ -288,7 +288,7 @@ public async Task TypedWorkflow_InsertMany_MultipleObjects_Succeeds() Assert.NotNull(result); Assert.Equal(3, result.Objects.Count()); Assert.Empty(result.Errors); - Assert.All(result.Objects, entry => Assert.NotEqual(Guid.Empty, entry.ID!.Value)); + Assert.All(result.Objects, entry => Assert.NotEqual(Guid.Empty, entry.UUID!.Value)); } [Fact] diff --git a/src/Weaviate.Client.Tests/Integration/_Integration.cs b/src/Weaviate.Client.Tests/Integration/_Integration.cs index 48cd7125..dd1039e7 100644 --- a/src/Weaviate.Client.Tests/Integration/_Integration.cs +++ b/src/Weaviate.Client.Tests/Integration/_Integration.cs @@ -169,7 +169,7 @@ public async Task CollectionFactory( ArgumentException.ThrowIfNullOrEmpty(name); // Default is Vectorizer.SelfProvided - vectorConfig ??= new VectorConfig("default"); + vectorConfig ??= Configure.Vector("default", v => v.SelfProvided()); references ??= []; diff --git a/src/Weaviate.Client.Tests/Unit/Mocks/MockHelpers.cs b/src/Weaviate.Client.Tests/Unit/Mocks/MockHelpers.cs index a4bc8bd1..3a33e3ff 100644 --- a/src/Weaviate.Client.Tests/Unit/Mocks/MockHelpers.cs +++ b/src/Weaviate.Client.Tests/Unit/Mocks/MockHelpers.cs @@ -108,6 +108,30 @@ Func> asyncHandler return client; } + /// + /// Creates a WeaviateClient that captures gRPC Search requests. + /// Delegates to MockGrpcClient for consistency. + /// + internal static ( + WeaviateClient Client, + Func GetCapturedRequest + ) CreateWithSearchCapture() + { + return MockGrpcClient.CreateWithSearchCapture(); + } + + /// + /// Creates a WeaviateClient that captures gRPC Aggregate requests. + /// Delegates to MockGrpcClient for consistency. + /// + internal static ( + WeaviateClient Client, + Func GetCapturedRequest + ) CreateWithAggregateCapture() + { + return MockGrpcClient.CreateWithAggregateCapture(); + } + // Legacy CreateWithHandler overloads consolidated into unified factory above. } @@ -171,6 +195,20 @@ public static ( return CreateWithRequestCapture("/weaviate.v1.Weaviate/Search"); } + /// + /// Creates a WeaviateClient that captures Aggregate requests. + /// + public static ( + WeaviateClient Client, + Func GetCapturedRequest + ) CreateWithAggregateCapture() + { + return CreateWithRequestCapture( + "/weaviate.v1.Weaviate/Aggregate", + () => new AggregateReply { Collection = "TestCollection" } + ); + } + private static T DecodeGrpcRequest(byte[] content) where T : Google.Protobuf.IMessage, new() { diff --git a/src/Weaviate.Client.Tests/Unit/TestAggregateVectorSyntax.cs b/src/Weaviate.Client.Tests/Unit/TestAggregateVectorSyntax.cs new file mode 100644 index 00000000..d5afdb3f --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestAggregateVectorSyntax.cs @@ -0,0 +1,182 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Tests.Unit.Mocks; + +namespace Weaviate.Client.Tests.Unit; + +/// +/// Unit tests verifying AggregateClient vector search syntax with aggregation. +/// +[Collection("Unit Tests")] +public class TestAggregateVectorSyntax : IAsyncLifetime +{ + private const string CollectionName = "TestCollection"; + + private Func _getRequest = null!; + private CollectionClient _collection = null!; + + public ValueTask InitializeAsync() + { + var (client, getRequest) = MockGrpcClient.CreateWithAggregateCapture(); + _getRequest = getRequest; + _collection = client.Collections.Use(CollectionName); + return ValueTask.CompletedTask; + } + + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + #region AggregateClient.NearVector Tests + + [Fact] + public async Task Aggregate_NearVector_WithMetrics_ProducesValidRequest() + { + // Act + await _collection.Aggregate.NearVector( + new[] { 1f, 2f, 3f }, + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + } + + [Fact] + public async Task Aggregate_NearVector_WithGroupBy_ProducesValidRequest() + { + // Act + await _collection.Aggregate.NearVector( + new[] { 1f, 2f, 3f }, + groupBy: new Aggregate.GroupBy("category", Limit: 10), + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.NotNull(request.GroupBy); + Assert.Equal("category", request.GroupBy.Property); + } + + [Fact] + public async Task Aggregate_NearVector_LambdaBuilder_ProducesValidRequest() + { + // Act - Lambda builder with multi-target + await _collection.Aggregate.NearVector( + v => v.TargetVectorsAverage(("title", new[] { 1f, 2f }), ("desc", new[] { 3f, 4f })), + returnMetrics: [Metrics.ForProperty("price").Number(mean: true, count: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + } + + #endregion + + #region AggregateClient.NearText Tests + + [Fact] + public async Task Aggregate_NearText_WithMetrics_ProducesValidRequest() + { + // Act + await _collection.Aggregate.NearText( + new NearTextInput("banana"), + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal("banana", request.NearText.Query[0]); + } + + [Fact] + public async Task Aggregate_NearText_WithTargetVectors_AndGroupBy_ProducesValidRequest() + { + // Act + await _collection.Aggregate.NearText( + new NearTextInput("banana", TargetVectors: TargetVectors.Sum("title", "description")), + groupBy: new Aggregate.GroupBy("category", Limit: 5), + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.NotNull(request.NearText.Targets); + Assert.NotNull(request.GroupBy); + Assert.Equal("category", request.GroupBy.Property); + } + + #endregion + + #region AggregateClient.Hybrid Tests + + [Fact] + public async Task Aggregate_Hybrid_TextOnly_WithMetrics_ProducesValidRequest() + { + // Act + await _collection.Aggregate.Hybrid( + "search query", + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + // AggregateRequest structure validated - hybrid search parameters handled internally + } + + [Fact] + public async Task Aggregate_Hybrid_WithVectors_AndGroupBy_ProducesValidRequest() + { + // Act + await _collection.Aggregate.Hybrid( + "search query", + vectors: new[] { 1f, 2f, 3f }, + groupBy: new Aggregate.GroupBy("category", Limit: 10), + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.GroupBy); + Assert.Equal("category", request.GroupBy.Property); + } + + [Fact] + public async Task Aggregate_Hybrid_NearTextInput_WithMetrics_ProducesValidRequest() + { + // Act + await _collection.Aggregate.Hybrid( + query: null, + vectors: new NearTextInput("banana", TargetVectors: new[] { "title" }), + returnMetrics: [Metrics.ForProperty("price").Number(mean: true)], + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + // AggregateRequest structure validated - NearText parameters handled internally + } + + #endregion +} diff --git a/src/Weaviate.Client.Tests/Unit/TestBuildTargetVectors.cs b/src/Weaviate.Client.Tests/Unit/TestBuildTargetVectors.cs new file mode 100644 index 00000000..fcfd2754 --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestBuildTargetVectors.cs @@ -0,0 +1,650 @@ +using Weaviate.Client.Grpc; +using Weaviate.Client.Models; +using V1 = Weaviate.Client.Grpc.Protobuf.V1; + +namespace Weaviate.Client.Tests.Unit; + +public class BuildTargetVectorTest +{ + #region Single Vector Tests + + [Fact] + public void SingleVector_NoTargetSpecified_CreatesTargetFromVectorName() + { + // Arrange + var vector = new NamedVector("myVector", new[] { 1f, 2f, 3f }); + + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(targets); + Assert.Single(targets.TargetVectors); + Assert.Equal("myVector", targets.TargetVectors[0]); + Assert.Null(vectorForTargets); + Assert.NotNull(vectors); + Assert.Single(vectors); + Assert.Equal("myVector", vectors[0].Name); + } + + [Fact] + public void SingleVector_SingleTargetSpecified_UsesProvidedTarget() + { + // Arrange + var vector = new NamedVector("myVector", new[] { 1f, 2f, 3f }); + var targetVectors = new SimpleTargetVectors(new[] { "myVector" }); + + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(targets); + Assert.Single(targets.TargetVectors); + Assert.Equal("myVector", targets.TargetVectors[0]); + Assert.Null(vectorForTargets); + Assert.NotNull(vectors); + Assert.Single(vectors); + } + + [Fact] + public void SingleVector_DefaultName_CreatesTargetFromVectorName() + { + // Arrange - using implicit conversion which creates "default" named vector + var vector = new NamedVector(new[] { 1f, 2f, 3f }); + + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(targets); + Assert.Single(targets.TargetVectors); + Assert.Equal("default", targets.TargetVectors[0]); + } + + #endregion + + #region Multiple Vector Tests + + [Fact] + public void MultipleVectors_NoTargetSpecified_CreatesTargetsFromAllVectorNames() + { + // Arrange + var vectors = new[] + { + new NamedVector("vector1", new[] { 1f, 2f }), + new NamedVector("vector2", new[] { 3f, 4f }), + }; + + // Act + var (targets, vectorForTargets, vectorsResult) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: vectors + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(2, targets.TargetVectors.Count); + Assert.Contains("vector1", targets.TargetVectors); + Assert.Contains("vector2", targets.TargetVectors); + } + + [Fact] + public void MultipleVectors_MatchingTargets_UsesVectorForTargets() + { + // Arrange + var vectors = new[] + { + new NamedVector("vector1", new[] { 1f, 2f }), + new NamedVector("vector2", new[] { 3f, 4f }), + }; + var targetVectors = new SimpleTargetVectors(new[] { "vector1", "vector2" }); + + // Act + var (targets, vectorForTargets, vectorsResult) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: vectors + ); + + // Assert + Assert.NotNull(targets); + Assert.NotNull(vectorForTargets); + Assert.Equal(2, vectorForTargets.Count); + Assert.Null(vectorsResult); + + // VectorForTargets should be ordered by name + Assert.Equal("vector1", vectorForTargets[0].Name); + Assert.Equal("vector2", vectorForTargets[1].Name); + } + + [Fact] + public void MultipleVectors_TargetsAreOrdered() + { + // Arrange - add vectors in reverse order + var vectors = new[] + { + new NamedVector("zeta", new[] { 1f, 2f }), + new NamedVector("alpha", new[] { 3f, 4f }), + new NamedVector("beta", new[] { 5f, 6f }), + }; + + // Act + var (targets, vectorForTargets, vectorsResult) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: vectors + ); + + // Assert - targets should be sorted alphabetically + Assert.NotNull(targets); + Assert.Equal(new[] { "alpha", "beta", "zeta" }, targets.TargetVectors.ToArray()); + } + + #endregion + + #region Combination Method Tests + + [Fact] + public void SimpleTargetVectors_SumCombination_SetsCombinationMethod() + { + // Arrange + var vector = new NamedVector("myVector", new[] { 1f, 2f }); + var targetVectors = TargetVectors.Sum("myVector"); + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeSum, targets.Combination); + } + + [Fact] + public void SimpleTargetVectors_AverageCombination_SetsCombinationMethod() + { + // Arrange + var vector = new NamedVector("myVector", new[] { 1f, 2f }); + var targetVectors = TargetVectors.Average("myVector"); + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeAverage, targets.Combination); + } + + [Fact] + public void SimpleTargetVectors_MinimumCombination_SetsCombinationMethod() + { + // Arrange + var vector = new NamedVector("myVector", new[] { 1f, 2f }); + var targetVectors = TargetVectors.Minimum("myVector"); + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeMin, targets.Combination); + } + + #endregion + + #region Weighted Target Vector Tests + + [Fact] + public void WeightedTargetVectors_ManualWeights_SetsWeightsAndCombination() + { + // Arrange + var vectors = new[] + { + new NamedVector("vector1", new[] { 1f, 2f }), + new NamedVector("vector2", new[] { 3f, 4f }), + }; + var targetVectors = TargetVectors.ManualWeights(("vector1", 0.7), ("vector2", 0.3)); + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: vectors + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeManual, targets.Combination); + Assert.Equal(2, targets.WeightsForTargets.Count); + + // Weights should be ordered by target name + var weight1 = targets.WeightsForTargets.First(w => w.Target == "vector1"); + var weight2 = targets.WeightsForTargets.First(w => w.Target == "vector2"); + Assert.Equal(0.7f, weight1.Weight, precision: 5); + Assert.Equal(0.3f, weight2.Weight, precision: 5); + } + + [Fact] + public void WeightedTargetVectors_RelativeScore_SetsWeightsAndCombination() + { + // Arrange + var vectors = new[] + { + new NamedVector("vector1", new[] { 1f, 2f }), + new NamedVector("vector2", new[] { 3f, 4f }), + }; + var targetVectors = TargetVectors.RelativeScore(("vector1", 0.8), ("vector2", 0.2)); + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: vectors + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeRelativeScore, targets.Combination); + Assert.Equal(2, targets.WeightsForTargets.Count); + } + + #endregion + + #region VectorSearchInput Tests + + [Fact] + public void VectorSearchInput_SingleVector_BuildsCorrectly() + { + // Arrange + VectorSearchInput input = ("myVector", new[] { 1f, 2f, 3f }); + + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector(input); + + // Assert + Assert.NotNull(targets); + Assert.Single(targets.TargetVectors); + Assert.Equal("myVector", targets.TargetVectors[0]); + } + + [Fact] + public void VectorSearchInput_Null_ReturnsAllNulls() + { + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector( + (VectorSearchInput?)null + ); + + // Assert + Assert.Null(targets); + Assert.Null(vectorForTargets); + Assert.Null(vectors); + } + + [Fact] + public void VectorSearchInput_MultiVector_BuildsCorrectly() + { + // Arrange - ColBERT style multi-vector + VectorSearchInput input = ( + "colbert", + new[,] + { + { 1f, 2f }, + { 3f, 4f }, + } + ); + + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector(input); + + // Assert + Assert.NotNull(targets); + Assert.Single(targets.TargetVectors); + Assert.Equal("colbert", targets.TargetVectors[0]); + Assert.NotNull(vectors); + Assert.Single(vectors); + Assert.Equal(V1.Vectors.Types.VectorType.MultiFp32, vectors[0].Type); + } + + [Fact] + public void VectorSearchInput_WithBuilder_SumCombination() + { + // Arrange + var input = new VectorSearchInput.Builder().TargetVectorsSum( + ("vector1", new[] { 1f, 2f }), + ("vector2", new[] { 3f, 4f }) + ); + + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector(input); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeSum, targets.Combination); + Assert.Equal(2, targets.TargetVectors.Count); + } + + [Fact] + public void VectorSearchInput_WithBuilder_AverageCombination() + { + // Arrange + var input = new VectorSearchInput.Builder().TargetVectorsAverage( + ("vector1", new[] { 1f, 2f }), + ("vector2", new[] { 3f, 4f }) + ); + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector(input); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeAverage, targets.Combination); + } + + [Fact] + public void VectorSearchInput_WithBuilder_ManualWeights() + { + // Arrange + var input = new VectorSearchInput.Builder().TargetVectorsManualWeights( + ("vector1", 0.7, new[] { 1f, 2f }), + ("vector2", 0.3, new[] { 3f, 4f }) + ); + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector(input); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeManual, targets.Combination); + Assert.Equal(2, targets.WeightsForTargets.Count); + } + + #endregion + + #region VectorType Tests + + [Fact] + public void SingleVector_FloatArray_HasCorrectVectorType() + { + // Arrange + var vector = new NamedVector("myVector", new[] { 1f, 2f, 3f }); + + // Act + var (_, _, vectors) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(vectors); + Assert.Single(vectors); + Assert.Equal(V1.Vectors.Types.VectorType.SingleFp32, vectors[0].Type); + } + + [Fact] + public void MultiVector_2DArray_HasCorrectVectorType() + { + // Arrange + var vector = new NamedVector( + "colbert", + new[,] + { + { 1f, 2f }, + { 3f, 4f }, + } + ); + + // Act + var (_, _, vectors) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: new[] { vector } + ); + + // Assert + Assert.NotNull(vectors); + Assert.Single(vectors); + Assert.Equal(V1.Vectors.Types.VectorType.MultiFp32, vectors[0].Type); + } + + #endregion + + #region Multiple Vectors Same Name (Sum/Average) + + [Fact] + public void MultipleVectorsSameName_SumCombination_UsesVectorForTargets() + { + // Arrange - two vectors with the same name for Sum combination + var vectors = new[] + { + new NamedVector("regular", new[] { 1f, 2f }), + new NamedVector("regular", new[] { 2f, 1f }), + }; + var targetVectors = TargetVectors.Sum("regular"); + + // Act + var (targets, vectorForTargets, vectorsResult) = WeaviateGrpcClient.BuildTargetVector( + targetVector: targetVectors, + vector: vectors + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeSum, targets.Combination); + Assert.Single(targets.TargetVectors); + Assert.Equal("regular", targets.TargetVectors[0]); + + // Should use VectorForTargets, not Vectors + Assert.NotNull(vectorForTargets); + Assert.Single(vectorForTargets); // One target name + Assert.Equal("regular", vectorForTargets[0].Name); + Assert.Equal(2, vectorForTargets[0].Vectors.Count); // Two vectors for that target + + Assert.Null(vectorsResult); + } + + [Fact] + public void VectorSearchInput_SumWithSameName_UsesVectorForTargets() + { + // Arrange - using the builder syntax like in the failing test + var input = new VectorSearchInput.Builder().TargetVectorsSum( + ("regular", new[] { 1f, 2f }), + ("regular", new[] { 2f, 1f }) + ); + + // Act + var (targets, vectorForTargets, vectorsResult) = WeaviateGrpcClient.BuildTargetVector( + input + ); + + // Assert + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeSum, targets.Combination); + + // Should use VectorForTargets with both vectors + Assert.NotNull(vectorForTargets); + Assert.Single(vectorForTargets); + Assert.Equal("regular", vectorForTargets[0].Name); + Assert.Equal(2, vectorForTargets[0].Vectors.Count); + + Assert.Null(vectorsResult); + } + + #endregion + + #region Edge Cases + + [Fact] + public void EmptyVectorList_NoTargetSpecified_ReturnsEmptyTargets() + { + // Act + var (targets, vectorForTargets, vectors) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: Array.Empty() + ); + + // Assert + Assert.NotNull(targets); + Assert.Empty(targets.TargetVectors); + } + + [Fact] + public void DuplicateVectorNames_DeduplicatesTargets() + { + // Arrange - two vectors with the same name + var vectors = new[] + { + new NamedVector("same", new[] { 1f, 2f }), + new NamedVector("same", new[] { 3f, 4f }), + }; + + // Act + var (targets, _, _) = WeaviateGrpcClient.BuildTargetVector( + targetVector: null, + vector: vectors + ); + + // Assert - should deduplicate target names + Assert.NotNull(targets); + Assert.Single(targets.TargetVectors); + Assert.Equal("same", targets.TargetVectors[0]); + } + + #endregion + + #region VectorSearchInput.Combine Tests + + [Fact] + public void Combine_WithTupleArray_CreatesCorrectVectorSearchInput() + { + // Arrange + var targetVectors = TargetVectors.Sum("first", "second"); + var vectors = new (string, Vector)[] + { + ("first", new[] { 1f, 2f }), + ("second", new[] { 3f, 4f }), + }; + + // Act + var result = VectorSearchInput.Combine(targetVectors, vectors); + + // Assert + Assert.Equal(2, result.Count()); + Assert.Contains("first", result.Vectors.Keys); + Assert.Contains("second", result.Vectors.Keys); + } + + [Fact] + public void Combine_WithVectors_CreatesCorrectVectorSearchInput() + { + // Arrange + var targetVectors = TargetVectors.Average("first", "second"); + var vectors = new Vectors { { "first", new[] { 1f, 2f } }, { "second", new[] { 3f, 4f } } }; + + // Act + var result = VectorSearchInput.Combine(targetVectors, vectors); + + // Assert + Assert.Equal(2, result.Count()); + Assert.Contains("first", result.Vectors.Keys); + Assert.Contains("second", result.Vectors.Keys); + } + + [Fact] + public void Combine_WithNamedVectorList_CreatesCorrectVectorSearchInput() + { + // Arrange + var targetVectors = TargetVectors.Minimum("first", "second"); + var vectors = new List + { + new("first", new[] { 1f, 2f }), + new("second", new[] { 3f, 4f }), + new("second", new[] { 5f, 6f }), // Multiple vectors for same target + }; + + // Act + var result = VectorSearchInput.Combine(targetVectors, vectors); + + // Assert + Assert.Equal(3, result.Count()); + Assert.Single(result.Vectors["first"]); + Assert.Equal(2, result.Vectors["second"].Length); + } + + [Fact] + public void Combine_WithWeightedTargets_PreservesWeights() + { + // Arrange + var targetVectors = TargetVectors.ManualWeights(("first", 0.7), ("second", 0.3)); + var vectors = new Vectors { { "first", new[] { 1f, 2f } }, { "second", new[] { 3f, 4f } } }; + + // Act + var result = VectorSearchInput.Combine(targetVectors, vectors); + + // Assert + Assert.NotNull(result.Weights); + Assert.Equal(0.7, result.Weights["first"][0]); + Assert.Equal(0.3, result.Weights["second"][0]); + } + + [Fact] + public void Combine_WithMultipleWeightsPerTarget_PreservesAllWeights() + { + // Arrange - multiple weights for "second" target + var targetVectors = TargetVectors.ManualWeights( + ("first", 1.0), + ("second", 1.0), + ("second", 2.0) + ); + var vectors = new (string, Vector)[] + { + ("first", new[] { 1f, 2f }), + ("second", new[] { 3f, 4f }), + ("second", new[] { 5f, 6f }), + }; + + // Act + var result = VectorSearchInput.Combine(targetVectors, vectors); + + // Assert + Assert.NotNull(result.Weights); + Assert.Single(result.Weights["first"]); + Assert.Equal(2, result.Weights["second"].Count); + Assert.Equal(1.0, result.Weights["second"][0]); + Assert.Equal(2.0, result.Weights["second"][1]); + } + + [Fact] + public void Combine_PreservesTargetOrder() + { + // Arrange + var targetVectors = TargetVectors.Sum("zeta", "alpha", "beta"); + var vectors = new (string, Vector)[] + { + ("zeta", new[] { 1f }), + ("alpha", new[] { 2f }), + ("beta", new[] { 3f }), + }; + + // Act + var result = VectorSearchInput.Combine(targetVectors, vectors); + + // Assert - targets should preserve order from TargetVectors + Assert.NotNull(result.Targets); + Assert.Equal(new[] { "zeta", "alpha", "beta" }, result.Targets); + } + + #endregion +} diff --git a/src/Weaviate.Client.Tests/Unit/TestFilters.cs b/src/Weaviate.Client.Tests/Unit/TestFilters.cs index 10a15178..3955d2fc 100644 --- a/src/Weaviate.Client.Tests/Unit/TestFilters.cs +++ b/src/Weaviate.Client.Tests/Unit/TestFilters.cs @@ -124,7 +124,7 @@ public void FilterRequestCreatesProperGrpcMessage_4() // Arrange Guid id = Guid.NewGuid(); - var f = Filter.ID.ContainsAny([id]); + var f = Filter.UUID.ContainsAny([id]); var expected = new Filters() { @@ -220,7 +220,7 @@ public void Filter_Not() // Arrange var uuid1 = Guid.NewGuid(); var uuid2 = Guid.NewGuid(); - var f1 = Filter.Not(Filter.ID.ContainsAny(new[] { uuid1, uuid2 })); + var f1 = Filter.Not(Filter.UUID.ContainsAny(new[] { uuid1, uuid2 })); var expectedF1 = new Filters { diff --git a/src/Weaviate.Client.Tests/Unit/TestGenerateVectorSyntax.cs b/src/Weaviate.Client.Tests/Unit/TestGenerateVectorSyntax.cs new file mode 100644 index 00000000..94a41619 --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestGenerateVectorSyntax.cs @@ -0,0 +1,219 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Tests.Unit.Mocks; + +namespace Weaviate.Client.Tests.Unit; + +/// +/// Unit tests verifying GenerateClient vector search syntax with generative prompts. +/// +[Collection("Unit Tests")] +public class TestGenerateVectorSyntax : IAsyncLifetime +{ + private const string CollectionName = "TestCollection"; + + private Func _getRequest = null!; + private CollectionClient _collection = null!; + + public ValueTask InitializeAsync() + { + var (client, getRequest) = MockGrpcClient.CreateWithSearchCapture(); + _getRequest = getRequest; + _collection = client.Collections.Use(CollectionName); + return ValueTask.CompletedTask; + } + + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + #region GenerateClient.NearVector Tests + + [Fact] + public async Task Generate_NearVector_WithSinglePrompt_ProducesValidRequest() + { + // Act + await _collection.Generate.NearVector( + new[] { 1f, 2f, 3f }, + singlePrompt: "Summarize this item", + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.NotNull(request.Generative); + Assert.NotNull(request.Generative.Single); + Assert.Equal("Summarize this item", request.Generative.Single.Prompt); + } + + [Fact] + public async Task Generate_NearVector_WithGroupedTask_ProducesValidRequest() + { + // Act + await _collection.Generate.NearVector( + new[] { 1f, 2f, 3f }, + groupedTask: new GroupedTask("Summarize all items"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.NotNull(request.Generative); + Assert.NotNull(request.Generative.Grouped); + Assert.Equal("Summarize all items", request.Generative.Grouped.Task); + } + + [Fact] + public async Task Generate_NearVector_LambdaBuilder_WithPrompts_ProducesValidRequest() + { + // Act - Lambda builder with Sum combination and generative prompts + await _collection.Generate.NearVector( + v => v.TargetVectorsSum(("title", new[] { 1f, 2f }), ("desc", new[] { 3f, 4f })), + singlePrompt: "Describe this", + groupedTask: new GroupedTask("Summarize all"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(CollectionName, request.Collection); + Assert.NotNull(request.NearVector); + Assert.NotNull(request.NearVector.Targets); + Assert.NotNull(request.Generative); + + // Verify Single prompt structure + Assert.NotNull(request.Generative.Single); + Assert.Equal("Describe this", request.Generative.Single.Prompt); + Assert.False(request.Generative.Single.Debug); // Default is false + + // Verify Grouped task structure + Assert.NotNull(request.Generative.Grouped); + Assert.Equal("Summarize all", request.Generative.Grouped.Task); + Assert.False(request.Generative.Grouped.Debug); // Default is false + } + + #endregion + + #region GenerateClient.NearText Tests + + [Fact] + public async Task Generate_NearText_WithSinglePrompt_ProducesValidRequest() + { + // Act + await _collection.Generate.NearText( + new NearTextInput("banana"), + singlePrompt: "Describe this fruit", + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal("banana", request.NearText.Query[0]); + Assert.NotNull(request.Generative); + Assert.NotNull(request.Generative.Single); + Assert.Equal("Describe this fruit", request.Generative.Single.Prompt); + } + + [Fact] + public async Task Generate_NearText_WithTargetVectors_AndPrompts_ProducesValidRequest() + { + // Act + await _collection.Generate.NearText( + new NearTextInput("banana", TargetVectors: TargetVectors.Sum("title", "description")), + singlePrompt: "Explain this", + groupedTask: new GroupedTask("Compare all"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.NotNull(request.NearText.Targets); + Assert.NotNull(request.Generative); + Assert.NotNull(request.Generative.Single); + Assert.Equal("Explain this", request.Generative.Single.Prompt); + Assert.NotNull(request.Generative.Grouped); + Assert.Equal("Compare all", request.Generative.Grouped.Task); + } + + #endregion + + #region GenerateClient.Hybrid Tests + + [Fact] + public async Task Generate_Hybrid_TextOnly_WithPrompt_ProducesValidRequest() + { + // Act + await _collection.Generate.Hybrid( + "search query", + singlePrompt: "Summarize this result", + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("search query", request.HybridSearch.Query); + Assert.NotNull(request.Generative); + Assert.NotNull(request.Generative.Single); + Assert.Equal("Summarize this result", request.Generative.Single.Prompt); + } + + [Fact] + public async Task Generate_Hybrid_WithVectors_AndPrompts_ProducesValidRequest() + { + // Act + await _collection.Generate.Hybrid( + "search query", + v => + v.NearVector() + .TargetVectorsSum(("title", new[] { 1f, 2f }), ("desc", new[] { 3f, 4f })), + singlePrompt: "Explain", + groupedTask: new GroupedTask("Compare"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("search query", request.HybridSearch.Query); + Assert.NotNull(request.Generative); + Assert.NotNull(request.Generative.Single); + Assert.Equal("Explain", request.Generative.Single.Prompt); + Assert.NotNull(request.Generative.Grouped); + Assert.Equal("Compare", request.Generative.Grouped.Task); + } + + [Fact] + public async Task Generate_Hybrid_NearTextInput_WithPrompt_ProducesValidRequest() + { + // Act - NearTextInput needs to be wrapped in HybridVectorInput + await _collection.Generate.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText( + new NearTextInput("banana", TargetVectors: new[] { "title", "description" }) + ), + singlePrompt: "Describe this item", + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.NotNull(request.Generative); + Assert.NotNull(request.Generative.Single); + Assert.Equal("Describe this item", request.Generative.Single.Prompt); + } + + #endregion +} diff --git a/src/Weaviate.Client.Tests/Unit/TestGenerativeShortcuts.cs b/src/Weaviate.Client.Tests/Unit/TestGenerativeShortcuts.cs index 9df13c95..e2d2af7c 100644 --- a/src/Weaviate.Client.Tests/Unit/TestGenerativeShortcuts.cs +++ b/src/Weaviate.Client.Tests/Unit/TestGenerativeShortcuts.cs @@ -1,5 +1,3 @@ -using Google.Protobuf; -using Weaviate.Client.Grpc; using Weaviate.Client.Grpc.Protobuf.V1; using Weaviate.Client.Models; using Weaviate.Client.Models.Generative; @@ -124,7 +122,7 @@ public async Task GenerateClient_NearText_WithStringPromptAndProvider_EnrichesPr await client .Collections.Use("TestCollection") .Generate.NearText( - text: "artificial intelligence", + query: "artificial intelligence", limit: 5, singlePrompt: "Explain this concept", provider: provider, diff --git a/src/Weaviate.Client.Tests/Unit/TestGrpcCancellation.cs b/src/Weaviate.Client.Tests/Unit/TestGrpcCancellation.cs index 2af61ab1..9d8df1fd 100644 --- a/src/Weaviate.Client.Tests/Unit/TestGrpcCancellation.cs +++ b/src/Weaviate.Client.Tests/Unit/TestGrpcCancellation.cs @@ -1,3 +1,4 @@ +#if ENABLE_INTERNAL_TESTS using Weaviate.Client.Grpc; using Weaviate.Client.Grpc.Protobuf.V1; using Weaviate.Client.Tests.Unit.Mocks; @@ -71,3 +72,4 @@ public async Task Search_NoCancellation_Succeeds() Assert.NotNull(reply); } } +#endif diff --git a/src/Weaviate.Client.Tests/Unit/TestHybridSearchInputSyntax.cs b/src/Weaviate.Client.Tests/Unit/TestHybridSearchInputSyntax.cs new file mode 100644 index 00000000..41c71f5f --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestHybridSearchInputSyntax.cs @@ -0,0 +1,2152 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Tests.Unit.Mocks; +using V1 = Weaviate.Client.Grpc.Protobuf.V1; + +namespace Weaviate.Client.Tests.Unit; + +/// +/// Unit tests verifying the hybrid search input syntax combinations compile correctly +/// and produce the expected gRPC request structure. +/// +[Collection("Unit Tests")] +public class TestHybridSearchInputSyntax : IAsyncLifetime +{ + private const string CollectionName = "TestCollection"; + + private Func _getRequest = null!; + private CollectionClient _collection = null!; + + public ValueTask InitializeAsync() + { + var (client, getRequest) = MockGrpcClient.CreateWithSearchCapture(); + _getRequest = getRequest; + _collection = client.Collections.Use(CollectionName); + return ValueTask.CompletedTask; + } + + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + #region Query-Only Tests + + [Fact] + public async Task Hybrid_QueryOnly_ProducesValidRequest() + { + // Act + await _collection.Query.Hybrid( + query: "search text", + vectors: null, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(CollectionName, request.Collection); + Assert.Equal("search text", request.HybridSearch.Query); + Assert.Null(request.HybridSearch.NearText); + Assert.Null(request.HybridSearch.NearVector); + Assert.Equal(0.7f, request.HybridSearch.Alpha, precision: 5); // Default alpha + } + + [Fact] + public async Task Hybrid_QueryOnly_WithAlpha_ProducesValidRequest() + { + // Act + await _collection.Query.Hybrid( + query: "search text", + vectors: (HybridVectorInput?)null, + alpha: 0.5f, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("search text", request.HybridSearch.Query); + Assert.Equal(0.5f, request.HybridSearch.Alpha, precision: 5); + } + + [Fact] + public async Task Hybrid_QueryOnly_WithFusionType_ProducesValidRequest() + { + // Act + await _collection.Query.Hybrid( + query: "search text", + vectors: (HybridVectorInput?)null, + fusionType: HybridFusion.RelativeScore, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.Hybrid.Types.FusionType.RelativeScore, request.HybridSearch.FusionType); + } + + #endregion + + #region VectorSearchInput - Float Array Tests + + [Fact] + public async Task Hybrid_VectorSearchInput_FloatArray_ProducesValidRequest() + { + // Arrange + float[] vector = [1f, 2f, 3f]; + + // Act - implicit conversion from float[] to HybridVectorInput + await _collection.Query.Hybrid( + query: null, + vectors: vector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("default", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_DoubleArray_ProducesValidRequest() + { + // Arrange + double[] vector = [1.0, 2.0, 3.0]; + + // Act - implicit conversion from double[] to HybridVectorInput + await _collection.Query.Hybrid( + query: null, + vectors: vector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + } + + #endregion + + #region VectorSearchInput - Named Tuple Tests + + [Fact] + public async Task Hybrid_VectorSearchInput_NamedTuple_ProducesValidRequest() + { + // Act - implicit conversion from (string, float[]) tuple + await _collection.Query.Hybrid( + query: null, + vectors: ("myVector", new float[] { 1f, 2f, 3f }), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("myVector", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_NamedTuple_Double_ProducesValidRequest() + { + // Act - implicit conversion from (string, double[]) tuple + await _collection.Query.Hybrid( + query: null, + vectors: ("myVector", new double[] { 1.0, 2.0, 3.0 }), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("myVector", request.HybridSearch.Targets.TargetVectors); + } + + #endregion + + #region VectorSearchInput - Multi-Vector (ColBERT) Tests + + [Fact] + public async Task Hybrid_VectorSearchInput_MultiVector_Float2D_ProducesValidRequest() + { + // Arrange + float[,] multiVector = + { + { 1f, 2f }, + { 3f, 4f }, + }; + + // Act - implicit conversion from (string, float[,]) tuple for ColBERT + await _collection.Query.Hybrid( + query: null, + vectors: ("colbert", multiVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("colbert", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_MultiVector_Double2D_ProducesValidRequest() + { + // Arrange + double[,] multiVector = + { + { 1.0, 2.0 }, + { 3.0, 4.0 }, + }; + + // Act - implicit conversion from (string, double[,]) tuple for ColBERT + await _collection.Query.Hybrid( + query: null, + vectors: ("colbert", multiVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("colbert", request.HybridSearch.Targets.TargetVectors); + } + + #endregion + + #region VectorSearchInput - NamedVector Tests + + [Fact] + public async Task Hybrid_VectorSearchInput_NamedVector_ProducesValidRequest() + { + // Arrange + var namedVector = new NamedVector("myVector", [1f, 2f, 3f]); + + // Act - implicit conversion from NamedVector + await _collection.Query.Hybrid( + query: null, + vectors: namedVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("myVector", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Vectors_ProducesValidRequest() + { + // Arrange + var vectors = new Vectors { { "vector1", new float[] { 1f, 2f } } }; + + // Act - implicit conversion from Vectors + await _collection.Query.Hybrid( + query: null, + vectors: vectors, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("vector1", request.HybridSearch.Targets.TargetVectors); + } + + #endregion + + #region VectorSearchInput Builder - Combination Methods + + [Fact] + public async Task Hybrid_VectorSearchInput_Builder_Sum_ProducesValidRequest() + { + // Arrange + var input = VectorSearchInput.Combine( + TargetVectors.Sum("vector1", "vector2"), + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + Assert.Contains("vector1", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("vector2", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Builder_Average_ProducesValidRequest() + { + // Arrange + var input = VectorSearchInput.Combine( + TargetVectors.Average("vector1", "vector2"), + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Builder_Minimum_ProducesValidRequest() + { + // Arrange + var input = VectorSearchInput.Combine( + TargetVectors.Minimum("vector1", "vector2"), + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeMin, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Builder_ManualWeights_ProducesValidRequest() + { + // Arrange + var input = VectorSearchInput.Combine( + TargetVectors.ManualWeights(("vector1", 0.7), ("vector2", 0.3)), + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Builder_RelativeScore_ProducesValidRequest() + { + // Arrange + var input = VectorSearchInput.Combine( + TargetVectors.RelativeScore(("vector1", 0.8), ("vector2", 0.2)), + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal( + V1.CombinationMethod.TypeRelativeScore, + request.HybridSearch.Targets.Combination + ); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Builder_SameNameSum_ProducesValidRequest() + { + // Arrange - multiple vectors with same name for sum combination + var input = VectorSearchInput.Combine( + TargetVectors.Sum("regular"), + ("regular", new float[] { 1f, 2f }), + ("regular", new float[] { 2f, 1f }) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + Assert.Single(request.HybridSearch.Targets.TargetVectors); + Assert.Equal("regular", request.HybridSearch.Targets.TargetVectors[0]); + } + + #endregion + + #region NearTextInput Tests + + [Fact] + public async Task Hybrid_NearTextInput_String_ProducesValidRequest() + { + // Act - implicit conversion from string to NearTextInput via HybridVectorInput + await _collection.Query.Hybrid( + query: null, + vectors: (HybridVectorInput)"semantic search", + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Contains("semantic search", request.HybridSearch.NearText.Query); + } + + [Fact] + public async Task Hybrid_NearTextInput_Explicit_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput("semantic search", Distance: 0.5f); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Contains("semantic search", request.HybridSearch.NearText.Query); + Assert.Equal(0.5, request.HybridSearch.NearText.Distance, precision: 5); + } + + [Fact] + public async Task Hybrid_NearTextInput_WithCertainty_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput("semantic search", Certainty: 0.8f); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Equal(0.8, request.HybridSearch.NearText.Certainty, precision: 5); + } + + [Fact] + public async Task Hybrid_NearTextInput_WithTargetVectors_Sum_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput( + "semantic search", + TargetVectors: TargetVectors.Sum("vector1", "vector2") + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets (matching Python client behavior) + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearTextInput_WithTargetVectors_Average_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput( + "semantic search", + TargetVectors: TargetVectors.Average("vector1", "vector2") + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearTextInput_WithTargetVectors_Minimum_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput( + "semantic search", + TargetVectors: TargetVectors.Minimum("vector1", "vector2") + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeMin, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearTextInput_WithTargetVectors_ManualWeights_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput( + "semantic search", + TargetVectors: TargetVectors.ManualWeights(("vector1", 0.7), ("vector2", 0.3)) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task Hybrid_NearTextInput_WithTargetVectors_RelativeScore_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput( + "semantic search", + TargetVectors: TargetVectors.RelativeScore(("vector1", 0.8), ("vector2", 0.2)) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal( + V1.CombinationMethod.TypeRelativeScore, + request.HybridSearch.Targets.Combination + ); + } + + [Fact] + public async Task Hybrid_NearTextInput_WithLambdaBuilder_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput( + "semantic search", + TargetVectors: t => t.Sum("vector1", "vector2") + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearTextInput_SimpleString_FromDocs_ProducesValidRequest() + { + // Validates corrected docs example at line 449-451 + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: new NearTextInput("banana"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.True(string.IsNullOrEmpty(request.HybridSearch.Query)); // Verify query is null or empty + Assert.NotNull(request.HybridSearch.NearText); + Assert.Contains("banana", request.HybridSearch.NearText.Query); + } + + #endregion + + #region NearVectorInput Tests + + [Fact] + public async Task Hybrid_NearVectorInput_Basic_ProducesValidRequest() + { + // Arrange + var nearVector = new NearVectorInput(new float[] { 1f, 2f, 3f }); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearVector(nearVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + } + + [Fact] + public async Task Hybrid_NearVectorInput_WithDistance_ProducesValidRequest() + { + // Arrange + var nearVector = new NearVectorInput(new float[] { 1f, 2f, 3f }, Distance: 0.5f); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearVector(nearVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(0.5, request.HybridSearch.NearVector.Distance, precision: 5); + } + + [Fact] + public async Task Hybrid_NearVectorInput_WithCertainty_ProducesValidRequest() + { + // Arrange + var nearVector = new NearVectorInput(new float[] { 1f, 2f, 3f }, Certainty: 0.9f); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearVector(nearVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(0.9, request.HybridSearch.NearVector.Certainty, precision: 5); + } + + [Fact] + public async Task Hybrid_NearVectorInput_Named_ProducesValidRequest() + { + // Arrange + VectorSearchInput vectorInput = ("myVector", new float[] { 1f, 2f, 3f }); + var nearVector = new NearVectorInput(vectorInput, Distance: 0.5f); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: nearVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets (matching Python client behavior) + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("myVector", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_NearVectorInput_MultiTarget_Sum_ProducesValidRequest() + { + // Arrange + var vectorInput = VectorSearchInput.Combine( + TargetVectors.Sum("vector1", "vector2"), + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ); + var nearVector = new NearVectorInput(vectorInput); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearVector(nearVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearVectorInput_MultiTarget_ManualWeights_ProducesValidRequest() + { + // Arrange + var vectorInput = VectorSearchInput.Combine( + TargetVectors.ManualWeights(("vector1", 0.6), ("vector2", 0.4)), + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ); + var nearVector = new NearVectorInput(vectorInput); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearVector(nearVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task Hybrid_NearVectorInput_WithCertainty_FromDocs_ProducesValidRequest() + { + // Validates new docs example for NearVectorInput in Hybrid + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: new NearVectorInput(new[] { 1f, 2f, 3f }, Certainty: 0.8f), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.True(string.IsNullOrEmpty(request.HybridSearch.Query)); // Verify query is null or empty + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(0.8f, request.HybridSearch.NearVector.Certainty, precision: 5); + } + + [Fact] + public async Task Hybrid_NearVectorInput_MultiTarget_FromDocs_ProducesValidRequest() + { + // Validates new docs example for NearVectorInput with multi-target + var vectorInput = VectorSearchInput.Combine( + TargetVectors.ManualWeights(("title", 0.7), ("description", 0.3)), + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: "search query", + vectors: new NearVectorInput(vectorInput, Distance: 0.5f), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("search query", request.HybridSearch.Query); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(0.5f, request.HybridSearch.NearVector.Distance, precision: 5); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + } + + #endregion + + #region Query + Vector Combinations + + [Fact] + public async Task Hybrid_QueryAndVectorSearchInput_ProducesValidRequest() + { + // Act + await _collection.Query.Hybrid( + query: "keyword search", + vectors: new float[] { 1f, 2f, 3f }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("keyword search", request.HybridSearch.Query); + Assert.NotNull(request.HybridSearch.Targets); + } + + [Fact] + public async Task Hybrid_QueryAndNearText_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput("semantic meaning"); + + // Act + await _collection.Query.Hybrid( + query: "keyword", + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("keyword", request.HybridSearch.Query); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Contains("semantic meaning", request.HybridSearch.NearText.Query); + } + + [Fact] + public async Task Hybrid_QueryAndNearVector_ProducesValidRequest() + { + // Arrange + var nearVector = new NearVectorInput(new float[] { 1f, 2f, 3f }); + + // Act + await _collection.Query.Hybrid( + query: "keyword", + vectors: HybridVectorInput.FromNearVector(nearVector), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("keyword", request.HybridSearch.Query); + Assert.NotNull(request.HybridSearch.NearVector); + } + + [Fact] + public async Task Hybrid_QueryAndMultiTargetVectors_ProducesValidRequest() + { + // Arrange + var vectorInput = VectorSearchInput.Combine( + TargetVectors.Sum("title", "content"), + ("title", new float[] { 1f, 2f }), + ("content", new float[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: "search query", + vectors: HybridVectorInput.FromVectorSearch(vectorInput), + alpha: 0.7f, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("search query", request.HybridSearch.Query); + Assert.Equal(0.7f, request.HybridSearch.Alpha, precision: 5); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + #endregion + + #region VectorSearchInput.Combine Tests + + [Fact] + public async Task Hybrid_VectorSearchInput_Combine_WithTuples_ProducesValidRequest() + { + // Arrange + var targetVectors = TargetVectors.Sum("first", "second"); + var input = VectorSearchInput.Combine( + targetVectors, + ("first", new float[] { 1f, 2f }), + ("second", new float[] { 3f, 4f }) + ); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Combine_WithVectors_ProducesValidRequest() + { + // Arrange + var targetVectors = TargetVectors.Average("first", "second"); + var vectors = new Vectors + { + { "first", new float[] { 1f, 2f } }, + { "second", new float[] { 3f, 4f } }, + }; + var input = VectorSearchInput.Combine(targetVectors, vectors); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_VectorSearchInput_Combine_WithWeights_ProducesValidRequest() + { + // Arrange + var targetVectors = TargetVectors.ManualWeights(("first", 0.7), ("second", 0.3)); + var vectors = new Vectors + { + { "first", new float[] { 1f, 2f } }, + { "second", new float[] { 3f, 4f } }, + }; + var input = VectorSearchInput.Combine(targetVectors, vectors); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromVectorSearch(input), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + #endregion + + #region Lambda Builder Syntax Tests + + [Fact] + public async Task Hybrid_LambdaBuilder_Sum_ProducesValidRequest() + { + // Act - using lambda directly in Hybrid call + await _collection.Query.Hybrid( + query: "search query", + vectors: b => + b.NearVector() + .TargetVectorsSum( + ("vector1", new float[] { 1f, 2f }), + ("vector2", new float[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("search query", request.HybridSearch.Query); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + Assert.Contains("vector1", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("vector2", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_LambdaBuilder_ManualWeights_ProducesValidRequest() + { + // Act - using lambda directly in Hybrid call with ManualWeights + await _collection.Query.Hybrid( + query: null, + vectors: b => + b.NearVector() + .TargetVectorsManualWeights( + ("title", 0.7, new float[] { 1f, 2f }), + ("description", 0.3, new float[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + Assert.Contains("title", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("description", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_LambdaBuilder_Average_ProducesValidRequest() + { + // Act + await _collection.Query.Hybrid( + query: "test", + vectors: b => + b.NearVector() + .TargetVectorsAverage( + ("vec1", new float[] { 1f }), + ("vec2", new float[] { 2f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + #endregion + + #region Validation Tests + + [Fact] + public async Task Hybrid_NullQueryAndNullVectors_ThrowsArgumentException() + { + // Act & Assert + await Assert.ThrowsAsync(() => + _collection.Query.Hybrid( + query: null, + vectors: (HybridVectorInput?)null, + cancellationToken: TestContext.Current.CancellationToken + ) + ); + } + + [Fact] + public void HybridVectorInput_MultipleTypes_ThrowsArgumentException() + { + // This test verifies the discriminated union constraint + // HybridVectorInput can only hold one of the three types + + // Arrange + var vectorSearch = new VectorSearchInput { { "test", new float[] { 1f, 2f } } }; + var nearText = new NearTextInput("test"); + + // Act - try to create HybridVectorInput with both (this should not compile, + // but since it's a runtime check, we verify the factory methods work correctly) + var input1 = HybridVectorInput.FromVectorSearch(vectorSearch); + var input2 = HybridVectorInput.FromNearText(nearText); + + // Assert - each input should only have its respective type set + Assert.NotNull(input1.VectorSearch); + Assert.Null(input1.NearText); + Assert.Null(input1.NearVector); + + Assert.Null(input2.VectorSearch); + Assert.NotNull(input2.NearText); + Assert.Null(input2.NearVector); + } + + #endregion + + #region Implicit Conversion Coverage Tests + + [Fact] + public async Task Hybrid_ImplicitConversion_Vector_ProducesValidRequest() + { + // Arrange + Vector vector = new float[] { 1f, 2f, 3f }; + + // Act - implicit conversion from Vector + await _collection.Query.Hybrid( + query: null, + vectors: vector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + } + + [Fact] + public async Task Hybrid_ImplicitConversion_TargetVectorsArray_ProducesValidRequest() + { + // Arrange + // Create NearTextInput with implicit conversion from string[] to TargetVectors + TargetVectors targets = new[] { "vector1", "vector2" }; + var nearText = new NearTextInput("query", TargetVectors: targets); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: HybridVectorInput.FromNearText(nearText), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - targets are moved to Hybrid.Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Contains("vector1", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("vector2", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_ImplicitConversion_String_ToNearText_ProducesValidRequest() + { + // Act - implicit conversion from string to HybridVectorInput (via NearText) + await _collection.Query.Hybrid( + query: null, + vectors: "semantic search query", + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Contains("semantic search query", request.HybridSearch.NearText.Query); + } + + [Fact] + public async Task Hybrid_ImplicitConversion_NamedVectorArray_ProducesValidRequest() + { + // Arrange + NamedVector[] namedVectors = + [ + new NamedVector("first", [1f, 2f]), + new NamedVector("second", [3f, 4f]), + ]; + + // Act - implicit conversion from NamedVector[] to VectorSearchInput to HybridVectorInput + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)namedVectors, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("second", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_ImplicitConversion_NearTextInput_ProducesValidRequest() + { + // Arrange + var nearText = new NearTextInput("semantic query", Distance: 0.3f); + + // Act - implicit conversion from NearTextInput to HybridVectorInput + await _collection.Query.Hybrid( + query: null, + vectors: nearText, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Equal(0.3, request.HybridSearch.NearText.Distance, precision: 5); + } + + [Fact] + public async Task Hybrid_ImplicitConversion_NearVectorInput_ProducesValidRequest() + { + // Arrange + var nearVector = new NearVectorInput(new float[] { 1f, 2f, 3f }, Distance: 0.4f); + + // Act - implicit conversion from NearVectorInput to HybridVectorInput + await _collection.Query.Hybrid( + query: null, + vectors: nearVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(0.4, request.HybridSearch.NearVector.Distance, precision: 5); + } + + [Fact] + public async Task Hybrid_ImplicitConversion_VectorSearchInput_ProducesValidRequest() + { + // Arrange + var vectorSearch = new VectorSearchInput { { "named", new float[] { 1f, 2f } } }; + + // Act - implicit conversion from VectorSearchInput to HybridVectorInput + await _collection.Query.Hybrid( + query: null, + vectors: vectorSearch, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("named", request.HybridSearch.Targets.TargetVectors); + } + + #endregion + + #region VectorSearchInput Dictionary Implicit Conversion Tests + + [Fact] + public async Task Hybrid_DictionaryStringFloatArray_ProducesValidRequest() + { + // Arrange + Dictionary dict = new() { ["first"] = [1f, 2f], ["second"] = [3f, 4f] }; + + // Act - implicit conversion from Dictionary + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("second", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringDoubleArray_ProducesValidRequest() + { + // Arrange + Dictionary dict = new() + { + ["first"] = [1.0, 2.0], + ["second"] = [3.0, 4.0], + }; + + // Act - implicit conversion from Dictionary + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("second", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringVectorArray_ProducesValidRequest() + { + // Arrange + Dictionary dict = new() + { + ["first"] = [new float[] { 1f, 2f }, new float[] { 5f, 6f }], + ["second"] = [new float[] { 3f, 4f }], + }; + + // Act - implicit conversion from Dictionary + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("second", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringFloat2D_ProducesValidRequest() + { + // Arrange - 2D arrays for ColBERT multi-vector + Dictionary dict = new() + { + ["colbert1"] = new float[,] + { + { 1f, 2f }, + { 3f, 4f }, + }, + ["colbert2"] = new float[,] + { + { 5f, 6f }, + { 7f, 8f }, + }, + }; + + // Act - implicit conversion from Dictionary + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("colbert1", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("colbert2", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringDouble2D_ProducesValidRequest() + { + // Arrange - 2D arrays for ColBERT multi-vector + Dictionary dict = new() + { + ["colbert1"] = new double[,] + { + { 1.0, 2.0 }, + { 3.0, 4.0 }, + }, + }; + + // Act - implicit conversion from Dictionary + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("colbert1", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringEnumerableFloatArray_ProducesValidRequest() + { + // Arrange + Dictionary> dict = new() + { + ["first"] = new List { new[] { 1f, 2f }, new[] { 5f, 6f } }, + ["second"] = new List { new[] { 3f, 4f } }, + }; + + // Act - implicit conversion from Dictionary> + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("second", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringEnumerableDoubleArray_ProducesValidRequest() + { + // Arrange + Dictionary> dict = new() + { + ["first"] = new List { new[] { 1.0, 2.0 } }, + }; + + // Act - implicit conversion from Dictionary> + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringEnumerableFloat2D_ProducesValidRequest() + { + // Arrange - IEnumerable of 2D arrays for multiple ColBERT vectors + Dictionary> dict = new() + { + ["colbert"] = new List + { + new float[,] + { + { 1f, 2f }, + { 3f, 4f }, + }, + new float[,] + { + { 5f, 6f }, + { 7f, 8f }, + }, + }, + }; + + // Act - implicit conversion from Dictionary> + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("colbert", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_DictionaryStringEnumerableDouble2D_ProducesValidRequest() + { + // Arrange + Dictionary> dict = new() + { + ["colbert"] = new List + { + new double[,] + { + { 1.0, 2.0 }, + { 3.0, 4.0 }, + }, + }, + }; + + // Act - implicit conversion from Dictionary> + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)dict, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("colbert", request.HybridSearch.Targets.TargetVectors); + } + + #endregion + + #region FactoryFn Implicit Conversion Tests + + [Fact] + public async Task Hybrid_FactoryFn_Sum_ProducesValidRequest() + { + // Act - using FactoryFn implicit conversion + VectorSearchInput.FactoryFn factory = b => + b.TargetVectorsSum( + ("first", new float[] { 1f, 2f }), + ("second", new float[] { 3f, 4f }) + ); + + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)factory, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("second", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_FactoryFn_Average_ProducesValidRequest() + { + // Act - using FactoryFn implicit conversion + VectorSearchInput.FactoryFn factory = b => + b.TargetVectorsAverage( + ("first", new float[] { 1f, 2f }), + ("second", new float[] { 3f, 4f }) + ); + + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)factory, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_FactoryFn_Minimum_ProducesValidRequest() + { + // Act - using FactoryFn implicit conversion + VectorSearchInput.FactoryFn factory = b => + b.TargetVectorsMinimum( + ("first", new float[] { 1f, 2f }), + ("second", new float[] { 3f, 4f }) + ); + + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)factory, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeMin, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_FactoryFn_ManualWeights_ProducesValidRequest() + { + // Act - using FactoryFn implicit conversion + VectorSearchInput.FactoryFn factory = b => + b.TargetVectorsManualWeights( + ("first", 0.7, new float[] { 1f, 2f }), + ("second", 0.3, new float[] { 3f, 4f }) + ); + + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)factory, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task Hybrid_FactoryFn_RelativeScore_ProducesValidRequest() + { + // Act - using FactoryFn implicit conversion + VectorSearchInput.FactoryFn factory = b => + b.TargetVectorsRelativeScore( + ("first", 0.6, new float[] { 1f, 2f }), + ("second", 0.4, new float[] { 3f, 4f }) + ); + + await _collection.Query.Hybrid( + query: null, + vectors: (VectorSearchInput)factory, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal( + V1.CombinationMethod.TypeRelativeScore, + request.HybridSearch.Targets.Combination + ); + } + + #endregion + + #region TargetVectors Combination Methods Tests (All via implicit conversions) + + [Fact] + public async Task Hybrid_TargetVectors_Sum_ViaLambda_ProducesValidRequest() + { + // Act - using lambda builder for TargetVectors + var nearText = new NearTextInput("query", TargetVectors: t => t.Sum("vec1", "vec2")); + + await _collection.Query.Hybrid( + query: null, + vectors: nearText, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_TargetVectors_Average_ViaLambda_ProducesValidRequest() + { + // Act + var nearText = new NearTextInput("query", TargetVectors: t => t.Average("vec1", "vec2")); + + await _collection.Query.Hybrid( + query: null, + vectors: nearText, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_TargetVectors_Minimum_ViaLambda_ProducesValidRequest() + { + // Act + var nearText = new NearTextInput("query", TargetVectors: t => t.Minimum("vec1", "vec2")); + + await _collection.Query.Hybrid( + query: null, + vectors: nearText, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeMin, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_TargetVectors_ManualWeights_ViaLambda_ProducesValidRequest() + { + // Act + var nearText = new NearTextInput( + "query", + TargetVectors: t => t.ManualWeights(("vec1", 0.6), ("vec2", 0.4)) + ); + + await _collection.Query.Hybrid( + query: null, + vectors: nearText, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task Hybrid_TargetVectors_RelativeScore_ViaLambda_ProducesValidRequest() + { + // Act + var nearText = new NearTextInput( + "query", + TargetVectors: t => t.RelativeScore(("vec1", 0.8), ("vec2", 0.2)) + ); + + await _collection.Query.Hybrid( + query: null, + vectors: nearText, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal( + V1.CombinationMethod.TypeRelativeScore, + request.HybridSearch.Targets.Combination + ); + } + + #endregion + + #region Collection Initializer Syntax Tests + + [Fact] + public async Task Hybrid_CollectionInitializer_SingleVector_ProducesValidRequest() + { + // Arrange - using collection initializer syntax + var input = new VectorSearchInput { { "named", new float[] { 1f, 2f, 3f } } }; + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: input, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("named", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_CollectionInitializer_MultipleVectors_ProducesValidRequest() + { + // Arrange - using collection initializer syntax with multiple vectors + var input = new VectorSearchInput + { + { "first", new float[] { 1f, 2f } }, + { "second", new float[] { 3f, 4f } }, + { "third", new float[] { 5f, 6f } }, + }; + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: input, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("first", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("second", request.HybridSearch.Targets.TargetVectors); + Assert.Contains("third", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_CollectionInitializer_MultipleVectorsSameName_ProducesValidRequest() + { + // Arrange - using collection initializer with multiple vectors for same target + var input = new VectorSearchInput + { + { "same", new float[] { 1f, 2f } }, + { "same", new float[] { 3f, 4f } }, // Same name, different vector + }; + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: input, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("same", request.HybridSearch.Targets.TargetVectors); + } + + [Fact] + public async Task Hybrid_CollectionInitializer_WithVectorsObject_ProducesValidRequest() + { + // Arrange - adding a Vectors object to VectorSearchInput + var vectors = new Vectors { { "fromVectors", new float[] { 1f, 2f } } }; + var input = new VectorSearchInput { vectors }; + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: input, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("fromVectors", request.HybridSearch.Targets.TargetVectors); + } + + #endregion + + #region NearVectorInput with All Combination Methods + + [Fact] + public async Task Hybrid_NearVectorInput_MultiTarget_Average_ProducesValidRequest() + { + // Arrange + var vectorInput = VectorSearchInput.Combine( + TargetVectors.Average("vec1", "vec2"), + ("vec1", new float[] { 1f, 2f }), + ("vec2", new float[] { 3f, 4f }) + ); + var nearVector = new NearVectorInput(vectorInput); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: nearVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearVectorInput_MultiTarget_Minimum_ProducesValidRequest() + { + // Arrange + var vectorInput = VectorSearchInput.Combine( + TargetVectors.Minimum("vec1", "vec2"), + ("vec1", new float[] { 1f, 2f }), + ("vec2", new float[] { 3f, 4f }) + ); + var nearVector = new NearVectorInput(vectorInput); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: nearVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(V1.CombinationMethod.TypeMin, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearVectorInput_MultiTarget_RelativeScore_ProducesValidRequest() + { + // Arrange + var vectorInput = VectorSearchInput.Combine( + TargetVectors.RelativeScore(("vec1", 0.7), ("vec2", 0.3)), + ("vec1", new float[] { 1f, 2f }), + ("vec2", new float[] { 3f, 4f }) + ); + var nearVector = new NearVectorInput(vectorInput); + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: nearVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal( + V1.CombinationMethod.TypeRelativeScore, + request.HybridSearch.Targets.Combination + ); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + #endregion + + #region NearVectorInput Implicit Conversions + + [Fact] + public async Task Hybrid_NearVectorInput_FromVectorSearchInput_Implicit_ProducesValidRequest() + { + // Arrange - implicit conversion from VectorSearchInput to NearVectorInput + VectorSearchInput vectorSearch = ("myVector", new float[] { 1f, 2f, 3f }); + NearVectorInput nearVector = vectorSearch; + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: nearVector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Contains("myVector", request.HybridSearch.Targets.TargetVectors); + } + + #endregion + + #region Extras + [Fact] + public async Task Hybrid_HybridNearTextInputBuilder_NearText_NoMethod_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with NearVector().ManualWeights() + await _collection.Query.Hybrid( + "test", + v => v.NearText("query"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Equal(V1.CombinationMethod.Unspecified, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Extras() + { + await _collection.Query.NearVector( + v => v.TargetVectorsSum(("title", new[] { 1f, 2f }), ("description", new[] { 3f, 4f })), + cancellationToken: TestContext.Current.CancellationToken + ); + + var queryNearVector3 = await _collection.Query.Hybrid( + query: "fluffy playful", + vectors: null, + alpha: 0.7f, + limit: 5, + returnProperties: ["name", "breed", "color", "counter"], + returnMetadata: MetadataOptions.Score | MetadataOptions.Distance, + cancellationToken: TestContext.Current.CancellationToken + ); + + await _collection.Query.Hybrid( + "search query", + v => + v.NearVector() + .TargetVectorsSum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + } + #endregion + + #region HybridVectorInput.FactoryFn Lambda Builder Tests + + [Fact] + public async Task Hybrid_HybridVectorInputBuilder_NearVector_ManualWeights_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with NearVector().ManualWeights() + await _collection.Query.Hybrid( + "test", + v => + v.NearVector() + .TargetVectorsManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("description", 0.8, new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(2, request.HybridSearch.NearVector.VectorForTargets.Count); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task Hybrid_HybridVectorInputBuilder_NearVector_Sum_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with NearVector().TargetVectorsSum() + await _collection.Query.Hybrid( + "test", + v => + v.NearVector() + .TargetVectorsSum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_HybridVectorInputBuilder_NearVector_WithManualWeights_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with manual weights + await _collection.Query.Hybrid( + "test", + v => + v.NearVector() + .TargetVectorsManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("description", 0.8, new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearVector); + // Note: Hybrid NearVector builders do not accept certainty/distance parameters + Assert.NotNull(request.HybridSearch.Targets); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_HybridVectorInputBuilder_NearText_ManualWeights_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with NearText().ManualWeights() + await _collection.Query.Hybrid( + "test", + v => + v.NearText(new[] { "concept1", "concept2" }) + .TargetVectorsManualWeights(("title", 1.2), ("description", 0.8)), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Equal(2, request.HybridSearch.NearText.Query.Count); + Assert.Equal(V1.CombinationMethod.TypeManual, request.HybridSearch.Targets.Combination); + Assert.Equal(2, request.HybridSearch.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task Hybrid_HybridVectorInputBuilder_NearText_Average_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with NearText().Average() + await _collection.Query.Hybrid( + "test", + v => + v.NearText(new[] { "concept1", "concept2" }) + .TargetVectorsAverage("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_HybridVectorInputBuilder_NearText_WithCertaintyAndMove_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with NearText parameters (no certainty/distance for Hybrid) + await _collection.Query.Hybrid( + "test", + v => + v.NearText( + new[] { "concept1" }, + moveTo: new Move("positive", 0.5f), + moveAway: new Move("negative", 0.3f) + ) + .TargetVectorsSum("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.HybridSearch.NearText); + // Note: Hybrid NearText builders do not accept certainty/distance parameters + Assert.NotNull(request.HybridSearch.NearText.MoveTo); + Assert.NotNull(request.HybridSearch.NearText.MoveAway); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_HybridVectorInputBuilder_Vectors_DirectSyntax_ProducesValidRequest() + { + // Act - using HybridVectorInput.FactoryFn with .Vectors() direct syntax (no combination method) + await _collection.Query.Hybrid( + "test", + v => v.Vectors(("myVector", new[] { 1f, 2f })), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert - .Vectors() creates a VectorSearchInput which should populate Targets + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal("test", request.HybridSearch.Query); + Assert.NotNull(request.HybridSearch.Targets); + Assert.Contains("myVector", request.HybridSearch.Targets.TargetVectors); + // Combination should be Unspecified when using .Vectors() without combination method + Assert.Equal(V1.CombinationMethod.Unspecified, request.HybridSearch.Targets.Combination); + } + + [Fact] + public async Task Hybrid_NearTextBuilder_FromDocs_ProducesValidRequest() + { + // Validates corrected docs example at line 462-467 + + // Act + await _collection.Query.Hybrid( + query: null, + vectors: v => v.NearText(["banana"]).TargetVectorsSum("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.True(string.IsNullOrEmpty(request.HybridSearch.Query)); // Verify query is null or empty + Assert.NotNull(request.HybridSearch.NearText); + Assert.Contains("banana", request.HybridSearch.NearText.Query); + // Note: Hybrid NearText builders do not accept certainty/distance parameters + Assert.NotNull(request.HybridSearch.Targets); + Assert.Equal(V1.CombinationMethod.TypeSum, request.HybridSearch.Targets.Combination); + } + + #endregion +} diff --git a/src/Weaviate.Client.Tests/Unit/TestMultiKeySortedList.cs b/src/Weaviate.Client.Tests/Unit/TestMultiKeySortedList.cs new file mode 100644 index 00000000..a208269d --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestMultiKeySortedList.cs @@ -0,0 +1,410 @@ +using Weaviate.Client.Internal; + +namespace Weaviate.Client.Tests.Unit; + +public class TestMultiKeySortedList +{ + private record TestItem(int Key, string Value); + + [Fact] + public void Add_WithItem_AddsToCorrectKey() + { + var list = new MultiKeySortedList(item => item.Key); + + var item1 = new TestItem(1, "first"); + var item2 = new TestItem(1, "second"); + var item3 = new TestItem(2, "third"); + + list.Add(item1); + list.Add(item2); + list.Add(item3); + + Assert.Equal(2, list[1].Length); + Assert.Contains(item1, list[1]); + Assert.Contains(item2, list[1]); + Assert.Single(list[2]); + Assert.Contains(item3, list[2]); + } + + [Fact] + public void Add_WithKeyAndValue_AddsToCorrectKey() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, "first" }, + { 1, "second" }, + { 2, "third" }, + }; + + Assert.Equal(new[] { "first", "second" }, list[1]); + Assert.Equal(new[] { "third" }, list[2]); + } + + [Fact] + public void Add_WithKeyAndArray_AddsAllValues() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + { 2, new[] { "third" } }, + }; + + Assert.Equal(new[] { "first", "second" }, list[1]); + Assert.Equal(new[] { "third" }, list[2]); + } + + [Fact] + public void ContainsKey_WithExistingKey_ReturnsTrue() + { + var list = new MultiKeySortedList(s => int.Parse(s)) { { 1, "value" } }; + + Assert.True(list.ContainsKey(1)); + } + + [Fact] + public void ContainsKey_WithNonExistingKey_ReturnsFalse() + { + var list = new MultiKeySortedList(s => int.Parse(s)); + + Assert.False(list.ContainsKey(1)); + } + + [Fact] + public void Remove_WithExistingKey_RemovesKeyAndReturnsTrue() + { + var list = new MultiKeySortedList(s => int.Parse(s)) { { 1, "value" } }; + + var result = list.Remove(1); + + Assert.True(result); + Assert.False(list.ContainsKey(1)); + } + + [Fact] + public void Remove_WithNonExistingKey_ReturnsFalse() + { + var list = new MultiKeySortedList(s => int.Parse(s)); + + var result = list.Remove(1); + + Assert.False(result); + } + + [Fact] + public void TryGetValue_WithExistingKey_ReturnsTrueAndValues() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, "first" }, + { 1, "second" }, + }; + + var result = list.TryGetValue(1, out var values); + + Assert.True(result); + Assert.Equal(new[] { "first", "second" }, values); + } + + [Fact] + public void TryGetValue_WithNonExistingKey_ReturnsFalseAndNull() + { + var list = new MultiKeySortedList(s => int.Parse(s)); + + var result = list.TryGetValue(1, out var values); + + Assert.False(result); + Assert.Null(values); + } + + [Fact] + public void Add_KeyValuePair_AddsValues() + { + var list = new MultiKeySortedList(s => int.Parse(s)); + var kvp = new KeyValuePair(1, ["first", "second"]); + + list.Add(kvp); + + Assert.Equal(new[] { "first", "second" }, list[1]); + } + + [Fact] + public void Clear_RemovesAllItems() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, "first" }, + { 2, "second" }, + }; + + list.Clear(); + + Assert.Empty(list); + Assert.False(list.ContainsKey(1)); + Assert.False(list.ContainsKey(2)); + } + + [Fact] + public void Contains_WithMatchingKeyValuePair_ReturnsTrue() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + }; + var kvp = new KeyValuePair(1, ["first", "second"]); + + var result = list.Contains(kvp); + + Assert.True(result); + } + + [Fact] + public void Contains_WithNonMatchingValues_ReturnsFalse() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + }; + var kvp = new KeyValuePair(1, ["different"]); + + var result = list.Contains(kvp); + + Assert.False(result); + } + + [Fact] + public void Contains_WithNonExistingKey_ReturnsFalse() + { + var list = new MultiKeySortedList(s => int.Parse(s)); + var kvp = new KeyValuePair(1, ["value"]); + + var result = list.Contains(kvp); + + Assert.False(result); + } + + [Fact] + public void CopyTo_CopiesAllKeyValuePairs() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + { 2, new[] { "third" } }, + }; + + var array = new KeyValuePair[2]; + list.CopyTo(array, 0); + + Assert.Equal(1, array[0].Key); + Assert.Equal(new[] { "first", "second" }, array[0].Value); + Assert.Equal(2, array[1].Key); + Assert.Equal(new[] { "third" }, array[1].Value); + } + + [Fact] + public void Remove_KeyValuePair_WithMatchingPair_RemovesAndReturnsTrue() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + }; + var kvp = new KeyValuePair(1, ["first", "second"]); + + var result = list.Remove(kvp); + + Assert.True(result); + Assert.False(list.ContainsKey(1)); + } + + [Fact] + public void Remove_KeyValuePair_WithNonMatchingPair_ReturnsFalse() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + }; + var kvp = new KeyValuePair(1, ["different"]); + + var result = list.Remove(kvp); + + Assert.False(result); + Assert.True(list.ContainsKey(1)); + } + + [Fact] + public void GetEnumerator_ReturnsAllKeyValuePairs() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + { 2, new[] { "third" } }, + }; + + var items = list.ToList(); + + Assert.Equal(2, items.Count); + Assert.Equal(1, items[0].Key); + Assert.Equal(new[] { "first", "second" }, items[0].Value); + Assert.Equal(2, items[1].Key); + Assert.Equal(new[] { "third" }, items[1].Value); + } + + [Fact] + public void Indexer_Get_ReturnsValues() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + }; + + var result = list[1]; + + Assert.Equal(new[] { "first", "second" }, result); + } + + [Fact] + public void Indexer_Get_WithNonExistingKey_ReturnsEmpty() + { + var list = new MultiKeySortedList(s => int.Parse(s)); + + var result = list[1]; + + Assert.Empty(result); + } + + [Fact] + public void Keys_ReturnsAllKeys() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, "first" }, + { 2, "second" }, + { 3, "third" }, + }; + + Assert.Equal([1, 2, 3], list.Keys); + } + + [Fact] + public void Values_ReturnsAllValues() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, "first" }, + { 1, "second" }, + { 2, "third" }, + }; + + Assert.Equal(["first", "second", "third"], list.Values); + } + + [Fact] + public void IDictionary_Keys_ReturnsAllKeys() + { + IDictionary list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first" } }, + { 2, new[] { "second" } }, + }; + + Assert.Equal([1, 2], list.Keys); + } + + [Fact] + public void IDictionary_Values_ReturnsAllValueArrays() + { + IDictionary list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + { 2, new[] { "third" } }, + }; + + var values = list.Values.ToList(); + Assert.Equal(2, values.Count); + Assert.Equal(new[] { "first", "second" }, values[0]); + Assert.Equal(new[] { "third" }, values[1]); + } + + [Fact] + public void Count_ReturnsNumberOfKeys() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, "first" }, + { 1, "second" }, + { 2, "third" }, + }; + + Assert.Equal(2, list.Count); + } + + [Fact] + public void IsReadOnly_ReturnsFalse() + { + var list = new MultiKeySortedList(s => int.Parse(s)); + + Assert.False(list.IsReadOnly); + } + + [Fact] + public void IDictionary_Indexer_Get_ReturnsArray() + { + IDictionary list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first", "second" } }, + }; + + var result = list[1]; + + Assert.Equal(new[] { "first", "second" }, result); + } + + [Fact] + public void IDictionary_Indexer_Get_WithNonExistingKey_ReturnsEmptyArray() + { + IDictionary list = new MultiKeySortedList(s => int.Parse(s)); + + var result = list[999]; + + Assert.Empty(result); + } + + [Fact] + public void IDictionary_Indexer_Set_ReplacesValues() + { + IDictionary list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, new[] { "first" } }, + }; + + list[1] = ["replaced1", "replaced2"]; + + var result = list[1]; + Assert.Equal(new[] { "replaced1", "replaced2" }, result); + } + + [Fact] + public void SortedOrder_MaintainsSortedKeys() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 5, "five" }, + { 1, "one" }, + { 3, "three" }, + }; + + Assert.Equal([1, 3, 5], list.Keys); + } + + [Fact] + public void MultipleValuesPerKey_MaintainsInsertionOrder() + { + var list = new MultiKeySortedList(s => int.Parse(s)) + { + { 1, "first" }, + { 1, "second" }, + { 1, "third" }, + }; + + Assert.Equal(new[] { "first", "second", "third" }, list[1]); + } +} diff --git a/src/Weaviate.Client.Tests/Unit/TestNearMediaSyntax.cs b/src/Weaviate.Client.Tests/Unit/TestNearMediaSyntax.cs new file mode 100644 index 00000000..62422dae --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestNearMediaSyntax.cs @@ -0,0 +1,620 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Tests.Unit.Mocks; +using V1 = Weaviate.Client.Grpc.Protobuf.V1; + +namespace Weaviate.Client.Tests.Unit; + +/// +/// Unit tests verifying the NearMedia search input syntax using NearMediaInput.FactoryFn lambda builders. +/// Target vectors MUST be specified via lambda builders - there is no separate targets parameter. +/// +[Collection("Unit Tests")] +public class TestNearMediaSyntax : IAsyncLifetime +{ + private const string CollectionName = "TestCollection"; + private static readonly byte[] TestMediaBytes = new byte[] { 1, 2, 3, 4, 5 }; + + private Func _getRequest = null!; + private CollectionClient _collection = null!; + + public ValueTask InitializeAsync() + { + var (client, getRequest) = MockGrpcClient.CreateWithSearchCapture(); + _getRequest = getRequest; + _collection = client.Collections.Use(CollectionName); + return ValueTask.CompletedTask; + } + + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + #region Simple Media Type Tests + + [Fact] + public async Task NearMedia_Image_WithoutTargetVectors_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Image(TestMediaBytes).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearImage.Image); + } + + [Fact] + public async Task NearMedia_Video_WithoutTargetVectors_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Video(TestMediaBytes).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVideo); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearVideo.Video); + } + + [Fact] + public async Task NearMedia_Audio_WithoutTargetVectors_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Audio(TestMediaBytes).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearAudio); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearAudio.Audio); + } + + [Fact] + public async Task NearMedia_Thermal_WithoutTargetVectors_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Thermal(TestMediaBytes).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearThermal); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearThermal.Thermal); + } + + [Fact] + public async Task NearMedia_Depth_WithoutTargetVectors_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Depth(TestMediaBytes).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearDepth); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearDepth.Depth); + } + + [Fact] + public async Task NearMedia_IMU_WithoutTargetVectors_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.IMU(TestMediaBytes).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImu); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearImu.Imu); + } + + #endregion + + #region Certainty and Distance Tests + + [Fact] + public async Task NearMedia_Image_WithCertainty_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Image(TestMediaBytes, certainty: 0.8f).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.Equal(0.8, request.NearImage.Certainty, precision: 5); + } + + [Fact] + public async Task NearMedia_Video_WithDistance_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Video(TestMediaBytes, distance: 0.3f).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVideo); + Assert.Equal(0.3, request.NearVideo.Distance, precision: 5); + } + + [Fact] + public async Task NearMedia_Audio_WithBothCertaintyAndDistance_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Audio(TestMediaBytes, certainty: 0.7f, distance: 0.2f).Build(), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearAudio); + Assert.Equal(0.7, request.NearAudio.Certainty, precision: 5); + Assert.Equal(0.2, request.NearAudio.Distance, precision: 5); + } + + #endregion + + #region Target Vectors - Sum + + [Fact] + public async Task NearMedia_Image_WithSum_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Image(TestMediaBytes).TargetVectorsSum("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.NotNull(request.NearImage.Targets); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearImage.Targets.Combination); + Assert.Equal(2, request.NearImage.Targets.TargetVectors.Count); + Assert.Contains("title", request.NearImage.Targets.TargetVectors); + Assert.Contains("description", request.NearImage.Targets.TargetVectors); + } + + [Fact] + public async Task NearMedia_Video_WithSum_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => + m.Video(TestMediaBytes, certainty: 0.8f) + .TargetVectorsSum("visual", "audio", "metadata"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVideo); + Assert.Equal(0.8, request.NearVideo.Certainty, precision: 5); + Assert.NotNull(request.NearVideo.Targets); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearVideo.Targets.Combination); + Assert.Equal(3, request.NearVideo.Targets.TargetVectors.Count); + } + + #endregion + + #region Target Vectors - Average + + [Fact] + public async Task NearMedia_Audio_WithAverage_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Audio(TestMediaBytes).TargetVectorsAverage("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearAudio); + Assert.NotNull(request.NearAudio.Targets); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.NearAudio.Targets.Combination); + Assert.Equal(2, request.NearAudio.Targets.TargetVectors.Count); + } + + #endregion + + #region Target Vectors - ManualWeights + + [Fact] + public async Task NearMedia_Image_WithManualWeights_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => + m.Image(TestMediaBytes, distance: 0.3f) + .TargetVectorsManualWeights(("title", 1.2), ("description", 0.8)), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.Equal(0.3, request.NearImage.Distance, precision: 5); + Assert.NotNull(request.NearImage.Targets); + Assert.Equal(V1.CombinationMethod.TypeManual, request.NearImage.Targets.Combination); + Assert.Equal(2, request.NearImage.Targets.WeightsForTargets.Count); + } + + #endregion + + #region Target Vectors - Minimum + + [Fact] + public async Task NearMedia_Thermal_WithMinimum_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Thermal(TestMediaBytes).TargetVectorsMinimum("v1", "v2", "v3"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearThermal); + Assert.NotNull(request.NearThermal.Targets); + Assert.Equal(V1.CombinationMethod.TypeMin, request.NearThermal.Targets.Combination); + Assert.Equal(3, request.NearThermal.Targets.TargetVectors.Count); + } + + #endregion + + #region Target Vectors - RelativeScore + + [Fact] + public async Task NearMedia_Depth_WithRelativeScore_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => + m.Depth(TestMediaBytes).TargetVectorsRelativeScore(("visual", 0.7), ("depth", 0.3)), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearDepth); + Assert.NotNull(request.NearDepth.Targets); + Assert.Equal(V1.CombinationMethod.TypeRelativeScore, request.NearDepth.Targets.Combination); + Assert.Equal(2, request.NearDepth.Targets.WeightsForTargets.Count); + } + + #endregion + + #region GroupBy Tests + + [Fact] + public async Task NearMedia_Image_WithGroupBy_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Image(TestMediaBytes).TargetVectorsSum("title", "description"), + new GroupByRequest("category") { ObjectsPerGroup = 5 }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.NotNull(request.GroupBy); + Assert.Equal("category", request.GroupBy.Path[0]); + Assert.Equal(5, request.GroupBy.ObjectsPerGroup); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearImage.Targets.Combination); + } + + [Fact] + public async Task NearMedia_Video_WithGroupBy_WithoutTargets_ProducesValidRequest() + { + // Act + await _collection.Query.NearMedia( + m => m.Video(TestMediaBytes, certainty: 0.75f).Build(), + new GroupByRequest("status") { ObjectsPerGroup = 3 }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVideo); + Assert.Equal(0.75, request.NearVideo.Certainty, precision: 5); + Assert.NotNull(request.GroupBy); + Assert.Equal("status", request.GroupBy.Path[0]); + Assert.Equal(3, request.GroupBy.ObjectsPerGroup); + } + + #endregion + + #region Complex Scenarios + + [Fact] + public async Task NearMedia_Image_WithLimitAndAutoLimit_ProducesValidRequest() + { + // Act - Test with limit and autoLimit parameters + await _collection.Query.NearMedia( + m => + m.Image(TestMediaBytes, certainty: 0.8f) + .TargetVectorsManualWeights(("v1", 1.2), ("v2", 0.8)), + limit: 10, + autoLimit: 3, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.Equal(0.8, request.NearImage.Certainty, precision: 5); + Assert.Equal(V1.CombinationMethod.TypeManual, request.NearImage.Targets.Combination); + Assert.Equal(3, (int)request.Autocut); + } + + [Fact] + public async Task NearMedia_AllMediaTypes_WithSameTargets_ProduceConsistentRequests() + { + // Test that all media types produce consistent target vector structure + var mediaTypes = new[] + { + (NearMediaInput.FactoryFn)(m => m.Image(TestMediaBytes).TargetVectorsSum("v1", "v2")), + (NearMediaInput.FactoryFn)(m => m.Video(TestMediaBytes).TargetVectorsSum("v1", "v2")), + (NearMediaInput.FactoryFn)(m => m.Audio(TestMediaBytes).TargetVectorsSum("v1", "v2")), + (NearMediaInput.FactoryFn)(m => m.Thermal(TestMediaBytes).TargetVectorsSum("v1", "v2")), + (NearMediaInput.FactoryFn)(m => m.Depth(TestMediaBytes).TargetVectorsSum("v1", "v2")), + (NearMediaInput.FactoryFn)(m => m.IMU(TestMediaBytes).TargetVectorsSum("v1", "v2")), + }; + + foreach (var mediaBuilder in mediaTypes) + { + // Reinitialize to clear previous request + await DisposeAsync(); + await InitializeAsync(); + + // Act + await _collection.Query.NearMedia( + mediaBuilder, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + + // Each media type should have its specific field set with consistent target vectors + var hasMediaField = + request.NearImage != null + || request.NearVideo != null + || request.NearAudio != null + || request.NearThermal != null + || request.NearDepth != null + || request.NearImu != null; + Assert.True(hasMediaField, "Request should have at least one media field set"); + + // Get the targets from whichever media field is set + V1.Targets? targets = null; + if (request.NearImage != null) + targets = request.NearImage.Targets; + else if (request.NearVideo != null) + targets = request.NearVideo.Targets; + else if (request.NearAudio != null) + targets = request.NearAudio.Targets; + else if (request.NearThermal != null) + targets = request.NearThermal.Targets; + else if (request.NearDepth != null) + targets = request.NearDepth.Targets; + else if (request.NearImu != null) + targets = request.NearImu.Targets; + + Assert.NotNull(targets); + Assert.Equal(V1.CombinationMethod.TypeSum, targets.Combination); + Assert.Equal(2, targets.TargetVectors.Count); + } + } + + #endregion + + #region Implicit Conversion Tests (Without .Build()) + + [Fact] + public async Task NearMedia_Image_WithoutBuild_ImplicitConversion_ProducesValidRequest() + { + // Act - No .Build() call, tests implicit conversion + await _collection.Query.NearMedia( + m => m.Image(TestMediaBytes), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearImage.Image); + } + + [Fact] + public async Task NearMedia_Video_WithoutBuild_ImplicitConversion_ProducesValidRequest() + { + // Act - No .Build() call + await _collection.Query.NearMedia( + m => m.Video(TestMediaBytes), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVideo); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearVideo.Video); + } + + [Fact] + public async Task NearMedia_Audio_WithoutBuild_ImplicitConversion_ProducesValidRequest() + { + // Act - No .Build() call + await _collection.Query.NearMedia( + m => m.Audio(TestMediaBytes), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearAudio); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearAudio.Audio); + } + + [Fact] + public async Task NearMedia_Thermal_WithCertainty_WithoutBuild_ImplicitConversion_ProducesValidRequest() + { + // Act - No .Build() call, with certainty parameter + await _collection.Query.NearMedia( + m => m.Thermal(TestMediaBytes, certainty: 0.85f), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearThermal); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearThermal.Thermal); + Assert.Equal(0.85, request.NearThermal.Certainty, precision: 5); + } + + [Fact] + public async Task NearMedia_Depth_WithDistance_WithoutBuild_ImplicitConversion_ProducesValidRequest() + { + // Act - No .Build() call, with distance parameter + await _collection.Query.NearMedia( + m => m.Depth(TestMediaBytes, distance: 0.25f), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearDepth); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearDepth.Depth); + Assert.Equal(0.25, request.NearDepth.Distance, precision: 5); + } + + [Fact] + public async Task NearMedia_IMU_WithBothParams_WithoutBuild_ImplicitConversion_ProducesValidRequest() + { + // Act - No .Build() call, with both certainty and distance + await _collection.Query.NearMedia( + m => m.IMU(TestMediaBytes, certainty: 0.9f, distance: 0.1f), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImu); + Assert.Equal(Convert.ToBase64String(TestMediaBytes), request.NearImu.Imu); + Assert.Equal(0.9, request.NearImu.Certainty, precision: 5); + Assert.Equal(0.1, request.NearImu.Distance, precision: 5); + } + + [Fact] + public async Task NearMedia_AllMediaTypes_WithoutBuild_ImplicitConversion_ProducesConsistentRequests() + { + // Test that all media types work without .Build() via implicit conversion + var mediaTypes = new[] + { + (NearMediaInput.FactoryFn)(m => m.Image(TestMediaBytes)), + (NearMediaInput.FactoryFn)(m => m.Video(TestMediaBytes)), + (NearMediaInput.FactoryFn)(m => m.Audio(TestMediaBytes)), + (NearMediaInput.FactoryFn)(m => m.Thermal(TestMediaBytes)), + (NearMediaInput.FactoryFn)(m => m.Depth(TestMediaBytes)), + (NearMediaInput.FactoryFn)(m => m.IMU(TestMediaBytes)), + }; + + foreach (var mediaBuilder in mediaTypes) + { + // Reinitialize to clear previous request + await DisposeAsync(); + await InitializeAsync(); + + // Act - No .Build() calls + await _collection.Query.NearMedia( + mediaBuilder, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + + // Each media type should have its specific field set + var hasMediaField = + request.NearImage != null + || request.NearVideo != null + || request.NearAudio != null + || request.NearThermal != null + || request.NearDepth != null + || request.NearImu != null; + Assert.True(hasMediaField, "Request should have at least one media field set"); + } + } + + [Fact] + public async Task NearMedia_WithGroupBy_WithoutBuild_ImplicitConversion_ProducesValidRequest() + { + // Act - No .Build() call, with GroupBy + await _collection.Query.NearMedia( + m => m.Image(TestMediaBytes, certainty: 0.75f), + new GroupByRequest("status") { ObjectsPerGroup = 3 }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearImage); + Assert.Equal(0.75, request.NearImage.Certainty, precision: 5); + Assert.NotNull(request.GroupBy); + Assert.Equal("status", request.GroupBy.Path[0]); + Assert.Equal(3, request.GroupBy.ObjectsPerGroup); + } + + #endregion +} diff --git a/src/Weaviate.Client.Tests/Unit/TestNearTextSyntax.cs b/src/Weaviate.Client.Tests/Unit/TestNearTextSyntax.cs new file mode 100644 index 00000000..f19e0496 --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestNearTextSyntax.cs @@ -0,0 +1,301 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Tests.Unit.Mocks; +using V1 = Weaviate.Client.Grpc.Protobuf.V1; + +namespace Weaviate.Client.Tests.Unit; + +/// +/// Unit tests verifying the NearText search input syntax using NearTextInput.FactoryFn lambda builders. +/// Target vectors MUST be specified via lambda builders - there is no separate targets parameter. +/// +[Collection("Unit Tests")] +public class TestNearTextSyntax : IAsyncLifetime +{ + private const string CollectionName = "TestCollection"; + + private Func _getRequest = null!; + private CollectionClient _collection = null!; + + public ValueTask InitializeAsync() + { + var (client, getRequest) = MockGrpcClient.CreateWithSearchCapture(); + _getRequest = getRequest; + _collection = client.Collections.Use(CollectionName); + return ValueTask.CompletedTask; + } + + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + #region Simple NearTextInput Tests + + [Fact] + public async Task NearText_SimpleNearTextInput_ProducesValidRequest() + { + // Act - Using NearTextInput without target vectors + await _collection.Query.NearText( + new NearTextInput(["banana"]), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Single(request.NearText.Query); + Assert.Equal("banana", request.NearText.Query[0]); + // Targets may be an empty object or null + } + + #endregion + + #region NearTextInput With Target Vectors + + [Fact] + public async Task NearText_NearTextInput_WithTargetVectors_ProducesValidRequest() + { + // Act - Using NearTextInput with TargetVectors.Sum + await _collection.Query.NearText( + new NearTextInput(["banana"], TargetVectors: TargetVectors.Sum("title", "description")), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.NotNull(request.NearText.Targets); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearText.Targets.Combination); + Assert.Equal(2, request.NearText.Targets.TargetVectors.Count); + } + + [Fact] + public async Task NearText_NearTextInput_WithCertaintyAndTargets_ProducesValidRequest() + { + // Act + await _collection.Query.NearText( + new NearTextInput( + ["banana"], + TargetVectors: TargetVectors.ManualWeights(("title", 1.2), ("description", 0.8)), + Certainty: 0.7f + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal(0.7, request.NearText.Certainty, precision: 5); + Assert.NotNull(request.NearText.Targets); + Assert.Equal(V1.CombinationMethod.TypeManual, request.NearText.Targets.Combination); + Assert.Equal(2, request.NearText.Targets.WeightsForTargets.Count); + } + + #endregion + + #region NearTextInput.FactoryFn Lambda Builder + + [Fact] + public async Task NearText_FactoryFn_Sum_ProducesValidRequest() + { + // Act - Lambda builder for NearTextInput with TargetVectorsSum + await _collection.Query.NearText( + v => v(["banana"]).TargetVectorsSum("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearText.Targets.Combination); + } + + [Fact] + public async Task NearText_FactoryFn_ManualWeights_ProducesValidRequest() + { + // Act - Lambda builder with TargetVectorsManualWeights + await _collection.Query.NearText( + v => v(["banana"]).TargetVectorsManualWeights(("title", 1.2), ("description", 0.8)), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal(V1.CombinationMethod.TypeManual, request.NearText.Targets.Combination); + Assert.Equal(2, request.NearText.Targets.WeightsForTargets.Count); + } + + [Fact] + public async Task NearText_FactoryFn_WithCertainty_ProducesValidRequest() + { + // Act - Lambda builder with certainty parameter + await _collection.Query.NearText( + v => v(["banana"], certainty: 0.7f).TargetVectorsSum("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal(0.7, request.NearText.Certainty, precision: 5); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearText.Targets.Combination); + } + + [Fact] + public async Task NearText_FactoryFn_WithMoveParameters_ProducesValidRequest() + { + // Act - Lambda builder with Move parameters + await _collection.Query.NearText( + v => + v( + ["banana"], + moveTo: new Move(concepts: "fruit", force: 0.5f), + moveAway: new Move(concepts: "vegetable", force: 0.3f) + ) + .TargetVectorsAverage("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.NotNull(request.NearText.MoveTo); + Assert.Equal(0.5f, request.NearText.MoveTo.Force); + Assert.NotNull(request.NearText.MoveAway); + Assert.Equal(0.3f, request.NearText.MoveAway.Force); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.NearText.Targets.Combination); + } + + [Fact] + public async Task NearText_FactoryFn_Average_ProducesValidRequest() + { + // Act + await _collection.Query.NearText( + v => v(["banana"]).TargetVectorsAverage("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.NearText.Targets.Combination); + } + + [Fact] + public async Task NearText_FactoryFn_Minimum_ProducesValidRequest() + { + // Act + await _collection.Query.NearText( + v => v(["banana"]).TargetVectorsMinimum("title", "description"), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal(V1.CombinationMethod.TypeMin, request.NearText.Targets.Combination); + } + + [Fact] + public async Task NearText_FactoryFn_RelativeScore_ProducesValidRequest() + { + // Act + await _collection.Query.NearText( + v => v(["banana"]).TargetVectorsRelativeScore(("title", 0.7), ("description", 0.3)), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal(V1.CombinationMethod.TypeRelativeScore, request.NearText.Targets.Combination); + Assert.Equal(2, request.NearText.Targets.WeightsForTargets.Count); + } + + #endregion + + #region GroupBy Tests + + [Fact] + public async Task NearText_WithGroupBy_ProducesValidRequest() + { + // Act - NearTextInput with GroupBy + await _collection.Query.NearText( + new NearTextInput(["banana"], TargetVectors: TargetVectors.Sum("title", "description")), + new GroupByRequest("category") { ObjectsPerGroup = 3 }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.NotNull(request.GroupBy); + Assert.Equal("category", request.GroupBy.Path[0]); + Assert.Equal(3, request.GroupBy.ObjectsPerGroup); + } + + [Fact] + public async Task NearText_FactoryFn_WithGroupBy_ProducesValidRequest() + { + // Act - Lambda builder with GroupBy + await _collection.Query.NearText( + v => v(["banana"]).TargetVectorsSum("title", "description"), + new GroupByRequest("category") { ObjectsPerGroup = 3 }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.NotNull(request.GroupBy); + Assert.Equal("category", request.GroupBy.Path[0]); + Assert.Equal(3, request.GroupBy.ObjectsPerGroup); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearText.Targets.Combination); + } + + [Fact] + public async Task NearText_WithMoveParameters_ProducesValidRequest() + { + // Act - Using NearTextInput with moveTo and moveAway + await _collection.Query.NearText( + new NearTextInput( + Query: "banana", + MoveTo: new Move(concepts: "fruit apple", force: 0.5f), + MoveAway: new Move(concepts: "vegetable", force: 0.3f) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearText); + Assert.Equal("banana", request.NearText.Query[0]); + + // Verify MoveTo + Assert.NotNull(request.NearText.MoveTo); + Assert.Equal("fruit apple", request.NearText.MoveTo.Concepts[0]); + Assert.Equal(0.5f, request.NearText.MoveTo.Force); + + // Verify MoveAway + Assert.NotNull(request.NearText.MoveAway); + Assert.Equal("vegetable", request.NearText.MoveAway.Concepts[0]); + Assert.Equal(0.3f, request.NearText.MoveAway.Force); + } + + #endregion +} diff --git a/src/Weaviate.Client.Tests/Unit/TestNearVectorSyntax.cs b/src/Weaviate.Client.Tests/Unit/TestNearVectorSyntax.cs new file mode 100644 index 00000000..f7dab9e1 --- /dev/null +++ b/src/Weaviate.Client.Tests/Unit/TestNearVectorSyntax.cs @@ -0,0 +1,380 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Tests.Unit.Mocks; +using V1 = Weaviate.Client.Grpc.Protobuf.V1; + +namespace Weaviate.Client.Tests.Unit; + +/// +/// Unit tests verifying the NearVector search input syntax combinations compile correctly +/// and produce the expected gRPC request structure. +/// Covers all examples from docs/VECTOR_API_OVERVIEW.md. +/// +[Collection("Unit Tests")] +public class TestNearVectorSyntax : IAsyncLifetime +{ + private const string CollectionName = "TestCollection"; + + private Func _getRequest = null!; + private CollectionClient _collection = null!; + + public ValueTask InitializeAsync() + { + var (client, getRequest) = MockGrpcClient.CreateWithSearchCapture(); + _getRequest = getRequest; + _collection = client.Collections.Use(CollectionName); + return ValueTask.CompletedTask; + } + + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + #region Simple Float Array (Implicit Conversion) + + [Fact] + public async Task NearVector_SimpleFloatArray_ProducesValidRequest() + { + // Arrange - Example from docs: await collection.Query.NearVector(new[] { 1f, 2f, 3f }); + float[] vector = [1f, 2f, 3f]; + + // Act + await _collection.Query.NearVector( + vector, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(CollectionName, request.Collection); + Assert.NotNull(request.NearVector); + Assert.Single(request.NearVector.Vectors); + Assert.NotEmpty(request.NearVector.Vectors[0].VectorBytes); + Assert.Empty(request.NearVector.VectorForTargets); // No named targets + } + + #endregion + + #region Named Vectors + + [Fact] + public async Task NearVector_NamedVectors_ProducesValidRequest() + { + // Arrange - Example from docs: + // await collection.Query.NearVector( + // new Vectors { + // { "title", new[] { 1f, 2f } }, + // { "description", new[] { 3f, 4f } } + // } + // ); + + // Act + await _collection.Query.NearVector( + new Vectors { { "title", new[] { 1f, 2f } }, { "description", new[] { 3f, 4f } } }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(CollectionName, request.Collection); + Assert.NotNull(request.NearVector); + Assert.Equal(2, request.NearVector.VectorForTargets.Count); + Assert.Empty(request.NearVector.Vectors); // Named vectors use VectorForTargets + + var titleVector = request.NearVector.VectorForTargets.FirstOrDefault(v => + v.Name == "title" + ); + Assert.NotNull(titleVector); + Assert.Single(titleVector.Vectors); + Assert.NotEmpty(titleVector.Vectors[0].VectorBytes); + + var descVector = request.NearVector.VectorForTargets.FirstOrDefault(v => + v.Name == "description" + ); + Assert.NotNull(descVector); + Assert.Single(descVector.Vectors); + Assert.NotEmpty(descVector.Vectors[0].VectorBytes); + } + + #endregion + + #region Lambda Builder - Sum Combination + + [Fact] + public async Task NearVector_LambdaBuilder_Sum_ProducesValidRequest() + { + // Arrange - Example from docs: + // await collection.Query.NearVector( + // v => v.TargetVectorsSum( + // ("title", new[] { 1f, 2f }), + // ("description", new[] { 3f, 4f }) + // ) + // ); + + // Act + await _collection.Query.NearVector( + v => v.TargetVectorsSum(("title", new[] { 1f, 2f }), ("description", new[] { 3f, 4f })), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(CollectionName, request.Collection); + Assert.NotNull(request.NearVector); + Assert.NotNull(request.NearVector.Targets); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearVector.Targets.Combination); + Assert.Equal(2, request.NearVector.Targets.TargetVectors.Count); + Assert.Contains("title", request.NearVector.Targets.TargetVectors); + Assert.Contains("description", request.NearVector.Targets.TargetVectors); + Assert.Equal(2, request.NearVector.VectorForTargets.Count); + } + + #endregion + + #region Lambda Builder - ManualWeights + + [Fact] + public async Task NearVector_LambdaBuilder_ManualWeights_ProducesValidRequest() + { + // Arrange - Example from docs: + // await collection.Query.NearVector( + // v => v.TargetVectorsManualWeights( + // ("title", 1.2, new[] { 1f, 2f }), + // ("description", 0.8, new[] { 3f, 4f }) + // ) + // ); + + // Act + await _collection.Query.NearVector( + v => + v.TargetVectorsManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("description", 0.8, new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.Equal(CollectionName, request.Collection); + Assert.NotNull(request.NearVector); + Assert.NotNull(request.NearVector.Targets); + Assert.Equal(V1.CombinationMethod.TypeManual, request.NearVector.Targets.Combination); + Assert.Equal(2, request.NearVector.Targets.WeightsForTargets.Count); + + // Verify weights are set correctly + var titleWeight = request.NearVector.Targets.WeightsForTargets.FirstOrDefault(w => + w.Target == "title" + ); + Assert.NotNull(titleWeight); + Assert.Equal(1.2, titleWeight.Weight, precision: 5); + + var descWeight = request.NearVector.Targets.WeightsForTargets.FirstOrDefault(w => + w.Target == "description" + ); + Assert.NotNull(descWeight); + Assert.Equal(0.8, descWeight.Weight, precision: 5); + } + + #endregion + + #region Multi-Vector (ColBERT-style) + + [Fact] + public async Task NearVector_MultiVector_ColBERTStyle_ProducesValidRequest() + { + // Arrange - Example from docs: + // await collection.Query.NearVector( + // ("colbert", new[,] { + // { 1f, 2f }, + // { 3f, 4f } + // }) + // ); + + // Act + await _collection.Query.NearVector( + ( + "colbert", + new[,] + { + { 1f, 2f }, + { 3f, 4f }, + } + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + + // Multi-vector (ColBERT) creates a named vector with multiple vector entries + // The exact structure depends on the conversion path, just verify NearVector is populated + Assert.True( + request.NearVector.VectorForTargets.Count > 0 || request.NearVector.Vectors.Count > 0, + "Expected multi-vector data in either VectorForTargets or Vectors" + ); + } + + #endregion + + #region With GroupBy + + [Fact] + public async Task NearVector_WithGroupBy_ProducesValidRequest() + { + // Arrange - Example from docs: + // await collection.Query.NearVector( + // new[] { 1f, 2f, 3f }, + // new GroupByRequest("category") { ObjectsPerGroup = 3 } + // ); + + // Act + await _collection.Query.NearVector( + new[] { 1f, 2f, 3f }, + new GroupByRequest("category") { ObjectsPerGroup = 3 }, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.NotNull(request.GroupBy); + Assert.Equal("category", request.GroupBy.Path[0]); + Assert.Equal(3, request.GroupBy.ObjectsPerGroup); + } + + #endregion + + #region NearVectorInput.FactoryFn Lambda Builder + + [Fact] + public async Task NearVector_NearVectorInputFactoryFn_WithCertainty_ProducesValidRequest() + { + // Arrange - Test NearVectorInput.FactoryFn with certainty parameter + // await collection.Query.NearVector( + // v => v(certainty: 0.8).TargetVectorsManualWeights( + // ("title", 1.2, new[] { 1f, 2f }), + // ("description", 0.8, new[] { 3f, 4f }) + // ) + // ); + + // Act + await _collection.Query.NearVector( + v => + v(certainty: 0.8f) + .TargetVectorsManualWeights( + ("title", 1.2, new[] { 1f, 2f }), + ("description", 0.8, new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.Equal(0.8, request.NearVector.Certainty, precision: 5); + Assert.Equal(V1.CombinationMethod.TypeManual, request.NearVector.Targets.Combination); + } + + [Fact] + public async Task NearVector_NearVectorInputFactoryFn_WithDistance_ProducesValidRequest() + { + // Arrange - Test NearVectorInput.FactoryFn with distance parameter + + // Act + await _collection.Query.NearVector( + v => + v(distance: 0.5f) + .TargetVectorsSum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.Equal(0.5, request.NearVector.Distance, precision: 5); + Assert.Equal(V1.CombinationMethod.TypeSum, request.NearVector.Targets.Combination); + } + + [Fact] + public async Task NearVector_NearVectorInputFactoryFn_Average_ProducesValidRequest() + { + // Act + await _collection.Query.NearVector( + v => + v() + .TargetVectorsAverage( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.Equal(V1.CombinationMethod.TypeAverage, request.NearVector.Targets.Combination); + } + + [Fact] + public async Task NearVector_NearVectorInputFactoryFn_Minimum_ProducesValidRequest() + { + // Act + await _collection.Query.NearVector( + v => + v() + .TargetVectorsMinimum( + ("title", new[] { 1f, 2f }), + ("description", new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.Equal(V1.CombinationMethod.TypeMin, request.NearVector.Targets.Combination); + } + + [Fact] + public async Task NearVector_NearVectorInputFactoryFn_RelativeScore_ProducesValidRequest() + { + // Act + await _collection.Query.NearVector( + v => + v() + .TargetVectorsRelativeScore( + ("title", 0.7, new[] { 1f, 2f }), + ("description", 0.3, new[] { 3f, 4f }) + ), + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + var request = _getRequest(); + Assert.NotNull(request); + Assert.NotNull(request.NearVector); + Assert.Equal( + V1.CombinationMethod.TypeRelativeScore, + request.NearVector.Targets.Combination + ); + Assert.Equal(2, request.NearVector.Targets.WeightsForTargets.Count); + } + + #endregion +} diff --git a/src/Weaviate.Client.Tests/Unit/TestTypedDataClient.cs b/src/Weaviate.Client.Tests/Unit/TestTypedDataClient.cs index e4903bc7..23ecdb38 100644 --- a/src/Weaviate.Client.Tests/Unit/TestTypedDataClient.cs +++ b/src/Weaviate.Client.Tests/Unit/TestTypedDataClient.cs @@ -125,7 +125,7 @@ public async Task InsertMany_WithTuplesOfDataAndVectors_AcceptsCorrectType() var collectionClient = new CollectionClient(client, "Articles"); var typedDataClient = new TypedDataClient(collectionClient.Data); - var vectors = new Models.Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = ("default", [0.1f, 0.2f, 0.3f]); var requests = new List<(TestArticle data, Models.Vectors vectors)> { (new TestArticle { Title = "Article 1" }, vectors), diff --git a/src/Weaviate.Client.Tests/Unit/TestTypedGenerateClient.cs b/src/Weaviate.Client.Tests/Unit/TestTypedGenerateClient.cs index c0febdcb..56df1837 100644 --- a/src/Weaviate.Client.Tests/Unit/TestTypedGenerateClient.cs +++ b/src/Weaviate.Client.Tests/Unit/TestTypedGenerateClient.cs @@ -155,7 +155,7 @@ public void NearVector_WithoutGroupBy_AcceptsCorrectParameters() var collectionClient = new CollectionClient(client, "Articles"); var typedGenerateClient = new TypedGenerateClient(collectionClient.Generate); - var vectors = new Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = new float[] { 0.1f, 0.2f, 0.3f }; var prompt = new SinglePrompt("Analyze these results"); // Act & Assert @@ -173,7 +173,7 @@ public void NearVector_WithGroupBy_AcceptsCorrectParameters() var collectionClient = new CollectionClient(client, "Articles"); var typedGenerateClient = new TypedGenerateClient(collectionClient.Generate); - var vectors = new Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = new float[] { 0.1f, 0.2f, 0.3f }; var groupBy = new GroupByRequest("Title") { NumberOfGroups = 10 }; var groupedTask = new GroupedTask("Summarize groups"); @@ -252,7 +252,7 @@ public void Hybrid_WithVectors_AcceptsCorrectParameters() var typedGenerateClient = new TypedGenerateClient(collectionClient.Generate); var query = "hybrid search"; - var vectors = new Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = new float[] { 0.1f, 0.2f, 0.3f }; var prompt = new SinglePrompt("Generate analysis"); // Act & Assert diff --git a/src/Weaviate.Client.Tests/Unit/TestTypedQueryClient.cs b/src/Weaviate.Client.Tests/Unit/TestTypedQueryClient.cs index 6f93c403..5a20116d 100644 --- a/src/Weaviate.Client.Tests/Unit/TestTypedQueryClient.cs +++ b/src/Weaviate.Client.Tests/Unit/TestTypedQueryClient.cs @@ -142,7 +142,7 @@ public void NearVector_WithoutGroupBy_AcceptsCorrectParameters() var collectionClient = new CollectionClient(client, "Articles"); var typedQueryClient = new TypedQueryClient(collectionClient.Query); - var vectors = new Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = new float[] { 0.1f, 0.2f, 0.3f }; // Act & Assert // Verify the method accepts Vectors parameter @@ -158,7 +158,7 @@ public void NearVector_WithGroupBy_AcceptsCorrectParameters() var collectionClient = new CollectionClient(client, "Articles"); var typedQueryClient = new TypedQueryClient(collectionClient.Query); - var vectors = new Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = new float[] { 0.1f, 0.2f, 0.3f }; var groupBy = new GroupByRequest("Title") { NumberOfGroups = 10 }; // Act & Assert @@ -213,7 +213,7 @@ public void Hybrid_WithVectors_AcceptsCorrectParameters() var typedQueryClient = new TypedQueryClient(collectionClient.Query); var query = "hybrid search"; - var vectors = new Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = new float[] { 0.1f, 0.2f, 0.3f }; // Act & Assert // Verify the method accepts query and Vectors parameters @@ -223,7 +223,7 @@ public void Hybrid_WithVectors_AcceptsCorrectParameters() } [Fact] - public void Hybrid_WithIHybridVectorInput_AcceptsCorrectParameters() + public void Hybrid_WithHybridVectorInput_AcceptsCorrectParameters() { // Arrange var client = CreateWeaviateClient(); @@ -248,7 +248,7 @@ public void Hybrid_WithGroupByAndVectors_AcceptsCorrectParameters() var query = "hybrid search"; var groupBy = new GroupByRequest("Title") { NumberOfGroups = 10 }; - var vectors = new Vectors { ["default"] = new float[] { 0.1f, 0.2f, 0.3f } }; + Vectors vectors = new float[] { 0.1f, 0.2f, 0.3f }; // Act & Assert // Verify the method accepts query, groupBy, and Vectors parameters @@ -380,6 +380,111 @@ public void TypedQueryClient_WrapsQueryClient_MaintainsTypeConstraints() Assert.NotNull(typedQueryClient); } + [Fact] + public void NearVector_AcceptsAllVectorInputFormats() + { + // Arrange + var client = CreateWeaviateClient(); + var collectionClient = new CollectionClient(client, "Articles"); + var queryClient = collectionClient.Query; + + // Test all vector input formats - verify implicit conversions to VectorSearchInput + + // 1. float[] - basic array + float[] vector1 = [20f, 21f, 22f]; + VectorSearchInput input1 = vector1; // Test implicit conversion from float[] + Assert.NotNull(input1); + + // 2. double[] - basic array + double[] vector1d = [20.0, 21.0, 22.0]; + VectorSearchInput input1d = vector1d; // Test implicit conversion from double[] + Assert.NotNull(input1d); + + // 3. Vector - implicit conversion from float[] + Vector vector2 = vector1; + VectorSearchInput input2 = vector2; // Test implicit conversion from Vector + Assert.NotNull(input2); + + // 4. NamedVector - tuple syntax (string, Vector) + NamedVector vector3 = ("default", vector2); + VectorSearchInput input3 = vector3; // Test implicit conversion from NamedVector + Assert.NotNull(input3); + + // 5. Vectors - implicit conversion from Vector + Vectors vector4 = vector2; + VectorSearchInput input4 = vector4; // Test implicit conversion from Vectors + Assert.NotNull(input4); + + // 6. Vectors - implicit conversion from NamedVector + Vectors vector5 = vector3; + VectorSearchInput input5 = vector5; // Test implicit conversion from Vectors (from NamedVector) + Assert.NotNull(input5); + + // 7. float[,] - 2D array (multi-vector) + float[,] vector6 = new[,] + { + { 20f, 21f, 22f }, + { 23f, 24f, 25f }, + }; + + // 8. Vector - implicit conversion from 2D array + Vector vector7 = vector6; + VectorSearchInput input7 = vector7; // Test implicit conversion from Vector (multi-vector) + Assert.NotNull(input7); + + // 9. NamedVector - from 2D array via Vector + NamedVector vector8 = ("default", vector7); + VectorSearchInput input8 = vector8; // Test implicit conversion from NamedVector (multi-vector) + Assert.NotNull(input8); + + // 10. Vectors - implicit conversion from 2D array + Vectors vector9 = vector6; + VectorSearchInput input9 = vector9; // Test implicit conversion from Vectors (multi-vector) + Assert.NotNull(input9); + + // 11. Vectors - from NamedVector (multi-vector) + Vectors vectorA = vector8; + VectorSearchInput inputA = vectorA; // Test implicit conversion from Vectors (from NamedVector multi-vector) + Assert.NotNull(inputA); + + // 12. NamedVector[] - array of named vectors (multiple target vectors) + NamedVector[] vectorB = [vector3, vector8]; + VectorSearchInput inputB = vectorB; // Test implicit conversion from NamedVector[] + Assert.NotNull(inputB); + + // Additional NamedVector[] test cases + + // 13. Single element NamedVector[] + NamedVector[] singleElementArray = [vector3]; + VectorSearchInput inputC = singleElementArray; // Test implicit conversion from single-element NamedVector[] + Assert.NotNull(inputC); + + // 14. NamedVector[] containing different named vectors + Vector textVector = new float[] { 1f, 2f, 3f }; + Vector imageVector = new float[] { 4f, 5f, 6f }; + NamedVector namedVector1 = ("text", textVector); + NamedVector namedVector2 = ("image", imageVector); + NamedVector[] multiNamedVectors = [namedVector1, namedVector2]; + VectorSearchInput inputD = multiNamedVectors; // Test implicit conversion from multi-target NamedVector[] + Assert.NotNull(inputD); + + // 15. NamedVector[] mixing single and multi vectors + Vector singleVector = new float[] { 7f, 8f, 9f }; + Vector multiVector = new float[,] + { + { 10f, 11f }, + { 12f, 13f }, + }; + NamedVector singleVec = ("single", singleVector); + NamedVector multiVec = ("multi", multiVector); + NamedVector[] mixedArray = [singleVec, multiVec]; + VectorSearchInput inputE = mixedArray; // Test implicit conversion from mixed NamedVector[] + Assert.NotNull(inputE); + + // Verify that QueryClient actually accepts VectorSearchInput + Assert.NotNull(queryClient); + } + private static WeaviateClient CreateWeaviateClient() { return Mocks.MockWeaviateClient.CreateWithMockHandler().Client; diff --git a/src/Weaviate.Client.Tests/Unit/TestVectorData.cs b/src/Weaviate.Client.Tests/Unit/TestVectorData.cs index 9c538962..bfa023d1 100644 --- a/src/Weaviate.Client.Tests/Unit/TestVectorData.cs +++ b/src/Weaviate.Client.Tests/Unit/TestVectorData.cs @@ -5,24 +5,26 @@ namespace Weaviate.Client.Tests.Unit; public class VectorDataTests { [Fact] - public void Vector_Create_Returns_VectorSingle() + public void Vector_Create_Returns_Single_Vector() { - var vector = Vector.Create(1, 2, 3); - Assert.IsAssignableFrom(vector); - Assert.IsType>(vector); + var vectors = new Vectors([1, 2, 3]); + Assert.True(vectors.ContainsKey("default")); + var vector = vectors["default"]; - Assert.Equal(3, vector.Dimensions); - Assert.Equal(1, vector.Count); + Assert.IsAssignableFrom(vector); + Assert.Equal((1, 3), vector.Dimensions); + Assert.Equal(3, vector.Count); Assert.False(vector.IsMultiVector); - Assert.Equal(new[] { 1, 2, 3 }, vector); + int[] values = vector; + Assert.Equal(new[] { 1, 2, 3 }, values); Assert.Equal(typeof(int), vector.ValueType); } [Fact] - public void Vector_Create_Returns_VectorMulti() + public void Vector_Create_Returns_Multi_Vector() { - var vector = Vector.Create( + var vectors = new Vectors( new[,] { { 1, 2 }, @@ -30,19 +32,15 @@ public void Vector_Create_Returns_VectorMulti() { 5, 6 }, } ); - Assert.IsAssignableFrom(vector); - Assert.IsType>(vector); + Assert.True(vectors.ContainsKey("default")); + var vector = vectors["default"]; - Assert.Equal(3, vector.Dimensions); - Assert.Equal(2, vector.Count); + Assert.IsAssignableFrom(vector); + Assert.Equal((3, 2), vector.Dimensions); + Assert.Equal(6, vector.Count); Assert.True(vector.IsMultiVector); - var vectorM = vector as VectorMulti; - Assert.NotNull(vectorM); - - Assert.Equal(new[] { 1, 2 }, vectorM[0]); - Assert.Equal(new[] { 3, 4 }, vectorM[1]); - Assert.Equal(typeof(int[]), vectorM.ValueType); + Assert.Equal(typeof(int[]), vector.ValueType); int[,] values = vector; Assert.Equal(1, values[0, 0]); @@ -54,21 +52,20 @@ public void Vector_Create_Returns_VectorMulti() } [Fact] - public void Implicit_Conversion_VectorSingle_To_VectorContainer() + public void Implicit_Conversion_Array_To_Vectors() { - var vector = Vector.Create(1.1, 2.2); - Vectors container = vector; + Vectors container = new[] { 1.1, 2.2 }; Assert.True(container.ContainsKey("default")); var stored = container["default"]; Assert.NotNull(stored); - Assert.Equal(new[] { 1.1, 2.2 }, stored); + double[] values = stored; + Assert.Equal(new[] { 1.1, 2.2 }, values); } [Fact] - public void Implicit_Conversion_VectorContainer_To_Array() + public void Implicit_Conversion_Vectors_NamedVector_To_Array() { - var vector = Vector.Create(1.1, 2.2); - Vectors container = vector; + Vectors container = new[] { 1.1, 2.2 }; Assert.True(container.ContainsKey("default")); double[] stored = container["default"]; Assert.NotNull(stored); @@ -76,33 +73,47 @@ public void Implicit_Conversion_VectorContainer_To_Array() } [Fact] - public void Implicit_Conversion_VectorMulti_To_VectorContainer() + public void Implicit_Conversion_MultiArray_To_Vectors() { - var multiVector = Vector.Create( - new[,] - { - { 1f, 2f }, - { 3f, 4f }, - } - ); - Vectors container = multiVector; + Vectors container = new[,] + { + { 1f, 2f }, + { 3f, 4f }, + }; Assert.True(container.ContainsKey("default")); - var stored = container["default"] as VectorMulti; + var stored = container["default"]; Assert.NotNull(stored); - Assert.Equal(2, stored.Dimensions); - Assert.Equal(new[] { 1f, 2f }, stored[0]); - Assert.Equal(new[] { 3f, 4f }, stored[1]); + Assert.Equal((2, 2), stored.Dimensions); + Assert.Equal(4, stored.Count); + Assert.True(stored.IsMultiVector); + + float[,] values = stored; + Assert.Equal(1f, values[0, 0]); + Assert.Equal(2f, values[0, 1]); + Assert.Equal(3f, values[1, 0]); + Assert.Equal(4f, values[1, 1]); } [Fact] public void VectorContainer_Add_SingleVector() { - var container = new Vectors(); - container.Add("vec", 10, 20, 30); + var container = new Vectors { { "vec", new[] { 10, 20, 30 } } }; Assert.True(container.ContainsKey("vec")); - var vector = container["vec"] as VectorSingle; + var vector = container["vec"]; Assert.NotNull(vector); - Assert.Equal(new[] { 10, 20, 30 }, vector); + int[] values = vector; + Assert.Equal([10, 20, 30], values); + } + + [Fact] + public void VectorContainer_Add_SingleVector_CollectionExpression() + { + var container = new Vectors("vec", [10, 20, 30]); + Assert.True(container.ContainsKey("vec")); + var vector = container["vec"]; + Assert.NotNull(vector); + int[] values = vector; + Assert.Equal(new[] { 10, 20, 30 }, values); } [Fact] @@ -118,35 +129,38 @@ public void VectorContainer_Add_MultiVector() } ); Assert.True(container.ContainsKey("multi")); - var multiVector = container["multi"] as VectorMulti; + var multiVector = container["multi"]; Assert.NotNull(multiVector); - Assert.Equal(2, multiVector.Count); - Assert.Equal(new[] { 1, 2 }, multiVector[0]); - Assert.Equal(new[] { 3, 4 }, multiVector[1]); + Assert.Equal((2, 2), multiVector.Dimensions); + Assert.Equal(4, multiVector.Count); + + int[,] values = multiVector; + Assert.Equal(1, values[0, 0]); + Assert.Equal(2, values[0, 1]); + Assert.Equal(3, values[1, 0]); + Assert.Equal(4, values[1, 1]); } [Fact] - public void VectorSingle_EnumerableBehavior() + public void Vector_Single_EnumerableBehavior() { - var vector = Vector.Create(5, 6, 7); + Vector vector = new[] { 5, 6, 7 }; var list = new List(vector.Cast()); Assert.Equal(new[] { 5, 6, 7 }, list); } [Fact] - public void VectorMulti_EnumerableBehavior() + public void Vector_Multi_EnumerableBehavior() { - var multiVector = Vector.Create( - new[,] - { - { 8, 9 }, - { 10, 11 }, - } - ); - var list = new List(multiVector.Cast()); - Assert.Equal(2, list.Count); - Assert.Equal(new[] { 8, 9 }, list[0]); - Assert.Equal(new[] { 10, 11 }, list[1]); + Vector multiVector = new[,] + { + { 8, 9 }, + { 10, 11 }, + }; + var list = multiVector.Cast(); + Assert.Equal(2, list.Count()); + Assert.Equal(new[] { 8, 9 }, list.ElementAt(0)); + Assert.Equal(new[] { 10, 11 }, list.ElementAt(1)); } [Fact] @@ -165,138 +179,25 @@ public void VectorContainer_TypeCheck_For_Single_And_MultiVector() }, }; - var v1 = Vector.Create(1f, 2f); - Vectors vc1 = v1; + Vectors vc1 = new[] { 1f, 2f }; + var singleVector = vc1["default"]; + Assert.False(singleVector.IsMultiVector); + Assert.Equal(typeof(float), singleVector.ValueType); - Assert.IsType(vc1["default"][0]); - - var v2 = Vector.Create( - new[,] - { - { 1f, 2f }, - { 3f, 4f }, - } - ); - Vectors vc2 = v2; - - Assert.IsType(vc2["default"][0]); + Vectors vc2 = new[,] + { + { 1f, 2f }, + { 3f, 4f }, + }; + var multiVector = vc2["default"]; + Assert.True(multiVector.IsMultiVector); + Assert.Equal(typeof(float[]), multiVector.ValueType); } #region Equality Tests - [Fact] - public void VectorSingle_Equality_WithSameValues_ShouldBeEqual() - { - // Arrange: Create two single vectors with identical values - var vector1 = new VectorSingle(new[] { 1.0f, 2.0f, 3.0f }); - var vector2 = new VectorSingle(new[] { 1.0f, 2.0f, 3.0f }); - - // Act & Assert - Assert.Equal(vector1, vector2); - Assert.True(vector1 == vector2); - Assert.False(vector1 != vector2); - Assert.Equal(vector1.GetHashCode(), vector2.GetHashCode()); - } - - [Fact] - public void VectorSingle_Equality_WithDifferentValues_ShouldNotBeEqual() - { - // Arrange: Create two single vectors with different values - var vector1 = new VectorSingle(new[] { 1.0f, 2.0f, 3.0f }); - var vector2 = new VectorSingle(new[] { 1.0f, 2.0f, 4.0f }); - - // Act & Assert - Assert.NotEqual(vector1, vector2); - Assert.False(vector1 == vector2); - Assert.True(vector1 != vector2); - } - - [Fact] - public void VectorSingle_Equality_WithDifferentNames_ShouldNotBeEqual() - { - // Arrange: Create two single vectors with same values but different names - var vector1 = new VectorSingle(new[] { 1.0f, 2.0f, 3.0f }) { Name = "vector1" }; - var vector2 = new VectorSingle(new[] { 1.0f, 2.0f, 3.0f }) { Name = "vector2" }; - - // Act & Assert - Assert.NotEqual(vector1, vector2); - } - - [Fact] - public void VectorMulti_Equality_WithSameValues_ShouldBeEqual() - { - // Arrange: Create two multi-vectors with identical values - var vector1 = new VectorMulti( - new[,] - { - { 1.0f, 2.0f, 3.0f }, - { 4.0f, 5.0f, 6.0f }, - { 7.0f, 8.0f, 9.0f }, - { 10.0f, 11.0f, 12.0f }, - } - ); - var vector2 = new VectorMulti( - new[,] - { - { 1.0f, 2.0f, 3.0f }, - { 4.0f, 5.0f, 6.0f }, - { 7.0f, 8.0f, 9.0f }, - { 10.0f, 11.0f, 12.0f }, - } - ); - - // Act & Assert - Assert.Equal(vector1, vector2); - Assert.True(vector1 == vector2); - Assert.False(vector1 != vector2); - Assert.Equal(vector1.GetHashCode(), vector2.GetHashCode()); - } - - [Fact] - public void VectorMulti_Equality_WithDifferentValues_ShouldNotBeEqual() - { - // Arrange: Create two multi-vectors with different values - var vector1 = new VectorMulti( - new[,] - { - { 1.0f, 2.0f, 3.0f }, - { 4.0f, 5.0f, 6.0f }, - } - ); - var vector2 = new VectorMulti( - new[,] - { - { 1.0f, 2.0f, 3.0f }, - { 4.0f, 5.0f, 7.0f }, // Different value - } - ); - - // Act & Assert - Assert.NotEqual(vector1, vector2); - } - - [Fact] - public void VectorMulti_Equality_WithDifferentDimensions_ShouldNotBeEqual() - { - // Arrange: Create two multi-vectors with different dimensions - var vector1 = new VectorMulti( - new[,] - { - { 1.0f, 2.0f, 3.0f }, - { 4.0f, 5.0f, 6.0f }, - } - ); - var vector2 = new VectorMulti( - new[,] - { - { 1.0f, 2.0f, 3.0f }, - { 4.0f, 5.0f, 6.0f }, - { 7.0f, 8.0f, 9.0f }, - } - ); - - // Act & Assert - Assert.NotEqual(vector1, vector2); - } + // Note: VectorSingle and VectorMulti are now internal types. + // The equality tests have been removed as they tested internal implementation details. + // The public API (Vector, NamedVector, Vectors) should be tested for equality instead. [Fact] public void Vectors_Equality_WithIdenticalSingleVectors_ShouldBeEqual() @@ -378,7 +279,7 @@ public void Vectors_Equality_CreatedInDifferentWays_ShouldBeEqual() vectors2.Add(new[] { 1.0f, 2.0f, 3.0f }); // Method 3: Using Create method - var vectors3 = Vectors.Create(1.0f, 2.0f, 3.0f); + var vectors3 = new Vectors([1.0f, 2.0f, 3.0f]); // Act & Assert Assert.Equal(vectors1, vectors2); @@ -466,49 +367,5 @@ public void Vectors_Equality_AfterReinsert_ShouldBeEqual() Assert.Equal(vectors1, vectors2); } - [Fact] - public void VectorSingle_Equality_WithIntegerType_ShouldBeEqual() - { - // Arrange: Test with integer type - var vector1 = new VectorSingle(new[] { 1, 2, 3, 4, 5 }); - var vector2 = new VectorSingle(new[] { 1, 2, 3, 4, 5 }); - - // Act & Assert - Assert.Equal(vector1, vector2); - } - - [Fact] - public void VectorMulti_Equality_WithDoubleType_ShouldBeEqual() - { - // Arrange: Test with double type - var vector1 = new VectorMulti( - new[,] - { - { 1.0, 2.0 }, - { 3.0, 4.0 }, - } - ); - var vector2 = new VectorMulti( - new[,] - { - { 1.0, 2.0 }, - { 3.0, 4.0 }, - } - ); - - // Act & Assert - Assert.Equal(vector1, vector2); - } - - [Fact] - public void VectorSingle_Equality_EmptyVectors_ShouldBeEqual() - { - // Arrange: Create two empty vectors - var vector1 = new VectorSingle(Array.Empty()); - var vector2 = new VectorSingle(Array.Empty()); - - // Act & Assert - Assert.Equal(vector1, vector2); - } #endregion } diff --git a/src/Weaviate.Client.Tests/Unit/TestVectorizers.cs b/src/Weaviate.Client.Tests/Unit/TestVectorizers.cs index 09f34e9d..5381ee9f 100644 --- a/src/Weaviate.Client.Tests/Unit/TestVectorizers.cs +++ b/src/Weaviate.Client.Tests/Unit/TestVectorizers.cs @@ -5,6 +5,11 @@ namespace Weaviate.Client.Tests.Unit; +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", + "CA1861:Avoid constant arrays as arguments", + Justification = "" +)] public partial class VectorConfigListTests { [Fact] @@ -36,17 +41,16 @@ public void Throws_When_HNSW_Already_Has_Quantizer() [Fact] public void NamedVectorInitialization() { - var v1 = new Vectors(); - v1.Add("default", new[] { 0.1f, 0.2f, 0.3f }); + var v1 = new Vectors { { "default", new[] { 0.1f, 0.2f, 0.3f } } }; // Act & Assert - Assert.Equal(new[] { 0.1f, 0.2f, 0.3f }, v1["default"].Cast()); + Assert.Equal([0.1f, 0.2f, 0.3f], v1["default"].Cast()); } [Fact] public void Test_VectorConfigList() { - Func transformerVectorizer = v => + static VectorizerConfig transformerVectorizer(VectorizerFactory v) => v.Text2VecTransformers(); // Arrange @@ -133,6 +137,11 @@ public void Test_NamedVectorConfig_None_Deserialization() } [Fact] + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", + "CA1869:Cache and reuse 'JsonSerializerOptions' instances", + Justification = "" + )] public void Test_NamedVectorConfig_Has_Properties() { // Arrange diff --git a/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj b/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj index 00a72220..f4fb666f 100644 --- a/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj +++ b/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj @@ -1,44 +1,44 @@ - - - enable - enable - Weaviate.Client.Tests - net8.0 - false - true - true - false - true - true - false - Exe - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - Always - - - - - - \ No newline at end of file + + + enable + enable + Weaviate.Client.Tests + net9.0 + false + true + true + false + true + true + false + Exe + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + Always + + + + + + diff --git a/src/Weaviate.Client.Tests/packages.lock.json b/src/Weaviate.Client.Tests/packages.lock.json index f48943f0..a6534525 100644 --- a/src/Weaviate.Client.Tests/packages.lock.json +++ b/src/Weaviate.Client.Tests/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0": { + "net9.0": { "coverlet.collector": { "type": "Direct", "requested": "[6.0.2, )", @@ -167,8 +167,7 @@ "contentHash": "UDY7blv4DCyIJ/8CkNrQKLaAZFypXQavRZ2DWf/2zi1mxYYKKw2t8AOCBWxNntyPZHPGhtEmL3snFM98ADZqTw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8", - "Microsoft.Extensions.Options": "9.0.8", - "System.Diagnostics.DiagnosticSource": "9.0.8" + "Microsoft.Extensions.Options": "9.0.8" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -219,8 +218,7 @@ "resolved": "9.0.8", "contentHash": "pYnAffJL7ARD/HCnnPvnFKSIHnTSmWz84WIlT9tPeQ4lHNiu0Az7N/8itihWvcF8sT+VVD5lq8V+ckMzu4SbOw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8", - "System.Diagnostics.DiagnosticSource": "9.0.8" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8" } }, "Microsoft.Extensions.Logging.Configuration": { @@ -247,8 +245,7 @@ "Microsoft.Extensions.Logging": "9.0.8", "Microsoft.Extensions.Logging.Abstractions": "9.0.8", "Microsoft.Extensions.Logging.Configuration": "9.0.8", - "Microsoft.Extensions.Options": "9.0.8", - "System.Text.Json": "9.0.8" + "Microsoft.Extensions.Options": "9.0.8" } }, "Microsoft.Extensions.Options": { @@ -350,13 +347,8 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "Lj8/a1Hzli1z6jo8H9urc16GxkpVJtJM+W9fmivXMNu7nwzHziGkxn4vO0DFscMbudkEVKSezdDuHk5kgM0X/g==" - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "6vPmJt73mgUo1gzc/OcXlJvClz/2jxZ4TQPRfriVaLoGRH2mye530D9WHJYbFQRNMxF3PWCoeofsFdCyN7fLzA==" + "resolved": "5.0.0", + "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" }, "System.Reflection.Metadata": { "type": "Transitive", @@ -380,20 +372,6 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "W+LotQsM4wBJ4PG7uRkyul4wqL4Fz7R4ty6uXrCNZUhbaHYANgrPaYR2ZpMVpdCjQEJ17Jr1NMN8hv4SHaHY4A==" - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "mIQir9jBqk0V7X0Nw5hzPJZC8DuGdf+2DS3jAVsr6rq5+/VyH5rza0XGcONJUWBrZ+G6BCwNyjWYd9lncBu48A==", - "dependencies": { - "System.IO.Pipelines": "9.0.8", - "System.Text.Encodings.Web": "9.0.8" - } - }, "xunit.analyzers": { "type": "Transitive", "resolved": "1.25.0", diff --git a/src/Weaviate.Client/AggregateClient.Hybrid.cs b/src/Weaviate.Client/AggregateClient.Hybrid.cs index cbeda5d6..0ef30690 100644 --- a/src/Weaviate.Client/AggregateClient.Hybrid.cs +++ b/src/Weaviate.Client/AggregateClient.Hybrid.cs @@ -7,34 +7,56 @@ public partial class AggregateClient /// /// Aggregate using hybrid search. /// - /// Search query - /// Alpha value for hybrid search - /// Vectors for search - /// Properties to query - /// Object limit - /// BM25 operator - /// Filters to apply - /// Target vector name - /// Maximum vector distance - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Aggregate result + public Task Hybrid( + string query, + float alpha = 0.7f, + string[]? queryProperties = null, + uint? objectLimit = null, + BM25Operator? bm25Operator = null, + Filter? filters = null, + float? maxVectorDistance = null, + bool totalCount = true, + IEnumerable? returnMetrics = null, + CancellationToken cancellationToken = default + ) => + Hybrid( + query: query, + vectors: (HybridVectorInput?)null, + alpha: alpha, + queryProperties: queryProperties, + objectLimit: objectLimit, + bm25Operator: bm25Operator, + filters: filters, + maxVectorDistance: maxVectorDistance, + totalCount: totalCount, + returnMetrics: returnMetrics, + cancellationToken: cancellationToken + ); + + /// + /// Aggregate using hybrid search. + /// public async Task Hybrid( - string? query = null, + string? query, + HybridVectorInput? vectors, float alpha = 0.7f, - Vectors? vectors = null, string[]? queryProperties = null, uint? objectLimit = null, BM25Operator? bm25Operator = null, Filter? filters = null, - string? targetVector = null, float? maxVectorDistance = null, bool totalCount = true, IEnumerable? returnMetrics = null, CancellationToken cancellationToken = default ) { + if (query is null && vectors is null) + { + throw new ArgumentException( + "At least one of 'query' or 'vectors' must be provided for hybrid search." + ); + } + var result = await _client.GrpcClient.AggregateHybrid( _collectionName, query, @@ -42,7 +64,6 @@ public async Task Hybrid( vectors, queryProperties, bm25Operator, - targetVector, maxVectorDistance, filters, null, @@ -59,36 +80,59 @@ public async Task Hybrid( /// /// Aggregate using hybrid search with grouping. /// - /// Search query - /// Group by configuration - /// Alpha value for hybrid search - /// Vectors for search - /// Properties to query - /// Object limit - /// BM25 operator - /// Filters to apply - /// Target vector name - /// Maximum vector distance - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Grouped aggregate result + public Task Hybrid( + string query, + Aggregate.GroupBy groupBy, + float alpha = 0.7f, + string[]? queryProperties = null, + uint? objectLimit = null, + BM25Operator? bm25Operator = null, + Filter? filters = null, + float? maxVectorDistance = null, + bool totalCount = true, + IEnumerable? returnMetrics = null, + CancellationToken cancellationToken = default + ) => + Hybrid( + query: query, + vectors: (HybridVectorInput?)null, + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + objectLimit: objectLimit, + bm25Operator: bm25Operator, + filters: filters, + maxVectorDistance: maxVectorDistance, + totalCount: totalCount, + returnMetrics: returnMetrics, + cancellationToken: cancellationToken + ); + + /// + /// Aggregate using hybrid search with grouping. + /// public async Task Hybrid( string? query, + HybridVectorInput? vectors, Aggregate.GroupBy groupBy, float alpha = 0.7f, - Vectors? vectors = null, string[]? queryProperties = null, uint? objectLimit = null, BM25Operator? bm25Operator = null, Filter? filters = null, - string? targetVector = null, float? maxVectorDistance = null, bool totalCount = true, IEnumerable? returnMetrics = null, CancellationToken cancellationToken = default ) { + if (query is null && vectors is null) + { + throw new ArgumentException( + "At least one of 'query' or 'vectors' must be provided for hybrid search." + ); + } + var result = await _client.GrpcClient.AggregateHybrid( _collectionName, query, @@ -96,7 +140,6 @@ public async Task Hybrid( vectors, queryProperties, bm25Operator, - targetVector, maxVectorDistance, filters, groupBy, @@ -110,3 +153,84 @@ public async Task Hybrid( return AggregateGroupByResult.FromGrpcReply(result); } } + +/// +/// Extension methods for AggregateClient Hybrid search with lambda vector builders. +/// +public static class AggregateClientHybridExtensions +{ + /// + /// Aggregate using hybrid search with a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. + /// + /// + /// await collection.Aggregate.Hybrid( + /// "test", + /// v => v.NearVector().ManualWeights( + /// ("title", 1.2, new[] { 1f, 2f }), + /// ("description", 0.8, new[] { 3f, 4f }) + /// ) + /// ); + /// + public static async Task Hybrid( + this AggregateClient client, + string query, + HybridVectorInput.FactoryFn vectors, + float alpha = 0.7f, + string[]? queryProperties = null, + uint? objectLimit = null, + BM25Operator? bm25Operator = null, + Filter? filters = null, + float? maxVectorDistance = null, + bool totalCount = true, + IEnumerable? returnMetrics = null, + CancellationToken cancellationToken = default + ) => + await client.Hybrid( + query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), + alpha: alpha, + queryProperties: queryProperties, + objectLimit: objectLimit, + bm25Operator: bm25Operator, + filters: filters, + maxVectorDistance: maxVectorDistance, + totalCount: totalCount, + returnMetrics: returnMetrics, + cancellationToken: cancellationToken + ); + + /// + /// Aggregate using hybrid search with grouping and a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. + /// + public static async Task Hybrid( + this AggregateClient client, + string query, + HybridVectorInput.FactoryFn vectors, + Aggregate.GroupBy groupBy, + float alpha = 0.7f, + string[]? queryProperties = null, + uint? objectLimit = null, + BM25Operator? bm25Operator = null, + Filter? filters = null, + float? maxVectorDistance = null, + bool totalCount = true, + IEnumerable? returnMetrics = null, + CancellationToken cancellationToken = default + ) => + await client.Hybrid( + query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + objectLimit: objectLimit, + bm25Operator: bm25Operator, + filters: filters, + maxVectorDistance: maxVectorDistance, + totalCount: totalCount, + returnMetrics: returnMetrics, + cancellationToken: cancellationToken + ); +} diff --git a/src/Weaviate.Client/AggregateClient.NearImage.cs b/src/Weaviate.Client/AggregateClient.NearImage.cs deleted file mode 100644 index 850dd8d9..00000000 --- a/src/Weaviate.Client/AggregateClient.NearImage.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Weaviate.Client.Models; - -namespace Weaviate.Client; - -public partial class AggregateClient -{ - /// - /// Aggregate near image. - /// - /// Image data - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Aggregate result - public async Task NearImage( - byte[] media, - double? certainty = null, - double? distance = null, - uint? limit = null, - Filter? filters = null, - TargetVectors? targetVector = null, - bool totalCount = true, - IEnumerable? returnMetrics = null, - CancellationToken cancellationToken = default - ) - { - var result = await _client.GrpcClient.AggregateNearMedia( - _collectionName, - media, - NearMediaType.Image, - certainty, - distance, - limit, - filters, - null, - targetVector, - totalCount, - _collectionClient.Tenant, - returnMetrics, - cancellationToken: cancellationToken - ); - - return AggregateResult.FromGrpcReply(result); - } - - /// - /// Aggregate near image with grouping. - /// - /// Image data - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Grouped aggregate result - public async Task NearImage( - byte[] media, - Aggregate.GroupBy? groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - Filter? filters = null, - TargetVectors? targetVector = null, - bool totalCount = true, - CancellationToken cancellationToken = default, - IEnumerable? metrics = null - ) - { - var result = await _client.GrpcClient.AggregateNearMedia( - _collectionName, - media, - NearMediaType.Image, - certainty, - distance, - limit, - filters, - groupBy, - targetVector, - totalCount, - _collectionClient.Tenant, - metrics, - cancellationToken: cancellationToken - ); - - return AggregateGroupByResult.FromGrpcReply(result); - } -} diff --git a/src/Weaviate.Client/AggregateClient.NearMedia.cs b/src/Weaviate.Client/AggregateClient.NearMedia.cs index 22487961..8b7de2cf 100644 --- a/src/Weaviate.Client/AggregateClient.NearMedia.cs +++ b/src/Weaviate.Client/AggregateClient.NearMedia.cs @@ -5,42 +5,45 @@ namespace Weaviate.Client; public partial class AggregateClient { /// - /// Aggregate near media. + /// Performs a near-media aggregation using media embeddings. /// - /// Media data - /// Type of media - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Aggregate result + /// + /// // Simple image aggregation + /// await collection.Aggregate.NearMedia(m => m.Image(imageBytes)); + /// + /// // With target vectors and metrics + /// await collection.Aggregate.NearMedia( + /// m => m.Video(videoBytes, certainty: 0.8f).Sum("v1", "v2"), + /// returnMetrics: new[] { Aggregate.Metric.Mean, Aggregate.Metric.Count } + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Filters to apply to the aggregation. + /// Maximum number of results to aggregate. + /// Whether to include total count in the result. + /// Metrics to calculate and return. + /// Cancellation token. + /// Aggregation results. public async Task NearMedia( - byte[] media, - NearMediaType mediaType, - double? certainty = null, - double? distance = null, - uint? limit = null, + NearMediaInput.FactoryFn media, Filter? filters = null, - TargetVectors? targetVector = null, + uint? limit = null, bool totalCount = true, IEnumerable? returnMetrics = null, CancellationToken cancellationToken = default ) { + var input = media(new NearMediaBuilder()); var result = await _client.GrpcClient.AggregateNearMedia( _collectionName, - media, - mediaType, - certainty, - distance, + input.Media, + input.Type, + input.Certainty, + input.Distance, limit, filters, null, - targetVector, + input.TargetVectors, totalCount, _collectionClient.Tenant, returnMetrics, @@ -51,44 +54,45 @@ public async Task NearMedia( } /// - /// Aggregate near media with grouping. + /// Performs a near-media aggregation with group-by. /// - /// Media data - /// Type of media - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Grouped aggregate result + /// + /// // Image aggregation with grouping + /// await collection.Aggregate.NearMedia( + /// m => m.Image(imageBytes).Sum("visual", "semantic"), + /// groupBy: new Aggregate.GroupBy("category"), + /// returnMetrics: new[] { Aggregate.Metric.Count } + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Group-by configuration. + /// Filters to apply to the aggregation. + /// Maximum number of results to aggregate. + /// Whether to include total count in the result. + /// Metrics to calculate and return. + /// Cancellation token. + /// Grouped aggregation results. public async Task NearMedia( - byte[] media, - NearMediaType mediaType, + NearMediaInput.FactoryFn media, Aggregate.GroupBy? groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, Filter? filters = null, - TargetVectors? targetVector = null, + uint? limit = null, bool totalCount = true, - CancellationToken cancellationToken = default, - IEnumerable? returnMetrics = null + IEnumerable? returnMetrics = null, + CancellationToken cancellationToken = default ) { + var input = media(new NearMediaBuilder()); var result = await _client.GrpcClient.AggregateNearMedia( _collectionName, - media, - mediaType, - certainty, - distance, + input.Media, + input.Type, + input.Certainty, + input.Distance, limit, filters, groupBy, - targetVector, + input.TargetVectors, totalCount, _collectionClient.Tenant, returnMetrics, diff --git a/src/Weaviate.Client/AggregateClient.NearObject.cs b/src/Weaviate.Client/AggregateClient.NearObject.cs index 9340abbe..1b47b00d 100644 --- a/src/Weaviate.Client/AggregateClient.NearObject.cs +++ b/src/Weaviate.Client/AggregateClient.NearObject.cs @@ -23,7 +23,7 @@ public async Task NearObject( double? distance = null, uint? limit = null, Filter? filters = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null @@ -37,7 +37,7 @@ public async Task NearObject( limit: limit, filters: filters, groupBy: null, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), totalCount: totalCount, _collectionClient.Tenant, metrics: returnMetrics, @@ -68,7 +68,7 @@ public async Task NearObject( double? distance = null, uint? limit = null, Filter? filters = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null @@ -82,7 +82,7 @@ public async Task NearObject( limit: limit, filters: filters, groupBy: groupBy, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), totalCount: totalCount, _collectionClient.Tenant, metrics: returnMetrics, diff --git a/src/Weaviate.Client/AggregateClient.NearText.cs b/src/Weaviate.Client/AggregateClient.NearText.cs index fbb5f44e..a1a95809 100644 --- a/src/Weaviate.Client/AggregateClient.NearText.cs +++ b/src/Weaviate.Client/AggregateClient.NearText.cs @@ -15,21 +15,33 @@ public partial class AggregateClient /// Move towards concept /// Move away from concept /// Filters to apply - /// Target vector name + /// Target vectors /// Whether to include total count /// Cancellation token for the operation /// Metrics to aggregate /// Grouped aggregate result - public async Task NearText( + /// + /// Aggregate near text. + /// + /// Text query + /// Certainty threshold + /// Distance threshold + /// Maximum number of results + /// Move towards concept + /// Move away from concept + /// Filters to apply + /// Whether to include total count + /// Cancellation token for the operation + /// Metrics to aggregate + /// Aggregate result + public async Task NearText( AutoArray query, - Aggregate.GroupBy? groupBy, double? certainty = null, double? distance = null, uint? limit = null, Move? moveTo = null, Move? moveAway = null, Filter? filters = null, - TargetVectors? targetVector = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null @@ -44,15 +56,15 @@ public async Task NearText( moveTo, moveAway, filters, - groupBy, - targetVector, + null, // No GroupByRequest for NearText + null, totalCount, _collectionClient.Tenant, returnMetrics, cancellationToken: cancellationToken ); - return AggregateGroupByResult.FromGrpcReply(result); + return AggregateResult.FromGrpcReply(result); } /// @@ -65,20 +77,35 @@ public async Task NearText( /// Move towards concept /// Move away from concept /// Filters to apply - /// Target vector name + /// Target vectors /// Whether to include total count /// Cancellation token for the operation /// Metrics to aggregate /// Aggregate result - public async Task NearText( + /// + /// Aggregate near text with grouping. + /// + /// Text query + /// Group by configuration + /// Certainty threshold + /// Distance threshold + /// Maximum number of results + /// Move towards concept + /// Move away from concept + /// Filters to apply + /// Whether to include total count + /// Cancellation token for the operation + /// Metrics to aggregate + /// Grouped aggregate result + public async Task NearText( AutoArray query, + Aggregate.GroupBy? groupBy, double? certainty = null, double? distance = null, uint? limit = null, Move? moveTo = null, Move? moveAway = null, Filter? filters = null, - TargetVectors? targetVector = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null @@ -93,14 +120,170 @@ public async Task NearText( moveTo, moveAway, filters, - null, // No GroupByRequest for NearText - targetVector, + groupBy, + null, totalCount, _collectionClient.Tenant, returnMetrics, cancellationToken: cancellationToken ); + return AggregateGroupByResult.FromGrpcReply(result); + } + + /// + /// Aggregate near text with grouping using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Group by configuration + /// Maximum number of results + /// Filters to apply + /// Whether to include total count + /// Cancellation token for the operation + /// Metrics to aggregate + /// Grouped aggregate result + /// + /// Aggregate near text with grouping using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Group by configuration + /// Maximum number of results + /// Filters to apply + /// Whether to include total count + /// Cancellation token for the operation + /// Metrics to aggregate + /// Grouped aggregate result + public async Task NearText( + NearTextInput query, + Aggregate.GroupBy? groupBy, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) + { + var result = await _client.GrpcClient.AggregateNearText( + _collectionName, + query.Query.ToArray(), + query.Certainty.HasValue ? (double?)query.Certainty.Value : null, + query.Distance.HasValue ? (double?)query.Distance.Value : null, + limit, + query.MoveTo, + query.MoveAway, + filters, + groupBy, + query.TargetVectors, + totalCount, + _collectionClient.Tenant, + returnMetrics, + cancellationToken: cancellationToken + ); + return AggregateGroupByResult.FromGrpcReply(result); + } + + /// + /// Aggregate near text using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Maximum number of results + /// Filters to apply + /// Whether to include total count + /// Cancellation token for the operation + /// Metrics to aggregate + /// Aggregate result + /// + /// Aggregate near text using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Maximum number of results + /// Filters to apply + /// Whether to include total count + /// Cancellation token for the operation + /// Metrics to aggregate + /// Aggregate result + public async Task NearText( + NearTextInput query, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) + { + var result = await _client.GrpcClient.AggregateNearText( + _collectionName, + query.Query.ToArray(), + query.Certainty.HasValue ? (double?)query.Certainty.Value : null, + query.Distance.HasValue ? (double?)query.Distance.Value : null, + limit, + query.MoveTo, + query.MoveAway, + filters, + null, // No GroupByRequest + query.TargetVectors, + totalCount, + _collectionClient.Tenant, + returnMetrics, + cancellationToken: cancellationToken + ); return AggregateResult.FromGrpcReply(result); } + + /// + /// Aggregate near text using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Maximum number of results. + /// Filters to apply. + /// Whether to include total count. + /// Cancellation token. + /// Metrics to return. + /// Aggregate result. + public async Task NearText( + NearTextInput.FactoryFn query, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) => + await NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + limit, + filters, + totalCount, + cancellationToken, + returnMetrics + ); + + /// + /// Aggregate near text with grouping using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Group-by configuration. + /// Maximum number of results. + /// Filters to apply. + /// Whether to include total count. + /// Cancellation token. + /// Metrics to return. + /// Aggregate group-by result. + public async Task NearText( + NearTextInput.FactoryFn query, + Aggregate.GroupBy? groupBy, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) => + await NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + groupBy, + limit, + filters, + totalCount, + cancellationToken, + returnMetrics + ); } diff --git a/src/Weaviate.Client/AggregateClient.NearVector.cs b/src/Weaviate.Client/AggregateClient.NearVector.cs index e3a865ca..084e7d4a 100644 --- a/src/Weaviate.Client/AggregateClient.NearVector.cs +++ b/src/Weaviate.Client/AggregateClient.NearVector.cs @@ -4,166 +4,232 @@ namespace Weaviate.Client; public partial class AggregateClient { + /// + /// Aggregate near vector. + /// + public async Task NearVector( + VectorSearchInput vectors, + double? certainty = null, + double? distance = null, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) + { + var result = await _client.GrpcClient.AggregateNearVector( + _collectionName, + vectors, + certainty, + distance, + limit, + filters, + null, // No GroupByRequest + totalCount, + _collectionClient.Tenant, + returnMetrics, + cancellationToken: cancellationToken + ); + return AggregateResult.FromGrpcReply(result); + } + /// /// Aggregate near vector with grouping. /// - /// Vector to search near - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Grouped aggregate result public async Task NearVector( - Vectors vector, - Aggregate.GroupBy? groupBy, + VectorSearchInput vectors, + Aggregate.GroupBy groupBy, double? certainty = null, double? distance = null, uint? limit = null, Filter? filters = null, - TargetVectors? targetVector = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null - ) => - await NearVector( - (NearVectorInput)vector, - groupBy, + ) + { + var result = await _client.GrpcClient.AggregateNearVector( + _collectionName, + vectors, certainty, distance, limit, filters, - targetVector, + groupBy, totalCount, - cancellationToken, - returnMetrics + _collectionClient.Tenant, + returnMetrics, + cancellationToken: cancellationToken ); + return AggregateGroupByResult.FromGrpcReply(result); + } /// - /// Aggregate near vector. + /// Aggregate near vector using lambda builder. /// - /// Vector to search near - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Aggregate result public async Task NearVector( - Vectors vector, + VectorSearchInput.FactoryFn vectors, double? certainty = null, double? distance = null, uint? limit = null, Filter? filters = null, - TargetVectors? targetVector = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null ) => await NearVector( - (NearVectorInput)vector, + vectors(new VectorSearchInput.Builder()), certainty, distance, limit, filters, - targetVector, totalCount, cancellationToken, returnMetrics ); /// - /// Aggregate near vector with grouping. + /// Aggregate near vector with grouping using lambda builder. /// - /// Vector to search near - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Grouped aggregate result public async Task NearVector( - NearVectorInput vector, - Aggregate.GroupBy? groupBy, + VectorSearchInput.FactoryFn vectors, + Aggregate.GroupBy groupBy, double? certainty = null, double? distance = null, uint? limit = null, Filter? filters = null, - TargetVectors? targetVector = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null - ) - { - var result = await _client.GrpcClient.AggregateNearVector( - _collectionName, - vector, + ) => + await NearVector( + vectors(new VectorSearchInput.Builder()), + groupBy, certainty, distance, limit, filters, - groupBy, - targetVector, totalCount, - _collectionClient.Tenant, - returnMetrics ?? [], - cancellationToken: cancellationToken + cancellationToken, + returnMetrics ); - return AggregateGroupByResult.FromGrpcReply(result); - } + /// + /// Aggregate near vector search using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Maximum number of results. + /// Filters to apply. + /// Whether to include total count. + /// Cancellation token. + /// Metrics to return. + /// Aggregate result. + public async Task NearVector( + NearVectorInput input, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) => + await NearVector( + vectors: input.Vector, + certainty: input.Certainty.HasValue ? (double?)input.Certainty.Value : null, + distance: input.Distance.HasValue ? (double?)input.Distance.Value : null, + limit: limit, + filters: filters, + totalCount: totalCount, + cancellationToken: cancellationToken, + returnMetrics: returnMetrics + ); /// - /// Aggregate near vector. + /// Aggregate near vector search with group-by using a NearVectorInput record. /// - /// Vector to search near - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Filters to apply - /// Target vector name - /// Whether to include total count - /// Cancellation token for the operation - /// Metrics to aggregate - /// Aggregate result + /// Near-vector input containing vector, certainty, and distance. + /// Group-by configuration. + /// Maximum number of results. + /// Filters to apply. + /// Whether to include total count. + /// Cancellation token. + /// Metrics to return. + /// Aggregate group-by result. + public async Task NearVector( + NearVectorInput input, + Aggregate.GroupBy groupBy, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) => + await NearVector( + vectors: input.Vector, + groupBy: groupBy, + certainty: input.Certainty.HasValue ? (double?)input.Certainty.Value : null, + distance: input.Distance.HasValue ? (double?)input.Distance.Value : null, + limit: limit, + filters: filters, + totalCount: totalCount, + cancellationToken: cancellationToken, + returnMetrics: returnMetrics + ); + + /// + /// Aggregate near vector search using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Maximum number of results. + /// Filters to apply. + /// Whether to include total count. + /// Cancellation token. + /// Metrics to return. + /// Aggregate result. public async Task NearVector( - NearVectorInput vector, - double? certainty = null, - double? distance = null, + NearVectorInput.FactoryFn vectors, uint? limit = null, Filter? filters = null, - TargetVectors? targetVector = null, bool totalCount = true, CancellationToken cancellationToken = default, IEnumerable? returnMetrics = null - ) - { - var result = await _client.GrpcClient.AggregateNearVector( - _collectionName, - vector, - certainty, - distance, + ) => + await NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), limit, filters, - null, // No GroupByRequest for NearVector - targetVector, totalCount, - _collectionClient.Tenant, - returnMetrics, - cancellationToken: cancellationToken + cancellationToken, + returnMetrics ); - return AggregateResult.FromGrpcReply(result); - } + /// + /// Aggregate near vector search with group-by using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Group-by configuration. + /// Maximum number of results. + /// Filters to apply. + /// Whether to include total count. + /// Cancellation token. + /// Metrics to return. + /// Aggregate group-by result. + public async Task NearVector( + NearVectorInput.FactoryFn vectors, + Aggregate.GroupBy groupBy, + uint? limit = null, + Filter? filters = null, + bool totalCount = true, + CancellationToken cancellationToken = default, + IEnumerable? returnMetrics = null + ) => + await NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + groupBy, + limit, + filters, + totalCount, + cancellationToken, + returnMetrics + ); } diff --git a/src/Weaviate.Client/DataClient.cs b/src/Weaviate.Client/DataClient.cs index 5c12034d..4281dbf6 100644 --- a/src/Weaviate.Client/DataClient.cs +++ b/src/Weaviate.Client/DataClient.cs @@ -216,7 +216,7 @@ public async Task InsertMany( var o = new Grpc.Protobuf.V1.BatchObject { Collection = _collectionName, - Uuid = (r.ID ?? Guid.NewGuid()).ToString(), + Uuid = (r.UUID ?? Guid.NewGuid()).ToString(), Properties = ObjectHelper.BuildBatchProperties(r.Data), Tenant = _collectionClient.Tenant, }; @@ -238,13 +238,17 @@ public async Task InsertMany( if (r.Vectors != null) { o.Vectors.AddRange( - r.Vectors.Select(v => new Grpc.Protobuf.V1.Vectors + r.Vectors.Select(kvp => { - Name = v.Key, - VectorBytes = v.Value.ToByteString(), - Type = v.Value.IsMultiVector - ? Grpc.Protobuf.V1.Vectors.Types.VectorType.MultiFp32 - : Grpc.Protobuf.V1.Vectors.Types.VectorType.SingleFp32, + var v = kvp.Value; + return new Grpc.Protobuf.V1.Vectors + { + Name = kvp.Key, + VectorBytes = v.ToByteString(), + Type = v.IsMultiVector + ? Grpc.Protobuf.V1.Vectors.Types.VectorType.MultiFp32 + : Grpc.Protobuf.V1.Vectors.Types.VectorType.SingleFp32, + }; }) ); } diff --git a/src/Weaviate.Client/Extensions.cs b/src/Weaviate.Client/Extensions.cs index ed579d01..ecb3a94c 100644 --- a/src/Weaviate.Client/Extensions.cs +++ b/src/Weaviate.Client/Extensions.cs @@ -10,7 +10,7 @@ namespace Weaviate.Client; public static class WeaviateExtensions { - static Func?> _objectToDict = (object? v) => + static readonly Func?> _objectToDict = v => v is null ? null : JsonSerializer.Deserialize>( @@ -295,7 +295,6 @@ internal static CollectionConfigExport ToModel(this Rest.Dto.Class collection) (var properties, var references) = UnmergeProperties(collection?.Properties ?? []); -#pragma warning disable CS0618 // Type or member is obsolete return new CollectionConfigExport() { Name = collection?.Class1 ?? string.Empty, @@ -347,7 +346,6 @@ internal static CollectionConfigExport ToModel(this Rest.Dto.Class collection) VectorConfig = vectorConfig, Vectorizer = collection?.Vectorizer ?? string.Empty, }; -#pragma warning restore CS0618 // Type or member is obsolete } internal static IEnumerable FromByteString(this Google.Protobuf.ByteString byteString) @@ -381,7 +379,7 @@ internal static IEnumerable FromByteString(this Google.Protobuf.ByteString } } - internal static Vector FromByteString(this Grpc.Protobuf.V1.Vectors vector) + internal static NamedVector FromByteString(this Grpc.Protobuf.V1.Vectors vector) where T : struct { var byteString = vector.VectorBytes; @@ -398,16 +396,17 @@ internal static Vector FromByteString(this Grpc.Protobuf.V1.Vectors vector) } } - private static VectorSingle VectorFromByteStringSingle( + private static NamedVector VectorFromByteStringSingle( Google.Protobuf.ByteString byteString, string vectorName ) where T : struct { - return new VectorSingle([.. FromByteString(byteString)]) { Name = vectorName }; + var data = new VectorSingle([.. FromByteString(byteString)]); + return (vectorName, new Vector(data)); } - private static VectorMulti VectorFromByteStringMulti( + private static NamedVector VectorFromByteStringMulti( Google.Protobuf.ByteString byteString, string vectorName ) @@ -460,7 +459,8 @@ string vectorName } } - return new VectorMulti(result) { Name = vectorName }; + var data = new VectorMulti(result); + return (vectorName, new Vector(data)); } internal static Stream ToStream(this IEnumerable items) @@ -554,29 +554,31 @@ internal static Google.Protobuf.ByteString ToByteString(this Vector vector) return vector.ToMultiDimensionalByteString(); } - return vector.ValueType switch - { - Type t when t == typeof(float) => ToByteString(vector as IEnumerable), - Type t when t == typeof(double) => ToByteString(vector as IEnumerable), - Type t when t == typeof(int) => ToByteString(vector as IEnumerable), - Type t when t == typeof(long) => ToByteString(vector as IEnumerable), - Type t when t == typeof(short) => ToByteString(vector as IEnumerable), - Type t when t == typeof(byte) => ToByteString(vector as IEnumerable), - Type t when t == typeof(bool) => ToByteString(vector as IEnumerable), - Type t when t == typeof(decimal) => ToByteString(vector as IEnumerable), - - _ => throw new NotSupportedException( - $"The type '{vector.ValueType.FullName}' is not supported by ToByteString." - ), - }; + // Use Match pattern to access internal vector data and convert to ByteString + return vector.Match(data => + data switch + { + VectorSingle v => ToByteString(v.Values), + VectorSingle v => ToByteString(v.Values), + VectorSingle v => ToByteString(v.Values), + VectorSingle v => ToByteString(v.Values), + VectorSingle v => ToByteString(v.Values), + VectorSingle v => ToByteString(v.Values), + VectorSingle v => ToByteString(v.Values), + VectorSingle v => ToByteString(v.Values), + _ => throw new NotSupportedException( + $"The type '{vector.ValueType.FullName}' is not supported by ToByteString." + ), + } + ); } internal static Google.Protobuf.ByteString ToMultiDimensionalByteString(this Vector vector) { - if (vector == null || vector.Dimensions == 0 || vector.Count == 0) + if (vector == null || vector.Count == 0) return Google.Protobuf.ByteString.Empty; - int cols = vector.Count; + int cols = vector.Dimensions.cols; using var ms = new MemoryStream(); using var writer = new BinaryWriter(ms); @@ -584,56 +586,60 @@ internal static Google.Protobuf.ByteString ToMultiDimensionalByteString(this Vec // Write the number of columns as a little-endian short (must match FromByteStringMulti) writer.Write((short)cols); - // Write all values in row-major order based on the concrete vector type + // Use Match pattern to access internal vector data // Pattern matching on the generic type once is much more efficient than // switching on every individual element - switch (vector) + vector.Match(data => { - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write(item); - break; - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write(item); - break; - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write(item); - break; - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write(item); - break; - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write(item); - break; - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write(item); - break; - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write(item); - break; - case VectorMulti v: - foreach (var row in v) - foreach (var item in row) - writer.Write((double)item); // BinaryWriter does not support decimal - break; - default: - throw new NotSupportedException( - $"The type '{vector.ValueType.FullName}' is not supported by ToMultiDimensionalByteString." - ); - } + switch (data) + { + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write(item); + break; + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write(item); + break; + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write(item); + break; + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write(item); + break; + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write(item); + break; + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write(item); + break; + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write(item); + break; + case VectorMulti v: + foreach (var row in v) + foreach (var item in row) + writer.Write((double)item); // BinaryWriter does not support decimal + break; + default: + throw new NotSupportedException( + $"The type '{vector.ValueType.FullName}' is not supported by ToMultiDimensionalByteString." + ); + } + return 0; // Return value required by Match, but not used + }); writer.Flush(); ms.Seek(0, SeekOrigin.Begin); // Reset stream position before reading diff --git a/src/Weaviate.Client/GenerateClient.FetchObjects.cs b/src/Weaviate.Client/GenerateClient.FetchObjects.cs index d73cca9a..6794e369 100644 --- a/src/Weaviate.Client/GenerateClient.FetchObjects.cs +++ b/src/Weaviate.Client/GenerateClient.FetchObjects.cs @@ -132,7 +132,7 @@ await _client.GrpcClient.FetchObjects( var result = await _client.GrpcClient.FetchObjects( _collectionName, returnProperties: returnProperties, - filters: Filter.ID.IsEqual(id), + filters: Filter.UUID.IsEqual(id), returnReferences: returnReferences, returnMetadata: returnMetadata, includeVectors: includeVectors, @@ -185,8 +185,8 @@ public async Task FetchObjectsByIDs( Filter idFilter = ids.Count == 1 - ? Filter.ID.IsEqual(ids.First()) - : Filter.AnyOf([.. ids.Select(id => Filter.ID.IsEqual(id))]); + ? Filter.UUID.IsEqual(ids.First()) + : Filter.AnyOf([.. ids.Select(id => Filter.UUID.IsEqual(id))]); if (filters is not null) idFilter = filters & idFilter; diff --git a/src/Weaviate.Client/GenerateClient.Hybrid.cs b/src/Weaviate.Client/GenerateClient.Hybrid.cs index c5056f16..8ef951b7 100644 --- a/src/Weaviate.Client/GenerateClient.Hybrid.cs +++ b/src/Weaviate.Client/GenerateClient.Hybrid.cs @@ -7,29 +7,8 @@ public partial class GenerateClient /// /// Hybrid search with generative AI capabilities. /// - /// Search query - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative result - public async Task Hybrid( - string? query, + public Task Hybrid( + string query, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -43,17 +22,15 @@ public async Task Hybrid( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) - { - var result = await _client.GrpcClient.SearchHybrid( - _collectionClient.Name, + ) => + Hybrid( query: query, + vectors: (HybridVectorInput?)null, alpha: alpha, queryProperties: queryProperties, fusionType: fusionType, @@ -64,49 +41,22 @@ public async Task Hybrid( autoLimit: autoLimit, filters: filters, rerank: rerank, - singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, - groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - targetVector: targetVector, - tenant: _collectionClient.Tenant, - consistencyLevel: _collectionClient.ConsistencyLevel, - returnMetadata: returnMetadata, - includeVectors: includeVectors, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, returnProperties: returnProperties, returnReferences: returnReferences, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken ); - return result; - } - /// - /// Hybrid search with generative AI capabilities and grouping. + /// Hybrid search with generative AI capabilities. /// - /// Search query - /// Group by configuration - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative group-by result - public async Task Hybrid( + public async Task Hybrid( string? query, - Models.GroupByRequest groupBy, + HybridVectorInput? vectors, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -120,7 +70,6 @@ public async Task Hybrid( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -128,10 +77,18 @@ public async Task Hybrid( CancellationToken cancellationToken = default ) { + if (query is null && vectors is null) + { + throw new ArgumentException( + "At least one of 'query' or 'vectors' must be provided for hybrid search." + ); + } + var result = await _client.GrpcClient.SearchHybrid( _collectionClient.Name, query: query, alpha: alpha, + vectors: vectors, queryProperties: queryProperties, fusionType: fusionType, maxVectorDistance: maxVectorDistance, @@ -140,11 +97,9 @@ public async Task Hybrid( bm25Operator: bm25Operator, autoLimit: autoLimit, filters: filters, - groupBy: groupBy, rerank: rerank, singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - targetVector: targetVector, tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, @@ -158,33 +113,11 @@ public async Task Hybrid( } /// - /// Hybrid search with generative AI capabilities using vectors. + /// Hybrid search with generative AI capabilities and grouping. /// - /// Search query - /// Vectors for search - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative result - public async Task Hybrid( - string? query, - Vectors vectors, + public Task Hybrid( + string query, + Models.GroupByRequest groupBy, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -198,65 +131,43 @@ public async Task Hybrid( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default ) => - await Hybrid( - query, - vectors: vectors, - alpha, - queryProperties, - fusionType, - maxVectorDistance, - limit, - offset, - bm25Operator, - autoLimit, - filters, - rerank, - singlePrompt, - groupedTask, - provider, - targetVector, - returnProperties, - returnReferences, - returnMetadata, - includeVectors, - cancellationToken + Hybrid( + query: query, + vectors: (HybridVectorInput?)null, + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken ); /// - /// Hybrid search with generative AI capabilities using hybrid vector input. + /// Hybrid search with generative AI capabilities and grouping. /// - /// Search query - /// Hybrid vector input - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative result - public async Task Hybrid( + public async Task Hybrid( string? query, - IHybridVectorInput vectors, + HybridVectorInput? vectors, + Models.GroupByRequest groupBy, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -270,7 +181,6 @@ public async Task Hybrid( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -278,18 +188,18 @@ public async Task Hybrid( CancellationToken cancellationToken = default ) { + if (query is null && vectors is null) + { + throw new ArgumentException( + "At least one of 'query' or 'vectors' must be provided for hybrid search." + ); + } + var result = await _client.GrpcClient.SearchHybrid( _collectionClient.Name, query: query, alpha: alpha, - vector: vectors as Vectors, - vectors as HybridNearVector - ?? ( - vectors is NearVectorInput nv - ? new HybridNearVector(nv, null, null, targetVector) - : null - ), - nearText: vectors as HybridNearText, + vectors: vectors, queryProperties: queryProperties, fusionType: fusionType, maxVectorDistance: maxVectorDistance, @@ -298,10 +208,10 @@ vectors is NearVectorInput nv bm25Operator: bm25Operator, autoLimit: autoLimit, filters: filters, + groupBy: groupBy, rerank: rerank, singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - targetVector: targetVector, tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, @@ -313,38 +223,32 @@ vectors is NearVectorInput nv return result; } +} +/// +/// Extension methods for GenerateClient Hybrid search with lambda vector builders. +/// +public static class GenerateClientHybridExtensions +{ /// - /// Hybrid search with generative AI capabilities, grouping, and hybrid vector input. + /// Hybrid search with generative AI capabilities using a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. /// - /// Search query - /// Group by configuration - /// Alpha value for hybrid search - /// Hybrid vector input - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative group-by result - public async Task Hybrid( - string? query, - Models.GroupByRequest groupBy, + /// + /// await collection.Generate.Hybrid( + /// "test", + /// v => v.NearVector().ManualWeights( + /// ("title", 1.2, new[] { 1f, 2f }), + /// ("description", 0.8, new[] { 3f, 4f }) + /// ), + /// singlePrompt: "Describe this item" + /// ); + /// + public static async Task Hybrid( + this GenerateClient client, + string query, + HybridVectorInput.FactoryFn vectors, float? alpha = null, - IHybridVectorInput? vectors = null, string[]? queryProperties = null, HybridFusion? fusionType = null, float? maxVectorDistance = null, @@ -357,26 +261,16 @@ public async Task Hybrid( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) - { - var result = await _client.GrpcClient.SearchHybrid( - _collectionClient.Name, + ) => + await client.Hybrid( query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), alpha: alpha, - vector: vectors as Vectors, - vectors as HybridNearVector - ?? ( - vectors is NearVectorInput nv - ? new HybridNearVector(nv, null, null, targetVector) - : null - ), - nearText: vectors as HybridNearText, queryProperties: queryProperties, fusionType: fusionType, maxVectorDistance: maxVectorDistance, @@ -385,20 +279,66 @@ vectors is NearVectorInput nv bm25Operator: bm25Operator, autoLimit: autoLimit, filters: filters, - groupBy: groupBy, rerank: rerank, - singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, - groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - targetVector: targetVector, - tenant: _collectionClient.Tenant, - consistencyLevel: _collectionClient.ConsistencyLevel, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, returnMetadata: returnMetadata, includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Hybrid search with generative AI capabilities and grouping using a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. + /// + public static async Task Hybrid( + this GenerateClient client, + string query, + HybridVectorInput.FactoryFn vectors, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await client.Hybrid( + query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, returnProperties: returnProperties, returnReferences: returnReferences, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken ); - - return result; - } } diff --git a/src/Weaviate.Client/GenerateClient.NearMedia.cs b/src/Weaviate.Client/GenerateClient.NearMedia.cs index d27613c7..85798397 100644 --- a/src/Weaviate.Client/GenerateClient.NearMedia.cs +++ b/src/Weaviate.Client/GenerateClient.NearMedia.cs @@ -5,41 +5,46 @@ namespace Weaviate.Client; public partial class GenerateClient { /// - /// Search near media with generative AI capabilities. + /// Performs a near-media search with generative AI capabilities. /// - /// Media data - /// Type of media - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative result + /// + /// // Simple image search with generation + /// await collection.Generate.NearMedia( + /// m => m.Image(imageBytes), + /// singlePrompt: "Describe this image" + /// ); + /// + /// // With target vectors and generation + /// await collection.Generate.NearMedia( + /// m => m.Video(videoBytes, certainty: 0.8f).Sum("v1", "v2"), + /// singlePrompt: "Summarize this video" + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative search result. public async Task NearMedia( - byte[] media, - NearMediaType mediaType, - double? certainty = null, - double? distance = null, + NearMediaInput.FactoryFn media, + Filter? filters = null, uint? limit = null, uint? offset = null, uint? autoLimit = null, - Filter? filters = null, Rerank? rerank = null, SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -47,12 +52,13 @@ public async Task NearMedia( CancellationToken cancellationToken = default ) { + var input = media(new NearMediaBuilder()); var result = await _client.GrpcClient.SearchNearMedia( _collectionClient.Name, - media: media, - mediaType: mediaType, - certainty: certainty, - distance: distance, + media: input.Media, + mediaType: input.Type, + certainty: input.Certainty, + distance: input.Distance, limit: limit, offset: offset, autoLimit: autoLimit, @@ -62,7 +68,7 @@ public async Task NearMedia( singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, tenant: _collectionClient.Tenant, - targetVector: targetVector, + targetVector: input.TargetVectors, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, includeVectors: includeVectors, @@ -75,43 +81,43 @@ public async Task NearMedia( } /// - /// Search near media with generative AI capabilities and grouping. + /// Performs a near-media search with generative AI capabilities and group-by aggregation. /// - /// Media data - /// Type of media - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative group-by result + /// + /// // Image search with grouping and generation + /// await collection.Generate.NearMedia( + /// m => m.Image(imageBytes).Sum("visual", "semantic"), + /// groupBy: new GroupByRequest("category", objectsPerGroup: 5), + /// groupedTask: "Summarize each group" + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative grouped search result. public async Task NearMedia( - byte[] media, - NearMediaType mediaType, + NearMediaInput.FactoryFn media, GroupByRequest groupBy, - double? certainty = null, - double? distance = null, + Filter? filters = null, uint? limit = null, uint? offset = null, uint? autoLimit = null, - Filter? filters = null, Rerank? rerank = null, SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -119,12 +125,13 @@ public async Task NearMedia( CancellationToken cancellationToken = default ) { + var input = media(new NearMediaBuilder()); var result = await _client.GrpcClient.SearchNearMedia( _collectionClient.Name, - media: media, - mediaType: mediaType, - certainty: certainty, - distance: distance, + media: input.Media, + mediaType: input.Type, + certainty: input.Certainty, + distance: input.Distance, limit: limit, offset: offset, autoLimit: autoLimit, @@ -134,7 +141,7 @@ public async Task NearMedia( singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, tenant: _collectionClient.Tenant, - targetVector: targetVector, + targetVector: input.TargetVectors, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, includeVectors: includeVectors, diff --git a/src/Weaviate.Client/GenerateClient.NearObject.cs b/src/Weaviate.Client/GenerateClient.NearObject.cs index 5701b6d5..5860ad3a 100644 --- a/src/Weaviate.Client/GenerateClient.NearObject.cs +++ b/src/Weaviate.Client/GenerateClient.NearObject.cs @@ -37,7 +37,7 @@ public async Task NearObject( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -58,7 +58,7 @@ public async Task NearObject( rerank: rerank, singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, @@ -106,7 +106,7 @@ public async Task NearObject( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -127,7 +127,7 @@ public async Task NearObject( rerank: rerank, singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, diff --git a/src/Weaviate.Client/GenerateClient.NearText.cs b/src/Weaviate.Client/GenerateClient.NearText.cs index 551ef589..b20ddca3 100644 --- a/src/Weaviate.Client/GenerateClient.NearText.cs +++ b/src/Weaviate.Client/GenerateClient.NearText.cs @@ -7,7 +7,7 @@ public partial class GenerateClient /// /// Search near text with generative AI capabilities. /// - /// Text to search near + /// Text to search near /// Certainty threshold /// Distance threshold /// Move towards concept @@ -20,7 +20,7 @@ public partial class GenerateClient /// Single prompt for generation /// Grouped prompt for generation /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name + /// Target vectors /// Properties to return /// References to return /// Metadata to return @@ -28,7 +28,7 @@ public partial class GenerateClient /// Cancellation token for the operation /// Generative result public async Task NearText( - AutoArray text, + AutoArray query, float? certainty = null, float? distance = null, Move? moveTo = null, @@ -41,7 +41,6 @@ public async Task NearText( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -51,7 +50,7 @@ public async Task NearText( { var result = await _client.GrpcClient.SearchNearText( _collectionClient.Name, - text.ToArray(), + query.ToArray(), distance: distance, certainty: certainty, limit: limit, @@ -59,7 +58,7 @@ public async Task NearText( moveAway: moveAway, offset: offset, autoLimit: autoLimit, - targetVector: targetVector, + targetVector: null, filters: filters, tenant: _collectionClient.Tenant, rerank: rerank, @@ -78,7 +77,7 @@ public async Task NearText( /// /// Search near text with generative AI capabilities and grouping. /// - /// Text to search near + /// Text to search near /// Group by configuration /// Certainty threshold /// Distance threshold @@ -92,7 +91,7 @@ public async Task NearText( /// Single prompt for generation /// Grouped prompt for generation /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name + /// Target vectors /// Properties to return /// References to return /// Metadata to return @@ -100,7 +99,7 @@ public async Task NearText( /// Cancellation token for the operation /// Generative group-by result public async Task NearText( - AutoArray text, + AutoArray query, GroupByRequest groupBy, float? certainty = null, float? distance = null, @@ -114,7 +113,6 @@ public async Task NearText( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -124,7 +122,7 @@ public async Task NearText( { var result = await _client.GrpcClient.SearchNearText( _collectionClient.Name, - text.ToArray(), + query.ToArray(), groupBy: groupBy, distance: distance, certainty: certainty, @@ -138,7 +136,7 @@ public async Task NearText( rerank: rerank, singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - targetVector: targetVector, + targetVector: null, consistencyLevel: _collectionClient.ConsistencyLevel, returnProperties: returnProperties, returnReferences: returnReferences, @@ -148,4 +146,234 @@ public async Task NearText( ); return result; } + + /// + /// Search near text with generative AI capabilities using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative search results. + public async Task NearText( + NearTextInput input, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _client.GrpcClient.SearchNearText( + _collectionClient.Name, + input.Query.ToArray(), + distance: input.Distance, + certainty: input.Certainty, + limit: limit, + moveTo: input.MoveTo, + moveAway: input.MoveAway, + offset: offset, + autoLimit: autoLimit, + targetVector: input.TargetVectors, + filters: filters, + tenant: _collectionClient.Tenant, + rerank: rerank, + singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, + groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, + consistencyLevel: _collectionClient.ConsistencyLevel, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + ); + return result; + } + + /// + /// Search near text with generative AI capabilities and grouping using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative grouped search results. + public async Task NearText( + NearTextInput input, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _client.GrpcClient.SearchNearText( + _collectionClient.Name, + input.Query.ToArray(), + groupBy: groupBy, + distance: input.Distance, + certainty: input.Certainty, + moveTo: input.MoveTo, + moveAway: input.MoveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + tenant: _collectionClient.Tenant, + rerank: rerank, + singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, + groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, + targetVector: input.TargetVectors, + consistencyLevel: _collectionClient.ConsistencyLevel, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + ); + return result; + } + + /// + /// Performs a near-text search with generative AI using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative search results. + public async Task NearText( + NearTextInput.FactoryFn query, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + filters, + limit, + offset, + autoLimit, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Performs a near-text search with generative AI and grouping using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative grouped search results. + public async Task NearText( + NearTextInput.FactoryFn query, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + groupBy, + filters, + limit, + offset, + autoLimit, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); } diff --git a/src/Weaviate.Client/GenerateClient.NearVector.cs b/src/Weaviate.Client/GenerateClient.NearVector.cs index 9599b6ed..ad262ce7 100644 --- a/src/Weaviate.Client/GenerateClient.NearVector.cs +++ b/src/Weaviate.Client/GenerateClient.NearVector.cs @@ -7,33 +7,14 @@ public partial class GenerateClient /// /// Search near vector with generative AI capabilities. /// - /// Vector to search near - /// Filters to apply - /// Certainty threshold - /// Distance threshold - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Target vector name - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative result public async Task NearVector( - NearVectorInput vector, + VectorSearchInput vectors, Filter? filters = null, float? certainty = null, float? distance = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, @@ -43,17 +24,15 @@ public async Task NearVector( MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) - { - var result = await _client.GrpcClient.SearchNearVector( + ) => + await _client.GrpcClient.SearchNearVector( _collectionClient.Name, - vector, + vectors, distance: distance, certainty: certainty, offset: offset, autoLimit: autoLimit, limit: limit, - targetVector: targetVector, filters: filters, tenant: _collectionClient.Tenant, rerank: rerank, @@ -66,41 +45,19 @@ public async Task NearVector( includeVectors: includeVectors, cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); - return result; - } /// /// Search near vector with generative AI capabilities and grouping. /// - /// Vector to search near - /// Group by configuration - /// Filters to apply - /// Distance threshold - /// Certainty threshold - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Target vector name - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative group-by result public async Task NearVector( - NearVectorInput vector, + VectorSearchInput vectors, GroupByRequest groupBy, Filter? filters = null, - float? distance = null, float? certainty = null, + float? distance = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, @@ -110,11 +67,10 @@ public async Task NearVector( MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) - { - var result = await _client.GrpcClient.SearchNearVector( + ) => + await _client.GrpcClient.SearchNearVector( _collectionClient.Name, - vector, + vectors, groupBy, filters: filters, distance: distance, @@ -122,7 +78,6 @@ public async Task NearVector( offset: offset, autoLimit: autoLimit, limit: limit, - targetVector: targetVector, tenant: _collectionClient.Tenant, rerank: rerank, singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, @@ -134,39 +89,18 @@ public async Task NearVector( includeVectors: includeVectors, cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); - return result; - } /// - /// Search near vector with generative AI capabilities. + /// Search near vector with generative AI capabilities using lambda builder. /// - /// Vector to search near - /// Filters to apply - /// Certainty threshold - /// Distance threshold - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Target vector name - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative result public async Task NearVector( - Vectors vector, + VectorSearchInput.FactoryFn vectors, Filter? filters = null, float? certainty = null, float? distance = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, @@ -176,64 +110,147 @@ public async Task NearVector( MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) - { - var result = await _client.GrpcClient.SearchNearVector( - _collectionClient.Name, - (NearVectorInput)vector, - distance: distance, - certainty: certainty, - offset: offset, + ) => + await NearVector( + vectors(new VectorSearchInput.Builder()), + filters, + certainty, + distance, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities and grouping using lambda builder. + /// + public async Task NearVector( + VectorSearchInput.FactoryFn vectors, + GroupByRequest groupBy, + Filter? filters = null, + float? certainty = null, + float? distance = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors(new VectorSearchInput.Builder()), + groupBy, + filters, + certainty, + distance, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative search results. + public async Task NearVector( + NearVectorInput input, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors: input.Vector, + filters: filters, + certainty: input.Certainty, + distance: input.Distance, autoLimit: autoLimit, limit: limit, - targetVector: targetVector, - filters: filters, - tenant: _collectionClient.Tenant, + offset: offset, rerank: rerank, - singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, - groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - consistencyLevel: _collectionClient.ConsistencyLevel, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, returnProperties: returnProperties, returnReferences: returnReferences, returnMetadata: returnMetadata, includeVectors: includeVectors, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + cancellationToken: cancellationToken ); - return result; - } /// - /// Search near vector with generative AI capabilities and grouping. + /// Search near vector with generative AI capabilities and grouping using a NearVectorInput record. /// - /// Vector to search near - /// Group by configuration - /// Filters to apply - /// Distance threshold - /// Certainty threshold - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Target vector name - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Generative group-by result + /// Near-vector input containing vector, certainty, and distance. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative grouped search results. public async Task NearVector( - Vectors vector, + NearVectorInput input, GroupByRequest groupBy, Filter? filters = null, - float? distance = null, - float? certainty = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, @@ -243,30 +260,129 @@ public async Task NearVector( MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) - { - var result = await _client.GrpcClient.SearchNearVector( - _collectionClient.Name, - (NearVectorInput)vector, - groupBy, + ) => + await NearVector( + vectors: input.Vector, + groupBy: groupBy, filters: filters, - distance: distance, - certainty: certainty, - offset: offset, + certainty: input.Certainty, + distance: input.Distance, autoLimit: autoLimit, limit: limit, - targetVector: targetVector, - tenant: _collectionClient.Tenant, + offset: offset, rerank: rerank, - singlePrompt: EnrichPrompt(singlePrompt, provider) as SinglePrompt, - groupedTask: EnrichPrompt(groupedTask, provider) as GroupedTask, - consistencyLevel: _collectionClient.ConsistencyLevel, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, returnProperties: returnProperties, returnReferences: returnReferences, returnMetadata: returnMetadata, includeVectors: includeVectors, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + cancellationToken: cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative search results. + public async Task NearVector( + NearVectorInput.FactoryFn vectors, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + filters, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities and grouping using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Generative grouped search results. + public async Task NearVector( + NearVectorInput.FactoryFn vectors, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + groupBy, + filters, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken ); - return result; - } } diff --git a/src/Weaviate.Client/Internal/KeySortedList.cs b/src/Weaviate.Client/Internal/KeySortedList.cs new file mode 100644 index 00000000..fde96c87 --- /dev/null +++ b/src/Weaviate.Client/Internal/KeySortedList.cs @@ -0,0 +1,225 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace Weaviate.Client.Internal; + +public class KeySortedList(Func KeySelector) + : IEnumerable>, + IDictionary + where TKey : notnull, IComparable +{ + private readonly SortedList _innerList = []; + + public void Add(TValue item) + { + var key = KeySelector(item); + _innerList.Add(key, item); + } + + public bool ContainsKey(TKey key) + { + return _innerList.ContainsKey(key); + } + + public bool Remove(TKey key) + { + return _innerList.Remove(key); + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + return _innerList.TryGetValue(key, out value); + } + + public IEnumerator> GetEnumerator() + { + return _innerList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(TKey key, TValue value) + { + ((IDictionary)_innerList).Add(key, value); + } + + public void Add(KeyValuePair item) + { + ((ICollection>)_innerList).Add(item); + } + + public void Clear() + { + ((ICollection>)_innerList).Clear(); + } + + public bool Contains(KeyValuePair item) + { + return ((ICollection>)_innerList).Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)_innerList).CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return ((ICollection>)_innerList).Remove(item); + } + + public TValue this[TKey key] => _innerList[key]; + + public IEnumerable Keys => _innerList.Keys; + + public IEnumerable Values => _innerList.Values; + + ICollection IDictionary.Keys => + ((IDictionary)_innerList).Keys; + + ICollection IDictionary.Values => + ((IDictionary)_innerList).Values; + + public int Count => ((ICollection>)_innerList).Count; + + public bool IsReadOnly => ((ICollection>)_innerList).IsReadOnly; + + TValue IDictionary.this[TKey key] + { + get => ((IDictionary)_innerList)[key]; + set => ((IDictionary)_innerList)[key] = value; + } +} + +public class MultiKeySortedList(Func KeySelector) + : IDictionary + where TKey : notnull, IComparable +{ + private readonly SortedList> _innerList = []; + + public void AddRange(IEnumerable items) + { + foreach (var item in items) + { + Add(item); + } + } + + public void Add(TValue item) + { + var key = KeySelector(item); + Add(key, item); + } + + public void Add(TKey key, TValue value) + { + if (!_innerList.TryGetValue(key, out var list)) + { + list = []; + _innerList[key] = list; + } + list.Add(value); + } + + public void Add(TKey key, TValue[] value) + { + foreach (var item in value) + { + Add(key, item); + } + } + + public bool ContainsKey(TKey key) + { + return _innerList.ContainsKey(key); + } + + public bool Remove(TKey key) + { + return _innerList.Remove(key); + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue[] value) + { + if (_innerList.TryGetValue(key, out var list)) + { + value = [.. list]; + return true; + } + value = null; + return false; + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + _innerList.Clear(); + } + + public bool Contains(KeyValuePair item) + { + if (_innerList.TryGetValue(item.Key, out var list)) + { + return item.Value.SequenceEqual(list); + } + return false; + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (var kvp in _innerList) + { + array[arrayIndex++] = new KeyValuePair(kvp.Key, [.. kvp.Value]); + } + } + + public bool Remove(KeyValuePair item) + { + if (Contains(item)) + { + return Remove(item.Key); + } + return false; + } + + public IEnumerator> GetEnumerator() + { + foreach (var kvp in _innerList) + { + yield return new KeyValuePair(kvp.Key, [.. kvp.Value]); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public TValue[] this[TKey key] => _innerList.TryGetValue(key, out var list) ? [.. list] : []; + + public IEnumerable Keys => _innerList.Keys; + + public IEnumerable Values => _innerList.Values.SelectMany(list => list); + + ICollection IDictionary.Keys => _innerList.Keys; + + ICollection IDictionary.Values => + [.. _innerList.Values.Select(list => (TValue[])[.. list])]; + + public int Count => _innerList.Count; + + public bool IsReadOnly => false; + + TValue[] IDictionary.this[TKey key] + { + get => _innerList.TryGetValue(key, out var list) ? [.. list] : []; + set { _innerList[key] = [.. value]; } + } +} diff --git a/src/Weaviate.Client/Models/Aggregate.cs b/src/Weaviate.Client/Models/Aggregate.cs index 3d42d056..0ce5a440 100644 --- a/src/Weaviate.Client/Models/Aggregate.cs +++ b/src/Weaviate.Client/Models/Aggregate.cs @@ -96,11 +96,11 @@ internal static AggregateResult FromGrpcReply(V1.AggregateReply reply) return new AggregateResult { Properties = ( - reply.SingleResult.Aggregations != null + reply.SingleResult?.Aggregations != null ? reply.SingleResult.Aggregations : new V1.AggregateReply.Types.Aggregations() ).Aggregations_.ToDictionary(x => x.Property, AggregateResult.FromGrpcProperty), - TotalCount = reply.SingleResult.ObjectsCount, + TotalCount = reply.SingleResult?.ObjectsCount ?? 0, }; } diff --git a/src/Weaviate.Client/Models/Batch.cs b/src/Weaviate.Client/Models/Batch.cs index f6001d3f..c6b69db9 100644 --- a/src/Weaviate.Client/Models/Batch.cs +++ b/src/Weaviate.Client/Models/Batch.cs @@ -4,19 +4,19 @@ namespace Weaviate.Client.Models; public record BatchInsertRequest( object Data, - Guid? ID = null, + Guid? UUID = null, Vectors? Vectors = null, IEnumerable? References = null ) { public static BatchInsertRequest Create( object data, - Guid? id = null, + Guid? uuid = null, Vectors? vectors = null, IEnumerable? references = null ) { - return new BatchInsertRequest(data, id, vectors, references); + return new BatchInsertRequest(data, uuid, vectors, references); } public static BatchInsertRequest[] Create(IEnumerable data) @@ -24,9 +24,9 @@ public static BatchInsertRequest[] Create(IEnumerable data) return data.Select(d => new BatchInsertRequest(d)).ToArray(); } - public static BatchInsertRequest[] Create(IEnumerable<(object data, Guid id)> requests) + public static BatchInsertRequest[] Create(IEnumerable<(object data, Guid uuid)> requests) { - return requests.Select(r => new BatchInsertRequest(r.data, r.id)).ToArray(); + return requests.Select(r => new BatchInsertRequest(r.data, r.uuid)).ToArray(); } public static BatchInsertRequest[] Create( @@ -44,7 +44,11 @@ public static BatchInsertRequest[] Create(IEnumerable<(object data, Vectors vect } } -public record BatchInsertResponseEntry(int Index, Guid? ID = null, WeaviateException? Error = null); +public record BatchInsertResponseEntry( + int Index, + Guid? UUID = null, + WeaviateException? Error = null +); public record BatchInsertResponse : IEnumerable { diff --git a/src/Weaviate.Client/Models/Filter.cs b/src/Weaviate.Client/Models/Filter.cs index c4c1f00c..d2f04c0f 100644 --- a/src/Weaviate.Client/Models/Filter.cs +++ b/src/Weaviate.Client/Models/Filter.cs @@ -126,7 +126,7 @@ protected Filter() { } public static Filter Not(Filter filter) => new NotNestedFilter(filter); - public static TypedGuid ID => new(Property("_id")); + public static TypedGuid UUID => new(Property("_id")); public static PropertyFilter Property(string name) => new(name.Decapitalize()); @@ -380,7 +380,7 @@ public TypedValue Count } } - public new TypedGuid ID + public new TypedGuid UUID { get { return new TypedGuid(Property("_id")); } } diff --git a/src/Weaviate.Client/Models/HybridVectorInput.cs b/src/Weaviate.Client/Models/HybridVectorInput.cs new file mode 100644 index 00000000..5d7f8950 --- /dev/null +++ b/src/Weaviate.Client/Models/HybridVectorInput.cs @@ -0,0 +1,123 @@ +namespace Weaviate.Client.Models; + +/// +/// Discriminated union for hybrid search vector inputs. +/// Can hold exactly one of: VectorSearchInput, NearTextInput, or NearVectorInput. +/// +public sealed class HybridVectorInput +{ + /// + /// Delegate for lambda builder pattern with HybridVectorInput. + /// + public delegate HybridVectorInput FactoryFn(HybridVectorInputBuilder builder); + + public VectorSearchInput? VectorSearch { get; } + public NearTextInput? NearText { get; } + public NearVectorInput? NearVector { get; } + + private HybridVectorInput( + VectorSearchInput? vectorSearch = null, + NearTextInput? nearText = null, + NearVectorInput? nearVector = null + ) + { + var setCount = + (vectorSearch != null ? 1 : 0) + + (nearText != null ? 1 : 0) + + (nearVector != null ? 1 : 0); + if (setCount != 1) + { + throw new ArgumentException( + "HybridVectorInput must contain exactly one of: VectorSearch, NearText, or NearVector." + ); + } + + VectorSearch = vectorSearch; + NearText = nearText; + NearVector = nearVector; + } + + /// + /// Creates a HybridVectorInput from a VectorSearchInput. + /// + public static HybridVectorInput FromVectorSearch(VectorSearchInput vectorSearch) + { + ArgumentNullException.ThrowIfNull(vectorSearch); + return new HybridVectorInput(vectorSearch: vectorSearch); + } + + /// + /// Creates a HybridVectorInput from a NearTextInput. + /// + public static HybridVectorInput FromNearText(NearTextInput nearText) + { + ArgumentNullException.ThrowIfNull(nearText); + return new HybridVectorInput(nearText: nearText); + } + + /// + /// Creates a HybridVectorInput from a NearVectorInput. + /// + public static HybridVectorInput FromNearVector(NearVectorInput nearVector) + { + ArgumentNullException.ThrowIfNull(nearVector); + return new HybridVectorInput(nearVector: nearVector); + } + + /// + /// Pattern matching over the union type. + /// + public TResult Match( + Func onVectorSearch, + Func onNearText, + Func onNearVector + ) + { + if (VectorSearch != null) + return onVectorSearch(VectorSearch); + if (NearText != null) + return onNearText(NearText); + if (NearVector != null) + return onNearVector(NearVector); + + throw new InvalidOperationException("HybridVectorInput is in an invalid state."); + } + + // Implicit conversions for ergonomic API usage + + public static implicit operator HybridVectorInput(VectorSearchInput vectorSearch) => + FromVectorSearch(vectorSearch); + + public static implicit operator HybridVectorInput(NearTextInput nearText) => + FromNearText(nearText); + + public static implicit operator HybridVectorInput(NearVectorInput nearVector) => + FromNearVector(nearVector); + + public static implicit operator HybridVectorInput(float[] vector) => FromVectorSearch(vector); + + public static implicit operator HybridVectorInput(double[] vector) => FromVectorSearch(vector); + + public static implicit operator HybridVectorInput(Vectors vectors) => FromVectorSearch(vectors); + + public static implicit operator HybridVectorInput(Vector vector) => + FromVectorSearch(new Vectors(vector)); + + public static implicit operator HybridVectorInput(NamedVector namedVector) => + FromVectorSearch(namedVector); + + public static implicit operator HybridVectorInput(string query) => + FromNearText(new NearTextInput(query)); + + public static implicit operator HybridVectorInput((string name, float[] vector) tuple) => + FromVectorSearch(new VectorSearchInput { { tuple.name, tuple.vector } }); + + public static implicit operator HybridVectorInput((string name, double[] vector) tuple) => + FromVectorSearch(new VectorSearchInput { { tuple.name, tuple.vector } }); + + public static implicit operator HybridVectorInput((string name, float[,] vectors) tuple) => + FromVectorSearch(new VectorSearchInput { { tuple.name, tuple.vectors } }); + + public static implicit operator HybridVectorInput((string name, double[,] vectors) tuple) => + FromVectorSearch(new VectorSearchInput { { tuple.name, tuple.vectors } }); +} diff --git a/src/Weaviate.Client/Models/Move.cs b/src/Weaviate.Client/Models/Move.cs index bb6bb253..76382f4f 100644 --- a/src/Weaviate.Client/Models/Move.cs +++ b/src/Weaviate.Client/Models/Move.cs @@ -6,14 +6,14 @@ public record Move public Guid[]? Objects { get; } public string[]? Concepts { get; } - public Move(float force, AutoArray objects) + public Move(AutoArray objects, float force) { Force = force; Objects = [.. objects]; Concepts = null; } - public Move(float force, AutoArray concepts) + public Move(AutoArray concepts, float force) { Force = force; Objects = null; diff --git a/src/Weaviate.Client/Models/NearMediaInput.cs b/src/Weaviate.Client/Models/NearMediaInput.cs new file mode 100644 index 00000000..c89e7a53 --- /dev/null +++ b/src/Weaviate.Client/Models/NearMediaInput.cs @@ -0,0 +1,259 @@ +using Weaviate.Client.Models; + +namespace Weaviate.Client; + +/// +/// Input for near-media searches (Image, Video, Audio, Thermal, etc.). +/// Supports optional target vectors for multi-vector collections. +/// +public record NearMediaInput( + byte[] Media, + NearMediaType Type, + TargetVectors? TargetVectors = null, + float? Certainty = null, + float? Distance = null +) +{ + /// + /// Delegate for lambda builder pattern with NearMediaInput. + /// + /// + /// m => m.Image(imageBytes).Sum("vector1", "vector2") + /// m => m.Video(videoBytes).ManualWeights(("title", 1.2), ("desc", 0.8)) + /// m => m.Audio(audioBytes).Build() // No targets, uses default vector + /// + public delegate NearMediaInput FactoryFn(NearMediaBuilder builder); + + /// + /// Enables returning a directly from a lambda via + /// its implicit conversion to . + /// (The implicit operator is declared on NearMediaBuilder.) + /// +} + +// ============================================================================ +// NearMedia Builder Infrastructure +// ============================================================================ + +/// +/// Starting point for NearMedia builder - select media type first. +/// +public interface INearMediaBuilderStart +{ + /// + /// Creates a near-image search with optional target vectors. + /// + NearMediaBuilder Image(byte[] media, float? certainty = null, float? distance = null); + + /// + /// Creates a near-video search with optional target vectors. + /// + NearMediaBuilder Video(byte[] media, float? certainty = null, float? distance = null); + + /// + /// Creates a near-audio search with optional target vectors. + /// + NearMediaBuilder Audio(byte[] media, float? certainty = null, float? distance = null); + + /// + /// Creates a near-thermal search with optional target vectors. + /// + NearMediaBuilder Thermal(byte[] media, float? certainty = null, float? distance = null); + + /// + /// Creates a near-depth search with optional target vectors. + /// + NearMediaBuilder Depth(byte[] media, float? certainty = null, float? distance = null); + + /// + /// Creates a near-IMU search with optional target vectors. + /// + NearMediaBuilder IMU(byte[] media, float? certainty = null, float? distance = null); +} + +/// +/// Builder interface for creating NearMediaInput with optional target vectors. +/// Can be used directly (for no targets) or chained with target vector methods. +/// +public interface INearMediaBuilder +{ + /// + /// Creates a NearMediaInput with manually weighted target vectors. + /// + /// Tuples of (targetName, weight) + NearMediaInput TargetVectorsManualWeights(params (string Name, double Weight)[] targets); + + /// + /// Creates a NearMediaInput that sums all target vectors. + /// + /// Target vector names + NearMediaInput TargetVectorsSum(params string[] targets); + + /// + /// Creates a NearMediaInput that averages all target vectors. + /// + /// Target vector names + NearMediaInput TargetVectorsAverage(params string[] targets); + + /// + /// Creates a NearMediaInput using minimum combination of target vectors. + /// + /// Target vector names + NearMediaInput TargetVectorsMinimum(params string[] targets); + + /// + /// Creates a NearMediaInput using relative score combination of target vectors. + /// + /// Tuples of (targetName, weight) + NearMediaInput TargetVectorsRelativeScore(params (string Name, double Weight)[] targets); + + /// + /// Completes the builder without specifying target vectors (uses default behavior). + /// + NearMediaInput Build(); +} + +/// +/// Internal implementation of INearMediaBuilder. +/// +public sealed class NearMediaBuilder : INearMediaBuilderStart, INearMediaBuilder +{ + private byte[]? _media; + private NearMediaType? _type; + private float? _certainty; + private float? _distance; + + public NearMediaBuilder Image(byte[] media, float? certainty = null, float? distance = null) + { + _media = media; + _type = NearMediaType.Image; + _certainty = certainty; + _distance = distance; + return this; + } + + public NearMediaBuilder Video(byte[] media, float? certainty = null, float? distance = null) + { + _media = media; + _type = NearMediaType.Video; + _certainty = certainty; + _distance = distance; + return this; + } + + public NearMediaBuilder Audio(byte[] media, float? certainty = null, float? distance = null) + { + _media = media; + _type = NearMediaType.Audio; + _certainty = certainty; + _distance = distance; + return this; + } + + public NearMediaBuilder Thermal(byte[] media, float? certainty = null, float? distance = null) + { + _media = media; + _type = NearMediaType.Thermal; + _certainty = certainty; + _distance = distance; + return this; + } + + public NearMediaBuilder Depth(byte[] media, float? certainty = null, float? distance = null) + { + _media = media; + _type = NearMediaType.Depth; + _certainty = certainty; + _distance = distance; + return this; + } + + public NearMediaBuilder IMU(byte[] media, float? certainty = null, float? distance = null) + { + _media = media; + _type = NearMediaType.IMU; + _certainty = certainty; + _distance = distance; + return this; + } + + public NearMediaInput TargetVectorsManualWeights(params (string Name, double Weight)[] targets) + { + ValidateMedia(); + return new NearMediaInput( + _media!, + _type!.Value, + TargetVectors.ManualWeights(targets), + _certainty, + _distance + ); + } + + public NearMediaInput TargetVectorsSum(params string[] targets) + { + ValidateMedia(); + return new NearMediaInput( + _media!, + _type!.Value, + TargetVectors.Sum(targets), + _certainty, + _distance + ); + } + + public NearMediaInput TargetVectorsAverage(params string[] targets) + { + ValidateMedia(); + return new NearMediaInput( + _media!, + _type!.Value, + TargetVectors.Average(targets), + _certainty, + _distance + ); + } + + public NearMediaInput TargetVectorsMinimum(params string[] targets) + { + ValidateMedia(); + return new NearMediaInput( + _media!, + _type!.Value, + TargetVectors.Minimum(targets), + _certainty, + _distance + ); + } + + public NearMediaInput TargetVectorsRelativeScore(params (string Name, double Weight)[] targets) + { + ValidateMedia(); + return new NearMediaInput( + _media!, + _type!.Value, + TargetVectors.RelativeScore(targets), + _certainty, + _distance + ); + } + + public NearMediaInput Build() + { + ValidateMedia(); + return new NearMediaInput(_media!, _type!.Value, null, _certainty, _distance); + } + + private void ValidateMedia() + { + if (_media == null || _type == null) + { + throw new InvalidOperationException( + "Media type and data must be set before building. " + + "Call Image(), Video(), Audio(), Thermal(), Depth(), or IMU() first." + ); + } + } + + // Implicit conversion to allow usage without calling Build() + public static implicit operator NearMediaInput(NearMediaBuilder builder) => builder.Build(); +} diff --git a/src/Weaviate.Client/Models/Search.cs b/src/Weaviate.Client/Models/Search.cs index df0c17c0..85a1d261 100644 --- a/src/Weaviate.Client/Models/Search.cs +++ b/src/Weaviate.Client/Models/Search.cs @@ -2,149 +2,68 @@ namespace Weaviate.Client.Models; -public interface IHybridVectorInput -{ - // This interface is used to mark hybrid vectors, which can be either near vector or near text. - // It allows for polymorphic behavior in the Hybrid methods. -} - -public interface INearVectorInput { } - -public record NearVectorInput : IEnumerable, IHybridVectorInput, INearVectorInput +public record NearVectorInput( + VectorSearchInput Vector, + float? Certainty = null, + float? Distance = null +) { - private readonly List _vectors = []; - - public IReadOnlyDictionary Vectors => - _vectors.GroupBy(v => v.Name).ToDictionary(g => g.Key, g => g.ToArray()); - - public NearVectorInput() { } - - public NearVectorInput(params Vector[] vectors) - { - foreach (var v in vectors) - { - Add(v.Name, v); - } - } - - public static implicit operator NearVectorInput(Vector vector) - { - return new NearVectorInput([vector]); - } - - public static implicit operator NearVectorInput(Vector[] vector) - { - return new NearVectorInput(vector); - } - - public static implicit operator NearVectorInput(Vectors vectors) - { - return new NearVectorInput([.. vectors.Values]); - } - - public void Add(string name, params Vector[] values) => - _vectors.AddRange(values.Select(v => Vector.Create(name, v))); - - public void Add(Models.Vectors vectors) - { - _vectors.AddRange(vectors.Values); - } - - private static NearVectorInput FromVectorDictionary( - IEnumerable>> vectors - ) - { - var ret = new NearVectorInput(); - foreach (var (name, values) in vectors) - { - ret.Add(name, [.. values]); - } - return ret; - } - - private static NearVectorInput FromSingleVectorDictionary( - Dictionary vectors, - Func converter + /// + /// Delegate for lambda builder pattern with NearVectorInput. + /// + public delegate NearVectorInput FactoryFn(NearVectorInputBuilder builder); + + /// + /// Constructor overload accepting lambda builder for vector input. + /// + public NearVectorInput( + VectorSearchInput.FactoryFn Vector, + float? Certainty = null, + float? Distance = null ) - { - var ret = new NearVectorInput(); - foreach (var (name, value) in vectors) - { - ret.Add(name, converter(value)); - } - return ret; - } - - public static implicit operator NearVectorInput(Dictionary vectors) => - FromVectorDictionary( - vectors.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.AsEnumerable())) - ); - - public static implicit operator NearVectorInput(Dictionary vectors) => - FromSingleVectorDictionary(vectors, Vector.Create); - - public static implicit operator NearVectorInput(Dictionary vectors) => - FromSingleVectorDictionary(vectors, Vector.Create); - - public static implicit operator NearVectorInput(Dictionary vectors) => - FromSingleVectorDictionary(vectors, Vector.Create); - - public static implicit operator NearVectorInput(Dictionary vectors) => - FromSingleVectorDictionary(vectors, Vector.Create); - - public static implicit operator NearVectorInput( - Dictionary> vectors - ) => - FromVectorDictionary( - vectors.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.Select(Vector.Create))) - ); - - public static implicit operator NearVectorInput( - Dictionary> vectors - ) => - FromVectorDictionary( - vectors.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.Select(Vector.Create))) - ); - - public static implicit operator NearVectorInput( - Dictionary> vectors - ) => - FromVectorDictionary( - vectors.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.Select(Vector.Create))) - ); - - public static implicit operator NearVectorInput( - Dictionary> vectors - ) => - FromVectorDictionary( - vectors.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.Select(Vector.Create))) - ); - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_vectors).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_vectors).GetEnumerator(); - } -} - -public record HybridNearVector( - NearVectorInput Vector, - float? Certainty = null, - float? Distance = null, - TargetVectors? targetVector = null -) : IHybridVectorInput { }; - -public record HybridNearText( - string Query, + : this( + Vector: Vector(new VectorSearchInput.Builder()), + Certainty: Certainty, + Distance: Distance + ) { } + + public static implicit operator NearVectorInput(VectorSearchInput vectors) => new(vectors); +}; + +public record NearTextInput( + AutoArray Query, + TargetVectors? TargetVectors = null, float? Certainty = null, float? Distance = null, Move? MoveTo = null, Move? MoveAway = null -) : IHybridVectorInput; +) +{ + /// + /// Delegate for lambda builder pattern with NearTextInput. + /// + public delegate NearTextInput FactoryFn(NearTextInputBuilder builder); + + /// + /// Constructor overload accepting lambda builder for target vectors. + /// + public NearTextInput( + AutoArray Query, + TargetVectors.FactoryFn TargetVectors, + float? Certainty = null, + float? Distance = null, + Move? MoveTo = null, + Move? MoveAway = null + ) + : this( + Query: Query, + Certainty: Certainty, + Distance: Distance, + MoveTo: MoveTo, + MoveAway: MoveAway, + TargetVectors: TargetVectors(new TargetVectors.Builder()) + ) { } +}; public abstract record BM25Operator(string Operator) { diff --git a/src/Weaviate.Client/Models/TargetVectors.cs b/src/Weaviate.Client/Models/TargetVectors.cs index 6872777b..754e70df 100644 --- a/src/Weaviate.Client/Models/TargetVectors.cs +++ b/src/Weaviate.Client/Models/TargetVectors.cs @@ -1,102 +1,240 @@ namespace Weaviate.Client.Models; +using System.Runtime.CompilerServices; using V1 = Grpc.Protobuf.V1; -public class TargetVectors : IEnumerable +/// +/// Base class for target vector configuration for text/media-based searches. +/// Cannot be constructed directly - use lambda syntax with builder methods. +/// +[CollectionBuilder(typeof(TargetVectors), nameof(Create))] +public abstract record TargetVectors : IEnumerable { - public List Targets { get; } = new List(); - internal V1.CombinationMethod Combination { get; } = V1.CombinationMethod.Unspecified; - public Dictionary>? Weights { get; } + internal TargetVectors() { } // Prevent external inheritance - public TargetVectors() { } + /// + /// Factory delegate for creating VectorSearchInput using a builder pattern. + /// Example: FactoryFn factory = b => b.Sum(("title", vec1), ("desc", vec2)) + /// + public delegate TargetVectors FactoryFn(Builder builder); - public void Add(string target) + public abstract IReadOnlyList Targets { get; } + internal abstract V1.CombinationMethod Combination { get; } + + public IEnumerator GetEnumerator() => Targets.GetEnumerator(); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => + GetEnumerator(); + + // Static helpers to build target vectors from VectorSearchInput + public static TargetVectors Unspecified(AutoArray vectors) { - Targets.Add(target); + return new SimpleTargetVectors(vectors?.ToArray() ?? [], V1.CombinationMethod.Unspecified); } - public void AddRange(IEnumerable targets) + public static TargetVectors Sum(VectorSearchInput vectors) { - Targets.AddRange(targets); + var targets = vectors.Targets ?? [.. vectors.Vectors.Keys]; + return new SimpleTargetVectors(targets, V1.CombinationMethod.TypeSum); } - public IEnumerator GetEnumerator() + public static TargetVectors Average(VectorSearchInput vectors) { - return Targets.GetEnumerator(); + var targets = vectors.Targets ?? [.. vectors.Vectors.Keys]; + return new SimpleTargetVectors(targets, V1.CombinationMethod.TypeAverage); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + public static TargetVectors Minimum(VectorSearchInput vectors) { - return GetEnumerator(); + var targets = vectors.Targets ?? [.. vectors.Vectors.Keys]; + return new SimpleTargetVectors(targets, V1.CombinationMethod.TypeMin); } - internal IEnumerable<(string name, double? weight)> GetVectorWithWeights() + // Static helpers for simple target vectors + public static TargetVectors Sum(params string[] targets) => + new SimpleTargetVectors(targets, V1.CombinationMethod.TypeSum); + + public static TargetVectors Average(params string[] targets) => + new SimpleTargetVectors(targets, V1.CombinationMethod.TypeAverage); + + public static TargetVectors Minimum(params string[] targets) => + new SimpleTargetVectors(targets, V1.CombinationMethod.TypeMin); + + // Static helpers for weighted target vectors + // Supports multiple weights per target (e.g., ManualWeights(("a", 1.0), ("a", 2.0))) + public static TargetVectors ManualWeights(params (string name, double weight)[] weights) { - foreach (var target in Targets) - { - if (Weights?.TryGetValue(target, out var weightList) ?? false) - { - foreach (var weight in weightList) - { - yield return (target, weight); - } - } - else - { - yield return (target, null); - } - } + var dict = weights + .GroupBy(w => w.name) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.Select(w => w.weight).ToList()); + return new WeightedTargetVectors( + targets: weights.Select(w => w.name).Distinct().ToList(), + combination: V1.CombinationMethod.TypeManual, + weights: dict + ); } - private TargetVectors(IEnumerable targets) + public static TargetVectors RelativeScore(params (string name, double weight)[] weights) { - Targets.AddRange(targets); + var dict = weights + .GroupBy(w => w.name) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.Select(w => w.weight).ToList()); + return new WeightedTargetVectors( + targets: weights.Select(w => w.name).Distinct().ToList(), + combination: V1.CombinationMethod.TypeRelativeScore, + weights: dict + ); } - private TargetVectors( - IEnumerable? targets = null, - V1.CombinationMethod combination = V1.CombinationMethod.Unspecified, - Dictionary>? weights = null - ) + // Implicit conversion from string array for convenience + public static implicit operator TargetVectors(string[] targets) => + new SimpleTargetVectors(targets, V1.CombinationMethod.Unspecified); + + /// + /// Creates a TargetVectors from a collection expression. + /// Enables syntax like: targets: ["title", "description"] + /// + public static TargetVectors Create(ReadOnlySpan targets) => + new SimpleTargetVectors(targets.ToArray(), V1.CombinationMethod.Unspecified); + + /// + /// Builder for creating TargetVectors via lambda syntax. + /// + public sealed class Builder { - Targets = [.. targets ?? weights?.Keys ?? Enumerable.Empty()]; - Combination = combination; - Weights = weights; - } + internal Builder() { } - // Implicit conversion from string[] - public static implicit operator TargetVectors(string[] names) => [.. names]; + /// + /// Specifies target vector names without a combination method. + /// + public SimpleTargetVectors Targets(params string[] names) + { + return new SimpleTargetVectors( + targets: names, + combination: V1.CombinationMethod.Unspecified + ); + } + + /// + /// Creates a multi-target configuration with Sum combination. + /// + public SimpleTargetVectors Sum(params string[] names) + { + return new SimpleTargetVectors( + targets: names, + combination: V1.CombinationMethod.TypeSum + ); + } - // Implicit conversion from List - public static implicit operator TargetVectors(List names) => [.. names]; + /// + /// Creates a multi-target configuration with Average combination. + /// + public SimpleTargetVectors Average(params string[] names) + { + return new SimpleTargetVectors( + targets: names, + combination: V1.CombinationMethod.TypeAverage + ); + } - // Sum - public static TargetVectors Sum(IEnumerable names) => - new(names, V1.CombinationMethod.TypeSum); + /// + /// Creates a multi-target configuration with Minimum combination. + /// + public SimpleTargetVectors Minimum(params string[] names) + { + return new SimpleTargetVectors( + targets: names, + combination: V1.CombinationMethod.TypeMin + ); + } - // Minimum - public static TargetVectors Minimum(IEnumerable names) => - new(names, V1.CombinationMethod.TypeMin); + /// + /// Creates a multi-target configuration with ManualWeights. + /// Supports multiple weights per target. + /// + public WeightedTargetVectors ManualWeights(params (string name, double weight)[] weights) + { + var dict = weights + .GroupBy(w => w.name) + .ToDictionary( + g => g.Key, + g => (IReadOnlyList)g.Select(w => w.weight).ToList() + ); + return new WeightedTargetVectors( + targets: weights.Select(w => w.name).Distinct().ToList(), + combination: V1.CombinationMethod.TypeManual, + weights: dict + ); + } - // Average - public static TargetVectors Average(IEnumerable names) => - new(names, V1.CombinationMethod.TypeAverage); + /// + /// Creates a multi-target configuration with RelativeScore. + /// Supports multiple weights per target. + /// + public WeightedTargetVectors RelativeScore(params (string name, double weight)[] weights) + { + var dict = weights + .GroupBy(w => w.name) + .ToDictionary( + g => g.Key, + g => (IReadOnlyList)g.Select(w => w.weight).ToList() + ); + return new WeightedTargetVectors( + targets: weights.Select(w => w.name).Distinct().ToList(), + combination: V1.CombinationMethod.TypeRelativeScore, + weights: dict + ); + } + } +} - // ManualWeights - public static TargetVectors ManualWeights( - params (string name, AutoArray weight)[] weights +/// +/// Simple target vectors without weights (Sum, Average, Minimum, Targets). +/// +public sealed record SimpleTargetVectors : TargetVectors +{ + internal SimpleTargetVectors( + IReadOnlyList targets, + V1.CombinationMethod combination = V1.CombinationMethod.Unspecified ) { - var dict = weights.ToDictionary(w => w.name, w => w.weight.ToList()); - return new TargetVectors(dict.Keys, V1.CombinationMethod.TypeManual, dict); + Targets = targets; + Combination = combination; } - // RelativeScore - public static TargetVectors RelativeScore( - params (string name, AutoArray weight)[] weights + public override IReadOnlyList Targets { get; } + internal override V1.CombinationMethod Combination { get; } +} + +/// +/// Weighted target vectors with per-target weights (ManualWeights, RelativeScore). +/// +public sealed record WeightedTargetVectors : TargetVectors +{ + internal WeightedTargetVectors( + IReadOnlyList targets, + V1.CombinationMethod combination, + IReadOnlyDictionary> weights ) { - var dict = weights.ToDictionary(w => w.name, w => w.weight.ToList()); - return new TargetVectors(dict.Keys, V1.CombinationMethod.TypeRelativeScore, dict); + Targets = targets; + Combination = combination; + Weights = weights; + } + + public override IReadOnlyList Targets { get; } + internal override V1.CombinationMethod Combination { get; } + public IReadOnlyDictionary> Weights { get; } + + internal IEnumerable<(string name, double weight)> GetTargetWithWeights() + { + foreach (var target in Targets) + { + if (Weights.TryGetValue(target, out var weightList)) + { + foreach (var weight in weightList) + yield return (target, weight); + } + } } } diff --git a/src/Weaviate.Client/Models/VectorConfig.cs b/src/Weaviate.Client/Models/VectorConfig.cs index 54f107ab..ca223aff 100644 --- a/src/Weaviate.Client/Models/VectorConfig.cs +++ b/src/Weaviate.Client/Models/VectorConfig.cs @@ -5,10 +5,10 @@ namespace Weaviate.Client.Models; /// public record VectorConfig : IEquatable { - /// The name of the vector configuration. - /// Configuration of a specific vectorizer used by this vector. - /// Vector-index config, that is specific to the type of index selected in vectorIndexType. - public VectorConfig( + /// The name of the vector configuration. + /// Configuration of a specific vectorizer used by this vector. + /// Vector-index config, that is specific to the type of index selected in vectorIndexType. + internal VectorConfig( string name, VectorizerConfig? vectorizer = null, VectorIndexConfig? vectorIndexConfig = null diff --git a/src/Weaviate.Client/Models/VectorData.cs b/src/Weaviate.Client/Models/VectorData.cs index 5b1b845b..1bb4c147 100644 --- a/src/Weaviate.Client/Models/VectorData.cs +++ b/src/Weaviate.Client/Models/VectorData.cs @@ -1,189 +1,34 @@ using System.Collections; +using System.Runtime.CompilerServices; namespace Weaviate.Client.Models; -public abstract record Vector : IEnumerable, IHybridVectorInput, INearVectorInput +/// +/// Internal interface for vector data storage. +/// +internal interface IVectorData { - public string Name { get; init; } = "default"; - public abstract int Dimensions { get; } - public abstract int Count { get; } - public abstract Type ValueType { get; } - public bool IsMultiVector => Count > 1; - - public object this[Index index] - { - get - { - return this switch - { - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorSingle v => v.Values[index.GetOffset(v.Values.Length)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - VectorMulti v => v[index.GetOffset(v.Dimensions)], - _ => throw new NotImplementedException( - "Indexing not supported for this vector type." - ), - }; - } - } - - public static Vector Create(string name, Vector values) - { - return values with { Name = name }; - } - - public static Vector Create(params T[] values) => new VectorSingle(values); - - public static Vector Create(T[,] values) => new VectorMulti(values); - - public static Vector Create(string name, params T[] values) - where T : struct => new VectorSingle(values) { Name = name }; - - public static Vector Create(string name, T[,] values) - where T : struct => new VectorMulti(values) { Name = name }; - - public IEnumerator GetEnumerator() - { - return this switch - { - VectorSingle v => v.Values.GetEnumerator(), - VectorSingle v => v.Values.GetEnumerator(), - VectorSingle v => v.Values.GetEnumerator(), - VectorSingle v => v.Values.GetEnumerator(), - VectorSingle v => v.Values.GetEnumerator(), - VectorSingle v => v.Values.GetEnumerator(), - VectorSingle v => v.Values.GetEnumerator(), - VectorSingle v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - VectorMulti v => v.Values.GetEnumerator(), - _ => throw new NotImplementedException("Use a derived type to enumerate values."), - }; - } - - #region Implicit Operators to Native Arrays - // SingleVector implicit operators (inverted) - public static implicit operator double[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - public static implicit operator float[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - public static implicit operator int[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - public static implicit operator long[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - public static implicit operator short[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - public static implicit operator byte[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - public static implicit operator bool[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - public static implicit operator decimal[](Vector vector) => - vector is VectorSingle v ? v.Values : throw new InvalidCastException(); - - // MultiVector implicit operators (inverted) - public static implicit operator double[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - - public static implicit operator float[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - - public static implicit operator int[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - - public static implicit operator long[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - - public static implicit operator short[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - - public static implicit operator byte[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - - public static implicit operator bool[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - - public static implicit operator decimal[,](Vector vector) => - vector is VectorMulti v ? v.Values : throw new InvalidCastException(); - #endregion - - #region Implicit Operators to Vector - // SingleVector implicit operators - public static implicit operator Vector(double[] values) => new VectorSingle(values); - - public static implicit operator Vector(float[] values) => new VectorSingle(values); - - public static implicit operator Vector(int[] values) => new VectorSingle(values); - - public static implicit operator Vector(long[] values) => new VectorSingle(values); - - public static implicit operator Vector(short[] values) => new VectorSingle(values); - - public static implicit operator Vector(byte[] values) => new VectorSingle(values); - - public static implicit operator Vector(bool[] values) => new VectorSingle(values); - - public static implicit operator Vector(decimal[] values) => new VectorSingle(values); - - // MultiVector implicit operators - public static implicit operator Vector(double[,] values) => new VectorMulti(values); - - public static implicit operator Vector(float[,] values) => new VectorMulti(values); - - public static implicit operator Vector(int[,] values) => new VectorMulti(values); - - public static implicit operator Vector(long[,] values) => new VectorMulti(values); - - public static implicit operator Vector(short[,] values) => new VectorMulti(values); - - public static implicit operator Vector(byte[,] values) => new VectorMulti(values); - - public static implicit operator Vector(bool[,] values) => new VectorMulti(values); - - public static implicit operator Vector(decimal[,] values) => new VectorMulti(values); - #endregion + (int rows, int cols) Dimensions { get; } + int Count { get; } + Type ValueType { get; } + bool IsMultiVector { get; } } -public sealed record VectorSingle : Vector, IEnumerable +/// +/// Internal storage for single-dimension vectors. +/// +internal sealed record VectorSingle(T[] Values) : IVectorData, IEnumerable + where T : struct { - public override int Dimensions => Values.Length; - public override int Count => 1; - public override Type ValueType => typeof(T); + public (int rows, int cols) Dimensions => (1, Values.Length); + public int Count => Values.Length; + public Type ValueType => typeof(T); + public bool IsMultiVector => false; - public new IEnumerator GetEnumerator() => ((IEnumerable)Values).GetEnumerator(); + public IEnumerator GetEnumerator() => ((IEnumerable)Values).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public T[] Values { get; init; } - - public VectorSingle(params T[] values) - { - Values = values; - } - public bool Equals(VectorSingle? other) { if (other is null) @@ -191,14 +36,13 @@ public bool Equals(VectorSingle? other) if (ReferenceEquals(this, other)) return true; - // Compare Name and Values - return Name == other.Name && Values.SequenceEqual(other.Values); + // Compare Values only + return Values.SequenceEqual(other.Values); } public override int GetHashCode() { var hash = new HashCode(); - hash.Add(Name); foreach (var value in Values) { hash.Add(value); @@ -207,54 +51,45 @@ public override int GetHashCode() } } -public sealed record VectorMulti : Vector, IEnumerable +/// +/// Internal storage for multi-dimension vectors (ColBERT-style). +/// +internal sealed record VectorMulti(T[,] Values) : IVectorData, IEnumerable + where T : struct { - public override int Dimensions => _rows; - public override Type ValueType => typeof(T[]); - public override int Count => _cols; + private readonly int _rows = Values.GetLength(0); + private readonly int _cols = Values.GetLength(1); - public new IEnumerator GetEnumerator() - { - for (int i = 0; i < _rows; i++) - { - yield return this[i]; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private readonly T[,] _values; - private readonly int _rows; - private readonly int _cols; - - public VectorMulti(T[,] values) - { - _values = values ?? throw new ArgumentNullException(nameof(values)); - _rows = values.GetLength(0); - _cols = values.GetLength(1); - } - - // Expose the underlying array as a property if needed - public T[,] Values => _values; - - // Optionally, provide an indexer for row/col access - public T this[int dimension, int index] => _values[dimension, index]; + public (int rows, int cols) Dimensions => (_rows, _cols); + public int Count => _rows * _cols; + public Type ValueType => typeof(T[]); + public bool IsMultiVector => true; - public T[] this[int dimension] + public T[] this[Index row] { get { - if (dimension < 0 || dimension >= _rows) - throw new IndexOutOfRangeException(nameof(dimension)); + if (row.Value < 0 || row.Value >= _rows) + throw new IndexOutOfRangeException(nameof(row)); var result = new T[_cols]; for (int i = 0; i < _cols; i++) { - result[i] = _values[dimension, i]; + result[i] = Values[row.Value, i]; } return result; } } + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _rows; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public bool Equals(VectorMulti? other) { if (other is null) @@ -262,15 +97,15 @@ public bool Equals(VectorMulti? other) if (ReferenceEquals(this, other)) return true; - // Compare Name, dimensions, and all values - if (Name != other.Name || _rows != other._rows || _cols != other._cols) + // Compare dimensions and all values + if (_rows != other._rows || _cols != other._cols) return false; for (int i = 0; i < _rows; i++) { for (int j = 0; j < _cols; j++) { - if (!EqualityComparer.Default.Equals(_values[i, j], other._values[i, j])) + if (!EqualityComparer.Default.Equals(Values[i, j], other.Values[i, j])) return false; } } @@ -281,188 +116,367 @@ public bool Equals(VectorMulti? other) public override int GetHashCode() { var hash = new HashCode(); - hash.Add(Name); hash.Add(_rows); hash.Add(_cols); for (int i = 0; i < _rows; i++) { for (int j = 0; j < _cols; j++) { - hash.Add(_values[i, j]); + hash.Add(Values[i, j]); } } return hash.ToHashCode(); } } -public class Vectors : Dictionary, IHybridVectorInput, INearVectorInput +/// +/// Represents vector data. +/// This is the user-facing type for vector inputs. +/// +[CollectionBuilder(typeof(VectorBuilder), nameof(VectorBuilder.Create))] +public class Vector : IEnumerable // Not sealed - allows NamedVector inheritance { - public void Add(T[] value) - where T : struct - { - Add("default", value); - } + private readonly IVectorData _data; - public void Add(T[,] value) - where T : struct + // Internal constructor for VectorSearchInputBuilder and derived classes + internal Vector(IVectorData data) { - Add("default", value); + _data = data; } - public void Add(string name, params T[] values) - where T : struct - { - Add(new VectorSingle(values) { Name = name }); - } + /// + /// Internal accessor for derived classes to extract data from other Vector instances. + /// + internal IVectorData GetData() => _data; - public void Add(string name, T[,] values) - where T : struct - { - Add(new VectorMulti(values) { Name = name }); - } + public (int rows, int cols) Dimensions => _data.Dimensions; + public int Count => _data.Count; + public Type ValueType => _data.ValueType; + public bool IsMultiVector => _data.IsMultiVector; - public void Add(Vector vector) - { - base.Add(vector.Name, vector); - } + // Implicit conversions for ergonomic syntax + public static implicit operator Vector(float[] values) => new(new VectorSingle(values)); - // Create vector data for simple struct values - public static Vectors Create(params T[] values) - where T : struct - { - return new Vectors { new VectorSingle(values) }; - } + public static implicit operator Vector(double[] values) => + new(new VectorSingle(values)); + + public static implicit operator Vector(int[] values) => new(new VectorSingle(values)); + + public static implicit operator Vector(long[] values) => new(new VectorSingle(values)); + + public static implicit operator Vector(short[] values) => new(new VectorSingle(values)); + + public static implicit operator Vector(byte[] values) => new(new VectorSingle(values)); + + public static implicit operator Vector(bool[] values) => new(new VectorSingle(values)); + + public static implicit operator Vector(decimal[] values) => + new(new VectorSingle(values)); + + public static implicit operator Vector(float[,] values) => new(new VectorMulti(values)); + + public static implicit operator Vector(double[,] values) => + new(new VectorMulti(values)); + + public static implicit operator Vector(int[,] values) => new(new VectorMulti(values)); + + public static implicit operator Vector(long[,] values) => new(new VectorMulti(values)); - public static Vectors Create(T[,] values) - where T : struct + public static implicit operator Vector(short[,] values) => new(new VectorMulti(values)); + + public static implicit operator Vector(byte[,] values) => new(new VectorMulti(values)); + + public static implicit operator Vector(bool[,] values) => new(new VectorMulti(values)); + + public static implicit operator Vector(decimal[,] values) => + new(new VectorMulti(values)); + + /// + /// Pattern matching helper for type-safe access to underlying vector data. + /// Used by gRPC layer to convert vectors to protobuf format. + /// + internal TResult Match(Func handler) { - return new Vectors { new VectorMulti(values) }; + return handler(_data); } - public static Vectors Create(Vector vector) => new Vectors { vector }; + public IEnumerator GetEnumerator() => + _data switch + { + VectorSingle v => v.Values.GetEnumerator(), + VectorSingle v => v.Values.GetEnumerator(), + VectorSingle v => v.Values.GetEnumerator(), + VectorSingle v => v.Values.GetEnumerator(), + VectorSingle v => v.Values.GetEnumerator(), + VectorSingle v => v.Values.GetEnumerator(), + VectorSingle v => v.Values.GetEnumerator(), + VectorSingle v => v.Values.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + VectorMulti v => v.GetEnumerator(), + _ => throw new NotSupportedException(), + }; - public static Vectors Create(string name, params T[] values) - where T : struct + #region Implicit Operators to Native Arrays + // SingleVector implicit operators (inverted) + public static implicit operator double[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + public static implicit operator float[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + public static implicit operator int[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + public static implicit operator long[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + public static implicit operator short[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + public static implicit operator byte[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + public static implicit operator bool[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + public static implicit operator decimal[](Vector vector) => + vector.GetData() is VectorSingle v ? v.Values : throw new InvalidCastException(); + + // MultiVector implicit operators (inverted) + public static implicit operator double[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + + public static implicit operator float[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + + public static implicit operator int[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + + public static implicit operator long[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + + public static implicit operator short[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + + public static implicit operator byte[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + + public static implicit operator bool[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + + public static implicit operator decimal[,](Vector vector) => + vector.GetData() is VectorMulti v ? v.Values : throw new InvalidCastException(); + #endregion +} + +/// +/// Builder for collection expression support on Vector type. +/// Defaults to int[] for integer literals (10, 20, 30). +/// Use float literals (10f, 20f, 30f) or explicit casts for other types. +/// +internal static class VectorBuilder +{ + public static Vector Create(ReadOnlySpan values) { - return new Vectors { new VectorSingle(values) { Name = name } }; + if (values.Length == 0) + throw new ArgumentException( + "Cannot create a Vector from an empty collection.", + nameof(values) + ); + + var first = values[0]; + return first switch + { + double _ => values.ToArray().Cast().ToArray(), + float _ => values.ToArray().Cast().ToArray(), + int _ => values.ToArray().Cast().ToArray(), + long _ => values.ToArray().Cast().ToArray(), + short _ => values.ToArray().Cast().ToArray(), + byte _ => values.ToArray().Cast().ToArray(), + bool _ => values.ToArray().Cast().ToArray(), + decimal _ => values.ToArray().Cast().ToArray(), + _ => throw new NotSupportedException( + $"Type {first.GetType()} is not supported for Vector creation." + ), + }; } - public static Vectors Create(string name, T[,] values) - where T : struct + public static Vector Create(ReadOnlySpan values) => values.ToArray(); + + public static Vector Create(ReadOnlySpan values) => values.ToArray(); + + public static Vector Create(ReadOnlySpan values) => values.ToArray(); + + public static Vector Create(ReadOnlySpan values) => values.ToArray(); + + public static Vector Create(ReadOnlySpan values) => values.ToArray(); + + public static Vector Create(ReadOnlySpan values) => values.ToArray(); + + public static Vector Create(ReadOnlySpan values) => values.ToArray(); + + public static Vector Create(ReadOnlySpan values) => values.ToArray(); +} + +/// +/// Represents a named vector - inherits from Vector and adds a Name property. +/// Used internally after builder processing. +/// +public sealed class NamedVector : Vector +{ + public string Name { get; init; } = "default"; + + internal NamedVector(string name, IVectorData data) + : base(data) { - return new Vectors { new VectorMulti(values) { Name = name } }; + Name = name; } - public static implicit operator Vectors(Vector vector) + /// + /// Creates a NamedVector from a Vector with a specified name. + /// Uses protected GetData() to extract internal data. + /// + public NamedVector(string name, Vector data) + : this(name, data.GetData()) { } + + /// + /// Creates a NamedVector from a Vector with a specified name. + /// Uses protected GetData() to extract internal data. + /// + public NamedVector(Vector data) + : this(data is NamedVector named ? named.Name : "default", data.GetData()) { } + + public static implicit operator NamedVector((string name, Vector vector) namedVector) => + new(namedVector.name, namedVector.vector); +} + +/// +/// Collection of named vectors. +/// +public class Vectors : Internal.KeySortedList +{ + public Vectors() + : base(v => (v as NamedVector)?.Name ?? "default") { } + + public Vectors(IEnumerable vector) + : this() { - return Vectors.Create(vector); + foreach (var v in vector) + { + Add(v); + } } + public Vectors(params Vector[] vector) + : this(vector.AsEnumerable()) { } + + public Vectors(string name, Vector vector) + : this((name, vector)) { } + + public Vectors(params (string name, Vector vector)[] vectors) + : this(vectors.Select(v => new NamedVector(v.name, v.vector))) { } + + // Implicit conversions + public static implicit operator Vectors(NamedVector vector) => new(vector); + + public static implicit operator Vectors(NamedVector[] vector) => new(vector); + + public static implicit operator Vectors(Vector vector) => new(vector); + + public static implicit operator Vectors((string name, Vector vector) v) => + new(v.name, v.vector); + + public static implicit operator Vectors((string name, Vector vector)[] v) => new(v); + #region Implicit Operators: Vectors from Native Arrays - public static implicit operator Vectors(double[] values) => Create(values); + public static implicit operator Vectors(float[] values) => new(values); - public static implicit operator Vectors(float[] values) => Create(values); + public static implicit operator Vectors(double[] values) => new(values); - public static implicit operator Vectors(int[] values) => Create(values); + public static implicit operator Vectors(int[] values) => new(values); - public static implicit operator Vectors(long[] values) => Create(values); + public static implicit operator Vectors(long[] values) => new(values); - public static implicit operator Vectors(short[] values) => Create(values); + public static implicit operator Vectors(short[] values) => new(values); - public static implicit operator Vectors(byte[] values) => Create(values); + public static implicit operator Vectors(byte[] values) => new(values); - public static implicit operator Vectors(bool[] values) => Create(values); + public static implicit operator Vectors(bool[] values) => new(values); - public static implicit operator Vectors(decimal[] values) => Create(values); + public static implicit operator Vectors(decimal[] values) => new(values); - public static implicit operator Vectors(double[,] values) => Create(values); + public static implicit operator Vectors(float[,] values) => new(values); - public static implicit operator Vectors(float[,] values) => Create(values); + public static implicit operator Vectors(double[,] values) => new(values); - public static implicit operator Vectors(int[,] values) => Create(values); + public static implicit operator Vectors(int[,] values) => new(values); - public static implicit operator Vectors(long[,] values) => Create(values); + public static implicit operator Vectors(long[,] values) => new(values); - public static implicit operator Vectors(short[,] values) => Create(values); + public static implicit operator Vectors(short[,] values) => new(values); - public static implicit operator Vectors(byte[,] values) => Create(values); + public static implicit operator Vectors(byte[,] values) => new(values); - public static implicit operator Vectors(bool[,] values) => Create(values); + public static implicit operator Vectors(bool[,] values) => new(values); - public static implicit operator Vectors(decimal[,] values) => Create(values); + public static implicit operator Vectors(decimal[,] values) => new(values); #endregion #region Implicit Operators: Vectors from Dictionary - public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); - public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); + + public static implicit operator Vectors(Dictionary vectors) => + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromDictionary(vectors); - - private static Vectors CreateVectorsFromDictionary(Dictionary vectors) - where T : struct - { - var container = new Vectors(); - foreach (var kvp in vectors) - { - container.Add(new VectorSingle(kvp.Value) { Name = kvp.Key }); - } - return container; - } + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); #endregion #region Implicit Operators: Vectors from Dictionary - public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); - public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); + + public static implicit operator Vectors(Dictionary vectors) => + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); public static implicit operator Vectors(Dictionary vectors) => - CreateVectorsFromMultiDictionary(vectors); - - private static Vectors CreateVectorsFromMultiDictionary(Dictionary vectors) - where T : struct - { - var container = new Vectors(); - foreach (var kvp in vectors) - { - container.Add(new VectorMulti(kvp.Value) { Name = kvp.Key }); - } - return container; - } + new(vectors.Select(kvp => (kvp.Key, (Vector)kvp.Value)).ToArray()); #endregion } diff --git a/src/Weaviate.Client/Models/VectorInputBuilders.cs b/src/Weaviate.Client/Models/VectorInputBuilders.cs new file mode 100644 index 00000000..ed1003be --- /dev/null +++ b/src/Weaviate.Client/Models/VectorInputBuilders.cs @@ -0,0 +1,600 @@ +namespace Weaviate.Client.Models; + +// ============================================================================ +// NearVector Builder Infrastructure +// ============================================================================ + +/// +/// Delegate for creating a NearVector builder. The builder is directly callable +/// to set certainty and distance parameters, then chainable to configure target vectors. +/// +/// +/// v => v(certainty: 0.8).TargetVectorsManualWeights( +/// ("title", 1.2, new[] { 1f, 2f }), +/// ("description", 0.8, new[] { 3f, 4f }) +/// ) +/// +public delegate INearVectorBuilder NearVectorInputBuilder( + float? certainty = null, + float? distance = null +); + +/// +/// Builder interface for creating NearVectorInput with integrated target vectors. +/// +public interface INearVectorBuilder +{ + /// + /// Creates a NearVectorInput with manually weighted target vectors. + /// + /// Tuples of (targetName, weight, vector) + NearVectorInput TargetVectorsManualWeights( + params (string Name, double Weight, Vector Vector)[] targets + ); + + /// + /// Creates a NearVectorInput that sums all target vectors. + /// + /// Tuples of (targetName, vector) + NearVectorInput TargetVectorsSum(params (string Name, Vector Vector)[] targets); + + /// + /// Creates a NearVectorInput that averages all target vectors. + /// + /// Tuples of (targetName, vector) + NearVectorInput TargetVectorsAverage(params (string Name, Vector Vector)[] targets); + + /// + /// Creates a NearVectorInput using minimum combination of target vectors. + /// + /// Tuples of (targetName, vector) + NearVectorInput TargetVectorsMinimum(params (string Name, Vector Vector)[] targets); + + /// + /// Creates a NearVectorInput using relative score combination of target vectors. + /// + /// Tuples of (targetName, weight, vector) + NearVectorInput TargetVectorsRelativeScore( + params (string Name, double Weight, Vector Vector)[] targets + ); +} + +/// +/// Internal implementation of INearVectorBuilder. +/// +internal sealed class NearVectorBuilder : INearVectorBuilder +{ + private readonly float? _certainty; + private readonly float? _distance; + + public NearVectorBuilder(float? certainty = null, float? distance = null) + { + _certainty = certainty; + _distance = distance; + } + + public NearVectorInput TargetVectorsManualWeights( + params (string Name, double Weight, Vector Vector)[] targetVectors + ) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsManualWeights( + targetVectors.Select(t => (t.Name, t.Weight, t.Vector)).ToArray() + ); + return new NearVectorInput(vectorSearchInput, _certainty, _distance); + } + + public NearVectorInput TargetVectorsSum(params (string Name, Vector Vector)[] targetVectors) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsSum( + targetVectors.Select(t => (t.Name, t.Vector)).ToArray() + ); + return new NearVectorInput(vectorSearchInput, _certainty, _distance); + } + + public NearVectorInput TargetVectorsAverage(params (string Name, Vector Vector)[] targetVectors) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsAverage( + targetVectors.Select(t => (t.Name, t.Vector)).ToArray() + ); + return new NearVectorInput(vectorSearchInput, _certainty, _distance); + } + + public NearVectorInput TargetVectorsMinimum(params (string Name, Vector Vector)[] targetVectors) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsMinimum( + targetVectors.Select(t => (t.Name, t.Vector)).ToArray() + ); + return new NearVectorInput(vectorSearchInput, _certainty, _distance); + } + + public NearVectorInput TargetVectorsRelativeScore( + params (string Name, double Weight, Vector Vector)[] targetVectors + ) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsRelativeScore( + targetVectors.Select(t => (t.Name, t.Weight, t.Vector)).ToArray() + ); + return new NearVectorInput(vectorSearchInput, _certainty, _distance); + } +} + +// ============================================================================ +// NearText Builder Infrastructure +// ============================================================================ + +/// +/// Delegate for creating a NearText builder. The builder is directly callable +/// to set query text and optional parameters, then chainable to configure target vectors. +/// +/// +/// v => v(["concept1", "concept2"]).ManualWeights( +/// ("title", 1.2), +/// ("description", 0.8) +/// ) +/// +public delegate INearTextBuilder NearTextInputBuilder( + AutoArray query, + float? certainty = null, + float? distance = null, + Move? moveTo = null, + Move? moveAway = null +); + +/// +/// Builder interface for creating NearTextInput with integrated target vectors. +/// +public interface INearTextBuilder +{ + /// + /// Creates a NearTextInput with manually weighted target vectors. + /// + /// Tuples of (targetName, weight) + NearTextInput TargetVectorsManualWeights(params (string Name, double Weight)[] targetVectors); + + /// + /// Creates a NearTextInput that sums all target vectors. + /// + /// Names of target vectors + NearTextInput TargetVectorsSum(params string[] targetVectors); + + /// + /// Creates a NearTextInput that averages all target vectors. + /// + /// Names of target vectors + NearTextInput TargetVectorsAverage(params string[] targetVectors); + + /// + /// Creates a NearTextInput using minimum combination of target vectors. + /// + /// Names of target vectors + NearTextInput TargetVectorsMinimum(params string[] targetVectors); + + /// + /// Creates a NearTextInput using relative score combination of target vectors. + /// + /// Tuples of (targetName, weight) + NearTextInput TargetVectorsRelativeScore(params (string Name, double Weight)[] targetVectors); +} + +/// +/// Internal implementation of INearTextBuilder. +/// +internal sealed class NearTextBuilder : INearTextBuilder +{ + private readonly string[] _query; + private readonly float? _certainty; + private readonly float? _distance; + private readonly Move? _moveTo; + private readonly Move? _moveAway; + + public NearTextBuilder( + AutoArray query, + float? certainty, + float? distance, + Move? moveTo, + Move? moveAway + ) + { + // Convert AutoArray to string[] for storage (AutoArray can't be stored as field) + _query = query.ToArray(); + _certainty = certainty; + _distance = distance; + _moveTo = moveTo; + _moveAway = moveAway; + } + + public NearTextInput TargetVectorsManualWeights( + params (string Name, double Weight)[] targetVectors + ) + { + var targetVectorsObj = TargetVectors.ManualWeights(targetVectors); + return new NearTextInput( + _query, + targetVectorsObj, + _certainty, + _distance, + _moveTo, + _moveAway + ); + } + + public NearTextInput TargetVectorsSum(params string[] targetVectors) + { + var targetVectorsObj = TargetVectors.Sum(targetVectors); + return new NearTextInput( + _query, + targetVectorsObj, + _certainty, + _distance, + _moveTo, + _moveAway + ); + } + + public NearTextInput TargetVectorsAverage(params string[] targetVectors) + { + var targetVectorsObj = TargetVectors.Average(targetVectors); + return new NearTextInput( + _query, + targetVectorsObj, + _certainty, + _distance, + _moveTo, + _moveAway + ); + } + + public NearTextInput TargetVectorsMinimum(params string[] targetVectors) + { + var targetVectorsObj = TargetVectors.Minimum(targetVectors); + return new NearTextInput( + _query, + targetVectorsObj, + _certainty, + _distance, + _moveTo, + _moveAway + ); + } + + public NearTextInput TargetVectorsRelativeScore( + params (string Name, double Weight)[] targetVectors + ) + { + var targetVectorsObj = TargetVectors.RelativeScore(targetVectors); + return new NearTextInput( + _query, + targetVectorsObj, + _certainty, + _distance, + _moveTo, + _moveAway + ); + } +} + +// ============================================================================ +// Hybrid Builder Infrastructure +// ============================================================================ + +/// +/// Builder class for creating HybridVectorInput. Provides methods to configure +/// either NearVector or NearText searches with target vectors. +/// +/// +/// v => v.NearVector().ManualWeights( +/// ("title", 1.2, new[] { 1f, 2f }), +/// ("description", 0.8, new[] { 3f, 4f }) +/// ) +/// +public sealed class HybridVectorInputBuilder +{ + /// + /// Configures hybrid search with NearVector and optional search parameters. + /// + public HybridNearVectorBuilder NearVector() + { + return new HybridNearVectorBuilder(); + } + + /// + /// Configures hybrid search with NearText and optional search parameters. + /// + public HybridNearTextBuilder NearText( + AutoArray query, + Move? moveTo = null, + Move? moveAway = null + ) + { + return new HybridNearTextBuilder(query, moveTo, moveAway); + } + + /// + /// Creates a HybridVectorInput directly from named vectors without a target vector combination method. + /// The combination method will be Unspecified (server default behavior). + /// Use this when providing vectors that don't require multi-target combination. + /// + /// + /// v => v.Vectors( + /// ("title", new[] { 1f, 2f }), + /// ("description", new[] { 3f, 4f }) + /// ) + /// + public HybridVectorInput Vectors(params (string Name, Vector Vector)[] namedVectors) + { + // Extract unique target names from the input + var targetNames = namedVectors.Select(nv => nv.Name).Distinct().ToArray(); + + // Create TargetVectors using implicit conversion (no combination method, Unspecified) + TargetVectors targets = targetNames; + + // Combine the targets with the vectors + var vectorSearch = VectorSearchInput.Combine(targets, namedVectors); + return HybridVectorInput.FromVectorSearch(vectorSearch); + } +} + +/// +/// Builder interface for creating HybridVectorInput with NearVector configuration. +/// +public interface IHybridNearVectorBuilder +{ + /// + /// Creates a HybridVectorInput with manually weighted target vectors for NearVector search. + /// + HybridVectorInput TargetVectorsManualWeights( + params (string Name, double Weight, Vector Vector)[] targets + ); + + /// + /// Creates a HybridVectorInput that sums all target vectors for NearVector search. + /// + HybridVectorInput TargetVectorsSum(params (string Name, Vector Vector)[] targets); + + /// + /// Creates a HybridVectorInput that averages all target vectors for NearVector search. + /// + HybridVectorInput TargetVectorsAverage(params (string Name, Vector Vector)[] targets); + + /// + /// Creates a HybridVectorInput using minimum combination for NearVector search. + /// + HybridVectorInput TargetVectorsMinimum(params (string Name, Vector Vector)[] targets); + + /// + /// Creates a HybridVectorInput using relative score combination for NearVector search. + /// + HybridVectorInput TargetVectorsRelativeScore( + params (string Name, double Weight, Vector Vector)[] targets + ); +} + +/// +/// Internal implementation of IHybridNearVectorBuilder. +/// +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +public sealed class HybridNearVectorBuilder : IHybridNearVectorBuilder +{ + public HybridNearVectorBuilder() { } + + public HybridVectorInput TargetVectorsManualWeights( + params (string Name, double Weight, Vector Vector)[] targets + ) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsManualWeights( + targets.Select(t => (t.Name, t.Weight, t.Vector)).ToArray() + ); + var nearVectorInput = new NearVectorInput(vectorSearchInput); + return HybridVectorInput.FromNearVector(nearVectorInput); + } + + public HybridVectorInput TargetVectorsSum(params (string Name, Vector Vector)[] targets) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsSum( + targets.Select(t => (t.Name, t.Vector)).ToArray() + ); + var nearVectorInput = new NearVectorInput(vectorSearchInput); + return HybridVectorInput.FromNearVector(nearVectorInput); + } + + public HybridVectorInput TargetVectorsAverage(params (string Name, Vector Vector)[] targets) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsAverage( + targets.Select(t => (t.Name, t.Vector)).ToArray() + ); + var nearVectorInput = new NearVectorInput(vectorSearchInput); + return HybridVectorInput.FromNearVector(nearVectorInput); + } + + public HybridVectorInput TargetVectorsMinimum(params (string Name, Vector Vector)[] targets) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsMinimum( + targets.Select(t => (t.Name, t.Vector)).ToArray() + ); + var nearVectorInput = new NearVectorInput(vectorSearchInput); + return HybridVectorInput.FromNearVector(nearVectorInput); + } + + public HybridVectorInput TargetVectorsRelativeScore( + params (string Name, double Weight, Vector Vector)[] targets + ) + { + var builder = new VectorSearchInput.Builder(); + var vectorSearchInput = builder.TargetVectorsRelativeScore( + targets.Select(t => (t.Name, t.Weight, t.Vector)).ToArray() + ); + var nearVectorInput = new NearVectorInput(vectorSearchInput); + return HybridVectorInput.FromNearVector(nearVectorInput); + } +} + +/// +/// Builder interface for creating HybridVectorInput with NearText configuration. +/// +public interface IHybridNearTextBuilder +{ + /// + /// Creates a HybridVectorInput with manually weighted target vectors for NearText search. + /// + HybridVectorInput TargetVectorsManualWeights(params (string Name, double Weight)[] targets); + + /// + /// Creates a HybridVectorInput that sums all target vectors for NearText search. + /// + HybridVectorInput TargetVectorsSum(params string[] targetNames); + + /// + /// Creates a HybridVectorInput that averages all target vectors for NearText search. + /// + HybridVectorInput TargetVectorsAverage(params string[] targetNames); + + /// + /// Creates a HybridVectorInput using minimum combination for NearText search. + /// + HybridVectorInput TargetVectorsMinimum(params string[] targetNames); + + /// + /// Creates a HybridVectorInput using relative score combination for NearText search. + /// + HybridVectorInput TargetVectorsRelativeScore(params (string Name, double Weight)[] targets); +} + +/// +/// Internal implementation of IHybridNearTextBuilder. +/// +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +public sealed class HybridNearTextBuilder : IHybridNearTextBuilder +{ + private readonly string[] _query; + private readonly Move? _moveTo; + private readonly Move? _moveAway; + + public HybridNearTextBuilder(AutoArray query, Move? moveTo, Move? moveAway) + { + // Convert AutoArray to string[] for storage (AutoArray can't be stored as field) + _query = query.ToArray(); + _moveTo = moveTo; + _moveAway = moveAway; + } + + public static implicit operator HybridVectorInput(HybridNearTextBuilder builder) + { + var nearTextInput = new NearTextInput( + builder._query, + (Weaviate.Client.Models.TargetVectors?)null, + null, + null, + builder._moveTo, + builder._moveAway + ); + return HybridVectorInput.FromNearText(nearTextInput); + } + + public HybridVectorInput TargetVectorsManualWeights( + params (string Name, double Weight)[] targets + ) + { + var targetVectors = TargetVectors.ManualWeights(targets); + var nearTextInput = new NearTextInput( + _query, + targetVectors, + null, + null, + _moveTo, + _moveAway + ); + return HybridVectorInput.FromNearText(nearTextInput); + } + + public HybridVectorInput TargetVectorsSum(params string[] targetNames) + { + var targetVectors = TargetVectors.Sum(targetNames); + var nearTextInput = new NearTextInput( + _query, + targetVectors, + null, + null, + _moveTo, + _moveAway + ); + return HybridVectorInput.FromNearText(nearTextInput); + } + + public HybridVectorInput TargetVectorsAverage(params string[] targetNames) + { + var targetVectors = TargetVectors.Average(targetNames); + var nearTextInput = new NearTextInput( + _query, + targetVectors, + null, + null, + _moveTo, + _moveAway + ); + return HybridVectorInput.FromNearText(nearTextInput); + } + + public HybridVectorInput TargetVectorsMinimum(params string[] targetNames) + { + var targetVectors = TargetVectors.Minimum(targetNames); + var nearTextInput = new NearTextInput( + _query, + targetVectors, + null, + null, + _moveTo, + _moveAway + ); + return HybridVectorInput.FromNearText(nearTextInput); + } + + public HybridVectorInput TargetVectorsRelativeScore( + params (string Name, double Weight)[] targets + ) + { + var targetVectors = TargetVectors.RelativeScore(targets); + var nearTextInput = new NearTextInput( + _query, + targetVectors, + null, + null, + _moveTo, + _moveAway + ); + return HybridVectorInput.FromNearText(nearTextInput); + } +} + +// ============================================================================ +// Factory Function Delegates +// ============================================================================ + +internal static class VectorInputBuilderFactories +{ + /// + /// Creates a NearVectorInputBuilder delegate for use in lambda expressions. + /// + public static NearVectorInputBuilder CreateNearVectorBuilder() => + (certainty, distance) => new NearVectorBuilder(certainty, distance); + + /// + /// Creates a NearTextInputBuilder delegate for use in lambda expressions. + /// + public static NearTextInputBuilder CreateNearTextBuilder() => + (query, certainty, distance, moveTo, moveAway) => + new NearTextBuilder(query, certainty, distance, moveTo, moveAway); + + /// + /// Creates a HybridVectorInputBuilder for use in lambda expressions. + /// + public static HybridVectorInputBuilder CreateHybridBuilder() => new(); +} diff --git a/src/Weaviate.Client/Models/VectorSearchInput.cs b/src/Weaviate.Client/Models/VectorSearchInput.cs new file mode 100644 index 00000000..27ecb4b1 --- /dev/null +++ b/src/Weaviate.Client/Models/VectorSearchInput.cs @@ -0,0 +1,370 @@ +namespace Weaviate.Client.Models; + +using V1 = Grpc.Protobuf.V1; + +/// +/// Represents a vector search input with optional target vectors and combination strategy. +/// Can be constructed using collection expression syntax: [namedVector1, namedVector2, ...] +/// +public sealed class VectorSearchInput : IEnumerable +{ + private readonly List _vectors; + + /// + /// Initializes a new instance of VectorSearchInput. Supports collection initializer syntax. + /// + public VectorSearchInput() + { + _vectors = []; + } + + internal VectorSearchInput( + IEnumerable vectors, + IReadOnlyList? targets = null, + V1.CombinationMethod combination = V1.CombinationMethod.Unspecified, + IReadOnlyDictionary>? weights = null + ) + { + _vectors = [.. vectors]; + Targets = targets; + Combination = combination; + Weights = weights; + } + + public IReadOnlyDictionary Vectors => + _vectors.GroupBy(v => v.Name).ToDictionary(g => g.Key, g => g.ToArray()); + + public IReadOnlyList? Targets { get; } + internal V1.CombinationMethod Combination { get; } + public IReadOnlyDictionary>? Weights { get; } + + public IEnumerator GetEnumerator() => _vectors.GetEnumerator(); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => + GetEnumerator(); + + /// + /// Adds named vectors to the collection. Supports collection initializer syntax. + /// + public void Add(string name, params Vector[] values) => + _vectors.AddRange(values.Select(v => new NamedVector(name, v))); + + /// + /// Adds a Vectors collection. Supports collection initializer syntax. + /// + public void Add(Vectors vectors) + { + _vectors.AddRange(vectors.Select(v => new NamedVector(v.Key, v.Value))); + } + + internal IEnumerable<(string name, double? weight)> GetVectorWithWeights() + { + var targets = Targets ?? Vectors.Keys.ToList(); + foreach (var target in targets) + { + if (Weights?.TryGetValue(target, out var weightList) ?? false) + { + foreach (var weight in weightList) + yield return (target, weight); + } + else + { + yield return (target, null); + } + } + } + + // Implicit conversions to reduce API overload count + + /// + /// Implicit conversion from float array to VectorSearchInput (creates single unnamed "default" vector) + /// + public static implicit operator VectorSearchInput(float[] values) => + new([new NamedVector(values)]); + + /// + /// Implicit conversion from double array to VectorSearchInput (creates single unnamed "default" vector) + /// + public static implicit operator VectorSearchInput(double[] values) => + new([new NamedVector(values)]); + + /// + /// Implicit conversion from Vector to VectorSearchInput (creates single unnamed "default" vector) + /// + public static implicit operator VectorSearchInput(Vector vector) => + new([new NamedVector(vector)]); + + /// + /// Implicit conversion from Vectors to VectorSearchInput (creates multiple named vectors) + /// + public static implicit operator VectorSearchInput(Vectors vectors) + { + return new VectorSearchInput() { vectors }; + } + + /// + /// Implicit conversion from NamedVector to VectorSearchInput (creates single named vector) + /// + public static implicit operator VectorSearchInput(NamedVector vector) => new([vector]); + + /// + /// Implicit conversion from NamedVector array to VectorSearchInput (creates multiple named vectors) + /// + public static implicit operator VectorSearchInput(NamedVector[] vectors) => new(vectors); + + /// + /// Implicit conversion from Dictionary of Vector arrays to VectorSearchInput + /// + public static implicit operator VectorSearchInput(Dictionary vectors) => + new(vectors: vectors.SelectMany(kvp => kvp.Value.Select(v => new NamedVector(kvp.Key, v)))); + + /// + /// Implicit conversion from Dictionary of float arrays to VectorSearchInput + /// + public static implicit operator VectorSearchInput(Dictionary vectors) => + new(vectors: vectors.Select(kvp => new NamedVector(kvp.Key, kvp.Value))); + + /// + /// Implicit conversion from Dictionary of double arrays to VectorSearchInput + /// + public static implicit operator VectorSearchInput(Dictionary vectors) => + new(vectors: vectors.Select(kvp => new NamedVector(kvp.Key, kvp.Value))); + + /// + /// Implicit conversion from Dictionary of 2D float arrays to VectorSearchInput + /// + public static implicit operator VectorSearchInput(Dictionary vectors) => + new(vectors: vectors.Select(kvp => new NamedVector(kvp.Key, kvp.Value))); + + /// + /// Implicit conversion from Dictionary of 2D double arrays to VectorSearchInput + /// + public static implicit operator VectorSearchInput(Dictionary vectors) => + new(vectors: vectors.Select(kvp => new NamedVector(kvp.Key, kvp.Value))); + + /// + /// Implicit conversion from Dictionary of float array enumerables to VectorSearchInput + /// + public static implicit operator VectorSearchInput( + Dictionary> vectors + ) => + new(vectors: vectors.SelectMany(kvp => kvp.Value.Select(v => new NamedVector(kvp.Key, v)))); + + /// + /// Implicit conversion from Dictionary of double array enumerables to VectorSearchInput + /// + public static implicit operator VectorSearchInput( + Dictionary> vectors + ) => + new(vectors: vectors.SelectMany(kvp => kvp.Value.Select(v => new NamedVector(kvp.Key, v)))); + + /// + /// Implicit conversion from Dictionary of 2D float array enumerables to VectorSearchInput + /// + public static implicit operator VectorSearchInput( + Dictionary> vectors + ) => + new(vectors: vectors.SelectMany(kvp => kvp.Value.Select(v => new NamedVector(kvp.Key, v)))); + + /// + /// Implicit conversion from Dictionary of 2D double array enumerables to VectorSearchInput + /// + public static implicit operator VectorSearchInput( + Dictionary> vectors + ) => + new(vectors: vectors.SelectMany(kvp => kvp.Value.Select(v => new NamedVector(kvp.Key, v)))); + + /// + /// Implicit conversion from tuple of (name, float[]) to VectorSearchInput + /// + public static implicit operator VectorSearchInput((string name, float[] vector) tuple) => + new([new NamedVector(tuple.name, tuple.vector)]); + + /// + /// Implicit conversion from tuple of (name, double[]) to VectorSearchInput + /// + public static implicit operator VectorSearchInput((string name, double[] vector) tuple) => + new([new NamedVector(tuple.name, tuple.vector)]); + + /// + /// Implicit conversion from tuple of (name, float[,]) to VectorSearchInput (for multi-vector/ColBERT) + /// + public static implicit operator VectorSearchInput((string name, float[,] vectors) tuple) => + new([new NamedVector(tuple.name, tuple.vectors)]); + + /// + /// Implicit conversion from tuple of (name, double[,]) to VectorSearchInput (for multi-vector/ColBERT) + /// + public static implicit operator VectorSearchInput((string name, double[,] vectors) tuple) => + new([new NamedVector(tuple.name, tuple.vectors)]); + + /// + /// Implicit conversion from FactoryFn to VectorSearchInput + /// + public static implicit operator VectorSearchInput(FactoryFn factory) => factory(new Builder()); + + /// + /// Builder for creating VectorSearchInput with multi-target combinations via lambda syntax. + /// + public sealed class Builder + { + internal Builder() { } + + /// + /// Creates a multi-target query with Sum combination. + /// + public VectorSearchInput TargetVectorsSum(params (string name, Vector vector)[] vectors) + { + var vectorList = vectors.Select(v => new NamedVector(v.name, v.vector)).ToList(); + var targets = vectors.Select(v => v.name).Distinct().ToList(); + + return new VectorSearchInput( + vectors: vectorList, + targets: targets, + combination: V1.CombinationMethod.TypeSum + ); + } + + /// + /// Creates a multi-target query with Average combination. + /// + public VectorSearchInput TargetVectorsAverage(params (string name, Vector vector)[] vectors) + { + var vectorList = vectors.Select(v => new NamedVector(v.name, v.vector)).ToList(); + var targets = vectors.Select(v => v.name).Distinct().ToList(); + + return new VectorSearchInput( + vectors: vectorList, + targets: targets, + combination: V1.CombinationMethod.TypeAverage + ); + } + + /// + /// Creates a multi-target query with Minimum combination. + /// + public VectorSearchInput TargetVectorsMinimum(params (string name, Vector vector)[] vectors) + { + var vectorList = vectors.Select(v => new NamedVector(v.name, v.vector)).ToList(); + var targets = vectors.Select(v => v.name).Distinct().ToList(); + + return new VectorSearchInput( + vectors: vectorList, + targets: targets, + combination: V1.CombinationMethod.TypeMin + ); + } + + /// + /// Creates a multi-target query with ManualWeights combination. + /// Weight comes before the vector in each tuple. + /// + public VectorSearchInput TargetVectorsManualWeights( + params (string name, double weight, Vector vector)[] entries + ) + { + var vectorList = entries.Select(e => new NamedVector(e.name, e.vector)); + var targets = entries.Select(e => e.name).Distinct().ToList(); + var weights = entries + .GroupBy(e => e.name) + .ToDictionary( + g => g.Key, + g => (IReadOnlyList)g.Select(e => e.weight).ToList() + ); + + return new VectorSearchInput( + vectors: vectorList, + targets: targets, + combination: V1.CombinationMethod.TypeManual, + weights: weights + ); + } + + /// + /// Creates a multi-target query with RelativeScore combination. + /// Weight comes before the vector in each tuple. + /// + public VectorSearchInput TargetVectorsRelativeScore( + params (string name, double weight, Vector vector)[] entries + ) + { + var vectorList = entries.Select(e => new NamedVector(e.name, e.vector)).ToList(); + var targets = entries.Select(e => e.name).Distinct().ToList(); + var weights = entries + .GroupBy(e => e.name) + .ToDictionary( + g => g.Key, + g => (IReadOnlyList)g.Select(e => e.weight).ToList() + ); + + return new VectorSearchInput( + vectors: vectorList, + targets: targets, + combination: V1.CombinationMethod.TypeRelativeScore, + weights: weights + ); + } + } + + /// + /// Factory delegate for creating VectorSearchInput using a builder pattern. + /// Example: FactoryFn factory = b => b.Sum(("title", vec1), ("desc", vec2)) + /// + public delegate VectorSearchInput FactoryFn(Builder builder); + + public static class CollectionBuilder + { + public static VectorSearchInput Create(ReadOnlySpan items) + { + return new VectorSearchInput(items.ToArray()); + } + } + + /// + /// Combines a TargetVectors configuration with named vectors to create a VectorSearchInput. + /// Supports multiple vectors per target (weights matched by order). + /// + public static VectorSearchInput Combine( + TargetVectors targetVectors, + params (string name, Vector vector)[] vectors + ) + { + var namedVectors = vectors.Select(v => new NamedVector(v.name, v.vector)); + return Combine(targetVectors, namedVectors); + } + + /// + /// Combines a TargetVectors configuration with a Vectors collection. + /// Note: Vectors only supports one vector per target name. + /// + public static VectorSearchInput Combine(TargetVectors targetVectors, Vectors vectors) + { + var namedVectors = vectors.Select(kvp => new NamedVector(kvp.Key, kvp.Value)); + return Combine(targetVectors, namedVectors); + } + + /// + /// Combines a TargetVectors configuration with an enumerable of NamedVectors. + /// Supports multiple vectors per target (weights matched by order). + /// + public static VectorSearchInput Combine( + TargetVectors targetVectors, + IEnumerable vectors + ) + { + var targets = targetVectors.Targets.ToList(); + + IReadOnlyDictionary>? weights = null; + if (targetVectors is WeightedTargetVectors weighted) + { + weights = weighted.Weights; + } + + return new VectorSearchInput( + vectors: vectors, + targets: targets, + combination: targetVectors.Combination, + weights: weights + ); + } +} diff --git a/src/Weaviate.Client/QueryClient.FetchObjects.cs b/src/Weaviate.Client/QueryClient.FetchObjects.cs index 7531e0d7..b4216f54 100644 --- a/src/Weaviate.Client/QueryClient.FetchObjects.cs +++ b/src/Weaviate.Client/QueryClient.FetchObjects.cs @@ -78,7 +78,7 @@ await _grpc.FetchObjects( var searchReply = await _grpc.FetchObjects( _collectionName, returnProperties: returnProperties, - filters: Filter.ID.IsEqual(id), + filters: Filter.UUID.IsEqual(id), tenant: _collectionClient.Tenant, returnReferences: returnReferences, returnMetadata: returnMetadata?.Disable(MetadataOptions.Certainty), @@ -109,8 +109,8 @@ await _grpc.FetchObjects( _collectionName, limit: limit, filters: filters != null - ? Filter.ID.IsEqual(ids.First()) & filters - : Filter.AnyOf([.. ids.Select(id => Filter.ID.IsEqual(id))]), + ? Filter.UUID.IsEqual(ids.First()) & filters + : Filter.AnyOf([.. ids.Select(id => Filter.UUID.IsEqual(id))]), sort: sort, tenant: _collectionClient.Tenant, rerank: rerank, diff --git a/src/Weaviate.Client/QueryClient.Hybrid.cs b/src/Weaviate.Client/QueryClient.Hybrid.cs index ab06e7b6..17695d99 100644 --- a/src/Weaviate.Client/QueryClient.Hybrid.cs +++ b/src/Weaviate.Client/QueryClient.Hybrid.cs @@ -4,9 +4,11 @@ namespace Weaviate.Client; public partial class QueryClient { - public async Task Hybrid( - string? query, - Vectors vectors, + /// + /// Performs a hybrid search (keyword + vector search). + /// + public Task Hybrid( + string query, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -17,20 +19,16 @@ public async Task Hybrid( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default ) => - await _grpc.SearchHybrid( - _collectionClient.Name, + Hybrid( query: query, + vectors: (HybridVectorInput?)null, alpha: alpha, - vector: vectors, - nearVector: null, - nearText: null, queryProperties: queryProperties, fusionType: fusionType, maxVectorDistance: maxVectorDistance, @@ -40,19 +38,19 @@ await _grpc.SearchHybrid( autoLimit: autoLimit, filters: filters, rerank: rerank, - targetVector: targetVector, - tenant: _collectionClient.Tenant, - consistencyLevel: _collectionClient.ConsistencyLevel, - returnMetadata: returnMetadata, - includeVectors: includeVectors, returnProperties: returnProperties, returnReferences: returnReferences, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken ); + /// + /// Performs a hybrid search (keyword + vector search). + /// public async Task Hybrid( string? query, - IHybridVectorInput? vectors = null, + HybridVectorInput? vectors, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -63,25 +61,25 @@ public async Task Hybrid( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) => - await _grpc.SearchHybrid( + ) + { + if (query is null && vectors is null) + { + throw new ArgumentException( + "At least one of 'query' or 'vectors' must be provided for hybrid search." + ); + } + + return await _grpc.SearchHybrid( _collectionClient.Name, query: query, alpha: alpha, - vector: vectors is Vector v ? Vectors.Create(v) : vectors as Vectors, - nearVector: vectors as HybridNearVector - ?? ( - vectors is NearVectorInput nv - ? new HybridNearVector(nv, null, null, targetVector) - : null - ), - nearText: vectors as HybridNearText, + vectors: vectors, queryProperties: queryProperties, fusionType: fusionType, maxVectorDistance: maxVectorDistance, @@ -91,7 +89,6 @@ vectors is NearVectorInput nv autoLimit: autoLimit, filters: filters, rerank: rerank, - targetVector: targetVector, tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, @@ -100,11 +97,14 @@ vectors is NearVectorInput nv returnReferences: returnReferences, cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); + } - public async Task Hybrid( - string? query, + /// + /// Performs a hybrid search (keyword + vector search) with grouping. + /// + public Task Hybrid( + string query, Models.GroupByRequest groupBy, - Vectors vectors, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -115,19 +115,16 @@ public async Task Hybrid( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default ) => - await _grpc.SearchHybrid( - _collectionClient.Name, + Hybrid( query: query, - vector: vectors, - nearVector: null, - nearText: null, + vectors: (HybridVectorInput?)null, + groupBy: groupBy, alpha: alpha, queryProperties: queryProperties, fusionType: fusionType, @@ -137,22 +134,21 @@ await _grpc.SearchHybrid( bm25Operator: bm25Operator, autoLimit: autoLimit, filters: filters, - groupBy: groupBy, rerank: rerank, - targetVector: targetVector, - tenant: _collectionClient.Tenant, - consistencyLevel: _collectionClient.ConsistencyLevel, - returnMetadata: returnMetadata, - includeVectors: includeVectors, returnProperties: returnProperties, returnReferences: returnReferences, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken ); + /// + /// Performs a hybrid search (keyword + vector search) with grouping. + /// public async Task Hybrid( string? query, + HybridVectorInput? vectors, Models.GroupByRequest groupBy, - IHybridVectorInput? vectors = null, float? alpha = null, string[]? queryProperties = null, HybridFusion? fusionType = null, @@ -163,19 +159,24 @@ public async Task Hybrid( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, VectorQuery? includeVectors = null, CancellationToken cancellationToken = default - ) => - await _grpc.SearchHybrid( + ) + { + if (query is null && vectors is null) + { + throw new ArgumentException( + "At least one of 'query' or 'vectors' must be provided for hybrid search." + ); + } + + return await _grpc.SearchHybrid( _collectionClient.Name, query: query, - vector: vectors is Vector v ? Vectors.Create(v) : vectors as Vectors, - nearVector: vectors as HybridNearVector, - nearText: vectors as HybridNearText, + vectors: vectors, alpha: alpha, queryProperties: queryProperties, fusionType: fusionType, @@ -187,7 +188,6 @@ await _grpc.SearchHybrid( filters: filters, groupBy: groupBy, rerank: rerank, - targetVector: targetVector, tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, @@ -196,4 +196,118 @@ await _grpc.SearchHybrid( returnReferences: returnReferences, cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); + } +} + +/// +/// Extension methods for QueryClient Hybrid search with lambda vector builders. +/// +public static class QueryClientHybridExtensions +{ + /// + /// Performs a hybrid search (keyword + vector search) using a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. + /// + /// + /// await collection.Query.Hybrid( + /// "test", + /// v => v.NearVector().ManualWeights( + /// ("title", 1.2, new[] { 1f, 2f }), + /// ("description", 0.8, new[] { 3f, 4f }) + /// ) + /// ); + /// + public static async Task Hybrid( + this QueryClient client, + string? query = null, + HybridVectorInput.FactoryFn? vectors = null, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var vectorsLocal = vectors?.Invoke(VectorInputBuilderFactories.CreateHybridBuilder()); + + return await client.Hybrid( + query: query, + vectors: vectorsLocal, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + } + + /// + /// Performs a hybrid search (keyword + vector search) with grouping using a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. + /// + public static async Task Hybrid( + this QueryClient client, + string? query, + HybridVectorInput.FactoryFn? vectors, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var vectorsLocal = vectors?.Invoke(VectorInputBuilderFactories.CreateHybridBuilder()); + + return await client.Hybrid( + query: query, + vectors: vectorsLocal, + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + } } diff --git a/src/Weaviate.Client/QueryClient.NearImage.cs b/src/Weaviate.Client/QueryClient.NearImage.cs deleted file mode 100644 index 133ed0a6..00000000 --- a/src/Weaviate.Client/QueryClient.NearImage.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Weaviate.Client.Models; - -namespace Weaviate.Client; - -public partial class QueryClient -{ - public async Task NearImage( - byte[] nearImage, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await NearMedia( - media: nearImage, - mediaType: NearMediaType.Image, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - returnProperties: returnProperties, - returnReferences: returnReferences, - cancellationToken: cancellationToken - ); - - return result; - } - - public async Task NearImage( - byte[] nearImage, - GroupByRequest groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) => - await NearMedia( - media: nearImage, - mediaType: NearMediaType.Image, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - groupBy: groupBy, - rerank: rerank, - targetVector: targetVector, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - returnProperties: returnProperties, - returnReferences: returnReferences, - cancellationToken: cancellationToken - ); -} diff --git a/src/Weaviate.Client/QueryClient.NearMedia.cs b/src/Weaviate.Client/QueryClient.NearMedia.cs index a2508c93..b3660aef 100644 --- a/src/Weaviate.Client/QueryClient.NearMedia.cs +++ b/src/Weaviate.Client/QueryClient.NearMedia.cs @@ -4,6 +4,159 @@ namespace Weaviate.Client; public partial class QueryClient { + /// + /// Performs a near-media search using media embeddings. + /// + /// + /// // Simple image search + /// await collection.Query.NearMedia(m => m.Image(imageBytes)); + /// + /// // With certainty and target vectors + /// await collection.Query.NearMedia(m => m.Image(imageBytes, certainty: 0.8f).Sum("v1", "v2")); + /// + /// // All media types supported + /// await collection.Query.NearMedia(m => m.Video(videoBytes)); + /// await collection.Query.NearMedia(m => m.Audio(audioBytes)); + /// await collection.Query.NearMedia(m => m.Thermal(thermalBytes)); + /// await collection.Query.NearMedia(m => m.Depth(depthBytes)); + /// await collection.Query.NearMedia(m => m.IMU(imuBytes)); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Search results. + public async Task NearMedia( + NearMediaInput.FactoryFn query, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var input = query(new NearMediaBuilder()); + return await _grpc.SearchNearMedia( + _collectionClient.Name, + media: input.Media, + mediaType: input.Type, + certainty: input.Certainty, + distance: input.Distance, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + groupBy: null, + rerank: rerank, + singlePrompt: null, + groupedTask: null, + tenant: _collectionClient.Tenant, + targetVector: input.TargetVectors, + consistencyLevel: _collectionClient.ConsistencyLevel, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + returnProperties: returnProperties, + returnReferences: returnReferences, + cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + ); + } + + /// + /// Performs a near-media search with group-by aggregation. + /// + /// + /// // Image search with grouping + /// await collection.Query.NearMedia( + /// m => m.Image(imageBytes).Sum("visual", "semantic"), + /// groupBy: new GroupByRequest("category", objectsPerGroup: 5) + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Grouped search results. + public async Task NearMedia( + NearMediaInput.FactoryFn query, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var input = query(new NearMediaBuilder()); + return await _grpc.SearchNearMedia( + _collectionClient.Name, + media: input.Media, + mediaType: input.Type, + certainty: input.Certainty, + distance: input.Distance, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + groupBy: groupBy, + rerank: rerank, + singlePrompt: null, + groupedTask: null, + tenant: _collectionClient.Tenant, + targetVector: input.TargetVectors, + consistencyLevel: _collectionClient.ConsistencyLevel, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + returnProperties: returnProperties, + returnReferences: returnReferences, + cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + ); + } + + /// + /// Performs a near-media search using raw media bytes and media type. + /// This is a legacy overload - prefer using the lambda builder overload with NearMediaInput.FactoryFn. + /// + /// The media content as a byte array. + /// The type of media (Image, Video, Audio, etc.). + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Filters to apply to the search. + /// Re-ranking configuration. + /// Target vectors factory function for multi-vector collections. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Search results. public async Task NearMedia( byte[] media, NearMediaType mediaType, @@ -14,7 +167,7 @@ public async Task NearMedia( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -36,7 +189,7 @@ await _grpc.SearchNearMedia( singlePrompt: null, groupedTask: null, tenant: _collectionClient.Tenant, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, includeVectors: includeVectors, @@ -45,6 +198,27 @@ await _grpc.SearchNearMedia( cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); + /// + /// Performs a near-media search with group-by aggregation using raw media bytes. + /// This is a legacy overload - prefer using the lambda builder overload with NearMediaInput.FactoryFn. + /// + /// The media content as a byte array. + /// The type of media (Image, Video, Audio, etc.). + /// Group-by configuration. + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Filters to apply to the search. + /// Re-ranking configuration. + /// Target vectors factory function for multi-vector collections. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Grouped search results. public async Task NearMedia( byte[] media, NearMediaType mediaType, @@ -56,7 +230,7 @@ public async Task NearMedia( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -78,7 +252,7 @@ await _grpc.SearchNearMedia( singlePrompt: null, groupedTask: null, tenant: _collectionClient.Tenant, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, includeVectors: includeVectors, diff --git a/src/Weaviate.Client/QueryClient.NearObject.cs b/src/Weaviate.Client/QueryClient.NearObject.cs index 455ea0e7..f712cbfe 100644 --- a/src/Weaviate.Client/QueryClient.NearObject.cs +++ b/src/Weaviate.Client/QueryClient.NearObject.cs @@ -13,7 +13,7 @@ public async Task NearObject( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -33,7 +33,7 @@ await _grpc.SearchNearObject( rerank: rerank, singlePrompt: null, groupedTask: null, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, @@ -53,7 +53,7 @@ public async Task NearObject( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -73,7 +73,7 @@ await _grpc.SearchNearObject( rerank: rerank, singlePrompt: null, groupedTask: null, - targetVector: targetVector, + targetVector: targets?.Invoke(new TargetVectors.Builder()), tenant: _collectionClient.Tenant, consistencyLevel: _collectionClient.ConsistencyLevel, returnMetadata: returnMetadata, diff --git a/src/Weaviate.Client/QueryClient.NearText.cs b/src/Weaviate.Client/QueryClient.NearText.cs index d2af8987..e3f5ba48 100644 --- a/src/Weaviate.Client/QueryClient.NearText.cs +++ b/src/Weaviate.Client/QueryClient.NearText.cs @@ -15,7 +15,6 @@ public async Task NearText( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -32,7 +31,7 @@ await _grpc.SearchNearText( moveAway: moveAway, offset: offset, autoLimit: autoLimit, - targetVector: targetVector, + targetVector: null, filters: filters, tenant: _collectionClient.Tenant, rerank: rerank, @@ -56,7 +55,6 @@ public async Task NearText( uint? autoLimit = null, Filter? filters = null, Rerank? rerank = null, - TargetVectors? targetVector = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -77,7 +75,7 @@ await _grpc.SearchNearText( filters: filters, tenant: _collectionClient.Tenant, rerank: rerank, - targetVector: targetVector, + targetVector: null, consistencyLevel: _collectionClient.ConsistencyLevel, returnProperties: returnProperties, returnReferences: returnReferences, @@ -85,4 +83,244 @@ await _grpc.SearchNearText( includeVectors: includeVectors, cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); + + // Lambda builder overload + /// + /// Performs a near-text search using a lambda builder for NearTextInput. + /// Allows specifying target vectors with combination methods (Sum, Average, ManualWeights, etc.) + /// using a fluent syntax. + /// + /// + /// await collection.Query.NearText( + /// q => q(["search query"], certainty: 0.7f) + /// .Sum("title", "description") + /// ) + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Search results. + public async Task NearText( + NearTextInput.FactoryFn query, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var input = query(VectorInputBuilderFactories.CreateNearTextBuilder()); + return await _grpc.SearchNearText( + _collectionClient.Name, + input.Query.ToArray(), + distance: input.Distance, + certainty: input.Certainty, + limit: limit, + moveTo: input.MoveTo, + moveAway: input.MoveAway, + offset: offset, + autoLimit: autoLimit, + targetVector: input.TargetVectors, + filters: filters, + tenant: _collectionClient.Tenant, + rerank: rerank, + consistencyLevel: _collectionClient.ConsistencyLevel, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + ); + } + + // Lambda builder overload with GroupBy + /// + /// Performs a near-text search with group-by using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Grouped search results. + public async Task NearText( + NearTextInput.FactoryFn query, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var input = query(VectorInputBuilderFactories.CreateNearTextBuilder()); + return await _grpc.SearchNearText( + _collectionClient.Name, + input.Query.ToArray(), + groupBy: groupBy, + distance: input.Distance, + certainty: input.Certainty, + moveTo: input.MoveTo, + moveAway: input.MoveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + tenant: _collectionClient.Tenant, + rerank: rerank, + targetVector: input.TargetVectors, + consistencyLevel: _collectionClient.ConsistencyLevel, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + ); + } +} + +/// +/// Extension methods for QueryClient NearText search with NearTextInput. +/// +public static class QueryClientNearTextExtensions +{ + /// + /// Performs a near-text search using a NearTextInput record. + /// + public static async Task NearText( + this QueryClient client, + NearTextInput input, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + // If input has target vectors, use the lambda builder overload + if (input.TargetVectors != null) + { + return await client.NearText( + _ => input, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + } + + // Otherwise use the base method + return await client.NearText( + text: input.Query, + certainty: input.Certainty, + distance: input.Distance, + moveTo: input.MoveTo, + moveAway: input.MoveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + } + + /// + /// Performs a near-text search with group-by using a NearTextInput record. + /// + public static async Task NearText( + this QueryClient client, + NearTextInput input, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + // If input has target vectors, use the lambda builder overload + if (input.TargetVectors != null) + { + return await client.NearText( + _ => input, + groupBy, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + } + + // Otherwise use the base method + return await client.NearText( + text: input.Query, + groupBy: groupBy, + certainty: input.Certainty, + distance: input.Distance, + moveTo: input.MoveTo, + moveAway: input.MoveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + } } diff --git a/src/Weaviate.Client/QueryClient.NearVector.cs b/src/Weaviate.Client/QueryClient.NearVector.cs index 91bdd043..cf43a574 100644 --- a/src/Weaviate.Client/QueryClient.NearVector.cs +++ b/src/Weaviate.Client/QueryClient.NearVector.cs @@ -4,15 +4,15 @@ namespace Weaviate.Client; public partial class QueryClient { + // Simple overload accepting VectorSearchInput (with implicit conversions support) public async Task NearVector( - Vectors vector, + VectorSearchInput vectors, Filter? filters = null, float? certainty = null, float? distance = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, AutoArray? returnProperties = null, IList? returnReferences = null, @@ -20,32 +20,35 @@ public async Task NearVector( VectorQuery? includeVectors = null, CancellationToken cancellationToken = default ) => - await NearVector( - (NearVectorInput)vector, - filters, - certainty, - distance, - autoLimit, - limit, - offset, - targetVector, - rerank, - returnProperties, - returnReferences, - returnMetadata, - includeVectors, - cancellationToken + await _grpc.SearchNearVector( + _collectionClient.Name, + vectors, + distance: distance, + certainty: certainty, + offset: offset, + autoLimit: autoLimit, + limit: limit, + filters: filters, + tenant: _collectionClient.Tenant, + rerank: rerank, + consistencyLevel: _collectionClient.ConsistencyLevel, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); - public async Task NearVector( - NearVectorInput vector, + // Simple overload with GroupBy + public async Task NearVector( + VectorSearchInput vectors, + GroupByRequest groupBy, Filter? filters = null, float? certainty = null, float? distance = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, AutoArray? returnProperties = null, IList? returnReferences = null, @@ -55,14 +58,14 @@ public async Task NearVector( ) => await _grpc.SearchNearVector( _collectionClient.Name, - vector, + vectors, + groupBy, + filters: filters, distance: distance, certainty: certainty, offset: offset, autoLimit: autoLimit, limit: limit, - targetVector: targetVector, - filters: filters, tenant: _collectionClient.Tenant, rerank: rerank, consistencyLevel: _collectionClient.ConsistencyLevel, @@ -73,16 +76,15 @@ await _grpc.SearchNearVector( cancellationToken: CreateTimeoutCancellationToken(cancellationToken) ); - public async Task NearVector( - Vectors vector, - GroupByRequest groupBy, + // Lambda syntax overload + public async Task NearVector( + VectorSearchInput.FactoryFn vectors, Filter? filters = null, - float? distance = null, float? certainty = null, + float? distance = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, AutoArray? returnProperties = null, IList? returnReferences = null, @@ -91,15 +93,13 @@ public async Task NearVector( CancellationToken cancellationToken = default ) => await NearVector( - (NearVectorInput)vector, - groupBy, + vectors(new VectorSearchInput.Builder()), filters, - distance, certainty, + distance, autoLimit, limit, offset, - targetVector, rerank, returnProperties, returnReferences, @@ -108,16 +108,16 @@ await NearVector( cancellationToken ); + // Lambda syntax overload with GroupBy public async Task NearVector( - NearVectorInput vector, + VectorSearchInput.FactoryFn vectors, GroupByRequest groupBy, Filter? filters = null, - float? distance = null, float? certainty = null, + float? distance = null, uint? autoLimit = null, uint? limit = null, uint? offset = null, - TargetVectors? targetVector = null, Rerank? rerank = null, AutoArray? returnProperties = null, IList? returnReferences = null, @@ -125,24 +125,208 @@ public async Task NearVector( VectorQuery? includeVectors = null, CancellationToken cancellationToken = default ) => - await _grpc.SearchNearVector( - _collectionClient.Name, - vector, + await NearVector( + vectors(new VectorSearchInput.Builder()), groupBy, + filters, + certainty, + distance, + autoLimit, + limit, + offset, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + // NearVectorInput overload + /// + /// Performs a near-vector search using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Search results. + public async Task NearVector( + NearVectorInput input, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors: input.Vector, filters: filters, - distance: distance, - certainty: certainty, + certainty: input.Certainty, + distance: input.Distance, + autoLimit: autoLimit, + limit: limit, offset: offset, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + // NearVectorInput overload with GroupBy + /// + /// Performs a near-vector search with group-by using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Grouped search results. + public async Task NearVector( + NearVectorInput input, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors: input.Vector, + groupBy: groupBy, + filters: filters, + certainty: input.Certainty, + distance: input.Distance, autoLimit: autoLimit, limit: limit, - targetVector: targetVector, - tenant: _collectionClient.Tenant, + offset: offset, rerank: rerank, - consistencyLevel: _collectionClient.ConsistencyLevel, returnProperties: returnProperties, returnReferences: returnReferences, returnMetadata: returnMetadata, includeVectors: includeVectors, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + cancellationToken: cancellationToken + ); + + // NearVectorInputBuilder lambda overload + /// + /// Performs a near-vector search using a lambda builder for NearVectorInput. + /// + /// + /// v => v(certainty: 0.8).ManualWeights( + /// ("title", 1.2f, new[] { 1f, 2f }), + /// ("description", 0.8f, new[] { 3f, 4f }) + /// ) + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Search results. + public async Task NearVector( + NearVectorInput.FactoryFn vectors, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + filters, + autoLimit, + limit, + offset, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + // NearVectorInputBuilder lambda overload with GroupBy + /// + /// Performs a near-vector search with group-by using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// Grouped search results. + public async Task NearVector( + NearVectorInput.FactoryFn vectors, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + await NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + groupBy, + filters, + autoLimit, + limit, + offset, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken ); } diff --git a/src/Weaviate.Client/Typed/TypedGenerateClient.BM25.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.BM25.cs new file mode 100644 index 00000000..38641b77 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.BM25.cs @@ -0,0 +1,136 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedGenerateClient +{ + /// + /// BM25 search with generative AI capabilities and grouping. + /// + /// Search query + /// Group by configuration + /// Fields to search in + /// Filters to apply + /// Auto-cut threshold + /// Maximum number of results + /// Offset for pagination + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. + /// Cursor for pagination + /// Consistency level + /// Properties to return + /// Metadata to return + /// Vectors to include + /// References to return + /// Cancellation token for the operation + /// Strongly-typed generative group-by result + public async Task> BM25( + string query, + GroupByRequest groupBy, + string[]? searchFields = null, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + Guid? after = null, + ConsistencyLevels? consistencyLevel = null, + AutoArray? returnProperties = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + IList? returnReferences = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.BM25( + query: query, + groupBy: groupBy, + searchFields: searchFields, + filters: filters, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + after: after, + consistencyLevel: consistencyLevel, + returnProperties: returnProperties, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + returnReferences: returnReferences, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// BM25 search with generative AI capabilities. + /// + /// Search query + /// Fields to search in + /// Filters to apply + /// Auto-cut threshold + /// Maximum number of results + /// Offset for pagination + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. + /// Cursor for pagination + /// Consistency level + /// Properties to return + /// Metadata to return + /// Vectors to include + /// References to return + /// Cancellation token for the operation + /// Strongly-typed generative result + public async Task> BM25( + string query, + string[]? searchFields = null, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + Guid? after = null, + ConsistencyLevels? consistencyLevel = null, + AutoArray? returnProperties = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + IList? returnReferences = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.BM25( + query: query, + searchFields: searchFields, + filters: filters, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + after: after, + consistencyLevel: consistencyLevel, + returnProperties: returnProperties, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + returnReferences: returnReferences, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} diff --git a/src/Weaviate.Client/Typed/TypedGenerateClient.Hybrid.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.Hybrid.cs new file mode 100644 index 00000000..45ee4de8 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.Hybrid.cs @@ -0,0 +1,317 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedGenerateClient +{ + /// + /// Hybrid search with generative AI capabilities (query-only, no vectors). + /// + public Task> Hybrid( + string query, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + Hybrid( + query: query, + vectors: (HybridVectorInput?)null, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Hybrid search with generative AI capabilities. + /// + public async Task> Hybrid( + string? query, + HybridVectorInput? vectors, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.Hybrid( + query: query, + vectors: vectors, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Hybrid search with generative AI capabilities and grouping (query-only, no vectors). + /// + public Task> Hybrid( + string query, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + Hybrid( + query: query, + vectors: (HybridVectorInput?)null, + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Hybrid search with generative AI capabilities and grouping. + /// + public async Task> Hybrid( + string? query, + HybridVectorInput? vectors, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.Hybrid( + query: query, + vectors: vectors, + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} + +/// +/// Extension methods for TypedGenerateClient Hybrid search with lambda vector builders. +/// +public static class TypedGenerateClientHybridExtensions +{ + /// + /// Hybrid search with generative AI capabilities using a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. + /// + public static async Task> Hybrid( + this TypedGenerateClient client, + string query, + HybridVectorInput.FactoryFn vectors, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + where T : class, new() => + await client.Hybrid( + query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Hybrid search with generative AI capabilities and grouping using a lambda to build HybridVectorInput. + /// This allows chaining NearVector or NearText configuration with target vectors. + /// + public static async Task> Hybrid( + this TypedGenerateClient client, + string query, + HybridVectorInput.FactoryFn vectors, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + where T : class, new() => + await client.Hybrid( + query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); +} diff --git a/src/Weaviate.Client/Typed/TypedGenerateClient.NearMedia.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.NearMedia.cs new file mode 100644 index 00000000..6f6b6124 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.NearMedia.cs @@ -0,0 +1,139 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedGenerateClient +{ + /// + /// Performs a near-media search with generative AI capabilities. + /// + /// + /// // Simple image search with generation + /// await typedGenerate.NearMedia( + /// m => m.Image(imageBytes), + /// singlePrompt: "Describe this image" + /// ); + /// + /// // With target vectors and generation + /// await typedGenerate.NearMedia( + /// m => m.Video(videoBytes, certainty: 0.8f).Sum("v1", "v2"), + /// singlePrompt: "Summarize this video" + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative search result. + public async Task> NearMedia( + NearMediaInput.FactoryFn media, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearMedia( + media: media, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-media search with generative AI capabilities and group-by aggregation. + /// + /// + /// // Image search with grouping and generation + /// await typedGenerate.NearMedia( + /// m => m.Image(imageBytes).Sum("visual", "semantic"), + /// groupBy: new GroupByRequest("category", objectsPerGroup: 5), + /// groupedTask: "Summarize each group" + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative grouped search result. + public async Task> NearMedia( + NearMediaInput.FactoryFn media, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearMedia( + media: media, + groupBy: groupBy, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} diff --git a/src/Weaviate.Client/GenerateClient.NearImage.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.NearObject.cs similarity index 78% rename from src/Weaviate.Client/GenerateClient.NearImage.cs rename to src/Weaviate.Client/Typed/TypedGenerateClient.NearObject.cs index 34b64788..8049f8fc 100644 --- a/src/Weaviate.Client/GenerateClient.NearImage.cs +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.NearObject.cs @@ -1,13 +1,14 @@ using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; -namespace Weaviate.Client; +namespace Weaviate.Client.Typed; -public partial class GenerateClient +public partial class TypedGenerateClient { /// - /// Search near image with generative AI capabilities. + /// Search near object with generative AI capabilities. /// - /// Image data + /// Object ID to search near /// Certainty threshold /// Distance threshold /// Maximum number of results @@ -18,15 +19,15 @@ public partial class GenerateClient /// Single prompt for generation /// Grouped prompt for generation /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name + /// Target vectors to search /// Properties to return /// References to return /// Metadata to return /// Vectors to include /// Cancellation token for the operation - /// Generative result - public async Task NearImage( - byte[] nearImage, + /// Strongly-typed generative result + public async Task> NearObject( + Guid nearObject, double? certainty = null, double? distance = null, uint? limit = null, @@ -37,7 +38,7 @@ public async Task NearImage( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -45,9 +46,8 @@ public async Task NearImage( CancellationToken cancellationToken = default ) { - var result = await NearMedia( - media: nearImage, - mediaType: NearMediaType.Image, + var result = await _generateClient.NearObject( + nearObject: nearObject, certainty: certainty, distance: distance, limit: limit, @@ -58,21 +58,20 @@ public async Task NearImage( singlePrompt: singlePrompt, groupedTask: groupedTask, provider: provider, - targetVector: targetVector, - returnMetadata: returnMetadata, - includeVectors: includeVectors, + targets: targets, returnProperties: returnProperties, returnReferences: returnReferences, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken ); - - return result; + return result.ToTyped(); } /// - /// Search near image with generative AI capabilities and grouping. + /// Search near object with generative AI capabilities and grouping. /// - /// Image data + /// Object ID to search near /// Group by configuration /// Certainty threshold /// Distance threshold @@ -84,15 +83,15 @@ public async Task NearImage( /// Single prompt for generation /// Grouped prompt for generation /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. - /// Target vector name + /// Target vectors to search /// Properties to return /// References to return /// Metadata to return /// Vectors to include /// Cancellation token for the operation - /// Generative group-by result - public async Task NearImage( - byte[] nearImage, + /// Strongly-typed generative group-by result + public async Task> NearObject( + Guid nearObject, GroupByRequest groupBy, double? certainty = null, double? distance = null, @@ -104,7 +103,7 @@ public async Task NearImage( SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, GenerativeProvider? provider = null, - TargetVectors? targetVector = null, + TargetVectors.FactoryFn? targets = null, AutoArray? returnProperties = null, IList? returnReferences = null, MetadataQuery? returnMetadata = null, @@ -112,28 +111,26 @@ public async Task NearImage( CancellationToken cancellationToken = default ) { - var result = await NearMedia( - media: nearImage, - mediaType: NearMediaType.Image, + var result = await _generateClient.NearObject( + nearObject: nearObject, + groupBy: groupBy, certainty: certainty, distance: distance, limit: limit, offset: offset, autoLimit: autoLimit, filters: filters, - groupBy: groupBy, rerank: rerank, singlePrompt: singlePrompt, groupedTask: groupedTask, provider: provider, - targetVector: targetVector, - returnMetadata: returnMetadata, - includeVectors: includeVectors, + targets: targets, returnProperties: returnProperties, returnReferences: returnReferences, - cancellationToken: CreateTimeoutCancellationToken(cancellationToken) + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken ); - - return result; + return result.ToTyped(); } } diff --git a/src/Weaviate.Client/Typed/TypedGenerateClient.NearText.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.NearText.cs new file mode 100644 index 00000000..1b16d611 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.NearText.cs @@ -0,0 +1,360 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedGenerateClient +{ + /// + /// Search near text with generative AI capabilities. + /// + /// Text to search near + /// Certainty threshold + /// Distance threshold + /// Move towards concept + /// Move away from concept + /// Maximum number of results + /// Offset for pagination + /// Auto-cut threshold + /// Filters to apply + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. + /// Target vectors to search + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative result + public async Task> NearText( + AutoArray query, + float? certainty = null, + float? distance = null, + Move? moveTo = null, + Move? moveAway = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearText( + query: query, + certainty: certainty, + distance: distance, + moveTo: moveTo, + moveAway: moveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Search near text with generative AI capabilities and grouping. + /// + /// Text to search near + /// Group by configuration + /// Certainty threshold + /// Distance threshold + /// Move towards concept + /// Move away from concept + /// Maximum number of results + /// Offset for pagination + /// Auto-cut threshold + /// Filters to apply + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Optional generative provider to enrich prompts that don't have a provider set. If the prompt already has a provider, it will not be overridden. + /// Target vectors to search + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative group-by result + public async Task> NearText( + AutoArray query, + GroupByRequest groupBy, + float? certainty = null, + float? distance = null, + Move? moveTo = null, + Move? moveAway = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearText( + query: query, + groupBy: groupBy, + certainty: certainty, + distance: distance, + moveTo: moveTo, + moveAway: moveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Search near text with generative AI capabilities using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative search result. + public async Task> NearText( + NearTextInput query, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearText( + query, + filters, + limit, + offset, + autoLimit, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Search near text with generative AI capabilities and grouping using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative grouped search result. + public async Task> NearText( + NearTextInput query, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearText( + query, + groupBy, + filters, + limit, + offset, + autoLimit, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-text search with generative AI using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative search result. + public Task> NearText( + NearTextInput.FactoryFn query, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + filters, + limit, + offset, + autoLimit, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Performs a near-text search with generative AI and grouping using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative grouped search result. + public Task> NearText( + NearTextInput.FactoryFn query, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + groupBy, + filters, + limit, + offset, + autoLimit, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); +} diff --git a/src/Weaviate.Client/Typed/TypedGenerateClient.NearVector.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.NearVector.cs new file mode 100644 index 00000000..62e294b6 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.NearVector.cs @@ -0,0 +1,461 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedGenerateClient +{ + /// + /// Search near vector with generative AI capabilities. + /// + /// Vector to search near + /// Filters to apply + /// Certainty threshold + /// Distance threshold + /// Auto-cut threshold + /// Maximum number of results + /// Offset for pagination + /// Target vector name + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative result + public async Task> NearVector( + VectorSearchInput vectors, + Filter? filters = null, + float? certainty = null, + float? distance = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearVector( + vectors: vectors, + filters: filters, + certainty: certainty, + distance: distance, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Search near vector with generative AI capabilities and grouping. + /// + /// Vector to search near + /// Group by configuration + /// Filters to apply + /// Distance threshold + /// Certainty threshold + /// Auto-cut threshold + /// Maximum number of results + /// Offset for pagination + /// Target vector name + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative group-by result + public async Task> NearVector( + VectorSearchInput vectors, + GroupByRequest groupBy, + Filter? filters = null, + float? distance = null, + float? certainty = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.NearVector( + vectors: vectors, + groupBy: groupBy, + filters: filters, + distance: distance, + certainty: certainty, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Search near vector with generative AI capabilities using lambda builder. + /// + /// Lambda function to build the vectors + /// Filters to apply + /// Certainty threshold + /// Distance threshold + /// Auto-cut threshold + /// Maximum number of results + /// Offset for pagination + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Optional generative provider + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative result + public Task> NearVector( + VectorSearchInput.FactoryFn vectors, + Filter? filters = null, + float? certainty = null, + float? distance = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(new VectorSearchInput.Builder()), + filters, + certainty, + distance, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities and grouping using lambda builder. + /// + /// Lambda function to build the vectors + /// Group by configuration + /// Filters to apply + /// Certainty threshold + /// Distance threshold + /// Auto-cut threshold + /// Maximum number of results + /// Offset for pagination + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Optional generative provider + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative group-by result + public Task> NearVector( + VectorSearchInput.FactoryFn vectors, + GroupByRequest groupBy, + Filter? filters = null, + float? certainty = null, + float? distance = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(new VectorSearchInput.Builder()), + groupBy, + filters, + certainty, + distance, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative search result. + public Task> NearVector( + NearVectorInput vectors, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors: vectors.Vector, + certainty: vectors.Certainty, + distance: vectors.Distance, + filters: filters, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities and grouping using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative grouped search result. + public Task> NearVector( + NearVectorInput vectors, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors: vectors.Vector, + certainty: vectors.Certainty, + distance: vectors.Distance, + groupBy: groupBy, + filters: filters, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative search result. + public Task> NearVector( + NearVectorInput.FactoryFn vectors, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + filters, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Search near vector with generative AI capabilities and grouping using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Single prompt for generative AI. + /// Grouped task for generative AI. + /// Generative AI provider configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed generative grouped search result. + public Task> NearVector( + NearVectorInput.FactoryFn vectors, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + groupBy, + filters, + autoLimit, + limit, + offset, + rerank, + singlePrompt, + groupedTask, + provider, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); +} diff --git a/src/Weaviate.Client/Typed/TypedGenerateClient.Objects.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.Objects.cs new file mode 100644 index 00000000..ce0621b2 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.Objects.cs @@ -0,0 +1,199 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedGenerateClient +{ + /// + /// Fetch objects with generative AI capabilities and grouping. + /// + /// Group by configuration + /// Cursor for pagination. + /// Maximum number of results + /// Filters to apply + /// Sort configuration + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative group-by result + public async Task> FetchObjects( + Models.GroupByRequest groupBy, + Guid? after = null, + uint? limit = null, + Filter? filters = null, + AutoArray? sort = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.FetchObjects( + groupBy: groupBy, + after: after, + limit: limit, + filters: filters, + sort: sort, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Fetch objects with generative AI capabilities. + /// + /// Cursor for pagination. + /// Maximum number of results + /// Filters to apply + /// Sort configuration + /// Rerank configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative result + public async Task?> FetchObjects( + Guid? after = null, + uint? limit = null, + Filter? filters = null, + AutoArray? sort = null, + Rerank? rerank = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.FetchObjects( + limit: limit, + filters: filters, + sort: sort, + rerank: rerank, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + after: after, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result?.ToTyped(); + } + + /// + /// Fetch a single object by ID with generative AI capabilities. + /// + /// Object UUID + /// Single prompt for generation + /// Grouped prompt for generation + /// Generative provider + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative result + public async Task?> FetchObjectByID( + Guid uuid, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.FetchObjectByID( + id: uuid, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result?.ToTyped(); + } + + /// + /// Fetch multiple objects by IDs with generative AI capabilities. + /// + /// Set of object UUIDs + /// Maximum number of results + /// Rerank configuration + /// Filters to apply + /// Sort configuration + /// Single prompt for generation + /// Grouped prompt for generation + /// Properties to return + /// References to return + /// Metadata to return + /// Vectors to include + /// Cancellation token for the operation + /// Strongly-typed generative result + public async Task> FetchObjectsByIDs( + HashSet uuids, + uint? limit = null, + Rerank? rerank = null, + Filter? filters = null, + AutoArray? sort = null, + SinglePrompt? singlePrompt = null, + GroupedTask? groupedTask = null, + GenerativeProvider? provider = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _generateClient.FetchObjectsByIDs( + ids: uuids, + limit: limit, + rerank: rerank, + filters: filters, + sort: sort, + singlePrompt: singlePrompt, + groupedTask: groupedTask, + provider: provider, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} diff --git a/src/Weaviate.Client/Typed/TypedGenerateClient.cs b/src/Weaviate.Client/Typed/TypedGenerateClient.cs index 61f8cfbd..6c2e7b83 100644 --- a/src/Weaviate.Client/Typed/TypedGenerateClient.cs +++ b/src/Weaviate.Client/Typed/TypedGenerateClient.cs @@ -1,6 +1,3 @@ -using Weaviate.Client.Models; -using Weaviate.Client.Models.Typed; - namespace Weaviate.Client.Typed; /// @@ -8,7 +5,7 @@ namespace Weaviate.Client.Typed; /// All generative methods return GenerativeWeaviateResult<T> or GenerativeGroupByResult<T> instead of untyped results. /// /// The C# type to deserialize object properties into. -public class TypedGenerateClient +public partial class TypedGenerateClient where T : class, new() { private readonly GenerateClient _generateClient; @@ -23,1342 +20,4 @@ public TypedGenerateClient(GenerateClient generateClient) _generateClient = generateClient; } - - #region Objects - /// - /// Fetch objects with generative AI capabilities and grouping. - /// - /// Group by configuration - /// Maximum number of results - /// Filters to apply - /// Sort configuration - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> FetchObjects( - Models.GroupByRequest groupBy, - uint? limit = null, - Filter? filters = null, - AutoArray? sort = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.FetchObjects( - groupBy: groupBy, - limit: limit, - filters: filters, - sort: sort, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Fetch objects with generative AI capabilities. - /// - /// Cursor for pagination. - /// Maximum number of results - /// Filters to apply - /// Sort configuration - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task?> FetchObjects( - Guid? after = null, - uint? limit = null, - Filter? filters = null, - AutoArray? sort = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.FetchObjects( - limit: limit, - filters: filters, - sort: sort, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - after: after, - provider: provider, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result?.ToTyped(); - } - - /// - /// Fetch a single object by ID with generative AI capabilities. - /// - /// Object UUID - /// Single prompt for generation - /// Grouped prompt for generation - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task?> FetchObjectByID( - Guid uuid, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.FetchObjectByID( - id: uuid, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result?.ToTyped(); - } - - /// - /// Fetch multiple objects by IDs with generative AI capabilities. - /// - /// Set of object UUIDs - /// Maximum number of results - /// Rerank configuration - /// Filters to apply - /// Sort configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> FetchObjectsByIDs( - HashSet uuids, - uint? limit = null, - Rerank? rerank = null, - Filter? filters = null, - AutoArray? sort = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.FetchObjectsByIDs( - ids: uuids, - limit: limit, - rerank: rerank, - filters: filters, - sort: sort, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - #endregion - - #region Search - - /// - /// Search near text with generative AI capabilities. - /// - /// Text to search near - /// Certainty threshold - /// Distance threshold - /// Move towards concept - /// Move away from concept - /// Maximum number of results - /// Offset for pagination - /// Auto-cut threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> NearText( - AutoArray text, - float? certainty = null, - float? distance = null, - Move? moveTo = null, - Move? moveAway = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearText( - text: text, - certainty: certainty, - distance: distance, - moveTo: moveTo, - moveAway: moveAway, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near text with generative AI capabilities and grouping. - /// - /// Text to search near - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Move towards concept - /// Move away from concept - /// Maximum number of results - /// Offset for pagination - /// Auto-cut threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> NearText( - AutoArray text, - GroupByRequest groupBy, - float? certainty = null, - float? distance = null, - Move? moveTo = null, - Move? moveAway = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearText( - text: text, - groupBy: groupBy, - certainty: certainty, - distance: distance, - moveTo: moveTo, - moveAway: moveAway, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near vector with generative AI capabilities. - /// - /// Vector to search near - /// Filters to apply - /// Certainty threshold - /// Distance threshold - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Target vector name - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> NearVector( - Vectors vector, - Filter? filters = null, - float? certainty = null, - float? distance = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - TargetVectors? targetVector = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearVector( - vector: vector, - filters: filters, - certainty: certainty, - distance: distance, - autoLimit: autoLimit, - limit: limit, - offset: offset, - targetVector: targetVector, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near vector with generative AI capabilities and grouping. - /// - /// Vector to search near - /// Group by configuration - /// Filters to apply - /// Distance threshold - /// Certainty threshold - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Target vector name - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> NearVector( - Vectors vector, - GroupByRequest groupBy, - Filter? filters = null, - float? distance = null, - float? certainty = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - TargetVectors? targetVector = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearVector( - vector: vector, - groupBy: groupBy, - filters: filters, - distance: distance, - certainty: certainty, - autoLimit: autoLimit, - limit: limit, - offset: offset, - targetVector: targetVector, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// BM25 search with generative AI capabilities and grouping. - /// - /// Search query - /// Group by configuration - /// Fields to search in - /// Filters to apply - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Consistency level - /// Properties to return - /// Metadata to return - /// Vectors to include - /// References to return - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> BM25( - string query, - GroupByRequest groupBy, - string[]? searchFields = null, - Filter? filters = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - ConsistencyLevels? consistencyLevel = null, - AutoArray? returnProperties = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - IList? returnReferences = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.BM25( - query: query, - groupBy: groupBy, - searchFields: searchFields, - filters: filters, - autoLimit: autoLimit, - limit: limit, - offset: offset, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - consistencyLevel: consistencyLevel, - returnProperties: returnProperties, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - returnReferences: returnReferences, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// BM25 search with generative AI capabilities. - /// - /// Search query - /// Fields to search in - /// Filters to apply - /// Auto-cut threshold - /// Maximum number of results - /// Offset for pagination - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Consistency level - /// Properties to return - /// Metadata to return - /// Vectors to include - /// References to return - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> BM25( - string query, - string[]? searchFields = null, - Filter? filters = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - ConsistencyLevels? consistencyLevel = null, - AutoArray? returnProperties = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - IList? returnReferences = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.BM25( - query: query, - searchFields: searchFields, - filters: filters, - autoLimit: autoLimit, - limit: limit, - offset: offset, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - consistencyLevel: consistencyLevel, - returnProperties: returnProperties, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - returnReferences: returnReferences, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Hybrid search with generative AI capabilities. - /// - /// Search query - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> Hybrid( - string? query, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.Hybrid( - query: query, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Hybrid search with generative AI capabilities and grouping. - /// - /// Search query - /// Group by configuration - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> Hybrid( - string? query, - Models.GroupByRequest groupBy, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.Hybrid( - query: query, - groupBy: groupBy, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Hybrid search with generative AI capabilities using vectors. - /// - /// Search query - /// Vectors for search - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> Hybrid( - string? query, - Vectors vectors, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.Hybrid( - query: query, - vectors: vectors, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Hybrid search with generative AI capabilities using hybrid vector input. - /// - /// Search query - /// Hybrid vector input - /// Alpha value for hybrid search - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> Hybrid( - string? query, - IHybridVectorInput vectors, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.Hybrid( - query: query, - vectors: vectors, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Hybrid search with generative AI capabilities, grouping, and hybrid vector input. - /// - /// Search query - /// Group by configuration - /// Alpha value for hybrid search - /// Hybrid vector input - /// Properties to query - /// Fusion type - /// Maximum vector distance - /// Maximum number of results - /// Offset for pagination - /// BM25 operator - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> Hybrid( - string? query, - Models.GroupByRequest groupBy, - float? alpha = null, - IHybridVectorInput? vectors = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.Hybrid( - query: query, - groupBy: groupBy, - alpha: alpha, - vectors: vectors, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near object with generative AI capabilities. - /// - /// Object ID to search near - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> NearObject( - Guid nearObject, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearObject( - nearObject: nearObject, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near object with generative AI capabilities and grouping. - /// - /// Object ID to search near - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> NearObject( - Guid nearObject, - GroupByRequest groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearObject( - nearObject: nearObject, - groupBy: groupBy, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near image with generative AI capabilities. - /// - /// Image data - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> NearImage( - byte[] nearImage, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearImage( - nearImage: nearImage, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near image with generative AI capabilities and grouping. - /// - /// Image data - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> NearImage( - byte[] nearImage, - GroupByRequest groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearImage( - nearImage: nearImage, - groupBy: groupBy, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near media with generative AI capabilities. - /// - /// Media data - /// Type of media - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative result - public async Task> NearMedia( - byte[] media, - NearMediaType mediaType, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearMedia( - media: media, - mediaType: mediaType, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Search near media with generative AI capabilities and grouping. - /// - /// Media data - /// Type of media - /// Group by configuration - /// Certainty threshold - /// Distance threshold - /// Maximum number of results - /// Offset for pagination - /// Auto-limit threshold - /// Filters to apply - /// Rerank configuration - /// Single prompt for generation - /// Grouped prompt for generation - /// Target vector name - /// Properties to return - /// References to return - /// Metadata to return - /// Vectors to include - /// Cancellation token for the operation - /// Strongly-typed generative group-by result - public async Task> NearMedia( - byte[] media, - NearMediaType mediaType, - GroupByRequest groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - SinglePrompt? singlePrompt = null, - GroupedTask? groupedTask = null, - GenerativeProvider? provider = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _generateClient.NearMedia( - media: media, - mediaType: mediaType, - groupBy: groupBy, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - singlePrompt: singlePrompt, - groupedTask: groupedTask, - provider: provider, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - #endregion } diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.BM25.cs b/src/Weaviate.Client/Typed/TypedQueryClient.BM25.cs new file mode 100644 index 00000000..30c1f7e3 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedQueryClient.BM25.cs @@ -0,0 +1,124 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedQueryClient +{ + /// + /// Performs a BM25 keyword search with group-by aggregation. + /// + /// The search query string. + /// Group-by configuration. + /// Fields to search in. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// BM25 search operator (AND/OR). + /// Re-ranking configuration. + /// Cursor for pagination. + /// Consistency level for the query. + /// Properties to return in the response. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cross-references to return. + /// Cancellation token. + /// A strongly-typed grouped result. + public async Task> BM25( + string query, + GroupByRequest groupBy, + string[]? searchFields = null, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + BM25Operator? searchOperator = null, + Rerank? rerank = null, + Guid? after = null, + ConsistencyLevels? consistencyLevel = null, + AutoArray? returnProperties = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + IList? returnReferences = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.BM25( + query: query, + groupBy: groupBy, + searchFields: searchFields, + filters: filters, + autoLimit: autoLimit, + limit: limit, + offset: offset, + searchOperator: searchOperator, + rerank: rerank, + after: after, + consistencyLevel: consistencyLevel, + returnProperties: returnProperties, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + returnReferences: returnReferences, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a BM25 keyword search. + /// + /// The search query string. + /// Fields to search in. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// BM25 search operator (AND/OR). + /// Re-ranking configuration. + /// Cursor for pagination. + /// Consistency level for the query. + /// Properties to return in the response. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cross-references to return. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public async Task>> BM25( + string query, + string[]? searchFields = null, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + BM25Operator? searchOperator = null, + Rerank? rerank = null, + Guid? after = null, + ConsistencyLevels? consistencyLevel = null, + AutoArray? returnProperties = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + IList? returnReferences = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.BM25( + query: query, + searchFields: searchFields, + filters: filters, + autoLimit: autoLimit, + limit: limit, + offset: offset, + searchOperator: searchOperator, + rerank: rerank, + after: after, + consistencyLevel: consistencyLevel, + returnProperties: returnProperties, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + returnReferences: returnReferences, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.Hybrid.cs b/src/Weaviate.Client/Typed/TypedQueryClient.Hybrid.cs new file mode 100644 index 00000000..f8d0f458 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedQueryClient.Hybrid.cs @@ -0,0 +1,300 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedQueryClient +{ + /// + /// Performs a hybrid search using keyword search only. + /// + public Task>> Hybrid( + string query, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + Hybrid( + query: query, + vectors: (HybridVectorInput?)null, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Performs a hybrid search combining keyword and vector search. + /// + public async Task>> Hybrid( + string? query, + HybridVectorInput? vectors, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.Hybrid( + query: query, + vectors: vectors, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a hybrid search with group-by aggregation using keyword search only. + /// + public Task> Hybrid( + string query, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + Hybrid( + query: query, + vectors: (HybridVectorInput?)null, + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Performs a hybrid search with group-by aggregation. + /// + public async Task> Hybrid( + string? query, + HybridVectorInput? vectors, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.Hybrid( + query: query, + vectors: vectors, + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} + +/// +/// Extension methods for TypedQueryClient Hybrid search with lambda vector builders. +/// +public static class TypedQueryClientHybridExtensions +{ + /// + /// Performs a hybrid search (keyword + vector search) using a lambda to build HybridVectorInput. + /// Supports NearVector, NearText, or direct vector input via .Vectors(). + /// + /// + /// // Using NearVector with target vectors: + /// await collection.Query.AsTyped<MyClass>().Hybrid( + /// "test", + /// v => v.NearVector().ManualWeights( + /// ("title", 1.2, new[] { 1f, 2f }), + /// ("description", 0.8, new[] { 3f, 4f }) + /// ) + /// ); + /// + /// // Using direct vectors (no combination method): + /// await collection.Query.AsTyped<MyClass>().Hybrid( + /// "test", + /// v => v.Vectors( + /// ("title", new[] { 1f, 2f }), + /// ("description", new[] { 3f, 4f }) + /// ) + /// ); + /// + public static async Task>> Hybrid( + this TypedQueryClient client, + string query, + HybridVectorInput.FactoryFn vectors, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + where T : class, new() => + await client.Hybrid( + query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Performs a hybrid search (keyword + vector search) with grouping using a lambda to build HybridVectorInput. + /// Supports NearVector, NearText, or direct vector input via .Vectors(). + /// + public static async Task> Hybrid( + this TypedQueryClient client, + string query, + HybridVectorInput.FactoryFn vectors, + GroupByRequest groupBy, + float? alpha = null, + string[]? queryProperties = null, + HybridFusion? fusionType = null, + float? maxVectorDistance = null, + uint? limit = null, + uint? offset = null, + BM25Operator? bm25Operator = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + where T : class, new() => + await client.Hybrid( + query: query, + vectors: vectors(VectorInputBuilderFactories.CreateHybridBuilder()), + groupBy: groupBy, + alpha: alpha, + queryProperties: queryProperties, + fusionType: fusionType, + maxVectorDistance: maxVectorDistance, + limit: limit, + offset: offset, + bm25Operator: bm25Operator, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); +} diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.NearMedia.cs b/src/Weaviate.Client/Typed/TypedQueryClient.NearMedia.cs new file mode 100644 index 00000000..914bb84f --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedQueryClient.NearMedia.cs @@ -0,0 +1,114 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedQueryClient +{ + /// + /// Performs a near-media search using media embeddings. + /// + /// + /// // Simple image search + /// await typedQuery.NearMedia(m => m.Image(imageBytes)); + /// + /// // With target vectors + /// await typedQuery.NearMedia(m => m.Video(videoBytes, certainty: 0.8f).Sum("v1", "v2")); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public async Task>> NearMedia( + NearMediaInput.FactoryFn media, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearMedia( + query: media, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-media search with group-by aggregation. + /// + /// + /// // Image search with grouping + /// await typedQuery.NearMedia( + /// m => m.Image(imageBytes).Sum("visual", "semantic"), + /// groupBy: new GroupByRequest("category", objectsPerGroup: 5) + /// ); + /// + /// Lambda builder for creating NearMediaInput with media data and target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public async Task> NearMedia( + NearMediaInput.FactoryFn media, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearMedia( + query: media, + groupBy: groupBy, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.NearObject.cs b/src/Weaviate.Client/Typed/TypedQueryClient.NearObject.cs new file mode 100644 index 00000000..febebc37 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedQueryClient.NearObject.cs @@ -0,0 +1,118 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedQueryClient +{ + /// + /// Performs a near-object search using another object as reference. + /// + /// The ID of the object to search near. + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result limit threshold. + /// Filters to apply to the search. + /// Re-ranking configuration. + /// Target vector configuration for named vectors. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public async Task>> NearObject( + Guid nearObject, + double? certainty = null, + double? distance = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + TargetVectors.FactoryFn? targets = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearObject( + nearObject: nearObject, + certainty: certainty, + distance: distance, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + targets: targets, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-object search with group-by aggregation. + /// + /// The ID of the object to search near. + /// Group-by configuration. + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result limit threshold. + /// Filters to apply to the search. + /// Re-ranking configuration. + /// Target vector configuration for named vectors. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public async Task> NearObject( + Guid nearObject, + GroupByRequest groupBy, + double? certainty = null, + double? distance = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + TargetVectors.FactoryFn? targets = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearObject( + nearObject: nearObject, + groupBy: groupBy, + certainty: certainty, + distance: distance, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + targets: targets, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.NearText.cs b/src/Weaviate.Client/Typed/TypedQueryClient.NearText.cs new file mode 100644 index 00000000..1c2b5350 --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedQueryClient.NearText.cs @@ -0,0 +1,306 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedQueryClient +{ + /// + /// Performs a near-text search using text embeddings. + /// + /// The text to search for (single or multiple strings). + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Move the query vector towards these concepts. + /// Move the query vector away from these concepts. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Filters to apply to the search. + /// Re-ranking configuration. + /// Target vector configuration for named vectors. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public async Task>> NearText( + AutoArray text, + float? certainty = null, + float? distance = null, + Move? moveTo = null, + Move? moveAway = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearText( + text: text, + certainty: certainty, + distance: distance, + moveTo: moveTo, + moveAway: moveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-text search with group-by aggregation. + /// + /// The text to search for (single or multiple strings). + /// Group-by configuration. + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Move the query vector towards these concepts. + /// Move the query vector away from these concepts. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Filters to apply to the search. + /// Re-ranking configuration. + /// Target vector configuration for named vectors. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public async Task> NearText( + AutoArray text, + GroupByRequest groupBy, + float? certainty = null, + float? distance = null, + Move? moveTo = null, + Move? moveAway = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Filter? filters = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearText( + text: text, + groupBy: groupBy, + certainty: certainty, + distance: distance, + moveTo: moveTo, + moveAway: moveAway, + limit: limit, + offset: offset, + autoLimit: autoLimit, + filters: filters, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-text search using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public async Task>> NearText( + NearTextInput input, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearText( + _ => input, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-text search with group-by using a NearTextInput record. + /// + /// Near-text input containing query text, target vectors, certainty, distance, and move parameters. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public async Task> NearText( + NearTextInput input, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearText( + _ => input, + groupBy, + filters: filters, + limit: limit, + offset: offset, + autoLimit: autoLimit, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-text search using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public Task>> NearText( + NearTextInput.FactoryFn query, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + filters, + limit, + offset, + autoLimit, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Performs a near-text search with group-by using a lambda builder for NearTextInput. + /// + /// Lambda builder for creating NearTextInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum number of results to return. + /// Number of results to skip. + /// Automatic result cutoff threshold. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public Task> NearText( + NearTextInput.FactoryFn query, + GroupByRequest groupBy, + Filter? filters = null, + uint? limit = null, + uint? offset = null, + uint? autoLimit = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearText( + query(VectorInputBuilderFactories.CreateNearTextBuilder()), + groupBy, + filters, + limit, + offset, + autoLimit, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); +} diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.NearVector.cs b/src/Weaviate.Client/Typed/TypedQueryClient.NearVector.cs new file mode 100644 index 00000000..5d9d123e --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedQueryClient.NearVector.cs @@ -0,0 +1,389 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedQueryClient +{ + /// + /// Performs a near-vector search using vector embeddings. + /// + /// The vector to search near. + /// Filters to apply to the search. + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public async Task>> NearVector( + VectorSearchInput vectors, + Filter? filters = null, + float? certainty = null, + float? distance = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearVector( + vectors: vectors, + filters: filters, + certainty: certainty, + distance: distance, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-vector search with group-by aggregation. + /// + /// The vector to search near. + /// Group-by configuration. + /// Filters to apply to the search. + /// Maximum distance threshold. + /// Minimum certainty threshold (0-1). + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public async Task> NearVector( + VectorSearchInput vectors, + GroupByRequest groupBy, + Filter? filters = null, + float? distance = null, + float? certainty = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.NearVector( + vectors: vectors, + groupBy: groupBy, + filters: filters, + distance: distance, + certainty: certainty, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Performs a near-vector search using a lambda to build vectors. + /// + /// Lambda function to build the vectors. + /// Filters to apply to the search. + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public Task>> NearVector( + VectorSearchInput.FactoryFn vectors, + Filter? filters = null, + float? certainty = null, + float? distance = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(new VectorSearchInput.Builder()), + filters, + certainty, + distance, + autoLimit, + limit, + offset, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Performs a near-vector search with group-by aggregation using a lambda to build vectors. + /// + /// Lambda function to build the vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Minimum certainty threshold (0-1). + /// Maximum distance threshold. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public Task> NearVector( + VectorSearchInput.FactoryFn vectors, + GroupByRequest groupBy, + Filter? filters = null, + float? certainty = null, + float? distance = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(new VectorSearchInput.Builder()), + groupBy, + filters, + distance, + certainty, + autoLimit, + limit, + offset, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Performs a near-vector search using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public Task>> NearVector( + NearVectorInput input, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors: input.Vector, + filters: filters, + certainty: input.Certainty, + distance: input.Distance, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Performs a near-vector search with group-by using a NearVectorInput record. + /// + /// Near-vector input containing vector, certainty, and distance. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public Task> NearVector( + NearVectorInput input, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors: input.Vector, + groupBy: groupBy, + filters: filters, + certainty: input.Certainty, + distance: input.Distance, + autoLimit: autoLimit, + limit: limit, + offset: offset, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + + /// + /// Performs a near-vector search using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the search results. + public Task>> NearVector( + NearVectorInput.FactoryFn vectors, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + filters, + autoLimit, + limit, + offset, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); + + /// + /// Performs a near-vector search with group-by using a lambda builder for NearVectorInput. + /// + /// Lambda builder for creating NearVectorInput with target vectors. + /// Group-by configuration. + /// Filters to apply to the search. + /// Automatic result cutoff threshold. + /// Maximum number of results to return. + /// Number of results to skip. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public Task> NearVector( + NearVectorInput.FactoryFn vectors, + GroupByRequest groupBy, + Filter? filters = null, + uint? autoLimit = null, + uint? limit = null, + uint? offset = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) => + NearVector( + vectors(VectorInputBuilderFactories.CreateNearVectorBuilder()), + groupBy, + filters, + autoLimit, + limit, + offset, + rerank, + returnProperties, + returnReferences, + returnMetadata, + includeVectors, + cancellationToken + ); +} diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.Objects.cs b/src/Weaviate.Client/Typed/TypedQueryClient.Objects.cs new file mode 100644 index 00000000..139dc1df --- /dev/null +++ b/src/Weaviate.Client/Typed/TypedQueryClient.Objects.cs @@ -0,0 +1,169 @@ +using Weaviate.Client.Models; +using Weaviate.Client.Models.Typed; + +namespace Weaviate.Client.Typed; + +public partial class TypedQueryClient +{ + /// + /// Fetches objects with group-by aggregation. + /// + /// Group-by configuration. + /// Cursor for pagination. + /// Maximum number of objects to return. + /// Filters to apply to the query. + /// Sorting configuration. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed grouped result. + public async Task> FetchObjects( + GroupByRequest groupBy, + Guid? after = null, + uint? limit = null, + Filter? filters = null, + AutoArray? sort = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.FetchObjects( + groupBy: groupBy, + after: after, + limit: limit, + filters: filters, + sort: sort, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Fetches objects from the collection. + /// + /// Cursor for pagination. + /// Maximum number of objects to return. + /// Number of results to skip. + /// Filters to apply to the query. + /// Sorting configuration. + /// Re-ranking configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the fetched objects. + public async Task>> FetchObjects( + Guid? after = null, + uint? limit = null, + uint? offset = null, + Filter? filters = null, + AutoArray? sort = null, + Rerank? rerank = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.FetchObjects( + after: after, + limit: limit, + offset: offset, + filters: filters, + sort: sort, + rerank: rerank, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } + + /// + /// Fetches a single object by its ID. + /// + /// The UUID of the object to fetch. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed object, or null if not found. + public async Task?> FetchObjectByID( + Guid uuid, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.FetchObjectByID( + id: uuid, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result?.ToTyped(); + } + + /// + /// Fetches multiple objects by their IDs. + /// + /// The UUIDs of the objects to fetch. + /// Maximum number of objects to return. + /// Re-ranking configuration. + /// Additional filters to apply. + /// Sorting configuration. + /// Properties to return in the response. + /// Cross-references to return. + /// Metadata to include in the response. + /// Vector configuration for returned objects. + /// Cancellation token. + /// A strongly-typed result containing the fetched objects. + public async Task>> FetchObjectsByIDs( + HashSet uuids, + uint? limit = null, + Rerank? rerank = null, + Filter? filters = null, + AutoArray? sort = null, + AutoArray? returnProperties = null, + IList? returnReferences = null, + MetadataQuery? returnMetadata = null, + VectorQuery? includeVectors = null, + CancellationToken cancellationToken = default + ) + { + var result = await _queryClient.FetchObjectsByIDs( + ids: uuids, + limit: limit, + rerank: rerank, + filters: filters, + sort: sort, + returnProperties: returnProperties, + returnReferences: returnReferences, + returnMetadata: returnMetadata, + includeVectors: includeVectors, + cancellationToken: cancellationToken + ); + return result.ToTyped(); + } +} diff --git a/src/Weaviate.Client/Typed/TypedQueryClient.cs b/src/Weaviate.Client/Typed/TypedQueryClient.cs index 96995fbb..a72907a4 100644 --- a/src/Weaviate.Client/Typed/TypedQueryClient.cs +++ b/src/Weaviate.Client/Typed/TypedQueryClient.cs @@ -1,6 +1,3 @@ -using Weaviate.Client.Models; -using Weaviate.Client.Models.Typed; - namespace Weaviate.Client.Typed; /// @@ -8,7 +5,7 @@ namespace Weaviate.Client.Typed; /// All query methods return WeaviateObject<T> instead of untyped WeaviateObject. /// /// The C# type to deserialize object properties into. -public class TypedQueryClient +public partial class TypedQueryClient where T : class, new() { private readonly QueryClient _queryClient; @@ -23,1116 +20,4 @@ public TypedQueryClient(QueryClient queryClient) _queryClient = queryClient; } - - #region Objects - - /// - /// Fetches objects with group-by aggregation. - /// - /// Group-by configuration. - /// Maximum number of objects to return. - /// Filters to apply to the query. - /// Sorting configuration. - /// Re-ranking configuration. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> FetchObjects( - GroupByRequest groupBy, - uint? limit = null, - Filter? filters = null, - AutoArray? sort = null, - Rerank? rerank = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.FetchObjects( - groupBy: groupBy, - limit: limit, - filters: filters, - sort: sort, - rerank: rerank, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Fetches objects from the collection. - /// - /// Cursor for pagination. - /// Maximum number of objects to return. - /// Filters to apply to the query. - /// Sorting configuration. - /// Re-ranking configuration. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the fetched objects. - public async Task>> FetchObjects( - Guid? after = null, - uint? limit = null, - Filter? filters = null, - AutoArray? sort = null, - Rerank? rerank = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.FetchObjects( - limit: limit, - filters: filters, - sort: sort, - rerank: rerank, - after: after, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Fetches a single object by its ID. - /// - /// The UUID of the object to fetch. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed object, or null if not found. - public async Task?> FetchObjectByID( - Guid uuid, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.FetchObjectByID( - id: uuid, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result?.ToTyped(); - } - - /// - /// Fetches multiple objects by their IDs. - /// - /// The UUIDs of the objects to fetch. - /// Maximum number of objects to return. - /// Re-ranking configuration. - /// Additional filters to apply. - /// Sorting configuration. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the fetched objects. - public async Task>> FetchObjectsByIDs( - HashSet uuids, - uint? limit = null, - Rerank? rerank = null, - Filter? filters = null, - AutoArray? sort = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.FetchObjectsByIDs( - ids: uuids, - limit: limit, - rerank: rerank, - filters: filters, - sort: sort, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - #endregion - - #region Search - - /// - /// Performs a near-text search using text embeddings. - /// - /// The text to search for (single or multiple strings). - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Move the query vector towards these concepts. - /// Move the query vector away from these concepts. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result cutoff threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> NearText( - AutoArray text, - float? certainty = null, - float? distance = null, - Move? moveTo = null, - Move? moveAway = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearText( - text: text, - certainty: certainty, - distance: distance, - moveTo: moveTo, - moveAway: moveAway, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-text search with group-by aggregation. - /// - /// The text to search for (single or multiple strings). - /// Group-by configuration. - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Move the query vector towards these concepts. - /// Move the query vector away from these concepts. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result cutoff threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> NearText( - AutoArray text, - GroupByRequest groupBy, - float? certainty = null, - float? distance = null, - Move? moveTo = null, - Move? moveAway = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearText( - text: text, - groupBy: groupBy, - certainty: certainty, - distance: distance, - moveTo: moveTo, - moveAway: moveAway, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-vector search using vector embeddings. - /// - /// The vector to search near. - /// Filters to apply to the search. - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Automatic result cutoff threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Target vector configuration for named vectors. - /// Re-ranking configuration. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> NearVector( - Vectors vector, - Filter? filters = null, - float? certainty = null, - float? distance = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - TargetVectors? targetVector = null, - Rerank? rerank = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearVector( - vector: vector, - filters: filters, - certainty: certainty, - distance: distance, - autoLimit: autoLimit, - limit: limit, - offset: offset, - targetVector: targetVector, - rerank: rerank, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-vector search with group-by aggregation. - /// - /// The vector to search near. - /// Group-by configuration. - /// Filters to apply to the search. - /// Maximum distance threshold. - /// Minimum certainty threshold (0-1). - /// Automatic result cutoff threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Target vector configuration for named vectors. - /// Re-ranking configuration. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> NearVector( - Vectors vector, - GroupByRequest groupBy, - Filter? filters = null, - float? distance = null, - float? certainty = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - TargetVectors? targetVector = null, - Rerank? rerank = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearVector( - vector: vector, - groupBy: groupBy, - filters: filters, - distance: distance, - certainty: certainty, - autoLimit: autoLimit, - limit: limit, - offset: offset, - targetVector: targetVector, - rerank: rerank, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a BM25 keyword search with group-by aggregation. - /// - /// The search query string. - /// Group-by configuration. - /// Fields to search in. - /// Filters to apply to the search. - /// Automatic result cutoff threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Re-ranking configuration. - /// Consistency level for the query. - /// Properties to return in the response. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cross-references to return. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> BM25( - string query, - GroupByRequest groupBy, - string[]? searchFields = null, - Filter? filters = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - Rerank? rerank = null, - ConsistencyLevels? consistencyLevel = null, - AutoArray? returnProperties = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - IList? returnReferences = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.BM25( - query: query, - groupBy: groupBy, - searchFields: searchFields, - filters: filters, - autoLimit: autoLimit, - limit: limit, - offset: offset, - rerank: rerank, - consistencyLevel: consistencyLevel, - returnProperties: returnProperties, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - returnReferences: returnReferences, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a BM25 keyword search. - /// - /// The search query string. - /// Fields to search in. - /// Filters to apply to the search. - /// Automatic result cutoff threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Re-ranking configuration. - /// Consistency level for the query. - /// Properties to return in the response. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cross-references to return. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> BM25( - string query, - string[]? searchFields = null, - Filter? filters = null, - uint? autoLimit = null, - uint? limit = null, - uint? offset = null, - Rerank? rerank = null, - ConsistencyLevels? consistencyLevel = null, - AutoArray? returnProperties = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - IList? returnReferences = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.BM25( - query: query, - searchFields: searchFields, - filters: filters, - autoLimit: autoLimit, - limit: limit, - offset: offset, - rerank: rerank, - consistencyLevel: consistencyLevel, - returnProperties: returnProperties, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - returnReferences: returnReferences, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a hybrid search combining keyword and vector search. - /// - /// The search query string. - /// The vectors to search near. - /// Balance between keyword and vector search (0=BM25, 1=vector). - /// Properties to search in. - /// Fusion algorithm for combining results. - /// Maximum vector distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Operator for BM25 search terms. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> Hybrid( - string? query, - Vectors vectors, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.Hybrid( - query: query, - vectors: vectors, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a hybrid search combining keyword and vector search. - /// - /// The search query string. - /// The vector input (can be Vectors, HybridNearVector, or HybridNearText). - /// Balance between keyword and vector search (0=BM25, 1=vector). - /// Properties to search in. - /// Fusion algorithm for combining results. - /// Maximum vector distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Operator for BM25 search terms. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> Hybrid( - string? query, - IHybridVectorInput? vectors = null, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.Hybrid( - query: query, - vectors: vectors, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a hybrid search with group-by aggregation. - /// - /// The search query string. - /// Group-by configuration. - /// The vectors to search near. - /// Balance between keyword and vector search (0=BM25, 1=vector). - /// Properties to search in. - /// Fusion algorithm for combining results. - /// Maximum vector distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Operator for BM25 search terms. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> Hybrid( - string? query, - GroupByRequest groupBy, - Vectors vectors, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.Hybrid( - query: query, - groupBy: groupBy, - vectors: vectors, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a hybrid search with group-by aggregation. - /// - /// The search query string. - /// Group-by configuration. - /// The vector input (can be Vectors, HybridNearVector, or HybridNearText). - /// Balance between keyword and vector search (0=BM25, 1=vector). - /// Properties to search in. - /// Fusion algorithm for combining results. - /// Maximum vector distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Operator for BM25 search terms. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> Hybrid( - string? query, - GroupByRequest groupBy, - IHybridVectorInput? vectors = null, - float? alpha = null, - string[]? queryProperties = null, - HybridFusion? fusionType = null, - float? maxVectorDistance = null, - uint? limit = null, - uint? offset = null, - BM25Operator? bm25Operator = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.Hybrid( - query: query, - groupBy: groupBy, - vectors: vectors, - alpha: alpha, - queryProperties: queryProperties, - fusionType: fusionType, - maxVectorDistance: maxVectorDistance, - limit: limit, - offset: offset, - bm25Operator: bm25Operator, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-object search using another object as reference. - /// - /// The ID of the object to search near. - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> NearObject( - Guid nearObject, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearObject( - nearObject: nearObject, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-object search with group-by aggregation. - /// - /// The ID of the object to search near. - /// Group-by configuration. - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> NearObject( - Guid nearObject, - GroupByRequest groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearObject( - nearObject: nearObject, - groupBy: groupBy, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-image search using image embeddings. - /// - /// The image data as a byte array. - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> NearImage( - byte[] nearImage, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearImage( - nearImage: nearImage, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-image search with group-by aggregation. - /// - /// The image data as a byte array. - /// Group-by configuration. - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> NearImage( - byte[] nearImage, - GroupByRequest groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearImage( - nearImage: nearImage, - groupBy: groupBy, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-media search using media embeddings (image, video, audio, etc.). - /// - /// The media data as a byte array. - /// The type of media (image, video, audio, etc.). - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed result containing the search results. - public async Task>> NearMedia( - byte[] media, - NearMediaType mediaType, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearMedia( - media: media, - mediaType: mediaType, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - /// - /// Performs a near-media search with group-by aggregation. - /// - /// The media data as a byte array. - /// The type of media (image, video, audio, etc.). - /// Group-by configuration. - /// Minimum certainty threshold (0-1). - /// Maximum distance threshold. - /// Maximum number of results to return. - /// Number of results to skip. - /// Automatic result limit threshold. - /// Filters to apply to the search. - /// Re-ranking configuration. - /// Target vector configuration for named vectors. - /// Properties to return in the response. - /// Cross-references to return. - /// Metadata to include in the response. - /// Vector configuration for returned objects. - /// Cancellation token. - /// A strongly-typed grouped result. - public async Task> NearMedia( - byte[] media, - NearMediaType mediaType, - GroupByRequest groupBy, - double? certainty = null, - double? distance = null, - uint? limit = null, - uint? offset = null, - uint? autoLimit = null, - Filter? filters = null, - Rerank? rerank = null, - TargetVectors? targetVector = null, - AutoArray? returnProperties = null, - IList? returnReferences = null, - MetadataQuery? returnMetadata = null, - VectorQuery? includeVectors = null, - CancellationToken cancellationToken = default - ) - { - var result = await _queryClient.NearMedia( - media: media, - mediaType: mediaType, - groupBy: groupBy, - certainty: certainty, - distance: distance, - limit: limit, - offset: offset, - autoLimit: autoLimit, - filters: filters, - rerank: rerank, - targetVector: targetVector, - returnProperties: returnProperties, - returnReferences: returnReferences, - returnMetadata: returnMetadata, - includeVectors: includeVectors, - cancellationToken: cancellationToken - ); - return result.ToTyped(); - } - - #endregion } diff --git a/src/Weaviate.Client/Weaviate.Client.csproj b/src/Weaviate.Client/Weaviate.Client.csproj index e5c6ffe9..53070deb 100644 --- a/src/Weaviate.Client/Weaviate.Client.csproj +++ b/src/Weaviate.Client/Weaviate.Client.csproj @@ -6,8 +6,11 @@ latest enable enable + + ENABLE_INTERNAL_TESTS + diff --git a/src/Weaviate.Client/WeaviateClient.cs b/src/Weaviate.Client/WeaviateClient.cs index 984b787a..0b8da7fd 100644 --- a/src/Weaviate.Client/WeaviateClient.cs +++ b/src/Weaviate.Client/WeaviateClient.cs @@ -1,11 +1,8 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Weaviate.Client.Grpc; using Weaviate.Client.Rest; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Weaviate.Client.Tests")] - namespace Weaviate.Client; public partial class WeaviateClient : IDisposable diff --git a/src/Weaviate.Client/gRPC/Aggregate.cs b/src/Weaviate.Client/gRPC/Aggregate.cs index 6c0fc1ea..36830711 100644 --- a/src/Weaviate.Client/gRPC/Aggregate.cs +++ b/src/Weaviate.Client/gRPC/Aggregate.cs @@ -5,7 +5,7 @@ namespace Weaviate.Client.Grpc; internal partial class WeaviateGrpcClient { - private Grpc.Protobuf.V1.AggregateRequest BaseAggregateRequest( + private static Grpc.Protobuf.V1.AggregateRequest BaseAggregateRequest( string collection, Filter? filter = null, Aggregate.GroupBy? groupBy = null, @@ -199,51 +199,26 @@ internal async Task AggregateNearText( metrics ); - request.NearText = new() { Query = { query }, Targets = targetVector ?? [] }; - - if (certainty.HasValue) - { - request.NearText.Certainty = certainty.Value; - } - if (distance.HasValue) - { - request.NearText.Distance = distance.Value; - } - if (moveTo is not null) - { - var uuids = moveTo.Objects is null ? [] : moveTo.Objects.Select(x => x.ToString()); - var concepts = moveTo.Concepts is null ? [] : moveTo.Concepts; - request.NearText.MoveTo = new NearTextSearch.Types.Move - { - Uuids = { uuids }, - Concepts = { concepts }, - Force = moveTo.Force, - }; - } - if (moveAway is not null) - { - var uuids = moveAway.Objects is null ? [] : moveAway.Objects.Select(x => x.ToString()); - var concepts = moveAway.Concepts is null ? [] : moveAway.Concepts; - request.NearText.MoveAway = new NearTextSearch.Types.Move - { - Uuids = { uuids }, - Concepts = { concepts }, - Force = moveAway.Force, - }; - } + request.NearText = BuildNearText( + query, + distance, + certainty, + moveTo, + moveAway, + targetVector + ); return await Aggregate(request, cancellationToken); } internal async Task AggregateNearVector( string collection, - Models.NearVectorInput vector, + VectorSearchInput vectors, double? certainty, double? distance, uint? limit, Filter? filter, Aggregate.GroupBy? groupBy, - TargetVectors? targetVector, bool totalCount, string? tenant, IEnumerable? metrics, @@ -260,7 +235,7 @@ internal async Task AggregateNearVector( metrics ); - request.NearVector = BuildNearVector(vector, certainty, distance, targetVector); + request.NearVector = BuildNearVector(vectors, certainty, distance); return await Aggregate(request, cancellationToken); } @@ -269,10 +244,9 @@ internal async Task AggregateHybrid( string collection, string? query, float alpha, - Models.Vectors? vectors, + HybridVectorInput? vectors, string[]? queryProperties, BM25Operator? bm25Operator, - string? targetVector, float? maxVectorDistance, Filter? filter, Aggregate.GroupBy? groupBy, @@ -293,40 +267,16 @@ internal async Task AggregateHybrid( metrics ); - request.Hybrid = new() { Query = query, Alpha = alpha }; - - if (queryProperties is not null && queryProperties.Length > 0) - { - request.Hybrid.Properties.AddRange(queryProperties); - } - - var (targets, _, vector) = BuildTargetVector( - targetVector is null ? null : [targetVector], - vectors?.Values + request.Hybrid = BuildHybrid( + query, + alpha, + vectors, + queryProperties, + null, + maxVectorDistance, + bm25Operator ); - request.Hybrid.Vectors.AddRange(vector ?? []); - request.Hybrid.Targets = targets; - - if (maxVectorDistance.HasValue) - { - request.Hybrid.VectorDistance = maxVectorDistance.Value; - } - - if (bm25Operator != null) - { - request.Hybrid.Bm25SearchOperator = new() - { - Operator = bm25Operator switch - { - BM25Operator.And => Protobuf.V1.SearchOperatorOptions.Types.Operator.And, - BM25Operator.Or => Protobuf.V1.SearchOperatorOptions.Types.Operator.Or, - _ => Protobuf.V1.SearchOperatorOptions.Types.Operator.Unspecified, - }, - MinimumOrTokensMatch = (bm25Operator as BM25Operator.Or)?.MinimumMatch ?? 1, - }; - } - return await Aggregate(request, cancellationToken); } diff --git a/src/Weaviate.Client/gRPC/Extensions.cs b/src/Weaviate.Client/gRPC/Extensions.cs index c1546f49..6e2468dc 100644 --- a/src/Weaviate.Client/gRPC/Extensions.cs +++ b/src/Weaviate.Client/gRPC/Extensions.cs @@ -27,21 +27,30 @@ internal partial class Targets { private static Targets ToGrpcTargets(Client.Models.TargetVectors targetVectors) { - List targets = new( - (targetVectors.Weights?.Count ?? 0) == 0 ? targetVectors.Targets : [] - ); - + List targets; List weightsForTargets = new(); - foreach (var w in targetVectors.Weights ?? []) + + // Handle SimpleTargetVectors vs WeightedTargetVectors + if (targetVectors is Client.Models.WeightedTargetVectors weighted) { - foreach (var v in w.Value) + // For weighted targets, build weights list + targets = new(); + foreach (var w in weighted.Weights) { - weightsForTargets.Add( - new WeightsForTarget { Target = w.Key, Weight = Convert.ToSingle(v) } - ); - targets.Add(w.Key); + foreach (var v in w.Value) + { + weightsForTargets.Add( + new WeightsForTarget { Target = w.Key, Weight = Convert.ToSingle(v) } + ); + targets.Add(w.Key); + } } } + else + { + // For simple targets, just use the target names + targets = new(targetVectors.Targets); + } var grpcTargets = new Targets { diff --git a/src/Weaviate.Client/gRPC/Result.cs b/src/Weaviate.Client/gRPC/Result.cs index 46a83086..4496bc36 100644 --- a/src/Weaviate.Client/gRPC/Result.cs +++ b/src/Weaviate.Client/gRPC/Result.cs @@ -84,19 +84,12 @@ internal static Vectors BuildVectorsFromResult(Grpc.Protobuf.V1.MetadataResult m { if (metadataResult.VectorBytes != null && metadataResult.VectorBytes.Length > 0) { - return Vector.Create([.. metadataResult.VectorBytes.FromByteString()]); + return metadataResult.VectorBytes.FromByteString().ToArray(); } var vectors = metadataResult.Vectors; - var result = new Vectors(); - - foreach (var vector in vectors) - { - result.Add(vector.FromByteString()); - } - - return result; + return new Vectors(vectors.Select(vector => vector.FromByteString())); } internal static GroupByObject BuildGroupByObjectFromResult( diff --git a/src/Weaviate.Client/gRPC/Search.Builders.cs b/src/Weaviate.Client/gRPC/Search.Builders.cs index fe4de48b..64059ca7 100644 --- a/src/Weaviate.Client/gRPC/Search.Builders.cs +++ b/src/Weaviate.Client/gRPC/Search.Builders.cs @@ -453,58 +453,82 @@ private static V1.ConsistencyLevel MapConsistencyLevel(ConsistencyLevels value) }; } - private static ( + /// + /// Converts a NamedVector to gRPC V1.Vectors format. + /// + private static V1.Vectors ConvertToGrpcVector(NamedVector namedVector) + { + return new V1.Vectors + { + Name = namedVector.Name, + Type = namedVector.IsMultiVector + ? V1.Vectors.Types.VectorType.MultiFp32 + : V1.Vectors.Types.VectorType.SingleFp32, + VectorBytes = namedVector.ToByteString(), + }; + } + + internal static ( V1.Targets? targets, IList? vectorForTargets, IList? vectors - ) BuildTargetVector(TargetVectors? targetVector, IEnumerable? vector = null) + ) BuildTargetVector(TargetVectors? targetVector, IEnumerable? vector = null) { IList? vectorForTarget = null; IList? vectors = null; vector ??= []; - targetVector ??= vector - .Select(v => v.Name) - .Where(tv => string.IsNullOrEmpty(tv) is false) - .ToHashSet() - .Order() - .ToArray(); + // If no target vector specified, create one from vector names + targetVector ??= new SimpleTargetVectors( + [ + .. vector + .Select(v => v.Name) + .Where(tv => string.IsNullOrEmpty(tv) is false) + .ToHashSet() + .Order(), + ] + ); if (targetVector.Count() == 1 && vector.Count() == 1) { // If only one target vector is specified, use Vectors // This also covers the case where no target vector is specified and only one vector is provided // In this case, we assume the single provided vector is the target - vectors = - [ - .. vector - .Select(v => new V1.Vectors - { - Name = v.Name, - Type = v.IsMultiVector - ? V1.Vectors.Types.VectorType.MultiFp32 - : V1.Vectors.Types.VectorType.SingleFp32, - VectorBytes = v.ToByteString(), - }) - .OrderBy(v => v.Name), - ]; + vectors = [.. vector.Select(v => ConvertToGrpcVector(v)).OrderBy(v => v.Name)]; return (targetVector, vectorForTarget, vectors); } var vectorUniqueNames = vector.Select(v => v.Name).ToHashSet(); - var vectorInstances = vector.GroupBy(v => v.Name).ToDictionary(g => g.Key, g => g.ToList()); - var vectorInstanceWeights = targetVector - .GetVectorWithWeights() - .GroupBy(v => v.name) - .ToDictionary(g => g.Key, g => g.Select(w => w.weight).ToList()); - - // If multiple target vectors are specified, use VectorForTargets. - // If multiple vectors are specified, ensure they align with the - // weights provided in the target vector. - bool vectorNamesMatch = vectorUniqueNames.SetEquals(targetVector); - bool vectorsMatchTargets = vectorInstances.Count == vectorInstanceWeights.Count; + var vectorsByName = vector.GroupBy(v => v.Name).ToDictionary(g => g.Key, g => g.ToList()); + + // Get weights if targetVector is WeightedTargetVectors + Dictionary> vectorInstanceWeights; + if (targetVector is WeightedTargetVectors weighted) + { + vectorInstanceWeights = weighted + .GetTargetWithWeights() + .GroupBy(v => v.name) + .ToDictionary(g => g.Key, g => g.Select(w => (double?)w.weight).ToList()); + } + else + { + // For SimpleTargetVectors, no weights + vectorInstanceWeights = targetVector.Targets.ToDictionary( + t => t, + t => new List { null } + ); + } + + // Determine if we should use VectorForTargets: + // 1. Multiple distinct target names, OR + // 2. Multiple vectors for the same target (e.g., Sum with same name) + // 3. A combination method is specified (Sum, Average, Minimum, etc.) + bool vectorNamesMatch = vectorUniqueNames.SetEquals(targetVector.Targets); + bool vectorsMatchTargets = vectorsByName.Count == vectorInstanceWeights.Count; + bool hasCombinationMethod = targetVector.Combination != V1.CombinationMethod.Unspecified; + bool hasMultipleVectorsPerTarget = vectorsByName.Any(kvp => kvp.Value.Count > 1); bool weightsCheck = true; if ( @@ -513,16 +537,24 @@ .. vector ) { // Ensure all vectors are present and match the weights provided - bool allVectorsPresentAndMatchingWeights = vectorInstances.All(kv => - vectorInstanceWeights.TryGetValue(kv.Key, out var count) - && count.Count == kv.Value.Count + bool allVectorsPresentAndMatchingWeights = vectorsByName.All(kv => + vectorInstanceWeights.TryGetValue(kv.Key, out var weights) + && weights.Count == kv.Value.Count ); - bool vectorsUseWeights = vector.Count() == targetVector.GetVectorWithWeights().Count(); + bool vectorsUseWeights = + vector.Count() == vectorInstanceWeights.Sum(kvp => kvp.Value.Count); weightsCheck = allVectorsPresentAndMatchingWeights && vectorsUseWeights; } - if (targetVector.Count() > 1 && vectorNamesMatch && vectorsMatchTargets && weightsCheck) + // Use VectorForTargets when: + // - Multiple distinct targets with matching vectors, OR + // - Multiple vectors for the same target (e.g., Sum, or just multiple vectors with same name) + bool useVectorForTargets = + (targetVector.Count() > 1 && vectorNamesMatch && vectorsMatchTargets && weightsCheck) + || (hasMultipleVectorsPerTarget && vectorNamesMatch); + + if (useVectorForTargets) { vectorForTarget = [ @@ -530,18 +562,7 @@ .. vectorUniqueNames .Select(name => new V1.VectorForTarget() { Name = name, - Vectors = - { - vectorInstances[name] - .Select(v => new V1.Vectors - { - Name = v.Name, - Type = v.IsMultiVector - ? V1.Vectors.Types.VectorType.MultiFp32 - : V1.Vectors.Types.VectorType.SingleFp32, - VectorBytes = v.ToByteString(), - }), - }, + Vectors = { vectorsByName[name].Select(v => ConvertToGrpcVector(v)) }, }) .OrderBy(vft => vft.Name), ]; @@ -549,21 +570,57 @@ .. vectorUniqueNames return (targetVector, vectorForTarget, vectors); } - vectors = - [ - .. vector.Select(v => new V1.Vectors - { - Name = v.Name, - Type = v.IsMultiVector - ? V1.Vectors.Types.VectorType.MultiFp32 - : V1.Vectors.Types.VectorType.SingleFp32, - VectorBytes = v.ToByteString(), - }), - ]; + vectors = [.. vector.Select(v => ConvertToGrpcVector(v))]; return (targetVector, vectorForTarget, vectors); } + /// + /// Overload for VectorSearchInput which contains both vectors and target configuration. + /// + internal static ( + V1.Targets? targets, + IList? vectorForTargets, + IList? vectors + ) BuildTargetVector(VectorSearchInput? vectorSearchInput) + { + if (vectorSearchInput == null) + return (null, null, null); + + var namedVectors = vectorSearchInput.ToList(); + + // Create TargetVectors from VectorSearchInput configuration + TargetVectors targetVector; + if (vectorSearchInput.Weights != null && vectorSearchInput.Weights.Count > 0) + { + // Weighted targets + targetVector = new WeightedTargetVectors( + vectorSearchInput.Targets ?? [.. vectorSearchInput.Vectors.Keys], + vectorSearchInput.Combination, + vectorSearchInput.Weights + ); + } + else if (vectorSearchInput.Targets != null) + { + // Simple targets with combination method + targetVector = new SimpleTargetVectors( + vectorSearchInput.Targets, + vectorSearchInput.Combination + ); + } + else + { + // Default: use vector names as targets + targetVector = new SimpleTargetVectors( + [.. vectorSearchInput.Vectors.Keys], + vectorSearchInput.Combination + ); + } + + // Reuse the existing BuildTargetVector logic + return BuildTargetVector(targetVector, namedVectors); + } + private static V1.NearTextSearch BuildNearText( string[] query, double? distance, @@ -573,7 +630,11 @@ private static V1.NearTextSearch BuildNearText( TargetVectors? targetVector = null ) { - var nearText = new V1.NearTextSearch { Query = { query }, Targets = targetVector ?? [] }; + var nearText = new V1.NearTextSearch + { + Query = { query }, + Targets = targetVector ?? new V1.Targets(), + }; if (moveTo is not null) { @@ -613,10 +674,9 @@ private static V1.NearTextSearch BuildNearText( } private static V1.NearVector BuildNearVector( - NearVectorInput vector, + VectorSearchInput vectors, double? certainty, - double? distance, - TargetVectors? targetVector + double? distance ) { V1.NearVector nearVector = new(); @@ -631,7 +691,7 @@ private static V1.NearVector BuildNearVector( nearVector.Certainty = certainty.Value; } - var (targets, vectorForTarget, vectors) = BuildTargetVector(targetVector, vector); + var (targets, vectorForTarget, vectorsLocal) = BuildTargetVector(vectors); if (targets is not null) { @@ -641,9 +701,9 @@ private static V1.NearVector BuildNearVector( { nearVector.VectorForTargets.Add(vectorForTarget); } - else if (vectors is not null) + else if (vectorsLocal is not null) { - nearVector.Vectors.Add(vectors); + nearVector.Vectors.Add(vectorsLocal); } return nearVector; @@ -677,25 +737,21 @@ private static void BuildBM25( } } - private static void BuildHybrid( - V1.SearchRequest request, + private static V1.Hybrid BuildHybrid( string? query = null, float? alpha = null, - Models.Vectors? vector = null, - HybridNearVector? nearVector = null, - HybridNearText? nearText = null, + HybridVectorInput? vectors = null, string[]? queryProperties = null, HybridFusion? fusionType = null, float? maxVectorDistance = null, - BM25Operator? bm25Operator = null, - TargetVectors? targetVector = null + BM25Operator? bm25Operator = null ) { - request.HybridSearch = new V1.Hybrid(); + var hybrid = new V1.Hybrid(); if (!string.IsNullOrEmpty(query)) { - request.HybridSearch.Query = query; + hybrid.Query = query; } else { @@ -705,71 +761,73 @@ private static void BuildHybrid( alpha ??= 0.7f; // Default alpha if not provided - request.HybridSearch.Alpha = alpha.Value; - - if (vector is not null && nearText is null && nearVector is null) - { - var (targets, vfts, vectors) = BuildTargetVector(targetVector, vector.Values); - - if (vfts is not null) - { - nearVector = new HybridNearVector( - vector, - Certainty: null, - Distance: null, - targetVector: targetVector - ); - vector = null; // Clear vector to avoid duplication - } - else if (vectors is not null) - { - request.HybridSearch.Vectors.Add(vectors); - request.HybridSearch.Targets = targets; - } - } + hybrid.Alpha = alpha.Value; - if (vector is null && nearText is not null && nearVector is null) + // Pattern match on HybridVectorInput to build the appropriate search type + if (vectors != null) { - request.HybridSearch.NearText = BuildNearText( - [nearText.Query], - nearText.Distance, - nearText.Certainty, - nearText.MoveTo, - nearText.MoveAway, - targetVector - ); - - request.HybridSearch.Targets = request.HybridSearch.NearText.Targets; - } - - if ( - vector is null - && nearText is null - && nearVector is not null - && nearVector.Vector is not null - ) - { - request.HybridSearch.NearVector = BuildNearVector( - nearVector.Vector, - nearVector.Certainty, - nearVector.Distance, - targetVector + vectors.Match( + onVectorSearch: vectorSearchInput => + { + // Build target vectors from VectorSearchInput (includes combination method) + var (targets, vfts, vectorsGrpc) = BuildTargetVector(vectorSearchInput); + // Set targets for hybrid search + if (targets is not null) + { + hybrid.Targets = targets; + } + // For Hybrid, vectors go in Vectors field (not VectorForTargets) + // If VectorForTargets was computed, flatten them into Vectors + if (vfts is not null) + { + foreach (var vft in vfts) + { + hybrid.Vectors.Add(vft.Vectors); + } + } + else if (vectorsGrpc is not null) + { + hybrid.Vectors.Add(vectorsGrpc); + } + return null; + }, + onNearText: nearText => + { + hybrid.NearText = BuildNearText( + nearText.Query.ToArray(), + nearText.Distance, + nearText.Certainty, + nearText.MoveTo, + nearText.MoveAway, + nearText.TargetVectors + ); + // Move targets to Hybrid message (matching Python client behavior) + hybrid.Targets = hybrid.NearText.Targets; + hybrid.NearText.Targets = new V1.Targets(); + return null; + }, + onNearVector: nearVector => + { + hybrid.NearVector = BuildNearVector( + nearVector.Vector, + nearVector.Certainty, + nearVector.Distance + ); + // Move targets to Hybrid message (matching Python client behavior) + hybrid.Targets = hybrid.NearVector.Targets; + hybrid.NearVector.Targets = new V1.Targets(); + return null; + } ); - request.HybridSearch.Targets = request.HybridSearch.NearVector.Targets; - } - - if (vector is null && nearText is null && nearVector is null && targetVector is not null) - { - request.HybridSearch.Targets = targetVector ?? []; } if (queryProperties is not null) { - request.HybridSearch.Properties.AddRange(queryProperties); + hybrid.Properties.AddRange(queryProperties); } if (fusionType.HasValue) { - request.HybridSearch.FusionType = fusionType switch + hybrid.FusionType = fusionType switch { HybridFusion.Ranked => V1.Hybrid.Types.FusionType.Ranked, HybridFusion.RelativeScore => V1.Hybrid.Types.FusionType.RelativeScore, @@ -778,11 +836,11 @@ vector is null } if (maxVectorDistance.HasValue) { - request.HybridSearch.VectorDistance = maxVectorDistance.Value; + hybrid.VectorDistance = maxVectorDistance.Value; } if (bm25Operator != null) { - request.HybridSearch.Bm25SearchOperator = new() + hybrid.Bm25SearchOperator = new() { Operator = bm25Operator switch { @@ -793,28 +851,31 @@ vector is null MinimumOrTokensMatch = (bm25Operator as BM25Operator.Or)?.MinimumMatch ?? 1, }; } + + return hybrid; } - private void BuildNearObject( - V1.SearchRequest request, + private static V1.NearObject BuildNearObject( Guid objectID, double? certainty, double? distance, TargetVectors? targetVector ) { - request.NearObject = new V1.NearObject { Id = objectID.ToString() }; + var nearObject = new V1.NearObject { Id = objectID.ToString() }; if (certainty.HasValue) { - request.NearObject.Certainty = certainty.Value; + nearObject.Certainty = certainty.Value; } if (distance.HasValue) { - request.NearObject.Distance = distance.Value; + nearObject.Distance = distance.Value; } - request.NearObject.Targets = targetVector ?? []; + nearObject.Targets = targetVector ?? new V1.Targets(); + + return nearObject; } } diff --git a/src/Weaviate.Client/gRPC/Search.cs b/src/Weaviate.Client/gRPC/Search.cs index e21f689a..62813b1c 100644 --- a/src/Weaviate.Client/gRPC/Search.cs +++ b/src/Weaviate.Client/gRPC/Search.cs @@ -71,11 +71,10 @@ internal partial class WeaviateGrpcClient internal async Task SearchNearVector( string collection, - NearVectorInput vector, + VectorSearchInput vectors, GroupByRequest? groupBy = null, float? distance = null, float? certainty = null, - TargetVectors? targetVector = null, uint? limit = null, uint? autoLimit = null, uint? offset = null, @@ -111,7 +110,7 @@ internal partial class WeaviateGrpcClient includeVectors: includeVectors ); - request.NearVector = BuildNearVector(vector, certainty, distance, targetVector); + request.NearVector = BuildNearVector(vectors, certainty, distance); return await Search(request, cancellationToken); } @@ -224,9 +223,7 @@ internal partial class WeaviateGrpcClient string collection, string? query = null, float? alpha = null, - Models.Vectors? vector = null, - HybridNearVector? nearVector = null, - HybridNearText? nearText = null, + HybridVectorInput? vectors = null, string[]? queryProperties = null, HybridFusion? fusionType = null, float? maxVectorDistance = null, @@ -239,7 +236,6 @@ internal partial class WeaviateGrpcClient Rerank? rerank = null, SinglePrompt? singlePrompt = null, GroupedTask? groupedTask = null, - TargetVectors? targetVector = null, string? tenant = null, ConsistencyLevels? consistencyLevel = null, AutoArray? returnProperties = null, @@ -249,13 +245,10 @@ internal partial class WeaviateGrpcClient CancellationToken cancellationToken = default ) { - if ( - !(vector is not null || nearVector is not null || nearText is not null) - && string.IsNullOrEmpty(query) - ) + if (vectors is null && string.IsNullOrEmpty(query)) { throw new ArgumentException( - "Either vector or query must be provided for hybrid search." + "Either vectors or query must be provided for hybrid search." ); } @@ -278,18 +271,14 @@ internal partial class WeaviateGrpcClient includeVectors: includeVectors ); - BuildHybrid( - request, + request.HybridSearch = BuildHybrid( query, alpha, - vector, - nearVector, - nearText, + vectors, queryProperties, fusionType, maxVectorDistance, - bm25Operator, - targetVector + bm25Operator ); return await Search(request, cancellationToken); @@ -337,7 +326,7 @@ internal partial class WeaviateGrpcClient includeVectors: includeVectors ); - BuildNearObject(request, objectID, certainty, distance, targetVector); + request.NearObject = BuildNearObject(objectID, certainty, distance, targetVector); return await Search(request, cancellationToken); } @@ -402,7 +391,7 @@ internal partial class WeaviateGrpcClient request.NearImage.Distance = distance.Value; } - request.NearImage.Targets = targetVector ?? []; + request.NearImage.Targets = targetVector ?? new V1.Targets(); break; case NearMediaType.Video: @@ -420,7 +409,7 @@ internal partial class WeaviateGrpcClient request.NearVideo.Distance = distance.Value; } - request.NearVideo.Targets = targetVector ?? []; + request.NearVideo.Targets = targetVector ?? new V1.Targets(); break; case NearMediaType.Audio: request.NearAudio = new V1.NearAudioSearch @@ -437,7 +426,7 @@ internal partial class WeaviateGrpcClient request.NearAudio.Distance = distance.Value; } - request.NearAudio.Targets = targetVector ?? []; + request.NearAudio.Targets = targetVector ?? new V1.Targets(); break; case NearMediaType.Depth: request.NearDepth = new V1.NearDepthSearch @@ -454,7 +443,7 @@ internal partial class WeaviateGrpcClient request.NearDepth.Distance = distance.Value; } - request.NearDepth.Targets = targetVector ?? []; + request.NearDepth.Targets = targetVector ?? new V1.Targets(); break; case NearMediaType.Thermal: request.NearThermal = new V1.NearThermalSearch @@ -471,7 +460,7 @@ internal partial class WeaviateGrpcClient request.NearThermal.Distance = distance.Value; } - request.NearThermal.Targets = targetVector ?? []; + request.NearThermal.Targets = targetVector ?? new V1.Targets(); break; case NearMediaType.IMU: request.NearImu = new V1.NearIMUSearch { Imu = Convert.ToBase64String(media) }; @@ -485,7 +474,7 @@ internal partial class WeaviateGrpcClient request.NearImu.Distance = distance.Value; } - request.NearImu.Targets = targetVector ?? []; + request.NearImu.Targets = targetVector ?? new V1.Targets(); break; default: throw new ArgumentException("Unsupported media type for near media search.");