mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Update AgentThread, MessageStores and Context Providers to deserialize via constructor. (#779)
* Update AgentThread, MessageStores and Context Providers to deserialize via constructor. * Fix pr comment. * Add additional validation for AgentThread deserialization * Update WorkflowMessageStore desreialize to improve error checking. * Reduce allocations in InMemoryChatMessageStore --------- Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
This commit is contained in:
@@ -40,7 +40,7 @@ await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedTh
|
||||
JsonElement reloadedSerializedThread = JsonSerializer.Deserialize<JsonElement>(await File.ReadAllTextAsync(tempFilePath));
|
||||
|
||||
// Deserialize the thread state after loading from storage.
|
||||
AgentThread resumedThread = await agent.DeserializeThreadAsync(reloadedSerializedThread);
|
||||
AgentThread resumedThread = agent.DeserializeThread(reloadedSerializedThread);
|
||||
|
||||
// Run the agent again with the resumed thread.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
|
||||
|
||||
+18
-14
@@ -38,12 +38,12 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
{
|
||||
Name = JokerName,
|
||||
Instructions = JokerInstructions,
|
||||
ChatMessageStoreFactory = () =>
|
||||
ChatMessageStoreFactory = (jsonElement, jso) =>
|
||||
{
|
||||
// Create a new chat message store for this agent that stores the messages in a vector store.
|
||||
// Each thread must get its own copy of the VectorChatMessageStore, since the store
|
||||
// also contains the id that the thread is stored under.
|
||||
return new VectorChatMessageStore(vectorStore);
|
||||
return new VectorChatMessageStore(vectorStore, jsonElement, jso);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -65,7 +65,7 @@ Console.WriteLine(JsonSerializer.Serialize(serializedThread, new JsonSerializerO
|
||||
// and loaded again later.
|
||||
|
||||
// Deserialize the thread state after loading from storage.
|
||||
AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
AgentThread resumedThread = agent.DeserializeThread(serializedThread);
|
||||
|
||||
// Run the agent with the thread that stores conversation history in the vector store a second time.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
|
||||
@@ -75,10 +75,21 @@ namespace SampleApp
|
||||
/// <summary>
|
||||
/// A sample implementation of <see cref="IChatMessageStore"/> that stores chat messages in a vector store.
|
||||
/// </summary>
|
||||
/// <param name="vectorStore">The vector store to store the messages in.</param>
|
||||
internal sealed class VectorChatMessageStore(VectorStore vectorStore) : IChatMessageStore
|
||||
internal sealed class VectorChatMessageStore : IChatMessageStore
|
||||
{
|
||||
private string? _threadId;
|
||||
private readonly VectorStore _vectorStore;
|
||||
|
||||
public VectorChatMessageStore(VectorStore vectorStore, JsonElement serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
this._vectorStore = vectorStore ?? throw new ArgumentNullException(nameof(vectorStore));
|
||||
|
||||
if (serializedStoreState.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
// Here we can deserialize the thread id so that we can access the same messages as before the suspension.
|
||||
this._threadId = JsonSerializer.Deserialize<string>(serializedStoreState);
|
||||
}
|
||||
}
|
||||
|
||||
public string? ThreadId => this._threadId;
|
||||
|
||||
@@ -86,7 +97,7 @@ namespace SampleApp
|
||||
{
|
||||
this._threadId ??= Guid.NewGuid().ToString();
|
||||
|
||||
var collection = vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
|
||||
var collection = this._vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
|
||||
await collection.EnsureCollectionExistsAsync(cancellationToken);
|
||||
|
||||
await collection.UpsertAsync(messages.Select(x => new ChatHistoryItem()
|
||||
@@ -101,7 +112,7 @@ namespace SampleApp
|
||||
|
||||
public async Task<IEnumerable<ChatMessage>> GetMessagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
|
||||
var collection = this._vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
|
||||
await collection.EnsureCollectionExistsAsync(cancellationToken);
|
||||
|
||||
var records = await collection
|
||||
@@ -124,13 +135,6 @@ namespace SampleApp
|
||||
return new ValueTask<JsonElement?>(JsonSerializer.SerializeToElement(this._threadId));
|
||||
}
|
||||
|
||||
public ValueTask DeserializeStateAsync(JsonElement? serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Here we can deserialize the thread id so that we can access the same messages as before the suspension.
|
||||
this._threadId = JsonSerializer.Deserialize<string>((JsonElement)serializedStoreState!);
|
||||
return new ValueTask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The data structure used to store chat history items in the vector store.
|
||||
/// </summary>
|
||||
|
||||
@@ -38,7 +38,7 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions()
|
||||
{
|
||||
Instructions = "You are a friendly assistant. Always address the user by their name.",
|
||||
AIContextProviderFactory = () => new SampleApp.UserInfoMemory(chatClient.AsIChatClient())
|
||||
AIContextProviderFactory = (jse, jso) => new UserInfoMemory(chatClient.AsIChatClient(), jse, jso)
|
||||
});
|
||||
|
||||
// Create a new thread for the conversation.
|
||||
@@ -57,7 +57,7 @@ var threadElement = await thread.SerializeAsync();
|
||||
Console.WriteLine("\n>> Use deserialized thread with previously created memories\n");
|
||||
|
||||
// Later we can deserialize the thread and continue the conversation with the previous memory component state.
|
||||
var deserializedThread = await agent.DeserializeThreadAsync(threadElement);
|
||||
var deserializedThread = agent.DeserializeThread(threadElement);
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", deserializedThread));
|
||||
|
||||
Console.WriteLine("\n>> Read memories from memory component\n");
|
||||
@@ -89,16 +89,30 @@ namespace SampleApp
|
||||
/// <summary>
|
||||
/// Sample memory component that can remember a user's name and age.
|
||||
/// </summary>
|
||||
internal sealed class UserInfoMemory(IChatClient chatClient, UserInfo? userInfo = null) : AIContextProvider
|
||||
internal sealed class UserInfoMemory : AIContextProvider
|
||||
{
|
||||
public UserInfo UserInfo { get; set; } = userInfo ?? new();
|
||||
private readonly IChatClient _chatClient;
|
||||
|
||||
public UserInfoMemory(IChatClient chatClient, UserInfo? userInfo = null)
|
||||
{
|
||||
this._chatClient = chatClient;
|
||||
this.UserInfo = userInfo ?? new UserInfo();
|
||||
}
|
||||
|
||||
public UserInfoMemory(IChatClient chatClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
this._chatClient = chatClient;
|
||||
this.UserInfo = JsonSerializer.Deserialize<UserInfo>(serializedState, jsonSerializerOptions) ?? new UserInfo();
|
||||
}
|
||||
|
||||
public UserInfo UserInfo { get; set; }
|
||||
|
||||
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Try and extract the user name and age from the message if we don't have it already and it's a user message.
|
||||
if ((this.UserInfo.UserName == null || this.UserInfo.UserAge == null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
|
||||
{
|
||||
var result = await chatClient.GetResponseAsync<UserInfo>(
|
||||
var result = await this._chatClient.GetResponseAsync<UserInfo>(
|
||||
context.RequestMessages,
|
||||
new ChatOptions()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user