| import importlib |
| import os |
| from abc import ABC, abstractmethod |
| from collections import defaultdict |
|
|
| from camel.typing import ModelType |
| from chatdev.chat_env import ChatEnv |
| from chatdev.utils import log_and_print_online |
|
|
|
|
| def check_bool(s): |
| return s.lower() == "true" |
|
|
|
|
| class ComposedPhase(ABC): |
| def __init__(self, |
| phase_name: str = None, |
| cycle_num: int = None, |
| composition: list = None, |
| config_phase: dict = None, |
| config_role: dict = None, |
| model_type: ModelType = ModelType.GPT_3_5_TURBO, |
| log_filepath: str = "" |
| ): |
| """ |
| |
| Args: |
| phase_name: name of this phase |
| cycle_num: loop times of this phase |
| composition: list of SimplePhases in this ComposePhase |
| config_phase: configuration of all SimplePhases |
| config_role: configuration of all Roles |
| """ |
|
|
| self.phase_name = phase_name |
| self.cycle_num = cycle_num |
| self.composition = composition |
| self.model_type = model_type |
| self.log_filepath = log_filepath |
|
|
| self.config_phase = config_phase |
| self.config_role = config_role |
|
|
| self.phase_env = dict() |
|
|
| |
| self.chat_turn_limit_default = 10 |
|
|
| |
| self.role_prompts = dict() |
| for role in self.config_role: |
| self.role_prompts[role] = "\n".join(self.config_role[role]) |
|
|
| |
| self.phases = dict() |
| for phase in self.config_phase: |
| assistant_role_name = self.config_phase[phase]['assistant_role_name'] |
| user_role_name = self.config_phase[phase]['user_role_name'] |
| phase_prompt = "\n".join(self.config_phase[phase]['phase_prompt']) |
| phase_module = importlib.import_module("chatdev.phase") |
| phase_class = getattr(phase_module, phase) |
| phase_instance = phase_class(assistant_role_name=assistant_role_name, |
| user_role_name=user_role_name, |
| phase_prompt=phase_prompt, |
| role_prompts=self.role_prompts, |
| phase_name=phase, |
| model_type=self.model_type, |
| log_filepath=self.log_filepath) |
| self.phases[phase] = phase_instance |
|
|
| @abstractmethod |
| def update_phase_env(self, chat_env): |
| """ |
| update self.phase_env (if needed) using chat_env, then the chatting will use self.phase_env to follow the context and fill placeholders in phase prompt |
| must be implemented in customized phase |
| the usual format is just like: |
| ``` |
| self.phase_env.update({key:chat_env[key]}) |
| ``` |
| Args: |
| chat_env: global chat chain environment |
| |
| Returns: None |
| |
| """ |
| pass |
|
|
| @abstractmethod |
| def update_chat_env(self, chat_env) -> ChatEnv: |
| """ |
| update chan_env based on the results of self.execute, which is self.seminar_conclusion |
| must be implemented in customized phase |
| the usual format is just like: |
| ``` |
| chat_env.xxx = some_func_for_postprocess(self.seminar_conclusion) |
| ``` |
| Args: |
| chat_env:global chat chain environment |
| |
| Returns: |
| chat_env: updated global chat chain environment |
| |
| """ |
| pass |
|
|
| @abstractmethod |
| def break_cycle(self, phase_env) -> bool: |
| """ |
| special conditions for early break the loop in ComposedPhase |
| Args: |
| phase_env: phase environment |
| |
| Returns: None |
| |
| """ |
| pass |
|
|
| def execute(self, chat_env) -> ChatEnv: |
| """ |
| similar to Phase.execute, but add control for breaking the loop |
| 1. receive information from environment(ComposedPhase): update the phase environment from global environment |
| 2. for each SimplePhase in ComposedPhase |
| a) receive information from environment(SimplePhase) |
| b) check loop break |
| c) execute the chatting |
| d) change the environment(SimplePhase) |
| e) check loop break |
| 3. change the environment(ComposedPhase): update the global environment using the conclusion |
| |
| Args: |
| chat_env: global chat chain environment |
| |
| Returns: |
| |
| """ |
| self.update_phase_env(chat_env) |
| for cycle_index in range(self.cycle_num): |
| for phase_item in self.composition: |
| assert phase_item["phaseType"] == "SimplePhase" |
| phase = phase_item['phase'] |
| max_turn_step = phase_item['max_turn_step'] |
| need_reflect = check_bool(phase_item['need_reflect']) |
| log_and_print_online( |
| f"**[Execute Detail]**\n\nexecute SimplePhase:[{phase}] in ComposedPhase:[{self.phase_name}], cycle {cycle_index}") |
| if phase in self.phases: |
| self.phases[phase].phase_env = self.phase_env |
| self.phases[phase].update_phase_env(chat_env) |
| if self.break_cycle(self.phases[phase].phase_env): |
| return chat_env |
| chat_env = self.phases[phase].execute(chat_env, |
| self.chat_turn_limit_default if max_turn_step <= 0 else max_turn_step, |
| need_reflect) |
| if self.break_cycle(self.phases[phase].phase_env): |
| return chat_env |
| else: |
| print(f"Phase '{phase}' is not yet implemented. \ |
| Please write its config in phaseConfig.json \ |
| and implement it in chatdev.phase") |
| chat_env = self.update_chat_env(chat_env) |
| return chat_env |
|
|
|
|
| class Art(ComposedPhase): |
| def __init__(self, **kwargs): |
| super().__init__(**kwargs) |
|
|
| def update_phase_env(self, chat_env): |
| pass |
|
|
| def update_chat_env(self, chat_env): |
| return chat_env |
|
|
| def break_cycle(self, chat_env) -> bool: |
| return False |
|
|
|
|
| class CodeCompleteAll(ComposedPhase): |
| def __init__(self, **kwargs): |
| super().__init__(**kwargs) |
|
|
| def update_phase_env(self, chat_env): |
| pyfiles = [filename for filename in os.listdir(chat_env.env_dict['directory']) if filename.endswith(".py")] |
| num_tried = defaultdict(int) |
| num_tried.update({filename: 0 for filename in pyfiles}) |
| self.phase_env = { |
| "max_num_implement": 5, |
| "pyfiles": pyfiles, |
| "num_tried": num_tried |
| } |
|
|
| def update_chat_env(self, chat_env): |
| return chat_env |
|
|
| def break_cycle(self, phase_env) -> bool: |
| if phase_env['unimplemented_file'] == "": |
| return True |
| else: |
| return False |
|
|
|
|
| class CodeReview(ComposedPhase): |
| def __init__(self, **kwargs): |
| super().__init__(**kwargs) |
|
|
| def update_phase_env(self, chat_env): |
| self.phase_env = {"modification_conclusion": ""} |
|
|
| def update_chat_env(self, chat_env): |
| return chat_env |
|
|
| def break_cycle(self, phase_env) -> bool: |
| if "<INFO> Finished".lower() in phase_env['modification_conclusion'].lower(): |
| return True |
| else: |
| return False |
|
|
|
|
| class Test(ComposedPhase): |
| def __init__(self, **kwargs): |
| super().__init__(**kwargs) |
|
|
| def update_phase_env(self, chat_env): |
| self.phase_env = dict() |
|
|
| def update_chat_env(self, chat_env): |
| return chat_env |
|
|
| def break_cycle(self, phase_env) -> bool: |
| if not phase_env['exist_bugs_flag']: |
| log_and_print_online(f"**[Test Info]**\n\nAI User (Software Test Engineer):\nTest Pass!\n") |
| return True |
| else: |
| return False |
|
|