agent.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import time
  2. from typing import List, TypedDict
  3. from opendevin.controller.agent import Agent
  4. from opendevin.controller.state.state import State
  5. from opendevin.events.action import (
  6. Action,
  7. AddTaskAction,
  8. AgentFinishAction,
  9. AgentRecallAction,
  10. AgentRejectAction,
  11. BrowseURLAction,
  12. CmdRunAction,
  13. FileReadAction,
  14. FileWriteAction,
  15. MessageAction,
  16. ModifyTaskAction,
  17. )
  18. from opendevin.events.observation import (
  19. AgentRecallObservation,
  20. CmdOutputObservation,
  21. FileReadObservation,
  22. FileWriteObservation,
  23. NullObservation,
  24. Observation,
  25. )
  26. from opendevin.llm.llm import LLM
  27. """
  28. FIXME: There are a few problems this surfaced
  29. * FileWrites seem to add an unintended newline at the end of the file
  30. * command_id is sometimes a number, sometimes a string
  31. * Why isn't the output of the background command split between two steps?
  32. * Browser not working
  33. """
  34. ActionObs = TypedDict(
  35. 'ActionObs', {'action': Action, 'observations': List[Observation]}
  36. )
  37. BACKGROUND_CMD = 'echo "This is in the background" && sleep .1 && echo "This too"'
  38. class DummyAgent(Agent):
  39. """
  40. The DummyAgent is used for e2e testing. It just sends the same set of actions deterministically,
  41. without making any LLM calls.
  42. """
  43. def __init__(self, llm: LLM):
  44. super().__init__(llm)
  45. self.steps: List[ActionObs] = [
  46. {
  47. 'action': AddTaskAction(parent='0', goal='check the current directory'),
  48. 'observations': [NullObservation('')],
  49. },
  50. {
  51. 'action': AddTaskAction(parent='0.0', goal='run ls'),
  52. 'observations': [NullObservation('')],
  53. },
  54. {
  55. 'action': ModifyTaskAction(id='0.0', state='in_progress'),
  56. 'observations': [NullObservation('')],
  57. },
  58. {
  59. 'action': MessageAction('Time to get started!'),
  60. 'observations': [NullObservation('')],
  61. },
  62. {
  63. 'action': CmdRunAction(command='echo "foo"'),
  64. 'observations': [
  65. CmdOutputObservation('foo', command_id=-1, command='echo "foo"')
  66. ],
  67. },
  68. {
  69. 'action': FileWriteAction(
  70. content='echo "Hello, World!"', path='hello.sh'
  71. ),
  72. 'observations': [FileWriteObservation('', path='hello.sh')],
  73. },
  74. {
  75. 'action': FileReadAction(path='hello.sh'),
  76. 'observations': [
  77. FileReadObservation('echo "Hello, World!"\n', path='hello.sh')
  78. ],
  79. },
  80. {
  81. 'action': CmdRunAction(command='bash hello.sh'),
  82. 'observations': [
  83. CmdOutputObservation(
  84. 'Hello, World!', command_id=-1, command='bash hello.sh'
  85. )
  86. ],
  87. },
  88. {
  89. 'action': CmdRunAction(command=BACKGROUND_CMD, background=True),
  90. 'observations': [
  91. CmdOutputObservation(
  92. 'Background command started. To stop it, send a `kill` action with id 42',
  93. command_id='42', # type: ignore[arg-type]
  94. command=BACKGROUND_CMD,
  95. ),
  96. CmdOutputObservation(
  97. 'This is in the background\nThis too\n',
  98. command_id='42', # type: ignore[arg-type]
  99. command=BACKGROUND_CMD,
  100. ),
  101. ],
  102. },
  103. {
  104. 'action': AgentRecallAction(query='who am I?'),
  105. 'observations': [
  106. AgentRecallObservation('', memories=['I am a computer.']),
  107. # CmdOutputObservation('This too\n', command_id='42', command=BACKGROUND_CMD),
  108. ],
  109. },
  110. {
  111. 'action': BrowseURLAction(url='https://google.com'),
  112. 'observations': [
  113. # BrowserOutputObservation('<html></html>', url='https://google.com', screenshot=""),
  114. ],
  115. },
  116. {
  117. 'action': AgentFinishAction(),
  118. 'observations': [],
  119. },
  120. {
  121. 'action': AgentRejectAction(),
  122. 'observations': [],
  123. },
  124. ]
  125. def step(self, state: State) -> Action:
  126. time.sleep(0.1)
  127. if state.iteration > 0:
  128. prev_step = self.steps[state.iteration - 1]
  129. if 'observations' in prev_step:
  130. expected_observations = prev_step['observations']
  131. hist_start = len(state.history) - len(expected_observations)
  132. for i in range(len(expected_observations)):
  133. hist_obs = state.history[hist_start + i][1].to_dict()
  134. expected_obs = expected_observations[i].to_dict()
  135. if (
  136. 'command_id' in hist_obs['extras']
  137. and hist_obs['extras']['command_id'] != -1
  138. ):
  139. del hist_obs['extras']['command_id']
  140. hist_obs['content'] = ''
  141. if (
  142. 'command_id' in expected_obs['extras']
  143. and expected_obs['extras']['command_id'] != -1
  144. ):
  145. del expected_obs['extras']['command_id']
  146. expected_obs['content'] = ''
  147. if hist_obs != expected_obs:
  148. print('\nactual', hist_obs)
  149. print('\nexpect', expected_obs)
  150. assert (
  151. hist_obs == expected_obs
  152. ), f'Expected observation {expected_obs}, got {hist_obs}'
  153. return self.steps[state.iteration]['action']
  154. def search_memory(self, query: str) -> List[str]:
  155. return ['I am a computer.']