Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Хранилище определяет, где живет журнал бесед, сколько загрузится журнал и как надежно можно возобновить сеансы.
Встроенные режимы хранения
Agent Framework поддерживает два регулярных режима хранения:
| Mode | Хранимые данные | Типичное использование |
|---|---|---|
| Состояние локального сеанса | Полная история чата в AgentSession.state (например, через InMemoryHistoryProvider) |
Службы, для которых не требуется сохраняемость бесед на стороне сервера |
| Управляемое службой хранилище | Состояние беседы в службе; AgentSession.service_session_id указывает на него |
Службы с поддержкой собственной постоянной беседы |
Хранилище журнала чата в памяти
Если поставщику не требуется журнал чата на стороне сервера, агент 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)
Сохраняемость локального журнала вызовов по службе
Запуски инструментов могут выполнять несколько вызовов модели до завершения одного agent.run() вызова. По умолчанию поставщики локального журнала сохраняются один раз после полного выполнения. Если вы хотите, чтобы локальная история более точно отражала диалоги, управляемые службой, настройте require_per_service_call_history_persistence=True так, чтобы поставщики истории запускались при каждом вызове модели.
from agent_framework import Agent, InMemoryHistoryProvider
from agent_framework.openai import OpenAIChatClient
agent = Agent(
client=OpenAIChatClient(),
name="StorageAgent",
instructions="You are a helpful assistant.",
context_providers=[InMemoryHistoryProvider("memory", load_messages=True)],
require_per_service_call_history_persistence=True,
)
Это важно
Используйте этот режим только для локальной истории, управляемой фреймворком. Если выполнение уже привязано к беседе, управляемой службой (например, через session.service_session_id или options={"conversation_id": ...}), Agent Framework вызывает ошибку, чтобы избежать смешивания двух моделей сохраняемости.
Этот режим особенно полезен, если посредническое ПО может завершиться немедленно после вызова средства: сохранение данных каждого вызова модели сохраняет локальную историю в соответствии с тем, как это сделал бы управляемый службой разговор.
Шаблон стороннего или пользовательского хранилища
Для хранилища истории на базе базы данных/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для получения сообщений, которые должны использоваться в качестве истории чатов для данного запуска - запускает необязательный фильтр
FuncprovideOutputMessageFilterдля сообщений, возвращаемыхProvideChatHistoryAsync. Этот фильтрFuncможно предоставить с помощью конструктораChatHistoryProvider. - объединяет отфильтрованные сообщения, возвращаемые
ProvideChatHistoryAsync, с сообщениями, переданными вызывающим, чтобы создать сообщения, запрашиваемые агентом. Журнал чата предопределен для входных сообщений агента. - помечает все отфильтрованные сообщения, возвращаемые
ProvideChatHistoryAsync, исходной информацией, указывая на то, что эти сообщения поступают из истории чата.
База InvokedCoreAsync выполняет следующие действия:
- проверяет, завершилось ли выполнение ошибкой и если да, возвращается без дополнительной обработки.
- фильтрует запросы агента, чтобы исключить сообщения, созданные с помощью
ChatHistoryProvider, так как мы хотим хранить только новые сообщения, а не те, которые были изначально созданыChatHistoryProvider. Обратите внимание, что этот фильтр можно переопределить с помощьюstoreInputMessageFilterпараметра конструктораChatHistoryProvider. - передает отфильтрованные сообщения запроса и все ответные сообщения
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), чтобы фиксировать обогащенный контекст и входные/выходные данные, не влияя на загрузку основного журнала.