Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El almacenamiento controla dónde reside el historial de conversaciones, cuánto historial se carga y cómo se pueden reanudar las sesiones de forma confiable.
Modos de almacenamiento integrados
Agent Framework admite dos modos de almacenamiento normales:
| Mode | Qué se almacena | Uso típico |
|---|---|---|
| Estado de sesión local | Historial de chat completo en AgentSession.state (por ejemplo, a través de InMemoryHistoryProvider) |
Servicios que no requieren persistencia de conversación del lado servidor |
| Almacenamiento administrado por el servicio | Estado de conversación en el servicio; AgentSession.service_session_id apunta a él |
Servicios con soporte de conversaciones persistentes nativas |
Almacenamiento del historial de chat en memoria
Cuando un proveedor no requiere el historial de chat del lado servidor, Agent Framework mantiene el historial localmente en la sesión y envía mensajes pertinentes en cada ejecució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)
Go almacena el historial local de chat en agent.Session mediante un agent.HistoryProvider. Si no configura un proveedor de historial, Agent Framework crea un proveedor en memoria predeterminado que se usa al pasar una sesión local explícita. Configure uno explícitamente cuando desee un identificador de origen estable o 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()
Reducción del tamaño del historial en memoria
Si el historial crece demasiado grande para los límites del modelo, aplique un reductor.
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 un HistoryProvider filtro para limitar los mensajes del historial cargados en la siguiente solicitud. Por ejemplo, mantenga solo los 20 mensajes del historial más recientes:
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 la reducción semántica o basada en tokens, utilice una estrategia de compactación antes de ejecutar, en lugar de basarse únicamente en el número de mensajes.
Nota:
La configuración del reducer se aplica a los proveedores de historial en memoria. En el caso del historial administrado por el servicio, el comportamiento de reducción es específico del proveedor o servicio.
Almacenamiento administrado por el servicio
Cuando el servicio administra el historial de conversaciones, la sesión almacena un identificador de conversación remota.
En el caso de las respuestas y conversaciones de OpenAI, los identificadores del lado del servicio, como resp_* y, conv_* por ejemplo, son opacos y tienen como ámbito la clave de API de respaldo o el proyecto de forma predeterminada. Esto suele ser suficiente cuando esa clave o proyecto ya está limitado a una aplicación, usuario o tenant. Si aloja un agente para varios usuarios finales con la misma clave subyacente o el mismo proyecto, mantenga esos identificadores en un almacenamiento confiable en el servidor, asócielos a sus propios identificadores de sesión y verifique la titularidad antes de reanudar una conversación.
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)
Go almacena identificadores de conversación específicos del proveedor en session.ServiceID(). Cree una sesión con un identificador de conversación de servicio existente cuando necesite reanudar el historial administrado por el servicio:
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()
Cuando un proveedor crea o actualiza el identificador de conversación remota durante una ejecución, la sesión se actualiza y se puede inspeccionar después de la llamada:
fmt.Println(session.ServiceID())
Los proveedores de historial local configurados se omiten para las sesiones administradas por el servicio, por lo que el servicio sigue siendo el origen del historial de conversaciones.
Persistencia del historial local por llamada de servicio
Las ejecuciones de herramientas pueden realizar varias llamadas de modelo antes de que una sola agent.run() se complete. De forma predeterminada, los proveedores de historial local se conservan una vez después de la ejecución completa. Si desea que el historial local refleje más estrechamente las conversaciones administradas por el servicio, configure require_per_service_call_history_persistence=True para que los proveedores de historial se activen alrededor de cada llamada 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 solo para el historial local administrado por el marco. Si la ejecución ya está enlazada a una conversación administrada por el servicio (por ejemplo, a través de session.service_session_id o options={"conversation_id": ...}), Agent Framework produce un error en lugar de combinar los dos modelos de persistencia.
Este modo es especialmente útil cuando el middleware puede finalizar inmediatamente después de una llamada a herramienta: la conservación por llamada de modelo mantiene el historial local alineado con lo que una conversación administrada por servicio mantendría.
Los proveedores de historial de go se ejecutan en torno a una invocación de agente. No existe una opción de persistencia independiente para cada llamada al servicio; si un bucle de herramientas realiza varias llamadas al proveedor dentro de una misma ejecución, persista el historial local una vez finalizada toda la ejecución o implemente un proveedor o middleware personalizados según las necesidades de almacenamiento de su aplicación.
Patrón de almacenamiento personalizado o de terceros
En el caso del historial respaldado por bases de datos, Redis y blob, implemente un proveedor de historial personalizado.
Guía clave:
- Almacene mensajes en una clave con ámbito de sesión.
- Mantenga el historial devuelto dentro de los límites del contexto del modelo.
- Conservar identificadores específicos del proveedor en el estado de sesión.
La clase base para los proveedores de historial es Microsoft.Agents.AI.ChatHistoryProvider.
Los proveedores de historial participan en la canalización del agente, tienen la capacidad de contribuir o invalidar los mensajes de entrada del agente y pueden almacenar nuevos mensajes.
ChatHistoryProvider tiene varios métodos virtuales que se pueden invalidar para implementar su propio proveedor de historial personalizado.
Consulte las distintas opciones de implementación siguientes para obtener más información sobre qué invalidar.
ChatHistoryProvider estado
Se ChatHistoryProvider adjunta una instancia a un agente y se usaría la misma instancia para todas las sesiones.
Esto significa que ChatHistoryProvider no debe almacenar ningún estado específico de sesión en la instancia del proveedor.
ChatHistoryProvider puede tener una referencia a un cliente de base de datos en un campo, pero no debe tener una clave de base de datos para el historial de chat en un campo.
En su lugar, ChatHistoryProvider puede almacenar cualquier valor específico de sesión, como claves de base de datos, mensajes o cualquier otra cosa que sea relevante en sí AgentSession . En los métodos virtuales de ChatHistoryProvider, se pasa una referencia al AIAgent y AgentSession actual.
Para facilitar el almacenamiento de estado tipado en AgentSession, se proporciona una clase de utilidad:
// 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);
Implementación sencilla ChatHistoryProvider
La implementación más sencilla ChatHistoryProvider normalmente invalidaría dos métodos:
- ChatHistoryProvider.ProvideChatHistoryAsync : cargue el historial de chat pertinente y devuelva los mensajes cargados.
- ChatHistoryProvider.StoreChatHistoryAsync : los mensajes de solicitud y respuesta de la tienda, todos los cuales deben ser nuevos.
Este es un ejemplo de un sencillo ChatHistoryProvider que almacena el historial de chat directamente en el estado de sesión.
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; } = [];
}
}
Implementación avanzada ChatHistoryProvider
Una implementación más avanzada podría optar por invalidar los métodos siguientes:
- ChatHistoryProvider.InvokingCoreAsync : se llama antes de que el agente invoque el LLM y permita modificar la lista de mensajes de solicitud.
- ChatHistoryProvider.InvokedCoreAsync : se llama después de que el agente haya invocado el LLM y permita el acceso a todos los mensajes de solicitud y respuesta.
ChatHistoryProvider proporciona implementaciones base de InvokingCoreAsync y InvokedCoreAsync.
La InvokingCoreAsync implementación base hace lo siguiente:
- llamadas
ProvideChatHistoryAsyncpara obtener los mensajes que se deben usar como historial de chat para la ejecución - ejecuta un filtro
FuncprovideOutputMessageFilteropcional en los mensajes devueltos porProvideChatHistoryAsync. Este filtroFuncse puede proporcionar a través delChatHistoryProviderconstructor . - combina los mensajes filtrados devueltos por
ProvideChatHistoryAsynccon los mensajes pasados al agente por el autor de la llamada para generar los mensajes de solicitud del agente. El historial de chat se antepone a los mensajes de entrada del agente. - marca todos los mensajes filtrados devueltos por
ProvideChatHistoryAsynccon información de origen, lo que indica que estos mensajes proceden del historial de chat.
La InvokedCoreAsync base hace lo siguiente:
- comprueba si se produjo un error en la ejecución y, si es así, devuelve sin realizar ningún procesamiento adicional.
- filtra los mensajes de solicitud del agente para excluir los mensajes generados por ,
ChatHistoryProviderya que solo queremos almacenar nuevos mensajes y no los generados porChatHistoryProvideren primer lugar. Tenga en cuenta que este filtro se puede invalidar a través delstoreInputMessageFilterparámetro en elChatHistoryProviderconstructor. - pasa los mensajes de solicitud filtrados y todos los mensajes de respuesta a
StoreChatHistoryAsyncpara el almacenamiento.
Es posible invalidar estos métodos para implementar un ChatHistoryProvider, pero esto requiere que el implementador implemente la propia funcionalidad base según corresponda.
Este es un ejemplo de esta implementación.
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; } = [];
}
}
- En Python, solo un proveedor de historial debe 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)
En Go, implemente agent.HistoryProvider cuando desee la base de datos, Redis, blob o historial respaldado por archivos. El asistente predeterminado creado por agent.NewHistoryProvider carga los mensajes anteriores en Provide y conserva los nuevos mensajes de solicitud y respuesta en Store. Mantenga las claves de almacenamiento en la sesión para que la instancia del proveedor se pueda reutilizar entre sesiones.
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)
},
})
}
Asocie el proveedor personalizado al 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),
},
})
No combine un local HistoryProvider configurado con una sesión administrada por el servicio. Use el almacenamiento del historial local o el estado de conversación remota del proveedor para una sesión determinada.
Conservación de sesiones entre reinicios
Conserve el objeto de sesión completa, no solo el texto del mensaje.
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)
Las sesiones se pueden conservar mediante la serialización JSON. Guarde todo el agent.Session, no solo el texto del mensaje o una clave de historial.
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 el almacenamiento respaldado por la base de datos, serialice la sesión en []byte y almacénela con el back-end preferido:
data, _ := json.Marshal(session)
db.Set(sessionID, data)
data, _ := db.Get(sessionID)
var resumed agent.Session
_ = json.Unmarshal(data, &resumed)
Sugerencia
Consulte el ejemplo de almacenamiento de sesión de terceros para obtener un ejemplo completo.
Importante
Trate AgentSession como un objeto de estado opaco y restáurelo con la misma configuración del agente o proveedor que la creó. Almacene las sesiones serializadas y los identificadores de sesión del lado del servicio como estado de aplicación de confianza. En aplicaciones hospedadas o multiinquilino, enlace cada sesión almacenada al usuario o inquilino autenticado antes de permitir que se reanude.
Sugerencia
Emplee un proveedor adicional de historial de auditoría/evaluación (load_messages=False, store_context_messages=True) para capturar un contexto enriquecido, además de entrada/salida, sin afectar la carga del historial principal.