GenericAgent Vision API SOP 实战指南:截图驱动 GUI 自动化中视觉模型的最小化调用规范
【免费下载链接】GenericAgentSelf-evolving agent: grows skill tree from 3.3K-line seed, achieving full system control with 6x less token consumption项目地址: https://gitcode.com/GitHub_Trending/pc/GenericAgent
导读
memory/vision_sop.md是 GenericAgent 项目中视觉模型(VLM)调用的标准操作规程(SOP),它定义了三个核心问题:什么时候才能用 vision API、怎么安全高效地调用ask_vision、以及没有现成vision_api.py时如何从模板初始化视觉能力。本指南以该 SOP 为骨架,结合仓库内memory/vision_api.template.py、memory/ljqCtrl.py、memory/ocr_utils.py与memory/computer_use.md的源码级细节,帮助你掌握一条"窗口枚举 → 局部截图 → 本地 OCR → 最后才上 vision"的省 token、高可靠的 GUI 自动化链路,并能在任何环境(Claude / OpenAI 兼容 / ModelScope)下快速接通视觉能力。
一、三条前置规则:Vision 是"最后手段"而非首选
vision_sop.md开篇即强调三条"必须遵守"的硬性规则,它们决定了整个 GUI 自动化的资源使用策略:
- 先枚举窗口:调用 vision 前必须先用
pygetwindow枚举窗口标题,确认目标窗口存在且已激活到前台。窗口不存在就不截图——这避免了对不存在或后台窗口做无意义的视觉分析。 - 禁止全屏截图:必须先利用
ljqCtrl截取窗口区域。能截局部(如标题栏)就不截整窗口,能截窗口就绝不全屏。全屏截图在任何场景下都不允许。这既是出于 token 成本控制,也是因为全屏图包含大量无关像素,会稀释 VLM 对目标区域的注意力。 - 能不用 vision 就不用:如果窗口标题或本地 OCR(
ocr_utils.py)能获取所需信息,就不要调用 vision API,省 token 且更可靠。Vision 是最后手段。
这条规则链并非孤立设计,它与仓库中memory/computer_use.md定义的"探测/定位四工具优先级"完全一致:
| 优先级 | 工具 | 定位 | 限制 |
|---|---|---|---|
| 0 | win32gui窗口枚举 | 始终先行,确定目标窗口、前台状态、客户区原点 | 仅定位窗口,不进控件 |
| 1 | Python UIA(控件树) | 首选探测与免坐标点击 | 游戏禁用;一旦对该窗口无效则弃用 |
| 2 | ui_detect.py+ljqCtrl截图 | 视觉检测控件,返回 bbox + OCR 文本 | bbox 为截图内坐标,需转屏幕物理坐标 |
| 3 | vision (VLM) | 仅语义理解、确认界面状态、辅助判断目标 | 不可信其坐标 |
从源码结构看,vision 处于优先级链条的最末端,其定位是"语义理解与状态确认",而不是坐标定位工具——这正是 SOP 要求"能不用就不用"的根本原因。
二、快速用法:ask_vision单函数入口
当确需调用视觉能力时,SOP 给出了最小可用调用方式:
from vision_api import ask_vision result = ask_vision(image, prompt="描述图片内容", timeout=60, max_pixels=1_440_000) # image: 文件路径(str/Path) 或 PIL Image # backend: 'claude'(默认) | 'openai' | 'modelscope' # 返回 str:成功为模型回复,失败为 'Error: ...'对照memory/vision_api.template.py中ask_vision的完整签名(第 25 行),可以对每个参数做更精确的说明:
image_input:接受文件路径(str/Path)或 PILImage对象。模板实现里会先做类型检查,其他类型直接抛出TypeError并返回Error: 图片处理失败 - ...。prompt:默认值为"详细描述这张图片的内容"。建议按任务写具体指令,因为该 prompt 会原样拼入多模态消息。timeout:请求超时秒数,默认 60。模板对requests.exceptions.Timeout单独捕获,返回Error: 请求超时 (>{timeout}s)。max_pixels:图片像素上限,默认1_440_000(约 1200×1200)。超过该值时模板会自动等比缩放,这是控制 token 消耗的关键参数。backend:后端选择,'claude'(默认)、'openai'、'modelscope'三选一,分别路由到_call_claude/_call_openai_compat/_call_openai_compat(modelscope 配置)。
返回值统一为str:成功返回模型回复文本;任何失败(图片处理异常、超时、网络错误、配置缺失、响应解析失败)都以"Error: ..."前缀返回,方便调用方在 GUI 自动化脚本中直接判断成败,而无需 try/except 包裹。
三、没有vision_api.py?从模板初始化视觉能力
SOP 给出了一套"自举"式的初始化流程,适用于任何全新环境(例如新部署的机器或新克隆的仓库):
- 复制模板:
memory/vision_api.template.py→memory/vision_api.py。 - 只改头部"用户配置区":去
mykey.py里扫描变量名(⚠️ 只看名字,禁止输出 apikey 值),尝试找能用配置名填入CLAUDE_CONFIG_KEY/OPENAI_CONFIG_KEY,DEFAULT_BACKEND选后端,并测试。 - 保底方案:没有可用 config 时,去 ModelScope 申请 token 填入
MODELSCOPE_API_KEY。
3.1 配置区字段逐一说明
模板第 5~16 行的"用户配置区"是唯一需要修改的地方:
CLAUDE_CONFIG_KEY = 'claude_config141' # mykey.py 中 Claude 配置的变量名 OPENAI_CONFIG_KEY = 'oai_config1' # mykey.py 中 OpenAI 配置的变量名 MODELSCOPE_API_KEY = '' # 直接填你的 ModelScope token DEFAULT_BACKEND = 'claude' # 默认后端: 'claude' / 'openai' / 'modelscope'模板注释中明确提示:mykey.py中的配置变量名不固定(可能形如xxx_config = {"apibase": ..., "apikey": ..., "model": ..., "proxy": None}),因此 SOP 强调"只打印变量名/字段名/model/apibase 域名路径/HTTP 状态码/错误类型,禁止打印完整 dict 和 apikey/token"——这是安全红线。
仓库中的mykey_template.py给出了典型配置的结构佐证:apikey必填且前缀决定鉴权方式(如sk-ant-*走x-api-key头,其它sk-*、cr_*、amp_*走 Bearer),apibase必填且遵循自动拼接规则,model必填([1m]后缀触发 1m 上下文 beta)。这意味着vision_api.py中读取的cfg['apibase'] / cfg['apikey'] / cfg['model'] / cfg.get('proxy')与项目主 LLM 配置体系是同一套凭据源,无需为视觉能力单独申请密钥。
3.2 ModelScope 保底通道
当mykey.py中没有任何可用配置时,模板提供了开箱即用的保底后端(第 18~19 行):
MODELSCOPE_API_BASE = 'https://api-inference.modelscope.cn' MODELSCOPE_MODEL = 'Qwen/Qwen3-VL-235B-A22B-Instruct'只需在MODELSCOPE_API_KEY填入从 ModelScope 申请的 token,并把DEFAULT_BACKEND设为'modelscope'即可工作,模型为 Qwen3-VL 系列视觉大模型。
四、源码级原理:一次ask_vision调用内部发生了什么
4.1 图片预处理管线(_prepare_image)
所有后端共用同一条预处理管线(memory/vision_api.template.py第 55~78 行),这也是max_pixels参数的实际作用点:
- 加载:PIL
Image对象直接使用;str/Path用Image.open打开;其他类型抛TypeError。 - 等比缩放:若
w * h > max_pixels,按scale = (max_pixels / (w * h)) ** 0.5计算缩放比,用LANCZOS重采样,并打印📐 缩放: 原尺寸 → 新尺寸日志。这是将任意分辨率截图收敛到 token 可控范围的关键。 - 通道归一化:
RGBA/LA/P模式统一转 RGB,以白底合成透明通道,避免后续 JPEG 编码报错或出现黑底。 - JPEG 编码:
quality=80, optimize=True压缩后 base64 编码,打印📦 Base64: xx.xKB便于估算请求体积。
4.2 三个后端的请求差异
Claude 后端(_call_claude):POST 到cfg['apibase'] + '/v1/messages',请求体使用 Anthropic Messages 格式,图片以{'type': 'image', 'source': {'type': 'base64', 'media_type': 'image/jpeg', 'data': b64}}放在content数组首位、prompt 文本在后;鉴权头为x-api-key+anthropic-version: 2023-06-01,返回解析resp.json()['content'][0]['text']。模板注释特别提示:不同的中转 apibase 可能已含/v1或路径不同,需按实际状态码和响应结构修正 endpoint。
OpenAI 兼容后端(_call_openai_compat):POST 到apibase.rstrip('/') + '/v1/chat/completions',请求体为标准 Chat Completions 多模态格式——content数组里文本在前、图片 URL 使用data:image/jpeg;base64,前缀的 data URI;鉴权头为Authorization: Bearer <apikey>。proxy参数会透传给requests的proxies({'https': proxy, 'http': proxy}),用于中转代理场景。ModelScope 后端复用同一函数,只是换成了MODELSCOPE_API_BASE / MODELSCOPE_API_KEY / MODELSCOPE_MODEL三个常量。
4.3 统一的错误返回协议
模板对四类异常做了归一化处理,调用方只需判断返回值是否以"Error:"开头:
- 图片处理失败:
Error: 图片处理失败 - <异常类型>: <详情> - 超时:
Error: 请求超时 (>{timeout}s) - 网络错误:
Error: API请求失败 - <异常类型>: <详情> - 配置缺失 / 响应解析失败(
KeyError/ValueError):Error: 响应解析失败 - <详情>
另有兜底分支:未知 backend 返回Error: 未知backend '<name>',可选: claude, openai, modelscope。
五、与截图链路协同:vision 之前应该做什么
SOP 的"禁止全屏截图"规则与ljqCtrl的窗口截图能力强绑定。参考memory/ljqCtrl_sop.md,vision 的输入图片应来自以下链路:
- 激活并定位窗口:
ljqCtrl.Activate(hwnd)(memory/ljqCtrl.py第 85 行)先把目标窗口置于前台,这是截图的先决条件。 - 窗口级截图:
ljqCtrl.GrabWindow(hwnd_or_name)(第 102 行)前台截图返回 PIL Image,直接可作为ask_vision的输入;GrabWindowBg(hwnd_or_name, timeout=5)(第 112 行)则是 Win10+ 的 WGC 后台截图。 - 必要时先试本地 OCR:
memory/ocr_utils.py基于rapidocr-onnxruntime(约 1 秒/次,中英文准确率高、带 bbox),提供ocr_image/ocr_screen/ocr_window三个入口。其中ocr_window(hwnd)使用PrintWindowAPI,支持远程桌面(RDP)断开后ImageGrab截图全黑的情况。只要 OCR 能给出文本信息(如按钮文字、弹窗内容),就完全没有必要调用 vision。
从computer_use.md的节奏建议看,完整流程是:进入新界面先只探测不操作(枚举窗口 + UIA + ljqCtrl 截图 + ui_detect),读完实际输出再决定下一步;仅当ui_detect的视觉检测与 OCR 都不足以判断界面语义时,才把截图交给ask_vision做"语义理解、确认界面状态、辅助判断目标"。此外要牢记:vision 返回的文本不可用于坐标定位——如果确实需要点击,必须回到ui_detectbbox +ClientToScreen(hwnd, (0,0)) / ljqCtrl.dpi_scale的物理坐标转换链路(详见 ljqCtrl 使用与坐标转换 SOP)。
六、macOS 平台的对应适配
SOP 针对 Windows 主链路给出,但仓库为 macOS 提供了完整的镜像实现(memory/maclijqCtrl.py),vision 的调用方式完全不变,只是截图来源替换为:
- 控制层:
import macljqCtrl as ljqCtrl(API 镜像,Quartz/screencapture 实现); - 窗口枚举:
ListWindows()返回 id/app/title/bbox/pid; - 区域截图:
GrabWindow(window_id)或ScreenCapAt(x, y, radius)(物理坐标); - 权限:首次使用需授予"辅助功能"权限,可用
AXIsProcessTrusted()检测。
在这些平台上,ask_vision(image, ...)依然接收 PIL Image 或文件路径,ljqCtrl.GrabWindow的返回可直接传入,无需改动 vision 侧代码——这正是"vision 只关心拿到什么图,不关心图从哪来"的设计价值。
七、实践清单与常见误区
标准调用流程(推荐顺序):
# 1. 枚举窗口,确认目标存在且在前台 # (pygetwindow 枚举标题 → ljqCtrl.Activate(hwnd)) # 2. 窗口级截图,绝不全屏 # img = ljqCtrl.GrabWindow(hwnd) # 或 ljqCtrl.GrabWindowBg(hwnd) # 3. 优先本地 OCR,能拿到文本就不用 vision # from ocr_utils import ocr_image # info = ocr_image(img) # if not info['text']: # # 4. 兜底才调 vision # result = ask_vision(img, prompt="描述图片内容", timeout=60, max_pixels=1_440_000) # if result.startswith('Error:'): # # 按 Error 前缀分流:超时重试、配置修正、换 backend常见误区:
- ❌ 直接把全屏
ImageGrab.grab()结果传给ask_vision——违反"禁止全屏截图"规则; - ❌ 对不存在的窗口/未激活窗口截图再送 vision——应先枚举并激活;
- ❌ 把 vision 返回的内容当作坐标依据点击——vision 只负责语义理解,点击坐标必须走
ui_detect+ 物理坐标转换; - ❌ 在日志中打印完整 config dict 或 apikey——SOP 与模板均明确禁止,只允许打印变量名/字段名/model/域名/状态码;
- ❌ 忽略
max_pixels缩放——大图不仅费 token,还可能因超出模型输入上限而报错。
八、总结
vision_sop.md的本质是一份"成本与可靠性优先"的视觉调用规范:通过"先枚举窗口、只截窗口、能本地 OCR 就不上 VLM"三条规则,把 vision API 的使用压缩到最少必要次数;再通过ask_vision的统一入口和三个后端(Claude / OpenAI 兼容 / ModelScope 保底)的透明切换,保证任何环境下都能以最低成本获得视觉理解能力。配合memory/vision_api.template.py的自举初始化流程、memory/ljqCtrl_sop.md的截图链路和memory/ocr_utils.py的本地 OCR,这套 SOP 构成了 GenericAgent 桌面自动化体系中"最后一公里"的语义确认环节——准确、省 token、且可审计。
延伸阅读:GUI 操作的整体节奏与工具优先级见 computer_use.md;窗口截图与 DPI 物理坐标换算见 ljqCtrl_sop.md;视觉能力模板实现见 vision_api.template.py;本地 OCR 能力见 ocr_utils.py。
【免费下载链接】GenericAgentSelf-evolving agent: grows skill tree from 3.3K-line seed, achieving full system control with 6x less token consumption项目地址: https://gitcode.com/GitHub_Trending/pc/GenericAgent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考