跳到主要内容

事件后处理:为什么原始事件要清洗、怎么清洗

本章讲 legacy/openadapt/events.py:录制落库的是原始事件流,重放和喂给模型前必须先压成干净的动作序列。

1. 它要解决的小问题

你「点一下发送按钮」这个动作,底层可能是:几十个 move(鼠标滑过去)+ 一个 click(press) + 一个 click(release)。喂给大模型的话,这些噪声既贵又乱。事件处理要做的是把物理输入归约成语义动作:几十个 move → 丢掉冗余的;逐字符按键 → 合成一段文本;连续同向滚动 → 合并成一次。

2. 思路/直觉:一串归约器,反复跑到不动为止

处理不是一次过,而是迭代到定点(fixpoint):反复跑归约,直到事件数不再变化(或到达 MAX_PROCESS_ITERS)。

原始 action_events


merge_events 里依次跑一串 process_fns:
remove_invalid_keyboard_events
remove_redundant_mouse_move_events
merge_consecutive_keyboard_events
merge_consecutive_mouse_move_events
merge_consecutive_mouse_scroll_events
merge_consecutive_mouse_click_events
│ 每跑完一个,还要 discard_unused_events:
│ 丢掉不再被任何动作引用的 window/screenshot/browser 事件

数量没变了? ──否──► 再来一轮
│是

processed_action_events(干净序列)

怎么读这张图: merge_events 里的 process_fns 是有序的归约器列表;外层 get_events 把整个 merge_events 反复调用直到收敛。

3. 真实实现

3.1 入口:get_events 的定点循环

get_events(events.py:22)从库里读出四类事件,然后 while True 反复调 merge_events,直到四类事件数都不再变化就 break(events.py:99-112),并受 MAX_PROCESS_ITERS(events.py:14,当前为 1)限制。

3.2 归约器清单:merge_events

merge_events(events.py:878)里 process_fns(events.py:913-923)按固定顺序跑归约,每跑完一个就对 window/screenshot/browser 调 discard_unused_events(events.py:939-953),把不再被动作引用的辅助事件丢掉——动作变少了,配套的截图/窗口也跟着瘦身。注意 remove_move_before_click注释掉了(events.py:920-922),因为它会导致 NaiveReplayStrategy 里点击丢失。

3.3 一个归约器长什么样:去冗余移动

remove_redundant_mouse_move_events(events.py:606)的判据很直白:一个 move 如果和前一个或后一个事件坐标相同(is_same_pos,events.py:614),就丢弃;被丢的事件挂进合并事件的 children,并把它占用的时间 dt 累加回保留事件的时间戳(events.py:648-667),这样时间轴不被压缩破坏

3.4 逐字符按键 → 文本

重放/提示时,ActionEvent.from_dict(models.py:384)能反向把一段 text(如 <cmd>-a)拆回 press/release 子事件序列(models.py:415-459),还特意兼容 OpenAI 偶尔把分隔符 >-< 写成 >< 的情况(handle_separator_variations,models.py:420-422)。这是「干净动作」和「模型可读文本」之间的双向桥。

4. 关键细节/坑

  • 合并不删数据。 被归约掉的原始事件都挂在保留事件的 children 里(events.py:653:657),可回溯、可重算。
  • 时间戳守恒。 每个归约器都小心把丢弃事件的时长补回保留事件,并在 merge_events 里用断言校验时间单调递增(events.py:927-938)——顺序错了直接 ipdb 断点(遗留调试代码,注意这是老版单体的痕迹)。
  • processed_action_events 是消费入口。 模型的 Recording.processed_action_events(models.py:106-118)属性就是重放/提示实际读的干净序列;原始 action_events 保留在库里。

5. 代码地图

主题文件路径符号
处理入口 + 定点循环legacy/openadapt/events.pyget_events
归约器编排legacy/openadapt/events.pymerge_events
去冗余移动legacy/openadapt/events.pyremove_redundant_mouse_move_events
合并连续点击/滚动/按键legacy/openadapt/events.pymerge_consecutive_mouse_click_eventsmerge_consecutive_mouse_scroll_eventsmerge_consecutive_keyboard_events
丢弃无用辅助事件legacy/openadapt/events.pydiscard_unused_events
文本↔按键序列互转legacy/openadapt/models.pyActionEvent.from_dict_create_key_events
干净序列属性legacy/openadapt/models.pyRecording.processed_action_events