ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从Agent Loop到PlanMode:一套能跑生产的AI Agent工程骨架

从Agent Loop到PlanMode:一套能跑生产的AI Agent工程骨架 类比一下来记忆整篇文章所讲的知识的体系Harness是操作系统——管所有资源的顶层容器LLM是CPU——吃进指令吐出结果Agent Loop 是内核主调度循环——不停地取任务、调度、等I/O返回、再取下一个Two-Stage ReAct是操作系统的模拟执行dry-run模式——先试运行看计划确认了再真正执行四个工具是文件系统的几种基本操作——read、write、edit、bash对应rwx权限模型AGENTS.md/Skills/PlanMode是三级存储体系——BIOS、硬盘、内存磁盘持久化。一、Agent Loop内核主调度循环所有Agent的本质就是一个while(!done)。while not terminated: 1. 组装上下文system prompt历史工具声明本次观测 2. 把上下文丢给LLM 3. 解析LLM返回 3.1 文本段 → 追加进上下文历史 3.2 有tool_calls → 逐个执行结果写回历史continue 3.3 无tool_calls → 终止循环返回文本就像操作系统内核的主调度循环从就绪队列取进程 → 分配 CPU 时间片 → 等 I/O 中断返回 → 再调度下一个。Agent Loop是不停地组装上下文 → 丢给 LLM → 等返回 → 执行工具I/O→ 写回历史 → 再循环。是整个系统的心脏。跑稳这个循环要想清楚三件事结束条件无tool_call、命中max_turns、用户取消、校验器判完成。没有明确的终止条件loop会一直转token烧到破产。验证方法别信模型自己说做完而是通过跑测试、diff检查、断言脚本。持久化状态上下文历史、PLAN.md/TODO.md、工具调用日志。上下文压缩会丢吞掉早期信息要写在磁盘上。适合上Agent Loop的场景高频每周至少跑1次一次性任务没必要别上Agent。几乎无需人工介入。失败可以重跑。二、Two-Stage ReAct先Dry-run再真正执行直接把工具声明丢给LLM它会非常冲动地去调用工具而不是先思考该怎么做。Two-Stage ReAct把“想”和“做”拆成两次独立的LLM调用让LLM克制一下欲望物理上隔离Stage 1无工具prompt只给任务上下文不暴露任何工具 → LLM输出计划/推理链/假设dry-run Stage 2带工具prompt把Stage1的输出拼回去再暴露工具声明 → LLM按计划调read/bash/edit...就像操作系统的dry-run模式——apt-get install -s、kubectl apply --dry-run、make -n。第一次调用是模拟执行不碰任何系统状态只输出打算做什么第二次才是真正执行修改状态。为什么即使模型已经具备“原生思考”能力这一层依然有工程价值介入点Stage1和Stage2之间可以拦下来——改计划、人工审批、合规审计。原生思考是黑盒拦不住。可观测性Two-Stage让计划显式成文本能diff、能回滚、能复盘。原生thinking块看得到但很难审计。多模型兼容任何程序都能dry-run不需要程序本身支持什么特殊能力。换一个便宜的小模型也能跑不必依赖具备原生思考能力的大模型。什么时候开启思考不是每轮都开贵新任务目标刚下发时先 dry-run 出计划工具返回非预期结果执行出错了重新dry-run想想要不要换方案涉及删除、推送、付费、写生产库等高危动作前大操作先试运行三、工具文件系统的几种基本操作只给Harness四个基础工具read、write、edit、bash。这正好对应文件系统/权限模型里的几种基本操作工具文件系统操作作用read读文件openread无副作用只看不动write写文件覆盖像重定向整文件覆盖edit修改文件内容像sed -i精准patch只改该改的那几行bash执行程序execute跑任何命令一条顶十个专用工具为什么有write还要editwrite是整文件覆盖文件一大token消耗极其巨大既慢又贵。而且大模型生成长文本时极易截断或者不小心引入新的语法错误。edit像sed -i只动需要改的那几行diff 可控、可评审、可回滚。给太多工具等于chmod 777乱开权限不是能力更强而是模型更容易冲动调用不该用的工具出错面反而更大。而且容易限制聪明模型的主动性聪明模型本来可以做更好的操作但看到你已经提供一个现成的某种操作后根据你给的描述以为它更合适就用了但模型并不知道你是怎么实现的可能并不是模型真正想要的操作。四个基础操作加上bash的万能执行能力已经能覆盖绝大多数任务。edit的唯一性校验必做模糊匹配若命中超过1处工具绝对不能盲目替换——就像sed匹配到多行时如果不指定行号就全局替换会把不该改的行一起改掉。必须抛出错误要求大模型提供更多的上下行代码以精确定位。这是防止Agent改代码时顺手改错另一个相似片段的措施。def apply_edit(text, old, new): cnt text.count(old) if cnt 0: raise ToolError(old_text not found) if cnt 1: raise ToolError(ambiguous match (1); provide more surrounding context) return text.replace(old, new, 1)四、Harness执行效率的提升只读并发涉写串行模型一轮推理可能吐出多个tool_call如果harness都一个一个串行执行效率就低了但harness也不应该无脑串行——要有合适的调度策略一般如果都是只读工具就可以并行。func dispatch(calls []ToolCall) { var readonly, writable []ToolCall for _, c : range calls { if isReadOnly(c) { readonly append(readonly, c) } else { writable append(writable, c) } } if len(writeable)0: // 只读批并发 goroutine fanOut(readonly) else: // 涉写批严格顺序执行 for _, c : range calls { exec(c) } }这个策略以极低的复杂度在绝大多数场景下同时保证了性能与正确性。五、三级记忆AGENTS.md/Skills/PlanMode1. AGENTS.md解决的是“当前项目是什么样”的问题。可以类比于操作系统BIOS信息——启动即加载告诉系统这台机器什么配置、什么目录结构、有什么禁区。Agent启动时读一次就知道自己在哪个项目里干活。注claudecode目前还是固执地读CLAUDE.md它的作用跟AGENTS.md是一样的。2. Skills解决的是“特定任务该怎么做”的问题。可以类比于硬盘上按需加载的二进制程序命中对应场景才加载进上下文CPU。AGENTS.md告诉机器是什么配置Skills告诉这个任务该调哪个“程序”。3. PlanModePLAN.md宏观路由表战略方向。保证Agent在跨越几十轮对话的长任务中不跑偏。TODO.md调度队列每完成一项划掉一项。上下文压缩就像内存回收会丢数据——早期对话历史被compact掉就没了。所以得定期flush到磁盘把进展写进PLAN.md/TODO.md防止掉电压缩丢失。Agent醒来重新读磁盘就知道该接着干啥。ThinkingPhase慢思考是 CPU 的流水线级纠错哪怕TODO.md里已经写了新任务如果没有每一轮的慢思考约束模型仍可能在选择具体实现路径时走捷径——跳过边界条件验证、不加权衡地选第一个能跑的方案。慢思考是每一步的即时纠偏PlanMode是磁盘上的状态兜底两者互补。六、串成一句话Harness操作系统 ├── LLMCPU算力核心 │ └── Agent Loop内核主调度循环 │ └── Two-Stage ReActdry-run模拟执行 → 真正执行 ├── 工具集文件系统基本操作read/write/edit/bash └── 三级记忆BIOS/硬盘/内存磁盘持久化跑得稳的Agent不是模型最猛的那个是Loop有终止条件、工具有边界约束、读写操作有并行串行优化、长任务有磁盘持久化兜底的那一个。附可直接参考的Loop伪代码def agent_loop(task, max_turns30): ctx bootstrap(task) #AGENTS.mdSkills发现历史 for turn in range(max_turns): if need_plan(ctx): #PlanMode介入 ctx two_stage_plan(ctx) resp llm(ctx) #Stage2带工具 ctx.append(resp.text) if not resp.tool_calls: return resp.text #终止条件 batch group_by_readonly(resp.tool_calls) for r in concurrent_run(batch.readonly): ctx.append(r.result) for w in sequential_run(batch.writable): if w.name edit and match_count(w) ! 1: ctx.append(edit_ambiguity_error(w)) break ctx.append(w.result) raise Timeout(max_turns reached)
返回列表