Files
AgentsMeeting/gateway/docs/xxm 稳定性与观察者模式.md
hmo 1b2b935832 Initial: multi-agent XMPP communication system with dashboard
- Platform-based architecture (Windows/Linux/Mac)
- Agent instance registry (agents.yaml)
- Management dashboard with cross-platform monitoring
- xmpp_bot with HTTP bridge + health endpoints
- wechat_agent with WeChat-Hermes bridging
- Platform services: ProcessGuardian, HealthProbe, APIRouter, ChannelBridge
- Deployment: systemd (Linux) + PowerShell (Windows)
- Monitoring: SSH+ejabberdctl for cross-platform presence
2026-06-12 21:51:36 +08:00

7.7 KiB
Raw Permalink Blame History

xxm(小小莫)稳定性与观察者模式

最后更新:2026-06-03


架构概要

xxmxxm@yoin.fun)是一个 XMPP bot,通过 slixmpp 连接 ejabberdxmpp.yoin.fun:3021),作为"小小莫"在群聊 coregroup@conference.yoin.fun 中观察和响应消息。

XMPP 消息
  ↓
xmpp_bot.py ← on_message / on_group_message
  ↓
session_router.py → route() → 构建 prompt
  ↓
chat_bridge.py → send_raw() → HTTP API 调用
  ↓
volcengine API (deepseek-v4-flash)

2026-06-03 修复记录

一、HTTP 请求死锁(最严重的稳定性问题)

问题urllib.request.urlopen(req, timeout=timeout) 在 Windows 特定网络条件下,timeout 参数不触发,请求永远挂在 socket 上。桥接线程卡死 → bot 不回消息。

修复:弃用 urllib.request,改用 requests 库。

# 旧(会挂死)
req = urllib.request.Request(url, data=data, headers=headers)
with urllib.request.urlopen(req, timeout=timeout) as resp:
    body = json.loads(resp.read())

# 新(Connect=10s, Read=60s,分别起效)
resp = requests.post(url, json=payload, timeout=(10, timeout))
resp.raise_for_status()
body = resp.json()

效果

  • Connect timeout 10sDNS / 连接层挂死不再发生
  • Read timeout 60sAPI 响应慢也能兜底
  • 异常精确分类:Timeout / HTTPError / RequestException 分别打不同日志

涉及文件chat_bridge.py


二、工具循环截断

问题_MAX_TOOL_LOOPS = 5,LLM 做多步排查(SSH 到服务器逐条查 nginx 配置)时很容易耗尽,耗尽后 return None → bot 告诉用户"模型无响应"。

修复

  1. _MAX_TOOL_LOOPS = 5 → 50(足够做深度排查)
  2. 50 轮耗尽后,再调一次不带 tools 参数的 API,强制模型用文字总结
# 耗尽后的兜底
final_resp = session.post(final_url, json={"model": model, "messages": messages}, ...)
final_msg = final_resp.json()["choices"][0]["message"]["content"]
if final_msg.strip():
    return final_msg.strip()

涉及文件chat_bridge.py


三、API Key 冷却与 retry-cache 绕过

问题opencode-go / opencode-go-new 两个 key 都在 403 冷却期(code 1010)。同时 opencode 的 retry-cache 机制(#25803/#24462)按 (provider, model) 缓存 429 错误,导致切换到另一个 provider 后仍然被旧 cache 截断。

修复

  1. 创建 api_proxy.py 本地 HTTP 代理(:8787
  2. 模型名重映射:deepseek-v4-flash-safedeepseek-v4-flash(不同的模型名绕开 retry-cache key 碰撞)
  3. 代理吞掉 429/5xx 错误码,自动重试最多 3 次(指数退避 1s/2s/4s)
  4. 代理配置为独立 providervolcengine-proxy/deepseek-v4-flash-safe
{
  "volcengine-proxy": {
    "type": "openai",
    "options": {
      "baseURL": "http://localhost:8787",
      "apiKey": "...",
      "model": "deepseek-v4-flash-safe"
    }
  }
}

涉及文件api_proxy.pystart_proxy.bat~/.config/opencode/config.json


四、观察者模式与 __SILENT__ 协议

问题:xxm 在群里应该只回应 @自己的消息,其他消息保持沉默。但 LLM 会说"我应该保持沉默"然后把这句话本身发出去(没有用 __SILENT__ 前缀)。

修复(两层防护):

第一层 — System Prompt 明确教学chat_bridge.py):

=== 群聊沉默协议 ===
保持沉默的方法:在回复的最开头写 __SILENT__
系统检测到 __SILENT__ 就不会把消息发出去。
注意:不要直接把"我应该保持沉默"当回复发出去。

第二层 — 自然语言兜底xmpp_bot.py):

_SILENCE_PATTERNS = [
    "保持沉默", "不应[该]?回复", "没有.*@.*我",
    "不是对[我我说]", "跟我无关", "我不用回复",
]

如果回复第一行匹配任何静默模式(即便没有 __SILENT__ 前缀),直接 suppress。只检查第一行,避免误杀多行正常回复。

涉及文件chat_bridge.pyxmpp_bot.py


五、群消息合并与串行化

问题:同个群多条消息靠近到达时,每条都触发独立的 LLM 调用和工具循环,导致:

  • 并发工具调用互相干扰(同时 SSH 到同一台服务器)
  • 多条重复排查(浪费额度)
  • log 混乱

修复3 秒 debounce + 房间级串行化

三个状态 / 两个路径

消息到达
  ├─ @xxm → 立即处理(绕过 batch)
  └─ 其他 → 进入 batch 系统
               │
         [BATCHING] 3s 窗口内可合并
               │  Timer 到期
               ▼
         [PROCESSING] LLM 调用中
               │  新消息 → 进入 pending 队列
               │  LLM 结束
               ▼
         [_batch_done] 检查 pending
               ├─ 有 pending → 立即发起下一批
               └─ 无 pending → IDLE

保证:同一房间同一时刻最多一个 LLM 调用在处理。

涉及文件xmpp_bot.py


六、context log 保存(self-message

问题:LLM 看不到自己在群里说过什么,因为 bot 的 self-message 被直接丢弃了。每次都是"失忆"状态。

修复self-message 不走 LLM,但写入 bridge context log_append_to_log("assistant", body)),这样下次 LLM 调用时通过 _read_recent_context() 能看到自己说过的话。

涉及文件xmpp_bot.pyon_group_messagenickname == bot_nick 分支)


七、part_prt_ 前缀修复(2026-06-11

问题chat_bridge.py_append_to_session() 生成 part ID 时用了 part_ 前缀:

part_id = "part_" + _uuid.uuid4().hex[:24]  # BUG!

但 OpenCode 1.17+ 要求 part ID 必须用 prt_ 前缀。这导致:

  • 每次 xxm 写入消息到 session → 产生 part_ 数据
  • compaction 扫描到旧前缀 → schema 校验失败 → 死循环
  • session 崩掉 → xxm 也卡住

修复part_prt_chat_bridge.py:335

连带发现ses_xxm_xmpp session 在代码里配了但 DB 里不存在,已手动创建。

八、session_search 工具说明

用途:让 xxm 能搜索其他 session 的历史对话。已内置为 function calling tool。

定义位置chat_bridge.py_TOOLS 列表

参数 默认值 说明
session_id 当前 session 指定要搜索的 session ID,空字符串则查自己
limit 20(最大100 返回最近多少条消息

调用方式LLM 通过 function calling 调用 session_search,不需要 xxm 写代码。

注意archived session 仍可搜索(extract_session_context 直接从 message 表读,不依赖 session 状态)。


关键配置

参数 说明
_MAX_TOOL_LOOPS 30 工具循环上限(超限后 clean final force,不泄漏 XML
DEFAULT_TIMEOUT 60s 每次 API 调用 read timeout
LOCK_DURATION 300s 锁定成功 provider(避免频繁切换)
FAILED_BACKOFF 1800s 失败 provider 冷却
_BATCH_WINDOW 3.0s 群消息合并窗口
api_proxy 重试 3 次 指数退避 1s/2s/4s

Provider 优先级

volcengine (deepseek-v4-flash)        # 主用,火山免费额度
  → opencode-go-new (deepseek-v4-flash)  # 备用,有订阅但冷却中
  → opencode-go (deepseek-v4-flash)      # 备用,冷却中

日志

日志文件 路径 用途
logs/xmpp_bot.log projects/.../logs/ XMPP 连接、消息收发、batch 状态
logs/bridge.log projects/.../logs/ LLM API 调用、耗时、工具调用
logs/api_proxy.log projects/.../logs/ 代理请求、错误吞没、重试