Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion dotnet/samples/01-get-started/04_memory/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ internal sealed class UserInfoMemory : AIContextProvider
private readonly IChatClient _chatClient;

public UserInfoMemory(IChatClient chatClient, Func<AgentSession?, UserInfo>? stateInitializer = null)
: base(null, null)
{
this._sessionState = new ProviderSessionState<UserInfo>(
stateInitializer ?? (_ => new UserInfo()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
// We also want to maintain that exclusion here.
ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
{
StorageInputMessageFilter = messages => messages.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.AIContextProvider && m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory)
StorageInputRequestMessageFilter = messages => messages.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.AIContextProvider && m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory)
}),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
// You may choose to persist the TextSearchProvider messages, if you want the search output to be provided to the model in future interactions as well.
ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions()
{
StorageInputMessageFilter = msgs => msgs.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory && m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.AIContextProvider)
StorageInputRequestMessageFilter = msgs => msgs.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory && m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.AIContextProvider)
})
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ public VectorChatHistoryProvider(
VectorStore vectorStore,
Func<AgentSession?, State>? stateInitializer = null,
string? stateKey = null)
: base(provideOutputMessageFilter: null, storeInputMessageFilter: null)
{
this._sessionState = new ProviderSessionState<State>(
stateInitializer ?? (_ => new State(Guid.NewGuid().ToString("N"))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ You remind users of upcoming calendar events when the user interacts with you.
""" },
ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
{
// Use StorageInputMessageFilter to provide a custom filter for messages stored in chat history.
// Use StorageInputRequestMessageFilter to provide a custom filter for request messages stored in chat history.
// By default the chat history provider will store all messages, except for those that came from chat history in the first place.
// In this case, we want to also exclude messages that came from AI context providers.
// You may want to store these messages, depending on their content and your requirements.
StorageInputMessageFilter = messages => messages.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.AIContextProvider && m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory)
StorageInputRequestMessageFilter = messages => messages.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.AIContextProvider && m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory)
}),
// Add multiple AI context providers: one that maintains a todo list and one that provides upcoming calendar entries.
// The agent will call each provider in sequence, accumulating context from each.
Expand Down
24 changes: 18 additions & 6 deletions dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,23 @@ public abstract class AIContextProvider
{
private static IEnumerable<ChatMessage> DefaultExternalOnlyFilter(IEnumerable<ChatMessage> messages)
=> messages.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External);
private static IEnumerable<ChatMessage> DefaultNoopFilter(IEnumerable<ChatMessage> messages)
=> messages;

/// <summary>
/// Initializes a new instance of the <see cref="AIContextProvider"/> class.
/// </summary>
/// <param name="provideInputMessageFilter">An optional filter function to apply to input messages before providing context via <see cref="ProvideAIContextAsync"/>. If not set, defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages.</param>
/// <param name="storeInputMessageFilter">An optional filter function to apply to request messages before storing context via <see cref="StoreAIContextAsync"/>. If not set, defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages.</param>
/// <param name="storeInputRequestMessageFilter">An optional filter function to apply to request messages before storing context via <see cref="StoreAIContextAsync"/>. If not set, defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages.</param>
/// <param name="storeInputResponseMessageFilter">An optional filter function to apply to response messages before storing context via <see cref="StoreAIContextAsync"/>. If not set, defaults to a no-op filter that includes all response messages.</param>
protected AIContextProvider(
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? provideInputMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputMessageFilter = null)
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputRequestMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputResponseMessageFilter = null)
{
this.ProvideInputMessageFilter = provideInputMessageFilter ?? DefaultExternalOnlyFilter;
this.StoreInputMessageFilter = storeInputMessageFilter ?? DefaultExternalOnlyFilter;
this.StoreInputRequestMessageFilter = storeInputRequestMessageFilter ?? DefaultExternalOnlyFilter;
this.StoreInputResponseMessageFilter = storeInputResponseMessageFilter ?? DefaultNoopFilter;
}

/// <summary>
Expand All @@ -55,7 +60,12 @@ protected AIContextProvider(
/// <summary>
/// Gets the filter function to apply to request messages before storing context via <see cref="StoreAIContextAsync"/>.
/// </summary>
protected Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> StoreInputMessageFilter { get; }
protected Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> StoreInputRequestMessageFilter { get; }

/// <summary>
/// Gets the filter function to apply to response messages before storing context via <see cref="StoreAIContextAsync"/>.
/// </summary>
protected Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> StoreInputResponseMessageFilter { get; }

/// <summary>
/// Gets the key used to store the provider state in the <see cref="AgentSession.StateBag"/>.
Expand Down Expand Up @@ -245,8 +255,10 @@ public ValueTask InvokedAsync(InvokedContext context, CancellationToken cancella
/// </para>
/// <para>
/// The default implementation of this method skips execution for any invocation failures,
/// filters the request messages using the configured store-input message filter
/// filters the request messages using the configured store-input request message filter
/// (which defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages),
/// filters the response messages using the configured store-input response message filter
/// (which defaults to a no-op, so all response messages are processed),
/// and calls <see cref="StoreAIContextAsync"/> to process the invocation results.
/// For most scenarios, overriding <see cref="StoreAIContextAsync"/> is sufficient to process invocation results,
/// while still benefiting from the default error handling and filtering behavior.
Expand All @@ -261,7 +273,7 @@ protected virtual ValueTask InvokedCoreAsync(InvokedContext context, Cancellatio
return default;
}

var subContext = new InvokedContext(context.Agent, context.Session, this.StoreInputMessageFilter(context.RequestMessages), context.ResponseMessages!);
var subContext = new InvokedContext(context.Agent, context.Session, this.StoreInputRequestMessageFilter(context.RequestMessages), this.StoreInputResponseMessageFilter(context.ResponseMessages!));
return this.StoreAIContextAsync(subContext, cancellationToken);
}

Expand Down
18 changes: 12 additions & 6 deletions dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,27 @@ public abstract class ChatHistoryProvider
{
private static IEnumerable<ChatMessage> DefaultExcludeChatHistoryFilter(IEnumerable<ChatMessage> messages)
=> messages.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory);
private static IEnumerable<ChatMessage> DefaultNoopFilter(IEnumerable<ChatMessage> messages)
=> messages;

private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? _provideOutputMessageFilter;
private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> _storeInputMessageFilter;
private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> _storeInputRequestMessageFilter;
private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> _storeInputResponseMessageFilter;

/// <summary>
/// Initializes a new instance of the <see cref="ChatHistoryProvider"/> class.
/// </summary>
/// <param name="provideOutputMessageFilter">An optional filter function to apply to messages when retrieving them from the chat history.</param>
/// <param name="storeInputMessageFilter">An optional filter function to apply to messages before storing them in the chat history. If not set, defaults to excluding messages with source type <see cref="AgentRequestMessageSourceType.ChatHistory"/>.</param>
/// <param name="storeInputRequestMessageFilter">An optional filter function to apply to request messages before storing them in the chat history. If not set, defaults to excluding messages with source type <see cref="AgentRequestMessageSourceType.ChatHistory"/>.</param>
/// <param name="storeInputResponseMessageFilter">An optional filter function to apply to response messages before storing them in the chat history. If not set, defaults to a no-op filter that includes all response messages.</param>
protected ChatHistoryProvider(
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? provideOutputMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputMessageFilter = null)
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputRequestMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputResponseMessageFilter = null)
{
this._provideOutputMessageFilter = provideOutputMessageFilter;
this._storeInputMessageFilter = storeInputMessageFilter ?? DefaultExcludeChatHistoryFilter;
this._storeInputRequestMessageFilter = storeInputRequestMessageFilter ?? DefaultExcludeChatHistoryFilter;
this._storeInputResponseMessageFilter = storeInputResponseMessageFilter ?? DefaultNoopFilter;
}

/// <summary>
Expand Down Expand Up @@ -216,7 +222,7 @@ public ValueTask InvokedAsync(InvokedContext context, CancellationToken cancella
/// To check if the invocation was successful, inspect the <see cref="InvokedContext.InvokeException"/> property.
/// </para>
/// <para>
/// The default implementation of this method, skips execution for any invocation failures, filters messages using the optional storage input message filter
/// The default implementation of this method, skips execution for any invocation failures, filters messages using the optional storage input request and response message filters
/// and calls <see cref="StoreChatHistoryAsync"/> to store new chat history messages.
/// For most scenarios, overriding <see cref="StoreChatHistoryAsync"/> is sufficient to store chat history messages, while still benefiting from the default error handling and filtering behavior.
/// However, for scenarios that require more control over error handling or message filtering, overriding this method allows you to directly control the messages that are stored for the invocation.
Expand All @@ -229,7 +235,7 @@ protected virtual ValueTask InvokedCoreAsync(InvokedContext context, Cancellatio
return default;
}

var subContext = new InvokedContext(context.Agent, context.Session, this._storeInputMessageFilter(context.RequestMessages), context.ResponseMessages!);
var subContext = new InvokedContext(context.Agent, context.Session, this._storeInputRequestMessageFilter(context.RequestMessages), this._storeInputResponseMessageFilter(context.ResponseMessages!));
return this.StoreChatHistoryAsync(subContext, cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
public InMemoryChatHistoryProvider(InMemoryChatHistoryProviderOptions? options = null)
: base(
options?.ProvideOutputMessageFilter,
options?.StorageInputMessageFilter)
options?.StorageInputRequestMessageFilter,
options?.StorageInputResponseMessageFilter)
{
this._sessionState = new ProviderSessionState<State>(
options?.StateInitializer ?? (_ => new State()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,19 @@ public sealed class InMemoryChatHistoryProviderOptions
/// Depending on your requirements, you could provide a different filter, that also excludes
/// messages from e.g. AI context providers.
/// </value>
public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? StorageInputMessageFilter { get; set; }
public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? StorageInputRequestMessageFilter { get; set; }

/// <summary>
/// Gets or sets an optional filter function applied to response messages before they are added to storage
/// during <see cref="ChatHistoryProvider.InvokedAsync"/>.
/// </summary>
/// <value>
/// When <see langword="null"/>, no filtering is applied to response messages before they are stored.
/// If you want to avoid persisting certain messages (for example, those with
/// <see cref="AgentRequestMessageSourceType.ChatHistory"/> source type or produced by AI context providers),
/// provide a filter that returns only the messages you want to keep.
/// </value>
public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? StorageInputResponseMessageFilter { get; set; }

/// <summary>
/// Gets or sets an optional filter function applied to messages produced by this provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ public abstract class MessageAIContextProvider : AIContextProvider
/// Initializes a new instance of the <see cref="MessageAIContextProvider"/> class.
/// </summary>
/// <param name="provideInputMessageFilter">An optional filter function to apply to input messages before providing messages via <see cref="ProvideMessagesAsync"/>. If not set, defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages.</param>
/// <param name="storeInputMessageFilter">An optional filter function to apply to request messages before storing messages via <see cref="AIContextProvider.StoreAIContextAsync"/>. If not set, defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages.</param>
/// <param name="storeInputRequestMessageFilter">An optional filter function to apply to request messages before storing messages via <see cref="AIContextProvider.StoreAIContextAsync"/>. If not set, defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages.</param>
/// <param name="storeInputResponseMessageFilter">An optional filter function to apply to response messages before storing messages via <see cref="AIContextProvider.StoreAIContextAsync"/>. If not set, defaults to including all response messages (no filtering).</param>
protected MessageAIContextProvider(
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? provideInputMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputMessageFilter = null)
: base(provideInputMessageFilter, storeInputMessageFilter)
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputRequestMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputResponseMessageFilter = null)
: base(provideInputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)
{
}

Expand Down
Loading
Loading