From 61368e075e5fa11a9d65e030c642813af7c276cb Mon Sep 17 00:00:00 2001 From: Brian Elgaard Bennett Date: Mon, 29 May 2023 18:14:32 +0200 Subject: [PATCH 1/4] Push to origin From 65433feb906112e82f12353cba6c19b36abe06fc Mon Sep 17 00:00:00 2001 From: Brian Elgaard Bennett Date: Mon, 29 May 2023 18:24:24 +0200 Subject: [PATCH 2/4] First steps until base64 of one file, upgrade to Weaviate 1.18.4, Upgrade .NET to 7.0.202. --- global.json | 2 +- .../Api/Data/Model/Vectorizer.cs | 3 +- .../Api/Schema/SchemaApi.cs | 5 +- .../SearchPioneer.Weaviate.Client.csproj | 2 +- .../Transport.cs | 10 ++- .../WeaviateClient.cs | 4 +- .../Api/Schema/SchemaTests.cs | 74 ++++++++++++++++++- .../TestBase.cs | 4 +- 8 files changed, 94 insertions(+), 10 deletions(-) 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/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/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..188269f 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.18.4"; 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..7a406c4 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,84 @@ using Xunit; +using Flurl.Http; 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(); + + //var flurlClient = new FlurlClient(); + //var weaviateClient = new WeaviateClient(new Config("http", "localhost:8080"), flurlClient); + var createStatus = Client.Schema.CreateSchemaClassString(DogSchema); + Assert.True(createStatus.HttpStatusCode == 200); + + var schema = Client.Schema.GetSchema(); + Assert.True(schema.HttpStatusCode == 200); + Assert.Single(schema.Result.Classes); + + // Convert sample images in C:\UnitySrc\private repos\weaviate-examples\nearest-neighbor-dog-search\flask-app\static\img to base64 + const string path = @"C:\UnitySrc\private repos\weaviate-examples\nearest-neighbor-dog-search\flask-app\static\img"; // TODO: Iterate over these!!! + var filePath = $@"{path}\Australian-Shepherd.jpg"; + var imageArray = System.IO.File.ReadAllBytes(filePath); + var base64ImageRepresentation = Convert.ToBase64String(imageArray); + + var batch = Client.Batch.CreateObjects(new CreateObjectsBatchRequest( + new WeaviateObject + { + Class = "Dog", + Properties = new Dictionary + { + { "breed", "Australian Shepherd" }, + { "image", base64ImageRepresentation }, + { "filepath", filePath } + } + }) + { + ConsistencyLevel = ConsistencyLevel.Quorum + }); + Assert.True(batch.HttpStatusCode == 200); + } + + 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() => From ec23b6e9a1471667f7d4b7ec2d015d8326c96fa9 Mon Sep 17 00:00:00 2001 From: Brian Elgaard Bennett Date: Mon, 5 Jun 2023 19:59:55 +0200 Subject: [PATCH 3/4] Fully functional image recognition sample, but still depends on images not checked in. --- .../Api/Schema/Model/Tokenization.cs | 3 +- .../TokenizationJsonConverter.cs | 3 + .../Api/Schema/SchemaTests.cs | 82 +++++++++++++------ 3 files changed, 61 insertions(+), 27 deletions(-) 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/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/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs b/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs index 7a406c4..4fee318 100644 --- a/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs +++ b/tests-integration/SearchPioneer.Weaviate.Client.IntegrationTests/Api/Schema/SchemaTests.cs @@ -14,7 +14,8 @@ using Xunit; -using Flurl.Http; +using System.Text.Json; +using System.Text.Json.Serialization; namespace SearchPioneer.Weaviate.Client.IntegrationTests.Api.Schema; @@ -27,38 +28,67 @@ public void BuildAnImageSearchApplication() { Client.Schema.DeleteAllClasses(); - //var flurlClient = new FlurlClient(); - //var weaviateClient = new WeaviateClient(new Config("http", "localhost:8080"), flurlClient); - var createStatus = Client.Schema.CreateSchemaClassString(DogSchema); + ApiResponse? createStatus = Client.Schema.CreateSchemaClassString(DogSchema); Assert.True(createStatus.HttpStatusCode == 200); - var schema = Client.Schema.GetSchema(); + ApiResponse? schema = Client.Schema.GetSchema(); Assert.True(schema.HttpStatusCode == 200); Assert.Single(schema.Result.Classes); - // Convert sample images in C:\UnitySrc\private repos\weaviate-examples\nearest-neighbor-dog-search\flask-app\static\img to base64 - const string path = @"C:\UnitySrc\private repos\weaviate-examples\nearest-neighbor-dog-search\flask-app\static\img"; // TODO: Iterate over these!!! - var filePath = $@"{path}\Australian-Shepherd.jpg"; - var imageArray = System.IO.File.ReadAllBytes(filePath); - var base64ImageRepresentation = Convert.ToBase64String(imageArray); + // Convert sample images to base64 + const string path = + @"C:\UnitySrc\private repos\weaviate-examples\nearest-neighbor-dog-search\flask-app\static\img"; - var batch = Client.Batch.CreateObjects(new CreateObjectsBatchRequest( - new WeaviateObject - { - Class = "Dog", - Properties = new Dictionary - { - { "breed", "Australian Shepherd" }, - { "image", base64ImageRepresentation }, - { "filepath", filePath } - } - }) - { - ConsistencyLevel = ConsistencyLevel.Quorum - }); - Assert.True(batch.HttpStatusCode == 200); - } + // 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"", From 62178a0946d8bbf134cad60cce84899862ceb146 Mon Sep 17 00:00:00 2001 From: Brian Elgaard Bennett Date: Mon, 5 Jun 2023 20:00:34 +0200 Subject: [PATCH 4/4] Bumped to Weaviate 1.19.6 --- src/SearchPioneer.Weaviate.Client/WeaviateClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs b/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs index 188269f..7294de6 100644 --- a/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs +++ b/src/SearchPioneer.Weaviate.Client/WeaviateClient.cs @@ -24,7 +24,7 @@ namespace SearchPioneer.Weaviate.Client; /// public class WeaviateClient { - private const string ClientVersion = "1.18.4"; + private const string ClientVersion = "1.19.6"; private static readonly Config DefaultConfig = new("http", "localhost:8080"); private readonly DbVersionSupport _dbVersionSupport;