mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-08-09 04:57:44 +00:00
feat: add a web UI server (#1345)
* update rdagent cmd * fix log error message * use multiProcessing.Process instead of subprocess.Popen * add traces to gitignore * add user interactor in RDLoop (finance scenarios) * add interactor (feedback, hypothesis) for quant scens * fix the test_end in qlib conf * add features init config, general instruction to qlib scenarios * set base features for based exp * fix bug when combine factors * move traces folder to git_ignore_folder * fix bug in features init * fix quant interact bug * fix logger warning error * bug fixes * modify rdagent logger, now it can set file output * adjust cli functions and fix logger bug * fix server port transport problem * update server_ui in cli * add web code * fix CI problem * black fix * update web ui README * update README * update readme
This commit is contained in:
@@ -42,6 +42,7 @@ class LLMHypothesisGen(HypothesisGen):
|
||||
),
|
||||
hypothesis_output_format=context_dict["hypothesis_output_format"],
|
||||
hypothesis_specification=context_dict["hypothesis_specification"],
|
||||
user_instruction=plan.get("user_instruction", None) if plan is not None else None,
|
||||
)
|
||||
user_prompt = T(".prompts:hypothesis_gen.user_prompt").r(
|
||||
targets=self.targets,
|
||||
|
||||
@@ -3,10 +3,17 @@ hypothesis_gen:
|
||||
The user is working on generating new hypotheses for the {{ targets }} in a data-driven research and development process.
|
||||
The {{ targets }} are used in the following scenario:
|
||||
{{ scenario }}
|
||||
|
||||
{% if user_instruction %}
|
||||
**User's overall instruction:**
|
||||
{{ user_instruction }}
|
||||
{% endif %}
|
||||
|
||||
The user has already proposed several hypotheses and conducted evaluations on them. This information will be provided to you. Your task is to analyze previous experiments, reflect on the decision made in each experiment, and consider why experiments with a decision of true were successful while those with a decision of false failed. Then, think about how to improve further — either by refining the existing approach or by exploring an entirely new direction.
|
||||
If one exists and you agree with it, feel free to use it. If you disagree, please generate an improved version.
|
||||
{% if hypothesis_specification %}
|
||||
To assist you in formulating new hypotheses, the user has provided some additional information: {{ hypothesis_specification }}.
|
||||
To assist you in formulating new hypotheses, the user has provided some additional information:
|
||||
{{ hypothesis_specification }}
|
||||
**Important:** If the hypothesis_specification outlines the next steps you need to follow, ensure you adhere to those instructions.
|
||||
{% endif %}
|
||||
Please generate the output using the following format and specifications:
|
||||
|
||||
@@ -4,6 +4,9 @@ It is from `rdagent/app/qlib_rd_loop/model.py` and try to replace `rdagent/app/q
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from multiprocessing import Queue
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from rdagent.components.workflow.conf import BasePropSetting
|
||||
@@ -11,6 +14,7 @@ from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||
from rdagent.core.developer import Developer
|
||||
from rdagent.core.proposal import (
|
||||
Experiment2Feedback,
|
||||
ExperimentPlan,
|
||||
Hypothesis,
|
||||
Hypothesis2Experiment,
|
||||
HypothesisFeedback,
|
||||
@@ -20,6 +24,7 @@ from rdagent.core.proposal import (
|
||||
from rdagent.core.scenario import Scenario
|
||||
from rdagent.core.utils import import_class
|
||||
from rdagent.log import rdagent_logger as logger
|
||||
from rdagent.utils.qlib import ALPHA20, validate_qlib_features
|
||||
from rdagent.utils.workflow import LoopBase, LoopMeta
|
||||
|
||||
|
||||
@@ -36,6 +41,11 @@ class RDLoop(LoopBase, metaclass=LoopMeta):
|
||||
else None
|
||||
)
|
||||
|
||||
self.plan: ExperimentPlan = {
|
||||
"features": ALPHA20,
|
||||
"feature_codes": {},
|
||||
} # for user interaction
|
||||
|
||||
self.hypothesis2experiment: Hypothesis2Experiment = (
|
||||
import_class(PROP_SETTING.hypothesis2experiment)()
|
||||
if hasattr(PROP_SETTING, "hypothesis2experiment") and PROP_SETTING.hypothesis2experiment
|
||||
@@ -58,8 +68,125 @@ class RDLoop(LoopBase, metaclass=LoopMeta):
|
||||
super().__init__()
|
||||
|
||||
# excluded steps
|
||||
def _set_interactor(self, user_request_q: Queue, user_response_q: Queue):
|
||||
self.user_request_q = user_request_q
|
||||
self.user_response_q = user_response_q
|
||||
|
||||
def _init_base_features(self, base_features_path: str | None):
|
||||
if base_features_path is not None:
|
||||
try:
|
||||
base_dir = Path(base_features_path)
|
||||
base_factors_file = base_dir / "base_factors.json"
|
||||
|
||||
feature_codes: dict[str, str] = {}
|
||||
for py_file in sorted(base_dir.glob("*.py")):
|
||||
feature_codes[py_file.name] = py_file.read_text()
|
||||
self.plan["feature_codes"] = feature_codes
|
||||
|
||||
if not base_factors_file.exists():
|
||||
logger.info(f"No base_factors.json found under {base_dir}. Keeping default base features.")
|
||||
logger.info(f"{len(feature_codes)} feature code files loaded from {base_dir}.")
|
||||
else:
|
||||
with base_factors_file.open("r") as f:
|
||||
features = json.load(f)
|
||||
|
||||
if not isinstance(features, dict):
|
||||
raise ValueError(
|
||||
"`base_factors.json` must contain a JSON object of feature_name -> expression."
|
||||
)
|
||||
|
||||
if validate_qlib_features(list(features.values())):
|
||||
self.plan["features"] = features
|
||||
logger.info(
|
||||
f"Loaded base features from {base_factors_file}. {len(features)} features loaded and {len(feature_codes)} feature code files loaded."
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Base feature validation failed for features loaded from {base_factors_file}. Using default features."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load base features from {base_features_path}: {e}. Using default features.")
|
||||
else:
|
||||
logger.info("No base features path provided. Using default features.")
|
||||
|
||||
def _interact_init_params(self) -> None:
|
||||
if not (hasattr(self, "user_request_q") and hasattr(self, "user_response_q")):
|
||||
return
|
||||
|
||||
logger.info("Waiting for user interaction on initial parameters...")
|
||||
try:
|
||||
self.user_request_q.put(
|
||||
{
|
||||
"user_instruction": None,
|
||||
}
|
||||
)
|
||||
res_dict = self.user_response_q.get()
|
||||
logger.info("Received user instruction response.")
|
||||
self.plan.update(res_dict)
|
||||
|
||||
if "feature_codes" not in self.plan:
|
||||
self.plan[
|
||||
"user_instruction"
|
||||
] += f"\n\n{str(list(self.plan['feature_codes'].keys()))} has been configured as the base factor; do not generate duplicate factors."
|
||||
fea_valid_msg = ""
|
||||
while True:
|
||||
logger.info("Requesting base feature configuration from user.")
|
||||
self.user_request_q.put(
|
||||
{
|
||||
"features": self.plan["features"],
|
||||
"feature_validation_msg": fea_valid_msg,
|
||||
}
|
||||
)
|
||||
self.plan["features"] = self.user_response_q.get()
|
||||
logger.info("Received base feature configuration response.")
|
||||
if validate_qlib_features(list(self.plan["features"].values())):
|
||||
logger.info(f"Base feature validation passed. {len(self.plan['features'])} features selected.")
|
||||
break
|
||||
else:
|
||||
logger.info("Base feature validation failed. Asking user to revise.")
|
||||
fea_valid_msg = "Some features are invalid, please revise."
|
||||
|
||||
except (EOFError, OSError):
|
||||
logger.info("User interaction failed, using default initial parameters.")
|
||||
return
|
||||
logger.info("Received user interaction on initial parameters.")
|
||||
|
||||
def _interact_hypo(self, hypo: Hypothesis) -> Hypothesis:
|
||||
if not (hasattr(self, "user_request_q") and hasattr(self, "user_response_q")):
|
||||
return hypo
|
||||
|
||||
logger.info("Waiting for user interaction on hypothesis...")
|
||||
try:
|
||||
self.user_request_q.put(hypo.__dict__)
|
||||
res_dict = self.user_response_q.get()
|
||||
modified_hypo = type(hypo)(**res_dict)
|
||||
except (EOFError, OSError, TypeError):
|
||||
logger.info("User interaction failed, using original hypothesis.")
|
||||
return hypo
|
||||
logger.info("Received user interaction on hypothesis.")
|
||||
return modified_hypo
|
||||
|
||||
def _interact_feedback(self, feedback: HypothesisFeedback) -> HypothesisFeedback:
|
||||
if not (hasattr(self, "user_request_q") and hasattr(self, "user_response_q")):
|
||||
return feedback
|
||||
|
||||
logger.info("Waiting for user interaction on feedback...")
|
||||
try:
|
||||
self.user_request_q.put(feedback.__dict__)
|
||||
res_dict = self.user_response_q.get()
|
||||
modified_feedback = HypothesisFeedback(**res_dict)
|
||||
except (EOFError, OSError, TypeError):
|
||||
logger.info("User interaction failed, using original feedback.")
|
||||
return feedback
|
||||
logger.info("Received user interaction on feedback.")
|
||||
return modified_feedback
|
||||
|
||||
def _propose(self):
|
||||
hypothesis = self.hypothesis_gen.gen(self.trace)
|
||||
hypothesis = self.hypothesis_gen.gen(self.trace, self.plan)
|
||||
|
||||
# user can change the hypothesis here
|
||||
hypothesis = self._interact_hypo(hypothesis)
|
||||
|
||||
logger.log_object(hypothesis, tag="hypothesis generation")
|
||||
return hypothesis
|
||||
|
||||
@@ -74,6 +201,11 @@ class RDLoop(LoopBase, metaclass=LoopMeta):
|
||||
if self.get_unfinished_loop_cnt(self.loop_idx) < RD_AGENT_SETTINGS.get_max_parallel():
|
||||
hypo = self._propose()
|
||||
exp = self._exp_gen(hypo)
|
||||
exp.base_features = self.plan["features"]
|
||||
exp.base_feature_codes = self.plan["feature_codes"]
|
||||
if exp.based_experiments:
|
||||
exp.based_experiments[-1].base_features = self.plan["features"]
|
||||
exp.based_experiments[-1].base_feature_codes = self.plan["feature_codes"]
|
||||
return {"propose": hypo, "exp_gen": exp}
|
||||
await asyncio.sleep(1)
|
||||
|
||||
@@ -99,6 +231,7 @@ class RDLoop(LoopBase, metaclass=LoopMeta):
|
||||
)
|
||||
else:
|
||||
feedback = self.summarizer.generate_feedback(prev_out["running"], self.trace)
|
||||
feedback = self._interact_feedback(feedback)
|
||||
logger.log_object(feedback, tag="feedback")
|
||||
return feedback
|
||||
|
||||
|
||||
Reference in New Issue
Block a user