一、什么是 Loop 工程
"智能体(Agent)"和"一次性调用大模型"最大的区别,在于循环。单次调用是"你问一句、它答一句";而 Agent 会反复执行一个闭环:先感知环境(读状态/读工具返回),再推理下一步,再行动(调工具/写文件),再观察结果,然后带着新信息进入下一轮。这个不断自转的环,就是 Loop 工程的研究对象。工程化的核心问题不是"模型够不够聪明",而是:这个环怎么停、怎么不死循环、怎么不爆上下文、怎么在出错时自愈。
一个常见误区是把"循环"和"工作流编排"混为一谈。两者都涉及多步执行,但工作流是固定 DAG (步骤和顺序写死,如"先查库再发邮件"),而 Agent 循环是动态决策(下一步由模型根据观测现场决定)。选哪种,取决于问题是否"路径已知":路径已知用 DAG 更可控、更省钱;路径未知、需要边走边看,才上自主循环。Loop 工程研究的就是后者------也是当前大模型落地最有含金量的部分。

二、循环必须有明确的终止条件
新手写 Agent 最容易踩的坑,是以为"模型自己知道什么时候停"。事实上,循环必须由显式终止条件驱动:要么是目标达成(如"进度达到 100%"),要么是达到最大轮次(硬上限)。没有最大轮次,一旦目标不可达,Agent 会一直转下去------既烧 token 又烧钱。我把"设 max_steps"列为 Loop 工程的第一铁律。
避坑:终止判断要落在"可观测的状态"上,而不是"模型说完了"。让模型自己决定停,容易出现"它以为完成了但事实没完成"。用工具返回/外部指标做终止判据更稳。
终止条件常见有四类:目标型(外部指标达标即停)、步数型(到 max_steps 停)、成本型(累计 token/费用超阈值停)、以及人工型(人随时喊停)。生产系统往往四类并用------尤其成本型,能防止一个失控任务把整月预算烧光。把终止条件写成配置而非硬编码,不同任务可灵活调整,也方便灰度放行新场景。
三、把循环建模成状态机
更工程化的做法是把循环看成状态机:每一轮都是一次"状态转移",整个运行有清晰的阶段(运行中 / 已完成 / 失败 / 转人工)。这样做的好处是可观测、可中断、可回放------你能随时知道 Agent 转到了第几轮、卡在哪一步,出了事也能从某轮快照恢复,而不是一团黑箱。

# -*- coding: utf-8 -*-
"""
零依赖(仅标准库)。演示一个 Agent 的核心循环:
observe(感知) -> reason(推理/规划) -> act(行动/调用工具) -> observe(观察结果) -> 循环
直到任务完成或达到最大轮次。
要点:
- 循环不是"无限递归",而是带明确终止条件的状态机。
- reason 这里用规则模拟(真实系统由 LLM 产出下一步动作)。
- act 调用一个"工具"(这里用计算器模拟外部能力)。
- 演示"为什么必须设 max_steps":防止目标不可达时死循环烧钱。
运行:
python agent_loop_sim.py
"""
from dataclasses import dataclass, field
@dataclass
class AgentState:
task: str
goal_progress: int = 0 # 目标完成度 0~100
steps: int = 0
trace: list = field(default_factory=list)
done: bool = False
def reason(state, fixed_step=None):
"""模拟 LLM 规划:根据当前进度决定下一步动作。"""
if state.goal_progress >= 100:
return ("finish", None)
# 固定步长(演示"难任务小步慢爬");否则按进度自适应
if fixed_step is not None:
return ("act", {"tool": "compute", "amount": fixed_step})
if state.goal_progress < 40:
return ("act", {"tool": "compute", "amount": 35})
return ("act", {"tool": "compute", "amount": 25})
def act(action):
"""模拟工具调用:返回观测结果。"""
tool = action.get("tool")
amount = action.get("amount", 0)
if tool == "compute":
# 模拟某计算型工具,带一次随机失败演示后续文章的"自愈"
return f"工具返回:推进了 {amount} 个单位"
return "未知工具"
def run_loop(task, max_steps=8, fixed_step=None):
s = AgentState(task=task)
print(f"任务:{task} (最大轮次={max_steps})")
while not s.done and s.steps < max_steps:
s.steps += 1
# 1) observe:读取当前进度(感知)
# 2) reason:规划下一步
kind, payload = reason(s, fixed_step)
if kind == "finish":
s.done = True
s.trace.append(f" [第{s.steps}轮] 推理=完成 -> 终止")
break
# 3) act:执行动作
obs = act(payload)
# 4) observe:根据观测更新状态
s.goal_progress = min(100, s.goal_progress + payload.get("amount", 0))
s.trace.append(f" [第{s.steps}轮] 动作={kind}(+{payload.get('amount')}) "
f"观测='{obs}' 进度={s.goal_progress}%")
print("\n".join(s.trace))
status = "已完成 ✅" if s.done else f"未达成 ⚠ 触及最大轮次 {max_steps}(防死循环)"
print(f"\n结果:{status} 最终进度={s.goal_progress}%")
return s
if __name__ == "__main__":
run_loop("把系统负载降到安全区间")
print("-" * 50)
# 目标不可达的极端演示:每轮只推进 5,上限 4 轮,最多到 20%
run_loop("极高难度任务(每轮仅+5,上限4轮)", max_steps=4, fixed_step=5)
运行 python agent_loop_sim.py 即可看到上面的自转过程:前 4 轮靠工具把进度推到 100%,第 5 轮感知到目标达成,推理返回"finish"从而干净终止。脚本还附带了一个"难任务"用例------每轮只 +5、上限 4 轮,进度停在 20%,由 max_steps 强制终止,直观演示防死循环机制。

可观测性落到实处,就是给每一轮打日志:轮次号、本轮输入、模型决策、工具调用、返回、状态变更。这些日志既是"出事时复盘"的依据,也是"训练评测集"的原料------把跑挂的案例收集起来,正是迭代 Agent 提示词和工具设计的最佳素材。很多团队忽略日志,结果 Agent 第二次犯同一个错,毫无据可查。状态机之所以重要,正是因为它让这些日志有统一的结构可读。

四、总结
Loop 工程的第一步,是把"Agent 会自己循环"这件事工程化:用感知---推理---行动---观察四段式定义每一轮,用显式终止条件(目标达成或 max_steps)防止死循环,用状态机让运行可观测、可回放。这层骨架立住之后,后面两篇要讲的"上下文记忆"和"可靠性护栏"才有挂载的地方。