refactor: refactor core framework to better propogate feedbacks (#599)

* refactor: Update type annotations and remove unused class in evolving modules

* refactor: Simplify evolving agent and feedback handling in CoSTEER module

* lint & CI

* mypy

* ruff for core

* mypy

* refactor: remove unnecessary comments and update feedback handling logic

* refactor: Add prev_task_feedback parameter to evolving strategies

* feat: Clear folder before extracting zip file in DockerEnv

* fix: Correct retrieval of last experiment from history
This commit is contained in:
you-n-g
2025-02-16 01:40:44 +08:00
committed by GitHub
parent 241c05ccac
commit ffc85936f1
24 changed files with 261 additions and 148 deletions
+41 -13
View File
@@ -2,8 +2,8 @@ import pickle
from pathlib import Path
from rdagent.components.coder.CoSTEER.config import CoSTEERSettings
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiFeedback
from rdagent.components.coder.CoSTEER.evolvable_subjects import EvolvingItem
from rdagent.components.coder.CoSTEER.evolving_agent import FilterFailedRAGEvoAgent
from rdagent.components.coder.CoSTEER.knowledge_management import (
CoSTEERKnowledgeBaseV1,
CoSTEERKnowledgeBaseV2,
@@ -11,8 +11,9 @@ from rdagent.components.coder.CoSTEER.knowledge_management import (
CoSTEERRAGStrategyV2,
)
from rdagent.core.developer import Developer
from rdagent.core.evaluation import Evaluator
from rdagent.core.evolving_agent import EvolvingStrategy
from rdagent.core.evaluation import Evaluator, Feedback
from rdagent.core.evolving_agent import EvolvingStrategy, RAGEvoAgent
from rdagent.core.exception import CoderError
from rdagent.core.experiment import Experiment
from rdagent.log import rdagent_logger as logger
@@ -83,9 +84,9 @@ class CoSTEER(Developer[Experiment]):
def develop(self, exp: Experiment) -> Experiment:
# init intermediate items
experiment = EvolvingItem.from_experiment(exp)
evo_exp = EvolvingItem.from_experiment(exp)
self.evolve_agent = FilterFailedRAGEvoAgent(
self.evolve_agent = RAGEvoAgent(
max_loop=self.max_loop,
evolving_strategy=self.evolving_strategy,
rag=self.rag,
@@ -94,16 +95,43 @@ class CoSTEER(Developer[Experiment]):
knowledge_self_gen=self.knowledge_self_gen,
)
experiment = self.evolve_agent.multistep_evolve(
experiment,
self.evaluator,
filter_final_evo=self.filter_final_evo,
)
for evo_exp in self.evolve_agent.multistep_evolve(evo_exp, self.evaluator):
assert isinstance(evo_exp, Experiment) # multiple inheritance
logger.log_object(evo_exp.sub_workspace_list, tag="evolving code")
for sw in evo_exp.sub_workspace_list:
logger.info(f"evolving code workspace: {sw}")
if self.with_feedback and self.filter_final_evo:
evo_exp = self._exp_postprocess_by_feedback(evo_exp, self.evolve_agent.evolving_trace[-1].feedback)
# save new knowledge base
if self.new_knowledge_base_path is not None:
pickle.dump(self.knowledge_base, open(self.new_knowledge_base_path, "wb"))
with self.new_knowledge_base_path.open("wb") as f:
pickle.dump(self.knowledge_base, f)
logger.info(f"New knowledge base saved to {self.new_knowledge_base_path}")
exp.sub_workspace_list = experiment.sub_workspace_list
exp.experiment_workspace = experiment.experiment_workspace
exp.sub_workspace_list = evo_exp.sub_workspace_list
exp.experiment_workspace = evo_exp.experiment_workspace
return exp
def _exp_postprocess_by_feedback(self, evo: Experiment, feedback: CoSTEERMultiFeedback) -> Experiment:
"""
Responsibility:
- Raise Error if it failed to handle the develop task
-
"""
assert isinstance(evo, Experiment)
assert isinstance(feedback, CoSTEERMultiFeedback)
assert len(evo.sub_workspace_list) == len(feedback)
# FIXME: when whould the feedback be None?
failed_feedbacks = [
f"- feedback{index + 1:02d}:\n - execution: {f.execution}\n - return_checking: {f.return_checking}\n - code: {f.code}"
for index, f in enumerate(feedback)
if f is not None and not f.final_decision
]
if len(failed_feedbacks) == len(feedback):
feedback_summary = "\n".join(failed_feedbacks)
raise CoderError(f"All tasks are failed:\n{feedback_summary}")
return evo
+31 -10
View File
@@ -1,6 +1,6 @@
from abc import abstractmethod
from dataclasses import dataclass
from typing import List
from typing import TYPE_CHECKING, List
from rdagent.components.coder.CoSTEER.evolvable_subjects import EvolvingItem
from rdagent.core.conf import RD_AGENT_SETTINGS
@@ -10,6 +10,9 @@ from rdagent.core.experiment import Task, Workspace
from rdagent.core.utils import multiprocessing_wrapper
from rdagent.log import rdagent_logger as logger
if TYPE_CHECKING:
from rdagent.core.scenario import Scenario
# TODO:
# 1. It seems logically sound, but we currently lack a scenario to apply it.
# 2. If it proves to be useful, relocate it to a more general location.
@@ -113,14 +116,35 @@ This implementation is {'SUCCESS' if self.final_decision else 'FAIL'}.
"""
class CoSTEERMultiFeedback(
Feedback,
List[CoSTEERSingleFeedback],
):
class CoSTEERMultiFeedback(Feedback):
"""Feedback contains a list, each element is the corresponding feedback for each factor implementation."""
def __init__(self, feedback_list: List[CoSTEERSingleFeedback]) -> None:
self.feedback_list = feedback_list
def __getitem__(self, index: int) -> CoSTEERSingleFeedback:
return self.feedback_list[index]
def __len__(self) -> int:
return len(self.feedback_list)
def append(self, feedback: CoSTEERSingleFeedback) -> None:
self.feedback_list.append(feedback)
def __iter__(self):
return iter(self.feedback_list)
def __bool__(self):
return all(feedback.final_decision for feedback in self.feedback_list)
class CoSTEEREvaluator(Evaluator):
def __init__(
self,
scen: "Scenario",
) -> None:
self.scen = scen
# TODO:
# I think we should have unified interface for all evaluates, for examples.
# So we should adjust the interface of other factors
@@ -135,7 +159,7 @@ class CoSTEEREvaluator(Evaluator):
raise NotImplementedError("Please implement the `evaluator` method")
class CoSTEERMultiEvaluator(Evaluator):
class CoSTEERMultiEvaluator(CoSTEEREvaluator):
"""This is for evaluation of experiment. Due to we have multiple tasks, so we will return a list of evaluation feebacks"""
def __init__(self, single_evaluator: CoSTEEREvaluator, *args, **kwargs) -> None:
@@ -164,9 +188,6 @@ class CoSTEERMultiEvaluator(Evaluator):
n=RD_AGENT_SETTINGS.multi_proc_n,
)
for index in range(len(evo.sub_tasks)):
evo.sub_workspace_list[index].feedback = multi_implementation_feedback[index]
final_decision = [
None if single_feedback is None else single_feedback.final_decision
for single_feedback in multi_implementation_feedback
@@ -177,4 +198,4 @@ class CoSTEERMultiEvaluator(Evaluator):
if final_decision[index]:
evo.sub_tasks[index].factor_implementation = True
return multi_implementation_feedback
return CoSTEERMultiFeedback(multi_implementation_feedback)
@@ -1,30 +0,0 @@
from rdagent.components.coder.CoSTEER.evolvable_subjects import EvolvingItem
from rdagent.core.evolving_agent import RAGEvoAgent
from rdagent.core.evolving_framework import EvolvableSubjects
from rdagent.core.exception import CoderError
class FilterFailedRAGEvoAgent(RAGEvoAgent):
def filter_evolvable_subjects_by_feedback(self, evo: EvolvableSubjects, feedback: list) -> EvolvableSubjects:
assert isinstance(evo, EvolvingItem)
# FIXME: the list does not align with the annotation; It should be MultipleFeedback instead of a list of feedbacks
assert isinstance(feedback, list)
assert len(evo.sub_workspace_list) == len(feedback)
for index in range(len(evo.sub_workspace_list)):
evo.sub_workspace_list[index].feedback = None
if evo.sub_workspace_list[index] is not None and feedback[index] is not None and not feedback[index]:
evo.sub_workspace_list[index].clear()
failed_feedbacks = [
f"- feedback{index + 1:02d}:\n - execution: {f.execution}\n - return_checking: {f.return_checking}\n - code: {f.code}"
for index, f in enumerate(feedback)
if f is not None and not f.final_decision
]
if len(failed_feedbacks) == len(feedback):
feedback_summary = "\n".join(failed_feedbacks)
raise CoderError(f"All tasks are failed:\n{feedback_summary}")
return evo
@@ -4,13 +4,17 @@ from abc import abstractmethod
from pathlib import Path
from rdagent.components.coder.CoSTEER.config import CoSTEERSettings
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEERMultiFeedback,
CoSTEERSingleFeedback,
)
from rdagent.components.coder.CoSTEER.evolvable_subjects import EvolvingItem
from rdagent.components.coder.CoSTEER.knowledge_management import (
CoSTEERQueriedKnowledge,
)
from rdagent.components.coder.CoSTEER.scheduler import random_select
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.core.evolving_framework import EvolvingStrategy, QueriedKnowledge
from rdagent.core.evolving_framework import EvolvingStrategy, EvoStep, QueriedKnowledge
from rdagent.core.experiment import FBWorkspace, Task
from rdagent.core.prompts import Prompts
from rdagent.core.scenario import Scenario
@@ -28,14 +32,27 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
def implement_one_task(
self,
target_task: Task,
queried_knowledge: QueriedKnowledge = None,
queried_knowledge: QueriedKnowledge | None = None,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> dict[str, str]: # FIXME: fix interface of previous implement
"""
This method will input the task & current workspace,
and output the modification to applied to the workspace.
(i.e. replace the content <filename> with <content>)
Parameters
----------
target_task : Task
queried_knowledge : QueriedKnowledge | None
workspace : FBWorkspace | None
prev_task_feedback : CoSTEERSingleFeedback | None
task feedback for previous evolving step
None indicate it is the first loop.
Return
------
The new files {<filename>: <content>} to update the workspace.
@@ -54,10 +71,13 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
return random_select(to_be_finished_task_index, evo, selected_num, queried_knowledge, scen)
@abstractmethod
def assign_code_list_to_evo(self, code_list: list, evo: EvolvingItem) -> None:
def assign_code_list_to_evo(self, code_list: list[dict], evo: EvolvingItem) -> None:
"""
Assign the code list to the evolving item.
Due to the implement_one_task take `workspace` as input and output the `modification`.
We should apply implmentation to evo
The code list is aligned with the evolving item's sub-tasks.
If a task is not implemented, put a None in the list.
"""
@@ -68,6 +88,7 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
*,
evo: EvolvingItem,
queried_knowledge: CoSTEERQueriedKnowledge | None = None,
evolving_trace: list[EvoStep] = [],
**kwargs,
) -> EvolvingItem:
# 1.找出需要evolve的task
@@ -93,11 +114,20 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
to_be_finished_task_index, evo, self.settings.select_threshold, queried_knowledge, self.scen
)
last_feedback = None
if len(evolving_trace) > 0:
last_feedback = evolving_trace[-1].feedback
assert isinstance(last_feedback, CoSTEERMultiFeedback)
result = multiprocessing_wrapper(
[
(
self.implement_one_task,
(evo.sub_tasks[target_index], queried_knowledge, evo.experiment_workspace),
(
evo.sub_tasks[target_index],
queried_knowledge,
evo.experiment_workspace,
None if last_feedback is None else last_feedback[target_index],
),
)
for target_index in to_be_finished_task_index
],
@@ -110,8 +140,4 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
evo = self.assign_code_list_to_evo(code_list, evo)
evo.corresponding_selection = to_be_finished_task_index
# After implementation, the feedback should be reset
for workspace in evo.sub_workspace_list:
workspace.feedback = None
return evo
@@ -15,7 +15,10 @@ import json
from rdagent.components.coder.CoSTEER import CoSTEER
from rdagent.components.coder.CoSTEER.config import CoSTEER_SETTINGS
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiEvaluator
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEERMultiEvaluator,
CoSTEERSingleFeedback,
)
from rdagent.components.coder.CoSTEER.evolving_strategy import (
MultiProcessEvolvingStrategy,
)
@@ -37,6 +40,7 @@ class EnsembleMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
target_task: EnsembleTask,
queried_knowledge: CoSTEERQueriedKnowledge | None = None,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> dict[str, str]:
# Get task information for knowledge querying
ensemble_information_str = target_task.get_task_information()
@@ -74,7 +78,7 @@ class EnsembleMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
user_prompt = T(".prompts:ensemble_coder.user").r(
ensemble_spec=workspace.file_dict["spec/ensemble.md"],
latest_code=workspace.file_dict.get("ensemble.py"),
latest_code_feedback=workspace.feedback,
latest_code_feedback=prev_task_feedback,
)
for _ in range(5):
@@ -2,7 +2,10 @@ import json
from rdagent.components.coder.CoSTEER import CoSTEER
from rdagent.components.coder.CoSTEER.config import CoSTEER_SETTINGS
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiEvaluator
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEERMultiEvaluator,
CoSTEERSingleFeedback,
)
from rdagent.components.coder.CoSTEER.evolving_strategy import (
MultiProcessEvolvingStrategy,
)
@@ -24,6 +27,7 @@ class FeatureMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
target_task: FeatureTask,
queried_knowledge: CoSTEERQueriedKnowledge | None = None,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> dict[str, str]:
# return a workspace with "load_data.py", "spec/load_data.md" inside
# assign the implemented code to the new workspace.
@@ -59,7 +63,7 @@ class FeatureMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
user_prompt = T(".prompts:feature_coder.user").r(
feature_spec=workspace.file_dict["spec/feature.md"],
latest_code=workspace.file_dict.get("feature.py"),
latest_code_feedback=workspace.feedback,
latest_code_feedback=prev_task_feedback,
)
for _ in range(5):
@@ -5,7 +5,10 @@ from jinja2 import Environment, StrictUndefined
from rdagent.components.coder.CoSTEER import CoSTEER
from rdagent.components.coder.CoSTEER.config import CoSTEER_SETTINGS
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiEvaluator
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEERMultiEvaluator,
CoSTEERSingleFeedback,
)
from rdagent.components.coder.CoSTEER.evolving_strategy import (
MultiProcessEvolvingStrategy,
)
@@ -30,6 +33,7 @@ class ModelMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
target_task: ModelTask,
queried_knowledge: CoSTEERQueriedKnowledge | None = None,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> dict[str, str]:
model_information_str = target_task.get_task_information()
@@ -74,7 +78,7 @@ class ModelMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
latest_model_code=workspace.get_codes(
r"^model_(?!test)\w+\.py$"
), # TODO: If we have high failure rate here, we should clean this step with less information.
latest_code_feedback=workspace.feedback,
latest_code_feedback=prev_task_feedback,
)
for _ in range(5):
@@ -26,7 +26,10 @@ import json
from rdagent.components.coder.CoSTEER import CoSTEER
from rdagent.components.coder.CoSTEER.config import CoSTEER_SETTINGS
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiEvaluator
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEERMultiEvaluator,
CoSTEERSingleFeedback,
)
from rdagent.components.coder.CoSTEER.evolving_strategy import (
MultiProcessEvolvingStrategy,
)
@@ -51,6 +54,7 @@ class DataLoaderMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
target_task: DataLoaderTask,
queried_knowledge: CoSTEERQueriedKnowledge | None = None,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> dict[str, str]:
# return a workspace with "load_data.py", "spec/load_data.md" inside
# assign the implemented code to the new workspace.
@@ -134,7 +138,7 @@ class DataLoaderMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
data_loader_spec=data_loader_spec,
folder_spec=data_folder_info,
latest_code=workspace.file_dict.get("load_data.py"),
latest_code_feedback=workspace.feedback,
latest_code_feedback=prev_task_feedback,
)
for _ in range(5):
@@ -2,7 +2,10 @@ import json
from rdagent.components.coder.CoSTEER import CoSTEER
from rdagent.components.coder.CoSTEER.config import CoSTEER_SETTINGS
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiEvaluator
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEERMultiEvaluator,
CoSTEERSingleFeedback,
)
from rdagent.components.coder.CoSTEER.evolving_strategy import (
MultiProcessEvolvingStrategy,
)
@@ -26,6 +29,7 @@ class WorkflowMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
target_task: WorkflowTask,
queried_knowledge: CoSTEERQueriedKnowledge | None = None,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> dict[str, str]:
# competition_info = self.scen.competition_descriptions
workflow_information_str = target_task.get_task_information()
@@ -64,7 +68,7 @@ class WorkflowMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
ensemble_code=workspace.file_dict["ensemble.py"],
latest_code=workspace.file_dict.get("main.py"),
workflow_spec=workspace.file_dict["spec/workflow.md"],
latest_code_feedback=workspace.feedback,
latest_code_feedback=prev_task_feedback,
)
for _ in range(5):
@@ -5,6 +5,7 @@ from rdagent.components.coder.factor_coder.evaluators import FactorEvaluatorForC
from rdagent.components.coder.factor_coder.evolving_strategy import (
FactorMultiProcessEvolvingStrategy,
)
from rdagent.core.experiment import Experiment
from rdagent.core.scenario import Scenario
@@ -20,3 +21,11 @@ class FactorCoSTEER(CoSTEER):
es = FactorMultiProcessEvolvingStrategy(scen=scen, settings=FACTOR_COSTEER_SETTINGS)
super().__init__(*args, settings=setting, eva=eva, es=es, evolving_version=2, scen=scen, **kwargs)
def develop(self, exp: Experiment) -> Experiment:
try:
exp = super().develop(exp)
finally:
es = self.evolve_agent.evolving_trace[-1]
exp.prop_dev_feedback = es.feedback
return exp
@@ -5,6 +5,7 @@ from pathlib import Path
from jinja2 import Environment, StrictUndefined
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERSingleFeedback
from rdagent.components.coder.CoSTEER.evolving_strategy import (
MultiProcessEvolvingStrategy,
)
@@ -74,6 +75,7 @@ class FactorMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
target_task: FactorTask,
queried_knowledge: CoSTEERQueriedKnowledge,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> str:
target_factor_task_information = target_task.get_task_information()
@@ -4,6 +4,7 @@ from pathlib import Path
from jinja2 import Environment, StrictUndefined
from rdagent.components.coder.CoSTEER.config import CoSTEER_SETTINGS
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERSingleFeedback
from rdagent.components.coder.CoSTEER.evolving_strategy import (
MultiProcessEvolvingStrategy,
)
@@ -30,6 +31,7 @@ class ModelMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
target_task: ModelTask,
queried_knowledge: CoSTEERQueriedKnowledge = None,
workspace: FBWorkspace | None = None,
prev_task_feedback: CoSTEERSingleFeedback | None = None,
) -> str:
model_information_str = target_task.get_task_information()