mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Simplify microagents (#8114)
Co-authored-by: Robert Brennan <accounts@rbren.io>
This commit is contained in:
parent
107789b5a8
commit
1b63633030
@ -4,7 +4,7 @@
|
||||
|
||||
Microagentes públicos são diretrizes especializadas acionadas por palavras-chave para todos os usuários do OpenHands.
|
||||
Eles são definidos em arquivos markdown no diretório
|
||||
[`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge).
|
||||
[`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge).
|
||||
|
||||
Microagentes públicos:
|
||||
- Monitoram comandos recebidos em busca de suas palavras-chave de acionamento.
|
||||
@ -15,7 +15,7 @@ Microagentes públicos:
|
||||
## Microagentes Públicos Atuais
|
||||
|
||||
Para mais informações sobre microagentes específicos, consulte seus arquivos de documentação individuais no
|
||||
diretório [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/).
|
||||
diretório [`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/).
|
||||
|
||||
### Agente GitHub
|
||||
**Arquivo**: `github.md`
|
||||
@ -59,7 +59,7 @@ yes | npm install package-name
|
||||
## Contribuindo com um Microagente Público
|
||||
|
||||
Você pode criar seus próprios microagentes públicos adicionando novos arquivos markdown ao
|
||||
diretório [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/).
|
||||
diretório [`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/).
|
||||
|
||||
### Melhores Práticas para Microagentes Públicos
|
||||
|
||||
@ -81,7 +81,7 @@ Antes de criar um microagente público, considere:
|
||||
|
||||
#### 2. Crie o Arquivo
|
||||
|
||||
Crie um novo arquivo markdown em [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/)
|
||||
Crie um novo arquivo markdown em [`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/)
|
||||
com um nome descritivo (por exemplo, `docker.md` para um agente focado em Docker).
|
||||
|
||||
Atualize o arquivo com o frontmatter necessário [de acordo com o formato exigido](./microagents-overview#microagent-format)
|
||||
|
||||
@ -32,7 +32,7 @@ Before creating a global microagent, consider:
|
||||
#### 2. Create File
|
||||
|
||||
Create a new Markdown file with a descriptive name in the appropriate directory:
|
||||
[`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge)
|
||||
[`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents)
|
||||
|
||||
#### 3. Testing the Global Microagent
|
||||
|
||||
|
||||
@ -15,11 +15,11 @@ This directory (`OpenHands/microagents/`) contains shareable microagents that ar
|
||||
Directory structure:
|
||||
```
|
||||
OpenHands/microagents/
|
||||
├── knowledge/ # Keyword-triggered expertise
|
||||
│ ├── git.md # Git operations
|
||||
│ ├── testing.md # Testing practices
|
||||
│ └── docker.md # Docker guidelines
|
||||
└── tasks/ # Interactive workflows
|
||||
├── # Keyword-triggered expertise
|
||||
│ ├── git.md # Git operations
|
||||
│ ├── testing.md # Testing practices
|
||||
│ └── docker.md # Docker guidelines
|
||||
└── # These microagents are always loaded
|
||||
├── pr_review.md # PR review process
|
||||
├── bug_fix.md # Bug fixing workflow
|
||||
└── feature.md # Feature implementation
|
||||
@ -37,8 +37,7 @@ your-repository/
|
||||
└── .openhands/
|
||||
└── microagents/
|
||||
└── repo.md # Repository-specific instructions
|
||||
└── knowledges/ # Private micro-agents that are only available inside this repo
|
||||
└── tasks/ # Private micro-agents that are only available inside this repo
|
||||
└── ... # Private micro-agents that are only available inside this repo
|
||||
```
|
||||
|
||||
|
||||
@ -47,7 +46,6 @@ your-repository/
|
||||
When OpenHands works with a repository, it:
|
||||
1. Loads repository-specific instructions from `.openhands/microagents/repo.md` if present
|
||||
2. Loads relevant knowledge agents based on keywords in conversations
|
||||
3. Enable task agent if user select one of them
|
||||
|
||||
## Types of Microagents
|
||||
|
||||
@ -68,7 +66,7 @@ Key characteristics:
|
||||
- **Reusable**: Knowledge can be applied across multiple projects
|
||||
- **Versioned**: Support multiple versions of tools/frameworks
|
||||
|
||||
You can see an example of a knowledge-based agent in [OpenHands's github microagent](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/github.md).
|
||||
You can see an example of a knowledge-based agent in [OpenHands's github microagent](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/github.md).
|
||||
|
||||
### 2. Repository Agents
|
||||
|
||||
@ -86,22 +84,6 @@ Key features:
|
||||
|
||||
You can see an example of a repo agent in [the agent for the OpenHands repo itself](https://github.com/All-Hands-AI/OpenHands/blob/main/.openhands/microagents/repo.md).
|
||||
|
||||
### 3. Task Agents
|
||||
|
||||
Task agents provide interactive workflows that guide users through common development tasks. They:
|
||||
- Accept user inputs
|
||||
- Follow predefined steps
|
||||
- Adapt to context
|
||||
- Provide consistent results
|
||||
|
||||
Key capabilities:
|
||||
- **Interactive**: Guide users through complex processes
|
||||
- **Validating**: Check inputs and conditions
|
||||
- **Flexible**: Adapt to different scenarios
|
||||
- **Reproducible**: Ensure consistent outcomes
|
||||
|
||||
Example workflow:
|
||||
You can see an example of a task-based agent in [OpenHands's pull request updating microagent](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/tasks/update_pr_description.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -113,13 +95,8 @@ You can see an example of a task-based agent in [OpenHands's pull request updati
|
||||
- Common problem solutions
|
||||
- General development guidelines
|
||||
|
||||
2. **Task Agents** - When you have:
|
||||
- Repeatable workflows
|
||||
- Multi-step processes
|
||||
- Common development tasks
|
||||
- Standard procedures
|
||||
|
||||
3. **Repository Agents** - When you need:
|
||||
2. **Repository Agents** - When you need:
|
||||
- Project-specific guidelines
|
||||
- Team conventions and practices
|
||||
- Custom workflow documentation
|
||||
@ -134,14 +111,8 @@ You can see an example of a task-based agent in [OpenHands's pull request updati
|
||||
- Use file patterns when relevant
|
||||
- Keep knowledge general and reusable
|
||||
|
||||
2. **For Task Agents**:
|
||||
- Break workflows into clear steps
|
||||
- Validate user inputs
|
||||
- Provide helpful defaults
|
||||
- Include usage examples
|
||||
- Make steps adaptable
|
||||
|
||||
3. **For Repository Agents**:
|
||||
2. **For Repository Agents**:
|
||||
- Document clear setup instructions
|
||||
- Include repository structure details
|
||||
- Specify testing and build procedures
|
||||
@ -152,9 +123,8 @@ You can see an example of a task-based agent in [OpenHands's pull request updati
|
||||
### Submission Process
|
||||
|
||||
1. Create your agent file in the appropriate directory:
|
||||
- `knowledge/` for expertise (public, shareable)
|
||||
- `tasks/` for workflows (public, shareable)
|
||||
- Note: Repository agents should remain in their respective repositories' `.openhands/microagents/` directory
|
||||
- `microagents/` for expertise (public, shareable)
|
||||
- Note: Repository-specific agents should remain in their respective repositories' `.openhands/microagents/` directory
|
||||
2. Test thoroughly
|
||||
3. Submit a pull request to OpenHands
|
||||
|
||||
|
||||
@ -38,4 +38,4 @@ For detailed information, see:
|
||||
|
||||
- [Microagents Overview](https://docs.all-hands.dev/modules/usage/prompting/microagents-overview)
|
||||
- [Microagents Syntax](https://docs.all-hands.dev/modules/usage/prompting/microagents-syntax)
|
||||
- [Example GitHub Microagent](https://github.com/All-Hands-AI/OpenHands/blob/main/microagents/knowledge/github.md)
|
||||
- [Example GitHub Microagent](https://github.com/All-Hands-AI/OpenHands/blob/main/microagents/github.md)
|
||||
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: add_openhands_repo_instruction
|
||||
type: task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: address_pr_comments
|
||||
type: task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: get_test_to_pass
|
||||
type: task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: update_pr_description
|
||||
type: task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: update_test_for_new_implementation
|
||||
type: task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
@ -109,7 +109,7 @@ class GitLabService(BaseGitService, GitService):
|
||||
raise self.handle_http_error(e)
|
||||
|
||||
async def execute_graphql_query(
|
||||
self, query: str, variables: dict[str, Any]|None = None
|
||||
self, query: str, variables: dict[str, Any] | None = None
|
||||
) -> Any:
|
||||
"""
|
||||
Execute a GraphQL query against the GitLab GraphQL API
|
||||
|
||||
@ -249,7 +249,7 @@ class Memory:
|
||||
"""
|
||||
Loads microagents from the global microagents_dir
|
||||
"""
|
||||
repo_agents, knowledge_agents, _ = load_microagents_from_dir(
|
||||
repo_agents, knowledge_agents = load_microagents_from_dir(
|
||||
GLOBAL_MICROAGENTS_DIR
|
||||
)
|
||||
for name, agent in knowledge_agents.items():
|
||||
|
||||
@ -2,18 +2,15 @@ from .microagent import (
|
||||
BaseMicroagent,
|
||||
KnowledgeMicroagent,
|
||||
RepoMicroagent,
|
||||
TaskMicroagent,
|
||||
load_microagents_from_dir,
|
||||
)
|
||||
from .types import MicroagentMetadata, MicroagentType, TaskInput
|
||||
from .types import MicroagentMetadata, MicroagentType
|
||||
|
||||
__all__ = [
|
||||
'BaseMicroagent',
|
||||
'KnowledgeMicroagent',
|
||||
'RepoMicroagent',
|
||||
'TaskMicroagent',
|
||||
'MicroagentMetadata',
|
||||
'MicroagentType',
|
||||
'TaskInput',
|
||||
'load_microagents_from_dir',
|
||||
]
|
||||
|
||||
@ -23,11 +23,23 @@ class BaseMicroagent(BaseModel):
|
||||
|
||||
@classmethod
|
||||
def load(
|
||||
cls, path: Union[str, Path], file_content: str | None = None
|
||||
cls,
|
||||
path: Union[str, Path],
|
||||
microagent_dir: Path | None = None,
|
||||
file_content: str | None = None,
|
||||
) -> 'BaseMicroagent':
|
||||
"""Load a microagent from a markdown file with frontmatter."""
|
||||
"""Load a microagent from a markdown file with frontmatter.
|
||||
|
||||
The agent's name is derived from its path relative to the microagent_dir.
|
||||
"""
|
||||
path = Path(path) if isinstance(path, str) else path
|
||||
|
||||
# Calculate derived name from relative path if microagent_dir is provided
|
||||
# Otherwise, we will rely on the name from metadata later
|
||||
derived_name = None
|
||||
if microagent_dir is not None:
|
||||
derived_name = str(path.relative_to(microagent_dir).with_suffix(''))
|
||||
|
||||
# Only load directly from path if file_content is not provided
|
||||
if file_content is None:
|
||||
with open(path) as f:
|
||||
@ -59,18 +71,33 @@ class BaseMicroagent(BaseModel):
|
||||
subclass_map = {
|
||||
MicroagentType.KNOWLEDGE: KnowledgeMicroagent,
|
||||
MicroagentType.REPO_KNOWLEDGE: RepoMicroagent,
|
||||
MicroagentType.TASK: TaskMicroagent,
|
||||
}
|
||||
if metadata.type not in subclass_map:
|
||||
raise ValueError(f'Unknown microagent type: {metadata.type}')
|
||||
|
||||
agent_class = subclass_map[metadata.type]
|
||||
# Infer the agent type:
|
||||
# 1. If triggers exist -> KNOWLEDGE
|
||||
# 2. Else (no triggers) -> REPO
|
||||
inferred_type: MicroagentType
|
||||
if metadata.triggers:
|
||||
inferred_type = MicroagentType.KNOWLEDGE
|
||||
else:
|
||||
# No triggers, default to REPO unless metadata explicitly says otherwise (which it shouldn't for REPO)
|
||||
# This handles cases where 'type' might be missing or defaulted by Pydantic
|
||||
inferred_type = MicroagentType.REPO_KNOWLEDGE
|
||||
|
||||
if inferred_type not in subclass_map:
|
||||
# This should theoretically not happen with the logic above
|
||||
raise ValueError(f'Could not determine microagent type for: {path}')
|
||||
|
||||
# Use derived_name if available (from relative path), otherwise fallback to metadata.name
|
||||
agent_name = derived_name if derived_name is not None else metadata.name
|
||||
|
||||
agent_class = subclass_map[inferred_type]
|
||||
return agent_class(
|
||||
name=metadata.name,
|
||||
name=agent_name,
|
||||
content=content,
|
||||
metadata=metadata,
|
||||
source=str(path),
|
||||
type=metadata.type,
|
||||
type=inferred_type,
|
||||
)
|
||||
|
||||
|
||||
@ -118,23 +145,14 @@ class RepoMicroagent(BaseMicroagent):
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
if self.type != MicroagentType.REPO_KNOWLEDGE:
|
||||
raise ValueError('RepoMicroagent must have type REPO_KNOWLEDGE')
|
||||
|
||||
|
||||
class TaskMicroagent(BaseMicroagent):
|
||||
"""Microagent specialized for task-based operations."""
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
if self.type != MicroagentType.TASK:
|
||||
raise ValueError('TaskMicroagent must have type TASK')
|
||||
raise ValueError(
|
||||
f'RepoMicroagent initialized with incorrect type: {self.type}'
|
||||
)
|
||||
|
||||
|
||||
def load_microagents_from_dir(
|
||||
microagent_dir: Union[str, Path],
|
||||
) -> tuple[
|
||||
dict[str, RepoMicroagent], dict[str, KnowledgeMicroagent], dict[str, TaskMicroagent]
|
||||
]:
|
||||
) -> tuple[dict[str, RepoMicroagent], dict[str, KnowledgeMicroagent]]:
|
||||
"""Load all microagents from the given directory.
|
||||
|
||||
Note, legacy repo instructions will not be loaded here.
|
||||
@ -150,9 +168,8 @@ def load_microagents_from_dir(
|
||||
|
||||
repo_agents = {}
|
||||
knowledge_agents = {}
|
||||
task_agents = {}
|
||||
|
||||
# Load all agents from .openhands/microagents directory
|
||||
# Load all agents from microagents directory
|
||||
logger.debug(f'Loading agents from {microagent_dir}')
|
||||
if microagent_dir.exists():
|
||||
for file in microagent_dir.rglob('*.md'):
|
||||
@ -161,15 +178,13 @@ def load_microagents_from_dir(
|
||||
if file.name == 'README.md':
|
||||
continue
|
||||
try:
|
||||
agent = BaseMicroagent.load(file)
|
||||
agent = BaseMicroagent.load(file, microagent_dir)
|
||||
if isinstance(agent, RepoMicroagent):
|
||||
repo_agents[agent.name] = agent
|
||||
elif isinstance(agent, KnowledgeMicroagent):
|
||||
knowledge_agents[agent.name] = agent
|
||||
elif isinstance(agent, TaskMicroagent):
|
||||
task_agents[agent.name] = agent
|
||||
logger.debug(f'Loaded agent {agent.name} from {file}')
|
||||
except Exception as e:
|
||||
raise ValueError(f'Error loading agent from {file}: {e}')
|
||||
|
||||
return repo_agents, knowledge_agents, task_agents
|
||||
return repo_agents, knowledge_agents
|
||||
|
||||
@ -8,7 +8,6 @@ class MicroagentType(str, Enum):
|
||||
|
||||
KNOWLEDGE = 'knowledge'
|
||||
REPO_KNOWLEDGE = 'repo'
|
||||
TASK = 'task'
|
||||
|
||||
|
||||
class MicroagentMetadata(BaseModel):
|
||||
@ -19,11 +18,3 @@ class MicroagentMetadata(BaseModel):
|
||||
version: str = Field(default='1.0.0')
|
||||
agent: str = Field(default='CodeActAgent')
|
||||
triggers: list[str] = [] # optional, only exists for knowledge microagents
|
||||
|
||||
|
||||
class TaskInput(BaseModel):
|
||||
"""Input parameter for task-based agents."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
required: bool = True
|
||||
|
||||
@ -457,7 +457,9 @@ class Runtime(FileEditRuntimeMixin):
|
||||
self.log('info', 'openhands_instructions microagent loaded.')
|
||||
loaded_microagents.append(
|
||||
BaseMicroagent.load(
|
||||
path='.openhands_instructions', file_content=obs.content
|
||||
path='.openhands_instructions',
|
||||
microagent_dir=None,
|
||||
file_content=obs.content,
|
||||
)
|
||||
)
|
||||
|
||||
@ -483,16 +485,13 @@ class Runtime(FileEditRuntimeMixin):
|
||||
# Clean up the temporary zip file
|
||||
zip_path.unlink()
|
||||
# Load all microagents using the existing function
|
||||
repo_agents, knowledge_agents, task_agents = load_microagents_from_dir(
|
||||
microagent_folder
|
||||
)
|
||||
repo_agents, knowledge_agents = load_microagents_from_dir(microagent_folder)
|
||||
self.log(
|
||||
'info',
|
||||
f'Loaded {len(repo_agents)} repo agents, {len(knowledge_agents)} knowledge agents, and {len(task_agents)} task agents',
|
||||
f'Loaded {len(repo_agents)} repo agents and {len(knowledge_agents)} knowledge agents',
|
||||
)
|
||||
loaded_microagents.extend(repo_agents.values())
|
||||
loaded_microagents.extend(knowledge_agents.values())
|
||||
loaded_microagents.extend(task_agents.values())
|
||||
shutil.rmtree(microagent_folder)
|
||||
|
||||
return loaded_microagents
|
||||
|
||||
@ -7,7 +7,7 @@ from conftest import (
|
||||
_load_runtime,
|
||||
)
|
||||
|
||||
from openhands.microagent import KnowledgeMicroagent, RepoMicroagent, TaskMicroagent
|
||||
from openhands.microagent import KnowledgeMicroagent, RepoMicroagent
|
||||
|
||||
|
||||
def _create_test_microagents(test_dir: str):
|
||||
@ -48,22 +48,6 @@ Repository-specific test instructions.
|
||||
"""
|
||||
(microagents_dir / 'repo.md').write_text(repo_agent)
|
||||
|
||||
# Create test task agent in a nested directory
|
||||
task_dir = microagents_dir / 'tasks' / 'nested'
|
||||
task_dir.mkdir(parents=True, exist_ok=True)
|
||||
task_agent = """---
|
||||
name: test_task
|
||||
type: task
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
---
|
||||
|
||||
# Test Task
|
||||
|
||||
Test task content
|
||||
"""
|
||||
(task_dir / 'task.md').write_text(task_agent)
|
||||
|
||||
# Create legacy repo instructions
|
||||
legacy_instructions = """# Legacy Instructions
|
||||
|
||||
@ -88,26 +72,20 @@ def test_load_microagents_with_trailing_slashes(
|
||||
a for a in loaded_agents if isinstance(a, KnowledgeMicroagent)
|
||||
]
|
||||
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)]
|
||||
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)]
|
||||
|
||||
# Check knowledge agents
|
||||
assert len(knowledge_agents) == 1
|
||||
agent = knowledge_agents[0]
|
||||
assert agent.name == 'test_knowledge_agent'
|
||||
assert agent.name == 'knowledge/knowledge'
|
||||
assert 'test' in agent.triggers
|
||||
assert 'pytest' in agent.triggers
|
||||
|
||||
# Check repo agents (including legacy)
|
||||
assert len(repo_agents) == 2 # repo.md + .openhands_instructions
|
||||
repo_names = {a.name for a in repo_agents}
|
||||
assert 'test_repo_agent' in repo_names
|
||||
assert 'repo' in repo_names
|
||||
assert 'repo_legacy' in repo_names
|
||||
|
||||
# Check task agents
|
||||
assert len(task_agents) == 1
|
||||
agent = task_agents[0]
|
||||
assert agent.name == 'test_task'
|
||||
|
||||
finally:
|
||||
_close_test_runtime(runtime)
|
||||
|
||||
@ -131,26 +109,20 @@ def test_load_microagents_with_selected_repo(temp_dir, runtime_cls, run_as_openh
|
||||
a for a in loaded_agents if isinstance(a, KnowledgeMicroagent)
|
||||
]
|
||||
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)]
|
||||
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)]
|
||||
|
||||
# Check knowledge agents
|
||||
assert len(knowledge_agents) == 1
|
||||
agent = knowledge_agents[0]
|
||||
assert agent.name == 'test_knowledge_agent'
|
||||
assert agent.name == 'knowledge/knowledge'
|
||||
assert 'test' in agent.triggers
|
||||
assert 'pytest' in agent.triggers
|
||||
|
||||
# Check repo agents (including legacy)
|
||||
assert len(repo_agents) == 2 # repo.md + .openhands_instructions
|
||||
repo_names = {a.name for a in repo_agents}
|
||||
assert 'test_repo_agent' in repo_names
|
||||
assert 'repo' in repo_names
|
||||
assert 'repo_legacy' in repo_names
|
||||
|
||||
# Check task agents
|
||||
assert len(task_agents) == 1
|
||||
agent = task_agents[0]
|
||||
assert agent.name == 'test_task'
|
||||
|
||||
finally:
|
||||
_close_test_runtime(runtime)
|
||||
|
||||
@ -184,14 +156,12 @@ Repository-specific test instructions.
|
||||
a for a in loaded_agents if isinstance(a, KnowledgeMicroagent)
|
||||
]
|
||||
repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)]
|
||||
task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)]
|
||||
|
||||
assert len(knowledge_agents) == 0
|
||||
assert len(repo_agents) == 1
|
||||
assert len(task_agents) == 0
|
||||
|
||||
agent = repo_agents[0]
|
||||
assert agent.name == 'test_repo_agent'
|
||||
assert agent.name == 'repo'
|
||||
|
||||
finally:
|
||||
_close_test_runtime(runtime)
|
||||
|
||||
@ -152,8 +152,9 @@ async def test_memory_with_microagents():
|
||||
# from the global directory that's in the repo
|
||||
assert len(memory.knowledge_microagents) > 0
|
||||
|
||||
# We know 'flarglebargle' exists in the global directory
|
||||
assert 'flarglebargle' in memory.knowledge_microagents
|
||||
# Check for the derived name 'flarglebargle'
|
||||
derived_name = 'flarglebargle'
|
||||
assert derived_name in memory.knowledge_microagents
|
||||
|
||||
# Create a microagent action with the trigger word
|
||||
microagent_action = RecallAction(
|
||||
@ -187,7 +188,8 @@ async def test_memory_with_microagents():
|
||||
assert source == EventSource.ENVIRONMENT
|
||||
assert observation.recall_type == RecallType.KNOWLEDGE
|
||||
assert len(observation.microagent_knowledge) == 1
|
||||
assert observation.microagent_knowledge[0].name == 'flarglebargle'
|
||||
# Check against the derived name
|
||||
assert observation.microagent_knowledge[0].name == derived_name
|
||||
assert observation.microagent_knowledge[0].trigger == 'flarglebargle'
|
||||
assert 'magic word' in observation.microagent_knowledge[0].content
|
||||
|
||||
@ -280,8 +282,9 @@ async def test_memory_with_agent_microagents():
|
||||
# from the global directory that's in the repo
|
||||
assert len(memory.knowledge_microagents) > 0
|
||||
|
||||
# We know 'flarglebargle' exists in the global directory
|
||||
assert 'flarglebargle' in memory.knowledge_microagents
|
||||
# Check for the derived name 'flarglebargle'
|
||||
derived_name = 'flarglebargle'
|
||||
assert derived_name in memory.knowledge_microagents
|
||||
|
||||
# Create a microagent action with the trigger word
|
||||
microagent_action = RecallAction(
|
||||
@ -315,7 +318,8 @@ async def test_memory_with_agent_microagents():
|
||||
assert source == EventSource.ENVIRONMENT
|
||||
assert observation.recall_type == RecallType.KNOWLEDGE
|
||||
assert len(observation.microagent_knowledge) == 1
|
||||
assert observation.microagent_knowledge[0].name == 'flarglebargle'
|
||||
# Check against the derived name
|
||||
assert observation.microagent_knowledge[0].name == derived_name
|
||||
assert observation.microagent_knowledge[0].trigger == 'flarglebargle'
|
||||
assert 'magic word' in observation.microagent_knowledge[0].content
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@ def test_load_markdown_without_frontmatter():
|
||||
content = '# Test Content\nThis is a test markdown file without frontmatter.'
|
||||
path = Path('test.md')
|
||||
|
||||
# Load the agent from content
|
||||
agent = BaseMicroagent.load(path, content)
|
||||
# Load the agent from content using keyword argument
|
||||
agent = BaseMicroagent.load(path=path, file_content=content)
|
||||
|
||||
# Verify it's loaded as a repo agent with default values
|
||||
assert isinstance(agent, RepoMicroagent)
|
||||
@ -28,8 +28,8 @@ def test_load_markdown_with_empty_frontmatter():
|
||||
)
|
||||
path = Path('test.md')
|
||||
|
||||
# Load the agent from content
|
||||
agent = BaseMicroagent.load(path, content)
|
||||
# Load the agent from content using keyword argument
|
||||
agent = BaseMicroagent.load(path=path, file_content=content)
|
||||
|
||||
# Verify it's loaded as a repo agent with default values
|
||||
assert isinstance(agent, RepoMicroagent)
|
||||
@ -52,8 +52,8 @@ name: custom_name
|
||||
This is a test markdown file with partial frontmatter."""
|
||||
path = Path('test.md')
|
||||
|
||||
# Load the agent from content
|
||||
agent = BaseMicroagent.load(path, content)
|
||||
# Load the agent from content using keyword argument
|
||||
agent = BaseMicroagent.load(path=path, file_content=content)
|
||||
|
||||
# Verify it uses provided name but default values for other fields
|
||||
assert isinstance(agent, RepoMicroagent)
|
||||
@ -79,8 +79,8 @@ version: 2.0.0
|
||||
This is a test markdown file with full frontmatter."""
|
||||
path = Path('test.md')
|
||||
|
||||
# Load the agent from content
|
||||
agent = BaseMicroagent.load(path, content)
|
||||
# Load the agent from content using keyword argument
|
||||
agent = BaseMicroagent.load(path=path, file_content=content)
|
||||
|
||||
# Verify all provided values are used
|
||||
assert isinstance(agent, RepoMicroagent)
|
||||
|
||||
@ -5,14 +5,12 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from openhands.core.exceptions import MicroagentValidationError
|
||||
from openhands.microagent import (
|
||||
BaseMicroagent,
|
||||
KnowledgeMicroagent,
|
||||
MicroagentMetadata,
|
||||
MicroagentType,
|
||||
RepoMicroagent,
|
||||
TaskMicroagent,
|
||||
load_microagents_from_dir,
|
||||
)
|
||||
|
||||
@ -26,9 +24,10 @@ def test_legacy_micro_agent_load(tmp_path):
|
||||
legacy_file = tmp_path / '.openhands_instructions'
|
||||
legacy_file.write_text(CONTENT)
|
||||
|
||||
micro_agent = BaseMicroagent.load(legacy_file)
|
||||
# Pass microagent_dir (tmp_path in this case) to load
|
||||
micro_agent = BaseMicroagent.load(legacy_file, tmp_path)
|
||||
assert isinstance(micro_agent, RepoMicroagent)
|
||||
assert micro_agent.name == 'repo_legacy'
|
||||
assert micro_agent.name == 'repo_legacy' # Legacy name is hardcoded
|
||||
assert micro_agent.content == CONTENT
|
||||
assert micro_agent.type == MicroagentType.REPO_KNOWLEDGE
|
||||
|
||||
@ -39,10 +38,9 @@ def temp_microagents_dir():
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
|
||||
# Create test knowledge agent
|
||||
# Create test knowledge agent (type inferred from triggers)
|
||||
knowledge_agent = """---
|
||||
name: test_knowledge_agent
|
||||
type: knowledge
|
||||
# type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
@ -56,10 +54,9 @@ Testing best practices and guidelines.
|
||||
"""
|
||||
(root / 'knowledge.md').write_text(knowledge_agent)
|
||||
|
||||
# Create test repo agent
|
||||
# Create test repo agent (type inferred from lack of triggers)
|
||||
repo_agent = """---
|
||||
name: test_repo_agent
|
||||
type: repo
|
||||
# type: repo
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
---
|
||||
@ -70,33 +67,19 @@ Repository-specific test instructions.
|
||||
"""
|
||||
(root / 'repo.md').write_text(repo_agent)
|
||||
|
||||
# Create test task agent
|
||||
task_agent = """---
|
||||
name: test_task
|
||||
type: task
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
---
|
||||
|
||||
# Test Task
|
||||
|
||||
Test task content
|
||||
"""
|
||||
(root / 'task.md').write_text(task_agent)
|
||||
|
||||
yield root
|
||||
|
||||
|
||||
def test_knowledge_agent():
|
||||
"""Test knowledge agent functionality."""
|
||||
# Note: We still pass type to the constructor here, as it expects it.
|
||||
# The loader infers the type before calling the constructor.
|
||||
agent = KnowledgeMicroagent(
|
||||
name='test',
|
||||
content='Test content',
|
||||
metadata=MicroagentMetadata(
|
||||
name='test', type=MicroagentType.KNOWLEDGE, triggers=['test', 'pytest']
|
||||
),
|
||||
metadata=MicroagentMetadata(name='test', triggers=['test', 'pytest']),
|
||||
source='test.md',
|
||||
type=MicroagentType.KNOWLEDGE,
|
||||
type=MicroagentType.KNOWLEDGE, # Constructor still needs type
|
||||
)
|
||||
|
||||
assert agent.match_trigger('running a test') == 'test'
|
||||
@ -107,42 +90,20 @@ def test_knowledge_agent():
|
||||
|
||||
def test_load_microagents(temp_microagents_dir):
|
||||
"""Test loading microagents from directory."""
|
||||
repo_agents, knowledge_agents, task_agents = load_microagents_from_dir(
|
||||
temp_microagents_dir
|
||||
)
|
||||
repo_agents, knowledge_agents = load_microagents_from_dir(temp_microagents_dir)
|
||||
|
||||
# Check knowledge agents
|
||||
# Check knowledge agents (name derived from filename: knowledge.md -> 'knowledge')
|
||||
assert len(knowledge_agents) == 1
|
||||
agent = knowledge_agents['test_knowledge_agent']
|
||||
assert isinstance(agent, KnowledgeMicroagent)
|
||||
assert 'test' in agent.triggers
|
||||
agent_k = knowledge_agents['knowledge']
|
||||
assert isinstance(agent_k, KnowledgeMicroagent)
|
||||
assert agent_k.type == MicroagentType.KNOWLEDGE # Check inferred type
|
||||
assert 'test' in agent_k.triggers
|
||||
|
||||
# Check repo agents
|
||||
# Check repo agents (name derived from filename: repo.md -> 'repo')
|
||||
assert len(repo_agents) == 1
|
||||
agent = repo_agents['test_repo_agent']
|
||||
assert isinstance(agent, RepoMicroagent)
|
||||
|
||||
# Check task agents
|
||||
assert len(task_agents) == 1
|
||||
agent = task_agents['test_task']
|
||||
assert isinstance(agent, TaskMicroagent)
|
||||
|
||||
|
||||
def test_invalid_agent_type(temp_microagents_dir):
|
||||
"""Test loading agent with invalid type."""
|
||||
invalid_agent = """---
|
||||
name: test_invalid
|
||||
type: invalid
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
---
|
||||
|
||||
Invalid agent content
|
||||
"""
|
||||
(temp_microagents_dir / 'invalid.md').write_text(invalid_agent)
|
||||
|
||||
with pytest.raises(MicroagentValidationError):
|
||||
BaseMicroagent.load(temp_microagents_dir / 'invalid.md')
|
||||
agent_r = repo_agents['repo']
|
||||
assert isinstance(agent_r, RepoMicroagent)
|
||||
assert agent_r.type == MicroagentType.REPO_KNOWLEDGE # Check inferred type
|
||||
|
||||
|
||||
def test_load_microagents_with_nested_dirs(temp_microagents_dir):
|
||||
@ -151,8 +112,7 @@ def test_load_microagents_with_nested_dirs(temp_microagents_dir):
|
||||
nested_dir = temp_microagents_dir / 'nested' / 'dir'
|
||||
nested_dir.mkdir(parents=True)
|
||||
nested_agent = """---
|
||||
name: nested_knowledge_agent
|
||||
type: knowledge
|
||||
# type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
@ -165,25 +125,25 @@ Testing nested directory loading.
|
||||
"""
|
||||
(nested_dir / 'nested.md').write_text(nested_agent)
|
||||
|
||||
repo_agents, knowledge_agents, task_agents = load_microagents_from_dir(
|
||||
temp_microagents_dir
|
||||
)
|
||||
repo_agents, knowledge_agents = load_microagents_from_dir(temp_microagents_dir)
|
||||
|
||||
# Check that we can find the nested agent
|
||||
assert len(knowledge_agents) == 2 # Original + nested
|
||||
agent = knowledge_agents['nested_knowledge_agent']
|
||||
assert isinstance(agent, KnowledgeMicroagent)
|
||||
assert 'nested' in agent.triggers
|
||||
# Check that we can find the nested agent (name derived from path: nested/dir/nested.md -> 'nested/dir/nested')
|
||||
assert (
|
||||
len(knowledge_agents) == 2
|
||||
) # Original ('knowledge') + nested ('nested/dir/nested')
|
||||
agent_n = knowledge_agents['nested/dir/nested']
|
||||
assert isinstance(agent_n, KnowledgeMicroagent)
|
||||
assert agent_n.type == MicroagentType.KNOWLEDGE # Check inferred type
|
||||
assert 'nested' in agent_n.triggers
|
||||
|
||||
|
||||
def test_load_microagents_with_trailing_slashes(temp_microagents_dir):
|
||||
"""Test loading microagents when directory paths have trailing slashes."""
|
||||
# Create a directory with trailing slash
|
||||
knowledge_dir = temp_microagents_dir / 'knowledge/'
|
||||
knowledge_dir = temp_microagents_dir / 'test_knowledge/'
|
||||
knowledge_dir.mkdir(exist_ok=True)
|
||||
knowledge_agent = """---
|
||||
name: trailing_knowledge_agent
|
||||
type: knowledge
|
||||
# type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
@ -196,12 +156,15 @@ Testing loading with trailing slashes.
|
||||
"""
|
||||
(knowledge_dir / 'trailing.md').write_text(knowledge_agent)
|
||||
|
||||
repo_agents, knowledge_agents, task_agents = load_microagents_from_dir(
|
||||
repo_agents, knowledge_agents = load_microagents_from_dir(
|
||||
str(temp_microagents_dir) + '/' # Add trailing slash to test
|
||||
)
|
||||
|
||||
# Check that we can find the agent despite trailing slashes
|
||||
assert len(knowledge_agents) == 2 # Original + trailing
|
||||
agent = knowledge_agents['trailing_knowledge_agent']
|
||||
assert isinstance(agent, KnowledgeMicroagent)
|
||||
assert 'trailing' in agent.triggers
|
||||
# Check that we can find the agent despite trailing slashes (name derived from path: test_knowledge/trailing.md -> 'test_knowledge/trailing')
|
||||
assert (
|
||||
len(knowledge_agents) == 2
|
||||
) # Original ('knowledge') + trailing ('test_knowledge/trailing')
|
||||
agent_t = knowledge_agents['test_knowledge/trailing']
|
||||
assert isinstance(agent_t, KnowledgeMicroagent)
|
||||
assert agent_t.type == MicroagentType.KNOWLEDGE # Check inferred type
|
||||
assert 'trailing' in agent_t.triggers
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user