番摊机器人 一句话让 Agent 截取真机屏幕

要实现“一句话让 Agent 截取真机屏幕”,核心在于将复杂的底层驱动(如 Appium、ADB、WDA)

封装为标准的 ‌Tool Schema‌,并配合现代化的设备自动化中间件(如 agent-device)。


以下是基于最新工程实践的最佳实现方案,分为‌架构选型‌、‌Tool 定义规范‌和‌代码实现‌三个部分。


一、 核心架构:为什么传统方式不行?


传统的 GUI 自动化(如纯 Appium 脚本)要求 Agent 理解坐标、XPath 和等待逻辑,这超出了 LLM 的稳定推理能力。

新范式‌采用 ‌Agent-Device 桥接模式‌:


Agent 侧‌:只负责发出高层意图(如“截图”、“点击登录”)。

中间件层‌(如 agent-device 或自定义 Sandbox):充当“眼睛”和“手”,负责维护 WebSocket/HTTP 连接,处理屏幕流传输。

Tool 层‌:提供极简的 API,仅暴露 screenshot()、click() 等原子操作,隐藏所有设备连接细节。

二、 Tool Schema 设计规范


一个高质量的截图 Tool 必须包含以下四个要素,以确保 Agent 能正确调用并处理异常:


表格

要素 说明 示例值

Name‌ 工具唯一标识 mobile_screenshot

Description‌ ‌关键‌:明确何时用、返回什么格式、注意事项 "Captures the current screen of the connected mobile device. Returns a base64 encoded PNG string. Use this when you need to verify UI state or analyze visual elements."

Parameters‌ 参数定义(尽量简化) {"quality": {"type": "integer", "description": "Image quality 1-100, default 80"}}

Returns‌ 返回值结构 {"status": "success", "image_data": "base64_string", "timestamp": "..."}

三、 实战代码实现

方案 A:使用现代化开源库 agent-device(推荐)


这是目前社区最推崇的方案,它专为 AI Agent 设计,屏蔽了 Appium 的复杂性,支持 iOS/Android/TV。


1. 安装依赖‌


bash

npm install -g agent-device@latest

# 或者在 Python 环境中通过 subprocess 调用,或使用其提供的 SDK



2. Python 中封装为 LangChain/LlamaIndex Tool‌

假设你已经启动了 agent-device 服务或通过其 SDK 连接了真机。


python

import base64

from langchain.tools import BaseTool

from pydantic import BaseModel, Field

# 假设使用 agent-device 的 Python 绑定或 HTTP 接口

import requests 


class ScreenshotInput(BaseModel):

    quality: int = Field(default=80, description="Image quality from 1 to 100")

    device_id: str = Field(default=None, description="Optional specific device ID if multiple connected")


class MobileScreenshotTool(BaseTool):

    name: str = "mobile_screenshot"

    description: str = (

        "Captures the current screen of the connected mobile device (iOS/Android). "

        "Returns a base64 encoded PNG image. "

        "Use this tool to verify the current UI state, read text from images, or check if an action succeeded."

    )

    args_schema: type[BaseModel] = ScreenshotInput


    def _run(self, quality: int = 80, device_id: str = None) -> str:

        try:

            # 调用 agent-device 或底层服务的截图接口

            # 这里以假设的本地服务接口为例,实际需根据部署调整

            url = "http://localhost:8080/screenshot"

            params = {"quality": quality}

            if device_id:

                params["deviceId"] = device_id

            

            response = requests.get(url, params=params)

            response.raise_for_status()

            

            # 假设返回的是二进制图片数据

            image_bytes = response.content

            base64_image = base64.b64encode(image_bytes).decode('utf-8')

            

            return f"data:image/png;base64,{base64_image}"

        

        except Exception as e:

            return f"Error capturing screenshot: {str(e)}"


    async def _arun(self, quality: int = 80, device_id: str = None) -> str:

        return self._run(quality, device_id)


方案 B:基于 Appium + 沙箱(传统稳健方案)


如果你必须使用 Appium(例如在企业内网环境),则需要通过 Sandbox 隔离环境,避免驱动冲突。


python

import os

import base64

from appium import webdriver

from appium.options.android import UiAutomator2Options

from langchain.tools import BaseTool

from pydantic import BaseModel, Field


class AppiumScreenshotTool(BaseTool):

    name: str = "appium_screenshot"

    description: str = "Takes a screenshot of the currently active Android/iOS device via Appium. Returns base64 string."

    args_schema: type[BaseModel] = BaseModel # 无参数或简单参数


    def _run(self) -> str:

        # 1. 初始化 Driver (实际生产中应复用 Driver 实例,而非每次创建)

        # 注意:driver 实例应在工具外部初始化并传入,或通过单例管理

        driver = self._get_driver() 

        

        try:

            # 2. 执行截图

            screenshot_base64 = driver.get_screenshot_as_base64()

            return f"data:image/png;base64,{screenshot_base64}"

        except Exception as e:

            return f"Failed to take screenshot: {str(e)}"

        finally:

            # 不要在这里 quit driver,除非是单次任务

            pass


    def _get_driver(self):

        # 模拟获取已连接的 driver

        # 实际项目中建议通过依赖注入传入 driver 对象

        options = UiAutomator2Options()

        options.platform_name = "Android"

        # 连接到本地 Appium Server

        driver = webdriver.Remote("http://localhost:4723", options=options)

        return driver


四、 如何让 Agent “一句话”调用?


在 System Prompt 中明确告知 Agent 该工具的存在和能力:


System Prompt 示例:‌

"You have access to a tool named mobile_screenshot. When the user asks to 'see what's on the screen', 'check the current page', or 'take a snapshot', you MUST call this tool. The tool returns a base64 image which you can then analyze using your vision capabilities."


用户指令示例:‌


“帮我看看现在手机屏幕上显示的是什么应用?”


Agent 执行流程:‌


思考‌:用户想知道屏幕内容 -> 需要视觉信息 -> 调用 mobile_screenshot。

行动‌:mobile_screenshot(quality=80)

观察‌:收到 Base64 图片字符串。

最终回答‌:结合视觉模型分析图片,回答“当前屏幕显示的是微信主界面...”

五、 关键避坑指南

Base64 长度限制‌:真机截图(尤其是高分屏)的 Base64 字符串非常长,可能超过 LLM 的 Context Window 或 API 限制。

优化‌:在 Tool 内部先进行图片压缩(Resize 到 720p 或更低),或转换为 JPEG 格式再编码。

状态同步‌:截图是瞬时状态。如果 Agent 需要基于截图做点击操作,必须确保截图后设备状态未发生改变。建议在 Tool 返回中附带时间戳。

隐私与安全‌:真机截图可能包含敏感信息。在生产环境中,建议在 Tool 层增加脱敏处理或权限确认机制。

错误反馈‌:如果截图失败(如设备断开),Tool 必须返回清晰的错误信息(如 "Device disconnected"),而不是静默失败,这样 Agent 才能尝试重连或报错。


通过这种封装,开发者无需关心底层的 ADB 命令或 Appium 配置,只需关注业务逻辑,真正实现了“一句话截真机屏”。