diff --git a/app/MindWork AI Studio/Tools/Rust/CountTokensRequest.cs b/app/MindWork AI Studio/Tools/Rust/CountTokensRequest.cs new file mode 100644 index 000000000..f2112f75d --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/CountTokensRequest.cs @@ -0,0 +1,6 @@ +namespace AIStudio.Tools.Rust; + +/// Request body used to count the tokens of a text in the Rust runtime. +/// The text to count. +/// Path of the tokenizer file to count with. +public sealed record CountTokensRequest(string Text, string TokenizerPath); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/DeleteTokenizerRequest.cs b/app/MindWork AI Studio/Tools/Rust/DeleteTokenizerRequest.cs new file mode 100644 index 000000000..fb7885ad7 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/DeleteTokenizerRequest.cs @@ -0,0 +1,5 @@ +namespace AIStudio.Tools.Rust; + +/// Request body used to delete the stored tokenizer of a model in the Rust runtime. +/// The model whose tokenizer gets deleted. +public sealed record DeleteTokenizerRequest(string ModelId); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/PrepareImageForModelRequest.cs b/app/MindWork AI Studio/Tools/Rust/PrepareImageForModelRequest.cs new file mode 100644 index 000000000..a42e650c5 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/PrepareImageForModelRequest.cs @@ -0,0 +1,6 @@ +namespace AIStudio.Tools.Rust; + +/// Request body used to prepare a local image for a model in the Rust runtime. +/// Absolute path of the image. +/// The size the prepared image may have at most. +public sealed record PrepareImageForModelRequest(string Path, long MaxBytes); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/PrepareImageRequest.cs b/app/MindWork AI Studio/Tools/Rust/PrepareImageRequest.cs new file mode 100644 index 000000000..ef18fe0a8 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/PrepareImageRequest.cs @@ -0,0 +1,6 @@ +namespace AIStudio.Tools.Rust; + +/// Request body used to check and optionally optimize a local image in the Rust runtime. +/// Absolute path of the image. +/// Whether the maximum-edge policy and re-encoding are applied. +public sealed record PrepareImageRequest(string Path, bool Optimize); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/StoreTokenizerRequest.cs b/app/MindWork AI Studio/Tools/Rust/StoreTokenizerRequest.cs new file mode 100644 index 000000000..8903435f1 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/StoreTokenizerRequest.cs @@ -0,0 +1,6 @@ +namespace AIStudio.Tools.Rust; + +/// Request body used to store a copy of a tokenizer file for a model in the Rust runtime. +/// The model the tokenizer belongs to. +/// Absolute path of the tokenizer file. +public sealed record StoreTokenizerRequest(string ModelId, string FilePath); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/ValidateTokenizerRequest.cs b/app/MindWork AI Studio/Tools/Rust/ValidateTokenizerRequest.cs new file mode 100644 index 000000000..08a1f4ea1 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/ValidateTokenizerRequest.cs @@ -0,0 +1,5 @@ +namespace AIStudio.Tools.Rust; + +/// Request body used to check a tokenizer file in the Rust runtime. +/// Absolute path of the tokenizer file. +public sealed record ValidateTokenizerRequest(string FilePath); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Image.cs b/app/MindWork AI Studio/Tools/Services/RustService.Image.cs index fd60f29c6..1890e8384 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Image.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Image.cs @@ -20,7 +20,7 @@ public sealed partial class RustService bool optimize, CancellationToken token = default) { - using var response = await this.http.PostAsJsonAsync("/image/prepare", new { path, optimize }, JSON_OPTIONS, token); + using var response = await this.http.PostAsJsonAsync("/image/prepare", new PrepareImageRequest(path, optimize), JSON_OPTIONS, token); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(JSON_OPTIONS, token) @@ -44,7 +44,7 @@ public sealed partial class RustService { try { - using var response = await this.http.PostAsJsonAsync("/image/prepare-for-model", new { path, maxBytes }, JSON_OPTIONS, token); + using var response = await this.http.PostAsJsonAsync("/image/prepare-for-model", new PrepareImageForModelRequest(path, maxBytes), JSON_OPTIONS, token); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(JSON_OPTIONS, token) diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Tokenizer.cs b/app/MindWork AI Studio/Tools/Services/RustService.Tokenizer.cs index 580e857ac..8162919ca 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Tokenizer.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Tokenizer.cs @@ -15,9 +15,7 @@ public sealed partial class RustService : ITokenizerStorage public async Task ValidateTokenizer(string filePath) { - var result = await this.http.PostAsJsonAsync("/tokenizer/validate", new { - file_path = filePath, - }, JSON_OPTIONS); + var result = await this.http.PostAsJsonAsync("/tokenizer/validate", new ValidateTokenizerRequest(filePath), JSON_OPTIONS); if (!result.IsSuccessStatusCode) { @@ -33,10 +31,7 @@ public sealed partial class RustService : ITokenizerStorage public async Task StoreTokenizer(string modelId, string filePath) { this.logger!.LogInformation($"Storing tokenizer for model '{modelId}' from file '{filePath}'"); - var result = await this.http.PostAsJsonAsync("/tokenizer/store", new { - model_id = modelId, - file_path = filePath, - }, JSON_OPTIONS); + var result = await this.http.PostAsJsonAsync("/tokenizer/store", new StoreTokenizerRequest(modelId, filePath), JSON_OPTIONS); if (!result.IsSuccessStatusCode) { @@ -50,9 +45,7 @@ public sealed partial class RustService : ITokenizerStorage public async Task DeleteTokenizer(string modelId) { this.logger!.LogInformation($"Deleting tokenizer for model '{modelId}'"); - var result = await this.http.PostAsJsonAsync("/tokenizer/delete", new { - model_id = modelId, - }, JSON_OPTIONS); + var result = await this.http.PostAsJsonAsync("/tokenizer/delete", new DeleteTokenizerRequest(modelId), JSON_OPTIONS); if (!result.IsSuccessStatusCode) { @@ -71,10 +64,7 @@ public sealed partial class RustService : ITokenizerStorage private async Task GetTokenCount(string providerName, string tokenizerPath, string text, CancellationToken cancellationToken) { - var result = await this.http.PostAsJsonAsync("/tokenizer/count", new { - text, - tokenizer_path = tokenizerPath, - }, JSON_OPTIONS, cancellationToken); + var result = await this.http.PostAsJsonAsync("/tokenizer/count", new CountTokensRequest(text, tokenizerPath), JSON_OPTIONS, cancellationToken); if (!result.IsSuccessStatusCode) { diff --git a/app/Tests/Serialization/Fixtures/runtime-count-tokens-request.json b/app/Tests/Serialization/Fixtures/runtime-count-tokens-request.json new file mode 100644 index 000000000..1cbf13dd4 --- /dev/null +++ b/app/Tests/Serialization/Fixtures/runtime-count-tokens-request.json @@ -0,0 +1 @@ +{"text":"Count these tokens.","tokenizer_path":"/samples/tokenizer.json"} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/runtime-delete-tokenizer-request.json b/app/Tests/Serialization/Fixtures/runtime-delete-tokenizer-request.json new file mode 100644 index 000000000..0724c8b11 --- /dev/null +++ b/app/Tests/Serialization/Fixtures/runtime-delete-tokenizer-request.json @@ -0,0 +1 @@ +{"model_id":"model-a"} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/runtime-prepare-image-for-model-request.json b/app/Tests/Serialization/Fixtures/runtime-prepare-image-for-model-request.json new file mode 100644 index 000000000..09c6394b0 --- /dev/null +++ b/app/Tests/Serialization/Fixtures/runtime-prepare-image-for-model-request.json @@ -0,0 +1 @@ +{"path":"/samples/picture.png","max_bytes":5242880} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/runtime-prepare-image-request.json b/app/Tests/Serialization/Fixtures/runtime-prepare-image-request.json new file mode 100644 index 000000000..6fa65e640 --- /dev/null +++ b/app/Tests/Serialization/Fixtures/runtime-prepare-image-request.json @@ -0,0 +1 @@ +{"path":"/samples/picture.png","optimize":true} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/runtime-store-tokenizer-request.json b/app/Tests/Serialization/Fixtures/runtime-store-tokenizer-request.json new file mode 100644 index 000000000..384983b42 --- /dev/null +++ b/app/Tests/Serialization/Fixtures/runtime-store-tokenizer-request.json @@ -0,0 +1 @@ +{"model_id":"model-a","file_path":"/samples/tokenizer.json"} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/runtime-validate-tokenizer-request.json b/app/Tests/Serialization/Fixtures/runtime-validate-tokenizer-request.json new file mode 100644 index 000000000..5773203ca --- /dev/null +++ b/app/Tests/Serialization/Fixtures/runtime-validate-tokenizer-request.json @@ -0,0 +1 @@ +{"file_path":"/samples/tokenizer.json"} \ No newline at end of file diff --git a/app/Tests/Serialization/JsonGoldenRequestTests.cs b/app/Tests/Serialization/JsonGoldenRequestTests.cs index 408e2384a..d63c4f388 100644 --- a/app/Tests/Serialization/JsonGoldenRequestTests.cs +++ b/app/Tests/Serialization/JsonGoldenRequestTests.cs @@ -1,6 +1,8 @@ using System.Text.Json; using AIStudio.Provider; +using AIStudio.Tools.Rust; +using AIStudio.Tools.Services; namespace AIStudio.Tests.Serialization; @@ -9,9 +11,9 @@ namespace AIStudio.Tests.Serialization; /// /// /// The files in the folder Fixtures were written with the contracts of reflection, before AI Studio got -/// contracts of its own. A provider reads every byte of a request, and one which got written otherwise, e.g., -/// with a field under another name or without the tools, gets refused or misread. The requests are made up, -/// see . +/// contracts of its own. A provider or the runtime reads every byte of a request, and one which got written +/// otherwise, e.g., with a field under another name or without the tools, gets refused or misread. The requests +/// are made up, see . /// [TestFixture] public sealed class JsonGoldenRequestTests @@ -24,6 +26,14 @@ public sealed class JsonGoldenRequestTests yield return new("anthropic-messages-request.json", () => JsonSampleRequests.Anthropic(), ProviderJsonOptions.OPTIONS); yield return new("embedding-request.json", JsonSampleRequests.Embedding, ProviderJsonOptions.OPTIONS); yield return new("google-embedding-request.json", JsonSampleRequests.GoogleEmbedding, ProviderJsonOptions.OPTIONS); + + // The requests to the runtime which were anonymous types once: + yield return new("runtime-validate-tokenizer-request.json", () => new ValidateTokenizerRequest("/samples/tokenizer.json"), RustService.JSON_OPTIONS); + yield return new("runtime-store-tokenizer-request.json", () => new StoreTokenizerRequest("model-a", "/samples/tokenizer.json"), RustService.JSON_OPTIONS); + yield return new("runtime-delete-tokenizer-request.json", () => new DeleteTokenizerRequest("model-a"), RustService.JSON_OPTIONS); + yield return new("runtime-count-tokens-request.json", () => new CountTokensRequest("Count these tokens.", "/samples/tokenizer.json"), RustService.JSON_OPTIONS); + yield return new("runtime-prepare-image-request.json", () => new PrepareImageRequest("/samples/picture.png", true), RustService.JSON_OPTIONS); + yield return new("runtime-prepare-image-for-model-request.json", () => new PrepareImageForModelRequest("/samples/picture.png", 5_242_880), RustService.JSON_OPTIONS); } [TestCaseSource(nameof(GoldenRequests))] diff --git a/app/Tests/Serialization/JsonTestRoots.Runtime.cs b/app/Tests/Serialization/JsonTestRoots.Runtime.cs index 3c4c38fc3..b0d827989 100644 --- a/app/Tests/Serialization/JsonTestRoots.Runtime.cs +++ b/app/Tests/Serialization/JsonTestRoots.Runtime.cs @@ -31,6 +31,12 @@ public static partial class JsonTestRoots yield return Case(new(typeof(SetInterfaceSizeRequest), options)); yield return Case(new(typeof(CreateMediaJobRequest), options)); yield return Case(new(typeof(LogEventRequest), options)); + yield return Case(new(typeof(ValidateTokenizerRequest), options)); + yield return Case(new(typeof(StoreTokenizerRequest), options)); + yield return Case(new(typeof(DeleteTokenizerRequest), options)); + yield return Case(new(typeof(CountTokensRequest), options)); + yield return Case(new(typeof(PrepareImageRequest), options)); + yield return Case(new(typeof(PrepareImageForModelRequest), options)); // Every request of the vector store: foreach (var type in typeof(QdrantEdgeClientImplementation).GetNestedTypes(BindingFlags.NonPublic).Where(type => type.Name.EndsWith("Request", StringComparison.Ordinal)).OrderBy(type => type.Name, StringComparer.Ordinal))