UFO 源码实战 (4):拆解 prompter 模块,看微软如何调教 GPT-4V

前言

在前两篇文章中,我们给 UFO 装上了“眼睛”(UI 截图与标注)和“双手”(Pywinauto 鼠标控制)。但还有一个终极问题没解决:

面对一张满是按钮的截图,UFO 怎么知道该点哪一个?它是如何规划“先点菜单 -> 再点保存 -> 输入文件名”这一系列步骤的?

这就涉及到了 Agent 的大脑——Prompt 构建模块

在 UFO 的源码中,ufo/prompter 目录藏着微软调教 GPT-4V 的核心秘籍。今天我们就来拆解 agent_prompter.py,看看一个工业级的 Agent Prompt 是如何动态生成的。


🏗️ 1. Prompt 的“三明治”架构

打开 ufo/prompter/agent_prompter.py,你会发现 UFO 并不是简单地把一句话扔给 GPT。它构建了一个非常厚的“三明治”结构,包含以下几层:

  1. System Prompt (系统人设):定义它是谁(Windows 操控专家)。

  2. Few-Shot Examples (少样本示例):教它怎么做(给几个操作演示)。

  3. Observation (当前观测):当前的截图 + 控件列表。

  4. History (记忆/历史):之前做了什么(防止死循环)。

  5. User Request (任务):用户到底想干嘛。


🎭 2. 系统人设 (System Prompt)

UFO 把提示词模板放在了外部文件中(通常是 ufo/prompter/templates/ 下的 .yaml 文件)。这种代码与提示词分离的设计非常值得学习。

让我们看看核心 System Prompt 大概长什么样(简化版):

YAML

system: "You are a helpful assistant that interacts with Windows applications..."
instructions:
  - "You will receive a screenshot with numeric labels on UI elements."
  - "Your goal is to choose the correct label to click or type text."
  - "Return your response in strict JSON format."

源码解析:

在 AgentPrompter 类的 construct_system_prompt() 方法中,它会加载这个模板。

关键点:微软在这里极其强调 JSON 格式的约束。它不仅要求 JSON,还详细定义了 JSON 的字段(如 Thought, ControlLabel, Function),确保 GPT 输出的内容能被 Python 代码直接解析。


💡 3. 核心秘籍:动态 Few-Shot (示例学习)

这是 UFO 变聪明的关键。如果你只给规则,GPT 可能会发懵;但如果你给它看几个例子,它学得飞快。

UFO 实现了一套基于检索的示例加载机制 (RAG-like)

源码逻辑 (build_examples_prompt):

Python

def build_examples_prompt(self, current_app_name):
    # 1. 加载示例库
    examples = load_examples()
    
    # 2. 筛选
    # 如果当前操作的是 Notepad,就优先加载 Notepad 的操作示例
    relevant_examples = [ex for ex in examples if ex['app'] == current_app_name]
    
    # 3. 构造 Prompt
    example_text = "Here are some examples of how to interact:\n"
    for ex in relevant_examples:
        example_text += f"User: {ex['user_query']}\n"
        example_text += f"Assistant: {ex['response_json']}\n"
        
    return example_text

为什么要动态加载?

因为 GPT 的上下文窗口(Context Window)是有限的(虽然在变大,但也要省着用)。

  • 当你操作 Word 时,UFO 会塞入“设置字体”、“保存文档”的示例。

  • 当你操作 PowerPoint 时,UFO 会塞入“插入幻灯片”、“播放”的示例。

    这种动态上下文切换,让 UFO 在不同软件下都能表现得像个专家。


📸 4. 融合视觉与文本 (Multimodal Context)

我们在第二篇讲过 SoM(Set-of-Marks)。在 prompter 模块中,这两种模态被正式组装在一起。

Python

# 构造 User Message
user_content = [
    # 1. 文本部分:告诉 GPT 所有的控件列表
    # 这样 GPT 即使看不清图片上的小字,也能通过列表查到 "ID 5: Save Button"
    {
        "type": "text", 
        "text": f"Current UI Structure:\n{ui_tree_text_description}"
    },
    # 2. 视觉部分:带红框标注的截图
    {
        "type": "image_url", 
        "image_url": {
            "url": f"data:image/jpeg;base64,{base64_image}"
        }
    }
]

微软的巧思:

单纯依靠视觉(GPT-4V)容易出现幻觉(Hallucination)。

UFO 通过在 Prompt 里附带一份纯文本的控件清单(包含 ID、名称、类型),构成了**“双重校验”**。

GPT 思考过程变成了:

  • 看图:“我觉得右上角那个红框 #5 像保存。”

  • 查清单:“清单上写着 #5 是 Button 'Save'。”

  • 结论:“没错,就是 #5!”


🧠 5. 记忆链 (History)

Agent 最怕陷入死循环:点了一个按钮 -> 没反应 -> 再点一次 -> 还没反应...

UFO 的 prompter 会维护一个 history 列表,记录过去 N 步的操作和结果

Python

# 简化逻辑
prompt += "History of actions:\n"
for step in history[-5:]: # 只看最近5步
    prompt += f"Step {step.index}: Clicked #5 (Save). Result: Success.\n"

自我反思机制:

如果在 History 中,GPT 发现上一步点击了 #5 但界面没变化(截图没变),它会在新的 Prompt 中收到这个反馈。

GPT-4V 就会在 Thought 中推理:“Previous click failed, maybe I need to click the menu first.”(上次失败了,也许我得先点菜单)。


📝 总结

通过拆解 prompter 模块,我们可以把 Microsoft UFO 的“大脑”公式总结为:

$$\text{Prompt} = \text{Role (System)} + \text{RAG Examples} + \text{Visual (Image)} + \text{Data (UI Tree)} + \text{Memory (History)}$$

这对我们开发自己的 Agent 有什么启示?

  1. 结构化输出是王道:一定要强制 LLM 输出 JSON,不要输出自然语言,否则代码没法接。

  2. 不要吝啬 Context:能给控件清单就给清单,不要只依赖截图。

  3. 动态示例 (RAG):针对不同场景动态替换 Few-Shot 示例,是提升准确率性价比最高的手段。

下期预告:

现在的 UFO 只能用官方的代码跑。如果你想给它加一个“语音控制”功能,或者想让它支持 Mac 系统(虽然很难),该怎么改?

下期文章:《UFO 源码实战 (5):实战修改源码,打造属于你的专属桌面助理》,我们将进行一次代码魔改实战!


想看更多硬核源码拆解?关注博主,不迷路! 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值