跳至主要內容

多用戶對話串

一個 Mastra 對話串可由多名用戶共享,而每名用戶都有自己的名稱和職能角色。你可在訊息正文中加入發言者身分,讓 Agent 從同一個共享對話串讀取內容時,仍能分辨不同用戶。

何時使用多用戶對話串
何時使用多用戶對話串 的直接連結

當多人透過同一個 Agent 就相同主題協作時,可使用多用戶對話串:

  • 由編輯、審閱者和審批者共同處理的協作文檔
  • 由一個助手服務多名參與者的群組聊天
  • 不同角色擁有不同權限的多持份者審閱流程

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

一個對話串只屬於一個 resourceId,因此共享對話串上的所有參與者都需要傳入相同的值。不要使用用戶 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 的指示中教它如何讀取 <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> 標籤會保留在訊息正文中,因此在往後的輪次重新載入歷史記錄時,模型仍可看到每句說話出自誰人。

與不同記憶層配合使用
與不同記憶層配合使用 的直接連結

用戶標記模式可與每個記憶層配合使用。請按對話需要記住每名用戶資料的時間長度選擇記憶層:

  • 短對話(單一工作階段,或對話串的內容足夠少,可完全容納於 lastMessages),或需要逐字記錄每句說話出自誰人時:只使用訊息歷史記錄。歷史記錄中的用戶標籤已經足夠,毋須額外的記憶層。
  • 長時間運作的對話串(對話內容超出 lastMessages,而且需要在歷史記錄被移除後仍保留每名用戶的資料):使用觀察式記憶
  • 需要結構化的參與者名單,或儲存空間適配器不支援 OM(OM 需要 LibSQL、PG 或 MongoDB):使用工作記憶

我們建議使用觀察式記憶或工作記憶,因為兩者涵蓋的需要有所重疊。同時運行兩者會增加延遲和 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. 的歸屬資料。

如果你的儲存空間支援 OM,多用戶對話串應優先使用 OM,而非工作記憶。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 需要支援它的儲存空間適配器:@mastra/libsql@mastra/pg@mastra/mongodb@mastra/oracledb

備註

如果你將 Observer 切換至能力較弱的模型,並看到資料被合併為通用的 User,請使用 observation.instruction 教 Observer 如何讀取 <turn> 標籤。

使用工作記憶
使用工作記憶 的直接連結

當 OM 不適用時,可使用工作記憶,例如儲存空間適配器不支援 OM,或你需要 Agent 在每一輪都能讀寫結構化且結果可預期的參與者名單。

預設的工作記憶範本假設每個對話串只有一名用戶(「First Name」、「Last Name」等)。對於多用戶對話串,請提供包含參與者名單的範本:

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',讓參與者名單屬於文檔,而非任何個別用戶。加入一項指示,要求 Agent 每當有新的 author_id 出現在 <turn> 中時,便將新參與者加入名單。

如需進一步了解範本,請參閱自訂範本

安全性
安全性 的直接連結

請從已驗證身分的請求上下文設定 speaker,切勿從請求正文設定。如果客戶端可自行選擇 author_id,一名用戶便可冒充另一名用戶。使用 Request Context 從驗證層讀取已驗證的用戶,並在呼叫 Agent 前於伺服器上建立 <turn> 標籤。