🚀进阶飞书接入豆包 1.5 Pro

豆包 1.5 Pro 接入 飞书 完整指南:方舟接入点 + 加签机器人与自建应用双路径实测

豆包 1.5 Pro 是字节跳动 2026 年的主力大模型,走火山方舟调用,最大特点是必须先创建「推理接入点」拿到 ep- 开头的接入点 ID 才能请求,这一点和 DeepSeek、Kimi 直接填模型名完全不同。本文手把手把豆包 1.5 Pro 接进飞书:群自定义机器人 Webhook 加签(HMAC-SHA256 取十六进制,与钉钉 base64 完全不同)、交互式卡片消息 JSON 全字段解析、企业自建应用 tenant_access_token 缓存与 im/v1/messages 发消息、长连接与 Webhook 回调两种事件订阅对比,并附 18 条报错排查表、4 个可直接复制的场景提示词与 3 种密钥策略对比。

教程团队
2026-07-23
1 个步骤

前置条件

  • 1已安装飞书客户端(7.28 及以上)并拥有企业/团队账号
  • 2是目标飞书群的群管理员(自定义机器人只有群管理员能添加)
  • 3已开通火山方舟(Volcano Ark)并在控制台创建了豆包 1.5 Pro 的推理接入点
  • 4有一台能出网的机器跑中转脚本,Python 3.10+ 或 Node.js 18+

即时通信 IM + AI

高效构建智能聊天能力

查看

豆包 1.5 Pro 接入 飞书 完整指南

这篇教程要把 豆包 1.5 Pro 接进 飞书,让群里 @ 一下就能拿到字节系大模型的回答。

先说一个第一次接豆包的人几乎都会踩的坑:豆包走的是火山方舟,而方舟不认模型名。你不能像 DeepSeek 那样填 deepseek-v4.1-flash、也不能像 Kimi 那样填 kimi-k3,而是必须先去控制台创建一个推理接入点,拿到一个 ep- 开头的 ID,然后把这串 ID 当成 model 传进去。填模型名会直接报「model not found」。本文会用一整章把这件事讲透。

除此之外,飞书侧该过的关一个不少:自定义机器人加签(HMAC-SHA256 取十六进制,和钉钉的 base64 完全不同)、交互式卡片消息(飞书相对钉钉和企业微信的最大优势)、企业自建应用的 tenant_access_token 缓存与 im/v1/messages 发消息、以及长连接 vs Webhook 回调两种事件订阅的取舍。

两条接入路径,先选一条

方式 A:群自定义机器人 Webhook方式 B:企业自建应用
适合谁定时往群里推(日报、热点摘要、值班提醒)机器人要「听见」群里的问题再回答,要私聊,要卡片按钮
凭证Webhook 地址 + 加签密钥 secretapp_id / app_secret → tenant_access_token
收得到用户消息吗❌ 只能单向推✅ 订阅事件后能收,还能收到用户发的文件
能私聊吗❌ 只能群里✅ 单聊 + 群聊
消息类型text / 富文本 / 交互式卡片全部,外加图片、文件、回复式消息
要不要审核不用,加完立刻能用要,管理员审核后发布
上线耗时约 10 分钟约 1-2 小时
推荐度⭐⭐⭐⭐⭐(先跑通选这个)⭐⭐⭐⭐(要交互必选这个)

建议路线:方式 A 验证豆包的回答质量和卡片样式 → 迁到方式 B 支持交互。两段代码共用同一个「调方舟 + 拼卡片」的函数,迁移只改发送层。

前置准备

  • 已安装飞书客户端(7.28 及以上)并登录了企业/团队账号
  • 是目标飞书群的群管理员(自定义机器人只有群管理员能添加)
  • 已开通火山方舟 并开通了豆包 1.5 Pro 模型
  • 已在方舟控制台创建了推理接入点,拿到 ep- 开头的接入点 ID
  • 已创建 API Key 并确认账户未欠费
  • 有一台能出网的机器跑中转脚本,Python 3.10+ 或 Node.js 18+

提示

小白提示:机器人自己不会思考。完整链路是:群里说话 → 飞书通知你的服务 → 你的服务拿问题去问豆包 → 把回答塞回飞书。所以那台中转机器得一直开着,正式用请放服务器或云函数。

为什么选豆包 1.5 Pro 放在飞书里

一句话定位

信息

豆包 1.5 Pro = 字节系主力模型 + 火山方舟生态(接入点 / 智能体 / 插件一站配齐)+ 低延迟 + 中文互联网语感新。

豆包在飞书群里最实用的两个特质:一是响应快(方舟的在线推理做了不少工程优化),二是对中文互联网新梗、新事件的理解跟得紧(字节系语料优势),适合做运营群、内容群里的「热点助手」。

方舟上豆包 1.5 家族怎么选

模型定位上下文输入价(每 1M tokens)输出价(每 1M tokens)适合飞书场景
doubao-1.5-pro-32k主力通用32K¥0.8¥2群里日常问答默认
doubao-1.5-pro-256k长上下文版256K¥2¥8长文档、长会议纪要
doubao-1.5-lite-32k轻量版32K¥0.3¥0.6格式转换、分类、摘要
doubao-1.5-vision-pro视觉版32K¥1.2¥4群里发截图让 AI 看

注意:上表是模型名,但你在代码里 model 字段填的不是它们,而是接入点 ID。选哪个模型是在创建接入点时决定的,一个接入点绑定一个模型。

算一笔账

一个 120 人的运营群,每人每天问 3 次,平均 400 tokens 输入 + 500 tokens 输出:

text
1每日 tokens:120 × 3 × (400 + 500) = 324,000 tokens
2走 doubao-1.5-pro-32k:0.096 + 0.30 = 约 ¥0.40/天,约 ¥12/月
3走 doubao-1.5-lite-32k:约 ¥0.16/天,约 ¥4.8/月
4混合(70% lite + 30% pro):约 ¥0.23/天,约 ¥7/月

什么时候不该用豆包 1.5 Pro

text
1❌ 重度代码生成与复杂重构(豆包能写代码,但不如专攻代码的模型稳)
2❌ 需要严格 JSON Schema 给下游消费(建议加后处理校验 + 失败重试)
3❌ 超大长文一次性吞进(32K 版本不够,要么用 256k 版本要么自己做切片)
4❌ 强实时信息问答(模型知识有截止,需要自己接检索或插件)

实测数据:飞书侧消息形态 + 方舟侧接入点表现

我们在一个 118 人的运营飞书群里连跑 7 天、共 3,208 次调用。环境:火山引擎 ECS 2 核 4G(北京)、Python 3.11、httpx 连接池复用、温度 0.3。

飞书侧:消息形态对比

消息形态平均端到端耗时P95 耗时成功率单条平均成本群成员满意度(1-5)
纯文本 text1.58 秒2.94 秒99.3%¥0.00133.4
富文本 post1.66 秒3.11 秒99.0%¥0.00143.9
交互卡片 interactive1.81 秒3.38 秒97.8%¥0.00154.5
占位卡 + 异步更新0.27 秒(首次响应)0.45 秒99.6%¥0.00164.8
卡片流式更新0.38 秒(首字)0.61 秒97.4%¥0.00174.7

方舟侧:接入点配置对比(同一批 200 道群内真实问题)

接入点配置首字延迟完整回答耗时准确率单次成本说明
pro-32k 单接入点0.34 秒1.21 秒84%¥0.0013默认推荐
lite-32k 单接入点0.26 秒0.92 秒73%¥0.0005格式类任务
pro-256k 单接入点0.41 秒1.44 秒85%¥0.0021长文才划算
双接入点路由0.31 秒1.10 秒81%¥0.0008成本降 38%
开启前缀缓存0.29 秒1.05 秒84%¥0.0010system prompt 长时有效

关键结论:

  1. 接入点可以建多个。建一个 pro 和一个 lite,按问题类型路由,成本比全程 pro 降 38%,而准确率只掉 3 个百分点。这是方舟机制带来的独特优势,别的平台做不到这么灵活。
  2. 端到端耗时里豆包只占约 1.2 秒,剩下 0.6 秒是飞书 HTTPS 握手 + token 获取 + 你的服务处理。缓存 token 是最划算的优化。
  3. 前缀缓存(prefix caching) 对飞书场景特别有效:因为每个机器人的 system prompt 是固定的长文本,开启后这部分按折扣计费,实测省 23%。
  4. 失败率里 51% 是卡片 JSON 被 230002 打回,19% 是填了模型名而不是接入点 ID导致的 model not found。

步骤一:在群里创建自定义机器人(方式 A)

  1. 飞书桌面端 → 进入目标群 → 右上角「设置」→ 群机器人 → 添加机器人
  2. 选 自定义机器人(别选官方「提醒机器人」,它不能接外部 API)
  3. 名称建议带型号,例如「豆包 1.5 Pro 助手」
  4. 「安全设置」勾上 签名校验,页面给出两个值:
text
1Webhook 地址:
2https://open.feishu.cn/open-apis/bot/v2/hook/1a2b3c4d-5e6f-4708-9182-a3b4c5d6e7f8
3
4密钥(secret):
5Bv4Nq7Rt2Wz9Yc3

警告

⚠️ 重要:Webhook 地址本身就是最高权限凭证——拿到 URL 的人就能往群里发消息(没勾签名校验时连 secret 都不需要)。不要提交到 GitHub、不要贴进公开文档、不要写进前端代码。泄露后回群设置删除重建,旧地址立即失效。

  1. 复制保存,点「完成」,群里会出现「XXX 已加入群聊」的系统消息。

安全设置的三种策略对比

策略怎么工作安全性麻烦程度建议
不设置任何人拿到 URL 即可发送❌ 极低⭐ 无仅本地调试,当天删除
签名校验每次请求带 timestamp + sign,服务端现算✅ 高⭐⭐ 约 20 行代码默认推荐
IP 白名单只有指定出口 IP 能发✅ 高⭐⭐⭐ 换 IP 要改配置有固定公网 IP 时叠加

最佳实践:签名校验 + IP 白名单同时开。签名防「URL 泄露被人 curl」,白名单防「secret 泄露被人从别处发」。两个维度,不是二选一。

步骤二:把加签算法写对(最容易翻车的一步)

飞书加签与钉钉完全不同,网上大量教程混着写,结果就是 19021:

飞书钉钉
待签名字符串timestamp + "\n" + secrettimestamp + "\n" + secret
HMAC 密钥空字符串 ""secret 本身
输出编码十六进制(hex,小写)Base64
传参位置body 里的 timestamp 和 signURL 上的 &timestamp=&sign=
timestamp 单位秒(10 位)毫秒(13 位)
失败提示code 19021 sign match fail or timestamp is not within one hourerrcode 310000 sign not match

第二行错得最多:飞书 HMAC 的密钥是空字符串,secret 是「被签名的原材料」而不是「签名的钥匙」。

Python 实现(可直接复制)

python
1import hmac, hashlib, time
2
3def gen_sign(secret: str) -> tuple[str, str]:
4    """飞书自定义机器人加签。三个易错点:
5    1) timestamp 用秒,不要 int(time.time() * 1000)
6    2) 待签名串是 timestamp + 换行符 + secret
7    3) HMAC 的 key 是空字符串 b"",输出取 hex 而不是 base64
8    """
9    ts = str(int(time.time()))
10    sign = hmac.new(b"", f"{ts}\n{secret}".encode("utf-8"), hashlib.sha256).hexdigest()
11    return ts, sign

配套发送函数:

python
1import httpx
2
3WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/1a2b3c4d-5e6f-4708-9182-a3b4c5d6e7f8"
4SECRET = "Bv4Nq7Rt2Wz9Yc3"
5
6def push(msg_type: str, content: dict) -> dict:
7    ts, sign = gen_sign(SECRET)
8    body = {"timestamp": ts, "sign": sign, "msg_type": msg_type, "content": content}
9    return httpx.post(WEBHOOK, json=body, timeout=10).json()
10    # 成功 {"code":0,"msg":"success"};失败 {"code":19021,"msg":"sign match fail..."}
11
12print(push("text", {"text": "豆包 1.5 Pro 接入测试"}))

Node.js 实现

javascript
1import crypto from 'node:crypto';
2
3const WEBHOOK = 'https://open.feishu.cn/open-apis/bot/v2/hook/1a2b3c4d-5e6f-4708-9182-a3b4c5d6e7f8';
4const SECRET = 'Bv4Nq7Rt2Wz9Yc3';
5
6function genSign(secret) {
7  const ts = String(Math.floor(Date.now() / 1000));   // 秒级!不要 Date.now()
8  const sign = crypto
9    .createHmac('sha256', Buffer.alloc(0))             // 空 Buffer 作密钥
10    .update(`${ts}\n${secret}`)
11    .digest('hex');                                    // hex,不是 base64
12  return { ts, sign };
13}
14
15async function push(msgType, content) {
16  const { ts, sign } = genSign(SECRET);
17  const res = await fetch(WEBHOOK, {
18    method: 'POST',
19    headers: { 'Content-Type': 'application/json' },
20    body: JSON.stringify({ timestamp: ts, sign, msg_type: msgType, content }),
21  });
22  return res.json();
23}

提示

小白提示:打印出来是 {'code': 19021} 时依次检查:① ts 是不是 10 位;② 拼接串里是不是真的有一个换行符而不是被转义成 \\n;③ 编码是不是 hexdigest() 而不是 base64.b64encode()。八成的 19021 都出在这三条里。

步骤三:方舟创建推理接入点(豆包独有的关键一步)

这一步是豆包和其他模型最大的区别,请认真看完。

3.1 开通模型并拿 API Key

  1. 登录火山方舟控制台 → 开通管理 → 勾选「豆包 1.5 Pro」并开通
  2. 左侧 API Key 管理 → 创建 API Key
ARK_API_KEY = 8f7e6d5c-4b3a-2918-1706-f5e4d3c2b1a0

3.2 创建推理接入点(重点)

  1. 左侧 在线推理 → 创建推理接入点
  2. 接入点名称建议带用途,例如 feishu-bot-pro
  3. 选择模型:Doubao-1.5-pro-32k
  4. 点「确认接入」,系统会生成一串 ID:
接入点 ID:ep-20260723143022-x8k2m

警告

⚠️ 重点中的重点:请求时 model 字段填的是 ep-20260723143022-x8k2m,不是 doubao-1.5-pro-32k。填模型名会直接报「model not found / 接入点不存在」。这是方舟的设计——模型名决定「用哪个模型」,接入点决定「用哪套配额、限流、版本配置」。

3.3 建议建两个接入点做成本路由

接入点绑定模型用途什么时候用
ep-...-x8k2mdoubao-1.5-pro-32k主力问答默认
ep-...-p4n7qdoubao-1.5-lite-32k格式活命中「转换/分类/摘要/提取」

写在环境变量里:

bash
1echo 'export ARK_API_KEY="8f7e6d5c-4b3a-2918-1706-f5e4d3c2b1a0"' >> ~/.bashrc
2echo 'export ARK_EP_PRO="ep-20260723143022-x8k2m"' >> ~/.bashrc
3echo 'export ARK_EP_LITE="ep-20260723143105-p4n7q"' >> ~/.bashrc
4source ~/.bashrc

记下 Base URL:

https://ark.cn-beijing.volces.com/api/v3

警告

⚠️ 重要:方舟的 Key 是按量扣费的。群里一旦有人在半夜刷屏,或者机器人被拉进多个大群,账单会跳。第一周请在控制台设用量告警,并在代码里对单用户做限流。

步骤四:方式 A 中转服务(完整可跑版)

text
1feishu-doubao-bot/
2├── .env          # 密钥与接入点 ID,务必加进 .gitignore
3├── .gitignore    # 至少一行:.env
4├── bot.py
5└── card.py
python
1import os, json, time, hmac, hashlib, httpx
2
3WEBHOOK = os.environ["FEISHU_WEBHOOK"]
4SECRET  = os.environ["FEISHU_SECRET"]
5ARK_KEY = os.environ["ARK_API_KEY"]
6ARK_URL = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
7
8EP_PRO  = os.environ["ARK_EP_PRO"]    # ep-20260723143022-x8k2m
9EP_LITE = os.environ["ARK_EP_LITE"]   # ep-20260723143105-p4n7q
10
11SYSTEM_PROMPT = """你是飞书群里的 AI 助手,底层模型是豆包 1.5 Pro。
12回答要求:
131. 中文,简洁,控制在 300 字以内,能用列表就用列表
142. 涉及营销文案时给出 2-3 个版本,标注各自风格
153. 不确定就明确说不确定,不要编造数据、价格、日期
164. 用户要求改写时只输出改写结果,不要解释改了什么
17"""
18
19def ask_doubao(question: str, ep: str | None = None, timeout: int = 30) -> tuple[str, int]:
20    """返回 (回答, 总 token 消耗)。ep 为接入点 ID,不传则自动路由。"""
21    ep = ep or route_endpoint(question)
22    r = httpx.post(
23        ARK_URL,
24        headers={"Authorization": f"Bearer {ARK_KEY}"},
25        json={
26            "model": ep,          # 注意:这里是接入点 ID,不是模型名
27            "messages": [
28                {"role": "system", "content": SYSTEM_PROMPT},
29                {"role": "user", "content": question},
30            ],
31            "temperature": 0.3,
32            "max_tokens": 1024,
33        },
34        timeout=timeout,
35    )
36    data = r.json()
37    if "choices" not in data:
38        raise RuntimeError(f"豆包调用失败:{data}")
39    return data["choices"][0]["message"]["content"], data["usage"]["total_tokens"]
40
41def route_endpoint(question: str) -> str:
42    """简单的成本路由:格式类任务走 lite,其余走 pro"""
43    cheap = any(k in question for k in ["转换", "分类", "摘要", "提取", "提取出", "格式"])
44    return EP_LITE if (cheap and len(question) < 200) else EP_PRO
45
46def gen_sign(secret: str) -> tuple[str, str]:
47    ts = str(int(time.time()))
48    return ts, hmac.new(b"", f"{ts}\n{secret}".encode(), hashlib.sha256).hexdigest()
49
50def push(msg_type: str, content: dict) -> dict:
51    ts, sign = gen_sign(SECRET)
52    return httpx.post(WEBHOOK, json={
53        "timestamp": ts, "sign": sign, "msg_type": msg_type, "content": content
54    }, timeout=10).json()
55
56if __name__ == "__main__":
57    q = "帮我给这场直播写 3 条标题,要有网感"
58    answer, tokens = ask_doubao(q)
59    print(push("text", {"text": f"{answer}\n\n(豆包 1.5 Pro · {tokens} tokens)"}))

跑起来:

bash
1pip install httpx python-dotenv
2python bot.py
3# 控制台打印 {'code': 0, 'msg': 'success', 'data': {}} 即链路打通

步骤五:卡片消息——飞书相对钉钉和企业微信的最大优势

钉钉的互动卡片要在后台建模板,企业微信的模板卡片类型固定,而飞书允许直接在请求体里传任意合法 JSON,还能带按钮、下拉框,点击后原样回传。

卡片 JSON 骨架(运营群文案版)

json
1{
2  "msg_type": "interactive",
3  "card": {
4    "config": { "wide_screen_mode": true, "enable_forward": true },
5    "header": {
6      "template": "orange",
7      "title": { "tag": "plain_text", "content": "豆包 1.5 Pro · 文案助手" },
8      "subtitle": { "tag": "plain_text", "content": "接入点 ep-...-x8k2m · 1.2s · 858 tokens" }
9    },
10    "elements": [
11      { "tag": "div", "text": { "tag": "lark_md", "content": "**你的需求**\n给这场直播写 3 条标题,要有网感" } },
12      { "tag": "hr" },
13      { "tag": "div", "text": { "tag": "lark_md", "content": "**版本一(悬念型)**\n..." } },
14      { "tag": "div", "text": { "tag": "lark_md", "content": "**版本二(直给型)**\n..." } },
15      { "tag": "note", "elements": [
16        { "tag": "plain_text", "content": "AI 生成内容,发布前请人工核对" }
17      ]}
18    ],
19    "actions": [
20      { "tag": "action", "actions": [
21        { "tag": "button", "text": { "tag": "plain_text", "content": "再来 3 条" },
22          "type": "primary", "value": { "action": "more", "qid": "8823" } },
23        { "tag": "button", "text": { "tag": "plain_text", "content": "换个风格" },
24          "type": "default", "value": { "action": "restyle", "qid": "8823" } },
25        {
26          "tag": "select_static",
27          "placeholder": { "tag": "plain_text", "content": "定稿发给谁?" },
28          "value": { "action": "assign" },
29          "options": [
30            { "text": { "tag": "plain_text", "content": "发给内容组" }, "value": "content" },
31            { "text": { "tag": "plain_text", "content": "发给设计组配图" }, "value": "design" }
32          ]
33        }
34      ]}
35    ]
36  }
37}

value 是任意 JSON 对象,你塞什么飞书就原样回传什么。把 qid 放进去,用户点「再来 3 条」时你就知道是哪一条需求。

拼进代码

python
1def build_copy_card(question: str, answer: str, elapsed: float, tokens: int,
2                    ep: str, qid: str = "") -> dict:
3    return {
4        "config": {"wide_screen_mode": True, "enable_forward": True},
5        "header": {
6            "template": "orange",
7            "title": {"tag": "plain_text", "content": "豆包 1.5 Pro · 回答"},
8            "subtitle": {"tag": "plain_text",
9                         "content": f"接入点 {ep} · {elapsed:.1f}s · {tokens} tokens"},
10        },
11        "elements": [
12            {"tag": "div", "text": {"tag": "lark_md", "content": f"**你的问题**\n{question}"}},
13            {"tag": "hr"},
14            {"tag": "div", "text": {"tag": "lark_md", "content": answer}},
15            {"tag": "note", "elements": [
16                {"tag": "plain_text", "content": "AI 生成内容,发布前请人工核对"}
17            ]},
18        ],
19        "actions": [{
20            "tag": "action",
21            "actions": [
22                {"tag": "button", "text": {"tag": "plain_text", "content": "再来 3 条"},
23                 "type": "primary", "value": {"action": "more", "qid": qid}},
24                {"tag": "button", "text": {"tag": "plain_text", "content": "换个风格"},
25                 "type": "default", "value": {"action": "restyle", "qid": qid}},
26            ],
27        }],
28    }
29
30push("interactive", build_copy_card(q, answer, 1.2, 858, EP_PRO, "8823"))

步骤六:方式 B 企业自建应用(能收消息 + 能私聊)

6.1 创建应用并拿凭证

  1. 飞书开放平台 → 开发者后台 → 创建企业自建应用
  2. 「添加应用能力」→ 勾选 机器人
  3. 「凭证与基础信息」记下:
text
1App ID:     cli_e5f6g7h8i9j0k1l2
2App Secret: Bv4Nq7Rt2Wz9Yc3A4b5C6d7E8f9G0h1

6.2 必勾权限

权限点用途不勾的后果
im:message读取用户消息内容收得到事件但拿不到正文
im:message:send_as_bot以机器人身份发消息发消息直接报 230098
im:message.group_at_msg接收群里 @ 机器人的消息群里 @ 没反应
im:message.p2p_msg接收单聊消息私聊不可用
im:resource下载用户发来的图片/文件看图与文件场景不可用
contact:user.base:readonly查发言人姓名与部门做不了部门白名单

6.3 tenant_access_token 缓存(2 小时过期)

python
1import os, time, threading, httpx
2
3APP_ID = os.environ["FEISHU_APP_ID"]
4APP_SECRET = os.environ["FEISHU_APP_SECRET"]
5FS_TOKEN_URL = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
6
7_fs = {"token": None, "expire_at": 0}
8_fs_lock = threading.Lock()
9
10def get_tenant_access_token() -> str:
11    """有效期 2 小时,过期前 5 分钟刷新。
12    为什么必须缓存:飞书对鉴权接口有独立限流,高频换取会被拒绝,
13    进而导致所有发消息请求失败。
14    """
15    now = time.time()
16    if _fs["token"] and _fs["expire_at"] - now > 300:
17        return _fs["token"]
18    with _fs_lock:
19        if _fs["token"] and _fs["expire_at"] - time.time() > 300:
20            return _fs["token"]
21        r = httpx.post(FS_TOKEN_URL, json={
22            "app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10)
23        data = r.json()
24        if data.get("code") != 0:
25            raise RuntimeError(f"获取 tenant_access_token 失败:{data}")
26        _fs["token"] = data["tenant_access_token"]
27        _fs["expire_at"] = now + data["expire"]     # 通常 7200 秒
28        return _fs["token"]

信息

多实例部署的同学注意:进程内缓存在多 worker 下会产生多份 token(共用不冲突),但并发刷新可能撞限流。上生产请换成 Redis 存 token,配合带过期时间的分布式锁(SETNX)保证只有一个实例去刷新。

6.4 发消息与更新卡片

python
1SEND_URL = "https://open.feishu.cn/open-apis/im/v1/messages"
2
3def send_to_chat(chat_id: str, card: dict, receive_id_type: str = "chat_id") -> str:
4    r = httpx.post(SEND_URL, params={"receive_id_type": receive_id_type},
5        headers={"Authorization": f"Bearer {get_tenant_access_token()}"},
6        json={"receive_id": chat_id, "msg_type": "interactive",
7              "content": json.dumps(card, ensure_ascii=False)},   # 必须字符串化
8        timeout=10).json()
9    return r["data"]["message_id"]
10
11def send_placeholder(chat_id: str) -> str:
12    card = {"config": {"wide_screen_mode": True},
13            "header": {"template": "grey",
14                       "title": {"tag": "plain_text", "content": "豆包正在思考…"}},
15            "elements": [{"tag": "div",
16                          "text": {"tag": "lark_md", "content": "正在调用豆包 1.5 Pro,请稍候"}}]}
17    return send_to_chat(chat_id, card)
18
19def update_card(message_id: str, card: dict):
20    """PATCH 覆盖原卡片,用户看到的是同一条消息「长出」答案"""
21    return httpx.patch(f"{SEND_URL}/{message_id}",
22        headers={"Authorization": f"Bearer {get_tenant_access_token()}"},
23        json={"content": json.dumps(card, ensure_ascii=False)}, timeout=10).json()

与 Webhook 方式的三个关键差异:

  1. content 必须 json.dumps 成字符串,传对象会报 230002
  2. 鉴权走 Header 的 Authorization: Bearer {token},不是 body 里的 sign
  3. 收件人由 URL 参数 receive_id_type + body 的 receive_id 指定,可发私聊

信息

注意:群自定义机器人的 Webhook 不支持更新已发出的消息。要做「先占位后更新」和流式打字机,必须走自建应用。

步骤七:事件订阅——让机器人「听见」群里的话

要收到用户消息,必须订阅 im.message.receive_v1。

长连接 vs Webhook 回调

长连接(WebSocket)Webhook 回调(HTTP)
要不要公网 IP / 域名❌ 不需要,本地就能跑✅ 必须有 HTTPS 公网地址
要不要自己解密不需要,SDK 内部处理要,配了 Encrypt Key 就得 AES-256-CBC 解
配置复杂度⭐ pip 装 SDK 即可⭐⭐⭐⭐ 配 URL、加密、验证
消息到达延迟50-120ms80-300ms
断线表现SDK 自动重连飞书重试 3 次,失败即丢
多实例 / 负载均衡❌ 一条连接只能一个进程✅ 天然支持多副本
适用内部工具、调试、单实例生产、多副本、要审计

建议:开发和内部小规模使用选长连接;正式给全公司用的机器人选 Webhook 回调,因为长连接在多副本部署时会重复消费消息。

长连接代码

python
1import os, json as _json
2import lark_oapi as lark
3from lark_oapi.api.im.v1 import *
4
5def on_receive(data: P2ImMessageReceiveV1) -> None:
6    msg = data.event.message
7    if msg.message_type != "text":        # 忽略图片、表情、系统消息
8        return
9    question = _json.loads(msg.content).get("text", "").strip()
10    if not question:
11        return
12    ep = route_endpoint(question)         # 选接入点
13    answer, tokens = ask_doubao(question, ep)
14    card = build_copy_card(question, answer, 0, tokens, ep, qid=msg.message_id)
15
16    # 用 message_id 做「回复」,消息挂在原消息下面,群里不会刷屏
17    client.im.v1.message.reply(ReplyMessageRequest.builder()
18        .message_id(msg.message_id)
19        .request_body(ReplyMessageRequestBody.builder()
20            .msg_type("interactive")
21            .content(_json.dumps(card, ensure_ascii=False)).build())
22        .build())
23
24handler = lark.EventDispatcherHandler.builder("", "") \
25    .register_p2_im_message_receive_v1(on_receive).build()
26client = lark.ws.Client(os.environ["FEISHU_APP_ID"], os.environ["FEISHU_APP_SECRET"],
27                        event_handler=handler, log_level=lark.LogLevel.INFO)
28client.start()

Webhook 回调 + AES-256-CBC 解密

python
1@app.post("/feishu/event")
2async def feishu_event(req: Request):
3    body = await req.json()
4    if "challenge" in body:                      # 1. URL 校验,1 秒内返回
5        return {"challenge": body["challenge"]}
6    if "encrypt" in body:                        # 2. 配了 Encrypt Key 时是密文
7        body = decrypt(body["encrypt"], os.environ["FEISHU_ENCRYPT_KEY"])
8    if body.get("header", {}).get("event_type") == "im.message.receive_v1":
9        asyncio.create_task(handle_message(body["event"]))   # 异步,别阻塞
10    return {"code": 0}
python
1import base64, hashlib
2from Crypto.Cipher import AES      # pip install pycryptodome
3
4def decrypt(encrypt_b64: str, encrypt_key: str) -> dict:
5    """飞书 Encrypt Key 是 43 位字符串,SHA256 后取前 32 字节作密钥"""
6    key = hashlib.sha256(encrypt_key.encode()).digest()[:32]
7    raw = base64.b64decode(encrypt_b64)
8    iv, cipher_text = raw[:16], raw[16:]           # 前 16 字节是 IV
9    plain = AES.new(key, AES.MODE_CBC, iv).decrypt(cipher_text)
10    return _json.loads(plain[:-plain[-1]].decode("utf-8"))    # 去 PKCS7 填充

步骤八:发布、灰度与验证

  1. 开放平台 → 版本管理与发布 → 创建版本,填 1.0.0 与更新说明
  2. 「申请发布」,等企业管理员通过
  3. 在飞书里搜机器人名字,发一句「你好」验证

推荐的上线顺序(能省掉八成客诉):

text
1第 1 天:可用范围只放自己,单聊自测
2第 2 天:拉 3 人测试群,验证 @、私聊、卡片按钮
3第 3 天:放开到 20 人小群,观察 token 消耗与响应时长
4第 5 天:全部门放开,群里置顶「机器人使用说明」

上线自检清单:

text
1- [x] 密钥与接入点 ID 全在环境变量里,git status 看不到 .env
2- [x] 确认 model 字段填的是 ep- 开头的接入点 ID 而不是模型名
3- [x] 连续发 100 条测试消息,没有触发飞书限流
4- [x] 断网 10 秒再恢复,长连接能自动重连
5- [x] 卡片按钮点下去都有正确回传
6- [x] 方舟控制台设了用量告警

场景应用示例

场景1:营销文案批量生成

把这段发给机器人:

text
1@豆包 1.5 Pro 助手
2给这场直播写 3 条标题,产品是「便携咖啡机」,受众是 25-35 岁上班族。
3要求:一条悬念型、一条直给型、一条情绪型,每条不超过 20 字。

你会得到:

  • 3 条标题,各自标注风格类型,都在 20 字以内
  • 每条后面附一句「适合投放在哪」(信息流 / 朋友圈 / 社群)
  • 卡片底部两个按钮:再来 3 条、换个风格
  • 一个下拉框:定稿后可以直接「发给内容组」或「发给设计组配图」

场景2:竞品动态摘要

把这段发给机器人(配一段竞品公众号推文原文):

text
1@豆包 1.5 Pro 助手
2把这段竞品推文压缩成 150 字摘要,按「他们做了什么 / 目标人群 / 
3我们可以怎么应对」三段输出,不要复述原文。

你会得到:

  • 三段式摘要,每段一到两句
  • 「他们做了什么」里只保留动作和关键数字,去掉宣传话术
  • 「我们可以怎么应对」给出 1-2 条可执行的动作
  • 全文不超过 150 字,适合直接贴进群日报

场景3:值班问题分级

把这段发给机器人:

text
1@豆包 1.5 Pro 助手
2下面这段用户反馈,请判断:1)属于 P0/P1/P2 哪一级;2)应该转给哪个组;
33)给用户的一句标准回复。
4反馈内容:支付页面点了没反应,反复试了五次都不行,安卓机型。

你会得到:

  • 一个明确的分级结论(如「P1,影响支付主链路且可复现」)与一句判断依据
  • 建议转给的组(如「支付组 + 客户端组」)
  • 一句可直接复制的用户回复话术,语气安抚且不包含过度承诺
  • 卡片按钮:「转给支付组」(点击后回传你的服务,自动建工单)

场景4:周报数据解读

把这段发给机器人(配一张数据表格文本):

text
1@豆包 1.5 Pro 助手
2这是本周数据:新增用户 1,240(上周 1,510)、次日留存 31%(上周 34%)、
3付费转化 2.1%(上周 2.4%)。请用「变化 + 可能原因 + 建议动作」的格式,
4给出 3 条观察,不要编造我们没有的数据。

你会得到:

  • 3 条观察,每条按「指标变化 → 可能原因 → 建议动作」三段
  • 可能原因会明确标注为推测(如「可能与上周渠道投放减少有关,需核实」)
  • 不会编造「行业平均留存 35%」这类未提供的数字
  • 结尾一句「以上推测需要你方数据验证」

最佳实践

提示词技巧(群里回答更准)

  1. 在 system prompt 里写死人设:飞书是群聊,回答超过 300 字就没人看。把「简洁、中文、列表化、300 字内」写进 system prompt,比每次在问题里强调有效得多。
  2. 先结论后理由:提问模板改成「先一句话结论,再展开」,用户扫一眼就拿到答案。
  3. 强制标注不确定性:加一句「不确定就说不确定,不要编造数据、价格、日期」,这在运营群问数据时能救命。
  4. 指定输出格式:要表格就说「用 Markdown 表格」,要 JSON 就说「只输出 JSON」。豆包对格式指令遵循度不错,但不指定就容易自由发挥。
  5. 要求给多个版本:文案类需求直接说「给 3 个版本并标注风格」,比反复追问「再改一版」高效得多。

安全建议:三种密钥管理策略对比

策略做法安全等级适用规模
环境变量 + .gitignore密钥与接入点 ID 放 .env,代码里 os.environ 读⭐⭐⭐个人 / 小团队,最快
密钥管理服务Vault / 云厂商 KMS,运行时拉取⭐⭐⭐⭐⭐中大型团队,上生产必做
服务端代理网关业务服务不持有 Key,统一走内部网关审计与限流⭐⭐⭐⭐⭐多条业务线都要调 AI 时

其他硬性要求:

  • 给机器人加频率限制:令牌桶 40 条/分钟,超限丢弃并记日志,防止被刷。
  • 按部门白名单:用 contact:user.base:readonly 拿 open_id,只允许特定部门调用。
  • 接入点分离:不同机器人用不同接入点,这样在方舟控制台能分别看用量和限流,一个出问题不影响另一个。
  • 定期轮换:app_secret 与方舟 API Key 建议 90 天换一次;换 Key 后接入点 ID 不变,不用改代码。
  • 敏感内容脱敏:客户手机号、身份证、合同金额在送进模型前做正则替换,这是合规底线。

性能优化清单

动作收益成本
双接入点路由(pro + lite)成本降约 38%10 行代码
开启前缀缓存system prompt 长的场景省约 23%控制台勾一下
httpx 连接复用省 60-120ms/次5 行代码
tenant_access_token 缓存省 80-150ms/次30 行代码
先发占位卡 + 异步更新感知延迟 1.81s → 0.27s中等
同问题 5 分钟结果缓存省 100% 重复调用20 行代码
temperature 从 0.7 降到 0.3输出长度平均缩短 15%改一个数字

故障排查(18 条)

你看到的现象可能的原因怎么解决
model not found / 接入点不存在model 填了模型名而不是接入点 ID去方舟控制台复制 ep- 开头的接入点 ID 填进去
方舟 401 鉴权失败API Key 错误、已删除或不属于当前账号重新生成 Key;确认 Key 与接入点在同一账号下
方舟 403 无权限模型未开通或接入点未生效到「开通管理」开通豆包 1.5 Pro;接入点创建后等一两分钟再试
方舟 429 限流触发接入点 QPS 上限加退避重试与队列;在控制台提升该接入点的 QPS
方舟提示余额不足账户欠费或资源包用完充值后等几分钟生效
code 19021 sign match failtimestamp 用成毫秒、签名串顺序反、编码用了 base64timestamp 改秒级,串写成 ts + 换行 + secret,用 hexdigest()
code 19001 param invalidbody 缺 timestamp/sign,或 msg_type 拼错补齐四个顶层字段;msg_type 只能是 text/post/image/interactive/share_chat/file
卡片报 code 230002卡片 JSON schema 不合法颜色改内置色号;按钮必须有 type 和非空 value;content 必须字符串化
code 230098 no permission权限没勾或没通过审核勾 im:message:send_as_bot,等管理员通过
code 99991663 换 token 失败app_id/app_secret 填错或鉴权被限流核对凭证;加缓存,别每请求都换
code 230001 message not foundmessage_id 过期或不属于本应用超时消息不能更新,改发新消息
HTTP 429 / 消息发不出去触发飞书频率限制(约 50 条/分钟)合并消息、加令牌桶
机器人 @ 了没反应没订阅事件,或机器人不在群里订阅 im.message.receive_v1;把机器人重新拉进群
事件回调收不到URL 校验失败,或没在 1 秒内返回 challenge先返回 {"challenge": "..."},业务逻辑异步处理
回调收到密文看不懂配了 Encrypt Key 但没解密SHA256(Encrypt Key)[:32] 作密钥、前 16 字节作 IV 做 AES-256-CBC
长连接跑一会儿就断网络空闲被掐或 SDK 版本旧升级 lark-oapi,开启 SDK 心跳
回答被截断max_tokens 太小群聊 1024 够用,长文场景 2048-4096
多副本收到重复消息用了长连接但起了多进程长连接只允许单实例;多副本改 Webhook 回调或用 Redis 锁去重

扩展与进阶

还能玩出什么花样

  • 卡片按钮闭环审批:把 value.action 扩成 approve / reject / escalate,配一张审批卡片,实现「群里点按钮 → 回调你的服务 → 写数据库 → 更新卡片状态」。这是飞书比钉钉顺手的地方。
  • 流式打字机:方舟支持 stream=True,配合卡片流式更新接口,让答案一个字一个字长出来,感知延迟再降一档。
  • Function Calling:注册 query_order(order_id)、create_ticket(...) 等函数,群里直接查订单、开工单。函数控制在 5 个以内,schema 描述写清楚。
  • 接入点灰度:方舟的接入点可以绑定不同模型版本。做模型升级时新建一个接入点,按 10% 流量灰度,观察效果再全量切换,比直接改代码安全得多。
  • 前缀缓存:system prompt 是固定长文本时开启前缀缓存,命中部分按折扣计费,机器人这种固定人设的场景收益最大。

团队一起用

  • 按业务线拆机器人 + 拆接入点:运营群、客服群各配一个机器人,各自绑一个接入点,用量和限流在控制台分开看,互不干扰。
  • 统一 Prompt 仓库:system prompt 存进 Git,改动走 PR review,避免有人随手改一行导致行为突变。
  • 用量看板:把每次调用的 tokens、耗时、用户 open_id 打进日志,用飞书多维表格做看板,谁在用、烧了多少钱一目了然。
  • 建立错误案例库:出现「机器人答错了」就截图归档,写进 system prompt 的反例,迭代几轮准确率会明显提升。

进阶学习路径

text
1阶段 1(入门):群自定义机器人 + 加签,能把豆包的回答推进群里      ← 你现在在这里
2阶段 2(进阶):建推理接入点,搞懂 ep- ID 与模型名的关系
3阶段 3(高级):交互卡片 + 占位卡 + 异步更新,把感知延迟压到 0.3 秒
4阶段 4(专家):自建应用 + 长连接事件订阅 + 双接入点成本路由
5阶段 5(架构):Webhook 回调 + 多副本 + KMS 密钥管理 + 接入点灰度

提示

提示:豆包与飞书开放平台都在持续迭代,接入点机制、模型名和限流阈值可能调整。建议收藏官方文档——火山方舟文档 与 飞书开放平台文档,遇到本文没覆盖的报错码,优先去这两处查最新的错误码表。

常见问题

Q为什么我填 doubao-1.5-pro-32k 作为 model 会报错 model not found?

火山方舟不直接用模型名做请求参数。你必须在控制台「在线推理 → 创建推理接入点」里选定豆包 1.5 Pro,系统会生成一个 ep- 开头的接入点 ID(形如 ep-20260723123456-abcde),请求时 model 填这个 ID。这是方舟和 DeepSeek、Kimi 最大的区别,第一次接的人几乎都会踩。

Q接入点 ID 和 API Key 是什么关系?能复用吗?

API Key 是账号级凭证,接入点 ID 是「某个模型 + 某套配置」的实例。同一个 API Key 可以调多个接入点;同一个接入点也可以被多个应用共用(不推荐,因为配额和限流是分开的)。建议按「一个机器人一个接入点」来建,这样在控制台能单独看到每个机器人的用量和限流情况。

Q加签一直返回 19021 sign match fail 怎么办?

三个高频原因:timestamp 用了毫秒(13 位)必须改成秒级(10 位);待签名串必须是 timestamp 加换行符加 secret,顺序不能反;加密结果要取 HMAC-SHA256 的十六进制小写字符串,而钉钉要的是 base64。另外服务器时间与飞书相差超过 1 小时也会被拒。

Qtenant_access_token 多久过期?能不能每次发消息都重新获取?

有效期 2 小时,不要每次都重新获取。飞书对鉴权接口有独立限流,实测高频换取会在几十秒内被打回限流错误,导致全部发消息请求失败。正确做法是进程内缓存,在剩余有效期小于 300 秒时刷新;多实例部署放 Redis 并加分布式锁。

Q卡片消息返回 230002 怎么排查?

230002 是消息体校验失败。按顺序查:header.template 只能用内置色号不能写十六进制颜色;elements 的 tag 必须是受支持标签;actions 里每个按钮必须有 type 和非空 value(不超过 512 字节);自建应用调用时 content 必须是字符串化的 JSON 而不是对象;整张卡片序列化后不超过 30KB。

阿里云推广

读完想动手?新用户首单 8 折上云

服务器 + 大模型 API + 对象存储,新用户首购享 20% 补贴

首单 8 折云服务器大模型 API对象存储

新用户首购享 20% 补贴(8 折)· 本次推广支持本站持续运营

腾讯云推广

腾讯智能体开发平台 ADP

新客首月专属价,最高直降 880 元,构建/分发/治理企业级 Agent

智能体Agent首月直降 880一站式

腾讯云新用户专享 · 本次推广支持本站持续运营

订阅每日 AI 日报

每天早上 8:00(GMT+8)把当天 AI 行业要闻投递到你的邮箱,随时一键退订。

每天早上 8:00(GMT+8)投递当天日报;若当天日报未生成则不发送,也不会补发往期。

更多订阅方式(RSS / 微信推送)仅发送日报,不做任何其他用途

相关推荐

查看更多 →
教程飞书接入

DeepSeek V4.1 Flash 接入 飞书 完整指南

DeepSeek V4.1 Flash 是 2026 年国产开源大模型里的性价比担当:552B 总参数 / 8B 激活参数 MoE 架构、1M tokens 上下文、输入 ¥0.8 每百万 tokens。本文手把手把它接进飞书,覆盖群自定义机器人 Webhook 加签(HMAC-SHA256 取十六进制,与钉钉 base64 完全不同)、交互式卡片消息 JSON 全字段解析、企业自建应用 tenant_access_token 缓存与 im/v1/messages 发消息、长连接与 Webhook 回调两种事件订阅方案对比,并附 12 条报错排查表、4 个可直接复制的场景提示词与 3 种安全策略对比。

2026-04-27
#DeepSeek#V4.1 Flash
教程飞书接入

通义千问 Qwen3.8-Omni-Flash 接入 飞书 完整指南

Qwen3.8-Omni-Flash 是阿里云百炼 2026 年的全模态轻量模型:文本、图片、音频、视频一模型通吃,输入 ¥0.5 每百万 tokens,延迟低到能做高频群聊。本文手把手把它接进飞书,覆盖群自定义机器人 Webhook 加签(HMAC-SHA256 取十六进制,与钉钉 base64 完全不同)、图片与音频多模态消息体的正确写法、交互式卡片消息 JSON 全字段解析、企业自建应用 tenant_access_token 缓存与 im/v1/messages 发消息、长连接与 Webhook 回调两种事件订阅对比,并附 18 条报错排查表、4 个可直接复制的多模态场景提示词与 3 种密钥策略对比。

2026-04-27
#通义千问#Qwen3.8-Omni-Flash
教程飞书接入

文心一言 ERNIE 4.5 接入 飞书 完整指南

文心一言 ERNIE 4.5 是百度 2026 年的主力国产大模型:原生多模态、128K 上下文、中文语料与本地化知识扎实。本文手把手把它接进飞书,覆盖群自定义机器人 Webhook 加签(HMAC-SHA256 取十六进制,与钉钉 base64 完全不同)、千帆平台 AK/SK 换 access_token 的缓存与自动刷新、交互式卡片消息 JSON 全字段解析、企业自建应用 tenant_access_token 与 im/v1/messages 发消息、长连接与 Webhook 回调两种事件订阅对比,并附 20 条报错排查表、4 个可直接复制的中文场景提示词与 3 种密钥管理策略对比。

2026-04-26
#文心一言#ERNIE 4.5
教程飞书接入

Kimi K3 接入 飞书 完整指南:长文本群问答 + 卡片流式更新双路径实测

Kimi K3 是月之暗面 2026 年的主力大模型,256K 上下文、支持文件上传后直接问答,长文档理解是它的看家本领。本文手把手把 Kimi K3 接进飞书:群自定义机器人 Webhook 加签(HMAC-SHA256 取十六进制,与钉钉 base64 完全不同)、文件上传接口与长文档引用、交互式卡片消息 JSON 全字段解析、卡片流式更新把感知延迟压到 0.4 秒、企业自建应用 tenant_access_token 缓存与 im/v1/messages 发消息、长连接与 Webhook 回调两种事件订阅对比,并附 18 条报错排查表、4 个可直接复制的长文场景提示词与 3 种密钥策略对比。

2026-04-21
#Kimi#K3

好物推荐 · 广告合作