mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [BREAKING] Change GetNewThread and DeserializeThread to async (#3152)
* Change GetNewThread and DeserializeThread plus ChatMessageStore and AIContextProvider Factories to async * Merge fixes
This commit is contained in:
@@ -52,27 +52,27 @@ public sealed class A2AAgent : AIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override AgentThread GetNewThread()
|
||||
=> new A2AAgentThread();
|
||||
public sealed override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
=> new(new A2AAgentThread());
|
||||
|
||||
/// <summary>
|
||||
/// Get a new <see cref="AgentThread"/> instance using an existing context id, to continue that conversation.
|
||||
/// </summary>
|
||||
/// <param name="contextId">The context id to continue.</param>
|
||||
/// <returns>A new <see cref="AgentThread"/> instance.</returns>
|
||||
public AgentThread GetNewThread(string contextId)
|
||||
=> new A2AAgentThread() { ContextId = contextId };
|
||||
/// <returns>A value task representing the asynchronous operation. The task result contains a new <see cref="AgentThread"/> instance.</returns>
|
||||
public ValueTask<AgentThread> GetNewThreadAsync(string contextId)
|
||||
=> new(new A2AAgentThread() { ContextId = contextId });
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> new A2AAgentThread(serializedThread, jsonSerializerOptions);
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
=> new(new A2AAgentThread(serializedThread, jsonSerializerOptions));
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(messages);
|
||||
|
||||
A2AAgentThread typedThread = this.GetA2AThread(thread, options);
|
||||
A2AAgentThread typedThread = await this.GetA2AThreadAsync(thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
this._logger.LogA2AAgentInvokingAgent(nameof(RunAsync), this.Id, this.Name);
|
||||
|
||||
@@ -139,7 +139,7 @@ public sealed class A2AAgent : AIAgent
|
||||
{
|
||||
_ = Throw.IfNull(messages);
|
||||
|
||||
A2AAgentThread typedThread = this.GetA2AThread(thread, options);
|
||||
A2AAgentThread typedThread = await this.GetA2AThreadAsync(thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
this._logger.LogA2AAgentInvokingAgent(nameof(RunStreamingAsync), this.Id, this.Name);
|
||||
|
||||
@@ -211,7 +211,7 @@ public sealed class A2AAgent : AIAgent
|
||||
/// <inheritdoc/>
|
||||
public override string? Description => this._description;
|
||||
|
||||
private A2AAgentThread GetA2AThread(AgentThread? thread, AgentRunOptions? options)
|
||||
private async ValueTask<A2AAgentThread> GetA2AThreadAsync(AgentThread? thread, AgentRunOptions? options, CancellationToken cancellationToken)
|
||||
{
|
||||
// Aligning with other agent implementations that support background responses, where
|
||||
// a thread is required for background responses to prevent inconsistent experience
|
||||
@@ -221,7 +221,7 @@ public sealed class A2AAgent : AIAgent
|
||||
throw new InvalidOperationException("A thread must be provided when AllowBackgroundResponses is enabled.");
|
||||
}
|
||||
|
||||
thread ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (thread is not A2AAgentThread typedThread)
|
||||
{
|
||||
|
||||
@@ -105,7 +105,8 @@ public abstract class AIAgent
|
||||
/// <summary>
|
||||
/// Creates a new conversation thread that is compatible with this agent.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="AgentThread"/> instance ready for use with this agent.</returns>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A value task that represents the asynchronous operation. The task result contains a new <see cref="AgentThread"/> instance ready for use with this agent.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method creates a fresh conversation thread that can be used to maintain state
|
||||
@@ -118,14 +119,15 @@ public abstract class AIAgent
|
||||
/// may be deferred until first use to optimize performance.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract AgentThread GetNewThread();
|
||||
public abstract ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes an agent thread from its JSON serialized representation.
|
||||
/// </summary>
|
||||
/// <param name="serializedThread">A <see cref="JsonElement"/> containing the serialized thread state.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings to customize the deserialization process.</param>
|
||||
/// <returns>A restored <see cref="AgentThread"/> instance with the state from <paramref name="serializedThread"/>.</returns>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A value task that represents the asynchronous operation. The task result contains a restored <see cref="AgentThread"/> instance with the state from <paramref name="serializedThread"/>.</returns>
|
||||
/// <exception cref="ArgumentException">The <paramref name="serializedThread"/> is not in the expected format.</exception>
|
||||
/// <exception cref="JsonException">The serialized data is invalid or cannot be deserialized.</exception>
|
||||
/// <remarks>
|
||||
@@ -133,7 +135,7 @@ public abstract class AIAgent
|
||||
/// allowing conversations to resume across application restarts or be migrated between
|
||||
/// different agent instances.
|
||||
/// </remarks>
|
||||
public abstract AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null);
|
||||
public abstract ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Run the agent with no message assuming that all required instructions are already provided to the agent or on the thread.
|
||||
|
||||
@@ -26,8 +26,8 @@ namespace Microsoft.Agents.AI;
|
||||
/// <item><description>Chat history reduction, e.g. where messages needs to be summarized or truncated to reduce the size.</description></item>
|
||||
/// </list>
|
||||
/// An <see cref="AgentThread"/> is always constructed by an <see cref="AIAgent"/> so that the <see cref="AIAgent"/>
|
||||
/// can attach any necessary behaviors to the <see cref="AgentThread"/>. See the <see cref="AIAgent.GetNewThread()"/>
|
||||
/// and <see cref="AIAgent.DeserializeThread(JsonElement, JsonSerializerOptions?)"/> methods for more information.
|
||||
/// can attach any necessary behaviors to the <see cref="AgentThread"/>. See the <see cref="AIAgent.GetNewThreadAsync(System.Threading.CancellationToken)"/>
|
||||
/// and <see cref="AIAgent.DeserializeThreadAsync(JsonElement, JsonSerializerOptions?, System.Threading.CancellationToken)"/> methods for more information.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Because of these behaviors, an <see cref="AgentThread"/> may not be reusable across different agents, since each agent
|
||||
@@ -37,13 +37,13 @@ namespace Microsoft.Agents.AI;
|
||||
/// To support conversations that may need to survive application restarts or separate service requests, an <see cref="AgentThread"/> can be serialized
|
||||
/// and deserialized, so that it can be saved in a persistent store.
|
||||
/// The <see cref="AgentThread"/> provides the <see cref="Serialize(JsonSerializerOptions?)"/> method to serialize the thread to a
|
||||
/// <see cref="JsonElement"/> and the <see cref="AIAgent.DeserializeThread(JsonElement, JsonSerializerOptions?)"/> method
|
||||
/// <see cref="JsonElement"/> and the <see cref="AIAgent.DeserializeThreadAsync(JsonElement, JsonSerializerOptions?, System.Threading.CancellationToken)"/> method
|
||||
/// can be used to deserialize the thread.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="AIAgent"/>
|
||||
/// <seealso cref="AIAgent.GetNewThread()"/>
|
||||
/// <seealso cref="AIAgent.DeserializeThread(JsonElement, JsonSerializerOptions?)"/>
|
||||
/// <seealso cref="AIAgent.GetNewThreadAsync(System.Threading.CancellationToken)"/>
|
||||
/// <seealso cref="AIAgent.DeserializeThreadAsync(JsonElement, JsonSerializerOptions?, System.Threading.CancellationToken)"/>
|
||||
public abstract class AgentThread
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -74,11 +74,11 @@ public abstract class DelegatingAIAgent : AIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AgentThread GetNewThread() => this.InnerAgent.GetNewThread();
|
||||
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default) => this.InnerAgent.GetNewThreadAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> this.InnerAgent.DeserializeThread(serializedThread, jsonSerializerOptions);
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
=> this.InnerAgent.DeserializeThreadAsync(serializedThread, jsonSerializerOptions, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<AgentRunResponse> RunCoreAsync(
|
||||
|
||||
@@ -42,20 +42,20 @@ public class CopilotStudioAgent : AIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override AgentThread GetNewThread()
|
||||
=> new CopilotStudioAgentThread();
|
||||
public sealed override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
=> new(new CopilotStudioAgentThread());
|
||||
|
||||
/// <summary>
|
||||
/// Get a new <see cref="AgentThread"/> instance using an existing conversation id, to continue that conversation.
|
||||
/// </summary>
|
||||
/// <param name="conversationId">The conversation id to continue.</param>
|
||||
/// <returns>A new <see cref="AgentThread"/> instance.</returns>
|
||||
public AgentThread GetNewThread(string conversationId)
|
||||
=> new CopilotStudioAgentThread() { ConversationId = conversationId };
|
||||
public ValueTask<AgentThread> GetNewThreadAsync(string conversationId)
|
||||
=> new(new CopilotStudioAgentThread() { ConversationId = conversationId });
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> new CopilotStudioAgentThread(serializedThread, jsonSerializerOptions);
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
=> new(new CopilotStudioAgentThread(serializedThread, jsonSerializerOptions));
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(
|
||||
@@ -68,7 +68,7 @@ public class CopilotStudioAgent : AIAgent
|
||||
|
||||
// Ensure that we have a valid thread to work with.
|
||||
// If the thread ID is null, we need to start a new conversation and set the thread ID accordingly.
|
||||
thread ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (thread is not CopilotStudioAgentThread typedThread)
|
||||
{
|
||||
throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
|
||||
@@ -106,7 +106,8 @@ public class CopilotStudioAgent : AIAgent
|
||||
|
||||
// Ensure that we have a valid thread to work with.
|
||||
// If the thread ID is null, we need to start a new conversation and set the thread ID accordingly.
|
||||
thread ??= this.GetNewThread();
|
||||
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (thread is not CopilotStudioAgentThread typedThread)
|
||||
{
|
||||
throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
|
||||
@@ -35,7 +36,7 @@ public static class CosmosDBChatExtensions
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(connectionString, databaseId, containerId);
|
||||
options.ChatMessageStoreFactory = (context, ct) => new ValueTask<ChatMessageStore>(new CosmosChatMessageStore(connectionString, databaseId, containerId));
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ public static class CosmosDBChatExtensions
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(accountEndpoint, new DefaultAzureCredential(), databaseId, containerId);
|
||||
options.ChatMessageStoreFactory = (context, ct) => new ValueTask<ChatMessageStore>(new CosmosChatMessageStore(accountEndpoint, new DefaultAzureCredential(), databaseId, containerId));
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ public static class CosmosDBChatExtensions
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(cosmosClient, databaseId, containerId);
|
||||
options.ChatMessageStoreFactory = (context, ct) => new ValueTask<ChatMessageStore>(new CosmosChatMessageStore(cosmosClient, databaseId, containerId));
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ internal class AgentEntity(IServiceProvider services, CancellationToken cancella
|
||||
// Start the agent response stream
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> responseStream = agentWrapper.RunStreamingAsync(
|
||||
this.State.Data.ConversationHistory.SelectMany(e => e.Messages).Select(m => m.ToChatMessage()),
|
||||
agentWrapper.GetNewThread(),
|
||||
await agentWrapper.GetNewThreadAsync(cancellationToken).ConfigureAwait(false),
|
||||
options: null,
|
||||
this._cancellationToken);
|
||||
|
||||
|
||||
@@ -32,11 +32,12 @@ public sealed class DurableAIAgent : AIAgent
|
||||
/// <summary>
|
||||
/// Creates a new agent thread for this agent using a random session ID.
|
||||
/// </summary>
|
||||
/// <returns>A new agent thread.</returns>
|
||||
public override AgentThread GetNewThread()
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A value task that represents the asynchronous operation. The task result contains a new agent thread.</returns>
|
||||
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
AgentSessionId sessionId = this._context.NewAgentSessionId(this._agentName);
|
||||
return new DurableAgentThread(sessionId);
|
||||
return ValueTask.FromResult<AgentThread>(new DurableAgentThread(sessionId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,12 +45,13 @@ public sealed class DurableAIAgent : AIAgent
|
||||
/// </summary>
|
||||
/// <param name="serializedThread">The serialized thread data.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional JSON serializer options.</param>
|
||||
/// <returns>The deserialized agent thread.</returns>
|
||||
public override AgentThread DeserializeThread(
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A value task that represents the asynchronous operation. The task result contains the deserialized agent thread.</returns>
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(
|
||||
JsonElement serializedThread,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions);
|
||||
return ValueTask.FromResult<AgentThread>(DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,12 +76,12 @@ public sealed class DurableAIAgent : AIAgent
|
||||
throw new NotSupportedException("Cancellation is not supported for durable agents.");
|
||||
}
|
||||
|
||||
thread ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (thread is not DurableAgentThread durableThread)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The provided thread is not valid for a durable agent. " +
|
||||
"Create a new thread using GetNewThread or provide a thread previously created by this agent.",
|
||||
"Create a new thread using GetNewThreadAsync or provide a thread previously created by this agent.",
|
||||
paramName: nameof(thread));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,16 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient)
|
||||
|
||||
public override string? Name { get; } = name;
|
||||
|
||||
public override AgentThread DeserializeThread(
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(
|
||||
JsonElement serializedThread,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions);
|
||||
return ValueTask.FromResult<AgentThread>(DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions));
|
||||
}
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new DurableAgentThread(AgentSessionId.WithRandomKey(this.Name!));
|
||||
return ValueTask.FromResult<AgentThread>(new DurableAgentThread(AgentSessionId.WithRandomKey(this.Name!)));
|
||||
}
|
||||
|
||||
protected override async Task<AgentRunResponse> RunCoreAsync(
|
||||
@@ -29,7 +29,7 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient)
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
thread ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (thread is not DurableAgentThread durableThread)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
||||
@@ -74,7 +74,7 @@ public static async Task<string> SpamDetectionOrchestration(
|
||||
|
||||
// Get the spam detection agent
|
||||
DurableAIAgent spamDetectionAgent = context.GetAgent("SpamDetectionAgent");
|
||||
AgentThread spamThread = spamDetectionAgent.GetNewThread();
|
||||
AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync();
|
||||
|
||||
// Step 1: Check if the email is spam
|
||||
AgentRunResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
|
||||
@@ -97,7 +97,7 @@ public static async Task<string> SpamDetectionOrchestration(
|
||||
{
|
||||
// Generate and send response for legitimate email
|
||||
DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent");
|
||||
AgentThread emailThread = emailAssistantAgent.GetNewThread();
|
||||
AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync();
|
||||
|
||||
AgentRunResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
|
||||
message:
|
||||
|
||||
@@ -38,15 +38,15 @@ public sealed class InMemoryAgentThreadStore : AgentThreadStore
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<AgentThread> GetThreadAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default)
|
||||
public override async ValueTask<AgentThread> GetThreadAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = GetKey(conversationId, agent.Id);
|
||||
JsonElement? threadContent = this._threads.TryGetValue(key, out var existingThread) ? existingThread : null;
|
||||
|
||||
return threadContent switch
|
||||
{
|
||||
null => new ValueTask<AgentThread>(agent.GetNewThread()),
|
||||
_ => new ValueTask<AgentThread>(agent.DeserializeThread(threadContent.Value)),
|
||||
null => await agent.GetNewThreadAsync(cancellationToken).ConfigureAwait(false),
|
||||
_ => await agent.DeserializeThreadAsync(threadContent.Value, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,6 @@ public sealed class NoopAgentThreadStore : AgentThreadStore
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<AgentThread> GetThreadAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new ValueTask<AgentThread>(agent.GetNewThread());
|
||||
return agent.GetNewThreadAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,15 @@ internal class PurviewAgent : AIAgent, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this._innerAgent.DeserializeThread(serializedThread, jsonSerializerOptions);
|
||||
return this._innerAgent.DeserializeThreadAsync(serializedThread, jsonSerializerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentThread GetNewThread()
|
||||
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this._innerAgent.GetNewThread();
|
||||
return this._innerAgent.GetNewThreadAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -20,8 +20,8 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
|
||||
this._emitEvents = emitEvents;
|
||||
}
|
||||
|
||||
private AgentThread EnsureThread(IWorkflowContext context) =>
|
||||
this._thread ??= this._agent.GetNewThread();
|
||||
private async Task<AgentThread> EnsureThreadAsync(IWorkflowContext context, CancellationToken cancellationToken) =>
|
||||
this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
private const string ThreadStateKey = nameof(_thread);
|
||||
protected internal override async ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
@@ -43,7 +43,7 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
|
||||
JsonElement? threadValue = await context.ReadStateAsync<JsonElement?>(ThreadStateKey, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
if (threadValue.HasValue)
|
||||
{
|
||||
this._thread = this._agent.DeserializeThread(threadValue.Value);
|
||||
this._thread = await this._agent.DeserializeThreadAsync(threadValue.Value, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await base.OnCheckpointRestoredAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
@@ -54,7 +54,10 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
|
||||
if (emitEvents ?? this._emitEvents)
|
||||
{
|
||||
// Run the agent in streaming mode only when agent run update events are to be emitted.
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentStream = this._agent.RunStreamingAsync(messages, this.EnsureThread(context), cancellationToken: cancellationToken);
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentStream = this._agent.RunStreamingAsync(
|
||||
messages,
|
||||
await this.EnsureThreadAsync(context, cancellationToken).ConfigureAwait(false),
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
List<AgentRunResponseUpdate> updates = [];
|
||||
|
||||
@@ -74,7 +77,10 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
|
||||
else
|
||||
{
|
||||
// Otherwise, run the agent in non-streaming mode.
|
||||
AgentRunResponse response = await this._agent.RunAsync(messages, this.EnsureThread(context), cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
AgentRunResponse response = await this._agent.RunAsync(
|
||||
messages,
|
||||
await this.EnsureThreadAsync(context, cancellationToken).ConfigureAwait(false),
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(response.Messages, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +63,15 @@ internal sealed class WorkflowHostAgent : AIAgent
|
||||
protocol.ThrowIfNotChatProtocol();
|
||||
}
|
||||
|
||||
public override AgentThread GetNewThread() => new WorkflowThread(this._workflow, this.GenerateNewId(), this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails);
|
||||
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
=> new(new WorkflowThread(this._workflow, this.GenerateNewId(), this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails));
|
||||
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> new WorkflowThread(this._workflow, serializedThread, this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, jsonSerializerOptions);
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
=> new(new WorkflowThread(this._workflow, serializedThread, this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, jsonSerializerOptions));
|
||||
|
||||
private ValueTask<WorkflowThread> UpdateThreadAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, CancellationToken cancellationToken = default)
|
||||
private async ValueTask<WorkflowThread> UpdateThreadAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
thread ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (thread is not WorkflowThread workflowThread)
|
||||
{
|
||||
@@ -80,7 +81,7 @@ internal sealed class WorkflowHostAgent : AIAgent
|
||||
// For workflow threads, messages are added directly via the internal AddMessages method
|
||||
// The MessageStore methods are used for agent invocation scenarios
|
||||
workflowThread.MessageStore.AddMessages(messages);
|
||||
return new ValueTask<WorkflowThread>(workflowThread);
|
||||
return workflowThread;
|
||||
}
|
||||
|
||||
protected override async
|
||||
|
||||
@@ -283,7 +283,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
|
||||
// We can derive the type of supported thread from whether we have a conversation id,
|
||||
// so let's update it and set the conversation id for the service thread case.
|
||||
this.UpdateThreadWithTypeAndConversationId(safeThread, chatResponse.ConversationId);
|
||||
await this.UpdateThreadWithTypeAndConversationIdAsync(safeThread, chatResponse.ConversationId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// To avoid inconsistent state we only notify the thread of the input messages if no error occurs after the initial request.
|
||||
await NotifyMessageStoreOfNewMessagesAsync(safeThread, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false);
|
||||
@@ -302,19 +302,30 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
: this.ChatClient.GetService(serviceType, serviceKey));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentThread GetNewThread()
|
||||
=> new ChatClientAgentThread
|
||||
public override async ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatMessageStore? messageStore = this._agentOptions?.ChatMessageStoreFactory is not null
|
||||
? await this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
|
||||
AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null
|
||||
? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
|
||||
return new ChatClientAgentThread
|
||||
{
|
||||
MessageStore = this._agentOptions?.ChatMessageStoreFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }),
|
||||
AIContextProvider = this._agentOptions?.AIContextProviderFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null })
|
||||
MessageStore = messageStore,
|
||||
AIContextProvider = contextProvider
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new agent thread instance using an existing conversation identifier to continue that conversation.
|
||||
/// </summary>
|
||||
/// <param name="conversationId">The identifier of an existing conversation to continue.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>
|
||||
/// A new <see cref="AgentThread"/> instance configured to work with the specified conversation.
|
||||
/// A value task representing the asynchronous operation. The task result contains a new <see cref="AgentThread"/> instance configured to work with the specified conversation.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -326,19 +337,26 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
/// instances that support server-side conversation storage through their underlying <see cref="IChatClient"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public AgentThread GetNewThread(string conversationId)
|
||||
=> new ChatClientAgentThread()
|
||||
public async ValueTask<AgentThread> GetNewThreadAsync(string conversationId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null
|
||||
? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
|
||||
return new ChatClientAgentThread()
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
AIContextProvider = this._agentOptions?.AIContextProviderFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null })
|
||||
AIContextProvider = contextProvider
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new agent thread instance using an existing <see cref="ChatMessageStore"/> to continue a conversation.
|
||||
/// </summary>
|
||||
/// <param name="chatMessageStore">The <see cref="ChatMessageStore"/> instance to use for managing the conversation's message history.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>
|
||||
/// A new <see cref="AgentThread"/> instance configured to work with the provided <paramref name="chatMessageStore"/>.
|
||||
/// A value task representing the asynchronous operation. The task result contains a new <see cref="AgentThread"/> instance configured to work with the provided <paramref name="chatMessageStore"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -347,36 +365,43 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
/// with a <see cref="ChatMessageStore"/> may not be compatible with these services.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Where a service requires server-side conversation storage, use <see cref="GetNewThread(string)"/>.
|
||||
/// Where a service requires server-side conversation storage, use <see cref="GetNewThreadAsync(string, CancellationToken)"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the agent detects, during the first run, that the underlying AI service requires server-side conversation storage,
|
||||
/// the thread will throw an exception to indicate that it cannot continue using the provided <see cref="ChatMessageStore"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public AgentThread GetNewThread(ChatMessageStore chatMessageStore)
|
||||
=> new ChatClientAgentThread()
|
||||
public async ValueTask<AgentThread> GetNewThreadAsync(ChatMessageStore chatMessageStore, CancellationToken cancellationToken = default)
|
||||
{
|
||||
AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null
|
||||
? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
|
||||
return new ChatClientAgentThread()
|
||||
{
|
||||
MessageStore = Throw.IfNull(chatMessageStore),
|
||||
AIContextProvider = this._agentOptions?.AIContextProviderFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null })
|
||||
AIContextProvider = contextProvider
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
public override async ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Func<JsonElement, JsonSerializerOptions?, ChatMessageStore>? chatMessageStoreFactory = this._agentOptions?.ChatMessageStoreFactory is null ?
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<ChatMessageStore>>? chatMessageStoreFactory = this._agentOptions?.ChatMessageStoreFactory is null ?
|
||||
null :
|
||||
(jse, jso) => this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso });
|
||||
(jse, jso, ct) => this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }, ct);
|
||||
|
||||
Func<JsonElement, JsonSerializerOptions?, AIContextProvider>? aiContextProviderFactory = this._agentOptions?.AIContextProviderFactory is null ?
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<AIContextProvider>>? aiContextProviderFactory = this._agentOptions?.AIContextProviderFactory is null ?
|
||||
null :
|
||||
(jse, jso) => this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso });
|
||||
(jse, jso, ct) => this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }, ct);
|
||||
|
||||
return new ChatClientAgentThread(
|
||||
return await ChatClientAgentThread.DeserializeAsync(
|
||||
serializedThread,
|
||||
jsonSerializerOptions,
|
||||
chatMessageStoreFactory,
|
||||
aiContextProviderFactory);
|
||||
aiContextProviderFactory,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#region Private
|
||||
@@ -426,7 +451,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
|
||||
// We can derive the type of supported thread from whether we have a conversation id,
|
||||
// so let's update it and set the conversation id for the service thread case.
|
||||
this.UpdateThreadWithTypeAndConversationId(safeThread, chatResponse.ConversationId);
|
||||
await this.UpdateThreadWithTypeAndConversationIdAsync(safeThread, chatResponse.ConversationId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Ensure that the author name is set for each message in the response.
|
||||
foreach (ChatMessage chatResponseMessage in chatResponse.Messages)
|
||||
@@ -653,7 +678,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
throw new InvalidOperationException("A thread must be provided when continuing a background response with a continuation token.");
|
||||
}
|
||||
|
||||
thread ??= this.GetNewThread();
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (thread is not ChatClientAgentThread typedThread)
|
||||
{
|
||||
throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
|
||||
@@ -735,13 +760,13 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
return (typedThread, chatOptions, inputMessagesForChatClient, aiContextProviderMessages, chatMessageStoreMessages, continuationToken);
|
||||
}
|
||||
|
||||
private void UpdateThreadWithTypeAndConversationId(ChatClientAgentThread thread, string? responseConversationId)
|
||||
private async Task UpdateThreadWithTypeAndConversationIdAsync(ChatClientAgentThread thread, string? responseConversationId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(responseConversationId) && !string.IsNullOrWhiteSpace(thread.ConversationId))
|
||||
{
|
||||
// We were passed a thread that is service managed, but we got no conversation id back from the chat client,
|
||||
// meaning the service doesn't support service managed threads, so the thread cannot be used with this service.
|
||||
throw new InvalidOperationException("Service did not return a valid conversation id when using a service managed thread.");
|
||||
// We were passed an AgentThread that has an id for service managed chat history, but we got no conversation id back from the chat client,
|
||||
// meaning the service doesn't support service managed chat history, so the thread cannot be used with this service.
|
||||
throw new InvalidOperationException("Service did not return a valid conversation id when using an AgentThread with service managed chat history.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(responseConversationId))
|
||||
@@ -752,10 +777,12 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the service doesn't use service side thread storage (i.e. we got no id back from invocation), and
|
||||
// If the service doesn't use service side chat history storage (i.e. we got no id back from invocation), and
|
||||
// the thread has no MessageStore yet, we should update the thread with the custom MessageStore or
|
||||
// default InMemoryMessageStore so that it has somewhere to store the chat history.
|
||||
thread.MessageStore ??= this._agentOptions?.ChatMessageStoreFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }) ?? new InMemoryChatMessageStore();
|
||||
thread.MessageStore ??= this._agentOptions?.ChatMessageStoreFactory is not null
|
||||
? await this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: new InMemoryChatMessageStore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
@@ -40,14 +42,14 @@ public sealed class ChatClientAgentOptions
|
||||
/// Gets or sets a factory function to create an instance of <see cref="ChatMessageStore"/>
|
||||
/// which will be used to store chat messages for this agent.
|
||||
/// </summary>
|
||||
public Func<ChatMessageStoreFactoryContext, ChatMessageStore>? ChatMessageStoreFactory { get; set; }
|
||||
public Func<ChatMessageStoreFactoryContext, CancellationToken, ValueTask<ChatMessageStore>>? ChatMessageStoreFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a factory function to create an instance of <see cref="AIContextProvider"/>
|
||||
/// which will be used to create a context provider for each new thread, and can then
|
||||
/// provide additional context for each agent run.
|
||||
/// </summary>
|
||||
public Func<AIContextProviderFactoryContext, AIContextProvider>? AIContextProviderFactory { get; set; }
|
||||
public Func<AIContextProviderFactoryContext, CancellationToken, ValueTask<AIContextProvider>>? AIContextProviderFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to use the provided <see cref="IChatClient"/> instance as is,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
@@ -22,48 +24,6 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatClientAgentThread"/> class from previously serialized state.
|
||||
/// </summary>
|
||||
/// <param name="serializedThreadState">A <see cref="JsonElement"/> representing the serialized state of the thread.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <param name="chatMessageStoreFactory">
|
||||
/// An optional factory function to create a custom <see cref="ChatMessageStore"/> from its serialized state.
|
||||
/// If not provided, the default in-memory message store will be used.
|
||||
/// </param>
|
||||
/// <param name="aiContextProviderFactory">
|
||||
/// An optional factory function to create a custom <see cref="AIContextProvider"/> from its serialized state.
|
||||
/// If not provided, no context provider will be configured.
|
||||
/// </param>
|
||||
internal ChatClientAgentThread(
|
||||
JsonElement serializedThreadState,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, ChatMessageStore>? chatMessageStoreFactory = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, AIContextProvider>? aiContextProviderFactory = null)
|
||||
{
|
||||
if (serializedThreadState.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
throw new ArgumentException("The serialized thread state must be a JSON object.", nameof(serializedThreadState));
|
||||
}
|
||||
|
||||
var state = serializedThreadState.Deserialize(
|
||||
AgentJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ThreadState))) as ThreadState;
|
||||
|
||||
this.AIContextProvider = aiContextProviderFactory?.Invoke(state?.AIContextProviderState ?? default, jsonSerializerOptions);
|
||||
|
||||
if (state?.ConversationId is string threadId)
|
||||
{
|
||||
this.ConversationId = threadId;
|
||||
|
||||
// Since we have an ID, we should not have a chat message store and we can return here.
|
||||
return;
|
||||
}
|
||||
|
||||
this._messageStore =
|
||||
chatMessageStoreFactory?.Invoke(state?.StoreState ?? default, jsonSerializerOptions) ??
|
||||
new InMemoryChatMessageStore(state?.StoreState ?? default, jsonSerializerOptions); // default to an in-memory store
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the underlying service thread to support cases where the chat history is stored by the agent service.
|
||||
/// </summary>
|
||||
@@ -152,6 +112,58 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
/// </summary>
|
||||
public AIContextProvider? AIContextProvider { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ChatClientAgentThread"/> class from previously serialized state.
|
||||
/// </summary>
|
||||
/// <param name="serializedThreadState">A <see cref="JsonElement"/> representing the serialized state of the thread.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <param name="chatMessageStoreFactory">
|
||||
/// An optional factory function to create a custom <see cref="ChatMessageStore"/> from its serialized state.
|
||||
/// If not provided, the default in-memory message store will be used.
|
||||
/// </param>
|
||||
/// <param name="aiContextProviderFactory">
|
||||
/// An optional factory function to create a custom <see cref="AIContextProvider"/> from its serialized state.
|
||||
/// If not provided, no context provider will be configured.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>A task representing the asynchronous operation. The task result contains the deserialized <see cref="ChatClientAgentThread"/>.</returns>
|
||||
internal static async Task<ChatClientAgentThread> DeserializeAsync(
|
||||
JsonElement serializedThreadState,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<ChatMessageStore>>? chatMessageStoreFactory = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<AIContextProvider>>? aiContextProviderFactory = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (serializedThreadState.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
throw new ArgumentException("The serialized thread state must be a JSON object.", nameof(serializedThreadState));
|
||||
}
|
||||
|
||||
var state = serializedThreadState.Deserialize(
|
||||
AgentJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ThreadState))) as ThreadState;
|
||||
|
||||
var thread = new ChatClientAgentThread();
|
||||
|
||||
thread.AIContextProvider = aiContextProviderFactory is not null
|
||||
? await aiContextProviderFactory.Invoke(state?.AIContextProviderState ?? default, jsonSerializerOptions, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
|
||||
if (state?.ConversationId is string threadId)
|
||||
{
|
||||
thread.ConversationId = threadId;
|
||||
|
||||
// Since we have an ID, we should not have a chat message store and we can return here.
|
||||
return thread;
|
||||
}
|
||||
|
||||
thread._messageStore =
|
||||
chatMessageStoreFactory is not null
|
||||
? await chatMessageStoreFactory.Invoke(state?.StoreState ?? default, jsonSerializerOptions, cancellationToken).ConfigureAwait(false)
|
||||
: new InMemoryChatMessageStore(state?.StoreState ?? default, jsonSerializerOptions); // default to an in-memory store
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user