引言:为什么多模态 Agent 是自动化操作的下一个范式

传统 RPA 依赖固定坐标和 DOM 结构,一旦界面改版或分辨率变化,脚本即刻失效。而人类操作界面时,依赖的是视觉信息——我们看按钮的样式、图标的语义、颜色与位置的关系。多模态 Agent 将这一能力赋予机器:通过视觉理解,Agent 可以“看”屏幕,识别可操作元素,并生成动作序列。本文以 DeepSeek 的视觉语言模型为基础,演示如何构建一个具备屏幕理解能力的操作 Agent,并深入工程细节。

之所以选择 DeepSeek,不仅因为其 API 兼容 OpenAI 格式、接入门槛低,更因为 deepseek-chat 模型在视觉问答任务上表现稳定,且支持通过 base_url 快速切换环境。我们将从零实现一个“屏幕阅读器→意图解析→动作生成”的闭环,并讨论其中的关键难点:如何将像素转化为结构化语义、如何保证动作的可靠性、如何处理错误恢复。

系统架构与数据流

我们的 Agent 由四层组成:采集层、感知层、决策层、执行层。采集层负责截屏或接收图像输入;感知层利用 DeepSeek 视觉模型将图像转为文本描述,例如“页面上方有搜索框,右侧蓝色按钮标有登录”;决策层根据用户指令和感知结果,使用语言模型生成 JSON 格式的动作序列;执行层调用模拟点击或键盘输入。整个流程中,感知与决策两次调用模型,但通过 prompt 工程可合并为一次,以减少延迟与成本。

数据流的关键在于中间表示的标准化。我们将感知结果统一为 JSON 结构,包含元素类型、文本、位置(归一化坐标)和置信度。决策层接收该结构,结合用户自然语言指令,输出形如 [{"action": "click", "target": {"text": "登录"}}] 的动作。这种设计让 Agent 具备可解释性,也便于回滚与重试。

第一步:屏幕感知——从像素到结构化语义

感知层是 Agent 的“眼睛”。朴素做法是将整个截图发给模型,但大图会增加 token 消耗且降低准确性。工程上,我们通常使用 OpenCV 做预处理:去除冗余背景、裁剪高亮区域、调整对比度,并将图像缩放至模型推荐的尺寸(如 1024x1024)。然后通过调用 DeepSeek 的 chat 接口,传入图像和提示词,要求返回 JSON 格式的元素清单。

实测中,直接问“描述屏幕内容”会得到过度泛化的回答。必须用强约束 prompt,例如:“你是 UI 自动化的视觉模块,请识别屏幕中所有可交互元素,输出 JSON 数组,每个元素包含 type(button/input/link)、text(若可见)、bbox(归一化 [x,y,w,h])、confidence。不要输出额外解释。” 下面是一个完整调用示例:

import base64, requests, json

def encode_image(path):
    with open(path, "rb") as f:
        return base64.b64encode(f.read()).decode()

response = requests.post(
    "https://api.deepseek.com/chat/completions",
    headers={
        "Authorization": f"Bearer your-deepseek-api-key",
        "Content-Type": "application/json"
    },
    json={
        "model": "deepseek-chat",
        "messages": [{
            "role": "user",
            "content": [
                {"type": "text", "text": "你是 UI 自动化视觉模块,识别屏幕中可交互元素,输出 JSON 数组,每个元素含 type, text, bbox, confidence。"},
                {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{encode_image('screen.png')}"}}
            ]
        }],
        "max_tokens": 800
    }
)
result = response.json()["choices"][0]["message"]["content"]
print(json.loads(result))

实践中我们常发现,模型会偶尔漏掉静态文本或误将装饰图标识别为按钮。对策是:在 prompt 中增加 “优先识别 text 和 input,忽略纯图像装饰”,并将输出 schema 定义为 JSON 后,用 pydantic 做校验,失败时用另一条降级 prompt 重试一次。此外,若页面元素过多(超过 20 个),建议分区域识别,否则准确率明显下降。

第二步:从视觉语义到动作决策

得到元素列表后,Agent 需要理解用户指令并生成操作。比如用户说“用百度搜索 DeepSeek 教程”,Agent 需在元素清单中找到搜索框和搜索按钮。匹配策略可以基于文本相似度(fuzzywuzzy)或使用 LLM 直接决策。我们将元素清单和指令一起发给模型,要求输出动作序列 JSON。

决策 prompt 设计为:”给定用户指令和屏幕元素,生成操作序列,动作类型为 click/type/scroll,target 为元素索引(从 0 开始)。只输出 JSON 数组。“ 由于元素索引可能因识别抖动而改变,我们建议使用元素的语义文本或坐标作为 target,并让模型输出 confidence。以下是决策代码片段:

import requests

def plan_actions(user_cmd, elements):
    prompt = f"用户指令:{user_cmd}\n屏幕元素:{json.dumps(elements, ensure_ascii=False)}\n请输出 JSON 动作数组,每个动作含 action(click/type/scroll)、target(元素文本或坐标)、value(若为 type)。"
    resp = requests.post(
        "https://api.deepseek.com/chat/completions",
        headers={"Authorization": "Bearer your-deepseek-api-key"},
        json={
            "model": "deepseek-chat",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.1,
            "response_format": {"type": "json_object"}
        }
    )
    return json.loads(resp.json()["choices"][0]["message"]["content"])["actions"]

这里有两个关键点:其一,temperature 调低至 0.1,避免随机性导致合法操作被拒绝;其二,强制 JSON 输出格式(如支持)能极大减少解析失败。若模型返回非法 JSON,我们实现了一个鲁棒解析器:提取第一个 [ 和最后一个 ] 之间的内容,用 json5 库解析,仍失败则向模型反馈错误并重试。

第三步:动作执行与坐标映射

动作不能直接使用识别时的像素坐标,因为截图与实时屏幕可能不同。我们在每次动作前重新截屏并定位目标。定位算法优先查找与决策时相同的元素文本;若因动态内容导致缺失,则退化为通过 OCR(如 paddleocr)重新提取文本。执行层使用 pyautogui 或 win32api 模拟鼠标键盘,注意屏幕缩放与多显示器问题:确保坐标经过 DPI 换算。

工程中我们遇到的坑包括:点击无效(元素被遮挡)、滑块拖动不精准、滚动位置错乱。多数问题的根源是视觉识别提供的 bbox 与实际可点击区域存在偏差(模型往往将整个文本块视为可点击)。我们总结出一个补偿方案:对 input 元素,点击其中心偏右 10 像素;对 button,点击文本中心即可。另外,若动作后界面状态未按预期变化,Agent 应具备验证机制——每次操作后对比前后截图的关键区域,若不一致则回滚并尝试备选动作。

第四步:错误处理与自我修复

单次视觉识别准确率不可能 100%,因此 Agent 必须有容错机制。我们设计了两级错误处理:局部容错——当某动作执行失败(如目标未找到),重新感知屏幕,更新元素列表,再试一次;全局重试——若连续多次失败,则修改决策策略,比如将 click 改为 key 事件(Tab 切换焦点)。此外,我们利用 DeepSeek 的自解释能力,将错误信息反馈给模型,让其提出替代方案,类似 ReAct 模式。

一个有效技巧是“动作后快照差异”:对每次操作后截屏,与操作前计算感知哈希(pHash),若差异小于阈值,则认为操作未生效,触发重试。我们在实验中对比了不同阈值对准确率的影响:阈值 0.2 时误报率为 8%,0.1 时升高到 15%,但召回率提升。最终选用 0.15 作为平衡点,并在代码中配置为可调参数。

性能调优:延迟与成本的双重优化

多模态调用比纯文本慢且贵。我们对 100 次操作进行基准测试:完整感知-决策流程平均耗时 2.8 秒,其中图像编码与网络传输占 60%。优化方案包括:1) 使用 DeepSeek 的流式输出(stream=true)减少首 token 等待;2) 在感知阶段只发送必要的裁剪图,减小尺寸;3) 对静态界面使用缓存,比如窗口标题没变就复用之前的元素列表。成本方面,我们按 token 估算每次操作约需 1500 输入 + 300 输出,折合人民币不到 0.01 元,可接受。

我们还实现了一个“快速路径”:当用户指令匹配常见模板(如“点击某按钮”)时,跳过 LLM 决策,直接基于元素文本匹配执行,速度提升至 0.4 秒。实测中,约 30% 的操作可走快速路径,整体体验显著改善。下表是不同配置下的对比:

方案平均延迟成功率成本/次
纯视觉识别2.8s89%0.01 元
快速路径+视觉1.2s93%0.006 元
带重试机制3.5s97%0.015 元

从 Demo 到生产:工程化要点

将原型推向生产,需解决稳定性与安全性。首先,API 调用应做并发控制,避免限流;我们使用 asyncio 封装,设置信号量。其次,所有外部输入(用户指令、图像)需过滤敏感信息,防止 prompt 注入——例如用户可能在指令中夹带“忽略之前指令”。我们增加了内容安全检测,对指令长度和关键词做限制。最后,模型输出需经过白名单校验,只允许预期的动作类型。

另一点常被忽视:环境一致性。不同 OS 的截图色彩空间、鼠标控制 API 差异较大,我们封装抽象层,统一接口。在 Windows 上使用 pyautogui,Linux 上使用 xdotool,macOS 使用 Quartz。虽然增加了维护成本,但换来的是跨平台兼容性。

案例复盘:自动化 PPT 翻页与标注

我们以“根据语音指令在幻灯片上做标注”为例。用户说“在第三页标题下加下划线”,Agent 流程:感知当前幻灯片,识别标题区域;决策层生成 draw_line 动作;执行层使用 opencv 在对应坐标绘制线条,并通过截图验证。过程中遇到的问题是,幻灯片页面切换后坐标失效——我们强制在每次动作前重新感知,并记录页面标识(如标题文本)用于定位。

另一个教训是:当指令涉及多个步骤(如“先翻到下一页,再点击图表”),一次性生成所有动作经常失败,因为第二步依赖第一步后的界面状态。我们的解决方案是迭代式操作:仅生成当前步骤,执行后再调用决策层,形成闭环。虽然增加调用次数,但成功率从 70% 提升至 94%。

总结与未来展望

本文展示了多模态 Agent 从理论到实践的完整路径。关键在于:视觉感知的准确性、动作决策的可靠性、以及容错机制的设计。DeepSeek 模型在这一任务上表现稳定,API 的灵活性允许我们自定义 prompt 和输出格式。下一步,我们计划引入记忆机制,让 Agent 记住常用界面的布局,减少重复感知;同时探索用视觉-语言模型直接预测动作坐标,省去中间表示,进一步降低延迟。

多模态 Agent 的潜力远不止于此,它可应用于自动测试、无障碍辅助、远程桌面控制等。希望本文的细节能帮助你在实际项目中避开常见陷阱,构建出自己的自动化视觉 Agent。