|
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 |
|
|