SAG架构实战:构建智能GUI自动化系统,告别脚本脆弱性

SAG架构实战:构建智能GUI自动化系统,告别脚本脆弱性
1. 项目概述为什么我们需要更“聪明”的GUI自动化如果你做过GUI自动化测试或者RPA机器人流程自动化大概率经历过这样的痛苦脚本运行得好好的突然就卡住了。可能是某个按钮的ID变了可能是弹窗没按预期出现也可能是屏幕分辨率调整导致元素定位失败。传统的基于坐标或固定属性定位的自动化脚本就像一台设定好程序的机器环境稍有风吹草动它就“死机”了。这正是“智能GUI自动化”要解决的核心痛点。它不再把应用程序界面看作一成不变的静态图片而是尝试像人一样去“理解”和“交互”。我最近深度实践了基于SAG架构的一套方案感觉像是给自动化脚本装上了眼睛和大脑。SAG即Skill-Agent-Grounding架构它不是一个具体的工具而是一种设计范式。简单来说它把复杂的自动化任务拆解“技能”负责具体的原子操作如点击、输入“智能体”负责决策和规划任务流程而“接地”则是确保智能体的抽象指令能准确映射到真实的屏幕元素上。这套组合拳打下来自动化脚本的健壮性和适应性得到了质的提升。它适合那些界面变化频繁、业务流程复杂或者需要处理非结构化场景的自动化需求比如跨平台软件测试、老旧系统集成、或者每日重复的办公流程自动化。无论你是测试开发工程师、RPA开发者还是任何被重复性GUI操作困扰的从业者理解并应用SAG架构的思路都能让你的自动化方案从“脆弱”走向“智能”。2. SAG架构深度解析构建自动化的大脑、小脑与感官要理解智能GUI自动化必须先吃透SAG架构的三层模型。这不仅仅是三个模块的堆砌而是一种清晰的责任划分和协作机制。2.1 Grounding层自动化的“眼睛”与“手”Grounding层我习惯称之为“接地层”或“感知执行层”。它的核心使命是解决“在哪里”和“做什么”的问题即把上层抽象的指令如“点击登录按钮”转化为对屏幕上具体像素的实际操作。传统自动化工具如Selenium、PyAutoGUI主要工作在这一层。但在SAG架构中Grounding被赋予了更高的要求和更多的“智能”多模态元素定位不再仅仅依赖容易变化的ID或XPath。成熟的Grounding层会融合多种定位策略视觉特征匹配通过截图、图标特征、文字OCR来识别元素。这对于那些属性缺失或动态生成的控件如游戏界面、Canvas绘制的元素至关重要。语义理解结合OCR提取的文字理解其含义。例如智能体发出指令“点击那个写着‘确认’的红色方块”Grounding层需要综合颜色、形状、文字语义来找到目标。布局上下文利用元素之间的相对位置关系。比如“密码输入框下方的那个按钮”即使按钮属性全变只要布局没大改依然能定位。状态感知与等待智能的Grounding需要判断界面状态。例如执行点击后它会监测是否有新窗口弹出、进度条是否开始走动、页面元素是否刷新从而判断操作是否成功并为下一步操作提供正确的“上下文环境”。实操心得在构建Grounding层时切忌“一根筋”。我通常会设计一个定位策略链。优先使用稳定的唯一属性如后端控制的测试ID失败后降级到视觉匹配再失败则尝试基于布局的推断。同时为所有定位操作设置合理的超时和重试机制并记录详细的定位日志这对后期排查“幽灵问题”有奇效。2.2 Skill层封装可复用的自动化“肌肉记忆”Skill层即技能层。如果说Grounding提供了基本动作移动鼠标、敲击键盘那么Skill就是将一系列基本动作组合成有意义的、可复用的高阶操作。它是自动化的“肌肉记忆”。一个设计良好的Skill应该具备以下特点原子性与复用性一个Skill只完成一件明确的事情。例如LoginSkill输入账号密码并登录、ExtractTableSkill识别并提取表格数据、HandlePopupSkill识别并处理常见弹窗。这些Skill可以在不同的自动化任务中被反复调用。自包含的健壮性每个Skill内部要处理自己的异常和边界情况。比如UploadFileSkill需要包含文件是否存在检查、文件选择对话框的等待与操作、上传进度监控等完整逻辑。统一的接口通常以函数或方法的形式暴露接收明确的参数如用户名、文件路径并返回标准的执行结果成功、失败及原因。在我的项目中Skill层像是一个不断丰富的工具箱。初期可能只有十几个核心Skill随着自动化场景的扩展工具箱会越来越充实。维护一个清晰的Skill清单文档对团队协作至关重要。2.3 Agent层运筹帷幄的“决策大脑”Agent层智能体层这是整个架构的“大脑”。它不关心具体如何点击也不关心“登录”这个动作有多少步骤它只负责根据目标和当前状态决定接下来该调用哪个Skill。Agent的核心是任务规划与决策逻辑。实现一个Agent可以从简单到复杂规则驱动型Agent最简单的形式是预定义的工作流或状态机。例如“如果当前页面是登录页则调用LoginSkill登录后如果出现欢迎弹窗则调用ClosePopupSkill”。这种Agent实现简单但灵活性差无法处理未预见的流程分支。LLM驱动型Agent这是当前实现“智能”的主流方向。利用大语言模型如GPT、Claude等的自然语言理解能力将用户用自然语言描述的目标如“帮我将这份报表中的数据汇总后发邮件给经理”分解成一系列Skill调用序列。工作流程Agent将当前屏幕的上下文信息如截图、OCR文字、可操作元素列表和用户目标一起构造提示词Prompt提交给LLM。LLM分析后输出下一步应该执行的Skill名称及其参数。Agent执行该Skill后将新的状态再次喂给LLM形成循环直至任务完成或无法继续。优势极度灵活可以处理开放域、描述性的任务无需为每个复杂流程编写硬代码。挑战成本API调用费用、延迟、以及LLM可能产生的“幻觉”输出不存在的Skill或参数。需要通过精心设计的Prompt和严格的输出格式校验来约束。在实际架构中往往是混合模式对于稳定、核心的业务流程使用规则引擎确保效率和确定性对于探索性、变化多的任务则启用LLM Agent来提供灵活性。3. 从零开始搭建你的智能GUI自动化实战环境理论讲完了我们动手搭一个。这里我以Python生态为例展示如何构建一个轻量级但功能完整的SAG自动化项目。你会看到各个层次的技术选型是如何落地的。3.1 基础工具链选型与配置首先我们需要选定各层的技术组件。我的选择基于“成熟、开源、Python友好”的原则。Grounding层核心PyAutoGUI跨平台的GUI控制库模拟鼠标键盘操作。简单直接但缺乏高级定位能力。适合作为底层执行器。OpenCV PyTesseract计算机视觉和OCR黄金组合。OpenCV用于图像处理缩放、灰度化、模板匹配PyTesseract用于从图像中提取文字。这是实现视觉定位的基石。鼠标指针一个强大的Windows GUI自动化库能获取丰富的控件属性比图像识别更精确。如果你的主战场是Windows桌面应用它几乎是必选项。Skill层实现直接使用Python函数和类进行封装。重点在于良好的代码结构和错误处理。Agent层实现LLM路径OpenAI API 或 本地LLM如通过Ollama部署用于驱动决策。初期建议使用OpenAI GPT-3.5/4 API快速验证后期考虑成本可迁移到本地模型。LangChain框架虽然不是必须但LangChain提供了大量用于构建Agent的工具、链和记忆组件能极大简化开发流程。例如它的Tool概念与我们的Skill层天然契合。环境搭建步骤创建虚拟环境这是保持环境纯净的好习惯。python -m venv venv_sag # Windows激活 venv_sag\Scripts\activate # macOS/Linux激活 source venv_sag/bin/activate安装核心依赖pip install pyautogui opencv-python pillow pytesseract langchain openai # 如果使用鼠标指针 pip install pywinauto # 如果使用Ollama本地LLM # pip install ollama配置Tesseract OCR从 GitHub 上的 tesseract-ocr/tessdata 项目下载chi_sim.traineddata中文语言包。将其放入Tesseract-OCR的tessdata目录。在代码中指定路径import pytesseract pytesseract.pytesseract.tesseract_cmd rC:\Program Files\Tesseract-OCR\tesseract.exe # 你的安装路径3.2 Grounding层实战编写一个健壮的视觉定位器光说不练假把式我们来写一个融合了多种定位策略的VisualLocator类。import cv2 import numpy as np import pyautogui import pytesseract from PIL import ImageGrab import time from dataclasses import dataclass from typing import Optional, Tuple dataclass class LocatorResult: success: bool center_x: Optional[int] None center_y: Optional[int] None confidence: float 0.0 method: str error_msg: str class VisualLocator: def __init__(self, default_wait2.0, default_confidence0.8): self.default_wait default_wait self.default_confidence default_confidence self.screen_size pyautogui.size() def locate_by_template(self, template_path: str, regionNone, confidenceNone) - LocatorResult: 通过模板匹配定位元素 confidence confidence or self.default_confidence try: # 截取屏幕 screenshot np.array(ImageGrab.grab(bboxregion)) if region else np.array(ImageGrab.grab()) screenshot_gray cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY) # 读取模板 template cv2.imread(template_path, cv2.IMREAD_GRAYSCALE) if template is None: return LocatorResult(False, error_msgf模板图片无法读取: {template_path}) # 进行匹配 result cv2.matchTemplate(screenshot_gray, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc cv2.minMaxLoc(result) if max_val confidence: h, w template.shape center_x max_loc[0] w // 2 center_y max_loc[1] h // 2 if region: center_x region[0] center_y region[1] return LocatorResult(True, center_x, center_y, max_val, template_matching) else: return LocatorResult(False, confidencemax_val, methodtemplate_matching, error_msg置信度不足) except Exception as e: return LocatorResult(False, error_msgf模板匹配异常: {str(e)}) def locate_by_text(self, target_text: str, regionNone, langchi_simeng) - LocatorResult: 通过OCR文本定位元素 try: # 截图并OCR screenshot ImageGrab.grab(bboxregion) if region else ImageGrab.grab() data pytesseract.image_to_data(screenshot, output_typepytesseract.Output.DICT, langlang) # 遍历所有识别出的文本块寻找目标文本 for i in range(len(data[text])): if target_text.lower() in data[text][i].lower().strip(): x, y, w, h data[left][i], data[top][i], data[width][i], data[height][i] center_x x w // 2 center_y y h // 2 if region: center_x region[0] center_y region[1] return LocatorResult(True, center_x, center_y, 1.0, ocr_text) return LocatorResult(False, methodocr_text, error_msgf未找到文本: {target_text}) except Exception as e: return LocatorResult(False, error_msgfOCR定位异常: {str(e)}) def locate_with_retry(self, strategies: list, max_retries3, delay1.0) - LocatorResult: 组合定位策略带重试机制。 strategies: 一个包含(方法名, 参数dict)的列表按顺序尝试。 for attempt in range(max_retries): for strategy in strategies: method_name strategy[0] kwargs strategy[1] method getattr(self, method_name) result method(**kwargs) if result.success: print(f第{attempt1}次重试策略[{method_name}]定位成功置信度{result.confidence:.2f}) return result else: print(f第{attempt1}次重试策略[{method_name}]失败: {result.error_msg}) if attempt max_retries - 1: print(f定位失败等待{delay}秒后重试...) time.sleep(delay) return LocatorResult(False, error_msgf所有策略尝试{max_retries}次后均失败) # 使用示例 locator VisualLocator() # 定义一个组合策略先尝试用模板找“登录按钮”找不到再用OCR找“登录”文字 strategies [ (locate_by_template, {template_path: button_login.png, confidence: 0.9}), (locate_by_text, {target_text: 登录, region: (0, 0, 500, 200)}) # 在屏幕特定区域搜索 ] result locator.locate_with_retry(strategies, max_retries2) if result.success: pyautogui.click(result.center_x, result.center_y)这个VisualLocator类提供了两种定位方式和一个强大的组合重试机制。在实际项目中你还可以为其添加更多策略比如基于颜色的定位、利用pywinauto的控件树定位等。注意事项视觉定位对屏幕缩放比例、主题颜色非常敏感。最好在100%缩放且固定主题的环境下运行。模板图片建议从实际运行环境中截取并确保大小一致。OCR的准确率受字体、背景复杂度影响很大可能需要针对特定应用调整pytesseract的参数如--psm页面分割模式。3.3 Skill层封装以“登录”和“处理弹窗”为例有了强大的定位器我们就可以封装稳定的Skill了。Skill的设计关键在于输入明确、逻辑完整、异常处理周全。class LoginSkill: 登录技能负责在目标应用中完成登录操作 def __init__(self, locator: VisualLocator, username: str, password: str): self.locator locator self.username username self.password password self.timeout 10 # 每个步骤的超时时间 def execute(self) - dict: 执行登录返回结果字典 result {skill: Login, success: False, message: , steps: []} try: # 步骤1定位并点击用户名输入框 step1_result self.locator.locate_with_retry([ (locate_by_text, {target_text: 用户名, region: (0,0,800,600)}), (locate_by_template, {template_path: input_username.png}) ], max_retries2) if not step1_result.success: result[message] f无法定位用户名输入框: {step1_result.error_msg} return result pyautogui.click(step1_result.center_x, step1_result.center_y) time.sleep(0.5) pyautogui.write(self.username) # 输入用户名 result[steps].append(输入用户名成功) # 步骤2定位并点击密码输入框逻辑类似略 # ... # 步骤3定位并点击登录按钮 step3_result self.locator.locate_with_retry([ (locate_by_text, {target_text: 登录}), (locate_by_template, {template_path: btn_login.png}) ], max_retries3) # 登录按钮可以多试几次 if not step3_result.success: result[message] f无法定位登录按钮: {step3_result.error_msg} return result pyautogui.click(step3_result.center_x, step3_result.center_y) result[steps].append(点击登录按钮成功) # 步骤4验证登录是否成功例如寻找登录后的特定元素 time.sleep(2) # 等待页面跳转 verification_result self.locator.locate_by_text(欢迎, confidence0.7) if verification_result.success: result[success] True result[message] 登录流程执行完毕并验证成功 else: result[message] 登录操作已完成但未检测到成功标志 return result except Exception as e: result[message] f登录技能执行过程中发生未预期异常: {str(e)} return result class HandleCommonPopupSkill: 处理常见弹窗技能识别并关闭广告、提示、确认框等 def __init__(self, locator: VisualLocator): self.locator locator # 预定义需要关闭的弹窗特征文本或图片 self.popup_patterns [ {type: text, content: 跳过广告, action: click}, {type: text, content: 知道了, action: click}, {type: template, content: close_button.png, action: click}, {type: text, content: 确认, action: click}, ] def execute(self) - dict: 扫描屏幕处理预定义的弹窗 result {skill: HandleCommonPopup, handled: False, details: []} for pattern in self.popup_patterns: loc_result None if pattern[type] text: loc_result self.locator.locate_by_text(pattern[content], confidence0.8) elif pattern[type] template: loc_result self.locator.locate_by_template(pattern[content], confidence0.85) if loc_result and loc_result.success: if pattern[action] click: pyautogui.click(loc_result.center_x, locator_result.center_y) result[handled] True result[details].append(f已点击{pattern[content]}) time.sleep(0.5) # 等待弹窗关闭 break # 处理一个弹窗后即可返回 return resultSkill的设计体现了“高内聚”的思想。LoginSkill封装了从定位到验证的完整登录逻辑而HandleCommonPopupSkill则是一个通用的“清道夫”。在实际系统中你会有一个Skill注册中心Agent从这里查找和调用它们。3.4 Agent层实现构建一个简单的LLM驱动智能体最后我们来看看最“智能”的部分——Agent。这里我们用LangChain来快速搭建一个基于LLM的Agent。假设我们已经有了上面两个Skill。from langchain.agents import Tool, AgentExecutor, create_react_agent from langchain_core.prompts import PromptTemplate from langchain_openai import ChatOpenAI import json # 1. 将Skill包装成LangChain的Tool # 假设我们已经有了skill实例 login_skill LoginSkill(locator, my_username, my_password) popup_skill HandleCommonPopupSkill(locator) def login_tool(query: str) - str: 当用户想要登录系统时使用此工具。输入应为空字符串。 result login_skill.execute() return json.dumps(result, ensure_asciiFalse) def handle_popup_tool(query: str) - str: 当需要关闭可能出现的弹窗、广告或提示框时使用此工具。输入应为空字符串。 result popup_skill.execute() return json.dumps(result, ensure_asciiFalse) # 定义Tools列表 tools [ Tool( nameUserLogin, funclogin_tool, description用于在目标软件或网页中执行登录操作。当任务要求登录、输入账号密码时使用。 ), Tool( nameClosePopup, funchandle_popup_tool, description用于识别并关闭屏幕上常见的弹窗、广告、通知或确认对话框。当界面被遮挡或出现意外提示时使用。 ), # 可以继续添加更多Tool如 ExtractData, SaveFile, SendEmail 等 ] # 2. 初始化LLM # 注意这里需要设置你的OpenAI API Key import os os.environ[OPENAI_API_KEY] your-api-key-here llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # temperature0使输出更确定 # 3. 创建Prompt模板指导Agent的行为 prompt PromptTemplate.from_template( 你是一个GUI自动化助手负责通过调用工具来完成用户的任务。 你可以使用的工具有 {tools} 请严格按照以下格式回应 思考你需要首先思考当前情况以及下一步该做什么 行动需要调用的工具名称必须是[{tool_names}]中的一个 行动输入调用该工具所需的输入是一个字符串 开始记住你必须通过“行动”和“行动输入”来调用工具我会把工具执行的结果返回给你。 之前的对话历史 {history} 用户当前的任务或指令是{input} 当前屏幕状态描述{screen_context} 你的回应 ) # 4. 创建Agent并执行 agent create_react_agent(llm, tools, prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue, handle_parsing_errorsTrue) # 模拟一个任务场景 screen_context 当前屏幕显示的是应用启动界面中央有一个登录窗口包含用户名和密码输入框。 user_input 请帮我登录这个系统如果有弹窗就关掉它。 # 执行任务 try: final_result agent_executor.invoke({ input: user_input, screen_context: screen_context, history: }) print(任务执行结果, final_result[output]) except Exception as e: print(fAgent执行出错{e})这个Agent的工作流程是接收用户指令和屏幕状态 - LLM思考决定下一步行动调用哪个Tool- 执行对应的Skill - 将Skill执行结果作为新的“历史”和“状态”反馈给LLM - LLM决定下一步直到任务完成或无法继续。实操心得LLM Agent的Prompt工程是关键。你需要清晰地定义每个Tool的职责并在Prompt中强调输出格式。screen_context的生成本身也是一个技术点可以通过简化的OCR摘要如“屏幕上有‘登录’、‘用户名’等文字”或图像描述模型来获得。初期为了降低复杂度可以由人工或简单的规则来生成screen_context。4. 项目部署与工程化实践让智能自动化脚本在开发环境跑起来只是第一步要真正用于生产必须考虑部署和工程化。这里分享几个关键实践。4.1 环境隔离与依赖管理GUI自动化脚本对环境极其敏感。必须确保从开发到测试再到生产环境的一致性。容器化部署Docker这是最理想的方案。将你的自动化脚本、Python环境、浏览器驱动、甚至整个带有所需应用的虚拟机镜像一起打包进Docker容器。这保证了绝对的运行环境一致性。不过在Docker容器内运行GUI应用需要配置显示服务器如Xvfb这是一个常见的坑点。# 一个简化的Dockerfile示例 FROM python:3.9-slim RUN apt-get update apt-get install -y \ tesseract-ocr \ tesseract-ocr-chi-sim \ libgl1-mesa-glx \ xvfb \ rm -rf /var/lib/apt/lists/* COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . /app WORKDIR /app # 使用xvfb-run来虚拟显示 CMD [xvfb-run, --auto-servernum, --server-args-screen 0 1024x768x24, python, main_agent.py]依赖清单锁定使用pip freeze requirements.txt严格锁定所有第三方库的版本避免因库版本升级导致的兼容性问题。4.2 配置外部化与安全管理脚本中的账号密码、API密钥、文件路径等都不应硬编码。使用配置文件采用YAML或JSON文件管理配置。区分开发、测试、生产环境。# config.yaml environments: production: application: login_url: https://prod-app.com username: prod_user username_input_region: [100, 200, 300, 250] # 屏幕坐标 llm: api_key: ${OPENAI_API_KEY} # 从环境变量读取 model: gpt-4 logging: level: INFO file_path: /var/log/auto_bot.log密钥管理敏感信息如API Key通过环境变量或专业的密钥管理服务如HashiCorp Vault、AWS Secrets Manager注入绝对不要提交到代码仓库。4.3 调度、监控与日志自动化任务需要被可靠地触发和监控。任务调度对于定时任务使用Apache Airflow或Celery这类成熟的调度系统。它们支持任务依赖、重试、报警等功能。简单的场景也可以用操作系统的cron或systemd timer。全面日志记录日志是你的“黑匣子”。不仅要记录信息、错误更要记录关键决策点。import logging import sys def setup_logger(): logger logging.getLogger(SAG_AutoBot) logger.setLevel(logging.DEBUG) # 控制台处理器 ch logging.StreamHandler(sys.stdout) ch.setLevel(logging.INFO) # 文件处理器 fh logging.FileHandler(automation.log) fh.setLevel(logging.DEBUG) # 格式 formatter logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s) ch.setFormatter(formatter) fh.setFormatter(formatter) logger.addHandler(ch) logger.addHandler(fh) return logger logger setup_logger() # 在Skill和Agent中关键位置记录 logger.info(f开始执行LoginSkill用户名: {self.username}) logger.debug(f视觉定位结果: {result}) if not result.success: logger.warning(f定位失败将尝试备用策略。错误: {result.error_msg})运行状态监控与报警脚本运行结束后可以通过邮件、钉钉、企业微信机器人发送执行报告。对于长时间运行的任务可以增加“心跳”机制定期上报状态到监控平台如Prometheus一旦失联则触发报警。4.4 版本控制与协作将你的SAG自动化项目当作一个正规的软件项目来管理。Git仓库使用Git进行版本控制。README.md中详细说明项目结构、环境搭建、配置方法和Skill清单。模块化设计将Grounding、Skill、Agent分层放在不同的Python包中方便维护和复用。Skill注册表维护一个中央的Skill注册表可以是一个Python字典或配置文件列出所有可用的Skill及其描述、参数。这方便Agent动态发现和调用也方便团队协作避免重复造轮子。5. 避坑指南与效能优化在实际项目中踩过无数坑后我总结出以下必须注意的事项和优化技巧。5.1 稳定性提升应对GUI自动化中的“玄学”问题异步加载与动态内容现代Web应用和桌面软件大量使用异步加载。你的脚本点击后内容可能过一会儿才出现。对策不要用固定的time.sleep要用显式等待。在Grounding层实现一个wait_until函数循环检测目标元素是否出现并设置超时。def wait_until(locator_func, timeout10, interval0.5, **kwargs): start time.time() while time.time() - start timeout: result locator_func(**kwargs) if result.success: return result time.sleep(interval) return LocatorResult(False, error_msgf等待超时 {timeout} 秒)屏幕分辨率与缩放这是视觉定位的头号杀手。在不同机器上UI元素的大小和位置可能因缩放设置如Windows的125%而完全不同。对策一强制要求运行环境为100%缩放。在脚本开始时可以尝试检测并警告。对策二使用相对坐标或基于控件的定位如pywinauto代替绝对坐标和固定尺寸的模板匹配。对策三模板匹配时将模板和截图都缩放到同一基准分辨率后再进行匹配。多窗口与焦点切换脚本可能意外点击到其他窗口导致后续操作失效。对策在关键操作前使用pyautogui或pywinauto的API将目标窗口前置并激活。记录目标窗口的句柄确保所有操作都在其上进行。5.2 性能优化让自动化跑得更快截图与OCR优化全屏截图和OCR非常耗时。对策尽量缩小截图范围。根据经验或布局只截取可能包含目标元素的区域。对于固定区域的文字可以缓存OCR结果避免重复识别。LLM调用优化如果使用云API延迟和成本是主要问题。对策一缓存LLM响应。对于常见的、确定的子任务如“登录”其决策路径是固定的没必要每次都问LLM。可以构建一个简单的规则缓存命中则直接执行。对策二使用更小的本地模型。对于规划逻辑不极端复杂的场景7B或13B参数的本地模型通过Ollama、LM Studio部署在速度和成本上远优于GPT-4且数据不出本地。对策三精心设计Prompt让LLM一次性输出多步计划而不是每一步都交互减少调用次数。5.3 维护性设计让项目长期健康运行元素识别信息集中管理不要将图片路径、OCR文本等硬编码在Skill里。创建一个UI_Elements.yaml文件集中管理。ui_elements: login_page: username_input: type: template primary: images/login/username_input.png fallback: - type: text value: 用户名 region: [100, 200, 400, 300] # 搜索区域 login_button: type: text primary: 登录 fallback: - type: template value: images/login/btn_login.png这样当UI变化时你只需要更新这个配置文件而不需要修改所有Skill的代码。Skill的版本化与测试为每个Skill编写单元测试模拟不同的屏幕状态验证其定位和操作逻辑。当应用升级后跑一遍Skill测试集能快速定位哪些Skill失效了。引入“录制-回放”作为辅助对于快速生成某些固定流程的初始脚本可以使用录制工具如pyautogui的录制功能、或专门的RPA录制器。但切记录制的脚本极其脆弱必须将其作为草稿然后人工重构为基于SAG架构的、使用健壮定位的Skill。从SAG架构的理论剖析到Grounding、Skill、Agent每一层的代码实战再到最终的部署、监控和避坑经验这套方法论的核心思想是分层解耦和智能增强。它不是为了替代传统的自动化而是为其注入灵活性和适应性。最让我有体会的是不要追求一步到位实现一个全能的LLM Agent。从解决一个具体的、高价值的自动化痛点开始用SAG的思想去设计哪怕最初Agent层只是一个简单的状态机你也能立刻获得比传统脚本更稳定的收益。随着Skill库的丰富和LLM集成经验的积累整个系统会变得越来越“聪明”。