agent.py 6.4 KB

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