--- title: "我如何打造 DigiKedai Telegram AI 客服机器人" description: "一个全天候解答顾客问题、并在 Telegram 和 WhatsApp 上自动开通免费试用账号的 AI 客服机器人——TypeScript、grammY、Cloudflare 隧道 webhook,以及自托管的 LiteLLM 网关。" pubDate: 2026-09-06 category: case-studies tags: [telegram, docker, cloudflare, litellm, typescript, ai] ogImage: /og/how-i-built-the-digikedai-telegram-bot.png banner: /banners/how-i-built-the-digikedai-telegram-bot.png --- Digi Kedai 销售数字产品——在线课程、电子书、模板——每一单都会带来一连串同样的顾客问题:*「这门课有免费试看吗?」「我怎么拿到账号?」「我该买哪个套餐?」*。对一个人的生意来说,人工回复根本忙不过来。于是我打造了一个 AI 客服机器人,全天候解答这些问题,甚至能在无人介入的情况下自动发放免费试用账号。 这篇讲的是它是怎么搭起来的——架构、LLM 接线,以及那些各自吃掉我一整个下午的 bug。 ## 为什么企业需要一个这样的机器人 讲架构之前,先说*为什么*。客服机器人不是噱头——它改变了小生意的经济学: - **省钱省人力。** 机器人每回答一个常规问题,你的团队就少答一个。Digi Kedai 用雇一个客服几分之一的成本,就得到了一个全天候的客服专员。 - **全天候即时响应。** 顾客会在半夜和周末提问。机器人几秒钟内用他们的语言回复——没有排队,没有「我们稍后回复」。 - **把浏览者变成买家。** 机器人不只是回答——它还会*向上销售*。一句「有免费试用吗?」几下点击就变成一个已领取的试用账号,全程无需人工。 - **永远记得顾客。** 长期记忆让回头客被当作熟客问候,而不是陌生人。 - **随商品目录扩展。** 加一个商品,机器人就已经认识它了——无需重新训练,无需新的 FAQ 页面。 对 Digi Kedai 这样一个人的生意来说,这就是在凌晨两点丢单和成交之间的差别。 ## 技术栈 - **TypeScript + Node 20**,作为单个容器跑在我的 Synology NAS 上。 - **grammY**——一个轻量的 Telegram Bot 框架,用 webhook 模式接线。 - **Hono**——一个极小的 HTTP 服务器,负责接收 webhook 并响应 `/health`。 - **Cloudflare 隧道**——`bot.digikedai.com` 直连容器;无需公网 IP,无需开放端口。 - **LiteLLM**——一个位于真实模型之前的自托管网关,这样我可以不碰机器人代码就切换供应商或加故障转移。 - **PostgreSQL**——对话与用户记忆存储,与我的 mem0 栈共用。 ## 架构 ``` 顾客 → Telegram/WhatsApp → Cloudflare 隧道 → bot.digikedai.com → Hono POST //webhook → grammY 处理器 → 存入 PostgreSQL → Agent.respond(系统提示词 + 检索到的商品 facts + 最近 20 条消息) → LiteLLM(模型别名 "mem0-openai")→ 持久化 + 回复 ``` 几个值得解释的决策: **Cloudflare 隧道直连容器,不经过 Traefik。** 我的 homelab 在 CGNAT 后面,没有隧道就没有任何东西能被公网访问。webhook URL 藏在一个**秘密路径段**(`//webhook`)后面,这样 Telegram 的更新只有知道这个 secret 的人才能送达——这是在 Telegram 自带 token 认证之外的第一道廉价防线。 **模型用 LiteLLM 的*别名*寻址,从不用原始模型名。** 机器人调用 `mem0-openai`;LiteLLM 把它映射到真实模型(背后还有 OpenRouter 故障转移)。机器人永远不需要知道实际是哪个供应商在服务请求。我设了 `temperature: 0.4` 和 `45s` 超时。 **机器人知道自己的商品目录,但不含价格和内部路径。** 一个构建时的生成器把单一的 `catalog_sku.csv`(唯一数据源)转成机器人导入的 TypeScript 模块——只含 SKU、名称、分类、大小和商品 URL,*别的什么都不带*。价格从不定死(「以页面当前价为准」),内部资源路径从不会进机器人镜像。这让打包体积精简,也防止模型泄露内部结构。 ## 检索:机器人如何真正*知道*商品目录 把整个目录塞进提示词里的通用 LLM 会瞎编。所以机器人先检索、再回答。它有一个三档检索器: 1. **SKU token 匹配**——`/\b[A-Z]{2,}\d{2,}\b/gi` 能精确命中 `CZH01` 这样的 SKU。 2. **整句子串匹配**——针对短而精确的查询。 3. **分段匹配**——切分 CJK 连续串(剥离「有/吗/哪些」这类疑问助词),并过滤英文停用词。 前 5 个匹配结果成为注入系统提示词的「facts」,模型被要求*只*基于实际检索到的内容回答——查不到时也要明说。 这比看起来更重要。顾客问*「CZH01 有免费版吗?」*之所以能得到关于 `FREECZH01` 的正确回答,是因为检索器在命中付费 SKU 时,会**自动附带它的免费 twin** 并排在第二位。这个细节把一句「抱歉,没有」变成了正确的向上销售。 ## 免费试用开通——全程无需人工 我最引以为豪的部分:机器人不只是*谈论*免费试用,而是真正*发放*它们。顾客有三种方式触发——`/trial` 命令、像*「我要这个试用:digikedai.com/products/czh01」*这样的自然语言消息,或者 `?start=CZH01` 这样的深链。 机器人不经过 n8n,而是**直接写入 NocoDB**——插入一条顾客记录和一条顾客-商品记录。我已有的 webhook 接手并自动开通真实账号,所以机器人从不碰文件服务器。整个过程是幂等的:如果顾客已有账号,就复用它而不是新建重复的;如果商品插入失败,就回滚顾客记录,这样重试不会卡死。 甚至还有多账号选择器。如果同一个 Telegram 用户有好几个账号,机器人会把它们列成内联按钮,问要把试用挂到哪个账号上。 ## 那些各自吃掉我一整个下午的 bug 上线并不顺利。有三个值得讲的调试故事: **1. 无限回复循环。** 如果服务器没在超时窗口内确认,Telegram 会重新投递同一条 webhook 更新。我的处理器跑得久(LLM 延迟),于是 Telegram 重发了同一条消息——机器人就一遍又一遍地回答它。修复是 `onTimeout: "return"` 加 50 秒窗口,这能让重投递的螺旋戛然而止。 **2. 模型别名 400。** 直接调用原始模型名(`gpt-5-mini`)返回 400。只有 LiteLLM 别名能通。这现在是仓库里的一条硬性规则:*LLM 必须用别名,从不用原始上游名。* **3. WhatsApp 的「m_text」bug。** 当我加 WhatsApp 通道(通过一个把 WhatsApp Web 事件 POST 到 webhook 的浏览器扩展)时,一个「自定义 webhook payload」设置里空字符串的模板值让扩展把字面字段*名* `m_text` 当成了消息文本——于是机器人回复*「我不明白 m_text」*。修复是关掉那个设置、改用默认 payload,适配器就能正确读取了。 三个故事共同的诚实结论是:故障不在难的部分——LLM 或检索。而在 **webhook 生命周期和 payload 契约的细节**,那些集成真正出问题的无聊边角。 ## 在 LLM 之上叠加确定性流程 LLM 擅长开放式问题,却很不擅长*状态*。所以那些需要可靠的对话环节——购买、试用领取、深链——都是**确定性状态机**,而非提示词工程: - **购买意图**(`order`、`want to buy`、`buy`…)在进入 LLM 之前就被拦截,驱动一个内联键盘(网店 → 管理员 → 返回),从不给自由文本回复。 - **深链**把 SKU 放在 `?start=` payload 里,但 Telegram 那里只允许 `A-Z a-z 0-9 _ -`——一个 `:` 会静默地把它弄坏。我吃过大亏,于是把分隔符从 `buy:SKU` 换成了 `buy-SKU`。 - **WhatsApp 里的跑题消息**会让模型输出一个哨兵值(`NO_REPLY`),代理把它变成*「什么都不发」*——这样机器人在没话可说时保持沉默,而不是胡言乱语。 ## 我会做不同的地方 - **启动时就加 `setMyCommands()`。** 命令列表定义在代码里,但我从没在 Telegram 注册它,所以聊天框里的命令菜单一直是空的,直到我用 Bot API 修好。小事,但因此损失了一个真实的顾客触点。 - **在写通道适配器之前先定好 payload 契约。** WhatsApp 的 `m_text` bug 就来自我信任了一个没读过契约的自定义功能。 - **把 webhook 超时当作一等架构**,而不是事后补丁——如果我在第一天就想到确认窗口,重投递循环本可以避免。 ## 结果 现在 NAS 上的一个容器就能跨两个通道(Telegram + WhatsApp)解答顾客问题,按 SKU 或自然语言检索到正确商品,并端到端发放免费试用账号——还有一套约 175 个单元测试覆盖确定性流程、检索和 payload 契约。源码在 [git.hoelee.com/hoelee/digikedai-bot](https://git.hoelee.com/hoelee/digikedai-bot)。 如果你也在打造一个 LLM 驱动的 Telegram 机器人,教训其实很朴素:模型是最容易的部分。webhook 生命周期、payload 契约、以及开通流程的幂等性才是真正会出问题的地方——先设计好这些。 --- ## 想为你的生意做一个这样的机器人吗? 我为企业定制 Telegram/WhatsApp AI 机器人、网站,以及自托管基础设施。如果一个这样的机器人能为你省时省钱——或者你想雇佣我——我非常乐意交流: - 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969) - 📧 **邮箱:** [me@hoelee.com](mailto:me@hoelee.com) - 🌐 **网站:** [hoelee.com](https://hoelee.com)