From 21e00c054bf11d48558248c0456a3d827fd2845f Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:49:01 +0000 Subject: [PATCH] .NET: Rename ChatMessageStore to ChatHistoryProvider (#3375) * Rename ChatMessageStore to ChatHistoryProvider * Fix merge issue * Fixed PR comments * Fix tests after property rename * Add unit tests and fix merge issues * Fix encoding --- .../Program.cs | 16 +- .../Program.cs | 2 +- .../Program.cs | 30 +- .../Agent_Step16_ChatReduction/Program.cs | 2 +- .../Program.cs | 2 +- .../AgentAbstractionsJsonUtilities.cs | 2 +- .../AgentThread.cs | 2 +- ...MessageStore.cs => ChatHistoryProvider.cs} | 50 +-- .../ChatHistoryProviderExtensions.cs | 51 +++ ...cs => ChatHistoryProviderMessageFilter.cs} | 34 +- .../ChatMessageStoreExtensions.cs | 50 --- .../InMemoryAgentThread.cs | 48 +-- ...tore.cs => InMemoryChatHistoryProvider.cs} | 63 ++-- .../PersistentAgentsClientExtensions.cs | 2 +- .../AzureAIProjectChatClientExtensions.cs | 2 +- ...eStore.cs => CosmosChatHistoryProvider.cs} | 78 ++--- .../CosmosDBChatExtensions.cs | 24 +- .../Microsoft.Agents.AI.CosmosNoSql.csproj | 2 +- .../OpenAIAssistantClientExtensions.cs | 2 +- ...tore.cs => WorkflowChatHistoryProvider.cs} | 6 +- .../WorkflowHostAgent.cs | 2 +- .../WorkflowThread.cs | 20 +- .../WorkflowsJsonUtilities.cs | 2 +- .../ChatClient/ChatClientAgent.cs | 118 +++---- .../ChatClient/ChatClientAgentOptions.cs | 16 +- .../ChatClient/ChatClientAgentThread.cs | 68 ++-- .../AnthropicChatCompletionFixture.cs | 4 +- .../AIProjectClientFixture.cs | 4 +- .../ChatHistoryProviderExtensionsTests.cs | 129 ++++++++ ... ChatHistoryProviderMessageFilterTests.cs} | 82 ++--- ...reTests.cs => ChatHistoryProviderTests.cs} | 44 +-- .../InMemoryAgentThreadTests.cs | 60 ++-- ...cs => InMemoryChatHistoryProviderTests.cs} | 290 +++++++++--------- .../TestJsonSerializerContext.cs | 2 +- ...s.cs => CosmosChatHistoryProviderTests.cs} | 212 ++++++------- .../ChatClient/ChatClientAgentOptionsTests.cs | 14 +- .../ChatClient/ChatClientAgentTests.cs | 56 ++-- .../ChatClient/ChatClientAgentThreadTests.cs | 82 ++--- ...hatClientAgent_BackgroundResponsesTests.cs | 80 ++--- ...tClientAgent_ChatHistoryManagementTests.cs | 144 ++++----- .../ChatClientAgent_DeserializeThreadTests.cs | 14 +- .../ChatClientAgent_GetNewThreadTests.cs | 20 +- .../TestEchoAgent.cs | 2 +- .../OpenAIChatCompletionFixture.cs | 4 +- .../OpenAIResponseFixture.cs | 4 +- 45 files changed, 1037 insertions(+), 904 deletions(-) rename dotnet/src/Microsoft.Agents.AI.Abstractions/{ChatMessageStore.cs => ChatHistoryProvider.cs} (83%) create mode 100644 dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProviderExtensions.cs rename dotnet/src/Microsoft.Agents.AI.Abstractions/{ChatMessageStoreMessageFilter.cs => ChatHistoryProviderMessageFilter.cs} (63%) delete mode 100644 dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreExtensions.cs rename dotnet/src/Microsoft.Agents.AI.Abstractions/{InMemoryChatMessageStore.cs => InMemoryChatHistoryProvider.cs} (74%) rename dotnet/src/Microsoft.Agents.AI.CosmosNoSql/{CosmosChatMessageStore.cs => CosmosChatHistoryProvider.cs} (86%) rename dotnet/src/Microsoft.Agents.AI.Workflows/{WorkflowMessageStore.cs => WorkflowChatHistoryProvider.cs} (93%) create mode 100644 dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderExtensionsTests.cs rename dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/{ChatMessageStoreMessageFilterTests.cs => ChatHistoryProviderMessageFilterTests.cs} (58%) rename dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/{ChatMessageStoreTests.cs => ChatHistoryProviderTests.cs} (53%) rename dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/{InMemoryChatMessageStoreTests.cs => InMemoryChatHistoryProviderTests.cs} (60%) rename dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/{CosmosChatMessageStoreTests.cs => CosmosChatHistoryProviderTests.cs} (72%) diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs index 3e52a5f01a..52efda2cf6 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs @@ -45,18 +45,18 @@ namespace SampleApp } // Get existing messages from the store - var invokingContext = new ChatMessageStore.InvokingContext(messages); - var storeMessages = await typedThread.MessageStore.InvokingAsync(invokingContext, cancellationToken); + var invokingContext = new ChatHistoryProvider.InvokingContext(messages); + var storeMessages = await typedThread.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken); // Clone the input messages and turn them into response messages with upper case text. List responseMessages = CloneAndToUpperCase(messages, this.Name).ToList(); // Notify the thread of the input and output messages. - var invokedContext = new ChatMessageStore.InvokedContext(messages, storeMessages) + var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages) { ResponseMessages = responseMessages }; - await typedThread.MessageStore.InvokedAsync(invokedContext, cancellationToken); + await typedThread.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken); return new AgentResponse { @@ -77,18 +77,18 @@ namespace SampleApp } // Get existing messages from the store - var invokingContext = new ChatMessageStore.InvokingContext(messages); - var storeMessages = await typedThread.MessageStore.InvokingAsync(invokingContext, cancellationToken); + var invokingContext = new ChatHistoryProvider.InvokingContext(messages); + var storeMessages = await typedThread.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken); // Clone the input messages and turn them into response messages with upper case text. List responseMessages = CloneAndToUpperCase(messages, this.Name).ToList(); // Notify the thread of the input and output messages. - var invokedContext = new ChatMessageStore.InvokedContext(messages, storeMessages) + var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages) { ResponseMessages = responseMessages }; - await typedThread.MessageStore.InvokedAsync(invokedContext, cancellationToken); + await typedThread.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken); foreach (var message in responseMessages) { diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs index a4904ecf77..1371f07ba7 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs @@ -66,7 +66,7 @@ AIAgent agent = azureOpenAIClient // Since we are using ChatCompletion which stores chat history locally, we can also add a message removal policy // that removes messages produced by the TextSearchProvider before they are added to the chat history, so that // we don't bloat chat history with all the search result messages. - ChatMessageStoreFactory = (ctx, ct) => new ValueTask(new InMemoryChatMessageStore(ctx.SerializedState, ctx.JsonSerializerOptions) + ChatHistoryProviderFactory = (ctx, ct) => new ValueTask(new InMemoryChatHistoryProvider(ctx.SerializedState, ctx.JsonSerializerOptions) .WithAIContextProviderMessageRemoval()), }); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs index a03b3bb349..50ad22784f 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs @@ -31,17 +31,17 @@ AIAgent agent = new AzureOpenAIClient( { ChatOptions = new() { Instructions = "You are good at telling jokes." }, Name = "Joker", - ChatMessageStoreFactory = (ctx, ct) => new ValueTask( - // 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. - new VectorChatMessageStore(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions)) + ChatHistoryProviderFactory = (ctx, ct) => new ValueTask( + // Create a new ChatHistoryProvider for this agent that stores chat history in a vector store. + // Each thread must get its own copy of the VectorChatHistoryProvider, since the provider + // also contains the id that the chat history is stored under. + new VectorChatHistoryProvider(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions)) }); // Start a new thread for the agent conversation. AgentThread thread = await agent.GetNewThreadAsync(); -// Run the agent with the thread that stores conversation history in the vector store. +// Run the agent with the thread that stores chat history in the vector store. Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread)); // Serialize the thread state, so it can be stored for later use. @@ -58,30 +58,30 @@ Console.WriteLine(JsonSerializer.Serialize(serializedThread, new JsonSerializerO // Deserialize the thread state after loading from storage. AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread); -// Run the agent with the thread that stores conversation history in the vector store a second time. +// Run the agent with the thread that stores chat 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)); -// We can access the VectorChatMessageStore via the thread's GetService method if we need to read the key under which threads are stored. -var messageStore = resumedThread.GetService()!; -Console.WriteLine($"\nThread is stored in vector store under key: {messageStore.ThreadDbKey}"); +// We can access the VectorChatHistoryProvider via the thread's GetService method if we need to read the key under which chat history is stored. +var chatHistoryProvider = resumedThread.GetService()!; +Console.WriteLine($"\nThread is stored in vector store under key: {chatHistoryProvider.ThreadDbKey}"); namespace SampleApp { /// - /// A sample implementation of that stores chat messages in a vector store. + /// A sample implementation of that stores chat history in a vector store. /// - internal sealed class VectorChatMessageStore : ChatMessageStore + internal sealed class VectorChatHistoryProvider : ChatHistoryProvider { private readonly VectorStore _vectorStore; - public VectorChatMessageStore(VectorStore vectorStore, JsonElement serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null) + public VectorChatHistoryProvider(VectorStore vectorStore, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null) { this._vectorStore = vectorStore ?? throw new ArgumentNullException(nameof(vectorStore)); - if (serializedStoreState.ValueKind is JsonValueKind.String) + if (serializedState.ValueKind is JsonValueKind.String) { // Here we can deserialize the thread id so that we can access the same messages as before the suspension. - this.ThreadDbKey = serializedStoreState.Deserialize(); + this.ThreadDbKey = serializedState.Deserialize(); } } diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs index a80dd0fed0..a822f3a423 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs @@ -24,7 +24,7 @@ AIAgent agent = new AzureOpenAIClient( { ChatOptions = new() { Instructions = "You are good at telling jokes." }, Name = "Joker", - ChatMessageStoreFactory = (ctx, ct) => new ValueTask(new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions)) + ChatHistoryProviderFactory = (ctx, ct) => new ValueTask(new InMemoryChatHistoryProvider(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions)) }); AgentThread thread = await agent.GetNewThreadAsync(); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs index dbdd3af6f0..63b6f8d4fc 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs @@ -45,7 +45,7 @@ AIAgent agent = new AzureOpenAIClient( You manage a TODO list for the user. When the user has completed one of the tasks it can be removed from the TODO list. Only provide the list of TODO items if asked. You remind users of upcoming calendar events when the user interacts with you. """ }, - ChatMessageStoreFactory = (ctx, ct) => new ValueTask(new InMemoryChatMessageStore() + ChatHistoryProviderFactory = (ctx, ct) => new ValueTask(new InMemoryChatHistoryProvider() // Use WithAIContextProviderMessageRemoval, so that we don't store the messages from the AI context provider in the chat history. // You may want to store these messages, depending on their content and your requirements. .WithAIContextProviderMessageRemoval()), diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs index 937d871c56..3d420fb573 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs @@ -82,7 +82,7 @@ public static partial class AgentAbstractionsJsonUtilities [JsonSerializable(typeof(AgentResponseUpdate[]))] [JsonSerializable(typeof(ServiceIdAgentThread.ServiceIdAgentThreadState))] [JsonSerializable(typeof(InMemoryAgentThread.InMemoryAgentThreadState))] - [JsonSerializable(typeof(InMemoryChatMessageStore.StoreState))] + [JsonSerializable(typeof(InMemoryChatHistoryProvider.State))] [ExcludeFromCodeCoverage] private sealed partial class JsonContext : JsonSerializerContext; diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs index 318307ec43..579202c368 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs @@ -68,7 +68,7 @@ public abstract class AgentThread /// is . /// /// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the , - /// including itself or any services it might be wrapping. For example, to access a if available for the instance, + /// including itself or any services it might be wrapping. For example, to access a if available for the instance, /// may be used to request it. /// public virtual object? GetService(Type serviceType, object? serviceKey = null) diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStore.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs similarity index 83% rename from dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStore.cs rename to dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs index 6c4eb1e3f9..1b75e45629 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs @@ -11,11 +11,12 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// -/// Provides an abstract base class for storing and managing chat messages associated with agent conversations. +/// Provides an abstract base class for fetching chat messages from, and adding chat messages to, chat history for the purposes of agent execution. /// /// /// -/// defines the contract for persistent storage of chat messages in agent conversations. +/// defines the contract that an can use to retrieve messsages from chat history +/// and provide notification of newly produced messages. /// Implementations are responsible for managing message persistence, retrieval, and any necessary optimization /// strategies such as truncation, summarization, or archival. /// @@ -28,11 +29,15 @@ namespace Microsoft.Agents.AI; /// Supporting serialization for thread persistence and migration /// /// +/// +/// A is only relevant for scenarios where the underlying AI service that the agent is using +/// does not use in-service chat history storage. +/// /// -public abstract class ChatMessageStore +public abstract class ChatHistoryProvider { /// - /// Called at the start of agent invocation to retrieve all messages from the store that should be provided as context for the next agent invocation. + /// Called at the start of agent invocation to provide messages from the chat history as context for the next agent invocation. /// /// Contains the request context including the caller provided messages that will be used by the agent for this invocation. /// The to monitor for cancellation requests. The default is . @@ -56,14 +61,14 @@ public abstract class ChatMessageStore /// /// /// - /// Each store instance should be associated with a single conversation thread to ensure proper message isolation + /// Each instance should be associated with a single to ensure proper message isolation /// and context management. /// /// public abstract ValueTask> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default); /// - /// Called at the end of the agent invocation to add new messages to the store. + /// Called at the end of the agent invocation to add new messages to the chat history. /// /// Contains the invocation context including request messages, response messages, and any exception that occurred. /// The to monitor for cancellation requests. The default is . @@ -71,7 +76,7 @@ public abstract class ChatMessageStore /// /// /// Messages should be added in the order they were generated to maintain proper chronological sequence. - /// The store is responsible for preserving message ordering and ensuring that subsequent calls to + /// The is responsible for preserving message ordering and ensuring that subsequent calls to /// return messages in the correct chronological order. /// /// @@ -80,7 +85,6 @@ public abstract class ChatMessageStore /// Validating message content and metadata /// Applying storage optimizations or compression /// Triggering background maintenance operations - /// Updating indices or search capabilities /// /// /// @@ -97,13 +101,13 @@ public abstract class ChatMessageStore /// A representation of the object's state. public abstract JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null); - /// Asks the for an object of the specified type . + /// Asks the for an object of the specified type . /// The type of object being requested. /// An optional key that can be used to help identify the target service. /// The found object, otherwise . /// is . /// - /// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the , + /// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the , /// including itself or any services it might be wrapping. /// public virtual object? GetService(Type serviceType, object? serviceKey = null) @@ -115,12 +119,12 @@ public abstract class ChatMessageStore : null; } - /// Asks the for an object of type . + /// Asks the for an object of type . /// The type of the object to be retrieved. /// An optional key that can be used to help identify the target service. /// The found object, otherwise . /// - /// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the , + /// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the , /// including itself or any services it might be wrapping. /// public TService? GetService(object? serviceKey = null) @@ -130,9 +134,9 @@ public abstract class ChatMessageStore /// Contains the context information provided to . /// /// - /// This class provides context about the invocation before the messages are retrieved from the store, - /// including the new messages that will be used. Stores can use this information to determine what - /// messages should be retrieved for the invocation. + /// This class provides context about the invocation including the new messages that will be used. + /// A can use this information to determine what messages should be provided + /// for the invocation. /// public sealed class InvokingContext { @@ -169,12 +173,12 @@ public abstract class ChatMessageStore /// Initializes a new instance of the class with the specified request messages. /// /// The caller provided messages that were used by the agent for this invocation. - /// The messages retrieved from the for this invocation. + /// The messages retrieved from the for this invocation. /// is . - public InvokedContext(IEnumerable requestMessages, IEnumerable? chatMessageStoreMessages) + public InvokedContext(IEnumerable requestMessages, IEnumerable? chatHistoryProviderMessages) { this.RequestMessages = Throw.IfNull(requestMessages); - this.ChatMessageStoreMessages = chatMessageStoreMessages; + this.ChatHistoryProviderMessages = chatHistoryProviderMessages; } /// @@ -182,18 +186,18 @@ public abstract class ChatMessageStore /// /// /// A collection of instances representing new messages that were provided by the caller. - /// This does not include any supplied messages. + /// This does not include any supplied messages. /// public IEnumerable RequestMessages { get; set { field = Throw.IfNull(value); } } /// - /// Gets the messages retrieved from the for this invocation, if any. + /// Gets the messages retrieved from the for this invocation, if any. /// /// - /// A collection of instances that were retrieved from the , - /// and were used by the agent as part of the invocation. May be null on the first run. + /// A collection of instances that were retrieved from the , + /// and were used by the agent as part of the invocation. /// - public IEnumerable? ChatMessageStoreMessages { get; set; } + public IEnumerable? ChatHistoryProviderMessages { get; set; } /// /// Gets or sets the messages provided by the for this invocation, if any. diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProviderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProviderExtensions.cs new file mode 100644 index 0000000000..0f5d9524cb --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProviderExtensions.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.AI; + +namespace Microsoft.Agents.AI; + +/// +/// Contains extension methods for the class. +/// +public static class ChatHistoryProviderExtensions +{ + /// + /// Adds message filtering to an existing , so that messages passed to the and messages + /// provided by the can be filtered, updated or replaced. + /// + /// The to add the message filter to. + /// An optional filter function to apply to messages produced by the . If null, no filter is applied at this + /// stage. + /// An optional filter function to apply to the invoked context messages before they are passed to the . If null, no + /// filter is applied at this stage. + /// The with filtering applied. + public static ChatHistoryProvider WithMessageFilters( + this ChatHistoryProvider provider, + Func, IEnumerable>? invokingMessagesFilter = null, + Func? invokedMessagesFilter = null) + { + return new ChatHistoryProviderMessageFilter( + innerProvider: provider, + invokingMessagesFilter: invokingMessagesFilter, + invokedMessagesFilter: invokedMessagesFilter); + } + + /// + /// Decorates the provided chat message so that it does not add + /// messages produced by any to chat history. + /// + /// The to add the message filter to. + /// A new instance that filters out messages so they do not get added. + public static ChatHistoryProvider WithAIContextProviderMessageRemoval(this ChatHistoryProvider provider) + { + return new ChatHistoryProviderMessageFilter( + innerProvider: provider, + invokedMessagesFilter: (ctx) => + { + ctx.AIContextProviderMessages = null; + return ctx; + }); + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreMessageFilter.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProviderMessageFilter.cs similarity index 63% rename from dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreMessageFilter.cs rename to dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProviderMessageFilter.cs index e58f233067..df7b536ea2 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreMessageFilter.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProviderMessageFilter.cs @@ -11,33 +11,33 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// -/// A decorator that allows filtering the messages -/// passed into and out of an inner . +/// A decorator that allows filtering the messages +/// passed into and out of an inner . /// -public sealed class ChatMessageStoreMessageFilter : ChatMessageStore +public sealed class ChatHistoryProviderMessageFilter : ChatHistoryProvider { - private readonly ChatMessageStore _innerChatMessageStore; + private readonly ChatHistoryProvider _innerProvider; private readonly Func, IEnumerable>? _invokingMessagesFilter; private readonly Func? _invokedMessagesFilter; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Use this constructor to customize how messages are filtered before and after invocation by - /// providing appropriate filter functions. If no filters are provided, the message store operates without + /// providing appropriate filter functions. If no filters are provided, the operates without /// additional filtering. - /// The underlying chat message store to be wrapped. Cannot be null. - /// An optional filter function to apply to messages before they are invoked. If null, no filter is applied at this - /// stage. - /// An optional filter function to apply to the invocation context after messages have been invoked. If null, no + /// The underlying to be wrapped. Cannot be null. + /// An optional filter function to apply to messages provided by the + /// before they are used by the agent. If null, no filter is applied at this stage. + /// An optional filter function to apply to the invocation context after messages have been produced. If null, no /// filter is applied at this stage. - /// Thrown if innerChatMessageStore is null. - public ChatMessageStoreMessageFilter( - ChatMessageStore innerChatMessageStore, + /// Thrown if is null. + public ChatHistoryProviderMessageFilter( + ChatHistoryProvider innerProvider, Func, IEnumerable>? invokingMessagesFilter = null, Func? invokedMessagesFilter = null) { - this._innerChatMessageStore = Throw.IfNull(innerChatMessageStore); + this._innerProvider = Throw.IfNull(innerProvider); if (invokingMessagesFilter == null && invokedMessagesFilter == null) { @@ -51,7 +51,7 @@ public sealed class ChatMessageStoreMessageFilter : ChatMessageStore /// public override async ValueTask> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default) { - var messages = await this._innerChatMessageStore.InvokingAsync(context, cancellationToken).ConfigureAwait(false); + var messages = await this._innerProvider.InvokingAsync(context, cancellationToken).ConfigureAwait(false); return this._invokingMessagesFilter != null ? this._invokingMessagesFilter(messages) : messages; } @@ -63,12 +63,12 @@ public sealed class ChatMessageStoreMessageFilter : ChatMessageStore context = this._invokedMessagesFilter(context); } - return this._innerChatMessageStore.InvokedAsync(context, cancellationToken); + return this._innerProvider.InvokedAsync(context, cancellationToken); } /// public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { - return this._innerChatMessageStore.Serialize(jsonSerializerOptions); + return this._innerProvider.Serialize(jsonSerializerOptions); } } diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreExtensions.cs deleted file mode 100644 index a205fc1d9e..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI; - -/// -/// Contains extension methods for the class. -/// -public static class ChatMessageStoreExtensions -{ - /// - /// Adds message filtering to an existing store, so that messages passed to the store and messages produced by the store - /// can be filtered, updated or replaced. - /// - /// The store to add the message filter to. - /// An optional filter function to apply to messages produced by the store. If null, no filter is applied at this - /// stage. - /// An optional filter function to apply to the invoked context messages before they are passed to the store. If null, no - /// filter is applied at this stage. - /// The with filtering applied. - public static ChatMessageStore WithMessageFilters( - this ChatMessageStore store, - Func, IEnumerable>? invokingMessagesFilter = null, - Func? invokedMessagesFilter = null) - { - return new ChatMessageStoreMessageFilter( - innerChatMessageStore: store, - invokingMessagesFilter: invokingMessagesFilter, - invokedMessagesFilter: invokedMessagesFilter); - } - - /// - /// Decorates the provided chat message store so that it does not store messages produced by any . - /// - /// The store to add the message filter to. - /// A new instance that filters out messages so they do not get stored. - public static ChatMessageStore WithAIContextProviderMessageRemoval(this ChatMessageStore store) - { - return new ChatMessageStoreMessageFilter( - innerChatMessageStore: store, - invokedMessagesFilter: (ctx) => - { - ctx.AIContextProviderMessages = null; - return ctx; - }); - } -} diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs index 13fcc134f0..cc909b936c 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs @@ -9,11 +9,11 @@ using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI; /// -/// Provides an abstract base class for agent threads that maintain all conversation state in local memory. +/// Provides an abstract base class for an that maintain all chat history in local memory. /// /// /// -/// is designed for scenarios where conversation state should be stored locally +/// is designed for scenarios where chat history should be stored locally /// rather than in external services or databases. This approach provides high performance and simplicity while /// maintaining full control over the conversation data. /// @@ -28,17 +28,17 @@ public abstract class InMemoryAgentThread : AgentThread /// /// Initializes a new instance of the class. /// - /// - /// An optional instance to use for storing chat messages. - /// If , a new empty message store will be created. + /// + /// An optional instance to use for storing chat messages. + /// If , a new empty will be created. /// /// - /// This constructor allows sharing of message stores between threads or providing pre-configured - /// message stores with specific reduction or processing logic. + /// This constructor allows sharing of between threads or providing pre-configured + /// with specific reduction or processing logic. /// - protected InMemoryAgentThread(InMemoryChatMessageStore? messageStore = null) + protected InMemoryAgentThread(InMemoryChatHistoryProvider? chatHistoryProvider = null) { - this.MessageStore = messageStore ?? []; + this.ChatHistoryProvider = chatHistoryProvider ?? []; } /// @@ -52,7 +52,7 @@ public abstract class InMemoryAgentThread : AgentThread /// protected InMemoryAgentThread(IEnumerable messages) { - this.MessageStore = [.. messages]; + this.ChatHistoryProvider = [.. messages]; } /// @@ -60,9 +60,9 @@ public abstract class InMemoryAgentThread : AgentThread /// /// A representing the serialized state of the thread. /// Optional settings for customizing the JSON deserialization process. - /// - /// Optional factory function to create the from its serialized state. - /// If not provided, a default factory will be used that creates a basic in-memory store. + /// + /// Optional factory function to create the from its serialized state. + /// If not provided, a default factory will be used that creates a basic . /// /// The is not a JSON object. /// The is invalid or cannot be deserialized to the expected type. @@ -73,7 +73,7 @@ public abstract class InMemoryAgentThread : AgentThread protected InMemoryAgentThread( JsonElement serializedThreadState, JsonSerializerOptions? jsonSerializerOptions = null, - Func? messageStoreFactory = null) + Func? chatHistoryProviderFactory = null) { if (serializedThreadState.ValueKind != JsonValueKind.Object) { @@ -83,15 +83,15 @@ public abstract class InMemoryAgentThread : AgentThread var state = serializedThreadState.Deserialize( AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(InMemoryAgentThreadState))) as InMemoryAgentThreadState; - this.MessageStore = - messageStoreFactory?.Invoke(state?.StoreState ?? default, jsonSerializerOptions) ?? - new InMemoryChatMessageStore(state?.StoreState ?? default, jsonSerializerOptions); + this.ChatHistoryProvider = + chatHistoryProviderFactory?.Invoke(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions) ?? + new InMemoryChatHistoryProvider(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions); } /// - /// Gets or sets the used by this thread. + /// Gets or sets the used by this thread. /// - public InMemoryChatMessageStore MessageStore { get; } + public InMemoryChatHistoryProvider ChatHistoryProvider { get; } /// /// Serializes the current object's state to a using the specified serialization options. @@ -100,11 +100,11 @@ public abstract class InMemoryAgentThread : AgentThread /// A representation of the object's state. public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { - var storeState = this.MessageStore.Serialize(jsonSerializerOptions); + var chatHistoryProviderState = this.ChatHistoryProvider.Serialize(jsonSerializerOptions); var state = new InMemoryAgentThreadState { - StoreState = storeState, + ChatHistoryProviderState = chatHistoryProviderState, }; return JsonSerializer.SerializeToElement(state, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(InMemoryAgentThreadState))); @@ -112,13 +112,13 @@ public abstract class InMemoryAgentThread : AgentThread /// public override object? GetService(Type serviceType, object? serviceKey = null) => - base.GetService(serviceType, serviceKey) ?? this.MessageStore?.GetService(serviceType, serviceKey); + base.GetService(serviceType, serviceKey) ?? this.ChatHistoryProvider?.GetService(serviceType, serviceKey); [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private string DebuggerDisplay => $"Count = {this.MessageStore.Count}"; + private string DebuggerDisplay => $"Count = {this.ChatHistoryProvider.Count}"; internal sealed class InMemoryAgentThreadState { - public JsonElement? StoreState { get; set; } + public JsonElement? ChatHistoryProviderState { get; set; } } } diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatMessageStore.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs similarity index 74% rename from dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatMessageStore.cs rename to dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs index 1fb1b568ae..ab408c6a5e 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatMessageStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs @@ -14,55 +14,54 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// -/// Provides an in-memory implementation of with support for message reduction and collection semantics. +/// Provides an in-memory implementation of with support for message reduction and collection semantics. /// /// /// -/// stores chat messages entirely in local memory, providing fast access and manipulation -/// capabilities. It implements both for agent integration and +/// stores chat messages entirely in local memory, providing fast access and manipulation +/// capabilities. It implements both for agent integration and /// for direct collection manipulation. /// /// -/// This store maintains all messages in memory. For long-running conversations or high-volume scenarios, consider using +/// This maintains all messages in memory. For long-running conversations or high-volume scenarios, consider using /// message reduction strategies or alternative storage implementations. /// /// [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(DebugView))] -public sealed class InMemoryChatMessageStore : ChatMessageStore, IList, IReadOnlyList +public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider, IList, IReadOnlyList { private List _messages; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// This constructor creates a basic in-memory store without message reduction capabilities. + /// This constructor creates a basic in-memory without message reduction capabilities. /// Messages will be stored exactly as added without any automatic processing or reduction. /// - public InMemoryChatMessageStore() + public InMemoryChatHistoryProvider() { this._messages = []; } /// - /// Initializes a new instance of the class from previously serialized state. + /// Initializes a new instance of the class from previously serialized state. /// - /// A representing the serialized state of the message store. + /// A representing the serialized state of the provider. /// Optional settings for customizing the JSON deserialization process. - /// The is not a valid JSON object or cannot be deserialized. + /// The is not a valid JSON object or cannot be deserialized. /// - /// This constructor enables restoration of message stores from previously saved state, allowing + /// This constructor enables restoration of messages from previously saved state, allowing /// conversation history to be preserved across application restarts or migrated between instances. - /// The store will be configured with default settings and message reduction before retrieval. /// - public InMemoryChatMessageStore(JsonElement serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null) - : this(null, serializedStoreState, jsonSerializerOptions, ChatReducerTriggerEvent.BeforeMessagesRetrieval) + public InMemoryChatHistoryProvider(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null) + : this(null, serializedState, jsonSerializerOptions, ChatReducerTriggerEvent.BeforeMessagesRetrieval) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A instance used to process, reduce, or optimize chat messages. @@ -77,29 +76,29 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList - public InMemoryChatMessageStore(IChatReducer chatReducer, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval) + public InMemoryChatHistoryProvider(IChatReducer chatReducer, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval) : this(chatReducer, default, null, reducerTriggerEvent) { Throw.IfNull(chatReducer); } /// - /// Initializes a new instance of the class, with an existing state from a serialized JSON element. + /// Initializes a new instance of the class, with an existing state from a serialized JSON element. /// /// An optional instance used to process or reduce chat messages. If null, no reduction logic will be applied. - /// A representing the serialized state of the store. + /// A representing the serialized state of the provider. /// Optional settings for customizing the JSON deserialization process. /// The event that should trigger the reducer invocation. - public InMemoryChatMessageStore(IChatReducer? chatReducer, JsonElement serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval) + public InMemoryChatHistoryProvider(IChatReducer? chatReducer, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval) { this.ChatReducer = chatReducer; this.ReducerTriggerEvent = reducerTriggerEvent; - if (serializedStoreState.ValueKind is JsonValueKind.Object) + if (serializedState.ValueKind is JsonValueKind.Object) { var jso = jsonSerializerOptions ?? AgentAbstractionsJsonUtilities.DefaultOptions; - var state = serializedStoreState.Deserialize( - jso.GetTypeInfo(typeof(StoreState))) as StoreState; + var state = serializedState.Deserialize( + jso.GetTypeInfo(typeof(State))) as State; if (state?.Messages is { } messages) { this._messages = messages; @@ -116,7 +115,7 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList - /// Gets the event that triggers the reducer invocation in this store. + /// Gets the event that triggers the reducer invocation in this provider. /// public ChatReducerTriggerEvent ReducerTriggerEvent { get; } @@ -156,7 +155,7 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { - StoreState state = new() + State state = new() { Messages = this._messages, }; var jso = jsonSerializerOptions ?? AgentAbstractionsJsonUtilities.DefaultOptions; - return JsonSerializer.SerializeToElement(state, jso.GetTypeInfo(typeof(StoreState))); + return JsonSerializer.SerializeToElement(state, jso.GetTypeInfo(typeof(State))); } /// @@ -218,13 +217,13 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList this.GetEnumerator(); - internal sealed class StoreState + internal sealed class State { public List Messages { get; set; } = []; } /// - /// Defines the events that can trigger a reducer in the . + /// Defines the events that can trigger a reducer in the . /// public enum ChatReducerTriggerEvent { @@ -235,15 +234,15 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList - /// Trigger the reducer before messages are retrieved from the store. + /// Trigger the reducer before messages are retrieved from the provider. /// The reducer will process the messages before they are returned to the caller. /// BeforeMessagesRetrieval } - private sealed class DebugView(InMemoryChatMessageStore store) + private sealed class DebugView(InMemoryChatHistoryProvider provider) { [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public ChatMessage[] Items => store._messages.ToArray(); + public ChatMessage[] Items => provider._messages.ToArray(); } } diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs index 718f4fc700..2058e6760b 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs @@ -192,7 +192,7 @@ public static class PersistentAgentsClientExtensions Description = options.Description ?? persistentAgentMetadata.Description, ChatOptions = options.ChatOptions, AIContextProviderFactory = options.AIContextProviderFactory, - ChatMessageStoreFactory = options.ChatMessageStoreFactory, + ChatHistoryProviderFactory = options.ChatHistoryProviderFactory, UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs }; diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs index 9db5fad7d3..37ee7fa82c 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs @@ -583,7 +583,7 @@ public static partial class AzureAIProjectChatClientExtensions if (options is not null) { agentOptions.AIContextProviderFactory = options.AIContextProviderFactory; - agentOptions.ChatMessageStoreFactory = options.ChatMessageStoreFactory; + agentOptions.ChatHistoryProviderFactory = options.ChatHistoryProviderFactory; agentOptions.UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs; } diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatMessageStore.cs b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatHistoryProvider.cs similarity index 86% rename from dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatMessageStore.cs rename to dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatHistoryProvider.cs index 5c2c23ff9e..41c9a211dc 100644 --- a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatMessageStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatHistoryProvider.cs @@ -15,11 +15,11 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// -/// Provides a Cosmos DB implementation of the abstract class. +/// Provides a Cosmos DB implementation of the abstract class. /// -[RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")] -[RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")] -public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable +[RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")] +[RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")] +public sealed class CosmosChatHistoryProvider : ChatHistoryProvider, IDisposable { private readonly CosmosClient _cosmosClient; private readonly Container _container; @@ -60,7 +60,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable public int MaxBatchSize { get; set; } = 100; /// - /// Gets or sets the maximum number of messages to retrieve from the store. + /// Gets or sets the maximum number of messages to retrieve from the provider. /// This helps prevent exceeding LLM context windows in long conversations. /// Default is null (no limit). When set, only the most recent messages are returned. /// @@ -73,17 +73,17 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable public int? MessageTtlSeconds { get; set; } = 86400; /// - /// Gets the conversation ID associated with this message store. + /// Gets the conversation ID associated with this provider. /// public string ConversationId { get; init; } /// - /// Gets the database ID associated with this message store. + /// Gets the database ID associated with this provider. /// public string DatabaseId { get; init; } /// - /// Gets the container ID associated with this message store. + /// Gets the container ID associated with this provider. /// public string ContainerId { get; init; } @@ -97,7 +97,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// Whether this instance owns the CosmosClient and should dispose it. /// Optional tenant identifier for hierarchical partitioning. /// Optional user identifier for hierarchical partitioning. - internal CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId, bool ownsClient, string? tenantId = null, string? userId = null) + internal CosmosChatHistoryProvider(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId, bool ownsClient, string? tenantId = null, string? userId = null) { this._cosmosClient = Throw.IfNull(cosmosClient); this._container = this._cosmosClient.GetContainer(Throw.IfNullOrWhitespace(databaseId), Throw.IfNullOrWhitespace(containerId)); @@ -121,20 +121,20 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable } /// - /// Initializes a new instance of the class using a connection string. + /// Initializes a new instance of the class using a connection string. /// /// The Cosmos DB connection string. /// The identifier of the Cosmos DB database. /// The identifier of the Cosmos DB container. /// Thrown when any required parameter is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(string connectionString, string databaseId, string containerId) + public CosmosChatHistoryProvider(string connectionString, string databaseId, string containerId) : this(connectionString, databaseId, containerId, Guid.NewGuid().ToString("N")) { } /// - /// Initializes a new instance of the class using a connection string. + /// Initializes a new instance of the class using a connection string. /// /// The Cosmos DB connection string. /// The identifier of the Cosmos DB database. @@ -142,13 +142,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// The unique identifier for this conversation thread. /// Thrown when any required parameter is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(string connectionString, string databaseId, string containerId, string conversationId) + public CosmosChatHistoryProvider(string connectionString, string databaseId, string containerId, string conversationId) : this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString)), databaseId, containerId, conversationId, ownsClient: true) { } /// - /// Initializes a new instance of the class using TokenCredential for authentication. + /// Initializes a new instance of the class using TokenCredential for authentication. /// /// The Cosmos DB account endpoint URI. /// The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential). @@ -156,13 +156,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// The identifier of the Cosmos DB container. /// Thrown when any required parameter is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId) + public CosmosChatHistoryProvider(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId) : this(accountEndpoint, tokenCredential, databaseId, containerId, Guid.NewGuid().ToString("N")) { } /// - /// Initializes a new instance of the class using a TokenCredential for authentication. + /// Initializes a new instance of the class using a TokenCredential for authentication. /// /// The Cosmos DB account endpoint URI. /// The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential). @@ -171,26 +171,26 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// The unique identifier for this conversation thread. /// Thrown when any required parameter is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId, string conversationId) + public CosmosChatHistoryProvider(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId, string conversationId) : this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential)), databaseId, containerId, conversationId, ownsClient: true) { } /// - /// Initializes a new instance of the class using an existing . + /// Initializes a new instance of the class using an existing . /// /// The instance to use for Cosmos DB operations. /// The identifier of the Cosmos DB database. /// The identifier of the Cosmos DB container. /// Thrown when is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId) + public CosmosChatHistoryProvider(CosmosClient cosmosClient, string databaseId, string containerId) : this(cosmosClient, databaseId, containerId, Guid.NewGuid().ToString("N")) { } /// - /// Initializes a new instance of the class using an existing . + /// Initializes a new instance of the class using an existing . /// /// The instance to use for Cosmos DB operations. /// The identifier of the Cosmos DB database. @@ -198,13 +198,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// The unique identifier for this conversation thread. /// Thrown when is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId) + public CosmosChatHistoryProvider(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId) : this(cosmosClient, databaseId, containerId, conversationId, ownsClient: false) { } /// - /// Initializes a new instance of the class using a connection string with hierarchical partition keys. + /// Initializes a new instance of the class using a connection string with hierarchical partition keys. /// /// The Cosmos DB connection string. /// The identifier of the Cosmos DB database. @@ -214,13 +214,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// The session identifier for hierarchical partitioning. /// Thrown when any required parameter is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(string connectionString, string databaseId, string containerId, string tenantId, string userId, string sessionId) + public CosmosChatHistoryProvider(string connectionString, string databaseId, string containerId, string tenantId, string userId, string sessionId) : this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString)), databaseId, containerId, Throw.IfNullOrWhitespace(sessionId), ownsClient: true, Throw.IfNullOrWhitespace(tenantId), Throw.IfNullOrWhitespace(userId)) { } /// - /// Initializes a new instance of the class using a TokenCredential for authentication with hierarchical partition keys. + /// Initializes a new instance of the class using a TokenCredential for authentication with hierarchical partition keys. /// /// The Cosmos DB account endpoint URI. /// The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential). @@ -231,13 +231,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// The session identifier for hierarchical partitioning. /// Thrown when any required parameter is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId, string tenantId, string userId, string sessionId) + public CosmosChatHistoryProvider(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId, string tenantId, string userId, string sessionId) : this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential)), databaseId, containerId, Throw.IfNullOrWhitespace(sessionId), ownsClient: true, Throw.IfNullOrWhitespace(tenantId), Throw.IfNullOrWhitespace(userId)) { } /// - /// Initializes a new instance of the class using an existing with hierarchical partition keys. + /// Initializes a new instance of the class using an existing with hierarchical partition keys. /// /// The instance to use for Cosmos DB operations. /// The identifier of the Cosmos DB database. @@ -247,43 +247,43 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable /// The session identifier for hierarchical partitioning. /// Thrown when is null. /// Thrown when any string parameter is null or whitespace. - public CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId, string tenantId, string userId, string sessionId) + public CosmosChatHistoryProvider(CosmosClient cosmosClient, string databaseId, string containerId, string tenantId, string userId, string sessionId) : this(cosmosClient, databaseId, containerId, Throw.IfNullOrWhitespace(sessionId), ownsClient: false, Throw.IfNullOrWhitespace(tenantId), Throw.IfNullOrWhitespace(userId)) { } /// - /// Creates a new instance of the class from previously serialized state. + /// Creates a new instance of the class from previously serialized state. /// /// The instance to use for Cosmos DB operations. - /// A representing the serialized state of the message store. + /// A representing the serialized state of the provider. /// The identifier of the Cosmos DB database. /// The identifier of the Cosmos DB container. /// Optional settings for customizing the JSON deserialization process. - /// A new instance of initialized from the serialized state. + /// A new instance of initialized from the serialized state. /// Thrown when is null. /// Thrown when the serialized state cannot be deserialized. - public static CosmosChatMessageStore CreateFromSerializedState(CosmosClient cosmosClient, JsonElement serializedStoreState, string databaseId, string containerId, JsonSerializerOptions? jsonSerializerOptions = null) + public static CosmosChatHistoryProvider CreateFromSerializedState(CosmosClient cosmosClient, JsonElement serializedState, string databaseId, string containerId, JsonSerializerOptions? jsonSerializerOptions = null) { Throw.IfNull(cosmosClient); Throw.IfNullOrWhitespace(databaseId); Throw.IfNullOrWhitespace(containerId); - if (serializedStoreState.ValueKind is not JsonValueKind.Object) + if (serializedState.ValueKind is not JsonValueKind.Object) { - throw new ArgumentException("Invalid serialized state", nameof(serializedStoreState)); + throw new ArgumentException("Invalid serialized state", nameof(serializedState)); } - var state = serializedStoreState.Deserialize(jsonSerializerOptions); + var state = serializedState.Deserialize(jsonSerializerOptions); if (state?.ConversationIdentifier is not { } conversationId) { - throw new ArgumentException("Invalid serialized state", nameof(serializedStoreState)); + throw new ArgumentException("Invalid serialized state", nameof(serializedState)); } // Use the internal constructor with all parameters to ensure partition key logic is centralized return state.UseHierarchicalPartitioning && state.TenantId != null && state.UserId != null - ? new CosmosChatMessageStore(cosmosClient, databaseId, containerId, conversationId, ownsClient: false, state.TenantId, state.UserId) - : new CosmosChatMessageStore(cosmosClient, databaseId, containerId, conversationId, ownsClient: false); + ? new CosmosChatHistoryProvider(cosmosClient, databaseId, containerId, conversationId, ownsClient: false, state.TenantId, state.UserId) + : new CosmosChatHistoryProvider(cosmosClient, databaseId, containerId, conversationId, ownsClient: false); } /// @@ -524,7 +524,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable } #pragma warning restore CA1513 - var state = new StoreState + var state = new State { ConversationIdentifier = this.ConversationId, TenantId = this._tenantId, @@ -632,7 +632,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable } } - private sealed class StoreState + private sealed class State { public string ConversationIdentifier { get; set; } = string.Empty; public string? TenantId { get; set; } diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs index 061b64593c..3d93e9dd6a 100644 --- a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs @@ -23,9 +23,9 @@ public static class CosmosDBChatExtensions /// The configured . /// Thrown when is null. /// Thrown when any string parameter is null or whitespace. - [RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")] - [RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")] - public static ChatClientAgentOptions WithCosmosDBMessageStore( + [RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")] + [RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")] + public static ChatClientAgentOptions WithCosmosDBChatHistoryProvider( this ChatClientAgentOptions options, string connectionString, string databaseId, @@ -36,7 +36,7 @@ public static class CosmosDBChatExtensions throw new ArgumentNullException(nameof(options)); } - options.ChatMessageStoreFactory = (context, ct) => new ValueTask(new CosmosChatMessageStore(connectionString, databaseId, containerId)); + options.ChatHistoryProviderFactory = (context, ct) => new ValueTask(new CosmosChatHistoryProvider(connectionString, databaseId, containerId)); return options; } @@ -51,9 +51,9 @@ public static class CosmosDBChatExtensions /// The configured . /// Thrown when or is null. /// Thrown when any string parameter is null or whitespace. - [RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")] - [RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")] - public static ChatClientAgentOptions WithCosmosDBMessageStoreUsingManagedIdentity( + [RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")] + [RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")] + public static ChatClientAgentOptions WithCosmosDBChatHistoryProviderUsingManagedIdentity( this ChatClientAgentOptions options, string accountEndpoint, string databaseId, @@ -70,7 +70,7 @@ public static class CosmosDBChatExtensions throw new ArgumentNullException(nameof(tokenCredential)); } - options.ChatMessageStoreFactory = (context, ct) => new ValueTask(new CosmosChatMessageStore(accountEndpoint, tokenCredential, databaseId, containerId)); + options.ChatHistoryProviderFactory = (context, ct) => new ValueTask(new CosmosChatHistoryProvider(accountEndpoint, tokenCredential, databaseId, containerId)); return options; } @@ -84,9 +84,9 @@ public static class CosmosDBChatExtensions /// The configured . /// Thrown when any required parameter is null. /// Thrown when any string parameter is null or whitespace. - [RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")] - [RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")] - public static ChatClientAgentOptions WithCosmosDBMessageStore( + [RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")] + [RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")] + public static ChatClientAgentOptions WithCosmosDBChatHistoryProvider( this ChatClientAgentOptions options, CosmosClient cosmosClient, string databaseId, @@ -97,7 +97,7 @@ public static class CosmosDBChatExtensions throw new ArgumentNullException(nameof(options)); } - options.ChatMessageStoreFactory = (context, ct) => new ValueTask(new CosmosChatMessageStore(cosmosClient, databaseId, containerId)); + options.ChatHistoryProviderFactory = (context, ct) => new ValueTask(new CosmosChatHistoryProvider(cosmosClient, databaseId, containerId)); return options; } } diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj index 7e13ec5998..f6f80b3dea 100644 --- a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj +++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj @@ -21,7 +21,7 @@ Microsoft Agent Framework Cosmos DB NoSQL Integration - Provides Cosmos DB NoSQL implementations for Microsoft Agent Framework storage abstractions including ChatMessageStore and CheckpointStore. + Provides Cosmos DB NoSQL implementations for Microsoft Agent Framework storage abstractions including ChatHistoryProvider and CheckpointStore. diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs index a03f7e7455..d167d1f0b4 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs @@ -205,7 +205,7 @@ public static class OpenAIAssistantClientExtensions Description = options.Description ?? assistantMetadata.Description, ChatOptions = options.ChatOptions, AIContextProviderFactory = options.AIContextProviderFactory, - ChatMessageStoreFactory = options.ChatMessageStoreFactory, + ChatHistoryProviderFactory = options.ChatHistoryProviderFactory, UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs }; diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowMessageStore.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowChatHistoryProvider.cs similarity index 93% rename from dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowMessageStore.cs rename to dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowChatHistoryProvider.cs index 87cef04e76..afe6706553 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowMessageStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowChatHistoryProvider.cs @@ -10,16 +10,16 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Workflows; -internal sealed class WorkflowMessageStore : ChatMessageStore +internal sealed class WorkflowChatHistoryProvider : ChatHistoryProvider { private int _bookmark; private readonly List _chatMessages = []; - public WorkflowMessageStore() + public WorkflowChatHistoryProvider() { } - public WorkflowMessageStore(StoreState state) + public WorkflowChatHistoryProvider(StoreState state) { this.ImportStoreState(Throw.IfNull(state)); } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs index 290fe6cac4..fe597c17f3 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs @@ -82,7 +82,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); + workflowThread.ChatHistoryProvider.AddMessages(messages); return workflowThread; } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowThread.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowThread.cs index 96d3f3b08a..270ec02149 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowThread.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowThread.cs @@ -37,7 +37,7 @@ internal sealed class WorkflowThread : AgentThread this._checkpointManager = checkpointManager ?? new(this._inMemoryCheckpointManager = new()); this.RunId = Throw.IfNullOrEmpty(runId); - this.MessageStore = new WorkflowMessageStore(); + this.ChatHistoryProvider = new WorkflowChatHistoryProvider(); } public WorkflowThread(Workflow workflow, JsonElement serializedThread, IWorkflowExecutionEnvironment executionEnvironment, CheckpointManager? checkpointManager = null, bool includeExceptionDetails = false, bool includeWorkflowOutputsInResponse = false, JsonSerializerOptions? jsonSerializerOptions = null) @@ -70,7 +70,7 @@ internal sealed class WorkflowThread : AgentThread this.RunId = threadState.RunId; this.LastCheckpoint = threadState.LastCheckpoint; - this.MessageStore = new WorkflowMessageStore(threadState.MessageStoreState); + this.ChatHistoryProvider = new WorkflowChatHistoryProvider(threadState.ChatHistoryProviderState); } public CheckpointInfo? LastCheckpoint { get; set; } @@ -81,7 +81,7 @@ internal sealed class WorkflowThread : AgentThread ThreadState info = new( this.RunId, this.LastCheckpoint, - this.MessageStore.ExportStoreState(), + this.ChatHistoryProvider.ExportStoreState(), this._inMemoryCheckpointManager); return marshaller.Marshal(info); @@ -100,7 +100,7 @@ internal sealed class WorkflowThread : AgentThread RawRepresentation = raw }; - this.MessageStore.AddMessages(update.ToChatMessage()); + this.ChatHistoryProvider.AddMessages(update.ToChatMessage()); return update; } @@ -117,7 +117,7 @@ internal sealed class WorkflowThread : AgentThread RawRepresentation = raw }; - this.MessageStore.AddMessages(update.ToChatMessage()); + this.ChatHistoryProvider.AddMessages(update.ToChatMessage()); return update; } @@ -156,7 +156,7 @@ internal sealed class WorkflowThread : AgentThread try { this.LastResponseId = Guid.NewGuid().ToString("N"); - List messages = this.MessageStore.GetFromBookmark().ToList(); + List messages = this.ChatHistoryProvider.GetFromBookmark().ToList(); #pragma warning disable CA2007 // Analyzer misfiring and not seeing .ConfigureAwait(false) below. await using Checkpointed checkpointed = @@ -240,7 +240,7 @@ internal sealed class WorkflowThread : AgentThread finally { // Do we want to try to undo the step, and not update the bookmark? - this.MessageStore.UpdateBookmark(); + this.ChatHistoryProvider.UpdateBookmark(); } } @@ -249,17 +249,17 @@ internal sealed class WorkflowThread : AgentThread public string RunId { get; } /// - public WorkflowMessageStore MessageStore { get; } + public WorkflowChatHistoryProvider ChatHistoryProvider { get; } internal sealed class ThreadState( string runId, CheckpointInfo? lastCheckpoint, - WorkflowMessageStore.StoreState messageStoreState, + WorkflowChatHistoryProvider.StoreState chatHistoryProviderState, InMemoryCheckpointManager? checkpointManager = null) { public string RunId { get; } = runId; public CheckpointInfo? LastCheckpoint { get; } = lastCheckpoint; - public WorkflowMessageStore.StoreState MessageStoreState { get; } = messageStoreState; + public WorkflowChatHistoryProvider.StoreState ChatHistoryProviderState { get; } = chatHistoryProviderState; public InMemoryCheckpointManager? CheckpointManager { get; } = checkpointManager; } } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowsJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowsJsonUtilities.cs index d8241f4681..f27b2d9299 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowsJsonUtilities.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowsJsonUtilities.cs @@ -83,7 +83,7 @@ internal static partial class WorkflowsJsonUtilities [JsonSerializable(typeof(EdgeConnection))] // Workflow-as-Agent - [JsonSerializable(typeof(WorkflowMessageStore.StoreState))] + [JsonSerializable(typeof(WorkflowChatHistoryProvider.StoreState))] [JsonSerializable(typeof(WorkflowThread.ThreadState))] // Message Types diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index d39a5c8893..3b416c8979 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -78,7 +78,7 @@ public sealed partial class ChatClientAgent : AIAgent /// The chat client to use when running the agent. /// /// Configuration options that control all aspects of the agent's behavior, including chat settings, - /// message store factories, context provider factories, and other advanced configurations. + /// chat history provider factories, context provider factories, and other advanced configurations. /// /// /// Optional logger factory for creating loggers used by the agent and its components. @@ -208,7 +208,7 @@ public sealed partial class ChatClientAgent : AIAgent ChatOptions? chatOptions, List inputMessagesForChatClient, IList? aiContextProviderMessages, - IList? chatMessageStoreMessages, + IList? chatHistoryProviderMessages, ChatClientAgentContinuationToken? continuationToken) = await this.PrepareThreadAndMessagesAsync(thread, inputMessages, options, cancellationToken).ConfigureAwait(false); @@ -231,7 +231,7 @@ public sealed partial class ChatClientAgent : AIAgent } catch (Exception ex) { - await NotifyMessageStoreOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); + await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); await NotifyAIContextProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false); throw; } @@ -246,7 +246,7 @@ public sealed partial class ChatClientAgent : AIAgent } catch (Exception ex) { - await NotifyMessageStoreOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); + await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); await NotifyAIContextProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false); throw; } @@ -273,7 +273,7 @@ public sealed partial class ChatClientAgent : AIAgent } catch (Exception ex) { - await NotifyMessageStoreOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); + await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); await NotifyAIContextProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false); throw; } @@ -286,7 +286,7 @@ public sealed partial class ChatClientAgent : AIAgent 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, chatOptions, cancellationToken).ConfigureAwait(false); + await NotifyChatHistoryProviderOfNewMessagesAsync(safeThread, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false); // Notify the AIContextProvider of all new messages. await NotifyAIContextProviderOfSuccessAsync(safeThread, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false); @@ -304,8 +304,8 @@ public sealed partial class ChatClientAgent : AIAgent /// public override async ValueTask 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) + ChatHistoryProvider? chatHistoryProvider = this._agentOptions?.ChatHistoryProviderFactory is not null + ? await this._agentOptions.ChatHistoryProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) : null; AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null @@ -314,7 +314,7 @@ public sealed partial class ChatClientAgent : AIAgent return new ChatClientAgentThread { - MessageStore = messageStore, + ChatHistoryProvider = chatHistoryProvider, AIContextProvider = contextProvider }; } @@ -329,8 +329,8 @@ public sealed partial class ChatClientAgent : AIAgent /// /// /// - /// This method creates threads that rely on server-side conversation storage, where the chat history - /// is maintained by the underlying AI service rather than in local message stores. + /// This method creates an that relies on server-side chat history storage, where the chat history + /// is maintained by the underlying AI service rather than by a local . /// /// /// Agent threads created with this method will only work with @@ -351,28 +351,28 @@ public sealed partial class ChatClientAgent : AIAgent } /// - /// Creates a new agent thread instance using an existing to continue a conversation. + /// Creates a new agent thread instance using an existing to continue a conversation. /// - /// The instance to use for managing the conversation's message history. + /// The instance to use for managing the conversation's message history. /// The to monitor for cancellation requests. /// - /// A value task representing the asynchronous operation. The task result contains a new instance configured to work with the provided . + /// A value task representing the asynchronous operation. The task result contains a new instance configured to work with the provided . /// /// /// /// This method creates threads that do not support server-side conversation storage. /// Some AI services require server-side conversation storage to function properly, and creating a thread - /// with a may not be compatible with these services. + /// with a may not be compatible with these services. /// /// /// Where a service requires server-side conversation storage, use . /// /// /// 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 . + /// the thread will throw an exception to indicate that it cannot continue using the provided . /// /// - public async ValueTask GetNewThreadAsync(ChatMessageStore chatMessageStore, CancellationToken cancellationToken = default) + public async ValueTask GetNewThreadAsync(ChatHistoryProvider chatHistoryProvider, CancellationToken cancellationToken = default) { AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null ? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) @@ -380,7 +380,7 @@ public sealed partial class ChatClientAgent : AIAgent return new ChatClientAgentThread() { - MessageStore = Throw.IfNull(chatMessageStore), + ChatHistoryProvider = Throw.IfNull(chatHistoryProvider), AIContextProvider = contextProvider }; } @@ -388,9 +388,9 @@ public sealed partial class ChatClientAgent : AIAgent /// public override async ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - Func>? chatMessageStoreFactory = this._agentOptions?.ChatMessageStoreFactory is null ? + Func>? chatHistoryProviderFactory = this._agentOptions?.ChatHistoryProviderFactory is null ? null : - (jse, jso, ct) => this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }, ct); + (jse, jso, ct) => this._agentOptions.ChatHistoryProviderFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }, ct); Func>? aiContextProviderFactory = this._agentOptions?.AIContextProviderFactory is null ? null : @@ -399,7 +399,7 @@ public sealed partial class ChatClientAgent : AIAgent return await ChatClientAgentThread.DeserializeAsync( serializedThread, jsonSerializerOptions, - chatMessageStoreFactory, + chatHistoryProviderFactory, aiContextProviderFactory, cancellationToken).ConfigureAwait(false); } @@ -422,7 +422,7 @@ public sealed partial class ChatClientAgent : AIAgent ChatOptions? chatOptions, List inputMessagesForChatClient, IList? aiContextProviderMessages, - IList? chatMessageStoreMessages, + IList? chatHistoryProviderMessages, ChatClientAgentContinuationToken? _) = await this.PrepareThreadAndMessagesAsync(thread, inputMessages, options, cancellationToken).ConfigureAwait(false); @@ -442,7 +442,7 @@ public sealed partial class ChatClientAgent : AIAgent } catch (Exception ex) { - await NotifyMessageStoreOfFailureAsync(safeThread, ex, inputMessages, chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); + await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, inputMessages, chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false); await NotifyAIContextProviderOfFailureAsync(safeThread, ex, inputMessages, aiContextProviderMessages, cancellationToken).ConfigureAwait(false); throw; } @@ -460,7 +460,7 @@ public sealed partial class ChatClientAgent : AIAgent } // Only notify the thread of new messages if the chatResponse was successful to avoid inconsistent message state in the thread. - await NotifyMessageStoreOfNewMessagesAsync(safeThread, inputMessages, chatMessageStoreMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false); + await NotifyChatHistoryProviderOfNewMessagesAsync(safeThread, inputMessages, chatHistoryProviderMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false); // Notify the AIContextProvider of all new messages. await NotifyAIContextProviderOfSuccessAsync(safeThread, inputMessages, aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false); @@ -672,7 +672,7 @@ public sealed partial class ChatClientAgent : AIAgent ChatOptions? ChatOptions, List InputMessagesForChatClient, IList? AIContextProviderMessages, - IList? ChatMessageStoreMessages, + IList? ChatHistoryProviderMessages, ChatClientAgentContinuationToken? ContinuationToken )> PrepareThreadAndMessagesAsync( AgentThread? thread, @@ -703,20 +703,20 @@ public sealed partial class ChatClientAgent : AIAgent List inputMessagesForChatClient = []; IList? aiContextProviderMessages = null; - IList? chatMessageStoreMessages = null; + IList? chatHistoryProviderMessages = null; // Populate the thread messages only if we are not continuing an existing response as it's not allowed if (chatOptions?.ContinuationToken is null) { - ChatMessageStore? chatMessageStore = ResolveChatMessageStore(typedThread, chatOptions); + ChatHistoryProvider? chatHistoryProvider = ResolveChatHistoryProvider(typedThread, chatOptions); - // Add any existing messages from the chatMessageStore to the messages to be sent to the chat client. - if (chatMessageStore is not null) + // Add any existing messages from the thread to the messages to be sent to the chat client. + if (chatHistoryProvider is not null) { - var invokingContext = new ChatMessageStore.InvokingContext(inputMessages); - var storeMessages = await chatMessageStore.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false); - inputMessagesForChatClient.AddRange(storeMessages); - chatMessageStoreMessages = storeMessages as IList ?? storeMessages.ToList(); + var invokingContext = new ChatHistoryProvider.InvokingContext(inputMessages); + var providerMessages = await chatHistoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false); + inputMessagesForChatClient.AddRange(providerMessages); + chatHistoryProviderMessages = providerMessages as IList ?? providerMessages.ToList(); } // Add the input messages before getting context from AIContextProvider. @@ -770,7 +770,7 @@ public sealed partial class ChatClientAgent : AIAgent chatOptions.ConversationId = typedThread.ConversationId; } - return (typedThread, chatOptions, inputMessagesForChatClient, aiContextProviderMessages, chatMessageStoreMessages, continuationToken); + return (typedThread, chatOptions, inputMessagesForChatClient, aiContextProviderMessages, chatHistoryProviderMessages, continuationToken); } private async Task UpdateThreadWithTypeAndConversationIdAsync(ChatClientAgentThread thread, string? responseConversationId, CancellationToken cancellationToken) @@ -791,78 +791,78 @@ public sealed partial class ChatClientAgent : AIAgent else { // 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 is not null - ? await this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) - : new InMemoryChatMessageStore(); + // the thread has no ChatHistoryProvider yet, we should update the thread with the custom ChatHistoryProvider or + // default InMemoryChatHistoryProvider so that it has somewhere to store the chat history. + thread.ChatHistoryProvider ??= this._agentOptions?.ChatHistoryProviderFactory is not null + ? await this._agentOptions.ChatHistoryProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) + : new InMemoryChatHistoryProvider(); } } - private static Task NotifyMessageStoreOfFailureAsync( + private static Task NotifyChatHistoryProviderOfFailureAsync( ChatClientAgentThread thread, Exception ex, IEnumerable requestMessages, - IEnumerable? chatMessageStoreMessages, + IEnumerable? chatHistoryProviderMessages, IEnumerable? aiContextProviderMessages, ChatOptions? chatOptions, CancellationToken cancellationToken) { - ChatMessageStore? chatMessageStore = ResolveChatMessageStore(thread, chatOptions); + ChatHistoryProvider? provider = ResolveChatHistoryProvider(thread, chatOptions); - // Only notify the message store if we have one. + // Only notify the provider if we have one. // If we don't have one, it means that the chat history is service managed and the underlying service is responsible for storing messages. - if (chatMessageStore is not null) + if (provider is not null) { - var invokedContext = new ChatMessageStore.InvokedContext(requestMessages, chatMessageStoreMessages) + var invokedContext = new ChatHistoryProvider.InvokedContext(requestMessages, chatHistoryProviderMessages!) { AIContextProviderMessages = aiContextProviderMessages, InvokeException = ex }; - return chatMessageStore.InvokedAsync(invokedContext, cancellationToken).AsTask(); + return provider.InvokedAsync(invokedContext, cancellationToken).AsTask(); } return Task.CompletedTask; } - private static Task NotifyMessageStoreOfNewMessagesAsync( + private static Task NotifyChatHistoryProviderOfNewMessagesAsync( ChatClientAgentThread thread, IEnumerable requestMessages, - IEnumerable? chatMessageStoreMessages, + IEnumerable? chatHistoryProviderMessages, IEnumerable? aiContextProviderMessages, IEnumerable responseMessages, ChatOptions? chatOptions, CancellationToken cancellationToken) { - ChatMessageStore? chatMessageStore = ResolveChatMessageStore(thread, chatOptions); + ChatHistoryProvider? provider = ResolveChatHistoryProvider(thread, chatOptions); - // Only notify the message store if we have one. + // Only notify the provider if we have one. // If we don't have one, it means that the chat history is service managed and the underlying service is responsible for storing messages. - if (chatMessageStore is not null) + if (provider is not null) { - var invokedContext = new ChatMessageStore.InvokedContext(requestMessages, chatMessageStoreMessages) + var invokedContext = new ChatHistoryProvider.InvokedContext(requestMessages, chatHistoryProviderMessages!) { AIContextProviderMessages = aiContextProviderMessages, ResponseMessages = responseMessages }; - return chatMessageStore.InvokedAsync(invokedContext, cancellationToken).AsTask(); + return provider.InvokedAsync(invokedContext, cancellationToken).AsTask(); } return Task.CompletedTask; } - private static ChatMessageStore? ResolveChatMessageStore(ChatClientAgentThread thread, ChatOptions? chatOptions) + private static ChatHistoryProvider? ResolveChatHistoryProvider(ChatClientAgentThread thread, ChatOptions? chatOptions) { - ChatMessageStore? chatMessageStore = thread.MessageStore; + ChatHistoryProvider? provider = thread.ChatHistoryProvider; - // If someone provided an override ChatMessageStore via AdditionalProperties, we should use that instead of the one on the thread. - if (chatOptions?.AdditionalProperties?.TryGetValue(out ChatMessageStore? overrideChatMessageStore) is true) + // If someone provided an override ChatHistoryProvider via AdditionalProperties, we should use that instead of the one on the thread. + if (chatOptions?.AdditionalProperties?.TryGetValue(out ChatHistoryProvider? overrideProvider) is true) { - chatMessageStore = overrideChatMessageStore; + provider = overrideProvider; } - return chatMessageStore; + return provider; } private static ChatClientAgentContinuationToken? WrapContinuationToken(ResponseContinuationToken? continuationToken, IEnumerable? inputMessages = null, List? responseUpdates = null) diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs index 719e863f0c..6f8451e2b8 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs @@ -39,10 +39,10 @@ public sealed class ChatClientAgentOptions public ChatOptions? ChatOptions { get; set; } /// - /// Gets or sets a factory function to create an instance of - /// which will be used to store chat messages for this agent. + /// Gets or sets a factory function to create an instance of + /// which will be used to provide chat history for this agent. /// - public Func>? ChatMessageStoreFactory { get; set; } + public Func>? ChatHistoryProviderFactory { get; set; } /// /// Gets or sets a factory function to create an instance of @@ -75,7 +75,7 @@ public sealed class ChatClientAgentOptions Name = this.Name, Description = this.Description, ChatOptions = this.ChatOptions?.Clone(), - ChatMessageStoreFactory = this.ChatMessageStoreFactory, + ChatHistoryProviderFactory = this.ChatHistoryProviderFactory, AIContextProviderFactory = this.AIContextProviderFactory, }; @@ -97,14 +97,14 @@ public sealed class ChatClientAgentOptions } /// - /// Context object passed to the to create a new instance of . + /// Context object passed to the to create a new instance of . /// - public sealed class ChatMessageStoreFactoryContext + public sealed class ChatHistoryProviderFactoryContext { /// - /// Gets or sets the serialized state of the chat message store, if any. + /// Gets or sets the serialized state of the , if any. /// - /// if there is no state, e.g. when the is first created. + /// if there is no state, e.g. when the is first created. public JsonElement SerializedState { get; set; } /// diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs index 06326d1ed2..a604a0d085 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs @@ -15,7 +15,7 @@ namespace Microsoft.Agents.AI; [DebuggerDisplay("{DebuggerDisplay,nq}")] public sealed class ChatClientAgentThread : AgentThread { - private ChatMessageStore? _messageStore; + private ChatHistoryProvider? _chatHistoryProvider; /// /// Initializes a new instance of the class. @@ -29,14 +29,14 @@ public sealed class ChatClientAgentThread : AgentThread /// /// /// - /// Note that either or may be set, but not both. - /// If is not null, setting will throw an + /// Note that either or may be set, but not both. + /// If is not null, setting will throw an /// exception. /// /// /// This property may be null in the following cases: /// - /// The thread stores messages via the and not in the agent service. + /// The thread stores messages via the and not in the agent service. /// This thread object is new and a server managed thread has not yet been created in the agent service. /// /// @@ -46,7 +46,7 @@ public sealed class ChatClientAgentThread : AgentThread /// to fork the thread with each iteration. /// /// - /// Attempted to set a conversation ID but a is already set. + /// Attempted to set a conversation ID but a is already set. public string? ConversationId { get; @@ -57,12 +57,12 @@ public sealed class ChatClientAgentThread : AgentThread return; } - if (this._messageStore is not null) + if (this._chatHistoryProvider is not null) { - // If we have a message store already, we shouldn't switch the thread to use a conversation id + // If we have a ChatHistoryProvider already, we shouldn't switch the thread to use a conversation id // since it means that the thread contents will essentially be deleted, and the thread will not work // with the original agent anymore. - throw new InvalidOperationException("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported."); + throw new InvalidOperationException("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported."); } field = Throw.IfNullOrWhitespace(value); @@ -70,40 +70,40 @@ public sealed class ChatClientAgentThread : AgentThread } /// - /// Gets or sets the used by this thread, for cases where messages should be stored in a custom location. + /// Gets or sets the used by this thread, for cases where messages should be stored in a custom location. /// /// /// - /// Note that either or may be set, but not both. - /// If is not null, and is set, + /// Note that either or may be set, but not both. + /// If is not null, and is set, /// will be reverted to null, and vice versa. /// /// /// This property may be null in the following cases: /// - /// The thread stores messages in the agent service and just has an id to the remove thread, instead of in an . - /// This thread object is new it is not yet clear whether it will be backed by a server managed thread or an . + /// The thread stores messages in the agent service and just has an id to the remove thread, instead of in an . + /// This thread object is new it is not yet clear whether it will be backed by a server managed thread or an . /// /// /// - public ChatMessageStore? MessageStore + public ChatHistoryProvider? ChatHistoryProvider { - get => this._messageStore; + get => this._chatHistoryProvider; internal set { - if (this._messageStore is null && value is null) + if (this._chatHistoryProvider is null && value is null) { return; } if (!string.IsNullOrWhiteSpace(this.ConversationId)) { - // If we have a conversation id already, we shouldn't switch the thread to use a message store + // If we have a conversation id already, we shouldn't switch the thread to use a ChatHistoryProvider // since it means that the thread will not work with the original agent anymore. - throw new InvalidOperationException("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported."); + throw new InvalidOperationException("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported."); } - this._messageStore = Throw.IfNull(value); + this._chatHistoryProvider = Throw.IfNull(value); } } @@ -117,9 +117,9 @@ public sealed class ChatClientAgentThread : AgentThread /// /// A representing the serialized state of the thread. /// Optional settings for customizing the JSON deserialization process. - /// - /// An optional factory function to create a custom from its serialized state. - /// If not provided, the default in-memory message store will be used. + /// + /// An optional factory function to create a custom from its serialized state. + /// If not provided, the default will be used. /// /// /// An optional factory function to create a custom from its serialized state. @@ -130,7 +130,7 @@ public sealed class ChatClientAgentThread : AgentThread internal static async Task DeserializeAsync( JsonElement serializedThreadState, JsonSerializerOptions? jsonSerializerOptions = null, - Func>? chatMessageStoreFactory = null, + Func>? chatHistoryProviderFactory = null, Func>? aiContextProviderFactory = null, CancellationToken cancellationToken = default) { @@ -152,14 +152,14 @@ public sealed class ChatClientAgentThread : AgentThread { thread.ConversationId = threadId; - // Since we have an ID, we should not have a chat message store and we can return here. + // Since we have an ID, we should not have a ChatHistoryProvider 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 + thread._chatHistoryProvider = + chatHistoryProviderFactory is not null + ? await chatHistoryProviderFactory.Invoke(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions, cancellationToken).ConfigureAwait(false) + : new InMemoryChatHistoryProvider(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions); // default to an in-memory ChatHistoryProvider return thread; } @@ -167,14 +167,14 @@ public sealed class ChatClientAgentThread : AgentThread /// public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { - JsonElement? storeState = this._messageStore?.Serialize(jsonSerializerOptions); + JsonElement? chatHistoryProviderState = this._chatHistoryProvider?.Serialize(jsonSerializerOptions); JsonElement? aiContextProviderState = this.AIContextProvider?.Serialize(jsonSerializerOptions); var state = new ThreadState { ConversationId = this.ConversationId, - StoreState = storeState is { ValueKind: not JsonValueKind.Undefined } ? storeState : null, + ChatHistoryProviderState = chatHistoryProviderState is { ValueKind: not JsonValueKind.Undefined } ? chatHistoryProviderState : null, AIContextProviderState = aiContextProviderState is { ValueKind: not JsonValueKind.Undefined } ? aiContextProviderState : null, }; @@ -185,20 +185,20 @@ public sealed class ChatClientAgentThread : AgentThread public override object? GetService(Type serviceType, object? serviceKey = null) => base.GetService(serviceType, serviceKey) ?? this.AIContextProvider?.GetService(serviceType, serviceKey) - ?? this.MessageStore?.GetService(serviceType, serviceKey); + ?? this.ChatHistoryProvider?.GetService(serviceType, serviceKey); [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string DebuggerDisplay => this.ConversationId is { } conversationId ? $"ConversationId = {conversationId}" : - this._messageStore is InMemoryChatMessageStore inMemoryStore ? $"Count = {inMemoryStore.Count}" : - this._messageStore is { } store ? $"Store = {store.GetType().Name}" : + this._chatHistoryProvider is InMemoryChatHistoryProvider inMemoryChatHistoryProvider ? $"Count = {inMemoryChatHistoryProvider.Count}" : + this._chatHistoryProvider is { } chatHistoryProvider ? $"ChatHistoryProvider = {chatHistoryProvider.GetType().Name}" : "Count = 0"; internal sealed class ThreadState { public string? ConversationId { get; set; } - public JsonElement? StoreState { get; set; } + public JsonElement? ChatHistoryProviderState { get; set; } public JsonElement? AIContextProviderState { get; set; } } diff --git a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionFixture.cs b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionFixture.cs index 2bec0b366e..e9a3cba95e 100644 --- a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionFixture.cs +++ b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionFixture.cs @@ -39,12 +39,12 @@ public class AnthropicChatCompletionFixture : IChatClientAgentFixture { var typedThread = (ChatClientAgentThread)thread; - if (typedThread.MessageStore is null) + if (typedThread.ChatHistoryProvider is null) { return []; } - return (await typedThread.MessageStore.InvokingAsync(new([]))).ToList(); + return (await typedThread.ChatHistoryProvider.InvokingAsync(new([]))).ToList(); } public Task CreateChatClientAgentAsync( diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs index ddb015eb17..41bbe82c5d 100644 --- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs +++ b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs @@ -48,12 +48,12 @@ public class AIProjectClientFixture : IChatClientAgentFixture return await this.GetChatHistoryFromResponsesChainAsync(chatClientThread.ConversationId); } - if (chatClientThread.MessageStore is null) + if (chatClientThread.ChatHistoryProvider is null) { return []; } - return (await chatClientThread.MessageStore.InvokingAsync(new([]))).ToList(); + return (await chatClientThread.ChatHistoryProvider.InvokingAsync(new([]))).ToList(); } private async Task> GetChatHistoryFromResponsesChainAsync(string conversationId) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderExtensionsTests.cs new file mode 100644 index 0000000000..84a0242320 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderExtensionsTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using Moq; + +namespace Microsoft.Agents.AI.Abstractions.UnitTests; + +/// +/// Contains tests for the class. +/// +public sealed class ChatHistoryProviderExtensionsTests +{ + [Fact] + public void WithMessageFilters_ReturnsChatHistoryProviderMessageFilter() + { + // Arrange + Mock providerMock = new(); + + // Act + ChatHistoryProvider result = providerMock.Object.WithMessageFilters( + invokingMessagesFilter: msgs => msgs, + invokedMessagesFilter: ctx => ctx); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task WithMessageFilters_InvokingFilter_IsAppliedAsync() + { + // Arrange + Mock providerMock = new(); + List innerMessages = [new(ChatRole.User, "Hello"), new(ChatRole.Assistant, "Hi")]; + ChatHistoryProvider.InvokingContext context = new([new ChatMessage(ChatRole.User, "Test")]); + + providerMock + .Setup(p => p.InvokingAsync(context, It.IsAny())) + .ReturnsAsync(innerMessages); + + ChatHistoryProvider filtered = providerMock.Object.WithMessageFilters( + invokingMessagesFilter: msgs => msgs.Where(m => m.Role == ChatRole.User)); + + // Act + List result = (await filtered.InvokingAsync(context, CancellationToken.None)).ToList(); + + // Assert + Assert.Single(result); + Assert.Equal(ChatRole.User, result[0].Role); + } + + [Fact] + public async Task WithMessageFilters_InvokedFilter_IsAppliedAsync() + { + // Arrange + Mock providerMock = new(); + List requestMessages = [new(ChatRole.User, "Hello")]; + List chatHistoryProviderMessages = [new(ChatRole.System, "System")]; + ChatHistoryProvider.InvokedContext context = new(requestMessages, chatHistoryProviderMessages) + { + ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")] + }; + + ChatHistoryProvider.InvokedContext? capturedContext = null; + providerMock + .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) + .Callback((ctx, _) => capturedContext = ctx) + .Returns(default(ValueTask)); + + ChatHistoryProvider filtered = providerMock.Object.WithMessageFilters( + invokedMessagesFilter: ctx => + { + ctx.ResponseMessages = null; + return ctx; + }); + + // Act + await filtered.InvokedAsync(context, CancellationToken.None); + + // Assert + Assert.NotNull(capturedContext); + Assert.Null(capturedContext.ResponseMessages); + } + + [Fact] + public void WithAIContextProviderMessageRemoval_ReturnsChatHistoryProviderMessageFilter() + { + // Arrange + Mock providerMock = new(); + + // Act + ChatHistoryProvider result = providerMock.Object.WithAIContextProviderMessageRemoval(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task WithAIContextProviderMessageRemoval_RemovesAIContextProviderMessagesAsync() + { + // Arrange + Mock providerMock = new(); + List requestMessages = [new(ChatRole.User, "Hello")]; + List chatHistoryProviderMessages = [new(ChatRole.System, "System")]; + List aiContextProviderMessages = [new(ChatRole.System, "Context")]; + ChatHistoryProvider.InvokedContext context = new(requestMessages, chatHistoryProviderMessages) + { + AIContextProviderMessages = aiContextProviderMessages + }; + + ChatHistoryProvider.InvokedContext? capturedContext = null; + providerMock + .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) + .Callback((ctx, _) => capturedContext = ctx) + .Returns(default(ValueTask)); + + ChatHistoryProvider filtered = providerMock.Object.WithAIContextProviderMessageRemoval(); + + // Act + await filtered.InvokedAsync(context, CancellationToken.None); + + // Assert + Assert.NotNull(capturedContext); + Assert.Null(capturedContext.AIContextProviderMessages); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreMessageFilterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderMessageFilterTests.cs similarity index 58% rename from dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreMessageFilterTests.cs rename to dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderMessageFilterTests.cs index ab10c377ae..43a3e78f10 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreMessageFilterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderMessageFilterTests.cs @@ -12,60 +12,60 @@ using Moq; namespace Microsoft.Agents.AI.Abstractions.UnitTests; /// -/// Contains tests for the class. +/// Contains tests for the class. /// -public sealed class ChatMessageStoreMessageFilterTests +public sealed class ChatHistoryProviderMessageFilterTests { [Fact] - public void Constructor_WithNullInnerStore_ThrowsArgumentNullException() + public void Constructor_WithNullInnerProvider_ThrowsArgumentNullException() { // Arrange, Act & Assert - Assert.Throws(() => new ChatMessageStoreMessageFilter(null!)); + Assert.Throws(() => new ChatHistoryProviderMessageFilter(null!)); } [Fact] - public void Constructor_WithOnlyInnerStore_Throws() + public void Constructor_WithOnlyInnerProvider_Throws() { // Arrange - var innerStoreMock = new Mock(); + var innerProviderMock = new Mock(); // Act & Assert - Assert.Throws(() => new ChatMessageStoreMessageFilter(innerStoreMock.Object)); + Assert.Throws(() => new ChatHistoryProviderMessageFilter(innerProviderMock.Object)); } [Fact] public void Constructor_WithAllParameters_CreatesInstance() { // Arrange - var innerStoreMock = new Mock(); + var innerProviderMock = new Mock(); IEnumerable InvokingFilter(IEnumerable msgs) => msgs; - ChatMessageStore.InvokedContext InvokedFilter(ChatMessageStore.InvokedContext ctx) => ctx; + ChatHistoryProvider.InvokedContext InvokedFilter(ChatHistoryProvider.InvokedContext ctx) => ctx; // Act - var filter = new ChatMessageStoreMessageFilter(innerStoreMock.Object, InvokingFilter, InvokedFilter); + var filter = new ChatHistoryProviderMessageFilter(innerProviderMock.Object, InvokingFilter, InvokedFilter); // Assert Assert.NotNull(filter); } [Fact] - public async Task InvokingAsync_WithNoOpFilters_ReturnsInnerStoreMessagesAsync() + public async Task InvokingAsync_WithNoOpFilters_ReturnsInnerProviderMessagesAsync() { // Arrange - var innerStoreMock = new Mock(); + var innerProviderMock = new Mock(); var expectedMessages = new List { new(ChatRole.User, "Hello"), new(ChatRole.Assistant, "Hi there!") }; - var context = new ChatMessageStore.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); + var context = new ChatHistoryProvider.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); - innerStoreMock + innerProviderMock .Setup(s => s.InvokingAsync(context, It.IsAny())) .ReturnsAsync(expectedMessages); - var filter = new ChatMessageStoreMessageFilter(innerStoreMock.Object, x => x, x => x); + var filter = new ChatHistoryProviderMessageFilter(innerProviderMock.Object, x => x, x => x); // Act var result = (await filter.InvokingAsync(context, CancellationToken.None)).ToList(); @@ -74,30 +74,30 @@ public sealed class ChatMessageStoreMessageFilterTests Assert.Equal(2, result.Count); Assert.Equal("Hello", result[0].Text); Assert.Equal("Hi there!", result[1].Text); - innerStoreMock.Verify(s => s.InvokingAsync(context, It.IsAny()), Times.Once); + innerProviderMock.Verify(s => s.InvokingAsync(context, It.IsAny()), Times.Once); } [Fact] public async Task InvokingAsync_WithInvokingFilter_AppliesFilterAsync() { // Arrange - var innerStoreMock = new Mock(); + var innerProviderMock = new Mock(); var innerMessages = new List { new(ChatRole.User, "Hello"), new(ChatRole.Assistant, "Hi there!"), new(ChatRole.User, "How are you?") }; - var context = new ChatMessageStore.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); + var context = new ChatHistoryProvider.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); - innerStoreMock + innerProviderMock .Setup(s => s.InvokingAsync(context, It.IsAny())) .ReturnsAsync(innerMessages); // Filter to only user messages IEnumerable InvokingFilter(IEnumerable msgs) => msgs.Where(m => m.Role == ChatRole.User); - var filter = new ChatMessageStoreMessageFilter(innerStoreMock.Object, InvokingFilter); + var filter = new ChatHistoryProviderMessageFilter(innerProviderMock.Object, InvokingFilter); // Act var result = (await filter.InvokingAsync(context, CancellationToken.None)).ToList(); @@ -105,22 +105,22 @@ public sealed class ChatMessageStoreMessageFilterTests // Assert Assert.Equal(2, result.Count); Assert.All(result, msg => Assert.Equal(ChatRole.User, msg.Role)); - innerStoreMock.Verify(s => s.InvokingAsync(context, It.IsAny()), Times.Once); + innerProviderMock.Verify(s => s.InvokingAsync(context, It.IsAny()), Times.Once); } [Fact] public async Task InvokingAsync_WithInvokingFilter_CanModifyMessagesAsync() { // Arrange - var innerStoreMock = new Mock(); + var innerProviderMock = new Mock(); var innerMessages = new List { new(ChatRole.User, "Hello"), new(ChatRole.Assistant, "Hi there!") }; - var context = new ChatMessageStore.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); + var context = new ChatHistoryProvider.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); - innerStoreMock + innerProviderMock .Setup(s => s.InvokingAsync(context, It.IsAny())) .ReturnsAsync(innerMessages); @@ -128,7 +128,7 @@ public sealed class ChatMessageStoreMessageFilterTests IEnumerable InvokingFilter(IEnumerable msgs) => msgs.Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")); - var filter = new ChatMessageStoreMessageFilter(innerStoreMock.Object, InvokingFilter); + var filter = new ChatHistoryProviderMessageFilter(innerProviderMock.Object, InvokingFilter); // Act var result = (await filter.InvokingAsync(context, CancellationToken.None)).ToList(); @@ -143,26 +143,26 @@ public sealed class ChatMessageStoreMessageFilterTests public async Task InvokedAsync_WithInvokedFilter_AppliesFilterAsync() { // Arrange - var innerStoreMock = new Mock(); + var innerProviderMock = new Mock(); var requestMessages = new List { new(ChatRole.User, "Hello") }; - var chatMessageStoreMessages = new List { new(ChatRole.System, "System") }; + var chatHistoryProviderMessages = new List { new(ChatRole.System, "System") }; var responseMessages = new List { new(ChatRole.Assistant, "Response") }; - var context = new ChatMessageStore.InvokedContext(requestMessages, chatMessageStoreMessages) + var context = new ChatHistoryProvider.InvokedContext(requestMessages, chatHistoryProviderMessages) { ResponseMessages = responseMessages }; - ChatMessageStore.InvokedContext? capturedContext = null; - innerStoreMock - .Setup(s => s.InvokedAsync(It.IsAny(), It.IsAny())) - .Callback((ctx, ct) => capturedContext = ctx) + ChatHistoryProvider.InvokedContext? capturedContext = null; + innerProviderMock + .Setup(s => s.InvokedAsync(It.IsAny(), It.IsAny())) + .Callback((ctx, ct) => capturedContext = ctx) .Returns(default(ValueTask)); // Filter that modifies the context - ChatMessageStore.InvokedContext InvokedFilter(ChatMessageStore.InvokedContext ctx) + ChatHistoryProvider.InvokedContext InvokedFilter(ChatHistoryProvider.InvokedContext ctx) { var modifiedRequestMessages = ctx.RequestMessages.Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")).ToList(); - return new ChatMessageStore.InvokedContext(modifiedRequestMessages, ctx.ChatMessageStoreMessages) + return new ChatHistoryProvider.InvokedContext(modifiedRequestMessages, ctx.ChatHistoryProviderMessages) { ResponseMessages = ctx.ResponseMessages, AIContextProviderMessages = ctx.AIContextProviderMessages, @@ -170,7 +170,7 @@ public sealed class ChatMessageStoreMessageFilterTests }; } - var filter = new ChatMessageStoreMessageFilter(innerStoreMock.Object, invokedMessagesFilter: InvokedFilter); + var filter = new ChatHistoryProviderMessageFilter(innerProviderMock.Object, invokedMessagesFilter: InvokedFilter); // Act await filter.InvokedAsync(context, CancellationToken.None); @@ -179,27 +179,27 @@ public sealed class ChatMessageStoreMessageFilterTests Assert.NotNull(capturedContext); Assert.Single(capturedContext.RequestMessages); Assert.Equal("[FILTERED] Hello", capturedContext.RequestMessages.First().Text); - innerStoreMock.Verify(s => s.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); + innerProviderMock.Verify(s => s.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); } [Fact] - public void Serialize_DelegatesToInnerStore() + public void Serialize_DelegatesToInnerProvider() { // Arrange - var innerStoreMock = new Mock(); + var innerProviderMock = new Mock(); var expectedJson = JsonSerializer.SerializeToElement("data", TestJsonSerializerContext.Default.String); - innerStoreMock + innerProviderMock .Setup(s => s.Serialize(It.IsAny())) .Returns(expectedJson); - var filter = new ChatMessageStoreMessageFilter(innerStoreMock.Object, x => x, x => x); + var filter = new ChatHistoryProviderMessageFilter(innerProviderMock.Object, x => x, x => x); // Act var result = filter.Serialize(); // Assert Assert.Equal(expectedJson.GetRawText(), result.GetRawText()); - innerStoreMock.Verify(s => s.Serialize(null), Times.Once); + innerProviderMock.Verify(s => s.Serialize(null), Times.Once); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderTests.cs similarity index 53% rename from dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreTests.cs rename to dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderTests.cs index 883941458c..02955f4a25 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatHistoryProviderTests.cs @@ -10,73 +10,73 @@ using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Abstractions.UnitTests; /// -/// Contains tests for the class. +/// Contains tests for the class. /// -public class ChatMessageStoreTests +public class ChatHistoryProviderTests { #region GetService Method Tests [Fact] - public void GetService_RequestingExactStoreType_ReturnsStore() + public void GetService_RequestingExactProviderType_ReturnsProvider() { - var store = new TestChatMessageStore(); - var result = store.GetService(typeof(TestChatMessageStore)); + var provider = new TestChatHistoryProvider(); + var result = provider.GetService(typeof(TestChatHistoryProvider)); Assert.NotNull(result); - Assert.Same(store, result); + Assert.Same(provider, result); } [Fact] - public void GetService_RequestingBaseStoreType_ReturnsStore() + public void GetService_RequestingBaseProviderType_ReturnsProvider() { - var store = new TestChatMessageStore(); - var result = store.GetService(typeof(ChatMessageStore)); + var provider = new TestChatHistoryProvider(); + var result = provider.GetService(typeof(ChatHistoryProvider)); Assert.NotNull(result); - Assert.Same(store, result); + Assert.Same(provider, result); } [Fact] public void GetService_RequestingUnrelatedType_ReturnsNull() { - var store = new TestChatMessageStore(); - var result = store.GetService(typeof(string)); + var provider = new TestChatHistoryProvider(); + var result = provider.GetService(typeof(string)); Assert.Null(result); } [Fact] public void GetService_WithServiceKey_ReturnsNull() { - var store = new TestChatMessageStore(); - var result = store.GetService(typeof(TestChatMessageStore), "some-key"); + var provider = new TestChatHistoryProvider(); + var result = provider.GetService(typeof(TestChatHistoryProvider), "some-key"); Assert.Null(result); } [Fact] public void GetService_WithNullServiceType_ThrowsArgumentNullException() { - var store = new TestChatMessageStore(); - Assert.Throws(() => store.GetService(null!)); + var provider = new TestChatHistoryProvider(); + Assert.Throws(() => provider.GetService(null!)); } [Fact] public void GetService_Generic_ReturnsCorrectType() { - var store = new TestChatMessageStore(); - var result = store.GetService(); + var provider = new TestChatHistoryProvider(); + var result = provider.GetService(); Assert.NotNull(result); - Assert.Same(store, result); + Assert.Same(provider, result); } [Fact] public void GetService_Generic_ReturnsNullForUnrelatedType() { - var store = new TestChatMessageStore(); - var result = store.GetService(); + var provider = new TestChatHistoryProvider(); + var result = provider.GetService(); Assert.Null(result); } #endregion - private sealed class TestChatMessageStore : ChatMessageStore + private sealed class TestChatHistoryProvider : ChatHistoryProvider { public override ValueTask> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default) => new(Array.Empty()); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryAgentThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryAgentThreadTests.cs index 906db4d30c..c35ff98711 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryAgentThreadTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryAgentThreadTests.cs @@ -16,29 +16,29 @@ public class InMemoryAgentThreadTests #region Constructor and Property Tests [Fact] - public void Constructor_SetsDefaultMessageStore() + public void Constructor_SetsDefaultChatHistoryProvider() { // Arrange & Act var thread = new TestInMemoryAgentThread(); // Assert - Assert.NotNull(thread.GetMessageStore()); - Assert.Empty(thread.GetMessageStore()); + Assert.NotNull(thread.GetChatHistoryProvider()); + Assert.Empty(thread.GetChatHistoryProvider()); } [Fact] - public void Constructor_WithMessageStore_SetsProperty() + public void Constructor_WithChatHistoryProvider_SetsProperty() { // Arrange - InMemoryChatMessageStore store = [new(ChatRole.User, "Hello")]; + InMemoryChatHistoryProvider provider = [new(ChatRole.User, "Hello")]; // Act - var thread = new TestInMemoryAgentThread(store); + var thread = new TestInMemoryAgentThread(provider); // Assert - Assert.Same(store, thread.GetMessageStore()); - Assert.Single(thread.GetMessageStore()); - Assert.Equal("Hello", thread.GetMessageStore()[0].Text); + Assert.Same(provider, thread.GetChatHistoryProvider()); + Assert.Single(thread.GetChatHistoryProvider()); + Assert.Equal("Hello", thread.GetChatHistoryProvider()[0].Text); } [Fact] @@ -51,27 +51,27 @@ public class InMemoryAgentThreadTests var thread = new TestInMemoryAgentThread(messages); // Assert - Assert.NotNull(thread.GetMessageStore()); - Assert.Single(thread.GetMessageStore()); - Assert.Equal("Hi", thread.GetMessageStore()[0].Text); + Assert.NotNull(thread.GetChatHistoryProvider()); + Assert.Single(thread.GetChatHistoryProvider()); + Assert.Equal("Hi", thread.GetChatHistoryProvider()[0].Text); } [Fact] public void Constructor_WithSerializedState_SetsProperty() { // Arrange - InMemoryChatMessageStore store = [new(ChatRole.User, "TestMsg")]; - var storeState = store.Serialize(); - var threadStateWrapper = new InMemoryAgentThread.InMemoryAgentThreadState { StoreState = storeState }; + InMemoryChatHistoryProvider provider = [new(ChatRole.User, "TestMsg")]; + var providerState = provider.Serialize(); + var threadStateWrapper = new InMemoryAgentThread.InMemoryAgentThreadState { ChatHistoryProviderState = providerState }; var json = JsonSerializer.SerializeToElement(threadStateWrapper, TestJsonSerializerContext.Default.InMemoryAgentThreadState); // Act var thread = new TestInMemoryAgentThread(json); // Assert - Assert.NotNull(thread.GetMessageStore()); - Assert.Single(thread.GetMessageStore()); - Assert.Equal("TestMsg", thread.GetMessageStore()[0].Text); + Assert.NotNull(thread.GetChatHistoryProvider()); + Assert.Single(thread.GetChatHistoryProvider()); + Assert.Equal("TestMsg", thread.GetChatHistoryProvider()[0].Text); } [Fact] @@ -99,9 +99,9 @@ public class InMemoryAgentThreadTests // Assert Assert.Equal(JsonValueKind.Object, json.ValueKind); - Assert.True(json.TryGetProperty("storeState", out var storeStateProperty)); - Assert.Equal(JsonValueKind.Object, storeStateProperty.ValueKind); - Assert.True(storeStateProperty.TryGetProperty("messages", out var messagesProperty)); + Assert.True(json.TryGetProperty("chatHistoryProviderState", out var providerStateProperty)); + Assert.Equal(JsonValueKind.Object, providerStateProperty.ValueKind); + Assert.True(providerStateProperty.TryGetProperty("messages", out var messagesProperty)); Assert.Equal(JsonValueKind.Array, messagesProperty.ValueKind); var messagesList = messagesProperty.EnumerateArray().ToList(); Assert.Single(messagesList); @@ -118,9 +118,9 @@ public class InMemoryAgentThreadTests // Assert Assert.Equal(JsonValueKind.Object, json.ValueKind); - Assert.True(json.TryGetProperty("storeState", out var storeStateProperty)); - Assert.Equal(JsonValueKind.Object, storeStateProperty.ValueKind); - Assert.True(storeStateProperty.TryGetProperty("messages", out var messagesProperty)); + Assert.True(json.TryGetProperty("chatHistoryProviderState", out var providerStateProperty)); + Assert.Equal(JsonValueKind.Object, providerStateProperty.ValueKind); + Assert.True(providerStateProperty.TryGetProperty("messages", out var messagesProperty)); Assert.Equal(JsonValueKind.Array, messagesProperty.ValueKind); Assert.Empty(messagesProperty.EnumerateArray()); } @@ -130,15 +130,15 @@ public class InMemoryAgentThreadTests #region GetService Tests [Fact] - public void GetService_RequestingChatMessageStore_ReturnsChatMessageStore() + public void GetService_RequestingChatHistoryProvider_ReturnsChatHistoryProvider() { // Arrange var thread = new TestInMemoryAgentThread(); // Act & Assert - Assert.NotNull(thread.GetService(typeof(ChatMessageStore))); - Assert.Same(thread.GetMessageStore(), thread.GetService(typeof(ChatMessageStore))); - Assert.Same(thread.GetMessageStore(), thread.GetService(typeof(InMemoryChatMessageStore))); + Assert.NotNull(thread.GetService(typeof(ChatHistoryProvider))); + Assert.Same(thread.GetChatHistoryProvider(), thread.GetService(typeof(ChatHistoryProvider))); + Assert.Same(thread.GetChatHistoryProvider(), thread.GetService(typeof(InMemoryChatHistoryProvider))); } #endregion @@ -147,9 +147,9 @@ public class InMemoryAgentThreadTests private sealed class TestInMemoryAgentThread : InMemoryAgentThread { public TestInMemoryAgentThread() { } - public TestInMemoryAgentThread(InMemoryChatMessageStore? store) : base(store) { } + public TestInMemoryAgentThread(InMemoryChatHistoryProvider? provider) : base(provider) { } public TestInMemoryAgentThread(IEnumerable messages) : base(messages) { } public TestInMemoryAgentThread(JsonElement serializedThreadState) : base(serializedThreadState) { } - public InMemoryChatMessageStore GetMessageStore() => this.MessageStore; + public InMemoryChatHistoryProvider GetChatHistoryProvider() => this.ChatHistoryProvider; } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatHistoryProviderTests.cs similarity index 60% rename from dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs rename to dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatHistoryProviderTests.cs index 43bfacca79..debaff73ef 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatHistoryProviderTests.cs @@ -14,24 +14,24 @@ using Moq; namespace Microsoft.Agents.AI.Abstractions.UnitTests; /// -/// Contains tests for the class. +/// Contains tests for the class. /// -public class InMemoryChatMessageStoreTests +public class InMemoryChatHistoryProviderTests { [Fact] public void Constructor_Throws_ForNullReducer() => // Arrange & Act & Assert - Assert.Throws(() => new InMemoryChatMessageStore(null!)); + Assert.Throws(() => new InMemoryChatHistoryProvider(null!)); [Fact] public void Constructor_DefaultsToBeforeMessageRetrieval_ForNotProvidedTriggerEvent() { // Arrange & Act var reducerMock = new Mock(); - var store = new InMemoryChatMessageStore(reducerMock.Object); + var provider = new InMemoryChatHistoryProvider(reducerMock.Object); // Assert - Assert.Equal(InMemoryChatMessageStore.ChatReducerTriggerEvent.BeforeMessagesRetrieval, store.ReducerTriggerEvent); + Assert.Equal(InMemoryChatHistoryProvider.ChatReducerTriggerEvent.BeforeMessagesRetrieval, provider.ReducerTriggerEvent); } [Fact] @@ -39,11 +39,11 @@ public class InMemoryChatMessageStoreTests { // Arrange & Act var reducerMock = new Mock(); - var store = new InMemoryChatMessageStore(reducerMock.Object, InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded); + var provider = new InMemoryChatHistoryProvider(reducerMock.Object, InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded); // Assert - Assert.Same(reducerMock.Object, store.ChatReducer); - Assert.Equal(InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded, store.ReducerTriggerEvent); + Assert.Same(reducerMock.Object, provider.ChatReducer); + Assert.Equal(InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded, provider.ReducerTriggerEvent); } [Fact] @@ -57,7 +57,7 @@ public class InMemoryChatMessageStoreTests { new(ChatRole.Assistant, "Hi there!") }; - var messageStoreMessages = new List() + var providerMessages = new List() { new(ChatRole.System, "original instructions") }; @@ -66,44 +66,44 @@ public class InMemoryChatMessageStoreTests new(ChatRole.System, "additional context") }; - var store = new InMemoryChatMessageStore(); - store.Add(messageStoreMessages[0]); - var context = new ChatMessageStore.InvokedContext(requestMessages, messageStoreMessages) + var provider = new InMemoryChatHistoryProvider(); + provider.Add(providerMessages[0]); + var context = new ChatHistoryProvider.InvokedContext(requestMessages, providerMessages) { AIContextProviderMessages = aiContextProviderMessages, ResponseMessages = responseMessages }; - await store.InvokedAsync(context, CancellationToken.None); + await provider.InvokedAsync(context, CancellationToken.None); - Assert.Equal(4, store.Count); - Assert.Equal("original instructions", store[0].Text); - Assert.Equal("Hello", store[1].Text); - Assert.Equal("additional context", store[2].Text); - Assert.Equal("Hi there!", store[3].Text); + Assert.Equal(4, provider.Count); + Assert.Equal("original instructions", provider[0].Text); + Assert.Equal("Hello", provider[1].Text); + Assert.Equal("additional context", provider[2].Text); + Assert.Equal("Hi there!", provider[3].Text); } [Fact] public async Task InvokedAsyncWithEmptyDoesNotFailAsync() { - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); - var context = new ChatMessageStore.InvokedContext([], []); - await store.InvokedAsync(context, CancellationToken.None); + var context = new ChatHistoryProvider.InvokedContext([], []); + await provider.InvokedAsync(context, CancellationToken.None); - Assert.Empty(store); + Assert.Empty(provider); } [Fact] public async Task InvokingAsyncReturnsAllMessagesAsync() { - var store = new InMemoryChatMessageStore + var provider = new InMemoryChatHistoryProvider { new ChatMessage(ChatRole.User, "Test1"), new ChatMessage(ChatRole.Assistant, "Test2") }; - var context = new ChatMessageStore.InvokingContext([]); - var result = (await store.InvokingAsync(context, CancellationToken.None)).ToList(); + var context = new ChatHistoryProvider.InvokingContext([]); + var result = (await provider.InvokingAsync(context, CancellationToken.None)).ToList(); Assert.Equal(2, result.Count); Assert.Contains(result, m => m.Text == "Test1"); @@ -115,26 +115,26 @@ public class InMemoryChatMessageStoreTests { var emptyObject = JsonSerializer.Deserialize("{}", TestJsonSerializerContext.Default.JsonElement); - var newStore = new InMemoryChatMessageStore(emptyObject); + var newProvider = new InMemoryChatHistoryProvider(emptyObject); - Assert.Empty(newStore); + Assert.Empty(newProvider); } [Fact] public async Task SerializeAndDeserializeConstructorRoundtripsAsync() { - var store = new InMemoryChatMessageStore + var provider = new InMemoryChatHistoryProvider { new ChatMessage(ChatRole.User, "A"), new ChatMessage(ChatRole.Assistant, "B") }; - var jsonElement = store.Serialize(); - var newStore = new InMemoryChatMessageStore(jsonElement); + var jsonElement = provider.Serialize(); + var newProvider = new InMemoryChatHistoryProvider(jsonElement); - Assert.Equal(2, newStore.Count); - Assert.Equal("A", newStore[0].Text); - Assert.Equal("B", newStore[1].Text); + Assert.Equal(2, newProvider.Count); + Assert.Equal("A", newProvider[0].Text); + Assert.Equal("B", newProvider[1].Text); } [Fact] @@ -147,66 +147,66 @@ public class InMemoryChatMessageStoreTests }; options.AddAIContentType(typeDiscriminatorId: "testContent"); - var store = new InMemoryChatMessageStore + var provider = new InMemoryChatHistoryProvider { new ChatMessage(ChatRole.User, [new TestAIContent("foo data")]), }; - var jsonElement = store.Serialize(options); - var newStore = new InMemoryChatMessageStore(jsonElement, options); + var jsonElement = provider.Serialize(options); + var newProvider = new InMemoryChatHistoryProvider(jsonElement, options); - Assert.Single(newStore); - var actualTestAIContent = Assert.IsType(newStore[0].Contents[0]); + Assert.Single(newProvider); + var actualTestAIContent = Assert.IsType(newProvider[0].Contents[0]); Assert.Equal("foo data", actualTestAIContent.TestData); } [Fact] public async Task SerializeAndDeserializeWorksWithExperimentalContentTypesAsync() { - var store = new InMemoryChatMessageStore + var provider = new InMemoryChatHistoryProvider { new ChatMessage(ChatRole.User, [new FunctionApprovalRequestContent("call123", new FunctionCallContent("call123", "some_func"))]), new ChatMessage(ChatRole.Assistant, [new FunctionApprovalResponseContent("call123", true, new FunctionCallContent("call123", "some_func"))]) }; - var jsonElement = store.Serialize(); - var newStore = new InMemoryChatMessageStore(jsonElement); + var jsonElement = provider.Serialize(); + var newProvider = new InMemoryChatHistoryProvider(jsonElement); - Assert.Equal(2, newStore.Count); - Assert.IsType(newStore[0].Contents[0]); - Assert.IsType(newStore[1].Contents[0]); + Assert.Equal(2, newProvider.Count); + Assert.IsType(newProvider[0].Contents[0]); + Assert.IsType(newProvider[1].Contents[0]); } [Fact] - public async Task InvokedAsyncWithEmptyMessagesDoesNotChangeStoreAsync() + public async Task InvokedAsyncWithEmptyMessagesDoesNotChangeProviderAsync() { - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var messages = new List(); - var context = new ChatMessageStore.InvokedContext(messages, []); - await store.InvokedAsync(context, CancellationToken.None); + var context = new ChatHistoryProvider.InvokedContext(messages, []); + await provider.InvokedAsync(context, CancellationToken.None); - Assert.Empty(store); + Assert.Empty(provider); } [Fact] public async Task InvokedAsync_WithNullContext_ThrowsArgumentNullExceptionAsync() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); // Act & Assert - await Assert.ThrowsAsync(() => store.InvokedAsync(null!, CancellationToken.None).AsTask()); + await Assert.ThrowsAsync(() => provider.InvokedAsync(null!, CancellationToken.None).AsTask()); } [Fact] - public void DeserializeContructor_WithNullSerializedState_CreatesEmptyStore() + public void DeserializeContructor_WithNullSerializedState_CreatesEmptyProvider() { // Act - var store = new InMemoryChatMessageStore(new JsonElement()); + var provider = new InMemoryChatHistoryProvider(new JsonElement()); // Assert - Assert.Empty(store); + Assert.Empty(provider); } [Fact] @@ -218,10 +218,10 @@ public class InMemoryChatMessageStoreTests TestJsonSerializerContext.Default.IDictionaryStringObject); // Act - var store = new InMemoryChatMessageStore(stateWithEmptyMessages); + var provider = new InMemoryChatHistoryProvider(stateWithEmptyMessages); // Assert - Assert.Empty(store); + Assert.Empty(provider); } [Fact] @@ -233,10 +233,10 @@ public class InMemoryChatMessageStoreTests TestJsonSerializerContext.Default.DictionaryStringObject); // Act - var store = new InMemoryChatMessageStore(stateWithNullMessages); + var provider = new InMemoryChatHistoryProvider(stateWithNullMessages); // Assert - Assert.Empty(store); + Assert.Empty(provider); } [Fact] @@ -254,159 +254,159 @@ public class InMemoryChatMessageStoreTests TestJsonSerializerContext.Default.DictionaryStringObject); // Act - var store = new InMemoryChatMessageStore(serializedState); + var provider = new InMemoryChatHistoryProvider(serializedState); // Assert - Assert.Equal(2, store.Count); - Assert.Equal("User message", store[0].Text); - Assert.Equal("Assistant message", store[1].Text); + Assert.Equal(2, provider.Count); + Assert.Equal("User message", provider[0].Text); + Assert.Equal("Assistant message", provider[1].Text); } [Fact] public void IndexerGet_ReturnsCorrectMessage() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); - store.Add(message1); - store.Add(message2); + provider.Add(message1); + provider.Add(message2); // Act & Assert - Assert.Same(message1, store[0]); - Assert.Same(message2, store[1]); + Assert.Same(message1, provider[0]); + Assert.Same(message2, provider[1]); } [Fact] public void IndexerSet_UpdatesMessage() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var originalMessage = new ChatMessage(ChatRole.User, "Original"); var newMessage = new ChatMessage(ChatRole.User, "Updated"); - store.Add(originalMessage); + provider.Add(originalMessage); // Act - store[0] = newMessage; + provider[0] = newMessage; // Assert - Assert.Same(newMessage, store[0]); - Assert.Equal("Updated", store[0].Text); + Assert.Same(newMessage, provider[0]); + Assert.Equal("Updated", provider[0].Text); } [Fact] public void IsReadOnly_ReturnsFalse() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); // Act & Assert - Assert.False(store.IsReadOnly); + Assert.False(provider.IsReadOnly); } [Fact] public void IndexOf_ReturnsCorrectIndex() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); var message3 = new ChatMessage(ChatRole.User, "Third"); - store.Add(message1); - store.Add(message2); + provider.Add(message1); + provider.Add(message2); // Act & Assert - Assert.Equal(0, store.IndexOf(message1)); - Assert.Equal(1, store.IndexOf(message2)); - Assert.Equal(-1, store.IndexOf(message3)); // Not in store + Assert.Equal(0, provider.IndexOf(message1)); + Assert.Equal(1, provider.IndexOf(message2)); + Assert.Equal(-1, provider.IndexOf(message3)); // Not in provider } [Fact] public void Insert_InsertsMessageAtCorrectIndex() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); var insertMessage = new ChatMessage(ChatRole.User, "Inserted"); - store.Add(message1); - store.Add(message2); + provider.Add(message1); + provider.Add(message2); // Act - store.Insert(1, insertMessage); + provider.Insert(1, insertMessage); // Assert - Assert.Equal(3, store.Count); - Assert.Same(message1, store[0]); - Assert.Same(insertMessage, store[1]); - Assert.Same(message2, store[2]); + Assert.Equal(3, provider.Count); + Assert.Same(message1, provider[0]); + Assert.Same(insertMessage, provider[1]); + Assert.Same(message2, provider[2]); } [Fact] public void RemoveAt_RemovesMessageAtIndex() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); var message3 = new ChatMessage(ChatRole.User, "Third"); - store.Add(message1); - store.Add(message2); - store.Add(message3); + provider.Add(message1); + provider.Add(message2); + provider.Add(message3); // Act - store.RemoveAt(1); + provider.RemoveAt(1); // Assert - Assert.Equal(2, store.Count); - Assert.Same(message1, store[0]); - Assert.Same(message3, store[1]); + Assert.Equal(2, provider.Count); + Assert.Same(message1, provider[0]); + Assert.Same(message3, provider[1]); } [Fact] public void Clear_RemovesAllMessages() { // Arrange - var store = new InMemoryChatMessageStore + var provider = new InMemoryChatHistoryProvider { new ChatMessage(ChatRole.User, "First"), new ChatMessage(ChatRole.Assistant, "Second") }; // Act - store.Clear(); + provider.Clear(); // Assert - Assert.Empty(store); + Assert.Empty(provider); } [Fact] public void Contains_ReturnsTrueForExistingMessage() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); - store.Add(message1); + provider.Add(message1); // Act & Assert - Assert.Contains(message1, store); - Assert.DoesNotContain(message2, store); + Assert.Contains(message1, provider); + Assert.DoesNotContain(message2, provider); } [Fact] public void CopyTo_CopiesMessagesToArray() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); - store.Add(message1); - store.Add(message2); + provider.Add(message1); + provider.Add(message2); var array = new ChatMessage[4]; // Act - store.CopyTo(array, 1); + provider.CopyTo(array, 1); // Assert Assert.Null(array[0]); @@ -419,54 +419,54 @@ public class InMemoryChatMessageStoreTests public void Remove_RemovesSpecificMessage() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); var message3 = new ChatMessage(ChatRole.User, "Third"); - store.Add(message1); - store.Add(message2); - store.Add(message3); + provider.Add(message1); + provider.Add(message2); + provider.Add(message3); // Act - var removed = store.Remove(message2); + var removed = provider.Remove(message2); // Assert Assert.True(removed); - Assert.Equal(2, store.Count); - Assert.Same(message1, store[0]); - Assert.Same(message3, store[1]); + Assert.Equal(2, provider.Count); + Assert.Same(message1, provider[0]); + Assert.Same(message3, provider[1]); } [Fact] public void Remove_ReturnsFalseForNonExistentMessage() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); - store.Add(message1); + provider.Add(message1); // Act - var removed = store.Remove(message2); + var removed = provider.Remove(message2); // Assert Assert.False(removed); - Assert.Single(store); + Assert.Single(provider); } [Fact] public void GetEnumerator_Generic_ReturnsAllMessages() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); - store.Add(message1); - store.Add(message2); + provider.Add(message1); + provider.Add(message2); // Act var messages = new List(); - messages.AddRange(store); + messages.AddRange(provider); // Assert Assert.Equal(2, messages.Count); @@ -478,15 +478,15 @@ public class InMemoryChatMessageStoreTests public void GetEnumerator_NonGeneric_ReturnsAllMessages() { // Arrange - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); var message1 = new ChatMessage(ChatRole.User, "First"); var message2 = new ChatMessage(ChatRole.Assistant, "Second"); - store.Add(message1); - store.Add(message2); + provider.Add(message1); + provider.Add(message2); // Act var messages = new List(); - var enumerator = ((System.Collections.IEnumerable)store).GetEnumerator(); + var enumerator = ((System.Collections.IEnumerable)provider).GetEnumerator(); while (enumerator.MoveNext()) { messages.Add((ChatMessage)enumerator.Current); @@ -517,15 +517,15 @@ public class InMemoryChatMessageStoreTests .Setup(r => r.ReduceAsync(It.Is>(x => x.SequenceEqual(originalMessages)), It.IsAny())) .ReturnsAsync(reducedMessages); - var store = new InMemoryChatMessageStore(reducerMock.Object, InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded); + var provider = new InMemoryChatHistoryProvider(reducerMock.Object, InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded); // Act - var context = new ChatMessageStore.InvokedContext(originalMessages, []); - await store.InvokedAsync(context, CancellationToken.None); + var context = new ChatHistoryProvider.InvokedContext(originalMessages, []); + await provider.InvokedAsync(context, CancellationToken.None); // Assert - Assert.Single(store); - Assert.Equal("Reduced", store[0].Text); + Assert.Single(provider); + Assert.Equal("Reduced", provider[0].Text); reducerMock.Verify(r => r.ReduceAsync(It.Is>(x => x.SequenceEqual(originalMessages)), It.IsAny()), Times.Once); } @@ -548,16 +548,16 @@ public class InMemoryChatMessageStoreTests .Setup(r => r.ReduceAsync(It.Is>(x => x.SequenceEqual(originalMessages)), It.IsAny())) .ReturnsAsync(reducedMessages); - var store = new InMemoryChatMessageStore(reducerMock.Object, InMemoryChatMessageStore.ChatReducerTriggerEvent.BeforeMessagesRetrieval); - // Add messages directly to the store for this test + var provider = new InMemoryChatHistoryProvider(reducerMock.Object, InMemoryChatHistoryProvider.ChatReducerTriggerEvent.BeforeMessagesRetrieval); + // Add messages directly to the provider for this test foreach (var msg in originalMessages) { - store.Add(msg); + provider.Add(msg); } // Act - var invokingContext = new ChatMessageStore.InvokingContext(Array.Empty()); - var result = (await store.InvokingAsync(invokingContext, CancellationToken.None)).ToList(); + var invokingContext = new ChatHistoryProvider.InvokingContext(Array.Empty()); + var result = (await provider.InvokingAsync(invokingContext, CancellationToken.None)).ToList(); // Assert Assert.Single(result); @@ -576,15 +576,15 @@ public class InMemoryChatMessageStoreTests var reducerMock = new Mock(); - var store = new InMemoryChatMessageStore(reducerMock.Object, InMemoryChatMessageStore.ChatReducerTriggerEvent.BeforeMessagesRetrieval); + var provider = new InMemoryChatHistoryProvider(reducerMock.Object, InMemoryChatHistoryProvider.ChatReducerTriggerEvent.BeforeMessagesRetrieval); // Act - var context = new ChatMessageStore.InvokedContext(originalMessages, []); - await store.InvokedAsync(context, CancellationToken.None); + var context = new ChatHistoryProvider.InvokedContext(originalMessages, []); + await provider.InvokedAsync(context, CancellationToken.None); // Assert - Assert.Single(store); - Assert.Equal("Hello", store[0].Text); + Assert.Single(provider); + Assert.Equal("Hello", provider[0].Text); reducerMock.Verify(r => r.ReduceAsync(It.IsAny>(), It.IsAny()), Times.Never); } @@ -599,14 +599,14 @@ public class InMemoryChatMessageStoreTests var reducerMock = new Mock(); - var store = new InMemoryChatMessageStore(reducerMock.Object, InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded) + var provider = new InMemoryChatHistoryProvider(reducerMock.Object, InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded) { originalMessages[0] }; // Act - var invokingContext = new ChatMessageStore.InvokingContext(Array.Empty()); - var result = (await store.InvokingAsync(invokingContext, CancellationToken.None)).ToList(); + var invokingContext = new ChatHistoryProvider.InvokingContext(Array.Empty()); + var result = (await provider.InvokingAsync(invokingContext, CancellationToken.None)).ToList(); // Assert Assert.Single(result); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/TestJsonSerializerContext.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/TestJsonSerializerContext.cs index 1f6f9bb578..397cd48c86 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/TestJsonSerializerContext.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/TestJsonSerializerContext.cs @@ -22,5 +22,5 @@ namespace Microsoft.Agents.AI.Abstractions.UnitTests; [JsonSerializable(typeof(InMemoryAgentThread.InMemoryAgentThreadState))] [JsonSerializable(typeof(ServiceIdAgentThread.ServiceIdAgentThreadState))] [JsonSerializable(typeof(ServiceIdAgentThreadTests.EmptyObject))] -[JsonSerializable(typeof(InMemoryChatMessageStoreTests.TestAIContent))] +[JsonSerializable(typeof(InMemoryChatHistoryProviderTests.TestAIContent))] internal sealed partial class TestJsonSerializerContext : JsonSerializerContext; diff --git a/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatMessageStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatHistoryProviderTests.cs similarity index 72% rename from dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatMessageStoreTests.cs rename to dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatHistoryProviderTests.cs index 9410e68f1b..ab2f58dfd5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatMessageStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatHistoryProviderTests.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.CosmosNoSql.UnitTests; /// -/// Contains tests for . +/// Contains tests for . /// /// Test Modes: /// - Default Mode: Cleans up all test data after each test run (deletes database) @@ -39,7 +39,7 @@ namespace Microsoft.Agents.AI.CosmosNoSql.UnitTests; /// - Reset to cleanup mode: $env:COSMOS_PRESERVE_CONTAINERS=""; dotnet test tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/ /// [Collection("CosmosDB")] -public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable +public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable { // Cosmos DB Emulator connection settings private const string EmulatorEndpoint = "https://localhost:8081"; @@ -154,13 +154,13 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); // Act - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, "test-conversation"); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, "test-conversation"); // Assert - Assert.NotNull(store); - Assert.Equal("test-conversation", store.ConversationId); - Assert.Equal(s_testDatabaseId, store.DatabaseId); - Assert.Equal(TestContainerId, store.ContainerId); + Assert.NotNull(provider); + Assert.Equal("test-conversation", provider.ConversationId); + Assert.Equal(s_testDatabaseId, provider.DatabaseId); + Assert.Equal(TestContainerId, provider.ContainerId); } [SkippableFact] @@ -171,13 +171,13 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); // Act - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId); // Assert - Assert.NotNull(store); - Assert.NotNull(store.ConversationId); - Assert.Equal(s_testDatabaseId, store.DatabaseId); - Assert.Equal(TestContainerId, store.ContainerId); + Assert.NotNull(provider); + Assert.NotNull(provider.ConversationId); + Assert.Equal(s_testDatabaseId, provider.DatabaseId); + Assert.Equal(TestContainerId, provider.ContainerId); } [SkippableFact] @@ -186,7 +186,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable { // Arrange & Act & Assert Assert.Throws(() => - new CosmosChatMessageStore((string)null!, s_testDatabaseId, TestContainerId, "test-conversation")); + new CosmosChatHistoryProvider((string)null!, s_testDatabaseId, TestContainerId, "test-conversation")); } [SkippableFact] @@ -197,7 +197,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); Assert.Throws(() => - new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, "")); + new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, "")); } #endregion @@ -211,23 +211,23 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable // Arrange this.SkipIfEmulatorNotAvailable(); var conversationId = Guid.NewGuid().ToString(); - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); var message = new ChatMessage(ChatRole.User, "Hello, world!"); - var context = new ChatMessageStore.InvokedContext([message], []) + var context = new ChatHistoryProvider.InvokedContext([message], []) { ResponseMessages = [] }; // Act - await store.InvokedAsync(context); + await provider.InvokedAsync(context); // Wait a moment for eventual consistency await Task.Delay(100); // Assert - var invokingContext = new ChatMessageStore.InvokingContext([]); - var messages = await store.InvokingAsync(invokingContext); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); + var messages = await provider.InvokingAsync(invokingContext); var messageList = messages.ToList(); // Simple assertion - if this fails, we know the deserialization is the issue @@ -277,7 +277,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable // Arrange this.SkipIfEmulatorNotAvailable(); var conversationId = Guid.NewGuid().ToString(); - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); var requestMessages = new[] { new ChatMessage(ChatRole.User, "First message"), @@ -293,18 +293,18 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable new ChatMessage(ChatRole.Assistant, "Response message") }; - var context = new ChatMessageStore.InvokedContext(requestMessages, []) + var context = new ChatHistoryProvider.InvokedContext(requestMessages, []) { AIContextProviderMessages = aiContextProviderMessages, ResponseMessages = responseMessages }; // Act - await store.InvokedAsync(context); + await provider.InvokedAsync(context); // Assert - var invokingContext = new ChatMessageStore.InvokingContext([]); - var retrievedMessages = await store.InvokingAsync(invokingContext); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); + var retrievedMessages = await provider.InvokingAsync(invokingContext); var messageList = retrievedMessages.ToList(); Assert.Equal(5, messageList.Count); Assert.Equal("First message", messageList[0].Text); @@ -324,11 +324,11 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable { // Arrange this.SkipIfEmulatorNotAvailable(); - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString()); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString()); // Act - var invokingContext = new ChatMessageStore.InvokingContext([]); - var messages = await store.InvokingAsync(invokingContext); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); + var messages = await provider.InvokingAsync(invokingContext); // Assert Assert.Empty(messages); @@ -343,18 +343,18 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable var conversation1 = Guid.NewGuid().ToString(); var conversation2 = Guid.NewGuid().ToString(); - using var store1 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversation1); - using var store2 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversation2); + using var store1 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversation1); + using var store2 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversation2); - var context1 = new ChatMessageStore.InvokedContext([new ChatMessage(ChatRole.User, "Message for conversation 1")], []); - var context2 = new ChatMessageStore.InvokedContext([new ChatMessage(ChatRole.User, "Message for conversation 2")], []); + var context1 = new ChatHistoryProvider.InvokedContext([new ChatMessage(ChatRole.User, "Message for conversation 1")], []); + var context2 = new ChatHistoryProvider.InvokedContext([new ChatMessage(ChatRole.User, "Message for conversation 2")], []); await store1.InvokedAsync(context1); await store2.InvokedAsync(context2); // Act - var invokingContext1 = new ChatMessageStore.InvokingContext([]); - var invokingContext2 = new ChatMessageStore.InvokingContext([]); + var invokingContext1 = new ChatHistoryProvider.InvokingContext([]); + var invokingContext2 = new ChatHistoryProvider.InvokingContext([]); var messages1 = await store1.InvokingAsync(invokingContext1); var messages2 = await store2.InvokingAsync(invokingContext2); @@ -379,7 +379,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable // Arrange this.SkipIfEmulatorNotAvailable(); var conversationId = $"test-conversation-{Guid.NewGuid():N}"; // Use unique conversation ID - using var originalStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); + using var originalStore = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); var messages = new[] { @@ -391,18 +391,18 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable }; // Act 1: Add messages - var invokedContext = new ChatMessageStore.InvokedContext(messages, []); + var invokedContext = new ChatHistoryProvider.InvokedContext(messages, []); await originalStore.InvokedAsync(invokedContext); // Act 2: Verify messages were added - var invokingContext = new ChatMessageStore.InvokingContext([]); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); var retrievedMessages = await originalStore.InvokingAsync(invokingContext); var retrievedList = retrievedMessages.ToList(); Assert.Equal(5, retrievedList.Count); - // Act 3: Create new store instance for same conversation (test persistence) - using var newStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); - var persistedMessages = await newStore.InvokingAsync(invokingContext); + // Act 3: Create new provider instance for same conversation (test persistence) + using var newProvider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversationId); + var persistedMessages = await newProvider.InvokingAsync(invokingContext); var persistedList = persistedMessages.ToList(); // Assert final state @@ -424,10 +424,10 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable { // Arrange this.SkipIfEmulatorNotAvailable(); - var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString()); + var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString()); // Act & Assert - store.Dispose(); // Should not throw + provider.Dispose(); // Should not throw } [SkippableFact] @@ -436,11 +436,11 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable { // Arrange this.SkipIfEmulatorNotAvailable(); - var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString()); + var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString()); // Act & Assert - store.Dispose(); // First call - store.Dispose(); // Second call - should not throw + provider.Dispose(); // First call + provider.Dispose(); // Second call - should not throw } #endregion @@ -455,13 +455,13 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); // Act - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789"); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789"); // Assert - Assert.NotNull(store); - Assert.Equal("session-789", store.ConversationId); - Assert.Equal(s_testDatabaseId, store.DatabaseId); - Assert.Equal(HierarchicalTestContainerId, store.ContainerId); + Assert.NotNull(provider); + Assert.Equal("session-789", provider.ConversationId); + Assert.Equal(s_testDatabaseId, provider.DatabaseId); + Assert.Equal(HierarchicalTestContainerId, provider.ContainerId); } [SkippableFact] @@ -473,13 +473,13 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable // Act TokenCredential credential = new DefaultAzureCredential(); - using var store = new CosmosChatMessageStore(EmulatorEndpoint, credential, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789"); + using var provider = new CosmosChatHistoryProvider(EmulatorEndpoint, credential, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789"); // Assert - Assert.NotNull(store); - Assert.Equal("session-789", store.ConversationId); - Assert.Equal(s_testDatabaseId, store.DatabaseId); - Assert.Equal(HierarchicalTestContainerId, store.ContainerId); + Assert.NotNull(provider); + Assert.Equal("session-789", provider.ConversationId); + Assert.Equal(s_testDatabaseId, provider.DatabaseId); + Assert.Equal(HierarchicalTestContainerId, provider.ContainerId); } [SkippableFact] @@ -490,13 +490,13 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); using var cosmosClient = new CosmosClient(EmulatorEndpoint, EmulatorKey); - using var store = new CosmosChatMessageStore(cosmosClient, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789"); + using var provider = new CosmosChatHistoryProvider(cosmosClient, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789"); // Assert - Assert.NotNull(store); - Assert.Equal("session-789", store.ConversationId); - Assert.Equal(s_testDatabaseId, store.DatabaseId); - Assert.Equal(HierarchicalTestContainerId, store.ContainerId); + Assert.NotNull(provider); + Assert.Equal("session-789", provider.ConversationId); + Assert.Equal(s_testDatabaseId, provider.DatabaseId); + Assert.Equal(HierarchicalTestContainerId, provider.ContainerId); } [SkippableFact] @@ -507,7 +507,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); Assert.Throws(() => - new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, null!, "user-456", "session-789")); + new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, null!, "user-456", "session-789")); } [SkippableFact] @@ -518,7 +518,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); Assert.Throws(() => - new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "", "session-789")); + new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "", "session-789")); } [SkippableFact] @@ -529,7 +529,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); Assert.Throws(() => - new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", " ")); + new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", " ")); } [SkippableFact] @@ -542,20 +542,20 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable const string UserId = "user-456"; const string SessionId = "session-789"; // Test hierarchical partitioning constructor with connection string - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId); var message = new ChatMessage(ChatRole.User, "Hello from hierarchical partitioning!"); - var context = new ChatMessageStore.InvokedContext([message], []); + var context = new ChatHistoryProvider.InvokedContext([message], []); // Act - await store.InvokedAsync(context); + await provider.InvokedAsync(context); // Wait a moment for eventual consistency await Task.Delay(100); // Assert - var invokingContext = new ChatMessageStore.InvokingContext([]); - var messages = await store.InvokingAsync(invokingContext); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); + var messages = await provider.InvokingAsync(invokingContext); var messageList = messages.ToList(); Assert.Single(messageList); @@ -594,7 +594,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable const string UserId = "user-batch"; const string SessionId = "session-batch"; // Test hierarchical partitioning constructor with connection string - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId); var messages = new[] { new ChatMessage(ChatRole.User, "First hierarchical message"), @@ -602,17 +602,17 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable new ChatMessage(ChatRole.User, "Third hierarchical message") }; - var context = new ChatMessageStore.InvokedContext(messages, []); + var context = new ChatHistoryProvider.InvokedContext(messages, []); // Act - await store.InvokedAsync(context); + await provider.InvokedAsync(context); // Wait a moment for eventual consistency await Task.Delay(100); // Assert - var invokingContext = new ChatMessageStore.InvokingContext([]); - var retrievedMessages = await store.InvokingAsync(invokingContext); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); + var retrievedMessages = await provider.InvokingAsync(invokingContext); var messageList = retrievedMessages.ToList(); Assert.Equal(3, messageList.Count); @@ -633,12 +633,12 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable const string SessionId = "session-isolation"; // Different userIds create different hierarchical partitions, providing proper isolation - using var store1 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId1, SessionId); - using var store2 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId2, SessionId); + using var store1 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId1, SessionId); + using var store2 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId2, SessionId); // Add messages to both stores - var context1 = new ChatMessageStore.InvokedContext([new ChatMessage(ChatRole.User, "Message from user 1")], []); - var context2 = new ChatMessageStore.InvokedContext([new ChatMessage(ChatRole.User, "Message from user 2")], []); + var context1 = new ChatHistoryProvider.InvokedContext([new ChatMessage(ChatRole.User, "Message from user 1")], []); + var context2 = new ChatHistoryProvider.InvokedContext([new ChatMessage(ChatRole.User, "Message from user 2")], []); await store1.InvokedAsync(context1); await store2.InvokedAsync(context2); @@ -647,8 +647,8 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable await Task.Delay(100); // Act & Assert - var invokingContext1 = new ChatMessageStore.InvokingContext([]); - var invokingContext2 = new ChatMessageStore.InvokingContext([]); + var invokingContext1 = new ChatHistoryProvider.InvokingContext([]); + var invokingContext2 = new ChatHistoryProvider.InvokingContext([]); var messages1 = await store1.InvokingAsync(invokingContext1); var messageList1 = messages1.ToList(); @@ -673,27 +673,27 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable const string UserId = "user-serialize"; const string SessionId = "session-serialize"; - using var originalStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId); + using var originalStore = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId); - var context = new ChatMessageStore.InvokedContext([new ChatMessage(ChatRole.User, "Test serialization message")], []); + var context = new ChatHistoryProvider.InvokedContext([new ChatMessage(ChatRole.User, "Test serialization message")], []); await originalStore.InvokedAsync(context); - // Act - Serialize the store state + // Act - Serialize the provider state var serializedState = originalStore.Serialize(); - // Create a new store from the serialized state + // Create a new provider from the serialized state using var cosmosClient = new CosmosClient(EmulatorEndpoint, EmulatorKey); var serializerOptions = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; - using var deserializedStore = CosmosChatMessageStore.CreateFromSerializedState(cosmosClient, serializedState, s_testDatabaseId, HierarchicalTestContainerId, serializerOptions); + using var deserializedStore = CosmosChatHistoryProvider.CreateFromSerializedState(cosmosClient, serializedState, s_testDatabaseId, HierarchicalTestContainerId, serializerOptions); // Wait a moment for eventual consistency await Task.Delay(100); - // Assert - The deserialized store should have the same functionality - var invokingContext = new ChatMessageStore.InvokingContext([]); + // Assert - The deserialized provider should have the same functionality + var invokingContext = new ChatHistoryProvider.InvokingContext([]); var messages = await deserializedStore.InvokingAsync(invokingContext); var messageList = messages.ToList(); @@ -712,27 +712,27 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); const string SessionId = "coexist-session"; - // Create simple store using simple partitioning container and hierarchical store using hierarchical container - using var simpleStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, SessionId); - using var hierarchicalStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-coexist", "user-coexist", SessionId); + // Create simple provider using simple partitioning container and hierarchical provider using hierarchical container + using var simpleProvider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, SessionId); + using var hierarchicalProvider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-coexist", "user-coexist", SessionId); // Add messages to both - var simpleContext = new ChatMessageStore.InvokedContext([new ChatMessage(ChatRole.User, "Simple partitioning message")], []); - var hierarchicalContext = new ChatMessageStore.InvokedContext([new ChatMessage(ChatRole.User, "Hierarchical partitioning message")], []); + var simpleContext = new ChatHistoryProvider.InvokedContext([new ChatMessage(ChatRole.User, "Simple partitioning message")], []); + var hierarchicalContext = new ChatHistoryProvider.InvokedContext([new ChatMessage(ChatRole.User, "Hierarchical partitioning message")], []); - await simpleStore.InvokedAsync(simpleContext); - await hierarchicalStore.InvokedAsync(hierarchicalContext); + await simpleProvider.InvokedAsync(simpleContext); + await hierarchicalProvider.InvokedAsync(hierarchicalContext); // Wait a moment for eventual consistency await Task.Delay(100); // Act & Assert - var invokingContext = new ChatMessageStore.InvokingContext([]); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); - var simpleMessages = await simpleStore.InvokingAsync(invokingContext); + var simpleMessages = await simpleProvider.InvokingAsync(invokingContext); var simpleMessageList = simpleMessages.ToList(); - var hierarchicalMessages = await hierarchicalStore.InvokingAsync(invokingContext); + var hierarchicalMessages = await hierarchicalProvider.InvokingAsync(invokingContext); var hierarchicalMessageList = hierarchicalMessages.ToList(); // Each should only see its own messages since they use different containers @@ -750,7 +750,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); const string ConversationId = "max-messages-test"; - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, ConversationId); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, ConversationId); // Add 10 messages var messages = new List(); @@ -760,16 +760,16 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable await Task.Delay(10); // Small delay to ensure different timestamps } - var context = new ChatMessageStore.InvokedContext(messages, []); - await store.InvokedAsync(context); + var context = new ChatHistoryProvider.InvokedContext(messages, []); + await provider.InvokedAsync(context); // Wait for eventual consistency await Task.Delay(100); // Act - Set max to 5 and retrieve - store.MaxMessagesToRetrieve = 5; - var invokingContext = new ChatMessageStore.InvokingContext([]); - var retrievedMessages = await store.InvokingAsync(invokingContext); + provider.MaxMessagesToRetrieve = 5; + var invokingContext = new ChatHistoryProvider.InvokingContext([]); + var retrievedMessages = await provider.InvokingAsync(invokingContext); var messageList = retrievedMessages.ToList(); // Assert - Should get the 5 most recent messages (6-10) in ascending order @@ -789,7 +789,7 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable this.SkipIfEmulatorNotAvailable(); const string ConversationId = "max-messages-null-test"; - using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, ConversationId); + using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, ConversationId); // Add 10 messages var messages = new List(); @@ -798,15 +798,15 @@ public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable messages.Add(new ChatMessage(ChatRole.User, $"Message {i}")); } - var context = new ChatMessageStore.InvokedContext(messages, []); - await store.InvokedAsync(context); + var context = new ChatHistoryProvider.InvokedContext(messages, []); + await provider.InvokedAsync(context); // Wait for eventual consistency await Task.Delay(100); // Act - No limit set (default null) - var invokingContext = new ChatMessageStore.InvokingContext([]); - var retrievedMessages = await store.InvokingAsync(invokingContext); + var invokingContext = new ChatHistoryProvider.InvokingContext([]); + var retrievedMessages = await provider.InvokingAsync(invokingContext); var messageList = retrievedMessages.ToList(); // Assert - Should get all 10 messages diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs index 896a4ceba5..8502550d2c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs @@ -23,7 +23,7 @@ public class ChatClientAgentOptionsTests Assert.Null(options.Name); Assert.Null(options.Description); Assert.Null(options.ChatOptions); - Assert.Null(options.ChatMessageStoreFactory); + Assert.Null(options.ChatHistoryProviderFactory); Assert.Null(options.AIContextProviderFactory); } @@ -37,7 +37,7 @@ public class ChatClientAgentOptionsTests Assert.Null(options.Name); Assert.Null(options.Description); Assert.Null(options.AIContextProviderFactory); - Assert.Null(options.ChatMessageStoreFactory); + Assert.Null(options.ChatHistoryProviderFactory); Assert.NotNull(options.ChatOptions); Assert.Null(options.ChatOptions.Instructions); Assert.Null(options.ChatOptions.Tools); @@ -117,8 +117,8 @@ public class ChatClientAgentOptionsTests const string Description = "Test description"; var tools = new List { AIFunctionFactory.Create(() => "test") }; - static ValueTask ChatMessageStoreFactoryAsync( - ChatClientAgentOptions.ChatMessageStoreFactoryContext ctx, CancellationToken ct) => new(new Mock().Object); + static ValueTask ChatHistoryProviderFactoryAsync( + ChatClientAgentOptions.ChatHistoryProviderFactoryContext ctx, CancellationToken ct) => new(new Mock().Object); static ValueTask AIContextProviderFactoryAsync( ChatClientAgentOptions.AIContextProviderFactoryContext ctx, CancellationToken ct) => new(new Mock().Object); @@ -129,7 +129,7 @@ public class ChatClientAgentOptionsTests Description = Description, ChatOptions = new() { Tools = tools }, Id = "test-id", - ChatMessageStoreFactory = ChatMessageStoreFactoryAsync, + ChatHistoryProviderFactory = ChatHistoryProviderFactoryAsync, AIContextProviderFactory = AIContextProviderFactoryAsync }; @@ -141,7 +141,7 @@ public class ChatClientAgentOptionsTests Assert.Equal(original.Id, clone.Id); Assert.Equal(original.Name, clone.Name); Assert.Equal(original.Description, clone.Description); - Assert.Same(original.ChatMessageStoreFactory, clone.ChatMessageStoreFactory); + Assert.Same(original.ChatHistoryProviderFactory, clone.ChatHistoryProviderFactory); Assert.Same(original.AIContextProviderFactory, clone.AIContextProviderFactory); // ChatOptions should be cloned, not the same reference @@ -170,7 +170,7 @@ public class ChatClientAgentOptionsTests Assert.Equal(original.Name, clone.Name); Assert.Equal(original.Description, clone.Description); Assert.Null(original.ChatOptions); - Assert.Null(clone.ChatMessageStoreFactory); + Assert.Null(clone.ChatHistoryProviderFactory); Assert.Null(clone.AIContextProviderFactory); } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs index fbafe5fcf2..7395b22ebf 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs @@ -222,10 +222,10 @@ public partial class ChatClientAgentTests } /// - /// Verify that RunAsync works with existing thread and can retreive messages if the thread has a MessageStore. + /// Verify that RunAsync works with existing thread and can retreive messages if the thread has a ChatHistoryProvider. /// [Fact] - public async Task RunAsyncRetrievesMessagesFromThreadWhenThreadStoresMessagesThreadAsync() + public async Task RunAsyncRetrievesMessagesFromThreadWhenThreadHasChatHistoryProviderAsync() { // Arrange Mock mockService = new(); @@ -372,11 +372,11 @@ public partial class ChatClientAgentTests Assert.Contains(capturedTools, t => t.Name == "context provider function"); // Verify that the thread was updated with the ai context provider, input and response messages - var messageStore = Assert.IsType(thread!.MessageStore); - Assert.Equal(3, messageStore.Count); - Assert.Equal("user message", messageStore[0].Text); - Assert.Equal("context provider message", messageStore[1].Text); - Assert.Equal("response", messageStore[2].Text); + var chatHistoryProvider = Assert.IsType(thread!.ChatHistoryProvider); + Assert.Equal(3, chatHistoryProvider.Count); + Assert.Equal("user message", chatHistoryProvider[0].Text); + Assert.Equal("context provider message", chatHistoryProvider[1].Text); + Assert.Equal("response", chatHistoryProvider[2].Text); mockProvider.Verify(p => p.InvokingAsync(It.IsAny(), It.IsAny()), Times.Once); mockProvider.Verify(p => p.InvokedAsync(It.Is(x => @@ -1270,10 +1270,10 @@ public partial class ChatClientAgentTests } /// - /// Verify that RunStreamingAsync uses the ChatMessageStore factory when the chat client returns no conversation id. + /// Verify that RunStreamingAsync uses the ChatHistoryProvider factory when the chat client returns no conversation id. /// [Fact] - public async Task RunStreamingAsyncUsesChatMessageStoreWhenNoConversationIdReturnedByChatClientAsync() + public async Task RunStreamingAsyncUsesChatHistoryProviderWhenNoConversationIdReturnedByChatClientAsync() { // Arrange Mock mockService = new(); @@ -1287,12 +1287,12 @@ public partial class ChatClientAgentTests It.IsAny>(), It.IsAny(), It.IsAny())).Returns(ToAsyncEnumerableAsync(returnUpdates)); - Mock>> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatMessageStore()); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatHistoryProvider()); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, - ChatMessageStoreFactory = mockFactory.Object + ChatHistoryProviderFactory = mockFactory.Object }); // Act @@ -1300,18 +1300,18 @@ public partial class ChatClientAgentTests await agent.RunStreamingAsync([new(ChatRole.User, "test")], thread).ToListAsync(); // Assert - var messageStore = Assert.IsType(thread!.MessageStore); - Assert.Equal(2, messageStore.Count); - Assert.Equal("test", messageStore[0].Text); - Assert.Equal("what?", messageStore[1].Text); - mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); + var chatHistoryProvider = Assert.IsType(thread!.ChatHistoryProvider); + Assert.Equal(2, chatHistoryProvider.Count); + Assert.Equal("test", chatHistoryProvider[0].Text); + Assert.Equal("what?", chatHistoryProvider[1].Text); + mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); } /// - /// Verify that RunStreamingAsync throws when a ChatMessageStore factory is provided and the chat client returns a conversation id. + /// Verify that RunStreamingAsync throws when a factory is provided and the chat client returns a conversation id. /// [Fact] - public async Task RunStreamingAsyncThrowsWhenChatMessageStoreFactoryProvidedAndConversationIdReturnedByChatClientAsync() + public async Task RunStreamingAsyncThrowsWhenChatHistoryProviderFactoryProvidedAndConversationIdReturnedByChatClientAsync() { // Arrange Mock mockService = new(); @@ -1325,18 +1325,18 @@ public partial class ChatClientAgentTests It.IsAny>(), It.IsAny(), It.IsAny())).Returns(ToAsyncEnumerableAsync(returnUpdates)); - Mock>> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatMessageStore()); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatHistoryProvider()); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, - ChatMessageStoreFactory = mockFactory.Object + ChatHistoryProviderFactory = mockFactory.Object }); // Act & Assert ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; var exception = await Assert.ThrowsAsync(async () => await agent.RunStreamingAsync([new(ChatRole.User, "test")], thread).ToListAsync()); - Assert.Equal("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.", exception.Message); + Assert.Equal("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported.", exception.Message); } /// @@ -1408,11 +1408,11 @@ public partial class ChatClientAgentTests Assert.Contains(capturedTools, t => t.Name == "context provider function"); // Verify that the thread was updated with the input, ai context provider, and response messages - var messageStore = Assert.IsType(thread!.MessageStore); - Assert.Equal(3, messageStore.Count); - Assert.Equal("user message", messageStore[0].Text); - Assert.Equal("context provider message", messageStore[1].Text); - Assert.Equal("response", messageStore[2].Text); + var chatHistoryProvider = Assert.IsType(thread!.ChatHistoryProvider); + Assert.Equal(3, chatHistoryProvider.Count); + Assert.Equal("user message", chatHistoryProvider[0].Text); + Assert.Equal("context provider message", chatHistoryProvider[1].Text); + Assert.Equal("response", chatHistoryProvider[2].Text); mockProvider.Verify(p => p.InvokingAsync(It.IsAny(), It.IsAny()), Times.Once); mockProvider.Verify(p => p.InvokedAsync(It.Is(x => diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs index 57af3b6449..ef5eb19c37 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs @@ -24,7 +24,7 @@ public class ChatClientAgentThreadTests // Assert Assert.Null(thread.ConversationId); - Assert.Null(thread.MessageStore); + Assert.Null(thread.ChatHistoryProvider); } [Fact] @@ -39,52 +39,52 @@ public class ChatClientAgentThreadTests // Assert Assert.Equal(ConversationId, thread.ConversationId); - Assert.Null(thread.MessageStore); + Assert.Null(thread.ChatHistoryProvider); } [Fact] - public void SetChatMessageStoreRoundtrips() + public void SetChatHistoryProviderRoundtrips() { // Arrange var thread = new ChatClientAgentThread(); - var messageStore = new InMemoryChatMessageStore(); + var chatHistoryProvider = new InMemoryChatHistoryProvider(); // Act - thread.MessageStore = messageStore; + thread.ChatHistoryProvider = chatHistoryProvider; // Assert - Assert.Same(messageStore, thread.MessageStore); + Assert.Same(chatHistoryProvider, thread.ChatHistoryProvider); Assert.Null(thread.ConversationId); } [Fact] - public void SetConversationIdThrowsWhenMessageStoreIsSet() + public void SetConversationIdThrowsWhenChatHistoryProviderIsSet() { // Arrange var thread = new ChatClientAgentThread { - MessageStore = new InMemoryChatMessageStore() + ChatHistoryProvider = new InMemoryChatHistoryProvider() }; // Act & Assert var exception = Assert.Throws(() => thread.ConversationId = "new-thread-id"); - Assert.Equal("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.", exception.Message); - Assert.NotNull(thread.MessageStore); + Assert.Equal("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported.", exception.Message); + Assert.NotNull(thread.ChatHistoryProvider); } [Fact] - public void SetChatMessageStoreThrowsWhenConversationIdIsSet() + public void SetChatHistoryProviderThrowsWhenConversationIdIsSet() { // Arrange var thread = new ChatClientAgentThread { ConversationId = "existing-thread-id" }; - var store = new InMemoryChatMessageStore(); + var provider = new InMemoryChatHistoryProvider(); // Act & Assert - var exception = Assert.Throws(() => thread.MessageStore = store); - Assert.Equal("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.", exception.Message); + var exception = Assert.Throws(() => thread.ChatHistoryProvider = provider); + Assert.Equal("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported.", exception.Message); Assert.NotNull(thread.ConversationId); } @@ -98,7 +98,7 @@ public class ChatClientAgentThreadTests // Arrange var json = JsonSerializer.Deserialize(""" { - "storeState": { "messages": [{"authorName": "testAuthor"}] } + "chatHistoryProviderState": { "messages": [{"authorName": "testAuthor"}] } } """, TestJsonSerializerContext.Default.JsonElement); @@ -108,10 +108,10 @@ public class ChatClientAgentThreadTests // Assert Assert.Null(thread.ConversationId); - var messageStore = thread.MessageStore as InMemoryChatMessageStore; - Assert.NotNull(messageStore); - Assert.Single(messageStore); - Assert.Equal("testAuthor", messageStore[0].AuthorName); + var chatHistoryProvider = thread.ChatHistoryProvider as InMemoryChatHistoryProvider; + Assert.NotNull(chatHistoryProvider); + Assert.Single(chatHistoryProvider); + Assert.Equal("testAuthor", chatHistoryProvider[0].AuthorName); } [Fact] @@ -129,7 +129,7 @@ public class ChatClientAgentThreadTests // Assert Assert.Equal("TestConvId", thread.ConversationId); - Assert.Null(thread.MessageStore); + Assert.Null(thread.ChatHistoryProvider); } [Fact] @@ -148,7 +148,7 @@ public class ChatClientAgentThreadTests var thread = await ChatClientAgentThread.DeserializeAsync(json, aiContextProviderFactory: (_, _, _) => new(mockProvider.Object)); // Assert - Assert.Null(thread.MessageStore); + Assert.Null(thread.ChatHistoryProvider); Assert.Same(thread.AIContextProvider, mockProvider.Object); } @@ -185,7 +185,7 @@ public class ChatClientAgentThreadTests Assert.True(json.TryGetProperty("conversationId", out var idProperty)); Assert.Equal("TestConvId", idProperty.GetString()); - Assert.False(json.TryGetProperty("storeState", out _)); + Assert.False(json.TryGetProperty("chatHistoryProviderState", out _)); } /// @@ -195,8 +195,8 @@ public class ChatClientAgentThreadTests public void VerifyThreadSerializationWithMessages() { // Arrange - InMemoryChatMessageStore store = [new(ChatRole.User, "TestContent") { AuthorName = "TestAuthor" }]; - var thread = new ChatClientAgentThread { MessageStore = store }; + InMemoryChatHistoryProvider provider = [new(ChatRole.User, "TestContent") { AuthorName = "TestAuthor" }]; + var thread = new ChatClientAgentThread { ChatHistoryProvider = provider }; // Act var json = thread.Serialize(); @@ -206,10 +206,10 @@ public class ChatClientAgentThreadTests Assert.False(json.TryGetProperty("conversationId", out _)); - Assert.True(json.TryGetProperty("storeState", out var storeStateProperty)); - Assert.Equal(JsonValueKind.Object, storeStateProperty.ValueKind); + Assert.True(json.TryGetProperty("chatHistoryProviderState", out var chatHistoryProviderStateProperty)); + Assert.Equal(JsonValueKind.Object, chatHistoryProviderStateProperty.ValueKind); - Assert.True(storeStateProperty.TryGetProperty("messages", out var messagesProperty)); + Assert.True(chatHistoryProviderStateProperty.TryGetProperty("messages", out var messagesProperty)); Assert.Equal(JsonValueKind.Array, messagesProperty.ValueKind); Assert.Single(messagesProperty.EnumerateArray()); @@ -260,15 +260,15 @@ public class ChatClientAgentThreadTests JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!); - var storeStateElement = JsonSerializer.SerializeToElement( + var chatHistoryProviderStateElement = JsonSerializer.SerializeToElement( new Dictionary { ["Key"] = "TestValue" }, TestJsonSerializerContext.Default.DictionaryStringObject); - var messageStoreMock = new Mock(); - messageStoreMock + var chatHistoryProviderMock = new Mock(); + chatHistoryProviderMock .Setup(m => m.Serialize(options)) - .Returns(storeStateElement); - thread.MessageStore = messageStoreMock.Object; + .Returns(chatHistoryProviderStateElement); + thread.ChatHistoryProvider = chatHistoryProviderMock.Object; // Act var json = thread.Serialize(options); @@ -278,13 +278,13 @@ public class ChatClientAgentThreadTests Assert.False(json.TryGetProperty("conversationId", out var idProperty)); - Assert.True(json.TryGetProperty("storeState", out var storeStateProperty)); - Assert.Equal(JsonValueKind.Object, storeStateProperty.ValueKind); + Assert.True(json.TryGetProperty("chatHistoryProviderState", out var chatHistoryProviderStateProperty)); + Assert.Equal(JsonValueKind.Object, chatHistoryProviderStateProperty.ValueKind); - Assert.True(storeStateProperty.TryGetProperty("Key", out var keyProperty)); + Assert.True(chatHistoryProviderStateProperty.TryGetProperty("Key", out var keyProperty)); Assert.Equal("TestValue", keyProperty.GetString()); - messageStoreMock.Verify(m => m.Serialize(options), Times.Once); + chatHistoryProviderMock.Verify(m => m.Serialize(options), Times.Once); } #endregion Serialize Tests @@ -311,19 +311,19 @@ public class ChatClientAgentThreadTests } [Fact] - public void GetService_RequestingChatMessageStore_ReturnsChatMessageStore() + public void GetService_RequestingChatHistoryProvider_ReturnsChatHistoryProvider() { // Arrange var thread = new ChatClientAgentThread(); - var messageStore = new InMemoryChatMessageStore(); - thread.MessageStore = messageStore; + var chatHistoryProvider = new InMemoryChatHistoryProvider(); + thread.ChatHistoryProvider = chatHistoryProvider; // Act - var result = thread.GetService(typeof(ChatMessageStore)); + var result = thread.GetService(typeof(ChatHistoryProvider)); // Assert Assert.NotNull(result); - Assert.Same(messageStore, result); + Assert.Same(chatHistoryProvider, result); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_BackgroundResponsesTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_BackgroundResponsesTests.cs index 79af3add1d..018da2b6db 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_BackgroundResponsesTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_BackgroundResponsesTests.cs @@ -336,11 +336,11 @@ public class ChatClientAgent_BackgroundResponsesTests // Arrange List capturedMessages = []; - // Create a mock message store that would normally provide messages - var mockMessageStore = new Mock(); - mockMessageStore - .Setup(ms => ms.InvokingAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync([new(ChatRole.User, "Message from message store")]); + // Create a mock chat history provider that would normally provide messages + var mockChatHistoryProvider = new Mock(); + mockChatHistoryProvider + .Setup(ms => ms.InvokingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync([new(ChatRole.User, "Message from chat history provider")]); // Create a mock AI context provider that would normally provide context var mockContextProvider = new Mock(); @@ -364,10 +364,10 @@ public class ChatClientAgent_BackgroundResponsesTests ChatClientAgent agent = new(mockChatClient.Object); - // Create a thread with both message store and AI context provider + // Create a thread with both chat history provider and AI context provider ChatClientAgentThread thread = new() { - MessageStore = mockMessageStore.Object, + ChatHistoryProvider = mockChatHistoryProvider.Object, AIContextProvider = mockContextProvider.Object }; @@ -384,9 +384,9 @@ public class ChatClientAgent_BackgroundResponsesTests // With continuation token, thread message population should be skipped Assert.Empty(capturedMessages); - // Verify that message store was never called due to continuation token - mockMessageStore.Verify( - ms => ms.InvokingAsync(It.IsAny(), It.IsAny()), + // Verify that chat history provider was never called due to continuation token + mockChatHistoryProvider.Verify( + ms => ms.InvokingAsync(It.IsAny(), It.IsAny()), Times.Never); // Verify that AI context provider was never called due to continuation token @@ -401,11 +401,11 @@ public class ChatClientAgent_BackgroundResponsesTests // Arrange List capturedMessages = []; - // Create a mock message store that would normally provide messages - var mockMessageStore = new Mock(); - mockMessageStore - .Setup(ms => ms.InvokingAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync([new(ChatRole.User, "Message from message store")]); + // Create a mock chat history provider that would normally provide messages + var mockChatHistoryProvider = new Mock(); + mockChatHistoryProvider + .Setup(ms => ms.InvokingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync([new(ChatRole.User, "Message from chat history provider")]); // Create a mock AI context provider that would normally provide context var mockContextProvider = new Mock(); @@ -429,10 +429,10 @@ public class ChatClientAgent_BackgroundResponsesTests ChatClientAgent agent = new(mockChatClient.Object); - // Create a thread with both message store and AI context provider + // Create a thread with both chat history provider and AI context provider ChatClientAgentThread thread = new() { - MessageStore = mockMessageStore.Object, + ChatHistoryProvider = mockChatHistoryProvider.Object, AIContextProvider = mockContextProvider.Object }; @@ -448,9 +448,9 @@ public class ChatClientAgent_BackgroundResponsesTests // With continuation token, thread message population should be skipped Assert.Empty(capturedMessages); - // Verify that message store was never called due to continuation token - mockMessageStore.Verify( - ms => ms.InvokingAsync(It.IsAny(), It.IsAny()), + // Verify that chat history provider was never called due to continuation token + mockChatHistoryProvider.Verify( + ms => ms.InvokingAsync(It.IsAny(), It.IsAny()), Times.Never); // Verify that AI context provider was never called due to continuation token @@ -610,7 +610,7 @@ public class ChatClientAgent_BackgroundResponsesTests } [Fact] - public async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitialRunForContextProviderAndMessageStoreAsync() + public async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitialRunForContextProviderAndChatHistoryProviderAsync() { // Arrange ChatResponseUpdate[] returnUpdates = @@ -630,11 +630,11 @@ public class ChatClientAgent_BackgroundResponsesTests ChatClientAgent agent = new(mockChatClient.Object); - List capturedMessagesAddedToStore = []; - var mockMessageStore = new Mock(); - mockMessageStore - .Setup(ms => ms.InvokedAsync(It.IsAny(), It.IsAny())) - .Callback((ctx, ct) => capturedMessagesAddedToStore.AddRange(ctx.ResponseMessages ?? [])) + List capturedMessagesAddedToProvider = []; + var mockChatHistoryProvider = new Mock(); + mockChatHistoryProvider + .Setup(ms => ms.InvokedAsync(It.IsAny(), It.IsAny())) + .Callback((ctx, ct) => capturedMessagesAddedToProvider.AddRange(ctx.ResponseMessages ?? [])) .Returns(new ValueTask()); AIContextProvider.InvokedContext? capturedInvokedContext = null; @@ -646,7 +646,7 @@ public class ChatClientAgent_BackgroundResponsesTests ChatClientAgentThread thread = new() { - MessageStore = mockMessageStore.Object, + ChatHistoryProvider = mockChatHistoryProvider.Object, AIContextProvider = mockContextProvider.Object }; @@ -662,9 +662,9 @@ public class ChatClientAgent_BackgroundResponsesTests await agent.RunStreamingAsync(thread, options: runOptions).ToListAsync(); // Assert - mockMessageStore.Verify(ms => ms.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); - Assert.Single(capturedMessagesAddedToStore); - Assert.Contains("once upon a time", capturedMessagesAddedToStore[0].Text); + mockChatHistoryProvider.Verify(ms => ms.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); + Assert.Single(capturedMessagesAddedToProvider); + Assert.Contains("once upon a time", capturedMessagesAddedToProvider[0].Text); mockContextProvider.Verify(cp => cp.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); Assert.NotNull(capturedInvokedContext?.ResponseMessages); @@ -673,7 +673,7 @@ public class ChatClientAgent_BackgroundResponsesTests } [Fact] - public async Task RunStreamingAsync_WhenResumingStreaming_UsesInputMessagesFromInitialRunForContextProviderAndMessageStoreAsync() + public async Task RunStreamingAsync_WhenResumingStreaming_UsesInputMessagesFromInitialRunForContextProviderAndChatHistoryProviderAsync() { // Arrange Mock mockChatClient = new(); @@ -686,11 +686,11 @@ public class ChatClientAgent_BackgroundResponsesTests ChatClientAgent agent = new(mockChatClient.Object); - List capturedMessagesAddedToStore = []; - var mockMessageStore = new Mock(); - mockMessageStore - .Setup(ms => ms.InvokedAsync(It.IsAny(), It.IsAny())) - .Callback((ctx, ct) => capturedMessagesAddedToStore.AddRange(ctx.RequestMessages)) + List capturedMessagesAddedToProvider = []; + var mockChatHistoryProvider = new Mock(); + mockChatHistoryProvider + .Setup(ms => ms.InvokedAsync(It.IsAny(), It.IsAny())) + .Callback((ctx, ct) => capturedMessagesAddedToProvider.AddRange(ctx.RequestMessages)) .Returns(new ValueTask()); AIContextProvider.InvokedContext? capturedInvokedContext = null; @@ -702,7 +702,7 @@ public class ChatClientAgent_BackgroundResponsesTests ChatClientAgentThread thread = new() { - MessageStore = mockMessageStore.Object, + ChatHistoryProvider = mockChatHistoryProvider.Object, AIContextProvider = mockContextProvider.Object }; @@ -718,9 +718,9 @@ public class ChatClientAgent_BackgroundResponsesTests await agent.RunStreamingAsync(thread, options: runOptions).ToListAsync(); // Assert - mockMessageStore.Verify(ms => ms.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); - Assert.Single(capturedMessagesAddedToStore); - Assert.Contains("Tell me a story", capturedMessagesAddedToStore[0].Text); + mockChatHistoryProvider.Verify(ms => ms.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); + Assert.Single(capturedMessagesAddedToProvider); + Assert.Contains("Tell me a story", capturedMessagesAddedToProvider[0].Text); mockContextProvider.Verify(cp => cp.InvokedAsync(It.IsAny(), It.IsAny()), Times.Once); Assert.NotNull(capturedInvokedContext?.RequestMessages); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs index 96edee3dac..3e995b3cee 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.Agents.AI.UnitTests; /// /// Contains unit tests that verify the chat history management functionality of the class, -/// e.g. that it correctly reads and updates chat history in any available or that +/// e.g. that it correctly reads and updates chat history in any available or that /// it uses conversation id correctly for service managed chat history. /// public class ChatClientAgent_ChatHistoryManagementTests @@ -137,13 +137,13 @@ public class ChatClientAgent_ChatHistoryManagementTests #endregion - #region ChatMessageStore Tests + #region ChatHistoryProvider Tests /// - /// Verify that RunAsync uses the default InMemoryChatMessageStore when the chat client returns no conversation id. + /// Verify that RunAsync uses the default InMemoryChatHistoryProvider when the chat client returns no conversation id. /// [Fact] - public async Task RunAsync_UsesDefaultInMemoryChatMessageStore_WhenNoConversationIdReturnedByChatClientAsync() + public async Task RunAsync_UsesDefaultInMemoryChatHistoryProvider_WhenNoConversationIdReturnedByChatClientAsync() { // Arrange Mock mockService = new(); @@ -162,17 +162,17 @@ public class ChatClientAgent_ChatHistoryManagementTests await agent.RunAsync([new(ChatRole.User, "test")], thread); // Assert - var messageStore = Assert.IsType(thread!.MessageStore); - Assert.Equal(2, messageStore.Count); - Assert.Equal("test", messageStore[0].Text); - Assert.Equal("response", messageStore[1].Text); + InMemoryChatHistoryProvider chatHistoryProvider = Assert.IsType(thread!.ChatHistoryProvider); + Assert.Equal(2, chatHistoryProvider.Count); + Assert.Equal("test", chatHistoryProvider[0].Text); + Assert.Equal("response", chatHistoryProvider[1].Text); } /// - /// Verify that RunAsync uses the ChatMessageStore factory when the chat client returns no conversation id. + /// Verify that RunAsync uses the ChatHistoryProvider factory when the chat client returns no conversation id. /// [Fact] - public async Task RunAsync_UsesChatMessageStoreFactory_WhenProvidedAndNoConversationIdReturnedByChatClientAsync() + public async Task RunAsync_UsesChatHistoryProviderFactory_WhenProvidedAndNoConversationIdReturnedByChatClientAsync() { // Arrange Mock mockService = new(); @@ -182,21 +182,21 @@ public class ChatClientAgent_ChatHistoryManagementTests It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - Mock mockChatMessageStore = new(); - mockChatMessageStore.Setup(s => s.InvokingAsync( - It.IsAny(), + Mock mockChatHistoryProvider = new(); + mockChatHistoryProvider.Setup(s => s.InvokingAsync( + It.IsAny(), It.IsAny())).ReturnsAsync([new ChatMessage(ChatRole.User, "Existing Chat History")]); - mockChatMessageStore.Setup(s => s.InvokedAsync( - It.IsAny(), + mockChatHistoryProvider.Setup(s => s.InvokedAsync( + It.IsAny(), It.IsAny())).Returns(new ValueTask()); - Mock>> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockChatMessageStore.Object); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockChatHistoryProvider.Object); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, - ChatMessageStoreFactory = mockFactory.Object + ChatHistoryProviderFactory = mockFactory.Object }); // Act @@ -204,29 +204,29 @@ public class ChatClientAgent_ChatHistoryManagementTests await agent.RunAsync([new(ChatRole.User, "test")], thread); // Assert - Assert.IsType(thread!.MessageStore, exactMatch: false); + Assert.IsType(thread!.ChatHistoryProvider, exactMatch: false); mockService.Verify( x => x.GetResponseAsync( It.Is>(msgs => msgs.Count() == 2 && msgs.Any(m => m.Text == "Existing Chat History") && msgs.Any(m => m.Text == "test")), It.IsAny(), It.IsAny()), Times.Once); - mockChatMessageStore.Verify(s => s.InvokingAsync( - It.Is(x => x.RequestMessages.Count() == 1), + mockChatHistoryProvider.Verify(s => s.InvokingAsync( + It.Is(x => x.RequestMessages.Count() == 1), It.IsAny()), Times.Once); - mockChatMessageStore.Verify(s => s.InvokedAsync( - It.Is(x => x.RequestMessages.Count() == 1 && x.ChatMessageStoreMessages != null && x.ChatMessageStoreMessages.Count() == 1 && x.ResponseMessages!.Count() == 1), + mockChatHistoryProvider.Verify(s => s.InvokedAsync( + It.Is(x => x.RequestMessages.Count() == 1 && x.ChatHistoryProviderMessages != null && x.ChatHistoryProviderMessages.Count() == 1 && x.ResponseMessages!.Count() == 1), It.IsAny()), Times.Once); - mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); + mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); } /// - /// Verify that RunAsync notifies the ChatMessageStore on failure. + /// Verify that RunAsync notifies the ChatHistoryProvider on failure. /// [Fact] - public async Task RunAsync_NotifiesChatMessageStore_OnFailureAsync() + public async Task RunAsync_NotifiesChatHistoryProvider_OnFailureAsync() { // Arrange Mock mockService = new(); @@ -236,15 +236,15 @@ public class ChatClientAgent_ChatHistoryManagementTests It.IsAny(), It.IsAny())).Throws(new InvalidOperationException("Test Error")); - Mock mockChatMessageStore = new(); + Mock mockChatHistoryProvider = new(); - Mock>> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockChatMessageStore.Object); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockChatHistoryProvider.Object); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, - ChatMessageStoreFactory = mockFactory.Object + ChatHistoryProviderFactory = mockFactory.Object }); // Act @@ -252,19 +252,19 @@ public class ChatClientAgent_ChatHistoryManagementTests await Assert.ThrowsAsync(() => agent.RunAsync([new(ChatRole.User, "test")], thread)); // Assert - Assert.IsType(thread!.MessageStore, exactMatch: false); - mockChatMessageStore.Verify(s => s.InvokedAsync( - It.Is(x => x.RequestMessages.Count() == 1 && x.ResponseMessages == null && x.InvokeException!.Message == "Test Error"), + Assert.IsType(thread!.ChatHistoryProvider, exactMatch: false); + mockChatHistoryProvider.Verify(s => s.InvokedAsync( + It.Is(x => x.RequestMessages.Count() == 1 && x.ResponseMessages == null && x.InvokeException!.Message == "Test Error"), It.IsAny()), Times.Once); - mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); + mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); } /// - /// Verify that RunAsync throws when a ChatMessageStore Factory is provided and the chat client returns a conversation id. + /// Verify that RunAsync throws when a ChatHistoryProvider Factory is provided and the chat client returns a conversation id. /// [Fact] - public async Task RunAsync_Throws_WhenChatMessageStoreFactoryProvidedAndConversationIdReturnedByChatClientAsync() + public async Task RunAsync_Throws_WhenChatHistoryProviderFactoryProvidedAndConversationIdReturnedByChatClientAsync() { // Arrange Mock mockService = new(); @@ -273,30 +273,30 @@ public class ChatClientAgent_ChatHistoryManagementTests It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" }); - Mock>> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatMessageStore()); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatHistoryProvider()); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, - ChatMessageStoreFactory = mockFactory.Object + ChatHistoryProviderFactory = mockFactory.Object }); // Act & Assert ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; - var exception = await Assert.ThrowsAsync(() => agent.RunAsync([new(ChatRole.User, "test")], thread)); - Assert.Equal("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.", exception.Message); + InvalidOperationException exception = await Assert.ThrowsAsync(() => agent.RunAsync([new(ChatRole.User, "test")], thread)); + Assert.Equal("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported.", exception.Message); } #endregion - #region ChatMessageStore Override Tests + #region ChatHistoryProvider Override Tests /// - /// Tests that RunAsync uses an override ChatMessageStore provided via AdditionalProperties instead of the store from a factory + /// Tests that RunAsync uses an override ChatHistoryProvider provided via AdditionalProperties instead of the provider from a factory /// if one is supplied. /// [Fact] - public async Task RunAsync_UsesOverrideChatMessageStore_WhenProvidedViaAdditionalPropertiesAsync() + public async Task RunAsync_UsesOverrideChatHistoryProvider_WhenProvidedViaAdditionalPropertiesAsync() { // Arrange Mock mockService = new(); @@ -306,63 +306,63 @@ public class ChatClientAgent_ChatHistoryManagementTests It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - // Arrange a chat message store to override the factory provided one. - Mock mockOverrideChatMessageStore = new(); - mockOverrideChatMessageStore.Setup(s => s.InvokingAsync( - It.IsAny(), + // Arrange a chat history provider to override the factory provided one. + Mock mockOverrideChatHistoryProvider = new(); + mockOverrideChatHistoryProvider.Setup(s => s.InvokingAsync( + It.IsAny(), It.IsAny())).ReturnsAsync([new ChatMessage(ChatRole.User, "Existing Chat History")]); - mockOverrideChatMessageStore.Setup(s => s.InvokedAsync( - It.IsAny(), + mockOverrideChatHistoryProvider.Setup(s => s.InvokedAsync( + It.IsAny(), It.IsAny())).Returns(new ValueTask()); - // Arrange a chat message store to provide to the agent via a factory at construction time. + // Arrange a chat history provider to provide to the agent via a factory at construction time. // This one shouldn't be used since it is being overridden. - Mock mockFactoryChatMessageStore = new(); - mockFactoryChatMessageStore.Setup(s => s.InvokingAsync( - It.IsAny(), - It.IsAny())).ThrowsAsync(FailException.ForFailure("Base ChatMessageStore shouldn't be used.")); - mockFactoryChatMessageStore.Setup(s => s.InvokedAsync( - It.IsAny(), - It.IsAny())).Throws(FailException.ForFailure("Base ChatMessageStore shouldn't be used.")); + Mock mockFactoryChatHistoryProvider = new(); + mockFactoryChatHistoryProvider.Setup(s => s.InvokingAsync( + It.IsAny(), + It.IsAny())).ThrowsAsync(FailException.ForFailure("Base ChatHistoryProvider shouldn't be used.")); + mockFactoryChatHistoryProvider.Setup(s => s.InvokedAsync( + It.IsAny(), + It.IsAny())).Throws(FailException.ForFailure("Base ChatHistoryProvider shouldn't be used.")); - Mock>> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockFactoryChatMessageStore.Object); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockFactoryChatHistoryProvider.Object); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, - ChatMessageStoreFactory = mockFactory.Object + ChatHistoryProviderFactory = mockFactory.Object }); // Act ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; - var additionalProperties = new AdditionalPropertiesDictionary(); - additionalProperties.Add(mockOverrideChatMessageStore.Object); + AdditionalPropertiesDictionary additionalProperties = new(); + additionalProperties.Add(mockOverrideChatHistoryProvider.Object); await agent.RunAsync([new(ChatRole.User, "test")], thread, options: new AgentRunOptions { AdditionalProperties = additionalProperties }); // Assert - Assert.Same(mockFactoryChatMessageStore.Object, thread!.MessageStore); + Assert.Same(mockFactoryChatHistoryProvider.Object, thread!.ChatHistoryProvider); mockService.Verify( x => x.GetResponseAsync( It.Is>(msgs => msgs.Count() == 2 && msgs.Any(m => m.Text == "Existing Chat History") && msgs.Any(m => m.Text == "test")), It.IsAny(), It.IsAny()), Times.Once); - mockOverrideChatMessageStore.Verify(s => s.InvokingAsync( - It.Is(x => x.RequestMessages.Count() == 1), + mockOverrideChatHistoryProvider.Verify(s => s.InvokingAsync( + It.Is(x => x.RequestMessages.Count() == 1), It.IsAny()), Times.Once); - mockOverrideChatMessageStore.Verify(s => s.InvokedAsync( - It.Is(x => x.RequestMessages.Count() == 1 && x.ChatMessageStoreMessages != null && x.ChatMessageStoreMessages.Count() == 1 && x.ResponseMessages!.Count() == 1), + mockOverrideChatHistoryProvider.Verify(s => s.InvokedAsync( + It.Is(x => x.RequestMessages.Count() == 1 && x.ChatHistoryProviderMessages != null && x.ChatHistoryProviderMessages.Count() == 1 && x.ResponseMessages!.Count() == 1), It.IsAny()), Times.Once); - mockFactoryChatMessageStore.Verify(s => s.InvokingAsync( - It.IsAny(), + mockFactoryChatHistoryProvider.Verify(s => s.InvokingAsync( + It.IsAny(), It.IsAny()), Times.Never); - mockFactoryChatMessageStore.Verify(s => s.InvokedAsync( - It.IsAny(), + mockFactoryChatHistoryProvider.Verify(s => s.InvokedAsync( + It.IsAny(), It.IsAny()), Times.Never); } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs index 97ce6b92f4..6b9a4c89e2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs @@ -46,25 +46,25 @@ public class ChatClientAgent_DeserializeThreadTests } [Fact] - public async Task DeserializeThread_UsesChatMessageStoreFactory_IfProvidedAsync() + public async Task DeserializeThread_UsesChatHistoryProviderFactory_IfProvidedAsync() { // Arrange var mockChatClient = new Mock(); - var mockMessageStore = new Mock(); + var mockChatHistoryProvider = new Mock(); var factoryCalled = false; var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { ChatOptions = new() { Instructions = "Test instructions" }, - ChatMessageStoreFactory = (_, _) => + ChatHistoryProviderFactory = (_, _) => { factoryCalled = true; - return new ValueTask(mockMessageStore.Object); + return new ValueTask(mockChatHistoryProvider.Object); } }); var json = JsonSerializer.Deserialize(""" { - "storeState": { } + "chatHistoryProviderState": { } } """, TestJsonSerializerContext.Default.JsonElement); @@ -72,9 +72,9 @@ public class ChatClientAgent_DeserializeThreadTests var thread = await agent.DeserializeThreadAsync(json); // Assert - Assert.True(factoryCalled, "ChatMessageStoreFactory was not called."); + Assert.True(factoryCalled, "ChatHistoryProviderFactory was not called."); Assert.IsType(thread); var typedThread = (ChatClientAgentThread)thread; - Assert.Same(mockMessageStore.Object, typedThread.MessageStore); + Assert.Same(mockChatHistoryProvider.Object, typedThread.ChatHistoryProvider); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs index 0cd49ce1eb..b582465a4d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs @@ -39,19 +39,19 @@ public class ChatClientAgent_GetNewThreadTests } [Fact] - public async Task GetNewThread_UsesChatMessageStoreFactory_IfProvidedAsync() + public async Task GetNewThread_UsesChatHistoryProviderFactory_IfProvidedAsync() { // Arrange var mockChatClient = new Mock(); - var mockMessageStore = new Mock(); + var mockChatHistoryProvider = new Mock(); var factoryCalled = false; var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { ChatOptions = new() { Instructions = "Test instructions" }, - ChatMessageStoreFactory = (_, _) => + ChatHistoryProviderFactory = (_, _) => { factoryCalled = true; - return new ValueTask(mockMessageStore.Object); + return new ValueTask(mockChatHistoryProvider.Object); } }); @@ -59,27 +59,27 @@ public class ChatClientAgent_GetNewThreadTests var thread = await agent.GetNewThreadAsync(); // Assert - Assert.True(factoryCalled, "ChatMessageStoreFactory was not called."); + Assert.True(factoryCalled, "ChatHistoryProviderFactory was not called."); Assert.IsType(thread); var typedThread = (ChatClientAgentThread)thread; - Assert.Same(mockMessageStore.Object, typedThread.MessageStore); + Assert.Same(mockChatHistoryProvider.Object, typedThread.ChatHistoryProvider); } [Fact] - public async Task GetNewThread_UsesChatMessageStore_FromTypedOverloadAsync() + public async Task GetNewThread_UsesChatHistoryProvider_FromTypedOverloadAsync() { // Arrange var mockChatClient = new Mock(); - var mockMessageStore = new Mock(); + var mockChatHistoryProvider = new Mock(); var agent = new ChatClientAgent(mockChatClient.Object); // Act - var thread = await agent.GetNewThreadAsync(mockMessageStore.Object); + var thread = await agent.GetNewThreadAsync(mockChatHistoryProvider.Object); // Assert Assert.IsType(thread); var typedThread = (ChatClientAgentThread)thread; - Assert.Same(mockMessageStore.Object, typedThread.MessageStore); + Assert.Same(mockChatHistoryProvider.Object, typedThread.ChatHistoryProvider); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs index b971736b74..cf63843def 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs @@ -26,7 +26,7 @@ internal class TestEchoAgent(string? id = null, string? name = null, string? pre private static ChatMessage UpdateThread(ChatMessage message, InMemoryAgentThread? thread = null) { - thread?.MessageStore.Add(message); + thread?.ChatHistoryProvider.Add(message); return message; } diff --git a/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs b/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs index 0fb9745d2d..f085187a21 100644 --- a/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs +++ b/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs @@ -32,12 +32,12 @@ public class OpenAIChatCompletionFixture : IChatClientAgentFixture { var typedThread = (ChatClientAgentThread)thread; - if (typedThread.MessageStore is null) + if (typedThread.ChatHistoryProvider is null) { return []; } - return (await typedThread.MessageStore.InvokingAsync(new([]))).ToList(); + return (await typedThread.ChatHistoryProvider.InvokingAsync(new([]))).ToList(); } public Task CreateChatClientAgentAsync( diff --git a/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs b/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs index c57e1c460d..9e8db6fb21 100644 --- a/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs +++ b/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs @@ -50,12 +50,12 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture return [.. previousMessages, responseMessage]; } - if (typedThread.MessageStore is null) + if (typedThread.ChatHistoryProvider is null) { return []; } - return (await typedThread.MessageStore.InvokingAsync(new([]))).ToList(); + return (await typedThread.ChatHistoryProvider.InvokingAsync(new([]))).ToList(); } private static ChatMessage ConvertToChatMessage(ResponseItem item)