1. 项目概述当AI学会“看”与“点”最近在折腾一些自动化脚本时我一直在想有没有一种方法能让AI不只是“理解”屏幕上的文字还能真正“看到”屏幕上的元素并且像真人一样去“操作”它们比如让它帮我自动填写一个复杂的网页表单或者在一款没有开放API的桌面软件里完成一系列点击和输入。这听起来像是RPA机器人流程自动化的活儿但传统的RPA工具要么太重要么不够灵活学习成本也高。直到我遇到了ScreenAgent这个项目它给我提供了一个全新的思路。简单来说ScreenAgent是一个基于大语言模型LLM的智能体框架它的核心能力是视觉感知和精准操作。它不依赖于任何预先编写好的脚本或固定的坐标而是通过实时截图让AI“看到”当前屏幕理解屏幕上有什么按钮、输入框、文本然后生成相应的操作指令如点击、输入、滚动再通过系统级的自动化工具如pyautogui去执行。这就像给电脑配了一个拥有“眼睛”和“手”的AI助手。你只需要用自然语言告诉它“帮我把桌面上的那个文档拖到回收站”或者“在这个网页的搜索框里输入‘ScreenAgent’并点击搜索”它就能尝试去理解和执行。这对于处理那些没有API、UI结构复杂多变、或者需要结合视觉判断的任务来说潜力巨大。无论是日常办公自动化、软件测试还是辅助一些重复性的电脑操作ScreenAgent都提供了一个极具想象力的起点。2. 核心架构与工作原理拆解ScreenAgent的魅力在于它将几个看似独立的技术栈巧妙地串联了起来形成了一个闭环的智能体系统。要理解它如何工作我们需要拆解其核心的三个环节视觉感知、决策推理和执行操作。2.1 视觉感知从像素到语义理解这是整个流程的起点也是最关键的一步。ScreenAgent获取屏幕信息的方式很简单——截图。但截图只是一张RGB像素矩阵AI如何从中理解“这是一个登录按钮”、“那是一段错误提示文本”呢这里通常结合了两种思路OCR光学字符识别提取文本信息这是基础。通过像pytesseract、easyocr或云端OCR服务将图片中的文字内容提取出来包括文字的位置边界框。这样AI就知道了屏幕上“写”了什么字以及这些字在哪里。视觉语言模型VLM进行元素理解这是更高级和核心的能力。单纯OCR不够因为屏幕上还有很多非文本元素图标、按钮、复选框以及它们之间的布局关系。ScreenAgent会将截图有时会连同OCR结果发送给一个多模态大语言模型例如 GPT-4V、Gemini Pro Vision 或开源的 LLaVA。你可以向VLM提问比如“请列出屏幕上所有可交互的元素如按钮、输入框、链接及其大致位置和可能的功能描述。”VLM的强大之处在于它不仅能识别物体还能结合常识进行推理。例如它看到一张图片旁边有“下载”、“预览”两个单词即使“下载”这个词没有直接贴在按钮图标上它也能推断出那个箭头图标很可能就是下载按钮。ScreenAgent利用VLM将原始的屏幕像素转化成了一个结构化的、富含语义的“屏幕描述”例如“屏幕中央有一个蓝色的‘提交’按钮其上方是一个标签为‘用户名’的输入框目前为空左侧有一个复选框旁边文字是‘记住我’。”注意VLM的识别精度和速度是关键瓶颈。复杂或非标准的UI界面可能导致识别错误。在实际应用中往往需要结合UI自动化框架如针对Windows的UIAutomation或针对浏览器的Playwright来获取更精确的元素树信息作为视觉识别的补充或验证但这会牺牲一部分通用性。2.2 决策推理大语言模型作为“大脑”拿到结构化的屏幕描述和用户指令例如“登录系统”后就轮到决策环节了。ScreenAgent会将以下信息拼接成一个提示词Prompt发送给文本大语言模型如 GPT-4, Claude, 或本地部署的 Llama 3系统指令定义AI助手的角色和目标“你是一个桌面自动化助手通过操作图形界面完成任务”。当前屏幕描述来自VLM的分析结果。用户指令本次需要完成的任务。操作历史可选之前几步的操作记录帮助模型理解上下文。操作规范告诉模型可以输出哪些类型的动作如CLICK [x, y],TYPE [text],PRESS [key],SCROLL [direction]以及坐标或参数的格式。大语言模型的工作是进行任务规划Task Planning和动作生成Action Generation。它会分析“要完成‘登录’我需要先点击‘用户名’输入框然后输入我的账号接着点击‘密码’输入框输入密码最后点击‘提交’按钮。” 然后它将这个计划转化为一系列具体的、可执行的操作指令。这个过程的挑战在于大语言模型是概率模型它的输出可能不稳定。有时它可能会生成不合逻辑的操作序列或者对元素定位产生混淆。因此ScreenAgent的提示词工程和可能的后处理逻辑如对坐标的合理性校验就显得尤为重要。2.3 执行操作从指令到物理动作决策模型输出如CLICK [950, 300]这样的指令后就进入执行阶段。ScreenAgent会调用像pyautogui、PyDirectInput这样的库来模拟鼠标和键盘事件。pyautogui.click(950, 300)将鼠标移动到屏幕坐标 (950, 300) 并点击。pyautogui.write(‘username’在当前位置输入文本。pyautogui.press(‘enter’)按下回车键。这里有几个技术细节需要注意坐标系统屏幕坐标的原点 (0,0) 通常在左上角。VLM或元素定位返回的坐标需要与这个系统对齐。有时返回的是相对坐标或边界框需要计算中心点作为点击位置。操作延迟在动作之间插入短暂的延迟如time.sleep(0.5)是必要的以等待界面响应如页面加载、动画完成。可靠性pyautogui的模拟是“盲操作”它不知道操作是否成功。因此在执行下一步操作前通常需要再次截图通过VLM或OCR验证上一步操作的结果例如检查登录按钮是否消失了或者是否有“登录成功”的提示出现从而实现一个“感知-决策-执行-验证”的闭环。3. 从零开始构建你的ScreenAgent环境与核心实现理解了原理我们来看看如何动手搭建一个基础版本的ScreenAgent。这里我会以Python为核心使用本地可运行的模型和库来演示确保你能在自己的机器上复现。3.1 环境准备与依赖安装首先创建一个干净的Python虚拟环境是个好习惯。然后安装核心依赖。# 创建并激活虚拟环境 (可选) python -m venv screenagent_env source screenagent_env/bin/activate # Linux/Mac # screenagent_env\Scripts\activate # Windows # 安装核心自动化与图像处理库 pip install pyautogui pillow opencv-python-headless # 安装OCR引擎这里以Tesseract的Python封装为例 # 注意需要先安装Tesseract-OCR本体 # Ubuntu: sudo apt install tesseract-ocr # Mac: brew install tesseract # Windows: 下载安装包从GitHub pip install pytesseract # 安装用于与LLM/VLM交互的库这里以OpenAI API和Llama.cpp本地推理为例 pip install openai # 如需使用GPT-4V # 对于本地模型可能需要安装llama-cpp-python # pip install llama-cpp-python除了Python包确保你的系统已经安装了Tesseract-OCR并且其可执行文件路径已添加到系统环境变量中或者在代码中指定路径。3.2 核心模块一屏幕感知器 (ScreenPerceptor)这个模块负责“看”。我们将实现一个类它能截图并能通过OCR和如果可用VLM来解析屏幕内容。import pyautogui import cv2 import pytesseract from PIL import Image import base64 from openai import OpenAI # 假设使用OpenAI API import json class ScreenPerceptor: def __init__(self, ocr_enginetesseract, vlm_clientNone): self.ocr_engine ocr_engine self.vlm_client vlm_client # 例如 OpenAI 客户端实例 # 如果是Windows可能需要指定tesseract路径 # pytesseract.pytesseract.tesseract_cmd r‘C:\Program Files\Tesseract-OCR\tesseract.exe’ def capture_screen(self, regionNone): 捕获屏幕截图可指定区域 (left, top, width, height) screenshot pyautogui.screenshot(regionregion) # 转换为OpenCV格式和PIL格式备用 screenshot_cv cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR) screenshot_pil screenshot return screenshot_pil, screenshot_cv def extract_text_with_ocr(self, image_pil): 使用OCR提取屏幕中的所有文本及位置 # 可以尝试不同的PSM模式来提高准确性 data pytesseract.image_to_data(image_pil, output_typepytesseract.Output.DICT) text_elements [] n_boxes len(data[text]) for i in range(n_boxes): if int(data[conf][i]) 60: # 置信度阈值 text data[text][i].strip() if text: x, y, w, h data[left][i], data[top][i], data[width][i], data[height][i] text_elements.append({ text: text, bbox: (x, y, xw, yh), # 左上右下 center: (x w//2, y h//2) }) return text_elements def analyze_with_vlm(self, image_pil, prompt): 使用VLM分析截图。需要配置API密钥。 if not self.vlm_client: return VLM client not configured. # 将PIL图像转换为base64 buffered BytesIO() image_pil.save(buffered, formatPNG) img_base64 base64.b64encode(buffered.getvalue()).decode(utf-8) # 构建请求 (以OpenAI GPT-4V为例) response self.vlm_client.chat.completions.create( modelgpt-4-vision-preview, messages[ { role: user, content: [ {type: text, text: prompt}, { type: image_url, image_url: {url: fdata:image/png;base64,{img_base64}}, }, ], } ], max_tokens1000, ) analysis_result response.choices[0].message.content # 尝试解析结果为结构化JSON try: # 假设我们要求VLM返回JSON格式 return json.loads(analysis_result) except json.JSONDecodeError: # 如果返回的是自然语言则直接返回文本 return analysis_result def get_screen_description(self, use_vlmTrue): 获取当前屏幕的综合描述 pil_img, cv_img self.capture_screen() ocr_results self.extract_text_with_ocr(pil_img) description 屏幕文本元素\n for elem in ocr_results: description f- 文本‘{elem[‘text’]}’位于位置 {elem[‘bbox’]}\n if use_vlm and self.vlm_client: vlm_prompt 请详细描述这张截图中的用户界面。重点指出 1. 所有看起来可以点击的按钮、图标或链接并描述其可能的功能。 2. 所有文本输入框。 3. 任何重要的状态信息或标题。 请以简洁清晰的列表形式返回。 vlm_analysis self.analyze_with_vlm(pil_img, vlm_prompt) description \n视觉模型分析\n str(vlm_analysis) return description, ocr_results, pil_img实操心得OCR的精度受屏幕分辨率、字体、背景对比度影响很大。对于UI自动化有时直接使用pyautogui.locateOnScreen(‘button.png’)进行模板匹配对于固定图标可能更可靠但缺乏灵活性。VLM API调用有成本和延迟。对于简单任务可以仅用OCR对于复杂界面VLM的理解能力无可替代。可以考虑将截图缩小后再发送给API以降低成本和提升速度。将OCR结果和VLM分析结果结合起来能相互补足。例如用OCR的精确定位来修正VLM返回的模糊位置描述。3.3 核心模块二决策大脑 (AgentBrain)这个模块负责“想”。它接收屏幕描述和用户任务调用LLM生成操作序列。class AgentBrain: def __init__(self, llm_client, system_promptNone): self.llm_client llm_client self.system_prompt system_prompt or 你是一个桌面自动化助手。你的目标是分析用户指令和当前屏幕描述生成下一步要执行的具体操作。 可用的操作类型 1. CLICK [x, y] - 在屏幕坐标(x, y)处单击。坐标是屏幕像素坐标原点在左上角。 2. DOUBLE_CLICK [x, y] - 双击。 3. RIGHT_CLICK [x, y] - 右键单击。 4. TYPE [“text”] - 在当前焦点处输入文本。确保文本用双引号包裹。 5. PRESS [“key”] - 按下单个键如 “enter”, “tab”, “esc”。用双引号包裹。 6. HOTKEY [“key1”, “key2”] - 组合键如 [“ctrl”, “c”]。 7. SCROLL [amount] - 滚动鼠标滚轮正数向上负数向下。 8. WAIT [seconds] - 等待指定秒数。 9. DRAG [x1, y1, x2, y2] - 从(x1,y1)拖拽到(x2,y2)。 你输出的必须是一个合法的JSON数组每个元素是一个操作对象。例如 [ {action: CLICK, params: [100, 200]}, {action: WAIT, params: [1.5]}, {action: TYPE, params: [Hello World]} ] 请只输出JSON不要有其他任何解释。 def plan_next_actions(self, screen_description, user_instruction, history[]): 根据当前状态规划下一步动作 user_message f 当前屏幕描述 {screen_description} 用户指令{user_instruction} 最近操作历史最新在最后 {history[-5:] if history else ‘无’} 请根据以上信息生成接下来要执行的一个或几个操作。思考要完成的步骤然后输出JSON数组。 messages [ {role: system, content: self.system_prompt}, {role: user, content: user_message} ] try: response self.llm_client.chat.completions.create( modelgpt-4, # 或其它文本模型 messagesmessages, temperature0.1, # 低温度保证输出稳定性 response_format{“type”: “json_object”} # 要求返回JSON如果API支持 ) plan_str response.choices[0].message.content # 清理可能出现的markdown代码块标记 plan_str plan_str.strip().replace(‘json’, ‘’).replace(‘’, ‘’) action_plan json.loads(plan_str) # 确保返回的是列表 if isinstance(action_plan, dict) and ‘actions’ in action_plan: action_plan action_plan[‘actions’] return action_plan except Exception as e: print(fLLM规划出错{e}) return []注意事项提示词工程是关键系统提示词定义了AI的行为边界。你需要清晰地说明操作格式、坐标系统并严格要求输出格式。不清晰的提示会导致输出解析失败。历史上下文提供最近几步的操作历史能帮助LLM理解当前任务进展到了哪一步避免重复操作或死循环。错误处理LLM的输出可能不符合JSON格式或者生成不合理坐标如负数或超出屏幕范围。代码中必须有健壮的异常捕获和结果验证逻辑。3.4 核心模块三动作执行器 (ActionExecutor)这个模块负责“做”。它将抽象的指令转化为真实的鼠标键盘事件。import pyautogui import time class ActionExecutor: def __init__(self, screen_widthNone, screen_heightNone): self.screen_width, self.screen_height pyautogui.size() # 安全特性将鼠标移动到屏幕角落默认左上角会触发pyautogui.FailSafeException可以紧急停止 pyautogui.FAILSAFE True def validate_coordinates(self, x, y): 确保坐标在屏幕范围内 x max(0, min(x, self.screen_width - 1)) y max(0, min(y, self.screen_height - 1)) return int(x), int(y) def execute_action(self, action_obj): 执行单个动作对象 action_type action_obj.get(‘action’).upper() params action_obj.get(‘params’, []) try: if action_type ‘CLICK’: x, y self.validate_coordinates(params[0], params[1]) pyautogui.click(x, y) print(f已点击 ({x}, {y})) elif action_type ‘DOUBLE_CLICK’: x, y self.validate_coordinates(params[0], params[1]) pyautogui.doubleClick(x, y) elif action_type ‘RIGHT_CLICK’: x, y self.validate_coordinates(params[0], params[1]) pyautogui.rightClick(x, y) elif action_type ‘TYPE’: text params[0] pyautogui.write(text) print(f已输入文本{text}) elif action_type ‘PRESS’: key params[0] pyautogui.press(key) print(f已按下按键{key}) elif action_type ‘HOTKEY’: pyautogui.hotkey(*params) print(f已按下组合键{params}) elif action_type ‘SCROLL’: amount params[0] pyautogui.scroll(amount) elif action_type ‘WAIT’: seconds params[0] time.sleep(seconds) print(f等待 {seconds} 秒) elif action_type ‘DRAG’: x1, y1, x2, y2 params x1, y1 self.validate_coordinates(x1, y1) x2, y2 self.validate_coordinates(x2, y2) pyautogui.moveTo(x1, y1) pyautogui.dragTo(x2, y2, duration0.5) # 添加拖拽持续时间使其更自然 else: print(f未知操作类型{action_type}) return False # 每个动作后稍作停顿让系统跟上 time.sleep(0.3) return True except Exception as e: print(f执行动作 {action_obj} 时出错{e}) return False def execute_plan(self, action_plan): 执行一个动作计划列表 for i, action in enumerate(action_plan): print(f执行步骤 {i1}/{len(action_plan)}: {action}) success self.execute_action(action) if not success: print(“动作执行失败停止计划。”) break避坑技巧启用故障安全FAILSAFE这是最重要的安全措施。当鼠标移动到屏幕左上角坐标0,0时pyautogui会抛出异常并停止所有后续操作防止脚本失控。操作间延迟time.sleep是必须的。UI响应需要时间尤其是网络应用或大型软件。延迟太短会导致操作在界面准备好之前触发造成混乱。坐标验证永远不要信任LLM生成的坐标是合理的。必须将其钳制在屏幕范围内。执行反馈打印出正在执行的动作对于调试和监控流程至关重要。4. 实战演练组装一个自动化登录助手现在我们把上面三个模块组装起来实现一个具体的场景让AI助手帮我们登录一个桌面应用例如一个假设的名为“TaskMaster”的待办事项软件。4.1 场景设定与流程设计假设“TaskMaster”登录界面包含以下元素一个“用户名”标签和其右侧的输入框。一个“密码”标签和其右侧的输入框密码类型。一个“记住我”复选框。一个蓝色的“登录”按钮。我们的目标是用户发出指令“登录TaskMaster用户名为admin密码为123456”AI能自动完成整个操作。流程设计如下感知截取当前屏幕通过VLM分析出所有交互元素及其大致位置。规划LLM根据用户指令和屏幕描述生成操作序列。预期序列可能是[点击用户名框 输入“admin” 点击密码框 输入“123456” 点击登录按钮]。执行与验证执行器按顺序执行操作。在执行“点击登录按钮”后再次截图通过OCR检查是否有“登录成功”或“错误”提示以验证任务是否完成。4.2 代码整合与主循环import json from openai import OpenAI import time # 初始化各个组件 openai_client OpenAI(api_key‘your-openai-api-key’) # 请替换为你的密钥 perceptor ScreenPerceptor(vlm_clientopenai_client) brain AgentBrain(llm_clientopenai_client) executor ActionExecutor() def run_agent_task(user_instruction, max_steps10): 运行智能体完成任务 history [] for step in range(max_steps): print(f\n 步骤 {step 1} ) # 1. 感知 print(“正在分析屏幕...”) screen_desc, ocr_results, current_screen_image perceptor.get_screen_description(use_vlmTrue) # 可选简单检查任务是否已完成例如搜索“登录成功”等关键词 if “登录成功” in screen_desc or “Welcome” in screen_desc: print(“检测到登录成功提示任务完成”) break # 2. 规划 print(“正在规划下一步操作...”) action_plan brain.plan_next_actions(screen_desc, user_instruction, history) print(f“生成的计划{action_plan}”) if not action_plan: print(“未生成有效计划退出。”) break # 3. 执行 print(“正在执行操作...”) executor.execute_plan(action_plan) # 记录历史 history.extend(action_plan) # 简单起见只记录最近几个动作的类型 history_for_next [f{a[‘action’]}{a.get(‘params’,[])} for a in action_plan] # 步骤间等待让界面稳定 time.sleep(2) print(“\n任务执行结束。”) if __name__ “__main__”: # 在执行前请确保目标应用TaskMaster的登录窗口已打开并处于前台。 user_command “登录TaskMaster用户名为admin密码为123456” run_agent_task(user_command)4.3 效果评估与调试运行上述脚本你会看到AI尝试去理解屏幕、生成操作并执行。但第一次尝试很可能不会完美成功。以下是几个常见的调试点VLM识别不准它可能把“用户名”标签识别为输入框或者找不到“登录”按钮。可以尝试优化给VLM的提示词例如“请专注于识别可交互的UI控件并给出其中心点像素坐标。” 或者在截图前手动将鼠标悬停在目标按钮上让界面显示出悬停状态如有有时能帮助VLM识别。LLM规划错误LLM可能先生成“点击密码框”再生成“输入密码”但漏掉了“点击用户名框”这一步。这通常是因为屏幕描述不够清晰或者历史上下文不足。可以尝试在系统提示词中更加强调“逐步思考”和“检查每个必要步骤”。坐标误差VLM返回的坐标是估算值点击可能偏移。一个改进策略是利用OCR提取出的“用户名”文本的坐标然后根据经验偏移一定像素例如向右150像素来定位其旁边的输入框。这需要你对目标应用的UI布局有一定先验知识。执行速度过快操作太快导致界面来不及响应。增加WAIT操作的时长或者在关键操作如点击登录按钮后强制等待更长时间。一个重要的进阶技巧是混合定位策略不要完全依赖VLM的坐标。可以结合使用OCR文本定位对于有明确文字标签的元素如按钮上的文字使用OCR获取的文本位置更精确。图像模板匹配对于固定的图标如软件Logo、关闭按钮X使用pyautogui.locateOnScreen()。VLM语义理解对于布局复杂、元素多样的界面用VLM来理解整体结构和元素关系指导前两种方法。5. 性能优化与生产级考量一个玩具级的ScreenAgent和能在实际工作中稳定运行的版本之间存在巨大差距。以下是提升其鲁棒性和可用性的关键方向。5.1 提升感知精度与速度分层感知策略不要每次都调用昂贵的VLM。可以设计一个决策器先尝试用OCR和模板匹配定位目标如果失败或置信度低再启用VLM进行深度分析。屏幕区域聚焦不需要分析整个屏幕。可以根据任务上下文只截取屏幕的相关区域例如活动窗口区域进行分析能大幅减少VLM的处理负担和干扰信息。缓存与记忆对于重复出现的元素如软件的菜单栏可以将其位置和特征缓存起来下次直接使用无需重复识别。使用专用UI自动化框架对于特定平台如Windowspywinauto或UIAutomation能直接获取UI元素的准确属性控件类型、名称、坐标比视觉识别可靠得多。可以将其作为视觉识别的“黄金标准”或后备方案。5.2 增强决策的可靠性与可解释性思维链Chain-of-Thought提示要求LLM在输出JSON前先输出它的思考过程。这不仅能提高规划质量也便于我们调试为什么AI会做出错误决策。多步验证与回滚在执行一个关键动作如提交表单前可以要求AI先“确认”一下当前状态。例如在点击“删除”按钮前先截图问VLM“当前是否有确认删除的对话框弹出”。如果没有则暂停或回退。定义子任务与技能将复杂任务分解为可复用的“技能”Skill如find_element(description),click_element(element),input_text(element, text)。让LLM调用这些技能而不是直接输出原始坐标。这降低了LLM的决策难度也使得整个系统更模块化。5.3 设计健壮的执行与错误处理机制动作执行后的状态验证这是实现闭环的关键。每个动作执行后都应有一个“验证”步骤。例如点击“保存”后检查是否出现“保存成功”的提示或者错误消息。这可以通过OCR扫描特定区域来实现。超时与重试机制对于“查找元素”或“等待某个状态出现”的操作需要设置超时。未能在规定时间内达到预期状态则触发重试或失败处理流程。异常分类与恢复策略定义常见的异常类型如ElementNotFoundError,UnexpectedScreenError并为每种异常设计恢复策略如重新扫描屏幕、回退上一步、向用户请求帮助等。操作录制与回放对于成功完成的任务序列可以将其录制下来记录每个动作的上下文和屏幕快照。未来遇到类似界面时可以优先尝试匹配和回放录制的序列而不是重新进行视觉识别和规划这能极大提高效率。5.4 资源、成本与隐私模型选择GPT-4V能力强大但成本高、延迟高。可以探索更经济的方案如使用本地部署的多模态模型LLaVA、Qwen-VL或专门针对UI理解微调的模型。隐私敏感屏幕截图可能包含敏感信息。如果使用云端VLM/LLM API必须谨慎考虑隐私风险。对于处理敏感数据的场景必须使用本地模型或建立私有化部署。计算资源本地运行VLM需要较强的GPU。需要权衡速度、精度和硬件成本。构建一个成熟的ScreenAgent系统本质上是在可靠性、通用性、成本和开发效率之间寻找平衡。从简单的概念验证出发针对具体的应用场景如自动化测试某一款特定软件、处理某一类固定格式的文档进行深度定制和优化是使其真正产生价值的最佳路径。它可能永远不会达到100%的全自动万能但在许多半结构化、重复性的数字劳动场景中成为一个强大的“副驾驶”或“效率倍增器”已经足够令人兴奋。