diff --git a/README.md b/README.md index 514fecaf..f5c777f6 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,15 @@ ## 📰 News +* **September 25th, 2023: The **Git** feature is now available**, enabling the programmer < img src='online_log/static/figures/programmer.png' height=20> to utilize GitHub for version control. To enable this feature, simply set ``"git_management"`` to ``"True"`` in ``ChatChainConfig.json``. +

+ +

* September 20th, 2023: The **Human-Agent-Interaction** mode is now available! You can get involved with the ChatDev team by playing the role of reviewer and making suggestions to the programmer ; try ``python3 run.py --task [description_of_your_idea] --config "Human"``. See [guide](wiki.md#human-agent-interaction) and [example](WareHouse/Gomoku_HumanAgentInteraction_20230920135038). -
+

+ +

* September 1st, 2023: The **Art** mode is available now! You can activate the designer agent to generate images used in the software; try ``python3 run.py --task [description_of_your_idea] --config "Art"``. See [guide](wiki.md#art) and [example](WareHouse/gomokugameArtExample_THUNLP_20230831122822). * August 28th, 2023: The system is publicly available. @@ -165,10 +171,9 @@ create a software package and generate a folder named ``/WareHouse/2048_THUNLP_t ## ⚖️ License -- The purpose of ChatDev is exclusively for research purposes. -- The source code is licensed under Apache 2.0. -- The datasets are licensed under CC BY NC 4.0, which allows for non-commercial use only. It is important to note that - any models trained using these datasets should not be employed for purposes other than research. +- Source Code Licensing: Our project's source code is licensed under the Apache 2.0 License. This license permits the use, modification, and distribution of the code, subject to certain conditions outlined in the Apache 2.0 License. +- Project Open-Source Status: The project is indeed open-source; however, this designation is primarily intended for non-commercial purposes. While we encourage collaboration and contributions from the community for research and non-commercial applications, it is important to note that any utilization of the project's components for commercial purposes necessitates separate licensing agreements. +- Data Licensing: The related data utilized in our project is licensed under CC BY-NC 4.0. This license explicitly permits non-commercial use of the data. We would like to emphasize that any models trained using these datasets should strictly adhere to the non-commercial usage restriction and should be employed exclusively for research purposes. ## Star History diff --git a/camel/typing.py b/camel/typing.py index 4a63153d..f3347774 100644 --- a/camel/typing.py +++ b/camel/typing.py @@ -60,7 +60,6 @@ class PhaseType(Enum): RECRUITING_CPO = "recruiting CPO" RECRUITING_CTO = "recruiting CTO" DEMAND_ANALYSIS = "demand analysis" - BRAINSTORMING = "brainstorming" CHOOSING_LANGUAGE = "choosing language" RECRUITING_PROGRAMMER = "recruiting programmer" RECRUITING_REVIEWER = "recruiting reviewer" diff --git a/chatdev/chat_chain.py b/chatdev/chat_chain.py index d66094b9..5a50652d 100644 --- a/chatdev/chat_chain.py +++ b/chatdev/chat_chain.py @@ -1,10 +1,10 @@ import importlib import json +import logging import os import shutil -from datetime import datetime -import logging import time +from datetime import datetime from camel.agents import RolePlaying from camel.configs import ChatGPTConfig @@ -63,7 +63,6 @@ class ChatChain: # init ChatEnv self.chat_env_config = ChatEnvConfig(clear_structure=check_bool(self.config["clear_structure"]), - brainstorming=check_bool(self.config["brainstorming"]), gui_design=check_bool(self.config["gui_design"]), git_management=check_bool(self.config["git_management"])) self.chat_env = ChatEnv(self.chat_env_config) @@ -102,8 +101,6 @@ class ChatChain: log_filepath=self.log_filepath) self.phases[phase] = phase_instance - - def make_recruitment(self): """ recruit all employees @@ -176,7 +173,8 @@ class ChatChain: root = os.path.dirname(filepath) # directory = root + "/WareHouse/" directory = os.path.join(root, "WareHouse") - log_filepath = os.path.join(directory, "{}.log".format("_".join([self.project_name, self.org_name,start_time]))) + log_filepath = os.path.join(directory, + "{}.log".format("_".join([self.project_name, self.org_name, start_time]))) return start_time, log_filepath def pre_processing(self): @@ -187,9 +185,7 @@ class ChatChain: """ if self.chat_env.config.clear_structure: filepath = os.path.dirname(__file__) - # root = "/".join(filepath.split("/")[:-1]) root = os.path.dirname(filepath) - # directory = root + "/WareHouse" directory = os.path.join(root, "WareHouse") for filename in os.listdir(directory): file_path = os.path.join(directory, filename) @@ -221,8 +217,8 @@ class ChatChain: preprocess_msg += "**task_prompt**: {}\n\n".format(self.task_prompt_raw) preprocess_msg += "**project_name**: {}\n\n".format(self.project_name) preprocess_msg += "**Log File**: {}\n\n".format(self.log_filepath) - preprocess_msg += "**ChatDevConfig**:\n {}\n\n".format(self.chat_env.config.__str__()) - preprocess_msg += "**ChatGPTConfig**:\n {}\n\n".format(chat_gpt_config) + preprocess_msg += "**ChatDevConfig**:\n{}\n\n".format(self.chat_env.config.__str__()) + preprocess_msg += "**ChatGPTConfig**:\n{}\n\n".format(chat_gpt_config) log_and_print_online(preprocess_msg) # init task prompt @@ -240,9 +236,33 @@ class ChatChain: self.chat_env.write_meta() filepath = os.path.dirname(__file__) - # root = "/".join(filepath.split("/")[:-1]) root = os.path.dirname(filepath) + if self.chat_env_config.git_management: + git_online_log = "**[Git Information]**\n\n" + + self.chat_env.codes.version += 1 + os.system("cd {}; git add .".format(self.chat_env.env_dict["directory"])) + git_online_log += "cd {}; git add .\n".format(self.chat_env.env_dict["directory"]) + os.system("cd {}; git commit -m \"v{} Final Version\"".format(self.chat_env.env_dict["directory"], self.chat_env.codes.version)) + git_online_log += "cd {}; git commit -m \"v{} Final Version\"\n".format(self.chat_env.env_dict["directory"], self.chat_env.codes.version) + log_and_print_online(git_online_log) + + git_info = "**[Git Log]**\n\n" + import subprocess + + # 执行git log命令 + command = "cd {}; git log".format(self.chat_env.env_dict["directory"]) + completed_process = subprocess.run(command, shell=True, text=True, stdout=subprocess.PIPE) + + if completed_process.returncode == 0: + log_output = completed_process.stdout + else: + log_output = "Error when executing " + command + + git_info += log_output + log_and_print_online(git_info) + post_info = "**[Post Info]**\n\n" now_time = now() time_format = "%Y%m%d%H%M%S" @@ -251,7 +271,8 @@ class ChatChain: duration = (datetime2 - datetime1).total_seconds() post_info += "Software Info: {}".format( - get_info(self.chat_env.env_dict['directory'], self.log_filepath) + "\n\n🕑**duration**={:.2f}s\n\n".format(duration)) + get_info(self.chat_env.env_dict['directory'], self.log_filepath) + "\n\n🕑**duration**={:.2f}s\n\n".format( + duration)) post_info += "ChatDev Starts ({})".format(self.start_time) + "\n\n" post_info += "ChatDev Ends ({})".format(now_time) + "\n\n" diff --git a/chatdev/chat_env.py b/chatdev/chat_env.py index 0a444011..a986310d 100644 --- a/chatdev/chat_env.py +++ b/chatdev/chat_env.py @@ -17,18 +17,17 @@ from chatdev.utils import log_and_print_online class ChatEnvConfig: def __init__(self, clear_structure, - brainstorming, gui_design, git_management): self.clear_structure = clear_structure - self.brainstorming = brainstorming self.gui_design = gui_design self.git_management = git_management def __str__(self): string = "" string += "ChatEnvConfig.clear_structure: {}\n".format(self.clear_structure) - string += "ChatEnvConfig.brainstorming: {}\n".format(self.brainstorming) + string += "ChatEnvConfig.git_management: {}\n".format(self.git_management) + string += "ChatEnvConfig.gui_design: {}\n".format(self.gui_design) return string @@ -112,7 +111,7 @@ class ChatEnv: else: os.kill(process.pid, signal.SIGTERM) if process.poll() is None: - os.kill(process.pid,signal.CTRL_BREAK_EVENT) + os.kill(process.pid, signal.CTRL_BREAK_EVENT) if return_code == 0: return False, success_info @@ -143,8 +142,8 @@ class ChatEnv: def update_codes(self, generated_content): self.codes._update_codes(generated_content) - def rewrite_codes(self) -> None: - self.codes._rewrite_codes(self.config.git_management) + def rewrite_codes(self, phase_info=None) -> None: + self.codes._rewrite_codes(self.config.git_management, phase_info) def get_codes(self) -> str: return self.codes._get_codes() diff --git a/chatdev/codes.py b/chatdev/codes.py index 56cad543..69c4738c 100644 --- a/chatdev/codes.py +++ b/chatdev/codes.py @@ -1,13 +1,15 @@ +import difflib import os import re +import subprocess from chatdev.utils import log_and_print_online -import difflib + class Codes: def __init__(self, generated_content=""): self.directory: str = None - self.version: float = 1.0 + self.version: float = 0.0 self.generated_content: str = generated_content self.codebooks = {} @@ -71,7 +73,7 @@ class Codes: log_and_print_online(update_codes_content) self.codebooks[key] = new_codes.codebooks[key] - def _rewrite_codes(self, git_management) -> None: + def _rewrite_codes(self, git_management, phase_info=None) -> None: directory = self.directory rewrite_codes_content = "**[Rewrite Codes]**\n\n" if os.path.exists(directory) and len(os.listdir(directory)) > 0: @@ -87,12 +89,35 @@ class Codes: rewrite_codes_content += os.path.join(directory, filename) + " Wrote\n" if git_management: + if not phase_info: + phase_info = "" + git_online_log = "**[Git Information]**\n\n" if self.version == 1.0: os.system("cd {}; git init".format(self.directory)) + git_online_log += "cd {}; git init\n".format(self.directory) os.system("cd {}; git add .".format(self.directory)) - os.system("cd {}; git commit -m \"{}\"".format(self.directory, self.version)) + git_online_log += "cd {}; git add .\n".format(self.directory) - log_and_print_online(rewrite_codes_content) + # check if there exist diff + completed_process = subprocess.run("cd {}; git status".format(self.directory), shell=True, text=True, + stdout=subprocess.PIPE) + if "nothing to commit" in completed_process.stdout: + self.version -= 1.0 + return + + os.system("cd {}; git commit -m \"v{}\"".format(self.directory, str(self.version) + " " + phase_info)) + git_online_log += "cd {}; git commit -m \"v{}\"\n".format(self.directory, + str(self.version) + " " + phase_info) + if self.version == 1.0: + os.system("cd {}; git submodule add ./{} {}".format(os.path.dirname(os.path.dirname(self.directory)), + "WareHouse/" + os.path.basename(self.directory), + "WareHouse/" + os.path.basename(self.directory))) + git_online_log += "cd {}; git submodule add ./{} {}\n".format( + os.path.dirname(os.path.dirname(self.directory)), + "WareHouse/" + os.path.basename(self.directory), + "WareHouse/" + os.path.basename(self.directory)) + log_and_print_online(rewrite_codes_content) + log_and_print_online(git_online_log) def _get_codes(self) -> str: content = "" diff --git a/chatdev/composed_phase.py b/chatdev/composed_phase.py index 654919a7..5bee8c2d 100644 --- a/chatdev/composed_phase.py +++ b/chatdev/composed_phase.py @@ -135,7 +135,7 @@ class ComposedPhase(ABC): """ self.update_phase_env(chat_env) - for cycle_index in range(self.cycle_num): + for cycle_index in range(1, self.cycle_num + 1): for phase_item in self.composition: assert phase_item["phaseType"] == "SimplePhase" # right now we do not support nested composition phase = phase_item['phase'] diff --git a/chatdev/phase.py b/chatdev/phase.py index 9a1ba1cd..56ddcd93 100644 --- a/chatdev/phase.py +++ b/chatdev/phase.py @@ -207,8 +207,6 @@ class Phase(ABC): question = """Answer their final discussed conclusion (Yes or No) in the discussion without any other words, e.g., "Yes" """ elif phase_name == "DemandAnalysis": question = """Answer their final product modality in the discussion without any other words, e.g., "PowerPoint" """ - # elif phase_name in [PhaseType.BRAINSTORMING]: - # question = """Conclude three most creative and imaginative brainstorm ideas from the whole discussion, in the format: "1) *; 2) *; 3) *; where '*' represents a suggestion." """ elif phase_name == "LanguageChoose": question = """Conclude the programming language being discussed for software development, in the format: "*" where '*' represents a programming language." """ elif phase_name == "EnvironmentDoc": @@ -356,7 +354,7 @@ class Coding(Phase): chat_env.update_codes(self.seminar_conclusion) if len(chat_env.codes.codebooks.keys()) == 0: raise ValueError("No Valid Codes.") - chat_env.rewrite_codes() + chat_env.rewrite_codes("Finish Coding") log_and_print_online( "**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'], self.log_filepath))) return chat_env @@ -392,7 +390,7 @@ class ArtIntegration(Phase): def update_chat_env(self, chat_env) -> ChatEnv: chat_env.update_codes(self.seminar_conclusion) - chat_env.rewrite_codes() + chat_env.rewrite_codes("Finish Art Integration") # chat_env.generate_images_from_codes() log_and_print_online( "**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'], self.log_filepath))) @@ -424,7 +422,7 @@ class CodeComplete(Phase): chat_env.update_codes(self.seminar_conclusion) if len(chat_env.codes.codebooks.keys()) == 0: raise ValueError("No Valid Codes.") - chat_env.rewrite_codes() + chat_env.rewrite_codes("Code Complete #" + str(self.phase_env["cycle_index"]) + " Finished") log_and_print_online( "**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'], self.log_filepath))) return chat_env @@ -463,7 +461,7 @@ class CodeReviewModification(Phase): def update_chat_env(self, chat_env) -> ChatEnv: if "```".lower() in self.seminar_conclusion.lower(): chat_env.update_codes(self.seminar_conclusion) - chat_env.rewrite_codes() + chat_env.rewrite_codes("Review #" + str(self.phase_env["cycle_index"]) + " Finished") log_and_print_online( "**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'], self.log_filepath))) self.phase_env['modification_conclusion'] = self.seminar_conclusion @@ -484,7 +482,7 @@ class CodeReviewHuman(Phase): def update_chat_env(self, chat_env) -> ChatEnv: if "```".lower() in self.seminar_conclusion.lower(): chat_env.update_codes(self.seminar_conclusion) - chat_env.rewrite_codes() + chat_env.rewrite_codes("Human Review #" + str(self.phase_env["cycle_index"]) + " Finished") log_and_print_online( "**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'], self.log_filepath))) return chat_env @@ -496,14 +494,14 @@ class CodeReviewHuman(Phase): f"Now you can participate in the development of the software!\n" f"The task is: {chat_env.env_dict['task_prompt']}\n" f"Please input your feedback (in one line). It can be bug report or new feature requirement.\n" - f"You are currently in the #{self.phase_env['cycle_index'] + 1} human feedback with a total of {self.phase_env['cycle_num']} feedbacks\n" + f"You are currently in the #{self.phase_env['cycle_index']} human feedback with a total of {self.phase_env['cycle_num']} feedbacks\n" f"Press [Enter] to submit.\n" f"You can type \"End\" to quit this mode at any time.\n" ) provided_comments = input(">>> ") self.phase_env["comments"] = provided_comments log_and_print_online( - f"**[User Provided Comments]**\n\n In the #{self.phase_env['cycle_index'] + 1} of total {self.phase_env['cycle_num']} comments: \n\n" + provided_comments) + f"**[User Provided Comments]**\n\n In the #{self.phase_env['cycle_index']} of total {self.phase_env['cycle_num']} comments: \n\n" + provided_comments) if provided_comments.lower() == "end": return chat_env @@ -592,7 +590,7 @@ class TestModification(Phase): def update_chat_env(self, chat_env) -> ChatEnv: if "```".lower() in self.seminar_conclusion.lower(): chat_env.update_codes(self.seminar_conclusion) - chat_env.rewrite_codes() + chat_env.rewrite_codes("Test #" + str(self.phase_env["cycle_index"]) + " Finished") log_and_print_online( "**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'], self.log_filepath))) return chat_env diff --git a/misc/github.png b/misc/github.png new file mode 100644 index 00000000..4fdb9c1b Binary files /dev/null and b/misc/github.png differ