mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-03 23:47:21 +00:00
Generated the JSON contracts of the runtime calls at compile time
This commit is contained in:
8 files changed
+177
-22
No files matched your search
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads the events of a file extraction from the runtime, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A converter reads the metadata of an event as the type its JSON tells, so each of these types needs a
|
||||
/// contract of its own here. A test fails for one which is missing.
|
||||
/// </remarks>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(ContentStreamSseEvent))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(ContentStreamTextMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamPdfMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamSpreadsheetMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamPresentationMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamImageMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamDocumentMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamErrorMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamPromptInjectionMetadata))]
|
||||
internal sealed partial class RustContentStreamJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Tools.Databases.VectorStore;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes nearly every call to the runtime, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The calls to the vector store are generic: every query of its client needs the answer type with the type
|
||||
/// of its result, e.g., DatabaseQueryResponse<bool?>. A test fails for a request or an answer which is
|
||||
/// missing here.
|
||||
/// </remarks>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
|
||||
// The requests:
|
||||
[JsonSerializable(typeof(SelectSecretRequest))]
|
||||
[JsonSerializable(typeof(StoreSecretRequest))]
|
||||
[JsonSerializable(typeof(RustService.ShareFileRequest))]
|
||||
[JsonSerializable(typeof(PreviousDirectory))]
|
||||
[JsonSerializable(typeof(SelectFileOptions))]
|
||||
[JsonSerializable(typeof(SaveFileOptions))]
|
||||
[JsonSerializable(typeof(OpenPathRequest))]
|
||||
[JsonSerializable(typeof(OpenDocumentRequest))]
|
||||
[JsonSerializable(typeof(RegisterShortcutRequest))]
|
||||
[JsonSerializable(typeof(ValidateShortcutRequest))]
|
||||
[JsonSerializable(typeof(SetInterfaceSizeRequest))]
|
||||
[JsonSerializable(typeof(CreateMediaJobRequest))]
|
||||
[JsonSerializable(typeof(LogEventRequest))]
|
||||
[JsonSerializable(typeof(ValidateTokenizerRequest))]
|
||||
[JsonSerializable(typeof(StoreTokenizerRequest))]
|
||||
[JsonSerializable(typeof(DeleteTokenizerRequest))]
|
||||
[JsonSerializable(typeof(CountTokensRequest))]
|
||||
[JsonSerializable(typeof(PrepareImageRequest))]
|
||||
[JsonSerializable(typeof(PrepareImageForModelRequest))]
|
||||
|
||||
// The answers:
|
||||
[JsonSerializable(typeof(RequestedSecret))]
|
||||
[JsonSerializable(typeof(StoreSecretResponse))]
|
||||
[JsonSerializable(typeof(DeleteSecretResponse))]
|
||||
[JsonSerializable(typeof(ShareFileResponse))]
|
||||
[JsonSerializable(typeof(ImagePrepareResponse))]
|
||||
[JsonSerializable(typeof(PreparedModelImage))]
|
||||
[JsonSerializable(typeof(TauriEvent))]
|
||||
[JsonSerializable(typeof(UpdateResponse))]
|
||||
[JsonSerializable(typeof(SetClipboardResponse))]
|
||||
[JsonSerializable(typeof(DirectorySelectionResponse))]
|
||||
[JsonSerializable(typeof(FileSelectionResponse))]
|
||||
[JsonSerializable(typeof(FilesSelectionResponse))]
|
||||
[JsonSerializable(typeof(FileSaveResponse))]
|
||||
[JsonSerializable(typeof(OpenPathResponse))]
|
||||
[JsonSerializable(typeof(OpenDocumentResponse))]
|
||||
[JsonSerializable(typeof(ShortcutRegistrationResult))]
|
||||
[JsonSerializable(typeof(ShortcutValidationResponse))]
|
||||
[JsonSerializable(typeof(ShortcutResponse))]
|
||||
[JsonSerializable(typeof(RuntimeInfoResponse))]
|
||||
[JsonSerializable(typeof(AppExitResponse))]
|
||||
[JsonSerializable(typeof(AppRestartResponse))]
|
||||
[JsonSerializable(typeof(InterfaceSizeResponse))]
|
||||
[JsonSerializable(typeof(CreateMediaJobResponse))]
|
||||
[JsonSerializable(typeof(MediaJobEvent))]
|
||||
[JsonSerializable(typeof(List<EnterpriseConfig>))]
|
||||
[JsonSerializable(typeof(GetLogPathsResponse))]
|
||||
[JsonSerializable(typeof(TokenizerResponse))]
|
||||
|
||||
// The vector store, with the result of every query its client sends:
|
||||
[JsonSerializable(typeof(QdrantEdgeInfo))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.EnsureVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.CheckVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.InsertEmbeddingRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.SearchEmbeddingRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.RetrieveVectorsRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.DeleteEmbeddingByFileRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.RelocateEmbeddingFileRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.OptimizeVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.DeleteVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.PrepareBackupRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.CopyStoreForBackupRequest))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseOperationResponse))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<VectorStoreEnsureResult>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<bool?>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<List<VectorSearchResult>>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<List<StoredVector>>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<VectorStoreBackupPreparation>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<long?>))]
|
||||
internal sealed partial class RustJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes the calls to the prompt injection filter of the
|
||||
/// runtime, built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsRequest))]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsResponse))]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsBatchRequest))]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsBatchResponse))]
|
||||
internal sealed partial class RustPromptInjectionJsonContext : JsonSerializerContext;
|
||||
@@ -112,7 +112,7 @@ public sealed partial class RustService
|
||||
|
||||
try
|
||||
{
|
||||
var sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent);
|
||||
var sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent, CONTENT_STREAM_JSON_OPTIONS);
|
||||
if (sseEvent is null)
|
||||
continue;
|
||||
|
||||
@@ -364,7 +364,7 @@ public sealed partial class RustService
|
||||
ContentStreamSseEvent? sseEvent = null;
|
||||
try
|
||||
{
|
||||
sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent);
|
||||
sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent, CONTENT_STREAM_JSON_OPTIONS);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@@ -460,7 +460,7 @@ public sealed partial class RustService
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorMessage = JsonSerializer.Deserialize<string>(jsonContent);
|
||||
var errorMessage = JsonSerializer.Deserialize<string>(jsonContent, CONTENT_STREAM_JSON_OPTIONS);
|
||||
if (string.IsNullOrWhiteSpace(errorMessage))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ public sealed partial class RustService
|
||||
using var response = await this.http.PostAsJsonAsync(
|
||||
"/security/prompt-injection/sanitize",
|
||||
new SanitizePromptInjectionsRequest(text),
|
||||
cancellationToken: timeoutTokenSource.Token);
|
||||
PROMPT_INJECTION_JSON_OPTIONS,
|
||||
timeoutTokenSource.Token);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -48,7 +49,7 @@ public sealed partial class RustService
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsResponse>(timeoutTokenSource.Token);
|
||||
return await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsResponse>(PROMPT_INJECTION_JSON_OPTIONS, timeoutTokenSource.Token);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -82,7 +83,8 @@ public sealed partial class RustService
|
||||
using var response = await this.http.PostAsJsonAsync(
|
||||
"/security/prompt-injection/sanitize-batch",
|
||||
new SanitizePromptInjectionsBatchRequest(texts),
|
||||
cancellationToken: timeoutTokenSource.Token);
|
||||
PROMPT_INJECTION_JSON_OPTIONS,
|
||||
timeoutTokenSource.Token);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -90,7 +92,7 @@ public sealed partial class RustService
|
||||
return null;
|
||||
}
|
||||
|
||||
var batchResponse = await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsBatchResponse>(timeoutTokenSource.Token);
|
||||
var batchResponse = await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsBatchResponse>(PROMPT_INJECTION_JSON_OPTIONS, timeoutTokenSource.Token);
|
||||
if (batchResponse.Results is null || batchResponse.Results.Count != texts.Count)
|
||||
{
|
||||
this.logger?.LogError("The prompt injection filter answered with {ResultCount} result(s) for {TextCount} text(s).", batchResponse.Results?.Count ?? 0, texts.Count);
|
||||
|
||||
@@ -37,15 +37,38 @@ public sealed partial class RustService : BackgroundService
|
||||
/// <summary>
|
||||
/// The JSON options of nearly every call to the runtime, which names its fields in snake case.
|
||||
/// </summary>
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
Converters =
|
||||
{
|
||||
new RustEnumConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
TypeInfoResolver = RustJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// The JSON options of the events of a file extraction, which name their fields themselves.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same as the defaults of System.Text.Json, with which these events were read before.
|
||||
/// </remarks>
|
||||
internal static readonly JsonSerializerOptions CONTENT_STREAM_JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
TypeInfoResolver = RustContentStreamJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// The JSON options of the prompt injection filter, which names its fields in camel case.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same as the defaults of the JSON calls of HttpClient, with which the filter was called before.
|
||||
/// </remarks>
|
||||
internal static readonly JsonSerializerOptions PROMPT_INJECTION_JSON_OPTIONS = new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
{
|
||||
TypeInfoResolver = RustPromptInjectionJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
private ILogger<RustService>? logger;
|
||||
private Encryption? encryptor;
|
||||
private string? cachedUserLanguage;
|
||||
|
||||
@@ -16,6 +16,8 @@ public sealed class JsonCoverageTests
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.Stored))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ProviderRequests))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ProviderAnswers))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeRequests))]
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeAnswers))]
|
||||
public void EveryContractComesFromSourceGeneration(JsonTestRoot root)
|
||||
{
|
||||
var typesWithoutGeneratedContract = JsonTypeGraph.Collect(root.Type, root.Options)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Tools.Databases.VectorStore;
|
||||
@@ -42,9 +41,9 @@ public static partial class JsonTestRoots
|
||||
foreach (var type in typeof(QdrantEdgeClientImplementation).GetNestedTypes(BindingFlags.NonPublic).Where(type => type.Name.EndsWith("Request", StringComparison.Ordinal)).OrderBy(type => type.Name, StringComparer.Ordinal))
|
||||
yield return Case(new(type, options));
|
||||
|
||||
// The prompt injection filter, which gets called with the defaults of HttpClient, i.e., with camel case:
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsRequest), JsonSerializerOptions.Web));
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsBatchRequest), JsonSerializerOptions.Web));
|
||||
// The prompt injection filter, which names its fields in camel case:
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsRequest), RustService.PROMPT_INJECTION_JSON_OPTIONS));
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsBatchRequest), RustService.PROMPT_INJECTION_JSON_OPTIONS));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,16 +93,16 @@ public static partial class JsonTestRoots
|
||||
yield return Case(new(typeof(RustService.DatabaseQueryResponse<long?>), options));
|
||||
|
||||
//
|
||||
// The events of a file extraction, which get read with the defaults of System.Text.Json. A converter reads
|
||||
// their metadata as the type the JSON tells:
|
||||
// The events of a file extraction, which name their fields themselves. A converter reads their metadata as
|
||||
// the type the JSON tells:
|
||||
//
|
||||
yield return Case(new(typeof(ContentStreamSseEvent), JsonSerializerOptions.Default));
|
||||
yield return Case(new(typeof(string), JsonSerializerOptions.Default), "error of a file extraction");
|
||||
yield return Case(new(typeof(ContentStreamSseEvent), RustService.CONTENT_STREAM_JSON_OPTIONS));
|
||||
yield return Case(new(typeof(string), RustService.CONTENT_STREAM_JSON_OPTIONS), "error of a file extraction");
|
||||
foreach (var type in ImplementationsOf(typeof(ContentStreamSseMetadata)))
|
||||
yield return Case(new(type, JsonSerializerOptions.Default));
|
||||
yield return Case(new(type, RustService.CONTENT_STREAM_JSON_OPTIONS));
|
||||
|
||||
// The prompt injection filter, which gets called with the defaults of HttpClient, i.e., with camel case:
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsResponse), JsonSerializerOptions.Web));
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsBatchResponse), JsonSerializerOptions.Web));
|
||||
// The prompt injection filter, which names its fields in camel case:
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsResponse), RustService.PROMPT_INJECTION_JSON_OPTIONS));
|
||||
yield return Case(new(typeof(SanitizePromptInjectionsBatchResponse), RustService.PROMPT_INJECTION_JSON_OPTIONS));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user