Refine all the implementation code to higher quality for release (#29)

* refine CI script

* refine all the code to higher quality

* refine the script to factor extraction and implementation

* add task loader interface

* add a task loader interface && move pdf analysis to pdf task loader

* change the name to global variables

---------

Co-authored-by: xuyang1 <xuyang1@microsoft.com>
This commit is contained in:
Xu Yang
2024-06-18 11:50:03 +08:00
committed by GitHub
parent ebb659a018
commit a126c84c92
30 changed files with 567 additions and 564 deletions
+1 -3
View File
@@ -24,12 +24,10 @@ jobs:
CHAT_AZURE_API_VERSION: ${{ secrets.CHAT_AZURE_API_VERSION }}
CHAT_MAX_TOKENS: ${{ secrets.CHAT_MAX_TOKENS }}
CHAT_MODEL: ${{ secrets.CHAT_MODEL }}
CHAT_OPENAI_API_KEY: ${{ secrets.CHAT_OPENAI_API_KEY }}
CHAT_TEMPERATURE: ${{ secrets.CHAT_TEMPERATURE }}
EMBEDDING_AZURE_API_BASE: ${{ secrets.CHAT_AZURE_API_BASE }}
EMBEDDING_AZURE_API_VERSION: ${{ secrets.CHAT_AZURE_API_VERSION }}
EMBEDDING_MODEL: ${{ secrets.EMBEDDING_MODEL }}
EMBEDDING_OPENAI_API_KEY: ${{ secrets.CHAT_OPENAI_API_KEY }}
name: lint test docs and build
run: make lint test docs build
strategy:
@@ -84,4 +82,4 @@ on:
- synchronize
push:
branches:
- master
- main
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
steps:
- uses: readthedocs/actions/preview@v1
with:
project-slug: fincov2
project-slug: RDAgent
name: Read the Docs Pull Request Preview
on:
pull_request_target:
+7 -7
View File
@@ -19,7 +19,7 @@ CONSTRAINTS_FILE := constraints/$(PYTHON_VERSION).txt
PUBLIC_DIR := $(shell [ "$$READTHEDOCS" = "True" ] && echo "$$READTHEDOCS_OUTPUT/html" || echo "public")
# URL and Path of changelog source code.
CHANGELOG_URL := $(shell echo $${CI_PAGES_URL:-https://peteryang1.github.io/fincov2}/_sources/changelog.md.txt)
CHANGELOG_URL := $(shell echo $${CI_PAGES_URL:-https://microsoft.github.io/rdagent}/_sources/changelog.md.txt)
CHANGELOG_PATH := docs/changelog.md
########################################################################################
@@ -63,8 +63,8 @@ dev-%:
init-qlib-env:
# note: You may need to install torch manually
# todo: downgrade ruamel.yaml in pyqlib
conda create -n qlibFinCo python=3.8 -y
@source $$(conda info --base)/etc/profile.d/conda.sh && conda activate qlibFinCo && which pip && pip install pyqlib && pip install ruamel-yaml==0.17.21 && pip install torch==2.1.1 && pip install catboost==0.24.3 && conda deactivate
conda create -n qlibRDAgent python=3.8 -y
@source $$(conda info --base)/etc/profile.d/conda.sh && conda activate qlibRDAgent && which pip && pip install pyqlib && pip install ruamel-yaml==0.17.21 && pip install torch==2.1.1 && pip install catboost==0.24.3 && conda deactivate
dev:
$(PIPRUN) pip install -e .[docs,lint,package,test] -c $(CONSTRAINTS_FILE)
@@ -89,11 +89,11 @@ isort:
# Check lint with mypy.
mypy:
$(PIPRUN) python -m mypy . --exclude FinCo --exclude finco --exclude rdagent/scripts --exclude test/scripts --exclude git_ignore_folder
$(PIPRUN) python -m mypy . --exclude rdagent/scripts --exclude git_ignore_folder
# Check lint with ruff.
ruff:
$(PIPRUN) ruff check . --exclude FinCo,finco,rdagent/scripts,test/scripts,git_ignore_folder
$(PIPRUN) ruff check . --exclude rdagent/scripts,git_ignore_folder
# Check lint with toml-sort.
toml-sort:
@@ -113,7 +113,7 @@ pre-commit:
# Clean and run test with coverage.
test-run:
$(PIPRUN) python -m coverage erase
$(PIPRUN) python -m coverage run --concurrency=multiprocessing -m pytest --ignore FinCo --ignore test/finco --ignore test/scripts
$(PIPRUN) python -m coverage run --concurrency=multiprocessing -m pytest --ignore test/scripts
$(PIPRUN) python -m coverage combine
# Generate coverage report for terminal and xml.
@@ -162,7 +162,7 @@ docs-gen:
# Generate mypy reports.
docs-mypy: docs-gen
$(PIPRUN) python -m mypy rdagent test --exclude FinCo --exclude git_ignore_folder --exclude finco --exclude rdagent/scripts --exclude test/scripts --html-report $(PUBLIC_DIR)/reports/mypy
$(PIPRUN) python -m mypy rdagent test --exclude git_ignore_folder --exclude rdagent/scripts --html-report $(PUBLIC_DIR)/reports/mypy
# Generate html coverage reports with badge.
docs-coverage: test-run docs-gen
+109 -69
View File
@@ -37,6 +37,7 @@ from tree_sitter import Language, Node, Parser
py_parser = Parser(Language(tree_sitter_python.language()))
CI_prompts = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
@dataclass
class CIError:
raw_str: str
@@ -80,8 +81,7 @@ class FixRecord:
"directly_fixed_errors": [error.to_dict() for error in self.directly_fixed_errors],
"manually_fixed_errors": [error.to_dict() for error in self.manually_fixed_errors],
"manual_instructions": {
key: [error.to_dict() for error in errors]
for key, errors in self.manual_instructions.items()
key: [error.to_dict() for error in errors] for key, errors in self.manual_instructions.items()
},
}
@@ -102,7 +102,6 @@ class CodeFile:
return code_with_lineno if isinstance(code, list) else "\n".join(code_with_lineno)
@classmethod
def remove_line_number(cls: CodeFile, code: list[str] | str) -> list[str] | str:
code_lines = code.split("\n") if isinstance(code, str) else code
@@ -124,8 +123,12 @@ class CodeFile:
self.code_lines_with_lineno = self.add_line_number(self.code_lines)
def get(
self, start: int = 1, end: int | None = None, *,
add_line_number: bool = False, return_list: bool = False,
self,
start: int = 1,
end: int | None = None,
*,
add_line_number: bool = False,
return_list: bool = False,
) -> list[str] | str:
"""
Retrieves a portion of the code lines.
@@ -183,7 +186,7 @@ class CodeFile:
return [(node.start_point.row, node.end_point.row + 1)]
blocks: list[tuple[int, int]] = []
block: tuple[int, int] | None = None # [start, end), line number starts from 0
block: tuple[int, int] | None = None # [start, end), line number starts from 0
for child in node.children:
if child.end_point.row + 1 - child.start_point.row > max_lines:
@@ -205,7 +208,7 @@ class CodeFile:
return blocks
# change line number to start from 1 and [start, end) to [start, end]
return [(a+1,b) for a,b in get_blocks_in_node(tree.root_node, max_lines)]
return [(a + 1, b) for a, b in get_blocks_in_node(tree.root_node, max_lines)]
def __str__(self) -> str:
return f"{self.path}"
@@ -221,7 +224,7 @@ class Repo(EvolvableSubjects):
excludes = [self.project_path / path for path in excludes]
git_ignored_output = subprocess.check_output(
["/usr/bin/git", "status", "--ignored", "-s"], # noqa: S603
["/usr/bin/git", "status", "--ignored", "-s"], # noqa: S603
cwd=str(self.project_path),
stderr=subprocess.STDOUT,
text=True,
@@ -264,6 +267,7 @@ class RuffRule:
"preview": false
}
"""
name: str
code: str
linter: str
@@ -290,7 +294,7 @@ class RuffEvaluator(Evaluator):
explain_command = f"ruff rule {error_code} --output-format json"
try:
out = subprocess.check_output(
shlex.split(explain_command), # noqa: S603
shlex.split(explain_command), # noqa: S603
stderr=subprocess.STDOUT,
text=True,
)
@@ -299,12 +303,11 @@ class RuffEvaluator(Evaluator):
return RuffRule(**json.loads(out))
def evaluate(self, evo: Repo, **kwargs: Any) -> CIFeedback: # noqa: ARG002
def evaluate(self, evo: Repo, **kwargs: Any) -> CIFeedback: # noqa: ARG002
"""Simply run ruff to get the feedbacks."""
try:
out = subprocess.check_output(
shlex.split(self.command), # noqa: S603
shlex.split(self.command), # noqa: S603
cwd=evo.project_path,
stderr=subprocess.STDOUT,
text=True,
@@ -313,7 +316,7 @@ class RuffEvaluator(Evaluator):
out = e.output
"""ruff output format:
src/finco/cli.py:9:5: ANN201 Missing return type annotation for public function `main`
rdagent/cli.py:9:5: ANN201 Missing return type annotation for public function `main`
|
9 | def main(prompt=None):
| ^^^^ ANN201
@@ -335,19 +338,22 @@ class RuffEvaluator(Evaluator):
# TODO @bowen: filter these files when running the check command
if evo.project_path / Path(file_path) not in evo.files:
continue
error = CIError(raw_str=raw_str,
file_path=file_path,
line=int(line_number),
column=int(column_number),
code=error_code,
msg=error_message,
hint=error_hint,
checker="ruff")
error = CIError(
raw_str=raw_str,
file_path=file_path,
line=int(line_number),
column=int(column_number),
code=error_code,
msg=error_message,
hint=error_hint,
checker="ruff",
)
errors[file_path].append(error)
return CIFeedback(errors=errors)
class MypyEvaluator(Evaluator):
def __init__(self, command: str | None = None) -> None:
@@ -356,10 +362,10 @@ class MypyEvaluator(Evaluator):
else:
self.command = command
def evaluate(self, evo: Repo, **kwargs: Any) -> CIFeedback: # noqa: ARG002
def evaluate(self, evo: Repo, **kwargs: Any) -> CIFeedback: # noqa: ARG002
try:
out = subprocess.check_output(
shlex.split(self.command), # noqa: S603
shlex.split(self.command), # noqa: S603
cwd=evo.project_path,
stderr=subprocess.STDOUT,
text=True,
@@ -377,7 +383,10 @@ class MypyEvaluator(Evaluator):
error_message = error_message.strip().replace("\n", " ")
if re.match(r".*[^\n]*?:\d+:\d+: note:.*", error_hint, flags=re.DOTALL) is not None:
error_hint_position = re.split(
pattern=r"[^\n]*?:\d+:\d+: note:", string=error_hint, maxsplit=1, flags=re.DOTALL,
pattern=r"[^\n]*?:\d+:\d+: note:",
string=error_hint,
maxsplit=1,
flags=re.DOTALL,
)[0]
error_hint_help = re.findall(r"^.*?:\d+:\d+: note: (.*)$", error_hint, flags=re.MULTILINE)
error_hint_help = "\n".join(error_hint_help)
@@ -385,14 +394,16 @@ class MypyEvaluator(Evaluator):
if evo.project_path / Path(file_path) not in evo.files:
continue
error = CIError(raw_str=raw_str,
file_path=file_path,
line=int(line_number),
column=int(column_number),
code=error_code,
msg=error_message,
hint=error_hint,
checker="mypy")
error = CIError(
raw_str=raw_str,
file_path=file_path,
line=int(line_number),
column=int(column_number),
code=error_code,
msg=error_message,
hint=error_hint,
checker="mypy",
)
errors[file_path].append(error)
@@ -418,13 +429,14 @@ class MultiEvaluator(Evaluator):
return CIFeedback(errors=all_errors)
class CIEvoStr(EvolvingStrategy):
def evolve( # noqa: C901, PLR0912, PLR0915
def evolve( # noqa: C901, PLR0912, PLR0915
self,
evo: Repo,
evolving_trace: list[EvoStep] | None = None,
knowledge_l: list[Knowledge] | None = None, # noqa: ARG002
**kwargs: Any, # noqa: ARG002
knowledge_l: list[Knowledge] | None = None, # noqa: ARG002
**kwargs: Any, # noqa: ARG002
) -> Repo:
@dataclass
@@ -443,13 +455,12 @@ class CIEvoStr(EvolvingStrategy):
# print statistics
checker_error_counts = {
checker: sum(c_statistics.values())
for checker, c_statistics in last_feedback.statistics().items()
checker: sum(c_statistics.values()) for checker, c_statistics in last_feedback.statistics().items()
}
print(
f"Found [red]{sum(checker_error_counts.values())}[/red] errors, "
"including: " +
", ".join(
"including: "
+ ", ".join(
f"[red]{count}[/red] [magenta]{checker}[/magenta] errors"
for checker, count in checker_error_counts.items()
),
@@ -496,7 +507,6 @@ class CIEvoStr(EvolvingStrategy):
CodeFixGroup(start_line, end_line, group_errors, session_id, []),
)
# Fix errors in each code block
with Progress(SpinnerColumn(), *Progress.get_default_columns(), TimeElapsedColumn()) as progress:
group_counts = sum([len(groups) for groups in fix_groups.values()])
@@ -509,7 +519,10 @@ class CIEvoStr(EvolvingStrategy):
end_line = code_fix_g.end_line
group_errors = code_fix_g.errors
code_snippet_with_lineno = file.get(
start_line, end_line, add_line_number=True, return_list=False,
start_line,
end_line,
add_line_number=True,
return_list=False,
)
errors_str = "\n\n".join(str(e) for e in group_errors)
@@ -531,11 +544,16 @@ class CIEvoStr(EvolvingStrategy):
advance=1,
)
# Manual inspection and repair
for file_path in last_feedback.errors:
print(Rule(f"[bright_blue]Checking[/bright_blue] [cyan]{file_path}[/cyan]",
style="bright_blue", align="left", characters="."))
print(
Rule(
f"[bright_blue]Checking[/bright_blue] [cyan]{file_path}[/cyan]",
style="bright_blue",
align="left",
characters=".",
)
)
file = evo.files[evo.project_path / Path(file_path)]
@@ -546,17 +564,20 @@ class CIEvoStr(EvolvingStrategy):
print(f"[yellow]Checking part {group_id}...[/yellow]")
front_context = file.get(start_line-3, start_line-1)
rear_context = file.get(end_line+1, end_line+3)
front_context_with_lineno = file.get(start_line-3, start_line-1, add_line_number=True)
rear_context_with_lineno = file.get(end_line+1, end_line+3, add_line_number=True)
front_context = file.get(start_line - 3, start_line - 1)
rear_context = file.get(end_line + 1, end_line + 3)
front_context_with_lineno = file.get(start_line - 3, start_line - 1, add_line_number=True)
rear_context_with_lineno = file.get(end_line + 1, end_line + 3, add_line_number=True)
code_snippet_with_lineno = file.get(start_line, end_line, add_line_number=True, return_list=False)
# print errors
printed_errors_str = "\n".join(
[f"[{error.checker}] {error.line: >{file.lineno_width}}:{error.column: <4}"
f" {error.code} {error.msg}" for error in group_errors],
[
f"[{error.checker}] {error.line: >{file.lineno_width}}:{error.column: <4}"
f" {error.code} {error.msg}"
for error in group_errors
],
)
print(
Panel.fit(
@@ -611,27 +632,34 @@ class CIEvoStr(EvolvingStrategy):
diff_new_lineno = start_line
for i in diff:
if i.startswith("+"):
table.add_row("", Text(str(diff_new_lineno), style="green bold"),
Text(i, style="green"))
table.add_row(
"", Text(str(diff_new_lineno), style="green bold"), Text(i, style="green")
)
diff_new_lineno += 1
elif i.startswith("-"):
table.add_row(Text(str(diff_original_lineno), style="red bold"), "",
Text(i, style="red"))
table.add_row(
Text(str(diff_original_lineno), style="red bold"), "", Text(i, style="red")
)
diff_original_lineno += 1
elif i.startswith("?"):
table.add_row("", "", Text(i, style="yellow"))
else:
table.add_row(str(diff_original_lineno), str(diff_new_lineno),
Syntax(i, lexer="python", background_color="default"))
table.add_row(
str(diff_original_lineno),
str(diff_new_lineno),
Syntax(i, lexer="python", background_color="default"),
)
diff_original_lineno += 1
diff_new_lineno += 1
table.add_row("", "", Rule(style="dark_orange"))
table.add_row("", "", Syntax(rear_context, lexer="python", background_color="default"))
print(Panel.fit(table, title="Repair Status"))
operation = Prompt.ask("Input your operation [ [red]([bold]s[/bold])kip[/red] / "
"[green]([bold]a[/bold])pply[/green] / "
"[yellow]manual instruction[/yellow] ]")
operation = Prompt.ask(
"Input your operation [ [red]([bold]s[/bold])kip[/red] / "
"[green]([bold]a[/bold])pply[/green] / "
"[yellow]manual instruction[/yellow] ]"
)
print()
if operation in ("s", "skip"):
fix_records[file_path].skipped_errors.extend(group_errors)
@@ -692,7 +720,7 @@ while True:
fix_records = repo.fix_records
filename = f"{DIR.name}_{start_timestamp}_round_{len(ea.evolving_trace)}_fix_records.json"
with Path(filename).open("w") as file:
json.dump({k:v.to_dict() for k,v in fix_records.items()}, file, indent=4)
json.dump({k: v.to_dict() for k, v in fix_records.items()}, file, indent=4)
# Count the number of skipped errors
skipped_errors_count = 0
@@ -735,15 +763,27 @@ while True:
total_errors_count = skipped_errors_count + directly_fixed_errors_count + manually_fixed_errors_count
table.add_row("Total Errors", "", Text(str(total_errors_count), style="cyan"), "")
table.add_row(Text("Skipped Errors", style="red"), skipped_errors_statistics,
Text(str(skipped_errors_count), style="red"),
Text(f"{skipped_errors_count / total_errors_count:.2%}"), style="red")
table.add_row(Text("Directly Fixed Errors", style="green"), directly_fixed_errors_statistics,
Text(str(directly_fixed_errors_count), style="green"),
Text(f"{directly_fixed_errors_count / total_errors_count:.2%}"), style="green")
table.add_row(Text("Manually Fixed Errors", style="yellow"), manually_fixed_errors_statistics,
Text(str(manually_fixed_errors_count), style="yellow"),
Text(f"{manually_fixed_errors_count / total_errors_count:.2%}"), style="yellow")
table.add_row(
Text("Skipped Errors", style="red"),
skipped_errors_statistics,
Text(str(skipped_errors_count), style="red"),
Text(f"{skipped_errors_count / total_errors_count:.2%}"),
style="red",
)
table.add_row(
Text("Directly Fixed Errors", style="green"),
directly_fixed_errors_statistics,
Text(str(directly_fixed_errors_count), style="green"),
Text(f"{directly_fixed_errors_count / total_errors_count:.2%}"),
style="green",
)
table.add_row(
Text("Manually Fixed Errors", style="yellow"),
manually_fixed_errors_statistics,
Text(str(manually_fixed_errors_count), style="yellow"),
Text(f"{manually_fixed_errors_count / total_errors_count:.2%}"),
style="yellow",
)
print(table)
operation = Prompt.ask("Start next round? (y/n)", choices=["y", "n"])
@@ -1,43 +1,16 @@
# %%
from pathlib import Path
from dotenv import load_dotenv
from rdagent.document_process.document_analysis import (
check_factor_viability,
classify_report_from_dict,
deduplicate_factors_by_llm,
extract_factors_from_report_dict,
merge_file_to_factor_dict_to_factor_dict,
classify_report_from_dict,
)
from rdagent.document_process.document_reader import load_and_process_pdfs_by_langchain
from rdagent.factor_implementation.share_modules.factor_implementation_utils import load_data_from_dict
from rdagent.factor_implementation.CoSTEER import CoSTEERFG
import pickle
from dotenv import load_dotenv
from rdagent.factor_implementation.task_loader.pdf_loader import FactorImplementationTaskLoaderFromPDFfiles
assert load_dotenv()
def extract_factors_and_implement(report_file_path: str) -> None:
assert load_dotenv()
docs_dict = load_and_process_pdfs_by_langchain(Path(report_file_path))
selected_report_dict = classify_report_from_dict(report_dict=docs_dict, vote_time=1)
file_to_factor_result = extract_factors_from_report_dict(docs_dict, selected_report_dict)
factor_dict = merge_file_to_factor_dict_to_factor_dict(file_to_factor_result)
factor_viability = check_factor_viability(factor_dict)
factor_dict, duplication_names_list = deduplicate_factors_by_llm(factor_dict, factor_viability)
factor_tasks = load_data_from_dict(factor_dict)
factor_generate_method = CoSTEERFG()
result = factor_generate_method.generate(factor_tasks)
return result
factor_tasks = FactorImplementationTaskLoaderFromPDFfiles().load(report_file_path)
implementation_result = CoSTEERFG().generate(factor_tasks)
return implementation_result
if __name__ == "__main__":
extract_factors_and_implement("/home/xuyang1/workspace/report.pdf")
# test_implement()
+17 -12
View File
@@ -3,7 +3,7 @@ from typing import List, Tuple, Union
from tqdm import tqdm
from collections import defaultdict
from rdagent.core.conf import RDAgentSettings
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.core.exception import ImplementRunException
from rdagent.core.task import (
TaskImplementation,
@@ -23,10 +23,12 @@ from rdagent.core.implementation import TaskGenerator
from rdagent.core.utils import multiprocessing_wrapper
from rdagent.factor_implementation.evolving.factor import FileBasedFactorImplementation
class BaseEval:
"""
The benchmark benchmark evaluation.
"""
def __init__(
self,
evaluator_l: List[FactorImplementationEvaluator],
@@ -95,7 +97,8 @@ class BaseEval:
else:
raise e
return eval_res
class FactorImplementEval(BaseEval):
def __init__(
self,
@@ -106,15 +109,17 @@ class FactorImplementEval(BaseEval):
**kwargs,
):
# evaluator collection for online evaluation
online_evaluator_l = [
FactorImplementationCorrelationEvaluator,
FactorImplementationIndexEvaluator,
FactorImplementationIndexFormatEvaluator,
FactorImplementationMissingValuesEvaluator,
FactorImplementationRowCountEvaluator,
FactorImplementationSingleColumnEvaluator,
FactorImplementationValuesEvaluator,
],
online_evaluator_l = (
[
FactorImplementationCorrelationEvaluator,
FactorImplementationIndexEvaluator,
FactorImplementationIndexFormatEvaluator,
FactorImplementationMissingValuesEvaluator,
FactorImplementationRowCountEvaluator,
FactorImplementationSingleColumnEvaluator,
FactorImplementationValuesEvaluator,
],
)
super().__init__(online_evaluator_l, test_case, method, *args, **kwargs)
self.test_round = test_round
@@ -148,7 +153,7 @@ class FactorImplementEval(BaseEval):
(self.eval_case, (gt_case.ground_truth, gen_factor))
for gt_case, gen_factor in zip(test_cases_all_rounds, gen_factor_l_all_rounds)
],
n=RDAgentSettings().evo_multi_proc_n,
n=RD_AGENT_SETTINGS.evo_multi_proc_n,
)
for gt_case, eval_res, gen_factor in tqdm(zip(test_cases_all_rounds, eval_res_list, gen_factor_l_all_rounds)):
+5 -9
View File
@@ -19,17 +19,13 @@ class RDAgentSettings(BaseSettings):
use_azure_token_provider: bool = False
max_retry: int = 10
retry_wait_seconds: int = 1
continuous_mode: bool = False
dump_chat_cache: bool = False
use_chat_cache: bool = False
dump_embedding_cache: bool = False
use_embedding_cache: bool = False
workspace: str = "./finco_workspace"
prompt_cache_path: str = str(Path.cwd() / "prompt_cache.db")
session_cache_folder_location: str = str(Path.cwd() / "session_cache_folder/")
max_past_message_include: int = 10
use_vector_only: bool = False
log_llm_chat_content: bool = True
# Chat configs
@@ -43,11 +39,9 @@ class RDAgentSettings(BaseSettings):
chat_seed: int | None = None
chat_frequency_penalty: float = 0.0
chat_presence_penalty: float = 0.0
chat_token_limit: int = (
100000 # 100000 is the maximum limit of gpt4, which might increase in the future version of gpt
)
default_system_prompt: str = "You are an AI assistant who helps to answer user's questions about finance."
# Embedding configs
@@ -56,17 +50,17 @@ class RDAgentSettings(BaseSettings):
embedding_azure_api_version: str = ""
embedding_model: str = ""
# llama2 related config
# offline llama2 related config
use_llama2: bool = False
llama2_ckpt_dir: str = "Llama-2-7b-chat"
llama2_tokenizer_path: str = "Llama-2-7b-chat/tokenizer.model"
llams2_max_batch_size: int = 8
# finco v2 configs
# azure document intelligence configs
azure_document_intelligence_key: str = ""
azure_document_intelligence_endpoint: str = ""
# fincov2 llama2 endpoint
# server served endpoints
use_gcr_endpoint: bool = False
gcr_endpoint_type: str = "llama2_70b" # or "llama3_70b", "phi2", "phi3_4k", "phi3_128k"
@@ -99,3 +93,5 @@ class RDAgentSettings(BaseSettings):
max_input_duplicate_factor_group: int = 600
max_output_duplicate_factor_group: int = 20
RD_AGENT_SETTINGS = RDAgentSettings()
+63
View File
@@ -0,0 +1,63 @@
from rdagent.core.evaluation import Evaluator
from rdagent.core.evolving_framework import Feedback, EvolvableSubjects, EvoStep
from tqdm import tqdm
from abc import ABC, abstractmethod
from typing import Any
class EvoAgent(ABC):
def __init__(self, max_loop, evolving_strategy) -> None:
self.max_loop = max_loop
self.evolving_strategy = evolving_strategy
@abstractmethod
def multistep_evolve(self, evo: EvolvableSubjects, eva: Evaluator | Feedback, **kwargs: Any) -> EvolvableSubjects:
pass
class RAGEvoAgent(EvoAgent):
def __init__(self, max_loop, evolving_strategy, rag) -> None:
super().__init__(max_loop, evolving_strategy)
self.rag = rag
self.evolving_trace = []
def multistep_evolve(
self,
evo: EvolvableSubjects,
eva: Evaluator | Feedback,
*,
with_knowledge: bool = False,
with_feedback: bool = True,
knowledge_self_gen: bool = False,
) -> EvolvableSubjects:
for _ in tqdm(range(self.max_loop), "Implementing factors"):
# 1. knowledge self-evolving
if knowledge_self_gen and self.rag is not None:
self.rag.generate_knowledge(self.evolving_trace)
# 2. RAG
queried_knowledge = None
if with_knowledge and self.rag is not None:
# TODO: Putting the evolving trace in here doesn't actually work
queried_knowledge = self.rag.query(evo, self.evolving_trace)
# 3. evolve
evo = self.evolving_strategy.evolve(
evo=evo,
evolving_trace=self.evolving_trace,
queried_knowledge=queried_knowledge,
)
# 4. Pack evolve results
es = EvoStep(evo, queried_knowledge)
# 5. Evaluation
if with_feedback:
es.feedback = (
eva if isinstance(eva, Feedback) else eva.evaluate(evo, queried_knowledge=queried_knowledge)
)
# 6. update trace
self.evolving_trace.append(es)
return evo
+1 -31
View File
@@ -33,27 +33,7 @@ class EvolvableSubjects:
return copy.deepcopy(self)
class QlibEvolvableSubjects(EvolvableSubjects):
...
class Evaluator(ABC):
"""Both external EvolvableSubjects and internal evovler, it is
FAQ:
- Q: If we have a external whitebox evaluator, do we need a
intenral EvolvableSubjects?
A: When the external evovler is very complex, maybe a internal LLM-based evovler
may provide more understandable feedbacks.
"""
@abstractmethod
def evaluate(self, evo: EvolvableSubjects, **kwargs: Any) -> Feedback:
raise NotImplementedError
class SelfEvaluator(Evaluator):
pass
class QlibEvolvableSubjects(EvolvableSubjects): ...
@dataclass
@@ -91,16 +71,6 @@ class EvolvingStrategy(ABC):
"""
class EvoAgent(ABC):
def __init__(self, max_loop, evolving_strategy) -> None:
self.max_loop = max_loop
self.evolving_strategy = evolving_strategy
@abstractmethod
def multistep_evolve(self, evo: EvolvableSubjects, eva: Evaluator | Feedback, **kwargs: Any) -> EvolvableSubjects:
pass
class RAGStrategy(ABC):
"""Retrival Augmentation Generation Strategy"""
+1 -1
View File
@@ -72,7 +72,7 @@ class LogColors:
return f"{style}{text}{self.END}"
class FinCoLog:
class RDAgentLog:
# logger.add(loguru_handler, level="INFO") # you can add use storage as a loguru handler
def __init__(self) -> None:
+11 -2
View File
@@ -2,13 +2,16 @@ from abc import ABC, abstractmethod
from typing import Tuple
import pandas as pd
'''
"""
This file contains the all the data class for rdagent task.
'''
"""
class BaseTask(ABC):
# 把name放在这里作为主键
pass
class TaskImplementation(ABC):
def __init__(self, target_task: BaseTask) -> None:
self.target_task = target_task
@@ -17,6 +20,7 @@ class TaskImplementation(ABC):
def execute(self, *args, **kwargs) -> Tuple[str, pd.DataFrame]:
raise NotImplementedError("__call__ method is not implemented.")
class TestCase:
def __init__(
self,
@@ -26,3 +30,8 @@ class TestCase:
self.ground_truth = ground_truth
self.target_task = target_task
class TaskLoader:
@abstractmethod
def load(self, *args, **kwargs) -> BaseTask | list[BaseTask]:
raise NotImplementedError("load method is not implemented.")
+2 -70
View File
@@ -14,7 +14,7 @@ import yaml
from fuzzywuzzy import fuzz
class RDAgentException(Exception): # noqa: N818
class RDAgentException(Exception): # noqa: N818
pass
@@ -31,6 +31,7 @@ class SingletonMeta(type):
cls._instance_dict[kwargs_hash] = super().__call__(**kwargs)
return cls._instance_dict[kwargs_hash]
class SingletonBaseClass(metaclass=SingletonMeta):
"""
Because we try to support defining Singleton with `class A(SingletonBaseClass)`
@@ -57,67 +58,6 @@ def similarity(text1: str, text2: str) -> int:
return fuzz.ratio(text1, text2)
def random_string(length: int = 10) -> str:
letters = string.ascii_letters + string.digits
return "".join(random.SystemRandom().choice(letters) for _ in range(length))
def remove_uncommon_keys(new_dict: dict, org_dict: dict) -> None:
keys_to_remove = []
for key in new_dict:
if key not in org_dict:
keys_to_remove.append(key)
elif isinstance(new_dict[key], dict) and isinstance(org_dict[key], dict):
remove_uncommon_keys(new_dict[key], org_dict[key])
elif isinstance(new_dict[key], dict) and isinstance(org_dict[key], str):
new_dict[key] = org_dict[key]
for key in keys_to_remove:
del new_dict[key]
def crawl_the_folder(folder_path: Path) -> list:
yaml_files = []
for root, _, files in os.walk(folder_path.as_posix()):
for file in files:
if file.endswith((".yaml", ".yml")):
yaml_file_path = Path(root) / file
yaml_files.append(str(yaml_file_path.relative_to(folder_path)))
return sorted(yaml_files)
def compare_yaml(file1: Path | str, file2: Path | str) -> bool:
with Path(file1).open() as stream:
data1 = yaml.safe_load(stream)
with Path(file2).open() as stream:
data2 = yaml.safe_load(stream)
return data1 == data2
def remove_keys(valid_keys: set[Any], ori_dict: dict[Any, Any]) -> dict[Any, Any]:
for key in list(ori_dict.keys()):
if key not in valid_keys:
ori_dict.pop(key)
return ori_dict
class YamlConfigCache(SingletonBaseClass):
def __init__(self) -> None:
super().__init__()
self.path_to_config = {}
def load(self, path: str) -> None:
with Path(path).open() as stream:
data = yaml.safe_load(stream)
self.path_to_config[path] = data
def __getitem__(self, path: str) -> Any:
if path not in self.path_to_config:
self.load(path)
return self.path_to_config[path]
def import_class(class_path: str) -> Any:
"""
Parameters
@@ -156,11 +96,3 @@ def multiprocessing_wrapper(func_calls: list[tuple[Callable, tuple]], n: int) ->
with mp.Pool(processes=n) as pool:
results = [pool.apply_async(f, args) for f, args in func_calls]
return [result.get() for result in results]
# You can test the above function
# def f(x):
# return x**2
#
# if __name__ == "__main__":
# print(multiprocessing_wrapper([(f, (i,)) for i in range(10)], 4))
@@ -9,7 +9,7 @@ from langchain.document_loaders import PyPDFDirectoryLoader, PyPDFLoader
if TYPE_CHECKING:
from langchain_core.documents import Document
from rdagent.core.conf import RDAgentSettings as Config
from rdagent.core.conf import RD_AGENT_SETTINGS
def load_documents_by_langchain(path: Path) -> list:
@@ -74,10 +74,8 @@ def load_and_process_one_pdf_by_azure_document_intelligence(
def load_and_process_pdfs_by_azure_document_intelligence(path: Path) -> dict[str, str]:
config = Config()
assert config.azure_document_intelligence_key is not None
assert config.azure_document_intelligence_endpoint is not None
assert RD_AGENT_SETTINGS.azure_document_intelligence_key is not None
assert RD_AGENT_SETTINGS.azure_document_intelligence_endpoint is not None
content_dict = {}
ab_path = path.resolve()
@@ -86,15 +84,15 @@ def load_and_process_pdfs_by_azure_document_intelligence(path: Path) -> dict[str
proc = load_and_process_one_pdf_by_azure_document_intelligence
content_dict[str(ab_path)] = proc(
ab_path,
config.azure_document_intelligence_key,
config.azure_document_intelligence_endpoint,
RD_AGENT_SETTINGS.azure_document_intelligence_key,
RD_AGENT_SETTINGS.azure_document_intelligence_endpoint,
)
else:
for file_path in ab_path.rglob("*"):
if file_path.is_file() and ".pdf" in file_path.suffixes:
content_dict[str(file_path)] = load_and_process_one_pdf_by_azure_document_intelligence(
file_path,
config.azure_document_intelligence_key,
config.azure_document_intelligence_endpoint,
RD_AGENT_SETTINGS.azure_document_intelligence_key,
RD_AGENT_SETTINGS.azure_document_intelligence_endpoint,
)
return content_dict
+28 -15
View File
@@ -5,13 +5,20 @@ from rdagent.core.implementation import TaskGenerator
from rdagent.core.task import TaskImplementation
from rdagent.factor_implementation.evolving.knowledge_management import FactorImplementationKnowledgeBaseV1
from rdagent.factor_implementation.evolving.factor import FactorImplementTask, FactorEvovlingItem
from rdagent.knowledge_management.knowledgebase import FactorImplementationGraphKnowledgeBase, FactorImplementationGraphRAGStrategy
from rdagent.factor_implementation.evolving.evolving_strategy import FactorEvolvingStrategyWithGraph
from rdagent.factor_implementation.evolving.evaluators import FactorImplementationsMultiEvaluator, FactorImplementationEvaluatorV1
from rdagent.factor_implementation.evolving.evolving_agent import RAGEvoAgent
from rdagent.factor_implementation.share_modules.factor_implementation_config import (
FactorImplementSettings,
from rdagent.factor_implementation.evolving.knowledge_management import (
FactorImplementationGraphKnowledgeBase,
FactorImplementationGraphRAGStrategy,
)
from rdagent.factor_implementation.evolving.evolving_strategy import FactorEvolvingStrategyWithGraph
from rdagent.factor_implementation.evolving.evaluators import (
FactorImplementationsMultiEvaluator,
FactorImplementationEvaluatorV1,
)
from rdagent.core.evolving_agent import RAGEvoAgent
from rdagent.factor_implementation.share_modules.factor_implementation_config import (
FACTOR_IMPLEMENT_SETTINGS,
)
class CoSTEERFG(TaskGenerator):
def __init__(
@@ -20,9 +27,17 @@ class CoSTEERFG(TaskGenerator):
with_feedback: bool = True,
knowledge_self_gen: bool = True,
) -> None:
self.max_loop = FactorImplementSettings().max_loop
self.knowledge_base_path = Path(FactorImplementSettings().knowledge_base_path) if FactorImplementSettings().knowledge_base_path is not None else None
self.new_knowledge_base_path = Path(FactorImplementSettings().new_knowledge_base_path) if FactorImplementSettings().new_knowledge_base_path is not None else None
self.max_loop = FACTOR_IMPLEMENT_SETTINGS.max_loop
self.knowledge_base_path = (
Path(FACTOR_IMPLEMENT_SETTINGS.knowledge_base_path)
if FACTOR_IMPLEMENT_SETTINGS.knowledge_base_path is not None
else None
)
self.new_knowledge_base_path = (
Path(FACTOR_IMPLEMENT_SETTINGS.new_knowledge_base_path)
if FACTOR_IMPLEMENT_SETTINGS.new_knowledge_base_path is not None
else None
)
self.with_knowledge = with_knowledge
self.with_feedback = with_feedback
self.knowledge_self_gen = knowledge_self_gen
@@ -32,7 +47,7 @@ class CoSTEERFG(TaskGenerator):
self.evolving_version = 2
def load_or_init_knowledge_base(self, former_knowledge_base_path: Path = None, component_init_list: list = []):
if former_knowledge_base_path is not None and former_knowledge_base_path.exists():
factor_knowledge_base = pickle.load(open(former_knowledge_base_path, "rb"))
if self.evolving_version == 1 and not isinstance(
@@ -53,7 +68,7 @@ class CoSTEERFG(TaskGenerator):
else FactorImplementationKnowledgeBaseV1()
)
return factor_knowledge_base
def generate(self, tasks: List[FactorImplementTask]) -> List[TaskImplementation]:
# init knowledge base
factor_knowledge_base = self.load_or_init_knowledge_base(
@@ -61,9 +76,7 @@ class CoSTEERFG(TaskGenerator):
component_init_list=[],
)
# init rag method
self.rag = (
FactorImplementationGraphRAGStrategy(factor_knowledge_base)
)
self.rag = FactorImplementationGraphRAGStrategy(factor_knowledge_base)
# init indermediate items
factor_implementations = FactorEvovlingItem(target_factor_tasks=tasks)
@@ -83,4 +96,4 @@ class CoSTEERFG(TaskGenerator):
pickle.dump(factor_knowledge_base, open(self.new_knowledge_base_path, "wb"))
self.knowledge_base = factor_knowledge_base
self.latest_factor_implementations = tasks
return factor_implementations
return factor_implementations
@@ -8,21 +8,23 @@ import pandas as pd
from jinja2 import Template
from rdagent.oai.llm_utils import APIBackend
from rdagent.core.log import FinCoLog
from rdagent.core.log import RDAgentLog
from rdagent.factor_implementation.evolving.evolving_strategy import FactorImplementTask, FactorEvovlingItem
from rdagent.core.task import (
TaskImplementation,
)
from typing import List, Tuple
from rdagent.core.evolving_framework import QueriedKnowledge,Feedback
from rdagent.core.evolving_framework import QueriedKnowledge, Feedback
from rdagent.core.evaluation import Evaluator
from rdagent.core.prompts import Prompts
from rdagent.factor_implementation.share_modules.factor_implementation_config import FactorImplementSettings
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.factor_implementation.share_modules.factor_implementation_config import FACTOR_IMPLEMENT_SETTINGS
from rdagent.core.utils import multiprocessing_wrapper
from pathlib import Path
evaluate_prompts = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
class FactorImplementationEvaluator(Evaluator):
# TODO:
# I think we should have unified interface for all evaluates, for examples.
@@ -58,6 +60,7 @@ class FactorImplementationEvaluator(Evaluator):
gt_df = gt_df.to_frame("gt_factor")
return gt_df, gen_df
class FactorImplementationCodeEvaluator(Evaluator):
def evaluate(
self,
@@ -89,7 +92,7 @@ class FactorImplementationCodeEvaluator(Evaluator):
system_prompt=system_prompt,
former_messages=[],
)
> FactorImplementSettings().chat_token_limit
> RD_AGENT_SETTINGS.chat_token_limit
):
execution_feedback_to_render = execution_feedback_to_render[len(execution_feedback_to_render) // 2 :]
user_prompt = Template(
@@ -109,6 +112,7 @@ class FactorImplementationCodeEvaluator(Evaluator):
return critic_response
class FactorImplementationSingleColumnEvaluator(FactorImplementationEvaluator):
def evaluate(
self,
@@ -442,7 +446,7 @@ class FactorImplementationValueEvaluator(Evaluator):
)
except Exception as e:
FinCoLog().warning(f"Error occurred when calculating the correlation: {str(e)}")
RDAgentLog().warning(f"Error occurred when calculating the correlation: {str(e)}")
conclusions.append(
f"Some error occurred when calculating the correlation. Investigate the factors that might be causing the discrepancies and ensure that the logic of the factor calculation is consistent. Error: {e}",
)
@@ -474,7 +478,9 @@ class FactorImplementationFinalDecisionEvaluator(Evaluator):
code_feedback: str,
**kwargs,
) -> Tuple:
system_prompt = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")["evaluator_final_decision_v1_system"]
system_prompt = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")[
"evaluator_final_decision_v1_system"
]
execution_feedback_to_render = execution_feedback
user_prompt = Template(
evaluate_prompts["evaluator_final_decision_v1_user"],
@@ -494,7 +500,7 @@ class FactorImplementationFinalDecisionEvaluator(Evaluator):
system_prompt=system_prompt,
former_messages=[],
)
> FactorImplementSettings().chat_token_limit
> RD_AGENT_SETTINGS.chat_token_limit
):
execution_feedback_to_render = execution_feedback_to_render[len(execution_feedback_to_render) // 2 :]
user_prompt = Template(
@@ -522,6 +528,7 @@ class FactorImplementationFinalDecisionEvaluator(Evaluator):
final_evaluation_dict["final_feedback"],
)
class FactorImplementationSingleFeedback:
"""This class is a feedback to single implementation which is generated from an evaluator."""
@@ -556,12 +563,14 @@ class FactorImplementationSingleFeedback:
This implementation is {'SUCCESS' if self.final_decision else 'FAIL'}.
"""
class FactorImplementationsMultiFeedback(
Feedback,
List[FactorImplementationSingleFeedback],
):
"""Feedback contains a list, each element is the corresponding feedback for each factor implementation."""
class FactorImplementationEvaluatorV1(FactorImplementationEvaluator):
"""This class is the v1 version of evaluator for a single factor implementation.
It calls several evaluators in share modules to evaluate the factor implementation.
@@ -633,7 +642,7 @@ class FactorImplementationEvaluatorV1(FactorImplementationEvaluator):
value_decision,
) = self.value_evaluator.evaluate(source_df=source_df, gt_df=gt_df)
except Exception as e:
FinCoLog().warning("Value evaluation failed with exception: %s", e)
RDAgentLog().warning("Value evaluation failed with exception: %s", e)
factor_feedback.factor_value_feedback = "Value evaluation failed."
value_decision = False
@@ -713,13 +722,12 @@ class FactorImplementationsMultiEvaluator(Evaluator):
),
),
)
multi_implementation_feedback = multiprocessing_wrapper(calls, n=FactorImplementSettings().evo_multi_proc_n)
multi_implementation_feedback = multiprocessing_wrapper(calls, n=FACTOR_IMPLEMENT_SETTINGS.evo_multi_proc_n)
final_decision = [
None if single_feedback is None else single_feedback.final_decision
for single_feedback in multi_implementation_feedback
]
print(f"Final decisions: {final_decision} True count: {final_decision.count(True)}")
RDAgentLog().info(f"Final decisions: {final_decision} True count: {final_decision.count(True)}")
return multi_implementation_feedback
@@ -1,50 +0,0 @@
from rdagent.core.evolving_framework import Feedback, EvolvableSubjects, Evaluator, EvoStep
from rdagent.core.evolving_framework import EvoAgent
from tqdm import tqdm
class RAGEvoAgent(EvoAgent):
def __init__(self, max_loop, evolving_strategy, rag) -> None:
super().__init__(max_loop, evolving_strategy)
self.rag = rag
self.evolving_trace = []
def multistep_evolve(
self,
evo: EvolvableSubjects,
eva: Evaluator | Feedback,
*,
with_knowledge: bool = False,
with_feedback: bool = True,
knowledge_self_gen: bool = False) -> EvolvableSubjects:
for _ in tqdm(range(self.max_loop), "Implementing factors"):
# 1. knowledge self-evolving
if knowledge_self_gen and self.rag is not None:
self.rag.generate_knowledge(self.evolving_trace)
# 2. 检索需要的Knowledge
queried_knowledge = None
if with_knowledge and self.rag is not None:
# TODO: 这里放了evolving_trace实际上没有作用
queried_knowledge = self.rag.query(evo, self.evolving_trace)
# 3. evolve
evo = self.evolving_strategy.evolve(
evo=evo,
evolving_trace=self.evolving_trace,
queried_knowledge=queried_knowledge,
)
# 4. 封装Evolve结果
es = EvoStep(evo, queried_knowledge)
# 5. 环境评测反馈
if with_feedback:
es.feedback = eva if isinstance(eva, Feedback) else eva.evaluate(evo, queried_knowledge=queried_knowledge)
# 6. 更新trace
self.evolving_trace.append(es)
for index, feedback in enumerate(es.feedback):
if feedback is not None:
evo.evolve_trace[evo.target_factor_tasks[index].factor_name][-1].feedback = feedback
return evo
@@ -8,10 +8,11 @@ from typing import TYPE_CHECKING
from jinja2 import Template
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.core.evolving_framework import EvolvingStrategy, QueriedKnowledge
from rdagent.oai.llm_utils import APIBackend
from rdagent.factor_implementation.share_modules.factor_implementation_config import (
FactorImplementSettings,
FACTOR_IMPLEMENT_SETTINGS,
)
from rdagent.core.task import (
@@ -81,18 +82,18 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
# 2. 选择selection方法
# if the number of factors to be implemented is larger than the limit, we need to select some of them
if FactorImplementSettings().select_ratio < 1:
if FACTOR_IMPLEMENT_SETTINGS.select_ratio < 1:
# if the number of loops is equal to the select_loop, we need to select some of them
implementation_factors_per_round = int(
FactorImplementSettings().select_ratio * len(to_be_finished_task_index)
FACTOR_IMPLEMENT_SETTINGS.select_ratio * len(to_be_finished_task_index)
)
if FactorImplementSettings().select_method == "random":
if FACTOR_IMPLEMENT_SETTINGS.select_method == "random":
to_be_finished_task_index = RandomSelect(
to_be_finished_task_index,
implementation_factors_per_round,
)
if FactorImplementSettings().select_method == "scheduler":
if FACTOR_IMPLEMENT_SETTINGS.select_method == "scheduler":
to_be_finished_task_index = LLMSelect(
to_be_finished_task_index,
implementation_factors_per_round,
@@ -105,17 +106,18 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
(self.implement_one_factor, (new_evo.target_factor_tasks[target_index], queried_knowledge))
for target_index in to_be_finished_task_index
],
n=FactorImplementSettings().evo_multi_proc_n,
n=FACTOR_IMPLEMENT_SETTINGS.evo_multi_proc_n,
)
for index, target_index in enumerate(to_be_finished_task_index):
new_evo.corresponding_implementations[target_index] = result[index]
if result[index].target_task.factor_name in new_evo.evolve_trace:
new_evo.evolve_trace[result[index].target_task.factor_name].append(result[index])
else:
new_evo.evolve_trace[result[index].target_task.factor_name] = [result[index]]
new_evo.corresponding_selection.append(to_be_finished_task_index)
# for target_index in to_be_finished_task_index:
# new_evo.corresponding_implementations[target_index] = self.implement_one_factor(
# new_evo.target_factor_tasks[target_index], queried_knowledge
# )
new_evo.corresponding_selection = to_be_finished_task_index
return new_evo
@@ -172,7 +174,7 @@ class FactorEvolvingStrategy(MultiProcessEvolvingStrategy):
session.build_chat_completion_message_and_calculate_token(
user_prompt,
)
< FactorImplementSettings().chat_token_limit
< RD_AGENT_SETTINGS.chat_token_limit
):
break
elif len(queried_former_failed_knowledge_to_render) > 1:
@@ -205,7 +207,7 @@ class FactorEvolvingStrategyWithGraph(MultiProcessEvolvingStrategy):
target_task: FactorImplementTask,
queried_knowledge,
) -> TaskImplementation:
error_summary = FactorImplementSettings().v2_error_summary
error_summary = FACTOR_IMPLEMENT_SETTINGS.v2_error_summary
# 1. 提取因子的背景信息
target_factor_task_information = target_task.get_factor_information()
@@ -284,7 +286,7 @@ class FactorEvolvingStrategyWithGraph(MultiProcessEvolvingStrategy):
)
if (
session_summary.build_chat_completion_message_and_calculate_token(error_summary_user_prompt)
< FactorImplementSettings().chat_token_limit
< RD_AGENT_SETTINGS.chat_token_limit
):
break
elif len(queried_similar_error_knowledge_to_render) > 0:
@@ -311,7 +313,7 @@ class FactorEvolvingStrategyWithGraph(MultiProcessEvolvingStrategy):
session.build_chat_completion_message_and_calculate_token(
user_prompt,
)
< FactorImplementSettings().chat_token_limit
< RD_AGENT_SETTINGS.chat_token_limit
):
break
elif len(queried_former_failed_knowledge_to_render) > 1:
@@ -1,6 +1,6 @@
from __future__ import annotations
from rdagent.factor_implementation.share_modules.factor_implementation_config import (
FactorImplementSettings,
FACTOR_IMPLEMENT_SETTINGS,
)
from rdagent.core.task import (
@@ -9,7 +9,7 @@ from rdagent.core.task import (
TestCase,
)
from rdagent.core.evolving_framework import EvolvableSubjects
from rdagent.core.log import FinCoLog
from rdagent.core.log import RDAgentLog
from pathlib import Path
@@ -35,7 +35,7 @@ class FactorImplementTask(BaseTask):
factor_name,
factor_description,
factor_formulation,
factor_formulation_description: str = '',
factor_formulation_description: str = "",
variables: dict = {},
resource: str = None,
) -> None:
@@ -68,20 +68,17 @@ class FactorEvovlingItem(EvolvableSubjects):
def __init__(
self,
target_factor_tasks: list[FactorImplementTask],
corresponding_gt: list[TestCase] = None,
corresponding_gt_implementations: list[TaskImplementation] = None,
):
super().__init__()
self.target_factor_tasks = target_factor_tasks
self.corresponding_implementations: list[TaskImplementation] = [None for _ in target_factor_tasks]
self.corresponding_selection: list[list] = []
self.evolve_trace = {}
self.corresponding_gt = corresponding_gt
self.corresponding_selection: list = None
if corresponding_gt_implementations is not None and len(
corresponding_gt_implementations,
) != len(target_factor_tasks):
self.corresponding_gt_implementations = None
FinCoLog.warning(
RDAgentLog().warning(
"The length of corresponding_gt_implementations is not equal to the length of target_factor_tasks, set corresponding_gt_implementations to None",
)
else:
@@ -112,10 +109,10 @@ class FileBasedFactorImplementation(TaskImplementation):
super().__init__(target_task)
self.code = code
self.executed_factor_value_dataframe = executed_factor_value_dataframe
self.logger = FinCoLog()
self.logger = RDAgentLog()
self.raise_exception = raise_exception
self.workspace_path = Path(
FactorImplementSettings().file_based_execution_workspace,
FACTOR_IMPLEMENT_SETTINGS.file_based_execution_workspace,
) / str(uuid.uuid4())
@staticmethod
@@ -151,17 +148,14 @@ class FileBasedFactorImplementation(TaskImplementation):
# TODO: to make the interface compatible with previous code. I kept the original behavior.
raise ValueError(self.FB_CODE_NOT_SET)
with FileLock(self.workspace_path / "execution.lock"):
(Path.cwd() / "git_ignore_folder" / "factor_implementation_execution_cache").mkdir(
exist_ok=True, parents=True
)
if FactorImplementSettings().enable_execution_cache:
if FACTOR_IMPLEMENT_SETTINGS.enable_execution_cache:
# NOTE: cache the result for the same code
target_file_name = md5_hash(self.code)
cache_file_path = (
Path.cwd()
/ "git_ignore_folder"
/ "factor_implementation_execution_cache"
/ f"{target_file_name}.pkl"
Path(FACTOR_IMPLEMENT_SETTINGS.implementation_execution_cache_location) / f"{target_file_name}.pkl"
)
Path(FACTOR_IMPLEMENT_SETTINGS.implementation_execution_cache_location).mkdir(
exist_ok=True, parents=True
)
if cache_file_path.exists() and not self.raise_exception:
cached_res = pickle.load(open(cache_file_path, "rb"))
@@ -173,7 +167,7 @@ class FileBasedFactorImplementation(TaskImplementation):
return self.FB_FROM_CACHE, self.executed_factor_value_dataframe
source_data_path = Path(
FactorImplementSettings().file_based_execution_data_folder,
FACTOR_IMPLEMENT_SETTINGS.file_based_execution_data_folder,
)
self.workspace_path.mkdir(exist_ok=True, parents=True)
@@ -189,7 +183,7 @@ class FileBasedFactorImplementation(TaskImplementation):
shell=True,
cwd=self.workspace_path,
stderr=subprocess.STDOUT,
timeout=FactorImplementSettings().file_based_execution_timeout,
timeout=FACTOR_IMPLEMENT_SETTINGS.file_based_execution_timeout,
)
except subprocess.CalledProcessError as e:
import site
@@ -206,7 +200,7 @@ class FileBasedFactorImplementation(TaskImplementation):
if self.raise_exception:
raise RuntimeErrorException(execution_feedback)
except subprocess.TimeoutExpired:
execution_feedback += f"Execution timeout error and the timeout is set to {FactorImplementSettings().file_based_execution_timeout} seconds."
execution_feedback += f"Execution timeout error and the timeout is set to {FACTOR_IMPLEMENT_SETTINGS.file_based_execution_timeout} seconds."
if self.raise_exception:
raise RuntimeErrorException(execution_feedback)
@@ -227,7 +221,7 @@ class FileBasedFactorImplementation(TaskImplementation):
if store_result and executed_factor_value_dataframe is not None:
self.executed_factor_value_dataframe = executed_factor_value_dataframe
if FactorImplementSettings().enable_execution_cache:
if FACTOR_IMPLEMENT_SETTINGS.enable_execution_cache:
pickle.dump(
(execution_feedback, executed_factor_value_dataframe),
open(cache_file_path, "wb"),
@@ -249,4 +243,3 @@ class FileBasedFactorImplementation(TaskImplementation):
with factor_path.open("r") as f:
code = f.read()
return FileBasedFactorImplementation(task, code=code, **kwargs)
@@ -18,7 +18,7 @@ from rdagent.core.evolving_framework import (
QueriedKnowledge,
RAGStrategy,
)
from rdagent.core.log import FinCoLog
from rdagent.core.log import RDAgentLog
from rdagent.core.prompts import Prompts
from rdagent.factor_implementation.evolving.evaluators import FactorImplementationSingleFeedback
from rdagent.core.task import (
@@ -30,9 +30,10 @@ from rdagent.knowledge_management.graph import UndirectedGraph, UndirectedNode
from rdagent.oai.llm_utils import APIBackend, calculate_embedding_distance_between_str_list
from rdagent.factor_implementation.share_modules.factor_implementation_config import (
FactorImplementSettings,
FACTOR_IMPLEMENT_SETTINGS,
)
class FactorImplementationKnowledge(Knowledge):
def __init__(
self,
@@ -138,9 +139,9 @@ class FactorImplementationRAGStrategyV1(RAGStrategy):
evo: EvolvableSubjects,
evolving_trace: list[EvoStep],
) -> QueriedKnowledge | None:
v1_query_former_trace_limit = FactorImplementSettings().v1_query_former_trace_limit
v1_query_similar_success_limit = FactorImplementSettings().v1_query_similar_success_limit
fail_task_trial_limit = FactorImplementSettings().fail_task_trial_limit
v1_query_former_trace_limit = FACTOR_IMPLEMENT_SETTINGS.v1_query_former_trace_limit
v1_query_similar_success_limit = FACTOR_IMPLEMENT_SETTINGS.v1_query_similar_success_limit
fail_task_trial_limit = FACTOR_IMPLEMENT_SETTINGS.fail_task_trial_limit
queried_knowledge = FactorImplementationQueriedKnowledgeV1()
for target_factor_task in evo.target_factor_tasks:
@@ -280,7 +281,7 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
return None
def query(self, evo: EvolvableSubjects, evolving_trace: list[EvoStep]) -> QueriedKnowledge | None:
conf_knowledge_sampler = FactorImplementSettings().v2_knowledge_sampler
conf_knowledge_sampler = FACTOR_IMPLEMENT_SETTINGS.v2_knowledge_sampler
factor_implementation_queried_graph_knowledge = FactorImplementationQueriedGraphKnowledge(
success_task_to_knowledge_dict=self.knowledgebase.success_task_to_knowledge_dict,
)
@@ -288,18 +289,18 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
factor_implementation_queried_graph_knowledge = self.former_trace_query(
evo,
factor_implementation_queried_graph_knowledge,
FactorImplementSettings().v2_query_former_trace_limit,
FACTOR_IMPLEMENT_SETTINGS.v2_query_former_trace_limit,
)
factor_implementation_queried_graph_knowledge = self.component_query(
evo,
factor_implementation_queried_graph_knowledge,
FactorImplementSettings().v2_query_component_limit,
FACTOR_IMPLEMENT_SETTINGS.v2_query_component_limit,
knowledge_sampler=conf_knowledge_sampler,
)
factor_implementation_queried_graph_knowledge = self.error_query(
evo,
factor_implementation_queried_graph_knowledge,
FactorImplementSettings().v2_query_error_limit,
FACTOR_IMPLEMENT_SETTINGS.v2_query_error_limit,
knowledge_sampler=conf_knowledge_sampler,
)
return factor_implementation_queried_graph_knowledge
@@ -327,7 +328,7 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
)["component_no_list"]
return [all_component_nodes[index - 1] for index in sorted(list(set(component_no_list)))]
except:
FinCoLog.warning("Error when analyzing components.")
RDAgentLog().warning("Error when analyzing components.")
analyze_component_user_prompt = "Your response is not a valid component index list."
return []
@@ -383,7 +384,7 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
"""
Query the former trace knowledge of the working trace, and find all the failed task information which tried more than fail_task_trial_limit times
"""
fail_task_trial_limit = FactorImplementSettings().fail_task_trial_limit
fail_task_trial_limit = FACTOR_IMPLEMENT_SETTINGS.fail_task_trial_limit
for target_factor_task in evo.target_factor_tasks:
target_factor_task_information = target_factor_task.get_factor_information()
@@ -705,7 +706,7 @@ class FactorImplementationGraphKnowledgeBase(KnowledgeBase):
Load knowledge, offer brief information of knowledge and common handle interfaces
"""
self.graph: UndirectedGraph = UndirectedGraph.load(Path.cwd() / "graph.pkl")
FinCoLog().info(f"Knowledge Graph loaded, size={self.graph.size()}")
RDAgentLog().info(f"Knowledge Graph loaded, size={self.graph.size()}")
if init_component_list:
for component in init_component_list:
@@ -901,4 +902,3 @@ class FactorImplementationGraphKnowledgeBase(KnowledgeBase):
intersection_node_list_sort_by_freq.append(node)
return intersection_node_list_sort_by_freq
@@ -1,27 +1,32 @@
from rdagent.oai.llm_utils import APIBackend
from jinja2 import Template
from rdagent.factor_implementation.share_modules.factor_implementation_config import FactorImplementSettings
import json
from rdagent.factor_implementation.share_modules.factor_implementation_utils import get_data_folder_intro
from rdagent.factor_implementation.evolving.factor import FactorEvovlingItem
from rdagent.core.prompts import Prompts
from rdagent.core.log import RDAgentLog
from rdagent.core.conf import RD_AGENT_SETTINGS
from pathlib import Path
scheduler_prompts = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
def RandomSelect(to_be_finished_task_index, implementation_factors_per_round):
import random
to_be_finished_task_index = random.sample(
to_be_finished_task_index,
implementation_factors_per_round,
)
print("The random selection is:",to_be_finished_task_index)
RDAgentLog().info(f"The random selection is: {to_be_finished_task_index}")
return to_be_finished_task_index
def LLMSelect(to_be_finished_task_index, implementation_factors_per_round, evo:FactorEvovlingItem, former_trace):
def LLMSelect(to_be_finished_task_index, implementation_factors_per_round, evo: FactorEvovlingItem, former_trace):
tasks = []
for i in to_be_finished_task_index:
# find corresponding former trace for each task
# find corresponding former trace for each task
target_factor_task_information = evo.target_factor_tasks[i].get_factor_information()
if target_factor_task_information in former_trace:
tasks.append((i, evo.target_factor_tasks[i], former_trace[target_factor_task_information]))
@@ -37,20 +42,17 @@ def LLMSelect(to_be_finished_task_index, implementation_factors_per_round, evo:F
)
while True:
user_prompt = (
Template(
scheduler_prompts["select_implementable_factor_user"],
)
.render(
factor_num = implementation_factors_per_round,
target_factor_tasks=tasks,
)
user_prompt = Template(
scheduler_prompts["select_implementable_factor_user"],
).render(
factor_num=implementation_factors_per_round,
target_factor_tasks=tasks,
)
if (
session.build_chat_completion_message_and_calculate_token(
user_prompt,
)
< FactorImplementSettings().chat_token_limit
< RD_AGENT_SETTINGS.chat_token_limit
):
break
@@ -65,5 +67,5 @@ def LLMSelect(to_be_finished_task_index, implementation_factors_per_round, evo:F
selection_index = [x for x in selection if isinstance(x, int)]
except:
return to_be_finished_task_index
return selection_index
@@ -217,3 +217,13 @@ select_implementable_factor_user: |-
{% endfor %}
{% endif %}
{% endfor %}
analyze_component_prompt_v1_system: |-
User is getting a new task that might consist of the components below (given in component_index: component_description):
{{all_component_content}}
You should find out what components does the new task have, and put their indices in a list.
Please response the critic in the json format. Here is an example structure for the JSON output, please strictly follow the format:
{
"component_no_list": the list containing indices of components.
}
@@ -9,13 +9,13 @@ SELECT_METHOD = Literal["random", "scheduler"]
class FactorImplementSettings(BaseSettings):
file_based_execution_data_folder: str = str(
(Path().cwd() / "git_ignore_folder" / "factor_implementation_source_data").absolute(),
(Path().cwd() / "factor_implementation_source_data").absolute(),
)
file_based_execution_workspace: str = str(
(Path().cwd() / "git_ignore_folder" / "factor_implementation_workspace").absolute(),
(Path().cwd() / "factor_implementation_workspace").absolute(),
)
implementation_execution_cache_location: str = str(
(Path().cwd() / "git_ignore_folder" / "factor_implementation_execution_cache.pkl").absolute(),
(Path().cwd() / "factor_implementation_execution_cache").absolute(),
)
enable_execution_cache: bool = True # whether to enable the execution cache
@@ -45,9 +45,5 @@ class FactorImplementSettings(BaseSettings):
knowledge_base_path: Union[str, None] = None
new_knowledge_base_path: Union[str, None] = None
chat_token_limit: int = (
100000 # 100000 is the maximum limit of gpt4, which might increase in the future version of gpt
)
FIS = FactorImplementSettings()
FACTOR_IMPLEMENT_SETTINGS = FactorImplementSettings()
@@ -4,7 +4,7 @@ import pandas as pd
# render it with jinja
from jinja2 import Template
from rdagent.factor_implementation.share_modules.factor_implementation_config import FIS
from rdagent.factor_implementation.share_modules.factor_implementation_config import FACTOR_IMPLEMENT_SETTINGS
from rdagent.factor_implementation.evolving.factor import FactorImplementTask
@@ -17,12 +17,13 @@ TPL = """
# Create a Jinja template from the string
JJ_TPL = Template(TPL)
def get_data_folder_intro():
"""Direclty get the info of the data folder.
It is for preparing prompting message.
"""
content_l = []
for p in Path(FIS.file_based_execution_data_folder).iterdir():
for p in Path(FACTOR_IMPLEMENT_SETTINGS.file_based_execution_data_folder).iterdir():
if p.name.endswith(".h5"):
df = pd.read_hdf(p)
# get df.head() as string with full width
@@ -49,17 +50,3 @@ def get_data_folder_intro():
f"file type {p.name} is not supported. Please implement its description function.",
)
return "\n ----------------- file spliter -------------\n".join(content_l)
def load_data_from_dict(factor_dict:dict) -> list[FactorImplementTask]:
"""Load data from a dict.
"""
task_l = []
for factor_name, factor_data in factor_dict.items():
task = FactorImplementTask(
factor_name=factor_name,
factor_description=factor_data["description"],
factor_formulation=factor_data["formulation"],
variables=factor_data["variables"],
)
task_l.append(task)
return task_l
@@ -0,0 +1,31 @@
import json
from pathlib import Path
from rdagent.core.task import TaskLoader
from rdagent.factor_implementation.evolving.factor import FactorImplementTask
class FactorImplementationTaskLoaderFromDict(TaskLoader):
def load(self, factor_dict: dict) -> list:
"""Load data from a dict."""
task_l = []
for factor_name, factor_data in factor_dict.items():
task = FactorImplementTask(
factor_name=factor_name,
factor_description=factor_data["description"],
factor_formulation=factor_data["formulation"],
variables=factor_data["variables"],
)
task_l.append(task)
return task_l
class FactorImplementationTaskLoaderFromJsonFile(TaskLoader):
def load(self, json_file_path: Path) -> list:
factor_dict = json.load(json_file_path)
return FactorImplementationTaskLoaderFromDict().load(factor_dict)
class FactorImplementationTaskLoaderFromJsonString(TaskLoader):
def load(self, json_string: str) -> list:
factor_dict = json.loads(json_string)
return FactorImplementationTaskLoaderFromDict().load(factor_dict)
@@ -10,9 +10,12 @@ import numpy as np
import pandas as pd
import tiktoken
from jinja2 import Template
from rdagent.core.conf import RDAgentSettings as Config
from rdagent.core.log import FinCoLog
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.core.log import RDAgentLog
from rdagent.core.prompts import Prompts
from rdagent.core.task import TaskLoader
from rdagent.document_reader.document_reader import load_and_process_pdfs_by_langchain
from rdagent.factor_implementation.task_loader.json_loader import FactorImplementationTaskLoaderFromDict
from rdagent.oai.llm_utils import APIBackend, create_embedding_with_multiprocessing
from sklearn.cluster import KMeans
from sklearn.metrics.pairwise import cosine_similarity
@@ -23,7 +26,6 @@ document_process_prompts = Prompts(file_path=Path(__file__).parent / "prompts.ya
def classify_report_from_dict(
report_dict: Mapping[str, str],
input_max_token: int = 128000,
vote_time: int = 1,
substrings: tuple[str] = (),
) -> dict[str, dict[str, str]]:
@@ -41,21 +43,19 @@ def classify_report_from_dict(
with a single key 'class' and its value being the classification result (0 or 1).
"""
if len(substrings) == 0:
substrings = (
"FinCo",
"金融工程",
"金工",
"回测",
"因子",
"机器学习",
"深度学习",
"量化",
)
# if len(substrings) == 0:
# substrings = (
# "金融工程",
# "金工",
# "回测",
# "因子",
# "机器学习",
# "深度学习",
# "量化",
# )
res_dict = {}
classify_prompt = document_process_prompts["classify_system"]
enc = tiktoken.encoding_for_model("gpt-4-turbo")
for key, value in report_dict.items():
if not key.endswith(".pdf"):
@@ -65,44 +65,47 @@ def classify_report_from_dict(
if isinstance(value, str):
content = value
else:
FinCoLog().warning(f"输入格式不符合要求: {file_name}")
RDAgentLog().warning(f"输入格式不符合要求: {file_name}")
res_dict[file_name] = {"class": 0}
continue
if not any(substring in content for substring in substrings) and False:
res_dict[file_name] = {"class": 0}
else:
while (
APIBackend().build_messages_and_calculate_token(
user_prompt=content,
system_prompt=classify_prompt,
)
> Config().chat_token_limit
):
content = content[: -(Config().chat_token_limit // 100)]
# pre-filter document with key words is not necessary, skip this check for now
# if (
# not any(substring in content for substring in substrings) and False
# ):
# res_dict[file_name] = {"class": 0}
# else:
while (
APIBackend().build_messages_and_calculate_token(
user_prompt=content,
system_prompt=classify_prompt,
)
> RD_AGENT_SETTINGS.chat_token_limit
):
content = content[: -(RD_AGENT_SETTINGS.chat_token_limit // 100)]
vote_list = []
for _ in range(vote_time):
user_prompt = content
system_prompt = classify_prompt
res = APIBackend().build_messages_and_create_chat_completion(
user_prompt=user_prompt,
system_prompt=system_prompt,
json_mode=True,
)
try:
res = json.loads(res)
vote_list.append(int(res["class"]))
except json.JSONDecodeError:
FinCoLog().warning(f"返回值无法解析: {file_name}")
res_dict[file_name] = {"class": 0}
count_0 = vote_list.count(0)
count_1 = vote_list.count(1)
if max(count_0, count_1) > int(vote_time / 2):
break
vote_list = []
for _ in range(vote_time):
user_prompt = content
system_prompt = classify_prompt
res = APIBackend().build_messages_and_create_chat_completion(
user_prompt=user_prompt,
system_prompt=system_prompt,
json_mode=True,
)
try:
res = json.loads(res)
vote_list.append(int(res["class"]))
except json.JSONDecodeError:
RDAgentLog().warning(f"返回值无法解析: {file_name}")
res_dict[file_name] = {"class": 0}
count_0 = vote_list.count(0)
count_1 = vote_list.count(1)
if max(count_0, count_1) > int(vote_time / 2):
break
result = 1 if count_1 > count_0 else 0
res_dict[file_name] = {"class": result}
result = 1 if count_1 > count_0 else 0
res_dict[file_name] = {"class": result}
return res_dict
@@ -235,7 +238,7 @@ def extract_factors_from_report_dict(
if int(value.get("class")) == 1:
useful_report_dict[key] = report_dict[key]
else:
FinCoLog().warning(f"输入格式不符合要求: {key}")
RDAgentLog().warning(f"Invalid input format: {key}")
final_report_factor_dict = {}
# for file_name, content in useful_report_dict.items():
@@ -265,7 +268,7 @@ def extract_factors_from_report_dict(
file_name = file_names[index]
final_report_factor_dict.setdefault(file_name, {})
final_report_factor_dict[file_name] = result.get()
FinCoLog().info(f"已经完成{len(final_report_factor_dict)}个报告的因子提取")
RDAgentLog().info(f"已经完成{len(final_report_factor_dict)}个报告的因子提取")
return final_report_factor_dict
@@ -409,7 +412,7 @@ def __kmeans_embeddings(embeddings: np.ndarray, k: int = 20) -> list[list[str]]:
random_state=42,
)
# KMeans算法使用欧氏距离, 我们需要自定义一个函数来找到最相似的簇中心
# KMeans algorithm uses Euclidean distance, and we need to customize a function to find the most similar cluster center
def find_closest_cluster_cosine_similarity(
data: np.ndarray,
centroids: np.ndarray,
@@ -417,25 +420,25 @@ def __kmeans_embeddings(embeddings: np.ndarray, k: int = 20) -> list[list[str]]:
similarity = cosine_similarity(data, centroids)
return np.argmax(similarity, axis=1)
# 初始化簇中心
# Initializes the cluster center
rng = np.random.default_rng()
centroids = rng.choice(x_normalized, size=k, replace=False)
# 迭代直到收敛或达到最大迭代次数
# Iterate until convergence or the maximum number of iterations is reached
for _ in range(kmeans.max_iter):
# 分配样本到最近的簇中心
# Assign the sample to the nearest cluster center
closest_clusters = find_closest_cluster_cosine_similarity(
x_normalized,
centroids,
)
# 更新簇中心
# update the cluster center
new_centroids = np.array(
[x_normalized[closest_clusters == i].mean(axis=0) for i in range(k)],
)
new_centroids = normalize(new_centroids) # 归一化新的簇中心
# 检查簇中心是否发生变化
# Check whether the cluster center has changed
if np.allclose(centroids, new_centroids):
break
@@ -477,18 +480,18 @@ Factor variables: {variables}
embeddings = create_embedding_with_multiprocessing(full_str_list)
target_k = None
if len(full_str_list) < Config().max_input_duplicate_factor_group:
if len(full_str_list) < RD_AGENT_SETTINGS.max_input_duplicate_factor_group:
kmeans_index_group = [list(range(len(full_str_list)))]
target_k = 1
else:
for k in range(
len(full_str_list) // Config().max_input_duplicate_factor_group,
len(full_str_list) // RD_AGENT_SETTINGS.max_input_duplicate_factor_group,
30,
):
kmeans_index_group = __kmeans_embeddings(embeddings=embeddings, k=k)
if len(kmeans_index_group[0]) < Config().max_input_duplicate_factor_group:
if len(kmeans_index_group[0]) < RD_AGENT_SETTINGS.max_input_duplicate_factor_group:
target_k = k
FinCoLog().info(f"K-means group number: {k}")
RDAgentLog().info(f"K-means group number: {k}")
break
factor_name_groups = [[factor_names[index] for index in index_group] for index_group in kmeans_index_group]
@@ -519,7 +522,7 @@ Factor variables: {variables}
return duplication_names_list
def deduplicate_factors_by_llm( # noqa: C901, PLR0912
def deduplicate_factors_by_llm( # noqa: C901, PLR0912
factor_dict: dict[str, dict[str, str]],
factor_viability_dict: dict[str, dict[str, str]] | None = None,
) -> list[list[str]]:
@@ -530,7 +533,7 @@ def deduplicate_factors_by_llm( # noqa: C901, PLR0912
new_round_names = []
for duplication_names in duplication_names_list:
if len(duplication_names) < Config().max_output_duplicate_factor_group:
if len(duplication_names) < RD_AGENT_SETTINGS.max_output_duplicate_factor_group:
final_duplication_names_list.append(duplication_names)
else:
new_round_names.extend(duplication_names)
@@ -566,3 +569,16 @@ def deduplicate_factors_by_llm( # noqa: C901, PLR0912
llm_deduplicated_factor_dict[factor_name] = factor_dict[factor_name]
return llm_deduplicated_factor_dict, final_duplication_names_list
class FactorImplementationTaskLoaderFromPDFfiles(TaskLoader):
def load(self, file_or_folder_path: Path) -> dict:
docs_dict = load_and_process_pdfs_by_langchain(Path(file_or_folder_path))
selected_report_dict = classify_report_from_dict(report_dict=docs_dict, vote_time=1)
file_to_factor_result = extract_factors_from_report_dict(docs_dict, selected_report_dict)
factor_dict = merge_file_to_factor_dict_to_factor_dict(file_to_factor_result)
factor_viability = check_factor_viability(factor_dict)
factor_dict, duplication_names_list = deduplicate_factors_by_llm(factor_dict, factor_viability)
return FactorImplementationTaskLoaderFromDict().load(factor_dict)
@@ -35,40 +35,45 @@ extract_factors_follow_user: |-
```
extract_factor_formulation_system: |-
用户会提供一篇金融工程研报,和用户从中提取到的因子列表,请结合文章和用户提供的因子名称和因子描述,按照要求抽取:
1. 因子的计算公式,使用latex格式,公式中的变量名称不能包含空格,可用下划线连接,公式中的因子名称与用户提供的因子名称保持一致;
2. 因子公式中的变量和函数解释,请使用英文描述,变量名和函数名请与公式中的名称对齐
I have a financial engineering research report and a list of factors extracted from it. I need assistance in extracting specific information based on the report and the provided list of factors. The tasks are as follows:
User has several source data:
1. The Stock Trade Data Table containing information about stock trades, such as daily open, close, high, low, vwap prices, volume, and turnover;
2. The Financial Data Table containing company financial statements such as the balance sheet, income statement, and cash flow statement;
3. The Stock Fundamental Data Table containing basic information about stocks, like total shares outstanding, free float shares, industry classification, market classification, etc;
4. The high frequency data containing price and volume of each stock containing open close high low volume vwap in each minute.
Please try to expand the formulation to using the source data provided by user.
1. For each factor, I need its calculation formula in LaTeX format. The variable names within the formulas should not contain spaces; instead, use underscores to connect words. Ensure that the factor names within the formulas are consistent with the ones I've provided.
2. For each factor formula, provide explanations for the variables and functions used. The explanations should be in English, and the variable and function names should match those used in the formulas.
Here are the sources of data I have:
1. Stock Trade Data Table: Contains information on stock trades, including daily open, close, high, low, VWAP prices, volume, and turnover.
2. Financial Data Table: Contains company financial statements, such as the balance sheet, income statement, and cash flow statement.
3. Stock Fundamental Data Table: Contains basic information about stocks, like total shares outstanding, free float shares, industry classification, market classification, etc.
4. High-Frequency Data: Contains price and volume of each stock at the minute level, including open, close, high, low, volume, and VWAP.
Please expand the formulation to use the source data I have provided. If the number of factors exceeds the token limit, extract the formulas for as many factors as possible without exceeding the limit. Ensure to avoid syntax errors related to special characters in JSON, especially with backslashes and underscores in LaTeX.
Provide your analysis in JSON format, using the following schema:
user will treat your factor name as key to store the factor, don't put any interaction message in the content. Just response the output without any interaction and explanation.
You can extract part of the user's input factors if token is not enough. To avoid the situation that you don't respond in the valid format, don't extract more than thirty factors in one response.
Be caution of the "\" in your formulation because In JSON, certain characters like the backslash need to be escaped with another backslash. Especially, _ and \_ are different in latex so use \_ to represent _ in latex.
Respond with your analysis in JSON format. The JSON schema should include:
```json
{
"name of factor 1": {
"factor name 1": {
"formulation": "latex formulation of factor 1",
"variables": {
"Name to variable or function 1": "Description to variable or function 1",
"Name to variable or function 2": "Description to variable or function 2"
"variable or function name 1": "description of variable or function 1",
"variable or function name 2": "description of variable or function 2"
}
},
"name of factor 2": {
"factor name 2": {
"formulation": "latex formulation of factor 2",
"variables": {
"Name to variable or function 1": "Description to variable or function 1",
"Name to variable or function 2": "Description to variable or function 2"
"variable or function name 1": "description of variable or function 1",
"variable or function name 2": "description of variable or function 2"
}
}
# Don't add ellipsis (...) or any filler text that might cause JSON parsing errors here!
}
```
Please only include the factors you've extracted in your response, without any additional messages or explanations. If you can only provide part of the factors, do not use ellipsis or any filler text that might cause JSON parsing errors.
extract_factor_formulation_user: |-
===========================Report content:=============================
{{ report_content }}
+17 -16
View File
@@ -15,12 +15,15 @@ from rdagent.core.evolving_framework import (
KnowledgeBase,
)
from rdagent.core.evolving_framework import EvoStep, EvolvableSubjects, RAGStrategy, Knowledge, QueriedKnowledge
from rdagent.factor_implementation.evolving.knowledge_management import FactorImplementationKnowledge, FactorImplementationQueriedGraphKnowledge
from rdagent.factor_implementation.share_modules.factor_implementation_config import FactorImplementSettings
from rdagent.factor_implementation.evolving.knowledge_management import (
FactorImplementationKnowledge,
FactorImplementationQueriedGraphKnowledge,
)
from rdagent.factor_implementation.share_modules.factor_implementation_config import FACTOR_IMPLEMENT_SETTINGS
from rdagent.knowledge_management.graph import UndirectedGraph, UndirectedNode
from rdagent.core.prompts import Prompts
from rdagent.core.log import FinCoLog
from rdagent.core.log import RDAgentLog
from rdagent.oai.llm_utils import APIBackend, calculate_embedding_distance_between_str_list
@@ -30,7 +33,7 @@ class FactorImplementationGraphKnowledgeBase(KnowledgeBase):
Load knowledge, offer brief information of knowledge and common handle interfaces
"""
self.graph: UndirectedGraph = UndirectedGraph.load(Path.cwd() / "graph.pkl")
FinCoLog().info(f"Knowledge Graph loaded, size={self.graph.size()}")
RDAgentLog().info(f"Knowledge Graph loaded, size={self.graph.size()}")
if init_component_list:
for component in init_component_list:
@@ -228,7 +231,6 @@ class FactorImplementationGraphKnowledgeBase(KnowledgeBase):
return intersection_node_list_sort_by_freq
class FactorImplementationGraphRAGStrategy(RAGStrategy):
def __init__(self, knowledgebase: FactorImplementationGraphKnowledgeBase) -> None:
super().__init__(knowledgebase)
@@ -302,26 +304,26 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
return None
def query(self, evo: EvolvableSubjects, evolving_trace: list[EvoStep]) -> QueriedKnowledge | None:
conf_knowledge_sampler = FactorImplementSettings().v2_knowledge_sampler
conf_knowledge_sampler = FACTOR_IMPLEMENT_SETTINGS.v2_knowledge_sampler
factor_implementation_queried_graph_knowledge = FactorImplementationQueriedGraphKnowledge(
success_task_to_knowledge_dict=self.knowledgebase.success_task_to_knowledge_dict,
)
factor_implementation_queried_graph_knowledge = self.former_trace_query(
evo,
factor_implementation_queried_graph_knowledge,
FactorImplementSettings().v2_query_former_trace_limit,
FACTOR_IMPLEMENT_SETTINGS.v2_query_former_trace_limit,
)
factor_implementation_queried_graph_knowledge = self.component_query(
evo,
factor_implementation_queried_graph_knowledge,
FactorImplementSettings().v2_query_component_limit,
FACTOR_IMPLEMENT_SETTINGS.v2_query_component_limit,
knowledge_sampler=conf_knowledge_sampler,
)
factor_implementation_queried_graph_knowledge = self.error_query(
evo,
factor_implementation_queried_graph_knowledge,
FactorImplementSettings().v2_query_error_limit,
FACTOR_IMPLEMENT_SETTINGS.v2_query_error_limit,
knowledge_sampler=conf_knowledge_sampler,
)
return factor_implementation_queried_graph_knowledge
@@ -349,7 +351,7 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
)["component_no_list"]
return [all_component_nodes[index - 1] for index in sorted(list(set(component_no_list)))]
except:
FinCoLog.warning("Error when analyzing components.")
RDAgentLog().warning("Error when analyzing components.")
analyze_component_user_prompt = "Your response is not a valid component index list."
return []
@@ -405,7 +407,7 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
"""
Query the former trace knowledge of the working trace, and find all the failed task information which tried more than fail_task_trial_limit times
"""
fail_task_trial_limit = FactorImplementSettings().fail_task_trial_limit
fail_task_trial_limit = FACTOR_IMPLEMENT_SETTINGS.fail_task_trial_limit
for target_factor_task in evo.target_factor_tasks:
target_factor_task_information = target_factor_task.get_factor_information()
@@ -438,9 +440,9 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
else:
current_index += 1
factor_implementation_queried_graph_knowledge.former_traces[
target_factor_task_information
] = former_trace_knowledge[-v2_query_former_trace_limit:]
factor_implementation_queried_graph_knowledge.former_traces[target_factor_task_information] = (
former_trace_knowledge[-v2_query_former_trace_limit:]
)
else:
factor_implementation_queried_graph_knowledge.former_traces[target_factor_task_information] = []
@@ -719,4 +721,3 @@ class FactorImplementationGraphRAGStrategy(RAGStrategy):
] = same_error_success_knowledge_pair_list
return factor_implementation_queried_graph_knowledge
@@ -1,9 +0,0 @@
analyze_component_prompt_v1_system: |-
User is getting a new task that might consist of the components below (given in component_index: component_description):
{{all_component_content}}
You should find out what components does the new task have, and put their indices in a list.
Please response the critic in the json format. Here is an example structure for the JSON output, please strictly follow the format:
{
"component_no_list": the list containing indices of components.
}
+3 -3
View File
@@ -6,7 +6,7 @@ import pandas as pd
from scipy.spatial.distance import cosine
from rdagent.oai.llm_utils import APIBackend
from rdagent.core.log import FinCoLog
from rdagent.core.log import RDAgentLog
class KnowledgeMetaData:
@@ -127,7 +127,7 @@ class PDVectorBase(VectorBase):
else:
self.vector_df = pd.DataFrame(columns=["id", "label", "content", "embedding"])
FinCoLog().info(f"VectorBase loaded, shape={self.vector_df.shape}")
RDAgentLog().info(f"VectorBase loaded, shape={self.vector_df.shape}")
def shape(self):
return self.vector_df.shape
@@ -205,4 +205,4 @@ class PDVectorBase(VectorBase):
def save(self, vector_df_path, **kwargs):
self.vector_df.to_pickle(vector_df_path)
FinCoLog().info(f"Save vectorBase vector_df to: {vector_df_path}")
RDAgentLog().info(f"Save vectorBase vector_df to: {vector_df_path}")
+57 -43
View File
@@ -18,8 +18,8 @@ from typing import Any
import numpy as np
import tiktoken
from rdagent.core.conf import RDAgentSettings as Config
from rdagent.core.log import FinCoLog, LogColors
from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.core.log import RDAgentLog, LogColors
from rdagent.core.utils import SingletonBaseClass
DEFAULT_QLIB_DOT_PATH = Path("./")
@@ -35,17 +35,17 @@ def md5_hash(input_string: str) -> str:
try:
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
except ImportError:
FinCoLog().warning("azure.identity is not installed.")
RDAgentLog().warning("azure.identity is not installed.")
try:
import openai
except ImportError:
FinCoLog().warning("openai is not installed.")
RDAgentLog().warning("openai is not installed.")
try:
from llama import Llama
except ImportError:
FinCoLog().warning("llama is not installed.")
RDAgentLog().warning("llama is not installed.")
class ConvManager:
@@ -133,7 +133,6 @@ class SQliteLazyCache(SingletonBaseClass):
)
self.conn.commit()
def embedding_set(self, content_to_embedding_dict: dict) -> None:
for key, value in content_to_embedding_dict.items():
md5_key = md5_hash(key)
@@ -147,15 +146,15 @@ class SQliteLazyCache(SingletonBaseClass):
class SessionChatHistoryCache(SingletonBaseClass):
def __init__(self) -> None:
"""load all history conversation json file from self.session_cache_location"""
self.cfg = Config()
self.cfg = RD_AGENT_SETTINGS
self.session_cache_location = Path(self.cfg.session_cache_folder_location)
self.cache = {}
if not self.session_cache_location.exists():
FinCoLog.warning(f"Directory {self.session_cache_location} does not exist.")
RDAgentLog().warning(f"Directory {self.session_cache_location} does not exist.")
self.session_cache_location.mkdir(parents=True, exist_ok=True)
json_files = [f for f in self.session_cache_location.iterdir() if f.suffix == ".json"]
if not json_files:
FinCoLog.info(f"No JSON files found in {self.session_cache_location}.")
RDAgentLog().info(f"No JSON files found in {self.session_cache_location}.")
for file_path in json_files:
conversation_id = file_path.stem
with file_path.open("r") as f:
@@ -177,7 +176,7 @@ class SessionChatHistoryCache(SingletonBaseClass):
class ChatSession:
def __init__(self, api_backend: Any, conversation_id: str | None = None, system_prompt: str | None = None) -> None:
self.conversation_id = str(uuid.uuid4()) if conversation_id is None else conversation_id
self.cfg = Config()
self.cfg = RD_AGENT_SETTINGS
self.system_prompt = system_prompt if system_prompt is not None else self.cfg.default_system_prompt
self.api_backend = api_backend
@@ -205,8 +204,10 @@ class ChatSession:
"""
messages = self.build_chat_completion_message(user_prompt)
response = self.api_backend._try_create_chat_completion_or_embedding( # noqa: SLF001
messages=messages, chat_completion=True, **kwargs,
response = self.api_backend._try_create_chat_completion_or_embedding( # noqa: SLF001
messages=messages,
chat_completion=True,
**kwargs,
)
messages.append(
{
@@ -226,7 +227,7 @@ class ChatSession:
class APIBackend:
def __init__( # noqa: C901, PLR0912, PLR0915
def __init__( # noqa: C901, PLR0912, PLR0915
self,
*,
chat_api_key: str | None = None,
@@ -242,7 +243,7 @@ class APIBackend:
use_embedding_cache: bool | None = None,
dump_embedding_cache: bool | None = None,
) -> None:
self.cfg = Config()
self.cfg = RD_AGENT_SETTINGS
if self.cfg.use_llama2:
self.generator = Llama.build(
ckpt_dir=self.cfg.llama2_ckpt_dir,
@@ -286,7 +287,7 @@ class APIBackend:
self.gcr_endpoint_do_sample = self.cfg.gcr_endpoint_do_sample
self.gcr_endpoint_max_token = self.cfg.gcr_endpoint_max_token
if not os.environ.get("PYTHONHTTPSVERIFY", "") and hasattr(ssl, "_create_unverified_context"):
ssl._create_default_https_context = ssl._create_unverified_context # noqa: SLF001
ssl._create_default_https_context = ssl._create_unverified_context # noqa: SLF001
self.encoder = None
else:
self.use_azure = self.cfg.use_azure
@@ -315,7 +316,8 @@ class APIBackend:
if self.use_azure_token_provider:
credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(
credential, "https://cognitiveservices.azure.com/.default",
credential,
"https://cognitiveservices.azure.com/.default",
)
self.chat_client = openai.AzureOpenAI(
azure_ad_token_provider=token_provider,
@@ -414,7 +416,9 @@ class APIBackend:
) -> str:
if former_messages is None:
former_messages = []
messages = self.build_messages(user_prompt, system_prompt, former_messages, shrink_multiple_break=shrink_multiple_break)
messages = self.build_messages(
user_prompt, system_prompt, former_messages, shrink_multiple_break=shrink_multiple_break
)
return self._try_create_chat_completion_or_embedding(
messages=messages,
chat_completion=True,
@@ -422,12 +426,12 @@ class APIBackend:
**kwargs,
)
def create_embedding(self, input_content: str | list[str], **kwargs: Any) -> list[Any] | Any:
input_content_list = [input_content] if isinstance(input_content, str) else input_content
resp = self._try_create_chat_completion_or_embedding(
input_content_list=input_content_list, embedding=True, **kwargs,
input_content_list=input_content_list,
embedding=True,
**kwargs,
)
if isinstance(input_content, str):
return resp[0]
@@ -454,7 +458,12 @@ class APIBackend:
return response
def _try_create_chat_completion_or_embedding(
self, max_retry: int = 10, *, chat_completion: bool = False, embedding: bool = False, **kwargs: Any,
self,
max_retry: int = 10,
*,
chat_completion: bool = False,
embedding: bool = False,
**kwargs: Any,
) -> Any:
assert not (chat_completion and embedding), "chat_completion and embedding cannot be True at the same time"
max_retry = self.cfg.max_retry if self.cfg.max_retry is not None else max_retry
@@ -464,23 +473,25 @@ class APIBackend:
return self._create_embedding_inner_function(**kwargs)
if chat_completion:
return self._create_chat_completion_auto_continue(**kwargs)
except openai.BadRequestError as e: # noqa: PERF203
print(e)
print(f"Retrying {i+1}th time...")
except openai.BadRequestError as e: # noqa: PERF203
RDAgentLog().warning(e)
RDAgentLog().warning(f"Retrying {i+1}th time...")
if "'messages' must contain the word 'json' in some form" in e.message:
kwargs["add_json_in_prompt"] = True
elif embedding and "maximum context length" in e.message:
kwargs["input_content_list"] = [
content[: len(content) // 2] for content in kwargs.get("input_content_list", [])
]
except Exception as e: # noqa: BLE001
print(e)
print(f"Retrying {i+1}th time...")
except Exception as e: # noqa: BLE001
RDAgentLog().warning(e)
RDAgentLog().warning(f"Retrying {i+1}th time...")
time.sleep(self.retry_wait_seconds)
error_message = f"Failed to create chat completion after {max_retry} retries."
raise RuntimeError(error_message)
def _create_embedding_inner_function(self, input_content_list: list[str], **kwargs: Any) -> list[Any]: # noqa: ARG002
def _create_embedding_inner_function(
self, input_content_list: list[str], **kwargs: Any
) -> list[Any]: # noqa: ARG002
content_to_embedding_dict = {}
filtered_input_content_list = []
if self.use_embedding_cache:
@@ -511,7 +522,6 @@ class APIBackend:
self.cache.embedding_set(content_to_embedding_dict)
return [content_to_embedding_dict[content] for content in input_content_list]
def _build_messages(self, messages: list[dict]) -> str:
log_messages = ""
for m in messages:
@@ -525,16 +535,16 @@ class APIBackend:
def log_messages(self, messages: list[dict]) -> None:
if self.cfg.log_llm_chat_content:
FinCoLog().info(self._build_messages(messages))
RDAgentLog().info(self._build_messages(messages))
def log_response(self, response: str | None = None, *, stream: bool = False) -> None:
if self.cfg.log_llm_chat_content:
if stream:
FinCoLog().info(f"\n{LogColors.CYAN}Response:{LogColors.END}")
RDAgentLog().info(f"\n{LogColors.CYAN}Response:{LogColors.END}")
else:
FinCoLog().info(f"\n{LogColors.CYAN}Response:{response}{LogColors.END}")
RDAgentLog().info(f"\n{LogColors.CYAN}Response:{response}{LogColors.END}")
def _create_chat_completion_inner_function( # noqa: C901, PLR0912, PLR0915
def _create_chat_completion_inner_function( # noqa: C901, PLR0912, PLR0915
self,
messages: list[dict],
temperature: float | None = None,
@@ -592,8 +602,8 @@ class APIBackend:
),
)
req = urllib.request.Request(self.gcr_endpoint, body, self.headers) # noqa: S310
response = urllib.request.urlopen(req) # noqa: S310
req = urllib.request.Request(self.gcr_endpoint, body, self.headers) # noqa: S310
response = urllib.request.urlopen(req) # noqa: S310
resp = json.loads(response.read().decode())["output"]
self.log_response(resp)
else:
@@ -662,7 +672,7 @@ class APIBackend:
def calculate_token_from_messages(self, messages: list[dict]) -> int:
if self.use_llama2 or self.use_gcr_endpoint:
FinCoLog().warning("num_tokens_from_messages() is not implemented for model llama2.")
RDAgentLog().warning("num_tokens_from_messages() is not implemented for model llama2.")
return 0 # TODO implement this function for llama2
if "gpt4" in self.chat_model or "gpt-4" in self.chat_model:
@@ -691,7 +701,9 @@ class APIBackend:
) -> int:
if former_messages is None:
former_messages = []
messages = self.build_messages(user_prompt, system_prompt, former_messages, shrink_multiple_break=shrink_multiple_break)
messages = self.build_messages(
user_prompt, system_prompt, former_messages, shrink_multiple_break=shrink_multiple_break
)
return self.calculate_token_from_messages(messages)
@@ -703,8 +715,10 @@ def create_embedding_with_multiprocessing(str_list: list, slice_count: int = 50,
embeddings = []
pool = multiprocessing.Pool(nproc)
result_list = [pool.apply_async(calculate_embedding_process, (str_list[index : index + slice_count],))
for index in range(0, len(str_list), slice_count)]
result_list = [
pool.apply_async(calculate_embedding_process, (str_list[index : index + slice_count],))
for index in range(0, len(str_list), slice_count)
]
pool.close()
pool.join()
@@ -713,16 +727,16 @@ def create_embedding_with_multiprocessing(str_list: list, slice_count: int = 50,
return embeddings
def calculate_embedding_distance_between_str_list(
source_str_list: list[str], target_str_list: list[str],
source_str_list: list[str],
target_str_list: list[str],
) -> list[list[float]]:
if not source_str_list or not target_str_list:
return [[]]
embeddings = create_embedding_with_multiprocessing(source_str_list + target_str_list, slice_count=50, nproc=8)
source_embeddings = embeddings[:len(source_str_list)]
target_embeddings = embeddings[len(source_str_list):]
source_embeddings = embeddings[: len(source_str_list)]
target_embeddings = embeddings[len(source_str_list) :]
source_embeddings_np = np.array(source_embeddings)
target_embeddings_np = np.array(target_embeddings)