feat: add user interaction in data science scenario (#1251)

* feat: add interactor classes and user interaction handling for experiments

* update code

* use fragment retry mechanism instead of rerun()

* fix a bug

* integrate user instructions into proposal and coder

* fix CI

* fix CI

* feat: add approval option for user instructions submission

* feat: enhance user instructions handling in Task and DSExperiment classes

* fix CI

* add user instructions into hypothesis rewrite

* add interface to command line

---------

Co-authored-by: Bowen Xian <xianbowen@outlook.com>
This commit is contained in:
Xu Yang
2025-09-18 11:10:12 +08:00
committed by GitHub
parent 121b2dbe48
commit 07c228ef9f
13 changed files with 448 additions and 12 deletions
+9
View File
@@ -61,6 +61,14 @@ def server_ui(port=19899):
subprocess.run(["python", "rdagent/log/server/app.py", f"--port={port}"])
def ds_user_interact(port=19900):
"""
start web app to show the log traces in real time
"""
commands = ["streamlit", "run", "rdagent/log/ui/ds_user_interact.py", f"--server.port={port}"]
subprocess.run(commands)
app.command(name="fin_factor")(fin_factor)
app.command(name="fin_model")(fin_model)
app.command(name="fin_quant")(fin_quant)
@@ -72,6 +80,7 @@ app.command(name="ui")(ui)
app.command(name="server_ui")(server_ui)
app.command(name="health_check")(health_check)
app.command(name="collect_info")(collect_info)
app.command(name="ds_user_interact")(ds_user_interact)
if __name__ == "__main__":
+5
View File
@@ -1,3 +1,4 @@
from pathlib import Path
from typing import Literal
from pydantic_settings import SettingsConfigDict
@@ -20,6 +21,7 @@ class DataScienceBasePropSetting(KaggleBasePropSetting):
planner: str = "rdagent.scenarios.data_science.proposal.exp_gen.planner.DSExpPlannerHandCraft"
hypothesis_gen: str = "rdagent.scenarios.data_science.proposal.exp_gen.router.ParallelMultiTraceExpGen"
interactor: str = "rdagent.components.interactor.SkipInteractor"
trace_scheduler: str = "rdagent.scenarios.data_science.proposal.exp_gen.trace_scheduler.RoundRobinScheduler"
"""Hypothesis generation class"""
@@ -182,6 +184,9 @@ class DataScienceBasePropSetting(KaggleBasePropSetting):
ensemble_time_upper_bound: bool = False
user_interaction_wait_seconds: int = 6000 # seconds to wait for user interaction
user_interaction_mid_folder: Path = Path.cwd() / "git_ignore_folder" / "RD-Agent_user_interaction"
DS_RD_SETTING = DataScienceBasePropSetting()
@@ -169,7 +169,8 @@ pipeline_coder:
8. **Try-except blocks are ONLY allowed when reading files. If no files are successfully read, it indicates incorrect file paths or reading methods, not a try-except issue. Try-except is PROHIBITED elsewhere in the code. Assert statements are PROHIBITED throughout the entire code.**
9. ATTENTION: ALWAYS use the best saved model (not necessarily final epoch) for predictions. **NEVER create dummy/placeholder submissions (e.g., all 1s, random values)**. If training fails, report failure honestly rather than generating fake submission files.
10. You should ALWAYS generate the complete code rather than partial code.
11. Strictly follow all specifications and general guidelines described above.
11. If the task contains any user instructions, you must strictly follow them. User instructions have the highest priority and should be followed even if they conflict with other specifications or guidelines.
12. Strictly follow all specifications and general guidelines described above.
### Output Format
{% if out_spec %}
+17
View File
@@ -0,0 +1,17 @@
from rdagent.core.experiment import ASpecificExp
from rdagent.core.interactor import Interactor
from rdagent.core.proposal import Trace
class SkipInteractor(Interactor[ASpecificExp]):
def interact(self, exp: ASpecificExp, trace: Trace) -> ASpecificExp:
"""
Interact with the user to get feedback or confirmation.
Responsibilities:
- Present the current state of the experiment to the user.
- Collect user input to guide the next steps in the experiment.
- Rewrite the experiment based on user feedback.
"""
return exp
+1
View File
@@ -11,6 +11,7 @@ class BasePropSetting(ExtendedBaseSettings):
knowledge_base: str = ""
knowledge_base_path: str = ""
hypothesis_gen: str = ""
interactor: str = ""
hypothesis2experiment: str = ""
coder: str = ""
runner: str = ""
+33 -3
View File
@@ -13,7 +13,7 @@ from collections.abc import Sequence
from copy import deepcopy
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, Generic, TypeVar
from typing import TYPE_CHECKING, Any, Generic, List, TypeVar
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.core.evaluation import Feedback
@@ -48,13 +48,28 @@ class AbsTask(ABC):
"""
class UserInstructions(List[str]):
def __str__(self) -> str:
if self:
return ("\nUser Instructions (Top priority!):\n" + "\n".join(f"- {ui}" for ui in self)) if self else ""
else:
return ""
class Task(AbsTask):
def __init__(self, name: str, version: int = 1, description: str = "") -> None:
def __init__(
self,
name: str,
version: int = 1,
description: str = "",
user_instructions: UserInstructions | None = None,
) -> None:
super().__init__(name, version)
self.description = description
self.user_instructions = user_instructions
def get_task_information(self) -> str:
return f"Task Name: {self.name}\nDescription: {self.description}"
return f"Task Name: {self.name}\nDescription: {self.description}{str(self.user_instructions)}"
def __repr__(self) -> str:
return f"<{self.__class__.__name__} {self.name}>"
@@ -410,6 +425,21 @@ class Experiment(
self.plan: ExperimentPlan | None = (
None # To store the planning information for this experiment, should be generated inside exp_gen.gen
)
self.user_instructions: UserInstructions | None = None # To store the user instructions for this experiment
def set_user_instructions(self, user_instructions: UserInstructions | None) -> None:
if user_instructions is None:
return
if not isinstance(user_instructions, UserInstructions) and isinstance(user_instructions, list):
user_instructions = UserInstructions(user_instructions)
self.user_instructions = user_instructions
for ws in self.sub_workspace_list:
if ws is not None:
ws.target_task.user_instructions = user_instructions # type: ignore[union-attr]
for task in self.sub_tasks:
task.user_instructions = user_instructions
if self.experiment_workspace is not None and self.experiment_workspace.target_task is not None:
self.experiment_workspace.target_task.user_instructions = user_instructions
@property
def result(self) -> object:
+26
View File
@@ -0,0 +1,26 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from trace import Trace
from typing import TYPE_CHECKING, Generic
from rdagent.core.experiment import ASpecificExp
if TYPE_CHECKING:
from rdagent.core.scenario import Scenario
class Interactor(ABC, Generic[ASpecificExp]):
def __init__(self, scen: Scenario) -> None:
self.scen: Scenario = scen
@abstractmethod
def interact(self, exp: ASpecificExp, trace: Trace | None = None) -> ASpecificExp:
"""
Interact with the experiment to get feedback or confirmation.
Responsibilities:
- Present the current state of the experiment.
- Collect input to guide the next steps in the experiment.
- Rewrite the experiment based on feedback.
"""
+172
View File
@@ -0,0 +1,172 @@
import json
import pickle
import time
from datetime import datetime, timedelta
from pathlib import Path
import streamlit as st
from streamlit import session_state as state
from rdagent.app.data_science.conf import DS_RD_SETTING
st.set_page_config(layout="wide", page_title="RD-Agent_user_interact", page_icon="🎓", initial_sidebar_state="expanded")
# 初始化session state
if "sessions" not in state:
state.sessions = {}
if "selected_session_name" not in state:
state.selected_session_name = None
def render_main_content():
"""渲染主要内容区域"""
if state.selected_session_name is not None and state.selected_session_name in state.sessions:
selected_session_data = state.sessions[state.selected_session_name]
if selected_session_data is not None:
st.title(
f"Session: {state.selected_session_name[:4]} with competition {selected_session_data['competition']}"
)
st.title("Contextual Information:")
st.subheader("Competition scenario:", divider=True)
scenario = st.code(selected_session_data["scenario_description"], language="yaml")
st.subheader("Former attempts summary:", divider=True)
scenario = st.code(selected_session_data["ds_trace_desc"], language="yaml")
if selected_session_data["current_code"] != "":
st.subheader("Current SOTA code", divider=True)
scenario = st.code(
body=selected_session_data["current_code"],
language="python",
)
st.subheader("Hypothesis candidates:", divider=True)
hypothesis_candidates = selected_session_data["hypothesis_candidates"]
tabs = st.tabs(
[
f"{'' if i == selected_session_data['target_hypothesis_index'] or selected_session_data['target_hypothesis_index'] == -1 else ''}Hypothesis {i+1}"
for i in range(len(hypothesis_candidates))
]
)
for index, hypothesis in enumerate(hypothesis_candidates):
with tabs[index]:
st.code(str(hypothesis), language="yaml")
st.text("✅ means picked as target hypothesis")
st.title("Decisions to make:")
with st.form(key="user_form"):
st.caption("Please modify the fields below and submit to provide your feedback.")
target_hypothesis = st.text_area(
"Target hypothesis: (you can copy from candidates)",
value=(original_hypothesis := selected_session_data["target_hypothesis"].hypothesis),
height="content",
)
target_task = st.text_area(
"Target task description:",
value=(original_task_desc := selected_session_data["task"].description),
height="content",
)
original_user_instruction = selected_session_data.get("user_instruction")
user_instruction_list = []
if selected_session_data.get("former_user_instructions") is not None:
st.caption(
"Former user instructions, you can modify or delete the content to remove certain instruction."
)
for user_instruction in selected_session_data.get("former_user_instructions"):
user_instruction_list.append(
st.text_area("Former user instruction", value=user_instruction, height="content")
)
user_instruction_list.append(st.text_area("Add new user instruction", value="", height="content"))
submit = st.form_submit_button("Submit")
approve = st.form_submit_button("Approve without changes")
if submit or approve:
if approve:
submit_dict = {
"action": "confirm",
}
else:
user_instruction_str_list = [ui for ui in user_instruction_list if ui.strip() != ""]
user_instruction_str_list = (
None if len(user_instruction_str_list) == 0 else user_instruction_str_list
)
action = (
"confirm"
if target_hypothesis == original_hypothesis
and target_task == original_task_desc
and user_instruction_str_list == original_user_instruction
else "rewrite"
)
submit_dict = {
"target_hypothesis": target_hypothesis,
"task_description": target_task,
"user_instruction": user_instruction_str_list,
"action": action,
}
json.dump(
submit_dict,
open(
DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}_RET.json", "w"
),
)
Path(DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}.pkl").unlink(
missing_ok=True
)
st.success("Your feedback has been submitted. Thank you!")
time.sleep(5)
state.selected_session_name = None
if st.button("Extend expiration by 60s"):
session_data = pickle.load(
open(DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}.pkl", "rb")
)
session_data["expired_datetime"] = session_data["expired_datetime"] + timedelta(seconds=60)
pickle.dump(
session_data,
open(DS_RD_SETTING.user_interaction_mid_folder / f"{state.selected_session_name}.pkl", "wb"),
)
else:
st.warning("Please select a session from the sidebar.")
# 每秒更新一次sessions
@st.fragment(run_every=1)
def update_sessions():
log_folder = Path(DS_RD_SETTING.user_interaction_mid_folder)
state.sessions = {}
for session_file in log_folder.glob("*.pkl"):
try:
session_data = pickle.load(open(session_file, "rb"))
if session_data["expired_datetime"] > datetime.now():
state.sessions[session_file.stem] = session_data
else:
session_file.unlink(missing_ok=True)
ret_file = log_folder / f"{session_file.stem}_RET.json"
ret_file.unlink(missing_ok=True)
except Exception as e:
continue
render_main_content()
@st.fragment(run_every=1)
def render_sidebar():
st.title("R&D-Agent User Interaction Portal")
if state.sessions:
st.header("Active Sessions")
st.caption("Click a session to view:")
session_names = [name for name in state.sessions]
for session_name in session_names:
with st.container(border=True):
remaining = state.sessions[session_name]["expired_datetime"] - datetime.now()
total_sec = int(remaining.total_seconds())
label = f"{total_sec}s to expire" if total_sec > 0 else "Expired"
if st.button(f"session id:{session_name[:4]}", key=f"session_btn_{session_name}"):
state.selected_session_name = session_name
state.data = state.sessions[session_name]
st.markdown(f"{label}")
else:
st.warning("No active sessions available. Please wait.")
update_sessions()
with st.sidebar:
render_sidebar()
@@ -3,13 +3,13 @@ from typing import Literal
import pandas as pd
from rdagent.core.experiment import Experiment, FBWorkspace, Task
from rdagent.core.experiment import Experiment, FBWorkspace, Task, UserInstructions
COMPONENT = Literal["DataLoadSpec", "FeatureEng", "Model", "Ensemble", "Workflow", "Pipeline"]
class DSExperiment(Experiment[Task, FBWorkspace, FBWorkspace]):
def __init__(self, pending_tasks_list: list, *args, **kwargs) -> None:
def __init__(self, pending_tasks_list: list, hypothesis_candidates: list | None = None, *args, **kwargs) -> None:
super().__init__(sub_tasks=[], *args, **kwargs)
# Status
# - Initial: blank;
@@ -18,11 +18,20 @@ class DSExperiment(Experiment[Task, FBWorkspace, FBWorkspace]):
# the initial workspace or the successful new version after coding
self.experiment_workspace = FBWorkspace()
self.pending_tasks_list = pending_tasks_list
self.hypothesis_candidates = hypothesis_candidates
self.format_check_result = None
# this field is optional. It is not none only when we have a format checker. Currently, only following cases are supported.
# - mle-bench
def set_user_instructions(self, user_instructions: UserInstructions | None):
super().set_user_instructions(user_instructions)
if user_instructions is None:
return
for task_list in self.pending_tasks_list:
for task in task_list:
task.user_instructions = user_instructions
def is_ready_to_run(self) -> bool:
"""
ready to run does not indicate the experiment is runnable
@@ -0,0 +1,120 @@
import json
import pickle
import time
import uuid
from abc import abstractmethod
from datetime import datetime, timedelta
from pathlib import Path
from rdagent.app.data_science.conf import DS_RD_SETTING
from rdagent.core.experiment import Task
from rdagent.core.interactor import Interactor
from rdagent.scenarios.data_science.experiment.experiment import DSExperiment
from rdagent.scenarios.data_science.proposal.exp_gen.base import DSHypothesis, DSTrace
from rdagent.utils.agent.tpl import T
class DSInteractor(Interactor[DSExperiment]):
@abstractmethod
def dump_and_wait_for_user_input(
self,
scenario_description: str,
ds_trace_desc: str,
current_code: str,
hypothesis_candidates: list[str],
target_hypothesis: DSHypothesis,
target_hypothesis_index: int,
task_description: Task,
exp: DSExperiment,
) -> DSExperiment:
raise NotImplementedError
def interact(self, exp: DSExperiment, trace: DSTrace) -> DSExperiment:
"""
Interact with the experiment to get feedback or confirmation.
Responsibilities:
- Present the current state of the experiment.
- Collect input to guide the next steps in the experiment.
- Rewrite the experiment based on feedback.
"""
scenario_description = self.scen.get_scenario_all_desc(
eda_output=exp.experiment_workspace.file_dict.get("EDA.md", None)
)
ds_trace_desc = T("scenarios.data_science.share:describe.trace").r(
exp_and_feedback_list=trace.experiment_and_feedback_list_after_init(return_type="all"),
type="all",
pipeline=DS_RD_SETTING.coder_on_whole_pipeline,
)
current_code = exp.experiment_workspace.file_dict.get("main.py", "")
target_hypothesis = exp.hypothesis
hypothesis_str_candidates = [hypo.hypothesis for hypo in exp.hypothesis_candidates]
target_hypothesis_index = (
hypothesis_str_candidates.index(target_hypothesis.hypothesis)
if target_hypothesis.hypothesis in hypothesis_str_candidates and not trace.is_selection_new_tree()
else -1
)
return self.dump_and_wait_for_user_input(
scenario_description=scenario_description,
ds_trace_desc=ds_trace_desc,
current_code=current_code,
hypothesis_candidates=exp.hypothesis_candidates,
target_hypothesis=target_hypothesis,
target_hypothesis_index=target_hypothesis_index,
task=exp.pending_tasks_list[0][0],
exp=exp,
)
class FBDSInteractor(DSInteractor):
def dump_and_wait_for_user_input(
self,
scenario_description: str,
ds_trace_desc: str,
current_code: str,
hypothesis_candidates: list[DSHypothesis],
target_hypothesis: DSHypothesis,
target_hypothesis_index: int,
task: Task,
exp: DSExperiment,
) -> DSExperiment:
information_to_user = {
"competition": DS_RD_SETTING.competition,
"scenario_description": scenario_description,
"ds_trace_desc": ds_trace_desc,
"current_code": current_code,
"hypothesis_candidates": hypothesis_candidates,
"target_hypothesis": (
hypothesis_candidates[target_hypothesis_index] if target_hypothesis_index != -1 else target_hypothesis
),
"target_hypothesis_index": target_hypothesis_index,
"task": task,
"expired_datetime": datetime.now() + timedelta(seconds=DS_RD_SETTING.user_interaction_wait_seconds),
"former_user_instructions": exp.user_instructions,
}
session_id = uuid.uuid4().hex
DS_RD_SETTING.user_interaction_mid_folder.mkdir(parents=True, exist_ok=True)
pickle.dump(information_to_user, open(DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}.pkl", "wb"))
while (
Path(DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}.pkl").exists()
and pickle.load(open(DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}.pkl", "rb"))[
"expired_datetime"
]
> datetime.now()
and not (DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}_RET.json").exists()
):
time.sleep(5)
Path(DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}.pkl").unlink(missing_ok=True)
if not (DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}_RET.json").exists():
return exp
else:
user_feedback = json.load(open(DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}_RET.json"))
if user_feedback["action"] == "confirm":
return exp
elif user_feedback["action"] == "rewrite":
exp.hypothesis.hypothesis = user_feedback["target_hypothesis"]
exp.pending_tasks_list[0][0].description = user_feedback["task_description"]
exp.set_user_instructions(user_feedback["user_instruction"])
Path(DS_RD_SETTING.user_interaction_mid_folder / f"{session_id}_RET.json").unlink(missing_ok=True)
return exp
+3
View File
@@ -105,6 +105,8 @@ class DataScienceRDLoop(RDLoop):
self.sota_exp_selector = import_class(PROP_SETTING.sota_exp_selector_name)()
self.exp_gen: ExpGen = import_class(PROP_SETTING.hypothesis_gen)(scen)
self.interactor = import_class(PROP_SETTING.interactor)(scen)
# coders
self.data_loader_coder = DataLoaderCoSTEER(scen)
self.feature_coder = FeatureCoSTEER(scen)
@@ -140,6 +142,7 @@ class DataScienceRDLoop(RDLoop):
# in parallel + multi-trace mode, the above global "trace.current_selection" will not be used
# instead, we will use the "local_selection" attached to each exp to in async_gen().
exp = await self.exp_gen.async_gen(self.trace, self)
exp = self.interactor.interact(exp, self.trace)
logger.log_object(exp)
return exp
@@ -196,6 +196,12 @@ hypothesis_gen:
5. **Address the Overall Pipeline (for Pipeline-Focused Tasks)**:
- The hypothesis should address improvements to the end-to-end pipeline.
- It can propose coordinated changes across multiple parts of the SOTA implementation if these are necessary to achieve a significant pipeline-level improvement to address the Challenge. (Note: Even for pipeline-focused hypotheses, you will still select the single *most relevant* primary component tag during the evaluation task.)
{% if former_user_instructions_str is not none %}
## 1.3. Mandatory Consideration of Past User Instructions
The user has provided specific instructions in previous experiments. These instructions may contain critical insights or constraints that must be considered when formulating your hypotheses. Carefully review the following past user instructions and ensure that your proposed hypotheses align with these directives:
{{ former_user_instructions_str }}
{% endif %}
# Task 2: Hypothesis Evaluation
After proposing one hypothesis for each relevant Identified Challenge, evaluate each one.
@@ -424,7 +430,13 @@ hypothesis_rewrite:
{% endfor %}
Your rewritten hypotheses **MUST** guide the agent towards different approaches, for example, different backbone models, different feature engineering methods, different ensemble strategies, different workflow optimizations, focus on efficiency etc. Avoid proposing hypotheses that are similar to those listed above.
{% endif %}
{% if former_user_instructions_str is not none %}
# Mandatory Consideration of Past User Instructions
The user has provided specific instructions in previous experiments. These instructions may contain critical insights or constraints that must be considered when rewriting your hypotheses. Carefully review the following past user instructions and ensure that your rewritten hypotheses align with these directives:
{{ former_user_instructions_str }}
{% endif %}
{% if rewrite_output_format is not none %}
## Output Format
{{ rewrite_output_format }}
@@ -760,7 +772,13 @@ task_gen:
If you are confident in a specific value based on strong evidence, prior experiments, or clear rationale, specify the value clearly.
{% include "scenarios.data_science.share:spec.hyperparameter" %}
{% if former_user_instructions_str is not none %}
# Mandatory Consideration of Past User Instructions
The user has provided specific instructions in previous experiments. These instructions may contain critical insights or constraints that must be considered in your sketch.
Carefully review and integrate these instructions into your design to ensure alignment with user expectations and requirements.
{{ former_user_instructions_str }}
{% endif %}
{% if task_output_format is not none %}
# Output Format
@@ -15,6 +15,7 @@ from rdagent.components.coder.data_science.model.exp import ModelTask
from rdagent.components.coder.data_science.pipeline.exp import PipelineTask
from rdagent.components.coder.data_science.raw_data_loader.exp import DataLoaderTask
from rdagent.components.coder.data_science.workflow.exp import WorkflowTask
from rdagent.core.experiment import UserInstructions
from rdagent.core.proposal import ExpGen
from rdagent.core.scenario import Scenario
from rdagent.log import rdagent_logger as logger
@@ -617,6 +618,7 @@ class DSProposalV2ExpGen(ExpGen):
inject_diverse: bool = False,
exp_gen_plan: Optional[Dict] = None,
sibling_exp: List[DSExperiment] | None = None,
former_user_instructions: UserInstructions | None = None,
) -> Dict:
problem_formatted_str = ""
for i, (problem_name, problem_dict) in enumerate(problems.items()):
@@ -641,6 +643,7 @@ class DSProposalV2ExpGen(ExpGen):
generate_unique_hypothesis=DS_RD_SETTING.enable_generate_unique_hypothesis and is_new_tree,
enable_simple_hypothesis=DS_RD_SETTING.enable_simple_hypothesis,
sibling_hypotheses=sibling_hypotheses,
former_user_instructions_str=str(former_user_instructions) if former_user_instructions else None,
)
user_prompt = T(".prompts_v2:hypothesis_gen.user").r(
scenario_desc=scenario_desc,
@@ -765,6 +768,7 @@ class DSProposalV2ExpGen(ExpGen):
sota_exp_desc: str,
exp_feedback_list_desc: str,
sibling_exp: List[DSExperiment] | None = None,
former_user_instructions: UserInstructions | None = None,
) -> Dict:
"""
Generate improved hypotheses based on critique feedback for each original hypothesis.
@@ -799,6 +803,7 @@ class DSProposalV2ExpGen(ExpGen):
),
enable_scale_check=DS_RD_SETTING.enable_scale_check,
sibling_hypotheses=sibling_hypotheses,
former_user_instructions_str=str(former_user_instructions) if former_user_instructions else None,
)
user_prompt = T(".prompts_v2:hypothesis_rewrite.user").r(
scenario_desc=scenario_desc,
@@ -1172,10 +1177,12 @@ class DSProposalV2ExpGen(ExpGen):
sota_exp_desc: str,
sota_exp: DSExperiment,
hypotheses: list[DSHypothesis],
hypotheses_candidates: list[DSHypothesis],
pipeline: bool,
failed_exp_feedback_list_desc: str,
fb_to_sota_exp: ExperimentFeedback | None = None,
sibling_exp: List[DSExperiment] | None = None,
former_user_instructions: UserInstructions = None,
) -> DSExperiment:
if pipeline:
component_info = get_component("Pipeline")
@@ -1192,6 +1199,7 @@ class DSProposalV2ExpGen(ExpGen):
metric_name=self.scen.metric_name,
sibling_tasks=sibling_tasks,
fix_seed_and_data_split=DS_RD_SETTING.fix_seed_and_data_split,
former_user_instructions_str=str(former_user_instructions) if former_user_instructions else None,
)
user_prompt = T(".prompts_v2:task_gen.user").r(
scenario_desc=scenario_desc,
@@ -1242,7 +1250,9 @@ class DSProposalV2ExpGen(ExpGen):
# Persist for later stages
task.package_info = get_packages(pkgs)
exp = DSExperiment(pending_tasks_list=[[task]], hypothesis=hypotheses[0])
exp = DSExperiment(
pending_tasks_list=[[task]], hypothesis=hypotheses[0], hypothesis_candidates=hypotheses_candidates
)
if sota_exp is not None:
exp.experiment_workspace.inject_code_from_file_dict(sota_exp.experiment_workspace)
@@ -1253,6 +1263,10 @@ class DSProposalV2ExpGen(ExpGen):
description=task_dict.get("workflow_update", "No update needed"),
)
exp.pending_tasks_list.append([workflow_task])
# 4) set user instructions
if former_user_instructions is not None:
exp.set_user_instructions(former_user_instructions)
return exp
def get_all_hypotheses(self, problem_dict: dict, hypothesis_dict: dict) -> list[DSHypothesis]:
@@ -1315,12 +1329,17 @@ class DSProposalV2ExpGen(ExpGen):
)
# all failed exp and feedbacks
failed_exp_feedback_list = trace.experiment_and_feedback_list_after_init(return_type="failed")
failed_exp_feedback_list_desc = T("scenarios.data_science.share:describe.trace").r(
exp_and_feedback_list=trace.experiment_and_feedback_list_after_init(return_type="failed"),
exp_and_feedback_list=failed_exp_feedback_list,
type="failed",
pipeline=pipeline,
)
#
if len(failed_exp_feedback_list) == 0:
former_user_instructions = None
else:
former_user_instructions = failed_exp_feedback_list[-1][0].user_instructions
# NOTE: we currently don't support inject diverse problems for the parallel + multi-trace mode,
if DS_RD_SETTING.enable_inject_diverse and len(trace.hist) > 0:
if len(trace.current_selection) == 0:
@@ -1371,6 +1390,7 @@ class DSProposalV2ExpGen(ExpGen):
exp_gen_plan=plan.get("exp_gen") if plan else None,
is_new_tree=is_new_tree,
sibling_exp=sibling_exp,
former_user_instructions=former_user_instructions,
)
if not pipeline:
sota_exp_model_file_count = len(
@@ -1415,6 +1435,7 @@ class DSProposalV2ExpGen(ExpGen):
sota_exp_desc=sota_exp_desc,
exp_feedback_list_desc=exp_feedback_list_desc,
sibling_exp=sibling_exp,
former_user_instructions=former_user_instructions,
)
logger.info(f"Successfully completed hypothesis critique and rewrite process")
except Exception as e:
@@ -1454,10 +1475,14 @@ class DSProposalV2ExpGen(ExpGen):
sota_exp_desc=sota_exp_desc,
sota_exp=sota_exp,
hypotheses=(
[new_hypothesis] if len(trace.hist) > 0 else self.get_all_hypotheses(all_problems, hypothesis_dict)
[new_hypothesis]
if not trace.is_selection_new_tree()
else self.get_all_hypotheses(all_problems, hypothesis_dict)
),
hypotheses_candidates=self.get_all_hypotheses(all_problems, hypothesis_dict),
pipeline=pipeline,
failed_exp_feedback_list_desc=failed_exp_feedback_list_desc,
fb_to_sota_exp=fb_to_sota_exp,
sibling_exp=sibling_exp,
former_user_instructions=former_user_instructions,
)