最近在用 Hermes 做一个企业微信机器人,把 AI 能力接入到真实业务里(客服、自动回复、文件处理这些)。
文本消息一切正常,但一到文件/图片就出问题:
典型报错:
Media placeholder was not replaced with a real file path: <screenshot_path>

当时第一反应是:
👉 “是不是配置没写对?”
结果一查,完全不是这么回事。
先明确现象:
| 场景 | 结果 |
|---|---|
| 发送文本 | 正常 |
| 发送图片 | 失败 |
| 发送文件 | 失败 |
| Hermes 日志 | 找不到文件路径 |
去看 Hermes 源码(wecom.py),关键函数:
async def _cache_media(self, kind, media):
里面只处理了两种情况:
base64
url
也就是说:
👉 Hermes 只认:
抓企业微信回调数据,发现:
{
"msgtype": "file",
"media_id": "xxxx"
}
👉 关键点来了:
企业微信不会直接给你文件内容,只给你一个 media_id
答案是:
❌ 完全没有处理
代码里:
url = str(media.get("url") or "").strip()
if not url:
return None
👉 没有 url → 直接 return None
👉 文件直接被“静默丢弃”
一句话总结:
Hermes 不支持企业微信的 media_id 文件机制
完整链路是这样的:
企业微信 → media_id → ❌ Hermes没下载 → ❌ 没文件 → ❌ 无法发送
核心思路:
👉 把 media_id 转成真实文件
接口:
POST https://qyapi.weixin.qq.com/cgi-bin/aibot/media/get
参数:
{
"robot_id": "...",
"secret": "...",
"media_id": "xxx"
}
返回:
👉 二进制文件流(不是 JSON)
例如:
/tmp/file.docx
{
"type": "file",
"path": "/tmp/file.docx"
}
在 wecom.py 的 _cache_media 中增加:
media_id = str(media.get("media_id") or "").strip()
if media_id:
raw, content_type = await self._download_media_by_id(media_id)
return cache_document_from_bytes(raw, "wecom_file"), content_type
并新增:
async def _download_media_by_id(self, media_id):
resp = await self._http_client.post(
"https://qyapi.weixin.qq.com/cgi-bin/aibot/media/get",
json={
"robot_id": self._bot_id,
"secret": self._secret,
"media_id": media_id,
},
)
return resp.content, resp.headers.get("content-type")
这次问题其实挺典型的,踩坑点在这几个地方:
→ 实际是源码没支持
→ 实际只支持 base64 / url
→ 企业微信用的是 media_id,不是文件
👉 不是你不会用 Hermes
👉 是 Hermes 目前版本没支持企业微信文件
如果你也在做类似集成,建议直接这样设计:
WeCom → media_id → 下载 → 本地文件 → AI处理 → 再发送
而不是依赖 Hermes 自动处理。
👉 Hermes 不会帮你下载文件,它只会发送“已经存在的文件”