diff --git a/.gitignore b/.gitignore index a7e0e36b1..94602be1e 100644 --- a/.gitignore +++ b/.gitignore @@ -267,4 +267,5 @@ _includes/code/csharp/bin _includes/code/csharp/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/csharp/AssemblyInfo.cs b/_includes/code/csharp/AssemblyInfo.cs new file mode 100644 index 000000000..17fe8b21b --- /dev/null +++ b/_includes/code/csharp/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using Xunit; + +// This forces all tests in this assembly to run sequentially +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] \ No newline at end of file diff --git a/_includes/code/csharp/BackupsTest.cs b/_includes/code/csharp/BackupsTest.cs new file mode 100644 index 000000000..6dca03323 --- /dev/null +++ b/_includes/code/csharp/BackupsTest.cs @@ -0,0 +1,158 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Threading; + +// Run sequentially to prevent backup conflicts on the filesystem backend +[Collection("Sequential")] +public class BackupsTest : IAsyncLifetime +{ + private WeaviateClient client; + private readonly BackupBackend _backend = new FilesystemBackend(); + + public async Task InitializeAsync() + { + client = await Connect.Local( + restPort: 8580, + grpcPort: 50551, + credentials: "root-user-key" + ); + + // Ensure a clean state + await CleanupCollections(); + } + + public Task DisposeAsync() + { + // The C# client manages connections automatically. + return Task.CompletedTask; + } + + // Helper method to set up collections for tests + private async Task SetupCollections() + { + await CleanupCollections(); + + await client.Collections.Create(new CollectionConfig + { + Name = "Article", + Properties = [Property.Text("title")] + }); + + await client.Collections.Create(new CollectionConfig + { + Name = "Publication", + Properties = [Property.Text("title")] + }); + + await client.Collections.Use("Article").Data.Insert(new { title = "Dummy" }); + await client.Collections.Use("Publication").Data.Insert(new { title = "Dummy" }); + } + + private async Task CleanupCollections() + { + if (await client.Collections.Exists("Article")) await client.Collections.Delete("Article"); + if (await client.Collections.Exists("Publication")) await client.Collections.Delete("Publication"); + } + + [Fact] + public async Task TestBackupAndRestoreLifecycle() + { + await SetupCollections(); + string backupId = "my-very-first-backup"; + + // START CreateBackup + var createResult = await client.Backups.CreateSync( + new BackupCreateRequest( + Id: backupId, + Backend: _backend, + Include: ["Article", "Publication"] + ) + ); + + Console.WriteLine($"Status: {createResult.Status}"); + // END CreateBackup + + Assert.Equal(BackupStatus.Success, createResult.Status); + + // START StatusCreateBackup + var createStatus = await client.Backups.GetStatus(_backend, backupId); + + Console.WriteLine($"Backup ID: {createStatus.Id}, Status: {createStatus.Status}"); + // END StatusCreateBackup + + Assert.Equal(BackupStatus.Success, createStatus.Status); + + // Delete all classes before restoring + await client.Collections.DeleteAll(); + Assert.False(await client.Collections.Exists("Article")); + Assert.False(await client.Collections.Exists("Publication")); + + // START RestoreBackup + var restoreResult = await client.Backups.RestoreSync( + new BackupRestoreRequest( + Id: backupId, + Backend: _backend, + Exclude: ["Article"] // Exclude Article from restoration + ) + ); + + Console.WriteLine($"Restore Status: {restoreResult.Status}"); + // END RestoreBackup + + Assert.Equal(BackupStatus.Success, restoreResult.Status); + + // Verify that Publication was restored and Article was excluded + Assert.True(await client.Collections.Exists("Publication")); + Assert.False(await client.Collections.Exists("Article")); + + // START StatusRestoreBackup + // Note: In C#, restore status is often tracked via the returned operation or by polling if async. + // GetRestoreStatus checks the status of a specific restore job. + // Since we ran RestoreSync, we know it is done. + // We can inspect the result returned from RestoreSync directly. + Console.WriteLine($"Restore ID: {restoreResult.Id}, Status: {restoreResult.Status}"); + // END StatusRestoreBackup + + Assert.Equal(BackupStatus.Success, restoreResult.Status); + + // Clean up + await client.Collections.Delete("Publication"); + } + + [Fact] + public async Task TestCancelBackup() + { + await SetupCollections(); + string backupId = "some-unwanted-backup"; + + // Start a backup to cancel (Async, creates the operation but returns immediately) + CancellationToken cancellationToken = new CancellationToken(); + var backupOperation = await client.Backups.Create( + new BackupCreateRequest( + Id: backupId, + Backend: _backend, + Include: ["Article", "Publication"] + ), + cancellationToken + ); + + Console.WriteLine($"Backup started with ID: {backupOperation.Current.Id}"); + + // START CancelBackup + await backupOperation.Cancel(cancellationToken); + // END CancelBackup + + // Wait for the cancellation to be processed + var finalStatus = await backupOperation.WaitForCompletion(); + + // Verify status + Assert.Equal(BackupStatus.Canceled, finalStatus.Status); + + // Clean up + await client.Collections.Delete("Article"); + await client.Collections.Delete("Publication"); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ConfigureBQTest.cs b/_includes/code/csharp/ConfigureBQTest.cs index b977fc543..19af297ea 100644 --- a/_includes/code/csharp/ConfigureBQTest.cs +++ b/_includes/code/csharp/ConfigureBQTest.cs @@ -15,16 +15,8 @@ public class ConfigureBQTest : 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 - if (await client.Collections.Exists(COLLECTION_NAME)) - { - await client.Collections.Delete(COLLECTION_NAME); - } } // Runs after each test @@ -37,6 +29,7 @@ public Task DisposeAsync() [Fact] public async Task TestEnableBQ() { + await client.Collections.Delete(COLLECTION_NAME); // START EnableBQ await client.Collections.Create(new CollectionConfig { @@ -59,6 +52,7 @@ await client.Collections.Create(new CollectionConfig [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 @@ -80,6 +74,7 @@ await collection.Config.Update(c => [Fact] public async Task TestBQWithOptions() { + await client.Collections.Delete(COLLECTION_NAME); // START BQWithOptions await client.Collections.Create(new CollectionConfig { diff --git a/_includes/code/csharp/ConfigurePQTest.cs b/_includes/code/csharp/ConfigurePQTest.cs index dbf92002f..b860a3af4 100644 --- a/_includes/code/csharp/ConfigurePQTest.cs +++ b/_includes/code/csharp/ConfigurePQTest.cs @@ -32,9 +32,7 @@ public async Task InitializeAsync() // 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,8 +57,6 @@ 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() { diff --git a/_includes/code/csharp/ConfigureRQTest.cs b/_includes/code/csharp/ConfigureRQTest.cs index 9ee5cce79..746e4b050 100644 --- a/_includes/code/csharp/ConfigureRQTest.cs +++ b/_includes/code/csharp/ConfigureRQTest.cs @@ -1,166 +1,169 @@ -// using Xunit; -// using Weaviate.Client; -// using Weaviate.Client.Models; -// using System; -// using System.Threading.Tasks; +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; -// 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 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 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 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 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 -// } + // TODO[g-despot] NEW: Needs not compression option + [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 + 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 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 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 CollectionConfig + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig("default", new Vectorizer.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 CollectionConfig + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig("default", new Vectorizer.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 + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ConfigureSQTest.cs b/_includes/code/csharp/ConfigureSQTest.cs index caea2b196..d98f6d808 100644 --- a/_includes/code/csharp/ConfigureSQTest.cs +++ b/_includes/code/csharp/ConfigureSQTest.cs @@ -15,9 +15,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 @@ -77,7 +75,6 @@ await collection.Config.Update(c => // END UpdateSchema } - // TODO[g-despot] Missing cache [Fact] public async Task TestSQWithOptions() { @@ -95,7 +92,6 @@ await client.Collections.Create(new CollectionConfig VectorCacheMaxObjects = 100000, Quantizer = new VectorIndex.Quantizers.SQ { - //Cache = true, TrainingLimit = 50000, RescoreLimit = 200 } diff --git a/_includes/code/csharp/ConnectionTest.cs b/_includes/code/csharp/ConnectionTest.cs index d2ee291b7..4a4825ccb 100644 --- a/_includes/code/csharp/ConnectionTest.cs +++ b/_includes/code/csharp/ConnectionTest.cs @@ -12,27 +12,87 @@ 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 +102,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 +122,19 @@ 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 + 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 } - } - }; - WeaviateClient client = new WeaviateClient(config); + }) + .BuildAsync(); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -92,20 +151,19 @@ 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 + 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 } - } - }; - WeaviateClient client = new WeaviateClient(config); + }) + .BuildAsync(); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -116,7 +174,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 +188,9 @@ 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 +204,17 @@ 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 + WeaviateClient client = await WeaviateClientBuilder.Local( + hostname: "localhost", + restPort: 8080, + grpcPort: 50051, + useSsl: false + ) + .WithHeaders(new Dictionary { { "X-Cohere-Api-Key", cohereApiKey } - } - }; - WeaviateClient client = new WeaviateClient(config); + }) + .BuildAsync(); var isReady = await client.IsReady(); Console.WriteLine(isReady); @@ -172,7 +230,7 @@ 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 @@ -185,8 +243,4 @@ public async Task TestConnectWCDWithThirdPartyKeys() Console.WriteLine(isReady); // END ThirdPartyAPIKeys } - - // START TimeoutWCD - // Coming soon - // END TimeoutWCD } \ No newline at end of file diff --git a/_includes/code/csharp/GetStartedTest.cs b/_includes/code/csharp/GetStartedTest.cs index bdcf77949..b80125246 100644 --- a/_includes/code/csharp/GetStartedTest.cs +++ b/_includes/code/csharp/GetStartedTest.cs @@ -22,7 +22,7 @@ public class GetStartedTests [Fact] public async Task GetStarted() { - var client = Connect.Local(); + var client = await Connect.Local(); const string collectionName = "Question"; try @@ -87,7 +87,7 @@ public async Task CreateCollectionAndRunNearTextQuery() string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); // 1. Connect to Weaviate - var client = Connect.Cloud(weaviateUrl, weaviateApiKey); + var client = await Connect.Cloud(weaviateUrl, weaviateApiKey); // 2. Prepare data (same as Python data_objects) var dataObjects = new List diff --git a/_includes/code/csharp/ManageCollectionsAliasTest.cs b/_includes/code/csharp/ManageCollectionsAliasTest.cs index e69de29bb..ca9e12957 100644 --- a/_includes/code/csharp/ManageCollectionsAliasTest.cs +++ b/_includes/code/csharp/ManageCollectionsAliasTest.cs @@ -0,0 +1,278 @@ +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; + +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 CollectionConfig + { + Name = Articles, + VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()), + Properties = + [ + Property.Text("title"), + Property.Text("content"), + ] + }); + + // Create an alias pointing to the collection + var alias = new Alias(ArticlesAlias, Articles); + await client.Alias.Add(alias); + // 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.TargetClass}"); + } + // 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.TargetClass}"); + } + // END GetAlias + Assert.NotNull(aliasInfo); + Assert.Equal(Articles, aliasInfo.TargetClass); + + // START UpdateAlias + // Create a new collection for migration + await client.Collections.Create(new CollectionConfig + { + Name = ArticlesV2, + VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()), + 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 CollectionConfig + { + Name = Articles, + VectorConfig = new VectorConfig("default", new Vectorizer.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) + alias = new Alias(ArticlesAlias, Articles); + await client.Alias.Add(alias); + + // 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); + } + + // TODO[g-despot] It's strange that I have to cast into primitive types + [Fact] + public async Task TestZeroDowntimeMigration() + { + // START Step1CreateOriginal + // Create original collection with data + await client.Collections.Create(new CollectionConfig + { + Name = ProductsV1, + VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()), + }); + + 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 + var alias = new Alias(ProductsAlias, ProductsV1); + await client.Alias.Add(alias); + // 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 CollectionConfig + { + Name = ProductsV2, + VectorConfig = new VectorConfig("default", new Vectorizer.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)); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs b/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs index f251a618b..8b81d36cc 100644 --- a/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs +++ b/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs @@ -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 @@ -37,16 +34,16 @@ await client.Collections.Create(new CollectionConfig { Name = "JeopardyCategory", Description = "A Jeopardy! category", - Properties = [ Property.Text("title") ] + Properties = [Property.Text("title")] }); await client.Collections.Create(new CollectionConfig { Name = "JeopardyQuestion", Description = "A Jeopardy! question", - Properties = [ Property.Text("question"), Property.Text("answer") ], + Properties = [Property.Text("question"), Property.Text("answer")], // highlight-start - References = [ new Reference("hasCategory", "JeopardyCategory") ] + References = [new Reference("hasCategory", "JeopardyCategory")] // highlight-end }); // END CrossRefDefinition @@ -61,12 +58,12 @@ 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" }; // 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 @@ -86,8 +83,8 @@ 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 categoryObjId = await categories.Data.Insert(new { title = "U.S. CITIES" }); @@ -115,7 +112,7 @@ await client.Collections.Create(new CollectionConfig { Name = "JeopardyCategory", Description = "A Jeopardy! category", - Properties = [ Property.Text("title") ] + Properties = [Property.Text("title")] }); // END TwoWayCategory1CrossReferences @@ -124,9 +121,9 @@ await client.Collections.Create(new CollectionConfig { Name = "JeopardyQuestion", Description = "A Jeopardy! question", - Properties = [ Property.Text("question"), Property.Text("answer") ], + Properties = [Property.Text("question"), Property.Text("answer")], // highlight-start - References = [ new Reference("hasCategory", "JeopardyCategory") ] + References = [new Reference("hasCategory", "JeopardyCategory")] // highlight-end }); // END TwoWayQuestionCrossReferences @@ -140,7 +137,7 @@ await category.Config.AddProperty( ); // 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" }); @@ -167,8 +164,8 @@ 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 categoryObjId = await categories.Data.Insert(new { title = "U.S. CITIES" }); @@ -198,8 +195,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,13 +227,12 @@ 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( @@ -257,15 +253,20 @@ await questions.Data.ReferenceDelete( 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" }); @@ -296,15 +297,15 @@ await client.Collections.Create(new CollectionConfig { Name = "JeopardyCategory", Description = "A Jeopardy! category", - Properties = [ Property.Text("title") ] + 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") ] + 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..8df5302f9 100644 --- a/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs +++ b/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs @@ -15,71 +15,73 @@ 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 wineReview = clientSrc.Collections.Use("WineReview"); var wineReviewData = Enumerable.Range(0, DATASET_SIZE) - .Select(i => new { title = $"Review {i}" }) + .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.Add(["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, + // 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 { + // 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 + // 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 = + Properties = [ Property.Text("review_body"), Property.Text("title"), @@ -89,79 +91,58 @@ private static async Task> CreateCollection(WeaviateCli ] }); } - // END CreateCollectionCollectionToCollection - // END CreateCollectionCollectionToTenant - // END CreateCollectionTenantToCollection - // END CreateCollectionTenantToTenant + // END CreateCollectionCollectionToCollection // END CreateCollectionCollectionToTenant // END CreateCollectionTenantToCollection // END CreateCollectionTenantToTenant + // TODO[g-despot] NEW: Why can't I insert many with preserved IDs? - // START CollectionToCollection - // START TenantToCollection - // START CollectionToTenant - // START TenantToTenant - private async Task MigrateData(CollectionClient collectionSrc, - CollectionClient collectionTgt) + // 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 + // END CollectionToCollection // END TenantToCollection // END CollectionToTenant // END TenantToTenant - private async Task VerifyMigration(CollectionClient collectionSrc, - CollectionClient collectionTgt, int numSamples) + 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; } @@ -178,14 +159,17 @@ 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() @@ -200,15 +184,17 @@ 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() @@ -217,17 +203,15 @@ private async Task CreateCollectionToTenant() } // 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); } - // END CreateTenants - // END CreateCollectionTenantToTenant + // END CreateTenants // END CreateCollectionTenantToTenant [Fact] // START CollectionToTenant @@ -236,17 +220,18 @@ 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() @@ -262,15 +247,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 } + // END TenantToTenant } \ No newline at end of file diff --git a/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs b/_includes/code/csharp/ManageCollectionsMultiTenancyTest.cs similarity index 53% rename from _includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs rename to _includes/code/csharp/ManageCollectionsMultiTenancyTest.cs index a084f816e..93198fe90 100644 --- a/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs +++ b/_includes/code/csharp/ManageCollectionsMultiTenancyTest.cs @@ -4,7 +4,7 @@ using System; using System.Threading.Tasks; using System.Linq; -using System.Collections.Generic; +using System.Text.Json; namespace WeaviateProject.Tests; @@ -13,7 +13,7 @@ public class ManageCollectionsMultiTenancyTest : IAsyncLifetime private WeaviateClient client; // Runs before each test (like @BeforeEach) - public Task InitializeAsync() + public async Task InitializeAsync() { string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); if (string.IsNullOrWhiteSpace(openaiApiKey)) @@ -21,11 +21,7 @@ public Task InitializeAsync() 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; + client = await Connect.Local(); } // Runs after each test (like @AfterEach) @@ -116,8 +112,7 @@ await client.Collections.Create(new CollectionConfig // START AddTenantsToClass await collection.Tenants.Add( - new Tenant { Name = "tenantA" }, - new Tenant { Name = "tenantB" } + ["tenantA", "tenantB"] ); // END AddTenantsToClass @@ -136,7 +131,7 @@ public async Task TestListTenants() Name = collectionName, MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); + await collection.Tenants.Add(["tenantA", "tenantB"]); // START ListTenants var tenants = await collection.Tenants.List(); @@ -155,7 +150,7 @@ public async Task TestGetTenantsByName() Name = collectionName, MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); + await collection.Tenants.Add(["tenantA", "tenantB"]); // START GetTenantsByName var tenantNames = new[] { "tenantA", "tenantB", "nonExistentTenant" }; @@ -175,7 +170,7 @@ public async Task TestGetOneTenant() Name = collectionName, MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }); + await collection.Tenants.Add(["tenantA"]); // START GetOneTenant string tenantName = "tenantA"; @@ -195,14 +190,14 @@ public async Task TestActivateTenant() Name = collectionName, MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } }); - await collection.Tenants.Add(new Tenant { Name = "tenantA", Status = TenantActivityStatus.Inactive }); + await collection.Tenants.Add(["tenantA"]); // START ActivateTenants - string tenantName = "tenantA"; + string[] tenantName = ["tenantA"]; await collection.Tenants.Activate(tenantName); // END ActivateTenants - var tenant = await collection.Tenants.Get(tenantName); + var tenant = await collection.Tenants.Get(tenantName.First()); Assert.Equal(TenantActivityStatus.Active, tenant?.Status); } @@ -215,21 +210,35 @@ public async Task TestDeactivateTenant() Name = collectionName, MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }); + await collection.Tenants.Add(["tenantA"]); // START DeactivateTenants - string tenantName = "tenantA"; + string[] tenantName = ["tenantA"]; await collection.Tenants.Deactivate(tenantName); // END DeactivateTenants - var tenant = await collection.Tenants.Get(tenantName); + var tenant = await collection.Tenants.Get(tenantName.First()); 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(Skip = "Requires offload-s3 module to be enabled")] + public async Task TestOffloadTenants() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create(new CollectionConfig + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + await collection.Tenants.Add(["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() @@ -240,8 +249,7 @@ public async Task TestRemoveTenants() Name = collectionName, MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } }); - await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); - + await collection.Tenants.Add(["tenantA", "tenantB"]); // START RemoveTenants await collection.Tenants.Delete(new[] { "tenantB", "tenantX" }); // END RemoveTenants @@ -250,4 +258,149 @@ public async Task TestRemoveTenants() Assert.Single(tenants); Assert.Equal("tenantA", tenants.First().Name); } + + [Fact] + public async Task TestChangeTenantState() + { + string collectionName = "MultiTenancyCollection"; + await client.Collections.Create(new CollectionConfig + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } + }); + + var collection = client.Collections.Use(collectionName); + await collection.Tenants.Add(["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 CollectionConfig + { + Name = "JeopardyQuestion", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + + var collection = client.Collections.Use("JeopardyQuestion"); + await collection.Tenants.Add(["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 CollectionConfig + { + Name = "JeopardyQuestion", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + + var jeopardyCollection = client.Collections.Use("JeopardyQuestion"); + await jeopardyCollection.Tenants.Add(["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 CollectionConfig + { + 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.Add(["tenantA"]); + // Add the cross-reference property to the multi-tenancy class + await multiCollection.Config.AddProperty( + 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); + } } \ No newline at end of file diff --git a/_includes/code/csharp/ManageCollectionsTest.cs b/_includes/code/csharp/ManageCollectionsTest.cs index f2cb3f43e..1785c85b4 100644 --- a/_includes/code/csharp/ManageCollectionsTest.cs +++ b/_includes/code/csharp/ManageCollectionsTest.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; +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,8 @@ 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. @@ -93,7 +87,7 @@ public async Task TestCreateCollectionWithPropertiesFromClass() // public string Title { get; set; } // public string Body { get; set; } // } - + await client.Collections.Create(new CollectionConfig { Name = "Article", @@ -119,7 +113,7 @@ await client.Collections.Create(new CollectionConfig }); // START AddProperty - CollectionClient articles = client.Collections.Use("Article"); + CollectionClient articles = client.Collections.Use("Article"); await articles.Config.AddProperty(Property.Text("description")); // END AddProperty @@ -148,7 +142,7 @@ await client.Collections.Create(new CollectionConfig 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] @@ -203,7 +197,7 @@ await client.Collections.Create(new CollectionConfig Property.Text("body"), ] }); - var articles = client.Collections.Use("Article"); + var articles = client.Collections.Use("Article"); // START AddNamedVectors await articles.Config.AddVector(Configure.Vectors.Text2VecCohere().New("body_vector", sourceProperties: "body")); // END AddNamedVectors @@ -213,11 +207,7 @@ 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"}]} + // TODO[g-despot] NEW: Unexpected status code UnprocessableEntity. Expected: OK. collection create. Server replied: {"error":[{"message":"module 'multi2vec-jinaai': textFields or imageFields setting needs to be present"}]} [Fact] public async Task CreateCollectionWithMultiVectors() { @@ -227,12 +217,12 @@ 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 + // Example 1 - Use a model integration + Configure.MultiVectors.Text2MultiVecJinaAI().New("jina_colbert"), + // Example 2 - User-provided multi-vector representations Configure.MultiVectors.SelfProvided().New("custom_multi_vector"), }, - Properties = [ Property.Text("text") ], + Properties = [Property.Text("text")], }); // END MultiValueVectorCollection @@ -242,9 +232,32 @@ 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 CollectionConfig + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + // Example 1 - Use a model integration + Configure.MultiVectors.Text2MultiVecJinaAI().New("jina_colbert", indexConfig: new VectorIndex.HNSW + { + MultiVector = new MultiVectorConfig { Encoding = new MuveraEncoding() } + }), + // Example 2 - User-provided multi-vector representations + Configure.MultiVectors.SelfProvided( + ).New("custom_multi_vector", indexConfig: new VectorIndex.HNSW + { + MultiVector = new MultiVectorConfig { Encoding = new MuveraEncoding() } + }), + } + }); + // END MultiValueVectorMuvera + + Assert.True(await client.Collections.Exists("DemoCollection")); + } [Fact] public async Task TestSetVectorIndexType() @@ -362,7 +375,7 @@ public async Task TestUpdateReranker() await client.Collections.Create(new CollectionConfig { 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(); @@ -404,7 +417,7 @@ public async Task TestUpdateGenerative() await client.Collections.Create(new CollectionConfig { 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(); @@ -428,7 +441,7 @@ public async Task TestCreateCollectionWithPropertyConfig() await client.Collections.Create(new CollectionConfig { Name = "Article", - Properties = + Properties = [ Property.Text( "title", @@ -603,7 +616,7 @@ public async Task TestReadOneCollection() await client.Collections.Create(new CollectionConfig { 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); @@ -628,7 +641,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"); } @@ -646,7 +658,7 @@ await client.Collections.Create(new CollectionConfig }); // START UpdateCollection - var articles = client.Collections.Use("Article"); + var articles = client.Collections.Use("Article"); await articles.Config.Update(c => { @@ -679,7 +691,7 @@ public async Task TestDeleteCollection() // { // await client.Collections.Create(new CollectionConfig { 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))); @@ -695,10 +707,10 @@ public async Task TestDeleteCollection() // public async Task TestUpdateCollectionShards() // { // await client.Collections.Create(new CollectionConfig { Name = "Article" }); - // var initialShards = await client.Collections.Use("Article").Shards.Get(); + // 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))); diff --git a/_includes/code/csharp/ManageObjectsCreateTest.cs b/_includes/code/csharp/ManageObjectsCreateTest.cs index b19ef944f..92160783e 100644 --- a/_includes/code/csharp/ManageObjectsCreateTest.cs +++ b/_includes/code/csharp/ManageObjectsCreateTest.cs @@ -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 } @@ -124,7 +124,7 @@ 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 @@ -148,7 +148,7 @@ 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 { @@ -171,7 +171,7 @@ 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 { @@ -193,7 +193,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); @@ -217,7 +217,7 @@ public async Task TestCreateObjectWithDeterministicId() }; 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 @@ -234,7 +234,7 @@ 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 { @@ -259,7 +259,7 @@ public async Task TestCreateObjectWithId() 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 @@ -282,7 +282,7 @@ 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, diff --git a/_includes/code/csharp/ManageObjectsDeleteTest.cs b/_includes/code/csharp/ManageObjectsDeleteTest.cs index 9b0c04aaa..6b8175437 100644 --- a/_includes/code/csharp/ManageObjectsDeleteTest.cs +++ b/_includes/code/csharp/ManageObjectsDeleteTest.cs @@ -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) @@ -47,7 +45,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,7 +59,7 @@ public async Task TestDeleteObject() [Fact] public async Task TestBatchDelete() { - var collection = client.Collections.Use(COLLECTION_NAME); + var collection = client.Collections.Use(COLLECTION_NAME); var objects = Enumerable.Range(0, 5) .Select(i => new { name = $"EphemeralObject_{i}" }) .ToArray(); // Creates an array T[] @@ -86,7 +84,7 @@ await collection.Data.DeleteMany( public async Task TestDeleteContains() { // START DeleteContains - var collection = client.Collections.Use(COLLECTION_NAME); + var collection = client.Collections.Use(COLLECTION_NAME); await collection.Data.InsertMany(new[] { new { name = "asia" }, @@ -104,7 +102,7 @@ await collection.Data.DeleteMany( [Fact] public async Task TestDryRun() { - var collection = client.Collections.Use(COLLECTION_NAME); + var collection = client.Collections.Use(COLLECTION_NAME); var objects = Enumerable.Range(0, 5) .Select(i => new { name = $"EphemeralObject_{i}" }) .ToArray(); // Creates an array T[] @@ -130,7 +128,7 @@ public async Task TestDryRun() [Fact] public async Task TestBatchDeleteWithIDs() { - var collection = client.Collections.Use(COLLECTION_NAME); + var collection = client.Collections.Use(COLLECTION_NAME); var objects = Enumerable.Range(0, 5) .Select(i => new { name = $"EphemeralObject_{i}" }) .ToArray(); // Creates an array T[] diff --git a/_includes/code/csharp/ManageObjectsImportTest.cs b/_includes/code/csharp/ManageObjectsImportTest.cs index ce91d3028..81f9c1b07 100644 --- a/_includes/code/csharp/ManageObjectsImportTest.cs +++ b/_includes/code/csharp/ManageObjectsImportTest.cs @@ -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 } @@ -102,7 +100,7 @@ await client.Collections.Create(new CollectionConfig // START BasicBatchImportExample 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. @@ -146,7 +144,7 @@ await client.Collections.Create(new CollectionConfig dataToInsert.Add((dataRow, objUuid)); } - var collection = client.Collections.Use("MyCollection"); + var collection = client.Collections.Use("MyCollection"); // highlight-start var response = await collection.Data.InsertMany(dataToInsert); @@ -177,27 +175,39 @@ await client.Collections.Create(new CollectionConfig }); // 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)); + + // 1. Use 'Vector' instead of 'VectorData' + // 2. Use dictionary initializer syntax for 'Vectors' + var vectors = new Vectors + { + { "default", vectorData } + }; + + // 3. Pass arguments to the constructor (Data is required) + // Signature: BatchInsertRequest(TData data, Guid? id = null, Vectors? vectors = null, ...) + dataToInsert.Add(new BatchInsertRequest( + dataRow, + objUuid, + 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 (response.Errors is a Dictionary) + if (response.Errors.Any()) { - 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().Data}"); } // END BatchImportWithVectorExample @@ -217,16 +227,16 @@ await client.Collections.Create(new CollectionConfig 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) { @@ -249,43 +259,48 @@ await client.Collections.Create(new CollectionConfig Name = "MyCollection", VectorConfig = new[] { - new VectorConfig("title", new Vectorizer.SelfProvided()), - new VectorConfig("body", new Vectorizer.SelfProvided()) - }, + new VectorConfig("title", new Vectorizer.SelfProvided()), + new VectorConfig("body", new Vectorizer.SelfProvided()) + }, Properties = [Property.Text("title"), Property.Text("body")] }); // START BatchImportWithNamedVectors - // Prepare the data and vectors - var dataToInsert = new List<(object properties, Dictionary vectors)>(); + // 1. Change list type to BatchInsertRequest + 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)); + // 2. Create the Vectors object mapping names to Vector wrappers + var namedVectors = new Vectors + { + { "title", titleVector }, + { "body", bodyVector } + }; + + // 3. Add a specific request object to the list + // Constructor signature: (Data, ID?, Vectors?, References?, Tenant?) + dataToInsert.Add(new BatchInsertRequest(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()) + // Check for errors (Access the Errors dictionary) + if (response.Errors.Any()) { - 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().Data}"); } // END BatchImportWithNamedVectors } @@ -294,21 +309,36 @@ 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 CollectionConfig + { + 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) @@ -359,7 +389,7 @@ public async Task TestCsvStreaming() // 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)) diff --git a/_includes/code/csharp/ManageObjectsReadAllTest.cs b/_includes/code/csharp/ManageObjectsReadAllTest.cs index 784bc1752..385e62640 100644 --- a/_includes/code/csharp/ManageObjectsReadAllTest.cs +++ b/_includes/code/csharp/ManageObjectsReadAllTest.cs @@ -5,24 +5,18 @@ using System.Threading.Tasks; using System.Collections.Generic; using System.Text.Json; +using System.Drawing; 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")) @@ -65,7 +59,7 @@ 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()) @@ -80,11 +74,11 @@ 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 + includeVectors: true // If using named vectors, you can specify ones to include )) // highlight-end { @@ -96,12 +90,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 diff --git a/_includes/code/csharp/ManageObjectsReadTest.cs b/_includes/code/csharp/ManageObjectsReadTest.cs index 5ed97582e..80da62ef1 100644 --- a/_includes/code/csharp/ManageObjectsReadTest.cs +++ b/_includes/code/csharp/ManageObjectsReadTest.cs @@ -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,7 +38,7 @@ 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")); @@ -55,11 +55,11 @@ 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"), // highlight-start - returnMetadata: MetadataOptions.Vector + includeVectors: true ); // highlight-end @@ -75,7 +75,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); @@ -89,7 +89,7 @@ public async Task TestReadObjectNamedVectors() // START ReadObjectNamedVectors var dataObject = await reviews.Query.FetchObjectByID(objUuid.Value, // Object UUID // highlight-start - returnMetadata: MetadataOptions.Vector // Specify to include vectors + includeVectors: true // Specify to include vectors ); // highlight-end @@ -111,7 +111,7 @@ 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")); diff --git a/_includes/code/csharp/ManageObjectsUpdateTest.cs b/_includes/code/csharp/ManageObjectsUpdateTest.cs index d92e6452b..711eaec77 100644 --- a/_includes/code/csharp/ManageObjectsUpdateTest.cs +++ b/_includes/code/csharp/ManageObjectsUpdateTest.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; -using System.Text.Json; 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 } @@ -53,7 +50,7 @@ await client.Collections.Create(new CollectionConfig // highlight-start // ===== Add three mock objects to the WineReviewNV collection ===== - var reviews = client.Collections.Use("WineReviewNV"); + var reviews = client.Collections.Use("WineReviewNV"); await reviews.Data.InsertMany(new[] { new { title = "Mock Wine A", review_body = "A fine mock vintage.", country = "Mocktugal" }, @@ -93,7 +90,7 @@ public async Task DisposeAsync() 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); @@ -116,7 +113,7 @@ private static async Task DelProps(WeaviateClient client, Guid uuidToUpdate, str [Fact] public async Task TestUpdateAndReplaceFlow() { - var jeopardy = client.Collections.Use("JeopardyQuestion"); + var jeopardy = client.Collections.Use("JeopardyQuestion"); var uuid = await jeopardy.Data.Insert(new { @@ -139,53 +136,51 @@ await jeopardy.Data.Replace(uuid, Assert.Equal(100d, props1["points"]); - var vector = Enumerable.Repeat(0.12345f, 300).ToArray(); + var vector = Enumerable.Repeat(0.12345f, 384).ToArray(); - // TODO[g-despot] Not implemented // START UpdateVector - // Coming soon + await jeopardy.Data.Replace(uuid, + data: 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); - // TODO[g-despot] Not implemented // START UpdateNamedVector - // Coming soon - // END UpdateNamedVector + var reviews = client.Collections.Use("WineReviewNV"); + + // Fetch an object to update + var result = await reviews.Query.FetchObjects(limit: 3); + var reviewUuid = result.Objects.First().ID.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(); - 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 - // ); + await reviews.Data.Replace( + id: reviewUuid, + data: 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 diff --git a/_includes/code/csharp/ModelProvidersTest.cs b/_includes/code/csharp/ModelProvidersTest.cs index e69de29bb..938465337 100644 --- a/_includes/code/csharp/ModelProvidersTest.cs +++ b/_includes/code/csharp/ModelProvidersTest.cs @@ -0,0 +1,242 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Linq; + +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 + ); + + // Verify connection (GetMeta is a quick way to check connectivity) + // Note: C# client constructor doesn't block, so we make a call to verify. + 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 CollectionConfig + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + Configure.Vectors.Text2VecWeaviate().New("title_vector", 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 CollectionConfig + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + Configure.Vectors.Text2VecWeaviate( + model: "Snowflake/snowflake-arctic-embed-l-v2.0" + ).New("title_vector", 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 CollectionConfig + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + Configure.Vectors.Text2VecWeaviate( + model: "Snowflake/snowflake-arctic-embed-m-v1.5" + // baseURL: null, + // dimensions: 0 + ).New("title_vector", 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/Program.cs b/_includes/code/csharp/Program.cs new file mode 100644 index 000000000..ffbe8f0ad --- /dev/null +++ b/_includes/code/csharp/Program.cs @@ -0,0 +1,46 @@ + +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/QuickstartLocalTest.cs b/_includes/code/csharp/QuickstartLocalTest.cs index b90729c78..95877e1fd 100644 --- a/_includes/code/csharp/QuickstartLocalTest.cs +++ b/_includes/code/csharp/QuickstartLocalTest.cs @@ -17,7 +17,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 +32,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 @@ -46,7 +46,7 @@ public async Task FullQuickstartWorkflowTest() var questions = await client.Collections.Create(new CollectionConfig { Name = collectionName, - Properties = + Properties = [ Property.Text("answer"), Property.Text("question"), @@ -83,17 +83,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 +104,22 @@ public async Task FullQuickstartWorkflowTest() Console.WriteLine(JsonSerializer.Serialize(obj.Properties)); } // END NearText - } - // START RAG - // Coming soon - // END RAG + // 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 Weaviate.Client.Models.Generative.Providers.OpenAI { } + } + ); + // highlight-end + + // Inspect the results + Console.WriteLine(JsonSerializer.Serialize(ragResponse.Generative.Values)); + // END RAG + await client.Collections.Delete(collectionName); + } } \ No newline at end of file diff --git a/_includes/code/csharp/QuickstartTest.cs b/_includes/code/csharp/QuickstartTest.cs index e062f1e69..4ed26bffc 100644 --- a/_includes/code/csharp/QuickstartTest.cs +++ b/_includes/code/csharp/QuickstartTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Net.Http; using Xunit; +using System.Linq; namespace WeaviateProject.Examples; @@ -21,7 +22,7 @@ public static async Task TestConnectionIsReady() string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); - WeaviateClient client = Connect.Cloud( + WeaviateClient client = await Connect.Cloud( weaviateUrl, weaviateApiKey ); @@ -40,11 +41,16 @@ 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)) { @@ -55,7 +61,7 @@ public static async Task FullQuickstartWorkflowTest() var questions = await client.Collections.Create(new CollectionConfig { Name = collectionName, - Properties = + Properties = [ Property.Text("answer"), Property.Text("question"), @@ -92,17 +98,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 +120,23 @@ 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 Weaviate.Client.Models.Generative.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..45b80355e --- /dev/null +++ b/_includes/code/csharp/RBACTest.cs @@ -0,0 +1,488 @@ +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*", null, null) // 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*", null, null) { 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*", null, null) { 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*", null, null) + { + 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*", null, null) { 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*", null, null) + { + 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); + } +} \ No newline at end of file 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..37205fff8 --- /dev/null +++ b/_includes/code/csharp/ReplicationTest.cs @@ -0,0 +1,174 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Text.Json; + +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 CollectionConfig + { + 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 + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchAggregateTest.cs b/_includes/code/csharp/SearchAggregateTest.cs index 5f4fe4acc..8d88015a4 100644 --- a/_includes/code/csharp/SearchAggregateTest.cs +++ b/_includes/code/csharp/SearchAggregateTest.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using System.Text.Json; +using System.Collections.Generic; 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"); + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); client = Connect.Cloud( weaviateUrl, - weaviateApiKey - ); + weaviateApiKey, + headers: new Dictionary() + { + { "X-OpenAI-Api-Key", openaiApiKey } + } + ).GetAwaiter().GetResult(); // END INSTANTIATION-COMMON } @@ -55,12 +61,12 @@ public async Task TestTextProp() var jeopardy = client.Collections.Use("JeopardyQuestion"); var response = await jeopardy.Aggregate.OverAll( // highlight-start - metrics: Metrics.ForProperty("answer") + metrics: [Metrics.ForProperty("answer") .Text( topOccurrencesCount: true, topOccurrencesValue: true, minOccurrences: 5 // Corresponds to topOccurrencesCutoff - ) + )] // highlight-end ); @@ -80,12 +86,12 @@ public async Task TestIntProp() var response = await jeopardy.Aggregate.OverAll( // highlight-start // Use .Number for floats (NUMBER datatype in Weaviate) - metrics: Metrics.ForProperty("points") + metrics: [Metrics.ForProperty("points") .Integer( sum: true, maximum: true, minimum: true - ) + )] // highlight-end ); @@ -129,7 +135,7 @@ public async Task TestNearTextWithLimit() // highlight-start limit: 10, // highlight-end - metrics: Metrics.ForProperty("points").Number(sum: true) + metrics: [Metrics.ForProperty("points").Number(sum: true)] ); var pointsMetrics = response.Properties["points"] as Aggregate.Number; @@ -148,7 +154,7 @@ public async Task TestHybrid() // highlight-start objectLimit: 10, // highlight-end - metrics: Metrics.ForProperty("points").Number(sum: true) + metrics: [Metrics.ForProperty("points").Number(sum: true)] ); var pointsMetrics = response.Properties["points"] as Aggregate.Number; @@ -167,7 +173,7 @@ public async Task TestNearTextWithDistance() // highlight-start distance: 0.19, // highlight-end - metrics: Metrics.ForProperty("points").Number(sum: true) + metrics: [Metrics.ForProperty("points").Number(sum: true)] ); var pointsMetrics = response.Properties["points"] as Aggregate.Number; diff --git a/_includes/code/csharp/SearchBasicTest.cs b/_includes/code/csharp/SearchBasicTest.cs index f62bb902d..cdaa5393d 100644 --- a/_includes/code/csharp/SearchBasicTest.cs +++ b/_includes/code/csharp/SearchBasicTest.cs @@ -3,7 +3,6 @@ using Weaviate.Client.Models; using System; using System.Threading.Tasks; -using System.Collections.Generic; using System.Text.Json; using System.Linq; @@ -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,19 @@ 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( + client = await Connect.Cloud( weaviateUrl, weaviateApiKey ); - - return Task.CompletedTask; } + // 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,7 +61,7 @@ public async Task BasicGet() Assert.True(response.Objects.First().Properties.ContainsKey("question")); } - // TODO[g-despot]: Enable when C# client supports offset + // TODO[g-despot]: NEW: Enable when C# client supports offset // [Fact] // public async Task BasicGetOffset() // { @@ -98,7 +96,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,7 +122,7 @@ 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, @@ -153,23 +151,25 @@ 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 +180,18 @@ public async Task GetObjectId() // ================================== // START GetObjectId - var jeopardy = client.Collections.Use>("JeopardyQuestion"); + 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().ID; + var response = await jeopardy.Query.FetchObjectByID( - Guid.Parse("36ddd591-2dee-4e7e-a3cc-eb86d30a4303") + (Guid)idToFetch ); Console.WriteLine(response); // END GetObjectId + Assert.NotNull(response); } [Fact] @@ -196,7 +201,7 @@ 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: [ @@ -211,18 +216,27 @@ public async Task GetWithCrossRefs() 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 +247,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 @@ -260,7 +274,7 @@ public async Task MultiTenancyGet() // ========================= // START MultiTenancy - var mtCollection = client.Collections.Use>("WineReviewMT"); + var mtCollection = client.Collections.Use("WineReviewMT"); // In the C# client, the tenant is specified directly in the query method // rather than creating a separate tenant-specific collection object. @@ -272,11 +286,12 @@ public async Task MultiTenancyGet() 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,10 +301,17 @@ 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().ID; + // START QueryWithReplication - var jeopardy = client.Collections.Use>("JeopardyQuestion").WithConsistencyLevel(ConsistencyLevels.Quorum); + var jeopardy = client.Collections.Use("JeopardyQuestion").WithConsistencyLevel(ConsistencyLevels.Quorum); + var response = await jeopardy.Query.FetchObjectByID( - Guid.Parse("36ddd591-2dee-4e7e-a3cc-eb86d30a4303") + (Guid)validId ); // The parameter passed to `withConsistencyLevel` can be one of: @@ -302,5 +324,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..2ba843ffa 100644 --- a/_includes/code/csharp/SearchFiltersTest.cs +++ b/_includes/code/csharp/SearchFiltersTest.cs @@ -0,0 +1,537 @@ +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; + +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").Equal("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").GreaterThan(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").Like("*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.And( + Filter.Property("round").Equal("Double Jeopardy!"), + Filter.Property("points").LessThan(600), + Filter.Not(Filter.Property("answer").Equal("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.Or( + Filter.Property("points").GreaterThan(700), // gte/greaterThanOrEqual not always explicitly named in helpers, check impl + Filter.Property("points").LessThan(500), + Filter.Property("round").Equal("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.And( + Filter.Property("points").GreaterThan(300), + Filter.Property("points").LessThan(700), + Filter.Property("round").Equal("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.And( + Filter.Property("answer").Like("*bird*"), + Filter.Or( + Filter.Property("points").GreaterThan(700), + Filter.Property("points").LessThan(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").Like("*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"); + + // NOTE: You would typically use a UUID known to exist in your data + Guid targetId = Guid.Parse("00037775-1432-35e5-bc59-443baaef7d80"); + + var response = await collection.Query.FetchObjects( + filters: Filter.ID.Equal(targetId) + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // Inspect returned objects + Console.WriteLine(o.ID); + } + // 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.GreaterThan(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 CollectionConfig + { + Name = collectionName, + Properties = [ + Property.Text("title"), + Property.Date("some_date") + ], + VectorConfig = new VectorConfig("default", new Vectorizer.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").GreaterThan(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").Length().GreaterThan(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 CollectionConfig + { + 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") + .WithinGeoRange(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); + } + } + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchGenerativeTest.cs b/_includes/code/csharp/SearchGenerativeTest.cs new file mode 100644 index 000000000..3050c3d95 --- /dev/null +++ b/_includes/code/csharp/SearchGenerativeTest.cs @@ -0,0 +1,292 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Text.Json; +using System.Collections.Generic; +using System.Net.Http; +using Weaviate.Client.Models.Generative; +using System.Linq; + +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); + } + + // TODO[g-despot] NEW: Grpc.Core.RpcException : Status(StatusCode="Unknown", Detail="connection to: OpenAI API failed with status: 400 request-id: req_5abd283f230349a08d87849af0a556ce error: Unsupported parameter: 'top_p' is not supported with this model.") + [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 ("Translate this into German: {review_body}"), + // highlight-start + groupedTask: new GroupedTask ("Summarize these reviews"){ Provider = new Providers.OpenAI { Model= "gpt-5-mini" } } + // 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 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 ("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 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) + ); + // 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 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) + ); + + // 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 SingleGenerativePropertiesPython + } + + [Fact] + public async Task TestSingleGenerativeParameters() + { + // START SingleGenerativeParametersPython + // 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 + 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.First()}"); + //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 + groupedTask: new GroupedTask (task) + ); + // highlight-end + + // print the generated response + Console.WriteLine($"Grouped task result: {response.Generative?.Values.First()}"); + // END GroupedGenerativePython + } + + // TODO[g-despot] NEW: Grpc.Core.RpcException : Status(StatusCode="Unknown", Detail="connection to: OpenAI API failed with status: 400 request-id: req_5abd283f230349a08d87849af0a556ce error: Unsupported parameter: 'top_p' is not supported with this model.") + [Fact] + public async Task TestGroupedGenerativeParameters() + { + // START GroupedGenerativeParametersPython + 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 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, + 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 Python + } + + // TODO[g-despot] NEW: Implement testing with images + // [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 GroupedTask("Formulate a Jeopardy!-style question about this image") + { + // highlight-start + 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 + ); + + // 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 + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchHybridTest.cs b/_includes/code/csharp/SearchHybridTest.cs index 2fd6ddc69..8ac41542c 100644 --- a/_includes/code/csharp/SearchHybridTest.cs +++ b/_includes/code/csharp/SearchHybridTest.cs @@ -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 } @@ -43,6 +40,7 @@ public void Dispose() GC.SuppressFinalize(this); } + // TODO[g-despot] NEW: Grpc.Core.RpcException : Status(StatusCode="Unknown", Detail="extract target vectors: class WineReviewNV has multiple vectors, but no target vectors were provided") [Fact] public async Task NamedVectorHybrid() { @@ -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 ); @@ -295,7 +293,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 +303,7 @@ public async Task TestHybridWithVector() var response = await jeopardy.Query.Hybrid( "food", // highlight-start - vectors: Vectors.Create("default", queryVector), + vectors: Vectors.Create(queryVector), // highlight-end alpha: 0.25f, limit: 3 @@ -355,7 +352,7 @@ public async Task TestVectorParameters() "large animal", moveAway: new Move(force: 0.5f, concepts: ["mammal", "terrestrial"]), limit: 1, - returnMetadata: MetadataOptions.Vector + includeVectors: true ); var nearTextVector = nearTextResponse.Objects.First().Vectors["default"]; @@ -400,9 +397,8 @@ 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 } diff --git a/_includes/code/csharp/SearchImageTest.cs b/_includes/code/csharp/SearchImageTest.cs index 3c1a3db50..15121f87a 100644 --- a/_includes/code/csharp/SearchImageTest.cs +++ b/_includes/code/csharp/SearchImageTest.cs @@ -7,7 +7,6 @@ using System.Linq; using System.IO; using System.Net.Http; -using System.Collections.Generic; namespace WeaviateProject.Tests; @@ -33,8 +32,8 @@ private static async Task FileToByteArray(string path) // 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"); diff --git a/_includes/code/csharp/SearchKeywordTest.cs b/_includes/code/csharp/SearchKeywordTest.cs index 4aa2ed4b5..7fd6de46d 100644 --- a/_includes/code/csharp/SearchKeywordTest.cs +++ b/_includes/code/csharp/SearchKeywordTest.cs @@ -22,18 +22,9 @@ 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 } @@ -66,13 +57,52 @@ public async Task TestBM25Basic() Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); } - // START BM25OperatorOrWithMin - // Coming soon - // END BM25OperatorOrWithMin + [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")); + } + + // 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 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END BM25OperatorAnd - // START BM25OperatorAnd - // Coming soon - // END BM25OperatorAnd + // Assert.True(response.Objects.Count == 0); + } [Fact] public async Task TestBM25WithScore() @@ -130,7 +160,7 @@ public async Task TestAutocut() var response = await jeopardy.Query.BM25( "safety", // highlight-start - autoCut: 1 + autoLimit: 1 // highlight-end ); @@ -249,9 +279,8 @@ 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 } diff --git a/_includes/code/csharp/SearchMultiTargetTest.cs b/_includes/code/csharp/SearchMultiTargetTest.cs new file mode 100644 index 000000000..191a51b05 --- /dev/null +++ b/_includes/code/csharp/SearchMultiTargetTest.cs @@ -0,0 +1,283 @@ +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.Net.Http; + +public class MultiTargetSearchTest : IAsyncLifetime +{ + private WeaviateClient client; + private const string CollectionName = "JeopardyTiny"; + + public async Task InitializeAsync() + { + // START LoadDataNamedVectors + var weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + var weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + var openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); + + // Fallback for local + 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 } } + ); + } + + // Start with a new collection + if (await client.Collections.Exists(CollectionName)) + { + await client.Collections.Delete(CollectionName); + } + + // Define a new schema + await client.Collections.Create(new CollectionConfig + { + Name = CollectionName, + Description = "Jeopardy game show questions", + VectorConfig = new VectorConfigList() + { + new VectorConfig("jeopardy_questions_vector", new Vectorizer.Text2VecOpenAI() { SourceProperties = ["question"] }), + new VectorConfig("jeopardy_answers_vector", new Vectorizer.Text2VecOpenAI() { SourceProperties = ["answer"] }) + }, + Properties = + [ + Property.Text("category"), + Property.Text("question"), + Property.Text("answer") + ] + }); + + // Get the sample data set + using var httpClient = new HttpClient(); + var responseBody = await httpClient.GetStringAsync("https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json"); + var data = JsonSerializer.Deserialize>(responseBody); + + // Prepare and upload the sample data + var collection = client.Collections.Use(CollectionName); + + // Use anonymous objects for insertion + var insertTasks = data.Select(row => + collection.Data.Insert(new + { + question = row.GetProperty("Question").ToString(), + answer = row.GetProperty("Answer").ToString(), + category = row.GetProperty("Category").ToString() + }) + ); + await Task.WhenAll(insertTasks); + // END LoadDataNamedVectors + + // Wait for indexing + await Task.Delay(2000); + } + + public Task DisposeAsync() + { + // Clean up + if (client != null) + { + // cleanup logic if needed + } + return Task.CompletedTask; + } + + [Fact] + public async Task TestMultiBasic() + { + // START MultiBasic + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + "a wild animal", + limit: 2, + // highlight-start + // Implicit conversion to TargetVectors.Average + targetVector: ["jeopardy_questions_vector", "jeopardy_answers_vector"], + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiBasic + + Assert.Equal(2, response.Objects.Count()); + } + + // [Fact] + // public async Task TestMultiTargetNearVector() + // { + // var collection = client.Collections.Use(CollectionName); + // var someResult = await collection.Query.FetchObjects(limit: 2, includeVectors: true); + + // var v1 = someResult.Objects.ElementAt(0).Vectors["jeopardy_questions_vector"]; + // var 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 using the Vectors dictionary + // vector: new Vectors + // { + // { "jeopardy_questions_vector", v1 }, + // { "jeopardy_answers_vector", v2 } + // }, + // // highlight-end + // limit: 2, + // // targetVector: ["jeopardy_questions_vector", "jeopardy_answers_vector"], // Optional if keys match + // returnMetadata: MetadataOptions.Distance + // ); + + // foreach (var o in response.Objects) + // { + // Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + // Console.WriteLine(o.Metadata.Distance); + // } + // // END MultiTargetNearVector + // Assert.Equal(2, response.Objects.Count()); + // } + + // [Fact] + // public async Task TestMultiTargetMultipleNearVectors() + // { + // var collection = client.Collections.Use(CollectionName); + // var someResult = await collection.Query.FetchObjects(limit: 3, includeVectors: true); + + // var v1 = someResult.Objects.ElementAt(0).Vectors["jeopardy_questions_vector"]; + // var v2 = someResult.Objects.ElementAt(1).Vectors["jeopardy_answers_vector"]; + // var v3 = someResult.Objects.ElementAt(2).Vectors["jeopardy_answers_vector"]; + + // // START MultiTargetMultipleNearVectorsV1 + // var response = await collection.Query.NearVector( + // // highlight-start + // // Pass multiple vectors for a single target using a multi-dimensional array/list + // vector: new Vectors + // { + // { "jeopardy_questions_vector", v1 }, + // { "jeopardy_answers_vector", new[] { v2, v3 } } // List of vectors for this target + // }, + // // highlight-end + // limit: 2, + // targetVector: ["jeopardy_questions_vector", "jeopardy_answers_vector"], + // returnMetadata: MetadataOptions.Distance + // ); + // // END MultiTargetMultipleNearVectorsV1 + // Assert.Equal(2, response.Objects.Count()); + + // // START MultiTargetMultipleNearVectorsV2 + // var responseV2 = await collection.Query.NearVector( + // vector: new Vectors + // { + // { "jeopardy_questions_vector", v1 }, + // { "jeopardy_answers_vector", new[] { v2, v3 } } + // }, + // // highlight-start + // // Specify weights matching the structure of the input vectors + // targetVector: TargetVectors.ManualWeights( + // ("jeopardy_questions_vector", 10), + // ("jeopardy_answers_vector", [30, 30]) // Array of weights for the array of vectors + // ), + // // highlight-end + // limit: 2, + // returnMetadata: MetadataOptions.Distance + // ); + // // END MultiTargetMultipleNearVectorsV2 + // Assert.Equal(2, responseV2.Objects.Count()); + // } + + [Fact] + public async Task TestMultiTargetWithSimpleJoin() + { + // START MultiTargetWithSimpleJoin + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + "a wild animal", + limit: 2, + // highlight-start + // Explicitly specify the join strategy + targetVector: TargetVectors.Average(["jeopardy_questions_vector", "jeopardy_answers_vector"]), + // TargetVectors.Sum(), TargetVectors.Minimum(), TargetVectors.ManualWeights(), TargetVectors.RelativeScore() also available + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiTargetWithSimpleJoin + Assert.Equal(2, response.Objects.Count()); + } + + [Fact] + public async Task TestMultiTargetManualWeights() + { + // START MultiTargetManualWeights + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + "a wild animal", + limit: 2, + // highlight-start + targetVector: TargetVectors.ManualWeights( + ("jeopardy_questions_vector", 10), + ("jeopardy_answers_vector", 50) + ), + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END MultiTargetManualWeights + Assert.Equal(2, response.Objects.Count()); + } + + [Fact] + public async Task TestMultiTargetRelativeScore() + { + // START MultiTargetRelativeScore + var collection = client.Collections.Use(CollectionName); + + var response = await collection.Query.NearText( + "a wild animal", + limit: 2, + // highlight-start + targetVector: TargetVectors.RelativeScore( + ("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.Equal(2, response.Objects.Count()); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchSimilarityTest.cs b/_includes/code/csharp/SearchSimilarityTest.cs index f409b41c8..be18fc22b 100644 --- a/_includes/code/csharp/SearchSimilarityTest.cs +++ b/_includes/code/csharp/SearchSimilarityTest.cs @@ -3,9 +3,9 @@ using Weaviate.Client.Models; using System; using System.Threading.Tasks; -using System.Collections.Generic; using System.Text.Json; using System.Linq; +using System.Collections.Generic; 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( - weaviateUrl, - weaviateApiKey - // additionalHeaders: new Dictionary - // { - // { "X-OpenAI-Api-Key", openaiApiKey }, - // { "X-Cohere-Api-Key", cohereApiKey } - // } - ); + // FIX: Use await instead of .GetAwaiter().GetResult() + client = await Connect.Cloud( + weaviateUrl, + 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] @@ -49,8 +51,8 @@ public async Task NamedVectorNearText() "a sweet German white wine", limit: 2, // highlight-start - targetVector: ["title_country"], // Specify the target vector for named vector collections - // highlight-end + targetVector: ["title_country"], + // highlight-end returnMetadata: MetadataOptions.Distance ); @@ -86,6 +88,8 @@ public async Task GetNearText() Console.WriteLine(o.Metadata.Distance); } // END GetNearText + + Assert.NotEmpty(response.Objects); } [Fact] @@ -93,7 +97,10 @@ 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 + + // FIX: Handle nullable ID safely Guid uuid = (Guid)initialResponse.Objects.First().ID; // START GetNearObject @@ -122,14 +129,16 @@ 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 + vector: queryVector, // your query vector goes here // highlight-end limit: 2, returnMetadata: MetadataOptions.Distance @@ -168,6 +177,8 @@ public async Task GetLimitOffset() Console.WriteLine(o.Metadata.Distance); } // END GetLimitOffset + + Assert.Equal(2, response.Objects.Count()); } [Fact] @@ -207,7 +218,7 @@ public async Task GetWithAutocut() var response = await jeopardy.Query.NearText( "animals in movies", // highlight-start - autoCut: 1, // number of close groups + autoLimit: 1, // number of close groups // highlight-end returnMetadata: MetadataOptions.Distance ); @@ -236,9 +247,8 @@ public async Task GetWithGroupBy() "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 } @@ -299,4 +309,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..678977b37 100644 --- a/_includes/code/csharp/StarterGuidesCollectionsTest.cs +++ b/_includes/code/csharp/StarterGuidesCollectionsTest.cs @@ -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 @@ -52,7 +49,6 @@ public async Task TestBasicSchema() // END BasicSchema } - // TODO[g-despot] Missing vectorizePropertyName [Fact] public async Task TestSchemaWithPropertyOptions() { @@ -67,12 +63,10 @@ await client.Collections.Create(new CollectionConfig 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 ) ] }); @@ -82,13 +76,14 @@ await client.Collections.Create(new CollectionConfig [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 = + Properties = [ Property.Text("question"), Property.Text("answer") diff --git a/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs b/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs index 778e9c0c0..f58c5b334 100644 --- a/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs +++ b/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs @@ -29,7 +29,7 @@ private record JeopardyQuestionWithVector [Fact] public async Task TestBringYourOwnVectors() { - using var client = Connect.Local(); + using var client = await Connect.Local(); string collectionName = "Question"; try @@ -45,12 +45,13 @@ public async Task TestBringYourOwnVectors() await client.Collections.Create(new CollectionConfig { Name = collectionName, - Properties = + Properties = [ Property.Text("answer"), Property.Text("question"), Property.Text("category") ], + // Configure the "default" vector to be SelfProvided (BYOV) VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) }); // END CreateCollection @@ -65,36 +66,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 + data: 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 +110,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 diff --git a/_includes/code/csharp/WeaviateProject.Tests.csproj b/_includes/code/csharp/WeaviateProject.Tests.csproj index 3cfcc5e2e..4384484b2 100644 --- a/_includes/code/csharp/WeaviateProject.Tests.csproj +++ b/_includes/code/csharp/WeaviateProject.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/_includes/code/csharp/WeaviateProject.csproj b/_includes/code/csharp/WeaviateProject.csproj new file mode 100644 index 000000000..52faa3aff --- /dev/null +++ b/_includes/code/csharp/WeaviateProject.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + Exe + false + + + + + + + + + + + + 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..710fd267e --- /dev/null +++ b/_includes/code/csharp/quickstart/GitHubReadmeExample.cs @@ -0,0 +1,57 @@ +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Text.Json; +using System.Collections.Generic; +using System.Linq; + +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 CollectionConfig + { + Name = "Article", + Properties = + [ + Property.Text("content") + ], + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecTransformers()) // Use a vectorizer to generate embeddings during import + // .vectorConfig(VectorConfig.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/QuickstartCreate.cs b/_includes/code/csharp/quickstart/QuickstartCreate.cs new file mode 100644 index 000000000..a0c8663f5 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartCreate.cs @@ -0,0 +1,77 @@ +// START CreateCollection +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +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 CollectionConfig + { + Name = collectionName, + VectorConfig = new VectorConfig("default", new Vectorizer.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..47e35b8b4 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartCreateVectors.cs @@ -0,0 +1,86 @@ +// START CreateCollection +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +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 CollectionConfig + { + Name = collectionName, + VectorConfig = new VectorConfig("default", new Vectorizer.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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartLocalCreate.cs b/_includes/code/csharp/quickstart/QuickstartLocalCreate.cs new file mode 100644 index 000000000..afb98aa6f --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalCreate.cs @@ -0,0 +1,82 @@ +// START CreateCollection +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; + +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 CollectionConfig + { + Name = collectionName, + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecOllama + { + ApiEndpoint = "http://ollama:11434", // If using Docker you might need: http://host.docker.internal:11434 + Model = "nomic-embed-text" // The model to use + }), + // 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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartLocalCreateVectors.cs b/_includes/code/csharp/quickstart/QuickstartLocalCreateVectors.cs new file mode 100644 index 000000000..4b8c3c776 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalCreateVectors.cs @@ -0,0 +1,90 @@ +// START CreateCollection +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; + +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 CollectionConfig + { + Name = collectionName, + // No automatic vectorization since we're providing vectors + VectorConfig = new VectorConfig("default", new Vectorizer.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 Vectors { { "default", [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 Vectors { { "default", [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 Vectors { { "default", [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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearText.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearText.cs new file mode 100644 index 000000000..39a2aeb90 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearText.cs @@ -0,0 +1,36 @@ +// START NearText +using Weaviate.Client; +using System; +using System.Threading.Tasks; +using System.Text.Json; +using System.Threading; + +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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearTextRAG.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearTextRAG.cs new file mode 100644 index 000000000..820f54dcc --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearTextRAG.cs @@ -0,0 +1,42 @@ +// START RAG +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using Weaviate.Client.Models.Generative; +using System.Text.Json; + +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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVector.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVector.cs new file mode 100644 index 000000000..0d059b154 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVector.cs @@ -0,0 +1,38 @@ +// START NearText +using Weaviate.Client; +using System; +using System.Threading.Tasks; +using System.Text.Json; + +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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVectorRAG.cs b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVectorRAG.cs new file mode 100644 index 000000000..cfedd4ec8 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartLocalQueryNearVectorRAG.cs @@ -0,0 +1,43 @@ +// START RAG +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Text.Json; + +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( + queryVector, + limit: 1, + returnProperties: ["title", "description", "genre"], + groupedTask: new GroupedTask("Write a tweet with emojis about this movie.") + { + Provider = new Weaviate.Client.Models.Generative.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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearText.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearText.cs new file mode 100644 index 000000000..07cd92f0a --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearText.cs @@ -0,0 +1,41 @@ +// START NearText +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Text.Json; + +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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearTextRAG.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearTextRAG.cs new file mode 100644 index 000000000..b7d3bb395 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearTextRAG.cs @@ -0,0 +1,48 @@ +// START RAG +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text.Json; + +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 Weaviate.Client.Models.Generative.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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearVector.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearVector.cs new file mode 100644 index 000000000..dba6e07da --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearVector.cs @@ -0,0 +1,42 @@ +// START NearText +using Weaviate.Client; +using System; +using System.Threading.Tasks; +using System.Text.Json; + +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 \ No newline at end of file diff --git a/_includes/code/csharp/quickstart/QuickstartQueryNearVectorRAG.cs b/_includes/code/csharp/quickstart/QuickstartQueryNearVectorRAG.cs new file mode 100644 index 000000000..bd0aaa510 --- /dev/null +++ b/_includes/code/csharp/quickstart/QuickstartQueryNearVectorRAG.cs @@ -0,0 +1,50 @@ +// START RAG +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text.Json; + +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( + queryVector, + limit: 1, + returnProperties: ["title", "description", "genre"], + groupedTask: new GroupedTask("Write a tweet with emojis about this movie.") + { + Provider = new Weaviate.Client.Models.Generative.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 \ No newline at end of file diff --git a/_includes/code/howto/search.filters.py b/_includes/code/howto/search.filters.py index e5ea1960b..dfadf860b 100644 --- a/_includes/code/howto/search.filters.py +++ b/_includes/code/howto/search.filters.py @@ -538,9 +538,9 @@ filters=Filter.by_property("country").is_none(True) # Find objects where the `country` property is null # highlight-end ) - +print("despot. othing") for o in response.objects: - print(o.properties) # Inspect returned objects + print("despot"+o.properties) # Inspect returned objects # END FilterByPropertyNullState diff --git a/_includes/code/java-v6/src/test/java/quickstart/QuickstartCreateVectors.java b/_includes/code/java-v6/src/test/java/quickstart/QuickstartCreateVectors.java index 6272eb53b..6a6d930d8 100644 --- a/_includes/code/java-v6/src/test/java/quickstart/QuickstartCreateVectors.java +++ b/_includes/code/java-v6/src/test/java/quickstart/QuickstartCreateVectors.java @@ -12,7 +12,6 @@ public class QuickstartCreateVectors { - // TODO[g-despot] DX: Far to complicated vector insertion public static void main(String[] args) throws Exception { WeaviateClient client = null; String collectionName = "Movie"; @@ -47,7 +46,6 @@ public static void main(String[] args) throws Exception { Map 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/quickstart/clients.install.mdx b/_includes/code/quickstart/clients.install.mdx index f42b68586..6da237bc9 100644 --- a/_includes/code/quickstart/clients.install.mdx +++ b/_includes/code/quickstart/clients.install.mdx @@ -60,7 +60,7 @@ Add this dependency to your project:

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..dc9cebceb 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/quickstart.short.create_collection.mdx b/_includes/code/quickstart/quickstart.short.create_collection.mdx index 0915ea5f1..eb81026e9 100644 --- a/_includes/code/quickstart/quickstart.short.create_collection.mdx +++ b/_includes/code/quickstart/quickstart.short.create_collection.mdx @@ -6,6 +6,7 @@ import TSCode from "!!raw-loader!/_includes/code/typescript/quickstart.short.cre import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/quickstart/short_1/quickstart.short.create_collection.go"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/quickstart/QuickstartCreate.java"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/QuickstartCreate.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/quickstart/QuickstartCreate.cs"; @@ -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..d79b50f9f 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..1cc06e973 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..b95c7a048 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..454243f2b 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..c3a2b19ea 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..f0a89ea9c 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..1878201d6 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..6a7e79320 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..86310e23c 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..a292e58a8 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..5cc34ec3d 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/docs/weaviate/client-libraries/csharp.mdx b/docs/weaviate/client-libraries/csharp.mdx index 49a9c4809..aea70bac6 100644 --- a/docs/weaviate/client-libraries/csharp.mdx +++ b/docs/weaviate/client-libraries/csharp.mdx @@ -47,7 +47,7 @@ This page broadly covers the Weaviate C# (beta release). For usage information n ## Installation ```xml - + ```
diff --git a/docs/weaviate/configuration/compression/multi-vectors.md b/docs/weaviate/configuration/compression/multi-vectors.md index 65a54f901..550e45a1d 100644 --- a/docs/weaviate/configuration/compression/multi-vectors.md +++ b/docs/weaviate/configuration/compression/multi-vectors.md @@ -10,7 +10,7 @@ import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBl import PyCode from '!!raw-loader!/\_includes/code/howto/manage-data.collections.py'; import TSCode from '!!raw-loader!/\_includes/code/howto/manage-data.collections.ts'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/ManageCollectionsTest.java"; - +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/ManageCollectionsTest.cs"; Multi-vector embeddings represent a single data object, like a document or image, using a set of multiple vectors rather than a single vector. This approach allows for a more granular capture of semantic information, as each vector can represent different parts of the object. However, this leads to a significant increase in memory consumption, as multiple vectors are stored for each item. @@ -61,6 +61,14 @@ Compression techniques become especially crucial for multi-vector systems to man ``` + + + The final dimensionality of the MUVERA encoded vector will be diff --git a/docs/weaviate/configuration/rbac/manage-groups.mdx b/docs/weaviate/configuration/rbac/manage-groups.mdx index 6c6c7214a..71f3f14be 100644 --- a/docs/weaviate/configuration/rbac/manage-groups.mdx +++ b/docs/weaviate/configuration/rbac/manage-groups.mdx @@ -12,6 +12,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import OidcGroupPyCode from "!!raw-loader!/_includes/code/python/howto.configure.rbac.oidc.groups.py"; import OidcGroupTSCode from "!!raw-loader!/_includes/code/typescript/howto.configure.rbac.oidc.groups.ts"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/RBACTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/RBACTest.cs"; :::info Added in `v1.33` @@ -70,6 +71,14 @@ This example assigns the `testRole` and `viewer` roles to the `/admin-group`. ``` + + + ### 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..0aed8c8cc 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..1de0230ef 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/manage-collections/migrate.mdx b/docs/weaviate/manage-collections/migrate.mdx index 2b0817a0f..e5f4e6589 100644 --- a/docs/weaviate/manage-collections/migrate.mdx +++ b/docs/weaviate/manage-collections/migrate.mdx @@ -75,7 +75,7 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col text={CSharpCode} startMarker="// START CreateCollectionCollectionToCollection" endMarker="// END CreateCollectionCollectionToCollection" - language="csharp" + language="csharpraw" /> @@ -117,7 +117,7 @@ Migrate: text={CSharpCode} startMarker="// START CollectionToCollection" endMarker="// END CollectionToCollection" - language="csharp" + language="csharpraw" /> @@ -158,7 +158,7 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col text={CSharpCode} startMarker="// START CreateCollectionCollectionToTenant" endMarker="// END CreateCollectionCollectionToTenant" - language="csharp" + language="csharpraw" /> @@ -197,7 +197,7 @@ Add tenants at the target instance before adding data objects. text={CSharpCode} startMarker="// START CreateTenants" endMarker="// END CreateTenants" - language="csharp" + language="csharpraw" /> @@ -239,7 +239,7 @@ Migrate: text={CSharpCode} startMarker="// START CollectionToTenant" endMarker="// END CollectionToTenant" - language="csharp" + language="csharpraw" /> @@ -280,7 +280,7 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col text={CSharpCode} startMarker="// START CreateCollectionTenantToCollection" endMarker="// END CreateCollectionTenantToCollection" - language="csharp" + language="csharpraw" /> @@ -322,7 +322,7 @@ Migrate: text={CSharpCode} startMarker="// START TenantToCollection" endMarker="// END TenantToCollection" - language="csharp" + language="csharpraw" /> @@ -363,7 +363,7 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col text={CSharpCode} startMarker="// START CreateCollectionTenantToTenant" endMarker="// END CreateCollectionTenantToTenant" - language="csharp" + language="csharpraw" /> @@ -402,7 +402,7 @@ Add tenants at the target instance before adding data objects. text={CSharpCode} startMarker="// START CreateTenants" endMarker="// END CreateTenants" - language="csharp" + language="csharpraw" /> @@ -444,7 +444,7 @@ Migrate: text={CSharpCode} startMarker="// START TenantToTenant" endMarker="// END TenantToTenant" - language="csharp" + language="csharpraw" /> diff --git a/docs/weaviate/manage-collections/multi-tenancy.mdx b/docs/weaviate/manage-collections/multi-tenancy.mdx index 47f0ef9f9..763d74440 100644 --- a/docs/weaviate/manage-collections/multi-tenancy.mdx +++ b/docs/weaviate/manage-collections/multi-tenancy.mdx @@ -10,7 +10,7 @@ import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.multi-tenancy import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.multi-tenancy.ts"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.multi-tenancy.java"; 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"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.multi-tenancy_test.go"; import GoCodeAuto from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.create_auto-multitenancy.go"; import CurlCode from "!!raw-loader!/_includes/code/howto/manage-data.multi-tenancy-curl.sh"; @@ -495,6 +495,14 @@ Change a tenant state between `ACTIVE`, `INACTIVE`, and `OFFLOADED`. 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..2deecc233 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) diff --git a/tests/docker-compose-three-nodes.yml b/tests/docker-compose-three-nodes.yml index c8cb25759..4caa6b48a 100644 --- a/tests/docker-compose-three-nodes.yml +++ b/tests/docker-compose-three-nodes.yml @@ -14,6 +14,7 @@ services: - "8180:8080" - 50151:50051 environment: + REPLICA_MOVEMENT_ENABLED: 'true' AUTOSCHEMA_ENABLED: 'false' QUERY_DEFAULTS_LIMIT: 25 QUERY_MAXIMUM_RESULTS: 10000 @@ -41,6 +42,7 @@ services: - "8181:8080" - 50152:50051 environment: + REPLICA_MOVEMENT_ENABLED: 'true' AUTOSCHEMA_ENABLED: 'false' QUERY_DEFAULTS_LIMIT: 25 QUERY_MAXIMUM_RESULTS: 10000 @@ -69,6 +71,7 @@ services: - "8182:8080" - 50153:50051 environment: + REPLICA_MOVEMENT_ENABLED: 'true' AUTOSCHEMA_ENABLED: 'false' QUERY_DEFAULTS_LIMIT: 25 QUERY_MAXIMUM_RESULTS: 10000 diff --git a/versions-config.json b/versions-config.json index 1529f2ad7..076f66076 100644 --- a/versions-config.json +++ b/versions-config.json @@ -11,5 +11,5 @@ "java_new_client_version": "6.0.0", "typescript_client_version": "3.9.0", "spark_connector_version": "1.4.0", - "csharp_client_version": "0.0.1-beta.4" + "csharp_client_version": "0.0.1-beta.5" }