儲存控制對話歷史紀錄的存放位置、載入的歷史紀錄量,以及會話恢復的可靠性。
內建儲存模式
代理框架支援兩種一般儲存模式:
| 模式 | 儲存的內容 | 典型用法 |
|---|---|---|
| 地方會期狀態 | 完整聊天紀錄位於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 方法皆會傳遞一個參考至當前 AIAgent 和 AgentSession。
為了方便地在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提供InvokingCoreAsync及InvokedCoreAsync的基礎實作。
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=False、 store_context_messages=True)來捕捉豐富的上下文及輸入/輸出,而不影響主要歷史載入。