Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ internal sealed class UpperCaseParrotAgent : AIAgent
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));

Expand Down Expand Up @@ -136,6 +146,9 @@ internal CustomAgentSession() { }

internal CustomAgentSession(JsonElement serializedSessionState, JsonSerializerOptions? jsonSerializerOptions = null)
: base(serializedSessionState, jsonSerializerOptions) { }

internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> base.Serialize(jsonSerializerOptions);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
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");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
// 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 }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
// 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));

Expand All @@ -52,9 +52,9 @@

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)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ You remind users of upcoming calendar events when the user interacts with you.
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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();
Expand Down
2 changes: 1 addition & 1 deletion dotnet/samples/M365Agent/AFAgentApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private async Task MessageActivityAsync(ITurnContext turnContext, ITurnState tur
}

// 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
Expand Down
13 changes: 13 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ public sealed override ValueTask<AgentSession> CreateSessionAsync(CancellationTo
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));
Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal A2AAgentSession(JsonElement serializedSessionState, JsonSerializerOptio
public string? TaskId { get; internal set; }

/// <inheritdoc/>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
var state = new A2AAgentSessionState
{
Expand Down
15 changes: 15 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
10 changes: 1 addition & 9 deletions dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -53,14 +53,6 @@ protected 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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ protected DelegatingAIAgent(AIAgent innerAgent)
/// <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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ protected InMemoryAgentSession(
/// </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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,9 @@ protected ServiceIdAgentSession(
/// <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
{
Expand Down
13 changes: 13 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ public sealed override ValueTask<AgentSession> CreateSessionAsync(CancellationTo
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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ public string? ConversationId
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);
}
1 change: 1 addition & 0 deletions dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 21 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ public override ValueTask<AgentSession> CreateSessionAsync(CancellationToken can
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>
Expand Down
15 changes: 15 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal DurableAgentSession(AgentSessionId sessionId)
internal AgentSessionId SessionId { get; }

/// <inheritdoc/>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
return JsonSerializer.SerializeToElement(
this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ public sealed override ValueTask<AgentSession> CreateSessionAsync(CancellationTo
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal GitHubCopilotAgentSession(JsonElement serializedThread, JsonSerializerO
}

/// <inheritdoc/>
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
State state = new()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
6 changes: 6 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public PurviewAgent(AIAgent innerAgent, PurviewWrapper purviewWrapper)
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)
{
Expand Down
Loading
Loading