// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// /// Provides an abstract base class for components that enhance AI context during agent invocations by supplying additional chat messages. /// /// /// /// A message AI context provider is a component that participates in the agent invocation lifecycle by: /// /// Listening to changes in conversations /// Providing additional messages to agents during invocation /// Processing invocation results for state management or learning /// /// /// /// Context providers operate through a two-phase lifecycle: they are called at the start of invocation via /// to provide context, and optionally called at the end of invocation via /// to process results. /// /// public abstract class MessageAIContextProvider : AIContextProvider { /// /// Initializes a new instance of the class. /// /// An optional filter function to apply to input messages before providing messages via . If not set, defaults to including only messages. /// An optional filter function to apply to request messages before storing messages via . If not set, defaults to including only messages. /// An optional filter function to apply to response messages before storing messages via . If not set, defaults to including all response messages (no filtering). protected MessageAIContextProvider( Func, IEnumerable>? provideInputMessageFilter = null, Func, IEnumerable>? storeInputRequestMessageFilter = null, Func, IEnumerable>? storeInputResponseMessageFilter = null) : base(provideInputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter) { } /// protected override async ValueTask 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. #pragma warning disable MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. return new AIContext { Messages = await this.ProvideMessagesAsync( new InvokingContext(context.Agent, context.Session, context.AIContext.Messages ?? []), cancellationToken).ConfigureAwait(false) }; #pragma warning restore MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. } /// /// Called at the start of agent invocation to provide additional messages. /// /// Contains the request context including the caller provided messages that will be used by the agent for this invocation. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. The task result contains the to be used by the agent during this invocation. /// /// /// Implementers can load any additional messages required at this time, such as: /// /// Retrieving relevant information from knowledge bases /// Adding system instructions or prompts /// Injecting contextual messages from conversation history /// /// /// public ValueTask> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default) => this.InvokingCoreAsync(Throw.IfNull(context), cancellationToken); /// /// Called at the start of agent invocation to provide additional messages. /// /// Contains the request context including the caller provided messages that will be used by the agent for this invocation. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. The task result contains the to be used by the agent during this invocation. /// /// /// Implementers can load any additional messages required at this time, such as: /// /// Retrieving relevant information from knowledge bases /// Adding system instructions or prompts /// Injecting contextual messages from conversation history /// /// /// /// The default implementation of this method filters the input messages using the configured provide-input message filter /// (which defaults to including only messages), /// then calls to get additional messages, /// stamps any messages with source attribution, /// and merges the returned messages with the original (unfiltered) input messages. /// For most scenarios, overriding 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 returned for the invocation. /// /// protected virtual async ValueTask> 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). #pragma warning disable MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var filteredContext = new InvokingContext( context.Agent, context.Session, this.ProvideInputMessageFilter(inputMessages)); #pragma warning restore MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 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); } /// /// When overridden in a derived class, provides additional messages to be merged with the input messages for the current invocation. /// /// /// /// This method is called from . /// Note that 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. /// /// /// In contrast with , this method only returns additional messages to be merged with the input, /// while is responsible for returning the full merged for the invocation. /// /// /// Contains the request context including the caller provided messages that will be used by the agent for this invocation. /// The to monitor for cancellation requests. The default is . /// /// A task that represents the asynchronous operation. The task result contains an /// with additional messages to be merged with the input messages. /// protected virtual ValueTask> ProvideMessagesAsync(InvokingContext context, CancellationToken cancellationToken = default) { return new ValueTask>([]); } /// /// Contains the context information provided to . /// /// /// 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. /// public new sealed class InvokingContext { /// /// Initializes a new instance of the class with the specified request messages. /// /// The agent being invoked. /// The session associated with the agent invocation. /// The messages to be used by the agent for this invocation. /// or is . [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] public InvokingContext( AIAgent agent, AgentSession? session, IEnumerable requestMessages) { this.Agent = Throw.IfNull(agent); this.Session = session; this.RequestMessages = Throw.IfNull(requestMessages); } /// /// Gets the agent that is being invoked. /// public AIAgent Agent { get; } /// /// Gets the agent session associated with the agent invocation. /// public AgentSession? Session { get; } /// /// Gets the messages that will be used by the agent for this invocation. instances can modify /// and return or return a new message list to add additional messages for the invocation. /// /// /// A collection of instances representing the messages that will be used by the agent for this invocation. /// /// /// /// If multiple instances are used in the same invocation, each /// will receive the messages returned by the previous allowing them to build on top of each other's context. /// /// /// The first in the invocation pipeline will receive the /// caller provided messages. /// /// public IEnumerable RequestMessages { get; set { field = Throw.IfNull(value); } } } }