Skip to content

4.9 循环终止条件:何时「关火收工」

本节学习目标

  • 列举 至少五种 正常与异常终止路径。
  • 能把 「无工具」正常退出预算耗尽 从 UI 表现上区分开。
  • 理解 用户中断 如何协作式地打断 async generator

终止 ≠ 失败:先分「出锅」与「糊锅」

类型是否成功用户心理
正常终止(无工具)成功「说完了」
用户主动取消中性「我不想等了」
预算耗尽受限成功/失败「被规则挡住」
致命错误失败「出问题了」

条件一:正常终止——本轮无 tool_use

当模型输出的 assistant 消息 不再包含 任何 tool_use 块,QueryEngine 认为 「推理闭环已闭合」

伪代码锚点

typescript
const toolUses = extractToolUses(assistantMsg);
if (toolUses.length === 0) {
  return {
    status: "completed",
    finalText: extractVisibleText(assistantMsg),
    usage: state.usage,
  };
}

生活类比:厨师尝完汤说「可以上桌了」,没有再开采购单——这一桌结束。


条件二:预算耗尽(三重其一)

详见 4.8。终止时 status 应携带 reason,便于 UI 与日志一致。

reason典型 UI
token_window_exceeded建议压缩 / 新会话
money_budget_exceeded费用上限
max_turns_exceeded步数上限

条件三:用户中断(协作式取消)

CLI 中常见 AbortController信号(SIGINT)。QueryEngine 应在:

  • fetch 层绑定 signal
  • 工具执行层支持超时/取消;
  • yield 之间检查 cancelled 标志。

教学伪代码

typescript
async function* queryLoop(state: State, ctx: QueryContext) {
  while (true) {
    if (ctx.abortSignal.aborted) {
      return { status: "cancelled", reason: "user_abort" };
    }
    // ... 八步 ...
  }
}

注意协作式意味着:若某工具 忽略 signal,终止会「慢半拍」——这是工程现实,不是理论缺陷。


条件四:致命 API / 鉴权错误

场景是否重试终止方式
401/403立即返回,明确提示检查密钥
400 结构错误通常否request_id 记录
模型不存在配置错误

条件五:压缩熔断(连续失败)

当自动压缩 连续失败 达到阈值(教学中 3 次),继续对话风险是 无限烧钱 + 无限错误

typescript
if (state.compactionCircuitOpen) {
  return {
    status: "aborted",
    reason: "compaction_circuit_breaker",
  };
}

生活类比:吸尘器 三次堵死 还硬吸,会烧电机——必须 停机清灰


条件六:空安全与实现守护(少见但真实)

守护说明
历史损坏无法修复的 messages → 终止并建议重置会话
工具注册表为空但模型强要工具降级或终止(视产品策略)

QueryResult 形状(教学)

typescript
type QueryResult = {
  status:
    | "completed"
    | "cancelled"
    | "budget_exceeded"
    | "fatal_error"
    | "compaction_circuit_breaker";
  reason?: string;
  finalText?: string;
  usage: UsageCounters;
  diagnostics?: { requestId?: string };
};
字段用途
statusUI 主分支
reason细粒度分析与翻译文案
diagnostics对接支持工单

async generatorreturn

消费者侧(简化):

typescript
const it = query(input, ctx);
for await (const ev of it) {
  render(ev);
}
// TS 里获取 generator return 值需额外 API;真实项目常把 result 放在最后一条 StreamEvent

教学中记住:终止 要么体现为 最后的 StreamEvent: done,要么为 query() Promise 解析值——具体以源码为准。


终止后的世界:资源清理

资源动作
HTTP 连接finally 里释放
子进程kill 或等待优雅退出
临时文件清理 sandbox
Telemetryflush()

与八步循环的对照

八步终止关联
6预算失败 → 终止
8无工具 → 正常终止
4不可恢复错误 → 终止
1压缩熔断 → 终止

小结

  • 正常终止 的核心判据:tool_use
  • 预算与用户 是两大「外部刹车」。
  • 熔断与鉴权 是「不能再自欺欺人往下跑」的 硬出口

下一篇:4.10 Thinking 模式

本项目仅用于教育学习目的。Claude Code 源码版权归 Anthropic, PBC 所有。