6.7 KiB
问答停在执行命令或读取技能:排查与修复
日期:2026-09-28。
本次检查针对普通问答和智能体问答:页面停在工具步骤,上游模型没有收到下一轮请求。 以下是代码审查和受控回归确认的触发路径;尚未收到具体生产对话 ID 和现场日志,不能据此认定某次现场故障只有一个根因。
已修复的等待路径
| 位置 | 原来的触发条件 | 当前行为 |
|---|---|---|
| LocalSandbox 命令执行 | subprocess.run 超时仅终止外层 shell,后代仍持有输出管道;Windows 超时收尾继续无期限 communicate |
独立进程组、关闭交互输入、临时文件采集输出;超时清理进程树并限制回收等待 |
| 技能视图刷新 | 进程锁、跨进程文件锁和等待并发校验结果均没有统一等待上限 | 等待最多 30 秒后抛出明确工具错误;不删除正在执行的刷新,不使用未经校验的旧视图 |
| AIO 沙箱 | 上次命令未返回时,后续命令或文件操作无限等待同一把锁 | 锁等待 30 秒后失败;不抢占旧请求的锁,不自动重复执行命令 |
| 文件分段读取 | 超长单行先读完整行才裁剪;FIFO/设备文件可能一直等数据 | 达到当前行输出预算即返回;本地读取用非阻塞打开并检查普通文件,远端分段读取同步限制 |
| 前端结束恢复 | 后端已 success/error,但旧 SSE 仍开着,SDK isLoading 一直为真;整段 SSE 期间的 sendInFlight 又把同一轮结束快照丢掉 |
核对轮次及运行 ID 后清理重连标记、关闭本地 reader、应用结束快照;不因此取消后端或覆盖新一轮 |
| 前端恢复查询 | /runs、检查点或历史请求悬挂,占着 single-flight 恢复槽 |
普通 SDK 请求设置 30 秒超时;恢复失败仍保留后续检查机会,SSE 不受此总时长限制 |
| 后端结束收尾 | 图已返回,但检查点检查、状态写入、日志刷新或标题同步永远不返回,导致 end 发不出去 |
终态检查、终态状态写入分别有 10 秒预算;后续元数据收尾共享 10 秒预算,独立 finally 发出 end |
技能锁超时走原有工具错误处理链,模型可收到失败结果并决定后续操作;不会把“超时”伪装成成功。 元数据收尾超时只发生在图已经结束后,不给整个研究/子智能体执行增加统一短时限。 终态检查点超时会报告运行失败,不能直接当成模型成功。
配置和上线
本次需同时更新后端代码和前端构建产物,重启后端后生效。只刷新页面或调线程数不能替代修复。
本次未操作生产服务、未发布前端包;正式离线发版仍使用独立的 deerflow-offline-deployment 仓库。
本地命令保留原来的 600 秒默认执行时限,新版允许在实际加载的配置文件中调整:
sandbox:
# 合并到已有 sandbox 段中,保留 use、mounts 等既有字段。
command_timeout_seconds: 600
该字段只控制 LocalSandbox 命令执行,必须为有限正数。未配置仍为 600 秒;不要把它误认为模型请求超时。 命令输出每个流最多读入 1 MiB 头尾内容,再经过既有工具输出裁剪。 运行中的命令约每 100ms 检查累计捕获输出,超过 16 MiB 请求终止;这是采样护栏,不是磁盘硬配额。 正常退出后的显式后台进程需要自行重定向输出和管理生命周期。
前端已有的无业务事件检查窗口仍为 90 秒;这是启动后台状态核对的时机,不是宣判任务失败的时限。 30 秒控制请求超时不影响合法的长时间 SSE 和工具执行。
仍需现场证据区分的情况
- 磁盘/NFS 本身阻塞的刷新执行者仍可能卡在操作系统 I/O;本次限制的是其他调用的等待,不宣称能终止任意内核 I/O。
- 自定义技能脚本自身请求外部服务不返回,仍会等到本地命令时限;建议技能内各 HTTP 请求也设置连接/读取超时。
- 整个图中的第三方异步工具、模型前中间件和检查点操作没有统一的“无事件即取消”规则,避免误杀合法研究任务。
- 检查点回滚是实际数据修改,保持原有完成/取消语义,不把仍在执行的回滚丢到后台后开放下一轮。
- 已提交给数据库的单条写入未必能被驱动撤销;取消和新轮次检查可阻止旧清理继续发起后续写入,不能保证撤回已经执行的 SQL。
再次出现时,记录对话 ID、运行 ID、最后一步和时间,并对照上游请求日志、运行状态及
RUN_COMPLETION_TIMEOUT run_id=... phase=terminal_checkpoint|terminal_status|metadata_cleanup。
技能锁超时会包含 Timed out waiting for skill view ...;沙箱命令超时会包含 Command timed out。
不要仅凭页面最后显示“读取技能”就断定仍在读文件,它也可能是结束消息没有传到浏览器。
回归入口
- 后端
packages/harness/tests/test_skill_view_waits.py:锁竞争和并发校验超时、失败后恢复。 - 后端
packages/harness/tests/test_skill_view_cache.py、test_skill_view_tool_reuse.py:既有启停与缓存安全性。 - 后端
packages/harness/tests/test_sandbox_command_liveness.py:真实父子进程、输出、非交互输入及特殊文件/长行。 - 后端
packages/harness/tests/test_run_completion_timeout.py:挂起的结束收尾、取消、新一轮隔离及正常长图执行。 - 前端
src/core/api/control-timeout.test.ts、src/core/threads/stream-terminal-recovery.test.ts:控制请求超时及真实 SDK reader 恢复。 - 前端既有
stream-recovery、stream-display、stream-reasoning-replay、stream-cursor测试继续验证断流重连和消息显示。
本机验证结果
- 后端
python -m pytest packages/harness/tests -q:263 passed。旧同步日志断言补了显式测试配置,避免当前启用异步日志时与后台队列竞速;异步日志仍由独立测试覆盖,未改实际运行配置。 - 前端八个相关测试文件打包后用 Node test runner 执行:31 passed;新增测试类型修正后其中三个新增用例再次通过。
- 修改的后端代码和测试通过 Ruff;
git diff --check通过。 - 完整前端类型检查未通过:75 个 TypeScript 错误,均在本次未修改的文件,涉及
nextra缺失、旧 JSX 类型、既有测试类型等。本次修改文件及新增测试无诊断。详细输出保留在frontend-web/.runtime/hang-typecheck.log(本机文件,不作为发版资产)。 - 命令进程树测试在 Windows 使用真实子进程;Linux 进程组分支已审查,但本机未做 Linux 实跑,AIO 未连接实际容器。未用生产模型调用或真实业务技能执行验证。