mini-swe-agent 1.17.5__py3-none-any.whl → 2.0.0a1__py3-none-any.whl
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.
- {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/METADATA +36 -52
- mini_swe_agent-2.0.0a1.dist-info/RECORD +70 -0
- mini_swe_agent-2.0.0a1.dist-info/entry_points.txt +5 -0
- minisweagent/__init__.py +19 -26
- minisweagent/agents/default.py +128 -113
- minisweagent/agents/interactive.py +119 -58
- minisweagent/config/README.md +3 -4
- minisweagent/config/__init__.py +36 -1
- minisweagent/config/benchmarks/swebench.yaml +156 -0
- minisweagent/config/{extra/swebench.yaml → benchmarks/swebench_backticks.yaml} +69 -64
- minisweagent/config/benchmarks/swebench_modal.yaml +47 -0
- minisweagent/config/{extra → benchmarks}/swebench_xml.yaml +73 -70
- minisweagent/config/default.yaml +24 -21
- minisweagent/config/inspector.tcss +42 -0
- minisweagent/config/mini.yaml +53 -71
- minisweagent/config/{github_issue.yaml → mini_textbased.yaml} +43 -29
- minisweagent/environments/__init__.py +1 -0
- minisweagent/environments/docker.py +67 -20
- minisweagent/environments/extra/bubblewrap.py +86 -47
- minisweagent/environments/extra/swerex_docker.py +53 -20
- minisweagent/environments/extra/swerex_modal.py +90 -0
- minisweagent/environments/local.py +62 -21
- minisweagent/environments/singularity.py +59 -18
- minisweagent/exceptions.py +22 -0
- minisweagent/models/__init__.py +6 -7
- minisweagent/models/extra/roulette.py +20 -17
- minisweagent/models/litellm_model.py +90 -44
- minisweagent/models/litellm_response_model.py +80 -0
- minisweagent/models/litellm_textbased_model.py +45 -0
- minisweagent/models/openrouter_model.py +87 -45
- minisweagent/models/openrouter_response_model.py +123 -0
- minisweagent/models/openrouter_textbased_model.py +76 -0
- minisweagent/models/portkey_model.py +84 -42
- minisweagent/models/portkey_response_model.py +163 -0
- minisweagent/models/requesty_model.py +91 -41
- minisweagent/models/test_models.py +246 -19
- minisweagent/models/utils/actions_text.py +60 -0
- minisweagent/models/utils/actions_toolcall.py +102 -0
- minisweagent/models/utils/actions_toolcall_response.py +110 -0
- minisweagent/models/utils/anthropic_utils.py +28 -0
- minisweagent/models/utils/cache_control.py +15 -2
- minisweagent/models/utils/content_string.py +74 -0
- minisweagent/models/utils/openai_multimodal.py +50 -0
- minisweagent/models/utils/retry.py +25 -0
- minisweagent/run/benchmarks/__init__.py +1 -0
- minisweagent/run/{extra → benchmarks}/swebench.py +56 -35
- minisweagent/run/{extra → benchmarks}/swebench_single.py +36 -26
- minisweagent/run/{extra → benchmarks}/utils/batch_progress.py +1 -1
- minisweagent/run/hello_world.py +6 -0
- minisweagent/run/mini.py +54 -63
- minisweagent/run/utilities/__init__.py +1 -0
- minisweagent/run/{extra → utilities}/config.py +2 -0
- minisweagent/run/{inspector.py → utilities/inspector.py} +90 -11
- minisweagent/run/{mini_extra.py → utilities/mini_extra.py} +9 -5
- minisweagent/utils/serialize.py +26 -0
- mini_swe_agent-1.17.5.dist-info/RECORD +0 -61
- mini_swe_agent-1.17.5.dist-info/entry_points.txt +0 -5
- minisweagent/agents/interactive_textual.py +0 -450
- minisweagent/config/extra/swebench_roulette.yaml +0 -233
- minisweagent/config/mini.tcss +0 -86
- minisweagent/models/anthropic.py +0 -35
- minisweagent/models/litellm_response_api_model.py +0 -82
- minisweagent/models/portkey_response_api_model.py +0 -75
- minisweagent/models/utils/key_per_thread.py +0 -20
- minisweagent/models/utils/openai_utils.py +0 -41
- minisweagent/run/github_issue.py +0 -87
- minisweagent/run/utils/__init__.py +0 -0
- minisweagent/run/utils/save.py +0 -78
- {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/WHEEL +0 -0
- {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/licenses/LICENSE.md +0 -0
- {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/top_level.txt +0 -0
- /minisweagent/config/{extra → benchmarks}/__init__.py +0 -0
- /minisweagent/run/{extra → benchmarks}/utils/__init__.py +0 -0
minisweagent/config/mini.tcss
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
Screen {
|
|
2
|
-
layout: grid;
|
|
3
|
-
grid-size: 1;
|
|
4
|
-
grid-rows: auto 1fr auto;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
#main {
|
|
8
|
-
height: 100%;
|
|
9
|
-
padding: 1;
|
|
10
|
-
layout: vertical;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
Footer {
|
|
14
|
-
dock: bottom;
|
|
15
|
-
content-align: center middle;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
#content {
|
|
19
|
-
height: auto;
|
|
20
|
-
min-height: 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.smart-input-container {
|
|
24
|
-
height: auto;
|
|
25
|
-
margin-top: 0;
|
|
26
|
-
padding: 0;
|
|
27
|
-
min-height: 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.multi-input {
|
|
31
|
-
height: auto;
|
|
32
|
-
max-height: 20;
|
|
33
|
-
min-height: 3;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.prompt-display {
|
|
37
|
-
margin-bottom: 1;
|
|
38
|
-
padding: 0 1;
|
|
39
|
-
text-style: bold;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.hint-text{
|
|
43
|
-
margin-top: 1;
|
|
44
|
-
margin-bottom: 1;
|
|
45
|
-
padding: 0 1;
|
|
46
|
-
color: white;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.message-container {
|
|
50
|
-
margin: 1;
|
|
51
|
-
padding: 1;
|
|
52
|
-
background: $surface;
|
|
53
|
-
height: auto;
|
|
54
|
-
width: 100%;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.message-header {
|
|
58
|
-
text-align: left;
|
|
59
|
-
color: $primary;
|
|
60
|
-
padding: 0 1;
|
|
61
|
-
text-style: bold;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.input-request-header {
|
|
65
|
-
color: $warning;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.message-content {
|
|
69
|
-
margin-top: 1;
|
|
70
|
-
padding: 0 1;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
Header.running {
|
|
74
|
-
background: $error;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.button-container {
|
|
78
|
-
layout: horizontal;
|
|
79
|
-
align-horizontal: center;
|
|
80
|
-
margin-top: 1;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.button-container Button {
|
|
84
|
-
margin: 0 1;
|
|
85
|
-
min-width: 10;
|
|
86
|
-
}
|
minisweagent/models/anthropic.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import warnings
|
|
3
|
-
from typing import Literal
|
|
4
|
-
|
|
5
|
-
from minisweagent.models.litellm_model import LitellmModel, LitellmModelConfig
|
|
6
|
-
from minisweagent.models.utils.cache_control import set_cache_control
|
|
7
|
-
from minisweagent.models.utils.key_per_thread import get_key_per_thread
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class AnthropicModelConfig(LitellmModelConfig):
|
|
11
|
-
set_cache_control: Literal["default_end"] | None = "default_end"
|
|
12
|
-
"""Set explicit cache control markers, for example for Anthropic models"""
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class AnthropicModel(LitellmModel):
|
|
16
|
-
"""This class is now only a thin wrapper around the LitellmModel class.
|
|
17
|
-
It is largely kept for backwards compatibility.
|
|
18
|
-
It will not be selected by `get_model` and `get_model_class` unless explicitly specified.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, *, config_class: type = AnthropicModelConfig, **kwargs):
|
|
22
|
-
super().__init__(config_class=config_class, **kwargs)
|
|
23
|
-
|
|
24
|
-
def query(self, messages: list[dict], **kwargs) -> dict:
|
|
25
|
-
api_key = None
|
|
26
|
-
# Legacy only
|
|
27
|
-
if rotating_keys := os.getenv("ANTHROPIC_API_KEYS"):
|
|
28
|
-
warnings.warn(
|
|
29
|
-
"ANTHROPIC_API_KEYS is deprecated and will be removed in the future. "
|
|
30
|
-
"Simply use the ANTHROPIC_API_KEY environment variable instead. "
|
|
31
|
-
"Key rotation is no longer required."
|
|
32
|
-
)
|
|
33
|
-
api_key = get_key_per_thread(rotating_keys.split("::"))
|
|
34
|
-
messages = set_cache_control(messages, mode="default_end")
|
|
35
|
-
return super().query(messages, api_key=api_key, **kwargs)
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
import litellm
|
|
6
|
-
from tenacity import (
|
|
7
|
-
before_sleep_log,
|
|
8
|
-
retry,
|
|
9
|
-
retry_if_not_exception_type,
|
|
10
|
-
stop_after_attempt,
|
|
11
|
-
wait_exponential,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
from minisweagent.models.litellm_model import LitellmModel, LitellmModelConfig
|
|
15
|
-
from minisweagent.models.utils.openai_utils import coerce_responses_text
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger("litellm_response_api_model")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@dataclass
|
|
21
|
-
class LitellmResponseAPIModelConfig(LitellmModelConfig):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class LitellmResponseAPIModel(LitellmModel):
|
|
26
|
-
def __init__(self, *, config_class: Callable = LitellmResponseAPIModelConfig, **kwargs):
|
|
27
|
-
super().__init__(config_class=config_class, **kwargs)
|
|
28
|
-
self._previous_response_id: str | None = None
|
|
29
|
-
|
|
30
|
-
@retry(
|
|
31
|
-
reraise=True,
|
|
32
|
-
stop=stop_after_attempt(10),
|
|
33
|
-
wait=wait_exponential(multiplier=1, min=4, max=60),
|
|
34
|
-
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
35
|
-
retry=retry_if_not_exception_type(
|
|
36
|
-
(
|
|
37
|
-
litellm.exceptions.UnsupportedParamsError,
|
|
38
|
-
litellm.exceptions.NotFoundError,
|
|
39
|
-
litellm.exceptions.PermissionDeniedError,
|
|
40
|
-
litellm.exceptions.ContextWindowExceededError,
|
|
41
|
-
litellm.exceptions.APIError,
|
|
42
|
-
litellm.exceptions.AuthenticationError,
|
|
43
|
-
KeyboardInterrupt,
|
|
44
|
-
)
|
|
45
|
-
),
|
|
46
|
-
)
|
|
47
|
-
def _query(self, messages: list[dict[str, str]], **kwargs):
|
|
48
|
-
try:
|
|
49
|
-
# Remove 'timestamp' field added by agent - not supported by OpenAI responses API
|
|
50
|
-
clean_messages = [{"role": msg["role"], "content": msg["content"]} for msg in messages]
|
|
51
|
-
resp = litellm.responses(
|
|
52
|
-
model=self.config.model_name,
|
|
53
|
-
input=clean_messages if self._previous_response_id is None else clean_messages[-1:],
|
|
54
|
-
previous_response_id=self._previous_response_id,
|
|
55
|
-
**(self.config.model_kwargs | kwargs),
|
|
56
|
-
)
|
|
57
|
-
self._previous_response_id = getattr(resp, "id", None)
|
|
58
|
-
return resp
|
|
59
|
-
except litellm.exceptions.AuthenticationError as e:
|
|
60
|
-
e.message += " You can permanently set your API key with `mini-extra config set KEY VALUE`."
|
|
61
|
-
raise e
|
|
62
|
-
|
|
63
|
-
def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
|
|
64
|
-
response = self._query(messages, **kwargs)
|
|
65
|
-
text = coerce_responses_text(response)
|
|
66
|
-
try:
|
|
67
|
-
cost = litellm.cost_calculator.completion_cost(response, model=self.config.model_name)
|
|
68
|
-
except Exception as e:
|
|
69
|
-
logger.critical(
|
|
70
|
-
f"Error calculating cost for model {self.config.model_name}: {e}. "
|
|
71
|
-
"Please check the 'Updating the model registry' section in the documentation. "
|
|
72
|
-
"http://bit.ly/4p31bi4 Still stuck? Please open a github issue for help!"
|
|
73
|
-
)
|
|
74
|
-
raise
|
|
75
|
-
self.n_calls += 1
|
|
76
|
-
self.cost += cost
|
|
77
|
-
from minisweagent.models import GLOBAL_MODEL_STATS
|
|
78
|
-
|
|
79
|
-
GLOBAL_MODEL_STATS.add(cost)
|
|
80
|
-
return {
|
|
81
|
-
"content": text,
|
|
82
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
import litellm
|
|
6
|
-
from tenacity import (
|
|
7
|
-
before_sleep_log,
|
|
8
|
-
retry,
|
|
9
|
-
retry_if_not_exception_type,
|
|
10
|
-
stop_after_attempt,
|
|
11
|
-
wait_exponential,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
from minisweagent.models import GLOBAL_MODEL_STATS
|
|
15
|
-
from minisweagent.models.portkey_model import PortkeyModel, PortkeyModelConfig
|
|
16
|
-
from minisweagent.models.utils.cache_control import set_cache_control
|
|
17
|
-
from minisweagent.models.utils.openai_utils import coerce_responses_text
|
|
18
|
-
|
|
19
|
-
logger = logging.getLogger("portkey_response_api_model")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclass
|
|
23
|
-
class PortkeyResponseAPIModelConfig(PortkeyModelConfig):
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class PortkeyResponseAPIModel(PortkeyModel):
|
|
28
|
-
def __init__(self, *, config_class: type = PortkeyResponseAPIModelConfig, **kwargs):
|
|
29
|
-
super().__init__(config_class=config_class, **kwargs)
|
|
30
|
-
self._previous_response_id: str | None = None
|
|
31
|
-
|
|
32
|
-
@retry(
|
|
33
|
-
reraise=True,
|
|
34
|
-
stop=stop_after_attempt(int(os.getenv("MSWEA_MODEL_RETRY_STOP_AFTER_ATTEMPT", "10"))),
|
|
35
|
-
wait=wait_exponential(multiplier=1, min=4, max=60),
|
|
36
|
-
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
37
|
-
retry=retry_if_not_exception_type((KeyboardInterrupt, TypeError, ValueError)),
|
|
38
|
-
)
|
|
39
|
-
def _query(self, messages: list[dict[str, str]], **kwargs):
|
|
40
|
-
input_messages = messages if self._previous_response_id is None else messages[-1:]
|
|
41
|
-
resp = self.client.responses.create(
|
|
42
|
-
model=self.config.model_name,
|
|
43
|
-
input=input_messages,
|
|
44
|
-
previous_response_id=self._previous_response_id,
|
|
45
|
-
**(self.config.model_kwargs | kwargs),
|
|
46
|
-
)
|
|
47
|
-
self._previous_response_id = getattr(resp, "id", None)
|
|
48
|
-
return resp
|
|
49
|
-
|
|
50
|
-
def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
|
|
51
|
-
if self.config.set_cache_control:
|
|
52
|
-
messages = set_cache_control(messages, mode=self.config.set_cache_control)
|
|
53
|
-
response = self._query(messages, **kwargs)
|
|
54
|
-
text = coerce_responses_text(response)
|
|
55
|
-
try:
|
|
56
|
-
cost = litellm.cost_calculator.completion_cost(response, model=self.config.model_name)
|
|
57
|
-
assert cost > 0.0, f"Cost is not positive: {cost}"
|
|
58
|
-
except Exception as e:
|
|
59
|
-
if self.config.cost_tracking != "ignore_errors":
|
|
60
|
-
raise RuntimeError(
|
|
61
|
-
f"Error calculating cost for model {self.config.model_name}: {e}. "
|
|
62
|
-
"You can ignore this issue from your config file with cost_tracking: 'ignore_errors' or "
|
|
63
|
-
"globally with export MSWEA_COST_TRACKING='ignore_errors' to ignore this error. "
|
|
64
|
-
) from e
|
|
65
|
-
cost = 0.0
|
|
66
|
-
self.n_calls += 1
|
|
67
|
-
self.cost += cost
|
|
68
|
-
GLOBAL_MODEL_STATS.add(cost)
|
|
69
|
-
return {
|
|
70
|
-
"content": text,
|
|
71
|
-
"extra": {
|
|
72
|
-
"response": response.model_dump() if hasattr(response, "model_dump") else {},
|
|
73
|
-
"cost": cost,
|
|
74
|
-
},
|
|
75
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""Utility for anthropic where we need different keys for different parallel
|
|
2
|
-
agents to not mess up prompt caching.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import threading
|
|
6
|
-
import warnings
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
_THREADS_THAT_USED_API_KEYS: list[Any] = []
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_key_per_thread(api_keys: list[Any]) -> Any:
|
|
13
|
-
"""Choose key based on thread name. Returns None if no keys are available."""
|
|
14
|
-
warnings.warn("get_key_per_thread is deprecated and will be removed in the future")
|
|
15
|
-
thread_name = threading.current_thread().name
|
|
16
|
-
if thread_name not in _THREADS_THAT_USED_API_KEYS:
|
|
17
|
-
_THREADS_THAT_USED_API_KEYS.append(thread_name)
|
|
18
|
-
thread_idx = _THREADS_THAT_USED_API_KEYS.index(thread_name)
|
|
19
|
-
key_idx = thread_idx % len(api_keys)
|
|
20
|
-
return api_keys[key_idx] or None
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from openai.types.responses.response_output_message import ResponseOutputMessage
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger("openai_utils")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def coerce_responses_text(resp: Any) -> str:
|
|
10
|
-
"""Helper to normalize OpenAI Responses API result to text.
|
|
11
|
-
|
|
12
|
-
Works with both OpenAI client responses and LiteLLM/Portkey responses.
|
|
13
|
-
"""
|
|
14
|
-
text = getattr(resp, "output_text", None)
|
|
15
|
-
if isinstance(text, str) and text:
|
|
16
|
-
return text
|
|
17
|
-
|
|
18
|
-
try:
|
|
19
|
-
output = []
|
|
20
|
-
for item in resp.output:
|
|
21
|
-
if isinstance(item, dict):
|
|
22
|
-
content = item.get("content", [])
|
|
23
|
-
elif isinstance(item, ResponseOutputMessage):
|
|
24
|
-
content = item.content
|
|
25
|
-
else:
|
|
26
|
-
continue
|
|
27
|
-
|
|
28
|
-
for content_item in content:
|
|
29
|
-
if isinstance(content_item, dict):
|
|
30
|
-
text_val = content_item.get("text")
|
|
31
|
-
elif hasattr(content_item, "text"):
|
|
32
|
-
text_val = content_item.text
|
|
33
|
-
else:
|
|
34
|
-
continue
|
|
35
|
-
|
|
36
|
-
if text_val:
|
|
37
|
-
output.append(text_val)
|
|
38
|
-
return "\n\n".join(output) or ""
|
|
39
|
-
except (AttributeError, IndexError, TypeError):
|
|
40
|
-
logger.warning(f"Could not extract text from response: {resp}")
|
|
41
|
-
return ""
|
minisweagent/run/github_issue.py
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import os
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import requests
|
|
6
|
-
import typer
|
|
7
|
-
import yaml
|
|
8
|
-
from rich.console import Console
|
|
9
|
-
|
|
10
|
-
from minisweagent.agents.interactive import InteractiveAgent
|
|
11
|
-
from minisweagent.config import builtin_config_dir, get_config_path
|
|
12
|
-
from minisweagent.environments.docker import DockerEnvironment
|
|
13
|
-
from minisweagent.models import get_model
|
|
14
|
-
from minisweagent.run.extra.config import configure_if_first_time
|
|
15
|
-
from minisweagent.run.utils.save import save_traj
|
|
16
|
-
|
|
17
|
-
DEFAULT_CONFIG = Path(os.getenv("MSWEA_GITHUB_CONFIG_PATH", builtin_config_dir / "github_issue.yaml"))
|
|
18
|
-
console = Console(highlight=False)
|
|
19
|
-
app = typer.Typer(rich_markup_mode="rich", add_completion=False)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def fetch_github_issue(issue_url: str) -> str:
|
|
23
|
-
"""Fetch GitHub issue text from the URL."""
|
|
24
|
-
# Convert GitHub issue URL to API URL
|
|
25
|
-
api_url = issue_url.replace("github.com", "api.github.com/repos").replace("/issues/", "/issues/")
|
|
26
|
-
|
|
27
|
-
headers = {}
|
|
28
|
-
if github_token := os.getenv("GITHUB_TOKEN"):
|
|
29
|
-
headers["Authorization"] = f"token {github_token}"
|
|
30
|
-
|
|
31
|
-
response = requests.get(api_url, headers=headers)
|
|
32
|
-
issue_data = response.json()
|
|
33
|
-
|
|
34
|
-
title = issue_data["title"]
|
|
35
|
-
body = issue_data["body"] or ""
|
|
36
|
-
|
|
37
|
-
return f"GitHub Issue: {title}\n\n{body}"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# fmt: off
|
|
41
|
-
@app.command()
|
|
42
|
-
def main(
|
|
43
|
-
issue_url: str = typer.Option(prompt="Enter GitHub issue URL", help="GitHub issue URL"),
|
|
44
|
-
config: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
|
|
45
|
-
model: str | None = typer.Option(None, "-m", "--model", help="Model to use"),
|
|
46
|
-
model_class: str | None = typer.Option(None, "--model-class", help="Model class to use (e.g., 'anthropic' or 'minisweagent.models.anthropic.AnthropicModel')", rich_help_panel="Advanced"),
|
|
47
|
-
yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
|
|
48
|
-
) -> InteractiveAgent:
|
|
49
|
-
# fmt: on
|
|
50
|
-
"""Run mini-SWE-agent on a GitHub issue"""
|
|
51
|
-
configure_if_first_time()
|
|
52
|
-
|
|
53
|
-
config_path = get_config_path(config)
|
|
54
|
-
console.print(f"Loading agent config from [bold green]'{config_path}'[/bold green]")
|
|
55
|
-
_config = yaml.safe_load(config_path.read_text())
|
|
56
|
-
_agent_config = _config.setdefault("agent", {})
|
|
57
|
-
if yolo:
|
|
58
|
-
_agent_config["mode"] = "yolo"
|
|
59
|
-
if model_class is not None:
|
|
60
|
-
_config.setdefault("model", {})["model_class"] = model_class
|
|
61
|
-
|
|
62
|
-
task = fetch_github_issue(issue_url)
|
|
63
|
-
|
|
64
|
-
agent = InteractiveAgent(
|
|
65
|
-
get_model(model, _config.get("model", {})),
|
|
66
|
-
DockerEnvironment(**_config.get("environment", {})),
|
|
67
|
-
**_agent_config,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
repo_url = issue_url.split("/issues/")[0]
|
|
71
|
-
if github_token := os.getenv("GITHUB_TOKEN"):
|
|
72
|
-
repo_url = repo_url.replace("https://github.com/", f"https://{github_token}@github.com/") + ".git"
|
|
73
|
-
|
|
74
|
-
agent.env.execute(f"git clone {repo_url} /testbed", cwd="/")
|
|
75
|
-
|
|
76
|
-
exit_status, result = None, None
|
|
77
|
-
try:
|
|
78
|
-
exit_status, result = agent.run(task)
|
|
79
|
-
except KeyboardInterrupt:
|
|
80
|
-
console.print("\n[bold red]KeyboardInterrupt -- goodbye[/bold red]")
|
|
81
|
-
finally:
|
|
82
|
-
save_traj(agent, Path("traj.json"), exit_status=exit_status, result=result)
|
|
83
|
-
return agent
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if __name__ == "__main__":
|
|
87
|
-
app()
|
|
File without changes
|
minisweagent/run/utils/save.py
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import json
|
|
3
|
-
from collections.abc import Callable
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from minisweagent import Agent, __version__
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _get_class_name_with_module(obj: Any) -> str:
|
|
11
|
-
"""Get the full class name with module path."""
|
|
12
|
-
return f"{obj.__class__.__module__}.{obj.__class__.__name__}"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _asdict(obj: Any) -> dict:
|
|
16
|
-
"""Convert config objects to dicts."""
|
|
17
|
-
if dataclasses.is_dataclass(obj):
|
|
18
|
-
return dataclasses.asdict(obj) # type: ignore[arg-type]
|
|
19
|
-
return obj # let's try our luck
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def save_traj(
|
|
23
|
-
agent: Agent | None,
|
|
24
|
-
path: Path | None,
|
|
25
|
-
*,
|
|
26
|
-
print_path: bool = True,
|
|
27
|
-
exit_status: str | None = None,
|
|
28
|
-
result: str | None = None,
|
|
29
|
-
extra_info: dict | None = None,
|
|
30
|
-
print_fct: Callable = print,
|
|
31
|
-
**kwargs,
|
|
32
|
-
):
|
|
33
|
-
"""Save the trajectory of the agent to a file.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
agent: The agent to save the trajectory of.
|
|
37
|
-
path: The path to save the trajectory to.
|
|
38
|
-
print_path: Whether to print confirmation of path to the terminal.
|
|
39
|
-
exit_status: The exit status of the agent.
|
|
40
|
-
result: The result/submission of the agent.
|
|
41
|
-
extra_info: Extra information to save (will be merged into the info dict).
|
|
42
|
-
**kwargs: Additional information to save (will be merged into top level)
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
if path is None:
|
|
46
|
-
return
|
|
47
|
-
data = {
|
|
48
|
-
"info": {
|
|
49
|
-
"exit_status": exit_status,
|
|
50
|
-
"submission": result,
|
|
51
|
-
"model_stats": {
|
|
52
|
-
"instance_cost": 0.0,
|
|
53
|
-
"api_calls": 0,
|
|
54
|
-
},
|
|
55
|
-
"mini_version": __version__,
|
|
56
|
-
},
|
|
57
|
-
"messages": [],
|
|
58
|
-
"trajectory_format": "mini-swe-agent-1",
|
|
59
|
-
} | kwargs
|
|
60
|
-
if agent is not None:
|
|
61
|
-
data["info"]["model_stats"]["instance_cost"] = agent.model.cost
|
|
62
|
-
data["info"]["model_stats"]["api_calls"] = agent.model.n_calls
|
|
63
|
-
data["messages"] = agent.messages
|
|
64
|
-
data["info"]["config"] = {
|
|
65
|
-
"agent": _asdict(agent.config),
|
|
66
|
-
"model": _asdict(agent.model.config),
|
|
67
|
-
"environment": _asdict(agent.env.config),
|
|
68
|
-
"agent_type": _get_class_name_with_module(agent),
|
|
69
|
-
"model_type": _get_class_name_with_module(agent.model),
|
|
70
|
-
"environment_type": _get_class_name_with_module(agent.env),
|
|
71
|
-
}
|
|
72
|
-
if extra_info:
|
|
73
|
-
data["info"].update(extra_info)
|
|
74
|
-
|
|
75
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
-
path.write_text(json.dumps(data, indent=2))
|
|
77
|
-
if print_path:
|
|
78
|
-
print_fct(f"Saved trajectory to '{path}'")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|