Files
OpenHands/opendevin/llm/llm.py
Christian Balcom 24b71927c3 fix(backend) changes to improve Command-R+ behavior, plus file i/o error improvements, attempt 2 (#1417)
* Some improvements to prompts, some better exception handling for various file IO errors, added timeout and max return token configurations for the LLM api.

* More monologue prompt improvements

* Dynamically set username provided in prompt.

* Remove absolute paths from llm prompts, fetch working directory from sandbox when resolving paths in fileio operations, add customizable timeout for bash commands, mention said timeout in llm prompt.

* Switched ssh_box to disabling tty echo and removed the logic attempting to delete it from the response afterwards, fixed get_working_directory for ssh_box.

* Update prompts in integration tests to match monologue agent changes.

* Minor tweaks to make merge easier.

* Another minor prompt tweak, better invalid json handling.

* Fix lint error

* More catch-up to fix lint errors introduced by merge.

* Force WORKSPACE_MOUNT_PATH_IN_SANDBOX to match WORKSPACE_MOUNT_PATH in local sandbox mode, combine exception handlers in prompts.py.

---------

Co-authored-by: Jim Su <jimsu@protonmail.com>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2024-04-28 21:58:53 -04:00

105 lines
5.1 KiB
Python

from litellm import completion as litellm_completion
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_random_exponential
from litellm.exceptions import APIConnectionError, RateLimitError, ServiceUnavailableError
from functools import partial
from opendevin import config
from opendevin.logger import llm_prompt_logger, llm_response_logger
from opendevin.logger import opendevin_logger as logger
from opendevin.schema import ConfigType
DEFAULT_API_KEY = config.get(ConfigType.LLM_API_KEY)
DEFAULT_BASE_URL = config.get(ConfigType.LLM_BASE_URL)
DEFAULT_MODEL_NAME = config.get(ConfigType.LLM_MODEL)
DEFAULT_API_VERSION = config.get(ConfigType.LLM_API_VERSION)
LLM_NUM_RETRIES = config.get(ConfigType.LLM_NUM_RETRIES)
LLM_RETRY_MIN_WAIT = config.get(ConfigType.LLM_RETRY_MIN_WAIT)
LLM_RETRY_MAX_WAIT = config.get(ConfigType.LLM_RETRY_MAX_WAIT)
LLM_TIMEOUT = config.get(ConfigType.LLM_TIMEOUT)
LLM_MAX_RETURN_TOKENS = config.get(ConfigType.LLM_MAX_RETURN_TOKENS)
class LLM:
"""
The LLM class represents a Language Model instance.
"""
def __init__(self,
model=DEFAULT_MODEL_NAME,
api_key=DEFAULT_API_KEY,
base_url=DEFAULT_BASE_URL,
api_version=DEFAULT_API_VERSION,
num_retries=LLM_NUM_RETRIES,
retry_min_wait=LLM_RETRY_MIN_WAIT,
retry_max_wait=LLM_RETRY_MAX_WAIT,
llm_timeout=LLM_TIMEOUT,
llm_max_return_tokens=LLM_MAX_RETURN_TOKENS
):
"""
Args:
model (str, optional): The name of the language model. Defaults to LLM_MODEL.
api_key (str, optional): The API key for accessing the language model. Defaults to LLM_API_KEY.
base_url (str, optional): The base URL for the language model API. Defaults to LLM_BASE_URL. Not necessary for OpenAI.
api_version (str, optional): The version of the API to use. Defaults to LLM_API_VERSION. Not necessary for OpenAI.
num_retries (int, optional): The number of retries for API calls. Defaults to LLM_NUM_RETRIES.
retry_min_wait (int, optional): The minimum time to wait between retries in seconds. Defaults to LLM_RETRY_MIN_TIME.
retry_max_wait (int, optional): The maximum time to wait between retries in seconds. Defaults to LLM_RETRY_MAX_TIME.
llm_timeout (int, optional): The maximum time to wait for a response in seconds. Defaults to LLM_TIMEOUT.
llm_max_return_tokens (int, optional): The maximum number of tokens to return. Defaults to LLM_MAX_RETURN_TOKENS.
Attributes:
model_name (str): The name of the language model.
api_key (str): The API key for accessing the language model.
base_url (str): The base URL for the language model API.
api_version (str): The version of the API to use.
completion (function): A decorator for the litellm completion function.
"""
logger.info(f'Initializing LLM with model: {model}')
self.model_name = model
self.api_key = api_key
self.base_url = base_url
self.api_version = api_version
self.llm_timeout = llm_timeout
self.llm_max_return_tokens = llm_max_return_tokens
self._completion = partial(
litellm_completion, model=self.model_name, api_key=self.api_key, base_url=self.base_url, api_version=self.api_version, max_tokens=self.llm_max_return_tokens, timeout=self.llm_timeout)
completion_unwrapped = self._completion
def attempt_on_error(retry_state):
logger.error(f'{retry_state.outcome.exception()}. Attempt #{retry_state.attempt_number} | You can customize these settings in the configuration.', exc_info=False)
return True
@retry(reraise=True,
stop=stop_after_attempt(num_retries),
wait=wait_random_exponential(min=retry_min_wait, max=retry_max_wait), retry=retry_if_exception_type((RateLimitError, APIConnectionError, ServiceUnavailableError)), after=attempt_on_error)
def wrapper(*args, **kwargs):
if 'messages' in kwargs:
messages = kwargs['messages']
else:
messages = args[1]
debug_message = ''
for message in messages:
debug_message += '\n\n----------\n\n' + message['content']
llm_prompt_logger.debug(debug_message)
resp = completion_unwrapped(*args, **kwargs)
message_back = resp['choices'][0]['message']['content']
llm_response_logger.debug(message_back)
return resp
self._completion = wrapper # type: ignore
@property
def completion(self):
"""
Decorator for the litellm completion function.
"""
return self._completion
def __str__(self):
if self.api_version:
return f'LLM(model={self.model_name}, api_version={self.api_version}, base_url={self.base_url})'
elif self.base_url:
return f'LLM(model={self.model_name}, base_url={self.base_url})'
return f'LLM(model={self.model_name})'