任务跑到一半停了,先看任务日志,不要一上来就翻模型返回。任务日志能直接告诉你是停在哪一步、上一步成功到哪,模型返回和工具调用日志都是围绕这一步去取的证据。如果先从模型返回开始看,容易在一堆正常请求里翻很久,仍不知道失败发生在哪个阶段。合理的顺序是:任务日志定位步骤,模型返回排除超时和截断,工具调用入参出参区分外部报错和参数拼装错误,最后用同一条输入做最小复现。
建议按“任务日志 → 模型返回 → 工具调用 → 最小复现”四步走。任务日志只用来确认最后一个成功步骤和第一个失败步骤;模型返回只看失败那一步的 status、duration、finish_reason 和 error;工具调用看请求体和响应体是否成对出现。三步都指向同一处时再改配置或提工单,只看到一处异常不要直接下结论。
先在任务记录里确认停在了哪一步
进控制台的运行记录(不同版本可能叫任务历史、执行记录或 Run 列表),按时间或 run_id 找到这次运行,点进详情页的“执行轨迹”或“步骤详情”标签。页面上通常会给出每一步的状态、开始结束时间和耗时,以及失败原因。这一步的目标不是读懂全部日志,而是拿到两个锚点:最后一个 success 的步骤,和第一个非 success 的步骤。
| 字段 | 含义 | 怎么用 |
|---|---|---|
| run_id | 运行实例 ID | 后续所有日志过滤都靠它 |
| step_id / step_name | 步骤标识与名称 | 确定停在哪个环节 |
| status | pending / running / success / failed / skipped | 找第一个非 success 的步骤 |
| started_at / ended_at | 步骤起止时间戳 | 算出实际耗时,与超时配置对比 |
| duration_ms | 步骤耗时 | 接近超时阈值时优先怀疑超时 |
| attempt | 重试次数 | 有重试但都失败,说明不是单次抖动 |
| error_code / error_message | 失败分类与原始报错 | 决定下一步看模型还是看工具 |
| trace_id / span_id | 链路标识 | 用它把模型日志和工具日志串起来 |
如果失败步骤的 error_code 属于超时或连接类,下一步直接去看该步骤的模型请求,不用再往后翻;如果失败步骤名本身是工具调用,就跳到第三段。步骤状态里出现 running 长时间不结束,一般也先按超时处理。
把这一轮的模型请求和返回单独找出来
按 run_id 和阶段过滤,把范围从“整轮对话”缩到“失败那一次模型调用”。下面命令假设日志是每行一条 JSON,字段名按你实际环境替换:
# 先看这一轮有哪些阶段
jq -r 'select(.run_id=="<run_id>") | .stage' staffdeck.log | sort | uniq -c
# 再单独取模型请求与返回
jq -c 'select(.run_id=="<run_id>" and .stage=="llm")
| {ts, model, status, duration_ms, finish_reason, error, prompt_tokens, completion_tokens}' staffdeck.log
如果不是 JSON 日志,用 grep 先粗筛再人工看:
grep '<run_id>' staffdeck.log | grep -i 'llm\|model' | tail -n 50
要看的字段有三个:status 是正常返回、超时还是中断;duration_ms 是否贴着配置的请求超时;finish_reason 是 stop、length、tool_calls 还是空值。区分方式比较直接:duration 接近超时阈值且没有响应体,通常是请求超时;有连接重置一类的 error.type、响应体为空,通常是连接中断;finish_reason 是 length 或返回 JSON 解析不完整,多半是内容被截断,报错会出现在解析层而不是请求层。注意确认日志级别,有些部署默认只在 warning 以上才打印模型返回,需要临时调高才能看到完整响应。
检查工具调用这一层的入参和出参
工具调用的日志要成对看请求和响应,只有 request 没有 response,说明请求没发出去或连接就断了;两者都有但 response 里带 error,就要看 error.code 是参数校验还是外部系统故障。日志样例:
{
"ts": "<ts>",
"run_id": "<run_id>",
"step": "call_crm_query",
"stage": "tool",
"tool": "crm.search_customer",
"status": "failed",
"duration_ms": 812,
"attempt": 1,
"request": {"query": null, "page_size": "20"},
"response": {"error": {"code": "INVALID_PARAM", "message": "query must be a non-empty string"}}
}
失败入参的典型形态有几种:必填字段是 null 或空字符串,往往来自上一步模型返回的 JSON 键名大小写不一致或输出被截断;类型错,数字传成字符串、布尔传成 "true";层级错,该传列表的地方传了对象;模板占位符没被替换,比如参数里还留着 {{customer_id}};枚举值超出外部系统接受范围。对应的返回片段通常分两类:4xx 加明确的 error.code,属于参数问题;5xx 或请求超时,属于外部系统或网络问题。前者回头查模型返回结构和参数拼装代码,后者查外部服务状态和凭据,不要混在一起改。
用同一条输入做最小复现
复现时不要重新组织输入,直接从任务详情里复制原始用户输入和上下文参数,跳过前面已成功的步骤,只跑失败那一步。每次只改一个变量,比如只调超时、只换模型参数、只关掉某个工具,否则改了也不知道是哪一项生效。
# 仅示意,替换成实际的命令或 SDK 调用方式
INPUT='<从任务详情复制的原始输入>'
for i in 1 2 3; do
echo "=== attempt $i ==="
staffdeck run `--step` call_crm_query `--input` "$INPUT" `--no-cache`
echo "--- end attempt $i ---"
done
每次重放记录这几项:失败是否落在同一个 step、error_code 是否一致、模型 duration 是否接近超时阈值、工具入参是否每次都在同一个字段出错。建议直接用一张表记,列是 attempt、失败步骤、error_code、duration_ms、finish_reason、入参差异。稳定复现基本可以按配置或参数拼装问题处理;偶尔才失败,优先怀疑超时、限流或外部系统抖动,这时去看同一时间段是否有其他 run 也在同一步失败,再决定是改配置还是先观察。