// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
///
/// Provides an in-memory implementation of with support for message reduction.
///
///
///
/// stores chat messages in the ,
/// providing fast access and manipulation capabilities integrated with session state management.
///
///
/// This maintains all messages in memory. For long-running conversations or high-volume scenarios, consider using
/// message reduction strategies or alternative storage implementations.
///
///
public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
{
private readonly ProviderSessionState _sessionState;
private IReadOnlyList? _stateKeys;
///
/// Initializes a new instance of the class.
///
///
/// Optional configuration options that control the provider's behavior, including state initialization,
/// message reduction, and serialization settings. If , default settings will be used.
///
public InMemoryChatHistoryProvider(InMemoryChatHistoryProviderOptions? options = null)
: base(
options?.ProvideOutputMessageFilter,
options?.StorageInputRequestMessageFilter,
options?.StorageInputResponseMessageFilter)
{
this._sessionState = new ProviderSessionState(
options?.StateInitializer ?? (_ => new State()),
options?.StateKey ?? this.GetType().Name,
options?.JsonSerializerOptions);
this.ChatReducer = options?.ChatReducer;
this.ReducerTriggerEvent = options?.ReducerTriggerEvent ?? InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.BeforeMessagesRetrieval;
}
///
public override IReadOnlyList StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
///
/// Gets the chat reducer used to process or reduce chat messages. If null, no reduction logic will be applied.
///
public IChatReducer? ChatReducer { get; }
///
/// Gets the event that triggers the reducer invocation in this provider.
///
public InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent ReducerTriggerEvent { get; }
///
/// Gets the chat messages stored for the specified session.
///
/// The agent session containing the state.
/// A list of chat messages, or an empty list if no state is found.
public List GetMessages(AgentSession? session)
=> this._sessionState.GetOrInitializeState(session).Messages;
///
/// Sets the chat messages for the specified session.
///
/// The agent session containing the state.
/// The messages to store.
/// is .
public void SetMessages(AgentSession? session, List messages)
{
Throw.IfNull(messages);
State state = this._sessionState.GetOrInitializeState(session);
state.Messages = messages;
}
///
protected override async ValueTask> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
State state = this._sessionState.GetOrInitializeState(context.Session);
if (this.ReducerTriggerEvent is InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.BeforeMessagesRetrieval && this.ChatReducer is not null)
{
// Apply pre-retrieval reduction if configured
await ReduceMessagesAsync(this.ChatReducer, state, cancellationToken).ConfigureAwait(false);
}
return state.Messages;
}
///
protected override async ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
State state = this._sessionState.GetOrInitializeState(context.Session);
// Add request and response messages to the provider
var allNewMessages = (context.RequestMessages ?? []).Concat(context.ResponseMessages ?? []);
state.Messages.AddRange(allNewMessages);
if (this.ReducerTriggerEvent is InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded && this.ChatReducer is not null)
{
// Apply pre-write reduction strategy if configured
await ReduceMessagesAsync(this.ChatReducer, state, cancellationToken).ConfigureAwait(false);
}
}
private static async Task ReduceMessagesAsync(IChatReducer reducer, State state, CancellationToken cancellationToken = default)
{
state.Messages = [.. await reducer.ReduceAsync(state.Messages, cancellationToken).ConfigureAwait(false)];
}
///
/// Represents the state of a stored in the .
///
public sealed class State
{
///
/// Gets or sets the list of chat messages.
///
[JsonPropertyName("messages")]
public List Messages { get; set; } = [];
}
}