// 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.
///
///
///
/// An AI context provider is a component that participates in the agent invocation lifecycle by:
///
/// - Listening to changes in conversations
/// - Providing additional context to agents during invocation
/// - Supplying additional function tools for enhanced capabilities
/// - 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.
///
///
/// Security considerations: Context providers may inject messages with any role, including system, which
/// has the highest trust level and directly shapes LLM behavior. Developers must ensure that all providers attached to an agent
/// are trusted. Agent Framework does not validate or filter the data returned by providers — it is accepted as-is and merged into
/// the request context. If a provider retrieves data from an external source (e.g., a vector database or memory service), be aware
/// that a compromised data source could introduce adversarial content designed to manipulate LLM behavior via indirect prompt injection.
/// Implementers should validate and sanitize data retrieved from external sources before returning it.
///
///
public abstract class AIContextProvider
{
private static IEnumerable DefaultExternalOnlyFilter(IEnumerable messages)
=> messages.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External);
private static IEnumerable DefaultNoopFilter(IEnumerable messages)
=> messages;
private IReadOnlyList? _stateKeys;
///
/// Initializes a new instance of the class.
///
/// An optional filter function to apply to input messages before providing context via . If not set, defaults to including only messages.
/// An optional filter function to apply to request messages before storing context via . If not set, defaults to including only messages.
/// An optional filter function to apply to response messages before storing context via . If not set, defaults to a no-op filter that includes all response messages.
protected AIContextProvider(
Func, IEnumerable>? provideInputMessageFilter = null,
Func, IEnumerable>? storeInputRequestMessageFilter = null,
Func, IEnumerable>? storeInputResponseMessageFilter = null)
{
this.ProvideInputMessageFilter = provideInputMessageFilter ?? DefaultExternalOnlyFilter;
this.StoreInputRequestMessageFilter = storeInputRequestMessageFilter ?? DefaultExternalOnlyFilter;
this.StoreInputResponseMessageFilter = storeInputResponseMessageFilter ?? DefaultNoopFilter;
}
///
/// Gets the filter function to apply to input messages before providing context via .
///
protected Func, IEnumerable> ProvideInputMessageFilter { get; }
///
/// Gets the filter function to apply to request messages before storing context via .
///
protected Func, IEnumerable> StoreInputRequestMessageFilter { get; }
///
/// Gets the filter function to apply to response messages before storing context via .
///
protected Func, IEnumerable> StoreInputResponseMessageFilter { get; }
///
/// Gets the set of keys used to store the provider state in the .
///
///
/// The default value is a single-element set containing the name of the concrete type (e.g. "TextSearchProvider").
/// Implementations may override this to provide custom keys, for example when multiple
/// instances of the same provider type are used in the same session, or when a provider
/// stores state under more than one key.
///
public virtual IReadOnlyList StateKeys => this._stateKeys ??= [this.GetType().Name];
///
/// Called at the start of agent invocation to provide additional context.
///
/// 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 with additional context to be used by the agent during this invocation.
///
///
/// Implementers can load any additional context required at this time, such as:
///
/// - Retrieving relevant information from knowledge bases
/// - Adding system instructions or prompts
/// - Providing function tools for the current invocation
/// - Injecting contextual messages from conversation history
///
///
///
/// Security consideration: Data retrieved from external sources (e.g., vector databases, memory services, or
/// knowledge bases) may contain adversarial content designed to influence LLM behavior via indirect prompt injection.
/// Implementers should validate data integrity and consider the trustworthiness of the data source.
///
///
public ValueTask InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
=> this.InvokingCoreAsync(Throw.IfNull(context), cancellationToken);
///
/// Called at the start of agent invocation to provide additional context.
///
/// 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 with additional context to be used by the agent during this invocation.
///
///
/// Implementers can load any additional context required at this time, such as:
///
/// - Retrieving relevant information from knowledge bases
/// - Adding system instructions or prompts
/// - Providing function tools for the current invocation
/// - 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 context,
/// stamps any messages from the returned context with source attribution,
/// and merges the returned context with the original (unfiltered) input context (concatenating instructions, messages, and tools).
/// For most scenarios, overriding is sufficient to provide additional context,
/// while still benefiting from the default filtering, merging and source stamping behavior.
/// However, for scenarios that require more control over context 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 inputContext = context.AIContext;
// Create a filtered context for ProvideAIContextAsync, 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,
new AIContext
{
Instructions = inputContext.Instructions,
Messages = inputContext.Messages is not null ? this.ProvideInputMessageFilter(inputContext.Messages) : null,
Tools = inputContext.Tools
});
#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 provided = await this.ProvideAIContextAsync(filteredContext, cancellationToken).ConfigureAwait(false);
var mergedInstructions = (inputContext.Instructions, provided.Instructions) switch
{
(null, null) => null,
(string a, null) => a,
(null, string b) => b,
(string a, string b) => a + "\n" + b
};
var providedMessages = provided.Messages is not null
? provided.Messages.Select(m => m.WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!))
: null;
var mergedMessages = (inputContext.Messages, providedMessages) switch
{
(null, null) => null,
(var a, null) => a,
(null, var b) => b,
(var a, var b) => a.Concat(b)
};
var mergedTools = (inputContext.Tools, provided.Tools) switch
{
(null, null) => null,
(var a, null) => a,
(null, var b) => b,
(var a, var b) => a.Concat(b)
};
return new AIContext
{
Instructions = mergedInstructions,
Messages = mergedMessages,
Tools = mergedTools
};
}
///
/// When overridden in a derived class, provides additional AI context to be merged with the input context for the current invocation.
///
///
///
/// This method is called from .
/// Note that can be overridden to directly control context merging and source stamping, in which case
/// it is up to the implementer to call this method as needed to retrieve the additional context.
///
///
/// In contrast with , this method only returns additional context to be merged with the input,
/// while is responsible for returning the full merged for the invocation.
///
///
/// Security consideration: Any messages, tools, or instructions returned by this method will be merged into the
/// AI request context. If data is retrieved from external or untrusted sources, implementers should validate and sanitize it
/// to prevent indirect prompt injection attacks.
///
///
/// 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 context to be merged with the input context.
///
protected virtual ValueTask ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
return new ValueTask(new AIContext());
}
///
/// Called at the end of the agent invocation to process the invocation results.
///
/// Contains the invocation context including request messages, response messages, and any exception that occurred.
/// The to monitor for cancellation requests. The default is .
/// A task that represents the asynchronous operation.
///
///
/// Implementers can use the request and response messages in the provided to:
///
/// - Update state based on conversation outcomes
/// - Extract and store memories or preferences from user messages
/// - Log or audit conversation details
/// - Perform cleanup or finalization tasks
///
///
///
/// The is passed a reference to the via and
/// allowing it to store state in the . Since an is used with many different sessions, it should
/// not store any session-specific information within its own instance fields. Instead, any session-specific state should be stored in the associated .
///
///
/// This method is called regardless of whether the invocation succeeded or failed.
/// To check if the invocation was successful, inspect the property.
///
///
public ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
=> this.InvokedCoreAsync(Throw.IfNull(context), cancellationToken);
///
/// Called at the end of the agent invocation to process the invocation results.
///
/// Contains the invocation context including request messages, response messages, and any exception that occurred.
/// The to monitor for cancellation requests. The default is .
/// A task that represents the asynchronous operation.
///
///
/// Implementers can use the request and response messages in the provided to:
///
/// - Update internal state based on conversation outcomes
/// - Extract and store memories or preferences from user messages
/// - Log or audit conversation details
/// - Perform cleanup or finalization tasks
///
///
///
/// This method is called regardless of whether the invocation succeeded or failed.
/// To check if the invocation was successful, inspect the property.
///
///
/// The default implementation of this method skips execution for any invocation failures,
/// filters the request messages using the configured store-input request message filter
/// (which defaults to including only 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 to process the invocation results.
/// For most scenarios, overriding is sufficient to process invocation results,
/// 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 processing of invocation results.
///
///
protected virtual ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
if (context.InvokeException is not null)
{
return default;
}
#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 subContext = new InvokedContext(context.Agent, context.Session, this.StoreInputRequestMessageFilter(context.RequestMessages), this.StoreInputResponseMessageFilter(context.ResponseMessages!));
#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.
return this.StoreAIContextAsync(subContext, cancellationToken);
}
///
/// When overridden in a derived class, processes invocation results at the end of the agent invocation.
///
/// Contains the invocation context including request messages, response messages, and any exception that occurred.
/// The to monitor for cancellation requests. The default is .
/// A task that represents the asynchronous operation.
///
///
/// This method is called from .
/// Note that can be overridden to directly control error handling, in which case
/// it is up to the implementer to call this method as needed to process the invocation results.
///
///
/// In contrast with , this method only processes the invocation results,
/// while is also responsible for error handling.
///
///
/// The default implementation of only calls this method if the invocation succeeded.
///
///
/// Security consideration: Messages being processed/stored may contain PII and sensitive conversation content.
/// Implementers should ensure appropriate encryption at rest and access controls for the storage backend.
///
///
protected virtual ValueTask StoreAIContextAsync(InvokedContext context, CancellationToken cancellationToken = default) =>
default;
/// Asks the for an object of the specified type .
/// The type of object being requested.
/// An optional key that can be used to help identify the target service.
/// The found object, otherwise .
/// is .
///
/// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the ,
/// including itself or any services it might be wrapping. This enables advanced scenarios where consumers need access to
/// specific provider implementations or their internal services.
///
public virtual object? GetService(Type serviceType, object? serviceKey = null)
{
_ = Throw.IfNull(serviceType);
return serviceKey is null && serviceType.IsInstanceOfType(this)
? this
: null;
}
/// Asks the for an object of type .
/// The type of the object to be retrieved.
/// An optional key that can be used to help identify the target service.
/// The found object, otherwise .
///
/// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the ,
/// including itself or any services it might be wrapping. This is a convenience overload of .
///
public TService? GetService(object? serviceKey = null)
=> this.GetService(typeof(TService), serviceKey) is TService service ? service : default;
///
/// 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. Context providers can use this information to determine what additional context
/// should be provided for the invocation.
///
public sealed class InvokingContext
{
///
/// Initializes a new instance of the class.
///
/// The agent being invoked.
/// The session associated with the agent invocation.
/// The AI context to be used by the agent for this invocation.
/// or is .
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public InvokingContext(
AIAgent agent,
AgentSession? session,
AIContext aiContext)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.AIContext = Throw.IfNull(aiContext);
}
///
/// 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 being built for the current invocation. Context providers can modify
/// and return or return a new instance to provide additional context for the invocation.
///
///
///
/// If multiple instances are used in the same invocation, each
/// will receive the context returned by the previous allowing them to build on top of each other's context.
///
///
/// The first in the invocation pipeline will receive an instance
/// that already contains the caller provided messages that will be used by the agent for this invocation.
///
///
/// It may also contain messages from chat history, if a is being used.
///
///
public AIContext AIContext { get; }
}
///
/// Contains the context information provided to .
///
///
/// This class provides context about a completed agent invocation, including the accumulated
/// request messages (user input, chat history and any others provided by AI context providers) that were used
/// and the response messages that were generated. It also indicates whether the invocation succeeded or failed.
///
public sealed class InvokedContext
{
///
/// Initializes a new instance of the class for a successful invocation.
///
/// The agent that was invoked.
/// The session associated with the agent invocation.
/// The accumulated request messages (user input, chat history and any others provided by AI context providers)
/// that were used by the agent for this invocation.
/// The response messages generated during this invocation.
/// , , or is .
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable requestMessages,
IEnumerable responseMessages)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.ResponseMessages = Throw.IfNull(responseMessages);
}
///
/// Initializes a new instance of the class for a failed invocation.
///
/// The agent that was invoked.
/// The session associated with the agent invocation.
/// The accumulated request messages (user input, chat history and any others provided by AI context providers)
/// that were used by the agent for this invocation.
/// The exception that caused the invocation to fail.
/// , , or is .
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable requestMessages,
Exception invokeException)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.InvokeException = Throw.IfNull(invokeException);
}
///
/// 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 accumulated request messages (user input, chat history and any others provided by AI context providers)
/// that were used by the agent for this invocation.
///
///
/// A collection of instances representing all messages that were used by the agent for this invocation.
///
public IEnumerable RequestMessages { get; }
///
/// Gets the collection of response messages generated during this invocation if the invocation succeeded.
///
///
/// A collection of instances representing the response,
/// or if the invocation failed.
///
public IEnumerable? ResponseMessages { get; }
///
/// Gets the that was thrown during the invocation, if the invocation failed.
///
///
/// The exception that caused the invocation to fail, or if the invocation succeeded.
///
public Exception? InvokeException { get; }
}
}