import json import logging import requests from minisweagent.models.openrouter_model import ( OpenRouterAPIError, OpenRouterAuthenticationError, OpenRouterModel, OpenRouterModelConfig, OpenRouterRateLimitError, ) from minisweagent.models.utils.actions_text import format_observation_messages, parse_regex_actions logger = logging.getLogger("openrouter_textbased_model") class OpenRouterTextbasedModelConfig(OpenRouterModelConfig): action_regex: str = r"```mswea_bash_command\s*\n(.*?)\n```" """Regex to extract the action from the LM's output.""" format_error_template: str = ( "Please always provide EXACTLY ONE action in triple backticks, found {{actions|length}} actions." ) """Template used when the LM's output is not in the expected format.""" class OpenRouterTextbasedModel(OpenRouterModel): def __init__(self, **kwargs): super().__init__(**kwargs) self.config = OpenRouterTextbasedModelConfig(**kwargs) def _query(self, messages: list[dict[str, str]], **kwargs): headers = { "Authorization": f"Bearer {self._api_key}", "Content-Type": "application/json", } payload = { "model": self.config.model_name, "messages": messages, "usage": {"include": True}, **(self.config.model_kwargs | kwargs), } try: response = requests.post(self._api_url, headers=headers, data=json.dumps(payload), timeout=60) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as e: if response.status_code == 401: error_msg = "Authentication failed. You can permanently set your API key with `mini-extra config set OPENROUTER_API_KEY YOUR_KEY`." raise OpenRouterAuthenticationError(error_msg) from e elif response.status_code == 429: raise OpenRouterRateLimitError("Rate limit exceeded") from e else: raise OpenRouterAPIError(f"HTTP {response.status_code}: {response.text}") from e except requests.exceptions.RequestException as e: raise OpenRouterAPIError(f"Request failed: {e}") from e def _parse_actions(self, response: dict) -> list[dict]: """Parse actions from the model response. Raises FormatError if not exactly one action.""" content = response["choices"][0]["message"]["content"] or "" return parse_regex_actions( content, action_regex=self.config.action_regex, format_error_template=self.config.format_error_template ) def format_observation_messages( self, message: dict, outputs: list[dict], template_vars: dict | None = None ) -> list[dict]: """Format execution outputs into observation messages.""" return format_observation_messages( outputs, observation_template=self.config.observation_template, template_vars=template_vars, multimodal_regex=self.config.multimodal_regex, )