Storage

Úložiště určuje, kde se nachází historie konverzací, kolik historie se načte a jak spolehlivě se dají relace obnovit.

Integrované režimy úložiště

Agent Framework podporuje dva běžné režimy úložiště:

Režim Co je uloženo Typické použití
Místní stav relace Úplná historie chatu v AgentSession.state (například prostřednictvím InMemoryHistoryProvider) Služby, které nevyžadují trvalost konverzací na straně serveru
Úložiště spravované službou Stav konverzace ve službě; AgentSession.service_session_id odkazuje na ni Služby s nativní podporou trvalých konverzací

Úložiště historie chatu v paměti

Pokud poskytovatel nevyžaduje historii chatu na straně serveru, agent Framework uchovává historii místně v relaci a odesílá relevantní zprávy při každém spuštění.

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)

Zmenšení velikosti historie v paměti

Pokud se historie pro limity modelů příliš rozrůstá, použijte redukční faktor.

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)
        })
    });

Poznámka:

Konfigurace redukčního nástroje se vztahuje na zprostředkovatele historie v paměti. V případě historie spravované služby je chování redukce specifické pro poskytovatele nebo službu.

Úložiště spravované službou

Když služba spravuje historii konverzací, relace uloží identifikátor vzdálené konverzace.

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)

Ukládání historie pro jednotlivá volání služby

Spuštění volání nástrojů může provádět více volání modelu před dokončením jednoho agent.run(). Ve výchozím nastavení zprostředkovatelé místní historie se uloží jednou po úplném spuštění. Pokud chcete, aby místní historie lépe zrcadlila konverzace spravované službou, nastavte require_per_service_call_history_persistence=True , aby poskytovatelé historie místo toho běželi kolem jednotlivých volání modelu.

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,
)

Důležité

Tento režim použijte pouze pro místní historii spravovanou rámcem. Pokud je spuštění již vázáno na konverzaci spravovanou službou (například prostřednictvím session.service_session_id nebo options={"conversation_id": ...}), agent Framework vyvolá chybu místo kombinování těchto dvou modelů trvalosti.

Tento režim je zvlášť užitečný, když se middleware může ukončit okamžitě po volání nástroje: při zachování na jedno volání modelu udržuje místní historii v souladu s tím, jak by ji vedla spravovaná služba.

Model úložiště třetích stran nebo vlastní úložiště

Pro databázi, Redis nebo historii založené na objektech blob implementujte vlastního poskytovatele historie.

Klíčové pokyny:

  • Ukládat zprávy pod klíčem s rozsahovou platností relace.
  • Uchovávejte vrácenou historii v rámci omezení kontextu modelu.
  • Zachovají identifikátory specifické pro zprostředkovatele ve stavu relace.

Základní třída pro poskytovatele historie je Microsoft.Agents.AI.ChatHistoryProvider. Poskytovatelé historie se účastní kanálu agenta, mají možnost přispívat do vstupních zpráv agenta nebo je přepsat a můžou ukládat nové zprávy. ChatHistoryProvider má různé virtuální metody, které je možné přepsat k implementaci vlastního poskytovatele historie. Další informace o tom, co je potřeba přepsat, jsou uvedeny v možnostech implementace níže.

ChatHistoryProvider Stav

Instance ChatHistoryProvider je přiřazena agentovi a stejná instance by se používala pro všechny relace. To znamená, že ChatHistoryProvider v instanci poskytovatele by se neměl ukládat žádný konkrétní stav relace. Může ChatHistoryProvider mít odkaz na databázového klienta v poli, ale neměl by mít v poli klíč databáze pro historii chatu.

Místo toho může ChatHistoryProvider ukládat jakékoli hodnoty specifické pro relaci, jako jsou databázové klíče, zprávy nebo cokoli jiného, co je relevantní samo o sobě v AgentSession. Všem virtuálním metodám ChatHistoryProvider se předává odkaz na aktuální AIAgent a AgentSession.

Chcete-li snadno ukládat stav s určeným typem v AgentSession, je k dispozici pomocná třída:

// 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);

Jednoduchá ChatHistoryProvider implementace

Nejjednodušší ChatHistoryProvider implementace by obvykle přepsala dvě metody:

  • ChatHistoryProvider.ProvideChatHistoryAsync – Načtěte relevantní historii chatu a vraťte načtené zprávy.
  • ChatHistoryProvider.StoreChatHistoryAsync – zprávy žádosti o uložení a odpovědi, z nichž všechny by měly být nové.

Tady je příklad jednoduchého ChatHistoryProvider uložení historie chatu přímo do stavu relace.

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; } = [];
    }
}

Pokročilá ChatHistoryProvider implementace

Pokročilejší implementace by se mohla rozhodnout přepsat následující metody:

  • ChatHistoryProvider.InvokingCoreAsync – Volá se před tím, než agent vyvolá LLM, a umožňuje úpravu seznamu zpráv žádosti.
  • ChatHistoryProvider.InvokedCoreAsync – je vyvoláno poté, co agent spustí LLM a umožní přístup ke všem zprávám o požadavcích a odpovědích.

ChatHistoryProvider poskytuje základní implementace InvokingCoreAsync a InvokedCoreAsync.

Základní InvokingCoreAsync implementace provede následující:

  • volání ProvideChatHistoryAsync pro získání zpráv, které by se měly použít jako historie chatu pro spuštění
  • spustí volitelný filtr FuncprovideOutputMessageFilter zpráv vrácených uživatelem ProvideChatHistoryAsync. Tento filtr Func lze zadat prostřednictvím konstruktoru ChatHistoryProvider .
  • sloučí filtrované zprávy, vrácené pomocí ProvideChatHistoryAsync, se zprávami předávanými volajícím do agenta k vytvoření žádostí agenta. Historie chatu je připojena před vstupní zprávy agenta.
  • označí všechny filtrované zprávy vrácené ProvideChatHistoryAsync zdrojovými informacemi, které označují, že tyto zprávy pocházejí z historie chatu.

Základ InvokedCoreAsync dělá následující:

  • zkontroluje, jestli spuštění selhalo, a pokud ano, vrátí se bez dalšího zpracování.
  • filtruje požadavky agenta na zprávy tak, aby byly vyloučeny ty, které byly vytvořeny aplikací ChatHistoryProvider, protože chceme ukládat pouze nové zprávy, nikoli ty, které vytvořil ChatHistoryProvider původně. Všimněte si, že tento filtr lze přepsat pomocí storeInputMessageFilter parametru v konstruktoru ChatHistoryProvider .
  • předá filtrované požadované zprávy a všechny zprávy odpovědi do StoreChatHistoryAsync úložiště.

Tyto metody je možné přepsat k implementaci ChatHistoryProvider, ale to vyžaduje, aby implementátor sám přiměřeně zajistil základní funkčnost. Tady je příklad takové implementace.

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; } = [];
    }
}
  • V Pythonu by měl používat load_messages=Truepouze jeden poskytovatel historie .
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)

Zachování relací během restartů

Zachovejte celý AgentSession, nejen text zprávy.

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)

Důležité

Zacházejte s AgentSession jako s neprůhledným stavovým objektem a obnovte ho se stejnou konfigurací agenta a poskytovatele, která ho vytvořila.

Návod

Použijte dalšího zprostředkovatele historie auditu/evaluace (load_messages=False, store_context_messages=True) k zachycení rozšířeného kontextu a vstupu/výstupu, aniž by to ovlivnilo načítání primární historie.

Další kroky