mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-14 16:26:55 +08:00
Merge pull request #1592 from Fivele-Li/update_knowledge_module
update knowledge module;
This commit is contained in:
@@ -1,10 +1,112 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from typing import List
|
from typing import List, Union
|
||||||
|
import pickle
|
||||||
|
import yaml
|
||||||
|
|
||||||
from qlib.workflow import R
|
from qlib.workflow import R
|
||||||
from qlib.finco.log import FinCoLog
|
from qlib.finco.log import FinCoLog
|
||||||
from qlib.finco.llm import APIBackend
|
from qlib.finco.llm import APIBackend
|
||||||
|
from qlib.finco.utils import similarity, random_string
|
||||||
|
|
||||||
|
logger = FinCoLog()
|
||||||
|
|
||||||
|
|
||||||
|
class Storage:
|
||||||
|
"""
|
||||||
|
This class is responsible for storage and loading of Knowledge related data.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, path: Union[str, Path], name: str = None):
|
||||||
|
self.path = path if isinstance(path, Path) else Path(path)
|
||||||
|
self.name = name if name else self.path.name
|
||||||
|
self.source = None
|
||||||
|
|
||||||
|
# todo: get document by key
|
||||||
|
self.documents = []
|
||||||
|
|
||||||
|
def add(self, documents: List):
|
||||||
|
self.documents.extend(documents)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def load(self, **kwargs):
|
||||||
|
raise NotImplementedError(f"Please implement the `load` method.")
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
raise NotImplementedError(f"Please implement the `save` method.")
|
||||||
|
|
||||||
|
|
||||||
|
class PickleStorage(Storage):
|
||||||
|
"""
|
||||||
|
This class is responsible for storage and loading of Knowledge related data in pickle format.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, path: Union[str, Path]):
|
||||||
|
super().__init__(path)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, path: Union[str, Path]):
|
||||||
|
"""use pickle as the default load method"""
|
||||||
|
path = path if isinstance(path, Path) else Path(path)
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
return pickle.load(f)
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
"""use pickle as the default save method"""
|
||||||
|
Path.mkdir(self.path.parent, exist_ok=True)
|
||||||
|
with open(self.path, "wb") as f:
|
||||||
|
pickle.dump(self, f)
|
||||||
|
|
||||||
|
|
||||||
|
class YamlStorage(Storage):
|
||||||
|
"""
|
||||||
|
This class is responsible for storage and loading of Knowledge related data in yaml format.
|
||||||
|
|
||||||
|
"""
|
||||||
|
DEFAULT_NAME = "storage.yml"
|
||||||
|
|
||||||
|
def __init__(self, path: Union[str, Path]):
|
||||||
|
super().__init__(path)
|
||||||
|
assert self.path.name, "Yaml storage should specify file name."
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""load data from yaml format file"""
|
||||||
|
try:
|
||||||
|
self.documents = yaml.load(open(self.path, "r"), Loader=yaml.FullLoader)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.warning(f"YamlStorage: file {self.path} doesn't exist.")
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
"""use pickle as the default save method"""
|
||||||
|
Path.mkdir(self.path.parent, exist_ok=True)
|
||||||
|
with open(self.path, 'w') as f:
|
||||||
|
yaml.dump(self.documents, f)
|
||||||
|
|
||||||
|
|
||||||
|
class ExperimentStorage(Storage):
|
||||||
|
"""
|
||||||
|
This class is responsible for storage and loading of mlflow related data.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, exp_name, path=None):
|
||||||
|
super().__init__(path=path)
|
||||||
|
self.exp_name = exp_name
|
||||||
|
self.exp = None
|
||||||
|
self.recs = []
|
||||||
|
self.docs = []
|
||||||
|
|
||||||
|
def load(self, exp_name, rec_id=None):
|
||||||
|
recs = []
|
||||||
|
self.exp = R.get_exp(experiment_name=exp_name)
|
||||||
|
for r in self.exp.list_recorders(rtype=self.exp.RT_L):
|
||||||
|
if rec_id is not None and r.id != rec_id:
|
||||||
|
continue
|
||||||
|
recs.append(r)
|
||||||
|
self.recs.extend(recs)
|
||||||
|
|
||||||
|
|
||||||
class Knowledge:
|
class Knowledge:
|
||||||
@@ -12,12 +114,24 @@ class Knowledge:
|
|||||||
Use to handle knowledge in finCo such as experiment and outside domain information
|
Use to handle knowledge in finCo such as experiment and outside domain information
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, storages: Union[List[Storage], Storage], name: str = None):
|
||||||
self.logger = FinCoLog()
|
self.name = name if name else random_string()
|
||||||
|
self.workdir = Path.cwd().joinpath("knowledge")
|
||||||
|
self.storages = [storages] if isinstance(storages, Storage) else storages
|
||||||
|
self.knowledge = []
|
||||||
|
|
||||||
def load(self, **kwargs):
|
def get_storage(self, name: str):
|
||||||
"""
|
"""
|
||||||
Load knowledge in memory
|
return first storage matched given name, else return None
|
||||||
|
"""
|
||||||
|
for storage in self.storages:
|
||||||
|
if storage.name == name:
|
||||||
|
return storage
|
||||||
|
return None
|
||||||
|
|
||||||
|
def summarize(self, **kwargs):
|
||||||
|
"""
|
||||||
|
summarize storage data to knowledge, default knowledge is storage.documents
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -25,7 +139,24 @@ class Knowledge:
|
|||||||
Return
|
Return
|
||||||
------
|
------
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(f"Please implement the `load` method.")
|
for storage in self.storages:
|
||||||
|
self.knowledge.extend(storage.documents)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, path: Union[str, Path]):
|
||||||
|
"""
|
||||||
|
Load knowledge in memory
|
||||||
|
use pickle as the default file type
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
Return
|
||||||
|
------
|
||||||
|
"""
|
||||||
|
""""""
|
||||||
|
path = path if isinstance(path, Path) else Path(path)
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
return pickle.load(f)
|
||||||
|
|
||||||
def brief(self, **kwargs):
|
def brief(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -39,39 +170,171 @@ class Knowledge:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError(f"Please implement the `load` method.")
|
raise NotImplementedError(f"Please implement the `load` method.")
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
"""save knowledge persistently"""
|
||||||
|
# todo: storages save index only
|
||||||
|
Path.mkdir(self.workdir.joinpath(self.name), exist_ok=True)
|
||||||
|
with open(self.workdir.joinpath(self.name).joinpath("knowledge.pkl"), "wb") as f:
|
||||||
|
pickle.dump(self, f)
|
||||||
|
|
||||||
class KnowledgeExperiment(Knowledge):
|
|
||||||
|
class ExperimentKnowledge(Knowledge):
|
||||||
"""
|
"""
|
||||||
Handle knowledge from experiments
|
Handle knowledge from experiments
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, exp_name, rec_id=None):
|
def __init__(self, storages: Union[List[ExperimentStorage], ExperimentStorage]):
|
||||||
super().__init__()
|
super().__init__(storages=storages)
|
||||||
self.exp_name = exp_name
|
self.storage = storages
|
||||||
self.exp = None
|
|
||||||
self.recs = []
|
|
||||||
|
|
||||||
self.load(exp_name=exp_name, rec_id=rec_id)
|
|
||||||
|
|
||||||
def load(self, exp_name, rec_id=None):
|
|
||||||
recs = []
|
|
||||||
self.exp = R.get_exp(experiment_name=exp_name)
|
|
||||||
for r in self.exp.list_recorders(rtype=self.exp.RT_L):
|
|
||||||
if rec_id is not None and r.id != rec_id:
|
|
||||||
continue
|
|
||||||
recs.append(r)
|
|
||||||
self.recs.extend(recs)
|
|
||||||
|
|
||||||
def brief(self):
|
def brief(self):
|
||||||
docs = []
|
docs = []
|
||||||
for recorder in self.recs:
|
for recorder in self.storage.recs:
|
||||||
docs.append({"exp_name": self.exp.name, "record_info": recorder.info,
|
docs.append({"exp_name": self.storage.exp.name, "record_info": recorder.info,
|
||||||
"config": recorder.load_object("config"),
|
"config": recorder.load_object("config"),
|
||||||
"context_summary": recorder.load_object("context_summary")})
|
"context_summary": recorder.load_object("context_summary")})
|
||||||
|
|
||||||
return docs
|
return docs
|
||||||
|
|
||||||
|
|
||||||
|
class PracticeKnowledge(Knowledge):
|
||||||
|
"""
|
||||||
|
some template sentence for now
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, storages: Union[List[YamlStorage], YamlStorage]):
|
||||||
|
super().__init__(storages=storages, name="practice")
|
||||||
|
|
||||||
|
self.summarize()
|
||||||
|
|
||||||
|
def add(self, docs: List):
|
||||||
|
storage = YamlStorage(path=self.workdir.joinpath(self.name).joinpath(YamlStorage.DEFAULT_NAME))
|
||||||
|
storage.add(documents=docs)
|
||||||
|
self.storages.append(storage)
|
||||||
|
self.summarize()
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class FinanceKnowledge(Knowledge):
|
||||||
|
"""
|
||||||
|
Knowledge from articles
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, storages: Union[List[YamlStorage], YamlStorage]):
|
||||||
|
super().__init__(storages=storages, name="finance")
|
||||||
|
|
||||||
|
storage = self.get_storage(YamlStorage.DEFAULT_NAME)
|
||||||
|
if len(storage.documents) == 0:
|
||||||
|
docs = self.read_files_in_directory(self.workdir.joinpath(self.name))
|
||||||
|
self.add(docs)
|
||||||
|
|
||||||
|
def add(self, docs: List):
|
||||||
|
storage = YamlStorage(path=self.workdir.joinpath(self.name).joinpath(YamlStorage.DEFAULT_NAME))
|
||||||
|
storage.add(documents=docs)
|
||||||
|
self.storages.append(storage)
|
||||||
|
self.summarize()
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_files_in_directory(directory):
|
||||||
|
"""
|
||||||
|
read all .txt files under directory
|
||||||
|
"""
|
||||||
|
# todo: split article in trunks
|
||||||
|
file_contents = []
|
||||||
|
for file_path in Path(directory).rglob("*.txt"):
|
||||||
|
if file_path.is_file():
|
||||||
|
file_content = file_path.read_text(encoding="utf-8")
|
||||||
|
file_contents.append(file_content)
|
||||||
|
return file_contents
|
||||||
|
|
||||||
|
|
||||||
|
class ExecuteKnowledge(Knowledge):
|
||||||
|
"""
|
||||||
|
Config and associate execution result(pass or error message). We can regard the example in prompt as pass execution
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, storages: Union[List[YamlStorage], YamlStorage]):
|
||||||
|
super().__init__(storages=storages, name="execute")
|
||||||
|
self.summarize()
|
||||||
|
|
||||||
|
def add(self, docs: List):
|
||||||
|
storage = YamlStorage(path=self.workdir.joinpath(YamlStorage.DEFAULT_NAME))
|
||||||
|
storage.add(documents=docs)
|
||||||
|
self.storages.append(storage)
|
||||||
|
self.summarize()
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class InfrastructureKnowledge(Knowledge):
|
||||||
|
"""
|
||||||
|
Knowledge from sentences, docstring, and code
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, storages: Union[List[YamlStorage], YamlStorage]):
|
||||||
|
super().__init__(storages=storages, name="infrastructure")
|
||||||
|
|
||||||
|
storage = self.get_storage(YamlStorage.DEFAULT_NAME)
|
||||||
|
if len(storage.documents) == 0:
|
||||||
|
docs = self.get_functions_and_docstrings(Path(__file__).parent.parent.parent)
|
||||||
|
self.add(docs)
|
||||||
|
|
||||||
|
def add(self, docs: List):
|
||||||
|
storage = YamlStorage(path=self.workdir.joinpath(self.name).joinpath(YamlStorage.DEFAULT_NAME))
|
||||||
|
storage.add(documents=docs)
|
||||||
|
self.storages.append(storage)
|
||||||
|
self.summarize()
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def get_functions_and_docstrings(self, directory):
|
||||||
|
"""
|
||||||
|
get all method and docstring in .py files under directory
|
||||||
|
|
||||||
|
"""
|
||||||
|
functions = []
|
||||||
|
for py_file_path in Path(directory).rglob('*.py'):
|
||||||
|
for _functions in self.get_functions_with_docstrings(py_file_path):
|
||||||
|
functions.append(_functions)
|
||||||
|
|
||||||
|
return functions
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_functions_with_docstrings(file_path):
|
||||||
|
"""
|
||||||
|
Extract method name and docstring using string matching method
|
||||||
|
"""
|
||||||
|
with open(file_path, "r", encoding="utf8") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
functions = []
|
||||||
|
current_func = None
|
||||||
|
docstring = None
|
||||||
|
for line in lines:
|
||||||
|
if line.strip().startswith("def ") or line.strip().startswith("class "):
|
||||||
|
func = line.strip().split(' ')[1].split('(')[0]
|
||||||
|
if func.startswith("__"):
|
||||||
|
continue
|
||||||
|
if current_func is not None:
|
||||||
|
docstring = docstring.replace('"""', "") if docstring else docstring
|
||||||
|
functions.append({"function": current_func, "docstring": docstring})
|
||||||
|
current_func = f"{file_path.name.split('.')[0]}.{func}"
|
||||||
|
docstring = None
|
||||||
|
elif current_func is not None and docstring is None and line.strip().startswith('"""'):
|
||||||
|
docstring = line
|
||||||
|
elif current_func is not None and docstring is not None:
|
||||||
|
docstring += line.strip()
|
||||||
|
if line.strip().endswith('"""'):
|
||||||
|
docstring = docstring.replace('"""', "") if docstring else docstring
|
||||||
|
functions.append({"function": current_func, "docstring": docstring})
|
||||||
|
current_func = None
|
||||||
|
docstring = None
|
||||||
|
|
||||||
|
return functions
|
||||||
|
|
||||||
|
|
||||||
class Topic:
|
class Topic:
|
||||||
|
|
||||||
def __init__(self, name: str, describe: Template):
|
def __init__(self, name: str, describe: Template):
|
||||||
@@ -97,60 +360,101 @@ class KnowledgeBase:
|
|||||||
Load knowledge, offer brief information of knowledge and common handle interfaces
|
Load knowledge, offer brief information of knowledge and common handle interfaces
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, init_path=None, topics: List[Topic] = None):
|
KT_EXECUTE = "execute"
|
||||||
|
KT_PRACTICE = "practice"
|
||||||
|
KT_FINANCE = "finance"
|
||||||
|
KT_INFRASTRUCTURE = "infrastructure"
|
||||||
|
|
||||||
|
def __init__(self, workdir=None):
|
||||||
self.logger = FinCoLog()
|
self.logger = FinCoLog()
|
||||||
init_path = init_path if init_path else Path.cwd()
|
self.workdir = Path(workdir) if workdir else Path.cwd()
|
||||||
|
|
||||||
if not init_path.exists():
|
if not self.workdir.exists():
|
||||||
self.logger.warning(f"{init_path} not exist, create empty directory.")
|
self.logger.warning(f"{self.workdir} not exist, create empty directory.")
|
||||||
Path.mkdir(init_path)
|
Path.mkdir(self.workdir)
|
||||||
|
|
||||||
self.knowledge = self.load(path=init_path)
|
self.practice_knowledge = self.load_practice_knowledge(self.workdir)
|
||||||
|
self.execute_knowledge = self.load_execute_knowledge(self.workdir)
|
||||||
|
self.finance_knowledge = self.load_finance_knowledge(self.workdir)
|
||||||
|
self.infrastructure_knowledge = self.load_infrastructure_knowledge(self.workdir)
|
||||||
|
|
||||||
# todo: replace list with persistent storage strategy such as ES/pinecone to enable
|
def load_experiment_knowledge(self, path) -> List:
|
||||||
# literal search/semantic search
|
# similar to practice knowledge, not use for now
|
||||||
self.docs = self.brief(knowledge=self.knowledge)
|
|
||||||
|
|
||||||
self.topics = topics if topics else []
|
|
||||||
|
|
||||||
def load(self, path) -> List:
|
|
||||||
if isinstance(path, str):
|
if isinstance(path, str):
|
||||||
path = Path(path)
|
path = Path(path)
|
||||||
|
|
||||||
knowledge = []
|
knowledge = []
|
||||||
path = path if path.name == "mlruns" else path.joinpath("mlruns")
|
path = path if path.name == "mlruns" else path.joinpath("mlruns")
|
||||||
|
# todo: check the influence of set uri
|
||||||
R.set_uri(path.as_uri())
|
R.set_uri(path.as_uri())
|
||||||
for exp_name in R.list_experiments():
|
for exp_name in R.list_experiments():
|
||||||
knowledge.append(KnowledgeExperiment(exp_name=exp_name))
|
knowledge.append(ExperimentKnowledge(storages=ExperimentStorage(exp_name=exp_name)))
|
||||||
|
|
||||||
self.logger.plain_info(f"Load knowledge from: {path} finished.")
|
self.logger.plain_info(f"Load knowledge from: {path} finished.")
|
||||||
return knowledge
|
return knowledge
|
||||||
|
|
||||||
def update(self, path):
|
def load_practice_knowledge(self, path: Path) -> PracticeKnowledge:
|
||||||
# note: only update new knowledge in future
|
self.practice_knowledge = PracticeKnowledge(
|
||||||
knowledge = self.load(path)
|
YamlStorage(path.joinpath(f"{self.KT_PRACTICE}/{YamlStorage.DEFAULT_NAME}")))
|
||||||
self.knowledge = knowledge
|
return self.practice_knowledge
|
||||||
self.docs = self.brief(self.knowledge)
|
|
||||||
self.logger.plain_info(f"Update knowledge finished.")
|
|
||||||
|
|
||||||
def brief(self, knowledge: List[Knowledge]) -> List:
|
def load_execute_knowledge(self, path: Path) -> ExecuteKnowledge:
|
||||||
docs = []
|
self.execute_knowledge = ExecuteKnowledge(
|
||||||
|
YamlStorage(path.joinpath(f"{self.KT_EXECUTE}/{YamlStorage.DEFAULT_NAME}")))
|
||||||
|
return self.execute_knowledge
|
||||||
|
|
||||||
|
def load_finance_knowledge(self, path: Path) -> FinanceKnowledge:
|
||||||
|
self.finance_knowledge = FinanceKnowledge(
|
||||||
|
YamlStorage(path.joinpath(f"{self.KT_FINANCE}/{YamlStorage.DEFAULT_NAME}")))
|
||||||
|
return self.finance_knowledge
|
||||||
|
|
||||||
|
def load_infrastructure_knowledge(self, path: Path) -> InfrastructureKnowledge:
|
||||||
|
self.infrastructure_knowledge = InfrastructureKnowledge(
|
||||||
|
YamlStorage(path.joinpath(f"{self.KT_INFRASTRUCTURE}/{YamlStorage.DEFAULT_NAME}")))
|
||||||
|
return self.infrastructure_knowledge
|
||||||
|
|
||||||
|
def get_knowledge(self, knowledge_type: str = None):
|
||||||
|
if knowledge_type == self.KT_EXECUTE:
|
||||||
|
knowledge = self.execute_knowledge.knowledge
|
||||||
|
elif knowledge_type == self.KT_PRACTICE:
|
||||||
|
knowledge = self.practice_knowledge.knowledge
|
||||||
|
elif knowledge_type == self.KT_FINANCE:
|
||||||
|
knowledge = self.finance_knowledge.knowledge
|
||||||
|
elif knowledge_type == self.KT_INFRASTRUCTURE:
|
||||||
|
knowledge = self.infrastructure_knowledge.knowledge
|
||||||
|
else:
|
||||||
|
knowledge = self.execute_knowledge.knowledge + self.practice_knowledge.knowledge \
|
||||||
|
+ self.finance_knowledge.knowledge + self.infrastructure_knowledge.knowledge
|
||||||
|
return knowledge
|
||||||
|
|
||||||
|
def query(self, knowledge_type: str = None, content: str = None, n: int = 5):
|
||||||
|
"""
|
||||||
|
|
||||||
|
@param knowledge_type: self.KT_EXECUTE, self.KT_PRACTICE or self.KT_FINANCE
|
||||||
|
@param content: content to query KnowledgeBase
|
||||||
|
@param n: top n knowledge to ask ChatGPT
|
||||||
|
@return:
|
||||||
|
"""
|
||||||
|
# todo: replace list with persistent storage strategy such as ES/pinecone to enable
|
||||||
|
# literal search/semantic search
|
||||||
|
|
||||||
|
knowledge = self.get_knowledge(knowledge_type=knowledge_type)
|
||||||
|
if len(knowledge) == 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
scores = []
|
||||||
for k in knowledge:
|
for k in knowledge:
|
||||||
docs.extend(k.brief())
|
scores.append(similarity(str(k), content))
|
||||||
|
sorted_indexes = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
|
||||||
|
similar_n_indexes = sorted_indexes[:n]
|
||||||
|
similar_n_docs = [knowledge[i] for i in similar_n_indexes]
|
||||||
|
|
||||||
self.logger.plain_info(f"Generate brief knowledge summary finished.")
|
prompt = Template(
|
||||||
return 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}""")
|
||||||
|
prompt_workflow_selection = prompt.render(content=content, docs=similar_n_docs)
|
||||||
|
response = APIBackend().build_messages_and_create_chat_completion(
|
||||||
|
user_prompt=prompt_workflow_selection, system_prompt="You are an excellent assistant."
|
||||||
|
)
|
||||||
|
|
||||||
def query(self, content: str = None):
|
return response
|
||||||
# todo: query by DSL
|
|
||||||
return self.docs
|
|
||||||
|
|
||||||
def query_topics(self):
|
|
||||||
knowledge_of_topics = []
|
|
||||||
for topic in self.topics:
|
|
||||||
knowledge_of_topics.append({topic.name: topic.knowledge})
|
|
||||||
return knowledge_of_topics
|
|
||||||
|
|
||||||
def summarize_by_topic(self):
|
|
||||||
for topic in self.topics:
|
|
||||||
topic.summarize(self.docs)
|
|
||||||
|
|||||||
@@ -216,6 +216,12 @@ CMDTask_system : |-
|
|||||||
Example output:
|
Example output:
|
||||||
cp -r a/b/c d/e/f
|
cp -r a/b/c d/e/f
|
||||||
|
|
||||||
|
Example input:
|
||||||
|
- User intention: Copy the folder from a/b/c to d/e/f
|
||||||
|
- User OS: Windows
|
||||||
|
Example output:
|
||||||
|
xcopy /Y /f a/b/c d/e/f
|
||||||
|
|
||||||
CMDTask_user : |-
|
CMDTask_user : |-
|
||||||
Example input:
|
Example input:
|
||||||
- User intention: "{{cmd_intention}}"
|
- User intention: "{{cmd_intention}}"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
import platform
|
||||||
import inspect
|
import inspect
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
from qlib.finco.llm import APIBackend
|
from qlib.finco.llm import APIBackend
|
||||||
from qlib.finco.tpl import get_tpl_path
|
from qlib.finco.tpl import get_tpl_path
|
||||||
@@ -17,6 +18,7 @@ from qlib.contrib.analyzer import HFAnalyzer, SignalAnalyzer
|
|||||||
from qlib.workflow import R
|
from qlib.workflow import R
|
||||||
from qlib.finco.log import FinCoLog, LogColors
|
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
|
||||||
|
|
||||||
COMPONENT_LIST = ["Dataset", "DataHandler", "Model", "Record", "Strategy", "Backtest"]
|
COMPONENT_LIST = ["Dataset", "DataHandler", "Model", "Record", "Strategy", "Backtest"]
|
||||||
|
|
||||||
@@ -176,8 +178,14 @@ class HighLevelPlanTask(PlanTask):
|
|||||||
assert thinking_detail is not None, "The thinking detail is not provided"
|
assert thinking_detail is not None, "The thinking detail is not provided"
|
||||||
assert user_intention is not None, "The user intention is not provided"
|
assert user_intention is not None, "The user intention is not provided"
|
||||||
|
|
||||||
|
practice_knowledge = KnowledgeBase().query(knowledge_type=KnowledgeBase.KT_PRACTICE, 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, algorithm_level=algorithm_level, thinking_detail=thinking_detail, user_intention=user_intention)
|
user_prompt = self.user.render(target=target, deliverable=deliverable, business_level=business_level,
|
||||||
|
algorithm_level=algorithm_level, thinking_detail=thinking_detail,
|
||||||
|
practice_knowledge=practice_knowledge, finance_knowledge=finance_knowledge,
|
||||||
|
user_intention=user_intention)
|
||||||
|
|
||||||
response = APIBackend().build_messages_and_create_chat_completion(
|
response = APIBackend().build_messages_and_create_chat_completion(
|
||||||
user_prompt, system_prompt
|
user_prompt, system_prompt
|
||||||
@@ -229,8 +237,14 @@ 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,
|
||||||
|
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, algorithm_level=algorithm_level, thinking_detail=thinking_detail, user_intention=user_intention, experiments=experiments)
|
user_prompt = self.user.render(target=target, deliverable=deliverable, 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:
|
||||||
@@ -341,11 +355,14 @@ class TrainTask(Task):
|
|||||||
try:
|
try:
|
||||||
# Run the command and capture the output
|
# Run the command and capture the output
|
||||||
workspace = self._context_manager.get_context("workspace")
|
workspace = self._context_manager.get_context("workspace")
|
||||||
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True, cwd=str(workspace))
|
result = 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])
|
||||||
|
|
||||||
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),
|
||||||
@@ -432,11 +449,9 @@ class AnalysisTask(Task):
|
|||||||
else "workflow_config.yaml"
|
else "workflow_config.yaml"
|
||||||
)
|
)
|
||||||
workspace = self._context_manager.get_context("workspace")
|
workspace = self._context_manager.get_context("workspace")
|
||||||
workflow_path = workspace.joinpath(workflow_config)
|
|
||||||
with workflow_path.open() as f:
|
|
||||||
workflow = yaml.safe_load(f)
|
|
||||||
|
|
||||||
experiment_name = workflow["experiment_name"] if "experiment_name" in workflow else "workflow"
|
# todo: analysis multi experiment(get recorder by id)
|
||||||
|
experiment_name = "workflow"
|
||||||
R.set_uri(Path.joinpath(workspace, 'mlruns').as_uri())
|
R.set_uri(Path.joinpath(workspace, 'mlruns').as_uri())
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
@@ -650,11 +665,19 @@ class HyperparameterActionTask(ActionTask):
|
|||||||
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,
|
||||||
|
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,
|
||||||
target_component_plan=target_component_plan,
|
target_component_plan=target_component_plan,
|
||||||
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,
|
||||||
|
infrastructure_knowledge=infrastructure_knowledge
|
||||||
)
|
)
|
||||||
former_messages = []
|
former_messages = []
|
||||||
if self.regenerate:
|
if self.regenerate:
|
||||||
@@ -987,7 +1010,9 @@ class SummarizeTask(Task):
|
|||||||
|
|
||||||
file_info = self.get_info_from_file(workspace)
|
file_info = self.get_info_from_file(workspace)
|
||||||
context_info = self.get_info_from_context() # too long context make response unstable.
|
context_info = self.get_info_from_context() # too long context make response unstable.
|
||||||
record_info = self.get_info_from_recorder(workspace, workflow_yaml["experiment_name"])
|
|
||||||
|
# todo: experiments perhaps have the same name, summarize experiment by loop
|
||||||
|
record_info = self.get_info_from_recorder(workspace, "workflow")
|
||||||
figure_path = self.get_figure_path(workspace)
|
figure_path = self.get_figure_path(workspace)
|
||||||
|
|
||||||
information = context_info + file_info + record_info
|
information = context_info + file_info + record_info
|
||||||
@@ -1012,7 +1037,7 @@ class SummarizeTask(Task):
|
|||||||
)
|
)
|
||||||
context_summary.update({key: response})
|
context_summary.update({key: response})
|
||||||
|
|
||||||
recorder = R.get_recorder(experiment_name=workflow_yaml["experiment_name"])
|
recorder = R.get_recorder(experiment_name="workflow")
|
||||||
recorder.save_objects(context_summary=context_summary)
|
recorder.save_objects(context_summary=context_summary)
|
||||||
|
|
||||||
prompt_workflow_selection = self.summarize_metrics_user.render(
|
prompt_workflow_selection = self.summarize_metrics_user.render(
|
||||||
@@ -1029,6 +1054,14 @@ 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,
|
||||||
|
"experiment_metrics": metrics_response}])
|
||||||
|
|
||||||
|
# notes: summarize after all experiment added to KnowledgeBase
|
||||||
|
topic = Topic(name="rollingModel", describe=Template("What conclusion can you draw"))
|
||||||
|
topic.summarize(KnowledgeBase().practice_knowledge.knowledge)
|
||||||
|
self.logger.info(f"Summary of topic: {topic.name}: {topic.knowledge}")
|
||||||
|
|
||||||
self._context_manager.set_context("summary", response)
|
self._context_manager.set_context("summary", response)
|
||||||
self.save_markdown(content=response, path=workspace)
|
self.save_markdown(content=response, path=workspace)
|
||||||
self.logger.info(f"Report has saved to {self.__DEFAULT_REPORT_NAME}", title="End")
|
self.logger.info(f"Report has saved to {self.__DEFAULT_REPORT_NAME}", title="End")
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
|
|
||||||
@@ -36,3 +38,8 @@ def similarity(text1, text2):
|
|||||||
|
|
||||||
# Maybe we can use other similarity algorithm such as tfidf
|
# Maybe we can use other similarity algorithm such as tfidf
|
||||||
return fuzz.ratio(text1, text2)
|
return fuzz.ratio(text1, text2)
|
||||||
|
|
||||||
|
|
||||||
|
def random_string(length=10):
|
||||||
|
letters = string.ascii_letters + string.digits
|
||||||
|
return ''.join(random.choice(letters) for i in range(length))
|
||||||
|
|||||||
@@ -174,16 +174,16 @@ class LearnManager:
|
|||||||
self.epoch = 0
|
self.epoch = 0
|
||||||
self.wm = WorkflowManager()
|
self.wm = WorkflowManager()
|
||||||
|
|
||||||
topics = [Topic(name=topic, describe=self.wm.prompt_template.get(f"Topic_{topic}")) for topic in
|
self.topics = [Topic(name=topic, describe=self.wm.prompt_template.get(f"Topic_{topic}")) for topic in
|
||||||
self.__DEFAULT_TOPICS]
|
self.__DEFAULT_TOPICS]
|
||||||
self.knowledge_base = KnowledgeBase(init_path=Path.cwd().joinpath('knowledge'), topics=topics)
|
self.knowledge_base = KnowledgeBase(workdir=Path.cwd().joinpath('knowledge'))
|
||||||
|
self.knowledge_base.execute_knowledge.add([])
|
||||||
|
self.knowledge_base.query(knowledge_type="infrastructure", content="resolve_path")
|
||||||
|
|
||||||
def run(self, prompt):
|
def run(self, prompt):
|
||||||
# todo: add early stop condition
|
# todo: add early stop condition
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
self.wm.run(prompt)
|
self.wm.run(prompt)
|
||||||
self.knowledge_base.update(self.wm._workspace)
|
|
||||||
self.knowledge_base.summarize_by_topic()
|
|
||||||
self.learn()
|
self.learn()
|
||||||
self.epoch += 1
|
self.epoch += 1
|
||||||
|
|
||||||
@@ -204,9 +204,12 @@ class LearnManager:
|
|||||||
user_prompt = self.wm.context.get_context("user_prompt")
|
user_prompt = self.wm.context.get_context("user_prompt")
|
||||||
summary = self.wm.context.get_context("summary")
|
summary = self.wm.context.get_context("summary")
|
||||||
|
|
||||||
|
[topic.summarize(self.knowledge_base.get_knowledge()) for topic in self.topics]
|
||||||
|
knowledge_of_topics = [{topic.name: topic.knowledge} for topic in self.topics]
|
||||||
|
|
||||||
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=self.knowledge_base.query_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
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user