Skip to content

第 3 步:安全阀

只加一样东西:循环上限。加完 Agent Loop 就完整了。

问题

第 2 步的循环没有终止条件:

ts
for (let step = 1; ; step++) {   // 永远不会自己停

模型返回 answer 时会 return,但如果它永远不返回 answer 呢?程序就卡死了。

这不是假想。真实 LLM 会反复调同一个工具,会因为提示词不清楚而绕圈, 也会因为工具一直报错而不断重试。

加上限

ts
const MAX_STEPS = 10;

function runSafe(task: string): string {
  const messages: Message[] = [{ role: "user", content: task }];

  //                    ↓ 差别 1:循环有了上限
  for (let step = 1; step <= MAX_STEPS; step++) {
    const output = model(messages);

    if (output.type === "answer") return output.content;

    const result = tools[output.name]?.() ?? "unknown tool";
    messages.push({ role: "tool", name: output.name, result });
  }

  // 差别 2:走完上限还没答案 → 主动失败
  throw new Error(`模型在 ${MAX_STEPS} 步内没有完成`);
}

和第 2 步只差这两处。其余代码(模型、工具、消息类型)直接 import 复用:

ts
import { model, tools, type Message, type ModelOutput } from "./2-loop.js";

上限设多少

这是个权衡:

后果
太小(3)复杂任务做不完
太大(1000)模型出错时白烧资源和 token
10学习够用
20~50真实框架的常见区间

运行

bash
npx tsx learn/3-safe.ts

文件里演示了两种模型。正常的:

text
=== 正常模型 ===
用户: 现在几点?天气怎么样?
  [step 1] getTime → 2026/8/15 17:31:44
  [step 2] getWeather → 晴天 28°C
  [step 3] 给出答案
答案: 现在是 2026/8/15 17:31:44,天气 晴天 28°C

坏掉的(永远要工具,从不回答):

text
=== 坏模型:被安全阀拦住 ===
用户: 永远不回答
  [step 1] getTime → ...
  ...
  [step 10] getTime → ...
拦住了: 模型在 10 步内没有完成

完整的 Agent Loop

三步走完,你得到了这个:

text
  用户任务

  ┌──────────────────────────────────────┐
  │ for (step = 1; step <= MAX; step++)  │ ← 第 3 步:安全阀
  │                                      │
  │   model(messages)                    │ ← 第 1 步:模型是函数
  │      ↓                               │
  │   type === "answer" ? → return       │ ← 第 1 步:两种输出
  │      ↓ 否则                           │
  │   执行工具                            │ ← 第 1 步:工具
  │      ↓                               │
  │   messages.push(结果) ───────────────┼─ ← 第 2 步:结果回传
  │      ↓                               │
  └───── 继续下一轮 ─────────────────────┘
     ↓ 超过上限
  throw Error

真实框架多了什么

循环本身不会再变。生产级框架在此之上加的是:

  • 参数校验 —— 模型给的参数类型不对、缺字段怎么办
  • 路径沙箱 —— 模型要读文件时,怎么防止它读到工作区外面
  • 错误回传 —— 工具执行失败时,把错误信息也写进 messages,让模型自己纠正
  • trace 日志 —— 每一步都记下来,事后能完整重放
  • 接入真实 LLM —— 把 model() 换成 API 调用,工具定义转成 JSON Schema

本项目根目录的 mini_harness.ts 就是加上这些之后的完整版,可以对照着看。

MIT 许可证开源