Type something to search...
构建智能代理:如何让ai像人类一样操作计算机并完成任务

构建智能代理:如何让ai像人类一样操作计算机并完成任务

在这篇博客中,我们将从零开始构建一个AI代理,以处理交互式任务,例如:

  • 从亚马逊给我买一个足球
    它打开Chrome,搜索一个查询,滚动,并将第一个足球添加到购物车。

  • 购买任务

打开Google Chrome并搜索谷歌股价

Image 2

  • 谷歌股价任务

Claude去年发布了一个计算机使用的beta版本,一个可以执行桌面任务的AI代理,但它仍然处于beta阶段,开发者可以指导Claude以人们的方式使用计算机,通过查看屏幕、移动光标、点击按钮和输入文本。

我们将做同样的事情,让大型语言模型看到我们的屏幕并采取相应的行动。

GitHub 代码

所有代码以及正确的设置都可以在我的 GitHub 仓库中找到。

代码库的组织结构如下:

/ai-desktop  
│── /OmniParser       
│── main.py           
│── utils.py          
│── config.py         
│── requirements.txt  
│── README.md

目录

我们的代理是如何工作的

在编写技术代码之前,我们需要了解我们的方案是什么,我们在编码什么,以及它如何能够高效。

让我们先进行可视化,以便更好地理解。

计算机使用AI代理架构(由Fareed Khan创建)

  1. 首先,用户向我们的代理提供输入命令,例如**“打开Chrome并给我买一瓶牛奶”**。
  2. VLMAgent(视觉语言模型)接收用户输入并开始处理。
  3. 代理使用OmniParser解析屏幕以提取相关信息。
  4. 代理分析屏幕并使用LLM OpenAI确定需要采取的行动。
  5. OmniParser处理屏幕内容并提取关键细节,例如按钮、文本字段或链接。
  6. LLM OpenAI解释提取的信息并决定下一步。
  7. 代理生成诸如鼠标移动、输入和点击等动作。
  8. 这些动作被发送到计算机执行。
  9. 计算机处理这些动作,例如打开Chrome、搜索牛奶、将其添加到购物车并进行结账。
  10. 系统从执行的动作中收集反馈,以检查任务是否完成。
  11. 如果任务尚未完成,循环将重复直到成功。
  12. 一旦所有动作成功执行,任务将被标记为完成,系统确认结果。

现在我们已经了解了我们方案的基本概述,是时候开始编码了。

设置阶段

克隆仓库并使用以下命令安装所需的库:

git clone --recursive https://github.com/FareedKhan-dev/ai-desktop
cd ai-desktop/OmniParser

pip install -r requirements.txt

现在,让我们导入所需的库。

import logging  
import os       
import sys      
import re       
import math

from dataclasses import dataclass, field  
from typing import List, Optional, Dict, Any, Tuple

import base64   
import io        
from PIL import Image  
import pyautogui 
import torch   
import asyncio  
from pathlib import Path

from OmniParser.utils.display import get_yolo_model, \
                                     get_caption_model_processor, \
                                     get_som_labeled_img, check_ocr_box

编写Omni Parser

Image 4

Omni Parser工作流程(由Fareed Khan创建)

Omni Parser的快速概述是,它是微软最近发布的用于屏幕解析的模型。由于其准确性,可以视为一次突破。

你可以查看其官方GitHub仓库,在我们可以与我们的AI代理一起使用之前,需要进行适当的设置。

首先,我们需要克隆他们的仓库并下载Omni Parser模型权重。

git clone https://github.com/microsoft/OmniParser/
cd OmniParser

在克隆仓库并导航到目录后,我们可以简单地下载模型。

for f in icon_detect/{train_args.yaml,model.pt,model.yaml} \
         icon_caption/{config.json,generation_config.json,model.safetensors}; do
    huggingface-cli download microsoft/OmniParser-v2.0 "$f" --local-dir weights
done

mv weights/icon_caption weights/icon_caption_florence

它将开始下载权重。确保正确重命名权重文件夹,如我在上面的命令中所做的那样。

你可以简单地使用他们的演示笔记本Gradio脚本来查看Omni Parser是如何工作的。例如,这就是它在我的Windows桌面屏幕截图上的表现。

Image 5

Omni Parser在我的桌面上的检测

它正确识别了大多数图标和文本,所以,是的,它相当不错。

我们需要定义一些重要参数,以帮助有效配置OmniParser。

config = {
    "som_model_path": "weights/icon_detect/model.pt",
    "caption_model_name": "microsoft/OmniParser-v2.0",
    "caption_model_path": "weights/icon_caption_florence",
    "BOX_TRESHOLD": 0.5,  
}

这些参数指定了结构化对象模型(SOM)和标题模型的路径,以及检测UI元素的阈值。

现在我们已经定义了配置,让我们初始化并加载所需的模型。

OmniParser使用基于YOLO的结构对象模型来检测UI元素,并使用标题模型提取有意义的描述。

以下是我们加载模型的方式:

def load_models(config: Dict):
    device = "cuda" if torch.cuda.is_available() else "cpu"

    som_model = get_yolo_model(model_path=config["som_model_path"])

    caption_model_processor = get_caption_model_processor(
        model_name=config["caption_model_name"],
        model_name_or_path=config["caption_model_path"],
        device=device
    )

    print("OmniParser模型加载成功!")
    return som_model, caption_model_processor

此函数确保模型正确加载到适当的设备(CPU或GPU)。

SOM模型帮助检测UI组件,而标题模型处理从屏幕提取的文本和标签。

现在,让我们创建主函数,该函数获取屏幕截图,使用OmniParser处理它,并提取结构化信息。

def parse_screen(image_base64: str, som_model, caption_model_processor) -> Tuple[str, List[Dict[str, Any]]]:
    image_bytes = base64.b64decode(image_base64)
    image = Image.open(io.BytesIO(image_bytes))
    print("图像大小:", image.size)

    box_overlay_ratio = max(image.size) / 3200
    draw_bbox_config = {
        "text_scale": 0.8 * box_overlay_ratio,
        "text_thickness": max(int(2 * box_overlay_ratio), 1),
        "text_padding": max(int(3 * box_overlay_ratio), 1),
        "thickness": max(int(3 * box_overlay_ratio), 1),
    }

    (text, ocr_bbox), _ = check_ocr_box(
        image,
        display_img=False,
        output_bb_format="xyxy",
        easyocr_args={"text_threshold": 0.8},
        use_paddleocr=False
    )

    dino_labeled_img, label_coordinates, parsed_content_list = get_som_labeled_img(
        image, 
        som_model, 
        BOX_TRESHOLD=config["BOX_TRESHOLD"], 
        output_coord_in_ratio=True, 
        ocr_bbox=ocr_bbox,
        draw_bbox_config=draw_bbox_config, 
        caption_model_processor=caption_model_processor, 
        ocr_text=text,
        use_local_semantics=True, 
        iou_threshold=0.7, 
        scale_img=False, 
        batch_size=128
    )

    return dino_labeled_img, parsed_content_list

我们正在解码Base64图像,然后使用OCR提取文本和边界框,通过OmniParser模型处理屏幕,结果返回标记的UI元素和结构化数据。

让我们在实际的桌面屏幕截图上测试我们的OmniParser。我们将加载一张示例图像,使用我们的函数处理它,并查看它提取UI元素的效果如何。

som_model, caption_model_processor = load_models(config)

with open("desktop_screenshot.jpg", "rb") as image_file:
    image_base64 = base64.b64encode(image_file.read()).decode("utf-8")

labeled_img, parsed_content = parse_screen(image_base64, som_model, caption_model_processor)

print(parsed_content)

[
    {'type': 'text',
     'bbox': [0.091, 0.004, 0.137, 0.028],
     'interactivity': False,
     'content': 'G Google',
     'source': 'box_ocr_content_ocr'},
     
    {'type': 'text',
     'bbox': [0.088, 0.040, 0.1895, 0.062],
     'interactivity': False,
     'content': 'https://www.google.com',
     'source': 'box_ocr_content_ocr'},
    ...
]

当我们运行此代码时,我们会看到解析的内容包含边界框、类型及许多其他重要信息,这些信息对于我们的视觉语言模型的使用至关重要。

现在,我们只实现了AI解析屏幕截图的部分,例如绘制边界框和提取信息。

屏幕截图工具

在我们的AI代理使用OmniParser解析UI元素之前,它首先需要捕获屏幕。

虽然我们可以手动截图并将其输入模型,但我们的目标是自动化整个过程。这意味着AI代理应该能够通过按需捕获屏幕截图来“看到”屏幕。

为此,我们将使用 pyautogui,这是一个简单有效的Python截图库。捕获的屏幕截图将由我们的OmniParser处理。

首先,让我们定义一个函数来捕获当前屏幕并将其保存为图像文件。

OUTPUT_DIR = "temp/"

def get_screenshot() -> Tuple[Image.Image, Path]:
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    img = pyautogui.screenshot()
    screenshot_path = OUTPUT_DIR / "screenshot.png"
    img.save(screenshot_path)
    return img, screenshot_path

我们首先创建一个临时目录(tmp/)来存储截图。之后我们使用 pyautogui.screenshot() 来捕获全屏截图,图像将保存在 tmp/ 目录中,文件名为 screenshot.png,我们的函数返回截图作为 PIL.Image 对象和其文件路径。

现在我们已经自动化了截图捕获,让我们将捕获的图像输入到我们的OmniParser中并提取结构化数据。

screenshot, screenshot_path = get_screenshot()

with open(screenshot_path, "rb") as image_file:
    image_base64 = base64.b64encode(image_file.read()).decode("utf-8")

som_model, caption_model_processor = load_models(config)

labeled_img, parsed_content = parse_screen(image_base64, som_model, caption_model_processor)

如果我们运行上述代码,我们的OmniParser将分析捕获的截图并提取结构化UI元素,但这次它将是自动化的。

给我们的代理赋予行动能力

Image 6

代理如何采取行动 (创建者:Fareed Khan)

到目前为止,我们已经使我们的AI代理具备了视觉,能够看到屏幕并理解屏幕上的内容,这要归功于OmniParser和pyautogui。但仅仅“看”是不够的。

为了真正有用,我们需要让我们的代理能够_做_事情。它需要能够像我们一样与计算机互动,使用鼠标和键盘。这就是我们进入动作执行步骤的地方。

为了使我们的代理具有交互性,我们需要定义一组它可以采取的动作。这些动作将是我们想要自动化的任何任务的构建块。

当你使用计算机时,你实际上是做什么?你可能会:

  • 移动鼠标:你在屏幕上引导光标指向事物。
  • 点击:你通过鼠标点击选择按钮、链接和图标。你可能会左键单击、右键单击,甚至双击。
  • 输入:你使用键盘在搜索框、表单和文档中输入文本。
  • 按键:你使用快捷键、导航和命令——例如按‘Enter’提交表单,或‘Ctrl+C’复制文本。
  • 滚动:你上下移动网页或文档以查看更多内容。

这些是我们与桌面互动的基本方式。

为了使我们的AI代理具备能力,我们需要赋予它相同的能力。让我们为我们的代理定义这些基本动作的列表:

possible_actions = [
    "key_press",       
    "type_text",      
    "move_mouse",      
    "click_left_mouse", 
    "click_right_mouse",
    "click_double_mouse",
    "capture_screenshot",
    "get_cursor_position", 
    "pause_briefly",   
    "scroll_up_page",  
    "scroll_down_page",
    "hover_mouse_over" 
]

这个possible_actions列表是我们代理的动作词汇。它是一组它可以理解和执行的命令。

在下一步中,我们将创建一个工具,能够将这些抽象的动作名称转换为实际的计算机操作。

实现动作

现在我们有一份代理 理解 的动作列表。但是它是如何在计算机上 执行 这些动作的呢?

我们需要一个工具来弥合代理动作命令与计算机输入系统之间的差距。我们称这个工具为 ComputerActions。

ComputerActions 工具就像是我们代理的一组机械手臂。当代理决定“click_left_mouse”时,ComputerActions 工具将使用 pyautogui 在当前光标位置模拟一次左键单击。类似地,对于“type_text”,它将使用 pyautogui 模拟键盘输入。

让我们勾勒出 ComputerActions 工具的结构。

class ComputerActions_Tool:
    tool_name = "computer_actions"

    def __init__(self):
        self.key_name_mapping = {
            "PageDown": "pagedown",  
            "PageUp": "pageup",
            "WindowsKey": "win",
            "EscapeKey": "esc",
            "EnterKey": "enter"
        }

    async def perform_action(self, action_type, action_details=None):
        try:
            if action_type == "move_mouse":
                if action_details is None or 'coordinates' not in action_details:
                    raise Exception("Mouse movement needs coordinates (x, y).")
                x_coord, y_coord = action_details['coordinates']
                pyautogui.moveTo(x_coord, y_coord, duration=0.2) 
                return {"action_result": f"Mouse moved to ({x_coord}, {y_coord})"}

            elif action_type == "click_left_mouse":
                pyautogui.click()
                return {"action_result": "Left mouse click performed."}

            else:
                raise Exception(f"Unknown action type: {action_type}")

        except Exception as action_error:
            return {"action_error_message": f"Error during action: {action_error}"}

在这个 ComputerActions_Tool 类中,perform_action 函数是工具的核心。

它接受 action_type(如“move_mouse”或“click_left_mouse”)和 action_details(可能包括坐标或要输入的文本)。

在这个函数内部,我们使用 pyautogui 将这些命令转换为实际的计算机交互。我们已经开始处理“move_mouse”和“click_left_mouse”作为示例,并将扩展它以包括我们 possible_actions 列表中的所有动作。

移动和点击

让我们更深入地了解一下我们在计算机动作工具中的perform_action函数是如何处理特定动作的。考虑“move_mouse”和“click_left_mouse”动作。

当代理想要移动鼠标时,它会向计算机动作工具发送一个命令,action_type为“move_mouse”,action_details包含目标坐标。

再看看这部分代码:

if action_type == "move_mouse":
    if action_details is None or 'coordinates' not in action_details:
        raise Exception("Mouse movement needs coordinates (x, y).")
    x_coord, y_coord = action_details['coordinates']
    pyautogui.moveTo(x_coord, y_coord, duration=0.2)
    return {"action_result": f"Mouse moved to ({x_coord}, {y_coord})"}

首先,它检查是否提供了action_details,以及它们是否包含‘coordinates’。如果没有,它会引发一个错误,因为移动鼠标需要知道_在哪里_移动。

然后,它从action_details中提取x_coordy_coord。最后,它使用pyautogui.moveTo(x_coord, y_coord, duration=0.2)在短时间内(0.2秒)平滑地将鼠标光标移动到指定位置。

该函数随后返回一条确认消息。

对于左键点击,过程更简单。代理发送一个命令,action_type为“click_left_mouse”。处理方式如下:

elif action_type == "click_left_mouse":
    pyautogui.click()
    return {"action_result": "Left mouse click performed."}

在这种情况下,不需要action_details。当pyautogui.click()函数在没有任何参数的情况下被调用时,它会在_当前_鼠标光标位置执行一次左键点击。

该函数随后返回一条简单的确认消息。

你可以看到添加更多动作是多么简单。对于“type_text”,我们将使用pyautogui.typewrite(text),对于“key_press”,我们将使用pyautogui.press(key),等等。

pyautogui提供了模拟我们所需的所有基本鼠标和键盘交互的函数。

动作协调器

我们现在有一个计算机动作工具(ComputerActions_Tool),可以执行动作。但谁来告诉这个工具_做什么_和_何时_做呢?

我们需要一个作为中央协调者的组件,接收来自AI代理的动作_指令_,然后使用适当的工具来_执行_它们。我们称这个组件为动作协调器(ActionOrchestrator)。

我们的动作协调器的工作是:

  1. 接收动作命令:它将从主要AI代理那里获取指令,告诉它执行哪个动作以及使用哪些参数(如坐标或文本)。
  2. 选择正确的工具:在我们当前的设置中,我们只有计算机动作工具(ComputerActions_Tool)。但在未来,我们可能会有其他工具(例如,直接与网络API交互的工具)。动作协调器将决定给定动作所需的工具。
  3. 执行动作:它将使用选定的工具来执行动作,传入必要的参数。
  4. 处理结果和错误:它将接收来自工具的反馈——动作是否成功、是否有错误,或动作的任何输出。然后,它将这些信息传递回主要AI代理。

让我们概述一下动作协调器类。

class ActionOrchestrator_Agent:

    def __init__(self, feedback_callback_function):
        self.computer_tool = ComputerActions_Tool() 
        self.feedback_callback = feedback_callback_function

    async def execute_agent_action(self, tool_name, action_input_details):
        self.feedback_callback(f"Attempting to use tool: {tool_name}, with details: {action_input_details}", "agent_message")

        try:
            if tool_name == "computer_actions":
                action_type_to_use = action_input_details.get("action") 
                action_parameters = action_input_details.get("parameters", None)

                action_result = await self.computer_tool.perform_action( 
                    action_type=action_type_to_use, action_details=action_parameters
                )

                if action_result.get("action_error_message"): 
                    self.feedback_callback(f"Tool reported an error: {action_result['action_error_message']}", "agent_error") 
                else:
                    if action_result.get("action_result"): 
                        self.feedback_callback(action_result["action_result"], "agent_output") 
                return action_result

            else:
                return {"action_error_message": f"Unknown tool requested: {tool_name}"}

        except Exception as orchestration_error:
            return {"action_error_message": f"Unexpected problem during action orchestration: {orchestration_error}"}

动作协调器代理(ActionOrchestrator_Agent)类通过反馈回调函数(feedback_callback_function)进行初始化,这使它能够发送关于其正在做什么的消息(用于日志记录或显示目的)。

execute_agent_action函数是主要的入口点。它将tool_name和action_input_details作为输入。

目前,它只处理“computer_actions”工具。它从action_input_details中提取action_type和action_parameters,调用计算机动作工具(ComputerActions_Tool)的perform_action函数,然后处理结果,通过反馈回调函数报告任何错误或输出。

有了动作协调器,我们已经完成了动作执行管道。我们的代理现在可以“看到”屏幕,决定一个动作,然后使用动作协调器和计算机动作工具在计算机上实际执行该动作。

下一个大步骤是引入“大脑”,即大型语言模型(LLM),以便在第一时间做出智能决策,决定采取哪些动作!

思维方法

我们已经构建了我们的AI代理的眼睛(OmniParser)和(动作执行),但它需要一个大脑来理解用户目标、分析屏幕并决定下一步该做什么。

这就是**大型语言模型(LLM)**派上用场的地方。它解释自然语言命令,如_“打开Chrome并搜索猫视频”_,处理来自OmniParser的UI元素,规划动作,并生成诸如move_mousetype_text的命令。

这使我们的代理变得智能和适应性强,而不是盲目地执行指令。

为了做出决策,LLM需要两个输入:用户的目标(例如,“预订飞往巴黎的航班”)和当前屏幕状态(来自OmniParser的结构化UI数据)。

UI数据被格式化为清晰的提示,列出按钮和文本框等元素,使LLM能够理解屏幕上可见的内容。例如:

{
  "type": "button",
  "text": "搜索",
  "bounding_box": [100, 200, 150, 220]
},
{
  "type": "text_field",
  "placeholder": "输入搜索词",
  "bounding_box": [80, 180, 300, 200]
}

LLM以结构化的行动计划作出响应,指定下一步动作、所需的参数(如坐标或文本)以及可选的推理以供调试。例如,如果用户想要打开Chrome:

{
  "reasoning": "用户想要打开Chrome。桌面上有一个Chrome图标。我应该双击它。",
  "next_action": "click_double_mouse",
  "target_element_id": "chrome_icon_id"
}

这种决策过程通过一个持续的循环与动作执行系统相连接:观察 → 决定 → 行动 → 观察

代理捕获屏幕,格式化数据,将其发送给LLM,提取响应,通过动作协调器执行动作,并重复此过程。这使得实时适应成为可能。

在我们的下一步中,我们将包括LLM提示,优化工作流程,处理错误并提高效率。通过这一设置,我们的AI代理可以真正看、思考和行动,改变我们与计算机的交互方式。

为清晰起见构建提示

为了控制我们的桌面AI代理,我们通过提示引导大型语言模型,给出明确的指示,以便它做出正确的决策。

一个好的提示包括:

  1. 用户目标: 用户想要什么(例如,“打开浏览器”或“获取股票价格”)。
  2. 当前屏幕状态: 屏幕上可见的内容(来自OmniParser)。

通过结合这些信息,大型语言模型可以确定最佳的行动。

我们希望给大型语言模型一个清晰的**“工作描述,”**它可用的工具,当前情况(屏幕状态),目标(用户查询),以及它应以何种格式传达其决策(JSON响应)。

You are a desktop AI agent that controls a computer 
using a mouse and keyboard.

我们立即告诉它_它是谁_。它不仅仅是任何AI,它是一个_桌面AI代理_。桌面AI代理做什么?它控制计算机!它_是如何_做到这一点的?使用鼠标和键盘,就像你我一样。

现在大型语言模型知道_它是什么_,我们需要告诉它_它能做什么_。

在这里,我们列出我们之前构建的计算机动作工具可以执行的所有动作。还记得可能的动作列表吗?

我们将其放入提示中:

Your available actions are:
- move_mouse: Moves the mouse cursor to a specific (x, y) coordinate 
on the screen.
- click_left_mouse: Performs a left mouse click at the current cursor position.
- type_text: Types the given text using the keyboard.
- key_press: Presses a specific key or key combination (e.g., 'Enter', 'Ctrl+C').
- ... (and so on for all actions in our 'possible_actions' list)

通过清晰且详细地列出这些动作,我们告诉大型语言模型它的能力。它不能发明新动作;它必须从这个列表中选择。这对保持控制和可预测性非常重要。

因此,大型语言模型知道它的工作和工具。但它仍然是盲目的!它需要_看到_屏幕才能做出决策。这就是我们将OmniParser的输出提供给它的地方。

看看提示的这一部分:

Here is the information about what is currently visible on the screen:
[Start of Screen Information]
[Structured output from OmniParser will be inserted here.
For example:
- Element type: Button, Text: 'Submit', Coordinates: (100, 200, 150, 250)
- Element type: Text Field, Placeholder: 'Search...', Coordinates: (50, 50, 300, 100)
- ... and so on for all detected UI elements]
[End of Screen Information]

我们向大型语言模型展示了桌面的图像,但不仅仅是原始图像。我们通过OmniParser提供了一个_结构化_的视图。

这就像在说:“嘿大型语言模型,现在屏幕上,我看到一个按钮,上面写着‘提交’,在这些坐标上,还有一个文本框,上面写着‘搜索…’在这里”。

现在大型语言模型可以看到它的工作区,并知道它的工具。但_目标_是什么?用户希望它做什么?这就是我们注入用户查询的地方:

The user's request is: {User Query}

我们直接告诉大型语言模型用户的请求是什么,比如“打开Google Chrome”或“在线购买牛奶”。

这就是大型语言模型需要达到的_目标_。

我们需要它以我们系统可以理解的方式_将决策反馈给我们_。这就是我们指定响应格式的原因!我们想要JSON!

让我们看看这一部分:

Respond in JSON format. Your JSON response should have the following fields:
- "reasoning": (Explain your thought process step-by-step. Why did you choose this action?)
- "next_action": (The action to perform next. Choose one from the available actions listed above.)
- "action_parameters": (A dictionary containing parameters needed for the action.
                         For 'move_mouse', include 'coordinates': [x, y].
                         For 'type_text', include 'text_to_type': 'the text'.
                         For 'key_press', include 'key_name': 'the key'.
                         If no parameters are needed, this field can be an empty dictionary.)

我们正在建立一个清晰的沟通协议。我们在说:“大型语言模型,当你决定做什么时,请以JSON格式告诉我。并确保你的JSON包含这些特定字段:reasoning、next_action和action_parameters。”

最后,为了使其更加清晰,我们给大型语言模型一个我们期望的JSON响应示例。

Example JSON Response:
{
  "reasoning": "The user wants to click the 'Submit' button. I see a button with text 'Submit' on the screen. I should move the mouse to its coordinates and perform a left click.",
  "next_action": "move_mouse",
  "action_parameters": { "coordinates": [125, 225] } // Example coordinates - use actual coordinates from parsed info
}

所以总结一下,我们的整个提示就像是给大型语言模型的详细说明手册。我们不仅仅是要求它做某事,而是仔细引导它完成整个决策过程:

  1. 理解你的角色。(桌面AI代理)
  2. 了解你的能力。(可用动作)
  3. 查看当前情况。(屏幕信息)
  4. 理解目标。(用户请求)
  5. 清晰地传达你的决策。(JSON响应格式)
  6. 这是我期望的示例。(示例JSON)

构建 VLM 代理

我们现在拥有所有的组件:

  1. 眼睛 (OmniParser)
  2. 手 (动作执行)
  3. 大脑 (LLM)
  4. 以及与其对话的方式 (提示)

下一个重要的步骤是将这些组件整合在一起,创建我们 AI 代理的中央协调器。我们称这个类为 VLMAgent(视觉语言模型代理)。

VLMAgent 类中最重要的部分是它的主方法,我们称之为 run。这个方法将是执行用户任务的入口点。让我们逐步概述一下 run 方法应该做的事情:

class VLMAgent_Agent:

    async def run_agent_task(self, user_query):

        previous_agent_messages = []

        while True:

            screenshot_image, screenshot_path = get_screenshot()

            labeled_image_base64, parsed_screen_content = parse_screen( 
                screenshot_image, self.omni_parser_models, self.omni_parser_config
            )

            prompt_to_llm = create_llm_prompt( 
                user_query=user_query,
                screen_content=parsed_screen_content,
                previous_messages=previous_agent_messages 
            )

            llm_response_text, _ = send_prompt_to_llm_api( 
                prompt=prompt_to_llm,
                llm_api_config=self.llm_config 
            )
            self.output_feedback_function(f"LLM 响应: {llm_response_text}", "llm_response")

            action_command_from_llm = parse_llm_response_for_action(llm_response_text)

            if action_command_from_llm and action_command_from_llm.get("next_action"): 
                action_description = generate_action_description(action_command_from_llm) 
                self.output_feedback_function(action_description, "agent_action")

            action_name = "computer_actions" 
            action_input = prepare_action_input(action_command_from_llm)

            action_result = await self.action_orchestrator.execute_agent_action( 
                tool_name=action_name, tool_input=action_input
            )

            previous_agent_messages = update_conversation_history( 
                previous_messages=previous_agent_messages,
                llm_response=llm_response_text,
                action_description=action_description,
                action_result=action_result
            )
            user_query = "继续之前的任务"

            if action_result.get("error"): 
                previous_agent_messages.append({"role": "user", "content": action_result["error"]}) 
            else:
                self.output_feedback_function("任务完成或无需动作。", "agent_status") 
                break

        self.output_feedback_function("代理任务结束。", "agent_status")

我们的 run_agent_task 方法包含了代理的整个工作流程。它是一个循环,持续进行直到代理决定完成。循环中的每一步对应我们构建的核心功能之一:

  1. 屏幕截图
  2. 解析
  3. 提示 LLM
  4. 动作执行
  5. 反馈处理

我们使用了占位符函数名,如 create_llm_promptsend_prompt_to_llm_apiparse_llm_response_for_action 等,以指示这些子函数将适合的位置。

以下是 run_agent_task 辅助函数的简要概述:

  • create_llm_prompt: 使用用户查询、屏幕内容和对话历史构建 LLM 提示。
  • send_prompt_to_llm_api: 将提示发送到 LLM API 并返回响应。
  • parse_llm_response_for_action: 从 LLM 的响应中提取动作和参数(处理 JSON 解析错误)。
  • prepare_action_input: 格式化 LLM 的动作输出,以供 ActionOrchestrator 使用。
  • update_conversation_history: 记录 LLM 的响应、动作和结果,以保持连贯性。
  • generate_action_description: 将动作转换为人类可读的描述。
  • get_screenshot & parse_screen: 使用 OmniParser 捕获和处理屏幕内容。

在接下来的部分中,我们将详细阐述这些辅助函数,并看看如何将所有内容整合到一个 main.py 脚本中,以实际运行我们的代理。

调用一切

最后,为了使我们的代理可运行,我们将创建一个主异步函数。它将调用到目前为止我们编写的所有内容。

async def main_function_agent(user_query):

    executor = LocalExecutor_Agent(output_callback_function=print)

    agent = VLMAgent_Agent(
        omni_parser_config=omni_parser_config,
        llm_config=llm_config,
        action_orchestrator=executor,
        output_feedback_function=print
    )

    print("等待5秒钟再执行...")
    await asyncio.sleep(5)

    await agent.run_agent_task(user_query=user_query)

if __name__ == "__main__":
    if len(sys.argv) > 1:
        user_input = " ".join(sys.argv[1:])  
    else:
        user_input = input("请输入您的命令: ")

asyncio.run(main_function_agent(user_input))

它必须是一个异步函数,因为它允许其他任务在等待时运行(例如,在进行I/O操作时,如API调用、延迟或用户输入)。

还有一件重要的事情,我添加了sleep(5),这是一个5秒的延迟,因为我需要最小化所有窗口,从桌面开始。这不是强制性的,但从桌面捕获屏幕会更容易,因为可见的图标更少。

执行任务

让我们测试我们的代理,看看它的表现如何,以及在哪些方面存在滞后。

请告诉我今天Google的股价。

Google股价任务

它打开Chrome,并正确地在搜索框中输入字符串**“Google的股价”**。

虽然我们可以添加功能来读取浏览器搜索结果的屏幕截图并在终端中返回价格值,但到目前为止执行的动作是正确的。

从亚马逊给我买一个足球。

对于购买任务,它打开Chrome,搜索**“足球 亚马逊”**,选择第一个链接,并将第一个商品添加到购物车。

当然,我们可以使提示更加详细,例如,指定价格限制,但这需要相应地升级我们的代码。

克隆这个

当我让它克隆我的一个GitHub仓库时,它失败了。相反,它随机点击不同的搜索框,导航到不相关的网站,并未达到预期目标。

错误的动作

使用 OmniParser + OmniTool

虽然您可以修改我的代码,但一种更强大和先进的方法是使用 OmniParserOmniTool,您可以在 OmniParser GitHub 仓库 中找到它。

它基于 OpenAI API,桌面环境将是虚拟的,而不是您实际的环境,出于安全原因。这个方法更为成熟和功能齐全,您应该去看看。

实际上,一位开发者甚至为开源的 LLM Qwen 2.5 Vision Model 定制了它,以便那些不想使用 OpenAI 的用户使用。

Qwen 2.5 Vision Model OmniTool 仓库:

Related Posts

结合chatgpt-o3-mini与perplexity Deep Research的3步提示:提升论文写作质量的终极指南

结合chatgpt-o3-mini与perplexity Deep Research的3步提示:提升论文写作质量的终极指南

AI 研究报告和论文写作 合并两个系统指令以获得两个模型的最佳效果 Perplexity AI 的 Deep Research 工具提供专家级的研究报告,而 OpenAI 的 ChatGPT-o3-mini-high 擅长推理。我发现你可以将它们结合起来生成令人难以置信的论文,这些论文比任何一个模型单独撰写的都要好。你只需要将这个一次性提示复制到 **

阅读更多
让 Excel 过时的 10 种 Ai 工具:实现数据分析自动化,节省手工作业时间

让 Excel 过时的 10 种 Ai 工具:实现数据分析自动化,节省手工作业时间

Non members click here作为一名软件开发人员,多年来的一个发现总是让我感到惊讶,那就是人们还在 Excel

阅读更多
使用 ChatGPT 搜索网络功能的 10 种创意方法

使用 ChatGPT 搜索网络功能的 10 种创意方法

例如,提示和输出 你知道可以使用 ChatGPT 的“搜索网络”功能来完成许多任务,而不仅仅是基本的网络搜索吗? 对于那些不知道的人,ChatGPT 新的“搜索网络”功能提供实时信息。 截至撰写此帖时,该功能仅对使用 ChatGPT 4o 和 4o-mini 的付费会员开放。 ![](https://images.weserv.nl/?url=https://cdn-im

阅读更多
掌握Ai代理:解密Google革命性白皮书的10个关键问题解答

掌握Ai代理:解密Google革命性白皮书的10个关键问题解答

10 个常见问题解答 本文是我推出的一个名为“10 个常见问题解答”的新系列的一部分。在本系列中,我旨在通过回答关于该主题的十个最常见问题来分解复杂的概念。我的目标是使用简单的语言和相关的类比,使这些想法易于理解。 图片来自 [Solen Feyissa](https://unsplash.com/@solenfeyissa?utm_source=medium&utm_medi

阅读更多
在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和科技这样一个动态的行业中,保持领先意味着不断提升你的技能。无论你是希望深入了解人工智能模型性能、掌握数据分析,还是希望通过人工智能转变传统领域如法律,这些课程都是你成功的捷径。以下是一个精心策划的高价值课程列表,可以助力你的职业发展,并让你始终处于创新的前沿。 1. 生成性人工智能简介课程: [生成性人工智能简介](https://genai.works

阅读更多
揭开真相!深度探悉DeepSeek AI的十大误区,您被误导了吗?

揭开真相!深度探悉DeepSeek AI的十大误区,您被误导了吗?

在AI军备竞赛中分辨事实与虚构 DeepSeek AI真的是它所宣传的游戏规则改变者,还是仅仅聪明的营销和战略炒作?👀 虽然一些人将其视为AI效率的革命性飞跃,但另一些人则认为它的成功建立在借用(甚至窃取的)创新和可疑的做法之上。传言称,DeepSeek的首席执行官在疫情期间像囤积卫生纸一样囤积Nvidia芯片——这只是冰山一角。 从其声称的550万美元培训预算到使用Open

阅读更多
Type something to search...