codeact_agent.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import re
  2. from typing import List, Mapping
  3. from opendevin.action import (
  4. Action,
  5. AgentEchoAction,
  6. AgentFinishAction,
  7. CmdRunAction,
  8. )
  9. from opendevin.agent import Agent
  10. from opendevin.llm.llm import LLM
  11. from opendevin.observation import (
  12. AgentMessageObservation,
  13. CmdOutputObservation,
  14. )
  15. from opendevin.parse_commands import parse_command_file
  16. from opendevin.state import State
  17. COMMAND_DOCS = parse_command_file()
  18. COMMAND_SEGMENT = (
  19. f"""
  20. Apart from the standard bash commands, you can also use the following special commands:
  21. {COMMAND_DOCS}
  22. """
  23. if COMMAND_DOCS is not None
  24. else ''
  25. )
  26. SYSTEM_MESSAGE = f"""You are a helpful assistant. You will be provided access (as root) to a bash shell to complete user-provided tasks.
  27. You will be able to execute commands in the bash shell, interact with the file system, install packages, and receive the output of your commands.
  28. DO NOT provide code in ```triple backticks```. Instead, you should execute bash command on behalf of the user by wrapping them with <execute> and </execute>.
  29. For example:
  30. You can list the files in the current directory by executing the following command:
  31. <execute>ls</execute>
  32. You can also install packages using pip:
  33. <execute> pip install numpy </execute>
  34. You can also write a block of code to a file:
  35. <execute>
  36. echo "import math
  37. print(math.pi)" > math.py
  38. </execute>
  39. {COMMAND_SEGMENT}
  40. When you are done, execute the following to close the shell and end the conversation:
  41. <execute>exit</execute>
  42. """
  43. INVALID_INPUT_MESSAGE = (
  44. "I don't understand your input. \n"
  45. 'If you want to execute command, please use <execute> YOUR_COMMAND_HERE </execute>.\n'
  46. 'If you already completed the task, please exit the shell by generating: <execute> exit </execute>.'
  47. )
  48. def parse_response(response) -> str:
  49. action = response.choices[0].message.content
  50. if '<execute>' in action and '</execute>' not in action:
  51. action += '</execute>'
  52. return action
  53. class CodeActAgent(Agent):
  54. """
  55. The Code Act Agent is a minimalist agent.
  56. The agent works by passing the model a list of action-observation pairs and prompting the model to take the next step.
  57. """
  58. def __init__(
  59. self,
  60. llm: LLM,
  61. ) -> None:
  62. """
  63. Initializes a new instance of the CodeActAgent class.
  64. Parameters:
  65. - llm (LLM): The llm to be used by this agent
  66. """
  67. super().__init__(llm)
  68. self.messages: List[Mapping[str, str]] = []
  69. def step(self, state: State) -> Action:
  70. """
  71. Performs one step using the Code Act Agent.
  72. This includes gathering info on previous steps and prompting the model to make a command to execute.
  73. Parameters:
  74. - state (State): used to get updated info and background commands
  75. Returns:
  76. - CmdRunAction(command) - command action to run
  77. - AgentEchoAction(content=INVALID_INPUT_MESSAGE) - invalid command output
  78. Raises:
  79. - NotImplementedError - for actions other than CmdOutputObservation or AgentMessageObservation
  80. """
  81. if len(self.messages) == 0:
  82. assert state.plan.main_goal, 'Expecting instruction to be set'
  83. self.messages = [
  84. {'role': 'system', 'content': SYSTEM_MESSAGE},
  85. {'role': 'user', 'content': state.plan.main_goal},
  86. ]
  87. updated_info = state.updated_info
  88. if updated_info:
  89. for prev_action, obs in updated_info:
  90. assert isinstance(
  91. prev_action, (CmdRunAction, AgentEchoAction)
  92. ), 'Expecting CmdRunAction or AgentEchoAction for Action'
  93. if isinstance(
  94. obs, AgentMessageObservation
  95. ): # warning message from itself
  96. self.messages.append(
  97. {'role': 'user', 'content': obs.content})
  98. elif isinstance(obs, CmdOutputObservation):
  99. content = 'OBSERVATION:\n' + obs.content
  100. content += f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]]'
  101. self.messages.append({'role': 'user', 'content': content})
  102. else:
  103. raise NotImplementedError(
  104. f'Unknown observation type: {obs.__class__}'
  105. )
  106. response = self.llm.completion(
  107. messages=self.messages,
  108. stop=['</execute>'],
  109. temperature=0.0
  110. )
  111. action_str: str = parse_response(response)
  112. state.num_of_chars += sum(len(message['content'])
  113. for message in self.messages) + len(action_str)
  114. self.messages.append({'role': 'assistant', 'content': action_str})
  115. command = re.search(r'<execute>(.*)</execute>', action_str, re.DOTALL)
  116. if command is not None:
  117. # a command was found
  118. command_group = command.group(1)
  119. if command_group.strip() == 'exit':
  120. return AgentFinishAction()
  121. return CmdRunAction(command=command_group)
  122. # # execute the code
  123. # # TODO: does exit_code get loaded into Message?
  124. # exit_code, observation = self.env.execute(command_group)
  125. # self._history.append(Message(Role.ASSISTANT, observation))
  126. else:
  127. # we could provide a error message for the model to continue similar to
  128. # https://github.com/xingyaoww/mint-bench/blob/main/mint/envs/general_env.py#L18-L23
  129. # observation = INVALID_INPUT_MESSAGE
  130. # self._history.append(Message(Role.ASSISTANT, observation))
  131. return AgentEchoAction(
  132. content=INVALID_INPUT_MESSAGE
  133. ) # warning message to itself
  134. def search_memory(self, query: str) -> List[str]:
  135. raise NotImplementedError('Implement this abstract method')