mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-05 12:00:58 +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:
@@ -50,7 +50,7 @@ class APIBackend(Singleton):
|
||||
fcl = FinCoLog()
|
||||
response = self.try_create_chat_completion(messages=messages)
|
||||
fcl.log_message(messages)
|
||||
fcl.info(response)
|
||||
fcl.log_response(response)
|
||||
return response
|
||||
|
||||
def try_create_chat_completion(self, max_retry=10, **kwargs):
|
||||
|
||||
@@ -1,19 +1,42 @@
|
||||
"""
|
||||
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 qlib.finco.utils import Singleton
|
||||
from qlib.log import get_module_logger
|
||||
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
|
||||
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("=" * 20 + f" BEGIN:{text} " + "=" * 20)
|
||||
logger.info(f"{color}{bold}{'-'} {title} {'-' * (length - len(title))}{LogColors.END}")
|
||||
yield
|
||||
logger.info("=" * 20 + f" END: {text} " + "=" * 20)
|
||||
logger.info("")
|
||||
|
||||
|
||||
@@ -21,7 +44,16 @@ class FinCoLog(Singleton):
|
||||
# TODO:
|
||||
# - config to file logger and save it into workspace
|
||||
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]]):
|
||||
"""
|
||||
@@ -36,13 +68,30 @@ class FinCoLog(Singleton):
|
||||
},
|
||||
]
|
||||
"""
|
||||
with formating_log(self.logger):
|
||||
with formatting_log(self.logger, "GPT Messages"):
|
||||
for m in messages:
|
||||
self.logger.info(f"Role: {m['role']}")
|
||||
self.logger.info(f"Content: {m['content']}")
|
||||
self.logger.info(
|
||||
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:
|
||||
# It looks wierd if we only have logger
|
||||
def info(self, *args, **kwargs):
|
||||
with formating_log(self.logger, "info"):
|
||||
self.logger.info(*args, **kwargs)
|
||||
def info(self, *args, plain=False, title="Info"):
|
||||
if plain:
|
||||
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}")
|
||||
|
||||
@@ -19,6 +19,7 @@ from qlib.workflow.record_temp import HFSignalRecord, SignalRecord
|
||||
from qlib.contrib.analyzer import HFAnalyzer, SignalAnalyzer
|
||||
from qlib.utils import init_instance_by_config
|
||||
from qlib.workflow import R
|
||||
from qlib.finco.log import FinCoLog
|
||||
|
||||
|
||||
class Task:
|
||||
@@ -42,14 +43,15 @@ class Task:
|
||||
self._context_manager = None
|
||||
self.prompt_template = PormptTemplate()
|
||||
self.executed = False
|
||||
self.logger: logging.Logger = get_module_logger(
|
||||
f"finco.{self.__class__.__name__}"
|
||||
)
|
||||
# self.logger: logging.Logger = get_module_logger(
|
||||
# f"finco.{self.__class__.__name__}"
|
||||
# )
|
||||
self.logger = FinCoLog()
|
||||
|
||||
def summarize(self) -> str:
|
||||
"""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"""
|
||||
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):
|
||||
"""assign the workflow context manager to the task"""
|
||||
@@ -90,6 +92,14 @@ class Task:
|
||||
def user(self):
|
||||
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):
|
||||
"""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()
|
||||
self.executed = True
|
||||
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":
|
||||
return [SLPlanTask()]
|
||||
elif workflow == "reinforcement learning":
|
||||
@@ -254,32 +268,36 @@ class RecorderTask(Task):
|
||||
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(",")
|
||||
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 = []
|
||||
for analyser in analysers:
|
||||
if analyser in self.__ANALYZERS_PROJECT.keys():
|
||||
@@ -578,7 +596,7 @@ class SummarizeTask(Task):
|
||||
if postfix in ["py", "log", "yaml"]:
|
||||
with open(file) as f:
|
||||
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
|
||||
# TODO: Perhaps summarization method instead of truncation would be a better approach
|
||||
result.append(
|
||||
@@ -612,11 +630,10 @@ class SummarizeTask(Task):
|
||||
for filename in files:
|
||||
postfix = filename.split(".")[-1]
|
||||
if postfix in ["jpeg"]:
|
||||
file_path = os.path.join(root, filename)
|
||||
file_list.append(str(Path(file_path).relative_to(self.workspace)))
|
||||
file_list.append(str(Path(self.workspace).joinpath(filename)))
|
||||
return file_list
|
||||
|
||||
def save_markdown(self, content: str):
|
||||
with open(Path(self.workspace).joinpath(self.__DEFAULT_REPORT_NAME), "w") as f:
|
||||
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)
|
||||
|
||||
@@ -110,15 +110,23 @@ class WorkflowManager:
|
||||
self.set_context("user_prompt", self.default_user_prompt)
|
||||
else:
|
||||
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
|
||||
task_list = [WorkflowTask(), RecorderTask(), SummarizeTask()]
|
||||
task_finished = []
|
||||
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
|
||||
# TODO: sort the task list based on the priority of the task
|
||||
# task_list = sorted(task_list, key=lambda x: x.task_type)
|
||||
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)
|
||||
res = t.execute()
|
||||
if not cfg.continous_mode:
|
||||
@@ -128,4 +136,5 @@ class WorkflowManager:
|
||||
task_list = res + task_list
|
||||
else:
|
||||
raise NotImplementedError(f"Unsupported Task type {t}")
|
||||
task_finished.append(t)
|
||||
return self._workspace
|
||||
|
||||
Reference in New Issue
Block a user