-
Notifications
You must be signed in to change notification settings - Fork 1.3k
.NET: Support a message only AIContextProvider as an AIAgent Decorator #4009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
westey-m
merged 6 commits into
microsoft:main
from
westey-m:context-provider-agent-decorator
Feb 19, 2026
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
667be29
Support a message only AIContextProvider as an AIAgent Decorator
westey-m 6172c15
Fix formatting
westey-m 399ae04
Address PR comments.
westey-m 0cdc9ec
Merge branch 'main' into context-provider-agent-decorator
westey-m 08abc1b
Merge branch 'main' into context-provider-agent-decorator
westey-m ed908b2
Merge branch 'main' into context-provider-agent-decorator
westey-m File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
203 changes: 203 additions & 0 deletions
203
dotnet/src/Microsoft.Agents.AI.Abstractions/MessageAIContextProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Extensions.AI; | ||
| using Microsoft.Shared.Diagnostics; | ||
|
|
||
| namespace Microsoft.Agents.AI; | ||
|
|
||
| /// <summary> | ||
| /// Provides an abstract base class for components that enhance AI context during agent invocations by supplying additional chat messages. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// A message AI context provider is a component that participates in the agent invocation lifecycle by: | ||
| /// <list type="bullet"> | ||
| /// <item><description>Listening to changes in conversations</description></item> | ||
| /// <item><description>Providing additional messages to agents during invocation</description></item> | ||
| /// <item><description>Processing invocation results for state management or learning</description></item> | ||
| /// </list> | ||
| /// </para> | ||
| /// <para> | ||
| /// Context providers operate through a two-phase lifecycle: they are called at the start of invocation via | ||
| /// <see cref="AIContextProvider.InvokingAsync"/> to provide context, and optionally called at the end of invocation via | ||
| /// <see cref="AIContextProvider.InvokedAsync"/> to process results. | ||
| /// </para> | ||
| /// </remarks> | ||
| public abstract class MessageAIContextProvider : AIContextProvider | ||
| { | ||
| /// <summary> | ||
| /// 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> | ||
| protected MessageAIContextProvider( | ||
| Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? provideInputMessageFilter = null, | ||
| Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputMessageFilter = null) | ||
| : base(provideInputMessageFilter, storeInputMessageFilter) | ||
| { | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override async ValueTask<AIContext> ProvideAIContextAsync(AIContextProvider.InvokingContext context, CancellationToken cancellationToken = default) | ||
| { | ||
| // Call ProvideMessagesAsync directly to return only additional messages. | ||
| // The base AIContextProvider.InvokingCoreAsync handles merging with the original input and stamping. | ||
| return new AIContext | ||
| { | ||
| Messages = await this.ProvideMessagesAsync( | ||
| new InvokingContext(context.Agent, context.Session, context.AIContext.Messages ?? []), | ||
| cancellationToken).ConfigureAwait(false) | ||
| }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Called at the start of agent invocation to provide additional messages. | ||
| /// </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> | ||
| /// <returns>A task that represents the asynchronous operation. The task result contains the <see cref="IEnumerable{ChatMessage}"/> to be used by the agent during this invocation.</returns> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// Implementers can load any additional messages required at this time, such as: | ||
| /// <list type="bullet"> | ||
| /// <item><description>Retrieving relevant information from knowledge bases</description></item> | ||
| /// <item><description>Adding system instructions or prompts</description></item> | ||
| /// <item><description>Injecting contextual messages from conversation history</description></item> | ||
| /// </list> | ||
| /// </para> | ||
| /// </remarks> | ||
| public ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default) | ||
| => this.InvokingCoreAsync(Throw.IfNull(context), cancellationToken); | ||
|
|
||
| /// <summary> | ||
| /// Called at the start of agent invocation to provide additional messages. | ||
| /// </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> | ||
| /// <returns>A task that represents the asynchronous operation. The task result contains the <see cref="IEnumerable{ChatMessage}"/> to be used by the agent during this invocation.</returns> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// Implementers can load any additional messages required at this time, such as: | ||
| /// <list type="bullet"> | ||
| /// <item><description>Retrieving relevant information from knowledge bases</description></item> | ||
| /// <item><description>Adding system instructions or prompts</description></item> | ||
| /// <item><description>Injecting contextual messages from conversation history</description></item> | ||
| /// </list> | ||
| /// </para> | ||
| /// <para> | ||
| /// The default implementation of this method filters the input messages using the configured provide-input message filter | ||
| /// (which defaults to including only <see cref="AgentRequestMessageSourceType.External"/> messages), | ||
| /// then calls <see cref="ProvideMessagesAsync"/> to get additional messages, | ||
| /// stamps any messages with <see cref="AgentRequestMessageSourceType.AIContextProvider"/> source attribution, | ||
| /// and merges the returned messages with the original (unfiltered) input messages. | ||
| /// For most scenarios, overriding <see cref="ProvideMessagesAsync"/> is sufficient to provide additional messages, | ||
| /// while still benefiting from the default filtering, merging and source stamping behavior. | ||
| /// However, for scenarios that require more control over message filtering, merging or source stamping, overriding this method | ||
| /// allows you to directly control the full <see cref="IEnumerable{ChatMessage}"/> returned for the invocation. | ||
| /// </para> | ||
| /// </remarks> | ||
| protected virtual async ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default) | ||
| { | ||
| var inputMessages = context.RequestMessages; | ||
|
|
||
| // Create a filtered context for ProvideMessagesAsync, filtering input messages | ||
| // to exclude non-external messages (e.g. chat history, other AI context provider messages). | ||
| var filteredContext = new InvokingContext( | ||
| context.Agent, | ||
| context.Session, | ||
| this.ProvideInputMessageFilter(inputMessages)); | ||
|
|
||
| var providedMessages = await this.ProvideMessagesAsync(filteredContext, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| // Stamp and merge provided messages. | ||
| providedMessages = providedMessages.Select(m => m.WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)); | ||
| return inputMessages.Concat(providedMessages); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// When overridden in a derived class, provides additional messages to be merged with the input messages for the current invocation. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// This method is called from <see cref="InvokingCoreAsync(InvokingContext, CancellationToken)"/>. | ||
| /// Note that <see cref="InvokingCoreAsync(InvokingContext, CancellationToken)"/> can be overridden to directly control messages merging and source stamping, in which case | ||
| /// it is up to the implementer to call this method as needed to retrieve the additional messages. | ||
| /// </para> | ||
| /// <para> | ||
| /// In contrast with <see cref="InvokingCoreAsync(InvokingContext, CancellationToken)"/>, this method only returns additional messages to be merged with the input, | ||
| /// while <see cref="InvokingCoreAsync(InvokingContext, CancellationToken)"/> is responsible for returning the full merged <see cref="IEnumerable{ChatMessage}"/> for the invocation. | ||
| /// </para> | ||
| /// </remarks> | ||
| /// <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> | ||
| /// <returns> | ||
| /// A task that represents the asynchronous operation. The task result contains an <see cref="IEnumerable{ChatMessage}"/> | ||
| /// with additional messages to be merged with the input messages. | ||
| /// </returns> | ||
| protected virtual ValueTask<IEnumerable<ChatMessage>> ProvideMessagesAsync(InvokingContext context, CancellationToken cancellationToken = default) | ||
| { | ||
| return new ValueTask<IEnumerable<ChatMessage>>([]); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Contains the context information provided to <see cref="InvokingCoreAsync(InvokingContext, CancellationToken)"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This class provides context about the invocation before the underlying AI model is invoked, including the messages | ||
| /// that will be used. Message AI Context providers can use this information to determine what additional messages | ||
| /// should be provided for the invocation. | ||
| /// </remarks> | ||
| public new sealed class InvokingContext | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="InvokingContext"/> class with the specified request messages. | ||
| /// </summary> | ||
| /// <param name="agent">The agent being invoked.</param> | ||
| /// <param name="session">The session associated with the agent invocation.</param> | ||
| /// <param name="requestMessages">The messages to be used by the agent for this invocation.</param> | ||
| /// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception> | ||
| public InvokingContext( | ||
| AIAgent agent, | ||
| AgentSession? session, | ||
| IEnumerable<ChatMessage> requestMessages) | ||
| { | ||
| this.Agent = Throw.IfNull(agent); | ||
| this.Session = session; | ||
| this.RequestMessages = Throw.IfNull(requestMessages); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the agent that is being invoked. | ||
| /// </summary> | ||
| public AIAgent Agent { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the agent session associated with the agent invocation. | ||
| /// </summary> | ||
| public AgentSession? Session { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the messages that will be used by the agent for this invocation. <see cref="ChatHistoryProvider"/> instances can modify | ||
| /// and return or return a new message list to add additional messages for the invocation. | ||
| /// </summary> | ||
| /// <value> | ||
| /// A collection of <see cref="ChatMessage"/> instances representing the messages that will be used by the agent for this invocation. | ||
| /// </value> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// If multiple <see cref="ChatHistoryProvider"/> instances are used in the same invocation, each <see cref="ChatHistoryProvider"/> | ||
| /// will receive the messages returned by the previous <see cref="ChatHistoryProvider"/> allowing them to build on top of each other's context. | ||
| /// </para> | ||
| /// <para> | ||
| /// The first <see cref="ChatHistoryProvider"/> in the invocation pipeline will receive the | ||
| /// caller provided messages. | ||
| /// </para> | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </remarks> | ||
| public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.