From a4bc2186fd3b1f2fc724a3713d7f4af6e855f3c4 Mon Sep 17 00:00:00 2001 From: wenhao Date: Thu, 22 Feb 2024 14:51:03 +0800 Subject: [PATCH 01/29] fix minor example bug --- docs/sphinx_doc/source/tutorial/103-example.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx_doc/source/tutorial/103-example.md b/docs/sphinx_doc/source/tutorial/103-example.md index 068b42ab7..08ead7cf5 100644 --- a/docs/sphinx_doc/source/tutorial/103-example.md +++ b/docs/sphinx_doc/source/tutorial/103-example.md @@ -52,7 +52,7 @@ from agentscope.agents import DialogAgent, UserAgent agentscope.init(model_configs="./openai_model_configs.json") # Create a dialog agent and a user agent -dialogAgent = DialogAgent(name="assistant", model_config_name="gpt-4") +dialogAgent = DialogAgent(name="assistant", model_config_name="gpt-4", sys_prompt="You are a helpful ai assistant") userAgent = UserAgent() ``` From b9f381d23a13e981fd0873b6adc4871a58ebd01e Mon Sep 17 00:00:00 2001 From: wenhao Date: Thu, 22 Feb 2024 16:52:19 +0800 Subject: [PATCH 02/29] fix single typo --- docs/sphinx_doc/source/tutorial/201-agent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx_doc/source/tutorial/201-agent.md b/docs/sphinx_doc/source/tutorial/201-agent.md index f4d513e53..76b6400b6 100644 --- a/docs/sphinx_doc/source/tutorial/201-agent.md +++ b/docs/sphinx_doc/source/tutorial/201-agent.md @@ -2,7 +2,7 @@ # Customizing Your Own Agent -This tutorial helps you to understand the `Agent` in mode depth and navigate through the process of crafting your own custom agent with AgentScope. We start by introducing the fundamental abstraction called `AgentBase`, which serves as the base class to maintain the general behaviors of all agents. Then, we will go through the *AgentPool*, an ensemble of pre-built, specialized agents, each designed with a specific purpose in mind. Finally, we will demonstrate how to customize your own agent, ensuring it fits the needs of your project. +This tutorial helps you to understand the `Agent` in more depth and navigate through the process of crafting your own custom agent with AgentScope. We start by introducing the fundamental abstraction called `AgentBase`, which serves as the base class to maintain the general behaviors of all agents. Then, we will go through the *AgentPool*, an ensemble of pre-built, specialized agents, each designed with a specific purpose in mind. Finally, we will demonstrate how to customize your own agent, ensuring it fits the needs of your project. ## Understanding `AgentBase` From e88ffad9155fa5e14ef7406cebd33ed980080709 Mon Sep 17 00:00:00 2001 From: wenhao Date: Thu, 22 Feb 2024 17:31:18 +0800 Subject: [PATCH 03/29] fix single typo in 105-logging.md --- docs/sphinx_doc/source/tutorial/105-logging.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/sphinx_doc/source/tutorial/105-logging.md b/docs/sphinx_doc/source/tutorial/105-logging.md index dd6cbbc56..913fc92cc 100644 --- a/docs/sphinx_doc/source/tutorial/105-logging.md +++ b/docs/sphinx_doc/source/tutorial/105-logging.md @@ -79,8 +79,7 @@ agentscope.web.init( ) ``` -By this way, you can see all the running instances and projects in `http://127. -0.0.1:5000` as follows: +By this way, you can see all the running instances and projects in `http://127.0.0.1:5000` as follows: ![webui](https://img.alicdn.com/imgextra/i3/O1CN01kpHFkn1HpeYEkn60I_!!6000000000807-0-tps-3104-1849.jpg) From 4e5c79db6bbafe0ca299ab493ca6ed27f75b1058 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Fri, 12 Apr 2024 15:38:00 +0800 Subject: [PATCH 04/29] adding more service functions --- src/agentscope/service/__init__.py | 6 ++ .../service/execute_code/exec_shell.py | 57 ++++++++++++++++++ src/agentscope/service/file/common.py | 59 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/agentscope/service/execute_code/exec_shell.py diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 50e9e89a6..d9eb55bb1 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -3,6 +3,7 @@ from loguru import logger from .execute_code.exec_python import execute_python_code +from .execute_code.exec_shell import execute_shell_command from .file.common import ( create_file, delete_file, @@ -10,6 +11,8 @@ create_directory, delete_directory, move_directory, + show_directory, + get_current_directory, ) from .file.text import read_text_file, write_text_file from .file.json import read_json_file, write_json_file @@ -35,12 +38,15 @@ def get_help() -> None: __all__ = [ "execute_python_code", + "execute_shell_command", "create_file", "delete_file", "move_file", "create_directory", "delete_directory", "move_directory", + "show_directory", + "get_current_directory", "read_text_file", "write_text_file", "read_json_file", diff --git a/src/agentscope/service/execute_code/exec_shell.py b/src/agentscope/service/execute_code/exec_shell.py new file mode 100644 index 000000000..aa01c97a5 --- /dev/null +++ b/src/agentscope/service/execute_code/exec_shell.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +"""Service to execute shell commands.""" +import subprocess +from agentscope.service.service_status import ServiceExecStatus +from agentscope.service.service_response import ServiceResponse + + +def execute_shell_command(command: str) -> ServiceResponse: + """ + Executes a given shell command(using subprocess) and returns the execution + result as a string. + + Args: + command (str): The shell command to execute. + + Returns: + ServiceResponse: Contains either the output from the shell command as a + string if sucessful, or an error message include the error type. + + Note: + Use any bash/shell commands you want (e.g. find, grep, cat, ls, cd), + but note that : + 1. interactive session commands (e.g. python, vim) are NOT supported, + so please do not invoke them. + 2. be VERY CAREFUL when using commands that will + change/edit the files current directory (e.g. rm, sed). + ... + """ + try: + result = subprocess.run( + command, + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=result.stdout.strip() if result.stdout else "Success.", + ) + except subprocess.CalledProcessError as e: + error_message = ( + e.stderr.strip() + if e.stderr + else "An error occurred \ + while executing the command." + ) + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) + except Exception as e: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=str(e), + ) diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index 82ec9eed9..4c37c061f 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -2,6 +2,7 @@ """ Common operators for file and directory. """ import os import shutil +from typing import List from agentscope.utils.common import write_file from agentscope.service.service_response import ServiceResponse @@ -201,3 +202,61 @@ def move_directory( status=ServiceExecStatus.ERROR, content=error_message, ) + + +def show_directory(directory_path: str) -> ServiceResponse: + """ + Show/List the contents of a directory. i.e. ls -a + + Args: + directory_path (`str`): + The path of the directory to show. + + Returns: + `ServiceResponse`: The results contain a list of direcotry contents, + or an error message if any, including the error type. + """ + if not os.path.exists(directory_path): + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content="FileNotFoundError: The directory does not exist.", + ) + if not os.path.isdir(directory_path): + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content="FileNotFoundError: The path is not a directory", + ) + try: + ls_result: List[str] = os.listdir(directory_path) + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=f"The listdir results are: {ls_result}", + ) + except Exception as e: + error_message = f"{e.__class__.__name__}: {e}" + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) + + +def get_current_directory() -> ServiceResponse: + """ + Get the current working directory path. + + Returns: + `ServiceResponse`: The current working directory path, or an error + message if any, including the error type. + """ + try: + cwd = os.getcwd() + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=cwd, + ) + except Exception as e: + error_message = f"{e.__class__.__name__}: {e}" + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) From cc82278b708c06b3b38dcf0996a7b6ac36271442 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Fri, 12 Apr 2024 15:47:28 +0800 Subject: [PATCH 05/29] dir test --- tests/operate_file_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/operate_file_test.py b/tests/operate_file_test.py index 7c1b5a2e9..514e7a004 100644 --- a/tests/operate_file_test.py +++ b/tests/operate_file_test.py @@ -15,6 +15,8 @@ write_text_file, read_json_file, write_json_file, + show_directory, + get_current_directory, ) from agentscope.service.service_status import ServiceExecStatus @@ -62,12 +64,19 @@ def test_file(self) -> None: def test_dir(self) -> None: """Execute dir test.""" + + is_success = get_current_directory().status + self.assertEqual(is_success, ServiceExecStatus.SUCCESS) + is_success = create_directory(self.dir_name).status self.assertEqual(is_success, ServiceExecStatus.SUCCESS) is_success = move_directory(self.dir_name, self.moved_dir_name).status self.assertEqual(is_success, ServiceExecStatus.SUCCESS) + is_success = show_directory(self.moved_dir_name).status + self.assertEqual(is_success, ServiceExecStatus.SUCCESS) + is_success = delete_directory(self.moved_dir_name).status self.assertEqual(is_success, ServiceExecStatus.SUCCESS) From 48b7a7d1b652bd53177994666cd5e74a8bd4caf4 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Fri, 12 Apr 2024 17:35:55 +0800 Subject: [PATCH 06/29] init shell command test --- tests/execute_shell_command_test.py | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/execute_shell_command_test.py diff --git a/tests/execute_shell_command_test.py b/tests/execute_shell_command_test.py new file mode 100644 index 000000000..7581792ff --- /dev/null +++ b/tests/execute_shell_command_test.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" Python code execution test.""" +import unittest +import sys + +from agentscope.service import execute_shell_command +from agentscope.service import ServiceExecStatus + + +class ExecuteShellCommandTest(unittest.TestCase): + """ + Python code execution test. + """ + + def setUp(self) -> None: + """Init for ExecuteShellCommandTest.""" + + # Basic expression + self.arg0 = "touch tmp_a.text" + + self.arg1 = "echo 'Helloworld' >> tmp_a.txt" + + self.args2 = "cat tmp_a.txt" + + self.args3 = "rm tmp_a.txt" + + def test(self) -> None: + """test command""" + result = execute_shell_command( + command=self.arg0, + ) + assert result.status == ServiceExecStatus.SUCCESS + assert result.content == "Success." + + result = execute_shell_command( + command=self.arg1, + ) + assert result.status == ServiceExecStatus.SUCCESS + assert result.content == "Success." + + result = execute_shell_command( + command=self.arg2, + ) + assert result.status == ServiceExecStatus.SUCCESS + assert result.content == "helloworld" + + result = execute_shell_command( + command=self.arg3, + ) + assert result.status == ServiceExecStatus.SUCCESS + assert result.content == "Success." + + result = execute_shell_command( + command=self.arg3, + ) + assert result.status == ServiceExecStatus.ERROR + + +if __name__ == "__main__": + unittest.main() From 84df79f883fb1f1761b6a693ca3df68acbf5eb8f Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Fri, 12 Apr 2024 17:48:05 +0800 Subject: [PATCH 07/29] init shell command test --- tests/execute_shell_command_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/execute_shell_command_test.py b/tests/execute_shell_command_test.py index 7581792ff..5b2b8e310 100644 --- a/tests/execute_shell_command_test.py +++ b/tests/execute_shell_command_test.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """ Python code execution test.""" import unittest -import sys from agentscope.service import execute_shell_command from agentscope.service import ServiceExecStatus From faa99e69492c8d7aa536d3dc588405aa4f5d9bb3 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Thu, 18 Apr 2024 11:20:03 +0800 Subject: [PATCH 08/29] exec shell --- src/agentscope/service/execute_code/exec_shell.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/agentscope/service/execute_code/exec_shell.py b/src/agentscope/service/execute_code/exec_shell.py index aa01c97a5..194762c6e 100644 --- a/src/agentscope/service/execute_code/exec_shell.py +++ b/src/agentscope/service/execute_code/exec_shell.py @@ -18,10 +18,11 @@ def execute_shell_command(command: str) -> ServiceResponse: string if sucessful, or an error message include the error type. Note: - Use any bash/shell commands you want (e.g. find, grep, cat, ls, cd), + Use any bash/shell commands you want (e.g. find, grep, cat, ls), but note that : - 1. interactive session commands (e.g. python, vim) are NOT supported, - so please do not invoke them. + 1. interactive session commands (e.g. python, vim) or commands that + change current state (e.g. cd that change the current directory) + are NOT supported yet, so please do not invoke them. 2. be VERY CAREFUL when using commands that will change/edit the files current directory (e.g. rm, sed). ... From 88d0cd06a7d43d40a4d91f5d5bca05d27fe7d624 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Thu, 18 Apr 2024 16:00:42 +0800 Subject: [PATCH 09/29] precomit --- src/agentscope/service/execute_code/exec_shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agentscope/service/execute_code/exec_shell.py b/src/agentscope/service/execute_code/exec_shell.py index 194762c6e..ef0f282f3 100644 --- a/src/agentscope/service/execute_code/exec_shell.py +++ b/src/agentscope/service/execute_code/exec_shell.py @@ -20,7 +20,7 @@ def execute_shell_command(command: str) -> ServiceResponse: Note: Use any bash/shell commands you want (e.g. find, grep, cat, ls), but note that : - 1. interactive session commands (e.g. python, vim) or commands that + 1. interactive session commands (e.g. python, vim) or commands that change current state (e.g. cd that change the current directory) are NOT supported yet, so please do not invoke them. 2. be VERY CAREFUL when using commands that will From 0d628b2fd96e93ed18e1a541bdd58a051a5e5282 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Mon, 22 Apr 2024 10:27:04 +0800 Subject: [PATCH 10/29] change in init and common --- src/agentscope/service/__init__.py | 3 ++ src/agentscope/service/file/common.py | 61 ++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 877091996..0f3db6d2c 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -70,3 +70,6 @@ def get_help() -> None: "parse_html_to_text", "download_from_url", ] + + +from .file.common import write_file_by_line, read_file_by_line diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index 4c37c061f..d3e12cc80 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -15,7 +15,7 @@ def create_file(file_path: str, content: str = "") -> ServiceResponse: Args: file_path (`str`): - The path where the file will be created. + The path where the file will be created. content (`str`): Content to write into the file. @@ -260,3 +260,62 @@ def get_current_directory() -> ServiceResponse: status=ServiceExecStatus.ERROR, content=error_message, ) + + +def write_file_by_line(file_path, content, start=0, end=-1): + try: + mode = 'w' if not os.path.exists(file_path) else 'r+' + insert = content.split('\n') + with open(file_path, mode, encoding='utf-8') as file: + if mode != 'w': + all_lines = file.readlines() + new_file = [''] if start == 0 else all_lines[:start] + new_file += [i + '\n' for i in insert] + new_file += [''] if end == -1 else all_lines[end:] + else: + new_file = insert + + file.seek(0) + file.writelines(new_file) + file.truncate() + obs = f'WRITE OPERATION:\nYou have written to "{file_path}" on these lines: {start}:{end}.' + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=obs + ''.join(new_file), + ) + except Exception as e: + error_message = f"{e.__class__.__name__}: {e}" + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) + +## Modified FROM Open-Devin/SWE-agent +def read_file_by_line(file_path, start=0, end=-1): + start = max(start, 0) + try: + with open(file_path, "r", encoding="utf-8") as file: + if end == -1: + if start == 0: + code_view = file.read() + else: + all_lines = file.readlines() + code_slice = all_lines[start:] + code_view = ''.join(code_slice) + else: + all_lines = file.readlines() + num_lines = len(all_lines) + begin = max(0, min(start, num_lines - 2)) + end = -1 if end > num_lines else max(begin + 1, end) + code_slice = all_lines[begin:end] + code_view = ''.join(code_slice) + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=f"Now read the file: {code_view}", + ) + except Exception as e: + error_message = f"{e.__class__.__name__}: {e}" + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) \ No newline at end of file From 2d7fe0b7f285f7f9d2ba97e4257eae91abee6430 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Mon, 22 Apr 2024 14:18:30 +0800 Subject: [PATCH 11/29] fix typo in tests --- tests/execute_shell_command_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/execute_shell_command_test.py b/tests/execute_shell_command_test.py index 5b2b8e310..bf281a648 100644 --- a/tests/execute_shell_command_test.py +++ b/tests/execute_shell_command_test.py @@ -19,9 +19,9 @@ def setUp(self) -> None: self.arg1 = "echo 'Helloworld' >> tmp_a.txt" - self.args2 = "cat tmp_a.txt" + self.arg2 = "cat tmp_a.txt" - self.args3 = "rm tmp_a.txt" + self.arg3 = "rm tmp_a.txt" def test(self) -> None: """test command""" From cd38a4f4bf8d752b0a87405b09e8813330cb72f0 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Mon, 22 Apr 2024 14:29:40 +0800 Subject: [PATCH 12/29] fix typo in tests --- tests/execute_shell_command_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/execute_shell_command_test.py b/tests/execute_shell_command_test.py index bf281a648..ae7fda6b2 100644 --- a/tests/execute_shell_command_test.py +++ b/tests/execute_shell_command_test.py @@ -41,7 +41,7 @@ def test(self) -> None: command=self.arg2, ) assert result.status == ServiceExecStatus.SUCCESS - assert result.content == "helloworld" + assert result.content == "Helloworld" result = execute_shell_command( command=self.arg3, From b5a3123fd079d480c3e9840906bc2e156594f358 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Mon, 22 Apr 2024 15:01:50 +0800 Subject: [PATCH 13/29] skip shell command tasks on windows --- tests/execute_shell_command_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/execute_shell_command_test.py b/tests/execute_shell_command_test.py index ae7fda6b2..206e3bcba 100644 --- a/tests/execute_shell_command_test.py +++ b/tests/execute_shell_command_test.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ Python code execution test.""" import unittest +import platform from agentscope.service import execute_shell_command from agentscope.service import ServiceExecStatus @@ -24,7 +25,9 @@ def setUp(self) -> None: self.arg3 = "rm tmp_a.txt" def test(self) -> None: - """test command""" + """test command, skip on windows""" + if platform.system() == "Windows": + return result = execute_shell_command( command=self.arg0, ) From 4813913270d6b3b7e6ec898f6d347c06879b5f89 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Tue, 23 Apr 2024 16:06:07 +0800 Subject: [PATCH 14/29] resolve comments --- src/agentscope/service/__init__.py | 4 ++-- tests/operate_file_test.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 877091996..b52023514 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -11,7 +11,7 @@ create_directory, delete_directory, move_directory, - show_directory, + list_directory_content, get_current_directory, ) from .file.text import read_text_file, write_text_file @@ -50,7 +50,7 @@ def get_help() -> None: "create_directory", "delete_directory", "move_directory", - "show_directory", + "list_directory_content", "get_current_directory", "read_text_file", "write_text_file", diff --git a/tests/operate_file_test.py b/tests/operate_file_test.py index 514e7a004..c0c663867 100644 --- a/tests/operate_file_test.py +++ b/tests/operate_file_test.py @@ -15,7 +15,7 @@ write_text_file, read_json_file, write_json_file, - show_directory, + list_directory_content, get_current_directory, ) from agentscope.service.service_status import ServiceExecStatus @@ -74,7 +74,7 @@ def test_dir(self) -> None: is_success = move_directory(self.dir_name, self.moved_dir_name).status self.assertEqual(is_success, ServiceExecStatus.SUCCESS) - is_success = show_directory(self.moved_dir_name).status + is_success = list_directory_content(self.moved_dir_name).status self.assertEqual(is_success, ServiceExecStatus.SUCCESS) is_success = delete_directory(self.moved_dir_name).status From 7878005ac4b471680e47dcdf0e8ac46b4b1d99a5 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Tue, 23 Apr 2024 16:06:49 +0800 Subject: [PATCH 15/29] resolve comments --- src/agentscope/service/execute_code/exec_shell.py | 3 +-- src/agentscope/service/file/common.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/agentscope/service/execute_code/exec_shell.py b/src/agentscope/service/execute_code/exec_shell.py index ef0f282f3..ffde21b9d 100644 --- a/src/agentscope/service/execute_code/exec_shell.py +++ b/src/agentscope/service/execute_code/exec_shell.py @@ -7,8 +7,7 @@ def execute_shell_command(command: str) -> ServiceResponse: """ - Executes a given shell command(using subprocess) and returns the execution - result as a string. + Executes a given shell command. Args: command (str): The shell command to execute. diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index 4c37c061f..adeb5a0ad 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -204,9 +204,9 @@ def move_directory( ) -def show_directory(directory_path: str) -> ServiceResponse: +def list_directory_content(directory_path: str) -> ServiceResponse: """ - Show/List the contents of a directory. i.e. ls -a + List the contents of a directory. i.e. ls -a Args: directory_path (`str`): @@ -230,7 +230,7 @@ def show_directory(directory_path: str) -> ServiceResponse: ls_result: List[str] = os.listdir(directory_path) return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content=f"The listdir results are: {ls_result}", + content=ls_result, ) except Exception as e: error_message = f"{e.__class__.__name__}: {e}" From 66f82b99697acc3750e0be68491be3e53ef7cbd4 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Tue, 23 Apr 2024 16:38:36 +0800 Subject: [PATCH 16/29] add files --- src/agentscope/service/__init__.py | 3 +- src/agentscope/service/file/common.py | 82 +++++++++++++++++++-------- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 0f3db6d2c..d9d72b069 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -72,4 +72,5 @@ def get_help() -> None: ] -from .file.common import write_file_by_line, read_file_by_line +from .file.common import write_file, read_file +from .file.check import exec_py_linting diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index d3e12cc80..815215a79 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -4,7 +4,6 @@ import shutil from typing import List -from agentscope.utils.common import write_file from agentscope.service.service_response import ServiceResponse from agentscope.service.service_status import ServiceExecStatus @@ -29,7 +28,19 @@ def create_file(file_path: str, content: str = "") -> ServiceResponse: status=ServiceExecStatus.ERROR, content="FileExistsError: The file already exists.", ) - return write_file(content, file_path) + try: + with open(file_path, "w", encoding="utf-8") as file: + file.write("") + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content="Success", + ) + except Exception as e: + error_message = f"{e.__class__.__name__}: {e}" + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) def delete_file(file_path: str) -> ServiceResponse: @@ -262,26 +273,37 @@ def get_current_directory() -> ServiceResponse: ) -def write_file_by_line(file_path, content, start=0, end=-1): +def write_file( + file_path: str, content: str, start_line: int = 0, end_line: int = -1 +): + """ + Write content to a file by replacing the current lines between and with . Default start_line = 0 and end_line = -1. Calling this with no args will replace the whole file, so besure to use this with caution when writing to a file that already exists. + + Args: + file_path (`str`): The path to the file to write to. + content (`str`): The content to write to the file. + start_line (`Optional[int]`, defaults to `0`): The start line of the file to be replace with . + end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be replace with . + """ try: - mode = 'w' if not os.path.exists(file_path) else 'r+' - insert = content.split('\n') - with open(file_path, mode, encoding='utf-8') as file: - if mode != 'w': + mode = "w" if not os.path.exists(file_path) else "r+" + insert = content.split("\n") + with open(file_path, mode, encoding="utf-8") as file: + if mode != "w": all_lines = file.readlines() - new_file = [''] if start == 0 else all_lines[:start] - new_file += [i + '\n' for i in insert] - new_file += [''] if end == -1 else all_lines[end:] + new_file = [""] if start_line == 0 else all_lines[:start_line] + new_file += [i + "\n" for i in insert] + new_file += [""] if end_line == -1 else all_lines[end_line:] else: new_file = insert file.seek(0) file.writelines(new_file) file.truncate() - obs = f'WRITE OPERATION:\nYou have written to "{file_path}" on these lines: {start}:{end}.' + obs = f'WRITE OPERATION:\nYou have written to "{file_path}" on these lines: {start_line}:{end_line}.' return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content=obs + ''.join(new_file), + content=obs + "".join(new_file), ) except Exception as e: error_message = f"{e.__class__.__name__}: {e}" @@ -290,32 +312,42 @@ def write_file_by_line(file_path, content, start=0, end=-1): content=error_message, ) -## Modified FROM Open-Devin/SWE-agent -def read_file_by_line(file_path, start=0, end=-1): - start = max(start, 0) + +def read_file(file_path: str, start_line: int = 0, end_line: int = -1): + """ + Shows a given file's contents starting from up to . Default: start_line = 0, end_line = -1. By default the whole file will be read. + + Args: + file_path (`str`): The path to the file to read. + start_line (`Optional[int]`, defaults to `0`): The start line of the file to be read. + end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be read. + """ + start_line = max(start_line, 0) try: with open(file_path, "r", encoding="utf-8") as file: - if end == -1: - if start == 0: + if end_line == -1: + if start_line == 0: code_view = file.read() else: all_lines = file.readlines() - code_slice = all_lines[start:] - code_view = ''.join(code_slice) + code_slice = all_lines[start_line:] + code_view = "".join(code_slice) else: all_lines = file.readlines() num_lines = len(all_lines) - begin = max(0, min(start, num_lines - 2)) - end = -1 if end > num_lines else max(begin + 1, end) - code_slice = all_lines[begin:end] - code_view = ''.join(code_slice) + begin = max(0, min(start_line, num_lines - 2)) + end_line = ( + -1 if end_line > num_lines else max(begin + 1, end_line) + ) + code_slice = all_lines[begin:end_line] + code_view = "".join(code_slice) return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content=f"Now read the file: {code_view}", + content=f"{code_view}", ) except Exception as e: error_message = f"{e.__class__.__name__}: {e}" return ServiceResponse( status=ServiceExecStatus.ERROR, content=error_message, - ) \ No newline at end of file + ) From 1cc008fc87b2d0026c0c360bbf8882065869d5db Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Tue, 23 Apr 2024 17:00:46 +0800 Subject: [PATCH 17/29] init SWE-agent example --- examples/swe_agent/swe_agent.py | 266 ++++++++++++++++++++++++ examples/swe_agent/swe_agent_prompts.py | 120 +++++++++++ src/agentscope/service/__init__.py | 10 +- src/agentscope/service/file/common.py | 23 +- 4 files changed, 408 insertions(+), 11 deletions(-) create mode 100644 examples/swe_agent/swe_agent.py create mode 100644 examples/swe_agent/swe_agent_prompts.py diff --git a/examples/swe_agent/swe_agent.py b/examples/swe_agent/swe_agent.py new file mode 100644 index 000000000..ce22616a0 --- /dev/null +++ b/examples/swe_agent/swe_agent.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- +"""An agent class that partially implements the SWE-agent. +SWE-agent is an agent designed for solving github issues. +More details can be found in https://swe-agent.com/. + +Here we partially implement and modified the SWE-agent, +try to make it work with wider range of tasks then just fixing github issues. +""" + +from agentscope.agents import AgentBase +from agentscope.message import Msg +from agentscope.models import ResponseParser, ResponseParsingError +from typing import List, Callable +import json +from agentscope.service import ( + ServiceFactory, + execute_shell_command, + write_file, + read_file, + exec_py_linting, +) + +from swe_agent_prompts import ( + get_system_prompt, + get_context_prompt, + get_step_prompt, +) + + +def prepare_func_prompt(function: Callable) -> str: + func, desc = ServiceFactory.get(function) + func_name = desc["function"]["name"] + func_desc = desc["function"]["description"] + args_desc = desc["function"]["parameters"]["properties"] + + args_list = [f"{func_name}: {func_desc}"] + for args_name, args_info in args_desc.items(): + if "type" in args_info: + args_line = ( + f'\t{args_name} ({args_info["type"]}): ' + f'{args_info.get("description", "")}' + ) + else: + args_line = f'\t{args_name}: {args_info.get("description", "")}' + args_list.append(args_line) + + func_prompt = "\n".join(args_list) + return func_prompt + + +COMMANDS_DISCRIPTION_DICT = { + "exit": "exit: Executed when the current task is complete, takes no arguments", # noqa + "scroll_up": "scroll_up: Scrolls up the current open file, will scroll up and show you the 100 lines above your current lines, takes no arguments", # noqa + "scroll_down": "scroll_down: Scrolls down the current open file, will scroll down and show you the 100 lines below your current lines'takes no arguments", # noqa + "goto": "goto: This will take you directly to the line and show you the 100 lines below it. \n line_num (int): The line number to go to.", # noqa +} + +COMMANDS_DISCRIPTION_DICT["write_file"] = prepare_func_prompt(write_file) +COMMANDS_DISCRIPTION_DICT["read_file"] = prepare_func_prompt(read_file) +COMMANDS_DISCRIPTION_DICT["execute_shell_command"] = prepare_func_prompt( + execute_shell_command, +) +COMMANDS_DISCRIPTION_DICT["exec_py_linting"] = prepare_func_prompt( + exec_py_linting, +) + + +ERROR_INFO_PROMPT = """Your response is not a JSON object, and cannot be parsed by `json.loads` in parse function: +## Your Response: +[YOUR RESPONSE BEGIN] +{response} +[YOUR RESPONSE END] + +## Error Information: +{error_info} + +Analyze the reason, and re-correct your response in the correct format.""" # pylint: disable=all # noqa + + +def count_file_lines(file_path: str) -> int: + with open(file_path, "r") as file: + lines = file.readlines() + return len(lines) + + +class SWEAgent(AgentBase): + """ + The SWE-agent + """ + + def __init__( + self, + name: str, + model_config_name: str, + ) -> None: + """ """ + super().__init__( + name=name, + model_config_name=model_config_name, + ) + + self.memory_window = 6 + self.max_retries = 2 + self.running_memory: List[str] = [] + self.cur_file: str = "" + self.cur_line: int = 0 + self.cur_file_content: str = "" + + self.main_goal = "" + self.commands_prompt = "" + self.get_commands_prompt() + + def get_current_file_content(self) -> None: + """ + Get the current file content. + """ + if self.cur_file == "": + return + start_line = self.cur_line - 50 + if start_line < 0: + start_line = 0 + end_line = self.cur_line + 50 + if end_line > count_file_lines(self.cur_file): + end_line = -1 + read_res = read_file(self.cur_file, start_line, end_line) + self.cur_file_content = read_res.content + + def step(self) -> Msg: + """ + Step the SWE-agent. + """ + message_list = [] + + # construct system prompt + system_prompt = get_system_prompt(self.commands_prompt) + message_list.append(Msg("user", system_prompt, role="system")) + + # construct context prompt, i.e. previous actions + context_prompt = get_context_prompt( + self.running_memory, + self.memory_window, + ) + message_list.append(Msg("user", context_prompt, role="user")) + + # construct step prompt for this instance + self.get_current_file_content() + step_prompt = get_step_prompt( + self.main_goal, + self.cur_file, + self.cur_line, + self.cur_file_content, + ) + message_list.append(Msg("user", step_prompt, role="user")) + + # get response from agent + try: + in_prompt = self.model.format(message_list) + res = self.model( + in_prompt, + parse_func=ResponseParser.to_dict, + max_retries=1, + ).json + + except ResponseParsingError as e: + response_msg = Msg(self.name, e.response.text, "assistant") + self.speak(response_msg) + + # Re-correct by model itself + error_msg = Msg( + name="system", + content={ + "action": {"name": "error"}, + "error_msg": ERROR_INFO_PROMPT.format( + parse_func=ResponseParser.to_dict, + error_info=e.error_info, + response=e.response.text, + ), + }, + role="system", + ) + self.speak(error_msg) + # continue + self.running_memory.append(error_msg) + return error_msg + + msg_res = Msg(self.name, res, role="assistant") + + self.speak( + Msg(self.name, json.dumps(res, indent=4), role="assistant"), + ) + + # parse and execute action + action = res.get("action") + + obs = self.prase_command(res["action"]) + + # add msg to context windows + self.running_memory.append(str(action, obs)) + return msg_res + + def reply(self, x: dict = None) -> dict: + action_name = None + self.main_goal = x.content + while not action_name == "exit": + msg = self.step() + action_name = msg.content["action"]["name"] + return msg + + def prase_command(self, command_call: dict) -> str: + command_name = command_call["name"] + command_args = command_call["arguments"] + if command_name == "exit": + return "Current task finished, exitting." + if command_name in ["goto", "scroll_up", "scroll_down"]: + if command_name == "goto": + line = command_call["arguments"]["line_num"] + command_str = f"Going to {self.cur_file} line \ + {command_args['line_mum']}." + command_failed_str = f"Failed to go to {self.cur_file} \ + line {command_args['line_num']}" + if command_name == "scroll_up": + line = self.cur_line - 100 + if line < 0: + line = 0 + command_str = ( + f"Scrolling up from file {self.cur_file} to line {line}." + ) + command_failed_str = ( + f"Failed to scroll up {self.cur_file} to line {line}" + ) + if command_name == "scroll_down": + line = self.cur_line + 100 + if line > count_file_lines(self.cur_file): + line = count_file_lines(self.cur_file) + command_str = ( + f"Scrolling down from file {self.cur_file} to line {line}." + ) + command_failed_str = ( + f"Failed to scrool down {self.cur_file} to line {line}" + ) + read_status = read_file(self.cur_file, line, line + 100) + if read_status.status == "success": + self.cur_line = line + obs = read_status.content + return f"{command_str}. Observe file content: {obs}" + else: + return command_failed_str + if command_name == "execute_shell_command": + return execute_shell_command(**command_args).content + if command_name == "write_file": + self.cur_file = command_args["file_path"] + self.cur_line = command_args.get("start_line", 0) + write_status = write_file(**command_args) + return write_status.content + if command_name == "read_file": + self.cur_file = command_args["file_path"] + self.cur_line = command_args.get("start_line", 0) + read_status = read_file(**command_args) + return read_status.content + if command_name == "exec_py_linting": + return exec_py_linting(**command_args).content + return "No such command" + + def get_commands_prompt(self) -> None: + for name, desc in COMMANDS_DISCRIPTION_DICT.items(): + self.commands_prompt += f"{name}: {desc}\n" diff --git a/examples/swe_agent/swe_agent_prompts.py b/examples/swe_agent/swe_agent_prompts.py new file mode 100644 index 000000000..ef7a742c0 --- /dev/null +++ b/examples/swe_agent/swe_agent_prompts.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# pylint: disable=C0301 +"""The SWE-agent relay heavily on it's prompts. +This file contains the neccessary prompts for the SWE-agent. +Some prompts are taken and modified from the original SWE-agent repo +or the SWE-agent implementation from Open-Devin. +""" + +WINDOW = 100 + + +def get_system_prompt(command_prompt: str) -> str: + """ + Get the system prompt for SWE-agent. + """ + return f""" + SETTING: + You are an autonomous coding agent, here to perform codding tasks given the instruction. + You have been designed with a wide range of programming tasks, from code editing and debugging to testing and deployment. + You have access to a variety of tools and commands that you can use to help you solve problems efficiently. + + You're working directly in the command line with a special interface. + + The special interface consists of a file editor that shows you {WINDOW} lines of a file at a time. + In addition to typical bash commands, you can also use the following commands to help you navigate and edit files. + + COMMANDS: + {command_prompt} + + Please note that THE WRITE COMMAND REQUIRES PROPER INDENTATION. + If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! + Indentation is important and code that is not indented correctly will fail and require fixing before it can be run. + + If you'd like to issue two commands at once, PLEASE DO NOT DO THAT! Please instead first submit just the first command, and then after receiving a response you'll be able to issue the second command. + You're free to use any other bash commands you want (e.g. find, grep, cat, ls) in addition to the special commands listed above. + + However, the environment does NOT support interactive session commands (e.g. vim, python), so please do not invoke them. + + Your Response should always be a valid JSON string that can be parsed. + + {RESPONSE_FORMAT_PROMPT} + + """ # noqa + + +RESPONSE_FORMAT_PROMPT = """ +## Response Format: +You should respond with a JSON object in the following format. +{ + "thought": "what you thought", + "action": {"name": "{command name}", "arguments": {"{argument1 name}": xxx, "{argument2 name}": xxx}} +} + +For Example: +{ + "thought": "First I'll start by using ls to see what files are in the current directory. Then maybe we can look at some relevant files to see what they look like.", + "action": {"name": "execute_shell_command", "arguments": {"command": "ls -a"}} +} +OUTPUT the JSON format and ONLY OUTPUT the JSON format. +Your Response should always be a valid JSON string that can be parsed. +""" # noqa + + +def get_step_prompt( + task: str, + file: str, + line: int, + current_file_content: str, +) -> str: + """ + Get the step prompt for SWE-agent. + """ + return f""" + We're currently perform the following coding task. Here's the original task description from the user. + {task} + + CURRENT + Open File: {file} on line {line} + + Current File Content: + {current_file_content} + + You can use these commands with the current file: + Navigation: `scroll_up`, `scroll_down`, and `goto ` + + + INSTRUCTIONS: + + 1. If you run a command and it doesn't work, try running a different command. A command that did not work once will not work the second time unless you modify it! + + 2. If you open a file and need to get to an area around a specific line that is not in the first 100 lines, say line 583, don't just use the scroll_down command multiple times. Instead, use the goto 583 command. It's much quicker. + + 3. Always make sure to look at the currently open file and the current working directory (which appears right after the currently open file). The currently open file might be in a different directory! Note that some commands, such as 'write_file' and 'read_file', open files, so they might change the current open file. + + 4. When editing files, it is easy to accidentally specify a wrong line number or to write code with incorrect indentation. Always check the code after you issue an edit to make sure that it reflects what you wanted to accomplish. If it didn't, issue another command to fix it. + + 5. After modifying python files, you can run `exec_py_linting` to check for errors. If there are errors, fix them and repeat the previous step. + + NOTE THAT THIS ENVIRONMENT DOES NOT SUPPORT INTERACTIVE SESSION COMMANDS, such as "vim" or "python", or "python3". So DONOT execute them by running `execute_shell_command` with `python` command or `python3` command. + If you want to check whether a python file is valid, you can use `exec_py_linting` to check for errors. + + {RESPONSE_FORMAT_PROMPT} +""" # noqa + + +def get_context_prompt(memory: list, window: int) -> str: + """ + Get the context prompt for the given memory and window. + """ + res = f"These are your past {window} actions:\n" + window_size = window if len(memory) > window else len(memory) + cur_mems = memory[-window_size:] + res += "===== Previous Actions =====\n" + for idx, mem in enumerate(cur_mems): + res += f"\nMemory {idx}:\n{mem}\n" + res += "======= End Actions =======\n" + res += "Use these memories to provide additional context to \ + the problem you are solving.\nRemember that you have already \ + completed these steps so you do not need to perform them again." + return res diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 5965a54c1..d971b0787 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -13,7 +13,10 @@ move_directory, list_directory_content, get_current_directory, + write_file, + read_file, ) +from .file.check import exec_py_linting from .file.text import read_text_file, write_text_file from .file.json import read_json_file, write_json_file from .sql_query.mysql import query_mysql @@ -69,8 +72,7 @@ def get_help() -> None: "load_web", "parse_html_to_text", "download_from_url", + "write_file", + "read_file", + "exec_py_linting", ] - - -from .file.common import write_file, read_file -from .file.check import exec_py_linting diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index 77e03b158..9ce64a644 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=C0301 """ Common operators for file and directory. """ import os import shutil @@ -30,7 +31,7 @@ def create_file(file_path: str, content: str = "") -> ServiceResponse: ) try: with open(file_path, "w", encoding="utf-8") as file: - file.write("") + file.write(content) return ServiceResponse( status=ServiceExecStatus.SUCCESS, content="Success", @@ -274,8 +275,11 @@ def get_current_directory() -> ServiceResponse: def write_file( - file_path: str, content: str, start_line: int = 0, end_line: int = -1 -): + file_path: str, + content: str, + start_line: int = 0, + end_line: int = -1, +) -> ServiceResponse: """ Write content to a file by replacing the current lines between and with . Default start_line = 0 and end_line = -1. Calling this with no args will replace the whole file, so besure to use this with caution when writing to a file that already exists. @@ -284,7 +288,7 @@ def write_file( content (`str`): The content to write to the file. start_line (`Optional[int]`, defaults to `0`): The start line of the file to be replace with . end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be replace with . - """ + """ # noqa try: mode = "w" if not os.path.exists(file_path) else "r+" insert = content.split("\n") @@ -300,7 +304,8 @@ def write_file( file.seek(0) file.writelines(new_file) file.truncate() - obs = f'WRITE OPERATION:\nYou have written to "{file_path}" on these lines: {start_line}:{end_line}.' + obs = f'WRITE OPERATION:\nYou have written to "{file_path}" \ + on these lines: {start_line}:{end_line}.' return ServiceResponse( status=ServiceExecStatus.SUCCESS, content=obs + "".join(new_file), @@ -313,7 +318,11 @@ def write_file( ) -def read_file(file_path: str, start_line: int = 0, end_line: int = -1): +def read_file( + file_path: str, + start_line: int = 0, + end_line: int = -1, +) -> ServiceResponse: """ Shows a given file's contents starting from up to . Default: start_line = 0, end_line = -1. By default the whole file will be read. @@ -321,7 +330,7 @@ def read_file(file_path: str, start_line: int = 0, end_line: int = -1): file_path (`str`): The path to the file to read. start_line (`Optional[int]`, defaults to `0`): The start line of the file to be read. end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be read. - """ + """ # noqa start_line = max(start_line, 0) try: with open(file_path, "r", encoding="utf-8") as file: From 95e39090132e3f6827874c7e4f8a80499f790955 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Tue, 23 Apr 2024 19:38:53 +0800 Subject: [PATCH 18/29] add missing file --- src/agentscope/service/file/check.py | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/agentscope/service/file/check.py diff --git a/src/agentscope/service/file/check.py b/src/agentscope/service/file/check.py new file mode 100644 index 000000000..b86249d2b --- /dev/null +++ b/src/agentscope/service/file/check.py @@ -0,0 +1,47 @@ +import subprocess + +from agentscope.service.service_response import ServiceResponse +from agentscope.service.service_status import ServiceExecStatus + +def exec_py_linting(file_path: str) -> ServiceResponse: + """ + Executes flake8 linting on the given .py file with specified checks and + returns the linting result. + + Args: + file_path (`str`): The path to the Python file to lint. + + Returns: + ServiceResponse: Contains either the output from the flake8 command as + a string if successful, or an error message including the error type. + """ + command = f"flake8 --isolated --select=F821,F822,F831,E111,E112,E113,E999,E902 {file_path}" + + try: + result = subprocess.run( + command, + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=result.stdout.strip() if result.stdout else "No lint errors found.", + ) + except subprocess.CalledProcessError as e: + error_message = ( + e.stderr.strip() + if e.stderr + else "An error occurred while linting the file." + ) + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) + except Exception as e: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=str(e), + ) \ No newline at end of file From 21e1d8130884e45f54b43e718454f0708cb3f411 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Tue, 23 Apr 2024 19:42:50 +0800 Subject: [PATCH 19/29] add doc string --- src/agentscope/service/file/check.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/agentscope/service/file/check.py b/src/agentscope/service/file/check.py index b86249d2b..96c675e05 100644 --- a/src/agentscope/service/file/check.py +++ b/src/agentscope/service/file/check.py @@ -1,22 +1,28 @@ +# -*- coding: utf-8 -*- +""" +Tools for checking files, such as linting and formatting. +""" import subprocess from agentscope.service.service_response import ServiceResponse from agentscope.service.service_status import ServiceExecStatus + def exec_py_linting(file_path: str) -> ServiceResponse: """ Executes flake8 linting on the given .py file with specified checks and returns the linting result. - + Args: file_path (`str`): The path to the Python file to lint. - + Returns: - ServiceResponse: Contains either the output from the flake8 command as + ServiceResponse: Contains either the output from the flake8 command as a string if successful, or an error message including the error type. """ - command = f"flake8 --isolated --select=F821,F822,F831,E111,E112,E113,E999,E902 {file_path}" - + command = f"flake8 --isolated --select=F821,F822,F831,\ + E111,E112,E113,E999,E902 {file_path}" + try: result = subprocess.run( command, @@ -28,7 +34,9 @@ def exec_py_linting(file_path: str) -> ServiceResponse: ) return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content=result.stdout.strip() if result.stdout else "No lint errors found.", + content=result.stdout.strip() + if result.stdout + else "No lint errors found.", ) except subprocess.CalledProcessError as e: error_message = ( @@ -44,4 +52,4 @@ def exec_py_linting(file_path: str) -> ServiceResponse: return ServiceResponse( status=ServiceExecStatus.ERROR, content=str(e), - ) \ No newline at end of file + ) From a36de71a7ef1853b6375f303eef17474cb4340d1 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Wed, 8 May 2024 13:43:59 +0800 Subject: [PATCH 20/29] add example notebook --- examples/swe_agent/main.ipynb | 331 ++++++++++++++++++++++++++ examples/swe_agent/swe_agent.py | 5 +- setup.py | 1 + src/agentscope/service/file/common.py | 5 +- 4 files changed, 339 insertions(+), 3 deletions(-) create mode 100644 examples/swe_agent/main.ipynb diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb new file mode 100644 index 000000000..a62178b48 --- /dev/null +++ b/examples/swe_agent/main.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example with SWE-agent\n", + "\n", + "SWE-agent is an agent designed for solving github issues.\n", + "More details can be found in https://swe-agent.com/.\n", + "\n", + "In the example here, we partially implement the SWE-agent, and provide a simple example of how to use the implemented SWE agent to fix a bug in a python file.\n", + "\n", + "## Prerequisites\n", + "\n", + "- Follow [READMD.md](https://github.com/modelscope/agentscope) to install AgentScope. \n", + "- Prepare a model configuration. AgentScope supports both local deployed model services (CPU or GPU) and third-party services. More details and example model configurations please refer to our [tutorial]\n", + "- Understand the ServiceFactory module and how to use it to pre-process the tool functions for LLMs. You can refer to the [ReAct agent example](../conversation_with_react_agent/main.ipynb).\n", + "\n", + "## Note\n", + "\n", + "- The example is tested with the following models. For other models, you may need to adjust the prompt.\n", + " - gpt-4\n", + "- How to enable agents with stronger programming capabilities remains an open challenge, and the current implementations are not perfect. Please feel free to explore it yourself." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1: Initialize the AgentScope environment and SWE-agent\n", + "\n", + "Here we init the agentscope environment and load the SWE-agent.\n", + "\n", + "The code of SWE-agent is in `swe_agent.py`, and the related prompts are in `swe_agent_prompts.py`.\n", + "\n", + "If you are interested in the details, please refer to the code and the origianl SWE-agent repo [here](https://github.com/princeton-nlp/SWE-agent)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from swe_agent import SWEAgent\n", + "\n", + "import agentscope\n", + "from agentscope.models import load_model_by_config_name\n", + "agentscope.init(\n", + " model_configs=\"xxx\", # fill you model config file path here\n", + ")\n", + "loaded_model = load_model_by_config_name('gpt-4')\n", + "\n", + "agent = SWEAgent(\n", + " name=\"assistant\",\n", + " model_config_name='gpt-4',\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 2: Create the code to be processed by the SWE-agent\n", + "\n", + "Here we use the `write_file` function to write the following code into `gcd.py`.\n", + "The code here is a wrong implementation of the Greatest Common Divisor (GCD) algorithm.\n", + "We will ask the SWE-agent to correct it in our next step." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'status': ,\n", + " 'content': 'WRITE OPERATION:\\nYou have written to \"gcd.py\" on these lines: 0:-1.\\ndef gcd(a, b):\\n if a == 0:\\n return b\\n while a != 0:\\n a, b = b, a\\n return b\\n\\ndef lcm(a, b):\\n return (a * b) // gcd(a, b)\\n\\n# testing on GCD and LCM functions\\nprint(\"GCD of 12 and 18 is:\", gcd(12, 18))\\nprint(\"LCM of 12 and 18 is:\", lcm(12, 18))\\n\\n'}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from agentscope.service import write_file\n", + "\n", + "# here the gcd code is written in a wrong way\n", + "wrong_gcd_text = \"\"\"\n", + "def gcd(a, b):\n", + " if a == 0:\n", + " return b\n", + " while a != 0:\n", + " a, b = b, a\n", + " return b\n", + "\n", + "def lcm(a, b):\n", + " return (a * b) // gcd(a, b)\n", + "\n", + "# testing on GCD and LCM functions\n", + "print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n", + "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", + "\"\"\"\n", + "\n", + "write_file(\"gcd.py\", wrong_gcd_text)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 3: Ask the SWE-agent to correct the code\n", + "\n", + "Here we simple ask the SWE-agent to correct the code in `gcd.py`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "user: The file in gcd.py is not returning the correct answer, can you fix the file for me?.\n", + "assistant: {\n", + " \"thought\": \"First, I need to view the file gcd.py to understand the bug the user is referring to. I'll read the file gcd.py using the `read_file` command.\",\n", + " \"action\": {\n", + " \"name\": \"read_file\",\n", + " \"arguments\": {\n", + " \"file_path\": \"gcd.py\"\n", + " }\n", + " }\n", + "}\n", + "assistant: \n", + "====Observation====\n", + "\n", + "def gcd(a, b):\n", + " if a == 0:\n", + " return b\n", + " while a != 0:\n", + " a, b = b, a\n", + " return b\n", + "\n", + "def lcm(a, b):\n", + " return (a * b) // gcd(a, b)\n", + "\n", + "# testing on GCD and LCM functions\n", + "print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n", + "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", + "\n", + "\n", + "assistant: {\n", + " \"thought\": \"I can see that the gcd algorithm implemented in the gcd function within gcd.py file is incorrect. In the while loop of the gcd function, a and b should repeatedly subtract one from the other until a is zero but it's subtracting a from itself which would result in always zero. I'll correct this by modifying the line 5: 'a, b = b, a' to 'a, b = b % a, a'. \",\n", + " \"action\": {\n", + " \"name\": \"write_file\",\n", + " \"arguments\": {\n", + " \"start_line\": 3,\n", + " \"end_line\": 6,\n", + " \"content\": \" if a == 0:\\n return b\\n while b != 0:\\n a, b = b % a, a\",\n", + " \"file_path\": \"gcd.py\"\n", + " }\n", + " }\n", + "}\n", + "assistant: \n", + "====Observation====\n", + "WRITE OPERATION:\n", + "You have written to \"gcd.py\" on these lines: 3:6.\n", + "def gcd(a, b):\n", + " if a == 0:\n", + " if a == 0:\n", + " return b\n", + " while b != 0:\n", + " a, b = b % a, a\n", + "\n", + "def lcm(a, b):\n", + " return (a * b) // gcd(a, b)\n", + "\n", + "# testing on GCD and LCM functions\n", + "print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n", + "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", + "\n", + "\n", + "assistant: {\n", + " \"thought\": \"The gcd function implemented is incorrect and it's causing the lcm function to fail as well. In the gcd function, while loop is incorrect. We should calculate gcd based on the fact that GCD of two numbers a and b is same as the GCD of b and a mod b. 'a mod b' can be calculated using the '%' operator. When b becomes 0, a will hold the largest common divisor (GCD). Let's fix this.\",\n", + " \"action\": {\n", + " \"name\": \"write_file\",\n", + " \"arguments\": {\n", + " \"start_line\": 2,\n", + " \"end_line\": 6,\n", + " \"content\": \" if b == 0:\\n return a\\n while b:\\n a, b = b, a % b\\n return a\",\n", + " \"file_path\": \"gcd.py\"\n", + " }\n", + " }\n", + "}\n", + "assistant: \n", + "====Observation====\n", + "WRITE OPERATION:\n", + "You have written to \"gcd.py\" on these lines: 2:6.\n", + "def gcd(a, b):\n", + " if b == 0:\n", + " return a\n", + " while b:\n", + " a, b = b, a % b\n", + " return a\n", + "\n", + "def lcm(a, b):\n", + " return (a * b) // gcd(a, b)\n", + "\n", + "# testing on GCD and LCM functions\n", + "print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n", + "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", + "\n", + "\n", + "assistant: {\n", + " \"thought\": \"It seems the gcd function in gcd.py has been fixed and any changes required have already been made in my previous actions. I should run the python file to determine if it produces expected results. To do that, I can use the 'execute_shell_command' by running the 'python3' command with 'gcd.py'.\",\n", + " \"action\": {\n", + " \"name\": \"execute_shell_command\",\n", + " \"arguments\": {\n", + " \"command\": \"python3 gcd.py\"\n", + " }\n", + " }\n", + "}\n", + "assistant: \n", + "====Observation====\n", + "GCD of 12 and 18 is: 6\n", + "LCM of 12 and 18 is: 36\n", + "assistant: {\n", + " \"thought\": \"The gcd.py file is fixed correctly and no further actions are needed.\",\n", + " \"action\": {\n", + " \"name\": \"exit\",\n", + " \"arguments\": {}\n", + " }\n", + "}\n", + "assistant: \n", + "====Observation====\n", + "Current task finished, exitting.\n" + ] + } + ], + "source": [ + "from loguru import logger\n", + "from agentscope.message import Msg\n", + "\n", + "mss = Msg(\n", + " name=\"user\", \n", + " content=\"The file in gcd.py is not returning the correct answer, can you fix the file for me?\", \n", + " role=\"user\"\n", + ")\n", + "logger.chat(mss)\n", + "answer_mss = agent(mss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After a few iterations, the assistant response:\n", + "```text\n", + "\n", + "assistant: {\n", + " \"thought\": \"It seems the gcd function in gcd.py has been fixed and any changes required have already been made in my previous actions. I should run the python file to determine if it produces expected results. To do that, I can use the 'execute_shell_command' by running the 'python3' command with 'gcd.py'.\",\n", + " \"action\": {\n", + " \"name\": \"execute_shell_command\",\n", + " \"arguments\": {\n", + " \"command\": \"python3 gcd.py\"\n", + " }\n", + " }\n", + "}\n", + "assistant: \n", + "====Observation====\n", + "GCD of 12 and 18 is: 6\n", + "LCM of 12 and 18 is: 36\n", + "\n", + "\n", + "assistant: {\n", + " \"thought\": \"The gcd.py file is fixed correctly and no further actions are needed.\",\n", + " \"action\": {\n", + " \"name\": \"exit\",\n", + " \"arguments\": {}\n", + " }\n", + "}\n", + "assistant: \n", + "====Observation====\n", + "Current task finished, exitting.\n", + "\n", + "```\n", + "\n", + "The SWE-agent finish the job successfully, and the code is now working fine." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Above we shown a example of how to use the SWEAgent to fix code errors.\n", + "Although the design of the SWE-agent is primarily aimed at addressing GitHub issues, with modifications, it can also be utilized for more general programming tasks.\n", + "\n", + "Currently, how to program using an agent remains a challenging open question, with the efficacy of agent programming potentially influenced by factors such as prompt construction, model capabilities, and the complexity of the task at hand. Here we just provide an interesting toy example. \n", + "\n", + "We encourage users to experiment by altering the prompts within this example or by assigning different tasks to the agent, among other methods of exploration. Please feel free to experiment and explore on your own. The AgentScope team will continue to provide updates, enhancing the capabilities of the Programming Agents in the future!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "datajuicer", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/swe_agent/swe_agent.py b/examples/swe_agent/swe_agent.py index ce22616a0..350bbb047 100644 --- a/examples/swe_agent/swe_agent.py +++ b/examples/swe_agent/swe_agent.py @@ -193,9 +193,12 @@ def step(self) -> Msg: action = res.get("action") obs = self.prase_command(res["action"]) + self.speak( + Msg(self.name, "\n====Observation====\n" + obs, role="assistant"), + ) # add msg to context windows - self.running_memory.append(str(action, obs)) + self.running_memory.append(str(action) + str(obs)) return msg_res def reply(self, x: dict = None) -> dict: diff --git a/setup.py b/setup.py index 860934d3d..2ed7a0e92 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ "Flask==3.0.0", "Flask-Cors==4.0.0", "Flask-SocketIO==5.3.6", + "flake8" # TODO: move into other requires "dashscope==1.14.1", "openai>=1.3.0", diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index 9ce64a644..dc6439fb7 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -287,7 +287,7 @@ def write_file( file_path (`str`): The path to the file to write to. content (`str`): The content to write to the file. start_line (`Optional[int]`, defaults to `0`): The start line of the file to be replace with . - end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be replace with . + end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be replace with . end_line = -1 means the end of the file, otherwise it should be a positive integer indicating the line number. """ # noqa try: mode = "w" if not os.path.exists(file_path) else "r+" @@ -297,7 +297,8 @@ def write_file( all_lines = file.readlines() new_file = [""] if start_line == 0 else all_lines[:start_line] new_file += [i + "\n" for i in insert] - new_file += [""] if end_line == -1 else all_lines[end_line:] + last_line = end_line + 1 + new_file += [""] if end_line == -1 else all_lines[last_line:] else: new_file = insert From 47e9a589e5874b91a0176f0ae9c3d04f857defd6 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Wed, 8 May 2024 13:46:40 +0800 Subject: [PATCH 21/29] fix typos and format --- examples/swe_agent/main.ipynb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb index a62178b48..fe6a1f261 100644 --- a/examples/swe_agent/main.ipynb +++ b/examples/swe_agent/main.ipynb @@ -9,7 +9,7 @@ "SWE-agent is an agent designed for solving github issues.\n", "More details can be found in https://swe-agent.com/.\n", "\n", - "In the example here, we partially implement the SWE-agent, and provide a simple example of how to use the implemented SWE agent to fix a bug in a python file.\n", + "In the example here, we partially implement the SWE-agent, and provide a simple example of how to use the implemented SWE-agent to fix a bug in a python file.\n", "\n", "## Prerequisites\n", "\n", @@ -260,6 +260,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "### Conlusion\n", + "\n", "After a few iterations, the assistant response:\n", "```text\n", "\n", @@ -298,7 +300,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Above we shown a example of how to use the SWEAgent to fix code errors.\n", + "Above we shown a example of how to use the SWE-agent to fix code errors.\n", "Although the design of the SWE-agent is primarily aimed at addressing GitHub issues, with modifications, it can also be utilized for more general programming tasks.\n", "\n", "Currently, how to program using an agent remains a challenging open question, with the efficacy of agent programming potentially influenced by factors such as prompt construction, model capabilities, and the complexity of the task at hand. Here we just provide an interesting toy example. \n", From 2f056228f6a8937f899bfdcc933cb43907af5298 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Thu, 9 May 2024 11:45:25 +0800 Subject: [PATCH 22/29] fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d629d3741..a3fc085a3 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ "Flask==3.0.0", "Flask-Cors==4.0.0", "Flask-SocketIO==5.3.6", - "flake8" + "flake8", # TODO: move into other requires "dashscope==1.14.1", "openai>=1.3.0", From 903d49de6888725728133d10588e72e9de0136db Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Wed, 15 May 2024 10:53:02 +0800 Subject: [PATCH 23/29] fix issues and update parser --- examples/swe_agent/main.ipynb | 73 +++------ examples/swe_agent/swe_agent.py | 29 ++-- examples/swe_agent/swe_agent_prompts.py | 2 +- examples/swe_agent/swe_agent_service_func.py | 147 +++++++++++++++++++ src/agentscope/service/__init__.py | 6 - src/agentscope/service/file/check.py | 55 ------- src/agentscope/service/file/common.py | 89 ----------- 7 files changed, 187 insertions(+), 214 deletions(-) create mode 100644 examples/swe_agent/swe_agent_service_func.py delete mode 100644 src/agentscope/service/file/check.py diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb index fe6a1f261..f22cb0586 100644 --- a/examples/swe_agent/main.ipynb +++ b/examples/swe_agent/main.ipynb @@ -6,10 +6,11 @@ "source": [ "# Example with SWE-agent\n", "\n", - "SWE-agent is an agent designed for solving github issues.\n", - "More details can be found in https://swe-agent.com/.\n", + "SWE-agent(SoftWare Engineering Agent) is an agent designed for solving real world software engineering problems, such as fixing github issues.\n", + "More details can be found in their [homepage](https://swe-agent.com/) and related [github repo](https://swe-agent.com/).\n", "\n", "In the example here, we partially implement the SWE-agent, and provide a simple example of how to use the implemented SWE-agent to fix a bug in a python file.\n", + "You should note that currently how to enable agents with stronger programming capabilities remains an open challenge, and the performance of the paritially implemented SWE-agent is not guaranteed.\n", "\n", "## Prerequisites\n", "\n", @@ -48,7 +49,7 @@ "import agentscope\n", "from agentscope.models import load_model_by_config_name\n", "agentscope.init(\n", - " model_configs=\"xxx\", # fill you model config file path here\n", + " model_configs=\"xxx.json\", # fill you model config file path here\n", ")\n", "loaded_model = load_model_by_config_name('gpt-4')\n", "\n", @@ -87,7 +88,7 @@ } ], "source": [ - "from agentscope.service import write_file\n", + "from swe_agent_service_func import write_file\n", "\n", "# here the gcd code is written in a wrong way\n", "wrong_gcd_text = \"\"\"\n", @@ -127,9 +128,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "user: The file in gcd.py is not returning the correct answer, can you fix the file for me?.\n", + "user: The file in gcd.py is not returning the correct answer, can you fix the file for me?\n", "assistant: {\n", - " \"thought\": \"First, I need to view the file gcd.py to understand the bug the user is referring to. I'll read the file gcd.py using the `read_file` command.\",\n", + " \"thought\": \"To debug the gcd.py file, first we need to read the contents of the file to understand the existing program logic.\",\n", " \"action\": {\n", " \"name\": \"read_file\",\n", " \"arguments\": {\n", @@ -155,45 +156,21 @@ "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", "\n", "\n", - "assistant: {\n", - " \"thought\": \"I can see that the gcd algorithm implemented in the gcd function within gcd.py file is incorrect. In the while loop of the gcd function, a and b should repeatedly subtract one from the other until a is zero but it's subtracting a from itself which would result in always zero. I'll correct this by modifying the line 5: 'a, b = b, a' to 'a, b = b % a, a'. \",\n", - " \"action\": {\n", - " \"name\": \"write_file\",\n", - " \"arguments\": {\n", - " \"start_line\": 3,\n", - " \"end_line\": 6,\n", - " \"content\": \" if a == 0:\\n return b\\n while b != 0:\\n a, b = b % a, a\",\n", - " \"file_path\": \"gcd.py\"\n", - " }\n", - " }\n", - "}\n", - "assistant: \n", - "====Observation====\n", - "WRITE OPERATION:\n", - "You have written to \"gcd.py\" on these lines: 3:6.\n", - "def gcd(a, b):\n", - " if a == 0:\n", - " if a == 0:\n", - " return b\n", - " while b != 0:\n", - " a, b = b % a, a\n", + "assistant: ```json\n", + "Upon reviewing the contents of gcd.py, I can see the gcd function is incorrectly implemented. While the function starts correctly by evaluating `if a == 0: return b`, the loop structure `while a != 0: a, b = b, a` does not accurately compute the greatest common divisor (gcd). \n", "\n", - "def lcm(a, b):\n", - " return (a * b) // gcd(a, b)\n", - "\n", - "# testing on GCD and LCM functions\n", - "print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n", - "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", + "Instead, this loop intends to swap a and b while 'a' is not zero, but does not perform any operation that would actually calculate the gcd. In fact, without proper adjustments, this results in an infinite loop when 'a' is not zero.\n", "\n", + "The correct implementation should be using Euclid's algorithm for calculating gcd which goes: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, 'a' is the gcd.\n", "\n", + "I will replace the current gcd function in the file with this correct implementation.```\n", + "system: {'action': {'name': 'error'}, 'error_msg': 'Your response is not a JSON object, and cannot be parsed by `json.loads` in parse function:\\n## Your Response:\\n[YOUR RESPONSE BEGIN]\\n```json\\nUpon reviewing the contents of gcd.py, I can see the gcd function is incorrectly implemented. While the function starts correctly by evaluating `if a == 0: return b`, the loop structure `while a != 0: a, b = b, a` does not accurately compute the greatest common divisor (gcd). \\n\\nInstead, this loop intends to swap a and b while \\'a\\' is not zero, but does not perform any operation that would actually calculate the gcd. In fact, without proper adjustments, this results in an infinite loop when \\'a\\' is not zero.\\n\\nThe correct implementation should be using Euclid\\'s algorithm for calculating gcd which goes: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, \\'a\\' is the gcd.\\n\\nI will replace the current gcd function in the file with this correct implementation.```\\n[YOUR RESPONSE END]\\n\\n## Error Information:\\nThe content between ```json and ``` MUST be a JSON object.When parsing \"```json\\nUpon reviewing the contents of gcd.py, I can see the gcd function is incorrectly implemented. While the function starts correctly by evaluating `if a == 0: return b`, the loop structure `while a != 0: a, b = b, a` does not accurately compute the greatest common divisor (gcd). \\n\\nInstead, this loop intends to swap a and b while \\'a\\' is not zero, but does not perform any operation that would actually calculate the gcd. In fact, without proper adjustments, this results in an infinite loop when \\'a\\' is not zero.\\n\\nThe correct implementation should be using Euclid\\'s algorithm for calculating gcd which goes: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, \\'a\\' is the gcd.\\n\\nI will replace the current gcd function in the file with this correct implementation.```\", an error occurred: Expecting value: line 2 column 1 (char 1)\\n\\nAnalyze the reason, and re-correct your response in the correct format.'}\n", "assistant: {\n", - " \"thought\": \"The gcd function implemented is incorrect and it's causing the lcm function to fail as well. In the gcd function, while loop is incorrect. We should calculate gcd based on the fact that GCD of two numbers a and b is same as the GCD of b and a mod b. 'a mod b' can be calculated using the '%' operator. When b becomes 0, a will hold the largest common divisor (GCD). Let's fix this.\",\n", + " \"thought\": \"To fix the gcd function, I need to use Euclid's algorithm. The algorithm is: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, 'a' is the gcd. I will replace the current faulty gcd function in the file with this correct implementation.\",\n", " \"action\": {\n", " \"name\": \"write_file\",\n", " \"arguments\": {\n", - " \"start_line\": 2,\n", - " \"end_line\": 6,\n", - " \"content\": \" if b == 0:\\n return a\\n while b:\\n a, b = b, a % b\\n return a\",\n", + " \"content\": \"def gcd(a, b):\\n while b != 0:\\n a, b = b, a % b\\n return a\\n\\ndef lcm(a, b):\\n return (a * b) // gcd(a, b)\\n\\n# testing on GCD and LCM functions\\nprint(\\\"GCD of 12 and 18 is:\\\", gcd(12, 18))\\nprint(\\\"LCM of 12 and 18 is:\\\", lcm(12, 18))\",\n", " \"file_path\": \"gcd.py\"\n", " }\n", " }\n", @@ -201,11 +178,8 @@ "assistant: \n", "====Observation====\n", "WRITE OPERATION:\n", - "You have written to \"gcd.py\" on these lines: 2:6.\n", - "def gcd(a, b):\n", - " if b == 0:\n", - " return a\n", - " while b:\n", + "You have written to \"gcd.py\" on these lines: 0:-1.def gcd(a, b):\n", + " while b != 0:\n", " a, b = b, a % b\n", " return a\n", "\n", @@ -216,9 +190,8 @@ "print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n", "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", "\n", - "\n", "assistant: {\n", - " \"thought\": \"It seems the gcd function in gcd.py has been fixed and any changes required have already been made in my previous actions. I should run the python file to determine if it produces expected results. To do that, I can use the 'execute_shell_command' by running the 'python3' command with 'gcd.py'.\",\n", + " \"thought\": \"Now I have fixed the gcd function, I now need to test if it's working as expected. I can do this by executing the Python file.\",\n", " \"action\": {\n", " \"name\": \"execute_shell_command\",\n", " \"arguments\": {\n", @@ -231,7 +204,7 @@ "GCD of 12 and 18 is: 6\n", "LCM of 12 and 18 is: 36\n", "assistant: {\n", - " \"thought\": \"The gcd.py file is fixed correctly and no further actions are needed.\",\n", + " \"thought\": \"The code has been corrected and tested. It is now returning the correct gcd and lcm. No further actions are required.\",\n", " \"action\": {\n", " \"name\": \"exit\",\n", " \"arguments\": {}\n", @@ -266,7 +239,7 @@ "```text\n", "\n", "assistant: {\n", - " \"thought\": \"It seems the gcd function in gcd.py has been fixed and any changes required have already been made in my previous actions. I should run the python file to determine if it produces expected results. To do that, I can use the 'execute_shell_command' by running the 'python3' command with 'gcd.py'.\",\n", + " \"thought\": \"Now I have fixed the gcd function, I now need to test if it's working as expected. I can do this by executing the Python file.\",\n", " \"action\": {\n", " \"name\": \"execute_shell_command\",\n", " \"arguments\": {\n", @@ -278,10 +251,8 @@ "====Observation====\n", "GCD of 12 and 18 is: 6\n", "LCM of 12 and 18 is: 36\n", - "\n", - "\n", "assistant: {\n", - " \"thought\": \"The gcd.py file is fixed correctly and no further actions are needed.\",\n", + " \"thought\": \"The code has been corrected and tested. It is now returning the correct gcd and lcm. No further actions are required.\",\n", " \"action\": {\n", " \"name\": \"exit\",\n", " \"arguments\": {}\n", @@ -303,7 +274,7 @@ "Above we shown a example of how to use the SWE-agent to fix code errors.\n", "Although the design of the SWE-agent is primarily aimed at addressing GitHub issues, with modifications, it can also be utilized for more general programming tasks.\n", "\n", - "Currently, how to program using an agent remains a challenging open question, with the efficacy of agent programming potentially influenced by factors such as prompt construction, model capabilities, and the complexity of the task at hand. Here we just provide an interesting toy example. \n", + "Currently, how to enable agent with general programming ablities remains a challenging open question, with the efficacy of agent programming potentially influenced by factors such as prompt construction, model capabilities, and the complexity of the task at hand. Here we just provide an interesting toy example. \n", "\n", "We encourage users to experiment by altering the prompts within this example or by assigning different tasks to the agent, among other methods of exploration. Please feel free to experiment and explore on your own. The AgentScope team will continue to provide updates, enhancing the capabilities of the Programming Agents in the future!" ] diff --git a/examples/swe_agent/swe_agent.py b/examples/swe_agent/swe_agent.py index 350bbb047..3b55431d5 100644 --- a/examples/swe_agent/swe_agent.py +++ b/examples/swe_agent/swe_agent.py @@ -9,15 +9,19 @@ from agentscope.agents import AgentBase from agentscope.message import Msg -from agentscope.models import ResponseParser, ResponseParsingError +from agentscope.exception import ResponseParsingError +from agentscope.parsers import MarkdownJsonDictParser from typing import List, Callable import json from agentscope.service import ( ServiceFactory, execute_shell_command, +) + +from swe_agent_service_func import ( + exec_py_linting, write_file, read_file, - exec_py_linting, ) from swe_agent_prompts import ( @@ -108,6 +112,7 @@ def __init__( self.main_goal = "" self.commands_prompt = "" + self.parser = MarkdownJsonDictParser() self.get_commands_prompt() def get_current_file_content(self) -> None: @@ -157,12 +162,12 @@ def step(self) -> Msg: in_prompt = self.model.format(message_list) res = self.model( in_prompt, - parse_func=ResponseParser.to_dict, + parse_func=self.parser.parse, max_retries=1, - ).json + ) except ResponseParsingError as e: - response_msg = Msg(self.name, e.response.text, "assistant") + response_msg = Msg(self.name, e.raw_response, "assistant") self.speak(response_msg) # Re-correct by model itself @@ -171,9 +176,9 @@ def step(self) -> Msg: content={ "action": {"name": "error"}, "error_msg": ERROR_INFO_PROMPT.format( - parse_func=ResponseParser.to_dict, - error_info=e.error_info, - response=e.response.text, + parse_func=self.parser.parse, + error_info=e.message, + response=e.raw_response, ), }, role="system", @@ -183,16 +188,16 @@ def step(self) -> Msg: self.running_memory.append(error_msg) return error_msg - msg_res = Msg(self.name, res, role="assistant") + msg_res = Msg(self.name, res.parsed, role="assistant") self.speak( - Msg(self.name, json.dumps(res, indent=4), role="assistant"), + Msg(self.name, json.dumps(res.parsed, indent=4), role="assistant"), ) # parse and execute action - action = res.get("action") + action = res.parsed.get("action") - obs = self.prase_command(res["action"]) + obs = self.prase_command(res.parsed["action"]) self.speak( Msg(self.name, "\n====Observation====\n" + obs, role="assistant"), ) diff --git a/examples/swe_agent/swe_agent_prompts.py b/examples/swe_agent/swe_agent_prompts.py index ef7a742c0..b743671cf 100644 --- a/examples/swe_agent/swe_agent_prompts.py +++ b/examples/swe_agent/swe_agent_prompts.py @@ -96,7 +96,7 @@ def get_step_prompt( 5. After modifying python files, you can run `exec_py_linting` to check for errors. If there are errors, fix them and repeat the previous step. - NOTE THAT THIS ENVIRONMENT DOES NOT SUPPORT INTERACTIVE SESSION COMMANDS, such as "vim" or "python", or "python3". So DONOT execute them by running `execute_shell_command` with `python` command or `python3` command. + NOTE THAT THIS ENVIRONMENT DOES NOT SUPPORT INTERACTIVE SESSION COMMANDS, such as "vim" or "python", or "python3". So DONOT execute them by running `execute_shell_command` with `python` command or `python3` command if the code need additional inputs. If you want to check whether a python file is valid, you can use `exec_py_linting` to check for errors. {RESPONSE_FORMAT_PROMPT} diff --git a/examples/swe_agent/swe_agent_service_func.py b/examples/swe_agent/swe_agent_service_func.py new file mode 100644 index 000000000..76bdaecce --- /dev/null +++ b/examples/swe_agent/swe_agent_service_func.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# pylint: disable=C0301 +""" +Tools for swe-agent, such as checking files with linting and formatting, +writing and reading files by lines, etc. +""" +import subprocess +import os + +from agentscope.service.service_response import ServiceResponse +from agentscope.service.service_status import ServiceExecStatus + + +def exec_py_linting(file_path: str) -> ServiceResponse: + """ + Executes flake8 linting on the given .py file with specified checks and + returns the linting result. + + Args: + file_path (`str`): The path to the Python file to lint. + + Returns: + ServiceResponse: Contains either the output from the flake8 command as + a string if successful, or an error message including the error type. + """ + command = f"flake8 --isolated --select=F821,F822,F831,\ + E111,E112,E113,E999,E902 {file_path}" + + try: + result = subprocess.run( + command, + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=result.stdout.strip() + if result.stdout + else "No lint errors found.", + ) + except subprocess.CalledProcessError as e: + error_message = ( + e.stderr.strip() + if e.stderr + else "An error occurred while linting the file." + ) + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) + except Exception as e: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=str(e), + ) + + +def write_file( + file_path: str, + content: str, + start_line: int = 0, + end_line: int = -1, +) -> ServiceResponse: + """ + Write content to a file by replacing the current lines between and with . Default start_line = 0 and end_line = -1. Calling this with no args will replace the whole file, so besure to use this with caution when writing to a file that already exists. + + Args: + file_path (`str`): The path to the file to write to. + content (`str`): The content to write to the file. + start_line (`Optional[int]`, defaults to `0`): The start line of the file to be replace with . + end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be replace with . end_line = -1 means the end of the file, otherwise it should be a positive integer indicating the line number. + """ # noqa + try: + mode = "w" if not os.path.exists(file_path) else "r+" + insert = content.split("\n") + with open(file_path, mode, encoding="utf-8") as file: + if mode != "w": + all_lines = file.readlines() + new_file = [""] if start_line == 0 else all_lines[:start_line] + new_file += [i + "\n" for i in insert] + last_line = end_line + 1 + new_file += [""] if end_line == -1 else all_lines[last_line:] + else: + new_file = insert + + file.seek(0) + file.writelines(new_file) + file.truncate() + obs = f'WRITE OPERATION:\nYou have written to "{file_path}" \ + on these lines: {start_line}:{end_line}.' + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=obs + "".join(new_file), + ) + except Exception as e: + error_message = f"{e.__class__.__name__}: {e}" + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) + + +def read_file( + file_path: str, + start_line: int = 0, + end_line: int = -1, +) -> ServiceResponse: + """ + Shows a given file's contents starting from up to . Default: start_line = 0, end_line = -1. By default the whole file will be read. + + Args: + file_path (`str`): The path to the file to read. + start_line (`Optional[int]`, defaults to `0`): The start line of the file to be read. + end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be read. + """ # noqa + start_line = max(start_line, 0) + try: + with open(file_path, "r", encoding="utf-8") as file: + if end_line == -1: + if start_line == 0: + code_view = file.read() + else: + all_lines = file.readlines() + code_slice = all_lines[start_line:] + code_view = "".join(code_slice) + else: + all_lines = file.readlines() + num_lines = len(all_lines) + begin = max(0, min(start_line, num_lines - 2)) + end_line = ( + -1 if end_line > num_lines else max(begin + 1, end_line) + ) + code_slice = all_lines[begin:end_line] + code_view = "".join(code_slice) + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=f"{code_view}", + ) + except Exception as e: + error_message = f"{e.__class__.__name__}: {e}" + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content=error_message, + ) diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index d7dea65b4..dce26c195 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -13,10 +13,7 @@ move_directory, list_directory_content, get_current_directory, - write_file, - read_file, ) -from .file.check import exec_py_linting from .file.text import read_text_file, write_text_file from .file.json import read_json_file, write_json_file from .sql_query.mysql import query_mysql @@ -73,9 +70,6 @@ def get_help() -> None: "load_web", "parse_html_to_text", "download_from_url", - "write_file", - "read_file", - "exec_py_linting", # to be deprecated "ServiceFactory", ] diff --git a/src/agentscope/service/file/check.py b/src/agentscope/service/file/check.py deleted file mode 100644 index 96c675e05..000000000 --- a/src/agentscope/service/file/check.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tools for checking files, such as linting and formatting. -""" -import subprocess - -from agentscope.service.service_response import ServiceResponse -from agentscope.service.service_status import ServiceExecStatus - - -def exec_py_linting(file_path: str) -> ServiceResponse: - """ - Executes flake8 linting on the given .py file with specified checks and - returns the linting result. - - Args: - file_path (`str`): The path to the Python file to lint. - - Returns: - ServiceResponse: Contains either the output from the flake8 command as - a string if successful, or an error message including the error type. - """ - command = f"flake8 --isolated --select=F821,F822,F831,\ - E111,E112,E113,E999,E902 {file_path}" - - try: - result = subprocess.run( - command, - shell=True, - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - return ServiceResponse( - status=ServiceExecStatus.SUCCESS, - content=result.stdout.strip() - if result.stdout - else "No lint errors found.", - ) - except subprocess.CalledProcessError as e: - error_message = ( - e.stderr.strip() - if e.stderr - else "An error occurred while linting the file." - ) - return ServiceResponse( - status=ServiceExecStatus.ERROR, - content=error_message, - ) - except Exception as e: - return ServiceResponse( - status=ServiceExecStatus.ERROR, - content=str(e), - ) diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index dc6439fb7..11199a666 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -272,92 +272,3 @@ def get_current_directory() -> ServiceResponse: status=ServiceExecStatus.ERROR, content=error_message, ) - - -def write_file( - file_path: str, - content: str, - start_line: int = 0, - end_line: int = -1, -) -> ServiceResponse: - """ - Write content to a file by replacing the current lines between and with . Default start_line = 0 and end_line = -1. Calling this with no args will replace the whole file, so besure to use this with caution when writing to a file that already exists. - - Args: - file_path (`str`): The path to the file to write to. - content (`str`): The content to write to the file. - start_line (`Optional[int]`, defaults to `0`): The start line of the file to be replace with . - end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be replace with . end_line = -1 means the end of the file, otherwise it should be a positive integer indicating the line number. - """ # noqa - try: - mode = "w" if not os.path.exists(file_path) else "r+" - insert = content.split("\n") - with open(file_path, mode, encoding="utf-8") as file: - if mode != "w": - all_lines = file.readlines() - new_file = [""] if start_line == 0 else all_lines[:start_line] - new_file += [i + "\n" for i in insert] - last_line = end_line + 1 - new_file += [""] if end_line == -1 else all_lines[last_line:] - else: - new_file = insert - - file.seek(0) - file.writelines(new_file) - file.truncate() - obs = f'WRITE OPERATION:\nYou have written to "{file_path}" \ - on these lines: {start_line}:{end_line}.' - return ServiceResponse( - status=ServiceExecStatus.SUCCESS, - content=obs + "".join(new_file), - ) - except Exception as e: - error_message = f"{e.__class__.__name__}: {e}" - return ServiceResponse( - status=ServiceExecStatus.ERROR, - content=error_message, - ) - - -def read_file( - file_path: str, - start_line: int = 0, - end_line: int = -1, -) -> ServiceResponse: - """ - Shows a given file's contents starting from up to . Default: start_line = 0, end_line = -1. By default the whole file will be read. - - Args: - file_path (`str`): The path to the file to read. - start_line (`Optional[int]`, defaults to `0`): The start line of the file to be read. - end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be read. - """ # noqa - start_line = max(start_line, 0) - try: - with open(file_path, "r", encoding="utf-8") as file: - if end_line == -1: - if start_line == 0: - code_view = file.read() - else: - all_lines = file.readlines() - code_slice = all_lines[start_line:] - code_view = "".join(code_slice) - else: - all_lines = file.readlines() - num_lines = len(all_lines) - begin = max(0, min(start_line, num_lines - 2)) - end_line = ( - -1 if end_line > num_lines else max(begin + 1, end_line) - ) - code_slice = all_lines[begin:end_line] - code_view = "".join(code_slice) - return ServiceResponse( - status=ServiceExecStatus.SUCCESS, - content=f"{code_view}", - ) - except Exception as e: - error_message = f"{e.__class__.__name__}: {e}" - return ServiceResponse( - status=ServiceExecStatus.ERROR, - content=error_message, - ) From f88ed5d47b24b10945f36a7ecb66e47baa0992bf Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Wed, 15 May 2024 11:52:30 +0800 Subject: [PATCH 24/29] resolve threads --- README.md | 1 + README_ZH.md | 1 + examples/swe_agent/main.ipynb | 93 ++++++++++--------------- examples/swe_agent/swe_agent_prompts.py | 7 +- src/agentscope/service/file/common.py | 2 +- 5 files changed, 45 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index a254ad6f5..caf95fbce 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ the following libraries. - [Conversation with ReAct Agent](./examples/conversation_with_react_agent) - [Conversation in Natural Language to Query SQL](./examples/conversation_nl2sql/) - [Conversation with RAG Agent](./examples/conversation_with_RAG_agents) + - [Conversation with SWE Agnet](./examples/swe_agent/) - Game - [Gomoku](./examples/game_gomoku) diff --git a/README_ZH.md b/README_ZH.md index dd7fd3346..693106447 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -110,6 +110,7 @@ AgentScope支持使用以下库快速部署本地模型服务。 - [与ReAct智能体对话](./examples/conversation_with_react_agent) - [通过对话查询SQL信息](./examples/conversation_nl2sql/) - [与RAG智能体对话](./examples/conversation_with_RAG_agents) + - [与SWE智能体对话](./examples/swe_agent/) - 游戏 - [五子棋](./examples/game_gomoku) diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb index f22cb0586..33e6f5c58 100644 --- a/examples/swe_agent/main.ipynb +++ b/examples/swe_agent/main.ipynb @@ -7,7 +7,7 @@ "# Example with SWE-agent\n", "\n", "SWE-agent(SoftWare Engineering Agent) is an agent designed for solving real world software engineering problems, such as fixing github issues.\n", - "More details can be found in their [homepage](https://swe-agent.com/) and related [github repo](https://swe-agent.com/).\n", + "More details can be found in the project's [homepage](https://swe-agent.com/) and related [github repo](https://swe-agent.com/).\n", "\n", "In the example here, we partially implement the SWE-agent, and provide a simple example of how to use the implemented SWE-agent to fix a bug in a python file.\n", "You should note that currently how to enable agents with stronger programming capabilities remains an open challenge, and the performance of the paritially implemented SWE-agent is not guaranteed.\n", @@ -15,8 +15,9 @@ "## Prerequisites\n", "\n", "- Follow [READMD.md](https://github.com/modelscope/agentscope) to install AgentScope. \n", - "- Prepare a model configuration. AgentScope supports both local deployed model services (CPU or GPU) and third-party services. More details and example model configurations please refer to our [tutorial]\n", - "- Understand the ServiceFactory module and how to use it to pre-process the tool functions for LLMs. You can refer to the [ReAct agent example](../conversation_with_react_agent/main.ipynb).\n", + "- Prepare a model configuration. AgentScope supports both local deployed model services (CPU or GPU) and third-party services. More details and example model configurations please refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-model.html).\n", + "- Understand the ServiceToolkit module and how to use it to pre-process the tool functions for LLMs. You can refer to the [ReAct agent example](../conversation_with_react_agent/main.ipynb) and you should also refer to the [tutorial](https://modelscope.github.io/agentscope/en/tutorial/204-service.html) for service functions.\n", + "\n", "\n", "## Note\n", "\n", @@ -25,6 +26,22 @@ "- How to enable agents with stronger programming capabilities remains an open challenge, and the current implementations are not perfect. Please feel free to explore it yourself." ] }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "YOUR_MODEL_CONFIGURATION_NAME = \"{YOUR_MODEL_CONFIGURATION_NAME}\"\n", + "\n", + "YOUR_MODEL_CONFIGURATION = {\n", + " \"model_type\": \"xxx\", \n", + " \"config_name\": YOUR_MODEL_CONFIGURATION_NAME\n", + " \n", + " # ...\n", + "}" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -47,11 +64,8 @@ "from swe_agent import SWEAgent\n", "\n", "import agentscope\n", - "from agentscope.models import load_model_by_config_name\n", - "agentscope.init(\n", - " model_configs=\"xxx.json\", # fill you model config file path here\n", - ")\n", - "loaded_model = load_model_by_config_name('gpt-4')\n", + "\n", + "agentscope.init(model_configs=YOUR_MODEL_CONFIGURATION)\n", "\n", "agent = SWEAgent(\n", " name=\"assistant\",\n", @@ -72,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -82,7 +96,7 @@ " 'content': 'WRITE OPERATION:\\nYou have written to \"gcd.py\" on these lines: 0:-1.\\ndef gcd(a, b):\\n if a == 0:\\n return b\\n while a != 0:\\n a, b = b, a\\n return b\\n\\ndef lcm(a, b):\\n return (a * b) // gcd(a, b)\\n\\n# testing on GCD and LCM functions\\nprint(\"GCD of 12 and 18 is:\", gcd(12, 18))\\nprint(\"LCM of 12 and 18 is:\", lcm(12, 18))\\n\\n'}" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -121,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -130,7 +144,7 @@ "text": [ "user: The file in gcd.py is not returning the correct answer, can you fix the file for me?\n", "assistant: {\n", - " \"thought\": \"To debug the gcd.py file, first we need to read the contents of the file to understand the existing program logic.\",\n", + " \"thought\": \"First thing I'll do is read the content of gcd.py file to understand the existing logic.\",\n", " \"action\": {\n", " \"name\": \"read_file\",\n", " \"arguments\": {\n", @@ -156,21 +170,14 @@ "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", "\n", "\n", - "assistant: ```json\n", - "Upon reviewing the contents of gcd.py, I can see the gcd function is incorrectly implemented. While the function starts correctly by evaluating `if a == 0: return b`, the loop structure `while a != 0: a, b = b, a` does not accurately compute the greatest common divisor (gcd). \n", - "\n", - "Instead, this loop intends to swap a and b while 'a' is not zero, but does not perform any operation that would actually calculate the gcd. In fact, without proper adjustments, this results in an infinite loop when 'a' is not zero.\n", - "\n", - "The correct implementation should be using Euclid's algorithm for calculating gcd which goes: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, 'a' is the gcd.\n", - "\n", - "I will replace the current gcd function in the file with this correct implementation.```\n", - "system: {'action': {'name': 'error'}, 'error_msg': 'Your response is not a JSON object, and cannot be parsed by `json.loads` in parse function:\\n## Your Response:\\n[YOUR RESPONSE BEGIN]\\n```json\\nUpon reviewing the contents of gcd.py, I can see the gcd function is incorrectly implemented. While the function starts correctly by evaluating `if a == 0: return b`, the loop structure `while a != 0: a, b = b, a` does not accurately compute the greatest common divisor (gcd). \\n\\nInstead, this loop intends to swap a and b while \\'a\\' is not zero, but does not perform any operation that would actually calculate the gcd. In fact, without proper adjustments, this results in an infinite loop when \\'a\\' is not zero.\\n\\nThe correct implementation should be using Euclid\\'s algorithm for calculating gcd which goes: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, \\'a\\' is the gcd.\\n\\nI will replace the current gcd function in the file with this correct implementation.```\\n[YOUR RESPONSE END]\\n\\n## Error Information:\\nThe content between ```json and ``` MUST be a JSON object.When parsing \"```json\\nUpon reviewing the contents of gcd.py, I can see the gcd function is incorrectly implemented. While the function starts correctly by evaluating `if a == 0: return b`, the loop structure `while a != 0: a, b = b, a` does not accurately compute the greatest common divisor (gcd). \\n\\nInstead, this loop intends to swap a and b while \\'a\\' is not zero, but does not perform any operation that would actually calculate the gcd. In fact, without proper adjustments, this results in an infinite loop when \\'a\\' is not zero.\\n\\nThe correct implementation should be using Euclid\\'s algorithm for calculating gcd which goes: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, \\'a\\' is the gcd.\\n\\nI will replace the current gcd function in the file with this correct implementation.```\", an error occurred: Expecting value: line 2 column 1 (char 1)\\n\\nAnalyze the reason, and re-correct your response in the correct format.'}\n", "assistant: {\n", - " \"thought\": \"To fix the gcd function, I need to use Euclid's algorithm. The algorithm is: while b is not zero, swap a, b = b, remainder of (a divided by b), when b becomes zero, 'a' is the gcd. I will replace the current faulty gcd function in the file with this correct implementation.\",\n", + " \"thought\": \"I can see the error in the gcd calculation. The gcd calculation in the file is incorrect. The correct way to find the gcd of two numbers using the Euclidean algorithm is to continually reduce a and b by finding the remainder of a % b until b is 0, at which point a will be the gcd of the original a and b values. In the given code, instead of finding the remainder, it is incorrectly swapping the values of a and b. I'll correct this error by modifying the gcd function using the write_file command, reflecting the correct Euclidean algorithm.\",\n", " \"action\": {\n", " \"name\": \"write_file\",\n", " \"arguments\": {\n", - " \"content\": \"def gcd(a, b):\\n while b != 0:\\n a, b = b, a % b\\n return a\\n\\ndef lcm(a, b):\\n return (a * b) // gcd(a, b)\\n\\n# testing on GCD and LCM functions\\nprint(\\\"GCD of 12 and 18 is:\\\", gcd(12, 18))\\nprint(\\\"LCM of 12 and 18 is:\\\", lcm(12, 18))\",\n", + " \"start_line\": 0,\n", + " \"end_line\": 15,\n", + " \"content\": \"def gcd(a, b):\\n while b != 0:\\n a, b = b, a % b\\n return a\\n\\n\\ndef lcm(a, b):\\n return (a * b) // gcd(a, b)\\n\\n\\n# testing on GCD and LCM functions\\nprint(\\\"GCD of 12 and 18 is:\\\", gcd(12, 18))\\nprint(\\\"LCM of 12 and 18 is:\\\", lcm(12, 18))\\n\",\n", " \"file_path\": \"gcd.py\"\n", " }\n", " }\n", @@ -178,20 +185,23 @@ "assistant: \n", "====Observation====\n", "WRITE OPERATION:\n", - "You have written to \"gcd.py\" on these lines: 0:-1.def gcd(a, b):\n", + "You have written to \"gcd.py\" on these lines: 0:15.def gcd(a, b):\n", " while b != 0:\n", " a, b = b, a % b\n", " return a\n", "\n", + "\n", "def lcm(a, b):\n", " return (a * b) // gcd(a, b)\n", "\n", + "\n", "# testing on GCD and LCM functions\n", "print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n", "print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n", "\n", + "\n", "assistant: {\n", - " \"thought\": \"Now I have fixed the gcd function, I now need to test if it's working as expected. I can do this by executing the Python file.\",\n", + " \"thought\": \"I should run the python file to see if the changes I made have fixed the gcd function.\",\n", " \"action\": {\n", " \"name\": \"execute_shell_command\",\n", " \"arguments\": {\n", @@ -204,7 +214,7 @@ "GCD of 12 and 18 is: 6\n", "LCM of 12 and 18 is: 36\n", "assistant: {\n", - " \"thought\": \"The code has been corrected and tested. It is now returning the correct gcd and lcm. No further actions are required.\",\n", + " \"thought\": \"The gcd.py file has been fixed and tested successfully. Therefore, no further actions are needed.\",\n", " \"action\": {\n", " \"name\": \"exit\",\n", " \"arguments\": {}\n", @@ -235,36 +245,7 @@ "source": [ "### Conlusion\n", "\n", - "After a few iterations, the assistant response:\n", - "```text\n", - "\n", - "assistant: {\n", - " \"thought\": \"Now I have fixed the gcd function, I now need to test if it's working as expected. I can do this by executing the Python file.\",\n", - " \"action\": {\n", - " \"name\": \"execute_shell_command\",\n", - " \"arguments\": {\n", - " \"command\": \"python3 gcd.py\"\n", - " }\n", - " }\n", - "}\n", - "assistant: \n", - "====Observation====\n", - "GCD of 12 and 18 is: 6\n", - "LCM of 12 and 18 is: 36\n", - "assistant: {\n", - " \"thought\": \"The code has been corrected and tested. It is now returning the correct gcd and lcm. No further actions are required.\",\n", - " \"action\": {\n", - " \"name\": \"exit\",\n", - " \"arguments\": {}\n", - " }\n", - "}\n", - "assistant: \n", - "====Observation====\n", - "Current task finished, exitting.\n", - "\n", - "```\n", - "\n", - "The SWE-agent finish the job successfully, and the code is now working fine." + "After a few iterations, the SWE-agent assistant finish the job successfully, and the code is now working fine." ] }, { diff --git a/examples/swe_agent/swe_agent_prompts.py b/examples/swe_agent/swe_agent_prompts.py index b743671cf..4c30e48af 100644 --- a/examples/swe_agent/swe_agent_prompts.py +++ b/examples/swe_agent/swe_agent_prompts.py @@ -36,8 +36,6 @@ def get_system_prompt(command_prompt: str) -> str: However, the environment does NOT support interactive session commands (e.g. vim, python), so please do not invoke them. - Your Response should always be a valid JSON string that can be parsed. - {RESPONSE_FORMAT_PROMPT} """ # noqa @@ -46,16 +44,20 @@ def get_system_prompt(command_prompt: str) -> str: RESPONSE_FORMAT_PROMPT = """ ## Response Format: You should respond with a JSON object in the following format. +```json { "thought": "what you thought", "action": {"name": "{command name}", "arguments": {"{argument1 name}": xxx, "{argument2 name}": xxx}} } +``` For Example: +```json { "thought": "First I'll start by using ls to see what files are in the current directory. Then maybe we can look at some relevant files to see what they look like.", "action": {"name": "execute_shell_command", "arguments": {"command": "ls -a"}} } +``` OUTPUT the JSON format and ONLY OUTPUT the JSON format. Your Response should always be a valid JSON string that can be parsed. """ # noqa @@ -99,6 +101,7 @@ def get_step_prompt( NOTE THAT THIS ENVIRONMENT DOES NOT SUPPORT INTERACTIVE SESSION COMMANDS, such as "vim" or "python", or "python3". So DONOT execute them by running `execute_shell_command` with `python` command or `python3` command if the code need additional inputs. If you want to check whether a python file is valid, you can use `exec_py_linting` to check for errors. + You should always notice your response format and respond with a JSON object in the following format. {RESPONSE_FORMAT_PROMPT} """ # noqa diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index 11199a666..ef8e8855b 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -15,7 +15,7 @@ def create_file(file_path: str, content: str = "") -> ServiceResponse: Args: file_path (`str`): - The path where the file will be created. + The path where the file will be created. content (`str`): Content to write into the file. From 815c99f02da1b361b33cec4f45e7e411c56454f8 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Wed, 15 May 2024 11:54:35 +0800 Subject: [PATCH 25/29] resolving threads --- examples/swe_agent/main.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb index 33e6f5c58..313825c1d 100644 --- a/examples/swe_agent/main.ipynb +++ b/examples/swe_agent/main.ipynb @@ -14,7 +14,7 @@ "\n", "## Prerequisites\n", "\n", - "- Follow [READMD.md](https://github.com/modelscope/agentscope) to install AgentScope. \n", + "- Follow [READMD.md](https://github.com/modelscope/agentscope) to install AgentScope. We require the lastest version, so you should build from source by running `pip install -e .` instead of intalling from pypi. \n", "- Prepare a model configuration. AgentScope supports both local deployed model services (CPU or GPU) and third-party services. More details and example model configurations please refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-model.html).\n", "- Understand the ServiceToolkit module and how to use it to pre-process the tool functions for LLMs. You can refer to the [ReAct agent example](../conversation_with_react_agent/main.ipynb) and you should also refer to the [tutorial](https://modelscope.github.io/agentscope/en/tutorial/204-service.html) for service functions.\n", "\n", From 9da20fb1e09fcab82823075e199c99ac7574bc6b Mon Sep 17 00:00:00 2001 From: garyzhang99 <46197280+garyzhang99@users.noreply.github.com> Date: Wed, 15 May 2024 11:59:07 +0800 Subject: [PATCH 26/29] Update main.ipynb --- examples/swe_agent/main.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb index 313825c1d..4092d82cd 100644 --- a/examples/swe_agent/main.ipynb +++ b/examples/swe_agent/main.ipynb @@ -69,7 +69,7 @@ "\n", "agent = SWEAgent(\n", " name=\"assistant\",\n", - " model_config_name='gpt-4',\n", + " model_config_name='YOUR_MODEL_CONFIGURATION_NAME',\n", ")\n" ] }, From 1975a6387d89ab41fc35afd4cab5fee3b57c571f Mon Sep 17 00:00:00 2001 From: garyzhang99 <46197280+garyzhang99@users.noreply.github.com> Date: Wed, 15 May 2024 12:00:22 +0800 Subject: [PATCH 27/29] Update main.ipynb --- examples/swe_agent/main.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb index 4092d82cd..7ee513a47 100644 --- a/examples/swe_agent/main.ipynb +++ b/examples/swe_agent/main.ipynb @@ -69,7 +69,7 @@ "\n", "agent = SWEAgent(\n", " name=\"assistant\",\n", - " model_config_name='YOUR_MODEL_CONFIGURATION_NAME',\n", + " model_config_name=YOUR_MODEL_CONFIGURATION_NAME,\n", ")\n" ] }, From e462249f53ce644a4076ef5a256ee30844e478f0 Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Wed, 15 May 2024 12:40:51 +0800 Subject: [PATCH 28/29] resolve threads --- README.md | 2 +- README_ZH.md | 2 +- examples/swe_agent/main.ipynb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 99bfca906..861899029 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,8 @@ the following libraries. - [Conversation with ReAct Agent](./examples/conversation_with_react_agent) - [Conversation in Natural Language to Query SQL](./examples/conversation_nl2sql/) - [Conversation with RAG Agent](./examples/conversation_with_RAG_agents) - - [Conversation with SWE Agnet](./examples/swe_agent/) - new[Conversation with gpt-4o](./examples/conversation_with_gpt-4o) + - new[Conversation with SWE Agnet](./examples/swe_agent/) - Game diff --git a/README_ZH.md b/README_ZH.md index eb6b9d236..3003ce42f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -119,8 +119,8 @@ AgentScope支持使用以下库快速部署本地模型服务。 - [与ReAct智能体对话](./examples/conversation_with_react_agent) - [通过对话查询SQL信息](./examples/conversation_nl2sql/) - [与RAG智能体对话](./examples/conversation_with_RAG_agents) - - [与SWE智能体对话](./examples/swe_agent/) - new[与gpt-4o模型对话](./examples/conversation_with_gpt-4o) + - new[与SWE智能体对话](./examples/swe_agent/) - 游戏 diff --git a/examples/swe_agent/main.ipynb b/examples/swe_agent/main.ipynb index 7ee513a47..59d15bcf3 100644 --- a/examples/swe_agent/main.ipynb +++ b/examples/swe_agent/main.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Example with SWE-agent\n", + "# Conversation with Software Engineering Agent\n", "\n", "SWE-agent(SoftWare Engineering Agent) is an agent designed for solving real world software engineering problems, such as fixing github issues.\n", "More details can be found in the project's [homepage](https://swe-agent.com/) and related [github repo](https://swe-agent.com/).\n", @@ -80,7 +80,7 @@ "## Step 2: Create the code to be processed by the SWE-agent\n", "\n", "Here we use the `write_file` function to write the following code into `gcd.py`.\n", - "The code here is a wrong implementation of the Greatest Common Divisor (GCD) algorithm.\n", + "The code here is a wrong implementation of the [Greatest Common Divisor (GCD) algorithm](https://en.wikipedia.org/wiki/Euclidean_algorithm).\n", "We will ask the SWE-agent to correct it in our next step." ] }, From 46ae325964439632034177c7e5d5fb054e667b5d Mon Sep 17 00:00:00 2001 From: garyzhang99 Date: Wed, 15 May 2024 15:24:50 +0800 Subject: [PATCH 29/29] README name --- README.md | 2 +- README_ZH.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 861899029..ec9e6cbe6 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ the following libraries. - [Conversation in Natural Language to Query SQL](./examples/conversation_nl2sql/) - [Conversation with RAG Agent](./examples/conversation_with_RAG_agents) - new[Conversation with gpt-4o](./examples/conversation_with_gpt-4o) - - new[Conversation with SWE Agnet](./examples/swe_agent/) + - new[Conversation with Software Engineering Agent](./examples/swe_agent/) - Game diff --git a/README_ZH.md b/README_ZH.md index 3003ce42f..40ebc42c7 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -120,7 +120,7 @@ AgentScope支持使用以下库快速部署本地模型服务。 - [通过对话查询SQL信息](./examples/conversation_nl2sql/) - [与RAG智能体对话](./examples/conversation_with_RAG_agents) - new[与gpt-4o模型对话](./examples/conversation_with_gpt-4o) - - new[与SWE智能体对话](./examples/swe_agent/) + - new[与SoftWare Engineering智能体对话](./examples/swe_agent/) - 游戏