mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Rename ChatMessageStore to ChatHistoryProvider (#3375)
* Rename ChatMessageStore to ChatHistoryProvider * Fix merge issue * Fixed PR comments * Fix tests after property rename * Add unit tests and fix merge issues * Fix encoding
This commit is contained in:
@@ -82,7 +82,7 @@ public static partial class AgentAbstractionsJsonUtilities
|
||||
[JsonSerializable(typeof(AgentResponseUpdate[]))]
|
||||
[JsonSerializable(typeof(ServiceIdAgentThread.ServiceIdAgentThreadState))]
|
||||
[JsonSerializable(typeof(InMemoryAgentThread.InMemoryAgentThreadState))]
|
||||
[JsonSerializable(typeof(InMemoryChatMessageStore.StoreState))]
|
||||
[JsonSerializable(typeof(InMemoryChatHistoryProvider.State))]
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
private sealed partial class JsonContext : JsonSerializerContext;
|
||||
|
||||
@@ -68,7 +68,7 @@ public abstract class AgentThread
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serviceType"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the <see cref="AgentThread"/>,
|
||||
/// including itself or any services it might be wrapping. For example, to access a <see cref="ChatMessageStore"/> if available for the instance,
|
||||
/// including itself or any services it might be wrapping. For example, to access a <see cref="ChatHistoryProvider"/> if available for the instance,
|
||||
/// <see cref="GetService"/> may be used to request it.
|
||||
/// </remarks>
|
||||
public virtual object? GetService(Type serviceType, object? serviceKey = null)
|
||||
|
||||
+27
-23
@@ -11,11 +11,12 @@ using Microsoft.Shared.Diagnostics;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="ChatMessageStore"/> defines the contract for persistent storage of chat messages in agent conversations.
|
||||
/// <see cref="ChatHistoryProvider"/> defines the contract that an <see cref="AIAgent"/> 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.
|
||||
/// </para>
|
||||
@@ -28,11 +29,15 @@ namespace Microsoft.Agents.AI;
|
||||
/// <item><description>Supporting serialization for thread persistence and migration</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A <see cref="ChatHistoryProvider"/> is only relevant for scenarios where the underlying AI service that the agent is using
|
||||
/// does not use in-service chat history storage.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class ChatMessageStore
|
||||
public abstract class ChatHistoryProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the request context including the caller provided messages that will be used by the agent for this invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
@@ -56,14 +61,14 @@ public abstract class ChatMessageStore
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Each store instance should be associated with a single conversation thread to ensure proper message isolation
|
||||
/// Each <see cref="ChatHistoryProvider"/> instance should be associated with a single <see cref="AgentThread"/> to ensure proper message isolation
|
||||
/// and context management.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the invocation context including request messages, response messages, and any exception that occurred.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
@@ -71,7 +76,7 @@ public abstract class ChatMessageStore
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 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 <see cref="ChatHistoryProvider"/> is responsible for preserving message ordering and ensuring that subsequent calls to
|
||||
/// <see cref="InvokingAsync"/> return messages in the correct chronological order.
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -80,7 +85,6 @@ public abstract class ChatMessageStore
|
||||
/// <item><description>Validating message content and metadata</description></item>
|
||||
/// <item><description>Applying storage optimizations or compression</description></item>
|
||||
/// <item><description>Triggering background maintenance operations</description></item>
|
||||
/// <item><description>Updating indices or search capabilities</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -97,13 +101,13 @@ public abstract class ChatMessageStore
|
||||
/// <returns>A <see cref="JsonElement"/> representation of the object's state.</returns>
|
||||
public abstract JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null);
|
||||
|
||||
/// <summary>Asks the <see cref="ChatMessageStore"/> for an object of the specified type <paramref name="serviceType"/>.</summary>
|
||||
/// <summary>Asks the <see cref="ChatHistoryProvider"/> for an object of the specified type <paramref name="serviceType"/>.</summary>
|
||||
/// <param name="serviceType">The type of object being requested.</param>
|
||||
/// <param name="serviceKey">An optional key that can be used to help identify the target service.</param>
|
||||
/// <returns>The found object, otherwise <see langword="null"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serviceType"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the <see cref="ChatMessageStore"/>,
|
||||
/// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the <see cref="ChatHistoryProvider"/>,
|
||||
/// including itself or any services it might be wrapping.
|
||||
/// </remarks>
|
||||
public virtual object? GetService(Type serviceType, object? serviceKey = null)
|
||||
@@ -115,12 +119,12 @@ public abstract class ChatMessageStore
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>Asks the <see cref="ChatMessageStore"/> for an object of type <typeparamref name="TService"/>.</summary>
|
||||
/// <summary>Asks the <see cref="ChatHistoryProvider"/> for an object of type <typeparamref name="TService"/>.</summary>
|
||||
/// <typeparam name="TService">The type of the object to be retrieved.</typeparam>
|
||||
/// <param name="serviceKey">An optional key that can be used to help identify the target service.</param>
|
||||
/// <returns>The found object, otherwise <see langword="null"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the <see cref="ChatMessageStore"/>,
|
||||
/// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the <see cref="ChatHistoryProvider"/>,
|
||||
/// including itself or any services it might be wrapping.
|
||||
/// </remarks>
|
||||
public TService? GetService<TService>(object? serviceKey = null)
|
||||
@@ -130,9 +134,9 @@ public abstract class ChatMessageStore
|
||||
/// Contains the context information provided to <see cref="InvokingAsync(InvokingContext, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 <see cref="ChatHistoryProvider"/> can use this information to determine what messages should be provided
|
||||
/// for the invocation.
|
||||
/// </remarks>
|
||||
public sealed class InvokingContext
|
||||
{
|
||||
@@ -169,12 +173,12 @@ public abstract class ChatMessageStore
|
||||
/// Initializes a new instance of the <see cref="InvokedContext"/> class with the specified request messages.
|
||||
/// </summary>
|
||||
/// <param name="requestMessages">The caller provided messages that were used by the agent for this invocation.</param>
|
||||
/// <param name="chatMessageStoreMessages">The messages retrieved from the <see cref="ChatMessageStore"/> for this invocation.</param>
|
||||
/// <param name="chatHistoryProviderMessages">The messages retrieved from the <see cref="ChatHistoryProvider"/> for this invocation.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception>
|
||||
public InvokedContext(IEnumerable<ChatMessage> requestMessages, IEnumerable<ChatMessage>? chatMessageStoreMessages)
|
||||
public InvokedContext(IEnumerable<ChatMessage> requestMessages, IEnumerable<ChatMessage>? chatHistoryProviderMessages)
|
||||
{
|
||||
this.RequestMessages = Throw.IfNull(requestMessages);
|
||||
this.ChatMessageStoreMessages = chatMessageStoreMessages;
|
||||
this.ChatHistoryProviderMessages = chatHistoryProviderMessages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -182,18 +186,18 @@ public abstract class ChatMessageStore
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A collection of <see cref="ChatMessage"/> instances representing new messages that were provided by the caller.
|
||||
/// This does not include any <see cref="ChatMessageStore"/> supplied messages.
|
||||
/// This does not include any <see cref="ChatHistoryProvider"/> supplied messages.
|
||||
/// </value>
|
||||
public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the messages retrieved from the <see cref="ChatMessageStore"/> for this invocation, if any.
|
||||
/// Gets the messages retrieved from the <see cref="ChatHistoryProvider"/> for this invocation, if any.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A collection of <see cref="ChatMessage"/> instances that were retrieved from the <see cref="ChatMessageStore"/>,
|
||||
/// and were used by the agent as part of the invocation. May be null on the first run.
|
||||
/// A collection of <see cref="ChatMessage"/> instances that were retrieved from the <see cref="ChatHistoryProvider"/>,
|
||||
/// and were used by the agent as part of the invocation.
|
||||
/// </value>
|
||||
public IEnumerable<ChatMessage>? ChatMessageStoreMessages { get; set; }
|
||||
public IEnumerable<ChatMessage>? ChatHistoryProviderMessages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the messages provided by the <see cref="AIContextProvider"/> for this invocation, if any.
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="ChatHistoryProvider"/> class.
|
||||
/// </summary>
|
||||
public static class ChatHistoryProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds message filtering to an existing <see cref="ChatHistoryProvider"/>, so that messages passed to the <see cref="ChatHistoryProvider"/> and messages
|
||||
/// provided by the <see cref="ChatHistoryProvider"/> can be filtered, updated or replaced.
|
||||
/// </summary>
|
||||
/// <param name="provider">The <see cref="ChatHistoryProvider"/> to add the message filter to.</param>
|
||||
/// <param name="invokingMessagesFilter">An optional filter function to apply to messages produced by the <see cref="ChatHistoryProvider"/>. If null, no filter is applied at this
|
||||
/// stage.</param>
|
||||
/// <param name="invokedMessagesFilter">An optional filter function to apply to the invoked context messages before they are passed to the <see cref="ChatHistoryProvider"/>. If null, no
|
||||
/// filter is applied at this stage.</param>
|
||||
/// <returns>The <see cref="ChatHistoryProvider"/> with filtering applied.</returns>
|
||||
public static ChatHistoryProvider WithMessageFilters(
|
||||
this ChatHistoryProvider provider,
|
||||
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? invokingMessagesFilter = null,
|
||||
Func<ChatHistoryProvider.InvokedContext, ChatHistoryProvider.InvokedContext>? invokedMessagesFilter = null)
|
||||
{
|
||||
return new ChatHistoryProviderMessageFilter(
|
||||
innerProvider: provider,
|
||||
invokingMessagesFilter: invokingMessagesFilter,
|
||||
invokedMessagesFilter: invokedMessagesFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decorates the provided chat message <see cref="ChatHistoryProvider"/> so that it does not add
|
||||
/// messages produced by any <see cref="AIContextProvider"/> to chat history.
|
||||
/// </summary>
|
||||
/// <param name="provider">The <see cref="ChatHistoryProvider"/> to add the message filter to.</param>
|
||||
/// <returns>A new <see cref="ChatHistoryProvider"/> instance that filters out <see cref="AIContextProvider"/> messages so they do not get added.</returns>
|
||||
public static ChatHistoryProvider WithAIContextProviderMessageRemoval(this ChatHistoryProvider provider)
|
||||
{
|
||||
return new ChatHistoryProviderMessageFilter(
|
||||
innerProvider: provider,
|
||||
invokedMessagesFilter: (ctx) =>
|
||||
{
|
||||
ctx.AIContextProviderMessages = null;
|
||||
return ctx;
|
||||
});
|
||||
}
|
||||
}
|
||||
+17
-17
@@ -11,33 +11,33 @@ using Microsoft.Shared.Diagnostics;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ChatMessageStore"/> decorator that allows filtering the messages
|
||||
/// passed into and out of an inner <see cref="ChatMessageStore"/>.
|
||||
/// A <see cref="ChatHistoryProvider"/> decorator that allows filtering the messages
|
||||
/// passed into and out of an inner <see cref="ChatHistoryProvider"/>.
|
||||
/// </summary>
|
||||
public sealed class ChatMessageStoreMessageFilter : ChatMessageStore
|
||||
public sealed class ChatHistoryProviderMessageFilter : ChatHistoryProvider
|
||||
{
|
||||
private readonly ChatMessageStore _innerChatMessageStore;
|
||||
private readonly ChatHistoryProvider _innerProvider;
|
||||
private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? _invokingMessagesFilter;
|
||||
private readonly Func<InvokedContext, InvokedContext>? _invokedMessagesFilter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatMessageStoreMessageFilter"/> class.
|
||||
/// Initializes a new instance of the <see cref="ChatHistoryProviderMessageFilter"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>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 <see cref="ChatHistoryProvider"/> operates without
|
||||
/// additional filtering.</remarks>
|
||||
/// <param name="innerChatMessageStore">The underlying chat message store to be wrapped. Cannot be null.</param>
|
||||
/// <param name="invokingMessagesFilter">An optional filter function to apply to messages before they are invoked. If null, no filter is applied at this
|
||||
/// stage.</param>
|
||||
/// <param name="invokedMessagesFilter">An optional filter function to apply to the invocation context after messages have been invoked. If null, no
|
||||
/// <param name="innerProvider">The underlying <see cref="ChatHistoryProvider"/> to be wrapped. Cannot be null.</param>
|
||||
/// <param name="invokingMessagesFilter">An optional filter function to apply to messages provided by the <see cref="ChatHistoryProvider"/>
|
||||
/// before they are used by the agent. If null, no filter is applied at this stage.</param>
|
||||
/// <param name="invokedMessagesFilter">An optional filter function to apply to the invocation context after messages have been produced. If null, no
|
||||
/// filter is applied at this stage.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if innerChatMessageStore is null.</exception>
|
||||
public ChatMessageStoreMessageFilter(
|
||||
ChatMessageStore innerChatMessageStore,
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="innerProvider"/> is null.</exception>
|
||||
public ChatHistoryProviderMessageFilter(
|
||||
ChatHistoryProvider innerProvider,
|
||||
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? invokingMessagesFilter = null,
|
||||
Func<InvokedContext, InvokedContext>? invokedMessagesFilter = null)
|
||||
{
|
||||
this._innerChatMessageStore = Throw.IfNull(innerChatMessageStore);
|
||||
this._innerProvider = Throw.IfNull(innerProvider);
|
||||
|
||||
if (invokingMessagesFilter == null && invokedMessagesFilter == null)
|
||||
{
|
||||
@@ -51,7 +51,7 @@ public sealed class ChatMessageStoreMessageFilter : ChatMessageStore
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var messages = await this._innerChatMessageStore.InvokingAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
var messages = await this._innerProvider.InvokingAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
return this._invokingMessagesFilter != null ? this._invokingMessagesFilter(messages) : messages;
|
||||
}
|
||||
|
||||
@@ -63,12 +63,12 @@ public sealed class ChatMessageStoreMessageFilter : ChatMessageStore
|
||||
context = this._invokedMessagesFilter(context);
|
||||
}
|
||||
|
||||
return this._innerChatMessageStore.InvokedAsync(context, cancellationToken);
|
||||
return this._innerProvider.InvokedAsync(context, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
return this._innerChatMessageStore.Serialize(jsonSerializerOptions);
|
||||
return this._innerProvider.Serialize(jsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="ChatMessageStore"/> class.
|
||||
/// </summary>
|
||||
public static class ChatMessageStoreExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="store">The store to add the message filter to.</param>
|
||||
/// <param name="invokingMessagesFilter">An optional filter function to apply to messages produced by the store. If null, no filter is applied at this
|
||||
/// stage.</param>
|
||||
/// <param name="invokedMessagesFilter">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.</param>
|
||||
/// <returns>The <see cref="ChatMessageStore"/> with filtering applied.</returns>
|
||||
public static ChatMessageStore WithMessageFilters(
|
||||
this ChatMessageStore store,
|
||||
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? invokingMessagesFilter = null,
|
||||
Func<ChatMessageStore.InvokedContext, ChatMessageStore.InvokedContext>? invokedMessagesFilter = null)
|
||||
{
|
||||
return new ChatMessageStoreMessageFilter(
|
||||
innerChatMessageStore: store,
|
||||
invokingMessagesFilter: invokingMessagesFilter,
|
||||
invokedMessagesFilter: invokedMessagesFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decorates the provided chat message store so that it does not store messages produced by any <see cref="AIContextProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="store">The store to add the message filter to.</param>
|
||||
/// <returns>A new <see cref="ChatMessageStore"/> instance that filters out <see cref="AIContextProvider"/> messages so they do not get stored.</returns>
|
||||
public static ChatMessageStore WithAIContextProviderMessageRemoval(this ChatMessageStore store)
|
||||
{
|
||||
return new ChatMessageStoreMessageFilter(
|
||||
innerChatMessageStore: store,
|
||||
invokedMessagesFilter: (ctx) =>
|
||||
{
|
||||
ctx.AIContextProviderMessages = null;
|
||||
return ctx;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ using Microsoft.Extensions.AI;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an abstract base class for agent threads that maintain all conversation state in local memory.
|
||||
/// Provides an abstract base class for an <see cref="AgentThread"/> that maintain all chat history in local memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="InMemoryAgentThread"/> is designed for scenarios where conversation state should be stored locally
|
||||
/// <see cref="InMemoryAgentThread"/> 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.
|
||||
/// </para>
|
||||
@@ -28,17 +28,17 @@ public abstract class InMemoryAgentThread : AgentThread
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InMemoryAgentThread"/> class.
|
||||
/// </summary>
|
||||
/// <param name="messageStore">
|
||||
/// An optional <see cref="InMemoryChatMessageStore"/> instance to use for storing chat messages.
|
||||
/// If <see langword="null"/>, a new empty message store will be created.
|
||||
/// <param name="chatHistoryProvider">
|
||||
/// An optional <see cref="InMemoryChatHistoryProvider"/> instance to use for storing chat messages.
|
||||
/// If <see langword="null"/>, a new empty <see cref="InMemoryChatHistoryProvider"/> will be created.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// 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 <see cref="ChatHistoryProvider"/> between threads or providing pre-configured
|
||||
/// <see cref="ChatHistoryProvider"/> with specific reduction or processing logic.
|
||||
/// </remarks>
|
||||
protected InMemoryAgentThread(InMemoryChatMessageStore? messageStore = null)
|
||||
protected InMemoryAgentThread(InMemoryChatHistoryProvider? chatHistoryProvider = null)
|
||||
{
|
||||
this.MessageStore = messageStore ?? [];
|
||||
this.ChatHistoryProvider = chatHistoryProvider ?? [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,7 +52,7 @@ public abstract class InMemoryAgentThread : AgentThread
|
||||
/// </remarks>
|
||||
protected InMemoryAgentThread(IEnumerable<ChatMessage> messages)
|
||||
{
|
||||
this.MessageStore = [.. messages];
|
||||
this.ChatHistoryProvider = [.. messages];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,9 +60,9 @@ public abstract class InMemoryAgentThread : AgentThread
|
||||
/// </summary>
|
||||
/// <param name="serializedThreadState">A <see cref="JsonElement"/> representing the serialized state of the thread.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <param name="messageStoreFactory">
|
||||
/// Optional factory function to create the <see cref="InMemoryChatMessageStore"/> from its serialized state.
|
||||
/// If not provided, a default factory will be used that creates a basic in-memory store.
|
||||
/// <param name="chatHistoryProviderFactory">
|
||||
/// Optional factory function to create the <see cref="InMemoryChatHistoryProvider"/> from its serialized state.
|
||||
/// If not provided, a default factory will be used that creates a basic <see cref="InMemoryChatHistoryProvider"/>.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="serializedThreadState"/> is not a JSON object.</exception>
|
||||
/// <exception cref="JsonException">The <paramref name="serializedThreadState"/> is invalid or cannot be deserialized to the expected type.</exception>
|
||||
@@ -73,7 +73,7 @@ public abstract class InMemoryAgentThread : AgentThread
|
||||
protected InMemoryAgentThread(
|
||||
JsonElement serializedThreadState,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, InMemoryChatMessageStore>? messageStoreFactory = null)
|
||||
Func<JsonElement, JsonSerializerOptions?, InMemoryChatHistoryProvider>? chatHistoryProviderFactory = null)
|
||||
{
|
||||
if (serializedThreadState.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
@@ -83,15 +83,15 @@ public abstract class InMemoryAgentThread : AgentThread
|
||||
var state = serializedThreadState.Deserialize(
|
||||
AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(InMemoryAgentThreadState))) as InMemoryAgentThreadState;
|
||||
|
||||
this.MessageStore =
|
||||
messageStoreFactory?.Invoke(state?.StoreState ?? default, jsonSerializerOptions) ??
|
||||
new InMemoryChatMessageStore(state?.StoreState ?? default, jsonSerializerOptions);
|
||||
this.ChatHistoryProvider =
|
||||
chatHistoryProviderFactory?.Invoke(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions) ??
|
||||
new InMemoryChatHistoryProvider(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="InMemoryChatMessageStore"/> used by this thread.
|
||||
/// Gets or sets the <see cref="InMemoryChatHistoryProvider"/> used by this thread.
|
||||
/// </summary>
|
||||
public InMemoryChatMessageStore MessageStore { get; }
|
||||
public InMemoryChatHistoryProvider ChatHistoryProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the current object's state to a <see cref="JsonElement"/> using the specified serialization options.
|
||||
@@ -100,11 +100,11 @@ public abstract class InMemoryAgentThread : AgentThread
|
||||
/// <returns>A <see cref="JsonElement"/> representation of the object's state.</returns>
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
var storeState = this.MessageStore.Serialize(jsonSerializerOptions);
|
||||
var chatHistoryProviderState = this.ChatHistoryProvider.Serialize(jsonSerializerOptions);
|
||||
|
||||
var state = new InMemoryAgentThreadState
|
||||
{
|
||||
StoreState = storeState,
|
||||
ChatHistoryProviderState = chatHistoryProviderState,
|
||||
};
|
||||
|
||||
return JsonSerializer.SerializeToElement(state, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(InMemoryAgentThreadState)));
|
||||
@@ -112,13 +112,13 @@ public abstract class InMemoryAgentThread : AgentThread
|
||||
|
||||
/// <inheritdoc/>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
+31
-32
@@ -14,55 +14,54 @@ using Microsoft.Shared.Diagnostics;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an in-memory implementation of <see cref="ChatMessageStore"/> with support for message reduction and collection semantics.
|
||||
/// Provides an in-memory implementation of <see cref="ChatHistoryProvider"/> with support for message reduction and collection semantics.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="InMemoryChatMessageStore"/> stores chat messages entirely in local memory, providing fast access and manipulation
|
||||
/// capabilities. It implements both <see cref="ChatMessageStore"/> for agent integration and <see cref="IList{ChatMessage}"/>
|
||||
/// <see cref="InMemoryChatHistoryProvider"/> stores chat messages entirely in local memory, providing fast access and manipulation
|
||||
/// capabilities. It implements both <see cref="ChatHistoryProvider"/> for agent integration and <see cref="IList{ChatMessage}"/>
|
||||
/// for direct collection manipulation.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This store maintains all messages in memory. For long-running conversations or high-volume scenarios, consider using
|
||||
/// This <see cref="ChatHistoryProvider"/> maintains all messages in memory. For long-running conversations or high-volume scenarios, consider using
|
||||
/// message reduction strategies or alternative storage implementations.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DebuggerDisplay("Count = {Count}")]
|
||||
[DebuggerTypeProxy(typeof(DebugView))]
|
||||
public sealed class InMemoryChatMessageStore : ChatMessageStore, IList<ChatMessage>, IReadOnlyList<ChatMessage>
|
||||
public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider, IList<ChatMessage>, IReadOnlyList<ChatMessage>
|
||||
{
|
||||
private List<ChatMessage> _messages;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatMessageStore"/> class.
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatHistoryProvider"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This constructor creates a basic in-memory store without message reduction capabilities.
|
||||
/// This constructor creates a basic in-memory <see cref="ChatHistoryProvider"/> without message reduction capabilities.
|
||||
/// Messages will be stored exactly as added without any automatic processing or reduction.
|
||||
/// </remarks>
|
||||
public InMemoryChatMessageStore()
|
||||
public InMemoryChatHistoryProvider()
|
||||
{
|
||||
this._messages = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatMessageStore"/> class from previously serialized state.
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatHistoryProvider"/> class from previously serialized state.
|
||||
/// </summary>
|
||||
/// <param name="serializedStoreState">A <see cref="JsonElement"/> representing the serialized state of the message store.</param>
|
||||
/// <param name="serializedState">A <see cref="JsonElement"/> representing the serialized state of the provider.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="serializedStoreState"/> is not a valid JSON object or cannot be deserialized.</exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="serializedState"/> is not a valid JSON object or cannot be deserialized.</exception>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatMessageStore"/> class.
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatHistoryProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="chatReducer">
|
||||
/// A <see cref="IChatReducer"/> instance used to process, reduce, or optimize chat messages.
|
||||
@@ -77,29 +76,29 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList<ChatMessa
|
||||
/// Message reducers enable automatic management of message storage by implementing strategies to
|
||||
/// keep memory usage under control while preserving important conversation context.
|
||||
/// </remarks>
|
||||
public InMemoryChatMessageStore(IChatReducer chatReducer, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval)
|
||||
public InMemoryChatHistoryProvider(IChatReducer chatReducer, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval)
|
||||
: this(chatReducer, default, null, reducerTriggerEvent)
|
||||
{
|
||||
Throw.IfNull(chatReducer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatMessageStore"/> class, with an existing state from a serialized JSON element.
|
||||
/// Initializes a new instance of the <see cref="InMemoryChatHistoryProvider"/> class, with an existing state from a serialized JSON element.
|
||||
/// </summary>
|
||||
/// <param name="chatReducer">An optional <see cref="IChatReducer"/> instance used to process or reduce chat messages. If null, no reduction logic will be applied.</param>
|
||||
/// <param name="serializedStoreState">A <see cref="JsonElement"/> representing the serialized state of the store.</param>
|
||||
/// <param name="serializedState">A <see cref="JsonElement"/> representing the serialized state of the provider.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <param name="reducerTriggerEvent">The event that should trigger the reducer invocation.</param>
|
||||
public InMemoryChatMessageStore(IChatReducer? chatReducer, JsonElement serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval)
|
||||
public InMemoryChatHistoryProvider(IChatReducer? chatReducer, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, ChatReducerTriggerEvent reducerTriggerEvent = ChatReducerTriggerEvent.BeforeMessagesRetrieval)
|
||||
{
|
||||
this.ChatReducer = chatReducer;
|
||||
this.ReducerTriggerEvent = reducerTriggerEvent;
|
||||
|
||||
if (serializedStoreState.ValueKind is JsonValueKind.Object)
|
||||
if (serializedState.ValueKind is JsonValueKind.Object)
|
||||
{
|
||||
var jso = jsonSerializerOptions ?? AgentAbstractionsJsonUtilities.DefaultOptions;
|
||||
var state = serializedStoreState.Deserialize(
|
||||
jso.GetTypeInfo(typeof(StoreState))) as StoreState;
|
||||
var state = serializedState.Deserialize(
|
||||
jso.GetTypeInfo(typeof(State))) as State;
|
||||
if (state?.Messages is { } messages)
|
||||
{
|
||||
this._messages = messages;
|
||||
@@ -116,7 +115,7 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList<ChatMessa
|
||||
public IChatReducer? ChatReducer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event that triggers the reducer invocation in this store.
|
||||
/// Gets the event that triggers the reducer invocation in this provider.
|
||||
/// </summary>
|
||||
public ChatReducerTriggerEvent ReducerTriggerEvent { get; }
|
||||
|
||||
@@ -156,7 +155,7 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList<ChatMessa
|
||||
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 sealed class InMemoryChatMessageStore : ChatMessageStore, IList<ChatMessa
|
||||
/// <inheritdoc />
|
||||
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)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -218,13 +217,13 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList<ChatMessa
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> this.GetEnumerator();
|
||||
|
||||
internal sealed class StoreState
|
||||
internal sealed class State
|
||||
{
|
||||
public List<ChatMessage> Messages { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the events that can trigger a reducer in the <see cref="InMemoryChatMessageStore"/>.
|
||||
/// Defines the events that can trigger a reducer in the <see cref="InMemoryChatHistoryProvider"/>.
|
||||
/// </summary>
|
||||
public enum ChatReducerTriggerEvent
|
||||
{
|
||||
@@ -235,15 +234,15 @@ public sealed class InMemoryChatMessageStore : ChatMessageStore, IList<ChatMessa
|
||||
AfterMessageAdded,
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -192,7 +192,7 @@ public static class PersistentAgentsClientExtensions
|
||||
Description = options.Description ?? persistentAgentMetadata.Description,
|
||||
ChatOptions = options.ChatOptions,
|
||||
AIContextProviderFactory = options.AIContextProviderFactory,
|
||||
ChatMessageStoreFactory = options.ChatMessageStoreFactory,
|
||||
ChatHistoryProviderFactory = options.ChatHistoryProviderFactory,
|
||||
UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs
|
||||
};
|
||||
|
||||
|
||||
@@ -583,7 +583,7 @@ public static partial class AzureAIProjectChatClientExtensions
|
||||
if (options is not null)
|
||||
{
|
||||
agentOptions.AIContextProviderFactory = options.AIContextProviderFactory;
|
||||
agentOptions.ChatMessageStoreFactory = options.ChatMessageStoreFactory;
|
||||
agentOptions.ChatHistoryProviderFactory = options.ChatHistoryProviderFactory;
|
||||
agentOptions.UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs;
|
||||
}
|
||||
|
||||
|
||||
+39
-39
@@ -15,11 +15,11 @@ using Microsoft.Shared.Diagnostics;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a Cosmos DB implementation of the <see cref="ChatMessageStore"/> abstract class.
|
||||
/// Provides a Cosmos DB implementation of the <see cref="ChatHistoryProvider"/> abstract class.
|
||||
/// </summary>
|
||||
[RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
[RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public sealed class CosmosChatHistoryProvider : ChatHistoryProvider, IDisposable
|
||||
{
|
||||
private readonly CosmosClient _cosmosClient;
|
||||
private readonly Container _container;
|
||||
@@ -60,7 +60,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
public int MaxBatchSize { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
@@ -73,17 +73,17 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
public int? MessageTtlSeconds { get; set; } = 86400;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the conversation ID associated with this message store.
|
||||
/// Gets the conversation ID associated with this provider.
|
||||
/// </summary>
|
||||
public string ConversationId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the database ID associated with this message store.
|
||||
/// Gets the database ID associated with this provider.
|
||||
/// </summary>
|
||||
public string DatabaseId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the container ID associated with this message store.
|
||||
/// Gets the container ID associated with this provider.
|
||||
/// </summary>
|
||||
public string ContainerId { get; init; }
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="ownsClient">Whether this instance owns the CosmosClient and should dispose it.</param>
|
||||
/// <param name="tenantId">Optional tenant identifier for hierarchical partitioning.</param>
|
||||
/// <param name="userId">Optional user identifier for hierarchical partitioning.</param>
|
||||
internal CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId, bool ownsClient, string? tenantId = null, string? userId = null)
|
||||
internal CosmosChatHistoryProvider(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId, bool ownsClient, string? tenantId = null, string? userId = null)
|
||||
{
|
||||
this._cosmosClient = Throw.IfNull(cosmosClient);
|
||||
this._container = this._cosmosClient.GetContainer(Throw.IfNullOrWhitespace(databaseId), Throw.IfNullOrWhitespace(containerId));
|
||||
@@ -121,20 +121,20 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using a connection string.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using a connection string.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The Cosmos DB connection string.</param>
|
||||
/// <param name="databaseId">The identifier of the Cosmos DB database.</param>
|
||||
/// <param name="containerId">The identifier of the Cosmos DB container.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
public CosmosChatMessageStore(string connectionString, string databaseId, string containerId)
|
||||
public CosmosChatHistoryProvider(string connectionString, string databaseId, string containerId)
|
||||
: this(connectionString, databaseId, containerId, Guid.NewGuid().ToString("N"))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using a connection string.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using a connection string.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The Cosmos DB connection string.</param>
|
||||
/// <param name="databaseId">The identifier of the Cosmos DB database.</param>
|
||||
@@ -142,13 +142,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="conversationId">The unique identifier for this conversation thread.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using TokenCredential for authentication.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using TokenCredential for authentication.
|
||||
/// </summary>
|
||||
/// <param name="accountEndpoint">The Cosmos DB account endpoint URI.</param>
|
||||
/// <param name="tokenCredential">The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential).</param>
|
||||
@@ -156,13 +156,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="containerId">The identifier of the Cosmos DB container.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
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"))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using a TokenCredential for authentication.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using a TokenCredential for authentication.
|
||||
/// </summary>
|
||||
/// <param name="accountEndpoint">The Cosmos DB account endpoint URI.</param>
|
||||
/// <param name="tokenCredential">The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential).</param>
|
||||
@@ -171,26 +171,26 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="conversationId">The unique identifier for this conversation thread.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using an existing <see cref="CosmosClient"/>.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using an existing <see cref="CosmosClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="cosmosClient">The <see cref="CosmosClient"/> instance to use for Cosmos DB operations.</param>
|
||||
/// <param name="databaseId">The identifier of the Cosmos DB database.</param>
|
||||
/// <param name="containerId">The identifier of the Cosmos DB container.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="cosmosClient"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
public CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId)
|
||||
public CosmosChatHistoryProvider(CosmosClient cosmosClient, string databaseId, string containerId)
|
||||
: this(cosmosClient, databaseId, containerId, Guid.NewGuid().ToString("N"))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using an existing <see cref="CosmosClient"/>.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using an existing <see cref="CosmosClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="cosmosClient">The <see cref="CosmosClient"/> instance to use for Cosmos DB operations.</param>
|
||||
/// <param name="databaseId">The identifier of the Cosmos DB database.</param>
|
||||
@@ -198,13 +198,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="conversationId">The unique identifier for this conversation thread.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="cosmosClient"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using a connection string with hierarchical partition keys.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using a connection string with hierarchical partition keys.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The Cosmos DB connection string.</param>
|
||||
/// <param name="databaseId">The identifier of the Cosmos DB database.</param>
|
||||
@@ -214,13 +214,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="sessionId">The session identifier for hierarchical partitioning.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using a TokenCredential for authentication with hierarchical partition keys.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using a TokenCredential for authentication with hierarchical partition keys.
|
||||
/// </summary>
|
||||
/// <param name="accountEndpoint">The Cosmos DB account endpoint URI.</param>
|
||||
/// <param name="tokenCredential">The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential).</param>
|
||||
@@ -231,13 +231,13 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="sessionId">The session identifier for hierarchical partitioning.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosChatMessageStore"/> class using an existing <see cref="CosmosClient"/> with hierarchical partition keys.
|
||||
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using an existing <see cref="CosmosClient"/> with hierarchical partition keys.
|
||||
/// </summary>
|
||||
/// <param name="cosmosClient">The <see cref="CosmosClient"/> instance to use for Cosmos DB operations.</param>
|
||||
/// <param name="databaseId">The identifier of the Cosmos DB database.</param>
|
||||
@@ -247,43 +247,43 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
/// <param name="sessionId">The session identifier for hierarchical partitioning.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="cosmosClient"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CosmosChatMessageStore"/> class from previously serialized state.
|
||||
/// Creates a new instance of the <see cref="CosmosChatHistoryProvider"/> class from previously serialized state.
|
||||
/// </summary>
|
||||
/// <param name="cosmosClient">The <see cref="CosmosClient"/> instance to use for Cosmos DB operations.</param>
|
||||
/// <param name="serializedStoreState">A <see cref="JsonElement"/> representing the serialized state of the message store.</param>
|
||||
/// <param name="serializedState">A <see cref="JsonElement"/> representing the serialized state of the provider.</param>
|
||||
/// <param name="databaseId">The identifier of the Cosmos DB database.</param>
|
||||
/// <param name="containerId">The identifier of the Cosmos DB container.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <returns>A new instance of <see cref="CosmosChatMessageStore"/> initialized from the serialized state.</returns>
|
||||
/// <returns>A new instance of <see cref="CosmosChatHistoryProvider"/> initialized from the serialized state.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="cosmosClient"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the serialized state cannot be deserialized.</exception>
|
||||
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<StoreState>(jsonSerializerOptions);
|
||||
var state = serializedState.Deserialize<State>(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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -524,7 +524,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
}
|
||||
#pragma warning restore CA1513
|
||||
|
||||
var state = new StoreState
|
||||
var state = new State
|
||||
{
|
||||
ConversationIdentifier = this.ConversationId,
|
||||
TenantId = this._tenantId,
|
||||
@@ -632,7 +632,7 @@ public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StoreState
|
||||
private sealed class State
|
||||
{
|
||||
public string ConversationIdentifier { get; set; } = string.Empty;
|
||||
public string? TenantId { get; set; }
|
||||
@@ -23,9 +23,9 @@ public static class CosmosDBChatExtensions
|
||||
/// <returns>The configured <see cref="ChatClientAgentOptions"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
[RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public static ChatClientAgentOptions WithCosmosDBMessageStore(
|
||||
[RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public static ChatClientAgentOptions WithCosmosDBChatHistoryProvider(
|
||||
this ChatClientAgentOptions options,
|
||||
string connectionString,
|
||||
string databaseId,
|
||||
@@ -36,7 +36,7 @@ public static class CosmosDBChatExtensions
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.ChatMessageStoreFactory = (context, ct) => new ValueTask<ChatMessageStore>(new CosmosChatMessageStore(connectionString, databaseId, containerId));
|
||||
options.ChatHistoryProviderFactory = (context, ct) => new ValueTask<ChatHistoryProvider>(new CosmosChatHistoryProvider(connectionString, databaseId, containerId));
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ public static class CosmosDBChatExtensions
|
||||
/// <returns>The configured <see cref="ChatClientAgentOptions"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/> or <paramref name="tokenCredential"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
[RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public static ChatClientAgentOptions WithCosmosDBMessageStoreUsingManagedIdentity(
|
||||
[RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public static ChatClientAgentOptions WithCosmosDBChatHistoryProviderUsingManagedIdentity(
|
||||
this ChatClientAgentOptions options,
|
||||
string accountEndpoint,
|
||||
string databaseId,
|
||||
@@ -70,7 +70,7 @@ public static class CosmosDBChatExtensions
|
||||
throw new ArgumentNullException(nameof(tokenCredential));
|
||||
}
|
||||
|
||||
options.ChatMessageStoreFactory = (context, ct) => new ValueTask<ChatMessageStore>(new CosmosChatMessageStore(accountEndpoint, tokenCredential, databaseId, containerId));
|
||||
options.ChatHistoryProviderFactory = (context, ct) => new ValueTask<ChatHistoryProvider>(new CosmosChatHistoryProvider(accountEndpoint, tokenCredential, databaseId, containerId));
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -84,9 +84,9 @@ public static class CosmosDBChatExtensions
|
||||
/// <returns>The configured <see cref="ChatClientAgentOptions"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when any string parameter is null or whitespace.</exception>
|
||||
[RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public static ChatClientAgentOptions WithCosmosDBMessageStore(
|
||||
[RequiresUnreferencedCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with trimming.")]
|
||||
[RequiresDynamicCode("The CosmosChatHistoryProvider uses JSON serialization which is incompatible with NativeAOT.")]
|
||||
public static ChatClientAgentOptions WithCosmosDBChatHistoryProvider(
|
||||
this ChatClientAgentOptions options,
|
||||
CosmosClient cosmosClient,
|
||||
string databaseId,
|
||||
@@ -97,7 +97,7 @@ public static class CosmosDBChatExtensions
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.ChatMessageStoreFactory = (context, ct) => new ValueTask<ChatMessageStore>(new CosmosChatMessageStore(cosmosClient, databaseId, containerId));
|
||||
options.ChatHistoryProviderFactory = (context, ct) => new ValueTask<ChatHistoryProvider>(new CosmosChatHistoryProvider(cosmosClient, databaseId, containerId));
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<PropertyGroup>
|
||||
<!-- NuGet Package Settings -->
|
||||
<Title>Microsoft Agent Framework Cosmos DB NoSQL Integration</Title>
|
||||
<Description>Provides Cosmos DB NoSQL implementations for Microsoft Agent Framework storage abstractions including ChatMessageStore and CheckpointStore.</Description>
|
||||
<Description>Provides Cosmos DB NoSQL implementations for Microsoft Agent Framework storage abstractions including ChatHistoryProvider and CheckpointStore.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -205,7 +205,7 @@ public static class OpenAIAssistantClientExtensions
|
||||
Description = options.Description ?? assistantMetadata.Description,
|
||||
ChatOptions = options.ChatOptions,
|
||||
AIContextProviderFactory = options.AIContextProviderFactory,
|
||||
ChatMessageStoreFactory = options.ChatMessageStoreFactory,
|
||||
ChatHistoryProviderFactory = options.ChatHistoryProviderFactory,
|
||||
UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -10,16 +10,16 @@ using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows;
|
||||
|
||||
internal sealed class WorkflowMessageStore : ChatMessageStore
|
||||
internal sealed class WorkflowChatHistoryProvider : ChatHistoryProvider
|
||||
{
|
||||
private int _bookmark;
|
||||
private readonly List<ChatMessage> _chatMessages = [];
|
||||
|
||||
public WorkflowMessageStore()
|
||||
public WorkflowChatHistoryProvider()
|
||||
{
|
||||
}
|
||||
|
||||
public WorkflowMessageStore(StoreState state)
|
||||
public WorkflowChatHistoryProvider(StoreState state)
|
||||
{
|
||||
this.ImportStoreState(Throw.IfNull(state));
|
||||
}
|
||||
@@ -82,7 +82,7 @@ internal sealed class WorkflowHostAgent : AIAgent
|
||||
|
||||
// For workflow threads, messages are added directly via the internal AddMessages method
|
||||
// The MessageStore methods are used for agent invocation scenarios
|
||||
workflowThread.MessageStore.AddMessages(messages);
|
||||
workflowThread.ChatHistoryProvider.AddMessages(messages);
|
||||
return workflowThread;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ internal sealed class WorkflowThread : AgentThread
|
||||
this._checkpointManager = checkpointManager ?? new(this._inMemoryCheckpointManager = new());
|
||||
|
||||
this.RunId = Throw.IfNullOrEmpty(runId);
|
||||
this.MessageStore = new WorkflowMessageStore();
|
||||
this.ChatHistoryProvider = new WorkflowChatHistoryProvider();
|
||||
}
|
||||
|
||||
public WorkflowThread(Workflow workflow, JsonElement serializedThread, IWorkflowExecutionEnvironment executionEnvironment, CheckpointManager? checkpointManager = null, bool includeExceptionDetails = false, bool includeWorkflowOutputsInResponse = false, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
@@ -70,7 +70,7 @@ internal sealed class WorkflowThread : AgentThread
|
||||
|
||||
this.RunId = threadState.RunId;
|
||||
this.LastCheckpoint = threadState.LastCheckpoint;
|
||||
this.MessageStore = new WorkflowMessageStore(threadState.MessageStoreState);
|
||||
this.ChatHistoryProvider = new WorkflowChatHistoryProvider(threadState.ChatHistoryProviderState);
|
||||
}
|
||||
|
||||
public CheckpointInfo? LastCheckpoint { get; set; }
|
||||
@@ -81,7 +81,7 @@ internal sealed class WorkflowThread : AgentThread
|
||||
ThreadState info = new(
|
||||
this.RunId,
|
||||
this.LastCheckpoint,
|
||||
this.MessageStore.ExportStoreState(),
|
||||
this.ChatHistoryProvider.ExportStoreState(),
|
||||
this._inMemoryCheckpointManager);
|
||||
|
||||
return marshaller.Marshal(info);
|
||||
@@ -100,7 +100,7 @@ internal sealed class WorkflowThread : AgentThread
|
||||
RawRepresentation = raw
|
||||
};
|
||||
|
||||
this.MessageStore.AddMessages(update.ToChatMessage());
|
||||
this.ChatHistoryProvider.AddMessages(update.ToChatMessage());
|
||||
|
||||
return update;
|
||||
}
|
||||
@@ -117,7 +117,7 @@ internal sealed class WorkflowThread : AgentThread
|
||||
RawRepresentation = raw
|
||||
};
|
||||
|
||||
this.MessageStore.AddMessages(update.ToChatMessage());
|
||||
this.ChatHistoryProvider.AddMessages(update.ToChatMessage());
|
||||
|
||||
return update;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ internal sealed class WorkflowThread : AgentThread
|
||||
try
|
||||
{
|
||||
this.LastResponseId = Guid.NewGuid().ToString("N");
|
||||
List<ChatMessage> messages = this.MessageStore.GetFromBookmark().ToList();
|
||||
List<ChatMessage> messages = this.ChatHistoryProvider.GetFromBookmark().ToList();
|
||||
|
||||
#pragma warning disable CA2007 // Analyzer misfiring and not seeing .ConfigureAwait(false) below.
|
||||
await using Checkpointed<StreamingRun> checkpointed =
|
||||
@@ -240,7 +240,7 @@ internal sealed class WorkflowThread : AgentThread
|
||||
finally
|
||||
{
|
||||
// Do we want to try to undo the step, and not update the bookmark?
|
||||
this.MessageStore.UpdateBookmark();
|
||||
this.ChatHistoryProvider.UpdateBookmark();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,17 +249,17 @@ internal sealed class WorkflowThread : AgentThread
|
||||
public string RunId { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ internal static partial class WorkflowsJsonUtilities
|
||||
[JsonSerializable(typeof(EdgeConnection))]
|
||||
|
||||
// Workflow-as-Agent
|
||||
[JsonSerializable(typeof(WorkflowMessageStore.StoreState))]
|
||||
[JsonSerializable(typeof(WorkflowChatHistoryProvider.StoreState))]
|
||||
[JsonSerializable(typeof(WorkflowThread.ThreadState))]
|
||||
|
||||
// Message Types
|
||||
|
||||
@@ -78,7 +78,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
/// <param name="chatClient">The chat client to use when running the agent.</param>
|
||||
/// <param name="options">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <param name="loggerFactory">
|
||||
/// Optional logger factory for creating loggers used by the agent and its components.
|
||||
@@ -208,7 +208,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
ChatOptions? chatOptions,
|
||||
List<ChatMessage> inputMessagesForChatClient,
|
||||
IList<ChatMessage>? aiContextProviderMessages,
|
||||
IList<ChatMessage>? chatMessageStoreMessages,
|
||||
IList<ChatMessage>? chatHistoryProviderMessages,
|
||||
ChatClientAgentContinuationToken? continuationToken) =
|
||||
await this.PrepareThreadAndMessagesAsync(thread, inputMessages, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -231,7 +231,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyMessageStoreOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyAIContextProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
@@ -246,7 +246,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyMessageStoreOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyAIContextProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
@@ -273,7 +273,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyMessageStoreOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyAIContextProviderOfFailureAsync(safeThread, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
@@ -286,7 +286,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
await this.UpdateThreadWithTypeAndConversationIdAsync(safeThread, chatResponse.ConversationId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// To avoid inconsistent state we only notify the thread of the input messages if no error occurs after the initial request.
|
||||
await NotifyMessageStoreOfNewMessagesAsync(safeThread, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyChatHistoryProviderOfNewMessagesAsync(safeThread, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Notify the AIContextProvider of all new messages.
|
||||
await NotifyAIContextProviderOfSuccessAsync(safeThread, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false);
|
||||
@@ -304,8 +304,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
ChatMessageStore? messageStore = this._agentOptions?.ChatMessageStoreFactory is not null
|
||||
? await this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
ChatHistoryProvider? chatHistoryProvider = this._agentOptions?.ChatHistoryProviderFactory is not null
|
||||
? await this._agentOptions.ChatHistoryProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
|
||||
AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null
|
||||
@@ -314,7 +314,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
|
||||
return new ChatClientAgentThread
|
||||
{
|
||||
MessageStore = messageStore,
|
||||
ChatHistoryProvider = chatHistoryProvider,
|
||||
AIContextProvider = contextProvider
|
||||
};
|
||||
}
|
||||
@@ -329,8 +329,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 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 <see cref="AgentThread"/> that relies on server-side chat history storage, where the chat history
|
||||
/// is maintained by the underlying AI service rather than by a local <see cref="ChatHistoryProvider"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Agent threads created with this method will only work with <see cref="ChatClientAgent"/>
|
||||
@@ -351,28 +351,28 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new agent thread instance using an existing <see cref="ChatMessageStore"/> to continue a conversation.
|
||||
/// Creates a new agent thread instance using an existing <see cref="ChatHistoryProvider"/> to continue a conversation.
|
||||
/// </summary>
|
||||
/// <param name="chatMessageStore">The <see cref="ChatMessageStore"/> instance to use for managing the conversation's message history.</param>
|
||||
/// <param name="chatHistoryProvider">The <see cref="ChatHistoryProvider"/> instance to use for managing the conversation's message history.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>
|
||||
/// A value task representing the asynchronous operation. The task result contains a new <see cref="AgentThread"/> instance configured to work with the provided <paramref name="chatMessageStore"/>.
|
||||
/// A value task representing the asynchronous operation. The task result contains a new <see cref="AgentThread"/> instance configured to work with the provided <paramref name="chatHistoryProvider"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 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 <see cref="ChatMessageStore"/> may not be compatible with these services.
|
||||
/// with a <see cref="ChatHistoryProvider"/> may not be compatible with these services.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Where a service requires server-side conversation storage, use <see cref="GetNewThreadAsync(string, CancellationToken)"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the agent detects, during the first run, that the underlying AI service requires server-side conversation storage,
|
||||
/// the thread will throw an exception to indicate that it cannot continue using the provided <see cref="ChatMessageStore"/>.
|
||||
/// the thread will throw an exception to indicate that it cannot continue using the provided <see cref="ChatHistoryProvider"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public async ValueTask<AgentThread> GetNewThreadAsync(ChatMessageStore chatMessageStore, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<AgentThread> GetNewThreadAsync(ChatHistoryProvider chatHistoryProvider, CancellationToken cancellationToken = default)
|
||||
{
|
||||
AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null
|
||||
? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
@@ -380,7 +380,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
|
||||
return new ChatClientAgentThread()
|
||||
{
|
||||
MessageStore = Throw.IfNull(chatMessageStore),
|
||||
ChatHistoryProvider = Throw.IfNull(chatHistoryProvider),
|
||||
AIContextProvider = contextProvider
|
||||
};
|
||||
}
|
||||
@@ -388,9 +388,9 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<ChatMessageStore>>? chatMessageStoreFactory = this._agentOptions?.ChatMessageStoreFactory is null ?
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<ChatHistoryProvider>>? 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<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<AIContextProvider>>? aiContextProviderFactory = this._agentOptions?.AIContextProviderFactory is null ?
|
||||
null :
|
||||
@@ -399,7 +399,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
return await ChatClientAgentThread.DeserializeAsync(
|
||||
serializedThread,
|
||||
jsonSerializerOptions,
|
||||
chatMessageStoreFactory,
|
||||
chatHistoryProviderFactory,
|
||||
aiContextProviderFactory,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -422,7 +422,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
ChatOptions? chatOptions,
|
||||
List<ChatMessage> inputMessagesForChatClient,
|
||||
IList<ChatMessage>? aiContextProviderMessages,
|
||||
IList<ChatMessage>? chatMessageStoreMessages,
|
||||
IList<ChatMessage>? chatHistoryProviderMessages,
|
||||
ChatClientAgentContinuationToken? _) =
|
||||
await this.PrepareThreadAndMessagesAsync(thread, inputMessages, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -442,7 +442,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyMessageStoreOfFailureAsync(safeThread, ex, inputMessages, chatMessageStoreMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyChatHistoryProviderOfFailureAsync(safeThread, ex, inputMessages, chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyAIContextProviderOfFailureAsync(safeThread, ex, inputMessages, aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
@@ -460,7 +460,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
|
||||
// Only notify the thread of new messages if the chatResponse was successful to avoid inconsistent message state in the thread.
|
||||
await NotifyMessageStoreOfNewMessagesAsync(safeThread, inputMessages, chatMessageStoreMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await NotifyChatHistoryProviderOfNewMessagesAsync(safeThread, inputMessages, chatHistoryProviderMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Notify the AIContextProvider of all new messages.
|
||||
await NotifyAIContextProviderOfSuccessAsync(safeThread, inputMessages, aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false);
|
||||
@@ -672,7 +672,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
ChatOptions? ChatOptions,
|
||||
List<ChatMessage> InputMessagesForChatClient,
|
||||
IList<ChatMessage>? AIContextProviderMessages,
|
||||
IList<ChatMessage>? ChatMessageStoreMessages,
|
||||
IList<ChatMessage>? ChatHistoryProviderMessages,
|
||||
ChatClientAgentContinuationToken? ContinuationToken
|
||||
)> PrepareThreadAndMessagesAsync(
|
||||
AgentThread? thread,
|
||||
@@ -703,20 +703,20 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
|
||||
List<ChatMessage> inputMessagesForChatClient = [];
|
||||
IList<ChatMessage>? aiContextProviderMessages = null;
|
||||
IList<ChatMessage>? chatMessageStoreMessages = null;
|
||||
IList<ChatMessage>? 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<ChatMessage> ?? storeMessages.ToList();
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(inputMessages);
|
||||
var providerMessages = await chatHistoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
|
||||
inputMessagesForChatClient.AddRange(providerMessages);
|
||||
chatHistoryProviderMessages = providerMessages as IList<ChatMessage> ?? providerMessages.ToList();
|
||||
}
|
||||
|
||||
// Add the input messages before getting context from AIContextProvider.
|
||||
@@ -770,7 +770,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
chatOptions.ConversationId = typedThread.ConversationId;
|
||||
}
|
||||
|
||||
return (typedThread, chatOptions, inputMessagesForChatClient, aiContextProviderMessages, chatMessageStoreMessages, continuationToken);
|
||||
return (typedThread, chatOptions, inputMessagesForChatClient, aiContextProviderMessages, chatHistoryProviderMessages, continuationToken);
|
||||
}
|
||||
|
||||
private async Task UpdateThreadWithTypeAndConversationIdAsync(ChatClientAgentThread thread, string? responseConversationId, CancellationToken cancellationToken)
|
||||
@@ -791,78 +791,78 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
else
|
||||
{
|
||||
// If the service doesn't use service side chat history storage (i.e. we got no id back from invocation), and
|
||||
// the thread has no MessageStore yet, we should update the thread with the custom MessageStore or
|
||||
// default InMemoryMessageStore so that it has somewhere to store the chat history.
|
||||
thread.MessageStore ??= this._agentOptions?.ChatMessageStoreFactory is not null
|
||||
? await this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: new InMemoryChatMessageStore();
|
||||
// the thread has no ChatHistoryProvider yet, we should update the thread with the custom ChatHistoryProvider or
|
||||
// default InMemoryChatHistoryProvider so that it has somewhere to store the chat history.
|
||||
thread.ChatHistoryProvider ??= this._agentOptions?.ChatHistoryProviderFactory is not null
|
||||
? await this._agentOptions.ChatHistoryProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false)
|
||||
: new InMemoryChatHistoryProvider();
|
||||
}
|
||||
}
|
||||
|
||||
private static Task NotifyMessageStoreOfFailureAsync(
|
||||
private static Task NotifyChatHistoryProviderOfFailureAsync(
|
||||
ChatClientAgentThread thread,
|
||||
Exception ex,
|
||||
IEnumerable<ChatMessage> requestMessages,
|
||||
IEnumerable<ChatMessage>? chatMessageStoreMessages,
|
||||
IEnumerable<ChatMessage>? chatHistoryProviderMessages,
|
||||
IEnumerable<ChatMessage>? 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<ChatMessage> requestMessages,
|
||||
IEnumerable<ChatMessage>? chatMessageStoreMessages,
|
||||
IEnumerable<ChatMessage>? chatHistoryProviderMessages,
|
||||
IEnumerable<ChatMessage>? aiContextProviderMessages,
|
||||
IEnumerable<ChatMessage> 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<ChatMessage>? inputMessages = null, List<ChatResponseUpdate>? responseUpdates = null)
|
||||
|
||||
@@ -39,10 +39,10 @@ public sealed class ChatClientAgentOptions
|
||||
public ChatOptions? ChatOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a factory function to create an instance of <see cref="ChatMessageStore"/>
|
||||
/// which will be used to store chat messages for this agent.
|
||||
/// Gets or sets a factory function to create an instance of <see cref="ChatHistoryProvider"/>
|
||||
/// which will be used to provide chat history for this agent.
|
||||
/// </summary>
|
||||
public Func<ChatMessageStoreFactoryContext, CancellationToken, ValueTask<ChatMessageStore>>? ChatMessageStoreFactory { get; set; }
|
||||
public Func<ChatHistoryProviderFactoryContext, CancellationToken, ValueTask<ChatHistoryProvider>>? ChatHistoryProviderFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a factory function to create an instance of <see cref="AIContextProvider"/>
|
||||
@@ -75,7 +75,7 @@ public sealed class ChatClientAgentOptions
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
ChatOptions = this.ChatOptions?.Clone(),
|
||||
ChatMessageStoreFactory = this.ChatMessageStoreFactory,
|
||||
ChatHistoryProviderFactory = this.ChatHistoryProviderFactory,
|
||||
AIContextProviderFactory = this.AIContextProviderFactory,
|
||||
};
|
||||
|
||||
@@ -97,14 +97,14 @@ public sealed class ChatClientAgentOptions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context object passed to the <see cref="ChatMessageStoreFactory"/> to create a new instance of <see cref="ChatMessageStore"/>.
|
||||
/// Context object passed to the <see cref="ChatHistoryProviderFactory"/> to create a new instance of <see cref="ChatHistoryProvider"/>.
|
||||
/// </summary>
|
||||
public sealed class ChatMessageStoreFactoryContext
|
||||
public sealed class ChatHistoryProviderFactoryContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the serialized state of the chat message store, if any.
|
||||
/// Gets or sets the serialized state of the <see cref="ChatHistoryProvider"/>, if any.
|
||||
/// </summary>
|
||||
/// <value><see langword="default"/> if there is no state, e.g. when the <see cref="ChatMessageStore"/> is first created.</value>
|
||||
/// <value><see langword="default"/> if there is no state, e.g. when the <see cref="ChatHistoryProvider"/> is first created.</value>
|
||||
public JsonElement SerializedState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Microsoft.Agents.AI;
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
public sealed class ChatClientAgentThread : AgentThread
|
||||
{
|
||||
private ChatMessageStore? _messageStore;
|
||||
private ChatHistoryProvider? _chatHistoryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatClientAgentThread"/> class.
|
||||
@@ -29,14 +29,14 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Note that either <see cref="ConversationId"/> or <see cref="MessageStore "/> may be set, but not both.
|
||||
/// If <see cref="MessageStore "/> is not null, setting <see cref="ConversationId"/> will throw an
|
||||
/// Note that either <see cref="ConversationId"/> or <see cref="ChatHistoryProvider "/> may be set, but not both.
|
||||
/// If <see cref="ChatHistoryProvider "/> is not null, setting <see cref="ConversationId"/> will throw an
|
||||
/// <see cref="InvalidOperationException "/> exception.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This property may be null in the following cases:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>The thread stores messages via the <see cref="ChatMessageStore"/> and not in the agent service.</description></item>
|
||||
/// <item><description>The thread stores messages via the <see cref="AI.ChatHistoryProvider"/> and not in the agent service.</description></item>
|
||||
/// <item><description>This thread object is new and a server managed thread has not yet been created in the agent service.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
@@ -46,7 +46,7 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
/// to fork the thread with each iteration.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException">Attempted to set a conversation ID but a <see cref="MessageStore"/> is already set.</exception>
|
||||
/// <exception cref="InvalidOperationException">Attempted to set a conversation ID but a <see cref="ChatHistoryProvider"/> is already set.</exception>
|
||||
public string? ConversationId
|
||||
{
|
||||
get;
|
||||
@@ -57,12 +57,12 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._messageStore is not null)
|
||||
if (this._chatHistoryProvider is not null)
|
||||
{
|
||||
// If we have a message store already, we shouldn't switch the thread to use a conversation id
|
||||
// If we have a ChatHistoryProvider already, we shouldn't switch the thread to use a conversation id
|
||||
// since it means that the thread contents will essentially be deleted, and the thread will not work
|
||||
// with the original agent anymore.
|
||||
throw new InvalidOperationException("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.");
|
||||
throw new InvalidOperationException("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported.");
|
||||
}
|
||||
|
||||
field = Throw.IfNullOrWhitespace(value);
|
||||
@@ -70,40 +70,40 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ChatMessageStore"/> used by this thread, for cases where messages should be stored in a custom location.
|
||||
/// Gets or sets the <see cref="AI.ChatHistoryProvider"/> used by this thread, for cases where messages should be stored in a custom location.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Note that either <see cref="ConversationId"/> or <see cref="MessageStore "/> may be set, but not both.
|
||||
/// If <see cref="ConversationId"/> is not null, and <see cref="MessageStore "/> is set, <see cref="ConversationId"/>
|
||||
/// Note that either <see cref="ConversationId"/> or <see cref="ChatHistoryProvider "/> may be set, but not both.
|
||||
/// If <see cref="ConversationId"/> is not null, and <see cref="ChatHistoryProvider "/> is set, <see cref="ConversationId"/>
|
||||
/// will be reverted to null, and vice versa.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This property may be null in the following cases:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>The thread stores messages in the agent service and just has an id to the remove thread, instead of in an <see cref="ChatMessageStore"/>.</description></item>
|
||||
/// <item><description>This thread object is new it is not yet clear whether it will be backed by a server managed thread or an <see cref="ChatMessageStore"/>.</description></item>
|
||||
/// <item><description>The thread stores messages in the agent service and just has an id to the remove thread, instead of in an <see cref="AI.ChatHistoryProvider"/>.</description></item>
|
||||
/// <item><description>This thread object is new it is not yet clear whether it will be backed by a server managed thread or an <see cref="AI.ChatHistoryProvider"/>.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public ChatMessageStore? MessageStore
|
||||
public ChatHistoryProvider? ChatHistoryProvider
|
||||
{
|
||||
get => this._messageStore;
|
||||
get => this._chatHistoryProvider;
|
||||
internal set
|
||||
{
|
||||
if (this._messageStore is null && value is null)
|
||||
if (this._chatHistoryProvider is null && value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.ConversationId))
|
||||
{
|
||||
// If we have a conversation id already, we shouldn't switch the thread to use a message store
|
||||
// If we have a conversation id already, we shouldn't switch the thread to use a ChatHistoryProvider
|
||||
// since it means that the thread will not work with the original agent anymore.
|
||||
throw new InvalidOperationException("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.");
|
||||
throw new InvalidOperationException("Only the ConversationId or ChatHistoryProvider may be set, but not both and switching from one to another is not supported.");
|
||||
}
|
||||
|
||||
this._messageStore = Throw.IfNull(value);
|
||||
this._chatHistoryProvider = Throw.IfNull(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +117,9 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
/// </summary>
|
||||
/// <param name="serializedThreadState">A <see cref="JsonElement"/> representing the serialized state of the thread.</param>
|
||||
/// <param name="jsonSerializerOptions">Optional settings for customizing the JSON deserialization process.</param>
|
||||
/// <param name="chatMessageStoreFactory">
|
||||
/// An optional factory function to create a custom <see cref="ChatMessageStore"/> from its serialized state.
|
||||
/// If not provided, the default in-memory message store will be used.
|
||||
/// <param name="chatHistoryProviderFactory">
|
||||
/// An optional factory function to create a custom <see cref="AI.ChatHistoryProvider"/> from its serialized state.
|
||||
/// If not provided, the default <see cref="InMemoryChatHistoryProvider"/> will be used.
|
||||
/// </param>
|
||||
/// <param name="aiContextProviderFactory">
|
||||
/// An optional factory function to create a custom <see cref="AIContextProvider"/> from its serialized state.
|
||||
@@ -130,7 +130,7 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
internal static async Task<ChatClientAgentThread> DeserializeAsync(
|
||||
JsonElement serializedThreadState,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<ChatMessageStore>>? chatMessageStoreFactory = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<ChatHistoryProvider>>? chatHistoryProviderFactory = null,
|
||||
Func<JsonElement, JsonSerializerOptions?, CancellationToken, ValueTask<AIContextProvider>>? aiContextProviderFactory = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -152,14 +152,14 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
{
|
||||
thread.ConversationId = threadId;
|
||||
|
||||
// Since we have an ID, we should not have a chat message store and we can return here.
|
||||
// Since we have an ID, we should not have a ChatHistoryProvider and we can return here.
|
||||
return thread;
|
||||
}
|
||||
|
||||
thread._messageStore =
|
||||
chatMessageStoreFactory is not null
|
||||
? await chatMessageStoreFactory.Invoke(state?.StoreState ?? default, jsonSerializerOptions, cancellationToken).ConfigureAwait(false)
|
||||
: new InMemoryChatMessageStore(state?.StoreState ?? default, jsonSerializerOptions); // default to an in-memory store
|
||||
thread._chatHistoryProvider =
|
||||
chatHistoryProviderFactory is not null
|
||||
? await chatHistoryProviderFactory.Invoke(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions, cancellationToken).ConfigureAwait(false)
|
||||
: new InMemoryChatHistoryProvider(state?.ChatHistoryProviderState ?? default, jsonSerializerOptions); // default to an in-memory ChatHistoryProvider
|
||||
|
||||
return thread;
|
||||
}
|
||||
@@ -167,14 +167,14 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
/// <inheritdoc/>
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
JsonElement? storeState = this._messageStore?.Serialize(jsonSerializerOptions);
|
||||
JsonElement? chatHistoryProviderState = this._chatHistoryProvider?.Serialize(jsonSerializerOptions);
|
||||
|
||||
JsonElement? aiContextProviderState = this.AIContextProvider?.Serialize(jsonSerializerOptions);
|
||||
|
||||
var state = new ThreadState
|
||||
{
|
||||
ConversationId = this.ConversationId,
|
||||
StoreState = storeState is { ValueKind: not JsonValueKind.Undefined } ? storeState : null,
|
||||
ChatHistoryProviderState = chatHistoryProviderState is { ValueKind: not JsonValueKind.Undefined } ? chatHistoryProviderState : null,
|
||||
AIContextProviderState = aiContextProviderState is { ValueKind: not JsonValueKind.Undefined } ? aiContextProviderState : null,
|
||||
};
|
||||
|
||||
@@ -185,20 +185,20 @@ public sealed class ChatClientAgentThread : AgentThread
|
||||
public override object? GetService(Type serviceType, object? serviceKey = null) =>
|
||||
base.GetService(serviceType, serviceKey)
|
||||
?? this.AIContextProvider?.GetService(serviceType, serviceKey)
|
||||
?? this.MessageStore?.GetService(serviceType, serviceKey);
|
||||
?? this.ChatHistoryProvider?.GetService(serviceType, serviceKey);
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private string DebuggerDisplay =>
|
||||
this.ConversationId is { } conversationId ? $"ConversationId = {conversationId}" :
|
||||
this._messageStore is InMemoryChatMessageStore inMemoryStore ? $"Count = {inMemoryStore.Count}" :
|
||||
this._messageStore is { } store ? $"Store = {store.GetType().Name}" :
|
||||
this._chatHistoryProvider is InMemoryChatHistoryProvider inMemoryChatHistoryProvider ? $"Count = {inMemoryChatHistoryProvider.Count}" :
|
||||
this._chatHistoryProvider is { } chatHistoryProvider ? $"ChatHistoryProvider = {chatHistoryProvider.GetType().Name}" :
|
||||
"Count = 0";
|
||||
|
||||
internal sealed class ThreadState
|
||||
{
|
||||
public string? ConversationId { get; set; }
|
||||
|
||||
public JsonElement? StoreState { get; set; }
|
||||
public JsonElement? ChatHistoryProviderState { get; set; }
|
||||
|
||||
public JsonElement? AIContextProviderState { get; set; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user