代理聊天历史记录和内存是允许代理跨对话保持上下文、记住用户首选项并提供个性化体验的关键功能。 代理框架提供了多种功能来满足不同的用例,从简单的内存中聊天消息存储到持久性数据库和专用内存服务。
聊天历史记录
代理框架支持各种聊天历史记录存储选项。 可用选项因用于生成代理的代理类型和基础服务而异。
下面是支持的两个主要方案:
-
内存中存储:代理基于不支持聊天历史记录服务内存储的服务(例如 OpenAI 聊天完成)。 默认情况下,代理框架会将完整的聊天历史记录存储在对象中
AgentThread,但开发人员可以提供自定义ChatMessageStore实现,以根据需要将聊天历史记录存储在第三方存储中。 -
服务内存储:代理基于需要聊天历史记录服务内存储的服务(例如 Azure AI Foundry Persistent Agents)。 代理框架将远程聊天历史记录的 ID 存储在对象中
AgentThread,不支持其他聊天历史记录存储选项。
内存中聊天历史记录存储
当使用不支持聊天历史记录服务内存储的服务时,代理框架将默认在对象中 AgentThread 存储聊天历史记录。 在这种情况下,存储在线程对象中的完整聊天历史记录以及任何新消息都将提供给每个代理运行的基础服务。 这允许与代理进行自然对话体验,其中调用方仅提供新用户消息,代理仅返回新答案,但代理有权访问完整的会话历史记录,并在生成其响应时使用它。
将 OpenAI 聊天完成用作代理的基础服务时,以下代码将导致包含代理运行的聊天历史记录的线程对象。
AIAgent agent = new OpenAIClient("<your_api_key>")
.GetChatClient(modelName)
.CreateAIAgent(JokerInstructions, JokerName);
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
如果消息存储在内存中,则可以从线程中检索消息列表,并根据需要直接作消息。
IList<ChatMessage>? messages = thread.GetService<IList<ChatMessage>>();
注释
仅当使用内存中存储时,才能以这种方式从 AgentThread 对象检索消息。
使用 In-Memory 存储减少聊天历史记录
当基础服务不支持服务内存储时,默认使用的内置 InMemoryChatMessageStore 功能可以使用化简器进行配置,以管理聊天历史记录的大小。
这可用于避免超出基础服务的上下文大小限制。
InMemoryChatMessageStore可以采用可选Microsoft.Extensions.AI.IChatReducer实现来减小聊天历史记录的大小。
它还允许你配置在将消息添加到聊天历史记录后或在为下一次调用返回聊天历史记录之前调用化简器的事件。
若要配置InMemoryChatMessageStore化简器,可以提供一个工厂来为每个新InMemoryChatMessageStore项构造一个新AgentThread项,并将其作为所选的化简器传递。
InMemoryChatMessageStore还可以传递可选触发器事件,该事件可以设置为或 InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAddedInMemoryChatMessageStore.ChatReducerTriggerEvent.BeforeMessagesRetrieval。
AIAgent agent = new OpenAIClient("<your_api_key>")
.GetChatClient(modelName)
.CreateAIAgent(new ChatClientAgentOptions
{
Name = JokerName,
Instructions = JokerInstructions,
ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(
new MessageCountingChatReducer(2),
ctx.SerializedState,
ctx.JsonSerializerOptions,
InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded)
});
注释
仅当使用此功能 InMemoryChatMessageStore时,才支持此功能。 当服务具有服务内聊天历史记录存储时,服务本身负责管理聊天历史记录的大小。 同样,使用第三方存储时(请参阅下文),它最多是第三方存储解决方案来管理聊天历史记录大小。 如果为消息存储提供一个 ChatMessageStoreFactory 服务,但使用具有内置聊天历史记录存储的服务,则不会使用工厂。
推理服务聊天历史记录存储
使用需要聊天历史记录服务内存储的服务时,Agent Framework 会将远程聊天历史记录的 ID 存储在对象中 AgentThread 。
例如,将 OpenAI 响应与 store=true 用作代理的基础服务时,以下代码将导致线程对象包含服务返回的最后一个响应 ID。
AIAgent agent = new OpenAIClient("<your_api_key>")
.GetOpenAIResponseClient(modelName)
.CreateAIAgent(JokerInstructions, JokerName);
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
注释
某些服务(例如 OpenAI 响应)支持聊天历史记录(store=true)的服务内存储,或为每个调用提供完整的聊天历史记录(store=false)。 因此,根据服务的使用模式,代理框架将默认将完整聊天历史记录存储在内存中,或存储对服务存储聊天历史记录的 ID 引用。
第三方聊天历史记录存储
使用不支持聊天历史记录服务存储的服务时,代理框架允许开发人员将聊天历史记录的默认内存中存储替换为第三方聊天历史记录存储。 开发人员需要提供基抽象 ChatMessageStore 类的子类。
该 ChatMessageStore 类定义用于存储和检索聊天消息的接口。 开发人员必须实现 AddMessagesAsync 和 GetMessagesAsync 方法,以便在生成消息时将消息添加到远程存储,并在调用基础服务之前从远程存储中检索消息。
代理将在处理用户查询时使用返回 GetMessagesAsync 的所有消息。 它由实现者 ChatMessageStore 决定,以确保聊天历史记录的大小不会超过基础服务的上下文窗口。
实现将聊天历史记录存储在远程存储中的自定义 ChatMessageStore 时,该线程的聊天历史记录应存储在该线程唯一的密钥下。 实现 ChatMessageStore 应生成此密钥并使其保持其状态。
ChatMessageStore 具有一个 Serialize 方法,可以在序列化线程时重写以序列化其状态。
ChatMessageStore还应提供一个构造函数,该构造函数采用JsonElement输入来支持其状态的反序列化。
若要向 a ChatMessageStore提供自定义ChatClientAgent项,可以在创建代理时使用该ChatMessageStoreFactory选项。
以下示例演示如何将自定义实现传递给基于 Azure OpenAI 聊天完成的实现ChatMessageStoreChatClientAgent。
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new AzureCliCredential())
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions
{
Name = JokerName,
Instructions = JokerInstructions,
ChatMessageStoreFactory = ctx =>
{
// Create a new chat message store for this agent that stores the messages in a custom store.
// Each thread must get its own copy of the CustomMessageStore, since the store
// also contains the id that the thread is stored under.
return new CustomMessageStore(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions);
}
});
小窍门
有关如何创建自定义消息存储的详细示例,请参阅 第三方存储中的存储聊天历史记录 教程。
长期记忆
代理框架允许开发人员提供自定义组件,这些组件可以提取内存或向代理提供内存。
若要实现此类内存组件,开发人员需要对抽象基类进行子类化 AIContextProvider 。 此类有两个核心方法, InvokingAsync 以及 InvokedAsync。 重写后, InvokedAsync 开发人员可以检查用户提供或代理生成的所有消息。
InvokingAsync 允许开发人员为特定代理运行注入其他上下文。 可以提供系统说明、其他消息和其他函数。
小窍门
有关如何创建自定义内存组件的详细示例,请参阅“ 将内存添加到代理 ”教程。
AgentThread 序列化
必须能够在代理调用之间保留对象 AgentThread 。 这样,用户可能会询问代理的问题,需要很长时间才能提出后续问题。 这样, AgentThread 状态就可以在服务或应用重启后幸存下来。
即使聊天历史记录存储在远程存储中,对象 AgentThread 仍包含引用远程聊天历史记录的 ID。 因此, AgentThread 丢失状态将导致远程聊天历史记录的 ID 丢失。
AgentThread以及附加到它的任何对象,因此都提供SerializeAsync序列化其状态的方法。 此外,还提供了 AIAgent 一种方法 DeserializeThread ,用于从序列化状态重新创建线程。 该方法DeserializeThread使用代理重新创建并ChatMessageStoreAIContextProvider配置线程。
// Serialize the thread state to a JsonElement, so it can be stored for later use.
JsonElement serializedThreadState = thread.Serialize();
// Re-create the thread from the JsonElement.
AgentThread resumedThread = AIAgent.DeserializeThread(serializedThreadState);
注释
AgentThread 对象可能包含不仅仅是聊天历史记录,例如上下文提供程序还可以将状态存储在线程对象中。 因此,请务必始终序列化、存储和反序列化整个 AgentThread 对象,以确保保留所有状态。
重要
始终将对象视为 AgentThread 不透明对象,除非你非常确定内部对象。 内容可能不仅因代理类型而异,而且因服务类型和配置而异。
警告
反序列化具有与最初创建的代理不同的线程,或者具有与原始代理不同的配置的代理,可能会导致错误或意外行为。
内存类型
Agent Framework 支持多种类型的内存来容纳不同的用例,包括将聊天历史记录作为短期内存的一部分进行管理,并提供用于提取、存储和注入长期内存的扩展点。
In-Memory 存储(默认)
在应用程序运行时,对话历史记录存储在内存中的最简单的内存形式。 这是默认行为,无需其他配置。
from agent_framework import ChatAgent
from agent_framework.openai import OpenAIChatClient
# Default behavior - uses in-memory storage
agent = ChatAgent(
chat_client=OpenAIChatClient(),
instructions="You are a helpful assistant."
)
# Conversation history is maintained in memory for this thread
thread = agent.get_new_thread()
response = await agent.run("Hello, my name is Alice", thread=thread)
持久消息存储
对于需要跨会话保留会话历史记录的应用程序,框架提供 ChatMessageStore 实现:
内置 ChatMessageStore
可以序列化的默认内存中实现:
from agent_framework import ChatMessageStore
# Create a custom message store
def create_message_store():
return ChatMessageStore()
agent = ChatAgent(
chat_client=OpenAIChatClient(),
instructions="You are a helpful assistant.",
chat_message_store_factory=create_message_store
)
Redis 消息存储
对于需要持久存储的生产应用程序:
from agent_framework.redis import RedisChatMessageStore
def create_redis_store():
return RedisChatMessageStore(
redis_url="redis://localhost:6379",
thread_id="user_session_123",
max_messages=100 # Keep last 100 messages
)
agent = ChatAgent(
chat_client=OpenAIChatClient(),
instructions="You are a helpful assistant.",
chat_message_store_factory=create_redis_store
)
自定义消息存储
可以通过实现以下各项 ChatMessageStoreProtocol来实现自己的存储后端:
from agent_framework import ChatMessage, ChatMessageStoreProtocol
from typing import Any
from collections.abc import Sequence
class DatabaseMessageStore(ChatMessageStoreProtocol):
def __init__(self, connection_string: str):
self.connection_string = connection_string
self._messages: list[ChatMessage] = []
async def add_messages(self, messages: Sequence[ChatMessage]) -> None:
"""Add messages to database."""
# Implement database insertion logic
self._messages.extend(messages)
async def list_messages(self) -> list[ChatMessage]:
"""Retrieve messages from database."""
# Implement database query logic
return self._messages
async def serialize(self, **kwargs: Any) -> Any:
"""Serialize store state for persistence."""
return {"connection_string": self.connection_string}
async def update_from_state(self, serialized_store_state: Any, **kwargs: Any) -> None:
"""Update store from serialized state."""
if serialized_store_state:
self.connection_string = serialized_store_state["connection_string"]
小窍门
有关如何创建自定义消息存储的详细示例,请参阅 第三方存储中的存储聊天历史记录 教程。
上下文提供程序 (动态内存)
上下文提供程序通过在每次代理调用之前注入相关上下文来实现复杂的内存模式:
基本上下文提供程序
from agent_framework import ContextProvider, Context, ChatMessage
from collections.abc import MutableSequence
from typing import Any
class UserPreferencesMemory(ContextProvider):
def __init__(self):
self.preferences = {}
async def invoking(self, messages: ChatMessage | MutableSequence[ChatMessage], **kwargs: Any) -> Context:
"""Provide user preferences before each invocation."""
if self.preferences:
preferences_text = ", ".join([f"{k}: {v}" for k, v in self.preferences.items()])
instructions = f"User preferences: {preferences_text}"
return Context(instructions=instructions)
return Context()
async def invoked(
self,
request_messages: ChatMessage | Sequence[ChatMessage],
response_messages: ChatMessage | Sequence[ChatMessage] | None = None,
invoke_exception: Exception | None = None,
**kwargs: Any,
) -> None:
"""Extract and store user preferences from the conversation."""
# Implement preference extraction logic
pass
小窍门
有关如何创建自定义内存组件的详细示例,请参阅“ 将内存添加到代理 ”教程。
外部内存服务
该框架支持与 Mem0 等专用内存服务集成:
from agent_framework.mem0 import Mem0Provider
# Using Mem0 for advanced memory capabilities
memory_provider = Mem0Provider(
api_key="your-mem0-api-key",
user_id="user_123",
application_id="my_app"
)
agent = ChatAgent(
chat_client=OpenAIChatClient(),
instructions="You are a helpful assistant with memory.",
context_providers=memory_provider
)
线程序列化和持久性
该框架支持序列化整个线程状态,以便在应用程序重启时保持持久性:
import json
# Create agent and thread
agent = ChatAgent(chat_client=OpenAIChatClient())
thread = agent.get_new_thread()
# Have conversation
await agent.run("Hello, my name is Alice", thread=thread)
# Serialize thread state
serialized_thread = await thread.serialize()
# Save to file/database
with open("thread_state.json", "w") as f:
json.dump(serialized_thread, f)
# Later, restore the thread
with open("thread_state.json", "r") as f:
thread_data = json.load(f)
restored_thread = await agent.deserialize_thread(thread_data)
# Continue conversation with full context
await agent.run("What's my name?", thread=restored_thread)