diff --git a/src/CompactifAI.Client/CompactifAI.Client.csproj b/src/CompactifAI.Client/CompactifAI.Client.csproj
index 55a82f1..c6720a6 100644
--- a/src/CompactifAI.Client/CompactifAI.Client.csproj
+++ b/src/CompactifAI.Client/CompactifAI.Client.csproj
@@ -21,6 +21,10 @@
false
true
+
+
+ true
+ true
diff --git a/src/CompactifAI.Client/CompactifAIClient.cs b/src/CompactifAI.Client/CompactifAIClient.cs
index 75a1cd5..bea89c3 100644
--- a/src/CompactifAI.Client/CompactifAIClient.cs
+++ b/src/CompactifAI.Client/CompactifAIClient.cs
@@ -1,7 +1,9 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
using CompactifAI.Client.Models;
+using CompactifAI.Client.Serialization;
using Microsoft.Extensions.Options;
namespace CompactifAI.Client;
@@ -13,7 +15,6 @@ public class CompactifAIClient : ICompactifAIClient
{
private readonly HttpClient _httpClient;
private readonly CompactifAIOptions _options;
- private readonly JsonSerializerOptions _jsonOptions;
///
/// Creates a new CompactifAI client.
@@ -24,12 +25,6 @@ public CompactifAIClient(HttpClient httpClient, IOptions opt
{
_httpClient = httpClient;
_options = options.Value;
- _jsonOptions = new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
- };
-
ConfigureHttpClient();
}
@@ -46,12 +41,6 @@ public CompactifAIClient(string apiKey, string? baseUrl = null)
BaseUrl = baseUrl ?? "https://api.compactif.ai/v1"
};
_httpClient = new HttpClient();
- _jsonOptions = new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
- };
-
ConfigureHttpClient();
}
@@ -76,10 +65,10 @@ public async Task CreateChatCompletionAsync(
var response = await _httpClient.PostAsJsonAsync(
"chat/completions",
request,
- _jsonOptions,
+ CompactifAIJsonContext.Default.ChatCompletionRequest,
cancellationToken);
- return await HandleResponseAsync(response, cancellationToken);
+ return await HandleResponseAsync(response, CompactifAIJsonContext.Default.ChatCompletionResponse, cancellationToken);
}
///
@@ -120,10 +109,10 @@ public async Task CreateCompletionAsync(
var response = await _httpClient.PostAsJsonAsync(
"completions",
request,
- _jsonOptions,
+ CompactifAIJsonContext.Default.CompletionRequest,
cancellationToken);
- return await HandleResponseAsync(response, cancellationToken);
+ return await HandleResponseAsync(response, CompactifAIJsonContext.Default.CompletionResponse, cancellationToken);
}
///
@@ -176,7 +165,7 @@ public async Task TranscribeAsync(
var response = await _httpClient.PostAsync("audio/transcriptions", content, cancellationToken);
- return await HandleResponseAsync(response, cancellationToken);
+ return await HandleResponseAsync(response, CompactifAIJsonContext.Default.TranscriptionResponse, cancellationToken);
}
///
@@ -209,7 +198,7 @@ public async Task ListModelsAsync(CancellationToken cancellation
{
var response = await _httpClient.GetAsync("models", cancellationToken);
- return await HandleResponseAsync(response, cancellationToken);
+ return await HandleResponseAsync(response, CompactifAIJsonContext.Default.ModelsResponse, cancellationToken);
}
///
@@ -217,7 +206,7 @@ public async Task GetModelAsync(string modelId, CancellationToken can
{
var response = await _httpClient.GetAsync($"models/{modelId}", cancellationToken);
- return await HandleResponseAsync(response, cancellationToken);
+ return await HandleResponseAsync(response, CompactifAIJsonContext.Default.ModelInfo, cancellationToken);
}
#endregion
@@ -226,6 +215,7 @@ public async Task GetModelAsync(string modelId, CancellationToken can
private async Task HandleResponseAsync(
HttpResponseMessage response,
+ JsonTypeInfo jsonTypeInfo,
CancellationToken cancellationToken)
{
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
@@ -238,7 +228,7 @@ private async Task HandleResponseAsync(
responseBody);
}
- var result = JsonSerializer.Deserialize(responseBody, _jsonOptions);
+ var result = JsonSerializer.Deserialize(responseBody, jsonTypeInfo);
if (result is null)
{
diff --git a/src/CompactifAI.Client/Extensions/ServiceCollectionExtensions.cs b/src/CompactifAI.Client/Extensions/ServiceCollectionExtensions.cs
index 78f00b1..c5c6111 100644
--- a/src/CompactifAI.Client/Extensions/ServiceCollectionExtensions.cs
+++ b/src/CompactifAI.Client/Extensions/ServiceCollectionExtensions.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -49,6 +50,12 @@ public static IServiceCollection AddCompactifAI(
/// The service collection.
/// The configuration section to bind from.
/// The service collection for chaining.
+ ///
+ /// This method uses configuration binding which requires reflection and is not AOT-compatible.
+ /// For AOT scenarios, use the overload that accepts an Action<CompactifAIOptions>.
+ ///
+ [RequiresUnreferencedCode("Configuration binding uses reflection. Use the Action overload for AOT scenarios.")]
+ [RequiresDynamicCode("Configuration binding uses reflection. Use the Action overload for AOT scenarios.")]
public static IServiceCollection AddCompactifAI(
this IServiceCollection services,
Microsoft.Extensions.Configuration.IConfiguration configuration)
diff --git a/src/CompactifAI.Client/Models/ChatModels.cs b/src/CompactifAI.Client/Models/ChatModels.cs
index 5bf9669..8900ded 100644
--- a/src/CompactifAI.Client/Models/ChatModels.cs
+++ b/src/CompactifAI.Client/Models/ChatModels.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using System.Text.Json.Serialization;
namespace CompactifAI.Client.Models;
@@ -150,7 +151,7 @@ public class ToolFunction
///
[JsonPropertyName("parameters")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public object? Parameters { get; set; }
+ public JsonElement? Parameters { get; set; }
}
///
diff --git a/src/CompactifAI.Client/Serialization/CompactifAIJsonContext.cs b/src/CompactifAI.Client/Serialization/CompactifAIJsonContext.cs
new file mode 100644
index 0000000..8bc6ffe
--- /dev/null
+++ b/src/CompactifAI.Client/Serialization/CompactifAIJsonContext.cs
@@ -0,0 +1,45 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using CompactifAI.Client.Models;
+
+namespace CompactifAI.Client.Serialization;
+
+///
+/// Source-generated JSON serialization context for CompactifAI models.
+/// Provides high-performance, AOT-compatible serialization without reflection.
+///
+[JsonSourceGenerationOptions(
+ PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ GenerationMode = JsonSourceGenerationMode.Default)]
+// Chat models
+[JsonSerializable(typeof(ChatCompletionRequest))]
+[JsonSerializable(typeof(ChatCompletionResponse))]
+[JsonSerializable(typeof(ChatMessage))]
+[JsonSerializable(typeof(ChatChoice))]
+[JsonSerializable(typeof(Tool))]
+[JsonSerializable(typeof(ToolFunction))]
+[JsonSerializable(typeof(Usage))]
+// Completion models
+[JsonSerializable(typeof(CompletionRequest))]
+[JsonSerializable(typeof(CompletionResponse))]
+[JsonSerializable(typeof(CompletionChoice))]
+// Transcription models
+[JsonSerializable(typeof(TranscriptionResponse))]
+[JsonSerializable(typeof(TranscriptionSegment))]
+// Model info
+[JsonSerializable(typeof(ModelsResponse))]
+[JsonSerializable(typeof(ModelInfo))]
+[JsonSerializable(typeof(ModelCapabilities))]
+// Supporting types
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(JsonElement))]
+public partial class CompactifAIJsonContext : JsonSerializerContext
+{
+}
diff --git a/tests/CompactifAI.Client.Tests/ModelsTests.cs b/tests/CompactifAI.Client.Tests/ModelsTests.cs
index de89db6..d715e1b 100644
--- a/tests/CompactifAI.Client.Tests/ModelsTests.cs
+++ b/tests/CompactifAI.Client.Tests/ModelsTests.cs
@@ -1,15 +1,14 @@
using System.Text.Json;
using CompactifAI.Client.Models;
+using CompactifAI.Client.Serialization;
using Xunit;
namespace CompactifAI.Client.Tests;
public class ModelsTests
{
- private readonly JsonSerializerOptions _jsonOptions = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
- };
+ // Use source-generated context for high-performance serialization
+ private static CompactifAIJsonContext JsonContext => CompactifAIJsonContext.Default;
#region ChatMessage Tests
@@ -56,7 +55,7 @@ public void ChatCompletionRequest_Serializes_WithRequiredFields()
}
};
- var json = JsonSerializer.Serialize(request, _jsonOptions);
+ var json = JsonSerializer.Serialize(request, JsonContext.ChatCompletionRequest);
Assert.Contains("\"model\":\"test-model\"", json);
Assert.Contains("\"messages\":", json);
@@ -73,7 +72,7 @@ public void ChatCompletionRequest_Serializes_OptionalFieldsOmittedWhenNull()
Messages = new List { ChatMessage.User("Test") }
};
- var json = JsonSerializer.Serialize(request, _jsonOptions);
+ var json = JsonSerializer.Serialize(request, JsonContext.ChatCompletionRequest);
Assert.DoesNotContain("temperature", json);
Assert.DoesNotContain("max_tokens", json);
@@ -93,7 +92,7 @@ public void ChatCompletionRequest_Serializes_WithAllOptionalFields()
FrequencyPenalty = 0.5
};
- var json = JsonSerializer.Serialize(request, _jsonOptions);
+ var json = JsonSerializer.Serialize(request, JsonContext.ChatCompletionRequest);
Assert.Contains("\"temperature\":0.7", json);
Assert.Contains("\"max_tokens\":100", json);
@@ -132,7 +131,7 @@ public void ChatCompletionResponse_Deserializes_Correctly()
}
""";
- var response = JsonSerializer.Deserialize(json, _jsonOptions);
+ var response = JsonSerializer.Deserialize(json, JsonContext.ChatCompletionResponse);
Assert.NotNull(response);
Assert.Equal("chatcmpl-123", response.Id);
@@ -161,7 +160,7 @@ public void CompletionRequest_Serializes_Correctly()
TopP = 0.95
};
- var json = JsonSerializer.Serialize(request, _jsonOptions);
+ var json = JsonSerializer.Serialize(request, JsonContext.CompletionRequest);
Assert.Contains("\"model\":\"test-model\"", json);
Assert.Contains("\"prompt\":\"Once upon a time\"", json);
@@ -198,7 +197,7 @@ public void CompletionResponse_Deserializes_Correctly()
}
""";
- var response = JsonSerializer.Deserialize(json, _jsonOptions);
+ var response = JsonSerializer.Deserialize(json, JsonContext.CompletionResponse);
Assert.NotNull(response);
Assert.Equal("cmpl-123", response.Id);
@@ -230,7 +229,7 @@ public void TranscriptionResponse_Deserializes_Correctly()
}
""";
- var response = JsonSerializer.Deserialize(json, _jsonOptions);
+ var response = JsonSerializer.Deserialize(json, JsonContext.TranscriptionResponse);
Assert.NotNull(response);
Assert.Equal("transcribe", response.Task);
@@ -270,7 +269,7 @@ public void ModelsResponse_Deserializes_Correctly()
}
""";
- var response = JsonSerializer.Deserialize(json, _jsonOptions);
+ var response = JsonSerializer.Deserialize(json, JsonContext.ModelsResponse);
Assert.NotNull(response);
Assert.Equal("list", response.Object);
@@ -309,7 +308,7 @@ public void ModelsResponse_ParametersNumber_HandlesVariousFormats(string paramet
}
""";
- var response = JsonSerializer.Deserialize(json, _jsonOptions);
+ var response = JsonSerializer.Deserialize(json, JsonContext.ModelsResponse);
Assert.NotNull(response);
Assert.Single(response.Data);
@@ -338,7 +337,7 @@ public void ModelsResponse_Deserializes_WithMissingParametersNumber()
}
""";
- var response = JsonSerializer.Deserialize(json, _jsonOptions);
+ var response = JsonSerializer.Deserialize(json, JsonContext.ModelsResponse);
Assert.NotNull(response);
Assert.Single(response.Data);
@@ -352,6 +351,9 @@ public void ModelsResponse_Deserializes_WithMissingParametersNumber()
[Fact]
public void Tool_Serializes_Correctly()
{
+ // Create JsonElement from anonymous object for parameters
+ var parametersJson = JsonSerializer.SerializeToElement(new { type = "object", properties = new { location = new { type = "string" } } });
+
var tool = new Tool
{
Type = "function",
@@ -359,11 +361,11 @@ public void Tool_Serializes_Correctly()
{
Name = "get_weather",
Description = "Get the current weather",
- Parameters = new { type = "object", properties = new { location = new { type = "string" } } }
+ Parameters = parametersJson
}
};
- var json = JsonSerializer.Serialize(tool, _jsonOptions);
+ var json = JsonSerializer.Serialize(tool, JsonContext.Tool);
Assert.Contains("\"type\":\"function\"", json);
Assert.Contains("\"name\":\"get_weather\"", json);