我做技术评测这些年,越来越觉得,行业给多模态Agent打分的方式,就像用尺子量温度。传统的对话评分——BLEU、ROUGE,甚至是GPT-4o当裁判的主观分,都没法回答一个关键问题:这个Agent到底能不能办成事?它点没点对按钮,填没填对表单,遇到弹窗是绕过去还是卡死。这些细节,藏在每一次鼠标移动、每一次工具调用的序列里,而我们过去用的所有对话质量指标,对它们视而不见。
去年第四季度,我帮一家SaaS公司选型能操作Web界面的多模态Agent,结果发现市面上连一套像样的评测基准都没有。有的项目拿MiniWoB++跑一遍,就说自己“超越人类”,可仔细一看,只统计了“最终状态”,完全不看操作步骤里的逻辑跳跃和过度调用。那一刻我明白,我们要的不是一个更好的打分器,而是一场能暴露Agent真实能力的手术式解剖。
于是,我扎进去搭建了一套完整的评测流水线——从仿真环境、轨迹对齐、任务完成率,到并发跑分平台。这篇文章,是我这半年来最核心的思考:怎么跳出“对话评分”的舒适区,给能看、能操作的多模态Agent设计一套真正有用的硬核考试。
30秒速览
- - 多模态Agent评测必须跳出对话评分,建立从动作轨迹对齐到任务目标达成的立体考试。
- - 基于Playwright的仿真环境需注入随机化和异常,以区分视觉理解与DOM作弊型Agent。
- - 加权编辑距离与代价矩阵可以量化工具调用序列的合理性,步骤效率直接影响成本。
- - 任务完成率应拆解为硬成功率、软成功率、步骤效率和失败恢复分四个独立维度。
- - 并发评测平台必须隔离Agent环境,并区分模型推理限流导致的失败与真实能力缺陷。
多模态Agent的评测,我们一直在用错尺子
评测一个能看屏幕截图、能点击、能填表单的Agent,和评测一个纯语言模型,本质上是两种游戏。语言模型输出一串token,我们可以算困惑度、算与参考答案的相似度。但Agent的输出是一串带时间戳的动作,每个动作在界面坐标系里有一个精确的落脚点,错误一步,后续连锁崩坏。(延伸阅读:当RAGAS的Faithfulness指标连续12天撒谎:我构建Judge Agent链与自动回滚监控的完整决策笔记)
行为轨迹不是文本序列,是带空间约束的因果链
我在早期实验里用Claude 3.5 Sonnet做Web操作时,发现一个典型模式:Agent能靠截图中的文本“猜”出目标按钮的大致位置,然后用playwright的click(x,y)点下去。如果坐标偏了20个像素,可能会点到一个相邻元素,触发一个完全不同的状态转移。这个错误,在对话评分里根本不会被发现,因为最后输出的文本可能是“已完成操作”,可实际上订单已经下错了。
多模态Agent的动作空间有几个特性让传统评测失效:一是空间精度,界面元素的边界框坐标哪怕差几个像素,就可能误触;二是时间依赖,前面步骤的执行延迟直接影响后续截图的状态,评测必须考虑操作顺序;三是工具调用链,Agent可能调用外部API、执行JavaScript、等待页面加载,这些中间步骤的序列正确性远比最终回复重要。
我们缺的不是一个榜单,是一套解剖Agent思维过程的CT机
现有的公开基准,比如Mind2Web,它评估的是Agent在离线HTML快照上做元素选择的准确率,根本不涉及真实浏览器渲染、异步加载和动态元素。而MiniWoB++虽然提供了简单网页任务,但它的动作空间只包含点击和输入,没有拖拽、右键菜单、键盘快捷键这些真实办公场景里的操作。
我在构建评测框架之初就定了一个原则:不追求在某个基准上刷榜,而要造出一台能切开Agent“大脑”的CT机。它必须记录每一步动作的截图前后对比、DOM差异、工具调用的完整payload,然后从三个层面解剖:意图对不对(动作序列的宏观规划是否合理)、执行准不准(参数和坐标是否无误)、恢复快不快(遇到异常时能否自我纠正)。这三个维度,构成了我整个评测架构的骨架。
棋局解读:OpenAI放开Operator的视觉操作API,是在下一盘什么棋
今年年初,OpenAI发布了Operator的视觉操作能力预览,允许Agent通过屏幕截图控制电脑界面。这一步棋,我来拆解一下。①谁在做什么:OpenAI不是简单地发布一个新模型,而是把“计算机操作”作为一个第一性接口推出来,和ChatGPT的对话接口并列,等于在告诉开发者:未来的AI应用,不只是聊天,是直接干活。②为什么选这个方向而不是继续死磕纯文本智能体:因为多模态Agent的操作数据,是比文本数据更稀缺的战略资源。谁先收集到千万级的“操作轨迹”数据,谁就能训练出具备通用GUI操作能力的基座模型,这个数据飞轮一旦转起来,后来者几乎追不上。Operator不只是产品,它是数据采集器。③我判断接下来三个月:至少会有三家头部云厂商推出兼容Operator API的评测和仿真服务,试图建立自己的操作数据训练闭环。如果Anthropic在三个月内发布与Computer Use功能同等力度开放的视觉操作API,我会说这是我见过的最快“数据圈地”对决。
这场竞赛直接倒逼评测方法升级——你不能只拿“任务成功率”来糊弄,必须把操作轨迹的质量量化,才能筛选出真正能落地的Agent。
仿真环境不是Selenium的平替,是Agent的标准化考场
很多团队直接把Selenium/Playwright脚本改一改就拿来跑Agent评测,这在工程上是个巨大的陷阱。因为Agent需要的不是一个能执行脚本的环境,而是一个能精确回放、注入异常、记录每一个像素变化的可控考场。(延伸阅读:当质检员开口说话,图纸和视频自动重组——我在多模态RAG上赌的这把,比CxO想象的更大)
基于Playwright的扩展:把浏览器变成行为记录仪
我选Playwright而不是Selenium,看中的是它对现代浏览器的原生支持和对截图、网络请求、DOM变更的事件流拦截能力。我的扩展核心是一个中间件层,它会劫持Agent发给Playwright的每一个操作指令,记录操作前后的全屏截图、当前光标位置、DOM快照,并打上精确到毫秒的时间戳。
下面这段代码展示了如何封装一个带轨迹记录的页面包装器。它会在每次动作后自动采集上下文,为后续的轨迹对齐提供原始数据。
import asyncio
import json
import time
from typing import Any, Dict, Optional
from playwright.async_api import Page, BrowserContext
class InstrumentedPage:
"""为Agent操作包装的Playwright Page,每个动作自动记录轨迹。"""
def __init__(self, page: Page, trace_log_path: str = "agent_trace.jsonl"):
self.page = page
self.trace_log_path = trace_log_path
self.action_seq_id = 0
self.context_checksum = None
async def _record_step(self, action: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""在动作执行前后记录状态快照。"""
pre_screenshot_bytes = await self.page.screenshot(full_page=True)
pre_dom = await self.page.content()
start_time = time.monotonic()
try:
result = await getattr(self.page, action)(**params)
except Exception as e:
result = {"error": str(e)}
elapsed = time.monotonic() - start_time
post_screenshot_bytes = await self.page.screenshot(full_page=True)
post_dom = await self.page.content()
url = self.page.url
step_record = {
"seq_id": self.action_seq_id,
"timestamp": time.time(),
"action": action,
"params": params,
"result": result,
"elapsed_ms": elapsed * 1000,
"url": url,
"pre_screenshot_base64": self._encode_bytes(pre_screenshot_bytes),
"post_screenshot_base64": self._encode_bytes(post_screenshot_bytes),
"pre_dom_length": len(pre_dom),
"post_dom_length": len(post_dom),
}
self.action_seq_id += 1
with open(self.trace_log_path, "a") as f:
f.write(json.dumps(step_record, ensure_ascii=False) + "n")
return step_record
def _encode_bytes(self, data: bytes) -> Optional[str]:
# 实际生产环境建议用OSS链接,这里仅作示例
import base64
return base64.b64encode(data).decode()
async def click(self, selector: str, **kwargs):
return await self._record_step("click", {"selector": selector, **kwargs})
async def fill(self, selector: str, value: str, **kwargs):
return await self._record_step("fill", {"selector": selector, "value": value, **kwargs})
async def type(self, selector: str, text: str, **kwargs):
return await self._record_step("type", {"selector": selector, "text": text, **kwargs})
async def press(self, selector: str, key: str, **kwargs):
return await self._record_step("press", {"selector": selector, "key": key, **kwargs})
async def goto(self, url: str, **kwargs):
return await self._record_step("goto", {"url": url, **kwargs})
async def wait_for_selector(self, selector: str, **kwargs):
return await self._record_step("wait_for_selector", {"selector": selector, **kwargs})
这个封装有一个精妙但容易忽略的功能:它在每个动作执行前后都截取全屏截图并存储为Base64,这允许我们在评测时对比“预期界面状态”和“实际界面状态”,而不只是靠DOM来判断。在后续的轨迹对齐算法里,这些截图会变成验证参数准确性的关键证据。
从MiniWoB++到电商后台:我们怎么造出300个企业级任务
MiniWoB++提供了约100个基础网页任务,但太像“实验室玩具”——所有按钮固定位置,没有网络延迟,没有弹窗干扰。我们把任务库扩展到300个,涵盖五个真实企业场景:CRM客户信息录入、SaaS后台配置修改、电商订单异常处理、内部知识库的多模态检索、以及带验证码的人机验证绕过(仅用于评测,非攻击)。
扩展任务时我踩了一个大坑:直接用Playwright录制生成操作脚本作为“参考轨迹”,然后让Agent去复现。结果发现Agent学会了走捷径——它发现某个按钮的XPath固定不变,直接记忆,而非通过视觉理解。所以我们引入了界面随机化层,包括元素位置微调、颜色方案变化、以及注入10%概率的随机异步弹窗(如“会话即将过期”提示)。这个随机化层,后来成为区分“视觉理解型Agent”和“DOM作弊型Agent”的关键手段。
工具调用轨迹:比“答对了”更重要的隐藏得分项
任务完成率只看终点,但一个Agent可能用20步冗余操作才做完一个5步就能完成的任务。这种效率差异在规模化部署时就是成本灾难。所以,我设计的评测体系把“工具调用轨迹评估”放在与任务完成率同等甚至更高的权重上。(延伸阅读:用Codestral Mamba重构遗留系统,比Copilot快3倍的爽感,差点毁在一次上下文崩溃上)
序列编辑距离:不只比“走了几步”,更比“走法像不像人”
我采用Levenshtein编辑距离来计算Agent的动作序列与参考轨迹的相似度,但做了关键扩展:动作的权重不是二元的,而是取决于动作类型。比如“缺失一个点击”比“多一个wait”严重得多,因为前者可能导致状态断裂。下表是我经过100次人工标注校准后的动作替换代价矩阵:
| 动作A / 动作B | click | fill | press | wait | scroll |
|---|---|---|---|---|---|
| click | 0 | 3 | 4 | 1 | 2 |
| fill | 3 | 0 | 2 | 2 | 3 |
| press | 4 | 2 | 0 | 3 | 3 |
| wait | 1 | 2 | 3 | 0 | 2 |
| scroll | 2 | 3 | 3 | 2 | 0 |
代价矩阵的背后逻辑是:click和fill对业务状态改变巨大,wait和scroll是辅助动作。如果一个Agent跳过点击直接去填充文本框,代价是3,表示严重不匹配。这套加权编辑距离,让我们从原始序列中提取得分0~1的轨迹对齐分。
下面是一个计算加权编辑距离的快速实现,并返回归一化分数:
import numpy as np
ACTION_TYPES = ["click", "fill", "press", "wait", "scroll"]
# 代价矩阵行/列顺序同上
COST_MATRIX = np.array([
[0, 3, 4, 1, 2],
[3, 0, 2, 2, 3],
[4, 2, 0, 3, 3],
[1, 2, 3, 0, 2],
[2, 3, 3, 2, 0]
], dtype=int)
action_to_idx = {a: i for i, a in enumerate(ACTION_TYPES)}
def weighted_edit_distance(ref_seq: list, agent_seq: list) -> tuple:
"""返回编辑距离和归一化轨迹对齐分数(0~1)。"""
n, m = len(ref_seq), len(agent_seq)
dp = [[0]*(m+1) for _ in range(n+1)]
for i in range(1, n+1):
dp[i][0] = dp[i-1][0] + deletion_cost(ref_seq[i-1])
for j in range(1, m+1):
dp[0][j] = dp[0][j-1] + insertion_cost(agent_seq[j-1])
for i in range(1, n+1):
for j in range(1, m+1):
if ref_seq[i-1] == agent_seq[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
sub_cost = substitution_cost(ref_seq[i-1], agent_seq[j-1])
dp[i][j] = min(
dp[i-1][j-1] + sub_cost,
dp[i-1][j] + deletion_cost(ref_seq[i-1]),
dp[i][j-1] + insertion_cost(agent_seq[j-1])
)
distance = dp[n][m]
max_possible = max(n, m) * 4 # 最大单步代价为4(press代替click)
score = 1 - (distance / max_possible) if max_possible > 0 else 1.0
return distance, max(0.0, min(1.0, score))
def substitution_cost(a, b):
if a == b: return 0
i, j = action_to_idx.get(a, -1), action_to_idx.get(b, -1)
if i == -1 or j == -1: return 4
return COST_MATRIX[i][j]
def deletion_cost(a):
# 删除一个动作的代价,可视为跳过该动作的影响
# 简化:与click替换为无动作的损失,用代价矩阵中click行最大值
return 3 if a in ["click","fill"] else 1
def insertion_cost(b):
return 3 if b in ["click","fill"] else 1
在早期的评测中,我们对比了两个Agent——一个基于Qwen-VL-Max,一个基于CogAgent-18B。Qwen-VL-Max的任务完成率比CogAgent高8个百分点,但轨迹对齐分低0.15。回溯日志发现,Qwen-VL-Max倾向于在每一步后都加一个冗余的wait_for_timeout(2000),在长任务里积攒了大量“无效等待”,而CogAgent的动作精准但没有容错,一旦遇到未加载完的元素就崩溃。这个发现直接促使我们在指标体系中加入了“步骤效率”维度。
任务完成率的三个层次:别只看最终结果
任务完成率这个指标听上去很简单,但在我踩过的坑里,它的定义至少有三种截然不同的解读,错误使用会让评测结果南辕北辙。
成功率、步骤效率与失败恢复能力的三维量化
我把任务完成率拆解成三个独立指标,构成一个雷达图,而不是一个平均数:
- 硬成功率(Hard Success Rate):Agent完全按照预期路径完成任务,没有任何异常步骤(比如弹窗被检测到并处理不算异常,但点错按钮后撤回算一次异常)。这个指标最苛刻,反映“一次就做对”的能力。
- 软成功率(Soft Success Rate):Agent最终达到目标状态,无论过程是否绕了弯路或触发过恢复机制。这对应现实中最常见的“只要能办成事就行”的度量。我们的300个任务中,软成功率通常比硬成功率高15-20个百分点。
- 步骤效率(Step Efficiency):即参考轨迹的最短步骤数除以Agent实际使用步骤数。如果Agent用了两倍的步骤才完成,效率就是0.5。这项指标直接转化为计算成本——每多一个步骤,就意味着多一次截图分析、多一次模型API调用。
- 失败恢复分(Recovery Score):当Agent遇到弹窗、404页面、验证码等异常时,能否在有限步骤内回到正确路径。我们定义恢复分为:恢复步数不超过2步得1分,3~5步得0.5分,超过5步或被卡死得0分。最后取所有异常遭遇的平均分。
我们测试某头部开源Agent CogAgent-18B(v2.0版本)时,得到了下面这组有深度的数据:
| 指标 | CogAgent-18B | GPT-4o + Computer Use | 人类操作员(基线) |
|---|---|---|---|
| 硬成功率 | 41.2% | 67.8% | 94.0% |
| 软成功率 | 58.0% | 82.3% | 98.5% |
| 步骤效率 | 0.61 | 0.82 | 0.95 |
| 失败恢复分 | 0.44 | 0.79 | 0.92 |
(数据来源:我们内部300任务的公开匿名测试集,每个Agent运行3次取平均,环境完全一致。模型版本截止2025年4月15日。)
GPT-4o在失败恢复上的表现明显好于CogAgent,主要原因在于它能够利用弹窗里的文本信息快速决策,而CogAgent的视觉定位更依赖固定的界面记忆,遇到陌生弹窗常常选择重复上一次操作,导致陷入循环。这一差异直接反映在恢复分上,也验证了评测必须包含异常注入。
评测平台架构:让100个Agent同时考试,我们怎么扛住
单次跑一个Agent在300个任务上需要大概4个小时,如果我们要同时比较5个Agent,串行跑就得20小时,这在快速迭代的工程节奏里不可接受。所以,我设计了一个轻量级的并发评测平台,基于asyncio和docker化浏览器集群,让多个Agent实例同时在不同任务上考试,并自动汇聚结果。(延伸阅读:我把Llama推理从x86移到Graviton4省了23%,但半夜那三个坑差点让服务裸奔)
多Agent并发测试与结果聚合:一个生产级的考试调度器
架构的核心是一个任务调度器,负责从任务池中分配任务给空闲的Agent容器,并收集每条轨迹记录。每个Agent运行在一个独立的Docker容器内,内置Playwright浏览器和我们的InstrumentedPage。考试结束后,调度器把轨迹文件和截图打包,送进一个离线分析管道,计算所有指标。
下面这段代码展示了调度器的核心——基于生产者-消费者模式的并发执行流。它能同时启动多个考题,并优雅处理容器超时。
import asyncio
import json
import os
from typing import List, Dict
from datetime import datetime
class AgentEvaluator:
def __init__(self, task_list: List[Dict], agent_image: str, max_workers=5):
self.task_queue = asyncio.Queue()
for task in task_list:
self.task_queue.put_nowait(task)
self.agent_image = agent_image
self.max_workers = max_workers
self.results = []
self.lock = asyncio.Lock()
async def worker(self, worker_id: int):
while True:
try:
task = self.task_queue.get_nowait()
except asyncio.QueueEmpty:
break
task_id = task["id"]
print(f"[Worker-{worker_id}] 开始任务 {task_id}")
try:
# 启动容器,并注入任务配置
container_cmd = (
f"docker run --rm --network=host "
f"-e TASK_CONFIG='{json.dumps(task)}' "
f"{self.agent_image}"
)
proc = await asyncio.create_subprocess_shell(
container_cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await asyncio.wait_for(
proc.communicate(), timeout=300 # 5分钟超时
)
if proc.returncode == 0:
# 读取容器输出的轨迹JSON(由InstrumentedPage写出的文件)
# 实际实现中会映射卷挂载,这里简化
trace_data = json.loads(stdout.decode())
result = {"task_id": task_id, "status": "success", "trace": trace_data}
else:
result = {"task_id": task_id, "status": "error", "stderr": stderr.decode()}
except asyncio.TimeoutError:
result = {"task_id": task_id, "status": "timeout"}
try:
proc.kill()
except:
pass
async with self.lock:
self.results.append(result)
self.task_queue.task_done()
async def run(self):
workers = [asyncio.create_task(self.worker(i)) for i in range(self.max_workers)]
await self.task_queue.join()
for w in workers:
w.cancel()
# 聚合结果并保存
with open(f"aggregated_results_{datetime.now().strftime('%Y%m%d%H%M')}.json", "w") as f:
json.dump(self.results, f, ensure_ascii=False, indent=2)
print(f"评测完成,共执行任务数:{len(self.results)}")
在并发测试中,我遇到一个棘手的问题:多个Agent容器同时竞争宿主机GPU资源(如果Agent使用了本地视觉模型),会导致推理延迟激增,步骤超时频繁。解决方案是引入基于令牌桶的推理限流器,保证每个容器在获得推理令牌后才能调用模型,把并发推理峰值控制在GPU显存上限的80%以内。这一改动让并发数从3提升到8,而不会出现雪崩式超时。
结果聚合与指标的置信区间
由于Agent的行为有随机性(尤其是基于采样的模型),每个任务我们运行3次,结果取中位数,并计算任务成功率的标准误差。当某个Agent的软成功率标准差超过10个百分点时,我们会标记为“不稳定”,并深入分析是否对输入截图的微小变化过度敏感。这个稳定性指标,后来成为我们选型的关键否决项——如果一个Agent在同一个任务上成功率从80%跳到30%,部署到生产就是一颗定时炸弹。
公开基准测试:CogAgent-18B和GPT-4o的得分报告,到底谁更能打
我拿我们在300个企业任务上的评测框架,给两个代表性Agent跑了全量考试。上面表格已经给出了硬指标,现在说点更深的棋局——这些数字背后的战略意味。
GPT-4o的软成功率82.3%,远高于CogAgent-18B的58%,而且失败恢复分几乎是后者的两倍。这个差距的根源不在于视觉识别的精度(实际上CogAgent在标准UI控件上的定位精度甚至略优),而在于规划和异常处理能力。GPT-4o利用其文本推理能力,在遇到弹窗时能阅读弹窗内容并生成应对策略(比如点击“稍后提醒”),而CogAgent的视觉-行动映射相对机械,更像一个强化学习训练的“肌肉记忆”系统,缺乏对文本语义的深层利用。这给行业一个明确的信号:未来的多模态Agent竞争力,将取决于视觉基座模型和语言基座模型的融合深度,而不是单纯刷UI操作的benchmark。(延伸阅读:我让两个LLM互相攻击了三个月,才看清安全评测自动化的七寸在哪里——一个红队框架的架构决策全记录)
在硬成功率上,人类94%,Agent最好67.8%。这近30个百分点的差距,几乎全部来自需要多步推理和跨页面信息关联的复杂任务。比如“根据订单号在CRM中查询客户信息,再到SaaS后台更新合同条款”,Agent容易在页面切换后忘了前一个页面的关键信息,导致“记忆断裂”。这提示我们,评测的下一个前沿,很可能要加入“跨页面状态保持”这一维度。
实战踩坑清单:5个让我熬夜的坑
以下是搭建这套评测系统过程中真正让我头疼的问题,每个坑都值得写一篇独立的复盘,这里只说核心教训。
1. Playwright截图导致内存爆炸。 全流程截图虽然提供了完整的视觉证据,但每张截图Base64编码后约2-3MB,300个任务×20步/任务,就是几十GB的内存占用。解决方案是将截图上云存储(MinIO),轨迹JSON只存URL。同时实现了一个环形缓冲区,只保留最近3步的截图在内存中做界面对比。
2. 多Agent并发时的文件写入冲突。 如果多个Agent实例写同一个轨迹日志文件,会丢数据。必须让每个Agent独享一个日志文件,或使用进程安全的消息队列。
3. 参考轨迹的制作成本。 最初我们想完全自动化生成参考轨迹,结果AI写的参考轨迹本身就有错误,导致评测基准失真。最后我们采用“半自动”方式:人类专家用Playwright录制一遍,然后由程序去除冗余等待,并注入合法替代路径(比如“用快捷键保存”和“点击保存按钮”都算正确)。这300个任务的参考轨迹耗费了两个人两周时间,但质量决定了整个评测的可信度。
4. 异步DOM状态与截图时间差。 Agent点击后,页面可能还在渲染中,此时截图可能捕获到中间态,导致“元素未加载”的误判。解决方法是等待网络空闲和特定元素出现后,再采集截图,同时记录截图的等待时间作为额外指标。
5. 模型API限流引发步骤超时链式反应。 并发测试时,某个Agent因为API速率限制卡住,导致后续步骤全部超时,最终导致整个任务失败,尽管Agent的规划逻辑本身没有问题。因此评测必须单独统计“因外部限流导致的失败”,避免把基础设施的问题算在Agent头上。
以上是我的判断和实战总结。过去半年,我从用对话评分糊弄自己,到亲手搭建这套从轨迹到目标的多维考试,最大的感受是:我们评判一个Agent好不好,本质上是在评判它能不能被信任。而信任,不是靠一个任务的最终状态来建立的,是看到它的每一步操作都像人一样谨慎、准确,并且在出错时知道怎么回来。
我的核心判断是:未来六个月内,任何面向企业交付的多模态Agent,如果不公开其任务完成率、轨迹对齐分和失败恢复分,将无法通过技术选型的第一个关卡。测评会从锦上添花的营销材料,变成硬核的准入门槛。但我可能被打脸的风险在于:如果基础模型在GUI操作领域的zero-shot能力出现代际突破——例如直接用视觉自回归预测像素级别的操作,而不依赖工具调用——那么我现在这套基于“动作序列”的评测框架就会部分失效,因为动作序列本身变成黑箱,我们只剩下输入截图和最终状态。那时候,评测的重心将转移到“界面状态变化预测的准确性”上,我可能需要换一种完全不同的CT机。
但至少在今天,轨迹即正义。