From 393ce68f40e2fdf70161de5009d201aa7976e822 Mon Sep 17 00:00:00 2001 From: Ilia Manolov Date: Fri, 20 Jun 2025 17:53:09 +0100 Subject: [PATCH 1/4] WIP forge build automation --- common/config/global_config.yaml | 6 +- hackbot/src/pocgen/build_agent.py | 116 +++++++++++++++++++ hackbot/src/pocgen/repo.py | 16 ++- hackbot/src/pocgen/runtime_environment.py | 3 +- hackbot/tests/pocgen/test_repo.py | 26 +++++ hackbot/utils/dspy/dspy_inference.py | 7 +- hackbot/utils/dspy/tools.py | 129 +++++++++++++++++++--- requirements-dev.lock | 4 +- requirements.lock | 4 +- 9 files changed, 282 insertions(+), 29 deletions(-) create mode 100644 hackbot/src/pocgen/build_agent.py create mode 100644 hackbot/tests/pocgen/test_repo.py diff --git a/common/config/global_config.yaml b/common/config/global_config.yaml index 25d7a3c6..fa1e20b5 100644 --- a/common/config/global_config.yaml +++ b/common/config/global_config.yaml @@ -187,7 +187,7 @@ docker: # Cache configs cache: scope_cache_enabled: true - llm_cache_enabled: true + llm_cache_enabled: false uac_get_bugs_cache_enabled: false # Whether to cache the entire get_bugs function results selective_disable_cache: [] # Currently not working consistently, so disable # Split into llm_cache and git_cache @@ -233,3 +233,7 @@ scope: # Enforce filtering regardless of readme classification. WIP force_filter_libs: false force_filter_interfaces: false + +build_agent: + enabled: false + approved_commands: ["forge", "npm", "solc", "foundryup", "pnpm", "yarn", "git submodule", "nvm"] diff --git a/hackbot/src/pocgen/build_agent.py b/hackbot/src/pocgen/build_agent.py new file mode 100644 index 00000000..8178b744 --- /dev/null +++ b/hackbot/src/pocgen/build_agent.py @@ -0,0 +1,116 @@ +from hackbot.src.pocgen.runtime_environment import RuntimeEnvironment, ExecutionResults +from typing import Sequence +from hackbot.utils.dspy.tools import enrich_tools, dspy_tool, async_execute_command_tool, async_common_tools +from common.config import global_config + +import dspy +from utils.dspy.dspy_inference import DSPYInference + + +@dspy_tool(runtime_env=RuntimeEnvironment, approved_commands=Sequence[str]) +async def final_check_before_completion(*, runtime_env: RuntimeEnvironment, approved_commands: Sequence[str]) -> str: + """Always run this tool before finishing. This tool will tell you whether you are done or not.""" + print("Trying to finish up...") + + result1 = await async_execute_command_tool( + "forge build", runtime_env=runtime_env, approved_commands=approved_commands + ) + if result1.startswith("Error"): + return ( + "Failure: Forge build failed! Please continue getting the repo to build. Here is your output: " + + result1 + ) + + print("Forge build passed!") + + return "Success: Forge build passed!" + + +class RunCommands(dspy.Signature): + """ + Run commands in the given directory. + + IMPORTANT: Your response MUST strictly follow the format below. Each field, including the headers, must be on a new line. + + Example: + [[ ## next_thought ## ]] + I need to see what files are in the current directory. + [[ ## next_tool_name ## ]] + async_list_files_tool + [[ ## next_tool_args ## ]] + {"relative_path": "."} + """ + + high_level_instructions: str = dspy.InputField() # type: ignore # noqa + forge_build_runs: bool = dspy.OutputField() # type: ignore # noqa + forge_test_runs: bool = dspy.OutputField() # type: ignore # noqa + shell_commands_ran: list[str] = dspy.OutputField() # type: ignore # noqa + + +async def compile_codebase(runtime_env: RuntimeEnvironment) -> ExecutionResults: + """Compile the codebase using a dspy-based LLM agent.""" + + approved_commands = global_config.build_agent.approved_commands + + await runtime_env.execute_command(["git", "reset", "--hard"]) + await runtime_env.execute_command(["git", "clean", "-ffdx"]) + + # All dspy inference and tool execution must happen within this block + with enrich_tools(runtime_env=runtime_env, approved_commands=approved_commands): + inf_module = DSPYInference( + pred_signature=RunCommands, + tools=[ + *async_common_tools, + final_check_before_completion, + ], + max_iters=50, + ) + + result = await inf_module.run( + high_level_instructions=f""" + You are a professional smart contract security engineer. + Your current directory is inside of a git repository of a Foundry Forge Solidity project. + Your job is to build the project and run the tests. + You are allowed to read through files and folders in the project to understand it. + You can additionally only run the following commands at the root of the project: + {', '.join(approved_commands)} + You will not combine commands with && or ||. + You will always try to use the non-interactive or json versions of the commands. This is because the output is passed back to you in the same format. + Sometimes repos fail to give instructions on how to set up dependencies. In this case you should figure out how to pull them from one of the supplied commands. + For example, if you find out that forge-std doesn't exist, you can pull it in via `forge install foundry-rs/forge-std`. + Please be mindful of required versions and stick to them. + You should run a command with `--help` if you are unsure about its capabilities and don't have any ideas of how to proceed. + When supplying any relative path to a tool you will never use `..`. This is forbidden and will cause the tool to fail. + You are not allowed to move freely using `cd`. + You can additionally modify any file in the project that you have already read. + Your goal is to run `forge build` successfully and to run `forge test` (which must complete but may have failing tests so it may not necessarily "succeed"). + Keep in mind that the `forge soldeer` subcommand exists and is the dependency manager for some projects. You should expect to see a `soldeer.lock` in these cases. + You will not give up until these commands run successfully. + You will never run any commands that open ports or a shell. + You only have one shot at this so make sure to do it right. + You should start by finding and opening the readme. Then, you should see what other files and folders are in the project. + Your response will always finish with a call to `final_check_before_completion` that returns a success. + You will not execute any further commands after you have verified that `forge build` and `forge test` work. + """ + ) + print(result.shell_commands_ran) + + if not result.forge_build_runs: + return ExecutionResults( + stdout=",".join(result.shell_commands_ran), + stderr="Build agent failed to build the repository.", + exit_code=1, + cmd_full="build_agent" + ) + + return ExecutionResults( + stdout=",".join(result.shell_commands_ran), + stderr="", + exit_code=0, + cmd_full="build_agent" + ) + + + + + \ No newline at end of file diff --git a/hackbot/src/pocgen/repo.py b/hackbot/src/pocgen/repo.py index 072cf479..2116cf2e 100644 --- a/hackbot/src/pocgen/repo.py +++ b/hackbot/src/pocgen/repo.py @@ -19,6 +19,7 @@ from hackbot.src.pocgen.utils import BugProcessingEnvInfo from hackbot.utils.llm import Spinner from common.utils.logging_utils import logger as log +from hackbot.src.pocgen.build_agent import compile_codebase as agentic_compile @dataclass @@ -539,11 +540,16 @@ async def deps_and_compile(self) -> ExecutionResults: Returns: ExecutionResults from the compilation """ - # Install dependencies first - await self.install_dependencies(run_npm_install=True) - - # Then compile - return await self.compile_codebase() + if global_config.build_agent.enabled: + log.info("Using the build agent to compile the codebase.") + return await agentic_compile(self.runtime_env) + else: + log.info("Using the classic deterministic method to compile the codebase.") + # Install dependencies first + await self.install_dependencies(run_npm_install=True) + + # Then compile + return await self.compile_codebase() async def copy_to(self, target_dir: Path) -> None: """Copy the current runtime environment to a target directory (on real disk).""" diff --git a/hackbot/src/pocgen/runtime_environment.py b/hackbot/src/pocgen/runtime_environment.py index 34531c05..89a10411 100644 --- a/hackbot/src/pocgen/runtime_environment.py +++ b/hackbot/src/pocgen/runtime_environment.py @@ -3,7 +3,7 @@ import subprocess from dataclasses import dataclass from pathlib import Path -from typing import Any, ContextManager, Protocol +from typing import Any, ContextManager, Protocol, runtime_checkable from common.utils import tempfiles from hackbot.utils.docker.docker_runtime import DockerWrapper @@ -22,6 +22,7 @@ class ExecutionResults: cmd_full: str # noqa +@runtime_checkable class RuntimeEnvironment(Protocol): """Protocol defining the interface for runtime environments.""" diff --git a/hackbot/tests/pocgen/test_repo.py b/hackbot/tests/pocgen/test_repo.py new file mode 100644 index 00000000..cbb95afb --- /dev/null +++ b/hackbot/tests/pocgen/test_repo.py @@ -0,0 +1,26 @@ +import pytest +# import asyncio +from hackbot.src.pocgen.repo import Repository +from hackbot.tests.test_template import slow_and_nondeterministic_test, TestTemplate +# from hackbot.src.pocgen.runtime_environment import LocalEnvironment +from common.utils import tempfiles +from hackbot.utils.git.clone import clone_repository + +class TestRepo(TestTemplate): + @pytest.fixture(autouse=True) + def setup_method(self, setup): + pass + + @pytest.mark.asyncio + @pytest.mark.parametrize("repo_url", [ + "https://github.com/code-423n4/2022-04-backed" + ]) + @slow_and_nondeterministic_test + async def test_deps_and_compile_with_agent(self, repo_url: str): + with tempfiles.TemporaryDirectory() as temp_dir: + clone_repository(repo_url, target_dir=temp_dir) + + async with Repository(from_dir=temp_dir) as repo: + result = await repo.deps_and_compile() + + assert result.exit_code == 0, f"Compilation failed with exit code {result.exit_code}\\nSTDOUT:\\n{result.stdout}\\nSTDERR:\\n{result.stderr}" \ No newline at end of file diff --git a/hackbot/utils/dspy/dspy_inference.py b/hackbot/utils/dspy/dspy_inference.py index 02f095b3..df5c3dba 100644 --- a/hackbot/utils/dspy/dspy_inference.py +++ b/hackbot/utils/dspy/dspy_inference.py @@ -111,9 +111,6 @@ def __init__( ) else: self.inference_module = dspy.Predict(pred_signature) - self.inference_module_async: Callable[..., Awaitable[Any]] = dspy_asyncify( - self.inference_module - ) @observe() @retry( @@ -134,9 +131,9 @@ async def run( try: if self.callback: with dspy.context(lm=self.lm, callbacks=[self.callback]): - result = await self.inference_module_async(**kwargs, lm=self.lm) + result = await self.inference_module.acall(**kwargs, lm=self.lm) else: - result = await self.inference_module_async(**kwargs, lm=self.lm) + result = await self.inference_module.acall(**kwargs, lm=self.lm) return result except Exception as e: log.error(f"Error in run: {str(e)}") diff --git a/hackbot/utils/dspy/tools.py b/hackbot/utils/dspy/tools.py index 54cc49eb..4edecc4a 100644 --- a/hackbot/utils/dspy/tools.py +++ b/hackbot/utils/dspy/tools.py @@ -6,6 +6,9 @@ import subprocess import inspect import functools +from typing import get_origin + +from hackbot.src.pocgen.runtime_environment import RuntimeEnvironment ValueT = TypeVar("ValueT") FuncT = TypeVar("FuncT", bound=Callable[..., Any]) @@ -33,7 +36,9 @@ def get(self, key: str, expected_type: Type[ValueT], default: ValueT | None = No if value is None: raise KeyError(f"Key '{key}' not found in context and no default provided") - if not isinstance(value, expected_type): + # Use get_origin to handle generic types like Sequence[str] + origin_type = get_origin(expected_type) or expected_type + if not isinstance(value, origin_type): raise ValueError( f"Value for key '{key}' must be of type {expected_type.__name__}, " f"got {type(value).__name__} instead" @@ -74,17 +79,32 @@ def decorator(func: FuncT) -> FuncT: ] new_sig = original_sig.replace(parameters=new_params) - @functools.wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Any: - context = tool_context.get() - injected_kwargs: dict[str, Any] = {} - - for key, expected_type in requirements.items(): - injected_kwargs[key] = context.get(key, expected_type) - - final_kwargs = {**kwargs, **injected_kwargs} - - return func(*args, **final_kwargs) + if inspect.iscoroutinefunction(func): + @functools.wraps(func) + async def async_wrapper(*args: Any, **kwargs: Any) -> Any: + context = tool_context.get() + injected_kwargs: dict[str, Any] = {} + + for key, expected_type in requirements.items(): + injected_kwargs[key] = context.get(key, expected_type) + + final_kwargs = {**kwargs, **injected_kwargs} + + return await func(*args, **final_kwargs) + wrapper = async_wrapper + else: + @functools.wraps(func) + def sync_wrapper(*args: Any, **kwargs: Any) -> Any: + context = tool_context.get() + injected_kwargs: dict[str, Any] = {} + + for key, expected_type in requirements.items(): + injected_kwargs[key] = context.get(key, expected_type) + + final_kwargs = {**kwargs, **injected_kwargs} + + return func(*args, **final_kwargs) + wrapper = sync_wrapper # This is the magic: we override the signature that dspy will inspect. wrapper.__signature__ = new_sig # type: ignore # noqa @@ -253,4 +273,87 @@ def prompt_modify_file_tool( return "Success: File modified!" -common_tools = [list_files_tool, list_folders_tool, read_file_tool, execute_command_tool, prompt_modify_file_tool] \ No newline at end of file +common_tools = [list_files_tool, list_folders_tool, read_file_tool, execute_command_tool, prompt_modify_file_tool] + + +# --- ASYNC TOOLS --- + +@dspy_tool(runtime_env=RuntimeEnvironment) +async def async_list_files_tool(relative_path: str | None = None, *, runtime_env: RuntimeEnvironment) -> str: + """List all files in the given relative path. Does not recurse.""" + if relative_path is None: + relative_path = "." + if ".." in relative_path: + return "Restricted: Tried to access parent directory!" + + cmd = ['find', '.', '-maxdepth', '1', '-type', 'f', '-exec', 'basename', '{}', ';'] + exec_results = await runtime_env.execute_command(cmd, cwd=Path(relative_path) if relative_path else None) + + if exec_results.exit_code != 0: + return f"Error listing files: {exec_results.stderr}" + + return exec_results.stdout.strip() + +@dspy_tool(runtime_env=RuntimeEnvironment) +async def async_list_folders_tool(relative_path: str | None = None, *, runtime_env: RuntimeEnvironment) -> str: + """List all folders in the given relative path. Does not recurse.""" + if relative_path is None: + relative_path = "." + if ".." in relative_path: + return "Restricted: Tried to access parent directory!" + + cmd = ['find', '.', '-maxdepth', '1', '-type', 'd', '-exec', 'basename', '{}', ';'] + exec_results = await runtime_env.execute_command(cmd, cwd=Path(relative_path) if relative_path else None) + + if exec_results.exit_code != 0: + return f"Error listing folders: {exec_results.stderr}" + + # Filter out '.' from the result + folders = [f for f in exec_results.stdout.strip().split('\n') if f and f != '.'] + return "\n".join(folders) + +@dspy_tool(runtime_env=RuntimeEnvironment) +async def async_read_file_tool(relative_path: str, *, runtime_env: RuntimeEnvironment) -> str: + """Read the contents of the given file.""" + if ".." in relative_path: + return "Restricted: Tried to access parent directory!" + + path = Path(relative_path) + if not await runtime_env.path_exists(path): + return f"Error: File {relative_path} does not exist!" + + return await runtime_env.read_file(path) + +@dspy_tool(runtime_env=RuntimeEnvironment) +async def async_write_file_tool(relative_path: str, new_content: str, *, runtime_env: RuntimeEnvironment) -> str: + """Create a new file or overwrite an existing file with the given new content.""" + if ".." in relative_path: + return "Restricted: Tried to access parent directory!" + + await runtime_env.write_file(Path(relative_path), new_content) + return "Success: File modified!" + + +@dspy_tool(runtime_env=RuntimeEnvironment, approved_commands=Sequence[str]) +async def async_execute_command_tool( + command: str, relative_path: str = ".", accept_nonzero_return_code: bool = False, *, runtime_env: RuntimeEnvironment, approved_commands: Sequence[str] +) -> str: + """Execute the given command inside of the given relative path.""" + if not any(command.startswith(cmd) for cmd in approved_commands): + return "Error: Command must start with one of the following: " + ", ".join(approved_commands) + + if ".." in relative_path: + return "Restricted: Tried to access parent directory!" + + exec_results = await runtime_env.execute_command(command.split(" "), cwd=Path(relative_path) if relative_path else None) + + output = f"Exit Code: {exec_results.exit_code}\n" + output += f"Stdout:\n{exec_results.stdout}\n" + output += f"Stderr:\n{exec_results.stderr}\n" + + if not accept_nonzero_return_code and exec_results.exit_code != 0: + return "Error executing command:\n" + output + + return "Success:\n" + output + +async_common_tools = [async_list_files_tool, async_list_folders_tool, async_read_file_tool, async_execute_command_tool, async_write_file_tool] \ No newline at end of file diff --git a/requirements-dev.lock b/requirements-dev.lock index cc597ef6..aff9f2da 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,7 +10,7 @@ # universal: false -e file:. -agentql==1.11.1 +agentql==1.11.2 # via archimedes-prototype aiofile==3.9.0 # via aiopath @@ -806,7 +806,7 @@ tenacity==9.0.0 # via langchain-core termcolor==2.5.0 # via archimedes-prototype -tf-playwright-stealth==1.1.2 +tf-playwright-stealth==1.2.0 # via agentql tiktoken==0.9.0 # via archimedes-prototype diff --git a/requirements.lock b/requirements.lock index b7005926..b0ac5005 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,7 +10,7 @@ # universal: false -e file:. -agentql==1.11.1 +agentql==1.11.2 # via archimedes-prototype aiofile==3.9.0 # via aiopath @@ -805,7 +805,7 @@ tenacity==9.0.0 # via langchain-core termcolor==2.5.0 # via archimedes-prototype -tf-playwright-stealth==1.1.2 +tf-playwright-stealth==1.2.0 # via agentql tiktoken==0.9.0 # via archimedes-prototype From 2d9315de9062f6b54406bd61466807c9bc864ff9 Mon Sep 17 00:00:00 2001 From: Ilia Manolov Date: Thu, 10 Jul 2025 15:21:16 +0100 Subject: [PATCH 2/4] Make pyright happy --- hackbot/utils/dspy/demo.py | 7 ++++-- hackbot/utils/dspy/dspy_inference.py | 6 +++--- pyproject.toml | 1 + requirements-dev.lock | 32 ++++++++++++++++++++++++++++ typings/dspy/predict/predict.pyi | 4 +++- typings/dspy/predict/react.pyi | 6 +++++- 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/hackbot/utils/dspy/demo.py b/hackbot/utils/dspy/demo.py index 23871839..9a05da4a 100644 --- a/hackbot/utils/dspy/demo.py +++ b/hackbot/utils/dspy/demo.py @@ -3,6 +3,7 @@ import sys from typing import Sequence from hackbot.utils.dspy.tools import common_tools, execute_command_tool, enrich_tools, dspy_tool +from hackbot.utils.dspy.dspy_structs import dspy_to_llm_struct from pathlib import Path import dspy @@ -80,7 +81,7 @@ class RunCommands(dspy.Signature): max_iters=50, ) - result = asyncio.run( + raw_result: dspy.Prediction = asyncio.run( inf_module.run( high_level_instructions=f""" You are a professional smart contract security engineer. @@ -109,4 +110,6 @@ class RunCommands(dspy.Signature): """ ) ) - print(result.shell_command_ran) + result = dspy_to_llm_struct(raw_result, RunCommands) + shell_command_ran = result.shell_command_ran + print(shell_command_ran) diff --git a/hackbot/utils/dspy/dspy_inference.py b/hackbot/utils/dspy/dspy_inference.py index eb3698d8..75ce6e43 100644 --- a/hackbot/utils/dspy/dspy_inference.py +++ b/hackbot/utils/dspy/dspy_inference.py @@ -1,6 +1,5 @@ -from typing import Callable, Awaitable, Any, get_origin +from typing import Callable, Any, get_origin import dspy -from dspy.utils.asyncify import asyncify as dspy_asyncify from langfuse.decorators import observe from litellm.exceptions import ServiceUnavailableError from loguru import logger as log @@ -127,8 +126,9 @@ def __init__( async def run( self, **kwargs: Any, - ) -> Any: + ) -> dspy.Prediction: try: + result: dspy.Prediction if self.callback: with dspy.context(lm=self.lm, callbacks=[self.callback]): result = await self.inference_module.acall(**kwargs, lm=self.lm) diff --git a/pyproject.toml b/pyproject.toml index 19edc4d0..d0126f40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,6 +91,7 @@ dev-dependencies = [ "pytest>=8.3.2", "black", "hanging-threads>=2.0.7", + "ipython>=9.4.0", ] excluded-dependencies = [ "hackbot @ file:///Users/hugo_dev/hackbot/Archimedes_Prototype/../hackbot", diff --git a/requirements-dev.lock b/requirements-dev.lock index a1e2e08f..368c1aa6 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -51,6 +51,8 @@ anyio==4.9.0 asgiref==3.8.1 # via archimedes-prototype # via opentelemetry-instrumentation-asgi +asttokens==3.0.0 + # via stack-data async-timeout==5.0.1 # via scrapegraphai asyncer==0.0.8 @@ -143,6 +145,8 @@ dataclasses-json==0.6.7 # via langchain-community datasets==3.4.1 # via dspy +decorator==5.2.1 + # via ipython deprecated==1.2.18 # via limits # via opentelemetry-api @@ -197,6 +201,8 @@ eth-utils==5.2.0 # via rlp execnet==2.1.1 # via pytest-xdist +executing==2.2.0 + # via stack-data fake-http-header==0.3.5 # via tf-playwright-stealth fastapi==0.115.11 @@ -318,11 +324,16 @@ importlib-resources==6.5.2 # via chromadb iniconfig==2.0.0 # via pytest +ipython==9.4.0 +ipython-pygments-lexers==1.1.1 + # via ipython itsdangerous==2.2.0 # via flask # via quart javascript==1!1.2.2 # via archimedes-prototype +jedi==0.19.2 + # via ipython jinja2==3.1.6 # via archimedes-prototype # via flask @@ -422,6 +433,8 @@ marshmallow==3.26.1 # via dataclasses-json matplotlib==3.10.1 # via seaborn +matplotlib-inline==0.1.7 + # via ipython mdurl==0.1.2 # via markdown-it-py minify-html==0.15.0 @@ -558,8 +571,12 @@ parameterized==0.9.0 # via solidity-parser parsimonious==0.10.0 # via eth-abi +parso==0.8.4 + # via jedi pathspec==0.12.1 # via black +pexpect==4.9.0 + # via ipython pillow==11.1.0 # via matplotlib platformdirs==4.3.6 @@ -577,6 +594,8 @@ primp==0.14.0 # via duckduckgo-search priority==2.0.0 # via hypercorn +prompt-toolkit==3.0.51 + # via ipython propcache==0.3.0 # via aiohttp # via yarl @@ -589,6 +608,10 @@ psutil==7.0.0 # via flufl-lock psycopg2-binary==2.9.10 # via archimedes-prototype +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data py-solc-x==2.0.3 # via archimedes-prototype pyarrow==19.0.1 @@ -636,6 +659,8 @@ pyee==12.1.1 pygithub==2.6.1 # via archimedes-prototype pygments==2.19.1 + # via ipython + # via ipython-pygments-lexers # via mpire # via rich pyjwt==2.10.1 @@ -792,6 +817,8 @@ sqlalchemy==2.0.41 # via sentry-sdk sqlalchemy-stubs==0.4 # via archimedes-prototype +stack-data==0.6.3 + # via ipython starlette==0.46.1 # via fastapi stripe==11.6.0 @@ -836,6 +863,9 @@ tqdm==4.67.1 # via optuna # via scrapegraphai # via semchunk +traitlets==5.14.3 + # via ipython + # via matplotlib-inline typer==0.15.2 # via agentql # via chromadb @@ -889,6 +919,8 @@ vulture==2.14 # via archimedes-prototype watchfiles==1.0.4 # via uvicorn +wcwidth==0.2.13 + # via prompt-toolkit websocket-client==1.8.0 # via kubernetes websockets==15.0.1 diff --git a/typings/dspy/predict/predict.pyi b/typings/dspy/predict/predict.pyi index d9c9038b..f8ae232e 100644 --- a/typings/dspy/predict/predict.pyi +++ b/typings/dspy/predict/predict.pyi @@ -2,8 +2,10 @@ This type stub file was generated by pyright. """ +from typing import Any, Coroutine from dspy.predict.parameter import Parameter from dspy.primitives.program import Module +from dspy.primitives.prediction import Prediction """ This type stub file was generated by pyright. @@ -33,7 +35,7 @@ class Predict(Module, Parameter): def __call__(self, *args, **kwargs): ... - async def acall(self, *args, **kwargs): + async def acall(self, *args: Any, **kwargs: Any) -> Prediction: ... def forward(self, **kwargs): diff --git a/typings/dspy/predict/react.pyi b/typings/dspy/predict/react.pyi index 23135101..8534a6ec 100644 --- a/typings/dspy/predict/react.pyi +++ b/typings/dspy/predict/react.pyi @@ -2,8 +2,9 @@ This type stub file was generated by pyright. """ -from typing import Callable +from typing import Callable, Any, Coroutine from dspy.primitives.program import Module +from dspy.primitives.prediction import Prediction """ This type stub file was generated by pyright. @@ -28,6 +29,9 @@ class ReAct(Module): Users can override this method to implement their own truncation logic. """ ... + + async def acall(self, *args: Any, **kwargs: Any) -> Prediction: + ... From 0b4b3d7e2eaa02b9c022097f000e9422e15ec999 Mon Sep 17 00:00:00 2001 From: Ilia Manolov Date: Thu, 10 Jul 2025 16:15:16 +0100 Subject: [PATCH 3/4] Fix wrong level of import. Dunno how it breaks invariant tests but this oughtta fix it --- hackbot/src/pocgen/build_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hackbot/src/pocgen/build_agent.py b/hackbot/src/pocgen/build_agent.py index 8178b744..f10d20a1 100644 --- a/hackbot/src/pocgen/build_agent.py +++ b/hackbot/src/pocgen/build_agent.py @@ -4,7 +4,7 @@ from common.config import global_config import dspy -from utils.dspy.dspy_inference import DSPYInference +from hackbot.utils.dspy.dspy_inference import DSPYInference @dspy_tool(runtime_env=RuntimeEnvironment, approved_commands=Sequence[str]) From a179bd43c86af456e2c1b7aba73303dcca0030e0 Mon Sep 17 00:00:00 2001 From: Ilia Manolov Date: Thu, 10 Jul 2025 16:17:15 +0100 Subject: [PATCH 4/4] Reenable llm_cache --- common/config/global_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/config/global_config.yaml b/common/config/global_config.yaml index 20402a43..d43b8dc2 100644 --- a/common/config/global_config.yaml +++ b/common/config/global_config.yaml @@ -187,7 +187,7 @@ docker: # Cache configs cache: scope_cache_enabled: true - llm_cache_enabled: false + llm_cache_enabled: true uac_get_bugs_cache_enabled: false # Whether to cache the entire get_bugs function results selective_disable_cache: [] # Currently not working consistently, so disable # Split into llm_cache and git_cache