1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-17 09:24:34 +08:00

Connect TrainTask & Rolling & DDG-DA (#1599)

* Connect train task to ddg-da & rolling

* Pylint & black formatting

* Formatting
This commit is contained in:
you-n-g
2023-07-17 09:58:58 +08:00
committed by GitHub
parent 5e0873ca81
commit 1c9841b15e
14 changed files with 584 additions and 356 deletions

View File

@@ -16,3 +16,5 @@ MAX_RETRY=120
CONTINOUS_MODE=True CONTINOUS_MODE=True
DEBUG_MODE=True DEBUG_MODE=True
# TEMPERATURE=

View File

@@ -1,5 +1,5 @@
# TODO: use pydantic for other modules in Qlib # TODO: use pydantic for other modules in Qlib
from pydantic import BaseSettings # from pydantic_settings import BaseSettings
from qlib.finco.utils import SingletonBaseClass from qlib.finco.utils import SingletonBaseClass
import os import os

97
qlib/finco/context.py Normal file
View File

@@ -0,0 +1,97 @@
from dataclasses import dataclass, field
import copy
from pathlib import Path
from typing import Optional, List
from qlib.finco.log import FinCoLog
from qlib.typehint import Literal
from qlib.finco.utils import similarity
@dataclass
class Design:
plan: str
classes: str
decision: str
@dataclass
class Exp:
"""Experiment"""
# compoments
dataset: Optional[Design] = None
datahandler: Optional[Design] = None
model: Optional[Design] = None
record: Optional[Design] = None
strategy: Optional[Design] = None
backtest: Optional[Design] = None
# basic
template: Optional[Path] = None
# rolling strategy. None indicates no rolling
rolling: Optional[Literal["base", "ddgda"]] = None
@dataclass
class StructContext:
"""Part of the context have clear meaning and structure, so they will be saved here and can be easily retrieved and understood"""
# TODO: move more content in WorkflowContextManager.context to here
workspace: Path
exp_list: List[Exp] = field(default_factory=list) # the planned experiments
class WorkflowContextManager:
"""Context Manager stores the context of the workflow"""
"""All context are key value pairs which saves the input, output and status of the whole workflow"""
def __init__(self, workspace: Path) -> None:
self.context = {}
self.logger = FinCoLog()
# this context is public
self.struct_context = StructContext(workspace) # TODO: move more content in context to here
self.set_context("workspace", workspace) # TODO: remove me
def set_context(self, key, value):
if key in self.context:
self.logger.warning("The key already exists in the context, the value will be overwritten")
self.context[key] = value
def get_context(self, key):
# NOTE: if the key doesn't exist, return None. In the future, we may raise an error to detect abnormal behavior
if key not in self.context:
self.logger.warning("The key doesn't exist in the context")
return None
return self.context[key]
def update_context(self, key, new_value):
# NOTE: if the key doesn't exist, return None. In the future, we may raise an error to detect abnormal behavior
if key not in self.context:
self.logger.warning("The key doesn't exist in the context")
self.context.update({key: new_value})
def get_all_context(self):
"""return a deep copy of the context"""
"""TODO: do we need to return a deep copy?"""
return copy.deepcopy(self.context)
def retrieve(self, query: str) -> dict:
if query in self.context.keys():
return {query: self.context.get(query)}
# Note: retrieve information from context by string similarity maybe abandon in future
scores = {}
for k, v in self.context.items():
scores.update({k: max(similarity(query, k), similarity(query, v))})
max_score_key = max(scores, key=scores.get)
return {max_score_key: self.context.get(max_score_key)}
def clear(self, reserve: list = None):
if reserve is None:
reserve = []
_context = {k: self.get_context(k) for k in reserve}
self.context = _context

View File

@@ -65,6 +65,7 @@ class YamlStorage(Storage):
This class is responsible for storage and loading of Knowledge related data in yaml format. This class is responsible for storage and loading of Knowledge related data in yaml format.
""" """
DEFAULT_NAME = "storage.yml" DEFAULT_NAME = "storage.yml"
def __init__(self, path: Union[str, Path]): def __init__(self, path: Union[str, Path]):
@@ -82,7 +83,7 @@ class YamlStorage(Storage):
def save(self, **kwargs): def save(self, **kwargs):
"""use pickle as the default save method""" """use pickle as the default save method"""
Path.mkdir(self.path.parent, exist_ok=True) Path.mkdir(self.path.parent, exist_ok=True)
with open(self.path, 'w') as f: with open(self.path, "w") as f:
yaml.dump(self.documents, f) yaml.dump(self.documents, f)
@@ -190,9 +191,14 @@ class ExperimentKnowledge(Knowledge):
def brief(self): def brief(self):
docs = [] docs = []
for recorder in self.storage.recs: for recorder in self.storage.recs:
docs.append({"exp_name": self.storage.exp.name, "record_info": recorder.info, docs.append(
"config": recorder.load_object("config"), {
"context_summary": recorder.load_object("context_summary")}) "exp_name": self.storage.exp.name,
"record_info": recorder.info,
"config": recorder.load_object("config"),
"context_summary": recorder.load_object("context_summary"),
}
)
return docs return docs
@@ -295,7 +301,7 @@ class InfrastructureKnowledge(Knowledge):
""" """
functions = [] functions = []
for py_file_path in Path(directory).rglob('*.py'): for py_file_path in Path(directory).rglob("*.py"):
for _functions in self.get_functions_with_docstrings(py_file_path): for _functions in self.get_functions_with_docstrings(py_file_path):
functions.append(_functions) functions.append(_functions)
@@ -314,7 +320,7 @@ class InfrastructureKnowledge(Knowledge):
docstring = None docstring = None
for line in lines: for line in lines:
if line.strip().startswith("def ") or line.strip().startswith("class "): if line.strip().startswith("def ") or line.strip().startswith("class "):
func = line.strip().split(' ')[1].split('(')[0] func = line.strip().split(" ")[1].split("(")[0]
if func.startswith("__"): if func.startswith("__"):
continue continue
if current_func is not None: if current_func is not None:
@@ -336,7 +342,6 @@ class InfrastructureKnowledge(Knowledge):
class Topic: class Topic:
def __init__(self, name: str, describe: Template): def __init__(self, name: str, describe: Template):
self.name = name self.name = name
self.describe = describe self.describe = describe
@@ -347,9 +352,7 @@ class Topic:
def summarize(self, docs: list): def summarize(self, docs: list):
self.logger.info(f"Summarize topic: \nname: {self.name}\ndescribe: {self.describe.module}") self.logger.info(f"Summarize topic: \nname: {self.name}\ndescribe: {self.describe.module}")
prompt_workflow_selection = self.describe.render(docs=docs) prompt_workflow_selection = self.describe.render(docs=docs)
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(user_prompt=prompt_workflow_selection)
user_prompt=prompt_workflow_selection
)
self.knowledge = response self.knowledge = response
self.docs = docs self.docs = docs
@@ -395,22 +398,26 @@ class KnowledgeBase:
def load_practice_knowledge(self, path: Path) -> PracticeKnowledge: def load_practice_knowledge(self, path: Path) -> PracticeKnowledge:
self.practice_knowledge = PracticeKnowledge( self.practice_knowledge = PracticeKnowledge(
YamlStorage(path.joinpath(f"{self.KT_PRACTICE}/{YamlStorage.DEFAULT_NAME}"))) YamlStorage(path.joinpath(f"{self.KT_PRACTICE}/{YamlStorage.DEFAULT_NAME}"))
)
return self.practice_knowledge return self.practice_knowledge
def load_execute_knowledge(self, path: Path) -> ExecuteKnowledge: def load_execute_knowledge(self, path: Path) -> ExecuteKnowledge:
self.execute_knowledge = ExecuteKnowledge( self.execute_knowledge = ExecuteKnowledge(
YamlStorage(path.joinpath(f"{self.KT_EXECUTE}/{YamlStorage.DEFAULT_NAME}"))) YamlStorage(path.joinpath(f"{self.KT_EXECUTE}/{YamlStorage.DEFAULT_NAME}"))
)
return self.execute_knowledge return self.execute_knowledge
def load_finance_knowledge(self, path: Path) -> FinanceKnowledge: def load_finance_knowledge(self, path: Path) -> FinanceKnowledge:
self.finance_knowledge = FinanceKnowledge( self.finance_knowledge = FinanceKnowledge(
YamlStorage(path.joinpath(f"{self.KT_FINANCE}/{YamlStorage.DEFAULT_NAME}"))) YamlStorage(path.joinpath(f"{self.KT_FINANCE}/{YamlStorage.DEFAULT_NAME}"))
)
return self.finance_knowledge return self.finance_knowledge
def load_infrastructure_knowledge(self, path: Path) -> InfrastructureKnowledge: def load_infrastructure_knowledge(self, path: Path) -> InfrastructureKnowledge:
self.infrastructure_knowledge = InfrastructureKnowledge( self.infrastructure_knowledge = InfrastructureKnowledge(
YamlStorage(path.joinpath(f"{self.KT_INFRASTRUCTURE}/{YamlStorage.DEFAULT_NAME}"))) YamlStorage(path.joinpath(f"{self.KT_INFRASTRUCTURE}/{YamlStorage.DEFAULT_NAME}"))
)
return self.infrastructure_knowledge return self.infrastructure_knowledge
def get_knowledge(self, knowledge_type: str = None): def get_knowledge(self, knowledge_type: str = None):
@@ -423,8 +430,12 @@ class KnowledgeBase:
elif knowledge_type == self.KT_INFRASTRUCTURE: elif knowledge_type == self.KT_INFRASTRUCTURE:
knowledge = self.infrastructure_knowledge.knowledge knowledge = self.infrastructure_knowledge.knowledge
else: else:
knowledge = self.execute_knowledge.knowledge + self.practice_knowledge.knowledge \ knowledge = (
+ self.finance_knowledge.knowledge + self.infrastructure_knowledge.knowledge self.execute_knowledge.knowledge
+ self.practice_knowledge.knowledge
+ self.finance_knowledge.knowledge
+ self.infrastructure_knowledge.knowledge
)
return knowledge return knowledge
def query(self, knowledge_type: str = None, content: str = None, n: int = 5): def query(self, knowledge_type: str = None, content: str = None, n: int = 5):
@@ -451,7 +462,8 @@ class KnowledgeBase:
prompt = Template( prompt = Template(
"""find the most relevant doc with this query: '{{content}}' from docs='{{docs}}'. """find the most relevant doc with this query: '{{content}}' from docs='{{docs}}'.
Just return the most relevant item I provided, no more explain. For example: {'function': 'config.resolve_path', 'docstring': None}""") Just return the most relevant item I provided, no more explain. For example: {'function': 'config.resolve_path', 'docstring': None}"""
)
prompt_workflow_selection = prompt.render(content=content, docs=similar_n_docs) prompt_workflow_selection = prompt.render(content=content, docs=similar_n_docs)
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
user_prompt=prompt_workflow_selection, system_prompt="You are an excellent assistant." user_prompt=prompt_workflow_selection, system_prompt="You are an excellent assistant."

View File

@@ -43,7 +43,7 @@ class APIBackend(SingletonBaseClass):
"content": system_prompt, "content": system_prompt,
} }
] ]
messages.extend(former_messages[-1*cfg.max_past_message_include:]) messages.extend(former_messages[-1 * cfg.max_past_message_include :])
messages.append( messages.append(
{ {
"role": "user", "role": "user",
@@ -82,7 +82,6 @@ class APIBackend(SingletonBaseClass):
temperature: float = None, temperature: float = None,
max_tokens: Optional[int] = None, max_tokens: Optional[int] = None,
) -> str: ) -> str:
if self.debug_mode: if self.debug_mode:
key = json.dumps(messages) key = json.dumps(messages)
if key in self.cache: if key in self.cache:

View File

@@ -12,6 +12,7 @@ class LogColors:
""" """
ANSI color codes for use in console output. ANSI color codes for use in console output.
""" """
RED = "\033[91m" RED = "\033[91m"
GREEN = "\033[92m" GREEN = "\033[92m"
YELLOW = "\033[93m" YELLOW = "\033[93m"
@@ -58,8 +59,11 @@ def formatting_log(logger, title="Info"):
a context manager, print liens before and after a function a context manager, print liens before and after a function
""" """
length = {"Start": 120, "Task": 120, "Info": 60, "Interact": 60, "End": 120}.get(title, 60) length = {"Start": 120, "Task": 120, "Info": 60, "Interact": 60, "End": 120}.get(title, 60)
color, bold = (LogColors.YELLOW, LogColors.BOLD) \ color, bold = (
if title in ["Start", "Task", "Info", "Interact", "End"] else (LogColors.CYAN, "") (LogColors.YELLOW, LogColors.BOLD)
if title in ["Start", "Task", "Info", "Interact", "End"]
else (LogColors.CYAN, "")
)
logger.info("") logger.info("")
logger.info(f"{color}{bold}{'-'} {title} {'-' * (length - len(title))}{LogColors.END}") logger.info(f"{color}{bold}{'-'} {title} {'-' * (length - len(title))}{LogColors.END}")
yield yield
@@ -99,12 +103,12 @@ class FinCoLog(SingletonBaseClass):
f"{LogColors.MAGENTA}{LogColors.BOLD}Role:{LogColors.END} " f"{LogColors.MAGENTA}{LogColors.BOLD}Role:{LogColors.END} "
f"{LogColors.CYAN}{m['role']}{LogColors.END}\n" f"{LogColors.CYAN}{m['role']}{LogColors.END}\n"
+ f"{LogColors.MAGENTA}{LogColors.BOLD}Content:{LogColors.END} " + f"{LogColors.MAGENTA}{LogColors.BOLD}Content:{LogColors.END} "
f"{LogColors.CYAN}{m['content']}{LogColors.END}\n") f"{LogColors.CYAN}{m['content']}{LogColors.END}\n"
)
def log_response(self, response: str): def log_response(self, response: str):
with formatting_log(self.logger, "GPT Response"): with formatting_log(self.logger, "GPT Response"):
self.logger.info( self.logger.info(f"{LogColors.CYAN}{response}{LogColors.END}\n")
f"{LogColors.CYAN}{response}{LogColors.END}\n")
# TODO: # TODO:
# It looks wierd if we only have logger # It looks wierd if we only have logger
@@ -118,14 +122,13 @@ class FinCoLog(SingletonBaseClass):
def plain_info(self, *args): def plain_info(self, *args):
for arg in args: for arg in args:
self.logger.info( self.logger.info(
f"{LogColors.YELLOW}{LogColors.BOLD}Info:{LogColors.END}{LogColors.WHITE}{arg}{LogColors.END}") f"{LogColors.YELLOW}{LogColors.BOLD}Info:{LogColors.END}{LogColors.WHITE}{arg}{LogColors.END}"
)
def warning(self, *args): def warning(self, *args):
for arg in args: for arg in args:
self.logger.warning( self.logger.warning(f"{LogColors.BLUE}{LogColors.BOLD}Warning:{LogColors.END}{arg}")
f"{LogColors.BLUE}{LogColors.BOLD}Warning:{LogColors.END}{arg}")
def error(self, *args): def error(self, *args):
for arg in args: for arg in args:
self.logger.error( self.logger.error(f"{LogColors.RED}{LogColors.BOLD}Error:{LogColors.END}{arg}")
f"{LogColors.RED}{LogColors.BOLD}Error:{LogColors.END}{arg}")

View File

@@ -10,8 +10,9 @@ from qlib.finco import get_finco_path
class PromptTemplate(SingletonBaseClass): class PromptTemplate(SingletonBaseClass):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
_template = yaml.load(open(Path.joinpath(get_finco_path(), "prompt_template.yaml"), "r"), _template = yaml.load(
Loader=yaml.FullLoader) open(Path.joinpath(get_finco_path(), "prompt_template.yaml"), "r"), Loader=yaml.FullLoader
)
for k, v in _template.items(): for k, v in _template.items():
if k == "mods": if k == "mods":
continue continue
@@ -28,5 +29,5 @@ class PromptTemplate(SingletonBaseClass):
file_path = Path(file_path) file_path = Path(file_path)
Path.mkdir(file_path.parent, exist_ok=True) Path.mkdir(file_path.parent, exist_ok=True)
with open(file_path, 'w') as f: with open(file_path, "w") as f:
yaml.dump(self.__dict__, f) yaml.dump(self.__dict__, f)

View File

@@ -2,7 +2,7 @@ import os
from pathlib import Path from pathlib import Path
import io import io
from typing import Any, List, Union from typing import Any, List, Optional, Union
import ruamel.yaml as yaml import ruamel.yaml as yaml
import abc import abc
import re import re
@@ -20,6 +20,8 @@ from qlib.finco.log import FinCoLog, LogColors
from qlib.finco.conf import Config from qlib.finco.conf import Config
from qlib.finco.knowledge import KnowledgeBase, Topic from qlib.finco.knowledge import KnowledgeBase, Topic
from qlib.finco.context import Design, Exp, WorkflowContextManager
COMPONENT_LIST = ["Dataset", "DataHandler", "Model", "Record", "Strategy", "Backtest"] COMPONENT_LIST = ["Dataset", "DataHandler", "Model", "Record", "Strategy", "Backtest"]
@@ -40,9 +42,19 @@ class Task:
- Edit Task: it is supposed to edit the code base directly. - Edit Task: it is supposed to edit the code base directly.
""" """
def __init__(self) -> None: _context_manager: WorkflowContextManager
self._context_manager = None
def __init__(self, tpl_ver: Optional[str] = None) -> None:
"""
Parameters
----------
tpl_ver : Optional[str]
The Version of the template.
If the previous results will greatly affect the next QA. We may use different version instead of combine everything in the same one.
"""
self.prompt_template = PromptTemplate() self.prompt_template = PromptTemplate()
self.tlp_ver = tpl_ver
self.executed = False self.executed = False
self.continuous = Config().continuous_mode self.continuous = Config().continuous_mode
self.logger = FinCoLog() self.logger = FinCoLog()
@@ -77,9 +89,9 @@ class Task:
def interact(self, prompt: str, **kwargs) -> Any: def interact(self, prompt: str, **kwargs) -> Any:
""" """
The user can interact with the task. This method only handle business in current task. It will return True The user can interact with the task. This method only handle business in current task. It will return True
while continuous is True. This method will return user input if input cannot be parsed as 'yes' or 'no'. while continuous is True. This method will return user input if input cannot be parsed as 'yes' or 'no'.
@return True, False, str @return True, False, str
""" """
self.logger.info(title="Interact") self.logger.info(title="Interact")
if self.continuous: if self.continuous:
@@ -100,11 +112,17 @@ class Task:
@property @property
def system(self): def system(self):
return self.prompt_template.get(self.__class__.__name__ + "_system") key = self.__class__.__name__ + "_system"
if self.tlp_ver is not None:
key = key + "." + self.tlp_ver
return self.prompt_template.get(key)
@property @property
def user(self): def user(self):
return self.prompt_template.get(self.__class__.__name__ + "_user") key = self.__class__.__name__ + "_user"
if self.tlp_ver is not None:
key = key + "." + self.tlp_ver
return self.prompt_template.get(key)
def __str__(self): def __str__(self):
return self.__class__.__name__ return self.__class__.__name__
@@ -123,9 +141,7 @@ class WorkflowTask(Task):
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
prompt_workflow_selection, self.system.render() prompt_workflow_selection, self.system.render()
) )
self.save_chat_history_to_context_manager( self.save_chat_history_to_context_manager(prompt_workflow_selection, response, self.system.render())
prompt_workflow_selection, response, self.system.render()
)
workflow = response.split(":")[1].strip().lower() workflow = response.split(":")[1].strip().lower()
self.executed = True self.executed = True
self._context_manager.set_context("workflow", workflow) self._context_manager.set_context("workflow", workflow)
@@ -156,13 +172,21 @@ class HighLevelPlanTask(PlanTask):
super().__init__() super().__init__()
def execute(self): def execute(self):
self._context_manager.set_context("target", "minimizing the maximum drawdown") self._context_manager.set_context("target", "minimizing the maximum drawdown")
self._context_manager.set_context("deliverable", "a daily quantitative investment strategy in A-share stock market. A model will be included in the strategy.") self._context_manager.set_context(
self._context_manager.set_context("user_intention", "build an A-share stock market daily portfolio in quantitative investment and minimize the maximum drawdown.") "deliverable",
"a daily quantitative investment strategy in A-share stock market. A model will be included in the strategy.",
)
self._context_manager.set_context(
"user_intention",
"build an A-share stock market daily portfolio in quantitative investment and minimize the maximum drawdown.",
)
self._context_manager.set_context("business_level", "Controller(e.g. Rolling retrain), Data") self._context_manager.set_context("business_level", "Controller(e.g. Rolling retrain), Data")
self._context_manager.set_context("algorithm_level", "supervised learning") self._context_manager.set_context("algorithm_level", "supervised learning")
self._context_manager.set_context("thinking_detail", "We want to leverage more recent data than outdated data. So we have to compare with or without rolling training process of the model like a meta controller. When with a rolling training process, data will be different at each time.") self._context_manager.set_context(
"thinking_detail",
"We want to leverage more recent data than outdated data. So we have to compare with or without rolling training process of the model like a meta controller. When with a rolling training process, data will be different at each time.",
)
target = self._context_manager.get_context("target") target = self._context_manager.get_context("target")
deliverable = self._context_manager.get_context("deliverable") deliverable = self._context_manager.get_context("deliverable")
@@ -182,24 +206,26 @@ class HighLevelPlanTask(PlanTask):
finance_knowledge = KnowledgeBase().query(knowledge_type=KnowledgeBase.KT_FINANCE, content=user_intention) finance_knowledge = KnowledgeBase().query(knowledge_type=KnowledgeBase.KT_FINANCE, content=user_intention)
system_prompt = self.system.render() system_prompt = self.system.render()
user_prompt = self.user.render(target=target, deliverable=deliverable, business_level=business_level, user_prompt = self.user.render(
algorithm_level=algorithm_level, thinking_detail=thinking_detail, target=target,
practice_knowledge=practice_knowledge, finance_knowledge=finance_knowledge, deliverable=deliverable,
user_intention=user_intention) business_level=business_level,
algorithm_level=algorithm_level,
response = APIBackend().build_messages_and_create_chat_completion( thinking_detail=thinking_detail,
user_prompt, system_prompt practice_knowledge=practice_knowledge,
finance_knowledge=finance_knowledge,
user_intention=user_intention,
) )
self.save_chat_history_to_context_manager( response = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt)
user_prompt, response, system_prompt
) response = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt)
self.save_chat_history_to_context_manager(user_prompt, response, system_prompt)
assert response is not None, "The response is None" assert response is not None, "The response is None"
res = re.search( res = re.search(r"Workflow:(.*)Experiments:(.*)Metrics:(.*)", response, re.S)
r"Workflow:(.*)Experiments:(.*)Metrics:(.*)", response, re.S
)
assert ( assert (
res is not None and len(res.groups()) == 3 res is not None and len(res.groups()) == 3
@@ -225,7 +251,7 @@ class SLPlanTask(PlanTask):
def execute(self): def execute(self):
workflow = self._context_manager.get_context("high_level_workflow") workflow = self._context_manager.get_context("high_level_workflow")
assert (workflow.lower() == "supervised learning"), "The workflow is not supervised learning" assert workflow.lower() == "supervised learning", "The workflow is not supervised learning"
target = self._context_manager.get_context("target") target = self._context_manager.get_context("target")
deliverable = self._context_manager.get_context("deliverable") deliverable = self._context_manager.get_context("deliverable")
@@ -237,30 +263,35 @@ class SLPlanTask(PlanTask):
experiment_count = max([i for i in range(10) if f"{i}." in experiments]) experiment_count = max([i for i in range(10) if f"{i}." in experiments])
infrastructure_knowledge = KnowledgeBase().query(knowledge_type=KnowledgeBase.KT_INFRASTRUCTURE, infrastructure_knowledge = KnowledgeBase().query(
content=experiments) knowledge_type=KnowledgeBase.KT_INFRASTRUCTURE, content=experiments
)
system_prompt = self.system.render() system_prompt = self.system.render()
user_prompt = self.user.render(target=target, deliverable=deliverable, business_level=business_level, user_prompt = self.user.render(
algorithm_level=algorithm_level, thinking_detail=thinking_detail, target=target,
infrastructure_knowledge=infrastructure_knowledge, deliverable=deliverable,
user_intention=user_intention, experiments=experiments) business_level=business_level,
algorithm_level=algorithm_level,
thinking_detail=thinking_detail,
infrastructure_knowledge=infrastructure_knowledge,
user_intention=user_intention,
experiments=experiments,
)
former_messages = [] former_messages = []
if self.replan: if self.replan:
user_prompt = f"your choice of predefined classes cannot be initialized.\nPlease rewrite the plan and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly." user_prompt = f"your choice of predefined classes cannot be initialized.\nPlease rewrite the plan and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly."
former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__]['None'][1:] former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__]["None"][1:]
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
user_prompt, system_prompt, former_messages=former_messages user_prompt, system_prompt, former_messages=former_messages
) )
self.save_chat_history_to_context_manager( self.save_chat_history_to_context_manager(user_prompt, system_prompt, self.system.render())
user_prompt, system_prompt, self.system.render()
)
for i in range(1, experiment_count + 1): for i in range(1, experiment_count + 1):
assert f"Experiment {i}" in response, f"The experiment {i} is not found in the response" assert f"Experiment {i}" in response, f"The experiment {i} is not found in the response"
self._context_manager.set_context("experiment_count", experiment_count) self._context_manager.set_context("experiment_count", experiment_count)
decision_pattern = re.compile("\((.*?)\)") decision_pattern = re.compile(r"\((.*?)\)")
class_pattern = re.compile("{(.*?)}-{(.*?)}") class_pattern = re.compile("{(.*?)}-{(.*?)}")
new_task = [] new_task = []
@@ -270,8 +301,10 @@ class SLPlanTask(PlanTask):
re_pattern = re_pattern + "Difference:(.*)" re_pattern = re_pattern + "Difference:(.*)"
re_pattern = re.compile(re_pattern, re.S) re_pattern = re.compile(re_pattern, re.S)
# 1) CURD on the workspace # 1) CURD on the workspace
self._context_manager
match_res = re.search(re_pattern, response) match_res = re.search(re_pattern, response)
for experiment_id in range(1, experiment_count + 1): for experiment_id in range(1, experiment_count + 1):
exp = Exp()
for name in COMPONENT_LIST: for name in COMPONENT_LIST:
target_line = [line for line in match_res.group(experiment_id).split("\n") if f"{name}:" in line] target_line = [line for line in match_res.group(experiment_id).split("\n") if f"{name}:" in line]
assert len(target_line) == 1, f"The {name} is not found in the response" assert len(target_line) == 1, f"The {name} is not found in the response"
@@ -289,27 +322,42 @@ class SLPlanTask(PlanTask):
self._context_manager.set_context(f"{name}_experiment_{experiment_id}_decision", decision) self._context_manager.set_context(f"{name}_experiment_{experiment_id}_decision", decision)
self._context_manager.set_context(f"{name}_experiment_{experiment_id}_classes", classes) self._context_manager.set_context(f"{name}_experiment_{experiment_id}_classes", classes)
self._context_manager.set_context(f"{name}_experiment_{experiment_id}_plan", target_line) self._context_manager.set_context(f"{name}_experiment_{experiment_id}_plan", target_line)
setattr(exp, name.lower(), Design(plan=target_line, classes=classes, decision=decision))
assert decision in ["Default", "Personized"], f"The decision of {name} is not correct" assert decision in ["Default", "Personized"], f"The decision of {name} is not correct"
# TODO: the strctured experiments should replace
self._context_manager.struct_context.exp_list.append(exp)
# 1) create a workspace # 1) create a workspace
# TODO: we have to make choice between `sl` and `sl-cfg` # TODO: we have to make choice between `sl` and `sl-cfg`
new_task.append( # new_task.append(
ConfigSearchTask() # # ConfigSearchTask(get_tpl_path()), # select template from the tpl folder directly. The prompt does not align with the task
) # ConfigSearchTask(), # select template from the baselines.
# )
# Because selecting template is not that stable. We try to start with
cfg_tpl = get_tpl_path() / "sl" / "workflow_config.yaml"
new_task.append(CMDTask(f"make a directory in the '{self._context_manager.struct_context.workspace}'"))
for i, exp in enumerate(self._context_manager.struct_context.exp_list, 1):
exp.template = cfg_tpl
new_task.append(
CMDTask(
f"copy the file '{cfg_tpl}' to '{self._context_manager.struct_context.workspace}' and rename to experiment_{i}.yaml"
)
)
# for name in COMPONENT_LIST: # for name in COMPONENT_LIST:
# if decision == "Default": # if decision == "Default":
new_task.extend([HyperparameterFinetuneActionTask()]) new_task.extend([HyperparameterFinetuneActionTask()])
# elif decision == "Personized": # elif decision == "Personized":
# # TODO open ImplementActionTask to let GPT write code # # TODO open ImplementActionTask to let GPT write code
# new_task.extend([HyperparameterActionTask(name), ConfigActionTask(name), YamlEditTask(name)]) # new_task.extend([HyperparameterActionTask(name), ConfigActionTask(name), YamlEditTask(name)])
# # new_task.extend([HyperparameterActionTask(name), ConfigActionTask(name), ImplementActionTask(name), CodeDumpTask(name), YamlEditTask(name)]) # # new_task.extend([HyperparameterActionTask(name), ConfigActionTask(name), ImplementActionTask(name), CodeDumpTask(name), YamlEditTask(name)])
return new_task return new_task
class RLPlanTask(PlanTask): class RLPlanTask(PlanTask):
def __init__( def __init__(
self, self,
) -> None: ) -> None:
super().__init__() super().__init__()
self.logger.error("The RL task is not implemented yet") self.logger.error("The RL task is not implemented yet")
@@ -328,7 +376,7 @@ class TrainTask(Task):
This train task is responsible for training model configure by yaml file. This train task is responsible for training model configure by yaml file.
""" """
def __init__(self, experiment_index, rolling = False, ddgda=False, **kwargs) -> None: def __init__(self, experiment_index, rolling=False, ddgda=False, **kwargs) -> None:
super().__init__() super().__init__()
self._output = None self._output = None
self._experiment_index = experiment_index self._experiment_index = experiment_index
@@ -344,41 +392,74 @@ class TrainTask(Task):
workflow = yaml.safe_load(f) workflow = yaml.safe_load(f)
self._context_manager.set_context(f"workflow_{self._experiment_index}_yaml", workflow) self._context_manager.set_context(f"workflow_{self._experiment_index}_yaml", workflow)
confirm = self.interact(f"I select this workflow file: " confirm = self.interact(
f"{LogColors().render(workflow_path, color=LogColors.YELLOW, style=LogColors.BOLD)}\n" f"I select this workflow file: "
f"{yaml.dump(workflow, default_flow_style=False)}" f"{LogColors().render(workflow_path, color=LogColors.YELLOW, style=LogColors.BOLD)}\n"
f"Are you sure you want to use? yes(Y/y), no(N/n):") f"{yaml.dump(workflow, default_flow_style=False)}"
f"Are you sure you want to use? yes(Y/y), no(N/n):"
)
if confirm is False: if confirm is False:
return [] return []
command = ["qrun", str(workflow_path)] if not self._rolling:
try: command = ["qrun", str(workflow_path)]
# Run the command and capture the output try:
workspace = self._context_manager.get_context("workspace") # Run the command and capture the output
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, workspace = self._context_manager.get_context("workspace")
text=True, encoding="utf8", cwd=str(workspace)) _ = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
text=True,
encoding="utf8",
cwd=str(workspace),
)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"An error occurred while running the subprocess: {e.stderr} {e.stdout}") print(f"An error occurred while running the subprocess: {e.stderr} {e.stdout}")
real_error = e.stderr+e.stdout real_error = e.stderr + e.stdout
KnowledgeBase().execute_knowledge.add([real_error]) KnowledgeBase().execute_knowledge.add([real_error])
if "data" in e.stdout.lower() or "handler" in e.stdout.lower(): if "data" in e.stdout.lower() or "handler" in e.stdout.lower():
return [HyperparameterActionTask("Dataset", regenerate=True, error=real_error), return [
HyperparameterActionTask("Dataset", regenerate=True, error=real_error),
HyperparameterActionTask("DataHandler", regenerate=True, error=real_error), HyperparameterActionTask("DataHandler", regenerate=True, error=real_error),
ConfigActionTask("Dataset"), ConfigActionTask("DataHandler"), YamlEditTask("Dataset"), ConfigActionTask("Dataset"),
YamlEditTask("DataHandler"), TrainTask()] ConfigActionTask("DataHandler"),
elif "model" in e.stdout.lower(): YamlEditTask("Dataset"),
return [HyperparameterActionTask("Model", regenerate=True, error=real_error), YamlEditTask("DataHandler"),
ConfigActionTask("Model"), YamlEditTask("Model"), TrainTask()] TrainTask(),
else: ]
ret_list = [] elif "model" in e.stdout.lower():
for component in COMPONENT_LIST: return [
ret_list.append(HyperparameterActionTask(component, regenerate=True, error=real_error)) HyperparameterActionTask("Model", regenerate=True, error=real_error),
ret_list.append(ConfigActionTask(component)) ConfigActionTask("Model"),
ret_list.append(YamlEditTask(component)) YamlEditTask("Model"),
ret_list.append(TrainTask()) TrainTask(),
return ret_list ]
else:
ret_list = []
for component in COMPONENT_LIST:
ret_list.append(HyperparameterActionTask(component, regenerate=True, error=real_error))
ret_list.append(ConfigActionTask(component))
ret_list.append(YamlEditTask(component))
ret_list.append(TrainTask())
return ret_list
elif not self._ddgda:
command = f"python -m qlib.contrib.rolling base --conf_path {workflow_path} run"
# Run the command and capture the output
workspace = self._context_manager.struct_context.workspace
subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True, cwd=str(workspace)
)
else:
command = f"python -m qlib.contrib.rolling ddgda --conf_path {workflow_path} run"
# Run the command and capture the output
workspace = self._context_manager.struct_context.workspace
subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True, cwd=str(workspace)
)
return [AnalysisTask()] return [AnalysisTask()]
@@ -386,9 +467,7 @@ class TrainTask(Task):
if self._output is not None: if self._output is not None:
# TODO: it will be overrides by later commands # TODO: it will be overrides by later commands
# utf8 can't decode normally on Windows # utf8 can't decode normally on Windows
self._context_manager.set_context( self._context_manager.set_context(self.__class__.__name__, self._output.decode("ANSI"))
self.__class__.__name__, self._output.decode("ANSI")
)
class AnalysisTask(Task): class AnalysisTask(Task):
@@ -415,9 +494,7 @@ class AnalysisTask(Task):
self._context_manager.set_context(k, v) self._context_manager.set_context(k, v)
def execute(self): def execute(self):
prompt = self.user.render( prompt = self.user.render(user_prompt=self._context_manager.get_context("user_prompt"))
user_prompt=self._context_manager.get_context("user_prompt")
)
be = APIBackend() be = APIBackend()
be.debug_mode = False be.debug_mode = False
@@ -430,8 +507,9 @@ class AnalysisTask(Task):
), ),
) )
analysers = response.replace(" ", "").split(",") analysers = response.replace(" ", "").split(",")
confirm = self.interact(f"I select these analysers: {analysers}\n" confirm = self.interact(
f"Are you sure you want to use? yes(Y/y), no(N/n) or prompt:") f"I select these analysers: {analysers}\n" f"Are you sure you want to use? yes(Y/y), no(N/n) or prompt:"
)
if confirm is False: if confirm is False:
analysers = [] analysers = []
break break
@@ -452,15 +530,14 @@ class AnalysisTask(Task):
# todo: analysis multi experiment(get recorder by id) # todo: analysis multi experiment(get recorder by id)
experiment_name = "workflow" experiment_name = "workflow"
R.set_uri(Path.joinpath(workspace, 'mlruns').as_uri()) R.set_uri(Path.joinpath(workspace, "mlruns").as_uri())
tasks = [] tasks = []
for analyser in analysers: for analyser in analysers:
if analyser in self.__ANALYZERS_PROJECT.keys(): if analyser in self.__ANALYZERS_PROJECT.keys():
tasks.append( tasks.append(
self.__ANALYZERS_PROJECT.get(analyser)( self.__ANALYZERS_PROJECT.get(analyser)(
recorder=R.get_recorder(experiment_name=experiment_name), recorder=R.get_recorder(experiment_name=experiment_name), output_dir=workspace
output_dir=workspace
) )
) )
@@ -475,12 +552,21 @@ class ActionTask(Task):
pass pass
class ConfigSearchTask(ActionTask): class ConfigSearchTask(ActionTask):
def __init__(self): """Find a template path that user can start with."""
super().__init__()
def crawl_the_folder(self, folder_path : Path): def __init__(self, conf_path: Optional[Union[Path, str]] = None):
super().__init__()
if conf_path is None:
# If no path provided, find path from the templates.
import qlib
conf_path = Path(os.path.abspath(inspect.getfile(qlib))).parent.parent / "examples" / "benchmarks"
if isinstance(conf_path, str):
conf_path = Path(conf_path)
self.conf_path = conf_path
def crawl_the_folder(self, folder_path: Path):
yaml_files = [] yaml_files = []
for root, _, files in os.walk(folder_path.as_posix()): for root, _, files in os.walk(folder_path.as_posix()):
for file in files: for file in files:
@@ -505,23 +591,18 @@ class ConfigSearchTask(ActionTask):
experiments.append((experiment_id, dataset_class, datahandler_class, model_class)) experiments.append((experiment_id, dataset_class, datahandler_class, model_class))
import qlib # TODO: each config should contains some descriptions to provide information to make the choice.
benchmarks_root_path = Path(os.path.abspath(inspect.getfile(qlib))).parent.parent / "examples" / "benchmarks" yaml_config_list = self.crawl_the_folder(self.conf_path)
yaml_config_list = self.crawl_the_folder(benchmarks_root_path)
system_prompt = self.system.render(yaml_config_list=yaml_config_list) system_prompt = self.system.render(yaml_config_list=yaml_config_list)
user_prompt = self.user.render(experiments=experiments) user_prompt = self.user.render(experiments=experiments)
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt)
user_prompt, system_prompt
)
former_messages = [] former_messages = []
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
user_prompt, self.system.render(), former_messages=former_messages user_prompt, self.system.render(), former_messages=former_messages
) )
self.save_chat_history_to_context_manager( self.save_chat_history_to_context_manager(user_prompt, response, self.system.render())
user_prompt, response, self.system.render()
)
experiment_count = self._context_manager.get_context("experiment_count") experiment_count = self._context_manager.get_context("experiment_count")
@@ -532,13 +613,22 @@ class ConfigSearchTask(ActionTask):
config_search_result = config_search_pattern.search(response) config_search_result = config_search_pattern.search(response)
return_task = [CMDTask(f"make a directory in the {self._context_manager.get_context('workspace')}"), ] return_task = [
CMDTask(f"make a directory in the {self._context_manager.get_context('workspace')}"),
]
for experiment_id in range(1, experiment_count + 1): for experiment_id in range(1, experiment_count + 1):
self._context_manager.set_context(f"experiment_{experiment_id}_template_config", config_search_result.group(experiment_id).strip('\n')) self._context_manager.set_context(
config_location = benchmarks_root_path / config_search_result.group(experiment_id) f"experiment_{experiment_id}_template_config", config_search_result.group(experiment_id).strip("\n")
return_task.append(CMDTask(f"copy file in {config_location} to {self._context_manager.get_context('workspace')} and rename to experiment_{experiment_id}.yaml")) )
config_location = self.conf_path / config_search_result.group(experiment_id)
return_task.append(
CMDTask(
f"copy file in {config_location} to {self._context_manager.get_context('workspace')} and rename to experiment_{experiment_id}.yaml"
)
)
return return_task return return_task
class CMDTask(ActionTask): class CMDTask(ActionTask):
""" """
This CMD task is responsible for ensuring compatibility across different operating systems. This CMD task is responsible for ensuring compatibility across different operating systems.
@@ -551,12 +641,8 @@ class CMDTask(ActionTask):
super().__init__() super().__init__()
def execute(self): def execute(self):
prompt = self.user.render( prompt = self.user.render(cmd_intention=self.cmd_intention, user_os=platform.system())
cmd_intention=self.cmd_intention, user_os=platform.system() response = APIBackend().build_messages_and_create_chat_completion(prompt, self.system.render())
)
response = APIBackend().build_messages_and_create_chat_completion(
prompt, self.system.render()
)
self._output = subprocess.check_output(response, shell=True, cwd=self.cwd) self._output = subprocess.check_output(response, shell=True, cwd=self.cwd)
return [] return []
@@ -564,9 +650,7 @@ class CMDTask(ActionTask):
if self._output is not None: if self._output is not None:
# TODO: it will be overrides by later commands # TODO: it will be overrides by later commands
# utf8 can't decode normally on Windows # utf8 can't decode normally on Windows
self._context_manager.set_context( self._context_manager.set_context(self.__class__.__name__, self._output.decode("ANSI"))
self.__class__.__name__, self._output.decode("ANSI")
)
class HyperparameterFinetuneActionTask(ActionTask): class HyperparameterFinetuneActionTask(ActionTask):
@@ -600,11 +684,9 @@ class HyperparameterFinetuneActionTask(ActionTask):
thinking_detail=thinking_detail, thinking_detail=thinking_detail,
user_intention=user_intention, user_intention=user_intention,
experiments=experiments, experiments=experiments,
template_configs=template_configs template_configs=template_configs,
)
response = APIBackend().build_messages_and_create_chat_completion(
user_prompt, system_prompt
) )
response = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt)
config_search_pattern = "" config_search_pattern = ""
for experiment_id in range(1, experiment_count + 1): for experiment_id in range(1, experiment_count + 1):
@@ -614,13 +696,15 @@ class HyperparameterFinetuneActionTask(ActionTask):
config_search_result = re.search(config_search_pattern, response) config_search_result = re.search(config_search_pattern, response)
return_tasks = [] return_tasks = []
for experiment_id in range(1, experiment_count + 1): for experiment_id in range(1, experiment_count + 1):
rolling_res = config_search_result.group((experiment_id-1) * 4 + 2).strip('\n') rolling_res = config_search_result.group((experiment_id - 1) * 4 + 2).strip("\n")
ddgda_res = config_search_result.group((experiment_id-1) * 4 + 3).strip('\n') ddgda_res = config_search_result.group((experiment_id - 1) * 4 + 3).strip("\n")
reason_res = config_search_result.group((experiment_id-1) * 4 + 4).strip('\n') reason_res = config_search_result.group((experiment_id - 1) * 4 + 4).strip("\n")
if "true" in ddgda_res.lower(): if "true" in ddgda_res.lower():
return_tasks.append(TrainTask(experiment_id, rolling=True, ddgda=True)) return_tasks.append(TrainTask(experiment_id, rolling=True, ddgda=True))
self._context_manager.struct_context.exp_list[experiment_id - 1].rolling = "ddgda"
if "true" in rolling_res.lower(): if "true" in rolling_res.lower():
return_tasks.append(TrainTask(experiment_id, rolling=True)) return_tasks.append(TrainTask(experiment_id, rolling=True))
self._context_manager.struct_context.exp_list[experiment_id - 1].rolling = "base"
else: else:
return_tasks.append(TrainTask(experiment_id)) return_tasks.append(TrainTask(experiment_id))
self._context_manager.set_context(f"experiment_{experiment_id}_rolling", rolling_res) self._context_manager.set_context(f"experiment_{experiment_id}_rolling", rolling_res)
@@ -652,24 +736,63 @@ class HyperparameterActionTask(ActionTask):
assert target_component_plan is not None, "target component plan is not set by plan maker" assert target_component_plan is not None, "target component plan is not set by plan maker"
assert target_component_classes is not None, "target component classes is not set by plan maker" assert target_component_classes is not None, "target component classes is not set by plan maker"
system_prompt = self.system.render(target_module=self.target_component, choice=target_component_decision, classes=target_component_classes) system_prompt = self.system.render(
target_module=self.target_component, choice=target_component_decision, classes=target_component_classes
)
target_component_classes_and_hyperparameters = [] target_component_classes_and_hyperparameters = []
for module_path, class_name in target_component_classes: for module_path, class_name in target_component_classes:
exec(f"from {module_path} import {class_name}") exec(f"from {module_path} import {class_name}")
hyperparameters = [hyperparameter for hyperparameter in {name: param for name, param in inspect.signature(eval(class_name).__init__).parameters.items() if name != "self" and name != "kwargs"}.keys()] hyperparameters = [
hyperparameter
for hyperparameter in {
name: param
for name, param in inspect.signature(eval(class_name).__init__).parameters.items()
if name != "self" and name != "kwargs"
}.keys()
]
if class_name == "LGBModel": if class_name == "LGBModel":
hyperparameters.extend([ "boosting_type", "num_leaves", "max_depth", "learning_rate", "n_estimators", "objective", "class_weight", "min_split_gain", "min_child_weight", "min_child_samples", "subsample", "subsample_freq", "colsample_bytree", "reg_alpha", "reg_lambda", "random_state", "n_jobs", "silent", "importance_type", "early_stopping_round", "metric", "num_class", "is_unbalance", "bagging_seed", "verbosity", ]) hyperparameters.extend(
[
"boosting_type",
"num_leaves",
"max_depth",
"learning_rate",
"n_estimators",
"objective",
"class_weight",
"min_split_gain",
"min_child_weight",
"min_child_samples",
"subsample",
"subsample_freq",
"colsample_bytree",
"reg_alpha",
"reg_lambda",
"random_state",
"n_jobs",
"silent",
"importance_type",
"early_stopping_round",
"metric",
"num_class",
"is_unbalance",
"bagging_seed",
"verbosity",
]
)
elif class_name == "SignalRecord": elif class_name == "SignalRecord":
hyperparameters.remove("model") hyperparameters.remove("model")
hyperparameters.remove("dataset") hyperparameters.remove("dataset")
hyperparameters.remove("recorder") hyperparameters.remove("recorder")
target_component_classes_and_hyperparameters.append((module_path, class_name, hyperparameters)) target_component_classes_and_hyperparameters.append((module_path, class_name, hyperparameters))
execute_knowledge = KnowledgeBase().query(knowledge_type=KnowledgeBase.KT_EXECUTE, execute_knowledge = KnowledgeBase().query(
content=target_component_plan) knowledge_type=KnowledgeBase.KT_EXECUTE, content=target_component_plan
infrastructure_knowledge = KnowledgeBase().query(knowledge_type=KnowledgeBase.KT_INFRASTRUCTURE, )
content=target_component_plan) infrastructure_knowledge = KnowledgeBase().query(
knowledge_type=KnowledgeBase.KT_INFRASTRUCTURE, content=target_component_plan
)
user_prompt = self.user.render( user_prompt = self.user.render(
user_requirement=user_prompt, user_requirement=user_prompt,
@@ -677,7 +800,7 @@ class HyperparameterActionTask(ActionTask):
target_component=self.target_component, target_component=self.target_component,
target_component_classes_and_hyperparameters=target_component_classes_and_hyperparameters, target_component_classes_and_hyperparameters=target_component_classes_and_hyperparameters,
execute_knowledge=execute_knowledge, execute_knowledge=execute_knowledge,
infrastructure_knowledge=infrastructure_knowledge infrastructure_knowledge=infrastructure_knowledge,
) )
former_messages = [] former_messages = []
if self.regenerate: if self.regenerate:
@@ -685,16 +808,14 @@ class HyperparameterActionTask(ActionTask):
user_prompt = f"your yaml config generated from your hyperparameter is not in the right format.\n The Yaml string generated from the hyperparameters is not in the right format.\nPlease rewrite the hyperparameters and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly.\nHyperparameters, Reason and Improve suggestion should always be included." user_prompt = f"your yaml config generated from your hyperparameter is not in the right format.\n The Yaml string generated from the hyperparameters is not in the right format.\nPlease rewrite the hyperparameters and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly.\nHyperparameters, Reason and Improve suggestion should always be included."
else: else:
user_prompt = f"your hyperparameter cannot be initialized, may be caused by wrong format of the value or wrong name or some value is not supported in Qlib.\nPlease rewrite the hyperparameters and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly.\nHyperparameters, Reason and Improve suggestion should always be included." user_prompt = f"your hyperparameter cannot be initialized, may be caused by wrong format of the value or wrong name or some value is not supported in Qlib.\nPlease rewrite the hyperparameters and answer with exact required format in system prompt and reply with no more explainations.\nThe error message: {self.error}. Please correct the former answer accordingly.\nHyperparameters, Reason and Improve suggestion should always be included."
former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][1:] former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__][
self.target_component
][1:]
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
user_prompt, system_prompt, former_messages=former_messages user_prompt, system_prompt, former_messages=former_messages
) )
self.save_chat_history_to_context_manager( self.save_chat_history_to_context_manager(user_prompt, response, system_prompt, self.target_component)
user_prompt, response, system_prompt, self.target_component res = re.search(r"(?i)Hyperparameters:(.*)Reason:(.*)Improve suggestion:(.*)", response, re.S)
)
res = re.search(
r"(?i)Hyperparameters:(.*)Reason:(.*)Improve suggestion:(.*)", response, re.S
)
try: try:
assert ( assert (
res is not None and len(res.groups()) == 3 res is not None and len(res.groups()) == 3
@@ -713,9 +834,7 @@ class HyperparameterActionTask(ActionTask):
self._context_manager.set_context(f"{self.target_component}_hyperparameters", hyperparameters) self._context_manager.set_context(f"{self.target_component}_hyperparameters", hyperparameters)
self._context_manager.set_context(f"{self.target_component}_reason", reason) self._context_manager.set_context(f"{self.target_component}_reason", reason)
self._context_manager.set_context( self._context_manager.set_context(f"{self.target_component}_improve_suggestion", improve_suggestion)
f"{self.target_component}_improve_suggestion", improve_suggestion
)
return [] return []
@@ -732,12 +851,14 @@ class ConfigActionTask(ActionTask):
target_component_classes = self._context_manager.get_context(f"{self.target_component}_classes") target_component_classes = self._context_manager.get_context(f"{self.target_component}_classes")
target_component_hyperparameters = self._context_manager.get_context(f"{self.target_component}_hyperparameters") target_component_hyperparameters = self._context_manager.get_context(f"{self.target_component}_hyperparameters")
system_prompt = self.system.render(target_module=self.target_component, choice=target_component_decision, classes=target_component_classes) system_prompt = self.system.render(
target_module=self.target_component, choice=target_component_decision, classes=target_component_classes
)
user_prompt = self.user.render( user_prompt = self.user.render(
user_requirement=user_prompt, user_requirement=user_prompt,
target_component_plan=target_component_plan, target_component_plan=target_component_plan,
target_component=self.target_component, target_component=self.target_component,
target_component_hyperparameters=target_component_hyperparameters target_component_hyperparameters=target_component_hyperparameters,
) )
former_messages = [] former_messages = []
# if self.reconfig and user_prompt == self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][-2]["content"]: # if self.reconfig and user_prompt == self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][-2]["content"]:
@@ -746,35 +867,36 @@ class ConfigActionTask(ActionTask):
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
user_prompt, system_prompt, former_messages=former_messages user_prompt, system_prompt, former_messages=former_messages
) )
self.save_chat_history_to_context_manager( self.save_chat_history_to_context_manager(user_prompt, response, system_prompt, self.target_component)
user_prompt, response, system_prompt, self.target_component
)
config = re.search(r"```yaml(.*)```", response, re.S).group(1) config = re.search(r"```yaml(.*)```", response, re.S).group(1)
try: try:
yaml_config = yaml.safe_load(io.StringIO(config)) yaml_config = yaml.safe_load(io.StringIO(config))
except yaml.YAMLError as e: except yaml.YAMLError as e:
self.logger.info(f"Yaml file is not in the correct format: {e}") self.logger.info(f"Yaml file is not in the correct format: {e}")
return_tasks = [HyperparameterActionTask(self.target_component, regenerate=True, error=str(e), error_type="yaml"), ConfigActionTask(self.target_component)] return_tasks = [
HyperparameterActionTask(self.target_component, regenerate=True, error=str(e), error_type="yaml"),
ConfigActionTask(self.target_component),
]
return return_tasks return return_tasks
if self.target_component == "Dataset": if self.target_component == "Dataset":
if 'handler' in yaml_config["dataset"]: if "handler" in yaml_config["dataset"]:
del yaml_config['dataset']['handler'] del yaml_config["dataset"]["handler"]
elif self.target_component == "DataHandler": elif self.target_component == "DataHandler":
for processor in yaml_config['handler']['kwargs']['infer_processors']: for processor in yaml_config["handler"]["kwargs"]["infer_processors"]:
if "kwargs" in processor and "fields_group" in processor["kwargs"]: if "kwargs" in processor and "fields_group" in processor["kwargs"]:
del processor["kwargs"]['fields_group'] del processor["kwargs"]["fields_group"]
for processor in yaml_config['handler']['kwargs']['learn_processors']: for processor in yaml_config["handler"]["kwargs"]["learn_processors"]:
if "kwargs" in processor and "fields_group" in processor["kwargs"]: if "kwargs" in processor and "fields_group" in processor["kwargs"]:
del processor["kwargs"]['fields_group'] del processor["kwargs"]["fields_group"]
if 'freq' in yaml_config['handler']['kwargs']: if "freq" in yaml_config["handler"]["kwargs"]:
yaml_config['handler']['kwargs']['freq'] = "day" # TODO hot fix freq because no data yaml_config["handler"]["kwargs"]["freq"] = "day" # TODO hot fix freq because no data
elif self.target_component == "Record": elif self.target_component == "Record":
for record in yaml_config['record']: for record in yaml_config["record"]:
if record['class'] == 'SigAnaRecord' and 'label_col' in record['kwargs']: if record["class"] == "SigAnaRecord" and "label_col" in record["kwargs"]:
del record['kwargs']["label_col"] del record["kwargs"]["label_col"]
def remove_default(config): def remove_default(config):
if isinstance(config, dict): if isinstance(config, dict):
@@ -787,6 +909,7 @@ class ConfigActionTask(ActionTask):
elif isinstance(config, list): elif isinstance(config, list):
for item in config: for item in config:
remove_default(item) remove_default(item)
remove_default(yaml_config) remove_default(yaml_config)
self._context_manager.set_context(f"{self.target_component}_config", yaml_config) self._context_manager.set_context(f"{self.target_component}_config", yaml_config)
@@ -797,7 +920,9 @@ class ImplementActionTask(ActionTask):
def __init__(self, target_component, reimplement=False) -> None: def __init__(self, target_component, reimplement=False) -> None:
super().__init__() super().__init__()
self.target_component = target_component self.target_component = target_component
assert COMPONENT_LIST.index(self.target_component) <= 2, "The target component is not in dataset datahandler and model" assert (
COMPONENT_LIST.index(self.target_component) <= 2
), "The target component is not in dataset datahandler and model"
self.reimplement = reimplement self.reimplement = reimplement
def execute(self): def execute(self):
@@ -809,16 +934,10 @@ class ImplementActionTask(ActionTask):
user_prompt = self._context_manager.get_context("user_prompt") user_prompt = self._context_manager.get_context("user_prompt")
prompt_element_dict = dict() prompt_element_dict = dict()
for component in COMPONENT_LIST: for component in COMPONENT_LIST:
prompt_element_dict[ prompt_element_dict[f"{component}_decision"] = self._context_manager.get_context(f"{component}_decision")
f"{component}_decision" prompt_element_dict[f"{component}_plan"] = self._context_manager.get_context(f"{component}_plan")
] = self._context_manager.get_context(f"{component}_decision")
prompt_element_dict[
f"{component}_plan"
] = self._context_manager.get_context(f"{component}_plan")
assert ( assert None not in prompt_element_dict.values(), "Some decision or plan is not set by plan maker"
None not in prompt_element_dict.values()
), "Some decision or plan is not set by plan maker"
config = self._context_manager.get_context(f"{self.target_component}_config") config = self._context_manager.get_context(f"{self.target_component}_config")
implement_prompt = self.user.render( implement_prompt = self.user.render(
@@ -830,7 +949,9 @@ class ImplementActionTask(ActionTask):
former_messages = [] former_messages = []
if self.reimplement: if self.reimplement:
implement_prompt = "your code seems wrong, please re-implement it and answer with exact required format and reply with no more explainations.\n" implement_prompt = "your code seems wrong, please re-implement it and answer with exact required format and reply with no more explainations.\n"
former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__][self.target_component][1:] former_messages = self._context_manager.get_context("chat_history")[self.__class__.__name__][
self.target_component
][1:]
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
implement_prompt, self.system.render(), former_messages=former_messages implement_prompt, self.system.render(), former_messages=former_messages
) )
@@ -838,17 +959,13 @@ class ImplementActionTask(ActionTask):
implement_prompt, response, self.system.render(), self.target_component implement_prompt, response, self.system.render(), self.target_component
) )
res = re.search( res = re.search(r"Code:(.*)Explanation:(.*)Modified config:(.*)", response, re.S)
r"Code:(.*)Explanation:(.*)Modified config:(.*)", response, re.S
)
assert ( assert (
res is not None and len(res.groups()) == 3 res is not None and len(res.groups()) == 3
), f"The response of implement action task of component {self.target_component} is not in the correct format" ), f"The response of implement action task of component {self.target_component} is not in the correct format"
code = re.search(r"```python(.*)```", res.group(1), re.S) code = re.search(r"```python(.*)```", res.group(1), re.S)
assert ( assert code is not None, "The code part of implementation action task response is not in the correct format"
code is not None
), "The code part of implementation action task response is not in the correct format"
code = code.group(1) code = code.group(1)
explanation = res.group(2) explanation = res.group(2)
modified_config = re.search(r"```yaml(.*)```", res.group(3), re.S) modified_config = re.search(r"```yaml(.*)```", res.group(3), re.S)
@@ -858,12 +975,8 @@ class ImplementActionTask(ActionTask):
modified_config = modified_config.group(1) modified_config = modified_config.group(1)
self._context_manager.set_context(f"{self.target_component}_code", code) self._context_manager.set_context(f"{self.target_component}_code", code)
self._context_manager.set_context( self._context_manager.set_context(f"{self.target_component}_code_explanation", explanation)
f"{self.target_component}_code_explanation", explanation self._context_manager.set_context(f"{self.target_component}_modified_config", modified_config)
)
self._context_manager.set_context(
f"{self.target_component}_modified_config", modified_config
)
return [] return []
@@ -908,15 +1021,16 @@ class YamlEditTask(ActionTask):
res = res | self.replace_key_value_recursive(item, target_key, new_value) res = res | self.replace_key_value_recursive(item, target_key, new_value)
return res return res
def execute(self): def execute(self):
# 1) read original and new content # 1) read original and new content
self.original_config_location = Path(os.path.join(self._context_manager.get_context('workspace'), "workflow_config.yaml")) self.original_config_location = Path(
os.path.join(self._context_manager.get_context("workspace"), "workflow_config.yaml")
)
with self.original_config_location.open("r") as f: with self.original_config_location.open("r") as f:
target_config = yaml.safe_load(f) target_config = yaml.safe_load(f)
update_config = self._context_manager.get_context(f'{self.target_component}_modified_config') update_config = self._context_manager.get_context(f"{self.target_component}_modified_config")
if update_config is None: if update_config is None:
update_config = self._context_manager.get_context(f'{self.target_component}_config') update_config = self._context_manager.get_context(f"{self.target_component}_config")
# 2) modify the module_path if code is implemented by finco # 2) modify the module_path if code is implemented by finco
# TODO because we skip code writing part, so we mute this step to avoid error # TODO because we skip code writing part, so we mute this step to avoid error
@@ -930,26 +1044,30 @@ class YamlEditTask(ActionTask):
if self.target_component == "Dataset": if self.target_component == "Dataset":
return [] return []
elif self.target_component == "DataHandler": elif self.target_component == "DataHandler":
dataset_update_config = self._context_manager.get_context(f'Dataset_modified_config') dataset_update_config = self._context_manager.get_context(f"Dataset_modified_config")
if dataset_update_config is None: if dataset_update_config is None:
dataset_update_config = self._context_manager.get_context(f'Dataset_config') dataset_update_config = self._context_manager.get_context(f"Dataset_config")
dataset_update_config['dataset']['kwargs']['handler'] = update_config['handler'] dataset_update_config["dataset"]["kwargs"]["handler"] = update_config["handler"]
update_config = dataset_update_config update_config = dataset_update_config
real_target_config_key = "dataset" real_target_config_key = "dataset"
else: else:
real_target_config_key = self.target_config_key real_target_config_key = self.target_config_key
# 3) replace the module # 3) replace the module
assert isinstance(update_config, dict) and real_target_config_key in update_config, "The config file is not in the correct format" assert (
assert self.replace_key_value_recursive(target_config, real_target_config_key, update_config[real_target_config_key]), "Replace of the yaml file failed." isinstance(update_config, dict) and real_target_config_key in update_config
), "The config file is not in the correct format"
assert self.replace_key_value_recursive(
target_config, real_target_config_key, update_config[real_target_config_key]
), "Replace of the yaml file failed."
# TODO hotfix for the bug that the record signalrecord config is not updated # TODO hotfix for the bug that the record signalrecord config is not updated
for record in target_config['task']['record']: for record in target_config["task"]["record"]:
if record['class'] == 'SignalRecord': if record["class"] == "SignalRecord":
if 'kwargs' in record and 'model' in record['kwargs']: if "kwargs" in record and "model" in record["kwargs"]:
del record['kwargs']["model"] del record["kwargs"]["model"]
if 'kwargs' in record and 'dataset' in record['kwargs']: if "kwargs" in record and "dataset" in record["kwargs"]:
del record['kwargs']["dataset"] del record["kwargs"]["dataset"]
# 4) save the config file # 4) save the config file
with self.original_config_location.open("w") as f: with self.original_config_location.open("w") as f:
@@ -957,20 +1075,25 @@ class YamlEditTask(ActionTask):
return [] return []
class CodeDumpTask(ActionTask): class CodeDumpTask(ActionTask):
def __init__(self, target_component) -> None: def __init__(self, target_component) -> None:
super().__init__() super().__init__()
self.target_component = target_component self.target_component = target_component
def execute(self): def execute(self):
code = self._context_manager.get_context(f'{self.target_component}_code') code = self._context_manager.get_context(f"{self.target_component}_code")
assert code is not None, "The code is not set" assert code is not None, "The code is not set"
with open(os.path.join(self._context_manager.get_context('workspace'), f'{self.target_component}_code.py'), 'w') as f: with open(
os.path.join(self._context_manager.get_context("workspace"), f"{self.target_component}_code.py"), "w"
) as f:
f.write(code) f.write(code)
try: try:
exec(f"from qlib.finco.{os.path.basename(self._context_manager.get_context('workspace'))}.{self.target_component}_code import *") exec(
f"from qlib.finco.{os.path.basename(self._context_manager.get_context('workspace'))}.{self.target_component}_code import *"
)
except (ImportError, AttributeError, SyntaxError): except (ImportError, AttributeError, SyntaxError):
return [ImplementActionTask(self.target_component, reimplement=True), CodeDumpTask(self.target_component)] return [ImplementActionTask(self.target_component, reimplement=True), CodeDumpTask(self.target_component)]
return [] return []
@@ -1054,8 +1177,9 @@ class SummarizeTask(Task):
user_prompt=prompt_workflow_selection, system_prompt=self.system.render() user_prompt=prompt_workflow_selection, system_prompt=self.system.render()
) )
KnowledgeBase().practice_knowledge.add([{"user_intention": user_prompt, KnowledgeBase().practice_knowledge.add(
"experiment_metrics": metrics_response}]) [{"user_intention": user_prompt, "experiment_metrics": metrics_response}]
)
# notes: summarize after all experiment added to KnowledgeBase # notes: summarize after all experiment added to KnowledgeBase
topic = Topic(name="rollingModel", describe=Template("What conclusion can you draw")) topic = Topic(name="rollingModel", describe=Template("What conclusion can you draw"))
@@ -1092,8 +1216,11 @@ class SummarizeTask(Task):
# in case of too large file # in case of too large file
# TODO: Perhaps summarization method instead of truncation would be a better approach # TODO: Perhaps summarization method instead of truncation would be a better approach
result.append( result.append(
{"file": file.name, "content": content[: self.__MAX_LENGTH_OF_FILE], {
"additional": self._context_manager.retrieve(file.name)} "file": file.name,
"content": content[: self.__MAX_LENGTH_OF_FILE],
"additional": self._context_manager.retrieve(file.name),
}
) )
return result return result
@@ -1134,8 +1261,9 @@ class SummarizeTask(Task):
postfix = filename.split(".")[-1] postfix = filename.split(".")[-1]
if postfix in ["jpeg"]: if postfix in ["jpeg"]:
description = self._context_manager.retrieve(filename) description = self._context_manager.retrieve(filename)
file_list.append({"file_name": filename, "description": description, file_list.append(
"path": str(Path(path).joinpath(filename))}) {"file_name": filename, "description": description, "path": str(Path(path).joinpath(filename))}
)
return file_list return file_list
def save_markdown(self, content: str, path): def save_markdown(self, content: str, path):

View File

@@ -10,13 +10,25 @@ data_handler_config: &data_handler_config
fit_start_time: 2008-01-01 fit_start_time: 2008-01-01
fit_end_time: 2014-12-31 fit_end_time: 2014-12-31
instruments: *market instruments: *market
infer_processors:
- class: RobustZScoreNorm
kwargs:
fields_group: feature
clip_outlier: true
- class: Fillna
kwargs:
fields_group: feature
learn_processors:
- class: DropnaLabel
- class: CSRankNorm
kwargs:
fields_group: label
port_analysis_config: &port_analysis_config port_analysis_config: &port_analysis_config
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs:
model: <MODEL> signal: <PRED>
dataset: <DATASET>
topk: 50 topk: 50
n_drop: 5 n_drop: 5
backtest: backtest:
@@ -32,18 +44,11 @@ port_analysis_config: &port_analysis_config
min_cost: 5 min_cost: 5
task: task:
model: model:
class: LGBModel class: LinearModel
module_path: qlib.contrib.model.gbdt module_path: qlib.contrib.model.linear
kwargs: kwargs:
loss: mse estimator: ridge
colsample_bytree: 0.8879 alpha: 0.05
learning_rate: 0.2
subsample: 0.8789
lambda_l1: 205.6999
lambda_l2: 580.9768
max_depth: 8
num_leaves: 210
num_threads: 20
dataset: dataset:
class: DatasetH class: DatasetH
module_path: qlib.data.dataset module_path: qlib.data.dataset
@@ -65,7 +70,7 @@ task:
- class: SigAnaRecord - class: SigAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
ana_long_short: False ana_long_short: True
ann_scaler: 252 ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp

View File

@@ -20,6 +20,7 @@ class SingletonBaseClass(metaclass=SingletonMeta):
This class becomes necessary This class becomes necessary
""" """
# TODO: Add move this class to Qlib's general utils. # TODO: Add move this class to Qlib's general utils.
@@ -42,4 +43,4 @@ def similarity(text1, text2):
def random_string(length=10): def random_string(length=10):
letters = string.ascii_letters + string.digits letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for i in range(length)) return "".join(random.choice(letters) for i in range(length))

View File

@@ -1,67 +1,40 @@
import sys import sys
import copy
import shutil import shutil
from pathlib import Path
from typing import List from typing import List
from pathlib import Path
from qlib.finco.task import HighLevelPlanTask, SummarizeTask, TrainTask from qlib.finco.task import HighLevelPlanTask, SummarizeTask
from qlib.finco.prompt_template import PromptTemplate, Template from qlib.finco.prompt_template import PromptTemplate, Template
from qlib.finco.log import FinCoLog, LogColors from qlib.finco.log import FinCoLog, LogColors
from qlib.finco.utils import similarity
from qlib.finco.llm import APIBackend from qlib.finco.llm import APIBackend
from qlib.finco.conf import Config from qlib.finco.conf import Config
from qlib.finco.knowledge import KnowledgeBase, Topic from qlib.finco.knowledge import KnowledgeBase, Topic
from qlib.finco.context import WorkflowContextManager
class WorkflowContextManager: # TODO: it is not necessary in current phase
"""Context Manager stores the context of the workflow""" # class TaskDAG:
# """
"""All context are key value pairs which saves the input, output and status of the whole workflow""" # This is a Task manager. it maintains a graph and a stack stucture to manager the task
# The reason why the DGA relationship is maintained outside instead of inside the task is that
def __init__(self) -> None: # - To make the creating of task simpler(user don't have to care about the relation-ship)
self.context = {} # - To manage the relation ship when poping and executing the tasks is relatively easier instead of scattering them everywhere
self.logger = FinCoLog() # """
# def __init__(self) -> None:
def set_context(self, key, value): # self._finished = []
if key in self.context: # self._stack = []
self.logger.warning("The key already exists in the context, the value will be overwritten") # self._dag = defaultdict(list) # from id(object) -> list of id(object)
self.context[key] = value #
# def pop(self):
def get_context(self, key): # return self._stack.pop(0)
# NOTE: if the key doesn't exist, return None. In the future, we may raise an error to detect abnormal behavior #
if key not in self.context: # def push(self, task: Union[Task, List[Task]], parent: Optional[Task] = None):
self.logger.warning("The key doesn't exist in the context") # if isinstance(task, Task):
return None # task = [task]
return self.context[key] # if parent is not None:
# self._dag
def update_context(self, key, new_value): #
# NOTE: if the key doesn't exist, return None. In the future, we may raise an error to detect abnormal behavior # def done(self) -> bool:
if key not in self.context: # return len(self._stack) == 0
self.logger.warning("The key doesn't exist in the context")
self.context.update({key: new_value})
def get_all_context(self):
"""return a deep copy of the context"""
"""TODO: do we need to return a deep copy?"""
return copy.deepcopy(self.context)
def retrieve(self, query: str) -> dict:
if query in self.context.keys():
return {query: self.context.get(query)}
# Note: retrieve information from context by string similarity maybe abandon in future
scores = {}
for k, v in self.context.items():
scores.update({k: max(similarity(query, k), similarity(query, v))})
max_score_key = max(scores, key=scores.get)
return {max_score_key: self.context.get(max_score_key)}
def clear(self, reserve: list = None):
if reserve is None:
reserve = []
_context = {k: self.get_context(k) for k in reserve}
self.context = _context
class WorkflowManager: class WorkflowManager:
@@ -78,8 +51,7 @@ class WorkflowManager:
self._confirm_and_rm() self._confirm_and_rm()
self.prompt_template = PromptTemplate() self.prompt_template = PromptTemplate()
self.context = WorkflowContextManager() self.context = WorkflowContextManager(workspace=self._workspace)
self.context.set_context("workspace", self._workspace)
self.default_user_prompt = "Please help me build a low turnover strategy that focus more on longterm return in China A csi300. Please help to use lightgbm model." self.default_user_prompt = "Please help me build a low turnover strategy that focus more on longterm return in China A csi300. Please help to use lightgbm model."
def _confirm_and_rm(self): def _confirm_and_rm(self):
@@ -92,7 +64,8 @@ class WorkflowManager:
f"If you do not need to delete {self._workspace}," f"If you do not need to delete {self._workspace},"
f" please change the workspace dir or rename existing files\n" f" please change the workspace dir or rename existing files\n"
f"Are you sure you want to delete, yes(Y/y), no (N/n):", f"Are you sure you want to delete, yes(Y/y), no (N/n):",
color=LogColors.WHITE) color=LogColors.WHITE,
)
) )
if str(flag) not in ["Y", "y"]: if str(flag) not in ["Y", "y"]:
sys.exit() sys.exit()
@@ -150,10 +123,12 @@ class WorkflowManager:
# TODO: sort the task list based on the priority of the task # TODO: sort the task list based on the priority of the task
# task_list = sorted(task_list, key=lambda x: x.task_type) # task_list = sorted(task_list, key=lambda x: x.task_type)
t = task_list.pop(0) t = task_list.pop(0)
self.logger.info(f"Task finished: {[str(task) for task in task_finished]}", self.logger.info(
f"Task in queue: {task_list_info}", f"Task finished: {[str(task) for task in task_finished]}",
f"Executing task: {str(t)}", f"Task in queue: {task_list_info}",
title="Task") f"Executing task: {str(t)}",
title="Task",
)
t.assign_context_manager(self.context) t.assign_context_manager(self.context)
res = t.execute() res = t.execute()
@@ -174,9 +149,10 @@ class LearnManager:
self.epoch = 0 self.epoch = 0
self.wm = WorkflowManager() self.wm = WorkflowManager()
self.topics = [Topic(name=topic, describe=self.wm.prompt_template.get(f"Topic_{topic}")) for topic in self.topics = [
self.__DEFAULT_TOPICS] Topic(name=topic, describe=self.wm.prompt_template.get(f"Topic_{topic}")) for topic in self.__DEFAULT_TOPICS
self.knowledge_base = KnowledgeBase(workdir=Path.cwd().joinpath('knowledge')) ]
self.knowledge_base = KnowledgeBase(workdir=Path.cwd().joinpath("knowledge"))
self.knowledge_base.execute_knowledge.add([]) self.knowledge_base.execute_knowledge.add([])
self.knowledge_base.query(knowledge_type="infrastructure", content="resolve_path") self.knowledge_base.query(knowledge_type="infrastructure", content="resolve_path")
@@ -209,14 +185,17 @@ class LearnManager:
for task in task_finished: for task in task_finished:
prompt_workflow_selection = self.wm.prompt_template.get(f"{self.__class__.__name__}_user").render( prompt_workflow_selection = self.wm.prompt_template.get(f"{self.__class__.__name__}_user").render(
summary=summary, brief=knowledge_of_topics, summary=summary,
brief=knowledge_of_topics,
task_finished=[str(t) for t in task_finished], task_finished=[str(t) for t in task_finished],
task=task.__class__.__name__, system=task.system.render(), user_prompt=user_prompt task=task.__class__.__name__,
system=task.system.render(),
user_prompt=user_prompt,
) )
response = APIBackend().build_messages_and_create_chat_completion( response = APIBackend().build_messages_and_create_chat_completion(
user_prompt=prompt_workflow_selection, user_prompt=prompt_workflow_selection,
system_prompt=self.wm.prompt_template.get(f"{self.__class__.__name__}_system").render() system_prompt=self.wm.prompt_template.get(f"{self.__class__.__name__}_system").render(),
) )
# todo: response assertion # todo: response assertion

View File

@@ -12,4 +12,4 @@ if [ -e $DIR/cridential.sh ]; then
fi fi
# run the command # run the command
python -m qlib.finco.cli "please help me build a low turnover strategy that focus more on longterm return" python -m qlib.finco.cli "build an A-share stock market daily portfolio in quantitative investment and minimize the maximum drawdown."

View File

@@ -176,11 +176,12 @@ setup(
], ],
"finco": [ "finco": [
# finco is not necessary for all Qlib users; So a single require section is used for it. # finco is not necessary for all Qlib users; So a single require section is used for it.
"openapi", "openai",
"pydantic", # Please add it to basic requirements after the design of pydantic is state. "pydantic", # Please add it to basic requirements after the design of pydantic is state.
"pydantic-settings",
"python-dotenv", # I don't think this is necessary if we use pydantic. "python-dotenv", # I don't think this is necessary if we use pydantic.
"fuzzywuzzy", "fuzzywuzzy",
"python-Levenshtein" # not necessary but accelerate fuzzywuzzy calculation "python-Levenshtein", # not necessary but accelerate fuzzywuzzy calculation
], ],
}, },
include_package_data=True, include_package_data=True,