OpenClaw 收不到图片和语音?一篇搞定排查与修复(含豆包 ASR 接入)
文字消息能回复,但图片和语音就是处理不了——这种情况你碰到过吗?
语音一直卡在"处理中",图片发过去没反应,排查起来很烦人。
最常见的根因其实是 Telegram 媒体下载链路不稳(代理/超时/DNS)。
这篇文章直接给你一套可用的排查和修复方案,包括:
- 先看哪些日志,快速定位问题出在哪一层
- 图片/语音都不处理的话怎么修
- 如何接入豆包的语音转写功能
先搞清楚是哪一层的问题
这看起来像是一个问题,其实是两个独立的问题叠加在一起的:
A 类:媒体下载链路坏了(图片/语音都不行)
你会看到这样的日志:
MediaFetchError ... TypeError: fetch failed
sendChatAction failed
getUpdates timed out
这说明问题出在 Telegram 的网络链路,可能是代理、超时、重试或者 DNS 的问题。
B 类:媒体下载成功,但语音转写链路坏了(只语音卡住)
典型现象:
- 图片能回
- 文字能回
- 语音一直处理中
这类问题通常是 ASR 命令不可用、鉴权不对、Resource ID 配错。
1) 从哪里开始查日志
先检查网关状态
openclaw gateway status
重点看:
Service: LaunchAgent (loaded)Runtime: runningRPC probe: ok
如果这里显示 not loaded 或 failed,先把服务修好,再继续往下查媒体问题。
看主日志和错误日志
最小复现流程(用于触发日志):
- 发一条文字
- 发一张图片
- 发一条 3-5 秒语音,然后立刻看日志
常用位置:
/tmp/openclaw/openclaw-YYYY-MM-DD.log~/.openclaw/logs/gateway.log~/.openclaw/logs/gateway.err.log
抓关键字:
MediaFetchErrorfetch failedsendChatAction failedgetUpdates timed outaudio/transcription/cli-audio-to-text
第三步:确认是“下载失败”还是“转写失败”
- 下载失败:通常在
MediaFetchError就挂掉了 - 转写失败:媒体下载成功后,卡在 ASR 命令执行或识别结果回写
2) 修复图片/语音都不处理(Telegram 媒体下载失败)
这其实是因为媒体链路比文字复杂:
- 文字消息链路短,偶尔能通
- 图片和语音需要多两步(
getFile+ 文件下载),对代理稳定性的要求更高
在 OpenClaw 里直接配置网络参数,改这个文件:~/.openclaw/openclaw.json
{
"channels": {
"telegram": {
"proxy": "http://127.0.0.1:7890",
"timeoutSeconds": 120,
"retry": {
"attempts": 8,
"minDelayMs": 1000,
"maxDelayMs": 15000,
"jitter": 0.2
},
"network": {
"autoSelectFamily": true
}
}
}
}
注意两点:
channels.telegram.proxy用http/https,不要直接填socks5h://...- 不要只依赖系统环境变量代理,OpenClaw 层要显式配
优先先改 proxy 和 timeout,确认媒体能下载后再调 retry/autoSelectFamily。
如果在群聊里测试
群聊场景还要多看一个日志:
channels.telegram.groupPolicy is "allowlist" but groupAllowFrom is empty
这代表群消息会被静默丢弃。你需要:
- 配
groupAllowFrom - 或把
groupPolicy改成open
3) 修复“语音一直处理中”(接入豆包录音文件识别)
如果图片能正常处理了但语音还是卡住,说明问题出在 ASR 这一层。
我能跑通的配置是:OpenClaw + 本地 CLI 包装器 + 火山引擎录音文件识别 v3。
文中“录音文件识别 v3”和“2.0-标准版”是等价的。
说明:这套是“录音文件识别”链路(文件 -> 文本),不是实时流式语音通话。
OpenClaw 的音频转写接口
OpenClaw 期望的接口形式是这样的:
Config ($INPUT) -> CLI -> transcription.txt ($OUTPUT)
也就是你需要给它一个可执行的命令,接收这两个参数:
--input-file $INPUT--output-file $OUTPUT
3.2 豆包录音文件识别的关键流程
- 上传本地
.ogg到可公网访问的临时地址(我用的是litterbox.catbox.moe) - 调用提交接口:
/api/v3/auc/bigmodel/submit - 轮询查询接口:
/api/v3/auc/bigmodel/query - 状态
Done后把识别文本写入$OUTPUT
关键参数:
- Resource ID 必须是:
volc.seedasr.auc
脚本与开通流程我放在文末的附录里。
3.3 OpenClaw 配置示例
{
"tools": {
"media": {
"audio": {
"models": {
"doubao": {
"type": "cli",
"command": "/your_user_root/.nvm/versions/node/v24.12.0/bin/node /your_user_root/.nvm/versions/node/v24.12.0/lib/node_modules/openclaw/dist/cli-audio-to-text.js --exec \"python3 /your_user_root/.openclaw/workspace/asr_cli.py --input-file \$INPUT --output-file \$OUTPUT\"",
"patterns": ["*.ogg"]
}
},
"defaultModel": "doubao"
}
}
}
}
3.4 必查故障点
1) 401 Unauthorized
优先检查 Token/AppID 是否正确(我这里踩过大小写误读)。
2) 1001 Requested resource not granted
通常是权限或 Resource ID 不匹配,确认你调用的是 v3 并且 volc.seedasr.auc 配对正确。
3) 语音长时间处理中
先确认你配置的命令真的存在,能手工执行。
可以先本地跑:
python3 asr_cli.py --input-file /path/to/test.ogg --output-file /tmp/asr.txt
4) 代理环境下 SSL 报错
必要时在 Python 里加 ssl._create_unverified_context() 规避本地代理证书干扰。
4) 排障顺序总结
每次遇到“语音/图片不处理”,按这个顺序走:
openclaw gateway status看服务是否在线- 日志搜
MediaFetchError判断是否媒体下载层失败 - 修
channels.telegram.proxy/timeout/retry/autoSelectFamily - 群聊场景检查
groupPolicy/groupAllowFrom - 媒体下载恢复后,单查 ASR 命令是否可执行
- 校验豆包鉴权 + Resource ID + 输出文件是否写成功
5) 怎么验证修好了
修完后,可以做这 3 个测试:
- 发文字:能立即回复
- 发图片:能触发处理并回复
- 发 3-5 秒语音:能在可接受时间内转写并回复
如果图片好了、语音还卡,继续查 ASR;如果三者都不回,回到网关和 Telegram 链路。
实测截图
下面是我配置完之后的实测回包截图(语音消息已正常转写并回复,可以看到豆包的 ASR 比 Telegrame 自带的转写识别还要准确一点):

附录
附录 A:脚本与命令
[!note] 附录 A:脚本与命令 下面是我实际跑通的两段脚本,Token/AppID/本机路径做了脱敏处理,按你的环境替换占位符即可。
asr_doubao.py
#!/usr/bin/env python3 """ Volcengine Doubao ASR (录音文件识别 v3) submit + query workflow """ import requests import time import os # ========== 配置区(替换成你的) ========== APPID = "<YOUR_APPID>" ACCESS_TOKEN = "<YOUR_ACCESS_TOKEN>" RESOURCE_ID = "volc.seedasr.auc" SUBMIT_URL = "https://openspeech.bytedance.com/api/v3/auc/bigmodel/submit" QUERY_URL = "https://openspeech.bytedance.com/api/v3/auc/bigmodel/query" # ====================================== def upload_to_litterbox(audio_path): """Upload audio to temporary storage (litterbox.catbox.moe)""" with open(audio_path, "rb") as f: files = {"fileToUpload": (os.path.basename(audio_path), f, "audio/ogg")} data = {"reqtype": "fileupload", "time": "1h"} try: resp = requests.post( "https://litterbox.catbox.moe/resources/internals/api.php", data=data, files=files, timeout=30, ) if resp.status_code == 200: return resp.text.strip() except Exception as exc: print(f"Upload error: {exc}") return None def submit_task(audio_url, enable_itn=True, enable_punc=True): headers = { "Content-Type": "application/json", "X-Api-App-Key": APPID, "X-Api-Access-Key": ACCESS_TOKEN, "X-Api-Resource-Id": RESOURCE_ID, "X-Api-Request-Id": f"openclaw_{int(time.time() * 1000)}", "X-Api-Sequence": "-1", } payload = { "user": {"uid": "openclaw_bot"}, "audio": {"format": "ogg", "url": audio_url}, "request": { "model_name": "bigmodel", "enable_itn": enable_itn, "enable_punc": enable_punc, }, } resp = requests.post(SUBMIT_URL, headers=headers, json=payload, timeout=10) status = resp.headers.get("X-Api-Status-Code") if status == "20000000": return headers["X-Api-Request-Id"] print(f"Submit failed: {status}") return None def query_task(task_id): headers = { "Content-Type": "application/json", "X-Api-App-Key": APPID, "X-Api-Access-Key": ACCESS_TOKEN, "X-Api-Resource-Id": RESOURCE_ID, "X-Api-Request-Id": task_id, } resp = requests.post(QUERY_URL, headers=headers, json={}, timeout=10) status = resp.headers.get("X-Api-Status-Code") if status == "20000000": data = resp.json() return data.get("result", {}).get("text", "") return None def transcribe(audio_path): print(f"Uploading {audio_path}...") audio_url = upload_to_litterbox(audio_path) if not audio_url: print("Failed to upload audio") return None print("Submitting transcription task...") task_id = submit_task(audio_url) if not task_id: print("Failed to submit task") return None for _ in range(30): time.sleep(2) text = query_task(task_id) if text: return text return None if __name__ == "__main__": audio_file = "/path/to/test.ogg" result = transcribe(audio_file) if result: print(result)asr_cli.py
#!/usr/bin/env python3 """CLI wrapper for OpenClaw audio transcription.""" import sys import argparse sys.path.insert(0, "/your_user_root/.openclaw/workspace") from asr_doubao import transcribe def main(): parser = argparse.ArgumentParser() parser.add_argument("--input-file", required=True) parser.add_argument("--output-file", required=True) args = parser.parse_args() result = transcribe(args.input_file) if result: with open(args.output_file, "w", encoding="utf-8") as f: f.write(result) print(f"Transcription saved to {args.output_file}") else: with open(args.output_file, "w", encoding="utf-8") as f: f.write("") print("Transcription failed") if __name__ == "__main__": main()





