2024-11-25 16:27:34 +08:00
from __future__ import annotations
from abc import abstractmethod
from rdagent.components.coder.CoSTEER.config import CoSTEERSettings
2025-02-16 01:40:44 +08:00
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEERMultiFeedback ,
CoSTEERSingleFeedback ,
)
2024-11-25 16:27:34 +08:00
from rdagent.components.coder.CoSTEER.evolvable_subjects import EvolvingItem
from rdagent.components.coder.CoSTEER.knowledge_management import (
CoSTEERQueriedKnowledge ,
)
from rdagent.core.conf import RD_AGENT_SETTINGS
2025-02-16 01:40:44 +08:00
from rdagent.core.evolving_framework import EvolvingStrategy , EvoStep , QueriedKnowledge
2025-02-11 20:50:19 +08:00
from rdagent.core.experiment import FBWorkspace , Task
from rdagent.core.scenario import Scenario
2024-11-25 16:27:34 +08:00
from rdagent.core.utils import multiprocessing_wrapper
class MultiProcessEvolvingStrategy ( EvolvingStrategy ):
2025-08-04 17:38:27 +08:00
KEY_CHANGE_SUMMARY = "__change_summary__" # Optional key for the summary of the change of evolving subjects
2025-10-22 17:35:18 +08:00
def __init__ ( self , scen : Scenario , settings : CoSTEERSettings , improve_mode : bool = False ):
2024-11-25 16:27:34 +08:00
super () . __init__ ( scen )
self . settings = settings
2025-10-22 17:35:18 +08:00
self . improve_mode = improve_mode # improve mode means we only implement the task which has failed before. The main diff is the first loop will not implement all tasks.
2024-11-25 16:27:34 +08:00
@abstractmethod
def implement_one_task (
self ,
target_task : Task ,
2025-02-16 01:40:44 +08:00
queried_knowledge : QueriedKnowledge | None = None ,
2025-01-17 22:53:05 +08:00
workspace : FBWorkspace | None = None ,
2025-02-16 01:40:44 +08:00
prev_task_feedback : CoSTEERSingleFeedback | None = None ,
2025-01-17 22:53:05 +08:00
) -> 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>)
2025-02-16 01:40:44 +08:00
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.
2025-01-17 22:53:05 +08:00
Return
------
The new files {<filename>: <content>} to update the workspace.
2025-08-04 17:38:27 +08:00
- Special Keys: self.KEY_CHANGE_SUMMARY;
2025-01-17 22:53:05 +08:00
"""
2024-11-25 16:27:34 +08:00
raise NotImplementedError
@abstractmethod
2025-02-16 01:40:44 +08:00
def assign_code_list_to_evo ( self , code_list : list [ dict ], evo : EvolvingItem ) -> None :
2024-11-25 16:27:34 +08:00
"""
Assign the code list to the evolving item.
2025-02-16 01:40:44 +08:00
Due to the implement_one_task take `workspace` as input and output the `modification`.
2025-03-18 17:15:08 +09:00
We should apply implementation to evo
2025-02-16 01:40:44 +08:00
2024-11-25 16:27:34 +08:00
The code list is aligned with the evolving item's sub-tasks.
If a task is not implemented, put a None in the list.
"""
raise NotImplementedError
def evolve (
self ,
* ,
evo : EvolvingItem ,
queried_knowledge : CoSTEERQueriedKnowledge | None = None ,
2025-02-16 01:40:44 +08:00
evolving_trace : list [ EvoStep ] = [],
2024-11-25 16:27:34 +08:00
** kwargs ,
) -> EvolvingItem :
2025-07-31 13:06:28 +08:00
code_list = [ None for _ in range ( len ( evo . sub_tasks ))]
2025-10-23 16:00:23 +08:00
last_feedback = None
if len ( evolving_trace ) > 0 :
last_feedback = evolving_trace [ - 1 ] . feedback
assert isinstance ( last_feedback , CoSTEERMultiFeedback )
2024-11-25 16:27:34 +08:00
# 1.找出需要evolve的task
2025-03-26 22:48:01 +08:00
to_be_finished_task_index : list [ int ] = []
2024-11-25 16:27:34 +08:00
for index , target_task in enumerate ( evo . sub_tasks ):
target_task_desc = target_task . get_task_information ()
if target_task_desc in queried_knowledge . success_task_to_knowledge_dict :
2025-03-26 22:48:01 +08:00
# NOTE: very weird logic:
# it depends on the knowledge to set the already finished task
2025-07-31 13:06:28 +08:00
code_list [ index ] = queried_knowledge . success_task_to_knowledge_dict [
2024-11-25 16:27:34 +08:00
target_task_desc
2025-07-31 13:06:28 +08:00
] . implementation . file_dict
2025-10-23 17:09:18 +08:00
else :
# Schedule the task only if:
# - it is not marked failed
# - and (in improve mode) we actually have prior failure feedback to act on
skip_for_improve_mode = self . improve_mode and (
last_feedback is None
or ( isinstance ( last_feedback , CoSTEERMultiFeedback ) and last_feedback [ index ] is None )
2025-10-23 16:00:23 +08:00
)
2025-10-23 17:09:18 +08:00
if target_task_desc not in queried_knowledge . failed_task_info_set and not skip_for_improve_mode :
to_be_finished_task_index . append ( index )
if skip_for_improve_mode :
code_list [ index ] = (
{}
) # empty implementation for skipped task, but assign_code_list_to_evo will still assign it
2024-11-25 16:27:34 +08:00
result = multiprocessing_wrapper (
[
2025-01-17 22:53:05 +08:00
(
self . implement_one_task ,
2025-02-16 01:40:44 +08:00
(
evo . sub_tasks [ target_index ],
queried_knowledge ,
evo . experiment_workspace ,
None if last_feedback is None else last_feedback [ target_index ],
),
2025-01-17 22:53:05 +08:00
)
2024-11-25 16:27:34 +08:00
for target_index in to_be_finished_task_index
],
n = RD_AGENT_SETTINGS . multi_proc_n ,
)
for index , target_index in enumerate ( to_be_finished_task_index ):
code_list [ target_index ] = result [ index ]
evo = self . assign_code_list_to_evo ( code_list , evo )
return evo