跳到主要内容
非官方简体中文翻译,内容版权归 Mastra 所有,遵循 Apache-2.0 许可。

Message history

消息历史是最基础也最重要的记忆形式。它让 LLM 在上下文窗口中看到最近的消息,使你的 agent 能够引用先前的交流并连贯地响应。

你也可以检索消息历史,在 UI 中展示过去的对话。

备注

每条消息属于一个 thread(对话)和一个 resource(与之关联的用户或实体)。详情请参阅 Threads 和 resources。

Threads 和 resources
Threads 和 resources的直接链接

Mastra 使用两个标识符组织对话:

  • Thread:包含一系列消息的对话会话。
  • Resource:拥有 thread 的实体,例如用户、组织、项目或应用中的其他领域实体。

Studio 会自动为你生成 thread 和 resource ID。当你自行调用 stream() 或 generate() 时,请显式提供这些标识符。

开始使用
开始使用的直接链接

安装 Mastra memory 模块以及适用于你的数据库的存储适配器。下面的示例使用 @mastra/libsql,它将数据本地存储在 mastra.db 文件中。

npm install @mastra/memory@latest @mastra/libsql@latest

消息历史需要存储适配器来持久化对话。如果你尚未配置,请在 Mastra 实例上配置存储:

src/mastra/index.ts
import { Mastra } from '@mastra/core'
import { LibSQLStore } from '@mastra/libsql'

export const mastra = new Mastra({
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db',
}),
})

在 agent 中实例化一个 Memory 实例:

src/mastra/agents/test-agent.ts
import { Memory } from '@mastra/memory'
import { Agent } from '@mastra/core/agent'

export const agent = new Agent({
id: 'test-agent',
memory: new Memory({
options: {
lastMessages: 10,
},
}),
})

调用 agent 时,消息会自动保存到数据库。你可以指定 threadId、resourceId 以及可选的 metadata:

await agent.generate('Hello', {
memory: {
thread: {
id: 'thread-123',
title: 'Support conversation',
metadata: { category: 'billing' },
},
resource: 'user-456',
},
})
备注

当你调用 agent.generate() 或 agent.stream() 时,threads 和消息会自动创建,但你也可以使用 createThread() 和 saveMessages() 手动创建它们。

你可以通过两种方式使用此历史:

  • 自动包含:Mastra 会自动将最近的消息包含在上下文窗口中。默认的 10 条消息使 agents 保持对话基础。需要时可通过 lastMessages 调整。
  • 手动查询:如需更多控制,可使用 recall() 直接查询 threads 和消息。使用结果来决定哪些记忆进入上下文窗口,或在 UI 中渲染对话历史。
备注

lastMessages 会计算每条已存储的消息,包括工具调用、工具结果以及任何类型的信号,因此单个轮次可能会向计数中添加多条消息。窗口还会在每次请求时向前滑动:一旦 thread 超过限制,最旧的消息会在每轮离开上下文,这会改变提示词的开头并使提供器提示词缓存失效。对于长时间运行的对话,请使用观察式记忆,它保持提示词前缀稳定。

从客户端发送消息
从客户端发送消息的直接链接

当你在客户端应用中使用 memory 时,请只发送新消息,而不是完整的对话历史。Mastra 会从存储中加载先前的消息。

AI SDK 示例请参阅使用 Mastra Memory。

传递完整的输入历史

Mastra 会在加载已存储消息之前过滤客户端回传的历史。当消息 ID 匹配时,它会使用已存储的副本作为基础,保留已存储的时间戳和提供器元数据,同时保留新的工具结果。

如果你自行组装请求输入并需要它完全按发送内容处理,请在该调用的 memory.options 中设置 retainFullInput: true,或在 memory 构造函数选项中设置以应用于整个 agent。这会禁用输入过滤,但已存储的历史仍会加载。每条尚未存储的输入消息都会保存到 thread,包括 few-shot 示例。

按 token 限制历史
按 token 限制历史的直接链接

消息数量不能很好地反映上下文大小:一个工具结果可能只有几个 token,也可能有数千个。使用 messageHistory 将最近的历史保持在 token 预算内:

src/mastra/agents/test-agent.ts
export const agent = new Agent({
id: 'test-agent',
memory: new Memory({
options: {
messageHistory: { maxTokens: 8_000, atMaxRemoveTokens: 2_000 },
},
}),
})

Mastra 会根据 maxTokens 计算完整提示词,包括记住的历史、系统指令、上下文和当前轮次。当提示词超出预算时,Mastra 会移除最旧的已记住消息,直到提示词至多为 maxTokens - atMaxRemoveTokens。atMaxRemoveTokens 默认为 maxTokens 的 25%。按块移除历史可以在多个轮次中保持提示词前缀稳定,有助于提供器提示词缓存保持热态。

系统消息、上下文、当前轮次的输入以及 agent 的响应永远不会被裁剪。关联的工具调用和结果会一起移除。如果仅受保护内容就超出 maxTokens,Mastra 会移除所有已记住的历史,但保留受保护内容。

被裁剪的消息仍保留在存储中。在 agent 运行期间,Mastra 会持久化每个 thread 的边界,使它们从后续轮次中排除。仅设置 messageHistory(不设置 lastMessages)会禁用默认的 10 条消息上限。同时设置两者可组合数量上限和 token 预算。将 maxTokens 设置为 0 可禁用消息历史。

提示

启用 memory 时,Studio 会使用消息历史在聊天侧边栏中显示过去的对话。

Thread 标题生成
Thread 标题生成的直接链接

启用 generateTitle 后,Mastra 可以根据对话记录自动生成描述性的 thread 标题。当你构建在线程列表或侧边栏中渲染对话标题的聊天界面时,请使用此选项。

src/mastra/agents/support-agent.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'

export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support agent',
instructions: 'Answer customer support questions.',
model: 'openai/gpt-5.6-sol',
memory: new Memory({
options: {
generateTitle: true,
},
}),
})

标题生成在 agent 响应后异步运行,不影响响应时间。

流式传输生成的标题
流式传输生成的标题的直接链接

默认情况下,标题生成在后台运行,运行的流不会等待它,因此 HTTP 客户端只会在下次获取 thread 时看到标题。设置 emitEvent: true 改为在运行的流上传递标题:流会等待标题,并在 finish 之前发出一个瞬时的 data-thread-title 片段。

src/mastra/agents/support-agent.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'

export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support agent',
instructions: 'Answer customer support questions.',
model: 'openai/gpt-5.6-sol',
memory: new Memory({
options: {
generateTitle: {
emitEvent: true,
},
},
}),
})

流消费者会收到带有已持久化标题的片段,因此聊天 UI 无需轮询即可重命名线程列表条目:

for await (const chunk of stream.fullStream) {
if (chunk.type === 'data-thread-title') {
renameThreadInSidebar(chunk.data.threadId, chunk.data.title)
}
}

该片段是瞬时的:它会传递给流消费者,但永远不会作为对话消息的一部分持久化。由于流会等待标题,emitEvent 会延迟生成标题那一轮的 finish:可能是 thread 的第一轮,或在 minMessages 设置更高阈值时的后续轮次。保持关闭可让标题生成完全非阻塞。

备注

emitEvent 适用于 stream() 运行。generate() 返回 JSON,无法携带该片段,因此保持非阻塞的标题生成。Durable 和 evented agents 尚未发出此片段。在所有这些情况下,标题仍会生成并持久化。

要优化成本或行为,请提供更小的 model 和自定义 instructions:

src/mastra/agents/support-agent.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'

export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support agent',
instructions: 'Answer customer support questions.',
model: 'openai/gpt-5.6-sol',
memory: new Memory({
options: {
generateTitle: {
model: 'openai/gpt-5-mini',
instructions: 'Generate a one-word title.',
},
},
}),
})

访问 memory
访问 memory的直接链接

要访问用于查询、克隆或删除 threads 和消息的 memory 函数,请在 agent 上调用 getMemory():

const agent = mastra.getAgentById('test-agent')
const memory = await agent.getMemory()

使用 Memory 实例查询已存储的 threads 和消息,或克隆对话。

查询
查询的直接链接

使用这些方法获取 threads 和消息,以便在 UI 中显示对话历史或用于自定义记忆检索逻辑。

注意

记忆系统不会强制执行访问控制。在运行任何查询之前,请在应用逻辑中验证当前用户有权访问所查询的 resourceId。

Threads
Threads的直接链接

使用 listThreads() 检索某个资源的 threads:

const result = await memory.listThreads({
filter: { resourceId: 'user-123' },
perPage: false,
})

分页遍历 threads:

const result = await memory.listThreads({
filter: { resourceId: 'user-123' },
page: 0,
perPage: 10,
})

console.log(result.threads) // thread objects
console.log(result.hasMore) // more pages available?

你还可以按 metadata 过滤并控制排序顺序:

const result = await memory.listThreads({
filter: {
resourceId: 'user-123',
metadata: { status: 'active' },
},
orderBy: { field: 'createdAt', direction: 'DESC' },
})

要按 ID 获取单个 thread,请使用 getThreadById():

const thread = await memory.getThreadById({ threadId: 'thread-123' })

Messages
Messages的直接链接

获得 thread 后,使用 recall() 检索其消息。它支持分页和语义搜索,并可选按日期过滤。

获取 thread 的历史而不分页。Recall 默认隐藏提醒信号;传入 hideSignals: false 以包含它们,传入 true 隐藏所有可识别的信号,或传入数组以省略选定的类型。匹配规则和优先级请参阅信号可见性和兼容性。

const { messages } = await memory.recall({
threadId: 'thread-123',
perPage: false,
})

分页遍历消息:

const { messages } = await memory.recall({
threadId: 'thread-123',
page: 0,
perPage: 50,
})

按日期范围过滤:

const { messages } = await memory.recall({
threadId: 'thread-123',
filter: {
dateRange: {
start: new Date('2025-01-01'),
end: new Date('2025-06-01'),
},
},
})

按浅层消息 metadata 过滤:

const { messages } = await memory.recall({
threadId: 'thread-123',
filter: {
metadata: {
category: 'billing',
escalated: true,
priority: 2,
archivedAt: null,
},
},
})

Metadata 过滤器仅匹配浅层标量值:string、有限 number、boolean 和 null。

所有指定的 metadata 键使用 AND 语义。null 过滤器仅匹配显式的 null 值。缺失的 metadata 键不匹配。

Metadata 键必须以字母或下划线开头,且只能包含字母数字字符。长度必须为 128 个字符或更少,并且不能使用 __proto__、constructor 或 prototype 等保留的原型键。

性能取决于存储后端。某些后端可以将部分过滤器下推到数据库,而其他后端会在应用 thread、resource 和日期约束之后、分页之前扫描候选消息。

按 ID 获取单条消息:

const { messages } = await memory.recall({
threadId: 'thread-123',
include: [{ id: 'msg-123' }],
})

按 ID 获取多条消息及其周围上下文:

const { messages } = await memory.recall({
threadId: 'thread-123',
include: [
{ id: 'msg-123' },
{
id: 'msg-456',
withPreviousMessages: 3,
withNextMessages: 1,
},
],
})

按含义搜索(设置请参阅语义召回):

const { messages } = await memory.recall({
threadId: 'thread-123',
vectorSearchString: 'project deadline discussion',
threadConfig: {
semanticRecall: true,
},
})

UI 格式
UI 格式的直接链接

消息查询返回 MastraDBMessage[] 格式。要在前端显示消息,你可能需要将它们转换为 UI 库期望的格式。例如,toAISdkV5Messages 会将消息转换为 AI SDK UI 格式。

Thread 克隆
Thread 克隆的直接链接

Thread 克隆会创建现有 thread 及其消息的副本。这对于分支对话、在可能具有破坏性的操作之前创建检查点,或测试对话的变体很有用。

const { thread, clonedMessages } = await memory.cloneThread({
sourceThreadId: 'thread-123',
title: 'Branched conversation',
})

你可以按数量或日期范围过滤克隆的消息,并指定自定义 thread ID。还可以使用工具方法检查克隆关系。

如果你不需要返回复制的消息,例如在分叉长 thread 时,请使用 copyThread()。它从不返回消息 payload,并且在 LibSQL 和 PostgreSQL 上会在数据库内部复制行。启用语义召回时,复制的消息仍会分批读回以生成 embeddings。

完整 API 请参阅 cloneThread()、copyThread() 和克隆工具。

删除消息
删除消息的直接链接

要从 thread 中移除消息,请使用 deleteMessages()。你可以按消息 ID 删除,或清除 thread 中的所有消息。