langchains_agent.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. from typing import List
  2. from opendevin.agent import Agent
  3. from opendevin.state import State
  4. from opendevin.action import Action
  5. from opendevin.llm.llm import LLM
  6. import agenthub.langchains_agent.utils.prompts as prompts
  7. from agenthub.langchains_agent.utils.monologue import Monologue
  8. from agenthub.langchains_agent.utils.memory import LongTermMemory
  9. from opendevin.action import (
  10. NullAction,
  11. CmdRunAction,
  12. CmdKillAction,
  13. BrowseURLAction,
  14. FileReadAction,
  15. FileWriteAction,
  16. AgentRecallAction,
  17. AgentThinkAction,
  18. AgentFinishAction,
  19. )
  20. from opendevin.observation import (
  21. CmdOutputObservation,
  22. )
  23. MAX_MONOLOGUE_LENGTH = 20000
  24. MAX_OUTPUT_LENGTH = 5000
  25. INITIAL_THOUGHTS = [
  26. "I exist!",
  27. "Hmm...looks like I can type in a command line prompt",
  28. "Looks like I have a web browser too!",
  29. "Here's what I want to do: $TASK",
  30. "How am I going to get there though?",
  31. "It seems like I have some kind of short term memory.",
  32. "Each of my thoughts seems to be stored in a numbered list.",
  33. "It seems whatever I say next will be added to the list.",
  34. "But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process.",
  35. "Fortunately I have long term memory!",
  36. "I can just say RECALL, followed by the thing I want to remember. And then related thoughts just spill out!",
  37. "Sometimes they're random thoughts that don't really have to do with what I wanted to remember. But usually they're exactly what I need!",
  38. "Let's try it out!",
  39. "RECALL what it is I want to do",
  40. "Here's what I want to do: $TASK",
  41. "How am I going to get there though?",
  42. "Neat! And it looks like it's easy for me to use the command line too! I just have to say RUN followed by the command I want to run. The command output just jumps into my head!",
  43. 'RUN echo "hello world"',
  44. "hello world",
  45. "Cool! I bet I can read and edit files too.",
  46. "RUN echo \"console.log('hello world')\" > test.js",
  47. "",
  48. "I just created test.js. I'll try and run it now.",
  49. "RUN node test.js",
  50. "hello world",
  51. "it works!",
  52. "And if I want to use the browser, I just need to say BROWSE, followed by a website I want to visit, or an action I want to take on the current site",
  53. "Let's try that...",
  54. "BROWSE google.com",
  55. '<form><input type="text"></input><button type="submit"></button></form>',
  56. "Very cool. Now to accomplish my task.",
  57. "I'll need a strategy. And as I make progress, I'll need to keep refining that strategy. I'll need to set goals, and break them into sub-goals.",
  58. "In between actions, I must always take some time to think, strategize, and set new goals. I should never take two actions in a row.",
  59. "OK so my task is to $TASK. I haven't made any progress yet. Where should I start?",
  60. "It seems like there might be an existing project here. I should probably start by running `ls` to see what's here.",
  61. ]
  62. class LangchainsAgent(Agent):
  63. _initialized = False
  64. def __init__(self, llm: LLM):
  65. super().__init__(llm)
  66. self.monologue = Monologue()
  67. self.memory = LongTermMemory()
  68. def _add_event(self, event: dict):
  69. if 'output' in event['args'] and len(event['args']['output']) > MAX_OUTPUT_LENGTH:
  70. event['args']['output'] = event['args']['output'][:MAX_OUTPUT_LENGTH] + "..."
  71. self.monologue.add_event(event)
  72. self.memory.add_event(event)
  73. if self.monologue.get_total_length() > MAX_MONOLOGUE_LENGTH:
  74. self.monologue.condense(self.llm)
  75. def _initialize(self, task):
  76. if self._initialized:
  77. return
  78. if task is None or task == "":
  79. raise ValueError("Instruction must be provided")
  80. self.monologue = Monologue()
  81. self.memory = LongTermMemory()
  82. next_is_output = False
  83. for thought in INITIAL_THOUGHTS:
  84. thought = thought.replace("$TASK", task)
  85. if next_is_output:
  86. d = {"action": "output", "args": {"output": thought}}
  87. next_is_output = False
  88. else:
  89. if thought.startswith("RUN"):
  90. command = thought.split("RUN ")[1]
  91. d = {"action": "run", "args": {"command": command}}
  92. next_is_output = True
  93. elif thought.startswith("RECALL"):
  94. query = thought.split("RECALL ")[1]
  95. d = {"action": "recall", "args": {"query": query}}
  96. next_is_output = True
  97. elif thought.startswith("BROWSE"):
  98. url = thought.split("BROWSE ")[1]
  99. d = {"action": "browse", "args": {"url": url}}
  100. next_is_output = True
  101. else:
  102. d = {"action": "think", "args": {"thought": thought}}
  103. self._add_event(d)
  104. self._initialized = True
  105. def step(self, state: State) -> Action:
  106. self._initialize(state.plan.main_goal)
  107. # TODO: make langchains agent use Action & Observation
  108. # completly from ground up
  109. # Translate state to action_dict
  110. for prev_action, obs in state.updated_info:
  111. d = None
  112. if isinstance(obs, CmdOutputObservation):
  113. if obs.error:
  114. d = {"action": "error", "args": {"output": obs.content}}
  115. else:
  116. d = {"action": "output", "args": {"output": obs.content}}
  117. else:
  118. d = {"action": "output", "args": {"output": obs.content}}
  119. if d is not None:
  120. self._add_event(d)
  121. d = None
  122. if isinstance(prev_action, CmdRunAction):
  123. d = {"action": "run", "args": {"command": prev_action.command}}
  124. elif isinstance(prev_action, CmdKillAction):
  125. d = {"action": "kill", "args": {"id": prev_action.id}}
  126. elif isinstance(prev_action, BrowseURLAction):
  127. d = {"action": "browse", "args": {"url": prev_action.url}}
  128. elif isinstance(prev_action, FileReadAction):
  129. d = {"action": "read", "args": {"file": prev_action.path}}
  130. elif isinstance(prev_action, FileWriteAction):
  131. d = {"action": "write", "args": {"file": prev_action.path, "content": prev_action.contents}}
  132. elif isinstance(prev_action, AgentRecallAction):
  133. d = {"action": "recall", "args": {"query": prev_action.query}}
  134. elif isinstance(prev_action, AgentThinkAction):
  135. d = {"action": "think", "args": {"thought": prev_action.thought}}
  136. elif isinstance(prev_action, AgentFinishAction):
  137. d = {"action": "finish"}
  138. elif isinstance(prev_action, NullAction):
  139. d = None
  140. else:
  141. raise ValueError(f"Unknown action type: {prev_action}")
  142. if d is not None:
  143. self._add_event(d)
  144. state.updated_info = []
  145. prompt = prompts.get_request_action_prompt(
  146. state.plan.main_goal,
  147. self.monologue.get_thoughts(),
  148. state.background_commands_obs,
  149. )
  150. messages = [{"content": prompt,"role": "user"}]
  151. resp = self.llm.completion(messages=messages)
  152. action_resp = resp['choices'][0]['message']['content']
  153. action = prompts.parse_action_response(action_resp)
  154. self.latest_action = action
  155. return action
  156. def search_memory(self, query: str) -> List[str]:
  157. return self.memory.search(query)