数据截至 (上游 commit 96983c73ed09)
第 7 章 · 巧妙之处、边界与横向对比
这章讲什么: 前六章讲了「它怎么转」。这一章讲「哪些地方值得抄」「它会在哪崩」「跟别人比取舍在哪」。
7.1 值得带走的十处设计
① 错误信息是写给模型的
UFO 里几乎所有异常文 本都直接面向模型措辞。三处例子:
| 位置 | 文本 |
|---|---|
ufo/module/dispatcher.py:55 | ... please retry or execute a different command. |
ufo/automator/action_execution.py:108 | ... please refresh the application state to get the latest interactable control information. |
ufo/client/computer.py:550 | Tool {tool_key} is not registered in current computer: {self._name} |
妙在哪: agent 系统里,异常的最终读者是 LLM 而不是人。把「下一步该怎么办」写进错误消息,等于免费获得一次纠错引导。
② id + name 双保险,不一致时执行且告知
模型必须同时报编号和名字;不一致时以编号为准执行,再把真实名字写进返回值告诉模型(ufo/client/mcp/local_servers/ui_mcp_server.py:312-327,_verify_id 在 :170)。
妙在哪: 拒绝执行会让屏幕毫无变化,模型无从判断发生了什么,只会原样重试。执行 + 告知,让模型下一步能看见后果并纠偏。
③ 声明式段间依赖 + 三处校验
@depends_on / @provides 把「我要什么、我给什么」写在类头上,构造期查一次链、运行期执行前后各查一次(ufo/agents/processors/core/strategy_dependency.py:481、:510;三个校验方法在 processor_framework.py:238、:265、:312)。
妙在哪: 用字典解耦管线段落,通常的代价是打错 key 只能在运行时炸。这套装饰器把契约显式化,把偶发 bug 变成确定性报错,却不牺牲可插拔性。
④ 一张 YAML 决定谁能用什么工具
config/ufo/mcp.yaml 用「agent 名 × 应用 root 名」两级键决定工具集。打开 Word 就多出 COM 工具,打开记事本就没有。
妙在哪: 上下文相关的工具集,通常要写一堆条件分支。这里退化成一次字典查找 + 一次 default 回退(ufo/client/computer.py:640-647)。加应用支持不需要改 Python。
⑤ 改 DAG 与点鼠标共用一条管线
Galaxy 的七个改图操作是七个 MCP 工具(ufo/client/mcp/local_servers/constellation_mcp_server.py:29),经过和 GUI 动作完全相同的 ActionCommandInfo → Command → dispatcher → Computer 链路。
妙在哪: 「编辑一个数据结构」和「操作一个 GUI」表面上毫 不相干,但抽象到「LLM 发起一次带 schema 的工具调用」这一层就完全同构。复用换来的是自动获得 schema 注入、错误回喂、日志、记忆一整套设施。
⑥ 运行态直接写进 prompt
拼改图 prompt 时,每个 DAG 节点后面标 ✏️ [MODIFIABLE] 或 🔒 [READ-ONLY](galaxy/agents/prompters/base_constellation_prompter.py:120-170)。
妙在哪: 与其让模型乱改再拒绝(浪费一轮 + 模型不知道为什么被拒),不如先把规则用它能读懂的方式告诉它。这是把并发约束翻译成自然语言。
⑦ 状态合并按「谁更靠前」而不是「谁更新」
_is_state_more_advanced 给状态排等级,只有编排器那份等级更高才覆盖 agent 那份(galaxy/session/observers/constellation_sync_observer.py:452-478)。
妙在哪: 两份数据合并的常见做法是比时间戳,但分布式下时钟不可靠。这里用领域语义的单调性(任务状态只会前进不会后退)来定优先级,比时间戳稳。