mini-swe-agent 1.8.1__tar.gz → 1.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {mini_swe_agent-1.8.1/src/mini_swe_agent.egg-info → mini_swe_agent-1.9.1}/PKG-INFO +3 -2
  2. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/pyproject.toml +2 -1
  3. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1/src/mini_swe_agent.egg-info}/PKG-INFO +3 -2
  4. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/mini_swe_agent.egg-info/SOURCES.txt +3 -1
  5. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/mini_swe_agent.egg-info/requires.txt +2 -1
  6. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/__init__.py +17 -2
  7. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/agents/default.py +5 -5
  8. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/agents/interactive.py +2 -2
  9. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/agents/interactive_textual.py +4 -4
  10. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/environments/docker.py +8 -4
  11. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/environments/extra/swerex_docker.py +11 -3
  12. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/environments/local.py +6 -1
  13. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/environments/singularity.py +6 -3
  14. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/models/litellm_model.py +4 -1
  15. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/models/test_models.py +5 -1
  16. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/extra/swebench.py +15 -12
  17. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/extra/swebench_single.py +18 -2
  18. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/mini.py +14 -17
  19. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/utils/save.py +3 -1
  20. mini_swe_agent-1.9.1/src/minisweagent/utils/__init__.py +0 -0
  21. mini_swe_agent-1.9.1/src/minisweagent/utils/log.py +36 -0
  22. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/LICENSE.md +0 -0
  23. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/README.md +0 -0
  24. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/setup.cfg +0 -0
  25. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/mini_swe_agent.egg-info/dependency_links.txt +0 -0
  26. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/mini_swe_agent.egg-info/entry_points.txt +0 -0
  27. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/mini_swe_agent.egg-info/top_level.txt +0 -0
  28. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/__main__.py +0 -0
  29. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/agents/__init__.py +0 -0
  30. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/README.md +0 -0
  31. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/__init__.py +0 -0
  32. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/default.yaml +0 -0
  33. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/extra/__init__.py +0 -0
  34. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/extra/swebench.yaml +0 -0
  35. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/github_issue.yaml +0 -0
  36. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/mini.tcss +0 -0
  37. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/mini.yaml +0 -0
  38. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/config/mini_no_temp.yaml +0 -0
  39. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/environments/__init__.py +0 -0
  40. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/environments/extra/__init__.py +0 -0
  41. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/models/__init__.py +2 -2
  42. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/models/anthropic.py +0 -0
  43. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/models/utils/__init__.py +0 -0
  44. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/models/utils/cache_control.py +0 -0
  45. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/models/utils/key_per_thread.py +0 -0
  46. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/py.typed +0 -0
  47. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/__init__.py +0 -0
  48. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/extra/__init__.py +0 -0
  49. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/extra/config.py +0 -0
  50. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/extra/utils/__init__.py +0 -0
  51. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/extra/utils/batch_progress.py +0 -0
  52. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/github_issue.py +0 -0
  53. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/hello_world.py +0 -0
  54. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/inspector.py +0 -0
  55. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/mini_extra.py +0 -0
  56. {mini_swe_agent-1.8.1 → mini_swe_agent-1.9.1}/src/minisweagent/run/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-swe-agent
3
- Version: 1.8.1
3
+ Version: 1.9.1
4
4
  Summary: Nano SWE Agent - A simple AI software engineering agent
5
5
  Author-email: Kilian Lieret <kilian.lieret@posteo.de>, "Carlos E. Jimenez" <carlosej@princeton.edu>
6
6
  License: MIT License
@@ -48,9 +48,10 @@ Requires-Dist: typer
48
48
  Requires-Dist: platformdirs
49
49
  Requires-Dist: textual
50
50
  Requires-Dist: prompt_toolkit
51
+ Requires-Dist: openai<=1.99.5
51
52
  Provides-Extra: full
52
53
  Requires-Dist: mini-swe-agent[dev]; extra == "full"
53
- Requires-Dist: swe-rex; extra == "full"
54
+ Requires-Dist: swe-rex>=1.4.0; extra == "full"
54
55
  Provides-Extra: dev
55
56
  Requires-Dist: datasets; extra == "dev"
56
57
  Requires-Dist: pytest; extra == "dev"
@@ -42,12 +42,13 @@ dependencies = [
42
42
  "platformdirs",
43
43
  "textual",
44
44
  "prompt_toolkit",
45
+ "openai <= 1.99.5", # https://github.com/SWE-agent/mini-swe-agent/issues/446
45
46
  ]
46
47
 
47
48
  [project.optional-dependencies]
48
49
  full = [
49
50
  "mini-swe-agent[dev]",
50
- "swe-rex",
51
+ "swe-rex>=1.4.0",
51
52
  ]
52
53
 
53
54
  dev = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-swe-agent
3
- Version: 1.8.1
3
+ Version: 1.9.1
4
4
  Summary: Nano SWE Agent - A simple AI software engineering agent
5
5
  Author-email: Kilian Lieret <kilian.lieret@posteo.de>, "Carlos E. Jimenez" <carlosej@princeton.edu>
6
6
  License: MIT License
@@ -48,9 +48,10 @@ Requires-Dist: typer
48
48
  Requires-Dist: platformdirs
49
49
  Requires-Dist: textual
50
50
  Requires-Dist: prompt_toolkit
51
+ Requires-Dist: openai<=1.99.5
51
52
  Provides-Extra: full
52
53
  Requires-Dist: mini-swe-agent[dev]; extra == "full"
53
- Requires-Dist: swe-rex; extra == "full"
54
+ Requires-Dist: swe-rex>=1.4.0; extra == "full"
54
55
  Provides-Extra: dev
55
56
  Requires-Dist: datasets; extra == "dev"
56
57
  Requires-Dist: pytest; extra == "dev"
@@ -49,4 +49,6 @@ src/minisweagent/run/extra/swebench_single.py
49
49
  src/minisweagent/run/extra/utils/__init__.py
50
50
  src/minisweagent/run/extra/utils/batch_progress.py
51
51
  src/minisweagent/run/utils/__init__.py
52
- src/minisweagent/run/utils/save.py
52
+ src/minisweagent/run/utils/save.py
53
+ src/minisweagent/utils/__init__.py
54
+ src/minisweagent/utils/log.py
@@ -9,6 +9,7 @@ typer
9
9
  platformdirs
10
10
  textual
11
11
  prompt_toolkit
12
+ openai<=1.99.5
12
13
 
13
14
  [dev]
14
15
  datasets
@@ -26,4 +27,4 @@ mkdocs-glightbox
26
27
 
27
28
  [full]
28
29
  mini-swe-agent[dev]
29
- swe-rex
30
+ swe-rex>=1.4.0
@@ -8,7 +8,7 @@ This file provides:
8
8
  unless you want the static type checking.
9
9
  """
10
10
 
11
- __version__ = "1.8.1"
11
+ __version__ = "1.9.1"
12
12
 
13
13
  import os
14
14
  from pathlib import Path
@@ -18,6 +18,8 @@ import dotenv
18
18
  from platformdirs import user_config_dir
19
19
  from rich.console import Console
20
20
 
21
+ from minisweagent.utils.log import logger
22
+
21
23
  package_dir = Path(__file__).resolve().parent
22
24
 
23
25
  global_config_dir = Path(os.getenv("MSWEA_GLOBAL_CONFIG_DIR") or user_config_dir("mini-swe-agent"))
@@ -45,6 +47,8 @@ class Model(Protocol):
45
47
 
46
48
  def query(self, messages: list[dict[str, str]], **kwargs) -> dict: ...
47
49
 
50
+ def get_template_vars(self) -> dict[str, Any]: ...
51
+
48
52
 
49
53
  class Environment(Protocol):
50
54
  """Protocol for execution environments."""
@@ -53,6 +57,8 @@ class Environment(Protocol):
53
57
 
54
58
  def execute(self, command: str, cwd: str = "") -> dict[str, str]: ...
55
59
 
60
+ def get_template_vars(self) -> dict[str, Any]: ...
61
+
56
62
 
57
63
  class Agent(Protocol):
58
64
  """Protocol for agents."""
@@ -64,4 +70,13 @@ class Agent(Protocol):
64
70
  def run(self, task: str, **kwargs) -> tuple[str, str]: ...
65
71
 
66
72
 
67
- __all__ = ["Agent", "Model", "Environment", "package_dir", "__version__", "global_config_file", "global_config_dir"]
73
+ __all__ = [
74
+ "Agent",
75
+ "Model",
76
+ "Environment",
77
+ "package_dir",
78
+ "__version__",
79
+ "global_config_file",
80
+ "global_config_dir",
81
+ "logger",
82
+ ]
@@ -1,7 +1,5 @@
1
1
  """Basic agent class. See https://mini-swe-agent.com/latest/advanced/control_flow/ for visual explanation."""
2
2
 
3
- import os
4
- import platform
5
3
  import re
6
4
  import subprocess
7
5
  from collections.abc import Callable
@@ -61,19 +59,21 @@ class DefaultAgent:
61
59
  self.messages: list[dict] = []
62
60
  self.model = model
63
61
  self.env = env
62
+ self.extra_template_vars = {}
64
63
 
65
64
  def render_template(self, template: str, **kwargs) -> str:
66
- cs = asdict(self.config) | asdict(self.env.config) | asdict(self.model.config) | platform.uname()._asdict()
67
- return Template(template).render(**kwargs, **cs, **os.environ)
65
+ template_vars = asdict(self.config) | self.env.get_template_vars() | self.model.get_template_vars()
66
+ return Template(template).render(**kwargs, **template_vars, **self.extra_template_vars)
68
67
 
69
68
  def add_message(self, role: str, content: str, **kwargs):
70
69
  self.messages.append({"role": role, "content": content, **kwargs})
71
70
 
72
71
  def run(self, task: str, **kwargs) -> tuple[str, str]:
73
72
  """Run step() until agent is finished. Return exit status & message"""
73
+ self.extra_template_vars |= {"task": task, **kwargs}
74
74
  self.messages = []
75
75
  self.add_message("system", self.render_template(self.config.system_template))
76
- self.add_message("user", self.render_template(self.config.instance_template, task=task, **kwargs))
76
+ self.add_message("user", self.render_template(self.config.instance_template))
77
77
  while True:
78
78
  try:
79
79
  self.step()
@@ -39,9 +39,9 @@ class InteractiveAgent(DefaultAgent):
39
39
  super().__init__(*args, config_class=config_class, **kwargs)
40
40
  self.cost_last_confirmed = 0.0
41
41
 
42
- def add_message(self, role: str, content: str):
42
+ def add_message(self, role: str, content: str, **kwargs):
43
43
  # Extend supermethod to print messages
44
- super().add_message(role, content)
44
+ super().add_message(role, content, **kwargs)
45
45
  if role == "assistant":
46
46
  console.print(
47
47
  f"\n[red][bold]mini-swe-agent[/bold] (step [bold]{self.model.n_calls}[/bold], [bold]${self.model.cost:.2f}[/bold]):[/red]\n",
@@ -44,8 +44,8 @@ class _TextualAgent(DefaultAgent):
44
44
  super().__init__(*args, config_class=TextualAgentConfig, **kwargs)
45
45
  self._current_action_from_human = False
46
46
 
47
- def add_message(self, role: str, content: str):
48
- super().add_message(role, content)
47
+ def add_message(self, role: str, content: str, **kwargs):
48
+ super().add_message(role, content, **kwargs)
49
49
  if self.app.agent_state != "UNINITIALIZED":
50
50
  self.app.call_from_thread(self.app.on_message_added)
51
51
 
@@ -276,8 +276,8 @@ class TextualAgent(App):
276
276
 
277
277
  self._vscroll = VerticalScroll()
278
278
 
279
- def run(self, task: str) -> tuple[str, str]:
280
- threading.Thread(target=lambda: self.agent.run(task), daemon=True).start()
279
+ def run(self, task: str, **kwargs) -> tuple[str, str]:
280
+ threading.Thread(target=lambda: self.agent.run(task, **kwargs), daemon=True).start()
281
281
  super().run()
282
282
  return self.exit_status, self.result
283
283
 
@@ -1,8 +1,9 @@
1
+ import logging
1
2
  import os
2
3
  import shlex
3
4
  import subprocess
4
5
  import uuid
5
- from dataclasses import dataclass, field
6
+ from dataclasses import asdict, dataclass, field
6
7
  from typing import Any
7
8
 
8
9
 
@@ -33,10 +34,14 @@ class DockerEnvironment:
33
34
  """This class executes bash commands in a Docker container using direct docker commands.
34
35
  See `DockerEnvironmentConfig` for keyword arguments.
35
36
  """
37
+ self.logger = logging.getLogger("minisweagent.environment")
36
38
  self.container_id: str | None = None
37
39
  self.config = config_class(**kwargs)
38
40
  self._start_container()
39
41
 
42
+ def get_template_vars(self) -> dict[str, Any]:
43
+ return asdict(self.config)
44
+
40
45
  def _start_container(self):
41
46
  """Start the Docker container and return the container ID."""
42
47
  container_name = f"minisweagent-{uuid.uuid4().hex[:8]}"
@@ -53,7 +58,7 @@ class DockerEnvironment:
53
58
  "sleep",
54
59
  self.config.container_timeout,
55
60
  ]
56
- print(f"Starting container with command: {shlex.join(cmd)}")
61
+ self.logger.debug(f"Starting container with command: {shlex.join(cmd)}")
57
62
  result = subprocess.run(
58
63
  cmd,
59
64
  capture_output=True,
@@ -61,7 +66,7 @@ class DockerEnvironment:
61
66
  timeout=120, # docker pull might take a while
62
67
  check=True,
63
68
  )
64
- print(f"Started container {container_name} with ID {result.stdout.strip()}")
69
+ self.logger.info(f"Started container {container_name} with ID {result.stdout.strip()}")
65
70
  self.container_id = result.stdout.strip()
66
71
 
67
72
  def execute(self, command: str, cwd: str = "") -> dict[str, Any]:
@@ -91,7 +96,6 @@ class DockerEnvironment:
91
96
  def cleanup(self):
92
97
  """Stop and remove the Docker container."""
93
98
  if getattr(self, "container_id", None) is not None: # if init fails early, container_id might not be set
94
- print(f"Stopping container {self.container_id}")
95
99
  cmd = f"(timeout 60 {self.config.executable} stop {self.container_id} || {self.config.executable} rm -f {self.container_id}) >/dev/null 2>&1 &"
96
100
  subprocess.Popen(cmd, shell=True)
97
101
 
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from dataclasses import dataclass, field
2
+ from dataclasses import asdict, dataclass, field
3
3
  from typing import Any
4
4
 
5
5
  from swerex.deployment.docker import DockerDeployment
@@ -29,11 +29,19 @@ class SwerexDockerEnvironment:
29
29
  output = asyncio.run(
30
30
  self.deployment.runtime.execute(
31
31
  RexCommand(
32
- command=command, shell=True, check=False, cwd=cwd or self.config.cwd, timeout=self.config.timeout
32
+ command=command,
33
+ shell=True,
34
+ check=False,
35
+ cwd=cwd or self.config.cwd,
36
+ timeout=self.config.timeout,
37
+ merge_output_streams=True,
33
38
  )
34
39
  )
35
40
  )
36
41
  return {
37
- "output": f"<stdout>\n{output.stdout}</stdout>\n<stderr>\n{output.stderr}</stderr>",
42
+ "output": output.stdout,
38
43
  "returncode": output.exit_code,
39
44
  }
45
+
46
+ def get_template_vars(self) -> dict[str, Any]:
47
+ return asdict(self.config)
@@ -1,6 +1,8 @@
1
1
  import os
2
+ import platform
2
3
  import subprocess
3
- from dataclasses import dataclass, field
4
+ from dataclasses import asdict, dataclass, field
5
+ from typing import Any
4
6
 
5
7
 
6
8
  @dataclass
@@ -31,3 +33,6 @@ class LocalEnvironment:
31
33
  stderr=subprocess.STDOUT,
32
34
  )
33
35
  return {"output": result.stdout, "returncode": result.returncode}
36
+
37
+ def get_template_vars(self) -> dict[str, Any]:
38
+ return asdict(self.config) | platform.uname()._asdict() | os.environ
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+ import logging
3
4
  import os
4
5
  import shutil
5
6
  import subprocess
6
7
  import tempfile
7
8
  import uuid
8
- from dataclasses import dataclass, field
9
+ from dataclasses import asdict, dataclass, field
9
10
  from pathlib import Path
10
11
  from typing import Any
11
12
 
@@ -27,14 +28,17 @@ class SingularityEnvironmentConfig:
27
28
  class SingularityEnvironment:
28
29
  def __init__(self, **kwargs):
29
30
  """Singularity environment. See `SingularityEnvironmentConfig` for kwargs."""
31
+ self.logger = logging.getLogger("minisweagent.environment")
30
32
  self.config = SingularityEnvironmentConfig(**kwargs)
31
33
  self.sandbox_dir = Path(tempfile.gettempdir()) / f"minisweagent-{uuid.uuid4().hex[:8]}"
32
-
33
34
  subprocess.run(
34
35
  [self.config.executable, "build", "--sandbox", self.sandbox_dir, self.config.image],
35
36
  check=True,
36
37
  )
37
38
 
39
+ def get_template_vars(self) -> dict[str, Any]:
40
+ return asdict(self.config)
41
+
38
42
  def execute(self, command: str, cwd: str = "") -> dict[str, Any]:
39
43
  """Execute a command in a Singularity container and return the result as a dict."""
40
44
  cmd = [self.config.executable, "exec"]
@@ -66,7 +70,6 @@ class SingularityEnvironment:
66
70
 
67
71
  def cleanup(self):
68
72
  if self.sandbox_dir.exists():
69
- print(f"Removing sandbox {self.sandbox_dir}")
70
73
  shutil.rmtree(self.sandbox_dir)
71
74
 
72
75
  def __del__(self):
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- from dataclasses import dataclass, field
4
+ from dataclasses import asdict, dataclass, field
5
5
  from pathlib import Path
6
6
  from typing import Any
7
7
 
@@ -68,3 +68,6 @@ class LitellmModel:
68
68
  return {
69
69
  "content": response.choices[0].message.content or "", # type: ignore
70
70
  }
71
+
72
+ def get_template_vars(self) -> dict[str, Any]:
73
+ return asdict(self.config) | {"n_model_calls": self.n_calls, "model_cost": self.cost}
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import time
3
- from dataclasses import dataclass
3
+ from dataclasses import asdict, dataclass
4
+ from typing import Any
4
5
 
5
6
  from minisweagent.models import GLOBAL_MODEL_STATS
6
7
 
@@ -36,3 +37,6 @@ class DeterministicModel:
36
37
  self.cost += self.config.cost_per_call
37
38
  GLOBAL_MODEL_STATS.add(self.config.cost_per_call)
38
39
  return {"content": output}
40
+
41
+ def get_template_vars(self) -> dict[str, Any]:
42
+ return asdict(self.config) | {"n_model_calls": self.n_calls, "model_cost": self.cost}
@@ -24,6 +24,7 @@ from minisweagent.environments import get_environment
24
24
  from minisweagent.models import get_model
25
25
  from minisweagent.run.extra.utils.batch_progress import RunBatchProgressManager
26
26
  from minisweagent.run.utils.save import save_traj
27
+ from minisweagent.utils.log import add_file_handler, logger
27
28
 
28
29
  _HELP_TEXT = """Run mini-SWE-agent on SWEBench instances.
29
30
 
@@ -141,7 +142,7 @@ def process_instance(
141
142
  )
142
143
  exit_status, result = agent.run(task)
143
144
  except Exception as e:
144
- print(f"Error processing instance {instance_id}: {e}\n{traceback.format_exc()}")
145
+ logger.error(f"Error processing instance {instance_id}: {e}", exc_info=True)
145
146
  exit_status, result = type(e).__name__, str(e)
146
147
  extra_info = {"traceback": traceback.format_exc()}
147
148
  finally:
@@ -152,6 +153,7 @@ def process_instance(
152
153
  result=result,
153
154
  extra_info=extra_info,
154
155
  instance_id=instance_id,
156
+ print_fct=logger.info,
155
157
  )
156
158
  update_preds_file(output_dir / "preds.json", instance_id, model.config.model_name, result)
157
159
  progress_manager.on_instance_end(instance_id, exit_status)
@@ -168,12 +170,12 @@ def filter_instances(
168
170
  before_filter = len(instances)
169
171
  instances = [instance for instance in instances if re.match(filter_spec, instance["instance_id"])]
170
172
  if (after_filter := len(instances)) != before_filter:
171
- print(f"Instance filter: {before_filter} -> {after_filter} instances")
173
+ logger.info(f"Instance filter: {before_filter} -> {after_filter} instances")
172
174
  if slice_spec:
173
175
  values = [int(x) if x else None for x in slice_spec.split(":")]
174
176
  instances = instances[slice(*values)]
175
177
  if (after_slice := len(instances)) != before_filter:
176
- print(f"Instance slice: {before_filter} -> {after_slice} instances")
178
+ logger.info(f"Instance slice: {before_filter} -> {after_slice} instances")
177
179
  return instances
178
180
 
179
181
 
@@ -193,20 +195,22 @@ def main(
193
195
  environment_class: str | None = typer.Option( None, "--environment-class", help="Environment type to use. Recommended are docker or singularity", rich_help_panel="Advanced"),
194
196
  ) -> None:
195
197
  # fmt: on
198
+ output_path = Path(output)
199
+ output_path.mkdir(parents=True, exist_ok=True)
200
+ logger.info(f"Results will be saved to {output_path}")
201
+ add_file_handler(output_path / "minisweagent.log")
202
+
196
203
  dataset_path = DATASET_MAPPING.get(subset, subset)
197
- print(f"Loading dataset {dataset_path}, split {split}...")
204
+ logger.info(f"Loading dataset {dataset_path}, split {split}...")
198
205
  instances = list(load_dataset(dataset_path, split=split))
199
206
 
200
207
  instances = filter_instances(instances, filter_spec=filter_spec, slice_spec=slice_spec, shuffle=shuffle)
201
- output_path = Path(output)
202
208
  if not redo_existing and (output_path / "preds.json").exists():
203
209
  existing_instances = list(json.loads((output_path / "preds.json").read_text()).keys())
204
- print(f"Skipping {len(existing_instances)} existing instances")
210
+ logger.info(f"Skipping {len(existing_instances)} existing instances")
205
211
  instances = [instance for instance in instances if instance["instance_id"] not in existing_instances]
212
+ logger.info(f"Running on {len(instances)} instances...")
206
213
 
207
- output_path.mkdir(parents=True, exist_ok=True)
208
- print(f"Running on {len(instances)} instances...")
209
- print(f"Results will be saved to {output_path}")
210
214
 
211
215
  config = yaml.safe_load(get_config_path(config_spec).read_text())
212
216
  if environment_class is not None:
@@ -224,8 +228,7 @@ def main(
224
228
  pass
225
229
  except Exception as e:
226
230
  instance_id = futures[future]
227
- print(f"Error in future for instance {instance_id}: {e}")
228
- traceback.print_exc()
231
+ logger.error(f"Error in future for instance {instance_id}: {e}", exc_info=True)
229
232
  progress_manager.on_uncaught_exception(instance_id, e)
230
233
 
231
234
  with Live(progress_manager.render_group, refresh_per_second=4):
@@ -239,7 +242,7 @@ def main(
239
242
  try:
240
243
  process_futures(futures)
241
244
  except KeyboardInterrupt:
242
- print("Cancelling all pending jobs. Press ^C again to exit immediately.")
245
+ logger.info("Cancelling all pending jobs. Press ^C again to exit immediately.")
243
246
  for future in futures:
244
247
  if not future.running() and not future.done():
245
248
  future.cancel()
@@ -1,11 +1,13 @@
1
1
  """Run on a single SWE-Bench instance."""
2
2
 
3
+ import traceback
3
4
  from pathlib import Path
4
5
 
5
6
  import typer
6
7
  import yaml
7
8
  from datasets import load_dataset
8
9
 
10
+ from minisweagent import global_config_dir
9
11
  from minisweagent.agents.interactive import InteractiveAgent
10
12
  from minisweagent.config import builtin_config_dir, get_config_path
11
13
  from minisweagent.models import get_model
@@ -13,9 +15,13 @@ from minisweagent.run.extra.swebench import (
13
15
  DATASET_MAPPING,
14
16
  get_sb_environment,
15
17
  )
18
+ from minisweagent.run.utils.save import save_traj
19
+ from minisweagent.utils.log import logger
16
20
 
17
21
  app = typer.Typer(add_completion=False)
18
22
 
23
+ DEFAULT_OUTPUT = global_config_dir / "last_swebench_single_run.traj.json"
24
+
19
25
 
20
26
  # fmt: off
21
27
  @app.command()
@@ -27,11 +33,12 @@ def main(
27
33
  config_path: Path = typer.Option( builtin_config_dir / "extra" / "swebench.yaml", "-c", "--config", help="Path to a config file", rich_help_panel="Basic"),
28
34
  environment_class: str | None = typer.Option(None, "--environment-class", rich_help_panel="Advanced"),
29
35
  exit_immediately: bool = typer.Option( False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting.", rich_help_panel="Basic"),
36
+ output: Path = typer.Option(DEFAULT_OUTPUT, "-o", "--output", help="Output trajectory file", rich_help_panel="Basic"),
30
37
  ) -> None:
31
38
  # fmt: on
32
39
  """Run on a single SWE-Bench instance."""
33
40
  dataset_path = DATASET_MAPPING.get(subset, subset)
34
- print(f"Loading dataset from {dataset_path}, split {split}...")
41
+ logger.info(f"Loading dataset from {dataset_path}, split {split}...")
35
42
  instances = {
36
43
  inst["instance_id"]: inst # type: ignore
37
44
  for inst in load_dataset(dataset_path, split=split)
@@ -51,7 +58,16 @@ def main(
51
58
  env,
52
59
  **({"mode": "yolo"} | config.get("agent", {})),
53
60
  )
54
- agent.run(instance["problem_statement"])
61
+
62
+ exit_status, result, extra_info = None, None, None
63
+ try:
64
+ exit_status, result = agent.run(instance["problem_statement"]) # type: ignore[arg-type]
65
+ except Exception as e:
66
+ logger.error(f"Error processing instance {instance_spec}: {e}", exc_info=True)
67
+ exit_status, result = type(e).__name__, str(e)
68
+ extra_info = {"traceback": traceback.format_exc()}
69
+ finally:
70
+ save_traj(agent, output, exit_status=exit_status, result=result, extra_info=extra_info) # type: ignore[arg-type]
55
71
 
56
72
 
57
73
  if __name__ == "__main__":
@@ -4,6 +4,7 @@
4
4
  # Read this first: https://mini-swe-agent.com/latest/usage/mini/ (usage)
5
5
 
6
6
  import os
7
+ import traceback
7
8
  from pathlib import Path
8
9
  from typing import Any
9
10
 
@@ -22,6 +23,7 @@ from minisweagent.environments.local import LocalEnvironment
22
23
  from minisweagent.models import get_model
23
24
  from minisweagent.run.extra.config import configure_if_first_time
24
25
  from minisweagent.run.utils.save import save_traj
26
+ from minisweagent.utils.log import logger
25
27
 
26
28
  DEFAULT_CONFIG = Path(os.getenv("MSWEA_MINI_CONFIG_PATH", builtin_config_dir / "mini.yaml"))
27
29
  DEFAULT_OUTPUT = global_config_dir / "last_mini_run.traj.json"
@@ -41,29 +43,19 @@ More information about the usage: [bold green]https://mini-swe-agent.com/latest/
41
43
  """
42
44
 
43
45
 
46
+ # fmt: off
44
47
  @app.command(help=_HELP_TEXT)
45
48
  def main(
46
- visual: bool = typer.Option(
47
- False,
48
- "-v",
49
- "--visual",
50
- help="Toggle (pager-style) UI (Textual) depending on the MSWEA_VISUAL_MODE_DEFAULT environment setting",
51
- ),
52
- model_name: str | None = typer.Option(
53
- None,
54
- "-m",
55
- "--model",
56
- help="Model to use",
57
- ),
49
+ visual: bool = typer.Option(False, "-v", "--visual", help="Toggle (pager-style) UI (Textual) depending on the MSWEA_VISUAL_MODE_DEFAULT environment setting",),
50
+ model_name: str | None = typer.Option( None, "-m", "--model", help="Model to use",),
58
51
  task: str | None = typer.Option(None, "-t", "--task", help="Task/problem statement", show_default=False),
59
52
  yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
60
53
  cost_limit: float | None = typer.Option(None, "-l", "--cost-limit", help="Cost limit. Set to 0 to disable."),
61
54
  config_spec: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
62
55
  output: Path | None = typer.Option(DEFAULT_OUTPUT, "-o", "--output", help="Output trajectory file"),
63
- exit_immediately: bool = typer.Option(
64
- False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting."
65
- ),
56
+ exit_immediately: bool = typer.Option( False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting."),
66
57
  ) -> Any:
58
+ # fmt: on
67
59
  configure_if_first_time()
68
60
  config = yaml.safe_load(get_config_path(config_spec).read_text())
69
61
 
@@ -92,13 +84,18 @@ def main(
92
84
  agent_class = InteractiveAgent
93
85
  if visual == (os.getenv("MSWEA_VISUAL_MODE_DEFAULT", "false") == "false"):
94
86
  agent_class = TextualAgent
95
- exit_status, result = None, None
87
+
96
88
  agent = agent_class(model, env, **config.get("agent", {}))
89
+ exit_status, result, extra_info = None, None, None
97
90
  try:
98
91
  exit_status, result = agent.run(task) # type: ignore[arg-type]
92
+ except Exception as e:
93
+ logger.error(f"Error running agent: {e}", exc_info=True)
94
+ exit_status, result = type(e).__name__, str(e)
95
+ extra_info = {"traceback": traceback.format_exc()}
99
96
  finally:
100
97
  if output:
101
- save_traj(agent, output, exit_status=exit_status, result=result) # type: ignore[arg-type]
98
+ save_traj(agent, output, exit_status=exit_status, result=result, extra_info=extra_info) # type: ignore[arg-type]
102
99
  return agent
103
100
 
104
101
 
@@ -1,4 +1,5 @@
1
1
  import json
2
+ from collections.abc import Callable
2
3
  from pathlib import Path
3
4
 
4
5
  from minisweagent import Agent, __version__
@@ -12,6 +13,7 @@ def save_traj(
12
13
  exit_status: str | None = None,
13
14
  result: str | None = None,
14
15
  extra_info: dict | None = None,
16
+ print_fct: Callable = print,
15
17
  **kwargs,
16
18
  ):
17
19
  """Save the trajectory of the agent to a file.
@@ -49,4 +51,4 @@ def save_traj(
49
51
  path.parent.mkdir(parents=True, exist_ok=True)
50
52
  path.write_text(json.dumps(data, indent=2))
51
53
  if print_path:
52
- print(f"Saved trajectory to '{path}'")
54
+ print_fct(f"Saved trajectory to '{path}'")
@@ -0,0 +1,36 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from rich.logging import RichHandler
5
+
6
+
7
+ def _setup_root_logger() -> None:
8
+ logger = logging.getLogger("minisweagent")
9
+ logger.setLevel(logging.DEBUG)
10
+ _handler = RichHandler(
11
+ show_path=False,
12
+ show_time=False,
13
+ show_level=False,
14
+ markup=True,
15
+ )
16
+ _formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
17
+ _handler.setFormatter(_formatter)
18
+ logger.addHandler(_handler)
19
+
20
+
21
+ def add_file_handler(path: Path | str, level: int = logging.DEBUG, *, print_path: bool = True) -> None:
22
+ logger = logging.getLogger("minisweagent")
23
+ handler = logging.FileHandler(path)
24
+ handler.setLevel(level)
25
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
26
+ handler.setFormatter(formatter)
27
+ logger.addHandler(handler)
28
+ if print_path:
29
+ print(f"Logging to '{path}'")
30
+
31
+
32
+ _setup_root_logger()
33
+ logger = logging.getLogger("minisweagent")
34
+
35
+
36
+ __all__ = ["logger"]
File without changes
File without changes
@@ -63,10 +63,10 @@ def get_model_name(input_model_name: str | None = None, config: dict | None = No
63
63
  config = {}
64
64
  if input_model_name:
65
65
  return input_model_name
66
- if from_env := os.getenv("MSWEA_MODEL_NAME"):
67
- return from_env
68
66
  if from_config := config.get("model_name"):
69
67
  return from_config
68
+ if from_env := os.getenv("MSWEA_MODEL_NAME"):
69
+ return from_env
70
70
  raise ValueError("No default model set. Please run `mini-extra config setup` to set one.")
71
71
 
72
72