故障时间线
本次故障从触发到恢复历经约8个小时,关键节点如下:
| 时间 | 事件 | 状态 |
|---|---|---|
| 10:00:21 | handai/deepseek-v4-flash 模型服务首次返回 400 format 错误 | 触发点 |
| 10:12:42 | h0assistant 主会话 context overflow(290条消息),自动压缩超时 | 加剧 |
| 10:15:41 | 上下文压缩 Compaction timed out, failover 到 qwen3.5-flash | 失败 |
| 10:46 | 首次 long-running session 告警 | 队列积压 |
| 13:01~13:12 | handai/deepseek-v4-flash 反复出 format 400 | 恶化 |
| 13:21~17:37 | h0assistant 卡死 processing 状态 | 完全卡死 |
| 16:17 | 飞书消息无法响应 | 影响用户 |
| 17:33 | 手动导出诊断包并重启网关 | 恢复 |
根本原因分析
本次故障并非单一故障点,而是链式连锁反应的结果.
触发点: 模型提供商 Tool Schema 兼容性问题
调用链路: OpenClaw -> handai(newapi) -> 火山引擎 deepseek-v4-flash. 日志中出现: FailoverError: provider rejected the request schema or tool payload. status=400, reason=format. 火山引擎 deepseek-v4 在 tool_call 参数校验上与 OpenAI 标准不完全一致,复杂 tool call 触发校验失败.
加剧因素 1: Context Overflow 死循环
290条历史消息触发 context overflow,自动压缩超时,全量摘要也失败,形成死循环.
加剧因素 2: 内存压力过高
网关进程内存达 1.95 GB,加载 59 个插件,大量未使用.
加剧因素 3: 历史会话积压
主 agent 66 个会话, h0assistant 18 个会话.
已采取的解决方案
方案一: 关闭模型 Tool Call 能力
compat.supportsTools = false,规避火山引擎 tool schema 校验问题.
方案二: 插件白名单削减
59 个插件缩减到 24 个必要插件.
方案三: 内存压力立即缓解
重启后内存从 1.95 GB 下降至 432 MB.
防御建议
建议一: 定期清理历史会话
建议二: 控制模型兼容性
建议三: 添加 Context Overflow 告警监控
效果总结
| 指标 | 整改前 | 整改后 | 变化 |
|---|---|---|---|
| 网关内存 | 1,955 MB | 432 MB | darr;78% |
| 插件数量 | 59 个 | 24 个 | darr;59% |
| deepseek-v4-flash tools | 开启 | 关闭 | 规避 400 format |
本次故障揭示了 AI Agent 系统在生产环境中的典型威胁:模型提供商兼容性问题、上下文管理失败、插件臃肿、内存危机之间形成链式反应.
后续复盘:supportsTools: false 是双刃剑(2026-07-01 更新)
在采用 supportsTools: false 作为应急方案一段时间后,我们发现这个配置本身引发了新的问题。
背景补充:DeepSeek 官方 vs 火山引擎
经查证,DeepSeek 官方 API(api.deepseek.com)完全支持 Function Calling</strong,遵循 OpenAI 标准 tools 参数格式。当时的 400 错误来自火山引擎的 deepseek-v4-flash 端点,其 tool_call 参数校验与 OpenAI 标准不完全一致。
调用链路:OpenClaw → handai(newapi) → 火山引擎 deepseek-v4-flash
supportsTools: false 的实际效果
通过 OpenClaw 源码分析,supportsTools: false 的效果是”禁用了一半”:
| 层面 | 行为 | 是否符合预期 |
|---|---|---|
| API 请求 | 不传 tools 字段给模型 API |
✅ 符合 |
| 工具构建 | 传给 runner 的工具列表为空 | ✅ 符合 |
| System Prompt | 仍然包含完整的工具描述(”你可以用 exec 工具…”) | ❌ 不符合 |
原因:effectiveToolsAllow(决定 system prompt 中列出哪些工具的参数)不依赖 toolsEnabled,而是来自 params.toolsAllow。因此即使工具被禁用,system prompt 仍然告诉模型”你有 exec、read、write 等工具可用”。
引发的新问题:死循环
在 2026-07-01 的分析中,我们发现 supportsTools: false 配置下的 deepseek-v4-flash 陷入了工具调用死循环(详见 OpenClaw 会话死循环案例分析)。
矛盾点:如果工具确实被禁用了,模型不应该能执行工具调用。但实际观察到模型反复执行 exec 命令 166 次,上下文膨胀到 654K tokens。
可能的解释:
- 模型收到 system prompt 说有工具可用,但 API 请求里没有 tools 定义,于是在文本输出中”模拟”工具调用格式
- OpenClaw 的 DSML 过滤器或其他文本解析路径检测到这些工具调用块并执行了它们
- 或者触发了 fallback 机制,切换到有工具能力的模型执行了工具调用
利弊分析
| 时间维度 | 利 | 弊 |
|---|---|---|
| 短期(应急) | 立即消除 400 错误,恢复服务 | 模型不能正常调用工具 |
| 长期(现状) | — | system prompt 仍诱导工具使用 → 死循环;虚假安全感;模型能力浪费 |
建议的长期方案
- 排查火山引擎 tool schema 兼容性问题:具体是哪个字段不兼容?能否在 handai provider 层做 schema 转换?
- 或换用 DeepSeek 官方 API(
api.deepseek.com)代替火山引擎端点,官方 API 完全兼容 OpenAI 格式 - 恢复
supportsTools: true,同时确保 loop detection 对 exec 生效(参考 Issue #93917) - 如果确实需要禁用工具:应该同时从 system prompt 中移除工具描述,而不是只禁用 API 层面的工具传递
本次复盘的核心教训:supportsTools: false 只禁用了 API 层面的工具传递,但没有同步调整 system prompt,导致”告诉模型有工具但又不给工具”的矛盾状态,反而引发了更严重的死循环问题。