action_parser.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import re
  2. from opendevin.controller.action_parser import ActionParser, ResponseParser
  3. from opendevin.events.action import (
  4. Action,
  5. AgentDelegateAction,
  6. AgentFinishAction,
  7. CmdRunAction,
  8. IPythonRunCellAction,
  9. MessageAction,
  10. )
  11. class CodeActResponseParser(ResponseParser):
  12. """
  13. Parser action:
  14. - CmdRunAction(command) - bash command to run
  15. - IPythonRunCellAction(code) - IPython code to run
  16. - AgentDelegateAction(agent, inputs) - delegate action for (sub)task
  17. - MessageAction(content) - Message action to run (e.g. ask for clarification)
  18. - AgentFinishAction() - end the interaction
  19. """
  20. def __init__(
  21. self,
  22. ):
  23. # Need pay attention to the item order in self.action_parsers
  24. self.action_parsers = [
  25. CodeActActionParserFinish(),
  26. CodeActActionParserCmdRun(),
  27. CodeActActionParserIPythonRunCell(),
  28. CodeActActionParserAgentDelegate(),
  29. ]
  30. self.default_parser = CodeActActionParserMessage()
  31. def parse(self, response: str) -> Action:
  32. action_str = self.parse_response(response)
  33. return self.parse_action(action_str)
  34. def parse_response(self, response) -> str:
  35. action = response.choices[0].message.content
  36. for lang in ['bash', 'ipython', 'browse']:
  37. if f'<execute_{lang}>' in action and f'</execute_{lang}>' not in action:
  38. action += f'</execute_{lang}>'
  39. return action
  40. def parse_action(self, action_str: str) -> Action:
  41. for action_parser in self.action_parsers:
  42. if action_parser.check_condition(action_str):
  43. return action_parser.parse(action_str)
  44. return self.default_parser.parse(action_str)
  45. class CodeActActionParserFinish(ActionParser):
  46. """
  47. Parser action:
  48. - AgentFinishAction() - end the interaction
  49. """
  50. def __init__(
  51. self,
  52. ):
  53. self.finish_command = None
  54. def check_condition(self, action_str: str) -> bool:
  55. self.finish_command = re.search(r'<finish>.*</finish>', action_str, re.DOTALL)
  56. return self.finish_command is not None
  57. def parse(self, action_str: str) -> Action:
  58. assert (
  59. self.finish_command is not None
  60. ), 'self.finish_command should not be None when parse is called'
  61. thought = action_str.replace(self.finish_command.group(0), '').strip()
  62. return AgentFinishAction(thought=thought)
  63. class CodeActActionParserCmdRun(ActionParser):
  64. """
  65. Parser action:
  66. - CmdRunAction(command) - bash command to run
  67. - AgentFinishAction() - end the interaction
  68. """
  69. def __init__(
  70. self,
  71. ):
  72. self.bash_command = None
  73. def check_condition(self, action_str: str) -> bool:
  74. self.bash_command = re.search(
  75. r'<execute_bash>(.*?)</execute_bash>', action_str, re.DOTALL
  76. )
  77. return self.bash_command is not None
  78. def parse(self, action_str: str) -> Action:
  79. assert (
  80. self.bash_command is not None
  81. ), 'self.bash_command should not be None when parse is called'
  82. thought = action_str.replace(self.bash_command.group(0), '').strip()
  83. # a command was found
  84. command_group = self.bash_command.group(1).strip()
  85. if command_group.strip() == 'exit':
  86. return AgentFinishAction()
  87. return CmdRunAction(command=command_group, thought=thought)
  88. class CodeActActionParserIPythonRunCell(ActionParser):
  89. """
  90. Parser action:
  91. - IPythonRunCellAction(code) - IPython code to run
  92. """
  93. def __init__(
  94. self,
  95. ):
  96. self.python_code = None
  97. self.jupyter_kernel_init_code: str = 'from agentskills import *'
  98. def check_condition(self, action_str: str) -> bool:
  99. self.python_code = re.search(
  100. r'<execute_ipython>(.*?)</execute_ipython>', action_str, re.DOTALL
  101. )
  102. return self.python_code is not None
  103. def parse(self, action_str: str) -> Action:
  104. assert (
  105. self.python_code is not None
  106. ), 'self.python_code should not be None when parse is called'
  107. code_group = self.python_code.group(1).strip()
  108. thought = action_str.replace(self.python_code.group(0), '').strip()
  109. return IPythonRunCellAction(
  110. code=code_group,
  111. thought=thought,
  112. kernel_init_code=self.jupyter_kernel_init_code,
  113. )
  114. class CodeActActionParserAgentDelegate(ActionParser):
  115. """
  116. Parser action:
  117. - AgentDelegateAction(agent, inputs) - delegate action for (sub)task
  118. """
  119. def __init__(
  120. self,
  121. ):
  122. self.agent_delegate = None
  123. def check_condition(self, action_str: str) -> bool:
  124. self.agent_delegate = re.search(
  125. r'<execute_browse>(.*)</execute_browse>', action_str, re.DOTALL
  126. )
  127. return self.agent_delegate is not None
  128. def parse(self, action_str: str) -> Action:
  129. assert (
  130. self.agent_delegate is not None
  131. ), 'self.agent_delegate should not be None when parse is called'
  132. thought = action_str.replace(self.agent_delegate.group(0), '').strip()
  133. browse_actions = self.agent_delegate.group(1).strip()
  134. task = f'{thought}. I should start with: {browse_actions}'
  135. return AgentDelegateAction(agent='BrowsingAgent', inputs={'task': task})
  136. class CodeActActionParserMessage(ActionParser):
  137. """
  138. Parser action:
  139. - MessageAction(content) - Message action to run (e.g. ask for clarification)
  140. """
  141. def __init__(
  142. self,
  143. ):
  144. pass
  145. def check_condition(self, action_str: str) -> bool:
  146. # We assume the LLM is GOOD enough that when it returns pure natural language
  147. # it wants to talk to the user
  148. return True
  149. def parse(self, action_str: str) -> Action:
  150. return MessageAction(content=action_str, wait_for_response=True)