跳至主要內容

多使用者 thread

單一 Mastra thread 可由多位使用者共用,每位使用者都有自己的名稱與功能角色。你可以在訊息內文中帶入發話者身分,讓 Agent 從單一共用 thread 讀取內容時,仍能區分不同使用者。

何時使用多使用者 thread
「何時使用多使用者 thread」的直接連結

當多人透過同一個 Agent 針對相同主題協作時,請使用多使用者 thread:

  • 由編輯者、審查者與核准者共同處理的協作文件
  • 由一個助理服務多位參與者的群組聊天
  • 不同角色具有不同權限的多方審查

讓所有參與者共用一個 resourceId
「share-one-resourceid-across-all-participants」的直接連結

一個 thread 只屬於一個 resourceId,因此共用 thread 上的所有參與者都必須傳入相同的值。請不要使用使用者 ID(單一使用者應用程式的預設做法),而是依對話本身設定 resourceId;例如,共用文件可使用 doc_${docId},群組聊天則可使用 room_${roomId}。所有人都指向相同的 resourceId 後,便會讀寫同一份歷史。

使用發話者身分標記每則使用者訊息
「使用發話者身分標記每則使用者訊息」的直接連結

模型需要知道每一輪是誰在發話。訊息內文是唯一會保留至歷史並重新回到脈絡中的位置,因此請將每則使用者訊息包在簡短的 <turn> 標籤中,並帶入發話者的 ID、名稱與角色。此標籤會持續附在訊息上,因此回想先前輪次時,模型仍能看出每段話是誰說的。

使用簡短的輔助函式建立標籤。以下範例是其中一種做法;請複製到專案中,再依你的使用者資料結構調整:

src/mastra/identity.ts
export 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>`,
}
}

在 Agent 的 instructions 中教它如何讀取 <turn> 標籤。Agent 必須設定 memory,才能以 threadresource 呼叫:

src/mastra/agents/collab.ts
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 user's identity:

<turn author_id="u_alice" author_name="Alice" functional_role="editor">
...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(),
})

使用包裝後的訊息呼叫 Agent。每位參與者都共用相同的 threadresource

src/mastra/call.ts
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> 標籤會保留在訊息內文中,因此日後回想歷史時,模型仍能看出每段話是誰說的。

與 Memory 層搭配使用
「與 Memory 層搭配使用」的直接連結

使用者標記模式可與每個 Memory 層搭配使用。請依對話需要保留每位使用者事實的時間長度選擇合適的層:

  • 短對話(單次工作階段,或 thread 小到足以放入 lastMessages),或需要逐字記錄誰說了什麼時:只使用訊息歷史即可。歷史中的使用者標籤已經足夠,不需要額外的 Memory 層。
  • 長時間執行的 thread(對話超出 lastMessages,而且每位使用者的事實必須在歷史遭移除後繼續保留):使用 Observational Memory
  • 需要結構化參與者清單,或儲存 adapter 不支援 OM(OM 需要 LibSQL、PG 或 MongoDB):使用 working memory

建議使用 Observational Memory 或 working memory 其中之一,因為兩者涵蓋的需求有所重疊。同時執行只會增加延遲與 token 成本,效益不大。

只使用訊息歷史
「只使用訊息歷史」的直接連結

對於短對話,或需要逐字記錄誰說了什麼的情境,歷史中的使用者標籤已經足夠。lastMessages 會將先前輪次帶回脈絡,並完整保留其歸屬資訊:

src/mastra/agents/collab-basic.ts
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,
},
})

模型會從目前訊息的 <turn> 標籤,以及透過 lastMessages 帶回的先前已標記訊息中讀取身分。

Observational Memory(OM)會將每位使用者的事實擷取至背景記錄,不會占用 Agent 的 Tool 額度。預設 Observer 模型能直接讀取 <turn> 標籤,並產生如 Alice stated her favorite color is teal.Bob asked for QA sign-off before publish. 的歸屬描述。

若儲存系統支援,多使用者 thread 應優先使用 OM,而非 working memory。OM 會自動擷取事實、可擴充至任意數量的參與者,也不需要維護範本。啟用時不需覆寫任何設定:

src/mastra/agents/collab-om.ts
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 需要支援它的儲存 adapter:@mastra/libsql@mastra/pg@mastra/mongodb@mastra/oracledb

備註

若將 Observer 改為較弱的模型後,發現事實都被歸為泛稱的 User,請使用 observation.instruction 教 Observer 如何讀取 <turn> 標籤。

搭配 working memory
「搭配 working memory」的直接連結

當 OM 不適用時,例如儲存 adapter 不支援 OM,或需要一份結構明確、具決定性且 Agent 每輪都能讀寫的參與者清單,請使用 working memory。

預設的 working memory 範本假設每個 thread 只有一位使用者(「First Name」、「Last Name」等)。若是多使用者 thread,請提供包含參與者清單的範本:

src/mastra/agents/collab-wm.ts
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
`,
},
},
})

設定 scope: 'thread',讓參與者清單屬於文件,而不是任何個別使用者。再加入一項 instruction,要求 Agent 每當新的 author_id 出現在 <turn> 中時,便將新參與者附加至清單。

如需範本的詳細資訊,請參閱自訂範本

安全性
「安全性」的直接連結

請從已驗證的要求脈絡設定 speaker,絕不要從要求內文取得。若用戶端可以自行選擇 author_id,使用者就能冒充他人。請使用 Request Context 從驗證層讀取已驗證的使用者,並在呼叫 Agent 前於伺服器端建立 <turn> 標籤。