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 @@ protected override async Task RunCoreAsync(IEnumerable 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 @@ protected override async IAsyncEnumerable RunCoreStreamingA } // 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 @@ // 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 @@ { 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 @@ // 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 @@ { 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 @@ You are a helpful personal assistant. 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 @@ private static JsonSerializerOptions CreateDefaultOptions() [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 virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOption /// 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 @@ 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 sealed class InvokedContext /// 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 InvokedContext(IEnumerable requestMessages, IEnumerable /// /// 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 @@ 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 ChatMessageStoreMessageFilter( /// 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 override ValueTask InvokedAsync(InvokedContext context, CancellationToken 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 @@ 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 @@ protected InMemoryAgentThread(InMemoryChatMessageStore? messageStore = null) /// protected InMemoryAgentThread(IEnumerable messages) { - this.MessageStore = [.. messages]; + this.ChatHistoryProvider = [.. messages]; } /// @@ -60,9 +60,9 @@ protected InMemoryAgentThread(IEnumerable messages) /// /// 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 @@ protected InMemoryAgentThread(IEnumerable messages) protected InMemoryAgentThread( JsonElement serializedThreadState, JsonSerializerOptions? jsonSerializerOptions = null, - Func? messageStoreFactory = null) + Func? chatHistoryProviderFactory = null) { if (serializedThreadState.ValueKind != JsonValueKind.Object) { @@ -83,15 +83,15 @@ protected InMemoryAgentThread( 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 @@ protected InMemoryAgentThread( /// 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 override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptio /// 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 @@ 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 InMemoryChatMessageStore(JsonElement serializedStoreState, JsonSerializer /// Message reducers enable automatic management of message storage by implementing strategies to /// keep memory usage under control while preserving important conversation context. /// - 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 InMemoryChatMessageStore(IChatReducer? chatReducer, JsonElement serialize public IChatReducer? ChatReducer { get; } /// - /// 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 override async ValueTask InvokedAsync(InvokedContext context, Cancellatio return; } - // Add request, AI context provider, and response messages to the store + // Add request, AI context provider, and response messages to the provider var allNewMessages = context.RequestMessages.Concat(context.AIContextProviderMessages ?? []).Concat(context.ResponseMessages ?? []); this._messages.AddRange(allNewMessages); @@ -169,13 +168,13 @@ public override async ValueTask InvokedAsync(InvokedContext context, Cancellatio /// 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 IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => 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 enum ChatReducerTriggerEvent AfterMessageAdded, /// - /// 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 ChatClientAgent AsAIAgent( 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 @@ private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion 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 @@ 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 @@ private static JsonSerializerOptions CreateDefaultJsonOptions() 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 @@ private static JsonSerializerOptions CreateDefaultJsonOptions() 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 @@ private static JsonSerializerOptions CreateDefaultJsonOptions() /// 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 @@ internal CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, st } /// - /// 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 CosmosChatMessageStore(string connectionString, string databaseId, string /// 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 CosmosChatMessageStore(string connectionString, string databaseId, string /// 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 CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCrede /// 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 CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, stri /// 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 CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, stri /// 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 CosmosChatMessageStore(string connectionString, string databaseId, string /// 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 CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCrede /// 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 override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptio } #pragma warning restore CA1513 - var state = new StoreState + var state = new State { ConversationIdentifier = this.ConversationId, TenantId = this._tenantId, @@ -632,7 +632,7 @@ public void Dispose() } } - 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 ChatClientAgentOptions WithCosmosDBMessageStore( 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 ChatClientAgentOptions WithCosmosDBMessageStore( /// 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 ChatClientAgentOptions WithCosmosDBMessageStoreUsingManagedIdentit 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 ChatClientAgentOptions WithCosmosDBMessageStoreUsingManagedIdentit /// 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 ChatClientAgentOptions WithCosmosDBMessageStore( 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 ChatClientAgent AsAIAgent( 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 @@ 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 @@ private async ValueTask UpdateThreadAsync(IEnumerable InvokeStageAsync( 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 @@ IAsyncEnumerable InvokeStageAsync( 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 @@ IAsyncEnumerable InvokeStageAsync( 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 @@ private static JsonSerializerOptions CreateDefaultOptions() [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 ChatClientAgent(IChatClient chatClient, string? instructions = null, stri /// 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 @@ protected override async IAsyncEnumerable RunCoreStreamingA 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 @@ protected override async IAsyncEnumerable RunCoreStreamingA } 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 @@ protected override async IAsyncEnumerable RunCoreStreamingA } 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 @@ protected override async IAsyncEnumerable RunCoreStreamingA } 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 @@ protected override async IAsyncEnumerable RunCoreStreamingA 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 @@ protected override async IAsyncEnumerable RunCoreStreamingA /// 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 override async ValueTask GetNewThreadAsync(CancellationToken return new ChatClientAgentThread { - MessageStore = messageStore, + ChatHistoryProvider = chatHistoryProvider, AIContextProvider = contextProvider }; } @@ -329,8 +329,8 @@ public override async ValueTask GetNewThreadAsync(CancellationToken /// /// /// - /// 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 async ValueTask GetNewThreadAsync(string conversationId, Can } /// - /// 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 async ValueTask GetNewThreadAsync(ChatMessageStore chatMessa return new ChatClientAgentThread() { - MessageStore = Throw.IfNull(chatMessageStore), + ChatHistoryProvider = Throw.IfNull(chatHistoryProvider), AIContextProvider = contextProvider }; } @@ -388,9 +388,9 @@ public async ValueTask GetNewThreadAsync(ChatMessageStore chatMessa /// 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 override async ValueTask DeserializeThreadAsync(JsonElement return await ChatClientAgentThread.DeserializeAsync( serializedThread, jsonSerializerOptions, - chatMessageStoreFactory, + chatHistoryProviderFactory, aiContextProviderFactory, cancellationToken).ConfigureAwait(false); } @@ -422,7 +422,7 @@ private async Task RunCoreAsync inputMessagesForChatClient, IList? aiContextProviderMessages, - IList? chatMessageStoreMessages, + IList? chatHistoryProviderMessages, ChatClientAgentContinuationToken? _) = await this.PrepareThreadAndMessagesAsync(thread, inputMessages, options, cancellationToken).ConfigureAwait(false); @@ -442,7 +442,7 @@ private async Task RunCoreAsync RunCoreAsync InputMessagesForChatClient, IList? AIContextProviderMessages, - IList? ChatMessageStoreMessages, + IList? ChatHistoryProviderMessages, ChatClientAgentContinuationToken? ContinuationToken )> PrepareThreadAndMessagesAsync( AgentThread? thread, @@ -703,20 +703,20 @@ private async Task 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 @@ private async Task 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 @@ private async Task UpdateThreadWithTypeAndConversationIdAsync(ChatClientAgentThr 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 ChatClientAgentOptions Clone() 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 AIContextProviderFactoryContext } /// - /// 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 @@ internal ChatClientAgentThread() /// /// /// - /// 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 @@ internal ChatClientAgentThread() /// 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 @@ internal set 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 @@ internal set } /// - /// 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 @@ internal set /// /// 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 @@ internal set 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 @@ internal static async Task DeserializeAsync( { 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 @@ chatMessageStoreFactory is not null /// 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 override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptio 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 async Task> GetChatHistoryAsync(AgentThread thread) { 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 async Task> GetChatHistoryAsync(AgentThread thread) 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 @@ 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 async Task InvokingAsync_WithNoOpFilters_ReturnsInnerStoreMessagesAsync() 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 async Task InvokingAsync_WithInvokingFilter_AppliesFilterAsync() // 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 async Task InvokingAsync_WithInvokingFilter_CanModifyMessagesAsync() 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 @@ IEnumerable InvokingFilter(IEnumerable msgs) => 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 @@ ChatMessageStore.InvokedContext InvokedFilter(ChatMessageStore.InvokedContext ct }; } - 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 @@ ChatMessageStore.InvokedContext InvokedFilter(ChatMessageStore.InvokedContext ct 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 @@ 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 void Constructor_WithMessages_SetsProperty() 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 void Serialize_ReturnsCorrectJson_WhenMessagesExist() // 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 void Serialize_ReturnsEmptyMessages_WhenNoMessages() // 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 void Serialize_ReturnsEmptyMessages_WhenNoMessages() #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 void GetService_RequestingChatMessageStore_ReturnsChatMessageStore() 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 @@ 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 void Constructor_Arguments_SetOnPropertiesCorrectly() { // 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 async Task InvokedAsyncAddsMessagesAsync() { new(ChatRole.Assistant, "Hi there!") }; - var messageStoreMessages = new List() + var providerMessages = new List() { new(ChatRole.System, "original instructions") }; @@ -66,44 +66,44 @@ public async Task InvokedAsyncAddsMessagesAsync() 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 async Task DeserializeConstructorWithEmptyElementAsync() { 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 async Task SerializeAndDeserializeConstructorRoundtripsWithCustomAIConten }; 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 async Task DeserializeContructor_WithEmptyMessages_DoesNotAddMessagesAsyn 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 async Task DeserializeConstructor_WithNullMessages_DoesNotAddMessagesAsyn 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 async Task DeserializeConstructor_WithValidMessages_AddsMessagesAsync() 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 void CopyTo_CopiesMessagesToArray() 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 void GetEnumerator_Generic_ReturnsAllMessages() 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 async Task AddMessagesAsync_WithReducer_AfterMessageAdded_InvokesReducerA .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 async Task GetMessagesAsync_WithReducer_BeforeMessagesRetrieval_InvokesRe .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 async Task AddMessagesAsync_WithReducer_ButWrongTrigger_DoesNotInvokeRedu 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 async Task GetMessagesAsync_WithReducer_ButWrongTrigger_DoesNotInvokeRedu 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 @@ 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 void Constructor_WithConnectionString_ShouldCreateInstance() 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 void Constructor_WithConnectionStringNoConversationId_ShouldCreateInstanc 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 void Constructor_WithNullConnectionString_ShouldThrowArgumentException() { // 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 void Constructor_WithEmptyConversationId_ShouldThrowArgumentException() this.SkipIfEmulatorNotAvailable(); Assert.Throws(() => - new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, "")); + new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, "")); } #endregion @@ -211,23 +211,23 @@ public async Task InvokedAsync_WithSingleMessage_ShouldAddMessageAsync() // 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 async Task InvokedAsync_WithMultipleMessages_ShouldAddAllMessagesAsync() // 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 async Task InvokedAsync_WithMultipleMessages_ShouldAddAllMessagesAsync() 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 async Task InvokingAsync_WithNoMessages_ShouldReturnEmptyAsync() { // 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 async Task InvokingAsync_WithConversationIsolation_ShouldOnlyReturnMessag 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 async Task FullWorkflow_AddAndGet_ShouldWorkCorrectlyAsync() // 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 async Task FullWorkflow_AddAndGet_ShouldWorkCorrectlyAsync() }; // 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 void Dispose_AfterUse_ShouldNotThrow() { // 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 void Dispose_MultipleCalls_ShouldNotThrow() { // 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 void Constructor_WithHierarchicalConnectionString_ShouldCreateInstance() 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 void Constructor_WithHierarchicalEndpoint_ShouldCreateInstance() // 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 void Constructor_WithHierarchicalCosmosClient_ShouldCreateInstance() 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 void Constructor_WithHierarchicalNullTenantId_ShouldThrowArgumentExceptio 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 void Constructor_WithHierarchicalEmptyUserId_ShouldThrowArgumentException 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 void Constructor_WithHierarchicalWhitespaceSessionId_ShouldThrowArgumentE 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 async Task InvokedAsync_WithHierarchicalPartitioning_ShouldAddMessageWith 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 async Task InvokedAsync_WithHierarchicalMultipleMessages_ShouldAddAllMess 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 async Task InvokedAsync_WithHierarchicalMultipleMessages_ShouldAddAllMess 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 async Task InvokingAsync_WithHierarchicalPartitionIsolation_ShouldIsolate 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 async Task InvokingAsync_WithHierarchicalPartitionIsolation_ShouldIsolate 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 async Task SerializeDeserialize_WithHierarchicalPartitioning_ShouldPreser 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 async Task HierarchicalAndSimplePartitioning_ShouldCoexistAsync() 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 async Task MaxMessagesToRetrieve_ShouldLimitAndReturnMostRecentAsync() 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 async Task MaxMessagesToRetrieve_ShouldLimitAndReturnMostRecentAsync() 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 async Task MaxMessagesToRetrieve_Null_ShouldReturnAllMessagesAsync() 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 async Task MaxMessagesToRetrieve_Null_ShouldReturnAllMessagesAsync() 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 void DefaultConstructor_InitializesWithNullValues() 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 void Constructor_WithNullValues_SetsPropertiesCorrectly() 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 void Clone_CreatesDeepCopyWithSameValues() 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 @@ static ValueTask AIContextProviderFactoryAsync( Description = Description, ChatOptions = new() { Tools = tools }, Id = "test-id", - ChatMessageStoreFactory = ChatMessageStoreFactoryAsync, + ChatHistoryProviderFactory = ChatHistoryProviderFactoryAsync, AIContextProviderFactory = AIContextProviderFactoryAsync }; @@ -141,7 +141,7 @@ static ValueTask AIContextProviderFactoryAsync( 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 void Clone_WithoutProvidingChatOptions_ClonesCorrectly() 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 async Task RunAsyncSetsAuthorNameOnAllResponseMessagesAsync(string? autho } /// - /// 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 async Task RunAsyncInvokesAIContextProviderAndUsesResultAsync() 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 async Task VerifyChatClientAgentStreamingAsync() } /// - /// 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 async Task RunStreamingAsyncUsesChatMessageStoreWhenNoConversationIdRetur 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 async Task RunStreamingAsyncUsesChatMessageStoreWhenNoConversationIdRetur 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 async Task RunStreamingAsyncThrowsWhenChatMessageStoreFactoryProvidedAndC 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 async Task RunStreamingAsyncInvokesAIContextProviderAndUsesResultAsync() 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 void ConstructorSetsDefaults() // Assert Assert.Null(thread.ConversationId); - Assert.Null(thread.MessageStore); + Assert.Null(thread.ChatHistoryProvider); } [Fact] @@ -39,52 +39,52 @@ public void SetConversationIdRoundtrips() // 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 async Task VerifyDeserializeWithMessagesAsync() // Arrange var json = JsonSerializer.Deserialize(""" { - "storeState": { "messages": [{"authorName": "testAuthor"}] } + "chatHistoryProviderState": { "messages": [{"authorName": "testAuthor"}] } } """, TestJsonSerializerContext.Default.JsonElement); @@ -108,10 +108,10 @@ public async Task VerifyDeserializeWithMessagesAsync() // 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 async Task VerifyDeserializeWithIdAsync() // Assert Assert.Equal("TestConvId", thread.ConversationId); - Assert.Null(thread.MessageStore); + Assert.Null(thread.ChatHistoryProvider); } [Fact] @@ -148,7 +148,7 @@ public async Task VerifyDeserializeWithAIContextProviderAsync() 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 void VerifyThreadSerializationWithId() 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 void VerifyThreadSerializationWithId() 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 void VerifyThreadSerializationWithMessages() 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 void VerifyThreadSerializationWithCustomOptions() 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 void VerifyThreadSerializationWithCustomOptions() 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 void GetService_RequestingAIContextProvider_ReturnsAIContextProvider() } [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 async Task RunAsync_WhenContinuationTokenProvided_SkipsThreadMessagePopul // 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 async Task RunAsync_WhenContinuationTokenProvided_SkipsThreadMessagePopul 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 async Task RunAsync_WhenContinuationTokenProvided_SkipsThreadMessagePopul // 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 async Task RunStreamingAsync_WhenContinuationTokenProvided_SkipsThreadMes // 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 async Task RunStreamingAsync_WhenContinuationTokenProvided_SkipsThreadMes 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 async Task RunStreamingAsync_WhenContinuationTokenProvided_SkipsThreadMes // 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 async Task RunStreamingAsync_WhenResponseUpdatesPresentInContinuationToke } [Fact] - public async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitialRunForContextProviderAndMessageStoreAsync() + public async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitialRunForContextProviderAndChatHistoryProviderAsync() { // Arrange ChatResponseUpdate[] returnUpdates = @@ -630,11 +630,11 @@ public async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitial 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 async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitial ChatClientAgentThread thread = new() { - MessageStore = mockMessageStore.Object, + ChatHistoryProvider = mockChatHistoryProvider.Object, AIContextProvider = mockContextProvider.Object }; @@ -662,9 +662,9 @@ public async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitial 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 async Task RunStreamingAsync_WhenResumingStreaming_UsesUpdatesFromInitial } [Fact] - public async Task RunStreamingAsync_WhenResumingStreaming_UsesInputMessagesFromInitialRunForContextProviderAndMessageStoreAsync() + public async Task RunStreamingAsync_WhenResumingStreaming_UsesInputMessagesFromInitialRunForContextProviderAndChatHistoryProviderAsync() { // Arrange Mock mockChatClient = new(); @@ -686,11 +686,11 @@ public async Task RunStreamingAsync_WhenResumingStreaming_UsesInputMessagesFromI 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 async Task RunStreamingAsync_WhenResumingStreaming_UsesInputMessagesFromI ChatClientAgentThread thread = new() { - MessageStore = mockMessageStore.Object, + ChatHistoryProvider = mockChatHistoryProvider.Object, AIContextProvider = mockContextProvider.Object }; @@ -718,9 +718,9 @@ public async Task RunStreamingAsync_WhenResumingStreaming_UsesInputMessagesFromI 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 async Task RunAsync_SetsConversationIdOnThread_WhenReturnedByChatClientAs #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 async Task RunAsync_UsesDefaultInMemoryChatMessageStore_WhenNoConversatio 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 async Task RunAsync_UsesChatMessageStoreFactory_WhenProvidedAndNoConversa 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 async Task RunAsync_UsesChatMessageStoreFactory_WhenProvidedAndNoConversa 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 async Task RunAsync_NotifiesChatMessageStore_OnFailureAsync() 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 async Task RunAsync_NotifiesChatMessageStore_OnFailureAsync() 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 async Task RunAsync_Throws_WhenChatMessageStoreFactoryProvidedAndConversa 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 async Task RunAsync_UsesOverrideChatMessageStore_WhenProvidedViaAdditiona 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 async Task DeserializeThread_UsesAIContextProviderFactory_IfProvidedAsync } [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 async Task DeserializeThread_UsesChatMessageStoreFactory_IfProvidedAsync( 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 async Task GetNewThread_UsesAIContextProviderFactory_IfProvidedAsync() } [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 async Task GetNewThread_UsesChatMessageStoreFactory_IfProvidedAsync() 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 @@ public override ValueTask GetNewThreadAsync(CancellationToken cance 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 async Task> GetChatHistoryAsync(AgentThread thread) { 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 async Task> GetChatHistoryAsync(AgentThread thread) 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)