codeact_swe_agent.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. from agenthub.codeact_swe_agent.prompt import (
  2. COMMAND_DOCS,
  3. MINIMAL_SYSTEM_PREFIX,
  4. SWE_EXAMPLE,
  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.llm.llm import LLM
  22. from opendevin.runtime.plugins import (
  23. AgentSkillsRequirement,
  24. JupyterRequirement,
  25. PluginRequirement,
  26. )
  27. from opendevin.runtime.tools import RuntimeTool
  28. def action_to_str(action: Action) -> str:
  29. if isinstance(action, CmdRunAction):
  30. return f'{action.thought}\n<execute_bash>\n{action.command}\n</execute_bash>'
  31. elif isinstance(action, IPythonRunCellAction):
  32. return f'{action.thought}\n<execute_ipython>\n{action.code}\n</execute_ipython>'
  33. elif isinstance(action, MessageAction):
  34. return action.content
  35. return ''
  36. def get_action_message(action: Action) -> dict[str, str] | None:
  37. if (
  38. isinstance(action, CmdRunAction)
  39. or isinstance(action, IPythonRunCellAction)
  40. or isinstance(action, MessageAction)
  41. ):
  42. return {
  43. 'role': 'user' if action.source == 'user' else 'assistant',
  44. 'content': action_to_str(action),
  45. }
  46. return None
  47. def get_observation_message(obs) -> dict[str, str] | None:
  48. if isinstance(obs, CmdOutputObservation):
  49. content = 'OBSERVATION:\n' + truncate_observation(obs.content)
  50. content += (
  51. f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]'
  52. )
  53. return {'role': 'user', 'content': content}
  54. elif isinstance(obs, IPythonRunCellObservation):
  55. content = 'OBSERVATION:\n' + obs.content
  56. # replace base64 images with a placeholder
  57. splitted = content.split('\n')
  58. for i, line in enumerate(splitted):
  59. if '![image](data:image/png;base64,' in line:
  60. splitted[i] = (
  61. '![image](data:image/png;base64, ...) already displayed to user'
  62. )
  63. content = '\n'.join(splitted)
  64. content = truncate_observation(content)
  65. return {'role': 'user', 'content': content}
  66. return None
  67. def truncate_observation(observation: str, max_chars: int = 10_000) -> str:
  68. """
  69. Truncate the middle of the observation if it is too long.
  70. """
  71. if len(observation) <= max_chars:
  72. return observation
  73. half = max_chars // 2
  74. return (
  75. observation[:half]
  76. + '\n[... Observation truncated due to length ...]\n'
  77. + observation[-half:]
  78. )
  79. def get_system_message() -> str:
  80. return f'{MINIMAL_SYSTEM_PREFIX}\n\n{COMMAND_DOCS}\n\n{SYSTEM_SUFFIX}'
  81. def get_in_context_example() -> str:
  82. return SWE_EXAMPLE
  83. class CodeActSWEAgent(Agent):
  84. VERSION = '1.5'
  85. """
  86. 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.
  87. It is intended use is **solving Github issues**.
  88. It removes web-browsing and Github capability from the original CodeAct agent to avoid confusion to the agent.
  89. """
  90. sandbox_plugins: list[PluginRequirement] = [
  91. # NOTE: AgentSkillsRequirement need to go before JupyterRequirement, since
  92. # AgentSkillsRequirement provides a lot of Python functions
  93. # and it need to be initialized before Jupyter for Jupyter to use those functions.
  94. AgentSkillsRequirement(),
  95. JupyterRequirement(),
  96. ]
  97. runtime_tools: list[RuntimeTool] = []
  98. system_message: str = get_system_message()
  99. 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!"
  100. response_parser = CodeActSWEResponseParser()
  101. def __init__(
  102. self,
  103. llm: LLM,
  104. ) -> None:
  105. """
  106. Initializes a new instance of the CodeActAgent class.
  107. Parameters:
  108. - llm (LLM): The llm to be used by this agent
  109. """
  110. super().__init__(llm)
  111. self.reset()
  112. def reset(self) -> None:
  113. """
  114. Resets the CodeAct Agent.
  115. """
  116. super().reset()
  117. def step(self, state: State) -> Action:
  118. """
  119. Performs one step using the CodeAct Agent.
  120. This includes gathering info on previous steps and prompting the model to make a command to execute.
  121. Parameters:
  122. - state (State): used to get updated info and background commands
  123. Returns:
  124. - CmdRunAction(command) - bash command to run
  125. - IPythonRunCellAction(code) - IPython code to run
  126. - MessageAction(content) - Message action to run (e.g. ask for clarification)
  127. - AgentFinishAction() - end the interaction
  128. """
  129. messages: list[dict[str, str]] = [
  130. {'role': 'system', 'content': self.system_message},
  131. {'role': 'user', 'content': self.in_context_example},
  132. ]
  133. for prev_action, obs in state.history:
  134. action_message = get_action_message(prev_action)
  135. if action_message:
  136. messages.append(action_message)
  137. obs_message = get_observation_message(obs)
  138. if obs_message:
  139. messages.append(obs_message)
  140. latest_user_message = [m for m in messages if m['role'] == 'user'][-1]
  141. if latest_user_message:
  142. if latest_user_message['content'].strip() == '/exit':
  143. return AgentFinishAction()
  144. latest_user_message['content'] += (
  145. f'\n\nENVIRONMENT REMINDER: You have {state.max_iterations - state.iteration} turns left to complete the task.'
  146. )
  147. response = self.llm.completion(
  148. messages=messages,
  149. stop=[
  150. '</execute_ipython>',
  151. '</execute_bash>',
  152. ],
  153. temperature=0.0,
  154. )
  155. state.num_of_chars += sum(
  156. len(message['content']) for message in messages
  157. ) + len(response.choices[0].message.content)
  158. return self.response_parser.parse(response)
  159. def search_memory(self, query: str) -> list[str]:
  160. raise NotImplementedError('Implement this abstract method')