1. 为什么要在 Telegram 机器人里塞进一个 AI 智能体如果你正在用 TypeScript 写 Telegram 机器人大概率已经体会过 grammY 的顺手类型推导完整、中间件模型清晰、插件生态够用。但真要把机器人从「关键词回复机」升级成「能理解上下文、能调用工具、能编排任务」的 AI 智能体很多人会卡在同一个地方——消息框架和智能体框架是两套东西怎么把它们缝在一起缝完之后鉴权、模型通道、会话隔离又该怎么管。OpenClaw 是一个面向多平台消息应用的 AI 智能体框架定位就是帮你把对话、工具调用、任务编排这些能力封装好让机器人具备「智能体」而不是「问答接口」的行为。grammY 则是 Telegram Bot API 生态里最受欢迎的 TypeScript 框架之一。把两者集成本质上是让 grammY 负责「收消息、发消息、管中间件」让 OpenClaw 负责「想事情、调工具、维护上下文」。这套组合适合谁我梳理了三类第一类是做社群运营工具的开发者机器人要能回答产品问题、查订单状态、按用户身份给不同答案纯规则匹配已经写不动了。第二类是做个人助理机器人的希望机器人记住每个用户的偏好还能调用天气、日历、搜索这类外部工具。第三类是把 Telegram 当交互入口、后端接自己业务系统的团队需要一个统一的智能体层来收敛模型调用和工具注册。真正动手时最容易被低估的不是集成代码本身而是模型通道的鉴权管理。OpenClaw 要调模型就得有 API Key、Base URL、Model ID 这三样东西。如果你同时用 Claude、GPT 或者国产模型Key 散落在各个环境变量里换模型要改代码、改配置、重启服务调试成本很高。这篇会用一个统一的 API 通道来收敛这件事让 OpenClaw 的模型配置只认一组 Base URL Key模型切换只改一个 Model ID 字符串。下面从环境准备一路走到本地跑通对话链路每一步都给可复制的代码和配置。你不需要先读完所有章节跟着 §3 的配置片段和 §4 的验证步骤走就能先跑起来一条最小闭环。2. TaoToken 前置把模型通道收敛成一组 Key在写集成代码之前先把模型通道这件事定下来。OpenClaw 初始化时需要指定 provider、model、apiKey如果你直接用各家官方通道意味着每换一个模型就要动一次配置。更麻烦的是Telegram 机器人通常是长期运行的服务改配置就得重启调试期间反复重启很烦。我试过把模型通道统一到一个兼容 Anthropic 协议的中转层OpenClaw 侧只认 Base URL 和 Key模型切换只改 Model ID。这样做的直接好处是本地开发、测试环境、线上环境可以用同一套配置结构只是 Key 不同换模型不用改 OpenClaw 的初始化逻辑只改一个字符串。具体来说你需要准备三样东西Base URL指向统一的 API 入口OpenClaw 的 Anthropic provider 会往这个地址发请求API Key在控制台生成的密钥用于鉴权Model ID你要调用的具体模型标识比如 Claude 系列或其它兼容模型获取路径很直接打开 https://taotoken.net/api 了解接口规范然后到 https://taotoken.net/api-keys 生成你的 Key。生成后先复制保存页面上通常只展示一次。如果你还没决定用哪个模型可以先到 https://taotoken.net/models 看看当前支持的模型列表和对应的 Model ID 写法避免拼错。这里有个容易踩的坑很多人把 Base URL 写成带/v1或者带具体路径的形式结果 OpenClaw 内部再拼一次路径变成双斜杠或者路径重复报 404。正确的做法是看 OpenClaw 的 provider 文档确认它期望的 Base URL 是根地址还是带版本号的地址。一般来说Anthropic 兼容通道的 Base URL 填到域名层级即可具体路径由 SDK 自己拼。另一个坑是 Key 的存放。不要把 Key 硬编码在agent.ts里也不要把.env提交到 Git。用dotenv加载.gitignore里加上.env。如果你用 Docker 部署用环境变量注入不要打进镜像层。配置好之后你的.env大概长这样TELEGRAM_BOT_TOKEN123456:ABC-DEF... TAOTOKEN_API_KEYsk-xxxxxxxxxxxxxxxx TAOTOKEN_BASE_URLhttps://taotoken.net/api OPENCLAW_MODEL_IDclaude-sonnet-4-20250514注意TAOTOKEN_BASE_URL这里我填的是https://taotoken.net/api不带尾部斜杠。如果你的 OpenClaw 版本要求带/v1按它的文档调整但不要两个都带。Model ID 先用一个你确认可用的后面验证通过再换。这一步做完模型通道就收敛成了一组环境变量。接下来写代码时OpenClaw 的初始化只读这三个值不再关心背后是哪家模型。3. 可复制配置OpenClaw grammY 的初始化与封装这一节给完整的可复制配置。先建项目、装依赖然后分三个文件组织agent.ts负责 OpenClaw 智能体bot.ts负责 grammY 机器人index.ts负责启动。这样拆分的好处是后面加工具、改模型、换中间件都不会互相污染。先初始化项目mkdir openclaw-grammy-demo cd openclaw-grammy-demo npm init -y npm install grammy openclaw dotenv npm install -D typescript ts-node types/node npx tsc --inittsconfig.json里确认target至少是ES2020module用commonjs或nodenext都行strict建议打开grammY 的类型推导在 strict 下体验更好。然后是agent.ts。这里的关键是把模型配置从环境变量读进来并且把 Base URL 传给 OpenClaw 的 provider。不同版本的 OpenClaw 配置字段名可能略有差异核心是baseURL、apiKey、model三个import { OpenClaw } from openclaw; import dotenv/config; const apiKey process.env.TAOTOKEN_API_KEY; const baseURL process.env.TAOTOKEN_BASE_URL; const modelId process.env.OPENCLAW_MODEL_ID; if (!apiKey || !baseURL || !modelId) { throw new Error(缺少 TAOTOKEN_API_KEY / TAOTOKEN_BASE_URL / OPENCLAW_MODEL_ID); } export function createAgent() { return new OpenClaw({ model: { provider: anthropic, model: modelId, apiKey, baseURL, }, systemPrompt: 你是一个运行在 Telegram 上的助手回答简洁、准确必要时调用工具。, }); }如果你的 OpenClaw 版本把baseURL放在顶层而不是model里按它的类型定义调整但值不变。写完后用npx tsc --noEmit检查一遍类型能提前发现字段名写错。接着是bot.ts。这里做三件事创建 Bot、注册消息处理、把 OpenClaw 的调用封装成一个带错误兜底的函数。会话隔离用 Telegram 的from.id作为sessionId这样每个用户的上下文互不干扰import { Bot, Context } from grammy; import { createAgent } from ./agent; export function createBot(token: string) { const bot new Bot(token); const agent createAgent(); bot.catch((err) { console.error(grammY 全局错误:, err.error); }); bot.on(message:text, async (ctx: Context) { const userId String(ctx.from?.id ?? anonymous); const userText ctx.message?.text ?? ; await ctx.replyWithChatAction(typing); try { const response await agent.chat(userText, { sessionId: userId }); await ctx.reply(response.text); } catch (error) { console.error(OpenClaw 调用失败:, error); await ctx.reply(抱歉我暂时无法处理你的请求请稍后再试。); } }); return bot; }最后是index.tsimport dotenv/config; import { createBot } from ./bot; const token process.env.TELEGRAM_BOT_TOKEN; if (!token) { throw new Error(TELEGRAM_BOT_TOKEN 环境变量未设置); } const bot createBot(token); bot.start(); console.log(OpenClaw grammY 机器人已启动);跑起来用npx ts-node src/index.ts。如果一切正常控制台会打印启动日志Telegram 里给机器人发消息就能收到回复。这里补一个配置对照表方便你核对每个值的来源和写法配置项来源示例值注意TELEGRAM_BOT_TOKENBotFather123456:ABC-DEF不要泄露TAOTOKEN_API_KEY控制台生成sk-xxxx只展示一次TAOTOKEN_BASE_URL接口文档https://taotoken.net/api不带尾斜杠OPENCLAW_MODEL_ID模型列表claude-sonnet-4-20250514拼写准确如果你用 Cline MCP 或者 Codex 的auth.json来管理本地开发凭据记得把 Base URL、Key、Model ID 三件套都写全缺一个都会在请求阶段报鉴权或模型不存在。Cline MCP 的配置里通常有baseUrl、apiKey、model三个字段Codex 的auth.json则可能把 Key 和 Base URL 分开存Model ID 在另一个配置里。不管哪种核对这三样是否一致。4. 验证请求从 /start 到一条完整对话链路配置写完先别急着加工具和命令用最小闭环验证通道是否通。验证分三步机器人能回/start、OpenClaw 能单独调通、消息回环能跑通。第一步启动服务后在 Telegram 里发/start。如果你还没注册start命令先加一个bot.command(start, (ctx) { return ctx.reply(你好我是由 OpenClaw 驱动的 Telegram 机器人。); });收到回复说明 grammY 和 Telegram 的连接没问题。如果没反应先看控制台有没有报错再确认 Token 是否正确、机器人是否被拉黑。第二步单独验证 OpenClaw 能不能调通模型。写一个临时脚本test-agent.tsimport dotenv/config; import { createAgent } from ./agent; async function main() { const agent createAgent(); const response await agent.chat(你好请用一句话介绍你自己); console.log(模型返回:, response.text); } main().catch((err) { console.error(调用失败:, err); process.exit(1); });跑npx ts-node src/test-agent.ts。如果打印出模型返回的文本说明 Base URL、Key、Model ID 三件套都正确。如果报 401说明 Key 无效或没带上如果报 404多半是 Base URL 路径拼错如果报模型不存在检查 Model ID 拼写。第三步回到 Telegram 发一条普通文本比如「帮我写一句 Python 的 hello world」。机器人应该先显示「正在输入」然后返回模型生成的内容。这条链路走通说明 grammY 收消息、OpenClaw 处理、回复发回 Telegram 的闭环成立。验证通过后你可以加一个/ai命令把命令参数直接作为问题方便快速测试bot.command(ai, async (ctx) { const question ctx.match; if (!question) { return ctx.reply(用法/ai 你的问题); } await ctx.replyWithChatAction(typing); try { const response await agent.chat(question, { sessionId: String(ctx.from?.id ?? anonymous), }); await ctx.reply(response.text); } catch (error) { console.error(error); await ctx.reply(处理失败请稍后再试。); } });注意bot.command和bot.on(message:text)的注册顺序。grammY 按注册顺序匹配中间件如果message:text注册在command之前命令消息可能被文本处理器先吃掉。把命令处理器放在前面或者用bot.command的独立匹配机制通常没问题但顺序还是建议命令在前。验证阶段还有一个细节replyWithChatAction(typing)在长回复时能提升体验但它本身也是一次 API 调用如果模型响应很快这个动作可能一闪而过属于正常现象。如果模型响应超过几秒用户会看到持续的「正在输入」体验更好。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth集成过程中最容易卡住的不是业务逻辑而是鉴权和网络层的报错。这一节把几个高频错误对照着讲每个都给定位思路和修复方向。401 Unauthorized。这是最常见的。表现是 OpenClaw 调用直接抛错日志里能看到 401。原因通常有三个Key 没读到、Key 无效、Key 没正确放进请求头。先确认.env里TAOTOKEN_API_KEY有值再确认agent.ts里确实把它传给了 OpenClaw。如果用的是 Cline MCP 或 Codex 的auth.json检查 Key 字段名是否匹配有些工具用apiKey有些用api_key写错就读不到。还有一种情况是 Key 复制时带了空格或换行用trim()处理一下。local proxy failed。这个报错通常出现在你本地配了代理但代理没启动或者端口不对。表现是请求发不出去日志里出现连接被拒绝。排查顺序先确认代理进程是否在跑再确认环境变量HTTP_PROXY/HTTPS_PROXY是否指向正确端口。如果你不需要代理把这两个环境变量清掉再试。注意有些 Node 版本对代理环境变量的处理不一致用axios或fetch时行为可能不同最稳妥的是在代码里显式配置而不是依赖环境变量。reading choices。这个报错一般出现在解析响应体时说明返回的结构和预期不符。常见原因是 Base URL 指向的接口返回了错误页或者非 JSON 内容比如 404 页面被当成 JSON 解析。先看完整响应体确认是不是路径拼错导致返回了 HTML。另一个原因是 Model ID 不被支持接口返回了错误对象而代码直接去读choices字段。修复方式是先打印原始响应再按实际结构解析。OAuth 相关报错。如果你用的是需要 OAuth 的通道可能会遇到 token 过期或 scope 不足。表现是请求被拒提示需要重新授权。处理方式是重新走一遍授权流程确认 scope 包含模型调用权限。如果你用的是 API Key 模式一般不会遇到 OAuth 问题但要注意有些工具会优先读 OAuth 凭据导致你配的 Key 被忽略。检查配置优先级确保 API Key 模式生效。下面这张表把报错、可能原因、修复方向列在一起方便对照报错可能原因修复方向401 UnauthorizedKey 缺失/无效/字段名错检查 .env 和配置字段local proxy failed代理未启动/端口错清代理变量或修端口reading choices响应非 JSON/路径错打印原始响应核对OAuth 报错token 过期/scope 不足重新授权或切 Key 模式排查时有个通用技巧在 OpenClaw 调用外面包一层把请求参数和原始响应都打出来。不要只打error.message很多时候 message 是笼统的原始响应里才有具体原因。比如try { const response await agent.chat(userText, { sessionId: userId }); await ctx.reply(response.text); } catch (error: any) { console.error(原始错误:, error?.response?.data ?? error); await ctx.reply(处理失败请稍后再试。); }这样日志里能看到接口返回的具体错误码和描述定位快很多。6. 语义一致 CTA把通道和文档放在手边跑通最小闭环之后下一步通常是加工具调用、加会话持久化、加多模型切换。这些扩展都依赖同一件事模型通道稳定可用。所以建议你把接入文档和 Key 管理页面放在手边改配置时直接对照。如果你还在验证阶段想先确认模型能不能正常对话可以到 https://taotoken.net/models 用对话界面快速试一条确认 Model ID 和返回格式没问题再回到代码里配。这样能避免在代码里反复试错。如果你准备长期跑编码类或 Agent 类任务比如让 Telegram 机器人调用代码工具、执行多步任务建议了解一下 Coding Plan它在长任务和工具调用场景下更省心https://taotoken.net/coding-plan日常排查和接入配置直接看接入文档https://taotoken.net/doc需要生成或管理 Key到控制台https://taotoken.net/api-keys接口规范以 API 页面为准https://taotoken.net/api最后给一个实用技巧把 Base URL、Key、Model ID 三件套写进一个config.ts所有模块从这里读不要散落在各个文件里。这样换模型、换 Key 只改一处也方便你在 Cline MCP、Codex 的auth.json和 OpenClaw 之间保持语义一致。配置收敛了后面加工具、加命令、加持久化才不会越改越乱。