Windows 上把 OpenClaw 拉起来,飞书那边发消息没回,日志却停在 qwen_client.py 调 Qwen 千问 401。这多半不是飞书机器人坏了,也不是 gateway.py 的 /feishu/webhook 写错,而是第二步 QwenClient 的 base_url 还指着 dashscope 的 text-generation 地址,requests 又多带了一层 /v1。把这条链路改到 TaoToken 统一接入,先去 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建 YOUR_API_KEY,再把 qwen_client.py 的 Base URL 填成 https://taotoken.net/api,模型保留 qwen-turbo,让 QwenClient 先单独跑通,最后回到 gateway.py 的 /feishu/webhook 验证飞书消息。下面按排障顺序拆。
1. OpenClaw 飞书链路断在 qwen_client.py 的 401
1.1 先确认报错发生在 Qwen API 调用之前
飞书发消息之后,OpenClaw 这边如果还能看到POST /feishu/webhook的访问日志,说明飞书事件已经推到了 gateway.py,消息解析也没把程序直接打崩。真正的断点出现在 QwenClient 发起模型请求那一步,日志里常见的是401 Unauthorized、Invalid API key,或者干脆在抛异常前卡一下网络超时。这个顺序很重要:如果连/feishu/webhook都没收到,先去查飞书开放平台的事件订阅和回调地址;如果收到了但 QwenClient 报 401,就不要再反复重启飞书机器人,问题在模型通道的 Key 或 Base URL 上。
原文第二步把self.base_url写成 dashscope 的 text-generation 地址,并在 headers 里用Authorization: Bearer api_key。照着 2.1 去阿里云百炼申请 Key 之后,很多人会把 Key 复制到环境变量,再把地址原样粘进qwen_client.py。这样做本身不算错,但 dashscope 原生接口和 OpenAI 兼容接口的路径写法并不一样,手工拼 URL 时特别容易多一个/v1,或者把/chat/completions拼到 text-generation 后面。飞书链路会直接断在 Qwen API 调用前,表现就是机器人不回复、日志里只有 401 或 404。
1.2 dashscope 的 text-generation 地址和 /v1 为什么会撞车
dashscope 的 text-generation 地址在浏览器里打开是一回事,放到 requests 里拼路径是另一回事。有的文档写/api/v1/services/aigc/text-generation/generation,有的 OpenAI 兼容示例又要求 base_url 以/v1结尾,然后再由 SDK 补/chat/completions。当你把地址抄进qwen_client.py的self.base_url,再在请求时手动拼路径,最终的 URL 可能变成https://某地址/v1/chat/completions,也可能变成https://某地址/api/v1/chat/completions。服务端收到的路径和它认识的路径对不上,就会返回 404;如果 Key 的鉴权方式也不匹配,就会直接 401。
当前这篇排障的核心不是研究 dashscope 每一层路径,而是先把变量锁死。TaoToken 在这里作为统一模型通道出现,Base URL 只填https://taotoken.net/api,末尾不带/v1,也不带任何查询参数。请求路径统一走/chat/completions,让代码回退到最朴素的 OpenAI 兼容写法。这样 QwenClient 只要能返回内容,就说明 Key、地址、模型名三件事至少没有互相打架,后面再回到飞书链路就不会被模型调用层反复干扰。
1.3 TaoToken 统一通道在这类排障里的位置
排障最怕同时改三个变量:Key、地址、模型名。你一边怀疑百炼的 Key 复制错了,一边怀疑 dashscope 地址多写了/v1,还一边怀疑 qwen-turbo 这个模型名是不是过期了,最后只会把 qwen_client.py 改成一团乱麻。更稳的做法是先把模型通道固定下来:打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建一把新 Key,模型广场里确认qwen-turbo还在列表里,然后把qwen_client.py的 Base URL 改成https://taotoken.net/api。这一步不是为了把 OpenClaw 变成另一个项目,而是用一个规范的兼容通道,把 dashscope 直连鉴权和路径写法问题先排除掉。
如果 401 消失、QwenClient 能正常返回一句话,说明飞书链路里的模型调用层已经通了。接下来再去看gateway.py的/feishu/webhook,日志会干净很多:要么是飞书事件订阅没配对,要么是消息格式没解析对,要么是回调地址飞书访问不到。至少不会再出现“明明飞书后台显示推送成功,OpenClaw 却只报 Qwen API 401”这种两头不靠的情况。
2. 把 2.1 的百炼 Key 换成 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建
2.1 Windows 上准备 Key、Base URL 和 qwen-turbo
原文 2.1 让你去阿里云百炼申请 api_key,这里把动作换到 TaoToken。在 Windows 浏览器里打开 TaoToken,注册并登录,进入控制台创建 API Key。复制出来之后不要直接写死在代码里,先在项目根目录建一个.env或者用系统环境变量存起来,变量名可以叫TAOTOKEN_API_KEY,值统一写成YOUR_API_KEY作为占位。准备材料只有三样:
- API Key:从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建,复制时注意不要带空格。
- Base URL:
https://taotoken.net/api,末尾不要加/v1,也不要加 UTM 参数。 - 模型名:排障阶段保留
qwen-turbo,正式使用以 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 模型广场当时列表为准。
Windows 上确认 Python 环境能跑起来,建议在 OpenClaw 目录里先执行python -m pip install requests。如果你之前装过 dashscope SDK,也不用急着卸载,先让 qwen_client.py 改用 requests 发请求,减少 SDK 在 base_url 上自动补齐路径带来的干扰。
2.2 qwen_client.py 里 self.base_url 和 headers 的改法
打开 OpenClaw 目录下的qwen_client.py,找到__init__里设置self.base_url的那一行。原来的 dashscope text-generation 地址整行删掉,改成 TaoToken 的兼容通道地址。注意这里只写https://taotoken.net/api,不要写成https://taotoken.net/api/v1,也不要把?utm_source=taotoken_aicg_blog_end拼到 API 地址后面。下面是一份可以直接对照的完整写法:
import os import requests class QwenClient: def __init__(self, api_key: str | None = None): self.api_key = api_key or os.environ.get("TAOTOKEN_API_KEY", "") if not self.api_key: raise ValueError("缺少 TAOTOKEN_API_KEY,请从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建") self.base_url = "https://taotoken.net/api" self.model = "qwen-turbo" def chat(self, user_text: str) -> str: url = f"{self.base_url}/chat/completions" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } body = { "model": self.model, "messages": [ {"role": "system", "content": "你是飞书里的助手,回答保持简洁。"}, {"role": "user", "content": user_text}, ], "temperature": 0.3, } resp = requests.post(url, headers=headers, json=body, timeout=60) if resp.status_code != 200: raise RuntimeError(f"Qwen API {resp.status_code}: {resp.text}") data = resp.json() return data["choices"][0]["message"]["content"]改完之后,headers里保留Authorization: Bearer YOUR_API_KEY,不要混入 dashscope 专用的鉴权头。如果你原来在 headers 里写了X-DashScope-API-Key,也先删掉,排障阶段只保留一种鉴权方式。
2.3 请求路径拼 /chat/completions 而不是 text-generation
TaoToken 兼容通道在代码里按 OpenAI 风格使用,所以请求路径是/chat/completions,不是 dashscope 的/text-generation/generation。这一点和原文第二步差别最大:原文可能用requests.post(self.base_url, ...)直接打 text-generation 地址,或者用 dashscope SDK 的Generation.call。现在要么把路径改成f"{self.base_url}/chat/completions",要么把整个调用换成上面那段 requests 写法。
响应解析也要跟着换。dashscope 原生返回常见的是output.text,OpenAI 兼容返回的是choices[0].message.content。如果你的gateway.py里还在读output.text,QwenClient 就算请求成功,飞书那边也会因为取不到字段而报错。排障时先让verify_qwen.py打印完整 JSON,确认结构之后再回去改 gateway 里的解析字段。
3. 单独跑 QwenClient:先别急着点飞书
3.1 写一个最小 requests 验证脚本
在 OpenClaw 目录里新建verify_qwen.py,只做一件事:用 QwenClient 发一句话,看能不能拿到文本。这个脚本不要经过飞书,也不要经过 gateway.py,避免把飞书回调的问题混进来。
from qwen_client import QwenClient def main(): client = QwenClient(api_key="YOUR_API_KEY") answer = client.chat("用一句话说明飞书机器人已经连上 Qwen 千问。") print("Qwen 返回:", answer) if __name__ == "__main__": main()Windows 下在项目目录打开 PowerShell,执行python verify_qwen.py。如果终端里打印出模型返回的一句话,说明 Base URL、Key、模型名这条线已经通了。如果报 401,先检查YOUR_API_KEY有没有复制完整;如果报 404,重点看self.base_url是不是多了/v1或者还留着 text-generation 路径。
3.2 401、404、/v1 重复的日志对照
下面这张表按本篇 qwen_client.py 的配置整理,只覆盖排障时最可能撞上的几种。
| 现象 | 日志/报错关键词 | 常见原因 | 处理 |
|---|---|---|---|
| 401 | Invalid API key、Unauthorized | Key 未创建、复制错、带空格,或用了旧百炼 Key | 回 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 重新创建 YOUR_API_KEY |
| 404 | Not Found、url not found | base_url 多写/v1,或路径仍是 text-generation | Base URL 只写https://taotoken.net/api,请求路径改/chat/completions |
| 400 | model not found | 模型 ID 不在当前列表 | 排障先用qwen-turbo,正式模型以 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 模型广场为准 |
| 超时 | Read timed out | 网络慢或 body 太大 | 保留timeout=60,先发短句测试 |
这张表里没有把 401 和 404 混在一起讲,因为它们的修法完全不同。401 是身份没被认出来,404 是路径没被认出来。把self.base_url改成https://taotoken.net/api之后,如果 404 还在,优先看路径拼接,而不是反复换 Key。
3.3 请求通了再启动 gateway.py
verify_qwen.py能稳定返回之后,再去启动gateway.py。Windows 上建议开两个终端:一个跑 gateway,一个用curl或浏览器访问本地端口确认服务活着。OpenClaw 的 gateway 通常负责接收飞书事件,QwenClient 只是它内部调用的一个模块。如果 gateway 启动时 import qwen_client 就报错,先回去检查类名和文件路径;如果启动成功但飞书没反应,再看/feishu/webhook有没有收到请求。
这里要分清顺序:QwenClient 单独跑通,只证明模型调用没问题;gateway 能启动,只证明本地服务没问题;飞书消息能回来,才证明整条链路都通了。排障时按这个顺序一层层确认,比在飞书后台反复点“重新发送”有效得多。
4. 回到飞书 /feishu/webhook 验证消息
4.1 检查 gateway.py 的启动顺序和端口
gateway.py 启动之后,先看它监听的端口和路径。OpenClaw 的飞书入口一般是/feishu/webhook,本地可以用http://127.0.0.1:端口/feishu/webhook试一下,确认服务会返回飞书 challenge 或者 405 之类的响应。如果本地都访问不到,飞书那边更不可能推送成功。Windows 防火墙偶尔会拦 Python 的监听端口,如果本地能访问、飞书访问不到,就去检查防火墙入站规则和回调地址是否写成了局域网 IP。
确认 gateway 活着之后,再看它有没有加载到正确的 QwenClient。有的项目会在启动时初始化模型客户端,如果初始化阶段就抛 401,gateway 可能表面启动成功,但一收到消息就报错。看日志里有没有 QwenClient 的初始化记录,没有的话就在 gateway 里加一行日志,打印当前base_url和模型名,确认不是又读到了旧的 dashscope 配置。
4.2 飞书事件订阅与回调地址
飞书开放平台里要确认事件订阅已经开启,并且请求网址就是 gateway 暴露出来的地址。如果 QwenClient 已经通了,飞书还是收不到消息,优先看 gateway 控制台有没有POST /feishu/webhook。没有这行日志,说明请求根本没到 OpenClaw,问题在飞书回调地址、事件类型或者权限配置上,跟 Qwen 的 401 已经不是同一层了。
有这行日志但立刻报错,再看错误堆栈。如果是KeyError: 'output',说明 gateway 还在按 dashscope 原生结构取字段;如果是RuntimeError: Qwen API 401,说明 QwenClient 拿到的 Key 不对。把这两类错误分开,飞书链路的排查会快很多。
4.3 用飞书发一条测试消息看完整链路
在飞书里给机器人发一条短消息,比如“测试 Qwen 是否在线”。理想情况下日志顺序是:飞书推送 POST 到/feishu/webhook,gateway 解析出用户文本,QwenClient 请求https://taotoken.net/api/chat/completions并返回,gateway 再把结果发回飞书。如果中间任何一步断了,日志会停在对应位置。
看到 QwenClient 返回内容、但飞书里没显示,就检查飞书消息发送接口的 token 和接收人 ID。看到 401 再次出现,就回到 qwen_client.py,确认self.api_key不是空字符串,self.base_url没有多写/v1。飞书这条链路一旦跑通,后面再换模型或加新机器人,都能用同一套排障顺序复用。
5. 跑通后的 Key 管理和后续接入
5.1 模型对话里确认 qwen-turbo
QwenClient 在本地返回正常之后,可以打开 TaoToken 模型对话,用同一把 Key 发一条测试消息。模型对话里能看到qwen-turbo是否还在可选列表,也能确认 Base URL 和模型 ID 的对应关系。如果这里返回正常,而 OpenClaw 里报 401,基本可以判定是项目里还残留了旧配置,比如.env里还写着百炼 Key,或者qwen_client.py被另一个文件覆盖了。
5.2 控制台看用量与创建新 Key
飞书机器人跑起来之后,去 控制台 API Keys 看一眼这次调用有没有记上账。如果用量在涨,说明请求确实走了统一通道;如果用量不动但本地能返回,就要检查是不是有缓存或者请求打到了别处。需要给测试环境、生产环境分开 Key 时,也在同一个控制台创建,不要复用同一把 Key 写进多个机器人。
5.3 需要写代码时再看 Coding Plan 和 Claude Code 文档
OpenClaw 的飞书机器人稳定之后,如果后面还想把日常写代码的流程接上,可以看 Coding Plan 是否覆盖你的使用量。Claude Code 的环境变量和配置文件写法,对照 Claude Code 接入文档 里的ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL三项即可,Base URL 同样填https://taotoken.net/api,不要加/v1,也不要把官网链接的 UTM 参数带进去。
这条链路排障到最后,真正要记住的只有两个动作:Key 从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建,Base URL 在 qwen_client.py 里只写https://taotoken.net/api。先让 QwenClient 单独返回一句话,再回 gateway.py 的/feishu/webhook看飞书消息。401 消失之后,剩下的就是飞书事件订阅和消息格式,不再难查。