.NET: [BREAKING] Move AgentSession.Serialize to AIAgent (#3650)

* Move AgentSession.Serialize to AIAgent

* Address PR comments.

* Improve code and fix unit test

* Update test agents to return a default json element instead of throwing where the the result of the serialization is never used.

* Update further tests to actually serialize the session
This commit is contained in:
westey
2026-02-04 15:52:15 +00:00
committed by GitHub
Unverified
parent d742364d81
commit 6255abd687
52 changed files with 295 additions and 46 deletions
@@ -31,6 +31,16 @@ namespace SampleApp
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
=> new(new CustomAgentSession());
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not CustomAgentSession typedSession)
{
throw new ArgumentException($"The provided session is not of type {nameof(CustomAgentSession)}.", nameof(session));
}
return typedSession.Serialize(jsonSerializerOptions);
}
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new CustomAgentSession(serializedSession, jsonSerializerOptions));
@@ -136,6 +146,9 @@ namespace SampleApp
internal CustomAgentSession(JsonElement serializedSessionState, JsonSerializerOptions? jsonSerializerOptions = null)
: base(serializedSessionState, jsonSerializerOptions) { }
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
}
}
@@ -55,7 +55,7 @@ await Task.Delay(TimeSpan.FromSeconds(2));
Console.WriteLine(await agent.RunAsync("What do you already know about my upcoming trip?", session));
Console.WriteLine("\n>> Serialize and deserialize the session to demonstrate persisted state\n");
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
AgentSession restoredSession = await agent.DeserializeSessionAsync(serializedSession);
Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredSession));
@@ -47,7 +47,7 @@ Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", session));
Console.WriteLine(await agent.RunAsync("I am 20 years old", session));
// We can serialize the session. The serialized state will include the state of the memory component.
var sesionElement = session.Serialize();
JsonElement sesionElement = agent.SerializeSession(session);
Console.WriteLine("\n>> Use deserialized session with previously created memories\n");
@@ -25,7 +25,7 @@ AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
// Serialize the session state to a JsonElement, so it can be stored for later use.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
// Save the serialized session to a temporary file (for demonstration purposes).
string tempFilePath = Path.GetTempFileName();
@@ -47,7 +47,7 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session
// Serialize the session state, so it can be stored for later use.
// Since the chat history is stored in the vector store, the serialized session
// only contains the guid that the messages are stored under in the vector store.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
Console.WriteLine("\n--- Serialized session ---\n");
Console.WriteLine(JsonSerializer.Serialize(serializedSession, new JsonSerializerOptions { WriteIndented = true }));
@@ -40,7 +40,7 @@ AgentResponse response = await agent.RunAsync("Write a very long novel about a t
// Poll for background responses until complete.
while (response.ContinuationToken is not null)
{
PersistAgentState(session, response.ContinuationToken);
PersistAgentState(agent, session, response.ContinuationToken);
await Task.Delay(TimeSpan.FromSeconds(10));
@@ -52,9 +52,9 @@ while (response.ContinuationToken is not null)
Console.WriteLine(response.Text);
void PersistAgentState(AgentSession? session, ResponseContinuationToken? continuationToken)
void PersistAgentState(AIAgent agent, AgentSession? session, ResponseContinuationToken? continuationToken)
{
stateStore["session"] = session!.Serialize();
stateStore["session"] = agent.SerializeSession(session!);
stateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
}
@@ -65,7 +65,7 @@ Console.WriteLine(await agent.RunAsync("I need to make a dentist appointment for
Console.WriteLine(await agent.RunAsync("I've taken Sally to soccer practice.", session) + "\n");
// We can serialize the session, and it will contain both the chat history and the data that each AI context provider serialized.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
// Let's print it to console to show the contents.
Console.WriteLine(JsonSerializer.Serialize(serializedSession, options: new JsonSerializerOptions() { WriteIndented = true, IndentSize = 2 }) + "\n");
// The serialized session can be stored long term in a persistent store, but in this case we will just deserialize again and continue the conversation.
@@ -25,7 +25,7 @@ AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
// Serialize the session state to a JsonElement, so it can be stored for later use.
JsonElement serializedSession = session.Serialize();
JsonElement serializedSession = agent.SerializeSession(session);
// Save the serialized session to a temporary file (for demonstration purposes).
string tempFilePath = Path.GetTempFileName();
@@ -80,7 +80,7 @@ internal sealed class AFAgentApplication : AgentApplication
}
// Serialize and save the updated conversation history back to turn state.
JsonElement sessionElementEnd = agentSession.Serialize(JsonUtilities.DefaultOptions);
JsonElement sessionElementEnd = this._agent.SerializeSession(agentSession, JsonUtilities.DefaultOptions);
turnState.SetValue("conversation.chatHistory", sessionElementEnd);
// End the streaming response
@@ -65,6 +65,19 @@ public sealed class A2AAgent : AIAgent
public ValueTask<AgentSession> CreateSessionAsync(string contextId)
=> new(new A2AAgentSession() { ContextId = contextId });
/// <inheritdoc/>
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
_ = Throw.IfNull(session);
if (session is not A2AAgentSession typedSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return typedSession.Serialize(jsonSerializerOptions);
}
/// <inheritdoc/>
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new A2AAgentSession(serializedSession, jsonSerializerOptions));
@@ -46,7 +46,7 @@ public sealed class A2AAgentSession : AgentSession
public string? TaskId { get; internal set; }
/// <inheritdoc/>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
var state = new A2AAgentSessionState
{
@@ -125,6 +125,21 @@ public abstract class AIAgent
/// </remarks>
public abstract ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Serializes an agent session to its JSON representation.
/// </summary>
/// <param name="session">The <see cref="AgentSession"/> to serialize.</param>
/// <param name="jsonSerializerOptions">Optional settings to customize the serialization process.</param>
/// <returns>A <see cref="JsonElement"/> containing the serialized session state.</returns>
/// <exception cref="ArgumentNullException"><paramref name="session"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The type of <paramref name="session"/> is not supported by this agent.</exception>
/// <remarks>
/// This method enables saving conversation sessions to persistent storage,
/// allowing conversations to resume across application restarts or be migrated between
/// different agent instances. Use <see cref="DeserializeSessionAsync"/> to restore the session.
/// </remarks>
public abstract JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null);
/// <summary>
/// Deserializes an agent session from its JSON serialized representation.
/// </summary>
@@ -36,7 +36,7 @@ namespace Microsoft.Agents.AI;
/// <para>
/// To support conversations that may need to survive application restarts or separate service requests, an <see cref="AgentSession"/> can be serialized
/// and deserialized, so that it can be saved in a persistent store.
/// The <see cref="AgentSession"/> provides the <see cref="Serialize(JsonSerializerOptions?)"/> method to serialize the session to a
/// The <see cref="AIAgent"/> provides the <see cref="AIAgent.SerializeSession(AgentSession, JsonSerializerOptions?)"/> method to serialize the session to a
/// <see cref="JsonElement"/> and the <see cref="AIAgent.DeserializeSessionAsync(JsonElement, JsonSerializerOptions?, System.Threading.CancellationToken)"/> method
/// can be used to deserialize the session.
/// </para>
@@ -53,14 +53,6 @@ public abstract class AgentSession
{
}
/// <summary>
/// Serializes the current object's state to a <see cref="JsonElement"/> using the specified serialization options.
/// </summary>
/// <param name="jsonSerializerOptions">The JSON serialization options to use.</param>
/// <returns>A <see cref="JsonElement"/> representation of the object's state.</returns>
public virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
/// <summary>Asks the <see cref="AgentSession"/> for an object of the specified type <paramref name="serviceType"/>.</summary>
/// <param name="serviceType">The type of object being requested.</param>
/// <param name="serviceKey">An optional key that can be used to help identify the target service.</param>
@@ -76,6 +76,10 @@ public abstract class DelegatingAIAgent : AIAgent
/// <inheritdoc />
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default) => this.InnerAgent.CreateSessionAsync(cancellationToken);
/// <inheritdoc />
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> this.InnerAgent.SerializeSession(session, jsonSerializerOptions);
/// <inheritdoc />
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> this.InnerAgent.DeserializeSessionAsync(serializedSession, jsonSerializerOptions, cancellationToken);
@@ -98,7 +98,7 @@ public abstract class InMemoryAgentSession : AgentSession
/// </summary>
/// <param name="jsonSerializerOptions">The JSON serialization options to use.</param>
/// <returns>A <see cref="JsonElement"/> representation of the object's state.</returns>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
protected internal virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
var chatHistoryProviderState = this.ChatHistoryProvider.Serialize(jsonSerializerOptions);
@@ -85,13 +85,9 @@ public abstract class ServiceIdAgentSession : AgentSession
/// <summary>
/// Serializes the current object's state to a <see cref="JsonElement"/> using the specified serialization options.
/// </summary>
/// <param name="jsonSerializerOptions">The JSON serialization options to use for the serialization process.</param>
/// <returns>A <see cref="JsonElement"/> representation of the object's state, containing the service session identifier.</returns>
/// <remarks>
/// The serialized state contains only the service session identifier, as all other conversation state
/// is maintained remotely by the backing service. This makes the serialized representation very lightweight.
/// </remarks>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
/// <param name="jsonSerializerOptions">The JSON serialization options to use.</param>
/// <returns>A <see cref="JsonElement"/> representation of the object's state.</returns>
protected internal virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
var state = new ServiceIdAgentSessionState
{
@@ -53,6 +53,19 @@ public class CopilotStudioAgent : AIAgent
public ValueTask<AgentSession> CreateSessionAsync(string conversationId)
=> new(new CopilotStudioAgentSession() { ConversationId = conversationId });
/// <inheritdoc/>
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
Throw.IfNull(session);
if (session is not CopilotStudioAgentSession typedSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return typedSession.Serialize(jsonSerializerOptions);
}
/// <inheritdoc/>
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new CopilotStudioAgentSession(serializedSession, jsonSerializerOptions));
@@ -25,4 +25,12 @@ public sealed class CopilotStudioAgentSession : ServiceIdAgentSession
get { return this.ServiceSessionId; }
internal set { this.ServiceSessionId = value; }
}
/// <summary>
/// Serializes the current object's state to a <see cref="JsonElement"/> using the specified serialization options.
/// </summary>
/// <param name="jsonSerializerOptions">The JSON serialization options to use.</param>
/// <returns>A <see cref="JsonElement"/> representation of the object's state.</returns>
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
@@ -8,6 +8,7 @@
- Switch to new "Run" method name ([#2843](https://github.com/microsoft/agent-framework/pull/2843))
- Removed AgentThreadMetadata and used AgentSessionId directly instead ([#3067](https://github.com/microsoft/agent-framework/pull/3067));
- Renamed AgentThread to AgentSession ([#3430](https://github.com/microsoft/agent-framework/pull/3430))
- Moved AgentSession.Serialize to AIAgent.SerializeSession ([#3650](https://github.com/microsoft/agent-framework/pull/3650))
## v1.0.0-preview.251204.1
@@ -40,6 +40,27 @@ public sealed class DurableAIAgent : AIAgent
return ValueTask.FromResult<AgentSession>(new DurableAgentSession(sessionId));
}
/// <summary>
/// Serializes an agent session to JSON.
/// </summary>
/// <param name="session">The session to serialize.</param>
/// <param name="jsonSerializerOptions">Optional JSON serializer options.</param>
/// <returns>A <see cref="JsonElement"/> containing the serialized session state.</returns>
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is null)
{
throw new ArgumentNullException(nameof(session));
}
if (session is not DurableAgentSession durableSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return durableSession.Serialize(jsonSerializerOptions);
}
/// <summary>
/// Deserializes an agent session from JSON.
/// </summary>
@@ -11,6 +11,21 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient)
public override string? Name { get; } = name;
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is null)
{
throw new ArgumentNullException(nameof(session));
}
if (session is not DurableAgentSession durableSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return durableSession.Serialize(jsonSerializerOptions);
}
public override ValueTask<AgentSession> DeserializeSessionAsync(
JsonElement serializedSession,
JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
@@ -26,7 +26,7 @@ public sealed class DurableAgentSession : AgentSession
internal AgentSessionId SessionId { get; }
/// <inheritdoc/>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
return JsonSerializer.SerializeToElement(
this,
@@ -97,6 +97,19 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
public ValueTask<AgentSession> CreateSessionAsync(string sessionId)
=> new(new GitHubCopilotAgentSession() { SessionId = sessionId });
/// <inheritdoc/>
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
_ = Throw.IfNull(session);
if (session is not GitHubCopilotAgentSession typedSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return typedSession.Serialize(jsonSerializerOptions);
}
/// <inheritdoc/>
public override ValueTask<AgentSession> DeserializeSessionAsync(
JsonElement serializedSession,
@@ -36,7 +36,7 @@ public sealed class GitHubCopilotAgentSession : AgentSession
}
/// <inheritdoc/>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
State state = new()
{
@@ -33,7 +33,7 @@ public sealed class InMemoryAgentSessionStore : AgentSessionStore
public override ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default)
{
var key = GetKey(conversationId, agent.Id);
this._threads[key] = session.Serialize();
this._threads[key] = agent.SerializeSession(session);
return default;
}
@@ -29,6 +29,12 @@ internal class PurviewAgent : AIAgent, IDisposable
this._purviewWrapper = purviewWrapper;
}
/// <inheritdoc/>
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
return this._innerAgent.SerializeSession(session, jsonSerializerOptions);
}
/// <inheritdoc/>
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
{
@@ -101,7 +101,8 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
protected internal override async ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
{
AIAgentHostState state = new(this._session?.Serialize(), this._currentTurnEmitEvents);
JsonElement? sessionState = this._session is not null ? this._agent.SerializeSession(this._session) : null;
AIAgentHostState state = new(sessionState, this._currentTurnEmitEvents);
Task coreStateTask = context.QueueStateUpdateAsync(AIAgentHostStateKey, state, cancellationToken: cancellationToken).AsTask();
Task userInputRequestsTask = this._userInputHandler?.OnCheckpointingAsync(UserInputRequestStateKey, context, cancellationToken).AsTask() ?? Task.CompletedTask;
Task functionCallRequestsTask = this._functionCallHandler?.OnCheckpointingAsync(FunctionCallRequestStateKey, context, cancellationToken).AsTask() ?? Task.CompletedTask;
@@ -68,6 +68,18 @@ internal sealed class WorkflowHostAgent : AIAgent
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
=> new(new WorkflowSession(this._workflow, this.GenerateNewId(), this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, this._includeWorkflowOutputsInResponse));
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
_ = Throw.IfNull(session);
if (session is not WorkflowSession workflowSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return workflowSession.Serialize(jsonSerializerOptions);
}
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new WorkflowSession(this._workflow, serializedSession, this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, this._includeWorkflowOutputsInResponse, jsonSerializerOptions));
@@ -75,7 +75,7 @@ internal sealed class WorkflowSession : AgentSession
public CheckpointInfo? LastCheckpoint { get; set; }
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
JsonMarshaller marshaller = new(jsonSerializerOptions);
SessionState info = new(
@@ -385,6 +385,19 @@ public sealed partial class ChatClientAgent : AIAgent
};
}
/// <inheritdoc/>
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
_ = Throw.IfNull(session);
if (session is not ChatClientAgentSession typedSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return typedSession.Serialize(jsonSerializerOptions);
}
/// <inheritdoc/>
public override async ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
{
@@ -165,7 +165,7 @@ public sealed class ChatClientAgentSession : AgentSession
}
/// <inheritdoc/>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
JsonElement? chatHistoryProviderState = this._chatHistoryProvider?.Serialize(jsonSerializerOptions);
@@ -251,7 +251,7 @@ public sealed class AGUIAgentTests
var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options);
AIAgent agent = chatClient.AsAIAgent(instructions: null, name: "agent1", description: "Test agent", tools: []);
AgentSession originalSession = await agent.CreateSessionAsync();
JsonElement serialized = originalSession.Serialize();
JsonElement serialized = agent.SerializeSession(originalSession);
// Act
AgentSession deserialized = await agent.DeserializeSessionAsync(serialized);
@@ -381,6 +381,9 @@ public class AIAgentTests
public override async ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> throw new NotImplementedException();
public override async ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
@@ -11,14 +11,6 @@ namespace Microsoft.Agents.AI.Abstractions.UnitTests;
/// </summary>
public class AgentSessionTests
{
[Fact]
public void Serialize_ReturnsDefaultJsonElement()
{
var session = new TestAgentSession();
var result = session.Serialize();
Assert.Equal(default, result);
}
#region GetService Method Tests
/// <summary>
@@ -71,6 +71,11 @@ public sealed class AggregatorPromptAgentFactoryTests
throw new NotImplementedException();
}
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
throw new NotImplementedException();
}
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
@@ -10,7 +10,7 @@ public sealed class DurableAgentSessionTests
public void BuiltInSerialization()
{
AgentSessionId sessionId = AgentSessionId.WithRandomKey("test-agent");
AgentSession session = new DurableAgentSession(sessionId);
DurableAgentSession session = new(sessionId);
JsonElement serializedSession = session.Serialize();
@@ -286,6 +286,9 @@ internal sealed class FakeChatClientAgent : AIAgent
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) =>
new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions));
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> throw new NotImplementedException();
protected override async Task<AgentResponse> RunCoreAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
@@ -350,6 +353,16 @@ internal sealed class FakeMultiMessageAgent : AIAgent
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) =>
new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions));
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not FakeInMemoryAgentSession fakeSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return fakeSession.Serialize(jsonSerializerOptions);
}
protected override async Task<AgentResponse> RunCoreAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
@@ -425,6 +438,8 @@ internal sealed class FakeMultiMessageAgent : AIAgent
: base(serializedSession, jsonSerializerOptions)
{
}
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
public override object? GetService(Type serviceType, object? serviceKey = null) => null;
@@ -340,6 +340,16 @@ internal sealed class FakeForwardedPropsAgent : AIAgent
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) =>
new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions));
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not FakeInMemoryAgentSession fakeSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return fakeSession.Serialize(jsonSerializerOptions);
}
private sealed class FakeInMemoryAgentSession : InMemoryAgentSession
{
public FakeInMemoryAgentSession()
@@ -351,6 +361,9 @@ internal sealed class FakeForwardedPropsAgent : AIAgent
: base(serializedSession, jsonSerializerOptions)
{
}
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
public override object? GetService(Type serviceType, object? serviceKey = null) => null;
@@ -423,6 +423,16 @@ internal sealed class FakeStateAgent : AIAgent
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) =>
new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions));
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not FakeInMemoryAgentSession fakeSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return fakeSession.Serialize(jsonSerializerOptions);
}
private sealed class FakeInMemoryAgentSession : InMemoryAgentSession
{
public FakeInMemoryAgentSession()
@@ -434,6 +444,9 @@ internal sealed class FakeStateAgent : AIAgent
: base(serializedSession, jsonSerializerOptions)
{
}
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
public override object? GetService(Type serviceType, object? serviceKey = null) => null;
@@ -431,6 +431,16 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) =>
new(new TestInMemoryAgentSession(serializedSession, jsonSerializerOptions));
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not TestInMemoryAgentSession testSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return testSession.Serialize(jsonSerializerOptions);
}
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
@@ -507,6 +517,9 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests
: base(serializedSessionState, jsonSerializerOptions, null)
{
}
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
private sealed class TestAgent : AIAgent
@@ -521,6 +534,16 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) =>
new(new TestInMemoryAgentSession(serializedSession, jsonSerializerOptions));
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not TestInMemoryAgentSession testSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return testSession.Serialize(jsonSerializerOptions);
}
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
@@ -13,6 +13,9 @@ internal sealed class TestAgent(string name, string description) : AIAgent
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default) => new(new DummyAgentSession());
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> throw new NotImplementedException();
public override ValueTask<AgentSession> DeserializeSessionAsync(
JsonElement serializedSession,
JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new DummyAgentSession());
@@ -385,6 +385,9 @@ public class AgentExtensionsTests
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> throw new NotImplementedException();
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
@@ -24,6 +24,9 @@ internal sealed class TestAIAgent : AIAgent
public override string? Description => this.DescriptionFunc?.Invoke() ?? base.Description;
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> throw new NotImplementedException();
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) =>
new(this.DeserializeSessionFunc(serializedSession, jsonSerializerOptions));
@@ -141,6 +141,9 @@ public class AgentWorkflowBuilderTests
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new DoubleEchoAgentSession());
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
protected override Task<AgentResponse> RunCoreAsync(
IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
@@ -149,6 +149,9 @@ public class InProcessExecutionTests
public override ValueTask<AgentSession> DeserializeSessionAsync(System.Text.Json.JsonElement serializedSession,
System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new SimpleTestAgentSession());
public override System.Text.Json.JsonElement SerializeSession(AgentSession session, System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
protected override Task<AgentResponse> RunCoreAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
@@ -30,6 +30,9 @@ public class RepresentationTests
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> throw new NotImplementedException();
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
@@ -19,6 +19,9 @@ internal sealed class RoleCheckAgent(bool allowOtherAssistantRoles, string? id =
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new RoleCheckAgentSession());
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default) => new(new RoleCheckAgentSession());
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
@@ -66,6 +66,9 @@ internal sealed class HelloAgent(string id = nameof(HelloAgent)) : AIAgent
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new HelloAgentSession());
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
IEnumerable<AgentResponseUpdate> update = [
@@ -21,6 +21,16 @@ internal class TestEchoAgent(string? id = null, string? name = null, string? pre
return serializedSession.Deserialize<EchoAgentSession>(jsonSerializerOptions) ?? await this.CreateSessionAsync(cancellationToken);
}
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (session is not EchoAgentSession typedSession)
{
throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized.");
}
return typedSession.Serialize(jsonSerializerOptions);
}
public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default) =>
new(new EchoAgentSession());
@@ -89,5 +99,11 @@ internal class TestEchoAgent(string? id = null, string? name = null, string? pre
}
}
private sealed class EchoAgentSession : InMemoryAgentSession;
private sealed class EchoAgentSession : InMemoryAgentSession
{
internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
return base.Serialize(jsonSerializerOptions);
}
}
}
@@ -51,6 +51,9 @@ public class TestReplayAgent(List<ChatMessage>? messages = null, string? id = nu
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new ReplayAgentSession());
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
public static TestReplayAgent FromStrings(params string[] messages) =>
new(ToChatMessages(messages));
@@ -45,6 +45,9 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
_ => throw new NotSupportedException(),
});
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
=> this.RunStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
@@ -361,7 +364,7 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
this.PairedRequests = state.PairedRequests;
}
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
protected override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
JsonElement sessionState = base.Serialize(jsonSerializerOptions);
@@ -51,6 +51,9 @@ public class WorkflowHostSmokeTests
return new(new Session());
}
public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
return await this.RunStreamingAsync(messages, session, options, cancellationToken)