封装应用程序的原生 API

UFO 基于 UI 控件对应用程序执行操作,但为其工具箱提供原生 API 可以提高操作的效率和准确性。本文档提供了如何将应用程序的原生 API 封装到 UFO 工具箱中的指导。

如何封装应用程序的原生 API?

在开发原生 API 封装器之前,我们强烈建议您阅读 Automator 的设计。

步骤 1:为原生 API 创建一个接收器

Receiver 是一个接收 AppAgent 发来的原生 API 调用并执行它们的类。要封装应用程序的原生 API,您需要创建一个 Receiver 类,其中包含执行原生 API 调用的方法。

要创建 Receiver 类,请遵循以下步骤:

1. 为您的应用程序创建一个文件夹

  • 导航到 ufo/automator/app_api/ 目录。
  • 创建一个以您的应用程序命名的文件夹。

2. 创建一个 Python 文件

  • 在您刚创建的文件夹中,添加一个以您的应用程序命名的 Python 文件,例如 {your_application}_client.py

3. 定义接收器类

  • 在 Python 文件中,定义一个名为 {Your_Receiver} 的类,继承自 ufo/automator/basic.py 中的 ReceiverBasic 类。
  • 使用执行原生 API 调用的对象初始化 Your_Receiver 类。例如,如果您的 API 基于 com 对象,请在 Your_Receiver 类的 __init__ 方法中初始化 com 对象。

WinCOMReceiverBasic 类的示例

class WinCOMReceiverBasic(ReceiverBasic):
    """
    The base class for Windows COM client.
    """

    _command_registry: Dict[str, Type[CommandBasic]] = {}

    def __init__(self, app_root_name: str, process_name: str, clsid: str) -> None:
        """
        Initialize the Windows COM client.
        :param app_root_name: The app root name.
        :param process_name: The process name.
        :param clsid: The CLSID of the COM object.
        """

        self.app_root_name = app_root_name
        self.process_name = process_name
        self.clsid = clsid
        self.client = win32com.client.Dispatch(self.clsid)
        self.com_object = self.get_object_from_process_name()

4. 定义执行原生 API 调用的方法

  • Your_Receiver 类中定义方法以执行原生 API 调用。

ExcelWinCOMReceiver 类的示例

def table2markdown(self, sheet_name: str) -> str:
    """
    Convert the table in the sheet to a markdown table string.
    :param sheet_name: The sheet name.
    :return: The markdown table string.
    """

    sheet = self.com_object.Sheets(sheet_name)
    data = sheet.UsedRange()
    df = pd.DataFrame(data[1:], columns=data[0])
    df = df.dropna(axis=0, how="all")
    df = df.applymap(self.format_value)

    return df.to_markdown(index=False)

5. 创建一个工厂类

  • 创建您的 Factory 类,继承自 APIReceiverFactory 类,以管理共享相同 API 类型的多个 Receiver 类。
  • ReceiverFactory 类中实现 create_receivername 方法。create_receiver 方法应返回 Receiver 类。
  • 默认情况下,create_receiverapp_root_nameprocess_name 作为参数,并返回 Receiver 类。
  • 使用装饰器 @ReceiverManager.register 注册 ReceiverFactory 类。

COMReceiverFactory 类的示例

from ufo.automator.puppeteer import ReceiverManager

@ReceiverManager.register
class COMReceiverFactory(APIReceiverFactory):
    """
    The factory class for the COM receiver.
    """

    def create_receiver(self, app_root_name: str, process_name: str) -> WinCOMReceiverBasic:
        """
        Create the wincom receiver.
        :param app_root_name: The app root name.
        :param process_name: The process name.
        :return: The receiver.
        """

        com_receiver = self.__com_client_mapper(app_root_name)
        clsid = self.__app_root_mappping(app_root_name)

        if clsid is None or com_receiver is None:
            # print_with_color(f"Warning: Win32COM API is not supported for {process_name}.", "yellow")
            return None

        return com_receiver(app_root_name, process_name, clsid)

    @classmethod
    def name(cls) -> str:
        """
        Get the name of the receiver factory.
        :return: The name of the receiver factory.
        """
        return "COM"

注意

如果不支持该应用程序,create_receiver 方法应返回 None

注意

您必须使用装饰器 @ReceiverManager.register 注册您的 ReceiverFactory,以便 ReceiverManager 管理 ReceiverFactory

Receiver 类现在已准备好接收来自 AppAgent 的原生 API 调用。

步骤 2:为原生 API 创建一个命令

命令是 AppAgent 可以在应用程序上执行的操作。要为原生 API 创建命令,您需要创建一个 Command 类,其中包含执行原生 API 调用的方法。

1. 创建一个命令类

  • Receiver 类所在的同一个 Python 文件中创建一个 Command 类。Command 类应继承自 ufo/automator/basic.py 中的 CommandBasic 类。

示例

class WinCOMCommand(CommandBasic):
    """
    The abstract command interface.
    """

    def __init__(self, receiver: WinCOMReceiverBasic, params=None) -> None:
        """
        Initialize the command.
        :param receiver: The receiver of the command.
        """
        self.receiver = receiver
        self.params = params if params is not None else {}

    @abstractmethod
    def execute(self):
        pass

    @classmethod
    def name(cls) -> str:
        """
        Get the name of the command.
        :return: The name of the command.
        """
        return cls.__name__

2. 定义执行方法

  • Command 类中定义 execute 方法,以调用接收器执行原生 API 调用。

示例

def execute(self):
    """
    Execute the command to insert a table.
    :return: The inserted table.
    """
    return self.receiver.insert_excel_table(
        sheet_name=self.params.get("sheet_name", 1),
        table=self.params.get("table"),
        start_row=self.params.get("start_row", 1),
        start_col=self.params.get("start_col", 1),
    )

3. 注册命令类

  • 使用 @your_receiver.register 装饰器在相应的 Receiver 类中注册 Command 类。

示例

@ExcelWinCOMReceiver.register
class InsertExcelTable(WinCOMCommand):
    ...

Command 类现在已在 Receiver 类中注册,可供 AppAgent 执行原生 API 调用。

步骤 3:为原生 API 提供提示描述

为了让 AppAgent 知道原生 API 调用的用法,您需要提供提示描述。

1. 创建一个 api.yaml 文件

- Create an `api.yaml` file in the `ufo/prompts/apps/{your_app_name}` directory.

2. 定义提示描述

  • api.yaml 文件中定义原生 API 调用的提示描述。

示例

table2markdown:
summary: |-
    "table2markdown" is to get the table content in a sheet of the Excel app and convert it to markdown format.
class_name: |-
    GetSheetContent
usage: |-
    [1] API call: table2markdown(sheet_name: str)
    [2] Args:
    - sheet_name: The name of the sheet in the Excel app.
    [3] Example: table2markdown(sheet_name="Sheet1")
    [4] Available control item: Any control item in the Excel app.
    [5] Return: the markdown format string of the table content of the sheet.

注意

table2markdown 是原生 API 调用的名称。它 必须 与相应的 Command 类中定义的 name() 匹配!

3. 在 config_dev.yaml 中注册提示地址

  • 通过将应用程序程序名称作为键,提示文件地址作为值,将其添加到 config_dev.yaml 文件的 APP_API_PROMPT_ADDRESS 字段中来注册提示地址。

示例

APP_API_PROMPT_ADDRESS: {
    "WINWORD.EXE": "ufo/prompts/apps/word/api.yaml",
    "EXCEL.EXE": "ufo/prompts/apps/excel/api.yaml",
    "msedge.exe": "ufo/prompts/apps/web/api.yaml",
    "chrome.exe": "ufo/prompts/apps/web/api.yaml"
    "your_application_program_name": "YOUR_APPLICATION_API_PROMPT"
} 

注意

your_application_program_name 必须与应用程序程序名称匹配。

AppAgent 现在可以使用提示描述来理解原生 API 调用的用法。


通过遵循这些步骤,您将成功将应用程序的原生 API 封装到 UFO 的工具箱中,从而允许 AppAgent 在应用程序上执行原生 API 调用!