mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Clean up usage of JsonSerializerContext (#229)
* Clean up usage of JsonSerializerContext * review feedback
This commit is contained in:
committed by
GitHub
Unverified
parent
e6aeb9a5db
commit
5e9b24e532
+2
@@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
|
||||
namespace HelloHttpApi.ApiService;
|
||||
|
||||
internal static class ActorFrameworkWebApplicationExtensions
|
||||
{
|
||||
public static void MapAgents(this WebApplication app)
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using HelloHttpApi.ApiService;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
|
||||
internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions jsonSerializerOptions, IActorRuntimeContext context, ILogger<ChatClientAgentActor> logger) : IActor
|
||||
namespace HelloHttpApi.ApiService;
|
||||
|
||||
internal sealed class ChatClientAgentActor(
|
||||
AIAgent agent,
|
||||
IActorRuntimeContext context,
|
||||
ILogger<ChatClientAgentActor> logger) : IActor
|
||||
{
|
||||
private string? _etag;
|
||||
private ChatClientAgentThread? _thread;
|
||||
@@ -31,8 +34,7 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions
|
||||
if (threadResult.Value is { } threadJson)
|
||||
{
|
||||
// Deserialize the thread state if it exist
|
||||
this._thread = threadJson.Deserialize<ChatClientAgentThread>(
|
||||
(JsonTypeInfo<ChatClientAgentThread>)jsonSerializerOptions.GetTypeInfo(typeof(ChatClientAgentThread)));
|
||||
this._thread = threadJson.Deserialize(ChatClientAgentActorJsonContext.Default.ChatClientAgentThread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +78,7 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions
|
||||
List<ChatMessage>? messages;
|
||||
if (message.Params is { } payload)
|
||||
{
|
||||
var arg = payload.Deserialize<ChatClientAgentRunRequest>(
|
||||
(JsonTypeInfo<ChatClientAgentRunRequest>)jsonSerializerOptions.GetTypeInfo(typeof(ChatClientAgentRunRequest)));
|
||||
var arg = payload.Deserialize(ChatClientAgentActorJsonContext.Default.ChatClientAgentRunRequest);
|
||||
messages = arg?.Messages;
|
||||
}
|
||||
|
||||
@@ -86,12 +87,11 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions
|
||||
Log.ProcessingAgentRequest(logger, requestId, context.ActorId.ToString(), messages.Count);
|
||||
try
|
||||
{
|
||||
var typeInfo = (JsonTypeInfo<AgentRunResponseUpdate>)jsonSerializerOptions.GetTypeInfo(typeof(AgentRunResponseUpdate));
|
||||
var i = 0;
|
||||
var updates = new List<AgentRunResponseUpdate>();
|
||||
await foreach (var update in agent.RunStreamingAsync(messages, this._thread, cancellationToken: cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var updateJson = JsonSerializer.SerializeToElement(update, typeInfo);
|
||||
var updateJson = JsonSerializer.SerializeToElement(update, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponseUpdate)));
|
||||
context.OnProgressUpdate(requestId, i++, updateJson);
|
||||
updates.Add(update);
|
||||
Log.AgentStreamingUpdate(logger, requestId, i);
|
||||
@@ -99,7 +99,7 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions
|
||||
|
||||
var serializedRunResponse = JsonSerializer.SerializeToElement(
|
||||
updates.ToAgentRunResponse(),
|
||||
(JsonTypeInfo<AgentRunResponse>)jsonSerializerOptions.GetTypeInfo(typeof(AgentRunResponse)));
|
||||
AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponseUpdate)));
|
||||
var writeResponse = await context.WriteAsync(
|
||||
new(this._etag, [new UpdateRequestOperation(requestId, RequestStatus.Completed, serializedRunResponse)]), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
|
||||
namespace HelloHttpApi.ApiService;
|
||||
|
||||
/// <summary>
|
||||
/// Source-generated JSON type information for use by ChatClientAgentActor.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(
|
||||
JsonSerializerDefaults.Web,
|
||||
UseStringEnumConverter = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
WriteIndented = false)]
|
||||
[JsonSerializable(typeof(ChatClientAgentThread))]
|
||||
[JsonSerializable(typeof(ChatClientAgentRunRequest))]
|
||||
internal sealed partial class ChatClientAgentActorJsonContext : JsonSerializerContext;
|
||||
-2
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.Agents.Orchestration;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
@@ -32,7 +31,6 @@ public static class HostApplicationBuilderAgentExtensions
|
||||
new ActorType(agentKey),
|
||||
(sp, ctx) => new ChatClientAgentActor(
|
||||
sp.GetRequiredKeyedService<AIAgent>(agentKey),
|
||||
sp.GetService<JsonSerializerOptions>() ?? JsonSerializerOptions.Web,
|
||||
ctx,
|
||||
sp.GetRequiredService<ILogger<ChatClientAgentActor>>()));
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using HelloHttpApi.ApiService;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
|
||||
namespace HelloHttpApi.ApiService;
|
||||
|
||||
/// <summary>
|
||||
/// High-performance logging messages using LoggerMessage source generator.
|
||||
/// </summary>
|
||||
|
||||
@@ -58,16 +58,34 @@ public readonly struct ActorId : IEquatable<ActorId>
|
||||
/// <summary>
|
||||
/// Convert a string of the format "type/key" into an <see cref="ActorId"/>.
|
||||
/// </summary>
|
||||
/// <param name="actorId">The actor ID string.</param>
|
||||
/// <param name="value">The actor ID string.</param>
|
||||
/// <returns>An instance of <see cref="ActorId"/>.</returns>
|
||||
public static ActorId Parse(string actorId)
|
||||
public static ActorId Parse(string value)
|
||||
{
|
||||
if (!KeyValueParser.TryParse(actorId, out string? type, out string? key))
|
||||
if (!TryParse(value, out var result))
|
||||
{
|
||||
throw new FormatException($"Invalid actor ID: '{actorId}'. Expected format is 'type/key'.");
|
||||
throw new FormatException($"Invalid actor ID: '{value}'. Expected format is 'type/key'.");
|
||||
}
|
||||
|
||||
return new ActorId(type, key);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool TryParse(string input, out ActorId actorId)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
int separatorIndex = input.IndexOf('/');
|
||||
if (separatorIndex >= 0)
|
||||
{
|
||||
var type = input.Substring(0, separatorIndex);
|
||||
var key = input.Substring(separatorIndex + 1);
|
||||
actorId = new ActorId(type, key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
actorId = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for JSON serialization with source generation support.
|
||||
/// </summary>
|
||||
internal static class JsonSerializerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the JsonTypeInfo for a type, preferring the one from options if available,
|
||||
/// otherwise falling back to the source-generated context.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to get JsonTypeInfo for.</typeparam>
|
||||
/// <param name="options">The JsonSerializerOptions to check first.</param>
|
||||
/// <param name="fallbackContext">The fallback JsonSerializerContext to use if not found in options.</param>
|
||||
/// <returns>The JsonTypeInfo for the requested type.</returns>
|
||||
public static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options, JsonSerializerContext fallbackContext)
|
||||
{
|
||||
// Try to get from the options first (if a context is configured)
|
||||
if (options.TypeInfoResolver?.GetTypeInfo(typeof(T), options) is JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
// Fall back to the provided source-generated context
|
||||
return (JsonTypeInfo<T>)fallbackContext.GetTypeInfo(typeof(T))!;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Provides helper methods for parsing key-value string representations.
|
||||
/// </summary>
|
||||
internal static class KeyValueParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a string in the format "key/value" into a tuple containing the key and value.
|
||||
/// </summary>
|
||||
public static bool TryParse(string input, [NotNullWhen(true)] out string? key, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
int separatorIndex = input.IndexOf('/');
|
||||
if (separatorIndex >= 0)
|
||||
{
|
||||
key = input.Substring(0, separatorIndex);
|
||||
value = input.Substring(separatorIndex + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
key = value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
@@ -88,9 +87,8 @@ internal sealed class ActorRuntimeBuilder : IActorRuntimeBuilder
|
||||
services.AddSingleton<IActorClient, InProcessActorClient>();
|
||||
services.AddSingleton<InProcessActorRuntime>(sp =>
|
||||
{
|
||||
var jsonSerializerOptions = sp.GetService<JsonSerializerOptions>() ?? new();
|
||||
var actorStateStorage = sp.GetRequiredService<IActorStateStorage>();
|
||||
return new InProcessActorRuntime(sp, this.ActorFactories, actorStateStorage, jsonSerializerOptions);
|
||||
return new InProcessActorRuntime(sp, this.ActorFactories, actorStateStorage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ internal sealed class InProcessActorContext : IActorRuntimeContext, IAsyncDispos
|
||||
{
|
||||
ActorId = context.ActorId,
|
||||
MessageId = entry.Request.MessageId,
|
||||
Data = JsonSerializer.SerializeToElement($"Error: {exception.Message}", context._runtime.JsonSerializerOptions.GetTypeInfo<string>(ActorRuntimeJsonContext.Default)),
|
||||
Data = JsonSerializer.SerializeToElement($"Error: {exception.Message}", ActorRuntimeJsonContext.Default.String),
|
||||
Status = RequestStatus.Failed,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static Microsoft.Extensions.AI.Agents.Runtime.ActivityExtensions;
|
||||
@@ -16,8 +15,7 @@ namespace Microsoft.Extensions.AI.Agents.Runtime;
|
||||
internal sealed class InProcessActorRuntime(
|
||||
IServiceProvider serviceProvider,
|
||||
IReadOnlyDictionary<ActorType, Func<IServiceProvider, IActorRuntimeContext, IActor>> actorFactories,
|
||||
IActorStateStorage storage,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
IActorStateStorage storage)
|
||||
{
|
||||
private static readonly ActivitySource ActivitySource = new(ActorRuntimeOpenTelemetryConsts.InProcessSourceName);
|
||||
private static readonly Meter Meter = new(ActorRuntimeOpenTelemetryConsts.InProcessSourceName);
|
||||
@@ -38,7 +36,6 @@ internal sealed class InProcessActorRuntime(
|
||||
private readonly ConcurrentDictionary<ActorId, InProcessActorContext> _actors = [];
|
||||
|
||||
public IActorStateStorage Storage { get; } = storage;
|
||||
public JsonSerializerOptions JsonSerializerOptions { get; } = jsonSerializerOptions;
|
||||
public IServiceProvider Services { get; } = serviceProvider;
|
||||
|
||||
internal InProcessActorContext GetOrCreateActor(ActorId actorId)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for JSON serialization with source generation support.
|
||||
/// </summary>
|
||||
internal static class JsonSerializerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the JsonTypeInfo for a type, preferring the one from options if available,
|
||||
/// otherwise falling back to the source-generated context.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to get JsonTypeInfo for.</typeparam>
|
||||
/// <param name="options">The JsonSerializerOptions to check first.</param>
|
||||
/// <param name="fallbackContext">The fallback JsonSerializerContext to use if not found in options.</param>
|
||||
/// <returns>The JsonTypeInfo for the requested type.</returns>
|
||||
public static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options, JsonSerializerContext fallbackContext)
|
||||
{
|
||||
// Try to get from the options first (if a context is configured)
|
||||
if (options.TypeInfoResolver?.GetTypeInfo(typeof(T), options) is JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
// Fall back to the provided source-generated context
|
||||
return (JsonTypeInfo<T>)fallbackContext.GetTypeInfo(typeof(T))!;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user