From c6c94d979b88feda229b9fb3fe404a1c0c121c60 Mon Sep 17 00:00:00 2001 From: Chase Date: Thu, 8 May 2025 09:15:15 -0700 Subject: [PATCH] Feature: Add custom agents via config (#8245) --- config.template.toml | 5 +++++ openhands/core/config/agent_config.py | 2 ++ openhands/core/config/utils.py | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/config.template.toml b/config.template.toml index 8d55055ca5..7038d03019 100644 --- a/config.template.toml +++ b/config.template.toml @@ -255,6 +255,11 @@ enable_history_truncation = true # useful when an agent doesn't demand high quality but uses a lot of tokens llm_config = 'gpt3' +[agent.CustomAgent] +# Example: use a custom agent from a different package +# This will be automatically be registered as a new agent named "CustomAgent" +classpath = "my_package.my_module.MyCustomAgent" + #################################### Sandbox ################################### # Configuration for the sandbox ############################################################################## diff --git a/openhands/core/config/agent_config.py b/openhands/core/config/agent_config.py index fe6ff8be59..6800e41690 100644 --- a/openhands/core/config/agent_config.py +++ b/openhands/core/config/agent_config.py @@ -10,6 +10,8 @@ from openhands.core.logger import openhands_logger as logger class AgentConfig(BaseModel): llm_config: str | None = Field(default=None) """The name of the llm config to use. If specified, this will override global llm config.""" + classpath: str | None = Field(default=None) + """The classpath of the agent to use. To be used for custom agents that are not defined in the openhands.agenthub package.""" enable_browsing: bool = Field(default=True) """Whether to enable browsing tool""" enable_llm_editor: bool = Field(default=False) diff --git a/openhands/core/config/utils.py b/openhands/core/config/utils.py index 1c1639a461..4882c0286c 100644 --- a/openhands/core/config/utils.py +++ b/openhands/core/config/utils.py @@ -28,6 +28,7 @@ from openhands.core.config.sandbox_config import SandboxConfig from openhands.core.config.security_config import SecurityConfig from openhands.storage import get_file_store from openhands.storage.files import FileStore +from openhands.utils.import_utils import get_impl JWT_SECRET = '.jwt_secret' load_dotenv() @@ -610,6 +611,30 @@ def parse_arguments() -> argparse.Namespace: return args +def register_custom_agents(config: AppConfig) -> None: + """Register custom agents from configuration. + + This function is called after configuration is loaded to ensure all custom agents + specified in the config are properly imported and registered. + """ + + # Import here to avoid circular dependency + from openhands.controller.agent import Agent + + for agent_name, agent_config in config.agents.items(): + if agent_config.classpath: + try: + agent_cls = get_impl(Agent, agent_config.classpath) + Agent.register(agent_name, agent_cls) + logger.openhands_logger.info( + f"Registered custom agent '{agent_name}' from {agent_config.classpath}" + ) + except Exception as e: + logger.openhands_logger.error( + f"Failed to register agent '{agent_name}': {e}" + ) + + def load_app_config( set_logging_levels: bool = True, config_file: str = 'config.toml' ) -> AppConfig: @@ -623,6 +648,7 @@ def load_app_config( load_from_toml(config, config_file) load_from_env(config, os.environ) finalize_config(config) + register_custom_agents(config) if set_logging_levels: logger.DEBUG = config.debug logger.DISABLE_COLOR_PRINTING = config.disable_color