browser-use 的判分器怎么用:让模型判模型能信到哪一步
本文基于 browser-use 仓库 commit f0aa3a8(2026-07-27)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/browser-use/browser-use 最新代码与文档为准。
判分器的价值不在于它给出的那个 true/false 有多准,而在于它把「任务算不算成功」这件事从 agent 的自我报告里拿走了。 一个跑浏览器的 agent 在最后一步调 done 时会自己填一个 success 字段,这个字段和它实际干成的事之间没有任何强制关系——页面白屏它也能报成功。browser-use 在 browser_use/agent/judge.py 里做的事,就是拿任务描述、每一步的动作记录和末尾若干张截图,再喂给一个模型,让它独立回答一次「这活干成了吗」。两个信号并存、不互相覆盖,你才有东西可以对账。
站内已有三篇讲评测的文章:Agent 评测方法 谈的是通用评测设计思路,评测集怎么构建 谈样本与标注,Agent 回归测试 谈怎么把评测挂进 CI;本篇不重复这些方法论,只做一件事——把 browser-use 这个具体仓库里判分器的输入面、约束面和失效面读一遍,让你判断它能不能直接拿来用。
一、它先解决的是「agent 自报成功」这个坑
browser_use/agent/views.py 里,AgentHistoryList.is_successful() 的注释写得很直白:成功与否由 agent 自己在最后一步决定。这就是自评。
判分器接进来的方式,在 browser_use/agent/service.py 的 _judge_and_log() 上有一段注释说明了设计取向:
"""Run judge evaluation and log the verdict.
The judge verdict is attached to the action result but does NOT override
last_result.success — that stays as the agent's self-report. Telemetry
sends both values so the eval platform can compare agent vs judge.
"""
判分结果挂在 ActionResult.judgement 上,不改写 success。日志逻辑也顺着这个思路走:自评成功且判分通过,什么都不打;自评成功但判分为 false,先打一行「Agent reported success but judge thinks task failed」的黄色警告,再打完整判词。也就是说,这套东西真正想帮你抓的是分歧——agent 觉得自己赢了、判分器不认,这一条才是你要人工看的。
仓库里还有一份面向 QA 场景的参考文档 skills/qa/references/browser-use-v2.md,它对云端接口那条路径给的口径更硬:判分结论优先于 agent 的自评分数,理由写得很直接——一个 agent 会心情愉快地给白屏页面打满分。两条路径的取向是一致的:自评当参考,判分当结论。关于 agent 谎报成功的一般性成因,可以配合 Agent 谎报成功怎么防 一起看。
二、判分器实际看到的是什么
construct_judge_messages() 的签名把输入面框得很清楚:task、final_result、agent_steps、screenshot_paths、max_images(默认 10)、ground_truth、use_vision。它返回的是一对 SystemMessage + UserMessage,判词模板全部写死在这个函数里。
几个细节决定了它的判断上限:
- 轨迹是文本化的。
agent_steps()把每一步的 action 列表model_dump成 JSON,再拼上该步各个ActionResult的extracted_content和error。判分器读到的是这份摘要,不是原始 DOM。 - 截图只取尾部。
screenshot_paths[-max_images:],_judge_trace()里传的是max_images=10。前面几十步长什么样,判分器看不到。 - 图片走 base64 内嵌。
_encode_image()读文件转 base64,包成ContentPartImageParam,media_type写死image/png。文件不存在时它直接返回空、这张图被静默跳过;只有读文件抛异常才会留一条 warning。也就是说截图丢了你在日志里看不见,判分器少看几张图也照样出结论。 use_vision会一路传下来。_judge_trace()把self.settings.use_vision传给判分器;只要它不是False,就带图。注意这里是「不是 False」,字面量'auto'同样会带图。- 三段输入各自截断到 40000 字符。
_truncate_text()默认从尾部截,插一个...[text truncated]...标记;from_beginning=True时改成保留末尾。超长的任务和超长的轨迹,中间那段就没了。
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
construct_judge_messages() | 拼判词:系统提示 + 任务/轨迹/结果/截图 | browser_use/agent/judge.py | 想改判分标准、想知道判分器到底看到了什么 |
JudgementResult | 判分结果的结构:reasoning / verdict / failure_reason / impossible_task / reached_captcha | browser_use/agent/views.py | 拿判分结果做下游统计或告警 |
_judge_trace() / _judge_and_log() | 调判分模型、挂结果、打日志 | browser_use/agent/service.py | 判分没触发、判分报错、想换判分模型 |
use_judge / judge_llm / ground_truth | Agent 构造参数,三个开关 | browser_use/agent/service.py、views.py | 单次跑任务时开关判分、指定判分模型、给标准答案 |
| YAML 任务集 | 一个文件一个任务:task + judge_context + max_steps | tests/agent_tasks/*.yaml | 想往 CI 里加自己的浏览器任务 |
| 任务集运行器 | 并行拉子进程跑任务、逐个判分、汇总分数 | tests/ci/evaluate_tasks.py | 本地批量跑任务集、看 CI 为什么绿 |
三、ground_truth 是这套东西里唯一的硬约束
不传 ground_truth,判分器就只能凭「用户意图」自由心证。传了之后,系统提示里会插进一整段标着 GROUND TRUTH VALIDATION (HIGHEST PRIORITY) 的内容,措辞是:ground truth 的优先级高于其它一切评判标准,若执行过程与最终回复没有满足它,verdict 必须为 false。
判词里把可以往这里塞的东西分成三类:判定条件(比如「成功弹窗必须出现」「必须恰好抽出 5 条」)、事实答案(一个日期、一个地名)、预期产出(「必须创建出一个文档」「文件应当被下载」)。这三类的共同点是可机械核对,而不是「结果应该不错」。
examples/features/judge_trace.py 这个例子把用法压到了最小:
agent = Agent(
task=task,
llm=llm,
use_judge=True,
judge_llm=llm,
ground_truth='16', # The TRUE answer is 17 but we put 16 to demonstrate judge can detect when the answer is wrong.
)
history = await agent.run()
if history.is_judged():
judgement = history.judgement()
这条注释是整个例子的重点:真答案是 17,故意传 16,用来演示判分器会因为对不上 ground truth 而给出否定结论。换个角度看,它同时暴露了一件事——判分器不校验你的 ground truth 对不对。你把标准答案写错了,它就照着错的判,而且判得非常自信。
is_judged() 和 judgement() 都定义在 views.py 里,读的是最后一个 ActionResult 上的 judgement 字段;后者返回 model_dump() 出来的字典。所以拿判分结果做统计时,先 is_judged() 再取值,不要假设它一定在。
四、YAML 任务集与 CI 那条链路
tests/agent_tasks/README.md 给的贡献格式只有四个键,amazon_laptop.yaml 就是照这个格式写的:
name: Amazon Laptop Search
task: Go to amazon.com, search for 'laptop', and return the first result
judge_context:
- The agent must navigate to amazon.com
- The agent must search for 'laptop'
- The agent must return name of the first laptop
max_steps: 10
judge_context 是一个列表,一行一条成功标准。README 的指引也很朴素:任务和标准都要写具体,judge_context 要写清什么才算成功的结果。另一个任务 browser_use_pip.yaml 更极端,只有一条标准——输出必须包含 pip install browser-use 这个命令。这两个样本反映的取向是:能被字符串级核对的标准,才是好标准。
跑法在 tests/ci/evaluate_tasks.py。这里有一个很容易踩的认知差,我读完才确认:这条 CI 链路并没有用 judge.py。它自己在文件里定义了一个只有 success 和 explanation 两个字段的 JudgeResponse,自己拼了一段以「You are a evaluator of a browser agent task inside a ci/cd pipeline」开头的提示词,把任务、agent 输出、debug 信息和 judge_context 拼进去,交给判分模型做结构化输出。也就是说,仓库里同时存在两套判分:库内那套(带截图、带 impossible_task 和 reached_captcha)和 CI 这套(纯文本、两字段)。你照着 README 加任务,走的是后者。
运行器本身的几个设定值得记住:MAX_PARALLEL = 10,每个任务开独立子进程,避免浏览器会话互相干扰;子进程里的 BrowserProfile 用的是 headless=True、user_data_dir=None、chromium_sandbox=False,最后一项注释写明是为 CI 环境让路;判分模型和 agent 模型分别取不同的环境变量,任一缺失就直接返回 success: True 并在 explanation 里写「Skipped」,理由是不让 fork PR 把 CI 弄红。
还有一件对不上的地方:README 让你跑 pytest tests/ci/test_agent_real_tasks.py,但当前仓库的 tests/ci/ 下找不到这个文件,而 .github/workflows/test.yaml 里实际执行的是 python tests/ci/evaluate_tasks.py。要在本地复现 CI 行为,照 workflow 里的命令走。
五、边界与代价:这个设计放弃了什么
判分模型默认就是 agent 自己那个模型。 service.py 里 if judge_llm is None: judge_llm = llm。同一个模型既执行又判分,它的盲区是共享的:看不出的页面状态、误读的语义、对自己那套动作序列的合理化,判分环节不会突然多出一双眼睛。要拿判分当独立信号,你得手动换一家;各家服务商的规则不同且会调整,以官方最新说明为准。
它默认是开的。 AgentSettings.use_judge 的默认值是 True,对外单步接口 take_step() 和主循环里的 _execute_step() 两处都在 history.is_done() 之后触发判分。这意味着每次跑完任务会多出一次带图的模型调用,judge_llm 也被注册进了 token 成本统计。你如果只是在本地反复调一个脚本,这笔开销是白花的,显式关掉更合适。成本口径的一般做法可以参考 Agent 成本实控。
判分失败等于没判,不等于失败。 _judge_trace() 整个调用包在 try/except 里,出异常只记一条 error 日志然后返回 None。批量统计时把「没有 judgement」和「verdict 为 false」混在一起算,通过率会莫名下滑。
结论是二值的,没有分数、没有部分得分。 JudgementResult 只有 reasoning、verdict、failure_reason、impossible_task、reached_captcha 五个字段。判词里还明确了一批一票否决项:找了 4 条却要求 10 条 → false;要求写进文档却只回了文本 → false;要求用某个站点或工具完成却绕开了 → false;报告说动作完成但截图显示没完成 → false。这套口径对「差一点」非常不宽容,好处是不会被含糊的部分完成糊弄过去,代价是你拿不到进度感——同一个任务从「完全跑不动」改进到「只差最后一步」,通过率数字上一点变化都没有。
它对内容幻觉的检出天生有缺口。 判词里有一条 screenshot is not entire content:agent 拿到的是完整 DOM,截图只是其中一部分,所以 agent 声称从页面上抽到的信息,即使截图里看不见,也可以假定它存在。这条规则是为了避免误杀正常的信息抽取,反过来也就给编造留了口子。要卡住这类问题,还得靠 ground_truth 里写死可核对的值。
它不看真实世界的副作用。 判分器的输入是任务、轨迹摘要、最终结果和截图。订单到底有没有落库、文件到底在不在磁盘上、表单提交后后端收没收到,它一概不知道,只能从截图上「看起来完成了」推断。凡是有真实写操作的任务,判分通过之后仍然需要一道外部核对。
明确不管的还有一类:你有没有资格跑这些任务。 判分器会在 reached_captcha 里标记撞上验证码,_judge_and_log() 里遇到这个标记会打一条日志,并把云端浏览器方案推给你。这只是它的产品提示,不是给你的合规结论。真实约束在别处:目标站点的使用条款是否允许自动访问、验证码和反自动化机制本身就代表站点不欢迎这种流量、频繁自动化操作可能让账号被判异常。如果你为了跑通任务把本地真实浏览器配置目录接上去,那么你的登录态、cookie、页面上的个人信息都会随着截图一起进到判分模型的上下文里——这是一条实实在在的数据外泄面。CI 里那个 user_data_dir=None 就是最保守的做法,值得抄。想把这类边界写成团队约定,可以参考 Agent 改动边界约定。
最后是稳定性。 判分是一次模型采样,同一条轨迹两次判分可以给出不同结论。加上 40000 字符截断和只取末尾 10 张截图,长任务的判分方差会更大。
六、上手与避坑清单
- 别把单次 verdict 当门禁。 会踩,是因为它看起来就是个布尔值,太像测试断言了。判分是模型采样,方差真实存在。避法:同一任务多跑几次看通过率而不是单次结果,并且尽量用 ground_truth 把判断收窄成可机械核对的条件。
- ground_truth 别写成形容词。 会踩,是因为写「结果应该完整准确」这种句子最省事。但它的优先级被设成最高,模糊的标准会让最高优先级去裁决一件没法裁决的事。避法:写「必须恰好返回 5 条」「页面上必须出现某个确定文案」这类判定条件,或者直接写标准答案。
- 判分模型要显式换一家。 会踩,是因为不传
judge_llm时它默认复用 agent 的模型,日志照样打,看不出异常。避法:构造 Agent 时显式传一个不同来源的judge_llm,让判分至少不是自证。 use_vision=False时判分器是盲判的。 会踩,是因为这个参数是给 agent 设的,很多人不知道它会一路传进判分器。无图之后判分器只剩文本轨迹,而判词里恰恰有大量依赖截图的规则(「截图显示动作没完成」「截图里有验证码」)。避法:关视觉时就不要指望它能纠正 agent 的自我叙述,或者干脆把判分一起关掉。- 盯住
impossible_task这个字段。 会踩,是因为它天生是个逃生舱:判词允许在任务描述含糊、页面真的坏了、缺凭据无法登录时把任务标成不可能。你的任务集写得糙一点,就会稳定收到一批「不是我的问题」。避法:把 verdict 为 false 且impossible_task为 true 的样本单独拉出来人工看一遍,多数时候要改的是任务描述而不是 agent。 - CI 的绿灯可能是空跑出来的。 会踩,是因为缺少任一环境变量时运行器直接返回
success: True,只在 explanation 里写「Skipped」。避法:汇总时读 explanation,别只数通过条数。 - 那条 CI 判分不是质量门。 会踩,是因为它输出「PASSED/TOTAL」和一个分数表,看着像门禁。但它只在通过率为 0 时
sys.exit(1),写在文件头的注释里。避法:想当门禁就自己按任务粒度设阈值,别复用它的退出码。 - 加任务时先确认自己在哪条链路上。 会踩,是因为两套判分同名同姓,
judge_context和ground_truth长得也像。往tests/agent_tasks/加 YAML 走的是evaluate_tasks.py那套纯文本判分,不带截图也不带impossible_task;要用库内那套,得在自己的脚本里构造 Agent 并传use_judge与ground_truth。避法:动手前先决定要哪套,两套的字段不通用。
收束
这套判分器的定位,是给一个本来只有自我报告的执行过程加一路旁证。它不是准确率工具,判词里那些一票否决项和「be initially doubtful of the agent’s self reported success」的措辞已经说明它宁可误杀不肯放过。用它抓分歧、用 ground_truth 定死可核对的点、用外部手段核实真实副作用,三层叠起来才算有验收。把它单独拎出来当分数用,你会得到一个很像指标、但既不稳定也不可比的数字。验收标准该怎么定义,可以再看一遍 Agent 验收标准。
接下来该读哪个文件,按你的目的分:想改判分标准,读 browser_use/agent/judge.py,判词全在那个函数里;想接判分结果做统计,读 browser_use/agent/views.py 里的 JudgementResult 与 judgement();想知道判分什么时候被触发、失败了会怎样,读 browser_use/agent/service.py 的 _judge_trace() 和 _judge_and_log();想往 CI 里加任务,读 tests/agent_tasks/README.md 和 tests/ci/evaluate_tasks.py,并且以后者为准。这个项目用 MIT 许可证,判词模板可以直接抄进自己的评测里改——但改完记得重新校准,它现在这套阈值是照着自己的场景调出来的。
本篇属于一个把开源浏览器操作 Agent 项目 browser-use逐层拆开讲的系列,整体地图见 browser-use 是什么;沿着这条线往下,还可以看 browser-use 的 MCP 双向设计 和 browser-use 的三条回放线。