diff --git a/rdagent/app/CI/README.md b/rdagent/app/CI/README.md new file mode 100644 index 00000000..73f08801 --- /dev/null +++ b/rdagent/app/CI/README.md @@ -0,0 +1,38 @@ +# CI 检查 + +`.github/workflows/ci.yml`配置了提交时自动运行`Makefile`: 91~103行的命令,可以在这调整执行的命令 + +在`.env`中设置`USE_CHAT_CACHE=True`可以让第二次修复快一些 + +# Rules + +`pyproject.toml`中配置全局屏蔽的规则 +- ruff: `[tool.ruff.lint].ignore` +- mypy: `[tool.mypy]` + +## ruff rules +ruff rules 比较好修改, 大多可以自动修复 + +对于一些规则可以在代码中添加注释来局部屏蔽, 例如添加 `# noqa E234,ANN001` +遇到的不好修改的规则: +- 捕获异常时应该处理每一种异常,不应该统一当作`Exception`处理 +- `subprogress()` 调用命令应该先判断命令是否安全 +- ... + +规则列表: [ruff rules](https://docs.astral.sh/ruff/rules/) + +## mypy rules + +Mypy检查Python中类型标注, 常遇到需要修改结构/同时修改其他文件的情况, 自动修复效果不好 + +局部屏蔽: `# type: ignore` + +规则列表: [mypy rules](https://mypy.readthedocs.io/en/stable/error_code_list.html) + +# Optimization (Maybe) + +- 添加指定文件夹检查的功能 +- 增加一个修改选项: 调用`vim`, 用户直接修改此部分代码 +- 显示时把`Original Code`部分去掉, 直接在输出的表示修改的diff部分用`^^^^^^`在代码行下标注出错误位置,这样能更直观地观察错误修复情况 +- 当前为线性执行完所有修复后交给用户检查, 可修改成 后台多线程 / 进程处理修复的任务, 终端实时展示处理完的修复让用户检查 +- ... diff --git a/rdagent/app/CI/run.py b/rdagent/app/CI/run.py index 82d2a1df..469cdf00 100644 --- a/rdagent/app/CI/run.py +++ b/rdagent/app/CI/run.py @@ -13,16 +13,6 @@ from pathlib import Path from typing import Any, Literal import tree_sitter_python -from rich import print -from rich.panel import Panel -from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn -from rich.prompt import Prompt -from rich.rule import Rule -from rich.syntax import Syntax -from rich.table import Table -from rich.text import Text -from tree_sitter import Language, Node, Parser - from rdagent.core.evaluation import Evaluator from rdagent.core.evolving_agent import EvoAgent from rdagent.core.evolving_framework import ( @@ -34,6 +24,15 @@ from rdagent.core.evolving_framework import ( ) from rdagent.core.prompts import Prompts from rdagent.oai.llm_utils import APIBackend +from rich import print +from rich.panel import Panel +from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn +from rich.prompt import Prompt +from rich.rule import Rule +from rich.syntax import Syntax +from rich.table import Table +from rich.text import Text +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") @@ -304,7 +303,7 @@ class RuffEvaluator(Evaluator): return RuffRule(**json.loads(out)) - def evaluate(self, evo: Repo, **kwargs: Any) -> CIFeedback: # noqa: ARG002 + def evaluate(self, evo: Repo, **kwargs: dict) -> CIFeedback: """Simply run ruff to get the feedbacks.""" try: out = subprocess.check_output( @@ -356,13 +355,14 @@ class RuffEvaluator(Evaluator): class MypyEvaluator(Evaluator): + def __init__(self, command: str | None = None) -> None: if command is None: self.command = "mypy . --pretty --no-error-summary --show-column-numbers" else: self.command = command - def evaluate(self, evo: Repo, **kwargs: Any) -> CIFeedback: # noqa: ARG002 + def evaluate(self, evo: Repo, **kwargs: dict) -> CIFeedback: try: out = subprocess.check_output( shlex.split(self.command), # noqa: S603 @@ -411,10 +411,12 @@ class MypyEvaluator(Evaluator): class MultiEvaluator(Evaluator): + def __init__(self, *evaluators: Evaluator) -> None: self.evaluators = evaluators - def evaluate(self, evo: Repo, **kwargs: Any) -> CIFeedback: + def evaluate(self, evo: Repo, **kwargs: dict) -> CIFeedback: + all_errors = defaultdict(list) for evaluator in self.evaluators: feedback: CIFeedback = evaluator.evaluate(evo, **kwargs) @@ -433,9 +435,10 @@ class CIEvoStr(EvolvingStrategy): 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, + **kwargs: dict, ) -> Repo: + @dataclass class CodeFixGroup: start_line: int @@ -549,7 +552,7 @@ class CIEvoStr(EvolvingStrategy): style="bright_blue", align="left", characters=".", - ) + ), ) file = evo.files[evo.project_path / Path(file_path)] @@ -630,12 +633,12 @@ class CIEvoStr(EvolvingStrategy): for i in diff: if i.startswith("+"): table.add_row( - "", Text(str(diff_new_lineno), style="green bold"), Text(i, style="green") + "", 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") + Text(str(diff_original_lineno), style="red bold"), "", Text(i, style="red"), ) diff_original_lineno += 1 elif i.startswith("?"): @@ -655,7 +658,7 @@ class CIEvoStr(EvolvingStrategy): operation = Prompt.ask( "Input your operation [ [red]([bold]s[/bold])kip[/red] / " "[green]([bold]a[/bold])pply[/green] / " - "[yellow]manual instruction[/yellow] ]" + "[yellow]manual instruction[/yellow] ]", ) print() if operation in ("s", "skip"): @@ -688,6 +691,21 @@ class CIEvoStr(EvolvingStrategy): return evo +class CIEvoAgent(EvoAgent): + def __init__(self, evolving_strategy: CIEvoStr) -> None: + super().__init__(max_loop=1, evolving_strategy=evolving_strategy) + self.evolving_trace = [] + + def multistep_evolve(self, evo: Repo, eva: Evaluator, **kwargs: Any) -> Repo: + + evo = self.evolving_strategy.evolve( + evo=evo, + evolving_trace=self.evolving_trace, + ) + + self.evolving_trace.append(EvoStep(evo, feedback=eva.evaluate(evo))) + + return evo DIR = None while DIR is None or not DIR.exists(): @@ -707,12 +725,11 @@ repo = Repo(DIR, excludes=excludes) # evaluator = MultiEvaluator(MypyEvaluator(), RuffEvaluator()) evaluator = RuffEvaluator() estr = CIEvoStr() -rag = None # RAG is not enable firstly. -ea = EvoAgent(estr, rag=rag) -ea.step_evolving(repo, evaluator) +ea = CIEvoAgent(estr) +ea.multistep_evolve(repo, evaluator) while True: print(Rule(f"Round {len(ea.evolving_trace)} repair", style="blue")) - repo: Repo = ea.step_evolving(repo, evaluator) + repo: Repo = ea.multistep_evolve(repo, evaluator) fix_records = repo.fix_records filename = f"{DIR.name}_{start_timestamp}_round_{len(ea.evolving_trace)}_fix_records.json"