跳到主要内容

快速开始:30 分钟搭出可收敛的 Agent Loop

七天实战

可视化直觉可打开 七天学会 Loop Engineering;本篇目标是:在本机写出一个带迭代预算、停止条件与循环指纹的 ReAct Runtime,并把它接到你已有的 Harness / 工具层。

学完本篇你能交付什么

  1. 一张「目标 → 循环 → 停止」的任务卡(一页纸)。
  2. 一段可运行的伪代码/Python 骨架:run_react_loop(goal, tools, budget)
  3. 至少一次「故意制造死循环再被熔断」的对照实验记录。
  4. 一份最小 Trace JSON,能回答:第几步、调了什么、为何停下。

若你还不会解析 tool_calls,先完成 Hermes 快速开始DSH 快速开始,再回来。


一、先画任务卡,再写循环

1.1 任务卡模板

字段示例说明
目标修复 tests/test_login.py 全部失败用户语言
验收(成功停止)pytest tests/test_login.py exit 0必须可机判
失败停止同一文件连续 3 次相同 diff 失败防空转
预算max_steps=12, max_tokens=80k, max_shell=8多维
允许工具read_file, edit_file, run_pytest白名单
禁止改测试断言来「变绿」Harness 策略
拓扑Plan–Execute(计划≤5 步)+ 内环 ReAct选型理由一句话

常见坑:验收写成「代码质量更好」——模型无法可靠停止。先把验收降维成测试、schema、HTTP 200、文件存在等。

1.2 开环基线(对照实验必做)

先跑一次 开环:模型一次性生成补丁,不读测试结果。记录:是否通过、耗时、token。再跑闭环。没有基线,你不知道 Loop 是在帮忙还是在烧钱。


二、最小 ReAct Runtime 骨架

下面用 Python 风格伪代码说明契约;真实项目可换成 TypeScript / 你们 Harness 的 Session API。

# 中文注释:最小可收敛 ReAct 循环骨架
from dataclasses import dataclass, field
from typing import Any, Callable
import hashlib, json, time

@dataclass
class Budget:
max_steps: int = 10
max_tokens: int = 50_000
max_wall_sec: float = 120.0
steps: int = 0
tokens: int = 0
t0: float = field(default_factory=time.time)

def exhausted(self) -> str | None:
if self.steps >= self.max_steps:
return "max_steps"
if self.tokens >= self.max_tokens:
return "max_tokens"
if time.time() - self.t0 >= self.max_wall_sec:
return "wall_clock"
return None

@dataclass
class TraceEvent:
step: int
kind: str
payload: dict

class LoopFingerprint:
"""中文注释:检测几乎相同的工具调用重复出现"""
def __init__(self, limit: int = 3):
self.limit = limit
self.counts: dict[str, int] = {}

def hit(self, tool: str, args: dict) -> bool:
key = tool + ":" + hashlib.sha256(
json.dumps(args, sort_keys=True).encode()
).hexdigest()[:16]
self.counts[key] = self.counts.get(key, 0) + 1
return self.counts[key] >= self.limit

def compress_observation(obs: str, limit: int = 2000) -> str:
"""中文注释:防止 Observation 撑爆上下文"""
if len(obs) <= limit:
return obs
return obs[: limit // 2] + "\n...[truncated]...\n" + obs[-limit // 2 :]

def run_react_loop(
goal: str,
messages: list,
call_llm: Callable[[list], dict],
call_tool: Callable[[str, dict], str],
is_success: Callable[[list], bool],
budget: Budget,
) -> dict:
# 中文注释:返回最终答案、停止原因与轨迹
fp = LoopFingerprint(limit=3)
trace: list[TraceEvent] = []

while True:
reason = budget.exhausted()
if reason:
return {"ok": False, "stop": reason, "trace": trace, "messages": messages}
if is_success(messages):
return {"ok": True, "stop": "acceptance", "trace": trace, "messages": messages}

budget.steps += 1
out = call_llm(messages)
budget.tokens += int(out.get("usage", 0))
trace.append(TraceEvent(budget.steps, "llm", {"raw": str(out)[:500]}))

tool_calls = out.get("tool_calls") or []
if not tool_calls:
messages.append({"role": "assistant", "content": out.get("content", "")})
if is_success(messages):
return {"ok": True, "stop": "acceptance", "trace": trace, "messages": messages}
continue

for tc in tool_calls:
name, args = tc["name"], tc.get("arguments") or {}
if fp.hit(name, args):
return {"ok": False, "stop": "fingerprint_loop", "trace": trace, "messages": messages}
obs = compress_observation(call_tool(name, args))
messages.append({"role": "tool", "name": name, "content": obs})
trace.append(TraceEvent(budget.steps, "tool", {"name": name, "args": args}))

2.1 你必须自己填的三块

钩子职责坏实现
call_llm带 tools schema 调模型忽略 usage,预算形同虚设
call_tool走 Harness(超时/沙箱)直接 os.system
is_success外部验收if "完成" in content

三、第一次练习:调研 Agent(只读工具)

3.1 场景

目标:「总结某开源 Agent 框架的 Loop 机制,列出停止条件相关 API」。
工具:web_searchfetch_urlwrite_notes(只写本地 notes.md)。
验收:notes.md 含三个二级标题且每节≥120 字;禁止改系统文件。

3.2 步骤

  1. 写任务卡。
  2. 开环:一次生成大纲,不搜索——往往空洞。
  3. 闭环:max_steps=8,指纹 limit=2(同 query 搜两次即停)。
  4. 对比 Trace:闭环是否出现「搜索→摘录→再搜补充」而非同词死磕。

3.3 预期失败与处理

现象原因处理
第 3 步就 fingerprint_loopquery 不变在 Observation 后注入「换关键词」提示,或降低 limit
notes 很短但模型自称完成验收太弱用字数/标题规则
token 先爆网页全文回灌compress_observation + 只留引用段

四、第二次练习:修测试直到绿(强验收)

4.1 拓扑选型

推荐 Plan–Execute

  1. 外环:让模型输出 JSON 计划 [{id, action, done_when}],最多 5 步。
  2. 内环:对当前步跑 ReAct(read/edit/pytest)。
  3. run_pytest 失败且错误签名未变 ≥3 次 → Replan(计入 max_replan=2)。

4.2 错误签名(比指纹更贴近修 bug)

# 中文注释:从 pytest 输出提取稳定签名
import re, hashlib

def error_signature(pytest_out: str) -> str:
lines = [ln for ln in pytest_out.splitlines() if "FAILED" in ln or "Error" in ln]
blob = "\n".join(lines[:20])
blob = re.sub(r"line \d+", "line N", blob)
return hashlib.sha256(blob.encode()).hexdigest()[:12]

把「相同签名重复」作为失败停止或 Replan 触发器,比「步数用完」更早救场。

4.3 安全门禁(Harness 协作)

  • edit_file 路径白名单:仅 src/,禁止擅自改测试断言(或需审批)。
  • run_pytest 超时 60s,禁止网络。
  • Trace 必须记录 diff 摘要,便于审计。

五、停止条件设计工作坊

5.1 三类谓词

类型例子优先级
成功测试绿 / JSON schema 过 / 人工点通过最高
失败指纹环、权限拒绝、不可恢复错误
预算steps/tokens/墙钟/费用保底
def stop_decision(ctx) -> str | None:
if ctx.acceptance():
return "success"
if ctx.fatal_error():
return "fatal"
if ctx.fingerprint_loop():
return "loop_detected"
if ctx.budget.exhausted():
return "budget"
return None

5.2 反模式:「模型说 done」

仅当 同时 满足:无待执行 tool_call、且外部验收通过,才可采信 Final Answer。否则把「我做完了」当噪声。


六、Trace:最小可回放字段

{
"run_id": "loop-20260306-001",
"goal": "fix login tests",
"topology": "plan_execute",
"budget": {"max_steps": 12, "max_tokens": 80000},
"events": [
{"step": 1, "type": "plan", "steps": ["read failing test", "patch auth", "pytest"]},
{"step": 2, "type": "tool", "name": "run_pytest", "sig": "a1b2c3", "ok": false},
{"step": 5, "type": "stop", "reason": "acceptance", "ok": true}
],
"spend": {"steps": 5, "tokens": 12400, "seconds": 47}
}

验收你的快速开始:给同事只看 Trace,不看聊天窗口,对方能复述发生了什么。做不到 = 还不可观测。


七、把循环接到 Harness(对照 DSH / Hermes)

能力放在 Harness放在 Loop
工具 Schema / 权限只消费白名单
沙箱与超时把超时当 Observation
Session 持久化决定何时 checkpoint
max_steps / 指纹可辅助主责
验收脚本可作为工具主责调用时机
Persona避免每步重写人设

集成口诀:Harness 保证「这一锤」安全;Loop 保证「这组锤」收敛。

若使用 DeepSeek Harness:把 call_tool 换成 Cordis 插件调用,并在 Session 挂载 Trace。若使用 Hermes 标签:在 call_llm 后统一解析,不要让 Loop 与解析器各搞一套消息格式。


八、30 分钟时间盒

分钟动作完成定义
0–5写任务卡验收可机判
5–15粘贴骨架并接一个 echo 工具能空跑停止
15–22接真实 search 或 pytest跑通一次
22–27故意同参调工具 3 次触发 fingerprint
27–30导出 Trace JSON字段齐全

超时未完成?缩小目标:只做「echo + 指纹熔断」也算达标。


九、Reflection 迷你实验(可选加餐)

在调研 Agent 成功后,加一轮:

  1. 用另一套 Prompt:「按清单检查 notes:是否引用具体仓库路径、是否编造 API」。
  2. 若反思列出缺口 → 最多再开 3 步 ReAct 补洞。
  3. max_reflect=1,禁止无限自我批评。

你会看到:反思若无外部清单,容易变成「文笔润色环」——生产上优先单元测试型 Evaluator。


十、检查单(贴在显示器边)

  • 验收不依赖模型自称
  • 至少三维预算
  • Observation 有压缩
  • 有指纹或错误签名
  • Trace 可回放
  • 工具走 Harness 而非裸执行
  • 开环基线已记录
  • 停止原因枚举有限且可监控告警

全部勾上,再进入 开发指南 学 Plan–Execute、Eval–Optimize 与多 Agent 编排的实现细节。


十一、TypeScript 对照片段(前端/Node Harness)

// 中文注释:Agent Loop 的停止裁决与预算扣减
type StopReason = "success" | "fatal" | "fingerprint" | "budget" | null;

interface LoopState {
step: number;
tokens: number;
maxSteps: number;
maxTokens: number;
}

function nextStop(state: LoopState, opts: {
accepted: boolean;
fatal: boolean;
fingerprinted: boolean;
}): StopReason {
if (opts.accepted) return "success";
if (opts.fatal) return "fatal";
if (opts.fingerprinted) return "fingerprint";
if (state.step >= state.maxSteps || state.tokens >= state.maxTokens) return "budget";
return null;
}

nextStop 测成纯函数:这是 Loop 层最值得单测的部分——比测 Prompt 文案稳得多。


十二、常见起步问题速答

Q:我要不要一开始就上 LangGraph?
A:先手写 80 行循环。等你痛过「状态丢了 / 无法 checkpoint」再引入图框架,收益最大。

Q:temperature 调低是不是就更稳?
A:它影响采样,不替代停止条件。低温照样会死循环。

Q:子站实验室和这篇什么关系?
A:用来感受「有反馈 vs 无反馈」。请把「传感器」映射成 Observation/评测,「执行器」映射成 tool,「饱和」映射成预算。不要在仓库里引入控制库来「控制 Agent」。

Q:多久算学会了快速开始?
A:你能在新任务上 10 分钟填完任务卡,并让指纹熔断在演示中稳定触发。

下一步:开发指南 将展开六类循环的实现清单、并发工具调用、以及生产级失败模式库。