app_config.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. from dataclasses import dataclass, field, fields, is_dataclass
  2. from typing import ClassVar
  3. from openhands.core import logger
  4. from openhands.core.config.agent_config import AgentConfig
  5. from openhands.core.config.config_utils import (
  6. OH_DEFAULT_AGENT,
  7. OH_MAX_ITERATIONS,
  8. get_field_info,
  9. )
  10. from openhands.core.config.llm_config import LLMConfig
  11. from openhands.core.config.sandbox_config import SandboxConfig
  12. from openhands.core.config.security_config import SecurityConfig
  13. @dataclass
  14. class AppConfig:
  15. """Configuration for the app.
  16. Attributes:
  17. llms: Dictionary mapping LLM names to their configurations.
  18. The default configuration is stored under the 'llm' key.
  19. agents: Dictionary mapping agent names to their configurations.
  20. The default configuration is stored under the 'agent' key.
  21. default_agent: Name of the default agent to use.
  22. sandbox: Sandbox configuration settings.
  23. runtime: Runtime environment identifier.
  24. file_store: Type of file store to use.
  25. file_store_path: Path to the file store.
  26. trajectories_path: Folder path to store trajectories.
  27. workspace_base: Base path for the workspace. Defaults to `./workspace` as absolute path.
  28. workspace_mount_path: Path to mount the workspace. Defaults to `workspace_base`.
  29. workspace_mount_path_in_sandbox: Path to mount the workspace in sandbox. Defaults to `/workspace`.
  30. workspace_mount_rewrite: Path to rewrite the workspace mount path.
  31. cache_dir: Path to cache directory. Defaults to `/tmp/cache`.
  32. run_as_openhands: Whether to run as openhands.
  33. max_iterations: Maximum number of iterations allowed.
  34. max_budget_per_task: Maximum budget per task, agent stops if exceeded.
  35. e2b_api_key: E2B API key.
  36. disable_color: Whether to disable terminal colors. For terminals that don't support color.
  37. debug: Whether to enable debugging mode.
  38. file_uploads_max_file_size_mb: Maximum file upload size in MB. `0` means unlimited.
  39. file_uploads_restrict_file_types: Whether to restrict upload file types.
  40. file_uploads_allowed_extensions: Allowed file extensions. `['.*']` allows all.
  41. """
  42. llms: dict[str, LLMConfig] = field(default_factory=dict)
  43. agents: dict = field(default_factory=dict)
  44. default_agent: str = OH_DEFAULT_AGENT
  45. sandbox: SandboxConfig = field(default_factory=SandboxConfig)
  46. security: SecurityConfig = field(default_factory=SecurityConfig)
  47. runtime: str = 'eventstream'
  48. file_store: str = 'memory'
  49. file_store_path: str = '/tmp/file_store'
  50. trajectories_path: str | None = None
  51. workspace_base: str | None = None
  52. workspace_mount_path: str | None = None
  53. workspace_mount_path_in_sandbox: str = '/workspace'
  54. workspace_mount_rewrite: str | None = None
  55. cache_dir: str = '/tmp/cache'
  56. run_as_openhands: bool = True
  57. max_iterations: int = OH_MAX_ITERATIONS
  58. max_budget_per_task: float | None = None
  59. e2b_api_key: str = ''
  60. modal_api_token_id: str = ''
  61. modal_api_token_secret: str = ''
  62. disable_color: bool = False
  63. jwt_secret: str = ''
  64. attach_session_middleware_class: str = (
  65. 'openhands.server.middleware.AttachSessionMiddleware'
  66. )
  67. debug: bool = False
  68. file_uploads_max_file_size_mb: int = 0
  69. file_uploads_restrict_file_types: bool = False
  70. file_uploads_allowed_extensions: list[str] = field(default_factory=lambda: ['.*'])
  71. runloop_api_key: str | None = None
  72. defaults_dict: ClassVar[dict] = {}
  73. def get_llm_config(self, name='llm') -> LLMConfig:
  74. """'llm' is the name for default config (for backward compatibility prior to 0.8)."""
  75. if name in self.llms:
  76. return self.llms[name]
  77. if name is not None and name != 'llm':
  78. logger.openhands_logger.warning(
  79. f'llm config group {name} not found, using default config'
  80. )
  81. if 'llm' not in self.llms:
  82. self.llms['llm'] = LLMConfig()
  83. return self.llms['llm']
  84. def set_llm_config(self, value: LLMConfig, name='llm') -> None:
  85. self.llms[name] = value
  86. def get_agent_config(self, name='agent') -> AgentConfig:
  87. """'agent' is the name for default config (for backward compatibility prior to 0.8)."""
  88. if name in self.agents:
  89. return self.agents[name]
  90. if 'agent' not in self.agents:
  91. self.agents['agent'] = AgentConfig()
  92. return self.agents['agent']
  93. def set_agent_config(self, value: AgentConfig, name='agent') -> None:
  94. self.agents[name] = value
  95. def get_agent_to_llm_config_map(self) -> dict[str, LLMConfig]:
  96. """Get a map of agent names to llm configs."""
  97. return {name: self.get_llm_config_from_agent(name) for name in self.agents}
  98. def get_llm_config_from_agent(self, name='agent') -> LLMConfig:
  99. agent_config: AgentConfig = self.get_agent_config(name)
  100. llm_config_name = agent_config.llm_config
  101. return self.get_llm_config(llm_config_name)
  102. def get_agent_configs(self) -> dict[str, AgentConfig]:
  103. return self.agents
  104. def __post_init__(self):
  105. """Post-initialization hook, called when the instance is created with only default values."""
  106. AppConfig.defaults_dict = self.defaults_to_dict()
  107. def defaults_to_dict(self) -> dict:
  108. """Serialize fields to a dict for the frontend, including type hints, defaults, and whether it's optional."""
  109. result = {}
  110. for f in fields(self):
  111. field_value = getattr(self, f.name)
  112. # dataclasses compute their defaults themselves
  113. if is_dataclass(type(field_value)):
  114. result[f.name] = field_value.defaults_to_dict()
  115. else:
  116. result[f.name] = get_field_info(f)
  117. return result
  118. def __str__(self):
  119. attr_str = []
  120. for f in fields(self):
  121. attr_name = f.name
  122. attr_value = getattr(self, f.name)
  123. if attr_name in [
  124. 'e2b_api_key',
  125. 'github_token',
  126. 'jwt_secret',
  127. 'modal_api_token_id',
  128. 'modal_api_token_secret',
  129. 'runloop_api_key',
  130. ]:
  131. attr_value = '******' if attr_value else None
  132. attr_str.append(f'{attr_name}={repr(attr_value)}')
  133. return f"AppConfig({', '.join(attr_str)}"
  134. def __repr__(self):
  135. return self.__str__()