OpenClaw 收不到图片和语音?一篇搞定排查与修复(含豆包 ASR 接入)

OpenClawTelegramASR排障

文字消息能回复,但图片和语音就是处理不了——这种情况你碰到过吗?

语音一直卡在"处理中",图片发过去没反应,排查起来很烦人。

最常见的根因其实是 Telegram 媒体下载链路不稳(代理/超时/DNS)。

这篇文章直接给你一套可用的排查和修复方案,包括:

  1. 先看哪些日志,快速定位问题出在哪一层
  2. 图片/语音都不处理的话怎么修
  3. 如何接入豆包的语音转写功能

先搞清楚是哪一层的问题

这看起来像是一个问题,其实是两个独立的问题叠加在一起的:

A 类:媒体下载链路坏了(图片/语音都不行)

你会看到这样的日志:

MediaFetchError ... TypeError: fetch failed
sendChatAction failed
getUpdates timed out

这说明问题出在 Telegram 的网络链路,可能是代理、超时、重试或者 DNS 的问题。

B 类:媒体下载成功,但语音转写链路坏了(只语音卡住)

典型现象:

  • 图片能回
  • 文字能回
  • 语音一直处理中

这类问题通常是 ASR 命令不可用、鉴权不对、Resource ID 配错。


1) 从哪里开始查日志

先检查网关状态

openclaw gateway status

重点看:

  • Service: LaunchAgent (loaded)
  • Runtime: running
  • RPC probe: ok

如果这里显示 not loaded 或 failed,先把服务修好,再继续往下查媒体问题。

看主日志和错误日志

最小复现流程(用于触发日志):

  1. 发一条文字
  2. 发一张图片
  3. 发一条 3-5 秒语音,然后立刻看日志

常用位置:

  • /tmp/openclaw/openclaw-YYYY-MM-DD.log
  • ~/.openclaw/logs/gateway.log
  • ~/.openclaw/logs/gateway.err.log

抓关键字:

  • MediaFetchError
  • fetch failed
  • sendChatAction failed
  • getUpdates timed out
  • audio / 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
      }
    }
  }
}

注意两点:

  1. channels.telegram.proxyhttp/https,不要直接填 socks5h://...
  2. 不要只依赖系统环境变量代理,OpenClaw 层要显式配

优先先改 proxytimeout,确认媒体能下载后再调 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 豆包录音文件识别的关键流程

  1. 上传本地 .ogg 到可公网访问的临时地址(我用的是 litterbox.catbox.moe
  2. 调用提交接口:/api/v3/auc/bigmodel/submit
  3. 轮询查询接口:/api/v3/auc/bigmodel/query
  4. 状态 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) 排障顺序总结

每次遇到“语音/图片不处理”,按这个顺序走:

  1. openclaw gateway status 看服务是否在线
  2. 日志搜 MediaFetchError 判断是否媒体下载层失败
  3. channels.telegram.proxy/timeout/retry/autoSelectFamily
  4. 群聊场景检查 groupPolicy/groupAllowFrom
  5. 媒体下载恢复后,单查 ASR 命令是否可执行
  6. 校验豆包鉴权 + Resource ID + 输出文件是否写成功

5) 怎么验证修好了

修完后,可以做这 3 个测试:

  1. 发文字:能立即回复
  2. 发图片:能触发处理并回复
  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()

附录 B:火山引擎开通流程

附录 B:火山引擎开通流程
  1. 打开火山引擎控制台并登录:火山引擎官网

  1. 先完成实名认证(点击“前往实名认证”,再用微信/抖音扫脸)。

  1. 进入豆包语音控制台:豆包语音,点击中间的“创建应用”。

  1. 创建应用时填写:
    • 应用名称:只支持英文(示例:Ollie
    • 应用简介:自己用即可
    • 接入能力:豆包录音文件识别模型 2.0-标准版

应用创建完成后查看 API 密钥页面(注意保密):