OceanPresent

GUI Agent在线轨迹Rubric评估协议


两个独立维度

将 AndroidDaily 论文的 GRADE 评估协议(人工准则+LLM 状态追踪+确定性仲裁)落地,用于评估运行在真实闭源商业 App上的 GUI Agent。保留 GRADE 手工三层准则能力,同时解决GUI Agent生产环境的两个现实约束:常无截图(隐私降级)、常无人工准则(流量无法全量标注)。

评估引擎在两个正交维度上工作。先把它们区分清楚,后续所有模块行为都由此决定。

1.1 数据维度:截图可用性

GUI Agent 轨迹中文本信号始终存在(instruction、afly _tree、action、thought、span),截图是可选叠加项。

判定规则:轨迹中每一步都有截图一 Full;否则(含部分缺图)一Text-only。无 Hybrid 态。
下游不感知:所有模块(证据层、裁决层、诊断层)消费统一接口,判官后端在模块内部路由切换,不暴露到外部。

1.2 规则维度:准则可用性

与截图无关。指任务是否有可用的评估准则,四级优先取最高级

级供给方式来源可靠性
G0手工三层准则人工编写 obligations / quality / negatives最高
G1essential-state 模板人工预制里程碑 matcher高
G2自动派生 rubricLLM 从 instruction 生成中(降权)
G3progressive judgeLLM 逐步判"是否推进任务"兜底

G0/G1 需人工投入;G2/G3纯自动。GO完全兼容 GRADE 原生用法—一这是方案对 AndroidDaily 的对齐保
证。

1.3 Rule Verifier 独立于两个维度

Rule-based 启发式(卡死/环路/漂移等)不依赖截图、不依赖准则,每条轨迹都跑,结果作为先验喂给后官。详见 §3.4。

flowchart TB
    OTel["GUI Agent OTel 轨迹<br/>instruction · a11y_tree · action · thought · span · token<br/>screenshot_raw / screenshot_som(可选)"]

    subgraph M1["① 轨迹装配 TrajectoryAssembler"]
        A1["归一化标准 step 序列<br/>探测截图可用性 → Full / Text-only"]
    end

    subgraph M2["② 准则供给 GuidelineProvider"]
        G["G0 手工三层准则<br/>G1 essential-state 模板<br/>G2 自动派生 rubric<br/>G3 progressive(无准则)"]
    end

    subgraph M3["③ 证据装配 EvidenceBuilder"]
        E["多源装配 · text-only 优先 · 有图叠加 SoM<br/>增量追踪 TraceEvidence / UpdateMemory<br/>→ EvidenceBundle B"]
    end

    subgraph RV["旁路:Rule Verifier(每条轨迹必跑)"]
        R["卡死 · 环路 · 漂移 · 输入残留 · 短路<br/>→ rule_hits"]
    end

    subgraph M4["④ 裁决 Verdict Engine"]
        V["G0/G1/G2: 三维检查 obligations / quality / negatives → 仲裁<br/>G3: 逐步 progressive 推进判定 → 汇总"]
    end

    DEC{"verdict == success ?"}

    subgraph M5["⑤ 诊断 DiagnosisWriter(仅非 success 触发)"]
        D["Probe: failure_category · critical_step · reason · citations"]
    end

    STORE[("GUI Agent_eval_result 存储<br/>keyed by trace_id")]
    BENCH["低置信 → 人工复核 → GUI Agent-JudgeBench"]

    OTel --> M1
    M1 --> M2
    M1 --> M3
    M1 --> RV
    R -. rule_hits .-> E
    R -. rule_hits .-> V
    M2 --> M4
    M3 --> M4
    M4 --> DEC
    DEC -- 是 --> STORE
    DEC -- 否 --> M5
    M5 --> STORE
    STORE --> BENCH

二、各准则级在管线里的走法

准则级走证据层?裁决层判定方式备注
G0✓(结构化)GRADE 三维检查(obligations 覆盖率 + quality 接地 + negatives 一票否决)裁决层最完整路径
G1✓(结构化)essential-state matcher 命中判定(允许路径多解、时序无关)操作义务松弛为里程碑命中
G2✓(结构化)同 G0 三维结构,但降权;negatives 只保留可交叉印证项自动派生准则不完全信任
G3✓(简化版)逐步问 LLM"是否推进任务",不做精确状态断言;rule_hits 仍作先验仍消费 step 观测序列,但不编译结构化 EvidenceBundle

G3 为什么还要走证据层:progressive judge 每一步仍需要当前观测(activity、a11y_tree、action、有图时的截图)作为判断依据,这正是 assemble_obs 的职责。只是 G3 不编译结构化的 EvidenceBundle——直接消费 step 观测序列,逐步 LLM 调用后汇总。Rule Verifier 对 G3 同样有效:规则侧检出的环路/漂移可作为 hard fail 直接终结 G3 判定。


三、模块设计

3.1 ① 轨迹装配(TrajectoryAssembler)

输入:OTel span 序列
输出:标准 step 列表 + Full/Text-only 标记

标准 step 结构(判官统一消费形态):

{
  "step_id": 5,
  "global_task": str,
  "activity": str,
  "a11y_tree": [{"text":str,"class_name":str,"bounds":BoundingBox,"is_clickable": bool,"is_enabled": bool,"is_scrollable": bool}],
  "agent_output": str,
  "screenshot": base64,
  "signals": [str], //["activity", "a11y_tree", "action", "thought", "screenshot_raw", "screenshot_som"]
  "rule_hits": [],
  "step_duration_ms": 1840,
  "token_usage": 1234
}
{
  "step_id": 5,
  "global_task": "在小红书搜索杭州旅游攻略,阅读3篇帖子并总结",
  "activity": "RedNote",
  "a11y_tree": [{"text":str,"class_name":str,"bounds":BoundingBox,"is_clickable": bool,"is_enabled": bool,"is_scrollable": bool}],
  "agent_output": "点击确认按钮{"type": "click", "target_id": 17, "text": "确认"}",
  "screenshot": base64,
  "signals": ["activity", "a11y_tree", "agent_output", "screenshot"],
  "rule_hits": [],
  "step_duration_ms": 1840,
  "token_usage": 1234
}

设计要点:

  • a11y_tree 单字段:直接存内容本身,不拆 hash_before/after + diff。相邻差异由证据层临时计算。
  • activity 派生:从 a11y_tree 根节点解析 package/class,不新增采集字段。
  • signals 声明:列明本步可用信号,下游判官据此切换行为,禁止引用未声明字段(防无图时幻觉)。

3.2 ② 准则供给(GuidelineProvider)

输入:task instruction + fingerprint
输出:(guideline_source, guideline) 元组

供给选择器:

def provide_guideline(task):
    g = lookup_manual_guideline(task.fingerprint)
    if g:
        return ("G0", g)  # 手工三层准则
    tpl = lookup_template(task.fingerprint)
    if tpl:
        return ("G1", tpl.essential_states)  # essential-state 模板
    if task.is_structured():
        return ("G2", derive_rubric(task))  # LLM 自动派生
    return ("G3", None)  # progressive 兜底

G0 手工三层准则结构(完全兼容 GRADE,以小红书搜索总结 case 为例):

{
  "task_template_id": "xhs.search_read_summarize.v1",
  "obligations": [
    {"id": "OBL-1", "desc": "在小红书内检索目标关键词", "required_signals": ["a11y_tree", "action"]},
    {"id": "OBL-2", "desc": "打开至少 3 篇不同的非广告帖子", "required_signals": ["activity", "a11y_tree"]},
    {"id": "OBL-3", "desc": "从所打开帖子中提取信息", "required_signals": ["a11y_tree"]},
    {"id": "OBL-4", "desc": "产出一份总结", "required_signals": ["a11y_tree", "action"]}
  ],
  "quality": [
    {"id": "QUAL-1", "desc": "总结须涵盖所读 3 篇帖子的核心观点", "anti_hallucination": "禁止引入证据包未出现的通用知识"},
    {"id": "QUAL-2", "desc": "总结基于实际阅读内容,非仅凭标题浅层浏览"}
  ],
  "negatives": [
    {"id": "NEG-1", "desc": "不得使用带'赞助/广告/推广'标签的帖子", "severity": "veto"},
    {"id": "NEG-2", "desc": "阅读帖子不足 3 篇即产出总结", "severity": "veto"},
    {"id": "NEG-3", "desc": "跳出小红书完成任务", "severity": "veto"}
  ]
}

存储:GUI Agent_manual_guideline 表(G0)、GUI Agent_essential_states 表(G1)。

G1 essential-state 模板结构(同一个小红书搜索总结 case 的 G1 版本):

G1 与 G0 的区别:G0 穷举操作义务(搜索、打开、提取、总结四条),G1 只列关键 UI 语义里程碑——判官只判"轨迹是否经过了这些里程碑",不关心具体路径。

{
  "task_template_id": "xhs.search_read_summarize.v1",
  "instruction_pattern": "在{app}搜索{keyword},阅读{n}篇{filter}帖子并总结",
  "essential_states": [
    {
      "step_hint": 1,
      "semantic_desc": "进入小红书并确认首页就绪",
      "matcher": {"type": "activity", "pattern": "com.xingin.xhs/.MainActivity"},
      "required": true
    },
    {
      "step_hint": 3,
      "semantic_desc": "搜索结果已展示(搜索词已输入,结果列表可见)",
      "matcher": {"type": "a11y", "pattern": "儿童牙膏", "mode": "fuzzy"},
      "required": true
    },
    {
      "step_hint": 6,
      "semantic_desc": "至少进入过 3 篇不同帖子的详情页",
      "matcher": {"type": "activity", "pattern": "com.xingin.xhs/.NoteDetailActivity"},
      "required": true,
      "min_hits": 3,
      "dedup_by": "note_id_param"
    },
    {
      "step_hint": -1,
      "semantic_desc": "轨迹末态存在 Agent 生成的总结文本",
      "matcher": {"type": "text", "pattern": "总结|推荐|观点|建议", "scope": "last_3_steps"},
      "required": true
    }
  ]
}

matcher 字段说明:

  • type: activity(匹配 Activity 名)/ a11y(匹配 a11y_tree 节点文本)/ text(匹配扁平化文本)
  • mode: exact(精确匹配)/ fuzzy(子串 + 模糊匹配,默认)
  • min_hits: 该里程碑需在轨迹中命中的最少次数(默认 1)
  • dedup_by: 去重策略(如 note_id_param 表示按 URL 参数中的 note_id 去重)
  • scope: 限定匹配范围(如 last_3_steps 只在末 3 步中查找)

裁决层 G1 判定逻辑:

def check_essential_states(B, essential_states):
    hits = []
    for es in essential_states:
        matched_steps = [s for s in B.steps if es.matcher.hit(s)]
        # 按 dedup_by 去重后计数
        deduped = (
            dedup(matched_steps, key=es.dedup_by) if es.dedup_by else matched_steps
        )
        if len(deduped) >= (es.min_hits or 1):
            hits.append(es)
    coverage = len(hits) / len(essential_states)
    all_required_hit = all(es in hits for es in essential_states if es.required)
    return {
        "coverage": f"{len(hits)}/{len(essential_states)}",
        "passed": all_required_hit,
        "hit_steps": [s.step_id for es in hits for s in matched_steps],
    }

与 G0 的 v_obl 相比:G0 逐条义务判覆盖率(路径灵活但义务明确),G1 只判里程碑是否被经过(更松弛,适合无法穷举义务的场景)。G1 不检查 quality / negatives——这些维度留给 G0 使用。

3.3 ③ 证据装配(EvidenceBuilder)

输入:标准轨迹 + 准则 + Rule Verifier 先验
输出:EvidenceBundle B(G0/G1/G2 走结构化;G3 走简化 step 观测序列)

保留 GRADE 增量状态追踪内核:

def build_evidence_bundle(traj, guideline, mode):
    M, P = {}, []
    rule_hits = RuleVerifier.scan(traj)  # 旁路先跑(§3.4)
    for step in traj.steps:
        obs = assemble_obs(step, mode)  # text-only: activity+a11y+action+thought
        # full: + screenshot_raw + screenshot_som
        p_t = trace_evidence(traj.instruction, guideline, obs, step.rule_hits)
        M = update_memory(M, p_t)  # 关键:跨步状态保持,防瞬态信息丢失
        P.append(p_t)
    return EvidenceBundle(
        instruction=traj.instruction,
        guideline=guideline,
        memory=M,
        evidence=P,
        rule_hits=rule_hits,
        mode=mode,
    )

三个必须保留的 GRADE 机制:

  1. TraceEvidence 只提原子事实——"点击了搜索结果第 2 项"而非"屏幕上有个列表"
  2. UpdateMemory 防瞬态丢失——step5 的价格在 step6 跳页后仍绑定在 memory
  3. Filtering 噪声过滤——忽略无效滑动、加载动画、误触回退

有图 vs 无图的处理在 assemble_obs 一处完成:text-only 态跳过 screenshot 字段;full 态把原始截图和 SoM 截图都装入 obs。证据层以上所有模块不感知这个差异。

3.4 旁路:Rule Verifier

每条轨迹都跑,与截图可用性、准则可用性均无关。 结果作为先验同时喂给证据层和裁决层。

启发式计算(基于 a11y_tree / activity / action)触发结论
卡死检测连续 3 步 a11y_tree 一致且 action 无变化failed,execution
冗余环路(activity, action_type, target) 重复 ≥3failed,execution
意图漂移activity 切出目标 App 且 5 步未回归reflection 扣分;未回归则 failed
输入残留a11y 焦点在 EditText、输入非空但无 submitexecution 扣分
短路检测essential-state 命中路径 / 实际步数 < 0.5效率扣分
冷启惩罚单步 latency > 阈值且非首次冷启效率扣分(不影响 verdict)

3.5 ④ 裁决(Verdict Engine)

输入:EvidenceBundle + guideline + rule_hits
输出:{verdict, dimensions, reason, citations, confidence, guideline_source, judge_backend}

按准则级分两条路径:

路径 A:G0 / G1 / G2 → GRADE 三维检查 + 确定性仲裁

v_obl = check_obligations(B, guideline.obligations)  # 覆盖率匹配,允许路径多解
v_qual = check_output_quality(B, guideline.quality)  # 接地验证:内容必须源于 B,禁幻觉
v_neg = check_negative(B, guideline.negatives)  # 违规检测,最高优先级


def arbitrate(v_obl, v_qual, v_neg):
    if v_neg.violated:
        return "risky" if (v_obl.passed and v_qual.passed) else "failed"
    if v_obl.passed and v_qual.passed:
        return "success"
    if v_obl.coverage > 0:
        return "partial"
    return "failed"

G1 的 v_obl 变为 essential-state matcher 命中判定(允许时序无关)。
G2 的仲裁降权:negatives 只保留可从 a11y 文案交叉印证的项。

路径 B:G3 → Progressive 逐步判定

G3 适用于开放式长任务(如"帮我整理一下今天的工作进展"),这类任务无法用操作义务或里程碑穷举。核心思路:把 goal 拆成 sub-goal,每步问 LLM 这步是否在推进某个 sub-goal,汇总后判成败。

Step 1 · Goal 分解(一次性 LLM 调用):

sub_goals = decompose_goal(traj.instruction)
# 输入: 用户指令
# 输出: [{"id": "SG-1", "desc": "打开目标 App", "status": "pending"},
#         {"id": "SG-2", "desc": "定位工作内容相关信息", "status": "pending"},
#         {"id": "SG-3", "desc": "整理并输出总结", "status": "pending"}]

Step 2 · 逐步推进判定(每步一次 LLM 调用,轻量 prompt):

progress = []
for i, step in enumerate(traj.steps):
    obs = assemble_obs(step, mode)  # 复用证据层的观测装配
    # 判官只看当前步观测 + sub-goal 列表 + 前序进展摘要
    p = progressive_judge(obs, sub_goals, progress[-3:], rule_hits)
    progress.append(p)

    # 早期终止:规则检出环路/漂移 → 直接 hard fail
    if "loop" in step.rule_hits or "drift_unrecovered" in step.rule_hits:
        return {
            "verdict": "failed",
            "critical_step": i,
            "reason": f"Rule 检出 {step.rule_hits[-1]},G3 提前终止",
        }

progressive_judge 的 prompt 骨架(轻量、聚焦):

# 当前步观测
activity: {obs.activity}
a11y_tree: {obs.a11y_tree}  (截断至 2000 字符)
action: {obs.action}
thought: {obs.thought}
rule_hits: {obs.rule_hits}

# 任务 sub-goals
{sub_goals 列表及当前状态}

# 最近 3 步进展摘要
{progress[-3:] 的 summary}

# 输出 JSON(轻量)
{
  "advances_subgoal": "SG-1" | null,    // 本步推进了哪个 sub-goal
  "action_quality": "productive" | "neutral" | "regressive" | "stuck",
  "note": "一句话说明本步做了什么"
}

Step 3 · 汇总判定:

def aggregate(progress, sub_goals):
    # 统计每个 sub-goal 是否至少被推进过一次
    sg_advanced = {sg["id"]: False for sg in sub_goals}
    for p in progress:
        if p["advances_subgoal"]:
            sg_advanced[p["advances_subgoal"]] = True

    # 统计 action_quality 分布
    qualities = [p["action_quality"] for p in progress]
    stuck_count = qualities.count("stuck")
    regressive_count = qualities.count("regressive")

    # 判定逻辑
    all_advanced = all(sg_advanced.values())
    if all_advanced and regressive_count == 0:
        return "success"
    if stuck_count >= 3 or regressive_count >= 2:
        return "failed"
    # 找第一次停滞/退步的 step 作为 critical_step
    critical = next(
        (
            i
            for i, p in enumerate(progress)
            if p["action_quality"] in ("stuck", "regressive")
        ),
        None,
    )
    if sum(sg_advanced.values()) / len(sg_advanced) >= 0.5:
        return "partial"
    return "failed"

G3 与 Rule Verifier 的协作:Rule Verifier 的 rule_hits 在 G3 中有双重作用:(1) 作为 progressive_judge prompt 的输入,让 LLM 知道规则侧已检出问题;(2) 作为 early termination 条件——检出环路或漂移不可恢复时,G3 不再继续逐步判定,直接以 failed 终结。

G3 的成本控制:每步一次 LLM 调用看似昂贵,但 prompt 极轻量(仅当前步 + sub-goal 列表 + 最近 3 步摘要,无全量轨迹回放),单次约 2000-3000 token。加上规则侧 early termination,实际绝大多数轨迹在 10-15 步内终结。

3.6 ⑤ 诊断(DiagnosisWriter)

仅 verdict ≠ success 时触发。Probe Agent 定位 critical_step + 7 类根因分类:

perception | grounding | planning | execution | reflection | safety | env

输出 failure_category + critical_step + reason + citations,写入 GUI Agent_eval_result。


四、判官输出契约

所有层、所有准则级共用同一 JSON 契约:

{
  "layer": "L3",
  "verdict": "success | partial | failed | risky",
  "overall_score": 0.0,
  "dimensions": {
    "task_completion":    {"score": 0.0, "note": ""},
    "step_correctness":   {"score": 0.0, "note": ""},
    "efficiency":         {"score": 0.0, "note": ""},
    "planning_quality":   {"score": 0.0, "note": ""},
    "safety_side_effect": {"score": 0.0, "note": ""},
    "recovery_robustness":{"score": 0.0, "note": ""}
  },
  "obligations": {"coverage": "3/4", "passed": false},
  "quality":     {"passed": true},
  "constraints": {"passed": true, "violated": null},
  "failure_category": ["perception|grounding|planning|execution|reflection|safety|env"] | null,
  "critical_step": 7,
  "reason": "自然语言诊断,每句引用具体 step_id 与证据字段名",
  "citations": [3, 5, 7],
  "evidence": [{"step": 7, "signal": "a11y|activity|screenshot|rule", "value": ""}],
  "evidence_signals": ["a11y_tree", "action", "rule_hits"],
  "confidence": 0.86,
  "guideline_source": "G0 | G1 | G2 | G3",
  "judge_backend": "text-llm | mllm"
}

关键约束:

  • evidence 内不允许引用未在 evidence_signals 中声明的字段——text-only 态审计抓手,杜绝无图时臆造视觉线索
  • critical_step 仅 verdict ≠ success 时填,指向第一次不可挽回的偏离步
  • reason 每句必须落到 step_id + 具体证据字段名,禁止泛泛表述
  • rule_hits 非空时 evidence 必须至少引用一条 rule

五、判官部署

5.1 两态路由

态主判官场景
FullMLLM Judge(Qwen-VL-Max / GPT-4o)离线评测、模型对比
Text-onlyText-LLM Judge(Qwen-Max / DeepSeek 级)生产隐私模式(主战场)

5.2 判官 Prompt 骨架

同一份 prompt,通过 signals 自适应有图 / 无图:

你是一位 Android Mobile GUI Agent 评审员。以下是一条完整轨迹。

# 任务
用户指令:<instruction>
准则来源:<G0/G1/G2/G3>
可选 三层准则 = {obligations, quality, negatives}
可选 essential_states = [{semantic_desc, matcher}, ...]

# 观测(按步,signals 声明可用信号)
[step 0]
  activity: com.x/Detail
  a11y_tree: <当前界面无障碍树文本>
  action: click(target_id=17, text="确认")
  thought(opt): "点击确认"
  screenshot_raw / screenshot_som(if available): <ref | omitted>
  rule_hits: []
[step 1] ...

# 输出:严格 JSON(契约),不要其它文字

# 评审准则
1. G0/G2: 三维分别判——obligations 覆盖率 / quality 接地 / negatives 一票否决
2. G1: essential-state matcher 逐个判命中(allowed 多解、时序无关)
3. G3: 拆 sub-goal,逐步判"是否推进任务"
4. risky 优先级最高:不可逆副作用即使达成任务也标 risky
5. 无图(signals 缺 screenshot):只用 activity + a11y_tree,禁臆造视觉线索
6. 有图:SoM 已编号可交互元素,判断 target_id 对应编号即可(不做 IoU)
7. reason 每句落到 step_id + 证据字段名;rule_hits 非空时必引用
8. critical_step 仅 verdict≠success 时填

This website has been running for 4 years 4 months 7 days 22 hours 43 minutes 21 seconds

皖ICP备2021007094号 2021-PRESENT © OceanPresent