<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>浏览器自动化 on 搬砖程序员带你飞</title><link>https://lpflpf.cn/tags/%E6%B5%8F%E8%A7%88%E5%99%A8%E8%87%AA%E5%8A%A8%E5%8C%96/</link><description>Recent content in 浏览器自动化 on 搬砖程序员带你飞</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>lipengfei634626165@163.com (lpflpf)</managingEditor><webMaster>lipengfei634626165@163.com (lpflpf)</webMaster><lastBuildDate>Mon, 21 Sep 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://lpflpf.cn/tags/%E6%B5%8F%E8%A7%88%E5%99%A8%E8%87%AA%E5%8A%A8%E5%8C%96/index.xml" rel="self" type="application/rss+xml"/><item><title>1092 次浏览器往返砍到 101 次：jev-ultrafast 教我的 Agent 延迟归因</title><link>https://lpflpf.cn/posts/jev-ultrafast-decision-loop-2026-09-21/</link><pubDate>Mon, 21 Sep 2026 09:00:00 +0800</pubDate><author>lipengfei634626165@163.com (lpflpf)</author><guid>https://lpflpf.cn/posts/jev-ultrafast-decision-loop-2026-09-21/</guid><description>&lt;img src="https://cdn.lpflpf.cn/covers/jev-ultrafast-decision-loop-plain.jpg" alt="Featured image of post 1092 次浏览器往返砍到 101 次：jev-ultrafast 教我的 Agent 延迟归因" /&gt;&lt;p&gt;浏览器 Agent 慢，多数人的第一反应是换一个更快的模型。&lt;/p&gt;&#10;&lt;p&gt;jev-ultrafast 给出的数字，值得看的其实不是&amp;quot;7 秒&amp;quot;，而是它顺手测出来的另一个数：&lt;strong&gt;单次任务的浏览器协议调用，中位从 1092 次掉到 101 次&lt;/strong&gt;。模型没换，任务没换，只是把&amp;quot;观察—决策—执行&amp;quot;这条链路的耦合方式改了。&lt;/p&gt;&#10;&lt;p&gt;这篇文章拆它到底砍了哪一层，以及你怎么给自己手里的 Agent 做同样的延迟归因。适合已经在用 Playwright / Browser Use / CDP 自建 loop、被模型往返和账单拖住的后端与 AI 工程师。&lt;/p&gt;&#10;&lt;h2 id="1092--101被砍掉的不是模型是浏览器往返"&gt;1092 → 101：被砍掉的不是模型，是浏览器往返&#10;&lt;/h2&gt;&lt;p&gt;先立一个归因框架，不然优化全是盲打：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;一次 Agent 任务的墙钟时间 = 模型决策耗时 + 浏览器协议往返耗时 + 页面自身加载耗时。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;大部分人只盯着第一项，因为它是唯一&amp;quot;看起来贵&amp;quot;的一项——打开 LangSmith 看 token 账单，第一反应是换 gpt-4o-mini。但 jev-ultrafast 的 &lt;code&gt;docs/performance.md&lt;/code&gt; 里，模型侧的中位延迟是 &lt;strong&gt;178ms&lt;/strong&gt;，即便多次 Jev 请求累计也就 3 秒左右——而原版 loop 光浏览器协议调用就有 1092 次。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="jev-ultrafast 官方测试结果看板：Google Flights 搜索任务 7.07 秒完成，右侧标注任务步骤打钩与 178ms 中位决策延迟" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://cdn.lpflpf.cn/images/posts/jev-flights-result-2026-09-21.png"&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;从官方记录的那次看板中能看到两个核心细节：右侧醒目标注了全链路任务耗时 &lt;strong&gt;7.07 秒&lt;/strong&gt;，每一步交互（单程/苏黎世/伦敦/日期/搜索）均通过了独立验证器；而底部实测出的决策模型中位延迟仅 &lt;strong&gt;178ms&lt;/strong&gt;。当模型侧被压缩至这个量级时，真正拖慢整个 Agent 的就是底层协议往返。&lt;/p&gt;&#10;&lt;p&gt;官方对原版 loop 的描述是：&lt;strong&gt;每次 DOM mutation 都会作废已有决策&lt;/strong&gt;（包括动画引起的 mutation），反复读取 accessibility tree，解析上百个 DOM 节点。注意&amp;quot;包括动画&amp;quot;这四个字——一个轮播图在转，你的 Agent 就在疯狂重读页面、重做决策。&lt;/p&gt;&#10;&lt;p&gt;新版的做法是：&lt;strong&gt;一次浏览器调用，原子读取可见控件的名字、值和文本&lt;/strong&gt;，并且保留对真实 DOM 节点的引用。读取次数从&amp;quot;每次变更一次&amp;quot;变成&amp;quot;每步一次&amp;quot;。&lt;/p&gt;&#10;&lt;p&gt;这就是协议调用能掉一个数量级的直接原因。它不是把 1092 次调用变快了，而是把大部分调用&lt;strong&gt;变得没必要&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;四个核心指标拉通对比，能一眼看出性能杠杆到底撬动在哪：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;浏览器协议调用：1092 次 → 101 次（-90.8%）&lt;/strong&gt;。从&amp;quot;每次 DOM 变更重采样&amp;quot;改为&amp;quot;每步一次原子快照&amp;quot;，直接砍掉九成无意义往返。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;任务墙钟耗时：9.450s → 7.092s（-25.0%）&lt;/strong&gt;。剔除动画引发的反复决策与等待抖动，端到端中位耗时稳稳降掉四分之一。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;决策模型请求：22 次 → 17 次（-22.7%）&lt;/strong&gt;。推测式并行合并决策头，原本分两步的问询被压进同一次网络往返。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;单次模型延迟：中位仅 178ms&lt;/strong&gt;。把动作空间封闭压紧之后，专用极速小模型才能真正接管决策层。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;换模型优化的是单次往返的成本，重构观测层优化的是往返的次数。次数是一个乘数，成本只是一个单价。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;判断你自己的 Agent 卡在哪，先数三个数：单次任务的模型请求数、浏览器协议调用数、单次模型延迟中位。如果协议调用是三位数而模型延迟是两位数毫秒，你该动的是观测层，不是模型选型。&lt;/p&gt;&#10;&lt;h2 id="一次请求里塞两个决策头动作空间的形状比模型选择更重要"&gt;一次请求里塞两个决策头：动作空间的形状比模型选择更重要&#10;&lt;/h2&gt;&lt;p&gt;动作空间封闭，是这套方案能换掉大模型的前提；怎么把操作和目标合进一次往返，是协议调用能砍掉的原因；fail-closed 校验，是保证执行层安全的底线。三件事都发生在&amp;quot;向模型提问&amp;quot;这一步。&lt;/p&gt;&#10;&lt;p&gt;每个观测会生成一张编号元素表：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[1] button Change ticket type · Round trip&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2] combobox Where from? · San Francisco&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[3] combobox Where to? · empty&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[4] textbox Departure · empty&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;img alt="jev-ultrafast 官方 Inspector 界面：左侧页面控件打上编号框，右侧实时显示决策耗时（351ms）、操作头概率分布（CLICK 76%、TYPE_TEXT 23%…）与目标元素排名" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://cdn.lpflpf.cn/images/posts/jev-inspector-2026-09-21.png"&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;操作集是固定封闭的八个：&lt;code&gt;CLICK&lt;/code&gt;、&lt;code&gt;TYPE_TEXT&lt;/code&gt;、&lt;code&gt;SELECT&lt;/code&gt;、&lt;code&gt;SCROLL_UP&lt;/code&gt;、&lt;code&gt;SCROLL_DOWN&lt;/code&gt;、&lt;code&gt;WAIT&lt;/code&gt;、&lt;code&gt;DONE&lt;/code&gt;、&lt;code&gt;BLOCKED&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;关键在于它怎么把操作和目标一起问出去。源码 &lt;code&gt;model.py::choose()&lt;/code&gt; 里，发往 TypeSafe（底层提供推测式多头预测的推理服务）的请求体包含一个 &lt;code&gt;operation&lt;/code&gt; 问题（选择上面八个之一），外加&lt;strong&gt;每个操作各自的目标问题&lt;/strong&gt;——&lt;code&gt;click_target&lt;/code&gt;、&lt;code&gt;type_text_target&lt;/code&gt;、&lt;code&gt;select_target&lt;/code&gt;。所有问题在&lt;strong&gt;同一次网络往返&lt;/strong&gt;里并行回答，这就是官方文档里那个 speculative fan-out（推测式扇出）模式。&lt;/p&gt;&#10;&lt;p&gt;而且每个目标头里&lt;strong&gt;只放兼容的元素&lt;/strong&gt;：&lt;code&gt;click_target&lt;/code&gt; 的候选里只有可点击元素，&lt;code&gt;select_target&lt;/code&gt; 的候选里只有观测到的下拉选项。源码注释写得很直白：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Unused target heads cannot cause an action. Validate the head selected by the operation.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;target_answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validate_choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;answers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;_target&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;未命中的目标头结果被直接丢弃，&lt;strong&gt;永远不会触发动作&lt;/strong&gt;。这就是&amp;quot;推测式&amp;quot;的代价边界：你多花了一点推理算力去买一次往返，但拿不到执行权限。&lt;/p&gt;&#10;&lt;p&gt;落到你自己的项目上，这是个可以立刻自查的判断题：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;模型在输出什么？&lt;/strong&gt; 是自由字符串（CSS 选择器、坐标、JS 片段），还是受限集合里的一个索引？&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;合法目标的边界在哪？&lt;/strong&gt; 每个操作是否只向模型暴露合法的目标？&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;非法输出怎么处理？&lt;/strong&gt; 是 fail-closed（不执行）还是 fail-open（猜一个执行）？&lt;/p&gt;&#10;&lt;p&gt;jev-ultrafast 的答案是 fail-closed，而且比 README 写的更严格。&lt;code&gt;validate_choice()&lt;/code&gt; 不只校验 choice 在候选集内，还要求概率分布的键集合与候选完全一致、每个概率在 0~1 之间、&lt;strong&gt;概率和与 1 的偏差小于 0.02&lt;/strong&gt;，以及：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;probabilities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;choice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;probabilities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1e-6&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;选中的那一项必须同时是概率最大的那一项&lt;/strong&gt;。任何一条不满足，直接抛 &lt;code&gt;ValueError(&amp;quot;Invalid TypeSafe response; no action executed.&amp;quot;)&lt;/code&gt;，一步都不执行。&lt;/p&gt;&#10;&lt;p&gt;对照上面的 Inspector 截图就能看得很直观：右侧面板实时展示了当前决策——Operation 判定为 &lt;code&gt;CLICK&lt;/code&gt;（置信度 76%），目标元素严格收敛在概率最高的 &lt;code&gt;[19]&lt;/code&gt; 选项（93%）。即便模型在同一次推理中也为 &lt;code&gt;type_text_target&lt;/code&gt; 计算了概率分布，但因为主操作未中，未命中的头会被 &lt;code&gt;if operation in targets:&lt;/code&gt; 这一行直接丢弃，根本进不去执行管道。&lt;/p&gt;&#10;&lt;p&gt;这层校验的意义不只是防错。它把&amp;quot;模型输出&amp;quot;和&amp;quot;可执行动作&amp;quot;之间的缝焊死了——&lt;strong&gt;模型输出永远不会变成选择器、坐标、shell 命令或可执行 JavaScript&lt;/strong&gt;，只能变成一个已观测节点的索引。这是把决策层换成小模型/专用模型的前提条件。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Jev Ultrafast 核心决策架构：单次网络往返并行推测操作头与多个目标头，仅选中的目标获执行权；仅自由文本字段唤醒生成模型" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://cdn.lpflpf.cn/images/posts/jev-decision-flow-2026-09-21.svg"&gt;&#10;&lt;/p&gt;&#10;&lt;h2 id="决策和生成分两个模型生成模型只在没有确定答案集合时才上场"&gt;决策和生成分两个模型：生成模型只在没有确定答案集合时才上场&#10;&lt;/h2&gt;&lt;p&gt;正如上面架构流程图所示，整套系统在这一步完成了关键分流：&lt;strong&gt;决策&lt;/strong&gt;与&lt;strong&gt;生成&lt;/strong&gt;被彻底切成了两个模型。&lt;/p&gt;&#10;&lt;p&gt;决策层不生成任何文字。只有当选中 &lt;code&gt;TYPE_TEXT&lt;/code&gt; 时，才调用一个 OpenAI 兼容的小模型（示例配置用 &lt;code&gt;inception/mercury-2.5&lt;/code&gt;，代码默认 base 指向 DeepSeek）来产出字段值。官方记录的那次运行里，两次生成分别是 Zurich 581ms、London 346ms。&lt;/p&gt;&#10;&lt;p&gt;这个 helper 的输入输出约束，比很多生产代码都严：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;span class="lnt"&gt;7&#10;&lt;/span&gt;&lt;span class="lnt"&gt;8&#10;&lt;/span&gt;&lt;span class="lnt"&gt;9&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;choices&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;输出必须能解析成 JSON、&lt;strong&gt;必须恰好只有 &lt;code&gt;text&lt;/code&gt; 一个键&lt;/strong&gt;、必须是非空字符串、长度不超过 2000。任何一条不满足就抛错，错误信息是 &lt;code&gt;&amp;quot;Text helper returned no valid field value; nothing typed.&amp;quot;&lt;/code&gt;——&lt;strong&gt;宁可什么都不输入，也不输入一个猜的值&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;官方在 performance.md 里坦承，早期探针阶段就淘汰过一个把出发地和目的地搞反的模型，以及另一个输出解释性文字而非合法 JSON 的模型。&lt;/p&gt;&#10;&lt;p&gt;由此可以提炼一条判定标准，比&amp;quot;哪些步骤该用小模型&amp;quot;更可操作：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;凡是&lt;strong&gt;有确定答案集合&lt;/strong&gt;的判断，就不该用生成模型。分类、选元素、路由、打分，这些都不该让 LLM 自由生成文本再解析；凡是&lt;strong&gt;没有确定答案集合&lt;/strong&gt;的（写一段文案、填一个城市名），才需要生成模型，并且必须加结构校验。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;分层选型的原则之外，这套架构落地时最隐蔽的挑战在于：&lt;strong&gt;极速决策层（178ms）与异步文本生成（300~500ms）解耦后，模型时钟与真实 DOM 渲染极易产生异步漂移&lt;/strong&gt;。如果前一步点击刚触发、异步事件尚未渲染完毕，下一帧观测到的极可能是陈旧状态。&lt;/p&gt;&#10;&lt;p&gt;代码里写着一条关键防御注释——&lt;code&gt;&amp;quot;A stale post-action observation must not erase the action&amp;quot;&lt;/code&gt;——&lt;strong&gt;执行先记录，再观测；已落地的动作绝不能被陈旧的后验观测所抹杀&lt;/strong&gt;。围绕这一原则，源码配套了四处工程硬边界，专门防范异步并发下的状态抖动：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;步数硬上限&lt;/strong&gt; &lt;code&gt;MAX_STEPS = 60&lt;/code&gt;，超过就抛错停下，不无限重试。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;等待时间有界&lt;/strong&gt; 输入 combobox 后等待可见建议上限 &lt;strong&gt;200ms&lt;/strong&gt;；其他交互最多等两帧或 50ms。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;文本复用条件严格&lt;/strong&gt; 陈旧页面重试时，只有文本 helper 的全部输入未变，才复用已生成的文本。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;焦点模拟&lt;/strong&gt; 防止后台标签页被降频，但不切换 Chrome 可见标签。&lt;/p&gt;&#10;&lt;h2 id="p025三个-pair一个任务数字有多可信作者自己写清楚了"&gt;p=0.25、三个 pair、一个任务：数字有多可信，作者自己写清楚了&#10;&lt;/h2&gt;&lt;p&gt;jev-ultrafast 最容易被忽略的资产，是它的实验设计。它没有只放一个漂亮数字，而是把&amp;quot;我做了什么才算可信&amp;quot;写清楚了。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;值得抄的四个动作：&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;冻结基线 commit&lt;/strong&gt;。原版 loop 固定为 &lt;code&gt;68c077bf79caca4e817b8e8a5854b2efa0c81ff6&lt;/code&gt;，不是&amp;quot;优化前的某个版本&amp;quot;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;交替运行&lt;/strong&gt;。六次运行按 pair 交替执行（原版/新版/原版/新版…），避免时间漂移只污染一侧。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;两侧固定非被测变量&lt;/strong&gt;。两臂用同一个自然语言目标、同一个独立结果校验器、同一视口 1120×780、同一个 TypeSafe &lt;code&gt;jev-1.13.0&lt;/code&gt;、同一个 &lt;code&gt;inception/mercury-2.5&lt;/code&gt;、同样的预算，&lt;strong&gt;并且都关掉文本推理&lt;/strong&gt;。原文说得很清楚：两臂都用 Mercury，是为了&amp;quot;不让 helper 模型的变化和代码变化混在一起&amp;quot;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;保留全部失败记录&lt;/strong&gt;。原文写明&amp;quot;All six attempts are included; no provider or verification failures occurred&amp;quot;，并且把开发过程中的尝试也留下了——原版 9.302s 通过，两个 accessibility-tree 方案 9.395s / 10.157s，第一个直接读 DOM 的方案 8.697s 但&lt;strong&gt;因为 name/value 提取不完整被独立校验判失败&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;strong&gt;它自己承认的三个洞：&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;样本量不够&lt;/strong&gt;。原文原话：&amp;ldquo;Three pairs are too few for a strong statistical claim (two-sided sign-test &lt;strong&gt;p = 0.25&lt;/strong&gt;)&amp;quot;。三个 pair、一个任务、一个浏览器 profile，作者自己定性为&amp;quot;a small controlled-input comparison, not a broad agent benchmark&amp;rdquo;。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;计时边界&lt;/strong&gt;。计时从初始首页观测后的第一次预测开始，&lt;strong&gt;不含初始导航和首次观测&lt;/strong&gt;，也不含跑完后的独立验证。Hacker News 上第一条质疑就冲着这个来的：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&amp;ldquo;Timing starts after initial page observation&amp;rdquo; Isn&amp;rsquo;t this the part that takes most time?&#10;（&amp;ldquo;计时从首次观测后才开始？但这难道不是整个过程里耗时最长的一部分吗？&amp;quot;）&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;这是个成立的质疑，作者也没有反驳——他只是把边界写在了明面上。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;成本数字不可复现&lt;/strong&gt;。performance.md 只给了两次文本 helper 的 OpenRouter 账单 &lt;strong&gt;$0.00006272&lt;/strong&gt;，并明确说这不是任务总成本：TypeSafe 的响应返回 token 数但不返回计费金额，浏览器成本完全没算。&lt;/p&gt;&#10;&lt;p&gt;网上流传的&amp;quot;单次 0.0039 美元&amp;quot;来自作者 X 帖和二手报道，&lt;strong&gt;仓库里没有这个数字&lt;/strong&gt;。凡是引用它，都应该标注为&amp;quot;作者在演示中宣称&amp;rdquo;，而不是当成已验证的成本。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;还有几个硬边界值得在选型时先看清楚：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;最关键的兼容性限制：Jev 只吃文本，不支持图像输入；DOM reader 不处理 shadow roots、frames、canvas。&lt;/strong&gt; 如果你的目标站点大量使用 Canvas 或无语义 div 渲染，DOM reader 会直接失效，视觉 grounding 仍然是必要的——这套方案根本用不上。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;成熟度问题：&lt;/strong&gt; 这是 &lt;strong&gt;3 次提交、无 release、无 tag&lt;/strong&gt; 的演示级 MVP。&lt;code&gt;DONE&lt;/code&gt; 需要独立结果验证，模型说完成不等于完成；owned tabs 复用现有 Chrome profile，有登录态隔离问题；文件上传、弹窗新标签、嵌套滚动、任意键盘控件暂不覆盖。&lt;/p&gt;&#10;&lt;p&gt;不过账得算全。这套方案真正的启示不是&amp;quot;赶紧换 Jev&amp;quot;，而是它证明了：&lt;strong&gt;动作空间的形状决定了你能不能用便宜的决策层&lt;/strong&gt;。把模型输出从自由字符串压成受限索引之后，决策层用什么模型才有得选。反过来，如果你的 Agent 还在让模型吐 CSS 选择器，那换什么模型都是在给一个漏水的桶换更快的龙头。&lt;/p&gt;&#10;&lt;p&gt;看清别人的局限，是为了把真正有用的武器拿来武装自己。你完全不需要等开源项目把这些边界解完，今天就能做一套针对自己 Agent 的轻量延迟归因。&lt;/p&gt;&#10;&lt;h3 id="明天加三行埋点先把自己的-agent-量出来"&gt;明天加三行埋点，先把自己的 Agent 量出来&#10;&lt;/h3&gt;&lt;p&gt;不用换模型，也不用接 TypeSafe。打开你现在跑的 Agent，加一行埋点，把一次任务里的三类数字打出来：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 伪代码，接在你现有 loop 的合适位置&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;model_requests&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 每次调用决策模型 +1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;model_latency_ms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="c1"&gt;# 每次模型调用的耗时&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;browser_protocol_calls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 每次 snapshot / click / read 协议调用 +1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;跑三个真实任务，把这三类指标对照下面的诊断逻辑排查：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;协议调用上百、模型延迟数十毫秒（乘数爆炸）&lt;/strong&gt; → 瓶颈在观测层。去看有没有&amp;quot;每次 DOM 变更都重做决策&amp;quot;的逻辑，尤其是&lt;strong&gt;动画引起的 mutation&lt;/strong&gt;。优先把全量重读页面收敛为每步一次的原子快照。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;模型请求数远大于交互步数（单步重复交税，接近 2:1 甚至更高）&lt;/strong&gt; → 瓶颈在网络往返。你正在为&amp;quot;选操作&amp;quot;和&amp;quot;选目标&amp;quot;付两次网络开销。看看能不能用 Speculative fan-out 把操作头与目标头合并进同一次请求并行回答。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;模型单次延迟占整体耗时大头（单价过高）&lt;/strong&gt; → 这时换模型才有收益。但先确认输出是不是自由字符串；如果是，先把它改成已观测节点的受限索引。动作空间的形状封闭了，极速决策层才有得选。&lt;/p&gt;&#10;&lt;p&gt;顺手验证一个最小命令，确认这套思路在你机器上能跑通（不调模型、不花钱）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/browser-use/jev-ultrafast.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; jev-ultrafast &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv sync&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv run python scripts/check_guards.py &lt;span class="c1"&gt;# 检查本地真实控件，不发模型请求&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这个脚本存在的意义本身就是一个信号：&lt;strong&gt;点击守卫、控件状态、遮挡判断这些东西，是可以在不调用任何模型的情况下单独验证的&lt;/strong&gt;。如果你自己的 Agent 里这些逻辑和模型调用缠在一起没法单独测，那可能才是最难改的那部分。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;strong&gt;参考链接与项目地址&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;jev-ultrafast 开源仓库：https://github.com/browser-use/jev-ultrafast&lt;/li&gt;&#10;&lt;li&gt;性能测试对照与计时边界：https://github.com/browser-use/jev-ultrafast/blob/main/docs/performance.md&lt;/li&gt;&#10;&lt;li&gt;决策模型与动作空间源码：https://github.com/browser-use/jev-ultrafast/blob/main/jev_ultrafast/model.py&lt;/li&gt;&#10;&lt;li&gt;步数上限设定源码（MAX_STEPS）：https://github.com/browser-use/jev-ultrafast/blob/main/jev_ultrafast/questions.py&lt;/li&gt;&#10;&lt;li&gt;TypeSafe speculative fan-out 模式文档：https://docs.typesafe.ai/patterns/fan-out&lt;/li&gt;&#10;&lt;li&gt;Hacker News 讨论帖：https://news.ycombinator.com/item?id=49735979&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item></channel></rss>