Mastra Slack Agent 模板详解:构建支持流式响应与线程记忆的多 Agent Slack Bot
2026/9/13 18:34:38 网站建设 项目流程

Mastra Slack Agent 模板详解:构建支持流式响应与线程记忆的多 Agent Slack Bot

【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra

本篇以 template-slack-agent 模板为主体,讲解如何在 Mastra 框架中把 AI Agent 接入 Slack:包括多 Agent 各自绑定独立 Slack App 与 Webhook 路由的架构、HMAC 签名安全校验、基于消息线程(thread)的会话记忆,以及把 Agent 流式输出逐字渲染到 Slack 消息中的完整实现。读完之后,你可以按 Quickstart 直接搭起一个可运行的 Slack Bot 项目,并能读懂模板每一处关键源码以完成二次定制。

模板要解决的问题

将 AI Agent 接入 Slack 是最常见的集成场景之一——无论是内部工具、客服还是团队自动化。这个模板展示了如何用 Mastra 把 Agent 接入 Slack,并做对了三件最容易踩坑的事:

  • 流式响应(streaming):Agent 生成回答的过程中,Slack 消息实时显示“思考中/调用工具中”的动画状态,最终替换为完整回复;
  • 线程级会话记忆(thread-based conversation memory):同一个 Slack 线程内的对话共享上下文,不同线程互不干扰;
  • 多 Agent 支持:每个 Agent 绑定自己独立的 Slack App 与独立的 webhook 路由(/slack/{agentName}/events),互不串扰。

模板内置了两个演示 Agent(reversecaps)来演示这套模式,你可以随时替换成自己的 Agent 快速上手。

模板结构与技术栈

模板采用src/mastra/下的单入口组织方式,核心文件如下:

文件职责
src/mastra/index.ts创建 Mastra 实例,注册 agents、workflows、LibSQL 存储与 Slack 路由
src/mastra/slack/routes.tsSlack 事件路由工厂,每个 Slack App 一个路由
src/mastra/slack/verify.tsSlack 请求签名(HMAC-SHA256)校验
src/mastra/slack/streaming.ts把 Agent 流式输出渲染到 Slack 消息
src/mastra/slack/status.ts / constants.ts状态文案与动画帧、计时常量
src/mastra/agents/reverse-agent.ts / caps-agent.ts两个演示 Agent
src/mastra/workflows/reverse-workflow.ts4 步演示工作流

package.json 声明了 Node.js>=22.13.0的运行要求,核心依赖为@mastra/core@mastra/libsql@mastra/memory@slack/web-api(^7.14.1),工具与工作流的 schema 使用zod(^4.3.6)。脚本方面提供devmastra dev)、buildstart三个命令。

前置条件

  • OpenAI API key:模板默认使用 OpenAI 模型(源码中为openai/gpt-5-mini),但换成任意模型即可;
  • Slack App:每个 Agent 各需要一个独立的 Slack App,包含 bot token(Bot User OAuth Token,xoxb-开头)和 signing secret;
  • ngrok 或类似隧道:本地开发时需要一个公网 URL 供 Slack 回调。

Quickstart

第 1 步:克隆模板

执行脚手架命令在本地生成项目:

npx create-mastra@latest --template slack-agent

第 2 步:配置环境变量

.env.example复制为.env并填写密钥。模板提供的 示例文件 结构如下(每 Agent 一对 Slack 凭证,变量名与 Agent 对应):

OPENAI_API_KEY=your-api-key # Slack App Configuration (per Agent, replace names with your Agents) # Reverse Agent App SLACK_REVERSE_BOT_TOKEN=xoxb-... SLACK_REVERSE_SIGNING_SECRET=... # Caps Agent App SLACK_CAPS_BOT_TOKEN=xoxb-... SLACK_CAPS_SIGNING_SECRET=...

从源码看,routes.ts中读取的是SLACK_{NAME}_BOT_TOKEN/SLACK_{NAME}_SIGNING_SECRET形式的变量(见 routes.ts#L114-L127)。示例文件里还多出一组SLACK_NUMBERS_*变量,而当前slackApps配置数组并未引用它们,可以视为预留的占位示例;新增 Agent 时按同样的命名规则补一对变量即可。

第 3 步:创建 Slack App

每个 Agent 都要在 api.slack.com/apps 上独立创建一套 App(Create New App → From scratch):

  1. OAuth & Permissions→ 添加 scopes:app_mentions:readchannels:historychat:writeim:history,并把 Bot User OAuth Token 复制进.env
  2. Event Subscriptions→ 开启订阅,Request URL 设置为https://your-server.com/slack/{agentName}/events{agentName}对应routes.ts中配置的name字段,如reversecaps);
  3. 订阅 bot events:app_mentionmessage.im
  4. Agents & AI Apps→ 开启开关;
  5. Basic Information→ 复制 Signing Secret 到.env

四个 scopes 与源码行为一一对应:chat:write用于chat.postMessage/chat.update发送与更新回复;channels:historyim:history用于读取频道和私聊消息;app_mentions:read用于接收 @ 提及事件。

第 4 步:启动开发服务器

ngrok http 4111

拿到公网 URL 后启动 Mastra 开发服务器并访问 http://localhost:4111 试聊:

npm run dev

把 ngrok 得到的公网域名填入第 3 步的 Request URL(例如https://xxxx.ngrok-free.app/slack/reverse/events),Slack 会先发送url_verification挑战请求,模板已内置应答逻辑,验证通过后事件订阅即生效。

工作原理:从 Slack 事件到 Agent 响应

这一节深入模板源码,说明一次 Slack 消息从进入到回复的完整链路。

多 App 路由工厂

Mastra 实例入口 把 agents、workflows、存储与路由一次性装配好:

export const mastra = new Mastra({ agents: { reverseAgent, capsAgent }, workflows: { reverseWorkflow }, storage: new LibSQLStore({ id: 'mastra', url: 'file:./mastra.db', }), server: { apiRoutes: slackRoutes, }, bundler: { externals: ['supports-color'], }, });

slackRoutes来自routes.ts的工厂函数。其核心是一个SlackAppConfig配置数组,每个元素声明一个 Slack App 的路由名、凭证与绑定的 Agent:

interface SlackAppConfig { name: string; // Route path: /slack/{name}/events botToken: string; signingSecret: string; agentName: string; // Mastra 实例中的 agent 名称 } const slackApps: SlackAppConfig[] = [ { name: 'reverse', botToken: process.env.SLACK_REVERSE_BOT_TOKEN!, signingSecret: process.env.SLACK_REVERSE_SIGNING_SECRET!, agentName: 'reverseAgent', }, { name: 'caps', botToken: process.env.SLACK_CAPS_BOT_TOKEN!, signingSecret: process.env.SLACK_CAPS_SIGNING_SECRET!, agentName: 'capsAgent', }, ]; export const slackRoutes = slackApps.map(createSlackEventsRoute);

createSlackEventsRoute(config)为每个 App 注册一个POST /slack/{name}/events路由,handler 内部的处理顺序是(见 routes.ts#L22-L109):

  1. URL 验证挑战:若payload.type === 'url_verification',直接回{ challenge: payload.challenge },这是 Slack 开启事件订阅时的握手;
  2. 签名校验:取出x-slack-signaturex-slack-request-timestamp两个请求头,缺失则 401;用verifySlackRequest()校验,失败同样 401;
  3. 事件过滤:忽略bot_id或带subtype的事件(即机器人自己的消息和消息编辑),避免自激循环;
  4. 文本预处理:只处理app_mentionmessage事件,并用正则/<@[A-Z0-9]+>/g剥掉 @ 机器人的提及片段,得到用户真正输入的文本;
  5. 异步处理:Slack 对事件回调有 3 秒超时,而 LLM 生成往往更久,因此路由立即返回{ ok: true },真正的 Agent 调用放进一个 fire-and-forget 的异步 IIFE 中执行;
  6. 调用streamToSlack:传入 channel、线程时间戳、Agent 名,以及两个关键的记忆标识:
    • resourceId: slack-${teamId}-${userId}—— 同一工作区同一用户共享资源;
    • threadId: slack-${channelId}-${threadTs}—— 记忆按“频道 + 线程”隔离,这正是“线程级会话记忆”的来源。

签名校验:HMAC-SHA256 + 时间窗口

verify.ts 实现了 Slack 官方的请求签名协议:

// Reject old requests (more than 5 minutes old) const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5; if (parseInt(timestamp) < fiveMinutesAgo) return false; const sigBasestring = `v0:${timestamp}:${body}`; const mySignature = 'v0=' + crypto.createHmac('sha256', signingSecret) .update(sigBasestring, 'utf8').digest('hex'); // 长度不一致或类型异常时先短路,再走 timingSafeEqual 恒定时间比较 return crypto.timingSafeEqual(Buffer.from(mySignature), Buffer.from(requestSignature));

三个要点:5 分钟时间戳容差用于防重放;签名基串为v0:{timestamp}:{body};比较前先用长度一致性检查短路,再使用timingSafeEqual防止时序侧信道。

流式渲染:动画状态机 + fullStream

模板最有特色的部分是 streaming.ts 中的streamToSlack(),它把 Agent 的流式输出“翻译”成 Slack 里一段不断刷新的状态消息:

  1. 先发一条“思考中”占位消息chat.postMessage到该线程),记下返回的ts作为后续更新句柄;
  2. 启动动画定时器:每ANIMATION_INTERVAL = 300ms帧号 +1 并调用chat.update刷新文案,文案由 status.ts 的getStatusText()根据当前 chunk 类型生成——工具调用显示“⚙️ Tool Call: Reverse Text...”、工作流步骤显示“📋 Workflow Step Start: Analyze Text...”之类;
  3. 消费 Agent 流agent.stream(message, { memory: { thread: threadId, resource: resourceId } })拿到fullStream,逐 chunk 更新内部状态StreamState
chunk 类型处理逻辑
text-delta累加state.text
tool-call记录工具名,立刻刷新状态消息并停留TOOL_DISPLAY_DELAY(300ms)
tool-output工作流事件会被包在 tool-output 里,拆出内层workflow-step-start时更新步骤名并停留STEP_DISPLAY_DELAY
workflow-execution-start记录工作流名,步骤显示为 “Starting”

状态文案的图标与 spinner 帧来自 constants.ts:SPINNER是 10 帧 braille 字符(⠋ ⠙ ⠹ ...),另有TOOL_ICONSWORKFLOW_ICONS两组图标,三个计时常量均为 300ms——想调整“动画手感”只需改这几个常量; 4.收尾:停止动画定时器,把完整state.text通过retrySlackUpdate()写回占位消息。该函数最多重试 3 次、间隔 500ms,以应对 Slack 的限流;动画期间的chat.update失败则被静默忽略(限流属正常现象); 5.错误路径:任何异常都会把❌ Error: ...写进占位消息(若占位消息尚未创建则补发一条),然后向上抛出,由路由侧记录日志——注释明确说明错误已投递到 Slack,路由只需打日志。

StreamStateStreamingOptions的接口定义见 types.ts,名字美化(kebab-case/camelCase → Title Case)的formatNamesleep工具在 utils.ts。

线程级记忆的落盘

会话记忆由三部分拼起来:

  • Agent 声明memory: new Memory({ options: { lastMessages: 20 } }),即上下文最多保留最近 20 条消息;
  • 调用agent.stream()时传入thread/resource作为记忆键;
  • 实例级LibSQLStore({ url: 'file:./mastra.db' })负责把消息、线程元数据持久化到本地 SQLite 文件。

从源码结构看,resourceIdteamId + userIdthreadIdchannelId + threadTs,意味着同一个人在同一个 Slack 线程里连续对话会共享上下文,换线程或换频道则开启新的记忆线程——这是模板“thread-based conversation memory”承诺的具体实现方式。

两个演示 Agent 的实现

caps-agent:最简单的形态

caps-agent.ts 演示了“单工具 + 记忆”的最小 Agent:

const allCapsTool = createTool({ id: 'all-caps', description: 'Converts text to ALL CAPS', inputSchema: z.object({ text: z.string() }), execute: async ({ text }) => text.toUpperCase(), }); export const capsAgent = new Agent({ id: 'caps-agent', name: 'caps-agent', description: 'Converts text to ALL CAPS', instructions: `You are an enthusiastic caps agent! ...`, model: 'openai/gpt-5-mini', tools: { allCapsTool }, memory: new Memory({ options: { lastMessages: 20 } }), });

reverse-agent:工具 + 工作流双能力

reverse-agent.ts 同时挂载了一个简单工具(reverse-text,逐字符反转)和一条多步工作流,并在instructions中教会模型按用户意图选择:简单反转走工具,要求“fancy”格式时走工作流。

配套的 reverse-workflow.ts 是一个 4 步串行工作流,每步都用 zod 声明输入输出 schema:

  1. analyze-text:统计字符数/单词数;
  2. reverse-text:执行反转;
  3. uppercase-text:转大写;
  4. format-output:用╔═╗边框排版成带统计信息的装饰块。
export const reverseWorkflow = createWorkflow({ id: 'reverse-workflow', inputSchema: z.object({ text: z.string() }), outputSchema: z.object({ result: z.string() }), }) .then(analyzeStep) .then(reverseStep) .then(uppercaseStep) .then(formatStep) .commit();

两个 Agent 的instructions里都有一条值得注意的提示工程细节:“只把用户当前消息里的文本传给工具/工作流,不要带上历史对话”。因为线程记忆会让模型拿到完整上下文,若不显式约束,它可能把历史消息里的文本也一并反转。

把它变成你自己的

模板的 README 给出了四个延伸方向,对应到源码就是:

  • 换掉演示 Agent:新建自己的 Agent 并注册进 index.ts 的agents对象,然后在 routes.ts 的slackApps数组里加一条配置(name、环境变量、agentName),同时在.env补上对应的SLACK_{NAME}_BOT_TOKEN/SLACK_{NAME}_SIGNING_SECRET,最后在 Slack 后台再建一个 App 指向新的/slack/{name}/events路由;
  • 加工具与工作流:像 reverse-agent 那样给 Agent 挂tools/workflows,让 Agent 在 Slack 里触发 API 调用、数据库操作或多步流程——工作流执行时状态消息会自动显示当前步骤名(见 streaming.ts 对workflow-step-start的处理);
  • 定制流式行为:动画帧、图标、300ms 计时常量集中在 constants.ts,状态文案模板在 status.ts,重试策略在 streaming.ts 的retrySlackUpdate
  • 上生产:去掉 ngrok,部署到带 TLS 的公网 URL,并在 Slack 后台把 Request URL 换掉即可。

关于 Mastra 模板

Mastra 模板是仓库内置的“即取即用”参考工程:展示一个可跑的集成模式,克隆下来拆开看、改成自己的即可。它们统一放在本仓库的templates/目录下(本模板位于 templates/template-slack-agent),与 monorepo 内其他包共享依赖版本。想理解更多实现细节,可直接从 templates/template-slack-agent/src/mastra/index.ts 入口顺着 import 一路读下去;模板的协作约定见 CONTRIBUTING.md。

【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询