config.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import os
  2. import argparse
  3. import toml
  4. import pathlib
  5. import platform
  6. from dotenv import load_dotenv
  7. from opendevin.schema import ConfigType
  8. import logging
  9. logger = logging.getLogger(__name__)
  10. DEFAULT_CONTAINER_IMAGE = 'ghcr.io/opendevin/sandbox'
  11. if os.getenv('OPEN_DEVIN_BUILD_VERSION'):
  12. DEFAULT_CONTAINER_IMAGE += ':' + (os.getenv('OPEN_DEVIN_BUILD_VERSION') or '')
  13. else:
  14. DEFAULT_CONTAINER_IMAGE += ':main'
  15. load_dotenv()
  16. DEFAULT_CONFIG: dict = {
  17. ConfigType.LLM_API_KEY: None,
  18. ConfigType.LLM_BASE_URL: None,
  19. ConfigType.WORKSPACE_BASE: os.getcwd(),
  20. ConfigType.WORKSPACE_MOUNT_PATH: None,
  21. ConfigType.WORKSPACE_MOUNT_PATH_IN_SANDBOX: '/workspace',
  22. ConfigType.WORKSPACE_MOUNT_REWRITE: None,
  23. ConfigType.CACHE_DIR: '/tmp/cache', # '/tmp/cache' is the default cache directory
  24. ConfigType.LLM_MODEL: 'gpt-3.5-turbo-1106',
  25. ConfigType.SANDBOX_CONTAINER_IMAGE: DEFAULT_CONTAINER_IMAGE,
  26. ConfigType.RUN_AS_DEVIN: 'true',
  27. ConfigType.LLM_EMBEDDING_MODEL: 'local',
  28. ConfigType.LLM_EMBEDDING_DEPLOYMENT_NAME: None,
  29. ConfigType.LLM_API_VERSION: None,
  30. ConfigType.LLM_NUM_RETRIES: 5,
  31. ConfigType.LLM_RETRY_MIN_WAIT: 3,
  32. ConfigType.LLM_RETRY_MAX_WAIT: 60,
  33. ConfigType.MAX_ITERATIONS: 100,
  34. ConfigType.AGENT_MEMORY_MAX_THREADS: 2,
  35. ConfigType.AGENT_MEMORY_ENABLED: False,
  36. # GPT-4 pricing is $10 per 1M input tokens. Since tokenization happens on LLM side,
  37. # we cannot easily count number of tokens, but we can count characters.
  38. # Assuming 5 characters per token, 5 million is a reasonable default limit.
  39. ConfigType.MAX_CHARS: 5_000_000,
  40. ConfigType.AGENT: 'MonologueAgent',
  41. ConfigType.E2B_API_KEY: '',
  42. ConfigType.SANDBOX_TYPE: 'ssh', # Can be 'ssh', 'exec', or 'e2b'
  43. ConfigType.USE_HOST_NETWORK: 'false',
  44. ConfigType.SSH_HOSTNAME: 'localhost',
  45. ConfigType.DISABLE_COLOR: 'false',
  46. }
  47. config_str = ''
  48. if os.path.exists('config.toml'):
  49. with open('config.toml', 'rb') as f:
  50. config_str = f.read().decode('utf-8')
  51. def int_value(value, default, config_key):
  52. # FIXME use a library
  53. try:
  54. return int(value)
  55. except ValueError:
  56. logger.warning(f'Invalid value for {config_key}: {value} not applied. Using default value {default}')
  57. return default
  58. tomlConfig = toml.loads(config_str)
  59. config = DEFAULT_CONFIG.copy()
  60. for k, v in config.items():
  61. if k in os.environ:
  62. config[k] = os.environ[k]
  63. elif k in tomlConfig:
  64. config[k] = tomlConfig[k]
  65. if k in [ConfigType.LLM_NUM_RETRIES, ConfigType.LLM_RETRY_MIN_WAIT, ConfigType.LLM_RETRY_MAX_WAIT]:
  66. config[k] = int_value(config[k], v, config_key=k)
  67. def get_parser():
  68. parser = argparse.ArgumentParser(
  69. description='Run an agent with a specific task')
  70. parser.add_argument(
  71. '-d',
  72. '--directory',
  73. type=str,
  74. help='The working directory for the agent',
  75. )
  76. parser.add_argument(
  77. '-t', '--task', type=str, default='', help='The task for the agent to perform'
  78. )
  79. parser.add_argument(
  80. '-f',
  81. '--file',
  82. type=str,
  83. help='Path to a file containing the task. Overrides -t if both are provided.',
  84. )
  85. parser.add_argument(
  86. '-c',
  87. '--agent-cls',
  88. default=config.get(ConfigType.AGENT),
  89. type=str,
  90. help='The agent class to use',
  91. )
  92. parser.add_argument(
  93. '-m',
  94. '--model-name',
  95. default=config.get(ConfigType.LLM_MODEL),
  96. type=str,
  97. help='The (litellm) model name to use',
  98. )
  99. parser.add_argument(
  100. '-i',
  101. '--max-iterations',
  102. default=config.get(ConfigType.MAX_ITERATIONS),
  103. type=int,
  104. help='The maximum number of iterations to run the agent',
  105. )
  106. parser.add_argument(
  107. '-n',
  108. '--max-chars',
  109. default=config.get(ConfigType.MAX_CHARS),
  110. type=int,
  111. help='The maximum number of characters to send to and receive from LLM per task',
  112. )
  113. return parser
  114. def parse_arguments():
  115. parser = get_parser()
  116. args, _ = parser.parse_known_args()
  117. if args.directory:
  118. config[ConfigType.WORKSPACE_BASE] = os.path.abspath(args.directory)
  119. print(f'Setting workspace base to {config[ConfigType.WORKSPACE_BASE]}')
  120. return args
  121. args = parse_arguments()
  122. def finalize_config():
  123. if config.get(ConfigType.WORKSPACE_MOUNT_REWRITE) and not config.get(ConfigType.WORKSPACE_MOUNT_PATH):
  124. base = config.get(ConfigType.WORKSPACE_BASE) or os.getcwd()
  125. parts = config[ConfigType.WORKSPACE_MOUNT_REWRITE].split(':')
  126. config[ConfigType.WORKSPACE_MOUNT_PATH] = base.replace(parts[0], parts[1])
  127. if config.get(ConfigType.WORKSPACE_MOUNT_PATH) is None:
  128. config[ConfigType.WORKSPACE_MOUNT_PATH] = os.path.abspath(config[ConfigType.WORKSPACE_BASE])
  129. USE_HOST_NETWORK = config[ConfigType.USE_HOST_NETWORK].lower() != 'false'
  130. if USE_HOST_NETWORK and platform.system() == 'Darwin':
  131. logger.warning(
  132. 'Please upgrade to Docker Desktop 4.29.0 or later to use host network mode on macOS. '
  133. 'See https://github.com/docker/roadmap/issues/238#issuecomment-2044688144 for more information.'
  134. )
  135. config[ConfigType.USE_HOST_NETWORK] = USE_HOST_NETWORK
  136. if config.get(ConfigType.WORKSPACE_MOUNT_PATH) is None:
  137. config[ConfigType.WORKSPACE_MOUNT_PATH] = config.get(ConfigType.WORKSPACE_BASE)
  138. finalize_config()
  139. def get(key: ConfigType, required: bool = False):
  140. """
  141. Get a key from the environment variables or config.toml or default configs.
  142. """
  143. if not isinstance(key, ConfigType):
  144. raise ValueError(f"key '{key}' must be an instance of ConfigType Enum")
  145. value = config.get(key)
  146. if not value and required:
  147. raise KeyError(f"Please set '{key}' in `config.toml` or `.env`.")
  148. return value
  149. _cache_dir = config.get(ConfigType.CACHE_DIR)
  150. if _cache_dir:
  151. pathlib.Path(_cache_dir).mkdir(parents=True, exist_ok=True)