diff --git a/global.json b/global.json index 7906b59..739dced 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.113", + "version": "7.0.202", "rollForward": "latestFeature", "allowPrerelease": false } diff --git a/src/SearchPioneer.Weaviate.Client/Api/Data/Model/Vectorizer.cs b/src/SearchPioneer.Weaviate.Client/Api/Data/Model/Vectorizer.cs index 55f3185..eac39d7 100644 --- a/src/SearchPioneer.Weaviate.Client/Api/Data/Model/Vectorizer.cs +++ b/src/SearchPioneer.Weaviate.Client/Api/Data/Model/Vectorizer.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com +// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,5 +18,6 @@ namespace SearchPioneer.Weaviate.Client; public static class Vectorizer { public static string Text2VecContextionary = "text2vec-contextionary"; + public static string Img2VecNeural = "img2vec-neural"; public static string None = "none"; } diff --git a/src/SearchPioneer.Weaviate.Client/Api/Schema/Model/Tokenization.cs b/src/SearchPioneer.Weaviate.Client/Api/Schema/Model/Tokenization.cs index 77669f9..2aec993 100644 --- a/src/SearchPioneer.Weaviate.Client/Api/Schema/Model/Tokenization.cs +++ b/src/SearchPioneer.Weaviate.Client/Api/Schema/Model/Tokenization.cs @@ -18,5 +18,6 @@ namespace SearchPioneer.Weaviate.Client; public enum Tokenization { Word, - Field + Field, + WhiteSpace } diff --git a/src/SearchPioneer.Weaviate.Client/Api/Schema/SchemaApi.cs b/src/SearchPioneer.Weaviate.Client/Api/Schema/SchemaApi.cs index 70cde42..2541047 100644 --- a/src/SearchPioneer.Weaviate.Client/Api/Schema/SchemaApi.cs +++ b/src/SearchPioneer.Weaviate.Client/Api/Schema/SchemaApi.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com +// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -45,6 +45,9 @@ public async Task> GetShardsAsync(GetShardsRequest request, public ApiResponse CreateSchemaClass(CreateClassRequest request) => _transport.PostAsync("/schema", request).GetAwaiter().GetResult(); + public ApiResponse CreateSchemaClassString(string request) + => _transport.PostStringAsync("/schema", request).GetAwaiter().GetResult(); + public async Task> CreateSchemaClassAsync(CreateClassRequest request, CancellationToken cancellationToken = default) => await _transport.PostAsync("/schema", request, cancellationToken).ConfigureAwait(false); diff --git a/src/SearchPioneer.Weaviate.Client/JsonConvertors/TokenizationJsonConverter.cs b/src/SearchPioneer.Weaviate.Client/JsonConvertors/TokenizationJsonConverter.cs index f746da4..4d3a84a 100644 --- a/src/SearchPioneer.Weaviate.Client/JsonConvertors/TokenizationJsonConverter.cs +++ b/src/SearchPioneer.Weaviate.Client/JsonConvertors/TokenizationJsonConverter.cs @@ -22,12 +22,14 @@ internal class TokenizationJsonConverter : JsonConverter { private const string Word = "word"; private const string Field = "field"; + private const string WhiteSpace = "whitespace"; private static Tokenization FromString(string status) => status switch { Word => Tokenization.Word, Field => Tokenization.Field, + WhiteSpace => Tokenization.WhiteSpace, _ => throw new ArgumentOutOfRangeException(nameof(status)) }; @@ -36,6 +38,7 @@ private static string ToString(Tokenization status) => { Tokenization.Word => Word, Tokenization.Field => Field, + Tokenization.WhiteSpace => WhiteSpace, _ => throw new ArgumentOutOfRangeException(nameof(status)) }; diff --git a/src/SearchPioneer.Weaviate.Client/SearchPioneer.Weaviate.Client.csproj b/src/SearchPioneer.Weaviate.Client/SearchPioneer.Weaviate.Client.csproj index 01002a6..7581469 100644 --- a/src/SearchPioneer.Weaviate.Client/SearchPioneer.Weaviate.Client.csproj +++ b/src/SearchPioneer.Weaviate.Client/SearchPioneer.Weaviate.Client.csproj @@ -1,6 +1,6 @@ - netstandard2.0;netstandard2.1;net461 + netstandard2.0;netstandard2.1 Weaviate .NET client .NET client for Weaviate enable diff --git a/src/SearchPioneer.Weaviate.Client/Transport.cs b/src/SearchPioneer.Weaviate.Client/Transport.cs index 76f3a44..4878e75 100644 --- a/src/SearchPioneer.Weaviate.Client/Transport.cs +++ b/src/SearchPioneer.Weaviate.Client/Transport.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com +// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -137,6 +137,14 @@ public async Task> PostAsync(string endp return result; } + public async Task> PostStringAsync(string endpoint, TRequest stringPayload, CancellationToken cancellationToken = default) + { + var result = GetResult(endpoint, stringPayload, "POST"); + var response = await _flurlClient.Request(result.Uri).PostStringAsync(stringPayload as string, cancellationToken).ConfigureAwait(false); + await EnrichResult(result, response).ConfigureAwait(false); + return result; + } + public async Task PostAsync(string endpoint, TRequest payload, CancellationToken cancellationToken = default) { var result = GetResult(endpoint, payload, "POST"); diff --git a/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs b/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs index 8e86aa3..7294de6 100644 --- a/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs +++ b/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com +// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ namespace SearchPioneer.Weaviate.Client; /// public class WeaviateClient { - private const string ClientVersion = "1.18.0"; + private const string ClientVersion = "1.19.6"; private static readonly Config DefaultConfig = new("http", "localhost:8080"); private readonly DbVersionSupport _dbVersionSupport; diff --git a/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs b/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs index f819c9d..4fee318 100644 --- a/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs +++ b/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com +// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,12 +14,114 @@ using Xunit; +using System.Text.Json; +using System.Text.Json.Serialization; namespace SearchPioneer.Weaviate.Client.IntegrationTests.Api.Schema; [Collection("Sequential")] public class SchemaTests : TestBase { + [Fact] + // [How to build an Image Search Application with Weaviate](https://weaviate.io/blog/how-to-build-an-image-search-application-with-weaviate) in .NET + public void BuildAnImageSearchApplication() + { + Client.Schema.DeleteAllClasses(); + + ApiResponse? createStatus = Client.Schema.CreateSchemaClassString(DogSchema); + Assert.True(createStatus.HttpStatusCode == 200); + + ApiResponse? schema = Client.Schema.GetSchema(); + Assert.True(schema.HttpStatusCode == 200); + Assert.Single(schema.Result.Classes); + + // Convert sample images to base64 + const string path = + @"C:\UnitySrc\private repos\weaviate-examples\nearest-neighbor-dog-search\flask-app\static\img"; + + // Iterate over all files in path + string[] files = Directory.GetFiles(path); + var requests = new List(); + foreach (string filePath in files) + { + var imageArray = File.ReadAllBytes(filePath); + var base64ImageRepresentation = Convert.ToBase64String(imageArray); + + var breed = Path.GetFileNameWithoutExtension(filePath).Replace("-", " "); + requests.Add(new WeaviateObject + { + Class = "Dog", + Properties = new Dictionary + { + { "breed", breed }, + { "image", base64ImageRepresentation }, + { "filepath", filePath } + } + }); + } + ApiResponse? batch = Client.Batch.CreateObjects(new CreateObjectsBatchRequest(requests.ToArray()) + { + ConsistencyLevel = ConsistencyLevel.One + }); + Assert.True(batch.HttpStatusCode == 200); + + const string searchImagePath = @"C:\UnitySrc\private repos\weaviate-examples\nearest-neighbor-dog-search\flask-app\static\golden-doodle-puppy.jpg"; + string searchImage = Convert.ToBase64String(File.ReadAllBytes(searchImagePath)); + GraphGetRequest request = new () + { + Class = "Dog", Limit = 2, + Fields = new Field[] {"breed".AsField(), "image".AsField(), "filepath".AsField()}, + NearImage = new NearImage {Image = searchImage} + }; + ApiResponse? results = Client.Graph.Get(request); + string json = results.Result.Data["Get"]!["Dog"].ToString(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + Dog[] dogs = JsonSerializer.Deserialize(json, options); + Assert.True(dogs.Length == 2); + Assert.Multiple( + () => Assert.Contains(dogs, dog => dog.Breed == "Goldendoodle"), + () => Assert.Contains(dogs, dog => dog.Breed == "Golden Retriever")); + } + + public class Dog + { + public string Breed { get; set; } = null!; + public string Image { get; set; } = null!; + public string Filepath { get; set; } = null!; + } + private const string DogSchema = @" + { + ""class"": ""Dog"", + ""description"": ""Images of different dogs"", + ""moduleConfig"": { + ""img2vec-neural"": { + ""imageFields"": [ + ""image"" + ] + } + }, + ""vectorIndexType"": ""hnsw"", + ""vectorizer"": ""img2vec-neural"", + ""properties"": [ + { + ""name"": ""breed"", + ""dataType"": [""string""], + ""description"": ""name of dog breed"" + }, + { + ""name"": ""image"", + ""dataType"": [""blob""], + ""description"": ""image"" + }, + { + ""name"": ""filepath"", + ""dataType"":[""string""], + ""description"": ""filepath of the images"" + } + ] + } +"; + [Fact] public void CreateBandClass() { diff --git a/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/TestBase.cs b/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/TestBase.cs index 430d653..b8d827a 100644 --- a/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/TestBase.cs +++ b/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/TestBase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com +// Copyright (C) 2023 Search Pioneer - https://www.searchpioneer.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ public abstract class TestBase protected static readonly string PIZZA_DOENER_ID = "d2b393ff-4b26-48c7-b554-218d970a9e17"; protected static readonly string SOUP_CHICKENSOUP_ID = "8c156d37-81aa-4ce9-a811-621e2702b825"; protected static readonly string SOUP_BEAUTIFUL_ID = "27351361-2898-4d1a-aad7-1ca48253eb0b"; - protected readonly WeaviateClient Client = new(new("http", "localhost:8080", "my-secret-key"), new FlurlClient()); + protected readonly WeaviateClient Client = new(new("http", "localhost:8080"), new FlurlClient()); // Uncomment to try alternate seperators // protected TestBase() =>