Skip to content

Commit

Permalink
Implement model (and some factor) coder with evolving (#52)
Browse files Browse the repository at this point in the history
* store code into FBImplementation

* fix path related bugs

* fix a bug

* fix factor related small bugs

* re-submit all model related code

* new code to model coder

* finish the model evolving code

---------

Co-authored-by: xuyang1 <[email protected]>
  • Loading branch information
peteryang1 and peteryangms authored Jul 10, 2024
1 parent 44e5610 commit a0385ea
Show file tree
Hide file tree
Showing 24 changed files with 1,249 additions and 297 deletions.
23 changes: 14 additions & 9 deletions rdagent/app/model_implementation/eval.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
from pathlib import Path

from rdagent.components.coder.model_coder.CoSTEER import ModelCoSTEER
from rdagent.components.loader.task_loader import ModelImpLoader, ModelTaskLoaderJson
from rdagent.scenarios.qlib.experiment.model_experiment import (
QlibModelExperiment,
QlibModelScenario,
)

DIRNAME = Path(__file__).absolute().resolve().parent

from rdagent.components.coder.model_coder.benchmark.eval import ModelImpValEval
from rdagent.components.coder.model_coder.model import (
ModelImpLoader,
ModelTaskLoaderJson,
)
from rdagent.components.coder.model_coder.one_shot import ModelCodeWriter

bench_folder = DIRNAME.parent.parent / "components" / "task_implementation" / "model_implementation" / "benchmark"
bench_folder = DIRNAME.parent.parent / "components" / "coder" / "model_coder" / "benchmark"
mtl = ModelTaskLoaderJson(str(bench_folder / "model_dict.json"))

task_l = mtl.load()

task_l = [t for t in task_l if t.key == "A-DGN"] # FIXME: other models does not work well
task_l = [t for t in task_l if t.name == "A-DGN"] # FIXME: other models does not work well

mtg = ModelCodeWriter()
model_experiment = QlibModelExperiment(sub_tasks=task_l)
# mtg = ModelCodeWriter(scen=QlibModelScenario())
mtg = ModelCoSTEER(scen=QlibModelScenario())

impl_l = mtg.generate(task_l)
model_experiment = mtg.generate(model_experiment)

# TODO: Align it with the benchmark framework after @wenjun's refine the evaluation part.
# Currently, we just handcraft a workflow for fast evaluation.
Expand All @@ -28,7 +33,7 @@
mie = ModelImpValEval()
# Evaluation:
eval_l = []
for impl in impl_l:
for impl in model_experiment.sub_implementations:
print(impl.target_task)
gt_impl = mil.load(impl.target_task)
eval_l.append(mie.evaluate(gt_impl, impl))
Expand Down
1 change: 0 additions & 1 deletion rdagent/components/coder/factor_coder/CoSTEER/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,4 @@ def generate(self, exp: FactorExperiment) -> FactorExperiment:
if self.new_knowledge_base_path is not None:
pickle.dump(factor_knowledge_base, open(self.new_knowledge_base_path, "wb"))
self.knowledge_base = factor_knowledge_base
self.latest_factor_implementations = exp.sub_tasks
return factor_experiment
17 changes: 11 additions & 6 deletions rdagent/components/coder/factor_coder/CoSTEER/evolving_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def implement_one_factor(
implement_prompts["evolving_strategy_factor_implementation_v1_system"],
)
.render(
data_info=get_data_folder_intro(),
scenario=self.scen.get_scenario_all_desc(),
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
)
)
Expand All @@ -154,7 +154,7 @@ def implement_one_factor(
)

queried_similar_successful_knowledge_to_render = queried_similar_successful_knowledge
while True:
for _ in range(10): # max attempt to reduce the length of user_prompt
user_prompt = (
Environment(undefined=StrictUndefined)
.from_string(
Expand All @@ -163,6 +163,7 @@ def implement_one_factor(
.render(
factor_information_str=factor_information_str,
queried_similar_successful_knowledge=queried_similar_successful_knowledge_to_render,
queried_former_failed_knowledge=queried_former_failed_knowledge_to_render,
)
.strip("\n")
)
Expand All @@ -187,8 +188,9 @@ def implement_one_factor(
# ast.parse(code)
factor_implementation = FileBasedFactorImplementation(
target_task,
code,
)
factor_implementation.prepare()
factor_implementation.inject_code(**{"factor.py": code})

return factor_implementation

Expand Down Expand Up @@ -255,7 +257,7 @@ def implement_one_factor(
queried_similar_error_knowledge_to_render = queried_similar_error_knowledge
error_summary_critics = ""
# 动态地防止prompt超长
while True:
for _ in range(10): # max attempt to reduce the length of user_prompt
# 总结error(可选)
if (
error_summary
Expand All @@ -266,6 +268,7 @@ def implement_one_factor(
Environment(undefined=StrictUndefined)
.from_string(implement_prompts["evolving_strategy_error_summary_v2_system"])
.render(
scenario=self.scen.get_scenario_all_desc(),
factor_information_str=target_factor_task_information,
code_and_feedback=queried_former_failed_knowledge_to_render[
-1
Expand All @@ -276,7 +279,7 @@ def implement_one_factor(
session_summary = APIBackend(use_chat_cache=False).build_chat_session(
session_system_prompt=error_summary_system_prompt,
)
while True:
for _ in range(10): # max attempt to reduce the length of error_summary_user_prompt
error_summary_user_prompt = (
Environment(undefined=StrictUndefined)
.from_string(implement_prompts["evolving_strategy_error_summary_v2_user"])
Expand Down Expand Up @@ -332,5 +335,7 @@ def implement_one_factor(
json_mode=True,
)
code = json.loads(response)["code"]
factor_implementation = FileBasedFactorImplementation(target_task, code)
factor_implementation = FileBasedFactorImplementation(target_task)
factor_implementation.prepare()
factor_implementation.inject_code(**{"factor.py": code})
return factor_implementation
2 changes: 1 addition & 1 deletion rdagent/components/coder/factor_coder/CoSTEER/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def LLMSelect(
)
)

while True:
for _ in range(10): # max attempt to reduce the length of user_prompt
user_prompt = (
Environment(undefined=StrictUndefined)
.from_string(
Expand Down
34 changes: 16 additions & 18 deletions rdagent/components/coder/factor_coder/factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,15 @@ class FileBasedFactorImplementation(FBImplementation):

def __init__(
self,
target_task: FactorTask,
code,
*args,
executed_factor_value_dataframe=None,
raise_exception=False,
**kwargs,
) -> None:
super().__init__(target_task)
self.code = code
super().__init__(*args, **kwargs)
self.executed_factor_value_dataframe = executed_factor_value_dataframe
self.logger = RDAgentLog()
self.raise_exception = raise_exception
self.workspace_path = Path(
FACTOR_IMPLEMENT_SETTINGS.file_based_execution_workspace,
) / str(uuid.uuid4())

@staticmethod
def link_data_to_workspace(data_path: Path, workspace_path: Path):
Expand All @@ -98,8 +94,10 @@ def execute_desc(self):
raise NotImplementedError

def prepare(self, *args, **kwargs):
# TODO move the prepare part code in execute into here
return super().prepare(*args, **kwargs)
self.workspace_path = Path(
FACTOR_IMPLEMENT_SETTINGS.file_based_execution_workspace,
) / str(uuid.uuid4())
self.workspace_path.mkdir(exist_ok=True, parents=True)

def execute(self, store_result: bool = False) -> Tuple[str, pd.DataFrame]:
"""
Expand All @@ -114,7 +112,7 @@ def execute(self, store_result: bool = False) -> Tuple[str, pd.DataFrame]:
parameters:
store_result: if True, store the factor value in the instance variable, this feature is to be used in the gt implementation to avoid multiple execution on the same gt implementation
"""
if self.code is None:
if self.code_dict is None or "factor.py" not in self.code_dict:
if self.raise_exception:
raise CodeFormatException(self.FB_CODE_NOT_SET)
else:
Expand All @@ -123,7 +121,7 @@ def execute(self, store_result: bool = False) -> Tuple[str, pd.DataFrame]:
with FileLock(self.workspace_path / "execution.lock"):
if FACTOR_IMPLEMENT_SETTINGS.enable_execution_cache:
# NOTE: cache the result for the same code
target_file_name = md5_hash(self.code)
target_file_name = md5_hash(self.code_dict["factor.py"])
cache_file_path = (
Path(FACTOR_IMPLEMENT_SETTINGS.implementation_execution_cache_location) / f"{target_file_name}.pkl"
)
Expand All @@ -142,10 +140,9 @@ def execute(self, store_result: bool = False) -> Tuple[str, pd.DataFrame]:
source_data_path = Path(
FACTOR_IMPLEMENT_SETTINGS.file_based_execution_data_folder,
)
self.workspace_path.mkdir(exist_ok=True, parents=True)

source_data_path.mkdir(exist_ok=True, parents=True)
code_path = self.workspace_path / f"{self.target_task.factor_name}.py"
code_path.write_text(self.code)
code_path = self.workspace_path / f"factor.py"

self.link_data_to_workspace(source_data_path, self.workspace_path)

Expand Down Expand Up @@ -212,10 +209,11 @@ def __repr__(self) -> str:
@staticmethod
def from_folder(task: FactorTask, path: Union[str, Path], **kwargs):
path = Path(path)
factor_path = (path / task.factor_name).with_suffix(".py")
with factor_path.open("r") as f:
code = f.read()
return FileBasedFactorImplementation(task, code=code, **kwargs)
code_dict = {}
for file_path in path.iterdir():
if file_path.suffix == ".py":
code_dict[file_path.name] = file_path.read_text()
return FileBasedFactorImplementation(target_task=task, code_dict=code_dict, **kwargs)


class FactorExperiment(Experiment[FactorTask, FileBasedFactorImplementation]): ...
16 changes: 9 additions & 7 deletions rdagent/components/coder/factor_coder/prompts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ evaluator_code_feedback_v1_system: |-
If the ground truth code is provided, your critic should only consider checking whether the user's code is align with the ground truth code since the ground truth is definitely correct.
If the ground truth code is not provided, your critic should consider checking whether the user's code is reasonable and correct.
Notice that your critics are not for user to debug the code. They are sent to the coding agent to correct the code. So don't give any following items for the user to check like "Please check the code line XXX".
You should provide the suggestion to each of your critic to help the user improve the code. Please response the critic in the following format. Here is an example structure for the output:
critic 1: The critic message to critic 1
critic 2: The critic message to critic 2
Expand Down Expand Up @@ -47,12 +49,10 @@ evolving_strategy_factor_implementation_v1_system: |-
{% if queried_former_failed_knowledge|length != 0 %}
--------------Your former latest attempt:---------------
{% for former_failed_knowledge in queried_former_failed_knowledge %}
=====Code to implementation {{ loop.index }}=====
{{ former_failed_knowledge.implementation.code }}
=====Feedback to implementation {{ loop.index }}=====
{{ former_failed_knowledge.feedback }}
{% endfor %}
=====Code to the former implementation=====
{{ queried_former_failed_knowledge[-1].implementation.code }}
=====Feedback to the former implementation=====
{{ queried_former_failed_knowledge[-1].feedback }}
{% endif %}
Please response the code in the following json format. Here is an example structure for the JSON output:
Expand Down Expand Up @@ -118,7 +118,9 @@ evolving_strategy_factor_implementation_v2_user: |-
{% endif %}
evolving_strategy_error_summary_v2_system: |-
You are doing the following task:
User is trying to implement some factors in the following scenario:
{{ scenario }}
User is doing the following task:
{{factor_information_str}}
You have written some code but it meets errors like the following:
Expand Down
86 changes: 86 additions & 0 deletions rdagent/components/coder/model_coder/CoSTEER/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import pickle
from pathlib import Path

from rdagent.components.coder.model_coder.conf import MODEL_IMPL_SETTINGS
from rdagent.components.coder.model_coder.CoSTEER.evaluators import (
ModelCoderMultiEvaluator,
)
from rdagent.components.coder.model_coder.CoSTEER.evolvable_subjects import (
ModelEvolvingItem,
)
from rdagent.components.coder.model_coder.CoSTEER.evolving_strategy import (
ModelCoderEvolvingStrategy,
)
from rdagent.components.coder.model_coder.CoSTEER.knowledge_management import (
ModelKnowledgeBase,
ModelRAGStrategy,
)
from rdagent.components.coder.model_coder.model import ModelExperiment
from rdagent.core.evolving_agent import RAGEvoAgent
from rdagent.core.task_generator import TaskGenerator


class ModelCoSTEER(TaskGenerator[ModelExperiment]):
def __init__(
self,
*args,
with_knowledge: bool = True,
with_feedback: bool = True,
knowledge_self_gen: bool = True,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
self.max_loop = MODEL_IMPL_SETTINGS.max_loop
self.knowledge_base_path = (
Path(MODEL_IMPL_SETTINGS.knowledge_base_path)
if MODEL_IMPL_SETTINGS.knowledge_base_path is not None
else None
)
self.new_knowledge_base_path = (
Path(MODEL_IMPL_SETTINGS.new_knowledge_base_path)
if MODEL_IMPL_SETTINGS.new_knowledge_base_path is not None
else None
)
self.with_knowledge = with_knowledge
self.with_feedback = with_feedback
self.knowledge_self_gen = knowledge_self_gen
self.evolving_strategy = ModelCoderEvolvingStrategy(scen=self.scen)
self.model_evaluator = ModelCoderMultiEvaluator(scen=self.scen)

def load_or_init_knowledge_base(self, former_knowledge_base_path: Path = None, component_init_list: list = []):
if former_knowledge_base_path is not None and former_knowledge_base_path.exists():
model_knowledge_base = pickle.load(open(former_knowledge_base_path, "rb"))
if not isinstance(model_knowledge_base, ModelKnowledgeBase):
raise ValueError("The former knowledge base is not compatible with the current version")
else:
model_knowledge_base = ModelKnowledgeBase()

return model_knowledge_base

def generate(self, exp: ModelExperiment) -> ModelExperiment:
# init knowledge base
model_knowledge_base = self.load_or_init_knowledge_base(
former_knowledge_base_path=self.knowledge_base_path,
component_init_list=[],
)
# init rag method
self.rag = ModelRAGStrategy(model_knowledge_base)

# init intermediate items
model_experiment = ModelEvolvingItem(sub_tasks=exp.sub_tasks)

self.evolve_agent = RAGEvoAgent(max_loop=self.max_loop, evolving_strategy=self.evolving_strategy, rag=self.rag)

model_experiment = self.evolve_agent.multistep_evolve(
model_experiment,
self.model_evaluator,
with_knowledge=self.with_knowledge,
with_feedback=self.with_feedback,
knowledge_self_gen=self.knowledge_self_gen,
)

# save new knowledge base
if self.new_knowledge_base_path is not None:
pickle.dump(model_knowledge_base, open(self.new_knowledge_base_path, "wb"))
self.knowledge_base = model_knowledge_base
return model_experiment
Loading

0 comments on commit a0385ea

Please sign in to comment.