☰
e2e智能体循环源码深读:tool-loop 如何驱动模型-动作循环
2026/10/9 3:52:21 网站建设 项目流程

e2e智能体循环源码深读:tool-loop 如何驱动模型-动作循环

【免费下载链接】e2eNext generation e2e testing framework for web and mobile apps.项目地址: https://gitcode.com/GitHub_Trending/e2e6/e2e

e2e 是一款面向 Web 与移动端的开源 AI 端到端测试框架:你用一句自然语言描述测试目标,智能体就会自己驱动应用去达成它。这篇文章深读 e2e 智能体的"心跳"——tool-loop 循环底盘,带你看懂模型-动作循环是如何被一轮一轮驱动、被安全收口的。

一、先看懂:一次agent.act背后发生了什么

在测试里写一行agent.act('把套餐升级到 Pro'),框架并不会立刻去"点按钮",而是启动一个智能体步骤(step),内部是一个反复转动的循环:

  1. 模型读取当前屏幕(语义树 + 可选截图),产出一个工具调用(如tap、type、scroll);
  2. 循环执行该工具,把"屏幕上发生了什么变化"作为结果送回给模型;
  3. 模型基于新结果决定下一步,直到它认为目标达成或彻底无解;
  4. 模型调用专门的complete_step工具,提交passed / failed / blocked判决,循环结束。

这个"模型 → 动作 → 观察 → 模型"的闭环,就是典型的tool-loop(工具循环)。下面进入源码,看 e2e 如何把它做成一个稳如底盘的机器。

二、循环底盘:tool-loop.ts的主循环

整个循环的核心在 tool-loop.ts,它被作者称为 "chassis"(底盘):

  • createToolLoopExecutor 是底盘的入口。你只需提供三样东西:系统提示词(system)、工具集(tools)、开场提示(buildPrompt),其余的循环纪律全部由底盘继承;
  • 每个步骤构造一个全新的 LoopRun 实例,持有本轮的全部状态:已用轮数、硬性停止信号、循环守卫状态、转录记录等;
  • run() 主循环 是一个for(;;)死循环,靠stopWhen条件刹车。
// packages/e2e/src/agent/tool-loop.ts 主循环骨架(示意) for (;;) { const loop = this.buildLoop(tools); const result = await loop.generate({ prompt, ... }); // 文本回复、上下文溢出、工具选择被拒……各自有恢复路径 // 都不命中时才 break,进入判决结算 }

buildLoop 把循环注册给 AI SDK 的ToolLoopAgent,两条stopWhen就是刹车片:

  • 模型自己刹车:complete_step一旦落判,立即停止;
  • 预算刹车:累计轮数达到maxModelCalls(默认 25 轮)即止,重试也共用同一份轮数预算,绝不"偷偷续命"。

一个很妙的设计哲学写在文件头注释里:硬性停止(预算耗尽/超时/取消)由底盘直接终结循环,绝不把控制权交给模型——预算是外边界,模型只有建议权。

三、每轮必过的"策略关卡":prepareTurn

真正让循环"聪明"的,是 prepareTurn——每发送一次模型请求前,底盘都要按固定顺序检查四件事。

🛡️ 循环守卫:3 次重复先警告,5 次强制收场

模型有时会"转圈":同一操作反复发、或在 A-B-A-B 的短周期里打转。loop-guards.ts 是纯检测器,默认阈值 很克制:

守卫警告强制收场
同一调用(含参数)连续重复3 次5 次
2~4 步短周期重复2 轮3 轮
连续失败的工具结果3 次5 次

检测逻辑在 checkLoopGuards:调用身份 = 工具名 + 精确序列化入参,所以"点两次不同按钮"绝不会被误判为重复。守卫的纪律同样写得很清楚——守卫从不替模型编造判决:警告只是往历史里注入一条[SYSTEM NOTICE]让模型改换思路;强制收场则是下一轮只给它complete_step一个工具,判决依然由模型自己写下。

⏰ 收尾通知:剩 5 轮提醒,剩 2 轮只给"交卷键"

常量定义 里藏着两条时间线:

  • 轮数线:剩余 5 轮时注入"快收尾了"提醒;剩余 2 轮时强制只提供complete_step(留两轮而不是两轮强制,是容忍模型交出一个不合规判决后还有机会修正);
  • 时钟线:剩余步骤时间低于 60 秒(且不超过总预算的 1/4)时,提前要求模型"现在就交最优判决"——因为慢速模型一回合就要几十秒,让模型带着自己的总结离场,远好过一次没有结论的STEP_TIMEOUT。

四、complete_step:判决工具与封闭式错误码

循环的终点是一把"锁"。createVerdictTool 创建了complete_step工具,规则是封闭式的:

  • passed= 应用表现符合预期且已验证;failed= 不符合;blocked= 凭据/环境/测试搭建受阻——blocked必须携带一个封闭清单内的 errorCode,否则被拒回给模型重写(判决规则原文);
  • 第一个被接受的判决是最终的,之后再交卷一律无效;
  • 一个真实踩坑的细节:某些模型习惯在 passed 判决上附带ACTION_FAILED,早期用 schema 拒绝会导致它反复重发直到预算耗尽——现在的策略是丢弃多余码并明确告知,而不是打回。

五、异常恢复:把"翻车"翻译成模型能读懂的话

tool-loop 里最见功力的,是它几乎不给"硬失败"留空间,每种异常都有恢复路径:

  • 普通工具失败→ 经 guard() 包装,变成tap failed: ...文本送回模型,它自己决定换路;
  • 模型用纯文本回复、没有工具调用→ continueAfterTextReply 注入一条"你上轮什么也没干"的系统提示,在同一轮预算上再问一次;
  • 模型拒绝强制工具选择(HTTP 400)或悄悄降级→ freeToolChoiceRetry 把该模型记入进程级记忆,后续改用auto模式 + "只允许工具调用"规则,代价只是每模型一次往返;
  • 上下文窗口溢出→ overflowRetry 收缩历史(被取代的整屏省略、超长文本截断)后只重试一次,第二次溢出才真正报错。

此外每个回合都被 recordTurn 逐条记账,既供--debug输出完整转录,也在报告里保留末尾 12 轮的裁剪版——循环的每一步都可审计。

六、内置智能体 = 底盘 + 语法工具集

现在可以回到全貌。内置智能体在 default-agent.ts 中装配,结构小到一眼看穿:

  • 底盘(tool-loop.ts):判决、预算、硬停止、循环守卫、收尾通知、转录——文件头注释 说得直白:"换掉大脑,纪律不变";
  • 工具词汇表:createGrammarTools 把每个手势做成独立工具——tap、double_tap、long_press、right_click、hover,外加像素坐标系的tap_at等。作者特意不用"一个工具 + kind 参数",因为实测有模型会给普通按钮乱填kind导致双击——工具必须被"选择",描述才能把边界说清;
  • 基线规则:BASE_RULES 教会模型核心世界观:屏幕是带稳定 id 的节点树、每个动作结果已含变化(所以动作后不必再 observe)、绝不臆造 id、只有看到证据才判 passed。

而这个底盘是可复用的:设备端执行器、API 执行器都能调用createToolLoopExecutor,换上自己的工具词表,就免费继承整套循环纪律——这正是 executor.ts 定义的"步骤执行器插座"的意图:换执行器只换思考方式,预算、录制、判决语法一概不动。

七、新手源码阅读路线图

如果读完本文想亲自过一遍,建议按这个顺序读,约 1500 行核心代码:

  1. tool-loop.ts — 主循环、prepareTurn、四类恢复路径(全文重点);
  2. loop-guards.ts — 228 行的纯函数守卫,最适合入门;
  3. primitives.ts —complete_step判决工具与语法工具集;
  4. default-agent.ts — 内置智能体的最终装配;
  5. executor.ts — 理解"框架管预算、执行器管思考"的信任模型。

📌 一句话总结:e2e 的 tool-loop 把"让模型自由行动"变成了"让模型在有刹车、有护栏、有审计的轨道上行动"——这大概就是智能体测试框架能进入生产 CI 的关键差别。

【免费下载链接】e2eNext generation e2e testing framework for web and mobile apps.项目地址: https://gitcode.com/GitHub_Trending/e2e6/e2e

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询