diff --git a/_includes/code/connections/timeouts-custom.mdx b/_includes/code/connections/timeouts-custom.mdx index a51bf15fc..c16868653 100644 --- a/_includes/code/connections/timeouts-custom.mdx +++ b/_includes/code/connections/timeouts-custom.mdx @@ -5,6 +5,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyV4Code from "!!raw-loader!/_includes/code/connections/connect-python-v4.py"; import TsV3Code from "!!raw-loader!/_includes/code/connections/connect-ts-v3.ts"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ConnectionTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ConnectionTest.cs"; @@ -31,4 +32,12 @@ import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/Conne language="java" /> + + + diff --git a/_includes/code/connections/timeouts-local.mdx b/_includes/code/connections/timeouts-local.mdx index d17480b8e..3fdf82267 100644 --- a/_includes/code/connections/timeouts-local.mdx +++ b/_includes/code/connections/timeouts-local.mdx @@ -5,6 +5,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyV4Code from "!!raw-loader!/_includes/code/connections/connect-python-v4.py"; import TsV3Code from "!!raw-loader!/_includes/code/connections/connect-ts-v3.ts"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ConnectionTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ConnectionTest.cs"; @@ -31,4 +32,12 @@ import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/Conne language="java" /> + + + diff --git a/_includes/code/csharp/ConfigureBQTest.cs b/_includes/code/csharp/ConfigureBQTest.cs new file mode 100644 index 000000000..ccb339633 --- /dev/null +++ b/_includes/code/csharp/ConfigureBQTest.cs @@ -0,0 +1,106 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; + +namespace WeaviateProject.Tests; + +public class ConfigureBQTest : 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 + + // 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; + } + + [Fact] + public async Task TestEnableBQ() + { + // START EnableBQ + await client.Collections.Create(new Collection + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig( + "default", + new Vectorizer.Text2VecContextionary(), + new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.BQ() + // highlight-end + } + ) + }); + // END EnableBQ + } + + [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 Collection + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }); + + // START UpdateSchema + await collection.Config.Update(c => + { + var vectorConfig = c.VectorConfig["default"]; + vectorConfig.VectorIndexConfig.UpdateHNSW(h => h.Quantizer = new VectorIndex.Quantizers.BQ()); + }); + // END UpdateSchema + } + + [Fact] + public async Task TestBQWithOptions() + { + // START BQWithOptions + await client.Collections.Create(new Collection + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig( + "default", + new Vectorizer.Text2VecContextionary(), + // highlight-start + new VectorIndex.HNSW + { + VectorCacheMaxObjects = 100000, + Quantizer = new VectorIndex.Quantizers.BQ + { + Cache = true, + RescoreLimit = 200 + } + } + // highlight-end + ) + }); + // END BQWithOptions + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ConfigurePQTest.cs b/_includes/code/csharp/ConfigurePQTest.cs new file mode 100644 index 000000000..b85f234d1 --- /dev/null +++ b/_includes/code/csharp/ConfigurePQTest.cs @@ -0,0 +1,218 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text.Json; +using System.Net.Http; +using System.Linq; +using System.Text.Json.Serialization; + +namespace WeaviateProject.Tests; + +public class ConfigurePQTest : IAsyncLifetime +{ + private static WeaviateClient client; + private static List data; + private const string COLLECTION_NAME = "Question"; + + // Runs once before any tests in the class + public async Task InitializeAsync() + { + // START DownloadData + using var httpClient = new HttpClient(); + var responseBody = await httpClient.GetStringAsync( + "https://raw.githubusercontent.com/weaviate-tutorials/intro-workshop/main/data/jeopardy_1k.json"); + + data = JsonSerializer.Deserialize>(responseBody); + + Console.WriteLine($"Data type: {data.GetType().Name}, Length: {data.Count}"); + Console.WriteLine(JsonSerializer.Serialize(data[1], new JsonSerializerOptions { WriteIndented = true })); + // END DownloadData + + // START ConnectCode + // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. + // This must be configured in Weaviate's environment variables. + client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + + var meta = await client.GetMeta(); + Console.WriteLine($"Weaviate info: {meta.Version}"); + // END ConnectCode + } + + // Runs once after all tests in the class + public async Task DisposeAsync() + { + if (await client.Collections.Exists(COLLECTION_NAME)) + { + await client.Collections.Delete(COLLECTION_NAME); + } + } + + // This helper runs before each test to ensure a clean collection state + private async Task BeforeEach() + { + if (await client.Collections.Exists(COLLECTION_NAME)) + { + await client.Collections.Delete(COLLECTION_NAME); + } + } + + // TODO[g-despot] Why is Encoder required? + // TODO[g-despot] Why are properties required? ERROR: didn't find a single property which is of type string or text and is not excluded from indexing + [Fact] + public async Task TestCollectionWithAutoPQ() + { + await BeforeEach(); + + // START CollectionWithAutoPQ + await client.Collections.Create(new Collection + { + Name = "Question", + VectorConfig = new VectorConfig( + "default", + new Vectorizer.Text2VecContextionary(), + new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.PQ + { + TrainingLimit = 50000, // Set the threshold to begin training + Encoder = new VectorIndex.Quantizers.PQ.EncoderConfig + { + Type = VectorIndex.Quantizers.EncoderType.Tile, + Distribution = VectorIndex.Quantizers.DistributionType.Normal, + }, + } + // highlight-end + } + ), + Properties = + [ + Property.Text("question"), + Property.Text("answer"), + Property.Text("category") + ] + }); + // END CollectionWithAutoPQ + + // Confirm that the collection has been created with the right settings + var collection = client.Collections.Use(COLLECTION_NAME); + var config = await collection.Get(); + Assert.NotNull(config); + var hnswConfig = config.VectorConfig["default"].VectorIndexConfig as VectorIndex.HNSW; + Assert.NotNull(hnswConfig?.Quantizer); + } + + [Fact] + public async Task TestUpdateSchemaWithPQ() + { + await BeforeEach(); + + // START InitialSchema + await client.Collections.Create(new Collection + { + Name = "Question", + Description = "A Jeopardy! question", + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()), + Properties = + [ + Property.Text("question"), + Property.Text("answer"), + Property.Text("category") + ] + }); + // END InitialSchema + + var collection = client.Collections.Use(COLLECTION_NAME); + var initialConfig = await collection.Get(); + Assert.NotNull(initialConfig); + + // START LoadData + var objectList = data.Select(obj => new + { + question = obj.GetProperty("Question").GetString(), + answer = obj.GetProperty("Answer").GetString() + }).ToArray(); + + await collection.Data.InsertMany(objectList); + // END LoadData + + var aggregateResponse = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(1000, aggregateResponse.TotalCount); + + // START UpdateSchema + await collection.Config.Update(c => + { + var vectorConfig = c.VectorConfig["default"]; + vectorConfig.VectorIndexConfig.UpdateHNSW(h => + h.Quantizer = new VectorIndex.Quantizers.PQ + { + TrainingLimit = 50000, + Encoder = new VectorIndex.Quantizers.PQ.EncoderConfig + { + Type = VectorIndex.Quantizers.EncoderType.Tile, + Distribution = VectorIndex.Quantizers.DistributionType.Normal, + }, + }); + }); + // END UpdateSchema + + var updatedConfig = await collection.Get(); + Assert.NotNull(updatedConfig); + var hnswConfig = updatedConfig.VectorConfig["default"].VectorIndexConfig as VectorIndex.HNSW; + Assert.IsType(hnswConfig?.Quantizer); + } + + [Fact] + public async Task TestGetSchema() + { + await BeforeEach(); + + // Create a collection with PQ enabled to inspect its schema + await client.Collections.Create(new Collection + { + Name = "Question", + VectorConfig = new VectorConfig( + "default", + new Vectorizer.Text2VecContextionary(), + new VectorIndex.HNSW + { + Quantizer = new VectorIndex.Quantizers.PQ + { + Encoder = new VectorIndex.Quantizers.PQ.EncoderConfig + { + Type = VectorIndex.Quantizers.EncoderType.Tile, + Distribution = VectorIndex.Quantizers.DistributionType.Normal, + }, + TrainingLimit = 50000 + } + } + ), + Properties = + [ + Property.Text("question"), + Property.Text("answer"), + Property.Text("category") + ] + }); + + // START GetSchema + var jeopardy = client.Collections.Use("Question"); + var config = await jeopardy.Get(); + + Console.WriteLine(JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); + // END GetSchema + Assert.NotNull(config); + + var hnswConfig = config.VectorConfig["default"].VectorIndexConfig as VectorIndex.HNSW; + var pqConfig = hnswConfig?.Quantizer as VectorIndex.Quantizers.PQ; + Assert.NotNull(pqConfig); + + // Print some of the config properties + Console.WriteLine($"Training: {pqConfig.TrainingLimit}"); + Console.WriteLine($"Segments: {pqConfig.Segments}"); + Console.WriteLine($"Centroids: {pqConfig.Centroids}"); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ConfigureRQTest.cs b/_includes/code/csharp/ConfigureRQTest.cs new file mode 100644 index 000000000..f2bebf560 --- /dev/null +++ b/_includes/code/csharp/ConfigureRQTest.cs @@ -0,0 +1,166 @@ +// using Xunit; +// using Weaviate.Client; +// using Weaviate.Client.Models; +// using System; +// using System.Threading.Tasks; + +// namespace WeaviateProject.Tests; + +// 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 + +// // 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; +// } + +// [Fact] +// public async Task TestEnableRQ() +// { +// // START EnableRQ +// await client.Collections.Create(new Collection +// { +// Name = "MyCollection", +// Properties = [Property.Text("title")], +// VectorConfig = new VectorConfig( +// "default", +// new Vectorizer.Text2VecContextionary(), +// 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 Collection +// { +// Name = "MyCollection", +// Properties = [Property.Text("title")], +// VectorConfig = new VectorConfig( +// "default", +// new Vectorizer.Text2VecContextionary(), +// 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 Collection +// { +// Name = "MyCollection", +// Properties = [Property.Text("title")], +// VectorConfig = new VectorConfig( +// "default", +// new Vectorizer.Text2VecContextionary(), +// // highlight-start +// // Omitting the Quantizer property results in an uncompressed index. +// new VectorIndex.HNSW() +// // highlight-end +// ) +// }); +// // END Uncompressed +// } + +// [Fact] +// public async Task TestRQWithOptions() +// { +// // START RQWithOptions +// await client.Collections.Create(new Collection +// { +// Name = "MyCollection", +// Properties = [Property.Text("title")], +// VectorConfig = new VectorConfig( +// "default", +// new Vectorizer.Text2VecContextionary(), +// 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 Collection +// { +// Name = "MyCollection", +// Properties = [Property.Text("title")], +// VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()) +// }); + +// // 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 Collection +// { +// Name = "MyCollection", +// Properties = [Property.Text("title")], +// VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()) +// }); + +// // 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 new file mode 100644 index 000000000..9ee88c528 --- /dev/null +++ b/_includes/code/csharp/ConfigureSQTest.cs @@ -0,0 +1,108 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; + +namespace WeaviateProject.Tests; + +public class ConfigureSQTest : 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 + + // 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; + } + + [Fact] + public async Task TestEnableSQ() + { + // START EnableSQ + await client.Collections.Create(new Collection + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig( + "default", + new Vectorizer.Text2VecContextionary(), + new VectorIndex.HNSW + { + // highlight-start + Quantizer = new VectorIndex.Quantizers.SQ() + // highlight-end + } + ) + }); + // END EnableSQ + } + + [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 Collection + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }); + + // START UpdateSchema + await collection.Config.Update(c => + { + var vectorConfig = c.VectorConfig["default"]; + vectorConfig.VectorIndexConfig.UpdateHNSW(h => h.Quantizer = new VectorIndex.Quantizers.SQ()); + }); + // END UpdateSchema + } + + // TODO[g-despot] Missing cache + [Fact] + public async Task TestSQWithOptions() + { + // START SQWithOptions + await client.Collections.Create(new Collection + { + Name = "MyCollection", + Properties = [Property.Text("title")], + VectorConfig = new VectorConfig( + "default", + new Vectorizer.Text2VecContextionary(), + // highlight-start + new VectorIndex.HNSW + { + VectorCacheMaxObjects = 100000, + Quantizer = new VectorIndex.Quantizers.SQ + { + //Cache = true, + TrainingLimit = 50000, + RescoreLimit = 200 + } + } + // highlight-end + ) + }); + // END SQWithOptions + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ConnectionTest.cs b/_includes/code/csharp/ConnectionTest.cs new file mode 100644 index 000000000..67e59717b --- /dev/null +++ b/_includes/code/csharp/ConnectionTest.cs @@ -0,0 +1,204 @@ +using Weaviate.Client; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace WeaviateProject.Examples; + +public class ConnectionTest +{ + //TODO[g-despot] Replace with readiness check + [Fact] + 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 + }; + using var client = new WeaviateClient(config); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // 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 TestConnectWCDWithApiKey() + { + // START APIKeyWCD + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + using var client = Connect.Cloud( + weaviateUrl, // Replace with your Weaviate Cloud URL + weaviateApiKey // Replace with your Weaviate Cloud key + ); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // END APIKeyWCD + } + + [Fact] + public async Task TestCustomConnection() + { + // START CustomConnect + // 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"); + + var config = new ClientConfiguration + { + UseSsl = true, // Corresponds to scheme("https") + RestAddress = httpHost, + RestPort = 443, + GrpcAddress = grpcHost, + GrpcPort = 443, + Credentials = Auth.ApiKey(weaviateApiKey), + // Headers = new Dictionary + // { + // { "X-Cohere-Api-Key", cohereApiKey } + // } + }; + using var client = new WeaviateClient(config); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // END CustomConnect + } + + [Fact] + public async Task TestCustomApiKeyConnection() + { + // START ConnectWithApiKeyExample + // 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"); + + var config = new ClientConfiguration + { + UseSsl = true, // Corresponds to scheme("https") + RestAddress = httpHost, + RestPort = 443, + GrpcAddress = grpcHost, + GrpcPort = 443, + Credentials = Auth.ApiKey(weaviateApiKey), + // Headers = new Dictionary + // { + // { "X-Cohere-Api-Key", cohereApiKey } + // } + }; + using var client = new WeaviateClient(config); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // END ConnectWithApiKeyExample + } + + [Fact] + public async Task TestConnectLocalNoAuth() + { + // START LocalNoAuth + using var client = Connect.Local(); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // END LocalNoAuth + } + + [Fact] + public async Task TestConnectLocalWithAuth() + { + // START LocalAuth + // 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) + }; + using var client = new WeaviateClient(config); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // END LocalAuth + } + + [Fact] + public async Task TestConnectLocalWithThirdPartyKeys() + { + // START LocalThirdPartyAPIKeys + // Best practice: store your credentials in environment variables + string cohereApiKey = Environment.GetEnvironmentVariable("COHERE_API_KEY"); + + var config = new ClientConfiguration + { + // Headers = new Dictionary + // { + // { "X-Cohere-Api-Key", cohereApiKey } + // } + }; + using var client = new WeaviateClient(config); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // END LocalThirdPartyAPIKeys + } + + [Fact] + public async Task TestConnectWCDWithThirdPartyKeys() + { + // START ThirdPartyAPIKeys + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + string cohereApiKey = Environment.GetEnvironmentVariable("COHERE_API_KEY"); + + using var client = Connect.Cloud( + weaviateUrl, // Replace with your Weaviate Cloud URL + weaviateApiKey // Replace with your Weaviate Cloud key + // headers: new Dictionary + // { + // { "X-Cohere-Api-Key", cohereApiKey } + // } + ); + + var meta = await client.GetMeta(); + Console.WriteLine(meta); + + // The 'using' statement handles freeing up resources automatically. + // END ThirdPartyAPIKeys + } +} \ No newline at end of file diff --git a/_includes/code/csharp/GetStartedTest.cs b/_includes/code/csharp/GetStartedTest.cs new file mode 100644 index 000000000..f6e1deb91 --- /dev/null +++ b/_includes/code/csharp/GetStartedTest.cs @@ -0,0 +1,79 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Linq; +using System.Threading.Tasks; +// ... other usings + +// 1. Define your strongly-typed class +public class JeopardyQuestion +{ + public string? question { get; set; } + public string? answer { get; set; } + public string? category { get; set; } +} + +public class GetStartedTests +{ + [Fact] + public async Task GetStarted() + { + var client = Connect.Local(); + const string collectionName = "Question"; + + try + { + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + + var questions = await client.Collections.Create(new Collection() + { + Name = collectionName, + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecOllama()), + Properties = new List + { + Property.Text("answer"), + Property.Text("question"), + Property.Text("category"), + } + }); + + // Download and parse data as before... + using var httpClient = new HttpClient(); + var resp = await httpClient.GetAsync( + "https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json" + ); + resp.EnsureSuccessStatusCode(); + var jsonString = await resp.Content.ReadAsStringAsync(); + var data = JsonSerializer.Deserialize>(jsonString); + + // ============================= YOUR NEW, CLEAN CODE ============================= + // 2. Prepare the data by mapping it to your new class + var dataObjects = data.Select(d => new JeopardyQuestion + { + answer = d.GetProperty("Answer").GetString(), + question = d.GetProperty("Question").GetString(), + category = d.GetProperty("Category").GetString() + }).ToList(); + // ============================================================================== + + var importResult = await questions.Data.InsertMany(dataObjects); + + var response = await questions.Query.NearText("biology", limit: 2); + // ... rest of the test + Assert.Equal(2, response.Objects.Count()); + } + finally + { + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + } + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageCollectionsAliasTest.cs b/_includes/code/csharp/ManageCollectionsAliasTest.cs new file mode 100644 index 000000000..e69de29bb diff --git a/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs b/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs new file mode 100644 index 000000000..975a0b509 --- /dev/null +++ b/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs @@ -0,0 +1,317 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Collections.Generic; + +namespace WeaviateProject.Tests; + +public class ManageCollectionsCrossReferencesTest : IAsyncLifetime +{ + private WeaviateClient client; + + // Runs before each test + public 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; + } + + // Runs after each test + public async Task DisposeAsync() + { + // Clean up collections after each test + await client.Collections.Delete("JeopardyQuestion"); + await client.Collections.Delete("JeopardyCategory"); + } + + [Fact] + public async Task TestCrossRefDefinition() + { + // START CrossRefDefinition + await client.Collections.Create(new Collection + { + Name = "JeopardyCategory", + Description = "A Jeopardy! category", + Properties = new() { Property.Text("title") } + }); + + await client.Collections.Create(new Collection + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = new() + { + Property.Text("question"), + Property.Text("answer") + }, + // highlight-start + References = new() + { + new Reference("hasCategory", "JeopardyCategory") + } + // highlight-end + }); + // END CrossRefDefinition + + // Verify collections were created properly + var questionConfig = await client.Collections.Export("JeopardyQuestion"); + Assert.Single(questionConfig.References); + Assert.Equal("hasCategory", questionConfig.References.First().Name); + } + + [Fact] + public async Task TestObjectWithCrossRef() + { + await SetupCollections(); + 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 newObject = await questions.Data.Insert( + properties, // The properties of the object + // highlight-start + references: [new ObjectReference("hasCategory", categoryUuid)] + // highlight-end + ); + // END ObjectWithCrossRef + + // Test results + var fetchedObj = await questions.Query.FetchObjectByID(newObject, returnReferences: [new QueryReference("hasCategory")]); + Assert.NotNull(fetchedObj); + Assert.True(fetchedObj.References.ContainsKey("hasCategory")); + } + + [Fact] + public async Task TestOneWay() + { + await SetupCollections(); + 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" }); + + // START OneWayCrossReferences + await questions.Data.ReferenceAdd( + from: questionObjId, + fromProperty: "hasCategory", + // highlight-start + to: categoryObjId + // highlight-end + ); + // END OneWayCrossReferences + + var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + Assert.NotNull(result); + Assert.True(result.References.ContainsKey("hasCategory")); + } + + [Fact] + public async Task TestTwoWay() + { + // START TwoWayCategory1CrossReferences + await client.Collections.Create(new Collection + { + Name = "JeopardyCategory", + Description = "A Jeopardy! category", + Properties = new() { Property.Text("title") } + }); + // END TwoWayCategory1CrossReferences + + // START TwoWayQuestionCrossReferences + await client.Collections.Create(new Collection + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = new() { Property.Text("question"), Property.Text("answer") }, + // highlight-start + References = new() { new Reference("hasCategory", "JeopardyCategory") } + // highlight-end + }); + // END TwoWayQuestionCrossReferences + + // START TwoWayCategoryCrossReferences + var category = client.Collections.Use("JeopardyCategory"); + await category.Config.AddProperty( + // highlight-start + Property.Reference("hasQuestion", "JeopardyQuestion") + // highlight-end + ); + // END TwoWayCategoryCrossReferences + + 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" }); + + // START TwoWayCrossReferences + // For the "San Francisco" JeopardyQuestion object, add a cross-reference to the "U.S. CITIES" JeopardyCategory object + // highlight-start + await questions.Data.ReferenceAdd(from: questionObjId, fromProperty: "hasCategory", to: categoryObjId); + // highlight-end + + // For the "U.S. CITIES" JeopardyCategory object, add a cross-reference to "San Francisco" + // highlight-start + await categories.Data.ReferenceAdd(from: categoryObjId, fromProperty: "hasQuestion", to: questionObjId); + // highlight-end + // END TwoWayCrossReferences + + var result = await categories.Query.FetchObjectByID(categoryObjId, returnReferences: [new QueryReference("hasQuestion")]); + Assert.NotNull(result); + Assert.True(result.References.ContainsKey("hasQuestion")); + } + + [Fact] + public async Task TestMultiple() + { + await SetupCollections(); + 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" }); + var categoryObjIdAlt = await categories.Data.Insert(new { title = "MUSEUMS" }); + + // START MultipleCrossReferences + // highlight-start + // Add multiple references - need to add them individually + foreach (var tempUuid in new[] { categoryObjId, categoryObjIdAlt }) + { + await questions.Data.ReferenceAdd( + from: questionObjId, + fromProperty: "hasCategory", + to: tempUuid + ); + } + // highlight-end + // END MultipleCrossReferences + + var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + Assert.NotNull(result); + Assert.True(result.References.ContainsKey("hasCategory")); + Assert.Equal(2, result.References["hasCategory"].Count); + } + + [Fact] + public async Task TestReadCrossRef() + { + await SetupCollections(); + 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( + new { question = "What is H2O?", answer = "Water" }, + references: [new ObjectReference("hasCategory", categoryResult)] + ); + + // START ReadCrossRef + // Include the cross-references in a query response + // highlight-start + var response = await questions.Query.FetchObjects( // Or `Hybrid`, `NearText`, etc. + limit: 2, + returnReferences: [new QueryReference("hasCategory", fields: ["title"])] + ); + // highlight-end + + // Or include cross-references in a single-object retrieval + // highlight-start + var obj = await questions.Query.FetchObjectByID( + questionObjId, + returnReferences: [new QueryReference("hasCategory", fields: ["title"])] + ); + // highlight-end + // END ReadCrossRef + + Assert.NotEmpty(response.Objects); + Assert.NotNull(obj); + 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 categoryObjId = await categories.Data.Insert(new { title = "MUSEUMS" }); + var questionObjId = await questions.Data.Insert( + new { question = "This city is known for the Golden Gate Bridge", answer = "San Francisco" }, + references: [new ObjectReference("hasCategory", categoryObjId)] + ); + + // START DeleteCrossReference + // From the "San Francisco" JeopardyQuestion object, delete the "MUSEUMS" category cross-reference + // highlight-start + await questions.Data.ReferenceDelete( + // highlight-end + from: questionObjId, + fromProperty: "hasCategory", + to: categoryObjId + ); + // END DeleteCrossReference + + var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + Assert.NotNull(result); + Assert.False(result.References.ContainsKey("hasCategory")); + } + + [Fact] + public async Task TestUpdate() + { + await SetupCollections(); + var questions = client.Collections.Use("JeopardyQuestion"); + var categories = client.Collections.Use("JeopardyCategory"); + + var categoryObjId = await categories.Data.Insert(new { title = "MUSEUMS" }); + await categories.Data.Insert(new { title = "U.S. CITIES" }); + var questionObjId = await questions.Data.Insert(new { question = "This city is known for the Golden Gate Bridge", answer = "San Francisco" }); + + // START UpdateCrossReference + // In the "San Francisco" JeopardyQuestion object, set the "hasCategory" cross-reference only to "MUSEUMS" + // highlight-start + await questions.Data.ReferenceReplace( + // highlight-end + from: questionObjId, + fromProperty: "hasCategory", + to: [categoryObjId] + ); + // END UpdateCrossReference + + var result = await questions.Query.FetchObjectByID(questionObjId, returnReferences: [new QueryReference("hasCategory")]); + Assert.NotNull(result); + Assert.True(result.References.ContainsKey("hasCategory")); + Assert.Single(result.References["hasCategory"]); + Assert.Equal(categoryObjId, result.References["hasCategory"][0].ID); + } + + // Helper method to set up collections + private async Task SetupCollections() + { + await client.Collections.Create(new Collection + { + Name = "JeopardyCategory", + Description = "A Jeopardy! category", + Properties = new() { Property.Text("title") } + }); + + await client.Collections.Create(new Collection + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = new() { Property.Text("question"), Property.Text("answer") }, + References = new() { new Reference("hasCategory", "JeopardyCategory") } + }); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs b/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs new file mode 100644 index 000000000..53f48f350 --- /dev/null +++ b/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs @@ -0,0 +1,276 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Collections.Generic; +using System.Text.Json; + +namespace WeaviateProject.Tests; + +public class ManageCollectionsMigrateDataTest : IAsyncLifetime +{ + private static WeaviateClient clientSrc; + private static WeaviateClient clientTgt; + private const int DATASET_SIZE = 50; + + // Runs once before any tests in the class + public async Task InitializeAsync() + { + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY"); + + // 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 + { + RestAddress = "localhost", + RestPort = 8090, + GrpcPort = 50061, + }); + + // Simulate weaviate-datasets by creating and populating source collections + await CreateCollection(clientSrc, "WineReview", false); + await CreateCollection(clientSrc, "WineReviewMT", true); + + var wineReview = clientSrc.Collections.Use("WineReview"); + var wineReviewData = Enumerable.Range(0, DATASET_SIZE) + .Select(i => new { title = $"Review {i}" }) + .ToArray(); + await wineReview.Data.InsertMany(wineReviewData); + + var wineReviewMT = clientSrc.Collections.Use("WineReviewMT"); + await wineReviewMT.Tenants.Add(new Tenant { Name = "tenantA" }); + await wineReviewMT.WithTenant("tenantA").Data.InsertMany(wineReviewData); + } + + // Runs once after all tests in the class + public async Task DisposeAsync() + { + // Clean up collections on both clients + await clientSrc.Collections.DeleteAll(); + await clientTgt.Collections.DeleteAll(); + } + + // START CreateCollectionCollectionToCollection + // START CreateCollectionCollectionToTenant + // START CreateCollectionTenantToCollection + // START CreateCollectionTenantToTenant + private static async Task> CreateCollection(WeaviateClient clientIn, + string collectionName, bool enableMt) + // END CreateCollectionCollectionToCollection + // END CreateCollectionCollectionToTenant + // END CreateCollectionTenantToCollection + // END CreateCollectionTenantToTenant + { + if (await clientIn.Collections.Exists(collectionName)) + { + await clientIn.Collections.Delete(collectionName); + } + // START CreateCollectionCollectionToCollection + // START CreateCollectionCollectionToTenant + // START CreateCollectionTenantToCollection + // START CreateCollectionTenantToTenant + return await clientIn.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = enableMt }, + // Additional settings not shown + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()), + GenerativeConfig = new Generative.CohereConfig(), + Properties = new() + { + Property.Text("review_body"), + Property.Text("title"), + Property.Text("country"), + Property.Int("points"), + Property.Number("price") + } + }); + } + // END CreateCollectionCollectionToCollection + // END CreateCollectionCollectionToTenant + // END CreateCollectionTenantToCollection + // END CreateCollectionTenantToTenant + + // START CollectionToCollection + // START TenantToCollection + // START CollectionToTenant + // START TenantToTenant + private async Task MigrateData(CollectionClient collectionSrc, + CollectionClient collectionTgt) + { + 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); + + foreach (var obj in response.Objects) + { + sourceObjects.Add(new WeaviateObject + { + ID = obj.ID, + // FIX 2: Cast the dynamic properties to a supported dictionary type + Properties = (IDictionary)obj.Properties, + Vectors = obj.Vectors + }); + } + + // FIX 3 (from previous advice): Convert the List to an Array before inserting + await collectionTgt.Data.InsertMany(sourceObjects.ToArray()); + + Console.WriteLine("Data migration complete."); + } + // END CollectionToCollection + // END TenantToCollection + // END CollectionToTenant + // END TenantToTenant + + private async Task VerifyMigration(CollectionClient collectionSrc, + CollectionClient collectionTgt, int numSamples) + { + // FIX 1: Use FetchObjects instead of Iterator + var srcResponse = await collectionSrc.Query.FetchObjects(limit: 10000); + var srcObjects = srcResponse.Objects; + + if (!srcObjects.Any()) + { + Console.WriteLine("No objects in source collection"); + 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 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("All sampled objects verified successfully!"); + return true; + } + + // START CreateCollectionCollectionToCollection + private async Task CreateCollectionToCollection() + { + await CreateCollection(clientTgt, "WineReview", false); + } + // END CreateCollectionCollectionToCollection + + [Fact] + // START CollectionToCollection + public async Task TestCollectionToCollection() + { + await CreateCollectionToCollection(); + + var reviewsSrc = clientSrc.Collections.Use("WineReview"); + var reviewsTgt = clientTgt.Collections.Use("WineReview"); + await MigrateData(reviewsSrc, reviewsTgt); + // END CollectionToCollection + + Assert.Equal(DATASET_SIZE, (await reviewsTgt.Aggregate.OverAll(totalCount: true)).TotalCount); + Assert.True(await VerifyMigration(reviewsSrc, reviewsTgt, 5)); + } + + // START CreateCollectionTenantToCollection + private async Task CreateTenantToCollection() + { + await CreateCollection(clientTgt, "WineReview", false); + } + // END CreateCollectionTenantToCollection + + [Fact] + // START TenantToCollection + public async Task TestTenantToCollection() + { + await CreateTenantToCollection(); + + 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)); + } + + // START CreateCollectionCollectionToTenant + private async Task CreateCollectionToTenant() + { + await CreateCollection(clientTgt, "WineReviewMT", true); + } + // END CreateCollectionCollectionToTenant + + // START CreateTenants + // START CreateCollectionTenantToTenant + private async Task CreateTenants() + { + 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 + + [Fact] + // START CollectionToTenant + public async Task TestCollectionToTenant() + { + await CreateCollectionToTenant(); + await CreateTenants(); + + var reviewsMtTgt = clientTgt.Collections.Use("WineReviewMT"); + var reviewsSrc = clientSrc.Collections.Use("WineReview"); + + var reviewsTgtTenantA = reviewsMtTgt.WithTenant("tenantA"); + + await MigrateData(reviewsSrc, reviewsTgtTenantA); + // END CollectionToTenant + + Assert.Equal(DATASET_SIZE, (await reviewsTgtTenantA.Aggregate.OverAll(totalCount: true)).TotalCount); + Assert.True(await VerifyMigration(reviewsSrc, reviewsTgtTenantA, 5)); + } + + // START CreateCollectionTenantToTenant + private async Task CreateTenantToTenant() + { + await CreateCollection(clientTgt, "WineReviewMT", true); + } + // END CreateCollectionTenantToTenant + + [Fact] + // START TenantToTenant + public async Task TestTenantToTenant() + { + await CreateTenantToTenant(); + await CreateTenants(); + + 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); + // END TenantToTenant + + Assert.Equal(DATASET_SIZE, (await reviewsTgtTenantA.Aggregate.OverAll(totalCount: true)).TotalCount); + Assert.True(await VerifyMigration(reviewsSrcTenantA, reviewsTgtTenantA, 5)); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageCollectionsTest.cs b/_includes/code/csharp/ManageCollectionsTest.cs new file mode 100644 index 000000000..c375a1cf9 --- /dev/null +++ b/_includes/code/csharp/ManageCollectionsTest.cs @@ -0,0 +1,692 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; + +// 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 +// that could otherwise interfere with each other. +[Collection("Sequential")] +public class ManageCollectionsTest : IAsyncLifetime +{ + private static readonly WeaviateClient client; + + // Static constructor acts like JUnit's @BeforeAll + static ManageCollectionsTest() + { + // Instantiate the client with the OpenAI API key + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + if (string.IsNullOrWhiteSpace(openaiApiKey)) + { + 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); + } + + // InitializeAsync is called before each test. We ensure all collections are deleted. + public async Task InitializeAsync() + { + await client.Collections.DeleteAll(); + } + + // DisposeAsync acts like JUnit's @AfterEach, cleaning up after every test. + public Task DisposeAsync() + { + // No action needed here since cleanup happens in InitializeAsync before the next test. + return Task.CompletedTask; + } + + [Fact] + public async Task TestBasicCreateCollection() + { + // START BasicCreateCollection + await client.Collections.Create(new Collection { Name = "Article" }); + // END BasicCreateCollection + + bool exists = await client.Collections.Exists("Article"); + Assert.True(exists); + } + + [Fact] + public async Task TestCreateCollectionWithProperties() + { + // START CreateCollectionWithProperties + await client.Collections.Create(new Collection + { + Name = "Article", + Properties = + [ + Property.Text("title"), + Property.Text("body"), + ] + }); + // END CreateCollectionWithProperties + + var config = await client.Collections.Export("Article"); + Assert.Equal(2, config.Properties.Count); + } + + [Fact] + public async Task TestAddProperties() + { + await client.Collections.Create(new Collection + { + Name = "Article", + Properties = + [ + Property.Text("title"), + Property.Text("body"), + ] + }); + + // START AddProperty + CollectionClient articles = client.Collections.Use("Article"); + await articles.Config.AddProperty(Property.Text("description")); + // END AddProperty + + var config = await client.Collections.Export("Article"); + Assert.Contains(config.Properties, p => p.Name == "description"); + } + + [Fact] + public async Task TestCreateCollectionWithVectorizer() + { + // START Vectorizer + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }, + Properties = + [ + Property.Text("title"), + Property.Text("body"), + ] + }); + // END Vectorizer + + var config = await client.Collections.Export("Article"); + Assert.True(config.VectorConfig.ContainsKey("default")); + Assert.Equal("text2vec-contextionary", config.VectorConfig["default"].Vectorizer.Identifier); + } + + [Fact] + public async Task TestCreateCollectionWithNamedVectors() + { + // START BasicNamedVectors + await client.Collections.Create(new Collection + { + Name = "ArticleNV", + VectorConfig = new VectorConfigList + { + new VectorConfig("title", new Vectorizer.Text2VecContextionary + { + SourceProperties = ["title"] + }, new VectorIndex.HNSW()), + new VectorConfig("title_country", new Vectorizer.Text2VecContextionary + { + SourceProperties = ["title", "country"] + }, new VectorIndex.HNSW()), + new VectorConfig("custom_vector", new Vectorizer.SelfProvided(), new VectorIndex.HNSW()) + }, + Properties = + [ + Property.Text("title"), + Property.Text("country"), + ] + }); + // END BasicNamedVectors + + var config = await client.Collections.Export("ArticleNV"); + Assert.Equal(3, config.VectorConfig.Count); + Assert.Contains("title", config.VectorConfig.Keys); + Assert.Contains("title_country", config.VectorConfig.Keys); + Assert.Contains("custom_vector", config.VectorConfig.Keys); + Assert.Contains(config.Properties, p => p.Name == "title"); + Assert.Contains(config.Properties, p => p.Name == "country"); + } + + [Fact] + public async Task ConfigurePropertyModuleSettings() + { + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }, + Properties = + [ + Property.Text("title"), + Property.Text("body"), + ] + }); + var articles = client.Collections.Use("Article"); + // START AddNamedVectors + await articles.Config.AddVector(Configure.Vectors.Text2VecCohere().New("body_vector", sourceProperties: "body")); + // END AddNamedVectors + + Collection config = await client.Collections.Export("Article"); + Assert.Equal(2, config.VectorConfig.Count); + //Assert.NotNull(config.VectorConfig["body_vector"]); + } + + // START AddNamedVectors + // Coming soon + // END AddNamedVectors + + // TODO[g-despot] {"error":[{"message":"parse vector index config: parse vector config for jina_colbert: multi vector index configured but vectorizer: \"text2vec-jinaai\" doesn't support multi vectors"}]} + [Fact] + public async Task CreateCollectionWithMultiVectors() + { + // START MultiValueVectorCollection + await client.Collections.Create(new Collection + { + Name = "DemoCollection", + VectorConfig = new VectorConfigList + { + // The factory function will automatically enable multi-vector support for the HNSW index + // Configure.MultiVectors.Text2VecJinaAI().New("jina_colbert"), + // Must explicitly enable multi-vector support for the HNSW index + Configure.MultiVectors.SelfProvided("custom_multi_vector"), + }, + Properties = new List { Property.Text("text") }, + }); + // END MultiValueVectorCollection + + // Assert + Collection config = await client.Collections.Export("DemoCollection"); + Assert.True(config.VectorConfig.ContainsKey("jina_colbert")); + Assert.True(config.VectorConfig.ContainsKey("custom_multi_vector")); + } + + // START MultiValueVectorCollection + // Coming soon + // END MultiValueVectorCollection + + [Fact] + public async Task TestSetVectorIndexType() + { + // START SetVectorIndexType + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", + new Vectorizer.Text2VecContextionary(), + new VectorIndex.HNSW() + ) + }, + Properties = + [ + Property.Text("title"), + Property.Text("body"), + ] + }); + // END SetVectorIndexType + + var config = await client.Collections.Export("Article"); + Assert.IsType(config.VectorConfig["default"].VectorIndexConfig); + } + + [Fact] + public async Task TestSetVectorIndexParams() + { + // START SetVectorIndexParams + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", + new Vectorizer.Text2VecContextionary(), + new VectorIndex.HNSW + { + EfConstruction = 300, + Distance = VectorIndexConfig.VectorDistance.Cosine + } + ) + }, + Properties = + [ + Property.Text("title") + ] + }); + // END SetVectorIndexParams + + var config = await client.Collections.Export("Article"); + var hnswConfig = Assert.IsType(config.VectorConfig["default"].VectorIndexConfig); + Assert.Equal(300, hnswConfig.EfConstruction); + Assert.Equal(VectorIndexConfig.VectorDistance.Cosine, hnswConfig.Distance); + } + + [Fact] + public async Task TestSetInvertedIndexParams() + { + // START SetInvertedIndexParams + await client.Collections.Create(new Collection + { + Name = "Article", + Properties = + [ + Property.Text("title", indexFilterable: true, indexSearchable: true), + Property.Text("chunk", indexFilterable: true, indexSearchable: true), + Property.Int("chunk_number", indexRangeFilters: true), + ], + InvertedIndexConfig = new InvertedIndexConfig + { + Bm25 = new BM25Config { B = 1, K1 = 2 }, + IndexNullState = true, + IndexPropertyLength = true, + IndexTimestamps = true, + } + }); + // END SetInvertedIndexParams + + var config = await client.Collections.Export("Article"); + Assert.Equal(1, config.InvertedIndexConfig.Bm25.B); + Assert.Equal(2, config.InvertedIndexConfig.Bm25.K1); + Assert.Equal(3, config.Properties.Count); + } + + [Fact] + public async Task TestSetReranker() + { + // START SetReranker + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }, + RerankerConfig = new Reranker.Cohere(), + Properties = + [ + Property.Text("title") + ] + }); + // END SetReranker + + var config = await client.Collections.Export("Article"); + Assert.NotNull(config.RerankerConfig); + Assert.Equal("reranker-cohere", (config.RerankerConfig as Reranker.Cohere)?.Type); + } + + [Fact] + public async Task TestUpdateReranker() + { + await client.Collections.Create(new Collection { Name = "Article" }); + + // START UpdateReranker + var collection = client.Collections.Use("Article"); + await collection.Config.Update(c => + { + c.RerankerConfig = new Reranker.Cohere(); + }); + // END UpdateReranker + + var config = await client.Collections.Export("Article"); + Assert.NotNull(config.RerankerConfig); + Assert.Equal("reranker-cohere", (config.RerankerConfig as Reranker.Cohere)?.Type); + } + + [Fact] + public async Task TestSetGenerative() + { + // START SetGenerative + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }, + GenerativeConfig = new Generative.CohereConfig(), + Properties = + [ + Property.Text("title") + ] + }); + // END SetGenerative + + var config = await client.Collections.Export("Article"); + Assert.NotNull(config.GenerativeConfig); + Assert.Equal("generative-cohere", (config.GenerativeConfig as Generative.CohereConfig)?.Type); + } + + [Fact] + public async Task TestUpdateGenerative() + { + await client.Collections.Create(new Collection { Name = "Article" }); + + // START UpdateGenerative + var collection = client.Collections.Use("Article"); + await collection.Config.Update(c => + { + c.GenerativeConfig = new Generative.CohereConfig(); + }); + // END UpdateGenerative + + var config = await client.Collections.Export("Article"); + Assert.NotNull(config.GenerativeConfig); + Assert.Equal("generative-cohere", (config.GenerativeConfig as Generative.CohereConfig)?.Type); + } + + // START ModuleSettings + // Coming soon + // END ModuleSettings + + // TODO[g-despot]: Missing vectorizePropertyName + [Fact] + public async Task TestCreateCollectionWithPropertyConfig() + { + // START PropModuleSettings + await client.Collections.Create(new Collection + { + Name = "Article", + Properties = new List + { + Property.Text( + "title", + // vectorizePropertyName: true, + tokenization: PropertyTokenization.Lowercase + ), + Property.Text( + "body", + // skipVectorization: true, + tokenization: PropertyTokenization.Whitespace + ), + }, + }); + // END PropModuleSettings + + var config = await client.Collections.Export("Article"); + Assert.Equal(2, config.Properties.Count); + } + + [Fact] + public async Task TestCreateCollectionWithTrigramTokenization() + { + // START TrigramTokenization + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }, + Properties = + [ + Property.Text("title", tokenization: PropertyTokenization.Trigram) + ] + }); + // END TrigramTokenization + + var config = await client.Collections.Export("Article"); + Assert.Single(config.Properties); + Assert.Equal(PropertyTokenization.Trigram, config.Properties.First().PropertyTokenization); + } + + [Fact] + public async Task TestDistanceMetric() + { + // START DistanceMetric + await client.Collections.Create(new Collection + { + Name = "Article", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", + new Vectorizer.Text2VecContextionary(), + new VectorIndex.HNSW + { + Distance = VectorIndexConfig.VectorDistance.Cosine + } + ) + }, + Properties = + [ + Property.Text("title") + ] + }); + // END DistanceMetric + + var config = await client.Collections.Export("Article"); + var hnswConfig = Assert.IsType(config.VectorConfig["default"].VectorIndexConfig); + Assert.Equal(VectorIndexConfig.VectorDistance.Cosine, hnswConfig.Distance); + } + + [Fact] + public async Task TestReplicationSettings() + { + // START ReplicationSettings + await client.Collections.Create(new Collection + { + Name = "Article", + ReplicationConfig = new ReplicationConfig { Factor = 1 } + }); + // END ReplicationSettings + + var config = await client.Collections.Export("Article"); + Assert.Equal(1, config.ReplicationConfig.Factor); + } + + [Fact] + public async Task TestAsyncRepair() + { + // START AsyncRepair + await client.Collections.Create(new Collection + { + Name = "Article", + ReplicationConfig = new ReplicationConfig + { + Factor = 1, + AsyncEnabled = true + } + }); + // END AsyncRepair + + var config = await client.Collections.Export("Article"); + Assert.True(config.ReplicationConfig.AsyncEnabled); + } + + [Fact] + public async Task TestAllReplicationSettings() + { + // START AllReplicationSettings + await client.Collections.Create(new Collection + { + Name = "Article", + ReplicationConfig = new ReplicationConfig + { + Factor = 1, + AsyncEnabled = true, + DeletionStrategy = DeletionStrategy.TimeBasedResolution, + }, + }); + // END AllReplicationSettings + + var config = await client.Collections.Export("Article"); + Assert.Equal(1, config.ReplicationConfig.Factor); + Assert.True(config.ReplicationConfig.AsyncEnabled); + } + + [Fact] + public async Task TestShardingSettings() + { + // START ShardingSettings + await client.Collections.Create(new Collection + { + Name = "Article", + ShardingConfig = new ShardingConfig + { + VirtualPerPhysical = 128, + DesiredCount = 1, + DesiredVirtualCount = 128, + } + }); + // END ShardingSettings + + var config = await client.Collections.Export("Article"); + Assert.Equal(128, config.ShardingConfig.VirtualPerPhysical); + Assert.Equal(1, config.ShardingConfig.DesiredCount); + Assert.Equal(128, config.ShardingConfig.DesiredVirtualCount); + } + + [Fact] + public async Task TestMultiTenancy() + { + // START Multi-tenancy + await client.Collections.Create(new Collection + { + Name = "Article", + MultiTenancyConfig = new MultiTenancyConfig + { + Enabled = true, + AutoTenantCreation = true, + AutoTenantActivation = true + } + }); + // END Multi-tenancy + + var config = await client.Collections.Export("Article"); + Assert.True(config.MultiTenancyConfig.AutoTenantActivation); + } + + [Fact] + public async Task TestReadOneCollection() + { + await client.Collections.Create(new Collection { Name = "Article" }); + + // START ReadOneCollection + var articles = client.Collections.Use("Article"); + var articlesConfig = await articles.Get(); + + Console.WriteLine(articlesConfig); + // END ReadOneCollection + + Assert.NotNull(articlesConfig); + Assert.Equal("Article", articlesConfig.Name); + } + + [Fact] + public async Task TestReadAllCollections() + { + await client.Collections.Create(new Collection { Name = "Article" }); + await client.Collections.Create(new Collection { Name = "Publication" }); + + // START ReadAllCollections + var response = new List(); + await foreach (var collection in client.Collections.List()) + { + response.Add(collection); + Console.WriteLine(collection); + } + // END ReadAllCollections + + Assert.Equal(2, response.Count); + Assert.Contains(response, c => c.Name == "Article"); + Assert.Contains(response, c => c.Name == "Publication"); + } + + [Fact] + public async Task TestUpdateCollection() + { + await client.Collections.Create(new Collection + { + Name = "Article", + InvertedIndexConfig = new InvertedIndexConfig + { + Bm25 = new BM25Config { K1 = 10 } + } + }); + + // START UpdateCollection + var articles = client.Collections.Use("Article"); + + await articles.Config.Update(c => + { + c.Description = "An updated collection description."; + c.InvertedIndexConfig.Bm25.K1 = 1.5f; + }); + // END UpdateCollection + + var config = await articles.Get(); + Assert.Equal("An updated collection description.", config.Description); + Assert.Equal(1.5f, config.InvertedIndexConfig.Bm25.K1); + } + + // START AddProperty + // Coming soon + // END AddProperty + + [Fact] + public async Task TestDeleteCollection() + { + string collectionName = "Article"; + await client.Collections.Create(new Collection { Name = collectionName }); + Assert.True(await client.Collections.Exists(collectionName)); + + // START DeleteCollection + await client.Collections.Delete(collectionName); + // END DeleteCollection + + Assert.False(await client.Collections.Exists(collectionName)); + } + + // [Fact] + // public async Task TestInspectCollectionShards() + // { + // await client.Collections.Create(new Collection { Name = "Article" }); + + // var articles = client.Collections.Use("Article"); + + // var articleShards = await articles.Shards.Get(); + // Console.WriteLine(string.Join(", ", articleShards.Select(s => s.Name))); + + // Assert.NotNull(articleShards); + // Assert.Single(articleShards); + // } + // START InspectCollectionShards + // Coming soon + // END InspectCollectionShards + + // [Fact] + // public async Task TestUpdateCollectionShards() + // { + // await client.Collections.Create(new Collection { Name = "Article" }); + // var initialShards = await client.Collections.Use("Article").Shards.Get(); + // var shardName = initialShards.First().Name; + + // var articles = client.Collections.Use("Article"); + + // var articleShards = await articles.Shards.Update(shardName, "READONLY"); + // Console.WriteLine(string.Join(", ", articleShards.Select(s => s.Status))); + + // Assert.NotNull(articleShards); + // Assert.Single(articleShards); + // Assert.Equal("READONLY", articleShards.First().Status); + // } + // START UpdateCollectionShards + // Coming soon + // END UpdateCollectionShards + + +} diff --git a/_includes/code/csharp/ManageObjectsCreateTest.cs b/_includes/code/csharp/ManageObjectsCreateTest.cs new file mode 100644 index 000000000..1fc05cd17 --- /dev/null +++ b/_includes/code/csharp/ManageObjectsCreateTest.cs @@ -0,0 +1,307 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; + +namespace WeaviateProject.Tests; + +[Collection("Sequential")] +public class ManageObjectsCreateTest : IAsyncLifetime +{ + private static readonly WeaviateClient client; + + // A helper method to generate a deterministic UUID v5 from a seed. + private static Guid GenerateUuid5(string seed) + { + // Namespace for UUIDv5, can be any valid Guid. + var namespaceId = Guid.Parse("00000000-0000-0000-0000-000000000000"); + + var namespaceBytes = namespaceId.ToByteArray(); + var nameBytes = Encoding.UTF8.GetBytes(seed); + + // Concatenate namespace and name bytes + var combinedBytes = new byte[namespaceBytes.Length + nameBytes.Length]; + Buffer.BlockCopy(namespaceBytes, 0, combinedBytes, 0, namespaceBytes.Length); + Buffer.BlockCopy(nameBytes, 0, combinedBytes, namespaceBytes.Length, nameBytes.Length); + + using (var sha1 = SHA1.Create()) + { + var hash = sha1.ComputeHash(combinedBytes); + var newGuid = new byte[16]; + Array.Copy(hash, 0, newGuid, 0, 16); + + // Set version to 5 + newGuid[6] = (byte)((newGuid[6] & 0x0F) | (5 << 4)); + // Set variant to RFC 4122 + newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); + + // In-place byte swap for correct Guid constructor order + (newGuid[0], newGuid[3]) = (newGuid[3], newGuid[0]); + (newGuid[1], newGuid[2]) = (newGuid[2], newGuid[1]); + (newGuid[4], newGuid[5]) = (newGuid[5], newGuid[4]); + (newGuid[6], newGuid[7]) = (newGuid[7], newGuid[6]); + + return new Guid(newGuid); + } + } + + // Static constructor acts like JUnit's @BeforeAll for one-time setup. + static ManageObjectsCreateTest() + { + // START INSTANTIATION-COMMON + client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + // END INSTANTIATION-COMMON + } + + // InitializeAsync runs once before the class tests start. + public async Task InitializeAsync() + { + await client.Collections.DeleteAll(); // Clean slate before tests + + // START Define the class + await client.Collections.Create(new Collection + { + Name = "JeopardyQuestion", + Properties = + [ + Property.Text("title", description: "Question title") + ], + VectorConfig = new VectorConfigList + { + new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + } + }); + + await client.Collections.Create(new Collection + { + Name = "WineReviewNV", + Properties = + [ + Property.Text("review_body", description: "Review body"), + Property.Text("title", description: "Name of the wine"), + Property.Text("country", description: "Originating country") + ], + VectorConfig = new VectorConfigList + { + new VectorConfig("title", new Vectorizer.Text2VecContextionary()), + new VectorConfig("review_body", new Vectorizer.Text2VecContextionary()), + new VectorConfig("title_country", new Vectorizer.Text2VecContextionary()) + } + }); + // END Define the class + + // Additional collections for other tests + await client.Collections.Create(new Collection + { + Name = "Publication", + Properties = + [ + Property.GeoCoordinate("headquartersGeoLocation") + ] + }); + await client.Collections.Create(new Collection + { + Name = "Author", + VectorConfig = new VectorConfigList + { + new VectorConfig("default", new Vectorizer.SelfProvided()) + } + }); + } + + // DisposeAsync acts like JUnit's @AfterAll for one-time teardown. + public async Task DisposeAsync() + { + await client.Collections.DeleteAll(); + } + + [Fact] + public async Task TestCreateObject() + { + // START CreateSimpleObject + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + // highlight-start + var uuid = await jeopardy.Data.Insert(new + { + // highlight-end + question = "This vector DB is OSS & supports automatic property type inference on import", + // answer = "Weaviate", // properties can be omitted + newProperty = 123 // will be automatically added as a number property + }); + + Console.WriteLine(uuid); // the return value is the object's UUID + // END CreateSimpleObject + + var result = await jeopardy.Query.FetchObjectByID(uuid); + Assert.NotNull(result); + var props = result.Properties as IDictionary; + Assert.Equal(123d, props["newProperty"]); + } + + [Fact] + public async Task TestCreateObjectWithVector() + { + // START CreateObjectWithVector + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var uuid = await jeopardy.Data.Insert( + new + { + question = "This vector DB is OSS and supports automatic property type inference on import", + answer = "Weaviate" + }, + // highlight-start + vectors: new float[300] // Using a zero vector for demonstration + ); + // highlight-end + + Console.WriteLine(uuid); // the return value is the object's UUID + // END CreateObjectWithVector + + var result = await jeopardy.Query.FetchObjectByID(uuid); + Assert.NotNull(result); + } + + [Fact] + public async Task TestCreateObjectNamedVectors() + { + // START CreateObjectNamedVectors + var reviews = client.Collections.Use("WineReviewNV"); // This collection must have named vectors configured + var uuid = await reviews.Data.Insert( + new + { + title = "A delicious Riesling", + review_body = "This wine is a delicious Riesling which pairs well with seafood.", + country = "Germany" + }, + // highlight-start + // Specify the named vectors, following the collection definition + vectors: new Vectors + { + { "title", new float[1536] }, + { "review_body", new float[1536] }, + { "title_country", new float[1536] } + } + ); + // highlight-end + + Console.WriteLine(uuid); // the return value is the object's UUID + // END CreateObjectNamedVectors + + var result = await reviews.Query.FetchObjectByID(uuid, returnMetadata: MetadataOptions.Vector); + Assert.NotNull(result); + Assert.NotNull(result.Vectors); + Assert.Contains("title", result.Vectors.Keys); + Assert.Contains("review_body", result.Vectors.Keys); + Assert.Contains("title_country", result.Vectors.Keys); + } + + [Fact] + public async Task TestCreateObjectWithDeterministicId() + { + // START CreateObjectWithDeterministicId + // highlight-start + // In C#, you can generate a deterministic UUID from a string or bytes. + // This helper function creates a UUID v5 for this purpose. + // highlight-end + + var dataObject = new + { + question = "This vector DB is OSS and supports automatic property type inference on import", + answer = "Weaviate" + }; + var dataObjectString = JsonSerializer.Serialize(dataObject); + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var uuid = await jeopardy.Data.Insert( + dataObject, + // highlight-start + id: GenerateUuid5(dataObjectString) + ); + // highlight-end + // END CreateObjectWithDeterministicId + + Assert.Equal(GenerateUuid5(dataObjectString), uuid); + await jeopardy.Data.DeleteByID(uuid); // Clean up + } + + [Fact] + public async Task TestCreateObjectWithId() + { + // START CreateObjectWithId + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var uuid = await jeopardy.Data.Insert( + new + { + question = "This vector DB is OSS and supports automatic property type inference on import", + answer = "Weaviate" + }, + // highlight-start + id: Guid.Parse("12345678-e64f-5d94-90db-c8cfa3fc1234") + ); + // highlight-end + + Console.WriteLine(uuid); // the return value is the object's UUID + // END CreateObjectWithId + + var result = await jeopardy.Query.FetchObjectByID(uuid); + Assert.NotNull(result); + var props = result.Properties as IDictionary; + Assert.Equal("This vector DB is OSS and supports automatic property type inference on import", props["question"]); + } + + [Fact] + public async Task TestWithGeoCoordinates() + { + // START WithGeoCoordinates + var publications = client.Collections.Use("Publication"); + + var uuid = await publications.Data.Insert( + new + { + headquartersGeoLocation = new GeoCoordinate(52.3932696f, 4.8374263f) + } + ); + // END WithGeoCoordinates + + var result = await publications.Query.FetchObjectByID(uuid); + Assert.NotNull(result); + await publications.Data.DeleteByID(uuid); + } + + [Fact] + public async Task TestCheckForAnObject() + { + // START CheckForAnObject + // generate uuid based on the key properties used during data insert + var objectUuid = GenerateUuid5("Author to fetch"); + // END CheckForAnObject + + var authors = client.Collections.Use("Author"); + await authors.Data.Insert( + new { name = "Author to fetch" }, + id: objectUuid, + vectors: new float[1536]); + + // START CheckForAnObject + // highlight-start + var authorExists = (await authors.Query.FetchObjectByID(objectUuid)) != null; + // highlight-end + + Console.WriteLine("Author exists: " + authorExists); + // END CheckForAnObject + + Assert.True(authorExists); + await authors.Data.DeleteByID(objectUuid); + Assert.False((await authors.Query.FetchObjectByID(objectUuid)) != null); + } + + // START ValidateObject + // Coming soon + // END ValidateObject +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageObjectsDeleteTest.cs b/_includes/code/csharp/ManageObjectsDeleteTest.cs new file mode 100644 index 000000000..c506f8200 --- /dev/null +++ b/_includes/code/csharp/ManageObjectsDeleteTest.cs @@ -0,0 +1,156 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Text.Json; + +namespace WeaviateProject.Tests; + +public class ManageObjectsDeleteTest : IAsyncLifetime +{ + private static readonly WeaviateClient client; + private const string COLLECTION_NAME = "EphemeralObject"; + + // 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 }); + } + + // Runs before each test (like @BeforeEach) + public async Task InitializeAsync() + { + if (await client.Collections.Exists(COLLECTION_NAME)) + { + await client.Collections.Delete(COLLECTION_NAME); + } + await client.Collections.Create(new Collection + { + Name = COLLECTION_NAME, + Properties = new[] { Property.Text("name") }.ToList() + }); + } + + // Runs after each test (like @AfterEach and @AfterAll) + public async Task DisposeAsync() + { + if (await client.Collections.Exists(COLLECTION_NAME)) + { + await client.Collections.Delete(COLLECTION_NAME); + } + } + + [Fact] + public async Task TestDeleteObject() + { + var collection = client.Collections.Use(COLLECTION_NAME); + var uuidToDelete = await collection.Data.Insert(new { name = "EphemeralObjectA" }); + Assert.NotNull(await collection.Query.FetchObjectByID(uuidToDelete)); + + // START DeleteObject + await collection.Data.DeleteByID(uuidToDelete); + // END DeleteObject + + Assert.Null(await collection.Query.FetchObjectByID(uuidToDelete)); + } + + [Fact] + public async Task TestBatchDelete() + { + var collection = client.Collections.Use(COLLECTION_NAME); + var objects = Enumerable.Range(0, 5) + .Select(i => new { name = $"EphemeralObject_{i}" }) + .ToArray(); // Creates an array T[] + + await collection.Data.InsertMany(objects); + var initialCount = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(5, initialCount.TotalCount); + + // START DeleteBatch + await collection.Data.DeleteMany( + // highlight-start + Filter.Property("name").Like("EphemeralObject*") + // highlight-end + ); + // END DeleteBatch + + var finalCount = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(0, finalCount.TotalCount); + } + + [Fact] + public async Task TestDeleteContains() + { + // START DeleteContains + var collection = client.Collections.Use(COLLECTION_NAME); + await collection.Data.InsertMany(new[] + { + new { name = "asia" }, + new { name = "europe" } + }); + + await collection.Data.DeleteMany( + // highlight-start + Filter.Property("name").ContainsAny(["europe", "asia"]) + // highlight-end + ); + // END DeleteContains + } + + [Fact] + public async Task TestDryRun() + { + var collection = client.Collections.Use(COLLECTION_NAME); + var objects = Enumerable.Range(0, 5) + .Select(i => new { name = $"EphemeralObject_{i}" }) + .ToArray(); // Creates an array T[] + + await collection.Data.InsertMany(objects); + + // START DryRun + var result = await collection.Data.DeleteMany( + Filter.Property("name").Like("EphemeralObject*"), + // highlight-start + dryRun: true + // highlight-end + ); + + Console.WriteLine(JsonSerializer.Serialize(result)); + // END DryRun + + Assert.Equal(5, result.Matches); + var finalCount = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(5, finalCount.TotalCount); // Objects should not be deleted + } + + [Fact] + public async Task TestBatchDeleteWithIDs() + { + var collection = client.Collections.Use(COLLECTION_NAME); + var objects = Enumerable.Range(0, 5) + .Select(i => new { name = $"EphemeralObject_{i}" }) + .ToArray(); // Creates an array T[] + + await collection.Data.InsertMany(objects); + var initialCount = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(5, initialCount.TotalCount); + + // START DeleteByIDBatch + var queryResponse = await collection.Query.FetchObjects(limit: 3); + var ids = queryResponse.Objects.Select(obj => obj.ID.Value).ToList(); + + await collection.Data.DeleteMany( + // highlight-start + Filter.ID.ContainsAny(ids) // Delete the 3 objects + // highlight-end + ); + // END DeleteByIDBatch + + var finalCount = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(2, finalCount.TotalCount); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageObjectsImportTest.cs b/_includes/code/csharp/ManageObjectsImportTest.cs new file mode 100644 index 000000000..fd37584eb --- /dev/null +++ b/_includes/code/csharp/ManageObjectsImportTest.cs @@ -0,0 +1,446 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Text.Json; +using System.Security.Cryptography; +using System.Text; +using System.Net.Http; +using System.Globalization; +using CsvHelper; + +namespace WeaviateProject.Tests; + +[Collection("Sequential")] +public class ManageObjectsImportTest : IAsyncLifetime +{ + private static readonly WeaviateClient client; + private const string JsonDataFile = "jeopardy_1k.json"; + private const string CsvDataFile = "jeopardy_1k.csv"; + + // Static constructor for one-time setup (like @BeforeAll) + static ManageObjectsImportTest() + { + // START INSTANTIATION-COMMON + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + if (string.IsNullOrWhiteSpace(openaiApiKey)) + { + throw new ArgumentException("Please set the OPENAI_API_KEY environment variable."); + } + + // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor. + // This must be configured in Weaviate's environment variables. + client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + // END INSTANTIATION-COMMON + } + + // A helper method to generate a deterministic UUID from a seed + private static Guid GenerateUuid5(string seed) + { + var namespaceId = Guid.Empty; + var namespaceBytes = namespaceId.ToByteArray(); + var nameBytes = Encoding.UTF8.GetBytes(seed); + + var combinedBytes = new byte[namespaceBytes.Length + nameBytes.Length]; + Buffer.BlockCopy(namespaceBytes, 0, combinedBytes, 0, namespaceBytes.Length); + Buffer.BlockCopy(nameBytes, 0, combinedBytes, namespaceBytes.Length, nameBytes.Length); + + using (var sha1 = SHA1.Create()) + { + var hash = sha1.ComputeHash(combinedBytes); + var newGuid = new byte[16]; + Array.Copy(hash, 0, newGuid, 0, 16); + + newGuid[6] = (byte)((newGuid[6] & 0x0F) | (5 << 4)); + newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); + + (newGuid[0], newGuid[3]) = (newGuid[3], newGuid[0]); + (newGuid[1], newGuid[2]) = (newGuid[2], newGuid[1]); + (newGuid[4], newGuid[5]) = (newGuid[5], newGuid[4]); + (newGuid[6], newGuid[7]) = (newGuid[7], newGuid[6]); + + return new Guid(newGuid); + } + } + + // Runs once before any tests in the class + public async Task InitializeAsync() + { + using var httpClient = new HttpClient(); + var jsonData = await httpClient.GetStreamAsync("https://raw.githubusercontent.com/weaviate-tutorials/edu-datasets/main/jeopardy_1k.json"); + using var fileStream = new FileStream(JsonDataFile, FileMode.Create, FileAccess.Write); + await jsonData.CopyToAsync(fileStream); + } + + // Runs once after all tests in the class + public async Task DisposeAsync() + { + await client.Collections.DeleteAll(); + File.Delete(JsonDataFile); + if (File.Exists(CsvDataFile)) File.Delete(CsvDataFile); + } + + private async Task BeforeEach() + { + await client.Collections.DeleteAll(); + } + + [Fact] + public async Task TestBasicBatchImport() + { + await BeforeEach(); + await client.Collections.Create(new Collection + { + Name = "MyCollection", + VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) + }); + + // START BasicBatchImportExample + var dataRows = Enumerable.Range(0, 5).Select(i => new { title = $"Object {i + 1}" }).ToList(); + + var collection = client.Collections.Use("MyCollection"); + + // The Java client uses insertMany for batching. + // There is no direct equivalent of the Python client's stateful batch manager. + // You collect objects and send them in a single request. + // highlight-start + var response = await collection.Data.InsertMany(add => + { + foreach (var dataRow in dataRows) + { + add(dataRow); + } + }); + // highlight-end + + var failedObjects = response.Where(r => r.Error != null).ToList(); + if (failedObjects.Any()) + { + Console.WriteLine($"Number of failed imports: {failedObjects.Count}"); + Console.WriteLine($"First failed object: {failedObjects.First().Error}"); + } + // END BasicBatchImportExample + + var result = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(5, result.TotalCount); + } + + // START ServerSideBatchImportExample + // Coming soon + // END ServerSideBatchImportExample + + [Fact] + public async Task TestBatchImportWithID() + { + await BeforeEach(); + await client.Collections.Create(new Collection + { + Name = "MyCollection", + VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) + }); + + // START BatchImportWithIDExample + var dataToInsert = new List<(object properties, Guid uuid)>(); + for (int i = 0; i < 5; i++) + { + var dataRow = new { title = $"Object {i + 1}" }; + var objUuid = GenerateUuid5(JsonSerializer.Serialize(dataRow)); + dataToInsert.Add((dataRow, objUuid)); + } + + var collection = client.Collections.Use("MyCollection"); + + // highlight-start + var response = await collection.Data.InsertMany(add => + { + foreach (var item in dataToInsert) + { + add(item.properties, item.uuid); + } + }); + // highlight-end + + var failedObjects = response.Where(r => r.Error != null).ToList(); + if (failedObjects.Any()) + { + Console.WriteLine($"Number of failed imports: {failedObjects.Count}"); + Console.WriteLine($"First failed object: {failedObjects.First().Error}"); + } + // END BatchImportWithIDExample + + var result = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(5, result.TotalCount); + var lastUuid = dataToInsert[4].uuid; + Assert.NotNull(await collection.Query.FetchObjectByID(lastUuid)); + } + + [Fact] + public async Task TestBatchImportWithVector() + { + await BeforeEach(); + await client.Collections.Create(new Collection + { + Name = "MyCollection", + VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) + }); + + // START BatchImportWithVectorExample + var dataToInsert = new List<(object properties, Guid uuid, float[] vector)>(); + var vector = Enumerable.Repeat(0.1f, 10).ToArray(); + + for (int i = 0; i < 5; i++) + { + var dataRow = new { title = $"Object {i + 1}" }; + var objUuid = GenerateUuid5(JsonSerializer.Serialize(dataRow)); + dataToInsert.Add((dataRow, objUuid, vector)); + } + + var collection = client.Collections.Use("MyCollection"); + + // highlight-start + var response = await collection.Data.InsertMany(add => + { + foreach (var item in dataToInsert) + { + add(item.properties, item.uuid, item.vector); + } + }); + // highlight-end + + var failedObjects = response.Where(r => r.Error != null).ToList(); + if (failedObjects.Any()) + { + Console.WriteLine($"Number of failed imports: {failedObjects.Count}"); + Console.WriteLine($"First failed object: {failedObjects.First().Error}"); + } + // END BatchImportWithVectorExample + + var result = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(5, result.TotalCount); + } + + [Fact] + public async Task TestBatchImportWithCrossReference() + { + await BeforeEach(); + await client.Collections.Create(new Collection { Name = "Publication", Properties = [Property.Text("title")] }); + await client.Collections.Create(new Collection + { + Name = "Author", + Properties = [Property.Text("name")], + References = [new Reference("writesFor", "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 response = await collection.Data.ReferenceAddMany(new DataReference(fromUuid, "writesFor", targetUuid)); + + if (response.HasErrors) + { + Console.WriteLine($"Number of failed imports: {response.Errors.Count}"); + Console.WriteLine($"First failed object: {response.Errors.First()}"); + } + // END BatchImportWithRefExample + + var result = await collection.Query.FetchObjectByID(fromUuid, returnReferences: [new QueryReference("writesFor")]); + Assert.NotNull(result); + Assert.True(result.References.ContainsKey("writesFor")); + } + + [Fact] + public async Task TestImportWithNamedVectors() + { + await BeforeEach(); + await client.Collections.Create(new Collection + { + Name = "MyCollection", + VectorConfig = new[] + { + 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)>(); + 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)); + // highlight-end + } + + var collection = client.Collections.Use("MyCollection"); + + // Insert the data using InsertMany + // highlight-start + var response = await collection.Data.InsertMany(add => + { + foreach (var item in dataToInsert) + { + add(item.properties, vectors: item.vectors); + } + }); + // highlight-end + + // Check for errors + var failedObjects = response.Where(r => r.Error != null).ToList(); + if (failedObjects.Any()) + { + Console.WriteLine($"Number of failed imports: {failedObjects.Count}"); + Console.WriteLine($"First failed object error: {failedObjects.First().Error}"); + } + // END BatchImportWithNamedVectors + } + + [Fact] + public async Task TestJsonStreaming() + { + await BeforeEach(); + await client.Collections.Create(new Collection { Name = "JeopardyQuestion" }); + + // START JSON streaming + int batchSize = 100; + var batch = new List(batchSize); + 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); + + await foreach (var obj in jsonObjects) + { + if (obj == null) continue; + var properties = new { question = obj["Question"], answer = obj["Answer"] }; + batch.Add(properties); + + if (batch.Count == batchSize) + { + await collection.Data.InsertMany(add => + { + foreach (var item in batch) + { + add(item); + } + }); Console.WriteLine($"Imported {batch.Count} articles..."); + batch.Clear(); + } + } + + if (batch.Any()) + { + await collection.Data.InsertMany(add => + { + foreach (var item in batch) + { + add(item); + } + }); Console.WriteLine($"Imported remaining {batch.Count} articles..."); + } + + Console.WriteLine("Finished importing articles."); + // END JSON streaming + + var result = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(1000, result.TotalCount); + } + + [Fact] + public async Task TestCsvStreaming() + { + await BeforeEach(); + // Create a CSV file from the JSON for the test + using (var fileStream = File.OpenRead(JsonDataFile)) + using (var writer = new StreamWriter(CsvDataFile)) + using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + { + var jsonObjects = JsonSerializer.DeserializeAsyncEnumerable>(fileStream); + csv.WriteHeader(); + await csv.NextRecordAsync(); + await foreach (var obj in jsonObjects) + { + if (obj != null) + { + csv.WriteRecord(new JeopardyQuestion { Question = obj["Question"]?.ToString(), Answer = obj["Answer"]?.ToString() }); + await csv.NextRecordAsync(); + } + } + } + + await client.Collections.Create(new Collection { Name = "JeopardyQuestion" }); + + // START CSV streaming + int batchSize = 100; + var batch = new List(batchSize); + 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)) + using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) + { + var records = csv.GetRecords(); + foreach (var record in records) + { + var properties = new { question = record.Question, answer = record.Answer }; + batch.Add(properties); + + if (batch.Count == batchSize) + { + await collection.Data.InsertMany(add => + { + foreach (var item in batch) + { + add(item); + } + }); Console.WriteLine($"Imported {batch.Count} articles..."); + batch.Clear(); + } + } + } + + if (batch.Any()) + { + await collection.Data.InsertMany(add => + { + foreach (var item in batch) + { + add(item); + } + }); Console.WriteLine($"Imported remaining {batch.Count} articles..."); + } + + Console.WriteLine("Finished importing articles."); + // END CSV streaming + + var result = await collection.Aggregate.OverAll(totalCount: true); + Assert.Equal(1000, result.TotalCount); + } + + // Helper class for CSV parsing + private class JeopardyQuestion + { + public string? Question { get; set; } + public string? Answer { get; set; } + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageObjectsReadAllTest.cs b/_includes/code/csharp/ManageObjectsReadAllTest.cs new file mode 100644 index 000000000..dfbb388bc --- /dev/null +++ b/_includes/code/csharp/ManageObjectsReadAllTest.cs @@ -0,0 +1,125 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text.Json; + +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 }); + } + + // Runs once before any tests in the class + public async Task InitializeAsync() + { + // Simulate weaviate-datasets by creating and populating collections + // Create WineReview collection + if (await client.Collections.Exists("WineReview")) + { + await client.Collections.Delete("WineReview"); + } + + var wineReview = await client.Collections.Create(new Collection { Name = "WineReview" }); + await wineReview.Data.InsertMany(new[] + { + new { title = "Review A" }, + new { title = "Review B" } + }); + + // Create WineReviewMT collection + if (await client.Collections.Exists("WineReviewMT")) + { + await client.Collections.Delete("WineReviewMT"); + } + var wineReviewMT = await client.Collections.Create(new Collection + { + Name = "WineReviewMT", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } + }); + + // Create and populate tenants + await wineReviewMT.Tenants.Add(["tenantA", "tenantB"]); + await wineReviewMT.WithTenant("tenantA").Data.Insert(new { title = "Tenant A Review 1" }); + await wineReviewMT.WithTenant("tenantB").Data.Insert(new { title = "Tenant B Review 1" }); + } + + // Runs once after all tests in the class + public async Task DisposeAsync() + { + await client.Collections.Delete("WineReview"); + await client.Collections.Delete("WineReviewMT"); + } + + [Fact] + public async Task TestReadAllProps() + { + // START ReadAllProps + var collection = client.Collections.Use("WineReview"); + + // highlight-start + await foreach (var item in collection.Iterator()) + { + // highlight-end + Console.WriteLine($"{item.ID} {JsonSerializer.Serialize(item.Properties)}"); + } + // END ReadAllProps + } + + [Fact] + public async Task TestReadAllVectors() + { + // START ReadAllVectors + 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 + )) + // highlight-end + { + Console.WriteLine(JsonSerializer.Serialize(item.Properties)); + // highlight-start + Console.WriteLine(JsonSerializer.Serialize(item.Vectors)); + // highlight-end + } + // 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"); + + // Get a list of tenants + // highlight-start + var tenants = await multiCollection.Tenants.List(); + // highlight-end + + // Iterate through tenants + foreach (var tenant in tenants) + { + // Iterate through objects within each tenant + // highlight-start + var tenantCollection = multiCollection.WithTenant(tenant.Name); + await foreach (var item in tenantCollection.Iterator()) + { + // highlight-end + Console.WriteLine($"{tenant.Name}: {JsonSerializer.Serialize(item.Properties)}"); + } + } + // END ReadAllTenants + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageObjectsReadTest.cs b/_includes/code/csharp/ManageObjectsReadTest.cs new file mode 100644 index 000000000..5ed97582e --- /dev/null +++ b/_includes/code/csharp/ManageObjectsReadTest.cs @@ -0,0 +1,123 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using static Weaviate.Client.Auth; + +namespace WeaviateProject.Tests; + +public class ManageObjectsReadTest : IDisposable +{ + private static readonly WeaviateClient client; + + // Static constructor for one-time setup (like @BeforeAll) + static ManageObjectsReadTest() + { + // START INSTANTIATION-COMMON + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + client = Connect.Cloud(weaviateUrl, weaviateApiKey); + // END INSTANTIATION-COMMON + } + + // Dispose is called once after all tests in the class are finished (like @AfterAll) + public void Dispose() + { + // The C# client does not have a close() method; disposal is handled + // by the garbage collector when the static instance is no longer needed. + GC.SuppressFinalize(this); + } + + [Fact] + public async Task TestReadObject() + { + // START ReadSimpleObject + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + // highlight-start + var dataObject = await jeopardy.Query.FetchObjectByID(Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b")); + // highlight-end + + if (dataObject != null) + { + Console.WriteLine(JsonSerializer.Serialize(dataObject.Properties)); + } + // END ReadSimpleObject + } + + [Fact] + public async Task TestReadObjectWithVector() + { + // START ReadObjectWithVector + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + var dataObject = await jeopardy.Query.FetchObjectByID(Guid.Parse("00ff6900-e64f-5d94-90db-c8cfa3fc851b"), + // highlight-start + returnMetadata: MetadataOptions.Vector + ); + // highlight-end + + if (dataObject?.Vectors.ContainsKey("default") ?? false) + { + var vector = dataObject.Vectors["default"]; + Console.WriteLine(vector); + } + // END ReadObjectWithVector + } + + [Fact] + public async Task TestReadObjectNamedVectors() + { + // START ReadObjectNamedVectors + var reviews = client.Collections.Use("WineReviewNV"); // Collection with named + // END ReadObjectNamedVectors // vectors + + var someObjResponse = await reviews.Query.FetchObjects(limit: 1); + if (!someObjResponse.Objects.Any()) + { + return; // Skip if no data + } + var objUuid = someObjResponse.Objects.First().ID; + var vectorNames = new List { "title", "review_body" }; + + // START ReadObjectNamedVectors + var dataObject = await reviews.Query.FetchObjectByID(objUuid.Value, // Object UUID + // highlight-start + returnMetadata: MetadataOptions.Vector // Specify to include vectors + ); + // highlight-end + + // The vectors are returned in the `Vectors` property as a dictionary + if (dataObject != null) + { + foreach (var name in vectorNames) + { + if (dataObject.Vectors.TryGetValue(name, out var vector)) + { + Console.WriteLine(vector); + } + } + } + // END ReadObjectNamedVectors + } + + [Fact] + public async Task TestCheckObject() + { + // START CheckForAnObject + 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")); + bool exists = dataObject != null; + + Console.WriteLine(exists); + // END CheckForAnObject + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ManageObjectsUpdateTest.cs b/_includes/code/csharp/ManageObjectsUpdateTest.cs new file mode 100644 index 000000000..cc4c885ea --- /dev/null +++ b/_includes/code/csharp/ManageObjectsUpdateTest.cs @@ -0,0 +1,217 @@ +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; + +namespace WeaviateProject.Tests; + +public class ManageObjectsUpdateTest : IAsyncLifetime +{ + private static readonly WeaviateClient client; + + // Static constructor for one-time setup + 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 }); + // END INSTANTIATION-COMMON + } + + // Runs once before any tests in the class (like @BeforeAll) + public async Task InitializeAsync() + { + // Simulate weaviate-datasets and set up collections + if (await client.Collections.Exists("WineReviewNV")) + { + await client.Collections.Delete("WineReviewNV"); + } + await client.Collections.Create(new Collection + { + Name = "WineReviewNV", + Properties = + [ + Property.Text("review_body", description: "Review body"), + Property.Text("title", description: "Name of the wine"), + Property.Text("country", description: "Originating country") + ], + VectorConfig = new[] + { + new VectorConfig("title", new Vectorizer.Text2VecContextionary()), + new VectorConfig("review_body", new Vectorizer.Text2VecContextionary()), + new VectorConfig( + "title_country", + new Vectorizer.Text2VecContextionary { SourceProperties = ["title", "country"] } + ) + } + }); + + // highlight-start + // ===== Add three mock objects to the WineReviewNV collection ===== + var reviews = client.Collections.Use("WineReviewNV"); + await reviews.Data.InsertMany(new[] + { + new { title = "Mock Wine A", review_body = "A fine mock vintage.", country = "Mocktugal" }, + new { title = "Mock Wine B", review_body = "Notes of mockberry.", country = "Mockstralia" }, + new { title = "Mock Wine C", review_body = "Pairs well with mock turtle soup.", country = "Republic of Mockdova" } + }); + // highlight-end + + // START Define the class + if (await client.Collections.Exists("JeopardyQuestion")) + { + await client.Collections.Delete("JeopardyQuestion"); + } + await client.Collections.Create(new Collection + { + Name = "JeopardyQuestion", + Description = "A Jeopardy! question", + Properties = + [ + Property.Text("question", description: "The question"), + Property.Text("answer", description: "The answer"), + Property.Number("points", description: "The points the question is worth") + ], + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()) + }); + // END Define the class + } + + // Runs once after all tests in the class (like @AfterAll) + public async Task DisposeAsync() + { + await client.Collections.Delete("WineReviewNV"); + await client.Collections.Delete("JeopardyQuestion"); + } + + // START DelProps + private static async Task DelProps(WeaviateClient client, Guid uuidToUpdate, string collectionName, + IEnumerable propNames) + { + var collection = client.Collections.Use(collectionName); + + // fetch the object to update + var objectData = await collection.Query.FetchObjectByID(uuidToUpdate); + if (objectData?.Properties is not IDictionary propertiesToUpdate) + { + return; + } + + // remove unwanted properties + foreach (var propName in propNames) + { + propertiesToUpdate.Remove(propName); + } + + // replace the properties + await collection.Data.Replace(uuidToUpdate, propertiesToUpdate); + } + // END DelProps + + [Fact] + public async Task TestUpdateAndReplaceFlow() + { + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + var uuid = await jeopardy.Data.Insert(new + { + question = "Test question", + answer = "Test answer", + points = -1 + }); + + // START UpdateProps + await jeopardy.Data.Replace(uuid, + // highlight-start + data: new { points = 100 } + // highlight-end + ); + // END UpdateProps + + var result1 = await jeopardy.Query.FetchObjectByID(uuid); + Assert.NotNull(result1); + var props1 = result1.Properties as IDictionary; + Assert.Equal(100d, props1["points"]); + + + var vector = Enumerable.Repeat(0.12345f, 300).ToArray(); + + // TODO[g-despot] Not implemented + // START UpdateVector + // Coming soon + // 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); + + + // TODO[g-despot] Not implemented + // START UpdateNamedVector + // Coming soon + // END UpdateNamedVector + + 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 + // ); + + + // START Replace + // highlight-start + await jeopardy.Data.Replace( + // highlight-end + uuid, + data: new { answer = "Replaced" } + // The other properties will be deleted + ); + // END Replace + + var result3 = await jeopardy.Query.FetchObjectByID(uuid); + Assert.NotNull(result3); + var props3 = result3.Properties as IDictionary; + Assert.Equal("Replaced", props3["answer"].ToString()); + Assert.DoesNotContain("question", props3.Keys); + + // START DelProps + + await DelProps(client, uuid, "JeopardyQuestion", new[] { "answer" }); + // END DelProps + + var result4 = await jeopardy.Query.FetchObjectByID(uuid); + Assert.NotNull(result4); + var props4 = result4.Properties as IDictionary; + Assert.False(props4.ContainsKey("answer")); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/ModelProvidersTest.cs b/_includes/code/csharp/ModelProvidersTest.cs new file mode 100644 index 000000000..e69de29bb diff --git a/_includes/code/csharp/QuickstartLocalTest.cs b/_includes/code/csharp/QuickstartLocalTest.cs new file mode 100644 index 000000000..e2a403fb7 --- /dev/null +++ b/_includes/code/csharp/QuickstartLocalTest.cs @@ -0,0 +1,113 @@ +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; +using Xunit; + +namespace WeaviateProject.Examples; + +[Collection("Sequential")] // Ensures tests in this class run one after another +public class QuickstartLocalTest +{ + [Fact] + public async Task TestConnectionIsReady() + { + // START InstantiationExample + using var client = Connect.Local(); + + // highlight-start + // GetMeta returns server info. A successful call indicates readiness. + var meta = await client.GetMeta(); + Console.WriteLine(meta); // Should print: `True` + // highlight-end + + // The 'using' statement handles freeing up resources automatically. + // END InstantiationExample + } + + [Fact] + public async Task FullQuickstartWorkflowTest() + { + using var client = Connect.Local(); + string collectionName = "Question"; + + // Clean up previous runs if they exist + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + + // START CreateCollection + // highlight-start + var questions = await client.Collections.Create(new Collection + { + Name = collectionName, + Properties = new() + { + Property.Text("answer"), + Property.Text("question"), + Property.Text("category") + }, + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecContextionary()), // Configure the text2vec-contextionary integration + GenerativeConfig = new Generative.CohereConfig() // Configure the Cohere generative AI integration + }); + // highlight-end + // END CreateCollection + + // START Import + // Get JSON data using HttpClient + using var httpClient = new HttpClient(); + var jsonData = await httpClient.GetStringAsync("https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json"); + + // highlight-start + var questionsToInsert = new List(); + + // Parse and prepare objects using System.Text.Json + var jsonObjects = JsonSerializer.Deserialize>>(jsonData); + foreach (var jsonObj in jsonObjects) + { + questionsToInsert.Add(new + { + answer = jsonObj["Answer"].GetString(), + question = jsonObj["Question"].GetString(), + category = jsonObj["Category"].GetString() + }); + } + + // Call InsertMany with the list of objects converted to an array + var insertResponse = await questions.Data.InsertMany(questionsToInsert.ToArray()); + // 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."); + // } + + // START NearText + // highlight-start + var response = await questions.Query.NearText("biology", limit: 2); + // highlight-end + + foreach (var obj in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(obj.Properties)); + } + // END NearText + } + + // START RAG + // Coming soon + // END RAG +} \ No newline at end of file diff --git a/_includes/code/csharp/QuickstartTest.cs b/_includes/code/csharp/QuickstartTest.cs new file mode 100644 index 000000000..b1c9f021e --- /dev/null +++ b/_includes/code/csharp/QuickstartTest.cs @@ -0,0 +1,124 @@ +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 Xunit; + +namespace WeaviateProject.Examples; + +[Collection("Sequential")] // Ensures tests in this class run one after another +public class QuickstartTest +{ + // TODO[g-despot] Replace meta with readiness + [Fact] + public static async Task TestConnectionIsReady() + { + // START InstantiationExample + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + WeaviateClient client = Connect.Cloud( + weaviateUrl, + weaviateApiKey + ); + + // highlight-start + // GetMeta returns server info. A successful call indicates readiness. + var meta = await client.GetMeta(); + Console.WriteLine(meta); // Should print: `True` + // highlight-end + // END InstantiationExample + } + + [Fact] + 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 collectionName = "Question"; + + WeaviateClient client = Connect.Cloud( + weaviateUrl, + weaviateApiKey + ); + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + // START CreateCollection + // highlight-start + var questions = await client.Collections.Create(new Collection + { + Name = collectionName, + Properties = new() + { + Property.Text("answer"), + Property.Text("question"), + Property.Text("category") + }, + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), // Configure the Weaviate Embeddings integration + GenerativeConfig = new Generative.CohereConfig() // Configure the Cohere generative AI integration + }); + // highlight-end + // END CreateCollection + + // START Import + // Get JSON data using HttpClient + using var httpClient = new HttpClient(); + var jsonData = await httpClient.GetStringAsync("https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/jeopardy_tiny.json"); + + // highlight-start + var questionsToInsert = new List(); + + // Parse and prepare objects using System.Text.Json + var jsonObjects = JsonSerializer.Deserialize>>(jsonData); + foreach (var jsonObj in jsonObjects) + { + questionsToInsert.Add(new + { + answer = jsonObj["Answer"].GetString(), + question = jsonObj["Question"].GetString(), + category = jsonObj["Category"].GetString() + }); + } + + // Call InsertMany with the list of objects converted to an array + var insertResponse = await questions.Data.InsertMany(questionsToInsert.ToArray()); + // 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."); + // } + + // START NearText + // highlight-start + var response = await questions.Query.NearText("biology", limit: 2); + // highlight-end + + foreach (var obj in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(obj.Properties)); + } + // END NearText + + await client.Collections.Delete(collectionName); + } + + // START RAG + // Coming soon + // END RAG +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchAggregateTest.cs b/_includes/code/csharp/SearchAggregateTest.cs new file mode 100644 index 000000000..34d3f88af --- /dev/null +++ b/_includes/code/csharp/SearchAggregateTest.cs @@ -0,0 +1,194 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Text.Json; + +namespace WeaviateProject.Tests; + +public class SearchAggregateTest : IDisposable +{ + private static readonly WeaviateClient client; + + // Static constructor for one-time setup (like @BeforeAll) + static SearchAggregateTest() + { + // START INSTANTIATION-COMMON + // Best practice: store your credentials in environment variables + string weaviateUrl = Environment.GetEnvironmentVariable("WEAVIATE_URL"); + string weaviateApiKey = Environment.GetEnvironmentVariable("WEAVIATE_API_KEY"); + + client = Connect.Cloud( + weaviateUrl, + weaviateApiKey + ); + // 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 TestMetaCount() + { + // START MetaCount + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.OverAll( + // highlight-start + totalCount: true + // highlight-end + ); + + Console.WriteLine(response.TotalCount); + // END MetaCount + } + + [Fact] + public async Task TestTextProp() + { + // START TextProp + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.OverAll( + // highlight-start + metrics: Metrics.ForProperty("answer") + .Text( + topOccurrencesCount: true, + topOccurrencesValue: true, + minOccurrences: 5 // Corresponds to topOccurrencesCutoff + ) + // highlight-end + ); + + var answerMetrics = response.Properties["answer"] as Aggregate.Text; + if (answerMetrics != null) + { + Console.WriteLine(JsonSerializer.Serialize(answerMetrics.TopOccurrences)); + } + // END TextProp + } + + [Fact] + public async Task TestIntProp() + { + // START IntProp + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.OverAll( + // highlight-start + // Use .Number for floats (NUMBER datatype in Weaviate) + metrics: Metrics.ForProperty("points") + .Integer( + sum: true, + maximum: true, + minimum: true + ) + // highlight-end + ); + + var pointsMetrics = response.Properties["points"] as Aggregate.Integer; + if (pointsMetrics != null) + { + Console.WriteLine($"Sum: {pointsMetrics.Sum}"); + Console.WriteLine($"Max: {pointsMetrics.Maximum}"); + Console.WriteLine($"Min: {pointsMetrics.Minimum}"); + } + // END IntProp + } + + [Fact] + public async Task TestGroupBy() + { + // START groupBy + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.OverAll( + // highlight-start + groupBy: new Aggregate.GroupBy("round") + // highlight-end + ); + + // print rounds names and the count for each + foreach (var group in response.Groups) + { + Console.WriteLine($"Value: {group.GroupedBy.Value} Count: {group.TotalCount}"); + } + // END groupBy + } + + //TODO[g-despot] Why doesn query need to be list? + [Fact] + public async Task TestNearTextWithLimit() + { + // START nearTextWithLimit + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.NearText( + ["animals in space"], + // highlight-start + limit: 10, + // highlight-end + metrics: Metrics.ForProperty("points").Number(sum: true) + ); + + var pointsMetrics = response.Properties["points"] as Aggregate.Number; + Console.WriteLine(JsonSerializer.Serialize(pointsMetrics)); + // END nearTextWithLimit + } + + [Fact] + public async Task TestHybrid() + { + // START HybridExample + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.Hybrid( + "animals in space", + // Additional parameters are available, such as `bm25Operator`, `filters`, etc. + // highlight-start + objectLimit: 10, + // highlight-end + metrics: Metrics.ForProperty("points").Number(sum: true) + ); + + var pointsMetrics = response.Properties["points"] as Aggregate.Number; + Console.WriteLine(JsonSerializer.Serialize(pointsMetrics)); + // END HybridExample + } + + [Fact] + public async Task TestNearTextWithDistance() + { + // Note: The C# client supports the 'distance' parameter for nearText aggregations. + // START nearTextWithDistance + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.NearText( + ["animals in space"], + // highlight-start + distance: 0.19, + // highlight-end + metrics: Metrics.ForProperty("points").Number(sum: true) + ); + + var pointsMetrics = response.Properties["points"] as Aggregate.Number; + Console.WriteLine(JsonSerializer.Serialize(pointsMetrics)); + // END nearTextWithDistance + } + + [Fact] + public async Task TestWhereFilter() + { + // Note: The C# client supports the 'filter' parameter for OverAll aggregations. + // START whereFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Aggregate.OverAll( + // highlight-start + filter: Filter.Property("round").Equal("Final Jeopardy!"), + // highlight-end + totalCount: true + ); + + Console.WriteLine(response.TotalCount); + // END whereFilter + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchBasicTest.cs b/_includes/code/csharp/SearchBasicTest.cs new file mode 100644 index 000000000..b943a6d6f --- /dev/null +++ b/_includes/code/csharp/SearchBasicTest.cs @@ -0,0 +1,261 @@ +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; + +// Note: This code assumes the existence of a Weaviate instance populated +// with 'JeopardyQuestion' and 'WineReviewMT' collections as per the Python examples. +public class SearchBasicTest : IAsyncLifetime +{ + private WeaviateClient client; + + public Task InitializeAsync() + { + // ================================ + // ===== 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"); + + // The Connect.Cloud helper method is a straightforward way to connect. + // We add the OpenAI API key to the headers for the text2vec-openai module. + client = Connect.Cloud( + weaviateUrl, + weaviateApiKey + ); + + return Task.CompletedTask; + } + + public Task DisposeAsync() + { + // The C# client manages its connections automatically and does not require an explicit 'close' method. + return Task.CompletedTask; + } + + [Fact] + public async Task BasicGet() + { + // ============================== + // ===== BASIC GET EXAMPLES ===== + // ============================== + + // BasicGetPython + var jeopardy = client.Collections.Use("JeopardyQuestion"); + // highlight-start + var response = await jeopardy.Query.FetchObjects(); + // highlight-end + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END BasicGetPython + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + } + + [Fact] + public async Task GetWithLimit() + { + // ==================================== + // ===== BASIC GET LIMIT EXAMPLES ===== + // ==================================== + + // GetWithLimitPython + var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + limit: 1 + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END GetWithLimitPython + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + Assert.Single(response.Objects); + } + + [Fact] + public async Task GetProperties() + { + // ========================================== + // ===== GET OBJECT PROPERTIES EXAMPLES ===== + // ========================================== + + // GetPropertiesPython + var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + limit: 1, + returnProperties: new[] { "question", "answer", "points" } + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END GetPropertiesPython + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + foreach (var propName in new[] { "question", "answer", "points" }) + { + Assert.True(response.Objects.First().Properties.ContainsKey(propName)); + } + } + + [Fact] + public async Task GetObjectVector() + { + // ====================================== + // ===== GET OBJECT VECTOR EXAMPLES ===== + // ====================================== + + // GetObjectVectorPython + var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + returnMetadata: (MetadataOptions.Vector, ["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())); + // END GetObjectVectorPython + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.IsType(response.Objects.First().Vectors["default"]); + } + + [Fact] + public async Task GetObjectId() + { + // ================================== + // ===== GET OBJECT ID EXAMPLES ===== + // ================================== + + // GetObjectIdPython + var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // Object IDs are included by default with the Weaviate C# client! :) + limit: 1 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(o.ID); + } + // END GetObjectIdPython + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.IsType(response.Objects.First().ID); + } + + [Fact] + public async Task GetWithCrossRefs() + { + // ============================== + // ===== GET WITH CROSS-REF EXAMPLES ===== + // ============================== + // GetWithCrossRefsPython + var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + // highlight-start + returnReferences: [ + new QueryReference( + linkOn: "hasCategory", + fields: ["title"] + ) + ], + // highlight-end + limit: 2 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(o.Properties["question"]); + // print referenced objects + // Note: References are grouped by property name ('hasCategory') + foreach (var refObj in o.References["hasCategory"]) + { + Console.WriteLine(JsonSerializer.Serialize(refObj.Properties)); + } + } + // END GetWithCrossRefsPython + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.True(response.Objects.First().References["hasCategory"].Count > 0); + } + + [Fact] + public async Task GetWithMetadata() + { + // ==================================== + // ===== GET WITH METADATA EXAMPLE ===== + // ==================================== + + // GetWithMetadataPython + var jeopardy = client.Collections.Use>("JeopardyQuestion"); + var response = await jeopardy.Query.FetchObjects( + limit: 1, + // highlight-start + returnMetadata: MetadataOptions.CreationTime + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); // View the returned properties + Console.WriteLine(o.Metadata.CreationTime); // View the returned creation time + } + // END GetWithMetadataPython + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.NotNull(response.Objects.First().Metadata.CreationTime); + } + + [Fact] + public async Task MultiTenancyGet() + { + // ========================= + // ===== MULTI-TENANCY ===== + // ========================= + + // MultiTenancy + 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. + // highlight-start + var response = await mtCollection.Query.FetchObjects( + tenant: "tenantA", + // highlight-end + returnProperties: new[] { "review_body", "title" }, + limit: 1 + ); + + Console.WriteLine(JsonSerializer.Serialize(response.Objects.First().Properties)); + // END MultiTenancy + + Assert.True(response.Objects.Count() > 0); + Assert.Equal("WineReviewMT", response.Objects.First().Collection); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchFiltersTest.cs b/_includes/code/csharp/SearchFiltersTest.cs new file mode 100644 index 000000000..e69de29bb diff --git a/_includes/code/csharp/SearchHybridTest.cs b/_includes/code/csharp/SearchHybridTest.cs new file mode 100644 index 000000000..2fd6ddc69 --- /dev/null +++ b/_includes/code/csharp/SearchHybridTest.cs @@ -0,0 +1,419 @@ +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; + +namespace WeaviateProject.Tests; + +public class SearchHybridTest : IDisposable +{ + private static readonly WeaviateClient client; + + // Static constructor for one-time setup (like @BeforeAll) + static SearchHybridTest() + { + // 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"); + + // 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); + // 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 NamedVectorHybrid() + { + // START NamedVectorHybrid + var reviews = client.Collections.Use("WineReviewNV"); + // highlight-start + var response = await reviews.Query.Hybrid( + "A French Riesling", + targetVector: ["title_country"], + limit: 3 + ); + // highlight-end + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END NamedVectorHybrid + + Assert.Equal("WineReviewNV", response.Objects.First().Collection); + } + + [Fact] + public async Task TestHybridBasic() + { + // START HybridBasic + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + // highlight-start + "food", + limit: 3 + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridBasic + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task TestHybridWithScore() + { + // START HybridWithScore + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + alpha: 0.5f, + // highlight-start + returnMetadata: MetadataOptions.Score | MetadataOptions.ExplainScore, + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + // highlight-start + Console.WriteLine($"Score: {o.Metadata.Score}, Explain Score: {o.Metadata.ExplainScore}"); + // highlight-end + } + // END HybridWithScore + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.NotNull(response.Objects.First().Metadata.Score); + Assert.NotNull(response.Objects.First().Metadata.ExplainScore); + } + + [Fact] + public async Task TestLimit() + { + // START limit + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // highlight-start + limit: 3, + offset: 1 + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END limit + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Equal(3, response.Objects.Count()); + } + + [Fact] + public async Task TestAutocut() + { + // START autocut + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // highlight-start + fusionType: HybridFusion.RelativeScore, + autoLimit: 1 + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END autocut + + Assert.True(response.Objects.Any()); + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task TestHybridWithAlpha() + { + // START HybridWithAlpha + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // highlight-start + alpha: 0.25f, + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridWithAlpha + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task TestHybridWithFusionType() + { + // START HybridWithFusionType + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // highlight-start + fusionType: HybridFusion.RelativeScore, + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridWithFusionType + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task HybridWithBM25OperatorOr() + { + // START HybridWithBM25OperatorOrWithMin + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + // highlight-start + "Australian mammal cute", + bm25Operator: new BM25Operator.Or(MinimumMatch: 2), + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridWithBM25OperatorOrWithMin + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task HybridWithBM25OperatorAnd() + { + + // START HybridWithBM25OperatorAnd + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + // highlight-start + "Australian mammal cute", + bm25Operator: new BM25Operator.And(), // Each result must include all tokens + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridWithBM25OperatorAnd + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task TestHybridWithProperties() + { + // START HybridWithProperties + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // highlight-start + queryProperties: ["question"], + // highlight-end + alpha: 0.25f, + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridWithProperties + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task TestHybridWithPropertyWeighting() + { + // START HybridWithPropertyWeighting + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // highlight-start + queryProperties: ["question^2", "answer"], + // highlight-end + alpha: 0.25f, + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridWithPropertyWeighting + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + // TODO[g-despot] Why is name required in VectorData.Create? + [Fact] + public async Task TestHybridWithVector() + { + // START HybridWithVector + var queryVector = Enumerable.Repeat(-0.02f, 1536).ToArray(); + + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // highlight-start + vectors: Vectors.Create("default", queryVector), + // highlight-end + alpha: 0.25f, + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END HybridWithVector + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + } + + [Fact] + public async Task TestHybridWithFilter() + { + // START HybridWithFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "food", + // 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 HybridWithFilter + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Equal("Double Jeopardy!", (response.Objects.First().Properties as IDictionary)["round"].ToString()); + } + + [Fact] + public async Task TestVectorParameters() + { + // START VectorParameters + var jeopardy = client.Collections.Use("JeopardyQuestion"); + // This query is complex and depends on a previous nearText query to get a vector. + // We simulate this by fetching a vector first. + var nearTextResponse = await jeopardy.Query.NearText( + "large animal", + moveAway: new Move(force: 0.5f, concepts: ["mammal", "terrestrial"]), + limit: 1, + returnMetadata: MetadataOptions.Vector + ); + var nearTextVector = nearTextResponse.Objects.First().Vectors["default"]; + + var response = await jeopardy.Query.Hybrid( + "California", + // highlight-start + maxVectorDistance: 0.4f, + vectors: nearTextVector, + // highlight-end + alpha: 0.75f, + limit: 5 + ); + // END VectorParameters + + Assert.True(response.Objects.Any() && response.Objects.Count() <= 5); + } + + [Fact] + public async Task TestVectorSimilarity() + { + // START VectorSimilarity + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "California", + // highlight-start + maxVectorDistance: 0.4f, // Maximum threshold for the vector search component + // highlight-end + alpha: 0.75f, + limit: 5 + ); + // END VectorSimilarity + + Assert.True(response.Objects.Any() && response.Objects.Count() <= 5); + } + + [Fact] + public async Task TestHybridGroupBy() + { + // START HybridGroupByPy4 + // Query + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.Hybrid( + "California", + alpha: 0.75f, + groupBy: new GroupByRequest + { + PropertyName = "round", // group by this property + NumberOfGroups = 2, // maximum number of groups + ObjectsPerGroup = 3, // maximum objects per group + } + ); + + foreach (var group in response.Groups.Values) + { + Console.WriteLine($"{group.Name} {JsonSerializer.Serialize(group.Objects)}"); + } + // END HybridGroupByPy4 + + Assert.True(response.Groups.Count > 0 && response.Groups.Count <= 2); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchImageTest.cs b/_includes/code/csharp/SearchImageTest.cs new file mode 100644 index 000000000..07043ddc3 --- /dev/null +++ b/_includes/code/csharp/SearchImageTest.cs @@ -0,0 +1,156 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Text.Json; +using System.Linq; +using System.IO; +using System.Net.Http; +using System.Collections.Generic; + +namespace WeaviateProject.Tests; + +public class SearchImageTest : IAsyncLifetime +{ + private static WeaviateClient client; + private const string QUERY_IMAGE_PATH = "images/search-image.jpg"; + + // START helper base64 functions + private static async Task UrlToBase64(string url) + { + using var httpClient = new HttpClient(); + var imageBytes = await httpClient.GetByteArrayAsync(url); + return Convert.ToBase64String(imageBytes); + } + + private static async Task FileToByteArray(string path) + { + return await File.ReadAllBytesAsync(path); + } + // END helper base64 functions + + // Runs once before any tests in the class (like @BeforeAll) + public async Task InitializeAsync() + { + client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8280 , GrpcPort = 50251}); + + if (await client.Collections.Exists("Dog")) + { + await client.Collections.Delete("Dog"); + } + + await client.Collections.Create(new Collection + { + Name = "Dog", + Properties = new() + { + Property.Blob("image"), + Property.Text("breed"), + Property.Text("description") + }, + VectorConfig = new VectorConfig( + "default", + new Vectorizer.Multi2VecClip { ImageFields = new[] { "image" }, TextFields = new[] { "breed", "description" } } + ) + }); + + // Prepare and ingest sample dog images + var dogs = client.Collections.Use("Dog"); + var sampleImages = new[] + { + new { url = "https://images.unsplash.com/photo-1489924034176-2e678c29d4c6?q=80&w=2342&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", breed = "Husky", description = "Siberian Husky with distinctive blue eyes, pointed ears, and thick white and grey fur coat, typical of arctic sled dogs" }, + new { url = "https://images.unsplash.com/photo-1633722715463-d30f4f325e24?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8R29sZGVuJTIwUmV0cmlldmVyfGVufDB8fDB8fHwy", breed = "Golden Retriever", description = "Golden Retriever with beautiful long golden fur, friendly expression, sitting and posing for the camera, known for being excellent family pets" }, + new { url = "https://images.unsplash.com/photo-1612979148245-d8c79c50935d?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OXx8ZG9nJTIwZ2VybWFuJTIwc2hlcGFyZHxlbnwwfHwwfHx8Mg%3D%3D", breed = "German Shepherd", description = "The German Shepherd, also known in Britain as an Alsatian, is a German breed of working dog of medium to large size. It was originally bred as a herding dog, for herding sheep. " } + }; + + Console.WriteLine("Inserting sample data..."); + foreach (var image in sampleImages) + { + string base64Image = await UrlToBase64(image.url); + await dogs.Data.Insert(new { image = base64Image, breed = image.breed, description = image.description }); + Console.WriteLine($"Inserted: {image.breed}"); + } + Console.WriteLine("Data insertion complete!"); + + // Download the specific image to be used for searches + var queryImageUrl = "https://images.unsplash.com/photo-1590419690008-905895e8fe0d?q=80&w=1336&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"; + using var httpClient = new HttpClient(); + var imageStream = await httpClient.GetStreamAsync(queryImageUrl); + + Directory.CreateDirectory("images"); + using var fileStream = new FileStream(QUERY_IMAGE_PATH, FileMode.Create, FileAccess.Write); + await imageStream.CopyToAsync(fileStream); + } + + // Runs once after all tests in the class (like @AfterAll) + public async Task DisposeAsync() + { + if (client != null) + { + if (await client.Collections.Exists("Dog")) + { + await client.Collections.Delete("Dog"); + } + } + if (File.Exists(QUERY_IMAGE_PATH)) File.Delete(QUERY_IMAGE_PATH); + if (Directory.Exists("images")) Directory.Delete("images"); + } + + [Fact] + public async Task TestSearchWithBase64() + { + // START search with base64 + // highlight-start + // The C# client's NearImage method takes a byte array directly. + var imageBytes = await FileToByteArray(QUERY_IMAGE_PATH); + // highlight-end + + // Get the collection containing images + var dogs = client.Collections.Use("Dog"); + + // Perform query + // highlight-start + var response = await dogs.Query.NearImage( + imageBytes, + // highlight-end + returnProperties: ["breed"], + limit: 1 + // targetVector: "vector_name" // required when using multiple named vectors + ); + + if (response.Objects.Any()) + { + Console.WriteLine(JsonSerializer.Serialize(response.Objects.First())); + } + // END search with base64 + } + + // START ImageFileSearch + // Coming soon + // END ImageFileSearch + + [Fact] + public async Task TestDistance() + { + // START Distance + var dogs = client.Collections.Use("Dog"); + var imageBytes = await FileToByteArray(QUERY_IMAGE_PATH); + + var response = await dogs.Query.NearImage( + imageBytes, + // highlight-start + distance: 0.8f, // Maximum accepted distance + returnMetadata: MetadataOptions.Distance, // return distance from the source image + // highlight-end + returnProperties: "breed", + limit: 5 + ); + + foreach (var item in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(item)); + } + // END Distance + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchKeywordTest.cs b/_includes/code/csharp/SearchKeywordTest.cs new file mode 100644 index 000000000..4aa2ed4b5 --- /dev/null +++ b/_includes/code/csharp/SearchKeywordTest.cs @@ -0,0 +1,269 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Text.Json; +using System.Linq; + +namespace WeaviateProject.Tests; + +public class SearchKeywordTest : IDisposable +{ + private static readonly WeaviateClient client; + + // Static constructor for one-time setup (like @BeforeAll) + static SearchKeywordTest() + { + // 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"); + + // 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); + // 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 TestBM25Basic() + { + // START BM25Basic + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + // highlight-start + "food", + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END BM25Basic + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + } + + // START BM25OperatorOrWithMin + // Coming soon + // END BM25OperatorOrWithMin + + // START BM25OperatorAnd + // Coming soon + // END BM25OperatorAnd + + [Fact] + public async Task TestBM25WithScore() + { + // START BM25WithScore + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + "food", + returnMetadata: MetadataOptions.Score, + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + // highlight-start + Console.WriteLine(o.Metadata.Score); + // highlight-end + } + // END BM25WithScore + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.NotNull(response.Objects.First().Metadata.Score); + } + + [Fact] + public async Task TestLimit() + { + // START limit + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + "safety", + // highlight-start + limit: 3, + offset: 1 + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END limit + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Contains("safety", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Equal(3, response.Objects.Count()); + } + + [Fact] + public async Task TestAutocut() + { + // START autocut + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + "safety", + // highlight-start + autoCut: 1 + // highlight-end + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END autocut + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Contains("safety", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + } + + [Fact] + public async Task TestBM25WithProperties() + { + // START BM25WithProperties + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + "safety", + // highlight-start + searchFields: ["question"], + // highlight-end + returnMetadata: MetadataOptions.Score, + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Score); + } + // END BM25WithProperties + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Contains("safety", response.Objects.First().Properties["question"].ToString().ToLower()); + } + + [Fact] + public async Task TestBM25WithBoostedProperties() + { + // START BM25WithBoostedProperties + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + "food", + // highlight-start + searchFields: ["question^2", "answer"], + // highlight-end + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END BM25WithBoostedProperties + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + } + + [Fact] + public async Task TestMultipleKeywords() + { + // START MultipleKeywords + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + // highlight-start + "food wine", // search for food or wine + // highlight-end + searchFields: ["question"], + limit: 5 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(o.Properties["question"]); + } + // END MultipleKeywords + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + var propertiesJson = JsonSerializer.Serialize(response.Objects.First().Properties).ToLower(); + Assert.True(propertiesJson.Contains("food") || propertiesJson.Contains("wine")); + } + + [Fact] + public async Task TestBM25WithFilter() + { + // START BM25WithFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.BM25( + "food", + // highlight-start + filters: Filter.Property("round").Equal("Double Jeopardy!"), + // highlight-end + returnProperties: ["answer", "question", "round"], // return these properties + limit: 3 + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + } + // END BM25WithFilter + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Contains("food", JsonSerializer.Serialize(response.Objects.First().Properties).ToLower()); + Assert.Equal("Double Jeopardy!", response.Objects.First().Properties["round"].ToString()); + } + + [Fact] + public async Task TestBM25GroupBy() + { + // START BM25GroupByPy4 + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + var response = await jeopardy.Query.BM25( + "California", + groupBy: new GroupByRequest + { + PropertyName = "round", // group by this property + NumberOfGroups = 2, // maximum number of groups + ObjectsPerGroup = 3, // maximum objects per group + } + ); + + foreach (var group in response.Groups.Values) + { + Console.WriteLine($"{group.Name} {JsonSerializer.Serialize(group.Objects)}"); + } + // END BM25GroupByPy4 + + Assert.True(response.Groups.Count > 0); + Assert.True(response.Groups.Count <= 2); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/SearchSimilarityTest.cs b/_includes/code/csharp/SearchSimilarityTest.cs new file mode 100644 index 000000000..f409b41c8 --- /dev/null +++ b/_includes/code/csharp/SearchSimilarityTest.cs @@ -0,0 +1,302 @@ +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 SearchSimilarityTest : IAsyncLifetime +{ + private WeaviateClient client; + + // InitializeAsync is used for asynchronous setup before tests in the class run. + 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"); + var cohereApiKey = Environment.GetEnvironmentVariable("COHERE_APIKEY"); + + client = Connect.Cloud( + weaviateUrl, + weaviateApiKey + // additionalHeaders: 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() + { + await client.Collections.DeleteAll(); + // The C# client, using HttpClient, manages its connections automatically and does not require an explicit 'close' method. + } + + [Fact] + public async Task NamedVectorNearText() + { + // START NamedVectorNearText + var reviews = client.Collections.Use("WineReviewNV"); + var response = await reviews.Query.NearText( + "a sweet German white wine", + limit: 2, + // highlight-start + targetVector: ["title_country"], // Specify the target vector for named vector collections + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END NamedVectorNearText + + Assert.Equal("WineReviewNV", response.Objects.First().Collection); + Assert.Equal(2, response.Objects.Count()); + Assert.True(response.Objects.First().Properties.ContainsKey("title")); + Assert.NotNull(response.Objects.First().Metadata.Distance); + } + + [Fact] + public async Task GetNearText() + { + // START GetNearText + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.NearText( + // highlight-start + "animals in movies", + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END GetNearText + } + + [Fact] + public async Task GetNearObject() + { + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var initialResponse = await jeopardy.Query.FetchObjects(limit: 1); + if (!initialResponse.Objects.Any()) return; // Skip test if no data + Guid uuid = (Guid)initialResponse.Objects.First().ID; + + // START GetNearObject + // highlight-start + var response = await jeopardy.Query.NearObject( + uuid, // A UUID of an object + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END GetNearObject + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Equal(2, response.Objects.Count()); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + Assert.NotNull(response.Objects.First().Metadata.Distance); + } + + [Fact] + 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 queryVector = initialResponse.Objects.First().Vectors["default"]; + + // START GetNearVector + // highlight-start + var response = await jeopardy.Query.NearVector( + queryVector, // your query vector goes here + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END GetNearVector + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Equal(2, response.Objects.Count()); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + Assert.NotNull(response.Objects.First().Metadata.Distance); + } + + [Fact] + public async Task GetLimitOffset() + { + // START GetLimitOffset + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.NearText( + "animals in movies", + // highlight-start + limit: 2, // return 2 objects + offset: 1, // With an offset of 1 + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END GetLimitOffset + } + + [Fact] + public async Task GetWithDistance() + { + // START GetWithDistance + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.NearText( + "animals in movies", + // highlight-start + distance: 0.25f, // max accepted distance + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END GetWithDistance + + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + Assert.NotNull(response.Objects.First().Metadata.Distance); + foreach (var o in response.Objects) + { + Assert.True(o.Metadata.Distance < 0.25f); + } + } + + [Fact] + public async Task GetWithAutocut() + { + // START Autocut + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.NearText( + "animals in movies", + // highlight-start + autoCut: 1, // number of close groups + // highlight-end + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END Autocut + + Assert.True(response.Objects.Count() > 0); + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + Assert.NotNull(response.Objects.First().Metadata.Distance); + } + + [Fact] + public async Task GetWithGroupBy() + { + // START GetWithGroupby + var jeopardy = client.Collections.Use("JeopardyQuestion"); + + // highlight-start + var response = await jeopardy.Query.NearText( + "animals in movies", // find object based on this query + limit: 10, // maximum total objects + returnMetadata: MetadataOptions.Distance, + groupBy: new GroupByRequest + { + PropertyName = "round", // group by this property + NumberOfGroups = 2, // maximum number of groups + ObjectsPerGroup = 2, // maximum objects per group + } + ); + // highlight-end + + foreach (var o in response.Objects) + { + Console.WriteLine(o.ID); + Console.WriteLine(o.BelongsToGroup); + Console.WriteLine(o.Metadata.Distance); + } + + foreach (var group in response.Groups.Values) + { + Console.WriteLine($"=========={group.Name}=========="); + Console.WriteLine(group.Objects.Count()); + foreach (var o in group.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(JsonSerializer.Serialize(o.Metadata)); + } + } + // END GetWithGroupby + + Assert.True(response.Objects.Count() > 0); + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.True(response.Objects.First().Properties.ContainsKey("question")); + Assert.NotNull(response.Objects.First().Metadata.Distance); + Assert.True(response.Groups.Count > 0); + Assert.True(response.Groups.Count <= 2); + } + + [Fact] + public async Task GetWithWhere() + { + // START GetWithFilter + var jeopardy = client.Collections.Use("JeopardyQuestion"); + var response = await jeopardy.Query.NearText( + "animals in movies", + // highlight-start + filters: Filter.Property("round").Equal("Double Jeopardy!"), + // highlight-end + limit: 2, + returnMetadata: MetadataOptions.Distance + ); + + foreach (var o in response.Objects) + { + Console.WriteLine(JsonSerializer.Serialize(o.Properties)); + Console.WriteLine(o.Metadata.Distance); + } + // END GetWithFilter + + Assert.True(response.Objects.Count() > 0); + Assert.Equal("JeopardyQuestion", response.Objects.First().Collection); + Assert.Equal("Double Jeopardy!", response.Objects.First().Properties["round"].ToString()); + 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 new file mode 100644 index 000000000..fd91feb6c --- /dev/null +++ b/_includes/code/csharp/StarterGuidesCollectionsTest.cs @@ -0,0 +1,139 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; + +namespace WeaviateProject.Tests; + +public class StarterGuidesCollectionsTest : IAsyncLifetime +{ + private WeaviateClient client; + + // Runs before each test + public 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 }); + // END-ANY + return Task.CompletedTask; + } + + // Runs after each test + public async Task DisposeAsync() + { + // Clean up any collections created during the tests + if (await client.Collections.Exists("Question")) + { + await client.Collections.Delete("Question"); + } + } + + [Fact] + public async Task TestBasicSchema() + { + // START BasicSchema + var questionsCollection = await client.Collections.Create(new Collection + { + Name = "Question", + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), // Set the vectorizer + GenerativeConfig = new Generative.CohereConfig(), // Set the generative module + Properties = new() + { + Property.Text("question"), + Property.Text("answer"), + Property.Text("category") + } + }); + + Console.WriteLine(questionsCollection); + // END BasicSchema + } + + // TODO[g-despot] Missing vectorizePropertyName + [Fact] + public async Task TestSchemaWithPropertyOptions() + { + // START SchemaWithPropertyOptions + await client.Collections.Create(new Collection + { + Name = "Question", + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), + GenerativeConfig = new Generative.CohereConfig(), + Properties = new() + { + 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 + ) + } + }); + // END SchemaWithPropertyOptions + } + + [Fact] + public async Task TestSchemaWithMultiTenancy() + { + // START SchemaWithMultiTenancy + await client.Collections.Create(new Collection + { + Name = "Question", + VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()), + GenerativeConfig = new Generative.CohereConfig(), + Properties = new() + { + Property.Text("question"), + Property.Text("answer") + }, + // highlight-start + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } // Enable multi-tenancy + // highlight-end + }); + // END SchemaWithMultiTenancy + } + + [Fact] + public async Task TestSchemaWithIndexSettings() + { + // START SchemaWithIndexSettings + await client.Collections.Create(new Collection + { + Name = "Question", + VectorConfig = new VectorConfig( + "default", // Set the name of the vector configuration + new Vectorizer.Text2VecWeaviate(), + // highlight-start + new VectorIndex.HNSW + { + Distance = VectorIndexConfig.VectorDistance.Cosine, // Configure the vector index + Quantizer = new VectorIndex.Quantizers.BQ() // Enable vector compression (quantization) + } + // highlight-end + ), + GenerativeConfig = new Generative.CohereConfig(), + Properties = new() + { + Property.Text("question"), + Property.Text("answer") + }, + // highlight-start + // Configure the inverted index + InvertedIndexConfig = new InvertedIndexConfig + { + IndexNullState = true, + IndexPropertyLength = true, + IndexTimestamps = true + } + // highlight-end + }); + // END SchemaWithIndexSettings + } +} \ No newline at end of file diff --git a/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs b/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs new file mode 100644 index 000000000..9ae430b08 --- /dev/null +++ b/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs @@ -0,0 +1,131 @@ +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; +using System.Text.Json.Serialization; + +namespace WeaviateProject.Tests; + +public class StarterGuidesCustomVectorsTest +{ + // Helper class for parsing the JSON data with vectors + private record JeopardyQuestionWithVector + { + [JsonPropertyName("Answer")] + public string Answer { get; init; } + [JsonPropertyName("Question")] + public string Question { get; init; } + [JsonPropertyName("Category")] + public string Category { get; init; } + [JsonPropertyName("vector")] + public float[] Vector { get; init; } + } + + [Fact] + public async Task TestBringYourOwnVectors() + { + using var client = Connect.Local(); + string collectionName = "Question"; + + try + { + // Clean slate + if (await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + + // START CreateCollection + // Create the collection. + await client.Collections.Create(new Collection + { + Name = collectionName, + Properties = new() + { + Property.Text("answer"), + Property.Text("question"), + Property.Text("category") + }, + VectorConfig = new VectorConfig("default", new Vectorizer.SelfProvided()) + }); + // END CreateCollection + + // START ImportData + var fname = "jeopardy_tiny_with_vectors_all-OpenAI-ada-002.json"; + var url = $"https://raw.githubusercontent.com/weaviate-tutorials/quickstart/main/data/{fname}"; + + using var httpClient = new HttpClient(); + var responseBody = await httpClient.GetStringAsync(url); + + var data = JsonSerializer.Deserialize>(responseBody); + + // Get a handle to the collection + var questions = client.Collections.Use(collectionName); + var questionObjs = new List(); + + foreach (var d in data) + { + // 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) + }); + // highlight-end + } + + var insertManyResponse = await questions.Data.InsertMany(questionObjs.ToArray()); + // 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 + + // Added a small delay to ensure indexing is complete + await Task.Delay(2000); + + var response = await questions.Query.NearVector( + queryVector, + limit: 2, + returnMetadata: MetadataOptions.Certainty + ); + + Console.WriteLine(JsonSerializer.Serialize(response.Objects)); + // END NearVector + + // ===== Test query results ===== + Assert.Equal(2, response.Objects.Count()); + // 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); + Assert.Equal(data[0].Question, props["question"].ToString()); + } + finally + { + if (client != null && await client.Collections.Exists(collectionName)) + { + await client.Collections.Delete(collectionName); + } + } + } +} \ No newline at end of file diff --git a/_includes/code/csharp/WeaviateProject.Tests.csproj b/_includes/code/csharp/WeaviateProject.Tests.csproj new file mode 100644 index 000000000..3cfcc5e2e --- /dev/null +++ b/_includes/code/csharp/WeaviateProject.Tests.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + true + + + + + + + + + + + + diff --git a/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs b/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs new file mode 100644 index 000000000..f693f4474 --- /dev/null +++ b/_includes/code/csharp/_ManageCollectionsMultiTenancyTest.cs @@ -0,0 +1,253 @@ +using Xunit; +using Weaviate.Client; +using Weaviate.Client.Models; +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Collections.Generic; + +namespace WeaviateProject.Tests; + +public class ManageCollectionsMultiTenancyTest : IAsyncLifetime +{ + private WeaviateClient client; + + // Runs before each test (like @BeforeEach) + public Task InitializeAsync() + { + string openaiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + if (string.IsNullOrWhiteSpace(openaiApiKey)) + { + throw new ArgumentException("Please set the OPENAI_API_KEY environment variable."); + } + + // Note: The C# client doesn't support setting headers like 'X-OpenAI-Api-Key' via the constructor for local connections. + // This must be configured in Weaviate's environment variables. + client = new WeaviateClient(new ClientConfiguration { RestAddress = "localhost", RestPort = 8080 }); + + return Task.CompletedTask; + } + + // Runs after each test (like @AfterEach) + public async Task DisposeAsync() + { + // Clean up any collections created during the tests + await client.Collections.DeleteAll(); + } + + [Fact] + public async Task TestEnableMultiTenancy() + { + // START EnableMultiTenancy + await client.Collections.Create(new Collection + { + Name = "MultiTenancyCollection", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + // END EnableMultiTenancy + + var config = await client.Collections.Export("MultiTenancyCollection"); + Assert.True(config.MultiTenancyConfig.Enabled); + } + + [Fact] + public async Task TestEnableAutoActivationMultiTenancy() + { + // START EnableAutoActivation + await client.Collections.Create(new Collection + { + Name = "MultiTenancyCollection", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantActivation = true } + }); + // END EnableAutoActivation + + var config = await client.Collections.Export("MultiTenancyCollection"); + Assert.True(config.MultiTenancyConfig.AutoTenantActivation); + } + + [Fact] + public async Task TestEnableAutoMT() + { + // START EnableAutoMT + await client.Collections.Create(new Collection + { + Name = "CollectionWithAutoMTEnabled", + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } + }); + // END EnableAutoMT + + var config = await client.Collections.Export("CollectionWithAutoMTEnabled"); + Assert.True(config.MultiTenancyConfig.AutoTenantCreation); + } + + [Fact] + public async Task TestUpdateAutoMT() + { + string collectionName = "MTCollectionNoAutoMT"; + await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = false } + }); + + // START UpdateAutoMT + var collection = client.Collections.Use(collectionName); + await collection.Config.Update(c => + { + c.MultiTenancyConfig.AutoTenantCreation = true; + }); + // END UpdateAutoMT + + var config = await client.Collections.Export(collectionName); + Assert.True(config.MultiTenancyConfig.AutoTenantCreation); + } + + [Fact] + public async Task TestAddTenantsToClass() + { + string collectionName = "MultiTenancyCollection"; + await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + + var collection = client.Collections.Use(collectionName); + + // START AddTenantsToClass + await collection.Tenants.Add( + new Tenant { Name = "tenantA" }, + new Tenant { Name = "tenantB" } + ); + // END AddTenantsToClass + + var tenants = (await collection.Tenants.List()).ToList(); + Assert.Equal(2, tenants.Count); + Assert.Contains(tenants, t => t.Name == "tenantA"); + Assert.Contains(tenants, t => t.Name == "tenantB"); + } + + [Fact] + public async Task TestListTenants() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); + + // START ListTenants + var tenants = await collection.Tenants.List(); + foreach (var t in tenants) Console.WriteLine(t.Name); + // END ListTenants + + Assert.Equal(2, tenants.Count()); + } + + [Fact] + public async Task TestGetTenantsByName() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); + + // START GetTenantsByName + var tenantNames = new[] { "tenantA", "tenantB", "nonExistentTenant" }; + var tenants = await collection.Tenants.List(tenantNames); + foreach (var t in tenants) Console.WriteLine(t.Name); + // END GetTenantsByName + + Assert.Equal(2, tenants.Count()); + } + + [Fact] + public async Task TestGetOneTenant() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + await collection.Tenants.Add(new Tenant { Name = "tenantA" }); + + // START GetOneTenant + string tenantName = "tenantA"; + var tenant = await collection.Tenants.Get(tenantName); + Console.WriteLine(tenant?.Name); + // END GetOneTenant + + Assert.NotNull(tenant); + } + + [Fact] + public async Task TestActivateTenant() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + await collection.Tenants.Add(new Tenant { Name = "tenantA", Status = TenantActivityStatus.Inactive }); + + // START ActivateTenants + string tenantName = "tenantA"; + await collection.Tenants.Activate(tenantName); + // END ActivateTenants + + var tenant = await collection.Tenants.Get(tenantName); + Assert.Equal(TenantActivityStatus.Active, tenant?.Status); + } + + [Fact] + public async Task TestDeactivateTenant() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true, AutoTenantCreation = true } + }); + await collection.Tenants.Add(new Tenant { Name = "tenantA" }); + + // START DeactivateTenants + string tenantName = "tenantA"; + await collection.Tenants.Deactivate(tenantName); + // END DeactivateTenants + + var tenant = await collection.Tenants.Get(tenantName); + Assert.Equal(TenantActivityStatus.Inactive, tenant?.Status); + } + + // START OffloadTenants + // Note: 'Offload' is not a current concept in the client. Use 'Deactivate' for similar functionality. + // Coming soon + // END OffloadTenants + + [Fact] + public async Task TestRemoveTenants() + { + string collectionName = "MultiTenancyCollection"; + var collection = await client.Collections.Create(new Collection + { + Name = collectionName, + MultiTenancyConfig = new MultiTenancyConfig { Enabled = true } + }); + await collection.Tenants.Add(new Tenant { Name = "tenantA" }, new Tenant { Name = "tenantB" }); + + // START RemoveTenants + await collection.Tenants.Delete(new[] { "tenantB", "tenantX" }); + // END RemoveTenants + + var tenants = (await collection.Tenants.List()).ToList(); + Assert.Single(tenants); + Assert.Equal("tenantA", tenants.First().Name); + } +} \ No newline at end of file diff --git a/_includes/code/csharp/_SearchGenerativeTest.cs b/_includes/code/csharp/_SearchGenerativeTest.cs new file mode 100644 index 000000000..4a8a70443 --- /dev/null +++ b/_includes/code/csharp/_SearchGenerativeTest.cs @@ -0,0 +1,304 @@ +// 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 new file mode 100644 index 000000000..e69de29bb diff --git a/_includes/code/howto/manage-data.create.with.geo.mdx b/_includes/code/howto/manage-data.create.with.geo.mdx index a837362a6..5bb29338f 100644 --- a/_includes/code/howto/manage-data.create.with.geo.mdx +++ b/_includes/code/howto/manage-data.create.with.geo.mdx @@ -3,6 +3,7 @@ import TabItem from '@theme/TabItem'; import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; import PyCode from '!!raw-loader!/_includes/code/howto/manage-data.create.py'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsCreateTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageObjectsCreateTest.cs"; @@ -126,4 +127,12 @@ public class App { language="java" /> + + + diff --git a/_includes/code/howto/manage-data.read.check.existence.mdx b/_includes/code/howto/manage-data.read.check.existence.mdx index ec4c872b0..6c7a8d971 100644 --- a/_includes/code/howto/manage-data.read.check.existence.mdx +++ b/_includes/code/howto/manage-data.read.check.existence.mdx @@ -3,6 +3,7 @@ import TabItem from '@theme/TabItem'; import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; import PyCode from '!!raw-loader!/_includes/code/howto/manage-data.create.py'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsReadTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageObjectsReadTest.cs"; @@ -129,5 +130,13 @@ public class App { ``` + + + diff --git a/_includes/code/howto/manage-data.shards.inspect.mdx b/_includes/code/howto/manage-data.shards.inspect.mdx index 3b8fe1c43..f387f8212 100644 --- a/_includes/code/howto/manage-data.shards.inspect.mdx +++ b/_includes/code/howto/manage-data.shards.inspect.mdx @@ -5,6 +5,7 @@ import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBl import PyCode from '!!raw-loader!/_includes/code/howto/manage-data.collections.py'; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.classes.java'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsTest.cs"; @@ -77,4 +78,12 @@ func main() { language="java" /> + + + diff --git a/_includes/code/howto/manage-data.shards.update.mdx b/_includes/code/howto/manage-data.shards.update.mdx index 048158e9b..79f09f2bd 100644 --- a/_includes/code/howto/manage-data.shards.update.mdx +++ b/_includes/code/howto/manage-data.shards.update.mdx @@ -6,6 +6,7 @@ import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.collections.p import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.shards_test.go"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.classes.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsTest.cs"; @@ -53,4 +54,12 @@ console.log(JSON.stringify(shards, null, 2)); language="java" /> + + + diff --git a/_includes/code/java-v6/src/test/java/ManageObjectsDeleteTest.java b/_includes/code/java-v6/src/test/java/ManageObjectsDeleteTest.java index 7be95e465..18680c0ea 100644 --- a/_includes/code/java-v6/src/test/java/ManageObjectsDeleteTest.java +++ b/_includes/code/java-v6/src/test/java/ManageObjectsDeleteTest.java @@ -63,22 +63,6 @@ void testDeleteObject() throws IOException { assertThat(collection.query.byId(uuidToDelete)).isNotPresent(); } - @Test - void testDeleteErrorHandling() { - // START DeleteError - // try { - // String nonExistentUuid = "00000000-0000-0000-0000-000000000000"; - // CollectionHandle> collection = - // client.collections.use(COLLECTION_NAME); - // collection.data.deleteById(nonExistentUuid); - // } catch (WeaviateApiException e) { - // // 404 error if the id was not found - // System.out.println(e); - // // assertThat(e.getStatusCode()).isEqualTo(404); - // } - // END DeleteError - } - @Test void testBatchDelete() { CollectionHandle> collection = client.collections.use(COLLECTION_NAME); diff --git a/_includes/code/quickstart.byov.schema.mdx b/_includes/code/quickstart.byov.schema.mdx index 618567a93..aca689617 100644 --- a/_includes/code/quickstart.byov.schema.mdx +++ b/_includes/code/quickstart.byov.schema.mdx @@ -1,12 +1,13 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; -import ByovAllPyCode from '!!raw-loader!/_includes/code/quickstart.byov.all.py'; -import ByovAllTsCode from '!!raw-loader!/_includes/code/quickstart.byov.all.ts'; -import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/StarterGuidesCustomVectorsTest.java"; -import ByovAllShCode from '!!raw-loader!/_includes/code/quickstart.byov.all.sh'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBlock"; +import ByovAllPyCode from "!!raw-loader!/_includes/code/quickstart.byov.all.py"; +import ByovAllTsCode from "!!raw-loader!/_includes/code/quickstart.byov.all.ts"; +import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/StarterGuidesCustomVectorsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/StarterGuidesCustomVectorsTest.cs"; +import ByovAllShCode from "!!raw-loader!/_includes/code/quickstart.byov.all.sh"; @@ -18,12 +19,12 @@ import ByovAllShCode from '!!raw-loader!/_includes/code/quickstart.byov.all.sh'; /> - + + + + @@ -32,4 +33,12 @@ import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/Start language="java" /> + + + diff --git a/_includes/code/quickstart/connect.partial.mdx b/_includes/code/quickstart/connect.partial.mdx index 34664e533..b196e3b21 100644 --- a/_includes/code/quickstart/connect.partial.mdx +++ b/_includes/code/quickstart/connect.partial.mdx @@ -5,6 +5,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyCodeV4 from "!!raw-loader!/_includes/code/connections/connect-python-v4.py"; import TsCodeV3 from "!!raw-loader!/_includes/code/connections/connect-ts-v3.ts"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ConnectionTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ConnectionTest.cs"; import JavaCode from "!!raw-loader!/_includes/code/connections/connect.java"; import ShellCode from "!!raw-loader!/_includes/code/connections/connect.sh"; import GoCode from "!!raw-loader!/_includes/code/connections/connect.go"; @@ -56,6 +57,14 @@ import HostnameWarning from "/_includes/wcs/hostname-warning.mdx"; /> + + + + + + diff --git a/_includes/code/quickstart/local.quickstart.import_objects.mdx b/_includes/code/quickstart/local.quickstart.import_objects.mdx index 226613983..3e0161444 100644 --- a/_includes/code/quickstart/local.quickstart.import_objects.mdx +++ b/_includes/code/quickstart/local.quickstart.import_objects.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/local.quickstart.import_ import TSCode from '!!raw-loader!/_includes/code/typescript/local.quickstart.import_objects.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart_local/2_2_add_objects/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartLocalTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartLocalTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart_local/Import.java'; @@ -63,6 +64,14 @@ During a batch import, any failed objects can be obtained through `batch.failed_ title="quickstart/Import.java" /> + + + diff --git a/_includes/code/quickstart/local.quickstart.is_ready.mdx b/_includes/code/quickstart/local.quickstart.is_ready.mdx index 1bea7c0d2..5ddd88578 100644 --- a/_includes/code/quickstart/local.quickstart.is_ready.mdx +++ b/_includes/code/quickstart/local.quickstart.is_ready.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/local.quickstart.is_read import TSCode from '!!raw-loader!/_includes/code/typescript/local.quickstart.is_ready.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart_local/1_is_ready/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartLocalTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartLocalTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart_local/IsReady.java'; @@ -61,6 +62,14 @@ import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/w title="quickstart/IsReady.java" /> + + + diff --git a/_includes/code/quickstart/local.quickstart.query.neartext.mdx b/_includes/code/quickstart/local.quickstart.query.neartext.mdx index d743019c3..ba02d5453 100644 --- a/_includes/code/quickstart/local.quickstart.query.neartext.mdx +++ b/_includes/code/quickstart/local.quickstart.query.neartext.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/local.quickstart.query.n import TSCode from '!!raw-loader!/_includes/code/typescript/local.quickstart.query.neartext.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart_local/3_1_neartext/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartLocalTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartLocalTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart_local/NearText.java'; @@ -61,6 +62,14 @@ import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/w title="quickstart/NearText.java" /> + + + diff --git a/_includes/code/quickstart/local.quickstart.query.rag.mdx b/_includes/code/quickstart/local.quickstart.query.rag.mdx index e1f723088..4d8fb0971 100644 --- a/_includes/code/quickstart/local.quickstart.query.rag.mdx +++ b/_includes/code/quickstart/local.quickstart.query.rag.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/local.quickstart.query.r import TSCode from '!!raw-loader!/_includes/code/typescript/local.quickstart.query.rag.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart_local/3_2_rag/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartLocalTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartLocalTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart_local/RAG.java'; @@ -61,6 +62,14 @@ import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/w title="quickstart/RAG.java" /> + + + diff --git a/_includes/code/quickstart/quickstart.create_collection.mdx b/_includes/code/quickstart/quickstart.create_collection.mdx index c8ce52004..1b9a139fb 100644 --- a/_includes/code/quickstart/quickstart.create_collection.mdx +++ b/_includes/code/quickstart/quickstart.create_collection.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/quickstart.create_collec import TSCode from '!!raw-loader!/_includes/code/typescript/quickstart.create_collection.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart/2_1_create_collection/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/CreateCollection.java'; import VectorConfigSyntax from "/_includes/vector-config-syntax.mdx"; import VectorsAutoSchemaError from "/_includes/error-note-vectors-autoschema.mdx"; @@ -67,6 +68,14 @@ import VectorsAutoSchemaError from "/_includes/error-note-vectors-autoschema.mdx /> + + + diff --git a/_includes/code/quickstart/quickstart.import_objects.mdx b/_includes/code/quickstart/quickstart.import_objects.mdx index b10076518..b74e0c1a3 100644 --- a/_includes/code/quickstart/quickstart.import_objects.mdx +++ b/_includes/code/quickstart/quickstart.import_objects.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/quickstart.import_object import TSCode from '!!raw-loader!/_includes/code/typescript/quickstart.import_objects.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart/2_2_add_objects/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/Import.java'; @@ -64,6 +65,14 @@ During a batch import, any failed objects can be obtained through `batch.failed_ title="quickstart/Import.java" /> + + + diff --git a/_includes/code/quickstart/quickstart.is_ready.mdx b/_includes/code/quickstart/quickstart.is_ready.mdx index 1b27e20e5..af72f1d7d 100644 --- a/_includes/code/quickstart/quickstart.is_ready.mdx +++ b/_includes/code/quickstart/quickstart.is_ready.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/quickstart.is_ready.py'; import TSCode from '!!raw-loader!/_includes/code/typescript/quickstart.is_ready.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart/1_is_ready/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/IsReady.java'; import HostnameWarning from '/_includes/wcs/hostname-warning.mdx'; @@ -63,6 +64,14 @@ import HostnameWarning from '/_includes/wcs/hostname-warning.mdx'; /> + + + diff --git a/_includes/code/quickstart/quickstart.query.neartext.mdx b/_includes/code/quickstart/quickstart.query.neartext.mdx index 6274866fe..89b7f8346 100644 --- a/_includes/code/quickstart/quickstart.query.neartext.mdx +++ b/_includes/code/quickstart/quickstart.query.neartext.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/quickstart.query.neartex import TSCode from '!!raw-loader!/_includes/code/typescript/quickstart.query.neartext.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart/3_1_neartext/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/NearText.java'; @@ -61,6 +62,14 @@ import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/w title="quickstart/NearText.java" /> + + + diff --git a/_includes/code/quickstart/quickstart.query.rag.mdx b/_includes/code/quickstart/quickstart.query.rag.mdx index 0b665bbe9..dc5741384 100644 --- a/_includes/code/quickstart/quickstart.query.rag.mdx +++ b/_includes/code/quickstart/quickstart.query.rag.mdx @@ -5,6 +5,7 @@ import PyCode from '!!raw-loader!/_includes/code/python/quickstart.query.rag.py' import TSCode from '!!raw-loader!/_includes/code/typescript/quickstart.query.rag.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/quickstart/3_2_rag/main.go'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/QuickstartTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/QuickstartTest.cs"; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/quickstart/RAG.java'; @@ -61,6 +62,14 @@ import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/w title="quickstart/RAG.java" /> + + + diff --git a/_includes/code/replication.get.object.by.id.mdx b/_includes/code/replication.get.object.by.id.mdx index 0b462a451..be0090629 100644 --- a/_includes/code/replication.get.object.by.id.mdx +++ b/_includes/code/replication.get.object.by.id.mdx @@ -4,6 +4,7 @@ import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBl import PyCode from '!!raw-loader!/_includes/code/howto/search.consistency.py'; import TSCode from '!!raw-loader!/_includes/code/howto/search.consistency.ts'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/SearchBasicTest.java"; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/SearchBasicTest.cs"; @@ -119,6 +120,14 @@ public class App { ``` + + + ```bash diff --git a/_includes/code/schema.things.properties.add.mdx b/_includes/code/schema.things.properties.add.mdx index d19ce6d6c..c8a4317a9 100644 --- a/_includes/code/schema.things.properties.add.mdx +++ b/_includes/code/schema.things.properties.add.mdx @@ -5,6 +5,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.collections.py"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.classes.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsTest.cs"; @@ -85,4 +86,12 @@ func main() { language="java" /> + + + diff --git a/_includes/code/tutorial.schema.create.mdx b/_includes/code/tutorial.schema.create.mdx index 8d187ab93..26e1c36f5 100644 --- a/_includes/code/tutorial.schema.create.mdx +++ b/_includes/code/tutorial.schema.create.mdx @@ -1,8 +1,9 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; -import PyCode from '!!raw-loader!/_includes/code/starter-guides/schema.py'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBlock"; +import PyCode from "!!raw-loader!/_includes/code/starter-guides/schema.py"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/StarterGuidesCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/StarterGuidesCollectionsTest.cs"; @@ -23,6 +24,14 @@ import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/Start language="java" /> + + + {/* ```go diff --git a/_includes/code/tutorial.schema.index-settings.mdx b/_includes/code/tutorial.schema.index-settings.mdx index c7f284489..07bef9f47 100644 --- a/_includes/code/tutorial.schema.index-settings.mdx +++ b/_includes/code/tutorial.schema.index-settings.mdx @@ -3,6 +3,7 @@ import TabItem from '@theme/TabItem'; import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; import PyCode from '!!raw-loader!/_includes/code/starter-guides/schema.py'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/StarterGuidesCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/StarterGuidesCollectionsTest.cs"; @@ -23,6 +24,14 @@ import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/Start language="java" /> + + + ```js diff --git a/_includes/code/tutorial.schema.multi-tenancy.mdx b/_includes/code/tutorial.schema.multi-tenancy.mdx index 982cf32ca..d0d58d45d 100644 --- a/_includes/code/tutorial.schema.multi-tenancy.mdx +++ b/_includes/code/tutorial.schema.multi-tenancy.mdx @@ -3,6 +3,7 @@ import TabItem from '@theme/TabItem'; import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; import PyCode from '!!raw-loader!/_includes/code/starter-guides/schema.py'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/StarterGuidesCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/StarterGuidesCollectionsTest.cs"; @@ -23,6 +24,14 @@ import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/Start language="java" /> + + + ```js diff --git a/_includes/code/tutorial.schema.properties.options.mdx b/_includes/code/tutorial.schema.properties.options.mdx index 258568b66..6cf0073cb 100644 --- a/_includes/code/tutorial.schema.properties.options.mdx +++ b/_includes/code/tutorial.schema.properties.options.mdx @@ -3,6 +3,7 @@ import TabItem from '@theme/TabItem'; import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; import PyCode from '!!raw-loader!/_includes/code/starter-guides/schema.py'; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/StarterGuidesCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/StarterGuidesCollectionsTest.cs"; @@ -23,6 +24,14 @@ import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/Start language="java" /> + + + ```js diff --git a/_includes/schema-delete-class.mdx b/_includes/schema-delete-class.mdx index 04d6b9453..ad367c9a6 100644 --- a/_includes/schema-delete-class.mdx +++ b/_includes/schema-delete-class.mdx @@ -4,6 +4,7 @@ import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBl import ManageCollectionsCode from '!!raw-loader!/_includes/code/howto/manage-data.collections.py'; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.classes.java'; import JavaV6Code from '!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsTest.java'; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsTest.cs"; You can delete any unwanted collection(s), along with the data that they contain. @@ -89,4 +90,12 @@ curl \ ``` + + + diff --git a/_includes/weaviate-embeddings-vectorizer-parameters.mdx b/_includes/weaviate-embeddings-vectorizer-parameters.mdx index ba94a278c..1ec7f3558 100644 --- a/_includes/weaviate-embeddings-vectorizer-parameters.mdx +++ b/_includes/weaviate-embeddings-vectorizer-parameters.mdx @@ -1,12 +1,12 @@ - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; -import PyCode from '!!raw-loader!/docs/weaviate/model-providers/_includes/provider.vectorizer.py'; -import TSCode from '!!raw-loader!/docs/weaviate/model-providers/_includes/provider.vectorizer.ts'; -import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/model-providers/2-usage-text/main.go'; -import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/ModelProvidersTest.java"; -import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/model_providers/UsageWeaviateTextEmbeddings.java'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBlock"; +import PyCode from "!!raw-loader!/docs/weaviate/model-providers/_includes/provider.vectorizer.py"; +import TSCode from "!!raw-loader!/docs/weaviate/model-providers/_includes/provider.vectorizer.ts"; +import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/model-providers/2-usage-text/main.go"; +import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ModelProvidersTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ModelProvidersTest.cs"; +import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/model_providers/UsageWeaviateTextEmbeddings.java"; - `model` (optional): The name of the model to use for embedding generation. - `dimensions` (optional): The number of dimensions to use for the generated embeddings. @@ -23,23 +23,23 @@ The following examples show how to configure Weaviate Embeddings-specific option language="py" /> - - - - - - - + + + + + + + - - - + + + + + + diff --git a/docs.sln b/docs.sln new file mode 100644 index 000000000..c5ead1f30 --- /dev/null +++ b/docs.sln @@ -0,0 +1,32 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_includes", "_includes", "{DE45C9AA-DBB3-91F8-540C-03A41DB425DA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "code", "code", "{9186D019-DB3D-BB6C-536B-D636275C82A4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WeaviateProject.Tests", "_includes\code\csharp\WeaviateProject.Tests.csproj", "{E1A0B893-B9C7-B0BB-27A1-C94B4D04BC73}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E1A0B893-B9C7-B0BB-27A1-C94B4D04BC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1A0B893-B9C7-B0BB-27A1-C94B4D04BC73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1A0B893-B9C7-B0BB-27A1-C94B4D04BC73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1A0B893-B9C7-B0BB-27A1-C94B4D04BC73}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9186D019-DB3D-BB6C-536B-D636275C82A4} = {DE45C9AA-DBB3-91F8-540C-03A41DB425DA} + {E1A0B893-B9C7-B0BB-27A1-C94B4D04BC73} = {9186D019-DB3D-BB6C-536B-D636275C82A4} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E51A6D01-9E9C-4B44-BE15-9ECBB531729D} + EndGlobalSection +EndGlobal diff --git a/docs/weaviate/client-libraries/csharp.mdx b/docs/weaviate/client-libraries/csharp.mdx new file mode 100644 index 000000000..6117b355e --- /dev/null +++ b/docs/weaviate/client-libraries/csharp.mdx @@ -0,0 +1,133 @@ +--- +title: C# - Beta release +sidebar_label: C# 🚧 +description: "Official C# client library documentation for integrating Weaviate with .NET applications and services." +image: og/docs/client-libraries.jpg +# tags: ['c#', 'csharp', 'client library', 'experimental'] +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBlock"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/GetStartedTest.cs"; +import QuickLinks from "/src/components/QuickLinks"; + +:::caution Preview + +The `C#` client is available as a beta release.
+ +This means that the library is still under development and may change in future releases, including potential breaking changes. +**We do not recommend using this client library in production environments at this time.** + +::: + +export const csharpCardsData = [ + { + title: "weaviate/csharp-client", + link: "https://github.com/weaviate/csharp-client/tree/mvp", + icon: "fa-brands fa-github", + }, + /*{ + title: "Reference manual", + link: "https://javadoc.io/doc/io.weaviate/client/latest/index.html", + icon: "fa-solid fa-book", + },*/ +]; + +:::note C# client (SDK) + +The latest C# client is version `v||site.csharp_client_version||`. + + + +::: + +This page broadly covers the Weaviate C# (beta release). For usage information not specific to the C# client, such as code examples, see the relevant pages in the [How-to manuals & Guides](../guides.mdx). + +## Installation + +```xml + +``` + +
+ Requirements: Weaviate version compatibility & gRPC + +#### Weaviate version compatibility + +The C# client requires Weaviate `1.33.0` or higher. Generally, we encourage you to use the latest version of the C# client and the Weaviate Database. + +#### gRPC + +The C# client uses remote procedure calls (RPCs) under-the-hood. Accordingly, a port for gRPC must be open to your Weaviate server. + +
+ docker-compose.yml example + +If you are running Weaviate with Docker, you can map the default port (`50051`) by adding the following to your `docker-compose.yml` file: + +```yaml +ports: + - 8080:8080 + - 50051:50051 +``` + +
+
+ +## Get started + +import BasicPrereqs from "/_includes/prerequisites-quickstart.md"; + + + +Get started with Weaviate using this C# example. The code walks you through these key steps: + +1. **[Connect to Weaviate](docs/weaviate/connections/index.mdx)**: Establish a connection to a local (or Cloud) Weaviate instance. +1. **[Create a collection](../manage-collections/index.mdx)**: Define the data schema for a `Question` collection, using an Ollama model to vectorize the data. +1. **[Import data](../manage-objects/import.mdx)**: Fetch sample Jeopardy questions and use Weaviate's batch import for efficient ingestion and automatic vector embedding generation. +1. **[Search/query the database](../search/index.mdx)**: Execute a vector search to find questions semantically similar to the query `biology`. + + + + + + + +For more code examples, check out the [How-to manuals & Guides](../guides.mdx) section. + +## Asynchronous usage + +_Coming soon_ + + + +## Releases + +Go to the [GitHub releases page](https://github.com/weaviate/csharp-client/releases) to see the history of the C# client library releases and change logs. + +
+ Click here for a table of Weaviate and corresponding client versions + +import ReleaseHistory from "/_includes/release-history.md"; + + + +
+ +## Code examples & further resources + +import CodeExamples from "/_includes/clients/code-examples.mdx"; + + + +## Questions and feedback + +import DocsFeedback from "/_includes/docs-feedback.mdx"; + + diff --git a/docs/weaviate/client-libraries/index.mdx b/docs/weaviate/client-libraries/index.mdx index 5c445cd23..001d44e6d 100644 --- a/docs/weaviate/client-libraries/index.mdx +++ b/docs/weaviate/client-libraries/index.mdx @@ -47,6 +47,13 @@ export const clientLibrariesData = [ link: "/weaviate/client-libraries/java", icon: "fab fa-java", }, + { + title: "C# Client", + description: + "Install and use the official C# library for interacting with Weaviate.", + link: "/weaviate/client-libraries/java", + icon: "fab fa-microsoft", + }, ];
diff --git a/docs/weaviate/configuration/compression/bq-compression.md b/docs/weaviate/configuration/compression/bq-compression.md index ab43b54dd..403292682 100644 --- a/docs/weaviate/configuration/compression/bq-compression.md +++ b/docs/weaviate/configuration/compression/bq-compression.md @@ -12,6 +12,7 @@ import TSCode from '!!raw-loader!/\_includes/code/howto/configure.bq-compression import TSCodeBQOptions from '!!raw-loader!/\_includes/code/howto/configure.bq-compression.options.ts'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/configure/compression.bq_test.go'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/ConfigureBQTest.java"; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/ConfigureBQTest.cs"; import JavaCode from '!!raw-loader!/\_includes/code/howto/java/src/test/java/io/weaviate/docs/bq-compression.java'; import CompressionByDefault from '/\_includes/compression-by-default.mdx'; @@ -74,6 +75,14 @@ BQ can be enabled at collection creation time through the collection definition: language="java" />
+ + +
## Enable compression for existing collection @@ -117,6 +126,14 @@ BQ can also be enabled for an existing collection by updating the collection def language="java" />
+ + +
## BQ parameters @@ -170,6 +187,14 @@ For example: language="java" />
+ + +
## Additional considerations diff --git a/docs/weaviate/configuration/compression/pq-compression.md b/docs/weaviate/configuration/compression/pq-compression.md index d06647607..bf9a44477 100644 --- a/docs/weaviate/configuration/compression/pq-compression.md +++ b/docs/weaviate/configuration/compression/pq-compression.md @@ -12,6 +12,7 @@ import TSCodeAutoPQ from '!!raw-loader!/\_includes/code/howto/configure.pq-compr import TSCodeManualPQ from '!!raw-loader!/\_includes/code/howto/configure.pq-compression.manual.ts'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/configure/compression.pq_test.go'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/ConfigurePQTest.java"; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/ConfigurePQTest.cs"; import JavaCode from '!!raw-loader!/\_includes/code/howto/java/src/test/java/io/weaviate/docs/pq-compression.java'; import CompressionByDefault from '/\_includes/compression-by-default.mdx'; @@ -78,6 +79,14 @@ To configure PQ in a collection, use the [PQ parameters](./pq-compression.md#pq- language="java" />
+ + + ### 3. Load your data @@ -155,6 +164,14 @@ Follow these steps to manually enable PQ. language="java" /> + + + ### 2. Load training data @@ -216,6 +233,14 @@ To enable PQ, update your collection definition as shown below. For additional c language="java" /> + + + ### 4. Load the rest of your data @@ -307,6 +332,14 @@ To review the current `pq` configuration, you can retrieve it as shown below. language="java" /> + + + ### Multiple vector embeddings (named vectors) diff --git a/docs/weaviate/configuration/compression/rq-compression.md b/docs/weaviate/configuration/compression/rq-compression.md index 6a91e0458..ce5d83da6 100644 --- a/docs/weaviate/configuration/compression/rq-compression.md +++ b/docs/weaviate/configuration/compression/rq-compression.md @@ -12,6 +12,7 @@ import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/configure/compre import TSCode from '!!raw-loader!/\_includes/code/howto/configure-rq/rq-compression-v3.ts'; import Java6Code from '!!raw-loader!/\_includes/code/java-v6/src/test/java/ConfigureRQTest.java'; import JavaCode from '!!raw-loader!/\_includes/code/howto/java/src/test/java/io/weaviate/docs/rq-compression.java'; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/ConfigureRQTest.cs"; import CompressionByDefault from '/\_includes/compression-by-default.mdx'; @@ -83,6 +84,14 @@ RQ can be enabled at collection creation time through the collection definition: language="java" /> + + + ### Enable compression for existing collection @@ -114,6 +123,14 @@ RQ can also be enabled for an existing collection by updating the collection def language="java" /> + + + + + + ### Enable compression for existing collection @@ -221,6 +246,14 @@ RQ can also be enabled for an existing collection by updating the collection def language="java" /> + + + ## RQ parameters @@ -272,6 +305,14 @@ import RQParameters from '/\_includes/configuration/rq-compression-parameters.md language="java" /> + + + + ```json { "class": "Article", @@ -317,7 +352,7 @@ This configuration for text objects defines the following:
- Sample configuration: Nested objects +Sample configuration: Nested objects :::info Added in `v1.22` ::: @@ -325,7 +360,9 @@ This configuration for text objects defines the following: This configuration for nested objects defines the following: - The collection name (`Person`) + - The vectorizer module (`text2vec-huggingface`) + - A set of properties (`last_name`, `address`) - `last_name` has `text` data type @@ -333,6 +370,8 @@ This configuration for nested objects defines the following: - The `address` property has two nested properties (`street` and `city`) + + ```json { "class": "Person", @@ -357,9 +396,9 @@ This configuration for nested objects defines the following:
- Sample configuration: Generative search +Sample configuration: Generative search -This configuration for [retrieval augmented generation](../search/generative.md) defines the following: +This configuration for [retrieval augmented generation](https://www.google.com/search?q=../search/generative.md) defines the following: - The collection name (`Article`) - The default vectorizer module (`text2vec-openai`) @@ -368,6 +407,8 @@ This configuration for [retrieval augmented generation](../search/generative.md) - The tokenization option for the `url` property - The vectorization option (`skip` vectorization) for the `url` property + + ```json { "class": "Article", @@ -408,19 +449,21 @@ This configuration for [retrieval augmented generation](../search/generative.md)
- Sample configuration: Images +Sample configuration: Images This configuration for image search defines the following: - The collection name (`Image`) + - The vectorizer module (`img2vec-neural`) - The `image` property configures collection to store image data. - The vector index distance metric (`cosine`) + - A set of properties (`image`), with the `image` property set as `blob`. -For image searches, see [Image search](../search/image.md). +For image searches, see [Image search](https://www.google.com/search?q=../search/image.md). ```json { @@ -490,6 +533,14 @@ Fetch the database schema to retrieve all of the collection definitions. language="java" /> + + + ## Update a collection definition @@ -498,7 +549,7 @@ import RaftRFChangeWarning from "/_includes/1-25-replication-factor.mdx"; -You can update a collection definition to change the [mutable collection settings](../config-refs/collections.mdx#mutability). +You can update a collection definition to change the [mutable collection settings](https://www.google.com/search?q=../config-refs/collections.mdx%23mutability). @@ -541,6 +592,14 @@ You can update a collection definition to change the [mutable collection setting language="java" /> + + + ## Delete a collection @@ -552,9 +611,9 @@ import CautionSchemaDeleteClass from "/_includes/schema-delete-class.mdx"; ## Add a property
- - Indexing limitations after data import - + +Indexing limitations after data import + There are no index limitations when you add collection properties before you import data. @@ -571,16 +630,55 @@ We are working on a re-indexing API to allow you to re-index the data after addi
-import CodeSchemaAddProperties from "/_includes/code/schema.things.properties.add.mdx"; - - + + + + + + + + + + + + + + + + + ## Further resources -- [Manage collections: Vectorizer and vector index](./vector-config.mdx) -- [References: Collection definition](/weaviate/config-refs/collections.mdx) -- [Concepts: Data structure](../concepts/data.md) -- +- [Manage collections: Vectorizer and vector index](https://www.google.com/search?q=./vector-config.mdx) +- [References: Collection definition](https://www.google.com/search?q=/weaviate/config-refs/collections.mdx) +- [Concepts: Data structure](https://www.google.com/search?q=../concepts/data.md) +- API References: REST: Schema diff --git a/docs/weaviate/manage-collections/cross-references.mdx b/docs/weaviate/manage-collections/cross-references.mdx index b7adcb614..f88dcf99a 100644 --- a/docs/weaviate/manage-collections/cross-references.mdx +++ b/docs/weaviate/manage-collections/cross-references.mdx @@ -13,6 +13,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.cross-refs.py"; import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.cross-refs.ts"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsCrossReferencesTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsCrossReferencesTest.cs"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.cross-refs.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.cross-refs_test.go"; import SkipLink from "/src/components/SkipValidationLink"; @@ -62,6 +63,14 @@ Include the reference property in the collection definition before adding cross- language="java" /> + + + ## Add a cross-reference property @@ -93,6 +102,14 @@ It is also possible to add a cross-reference property to an existing collection language="java" /> + + + ## Create an object with a cross-reference @@ -124,6 +141,14 @@ Specify a cross-reference when creating an object. language="java" /> + + + ## Add a one-way cross-reference @@ -171,6 +196,14 @@ Specify the required id and properties for the source and the target. language="java" /> + + + ## Add two-way cross-references @@ -204,6 +237,14 @@ Create the `JeopardyCategory` collection: language="java" /> + + + Create the `JeopardyQuestion` collection including the reference property to `JeopardyCategory`: @@ -233,6 +274,14 @@ Create the `JeopardyQuestion` collection including the reference property to `Je language="java" /> + + + Modify `JeopardyCategory` to add the reference to `JeopardyQuestion`: @@ -262,6 +311,14 @@ Modify `JeopardyCategory` to add the reference to `JeopardyQuestion`: language="java" /> + + + And add the cross-references: @@ -307,6 +364,14 @@ And add the cross-references: language="java" /> + + + ## Add multiple (one-to-many) cross-references @@ -354,6 +419,14 @@ Weaviate allows creation of multiple cross-references from one source object. language="java" /> + + + ## Read cross-references @@ -385,6 +458,14 @@ Cross-references can be read as part of the object. language="java" /> + + + ## Delete a cross-reference @@ -432,6 +513,14 @@ Deleting a cross-reference with the same parameters used to define the cross-ref language="java" /> + + +
@@ -489,6 +578,14 @@ The targets of a cross-reference can be updated. language="java" /> + + + ## Related pages diff --git a/docs/weaviate/manage-collections/generative-reranker-models.mdx b/docs/weaviate/manage-collections/generative-reranker-models.mdx index bdb4b86b5..f7d41fbaa 100644 --- a/docs/weaviate/manage-collections/generative-reranker-models.mdx +++ b/docs/weaviate/manage-collections/generative-reranker-models.mdx @@ -11,6 +11,7 @@ import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.collections.p import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.collections.ts"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.classes.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsTest.cs"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.classes_test.go"; :::tip Embedding models / Vectorizers @@ -61,6 +62,14 @@ Configure a [`reranker`](../concepts/search/index.md#rerank) model integration f language="java" /> + + + ## Update the reranker model integration @@ -104,6 +113,14 @@ Update the [`reranker`](../concepts/search/index.md#rerank) model integration fo language="java" /> + + + ## Specify a generative model integration @@ -156,6 +173,14 @@ Specify a `generative` model integration for a collection (for RAG). language="java" /> + + + ## Update the generative model integration @@ -199,6 +224,14 @@ Update a [`generative`](../concepts/search/index.md#retrieval-augmented-generati language="java" /> + + + import RuntimeGenerative from "/_includes/runtime-generative.mdx"; diff --git a/docs/weaviate/manage-collections/inverted-index.mdx b/docs/weaviate/manage-collections/inverted-index.mdx index d565a52de..1ea41fd05 100644 --- a/docs/weaviate/manage-collections/inverted-index.mdx +++ b/docs/weaviate/manage-collections/inverted-index.mdx @@ -11,6 +11,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"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.classes.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.classes_test.go"; @@ -83,6 +84,14 @@ The inverted index in Weaviate can be enabled through parameters at the property language="java" /> + + + ## Set inverted index parameters @@ -147,6 +156,14 @@ The inverted index in Weaviate can be configured through various parameters at t language="java" /> + + + ## Set tokenization type for property @@ -213,6 +230,14 @@ Tokenization determines how text content is broken down into individual terms th language="java" /> + + + ## Further resources diff --git a/docs/weaviate/manage-collections/migrate.mdx b/docs/weaviate/manage-collections/migrate.mdx index 904731998..ad5ed48bb 100644 --- a/docs/weaviate/manage-collections/migrate.mdx +++ b/docs/weaviate/manage-collections/migrate.mdx @@ -11,6 +11,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.migrate.data.v4.py"; import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.migrate.data.ts"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsMigrateDataTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsMigrateDataTest.cs"; Follow these examples to migrate data manually when using a backup is not possible. They cover all permutations between: @@ -69,6 +70,14 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col language="javaraw" /> + + + #### Step 2: Migrate the data @@ -103,6 +112,14 @@ Migrate: language="javaraw" /> + + + ## Collection → Tenant @@ -136,6 +153,14 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col language="javaraw" /> + + + #### Step 2: Create the tenant(s) @@ -167,6 +192,14 @@ Add tenants at the target instance before adding data objects. language="javaraw" /> + + + #### Step 3: Migrate the data @@ -201,6 +234,14 @@ Migrate: language="javaraw" /> + + + ## Tenant → Collection @@ -234,6 +275,14 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col language="javaraw" /> + + + #### Step 2: Migrate the data @@ -268,6 +317,14 @@ Migrate: language="javaraw" /> + + + ## Tenant → Tenant @@ -301,6 +358,14 @@ Create a collection (e.g. `WineReview`) at the target instance, matching the col language="javaraw" /> + + + #### Step 2: Create the tenant(s) @@ -332,6 +397,14 @@ Add tenants at the target instance before adding data objects. language="javaraw" /> + + + #### Step 3: Migrate the data @@ -366,6 +439,14 @@ Migrate: language="javaraw" /> + + + ## Related pages diff --git a/docs/weaviate/manage-collections/multi-node-setup.mdx b/docs/weaviate/manage-collections/multi-node-setup.mdx index 88c92e37d..eb1f3c218 100644 --- a/docs/weaviate/manage-collections/multi-node-setup.mdx +++ b/docs/weaviate/manage-collections/multi-node-setup.mdx @@ -10,6 +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"; import JavaReplicationCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.replication.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.classes_test.go"; @@ -65,6 +66,14 @@ Configure replication settings, such as [async replication](/deploy/configuratio language="java" /> + + + ```bash @@ -152,6 +161,14 @@ Configure sharding per collection. language="java" /> + + +
diff --git a/docs/weaviate/manage-collections/multi-tenancy.mdx b/docs/weaviate/manage-collections/multi-tenancy.mdx index 0b143128d..64841aa37 100644 --- a/docs/weaviate/manage-collections/multi-tenancy.mdx +++ b/docs/weaviate/manage-collections/multi-tenancy.mdx @@ -10,6 +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 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"; @@ -79,6 +80,14 @@ Multi-tenancy is disabled by default. To enable multi-tenancy, set `multiTenancy language="java" /> + + + ## Automatically add new tenants @@ -122,6 +131,14 @@ import AutoTenant from "/_includes/auto-tenant.mdx"; language="java" /> + + + + + + ## Add new tenants manually @@ -233,6 +258,14 @@ Tenant status is available from Weaviate `1.21` onwards. language="java" /> + + + ## List all tenants @@ -282,6 +315,14 @@ This example lists the tenants in the `MultiTenancyCollection` collection: language="java" /> + + + ## Get tenants by name @@ -315,6 +356,14 @@ This example returns `tenantA` and `tenantB` from the `MultiTenancyCollection` c language="java" /> + + + ## Get one tenant @@ -348,6 +397,14 @@ This example returns a tenant from the `MultiTenancyCollection` collection: language="java" /> + + + ## Delete tenants @@ -399,6 +456,14 @@ Deleting a tenant deletes all associated objects. language="java" /> + + + ## Manage tenant states diff --git a/docs/weaviate/manage-collections/tenant-states.mdx b/docs/weaviate/manage-collections/tenant-states.mdx index 5b08c3cb2..1fa7ccc36 100644 --- a/docs/weaviate/manage-collections/tenant-states.mdx +++ b/docs/weaviate/manage-collections/tenant-states.mdx @@ -10,6 +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"; ![Storage Tiers](./img/storage-tiers.jpg) @@ -121,6 +122,14 @@ To activate an `INACTIVE` tenant from disk, or to onload and activate an `OFFLOA language="java" /> + + + ## Deactivate tenant @@ -155,6 +164,14 @@ To deactivate an `ACTIVE` tenant, or to onload an `OFFLOADED` tenant from cloud language="java" /> + + + ## Offload tenant @@ -189,6 +206,14 @@ To offload an `ACTIVE` or `INACTIVE` tenant to cloud, call: language="java" /> + + + :::caution Requires Offload Module @@ -229,6 +254,14 @@ Enable this to automatically activate `INACTIVE` or `OFFLOADED` tenants if a sea language="java" /> + + + ## Questions and feedback diff --git a/docs/weaviate/manage-collections/vector-config.mdx b/docs/weaviate/manage-collections/vector-config.mdx index de2c6581a..e24be230a 100644 --- a/docs/weaviate/manage-collections/vector-config.mdx +++ b/docs/weaviate/manage-collections/vector-config.mdx @@ -13,6 +13,7 @@ import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.collections.t import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.classes.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageCollectionsTest.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.classes_test.go"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageCollectionsTest.cs"; import VectorConfigSyntax from "/_includes/vector-config-syntax.mdx"; @@ -73,6 +74,14 @@ Collection level settings override default values and general configuration para language="java" /> + + + ## Specify vectorizer settings @@ -124,6 +133,14 @@ To configure how a vectorizer works (i.e. what model to use) with a specific col language="java" /> + + + ## Define named vectors @@ -176,6 +193,14 @@ As such, each named vector configuration can include its own vectorizer and vect language="java" /> + + + ## Add new named vectors @@ -217,6 +242,14 @@ Named vectors can be added to existing collection definitions with named vectors language="java" /> + + + :::caution Objects aren't automatically revectorized @@ -263,6 +296,14 @@ Multi-vector embeddings, also known as multi-vectors, represent a single object language="java" /> + + + :::tip Use quantization and encoding to compress your vectors @@ -314,6 +355,14 @@ The vector index type can be set for each collection at creation time, between ` language="java" /> + + +
@@ -372,6 +421,14 @@ Was added in `v1.27` language="java" /> + + +
@@ -430,6 +487,14 @@ Configure individual properties in a collection. Each property can have it's own language="java" /> + + + ## Specify a distance metric @@ -477,6 +542,14 @@ If you choose to bring your own vectors, you should specify the `distance metric language="java" /> + + +
diff --git a/docs/weaviate/manage-objects/create.mdx b/docs/weaviate/manage-objects/create.mdx index e7f607945..3df966a1c 100644 --- a/docs/weaviate/manage-objects/create.mdx +++ b/docs/weaviate/manage-objects/create.mdx @@ -13,6 +13,7 @@ import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.create.ts"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.create.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsCreateTest.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.create_test.go"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageObjectsCreateTest.cs"; The examples on this page demonstrate how to create individual objects in Weaviate. @@ -65,6 +66,14 @@ This example creates an object in the `JeopardyQuestion` collection. language="java" /> + + +
@@ -127,6 +136,14 @@ When you create an object, you can provide a vector. (For specifying multiple, n language="java" /> + + + ## Create an object with named vectors @@ -161,6 +178,14 @@ When you create an object, you can specify named vectors (if [configured in your language="java" /> + + + ## Create an object with a specified ID @@ -212,6 +237,14 @@ If no ID is provided, Weaviate will generate a random [UUID](https://en.wikipedi language="java" /> + + + ## Generate deterministic IDs @@ -254,6 +287,14 @@ Object IDs are not randomly generated. The same value always generates the same language="java" /> + + +
@@ -304,6 +345,14 @@ You can create an object with cross-references to other objects. language="java" /> + + + :::tip Additional information @@ -367,6 +416,14 @@ Before you create an object, you can + + + ## Multiple vector embeddings (named vectors) diff --git a/docs/weaviate/manage-objects/delete.mdx b/docs/weaviate/manage-objects/delete.mdx index 2506fee6c..ec695e0e3 100644 --- a/docs/weaviate/manage-objects/delete.mdx +++ b/docs/weaviate/manage-objects/delete.mdx @@ -13,6 +13,7 @@ import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.delete.ts"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.delete.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsDeleteTest.java"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.delete_test.go"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageObjectsDeleteTest.cs"; import SkipLink from "/src/components/SkipValidationLink"; Weaviate allows object deletion by id or by a set of criteria. @@ -74,6 +75,14 @@ To delete by id, specify the collection name and the object id. startMarker="// START DeleteObject" endMarker="// END DeleteObject" language="java" + /> + + + @@ -141,6 +150,14 @@ To delete objects that match a set of criteria, specify the collection and a [`w language="java" /> + + +
@@ -213,6 +230,14 @@ client.batch().objectsBatchDeleter() ``` + + +
@@ -294,6 +319,14 @@ client.batch().objectsBatchDeleter() ``` + + + ## Delete all objects @@ -346,6 +379,14 @@ Objects must belong to a collection in Weaviate. Accordingly [deleting collectio language="java" /> + + +
diff --git a/docs/weaviate/manage-objects/import.mdx b/docs/weaviate/manage-objects/import.mdx index 6b9979272..bafde75b7 100644 --- a/docs/weaviate/manage-objects/import.mdx +++ b/docs/weaviate/manage-objects/import.mdx @@ -13,6 +13,7 @@ import TSCode from '!!raw-loader!/_includes/code/howto/manage-data.import.ts'; import TsSuppCode from '!!raw-loader!/_includes/code/howto/sample-data.ts'; import JavaCode from '!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.import.java'; import JavaV6Code from '!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsImportTest.java'; +import CSharpCode from '!!raw-loader!/_includes/code/csharp/ManageObjectsImportTest.cs'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/manage-data.import_test.go'; import SkipLink from '/src/components/SkipValidationLink' @@ -85,6 +86,14 @@ Find out more about error handling on the Python client [reference page](/weavia language="java" /> + + + ## Server-side batching @@ -148,6 +157,14 @@ Find out more about error handling on the Python client [reference page](/weavia ``` + + + ## Use the gRPC API @@ -215,6 +232,11 @@ if err != nil { } ``` + + + +The C# uses gRPC by default. + @@ -285,6 +307,14 @@ Weaviate generates an UUID for each object. Object IDs must be unique. If you se language="java" /> + + + ## Specify a vector @@ -332,6 +362,14 @@ Use the `vector` property to specify a vector for each object. language="java" /> + + + ## Specify named vectors @@ -366,6 +404,14 @@ When you create an object, you can specify named vectors (if [configured in your language="java" /> + + + ## Import with references @@ -389,6 +435,14 @@ You can batch create links from an object to another other object through cross- language="java" /> + + + ## Python-specific considerations @@ -436,6 +490,14 @@ To try the example code, download the sample data and create the sample input fi language="java" /> + + +
@@ -468,6 +530,14 @@ To try the example code, download the sample data and create the sample input fi language="java" /> + + +
@@ -500,6 +570,14 @@ To try the example code, download the sample data and create the sample input fi language="java" /> + + +
@@ -536,6 +614,14 @@ Note that each provider exposes different configuration options. language="java" /> + + + ## Additional considerations diff --git a/docs/weaviate/manage-objects/read-all-objects.mdx b/docs/weaviate/manage-objects/read-all-objects.mdx index c2079b66c..1d9e8acad 100644 --- a/docs/weaviate/manage-objects/read-all-objects.mdx +++ b/docs/weaviate/manage-objects/read-all-objects.mdx @@ -12,6 +12,7 @@ import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.read-all-obje import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.read-all-objects.ts"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.read-all-objects.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsReadAllTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageObjectsReadAllTest.cs"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.read-all-objects_test.go"; Weaviate provides the necessary APIs to iterate through all your data. This is useful when you want to manually copy/migrate your data (and vector embeddings) from one place to another. @@ -67,6 +68,14 @@ The following code iterates through all objects, providing the properties and id language="java" /> + + + ## Read all objects including vectors @@ -114,6 +123,14 @@ Read through all data including the vectors. (Also applicable where [named vecto language="java" /> + + + ## Read all objects - Multi-tenant collections @@ -165,6 +182,14 @@ For classes where [multi-tenancy](../concepts/data.md#multi-tenancy) is enabled, language="java" /> + + + ## Related pages diff --git a/docs/weaviate/manage-objects/read.mdx b/docs/weaviate/manage-objects/read.mdx index 50513a1e3..bdcb60576 100644 --- a/docs/weaviate/manage-objects/read.mdx +++ b/docs/weaviate/manage-objects/read.mdx @@ -11,6 +11,7 @@ import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.read.py"; import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.read.ts"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.read.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsReadTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageObjectsReadTest.cs"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.read_test.go"; import SkipLink from "/src/components/SkipValidationLink"; @@ -68,6 +69,14 @@ Use an ID to retrieve an object. If the id doesn't exist, Weaviate returns a 404 language="java" /> + + + ## Retrieve the object's vector @@ -115,6 +124,14 @@ Object vectors can be retrieved by specifying its return. language="java" /> + + + ## Retrieve named vectors @@ -146,6 +163,14 @@ Where [named vectors](../config-refs/collections.mdx#named-vectors) are used, yo language="java" /> + + + ## Check object existence diff --git a/docs/weaviate/manage-objects/update.mdx b/docs/weaviate/manage-objects/update.mdx index a4649ea7d..b8a78623e 100644 --- a/docs/weaviate/manage-objects/update.mdx +++ b/docs/weaviate/manage-objects/update.mdx @@ -11,6 +11,7 @@ import PyCode from "!!raw-loader!/_includes/code/howto/manage-data.update.py"; import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.update.ts"; import JavaCode from "!!raw-loader!/_includes/code/howto/java/src/test/java/io/weaviate/docs/manage-data.update.java"; import JavaV6Code from "!!raw-loader!/_includes/code/java-v6/src/test/java/ManageObjectsUpdateTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/ManageObjectsUpdateTest.cs"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.update_test.go"; import RestObjectsCRUDClassnameNote from "/_includes/rest-objects-crud-classname-note.md"; import SkipLink from "/src/components/SkipValidationLink"; @@ -81,6 +82,14 @@ However, if you add a _new_ property to your collection definition, Weaviate onl language="java" /> + + + ## Update object vector @@ -122,6 +131,14 @@ The object vector can also be updated similarly to properties. For [named vector > Coming soon + + + ## Replace an entire object @@ -169,6 +186,14 @@ The entire object can be replaced by providing the collection name, id and the n language="java" /> + + + ## Delete a property @@ -218,6 +243,14 @@ At object level, you can replace the object with a copy that has those propertie language="java" /> + + + ## Related pages diff --git a/docs/weaviate/model-providers/weaviate/embeddings.md b/docs/weaviate/model-providers/weaviate/embeddings.md index b38c9582f..65741f4de 100644 --- a/docs/weaviate/model-providers/weaviate/embeddings.md +++ b/docs/weaviate/model-providers/weaviate/embeddings.md @@ -22,6 +22,7 @@ import PyCode from "!!raw-loader!../\_includes/provider.vectorizer.py"; import TSCode from "!!raw-loader!../\_includes/provider.vectorizer.ts"; import GoCode from "!!raw-loader!/\_includes/code/howto/go/docs/model-providers/2-usage-text/main.go"; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/ModelProvidersTest.java"; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/ModelProvidersTest.cs"; import JavaCode from "!!raw-loader!/\_includes/code/howto/java/src/test/java/io/weaviate/docs/model_providers/UsageWeaviateTextEmbeddingsArcticEmbedLV20.java"; import JavaImportQueries from "!!raw-loader!/\_includes/code/howto/java/src/test/java/io/weaviate/docs/model_providers/ImportAndQueries.java"; @@ -89,6 +90,14 @@ Weaviate Embeddings is integrated with Weaviate Cloud. Your Weaviate Cloud crede language="javaraw" /> + + + ## Configure the vectorizer @@ -136,6 +145,14 @@ Weaviate Embeddings is integrated with Weaviate Cloud. Your Weaviate Cloud crede language="java" /> + + + ### Select a model @@ -183,6 +200,14 @@ You can specify one of the [available models](#available-models) for the vectori language="java" /> + + + You can [specify](#vectorizer-parameters) one of the [available models](#available-models) for Weaviate to use. The [default model](#available-models) is used if no model is specified. @@ -246,6 +271,14 @@ After configuring the vectorizer, [import data](../../manage-objects/import.mdx) language="java" /> + + + :::tip Re-use existing vectors @@ -305,6 +338,14 @@ The query below returns the `n` most similar objects from the database, set by ` language="java" /> + + + ### Hybrid search @@ -359,6 +400,14 @@ The query below returns the `n` best scoring objects from the database, set by ` language="java" /> + + + ## References diff --git a/docs/weaviate/search/aggregate.md b/docs/weaviate/search/aggregate.md index 6cf962ba7..6acc4e21c 100644 --- a/docs/weaviate/search/aggregate.md +++ b/docs/weaviate/search/aggregate.md @@ -13,6 +13,7 @@ import PyCodeV3 from '!!raw-loader!/\_includes/code/howto/search.aggregate-v3.py import TSCode from '!!raw-loader!/\_includes/code/howto/search.aggregate.ts'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/mainpkg/search-aggregation_test.go'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/SearchAggregateTest.java"; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/SearchAggregateTest.cs"; `Aggregate` queries process the result set to return calculated results. Use `aggregate` queries for groups of objects or the entire result set. @@ -73,6 +74,14 @@ Return the number of objects matched by the query. language="java" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Fetch objects by ID + +Fetch an object by its UUID: + + + + + + + ## `limit` returned objects Use `limit` to set a fixed maximum number of objects to return. @@ -140,6 +164,14 @@ Use `limit` to set a fixed maximum number of objects to return. language="java" /> + + + + + + + + + + + + + + + + + + + + + + + + ## Replication diff --git a/docs/weaviate/search/bm25.md b/docs/weaviate/search/bm25.md index f8e5704c5..3827cb4cc 100644 --- a/docs/weaviate/search/bm25.md +++ b/docs/weaviate/search/bm25.md @@ -15,6 +15,7 @@ import TSCode from '!!raw-loader!/\_includes/code/howto/search.bm25.ts'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/mainpkg/search-bm25_test.go'; import JavaCode from '!!raw-loader!/\_includes/code/howto/java/src/test/java/io/weaviate/docs/search/KeywordSearchTest.java'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/SearchKeywordTest.java"; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/SearchKeywordTest.cs"; import GQLCode from '!!raw-loader!/\_includes/code/howto/search.bm25.gql.py'; `Keyword` search, also called "BM25 (Best match 25)" or "sparse vector" search, returns objects that have the highest BM25F scores. @@ -64,6 +65,14 @@ To use BM25 keyword search, define a search string. language="java" /> + + + + + + + + + + + + + + + + + + + + + :::tip Tokenization and fuzzy matching @@ -463,6 +520,14 @@ Optionally, use `offset` to paginate the results. language="java" /> + + + + + + + + +
@@ -633,6 +714,14 @@ For more specific results, use a [`filter`](../api/graphql/filters.md) to narrow language="java" /> + + + + + + :::tip Best practices diff --git a/docs/weaviate/search/filters.md b/docs/weaviate/search/filters.md index 2cd195e05..0d0991671 100644 --- a/docs/weaviate/search/filters.md +++ b/docs/weaviate/search/filters.md @@ -13,6 +13,7 @@ import PyCodeV3 from '!!raw-loader!/\_includes/code/howto/search.filters-v3.py'; import JavaScriptCode from '!!raw-loader!/\_includes/code/howto/search.filters.ts'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/mainpkg/search-filters_test.go'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/SearchFiltersTest.java"; +import CSharpCode from "!!raw-loader!/\_includes/code/csharp/SearchFiltersTest.cs"; Filters let you include, or exclude, particular objects from your result set based on provided conditions.
For a list of filter operators, see the [API reference page](../api/graphql/filters.md#filter-structure). @@ -54,6 +55,14 @@ Add a `filter` to your query, to limit the result set. language="java" /> + + + + + + + + + + + + + + + + + + + + +
@@ -567,6 +624,14 @@ If the object property is a `text`, or `text`-like data type such as object ID, language="java" /> + + + + + + + + + + + + ## Filter by metadata @@ -797,6 +886,14 @@ For the full list, see [API references: Filters](../api/graphql/filters.md#speci language="java" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -670,6 +759,14 @@ The only available search threshold is `max vector distance`, which will set the language="java" /> + + + ## Group results @@ -704,6 +801,14 @@ Define criteria to group search results. language="java" /> + + +
@@ -757,6 +862,14 @@ Optionally, use `offset` to paginate the results. language="java" /> + + + + + + + + + + + +
@@ -139,6 +148,14 @@ You can search by a base64 representation of an image: language="java" /> + + +
@@ -198,6 +215,14 @@ You can create a base64 representation of an online image, and use it as input f language="java" /> + + + ## Combination with other operators diff --git a/docs/weaviate/search/similarity.md b/docs/weaviate/search/similarity.md index 91f04b1f4..325ee1560 100644 --- a/docs/weaviate/search/similarity.md +++ b/docs/weaviate/search/similarity.md @@ -14,6 +14,7 @@ import TSCode from '!!raw-loader!/\_includes/code/howto/search.similarity.ts'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/mainpkg/search-similarity_test.go'; import JavaCode from '!!raw-loader!/\_includes/code/howto/java/src/test/java/io/weaviate/docs/search/VectorSearchTest.java'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/SearchSimilarityTest.java"; +import CSharpCode from "!!raw-loader!/_includes/code/csharp/SearchSimilarityTest.cs"; Vector search returns the objects with most similar vectors to that of the query. @@ -70,6 +71,14 @@ Use the [`Near Text`](../api/graphql/search-operators.md#neartext) operator to f language="graphql" /> + + +
@@ -129,6 +138,14 @@ This example uses a base64 representation of an image. language="java" /> + + + See [Image search](./image.md) for more information. @@ -178,6 +195,14 @@ If you have an object ID, use the [`Near Object`](../api/graphql/search-operator language="java" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### Step 2: Create the original collection @@ -163,6 +172,14 @@ Let's create our initial products collection and populate it with data. language="java" /> + + + ### Step 3: Create an alias for production access @@ -210,6 +227,14 @@ Now create an alias that your application will use. This decouples your applicat language="java" /> + + + ### Step 4: Use the alias in your application @@ -257,6 +282,14 @@ Your application code should reference the alias, not the underlying collection. language="java" /> + + + The key point is that your application code doesn't need to know whether it's accessing `Products_v1` or `Products_v2` - it just uses the stable alias name. @@ -306,6 +339,14 @@ Now let's create a new version of the collection with an additional field (e.g., language="java" /> + + + ### Step 6: Migrate data to the new collection @@ -353,6 +394,14 @@ Copy data from the old collection to the new one, adding default values for new language="java" /> + + + ### Step 7: Update the alias (instant switch) @@ -400,6 +449,14 @@ This is the magic moment - update the alias to point to the new collection. This language="java" /> + + + ### Step 8: Verify and clean up @@ -447,6 +504,14 @@ After verifying that everything works correctly with the new collection, you can language="java" /> + + + ## Summary diff --git a/docusaurus.config.js b/docusaurus.config.js index 6d86cdd8e..f8bdbf2cd 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -151,7 +151,7 @@ const config = { prism: { theme: prismThemes.github, darkTheme: prismThemes.dracula, - additionalLanguages: ["java"], + additionalLanguages: ['java', 'csharp'], }, docs: { sidebar: { diff --git a/sidebars.js b/sidebars.js index cf760a41a..70f549b33 100644 --- a/sidebars.js +++ b/sidebars.js @@ -642,6 +642,7 @@ const sidebars = { { type: "category", label: "Java", + collapsed: false, className: "sidebar-item", link: { type: "doc", @@ -651,6 +652,11 @@ const sidebars = { "weaviate/client-libraries/java/java-v6", ], }, + { + type: "doc", + id: "weaviate/client-libraries/csharp", + className: "sidebar-item", + }, { type: "doc", id: "weaviate/client-libraries/go", diff --git a/src/components/Documentation/FilteredTextBlock.js b/src/components/Documentation/FilteredTextBlock.js index 4e58ce44e..dd3bced0a 100644 --- a/src/components/Documentation/FilteredTextBlock.js +++ b/src/components/Documentation/FilteredTextBlock.js @@ -44,6 +44,7 @@ DOC_SYSTEMS.js = DOC_SYSTEMS.ts; DOC_SYSTEMS.gonew = DOC_SYSTEMS.go; DOC_SYSTEMS.goraw = DOC_SYSTEMS.go; DOC_SYSTEMS.javaraw = DOC_SYSTEMS.java; +DOC_SYSTEMS.csharpraw = DOC_SYSTEMS.csharp; // Custom styles for badges const badgeStyles = { @@ -82,6 +83,14 @@ const FilteredTextBlock = ({ let withinMarkers = false; let format; switch (language) { + case 'csharp': // Add this new case + // remove leading indent of 4 spaces + format = (input) => input.replace(/^ /, ''); + break; + case 'csharpraw': // Add this new case + // remove leading indent of 4 spaces + format = (input) => input.replace(/^ /, ''); + break; case 'java': // remove leading indent of 4 spaces format = (input) => input.replace(/^ /, ''); @@ -146,6 +155,9 @@ const FilteredTextBlock = ({ case 'javaraw': language2 = 'java'; break; + case 'csharpraw': + language2 = 'csharp'; + break; } // Generate GitHub URL if path is provided diff --git a/tools/add_csharp.py b/tools/add_csharp.py new file mode 100644 index 000000000..75217f6de --- /dev/null +++ b/tools/add_csharp.py @@ -0,0 +1,209 @@ +import os +import re +import argparse +from pathlib import Path + +# Template for the new C# TabItem +CSHARP_TAB_TEMPLATE = """ + + + """ + + +def process_imports(content): + """ + Adds a CSharpCode import if a JavaV6Code import exists and the CSharpCode + import does not. + """ + # If C# import already exists, do nothing. + if re.search(r"import\s+CSharpCode\s+from", content): + return content + + # Find the Java V6 import to base the new import on. + # It captures the full import statement and the file path part. + java_import_match = re.search( + r'(import\s+JavaV6Code\s+from\s+([\'"]).*?java-v6/(.*?)(\2;))', + content, + re.DOTALL, + ) + if not java_import_match: + return content + + full_import_statement = java_import_match.group(0) + java_file_path = java_import_match.group(3) + + # Create the corresponding C# file path by replacing .java with .cs + csharp_file_path = re.sub(r"\.java$", ".cs", java_file_path) + + # Create the new import statement for C# + new_import_statement = ( + full_import_statement.replace("JavaV6Code", "CSharpCode") + .replace("java-v6/", "csharp/") + .replace(java_file_path, csharp_file_path) + ) + + # Insert the new import directly after the Java V6 one for consistency. + return content.replace( + full_import_statement, f"{full_import_statement}\n{new_import_statement}" + ) + + +def add_csharp_tab_to_block(tabs_block_match): + """ + A replacer function for re.sub that processes a single ... block. + """ + tabs_block = tabs_block_match.group(0) + + # Condition 1: Don't add if a C# tab already exists. + # Condition 2: Don't add if there's no Java v6 tab to use as a template. + if 'value="csharp"' in tabs_block or 'value="java6"' not in tabs_block: + return tabs_block + + # Find the Java v6 tab. We assume TabItems are not nested within each other. + java6_tab_match = re.search( + r'(.*?)', tabs_block, re.DOTALL + ) + if not java6_tab_match: + return tabs_block + + java6_tab_text = java6_tab_match.group(1) + + # Extract start and end markers from within the Java v6 tab text. + marker_match = re.search( + r'startMarker="([^"]+)"[\s\S]*?endMarker="([^"]+)"', java6_tab_text, re.DOTALL + ) + if not marker_match: + # Cannot proceed if markers aren't found in the Java v6 tab. + return tabs_block + + start_marker, end_marker = marker_match.groups() + + # Create the new C# tab from the template. + csharp_tab_text = CSHARP_TAB_TEMPLATE.format( + start_marker=start_marker, end_marker=end_marker + ).strip() + + # Determine the correct insertion point. + # We insert after the Java v6 tab, OR after the regular Java tab + # if it immediately follows the Java v6 tab. + + end_of_java6_match = java6_tab_match.end() + rest_of_block_after_java6 = tabs_block[end_of_java6_match:] + + # Check if the next element is a regular Java tab. + if re.match(r'\s*.*?)', + rest_of_block_after_java6, + re.DOTALL, + ) + if java_tab_match: + java_tab_text = java_tab_match.group(1) + # Replace the Java tab with itself plus the new C# tab. + return tabs_block.replace( + java_tab_text, f"{java_tab_text}\n {csharp_tab_text}" + ) + + # If no regular Java tab followed, insert after the Java v6 tab. + # Replace the Java v6 tab with itself plus the new C# tab. + return tabs_block.replace(java6_tab_text, f"{java6_tab_text}\n {csharp_tab_text}") + + +def process_file_content(content): + """ + Runs the import and tab processing on the entire file content. + Returns the new content and a boolean indicating if changes were made. + """ + original_content = content + + # Step 1: Add C# import statement if needed. + content_with_imports = process_imports(original_content) + + # Step 2: Find all blocks and apply the replacer function to each. + tabs_pattern = re.compile(r"]*>.*?", re.DOTALL) + new_content = tabs_pattern.sub(add_csharp_tab_to_block, content_with_imports) + + return new_content, new_content != original_content + + +def process_directory(directory_path, file_extensions, dry_run=True): + """ + Process all files with given extensions in a directory and its subdirectories. + """ + directory = Path(directory_path) + files_processed = 0 + files_modified = 0 + + for ext in file_extensions: + for file_path in directory.glob(f"**/*{ext}"): + files_processed += 1 + + # Read the file content + with open(file_path, "r", encoding="utf-8") as file: + try: + content = file.read() + except UnicodeDecodeError: + print(f"Error reading {file_path}: UnicodeDecodeError") + continue + + # Process the content to add tabs and imports + new_content, was_modified = process_file_content(content) + + # If content was modified + if was_modified: + files_modified += 1 + print(f"Modified: {file_path}") + + # Write the changes if not in dry-run mode + if not dry_run: + with open(file_path, "w", encoding="utf-8") as file: + file.write(new_content) + + return files_processed, files_modified + + +def main(): + parser = argparse.ArgumentParser( + description="Adds C# TabItem blocks after Java v6/Java tabs in documentation files." + ) + parser.add_argument("directory", help="Directory to process") + parser.add_argument( + "--ext", + nargs="+", + default=[".md", ".mdx"], + help="File extensions to process (default: .md .mdx)", + ) + parser.add_argument( + "--apply", + action="store_true", + help="Apply changes (without this flag, runs in dry-run mode)", + ) + + args = parser.parse_args() + + print(f"Processing files in {args.directory}") + print(f"File extensions: {', '.join(args.ext)}") + print( + f"Mode: {'Apply Changes' if args.apply else 'Dry Run (no changes will be made)'}" + ) + + files_processed, files_modified = process_directory( + args.directory, args.ext, dry_run=not args.apply + ) + + print(f"\nSummary:") + print(f"Files processed: {files_processed}") + print(f"Files that would be/were modified: {files_modified}") + + if not args.apply and files_modified > 0: + print("\nRun with --apply to make the changes.") + + +if __name__ == "__main__": + main() diff --git a/versions-config.json b/versions-config.json index 7e4b0c455..02d0d096d 100644 --- a/versions-config.json +++ b/versions-config.json @@ -11,5 +11,6 @@ "java_new_client_version": "6.0.0-M1", "javascript_client_version": "2.14.5", "typescript_client_version": "3.9.0", - "spark_connector_version": "1.4.0" + "spark_connector_version": "1.4.0", + "csharp_client_version": "0.0.1-beta.4" }