From ad3cd3d8866d6b2e68d157064a2839280d4596a3 Mon Sep 17 00:00:00 2001 From: Huge Date: Sat, 23 Nov 2024 23:43:10 +0800 Subject: [PATCH 1/5] add parser --- .gitignore | 1 + config.yaml | 7 ++++++- scripts/document_generation.py | 16 +++------------- scripts/parser.py | 21 +++++++++++++++++++++ scripts/self_explorer.py | 16 +++------------- scripts/task_executor.py | 16 +++------------- 6 files changed, 37 insertions(+), 40 deletions(-) create mode 100644 .gitignore create mode 100644 scripts/parser.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a09c56d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea diff --git a/config.yaml b/config.yaml index d0c1ad6..6341df4 100644 --- a/config.yaml +++ b/config.yaml @@ -1,5 +1,10 @@ -MODEL: "OpenAI" # The type of multi-modal LLM you would like to use to power the AppAgent, must be either OpenAI or Qwen +MODEL: "ollama" # The type of multi-modal LLM you would like to use to power the AppAgent, must be either OpenAI or Qwen +# OLLAMA +OLLAMA_API_BASE: "http://localhost:11434/api/chat" +OLLAMA_API_MODEL: "llava:13b" + +# OPENAI OPENAI_API_BASE: "https://api.openai.com/v1/chat/completions" OPENAI_API_KEY: "sk-" # Set the value to sk-xxx if you host the openai interface for open llm model OPENAI_API_MODEL: "gpt-4-vision-preview" # The only OpenAI model by now that accepts visual input diff --git a/scripts/document_generation.py b/scripts/document_generation.py index 24db53b..4f814cf 100644 --- a/scripts/document_generation.py +++ b/scripts/document_generation.py @@ -8,9 +8,10 @@ import prompts from config import load_config -from model import OpenAIModel, QwenModel from utils import print_with_color +from .parser import parse as model_parse + arg_desc = "AppAgent - Human Demonstration" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) parser.add_argument("--app", required=True) @@ -20,18 +21,7 @@ configs = load_config() -if configs["MODEL"] == "OpenAI": - mllm = OpenAIModel(base_url=configs["OPENAI_API_BASE"], - api_key=configs["OPENAI_API_KEY"], - model=configs["OPENAI_API_MODEL"], - temperature=configs["TEMPERATURE"], - max_tokens=configs["MAX_TOKENS"]) -elif configs["MODEL"] == "Qwen": - mllm = QwenModel(api_key=configs["DASHSCOPE_API_KEY"], - model=configs["QWEN_MODEL"]) -else: - print_with_color(f"ERROR: Unsupported model type {configs['MODEL']}!", "red") - sys.exit() +mllm = model_parse(configs) root_dir = args["root_dir"] work_dir = os.path.join(root_dir, "apps") diff --git a/scripts/parser.py b/scripts/parser.py new file mode 100644 index 0000000..f0955e3 --- /dev/null +++ b/scripts/parser.py @@ -0,0 +1,21 @@ +import sys +from typing import Optional +from model import BaseModel, OpenAIModel, QwenModel +from utils import print_with_color + + +def parse(configs: dict) -> BaseModel: + mllm: Optional[BaseModel] = None + if configs["MODEL"] == "OpenAI": + mllm = OpenAIModel(base_url=configs["OPENAI_API_BASE"], + api_key=configs["OPENAI_API_KEY"], + model=configs["OPENAI_API_MODEL"], + temperature=configs["TEMPERATURE"], + max_tokens=configs["MAX_TOKENS"]) + elif configs["MODEL"] == "Qwen": + mllm = QwenModel(api_key=configs["DASHSCOPE_API_KEY"], + model=configs["QWEN_MODEL"]) + else: + print_with_color(f"ERROR: Unsupported model type {configs['MODEL']}!", "red") + sys.exit() + return mllm \ No newline at end of file diff --git a/scripts/self_explorer.py b/scripts/self_explorer.py index b0e16bb..5ff53e7 100644 --- a/scripts/self_explorer.py +++ b/scripts/self_explorer.py @@ -10,8 +10,9 @@ import prompts from config import load_config from and_controller import list_all_devices, AndroidController, traverse_tree -from model import parse_explore_rsp, parse_reflect_rsp, OpenAIModel, QwenModel +from model import parse_explore_rsp, parse_reflect_rsp from utils import print_with_color, draw_bbox_multi +from .parser import parse as model_parse arg_desc = "AppAgent - Autonomous Exploration" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) @@ -21,18 +22,7 @@ configs = load_config() -if configs["MODEL"] == "OpenAI": - mllm = OpenAIModel(base_url=configs["OPENAI_API_BASE"], - api_key=configs["OPENAI_API_KEY"], - model=configs["OPENAI_API_MODEL"], - temperature=configs["TEMPERATURE"], - max_tokens=configs["MAX_TOKENS"]) -elif configs["MODEL"] == "Qwen": - mllm = QwenModel(api_key=configs["DASHSCOPE_API_KEY"], - model=configs["QWEN_MODEL"]) -else: - print_with_color(f"ERROR: Unsupported model type {configs['MODEL']}!", "red") - sys.exit() +mllm = model_parse(configs) app = args["app"] root_dir = args["root_dir"] diff --git a/scripts/task_executor.py b/scripts/task_executor.py index e092a15..d0d8ec2 100644 --- a/scripts/task_executor.py +++ b/scripts/task_executor.py @@ -10,8 +10,9 @@ import prompts from config import load_config from and_controller import list_all_devices, AndroidController, traverse_tree -from model import parse_explore_rsp, parse_grid_rsp, OpenAIModel, QwenModel +from model import parse_explore_rsp, parse_grid_rsp from utils import print_with_color, draw_bbox_multi, draw_grid +from .parser import parse as model_parse arg_desc = "AppAgent Executor" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) @@ -21,18 +22,7 @@ configs = load_config() -if configs["MODEL"] == "OpenAI": - mllm = OpenAIModel(base_url=configs["OPENAI_API_BASE"], - api_key=configs["OPENAI_API_KEY"], - model=configs["OPENAI_API_MODEL"], - temperature=configs["TEMPERATURE"], - max_tokens=configs["MAX_TOKENS"]) -elif configs["MODEL"] == "Qwen": - mllm = QwenModel(api_key=configs["DASHSCOPE_API_KEY"], - model=configs["QWEN_MODEL"]) -else: - print_with_color(f"ERROR: Unsupported model type {configs['MODEL']}!", "red") - sys.exit() +mllm = model_parse(configs) app = args["app"] root_dir = args["root_dir"] From 8838a4babff09990511c18c08e701a2f97717ff5 Mon Sep 17 00:00:00 2001 From: Huge Date: Sun, 24 Nov 2024 00:53:54 +0800 Subject: [PATCH 2/5] add OllamaModel --- .gitignore | 1 + config.yaml | 2 +- learn.py | 7 +++-- scripts/document_generation.py | 2 +- scripts/model.py | 37 ++++++++++++++++++++++++++ scripts/{parser.py => model_parser.py} | 5 +++- scripts/self_explorer.py | 9 ++++--- scripts/task_executor.py | 2 +- 8 files changed, 56 insertions(+), 9 deletions(-) rename scripts/{parser.py => model_parser.py} (77%) diff --git a/.gitignore b/.gitignore index a09c56d..569c507 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /.idea +/apps diff --git a/config.yaml b/config.yaml index 6341df4..2b271c4 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,4 @@ -MODEL: "ollama" # The type of multi-modal LLM you would like to use to power the AppAgent, must be either OpenAI or Qwen +MODEL: "Ollama" # The type of multi-modal LLM you would like to use to power the AppAgent, must be either OpenAI or Qwen # OLLAMA OLLAMA_API_BASE: "http://localhost:11434/api/chat" diff --git a/learn.py b/learn.py index c922200..f606a8f 100644 --- a/learn.py +++ b/learn.py @@ -8,6 +8,8 @@ arg_desc = "AppAgent - exploration phase" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) parser.add_argument("--app") +parser.add_argument("--user_input") +parser.add_argument("--task_desc") parser.add_argument("--root_dir", default="./") args = vars(parser.parse_args()) @@ -26,7 +28,7 @@ "main interface of the app on your phone.", "yellow") print_with_color("Choose from the following modes:\n1. autonomous exploration\n2. human demonstration\n" "Type 1 or 2.", "blue") -user_input = "" +user_input = args["user_input"] while user_input != "1" and user_input != "2": user_input = input() @@ -36,7 +38,8 @@ app = app.replace(" ", "") if user_input == "1": - os.system(f"python scripts/self_explorer.py --app {app} --root_dir {root_dir}") + task_desc = args["task_desc"] + os.system(f"python scripts/self_explorer.py --app {app} --root_dir {root_dir} --task_desc '{task_desc}'") else: demo_timestamp = int(time.time()) demo_name = datetime.datetime.fromtimestamp(demo_timestamp).strftime(f"demo_{app}_%Y-%m-%d_%H-%M-%S") diff --git a/scripts/document_generation.py b/scripts/document_generation.py index 4f814cf..79659c3 100644 --- a/scripts/document_generation.py +++ b/scripts/document_generation.py @@ -10,7 +10,7 @@ from config import load_config from utils import print_with_color -from .parser import parse as model_parse +from model_parser import parse as model_parse arg_desc = "AppAgent - Human Demonstration" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) diff --git a/scripts/model.py b/scripts/model.py index bf632db..8dee31e 100644 --- a/scripts/model.py +++ b/scripts/model.py @@ -1,3 +1,4 @@ +import json import re from abc import abstractmethod from typing import List @@ -209,3 +210,39 @@ def parse_reflect_rsp(rsp): print_with_color(f"ERROR: an exception occurs while parsing the model response: {e}", "red") print_with_color(rsp, "red") return ["ERROR"] + + +class OllamaModel(BaseModel): + def __init__(self, base_url: str, model: str): + super().__init__() + self.base_url = base_url + self.model = model + + def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): + for idx, img in enumerate(images): + base64_img = encode_image(img) + images[idx] = base64_img + headers = { + "Content-Type": "application/json" + } + payload = { + "model": self.model, + "messages": [ + { + "role": "user", + "content": prompt, + 'images': images + } + ], + "stream": False + } + response = requests.post(self.base_url, headers=headers, json=payload).json() + print('get_model_response:', json.dumps(response, indent=2)) + if "error" not in response: + total_duration = response["total_duration"] + print_with_color(f"Request duration is " + f"{'{0:.2f}'.format(total_duration / 10 ** 9)}s", + "yellow") + else: + return False, response["error"] + return True, response["message"]["content"] diff --git a/scripts/parser.py b/scripts/model_parser.py similarity index 77% rename from scripts/parser.py rename to scripts/model_parser.py index f0955e3..cc79449 100644 --- a/scripts/parser.py +++ b/scripts/model_parser.py @@ -1,6 +1,6 @@ import sys from typing import Optional -from model import BaseModel, OpenAIModel, QwenModel +from model import BaseModel, OpenAIModel, QwenModel, OllamaModel from utils import print_with_color @@ -15,6 +15,9 @@ def parse(configs: dict) -> BaseModel: elif configs["MODEL"] == "Qwen": mllm = QwenModel(api_key=configs["DASHSCOPE_API_KEY"], model=configs["QWEN_MODEL"]) + elif configs["MODEL"] == 'Ollama': + mllm = OllamaModel(base_url=configs["OLLAMA_API_BASE"], + model=configs["OLLAMA_API_MODEL"]) else: print_with_color(f"ERROR: Unsupported model type {configs['MODEL']}!", "red") sys.exit() diff --git a/scripts/self_explorer.py b/scripts/self_explorer.py index 5ff53e7..afa5a28 100644 --- a/scripts/self_explorer.py +++ b/scripts/self_explorer.py @@ -12,11 +12,12 @@ from and_controller import list_all_devices, AndroidController, traverse_tree from model import parse_explore_rsp, parse_reflect_rsp from utils import print_with_color, draw_bbox_multi -from .parser import parse as model_parse +from model_parser import parse as model_parse arg_desc = "AppAgent - Autonomous Exploration" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) parser.add_argument("--app") +parser.add_argument("--task_desc") parser.add_argument("--root_dir", default="./") args = vars(parser.parse_args()) @@ -69,8 +70,10 @@ sys.exit() print_with_color(f"Screen resolution of {device}: {width}x{height}", "yellow") -print_with_color("Please enter the description of the task you want me to complete in a few sentences:", "blue") -task_desc = input() +task_desc = args['task_desc'] +if not task_desc: + print_with_color("Please enter the description of the task you want me to complete in a few sentences:", "blue") + task_desc = input() round_count = 0 doc_count = 0 diff --git a/scripts/task_executor.py b/scripts/task_executor.py index d0d8ec2..4ac0e4e 100644 --- a/scripts/task_executor.py +++ b/scripts/task_executor.py @@ -12,7 +12,7 @@ from and_controller import list_all_devices, AndroidController, traverse_tree from model import parse_explore_rsp, parse_grid_rsp from utils import print_with_color, draw_bbox_multi, draw_grid -from .parser import parse as model_parse +from model_parser import parse as model_parse arg_desc = "AppAgent Executor" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) From 329876f67adb8d1ea0d697b1f483b3217f3d9ed3 Mon Sep 17 00:00:00 2001 From: Huge Date: Mon, 25 Nov 2024 10:34:22 +0800 Subject: [PATCH 3/5] refine prompts for self explorer --- scripts/model.py | 26 ++++++++++++++------------ scripts/prompts.py | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/scripts/model.py b/scripts/model.py index 8dee31e..0603f34 100644 --- a/scripts/model.py +++ b/scripts/model.py @@ -101,10 +101,10 @@ def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): def parse_explore_rsp(rsp): try: - observation = re.findall(r"Observation: (.*?)$", rsp, re.MULTILINE)[0] - think = re.findall(r"Thought: (.*?)$", rsp, re.MULTILINE)[0] - act = re.findall(r"Action: (.*?)$", rsp, re.MULTILINE)[0] - last_act = re.findall(r"Summary: (.*?)$", rsp, re.MULTILINE)[0] + observation = rsp['Observation'] + think = rsp['Thought'] + act = rsp['Action'] + last_act = rsp['Summary'] print_with_color("Observation:", "yellow") print_with_color(observation, "magenta") print_with_color("Thought:", "yellow") @@ -138,7 +138,7 @@ def parse_explore_rsp(rsp): print_with_color(f"ERROR: Undefined act {act_name}!", "red") return ["ERROR"] except Exception as e: - print_with_color(f"ERROR: an exception occurs while parsing the model response: {e}", "red") + print_with_color(f"ERROR: an exception occurs while parsing the model response: {e.with_traceback()}", "red") print_with_color(rsp, "red") return ["ERROR"] @@ -190,8 +190,8 @@ def parse_grid_rsp(rsp): def parse_reflect_rsp(rsp): try: - decision = re.findall(r"Decision: (.*?)$", rsp, re.MULTILINE)[0] - think = re.findall(r"Thought: (.*?)$", rsp, re.MULTILINE)[0] + decision = rsp['Decision'] + think = rsp['Thought'] print_with_color("Decision:", "yellow") print_with_color(decision, "magenta") print_with_color("Thought:", "yellow") @@ -199,7 +199,7 @@ def parse_reflect_rsp(rsp): if decision == "INEFFECTIVE": return [decision, think] elif decision == "BACK" or decision == "CONTINUE" or decision == "SUCCESS": - doc = re.findall(r"Documentation: (.*?)$", rsp, re.MULTILINE)[0] + doc = rsp['Documentation'] print_with_color("Documentation:", "yellow") print_with_color(doc, "magenta") return [decision, think, doc] @@ -234,15 +234,17 @@ def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): 'images': images } ], - "stream": False + "stream": False, + "format": "json", } + # print('get_model_request:\n', prompt) response = requests.post(self.base_url, headers=headers, json=payload).json() - print('get_model_response:', json.dumps(response, indent=2)) + print('get_model_response:\n', json.dumps(response, indent=2)) if "error" not in response: total_duration = response["total_duration"] print_with_color(f"Request duration is " f"{'{0:.2f}'.format(total_duration / 10 ** 9)}s", "yellow") else: - return False, response["error"] - return True, response["message"]["content"] + return False, response['error'] + return True, json.loads(response["message"]["content"]) diff --git a/scripts/prompts.py b/scripts/prompts.py index 5f15ec5..d6371f3 100644 --- a/scripts/prompts.py +++ b/scripts/prompts.py @@ -169,15 +169,21 @@ The task you need to complete is to . Your past actions to proceed with this task are summarized as follows: Now, given the following labeled screenshot, you need to think and call the function needed to proceed with the task. -Your output should include three parts in the given format: +Your output should include the exact four parts (Observation, Thought, Action and Summary) in the following JSON format: +{ + "Observation": "your observation", + "Thought": "your thought", + "Action": "text('text')", + "Summary": "your summary" +} Observation: Thought: Action: +there is nothing to be done, you should output FINISH. You cannot output anything else except a function call defined +above or FINISH in this field. You can only take one action at a time, so please directly call the function> Summary: -You can only take one action at a time, so please directly call the function.""" +""" self_explore_reflect_template = """I will give you screenshots of a mobile app before and after the UI element labeled with the number '' on the first screenshot. The numeric tag of each element is located at @@ -185,20 +191,29 @@ The action was also an attempt to proceed with a larger task, which is to . Your job is to carefully analyze the difference between the two screenshots to determine if the action is in accord with the description above and at -the same time effectively moved the task forward. Your output should be determined based on the following situations: +the same time effectively moved the task forward. Your output should be determined based on the following situations: 1. BACK If you think the action navigated you to a page where you cannot proceed with the given task, you should go back to the previous interface. At the same time, describe the functionality of the UI element concisely in one or two sentences by observing the difference between the two screenshots. Notice that your description of the UI element should focus on the general function. Never include the numeric tag of the UI element in your description. You can use pronouns such as -"the UI element" to refer to the element. Your output should be in the following format: +"the UI element" to refer to the element. Your output should be in the following JSON format: +{ + "Decision": "your decision", + "Thought": "your thought", + "Documentation": "your documentation" +} Decision: BACK Thought: Documentation: 2. INEFFECTIVE If you find the action changed nothing on the screen (screenshots before and after the action are identical), you should continue to interact with other elements on the screen. Notice that if you find the location of the cursor -changed between the two screenshots, then they are not identical. Your output should be in the following format: +changed between the two screenshots, then they are not identical. Your output should be in the following JSON format: +{ + "Decision": "your decision", + "Thought": "your thought" +} Decision: INEFFECTIVE Thought: 3. CONTINUE @@ -207,7 +222,12 @@ describe the functionality of the UI element concisely in one or two sentences by observing the difference between the two screenshots. Notice that your description of the UI element should focus on the general function. Never include the numeric tag of the UI element in your description. You can use pronouns such as "the UI element" to refer to the -element. Your output should be in the following format: +element. Your output should be in the following JSON format: +{ + "Decision": "your decision", + "Thought": "your thought", + "Documentation": "your documentation" +} Decision: CONTINUE Thought: @@ -216,7 +236,12 @@ If you think the action successfully moved the task forward (even though it did not completed the task), you should describe the functionality of the UI element concisely in one or two sentences. Notice that your description of the UI element should focus on the general function. Never include the numeric tag of the UI element in your description. You -can use pronouns such as "the UI element" to refer to the element. Your output should be in the following format: +can use pronouns such as "the UI element" to refer to the element. Your output should be in the following JSON format: +{ + "Decision": "your decision", + "Thought": "your thought", + "Documentation": "your documentation" +} Decision: SUCCESS Thought: Documentation: From 32628b8d2b7da03f3b3719340e1e05695c6c5d9a Mon Sep 17 00:00:00 2001 From: Huge Date: Tue, 26 Nov 2024 03:04:57 +0800 Subject: [PATCH 4/5] optimize prompts --- learn.py | 6 +++--- scripts/model.py | 2 +- scripts/prompts.py | 12 +++++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/learn.py b/learn.py index f606a8f..2473bcf 100644 --- a/learn.py +++ b/learn.py @@ -7,9 +7,9 @@ arg_desc = "AppAgent - exploration phase" parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) -parser.add_argument("--app") -parser.add_argument("--user_input") -parser.add_argument("--task_desc") +parser.add_argument("--app", default="Chrome") +parser.add_argument("--user_input", default="1") +parser.add_argument("--task_desc", default="open the baidu.com website") parser.add_argument("--root_dir", default="./") args = vars(parser.parse_args()) diff --git a/scripts/model.py b/scripts/model.py index 0603f34..22d90cd 100644 --- a/scripts/model.py +++ b/scripts/model.py @@ -239,7 +239,7 @@ def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): } # print('get_model_request:\n', prompt) response = requests.post(self.base_url, headers=headers, json=payload).json() - print('get_model_response:\n', json.dumps(response, indent=2)) + # print('get_model_response:\n', json.dumps(response, indent=2)) if "error" not in response: total_duration = response["total_duration"] print_with_color(f"Request duration is " diff --git a/scripts/prompts.py b/scripts/prompts.py index d6371f3..d357c69 100644 --- a/scripts/prompts.py +++ b/scripts/prompts.py @@ -168,6 +168,7 @@ The task you need to complete is to . Your past actions to proceed with this task are summarized as follows: + Now, given the following labeled screenshot, you need to think and call the function needed to proceed with the task. Your output should include the exact four parts (Observation, Thought, Action and Summary) in the following JSON format: { @@ -180,7 +181,8 @@ Thought: Action: +above or FINISH in this field. You can only take one action at a time, so please directly call the function in python +syntax> Summary: """ @@ -199,7 +201,7 @@ the general function. Never include the numeric tag of the UI element in your description. You can use pronouns such as "the UI element" to refer to the element. Your output should be in the following JSON format: { - "Decision": "your decision", + "Decision": "BACK", "Thought": "your thought", "Documentation": "your documentation" } @@ -211,7 +213,7 @@ should continue to interact with other elements on the screen. Notice that if you find the location of the cursor changed between the two screenshots, then they are not identical. Your output should be in the following JSON format: { - "Decision": "your decision", + "Decision": "INEFFECTIVE", "Thought": "your thought" } Decision: INEFFECTIVE @@ -224,7 +226,7 @@ numeric tag of the UI element in your description. You can use pronouns such as "the UI element" to refer to the element. Your output should be in the following JSON format: { - "Decision": "your decision", + "Decision": "CONTINUE", "Thought": "your thought", "Documentation": "your documentation" } @@ -238,7 +240,7 @@ element should focus on the general function. Never include the numeric tag of the UI element in your description. You can use pronouns such as "the UI element" to refer to the element. Your output should be in the following JSON format: { - "Decision": "your decision", + "Decision": "SUCCESS", "Thought": "your thought", "Documentation": "your documentation" } From 4d3b5d2586710025312de04020ac6c76c4e274d3 Mon Sep 17 00:00:00 2001 From: Huge Date: Mon, 17 Feb 2025 00:31:11 +0800 Subject: [PATCH 5/5] cv_example --- .gitignore | 2 ++ config.yaml | 2 +- learn.py | 2 +- scripts/cv_example.py | 48 ++++++++++++++++++++++++++++++++++++++++++ scripts/image.jpg | Bin 0 -> 47467 bytes scripts/model.py | 19 +++++++++++------ scripts/prompts.py | 16 +++++++------- 7 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 scripts/cv_example.py create mode 100644 scripts/image.jpg diff --git a/.gitignore b/.gitignore index 569c507..9c6d4e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /.idea /apps +/venv +__pycache__ \ No newline at end of file diff --git a/config.yaml b/config.yaml index 2b271c4..f84d81f 100644 --- a/config.yaml +++ b/config.yaml @@ -2,7 +2,7 @@ MODEL: "Ollama" # The type of multi-modal LLM you would like to use to power th # OLLAMA OLLAMA_API_BASE: "http://localhost:11434/api/chat" -OLLAMA_API_MODEL: "llava:13b" +OLLAMA_API_MODEL: "llama3.2-vision:11b" # OPENAI OPENAI_API_BASE: "https://api.openai.com/v1/chat/completions" diff --git a/learn.py b/learn.py index 2473bcf..a73feea 100644 --- a/learn.py +++ b/learn.py @@ -9,7 +9,7 @@ parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=arg_desc) parser.add_argument("--app", default="Chrome") parser.add_argument("--user_input", default="1") -parser.add_argument("--task_desc", default="open the baidu.com website") +parser.add_argument("--task_desc", default="open the baidu.com website with a browser") parser.add_argument("--root_dir", default="./") args = vars(parser.parse_args()) diff --git a/scripts/cv_example.py b/scripts/cv_example.py new file mode 100644 index 0000000..edb57af --- /dev/null +++ b/scripts/cv_example.py @@ -0,0 +1,48 @@ +import json +from config import load_config +from model_parser import parse as model_parse + +configs = load_config('../config.yaml') +mllm = model_parse(configs) + +form = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "objects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of the object." + }, + "color": { + "type": "string", + "description": "The color of the object." + } + }, + "required": ["type", "color"] + } + }, + "count": { + "type": "integer", + "description": "The number of objects present in this image." + } + }, + "required": ["objects", "count"] +} + +prompt = """ +Please count the objects present in this picture? Describe the type, color of each object. +Please respond with the following json format: +%s +""".format(json.dumps(form, indent=2)) + +prompt = """What is inside this image? +""" + +status, rsp = mllm.get_model_response(prompt, ['./image.jpg'], '') +print(status) +print(json.dumps(rsp,indent=2)) diff --git a/scripts/image.jpg b/scripts/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c560fe774f5974777e3d0bc67e08b5e603ad0d3 GIT binary patch literal 47467 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 (bool, str): + def get_model_response(self, prompt: str, images: List[str], form='json') -> (bool, str): pass @@ -28,7 +28,7 @@ def __init__(self, base_url: str, api_key: str, model: str, temperature: float, self.temperature = temperature self.max_tokens = max_tokens - def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): + def get_model_response(self, prompt: str, images: List[str], form='json') -> (bool, str): content = [ { "type": "text", @@ -77,7 +77,7 @@ def __init__(self, api_key: str, model: str): self.model = model dashscope.api_key = api_key - def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): + def get_model_response(self, prompt: str, images: List[str], form='json') -> (bool, str): content = [{ "text": prompt }] @@ -218,7 +218,7 @@ def __init__(self, base_url: str, model: str): self.base_url = base_url self.model = model - def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): + def get_model_response(self, prompt: str, images: List[str], form='json') -> (bool, str): for idx, img in enumerate(images): base64_img = encode_image(img) images[idx] = base64_img @@ -235,11 +235,13 @@ def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): } ], "stream": False, - "format": "json", + "format": form, } + if len(form) == 0: + del payload['format'] # print('get_model_request:\n', prompt) response = requests.post(self.base_url, headers=headers, json=payload).json() - # print('get_model_response:\n', json.dumps(response, indent=2)) + print('get_model_response:\n', json.dumps(response, indent=2)) if "error" not in response: total_duration = response["total_duration"] print_with_color(f"Request duration is " @@ -247,4 +249,7 @@ def get_model_response(self, prompt: str, images: List[str]) -> (bool, str): "yellow") else: return False, response['error'] - return True, json.loads(response["message"]["content"]) + content = response["message"]["content"] + if form: + content = json.loads(content) + return True, content diff --git a/scripts/prompts.py b/scripts/prompts.py index d357c69..9b67006 100644 --- a/scripts/prompts.py +++ b/scripts/prompts.py @@ -139,7 +139,7 @@ given a screenshot of a smartphone app. The interactive UI elements on the screenshot are labeled with numeric tags starting from 1. -You can call the following functions to interact with those labeled elements to control the smartphone: +You can call the following functions (in python syntax) to interact with those labeled elements to control the smartphone: 1. tap(element: int) This function is used to tap an UI element shown on the smartphone screen. @@ -147,10 +147,10 @@ A simple use case can be tap(5), which taps the UI element labeled with the number 5. 2. text(text_input: str) -This function is used to insert text input in an input field/box. text_input is the string you want to insert and must -be wrapped with double quotation marks. A simple use case can be text("Hello, world!"), which inserts the string -"Hello, world!" into the input area on the smartphone screen. This function is only callable when you see a keyboard -showing in the lower half of the screen. +This function is used to insert text input in an input field/box when a keyboard shows up below the screen. text_input +is the string you want to insert and must be wrapped with double quotation marks. A simple use case can be +text("Hello, world!"), which inserts the string "Hello, world!" into the input area on the smartphone screen. This +function is ONLY callable when you see a KEYBOARD showing in the lower half of the screen. 3. long_press(element: int) This function is used to long press an UI element shown on the smartphone screen. @@ -179,10 +179,10 @@ } Observation: Thought: -Action: +above or FINISH in this field. You can only take one action at a time, so please directly call the +function> Summary: """