进阶 mastra.ai 2026-10-12 10:15:56 · 46 阅读

第13章 Channels——将 Agent 接入 Slack、Discord 等消息平台

Channels(频道) 新增于:@mastra/core@1.22.0 Channels 用于把 agent 接入 Slack、Microsoft Teams、Discord、Telegram、WhatsApp、GitHub、Linear 等消息与协作平台。当用户在平台上发送消息或评论时,agent 会接收并通过正常的 agent 流程处理,再把响应流式传回对话。Mastra 在这一层使用 Chat SDK。 从你所使用平台的页面开始: DiscordGitHubiMessageMicrosoft TeamsSlackTelegramWhatsApp 其他适配器可参见 Other adapters。除了这里列出的平台,Mastra channels 还兼容 Chat SDK 的其他适配器,配置方式在各适配器间保持一致。 何时使用 channels 当 agent 需要满足以下场景时,可以使用 channels: 在用户日常沟通或工作的平台上直接触达他们。 在 Slack、Microsoft Teams、Discord、Telegram、WhatsApp 等聊天平台中响应。 支持多人群聊 agent,让多人在共享的频道或会话中与同一个 agent 交互。 集成协作流程,例如 GitHub issue、pull request 讨论、Linear 评论。 用 Mastra Connect 更快配置 channels 如果你不想为每个 channel 单独配置 bot 或应用并自行维护凭证,可以使用 Mastra Connect。它会帮你管理凭证,简化 channel 的配置和维护。 配置 agent Channels 基于 Chat SDK 适配器,Mastra 侧的用法是统一的:创建一个 channel 适配器,然后挂载到 agent 上。 src/mastra/agents/your-agent.tsimport { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'

export const yourAgent = new Agent({
id: 'your-agent',
name: 'Your Agent',
instructions: 'You are a helpful assistant.',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: createSlackAdapter(),
},
},
})
注意:Channel 适配器需要配置对应平台的凭据和环境变量才能完成身份验证和请求校验,比如 bot token、签名密钥、App ID、webhook 验证 token 等。具体变量名请查阅所用平台的指南或 Chat SDK 适配器目录。 建议为 Channel 配置存储。存储可以在服务重启后保留 Channel 和内存状态,包括 thread 订阅和工具审批记录: src/mastra/index.tsimport { Mastra } from '@mastra/core'
import { LibSQLStore } from '@mastra/libsql'
import { yourAgent } from './agents/your-agent'

export const mastra = new Mastra({
agents: { yourAgent },
storage: new LibSQLStore({
id: 'mastra-storage',
url: process.env.DATABASE_URL,
}),
})
Webhook 路由(直达链接) 各平台通过 webhook 将 Channel 活动发送到 Mastra。Webhook 是一个 HTTP 端点,当有事件发生时(比如收到新消息、被 @ 提及,或用户在交互式工具审批卡片上点击「Approve」),平台就会调用它。Webhook 把消息交给你的 agent 处理,agent 再在同一个 Channel 中回复。 Mastra 会为每个已配置的适配器自动注册一个 webhook 路由,并代为处理请求: /api/agents//channels//webhook
例如,ID 为 your-agent 的 agent 使用 Slack 适配器时,路由是: /api/agents/your-agent/channels/slack/webhook
把平台的 webhook、事件或交互 URL 指向这个路径即可。具体操作请参考所用平台的指南或 Chat SDK 文档。注意,webhook 会在 agent 完成响应之前先确认请求。关于验证或处理函数执行失败时的行为,参见 Error handling and delivery 一节。 本地开发时,平台 webhook 需要一个公网 URL 才能访问你的本地服务器。可以用 cloudflared 或 ngrok 之类的隧道工具把服务器暴露出去,默认端口为 localhost:4111: npmpnpmYarnBunnpx cloudflared tunnel --url http://localhost:4111
pnpm dlx cloudflared tunnel --url http://localhost:4111
yarn dlx cloudflared tunnel --url http://localhost:4111
bun x cloudflared tunnel --url http://localhost:4111
使用生成的公网 URL 作为 webhook 路径的 base URL,例如 https://abc123.trycloudflare.com/api/agents/your-agent/channels/slack/webhook。 注意 Tunnel URL 仅用于本地开发。部署 Mastra server 之后,记得把平台的 webhook、event 或 interactions URL 更新为你的生产环境地址。 线程上下文 用户在频道的会话线程中 @ 提到 agent 时,agent 可能没有此前的上下文。第一次被提及时,Mastra 会先从平台拉取更早的消息,保存到 agent 的记忆线程中,然后再处理这条提及。 线程中第一次被提及时,agent 会拉取该线程的首条消息以及提及之前的最近几条消息。maxMessages(默认 10)控制这个窗口大小:即首条消息加上最多 maxMessages - 1 条最近的消息。如果中间还有其他消息,会在相应位置保存一条 [… N earlier Slack messages omitted] 占位标记(平台名称随频道类型变化)。 每条拉取到的消息都会作为独立的 user 消息存入 memory,附带其作者、平台消息 id 和附件,并带有 source: 'thread-history' 属性。随后那条提及本身作为普通消息正常派发。 agent 回复之后会订阅该线程,通过 Mastra memory 拥有完整的历史记录。 之后该线程的消息不会再从平台重新拉取。 设置 threadContext: { maxMessages: 0 } 可以关闭此行为。注意这只对非私信线程生效。 没有配置 memory 的 agent 无法写入这些记录,因此会把同样的历史以上下文文本的形式注入提及消息中。写入 memory 失败时也走同样的回退逻辑。 memory 的 lastMessages 选项限制了最终送入 prompt 的消息数量。其默认值为 10,当 maxMessages 为 10 时会把首条消息和省略标记裁剪掉,所以建议把 lastMessages 设置为至少 maxMessages + 1。 Mastra 还会附加一条简短的 system 消息,告诉 agent 请求来自哪个频道和平台,比如消息是来自私信还是公开频道。设置 threadContext: { addSystemMessage: false } 可以跳过它。 工具审批 设置了 requireApproval: true 的工具会渲染成带有 Approve 和 Deny 按钮的交互式卡片: src/mastra/tools/delete-file.tsimport { promises as fs } from 'node:fs'
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'

export const deleteFile = createTool({
id: 'delete-file',
description: '从系统中删除一个文件',
inputSchema: z.object({
path: z.string().describe('要删除的文件路径'),
}),
requireApproval: true,
execute: async ({ path }) => {
await fs.unlink(path)
return { deleted: path }
},
})
Agent 调用这个工具时,用户会看到一张卡片,上面显示工具名称、参数,以及 Approve 和 Deny 按钮,工具只有在获得批准后才会执行。 在 adapter 上设置 toolDisplay: 'text',可以把工具调用渲染成纯文本而不是交互卡片。在 'hidden' 模式下,当同一线程后来收到新的用户消息时,autoResumeSuspendedTools 可以恢复带有数据的 suspend() 流程,但这需要启用 memory。注意,hidden 模式只是隐藏了审批按钮,并不等于用户同意——需要审批的工具会一直处于挂起状态,直到通过 UI 或 API 操作显式提交批准或拒绝。 自定义动作处理 使用 handlers.onAction 来处理你自行发送的卡片上的按钮点击和下拉选择。该处理函数会收到 Chat SDK 的 action 事件、默认处理函数,以及与其他 channel handler 相同的 ctx。在委托给默认处理函数之前设置在 ctx.requestContext 上的值,会一路传递到恢复的工具调用中。默认处理函数只处理内置的 Approve 和 Deny 按钮,忽略其他所有 action ID,所以对不处理的事件应调用它,以保证工具审批功能正常: src/mastra/agents/your-agent.tschannels: {
adapters: {
slack: createSlackAdapter(),
},
handlers: {
onAction: async (event, defaultHandler, ctx) => {
if (event.actionId === 'retry') {
await event.thread?.post('正在重试...')
return }
await defaultHandler()
},
},
},
设置 onAction: false 会完全禁用动作处理,包括内置的工具审批按钮。如果需要在 channels 启动后再注册更多 Chat SDK 处理器,可以使用 agent.channels.sdk。 回复格式化Direct link to 回复格式化 Agent 回复默认以 markdown 发布。对于原生支持 markdown 渲染的平台(如 Slack),粗体文本、链接和表格会直接渲染;其他平台则会将 markdown 转换为各自的格式。Agent 只需编写标准 markdown,就能在任何地方正确渲染,与在 Studio 中看到的渲染效果一致。 如果想以纯文本字面形式发布回复,可以在 adapter 上设置 textFormat: 'plain': src/mastra/agents/your-agent.tschannels: {
adapters: {
slack: {
adapter: createSlackAdapter(),
textFormat: 'plain',
},
},
},
如果你的 Agent 被提示输出平台特有的方言(如 Slack mrkdwn)而非标准 markdown,可以用这个逃生通道。不过,如果你之前为了绕过 markdown 被字面渲染而添加了这类提示指令,现在应该把它们删掉——默认行为已能原生渲染标准 markdown。textFormat 只影响最终回复文本,工具卡片、错误消息和原生流式文本不受影响。 多用户识别Direct link to 多用户识别 在群聊中,Mastra 会在每条消息前加上发送者的名称和平台 ID,让 Agent 能够区分不同的说话人: [Alice (@U123ABC)]: Can you help me with this?
[Bob (@U456DEF)]: I have a question too.
每条消息的 Signal 元数据Direct link to 每条消息的 Signal 元数据 在自定义 Channel 处理器中使用 signalMetadata,可以为单条入站消息附加结构化上下文。当这条消息启动空闲 run 或加入进行中的 run 时,元数据会随消息一起传递。 下面的示例在调用默认处理器之前附加了已注册的文件 ID: src/mastra/agents/your-agent.tsimport { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'

export const yourAgent = new Agent({
id: 'your-agent',
name: 'Your Agent',
instructions: 'You are a helpful assistant.',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: createSlackAdapter(),
},
handlers: {
onDirectMessage: async (thread, message, defaultHandler, ctx) => {
ctx.signalMetadata.attachmentIds = ['file-1']
await defaultHandler(thread, message)
},
},
},
})
只能添加可 JSON 序列化、非敏感的值。Signal 元数据可能保存在内存中,或通过共享的发布-订阅传输通道发送,但不会被加入模型 prompt。 requestContext 用于运行级别的配置,比如某条触发运行的消息携带的凭据。signalMetadata 用于必须与单条消息绑定的上下文,包括发送给正在运行中的 run 的消息。 错误处理与消息投递 Channel webhook 会在 agent 完成之前就向平台确认收到,因此返回 200 只代表“已收到”,而非“已回答”。在确认之前,adapter 会同步拒绝非法请求(验证失败返回 401,请求体无法解析返回 400,瞬时状态故障返回 503),平台会根据自身策略在非 2xx 响应时重新投递。一旦返回 200,平台就不会再重试,错误由 Mastra 接管。默认 handler 会捕获 agent 运行失败,并向线程发送错误消息(可通过 formatError 自定义,默认为 ❌ Error: {message})。自定义 handler 中针对单条消息抛出的异常只会被 Chat SDK 记录日志,不会发送任何消息或重试(多发送者批量消息参见 Batched messages 一节)。自定义 handler 中的失败需要自己捕获: src/mastra/agents/your-agent.tschannels: {
adapters: {
slack: createSlackAdapter(),
},
handlers: {
onDirectMessage: async (thread, message, defaultHandler, ctx) => {
try {
ctx.signalMetadata.ticketId = await lookupTicket(message)
} catch (error) {
ctx.mastra?.getLogger().error('Ticket lookup failed', { error })
await thread.post('Something went wrong looking up your ticket. Try again in a moment.')
return
}
await defaultHandler(thread, message)
},
},
},
平台重试有时会导致同一事件被投递多次。Adapter 利用 channel state 进行去重,存储层可以在重启后保留这些状态。由于这种保护是尽力而为的,自定义 handler 的副作用应当设计为幂等,这样即使重复投递,也不会重复执行诸如创建工单之类的工作。 批量消息 当 chatOptions.concurrency 策略(burst、debounce 或 queue)将多条消息合并为一次分发时,Mastra 会把这一批按发送者拆分为多轮:同一发送者的连续消息构成一轮,按时间从早到晚排列。你的 handler 每轮运行一次,每轮都有各自的 ctx.requestContext、ctx.signalMetadata 和 ctx.skipped(该轮中较早的消息),因此授权和上下文对每个发送者都生效。在 handler 内部务必 await defaultHandler,否则不同发送者的轮次可能重叠执行。某一轮 handler 抛出异常时会被记录,其余轮次仍会继续运行,首个错误随后再抛出。如果一批消息因其中某条提及了机器人而被路由到 onMention,其他发送者未提及机器人的消息也会进入 onMention,此时 message.isMention 为 false。 多模态内容 Gemini 这类模型可以原生处理图片、视频和音频。结合 inlineMedia 和 inlineLinks,用户就能跨平台与你的 agent 分享富媒体内容: src/mastra/agents/vision-agent.tsimport { Agent } from '@mastra/core/agent'
import { createDiscordAdapter } from '@chat-adapter/discord'

export const visionAgent = new Agent({
id: 'vision-agent',
name: 'Vision Agent',
instructions: 'You can see images, watch videos, and listen to audio.',
model: 'google/gemini-2.5-flash',
channels: {
adapters: {
discord: createDiscordAdapter(),
},
inlineMedia: ['image/*', 'video/*', 'audio/*'],
inlineLinks: [
{ match: 'youtube.com', mimeType: 'video/*' },
{ match: 'youtu.be', mimeType: 'video/*' },
'imgur.com',
],
},
})
配置完成后: 用户上传截图,agent 描述图中内容。 用户上传 .mp4 视频片段,agent 总结视频。 用户粘贴 YouTube 链接,agent 观看并讨论视频。 用户粘贴 imgur 链接,agent 直接查看图片。 默认情况下只有图片会以内联方式发送(inlineMedia: ['image/*'])。不支持的类型会以文本摘要的形式告知 agent,这样 agent 既能了解文件内容,又不会在拒绝该类型文件的模型上报错。所有 inlineMedia 模式请参阅 Channels 参考,域名匹配、HEAD 检测和强制 MIME 类型请参阅 inlineLinks 参考。 Serverless 部署 在 Vercel 这类 serverless 平台上,每个请求都运行在独立的、短生命周期的实例中。要让 Channels 在这种环境下可靠工作,需要两样东西:一种在 agent 响应期间保持函数存活的方式,以及一个让实例之间协同工作的共享 pub/sub。 用 waitUntil 保持函数存活 Channel webhook 会立即返回 200 响应,然后 agent 在后台运行并发布回复。但大多数 serverless 平台在函数响应后会立刻冻结实例,导致 agent 还没来得及回答运行就被中断了。传入一个 waitUntil 函数,平台就会保持实例存活,直到运行结束。 在 Vercel 上,传入 @vercel/functions 提供的 waitUntil: src/mastra/agents/your-agent.tsimport { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
import { waitUntil } from '@vercel/functions'

export const yourAgent = new Agent({
id: 'your-agent',
name: 'Your Agent',
instructions: 'You are a helpful assistant.',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: createSlackAdapter(),
},
waitUntil,
},
})
Vercel 和 AWS Lambda 需要显式使用 waitUntil,因为响应一发送函数就会被冻结。Cloudflare Workers 和 Netlify Functions 会从请求上下文中自动检测,无需手动调用。如果某个运行时的 waitUntil 挂在请求上下文上但无法自动检测,可以使用 resolveWaitUntil。详见 Channels 参考文档。 通过共享 pub/sub 协调多实例 Channels 通过 agent 的 signal 管道路由消息,并且每次 run 会在其 thread 上获取一个 lease,确保同一时刻只有一个 run 拥有该对话。 默认的内存级 pub/sub 无法跨实例,因此在 serverless 环境中,后续消息可能被路由到与运行 agent 不同的实例上。 没有共享 pub/sub 时,该实例无法触达正在运行的 run,于是会自己启动一个新的,导致原来的 run 不受影响、同一 thread 被处理两次。 在 Mastra 实例上配置基于 Redis Streams 的共享 pub/sub,让 lease 和 signal 能跨实例协调: src/mastra/index.tsimport { Mastra } from '@mastra/core'
import { RedisStreamsPubSub } from '@mastra/redis-streams'
import { yourAgent } from './agents/your-agent'

export const mastra = new Mastra({
agents: { yourAgent },
pubsub: new RedisStreamsPubSub({
url: process.env.REDIS_URL,
keyPrefix: 'mastra:my-app',
}),
})
Vercel 的托管 Redis 集成和 Upstash Redis 都能正常使用。关于何时需要分布式 pub/sub,请参考 PubSub 指南和 RedisStreamsPubSub 文档。 其他适配器 Mastra channels 使用 Chat SDK 适配器,因此上述平台指南并非支持平台的完整列表。任何导出与 channels.adapters 兼容的适配器工厂的 Chat SDK 平台适配器都可以使用。 适配器目录 Chat SDK 的适配器目录是可用适配器的权威来源。下面的列表只是一个快照,不一定总是最新的。 截至撰写本文时,Chat SDK 列出的适配器包括: AgentPhone Baileys WhatsApp Discord GitHub Google Chat iMessage Kapso Lark / 飞书 Linear Linq Liveblocks Matrix Mattermost Messenger Novu Resend Sendblue Telegram Twilio Velt Web Webex 微信 WhatsApp Business Cloud X Zalo Zernio 请查阅各适配器文档,了解包名、凭证变量、webhook 要求以及平台特定的行为。 沿用相同的 Mastra 接入方式 每个 Chat SDK 适配器在 Mastra 这边的配置流程都一样:安装适配器包,把它的工厂添加到 channels.adapters,然后配置平台凭证,并将平台的 webhook 指向生成的 Mastra 路由。 可参考以下平台指南了解 Mastra 的接入方式: Discord Microsoft Teams Slack Telegram WhatsApp 查阅适配器文档 在把新适配器接入 Mastra 之前,请先查阅适配器文档,确认以下事项: 对附件、卡片和富消息的支持情况。 凭证相关的环境变量。 平台所需的权限或 scopes。 普通消息是否需要长时间运行的监听器或轮询。 平台是否需要为设置、事件或交互单独配置路由。 Webhook 验证行为。 相关内容 Channels 参考 AgentController channels 📹 Mastra channels 工作坊

评论 (0)