Skip to content

Commit 2e9b738

Browse files
authored
Merge branch 'main' into allow-run-locally
2 parents 72b7b48 + e052bef commit 2e9b738

File tree

4 files changed

+123
-14
lines changed

4 files changed

+123
-14
lines changed

README.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,19 @@ Read our paper for more details [coming soon!].
5151
You can run the software directly using Docker.
5252

5353
1. [Install Docker](https://docs.docker.com/engine/install/), then start Docker locally.
54-
2. Run `docker pull --platform=linux/arm64 sweagent/swe-agent:latest` (replace `arm64` with `amd64` if you're running on x86)
54+
2. Run `docker pull sweagent/swe-agent:latest`
5555
3. Add your API tokens to a file `keys.cfg` as explained [below](#-add-your-api-keystokens)
5656

5757
Then run
5858

5959
```bash
60-
# Please remove all comments (lines starting with '#') before running this command!
60+
# NOTE:
61+
# This assumes that keys.cfg is in your current directory (else fix the path below)
62+
# This command is equivalent to the script shown in the quickstart
6163
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \
62-
# replace /xxxx/keys.cfg with the paths to your keys
63-
-v /xxxx/keys.cfg:/app/keys.cfg \
64-
# replace with your architecture, either arm64 or amd64
65-
--platform=linux/arm64 \
64+
-v $(pwd)/keys.cfg:/app/keys.cfg \
6665
sweagent/swe-agent-run:latest \
6766
python run.py --image_name=sweagent/swe-agent:latest \
68-
# the rest of the command as shown in the quickstart/benchmarking section,
69-
# for example to run on a specific github issue
7067
--model_name gpt4 \
7168
--data_path https://github.com/pvlib/pvlib-python/issues/1603 \
7269
--config_file config/default_from_url.yaml --skip_existing=False
@@ -79,7 +76,7 @@ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \
7976
> * See the [installation issues section](#-installation-issues) for more help if you run into
8077
> trouble.
8178
82-
### 🐍 Setup with conda (development version)
79+
### 🐍 Setup with conda (developer version)
8380

8481
To install the development version:
8582

run.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import subprocess
66
import traceback
77
from typing import Any, Dict, Optional
8+
import rich.console
9+
import rich.markdown
10+
import rich.panel
11+
import rich.markdown
812
import yaml
913

1014
from dataclasses import dataclass
@@ -165,15 +169,15 @@ def main(args: ScriptArguments):
165169
def should_open_pr(args: ScriptArguments, info: Dict[str, Any], *, token: str="") -> bool:
166170
"""Does opening a PR make sense?"""
167171
if not info.get("submission"):
168-
logger.info("Not openening PR because submission was made.")
172+
logger.info("Not opening PR because submission was made.")
169173
return False
170174
if info["exit_status"] != "submitted":
171-
logger.info("Not openening PR because exit status was %s and not submitted.", info["exit_status"])
175+
logger.info("Not opening PR because exit status was %s and not submitted.", info["exit_status"])
172176
return False
173177
try:
174178
issue = get_gh_issue_data(args.environment.data_path, token=token)
175179
except InvalidGithubURL:
176-
logger.info("Currently only github is supported to open PRs to. Skipping PR creation.")
180+
logger.info("Currently only GitHub is supported to open PRs to. Skipping PR creation.")
177181
return False
178182
if issue.state != "open":
179183
logger.info(f"Issue is not open (state={issue.state}. Skipping PR creation.")
@@ -259,6 +263,8 @@ def save_predictions(traj_dir: Path, instance_id: str, info):
259263
logger.info(f"Saved predictions to {output_file}")
260264

261265

266+
267+
262268
def save_patch(traj_dir: Path, instance_id: str, info) -> Optional[Path]:
263269
"""Create patch files that can be applied with `git am`.
264270
@@ -273,7 +279,7 @@ def save_patch(traj_dir: Path, instance_id: str, info) -> Optional[Path]:
273279
return
274280
model_patch = info["submission"]
275281
patch_output_file.write_text(model_patch)
276-
logger.info(f"Saved patch to {patch_output_file}")
282+
_print_patch_message(patch_output_file)
277283
return patch_output_file
278284

279285

@@ -291,6 +297,31 @@ def apply_patch(local_dir: Path, patch_file: Path) -> None:
291297
return
292298
logger.info(f"Applied patch {patch_file} to {local_dir}")
293299

300+
301+
def _print_patch_message(patch_output_file: Path):
302+
console = rich.console.Console()
303+
msg = [
304+
"SWE-agent has produced a patch that it believes will solve the issue you submitted!",
305+
"Use the code snippet below to inspect or apply it!"
306+
]
307+
panel = rich.panel.Panel.fit(
308+
"\n".join(msg),
309+
title="🎉 Submission successful 🎉",
310+
)
311+
console.print(panel)
312+
content = [
313+
"```bash",
314+
f"# The patch has been saved to your local filesystem at:",
315+
f"PATCH_FILE_PATH='{patch_output_file.resolve()}'",
316+
"# Inspect it:",
317+
"cat \"${PATCH_FILE_PATH}\"",
318+
"# Apply it to a local repository:",
319+
f"cd <your local repo root>",
320+
"git apply \"${PATCH_FILE_PATH}\"",
321+
"```",
322+
]
323+
console.print(rich.markdown.Markdown("\n".join(content)))
324+
294325

295326
def get_args(args=None) -> ScriptArguments:
296327
"""Parse command line arguments and return a ScriptArguments object.

run_replay.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def create_task_instances_tmp_file(data: List[Dict[str, Any]]) -> str:
6363
with open(tmp_path, "w") as f:
6464
for d in data:
6565
print(json.dumps(d), file=f, end="\n", flush=True)
66-
return replay_task_instances_path
66+
return tmp_path
6767

6868
is_other = False
6969
if data_path.endswith(".jsonl"):

tests/test_parsing.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import pytest
2+
from sweagent.agent.commands import Command
3+
from sweagent.agent.parsing import (
4+
FormatError, ParseFunction, ActionParser, ThoughtActionParser,
5+
XMLThoughtActionParser, EditFormat, Identity, JsonParser
6+
)
7+
8+
9+
def test_parse_function_registry():
10+
assert isinstance(ParseFunction.get("ActionParser"), ActionParser)
11+
assert isinstance(ParseFunction.get("ThoughtActionParser"), ThoughtActionParser)
12+
assert isinstance(ParseFunction.get("XMLThoughtActionParser"), XMLThoughtActionParser)
13+
assert isinstance(ParseFunction.get("EditFormat"), EditFormat)
14+
assert isinstance(ParseFunction.get("Identity"), Identity)
15+
assert isinstance(ParseFunction.get("JsonParser"), JsonParser)
16+
with pytest.raises(ValueError):
17+
ParseFunction.get("InvalidParser")
18+
19+
20+
def test_action_parser():
21+
parser = ActionParser()
22+
command = Command(code='ls', name='ls')
23+
thought, action = parser("ls -l", [command])
24+
assert thought == "ls -l"
25+
assert action == "ls -l"
26+
with pytest.raises(FormatError):
27+
parser("invalid command", [command])
28+
29+
30+
def test_thought_action_parser():
31+
parser = ThoughtActionParser()
32+
model_response = "Let's look at the files in the current directory.\n```\nls -l\n```"
33+
thought, action = parser(model_response, [])
34+
assert thought == "Let's look at the files in the current directory.\n"
35+
assert action == "ls -l\n"
36+
with pytest.raises(FormatError):
37+
parser("No code block", [])
38+
39+
40+
def test_xml_thought_action_parser():
41+
parser = XMLThoughtActionParser()
42+
model_response = "Let's look at the files in the current directory.\n<command>\nls -l\n</command>"
43+
thought, action = parser(model_response, [])
44+
assert thought == "Let's look at the files in the current directory."
45+
assert action == "ls -l"
46+
with pytest.raises(FormatError):
47+
parser("No command tags", [])
48+
49+
50+
def test_edit_format_parser():
51+
parser = EditFormat()
52+
model_response = "Let's replace the contents.\n```\nimport os\nos.listdir()\n```"
53+
thought, action = parser(model_response, [])
54+
assert thought == "Let's replace the contents.\n"
55+
assert action == "import os\nos.listdir()\n"
56+
with pytest.raises(FormatError):
57+
parser("No code block", [])
58+
59+
60+
def test_identity_parser():
61+
parser = Identity()
62+
model_response = "Return as is"
63+
thought, action = parser(model_response, [])
64+
assert thought == model_response
65+
assert action == model_response
66+
67+
68+
def test_json_parser():
69+
parser = JsonParser()
70+
model_response = '{"thought": "List files", "command": {"name": "ls", "arguments": {"path": "."}}}'
71+
thought, action = parser(model_response, [])
72+
assert thought == "List files"
73+
assert action == "ls ."
74+
75+
invalid_json = "Not a JSON"
76+
with pytest.raises(FormatError):
77+
parser(invalid_json, [])
78+
79+
missing_keys = '{"thought": "Missing command key"}'
80+
with pytest.raises(FormatError):
81+
parser(missing_keys, [])

0 commit comments

Comments
 (0)