跳转到内容

记忆

记忆是智能体系统的核心组件。它允许您存储和检索过去的信息。

在LlamaIndex中,您通常可以通过使用现有的BaseMemory类,或创建自定义类来定制内存。

当智能体运行时,它会调用 memory.put() 来存储信息,并调用 memory.get() 来检索信息。

注意: ChatMemoryBuffer 已被弃用。在未来的版本中,默认值将被 Memory 类替代,该类更灵活且支持更复杂的内存配置。本节中的示例将使用 Memory 类。默认情况下,整个框架使用 ChatMemoryBuffer 来创建聊天历史的基本缓冲区,为智能体提供符合令牌限制的最后 X 条消息。Memory 类的操作方式类似,但更灵活且支持更复杂的内存配置。

使用 Memory 类,您可以创建一个同时具备短期记忆(即消息的先进先出队列)和可选长期记忆(即随时间提取信息)的记忆系统。

您可以通过将记忆传递给 run() 方法来为智能体设置记忆:

from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.memory import Memory
memory = Memory.from_defaults(session_id="my_session", token_limit=40000)
agent = FunctionAgent(llm=llm, tools=tools)
response = await agent.run("<question that invokes tool>", memory=memory)

你也可以通过直接调用 memory.put_messages()memory.get() 并传入聊天记录来手动管理内存。

from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.llms import ChatMessage
from llama_index.core.memory import Memory
memory = Memory.from_defaults(session_id="my_session", token_limit=40000)
memory.put_messages(
[
ChatMessage(role="user", content="Hello, world!"),
ChatMessage(role="assistant", content="Hello, world to you too!"),
]
)
chat_history = memory.get()
agent = FunctionAgent(llm=llm, tools=tools)
# passing in the chat history overrides any existing memory
response = await agent.run(
"<question that invokes tool>", chat_history=chat_history
)

你可以通过从智能体上下文中获取最新记忆:

from llama_index.core.workflow import Context
ctx = Context(agent)
response = await ctx.run("<question that invokes tool>", ctx=ctx)
# get the memory
memory = await ctx.store.get("memory")
chat_history = memory.get()

默认情况下,Memory 类将存储符合令牌限制的最后 X 条消息。您可以通过向 Memory 类传入 token_limitchat_history_token_ratio 参数来自定义此行为。

  • token_limit (默认值: 30000): 存储短期和长期令牌的最大数量。
  • chat_history_token_ratio (默认值: 0.7): 短期聊天历史中令牌数量与总令牌限制的比例。如果聊天历史超过此比例,最早的消息将被刷新到长期记忆中(如果启用了长期记忆功能)。
  • token_flush_size (默认值: 3000): 当聊天记录超过令牌限制时,刷新到长期记忆中的令牌数量。
memory = Memory.from_defaults(
session_id="my_session",
token_limit=40000,
chat_history_token_ratio=0.7,
token_flush_size=3000,
)

长期记忆表示为 Memory Block 对象。这些对象接收从短期记忆中刷新的消息,并可选择性地处理它们以提取信息。然后在检索记忆时,短期记忆和长期记忆会被合并在一起。

目前,有三个预定义的内存块:

  • StaticMemoryBlock: 存储静态信息的内存块。
  • FactExtractionMemoryBlock: 一个从聊天记录中提取事实的记忆块。
  • VectorMemoryBlock: 一个存储和从向量数据库中检索批量聊天消息的内存块。

默认情况下,根据 insert_method 参数,内存块将被插入到系统消息或最新的用户消息中。

这听起来有点复杂,但实际上非常简单。让我们来看一个例子:

from llama_index.core.memory import (
StaticMemoryBlock,
FactExtractionMemoryBlock,
VectorMemoryBlock,
)
blocks = [
StaticMemoryBlock(
name="core_info",
static_content="My name is Logan, and I live in Saskatoon. I work at LlamaIndex.",
priority=0,
),
FactExtractionMemoryBlock(
name="extracted_info",
llm=llm,
max_facts=50,
priority=1,
),
VectorMemoryBlock(
name="vector_memory",
# required: pass in a vector store like qdrant, chroma, weaviate, milvus, etc.
vector_store=vector_store,
priority=2,
embed_model=embed_model,
# The top-k message batches to retrieve
# similarity_top_k=2,
# optional: How many previous messages to include in the retrieval query
# retrieval_context_window=5
# optional: pass optional node-postprocessors for things like similarity threshold, etc.
# node_postprocessors=[...],
),
]

在这里,我们设置了三个记忆块:

  • core_info: 一个静态内存块,存储关于用户的一些核心信息。静态内容可以是字符串或ContentBlock对象列表,例如TextBlockImageBlock等。此信息将始终插入到内存中。
  • extracted_info: 一个从聊天记录中提取信息的记忆块。这里我们传入llm用于从已刷新的聊天记录中提取事实,并将max_facts设置为50。如果提取的事实数量超过此限制,max_facts将自动进行摘要并缩减,以便为新信息留出空间。
  • vector_memory:一个向量记忆块,用于存储和检索来自向量数据库的批量聊天消息。每个批次是已刷新的聊天消息列表。这里我们传入了vector_storeembed_model来用于存储和检索聊天消息。

您还会注意到我们为每个区块设置了priority。这用于确定当记忆区块内容(即长期记忆)+ 短期记忆超过Memory对象的令牌限制时的处理方式。

当记忆块变得过长时,它们会自动被"截断"。默认情况下,这意味着它们会从记忆中移除,直到有足够的空间为止。这可以通过实现自定义截断逻辑的记忆块子类来进行定制。

  • priority=0: 此代码块将始终保留在内存中。
  • priority=1, 2, 3, etc: 这决定了当内存超过令牌限制时内存块的截断顺序,以帮助整体短期记忆 + 长期记忆内容小于或等于 token_limit

现在,让我们将这些代码块传入 Memory 类:

memory = Memory.from_defaults(
session_id="my_session",
token_limit=40000,
memory_blocks=blocks,
insert_method="system",
)

随着内存的使用,短期记忆将会填满。一旦短期记忆超过 chat_history_token_ratio,最旧的符合 token_flush_size 的消息将被清空并发送到每个记忆块进行处理。

当记忆被检索时,短期记忆和长期记忆会合并在一起。Memory对象将确保短期记忆+长期记忆内容小于或等于token_limit。如果内容过长,将在记忆块上调用.truncate()方法,使用priority来确定截断顺序。

一旦记忆收集到足够的信息,我们可能会从记忆中看到类似这样的内容:

# optionally pass in a list of messages to get, which will be forwarded to the memory blocks
chat_history = memory.get(messages=[...])
print(chat_history[0].content)

这将打印类似以下内容:

<memory>
<static_memory>
My name is Logan, and I live in Saskatoon. I work at LlamaIndex.
</static_memory>
<fact_extraction_memory>
<fact>Fact 1</fact>
<fact>Fact 2</fact>
<fact>Fact 3</fact>
</fact_extraction_memory>
<retrieval_based_memory>
<message role='user'>Msg 1</message>
<message role='assistant'>Msg 2</message>
<message role='user'>Msg 3</message>
</retrieval_based_memory>
</memory>

在这里,内存被插入到系统消息中,每个内存块都有特定的部分。

虽然提供了预定义的内存块,您也可以创建自己的自定义内存块。

from typing import Optional, List, Any
from llama_index.core.llms import ChatMessage
from llama_index.core.memory.memory import BaseMemoryBlock
# use generics to define the output type of the memory block
# can be str or List[ContentBlock]
class MentionCounter(BaseMemoryBlock[str]):
"""
A memory block that counts the number of times a user mentions a specific name.
"""
mention_name: str = "Logan"
mention_count: int = 0
async def _aget(
self, messages: Optional[List[ChatMessage]] = None, **block_kwargs: Any
) -> str:
return f"Logan was mentioned {self.mention_count} times."
async def _aput(self, messages: List[ChatMessage]) -> None:
for message in messages:
if self.mention_name in message.content:
self.mention_count += 1
async def atruncate(
self, content: str, tokens_to_truncate: int
) -> Optional[str]:
return ""

在这里,我们定义了一个记忆块,用于统计用户提及特定名称的次数。

它的截断方法很基础,仅返回一个空字符串。

默认情况下,Memory 类使用内存中的 SQLite 数据库。您可以通过更改数据库 URI 来接入任何远程数据库。

您可以自定义表名,也可以选择直接传入异步引擎。这对于管理您自己的连接池非常有用。

from llama_index.core.memory import Memory
memory = Memory.from_defaults(
session_id="my_session",
token_limit=40000,
async_database_uri="postgresql+asyncpg://postgres:mark90@localhost:5432/postgres",
# Optional: specify a table name
# table_name="memory_table",
# Optional: pass in an async engine directly
# this is useful for managing your own connection pool
# async_engine=engine,
)

在文档的当前阶段,您可能已经遇到过这样的情况:在使用工作流时,需要序列化一个 Context 对象来保存和恢复特定的工作流状态。工作流 Context 是一个复杂对象,它既包含工作流的运行时信息,也包含在工作流步骤间共享的键值对数据。

相比之下,Memory 对象是一个更简单的对象,仅包含 ChatMessage 对象,并可选择性地包含一个用于长期记忆的 MemoryBlock 对象列表。

在大多数实际情况下,您最终会同时使用两者。如果您没有自定义记忆功能,那么序列化 Context 对象就足够了。

from llama_index.core.workflow import Context
ctx = Context(workflow)
# serialize the context
ctx_dict = ctx.to_dict()
# deserialize the context
ctx = Context.from_dict(workflow, ctx_dict)

在其他情况下,比如使用 FunctionAgentAgentWorkflowReActAgent 时,如果你自定义了记忆模块,那么你需要将其作为单独的运行时参数提供(特别是因为除了默认配置之外,Memory 对象是不可序列化的)。

response = await agent.run("Hello!", memory=memory)

最后,在某些情况下(如人在回路),您需要同时提供 Context(用于恢复工作流)和 Memory(用于存储聊天记录)。

response = await agent.run("Hello!", ctx=ctx, memory=memory)

llama_index.core.memory 中,我们提供几种不同的记忆类型:

  • ChatMemoryBuffer: 一个基础内存缓冲区,用于存储符合令牌限制的最后X条消息。
  • ChatSummaryMemoryBuffer: 一个存储最近X条消息的内存缓冲区,这些消息需符合令牌限制,并在对话过长时定期进行总结。
  • VectorMemory:一种能够存储和从向量数据库中检索聊天消息的记忆模块。它不保证消息的顺序,并返回与最新用户消息最相似的消息。
  • SimpleComposableMemory: 一种将多个记忆组合在一起的内存。通常用于将VectorMemoryChatMemoryBufferChatSummaryMemoryBuffer结合使用。

您可以在下方找到几个实际应用中的记忆示例:

注意:已弃用的示例: