princeton-nlp / SWE-agent

[NeurIPS 2024] SWE-agent takes a GitHub issue and tries to automatically fix it, using GPT-4, or your LM of choice. It can also be employed for offensive cybersecurity or competitive coding challenges.
https://swe-agent.com
MIT License
13.67k stars 1.38k forks source link

AssertionError: `self.env.record` is None after task initiation in Gradio interface #699

Closed SM-Kumail-Rizvi closed 3 months ago

SM-Kumail-Rizvi commented 3 months ago

Describe the bug

While attempting to run an inference task using a Gradio interface on the SWE Agent, I encountered an AssertionError. The error occurs right after initiating the task, where self.env.record is expected to be populated with data but remains None. This prevents the process from moving forward, leading to an environment shutdown.

Steps/commands/code to Reproduce

1. Start the Gradio interface using the command: python3.11 run_gradio.py. 2. Input the following JSON task in the Gradio interface, which is designed to set up server-side login validation:

JSON Task:

[
  {
    "issue": {
      "type": "feature request",
      "description": "Implement Server-Side Login Validation",
      "details": {
        "KnowledgeGraph": [
          "Server->authenticates->User",
          "Server->validates->Credentials",
          "Server->queries->Firestore"
        ],
        "CodeBase": {
          "Type": "Firestore Cloud Function",
          "Language": "Typescript"
        },
        ...
      },
      "version": 34,
      "repo": "Sleep-Well-Dev/Sleep-AI",
      "localBranch": "feature/feature-branch",
      "base_commit": "a1b2c3d4e5", 
      "problem_statement": "Implement secure server-side login validation using Firestore.",
      "instance_id": "1"
    }
  }
]

3. Submit the task for processing to see how its running on UbuntU WSL.

This is run.py file, run function where the error occurs (the line is bold below):

def run(self, index):
        print(f"ℹ️ Starting run method for index {index}")

        # Reset environment
        instance_id = self.env.data[index]["instance_id"]
        print(f"ℹ️ Instance ID: {instance_id}")  

        for hook in self.hooks:
            hook.on_instance_start(index=index, instance=self.env.data[index])

        assert isinstance(instance_id, str)

        if self.should_skip(instance_id):
            for hook in self.hooks:
                hook.on_instance_skipped()
            print("⚠️ Skipping instance based on should_skip check")
            raise _ContinueLoop

        print(f"▶️ Beginning task {index}")
        logger.info("▶️  Beginning task " + str(index))

        # Reset the environment and get initial observation and info
        observation, info = self.env.reset(index)
        print(f"ℹ️ Reset environment. Observation: {observation}, Info: {info}")

        if info is None:
            print("⚠️ Info is None after reset. Skipping instance.")
            raise _ContinueLoop

        # Get additional information from the environment
        issue = getattr(self.env, "query", None)
        files = []

        print(f"ℹ️ self.env.record (before assertion): {self.env.record}")
        **assert self.env.record is not None, f"self.env.record is None at index {index}. Info: {info}, Observation: {observation}"**
        print(f"ℹ️ self.env.record: {self.env.record}")

        # Process 'patch' information if available
        if "patch" in self.env.record:
            files = "\n".join([f"- {x.path}" for x in PatchSet(self.env.record["patch"]).modified_files])

        # Process 'test_patch' information if available
        test_files = []
        if "test_patch" in self.env.record:
            test_patch_obj = PatchSet(self.env.record["test_patch"])
            test_files = "\n".join([f"- {x.path}" for x in test_patch_obj.modified_files + test_patch_obj.added_files])

        # Process 'FAIL_TO_PASS' tests information if available
        tests = ""
        if "FAIL_TO_PASS" in self.env.record:
            tests = "\n".join([f"- {x}" for x in self.env.record["FAIL_TO_PASS"]])

        setup_args = {"issue": issue, "files": files, "test_files": test_files, "tests": tests}
        print(f"ℹ️ Setup args: {setup_args}")

        # Run agent with setup arguments and environment
        info, trajectory = self.agent.run(
            setup_args=setup_args,
            env=self.env,
            observation=observation,
            traj_dir=self.traj_dir,
            return_type="info_trajectory",
        )

        # Save predictions based on instance_id
        self._save_predictions(instance_id, info)

        # Notify hooks of instance completion
        for hook in self.hooks:
            hook.on_instance_completed(info=info, trajectory=trajectory)

        print("✅ Task completed successfully")
        logger.info("✅ Task completed successfully")

This is the current swe_env.py file class SWEEnv(gym.Env) along with reset() function, where I have suggested to done possible changes by ChatGPT-4, but still error persists

class SWEEnv(gym.Env):
    """Gym environment for SWE-bench. This class should handle all communication with the docker container."""
    name = "swe_main"
    cached_image_prefix = "swe-agent-task-env-"

    def __init__(self, args: EnvironmentArguments):
        super().__init__()
        t0 = time.perf_counter()
        self.args = args
        self.base_commit: str | None = None
        self.communicate_output: str | None = None
        self.container_name: str | None = args.container_name
        self.install_environment = args.install_environment
        self.logger = get_logger("SWEEnv")
        self.persistent = args.container_name is not None
        self.returncode: None | int = None
        if not self.args.verbose:
            self.logger.disabled = True

        #: The commit hash of the swe-agent repository
        self.commit_sha = None
        try:
            repo = Repo(REPO_ROOT, search_parent_directories=True)
            self.commit_sha = repo.head.object.hexsha
        except KeyboardInterrupt:
            raise
        except Exception as e:
            self.logger.exception("Failed to get commit hash for this repo: %s", str(e))

        print("\n I AM HITTING BITBUCKET TOKEN")
        self._bitbucket_token: str = keys_config.get("BITBUCKET_TOKEN", "")  # Ensure you have this in your config

        # Load Task Instances
        self.data_path = self.args.data_path
        self.data = get_instances(
            self.data_path,
            self.args.base_commit,
            self.args.split,
            token=self._bitbucket_token,
            repo_path=self.args.repo_path,
        )

        ######################################################################################
        if not self.data:
            raise ValueError("No data loaded. Please check the data source and path.")
        print(f"Data loaded: {self.data}")
        ######################################################################################

        #: Instance we're currently processing. Gets set in self.reset.
        self.record: dict[str, Any] | None = None
        self.logger.info(f"💽 Loaded dataset from {self.data_path}")

        # Establish connection with execution container
        self.image_name = args.image_name
        self.container_obj: docker.models.containers.Container | None = None
        self.container: subprocess.Popen | None = None
        self._reset_container()

        self.idx = 0
        self.clean_multi_line_functions = lambda x: x
        self.hooks: list[EnvHook] = []

        self.logger.debug("Environment initialization took %.2f seconds", time.perf_counter() - t0)

reset function in SWEEnv class:

def reset(self, index: int | None = None, apply_test_patch: bool = False) -> tuple[str | None, dict]:
        """
        Function to reset container between each task instance.

        * Clones instance's repository
        * Cleans repository of prior modifications
        * Resets environment variables
        * Check out base commit

        Args:
            index: index of task instance to reset to

        Returns:
            observation: output from container
            info: additional information (e.g. debugging information)
        """
        info = {}
        info["commit_sha"] = self.commit_sha

        # Get task instance
        self.idx = index if index is not None else self.idx
        self.record = self.data[self.idx]
        self.idx += 1

        # Set query, gold command
        self.base_commit = self.record["base_commit"]
        self.query = self.record["problem_statement"]
        self.reward = None

        ### Reset Container ###

        if self.args.cache_task_images:
            cached_image = self._get_cached_task_image_name()
            if image_exists(cached_image):
                self.logger.info(f"Restore environment from cached image {cached_image}")
                self.close()  # stop current container
                self._init_container(cached_image=cached_image)
                self.communicate("export $(xargs </.env)")
                envs = self.communicate("env")
                self.logger.debug(f"Environment variables restored from the image:\n{envs}\n")
                if apply_test_patch:
                    self._apply_test_patch()
                return None, info
            else:
                self.logger.info(f"Cached image {cached_image} not found, rebuilding task environment...")

        # Clone repository if not already cloned
        self.communicate(input="cd /")
        folders = self.communicate(input="ls").split("\n")
        if self._repo_name not in folders:
            self._copy_repo()

        # Clean repository of any modifications + Checkout base commit
        for cmd in [
            "echo -n > /root/files_to_edit.txt",
            f"cd {self._repo_name}",
            "export ROOT=$(pwd -P)",
            "git status",
            "git restore .",
            f"git reset --hard {self.base_commit}",
            "git clean -fdxq",
        ]:
            self.communicate_with_handling(
                input=cmd,
                error_msg="Failed to clean repository",
            )

        # Reset environment variables
        for cmd in [
            'export CURRENT_FILE=""',
            "export CURRENT_LINE=0",
            "export SEARCH_RESULTS=()",
            "export SEARCH_FILES=()",
            "export SEARCH_INDEX=0",
        ]:
            self.communicate_with_handling(
                input=cmd,
                error_msg="Failed to reset environment variables",
            )

        # Set up environment
        self.communicate_with_handling(
            "source /root/miniconda3/etc/profile.d/conda.sh",
            error_msg="Failed to source conda",
        )

        system = self.communicate("uname -s").strip().lower()
        arch = self.communicate("uname -m").strip().lower()
        if system == "linux" and arch == "x86_64":
            self.communicate_with_handling(
                "apt update; apt install build-essential -y",
                error_msg="Failed to install build-essential",
                timeout_duration=LONG_TIMEOUT,
            )

        # Call install environment helper function if specified
        if self.install_environment:
            self.install_env()
        # Install mypy for linting purposes
        self.communicate_with_handling("pip install flake8", error_msg="Failed to install flake8 (lint library)")

        if self.args.cache_task_images:
            envs = self.communicate("env")
            self.logger.debug(f"Environment variables to save:\n{envs}\n")
            self.communicate("env >> /.env")
            assert self.container_obj is not None  # mypy
            self.container_obj.commit(cached_image)
            self.logger.info(f"Container with environment {self.container_obj.id} cached as image {cached_image}")

        if apply_test_patch:
            self._apply_test_patch()
        # Write any metadata to info if necessary
        return None, info

,

Error message/results

ℹ️ Starting run method for index 0
ℹ️ Instance ID: 1
▶️ Beginning task 0
INFO     ▶️  Beginning task 0
ℹ️ Reset environment. Observation: None, Info: {'instance_id': '1a92d4830c765392457eac7a2ee276474a8aae414243b270ea3f3e32a98ee9d1', 'query': '[{\n    "issue": {\n      "type": "feature request",\n      "description": "Implement Server-Side Login Validation",\n      ... }]
ℹ️ self.env.record (before assertion): None
**Traceback (most recent call last):
  File "/home/kumail/SWE-agent2/run.py", line 375, in main
    self.run(index)
  File "/home/kumail/SWE-agent2/run.py", line 329, in run
    assert self.env.record is not None, f"self.env.record is None at index {index}. Info: {info}, Observation: {observation}"
AssertionError: self.env.record is None at index 0. Info: {'instance_id': '1a92d4830c765392457eac7a2ee276474a8aae414243b270ea3f3e32a98ee9d1', 'query': '[{\n    "issue": {\n      "type": "feature request",\n      "description": "Implement Server-Side Login Validation",\n      ... }]'}, Observation: None
WARNING  ❌ Failed on unknown instance
INFO     Beginning environment shutdown...**
INFO     Agent container stopped
INFO     Found image sweagent/swe-agent:latest with tags: ['sweagent/swe-agent:latest'], created: 2024-07-02T00:13:54.971234754Z for linux amd64.
DEBUG    Starting container with command: docker run -i --rm --name sweagent-swe-agent-latest-ebe823fa1c sweagent/swe-agent:latest /bin/bash -l
INFO     🌱 Environment Initialized

System Information

Checklist

klieret commented 3 months ago

Hi @SM-Kumail-Rizvi. Thank you for writing all of this up, I'm not sure if I can help you with this, as this is probably has to do with your gradio app. As you write, env.record should be set in env.reset as self.record = self.data[self.idx]. If this line runs before your check, it sounds like self.data is filled with None values, so you'd have to check how that list is populated in your usage of SWE-agent.