app_config.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import os
  2. import uuid
  3. from dataclasses import dataclass, field, fields, is_dataclass
  4. from typing import ClassVar
  5. from openhands.core import logger
  6. from openhands.core.config.agent_config import AgentConfig
  7. from openhands.core.config.config_utils import (
  8. OH_DEFAULT_AGENT,
  9. OH_MAX_ITERATIONS,
  10. UndefinedString,
  11. get_field_info,
  12. )
  13. from openhands.core.config.llm_config import LLMConfig
  14. from openhands.core.config.sandbox_config import SandboxConfig
  15. from openhands.core.config.security_config import SecurityConfig
  16. @dataclass
  17. class AppConfig:
  18. """Configuration for the app.
  19. Attributes:
  20. llms: A dictionary of name -> LLM configuration. Default config is under 'llm' key.
  21. agents: A dictionary of name -> Agent configuration. Default config is under 'agent' key.
  22. default_agent: The name of the default agent to use.
  23. sandbox: The sandbox configuration.
  24. runtime: The runtime environment.
  25. file_store: The file store to use.
  26. file_store_path: The path to the file store.
  27. workspace_base: The base path for the workspace. Defaults to ./workspace as an absolute path.
  28. workspace_mount_path: The path to mount the workspace. This is set to the workspace base by default.
  29. workspace_mount_path_in_sandbox: The path to mount the workspace in the sandbox. Defaults to /workspace.
  30. workspace_mount_rewrite: The path to rewrite the workspace mount path to.
  31. cache_dir: The path to the cache directory. Defaults to /tmp/cache.
  32. run_as_openhands: Whether to run as openhands.
  33. max_iterations: The maximum number of iterations.
  34. max_budget_per_task: The maximum budget allowed per task, beyond which the agent will stop.
  35. e2b_api_key: The E2B API key.
  36. disable_color: Whether to disable color. For terminals that don't support color.
  37. debug: Whether to enable debugging.
  38. enable_cli_session: Whether to enable saving and restoring the session when run from CLI.
  39. file_uploads_max_file_size_mb: Maximum file size for uploads in megabytes. 0 means no limit.
  40. file_uploads_restrict_file_types: Whether to restrict file types for file uploads. Defaults to False.
  41. file_uploads_allowed_extensions: List of allowed file extensions for uploads. ['.*'] means all extensions are allowed.
  42. """
  43. llms: dict[str, LLMConfig] = field(default_factory=dict)
  44. agents: dict = field(default_factory=dict)
  45. default_agent: str = OH_DEFAULT_AGENT
  46. sandbox: SandboxConfig = field(default_factory=SandboxConfig)
  47. security: SecurityConfig = field(default_factory=SecurityConfig)
  48. runtime: str = 'eventstream'
  49. file_store: str = 'memory'
  50. file_store_path: str = '/tmp/file_store'
  51. # TODO: clean up workspace path after the removal of ServerRuntime
  52. workspace_base: str = os.path.join(os.getcwd(), 'workspace')
  53. workspace_mount_path: str | None = (
  54. UndefinedString.UNDEFINED # this path should always be set when config is fully loaded
  55. ) # when set to None, do not mount the workspace
  56. workspace_mount_path_in_sandbox: str = '/workspace'
  57. workspace_mount_rewrite: str | None = None
  58. cache_dir: str = '/tmp/cache'
  59. run_as_openhands: bool = True
  60. max_iterations: int = OH_MAX_ITERATIONS
  61. max_budget_per_task: float | None = None
  62. e2b_api_key: str = ''
  63. disable_color: bool = False
  64. jwt_secret: str = uuid.uuid4().hex
  65. debug: bool = False
  66. enable_cli_session: bool = False
  67. file_uploads_max_file_size_mb: int = 0
  68. file_uploads_restrict_file_types: bool = False
  69. file_uploads_allowed_extensions: list[str] = field(default_factory=lambda: ['.*'])
  70. defaults_dict: ClassVar[dict] = {}
  71. def get_llm_config(self, name='llm') -> LLMConfig:
  72. """Llm is the name for default config (for backward compatibility prior to 0.8)"""
  73. if name in self.llms:
  74. return self.llms[name]
  75. if name is not None and name != 'llm':
  76. logger.openhands_logger.warning(
  77. f'llm config group {name} not found, using default config'
  78. )
  79. if 'llm' not in self.llms:
  80. self.llms['llm'] = LLMConfig()
  81. return self.llms['llm']
  82. def set_llm_config(self, value: LLMConfig, name='llm'):
  83. self.llms[name] = value
  84. def get_agent_config(self, name='agent') -> AgentConfig:
  85. """Agent is the name for default config (for backward compability prior to 0.8)"""
  86. if name in self.agents:
  87. return self.agents[name]
  88. if 'agent' not in self.agents:
  89. self.agents['agent'] = AgentConfig()
  90. return self.agents['agent']
  91. def set_agent_config(self, value: AgentConfig, name='agent'):
  92. self.agents[name] = value
  93. def get_agent_to_llm_config_map(self) -> dict[str, LLMConfig]:
  94. """Get a map of agent names to llm configs."""
  95. return {name: self.get_llm_config_from_agent(name) for name in self.agents}
  96. def get_llm_config_from_agent(self, name='agent') -> LLMConfig:
  97. agent_config: AgentConfig = self.get_agent_config(name)
  98. llm_config_name = agent_config.llm_config
  99. return self.get_llm_config(llm_config_name)
  100. def get_agent_configs(self) -> dict[str, AgentConfig]:
  101. return self.agents
  102. def __post_init__(self):
  103. """Post-initialization hook, called when the instance is created with only default values."""
  104. AppConfig.defaults_dict = self.defaults_to_dict()
  105. def defaults_to_dict(self) -> dict:
  106. """Serialize fields to a dict for the frontend, including type hints, defaults, and whether it's optional."""
  107. result = {}
  108. for f in fields(self):
  109. field_value = getattr(self, f.name)
  110. # dataclasses compute their defaults themselves
  111. if is_dataclass(type(field_value)):
  112. result[f.name] = field_value.defaults_to_dict()
  113. else:
  114. result[f.name] = get_field_info(f)
  115. return result
  116. def __str__(self):
  117. attr_str = []
  118. for f in fields(self):
  119. attr_name = f.name
  120. attr_value = getattr(self, f.name)
  121. if attr_name in [
  122. 'e2b_api_key',
  123. 'github_token',
  124. 'jwt_secret',
  125. ]:
  126. attr_value = '******' if attr_value else None
  127. attr_str.append(f'{attr_name}={repr(attr_value)}')
  128. return f"AppConfig({', '.join(attr_str)}"
  129. def __repr__(self):
  130. return self.__str__()