多数 AI 应用仍停留在“人提问、模型回答、人再修正”的交互方式里。Loop Engineering 改变的是责任边界:开发者不再逐轮指导模型,而是设计一套能够代替自己持续指导模型的系统,包括目标、工具、反馈、预算、停止条件与审计记录。
这并不是某个新的 Agent 框架。它更像一种工程方法:把一次性的提示词,升级为一个可验证、可恢复、可约束的执行循环。
真正需要设计的不是 Prompt,而是 Loop
一个最小 Agent 循环通常包含四个动作:
- 读取当前任务和运行状态。
- 让模型选择下一步动作。
- 执行动作并收集结果。
- 将结果反馈给模型,直到任务完成或触发停止条件。
可以把它抽象成下面的状态转换:
目标 + 当前状态
↓
模型决策
↓
工具调用 → 结构化结果 → 状态更新
↑ ↓
└──── 未完成则继续 ──┘
这里最重要的变化是:模型不直接拥有系统控制权。模型只能提出结构化意图,循环控制器负责验证参数、调用工具、记录结果,并判断是否允许继续。
因此,Loop Engineering 的核心工作通常不是反复润色一句提示词,而是回答这些工程问题:
- 模型能执行哪些动作,哪些动作必须禁止?
- 每个动作返回什么结构,失败是否可以重试?
- 什么证据可以证明任务已经完成?
- 最多允许多少轮、多少 Token、多少费用和多少运行时间?
- 进程中断后,能否从最近一次状态继续?
- 人类应该在哪些风险节点介入审批?
一个可以运行的受控 Agent 循环
下面是一个最小实践示例。它使用确定性的 decide 函数模拟模型,因此不需要 API Key,可以直接运行。接入真实 LLM 时,只需替换该函数,并要求模型返回相同的 JSON 结构。
将代码保存为 agent_loop.py,然后在任意 Python 项目目录执行 python agent_loop.py。示例只开放“检查 README”和“运行单元测试”两个工具,并把每一步写入 agent-events.jsonl。
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
WORKSPACE = Path.cwd().resolve()
MAX_STEPS = 6
LOG_FILE = WORKSPACE / "agent-events.jsonl"
def check_file(path: str) -> dict[str, Any]:
target = (WORKSPACE / path).resolve()
if WORKSPACE not in target.parents and target != WORKSPACE:
return {"ok": False, "error": "path escapes workspace"}
if not target.is_file():
return {"ok": False, "error": f"missing file: {path}"}
return {"ok": True, "path": path, "size": target.stat().st_size}
def run_tests() -> dict[str, Any]:
command = [sys.executable, "-m", "unittest", "discover"]
try:
result = subprocess.run(
command,
cwd=WORKSPACE,
capture_output=True,
text=True,
timeout=30,
)
except subprocess.TimeoutExpired:
return {"ok": False, "error": "tests timed out"}
output = (result.stdout + result.stderr)[-4000:]
return {
"ok": result.returncode == 0,
"returncode": result.returncode,
"output": output,
}
TOOLS = {
"check_file": lambda args: check_file(args["path"]),
"run_tests": lambda args: run_tests(),
}
def decide(history: list[dict[str, Any]]) -> dict[str, Any]:
"""确定性模型替身;生产环境可替换为返回 JSON 的 LLM 调用。"""
if not history:
return {
"action": "tool",
"tool": "check_file",
"args": {"path": "README.md"},
"reason": "确认项目说明文件存在",
}
if len(history) == 1:
return {
"action": "tool",
"tool": "run_tests",
"args": {},
"reason": "收集可验证的测试结果",
}
tests = history[-1]["result"]
return {
"action": "finish",
"status": "completed" if tests.get("ok") else "needs_human",
"summary": "测试通过" if tests.get("ok") else "测试失败,需要人工检查",
}
def append_event(event: dict[str, Any]) -> None:
with LOG_FILE.open("a", encoding="utf-8") as file:
file.write(json.dumps(event, ensure_ascii=False) + "\n")
def main() -> None:
history: list[dict[str, Any]] = []
for step in range(1, MAX_STEPS + 1):
decision = decide(history)
append_event({"step": step, "type": "decision", "data": decision})
if decision.get("action") == "finish":
print(json.dumps(decision, ensure_ascii=False, indent=2))
return
tool_name = decision.get("tool")
if tool_name not in TOOLS:
raise RuntimeError(f"tool is not allowed: {tool_name}")
result = TOOLS[tool_name](decision.get("args", {}))
observation = {"decision": decision, "result": result}
history.append(observation)
append_event({"step": step, "type": "observation", "data": result})
print(json.dumps({
"action": "finish",
"status": "budget_exhausted",
"summary": f"超过最大步数 {MAX_STEPS}",
}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
这个示例刻意把“决策”和“执行”分开。真实模型可以决定调用 check_file,但无法自行读取工作区之外的文件,也无法把任意字符串交给 Shell。即使模型输出了不存在的工具名,控制器也会拒绝执行。
接入 LLM 时,可以这样约束决策输出:
你是项目检查 Agent。根据目标、历史动作和工具结果选择下一步。
只允许输出 JSON,不要输出解释文字。
可用动作:
1. {"action":"tool","tool":"check_file","args":{"path":"..."},"reason":"..."}
2. {"action":"tool","tool":"run_tests","args":{},"reason":"..."}
3. {"action":"finish","status":"completed|needs_human","summary":"..."}
规则:
- 不得虚构工具执行结果。
- 完成状态必须有测试结果作为证据。
- 同一个失败动作最多重试一次。
- 信息不足或风险不可判断时返回 needs_human。
在生产系统中,还应使用 JSON Schema 或 SDK 的结构化输出能力校验响应,而不是依赖字符串格式恰好正确。
让循环可靠的五个控制面
1. 状态必须显式保存
只把历史放在模型上下文中,会同时遇到上下文长度、费用和故障恢复问题。任务目标、动作结果、重试次数、资源消耗和审批状态应保存在数据库或事件日志中。模型上下文只是状态的一个投影,不应成为唯一事实来源。
2. 工具应当小而窄
与其开放一个可以执行任意命令的 shell,更稳妥的做法是提供 run_tests、read_issue、create_patch、request_review 这类边界清晰的工具。工具参数应做类型校验、路径限制、超时控制和输出截断。
3. 完成需要证据
模型说“已经完成”不等于任务真的完成。代码 Agent 可以要求测试退出码、静态检查结果和变更摘要;数据 Agent 可以要求行数、校验和与异常记录;运维 Agent 可以要求健康检查和回滚点。
4. 预算是循环的一部分
循环必须具备硬停止条件,例如最大步数、最大持续时间、最大 Token、最大费用和连续失败次数。没有预算的自主运行,本质上是无法预测成本和影响范围的后台任务。
5. 高风险动作需要审批
读取文档与删除数据不应拥有相同权限。可以按风险把工具分为自动执行、执行前确认、禁止执行三类。涉及生产发布、资金、权限、外部通信和不可逆写入时,应把人工审批设计为状态机中的正式节点。
从小闭环开始,而不是追求全自主
采用 Loop Engineering 时,可以先选择一个结果容易验证、工具数量有限、失败成本较低的任务,例如检查仓库健康状态、整理工单信息或生成待审核补丁。
上线前至少检查以下事项:
- 目标和完成条件能否被机器验证?
- 模型输出是否经过结构化校验?
- 工具是否使用最小权限并限制参数?
- 是否设置步数、时间、费用和重试预算?
- 每一步是否留下可检索的审计记录?
- 中断后是否能够恢复或安全重跑?
- 高风险动作是否进入人工审批?
Loop Engineering 的价值不在于让模型无限运行,而在于把模型放进一个边界明确的反馈系统。真正可以代表开发者工作的,不是一段更长的 Prompt,而是一套能够观察、决策、执行、验证,并在不确定时停下来的工程循环。