test_micro_agents.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import json
  2. import os
  3. from unittest.mock import MagicMock
  4. import pytest
  5. import yaml
  6. from pytest import TempPathFactory
  7. from openhands.agenthub.micro.registry import all_microagents
  8. from openhands.controller.agent import Agent
  9. from openhands.controller.state.state import State
  10. from openhands.core.config import AgentConfig
  11. from openhands.events import EventSource
  12. from openhands.events.action import MessageAction
  13. from openhands.events.stream import EventStream
  14. from openhands.memory.history import ShortTermHistory
  15. from openhands.storage import get_file_store
  16. @pytest.fixture
  17. def temp_dir(tmp_path_factory: TempPathFactory) -> str:
  18. return str(tmp_path_factory.mktemp('test_micro_agents'))
  19. @pytest.fixture
  20. def event_stream(temp_dir):
  21. file_store = get_file_store('local', temp_dir)
  22. event_stream = EventStream('asdf', file_store)
  23. yield event_stream
  24. # clear after each test
  25. event_stream.clear()
  26. @pytest.fixture
  27. def agent_configs():
  28. return {
  29. 'CoderAgent': AgentConfig(memory_enabled=True),
  30. 'PlannerAgent': AgentConfig(memory_enabled=True),
  31. }
  32. def test_all_agents_are_loaded():
  33. assert all_microagents is not None
  34. assert len(all_microagents) > 1
  35. base = os.path.join('openhands', 'agenthub', 'micro')
  36. full_path = os.path.dirname(__file__) + '/../../' + base
  37. agent_names = set()
  38. for root, _, files in os.walk(full_path):
  39. for file in files:
  40. if file == 'agent.yaml':
  41. file_path = os.path.join(root, file)
  42. with open(file_path, 'r') as yaml_file:
  43. data = yaml.safe_load(yaml_file)
  44. agent_names.add(data['name'])
  45. assert agent_names == set(all_microagents.keys())
  46. def test_coder_agent_with_summary(event_stream: EventStream, agent_configs: dict):
  47. """Coder agent should render code summary as part of prompt"""
  48. mock_llm = MagicMock()
  49. content = json.dumps({'action': 'finish', 'args': {}})
  50. mock_llm.completion.return_value = {'choices': [{'message': {'content': content}}]}
  51. mock_llm.format_messages_for_llm.return_value = [
  52. {
  53. 'role': 'user',
  54. 'content': "This is a dummy task. This is a dummy summary about this repo. Here's a summary of the codebase, as it relates to this task.",
  55. }
  56. ]
  57. coder_agent = Agent.get_cls('CoderAgent')(
  58. llm=mock_llm, config=agent_configs['CoderAgent']
  59. )
  60. assert coder_agent is not None
  61. task = 'This is a dummy task'
  62. history = ShortTermHistory()
  63. history.set_event_stream(event_stream)
  64. event_stream.add_event(MessageAction(content=task), EventSource.USER)
  65. summary = 'This is a dummy summary about this repo'
  66. state = State(history=history, inputs={'summary': summary})
  67. coder_agent.step(state)
  68. mock_llm.completion.assert_called_once()
  69. _, kwargs = mock_llm.completion.call_args
  70. prompt_element = kwargs['messages'][0]['content']
  71. if isinstance(prompt_element, dict):
  72. prompt = prompt_element['content']
  73. else:
  74. prompt = prompt_element
  75. assert task in prompt
  76. assert "Here's a summary of the codebase, as it relates to this task" in prompt
  77. assert summary in prompt
  78. def test_coder_agent_without_summary(event_stream: EventStream, agent_configs: dict):
  79. """When there's no codebase_summary available, there shouldn't be any prompt
  80. about 'code summary'
  81. """
  82. mock_llm = MagicMock()
  83. content = json.dumps({'action': 'finish', 'args': {}})
  84. mock_llm.completion.return_value = {'choices': [{'message': {'content': content}}]}
  85. mock_llm.format_messages_for_llm.return_value = [
  86. {
  87. 'role': 'user',
  88. 'content': [
  89. {
  90. 'type': 'text',
  91. 'text': "This is a dummy task. This is a dummy summary about this repo. Here's a summary of the codebase, as it relates to this task.",
  92. }
  93. ],
  94. }
  95. ]
  96. coder_agent = Agent.get_cls('CoderAgent')(
  97. llm=mock_llm, config=agent_configs['CoderAgent']
  98. )
  99. assert coder_agent is not None
  100. task = 'This is a dummy task'
  101. history = ShortTermHistory()
  102. history.set_event_stream(event_stream)
  103. event_stream.add_event(MessageAction(content=task), EventSource.USER)
  104. # set state without codebase summary
  105. state = State(history=history)
  106. coder_agent.step(state)
  107. mock_llm.completion.assert_called_once()
  108. _, kwargs = mock_llm.completion.call_args
  109. prompt_element = kwargs['messages'][0]['content']
  110. if isinstance(prompt_element, dict):
  111. prompt = prompt_element['content']
  112. else:
  113. prompt = prompt_element
  114. print(f'\n{prompt_element}\n')
  115. assert "Here's a summary of the codebase, as it relates to this task" not in prompt