Weixin (WeChat)
通过 WeChat(微信)将 Hermes 接入腾讯的个人消息平台。该适配器使用腾讯的 iLink Bot API,面向的是个人微信账号,与 WeCom(企业微信)不同。消息通过 long-polling 收发,因此不需要公网端点或 webhook。
这个适配器面向的是个人微信账号。如果你需要企业场景,请改看 WeCom adapter。
前提条件
- 一个个人微信账号
- Python 包:
aiohttp和cryptography - 安装了
messagingextra 后,终端二维码渲染会自动可用
pip install aiohttp cryptography
# Optional: for terminal QR code display
pip install hermes-agent[messaging]
设置
1. 运行设置向导
最简单的接入方式是使用交互式设置:
hermes gateway setup
选择 Weixin。向导会:
- 向 iLink Bot API 请求二维码
- 在终端显示二维码(或给出 URL)
- 等你用微信手机端扫码
- 提示你在手机上确认登录
- 自动把账户凭据保存到
~/.hermes/weixin/accounts/
成功后会看到类似:
微信连接成功,account_id=your-account-id
向导会保存 account_id、token 和 base_url,因此通常不需要手动配置。
2. 配置环境变量
完成首次扫码后,至少在 ~/.hermes/.env 中设置:
WEIXIN_ACCOUNT_ID=your-account-id
# Optional: override the token (normally auto-saved from QR login)
# WEIXIN_TOKEN=your-bot-token
# Optional: restrict access
WEIXIN_DM_POLICY=open
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
# Optional: restore legacy multiline splitting behavior
# WEIXIN_SPLIT_MULTILINE_MESSAGES=true
# Optional: home channel for cron/notifications
WEIXIN_HOME_CHANNEL=chat_id
WEIXIN_HOME_CHANNEL_NAME=Home
3. 启动网关
hermes gateway
适配器会恢复已保存的凭据,连接 iLink API,并开始 long-poll 收消息。
功能
- Long-poll transport:无需公网端点、webhook 或 WebSocket
- 二维码登录:通过
hermes gateway setup扫码接入 - 私聊和群聊消息:支持可配置访问策略
- 媒体支持:图片、视频、文件和语音消息
- AES-128-ECB 加密 CDN:自动加解密所有媒体传输
- context token 持久化:重启后仍能保持回复上下文连续性
- Markdown 保留:尽量保留标题、表格、代码块等 Markdown 结构
- 智能分块:未超限时尽量保持为一条消息;超限时按逻辑边界拆分
- typing indicators:处理时在客户端显示“正在输入”
- SSRF 防护:下载外部媒体前会校验 URL
- 消息去重:5 分钟滑动窗口避免重复处理
- 自动重试:可从瞬时 API 错误中恢复
配置选项
在 config.yaml 的 platforms.weixin.extra 下配置:
| Key | Default | Description |
|---|---|---|
account_id | — | iLink Bot account ID(必需) |
token | — | iLink Bot token(必需,通常由扫码登录自动保存) |
base_url | https://ilinkai.weixin.qq.com | iLink API 基础 URL |
cdn_base_url | https://novac2c.cdn.weixin.qq.com/c2c | 媒体 CDN 基础 URL |
dm_policy | open | 私聊访问策略:open、allowlist、disabled、pairing |
group_policy | disabled | 群访问策略:open、allowlist、disabled |
allow_from | [] | 私聊 allowlist(当 dm_policy=allowlist 时使用) |
group_allow_from | [] | 群 allowlist(当 group_policy=allowlist 时使用) |
split_multiline_messages | false | 为 true 时,多行回复会拆成多条消息;为 false 时,未超限的多行回复会尽量保留在一条消息内 |
访问策略
DM Policy
| Value | Behavior |
|---|---|
open | 任何人都可以私聊 bot(默认) |
allowlist | 只有 allow_from 中的用户能私聊 |
disabled | 忽略所有私聊 |
pairing | 配对模式(用于初始接入) |
WEIXIN_DM_POLICY=allowlist
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
Group Policy
| Value | Behavior |
|---|---|
open | 在所有群中响应 |
allowlist | 只在 group_allow_from 指定的群中响应 |
disabled | 忽略所有群消息(默认) |
WEIXIN_GROUP_POLICY=allowlist
WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2
Weixin 的默认群策略是 disabled(不同于 WeCom 的 open)。这是有意为之,因为个人微信账号通常加入了很多群。
媒体支持
入站(接收)
适配器会接收用户发送的媒体附件,从微信 CDN 下载、解密并缓存到本地供 agent 处理:
| Type | How it's handled |
|---|---|
| Images | 下载、AES 解密,并缓存为 JPEG |
| Video | 下载、AES 解密,并缓存为 MP4 |
| Files | 下载、AES 解密并缓存,保留原始文件名 |
| Voice | 如果平台提供了文本转写,则直接抽取文本;否则下载并缓存 SILK 音频 |
Quoted messages: 被引用消息中的媒体也会被抽取,方便 agent 理解用户正在回复什么。
AES-128-ECB 加密 CDN
微信媒体通过加密 CDN 传输。适配器会自动处理:
- 入站:通过
encrypted_query_paramURL 下载密文,并使用消息中附带的每文件密钥进行 AES-128-ECB 解密 - 出站:在本地用随机 AES-128-ECB 密钥加密文件,上传到 CDN,再把加密后的引用写入消息
- AES 密钥为 16 字节(128 bit),可能以 base64 或 hex 编码形式下发,适配器会自动兼容
- 这依赖
cryptographyPython 包
出站(发送)
| Method | What it sends |
|---|---|
send | 带 Markdown 格式的文本消息 |
send_image / send_image_file | 原生图片消息(通过 CDN 上传) |
send_document | 文件附件(通过 CDN 上传) |
send_video | 视频消息(通过 CDN 上传) |
所有出站媒体都会经过加密 CDN 上传流程:
- 生成随机 AES-128 密钥
- 用 AES-128-ECB + PKCS#7 padding 对文件加密
- 向 iLink API 请求上传 URL(
getuploadurl) - 把密文上传到 CDN
- 发送带有加密媒体引用的消息
Context Token Persistence
iLink Bot API 要求同一 peer 的每次出站消息都带上对应的 context_token。适配器会维护一个磁盘持久化的 context token 存储:
- token 按 account + peer 保存到
~/.hermes/weixin/accounts/<account_id>.context-tokens.json - 启动时会恢复已有 token
- 每条入站消息都会更新对应发送者的 token
- 出站消息会自动附带最新 token
这能确保即使网关重启,回复上下文也能保持连续。
Markdown Formatting
通过 iLink Bot API 连接的微信客户端可以直接渲染 Markdown,因此适配器会尽量保留原始 Markdown,而不是改写:
- 标题会保留为 Markdown heading
- 表格会保留为 Markdown 表格
- 代码块会保留为 fenced code block
- 在非代码块区域,过多空行会被折叠为双换行
Message Chunking
只要消息未超限,适配器就会尽量作为单条聊天消息发送。只有超长消息才会被拆分:
- 最大消息长度:4000 字符
- 未超限时,即使包含多个段落或换行,也尽量保持为单条消息
- 超长时按逻辑边界拆分(段落、空行、代码块)
- 代码块会尽量保持完整,避免在中间拆开
- 单个超大块若仍无法容纳,则退回到底层适配器的截断逻辑
- 分块之间有 0.3 秒延迟,避免触发 WeChat 限流
Typing Indicators
适配器会在微信客户端中显示“正在输入”:
- 收到消息后,适配器会通过
getconfigAPI 获取typing_ticket - typing ticket 会按用户缓存 10 分钟
send_typing发送开始输入信号,stop_typing发送停止输入信号- 网关会在 agent 处理消息时自动触发这些 typing indicators
Long-Poll Connection
适配器通过 HTTP long-poll(而不是 WebSocket)接收消息。
How It Works
- Connect:校验凭据并启动 poll loop
- Poll:调用
getupdates,超时时间 35 秒;服务端会一直持有该请求,直到消息到来或超时 - Dispatch:收到入站消息后,通过
asyncio.create_task并发分发处理 - Sync buffer:把持久化同步游标(
get_updates_buf)保存到磁盘,重启后能从正确位置继续
Retry Behavior
遇到 API 错误时,适配器采用简单的重试策略:
| Condition | Behavior |
|---|---|
| 瞬时错误(第 1~2 次) | 2 秒后重试 |
| 连续错误(第 3 次及以后) | 回退 30 秒,然后重置计数 |
会话过期(errcode=-14) | 暂停 10 分钟(通常需要重新登录) |
| Timeout | 立即重新 poll(这是正常 long-poll 行为) |
Deduplication
适配器会用 message ID 做 5 分钟窗口去重,防止在网络抖动或重叠 poll 响应场景下重复处理同一条消息。
Token Lock
同一个 Weixin token 在同一时间只能由一个网关实例使用。适配器启动时会获取 scoped lock,关闭时释放。如果另一个网关已经在使用同一个 token,启动会失败并给出明确错误。
All Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
WEIXIN_ACCOUNT_ID | ✅ | — | iLink Bot account ID(扫码登录后获得) |
WEIXIN_TOKEN | ✅ | — | iLink Bot token(通常由扫码登录自动保存) |
WEIXIN_BASE_URL | — | https://ilinkai.weixin.qq.com | iLink API 基础 URL |
WEIXIN_CDN_BASE_URL | — | https://novac2c.cdn.weixin.qq.com/c2c | 媒体 CDN 基础 URL |
WEIXIN_DM_POLICY | — | open | 私聊访问策略 |
WEIXIN_GROUP_POLICY | — | disabled | 群访问策略 |
WEIXIN_ALLOWED_USERS | — | (empty) | 私聊 allowlist(逗号分隔) |
WEIXIN_GROUP_ALLOWED_USERS | — | (empty) | 群 allowlist(逗号分隔) |
WEIXIN_HOME_CHANNEL | — | — | cron / 通知输出 chat ID |
WEIXIN_HOME_CHANNEL_NAME | — | Home | home channel 显示名 |
WEIXIN_ALLOW_ALL_USERS | — | — | setup wizard 使用的全开放标志 |
Troubleshooting
| Problem | Fix |
|---|---|
Weixin startup failed: aiohttp and cryptography are required | 安装依赖:pip install aiohttp cryptography |
Weixin startup failed: WEIXIN_TOKEN is required | 重新运行 hermes gateway setup 完成扫码登录,或手动设置 WEIXIN_TOKEN |
Weixin startup failed: WEIXIN_ACCOUNT_ID is required | 在 .env 中设置 WEIXIN_ACCOUNT_ID,或重新运行设置向导 |
Another local Hermes gateway is already using this Weixin token | 先停止另一个实例;同一个 token 只能有一个 poller |
Session expired (errcode=-14) | 登录会话过期,请重新运行 hermes gateway setup 扫码 |
| 二维码在设置过程中反复过期 | 二维码最多自动刷新 3 次;若仍失败,请检查网络连接 |
| 私聊不回复 | 检查 WEIXIN_DM_POLICY;如果是 allowlist,发送者必须在 WEIXIN_ALLOWED_USERS 中 |
| 群消息被忽略 | Weixin 默认禁用群响应;请设置 WEIXIN_GROUP_POLICY=open 或 allowlist |
| 媒体下载 / 上传失败 | 确认已安装 cryptography,并检查能否访问 novac2c.cdn.weixin.qq.com |
Blocked unsafe URL (SSRF protection) | 出站媒体 URL 指向了私有 / 内网地址,只允许公开地址 |
| 语音消息显示为文本 | 如果 WeChat 平台提供了转写,适配器会优先使用文本;这是正常行为 |
| 消息似乎重复出现 | 适配器会按 message ID 去重;若仍有重复,请检查是否有多个网关实例在运行 |
iLink POST ... HTTP 4xx/5xx | iLink 服务返回错误。请检查 token 是否有效,以及网络连通性 |
| 终端二维码无法渲染 | 重新安装 messaging extra:pip install hermes-agent[messaging];也可直接打开二维码上方打印的 URL |