如何让多个用户共享同一个 Mastra 记忆线程并保留发言人身份
如何让多个用户共享同一个 Mastra 记忆线程并保留发言人身份【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra当你让多个参与者围绕同一份文档、同一个群组话题与同一个 Mastra agent 对话时会遇到两个问题一是多人如何读写同一份对话历史二是模型如何知道每一轮是谁在说话。Mastra 的 Multi-user threads 文档给出的方案是所有参与者共用同一个resourceId和thread并把发言人身份写进消息正文的turn标签里。标签随消息一起持久化后续任何一轮召回历史时模型仍然能看到“谁说了什么”。适用前提agent 必须配置了memory这样才能用thread和resource调用。文档示例使用mastra/libsql作为存储适配器本地保存为 SQLite 文件。第一步所有参与者共用同一个resourceIdMastra 的线程归属唯一resourceId见 Message historyThread 是对话Resource 是拥有该对话的实体。单用户应用通常把resourceId设为用户 ID但共享线程要反过来——以对话本身作为 key共享文档用doc_${docId}这类值群组聊天用room_${roomId}。所有人指向同一个resourceId才会读写同一份历史。第二步为每条用户消息打上发言人标签模型在每一轮都需要知道谁在说话。消息正文是唯一能进入历史、又能回到上下文的位置所以要把每条用户消息包进一个带发言人id、name、role的turn标签。文档提供了一个可复制进项目、再按你自己用户数据形状改造的辅助函数文档示例文件名为src/mastra/identity.tsexport type Speaker { id: string name: string role: string } function escapeAttr(value: string) { return value .replace(//g, amp;) .replace(//g, quot;) .replace(//g, lt;) .replace(//g, gt;) } export function asUserTurn(speaker: Speaker, text: string) { const id escapeAttr(speaker.id) const name escapeAttr(speaker.name) const role escapeAttr(speaker.role) return { role: user as const, content: turn author_id${id} author_name${name} functional_role${role} ${text} /turn, } }第三步配置带 memory 的 agent 并在 instructions 中教会它读标签下面示例与文档一致其中文档代码块里的model使用了文档站点在构建时替换的占位 token__GATEWAY_OPENAI_MODEL_MINI__其解析值为openai/gpt-5-mini这里直接写解析后的模型名import { Agent } from mastra/core/agent import { Memory } from mastra/memory import { LibSQLStore } from mastra/libsql const memory new Memory({ storage: new LibSQLStore({ id: collab-storage, url: file:./collab.db }), options: { lastMessages: 20, }, }) export const collabAgent new Agent({ id: collab, name: CollabAgent, model: openai/gpt-5-mini, memory, instructions: You are a collaborative document assistant. Multiple users talk to you in the SAME thread. Every user message is wrapped in a turn tag carrying the users identity: turn author_idu_alice author_nameAlice functional_roleeditor ...message text... /turn Rules: 1. Address users by their author_name. 2. Respect functional_role: editors propose changes, reviewers approve. 3. When attributing past statements, read author_name from the surrounding turn tag. 4. Do not echo the turn tags back at users. .trim(), })instructions 里的规则用author_name称呼用户、尊重functional_role、引用过往发言时从turn标签取归属、不要把标签回显给用户是模型能正确区分发言人的关键不要省略。第四步所有参与者用同一个thread和resource调用 agent每个参与者调用 agent 时传入的是包装后的消息而memory选项里的thread和resource对所有人保持同一值import { asUserTurn } from ./identity const docResourceId doc_42 const docThreadId doc_42 const alice { id: u_alice, name: Alice, role: editor } const bob { id: u_bob, name: Bob, role: reviewer } await collabAgent.generate([asUserTurn(alice, My favorite color is teal.)], { memory: { thread: docThreadId, resource: docResourceId }, }) await collabAgent.generate([asUserTurn(bob, I want QA sign-off before publish.)], { memory: { thread: docThreadId, resource: docResourceId }, })turn标签持久化在消息正文里因此后续轮次召回历史时模型仍能看到每一轮是谁说的。客户端调用时只发送新增的这一条消息不要重发完整历史——Message history 文档明确警告重发历史是冗余的且客户端时间戳与存储时间戳冲突时可能引起消息排序错误。验证确认带标签的消息已落库并被后续轮次读取turn标签随消息持久化这是文档给出的直接结果。要核对历史确实写入了共享线程可以用 Message history 文档提供的查询路径先通过agent.getMemory()拿到Memory实例再用recall()拉取该线程的消息const memory await collabAgent.getMemory() const { messages } await memory.recall({ threadId: docThreadId, perPage: false, })检查返回的消息每个参与者发送的 user 消息正文应包含对应的turn标签author_id、author_name、functional_role。文档对启用 Observational Memory 时的效果给出了示例文档示例非固定输出默认 Observer 模型原生读取turn标签产出类似Alice stated her favorite color is teal.和Bob asked for QA sign-off before publish.的归属记录。如果改用能力较弱的 Observer 模型后看到事实被归属到泛化的User而不是具体参与者文档建议用observation.instruction说明如何解读turn标签。查询前注意Message history 文档指出 memory 系统本身不强制访问控制查询resourceId前要在应用逻辑里确认当前用户有权访问该资源。长对话的可选分支Observational Memory 或 working memory上面lastMessages: 20的记忆方式只适合短对话只要线程不超过lastMessages能带回的范围历史里的用户标签就够用。文档给出了按需求选层的判断依据短对话或需要逐字保留谁说了什么的记录只用消息历史无需额外记忆层。长线程对话超出lastMessages、需要每用户事实在被历史淘汰后仍存活用 Observational Memory文档推荐。需要结构化参与者列表或存储适配器不支持 OM用 working memory。文档建议二者只选其一OM 和 working memory 覆盖重叠的需求同时开启只会增加延迟和 token 成本收益不大。分支一启用 Observational Memory文档推荐OM 把每用户事实提取进后台日志不占用 agent 的工具预算。无需额外覆盖开启即可import { Memory } from mastra/memory import { LibSQLStore } from mastra/libsql const memory new Memory({ storage: new LibSQLStore({ id: collab-storage, url: file:./collab.db }), options: { lastMessages: 20, observationalMemory: true, }, })OM 有存储适配器要求这里两份文档的表述略有出入按各自文档保留Multi-user threads 列出mastra/libsql、mastra/pg、mastra/mongodb、mastra/oracledbObservational Memory 文档则列出mastra/pg、mastra/libsql、mastra/mysql、mastra/mongodb、mastra/convex、mastra/oracledb。选适配器时以你自己的版本实际支持为准。分支二working memory 保存参与者列表默认 working memory 模板假设每线程只有一个用户First Name、Last Name 等多用户线程要自定义带参与者列表的模板并把scope设为thread让参与者列表归属于文档而不是某个用户import { Memory } from mastra/memory import { LibSQLStore } from mastra/libsql const memory new Memory({ storage: new LibSQLStore({ id: collab-storage, url: file:./collab.db }), options: { lastMessages: 20, workingMemory: { enabled: true, scope: thread, template: # Document Collaboration State ## Participants !-- One entry per known collaborator. Use author_id as the stable key. -- !-- - **author_name** (author_id, functional_role): their position -- ## Open Questions ## Decisions , }, }, })再加一条 instruction每当turn里出现新的author_id就让 agent 把新参与者追加到列表。模板写法细节可参考 Working memory 文档的 Custom templates 一节。安全边界发言人必须来自认证上下文文档明确要求speaker从你的认证请求上下文中取绝不能来自请求体——如果客户端可以自己选author_id一个用户就能冒充另一个用户。做法是用 Request Context 在 server 端从认证层读取已验证的用户再构建turn标签后才调用 agent。Request Context 支持在 server middleware 中从请求提取信息后set进上下文agent 调用时传入即可具体认证字段怎么取取决于你自己的 auth 层实现文档只约束“来自认证上下文”这一原则。相关文档Multi-user threads本文主文档Message historythread/resource 概念、recall()查询Observational Memory长对话记忆层配置Working memory模板与 scope 写法Request Context请求级值传递与 middleware 示例【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考