From 3d0d9d77584769c44dc4bda0429c5e11fd9c5633 Mon Sep 17 00:00:00 2001 From: Timothyxxx <384084775@qq.com> Date: Wed, 29 Nov 2023 20:21:57 +0800 Subject: [PATCH] Run through gpt_4v agent pipeline --- README.md | 4 +- desktop_env/controllers/python.py | 8 ++- desktop_env/envs/desktop_env.py | 63 ++++++++++++------ desktop_env/windows_server/main.py | 6 +- gpt_4v_agent_exp.py | 47 +++++++++++++ mm_agents/gpt_4v_agent.py | 45 +++++++++---- .../{gpt_4v_prompt.txt => gpt_4v_prompt.py} | 9 ++- screenshot.png | Bin 364474 -> 845969 bytes 8 files changed, 135 insertions(+), 47 deletions(-) create mode 100644 gpt_4v_agent_exp.py rename mm_agents/{gpt_4v_prompt.txt => gpt_4v_prompt.py} (76%) diff --git a/README.md b/README.md index 815c35d..e699a2e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ## Setup guide +### For members of the team 1. Download OS image 1. Download kubuntu from 2. Download ubuntu from @@ -22,7 +23,8 @@ 2. `rm -rf ~/screenshot.png` 7. Set up python and install [mouse](https://github.com/boppreh/mouse/) and [keyboard](https://github.com/jordansissel/xdotool) - +### For users of the environment +todo ## Road map (Proposed) diff --git a/desktop_env/controllers/python.py b/desktop_env/controllers/python.py index 6b5e627..735bd44 100644 --- a/desktop_env/controllers/python.py +++ b/desktop_env/controllers/python.py @@ -1,10 +1,11 @@ import requests import json + class PythonController: def __init__(self, http_server: str): self.http_server = http_server - + def _execute_python_command(self, command: str) -> None: payload = json.dumps({ "command": command @@ -12,7 +13,7 @@ class PythonController: headers = { 'Content-Type': 'application/json' } - + try: response = requests.post(self.http_server + "/execute", headers=headers, data=payload) if response.status_code == 200: @@ -22,10 +23,11 @@ class PythonController: except requests.exceptions.RequestException as e: print("An error occurred while trying to execute the command:", e) + # example usage if __name__ == '__main__': # replace with your actual server URL of the vm - server_url = "http://192.168.7.129:5000" + server_url = "http://192.168.7.129:5000" controller = PythonController(server_url) # example commands diff --git a/desktop_env/envs/desktop_env.py b/desktop_env/envs/desktop_env.py index 962d176..d7b1b98 100644 --- a/desktop_env/envs/desktop_env.py +++ b/desktop_env/envs/desktop_env.py @@ -1,3 +1,4 @@ +import os from enum import Enum from typing import Literal, List, Tuple import subprocess @@ -7,10 +8,13 @@ import time import gymnasium as gym from gymnasium import spaces import numpy as np -from PIL import Image +import uuid + +from desktop_env.controllers.mouse import MouseClick, AbstractMouseController, XDoToolMouseController, \ + PythonMouseController +from desktop_env.controllers.keyboard import AbstractKeyboardController, XDoToolKeyboardController, \ + PythonKeyboardController -from desktop_env.controllers.mouse import MouseClick, AbstractMouseController, XDoToolMouseController, PythonMouseController -from desktop_env.controllers.keyboard import AbstractKeyboardController, XDoToolKeyboardController, PythonKeyboardController class Action(Enum): CLICK = 0 @@ -29,14 +33,25 @@ VM_TYPE = Literal['ubuntu', 'windows'] class DesktopEnv(gym.Env): """DesktopEnv with OpenAI Gym interface.""" - def __init__(self, path_to_vm: str, username: str, password: str, - host: str, snapshot_path: str = "some_point_browser", vm_os: VM_TYPE = "ubuntu"): + def __init__( + self, + path_to_vm: str, + username: str, + password: str = None, + host: str = "192.168.7.128:5000", + snapshot_path: str = "initial_state_with_env_set", + vm_os: VM_TYPE = "ubuntu"): + # The path to the vmx file of your vm self.path_to_vm = path_to_vm + + # username and password for your vm self.username = username self.password = password + self.host = host self.snapshot_path = snapshot_path # todo: handling the logic of snapshot directory + # TODO: get the screen width and height from the vm, or standardize it self.screen_width = 800 self.screen_height = 800 # Define the action and observation space @@ -49,7 +64,8 @@ class DesktopEnv(gym.Env): "text": spaces.MultiDiscrete([128] * 10) # max 10 characters, ASCII }) - self.observation_space = spaces.Box(low=0, high=255, shape=(self.screen_width, self.screen_height, 3), dtype=np.uint8) + self.observation_space = spaces.Box(low=0, high=255, shape=(self.screen_width, self.screen_height, 3), + dtype=np.uint8) # Additional setup self.metadata = {'render.modes': ['rgb_array']} @@ -75,6 +91,7 @@ class DesktopEnv(gym.Env): return mouse_controller, keyboard_controller def _start_emulator(self): + # fixme: check if the vm is running while True: try: output = subprocess.check_output(f"vmrun -T ws list", shell=True, stderr=subprocess.STDOUT) @@ -85,39 +102,43 @@ class DesktopEnv(gym.Env): else: print("Starting VM...") self._execute_command(["vmrun", "-T", "ws", "start", self.path_to_vm]) - time.sleep(5) + time.sleep(10) except subprocess.CalledProcessError as e: print(f"Error executing command: {e.output.decode().strip()}") def _execute_command(self, command: List[str]) -> None: - process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - stdout, stderr = process.communicate() - if process.returncode != 0: - print(f"Error executing command: {command}") - return None - else: - return stdout.decode() + subprocess.run(command, shell=True, stderr=subprocess.STDOUT, timeout=60) def _save_state(self): self._execute_command(["vmrun", "-T", "ws" "snapshot", self.path_to_vm, self.snapshot_path]) def _get_screenshot(self): - image_path = "./screenshot.png" - self._execute_command( - ["vmrun", "-T", "ws", "-gu", self.username, "-gp", self.password, "captureScreen", self.path_to_vm, - image_path]) + # todo: hash it and store it in a temporary directory + + random_uuid = str(uuid.uuid4()) + os.makedirs(os.path.join("tmp", random_uuid), exist_ok=True) + image_path = os.path.join("tmp", random_uuid, "screenshot.png") + + if self.password: + self._execute_command( + ["vmrun", "-T", "ws", "-gu", self.username, "-gp", self.password, "captureScreen", self.path_to_vm, + image_path]) + else: + self._execute_command( + ["vmrun", "-T", "ws", "-gu", self.username, "captureScreen", self.path_to_vm, image_path]) + return image_path def _get_obs(self): screenshot_image_path = self._get_screenshot() - with Image.open(screenshot_image_path) as img: - return np.array(img) + return screenshot_image_path def reset(self): print("Resetting environment...") print("Reverting to snapshot to {}...".format(self.snapshot_path)) self._execute_command(["vmrun", "-T", "ws", "revertToSnapshot", self.path_to_vm, self.snapshot_path]) + time.sleep(5) print("Starting emulator...") self._start_emulator() @@ -165,7 +186,7 @@ class DesktopEnv(gym.Env): elif click == MouseClick.WHEEL_DOWN: self.mouse_controller.scroll_down() elif action_type == Action.MOUSE_MOVE: - self.mouse_controller.mouse_move(x = action['x'], y = action['y']) + self.mouse_controller.mouse_move(x=action['x'], y=action['y']) elif action_type == Action.KEY: key_sequence = ''.join(map(chr, action['key'])) # Convert integer array to string self.keyboard_controller.key(key_sequence) diff --git a/desktop_env/windows_server/main.py b/desktop_env/windows_server/main.py index 56b7fc2..467b40e 100644 --- a/desktop_env/windows_server/main.py +++ b/desktop_env/windows_server/main.py @@ -11,13 +11,9 @@ def execute_command(): # Execute the command without any safety checks. try: - process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = process.communicate() - + subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return jsonify({ 'status': 'success', - 'output': stdout.decode(), - 'error': stderr.decode() }) except Exception as e: return jsonify({ diff --git a/gpt_4v_agent_exp.py b/gpt_4v_agent_exp.py new file mode 100644 index 0000000..fe78970 --- /dev/null +++ b/gpt_4v_agent_exp.py @@ -0,0 +1,47 @@ +import os +from pprint import pprint +from desktop_env.envs.desktop_env import DesktopEnv, Action, MouseClick +from mm_agents.gpt_4v_agent import GPT4v_Agent + + +def gpt_4v_agent(): + api_key = os.environ.get("OPENAI_API_KEY") + agent = GPT4v_Agent(api_key=api_key, instruction="Clear the recycle bin.") + env = DesktopEnv( + path_to_vm=r"""C:\Users\tianbaox\Documents\Virtual Machines\Win10\Win10.vmx""", # automitically load the snapshot and start the vm + # path_to_vm="/home/yuri/vmware/Ubuntu 64-bit/Ubuntu 64-bit.vmx", + username="tianbaox", + password="951753", + # host="192.168.7.128", + host="http://192.168.13.128:5000", + vm_os="windows" + ) + + # reset the environment to certain snapshot + observation = env.reset() + done = False + + while not done: + # todo: action needs to be redesigned, need to support multiple actions at one step + action = agent.predict(obs=observation) + print("Action:", action) + + + # fixme: step not working + observation, reward, done, info = env.step(action) + print("Observation:", observation) + print("Reward:", reward) + print("Info:", info) + + print("================================\n") + + if done: + print("The episode is done.") + break + + env.close() + print("Environment closed.") + + +if __name__ == "__main__": + gpt_4v_agent() diff --git a/mm_agents/gpt_4v_agent.py b/mm_agents/gpt_4v_agent.py index 663bf1e..fdbf4c7 100644 --- a/mm_agents/gpt_4v_agent.py +++ b/mm_agents/gpt_4v_agent.py @@ -1,9 +1,10 @@ import os +import re import base64 from desktop_env.envs.desktop_env import Action, MouseClick -import json5 +import json import requests - +from mm_agents.gpt_4v_prompt import SYS_PROMPT # Function to encode the image def encode_image(image_path): @@ -11,6 +12,32 @@ def encode_image(image_path): return base64.b64encode(image_file.read()).decode('utf-8') +def parse_action_from_string(input_string): + # Search for a JSON string within the input string + matches = re.findall(r'```json\s+(.*?)\s+```', input_string, re.DOTALL) + if matches: + # Assuming there's only one match, parse the JSON string into a dictionary + try: + action_dict = json.loads(matches[0]) + return action_dict + except json.JSONDecodeError as e: + return f"Failed to parse JSON: {e}" + else: + matches = re.findall(r'```\s+(.*?)\s+```', input_string, re.DOTALL) + if matches: + # Assuming there's only one match, parse the JSON string into a dictionary + try: + action_dict = json.loads(matches[0]) + return action_dict + except json.JSONDecodeError as e: + return f"Failed to parse JSON: {e}" + else: + try: + action_dict = json.loads(input_string) + return action_dict + except json.JSONDecodeError as e: + raise ValueError("Invalid response format: " + input_string) + class GPT4v_Agent: def __init__(self, api_key, instruction, model="gpt-4-vision-preview", max_tokens=300): self.instruction = instruction @@ -22,18 +49,13 @@ class GPT4v_Agent: "Authorization": f"Bearer {api_key}" } - # load prompt from file - self.prompt = "" - with open("gpt_4v_prompt.txt", "r") as f: - self.prompt = f.read() - self.trajectory = [ { "role": "system", "content": [ { "type": "text", - "text": self.prompt + "text": SYS_PROMPT }, ] } @@ -79,12 +101,7 @@ class GPT4v_Agent: """ # parse from the response - if response.startswith("```json"): - action = json5.loads(response[7:-3]) - elif response.startswith("```"): - action = json5.loads(response[3:-3]) - else: - action = json5.loads(response) + action = parse_action_from_string(response) # add action into the trajectory self.trajectory.append({ diff --git a/mm_agents/gpt_4v_prompt.txt b/mm_agents/gpt_4v_prompt.py similarity index 76% rename from mm_agents/gpt_4v_prompt.txt rename to mm_agents/gpt_4v_prompt.py index 5fe9c7c..bfe5430 100644 --- a/mm_agents/gpt_4v_prompt.txt +++ b/mm_agents/gpt_4v_prompt.py @@ -1,3 +1,4 @@ +SYS_PROMPT = """ You will act as an agent which follow my instruction and perform desktop computer tasks as instructed. You must have good knowledge of computer and good internet connection. For each step, you will get an observation of an image, which is the screenshot of the computer screen. And you will predict the action of the computer based on the image. Here is the description of the action space: @@ -13,7 +14,7 @@ Firstly you need to predict the class of your action, select from one below: - **TYPE**: type a string on the keyboard Then you need to predict the parameters of your action: -- For MOUSE_MOVE, you need to predict the x and y coordinate of the mouse cursor +- For MOUSE_MOVE, you need to predict the x and y coordinate of the mouse cursor, the left top corner of the screen is (0, 0), the right bottom corner of the screen is (1920, 1080) for example, format as: ``` { @@ -30,7 +31,7 @@ for example, format as: "click_type": "LEFT" } ``` -- For [KEY, KEY_DOWN, KEY_UP, TYPE], you need to choose a(multiple) key(s) from the keyboard, select from [A-Z, 0-9, F1-F12, ESC, TAB, ENTER, SPACE, BACKSPACE, SHIFT, CTRL, ALT, UP, DOWN, LEFT, RIGHT, CAPSLOCK, NUMLOCK, SCROLLLOCK, INSERT, DELETE, HOME, END, PAGEUP, PAGEDOWN]: +- For [KEY, KEY_DOWN, KEY_UP, TYPE], you need to choose a(multiple) key(s) from the keyboard for example, format as: ``` { @@ -49,4 +50,6 @@ for example, format as: } ``` -For every setup, you should only return the action_type and the parameters of your action as a dict, without any other things. \ No newline at end of file +For every step, you should only return the action_type and the parameters of your action as a dict, without any other things. You MUST wrap the dict with backticks (\`). +You MUST choose and ONLY CHOOSE from the action space above, otherwise your action will be considered as invalid and you will get a penalty. +""" \ No newline at end of file diff --git a/screenshot.png b/screenshot.png index 0ea0c0fb40c7a665fa4f0ad48478a61e39ada75a..a0b20d0f20af3fec8b1103c1afda04efdb03c863 100644 GIT binary patch literal 845969 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