diff --git a/.gitignore b/.gitignore index a7e0e36b1..46ff96555 100644 --- a/.gitignore +++ b/.gitignore @@ -265,6 +265,9 @@ __marimo__/ # C# code _includes/code/csharp/bin _includes/code/csharp/obj +_includes/code/csharp/quickstart/bin +_includes/code/csharp/quickstart/obj *.sln +# Exclude WCD backups tests/backups/ \ No newline at end of file diff --git a/_includes/code/automated-backup.py b/_includes/code/automated-backup.py new file mode 100644 index 000000000..21cc04f2d --- /dev/null +++ b/_includes/code/automated-backup.py @@ -0,0 +1,183 @@ +import weaviate +from weaviate.classes.init import Auth +from weaviate.classes.data import GeoCoordinate +import json +import os +from datetime import datetime + +# Custom JSON encoder to handle datetime and Weaviate-specific objects +class WeaviateEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime): + return obj.isoformat() + if isinstance(obj, GeoCoordinate): + return { + "latitude": obj.latitude, + "longitude": obj.longitude + } + # Handle any other non-serializable objects by converting to string + try: + return super().default(obj) + except TypeError: + return str(obj) + +# Configuration +wcd_url = os.environ["WEAVIATE_URL"] +wcd_api_key = os.environ["WEAVIATE_API_KEY"] +BASE_DIR = "/Users/ivandespot/dev/docs/tests/backups" +BACKUP_DIR = os.path.join(BASE_DIR, f"backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}") + +# Create backup directory +os.makedirs(BACKUP_DIR, exist_ok=True) + +# Connect to Weaviate Cloud +client = weaviate.connect_to_weaviate_cloud( + cluster_url=wcd_url, auth_credentials=Auth.api_key(wcd_api_key) +) + +try: + # Get all collections + collections = client.collections.list_all() + print(f"Found {len(collections)} collections to back up") + + backup_metadata = { + "timestamp": datetime.now().isoformat(), + "cluster_url": wcd_url, + "collections": [], + } + + # Back up each collection + for collection_name in collections: + print(f"\nBacking up collection: {collection_name}") + collection = client.collections.get(collection_name) + + # Get collection config (schema) + config = collection.config.get() + config_dict = { + "name": collection_name, + "description": config.description, + "properties": [ + { + "name": prop.name, + "data_type": prop.data_type.value, + "description": prop.description, + } + for prop in config.properties + ], + "vectorizer_config": str(config.vectorizer_config), + "vector_index_config": str(config.vector_index_config), + "generative_config": ( + str(config.generative_config) if config.generative_config else None + ), + "replication_config": ( + str(config.replication_config) if config.replication_config else None + ), + "multi_tenancy_config": ( + str(config.multi_tenancy_config) + if config.multi_tenancy_config + else None + ), + } + + # Check if multi-tenancy is enabled + is_multi_tenant = config.multi_tenancy_config and config.multi_tenancy_config.enabled + + # Save collection config + config_file = os.path.join(BACKUP_DIR, f"{collection_name}_config.json") + with open(config_file, "w") as f: + json.dump(config_dict, f, indent=2, cls=WeaviateEncoder) + + collection_metadata = { + "name": collection_name, + "config_file": f"{collection_name}_config.json", + "is_multi_tenant": is_multi_tenant, + "tenants": [] + } + + if is_multi_tenant: + # Get all tenants (returns list of tenant names as strings) + tenants = collection.tenants.get() + print(f" Found {len(tenants)} tenants") + + # Back up each tenant + for tenant_name in tenants: + print(f" Backing up tenant: {tenant_name}") + + # Get tenant-specific collection + tenant_collection = collection.with_tenant(tenant_name) + + # Export tenant objects + objects = [] + object_count = 0 + + try: + for item in tenant_collection.iterator(include_vector=True): + obj = { + "uuid": str(item.uuid), + "properties": item.properties, + "vector": item.vector, + } + objects.append(obj) + object_count += 1 + + if object_count % 1000 == 0: + print(f" Exported {object_count} objects...") + + # Save tenant objects + objects_file = os.path.join(BACKUP_DIR, f"{collection_name}_{tenant_name}_objects.json") + with open(objects_file, "w") as f: + json.dump(objects, f, indent=2, cls=WeaviateEncoder) + + print(f" ✓ Backed up {object_count} objects for tenant {tenant_name}") + + collection_metadata["tenants"].append({ + "tenant_name": tenant_name, + "object_count": object_count, + "objects_file": f"{collection_name}_{tenant_name}_objects.json" + }) + except Exception as e: + print(f" ⚠ Warning: Could not back up tenant {tenant_name}: {e}") + collection_metadata["tenants"].append({ + "tenant_name": tenant_name, + "object_count": 0, + "error": str(e) + }) + else: + # Non-multi-tenant collection - backup normally + objects = [] + object_count = 0 + + for item in collection.iterator(include_vector=True): + obj = { + "uuid": str(item.uuid), + "properties": item.properties, + "vector": item.vector, + } + objects.append(obj) + object_count += 1 + + if object_count % 1000 == 0: + print(f" Exported {object_count} objects...") + + # Save objects + objects_file = os.path.join(BACKUP_DIR, f"{collection_name}_objects.json") + with open(objects_file, "w") as f: + json.dump(objects, f, indent=2, cls=WeaviateEncoder) + + print(f" ✓ Backed up {object_count} objects") + + collection_metadata["object_count"] = object_count + collection_metadata["objects_file"] = f"{collection_name}_objects.json" + + backup_metadata["collections"].append(collection_metadata) + + # Save backup metadata + metadata_file = os.path.join(BACKUP_DIR, "backup_metadata.json") + with open(metadata_file, "w") as f: + json.dump(backup_metadata, f, indent=2, cls=WeaviateEncoder) + + print(f"\n✓ Backup completed successfully!") + print(f"Backup location: {BACKUP_DIR}") + +finally: + client.close() \ No newline at end of file diff --git a/_includes/code/connections/timeouts-cloud.mdx b/_includes/code/connections/timeouts-cloud.mdx index d72e7c815..89416c08f 100644 --- a/_includes/code/connections/timeouts-cloud.mdx +++ b/_includes/code/connections/timeouts-cloud.mdx @@ -32,7 +32,7 @@ import CSharpCode from "!!raw-loader!/_includes/code/csharp/ConnectionTest.cs"; language="java" /> - + - + - + v.Text2VecTransformers(), + index: new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.BQ(), + // highlight-end + } + ), + } + ); // END EnableBQ } [Fact] public async Task TestUpdateSchema() { + await client.Collections.Delete(COLLECTION_NAME); // Note: Updating quantization settings on an existing collection is not supported by Weaviate // and will result in an error, as noted in the Java test. This test demonstrates the syntax for attempting the update. - var collection = await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - Properties = [Property.Text("title")], - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }); + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + } + ); // START UpdateSchema await collection.Config.Update(c => { var vectorConfig = c.VectorConfig["default"]; - vectorConfig.VectorIndexConfig.UpdateHNSW(h => h.Quantizer = new VectorIndex.Quantizers.BQ()); + vectorConfig.VectorIndexConfig.UpdateHNSW(h => + h.Quantizer = new VectorIndex.Quantizers.BQ() + ); }); // END UpdateSchema } @@ -80,27 +79,30 @@ await collection.Config.Update(c => [Fact] public async Task TestBQWithOptions() { + await client.Collections.Delete(COLLECTION_NAME); // START BQWithOptions - await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - Properties = [Property.Text("title")], - VectorConfig = new VectorConfig( - "default", - new Vectorizer.Text2VecTransformers(), - // highlight-start - new VectorIndex.HNSW - { - VectorCacheMaxObjects = 100000, - Quantizer = new VectorIndex.Quantizers.BQ + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW { - Cache = true, - RescoreLimit = 200 + VectorCacheMaxObjects = 100000, + Quantizer = new VectorIndex.Quantizers.BQ + { + Cache = true, + RescoreLimit = 200, + }, } - } // highlight-end - ) - }); + ), + } + ); // END BQWithOptions } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ConfigurePQTest.cs b/_includes/code/csharp/ConfigurePQTest.cs index dbf92002f..a372fa455 100644 --- a/_includes/code/csharp/ConfigurePQTest.cs +++ b/_includes/code/csharp/ConfigurePQTest.cs @@ -1,13 +1,12 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; -using System.Text.Json; -using System.Net.Http; using System.Linq; -using System.Text.Json.Serialization; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -23,18 +22,19 @@ public async Task InitializeAsync() // START DownloadData using var httpClient = new HttpClient(); var responseBody = await httpClient.GetStringAsync( - "https://raw.githubusercontent.com/weaviate-tutorials/intro-workshop/main/data/jeopardy_1k.json"); + "https://raw.githubusercontent.com/weaviate-tutorials/intro-workshop/main/data/jeopardy_1k.json" + ); data = JsonSerializer.Deserialize>(responseBody); Console.WriteLine($"Data type: {data.GetType().Name}, Length: {data.Count}"); - Console.WriteLine(JsonSerializer.Serialize(data[1], new JsonSerializerOptions { WriteIndented = true })); + Console.WriteLine( + JsonSerializer.Serialize(data[1], new JsonSerializerOptions { WriteIndented = true }) + ); // END DownloadData // START ConnectCode - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + client = await Connect.Local(); var meta = await client.GetMeta(); Console.WriteLine($"Weaviate info: {meta.Version}"); @@ -59,42 +59,42 @@ private async Task BeforeEach() } } - // TODO[g-despot] Why is Encoder required? - // TODO[g-despot] Why are properties required? ERROR: didn't find a single property which is of type string or text and is not excluded from indexing [Fact] public async Task TestCollectionWithAutoPQ() { await BeforeEach(); // START CollectionWithAutoPQ - await client.Collections.Create(new CollectionConfig - { - Name = "Question", - VectorConfig = new VectorConfig( - "default", - new Vectorizer.Text2VecTransformers(), - new VectorIndex.HNSW - { - // highlight-start - Quantizer = new VectorIndex.Quantizers.PQ + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Question", + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW { - TrainingLimit = 50000, // Set the threshold to begin training - Encoder = new VectorIndex.Quantizers.PQ.EncoderConfig + // highlight-start + Quantizer = new VectorIndex.Quantizers.PQ { - Type = VectorIndex.Quantizers.EncoderType.Tile, - Distribution = VectorIndex.Quantizers.DistributionType.Normal, + TrainingLimit = 50000, // Set the threshold to begin training + Encoder = new VectorIndex.Quantizers.PQ.EncoderConfig + { + Type = VectorIndex.Quantizers.EncoderType.Tile, + Distribution = VectorIndex.Quantizers.DistributionType.Normal, + }, }, + // highlight-end } - // highlight-end - } - ), - Properties = - [ - Property.Text("question"), - Property.Text("answer"), - Property.Text("category") - ] - }); + ), + Properties = + [ + Property.Text("question"), + Property.Text("answer"), + Property.Text("category"), + ], + } + ); // END CollectionWithAutoPQ // Confirm that the collection has been created with the right settings @@ -111,18 +111,20 @@ public async Task TestUpdateSchemaWithPQ() await BeforeEach(); // START InitialSchema - await client.Collections.Create(new CollectionConfig - { - Name = "Question", - Description = "A Jeopardy! question", - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()), - Properties = - [ - Property.Text("question"), - Property.Text("answer"), - Property.Text("category") - ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Question", + Description = "A Jeopardy! question", + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + Properties = + [ + Property.Text("question"), + Property.Text("answer"), + Property.Text("category"), + ], + } + ); // END InitialSchema var collection = client.Collections.Use(COLLECTION_NAME); @@ -131,10 +133,11 @@ await client.Collections.Create(new CollectionConfig // START LoadData var objectList = data.Select(obj => new - { - question = obj.GetProperty("Question").GetString(), - answer = obj.GetProperty("Answer").GetString() - }).ToArray(); + { + question = obj.GetProperty("Question").GetString(), + answer = obj.GetProperty("Answer").GetString(), + }) + .ToArray(); await collection.Data.InsertMany(objectList); // END LoadData @@ -155,13 +158,15 @@ await collection.Config.Update(c => Type = VectorIndex.Quantizers.EncoderType.Tile, Distribution = VectorIndex.Quantizers.DistributionType.Normal, }, - }); + } + ); }); // END UpdateSchema var updatedConfig = await collection.Config.Get(); Assert.NotNull(updatedConfig); - var hnswConfig = updatedConfig.VectorConfig["default"].VectorIndexConfig as VectorIndex.HNSW; + var hnswConfig = + updatedConfig.VectorConfig["default"].VectorIndexConfig as VectorIndex.HNSW; Assert.IsType(hnswConfig?.Quantizer); } @@ -171,38 +176,42 @@ public async Task TestGetSchema() await BeforeEach(); // Create a collection with PQ enabled to inspect its schema - await client.Collections.Create(new CollectionConfig - { - Name = "Question", - VectorConfig = new VectorConfig( - "default", - new Vectorizer.Text2VecTransformers(), - new VectorIndex.HNSW - { - Quantizer = new VectorIndex.Quantizers.PQ + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Question", + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW { - Encoder = new VectorIndex.Quantizers.PQ.EncoderConfig + Quantizer = new VectorIndex.Quantizers.PQ { - Type = VectorIndex.Quantizers.EncoderType.Tile, - Distribution = VectorIndex.Quantizers.DistributionType.Normal, + Encoder = new VectorIndex.Quantizers.PQ.EncoderConfig + { + Type = VectorIndex.Quantizers.EncoderType.Tile, + Distribution = VectorIndex.Quantizers.DistributionType.Normal, + }, + TrainingLimit = 50000, }, - TrainingLimit = 50000 } - } - ), - Properties = - [ - Property.Text("question"), - Property.Text("answer"), - Property.Text("category") - ] - }); + ), + Properties = + [ + Property.Text("question"), + Property.Text("answer"), + Property.Text("category"), + ], + } + ); // START GetSchema var jeopardy = client.Collections.Use("Question"); var config = await jeopardy.Config.Get(); - Console.WriteLine(JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); + Console.WriteLine( + JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }) + ); // END GetSchema Assert.NotNull(config); @@ -215,4 +224,4 @@ await client.Collections.Create(new CollectionConfig Console.WriteLine($"Segments: {pqConfig.Segments}"); Console.WriteLine($"Centroids: {pqConfig.Centroids}"); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ConfigureRQTest.cs b/_includes/code/csharp/ConfigureRQTest.cs index 9ee5cce79..e54125715 100644 --- a/_includes/code/csharp/ConfigureRQTest.cs +++ b/_includes/code/csharp/ConfigureRQTest.cs @@ -1,166 +1,182 @@ -// using Xunit; -// using Weaviate.Client; -// using Weaviate.Client.Models; -// using System; -// using System.Threading.Tasks; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; -// namespace WeaviateProject.Tests; +namespace WeaviateProject.Tests; -// public class ConfigureRQTest : IAsyncLifetime -// { -// private WeaviateClient client; -// private const string COLLECTION_NAME = "MyCollection"; +public class ConfigureRQTest : IAsyncLifetime +{ + private WeaviateClient client; + private const string COLLECTION_NAME = "MyCollection"; -// // Runs before each test -// public async Task InitializeAsync() -// { -// // START ConnectCode -// // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. -// // This must be configured in Weaviate's environment variables. -// client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); -// // END ConnectCode + // Runs before each test + public async Task InitializeAsync() + { + // START ConnectCode + client = await Connect.Local(); + // END ConnectCode -// // Clean slate for each test -// if (await client.Collections.Exists(COLLECTION_NAME)) -// { -// await client.Collections.Delete(COLLECTION_NAME); -// } -// } + // Clean slate for each test + if (await client.Collections.Exists(COLLECTION_NAME)) + { + await client.Collections.Delete(COLLECTION_NAME); + } + } -// // Runs after each test -// public Task DisposeAsync() -// { -// // No action needed here, as cleanup happens in InitializeAsync before the next test. -// return Task.CompletedTask; -// } + // Runs after each test + public Task DisposeAsync() + { + // No action needed here, as cleanup happens in InitializeAsync before the next test. + return Task.CompletedTask; + } -// [Fact] -// public async Task TestEnableRQ() -// { -// // START EnableRQ -// await client.Collections.Create(new CollectionConfig -// { -// Name = "MyCollection", -// Properties = [Property.Text("title")], -// VectorConfig = new VectorConfig( -// "default", -// new Vectorizer.Text2VecTransformers(), -// new VectorIndex.HNSW -// { -// // highlight-start -// Quantizer = new VectorIndex.Quantizers.RQ() -// // highlight-end -// } -// ) -// }); -// // END EnableRQ -// } + [Fact] + public async Task TestEnableRQ() + { + // START EnableRQ + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.RQ(), + // highlight-end + } + ), + } + ); + // END EnableRQ + } -// [Fact] -// public async Task Test1BitEnableRQ() -// { -// // START 1BitEnableRQ -// await client.Collections.Create(new CollectionConfig -// { -// Name = "MyCollection", -// Properties = [Property.Text("title")], -// VectorConfig = new VectorConfig( -// "default", -// new Vectorizer.Text2VecTransformers(), -// new VectorIndex.HNSW -// { -// // highlight-start -// Quantizer = new VectorIndex.Quantizers.RQ { Bits = 1 } -// // highlight-end -// } -// ) -// }); -// // END 1BitEnableRQ -// } + [Fact] + public async Task Test1BitEnableRQ() + { + // START 1BitEnableRQ + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.RQ { Bits = 1 }, + // highlight-end + } + ), + } + ); + // END 1BitEnableRQ + } -// [Fact] -// public async Task TestUncompressed() -// { -// // START Uncompressed -// await client.Collections.Create(new CollectionConfig -// { -// Name = "MyCollection", -// Properties = [Property.Text("title")], -// VectorConfig = new VectorConfig( -// "default", -// new Vectorizer.Text2VecTransformers(), -// // highlight-start -// // Omitting the Quantizer property results in an uncompressed index. -// new VectorIndex.HNSW() -// // highlight-end -// ) -// }); -// // END Uncompressed -// } + [Fact] + public async Task TestUncompressed() + { + // START Uncompressed + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.None { }, + // highlight-end + } + // highlight-end + ), + } + ); + // END Uncompressed + } -// [Fact] -// public async Task TestRQWithOptions() -// { -// // START RQWithOptions -// await client.Collections.Create(new CollectionConfig -// { -// Name = "MyCollection", -// Properties = [Property.Text("title")], -// VectorConfig = new VectorConfig( -// "default", -// new Vectorizer.Text2VecTransformers(), -// new VectorIndex.HNSW -// { -// // highlight-start -// Quantizer = new VectorIndex.Quantizers.RQ -// { -// Bits = 8, // Optional: Number of bits -// RescoreLimit = 20 // Optional: Number of candidates to fetch before rescoring -// } -// // highlight-end -// } -// ) -// }); -// // END RQWithOptions -// } + [Fact] + public async Task TestRQWithOptions() + { + // START RQWithOptions + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.RQ + { + Bits = 8, // Optional: Number of bits + RescoreLimit = 20, // Optional: Number of candidates to fetch before rescoring + }, + // highlight-end + } + ), + } + ); + // END RQWithOptions + } -// [Fact] -// public async Task TestUpdateSchema() -// { -// // Note: Updating quantization settings on an existing collection is not supported by Weaviate -// // and will result in an error, as noted in the Java test. This test demonstrates the syntax for attempting the update. -// var collection = await client.Collections.Create(new CollectionConfig -// { -// Name = "MyCollection", -// Properties = [Property.Text("title")], -// VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()) -// }); + [Fact] + public async Task TestUpdateSchema() + { + // Note: Updating quantization settings on an existing collection is not supported by Weaviate + // and will result in an error, as noted in the Java test. This test demonstrates the syntax for attempting the update. + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + } + ); -// // START UpdateSchema -// await collection.Config.Update(c => -// { -// var vectorConfig = c.VectorConfig["default"]; -// vectorConfig.VectorIndexConfig.UpdateHNSW(h => h.Quantizer = new VectorIndex.Quantizers.RQ()); -// }); -// // END UpdateSchema -// } + // START UpdateSchema + await collection.Config.Update(c => + { + var vectorConfig = c.VectorConfig["default"]; + vectorConfig.VectorIndexConfig.UpdateHNSW(h => + h.Quantizer = new VectorIndex.Quantizers.RQ() + ); + }); + // END UpdateSchema + } -// [Fact] -// public async Task Test1BitUpdateSchema() -// { -// var collection = await client.Collections.Create(new CollectionConfig -// { -// Name = "MyCollection", -// Properties = [Property.Text("title")], -// VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()) -// }); + [Fact] + public async Task Test1BitUpdateSchema() + { + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + } + ); -// // START 1BitUpdateSchema -// await collection.Config.Update(c => -// { -// var vectorConfig = c.VectorConfig["default"]; -// vectorConfig.VectorIndexConfig.UpdateHNSW(h => h.Quantizer = new VectorIndex.Quantizers.RQ { Bits = 1 }); -// }); -// // END 1BitUpdateSchema -// } -// } \ No newline at end of file + // START 1BitUpdateSchema + await collection.Config.Update(c => + { + var vectorConfig = c.VectorConfig["default"]; + vectorConfig.VectorIndexConfig.UpdateHNSW(h => + h.Quantizer = new VectorIndex.Quantizers.RQ { Bits = 1 } + ); + }); + // END 1BitUpdateSchema + } +} diff --git a/_includes/code/csharp/ConfigureSQTest.cs b/_includes/code/csharp/ConfigureSQTest.cs index caea2b196..6d2c83053 100644 --- a/_includes/code/csharp/ConfigureSQTest.cs +++ b/_includes/code/csharp/ConfigureSQTest.cs @@ -1,8 +1,7 @@ -using Xunit; +using System.Threading.Tasks; using Weaviate.Client; using Weaviate.Client.Models; -using System; -using System.Threading.Tasks; +using Xunit; namespace WeaviateProject.Tests; @@ -15,9 +14,7 @@ public class ConfigureSQTest : IAsyncLifetime public async Task InitializeAsync() { // START ConnectCode - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + client = await Connect.Local(); // END ConnectCode // Clean slate for each test @@ -38,21 +35,23 @@ public Task DisposeAsync() public async Task TestEnableSQ() { // START EnableSQ - await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - Properties = [Property.Text("title")], - VectorConfig = new VectorConfig( - "default", - new Vectorizer.Text2VecTransformers(), - new VectorIndex.HNSW - { - // highlight-start - Quantizer = new VectorIndex.Quantizers.SQ() - // highlight-end - } - ) - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.SQ(), + // highlight-end + } + ), + } + ); // END EnableSQ } @@ -61,48 +60,51 @@ public async Task TestUpdateSchema() { // Note: Updating quantization settings on an existing collection is not supported by Weaviate // and will result in an error, as noted in the Java test. This test demonstrates the syntax for attempting the update. - var collection = await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - Properties = [Property.Text("title")], - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }); + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + } + ); // START UpdateSchema await collection.Config.Update(c => { var vectorConfig = c.VectorConfig["default"]; - vectorConfig.VectorIndexConfig.UpdateHNSW(h => h.Quantizer = new VectorIndex.Quantizers.SQ()); + vectorConfig.VectorIndexConfig.UpdateHNSW(h => + h.Quantizer = new VectorIndex.Quantizers.SQ() + ); }); // END UpdateSchema } - // TODO[g-despot] Missing cache [Fact] public async Task TestSQWithOptions() { // START SQWithOptions - await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - Properties = [Property.Text("title")], - VectorConfig = new VectorConfig( - "default", - new Vectorizer.Text2VecTransformers(), - // highlight-start - new VectorIndex.HNSW - { - VectorCacheMaxObjects = 100000, - Quantizer = new VectorIndex.Quantizers.SQ + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW { - //Cache = true, - TrainingLimit = 50000, - RescoreLimit = 200 + VectorCacheMaxObjects = 100000, + Quantizer = new VectorIndex.Quantizers.SQ + { + TrainingLimit = 50000, + RescoreLimit = 200, + }, } - } // highlight-end - ) - }); + ), + } + ); // END SQWithOptions } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ConnectionTest.cs b/_includes/code/csharp/ConnectionTest.cs index d2ee291b7..1d0f71e31 100644 --- a/_includes/code/csharp/ConnectionTest.cs +++ b/_includes/code/csharp/ConnectionTest.cs @@ -1,8 +1,8 @@ -using Weaviate.Client; using System; +using System.Collections.Generic; using System.Threading.Tasks; +using Weaviate.Client; using Xunit; -using System.Collections.Generic; namespace WeaviateProject.Examples; @@ -12,27 +12,86 @@ public class ConnectionTest public async Task TestConnectLocalWithCustomUrl() { // START CustomURL - var config = new ClientConfiguration - { - RestAddress = "127.0.0.1", - RestPort = 8080, - GrpcAddress = "127.0.0.1", - GrpcPort = 50051 // Default gRPC port - }; - WeaviateClient client = new WeaviateClient(config); + WeaviateClient client = await WeaviateClientBuilder + .Custom( + restEndpoint: "127.0.0.1", + restPort: "8080", + grpcEndpoint: "127.0.0.1", + grpcPort: "50051", + useSsl: false + ) + .BuildAsync(); var isReady = await client.IsReady(); Console.WriteLine(isReady); // END CustomURL } - // TODO[g-despot] How to add timeout - // START TimeoutLocal - // Coming soon - // END TimeoutLocal - // START TimeoutCustom - // Coming soon - // END TimeoutCustom + [Fact] + public async Task TestConnectLocalWithTimeouts() + { + // START TimeoutLocal + WeaviateClient client = await Connect.Local( + initTimeout: TimeSpan.FromSeconds(30), + queryTimeout: TimeSpan.FromSeconds(60), + insertTimeout: TimeSpan.FromSeconds(120) + ); + + var isReady = await client.IsReady(); + Console.WriteLine(isReady); + // END TimeoutLocal + } + + [Fact] + public async Task TestConnectCloudWithTimeouts() + { + // START TimeoutWCD + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + WeaviateClient client = await Connect.Cloud( + weaviateUrl, + weaviateApiKey, + initTimeout: TimeSpan.FromSeconds(30), + queryTimeout: TimeSpan.FromSeconds(60), + insertTimeout: TimeSpan.FromSeconds(120) + ); + + var isReady = await client.IsReady(); + Console.WriteLine(isReady); + // END TimeoutWCD + } + + [Fact] + public async Task TestConnectCustomWithTimeouts() + { + // START TimeoutCustom + // Best practice: store your credentials in environment variables + string httpHost = Environment.GetEnvironmentVariable("WEAVIATE_HTTP_HOST"); + string grpcHost = Environment.GetEnvironmentVariable("WEAVIATE_GRPC_HOST"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string cohereApiKey = Environment.GetEnvironmentVariable("COHERE_API_KEY"); + + WeaviateClient client = await WeaviateClientBuilder + .Custom( + restEndpoint: httpHost, + restPort: "443", + grpcEndpoint: grpcHost, + grpcPort: "443", + useSsl: true + ) + .WithCredentials(Auth.ApiKey(weaviateApiKey)) + .WithHeaders(new Dictionary { { "X-Cohere-Api-Key", cohereApiKey } }) + .WithInitTimeout(TimeSpan.FromSeconds(30)) + .WithQueryTimeout(TimeSpan.FromSeconds(60)) + .WithInsertTimeout(TimeSpan.FromSeconds(120)) + .BuildAsync(); + + var isReady = await client.IsReady(); + Console.WriteLine(isReady); + // END TimeoutCustom + } [Fact] public async Task TestConnectWCDWithApiKey() @@ -42,7 +101,7 @@ public async Task TestConnectWCDWithApiKey() string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); - WeaviateClient client = Connect.Cloud( + WeaviateClient client = await Connect.Cloud( weaviateUrl, // Replace with your Weaviate Cloud URL weaviateApiKey // Replace with your Weaviate Cloud key ); @@ -62,20 +121,17 @@ public async Task TestCustomConnection() string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); string cohereApiKey = Environment.GetEnvironmentVariable("COHERE_API_KEY"); - var config = new ClientConfiguration - { - UseSsl = true, // Corresponds to scheme("https") - RestAddress = httpHost, - RestPort = 443, - GrpcAddress = grpcHost, - GrpcPort = 443, - Credentials = Auth.ApiKey(weaviateApiKey), - Headers = new Dictionary - { - { "X-Cohere-Api-Key", cohereApiKey } - } - }; - WeaviateClient client = new WeaviateClient(config); + WeaviateClient client = await WeaviateClientBuilder + .Custom( + restEndpoint: httpHost, + restPort: "443", + grpcEndpoint: grpcHost, + grpcPort: "443", + useSsl: true + ) + .WithCredentials(Auth.ApiKey(weaviateApiKey)) + .WithHeaders(new Dictionary { { "X-Cohere-Api-Key", cohereApiKey } }) + .BuildAsync(); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -92,20 +148,17 @@ public async Task TestCustomApiKeyConnection() string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); string cohereApiKey = Environment.GetEnvironmentVariable("COHERE_API_KEY"); - var config = new ClientConfiguration - { - UseSsl = true, // Corresponds to scheme("https") - RestAddress = httpHost, - RestPort = 443, - GrpcAddress = grpcHost, - GrpcPort = 443, - Credentials = Auth.ApiKey(weaviateApiKey), - Headers = new Dictionary - { - { "X-Cohere-Api-Key", cohereApiKey } - } - }; - WeaviateClient client = new WeaviateClient(config); + WeaviateClient client = await WeaviateClientBuilder + .Custom( + restEndpoint: httpHost, + restPort: "443", + grpcEndpoint: grpcHost, + grpcPort: "443", + useSsl: true + ) + .WithCredentials(Auth.ApiKey(weaviateApiKey)) + .WithHeaders(new Dictionary { { "X-Cohere-Api-Key", cohereApiKey } }) + .BuildAsync(); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -116,7 +169,7 @@ public async Task TestCustomApiKeyConnection() public async Task TestConnectLocalNoAuth() { // START LocalNoAuth - WeaviateClient client = Connect.Local(); + WeaviateClient client = await Connect.Local(); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -130,12 +183,7 @@ public async Task TestConnectLocalWithAuth() // Best practice: store your credentials in environment variables string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_LOCAL_API_KEY"); - // The Connect.Local() helper doesn't support auth, so we must use a custom configuration. - var config = new ClientConfiguration - { - Credentials = Auth.ApiKey(weaviateApiKey) - }; - WeaviateClient client = new WeaviateClient(config); + WeaviateClient client = await Connect.Local(credentials: Auth.ApiKey(weaviateApiKey)); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -149,14 +197,10 @@ public async Task TestConnectLocalWithThirdPartyKeys() // Best practice: store your credentials in environment variables string cohereApiKey = Environment.GetEnvironmentVariable("COHERE_API_KEY"); - var config = new ClientConfiguration - { - Headers = new Dictionary - { - { "X-Cohere-Api-Key", cohereApiKey } - } - }; - WeaviateClient client = new WeaviateClient(config); + WeaviateClient client = await WeaviateClientBuilder + .Local(hostname: "localhost", restPort: 8080, grpcPort: 50051, useSsl: false) + .WithHeaders(new Dictionary { { "X-Cohere-Api-Key", cohereApiKey } }) + .BuildAsync(); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -172,21 +216,14 @@ public async Task TestConnectWCDWithThirdPartyKeys() string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); string cohereApiKey = Environment.GetEnvironmentVariable("COHERE_API_KEY"); - WeaviateClient client = Connect.Cloud( + WeaviateClient client = await Connect.Cloud( weaviateUrl, // Replace with your Weaviate Cloud URL weaviateApiKey, // Replace with your Weaviate Cloud key - new Dictionary - { - { "X-Cohere-Api-Key", cohereApiKey } - } + new Dictionary { { "X-Cohere-Api-Key", cohereApiKey } } ); var isReady = await client.IsReady(); Console.WriteLine(isReady); // END ThirdPartyAPIKeys } - - // START TimeoutWCD - // Coming soon - // END TimeoutWCD -} \ No newline at end of file +} diff --git a/_includes/code/csharp/Folder.DotSettings.user b/_includes/code/csharp/Folder.DotSettings.user new file mode 100644 index 000000000..04cd255c5 --- /dev/null +++ b/_includes/code/csharp/Folder.DotSettings.user @@ -0,0 +1,2 @@ + + ForceIncluded \ No newline at end of file diff --git a/_includes/code/csharp/GetStartedTest.cs b/_includes/code/csharp/GetStartedTest.cs index bdcf77949..808d73d38 100644 --- a/_includes/code/csharp/GetStartedTest.cs +++ b/_includes/code/csharp/GetStartedTest.cs @@ -1,20 +1,21 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; +using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Text.Json; -using System.Linq; using System.Threading.Tasks; -using System; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; + // ... other usings // 1. Define your strongly-typed class public class JeopardyQuestion { - public string? question { get; set; } - public string? answer { get; set; } - public string? category { get; set; } + public string Question { get; set; } + public string Answer { get; set; } + public string Category { get; set; } } public class GetStartedTests @@ -22,7 +23,7 @@ public class GetStartedTests [Fact] public async Task GetStarted() { - var client = Connect.Local(); + var client = await Connect.Local(); const string collectionName = "Question"; try @@ -32,17 +33,22 @@ public async Task GetStarted() await client.Collections.Delete(collectionName); } - var questions = await client.Collections.Create(new CollectionConfig() - { - Name = collectionName, - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecOllama { ApiEndpoint = "http://host.docker.internal:11434" }), - Properties = - [ - Property.Text("answer"), - Property.Text("question"), - Property.Text("category"), - ] - }); + var questions = await client.Collections.Create( + new CollectionCreateParams() + { + Name = collectionName, + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecOllama(apiEndpoint: "http://host.docker.internal:11434") + ), + Properties = + [ + Property.Text("answer"), + Property.Text("question"), + Property.Text("category"), + ], + } + ); // Download and parse data as before... using var httpClient = new HttpClient(); @@ -56,16 +62,17 @@ public async Task GetStarted() // ============================= YOUR NEW, CLEAN CODE ============================= // 2. Prepare the data by mapping it to your new class var dataObjects = data.Select(d => new JeopardyQuestion - { - answer = d.GetProperty("Answer").GetString(), - question = d.GetProperty("Question").GetString(), - category = d.GetProperty("Category").GetString() - }).ToList(); + { + Answer = d.GetProperty("Answer").GetString(), + Question = d.GetProperty("Question").GetString(), + Category = d.GetProperty("Category").GetString(), + }) + .ToList(); // ============================================================================== var importResult = await questions.Data.InsertMany(dataObjects); await Task.Delay(2000); // Wait for data to be indexed - + var response = await questions.Query.NearText("biology", limit: 2); // ... rest of the test Assert.Equal(2, response.Objects.Count()); @@ -82,50 +89,68 @@ public async Task GetStarted() [Fact] public async Task CreateCollectionAndRunNearTextQuery() { + // START GetStarted // Best practice: store your credentials in environment variables string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); - // 1. Connect to Weaviate - var client = Connect.Cloud(weaviateUrl, weaviateApiKey); + // 1. Connect to Weaviate Cloud + var client = await Connect.Cloud(weaviateUrl, weaviateApiKey); - // 2. Prepare data (same as Python data_objects) + // 2. Prepare data var dataObjects = new List { - new {title = "The Matrix", description = "A computer hacker learns about the true nature of reality and his role in the war against its controllers.", genre = "Science Fiction"}, - new {title = "Spirited Away", description = "A young girl becomes trapped in a mysterious world of spirits and must find a way to save her parents and return home.", genre = "Animation"}, - new {title = "The Lord of the Rings: The Fellowship of the Ring", description = "A meek Hobbit and his companions set out on a perilous journey to destroy a powerful ring and save Middle-earth.", genre = "Fantasy"} + new + { + title = "The Matrix", + description = "A computer hacker learns about the true nature of reality and his role in the war against its controllers.", + genre = "Science Fiction", + }, + new + { + title = "Spirited Away", + description = "A young girl becomes trapped in a mysterious world of spirits and must find a way to save her parents and return home.", + genre = "Animation", + }, + new + { + title = "The Lord of the Rings: The Fellowship of the Ring", + description = "A meek Hobbit and his companions set out on a perilous journey to destroy a powerful ring and save Middle-earth.", + genre = "Fantasy", + }, }; var CollectionName = "Movie"; await client.Collections.Delete(CollectionName); // 3. Create the collection - var movies = await client.Collections.Create(new CollectionConfig - { - Name = CollectionName, - VectorConfig = new VectorConfigList + var movies = await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.Text2VecWeaviate()) - }, - }); + Name = CollectionName, + VectorConfig = Configure.Vector("default", v => v.Text2VecWeaviate()), + } + ); // 4. Import the data var result = await movies.Data.InsertMany(dataObjects); // 5. Run the query - var response = await movies.Query.NearText( - "sci-fi", - limit: 2 - ); + var response = await movies.Query.NearText("sci-fi", limit: 2); // 6. Inspect the results foreach (var obj in response.Objects) { Console.WriteLine(JsonSerializer.Serialize(obj.Properties)); } + // END GetStarted Assert.Equal(2, response.Objects.Count); - Assert.Contains(response.Objects, o => o.Properties.ContainsKey("title") && o.Properties["title"].ToString() == "The Matrix"); + Assert.Contains( + response.Objects, + o => + o.Properties.ContainsKey("title") + && o.Properties["title"].ToString() == "The Matrix" + ); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ManageCollectionsAliasTest.cs b/_includes/code/csharp/ManageCollectionsAliasTest.cs index e69de29bb..001450376 100644 --- a/_includes/code/csharp/ManageCollectionsAliasTest.cs +++ b/_includes/code/csharp/ManageCollectionsAliasTest.cs @@ -0,0 +1,285 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; + +public class ManageCollectionsAliasTest : IAsyncLifetime +{ + private WeaviateClient client; + + // Constant names to avoid typos + private const string Articles = "Articles"; + private const string ArticlesV2 = "ArticlesV2"; + private const string ArticlesAlias = "ArticlesAlias"; + private const string ProductsV1 = "Products_v1"; + private const string ProductsV2 = "Products_v2"; + private const string ProductsAlias = "ProductsAlias"; + + public async Task InitializeAsync() + { + // START ConnectToWeaviate + // Connect to local Weaviate instance + client = await Connect.Local(); + // END ConnectToWeaviate + + // Initial Cleanup to ensure clean state + await CleanupResources(); + } + + public async Task DisposeAsync() + { + await CleanupResources(); + } + + private async Task CleanupResources() + { + // Cleanup aliases first + await client.Alias.Delete(ArticlesAlias); + await client.Alias.Delete(ProductsAlias); + + // Cleanup collections + await client.Collections.Delete(Articles); + await client.Collections.Delete(ArticlesV2); + await client.Collections.Delete(ProductsV1); + await client.Collections.Delete(ProductsV2); + } + + [Fact] + public async Task TestAliasBasicWorkflow() + { + // START CreateAlias + // Create a collection first + await client.Collections.Create( + new CollectionCreateParams + { + Name = Articles, + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + Properties = [Property.Text("title"), Property.Text("content")], + } + ); + + // Create an alias pointing to the collection + await client.Alias.Create(ArticlesAlias, Articles); + // END CreateAlias + + // START ListAllAliases + // Get all aliases in the instance + var allAliases = await client.Alias.List(); + + foreach (var entry in allAliases) + { + Console.WriteLine($"Alias: {entry.Name} -> Collection: {entry.TargetCollection}"); + } + // END ListAllAliases + + // START ListCollectionAliases + // Get all aliases pointing to a specific collection + var collectionAliases = await client.Alias.List(Articles); + + foreach (var entry in collectionAliases) + { + Console.WriteLine($"Alias pointing to Articles: {entry.Name}"); + } + // END ListCollectionAliases + + // START GetAlias + // Get information about a specific alias + var aliasInfo = await client.Alias.Get(aliasName: ArticlesAlias); + + if (aliasInfo != null) + { + Console.WriteLine($"Alias: {aliasInfo.Name}"); + Console.WriteLine($"Target collection: {aliasInfo.TargetCollection}"); + } + // END GetAlias + Assert.NotNull(aliasInfo); + Assert.Equal(Articles, aliasInfo.TargetCollection); + + // START UpdateAlias + // Create a new collection for migration + await client.Collections.Create( + new CollectionCreateParams + { + Name = ArticlesV2, + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + Properties = + [ + Property.Text("title"), + Property.Text("content"), + Property.Text("author"), // New field + ], + } + ); + + // Update the alias to point to the new collection + bool success = + (await client.Alias.Update(aliasName: ArticlesAlias, targetCollection: ArticlesV2)) + != null; + + if (success) + { + Console.WriteLine("Alias updated successfully"); + } + // END UpdateAlias + Assert.True(success); + + // Delete original collection to prove alias still works pointing to V2 + await client.Collections.Delete(Articles); + + // START UseAlias + // Ensure the Articles collection exists (it might have been deleted in previous examples) + // Note: In C# we check existence first to avoid errors if it already exists + if (!await client.Collections.Exists(Articles)) + { + await client.Collections.Create( + new CollectionCreateParams + { + Name = Articles, + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + Properties = [Property.Text("title"), Property.Text("content")], + } + ); + } + // END UseAlias + + // START DeleteAlias + // Delete an alias (the underlying collection remains) + await client.Alias.Delete(aliasName: ArticlesAlias); + // END DeleteAlias + Assert.Null(await client.Alias.Get(ArticlesAlias)); + + // Re-create alias for the usage example below (since we just deleted it) + await client.Alias.Create(ArticlesAlias, Articles); + + // START UseAlias + // Use the alias just like a collection name + var articles = client.Collections.Use(ArticlesAlias); + + // Insert data using the alias + await articles.Data.Insert( + new + { + title = "Using Aliases in Weaviate", + content = "Aliases make collection management easier...", + } + ); + + // Query using the alias + var results = await articles.Query.FetchObjects(limit: 5); + + foreach (var obj in results.Objects) + { + Console.WriteLine($"Found: {obj.Properties["title"]}"); + } + // END UseAlias + Assert.Single(results.Objects); + } + + [Fact] + public async Task TestZeroDowntimeMigration() + { + // START Step1CreateOriginal + // Create original collection with data + await client.Collections.Create( + new CollectionCreateParams + { + Name = ProductsV1, + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + } + ); + + var productsV1 = client.Collections.Use(ProductsV1); + + // Batch insert works best with anonymous objects here + await productsV1.Data.InsertMany( + new[] + { + new { name = "Product A", price = 100 }, + new { name = "Product B", price = 200 }, + } + ); + // END Step1CreateOriginal + + // START Step2CreateAlias + // Create alias pointing to current collection + await client.Alias.Create(ProductsAlias, ProductsV1); + // END Step2CreateAlias + + // START MigrationUseAlias + // Your application always uses the alias name "Products" + var products = client.Collections.Use(ProductsAlias); + + // Insert data through the alias + await products.Data.Insert(new { name = "Product C", price = 300 }); + + // Query through the alias + var results = await products.Query.FetchObjects(limit: 5); + foreach (var obj in results.Objects) + { + Console.WriteLine( + $"Product: {obj.Properties["name"]}, Price: ${obj.Properties["price"]}" + ); + } + // END MigrationUseAlias + Assert.Equal(3, results.Objects.Count); + + // START Step3NewCollection + // Create new collection with updated schema + await client.Collections.Create( + new CollectionCreateParams + { + Name = ProductsV2, + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + Properties = + [ + Property.Text("name"), + Property.Number("price"), + Property.Text("category"), // New field + ], + } + ); + // END Step3NewCollection + + // START Step4MigrateData + // Migrate data to new collection + var productsV2 = client.Collections.Use(ProductsV2); + var oldData = (await productsV1.Query.FetchObjects()).Objects; + + foreach (var obj in oldData) + { + // Convert property values to primitives (string, double, etc.) explicitly. + await productsV2.Data.Insert( + new + { + name = obj.Properties["name"].ToString(), + price = Convert.ToDouble(obj.Properties["price"].ToString()), + category = "General", + } + ); + } + // END Step4MigrateData + + // START Step5UpdateAlias + // Switch alias to new collection (instant switch!) + await client.Alias.Update(aliasName: ProductsAlias, targetCollection: ProductsV2); + + // All queries using "Products" alias now use the new collection + products = client.Collections.Use(ProductsAlias); + var result = await products.Query.FetchObjects(limit: 1); + + // Will include the new "category" field + Console.WriteLine(JsonSerializer.Serialize(result.Objects.First().Properties)); + // END Step5UpdateAlias + + Assert.True(result.Objects.First().Properties.ContainsKey("category")); + + // START Step6Cleanup + // Clean up old collection after verification + await client.Collections.Delete(ProductsV1); + // END Step6Cleanup + Assert.False(await client.Collections.Exists(ProductsV1)); + } +} diff --git a/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs b/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs index f251a618b..5d57e8068 100644 --- a/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs +++ b/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs @@ -1,10 +1,10 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Linq; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -13,12 +13,9 @@ public class ManageCollectionsCrossReferencesTest : IAsyncLifetime private WeaviateClient client; // Runs before each test - public Task InitializeAsync() + public async Task InitializeAsync() { - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); - return Task.CompletedTask; + client = await Connect.Local(); } // Runs after each test @@ -33,22 +30,26 @@ public async Task DisposeAsync() public async Task TestCrossRefDefinition() { // START CrossRefDefinition - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyCategory", - Description = "A Jeopardy! category", - Properties = [ Property.Text("title") ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyCategory", + Description = "A Jeopardy! category", + Properties = [Property.Text("title")], + } + ); - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyQuestion", - Description = "A Jeopardy! question", - Properties = [ Property.Text("question"), Property.Text("answer") ], - // highlight-start - References = [ new Reference("hasCategory", "JeopardyCategory") ] - // highlight-end - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = [Property.Text("question"), Property.Text("answer")], + // highlight-start + References = [new Reference("hasCategory", "JeopardyCategory")], + // highlight-end + } + ); // END CrossRefDefinition // Verify collections were created properly @@ -61,23 +62,30 @@ await client.Collections.Create(new CollectionConfig public async Task TestObjectWithCrossRef() { await SetupCollections(); - var categories = client.Collections.Use("JeopardyCategory"); + var categories = client.Collections.Use("JeopardyCategory"); var categoryUuid = await categories.Data.Insert(new { title = "Weaviate" }); - var properties = new { question = "What tooling helps make Weaviate scalable?", answer = "Sharding, multi-tenancy, and replication" }; + var properties = new + { + question = "What tooling helps make Weaviate scalable?", + answer = "Sharding, multi-tenancy, and replication", + }; // START ObjectWithCrossRef - var questions = client.Collections.Use("JeopardyQuestion"); + var questions = client.Collections.Use("JeopardyQuestion"); var newObject = await questions.Data.Insert( properties, // The properties of the object - // highlight-start + // highlight-start references: [new ObjectReference("hasCategory", categoryUuid)] // highlight-end ); // END ObjectWithCrossRef // Test results - var fetchedObj = await questions.Query.FetchObjectByID(newObject, returnReferences: [new QueryReference("hasCategory")]); + var fetchedObj = await questions.Query.FetchObjectByID( + newObject, + returnReferences: [new QueryReference("hasCategory")] + ); Assert.NotNull(fetchedObj); Assert.True(fetchedObj.References.ContainsKey("hasCategory")); } @@ -86,10 +94,16 @@ public async Task TestObjectWithCrossRef() public async Task TestOneWay() { await SetupCollections(); - var questions = client.Collections.Use("JeopardyQuestion"); - var categories = client.Collections.Use("JeopardyCategory"); + var questions = client.Collections.Use("JeopardyQuestion"); + var categories = client.Collections.Use("JeopardyCategory"); - var questionObjId = await questions.Data.Insert(new { question = "This city is known for the Golden Gate Bridge", answer = "San Francisco" }); + var questionObjId = await questions.Data.Insert( + new + { + question = "This city is known for the Golden Gate Bridge", + answer = "San Francisco", + } + ); var categoryObjId = await categories.Data.Insert(new { title = "U.S. CITIES" }); // START OneWayCrossReferences @@ -102,7 +116,10 @@ await questions.Data.ReferenceAdd( ); // END OneWayCrossReferences - var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + var result = await questions.Query.FetchObjectByID( + questionObjId, + returnReferences: [new QueryReference("hasCategory")] + ); Assert.NotNull(result); Assert.True(result.References.ContainsKey("hasCategory")); } @@ -111,54 +128,75 @@ await questions.Data.ReferenceAdd( public async Task TestTwoWay() { // START TwoWayCategory1CrossReferences - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyCategory", - Description = "A Jeopardy! category", - Properties = [ Property.Text("title") ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyCategory", + Description = "A Jeopardy! category", + Properties = [Property.Text("title")], + } + ); // END TwoWayCategory1CrossReferences // START TwoWayQuestionCrossReferences - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyQuestion", - Description = "A Jeopardy! question", - Properties = [ Property.Text("question"), Property.Text("answer") ], - // highlight-start - References = [ new Reference("hasCategory", "JeopardyCategory") ] - // highlight-end - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = [Property.Text("question"), Property.Text("answer")], + // highlight-start + References = [new Reference("hasCategory", "JeopardyCategory")], + // highlight-end + } + ); // END TwoWayQuestionCrossReferences // START TwoWayCategoryCrossReferences var category = client.Collections.Use("JeopardyCategory"); - await category.Config.AddProperty( + await category.Config.AddReference( // highlight-start Property.Reference("hasQuestion", "JeopardyQuestion") // highlight-end ); // END TwoWayCategoryCrossReferences - var questions = client.Collections.Use("JeopardyQuestion"); + var questions = client.Collections.Use("JeopardyQuestion"); var categories = client.Collections.Use("JeopardyCategory"); - var questionObjId = await questions.Data.Insert(new { question = "This city is known for the Golden Gate Bridge", answer = "San Francisco" }); + var questionObjId = await questions.Data.Insert( + new + { + question = "This city is known for the Golden Gate Bridge", + answer = "San Francisco", + } + ); var categoryObjId = await categories.Data.Insert(new { title = "U.S. CITIES" }); // START TwoWayCrossReferences // For the "San Francisco" JeopardyQuestion object, add a cross-reference to the "U.S. CITIES" JeopardyCategory object // highlight-start - await questions.Data.ReferenceAdd(from: questionObjId, fromProperty: "hasCategory", to: categoryObjId); + await questions.Data.ReferenceAdd( + from: questionObjId, + fromProperty: "hasCategory", + to: categoryObjId + ); // highlight-end // For the "U.S. CITIES" JeopardyCategory object, add a cross-reference to "San Francisco" // highlight-start - await categories.Data.ReferenceAdd(from: categoryObjId, fromProperty: "hasQuestion", to: questionObjId); + await categories.Data.ReferenceAdd( + from: categoryObjId, + fromProperty: "hasQuestion", + to: questionObjId + ); // highlight-end // END TwoWayCrossReferences - var result = await categories.Query.FetchObjectByID(categoryObjId, returnReferences: [new QueryReference("hasQuestion")]); + var result = await categories.Query.FetchObjectByID( + categoryObjId, + returnReferences: [new QueryReference("hasQuestion")] + ); Assert.NotNull(result); Assert.True(result.References.ContainsKey("hasQuestion")); } @@ -167,10 +205,16 @@ await category.Config.AddProperty( public async Task TestMultiple() { await SetupCollections(); - var questions = client.Collections.Use("JeopardyQuestion"); - var categories = client.Collections.Use("JeopardyCategory"); + var questions = client.Collections.Use("JeopardyQuestion"); + var categories = client.Collections.Use("JeopardyCategory"); - var questionObjId = await questions.Data.Insert(new { question = "This city is known for the Golden Gate Bridge", answer = "San Francisco" }); + var questionObjId = await questions.Data.Insert( + new + { + question = "This city is known for the Golden Gate Bridge", + answer = "San Francisco", + } + ); var categoryObjId = await categories.Data.Insert(new { title = "U.S. CITIES" }); var categoryObjIdAlt = await categories.Data.Insert(new { title = "MUSEUMS" }); @@ -188,7 +232,10 @@ await questions.Data.ReferenceAdd( // highlight-end // END MultipleCrossReferences - var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + var result = await questions.Query.FetchObjectByID( + questionObjId, + returnReferences: [new QueryReference("hasCategory")] + ); Assert.NotNull(result); Assert.True(result.References.ContainsKey("hasCategory")); Assert.Equal(2, result.References["hasCategory"].Count); @@ -198,8 +245,8 @@ await questions.Data.ReferenceAdd( public async Task TestReadCrossRef() { await SetupCollections(); - var questions = client.Collections.Use("JeopardyQuestion"); - var categories = client.Collections.Use("JeopardyCategory"); + var questions = client.Collections.Use("JeopardyQuestion"); + var categories = client.Collections.Use("JeopardyCategory"); var categoryResult = await categories.Data.Insert(new { title = "SCIENCE" }); var questionObjId = await questions.Data.Insert( @@ -230,17 +277,20 @@ public async Task TestReadCrossRef() Assert.True(obj.References.ContainsKey("hasCategory")); } - // TODO[g-despot] ERROR: Unexpected status code NoContent. Expected: OK. reference delete. [Fact] public async Task TestDelete() { await SetupCollections(); - var questions = client.Collections.Use("JeopardyQuestion"); - var categories = client.Collections.Use("JeopardyCategory"); + var questions = client.Collections.Use("JeopardyQuestion"); + var categories = client.Collections.Use("JeopardyCategory"); var categoryObjId = await categories.Data.Insert(new { title = "MUSEUMS" }); var questionObjId = await questions.Data.Insert( - new { question = "This city is known for the Golden Gate Bridge", answer = "San Francisco" }, + new + { + question = "This city is known for the Golden Gate Bridge", + answer = "San Francisco", + }, references: [new ObjectReference("hasCategory", categoryObjId)] ); @@ -255,21 +305,35 @@ await questions.Data.ReferenceDelete( ); // END DeleteCrossReference - var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + var result = await questions.Query.FetchObjectByID( + questionObjId, + returnReferences: [new QueryReference("hasCategory")] + ); Assert.NotNull(result); - Assert.False(result.References.ContainsKey("hasCategory")); + + // FIX: Check if the reference list is empty OR if the key is missing + if (result.References.ContainsKey("hasCategory")) + { + Assert.Empty(result.References["hasCategory"]); + } } [Fact] public async Task TestUpdate() { await SetupCollections(); - var questions = client.Collections.Use("JeopardyQuestion"); - var categories = client.Collections.Use("JeopardyCategory"); + var questions = client.Collections.Use("JeopardyQuestion"); + var categories = client.Collections.Use("JeopardyCategory"); var categoryObjId = await categories.Data.Insert(new { title = "MUSEUMS" }); await categories.Data.Insert(new { title = "U.S. CITIES" }); - var questionObjId = await questions.Data.Insert(new { question = "This city is known for the Golden Gate Bridge", answer = "San Francisco" }); + var questionObjId = await questions.Data.Insert( + new + { + question = "This city is known for the Golden Gate Bridge", + answer = "San Francisco", + } + ); // START UpdateCrossReference // In the "San Francisco" JeopardyQuestion object, set the "hasCategory" cross-reference only to "MUSEUMS" @@ -282,29 +346,36 @@ await questions.Data.ReferenceReplace( ); // END UpdateCrossReference - var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + var result = await questions.Query.FetchObjectByID( + questionObjId, + returnReferences: [new QueryReference("hasCategory")] + ); Assert.NotNull(result); Assert.True(result.References.ContainsKey("hasCategory")); Assert.Single(result.References["hasCategory"]); - Assert.Equal(categoryObjId, result.References["hasCategory"][0].ID); + Assert.Equal(categoryObjId, result.References["hasCategory"][0].UUID); } // Helper method to set up collections private async Task SetupCollections() { - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyCategory", - Description = "A Jeopardy! category", - Properties = [ Property.Text("title") ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyCategory", + Description = "A Jeopardy! category", + Properties = [Property.Text("title")], + } + ); - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyQuestion", - Description = "A Jeopardy! question", - Properties = [ Property.Text("question"), Property.Text("answer") ], - References = [ new Reference("hasCategory", "JeopardyCategory") ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = [Property.Text("question"), Property.Text("answer")], + References = [new Reference("hasCategory", "JeopardyCategory")], + } + ); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs b/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs index 45eacc824..61c817ddb 100644 --- a/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs +++ b/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs @@ -1,11 +1,11 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -15,153 +15,144 @@ public class ManageCollectionsMigrateDataTest : IAsyncLifetime private static WeaviateClient clientTgt; private const int DATASET_SIZE = 50; - // Runs once before any tests in the class + // Defines the schema structure for strong typing + private class WineReviewModel + { + public string title { get; set; } + public string review_body { get; set; } + public string country { get; set; } + public int? points { get; set; } + public double? price { get; set; } + } + public async Task InitializeAsync() { - string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); + clientSrc = await Connect.Local(restPort: 8080, grpcPort: 50051); + clientTgt = await Connect.Local(restPort: 8090, grpcPort: 50061); - // Connect to the source Weaviate instance - clientSrc = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); - // Connect to the target Weaviate instance - clientTgt = new WeaviateClient(new ClientConfiguration + // Ensure clean state for source collections + if (await clientSrc.Collections.Exists("WineReview")) { - RestAddress = "localhost", - RestPort = 8090, - GrpcPort = 50061, - }); + await clientSrc.Collections.Delete("WineReview"); + } + if (await clientSrc.Collections.Exists("WineReviewMT")) + { + await clientSrc.Collections.Delete("WineReviewMT"); + } - // Simulate weaviate-datasets by creating and populating source collections await CreateCollection(clientSrc, "WineReview", false); await CreateCollection(clientSrc, "WineReviewMT", true); - var wineReview = clientSrc.Collections.Use("WineReview"); - var wineReviewData = Enumerable.Range(0, DATASET_SIZE) - .Select(i => new { title = $"Review {i}" }) + var wineReview = clientSrc.Collections.Use("WineReview"); + var wineReviewData = Enumerable + .Range(0, DATASET_SIZE) + .Select(i => new WineReviewModel + { + title = $"Review {i}", + review_body = "Description...", + price = 10.5 + i, + }) .ToArray(); + await wineReview.Data.InsertMany(wineReviewData); - var wineReviewMT = clientSrc.Collections.Use("WineReviewMT"); - await wineReviewMT.Tenants.Add(new Tenant { Name = "tenantA" }); + var wineReviewMT = clientSrc.Collections.Use("WineReviewMT"); + await wineReviewMT.Tenants.Create(["tenantA"]); await wineReviewMT.WithTenant("tenantA").Data.InsertMany(wineReviewData); } - // Runs once after all tests in the class public async Task DisposeAsync() { - // Clean up collections on both clients await clientSrc.Collections.DeleteAll(); await clientTgt.Collections.DeleteAll(); } - // START CreateCollectionCollectionToCollection - // START CreateCollectionCollectionToTenant - // START CreateCollectionTenantToCollection - // START CreateCollectionTenantToTenant - private static async Task> CreateCollection(WeaviateClient clientIn, - string collectionName, bool enableMt) - // END CreateCollectionCollectionToCollection - // END CreateCollectionCollectionToTenant - // END CreateCollectionTenantToCollection - // END CreateCollectionTenantToTenant + // START CreateCollectionCollectionToCollection // START CreateCollectionCollectionToTenant // START CreateCollectionTenantToCollection // START CreateCollectionTenantToTenant + private static async Task CreateCollection( + WeaviateClient clientIn, + string collectionName, + bool enableMt + ) { + // END CreateCollectionCollectionToCollection // END CreateCollectionCollectionToTenant // END CreateCollectionTenantToCollection // END CreateCollectionTenantToTenant if (await clientIn.Collections.Exists(collectionName)) { await clientIn.Collections.Delete(collectionName); } - // START CreateCollectionCollectionToCollection - // START CreateCollectionCollectionToTenant - // START CreateCollectionTenantToCollection - // START CreateCollectionTenantToTenant - return await clientIn.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = enableMt }, - // Additional settings not shown - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()), - GenerativeConfig = new GenerativeConfig.Cohere(), - Properties = - [ - Property.Text("review_body"), - Property.Text("title"), - Property.Text("country"), - Property.Int("points"), - Property.Number("price") - ] - }); + // START CreateCollectionCollectionToCollection // START CreateCollectionCollectionToTenant // START CreateCollectionTenantToCollection // START CreateCollectionTenantToTenant + return await clientIn.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = enableMt }, + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + Properties = + [ + Property.Text("review_body"), + Property.Text("title"), + Property.Text("country"), + Property.Int("points"), + Property.Number("price"), + ], + } + ); } - // END CreateCollectionCollectionToCollection - // END CreateCollectionCollectionToTenant - // END CreateCollectionTenantToCollection - // END CreateCollectionTenantToTenant - // START CollectionToCollection - // START TenantToCollection - // START CollectionToTenant - // START TenantToTenant - private async Task MigrateData(CollectionClient collectionSrc, - CollectionClient collectionTgt) + // END CreateCollectionCollectionToCollection // END CreateCollectionCollectionToTenant // END CreateCollectionTenantToCollection // END CreateCollectionTenantToTenant + // START CollectionToCollection // START TenantToCollection // START CollectionToTenant // START TenantToTenant + private async Task MigrateData( + CollectionClient collectionSrc, + CollectionClient collectionTgt + ) { Console.WriteLine("Starting data migration..."); - var sourceObjects = new List(); - // FIX 1: Use FetchObjects instead of Iterator for better tenant support - var response = await collectionSrc.Query.FetchObjects(limit: 10000, returnMetadata: MetadataOptions.Vector); + // Fetch source objects + var response = await collectionSrc.Query.FetchObjects(limit: 10000, includeVectors: true); + // Map to Strong Type List + var sourceObjects = new List(); foreach (var obj in response.Objects) { - sourceObjects.Add(new WeaviateObject + // Deserialize the inner properties Dictionary to the POCO type + var json = JsonSerializer.Serialize(obj.Properties); + var typedObj = JsonSerializer.Deserialize(json); + if (typedObj != null) { - ID = obj.ID, - // FIX 2: Cast the dynamic properties to a supported dictionary type - Properties = (IDictionary)obj.Properties, - Vectors = obj.Vectors - }); + sourceObjects.Add(typedObj); + } } - // FIX 3 (from previous advice): Convert the List to an Array before inserting + // InsertMany using Strong Types await collectionTgt.Data.InsertMany(sourceObjects.ToArray()); - Console.WriteLine("Data migration complete."); + Console.WriteLine($"Data migration complete. Migrated {sourceObjects.Count} objects."); } - // END CollectionToCollection - // END TenantToCollection - // END CollectionToTenant - // END TenantToTenant - private async Task VerifyMigration(CollectionClient collectionSrc, - CollectionClient collectionTgt, int numSamples) + // END CollectionToCollection // END TenantToCollection // END CollectionToTenant // END TenantToTenant + + private async Task VerifyMigration(CollectionClient collectionTgt, int expectedCount) { - // FIX 1: Use FetchObjects instead of Iterator - var srcResponse = await collectionSrc.Query.FetchObjects(limit: 10000); - var srcObjects = srcResponse.Objects; + // Verification modified because InsertMany generates NEW IDs. + // We check if the total count matches and if a sample query works. + var countResult = await collectionTgt.Aggregate.OverAll(totalCount: true); - if (!srcObjects.Any()) + if (countResult.TotalCount != expectedCount) { - Console.WriteLine("No objects in source collection"); + Console.WriteLine( + $"Count mismatch. Expected {expectedCount}, found {countResult.TotalCount}" + ); return false; } - var sampledObjects = srcObjects.OrderBy(x => Guid.NewGuid()).Take(numSamples).ToList(); - - Console.WriteLine($"Verifying {sampledObjects.Count} random objects..."); - foreach (var srcObj in sampledObjects) + var sample = await collectionTgt.Query.FetchObjects(limit: 1); + if (sample.Objects.Count == 0 || !sample.FirstOrDefault().Properties.ContainsKey("title")) { - var tgtObj = await collectionTgt.Query.FetchObjectByID(srcObj.ID.Value); - if (tgtObj == null) - { - Console.WriteLine($"Object {srcObj.ID} not found in target collection"); - return false; - } - - var srcJson = JsonSerializer.Serialize(srcObj.Properties); - var tgtJson = JsonSerializer.Serialize(tgtObj.Properties); - if (srcJson != tgtJson) - { - Console.WriteLine($"Properties mismatch for object {srcObj.ID}"); - return false; - } + Console.WriteLine("Data verification failed. Properties missing."); + return false; } - Console.WriteLine("All sampled objects verified successfully!"); + + Console.WriteLine("Verification successful!"); return true; } @@ -170,6 +161,7 @@ private async Task CreateCollectionToCollection() { await CreateCollection(clientTgt, "WineReview", false); } + // END CreateCollectionCollectionToCollection [Fact] @@ -178,20 +170,25 @@ public async Task TestCollectionToCollection() { await CreateCollectionToCollection(); - var reviewsSrc = clientSrc.Collections.Use("WineReview"); - var reviewsTgt = clientTgt.Collections.Use("WineReview"); - await MigrateData(reviewsSrc, reviewsTgt); + var reviewsSrc = clientSrc.Collections.Use("WineReview"); + var reviewsTgt = clientTgt.Collections.Use("WineReview"); + + // Pass the Type to the generic method // END CollectionToCollection + await MigrateData(reviewsSrc, reviewsTgt); - Assert.Equal(DATASET_SIZE, (await reviewsTgt.Aggregate.OverAll(totalCount: true)).TotalCount); - Assert.True(await VerifyMigration(reviewsSrc, reviewsTgt, 5)); + Assert.True(await VerifyMigration(reviewsTgt, DATASET_SIZE)); + // START CollectionToCollection } + // END CollectionToCollection + // START CreateCollectionTenantToCollection private async Task CreateTenantToCollection() { await CreateCollection(clientTgt, "WineReview", false); } + // END CreateCollectionTenantToCollection [Fact] @@ -200,34 +197,41 @@ public async Task TestTenantToCollection() { await CreateTenantToCollection(); - var reviewsSrc = clientSrc.Collections.Use("WineReviewMT"); - var reviewsTgt = clientTgt.Collections.Use("WineReview"); + var reviewsSrc = clientSrc.Collections.Use("WineReviewMT"); + var reviewsTgt = clientTgt.Collections.Use("WineReview"); var reviewsSrcTenantA = reviewsSrc.WithTenant("tenantA"); - await MigrateData(reviewsSrcTenantA, reviewsTgt); - // END TenantToCollection - Assert.Equal(DATASET_SIZE, (await reviewsTgt.Aggregate.OverAll(totalCount: true)).TotalCount); - Assert.True(await VerifyMigration(reviewsSrcTenantA, reviewsTgt, 5)); + await MigrateData(reviewsSrcTenantA, reviewsTgt); + + // END TenantToCollection + Assert.True(await VerifyMigration(reviewsTgt, DATASET_SIZE)); + // START TenantToCollection } + // END TenantToCollection + // START CreateCollectionCollectionToTenant private async Task CreateCollectionToTenant() { await CreateCollection(clientTgt, "WineReviewMT", true); } + // END CreateCollectionCollectionToTenant - // START CreateTenants - // START CreateCollectionTenantToTenant + // START CreateTenants // START CreateCollectionTenantToTenant private async Task CreateTenants() { - var reviewsMtTgt = clientTgt.Collections.Use("WineReviewMT"); + var reviewsMtTgt = clientTgt.Collections.Use("WineReviewMT"); - var tenantsTgt = new[] { new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" } }; - await reviewsMtTgt.Tenants.Add(tenantsTgt); + var tenantsTgt = new[] + { + new Tenant { Name = "tenantA" }, + new Tenant { Name = "tenantB" }, + }; + await reviewsMtTgt.Tenants.Create(tenantsTgt); } - // END CreateTenants - // END CreateCollectionTenantToTenant + + // END CreateTenants // END CreateCollectionTenantToTenant [Fact] // START CollectionToTenant @@ -236,23 +240,26 @@ public async Task TestCollectionToTenant() await CreateCollectionToTenant(); await CreateTenants(); - var reviewsMtTgt = clientTgt.Collections.Use("WineReviewMT"); - var reviewsSrc = clientSrc.Collections.Use("WineReview"); + var reviewsMtTgt = clientTgt.Collections.Use("WineReviewMT"); + var reviewsSrc = clientSrc.Collections.Use("WineReview"); var reviewsTgtTenantA = reviewsMtTgt.WithTenant("tenantA"); - await MigrateData(reviewsSrc, reviewsTgtTenantA); + await MigrateData(reviewsSrc, reviewsTgtTenantA); // END CollectionToTenant - Assert.Equal(DATASET_SIZE, (await reviewsTgtTenantA.Aggregate.OverAll(totalCount: true)).TotalCount); - Assert.True(await VerifyMigration(reviewsSrc, reviewsTgtTenantA, 5)); + Assert.True(await VerifyMigration(reviewsTgtTenantA, DATASET_SIZE)); + // START CollectionToTenant } + // END CollectionToTenant + // START CreateCollectionTenantToTenant private async Task CreateTenantToTenant() { await CreateCollection(clientTgt, "WineReviewMT", true); } + // END CreateCollectionTenantToTenant [Fact] @@ -262,15 +269,16 @@ public async Task TestTenantToTenant() await CreateTenantToTenant(); await CreateTenants(); - var reviewsMtSrc = clientSrc.Collections.Use("WineReviewMT"); - var reviewsMtTgt = clientTgt.Collections.Use("WineReviewMT"); + var reviewsMtSrc = clientSrc.Collections.Use("WineReviewMT"); + var reviewsMtTgt = clientTgt.Collections.Use("WineReviewMT"); var reviewsSrcTenantA = reviewsMtSrc.WithTenant("tenantA"); var reviewsTgtTenantA = reviewsMtTgt.WithTenant("tenantA"); - await MigrateData(reviewsSrcTenantA, reviewsTgtTenantA); + await MigrateData(reviewsSrcTenantA, reviewsTgtTenantA); // END TenantToTenant - Assert.Equal(DATASET_SIZE, (await reviewsTgtTenantA.Aggregate.OverAll(totalCount: true)).TotalCount); - Assert.True(await VerifyMigration(reviewsSrcTenantA, reviewsTgtTenantA, 5)); + Assert.True(await VerifyMigration(reviewsTgtTenantA, DATASET_SIZE)); + // START TenantToTenant } -} \ No newline at end of file + // END TenantToTenant +} diff --git a/_includes/code/csharp/ManageCollectionsMultiTenancyTest.cs b/_includes/code/csharp/ManageCollectionsMultiTenancyTest.cs new file mode 100644 index 000000000..ded8fd697 --- /dev/null +++ b/_includes/code/csharp/ManageCollectionsMultiTenancyTest.cs @@ -0,0 +1,465 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; + +namespace WeaviateProject.Tests; + +public class ManageCollectionsMultiTenancyTest : IAsyncLifetime +{ + private WeaviateClient client; + + // Runs before each test (like @BeforeEach) + public async Task InitializeAsync() + { + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + if (string.IsNullOrWhiteSpace(openaiApiKey)) + { + throw new ArgumentException("Please set the OPENAI_API_KEY environment variable."); + } + + client = await Connect.Local(); + } + + // Runs after each test (like @AfterEach) + public async Task DisposeAsync() + { + // Clean up any collections created during the tests + await client.Collections.DeleteAll(); + } + + [Fact] + public async Task TestEnableMultiTenancy() + { + // START EnableMultiTenancy + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MultiTenancyCollection", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + // END EnableMultiTenancy + + var config = await client.Collections.Export("MultiTenancyCollection"); + Assert.True(config.MultiTenancyConfig.Enabled); + } + + [Fact] + public async Task TestEnableAutoActivationMultiTenancy() + { + // START EnableAutoActivation + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MultiTenancyCollection", + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantActivation = true, + }, + } + ); + // END EnableAutoActivation + + var config = await client.Collections.Export("MultiTenancyCollection"); + Assert.True(config.MultiTenancyConfig.AutoTenantActivation); + } + + [Fact] + public async Task TestEnableAutoMT() + { + // START EnableAutoMT + await client.Collections.Create( + new CollectionCreateParams + { + Name = "CollectionWithAutoMTEnabled", + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = true, + }, + } + ); + // END EnableAutoMT + + var config = await client.Collections.Export("CollectionWithAutoMTEnabled"); + Assert.True(config.MultiTenancyConfig.AutoTenantCreation); + } + + [Fact] + public async Task TestUpdateAutoMT() + { + string collectionName = "MTCollectionNoAutoMT"; + await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = false, + }, + } + ); + + // START UpdateAutoMT + var collection = client.Collections.Use(collectionName); + await collection.Config.Update(c => + { + c.MultiTenancyConfig.AutoTenantCreation = true; + }); + // END UpdateAutoMT + + var config = await client.Collections.Export(collectionName); + Assert.True(config.MultiTenancyConfig.AutoTenantCreation); + } + + [Fact] + public async Task TestAddTenantsToClass() + { + string collectionName = "MultiTenancyCollection"; + await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + + var collection = client.Collections.Use(collectionName); + + // START AddTenantsToClass + await collection.Tenants.Create(["tenantA", "tenantB"]); + // END AddTenantsToClass + + var tenants = (await collection.Tenants.List()).ToList(); + Assert.Equal(2, tenants.Count); + Assert.Contains(tenants, t => t.Name == "tenantA"); + Assert.Contains(tenants, t => t.Name == "tenantB"); + } + + [Fact] + public async Task TestListTenants() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + await collection.Tenants.Create(["tenantA", "tenantB"]); + + // START ListTenants + var tenants = await collection.Tenants.List(); + foreach (var t in tenants) + Console.WriteLine(t.Name); + // END ListTenants + + Assert.Equal(2, tenants.Count()); + } + + [Fact] + public async Task TestGetTenantsByName() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + await collection.Tenants.Create(["tenantA", "tenantB"]); + + // START GetTenantsByName + var tenantNames = new[] { "tenantA", "tenantB", "nonExistentTenant" }; + var tenants = await collection.Tenants.List(tenantNames); + foreach (var t in tenants) + Console.WriteLine(t.Name); + // END GetTenantsByName + + Assert.Equal(2, tenants.Count()); + } + + [Fact] + public async Task TestGetOneTenant() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + await collection.Tenants.Create(["tenantA"]); + + // START GetOneTenant + string tenantName = "tenantA"; + var tenant = await collection.Tenants.Get(tenantName); + Console.WriteLine(tenant?.Name); + // END GetOneTenant + + Assert.NotNull(tenant); + } + + [Fact] + public async Task TestActivateTenant() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + await collection.Tenants.Create(["tenantA"]); + + // START ActivateTenants + string[] tenantName = ["tenantA"]; + await collection.Tenants.Activate(tenantName); + // END ActivateTenants + + var tenant = await collection.Tenants.Get(tenantName.First()); + Assert.Equal(TenantActivityStatus.Active, tenant?.Status); + } + + [Fact] + public async Task TestDeactivateTenant() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = true, + }, + } + ); + await collection.Tenants.Create(["tenantA"]); + + // START DeactivateTenants + string[] tenantName = ["tenantA"]; + await collection.Tenants.Deactivate(tenantName); + // END DeactivateTenants + + var tenant = await collection.Tenants.Get(tenantName.First()); + Assert.Equal(TenantActivityStatus.Inactive, tenant?.Status); + } + + [Fact(Skip = "Requires offload-s3 module to be enabled")] + public async Task TestOffloadTenants() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + await collection.Tenants.Create(["tenantA"]); + // START OffloadTenants + await collection.Tenants.Offload(new[] { "tenantA" }); + // END OffloadTenants + + var tenants = (await collection.Tenants.List()).ToList(); + Assert.Single(tenants); + Assert.Equal("tenantA", tenants.First().Name); + } + + [Fact] + public async Task TestRemoveTenants() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + await collection.Tenants.Create(["tenantA", "tenantB"]); + // START RemoveTenants + await collection.Tenants.Delete(new[] { "tenantB", "tenantX" }); + // END RemoveTenants + + var tenants = (await collection.Tenants.List()).ToList(); + Assert.Single(tenants); + Assert.Equal("tenantA", tenants.First().Name); + } + + [Fact] + public async Task TestChangeTenantState() + { + string collectionName = "MultiTenancyCollection"; + await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = true, + }, + } + ); + + var collection = client.Collections.Use(collectionName); + await collection.Tenants.Create(["tenantA"]); + + // START ChangeTenantState + string tenantName = "tenantA"; + var multiCollection = client.Collections.Use(collectionName); + + // Deactivate + await multiCollection.Tenants.Update([ + new Tenant { Name = tenantName, Status = TenantActivityStatus.Inactive }, + ]); + + // Activate + await multiCollection.Tenants.Update([ + new Tenant { Name = tenantName, Status = TenantActivityStatus.Active }, + ]); + + // Offloading requires S3/warm/cold configuration + // END ChangeTenantState + + var tenants = await multiCollection.Tenants.List(); + Assert.Contains( + tenants, + t => t.Name == tenantName && t.Status == TenantActivityStatus.Active + ); + } + + [Fact] + public async Task TestCreateTenantObject() + { + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyQuestion", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + + var collection = client.Collections.Use("JeopardyQuestion"); + await collection.Tenants.Create(["tenantA"]); + + // START CreateMtObject + // highlight-start + var jeopardy = client.Collections.Use("JeopardyQuestion").WithTenant("tenantA"); + // highlight-end + + var uuid = await jeopardy.Data.Insert( + new + { + question = "This vector DB is OSS & supports automatic property type inference on import", + } + ); + + Console.WriteLine(uuid); // the return value is the object's UUID + // END CreateMtObject + + var result = await jeopardy.Query.FetchObjectByID(uuid); + Assert.NotNull(result); + } + + [Fact] + public async Task TestSearchTenant() + { + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyQuestion", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true }, + } + ); + + var jeopardyCollection = client.Collections.Use("JeopardyQuestion"); + await jeopardyCollection.Tenants.Create(["tenantA"]); + + // Insert some test data + var jeopardyTenant = jeopardyCollection.WithTenant("tenantA"); + await jeopardyTenant.Data.Insert(new { question = "Test question" }); + + // START Search + // highlight-start + var jeopardy = client.Collections.Use("JeopardyQuestion").WithTenant("tenantA"); + // highlight-end + + var response = await jeopardy.Query.FetchObjects(limit: 2); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END Search + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestAddReferenceToTenantObject() + { + await client.Collections.Delete("MultiTenancyCollection"); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MultiTenancyCollection", + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantActivation = true, + }, + } + ); + + var jeopardy = client.Collections.Use("JeopardyCategory"); + var categoryId = await jeopardy.Data.Insert(new { category = "Software" }); + + // START AddCrossRef + var multiCollection = client.Collections.Use("MultiTenancyCollection"); + await multiCollection.Tenants.Create(["tenantA"]); + // Add the cross-reference property to the multi-tenancy class + await multiCollection.Config.AddReference( + Property.Reference("hasCategory", "JeopardyCategory") + ); + + // Get collection specific to the required tenant + // highlight-start + var multiTenantA = multiCollection.WithTenant("tenantA"); + // highlight-end + + // Insert an object to tenantA + var objectId = await multiTenantA.Data.Insert( + new + { + question = "This vector DB is OSS & supports automatic property type inference on import", + } + ); + + // Add reference from MultiTenancyCollection object to a JeopardyCategory object + // highlight-start + await multiTenantA.Data.ReferenceAdd( + // highlight-end + from: objectId, // MultiTenancyCollection object id (a Jeopardy question) + fromProperty: "hasCategory", + to: categoryId // JeopardyCategory id + ); + // END AddCrossRef + + // Test + var result = await multiTenantA.Query.FetchObjectByID(objectId); + } +} diff --git a/_includes/code/csharp/ManageCollectionsTest.cs b/_includes/code/csharp/ManageCollectionsTest.cs index f2cb3f43e..9a5944583 100644 --- a/_includes/code/csharp/ManageCollectionsTest.cs +++ b/_includes/code/csharp/ManageCollectionsTest.cs @@ -1,10 +1,11 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; +using static Weaviate.Client.Models.VectorIndexConfig; // This attribute ensures that tests in this class do not run in parallel, // which is important because they share a client and perform cleanup operations @@ -24,15 +25,11 @@ static ManageCollectionsTest() throw new ArgumentException("Please set the OPENAI_API_KEY environment variable."); } - // TODO[g-despot] Headers are currently not supported var headers = new Dictionary { { "X-OpenAI-Api-Key", openaiApiKey } }; - var config = new ClientConfiguration - { - RestAddress = "localhost", - RestPort = 8080, - //Headers = headers - }; - client = new WeaviateClient(config); + client = Connect + .Local(hostname: "localhost", restPort: 8080, headers: headers) + .GetAwaiter() + .GetResult(); } // InitializeAsync is called before each test. We ensure all collections are deleted. @@ -52,10 +49,12 @@ public Task DisposeAsync() public async Task TestBasicCreateCollection() { // START BasicCreateCollection - await client.Collections.Create(new CollectionConfig { Name = "Article" }); + await client.Collections.Create(new CollectionCreateParams { Name = "Article" }); // END BasicCreateCollection + // START CheckIfExists bool exists = await client.Collections.Exists("Article"); + // END CheckIfExists Assert.True(exists); } @@ -63,15 +62,13 @@ public async Task TestBasicCreateCollection() public async Task TestCreateCollectionWithProperties() { // START CreateCollectionWithProperties - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - Properties = - [ - Property.Text("title"), - Property.Text("body"), - ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Article", + Properties = [Property.Text("title"), Property.Text("body")], + } + ); // END CreateCollectionWithProperties var config = await client.Collections.Export("Article"); @@ -93,12 +90,14 @@ public async Task TestCreateCollectionWithPropertiesFromClass() // public string Title { get; set; } // public string Body { get; set; } // } - - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - Properties = [.. Property.FromClass
()], - }); + + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Article", + Properties = [.. Property.FromClass
()], + } + ); // END CreateCollectionWithClassProperties var config = await client.Collections.Export("Article"); @@ -108,18 +107,16 @@ await client.Collections.Create(new CollectionConfig [Fact] public async Task TestAddProperties() { - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - Properties = - [ - Property.Text("title"), - Property.Text("body"), - ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Article", + Properties = [Property.Text("title"), Property.Text("body")], + } + ); // START AddProperty - CollectionClient articles = client.Collections.Use("Article"); + CollectionClient articles = client.Collections.Use("Article"); await articles.Config.AddProperty(Property.Text("description")); // END AddProperty @@ -131,51 +128,55 @@ await client.Collections.Create(new CollectionConfig public async Task TestCreateCollectionWithVectorizer() { // START Vectorizer - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }, - Properties = - [ - Property.Text("title"), - Property.Text("body"), - ] - }); + Name = "Article", + VectorConfig = new VectorConfigList + { + Configure.Vector("default", v => v.Text2VecTransformers()), + }, + Properties = [Property.Text("title"), Property.Text("body")], + } + ); // END Vectorizer var config = await client.Collections.Export("Article"); Assert.True(config.VectorConfig.ContainsKey("default")); - Assert.Equal("text2vec-contextionary", config.VectorConfig["default"].Vectorizer.Identifier); + Assert.Equal("text2vec-transformers", config.VectorConfig["default"].Vectorizer.Identifier); } [Fact] public async Task TestCreateCollectionWithNamedVectors() { // START BasicNamedVectors - await client.Collections.Create(new CollectionConfig - { - Name = "ArticleNV", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("title", new Vectorizer.Text2VecTransformers + Name = "ArticleNV", + VectorConfig = new VectorConfigList { - SourceProperties = ["title"] - }, new VectorIndex.HNSW()), - new VectorConfig("title_country", new Vectorizer.Text2VecTransformers - { - SourceProperties = ["title", "country"] - }, new VectorIndex.HNSW()), - new VectorConfig("custom_vector", new Vectorizer.SelfProvided(), new VectorIndex.HNSW()) - }, - Properties = - [ - Property.Text("title"), - Property.Text("country"), - ] - }); + Configure.Vector( + "title", + v => v.Text2VecTransformers(), + sourceProperties: ["title"], + index: new VectorIndex.HNSW() + ), + Configure.Vector( + "title_country", + v => v.Text2VecTransformers(), + sourceProperties: ["title", "country"], + index: new VectorIndex.HNSW() + ), + Configure.Vector( + "custom_vector", + v => v.SelfProvided(), + index: new VectorIndex.HNSW() + ), + }, + Properties = [Property.Text("title"), Property.Text("country")], + } + ); // END BasicNamedVectors var config = await client.Collections.Export("ArticleNV"); @@ -190,22 +191,19 @@ await client.Collections.Create(new CollectionConfig [Fact] public async Task ConfigurePropertyModuleSettings() { - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }, - Properties = - [ - Property.Text("title"), - Property.Text("body"), - ] - }); - var articles = client.Collections.Use("Article"); + Name = "Article", + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + Properties = [Property.Text("title"), Property.Text("body")], + } + ); + var articles = client.Collections.Use("Article"); // START AddNamedVectors - await articles.Config.AddVector(Configure.Vectors.Text2VecCohere().New("body_vector", sourceProperties: "body")); + await articles.Config.AddVector( + Configure.Vector("body_vector", v => v.Text2VecCohere(), sourceProperties: "body") + ); // END AddNamedVectors CollectionConfig config = await client.Collections.Export("Article"); @@ -213,27 +211,24 @@ await client.Collections.Create(new CollectionConfig //Assert.NotNull(config.VectorConfig["body_vector"]); } - // START AddNamedVectors - // Coming soon - // END AddNamedVectors - - // TODO[g-despot] {"error":[{"message":"parse vector index config: parse vector config for jina_colbert: multi vector index configured but vectorizer: \"text2vec-jinaai\" doesn't support multi vectors"}]} [Fact] public async Task CreateCollectionWithMultiVectors() { // START MultiValueVectorCollection - await client.Collections.Create(new CollectionConfig - { - Name = "DemoCollection", - VectorConfig = new VectorConfigList - { - // The factory function will automatically enable multi-vector support for the HNSW index - // Configure.MultiVectors.Text2VecJinaAI().New("jina_colbert"), - // Must explicitly enable multi-vector support for the HNSW index - Configure.MultiVectors.SelfProvided().New("custom_multi_vector"), - }, - Properties = [ Property.Text("text") ], - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "DemoCollection", + VectorConfig = + [ + // Example 1 - Use a model integration + Configure.MultiVector("jina_colbert", v => v.Text2MultiVecJinaAI()), + // Example 2 - User-provided multi-vector representations + Configure.MultiVector("custom_multi_vector", v => v.SelfProvided()), + ], + Properties = [Property.Text("text")], + } + ); // END MultiValueVectorCollection // Assert @@ -242,30 +237,61 @@ await client.Collections.Create(new CollectionConfig Assert.True(config.VectorConfig.ContainsKey("custom_multi_vector")); } - // START MultiValueVectorCollection - // Coming soon - // END MultiValueVectorCollection + [Fact] + public async Task TestMultiValueVectorMuvera() + { + // START MultiValueVectorMuvera + await client.Collections.Create( + new CollectionCreateParams + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + // Example 1 - Use a model integration + Configure.MultiVector( + "jina_colbert", + v => v.Text2MultiVecJinaAI(), + index: new VectorIndex.HNSW + { + MultiVector = new MultiVectorConfig { Encoding = new MuveraEncoding() }, + } + ), + // Example 2 - User-provided multi-vector representations + Configure.MultiVector( + "custom_multi_vector", + v => v.SelfProvided(), + index: new VectorIndex.HNSW + { + MultiVector = new MultiVectorConfig { Encoding = new MuveraEncoding() }, + } + ), + }, + } + ); + // END MultiValueVectorMuvera + + Assert.True(await client.Collections.Exists("DemoCollection")); + } [Fact] public async Task TestSetVectorIndexType() { // START SetVectorIndexType - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", - new Vectorizer.Text2VecTransformers(), - new VectorIndex.HNSW() - ) - }, - Properties = - [ - Property.Text("title"), - Property.Text("body"), - ] - }); + Name = "Article", + VectorConfig = new VectorConfigList + { + Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW() + ), + }, + Properties = [Property.Text("title"), Property.Text("body")], + } + ); // END SetVectorIndexType var config = await client.Collections.Export("Article"); @@ -276,29 +302,31 @@ await client.Collections.Create(new CollectionConfig public async Task TestSetVectorIndexParams() { // START SetVectorIndexParams - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", - new Vectorizer.Text2VecTransformers(), - new VectorIndex.HNSW - { - EfConstruction = 300, - Distance = VectorIndexConfig.VectorDistance.Cosine - } - ) - }, - Properties = - [ - Property.Text("title") - ] - }); + Name = "Article", + VectorConfig = new[] + { + Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW() + { + EfConstruction = 300, + Distance = VectorDistance.Cosine, + } + ), + }, + Properties = [Property.Text("title")], + } + ); // END SetVectorIndexParams var config = await client.Collections.Export("Article"); - var hnswConfig = Assert.IsType(config.VectorConfig["default"].VectorIndexConfig); + var hnswConfig = Assert.IsType( + config.VectorConfig["default"].VectorIndexConfig + ); Assert.Equal(300, hnswConfig.EfConstruction); Assert.Equal(VectorIndexConfig.VectorDistance.Cosine, hnswConfig.Distance); } @@ -307,23 +335,25 @@ await client.Collections.Create(new CollectionConfig public async Task TestSetInvertedIndexParams() { // START SetInvertedIndexParams - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - Properties = - [ - Property.Text("title", indexFilterable: true, indexSearchable: true), - Property.Text("chunk", indexFilterable: true, indexSearchable: true), - Property.Int("chunk_number", indexRangeFilters: true), - ], - InvertedIndexConfig = new InvertedIndexConfig + await client.Collections.Create( + new CollectionCreateParams { - Bm25 = new BM25Config { B = 1, K1 = 2 }, - IndexNullState = true, - IndexPropertyLength = true, - IndexTimestamps = true, + Name = "Article", + Properties = + [ + Property.Text("title", indexFilterable: true, indexSearchable: true), + Property.Text("chunk", indexFilterable: true, indexSearchable: true), + Property.Int("chunk_number", indexRangeFilters: true), + ], + InvertedIndexConfig = new InvertedIndexConfig + { + Bm25 = new BM25Config { B = 1, K1 = 2 }, + IndexNullState = true, + IndexPropertyLength = true, + IndexTimestamps = true, + }, } - }); + ); // END SetInvertedIndexParams var config = await client.Collections.Export("Article"); @@ -336,19 +366,15 @@ await client.Collections.Create(new CollectionConfig public async Task TestSetReranker() { // START SetReranker - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }, - RerankerConfig = new Reranker.Cohere(), - Properties = - [ - Property.Text("title") - ] - }); + Name = "Article", + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + RerankerConfig = Configure.Reranker.Cohere(), + Properties = [Property.Text("title")], + } + ); // END SetReranker var config = await client.Collections.Export("Article"); @@ -359,13 +385,13 @@ await client.Collections.Create(new CollectionConfig [Fact] public async Task TestUpdateReranker() { - await client.Collections.Create(new CollectionConfig { Name = "Article" }); + await client.Collections.Create(new CollectionCreateParams { Name = "Article" }); // START UpdateReranker - var collection = client.Collections.Use("Article"); + var collection = client.Collections.Use("Article"); await collection.Config.Update(c => { - c.RerankerConfig = new Reranker.Cohere(); + c.RerankerConfig = Configure.Reranker.Cohere(); }); // END UpdateReranker @@ -378,70 +404,91 @@ await collection.Config.Update(c => public async Task TestSetGenerative() { // START SetGenerative - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }, - GenerativeConfig = new GenerativeConfig.Cohere(), - Properties = - [ - Property.Text("title") - ] - }); + Name = "Article", + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + GenerativeConfig = Configure.Generative.Cohere(), + Properties = [Property.Text("title")], + } + ); // END SetGenerative var config = await client.Collections.Export("Article"); Assert.NotNull(config.GenerativeConfig); - Assert.Equal("generative-cohere", (config.GenerativeConfig as GenerativeConfig.Cohere)?.Type); + Assert.Equal( + "generative-cohere", + (config.GenerativeConfig as GenerativeConfig.Cohere)?.Type + ); } [Fact] public async Task TestUpdateGenerative() { - await client.Collections.Create(new CollectionConfig { Name = "Article" }); + await client.Collections.Create(new CollectionCreateParams { Name = "Article" }); // START UpdateGenerative - var collection = client.Collections.Use("Article"); + var collection = client.Collections.Use("Article"); await collection.Config.Update(c => { - c.GenerativeConfig = new GenerativeConfig.Cohere(); + c.GenerativeConfig = Configure.Generative.Cohere(); }); // END UpdateGenerative var config = await client.Collections.Export("Article"); Assert.NotNull(config.GenerativeConfig); - Assert.Equal("generative-cohere", (config.GenerativeConfig as GenerativeConfig.Cohere)?.Type); + Assert.Equal( + "generative-cohere", + (config.GenerativeConfig as GenerativeConfig.Cohere)?.Type + ); } - // START ModuleSettings - // Coming soon - // END ModuleSettings + [Fact] + public async Task TestCreateCollectionWithVectorizerSettings() + { + // START ModuleSettings + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Article", + VectorConfig = new VectorConfigList + { + Configure.Vector( + "default", + v => + v.Text2VecTransformers( + // The available settings depend on the module + // inferenceUrl: "http://custom-inference:8080", + // vectorizeCollectionName: false + ) + ), + }, + Properties = [Property.Text("title"), Property.Text("body")], + } + ); + // END ModuleSettings + + var config = await client.Collections.Export("Article"); + Assert.True(config.VectorConfig.ContainsKey("default")); + Assert.Equal("text2vec-transformers", config.VectorConfig["default"].Vectorizer.Identifier); + } - // TODO[g-despot]: Missing vectorizePropertyName [Fact] public async Task TestCreateCollectionWithPropertyConfig() { // START PropModuleSettings - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - Properties = - [ - Property.Text( - "title", - // vectorizePropertyName: true, - tokenization: PropertyTokenization.Lowercase - ), - Property.Text( - "body", - // skipVectorization: true, - tokenization: PropertyTokenization.Whitespace - ), - ], - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Article", + Properties = + [ + Property.Text("title", tokenization: PropertyTokenization.Lowercase), + Property.Text("body", tokenization: PropertyTokenization.Whitespace), + ], + } + ); // END PropModuleSettings var config = await client.Collections.Export("Article"); @@ -452,18 +499,14 @@ await client.Collections.Create(new CollectionConfig public async Task TestCreateCollectionWithTrigramTokenization() { // START TrigramTokenization - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }, - Properties = - [ - Property.Text("title", tokenization: PropertyTokenization.Trigram) - ] - }); + Name = "Article", + VectorConfig = new[] { Configure.Vector("default", v => v.Text2VecTransformers()) }, + Properties = [Property.Text("title", tokenization: PropertyTokenization.Trigram)], + } + ); // END TrigramTokenization var config = await client.Collections.Export("Article"); @@ -475,28 +518,27 @@ await client.Collections.Create(new CollectionConfig public async Task TestDistanceMetric() { // START DistanceMetric - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", - new Vectorizer.Text2VecTransformers(), - new VectorIndex.HNSW - { - Distance = VectorIndexConfig.VectorDistance.Cosine - } - ) - }, - Properties = - [ - Property.Text("title") - ] - }); + Name = "Article", + VectorConfig = new[] + { + Configure.Vector( + "default", + v => v.Text2VecTransformers(), + index: new VectorIndex.HNSW() { Distance = VectorDistance.Cosine } + ), + }, + Properties = [Property.Text("title")], + } + ); // END DistanceMetric var config = await client.Collections.Export("Article"); - var hnswConfig = Assert.IsType(config.VectorConfig["default"].VectorIndexConfig); + var hnswConfig = Assert.IsType( + config.VectorConfig["default"].VectorIndexConfig + ); Assert.Equal(VectorIndexConfig.VectorDistance.Cosine, hnswConfig.Distance); } @@ -504,11 +546,13 @@ await client.Collections.Create(new CollectionConfig public async Task TestReplicationSettings() { // START ReplicationSettings - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - ReplicationConfig = new ReplicationConfig { Factor = 1 } - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Article", + ReplicationConfig = new ReplicationConfig { Factor = 1 }, + } + ); // END ReplicationSettings var config = await client.Collections.Export("Article"); @@ -519,15 +563,13 @@ await client.Collections.Create(new CollectionConfig public async Task TestAsyncRepair() { // START AsyncRepair - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - ReplicationConfig = new ReplicationConfig + await client.Collections.Create( + new CollectionCreateParams { - Factor = 1, - AsyncEnabled = true + Name = "Article", + ReplicationConfig = new ReplicationConfig { Factor = 1, AsyncEnabled = true }, } - }); + ); // END AsyncRepair var config = await client.Collections.Export("Article"); @@ -538,16 +580,18 @@ await client.Collections.Create(new CollectionConfig public async Task TestAllReplicationSettings() { // START AllReplicationSettings - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - ReplicationConfig = new ReplicationConfig + await client.Collections.Create( + new CollectionCreateParams { - Factor = 1, - AsyncEnabled = true, - DeletionStrategy = DeletionStrategy.TimeBasedResolution, - }, - }); + Name = "Article", + ReplicationConfig = new ReplicationConfig + { + Factor = 1, + AsyncEnabled = true, + DeletionStrategy = DeletionStrategy.TimeBasedResolution, + }, + } + ); // END AllReplicationSettings var config = await client.Collections.Export("Article"); @@ -559,16 +603,18 @@ await client.Collections.Create(new CollectionConfig public async Task TestShardingSettings() { // START ShardingSettings - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - ShardingConfig = new ShardingConfig + await client.Collections.Create( + new CollectionCreateParams { - VirtualPerPhysical = 128, - DesiredCount = 1, - DesiredVirtualCount = 128, + Name = "Article", + ShardingConfig = new ShardingConfig + { + VirtualPerPhysical = 128, + DesiredCount = 1, + DesiredVirtualCount = 128, + }, } - }); + ); // END ShardingSettings var config = await client.Collections.Export("Article"); @@ -581,16 +627,18 @@ await client.Collections.Create(new CollectionConfig public async Task TestMultiTenancy() { // START Multi-tenancy - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - MultiTenancyConfig = new MultiTenancyConfig + await client.Collections.Create( + new CollectionCreateParams { - Enabled = true, - AutoTenantCreation = true, - AutoTenantActivation = true + Name = "Article", + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = true, + AutoTenantActivation = true, + }, } - }); + ); // END Multi-tenancy var config = await client.Collections.Export("Article"); @@ -600,10 +648,10 @@ await client.Collections.Create(new CollectionConfig [Fact] public async Task TestReadOneCollection() { - await client.Collections.Create(new CollectionConfig { Name = "Article" }); + await client.Collections.Create(new CollectionCreateParams { Name = "Article" }); // START ReadOneCollection - var articles = client.Collections.Use("Article"); + var articles = client.Collections.Use("Article"); var articlesConfig = await articles.Config.Get(); Console.WriteLine(articlesConfig); @@ -616,8 +664,8 @@ public async Task TestReadOneCollection() [Fact] public async Task TestReadAllCollections() { - await client.Collections.Create(new CollectionConfig { Name = "Article" }); - await client.Collections.Create(new CollectionConfig { Name = "Publication" }); + await client.Collections.Create(new CollectionCreateParams { Name = "Article" }); + await client.Collections.Create(new CollectionCreateParams { Name = "Publication" }); // START ReadAllCollections var response = new List(); @@ -628,7 +676,6 @@ public async Task TestReadAllCollections() } // END ReadAllCollections - Assert.Equal(2, response.Count); Assert.Contains(response, c => c.Name == "Article"); Assert.Contains(response, c => c.Name == "Publication"); } @@ -636,17 +683,16 @@ public async Task TestReadAllCollections() [Fact] public async Task TestUpdateCollection() { - await client.Collections.Create(new CollectionConfig - { - Name = "Article", - InvertedIndexConfig = new InvertedIndexConfig + await client.Collections.Create( + new CollectionCreateParams { - Bm25 = new BM25Config { K1 = 10 } + Name = "Article", + InvertedIndexConfig = new InvertedIndexConfig { Bm25 = new BM25Config { K1 = 10 } }, } - }); + ); // START UpdateCollection - var articles = client.Collections.Use("Article"); + var articles = client.Collections.Use("Article"); await articles.Config.Update(c => { @@ -664,7 +710,7 @@ await articles.Config.Update(c => public async Task TestDeleteCollection() { string collectionName = "Article"; - await client.Collections.Create(new CollectionConfig { Name = collectionName }); + await client.Collections.Create(new CollectionCreateParams { Name = collectionName }); Assert.True(await client.Collections.Exists(collectionName)); // START DeleteCollection @@ -677,9 +723,9 @@ public async Task TestDeleteCollection() // [Fact] // public async Task TestInspectCollectionShards() // { - // await client.Collections.Create(new CollectionConfig { Name = "Article" }); + // await client.Collections.Create(new CollectionCreateParams { Name = "Article" }); - // var articles = client.Collections.Use("Article"); + // var articles = client.Collections.Use("Article"); // var articleShards = await articles.Shards.Get(); // Console.WriteLine(string.Join(", ", articleShards.Select(s => s.Name))); @@ -694,11 +740,11 @@ public async Task TestDeleteCollection() // [Fact] // public async Task TestUpdateCollectionShards() // { - // await client.Collections.Create(new CollectionConfig { Name = "Article" }); - // var initialShards = await client.Collections.Use("Article").Shards.Get(); + // await client.Collections.Create(new CollectionCreateParams { Name = "Article" }); + // var initialShards = await client.Collections.Use("Article").Shards.Get(); // var shardName = initialShards.First().Name; - // var articles = client.Collections.Use("Article"); + // var articles = client.Collections.Use("Article"); // var articleShards = await articles.Shards.Update(shardName, "READONLY"); // Console.WriteLine(string.Join(", ", articleShards.Select(s => s.Status))); @@ -710,6 +756,4 @@ public async Task TestDeleteCollection() // START UpdateCollectionShards // Coming soon // END UpdateCollectionShards - - } diff --git a/_includes/code/csharp/ManageObjectsCreateTest.cs b/_includes/code/csharp/ManageObjectsCreateTest.cs index b19ef944f..52c93d3ae 100644 --- a/_includes/code/csharp/ManageObjectsCreateTest.cs +++ b/_includes/code/csharp/ManageObjectsCreateTest.cs @@ -1,12 +1,12 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -54,7 +54,7 @@ private static Guid GenerateUuid5(string seed) static ManageObjectsCreateTest() { // START INSTANTIATION-COMMON - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + client = Connect.Local().Result; // END INSTANTIATION-COMMON } @@ -64,54 +64,65 @@ public async Task InitializeAsync() await client.Collections.DeleteAll(); // Clean slate before tests // START Define the class - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyQuestion", - Properties = - [ - Property.Text("title", description: "Question title") - ], - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.Text2VecTransformers()) + Name = "JeopardyQuestion", + Properties = [Property.Text("title", description: "Question title")], + VectorConfig = new[] { Configure.Vector("default", v => v.Text2VecTransformers()) }, } - }); + ); - await client.Collections.Create(new CollectionConfig - { - Name = "WineReviewNV", - Properties = - [ - Property.Text("review_body", description: "Review body"), - Property.Text("title", description: "Name of the wine"), - Property.Text("country", description: "Originating country") - ], - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("title", new Vectorizer.Text2VecTransformers()), - new VectorConfig("review_body", new Vectorizer.Text2VecTransformers()), - new VectorConfig("title_country", new Vectorizer.Text2VecTransformers()) + Name = "WineReviewNV", + Properties = + [ + Property.Text("review_body", description: "Review body"), + Property.Text("title", description: "Name of the wine"), + Property.Text("country", description: "Originating country"), + ], + VectorConfig = new VectorConfigList + { + Configure.Vector( + "title", + v => v.Text2VecTransformers(), + sourceProperties: ["title"] + ), + Configure.Vector( + "review_body", + v => v.Text2VecTransformers(), + sourceProperties: ["review_body"] + ), + Configure.Vector( + "title_country", + v => v.Text2VecTransformers(), + sourceProperties: ["title", "country"] + ), + }, } - }); + ); // END Define the class // Additional collections for other tests - await client.Collections.Create(new CollectionConfig - { - Name = "Publication", - Properties = - [ - Property.GeoCoordinate("headquartersGeoLocation") - ] - }); - await client.Collections.Create(new CollectionConfig - { - Name = "Author", - VectorConfig = new VectorConfigList + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("default", new Vectorizer.SelfProvided()) + Name = "Publication", + Properties = [Property.GeoCoordinate("headquartersGeoLocation")], } - }); + ); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Author", + VectorConfig = new VectorConfigList + { + Configure.Vector("default", v => v.SelfProvided()), + }, + } + ); } // DisposeAsync acts like JUnit's @AfterAll for one-time teardown. @@ -124,16 +135,18 @@ public async Task DisposeAsync() public async Task TestCreateObject() { // START CreateSimpleObject - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); // highlight-start - var uuid = await jeopardy.Data.Insert(new - { - // highlight-end - question = "This vector DB is OSS & supports automatic property type inference on import", - // answer = "Weaviate", // properties can be omitted - newProperty = 123 // will be automatically added as a number property - }); + var uuid = await jeopardy.Data.Insert( + new + { + // highlight-end + question = "This vector DB is OSS & supports automatic property type inference on import", + // answer = "Weaviate", // properties can be omitted + newProperty = 123, // will be automatically added as a number property + } + ); Console.WriteLine(uuid); // the return value is the object's UUID // END CreateSimpleObject @@ -148,12 +161,12 @@ public async Task TestCreateObject() public async Task TestCreateObjectWithVector() { // START CreateObjectWithVector - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var uuid = await jeopardy.Data.Insert( new { question = "This vector DB is OSS and supports automatic property type inference on import", - answer = "Weaviate" + answer = "Weaviate", }, // highlight-start vectors: new float[300] // Using a zero vector for demonstration @@ -171,13 +184,13 @@ public async Task TestCreateObjectWithVector() public async Task TestCreateObjectNamedVectors() { // START CreateObjectNamedVectors - var reviews = client.Collections.Use("WineReviewNV"); // This collection must have named vectors configured + var reviews = client.Collections.Use("WineReviewNV"); // This collection must have named vectors configured var uuid = await reviews.Data.Insert( new { title = "A delicious Riesling", review_body = "This wine is a delicious Riesling which pairs well with seafood.", - country = "Germany" + country = "Germany", }, // highlight-start // Specify the named vectors, following the collection definition @@ -185,7 +198,7 @@ public async Task TestCreateObjectNamedVectors() { { "title", new float[1536] }, { "review_body", new float[1536] }, - { "title_country", new float[1536] } + { "title_country", new float[1536] }, } ); // highlight-end @@ -193,7 +206,7 @@ public async Task TestCreateObjectNamedVectors() Console.WriteLine(uuid); // the return value is the object's UUID // END CreateObjectNamedVectors - var result = await reviews.Query.FetchObjectByID(uuid, returnMetadata: MetadataOptions.Vector); + var result = await reviews.Query.FetchObjectByID(uuid, includeVectors: true); Assert.NotNull(result); Assert.NotNull(result.Vectors); Assert.Contains("title", result.Vectors.Keys); @@ -213,15 +226,15 @@ public async Task TestCreateObjectWithDeterministicId() var dataObject = new { question = "This vector DB is OSS and supports automatic property type inference on import", - answer = "Weaviate" + answer = "Weaviate", }; var dataObjectString = JsonSerializer.Serialize(dataObject); - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var uuid = await jeopardy.Data.Insert( dataObject, // highlight-start - id: GenerateUuid5(dataObjectString) + uuid: GenerateUuid5(dataObjectString) ); // highlight-end // END CreateObjectWithDeterministicId @@ -234,15 +247,15 @@ public async Task TestCreateObjectWithDeterministicId() public async Task TestCreateObjectWithId() { // START CreateObjectWithId - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var uuid = await jeopardy.Data.Insert( new { question = "This vector DB is OSS and supports automatic property type inference on import", - answer = "Weaviate" + answer = "Weaviate", }, // highlight-start - id: Guid.Parse("12345678-e64f-5d94-90db-c8cfa3fc1234") + uuid: Guid.Parse("12345678-e64f-5d94-90db-c8cfa3fc1234") ); // highlight-end @@ -252,20 +265,20 @@ public async Task TestCreateObjectWithId() var result = await jeopardy.Query.FetchObjectByID(uuid); Assert.NotNull(result); var props = result.Properties as IDictionary; - Assert.Equal("This vector DB is OSS and supports automatic property type inference on import", props["question"]); + Assert.Equal( + "This vector DB is OSS and supports automatic property type inference on import", + props["question"] + ); } [Fact] public async Task TestWithGeoCoordinates() { // START WithGeoCoordinates - var publications = client.Collections.Use("Publication"); + var publications = client.Collections.Use("Publication"); var uuid = await publications.Data.Insert( - new - { - headquartersGeoLocation = new GeoCoordinate(52.3932696f, 4.8374263f) - } + new { headquartersGeoLocation = new GeoCoordinate(52.3932696f, 4.8374263f) } ); // END WithGeoCoordinates @@ -282,11 +295,12 @@ public async Task TestCheckForAnObject() var objectUuid = GenerateUuid5("Author to fetch"); // END CheckForAnObject - var authors = client.Collections.Use("Author"); + var authors = client.Collections.Use("Author"); await authors.Data.Insert( new { name = "Author to fetch" }, - id: objectUuid, - vectors: new float[1536]); + uuid: objectUuid, + vectors: new float[1536] + ); // START CheckForAnObject // highlight-start @@ -304,4 +318,4 @@ await authors.Data.Insert( // START ValidateObject // Coming soon // END ValidateObject -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ManageObjectsDeleteTest.cs b/_includes/code/csharp/ManageObjectsDeleteTest.cs index 9b0c04aaa..dfaf68b45 100644 --- a/_includes/code/csharp/ManageObjectsDeleteTest.cs +++ b/_includes/code/csharp/ManageObjectsDeleteTest.cs @@ -1,10 +1,10 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -16,9 +16,7 @@ public class ManageObjectsDeleteTest : IAsyncLifetime // Static constructor for one-time setup (like @BeforeAll) static ManageObjectsDeleteTest() { - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + client = Connect.Local().GetAwaiter().GetResult(); } // Runs before each test (like @BeforeEach) @@ -28,11 +26,13 @@ public async Task InitializeAsync() { await client.Collections.Delete(COLLECTION_NAME); } - await client.Collections.Create(new CollectionConfig - { - Name = COLLECTION_NAME, - Properties = [ Property.Text("name") ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = COLLECTION_NAME, + Properties = [Property.Text("name")], + } + ); } // Runs after each test (like @AfterEach and @AfterAll) @@ -47,7 +47,7 @@ public async Task DisposeAsync() [Fact] public async Task TestDeleteObject() { - var collection = client.Collections.Use(COLLECTION_NAME); + var collection = client.Collections.Use(COLLECTION_NAME); var uuidToDelete = await collection.Data.Insert(new { name = "EphemeralObjectA" }); Assert.NotNull(await collection.Query.FetchObjectByID(uuidToDelete)); @@ -61,8 +61,9 @@ public async Task TestDeleteObject() [Fact] public async Task TestBatchDelete() { - var collection = client.Collections.Use(COLLECTION_NAME); - var objects = Enumerable.Range(0, 5) + var collection = client.Collections.Use(COLLECTION_NAME); + var objects = Enumerable + .Range(0, 5) .Select(i => new { name = $"EphemeralObject_{i}" }) .ToArray(); // Creates an array T[] @@ -73,7 +74,7 @@ public async Task TestBatchDelete() // START DeleteBatch await collection.Data.DeleteMany( // highlight-start - Filter.Property("name").Like("EphemeralObject*") + Filter.Property("name").IsLike("EphemeralObject*") // highlight-end ); // END DeleteBatch @@ -86,12 +87,8 @@ await collection.Data.DeleteMany( public async Task TestDeleteContains() { // START DeleteContains - var collection = client.Collections.Use(COLLECTION_NAME); - await collection.Data.InsertMany(new[] - { - new { name = "asia" }, - new { name = "europe" } - }); + var collection = client.Collections.Use(COLLECTION_NAME); + await collection.Data.InsertMany(new[] { new { name = "asia" }, new { name = "europe" } }); await collection.Data.DeleteMany( // highlight-start @@ -104,8 +101,9 @@ await collection.Data.DeleteMany( [Fact] public async Task TestDryRun() { - var collection = client.Collections.Use(COLLECTION_NAME); - var objects = Enumerable.Range(0, 5) + var collection = client.Collections.Use(COLLECTION_NAME); + var objects = Enumerable + .Range(0, 5) .Select(i => new { name = $"EphemeralObject_{i}" }) .ToArray(); // Creates an array T[] @@ -113,7 +111,7 @@ public async Task TestDryRun() // START DryRun var result = await collection.Data.DeleteMany( - Filter.Property("name").Like("EphemeralObject*"), + Filter.Property("name").IsLike("EphemeralObject*"), // highlight-start dryRun: true // highlight-end @@ -130,8 +128,9 @@ public async Task TestDryRun() [Fact] public async Task TestBatchDeleteWithIDs() { - var collection = client.Collections.Use(COLLECTION_NAME); - var objects = Enumerable.Range(0, 5) + var collection = client.Collections.Use(COLLECTION_NAME); + var objects = Enumerable + .Range(0, 5) .Select(i => new { name = $"EphemeralObject_{i}" }) .ToArray(); // Creates an array T[] @@ -141,16 +140,16 @@ public async Task TestBatchDeleteWithIDs() // START DeleteByIDBatch var queryResponse = await collection.Query.FetchObjects(limit: 3); - var ids = queryResponse.Objects.Select(obj => obj.ID.Value).ToList(); + var ids = queryResponse.Objects.Select(obj => obj.UUID.Value).ToList(); await collection.Data.DeleteMany( // highlight-start Filter.ID.ContainsAny(ids) // Delete the 3 objects - // highlight-end + // highlight-end ); // END DeleteByIDBatch var finalCount = await collection.Aggregate.OverAll(totalCount: true); Assert.Equal(2, finalCount.TotalCount); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ManageObjectsImportTest.cs b/_includes/code/csharp/ManageObjectsImportTest.cs index ce91d3028..c6d05e225 100644 --- a/_includes/code/csharp/ManageObjectsImportTest.cs +++ b/_includes/code/csharp/ManageObjectsImportTest.cs @@ -1,17 +1,17 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; -using System.Linq; +using System.Globalization; using System.IO; -using System.Text.Json; +using System.Linq; +using System.Net.Http; using System.Security.Cryptography; using System.Text; -using System.Net.Http; -using System.Globalization; +using System.Text.Json; +using System.Threading.Tasks; using CsvHelper; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -32,9 +32,7 @@ static ManageObjectsImportTest() throw new ArgumentException("Please set the OPENAI_API_KEY environment variable."); } - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + client = Connect.Local().GetAwaiter().GetResult(); // END INSTANTIATION-COMMON } @@ -71,7 +69,9 @@ private static Guid GenerateUuid5(string seed) public async Task InitializeAsync() { using var httpClient = new HttpClient(); - var jsonData = await httpClient.GetStreamAsync("https://raw.githubusercontent.com/weaviate-tutorials/edu-datasets/main/jeopardy_1k.json"); + var jsonData = await httpClient.GetStreamAsync( + "https://raw.githubusercontent.com/weaviate-tutorials/edu-datasets/main/jeopardy_1k.json" + ); using var fileStream = new FileStream(JsonDataFile, FileMode.Create, FileAccess.Write); await jsonData.CopyToAsync(fileStream); } @@ -81,7 +81,8 @@ public async Task DisposeAsync() { await client.Collections.DeleteAll(); File.Delete(JsonDataFile); - if (File.Exists(CsvDataFile)) File.Delete(CsvDataFile); + if (File.Exists(CsvDataFile)) + File.Delete(CsvDataFile); } private async Task BeforeEach() @@ -93,20 +94,22 @@ private async Task BeforeEach() public async Task TestBasicBatchImport() { await BeforeEach(); - await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + } + ); // START BasicBatchImportExample - var dataRows = Enumerable.Range(0, 5).Select(i => new { title = $"Object {i + 1}" }).ToList(); + var dataRows = Enumerable + .Range(0, 5) + .Select(i => new { title = $"Object {i + 1}" }) + .ToList(); - var collection = client.Collections.Use("MyCollection"); + var collection = client.Collections.Use("MyCollection"); - // The Java client uses insertMany for batching. - // There is no direct equivalent of the Python client's stateful batch manager. - // You collect objects and send them in a single request. // highlight-start var response = await collection.Data.InsertMany(dataRows); // highlight-end @@ -131,22 +134,31 @@ await client.Collections.Create(new CollectionConfig public async Task TestBatchImportWithID() { await BeforeEach(); - await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + } + ); // START BatchImportWithIDExample - var dataToInsert = new List<(object properties, Guid uuid)>(); + var dataToInsert = new List(); + var vectorData = Enumerable.Repeat(0.1f, 10).ToArray(); + for (int i = 0; i < 5; i++) { var dataRow = new { title = $"Object {i + 1}" }; var objUuid = GenerateUuid5(JsonSerializer.Serialize(dataRow)); - dataToInsert.Add((dataRow, objUuid)); + + var vectors = new Vectors { { "default", vectorData } }; + + dataToInsert.Add( + BatchInsertRequest.Create(data: dataRow, id: objUuid, vectors: vectors) + ); } - var collection = client.Collections.Use("MyCollection"); + var collection = client.Collections.Use("MyCollection"); // highlight-start var response = await collection.Data.InsertMany(dataToInsert); @@ -160,44 +172,52 @@ await client.Collections.Create(new CollectionConfig } // END BatchImportWithIDExample + Assert.Empty(failedObjects); + var result = await collection.Aggregate.OverAll(totalCount: true); Assert.Equal(5, result.TotalCount); - var lastUuid = dataToInsert[4].uuid; - Assert.NotNull(await collection.Query.FetchObjectByID(lastUuid)); + + var lastUuid = dataToInsert[4].ID; + Assert.NotNull(await collection.Query.FetchObjectByID((Guid)lastUuid)); } [Fact] public async Task TestBatchImportWithVector() { await BeforeEach(); - await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "MyCollection", + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + } + ); // START BatchImportWithVectorExample - var dataToInsert = new List<(object properties, Guid uuid, float[] vector)>(); - var vector = Enumerable.Repeat(0.1f, 10).ToArray(); + var dataToInsert = new List(); + var vectorData = Enumerable.Repeat(0.1f, 10).ToArray(); for (int i = 0; i < 5; i++) { var dataRow = new { title = $"Object {i + 1}" }; var objUuid = GenerateUuid5(JsonSerializer.Serialize(dataRow)); - dataToInsert.Add((dataRow, objUuid, vector)); + + var vectors = new Vectors { { "default", vectorData } }; + + dataToInsert.Add( + BatchInsertRequest.Create(data: dataRow, id: objUuid, vectors: vectors) + ); } - var collection = client.Collections.Use("MyCollection"); + var collection = client.Collections.Use("MyCollection"); - // highlight-start var response = await collection.Data.InsertMany(dataToInsert); - // highlight-end - var failedObjects = response.Where(r => r.Error != null).ToList(); - if (failedObjects.Any()) + // Handle errors + if (response.HasErrors) { - Console.WriteLine($"Number of failed imports: {failedObjects.Count}"); - Console.WriteLine($"First failed object: {failedObjects.First().Error}"); + Console.WriteLine($"Number of failed imports: {response.Errors.Count()}"); + Console.WriteLine($"First failed object: {response.Errors.First().Message}"); } // END BatchImportWithVectorExample @@ -209,24 +229,34 @@ await client.Collections.Create(new CollectionConfig public async Task TestBatchImportWithCrossReference() { await BeforeEach(); - await client.Collections.Create(new CollectionConfig { Name = "Publication", Properties = [Property.Text("title")] }); - await client.Collections.Create(new CollectionConfig - { - Name = "Author", - Properties = [Property.Text("name")], - References = [new Reference("writesFor", "Publication")] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Publication", + Properties = [Property.Text("title")], + } + ); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Author", + Properties = [Property.Text("name")], + References = [new Reference("writesFor", "Publication")], + } + ); - var authors = client.Collections.Use("Author"); - var publications = client.Collections.Use("Publication"); + var authors = client.Collections.Use("Author"); + var publications = client.Collections.Use("Publication"); var fromUuid = await authors.Data.Insert(new { name = "Jane Austen" }); var targetUuid = await publications.Data.Insert(new { title = "Ye Olde Times" }); // START BatchImportWithRefExample - var collection = client.Collections.Use("Author"); + var collection = client.Collections.Use("Author"); - var response = await collection.Data.ReferenceAddMany(new DataReference(fromUuid, "writesFor", targetUuid)); + var response = await collection.Data.ReferenceAddMany([ + new DataReference(fromUuid, "writesFor", targetUuid), + ]); if (response.HasErrors) { @@ -235,7 +265,10 @@ await client.Collections.Create(new CollectionConfig } // END BatchImportWithRefExample - var result = await collection.Query.FetchObjectByID(fromUuid, returnReferences: [new QueryReference("writesFor")]); + var result = await collection.Query.FetchObjectByID( + fromUuid, + returnReferences: [new QueryReference("writesFor")] + ); Assert.NotNull(result); Assert.True(result.References.ContainsKey("writesFor")); } @@ -244,48 +277,47 @@ await client.Collections.Create(new CollectionConfig public async Task TestImportWithNamedVectors() { await BeforeEach(); - await client.Collections.Create(new CollectionConfig - { - Name = "MyCollection", - VectorConfig = new[] + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("title", new Vectorizer.SelfProvided()), - new VectorConfig("body", new Vectorizer.SelfProvided()) - }, - Properties = [Property.Text("title"), Property.Text("body")] - }); + Name = "MyCollection", + VectorConfig = new[] + { + Configure.Vector("title", v => v.SelfProvided()), + Configure.Vector("body", v => v.SelfProvided()), + }, + Properties = [Property.Text("title"), Property.Text("body")], + } + ); // START BatchImportWithNamedVectors - // Prepare the data and vectors - var dataToInsert = new List<(object properties, Dictionary vectors)>(); + var dataToInsert = new List(); + for (int i = 0; i < 5; i++) { var dataRow = new { title = $"Object {i + 1}", body = $"Body {i + 1}" }; var titleVector = Enumerable.Repeat(0.12f, 1536).ToArray(); var bodyVector = Enumerable.Repeat(0.34f, 1536).ToArray(); + // highlight-start - var namedVectors = new Dictionary - { - { "title", titleVector }, - { "body", bodyVector } - }; - dataToInsert.Add((dataRow, namedVectors)); + var namedVectors = new Vectors { { "title", titleVector }, { "body", bodyVector } }; + + dataToInsert.Add(BatchInsertRequest.Create(dataRow, vectors: namedVectors)); // highlight-end } - var collection = client.Collections.Use("MyCollection"); + var collection = client.Collections.Use("MyCollection"); // Insert the data using InsertMany // highlight-start var response = await collection.Data.InsertMany(dataToInsert); // highlight-end - // Check for errors - var failedObjects = response.Where(r => r.Error != null).ToList(); - if (failedObjects.Any()) + // Handle errors + if (response.HasErrors) { - Console.WriteLine($"Number of failed imports: {failedObjects.Count}"); - Console.WriteLine($"First failed object error: {failedObjects.First().Error}"); + Console.WriteLine($"Number of failed imports: {response.Errors.Count()}"); + Console.WriteLine($"First failed object error: {response.Errors.First().Message}"); } // END BatchImportWithNamedVectors } @@ -294,21 +326,39 @@ await client.Collections.Create(new CollectionConfig public async Task TestJsonStreaming() { await BeforeEach(); - await client.Collections.Create(new CollectionConfig { Name = "JeopardyQuestion" }); + // Ensure using correct Collection creation syntax + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyQuestion", + // Optional: Define properties explicitly if needed, but auto-schema usually handles it + Properties = [Property.Text("question"), Property.Text("answer")], + } + ); // START JSON streaming int batchSize = 100; var batch = new List(batchSize); - var collection = client.Collections.Use("JeopardyQuestion"); + var collection = client.Collections.Use("JeopardyQuestion"); Console.WriteLine("JSON streaming, to avoid running out of memory on large files..."); using var fileStream = File.OpenRead(JsonDataFile); - var jsonObjects = JsonSerializer.DeserializeAsyncEnumerable>(fileStream); + + // Deserialize as JsonElement to handle types more safely/explicitly than Dictionary + var jsonObjects = JsonSerializer.DeserializeAsyncEnumerable(fileStream); await foreach (var obj in jsonObjects) { - if (obj == null) continue; - var properties = new { question = obj["Question"], answer = obj["Answer"] }; + // JsonElement is a struct, checking ValueKind is safer than null check + if (obj.ValueKind == JsonValueKind.Null || obj.ValueKind == JsonValueKind.Undefined) + continue; + + var properties = new + { + question = obj.GetProperty("Question").ToString(), + answer = obj.GetProperty("Answer").ToString(), + }; + batch.Add(properties); if (batch.Count == batchSize) @@ -341,25 +391,33 @@ public async Task TestCsvStreaming() using (var writer = new StreamWriter(CsvDataFile)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { - var jsonObjects = JsonSerializer.DeserializeAsyncEnumerable>(fileStream); + var jsonObjects = JsonSerializer.DeserializeAsyncEnumerable>( + fileStream + ); csv.WriteHeader(); await csv.NextRecordAsync(); await foreach (var obj in jsonObjects) { if (obj != null) { - csv.WriteRecord(new JeopardyQuestion { Question = obj["Question"]?.ToString(), Answer = obj["Answer"]?.ToString() }); + csv.WriteRecord( + new JeopardyQuestion + { + Question = obj["Question"]?.ToString(), + Answer = obj["Answer"]?.ToString(), + } + ); await csv.NextRecordAsync(); } } } - await client.Collections.Create(new CollectionConfig { Name = "JeopardyQuestion" }); + await client.Collections.Create(new CollectionCreateParams { Name = "JeopardyQuestion" }); // START CSV streaming int batchSize = 100; var batch = new List(batchSize); - var collection = client.Collections.Use("JeopardyQuestion"); + var collection = client.Collections.Use("JeopardyQuestion"); Console.WriteLine("CSV streaming to not load all records in RAM at once..."); using (var reader = new StreamReader(CsvDataFile)) @@ -396,7 +454,7 @@ public async Task TestCsvStreaming() // Helper class for CSV parsing private class JeopardyQuestion { - public string? Question { get; set; } - public string? Answer { get; set; } + public string Question { get; set; } + public string Answer { get; set; } } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ManageObjectsReadAllTest.cs b/_includes/code/csharp/ManageObjectsReadAllTest.cs index 784bc1752..38635138e 100644 --- a/_includes/code/csharp/ManageObjectsReadAllTest.cs +++ b/_includes/code/csharp/ManageObjectsReadAllTest.cs @@ -1,28 +1,22 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; +using System.Drawing; using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; public class ManageObjectsReadAllTest : IAsyncLifetime { - private static readonly WeaviateClient client; - - // Static constructor for one-time setup (like @BeforeAll) - static ManageObjectsReadAllTest() - { - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); - } + private WeaviateClient client; // Runs once before any tests in the class public async Task InitializeAsync() { + client = await Connect.Local(hostname: "localhost", restPort: 8080); // Simulate weaviate-datasets by creating and populating collections // Create WineReview collection if (await client.Collections.Exists("WineReview")) @@ -30,26 +24,32 @@ public async Task InitializeAsync() await client.Collections.Delete("WineReview"); } - var wineReview = await client.Collections.Create(new CollectionConfig { Name = "WineReview" }); - await wineReview.Data.InsertMany(new[] - { - new { title = "Review A" }, - new { title = "Review B" } - }); + var wineReview = await client.Collections.Create( + new CollectionCreateParams { Name = "WineReview" } + ); + await wineReview.Data.InsertMany( + new[] { new { title = "Review A" }, new { title = "Review B" } } + ); // Create WineReviewMT collection if (await client.Collections.Exists("WineReviewMT")) { await client.Collections.Delete("WineReviewMT"); } - var wineReviewMT = await client.Collections.Create(new CollectionConfig - { - Name = "WineReviewMT", - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } - }); + var wineReviewMT = await client.Collections.Create( + new CollectionCreateParams + { + Name = "WineReviewMT", + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = true, + }, + } + ); // Create and populate tenants - await wineReviewMT.Tenants.Add(["tenantA", "tenantB"]); + await wineReviewMT.Tenants.Create(["tenantA", "tenantB"]); await wineReviewMT.WithTenant("tenantA").Data.Insert(new { title = "Tenant A Review 1" }); await wineReviewMT.WithTenant("tenantB").Data.Insert(new { title = "Tenant B Review 1" }); } @@ -65,13 +65,13 @@ public async Task DisposeAsync() public async Task TestReadAllProps() { // START ReadAllProps - var collection = client.Collections.Use("WineReview"); + var collection = client.Collections.Use("WineReview"); // highlight-start await foreach (var item in collection.Iterator()) { // highlight-end - Console.WriteLine($"{item.ID} {JsonSerializer.Serialize(item.Properties)}"); + Console.WriteLine($"{item.UUID} {JsonSerializer.Serialize(item.Properties)}"); } // END ReadAllProps } @@ -80,12 +80,14 @@ public async Task TestReadAllProps() public async Task TestReadAllVectors() { // START ReadAllVectors - var collection = client.Collections.Use("WineReview"); + var collection = client.Collections.Use("WineReview"); - await foreach (var item in collection.Iterator( - // highlight-start - returnMetadata: MetadataOptions.Vector // If using named vectors, you can specify ones to include - )) + await foreach ( + var item in collection.Iterator( + // highlight-start + includeVectors: true // If using named vectors, you can specify ones to include + ) + ) // highlight-end { Console.WriteLine(JsonSerializer.Serialize(item.Properties)); @@ -96,12 +98,11 @@ public async Task TestReadAllVectors() // END ReadAllVectors } - // TODO[g-despot] Grpc.Core.RpcException : Status(StatusCode="Unknown", Detail="explorer: list class: search: object search at index winereviewmt: class WineReviewMT has multi-tenancy enabled, but request was without tenant") [Fact] public async Task TestReadAllTenants() { // START ReadAllTenants - var multiCollection = client.Collections.Use("WineReviewMT"); + var multiCollection = client.Collections.Use("WineReviewMT"); // Get a list of tenants // highlight-start @@ -122,4 +123,4 @@ public async Task TestReadAllTenants() } // END ReadAllTenants } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ManageObjectsReadTest.cs b/_includes/code/csharp/ManageObjectsReadTest.cs index 5ed97582e..7647debd7 100644 --- a/_includes/code/csharp/ManageObjectsReadTest.cs +++ b/_includes/code/csharp/ManageObjectsReadTest.cs @@ -1,11 +1,11 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; using static Weaviate.Client.Auth; namespace WeaviateProject.Tests; @@ -22,7 +22,7 @@ static ManageObjectsReadTest() string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); - client = Connect.Cloud(weaviateUrl, weaviateApiKey); + client = Connect.Cloud(weaviateUrl, weaviateApiKey).Result; // END INSTANTIATION-COMMON } @@ -38,10 +38,12 @@ public void Dispose() public async Task TestReadObject() { // START ReadSimpleObject - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); // highlight-start - var dataObject = await jeopardy.Query.FetchObjectByID(Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b")); + var dataObject = await jeopardy.Query.FetchObjectByID( + Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b") + ); // highlight-end if (dataObject != null) @@ -55,11 +57,12 @@ public async Task TestReadObject() public async Task TestReadObjectWithVector() { // START ReadObjectWithVector - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); - var dataObject = await jeopardy.Query.FetchObjectByID(Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b"), + var dataObject = await jeopardy.Query.FetchObjectByID( + Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b"), // highlight-start - returnMetadata: MetadataOptions.Vector + includeVectors: true ); // highlight-end @@ -75,7 +78,7 @@ public async Task TestReadObjectWithVector() public async Task TestReadObjectNamedVectors() { // START ReadObjectNamedVectors - var reviews = client.Collections.Use("WineReviewNV"); // Collection with named + var reviews = client.Collections.Use("WineReviewNV"); // Collection with named // END ReadObjectNamedVectors // vectors var someObjResponse = await reviews.Query.FetchObjects(limit: 1); @@ -83,13 +86,14 @@ public async Task TestReadObjectNamedVectors() { return; // Skip if no data } - var objUuid = someObjResponse.Objects.First().ID; + var objUuid = someObjResponse.Objects.First().UUID; var vectorNames = new List { "title", "review_body" }; // START ReadObjectNamedVectors - var dataObject = await reviews.Query.FetchObjectByID(objUuid.Value, // Object UUID - // highlight-start - returnMetadata: MetadataOptions.Vector // Specify to include vectors + var dataObject = await reviews.Query.FetchObjectByID( + objUuid.Value, // Object UUID + // highlight-start + includeVectors: true // Specify to include vectors ); // highlight-end @@ -111,13 +115,15 @@ public async Task TestReadObjectNamedVectors() public async Task TestCheckObject() { // START CheckForAnObject - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); // The C# client checks for existence by attempting to fetch an object and checking for null. - var dataObject = await jeopardy.Query.FetchObjectByID(Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b")); + var dataObject = await jeopardy.Query.FetchObjectByID( + Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b") + ); bool exists = dataObject != null; Console.WriteLine(exists); // END CheckForAnObject } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ManageObjectsUpdateTest.cs b/_includes/code/csharp/ManageObjectsUpdateTest.cs index d92e6452b..7f73bd097 100644 --- a/_includes/code/csharp/ManageObjectsUpdateTest.cs +++ b/_includes/code/csharp/ManageObjectsUpdateTest.cs @@ -1,11 +1,10 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; -using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -17,9 +16,7 @@ public class ManageObjectsUpdateTest : IAsyncLifetime static ManageObjectsUpdateTest() { // START INSTANTIATION-COMMON - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + client = Connect.Local().GetAwaiter().GetResult(); // END INSTANTIATION-COMMON } @@ -31,35 +28,63 @@ public async Task InitializeAsync() { await client.Collections.Delete("WineReviewNV"); } - await client.Collections.Create(new CollectionConfig - { - Name = "WineReviewNV", - Properties = - [ - Property.Text("review_body", description: "Review body"), - Property.Text("title", description: "Name of the wine"), - Property.Text("country", description: "Originating country") - ], - VectorConfig = new[] + await client.Collections.Create( + new CollectionCreateParams { - new VectorConfig("title", new Vectorizer.Text2VecTransformers()), - new VectorConfig("review_body", new Vectorizer.Text2VecTransformers()), - new VectorConfig( - "title_country", - new Vectorizer.Text2VecTransformers { SourceProperties = ["title", "country"] } - ) + Name = "WineReviewNV", + Properties = + [ + Property.Text("review_body", description: "Review body"), + Property.Text("title", description: "Name of the wine"), + Property.Text("country", description: "Originating country"), + ], + VectorConfig = new[] + { + Configure.Vector( + "title", + v => v.Text2VecTransformers(), + sourceProperties: ["title"] + ), + Configure.Vector( + "review_body", + v => v.Text2VecTransformers(), + sourceProperties: ["review_body"] + ), + Configure.Vector( + "title_country", + v => v.Text2VecTransformers(), + sourceProperties: ["title", "country"] + ), + }, } - }); + ); // highlight-start // ===== Add three mock objects to the WineReviewNV collection ===== - var reviews = client.Collections.Use("WineReviewNV"); - await reviews.Data.InsertMany(new[] - { - new { title = "Mock Wine A", review_body = "A fine mock vintage.", country = "Mocktugal" }, - new { title = "Mock Wine B", review_body = "Notes of mockberry.", country = "Mockstralia" }, - new { title = "Mock Wine C", review_body = "Pairs well with mock turtle soup.", country = "Republic of Mockdova" } - }); + var reviews = client.Collections.Use("WineReviewNV"); + await reviews.Data.InsertMany( + new[] + { + new + { + title = "Mock Wine A", + review_body = "A fine mock vintage.", + country = "Mocktugal", + }, + new + { + title = "Mock Wine B", + review_body = "Notes of mockberry.", + country = "Mockstralia", + }, + new + { + title = "Mock Wine C", + review_body = "Pairs well with mock turtle soup.", + country = "Republic of Mockdova", + }, + } + ); // highlight-end // START Define the class @@ -67,18 +92,20 @@ await reviews.Data.InsertMany(new[] { await client.Collections.Delete("JeopardyQuestion"); } - await client.Collections.Create(new CollectionConfig - { - Name = "JeopardyQuestion", - Description = "A Jeopardy! question", - Properties = - [ - Property.Text("question", description: "The question"), - Property.Text("answer", description: "The answer"), - Property.Number("points", description: "The points the question is worth") - ], - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()) - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = + [ + Property.Text("question", description: "The question"), + Property.Text("answer", description: "The answer"), + Property.Number("points", description: "The points the question is worth"), + ], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), + } + ); // END Define the class } @@ -90,10 +117,14 @@ public async Task DisposeAsync() } // START DelProps - private static async Task DelProps(WeaviateClient client, Guid uuidToUpdate, string collectionName, - IEnumerable propNames) + private static async Task DelProps( + WeaviateClient client, + Guid uuidToUpdate, + string collectionName, + IEnumerable propNames + ) { - var collection = client.Collections.Use(collectionName); + var collection = client.Collections.Use(collectionName); // fetch the object to update var objectData = await collection.Query.FetchObjectByID(uuidToUpdate); @@ -111,24 +142,28 @@ private static async Task DelProps(WeaviateClient client, Guid uuidToUpdate, str // replace the properties await collection.Data.Replace(uuidToUpdate, propertiesToUpdate); } + // END DelProps [Fact] public async Task TestUpdateAndReplaceFlow() { - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); - var uuid = await jeopardy.Data.Insert(new - { - question = "Test question", - answer = "Test answer", - points = -1 - }); + var uuid = await jeopardy.Data.Insert( + new + { + question = "Test question", + answer = "Test answer", + points = -1, + } + ); // START UpdateProps - await jeopardy.Data.Replace(uuid, - // highlight-start - data: new { points = 100 } + await jeopardy.Data.Replace( + uuid, + // highlight-start + properties: new { points = 100 } // highlight-end ); // END UpdateProps @@ -138,62 +173,59 @@ await jeopardy.Data.Replace(uuid, var props1 = result1.Properties as IDictionary; Assert.Equal(100d, props1["points"]); + var vector = Enumerable.Repeat(0.12345f, 384).ToArray(); - var vector = Enumerable.Repeat(0.12345f, 300).ToArray(); - - // TODO[g-despot] Not implemented - // START UpdateVector - // Coming soon + // START UpdateVector + await jeopardy.Data.Replace( + uuid, + properties: new { points = 100 }, + // highlight-start + vectors: vector + // highlight-end + ); // END UpdateVector - // await jeopardy.Data.Update(uuid, - // properties: new { points = 100 }, - // // highlight-start - // vector: vector - // // highlight-end - // ); - - // var result2 = await jeopardy.Query.FetchObjectByID(uuid, returnMetadata: MetadataOptions.Vector); - // Assert.NotNull(result2); - // Assert.Equal(300, result2.Vectors["default"].Dimensions); + var result2 = await jeopardy.Query.FetchObjectByID(uuid, includeVectors: true); + Assert.NotNull(result2); + Assert.Equal(384, result2.Vectors["default"].Dimensions.cols); - // TODO[g-despot] Not implemented // START UpdateNamedVector - // Coming soon - // END UpdateNamedVector + var reviews = client.Collections.Use("WineReviewNV"); - var reviews = client.Collections.Use("WineReviewNV"); - var reviewResponse = await reviews.Query.FetchObjects(limit: 1); - var reviewUuid = reviewResponse.Objects.First().ID.Value; - - var titleVector = Enumerable.Repeat(0.12345f, 300).ToArray(); - var reviewBodyVector = Enumerable.Repeat(0.23456f, 300).ToArray(); - var titleCountryVector = Enumerable.Repeat(0.34567f, 300).ToArray(); - - // await reviews.Data.Update(reviewUuid, - // data: new - // { - // title = "A delicious wine", - // review_body = "This mystery wine is a delight to the senses.", - // country = "Mordor" - // }, - // // highlight-start - // vectors: new Dictionary - // { - // { "title", titleVector }, - // { "review_body", reviewBodyVector }, - // { "title_country", titleCountryVector } - // } - // // highlight-end - // ); + // Fetch an object to update + var result = await reviews.Query.FetchObjects(limit: 3); + var reviewUuid = result.Objects.First().UUID.Value; + // Create vectors + float[] titleVector = Enumerable.Repeat(0.12345f, 384).ToArray(); + float[] reviewBodyVector = Enumerable.Repeat(0.12345f, 384).ToArray(); + float[] titleCountryVector = Enumerable.Repeat(0.12345f, 384).ToArray(); + + await reviews.Data.Replace( + uuid: reviewUuid, + properties: new + { + title = "A delicious wine", + review_body = "This mystery wine is a delight to the senses.", + country = "Mordor", + }, + // highlight-start + vectors: new Vectors + { + { "title", titleVector }, + { "review_body", reviewBodyVector }, + { "title_country", titleCountryVector }, + } + // highlight-end + ); + // END UpdateNamedVector // START Replace // highlight-start await jeopardy.Data.Replace( // highlight-end uuid, - data: new { answer = "Replaced" } + properties: new { answer = "Replaced" } // The other properties will be deleted ); // END Replace @@ -214,4 +246,4 @@ await jeopardy.Data.Replace( var props4 = result4.Properties as IDictionary; Assert.False(props4.ContainsKey("answer")); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/ModelProvidersTest.cs b/_includes/code/csharp/ModelProvidersTest.cs index e69de29bb..0d67eb006 100644 --- a/_includes/code/csharp/ModelProvidersTest.cs +++ b/_includes/code/csharp/ModelProvidersTest.cs @@ -0,0 +1,274 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; + +public class ModelProvidersTest : IAsyncLifetime +{ + private WeaviateClient client; + + public async Task InitializeAsync() + { + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + // Create the client for the test class context + if (!string.IsNullOrEmpty(weaviateUrl)) + { + client = await Connect.Cloud(weaviateUrl, weaviateApiKey); + } + else + { + // Fallback/Mock for compilation if env vars aren't set + client = await Connect.Local(); + } + + // Cleanup before tests + if (await client.Collections.Exists("DemoCollection")) + { + await client.Collections.Delete("DemoCollection"); + } + } + + public async Task DisposeAsync() + { + if (client != null && await client.Collections.Exists("DemoCollection")) + { + await client.Collections.Delete("DemoCollection"); + } + } + + [Fact] + public async Task TestWeaviateInstantiation() + { + // START WeaviateInstantiation + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + // highlight-start + using var client = await Connect.Cloud( + weaviateUrl, // Replace with your Weaviate Cloud URL + weaviateApiKey // Replace with your Weaviate Cloud key + ); + + var meta = await client.GetMeta(); + Console.WriteLine(meta.Version); + // highlight-end + // END WeaviateInstantiation + } + + [Fact] + public async Task TestWeaviateVectorizer() + { + if (await client.Collections.Exists("DemoCollection")) + await client.Collections.Delete("DemoCollection"); + + // START BasicVectorizerWeaviate + await client.Collections.Create( + new CollectionCreateParams + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + Configure.Vector( + "title_vector", + v => v.Text2VecWeaviate(), + sourceProperties: ["title"] + ), + }, + Properties = [Property.Text("title"), Property.Text("description")], + } + ); + // END BasicVectorizerWeaviate + + var config = await client.Collections.Export("DemoCollection"); + Assert.True(config.VectorConfig.ContainsKey("title_vector")); + Assert.Equal( + "text2vec-weaviate", + config.VectorConfig["title_vector"].Vectorizer.Identifier + ); + + await client.Collections.Delete("DemoCollection"); + } + + [Fact] + public async Task TestWeaviateVectorizerModel() + { + // START VectorizerWeaviateCustomModel + await client.Collections.Create( + new CollectionCreateParams + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + Configure.Vector( + "title_vector", + v => v.Text2VecWeaviate(model: "Snowflake/snowflake-arctic-embed-l-v2.0"), + sourceProperties: ["title"] + ), + }, + Properties = [Property.Text("title"), Property.Text("description")], + } + ); + // END VectorizerWeaviateCustomModel + + var config = await client.Collections.Export("DemoCollection"); + Assert.True(config.VectorConfig.ContainsKey("title_vector")); + Assert.Equal( + "text2vec-weaviate", + config.VectorConfig["title_vector"].Vectorizer.Identifier + ); + + await client.Collections.Delete("DemoCollection"); + } + + [Fact] + public async Task TestWeaviateVectorizerParameters() + { + // START SnowflakeArcticEmbedMV15 + await client.Collections.Create( + new CollectionCreateParams + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + Configure.Vector( + "title_vector", + v => + v.Text2VecWeaviate( + model: "Snowflake/snowflake-arctic-embed-m-v1.5" + // baseURL: null, + // dimensions: 0 + ), + sourceProperties: ["title"] + ), + }, + Properties = [Property.Text("title"), Property.Text("description")], + } + ); + // END SnowflakeArcticEmbedMV15 + + var config = await client.Collections.Export("DemoCollection"); + Assert.True(config.VectorConfig.ContainsKey("title_vector")); + Assert.Equal( + "text2vec-weaviate", + config.VectorConfig["title_vector"].Vectorizer.Identifier + ); + } + + [Fact] + public async Task TestInsertData() + { + // Ensure collection exists from previous test steps or recreate + if (!await client.Collections.Exists("DemoCollection")) + { + await TestWeaviateVectorizerParameters(); // Re-run creation + } + + // START BatchImportExample + // Define the source objects + var sourceObjects = new[] + { + new + { + title = "The Shawshank Redemption", + description = "A wrongfully imprisoned man forms an inspiring friendship while finding hope and redemption in the darkest of places.", + }, + new + { + title = "The Godfather", + description = "A powerful mafia family struggles to balance loyalty, power, and betrayal in this iconic crime saga.", + }, + new + { + title = "The Dark Knight", + description = "Batman faces his greatest challenge as he battles the chaos unleashed by the Joker in Gotham City.", + }, + new + { + title = "Jingle All the Way", + description = "A desperate father goes to hilarious lengths to secure the season's hottest toy for his son on Christmas Eve.", + }, + new + { + title = "A Christmas Carol", + description = "A miserly old man is transformed after being visited by three ghosts on Christmas Eve in this timeless tale of redemption.", + }, + }; + + // Get a handle to the collection + var collection = client.Collections.Use("DemoCollection"); + + // Insert the data using insertMany + var response = await collection.Data.InsertMany(sourceObjects); + + // Check for errors + if (response.HasErrors) + { + Console.WriteLine($"Number of failed imports: {response.Errors.Count()}"); + Console.WriteLine($"First failed object error: {response.Errors.First().Message}"); + } + else + { + Console.WriteLine($"Successfully inserted {response.Objects.Count()} objects."); + } + // END BatchImportExample + + Assert.False(response.HasErrors); + } + + [Fact] + public async Task TestNearText() + { + // Ensure data exists + await TestInsertData(); + + // START NearTextExample + var collection = client.Collections.Use("DemoCollection"); + + // highlight-start + var response = await collection.Query.NearText( + "A holiday film", // The model provider integration will automatically vectorize the query + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + // highlight-end + + foreach (var o in response.Objects) + { + Console.WriteLine(o.Properties["title"]); + } + // END NearTextExample + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestHybrid() + { + // Ensure data exists + await TestInsertData(); + + // START HybridExample + var collection = client.Collections.Use("DemoCollection"); + + // highlight-start + var response = await collection.Query.Hybrid( + "A holiday film", // The model provider integration will automatically vectorize the query + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + // highlight-end + + foreach (var o in response.Objects) + { + Console.WriteLine(o.Properties["title"]); + } + // END HybridExample + + Assert.NotEmpty(response.Objects); + } +} diff --git a/_includes/code/csharp/QuickstartLocalTest.cs b/_includes/code/csharp/QuickstartLocalTest.cs index b90729c78..d6fc27dfc 100644 --- a/_includes/code/csharp/QuickstartLocalTest.cs +++ b/_includes/code/csharp/QuickstartLocalTest.cs @@ -1,11 +1,12 @@ -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Text.Json; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Generative; using Xunit; namespace WeaviateProject.Examples; @@ -17,7 +18,7 @@ public class QuickstartLocalTest public async Task TestConnectionIsReady() { // START InstantiationExample - using var client = Connect.Local(); + using var client = await Connect.Local(); // highlight-start // GetMeta returns server info. A successful call indicates readiness. @@ -32,7 +33,7 @@ public async Task TestConnectionIsReady() [Fact] public async Task FullQuickstartWorkflowTest() { - using var client = Connect.Local(); + using var client = await Connect.Local(); string collectionName = "Question"; // Clean up previous runs if they exist @@ -43,39 +44,47 @@ public async Task FullQuickstartWorkflowTest() // START CreateCollection // highlight-start - var questions = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - Properties = - [ + var questions = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + Properties = + [ Property.Text("answer"), Property.Text("question"), - Property.Text("category") - ], - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()), // Configure the text2vec-contextionary integration - GenerativeConfig = new GenerativeConfig.Cohere() // Configure the Cohere generative AI integration - }); + Property.Text("category"), + ], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), // Configure the text2vec-transformers integration + GenerativeConfig = Configure.Generative.Cohere(), // Configure the Cohere generative AI integration + } + ); // highlight-end // END CreateCollection // START Import // Get JSON data using HttpClient using var httpClient = new HttpClient(); - var jsonData = await httpClient.GetStringAsync("https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json"); + var jsonData = await httpClient.GetStringAsync( + "https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json" + ); // highlight-start var questionsToInsert = new List(); // Parse and prepare objects using System.Text.Json - var jsonObjects = JsonSerializer.Deserialize>>(jsonData); + var jsonObjects = JsonSerializer.Deserialize>>( + jsonData + ); foreach (var jsonObj in jsonObjects) { - questionsToInsert.Add(new - { - answer = jsonObj["Answer"].GetString(), - question = jsonObj["Question"].GetString(), - category = jsonObj["Category"].GetString() - }); + questionsToInsert.Add( + new + { + answer = jsonObj["Answer"].GetString(), + question = jsonObj["Question"].GetString(), + category = jsonObj["Category"].GetString(), + } + ); } // Call InsertMany with the list of objects converted to an array @@ -83,17 +92,16 @@ public async Task FullQuickstartWorkflowTest() // highlight-end // END Import - // TODO[g-despot] Error handling missing // Check for errors - // if (insertResponse.HasErrors) - // { - // Console.WriteLine($"Number of failed imports: {insertResponse.Errors.Count}"); - // Console.WriteLine($"First failed object error: {insertResponse.Errors.First()}"); - // } - // else - // { - // Console.WriteLine($"Successfully inserted {insertResponse.Results.Count} objects."); - // } + if (insertResponse.HasErrors) + { + Console.WriteLine($"Number of failed imports: {insertResponse.Errors.Count()}"); + Console.WriteLine($"First failed object error: {insertResponse.Errors.First()}"); + } + else + { + Console.WriteLine($"Successfully inserted {insertResponse.Objects.Count()} objects."); + } // START NearText // highlight-start @@ -105,9 +113,20 @@ public async Task FullQuickstartWorkflowTest() Console.WriteLine(JsonSerializer.Serialize(obj.Properties)); } // END NearText - } - // START RAG - // Coming soon - // END RAG -} \ No newline at end of file + // START RAG + // highlight-start + var ragResponse = await questions.Generate.NearText( + "biology", + limit: 2, + groupedTask: new GroupedTask("Write a tweet with emojis about these facts."), + provider: new Providers.OpenAI { } + ); + // highlight-end + + // Inspect the results + Console.WriteLine(JsonSerializer.Serialize(ragResponse.Generative.Values)); + // END RAG + await client.Collections.Delete(collectionName); + } +} diff --git a/_includes/code/csharp/QuickstartTest.cs b/_includes/code/csharp/QuickstartTest.cs index e062f1e69..894f41c68 100644 --- a/_includes/code/csharp/QuickstartTest.cs +++ b/_includes/code/csharp/QuickstartTest.cs @@ -1,10 +1,12 @@ -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Text.Json; using System.Collections.Generic; +using System.Linq; using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Generative; using Xunit; namespace WeaviateProject.Examples; @@ -12,7 +14,6 @@ namespace WeaviateProject.Examples; [Collection("Sequential")] // Ensures tests in this class run one after another public class QuickstartTest { - // TODO[g-despot] Replace meta with readiness [Fact] public static async Task TestConnectionIsReady() { @@ -21,14 +22,11 @@ public static async Task TestConnectionIsReady() string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); - WeaviateClient client = Connect.Cloud( - weaviateUrl, - weaviateApiKey - ); + WeaviateClient client = await Connect.Cloud(weaviateUrl, weaviateApiKey); // highlight-start // GetMeta returns server info. A successful call indicates readiness. - var meta = await client.GetMeta(); + var meta = await client.IsReady(); Console.WriteLine(meta); // highlight-end // END InstantiationExample @@ -40,11 +38,13 @@ public static async Task FullQuickstartWorkflowTest() // Best practice: store your credentials in environment variables string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); string collectionName = "Question"; - WeaviateClient client = Connect.Cloud( + WeaviateClient client = await Connect.Cloud( weaviateUrl, - weaviateApiKey + weaviateApiKey, + headers: new Dictionary { { "X-OpenAI-Api-Key", openaiApiKey } } ); if (await client.Collections.Exists(collectionName)) { @@ -52,39 +52,47 @@ public static async Task FullQuickstartWorkflowTest() } // START CreateCollection // highlight-start - var questions = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - Properties = - [ + var questions = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + Properties = + [ Property.Text("answer"), Property.Text("question"), - Property.Text("category") - ], - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), // Configure the Weaviate Embeddings integration - GenerativeConfig = new GenerativeConfig.Cohere() // Configure the Cohere generative AI integration - }); + Property.Text("category"), + ], + VectorConfig = Configure.Vector("default", v => v.Text2VecWeaviate()), // Configure the Weaviate Embeddings integration + GenerativeConfig = Configure.Generative.Cohere(), // Configure the Cohere generative AI integration + } + ); // highlight-end // END CreateCollection // START Import // Get JSON data using HttpClient using var httpClient = new HttpClient(); - var jsonData = await httpClient.GetStringAsync("https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json"); + var jsonData = await httpClient.GetStringAsync( + "https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json" + ); // highlight-start var questionsToInsert = new List(); // Parse and prepare objects using System.Text.Json - var jsonObjects = JsonSerializer.Deserialize>>(jsonData); + var jsonObjects = JsonSerializer.Deserialize>>( + jsonData + ); foreach (var jsonObj in jsonObjects) { - questionsToInsert.Add(new - { - answer = jsonObj["Answer"].GetString(), - question = jsonObj["Question"].GetString(), - category = jsonObj["Category"].GetString() - }); + questionsToInsert.Add( + new + { + answer = jsonObj["Answer"].GetString(), + question = jsonObj["Question"].GetString(), + category = jsonObj["Category"].GetString(), + } + ); } // Call InsertMany with the list of objects converted to an array @@ -92,17 +100,16 @@ public static async Task FullQuickstartWorkflowTest() // highlight-end // END Import - // TODO[g-despot] Error handling missing // Check for errors - // if (insertResponse.HasErrors) - // { - // Console.WriteLine($"Number of failed imports: {insertResponse.Errors.Count}"); - // Console.WriteLine($"First failed object error: {insertResponse.Errors.First()}"); - // } - // else - // { - // Console.WriteLine($"Successfully inserted {insertResponse.Results.Count} objects."); - // } + if (insertResponse.HasErrors) + { + Console.WriteLine($"Number of failed imports: {insertResponse.Errors.Count()}"); + Console.WriteLine($"First failed object error: {insertResponse.Errors.First()}"); + } + else + { + Console.WriteLine($"Successfully inserted {insertResponse.Objects.Count()} objects."); + } // START NearText // highlight-start @@ -115,10 +122,19 @@ public static async Task FullQuickstartWorkflowTest() } // END NearText + // START RAG + // highlight-start + var ragResponse = await questions.Generate.NearText( + "biology", + limit: 2, + groupedTask: new GroupedTask("Write a tweet with emojis about these facts."), + provider: new Providers.OpenAI() { } + ); + // highlight-end + + // Inspect the results + Console.WriteLine(JsonSerializer.Serialize(ragResponse.Generative.Values)); + // END RAG await client.Collections.Delete(collectionName); } - - // START RAG - // Coming soon - // END RAG -} \ No newline at end of file +} diff --git a/_includes/code/csharp/RBACTest.cs b/_includes/code/csharp/RBACTest.cs new file mode 100644 index 000000000..a83b85b50 --- /dev/null +++ b/_includes/code/csharp/RBACTest.cs @@ -0,0 +1,478 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Weaviate.Client.Models; +using Xunit; + +namespace Weaviate.Client.Tests.RBAC; + +public class RBACTest : IAsyncLifetime +{ + private WeaviateClient client; + private const string RootUserKey = "root-user-key"; + + public async Task InitializeAsync() + { + // START AdminClient + // Connect to Weaviate as root user + client = await Connect.Local(restPort: 8580, grpcPort: 50551, credentials: RootUserKey); + // END AdminClient + + await Cleanup(); + } + + public Task DisposeAsync() + { + // C# client manages resources automatically, but we can run cleanup + return Cleanup(); + } + + private async Task Cleanup() + { + // Clean up all test roles + var builtInRoles = new List { "admin", "root", "viewer", "read-only" }; + var allRoles = await client.Roles.ListAll(); + + foreach (var role in allRoles) + { + if (!builtInRoles.Contains(role.Name)) + { + await client.Roles.Delete(role.Name); + } + } + + // Clean up all test users + var allUsers = await client.Users.Db.List(); + foreach (var user in allUsers) + { + if (user.UserId != "root-user") + { + await client.Users.Db.Delete(user.UserId); + } + } + } + + [Fact] + public async Task TestRolePermissionTypes() + { + // START AddManageRolesPermission + var rolesPermissions = new PermissionScope[] + { + new Permissions.Roles("testRole*", RolesScope.Match) // Only allow role management with the current user's permission level + { + Create = true, // Allow creating roles + Read = true, // Allow reading roles + Update = true, // Allow updating roles + Delete = true, // Allow deleting roles + }, + }; + + await client.Roles.Create("testRole_ManageRoles", rolesPermissions); + // END AddManageRolesPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageRoles")); + + // START AddManageUsersPermission + var usersPermissions = new PermissionScope[] + { + new Permissions.Users("testUser*") // Applies to all users starting with "testUser" + { + Create = true, // Allow creating users + Read = true, // Allow reading user info + Update = true, // Allow rotating user API key + Delete = true, // Allow deleting users + AssignAndRevoke = true, // Allow assigning and revoking roles to and from users + }, + }; + + await client.Roles.Create("testRole_ManageUsers", usersPermissions); + // END AddManageUsersPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageUsers")); + + // START AddCollectionsPermission + var collectionsPermissions = new PermissionScope[] + { + new Permissions.Collections("TargetCollection*") // Applies to all collections starting with "TargetCollection" + { + Create = true, // Allow creating new collections + Read = true, // Allow reading collection info/metadata + Update = true, // Allow updating collection configuration + Delete = true, // Allow deleting collections + }, + }; + + await client.Roles.Create("testRole_ManageCollections", collectionsPermissions); + // END AddCollectionsPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageCollections")); + + // START AddTenantPermission + var tenantsPermissions = new PermissionScope[] + { + new Permissions.Tenants("TargetCollection*", "TargetTenant*") // Applies to specified collections/tenants + { + Create = true, // Allow creating new tenants + Read = true, // Allow reading tenant info/metadata + Update = true, // Allow updating tenant states + Delete = true, // Allow deleting tenants + }, + }; + + await client.Roles.Create("testRole_ManageTenants", tenantsPermissions); + // END AddTenantPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageTenants")); + + // START AddDataObjectPermission + var dataPermissions = new PermissionScope[] + { + new Permissions.Data("TargetCollection*", "TargetTenant*") // Applies to all collections starting with "TargetCollection" + { + Create = true, // Allow data inserts + Read = true, // Allow query and fetch operations + Update = true, // Allow data updates + Delete = true, // Allow data deletes + }, + }; + + await client.Roles.Create("testRole_ManageData", dataPermissions); + // END AddDataObjectPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageData")); + + // START AddBackupPermission + var backupPermissions = new PermissionScope[] + { + new Permissions.Backups("TargetCollection*") // Applies to all collections starting with "TargetCollection" + { + Manage = true, // Allow managing backups + }, + }; + + await client.Roles.Create("testRole_ManageBackups", backupPermissions); + // END AddBackupPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageBackups")); + + // START AddClusterPermission + var clusterPermissions = new PermissionScope[] + { + new Permissions.Cluster { Read = true }, // Allow reading cluster data + }; + + await client.Roles.Create("testRole_ReadCluster", clusterPermissions); + // END AddClusterPermission + Assert.NotNull(await client.Roles.Get("testRole_ReadCluster")); + + // START AddNodesPermission + var verbosePermissions = new PermissionScope[] + { + new Permissions.Nodes("TargetCollection*", NodeVerbosity.Verbose) // Applies to all collections starting with "TargetCollection" + { + Read = true, // Allow reading node metadata + }, + }; + + await client.Roles.Create("testRole_ReadNodes", verbosePermissions); + // END AddNodesPermission + Assert.NotNull(await client.Roles.Get("testRole_ReadNodes")); + + // START AddAliasPermission + var aliasPermissions = new PermissionScope[] + { + new Permissions.Alias("TargetCollection*", "TargetAlias*") + { + Create = true, // Allow alias creation + Read = true, // Allow listing aliases + Update = true, // Allow updating aliases + // Delete is false by default + }, + }; + + await client.Roles.Create("testRole_ManageAliases", aliasPermissions); + // END AddAliasPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageAliases")); + + // START AddReplicationsPermission + var replicatePermissions = new PermissionScope[] + { + new Permissions.Replicate("TargetCollection*", "TargetShard*") + { + Create = true, // Allow replica movement operations + Read = true, // Allow retrieving replication status + Update = true, // Allow cancelling replication operations + // Delete is false by default + }, + }; + + await client.Roles.Create("testRole_ManageReplicas", replicatePermissions); + // END AddReplicationsPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageReplicas")); + + // START AddGroupsPermission + var groupsPermissions = new PermissionScope[] + { + new Permissions.Groups("TargetGroup*", RbacGroupType.Oidc) + { + Read = true, // Allow reading group information + AssignAndRevoke = true, // Allow assigning and revoking group memberships + }, + }; + + await client.Roles.Create("testRole_ManageGroups", groupsPermissions); + // END AddGroupsPermission + Assert.NotNull(await client.Roles.Get("testRole_ManageGroups")); + } + + [Fact] + public async Task TestRoleLifecycle() + { + string testRole = "testRole"; + string testUser = "custom-user"; + + var initialPermissions = new PermissionScope[] + { + new Permissions.Collections("TargetCollection*") { Read = true }, + }; + await client.Roles.Create(testRole, initialPermissions); + + // START AddRoles + var additionalPermissions = new PermissionScope[] + { + new Permissions.Data("TargetCollection*", "TargetTenant*") { Create = true }, + }; + await client.Roles.AddPermissions(testRole, additionalPermissions); + // END AddRoles + + // START CheckRoleExists + // In C#, we check by attempting to get the role + var retrievedRole = await client.Roles.Get(testRole); + bool exists = retrievedRole != null; + Console.WriteLine(exists); + // END CheckRoleExists + Assert.True(exists); + + // START InspectRole + var testRoleData = await client.Roles.Get(testRole); + Console.WriteLine(testRoleData); + // END InspectRole + Assert.NotNull(testRoleData); + Assert.Equal(2, testRoleData.Permissions.Count()); + + // Check for presence of specific permission types + Assert.Contains(testRoleData.Permissions, p => p is Permissions.Collections { Read: true }); + Assert.Contains(testRoleData.Permissions, p => p is Permissions.Data { Create: true }); + + await client.Users.Db.Create(testUser); + await client.Users.Db.AssignRoles(testUser, new[] { testRole }); + + // START AssignedUsers + var assignedUsers = await client.Roles.GetUserAssignments(testRole); + foreach (var assignment in assignedUsers) + { + Console.WriteLine(assignment.UserId); + } + // END AssignedUsers + Assert.Contains(assignedUsers, a => a.UserId == testUser); + + // START ListAllRoles + var allRoles = await client.Roles.ListAll(); + foreach (var role in allRoles) + { + Console.WriteLine($"{role.Name} {role}"); + } + // END ListAllRoles + Assert.Contains(allRoles, r => r.Name == testRole); + + // START RemovePermissions + var permissionsToRemove = new PermissionScope[] + { + new Permissions.Collections("TargetCollection*") { Read = true }, + new Permissions.Data("TargetCollection*", "TargetTenant*") { Create = true }, + }; + await client.Roles.RemovePermissions(testRole, permissionsToRemove); + // END RemovePermissions + + var roleAfterRemove = await client.Roles.Get(testRole); + Assert.Empty(roleAfterRemove.Permissions); + + // START DeleteRole + await client.Roles.Delete(testRole); + // END DeleteRole + + // Assert role is gone (Get throws NotFound or returns null depending on implementation, assuming similar to Exists check) + // Based on provided Integration tests, Get throws NotFound when deleted if wrapped, or we check List + await Assert.ThrowsAsync(async () => + await client.Roles.Get(testRole) + ); + } + + [Fact] + public async Task TestRoleExamples() + { + string testUser = "custom-user"; + await client.Users.Db.Create(testUser); + + // START ReadWritePermissionDefinition + // Define permissions (example confers read+write rights to collections starting with "TargetCollection") + var rwPermissions = new PermissionScope[] + { + // Collection level permissions + new Permissions.Collections("TargetCollection*") + { + Create = true, // Allow creating new collections + Read = true, // Allow reading collection info/metadata + Update = true, // Allow updating collection configuration + Delete = true, // Allow deleting collections + }, + // Collection data level permissions + new Permissions.Data("TargetCollection*", "TargetTenant*") + { + Create = true, // Allow data inserts + Read = true, // Allow query and fetch operations + Update = true, // Allow data updates + Delete = true, // Allow data deletes + }, + new Permissions.Backups("TargetCollection*") { Manage = true }, + new Permissions.Nodes("TargetCollection*", NodeVerbosity.Verbose) { Read = true }, + new Permissions.Cluster { Read = true }, + }; + + // Create a new role + await client.Roles.Create("rw_role", rwPermissions); + // END ReadWritePermissionDefinition + + // START ReadWritePermissionAssignment + // Assign the role to a user + await client.Users.Db.AssignRoles(testUser, new[] { "rw_role" }); + // END ReadWritePermissionAssignment + + var userRoles = await client.Users.Db.GetRoles(testUser); + Assert.Contains(userRoles, r => r.Name == "rw_role"); + await client.Users.Db.RevokeRoles(testUser, new[] { "rw_role" }); + + // START ViewerPermissionDefinition + // Define permissions (example confers viewer rights to collections starting with "TargetCollection") + var viewerPermissions = new PermissionScope[] + { + new Permissions.Collections("TargetCollection*") { Read = true }, + new Permissions.Data("TargetCollection*", "TargetTenant*") { Read = true }, + }; + + // Create a new role + await client.Roles.Create("viewer_role", viewerPermissions); + // END ViewerPermissionDefinition + + // START ViewerPermissionAssignment + // Assign the role to a user + await client.Users.Db.AssignRoles(testUser, new[] { "viewer_role" }); + // END ViewerPermissionAssignment + + userRoles = await client.Users.Db.GetRoles(testUser); + Assert.Contains(userRoles, r => r.Name == "viewer_role"); + await client.Users.Db.RevokeRoles(testUser, new[] { "viewer_role" }); + + // START MTPermissionsExample + var mtPermissions = new PermissionScope[] + { + new Permissions.Tenants("TargetCollection*", "TargetTenant*") + { + Create = true, // Allow creating new tenants + Read = true, // Allow reading tenant info/metadata + Update = true, // Allow updating tenant states + Delete = true, // Allow deleting tenants + }, + new Permissions.Data("TargetCollection*", "TargetTenant*") + { + Create = true, // Allow data inserts + Read = true, // Allow query and fetch operations + Update = true, // Allow data updates + Delete = true, // Allow data deletes + }, + }; + + // Create a new role + await client.Roles.Create("tenant_manager", mtPermissions); + // END MTPermissionsExample + + // START MTPermissionsAssignment + // Assign the role to a user + await client.Users.Db.AssignRoles(testUser, new[] { "tenant_manager" }); + // END MTPermissionsAssignment + + userRoles = await client.Users.Db.GetRoles(testUser); + Assert.Contains(userRoles, r => r.Name == "tenant_manager"); + } + + [Fact] + public async Task TestUserLifecycle() + { + string testUser = "custom-user"; + string testRole = "testRole"; + + try + { + await client.Users.Db.Delete(testUser); + } + catch + { /* ignore if not exists */ + } + + // START CreateUser + string userApiKey = await client.Users.Db.Create(testUser); + Console.WriteLine(userApiKey); + // END CreateUser + Assert.False(string.IsNullOrEmpty(userApiKey)); + + // START RotateApiKey + string newApiKey = await client.Users.Db.RotateApiKey(testUser); + Console.WriteLine(newApiKey); + // END RotateApiKey + Assert.False(string.IsNullOrEmpty(newApiKey)); + Assert.NotEqual(userApiKey, newApiKey); + + var permissions = new PermissionScope[] + { + new Permissions.Collections("TargetCollection*") { Read = true }, + }; + await client.Roles.Create(testRole, permissions); + + // START AssignRole + await client.Users.Db.AssignRoles(testUser, new[] { testRole, "viewer" }); + // END AssignRole + + var roles = await client.Users.Db.GetRoles(testUser); + var roleNames = roles.Select(r => r.Name).ToList(); + Assert.Contains(testRole, roleNames); + Assert.Contains("viewer", roleNames); + + // START ListAllUsers + var allUsers = await client.Users.Db.List(); + Console.WriteLine(string.Join(", ", allUsers.Select(u => u.UserId))); + // END ListAllUsers + Assert.Contains(allUsers, u => u.UserId == testUser); + + // START ListUserRoles + var userRoles = await client.Users.Db.GetRoles(testUser); + foreach (var role in userRoles) + { + Console.WriteLine(role.Name); + } + // END ListUserRoles + var userRoleNames = userRoles.Select(r => r.Name).ToList(); + Assert.Contains(testRole, userRoleNames); + Assert.Contains("viewer", userRoleNames); + + // START RevokeRoles + await client.Users.Db.RevokeRoles(testUser, new[] { testRole }); + // END RevokeRoles + + roles = await client.Users.Db.GetRoles(testUser); + roleNames = roles.Select(r => r.Name).ToList(); + Assert.DoesNotContain(testRole, roleNames); + Assert.Contains("viewer", roleNames); + + // START DeleteUser + await client.Users.Db.Delete(testUser); + // END DeleteUser + + var usersAfterDelete = await client.Users.Db.List(); + Assert.DoesNotContain(usersAfterDelete, u => u.UserId == testUser); + } +} diff --git a/_includes/code/csharp/README.md b/_includes/code/csharp/README.md new file mode 100644 index 000000000..928d9edb1 --- /dev/null +++ b/_includes/code/csharp/README.md @@ -0,0 +1,6 @@ +To run all the tests, use this command: +- `dotnet test WeaviateProject.Tests.csproj` +- `dotnet test WeaviateProject.Tests.csproj --filter "FullyQualifiedName~ConfigurePQTest"` + +To run quickstart examples, use this command: +- `dotnet run --project WeaviateProject.csproj` diff --git a/_includes/code/csharp/ReplicationTest.cs b/_includes/code/csharp/ReplicationTest.cs new file mode 100644 index 000000000..10f47de0c --- /dev/null +++ b/_includes/code/csharp/ReplicationTest.cs @@ -0,0 +1,180 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; + +public class ReplicationTest : IAsyncLifetime +{ + private WeaviateClient client; + private const string CollectionName = "MyReplicatedDocCollection"; + + public async Task InitializeAsync() + { + // Connect to local Weaviate instance (Ports match Python script) + // Assuming a multi-node cluster is running at these ports + client = await Connect.Local(restPort: 8180, grpcPort: 50151); + + // Cleanup from previous runs + if (await client.Collections.Exists(CollectionName)) + { + await client.Collections.Delete(CollectionName); + } + await client.Cluster.Replications.DeleteAll(); + } + + public Task DisposeAsync() + { + return Task.CompletedTask; + } + + [Fact] + public async Task TestReplicationWorkflow() + { + // Setup: Create collection with Replication Factor = 2 + await client.Collections.Create( + new CollectionCreateParams + { + Name = CollectionName, + Properties = [Property.Text("title"), Property.Text("body")], + ReplicationConfig = new ReplicationConfig { Factor = 2 }, + } + ); + + var replicaCollection = client.Collections.Use(CollectionName); + + // Insert dummy data + await replicaCollection.Data.Insert( + new + { + title = "Lorem Ipsum", + body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + } + ); + + // Give the cluster a moment to propagate metadata + await Task.Delay(1000); + + // --- Logic to determine Source and Target Nodes --- + + // In C#, we use ListVerbose to get sharding state + var nodes = await client.Cluster.Nodes.ListVerbose(collection: CollectionName); + Assert.True(nodes.Length >= 2, "Cluster must have at least 2 nodes for this test"); + + // Find a shard and its current replicas + // We look for a node that holds a shard for this collection + var sourceNodeData = nodes.First(n => + n.Shards != null && n.Shards.Any(s => s.Collection == CollectionName) + ); + var shardData = sourceNodeData.Shards!.First(s => s.Collection == CollectionName); + + string shardName = shardData.Name; + string sourceNodeName = sourceNodeData.Name; + + // Find all current replicas for this specific shard + // (Nodes that have a shard with the same name and collection) + var currentReplicaNodes = nodes + .Where(n => + n.Shards != null + && n.Shards.Any(s => s.Name == shardName && s.Collection == CollectionName) + ) + .Select(n => n.Name) + .ToHashSet(); + + // Find a target node that DOES NOT currently hold this shard + var targetNodeName = nodes + .Select(n => n.Name) + .FirstOrDefault(n => !currentReplicaNodes.Contains(n)); + + // Fallback if all nodes hold the shard (unlikely with factor 2 on 3 nodes, but safe check) + if (targetNodeName == null) + { + Console.WriteLine("All nodes already hold this shard. Using node2 as fallback/force."); + targetNodeName = "node2"; + } + + Console.WriteLine( + $"Shard: {shardName}, Source: {sourceNodeName}, Target: {targetNodeName}" + ); + + // 1. Replicate (Copy) a shard + // START ReplicateShard + var replicateRequest = new ReplicateRequest( + Collection: CollectionName, + Shard: shardName, + SourceNode: sourceNodeName, + TargetNode: targetNodeName, + Type: ReplicationType.Copy // For copying a shard + // Type: ReplicationType.Move // For moving a shard + ); + + var operation = await client.Cluster.Replicate(replicateRequest); + var operationId = operation.Current.Id; + + Console.WriteLine($"Replication initiated, ID: {operationId}"); + // END ReplicateShard + + // 2. List replication operations + // START ListReplicationOperations + var allOps = await client.Cluster.Replications.ListAll(); + Console.WriteLine($"Total replication operations: {allOps.Count()}"); + + var filteredOps = await client.Cluster.Replications.List( + collection: CollectionName, + targetNode: targetNodeName + ); + Console.WriteLine( + $"Filtered operations for collection '{CollectionName}' on '{targetNodeName}': {filteredOps.Count()}" + ); + // END ListReplicationOperations + + // Wait for operation to progress slightly + await Task.Delay(2000); + + // 3. Get replication operation status + // START CheckOperationStatus + var opStatus = await client.Cluster.Replications.Get(operationId, includeHistory: true); + Console.WriteLine($"Status for {operationId}: {opStatus.Status.State}"); + Console.WriteLine( + $"History for {operationId}: {JsonSerializer.Serialize(opStatus.StatusHistory)}" + ); + // END CheckOperationStatus + + // 4. Cancel a replication operation + // START CancelOperation + await client.Cluster.Replications.Cancel(operationId); + // END CancelOperation + + // 5. Delete a replication operation record + // START DeleteOperationRecord + await client.Cluster.Replications.Delete(operationId); + // END DeleteOperationRecord + + // 6. Delete all replication operations + // START DeleteAllOperationRecords + await client.Cluster.Replications.DeleteAll(); + // END DeleteAllOperationRecords + + // 7. Query Sharding State + // START CheckShardingState + var shardingState = await client.Cluster.Nodes.ListVerbose(collection: CollectionName); + + Console.WriteLine($"Nodes participating in '{CollectionName}':"); + foreach (var node in shardingState) + { + if (node.Shards != null) + { + foreach (var s in node.Shards) + { + if (s.Collection == CollectionName) + { + Console.WriteLine($"Node: {node.Name}, Shard: {s.Name}"); + } + } + } + } + // END CheckShardingState + } +} diff --git a/_includes/code/csharp/SearchAggregateTest.cs b/_includes/code/csharp/SearchAggregateTest.cs index 5f4fe4acc..292857422 100644 --- a/_includes/code/csharp/SearchAggregateTest.cs +++ b/_includes/code/csharp/SearchAggregateTest.cs @@ -1,9 +1,10 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; +using System.Collections.Generic; using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -18,11 +19,16 @@ static SearchAggregateTest() // Best practice: store your credentials in environment variables string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); - - client = Connect.Cloud( - weaviateUrl, - weaviateApiKey - ); + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + + client = Connect + .Cloud( + weaviateUrl, + weaviateApiKey, + headers: new Dictionary() { { "X-OpenAI-Api-Key", openaiApiKey } } + ) + .GetAwaiter() + .GetResult(); // END INSTANTIATION-COMMON } @@ -55,12 +61,16 @@ public async Task TestTextProp() var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Aggregate.OverAll( // highlight-start - metrics: Metrics.ForProperty("answer") - .Text( - topOccurrencesCount: true, - topOccurrencesValue: true, - minOccurrences: 5 // Corresponds to topOccurrencesCutoff - ) + returnMetrics: + [ + Metrics + .ForProperty("answer") + .Text( + topOccurrencesCount: true, + topOccurrencesValue: true, + minOccurrences: 5 // Threshold minimum count + ), + ] // highlight-end ); @@ -80,12 +90,10 @@ public async Task TestIntProp() var response = await jeopardy.Aggregate.OverAll( // highlight-start // Use .Number for floats (NUMBER datatype in Weaviate) - metrics: Metrics.ForProperty("points") - .Integer( - sum: true, - maximum: true, - minimum: true - ) + returnMetrics: + [ + Metrics.ForProperty("points").Integer(sum: true, maximum: true, minimum: true), + ] // highlight-end ); @@ -118,18 +126,17 @@ public async Task TestGroupBy() // END groupBy } - //TODO[g-despot] Why doesn query need to be list? [Fact] public async Task TestNearTextWithLimit() { // START nearTextWithLimit var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Aggregate.NearText( - ["animals in space"], + "animals in space", // highlight-start limit: 10, // highlight-end - metrics: Metrics.ForProperty("points").Number(sum: true) + returnMetrics: [Metrics.ForProperty("points").Number(sum: true)] ); var pointsMetrics = response.Properties["points"] as Aggregate.Number; @@ -148,7 +155,7 @@ public async Task TestHybrid() // highlight-start objectLimit: 10, // highlight-end - metrics: Metrics.ForProperty("points").Number(sum: true) + returnMetrics: [Metrics.ForProperty("points").Number(sum: true)] ); var pointsMetrics = response.Properties["points"] as Aggregate.Number; @@ -167,7 +174,7 @@ public async Task TestNearTextWithDistance() // highlight-start distance: 0.19, // highlight-end - metrics: Metrics.ForProperty("points").Number(sum: true) + returnMetrics: [Metrics.ForProperty("points").Number(sum: true)] ); var pointsMetrics = response.Properties["points"] as Aggregate.Number; @@ -183,7 +190,7 @@ public async Task TestWhereFilter() var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Aggregate.OverAll( // highlight-start - filters: Filter.Property("round").Equal("Final Jeopardy!"), + filters: Filter.Property("round").IsEqual("Final Jeopardy!"), // highlight-end totalCount: true ); @@ -191,4 +198,4 @@ public async Task TestWhereFilter() Console.WriteLine(response.TotalCount); // END whereFilter } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/SearchBasicTest.cs b/_includes/code/csharp/SearchBasicTest.cs index f62bb902d..475d7eeca 100644 --- a/_includes/code/csharp/SearchBasicTest.cs +++ b/_includes/code/csharp/SearchBasicTest.cs @@ -1,11 +1,10 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Text.Json; using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; // Note: This code assumes the existence of a Weaviate instance populated // with 'JeopardyQuestion' and 'WineReviewMT' collections @@ -13,7 +12,8 @@ public class SearchBasicTest : IAsyncLifetime { private WeaviateClient client; - public Task InitializeAsync() + // Fixed: Signature is Task, not Task + public async Task InitializeAsync() { // ================================ // ===== INSTANTIATION-COMMON ===== @@ -22,21 +22,16 @@ public Task InitializeAsync() // Best practice: store your credentials in environment variables var weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); var weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); - var openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); + // var openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); - // The Connect.Cloud helper method is a straightforward way to connect. - // We add the OpenAI API key to the headers for the text2vec-openai module. - client = Connect.Cloud( - weaviateUrl, - weaviateApiKey - ); - - return Task.CompletedTask; + client = await Connect.Cloud(weaviateUrl, weaviateApiKey); } + // Fixed: Implement DisposeAsync from IAsyncLifetime instead of Dispose public Task DisposeAsync() { - // The C# client manages its connections automatically and does not require an explicit 'close' method. + // No explicit cleanup needed for the client in this context, + // but the interface requires the method. return Task.CompletedTask; } @@ -63,32 +58,28 @@ public async Task BasicGet() Assert.True(response.Objects.First().Properties.ContainsKey("question")); } - // TODO[g-despot]: Enable when C# client supports offset - // [Fact] - // public async Task BasicGetOffset() - // { - // // ============================== - // // ===== BASIC GET EXAMPLES ===== - // // ============================== - - // // START - // var jeopardy = client.Collections.Use("JeopardyQuestion"); - // // highlight-start - // var response = await jeopardy.Query.FetchObjects(offset: 1, limit: 1); - // // highlight-end - - // foreach (var o in response.Objects) - // { - // Console.WriteLine(JsonSerializer.Serialize(o.Properties)); - // } - // // END - - // Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - // Assert.True(response.Objects.First().Properties.ContainsKey("question")); - // } - // START GetWithOffset - // Coming soon - // END GetWithOffset + [Fact] + public async Task BasicGetOffset() + { + // ============================== + // ===== BASIC GET EXAMPLES ===== + // ============================== + + // START GetWithOffset + var jeopardy = client.Collections.Use("JeopardyQuestion"); + // highlight-start + var response = await jeopardy.Query.FetchObjects(offset: 1, limit: 1); + // highlight-end + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END GetWithOffset + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + } [Fact] public async Task GetWithLimit() @@ -98,7 +89,7 @@ public async Task GetWithLimit() // ==================================== // START GetWithLimit - var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.FetchObjects( // highlight-start limit: 1 @@ -124,12 +115,12 @@ public async Task GetProperties() // ========================================== // START GetProperties - var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.FetchObjects( // highlight-start limit: 1, returnProperties: new[] { "question", "answer", "points" } - // highlight-end + // highlight-end ); foreach (var o in response.Objects) @@ -153,23 +144,23 @@ public async Task GetObjectVector() // ====================================== // START GetObjectVector - var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.FetchObjects( // highlight-start - returnMetadata: (MetadataOptions.Vector, ["default"]), + includeVectors: new[] { "default" }, // highlight-end limit: 1 ); - // Note: The C# client returns a dictionary of named vectors. - // We assume the default vector name is 'default'. - //TODO[g-despot]: Why is vector not returned? Console.WriteLine("Vector for 'default':"); - Console.WriteLine(JsonSerializer.Serialize(response.Objects.First())); + if (response.Objects.Any()) + { + Console.WriteLine(JsonSerializer.Serialize(response.Objects.First())); + } // END GetObjectVector Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.IsType(response.Objects.First().Vectors["default"]); + Assert.True(response.Objects.First().Vectors.ContainsKey("default")); } [Fact] @@ -180,13 +171,16 @@ public async Task GetObjectId() // ================================== // START GetObjectId - var jeopardy = client.Collections.Use>("JeopardyQuestion"); - var response = await jeopardy.Query.FetchObjectByID( - Guid.Parse("36ddd591-2dee-4e7e-a3cc-eb86d30a4303") - ); + var jeopardy = client.Collections.Use("JeopardyQuestion"); + // Ensure you use a UUID that actually exists in your DB, or fetch one first + var allObjs = await jeopardy.Query.FetchObjects(limit: 1); + var idToFetch = allObjs.Objects.First().UUID; + + var response = await jeopardy.Query.FetchObjectByID((Guid)idToFetch); Console.WriteLine(response); // END GetObjectId + Assert.NotNull(response); } [Fact] @@ -196,33 +190,37 @@ public async Task GetWithCrossRefs() // ===== GET WITH CROSS-REF EXAMPLES ===== // ============================== // START GetWithCrossRefs - var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.FetchObjects( // highlight-start - returnReferences: [ - new QueryReference( - linkOn: "hasCategory", - fields: ["title"] - ) - ], + returnReferences: [new QueryReference(linkOn: "hasCategory", fields: ["title"])], // highlight-end limit: 2 ); foreach (var o in response.Objects) { - Console.WriteLine(o.Properties["question"]); + if (o.Properties.ContainsKey("question")) + Console.WriteLine(o.Properties["question"]); + // print referenced objects // Note: References are grouped by property name ('hasCategory') - foreach (var refObj in o.References["hasCategory"]) + if (o.References != null && o.References.ContainsKey("hasCategory")) { - Console.WriteLine(JsonSerializer.Serialize(refObj.Properties)); + foreach (var refObj in o.References["hasCategory"]) + { + Console.WriteLine(JsonSerializer.Serialize(refObj.Properties)); + } } } // END GetWithCrossRefs Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.True(response.Objects.First().References["hasCategory"].Count > 0); + // Asserting count > 0 requires data to actually have references + if (response.Objects.First().References.ContainsKey("hasCategory")) + { + Assert.True(response.Objects.First().References["hasCategory"].Count > 0); + } } [Fact] @@ -233,7 +231,7 @@ public async Task GetWithMetadata() // ==================================== // START GetWithMetadata - var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.FetchObjects( limit: 1, // highlight-start @@ -243,8 +241,8 @@ public async Task GetWithMetadata() foreach (var o in response.Objects) { - Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // View the returned properties - Console.WriteLine(o.Metadata.CreationTime); // View the returned creation time + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // View the returned properties + Console.WriteLine(o.Metadata.CreationTime); // View the returned creation time } // END GetWithMetadata @@ -260,23 +258,19 @@ public async Task MultiTenancyGet() // ========================= // START MultiTenancy - var mtCollection = client.Collections.Use>("WineReviewMT"); + var mtCollection = client.Collections.Use("WineReviewMT").WithTenant("tenantA"); - // In the C# client, the tenant is specified directly in the query method - // rather than creating a separate tenant-specific collection object. - // highlight-start var response = await mtCollection.Query.FetchObjects( - tenant: "tenantA", - // highlight-end returnProperties: new[] { "review_body", "title" }, limit: 1 ); - Console.WriteLine(JsonSerializer.Serialize(response.Objects.First().Properties)); + if (response.Objects.Any()) + { + Console.WriteLine(JsonSerializer.Serialize(response.Objects.First().Properties)); + Assert.Equal("WineReviewMT", response.Objects.First().Collection); + } // END MultiTenancy - - Assert.True(response.Objects.Count() > 0); - Assert.Equal("WineReviewMT", response.Objects.First().Collection); } [Fact] @@ -286,11 +280,19 @@ public async Task GetObjectWithReplication() // ===== GET OBJECT ID EXAMPLES ===== // ================================== + // Fetch a valid ID first to ensure test success + var jeopardyInit = client.Collections.Use("JeopardyQuestion"); + var initResp = await jeopardyInit.Query.FetchObjects(limit: 1); + if (!initResp.Objects.Any()) + return; + var validId = initResp.Objects.First().UUID; + // START QueryWithReplication - var jeopardy = client.Collections.Use>("JeopardyQuestion").WithConsistencyLevel(ConsistencyLevels.Quorum); - var response = await jeopardy.Query.FetchObjectByID( - Guid.Parse("36ddd591-2dee-4e7e-a3cc-eb86d30a4303") - ); + var jeopardy = client + .Collections.Use("JeopardyQuestion") + .WithConsistencyLevel(ConsistencyLevels.Quorum); + + var response = await jeopardy.Query.FetchObjectByID((Guid)validId); // The parameter passed to `withConsistencyLevel` can be one of: // * 'ALL', @@ -302,5 +304,6 @@ public async Task GetObjectWithReplication() Console.WriteLine(response); // END QueryWithReplication + Assert.NotNull(response); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/SearchFiltersTest.cs b/_includes/code/csharp/SearchFiltersTest.cs index e69de29bb..545eb1771 100644 --- a/_includes/code/csharp/SearchFiltersTest.cs +++ b/_includes/code/csharp/SearchFiltersTest.cs @@ -0,0 +1,539 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; + +public class SearchFilterTest : IAsyncLifetime +{ + private WeaviateClient client; + + public async Task InitializeAsync() + { + // START INSTANTIATION-COMMON + // Best practice: store your credentials in environment variables + var weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + var weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + var openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); + + // Fallback to local if env vars are not set (for local testing) + if (string.IsNullOrEmpty(weaviateUrl)) + { + client = await Connect.Local( + headers: new Dictionary { { "X-OpenAI-Api-Key", openaiApiKey } } + ); + } + else + { + client = await Connect.Cloud( + weaviateUrl, + weaviateApiKey, + headers: new Dictionary { { "X-OpenAI-Api-Key", openaiApiKey } } + ); + } + // END INSTANTIATION-COMMON + } + + public Task DisposeAsync() + { + // The C# client manages connections automatically. + return Task.CompletedTask; + } + + [Fact] + public async Task TestSingleFilter() + { + // START SingleFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + filters: Filter.Property("round").IsEqual("Double Jeopardy!"), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END SingleFilter + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestSingleFilterNearText() + { + // START NearTextSingleFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.NearText( + "fashion icons", + // highlight-start + filters: Filter.Property("points").IsGreaterThan(200), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END NearTextSingleFilter + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestContainsAnyFilter() + { + // START ContainsAnyFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + // highlight-start + string[] tokens = ["australia", "india"]; + // highlight-end + + var response = await jeopardy.Query.FetchObjects( + // highlight-start + // Find objects where the `answer` property contains any of the strings in `tokens` + filters: Filter.Property("answer").ContainsAny(tokens), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END ContainsAnyFilter + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestContainsAllFilter() + { + // START ContainsAllFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + // highlight-start + string[] tokens = ["blue", "red"]; + // highlight-end + + var response = await jeopardy.Query.FetchObjects( + // highlight-start + // Find objects where the `question` property contains all of the strings in `tokens` + filters: Filter.Property("question").ContainsAll(tokens), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END ContainsAllFilter + } + + [Fact] + public async Task TestContainsNoneFilter() + { + // START ContainsNoneFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + // highlight-start + string[] tokens = ["bird", "animal"]; + // highlight-end + + var response = await jeopardy.Query.FetchObjects( + // highlight-start + // Find objects where the `question` property contains none of the strings in `tokens` + filters: Filter.Property("question").ContainsNone(tokens), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END ContainsNoneFilter + } + + [Fact] + public async Task TestLikeFilter() + { + // START LikeFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + filters: Filter.Property("answer").IsLike("*ala*"), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END LikeFilter + } + + [Fact] + public async Task TestMultipleFiltersAnd() + { + // START MultipleFiltersAnd + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + // Combine filters with Filter.And(), Filter.Or(), and Filter.Not() + filters: Filter.AllOf( + Filter.Property("round").IsEqual("Double Jeopardy!"), + Filter.Property("points").IsLessThan(600), + Filter.Not(Filter.Property("answer").IsEqual("Yucatan")) + ), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END MultipleFiltersAnd + } + + [Fact] + public async Task TestMultipleFiltersAnyOf() + { + // START MultipleFiltersAnyOf + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + filters: Filter.AnyOf( + Filter.Property("points").IsGreaterThan(700), // gte/greaterThanOrEqual not always explicitly named in helpers, check impl + Filter.Property("points").IsLessThan(500), + Filter.Property("round").IsEqual("Double Jeopardy!") + ), + // highlight-end + limit: 5 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END MultipleFiltersAnyOf + } + + [Fact] + public async Task TestMultipleFiltersAllOf() + { + // START MultipleFiltersAllOf + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + filters: Filter.AllOf( + Filter.Property("points").IsGreaterThan(300), + Filter.Property("points").IsLessThan(700), + Filter.Property("round").IsEqual("Double Jeopardy!") + ), + // highlight-end + limit: 5 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END MultipleFiltersAllOf + } + + [Fact] + public async Task TestMultipleFiltersNested() + { + // START MultipleFiltersNested + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + filters: Filter.AllOf( + Filter.Property("answer").IsLike("*bird*"), + Filter.AnyOf( + Filter.Property("points").IsGreaterThan(700), + Filter.Property("points").IsLessThan(300) + ) + ), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END MultipleFiltersNested + } + + [Fact] + public async Task TestCrossReferenceQuery() + { + // CrossReference + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + var response = await jeopardy.Query.FetchObjects( + // highlight-start + // Filter by property on the referenced object + filters: Filter.Reference("hasCategory").Property("title").IsLike("*TRANSPORTATION*"), + // Retrieve the referenced object with specific properties + returnReferences: [new QueryReference("hasCategory", fields: ["title"])], + // highlight-end + limit: 1 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + + // Access the referenced object's property + if (o.References != null && o.References.ContainsKey("hasCategory")) + { + // Get the first referenced object + var refObject = o.References["hasCategory"].First(); + // Access its 'title' property + Console.WriteLine(refObject.Properties["title"]); + } + } + // END CrossReference + + Assert.NotEmpty(response.Objects); + // Verify that the filter worked (all returned objects should be linked to 'TRANSPORTATION') + var firstRef = response.Objects.First().References["hasCategory"].First(); + Assert.Contains("TRANSPORTATION", firstRef.Properties["title"].ToString()); + } + + [Fact] + public async Task TestFilterById() + { + // START FilterById + var collection = client.Collections.Use("Article"); + + Guid targetId = Guid.Parse("00037775-1432-35e5-bc59-443baaef7d80"); + + var response = await collection.Query.FetchObjects(filters: Filter.ID.IsEqual(targetId)); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // Inspect returned objects + Console.WriteLine(o.UUID); + } + // END FilterById + } + + [Fact] + public async Task TestFilterByTimestamp() + { + // START FilterByTimestamp + // highlight-start + // Set the timezone for avoidance of doubt + DateTime filterTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc); + // highlight-end + + var collection = client.Collections.Use("Article"); + + var response = await collection.Query.FetchObjects( + limit: 3, + // highlight-start + filters: Filter.CreationTime.IsGreaterThan(filterTime), + returnMetadata: MetadataOptions.CreationTime + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // Inspect returned objects + Console.WriteLine(o.Metadata.CreationTime); // Inspect object creation time + } + // END FilterByTimestamp + } + + [Fact] + public async Task TestFilterByDateDatatype() + { + string collectionName = "CollectionWithDate"; + + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + + try + { + await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + Properties = [Property.Text("title"), Property.Date("some_date")], + // VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + } + ); + + var collection = client.Collections.Use(collectionName); + + // 1. Create a list to hold objects + var objects = new List(); + + // 2. Populate list + for (int year = 2020; year <= 2024; year++) + { + for (int month = 1; month <= 12; month += 2) + { + for (int day = 1; day <= 20; day += 5) + { + DateTime date = new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc); + objects.Add( + new + { + title = $"Object: yr/month/day:{year}/{month}/{day}", + some_date = date, + } + ); + } + } + } + + // 3. Insert + await collection.Data.InsertMany(objects.ToArray()); + Console.WriteLine($"Successfully inserted {objects.Count} objects."); + + // START FilterByDateDatatype + // highlight-start + // Use DateTime object for filter + DateTime filterTime = new DateTime(2022, 6, 10, 0, 0, 0, DateTimeKind.Utc); + // highlight-end + + var response = await collection.Query.FetchObjects( + limit: 3, + // highlight-start + // This property (`some_date`) is a `DATE` datatype + filters: Filter.Property("some_date").IsGreaterThan(filterTime) + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // Inspect returned objects + } + // END FilterByDateDatatype + + Assert.NotEmpty(response.Objects); + } + finally + { + await client.Collections.Delete(collectionName); + } + } + + [Fact] + public async Task TestFilterByPropertyLength() + { + // START FilterByPropertyLength + int lengthThreshold = 20; + + var collection = client.Collections.Use("JeopardyQuestion"); + var response = await collection.Query.FetchObjects( + limit: 3, + // highlight-start + filters: Filter.Property("answer").HasLength().IsGreaterThan(lengthThreshold) + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // Inspect returned objects + Console.WriteLine(o.Properties["answer"].ToString().Length); // Inspect property length + } + // END FilterByPropertyLength + } + + [Fact] + public async Task TestFilterByPropertyNullState() + { + // START FilterByPropertyNullState + var collection = client.Collections.Use("WineReview"); + var response = await collection.Query.FetchObjects( + limit: 3, + // highlight-start + // This requires the `country` property to be configured with `indexNullState: true` in the schema + filters: Filter.Property("country").IsNull() // Find objects where the `country` property is null + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // Inspect returned objects + } + // END FilterByPropertyNullState + } + + [Fact] + public async Task TestFilterByGeolocation() + { + string collectionName = "Publication"; + var localClient = await Connect.Local(); // Create separate client connection for isolated setup if needed + + if (await localClient.Collections.Exists(collectionName)) + { + await localClient.Collections.Delete(collectionName); + } + + try + { + await localClient.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + Properties = + [ + Property.Text("title"), + Property.GeoCoordinate("headquartersGeoLocation"), + ], + } + ); + + var publications = localClient.Collections.Use(collectionName); + await publications.Data.Insert( + new + { + title = "Weaviate HQ", + headquartersGeoLocation = new GeoCoordinate(52.3932696f, 4.8374263f), + } + ); + + // START FilterbyGeolocation + var response = await publications.Query.FetchObjects( + filters: Filter + .Property("headquartersGeoLocation") + .IsWithinGeoRange(new GeoCoordinate(52.39f, 4.84f), 1000.0f) // In meters + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // Inspect returned objects + } + // END FilterbyGeolocation + + Assert.Single(response.Objects); + } + finally + { + if (await localClient.Collections.Exists(collectionName)) + { + await localClient.Collections.Delete(collectionName); + } + } + } +} diff --git a/_includes/code/csharp/SearchGenerativeTest.cs b/_includes/code/csharp/SearchGenerativeTest.cs new file mode 100644 index 000000000..89ed83f02 --- /dev/null +++ b/_includes/code/csharp/SearchGenerativeTest.cs @@ -0,0 +1,301 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Generative; +using Xunit; + +namespace WeaviateProject.Tests; + +public class SearchGenerativeTest : IDisposable +{ + private static readonly WeaviateClient client; + + static SearchGenerativeTest() + { + // START INSTANTIATION-COMMON + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); + string anthropicApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_APIKEY"); + + client = Connect + .Cloud( + weaviateUrl, + weaviateApiKey, + headers: new Dictionary + { + { "X-OpenAI-Api-Key", openaiApiKey }, + { "Anthropic-Api-Key", anthropicApiKey }, + } + ) + .GetAwaiter() + .GetResult(); + // END INSTANTIATION-COMMON + } + + // Dispose is called once after all tests in the class are finished (like @AfterAll) + public void Dispose() + { + // The C# client manages connections automatically and does not require an explicit 'close' method. + GC.SuppressFinalize(this); + } + + [Fact] + public async Task TestDynamicRag() + { + // START DynamicRag + var reviews = client.Collections.Use("WineReviewNV"); + var response = await reviews.Generate.NearText( + query => query("a sweet German white wine").TargetVectorsMinimum("title_country"), + limit: 2, + provider: new Providers.OpenAI { Model = "gpt-5-mini" }, + singlePrompt: new SinglePrompt("Translate this into German: {review_body}"), + // highlight-start + groupedTask: new GroupedTask("Summarize these reviews") + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); + Console.WriteLine($"Single prompt result: {o.Generative?.Values.First()}"); + } + Console.WriteLine($"Grouped task result: {response.Generative?.Values.First()}"); + // END DynamicRag + } + + [Fact] + public async Task TestNamedVectorNearText() + { + // START NamedVectorNearText + var reviews = client.Collections.Use("WineReviewNV"); + var response = await reviews.Generate.NearText( + query => query("a sweet German white wine").TargetVectorsMinimum("title_country"), + limit: 2, + // highlight-start + returnMetadata: MetadataOptions.Distance, + singlePrompt: new SinglePrompt("Translate this into German: {review_body}"), + groupedTask: new GroupedTask("Summarize these reviews") + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); + Console.WriteLine($"Single prompt result: {o.Generative?.Values.First()}"); + } + Console.WriteLine($"Grouped task result: {response.Generative?.Values.First()}"); + // END NamedVectorNearText + } + + [Fact] + public async Task TestSingleGenerative() + { + // START SingleGenerative + // highlight-start + var prompt = + "Convert the following into a question for twitter. Include emojis for fun, but do not include the answer: {question}."; + // highlight-end + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + // highlight-start + var response = await jeopardy.Generate.NearText( + // highlight-end + "World history", + limit: 2, + // highlight-start + singlePrompt: new SinglePrompt(prompt) + ); + // highlight-end + + foreach (var o in response.Objects) + { + var props = o.Properties as IDictionary; + Console.WriteLine($"Property 'question': {props?["question"]}"); + // highlight-start + Console.WriteLine($"Single prompt result: {o.Generative?.Values.First()}"); + // highlight-end + } + // END SingleGenerative + } + + [Fact] + public async Task TestSingleGenerativeProperties() + { + // START SingleGenerativeProperties + // highlight-start + var prompt = + "Convert this quiz question: {question} and answer: {answer} into a trivia tweet."; + // highlight-end + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Generate.NearText( + "World history", + limit: 2, + singlePrompt: new SinglePrompt(prompt) + ); + + // print source properties and generated responses + foreach (var o in response.Objects) + { + Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); + Console.WriteLine($"Single prompt result: {o.Generative?.Values.First()}"); + } + // END SingleGenerativeProperties + } + + [Fact] + public async Task TestSingleGenerativeParameters() + { + // START SingleGenerativeParameters + // highlight-start + var singlePrompt = new SinglePrompt( + "Convert this quiz question: {question} and answer: {answer} into a trivia tweet." + ) + { + // Metadata = true, + Debug = true, + }; + // highlight-end + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Generate.NearText( + "World history", + limit: 2, + // highlight-start + singlePrompt: singlePrompt + // highlight-end + // provider: new GenerativeProvider.OpenAI() + ); + + // print source properties and generated responses + foreach (var o in response.Objects) + { + Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); + Console.WriteLine($"Single prompt result: {o.Generative?.Values.First()}"); + //Console.WriteLine($"Debug: {o.Generative?}"); + //Console.WriteLine($"Metadata: {JsonSerializer.Serialize(o.Generative?.Metadata)}"); + } + // END SingleGenerativeParameters + } + + [Fact] + public async Task TestGroupedGenerative() + { + // START GroupedGenerative + // highlight-start + var task = "What do these animals have in common, if anything?"; + // highlight-end + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Generate.NearText( + "Cute animals", + limit: 3, + // highlight-start + groupedTask: new GroupedTask(task) + ); + // highlight-end + + // print the generated response + Console.WriteLine($"Grouped task result: {response.Generative?.Values.First()}"); + // END GroupedGenerative + } + + [Fact] + public async Task TestGroupedGenerativeParameters() + { + // START GroupedGenerativeParameters + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Generate.NearText( + "Cute animals", + limit: 3, + // highlight-start + groupedTask: new GroupedTask("What do these animals have in common, if anything?") + { + Debug = true, + }, + provider: new Providers.OpenAI { ReturnMetadata = true, Model = "gpt-5-mini" } + // highlight-end + ); + + // print the generated response + Console.WriteLine($"Grouped task result: {response.Generative?.Values.First()}"); + // Console.WriteLine($"Metadata: {JsonSerializer.Serialize(response.Generative?.Metadata)}"); + // END GroupedGenerativeParameters + } + + [Fact] + public async Task TestGroupedGenerativeProperties() + { + // START GroupedGenerativeProperties + var task = "What do these animals have in common, if anything?"; + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Generate.NearText( + "Australian animals", + limit: 3, + groupedTask: new GroupedTask(task) + { + // highlight-start + Properties = ["answer", "question"], + // highlight-end + } + ); + + // print the generated response + // highlight-start + foreach (var o in response.Objects) + { + Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); + } + Console.WriteLine($"Grouped task result: {response.Generative?.Values.First()}"); + // highlight-end + // END GroupedGenerativeProperties + } + + // TODO[g-despot] NEW: Implement testing with images + // [Fact] +#pragma warning disable xUnit1013 // Public method should be marked as test + public async Task TestWorkingWithImages() +#pragma warning restore xUnit1013 // Public method should be marked as test + { + // START WorkingWithImages + var srcImgPath = + "https://images.unsplash.com/photo-1459262838948-3e2de6c1ec80?w=500&h=500&fit=crop"; + using var httpClient = new HttpClient(); + var imageBytes = await httpClient.GetByteArrayAsync(srcImgPath); + var base64Image = Convert.ToBase64String(imageBytes); + + var groupedTask = new GroupedTask("Formulate a Jeopardy!-style question about this image"); + // highlight-start + var provider = new Providers.Anthropic + { + MaxTokens = 1000, + Images = [base64Image], // A list of base64 encoded strings of the image bytes + ImageProperties = ["img"], // Properties containing images in Weaviate } + }; + // highlight-end + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Generate.NearText( + "Australian animals", + limit: 3, + groupedTask: groupedTask, + provider: provider + ); + + // Print the source property and the generated response + foreach (var o in response.Objects) + { + Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); + } + Console.WriteLine($"Grouped task result: {response.Generative?.Values.First()}"); + // END WorkingWithImages + } +} diff --git a/_includes/code/csharp/SearchHybridTest.cs b/_includes/code/csharp/SearchHybridTest.cs index 2fd6ddc69..aced20dff 100644 --- a/_includes/code/csharp/SearchHybridTest.cs +++ b/_includes/code/csharp/SearchHybridTest.cs @@ -1,11 +1,11 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Text.Json; -using System.Linq; using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -22,17 +22,14 @@ static SearchHybridTest() string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); - // The C# client uses a configuration object. - var config = new ClientConfiguration - { - GrpcAddress = weaviateUrl, - // Headers = new() - // { - // { "Authorization", $"Bearer {weaviateApiKey}" }, - // { "X-OpenAI-Api-Key", openaiApiKey } - // } - }; - client = new WeaviateClient(config); + client = Connect + .Cloud( + weaviateUrl, + weaviateApiKey, + headers: new Dictionary() { { "X-OpenAI-Api-Key", openaiApiKey } } + ) + .GetAwaiter() + .GetResult(); // END INSTANTIATION-COMMON } @@ -50,8 +47,7 @@ public async Task NamedVectorHybrid() var reviews = client.Collections.Use("WineReviewNV"); // highlight-start var response = await reviews.Query.Hybrid( - "A French Riesling", - targetVector: ["title_country"], + vectors: v => v.NearText(["A French Riesling"]).TargetVectorsMinimum("title_country"), limit: 3 ); // highlight-end @@ -104,7 +100,9 @@ public async Task TestHybridWithScore() { Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // highlight-start - Console.WriteLine($"Score: {o.Metadata.Score}, Explain Score: {o.Metadata.ExplainScore}"); + Console.WriteLine( + $"Score: {o.Metadata.Score}, Explain Score: {o.Metadata.ExplainScore}" + ); // highlight-end } // END HybridWithScore @@ -117,7 +115,7 @@ public async Task TestHybridWithScore() [Fact] public async Task TestLimit() { - // START limit + // START limit var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.Hybrid( "food", @@ -131,7 +129,7 @@ public async Task TestLimit() { Console.WriteLine(JsonSerializer.Serialize(o.Properties)); } - // END limit + // END limit Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); Assert.Equal(3, response.Objects.Count()); @@ -140,7 +138,7 @@ public async Task TestLimit() [Fact] public async Task TestAutocut() { - // START autocut + // START autocut var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.Hybrid( "food", @@ -154,7 +152,7 @@ public async Task TestAutocut() { Console.WriteLine(JsonSerializer.Serialize(o.Properties)); } - // END autocut + // END autocut Assert.True(response.Objects.Any()); Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); @@ -212,7 +210,7 @@ public async Task HybridWithBM25OperatorOr() var response = await jeopardy.Query.Hybrid( // highlight-start "Australian mammal cute", - bm25Operator: new BM25Operator.Or(MinimumMatch: 2), + bm25Operator: new BM25Operator.Or(MinimumMatch: 1), // highlight-end limit: 3 ); @@ -229,14 +227,13 @@ public async Task HybridWithBM25OperatorOr() [Fact] public async Task HybridWithBM25OperatorAnd() { - // START HybridWithBM25OperatorAnd var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.Hybrid( // highlight-start "Australian mammal cute", - bm25Operator: new BM25Operator.And(), // Each result must include all tokens - // highlight-end + bm25Operator: new BM25Operator.And(), // Each result must include all tokens + // highlight-end limit: 3 ); @@ -295,7 +292,6 @@ public async Task TestHybridWithPropertyWeighting() Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); } - // TODO[g-despot] Why is name required in VectorData.Create? [Fact] public async Task TestHybridWithVector() { @@ -306,7 +302,7 @@ public async Task TestHybridWithVector() var response = await jeopardy.Query.Hybrid( "food", // highlight-start - vectors: Vectors.Create("default", queryVector), + vectors: queryVector, // highlight-end alpha: 0.25f, limit: 3 @@ -329,7 +325,7 @@ public async Task TestHybridWithFilter() var response = await jeopardy.Query.Hybrid( "food", // highlight-start - filters: Filter.Property("round").Equal("Double Jeopardy!"), + filters: Filter.Property("round").IsEqual("Double Jeopardy!"), // highlight-end limit: 3 ); @@ -341,7 +337,10 @@ public async Task TestHybridWithFilter() // END HybridWithFilter Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Equal("Double Jeopardy!", (response.Objects.First().Properties as IDictionary)["round"].ToString()); + Assert.Equal( + "Double Jeopardy!", + (response.Objects.First().Properties as IDictionary)["round"].ToString() + ); } [Fact] @@ -349,27 +348,23 @@ public async Task TestVectorParameters() { // START VectorParameters var jeopardy = client.Collections.Use("JeopardyQuestion"); - // This query is complex and depends on a previous nearText query to get a vector. - // We simulate this by fetching a vector first. - var nearTextResponse = await jeopardy.Query.NearText( - "large animal", - moveAway: new Move(force: 0.5f, concepts: ["mammal", "terrestrial"]), - limit: 1, - returnMetadata: MetadataOptions.Vector - ); - var nearTextVector = nearTextResponse.Objects.First().Vectors["default"]; var response = await jeopardy.Query.Hybrid( "California", // highlight-start maxVectorDistance: 0.4f, - vectors: nearTextVector, + vectors: v => + v.NearText( + "large animal", + moveAway: new Move(force: 0.5f, concepts: ["mammal", "terrestrial"]) + ), // highlight-end alpha: 0.75f, limit: 5 ); // END VectorParameters - + System.Console.WriteLine("despot"); + System.Console.WriteLine(JsonSerializer.Serialize(response)); Assert.True(response.Objects.Any() && response.Objects.Count() <= 5); } @@ -382,7 +377,7 @@ public async Task TestVectorSimilarity() "California", // highlight-start maxVectorDistance: 0.4f, // Maximum threshold for the vector search component - // highlight-end + // highlight-end alpha: 0.75f, limit: 5 ); @@ -400,11 +395,10 @@ public async Task TestHybridGroupBy() var response = await jeopardy.Query.Hybrid( "California", alpha: 0.75f, - groupBy: new GroupByRequest + groupBy: new GroupByRequest("round") // group by this property { - PropertyName = "round", // group by this property - NumberOfGroups = 2, // maximum number of groups - ObjectsPerGroup = 3, // maximum objects per group + NumberOfGroups = 2, // maximum number of groups + ObjectsPerGroup = 3, // maximum objects per group } ); @@ -416,4 +410,4 @@ public async Task TestHybridGroupBy() Assert.True(response.Groups.Count > 0 && response.Groups.Count <= 2); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/SearchImageTest.cs b/_includes/code/csharp/SearchImageTest.cs index 3c1a3db50..b90aa08db 100644 --- a/_includes/code/csharp/SearchImageTest.cs +++ b/_includes/code/csharp/SearchImageTest.cs @@ -1,13 +1,12 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Text.Json; -using System.Linq; using System.IO; +using System.Linq; using System.Net.Http; -using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -28,53 +27,83 @@ private static async Task FileToByteArray(string path) { return await File.ReadAllBytesAsync(path); } + // END helper base64 functions // Runs once before any tests in the class (like @BeforeAll) public async Task InitializeAsync() { - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8280 , GrpcPort = 50251}); + client = await Connect.Local(restPort: 8280, grpcPort: 50251); if (await client.Collections.Exists("Dog")) { await client.Collections.Delete("Dog"); } - await client.Collections.Create(new CollectionConfig - { - Name = "Dog", - Properties = - [ - Property.Blob("image"), - Property.Text("breed"), - Property.Text("description") - ], - VectorConfig = new VectorConfig( - "default", - new Vectorizer.Multi2VecClip { ImageFields = new[] { "image" }, TextFields = new[] { "breed", "description" } } - ) - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Dog", + Properties = + [ + Property.Blob("image"), + Property.Text("breed"), + Property.Text("description"), + ], + VectorConfig = Configure.Vector( + "default", + v => + v.Multi2VecClip( + imageFields: new[] { "image" }, + textFields: new[] { "breed", "description" } + ) + ), + } + ); // Prepare and ingest sample dog images var dogs = client.Collections.Use("Dog"); var sampleImages = new[] { - new { url = "https://images.unsplash.com/photo-1489924034176-2e678c29d4c6?q=80&w=2342&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", breed = "Husky", description = "Siberian Husky with distinctive blue eyes, pointed ears, and thick white and grey fur coat, typical of arctic sled dogs" }, - new { url = "https://images.unsplash.com/photo-1633722715463-d30f4f325e24?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8R29sZGVuJTIwUmV0cmlldmVyfGVufDB8fDB8fHwy", breed = "Golden Retriever", description = "Golden Retriever with beautiful long golden fur, friendly expression, sitting and posing for the camera, known for being excellent family pets" }, - new { url = "https://images.unsplash.com/photo-1612979148245-d8c79c50935d?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OXx8ZG9nJTIwZ2VybWFuJTIwc2hlcGFyZHxlbnwwfHwwfHx8Mg%3D%3D", breed = "German Shepherd", description = "The German Shepherd, also known in Britain as an Alsatian, is a German breed of working dog of medium to large size. It was originally bred as a herding dog, for herding sheep. " } + new + { + url = "https://images.unsplash.com/photo-1489924034176-2e678c29d4c6?q=80&w=2342&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + breed = "Husky", + description = "Siberian Husky with distinctive blue eyes, pointed ears, and thick white and grey fur coat, typical of arctic sled dogs", + }, + new + { + url = "https://images.unsplash.com/photo-1633722715463-d30f4f325e24?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8R29sZGVuJTIwUmV0cmlldmVyfGVufDB8fDB8fHwy", + breed = "Golden Retriever", + description = "Golden Retriever with beautiful long golden fur, friendly expression, sitting and posing for the camera, known for being excellent family pets", + }, + new + { + url = "https://images.unsplash.com/photo-1612979148245-d8c79c50935d?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OXx8ZG9nJTIwZ2VybWFuJTIwc2hlcGFyZHxlbnwwfHwwfHx8Mg%3D%3D", + breed = "German Shepherd", + description = "The German Shepherd, also known in Britain as an Alsatian, is a German breed of working dog of medium to large size. It was originally bred as a herding dog, for herding sheep. ", + }, }; Console.WriteLine("Inserting sample data..."); foreach (var image in sampleImages) { string base64Image = await UrlToBase64(image.url); - await dogs.Data.Insert(new { image = base64Image, breed = image.breed, description = image.description }); + await dogs.Data.Insert( + new + { + image = base64Image, + breed = image.breed, + description = image.description, + } + ); Console.WriteLine($"Inserted: {image.breed}"); } Console.WriteLine("Data insertion complete!"); // Download the specific image to be used for searches - var queryImageUrl = "https://images.unsplash.com/photo-1590419690008-905895e8fe0d?q=80&w=1336&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"; + var queryImageUrl = + "https://images.unsplash.com/photo-1590419690008-905895e8fe0d?q=80&w=1336&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"; using var httpClient = new HttpClient(); var imageStream = await httpClient.GetStreamAsync(queryImageUrl); @@ -93,8 +122,10 @@ public async Task DisposeAsync() await client.Collections.Delete("Dog"); } } - if (File.Exists(QUERY_IMAGE_PATH)) File.Delete(QUERY_IMAGE_PATH); - if (Directory.Exists("images")) Directory.Delete("images"); + if (File.Exists(QUERY_IMAGE_PATH)) + File.Delete(QUERY_IMAGE_PATH); + if (Directory.Exists("images")) + Directory.Delete("images"); } [Fact] @@ -110,13 +141,10 @@ public async Task TestSearchWithBase64() var dogs = client.Collections.Use("Dog"); // Perform query - // highlight-start - var response = await dogs.Query.NearImage( - imageBytes, - // highlight-end + var response = await dogs.Query.NearMedia( + query => query.Image(imageBytes).Build(), returnProperties: ["breed"], limit: 1 - // targetVector: "vector_name" // required when using multiple named vectors ); if (response.Objects.Any()) @@ -137,12 +165,9 @@ public async Task TestDistance() var dogs = client.Collections.Use("Dog"); var imageBytes = await FileToByteArray(QUERY_IMAGE_PATH); - var response = await dogs.Query.NearImage( - imageBytes, - // highlight-start - distance: 0.8f, // Maximum accepted distance + var response = await dogs.Query.NearMedia( + query => query.Image(imageBytes, distance: 0.8f).Build(), returnMetadata: MetadataOptions.Distance, // return distance from the source image - // highlight-end returnProperties: "breed", limit: 5 ); @@ -153,4 +178,4 @@ public async Task TestDistance() } // END Distance } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/SearchKeywordTest.cs b/_includes/code/csharp/SearchKeywordTest.cs index 4aa2ed4b5..30583a084 100644 --- a/_includes/code/csharp/SearchKeywordTest.cs +++ b/_includes/code/csharp/SearchKeywordTest.cs @@ -1,10 +1,10 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Text.Json; using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -22,18 +22,14 @@ static SearchKeywordTest() string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); // The C# client uses a configuration object. - var config = new ClientConfiguration - { - // For Weaviate Cloud, the URL is the full gRPC address - GrpcAddress = weaviateUrl, - // Headers are added to the configuration - // Headers = new() - // { - // { "Authorization", $"Bearer {weaviateApiKey}" }, - // { "X-OpenAI-Api-Key", openaiApiKey } - // } - }; - client = new WeaviateClient(config); + client = Connect + .Cloud( + restEndpoint: weaviateUrl, + apiKey: weaviateApiKey, + headers: new() { { "X-OpenAI-Api-Key", openaiApiKey } } + ) + .GetAwaiter() + .GetResult(); // END INSTANTIATION-COMMON } @@ -63,16 +59,64 @@ public async Task TestBM25Basic() // END BM25Basic Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Contains( + "food", + JsonSerializer.Serialize(response.Objects.First().Properties).ToLower() + ); + } + + [Fact] + public async Task TestBM25OperatorOrWithMin() + { + // START BM25OperatorOrWithMin + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + // highlight-start + query: "Australian mammal cute", + searchOperator: new BM25Operator.Or(MinimumMatch: 1), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END BM25OperatorOrWithMin + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + var propertiesJson = JsonSerializer + .Serialize(response.Objects.First().Properties) + .ToLower(); + Assert.True( + propertiesJson.Contains("australia") + || propertiesJson.Contains("mammal") + || propertiesJson.Contains("cute") + ); } - // START BM25OperatorOrWithMin - // Coming soon - // END BM25OperatorOrWithMin + // TODO[g-despot] Does the search operator work? + [Fact] + public async Task TestBM25OperatorAnd() + { + // START BM25OperatorAnd + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + // highlight-start + query: "Australian mammal cute", + searchOperator: new BM25Operator.And(), // Each result must include all tokens (e.g. "australian", "mammal", "cute") + // highlight-end + limit: 3 + ); - // START BM25OperatorAnd - // Coming soon - // END BM25OperatorAnd + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END BM25OperatorAnd + + // Assert.True(response.Objects.Count == 0); + } [Fact] public async Task TestBM25WithScore() @@ -94,7 +138,10 @@ public async Task TestBM25WithScore() } // END BM25WithScore Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Contains( + "food", + JsonSerializer.Serialize(response.Objects.First().Properties).ToLower() + ); Assert.NotNull(response.Objects.First().Metadata.Score); } @@ -118,7 +165,10 @@ public async Task TestLimit() // END limit Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Contains("safety", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Contains( + "safety", + JsonSerializer.Serialize(response.Objects.First().Properties).ToLower() + ); Assert.Equal(3, response.Objects.Count()); } @@ -130,7 +180,7 @@ public async Task TestAutocut() var response = await jeopardy.Query.BM25( "safety", // highlight-start - autoCut: 1 + autoLimit: 1 // highlight-end ); @@ -141,7 +191,10 @@ public async Task TestAutocut() // END autocut Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Contains("safety", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Contains( + "safety", + JsonSerializer.Serialize(response.Objects.First().Properties).ToLower() + ); } [Fact] @@ -166,7 +219,10 @@ public async Task TestBM25WithProperties() // END BM25WithProperties Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Contains("safety", response.Objects.First().Properties["question"].ToString().ToLower()); + Assert.Contains( + "safety", + response.Objects.First().Properties["question"].ToString().ToLower() + ); } [Fact] @@ -189,7 +245,10 @@ public async Task TestBM25WithBoostedProperties() // END BM25WithBoostedProperties Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Contains( + "food", + JsonSerializer.Serialize(response.Objects.First().Properties).ToLower() + ); } [Fact] @@ -200,7 +259,7 @@ public async Task TestMultipleKeywords() var response = await jeopardy.Query.BM25( // highlight-start "food wine", // search for food or wine - // highlight-end + // highlight-end searchFields: ["question"], limit: 5 ); @@ -212,7 +271,9 @@ public async Task TestMultipleKeywords() // END MultipleKeywords Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - var propertiesJson = JsonSerializer.Serialize(response.Objects.First().Properties).ToLower(); + var propertiesJson = JsonSerializer + .Serialize(response.Objects.First().Properties) + .ToLower(); Assert.True(propertiesJson.Contains("food") || propertiesJson.Contains("wine")); } @@ -224,7 +285,7 @@ public async Task TestBM25WithFilter() var response = await jeopardy.Query.BM25( "food", // highlight-start - filters: Filter.Property("round").Equal("Double Jeopardy!"), + filters: Filter.Property("round").IsEqual("Double Jeopardy!"), // highlight-end returnProperties: ["answer", "question", "round"], // return these properties limit: 3 @@ -237,7 +298,10 @@ public async Task TestBM25WithFilter() // END BM25WithFilter Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); - Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Contains( + "food", + JsonSerializer.Serialize(response.Objects.First().Properties).ToLower() + ); Assert.Equal("Double Jeopardy!", response.Objects.First().Properties["round"].ToString()); } @@ -249,11 +313,10 @@ public async Task TestBM25GroupBy() var response = await jeopardy.Query.BM25( "California", - groupBy: new GroupByRequest + groupBy: new GroupByRequest("round") // group by this property { - PropertyName = "round", // group by this property - NumberOfGroups = 2, // maximum number of groups - ObjectsPerGroup = 3, // maximum objects per group + NumberOfGroups = 2, // maximum number of groups + ObjectsPerGroup = 3, // maximum objects per group } ); @@ -266,4 +329,4 @@ public async Task TestBM25GroupBy() Assert.True(response.Groups.Count > 0); Assert.True(response.Groups.Count <= 2); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/SearchMultiTargetTest.cs b/_includes/code/csharp/SearchMultiTargetTest.cs new file mode 100644 index 000000000..f792e8e4c --- /dev/null +++ b/_includes/code/csharp/SearchMultiTargetTest.cs @@ -0,0 +1,302 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; + +namespace Weaviate.Tests; + +[Collection("Sequential")] +public class MultiTargetVectorsTest : IAsyncLifetime +{ + private WeaviateClient client; + private const string CollectionName = "JeopardyTiny"; + + public async Task InitializeAsync() + { + // 1. Connect + string url = Environment.GetEnvironmentVariable("WEAVIATE_URL") ?? "http://localhost:8080"; + string apiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string openaiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + + // Note: For these tests to run exactly like Python, we need an OpenAI key. + // If not present, tests relying on 'NearText' will fail or need mocking. + client = await Connect.Cloud( + url, + apiKey, + new Dictionary { { "X-OpenAI-Api-Key", openaiKey } } + ); + + // 2. Clean up + if (await client.Collections.Exists(CollectionName)) + { + await client.Collections.Delete(CollectionName); + } + + // 3. Define Schema (LoadDataNamedVectors) + await client.Collections.Create( + new CollectionCreateParams + { + Name = CollectionName, + Description = "Jeopardy game show questions", + VectorConfig = + [ + Configure.Vector( + "jeopardy_questions_vector", + v => v.Text2VecOpenAI(vectorizeCollectionName: false), + sourceProperties: ["question"] + ), + Configure.Vector( + "jeopardy_answers_vector", + v => v.Text2VecOpenAI(vectorizeCollectionName: false), + sourceProperties: ["answer"] + ), + ], + Properties = + [ + Property.Text("category"), + Property.Text("question"), + Property.Text("answer"), + ], + } + ); + + // 4. Load Data + await LoadData(); + } + + private async Task LoadData() + { + using var httpClient = new HttpClient(); + var jsonStr = await httpClient.GetStringAsync( + "https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json" + ); + + using var doc = JsonDocument.Parse(jsonStr); + var dataObjects = new List(); + + foreach (var element in doc.RootElement.EnumerateArray()) + { + var props = new + { + question = element.GetProperty("Question").ToString(), + answer = element.GetProperty("Answer").ToString(), + category = element.GetProperty("Category").ToString(), + }; + + dataObjects.Add(new BatchInsertRequest(props)); + } + + var collection = client.Collections.Use(CollectionName); + var response = await collection.Data.InsertMany(dataObjects); + + if (response.HasErrors) + { + throw new Exception($"Import failed: {response.Errors.First().Message}"); + } + } + + public async Task DisposeAsync() + { + // Cleanup after tests + if (client != null) + { + await client.Collections.Delete(CollectionName); + } + } + + [Fact] + public async Task TestMultiBasic() + { + // START MultiBasic + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + query => + query(["a wild animal"]) + // highlight-start + .TargetVectorsMinimum("jeopardy_questions_vector", "jeopardy_answers_vector"), // Specify the target vectors + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiBasic + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestMultiTargetNearVector() + { + var collection = client.Collections.Use(CollectionName); + + // Fetch objects to get existing vectors for the test + var someResult = await collection.Query.FetchObjects(limit: 2, includeVectors: true); + + // Explicitly cast to float[] + float[] v1 = someResult.Objects.ElementAt(0).Vectors["jeopardy_questions_vector"]; + float[] v2 = someResult.Objects.ElementAt(1).Vectors["jeopardy_answers_vector"]; + + // START MultiTargetNearVector + var response = await collection.Query.NearVector( + // highlight-start + // Specify the query vectors for each target vector + vectors: new Vectors + { + { "jeopardy_questions_vector", v1 }, + { "jeopardy_answers_vector", v2 }, + }, + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiTargetNearVector + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestMultiTargetMultipleNearVectors() + { + var collection = client.Collections.Use(CollectionName); + var someResult = await collection.Query.FetchObjects(limit: 3, includeVectors: true); + + float[] v1 = someResult.Objects.ElementAt(0).Vectors["jeopardy_questions_vector"]; + float[] v2 = someResult.Objects.ElementAt(1).Vectors["jeopardy_answers_vector"]; + float[] v3 = someResult.Objects.ElementAt(2).Vectors["jeopardy_answers_vector"]; + + // START MultiTargetMultipleNearVectorsV1 + var response = await collection.Query.NearVector( + // highlight-start + // Use NearVectorInput to pass multiple vectors naturally + vectors: v => + v.TargetVectorsSum( + ("jeopardy_questions_vector", v1), + ("jeopardy_answers_vector", v2), + ("jeopardy_answers_vector", v3) + ), + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + // END MultiTargetMultipleNearVectorsV1 + Assert.Equal(2, response.Objects.Count); + + // START MultiTargetMultipleNearVectorsV2 + var responseV2 = await collection.Query.NearVector( + // highlight-start + vectors: v => + v.TargetVectorsManualWeights( + ("jeopardy_questions_vector", 10, v1), + ("jeopardy_answers_vector", 30, v2), + ("jeopardy_answers_vector", 30, v3) + ), + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + // END MultiTargetMultipleNearVectorsV2 + Assert.Equal(2, responseV2.Objects.Count); + } + + [Fact] + public async Task TestSimpleJoinStrategy() + { + // START MultiTargetWithSimpleJoin + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + query => + query(["a wild animal"]) + // highlight-start + // Specify the target vectors and the join strategy + // Available: Sum, Minimum, Average, ManualWeights, RelativeScore + .TargetVectorsAverage("jeopardy_questions_vector", "jeopardy_answers_vector"), + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiTargetWithSimpleJoin + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestManualWeights() + { + // START MultiTargetManualWeights + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + query => + query(["a wild animal"]) + // highlight-start + .TargetVectorsManualWeights( + ("jeopardy_questions_vector", 10), + ("jeopardy_answers_vector", 50) + ), + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiTargetManualWeights + + Assert.NotEmpty(response.Objects); + } + + [Fact] + public async Task TestRelativeScore() + { + // START MultiTargetRelativeScore + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + query => + query(["a wild animal"]) + // highlight-start + .TargetVectorsRelativeScore( + ("jeopardy_questions_vector", 10), + ("jeopardy_answers_vector", 10) + ), + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiTargetRelativeScore + + Assert.NotEmpty(response.Objects); + } +} diff --git a/_includes/code/csharp/SearchSimilarityTest.cs b/_includes/code/csharp/SearchSimilarityTest.cs index f409b41c8..c090b874b 100644 --- a/_includes/code/csharp/SearchSimilarityTest.cs +++ b/_includes/code/csharp/SearchSimilarityTest.cs @@ -1,11 +1,11 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; using System.Collections.Generic; -using System.Text.Json; using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; public class SearchSimilarityTest : IAsyncLifetime { @@ -21,23 +21,25 @@ public async Task InitializeAsync() var openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); var cohereApiKey = Environment.GetEnvironmentVariable("COHERE_APIKEY"); - client = Connect.Cloud( + // FIX: Use await instead of .GetAwaiter().GetResult() + client = await Connect.Cloud( weaviateUrl, - weaviateApiKey - // additionalHeaders: new Dictionary - // { - // { "X-OpenAI-Api-Key", openaiApiKey }, - // { "X-Cohere-Api-Key", cohereApiKey } - // } + weaviateApiKey, + headers: new Dictionary() + { + { "X-OpenAI-Api-Key", openaiApiKey }, + { "X-Cohere-Api-Key", cohereApiKey }, + } ); // END INSTANTIATION-COMMON } - // DisposeAsync is used for asynchronous teardown after all tests in the class have run. - public async Task DisposeAsync() + // FIX: Implement DisposeAsync correctly to avoid NotImplementedException + public Task DisposeAsync() { - await client.Collections.DeleteAll(); - // The C# client, using HttpClient, manages its connections automatically and does not require an explicit 'close' method. + // The C# client manages connections automatically. + // No specific cleanup is required here, but the method must return a completed task. + return Task.CompletedTask; } [Fact] @@ -46,11 +48,12 @@ public async Task NamedVectorNearText() // START NamedVectorNearText var reviews = client.Collections.Use("WineReviewNV"); var response = await reviews.Query.NearText( - "a sweet German white wine", + query => + query(["a sweet German white wine"]) + // highlight-start + .TargetVectorsMinimum("title_country"), + // highlight-end limit: 2, - // highlight-start - targetVector: ["title_country"], // Specify the target vector for named vector collections - // highlight-end returnMetadata: MetadataOptions.Distance ); @@ -73,9 +76,9 @@ public async Task GetNearText() // START GetNearText var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Query.NearText( - // highlight-start + // highlight-start "animals in movies", - // highlight-end + // highlight-end limit: 2, returnMetadata: MetadataOptions.Distance ); @@ -86,6 +89,8 @@ public async Task GetNearText() Console.WriteLine(o.Metadata.Distance); } // END GetNearText + + Assert.NotEmpty(response.Objects); } [Fact] @@ -93,14 +98,18 @@ public async Task GetNearObject() { var jeopardy = client.Collections.Use("JeopardyQuestion"); var initialResponse = await jeopardy.Query.FetchObjects(limit: 1); - if (!initialResponse.Objects.Any()) return; // Skip test if no data - Guid uuid = (Guid)initialResponse.Objects.First().ID; + + if (!initialResponse.Objects.Any()) + return; // Skip test if no data + + // FIX: Handle nullable ID safely + Guid uuid = (Guid)initialResponse.Objects.First().UUID; // START GetNearObject // highlight-start var response = await jeopardy.Query.NearObject( uuid, // A UUID of an object - // highlight-end + // highlight-end limit: 2, returnMetadata: MetadataOptions.Distance ); @@ -122,15 +131,18 @@ public async Task GetNearObject() public async Task GetNearVector() { var jeopardy = client.Collections.Use("JeopardyQuestion"); - var initialResponse = await jeopardy.Query.FetchObjects(limit: 1, returnMetadata: MetadataOptions.Vector); - if (!initialResponse.Objects.Any()) return; // Skip test if no data + var initialResponse = await jeopardy.Query.FetchObjects(limit: 1, includeVectors: true); + + if (initialResponse.Objects.Count == 0) + return; // Skip test if no data + var queryVector = initialResponse.Objects.First().Vectors["default"]; // START GetNearVector // highlight-start var response = await jeopardy.Query.NearVector( - queryVector, // your query vector goes here - // highlight-end + vectors: queryVector, // your query vector goes here + // highlight-end limit: 2, returnMetadata: MetadataOptions.Distance ); @@ -156,9 +168,9 @@ public async Task GetLimitOffset() var response = await jeopardy.Query.NearText( "animals in movies", // highlight-start - limit: 2, // return 2 objects - offset: 1, // With an offset of 1 - // highlight-end + limit: 2, // return 2 objects + offset: 1, // With an offset of 1 + // highlight-end returnMetadata: MetadataOptions.Distance ); @@ -168,6 +180,8 @@ public async Task GetLimitOffset() Console.WriteLine(o.Metadata.Distance); } // END GetLimitOffset + + Assert.Equal(2, response.Objects.Count()); } [Fact] @@ -179,7 +193,7 @@ public async Task GetWithDistance() "animals in movies", // highlight-start distance: 0.25f, // max accepted distance - // highlight-end + // highlight-end returnMetadata: MetadataOptions.Distance ); @@ -207,8 +221,8 @@ public async Task GetWithAutocut() var response = await jeopardy.Query.NearText( "animals in movies", // highlight-start - autoCut: 1, // number of close groups - // highlight-end + autoLimit: 1, // number of close groups + // highlight-end returnMetadata: MetadataOptions.Distance ); @@ -233,21 +247,20 @@ public async Task GetWithGroupBy() // highlight-start var response = await jeopardy.Query.NearText( - "animals in movies", // find object based on this query - limit: 10, // maximum total objects + "animals in movies", // find object based on this query + limit: 10, // maximum total objects returnMetadata: MetadataOptions.Distance, - groupBy: new GroupByRequest + groupBy: new GroupByRequest("round") // group by this property { - PropertyName = "round", // group by this property - NumberOfGroups = 2, // maximum number of groups - ObjectsPerGroup = 2, // maximum objects per group + NumberOfGroups = 2, // maximum number of groups + ObjectsPerGroup = 2, // maximum objects per group } ); // highlight-end foreach (var o in response.Objects) { - Console.WriteLine(o.ID); + Console.WriteLine(o.UUID); Console.WriteLine(o.BelongsToGroup); Console.WriteLine(o.Metadata.Distance); } @@ -280,7 +293,7 @@ public async Task GetWithWhere() var response = await jeopardy.Query.NearText( "animals in movies", // highlight-start - filters: Filter.Property("round").Equal("Double Jeopardy!"), + filters: Filter.Property("round").IsEqual("Double Jeopardy!"), // highlight-end limit: 2, returnMetadata: MetadataOptions.Distance @@ -299,4 +312,4 @@ public async Task GetWithWhere() Assert.True(response.Objects.First().Properties.ContainsKey("question")); Assert.NotNull(response.Objects.First().Metadata.Distance); } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/StarterGuidesCollectionsTest.cs b/_includes/code/csharp/StarterGuidesCollectionsTest.cs index bacf853a4..488cbd73e 100644 --- a/_includes/code/csharp/StarterGuidesCollectionsTest.cs +++ b/_includes/code/csharp/StarterGuidesCollectionsTest.cs @@ -1,8 +1,8 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -11,14 +11,11 @@ public class StarterGuidesCollectionsTest : IAsyncLifetime private WeaviateClient client; // Runs before each test - public Task InitializeAsync() + public async Task InitializeAsync() { // START-ANY - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + client = await Connect.Local(); // END-ANY - return Task.CompletedTask; } // Runs after each test @@ -35,68 +32,66 @@ public async Task DisposeAsync() public async Task TestBasicSchema() { // START BasicSchema - var questionsCollection = await client.Collections.Create(new CollectionConfig - { - Name = "Question", - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), // Set the vectorizer - GenerativeConfig = new GenerativeConfig.Cohere(), // Set the generative module - Properties = - [ - Property.Text("question"), - Property.Text("answer"), - Property.Text("category") - ] - }); + var questionsCollection = await client.Collections.Create( + new CollectionCreateParams + { + Name = "Question", + VectorConfig = Configure.Vector("default", v => v.Text2VecWeaviate()), + GenerativeConfig = Configure.Generative.Cohere(), // Set the generative module + Properties = + [ + Property.Text("question"), + Property.Text("answer"), + Property.Text("category"), + ], + } + ); Console.WriteLine(questionsCollection); // END BasicSchema } - // TODO[g-despot] Missing vectorizePropertyName [Fact] public async Task TestSchemaWithPropertyOptions() { // START SchemaWithPropertyOptions - await client.Collections.Create(new CollectionConfig - { - Name = "Question", - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), - GenerativeConfig = new GenerativeConfig.Cohere(), - Properties = - [ - Property.Text( - "question", - tokenization: PropertyTokenization.Lowercase - // vectorizePropertyName: true // Pass as a simple named argument - ), - Property.Text( - "answer", - tokenization: PropertyTokenization.Whitespace - // vectorizePropertyName: false // Pass as a simple named argument - ) - ] - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Question", + VectorConfig = Configure.Vector("default", v => v.Text2VecWeaviate()), + GenerativeConfig = Configure.Generative.Cohere(), + Properties = + [ + Property.Text("question", tokenization: PropertyTokenization.Lowercase), + Property.Text("answer", tokenization: PropertyTokenization.Whitespace), + ], + } + ); // END SchemaWithPropertyOptions } [Fact] public async Task TestSchemaWithMultiTenancy() { + await client.Collections.Delete("Question"); // START SchemaWithMultiTenancy - await client.Collections.Create(new CollectionConfig - { - Name = "Question", - VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), - GenerativeConfig = new GenerativeConfig.Cohere(), - Properties = - [ - Property.Text("question"), - Property.Text("answer") - ], - // highlight-start - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } // Enable multi-tenancy - // highlight-end - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Question", + VectorConfig = Configure.Vector("default", v => v.Text2VecWeaviate()), + GenerativeConfig = Configure.Generative.Cohere(), + Properties = [Property.Text("question"), Property.Text("answer")], + // highlight-start + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = true, + }, // Enable multi-tenancy + // highlight-end + } + ); // END SchemaWithMultiTenancy } @@ -104,36 +99,34 @@ await client.Collections.Create(new CollectionConfig public async Task TestSchemaWithIndexSettings() { // START SchemaWithIndexSettings - await client.Collections.Create(new CollectionConfig - { - Name = "Question", - VectorConfig = new VectorConfig( - "default", // Set the name of the vector configuration - new Vectorizer.Text2VecWeaviate(), + await client.Collections.Create( + new CollectionCreateParams + { + Name = "Question", + VectorConfig = Configure.Vector( + "default", + v => v.Text2VecWeaviate(), + // highlight-start + new VectorIndex.HNSW + { + Distance = VectorIndexConfig.VectorDistance.Cosine, // Configure the vector index + Quantizer = new VectorIndex.Quantizers.BQ(), // Enable vector compression (quantization) + } + // highlight-end + ), + GenerativeConfig = Configure.Generative.Cohere(), + Properties = [Property.Text("question"), Property.Text("answer")], // highlight-start - new VectorIndex.HNSW + // Configure the inverted index + InvertedIndexConfig = new InvertedIndexConfig { - Distance = VectorIndexConfig.VectorDistance.Cosine, // Configure the vector index - Quantizer = new VectorIndex.Quantizers.BQ() // Enable vector compression (quantization) - } + IndexNullState = true, + IndexPropertyLength = true, + IndexTimestamps = true, + }, // highlight-end - ), - GenerativeConfig = new GenerativeConfig.Cohere(), - Properties = - [ - Property.Text("question"), - Property.Text("answer") - ], - // highlight-start - // Configure the inverted index - InvertedIndexConfig = new InvertedIndexConfig - { - IndexNullState = true, - IndexPropertyLength = true, - IndexTimestamps = true } - // highlight-end - }); + ); // END SchemaWithIndexSettings } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs b/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs index 778e9c0c0..9d0f0c958 100644 --- a/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs +++ b/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs @@ -1,13 +1,13 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; using System; -using System.Threading.Tasks; -using System.Text.Json; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System.Net.Http; +using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Xunit; namespace WeaviateProject.Tests; @@ -18,10 +18,13 @@ private record JeopardyQuestionWithVector { [JsonPropertyName("Answer")] public string Answer { get; init; } + [JsonPropertyName("Question")] public string Question { get; init; } + [JsonPropertyName("Category")] public string Category { get; init; } + [JsonPropertyName("vector")] public float[] Vector { get; init; } } @@ -29,7 +32,7 @@ private record JeopardyQuestionWithVector [Fact] public async Task TestBringYourOwnVectors() { - using var client = Connect.Local(); + using var client = await Connect.Local(); string collectionName = "Question"; try @@ -42,22 +45,26 @@ public async Task TestBringYourOwnVectors() // START CreateCollection // Create the collection. - await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - Properties = - [ - Property.Text("answer"), - Property.Text("question"), - Property.Text("category") - ], - VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) - }); + await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + Properties = + [ + Property.Text("answer"), + Property.Text("question"), + Property.Text("category"), + ], + // Configure the "default" vector to be SelfProvided (BYOV) + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + } + ); // END CreateCollection // START ImportData var fname = "jeopardy_tiny_with_vectors_all-OpenAI-ada-002.json"; - var url = $"https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/{fname}"; + var url = + $"https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/{fname}"; using var httpClient = new HttpClient(); var responseBody = await httpClient.GetStringAsync(url); @@ -65,36 +72,29 @@ await client.Collections.Create(new CollectionConfig var data = JsonSerializer.Deserialize>(responseBody); // Get a handle to the collection - var questions = client.Collections.Use(collectionName); - var questionObjs = new List(); + var questions = client.Collections.Use(collectionName); - foreach (var d in data) + // Using Insert in a loop allows explicit vector assignment per object. + // Using Task.WhenAll creates a parallel batch-like effect. + var insertTasks = data.Select(d => { // highlight-start - var properties = new Dictionary - { - { "answer", d.Answer }, - { "question", d.Question }, - { "category", d.Category } - }; - - questionObjs.Add(new WeaviateObject - { - Properties = properties, - Vectors = Vectors.Create("default", d.Vector) - }); + return questions.Data.Insert( + // Pass properties as an Anonymous Type + properties: new + { + answer = d.Answer, + question = d.Question, + category = d.Category, + }, + // Explicitly pass the vector + vectors: d.Vector + ); // highlight-end - } + }); - var insertManyResponse = await questions.Data.InsertMany(questionObjs.ToArray()); + await Task.WhenAll(insertTasks); // END ImportData - // TODO[g-despot] Error handling missing - // Pass the list of objects (converted to an array) to InsertMany - // if (insertManyResponse.HasErrors) - // { - // Console.WriteLine($"Number of failed imports: {insertManyResponse.Errors.Count}"); - // Console.WriteLine($"First failed object error: {insertManyResponse.Errors.First()}"); - // } // START NearVector var queryVector = data[0].Vector; // Use a vector from the dataset for a reliable query @@ -116,8 +116,8 @@ await client.Collections.Create(new CollectionConfig // The first result should be the object we used for the query, with near-perfect certainty Assert.NotNull(response.Objects.First().Metadata.Certainty); Assert.True(response.Objects.First().Metadata.Certainty > 0.999); - var props = response.Objects.First().Properties as IDictionary; - Assert.NotNull(props); + + var props = response.Objects.First().Properties; Assert.Equal(data[0].Question, props["question"].ToString()); } finally @@ -128,4 +128,4 @@ await client.Collections.Create(new CollectionConfig } } } -} \ No newline at end of file +} diff --git a/_includes/code/csharp/WeaviateProject.Tests.csproj b/_includes/code/csharp/WeaviateProject.Tests.csproj index 3cfcc5e2e..43851bb41 100644 --- a/_includes/code/csharp/WeaviateProject.Tests.csproj +++ b/_includes/code/csharp/WeaviateProject.Tests.csproj @@ -1,17 +1,21 @@ - - net8.0 + net9.0 true + false + false + + + + + - - + - diff --git a/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs b/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs deleted file mode 100644 index a084f816e..000000000 --- a/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs +++ /dev/null @@ -1,253 +0,0 @@ -using Xunit; -using Weaviate.Client; -using Weaviate.Client.Models; -using System; -using System.Threading.Tasks; -using System.Linq; -using System.Collections.Generic; - -namespace WeaviateProject.Tests; - -public class ManageCollectionsMultiTenancyTest : IAsyncLifetime -{ - private WeaviateClient client; - - // Runs before each test (like @BeforeEach) - public Task InitializeAsync() - { - string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); - if (string.IsNullOrWhiteSpace(openaiApiKey)) - { - throw new ArgumentException("Please set the OPENAI_API_KEY environment variable."); - } - - // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. - // This must be configured in Weaviate's environment variables. - client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); - - return Task.CompletedTask; - } - - // Runs after each test (like @AfterEach) - public async Task DisposeAsync() - { - // Clean up any collections created during the tests - await client.Collections.DeleteAll(); - } - - [Fact] - public async Task TestEnableMultiTenancy() - { - // START EnableMultiTenancy - await client.Collections.Create(new CollectionConfig - { - Name = "MultiTenancyCollection", - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } - }); - // END EnableMultiTenancy - - var config = await client.Collections.Export("MultiTenancyCollection"); - Assert.True(config.MultiTenancyConfig.Enabled); - } - - [Fact] - public async Task TestEnableAutoActivationMultiTenancy() - { - // START EnableAutoActivation - await client.Collections.Create(new CollectionConfig - { - Name = "MultiTenancyCollection", - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantActivation = true } - }); - // END EnableAutoActivation - - var config = await client.Collections.Export("MultiTenancyCollection"); - Assert.True(config.MultiTenancyConfig.AutoTenantActivation); - } - - [Fact] - public async Task TestEnableAutoMT() - { - // START EnableAutoMT - await client.Collections.Create(new CollectionConfig - { - Name = "CollectionWithAutoMTEnabled", - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } - }); - // END EnableAutoMT - - var config = await client.Collections.Export("CollectionWithAutoMTEnabled"); - Assert.True(config.MultiTenancyConfig.AutoTenantCreation); - } - - [Fact] - public async Task TestUpdateAutoMT() - { - string collectionName = "MTCollectionNoAutoMT"; - await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = false } - }); - - // START UpdateAutoMT - var collection = client.Collections.Use(collectionName); - await collection.Config.Update(c => - { - c.MultiTenancyConfig.AutoTenantCreation = true; - }); - // END UpdateAutoMT - - var config = await client.Collections.Export(collectionName); - Assert.True(config.MultiTenancyConfig.AutoTenantCreation); - } - - [Fact] - public async Task TestAddTenantsToClass() - { - string collectionName = "MultiTenancyCollection"; - await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } - }); - - var collection = client.Collections.Use(collectionName); - - // START AddTenantsToClass - await collection.Tenants.Add( - new Tenant { Name = "tenantA" }, - new Tenant { Name = "tenantB" } - ); - // END AddTenantsToClass - - var tenants = (await collection.Tenants.List()).ToList(); - Assert.Equal(2, tenants.Count); - Assert.Contains(tenants, t => t.Name == "tenantA"); - Assert.Contains(tenants, t => t.Name == "tenantB"); - } - - [Fact] - public async Task TestListTenants() - { - string collectionName = "MultiTenancyCollection"; - var collection = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } - }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); - - // START ListTenants - var tenants = await collection.Tenants.List(); - foreach (var t in tenants) Console.WriteLine(t.Name); - // END ListTenants - - Assert.Equal(2, tenants.Count()); - } - - [Fact] - public async Task TestGetTenantsByName() - { - string collectionName = "MultiTenancyCollection"; - var collection = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } - }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); - - // START GetTenantsByName - var tenantNames = new[] { "tenantA", "tenantB", "nonExistentTenant" }; - var tenants = await collection.Tenants.List(tenantNames); - foreach (var t in tenants) Console.WriteLine(t.Name); - // END GetTenantsByName - - Assert.Equal(2, tenants.Count()); - } - - [Fact] - public async Task TestGetOneTenant() - { - string collectionName = "MultiTenancyCollection"; - var collection = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } - }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }); - - // START GetOneTenant - string tenantName = "tenantA"; - var tenant = await collection.Tenants.Get(tenantName); - Console.WriteLine(tenant?.Name); - // END GetOneTenant - - Assert.NotNull(tenant); - } - - [Fact] - public async Task TestActivateTenant() - { - string collectionName = "MultiTenancyCollection"; - var collection = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } - }); - await collection.Tenants.Add(new Tenant { Name = "tenantA", Status = TenantActivityStatus.Inactive }); - - // START ActivateTenants - string tenantName = "tenantA"; - await collection.Tenants.Activate(tenantName); - // END ActivateTenants - - var tenant = await collection.Tenants.Get(tenantName); - Assert.Equal(TenantActivityStatus.Active, tenant?.Status); - } - - [Fact] - public async Task TestDeactivateTenant() - { - string collectionName = "MultiTenancyCollection"; - var collection = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } - }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }); - - // START DeactivateTenants - string tenantName = "tenantA"; - await collection.Tenants.Deactivate(tenantName); - // END DeactivateTenants - - var tenant = await collection.Tenants.Get(tenantName); - Assert.Equal(TenantActivityStatus.Inactive, tenant?.Status); - } - - // START OffloadTenants - // Note: 'Offload' is not a current concept in the client. Use 'Deactivate' for similar functionality. - // Coming soon - // END OffloadTenants - - [Fact] - public async Task TestRemoveTenants() - { - string collectionName = "MultiTenancyCollection"; - var collection = await client.Collections.Create(new CollectionConfig - { - Name = collectionName, - MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } - }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); - - // START RemoveTenants - await collection.Tenants.Delete(new[] { "tenantB", "tenantX" }); - // END RemoveTenants - - var tenants = (await collection.Tenants.List()).ToList(); - Assert.Single(tenants); - Assert.Equal("tenantA", tenants.First().Name); - } -} \ No newline at end of file diff --git a/_includes/code/csharp/_SearchGenerativeTest.cs b/_includes/code/csharp/_SearchGenerativeTest.cs deleted file mode 100644 index 4a8a70443..000000000 --- a/_includes/code/csharp/_SearchGenerativeTest.cs +++ /dev/null @@ -1,304 +0,0 @@ -// using Xunit; -// using Weaviate.Client; -// using Weaviate.Client.Models; -// using System; -// using System.Threading.Tasks; -// using System.Text.Json; -// using System.Linq; -// using System.Collections.Generic; -// using System.Net.Http; - -// namespace WeaviateProject.Tests; - -// public class GenerativeSearchTest : IDisposable -// { -// private static readonly WeaviateClient client; - -// // Static constructor for one-time setup (like @BeforeAll) -// static GenerativeSearchTest() -// { -// // START INSTANTIATION-COMMON -// // Best practice: store your credentials in environment variables -// string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); -// string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); -// string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); -// string anthropicApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_APIKEY"); - -// var config = new ClientConfiguration -// { -// GrpcAddress = weaviateUrl, -// // Headers = new() -// // { -// // { "Authorization", $"Bearer {weaviateApiKey}" }, -// // { "X-OpenAI-Api-Key", openaiApiKey }, -// // { "X-Anthropic-Api-Key", anthropicApiKey } -// // } -// }; -// client = new WeaviateClient(config); -// // END INSTANTIATION-COMMON -// } - -// // Dispose is called once after all tests in the class are finished (like @AfterAll) -// public void Dispose() -// { -// // The C# client manages connections automatically and does not require an explicit 'close' method. -// GC.SuppressFinalize(this); -// } - -// [Fact] -// public async Task TestDynamicRag() -// { -// // START DynamicRag -// var reviews = client.Collections.Use("WineReviewNV"); -// var response = await reviews.Generate.NearText( -// "a sweet German white wine", -// limit: 2, -// targetVector: ["title_country"], -// prompt: new SinglePrompt { Prompt = "Translate this into German: {review_body}" }, -// groupedPrompt: new GroupedPrompt { Task = "Summarize these reviews" } -// // highlight-start -// // provider: new GenerativeProvider.(OpenAI) { Temperature = 0.1f } -// // highlight-end -// ); - -// foreach (var o in response.Objects) -// { -// Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); -// Console.WriteLine($"Single prompt result: {o.Generative?.Values}"); -// } -// Console.WriteLine($"Grouped task result: {response.Generative?.Values}"); -// // END DynamicRag -// } - -// [Fact] -// public async Task TestNamedVectorNearText() -// { -// // START NamedVectorNearTextPython -// var reviews = client.Collections.Use("WineReviewNV"); -// var response = await reviews.Generate.NearText( -// "a sweet German white wine", -// limit: 2, -// // highlight-start -// targetVector: ["title_country"], // Specify the target vector for named vector collections -// returnMetadata: MetadataOptions.Distance, -// prompt: new SinglePrompt { Prompt = "Translate this into German: {review_body}" }, -// groupedPrompt: new GroupedPrompt { Task = "Summarize these reviews" } -// // highlight-end -// ); - -// foreach (var o in response.Objects) -// { -// Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); -// Console.WriteLine($"Single prompt result: {o.Generative?.Values}"); -// } -// Console.WriteLine($"Grouped task result: {response.Generative?.Values}"); -// // END NamedVectorNearTextPython -// } - -// [Fact] -// public async Task TestSingleGenerative() -// { -// // START SingleGenerativePython -// // highlight-start -// var prompt = "Convert the following into a question for twitter. Include emojis for fun, but do not include the answer: {question}."; -// // highlight-end - -// var jeopardy = client.Collections.Use("JeopardyQuestion"); -// // highlight-start -// var response = await jeopardy.Generate.NearText( -// // highlight-end -// "World history", -// limit: 2, -// // highlight-start -// prompt: new SinglePrompt { Prompt = prompt } -// ); -// // highlight-end - -// foreach (var o in response.Objects) -// { -// var props = o.Properties as IDictionary; -// Console.WriteLine($"Property 'question': {props?["question"]}"); -// // highlight-start -// Console.WriteLine($"Single prompt result: {o.Generative?.Values}"); -// // highlight-end -// } -// // END SingleGenerativePython -// } - -// [Fact] -// public async Task TestSingleGenerativeProperties() -// { -// // START SingleGenerativePropertiesPython -// // highlight-start -// var prompt = "Convert this quiz question: {question} and answer: {answer} into a trivia tweet."; -// // highlight-end - -// var jeopardy = client.Collections.Use("JeopardyQuestion"); -// var response = await jeopardy.Generate.NearText( -// "World history", -// limit: 2, -// prompt: new SinglePrompt { Prompt = prompt } -// ); - -// // print source properties and generated responses -// foreach (var o in response.Objects) -// { -// Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); -// Console.WriteLine($"Single prompt result: {o.Generative?.Values}"); -// } -// // END SingleGenerativePropertiesPython -// } - -// [Fact] -// public async Task TestSingleGenerativeParameters() -// { -// // START SingleGenerativeParametersPython -// // highlight-start -// var singlePrompt = new SinglePrompt -// { -// Prompt = "Convert this quiz question: {question} and answer: {answer} into a trivia tweet.", -// // Metadata = true, -// Debug = true -// }; -// // highlight-end - -// var jeopardy = client.Collections.Use("JeopardyQuestion"); -// var response = await jeopardy.Generate.NearText( -// "World history", -// limit: 2, -// // highlight-start -// prompt: singlePrompt -// // highlight-end -// // provider: new GenerativeProvider.OpenAI() -// ); - -// // print source properties and generated responses -// foreach (var o in response.Objects) -// { -// Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); -// Console.WriteLine($"Single prompt result: {o.Generative?.Values}"); -// //Console.WriteLine($"Debug: {o.Generative?}"); -// //Console.WriteLine($"Metadata: {JsonSerializer.Serialize(o.Generative?.Metadata)}"); -// } -// // END SingleGenerativeParametersPython -// } - -// [Fact] -// public async Task TestGroupedGenerative() -// { -// // START GroupedGenerativePython -// // highlight-start -// var task = "What do these animals have in common, if anything?"; -// // highlight-end - -// var jeopardy = client.Collections.Use("JeopardyQuestion"); -// var response = await jeopardy.Generate.NearText( -// "Cute animals", -// limit: 3, -// // highlight-start -// groupedPrompt: new GroupedPrompt { Task = task } -// ); -// // highlight-end - -// // print the generated response -// Console.WriteLine($"Grouped task result: {response.Generative?.Values}"); -// // END GroupedGenerativePython -// } - -// // TODO[g-despot] Metadata missing -// [Fact] -// public async Task TestGroupedGenerativeParameters() -// { -// // START GroupedGenerativeParametersPython -// // highlight-start -// var groupedTask = new GroupedPrompt -// { -// Task = "What do these animals have in common, if anything?", -// // Metadata = true -// }; -// // highlight-end - -// var jeopardy = client.Collections.Use("JeopardyQuestion"); -// var response = await jeopardy.Generate.NearText( -// "Cute animals", -// limit: 3, -// // highlight-start -// groupedPrompt: groupedTask -// // highlight-end -// // provider: new GenerativeProvider.OpenAI() -// ); - -// // print the generated response -// Console.WriteLine($"Grouped task result: {response.Generative?.Values}"); -// // Console.WriteLine($"Metadata: {JsonSerializer.Serialize(response.Generative?.Metadata)}"); -// // END GroupedGenerativeParametersPython -// } - -// [Fact] -// public async Task TestGroupedGenerativeProperties() -// { -// // START GroupedGenerativeProperties Python -// var task = "What do these animals have in common, if anything?"; - -// var jeopardy = client.Collections.Use("JeopardyQuestion"); -// var response = await jeopardy.Generate.NearText( -// "Australian animals", -// limit: 3, -// groupedPrompt: new GroupedPrompt -// { -// Task = task, -// // highlight-start -// Properties = ["answer", "question"] -// // highlight-end -// } -// ); - -// // print the generated response -// // highlight-start -// foreach (var o in response.Objects) -// { -// Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); -// } -// Console.WriteLine($"Grouped task result: {response.Generative?.Values}"); -// // highlight-end -// // END GroupedGenerativeProperties Python -// } - -// //TODO[g-despot] Missing image processing -// [Fact] -// public async Task TestWorkingWithImages() -// { -// // START WorkingWithImages -// var srcImgPath = "https://images.unsplash.com/photo-1459262838948-3e2de6c1ec80?w=500&h=500&fit=crop"; -// using var httpClient = new HttpClient(); -// var imageBytes = await httpClient.GetByteArrayAsync(srcImgPath); -// var base64Image = Convert.ToBase64String(imageBytes); - -// var groupedTask = new GroupedPrompt -// { -// // highlight-start -// Task = "Formulate a Jeopardy!-style question about this image", -// // Images = [base64Image] // A list of base64 encoded strings of the image bytes -// // ImageProperties = ["img"] // Properties containing images in Weaviate -// // highlight-end -// }; - -// var jeopardy = client.Collections.Use("JeopardyQuestion"); -// var response = await jeopardy.Generate.NearText( -// "Australian animals", -// limit: 3, -// groupedPrompt: groupedTask -// // highlight-start -// // highlight-end -// // provider: new GenerativeProvider.Anthropic { MaxTokensToSample = 1000 } -// ); - -// // Print the source property and the generated response -// foreach (var o in response.Objects) -// { -// Console.WriteLine($"Properties: {JsonSerializer.Serialize(o.Properties)}"); -// } -// Console.WriteLine($"Grouped task result: {response.Generative?.Result}"); -// // END WorkingWithImages -// } -// } \ No newline at end of file diff --git a/_includes/code/csharp/_SearchMultiTargetTest.cs b/_includes/code/csharp/_SearchMultiTargetTest.cs deleted file mode 100644 index e69de29bb..000000000 diff --git a/_includes/code/csharp/quickstart/GitHubReadmeExample.cs b/_includes/code/csharp/quickstart/GitHubReadmeExample.cs new file mode 100644 index 000000000..38f782bb0 --- /dev/null +++ b/_includes/code/csharp/quickstart/GitHubReadmeExample.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; + +namespace WeaviateProject.Examples +{ + public class GitHubReadmeExample + { + public static async Task Run() + { + // Connect to Weaviate + // Using try-with-resources ensures client.close() is called automatically + var client = await Connect.Local(); + + // Clean slate (not in original script, but helpful for re-running main methods) + if (await client.Collections.Exists("Article")) + { + await client.Collections.Delete("Article"); + } + + // Create a collection + var articles = await client.Collections.Create( + new CollectionCreateParams + { + Name = "Article", + Properties = [Property.Text("content")], + VectorConfig = Configure.Vector("default", v => v.Text2VecTransformers()), // Use a vectorizer to generate embeddings during import + // VectorConfig = Configure.Vector("default", v => v.SelfProvided()) // If you want to import your own pre-generated embeddings + } + ); + + // Insert objects and generate embeddings + var data = new List + { + new { content = "Vector databases enable semantic search" }, + new { content = "Machine learning models generate embeddings" }, + new { content = "Weaviate supports hybrid search capabilities" }, + }; + await articles.Data.InsertMany(data.ToArray()); + + await Task.Delay(1000); + // Perform semantic search + var results = await articles.Query.NearText("Search objects by meaning", limit: 1); + // Print result + if (results.Objects.Count > 0) + { + Console.WriteLine(JsonSerializer.Serialize(results.Objects.First())); + } + } + } +} diff --git a/_includes/code/csharp/quickstart/Program.cs b/_includes/code/csharp/quickstart/Program.cs new file mode 100644 index 000000000..d595a13fa --- /dev/null +++ b/_includes/code/csharp/quickstart/Program.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; +using WeaviateProject.Examples; + +public class Program +{ + public static async Task Main(string[] args) + { + Console.WriteLine("Running QuickstartCreate..."); + await QuickstartCreate.Run(); + + Console.WriteLine("Running QuickstartQueryNearText..."); + await QuickstartQueryNearText.Run(); + + Console.WriteLine("Running QuickstartQueryNearTextRAG..."); + await QuickstartQueryNearTextRAG.Run(); + + Console.WriteLine("Running QuickstartCreateVectors..."); + await QuickstartCreateVectors.Run(); + + Console.WriteLine("Running QuickstartQueryNearVector..."); + await QuickstartQueryNearVector.Run(); + + Console.WriteLine("Running QuickstartQueryNearVectorRAG..."); + await QuickstartQueryNearVectorRAG.Run(); + + Console.WriteLine("Running QuickstartLocalCreate..."); + await QuickstartLocalCreate.Run(); + + Console.WriteLine("Running QuickstartLocalQueryNearText..."); + await QuickstartLocalQueryNearText.Run(); + + Console.WriteLine("Running QuickstartLocalQueryNearTextRAG..."); + await QuickstartLocalQueryNearTextRAG.Run(); + + Console.WriteLine("Running QuickstartLocalCreateVectors..."); + await QuickstartLocalCreateVectors.Run(); + + Console.WriteLine("Running QuickstartLocalQueryNearVector..."); + await QuickstartLocalQueryNearVector.Run(); + + Console.WriteLine("Running QuickstartLocalQueryNearVectorRAG..."); + await QuickstartLocalQueryNearVectorRAG.Run(); + } +} diff --git a/_includes/code/csharp/quickstart/QuickstartCreate.cs b/_includes/code/csharp/quickstart/QuickstartCreate.cs new file mode 100644 index 000000000..e1a4f4c6f --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartCreate.cs @@ -0,0 +1,84 @@ +// START CreateCollection +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; + +namespace WeaviateProject.Examples +{ + public class QuickstartCreate + { + public static async Task Run() + { + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string collectionName = "Movie"; + + // Connect to your Weaviate Cloud instance + var client = await Connect.Cloud(weaviateUrl, weaviateApiKey); + // END CreateCollection + // NOT SHOWN TO THE USER - DELETE EXISTING COLLECTION + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + // START CreateCollection + + // Create a collection + var movies = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + VectorConfig = Configure.Vector("default", v => v.Text2VecWeaviate()), + // Define properties for the collection + Properties = + [ + Property.Text("title"), + Property.Text("description"), + Property.Text("genre"), + ], + } + ); + + // Import three objects + var dataObjects = new List + { + new + { + title = "The Matrix", + description = "A computer hacker learns about the true nature of reality and his role in the war against its controllers.", + genre = "Science Fiction", + }, + new + { + title = "Spirited Away", + description = "A young girl becomes trapped in a mysterious world of spirits and must find a way to save her parents and return home.", + genre = "Animation", + }, + new + { + title = "The Lord of the Rings: The Fellowship of the Ring", + description = "A meek Hobbit and his companions set out on a perilous journey to destroy a powerful ring and save Middle-earth.", + genre = "Fantasy", + }, + }; + + // Insert objects using InsertMany + var insertResponse = await movies.Data.InsertMany(dataObjects.ToArray()); + + if (insertResponse.HasErrors) + { + Console.WriteLine($"Errors during import: {insertResponse.Errors}"); + } + else + { + Console.WriteLine( + $"Imported & vectorized {insertResponse.Count} objects into the Movie collection" + ); + } + } + } +} +// END CreateCollection diff --git a/_includes/code/csharp/quickstart/QuickstartCreateVectors.cs b/_includes/code/csharp/quickstart/QuickstartCreateVectors.cs new file mode 100644 index 000000000..9ebb7f3d1 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartCreateVectors.cs @@ -0,0 +1,111 @@ +// START CreateCollection +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; + +namespace WeaviateProject.Examples +{ + public class QuickstartCreateVectors + { + public static async Task Run() + { + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string collectionName = "Movie"; + + var client = await Connect.Cloud(weaviateUrl, weaviateApiKey); + // END CreateCollection + + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + // START CreateCollection + + // Step 1.2: Create a collection + var movies = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + Properties = + [ + Property.Text("title"), + Property.Text("description"), + Property.Text("genre"), + ], + } + ); + + // Step 1.3: Import three objects using collection initialization + var dataToInsert = new List + { + new BatchInsertRequest( + new + { + title = "The Matrix", + description = "A computer hacker learns about the true nature of reality and his role in the war against its controllers.", + genre = "Science Fiction", + }, + null, + new Vectors + { + { + "default", + new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f } + }, + } + ), + new BatchInsertRequest( + new + { + title = "Spirited Away", + description = "A young girl becomes trapped in a mysterious world of spirits and must find a way to save her parents and return home.", + genre = "Animation", + }, + null, + new Vectors + { + { + "default", + new float[] { 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f } + }, + } + ), + new BatchInsertRequest( + new + { + title = "The Lord of the Rings: The Fellowship of the Ring", + description = "A meek Hobbit and his companions set out on a perilous journey to destroy a powerful ring and save Middle-earth.", + genre = "Fantasy", + }, + null, + new Vectors + { + { + "default", + new float[] { 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f } + }, + } + ), + }; + + // Insert the objects with vectors + var insertResponse = await movies.Data.InsertMany(dataToInsert); + + if (insertResponse.HasErrors) + { + Console.WriteLine($"Errors during import: {insertResponse.Errors}"); + } + else + { + Console.WriteLine( + $"Imported {insertResponse.Count} objects with vectors into the Movie collection" + ); + } + } + } +} +// END CreateCollection diff --git a/_includes/code/csharp/quickstart/QuickstartLocalCreate.cs b/_includes/code/csharp/quickstart/QuickstartLocalCreate.cs new file mode 100644 index 000000000..6d00abfb2 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalCreate.cs @@ -0,0 +1,92 @@ +// START CreateCollection +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; + +namespace WeaviateProject.Examples +{ + public class QuickstartLocalCreate + { + public static async Task Run() + { + string collectionName = "Movie"; + + // Step 1.1: Connect to your local Weaviate instance + var client = await Connect.Local(); + // END CreateCollection + // NOT SHOWN TO THE USER - DELETE EXISTING COLLECTION + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + // START CreateCollection + + // Step 1.2: Create a collection + var movies = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + VectorConfig = Configure.Vector( + "default", + v => + v.Text2VecOllama( + apiEndpoint: "http://ollama:11434", + model: "nomic-embed-text" + ) + ), + // Define properties for the collection + Properties = + [ + Property.Text("title"), + Property.Text("description"), + Property.Text("genre"), + ], + } + ); + + // Step 1.3: Import three objects + var dataObjects = new List + { + new + { + title = "The Matrix", + description = "A computer hacker learns about the true nature of reality and his role in the war against its controllers.", + genre = "Science Fiction", + }, + new + { + title = "Spirited Away", + description = "A young girl becomes trapped in a mysterious world of spirits and must find a way to save her parents and return home.", + genre = "Animation", + }, + new + { + title = "The Lord of the Rings: The Fellowship of the Ring", + description = "A meek Hobbit and his companions set out on a perilous journey to destroy a powerful ring and save Middle-earth.", + genre = "Fantasy", + }, + }; + + // Insert objects using InsertMany + var insertResponse = await movies.Data.InsertMany(dataObjects.ToArray()); + + if (insertResponse.HasErrors) + { + Console.WriteLine($"Errors during import: {insertResponse.Errors}"); + } + else + { + Console.WriteLine( + $"Imported & vectorized {insertResponse.Count} objects into the Movie collection" + ); + } + // END CreateCollection + Thread.Sleep(1000); + // START CreateCollection + } + } +} +// END CreateCollection diff --git a/_includes/code/csharp/quickstart/QuickstartLocalCreateVectors.cs b/_includes/code/csharp/quickstart/QuickstartLocalCreateVectors.cs new file mode 100644 index 000000000..1e08c3d1b --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalCreateVectors.cs @@ -0,0 +1,97 @@ +// START CreateCollection +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; + +namespace WeaviateProject.Examples +{ + public class QuickstartLocalCreateVectors + { + public static async Task Run() + { + string collectionName = "Movie"; + + // Step 1.1: Connect to your local Weaviate instance + var client = await Connect.Local(); + // END CreateCollection + // NOT SHOWN TO THE USER - DELETE EXISTING COLLECTION + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + // START CreateCollection + + // Step 1.2: Create a collection + var movies = await client.Collections.Create( + new CollectionCreateParams + { + Name = collectionName, + // No automatic vectorization since we're providing vectors + VectorConfig = Configure.Vector("default", v => v.SelfProvided()), + // Define properties for the collection + Properties = + [ + Property.Text("title"), + Property.Text("description"), + Property.Text("genre"), + ], + } + ); + + // Step 1.3: Import three objects + var dataToInsert = new List + { + new( + new + { + title = "The Matrix", + description = "A computer hacker learns about the true nature of reality and his role in the war against its controllers.", + genre = "Science Fiction", + }, + null, + new float[] { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f } + ), + new( + new + { + title = "Spirited Away", + description = "A young girl becomes trapped in a mysterious world of spirits and must find a way to save her parents and return home.", + genre = "Animation", + }, + null, + new float[] { 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f } + ), + new( + new + { + title = "The Lord of the Rings: The Fellowship of the Ring", + description = "A meek Hobbit and his companions set out on a perilous journey to destroy a powerful ring and save Middle-earth.", + genre = "Fantasy", + }, + null, + new float[] { 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f } + ), + }; + + // Insert the objects with vectors + var insertResponse = await movies.Data.InsertMany(dataToInsert); + if (insertResponse.HasErrors) + { + Console.WriteLine($"Errors during import: {insertResponse.Errors}"); + } + else + { + Console.WriteLine( + $"Imported {insertResponse.Count} objects with vectors into the Movie collection" + ); + } + // END CreateCollection + Thread.Sleep(1000); + // START CreateCollection + } + } +} +// END CreateCollection diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearText.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearText.cs new file mode 100644 index 000000000..6d6b544bb --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearText.cs @@ -0,0 +1,41 @@ +// START NearText +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Weaviate.Client; + +namespace WeaviateProject.Examples +{ + public class QuickstartLocalQueryNearText + { + public static async Task Run() + { + // Step 2.1: Connect to your local Weaviate instance + var client = await Connect.Local(); + + // Step 2.2: Perform a semantic search with NearText + var movies = client.Collections.Use("Movie"); + // highlight-start + var response = await movies.Query.NearText( + "sci-fi", + limit: 2, + returnProperties: ["title", "description", "genre"] + ); + // highlight-end + + // Inspect the results + Console.WriteLine("--- Query Results ---"); + foreach (var obj in response.Objects) + { + Console.WriteLine( + JsonSerializer.Serialize( + obj.Properties, + new JsonSerializerOptions { WriteIndented = true } + ) + ); + } + } + } +} +// END NearText diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearTextRAG.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearTextRAG.cs new file mode 100644 index 000000000..334ff58cf --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearTextRAG.cs @@ -0,0 +1,40 @@ +// START RAG +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Generative; + +namespace WeaviateProject.Examples +{ + public class QuickstartLocalQueryNearTextRAG + { + public static async Task Run() + { + // Step 3.1: Connect to your local Weaviate instance + var client = await Connect.Local(); + + // Step 3.2: Perform RAG with nearText results + var movies = client.Collections.Use("Movie"); + + // highlight-start + var response = await movies.Generate.NearText( + "sci-fi", + limit: 1, + returnProperties: ["title", "description", "genre"], + groupedTask: new GroupedTask("Write a tweet with emojis about this movie."), + provider: new Providers.Ollama + { + ApiEndpoint = "http://ollama:11434", // If using Docker you might need: http://host.docker.internal:11434 + Model = "llama3.2", // The model to use + } + ); + // highlight-end + + // Inspect the results + Console.WriteLine(JsonSerializer.Serialize(response.Generative.Values)); + } + } +} +// END RAG diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVector.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVector.cs new file mode 100644 index 000000000..3850292f3 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVector.cs @@ -0,0 +1,43 @@ +// START NearText +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; + +namespace WeaviateProject.Examples +{ + public class QuickstartLocalQueryNearVector + { + public static async Task Run() + { + // Step 2.1: Connect to your local Weaviate instance + var client = await Connect.Local(); + + // Step 2.2: Perform a vector search with NearVector + var movies = client.Collections.Use("Movie"); + + // highlight-start + float[] queryVector = [0.11f, 0.21f, 0.31f, 0.41f, 0.51f, 0.61f, 0.71f, 0.81f]; + + var response = await movies.Query.NearVector( + queryVector, + limit: 2, + returnProperties: ["title", "description", "genre"] + ); + // highlight-end + + // Inspect the results + Console.WriteLine("--- Query Results ---"); + foreach (var obj in response.Objects) + { + Console.WriteLine( + JsonSerializer.Serialize( + obj.Properties, + new JsonSerializerOptions { WriteIndented = true } + ) + ); + } + } + } +} +// END NearText diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVectorRAG.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVectorRAG.cs new file mode 100644 index 000000000..30a4f7d02 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVectorRAG.cs @@ -0,0 +1,42 @@ +// START RAG +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Generative; + +namespace WeaviateProject.Examples +{ + public class QuickstartLocalQueryNearVectorRAG + { + public static async Task Run() + { + // Step 3.1: Connect to your local Weaviate instance + var client = await Connect.Local(); + + // Step 3.2: Perform RAG with NearVector results + var movies = client.Collections.Use("Movie"); + + // highlight-start + float[] queryVector = [0.11f, 0.21f, 0.31f, 0.41f, 0.51f, 0.61f, 0.71f, 0.81f]; + + var response = await movies.Generate.NearVector( + vectors: queryVector, + limit: 1, + returnProperties: ["title", "description", "genre"], + groupedTask: new GroupedTask("Write a tweet with emojis about this movie."), + provider: new Providers.Ollama + { + ApiEndpoint = "http://ollama:11434", // If using Docker you might need: http://host.docker.internal:11434 + Model = "llama3.2", // The model to use + } + ); + // highlight-end + + // Inspect the results + Console.WriteLine(JsonSerializer.Serialize(response.Generative.Values)); + } + } +} +// END RAG diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearText.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearText.cs new file mode 100644 index 000000000..8f9dfb679 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearText.cs @@ -0,0 +1,46 @@ +// START NearText +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; + +namespace WeaviateProject.Examples +{ + public class QuickstartQueryNearText + { + public static async Task Run() + { + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + // Step 2.1: Connect to your Weaviate Cloud instance + var client = await Connect.Cloud(weaviateUrl, weaviateApiKey); + + // Step 2.2: Perform a semantic search with NearText + var movies = client.Collections.Use("Movie"); + + // highlight-start + var response = await movies.Query.NearText( + "sci-fi", + limit: 2, + returnProperties: ["title", "description", "genre"] + ); + // highlight-end + + // Inspect the results + Console.WriteLine("--- Query Results ---"); + foreach (var obj in response.Objects) + { + Console.WriteLine( + JsonSerializer.Serialize( + obj.Properties, + new JsonSerializerOptions { WriteIndented = true } + ) + ); + } + } + } +} +// END NearText diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearTextRAG.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearTextRAG.cs new file mode 100644 index 000000000..f8d7e89d4 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearTextRAG.cs @@ -0,0 +1,52 @@ +// START RAG +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Generative; + +namespace WeaviateProject.Examples +{ + public class QuickstartQueryNearTextRAG + { + public static async Task Run() + { + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string anthropicApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"); + + // Step 3.1: Connect to your Weaviate Cloud instance + var client = await Connect.Cloud( + weaviateUrl, + weaviateApiKey, + headers: new Dictionary + { + { "X-Anthropic-Api-Key", anthropicApiKey }, + } + ); + + // Step 3.2: Perform RAG with nearText results + var movies = client.Collections.Use("Movie"); + + // highlight-start + var response = await movies.Generate.NearText( + "sci-fi", + limit: 1, + returnProperties: ["title", "description", "genre"], + groupedTask: new GroupedTask("Write a tweet with emojis about this movie."), + provider: new Providers.Anthropic + { + Model = "claude-3-5-haiku-latest", // The model to use + } + ); + // highlight-end + + // Inspect the results + Console.WriteLine(JsonSerializer.Serialize(response.Generative.Values)); + } + } +} +// END RAG diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearVector.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearVector.cs new file mode 100644 index 000000000..754f34246 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearVector.cs @@ -0,0 +1,47 @@ +// START NearText +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; + +namespace WeaviateProject.Examples +{ + public class QuickstartQueryNearVector + { + public static async Task Run() + { + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + // Step 2.1: Connect to your Weaviate Cloud instance + var client = await Connect.Cloud(weaviateUrl, weaviateApiKey); + + // Step 2.2: Perform a vector search with NearVector + var movies = client.Collections.Use("Movie"); + + // highlight-start + float[] queryVector = [0.11f, 0.21f, 0.31f, 0.41f, 0.51f, 0.61f, 0.71f, 0.81f]; + + var response = await movies.Query.NearVector( + queryVector, + limit: 2, + returnProperties: ["title", "description", "genre"] + ); + // highlight-end + + // Inspect the results + Console.WriteLine("--- Query Results ---"); + foreach (var obj in response.Objects) + { + Console.WriteLine( + JsonSerializer.Serialize( + obj.Properties, + new JsonSerializerOptions { WriteIndented = true } + ) + ); + } + } + } +} +// END NearText diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearVectorRAG.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearVectorRAG.cs new file mode 100644 index 000000000..94233f6ba --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearVectorRAG.cs @@ -0,0 +1,54 @@ +// START RAG +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Weaviate.Client; +using Weaviate.Client.Models; +using Weaviate.Client.Models.Generative; + +namespace WeaviateProject.Examples +{ + public class QuickstartQueryNearVectorRAG + { + public static async Task Run() + { + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string anthropicApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"); + + // Step 3.1: Connect to your Weaviate Cloud instance + var client = await Connect.Cloud( + weaviateUrl, + weaviateApiKey, + headers: new Dictionary + { + { "X-Anthropic-Api-Key", anthropicApiKey }, + } + ); + + // Step 3.2: Perform RAG with NearVector results + var movies = client.Collections.Use("Movie"); + + // highlight-start + float[] queryVector = [0.11f, 0.21f, 0.31f, 0.41f, 0.51f, 0.61f, 0.71f, 0.81f]; + + var response = await movies.Generate.NearVector( + vectors: queryVector, + limit: 1, + returnProperties: ["title", "description", "genre"], + groupedTask: new GroupedTask("Write a tweet with emojis about this movie."), + provider: new Providers.Anthropic + { + Model = "claude-3-5-haiku-latest", // The model to use + } + ); + // highlight-end + + // Inspect the results + Console.WriteLine(JsonSerializer.Serialize(response.Generative.Values)); + } + } +} +// END RAG diff --git a/_includes/code/csharp/quickstart/WeaviateProject.csproj b/_includes/code/csharp/quickstart/WeaviateProject.csproj new file mode 100644 index 000000000..bcc454a3c --- /dev/null +++ b/_includes/code/csharp/quickstart/WeaviateProject.csproj @@ -0,0 +1,15 @@ + + + net9.0 + Exe + false + + + + + + + + + + diff --git a/_includes/code/howto/manage-data.create.with.geo.mdx b/_includes/code/howto/manage-data.create.with.geo.mdx index 417189881..67e8b5694 100644 --- a/_includes/code/howto/manage-data.create.with.geo.mdx +++ b/_includes/code/howto/manage-data.create.with.geo.mdx @@ -127,7 +127,7 @@ public class App { language="java" /> - + - + - + - + props1 = Map.of("title", "The Matrix", "description", "A computer hacker learns about the true nature of reality and his role in the war against its controllers.", "genre", "Science Fiction"); - // Use primitive float[] for v6 float[] vector1 = new float[] {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f}; diff --git a/_includes/code/python/keycloak_helper_script.py b/_includes/code/python/keycloak_helper_script.py index c9f0eda08..05c52c872 100644 --- a/_includes/code/python/keycloak_helper_script.py +++ b/_includes/code/python/keycloak_helper_script.py @@ -90,6 +90,7 @@ def create_realm(self) -> bool: "realm": self.realm_name, "enabled": True, "displayName": "Weaviate Test Realm", + "sslRequired": "none", "registrationAllowed": False, "loginWithEmailAllowed": True, "duplicateEmailsAllowed": False, diff --git a/_includes/code/quickstart.byov.schema.mdx b/_includes/code/quickstart.byov.schema.mdx index d589eea25..a854432c3 100644 --- a/_includes/code/quickstart.byov.schema.mdx +++ b/_includes/code/quickstart.byov.schema.mdx @@ -34,7 +34,7 @@ import ByovAllShCode from "!!raw-loader!/_includes/code/quickstart.byov.all.sh"; language="java" /> - + - +
```
- + Add this package to your project:

```xml - + ```
diff --git a/_includes/code/quickstart/clients.install.new.mdx b/_includes/code/quickstart/clients.install.new.mdx index be6090e16..b64c76aa5 100644 --- a/_includes/code/quickstart/clients.install.new.mdx +++ b/_includes/code/quickstart/clients.install.new.mdx @@ -44,5 +44,12 @@ go get github.com/weaviate/weaviate-go-client/v5 ``` +
+ + +```xml + +``` + diff --git a/_includes/code/quickstart/connect.partial.mdx b/_includes/code/quickstart/connect.partial.mdx index 89b39bfdf..210a1bca4 100644 --- a/_includes/code/quickstart/connect.partial.mdx +++ b/_includes/code/quickstart/connect.partial.mdx @@ -57,7 +57,7 @@ import HostnameWarning from "/_includes/wcs/hostname-warning.mdx"; />
- + - + - + - + - + - + - + - + - + - + - + @@ -59,5 +60,13 @@ The collection also contains a configuration for the generative (RAG) integratio language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.import-vectors.query.rag.mdx b/_includes/code/quickstart/quickstart.short.import-vectors.query.rag.mdx index 904c96017..078c208a1 100644 --- a/_includes/code/quickstart/quickstart.short.import-vectors.query.rag.mdx +++ b/_includes/code/quickstart/quickstart.short.import-vectors.query.rag.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.imp import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_vectors_3/quickstart.short.import_vectors.query.rag.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartQueryNearVectorRAG.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartQueryNearVectorRAG.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartQueryNearVectorRAG.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.import_vectors.create_collection.mdx b/_includes/code/quickstart/quickstart.short.import_vectors.create_collection.mdx index 8eedb52c3..243072faf 100644 --- a/_includes/code/quickstart/quickstart.short.import_vectors.create_collection.mdx +++ b/_includes/code/quickstart/quickstart.short.import_vectors.create_collection.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.imp import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_vectors_1/quickstart.short.import_vectors.create_collection.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartCreateVectors.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartCreateVectors.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartCreateVectors.cs"; @@ -58,6 +59,13 @@ The collection also contains a configuration for the generative (RAG) integratio endMarker="// END CreateCollection" language="javaraw" /> - + + + diff --git a/_includes/code/quickstart/quickstart.short.import_vectors.query.nearvector.mdx b/_includes/code/quickstart/quickstart.short.import_vectors.query.nearvector.mdx index 40a381728..97ea26d49 100644 --- a/_includes/code/quickstart/quickstart.short.import_vectors.query.nearvector.mdx +++ b/_includes/code/quickstart/quickstart.short.import_vectors.query.nearvector.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.imp import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_vectors_2/quickstart.short.import_vectors.query.nearvector.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartQueryNearVector.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartQueryNearVector.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartQueryNearVector.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.local.create_collection.mdx b/_includes/code/quickstart/quickstart.short.local.create_collection.mdx index 8e491ae60..17ae13851 100644 --- a/_includes/code/quickstart/quickstart.short.local.create_collection.mdx +++ b/_includes/code/quickstart/quickstart.short.local.create_collection.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.loc import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_local_1/quickstart.short.local.create_collection.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartLocalCreate.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartLocalCreate.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartLocalCreate.cs"; @@ -58,6 +59,13 @@ The collection also contains a configuration for the generative (RAG) integratio endMarker="// END CreateCollection" language="javaraw" /> - + + + diff --git a/_includes/code/quickstart/quickstart.short.local.import-vectors.query.rag.mdx b/_includes/code/quickstart/quickstart.short.local.import-vectors.query.rag.mdx index afe3cc162..dbe6ca859 100644 --- a/_includes/code/quickstart/quickstart.short.local.import-vectors.query.rag.mdx +++ b/_includes/code/quickstart/quickstart.short.local.import-vectors.query.rag.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.loc import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_local_vectors_3/quickstart.short.local.import_vectors.query.rag.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartLocalQueryNearVectorRAG.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartLocalQueryNearVectorRAG.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVectorRAG.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.local.import_vectors.create_collection.mdx b/_includes/code/quickstart/quickstart.short.local.import_vectors.create_collection.mdx index 096249152..ed7a1bcd1 100644 --- a/_includes/code/quickstart/quickstart.short.local.import_vectors.create_collection.mdx +++ b/_includes/code/quickstart/quickstart.short.local.import_vectors.create_collection.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.loc import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_local_vectors_1/quickstart.short.local.import_vectors.create_collection.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartLocalCreateVectors.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartLocalCreateVectors.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartLocalCreateVectors.cs"; @@ -58,6 +59,13 @@ The collection also contains a configuration for the generative (RAG) integratio endMarker="// END CreateCollection" language="javaraw" /> - + + + diff --git a/_includes/code/quickstart/quickstart.short.local.import_vectors.query.nearvector.mdx b/_includes/code/quickstart/quickstart.short.local.import_vectors.query.nearvector.mdx index f01e5b3ba..0024b73d9 100644 --- a/_includes/code/quickstart/quickstart.short.local.import_vectors.query.nearvector.mdx +++ b/_includes/code/quickstart/quickstart.short.local.import_vectors.query.nearvector.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.loc import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_local_vectors_2/quickstart.short.local.import_vectors.query.nearvector.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartLocalQueryNearVector.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartLocalQueryNearVector.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVector.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.local.query.neartext.mdx b/_includes/code/quickstart/quickstart.short.local.query.neartext.mdx index 858af1d81..8b6d21dd3 100644 --- a/_includes/code/quickstart/quickstart.short.local.query.neartext.mdx +++ b/_includes/code/quickstart/quickstart.short.local.query.neartext.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.loc import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_local_2/quickstart.short.local.query.neartext.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartLocalQueryNearText.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartLocalQueryNearText.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartLocalQueryNearText.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.local.query.rag.mdx b/_includes/code/quickstart/quickstart.short.local.query.rag.mdx index 310377e92..24f0c8e12 100644 --- a/_includes/code/quickstart/quickstart.short.local.query.rag.mdx +++ b/_includes/code/quickstart/quickstart.short.local.query.rag.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.loc import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_local_3/quickstart.short.local.query.rag.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartLocalQueryNearTextRAG.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartLocalQueryNearTextRAG.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartLocalQueryNearTextRAG.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.query.neartext.mdx b/_includes/code/quickstart/quickstart.short.query.neartext.mdx index 79b4ec8e2..f8e415652 100644 --- a/_includes/code/quickstart/quickstart.short.query.neartext.mdx +++ b/_includes/code/quickstart/quickstart.short.query.neartext.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.que import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_2/quickstart.short.query.neartext.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartQueryNearText.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartQueryNearText.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartQueryNearText.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/quickstart/quickstart.short.query.rag.mdx b/_includes/code/quickstart/quickstart.short.query.rag.mdx index c220b4f5a..57168079f 100644 --- a/_includes/code/quickstart/quickstart.short.query.rag.mdx +++ b/_includes/code/quickstart/quickstart.short.query.rag.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.que import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_3/quickstart.short.query.rag.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartQueryNearTextRAG.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartQueryNearTextRAG.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartQueryNearTextRAG.cs"; @@ -48,4 +49,12 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w language="javaraw" /> + + + diff --git a/_includes/code/replication.get.object.by.id.mdx b/_includes/code/replication.get.object.by.id.mdx index 6f5cc7852..c32cedeeb 100644 --- a/_includes/code/replication.get.object.by.id.mdx +++ b/_includes/code/replication.get.object.by.id.mdx @@ -120,7 +120,7 @@ public class App { ``` - + - + - + - + - + - + - + - + [Integration tests](https://github.com/Unipisa/WeaviateNET/tree/master/src/WeaviateNET.Test) | [MIT](https://github.com/Unipisa/WeaviateNET/blob/master/LICENSE.txt) | -| .NET/C# | Stuart Cam
[Search Pioneer](https://searchpioneer.com/) | [GitHub](https://github.com/searchpioneer/weaviate-dotnet-client) | [NuGet](https://www.nuget.org/packages/SearchPioneer.Weaviate.Client) | [GitHub README](https://github.com/searchpioneer/weaviate-dotnet-client)
[Integration tests](https://github.com/searchpioneer/weaviate-dotnet-client/tree/main/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api) | [Apache 2.0](https://github.com/searchpioneer/weaviate-dotnet-client/blob/main/license.txt) | | PHP | [Tim Kleyersburg](https://www.tim-kleyersburg.de/) | [GitHub](https://github.com/timkley/weaviate-php) | [Packagist](https://packagist.org/packages/timkley/weaviate-php) | [GitHub README](https://github.com/timkley/weaviate-php) | [MIT](https://github.com/timkley/weaviate-php/blob/main/LICENSE.md) | | Ruby | Andrei Bondarev
[Source Labs](https://www.sourcelabs.io/) | [GitHub](https://github.com/andreibondarev/weaviate-ruby) | [RubyGems](https://rubygems.org/gems/weaviate-ruby) | [RubyDoc](https://rubydoc.info/gems/weaviate-ruby) | [MIT](https://github.com/andreibondarev/weaviate-ruby/blob/main/LICENSE.txt) diff --git a/docs/weaviate/client-libraries/csharp.mdx b/docs/weaviate/client-libraries/csharp.mdx index 49a9c4809..9fd4a7f10 100644 --- a/docs/weaviate/client-libraries/csharp.mdx +++ b/docs/weaviate/client-libraries/csharp.mdx @@ -1,6 +1,6 @@ --- -title: C# - Beta release -sidebar_label: C# 🚧 +title: C# +sidebar_label: C# description: "Official C# client library documentation for integrating Weaviate with .NET applications and services." image: og/docs/client-libraries.jpg # tags: ['c#', 'csharp', 'client library', 'experimental'] @@ -12,15 +12,6 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import CSharpCode from "!!raw-loader!/_includes/code/csharp/GetStartedTest.cs"; import QuickLinks from "/src/components/QuickLinks"; -:::caution Preview - -The `C#` client is available as a beta release.
- -This means that the library is still under development and may change in future releases, including potential breaking changes. -**We do not recommend using this client library in production environments at this time.** - -::: - export const csharpCardsData = [ { title: "weaviate/csharp-client", @@ -47,7 +38,7 @@ This page broadly covers the Weaviate C# (beta release). For usage information n ## Installation ```xml - + ```
@@ -89,7 +80,7 @@ Get started with Weaviate using this C# example. The code walks you through thes 1. **[Search/query the database](../search/index.mdx)**: Execute a vector search to find questions semantically similar to the query `biology`. - + - ## Releases Go to the [GitHub releases page](https://github.com/weaviate/csharp-client/releases) to see the history of the C# client library releases and change logs. diff --git a/docs/weaviate/client-libraries/index.mdx b/docs/weaviate/client-libraries/index.mdx index 55099d8fd..62450290b 100644 --- a/docs/weaviate/client-libraries/index.mdx +++ b/docs/weaviate/client-libraries/index.mdx @@ -7,7 +7,7 @@ image: og/docs/client-libraries.jpg # tags: ['client libraries', 'cli'] --- -You can interact with Weaviate by using the GraphQL, gRPC or RESTful API directly, or with one of the available client libraries. Currently, Weaviate supports: +You can interact with Weaviate by using the GraphQL, gRPC or RESTful API directly, or with one of the available client libraries. Currently, Weaviate offers these libraries: import CardsSection from "/src/components/CardsSection"; @@ -21,8 +21,7 @@ export const clientLibrariesData = [ }, { title: "TypeScript / JavaScript Client", - description: - "Use the official client (v3) with Node.js.", + description: "Use the official client (v3) with Node.js.", link: "/weaviate/client-libraries/typescript/", icon: "fab fa-js", }, @@ -64,14 +63,16 @@ import ClientCapabilitiesOverview from "/_includes/client.capabilities.mdx"; -### Community clients +:::info Don't see your preferred language? -There also exist [community clients](./community.md) that were prepared by our wonderful community members. These clients are not maintained by the core Weaviate team, but by the community members themselves. To contribute to these clients, contact the maintainers directly. +If you want to contribute a client, or to request a particular client, let us know in [the community forum](https://forum.weaviate.io/) -:::note Don't see your preferred language? -If you want to contribute a client, or to request a particular client, let us know in [the forum](https://forum.weaviate.io/) ::: +### Community clients + +There also exist [community clients](./community.md) that were prepared by our wonderful community members. These clients are not maintained by the core Weaviate team, but by the community members themselves. To contribute to these clients, contact the maintainers directly. + ## Questions and feedback import DocsFeedback from "/_includes/docs-feedback.mdx"; diff --git a/docs/weaviate/configuration/compression/bq-compression.md b/docs/weaviate/configuration/compression/bq-compression.md index d45731221..7b7494476 100644 --- a/docs/weaviate/configuration/compression/bq-compression.md +++ b/docs/weaviate/configuration/compression/bq-compression.md @@ -75,7 +75,7 @@ BQ can be enabled at collection creation time through the collection definition: language="java" /> - + - + - + + + + The final dimensionality of the MUVERA encoded vector will be diff --git a/docs/weaviate/configuration/compression/pq-compression.md b/docs/weaviate/configuration/compression/pq-compression.md index a595ed0bc..4bb49d48d 100644 --- a/docs/weaviate/configuration/compression/pq-compression.md +++ b/docs/weaviate/configuration/compression/pq-compression.md @@ -79,7 +79,7 @@ To configure PQ in a collection, use the [PQ parameters](./pq-compression.md#pq- language="java" /> - + - + - + - + - + - + - + - + - + - + - + - + - + + + + ### Revoke roles from an OIDC group @@ -117,6 +126,14 @@ This example removes the `testRole` and `viewer` roles from the `/admin-group`. ``` + + + ### List roles assigned to an OIDC group @@ -162,6 +179,14 @@ Retrieve a list of all roles that have been assigned to a specific OIDC group. ``` + + +
@@ -216,6 +241,14 @@ This example shows how to get a list of all OIDC groups that Weaviate is aware o ``` + + +
@@ -272,6 +305,14 @@ This example shows which groups have the `testRole` assigned to them. ``` + + +
diff --git a/docs/weaviate/configuration/rbac/manage-roles.mdx b/docs/weaviate/configuration/rbac/manage-roles.mdx index 9853b09d1..d44dffc5f 100644 --- a/docs/weaviate/configuration/rbac/manage-roles.mdx +++ b/docs/weaviate/configuration/rbac/manage-roles.mdx @@ -14,9 +14,10 @@ import PyCode from "!!raw-loader!/_includes/code/python/howto.configure.rbac.per import TSCode from "!!raw-loader!/_includes/code/typescript/howto.configure.rbac.permissions.ts"; import RolePyCode from "!!raw-loader!/_includes/code/python/howto.configure.rbac.roles.py"; import RoleTSCode from "!!raw-loader!/_includes/code/typescript/howto.configure.rbac.roles.ts"; -import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/RBACTest.java"; +import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/RBACTest.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/rbac-roles.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/configure/rbac.roles_test.go"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/RBACTest.cs"; :::info Added in `v1.29` Role-based access control (RBAC) is generally available in Weaviate from version `v1.29`. @@ -78,6 +79,14 @@ Role management requires appropriate `role` resource permissions that can be obt language="java" /> + + + ## Role management {#role-management} @@ -155,6 +164,14 @@ This example creates a role called `testRole` with permissions to: language="java" /> + + + #### Create a role with `User Management` permissions {#user-management-permissions} @@ -174,7 +191,7 @@ This example creates a role called `testRole` with permissions to: /> - @@ -199,11 +216,19 @@ This example creates a role called `testRole` with permissions to: + text={JavaCode} + startMarker="// START AddManageUsersPermission" + endMarker="// END AddManageUsersPermission" + language="java" + /> + + + @@ -254,6 +279,14 @@ This example creates a role called `testRole` with permissions to: language="java" /> + + + #### Create a role with `Tenant` permissions {#tenants-permissions} @@ -305,6 +338,14 @@ This example creates a role called `testRole` with permissions to: language="java" /> + + + #### Create a role with `Data Objects` permissions {#data-permissions} @@ -355,6 +396,14 @@ This example creates a role called `testRole` with permissions to: language="java" /> + + + #### Create a role with `Backups` permissions {#backups-permissions} @@ -404,6 +453,14 @@ This example creates a role called `testRole` with permissions to: language="java" /> + + + #### Create a role with `Cluster Data Access` permissions {#clusters-permissions} @@ -453,6 +510,14 @@ This example creates a role called `testRole` with permissions to: language="java" /> + + + #### Create a role with `Node Data Access` permissions {#nodes-permissions} @@ -502,6 +567,14 @@ This example creates a role called `testRole` with permissions to: language="java" /> + + + #### Create a role with `Collection Alias` permissions {#aliases-permissions} @@ -529,10 +602,10 @@ This example creates a role called `testRole` with permissions to: @@ -545,10 +618,18 @@ This example creates a role called `testRole` with permissions to: + + + @@ -599,6 +680,14 @@ endMarker="// END AddReplicationsPermission" language="java" /> + + + #### Create a role with `Groups` permissions {#groups-permissions} @@ -647,6 +736,14 @@ endMarker="// END AddGroupsPermission" language="java" /> + + + ### Grant additional permissions @@ -698,6 +795,14 @@ This example grants additional permissions to the role `testRole` to: language="java" /> + + + ### Remove permissions from a role @@ -719,12 +824,12 @@ This example removes the following permissions from the role `testRole`: /> - + + + + ### Check if a role exists @@ -797,6 +910,14 @@ Check if the role `testRole` exists: language="java" /> + + + ### Inspect a role @@ -844,6 +965,14 @@ View the permissions assigned to a role. language="java" /> + + + ### List all roles @@ -891,6 +1020,14 @@ View all roles in the system and their permissions. language="java" /> + + + ### List users with a role @@ -907,12 +1044,12 @@ List all users who have the role `testRole`. /> - + + + + ### Delete a role @@ -954,12 +1099,12 @@ Deleting a role will remove it from the system, and revoke the associated permis /> - + + + + ## User management {#user-management} diff --git a/docs/weaviate/configuration/rbac/manage-users.mdx b/docs/weaviate/configuration/rbac/manage-users.mdx index cb3995690..7deabecb9 100644 --- a/docs/weaviate/configuration/rbac/manage-users.mdx +++ b/docs/weaviate/configuration/rbac/manage-users.mdx @@ -24,6 +24,7 @@ import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/w import OidcUserJavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/rbac-oidc-users.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/configure/rbac.users_test.go"; import OidcUserGoCode from "!!raw-loader!/_includes/code/howto/go/docs/configure/rbac.oidc.users_test.go"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/RBACTest.cs"; :::info Added in `v1.29` and `v1.30` Role-based access control (RBAC) is generally available in Weaviate from version `v1.29`. @@ -93,6 +94,14 @@ This example shows how to get a list of all the users (`db_user`, `db_env_user` language="java" /> + + +
@@ -152,6 +161,14 @@ This example creates a user called `custom-user`. language="java" /> + + +
@@ -208,6 +225,14 @@ This example deletes a user called `custom-user`. language="java" /> + + + ### Rotate database user API key {#rotate-user-api-key} @@ -255,6 +280,14 @@ This example updates (rotates) the API key for `custom-user`. language="java" /> + + +
@@ -315,6 +348,14 @@ This example assigns the custom `testRole` role and predefined `viewer` role to language="java" /> + + + ### Remove a role from a database user @@ -364,6 +405,14 @@ This example removes the role `testRole` from the user `custom-user`. language="java" /> + + + ### Get a database user's roles @@ -411,6 +460,14 @@ Retrieve the role information for any user. language="java" /> + + +
@@ -474,6 +531,14 @@ This example assigns the custom `testRole` role and predefined `viewer` role to language="java" /> + + + ### Remove a role from an OIDC user @@ -523,6 +588,14 @@ This example removes the role `testRole` from the user `custom-user`. language="java" /> + + + ### Get an OIDC user's roles @@ -570,6 +643,14 @@ Retrieve the role information for an OIDC user. language="java" /> + + +
diff --git a/docs/weaviate/connections/connect-cloud.mdx b/docs/weaviate/connections/connect-cloud.mdx index 268b6bf13..ce7c5cfdc 100644 --- a/docs/weaviate/connections/connect-cloud.mdx +++ b/docs/weaviate/connections/connect-cloud.mdx @@ -104,7 +104,7 @@ import HostnameWarning from "/_includes/wcs/hostname-warning.mdx"; /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + ## Read a single collection definition @@ -321,7 +329,7 @@ Retrieve a collection definition from the schema. language="java" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -112,12 +112,12 @@ Migrate: language="javaraw" /> - + @@ -153,12 +153,12 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col language="javaraw" /> - + @@ -192,12 +192,12 @@ Add tenants at the target instance before adding data objects. language="javaraw" /> - + @@ -234,12 +234,12 @@ Migrate: language="javaraw" /> - + @@ -275,12 +275,12 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col language="javaraw" /> - + @@ -317,12 +317,12 @@ Migrate: language="javaraw" /> - + @@ -358,12 +358,12 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col language="javaraw" /> - + @@ -397,12 +397,12 @@ Add tenants at the target instance before adding data objects. language="javaraw" /> - + @@ -439,12 +439,12 @@ Migrate: language="javaraw" /> - + diff --git a/docs/weaviate/manage-collections/multi-node-setup.mdx b/docs/weaviate/manage-collections/multi-node-setup.mdx index 766089808..84d07c945 100644 --- a/docs/weaviate/manage-collections/multi-node-setup.mdx +++ b/docs/weaviate/manage-collections/multi-node-setup.mdx @@ -66,7 +66,7 @@ Configure replication settings, such as [async replication](/deploy/configuratio language="java" /> - + - + - + - + - + - + - + - + - + - + + + + :::info Learn more @@ -548,6 +556,14 @@ Multi-tenancy collections require tenant name (e.g. `tenantA`) with each CRUD op language="java" /> + + + ## Search queries @@ -595,6 +611,14 @@ Multi-tenancy collections require the tenant name (e.g. `tenantA`) with each `Ge language="java" /> + + + ## Cross-references @@ -651,6 +675,14 @@ Multi-tenancy collections require the tenant name (e.g. `tenantA`) when creating language="java" /> + + + ## Backups diff --git a/docs/weaviate/manage-collections/tenant-states.mdx b/docs/weaviate/manage-collections/tenant-states.mdx index 68cc6e9d0..2fd8cbc53 100644 --- a/docs/weaviate/manage-collections/tenant-states.mdx +++ b/docs/weaviate/manage-collections/tenant-states.mdx @@ -10,7 +10,7 @@ import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBl import PyCode from '!!raw-loader!/_includes/code/howto/manage-data.multi-tenancy.py'; import TSCode from '!!raw-loader!/_includes/code/howto/manage-data.multi-tenancy.ts'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsMultiTenancyTest.java"; -import CSharpCode from "!!raw-loader!/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsMultiTenancyTest.cs"; ![Storage Tiers](./img/storage-tiers.jpg) @@ -122,7 +122,7 @@ To activate an `INACTIVE` tenant from disk, or to onload and activate an `OFFLOA language="java" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + The C# uses gRPC by default. @@ -296,7 +296,7 @@ Weaviate generates an UUID for each object. Object IDs must be unique. If you se language="java" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + Coming soon - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - -```ts -// Java support coming soon -``` - + + @@ -128,6 +130,14 @@ Any vector-based search on collections with [named vectors](../config-refs/colle language="java" /> + + + + + + - - -```java -// Java support coming soon -``` - + + @@ -335,6 +354,14 @@ Grouped task search returns one response that includes all of the query results. language="java" /> + + + + + + - - -```java -// Java support coming soon -``` - + + @@ -511,12 +547,13 @@ The following fields are available for generative search with images: language="java" /> - - -```java -// Java support coming soon -``` - + + diff --git a/docs/weaviate/search/hybrid.md b/docs/weaviate/search/hybrid.md index a94e57391..68f778bb7 100644 --- a/docs/weaviate/search/hybrid.md +++ b/docs/weaviate/search/hybrid.md @@ -61,7 +61,7 @@ Combine the results of a vector search and a keyword search. The search uses a s language="java" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + ## Specify query vectors @@ -115,6 +124,14 @@ You can specify multiple query vectors in the search query with a `nearVector` s language="java" /> + + + You can also specify the query vectors as an array of vectors. The array will be parsed according to the order of the specified target vectors. @@ -157,6 +174,14 @@ The target vectors can be specified as an array as shown here. language="java" /> + + + #### Target vectors and weights @@ -188,6 +213,14 @@ If you want to provide weights for each target vector you can do it as shown her language="java" /> + + + ## Specify target vector names and join strategy @@ -221,6 +254,14 @@ The `sum`, `average`, `minimum` join strategies only require the name of the str language="java" /> + + + ## Weight raw vector distances @@ -259,6 +300,14 @@ Each distance between the query vector and the target vector is multiplied by th language="java" /> + + + ## Weight normalized vector distances @@ -299,6 +348,14 @@ For a more detailed explanation of how scores are normalized, see the blog post language="java" /> + + + ## Related pages diff --git a/docs/weaviate/search/rerank.md b/docs/weaviate/search/rerank.md index 6b33a29b0..bd81aeedb 100644 --- a/docs/weaviate/search/rerank.md +++ b/docs/weaviate/search/rerank.md @@ -16,8 +16,6 @@ import SimilarityPyCodeV3 from '!!raw-loader!/_includes/code/howto/search.simila import SimilarityTSCode from '!!raw-loader!/_includes/code/howto/search.similarity.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/mainpkg/search-rerank_test.go'; - - Reranking modules reorder the search result set according to a different set of criteria or a different (e.g. more expensive) algorithm.
diff --git a/docs/weaviate/search/similarity.md b/docs/weaviate/search/similarity.md index 0a57c7b50..522ac55e7 100644 --- a/docs/weaviate/search/similarity.md +++ b/docs/weaviate/search/similarity.md @@ -75,7 +75,7 @@ Use the [`Near Text`](../api/graphql/search-operators.md#neartext) operator to f language="graphql" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +