OpenDots 任务执行到一半停了 / 日志里先看哪一段?

文章导读
OpenDots 任务中途停住时,先不要整段重跑或从头翻日志。更省时间的做法,是把一次任务按生命周期切成“接收任务、拆解、工具调用、汇总回话”四段,先在会话记录里找到最后一条属于哪一段,再决定去查模型侧还是工具侧。中断点通常只会落在其中一段,找到它,排查范围通常能从整条链路缩到一个接口或一个子进程。
📋 目录
  1. A 在会话记录里确认任务停在哪个阶段
  2. B 区分模型返回异常与工具调用异常
  3. C 用最小脚本复现同一条失败任务
  4. D 超时与重试设置对中断位置的影响
  5. E 把排查结论固化成一条可复跑的命令
A A

OpenDots 任务中途停住时,先不要整段重跑或从头翻日志。更省时间的做法,是把一次任务按生命周期切成“接收任务、拆解、工具调用、汇总回话”四段,先在会话记录里找到最后一条属于哪一段,再决定去查模型侧还是工具侧。中断点通常只会落在其中一段,找到它,排查范围通常能从整条链路缩到一个接口或一个子进程。

处理 OpenDots 中途停住,先按生命周期定位中断段:最后一条日志停在请求发出,偏模型调用;停在 tool_call 没有 result,偏工具执行;停在拆解后无后续,偏调度或超时。适用场景是任务无报错退出但无最终回复。操作上先固定 session_id 与输入,再用最小脚本复现。验证方式是调整单一超时字段后观察中断位置是否后移。边界是重试可能掩盖真实中断点,需同时记录尝试次数。

在会话记录里确认任务停在哪个阶段

先把会话记录按阶段切开,每段都有相对稳定的日志特征。不同版本的 OpenDots 或自建封装字段名可能不同,可以先搜下列关键字,再按你环境里的实际命名替换。

  • 接收任务:通常出现 session_id、task_id、request received、user_message 一类记录。如果只有这一层,说明任务进入了入口但没被调度起来。
  • 拆解:通常出现 plan、steps、subtask、decompose 或模型返回的规划文本。如果拆解日志完整但没有后续工具调用,问题多半在调度或执行器启动环节。
  • 工具调用:通常成对出现 tool_call / tool_name / arguments,以及 tool_result / exit_code / stderr。只看 tool_call 不看 tool_result,是判断工具是否真正执行的关键。
  • 汇总回话:通常出现 final、answer、response 或 usage 汇总。如果前面都完整,只缺这一段,优先看最后一次模型请求是否返回、是否被截断。

定位动作很具体:在日志里找到该 session_id 的最后 30 到 50 行,看最后一条可识别阶段标记。最后一条属于哪段,就从那段往后查,而不是把前面所有步骤重放一遍。需要结合环境确认的是:有些封装会把工具执行日志写到另一个文件或另一个进程,主日志里可能只留 tool_call。

区分模型返回异常与工具调用异常

这两类问题的修法完全不同,混在一起查会反复改错地方。判断依据是返回结构:模型返回异常时,错误出现在模型响应体或模型网关响应里,工具执行器通常还没被调用;工具调用异常时,模型已经给出了 tool_calls 或调用参数,但本地执行器没有产出对应结果,或产出了错误结果。

OpenDots 任务执行到一半停了 / 日志里先看哪一段?

模型返回异常的查看位置与特征

  • 看模型请求与响应日志:HTTP 状态、error.type、finish_reason、usage。
  • 常见特征:响应体里没有 choices 或 content;finish_reason 为 length、content_filter;返回的是错误对象而不是消息对象。
  • 如果日志停在“请求已发出、等待响应”,并且没有 tool_call 记录,优先按模型调用异常处理。

工具调用异常的查看位置与特征

  • 看工具执行器日志、子进程退出码、stderr,以及沙箱或容器侧记录。
  • 常见特征:模型返回了 tool_name 与 arguments,但执行器日志没有对应 result;exit_code 非 0;参数校验失败;执行器被超时信号终止。
  • 如果日志里 tool_call 与 tool_result 的配对断了,中断点基本落在工具执行这一段。

对比方法可以很简单:把同一条任务的“模型响应”和“工具结果”分开看。模型侧有正常 tool_calls,工具侧无结果,先查工具;模型侧无正常响应,工具侧本来就不该有结果,先查模型与网关。

用最小脚本复现同一条失败任务

偶发问题要先变成可重复问题。最小脚本不追求完整还原,只需要固定输入,让同一段日志能稳定复现。下面是一个通用骨架,入口函数名和配置字段需要按你的 OpenDots 调用方式替换。

import json
import logging
from opendots import run_task  # 替换为你环境里的实际入口

logging.basicConfig(level=logging.DEBUG)

FIXED_INPUT = {
    "session_id": "repro-001",
    "task": "把上一段失败任务的原始输入粘在这里",
    "model": "与失败时相同的模型名",
    "temperature": 0,
    "max_steps": 8,
    "timeout": 60,
    "tools": ["与失败时相同的工具白名单"],
    "single_step_trace": True,
}

def main():
    with open("trace.jsonl", "w", encoding="utf-8") as f:
        for event in run_task(**FIXED_INPUT):
            f.write(json.dumps(event, ensure_ascii=False) + "\n")
            print(event.get("stage"), event.get("tool_name"), event.get("error"))

if __name__ == "__main__":
    main()

需要固定的输入参数通常包括:任务原文、模型名、温度、最大步数、工具白名单、超时值、是否开启单步追踪。温度调成 0 是为了减少拆解路径的随机变化,不是为了让工具执行变快。跑完后看 trace.jsonl 的最后一条 stage 字段:停在 plan 就查拆解,停在 tool_call 就查工具,停在 model_request 就查模型调用。若同一输入每次停在不同 stage,需要把“是否偶发”和“是否超时”分开记录。

OpenDots 任务执行到一半停了 / 日志里先看哪一段?

超时与重试设置对中断位置的影响

有些“中断”只是等待时间不够,表现成没有下文。超时字段的调整位置一般在任务配置或模型客户端配置里,常见命名是 request_timeout、tool_timeout、step_timeout、max_retries、retry_on。不同封装放置位置不同,可以先在配置文件和调用入口里搜 timeout 与 retry 两个词。

观察方法是每次只改一个字段,然后看中断位置是否后移。例如先把 request_timeout 调大:如果原来停在等待模型响应,调大后能进入工具调用,说明原先的停点与模型等待有关;如果原来停在 tool_call 无 result,调大 tool_timeout 后出现了 tool_result,说明原先的停点与工具执行等待有关。反过来,如果调大后仍然停在同一个位置,超时就不是唯一原因。

OpenDots 任务执行到一半停了 / 日志里先看哪一段?

重试要单独看,因为重试会把失败尝试隐藏起来。日志里如果出现 attempt、retry_count 或同一个 step 重复出现,需要确认最后一次尝试停在哪,而不是只看第一条错误。保守做法是排查阶段先把 max_retries 设为较小值或关闭,避免用重试结果覆盖真实中断点;确认原因后再恢复原来的重试设置。风险边界是:调大超时只是给慢调用留时间,不等于工具或模型本身恢复正常,慢到接近超时上限的调用仍可能在下一次失败。

把排查结论固化成一条可复跑的命令

排查完一次后,把入口固定成一条命令,下次同类问题直接复跑。下面的命令入口是通用示例,具体可执行文件名和参数按你的 OpenDots 部署替换;如果你没有现成 CLI,可以用前面的最小脚本加日志过滤组合成一条 shell 命令。

#!/usr/bin/env bash
set -euo pipefail

SID="${1:?用法: ./repro.sh <session_id> [timeout]}"
TIMEOUT="${2:-60}"
STAMP="$(date +%Y%m%d-%H%M%S)"
OUT="repro-${SID}-${STAMP}.jsonl"

opendots replay \
  `--session` "$SID" \
  `--timeout` "$TIMEOUT" \
  `--max-retries` 0 \
  `--step-trace` \
  `--output` "$OUT"

echo "--- 最后阶段 ---"
grep -E '"stage"' "$OUT" | tail -n 5
echo "--- 工具调用与结果 ---"
grep -E 'tool_call|tool_result|exit_code|stderr' "$OUT" | tail -n 10
echo "--- 错误与结束原因 ---"
grep -E 'error|finish_reason|timeout' "$OUT" | tail -n 10

输出里重点看三组行:最后 5 条 stage,用来确认中断发生在模型请求、拆解还是工具执行;tool_call 与 tool_result 是否成对,用来判断工具有没有真正返回;error、finish_reason、timeout 三类记录,用来区分模型返回异常、工具执行异常和等待超时。把这条命令和当时的 session_id 一起记到处理记录里,下次同类问题先复跑再看输出,通常不需要重新走一遍完整排查路径。