钉钉接 AI 有三条路,选错不是"多写点代码",而是上线后收不到消息、被限流、或者数据边界说不清
| 维度 | 方案 A:企业内部机器人 + Stream | 方案 B:自定义群机器人 Webhook | 方案 C:第三方 SaaS 托管 |
|---|---|---|---|
| 收消息 | ✅ 单聊/群聊都可收可发 | ❌ 只能往群里发 | ✅ 平台代收代发 |
| 要不要公网地址 | 不用:服务主动连钉钉长连接 | 不用(只出不进) | 不用 |
| 数据边界 | 消息与回复都在自己服务器 | 推送内容经钉钉服务器 | 对话内容经第三方平台 |
| 上线耗时 | 1–3 天(主要是长连接与卡片调试) | 约 10 分钟 | 半天到 1 天(配置为主) |
| 适合场景 | 正式 AI 助手、客服答疑、流程查询 | 告警播报、日报推送、CI/CD 通知 | 快速验证价值、无开发人力 |
一句话:做对话助手选 A,做通知播报选 B,先验证价值选 C。最常见的错误是拿方案 B 做对话(收不到群里的消息,做不了),或者没评估数据边界就直接上方案 C。
这是钉钉官方推荐的接入方式:你的服务用 Stream 模式主动和钉钉建立 WebSocket 长连接,员工在机器人会话里发消息 → 钉钉通过这条连接把消息推给你的服务 → 服务调 AI → 再通过连接把回复发回去。因为没有入站 HTTP 回调,不需要公网 IP、不需要备案域名、不需要在防火墙上开端口,内网机器也能跑,这是它和"HTTP 回调"路线最大的区别。
钉钉开发者后台 →「应用开发」→「企业内部应用」→ 创建应用,然后在应用里「添加机器人」,消息接收模式选 Stream 模式。记下三个值:
Client ID(原 AppKey):应用凭证页Client Secret(原 AppSecret):应用凭证页(只显示一次,立即存入密钥管理,不要进代码库)robotCode:机器人详情页,发消息时要带上发布前先把可见范围设成一个小部门做灰度,验证通过再放开。
Stream 模式的核心是:服务启动时用凭证换一个临时连接地址,然后连上去、订阅机器人消息主题。连接断了要重连——这是后面坑 1 的根源。
# dingtalk_stream_bot.py —— 建立长连接并订阅机器人消息
from dingtalk_stream import DingTalkStreamClient, Credential
from dingtalk_stream.chatbot import ChatbotHandler, ChatbotMessage
class AIHandler(ChatbotHandler):
async def process(self, callback):
msg = ChatbotMessage.from_dict(callback.data)
# 先回执,再异步处理,别在回调里等 AI(见坑 3)
self.reply_text("收到,正在处理…", msg)
question = msg.text.content.strip()
answer = ask_ai(question) # 你自己的 AI 服务 / 知识库
self.reply_text(answer, msg)
return 200, "OK"
credential = Credential(CLIENT_ID, CLIENT_SECRET) # 从环境变量读,别写死
client = DingTalkStreamClient(credential)
client.register_callback_handler("/v1.0/im/bot/messages/get", AIHandler())
client.start_forever() # 内部含自动重连,但要有进程级守护
调试期最容易踩的坑:话题(topic)写错——机器人消息的 topic 是固定的,写错会表现为"连上了但永远收不到消息",日志里却没有任何报错。
回复会话用机器人消息接口;如果要做"审批进度""工单状态"这类可点击的交互,用互动卡片。两个接口都走同一个 access_token,必须集中缓存(有效期 7200 秒,有获取频率限制)。
# dingtalk_client.py —— token 集中缓存 + 发送机器人消息
import time, requests
_token, _ts = "", 0
def get_token():
global _token, _ts
if _token and time.time() - _ts < 7000: # 留 200 秒余量
return _token
r = requests.post("https://api.dingtalk.com/v1.0/oauth2/accessToken",
json={"appKey": CLIENT_ID, "appSecret": CLIENT_SECRET},
timeout=5).json()
_token, _ts = r["accessToken"], time.time()
return _token
def reply_text(user_id, text):
requests.post(
"https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",
headers={"x-acs-dingtalk-access-token": get_token()},
json={"robotCode": ROBOT_CODE, "userIds": [user_id],
"msgKey": "sampleText", "msgParam": '{"content": "%s"}' % text[:1800]},
timeout=5)
注意最后一行:钉钉的文本消息内容是包在 msgParam 的 JSON 字符串里的,超长要先截断或分段,否则接口直接报错(见坑 5)。
把 ask_ai() 换成你的 RAG 知识库或大模型服务即可。三个工程要点:
conversationId 存最近几轮,别把整段历史无限拼进 prompt群设置 →「智能群助手」→「添加机器人」→「自定义」,拿到一个 Webhook 地址,之后往这个地址 POST JSON 就能往群里发消息(可选加签校验):
curl 'https://oapi.dingtalk.com/robot/send?access_token=你的TOKEN' \
-H 'Content-Type: application/json' \
-d '{"msgtype":"text","text":{"content":"部署完成:订单服务 v2.3 已上线"}}'
它的定位是单向通知:监控告警、CI/CD 结果、每日播报。三个硬限制:收不到群里的消息(做不了对话)、每个机器人每分钟发送条数有限(高频告警必须做聚合)、@ 人要填手机号或 userId(不是昵称)。支持 text、markdown、link、actionCard 等类型,适合"把系统里的事件告诉人",不适合"人问系统答"。
市面上有不少托管平台:授权钉钉后网页配置知识库和话术,不用自己写长连接代码,半天就能上线。适合两种情况:没有开发人力、想先小成本验证 AI 助手有没有价值。
但要在签约前问清四件事,不然日后很难受:
| 维度 | 钉钉 | 飞书 | 企业微信 |
|---|---|---|---|
| 主流接入方式 | 企业内部机器人 + Stream 长连接 | 事件订阅(长连接或 HTTP 回调) | 自建应用 + HTTP 回调 |
| 要不要公网地址 | 不用(长连接主动外连) | 二选一,长连接可免 | 要:必须 HTTPS 回调 |
| 回调时效要求 | 长连接无 5 秒硬限 | HTTP 回调同样要求快速返回 | 5 秒内必须响应,否则重试 |
| 消息去重 | 按消息 ID 去重 | 按事件 ID 去重 | 按 MsgId 去重 |
| 内网部署友好度 | 高:不需要入站端口 | 中 | 低:要暴露回调地址 |
结论:如果公司内网出网受限、又不想为回调单独申请公网域名,钉钉的 Stream 模式是三家里面最好落地的;企业微信的 5 秒回调硬限最需要工程兜底;飞书介于两者之间。多平台一起接的时候,把"收消息 → 调 AI → 回复"抽成一层统一网关,各平台的差异只留在适配层,后面加平台才不会推倒重来。
现象:早上员工问机器人没反应,重启服务就好了。
根因:长连接被中间网络设备掐断,客户端没有重连成功,或者进程因为未捕获异常退出了。
现象:日志显示已连接,员工发消息却毫无反应,也没有报错。
根因:订阅的 topic 写错;或者机器人没有发布、可见范围没包含提问人;或者注册的回调路径与消息类型不匹配。
现象:问题复杂时,同一句话被回答两三次,或者只回了个"收到"。
根因:在回调里同步等 AI,超时后平台重推;或者先回了"收到"却没有后续消息。
现象:故障期间告警刷屏,最关键的几条反而没到群里。
根因:自定义群机器人有每分钟发送条数上限,超了直接丢弃或报错。
现象:AI 回答长一点,员工端收不到或只有前半截,接口报参数错误。
根因:文本内容包在 msgParam 的 JSON 字符串里,超长或引号未转义都会失败。
json.dumps 生成 msgParam 而不是手拼字符串;超长内容按段落切分成多条,或只回摘要 + 链接到知识库页面。三种方案没有绝对优劣,选择取决于三件事:数据边界要求多高、有多少开发人力、要解决什么场景。通知播报别上方案 A 浪费人力;正式对话助手别用方案 B(收不到消息);上方案 C 之前把数据边界四问问清楚。多数团队的真实路径是:先用 C 或 B 快速验证,验证成功后迁到方案 A 做长期自营。
如果只需要一个结论:做对话选 A,做通知选 B,做验证选 C——别混用。