飞书对接 AI 第二步:机器人发消息完整代码(receive_id、限流、错误码,2026 新版接口)
上一篇讲了怎么拿 tenant_access_token([L1]),这一课用上它:让机器人把消息发出去。
群里 @AI 问一句、日报定时推到团队群、客服自动回复——这些场景的底层都是同一个接口:POST /open-apis/im/v1/messages。
它只有三个必填字段,但实际对接的人大多会卡在三个地方:receive_id_type 选错、content 的双重 JSON 序列化、批量推送撞限流。这篇按官方现行文档写全,代码可以直接跑。
一、接口速览
| 项 | 值 |
|---|---|
| URL | https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type={type} |
| Method | POST |
| Content-Type | application/json; charset=utf-8 |
| 权限(任一) | im:message 或 im:message:send_as_bot |
| 接口频率 | 1000 次/分钟、50 次/秒 |
| 细粒度限流 | 同一用户 5 QPS;同一群组内机器人共享 5 QPS |
| 体积上限 | 文本 ≤150 KB;卡片/富文本 ≤30 KB |
请求体四个字段:
| 字段 | 必填 | 说明 |
|---|---|---|
receive_id | 是 | 接收者 ID,类型与 receive_id_type 一致 |
msg_type | 是 | 消息类型(见第三节) |
content | 是 | JSON 序列化后的字符串(不是对象,第四节详讲) |
uuid | 否 | 幂等键:相同 uuid 在 1 小时内至多成功发送一条,≤50 字符 |
二、receive_id_type:五种取值,选错一个全失败
"发给谁"由 URL 参数 receive_id_type + 请求体 receive_id 成对决定:
| receive_id_type | 发给谁 | 典型场景 |
|---|---|---|
open_id | 单个用户 | 应用维度用户 ID(ou_ 开头),最常用 |
union_id | 单个用户 | 跨应用统一 ID |
user_id | 单个用户 | 租户内员工 ID |
email | 单个用户 | 直接用邮箱发 |
chat_id | 群聊 | 往群里发必须用这个 |
两个高频翻车点:
- 发群消息却传了用户的 open_id——报错或消息"发成功"但群里看不到。发群只认
chat_id - chat_id 去哪拿:把机器人拉进群 → 调「获取群列表」
GET /open-apis/im/v1/chats→ 返回里就是 chat_id
结论:receive_id 和 receive_id_type 必须成对匹配,群消息只认 chat_id。
三、msg_type:十一种消息类型,先用 text
官方支持:text(文本)、post(富文本)、image(图片)、file(文件)、audio(语音)、media(视频)、sticker(表情包)、interactive(卡片)、share_chat(分享群名片)、share_user(分享个人名片)、system(系统消息,仅单聊)。
图片/音频/视频/文件要先调上传接口拿 key,不能直接传文件。interactive 卡片是 L3 的主题(把大模型回复做成带按钮的交互卡片)。
这一课先把 text 跑通——它是后面一切的基础。
四、content 的坑:双重 JSON 序列化
最反直觉的一个坑。请求体长这样:
{
"receive_id": "oc_xxx",
"msg_type": "text",
"content": "{\"text\":\"hello\"}"
}
content 的值不是 JSON 对象,是一个字符串——内层 JSON 先序列化一次,再作为字符串放进外层 body 序列化第二次。直接传对象报 230001。
三个细节:
- 换行符:文本里的
\n在 content 字符串里是\\n(用json.dumps自动处理,别手拼字符串) - 超长:AI 生成长回复直接塞会撞
230025(content reaches its limit),进飞书前先截断或分段 - 中文:序列化时
ensure_ascii=False,否则中文变\uXXXX(不影响发送但影响调试)
结论:content 双重序列化 + 换行转义 + 超长截断,三件事少做一件就报错。
五、完整代码(复用 L1 的 token 管理器)
L1 的 FeishuToken 类(缓存 + 提前刷新 + 错误码映射)直接拿来用,这课只加发送部分:
import json
import urllib.error
import urllib.request
SEND_URL = "https://open.feishu.cn/open-apis/im/v1/messages"
# 官方错误码 → 中文排查(im/v1/messages 高频 12 个)
SEND_ERROR_HINTS = {
230001: "请求参数不合法:检查 receive_id_type 与 receive_id 是否匹配、content 是否为合法 JSON 字符串",
230002: "机器人不在该群里:先把机器人拉进目标群",
230006: "机器人能力未启用:去开发者后台开启机器人能力",
230013: "机器人对该用户不可见:检查应用可用范围是否包含该用户",
230020: "触发频率限制:同一用户/群仅 5QPS,客户端排队重试",
230022: "消息含敏感信息被拦截:改写内容后重试",
230025: "消息超长:文本≤150KB、卡片/富文本≤30KB,AI 回复先截断",
230027: "缺少权限:确认已开通 im:message 或 im:message:send_as_bot",
230034: "receive_id 无效:核对 ID 来源与 receive_id_type 是否一致",
230035: "无发送权限:检查应用身份权限配置",
230053: "用户已停用该机器人:对方关闭了机器人会话",
232009: "群已解散:chat_id 失效,重新获取群列表",
}
def send_text(token_header, receive_id, text, receive_id_type="open_id", uuid=None):
"""发送文本消息。content 双重序列化在这里处理。"""
inner = json.dumps({"text": text}, ensure_ascii=False) # 第一层:内层 JSON
body = {
"receive_id": receive_id,
"msg_type": "text",
"content": inner, # 第二层:作为字符串放入外层 body
}
if uuid:
body["uuid"] = uuid[:50] # 幂等键,1 小时内同 uuid 只发一条
req = urllib.request.Request(
f"{SEND_URL}?receive_id_type={receive_id_type}",
data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
headers={**token_header, "Content-Type": "application/json; charset=utf-8"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
payload = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
# 发送失败是 HTTP 400,错误码在响应体里,必须读 body
payload = json.loads(e.read().decode("utf-8"))
if payload.get("code") != 0:
code = payload.get("code")
hint = SEND_ERROR_HINTS.get(code, "查官方文档 im/v1/messages 错误码章节")
raise RuntimeError(f"[{code}] {payload.get('msg')} | 排查:{hint}")
return payload["data"] # 含 message_id
if __name__ == "__main__":
import os
from l1_token import FeishuToken # L1 代码存为 l1_token.py
ft = FeishuToken(os.environ["FEISHU_APP_ID"], os.environ["FEISHU_APP_SECRET"])
msg = send_text(
ft.auth_header(),
receive_id=os.environ["FEISHU_TEST_CHAT_ID"], # 群 chat_id
text="Hello from 提示岛教程 L2:机器人发消息跑通了",
receive_id_type="chat_id",
uuid="l2-demo-0001", # 重试不重复
)
print("message_id:", msg.get("message_id"))
这段代码把三个坑都处理了:receive_id_type 显式传参(群消息传 chat_id)、content 用 json.dumps 做内层序列化(换行自动转义)、uuid 幂等防重复。
批量推送的限流处理:同一群 5 QPS,给 200 个群发日报时自己加排队间隔(如 time.sleep(0.25)),重试时带同一个 uuid——网络超时重试不会造成重复消息。
六、限流与幂等:uuid 是官方给的防重复开关
| 限制 | 值 | 应对 |
|---|---|---|
| 接口整体 | 1000 次/分钟、50 次/秒 | 一般够用 |
| 同一用户 | 5 QPS | 批量私信自己排队 |
| 同一群组 | 机器人共享 5 QPS | 多机器人同群要合并计算 |
| uuid 幂等 | 相同 uuid 1 小时内至多成功 1 条 | 超时重试必带,防重复消息 |
结论:批量推送必须自己限流;重试带 uuid 防重复,这是官方给的幂等开关。
七、两段可以直接复制的机器人提示词
消息通道打通了,接下来是"发什么"。这两段来自提示岛站内条目,对应机器人两个最典型场景。
① 群内自动回复(客服/答疑机器人)——id=5058「AI 客服专家」
Act as an AI Customer Support Specialist. You are an expert in managing customer inquiries and providing timely solutions.
Your task is to:
- Understand and categorize customer issues
- Provide accurate and helpful responses
- Escalate complex issues to human agents as needed
Rules:
- Maintain a professional and friendly tone
- Ensure customer satisfaction with every interaction
- Follow company policies and procedures for handling customer data
Variables:
- ${customerIssue} - Description of the customer's issue
- ${responseTime:immediate} - Desired response time
用法:事件订阅收到群消息后,把用户问题填进 ${customerIssue},大模型回复经 send_text 发回群里。注意 ${responseTime:immediate} 这种带默认值的变量写法——冒号后是缺省值,调用方不传就用它,这个设计值得抄。
② 定时推送(日报/简报机器人)——id=11805「简报写手机器人」核心段
I'd like you to simulate a BOT called '[NAME OF YOUR NEWSLETTER] BOT'. In this role your task is to act as a collaborative writer and editor for [NAME OF YOUR NEWSLETTER]. You should strive to maintain the style, tone, and register used in all the examples I provide with anything you write.
[NEWSLETTER CREATION PROCESS] As a co-writer and editor of [NAME OF YOUR NEWSLETTER] one of your tasks is to assist me in drafting newsletters for my input and final approval. There are 6 phases to this process. They are: RAW INPUT COLLECTION + ORGANIZATION, OUTLINING, EXPANDING THE OUTLINE, EDITING, CALL TO ACTION, LOGO.
(全文 3566 字,站内可一键复制。)用法:定时任务触发 → 这段提示词产出简报 → send_text 推到群。它把生成过程拆成 6 个阶段(收集→大纲→扩写→编辑→行动号召→署名),比"帮我写个日报"稳定得多。
结论:机器人提示词要带流程阶段和变量缺省值,输出才稳定到能直接推送。
这一课做完,你手上有什么
- 一个能往群/单聊发文本的完整函数(含错误码中文映射)
- receive_id_type 五值对照 + chat_id 获取路径
- uuid 幂等 + 5QPS 限流的批量推送策略
- 客服、简报两段可直接改用的机器人提示词
下一课(L3)讲消息卡片:msg_type=interactive,把大模型的回复做成带按钮、可交互的卡片,以及 30KB 限制下的卡片 JSON 瘦身技巧。
附:提示岛是什么
本文提示词来自提示岛(prompts.ssjintaitong.com),AI 提示词与 Skill 库,实测收录 10907 条,其中文字提示词 3482 条(完整全文、一键复制),Skill/插件 5298 条,23 个分类。免登录、免注册。
飞书相关入口:
- 飞书 AI 自动化专题(145 条):
prompts.ssjintaitong.com/topic/feishu - 本文两段提示词原文:
prompts.ssjintaitong.com/prompts/5058、/prompts/11805 - 文字提示词总库:
prompts.ssjintaitong.com/prompts?type=text - 本系列 L1(token 获取):同专题可查
站内两个实用功能:Skill 质量评分(5298 个插件条目按四维度打 A-D 档,报告在 /quality-report);每条附原始出处(GitHub 链接 + 许可证)。搜索技巧:用场景词搜,搜「飞书」有 130 条。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)