diff --git a/tools/code/common.tests/ApiDiagnostic.cs b/tools/code/common.tests/ApiDiagnostic.cs index 4ca2482e..f929bfab 100644 --- a/tools/code/common.tests/ApiDiagnostic.cs +++ b/tools/code/common.tests/ApiDiagnostic.cs @@ -25,12 +25,46 @@ from type in Gen.Const("fixed") }; } +public sealed record ApiDiagnosticLargeLanguageModelMessages +{ + public required string Messages { get; init; } + public required int MaxSizeInBytes { get; init; } + + public static Gen Generate() => + from messages in Gen.OneOfConst("all", "errors") + from maxSize in Gen.Int[0, 65536] + select new ApiDiagnosticLargeLanguageModelMessages + { + Messages = messages, + MaxSizeInBytes = maxSize + }; +} + +public sealed record ApiDiagnosticLargeLanguageModel +{ + public Option Logs { get; init; } + public Option Requests { get; init; } + public Option Responses { get; init; } + + public static Gen Generate() => + from logs in Gen.OneOfConst("enabled", "disabled").OptionOf() + from requests in ApiDiagnosticLargeLanguageModelMessages.Generate().OptionOf() + from responses in ApiDiagnosticLargeLanguageModelMessages.Generate().OptionOf() + select new ApiDiagnosticLargeLanguageModel + { + Logs = logs, + Requests = requests, + Responses = responses + }; +} + public record ApiDiagnosticModel { public required ApiDiagnosticName Name { get; init; } public required LoggerName LoggerName { get; init; } public Option AlwaysLog { get; init; } public Option Sampling { get; init; } + public Option LargeLanguageModel { get; init; } public static Gen Generate() => from loggerType in LoggerType.Generate() @@ -38,12 +72,14 @@ from loggerName in LoggerModel.GenerateName(loggerType) from name in GenerateName(loggerType) from alwaysLog in Gen.Const("allErrors").OptionOf() from sampling in ApiDiagnosticSampling.Generate().OptionOf() + from largeLanguageModel in ApiDiagnosticLargeLanguageModel.Generate().OptionOf() select new ApiDiagnosticModel { Name = name, LoggerName = loggerName, AlwaysLog = alwaysLog, - Sampling = sampling + Sampling = sampling, + LargeLanguageModel = largeLanguageModel }; public static Gen GenerateName() => @@ -58,7 +94,9 @@ private static Gen GenerateName(LoggerType loggerType) => }; public static Gen> GenerateSet() => - Generate().FrozenSetOf(0, 20, Comparer); + from diagnostics in Generate().FrozenSetOf(0, 20, Comparer) + from largeLanguageModel in ApiDiagnosticLargeLanguageModel.Generate() + select EnsureLargeLanguageModel(diagnostics, largeLanguageModel); public static Gen> GenerateSet(FrozenSet originalSet, ICollection loggers) { @@ -77,10 +115,24 @@ select diagnostic with LoggerName = logger.Name }) .SequenceToFrozenSet(originalSet.Comparer) - select updates; + from largeLanguageModel in ApiDiagnosticLargeLanguageModel.Generate() + select EnsureLargeLanguageModel(updates, largeLanguageModel); } private static IEqualityComparer Comparer { get; } = EqualityComparerBuilder.For() .EquateBy(x => x.Name); + + private static FrozenSet EnsureLargeLanguageModel(FrozenSet diagnostics, ApiDiagnosticLargeLanguageModel largeLanguageModel) + { + if (diagnostics.Count == 0 || diagnostics.Any(diagnostic => diagnostic.LargeLanguageModel.IsSome)) + { + return diagnostics; + } + + var diagnosticArray = diagnostics.ToArray(); + diagnosticArray[0] = diagnosticArray[0] with { LargeLanguageModel = LanguageExt.Prelude.Some(largeLanguageModel) }; + + return diagnosticArray.ToFrozenSet(Comparer); + } } diff --git a/tools/code/common.tests/ApiDiagnosticSerializationTests.cs b/tools/code/common.tests/ApiDiagnosticSerializationTests.cs new file mode 100644 index 00000000..f2e7270c --- /dev/null +++ b/tools/code/common.tests/ApiDiagnosticSerializationTests.cs @@ -0,0 +1,165 @@ +using common; +using FluentAssertions; +using System.Text.Json; +using System.Text.Json.Nodes; +using Xunit; + +namespace common.tests; + +public class ApiDiagnosticSerializationTests +{ + [Fact] + public void ApiDiagnosticDto_Deserializes_WhenLargeLanguageModelMissing() + { + const string json = """ + { + "properties": { + "loggerId": "/loggers/test-logger" + } + } + """; + + var dto = JsonSerializer.Deserialize(json, JsonObjectExtensions.SerializerOptions); + + dto.Should().NotBeNull(); + dto!.Properties.LargeLanguageModel.Should().BeNull(); + } + + [Fact] + public void DiagnosticDto_Deserializes_WhenLargeLanguageModelMissing() + { + const string json = """ + { + "properties": { + "loggerId": "/loggers/test-logger" + } + } + """; + + var dto = JsonSerializer.Deserialize(json, JsonObjectExtensions.SerializerOptions); + + dto.Should().NotBeNull(); + dto!.Properties.LargeLanguageModel.Should().BeNull(); + } + + [Fact] + public void ApiDiagnosticDto_UsesCamelCase_ForLargeLanguageModel() + { + var dto = new ApiDiagnosticDto + { + Properties = new ApiDiagnosticDto.DiagnosticContract + { + LargeLanguageModel = new ApiDiagnosticDto.LargeLanguageModelSettings + { + Logs = "enabled", + Requests = new ApiDiagnosticDto.LargeLanguageModelMessageSettings + { + Messages = "all", + MaxSizeInBytes = 4096 + }, + Responses = new ApiDiagnosticDto.LargeLanguageModelMessageSettings + { + Messages = "errors", + MaxSizeInBytes = 1024 + } + } + } + }; + + var json = JsonSerializer.Serialize(dto, JsonObjectExtensions.SerializerOptions); + var node = JsonNode.Parse(json)!.AsObject(); + + var properties = node["properties"]!.AsObject(); + properties.ContainsKey("largeLanguageModel").Should().BeTrue(); + + var llm = properties["largeLanguageModel"]!.AsObject(); + llm.ContainsKey("logs").Should().BeTrue(); + llm.ContainsKey("requests").Should().BeTrue(); + llm.ContainsKey("responses").Should().BeTrue(); + + var requests = llm["requests"]!.AsObject(); + requests["maxSizeInBytes"]!.GetValue().Should().Be(4096); + + var responses = llm["responses"]!.AsObject(); + responses["maxSizeInBytes"]!.GetValue().Should().Be(1024); + } + + [Theory] + [InlineData(0)] + [InlineData(65536)] + public void ApiDiagnosticDto_SerializesEdge_MaxSizeInBytes(int maxSize) + { + var dto = new ApiDiagnosticDto + { + Properties = new ApiDiagnosticDto.DiagnosticContract + { + LargeLanguageModel = new ApiDiagnosticDto.LargeLanguageModelSettings + { + Requests = new ApiDiagnosticDto.LargeLanguageModelMessageSettings + { + Messages = "all", + MaxSizeInBytes = maxSize + }, + Responses = new ApiDiagnosticDto.LargeLanguageModelMessageSettings + { + Messages = "all", + MaxSizeInBytes = maxSize + } + } + } + }; + + var roundTripped = JsonSerializer.Deserialize( + JsonSerializer.Serialize(dto, JsonObjectExtensions.SerializerOptions), + JsonObjectExtensions.SerializerOptions); + + roundTripped!.Properties.LargeLanguageModel!.Requests!.MaxSizeInBytes.Should().Be(maxSize); + roundTripped.Properties.LargeLanguageModel!.Responses!.MaxSizeInBytes.Should().Be(maxSize); + } + + [Theory] + [InlineData(0)] + [InlineData(65536)] + public void DiagnosticDto_SerializesEdge_MaxSizeInBytes(int maxSize) + { + var dto = new DiagnosticDto + { + Properties = new DiagnosticDto.DiagnosticContract + { + LargeLanguageModel = new DiagnosticDto.LargeLanguageModelSettings + { + Logs = "disabled", + Requests = new DiagnosticDto.LargeLanguageModelMessageSettings + { + Messages = "errors", + MaxSizeInBytes = maxSize + }, + Responses = new DiagnosticDto.LargeLanguageModelMessageSettings + { + Messages = "all", + MaxSizeInBytes = maxSize + } + } + } + }; + + var json = JsonSerializer.Serialize(dto, JsonObjectExtensions.SerializerOptions); + var node = JsonNode.Parse(json)!.AsObject(); + + var properties = node["properties"]!.AsObject(); + properties.ContainsKey("largeLanguageModel").Should().BeTrue(); + + var llm = properties["largeLanguageModel"]!.AsObject(); + llm.ContainsKey("logs").Should().BeTrue(); + llm.ContainsKey("requests").Should().BeTrue(); + llm.ContainsKey("responses").Should().BeTrue(); + + var requests = llm["requests"]!.AsObject(); + requests["messages"]!.GetValue().Should().Be("errors"); + requests["maxSizeInBytes"]!.GetValue().Should().Be(maxSize); + + var responses = llm["responses"]!.AsObject(); + responses["messages"]!.GetValue().Should().Be("all"); + responses["maxSizeInBytes"]!.GetValue().Should().Be(maxSize); + } +} diff --git a/tools/code/common.tests/Diagnostic.cs b/tools/code/common.tests/Diagnostic.cs index 64d1dd34..af391793 100644 --- a/tools/code/common.tests/Diagnostic.cs +++ b/tools/code/common.tests/Diagnostic.cs @@ -20,12 +20,46 @@ from type in Gen.Const("fixed") }; } +public sealed record DiagnosticLargeLanguageModelMessages +{ + public required string Messages { get; init; } + public required int MaxSizeInBytes { get; init; } + + public static Gen Generate() => + from messages in Gen.OneOfConst("all", "errors") + from maxSize in Gen.Int[0, 65536] + select new DiagnosticLargeLanguageModelMessages + { + Messages = messages, + MaxSizeInBytes = maxSize + }; +} + +public sealed record DiagnosticLargeLanguageModel +{ + public Option Logs { get; init; } + public Option Requests { get; init; } + public Option Responses { get; init; } + + public static Gen Generate() => + from logs in Gen.OneOfConst("enabled", "disabled").OptionOf() + from requests in DiagnosticLargeLanguageModelMessages.Generate().OptionOf() + from responses in DiagnosticLargeLanguageModelMessages.Generate().OptionOf() + select new DiagnosticLargeLanguageModel + { + Logs = logs, + Requests = requests, + Responses = responses + }; +} + public record DiagnosticModel { public required DiagnosticName Name { get; init; } public required LoggerName LoggerName { get; init; } public Option AlwaysLog { get; init; } public Option Sampling { get; init; } + public Option LargeLanguageModel { get; init; } public static Gen Generate() => from name in GenerateName() @@ -33,12 +67,14 @@ from loggerType in LoggerType.Generate() from loggerName in LoggerModel.GenerateName(loggerType) from alwaysLog in Gen.Const("allErrors").OptionOf() from sampling in DiagnosticSampling.Generate().OptionOf() + from largeLanguageModel in DiagnosticLargeLanguageModel.Generate().OptionOf() select new DiagnosticModel { Name = name, LoggerName = loggerName, AlwaysLog = alwaysLog, - Sampling = sampling + Sampling = sampling, + LargeLanguageModel = largeLanguageModel }; public static Gen GenerateName() => @@ -46,5 +82,20 @@ from name in Generator.AlphaNumericStringBetween(10, 20) select DiagnosticName.From(name); public static Gen> GenerateSet() => - Generate().FrozenSetOf(x => x.Name, 0, 20); + from diagnostics in Generate().FrozenSetOf(x => x.Name, 0, 20) + from largeLanguageModel in DiagnosticLargeLanguageModel.Generate() + select EnsureLargeLanguageModel(diagnostics, largeLanguageModel); + + private static FrozenSet EnsureLargeLanguageModel(FrozenSet diagnostics, DiagnosticLargeLanguageModel largeLanguageModel) + { + if (diagnostics.Count == 0 || diagnostics.Any(diagnostic => diagnostic.LargeLanguageModel.IsSome)) + { + return diagnostics; + } + + var diagnosticArray = diagnostics.ToArray(); + diagnosticArray[0] = diagnosticArray[0] with { LargeLanguageModel = LanguageExt.Prelude.Some(largeLanguageModel) }; + + return diagnosticArray.ToFrozenSet(x => x.Name); + } } diff --git a/tools/code/common.tests/common.tests.csproj b/tools/code/common.tests/common.tests.csproj index f0f13a0a..d7056be0 100644 --- a/tools/code/common.tests/common.tests.csproj +++ b/tools/code/common.tests/common.tests.csproj @@ -7,12 +7,19 @@ 8-all CA1034,CA1062,CA1724,CA2007,CA1848,CA1716,NU1903 enable + true + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/tools/code/common/ApiDiagnostic.cs b/tools/code/common/ApiDiagnostic.cs index bfe78193..7f696354 100644 --- a/tools/code/common/ApiDiagnostic.cs +++ b/tools/code/common/ApiDiagnostic.cs @@ -147,6 +147,10 @@ public sealed record DiagnosticContract [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? HttpCorrelationProtocol { get; init; } + [JsonPropertyName("largeLanguageModel")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelSettings? LargeLanguageModel { get; init; } + [JsonPropertyName("logClientIp")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool? LogClientIp { get; init; } @@ -233,6 +237,32 @@ public sealed record SamplingSettings [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? SamplingType { get; init; } } + + public sealed record LargeLanguageModelSettings + { + [JsonPropertyName("logs")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? Logs { get; init; } + + [JsonPropertyName("requests")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelMessageSettings? Requests { get; init; } + + [JsonPropertyName("responses")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelMessageSettings? Responses { get; init; } + } + + public sealed record LargeLanguageModelMessageSettings + { + [JsonPropertyName("messages")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? Messages { get; init; } + + [JsonPropertyName("maxSizeInBytes")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int? MaxSizeInBytes { get; init; } + } } public static class ApiDiagnosticModule @@ -262,7 +292,7 @@ public static IAsyncEnumerable ListNames(this ApiDiagnosticsU public static async ValueTask GetDto(this ApiDiagnosticUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) { var content = await pipeline.GetContent(uri.ToUri(), cancellationToken); - return content.ToObjectFromJson(); + return content.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); } public static async ValueTask Delete(this ApiDiagnosticUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) => @@ -270,7 +300,7 @@ public static async ValueTask Delete(this ApiDiagnosticUri uri, HttpPipeline pip public static async ValueTask PutDto(this ApiDiagnosticUri uri, ApiDiagnosticDto dto, HttpPipeline pipeline, CancellationToken cancellationToken) { - var content = BinaryData.FromObjectAsJson(dto); + var content = BinaryData.FromObjectAsJson(dto, JsonObjectExtensions.SerializerOptions); await pipeline.PutContent(uri.ToUri(), content, cancellationToken); } @@ -301,6 +331,6 @@ public static async ValueTask WriteDto(this ApiDiagnosticInformationFile file, A public static async ValueTask ReadDto(this ApiDiagnosticInformationFile file, CancellationToken cancellationToken) { var content = await file.ToFileInfo().ReadAsBinaryData(cancellationToken); - return content.ToObjectFromJson(); + return content.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); } } diff --git a/tools/code/common/Diagnostic.cs b/tools/code/common/Diagnostic.cs index 8878f60b..4bbcd9f6 100644 --- a/tools/code/common/Diagnostic.cs +++ b/tools/code/common/Diagnostic.cs @@ -144,6 +144,10 @@ public sealed record DiagnosticContract [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? HttpCorrelationProtocol { get; init; } + [JsonPropertyName("largeLanguageModel")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelSettings? LargeLanguageModel { get; init; } + [JsonPropertyName("logClientIp")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool? LogClientIp { get; init; } @@ -230,6 +234,32 @@ public sealed record SamplingSettings [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? SamplingType { get; init; } } + + public sealed record LargeLanguageModelSettings + { + [JsonPropertyName("logs")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? Logs { get; init; } + + [JsonPropertyName("requests")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelMessageSettings? Requests { get; init; } + + [JsonPropertyName("responses")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelMessageSettings? Responses { get; init; } + } + + public sealed record LargeLanguageModelMessageSettings + { + [JsonPropertyName("messages")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? Messages { get; init; } + + [JsonPropertyName("maxSizeInBytes")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int? MaxSizeInBytes { get; init; } + } } public static class DiagnosticModule @@ -257,13 +287,13 @@ public static IAsyncEnumerable ListNames(this DiagnosticsUri uri public static async ValueTask> TryGetDto(this DiagnosticUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) { var contentOption = await pipeline.GetContentOption(uri.ToUri(), cancellationToken); - return contentOption.Map(content => content.ToObjectFromJson()); + return contentOption.Map(content => content.ToObjectFromJson(JsonObjectExtensions.SerializerOptions)); } public static async ValueTask GetDto(this DiagnosticUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) { var content = await pipeline.GetContent(uri.ToUri(), cancellationToken); - return content.ToObjectFromJson(); + return content.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); } public static async ValueTask Delete(this DiagnosticUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) => @@ -271,7 +301,7 @@ public static async ValueTask Delete(this DiagnosticUri uri, HttpPipeline pipeli public static async ValueTask PutDto(this DiagnosticUri uri, DiagnosticDto dto, HttpPipeline pipeline, CancellationToken cancellationToken) { - var content = BinaryData.FromObjectAsJson(dto); + var content = BinaryData.FromObjectAsJson(dto, JsonObjectExtensions.SerializerOptions); await pipeline.PutContent(uri.ToUri(), content, cancellationToken); } @@ -299,11 +329,11 @@ public static async ValueTask WriteDto(this DiagnosticInformationFile file, Diag public static async ValueTask ReadDto(this DiagnosticInformationFile file, CancellationToken cancellationToken) { var content = await file.ToFileInfo().ReadAsBinaryData(cancellationToken); - return content.ToObjectFromJson(); + return content.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); } public static Option TryGetLoggerName(DiagnosticDto dto) => from loggerId in Prelude.Optional(dto.Properties.LoggerId) from loggerNameString in loggerId.Split('/').LastOrNone() select LoggerName.From(loggerNameString); -} \ No newline at end of file +} diff --git a/tools/code/common/WorkspaceDiagnostic.cs b/tools/code/common/WorkspaceDiagnostic.cs index 98fb532a..dd45bcd6 100644 --- a/tools/code/common/WorkspaceDiagnostic.cs +++ b/tools/code/common/WorkspaceDiagnostic.cs @@ -139,6 +139,10 @@ public sealed record DiagnosticContract [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? HttpCorrelationProtocol { get; init; } + [JsonPropertyName("largeLanguageModel")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelSettings? LargeLanguageModel { get; init; } + [JsonPropertyName("logClientIp")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool? LogClientIp { get; init; } @@ -225,6 +229,32 @@ public sealed record SamplingSettings [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string? SamplingType { get; init; } } + + public sealed record LargeLanguageModelSettings + { + [JsonPropertyName("logs")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? Logs { get; init; } + + [JsonPropertyName("requests")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelMessageSettings? Requests { get; init; } + + [JsonPropertyName("responses")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public LargeLanguageModelMessageSettings? Responses { get; init; } + } + + public sealed record LargeLanguageModelMessageSettings + { + [JsonPropertyName("messages")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? Messages { get; init; } + + [JsonPropertyName("maxSizeInBytes")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int? MaxSizeInBytes { get; init; } + } } public static class WorkspaceDiagnosticModule @@ -246,7 +276,7 @@ public static IAsyncEnumerable ListNames(this WorkspaceDiagnosti public static async ValueTask GetDto(this WorkspaceDiagnosticUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) { var content = await pipeline.GetContent(uri.ToUri(), cancellationToken); - return content.ToObjectFromJson(); + return content.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); } public static async ValueTask Delete(this WorkspaceDiagnosticUri uri, HttpPipeline pipeline, CancellationToken cancellationToken) => @@ -254,7 +284,7 @@ public static async ValueTask Delete(this WorkspaceDiagnosticUri uri, HttpPipeli public static async ValueTask PutDto(this WorkspaceDiagnosticUri uri, WorkspaceDiagnosticDto dto, HttpPipeline pipeline, CancellationToken cancellationToken) { - var content = BinaryData.FromObjectAsJson(dto); + var content = BinaryData.FromObjectAsJson(dto, JsonObjectExtensions.SerializerOptions); await pipeline.PutContent(uri.ToUri(), content, cancellationToken); } @@ -267,6 +297,6 @@ public static async ValueTask WriteDto(this WorkspaceDiagnosticInformationFile f public static async ValueTask ReadDto(this WorkspaceDiagnosticInformationFile file, CancellationToken cancellationToken) { var content = await file.ToFileInfo().ReadAsBinaryData(cancellationToken); - return content.ToObjectFromJson(); + return content.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); } -} \ No newline at end of file +} diff --git a/tools/code/integration.tests/ApiDiagnostic.cs b/tools/code/integration.tests/ApiDiagnostic.cs index 2c983dfb..a6c1ee4a 100644 --- a/tools/code/integration.tests/ApiDiagnostic.cs +++ b/tools/code/integration.tests/ApiDiagnostic.cs @@ -60,25 +60,10 @@ async ValueTask put(ApiDiagnosticModel model, ApiName apiName, ManagementService { var serviceUri = getServiceUri(serviceName); var diagnosticUri = ApiDiagnosticUri.From(model.Name, apiName, serviceUri); - var dto = getDto(model); + var dto = MapToDto(model); await diagnosticUri.PutDto(dto, pipeline, cancellationToken); } - - static ApiDiagnosticDto getDto(ApiDiagnosticModel model) => - new() - { - Properties = new ApiDiagnosticDto.DiagnosticContract - { - LoggerId = $"/loggers/{model.LoggerName}", - AlwaysLog = model.AlwaysLog.ValueUnsafe(), - Sampling = model.Sampling.Map(sampling => new ApiDiagnosticDto.SamplingSettings - { - SamplingType = sampling.Type, - Percentage = sampling.Percentage - }).ValueUnsafe() - } - }; } public static void ConfigureValidateExtractedApiDiagnostics(IHostApplicationBuilder builder) @@ -119,6 +104,7 @@ static string normalizeDto(ApiDiagnosticDto dto) => { LoggerId = string.Join('/', dto.Properties.LoggerId?.Split('/')?.TakeLast(2)?.ToArray() ?? []), AlwaysLog = dto.Properties.AlwaysLog ?? string.Empty, + LargeLanguageModel = NormalizeLargeLanguageModel(dto.Properties.LargeLanguageModel), Sampling = new { Type = dto.Properties.Sampling?.SamplingType ?? string.Empty, @@ -195,7 +181,7 @@ async ValueTask> getWithCo using (contents) { var data = await BinaryData.FromStreamAsync(contents, cancellationToken); - var dto = data.ToObjectFromJson(); + var dto = data.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); return (name, dto); } }); @@ -240,25 +226,10 @@ await models.IterParallel(async model => async ValueTask writeInformationFile(ApiDiagnosticModel model, ApiName apiName, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) { var informationFile = ApiDiagnosticInformationFile.From(model.Name, apiName, serviceDirectory); - var dto = getDto(model); + var dto = MapToDto(model); await informationFile.WriteDto(dto, cancellationToken); } - - static ApiDiagnosticDto getDto(ApiDiagnosticModel model) => - new() - { - Properties = new ApiDiagnosticDto.DiagnosticContract - { - LoggerId = $"/loggers/{model.LoggerName}", - AlwaysLog = model.AlwaysLog.ValueUnsafe(), - Sampling = model.Sampling.Map(sampling => new ApiDiagnosticDto.SamplingSettings - { - SamplingType = sampling.Type, - Percentage = sampling.Percentage - }).ValueUnsafe() - } - }; } public static void ConfigureValidatePublishedApiDiagnostics(IHostApplicationBuilder builder) { @@ -298,6 +269,7 @@ static string normalizeDto(ApiDiagnosticDto dto) => { LoggerId = string.Join('/', dto.Properties.LoggerId?.Split('/')?.TakeLast(2)?.ToArray() ?? []), AlwaysLog = dto.Properties.AlwaysLog ?? string.Empty, + LargeLanguageModel = NormalizeLargeLanguageModel(dto.Properties.LargeLanguageModel), Sampling = new { Type = dto.Properties.Sampling?.SamplingType ?? string.Empty, @@ -305,6 +277,52 @@ static string normalizeDto(ApiDiagnosticDto dto) => } }.ToString()!; } + + private static ApiDiagnosticDto MapToDto(ApiDiagnosticModel model) => + new() + { + Properties = new ApiDiagnosticDto.DiagnosticContract + { + LoggerId = $"/loggers/{model.LoggerName}", + AlwaysLog = model.AlwaysLog.ValueUnsafe(), + Sampling = model.Sampling.Map(sampling => new ApiDiagnosticDto.SamplingSettings + { + SamplingType = sampling.Type, + Percentage = sampling.Percentage + }).ValueUnsafe(), + LargeLanguageModel = model.LargeLanguageModel.Match(MapLargeLanguageModel, () => null) + } + }; + + private static ApiDiagnosticDto.LargeLanguageModelSettings MapLargeLanguageModel(ApiDiagnosticLargeLanguageModel largeLanguageModel) => + new() + { + Logs = largeLanguageModel.Logs.Match(logs => logs, () => (string?)null), + Requests = largeLanguageModel.Requests.Match(MapLargeLanguageModelMessages, () => null), + Responses = largeLanguageModel.Responses.Match(MapLargeLanguageModelMessages, () => null) + }; + + private static ApiDiagnosticDto.LargeLanguageModelMessageSettings MapLargeLanguageModelMessages(ApiDiagnosticLargeLanguageModelMessages messages) => + new() + { + Messages = messages.Messages, + MaxSizeInBytes = messages.MaxSizeInBytes + }; + + private static object NormalizeLargeLanguageModel(ApiDiagnosticDto.LargeLanguageModelSettings? largeLanguageModel) => + new + { + Logs = largeLanguageModel?.Logs ?? string.Empty, + Requests = NormalizeLargeLanguageModelMessages(largeLanguageModel?.Requests), + Responses = NormalizeLargeLanguageModelMessages(largeLanguageModel?.Responses) + }; + + private static object NormalizeLargeLanguageModelMessages(ApiDiagnosticDto.LargeLanguageModelMessageSettings? largeLanguageModelMessages) => + new + { + Messages = largeLanguageModelMessages?.Messages ?? string.Empty, + MaxSizeInBytes = largeLanguageModelMessages?.MaxSizeInBytes ?? 0 + }; } //internal static class ApiDiagnosticModule //{ diff --git a/tools/code/integration.tests/Diagnostic.cs b/tools/code/integration.tests/Diagnostic.cs index d724365b..5a784983 100644 --- a/tools/code/integration.tests/Diagnostic.cs +++ b/tools/code/integration.tests/Diagnostic.cs @@ -89,26 +89,11 @@ async ValueTask put(DiagnosticModel model, ManagementServiceName serviceName, Ca { var serviceUri = getServiceUri(serviceName); - var dto = getDto(model); + var dto = MapToDto(model); await DiagnosticUri.From(model.Name, serviceUri) .PutDto(dto, pipeline, cancellationToken); } - - static DiagnosticDto getDto(DiagnosticModel model) => - new() - { - Properties = new DiagnosticDto.DiagnosticContract - { - LoggerId = $"/loggers/{model.LoggerName}", - AlwaysLog = model.AlwaysLog.ValueUnsafe(), - Sampling = model.Sampling.Map(sampling => new DiagnosticDto.SamplingSettings - { - SamplingType = sampling.Type, - Percentage = sampling.Percentage - }).ValueUnsafe() - } - }; } public static void ConfigureValidateExtractedDiagnostics(IHostApplicationBuilder builder) @@ -154,6 +139,7 @@ static string normalizeDto(DiagnosticDto dto) => { LoggerId = string.Join('/', dto.Properties.LoggerId?.Split('/')?.TakeLast(2)?.ToArray() ?? []), AlwaysLog = dto.Properties.AlwaysLog ?? string.Empty, + LargeLanguageModel = NormalizeLargeLanguageModel(dto.Properties.LargeLanguageModel), Sampling = new { Type = dto.Properties.Sampling?.SamplingType ?? string.Empty, @@ -232,7 +218,7 @@ async ValueTask> getWithCommit(M using (contents) { var data = await BinaryData.FromStreamAsync(contents, cancellationToken); - var dto = data.ToObjectFromJson(); + var dto = data.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); return (name, dto); } }); @@ -275,25 +261,10 @@ await models.IterParallel(async model => static async ValueTask writeInformationFile(DiagnosticModel model, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) { var informationFile = DiagnosticInformationFile.From(model.Name, serviceDirectory); - var dto = getDto(model); + var dto = MapToDto(model); await informationFile.WriteDto(dto, cancellationToken); } - - static DiagnosticDto getDto(DiagnosticModel model) => - new() - { - Properties = new DiagnosticDto.DiagnosticContract - { - LoggerId = $"/loggers/{model.LoggerName}", - AlwaysLog = model.AlwaysLog.ValueUnsafe(), - Sampling = model.Sampling.Map(sampling => new DiagnosticDto.SamplingSettings - { - SamplingType = sampling.Type, - Percentage = sampling.Percentage - }).ValueUnsafe() - } - }; } public static void ConfigureValidatePublishedDiagnostics(IHostApplicationBuilder builder) @@ -335,6 +306,7 @@ static string normalizeDto(DiagnosticDto dto) => { LoggerId = string.Join('/', dto.Properties.LoggerId?.Split('/')?.TakeLast(2)?.ToArray() ?? []), AlwaysLog = dto.Properties.AlwaysLog ?? string.Empty, + LargeLanguageModel = NormalizeLargeLanguageModel(dto.Properties.LargeLanguageModel), Sampling = new { Type = dto.Properties.Sampling?.SamplingType ?? string.Empty, @@ -346,15 +318,18 @@ static string normalizeDto(DiagnosticDto dto) => public static Gen GenerateUpdate(DiagnosticModel original) => from alwaysLog in Gen.Const("allErrors").OptionOf() from sampling in DiagnosticSampling.Generate().OptionOf() + from largeLanguageModel in DiagnosticLargeLanguageModel.Generate().OptionOf() select original with { AlwaysLog = alwaysLog, - Sampling = sampling + Sampling = sampling, + LargeLanguageModel = largeLanguageModel }; public static Gen GenerateOverride(DiagnosticDto original) => from alwaysLog in Gen.Const("allErrors").OptionOf() from sampling in DiagnosticSampling.Generate().OptionOf() + from largeLanguageModel in DiagnosticLargeLanguageModel.Generate().OptionOf() select new DiagnosticDto { Properties = new DiagnosticDto.DiagnosticContract @@ -364,14 +339,17 @@ from sampling in DiagnosticSampling.Generate().OptionOf() { SamplingType = sampling.Type, Percentage = sampling.Percentage - }).ValueUnsafe() + }).ValueUnsafe(), + LargeLanguageModel = largeLanguageModel.Match(MapLargeLanguageModel, () => original.Properties.LargeLanguageModel) } }; public static FrozenDictionary GetDtoDictionary(IEnumerable models) => models.ToFrozenDictionary(model => model.Name, GetDto); - private static DiagnosticDto GetDto(DiagnosticModel model) => + private static DiagnosticDto GetDto(DiagnosticModel model) => MapToDto(model); + + private static DiagnosticDto MapToDto(DiagnosticModel model) => new() { Properties = new DiagnosticDto.DiagnosticContract @@ -382,7 +360,38 @@ private static DiagnosticDto GetDto(DiagnosticModel model) => { SamplingType = sampling.Type, Percentage = sampling.Percentage - }).ValueUnsafe() + }).ValueUnsafe(), + LargeLanguageModel = model.LargeLanguageModel.Match(MapLargeLanguageModel, () => null) } }; -} \ No newline at end of file + + private static DiagnosticDto.LargeLanguageModelSettings MapLargeLanguageModel(DiagnosticLargeLanguageModel largeLanguageModel) => + new() + { + Logs = largeLanguageModel.Logs.Match(logs => logs, () => (string?)null), + Requests = largeLanguageModel.Requests.Match(MapLargeLanguageModelMessages, () => null), + Responses = largeLanguageModel.Responses.Match(MapLargeLanguageModelMessages, () => null) + }; + + private static DiagnosticDto.LargeLanguageModelMessageSettings MapLargeLanguageModelMessages(DiagnosticLargeLanguageModelMessages messages) => + new() + { + Messages = messages.Messages, + MaxSizeInBytes = messages.MaxSizeInBytes + }; + + private static object NormalizeLargeLanguageModel(DiagnosticDto.LargeLanguageModelSettings? largeLanguageModel) => + new + { + Logs = largeLanguageModel?.Logs ?? string.Empty, + Requests = NormalizeLargeLanguageModelMessages(largeLanguageModel?.Requests), + Responses = NormalizeLargeLanguageModelMessages(largeLanguageModel?.Responses) + }; + + private static object NormalizeLargeLanguageModelMessages(DiagnosticDto.LargeLanguageModelMessageSettings? largeLanguageModelMessages) => + new + { + Messages = largeLanguageModelMessages?.Messages ?? string.Empty, + MaxSizeInBytes = largeLanguageModelMessages?.MaxSizeInBytes ?? 0 + }; +} diff --git a/tools/code/publisher/ApiDiagnostic.cs b/tools/code/publisher/ApiDiagnostic.cs index 44e900da..28e99caf 100644 --- a/tools/code/publisher/ApiDiagnostic.cs +++ b/tools/code/publisher/ApiDiagnostic.cs @@ -144,7 +144,7 @@ public static FindApiDiagnosticDto GetFindApiDiagnosticDto(IServiceProvider prov var contentsOption = await tryGetFileContents(informationFile.ToFileInfo(), cancellationToken); return from contents in contentsOption - let dto = contents.ToObjectFromJson() + let dto = contents.ToObjectFromJson(JsonObjectExtensions.SerializerOptions) select overrideDto(dto, name, apiName); }; @@ -277,4 +277,4 @@ private static DeleteApiDiagnosticFromApim GetDeleteApiDiagnosticFromApim(IServi await resourceUri.Delete(pipeline, cancellationToken); }; } -} \ No newline at end of file +} diff --git a/tools/code/publisher/Diagnostic.cs b/tools/code/publisher/Diagnostic.cs index 95547ead..a009472f 100644 --- a/tools/code/publisher/Diagnostic.cs +++ b/tools/code/publisher/Diagnostic.cs @@ -147,7 +147,7 @@ private static FindDiagnosticDto GetFindDiagnosticDto(IServiceProvider provider) var contentsOption = await tryGetFileContents(informationFileInfo, cancellationToken); return from contents in contentsOption - let dto = contents.ToObjectFromJson() + let dto = contents.ToObjectFromJson(JsonObjectExtensions.SerializerOptions) select overrideDto(name, dto); }; } @@ -251,4 +251,4 @@ await DiagnosticUri.From(name, serviceUri) .Delete(pipeline, cancellationToken); }; } -} \ No newline at end of file +} diff --git a/tools/code/publisher/WorkspaceDiagnostic.cs b/tools/code/publisher/WorkspaceDiagnostic.cs index fbcb451f..8cc73d5f 100644 --- a/tools/code/publisher/WorkspaceDiagnostic.cs +++ b/tools/code/publisher/WorkspaceDiagnostic.cs @@ -142,7 +142,7 @@ private static FindWorkspaceDiagnosticDto GetFindWorkspaceDiagnosticDto(IService var contentsOption = await tryGetFileContents(informationFile.ToFileInfo(), cancellationToken); return from contents in contentsOption - select contents.ToObjectFromJson(); + select contents.ToObjectFromJson(JsonObjectExtensions.SerializerOptions); }; } @@ -246,4 +246,4 @@ await WorkspaceDiagnosticUri.From(name, workspaceName, serviceUri) .Delete(pipeline, cancellationToken); }; } -} \ No newline at end of file +}