前言
在前几篇中,我们的 Agent 已经学会了“看图”并做出了“决策”(例如:“我应该点击左下角的背包图标”)。但是,大模型输出的只是这一行文本,电脑屏幕并不会自己做出反应。
“最后的一公里” 往往是最考验工程能力的。对于《荒野大镖客2》这样的 3A 大作,或者 Photoshop 这样复杂的软件,简单的 API 调用往往无效。
本篇我们将下沉到 cradle/environment/ 和 cradle/agent/ 的底层,揭秘 Cradle 的 IO 控制层 与 技能库 (Skill Registry) 设计。看看它是如何解决“鼠标点不准”、“键盘按无效”以及“动作太僵硬”这三大难题的。
1. IO 控制层:模拟“幽灵”之手
在 cradle/environment/input/ 目录下,封装了 Agent 与操作系统交互的所有接口。Cradle 在这里做了一个很重要的抽象:将不同的 OS 交互屏蔽在统一的接口之下。
1.1 基础实现库选择
对于通用软件(Chrome, Office),pyautogui 足够好用。但对于 3D 游戏(如 RDR2),传统的 Win32 API 往往失效(因为游戏使用 DirectInput 捕获硬件信号)。
Cradle 在源码中通常混合使用了以下几种方案:
-
PyAutoGUI: 处理标准的桌面鼠标移动、点击。
-
PyDirectInput / Ctypes: 专门用于游戏场景。它能绕过系统层面的某些钩子,直接向驱动层发送 Scancode(扫描码)。
1.2 动作原语 (Atomic Actions)
Cradle 定义了一套“动作原子”,这是 Agent 能做的最小单位。在源码的 executor 类中,你会看到类似这样的定义:
Python
# 伪代码示意
class InputExecutor:
def mouse_click(self, x, y, button='left'):
# 移动到指定位置
self.move_to(x, y)
# 模拟点击
pyautogui.click(button=button)
def key_press(self, key, duration=0.1):
# 对于游戏,持续按压时间(duration)至关重要
# 按下时间太短(<50ms)可能被游戏引擎忽略
pydirectinput.keyDown(key)
time.sleep(duration)
pydirectinput.keyUp(key)
关键点:Duration (按压时长)。你会发现源码中大量出现了 time.sleep。这是为了让操作更像人,同时确保游戏引擎有足够的时间检测到输入状态的变化。
2. 精准度控制:解决坐标映射难题
这是 GCC (通用计算机控制) 中最容易出 Bug 的地方。
问题场景:
-
游戏运行在 1920x1080 分辨率。
-
为了节省 Token,截图被 Resize 成了 1024x1024 喂给 GPT-4V。
-
GPT-4V 返回坐标
(512, 512)。 -
如果我们直接点击屏幕的
(512, 512),那就点偏了。
2.1 归一化坐标系 (Normalized Coordinates)
Cradle 的解决方案是在内部维护两套坐标系,并使用归一化坐标进行转换。
在 cradle/utils/geometry.py 中,通常包含此类逻辑:
Python
def transform_coordinates(point, source_dim, target_dim):
# point: (x, y) from GPT-4V
# source_dim: (1024, 1024)
# target_dim: (1920, 1080)
scale_x = target_dim[0] / source_dim[0]
scale_y = target_dim[1] / source_dim[1]
return (int(point[0] * scale_x), int(point[1] * scale_y))
此外,配合上一篇提到的 SoM (Set-of-Mark),Agent 其实更多是在**“点标签”**(Click Tag 1),而不是直接算坐标。Cradle 会在后台查找 Tag 1 对应的原始 Bounding Box 中心点,从而保证 100% 的像素级精准。
3. 技能库 (Skill Registry):从原子到复合
如果让 GPT-4V 每一步都输出“按下 W 0.1秒”,那玩一个游戏可能需要消耗几百万个 Token,且延迟极高。
Cradle 引入了 Skill Registry 的概念,这是一种分层控制 (Hierarchical Control) 思想。
3.1 技能的分层结构
-
Level 0: Atomic (原子级) ->
click,type,scroll -
Level 1: Composite (复合级) ->
buy_item,turn_around,Maps_menu -
Level 2: Strategic (策略级) ->
complete_mission
3.2 技能注册机制
在源码中,技能通常通过装饰器或配置文件注册。这使得扩展 Cradle 变得非常容易。
Python
# 技能定义示例
@register_skill("buy_item_from_shop")
def buy_item(item_name):
"""
Description: Buy a specific item from the shop menu.
"""
# 1. 识别物品位置
item_loc = find_text_on_screen(item_name)
if item_loc:
# 2. 移动鼠标并点击
mouse_click(item_loc)
# 3. 确认购买 (通常是长按 Enter)
key_press('enter', duration=1.0)
else:
raise SkillExecutionError("Item not found")
RAG 的作用:
当 Agent 决定要做某事时,它会先去 Skill Registry 检索。检索到的不仅仅是函数名,还有函数的 Docstring(使用说明)。这些说明会被塞入 Prompt,告诉 LLM:“你可以调用 buy_item 这个函数,它需要参数 item_name。”
4. 动作执行的稳定性保障
除了精准度,稳定性也是 IO 层的核心 KPI。Cradle 在 executor 中加入了一些防御性编程:
-
重试机制 (Retry Logic):如果 OCR 识别点击后的画面没有变化,底层可能会自动尝试再次点击。
-
异常捕获:防止
pyautogui抛出的 FailSafe 异常导致整个 Agent 崩溃。 -
鼠标平滑移动:在某些反作弊严格的游戏中,瞬间移动鼠标会被判定为脚本。Cradle 的底层实现有时会利用 Bezier 曲线生成鼠标轨迹,模拟人类手的抖动和加速减速。
5. 总结
Cradle 的 IO 控制层向我们展示了,要让 AI 真正接管电脑,仅仅有“智能”是不够的,还需要极强的“工程落地能力”。
-
底层:利用
ctypes/DirectInput搞定游戏的驱动级输入。 -
中间层:利用坐标映射解决视觉与执行的偏差。
-
上层:利用 Skill Registry 将复杂逻辑封装,减轻大模型的推理负担。
正是这一层看似枯燥的代码,确保了 Agent 的每一次点击都掷地有声。
下一篇预告:
Agent 玩了一天游戏,它怎么记得上午去过哪里?如果任务被打断,它怎么恢复状态?在下一篇 【Cradle 源码解析五】记忆的艺术:向量存储与长短期记忆管理 中,我们将解析 Cradle 的 Memory 模块,看看它是如何利用 RAG 和向量数据库构建“数字海马体”的。

431

被折叠的 条评论
为什么被折叠?



