-
Notifications
You must be signed in to change notification settings - Fork 592
Description
Bug Report: Cannot Serialize AgentThread with FunctionApprovalRequestContent
Summary
When using ApprovalRequiredAIFunction with AgentThread, attempting to serialize the thread while it contains a FunctionApprovalRequestContent message throws a NotSupportedException. This makes it impossible to persist conversations that use human-in-the-loop approval patterns, which breaks the documented workflow for persisted conversations.
The official documentation on persisted conversations demonstrates how to serialize and deserialize AgentThread objects to maintain conversation state across sessions. However, this feature is incompatible with ApprovalRequiredAIFunction, which is a core pattern documented in the function approvals tutorial.
Environment
- Package:
Microsoft.Agents.AI.OpenAI(latest version) - Target Framework: .NET 9.0
- Model: Azure OpenAI
gpt-4o-mini - Method:
AgentThread.Serialize()+JsonSerializer.Serialize()
Steps to Reproduce
- Create an agent with functions wrapped in
ApprovalRequiredAIFunction - Create an
AgentThreadand invoke the agent with streaming - Receive a
FunctionApprovalRequestContentin the response stream - Attempt to serialize the thread using the documented approach:
JsonElement serializedThread = thread.Serialize(); string json = JsonSerializer.Serialize(serializedThread, JsonSerializerOptions.Web);
Expected Behavior
The thread should serialize successfully, allowing the conversation (including pending approval requests) to be persisted and resumed later, as shown in the persisted conversation documentation.
Actual Behavior
Serialization throws:
System.NotSupportedException: Runtime type 'Microsoft.Extensions.AI.FunctionApprovalRequestContent' is not supported by polymorphic type 'Microsoft.Extensions.AI.AIContent'.
Path: $.Messages.Contents.
at System.Text.Json.ThrowHelper.ThrowNotSupportedException_RuntimeTypeNotSupported(Type baseType, Type runtimeType)
at System.Text.Json.Serialization.Metadata.PolymorphicTypeResolver.TryGetDerivedJsonTypeInfo(Type runtimeType, JsonTypeInfo& jsonTypeInfo, Object& typeDiscriminator)
at System.Text.Json.Serialization.JsonConverter.ResolvePolymorphicConverter(Object value, JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options, WriteStack& state)
The FunctionApprovalRequestContent type is not registered in the polymorphic JSON serialization hierarchy for AIContent, making it impossible to serialize.
Full Reproduction Code
Tool Class
using System.ComponentModel;
namespace AgentFrameworkExperiments.Tools;
public class ProductivityTools
{
[Description("Gets the current date and time")]
public string GetCurrentTime()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
[Description("Sends an email to a recipient")]
public string SendEmail(
[Description("The email address of the recipient")] string to,
[Description("The subject of the email")] string subject,
[Description("The body content of the email")] string body)
{
Console.WriteLine($"[SendEmail] To: {to}, Subject: {subject}");
return $"Email sent successfully to {to}";
}
}Demo Code (REPRODUCES BUG)
#pragma warning disable MEAI001
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using AgentFrameworkExperiments.Tools;
namespace AgentFrameworkExperiments.Demos;
public static class Demo15_PersistedConversationWithApprovals
{
public static async Task RunAsync()
{
var endpoint = new Uri("https://YOUR-ENDPOINT.openai.azure.com/");
var credential = new AzureKeyCredential("YOUR-API-KEY");
var tools = new ProductivityTools();
// Both functions require approval
var getTimeFunction = AIFunctionFactory.Create(tools.GetCurrentTime);
var approvalRequiredGetTime = new ApprovalRequiredAIFunction(getTimeFunction);
var sendEmailFunction = AIFunctionFactory.Create(tools.SendEmail);
var approvalRequiredSendEmail = new ApprovalRequiredAIFunction(sendEmailFunction);
AIAgent agent = new AzureOpenAIClient(endpoint, credential)
.GetChatClient("gpt-4o-mini")
.CreateAIAgent(
instructions: "You are a helpful productivity assistant.",
name: "ProductivityAgent",
tools: [approvalRequiredGetTime, approvalRequiredSendEmail]
);
string threadFilePath = "persisted_thread_with_approvals.json";
AgentThread thread = agent.GetNewThread();
Console.Write("User: ");
string? userInput = Console.ReadLine();
// Start streaming
bool hasApprovalRequest = false;
FunctionApprovalRequestContent? approvalRequest = null;
await foreach (var streamChunk in agent.RunStreamingAsync(userInput, thread))
{
if (!hasApprovalRequest && streamChunk.Contents != null)
{
approvalRequest = streamChunk.Contents
.OfType<FunctionApprovalRequestContent>()
.FirstOrDefault();
if (approvalRequest != null)
{
hasApprovalRequest = true;
}
}
if (!string.IsNullOrEmpty(streamChunk.Text))
{
Console.Write(streamChunk.Text);
}
}
Console.WriteLine("\n");
// At this point, thread contains FunctionApprovalRequestContent
// Attempting to serialize now throws NotSupportedException
Console.WriteLine("Attempting to serialize thread...");
try
{
JsonElement serializedThread = thread.Serialize();
string json = JsonSerializer.Serialize(serializedThread, JsonSerializerOptions.Web);
await File.WriteAllTextAsync(threadFilePath, json);
Console.WriteLine("✓ Success!");
}
catch (NotSupportedException ex)
{
Console.WriteLine($"❌ FAILED: {ex.Message}");
// Output:
// Runtime type 'Microsoft.Extensions.AI.FunctionApprovalRequestContent'
// is not supported by polymorphic type 'Microsoft.Extensions.AI.AIContent'
}
}
}Test Scenario
User input: "What time is it?"
Expected Result: Thread serializes successfully with pending approval request
Actual Result: NotSupportedException thrown during serialization
Impact
This bug makes it impossible to build production applications that combine:
Real-World Use Cases Blocked:
- Web APIs: Cannot save conversation state between HTTP requests when approvals are pending
- Chatbots: Cannot persist user sessions when waiting for approval confirmations
- Multi-turn workflows: Cannot checkpoint conversations that require human oversight
- Disaster recovery: Cannot restore conversations that were interrupted during approval flows
Workarounds Attempted
❌ Workaround 1: Only serialize after approval completes
Problem: If the application crashes or user disconnects while approval is pending, conversation state is lost. This defeats the purpose of persistence.
❌ Workaround 2: Don't use ApprovalRequiredAIFunction
Problem: Removes safety guarantees for dangerous operations (sending emails, deleting data, financial transactions, etc.)
❌ Workaround 3: Custom serialization
Problem: AgentThread internal state is not fully accessible, making custom serialization unreliable and fragile.
Questions / Clarification Needed
If I'm misunderstanding how to properly persist threads with approval-required functions:
- Is there an alternative approach to serialize threads containing
FunctionApprovalRequestContent? - Is this by design? If so, what is the recommended pattern for persisting conversations with human-in-the-loop approvals?
- Should approval requests be transient? If yes, this limitation should be clearly documented in both tutorials.
The current documentation does not mention any incompatibility between these two core features, leading developers to assume they can be used together.
Suggested Fix
Option 1: Register FunctionApprovalRequestContent in polymorphic serialization
Add FunctionApprovalRequestContent (and FunctionApprovalResponseContent) to the JSON polymorphic type hierarchy for AIContent.
Option 2: Document the limitation
If approval requests are intentionally transient, clearly document in both tutorials that:
- Threads cannot be serialized while approval requests are pending
- Threads must wait for approval completion before persistence
- Provide guidance on handling this in distributed/web scenarios
Option 3: Provide a workaround API
Add a method like thread.SerializeWithoutPendingApprovals() or thread.HasPendingApprovals() to give developers control over when serialization is safe.
Additional Context
I've created a comparison demo (Demo16) with auto-execute functions (no approvals), and that serializes successfully. This confirms the issue is specific to ApprovalRequiredAIFunction and not a general serialization problem.
Both features are prominently documented as core Agent Framework capabilities, so their incompatibility is unexpected and breaks reasonable developer expectations.
Request
Please either:
- Fix the serialization to support
FunctionApprovalRequestContent, OR - Clearly document this limitation and provide guidance on the recommended pattern for persisting conversations with approval workflows
Thank you for your time and consideration. If I've misunderstood something or there's an undocumented approach, please let me know!