codeact_swe_agent.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. from agenthub.codeact_swe_agent.prompt import (
  2. COMMAND_DOCS,
  3. SWE_EXAMPLE,
  4. SYSTEM_PREFIX,
  5. SYSTEM_SUFFIX,
  6. )
  7. from agenthub.codeact_swe_agent.response_parser import CodeActSWEResponseParser
  8. from opendevin.controller.agent import Agent
  9. from opendevin.controller.state.state import State
  10. from opendevin.events.action import (
  11. Action,
  12. AgentFinishAction,
  13. CmdRunAction,
  14. IPythonRunCellAction,
  15. MessageAction,
  16. )
  17. from opendevin.events.observation import (
  18. CmdOutputObservation,
  19. IPythonRunCellObservation,
  20. )
  21. from opendevin.events.observation.observation import Observation
  22. from opendevin.events.serialization.event import truncate_content
  23. from opendevin.llm.llm import LLM
  24. from opendevin.runtime.plugins import (
  25. AgentSkillsRequirement,
  26. JupyterRequirement,
  27. PluginRequirement,
  28. )
  29. from opendevin.runtime.tools import RuntimeTool
  30. def get_system_message() -> str:
  31. return f'{SYSTEM_PREFIX}\n\n{COMMAND_DOCS}\n\n{SYSTEM_SUFFIX}'
  32. def get_in_context_example() -> str:
  33. return SWE_EXAMPLE
  34. class CodeActSWEAgent(Agent):
  35. VERSION = '1.6'
  36. """
  37. This agent is an adaptation of the original [SWE Agent](https://swe-agent.com/) based on CodeAct 1.5 using the `agentskills` library of OpenDevin.
  38. It is intended use is **solving Github issues**.
  39. It removes web-browsing and Github capability from the original CodeAct agent to avoid confusion to the agent.
  40. """
  41. sandbox_plugins: list[PluginRequirement] = [
  42. # NOTE: AgentSkillsRequirement need to go before JupyterRequirement, since
  43. # AgentSkillsRequirement provides a lot of Python functions,
  44. # and it needs to be initialized before Jupyter for Jupyter to use those functions.
  45. AgentSkillsRequirement(),
  46. JupyterRequirement(),
  47. ]
  48. runtime_tools: list[RuntimeTool] = []
  49. system_message: str = get_system_message()
  50. in_context_example: str = f"Here is an example of how you can interact with the environment for task solving:\n{get_in_context_example()}\n\nNOW, LET'S START!"
  51. response_parser = CodeActSWEResponseParser()
  52. def __init__(
  53. self,
  54. llm: LLM,
  55. ) -> None:
  56. """Initializes a new instance of the CodeActAgent class.
  57. Parameters:
  58. - llm (LLM): The llm to be used by this agent
  59. """
  60. super().__init__(llm)
  61. self.reset()
  62. def action_to_str(self, action: Action) -> str:
  63. if isinstance(action, CmdRunAction):
  64. return (
  65. f'{action.thought}\n<execute_bash>\n{action.command}\n</execute_bash>'
  66. )
  67. elif isinstance(action, IPythonRunCellAction):
  68. return f'{action.thought}\n<execute_ipython>\n{action.code}\n</execute_ipython>'
  69. elif isinstance(action, MessageAction):
  70. return action.content
  71. return ''
  72. def get_action_message(self, action: Action) -> dict[str, str] | None:
  73. if (
  74. isinstance(action, CmdRunAction)
  75. or isinstance(action, IPythonRunCellAction)
  76. or isinstance(action, MessageAction)
  77. ):
  78. return {
  79. 'role': 'user' if action.source == 'user' else 'assistant',
  80. 'content': self.action_to_str(action),
  81. }
  82. return None
  83. def get_observation_message(self, obs: Observation) -> dict[str, str] | None:
  84. max_message_chars = self.llm.config.max_message_chars
  85. if isinstance(obs, CmdOutputObservation):
  86. content = 'OBSERVATION:\n' + truncate_content(
  87. obs.content, max_message_chars
  88. )
  89. content += (
  90. f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]'
  91. )
  92. return {'role': 'user', 'content': content}
  93. elif isinstance(obs, IPythonRunCellObservation):
  94. content = 'OBSERVATION:\n' + obs.content
  95. # replace base64 images with a placeholder
  96. splitted = content.split('\n')
  97. for i, line in enumerate(splitted):
  98. if '![image](data:image/png;base64,' in line:
  99. splitted[i] = (
  100. '![image](data:image/png;base64, ...) already displayed to user'
  101. )
  102. content = '\n'.join(splitted)
  103. content = truncate_content(content, max_message_chars)
  104. return {'role': 'user', 'content': content}
  105. return None
  106. def reset(self) -> None:
  107. """Resets the CodeAct Agent."""
  108. super().reset()
  109. def step(self, state: State) -> Action:
  110. """Performs one step using the CodeAct Agent.
  111. This includes gathering info on previous steps and prompting the model to make a command to execute.
  112. Parameters:
  113. - state (State): used to get updated info and background commands
  114. Returns:
  115. - CmdRunAction(command) - bash command to run
  116. - IPythonRunCellAction(code) - IPython code to run
  117. - MessageAction(content) - Message action to run (e.g. ask for clarification)
  118. - AgentFinishAction() - end the interaction
  119. """
  120. # if we're done, go back
  121. latest_user_message = state.history.get_last_user_message()
  122. if latest_user_message and latest_user_message.strip() == '/exit':
  123. return AgentFinishAction()
  124. # prepare what we want to send to the LLM
  125. messages: list[dict[str, str]] = self._get_messages(state)
  126. response = self.llm.completion(
  127. messages=messages,
  128. stop=[
  129. '</execute_ipython>',
  130. '</execute_bash>',
  131. ],
  132. temperature=0.0,
  133. )
  134. return self.response_parser.parse(response)
  135. def _get_messages(self, state: State) -> list[dict[str, str]]:
  136. messages = [
  137. {'role': 'system', 'content': self.system_message},
  138. {'role': 'user', 'content': self.in_context_example},
  139. ]
  140. for event in state.history.get_events():
  141. # create a regular message from an event
  142. if isinstance(event, Action):
  143. message = self.get_action_message(event)
  144. elif isinstance(event, Observation):
  145. message = self.get_observation_message(event)
  146. else:
  147. raise ValueError(f'Unknown event type: {type(event)}')
  148. # add regular message
  149. if message:
  150. messages.append(message)
  151. # the latest user message is important:
  152. # we want to remind the agent of the environment constraints
  153. latest_user_message = next(
  154. (m for m in reversed(messages) if m['role'] == 'user'), None
  155. )
  156. # add a reminder to the prompt
  157. if latest_user_message:
  158. latest_user_message['content'] += (
  159. f'\n\nENVIRONMENT REMINDER: You have {state.max_iterations - state.iteration} turns left to complete the task.'
  160. )
  161. return messages