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

Optimize log and interact of FinCo (#1549)

* use FinCoLog for a better interact experience

* addition file changes

* optimize format

* optimize format
This commit is contained in:
Fivele-Li
2023-06-14 14:48:17 +08:00
committed by GitHub
parent fa7ef29281
commit 1a523df007
4 changed files with 122 additions and 47 deletions

View File

@@ -50,7 +50,7 @@ class APIBackend(Singleton):
fcl = FinCoLog() fcl = FinCoLog()
response = self.try_create_chat_completion(messages=messages) response = self.try_create_chat_completion(messages=messages)
fcl.log_message(messages) fcl.log_message(messages)
fcl.info(response) fcl.log_response(response)
return response return response
def try_create_chat_completion(self, max_retry=10, **kwargs): def try_create_chat_completion(self, max_retry=10, **kwargs):

View File

@@ -1,19 +1,42 @@
""" """
This module will base on Qlib's logger module and provides some interactive functions. This module will base on Qlib's logger module and provides some interactive functions.
""" """
from email import message_from_binary_file import logging
from typing import Dict, List from typing import Dict, List
from qlib.finco.utils import Singleton from qlib.finco.utils import Singleton
from qlib.log import get_module_logger from qlib.log import get_module_logger
from contextlib import contextmanager from contextlib import contextmanager
# a context manager, print liens before and after a function
class LogColors:
"""
ANSI color codes for use in console output.
"""
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
MAGENTA = "\033[95m"
CYAN = "\033[96m"
BOLD = "\033[1m"
END = "\033[0m"
WHITE = "\033[97m"
GRAY = "\033[90m"
BLACK = "\033[30m"
# TODO: Provide better interface to render text. (e.g. render(text, color.., style ..))
@contextmanager @contextmanager
def formating_log(logger, text="Interaction"): def formatting_log(logger, title="Info"):
"""
a context manager, print liens before and after a function
"""
length = {"Start": 120, "Task": 120, "Info": 60}.get(title, 60)
color, bold = (LogColors.YELLOW, LogColors.BOLD) if title in ["Start", "Info", "Task"] else (LogColors.CYAN, "")
logger.info("") logger.info("")
logger.info("=" * 20 + f" BEGIN:{text} " + "=" * 20) logger.info(f"{color}{bold}{'-'} {title} {'-' * (length - len(title))}{LogColors.END}")
yield yield
logger.info("=" * 20 + f" END: {text} " + "=" * 20)
logger.info("") logger.info("")
@@ -21,7 +44,16 @@ class FinCoLog(Singleton):
# TODO: # TODO:
# - config to file logger and save it into workspace # - config to file logger and save it into workspace
def __init__(self) -> None: def __init__(self) -> None:
self.logger = get_module_logger("interactive") # self.logger = get_module_logger("interactive")
self.logger = logging.Logger("interactive")
# TODO: merge these with Qlib's default logger.
# We can do the same thing by changing the default log dict of Qlib.
# Reference: https://github.com/microsoft/qlib/blob/main/qlib/config.py#L155
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(message)s"))
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
def log_message(self, messages: List[Dict[str, str]]): def log_message(self, messages: List[Dict[str, str]]):
""" """
@@ -36,13 +68,30 @@ class FinCoLog(Singleton):
}, },
] ]
""" """
with formating_log(self.logger): with formatting_log(self.logger, "GPT Messages"):
for m in messages: for m in messages:
self.logger.info(f"Role: {m['role']}") self.logger.info(
self.logger.info(f"Content: {m['content']}") f"{LogColors.MAGENTA}{LogColors.BOLD}Role:{LogColors.END} "
f"{LogColors.CYAN}{m['role']}{LogColors.END}\n"
+ f"{LogColors.MAGENTA}{LogColors.BOLD}Content:{LogColors.END} "
f"{LogColors.CYAN}{m['content']}{LogColors.END}")
def log_response(self, response: str):
with formatting_log(self.logger, "GPT Response"):
self.logger.info(
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
def info(self, *args, **kwargs): def info(self, *args, plain=False, title="Info"):
with formating_log(self.logger, "info"): if plain:
self.logger.info(*args, **kwargs) return self.plain_info(*args)
with formatting_log(self.logger, title):
for arg in args:
self.logger.info(f"{LogColors.WHITE}{arg}{LogColors.END}")
def plain_info(self, *args):
for arg in args:
# self.logger.info(arg)
self.logger.info(
f"{LogColors.YELLOW}{LogColors.BOLD}Info:{LogColors.END}{LogColors.WHITE}{arg}{LogColors.END}")

View File

@@ -19,6 +19,7 @@ from qlib.workflow.record_temp import HFSignalRecord, SignalRecord
from qlib.contrib.analyzer import HFAnalyzer, SignalAnalyzer from qlib.contrib.analyzer import HFAnalyzer, SignalAnalyzer
from qlib.utils import init_instance_by_config from qlib.utils import init_instance_by_config
from qlib.workflow import R from qlib.workflow import R
from qlib.finco.log import FinCoLog
class Task: class Task:
@@ -42,14 +43,15 @@ class Task:
self._context_manager = None self._context_manager = None
self.prompt_template = PormptTemplate() self.prompt_template = PormptTemplate()
self.executed = False self.executed = False
self.logger: logging.Logger = get_module_logger( # self.logger: logging.Logger = get_module_logger(
f"finco.{self.__class__.__name__}" # f"finco.{self.__class__.__name__}"
) # )
self.logger = FinCoLog()
def summarize(self) -> str: def summarize(self) -> str:
"""After the execution of the task, it is supposed to generated some context about the execution""" """After the execution of the task, it is supposed to generated some context about the execution"""
"""This function might be converted to abstract method in the future""" """This function might be converted to abstract method in the future"""
self.logger.info("The method has nothing to summarize") self.logger.info(f"{self.__class__.__name__}: The task has nothing to summarize", plain=True)
def assign_context_manager(self, context_manager): def assign_context_manager(self, context_manager):
"""assign the workflow context manager to the task""" """assign the workflow context manager to the task"""
@@ -90,6 +92,14 @@ class Task:
def user(self): def user(self):
return self.prompt_template.__getattribute__(self.__class__.__name__ + "_user") return self.prompt_template.__getattribute__(self.__class__.__name__ + "_user")
@staticmethod
def confirm(prompt: str):
answer = input(prompt)
return answer
def __str__(self):
return self.__class__.__name__
class WorkflowTask(Task): class WorkflowTask(Task):
"""This task is supposed to be the first task of the workflow""" """This task is supposed to be the first task of the workflow"""
@@ -114,6 +124,10 @@ class WorkflowTask(Task):
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)
answer = self.confirm(f"I select this workflow: {workflow}\n"
f"Are you sure you want to use? yes(Y/y), no(N/n):")
if str(answer) not in ["Y", "y"]:
return []
if workflow == "supervised learning": if workflow == "supervised learning":
return [SLPlanTask()] return [SLPlanTask()]
elif workflow == "reinforcement learning": elif workflow == "reinforcement learning":
@@ -254,32 +268,36 @@ class RecorderTask(Task):
ANALYZERS_DOCS=self.__ANALYZERS_DOCS, ANALYZERS_DOCS=self.__ANALYZERS_DOCS,
), ),
) )
# it's better to move to another Task
workflow_config = (
self._context_manager.get_context("workflow_config")
if self._context_manager.get_context("workflow_config")
else "workflow_config.yaml"
)
workspace = self._context_manager.get_context("workspace")
with workspace.joinpath(workflow_config).open() as f:
workflow = yaml.safe_load(f)
model = init_instance_by_config(workflow["task"]["model"])
dataset = init_instance_by_config(workflow["task"]["dataset"])
with R.start(experiment_name="finCo"):
model.fit(dataset)
R.save_objects(trained_model=model)
# prediction
recorder = R.get_recorder()
sr = SignalRecord(model, dataset, recorder)
sr.generate()
analysers = response.split(",") analysers = response.split(",")
if isinstance(analysers, list):
self.logger.info(f"selected analysers: {analysers}") answer = self.confirm(f"I select these analysers: {analysers}\nAre you sure you want to use? yes(Y/y), no(N/n):")
if str(answer) not in ["Y", "y"]:
analysers = []
if isinstance(analysers, list) and len(analysers):
self.logger.info(f"selected analysers: {analysers}", plain=True)
# it's better to move to another Task
workflow_config = (
self._context_manager.get_context("workflow_config")
if self._context_manager.get_context("workflow_config")
else "workflow_config.yaml"
)
workspace = self._context_manager.get_context("workspace")
with workspace.joinpath(workflow_config).open() as f:
workflow = yaml.safe_load(f)
model = init_instance_by_config(workflow["task"]["model"])
dataset = init_instance_by_config(workflow["task"]["dataset"])
with R.start(experiment_name="finCo"):
model.fit(dataset)
R.save_objects(trained_model=model)
# prediction
recorder = R.get_recorder()
sr = SignalRecord(model, dataset, recorder)
sr.generate()
tasks = [] tasks = []
for analyser in analysers: for analyser in analysers:
if analyser in self.__ANALYZERS_PROJECT.keys(): if analyser in self.__ANALYZERS_PROJECT.keys():
@@ -578,7 +596,7 @@ class SummarizeTask(Task):
if postfix in ["py", "log", "yaml"]: if postfix in ["py", "log", "yaml"]:
with open(file) as f: with open(file) as f:
content = f.read() content = f.read()
self.logger.info(f"file to summarize: {file}") self.logger.info(f"file to summarize: {file}", plain=True)
# 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(
@@ -612,11 +630,10 @@ class SummarizeTask(Task):
for filename in files: for filename in files:
postfix = filename.split(".")[-1] postfix = filename.split(".")[-1]
if postfix in ["jpeg"]: if postfix in ["jpeg"]:
file_path = os.path.join(root, filename) file_list.append(str(Path(self.workspace).joinpath(filename)))
file_list.append(str(Path(file_path).relative_to(self.workspace)))
return file_list return file_list
def save_markdown(self, content: str): def save_markdown(self, content: str):
with open(Path(self.workspace).joinpath(self.__DEFAULT_REPORT_NAME), "w") as f: with open(Path(self.workspace).joinpath(self.__DEFAULT_REPORT_NAME), "w") as f:
f.write(content) f.write(content)
self.logger.info(f"report has saved to {self.__DEFAULT_REPORT_NAME}") self.logger.info(f"report has saved to {self.__DEFAULT_REPORT_NAME}", plain=True)

View File

@@ -110,15 +110,23 @@ class WorkflowManager:
self.set_context("user_prompt", self.default_user_prompt) self.set_context("user_prompt", self.default_user_prompt)
else: else:
self.set_context("user_prompt", prompt) self.set_context("user_prompt", prompt)
self.fco.info(f"user_prompt: {self.get_context().get_context('user_prompt')}", title="Start")
# NOTE: list may not be enough for general task list # NOTE: list may not be enough for general task list
task_list = [WorkflowTask(), RecorderTask(), SummarizeTask()] task_list = [WorkflowTask(), RecorderTask(), SummarizeTask()]
task_finished = []
while len(task_list): while len(task_list):
self.fco.info(f"Current Task List: {task_list}") task_list_info = [str(task) for task in task_list]
# task list is not long, so sort it is not a big problem # task list is not long, so sort it is not a big problem
# 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.fco.info(f"Task finished: {[str(task) for task in task_finished]}",
f"Task in queue: {task_list_info}",
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()
if not cfg.continous_mode: if not cfg.continous_mode:
@@ -128,4 +136,5 @@ class WorkflowManager:
task_list = res + task_list task_list = res + task_list
else: else:
raise NotImplementedError(f"Unsupported Task type {t}") raise NotImplementedError(f"Unsupported Task type {t}")
task_finished.append(t)
return self._workspace return self._workspace