mini-swe-agent 1.13.4__py3-none-any.whl → 1.14.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-swe-agent
3
- Version: 1.13.4
3
+ Version: 1.14.0
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
@@ -1,5 +1,5 @@
1
- mini_swe_agent-1.13.4.dist-info/licenses/LICENSE.md,sha256=D3luWPkdHAe7LBsdD4vzqDAXw6Xewb3G-uczss0uh1s,1094
2
- minisweagent/__init__.py,sha256=BrTgbkr33E2jWEByqcdU-bYDBm8jgXYtL17_H44Yb0A,2016
1
+ mini_swe_agent-1.14.0.dist-info/licenses/LICENSE.md,sha256=D3luWPkdHAe7LBsdD4vzqDAXw6Xewb3G-uczss0uh1s,1094
2
+ minisweagent/__init__.py,sha256=x76NxcXdWcajgoVsrPVO4EaR79r1Bnk3bF6wifAmcYo,2016
3
3
  minisweagent/__main__.py,sha256=FIyAOiw--c3FQ2g240FOM1FdL0lk_PxSpixu0pQ7WFo,194
4
4
  minisweagent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  minisweagent/agents/__init__.py,sha256=cpjJLzg1IGxLM-tZpoMJV9S33ye13XtdBO0x7DU_Lrk,48
@@ -23,17 +23,17 @@ minisweagent/environments/singularity.py,sha256=HSwRTWef7cMCgBiGAh5DIrxW8HkZ9C9Z
23
23
  minisweagent/environments/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  minisweagent/environments/extra/bubblewrap.py,sha256=G12Dm63N30qByfLb1SKNsI4G4gLyKBfomnOIsPqRNZk,3662
25
25
  minisweagent/environments/extra/swerex_docker.py,sha256=WPYbohT_vqTHkde9cxpbV6chLXCpLl0PDAcgMbZsV0M,1707
26
- minisweagent/models/__init__.py,sha256=hMRTK5yfn2vymiXecZsSAGusxsTvrnQQVm8ih0y_PNM,4081
27
- minisweagent/models/anthropic.py,sha256=D8nHvvbgzPjla0He8p0O9kaXASPWg1Sai0pHsAj_Yn8,855
28
- minisweagent/models/litellm_model.py,sha256=RSvNzK6ksOOcTEY-kM36KJql9-iSH29kq0cKYXj2mxE,3034
29
- minisweagent/models/openrouter_model.py,sha256=ugI3322pyUQ8wBOuMysPoKlp0oRAmR38a6GcdjE6qHI,3748
30
- minisweagent/models/portkey_model.py,sha256=IcFNz33AYeYgj0N9PGWV7QCn1fT8MfMWZx_OEzLaPsc,4548
26
+ minisweagent/models/__init__.py,sha256=Ol95N3oEDlojz7IBw10A7xf5rKfxtjESyJuEWOwZyzo,4171
27
+ minisweagent/models/anthropic.py,sha256=4p-LxQ_RYQUX1rBsffAj3T1bBb2uMRhA4IyKfDcMpgo,1517
28
+ minisweagent/models/litellm_model.py,sha256=qAYsFBzZGPRGGQfbNCJXKH9eRpzmJKZ9ORTJHsOdhL0,3419
29
+ minisweagent/models/openrouter_model.py,sha256=7SwS4Raw-i_gAM9AGJ8EWoTrnCP8MfHqWf7UZ0sb4ds,4095
30
+ minisweagent/models/portkey_model.py,sha256=lARromtKwiXJtxxXScOkp9PCl3oS76QMQh_6DeehoiI,4895
31
31
  minisweagent/models/test_models.py,sha256=ItCA6ddntzkYA7dzSuUEaLMV-AE8TBuXBFP8CzpiO3U,1351
32
32
  minisweagent/models/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  minisweagent/models/extra/roulette.py,sha256=SqLj_wz9Vkbxou7i9Ef4Uzmg_eheDouNySkkV7pm2Ys,2093
34
34
  minisweagent/models/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- minisweagent/models/utils/cache_control.py,sha256=mG9cE56HQaUwXfoqvXoH6LcbMV_G1vlEE1aBBpikXYg,1608
36
- minisweagent/models/utils/key_per_thread.py,sha256=Vlxt--rapNNYCgIHrMCu1WVAkuiVIhC_awbarkbnkZQ,644
35
+ minisweagent/models/utils/cache_control.py,sha256=rAjW_FPWxG7bnuNe94CARAq9LKaTgFtSSp9zMQvssRs,2535
36
+ minisweagent/models/utils/key_per_thread.py,sha256=4YZXATIw-Fozi7M-1i-wyjIBf-GtQM71kkOHxSPkwrE,748
37
37
  minisweagent/run/__init__.py,sha256=WIoYgHVl7iZF2YncrfV3IttupG6P5KogroKHKECka3A,38
38
38
  minisweagent/run/github_issue.py,sha256=35mZoPLc4JV6XXJKRv55lnuKbXf5lDftd51N89-x9J0,3192
39
39
  minisweagent/run/hello_world.py,sha256=erLnEwNmPFLxq3-8zyv66Vy1kIqMqQf97vISX7LrQXg,959
@@ -50,8 +50,8 @@ minisweagent/run/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
50
50
  minisweagent/run/utils/save.py,sha256=2xd-UnUzI7Fr_AUZ5KEJ53Aa4kpuuGYxkLwyUcvqyMM,2503
51
51
  minisweagent/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  minisweagent/utils/log.py,sha256=ruDMNKMrVC9NPvCeHwO3QYz5jsVNUGQB2dRAEAPAWp8,996
53
- mini_swe_agent-1.13.4.dist-info/METADATA,sha256=H4fwYy_JfcF7-Q6Z1_GRTFEheiqfSxnA65cDA-A3WG4,14151
54
- mini_swe_agent-1.13.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
- mini_swe_agent-1.13.4.dist-info/entry_points.txt,sha256=d1_yRbTaGjs1UXHa6JQK0sKDGBIVGm8oeW0k2kfbJgQ,182
56
- mini_swe_agent-1.13.4.dist-info/top_level.txt,sha256=zKF4t8bFpV87fdVABZt2Da-vnb4Vkh_CxkwQx5YT4Ew,13
57
- mini_swe_agent-1.13.4.dist-info/RECORD,,
53
+ mini_swe_agent-1.14.0.dist-info/METADATA,sha256=_quEJiibMIZ98LVaarGvBumSvBXWihTaeelQMGF7Wf8,14151
54
+ mini_swe_agent-1.14.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
+ mini_swe_agent-1.14.0.dist-info/entry_points.txt,sha256=d1_yRbTaGjs1UXHa6JQK0sKDGBIVGm8oeW0k2kfbJgQ,182
56
+ mini_swe_agent-1.14.0.dist-info/top_level.txt,sha256=zKF4t8bFpV87fdVABZt2Da-vnb4Vkh_CxkwQx5YT4Ew,13
57
+ mini_swe_agent-1.14.0.dist-info/RECORD,,
minisweagent/__init__.py CHANGED
@@ -8,7 +8,7 @@ This file provides:
8
8
  unless you want the static type checking.
9
9
  """
10
10
 
11
- __version__ = "1.13.4"
11
+ __version__ = "1.14.0"
12
12
 
13
13
  import os
14
14
  from pathlib import Path
@@ -55,6 +55,13 @@ def get_model(input_model_name: str | None = None, config: dict | None = None) -
55
55
  if (from_env := os.getenv("MSWEA_MODEL_API_KEY")) and not str(type(model_class)).endswith("DeterministicModel"):
56
56
  config.setdefault("model_kwargs", {})["api_key"] = from_env
57
57
 
58
+ if (
59
+ any(s in resolved_model_name.lower() for s in ["anthropic", "sonnet", "opus", "claude"])
60
+ and "set_cache_control" not in config
61
+ ):
62
+ # Select cache control for Anthropic models by default
63
+ config["set_cache_control"] = "default_end"
64
+
58
65
  return model_class(**config)
59
66
 
60
67
 
@@ -98,11 +105,6 @@ def get_model_class(model_name: str, model_class: str = "") -> type:
98
105
  msg = f"Unknown model class: {model_class} (resolved to {full_path}, available: {_MODEL_CLASS_MAPPING})"
99
106
  raise ValueError(msg)
100
107
 
101
- if any(s in model_name.lower() for s in ["anthropic", "sonnet", "opus", "claude"]):
102
- from minisweagent.models.anthropic import AnthropicModel
103
-
104
- return AnthropicModel
105
-
106
108
  # Default to LitellmModel
107
109
  from minisweagent.models.litellm_model import LitellmModel
108
110
 
@@ -1,19 +1,35 @@
1
1
  import os
2
+ import warnings
3
+ from typing import Literal
2
4
 
3
- from minisweagent.models.litellm_model import LitellmModel
5
+ from minisweagent.models.litellm_model import LitellmModel, LitellmModelConfig
4
6
  from minisweagent.models.utils.cache_control import set_cache_control
5
7
  from minisweagent.models.utils.key_per_thread import get_key_per_thread
6
8
 
7
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
+
8
15
  class AnthropicModel(LitellmModel):
9
- """For the use of anthropic models, we need to add explicit cache control marks
10
- to the messages or we lose out on the benefits of the cache.
11
- Because break points are limited per key, we also need to rotate between different keys
12
- if running with multiple agents in parallel threads.
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.
13
19
  """
14
20
 
21
+ def __init__(self, *, config_class: type = AnthropicModelConfig, **kwargs):
22
+ super().__init__(config_class=config_class, **kwargs)
23
+
15
24
  def query(self, messages: list[dict], **kwargs) -> dict:
16
25
  api_key = None
26
+ # Legacy only
17
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
+ )
18
33
  api_key = get_key_per_thread(rotating_keys.split("::"))
19
- return super().query(set_cache_control(messages), api_key=api_key, **kwargs)
34
+ messages = set_cache_control(messages, mode="default_end")
35
+ return super().query(messages, api_key=api_key, **kwargs)
@@ -3,7 +3,7 @@ import logging
3
3
  import os
4
4
  from dataclasses import asdict, dataclass, field
5
5
  from pathlib import Path
6
- from typing import Any
6
+ from typing import Any, Literal
7
7
 
8
8
  import litellm
9
9
  from tenacity import (
@@ -15,6 +15,7 @@ from tenacity import (
15
15
  )
16
16
 
17
17
  from minisweagent.models import GLOBAL_MODEL_STATS
18
+ from minisweagent.models.utils.cache_control import set_cache_control
18
19
 
19
20
  logger = logging.getLogger("litellm_model")
20
21
 
@@ -24,11 +25,13 @@ class LitellmModelConfig:
24
25
  model_name: str
25
26
  model_kwargs: dict[str, Any] = field(default_factory=dict)
26
27
  litellm_model_registry: Path | str | None = os.getenv("LITELLM_MODEL_REGISTRY_PATH")
28
+ set_cache_control: Literal["default_end"] | None = None
29
+ """Set explicit cache control markers, for example for Anthropic models"""
27
30
 
28
31
 
29
32
  class LitellmModel:
30
- def __init__(self, **kwargs):
31
- self.config = LitellmModelConfig(**kwargs)
33
+ def __init__(self, *, config_class: type = LitellmModelConfig, **kwargs):
34
+ self.config = config_class(**kwargs)
32
35
  self.cost = 0.0
33
36
  self.n_calls = 0
34
37
  if self.config.litellm_model_registry and Path(self.config.litellm_model_registry).is_file():
@@ -60,6 +63,8 @@ class LitellmModel:
60
63
  raise e
61
64
 
62
65
  def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
66
+ if self.config.set_cache_control:
67
+ messages = set_cache_control(messages, mode=self.config.set_cache_control)
63
68
  response = self._query(messages, **kwargs)
64
69
  try:
65
70
  cost = litellm.cost_calculator.completion_cost(response)
@@ -2,7 +2,7 @@ import json
2
2
  import logging
3
3
  import os
4
4
  from dataclasses import asdict, dataclass, field
5
- from typing import Any
5
+ from typing import Any, Literal
6
6
 
7
7
  import requests
8
8
  from tenacity import (
@@ -14,6 +14,7 @@ from tenacity import (
14
14
  )
15
15
 
16
16
  from minisweagent.models import GLOBAL_MODEL_STATS
17
+ from minisweagent.models.utils.cache_control import set_cache_control
17
18
 
18
19
  logger = logging.getLogger("openrouter_model")
19
20
 
@@ -22,6 +23,8 @@ logger = logging.getLogger("openrouter_model")
22
23
  class OpenRouterModelConfig:
23
24
  model_name: str
24
25
  model_kwargs: dict[str, Any] = field(default_factory=dict)
26
+ set_cache_control: Literal["default_end"] | None = None
27
+ """Set explicit cache control markers, for example for Anthropic models"""
25
28
 
26
29
 
27
30
  class OpenRouterAPIError(Exception):
@@ -90,6 +93,8 @@ class OpenRouterModel:
90
93
  raise OpenRouterAPIError(f"Request failed: {e}") from e
91
94
 
92
95
  def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
96
+ if self.config.set_cache_control:
97
+ messages = set_cache_control(messages, mode=self.config.set_cache_control)
93
98
  response = self._query(messages, **kwargs)
94
99
 
95
100
  # Extract cost from usage information
@@ -3,7 +3,7 @@ import logging
3
3
  import os
4
4
  from dataclasses import asdict, dataclass, field
5
5
  from pathlib import Path
6
- from typing import Any
6
+ from typing import Any, Literal
7
7
 
8
8
  import litellm
9
9
  from tenacity import (
@@ -15,6 +15,7 @@ from tenacity import (
15
15
  )
16
16
 
17
17
  from minisweagent.models import GLOBAL_MODEL_STATS
18
+ from minisweagent.models.utils.cache_control import set_cache_control
18
19
 
19
20
  logger = logging.getLogger("portkey_model")
20
21
 
@@ -37,6 +38,8 @@ class PortkeyModelConfig:
37
38
  doesn't match the Portkey model name.
38
39
  Note that this might change if we get better support for Portkey and change how we calculate costs.
39
40
  """
41
+ set_cache_control: Literal["default_end"] | None = None
42
+ """Set explicit cache control markers, for example for Anthropic models"""
40
43
 
41
44
 
42
45
  class PortkeyModel:
@@ -85,6 +88,8 @@ class PortkeyModel:
85
88
  )
86
89
 
87
90
  def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
91
+ if self.config.set_cache_control:
92
+ messages = set_cache_control(messages, mode=self.config.set_cache_control)
88
93
  response = self._query(messages, **kwargs)
89
94
  response_for_cost_calc = response.model_copy()
90
95
  if self.config.litellm_model_name_override:
@@ -1,3 +1,8 @@
1
+ import copy
2
+ import warnings
3
+ from typing import Literal
4
+
5
+
1
6
  def _get_content_text(entry: dict) -> str:
2
7
  if isinstance(entry["content"], str):
3
8
  return entry["content"]
@@ -29,14 +34,39 @@ def _set_cache_control(entry: dict) -> None:
29
34
  entry["cache_control"] = {"type": "ephemeral"}
30
35
 
31
36
 
32
- def set_cache_control(messages: list[dict], last_n_messages_offset: int = 0) -> list[dict]:
37
+ def set_cache_control(
38
+ messages: list[dict], *, mode: Literal["default_end"] | None = "default_end", last_n_messages_offset: int = 0
39
+ ) -> list[dict]:
33
40
  """This messages processor adds manual cache control marks to the messages."""
34
- new_messages = []
35
- n_tagged = 0
36
- for i_entry, entry in enumerate(reversed(messages)):
41
+ if mode != "default_end":
42
+ raise ValueError(f"Invalid mode: {mode}")
43
+ if last_n_messages_offset:
44
+ warnings.warn("last_n_messages_offset is deprecated and will be removed in the future")
45
+ messages = copy.deepcopy(messages)
46
+
47
+ # Find all user messages
48
+ user_message_indices = []
49
+ for i, msg in enumerate(messages):
50
+ if msg["role"] == "user":
51
+ user_message_indices.append(i)
52
+
53
+ # Clear cache control from all messages first
54
+ for entry in messages:
37
55
  _clear_cache_control(entry)
38
- if n_tagged < 2 and entry["role"] in ["user"] and i_entry >= last_n_messages_offset:
39
- _set_cache_control(entry)
40
- n_tagged += 1
41
- new_messages.append(entry)
42
- return list(reversed(new_messages))
56
+
57
+ # Add cache control to user messages, respecting offset
58
+ if user_message_indices:
59
+ # If offset is specified, exclude the last N messages from getting cache control
60
+ messages_to_cache = user_message_indices
61
+ if last_n_messages_offset > 0:
62
+ messages_to_cache = (
63
+ user_message_indices[:-last_n_messages_offset]
64
+ if last_n_messages_offset < len(user_message_indices)
65
+ else []
66
+ )
67
+
68
+ # Add cache control to the selected user messages
69
+ for idx in messages_to_cache:
70
+ _set_cache_control(messages[idx])
71
+
72
+ return messages
@@ -3,6 +3,7 @@ agents to not mess up prompt caching.
3
3
  """
4
4
 
5
5
  import threading
6
+ import warnings
6
7
  from typing import Any
7
8
 
8
9
  _THREADS_THAT_USED_API_KEYS: list[Any] = []
@@ -10,6 +11,7 @@ _THREADS_THAT_USED_API_KEYS: list[Any] = []
10
11
 
11
12
  def get_key_per_thread(api_keys: list[Any]) -> Any:
12
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")
13
15
  thread_name = threading.current_thread().name
14
16
  if thread_name not in _THREADS_THAT_USED_API_KEYS:
15
17
  _THREADS_THAT_USED_API_KEYS.append(thread_name)