Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O armazenamento controla onde reside o histórico das conversas, quanto histórico é carregado e com que fiabilidade as sessões podem ser retomadas.
Modos de armazenamento incorporados
O Agent Framework suporta dois modos regulares de armazenamento:
| Modo | O que é armazenado | Uso típico |
|---|---|---|
| Estado da sessão local | Histórico completo de chat em AgentSession.state (por exemplo, via InMemoryHistoryProvider) |
Serviços que não requerem persistência de conversas do lado do servidor |
| Armazenamento gerido por serviços | Estado de conversa no serviço; AgentSession.service_session_id aponta para este estado |
Serviços com suporte nativo de conversa persistente |
Armazenamento do histórico de bate-papo na memória
Quando um fornecedor não exige histórico de chat do lado do servidor, o Agent Framework mantém o histórico localmente na sessão e envia mensagens relevantes em cada execução.
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)
Go armazena o histórico de conversas local em agent.Session através de um agent.HistoryProvider. Se não configurar um fornecedor de histórico, o Agent Framework cria um fornecedor predefinido em memória que é usado quando passa uma sessão local explícita. Configura um explicitamente quando quiseres um ID de fonte estável ou filtros personalizados.
history := agent.NewInMemoryHistoryProvider(agent.InMemoryHistoryProviderConfig{
SourceID: "chat_history",
})
a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
Instructions: "You are a helpful assistant.",
Config: agent.Config{
Name: "StorageAgent",
HistoryProvider: history,
},
})
session, err := a.CreateSession(ctx)
if err != nil {
panic(err)
}
_, err = a.RunText(ctx, "Remember that I like Italian food.", agent.WithSession(session)).Collect()
_, err = a.RunText(ctx, "What kind of food do I like?", agent.WithSession(session)).Collect()
Redução do tamanho do histórico em memória
Se o histórico crescer demasiado para os limites do modelo, aplique um redutor.
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)
})
});
Use um HistoryProvider filtro para limitar as mensagens de histórico carregadas no próximo pedido. Por exemplo, guarde apenas as 20 mensagens de histórico mais recentes:
history := agent.NewInMemoryHistoryProvider(agent.InMemoryHistoryProviderConfig{
SourceID: "chat_history",
ProvideOutputMessageFilter: func(_ context.Context, messages []*message.Message) ([]*message.Message, error) {
if len(messages) <= 20 {
return messages, nil
}
return messages[len(messages)-20:], nil
},
})
Para redução semântica ou sensível aos tokens, use uma estratégia de compactação antes de executar, em vez de depender apenas do número de mensagens.
Observação
A configuração do redutor aplica-se a fornecedores de histórico em memória. Para o histórico de gestão de serviços, o comportamento de redução é específico do fornecedor/serviço.
Armazenamento gerido por serviços
Quando o serviço gere o histórico de conversas, a sessão armazena um identificador remoto de conversa.
Para os Responses e Conversations da OpenAI, os IDs do serviço, como resp_* e conv_*, são opacos e estão, por predefinição, associados à chave da API ou ao projeto subjacentes. Isto é geralmente suficiente quando essa chave ou esse projeto já está associado a uma aplicação, utilizador ou locatário. Se hospedar um agente para múltiplos utilizadores finais com a mesma chave de apoio ou projeto, mantenha esses IDs em armazenamento confiável do lado do servidor, mapeie-os a partir dos seus próprios IDs de sessão e verifique a propriedade antes de retomar uma conversa.
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)
O Go armazena identificadores de conversa específicos do fornecedor em session.ServiceID(). Crie uma sessão com um ID de conversa de serviço existente quando precisar de retomar o histórico de gestão de serviços:
session, err := a.CreateSession(ctx, agent.WithServiceID("<service-conversation-id>"))
if err != nil {
panic(err)
}
_, err = a.RunText(ctx, "Continue this conversation.", agent.WithSession(session)).Collect()
Quando um fornecedor cria ou atualiza o identificador de conversa remota durante uma execução, a sessão é atualizada e pode inspecioná-la após a chamada:
fmt.Println(session.ServiceID())
Os fornecedores de histórico local configurados são ignorados para sessões geridas pelo serviço, pelo que o serviço continua a ser a fonte do histórico de conversas.
Persistência do histórico local para cada chamada de serviço
As execuções de chamadas de ferramentas podem fazer múltiplas chamadas de modelo antes de uma única agent.run() ser concluída. Por padrão, os fornecedores de histórico local são mantidos uma vez após a execução completa. Se quiser que o histórico local espelhe mais de perto as conversas geridas por serviços, defina require_per_service_call_history_persistence=True para que os fornecedores de histórico corram em torno de cada chamada de modelo.
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,
)
Importante
Use este modo apenas para história local gerida por framework. Se a execução já estiver ligada a uma conversa gerida por serviços (por exemplo, via session.service_session_id ou options={"conversation_id": ...}), o Agent Framework gera um erro em vez de misturar os dois modelos de persistência.
Este modo é especialmente útil quando o middleware pode terminar imediatamente após uma chamada de ferramenta: persistir em cada chamada de modelo mantém o histórico local alinhado com aquilo que uma conversa gerida por um serviço manteria.
Os fornecedores de histórico funcionam em torno de uma invocação de agente. Não existe uma opção de persistência separada por chamada de serviço; se um ciclo de ferramentas fizer múltiplas chamadas ao fornecedor numa única execução, deve persistir o histórico local após a execução completa ou implementar um fornecedor ou middleware personalizado para responder às necessidades de armazenamento da tua aplicação.
Padrão de armazenamento de terceiros/personalizado
Para histórico suportado por bases de dados/Redis/blobs, implemente um provedor de histórico personalizado.
Orientações principais:
- Armazenar mensagens sob uma chave com âmbito de sessão.
- Mantenha o histórico devolvido dentro dos limites do contexto do modelo.
- Persistir identificadores específicos do fornecedor no estado da sessão.
A classe base para os fornecedores de história é Microsoft.Agents.AI.ChatHistoryProvider.
Os fornecedores de histórico participam no pipeline do agente, têm a capacidade de contribuir ou sobrepor mensagens de entrada do agente e podem armazenar novas mensagens.
ChatHistoryProvider tem vários métodos virtuais que podem ser substituídos para implementar o seu próprio provedor de histórico personalizado.
Consulte as diferentes opções de implementação abaixo para mais informações sobre o que pode ser substituído.
ChatHistoryProvider Estado
Uma ChatHistoryProvider instância está associada a um agente e a mesma instância seria usada para todas as sessões.
Isto significa que o ChatHistoryProvider não deve armazenar nenhum estado específico da sessão na instância do provedor.
Pode ChatHistoryProvider ter uma referência a um cliente da base de dados num campo, mas não deve ter uma chave da base de dados para o histórico de chat num campo.
Em vez disso, ChatHistoryProvider pode armazenar quaisquer valores específicos da sessão, como chaves de base de dados, mensagens ou qualquer outra coisa relevante no próprio AgentSession. Todos os métodos virtuais em ChatHistoryProvider recebem uma referência ao atual AIAgent e AgentSession.
Para permitir o armazenamento fácil do estado tipado no AgentSession, é fornecida uma classe utilitária:
// 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);
Implementação simples ChatHistoryProvider
A implementação ChatHistoryProvider simples normalmente sobrepõe-se a dois métodos:
- ChatHistoryProvider.ProvideChatHistoryAsync - Carregar o histórico de chat relevante e devolver as mensagens carregadas.
- ChatHistoryProvider.StoreChatHistoryAsync - Armazenar mensagens de pedido e resposta, todas elas devem ser novas.
Aqui está um exemplo de um simples ChatHistoryProvider que armazena o histórico do chat diretamente no estado da sessão.
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; } = [];
}
}
Implementação avançada ChatHistoryProvider
Uma implementação mais avançada poderia optar por sobrepor os seguintes métodos:
- ChatHistoryProvider.InvokingCoreAsync - Chamado antes do agente invocar o LLM e permitir a modificação da lista de mensagens de pedido.
- ChatHistoryProvider.InvokedCoreAsync - Chamado depois de o agente ter invocado o LLM e permite o acesso a todas as mensagens de pedido e resposta.
ChatHistoryProvider fornece implementações base de InvokingCoreAsync e InvokedCoreAsync.
A InvokingCoreAsync implementação base faz o seguinte:
- Chama
ProvideChatHistoryAsyncpara receber as mensagens que devem ser usadas como histórico de chat para a execução. - executa um filtro
FuncprovideOutputMessageFilteropcional nas mensagens devolvidas porProvideChatHistoryAsync. Este filtroFuncpode ser fornecido através doChatHistoryProviderconstrutor. - funde as mensagens filtradas devolvidas por
ProvideChatHistoryAsynccom as mensagens passadas ao agente pelo chamador, para produzir as mensagens de pedido do agente. O histórico do chat é anteposto às mensagens de entrada do agente. - carimba todas as mensagens filtradas devolvidas por
ProvideChatHistoryAsynccom informação de origem, indicando que as mensagens provenientes são do histórico do chat.
A InvokedCoreAsync base faz o seguinte:
- verifica se a execução falhou e, em caso afirmativo, retorna sem fazer mais nenhum processamento.
- Filtra as mensagens de pedido de agente para excluir mensagens que foram produzidas por um
ChatHistoryProvider, uma vez que queremos armazenar apenas novas mensagens e não aquelas que foram produzidas inicialmente peloChatHistoryProvider. Tenha em atenção que este filtro pode ser substituído através do parâmetrostoreInputMessageFilterno construtorChatHistoryProvider. - Passa as mensagens de pedido filtradas e todas as mensagens de resposta para
StoreChatHistoryAsyncarmazenamento.
É possível substituir estes métodos para implementar um ChatHistoryProvider, no entanto, isso requer que o implementador crie a funcionalidade fundamental por si mesmo, conforme apropriado.
Aqui está um exemplo de tal implementação.
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; } = [];
}
}
- Em Python, apenas um fornecedor de histórico deve usar
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)
No Go, implementa agent.HistoryProvider quando quiseres base de dados, Redis, blob ou histórico com ficheiros. O assistente predefinido criado por agent.NewHistoryProvider carrega mensagens anteriores em Provide e armazena novas mensagens de solicitação e resposta em Store. Mantém quaisquer chaves de armazenamento na sessão para que a instância do fornecedor possa ser reutilizada entre sessões.
import (
"context"
"fmt"
"time"
"github.com/microsoft/agent-framework-go/agent"
"github.com/microsoft/agent-framework-go/message"
)
type MessageStore interface {
LoadMessages(context.Context, string) ([]*message.Message, error)
AppendMessages(context.Context, string, []*message.Message) error
}
func NewDatabaseHistoryProvider(store MessageStore) agent.HistoryProvider {
const stateKey = "database_history.key"
historyKey := func(session *agent.Session) string {
var key string
if ok, _ := session.Get(stateKey, &key); ok && key != "" {
return key
}
key = fmt.Sprintf("history-%d", time.Now().UnixNano())
session.Set(stateKey, key)
return key
}
return agent.NewHistoryProvider(agent.HistoryProviderConfig{
SourceID: "database_history",
Provide: func(ctx context.Context, invoking agent.InvokingContext) ([]*message.Message, error) {
session, _ := agent.GetOption(invoking.Options, agent.WithSession)
if session == nil {
return nil, nil
}
return store.LoadMessages(ctx, historyKey(session))
},
Store: func(ctx context.Context, invoked agent.InvokedContext) error {
session, _ := agent.GetOption(invoked.Options, agent.WithSession)
if session == nil {
return nil
}
allMessages := make([]*message.Message, 0, len(invoked.RequestMessages)+len(invoked.ResponseMessages))
allMessages = append(allMessages, invoked.RequestMessages...)
allMessages = append(allMessages, invoked.ResponseMessages...)
return store.AppendMessages(ctx, historyKey(session), allMessages)
},
})
}
Associe o fornecedor personalizado ao agente:
a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
Instructions: "You are a helpful assistant.",
Config: agent.Config{
Name: "StorageAgent",
HistoryProvider: NewDatabaseHistoryProvider(store),
},
})
Não combine uma sessão local HistoryProvider configurada com uma sessão gerida por serviços. Use o armazenamento de histórico local ou o estado de conversa remota do fornecedor para uma dada sessão.
Sessões persistentes ao longo dos reinícios
Armazene o objeto completo da sessão, não apenas o texto da mensagem.
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)
As sessões podem ser mantidas através da serialização JSON. Guarda o texto completo agent.Session, não só o texto da mensagem ou uma chave de histórico.
data, err := json.Marshal(session)
if err != nil {
panic(err)
}
if err := os.WriteFile("session.json", data, 0o644); err != nil {
panic(err)
}
loaded, err := os.ReadFile("session.json")
if err != nil {
panic(err)
}
var resumed agent.Session
if err := json.Unmarshal(loaded, &resumed); err != nil {
panic(err)
}
_, err = a.RunText(ctx, "Continue this conversation.", agent.WithSession(&resumed)).Collect()
Para armazenamento com base de dados, serialize a sessão para []byte e armazene-a com o backend de sua preferência:
data, _ := json.Marshal(session)
db.Set(sessionID, data)
data, _ := db.Get(sessionID)
var resumed agent.Session
_ = json.Unmarshal(data, &resumed)
Sugestão
Consulte o exemplo de armazenamento de sessão de terceiros para um exemplo completo.
Importante
Trate AgentSession como um objeto de estado opaco e restaure-o com a mesma configuração de agente/fornecedor que o criou. Armazenar sessões serializadas e quaisquer IDs de sessão do lado do serviço como estado de aplicação confiável. Em aplicações alojadas ou multi-inquilinas, vincule cada sessão armazenada ao utilizador ou inquilino autenticado antes de permitir que retome.
Sugestão
Use um fornecedor adicional de histórico de auditoria/avaliação (load_messages=False, store_context_messages=True) para captar contexto enriquecido mais entrada/saída sem afetar o carregamento do histórico primário.