Slack 是怎么接入 Claude Agent 的
约 1381 字大约 5 分钟
ClaudeSlackBolt.jsAgent
2026-07-13
把 AI agent 接进 Slack,很多人以为难点在 Claude 那边。其实反过来:Slack 侧的"agent 体验"是 Slack 自己的 Assistant API 提供的,跟 Claude 无关;Claude 只负责"给下一段回复"。 两块是彻底解耦的。
本文以 Slack 官方示例仓库 slack-samples/bolt-js-starter-agent 为基础,讲清楚这条链路。它是本系列 LangChain / LangGraph / CrewAI 是怎么调用 Claude 的 的延续——核心还是那个问题:agent 循环归谁管。
整体链路
Slack 事件(用户在 Assistant 面板发消息)
└─ Bolt.js 的 Assistant 中间件 ← Slack 侧:UI、线程、状态气泡、快捷提示
└─ 调用 Claude(Messages API 或 Agent SDK) ← Claude 侧:只负责生成回复
└─ (可选)Slack MCP Server ← 让 Claude 反过来读写 Slack
└─ chat.update 逐步覆盖回帖 ← Slack 侧:模拟打字机(有限流)记住一句话:换成 OpenAI,Slack 那半边代码几乎不动。 所以要分清哪些是 Slack 的能力、哪些是 Claude 的能力。
一、Slack 侧:Bolt.js 的 Assistant 类
Slack 不需要你监听裸事件,而是提供了一个封装好的 Assistant 中间件,帮你处理三类事件:
import { App, Assistant } from "@slack/bolt";
const assistant = new Assistant({
// 用户点开 Assistant 面板 / 开启 DM → assistant_thread_started
threadStarted: async ({ say, setSuggestedPrompts, setTitle }) => {
await say("👋 我是 AI 助手");
await setSuggestedPrompts({
prompts: [{ title: "查订单", message: "我想查订单" }],
});
},
// 用户发消息 → 这里才调用 Claude
userMessage: async ({ message, say, setStatus, getThreadContext }) => {
await setStatus("is thinking..."); // 显示"正在思考"气泡
const reply = await callClaude(message.text); // ← 见第二节
await say(reply);
},
// 用户切换频道 → assistant_thread_context_changed
threadContextChanged: async ({ saveThreadContext }) => {
await saveThreadContext();
},
});
app.assistant(assistant);这里的 setStatus / setTitle / setSuggestedPrompts 都是 Slack 的 assistant.threads.* API,渲染成 Slack UI 里的"正在思考"、线程标题、快捷提示按钮。
| 事件 | 触发时机 | 常用能力 |
|---|---|---|
assistant_thread_started | 用户点开 Assistant 面板 / 开启 DM | setTitle、setSuggestedPrompts |
message(assistant thread 内) | 用户发消息 | setStatus、say → 调 Claude |
assistant_thread_context_changed | 用户在频道间切换 | saveThreadContext |
提示
这一整层是 Slack 的能力,与用哪个大模型无关。需要 assistant:write / chat:write scope。
二、Claude 侧:又是"agent 循环归谁管"
官方仓库同时提供了两种实现,正好对应本系列的核心分野。
方式 A —— 普通 Messages API(自己管历史)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// 每个线程一份历史:Map<channel-thread, messages[]>
async function callClaude(text: string, history: Anthropic.MessageParam[]) {
history.push({ role: "user", content: text });
const res = await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 1024,
messages: history, // ← 每次把整个线程历史重发(Messages API 无状态)
});
const reply = res.content
.filter((c) => c.type === "text")
.map((c) => (c as Anthropic.TextBlock).text)
.join("");
history.push({ role: "assistant", content: reply });
return reply;
}对话历史你自己存,工具循环(tool_use → 执行 → 回填)也在你的代码里。
方式 B —— Claude Agent SDK(循环交给 SDK)
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const event of query({
prompt: message.text,
options: {
model: "claude-opus-4-8",
// resume: 上次存下的 sessionId —— 复用同一线程的会话
},
})) {
// 迭代 SDK 吐出的事件(assistant 文本 / tool_use / result ...)
}你只需要拿 "线程 = channel_id + thread_ts" 当 key,存一个 session id;历史和工具循环由 SDK 维护。
注意
query() 的确切字段名(session 恢复参数、事件 type 枚举)以 Agent SDK 官方文档 为准——结构对,字段可能有出入,别照抄。
两种方式对照
| 维度 | Messages API | Claude Agent SDK |
|---|---|---|
| agent 循环 | 你的应用层(手动) | SDK 内部(自动) |
| 会话历史 | 自己用 Map 维护 | 存一个 sessionId 即可 |
| 工具调用 | 手动声明 + 处理 tool_use | 挂上 MCP / 工具自动跑 |
| 适用 | 简单问答 | 多轮、多工具的复杂 agent |
三、流式"打字机"回帖
Slack 消息不能像 SSE 一样逐 token 推。通用做法是先发一条占位消息,再用 chat.update 反复覆盖:
const msg = await client.chat.postMessage({ channel, thread_ts, text: "⏳" });
let buf = "";
for await (const chunk of claudeStream) {
buf += chunk;
await client.chat.update({ channel, ts: msg.ts, text: buf + " ▌" });
await delay(400); // ← 关键:Slack 有限流,别每个 token 都 update
}
await client.chat.update({ channel, ts: msg.ts, text: buf }); // 去掉光标Bolt 较新版本提供了 sayStream() 帮你追加内容,但节流这件事逃不掉——这是 Slack 的限流(约 Tier 3,每频道每分钟几十次更新),不是 Claude 的限制。
四、工具:Slack 自己也有 MCP Server
官方示例接的是 Slack MCP Server(搜消息、读频道、发消息、管 Canvas)。完整链路是:
Claude(决定调工具)→ Slack MCP Server(读/写 Slack)→ 结果回给 Claude → 再回帖- Agent SDK 版本:直接把这个 MCP server 挂上去,工具循环自动跑。
- Messages API 版本:自己在
tools=[...]里声明工具,并处理返回的tool_use块。
这样 agent 不仅能回复,还能主动"在工作区里搜一下历史消息""把结论写进 Canvas"。
一句话总结
Slack 接 Claude = Slack 的 Assistant API 负责 UI 和线程体验(状态气泡、快捷提示,流式更新靠
chat.update节流)+ Claude 负责生成回复。而"用普通 Messages API 还是 Agent SDK",又回到那个核心区别:对话历史和工具循环,是你自己管,还是交给 SDK 管。
理解这条分界,再看任何"把 XX 大模型接进 IM"的方案,都能一眼看出哪半边是平台能力、哪半边是模型能力。
延伸阅读
- LangChain / LangGraph / CrewAI 是怎么调用 Claude 的 —— 同一主线:agent 循环归谁管
- slack-samples/bolt-js-starter-agent —— 官方完整示例(含 Agent SDK 与 Messages API 两版)
- Slack Bolt.js Assistant 文档
- Claude Agent SDK (TypeScript)
