Files
WeChat-AI/docs/runbook.md
T

15 KiB
Raw Blame History

WeChat-AI 运维手册

1. 首次部署清单

1.1 依赖

  • Node.js 20+
  • pnpm
  • Upstash Redisrediss://...
  • LINUX DO OAuth 应用
  • LLM API KeyOpenAI 兼容)

1.2 .env 必填

REDIS_URL=rediss://default:密码@xxxx.upstash.io:6379
LLM_API_KEY=...
LLM_BASE_URL=...
LLM_MODEL=...

LINUXDO_CLIENT_ID=...
LINUXDO_CLIENT_SECRET=...
LINUXDO_REDIRECT_URI=http://127.0.0.1:8787/api/v1/auth/callback
# 可选:数字 ID 或用户名;留空时「第一个登录的用户」自动成为管理员
LINUXDO_ADMIN_IDS=

PUBLIC_BASE_URL=http://127.0.0.1:8787

LINUX DO 应用回调地址必须与 LINUXDO_REDIRECT_URI 完全一致

1.4 多节点运维要点

说明
共享 所有节点同一 REDIS_URLPUBLIC_BASE_URL(主域名)、OAuth 回调
每机 唯一 WORKER_ID;可选 NODE_LABEL / NODE_REGION
入口 主域名 → cloudflare-workerORIGINS(源站 IP:端口)
后台 /admin节点:进程心跳与 bot 租约;不显示源站 URL
扫码 登录会话在 Redis,无需粘性会话
探活 LB 用 /health/readyDocker 可用 /health
下线 从 Worker ORIGINS 移除并 deploy;停容器后租约 TTL 过期自动转移
扩容 新机起容器 + 更新 ORIGINSbot 由租约自动分片
日常代码热修 pnpm release:packpnpm docker:build/admin 上传通道包 → 节点「更新」
基础镜像变更 仍需各机 docker build / 拉新镜像

进程内 HTTP 限流为单机计数;生产建议在 Cloudflare 对 /api/v1/auth/* 做 Rate Limiting。

1.3 启动

cd F:\Code-Other-4\WeChat-AI
pnpm install
pnpm diag
pnpm db:seed
pnpm dev
页面 URL
用户中心 http://127.0.0.1:8787/app
管理后台 http://127.0.0.1:8787/admin

2. 用户操作流程

  1. 打开 /appLINUX DO 登录
  2. 扫码添加微信机器人ClawBot
  3. 微信好友私聊机器人 → 在用户中心 批准
  4. 可选:分配人设(猫娘 / 女友)
  5. 正常聊天(AI 会分多条气泡回复)

2.0 输入状态(「对方正在输入中」)

两步协议,packages/ilink 内部完成,无需配置:

  1. POST /ilink/bot/getconfig { ilink_user_id, context_token }typing_ticket
  2. POST /ilink/bot/sendtyping { ilink_user_id, typing_ticket, status }status: 1 开始,status: 2 停止

票据按用户缓存(服务端有效期约 24h,本地按 20h 过期后自动重取),所以每个 peer 大约一天一次 getconfig;并发的多次输入调用会合并成一次取票。

指示器的生命周期:收到消息立刻开始 → 调模型前再次开始 → 多气泡之间每条前再次开始 → handleJobfinally 统一停止。回复、拒绝、限流、用户互聊中继、异常,任何出口都会停止,不会把「正在输入中」留在对方屏幕上。主动联系发完也会停止。

排查:getconfig 失败时会退化成不带票据的 sendtyping(指示器可能不显示,但绝不影响回复);票据被服务端提前失效时会强制重取并重试一次。

2.0.1 图片理解(入站 Vision

默认关闭,且由 apps/api/src/shipped-defaults.test.ts 守着——VISION_ENABLED 只认精确的 "true""1" / "yes" / "TRUE" 都不算开。关闭状态下:不去 CDN 取字节、不调任何模型,收到图片直接回一句按类型区分的话。

出站语音 / 视频 / 文件sendVoice / sendVideo / sendFile 已在 packages/ilink 实现并有测试,但没有任何调用点,回复路径不会用到。要真正让角色发语音,还差两段:TTS 产出音频、再转成微信用的 SILK 编码(TTS 给的是 mp3/wav/opus,直接发大概率放不出来)。另外出站 voice 的 item type 只在入站验证过,需真机确认。

关键点:人设模型不需要支持视觉。 默认的 caption 模式先让一个识图端点把图片转成文字描述,只把这段文字交给人设模型——所以 deepseek-v4-flash 这类纯文本模型照样能"看图"。描述还会写进对话历史,隔几轮再问"刚那张图里的猫呢"仍然接得上。

模式 人设模型要求 说明
caption(默认) 识图端点出描述 → 文字进人设模型
direct 必须支持视觉 图片原样交给人设模型;不支持就直接报错
环境变量 默认 说明
VISION_ENABLED false 总闸。关闭时不下载、不调模型
VISION_MODE caption 见上表
VISION_BASE_URL / VISION_API_KEY 识图端点。env-only(与平台 LLM 同信任级,直连不走 tools)。留空则复用 LLM_BASE_URL / LLM_API_KEY
VISION_MODEL 必填,留空则所有图片按「看不了」处理。对「我的模型连接」无效(那条链路只认连接里的模型名)
VISION_CAPTION_MAX_TOKENS 300 描述长度上限
VISION_MAX_IMAGES 2 单条消息最多识别几张(每张都实打实花 token)
INBOUND_MEDIA_MAX_BYTES 4194304 单个附件下载上限(解密后原始大小;base64 再涨约 1/3)

caption 模式的成本:每张图多一次识图调用(记在机器人主人账上),人设模型那一轮只多几十个 token 的描述文字——比把 4MB base64 塞进上下文便宜得多。识图失败会降级成「看不了」,不会让回复失败

行为说明:

  • 只有图片会被下载。语音/视频/文件的字节拿来也喂不进模型,所以根本不去 CDN 取——微信语音是 SILK/AMR,没有 OpenAI 兼容端点收。
  • caption 模式下人设模型收不到字节,只收到方括号里的描述;提示词会要求它「当作亲眼所见,但只依据描述内容,不要往外扩写」。
  • 语音靠微信自带转写VOICE_TRANSCRIPT_ENABLED默认开):转写文字随入站消息一起到,extractText 把它并入文本,这类语音当文字处理,不再额外列为附件(否则会告诉模型「你听不到」它正要读的内容)。这个开关与 VISION_ENABLED 互不相干——用转写不花钱、不需要任何模型。设 false 后语音一律回「没听清,麻烦打字」。
  • 模型看不到的附件仍会写进系统提示,并明确要求不许猜测内容——否则人设会张口就编视频里有什么。
  • 整条消息只有看不了的媒体且没有文字时,直接回一句按类型区分的话(不调模型,不花钱)。
  • 附件下载失败不会拖垮回复,降级成「只告知存在」。
  • Chatflow 模式的人设看到的是 [图片] 占位符——图执行器没有多模态节点。
  • 历史里存的也是 [图片] 占位符(字节不落库),所以下一轮追问「所以呢?」时上下文仍知道发过图。

2.1 智能体主动找用户(空闲触发)

默认全局关闭。开启后,空闲一段时间的用户可被角色主动联系。

  1. .env 设置 PROACTIVE_ENABLED=true 并重启服务
  2. /app 机器人卡片中打开 「主动找用户聊天」 并保存参数(空闲小时 / 间隔 / 每日上限 / 安静时段)
  3. 在用户列表对该 peer 勾选 「允许主动」(仅已批准用户)
  4. 对方须曾经聊过(系统存有 iLink context_token);从未发过消息的人无法冷启动主动触达
环境变量 默认 说明
PROACTIVE_ENABLED false 全局总闸
PROACTIVE_IDLE_HOURS 12 默认空闲阈值(小时)
PROACTIVE_MIN_INTERVAL_HOURS 24 两次主动最小间隔
PROACTIVE_MAX_PER_DAY 1 每用户每日上限
PROACTIVE_QUIET_HOURS 0-8 安静时段(上海时区);空字符串关闭
PROACTIVE_SCAN_INTERVAL_SEC 300 扫描周期
PROACTIVE_MAX_PER_SCAN 10 每轮最多发送数

2.2 用户之间通过 @LINUX DO 用户名对话

机器人可中继两个已绑定用户的文字消息(不经过 LLM)。

  1. 双方均用 LINUX DO 登录 /app
  2. 用户中心 → 用户对话 → 生成绑定码 → 微信给任意机器人发 /绑定 ABC123
  3. 双方至少各给机器人发过一次消息(写入 context_token,否则不可达)
  4. A 发送整条消息 @对方用户名 → B 收到请求 → /同意
  5. 会话中直接发文字(前缀 [用户名]);/断开 结束;空闲约 30 分钟自动结束
命令 说明
/绑定 CODE 认领绑定码
/解绑 解除绑定
/我的身份 查看绑定与会话状态
@username 发起对话请求(整条消息)
/同意 /拒绝 处理入站请求
/取消请求 取消自己发出的请求
/断开 结束当前用户对话
/拉黑 用户名 拉黑(无法再互相 @
/取消拉黑 用户名 移出黑名单
/黑名单 查看黑名单

2.3 管理后台广播(全站 / 单 bot 推送)

管理员在 /admin广播 可向微信用户推送纯文本(系统更新、通知等)。

  1. 登录管理后台 → 侧栏 广播
  2. 撰写文本,选择范围:
    • 全部机器人:每个 bot 下全部有 context_token 的 peer(含未批准)
    • 指定机器人:多选 bot,再向其可触达 peer 群发
    • 指定 Peer:选 bot → 勾选 peers
  3. 预估人数 → 确认后创建异步任务;列表可看进度 / 取消
  4. 机器人详情页也可 向此 bot 群发 / 对某 peer 发消息(跳转并预填)
环境变量 默认 说明
BROADCAST_INTERVAL_MS 200 两条消息间隔(限速,降低 iLink 风险)
BROADCAST_MAX_TEXT 2000 文本最大长度
BROADCAST_HISTORY 100 保留的历史任务数

约束:

  • 从未给 bot 发过消息的用户无法送达(无 context_token
  • 需 Worker 开启(WORKER_ENABLED 默认 true);Worker 关则任务一直 pending
  • 已发出的消息无法撤回;取消只停止剩余目标
  • 不进 LLM / 人设记忆
环境变量 默认 说明
P2P_ENABLED true 总开关(false 关闭)
P2P_BIND_CODE_TTL_SEC 600 绑定码有效期
P2P_REQUEST_TTL_SEC 300 对话请求有效期
P2P_SESSION_IDLE_SEC 1800 会话空闲超时
P2P_RELAY_MAX_CHARS 500 单条中继最大字数
P2P_MAX_REQUESTS_PER_DAY 20 每 peer 每日发起 @ 次数

跨 Bot 可用:A 绑在 Bot1、B 绑在 Bot2,中继走目标方 bot 凭证 + 其 context_token

2.4 记忆检索与时间工具

环境变量 默认 说明
MEMORY_TOP_K 12 记忆超过全量阈值时注入条数
MEMORY_FULL_INJECT_MAX 20 ≤ 此数时仍全量注入(与旧行为一致)
MEMORY_MAX_ITEMS 100 每个 peer+人设最多存储条数
TIME_TOOL_ENABLED true 允许模型调用 get_current_time
TIME_TOOL_TIMEZONE Asia/Shanghai 默认时区

用户中心 → 微信用户行 → 记忆:查看 / 删单条 / 清空。

日志关键字:[proactive]action=send|skip|lock_miss|no_ctx);[broadcast](管理广播任务)。

2.5 自定义模型与联网搜索(经 HF 工具服务)

主站直连管理员配置的平台 LLM;用户自定义 API 与联网搜索一律经 huggingface/wechat-ai-tools 出站。

环境变量 说明
TOOLS_BASE_URL 工具服务根地址(HF Space / 自托管容器)
TOOLS_API_KEY 与工具服务共享的调用密钥
LLM_PROVIDER_SECRET 加密用户保存的自定义 API Key(必填,否则无法添加连接)
WEB_SEARCH_ENABLED 全局搜索开关;人设还需自行开启

用户路径:/app我的模型 添加连接 → 人设编辑器里选择该连接 / 勾选联网搜索。

排查:

  • pnpm diag 会探测 TOOLS_BASE_URL/health
  • /health/readyWEB_SEARCH_ENABLED=true 且 tools 不可达时返回 503(结果缓存 15s)
  • 日志与审计不记录 upstream api_key

2.6 Chatflow

人设可切 chatflow 模式,用 /chatflow?persona=<id> 编排流程图。

环境变量 默认 说明
CHATFLOW_HTTP_ALLOWLIST http 节点额外允许的 hosttools host:port 始终允许);* = 任意公网,内网/云元数据仍拦
CHATFLOW_MAX_STEPS 32 单次执行最大步数
CHATFLOW_MAX_NODES 40 图最大节点数

要点:chatflow 人设不参与主动联系;试聊强制走平台模型。详见 docs/chatflow.md

3. 管理员

  • 配置了 LINUXDO_ADMIN_IDS:名单内用户登录后为管理员
  • 未配置:FIRST_USER_IS_ADMIN 默认 true首个登录用户自动管理员
  • 打开 /admin:今日 Token、用户数、机器人、审计、广播

4. Worker 与规模

API 与 iLink Worker 同进程(单镜像 / 单容器)。

  • 收消息:getUpdates 长轮询(每 bot 一路,有 MAX_BOTS_PER_WORKER 上限)
  • 回消息:进程内 inbox 队列 + REPLY_CONCURRENCY 并发,避免 LLM 堵住轮询
  • 日志出现 at capacity:提高 MAX_BOTS_PER_WORKER,或同镜像多副本分片

5. 故障

现象 处理
Redis Connection closed / 占位符 填真实 Upstash rediss:// URL
OAuth redirect_uri mismatch 控制台回调与 .env 一致
OAuth userinfo 失败 确认 scope openid profile(已默认)
无管理员 清空 Redis 用户或设置 LINUXDO_ADMIN_IDS 后重登
微信 session expired 在用户中心删除机器人后重新扫码
无 AI 回复 检查 LLM_API_KEY、用户是否批准
at capacity / 部分 bot 不 poll 调高 MAX_BOTS_PER_WORKER
自定义模型报 TOOLS_BASE_URL required 部署工具服务并配置 TOOLS_BASE_URL / TOOLS_API_KEY
添加模型连接 503 未设置 LLM_PROVIDER_SECRET
chatflow http_blocked 目标域名不在 tools host / CHATFLOW_HTTP_ALLOWLIST;或命中内网段(报文里带 private host blocked 与原因);或重定向跳进内网
chatflow http 节点全部 resolves to ... 被拦 本机 DNS 在劫持解析(把所有域名答成 CGNAT/198.18 之类占位地址)。先 nslookup 确认,再排查 resolver,不要直接放宽白名单
chatflow search_disabled 人设未开搜索,或 WEB_SEARCH_ENABLED / TOOLS 未配
/health/ready 503 但 Redis 正常 tools 网关不可达(见 §2.5

6. 备份

  • Upstash:控制台备份 / 导出(按套餐)
  • Redis wa:bot:{id}:credsBot token,敏感)
  • .env(勿提交 Git

7. 文档

  • Upstashdocs/upstash-redis.md
  • OAuthdocs/oauth-linuxdo.md
  • APIdocs/admin-api.md
  • AI 网关(主站↔HF 契约):docs/ai-gateway.md
  • Chatflowdocs/chatflow.md