diff --git a/config.template.toml b/config.template.toml index aefb52376803..ccb7b1159747 100644 --- a/config.template.toml +++ b/config.template.toml @@ -223,8 +223,8 @@ codeact_enable_jupyter = true # LLM config group to use #llm_config = 'your-llm-config-group' -# Whether to use microagents at all -#use_microagents = true +# Whether to use prompt extension (e.g., microagent, repo/runtime info) at all +#enable_prompt_extensions = true # List of microagents to disable #disabled_microagents = [] diff --git a/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/configuration-options.md b/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/configuration-options.md index 848b85a53164..b79a65073acc 100644 --- a/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/configuration-options.md +++ b/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/configuration-options.md @@ -373,7 +373,7 @@ Agent 配置选项在 `config.toml` 文件的 `[agent]` 和 `[agent. - 描述: 是否在 action space 中启用 Jupyter **Microagent 使用** -- `use_microagents` +- `enable_prompt_extensions` - 类型: `bool` - 默认值: `true` - 描述: 是否使用 microagents diff --git a/docs/modules/usage/configuration-options.md b/docs/modules/usage/configuration-options.md index 422dc1cc4913..a3c11de52ed8 100644 --- a/docs/modules/usage/configuration-options.md +++ b/docs/modules/usage/configuration-options.md @@ -336,7 +336,7 @@ The agent configuration options are defined in the `[agent]` and `[agent. list[Message]: ) ) + # Repository and runtime info + additional_info = self.prompt_manager.get_additional_info() + if self.config.enable_prompt_extensions and additional_info: + # only add these if prompt extension is enabled + messages.append( + Message( + role='user', + content=[TextContent(text=additional_info)], + ) + ) + pending_tool_call_action_messages: dict[str, Message] = {} tool_call_id_to_message: dict[str, Message] = {} diff --git a/openhands/agenthub/codeact_agent/prompts/system_prompt.j2 b/openhands/agenthub/codeact_agent/prompts/system_prompt.j2 index b6dfcd9bda75..325392f2e662 100644 --- a/openhands/agenthub/codeact_agent/prompts/system_prompt.j2 +++ b/openhands/agenthub/codeact_agent/prompts/system_prompt.j2 @@ -3,26 +3,4 @@ You are OpenHands agent, a helpful AI assistant that can interact with a compute * If user provides a path, you should NOT assume it's relative to the current working directory. Instead, you should explore the file system to find the file before working on it. * When configuring git credentials, use "openhands" as the user.name and "openhands@all-hands.dev" as the user.email by default, unless explicitly instructed otherwise. * The assistant MUST NOT include comments in the code unless they are necessary to describe non-obvious behavior. -{{ runtime_info }} -{% if repository_info %} - -At the user's request, repository {{ repository_info.repo_name }} has been cloned to directory {{ repository_info.repo_directory }}. - -{% endif %} -{% if repository_instructions -%} - -{{ repository_instructions }} - -{% endif %} -{% if runtime_info and runtime_info.available_hosts -%} - -The user has access to the following hosts for accessing a web application, -each of which has a corresponding port: -{% for host, port in runtime_info.available_hosts.items() -%} -* {{ host }} (port {{ port }}) -{% endfor %} -When starting a web server, use the corresponding ports. You should also -set any options to allow iframes and CORS requests. - -{% endif %} diff --git a/openhands/core/config/agent_config.py b/openhands/core/config/agent_config.py index 77e9dbc1e32d..375fd9b12e8a 100644 --- a/openhands/core/config/agent_config.py +++ b/openhands/core/config/agent_config.py @@ -17,7 +17,7 @@ class AgentConfig: memory_enabled: Whether long-term memory (embeddings) is enabled. memory_max_threads: The maximum number of threads indexing at the same time for embeddings. llm_config: The name of the llm config to use. If specified, this will override global llm config. - use_microagents: Whether to use microagents at all. Default is True. + enable_prompt_extensions: Whether to use prompt extensions (e.g., microagents, inject runtime info). Default is True. disabled_microagents: A list of microagents to disable. Default is None. condenser: Configuration for the memory condenser. Default is NoOpCondenserConfig. """ @@ -29,7 +29,7 @@ class AgentConfig: memory_enabled: bool = False memory_max_threads: int = 3 llm_config: str | None = None - use_microagents: bool = True + enable_prompt_extensions: bool = True disabled_microagents: list[str] | None = None condenser: CondenserConfig = field(default_factory=NoOpCondenserConfig) # type: ignore diff --git a/openhands/utils/prompt.py b/openhands/utils/prompt.py index 8d81cbbdf9d6..1861c45308b5 100644 --- a/openhands/utils/prompt.py +++ b/openhands/utils/prompt.py @@ -28,6 +28,33 @@ class RepositoryInfo: repo_directory: str | None = None +ADDITIONAL_INFO_TEMPLATE = Template( + """ +{% if repository_info %} + +At the user's request, repository {{ repository_info.repo_name }} has been cloned to directory {{ repository_info.repo_directory }}. + +{% endif %} +{% if repository_instructions -%} + +{{ repository_instructions }} + +{% endif %} +{% if runtime_info and runtime_info.available_hosts -%} + +The user has access to the following hosts for accessing a web application, +each of which has a corresponding port: +{% for host, port in runtime_info.available_hosts.items() -%} +* {{ host }} (port {{ port }}) +{% endfor %} +When starting a web server, use the corresponding ports. You should also +set any options to allow iframes and CORS requests. + +{% endif %} +""" +) + + class PromptManager: """ Manages prompt templates and micro-agents for AI interactions. @@ -59,6 +86,9 @@ def __init__( self.repo_microagents: dict[str, RepoMicroAgent] = {} if microagent_dir: + # This loads micro-agents from the microagent_dir + # which is typically the OpenHands/microagents (i.e., the PUBLIC microagents) + # Only load KnowledgeMicroAgents repo_microagents, knowledge_microagents, _ = load_microagents_from_dir( microagent_dir @@ -79,6 +109,10 @@ def __init__( self.repo_microagents[name] = microagent def load_microagents(self, microagents: list[BaseMicroAgent]): + """Load microagents from a list of BaseMicroAgents. + + This is typically used when loading microagents from inside a repo. + """ # Only keep KnowledgeMicroAgents and RepoMicroAgents for microagent in microagents: if microagent.name in self.disabled_microagents: @@ -98,6 +132,13 @@ def _load_template(self, template_name: str) -> Template: return Template(file.read()) def get_system_message(self) -> str: + return self.system_template.render().strip() + + def get_additional_info(self) -> str: + """Gets information about the repository and runtime. + + This is used to inject information about the repository and runtime into the initial user message. + """ repo_instructions = '' assert ( len(self.repo_microagents) <= 1 @@ -108,7 +149,7 @@ def get_system_message(self) -> str: repo_instructions += '\n\n' repo_instructions += microagent.content - return self.system_template.render( + return ADDITIONAL_INFO_TEMPLATE.render( repository_instructions=repo_instructions, repository_info=self.repository_info, runtime_info=self.runtime_info, diff --git a/tests/unit/test_codeact_agent.py b/tests/unit/test_codeact_agent.py index b1f5e420c3b4..84f0b8fc1993 100644 --- a/tests/unit/test_codeact_agent.py +++ b/tests/unit/test_codeact_agent.py @@ -471,7 +471,7 @@ def test_mock_function_calling(): llm = Mock() llm.is_function_calling_active = lambda: False config = AgentConfig() - config.use_microagents = False + config.enable_prompt_extensions = False agent = CodeActAgent(llm=llm, config=config) assert agent.mock_function_calling is True @@ -509,7 +509,7 @@ def test_step_with_no_pending_actions(mock_state: State): # Create agent with mocked LLM config = AgentConfig() - config.use_microagents = False + config.enable_prompt_extensions = False agent = CodeActAgent(llm=llm, config=config) # Test step with no pending actions diff --git a/tests/unit/test_prompt_manager.py b/tests/unit/test_prompt_manager.py index 4f2a69f7f0d5..46f1f5a254a1 100644 --- a/tests/unit/test_prompt_manager.py +++ b/tests/unit/test_prompt_manager.py @@ -59,9 +59,10 @@ def test_prompt_manager_with_microagent(prompt_dir): # Test with GitHub repo manager.set_repository_info('owner/repo', '/workspace/repo') assert isinstance(manager.get_system_message(), str) - assert '' in manager.get_system_message() - assert 'owner/repo' in manager.get_system_message() - assert '/workspace/repo' in manager.get_system_message() + additional_info = manager.get_additional_info() + assert '' in additional_info + assert 'owner/repo' in additional_info + assert '/workspace/repo' in additional_info assert isinstance(manager.get_example_user_message(), str) @@ -85,13 +86,7 @@ def test_prompt_manager_file_not_found(prompt_dir): def test_prompt_manager_template_rendering(prompt_dir): # Create temporary template files with open(os.path.join(prompt_dir, 'system_prompt.j2'), 'w') as f: - f.write("""System prompt: bar -{% if repository_info %} - -At the user's request, repository {{ repository_info.repo_name }} has been cloned to directory {{ repository_info.repo_directory }}. - -{% endif %} -{{ repo_instructions }}""") + f.write("""System prompt: bar""") with open(os.path.join(prompt_dir, 'user_prompt.j2'), 'w') as f: f.write('User prompt: foo') @@ -106,12 +101,13 @@ def test_prompt_manager_template_rendering(prompt_dir): assert manager.repository_info.repo_name == 'owner/repo' system_msg = manager.get_system_message() assert 'System prompt: bar' in system_msg - assert '' in system_msg + additional_info = manager.get_additional_info() + assert '' in additional_info assert ( "At the user's request, repository owner/repo has been cloned to directory /workspace/repo." - in system_msg + in additional_info ) - assert '' in system_msg + assert '' in additional_info assert manager.get_example_user_message() == 'User prompt: foo' # Clean up temporary files