共用方式為


儲存體

儲存控制對話歷史紀錄的存放位置、載入的歷史紀錄量,以及會話恢復的可靠性。

內建儲存模式

代理框架支援兩種一般儲存模式:

模式 儲存的內容 典型用法
地方會期狀態 完整聊天紀錄位於AgentSession.state(例如透過InMemoryHistoryProvider 不需要伺服器端對話持久性的服務
服務代管儲存空間 服務中的對話狀態; AgentSession.service_session_id 指向它 具備原生持續對話支援的服務

記憶體內聊天記錄儲存

當提供者不需要伺服器端的聊天記錄時,Agent Framework 會在會話中本地保存歷史,並在每次執行時發送相關訊息。

AIAgent agent = new OpenAIClient("<your_api_key>")
    .GetChatClient(modelName)
    .AsAIAgent(instructions: "You are a helpful assistant.", name: "Assistant");

AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));

// When in-memory chat history storage is used, it's possible to access the chat history
// that is stored in the session via the provider attached to the agent.
var provider = agent.GetService<InMemoryChatHistoryProvider>();
List<ChatMessage>? messages = provider?.GetMessages(session);
from agent_framework import InMemoryHistoryProvider
from agent_framework.openai import OpenAIChatClient

agent = OpenAIChatClient().as_agent(
    name="StorageAgent",
    instructions="You are a helpful assistant.",
    context_providers=[InMemoryHistoryProvider("memory", load_messages=True)],
)

session = agent.create_session()
await agent.run("Remember that I like Italian food.", session=session)

縮小記憶體歷史大小

如果歷史資料過大超出模型限制,則可使用縮減器。

AIAgent agent = new OpenAIClient("<your_api_key>")
    .GetChatClient(modelName)
    .AsAIAgent(new ChatClientAgentOptions
    {
        Name = "Assistant",
        ChatOptions = new() { Instructions = "You are a helpful assistant." },
        ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
        {
            ChatReducer = new MessageCountingChatReducer(20)
        })
    });

備註

縮減器配置適用於記憶體內歷史提供者。 對於由服務管理的歷史紀錄,減少行為取決於提供者或服務的具體要求。

服務代管儲存空間

當服務管理對話歷史時,會話會儲存遠端對話識別碼。

AIAgent agent = new OpenAIClient("<your_api_key>")
    .GetOpenAIResponseClient(modelName)
    .AsAIAgent(instructions: "You are a helpful assistant.", name: "Assistant");

AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));

// In this case, since we know we are working with a ChatClientAgent, we can cast
// the AgentSession to a ChatClientAgentSession to retrieve the remote conversation
// identifier.
ChatClientAgentSession typedSession = (ChatClientAgentSession)session;
Console.WriteLine(typedSession.ConversationId);
# Rehydrate when the service already has the conversation state.
session = agent.get_session(service_session_id="<service-conversation-id>")
response = await agent.run("Continue this conversation.", session=session)

第三方/自訂儲存模式

對於資料庫/Redis/blob 備份的歷史,請實作自訂歷史提供者。

主要指引:

  • 將訊息儲存在會話範圍的密鑰下。
  • 將回傳的歷史記錄保持在模型上下文的範圍內。
  • 在會話狀態中持續保留提供者專屬識別碼。

歷史提供者的基底類別為 Microsoft.Agents.AI.ChatHistoryProvider。 歷史提供者參與代理管線,能貢獻或覆寫代理輸入訊息,並可儲存新訊息。 ChatHistoryProvider 具有多個虛擬方法,您可以覆寫從而實作自己的自訂歷史紀錄提供者。 您可以參閱下方的各種實施方案,以了解應該覆寫的部分。

ChatHistoryProvider 狀態

一個 ChatHistoryProvider 實例會附加到代理,所有會話都會使用同一個實例。 這表示 在 ChatHistoryProvider 提供者實例中不應儲存任何特定會話狀態。 欄位 ChatHistoryProvider 裡可能有資料庫用戶端的參照,但在欄位中不應該有用於聊天紀錄的資料庫金鑰。

相反地,ChatHistoryProvider 可能會儲存任何會話特定的值,例如資料庫鍵、訊息或其他與 AgentSession 本身相關的內容。 虛擬 ChatHistoryProvider 方法皆會傳遞一個參考至當前 AIAgentAgentSession

為了方便地在AgentSession中儲存型別狀態,提供了一個工具類別:

// First define a type containing the properties to store in state
internal class MyCustomState
{
    public string? DbKey { get; set; }
}

// Create the helper
var sessionStateHelper = new ProviderSessionState<MyCustomState>(
    // stateInitializer is called when there is no state in the session for this ChatHistoryProvider yet
    stateInitializer: currentSession => new MyCustomState() { DbKey = Guid.NewGuid().ToString() },
    // The key under which to store state in the session for this provider. Make sure it does not clash with the keys of other providers.
    stateKey: this.GetType().Name,
    // An optional jsonSerializerOptions to control the serialization/deserialization of the custom state object
    jsonSerializerOptions: myJsonSerializerOptions);

// Using the helper you can read state:
MyCustomState state = sessionStateHelper.GetOrInitializeState(session);
Console.WriteLine(state.DbKey);

// And write state:
sessionStateHelper.SaveState(session, state);

簡單 ChatHistoryProvider 實作

ChatHistoryProvider 簡單的實作通常會覆蓋兩種方法:

  • ChatHistoryProvider.ProvideChatHistoryAsync - 載入相關的聊天紀錄並回傳載入的訊息。
  • ChatHistoryProvider.StoreChatHistoryAsync - 儲存請求與回應訊息,這些訊息都應該是新的。

這裡有一個 ChatHistoryProvider 簡單範例,直接將聊天歷史儲存在會話狀態中。

public sealed class SimpleInMemoryChatHistoryProvider : ChatHistoryProvider
{
    private readonly ProviderSessionState<State> _sessionState;

    public SimpleInMemoryChatHistoryProvider(
        Func<AgentSession?, State>? stateInitializer = null,
        string? stateKey = null)
    {
        this._sessionState = new ProviderSessionState<State>(
            stateInitializer ?? (_ => new State()),
            stateKey ?? this.GetType().Name);
    }

    public override string StateKey => this._sessionState.StateKey;

    protected override ValueTask<IEnumerable<ChatMessage>> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default) =>
        // return all messages in the session state
        new(this._sessionState.GetOrInitializeState(context.Session).Messages);

    protected override ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default)
    {
        var state = this._sessionState.GetOrInitializeState(context.Session);

        // Add both request and response messages to the session state.
        var allNewMessages = context.RequestMessages.Concat(context.ResponseMessages ?? []);
        state.Messages.AddRange(allNewMessages);

        this._sessionState.SaveState(context.Session, state);

        return default;
    }

    public sealed class State
    {
        [JsonPropertyName("messages")]
        public List<ChatMessage> Messages { get; set; } = [];
    }
}

進階 ChatHistoryProvider 實作

更進階的實作可以選擇覆寫以下方法:

  • ChatHistoryProvider.InvokingCoreAsync - 在代理呼叫 LLM 並允許修改請求訊息清單之前呼叫。
  • ChatHistoryProvider.InvokedCoreAsync - 在代理呼叫 LLM 後呼叫,允許存取所有請求與回應訊息。

ChatHistoryProvider提供InvokingCoreAsyncInvokedCoreAsync的基礎實作。

InvokingCoreAsync基礎實作的做法如下:

  • 呼叫 ProvideChatHistoryAsync 來取得應用於執行聊天記錄的訊息
  • 對由 返回的Func訊息執行可選過濾器。provideOutputMessageFilterProvideChatHistoryAsync 濾波器 Func 可由 ChatHistoryProvider 建構子提供。
  • 將由 回 ProvideChatHistoryAsync 傳的過濾訊息與呼叫者傳入代理的訊息合併,產生代理請求訊息。 聊天紀錄會加在客服輸入訊息的前面。
  • 蓋章所有已過濾的 ProvideChatHistoryAsync 訊息,並附上來源資訊,表示這些訊息來自聊天歷史。

InvokedCoreAsync底座的功能如下:

  • 檢查執行是否失敗,若失敗則返回,無需進一步處理。
  • 過濾代理請求訊息,排除由 ChatHistoryProvider產生的訊息,因為我們只想儲存新訊息,而非最初由 產生 ChatHistoryProvider 的訊息。 請注意,這個過濾器可以透過ChatHistoryProvider建構子上的storeInputMessageFilter參數來覆寫。
  • 將過濾過的請求訊息及所有回應訊息傳送至 StoreChatHistoryAsync 儲存。

你可以覆蓋這些方法以實現ChatHistoryProvider,不過,這需要實施者適當地自行設計基礎功能。 這裡有一個此類實作的範例。

public sealed class AdvancedInMemoryChatHistoryProvider : ChatHistoryProvider
{
    private readonly ProviderSessionState<State> _sessionState;

    public AdvancedInMemoryChatHistoryProvider(
        Func<AgentSession?, State>? stateInitializer = null,
        string? stateKey = null)
    {
        this._sessionState = new ProviderSessionState<State>(
            stateInitializer ?? (_ => new State()),
            stateKey ?? this.GetType().Name);
    }

    public override string StateKey => this._sessionState.StateKey;

    protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
    {
        // Retrieve the chat history from the session state.
        var chatHistory = this._sessionState.GetOrInitializeState(context.Session).Messages;

        // Stamp the messages with this class as the source, so that they can be filtered out later if needed when storing the agent input/output.
        var stampedChatHistory = chatHistory.Select(message => message.WithAgentRequestMessageSource(AgentRequestMessageSourceType.ChatHistory, this.GetType().FullName!));

        // Merge the original input with the chat history to produce a combined agent input.
        return new(stampedChatHistory.Concat(context.RequestMessages));
    }

    protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
    {
        if (context.InvokeException is not null)
        {
            return default;
        }

        // Since we are receiving all messages that were contributed earlier, including those from chat history, we need to filter out the messages that came from chat history
        // so that we don't store message we already have in storage.
        var filteredRequestMessages = context.RequestMessages.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory);

        var state = this._sessionState.GetOrInitializeState(context.Session);

        // Add both request and response messages to the state.
        var allNewMessages = filteredRequestMessages.Concat(context.ResponseMessages ?? []);
        state.Messages.AddRange(allNewMessages);

        this._sessionState.SaveState(context.Session, state);

        return default;
    }

    public sealed class State
    {
        [JsonPropertyName("messages")]
        public List<ChatMessage> Messages { get; set; } = [];
    }
}
  • 在 Python 中,只有一個歷史提供者應該使用 load_messages=True
from agent_framework.openai import OpenAIChatClient

history = DatabaseHistoryProvider(db_client)
agent = OpenAIChatClient().as_agent(
    name="StorageAgent",
    instructions="You are a helpful assistant.",
    context_providers=[history],
)

session = agent.create_session()
await agent.run("Store this conversation.", session=session)

重啟後繼續維持會話

持續保存完整的 AgentSession訊息,而非僅是訊息文字。

JsonElement serialized = agent.SerializeSession(session);
// Store serialized payload in durable storage.
AgentSession resumed = await agent.DeserializeSessionAsync(serialized);
serialized = session.to_dict()
# Store serialized payload in durable storage.
resumed = AgentSession.from_dict(serialized)

這很重要

AgentSession 視為不透明狀態物件,並使用創建它的相同代理程式/提供者設定來還原它。

小提示

使用額外的審計/評估歷史提供者(load_messages=Falsestore_context_messages=True)來捕捉豐富的上下文及輸入/輸出,而不影響主要歷史載入。

後續步驟