飞书 * OpenClaw 集成:ID 体系全景与排坑指南
〇、一图看懂(先看这张再往下读)

这张图想讲清楚两件事(个人用户视角):
-
1. 您自己一个人,在 10 个您建的飞书机器人下,有 10 个完全不同的 open_id——飞书故意这么设计 -
2. union_id 是把这些 open_id 串起来的”红线”——想跨机器人找回”您自己”,靠它
看完这张图,再看下面的术语和细节,就不会迷路。
一、ID 体系总览
1.0 为什么会有这么多 ID
场景设定:您是个人开发者,注册了 10 个飞书机器人(feishu_cio / feishu_writer / wuzhao / duola 等),每个机器人都是您自己跟它聊。
核心类比——您(个人开发者)经营着 10 个小工作室(一人公司模式):
完整 ID 映射表(一人公司类比):
|
|
|
|
|
|---|---|---|---|
| 您
|
|
|
|
| union_id |
|
on_xxxxxx |
跨工作室识别”同一个人”
|
| open_id |
|
ou_aaaa... |
发消息/调 API 的主语 ID
|
| user_id |
|
u_xxxx |
|
| 飞书 app
|
|
|
|
appId
cli_xxxxx) |
|
cli_xxxxx |
|
| appSecret |
|
*** |
|
| tenant_access_token |
|
t-xxxxxx |
|
message_id
om_xxx) |
|
om_xxxxx |
|
chat_id
oc_xxx) |
部门群/项目组
|
oc_xxxxx |
|
| chat_type
|
1对1 私聊 vs 群聊 |
|
|
| agentId |
|
feishu_cio |
|
| accountId |
|
feishu_cio |
|
| session key |
|
agent:feishu_cio:feishu:direct:ou_aaaa... |
|
| subagent run id |
|
|
|
| cron job id |
|
|
|
一句话讲完:
您 = 一个人。10 个工作室 = 10 个飞书机器人。每个工作室给您发不同工号(open_id),但您的身份证号(union_id)跨 10 个工作室都一样。
为什么飞书要把 ID 设计得这么复杂(一人公司体系为什么这么设计):
-
• 数据隔离:A 工作室看不到您在 B 工作室的工号 → 公司之间不互通工号体系(商业机密) -
• 隐私保护:B 工作室不知道您在 A 工作室的工资 → 公司之间不知道对方薪资/资产(同业不互知) -
• 解耦:A 工作室倒闭时,您的 A 工作室工号跟着失效,不污染 B 工作室 → app 下线时 ou_xxx 跟着失效(不影响其他机器人) -
• API 鉴权:调 A 工作室的 API 必须用 A 工作室的工商注册号 + 营业执照密钥 → tenant_access_token 必须用对应 appId + appSecret 申请(用 A 的 token 调 B 的 API,B 不认)
这套 ID 体系在您日常工作中的「实际作用」:
-
1. 发消息时(im/v1/messages):OpenClaw 给您发消息,用 open_id作为receive_id。类比:就像您给某公司发文件,必须用那家公司的工号——工号能反查到这个人在这家公司里的位置。 -
2. 反查用户时(contact/v3/users):拿到一个 open_id想知道是谁,用对应appId拿 token 再调 API。类比:就像您拿到一个工号想查人,必须去那家公司的人事系统查——工号是公司内有效的,跨公司查不到。 -
3. 跨工作室协作时:让 A 工作室的输出推给 B 工作室的客户,必须用 B 工作室视角下的 open_id。类比:A 公司想联系 B 公司的客户,必须用 B 公司内部工号——A 公司工号 B 公司不认。 -
4. 配置 cron job 时:定时推送用 delivery.to: user:ou_xxx,这个 ou_xxx 必须是该 cron job 所属 agent 视角下的。类比:您让 A 工作室每天给某客户发邮件,必须用 A 工作室内部工号——用 B 工作室的工号 A 工作室查不到这个人。
对您意味着什么(总结):
-
• 想要”我用什么 ID 给您发消息” → 用 open_id(每个工作室下您看到的都不一样) -
• 想要”识别’您’就是同一个人” → 用 union_id(跨 10 个工作室都一样) -
• 想调某工作室的 API → 必须用该工作室的 appId + appSecret 拿 token(跨工作室 token 互不认) -
• 这几个 ID 各自有用途,不能混用——下面会具体讲混用会出什么问题
📌 重要前提:本节讲的”app”在飞书术语里叫”app”,但对应到您的工作流就是”您自己建的工作室/机器人”。文中”app A”和”app B”换成”feishu_cio 工作室”和”feishu_writer 工作室”会更容易理解。
1.1 飞书原生 ID(4 套)
|
|
|
|
|
|---|---|---|---|
ou_ |
open_id | 单个飞书应用(app)
|
|
on_ |
union_id | 整个企业/租户
|
|
u_ |
user_id |
|
|
oc_ |
chat_id |
|
|
1.2 OpenClaw 内部 ID
|
|
|
|
|
|---|---|---|---|
| agentId | feishu_cio
duola, wuzhao |
openclaw.json
agents.list[].id |
|
| accountId | feishu_cio |
|
与 agentId 一一对应
|
| appId | cli_xxxxxxxxxxxxxxxx |
|
|
| session key | agent:feishu_cio:feishu:direct:ou_xxxxxxxx |
~/.openclaw/agents/<agentId>/sessions/sessions.json |
|
| subagent run id |
|
~/.openclaw/state/openclaw.sqlite |
|
| cron job id |
|
~/.openclaw/state/openclaw.sqlite |
|
「三同一对应」原则(理解 OpenClaw 与飞书绑定关系的关键):
飞书 1 个 app (appId: cli_xxx) ↕ 一一对应OpenClaw 1 个 agent (agentId: feishu_cio) ↕ 一一对应飞书通道 1 个 account (accountId: feishu_cio)
记住一句话:在 OpenClaw 里,飞书通道下写 accountId 还是 agentId,值都一样(feishu_cio)。但 appId 是另一个东西(cli_xxx 形式),在 openclaw.json 的 channels.feishu.accounts.<id>.{appId, appSecret} 里。
二、核心原理:为什么不同 agent 下 open_id 不同
2.1 飞书视角的 ID 隔离
飞书的 open_id 设计原则:
-
• 同一用户(在飞书抽象术语里叫”同一用户”),在 agent A 下看到的 open_id 是 ou_aaa -
• 同一用户,在 机器人 B 下看到的 open_id 是 ou_bbb(完全不同的字符串) -
• 即便两个 agent 属于同一个企业也做不到直接互通 open_id
🔄 翻译成您的工作流:
• “同一用户” = 您自己(个人开发者身份) • “agent A” = 您建的 feishu_cio 机器人 • “agent B” = 您建的 feishu_writer 机器人 • 也就是说:您在 feishu_cio 下看到的 open_id 是 ou_aaa,在 feishu_writer 下看到的 open_id 是ou_bbb——两个完全不同的字符串,飞书故意不关联。
为什么这么设计(个人用户视角的具体含义):
-
• 隔离数据:feishu_cio 不能用 ou_aaa假装是 feishu_writer 调 API 查”您” -
• 隐私:feishu_cio 不会”知道” feishu_writer 跟”您”聊过什么,反之亦然 -
• 解耦:feishu_cio 下线时, ou_aaa也跟着失效,不污染 feishu_writer
2.2 用 OpenClaw 真实案例讲清楚
下面这个表,能让您直接看到——您自己,在您建的 10 个机器人下,open_id 全都不一样:
|
|
|
|---|---|
|
|
ou_aaaa1111aaaa1111aaaa1111 |
|
|
ou_bbbb2222bbbb2222bbbb2222 |
|
|
ou_cccc3333cccc3333cccc3333 |
|
|
ou_dddd4444dddd4444dddd4444 |
|
|
ou_eeee5555eeee5555eeee5555 |
|
|
ou_ffff6666ffff6666ffff6666 |
|
|
ou_7777aaaa7777aaaa7777aaaa |
|
|
ou_8888bbbb8888bbbb8888bbbb |
|
|
ou_9999cccc9999cccc9999cccc |
|
|
|
这意味着(个人用户视角):
-
• 上面 9 个 ou_xxxx全是您自己——不是 9 个员工、不是 9 个用户 -
• 飞书不会告诉您 “这些 open_id 是同一个人” -
• 您只能用 union_id跨机器人关联(比如您的 union_id 是on_xxxxxx—— 跨 10 个机器人都一样)
实际场景举例:
-
• 您给 feishu_cio这个机器人发消息时,飞书给feishu_cio分配给您的 open_id 是ou_aaaa1111aaaa1111aaaa1111 -
• 您给 feishu_writer发消息时,飞书给feishu_writer分配给您的 open_id 是ou_cccc3333cccc3333cccc3333 -
• 这两个 open_id 字符串没有任何关联性,飞书故意不让你建立关联——这就是”机器人维度隔离”
为什么您会建 10 个机器人(个人开发者的常见原因):
-
• 多场景分线:写作归写作机、研究归研究机、记账归记账机——避免一个机器人的”乱聊天”污染另一个 -
• 上下文隔离:feishu_cio 不会”记住”您跟 feishu_writer 聊过的内容——每个机器人都是独立大脑 -
• 故障隔离:某个机器人出问题时(凭证过期、限流、被封),其他 9 个照常运转 -
• 配置差异:不同机器人绑不同的飞书 agent(不同 cli_xxx),凭证独立管理
2.3 OpenClaw 的会话隔离机制
OpenClaw 通过 agent 维度 隔离会话状态:
文件系统层面:
~/.openclaw/agents/├── feishu_cio/│ ├── agent/│ └── sessions/│ └── sessions.json # feishu_cio 视角下所有 session├── wuzhao/│ └── sessions/│ └── sessions.json # wuzhao 视角下所有 session└── ...
session key 格式:
agent:<agentId>:<channel>:<channelId>:<peerId>示例:agent:feishu_cio:feishu:direct:ou_aaaa1111aaaa1111aaaa1111 # feishu_cio 跟"您"私聊agent:duola:feishu:group:oc_xxxxxxxxxxxxxxxxx # duola 视角下某个群
为什么这么设计:
-
• 同一个用户给 feishu_cio 和 wuzhao 发的消息完全隔离(不同 session 文件) -
• 一个 agent 的上下文不会泄漏到另一个 agent -
• cron job 推送时按 agent 路由到对应 bot
subagent 调度时:
-
• 父 agent(如 feishu_cio) spawn出子 agent -
• 子 agent 的 session key 以 subagent:<run_id>标识 -
• 完成后结果通过 requester_origin_json推回父 agent 的私聊 -
• 推回的 to字段必须用父 agent 视角下用户的 open_id
2.4 三层 ID 体系对应关系(建议结合 §〇 全景图阅读)
┌──────────────────────────────────────────────────────────────┐│ 飞书企业 (Tenant) ││ ├─ "您" (union_id: on_xxxxxxxxxxxxxxxxxxxxxxxx) ││ │ ├─ 在 feishu_cio 视角: ou_aaaa1111aaaa1111aaaa1111 ││ │ ├─ 在 feishu_co 视角: ou_bbbb2222bbbb2222bbbb2222 ││ │ ├─ 在 duola 视角: ou_eeee5555eeee5555eeee5555 ││ │ └─ ... (跨 10 个 agent) ││ └─ 其他用户... ││ ││ 飞书 agent (appId: cli_xxxxxxxx) ││ └─ 绑定到 OpenClaw agent (agentId: feishu_cio) ││ └─ agent 视角下的用户清单 ││ ├─ 您: ou_aaaa1111aaaa1111aaaa1111 ││ └─ 其他用户... ││ ││ OpenClaw Agent (agentId: feishu_cio) ││ └─ Sessions ││ ├─ agent:feishu_cio:feishu:direct:ou_aaaa1111... ││ ├─ agent:feishu_cio:feishu:group:oc_xxxxxx ││ └─ agent:feishu_cio:cron:xxx │└──────────────────────────────────────────────────────────────┘
关键点解读:
-
• Tenant 是企业层,所有用户都属于一个企业 -
• App 是飞书开放平台的应用,每个 agent 绑一个 app -
• Agent 是 OpenClaw 视角下的智能体,与 appId 一一对应 -
• open_id 是 app 维度生成的,所以同一个用户在 10 个 agent 下字符串完全不同 -
• union_id 是 Tenant 维度生成的,所以跨 10 个 app 都一样
📌 提示:本节 ASCII 框图是 §〇 mermaid 全景图的文字版。读图请优先看 §〇。
三、各 ID 的使用场景
3.1 配置 cron job 的 to 字段
最常见场景,也是最容易踩坑的场景。
{ "delivery": { "mode": "announce", "channel": "feishu", "to": "user:ou_aaaa1111aaaa1111aaaa1111" // ✅ 私聊给"您" }}
to 字段的合法值:
to
|
|
|
|---|---|---|
user:ou_xxx |
私聊
|
user:ou_aaaa1111aaaa1111aaaa1111 |
user:oc_xxx |
群发
|
user:oc_xxxxxx9999xxxxxx9999xxxxxx |
oc_xxx
|
|
oc_xxxxxx9999xxxxxx9999xxxxxx |
最关键的检查清单:
-
1. ou_xxx必须是 cron job 所属 agent 视角下的 ID(不是其他 agent 下的) -
2. oc_xxx必须是 bot 已经在的群(bot 不在群里 → 报 230002 错误) -
3. 必须带 user:前缀(私有和群都是user:xxx形式)
怎么找正确的 to 值:
# 1. 在对应 agent 的 session 文件里找(以 feishu_cio 为例)cat ~/.openclaw/agents/feishu_cio/sessions/sessions.json | \ python3 -c "import json, sys, red = json.load(sys.stdin)for k in sorted(d.keys()): if 'feishu:direct:ou_' in k: m = re.search(r'feishu:direct:(ou_[a-f0-9]{20,32})', k) if m: print(f' user:{m.group(1)}')"# 输出: user:ou_aaaa1111aaaa1111aaaa1111
如果 cron job 在 feishu_cio 下,但 to 填的是 feishu_writer 视角下的 ID:
-
• 飞书返回 230002 / 230020 -
• 必须改成 feishu_cio视角下对应的ou_xxx
3.2 调用飞书通讯录 API 反查用户
场景:拿到一个 ou_xxx,想知道它是谁。
注意:
-
• 必须用对应 agent 的 appId/appSecret 拿 token -
• 跨 app 的 open_id 在 token 视角下查不到(HTTP 400)
# 用 feishu_cio 视角查app_id = "cli_xxxxxxxxxxxxxxxx" # 从 ~/.openclaw/openclaw.json 读 feishu_cio 对应的 appIdapp_secret = "***" # 同上# 1. 拿 tokenimport requeststoken = requests.post( "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", json={"app_id": app_id, "app_secret": app_secret}).json()["tenant_access_token"]# 2. 查 open_idr = requests.get( f"https://open.feishu.cn/open-apis/contact/v3/users/{open_id}?user_id_type=open_id", headers={"Authorization": f"Bearer {token}"}).json()# 返回:# {"code": 0, "data": {"user": {"name": "您", "open_id": "ou_aaaa...", "union_id": "on_xxxxxx", "mobile": "+86xxxxxxxxxxx"}}}
三种 ID 都能作为查询路径:
# 用 open_id 查GET /contact/v3/users/ou_xxx?user_id_type=open_id# 用 union_id 查(同一个用户跨 10 个 app 都用这个 union_id)GET /contact/v3/users/on_xxx?user_id_type=union_id# 用 user_id 查(部分企业才有)GET /contact/v3/users/u_xxx?user_id_type=user_id
3.3 跨 agent 调度用户
场景:feishu_cio 想给 wuzhao 视角下”您”发消息。
错的做法:
// 在 feishu_cio 视角下拿到的 ou_xxx,发给 wuzhao → 必死{ "channel": "feishu", "accountId": "wuzhao", "to": "user:ou_aaaa1111aaaa1111aaaa1111" // ❌ 这是 feishu_cio 视角的 ID}
对的两种方式:
方式 A:直接用目标 agent 视角下的 open_id
{ "channel": "feishu", "accountId": "wuzhao", "to": "user:ou_ffff6666ffff6666ffff6666" // ✅ wuzhao 视角下的"您"}
方式 B:先查 wuzhao 的 session 文件得到正确 ID
cat ~/.openclaw/agents/wuzhao/sessions/sessions.json | \ python3 -c "..." | grep "ou_" # → 拿到 ou_ffff6666...
现实场景:为什么您会需要”跨机器人调度”?
-
• 业务场景:feishu_cio(情报机器人)发现了一条告警,希望通过 wuzhao 机器人转发给”您” -
• 技术场景:feishu_writer 写了一篇报告,希望通过 feishu_product 机器人推送给”您” -
• 共同点:feishu_cio / feishu_writer 自己不能直接发(它们的 bot 跟”您”的关系是隔离的)——必须切换到目标机器人视角找到对应的 open_id
3.4 Session Key 解析
场景:从 OpenClaw session 名称反推是哪个 agent 的哪个私聊。
agent:feishu_cio:feishu:direct:ou_aaaa1111aaaa1111aaaa1111 │ │ │ │ │ │ │ └─ peer open_id(feishu_cio 视角下) │ │ └─ direct=私聊 / group=群 │ └─ channel: feishu └─ agentId
变体:
-
• agent:<id>:cron:<job_id>— 定时任务 session -
• agent:<id>:subagent:<run_id>— 子智能体 session -
• agent:<id>:feishu:group:oc_xxx— 群 session -
• agent:<id>:main— 主 session(agent 自身后台用)
3.5 通过 union_id 关联跨 app 用户
场景:想知道 feishu_cio 视角下的”您”和 wuzhao 视角下的”您”是不是同一个人。
# 1. 在 feishu_cio 视角查"您"user_a = query_contact("ou_aaaa1111aaaa1111aaaa1111", token_cio)union_id = user_a["union_id"] # on_xxxxxxxxxxxxxxxxxxxxxxxx (跨 10 个 app 都一样)# 2. 用 union_id 去 wuzhao 视角查user_b = query_contact(union_id, token_wuzhao, user_id_type="union_id")# → 返回 wuzhao 视角下的 open_id: ou_ffff6666ffff6666ffff6666
这是唯一能”识别同一个人”的方法:
-
• 您拿到 ou_aaaa...不知道是员工 A 还是员工 B -
• 通过通讯录 API 查 union_id -
• 然后用 union_id在其他 app 视角下反查 open_id -
• 把这些 open_id 关联到同一个员工档案里
3.6 端到端实战:从您发消息到 OpenClaw 处理的完整链路
场景:您在飞书客户端里给 feishu_cio 机器人发了一条消息:「今天天气怎么样?」
完整链路图(每条线都标了用到的 ID):

12 步文字拆解(每步标了用到的 ID):
|
|
|
|
|---|---|---|
|
|
您
|
union_id: on_xxxxxx
|
|
|
|
appId: cli_xxxxx |
|
|
|
message_id: om_xxxxxxxxxxxxxxxxxxxxxx |
|
|
|
open_id: ou_aaaa1111aaaa1111aaaa1111
|
|
|
|
message_id / sender.open_id / chat_id / chat_type=direct |
|
|
OpenClaw Gateway
|
|
|
|
|
cli_xxxxx
accountId: feishu_cio |
|
|
|
message_id |
|
|
|
accountId = agentId: feishu_cio |
|
|
加载/创建 session
|
agent:feishu_cio:feishu:direct:ou_aaaa1111aaaa1111aaaa1111session 文件:~/.openclaw/agents/feishu_cio/sessions/sessions.json |
|
|
feishu_cio agent 调飞书 API 拿 token | POST /auth/v3/tenant_access_token/internal
{app_id: cli_xxxxx, app_secret: ***}返回:tenant_access_token(2 小时有效) |
|
|
|
GET /contact/v3/users/ou_aaaa1111aaaa1111aaaa1111?user_id_type=open_id
Authorization: Bearer <tenant_access_token>返回:{name, union_id: on_xxxxxx, mobile: +86xxxxxxxxxxx} |
|
|
|
|
|
|
LLM 思考 + 生成回复文本 |
|
|
|
|
POST /im/v1/messages?receive_id_type=open_id
Authorization: Bearer <tenant_access_token>body:{receive_id: "ou_aaaa1111aaaa1111aaaa1111", msg_type: "text", content: {...}}⚠️ receive_id 必须用 open_id,不能用 union_id(坑 4 强调过) |
|
|
|
|
ID 用量速查表(一次端到端流程涉及的全部 ID):
|
|
|
|
|
|---|---|---|---|
union_id |
on_xxxxxx |
|
|
appId |
cli_xxxxx |
|
|
appSecret |
*** |
|
|
tenant_access_token |
t-xxxxxx |
|
|
open_id |
ou_aaaa1111aaaa1111aaaa1111 |
|
5 |
chat_id |
oc_xxxxxx |
|
|
message_id |
om_xxxxxx |
|
|
agentId
accountId |
feishu_cio |
|
|
session key |
agent:feishu_cio:feishu:direct:ou_aaaa... |
|
|
chat_type |
direct |
|
|
如果换成群消息:唯一区别是
-
• chat_type = group -
• chat_id = oc_xxxxxx(群的 chat_id) -
• session key 变成 agent:feishu_cio:feishu:group:oc_xxxxxx -
• 步骤 15 的 receive_id改成oc_xxxxxx(群 ID),仍不是用 union_id
一眼记住:open_id 出现 5 次——它是这条链路里最忙的 ID,理解链路就抓住它。
四、可能遇见的坑
坑 1:cron job to 字段填错
症状:
OutboundDeliveryError: Feishu send failed{"feishu_code": 99992360, "feishu_msg": "Invalid ids: [feishu_cio]"}
根因:to 字段填的是 agentId/accountId,不是用户的 open_id。
修法:
-
• 私聊: to: user:ou_xxx(带user:前缀 + 用户 open_id) -
• 群发: to: user:oc_xxx(带user:前缀 + 群 chat_id)
坑 2:cron job to 用群 chat_id 但 bot 不在群里
症状:
OutboundDeliveryError: Feishu send failed{"feishu_code": 230002, "feishu_msg": "Bot/User can NOT be out of the chat."}
根因:
-
• to: oc_xxx是个群 chat_id -
• 但这个飞书 bot 没被拉进这个群 -
• 飞书不会自动加 bot,必须先在飞书群聊里 @ 这个机器人
修法(二选一):
-
• 在飞书里把机器人拉进群( oc_xxx必须是 bot 已经在的群) -
• 改成发私聊( to: user:ou_xxx)
坑 3:跨 agent 混用 open_id
症状:
feishu_code: 230020feishu_msg: "user not exist" 或 "chat not exist"
根因:
-
• A agent 视角下的 ou_xxx填到了 B agent 的 cron job 里 -
• B agent 的飞书应用不识别这个 ID(飞书的设计隔离)
举例:
# ❌ 错feishu_writer 的 cron job 里 to 写 ou_aaaa1111aaaa1111aaaa1111# 这个 ID 是 feishu_cio 视角下的"您"# feishu_writer 的飞书 app 不认这个 ID# ✅ 对feishu_writer 的 cron job 里 to 写 ou_cccc3333cccc3333cccc3333# 这个 ID 才是 feishu_writer 视角下"您"
修法:
-
• 找到目标 agent 视角下的正确 open_id(查它的 session 文件) -
• 千万不要直接复制别处看到的 ou_xxx
坑 4:用 union_id 当 receive_id
症状:
feishu_code: 230002feishu_msg: "Invalid receive_id"
根因:
-
• im/v1/messages的receive_id只接受open_id或chat_id(群 ID) -
• 不接受 union_id或user_id -
• 哪怕 union_id 才是”用户统一标识”,发消息时也只能用 open_id
修法:
-
• 用 GET /contact/v3/users/{union_id}?user_id_type=union_id反查 open_id -
• 然后用 open_id 发消息
五、怎么提取openid
你在飞书里直接让机器人帮你提取就行
prompt:请你把我openclaw里的每个飞书机器人视角的 open_id整理成表格
六、最佳实践
6.1 配置 cron job 推送前的检查清单
-
• [ ] to字段格式正确(user:xxx) -
• [ ] ou_xxx来自对应 agent 的 session 文件(不是凭印象或别处的) -
• [ ] oc_xxx群,bot 已经被拉进群(oc_xxx必须是 bot 实际在的群) -
• [ ] bestEffort字段按需设置(true失败不告警 /false失败告警) -
• [ ] 测试推送:手动 trigger 一次,验证能收到
6.2 跨 agent 调用 open_id 时的安全检查
def safe_query(open_id, expected_agent): """跨 agent 查询时,先确认 open_id 来源""" # 1. 在 expected_agent 的 session 文件里找 sess_path = f"~/.openclaw/agents/{expected_agent}/sessions/sessions.json" # ... 找到则合法 # 2. 找不到 → 跨 app 错配,不可用 raise ValueError(f"open_id {open_id} 不属于 {expected_agent}")
6.3 写飞书集成代码的契约
永远不要假设:
-
• ❌ “用户只有一个 open_id” → 不同 app 下有多个 -
• ❌ “我可以从 inbound metadata 直接拿 appId” → 那个是 agentId,appId 在配置里 -
• ❌ “open_id 是稳定的” → 同一用户在 app 升级后可能变
永远要做的:
-
• ✅ 用 union_id 关联跨 app 用户 -
• ✅ 查通讯录 API 前先拿对应 app 的 token -
• ✅ session 文件里找不到的 ID 先校验
6.4 调试时的思考顺序
遇到”消息发不出去”时,按这个顺序排查:
-
1. to 字段格式对吗?( user:ou_xxx/user:oc_xxx) -
2. ou_xxx 是哪个 agent 视角下的?(必须和 agent 匹配) -
3. oc_xxx 群,bot 在群里吗?(飞书后台 → 群设置 → 机器人) -
4. union_id 拼接过吗?(不能直接当 receive_id) -
5. 消息内容超长吗?(超过限制 → 拆/换卡片) -
6. token 过期吗?(2 小时重新拿) -
7. rate limit 触发了吗?(加 sleep / 错峰)
夜雨聆风