// 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); } }
}
}