Telegram 机器人重复回复:先去重 update,再检查模型与发送
按 Telegram Bot API 的 update_id、轮询 offset 与 Webhook 重投规则,拆开事件接收、模型生成和消息发送,处理重启、并发与发送结果不明。

群里只问了一次,机器人却回了两遍。先别急着认定模型重复生成:同一条平台事件可能被再次投递,应用可能启动了多个处理进程,发送成功后的回执也可能丢失。接收事件、生成答案和发出消息要分别记录,重复发生在哪一步,修复就放在哪一步。
本文于 2026 年 11 月 10 日查阅 Telegram Bot API,页面最新变更为 11 月 9 日的 Bot API 10.4。本文使用公开教学事件说明设计,不连接真实 Bot,也没有复现某个机器人框架缺陷。群机器人方向沿用选题库,并扩展到已有 Telegram 接入教程的后续使用问题。
Telegram 明确提供了去重线索
官方 Update 定义说明,update_id 可以帮助忽略重复更新和恢复顺序。轮询和 Webhook 的确认方式不同:
getUpdates:当下一次调用的offset高于某条更新的update_id,该更新被视为确认。官方要求每次响应后重新计算 offset,避免重复取到旧更新。- Webhook:Telegram 向配置地址投递更新;非
2XY响应会触发重试,次数由平台决定,不能编一个固定重试时间表。
两者不能同时作为同一个 Bot 的有效接收方式;官方说明,有 Webhook 时不能使用 getUpdates。切换方式前先检查当前配置,不要为了“清理重复”直接丢弃所有待处理更新。
update_id 通常递增,但官方也说明,至少一周没有新更新后,下一个编号会随机选择。因此不要把某个永久最大值当成所有未来事件的可靠过滤条件。按事件身份去重,与把流重新排序,是两件事。
在调用模型之前,先可靠接住事件
推荐的职责划分是:先按平台要求验证来源与数据结构,再以 Bot 标识 + update_id 接收事件,持久写入待办,最后交给后台处理。不同 Bot 的编号不应混在一个全局去重空间里。
这里的先后顺序很关键。收到 Webhook 就立即回成功,但待办只在内存里,程序崩溃后会丢任务;一直等模型生成并发完消息再回应,又会把慢任务拖进投递时限。更合理的边界是:可靠保存待办后确认接收,后续失败由自己的任务系统恢复。
去重记录与待办创建应在同一可靠事务边界内完成。并发收到同一更新时,只有一个处理者成功创建任务;已可靠接收的重复投递返回相应成功确认,不再重复调用模型。数据库唯一约束是实现原子接收的一种方式,具体设计按系统选择。
教学记录可以保留如下结构;它不是 Telegram 的原生 Update 格式:
{
"event_key": "demo-bot:1001",
"state": "accepted",
"generation_ref": null,
"send_state": "not_started",
"sent_message_ref": null
}
只在内存里保存 seen_ids 不够,重启和多实例都会让它失效。任务处理中断后也要支持重新领取;不能为了恢复任务直接删除接收记录,否则重投可能变成另一次完整执行。
去重事件以后,还要管理发送结果
模型只生成一次,不代表消息只发送一次。假设发送请求已经被 Telegram 接受,但本地没收到响应,程序可能不知道消息是否存在。输入侧去重解决不了这个窗口。
成功收到发送结果时,保存对应消息标识并继续。结果不明时,标记 pending_confirmation,使用系统可取得的记录排查;无法可靠确认就暂停或交给人处理。不要假设 Telegram sendMessage 接受任意业务幂等头,也不要用模型重新生成一次答案来掩盖发送状态不明。
工作流副作用恢复解释了同类业务问题。不同平台的事件 ID、确认机制和消息查询能力各不相同,不能把本篇的 update_id 直接搬到飞书或 QQ。
用这四种情况检查自己的实现
在隔离测试 Bot 或本地事件替身中验证:同一更新连续投递、两个处理者并发接收、保存待办后重启、发送结果不明。检查任务数、模型调用数、发送尝试数与最终可见消息数;这几个数字要分别看,不能只数群里的气泡。
本文没有运行上述平台验收,也没有足够近期中文原帖证明这是新一轮普遍故障。它是根据官方投递契约设计的长期恢复方法。已有 Hermes Telegram 接入教程可作为前置阅读;模型端按连接基础配置。
使用 Nbility 时,用量日志能帮助查是否出现多次模型请求,但不能替代 Telegram 的接收和发送记录。按脱敏日志模板保留事件关联和阶段状态,去掉 Bot token、聊天正文和真实群成员信息,再判断重复发生在哪一步。


