Slack 多 AI 群聊实战:从一个 Bot 到两个 Agent 自主对话

2026-10-21

Slack 多 AI 群聊实战:从一个 Bot 到两个 Agent 自主对话

我想让两个 AI 在同一个群里对话、互相派活——听起来简单,真动手才发现:市面上的多 Agent 群聊方案要么是企业付费产品(按席位订阅、还要预充值),要么只能做到"一个 bot 被 @ 了答一句话",离"多个 AI 自主协作"还差着关键一层。这篇记录我用 Slack 免费版 + Socket Mode + 免费模型全自建跑通的全过程:先搭一个 @ 就回话的机器人,再让两个独立环境里的 Agent 在同一个频道里多回合自主对话。代码完整可跑,坑一个不落。

为什么做这个

单个 AI 助手用久了,很自然会冒出一个念头:能不能让两个 AI 在同一个群里,一个拆任务、一个执行,我只在旁边看?要实现这件事,载体、接入方式、模型来源三样都得选对。

为什么选 Slack:免费版就够用(消息保留 90 天、最多装 10 个 App);官方 SDK 和文档成熟;最关键的是,Slack 不限制 bot 之间互相发消息、互相 @——多个 AI 同群对话在机制上是通的。

为什么用 Socket Mode 而不是 Webhook:HTTP/Webhook 模式要求你有一个公网 HTTPS 回调地址——公网 IP、域名、证书、内网穿透,一样都不能少。而 Socket Mode 是反过来的:bot 程序主动向 Slack 建一条 WebSocket 长连接(wss,走 443 端口),事件从这条连接里推下来。只要机器能出网就能跑,不需要公网 IP、不需要域名、不需要服务器,本地电脑、家用网络、云 VM 都可以。官方的定位也是如此:开发和本地部署用 Socket Mode,正式生产再考虑切 HTTP。

为什么模型走 API 不走网页版:bot 收到问题后要程序化地拿回复,只能走 API 密钥调用。任何 OpenAI 兼容接口都行,一次接通,换服务商只改地址和模型名。

工具环境

工具 用途 版本/说明
Slack 工作区 群聊载体 免费版即可,本人须是管理员(建 App、装 App、拉人进频道)
Python 跑 bot 程序 3.10+,用官方 slack-bolt + slack-sdk
模型 API 生成回复 任意 OpenAI 兼容接口(OpenRouter、DeepSeek、通义等),示例用免费模型
运行机器 托管 bot 进程 任意能出网的机器,无需公网 IP

怎么做的

第一步:建 App 并配齐权限

在 api.slack.com/apps 点 Create an App → Blank app,起名、选目标工作区。然后配两样东西,缺一个都跑不通:

Bot Token Scopes 三件套:

  • app_mentions:read — 接收 @ 提及
  • chat:write — 发送消息
  • channels:history — 读已加入频道的消息

订阅 Bot 事件:app_mention、message.channels。

最后点 Install 把 App 装进工作区。

第二步:开 Socket Mode,拿两个 token

这套系统有两套密钥,用途完全不同,别搞混:

密钥 前缀 用途 从哪拿
Bot User OAuth Token xoxb- 调 Slack Web API:发消息、读历史 App 设置页 OAuth & Permissions
App-Level Token xapp- 建 Socket Mode 连接:收事件 App 设置页 Basic Information → App-Level Tokens,生成时带 connections:write 权限

顺序是先在 Socket Mode 页打开 Enable Socket Mode 开关,再去生成 App-Level Token。两个 token 只存本地 .env 文件(权限设 600),永不进代码仓库。

第三步:把 bot 拉进频道

最容易漏、漏了必翻车的一步:App 装进工作区 ≠ 进了频道。不在频道里的 bot 收不到事件、发不进消息。在频道里发一句 /invite @机器人名 即可;或者先 @ 它一下,Slack 会弹出"它不在这个频道中"的提示,点里面的"添加他们"也行。

第四步:写 bot 程序

官方推荐的 Bolt 框架 + Socket Mode,接模型用 API 直调。这一份就是完整可跑的版本:

import os
import re
import requests
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

MODEL = os.environ.get("BOT_MODEL", "minimax/minimax-m3.1-flash:free")
API_BASE = os.environ.get("LLM_API_BASE", "https://openrouter.ai/api/v1")
API_KEY = [SECURITY_DATA]"LLM_API_KEY"]


def ask_llm(question: str) -> str:
    r = requests.post(
        f"{API_BASE}/chat/completions",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "model": MODEL,
            "messages": [{"role": "user", "content": question}],
            "max_tokens": 600,
        },
        timeout=90,
    )
    r.raise_for_status()
    content = r.json()["choices"][0]["message"]["content"]
    return (content or "(模型没返回内容)").strip()[:3000]  # 截断防刷屏


app = App(token=os.environ["SLACK_BOT_TOKEN"])


@app.event("app_mention")
def on_mention(event, say):
    if event.get("bot_id"):  # v1 先忽略所有 bot 消息防回路(互聊放行见进阶节)
        return
    question = re.sub(r"<@[A-Z0-9]+>\s*", "", event.get("text", "")).strip()
    reply = ask_llm(question) if question else "你好!@我 并带上问题,我来回答。"
    say(text=reply, thread_ts=event["ts"])  # 回进帖子串,不刷屏


@app.event("message")
def on_message():
    pass  # 只响应 @ 提及,普通消息不处理


if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

.env 模板(值换成你自己的):

SLACK_BOT_TOKEN=xoxb-你的-bot-token
SLACK_APP_TOKEN=xapp-你的-app-token
LLM_API_KEY=[SECURITY_DATA]
BOT_MODEL=minimax/minimax-m3.1-flash:free

装依赖并启动:

python3 -m venv .venv && ./.venv/bin/pip install slack-bolt slack-sdk requests
set -a; source .env; set +a
./.venv/bin/python -u bot.py

第五步:四步验证清单

别急着在频道里喊机器人,先把链路拆开逐段验:

  1. token 有效:curl https://slack.com/api/auth.test -H "Authorization: Bearer <xoxb-token>" 返回 {"ok":true,...}
  2. 建连成功:启动日志出现 ⚡️ Bolt app is running! 和 Starting to receive messages from a new connection
  3. 发得出:用 chat.postMessage 往频道发一条测试消息,返回 ok:true(若报 not_in_channel,回第三步)
  4. 收得到、回得了:真人在频道 @ 机器人提一个问题,帖子串里出现模型回复

四步全过,才算跑通。

踩过的坑

坑 1:not_in_channel ——bot 没进频道

现象:调 chat.postMessage 发消息,返回 not_in_channel;程序跑着也收不到任何事件。

原因:App 装进工作区,不等于进了频道。不在频道里的 bot,对频道是"隐身"的。

解法:按第三步把 bot 拉进频道。注意这不是 token 问题,别去重装 App、重拿 token,方向错了越折腾越乱。

坑 2:找错了 slackbot

现象:在手机上跟 "slackbot" 私聊提问,对方回一句 "Sorry, you can't perform this action!",完全不搭理问题。

原因:slackbot 是 Slack 内置的官方机器人,不是我自建的 App,两者毫无关系。而且第一版就没配私聊事件,本来也不会回任何人的私聊。

解法:去频道里找自建 App——侧边栏的「应用」区能看到它,@ 它提问。

坑 3:免费模型 slug 下架,接口 404

现象:昨天还跑得好好的免费模型,今天调用直接返回 404。

原因:免费模型的 slug(模型标识)轮换频繁,服务商说下架就下架,代码里写死的模型名随时会失效。

解法:别把模型名当常量。先拉一遍服务商的模型列表接口(如 OpenRouter 的 GET /api/v1/models),筛出价格为 0 的在架免费清单再选;程序里再配一个自动路由的兜底模型,主力挂了能顶上。

坑 4:推理模型返回空内容

现象:接口返回成功,但拿到的回复内容是空的(None)。我对着日志查了半天自己的代码。

原因:代码没错。推理型模型会先把输出额度耗在"思考过程"上,max_tokens 给得不够时,正文还没生成就截断了,只剩一个空壳。

解法:群聊场景换非推理模型,回复快还稳定;非要用的话,在请求参数里关掉推理输出,或者把 max_tokens 给足。

坑 5:帖子串里追问不带 @,bot 不回

现象:第一次提问答得好好的,顺着帖子串继续追问,bot 装死。

原因:第一版只监听 app_mention 事件,帖子串里的普通消息不在触发范围内——这是设计如此,不是 bug。

解法:每次提问都带 @。想实现免 @ 追问,得升级成帖子串追踪模式:订阅 message 事件后按帖子维护会话状态,那是另一个工程量,别指望改一行配置就行。

坑 6:设置页打开一片空白

现象:api.slack.com 的 Socket Mode、OAuth 等设置页初次打开空白一片,怀疑自己没权限。

原因:单页应用渲染慢而已。

解法:等十几秒,或者刷新一次就出来了。别在空白期乱点、乱改设置。

坑 7:bot 一启动就崩在 auth.test 超时

现象:程序启动时卡在鉴权请求上读超时崩溃,看起来像 token 写错了。

原因:是运行环境的网络请求被卡住,跟 token 和代码都无关。

解法:先用验证清单第 1 步——curl 单测 token 和网络。网络通了重启程序就好。排查顺序反了,会浪费大量时间在改代码上。

进阶:让两个 Agent 自己对话

双 Agent 自主对话流程:A 侧发言、监控轮询、唤醒 B 侧、B 侧串内回复,多轮往返,任一停止条件触发即停

先分清:App 后端是裸模型,不是 Agent 本体

这是整篇最关键的一个认知。自建 App 的后端只能调裸模型 API——无记忆、无工具、答完就忘。哪怕你给它接的模型,和某个 Agent 产品用的是同款模型,它也只是裸模型,不是那个带记忆、能调工具干活的 Agent 本体。

真正带记忆和工具的 Agent 想进群,目前只有一条路:产品方提供的官方 Slack 连接器(走 OAuth 授权)。这条路的身份和代价也完全不同:Agent 以授权账号本人的身份发言;而且连接器是拉取式的,没有实时事件推送,所谓"近实时"只能靠定时轮询频道新消息实现。

三种接入模式对比

模式 身份 能力 实时性 成本
A 自建 App 每个 App 一个独立 bot 身份 裸模型,无记忆/工具 Socket Mode 实时 免费(免费模型)
B1 连接器 · 单账号多环境 多个环境共用同一个账号名 完整 Agent(记忆+工具) 轮询,分钟级 免费
B2 连接器 · 多账号 每个环境一个账号,天然分开 完整 Agent(记忆+工具) 轮询,分钟级 免费

选型口径:"多个 AI 同群 + @ 谁谁干活"有两个硬指标——身份分得开、真能干活。B2 两样都满足,最稳;B1 靠每次发言自报家门区分身份,很脆弱,只能当备选;A 胜在实时,但干不了活,只适合闲聊和演示。我的最终形态是 A 和 B2 一起上:App bot 负责实时响应和消息检测,真干活的对话走连接器身份。

bot 互聊:机制不难,防循环是自己的事

Slack 不拦 bot 互聊:bot 发出的消息会产生 message 事件,同一频道里的其他 App 能收到;bot 在频道里 @ 另一个 App,也会正常触发对方的 app_mention。所以实现只剩三件事:认得出对方、接得住话、停得下来。其中最重要的是第三件——防循环是开发者自己的责任,平台不管。

防循环三件套:

  1. 按 bot_id 识别发送方:只过滤自己、放行对方。最常见的写错法是"凡是 bot 发的发言一律忽略"——那样对方永远听不见你,互聊根本起不来。对方的 bot_id 可以从它发过的任一消息的 payload 里读到,也可以用 bots.info 查。
  2. 轮次上限:比如聊满 10 轮自动停。两个 bot 无限对喷,烧掉的是你的模型调用额度。
  3. 最短回复间隔:每轮之间强制留几秒间隔,防刷屏,也防触发平台限流。

另外留一个人工叫停的口子:对话中出现约定的停止词,立即收尾。

双边监控:两个 Agent 环境怎么对上话

连接器模式下两边都没有事件推送,我实测落地的组合是两种架构混搭:

  • 环境 A(有 Socket Mode 落地条件)走"实时检测 + 轮询唤醒":前面那个 bot 程序顺带当检测器——目标账号在频道的发言实时写入一个本地队列;另有一个监控任务每 30 秒扫一次队列,有新消息就唤醒本环境的 Agent,Agent 读完上下文再决定回不回、回进原来的帖子串。延迟在 30 秒级。
  • 环境 B(没有实时通道)走"定时拉取 + 水位线":定时任务每 5 分钟拉一次频道新消息,只取上次处理到的时间戳之后的部分,筛出 @ 自己的,在帖子串里回复。

回合延迟由慢的一侧决定:这个组合一回合大约 5 分钟。不对称没关系,能接受就足够好,没必要强求两边一致。

两边能对上话,靠的是一套共同遵守的同步协议:

  • 按消息时间戳(ts)去重,每条消息只处理一次,进程重启也不会重复回复
  • 绝不回复自己发的消息
  • 只回 @ 自己的消息;帖子串里的对话回进串里,不另起新楼
  • 收尾口令:对方说「停止」或「结束」即结束本轮对话,不再续话;另设一个全局暂停口令,让两边都停下监控
  • 累计上限:轮询唤醒的那一侧设了唤醒次数上限,到顶自动停,防无人值守时空转;另一侧当时靠收尾口令停,还没有等价的计数上限——这是我复盘时认为该补齐的,两边都该有硬上限,不能只靠默契

长期维护

bot 跑通只是开始。长期运行我栽的跟头是:把 bot 挂在临时会话的后台进程上,运行环境一重启就被杀,一天之内死了三次。要无人值守,得解决三件事:

  1. 进程托管:用 systemd 这类系统服务托管 bot,配 Restart=always、RestartSec=5——进程被杀后 5 秒内自动拉起并重连 Socket Mode。不要依赖任何会话级的后台进程,会话结束、环境重启,它就没了。
  2. 守护自查:再加一个每 30 分钟跑一次的守护脚本:服务定义丢了自动重装、服务停了自动拉起,正常时保持静默,只在修复或失败时才报警。服务定义和安装脚本本身要放在持久化目录里留一份模板,丢了能一键恢复。这样最坏的掉线窗口就是守护间隔——30 分钟。
  3. 环境变量一致性:这是最隐蔽的坑。系统服务的运行环境和你交互 shell 里的环境不是一回事:shell 里跑得通的程序,换成服务启动可能因为少了某些环境变量(最典型的是网络代理变量)直接连不上 Slack,报一堆看不懂的 SSL 错误。解法是把服务需要的变量显式写进一个专属的 EnvironmentFile 文件让服务引用,别假设"在这台机器上能跑,服务里就一定行"。

复盘

做对的几件事:

  • 先想清楚要"闲聊"还是要"干活",再选接入模式。App 模式和连接器模式不是谁替代谁,而是分工:实时响应靠前者,真干活靠后者。选型想明白了,后面少走很多回头路。
  • 验证清单前置。token、建连、发送、接收四步拆开逐段验,任何一步挂了都知道往哪个方向查,而不是对着"bot 不回"这一个现象瞎猜——坑 1 和坑 7 都是靠这个顺序省下来的时间。
  • 防循环在写第一行互聊代码之前就定好。bot 对喷的速度比人快得多,事后补刹车,代价是已经烧掉的额度。

可迁移的经验:凡是"两个自动化系统要互相调用"的场景——不止群聊 bot——去重、只认明确触发、轮次上限、停止口令,这四件套都通用。

总结

  1. Socket Mode 把做聊天机器人的门槛降到了零基建:机器能出网就行,不需要公网 IP、域名和服务器。
  2. App 后端是裸模型,不是 Agent 本体;先想清要闲聊还是要干活,再选接入模式。
  3. 免费档完全够用:免费工作区 + Socket Mode + 免费模型,就能跑通完整的多 AI 群聊,成本为零。
  4. bot 互聊的难点不在"聊",在"停":bot_id 识别、轮次上限、消息去重、停止口令,一个不能少。
  5. 长期运行靠托管和守护,不靠进程运气:自动拉起 + 定时自查,才能真正无人值守。

以上,全文完。

最后认识一下,我是 Liyu,一名 AI 实践爱好者,会不定期记录我的学习过程,期待你的关注和分享。

← 返回文章列表