mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
fix occurrences of MicroAgent to the standard "Microagent" (#7791)
This commit is contained in:
@@ -21,8 +21,8 @@ OpenHandsがリポジトリで動作する際:
|
|||||||
```
|
```
|
||||||
---
|
---
|
||||||
name: <Microagentの名前>
|
name: <Microagentの名前>
|
||||||
type: <MicroAgentのタイプ>
|
type: <Microagentのタイプ>
|
||||||
version: <MicroAgentのバージョン>
|
version: <Microagentのバージョン>
|
||||||
agent: <エージェントのタイプ (通常はCodeActAgent)>
|
agent: <エージェントのタイプ (通常はCodeActAgent)>
|
||||||
triggers:
|
triggers:
|
||||||
- <オプション: microagentをトリガーするキーワード。トリガーを削除すると、常に含まれるようになります>
|
- <オプション: microagentをトリガーするキーワード。トリガーを削除すると、常に含まれるようになります>
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ Todos os microagentes usam arquivos markdown com frontmatter YAML que possuem in
|
|||||||
```
|
```
|
||||||
---
|
---
|
||||||
name: <Nome do microagente>
|
name: <Nome do microagente>
|
||||||
type: <Tipo do MicroAgent>
|
type: <Tipo do Microagent>
|
||||||
version: <Versão do MicroAgent>
|
version: <Versão do Microagent>
|
||||||
agent: <O tipo de agente (normalmente CodeActAgent)>
|
agent: <O tipo de agente (normalmente CodeActAgent)>
|
||||||
triggers:
|
triggers:
|
||||||
- <Palavras-chave opcionais que acionam o microagente. Se os gatilhos forem removidos, ele sempre será incluído>
|
- <Palavras-chave opcionais que acionam o microagente. Se os gatilhos forem removidos, ele sempre será incluído>
|
||||||
|
|||||||
@@ -4961,19 +4961,19 @@
|
|||||||
"tr": "Gezinme tamamlandı"
|
"tr": "Gezinme tamamlandı"
|
||||||
},
|
},
|
||||||
"OBSERVATION_MESSAGE$RECALL": {
|
"OBSERVATION_MESSAGE$RECALL": {
|
||||||
"en": "MicroAgent Activated",
|
"en": "Microagent Activated",
|
||||||
"ja": "マイクロエージェントが有効化されました",
|
"ja": "マイクロエージェントが有効化されました",
|
||||||
"zh-CN": "微代理已激活",
|
"zh-CN": "微代理已激活",
|
||||||
"zh-TW": "微代理已啟動",
|
"zh-TW": "微代理已啟動",
|
||||||
"ko-KR": "마이크로에이전트 활성화됨",
|
"ko-KR": "마이크로에이전트 활성화됨",
|
||||||
"no": "MikroAgent aktivert",
|
"no": "MikroAgent aktivert",
|
||||||
"it": "MicroAgent attivato",
|
"it": "Microagent attivato",
|
||||||
"pt": "MicroAgent ativado",
|
"pt": "Microagent ativado",
|
||||||
"es": "MicroAgent activado",
|
"es": "Microagent activado",
|
||||||
"ar": "تم تنشيط الوكيل المصغر",
|
"ar": "تم تنشيط الوكيل المصغر",
|
||||||
"fr": "MicroAgent activé",
|
"fr": "Microagent activé",
|
||||||
"tr": "MikroAjan Etkinleştirildi",
|
"tr": "MikroAjan Etkinleştirildi",
|
||||||
"de": "MicroAgent aktiviert"
|
"de": "Microagent aktiviert"
|
||||||
},
|
},
|
||||||
"EXPANDABLE_MESSAGE$SHOW_DETAILS": {
|
"EXPANDABLE_MESSAGE$SHOW_DETAILS": {
|
||||||
"en": "Show details",
|
"en": "Show details",
|
||||||
@@ -6089,4 +6089,4 @@
|
|||||||
"tr": "belgelendirme",
|
"tr": "belgelendirme",
|
||||||
"de": "Dokumentation"
|
"de": "Dokumentation"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# OpenHands MicroAgents
|
# OpenHands Microagents
|
||||||
|
|
||||||
MicroAgents are specialized prompts that enhance OpenHands with domain-specific knowledge and task-specific workflows. They help developers by providing expert guidance, automating common tasks, and ensuring consistent practices across projects. Each microagent is designed to excel in a specific area, from Git operations to code review processes.
|
Microagents are specialized prompts that enhance OpenHands with domain-specific knowledge and task-specific workflows. They help developers by providing expert guidance, automating common tasks, and ensuring consistent practices across projects. Each microagent is designed to excel in a specific area, from Git operations to code review processes.
|
||||||
|
|
||||||
## Sources of Microagents
|
## Sources of Microagents
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ When OpenHands works with a repository, it:
|
|||||||
2. Loads relevant knowledge agents based on keywords in conversations
|
2. Loads relevant knowledge agents based on keywords in conversations
|
||||||
3. Enable task agent if user select one of them
|
3. Enable task agent if user select one of them
|
||||||
|
|
||||||
## Types of MicroAgents
|
## Types of Microagents
|
||||||
|
|
||||||
Most microagents use markdown files with YAML frontmatter. For repository agents (repo.md), the frontmatter is optional - if not provided, the file will be loaded with default settings as a repository agent.
|
Most microagents use markdown files with YAML frontmatter. For repository agents (repo.md), the frontmatter is optional - if not provided, the file will be loaded with default settings as a repository agent.
|
||||||
|
|
||||||
|
|||||||
@@ -222,14 +222,14 @@ class BrowserUnavailableException(Exception):
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
|
|
||||||
class MicroAgentError(Exception):
|
class MicroagentError(Exception):
|
||||||
"""Base exception for all microagent errors."""
|
"""Base exception for all microagent errors."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MicroAgentValidationError(MicroAgentError):
|
class MicroagentValidationError(MicroagentError):
|
||||||
"""Raised when there's a validation error in microagent metadata."""
|
"""Raised when there's a validation error in microagent metadata."""
|
||||||
|
|
||||||
def __init__(self, message: str = 'Micro agent validation failed') -> None:
|
def __init__(self, message: str = 'Microagent validation failed') -> None:
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from openhands.events.event import Event
|
|||||||
from openhands.integrations.provider import ProviderToken, ProviderType, SecretStore
|
from openhands.integrations.provider import ProviderToken, ProviderType, SecretStore
|
||||||
from openhands.llm.llm import LLM
|
from openhands.llm.llm import LLM
|
||||||
from openhands.memory.memory import Memory
|
from openhands.memory.memory import Memory
|
||||||
from openhands.microagent.microagent import BaseMicroAgent
|
from openhands.microagent.microagent import BaseMicroagent
|
||||||
from openhands.runtime import get_runtime_cls
|
from openhands.runtime import get_runtime_cls
|
||||||
from openhands.runtime.base import Runtime
|
from openhands.runtime.base import Runtime
|
||||||
from openhands.security import SecurityAnalyzer, options
|
from openhands.security import SecurityAnalyzer, options
|
||||||
@@ -160,7 +160,7 @@ def create_memory(
|
|||||||
memory.set_runtime_info(runtime)
|
memory.set_runtime_info(runtime)
|
||||||
|
|
||||||
# loads microagents from repo/.openhands/microagents
|
# loads microagents from repo/.openhands/microagents
|
||||||
microagents: list[BaseMicroAgent] = runtime.get_microagents_from_selected_repo(
|
microagents: list[BaseMicroagent] = runtime.get_microagents_from_selected_repo(
|
||||||
selected_repository
|
selected_repository
|
||||||
)
|
)
|
||||||
memory.load_user_workspace_microagents(microagents)
|
memory.load_user_workspace_microagents(microagents)
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ from openhands.events.observation.agent import (
|
|||||||
from openhands.events.observation.empty import NullObservation
|
from openhands.events.observation.empty import NullObservation
|
||||||
from openhands.events.stream import EventStream, EventStreamSubscriber
|
from openhands.events.stream import EventStream, EventStreamSubscriber
|
||||||
from openhands.microagent import (
|
from openhands.microagent import (
|
||||||
BaseMicroAgent,
|
BaseMicroagent,
|
||||||
KnowledgeMicroAgent,
|
KnowledgeMicroagent,
|
||||||
RepoMicroAgent,
|
RepoMicroagent,
|
||||||
load_microagents_from_dir,
|
load_microagents_from_dir,
|
||||||
)
|
)
|
||||||
from openhands.runtime.base import Runtime
|
from openhands.runtime.base import Runtime
|
||||||
@@ -58,8 +58,8 @@ class Memory:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Additional placeholders to store user workspace microagents
|
# Additional placeholders to store user workspace microagents
|
||||||
self.repo_microagents: dict[str, RepoMicroAgent] = {}
|
self.repo_microagents: dict[str, RepoMicroagent] = {}
|
||||||
self.knowledge_microagents: dict[str, KnowledgeMicroAgent] = {}
|
self.knowledge_microagents: dict[str, KnowledgeMicroagent] = {}
|
||||||
|
|
||||||
# Store repository / runtime info to send them to the templating later
|
# Store repository / runtime info to send them to the templating later
|
||||||
self.repository_info: RepositoryInfo | None = None
|
self.repository_info: RepositoryInfo | None = None
|
||||||
@@ -229,7 +229,7 @@ class Memory:
|
|||||||
return recalled_content
|
return recalled_content
|
||||||
|
|
||||||
def load_user_workspace_microagents(
|
def load_user_workspace_microagents(
|
||||||
self, user_microagents: list[BaseMicroAgent]
|
self, user_microagents: list[BaseMicroagent]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
This method loads microagents from a user's cloned repo or workspace directory.
|
This method loads microagents from a user's cloned repo or workspace directory.
|
||||||
@@ -240,9 +240,9 @@ class Memory:
|
|||||||
'Loading user workspace microagents: %s', [m.name for m in user_microagents]
|
'Loading user workspace microagents: %s', [m.name for m in user_microagents]
|
||||||
)
|
)
|
||||||
for user_microagent in user_microagents:
|
for user_microagent in user_microagents:
|
||||||
if isinstance(user_microagent, KnowledgeMicroAgent):
|
if isinstance(user_microagent, KnowledgeMicroagent):
|
||||||
self.knowledge_microagents[user_microagent.name] = user_microagent
|
self.knowledge_microagents[user_microagent.name] = user_microagent
|
||||||
elif isinstance(user_microagent, RepoMicroAgent):
|
elif isinstance(user_microagent, RepoMicroagent):
|
||||||
self.repo_microagents[user_microagent.name] = user_microagent
|
self.repo_microagents[user_microagent.name] = user_microagent
|
||||||
|
|
||||||
def _load_global_microagents(self) -> None:
|
def _load_global_microagents(self) -> None:
|
||||||
@@ -253,10 +253,10 @@ class Memory:
|
|||||||
GLOBAL_MICROAGENTS_DIR
|
GLOBAL_MICROAGENTS_DIR
|
||||||
)
|
)
|
||||||
for name, agent in knowledge_agents.items():
|
for name, agent in knowledge_agents.items():
|
||||||
if isinstance(agent, KnowledgeMicroAgent):
|
if isinstance(agent, KnowledgeMicroagent):
|
||||||
self.knowledge_microagents[name] = agent
|
self.knowledge_microagents[name] = agent
|
||||||
for name, agent in repo_agents.items():
|
for name, agent in repo_agents.items():
|
||||||
if isinstance(agent, RepoMicroAgent):
|
if isinstance(agent, RepoMicroagent):
|
||||||
self.repo_microagents[name] = agent
|
self.repo_microagents[name] = agent
|
||||||
|
|
||||||
def set_repository_info(self, repo_name: str, repo_directory: str) -> None:
|
def set_repository_info(self, repo_name: str, repo_directory: str) -> None:
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
from .microagent import (
|
from .microagent import (
|
||||||
BaseMicroAgent,
|
BaseMicroagent,
|
||||||
KnowledgeMicroAgent,
|
KnowledgeMicroagent,
|
||||||
RepoMicroAgent,
|
RepoMicroagent,
|
||||||
TaskMicroAgent,
|
TaskMicroagent,
|
||||||
load_microagents_from_dir,
|
load_microagents_from_dir,
|
||||||
)
|
)
|
||||||
from .types import MicroAgentMetadata, MicroAgentType, TaskInput
|
from .types import MicroagentMetadata, MicroagentType, TaskInput
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'BaseMicroAgent',
|
'BaseMicroagent',
|
||||||
'KnowledgeMicroAgent',
|
'KnowledgeMicroagent',
|
||||||
'RepoMicroAgent',
|
'RepoMicroagent',
|
||||||
'TaskMicroAgent',
|
'TaskMicroagent',
|
||||||
'MicroAgentMetadata',
|
'MicroagentMetadata',
|
||||||
'MicroAgentType',
|
'MicroagentType',
|
||||||
'TaskInput',
|
'TaskInput',
|
||||||
'load_microagents_from_dir',
|
'load_microagents_from_dir',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,25 +6,25 @@ import frontmatter
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from openhands.core.exceptions import (
|
from openhands.core.exceptions import (
|
||||||
MicroAgentValidationError,
|
MicroagentValidationError,
|
||||||
)
|
)
|
||||||
from openhands.core.logger import openhands_logger as logger
|
from openhands.core.logger import openhands_logger as logger
|
||||||
from openhands.microagent.types import MicroAgentMetadata, MicroAgentType
|
from openhands.microagent.types import MicroagentMetadata, MicroagentType
|
||||||
|
|
||||||
|
|
||||||
class BaseMicroAgent(BaseModel):
|
class BaseMicroagent(BaseModel):
|
||||||
"""Base class for all microagents."""
|
"""Base class for all microagents."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
content: str
|
content: str
|
||||||
metadata: MicroAgentMetadata
|
metadata: MicroagentMetadata
|
||||||
source: str # path to the file
|
source: str # path to the file
|
||||||
type: MicroAgentType
|
type: MicroagentType
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(
|
def load(
|
||||||
cls, path: Union[str, Path], file_content: str | None = None
|
cls, path: Union[str, Path], file_content: str | None = None
|
||||||
) -> 'BaseMicroAgent':
|
) -> 'BaseMicroagent':
|
||||||
"""Load a microagent from a markdown file with frontmatter."""
|
"""Load a microagent from a markdown file with frontmatter."""
|
||||||
path = Path(path) if isinstance(path, str) else path
|
path = Path(path) if isinstance(path, str) else path
|
||||||
|
|
||||||
@@ -35,12 +35,12 @@ class BaseMicroAgent(BaseModel):
|
|||||||
|
|
||||||
# Legacy repo instructions are stored in .openhands_instructions
|
# Legacy repo instructions are stored in .openhands_instructions
|
||||||
if path.name == '.openhands_instructions':
|
if path.name == '.openhands_instructions':
|
||||||
return RepoMicroAgent(
|
return RepoMicroagent(
|
||||||
name='repo_legacy',
|
name='repo_legacy',
|
||||||
content=file_content,
|
content=file_content,
|
||||||
metadata=MicroAgentMetadata(name='repo_legacy'),
|
metadata=MicroagentMetadata(name='repo_legacy'),
|
||||||
source=str(path),
|
source=str(path),
|
||||||
type=MicroAgentType.REPO_KNOWLEDGE,
|
type=MicroagentType.REPO_KNOWLEDGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
file_io = io.StringIO(file_content)
|
file_io = io.StringIO(file_content)
|
||||||
@@ -51,15 +51,15 @@ class BaseMicroAgent(BaseModel):
|
|||||||
metadata_dict = loaded.metadata or {}
|
metadata_dict = loaded.metadata or {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
metadata = MicroAgentMetadata(**metadata_dict)
|
metadata = MicroagentMetadata(**metadata_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise MicroAgentValidationError(f'Error loading metadata: {e}') from e
|
raise MicroagentValidationError(f'Error loading metadata: {e}') from e
|
||||||
|
|
||||||
# Create appropriate subclass based on type
|
# Create appropriate subclass based on type
|
||||||
subclass_map = {
|
subclass_map = {
|
||||||
MicroAgentType.KNOWLEDGE: KnowledgeMicroAgent,
|
MicroagentType.KNOWLEDGE: KnowledgeMicroagent,
|
||||||
MicroAgentType.REPO_KNOWLEDGE: RepoMicroAgent,
|
MicroagentType.REPO_KNOWLEDGE: RepoMicroagent,
|
||||||
MicroAgentType.TASK: TaskMicroAgent,
|
MicroagentType.TASK: TaskMicroagent,
|
||||||
}
|
}
|
||||||
if metadata.type not in subclass_map:
|
if metadata.type not in subclass_map:
|
||||||
raise ValueError(f'Unknown microagent type: {metadata.type}')
|
raise ValueError(f'Unknown microagent type: {metadata.type}')
|
||||||
@@ -74,7 +74,7 @@ class BaseMicroAgent(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class KnowledgeMicroAgent(BaseMicroAgent):
|
class KnowledgeMicroagent(BaseMicroagent):
|
||||||
"""Knowledge micro-agents provide specialized expertise that's triggered by keywords in conversations. They help with:
|
"""Knowledge micro-agents provide specialized expertise that's triggered by keywords in conversations. They help with:
|
||||||
- Language best practices
|
- Language best practices
|
||||||
- Framework guidelines
|
- Framework guidelines
|
||||||
@@ -84,8 +84,8 @@ class KnowledgeMicroAgent(BaseMicroAgent):
|
|||||||
|
|
||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
if self.type != MicroAgentType.KNOWLEDGE:
|
if self.type != MicroagentType.KNOWLEDGE:
|
||||||
raise ValueError('KnowledgeMicroAgent must have type KNOWLEDGE')
|
raise ValueError('KnowledgeMicroagent must have type KNOWLEDGE')
|
||||||
|
|
||||||
def match_trigger(self, message: str) -> str | None:
|
def match_trigger(self, message: str) -> str | None:
|
||||||
"""Match a trigger in the message.
|
"""Match a trigger in the message.
|
||||||
@@ -103,10 +103,10 @@ class KnowledgeMicroAgent(BaseMicroAgent):
|
|||||||
return self.metadata.triggers
|
return self.metadata.triggers
|
||||||
|
|
||||||
|
|
||||||
class RepoMicroAgent(BaseMicroAgent):
|
class RepoMicroagent(BaseMicroagent):
|
||||||
"""MicroAgent specialized for repository-specific knowledge and guidelines.
|
"""Microagent specialized for repository-specific knowledge and guidelines.
|
||||||
|
|
||||||
RepoMicroAgents are loaded from `.openhands/microagents/repo.md` files within repositories
|
RepoMicroagents are loaded from `.openhands/microagents/repo.md` files within repositories
|
||||||
and contain private, repository-specific instructions that are automatically loaded when
|
and contain private, repository-specific instructions that are automatically loaded when
|
||||||
working with that repository. They are ideal for:
|
working with that repository. They are ideal for:
|
||||||
- Repository-specific guidelines
|
- Repository-specific guidelines
|
||||||
@@ -117,23 +117,23 @@ class RepoMicroAgent(BaseMicroAgent):
|
|||||||
|
|
||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
if self.type != MicroAgentType.REPO_KNOWLEDGE:
|
if self.type != MicroagentType.REPO_KNOWLEDGE:
|
||||||
raise ValueError('RepoMicroAgent must have type REPO_KNOWLEDGE')
|
raise ValueError('RepoMicroagent must have type REPO_KNOWLEDGE')
|
||||||
|
|
||||||
|
|
||||||
class TaskMicroAgent(BaseMicroAgent):
|
class TaskMicroagent(BaseMicroagent):
|
||||||
"""MicroAgent specialized for task-based operations."""
|
"""Microagent specialized for task-based operations."""
|
||||||
|
|
||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
if self.type != MicroAgentType.TASK:
|
if self.type != MicroagentType.TASK:
|
||||||
raise ValueError('TaskMicroAgent must have type TASK')
|
raise ValueError('TaskMicroagent must have type TASK')
|
||||||
|
|
||||||
|
|
||||||
def load_microagents_from_dir(
|
def load_microagents_from_dir(
|
||||||
microagent_dir: Union[str, Path],
|
microagent_dir: Union[str, Path],
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
dict[str, RepoMicroAgent], dict[str, KnowledgeMicroAgent], dict[str, TaskMicroAgent]
|
dict[str, RepoMicroagent], dict[str, KnowledgeMicroagent], dict[str, TaskMicroagent]
|
||||||
]:
|
]:
|
||||||
"""Load all microagents from the given directory.
|
"""Load all microagents from the given directory.
|
||||||
|
|
||||||
@@ -161,12 +161,12 @@ def load_microagents_from_dir(
|
|||||||
if file.name == 'README.md':
|
if file.name == 'README.md':
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
agent = BaseMicroAgent.load(file)
|
agent = BaseMicroagent.load(file)
|
||||||
if isinstance(agent, RepoMicroAgent):
|
if isinstance(agent, RepoMicroagent):
|
||||||
repo_agents[agent.name] = agent
|
repo_agents[agent.name] = agent
|
||||||
elif isinstance(agent, KnowledgeMicroAgent):
|
elif isinstance(agent, KnowledgeMicroagent):
|
||||||
knowledge_agents[agent.name] = agent
|
knowledge_agents[agent.name] = agent
|
||||||
elif isinstance(agent, TaskMicroAgent):
|
elif isinstance(agent, TaskMicroagent):
|
||||||
task_agents[agent.name] = agent
|
task_agents[agent.name] = agent
|
||||||
logger.debug(f'Loaded agent {agent.name} from {file}')
|
logger.debug(f'Loaded agent {agent.name} from {file}')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from enum import Enum
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class MicroAgentType(str, Enum):
|
class MicroagentType(str, Enum):
|
||||||
"""Type of microagent."""
|
"""Type of microagent."""
|
||||||
|
|
||||||
KNOWLEDGE = 'knowledge'
|
KNOWLEDGE = 'knowledge'
|
||||||
@@ -11,11 +11,11 @@ class MicroAgentType(str, Enum):
|
|||||||
TASK = 'task'
|
TASK = 'task'
|
||||||
|
|
||||||
|
|
||||||
class MicroAgentMetadata(BaseModel):
|
class MicroagentMetadata(BaseModel):
|
||||||
"""Metadata for all microagents."""
|
"""Metadata for all microagents."""
|
||||||
|
|
||||||
name: str = 'default'
|
name: str = 'default'
|
||||||
type: MicroAgentType = Field(default=MicroAgentType.REPO_KNOWLEDGE)
|
type: MicroagentType = Field(default=MicroagentType.REPO_KNOWLEDGE)
|
||||||
version: str = Field(default='1.0.0')
|
version: str = Field(default='1.0.0')
|
||||||
agent: str = Field(default='CodeActAgent')
|
agent: str = Field(default='CodeActAgent')
|
||||||
triggers: list[str] = [] # optional, only exists for knowledge microagents
|
triggers: list[str] = [] # optional, only exists for knowledge microagents
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ from openhands.integrations.provider import (
|
|||||||
)
|
)
|
||||||
from openhands.integrations.service_types import Repository
|
from openhands.integrations.service_types import Repository
|
||||||
from openhands.microagent import (
|
from openhands.microagent import (
|
||||||
BaseMicroAgent,
|
BaseMicroagent,
|
||||||
load_microagents_from_dir,
|
load_microagents_from_dir,
|
||||||
)
|
)
|
||||||
from openhands.runtime.plugins import (
|
from openhands.runtime.plugins import (
|
||||||
@@ -414,13 +414,13 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
|
|
||||||
def get_microagents_from_selected_repo(
|
def get_microagents_from_selected_repo(
|
||||||
self, selected_repository: str | None
|
self, selected_repository: str | None
|
||||||
) -> list[BaseMicroAgent]:
|
) -> list[BaseMicroagent]:
|
||||||
"""Load microagents from the selected repository.
|
"""Load microagents from the selected repository.
|
||||||
If selected_repository is None, load microagents from the current workspace.
|
If selected_repository is None, load microagents from the current workspace.
|
||||||
This is the main entry point for loading microagents.
|
This is the main entry point for loading microagents.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
loaded_microagents: list[BaseMicroAgent] = []
|
loaded_microagents: list[BaseMicroagent] = []
|
||||||
workspace_root = Path(self.config.workspace_mount_path_in_sandbox)
|
workspace_root = Path(self.config.workspace_mount_path_in_sandbox)
|
||||||
microagents_dir = workspace_root / '.openhands' / 'microagents'
|
microagents_dir = workspace_root / '.openhands' / 'microagents'
|
||||||
repo_root = None
|
repo_root = None
|
||||||
@@ -450,7 +450,7 @@ class Runtime(FileEditRuntimeMixin):
|
|||||||
if isinstance(obs, FileReadObservation):
|
if isinstance(obs, FileReadObservation):
|
||||||
self.log('info', 'openhands_instructions microagent loaded.')
|
self.log('info', 'openhands_instructions microagent loaded.')
|
||||||
loaded_microagents.append(
|
loaded_microagents.append(
|
||||||
BaseMicroAgent.load(
|
BaseMicroagent.load(
|
||||||
path='.openhands_instructions', file_content=obs.content
|
path='.openhands_instructions', file_content=obs.content
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from openhands.events.stream import EventStream
|
|||||||
from openhands.integrations.provider import PROVIDER_TOKEN_TYPE, ProviderHandler
|
from openhands.integrations.provider import PROVIDER_TOKEN_TYPE, ProviderHandler
|
||||||
from openhands.integrations.service_types import Repository
|
from openhands.integrations.service_types import Repository
|
||||||
from openhands.memory.memory import Memory
|
from openhands.memory.memory import Memory
|
||||||
from openhands.microagent.microagent import BaseMicroAgent
|
from openhands.microagent.microagent import BaseMicroagent
|
||||||
from openhands.runtime import get_runtime_cls
|
from openhands.runtime import get_runtime_cls
|
||||||
from openhands.runtime.base import Runtime
|
from openhands.runtime.base import Runtime
|
||||||
from openhands.runtime.impl.remote.remote_runtime import RemoteRuntime
|
from openhands.runtime.impl.remote.remote_runtime import RemoteRuntime
|
||||||
@@ -407,7 +407,7 @@ class AgentSession:
|
|||||||
memory.set_runtime_info(self.runtime)
|
memory.set_runtime_info(self.runtime)
|
||||||
|
|
||||||
# loads microagents from repo/.openhands/microagents
|
# loads microagents from repo/.openhands/microagents
|
||||||
microagents: list[BaseMicroAgent] = await call_sync_from_async(
|
microagents: list[BaseMicroagent] = await call_sync_from_async(
|
||||||
self.runtime.get_microagents_from_selected_repo,
|
self.runtime.get_microagents_from_selected_repo,
|
||||||
selected_repository.full_name if selected_repository else None,
|
selected_repository.full_name if selected_repository else None,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from conftest import (
|
|||||||
_load_runtime,
|
_load_runtime,
|
||||||
)
|
)
|
||||||
|
|
||||||
from openhands.microagent import KnowledgeMicroAgent, RepoMicroAgent, TaskMicroAgent
|
from openhands.microagent import KnowledgeMicroagent, RepoMicroagent, TaskMicroagent
|
||||||
|
|
||||||
|
|
||||||
def _create_test_microagents(test_dir: str):
|
def _create_test_microagents(test_dir: str):
|
||||||
@@ -85,10 +85,10 @@ def test_load_microagents_with_trailing_slashes(
|
|||||||
|
|
||||||
# Verify all agents are loaded
|
# Verify all agents are loaded
|
||||||
knowledge_agents = [
|
knowledge_agents = [
|
||||||
a for a in loaded_agents if isinstance(a, KnowledgeMicroAgent)
|
a for a in loaded_agents if isinstance(a, KnowledgeMicroagent)
|
||||||
]
|
]
|
||||||
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroAgent)]
|
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)]
|
||||||
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroAgent)]
|
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)]
|
||||||
|
|
||||||
# Check knowledge agents
|
# Check knowledge agents
|
||||||
assert len(knowledge_agents) == 1
|
assert len(knowledge_agents) == 1
|
||||||
@@ -128,10 +128,10 @@ def test_load_microagents_with_selected_repo(temp_dir, runtime_cls, run_as_openh
|
|||||||
|
|
||||||
# Verify all agents are loaded
|
# Verify all agents are loaded
|
||||||
knowledge_agents = [
|
knowledge_agents = [
|
||||||
a for a in loaded_agents if isinstance(a, KnowledgeMicroAgent)
|
a for a in loaded_agents if isinstance(a, KnowledgeMicroagent)
|
||||||
]
|
]
|
||||||
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroAgent)]
|
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)]
|
||||||
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroAgent)]
|
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)]
|
||||||
|
|
||||||
# Check knowledge agents
|
# Check knowledge agents
|
||||||
assert len(knowledge_agents) == 1
|
assert len(knowledge_agents) == 1
|
||||||
@@ -181,10 +181,10 @@ Repository-specific test instructions.
|
|||||||
|
|
||||||
# Verify only repo agent is loaded
|
# Verify only repo agent is loaded
|
||||||
knowledge_agents = [
|
knowledge_agents = [
|
||||||
a for a in loaded_agents if isinstance(a, KnowledgeMicroAgent)
|
a for a in loaded_agents if isinstance(a, KnowledgeMicroagent)
|
||||||
]
|
]
|
||||||
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroAgent)]
|
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)]
|
||||||
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroAgent)]
|
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)]
|
||||||
|
|
||||||
assert len(knowledge_agents) == 0
|
assert len(knowledge_agents) == 0
|
||||||
assert len(repo_agents) == 1
|
assert len(repo_agents) == 1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from openhands.microagent.microagent import BaseMicroAgent, RepoMicroAgent
|
from openhands.microagent.microagent import BaseMicroagent, RepoMicroagent
|
||||||
from openhands.microagent.types import MicroAgentType
|
from openhands.microagent.types import MicroagentType
|
||||||
|
|
||||||
|
|
||||||
def test_load_markdown_without_frontmatter():
|
def test_load_markdown_without_frontmatter():
|
||||||
@@ -10,13 +10,13 @@ def test_load_markdown_without_frontmatter():
|
|||||||
path = Path('test.md')
|
path = Path('test.md')
|
||||||
|
|
||||||
# Load the agent from content
|
# Load the agent from content
|
||||||
agent = BaseMicroAgent.load(path, content)
|
agent = BaseMicroagent.load(path, content)
|
||||||
|
|
||||||
# Verify it's loaded as a repo agent with default values
|
# Verify it's loaded as a repo agent with default values
|
||||||
assert isinstance(agent, RepoMicroAgent)
|
assert isinstance(agent, RepoMicroagent)
|
||||||
assert agent.name == 'default'
|
assert agent.name == 'default'
|
||||||
assert agent.content == content
|
assert agent.content == content
|
||||||
assert agent.type == MicroAgentType.REPO_KNOWLEDGE
|
assert agent.type == MicroagentType.REPO_KNOWLEDGE
|
||||||
assert agent.metadata.agent == 'CodeActAgent'
|
assert agent.metadata.agent == 'CodeActAgent'
|
||||||
assert agent.metadata.version == '1.0.0'
|
assert agent.metadata.version == '1.0.0'
|
||||||
|
|
||||||
@@ -29,16 +29,16 @@ def test_load_markdown_with_empty_frontmatter():
|
|||||||
path = Path('test.md')
|
path = Path('test.md')
|
||||||
|
|
||||||
# Load the agent from content
|
# Load the agent from content
|
||||||
agent = BaseMicroAgent.load(path, content)
|
agent = BaseMicroagent.load(path, content)
|
||||||
|
|
||||||
# Verify it's loaded as a repo agent with default values
|
# Verify it's loaded as a repo agent with default values
|
||||||
assert isinstance(agent, RepoMicroAgent)
|
assert isinstance(agent, RepoMicroagent)
|
||||||
assert agent.name == 'default'
|
assert agent.name == 'default'
|
||||||
assert (
|
assert (
|
||||||
agent.content
|
agent.content
|
||||||
== '# Test Content\nThis is a test markdown file with empty frontmatter.'
|
== '# Test Content\nThis is a test markdown file with empty frontmatter.'
|
||||||
)
|
)
|
||||||
assert agent.type == MicroAgentType.REPO_KNOWLEDGE
|
assert agent.type == MicroagentType.REPO_KNOWLEDGE
|
||||||
assert agent.metadata.agent == 'CodeActAgent'
|
assert agent.metadata.agent == 'CodeActAgent'
|
||||||
assert agent.metadata.version == '1.0.0'
|
assert agent.metadata.version == '1.0.0'
|
||||||
|
|
||||||
@@ -53,16 +53,16 @@ This is a test markdown file with partial frontmatter."""
|
|||||||
path = Path('test.md')
|
path = Path('test.md')
|
||||||
|
|
||||||
# Load the agent from content
|
# Load the agent from content
|
||||||
agent = BaseMicroAgent.load(path, content)
|
agent = BaseMicroagent.load(path, content)
|
||||||
|
|
||||||
# Verify it uses provided name but default values for other fields
|
# Verify it uses provided name but default values for other fields
|
||||||
assert isinstance(agent, RepoMicroAgent)
|
assert isinstance(agent, RepoMicroagent)
|
||||||
assert agent.name == 'custom_name'
|
assert agent.name == 'custom_name'
|
||||||
assert (
|
assert (
|
||||||
agent.content
|
agent.content
|
||||||
== '# Test Content\nThis is a test markdown file with partial frontmatter.'
|
== '# Test Content\nThis is a test markdown file with partial frontmatter.'
|
||||||
)
|
)
|
||||||
assert agent.type == MicroAgentType.REPO_KNOWLEDGE
|
assert agent.type == MicroagentType.REPO_KNOWLEDGE
|
||||||
assert agent.metadata.agent == 'CodeActAgent'
|
assert agent.metadata.agent == 'CodeActAgent'
|
||||||
assert agent.metadata.version == '1.0.0'
|
assert agent.metadata.version == '1.0.0'
|
||||||
|
|
||||||
@@ -80,15 +80,15 @@ This is a test markdown file with full frontmatter."""
|
|||||||
path = Path('test.md')
|
path = Path('test.md')
|
||||||
|
|
||||||
# Load the agent from content
|
# Load the agent from content
|
||||||
agent = BaseMicroAgent.load(path, content)
|
agent = BaseMicroagent.load(path, content)
|
||||||
|
|
||||||
# Verify all provided values are used
|
# Verify all provided values are used
|
||||||
assert isinstance(agent, RepoMicroAgent)
|
assert isinstance(agent, RepoMicroagent)
|
||||||
assert agent.name == 'test_agent'
|
assert agent.name == 'test_agent'
|
||||||
assert (
|
assert (
|
||||||
agent.content
|
agent.content
|
||||||
== '# Test Content\nThis is a test markdown file with full frontmatter.'
|
== '# Test Content\nThis is a test markdown file with full frontmatter.'
|
||||||
)
|
)
|
||||||
assert agent.type == MicroAgentType.REPO_KNOWLEDGE
|
assert agent.type == MicroagentType.REPO_KNOWLEDGE
|
||||||
assert agent.metadata.agent == 'CustomAgent'
|
assert agent.metadata.agent == 'CustomAgent'
|
||||||
assert agent.metadata.version == '2.0.0'
|
assert agent.metadata.version == '2.0.0'
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from openhands.core.exceptions import MicroAgentValidationError
|
from openhands.core.exceptions import MicroagentValidationError
|
||||||
from openhands.microagent import (
|
from openhands.microagent import (
|
||||||
BaseMicroAgent,
|
BaseMicroagent,
|
||||||
KnowledgeMicroAgent,
|
KnowledgeMicroagent,
|
||||||
MicroAgentMetadata,
|
MicroagentMetadata,
|
||||||
MicroAgentType,
|
MicroagentType,
|
||||||
RepoMicroAgent,
|
RepoMicroagent,
|
||||||
TaskMicroAgent,
|
TaskMicroagent,
|
||||||
load_microagents_from_dir,
|
load_microagents_from_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ def test_legacy_micro_agent_load(tmp_path):
|
|||||||
legacy_file = tmp_path / '.openhands_instructions'
|
legacy_file = tmp_path / '.openhands_instructions'
|
||||||
legacy_file.write_text(CONTENT)
|
legacy_file.write_text(CONTENT)
|
||||||
|
|
||||||
micro_agent = BaseMicroAgent.load(legacy_file)
|
micro_agent = BaseMicroagent.load(legacy_file)
|
||||||
assert isinstance(micro_agent, RepoMicroAgent)
|
assert isinstance(micro_agent, RepoMicroagent)
|
||||||
assert micro_agent.name == 'repo_legacy'
|
assert micro_agent.name == 'repo_legacy'
|
||||||
assert micro_agent.content == CONTENT
|
assert micro_agent.content == CONTENT
|
||||||
assert micro_agent.type == MicroAgentType.REPO_KNOWLEDGE
|
assert micro_agent.type == MicroagentType.REPO_KNOWLEDGE
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -89,14 +89,14 @@ Test task content
|
|||||||
|
|
||||||
def test_knowledge_agent():
|
def test_knowledge_agent():
|
||||||
"""Test knowledge agent functionality."""
|
"""Test knowledge agent functionality."""
|
||||||
agent = KnowledgeMicroAgent(
|
agent = KnowledgeMicroagent(
|
||||||
name='test',
|
name='test',
|
||||||
content='Test content',
|
content='Test content',
|
||||||
metadata=MicroAgentMetadata(
|
metadata=MicroagentMetadata(
|
||||||
name='test', type=MicroAgentType.KNOWLEDGE, triggers=['test', 'pytest']
|
name='test', type=MicroagentType.KNOWLEDGE, triggers=['test', 'pytest']
|
||||||
),
|
),
|
||||||
source='test.md',
|
source='test.md',
|
||||||
type=MicroAgentType.KNOWLEDGE,
|
type=MicroagentType.KNOWLEDGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert agent.match_trigger('running a test') == 'test'
|
assert agent.match_trigger('running a test') == 'test'
|
||||||
@@ -114,18 +114,18 @@ def test_load_microagents(temp_microagents_dir):
|
|||||||
# Check knowledge agents
|
# Check knowledge agents
|
||||||
assert len(knowledge_agents) == 1
|
assert len(knowledge_agents) == 1
|
||||||
agent = knowledge_agents['test_knowledge_agent']
|
agent = knowledge_agents['test_knowledge_agent']
|
||||||
assert isinstance(agent, KnowledgeMicroAgent)
|
assert isinstance(agent, KnowledgeMicroagent)
|
||||||
assert 'test' in agent.triggers
|
assert 'test' in agent.triggers
|
||||||
|
|
||||||
# Check repo agents
|
# Check repo agents
|
||||||
assert len(repo_agents) == 1
|
assert len(repo_agents) == 1
|
||||||
agent = repo_agents['test_repo_agent']
|
agent = repo_agents['test_repo_agent']
|
||||||
assert isinstance(agent, RepoMicroAgent)
|
assert isinstance(agent, RepoMicroagent)
|
||||||
|
|
||||||
# Check task agents
|
# Check task agents
|
||||||
assert len(task_agents) == 1
|
assert len(task_agents) == 1
|
||||||
agent = task_agents['test_task']
|
agent = task_agents['test_task']
|
||||||
assert isinstance(agent, TaskMicroAgent)
|
assert isinstance(agent, TaskMicroagent)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_agent_type(temp_microagents_dir):
|
def test_invalid_agent_type(temp_microagents_dir):
|
||||||
@@ -141,8 +141,8 @@ Invalid agent content
|
|||||||
"""
|
"""
|
||||||
(temp_microagents_dir / 'invalid.md').write_text(invalid_agent)
|
(temp_microagents_dir / 'invalid.md').write_text(invalid_agent)
|
||||||
|
|
||||||
with pytest.raises(MicroAgentValidationError):
|
with pytest.raises(MicroagentValidationError):
|
||||||
BaseMicroAgent.load(temp_microagents_dir / 'invalid.md')
|
BaseMicroagent.load(temp_microagents_dir / 'invalid.md')
|
||||||
|
|
||||||
|
|
||||||
def test_load_microagents_with_nested_dirs(temp_microagents_dir):
|
def test_load_microagents_with_nested_dirs(temp_microagents_dir):
|
||||||
@@ -172,7 +172,7 @@ Testing nested directory loading.
|
|||||||
# Check that we can find the nested agent
|
# Check that we can find the nested agent
|
||||||
assert len(knowledge_agents) == 2 # Original + nested
|
assert len(knowledge_agents) == 2 # Original + nested
|
||||||
agent = knowledge_agents['nested_knowledge_agent']
|
agent = knowledge_agents['nested_knowledge_agent']
|
||||||
assert isinstance(agent, KnowledgeMicroAgent)
|
assert isinstance(agent, KnowledgeMicroagent)
|
||||||
assert 'nested' in agent.triggers
|
assert 'nested' in agent.triggers
|
||||||
|
|
||||||
|
|
||||||
@@ -203,5 +203,5 @@ Testing loading with trailing slashes.
|
|||||||
# Check that we can find the agent despite trailing slashes
|
# Check that we can find the agent despite trailing slashes
|
||||||
assert len(knowledge_agents) == 2 # Original + trailing
|
assert len(knowledge_agents) == 2 # Original + trailing
|
||||||
agent = knowledge_agents['trailing_knowledge_agent']
|
agent = knowledge_agents['trailing_knowledge_agent']
|
||||||
assert isinstance(agent, KnowledgeMicroAgent)
|
assert isinstance(agent, KnowledgeMicroagent)
|
||||||
assert 'trailing' in agent.triggers
|
assert 'trailing' in agent.triggers
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import pytest
|
|||||||
from openhands.controller.state.state import State
|
from openhands.controller.state.state import State
|
||||||
from openhands.core.message import Message, TextContent
|
from openhands.core.message import Message, TextContent
|
||||||
from openhands.events.observation.agent import MicroagentKnowledge
|
from openhands.events.observation.agent import MicroagentKnowledge
|
||||||
from openhands.microagent import BaseMicroAgent
|
from openhands.microagent import BaseMicroagent
|
||||||
from openhands.utils.prompt import PromptManager, RepositoryInfo, RuntimeInfo
|
from openhands.utils.prompt import PromptManager, RepositoryInfo, RuntimeInfo
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ def test_prompt_manager_file_not_found(prompt_dir):
|
|||||||
"""Test PromptManager behavior when a template file is not found."""
|
"""Test PromptManager behavior when a template file is not found."""
|
||||||
# Test with a non-existent template
|
# Test with a non-existent template
|
||||||
with pytest.raises(FileNotFoundError):
|
with pytest.raises(FileNotFoundError):
|
||||||
BaseMicroAgent.load(
|
BaseMicroagent.load(
|
||||||
os.path.join(prompt_dir, 'micro', 'non_existent_microagent.md')
|
os.path.join(prompt_dir, 'micro', 'non_existent_microagent.md')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user