|
|
@@ -1,8 +1,28 @@
|
|
|
-from typing import List, Any
|
|
|
+from typing import List, Dict, Type
|
|
|
|
|
|
+import agenthub.langchains_agent.utils.llm as llm
|
|
|
from opendevin.agent import Agent
|
|
|
-from agenthub.langchains_agent.utils.agent import Agent as LangchainsAgentImpl
|
|
|
-from opendevin.lib.event import Event
|
|
|
+from opendevin.action import (
|
|
|
+ Action,
|
|
|
+ CmdRunAction,
|
|
|
+ CmdKillAction,
|
|
|
+ BrowseURLAction,
|
|
|
+ FileReadAction,
|
|
|
+ FileWriteAction,
|
|
|
+ AgentRecallAction,
|
|
|
+ AgentThinkAction,
|
|
|
+ AgentFinishAction,
|
|
|
+)
|
|
|
+from opendevin.observation import (
|
|
|
+ Observation,
|
|
|
+ CmdOutputObservation,
|
|
|
+ BrowserOutputObservation,
|
|
|
+)
|
|
|
+from opendevin.state import State
|
|
|
+
|
|
|
+from agenthub.langchains_agent.utils.monologue import Monologue
|
|
|
+from agenthub.langchains_agent.utils.memory import LongTermMemory
|
|
|
+
|
|
|
|
|
|
INITIAL_THOUGHTS = [
|
|
|
"I exist!",
|
|
|
@@ -43,59 +63,135 @@ INITIAL_THOUGHTS = [
|
|
|
]
|
|
|
|
|
|
|
|
|
+MAX_OUTPUT_LENGTH = 5000
|
|
|
+MAX_MONOLOGUE_LENGTH = 20000
|
|
|
+
|
|
|
+
|
|
|
+ACTION_TYPE_TO_CLASS: Dict[str, Type[Action]] = {
|
|
|
+ "run": CmdRunAction,
|
|
|
+ "kill": CmdKillAction,
|
|
|
+ "browse": BrowseURLAction,
|
|
|
+ "read": FileReadAction,
|
|
|
+ "write": FileWriteAction,
|
|
|
+ "recall": AgentRecallAction,
|
|
|
+ "think": AgentThinkAction,
|
|
|
+ "finish": AgentFinishAction,
|
|
|
+}
|
|
|
+
|
|
|
+CLASS_TO_ACTION_TYPE: Dict[Type[Action], str] = {v: k for k, v in ACTION_TYPE_TO_CLASS.items()}
|
|
|
+
|
|
|
class LangchainsAgent(Agent):
|
|
|
_initialized = False
|
|
|
- agent: Any = None
|
|
|
+
|
|
|
+ def __init__(self, model_name: str):
|
|
|
+ super().__init__(model_name)
|
|
|
+ self.monologue = Monologue(self.model_name)
|
|
|
+ self.memory = LongTermMemory()
|
|
|
+
|
|
|
+ def _add_event(self, event: dict):
|
|
|
+ if 'output' in event['args'] and len(event['args']['output']) > MAX_OUTPUT_LENGTH:
|
|
|
+ event['args']['output'] = event['args']['output'][:MAX_OUTPUT_LENGTH] + "..."
|
|
|
+
|
|
|
+ self.monologue.add_event(event)
|
|
|
+ self.memory.add_event(event)
|
|
|
+ if self.monologue.get_total_length() > MAX_MONOLOGUE_LENGTH:
|
|
|
+ self.monologue.condense()
|
|
|
|
|
|
def _initialize(self):
|
|
|
if self._initialized:
|
|
|
return
|
|
|
+
|
|
|
if self.instruction is None or self.instruction == "":
|
|
|
raise ValueError("Instruction must be provided")
|
|
|
- self.agent = LangchainsAgentImpl(self.instruction, self.model_name)
|
|
|
+
|
|
|
next_is_output = False
|
|
|
for thought in INITIAL_THOUGHTS:
|
|
|
thought = thought.replace("$TASK", self.instruction)
|
|
|
if next_is_output:
|
|
|
- event = Event("output", {"output": thought})
|
|
|
+ d = {"action": "output", "args": {"output": thought}}
|
|
|
next_is_output = False
|
|
|
else:
|
|
|
if thought.startswith("RUN"):
|
|
|
command = thought.split("RUN ")[1]
|
|
|
- event = Event("run", {"command": command})
|
|
|
+ d = {"action": "run", "args": {"command": command}}
|
|
|
next_is_output = True
|
|
|
+
|
|
|
elif thought.startswith("RECALL"):
|
|
|
query = thought.split("RECALL ")[1]
|
|
|
- event = Event("recall", {"query": query})
|
|
|
+ d = {"action": "recall", "args": {"query": query}}
|
|
|
next_is_output = True
|
|
|
+
|
|
|
elif thought.startswith("BROWSE"):
|
|
|
url = thought.split("BROWSE ")[1]
|
|
|
- event = Event("browse", {"url": url})
|
|
|
+ d = {"action": "browse", "args": {"url": url}}
|
|
|
next_is_output = True
|
|
|
else:
|
|
|
- event = Event("think", {"thought": thought})
|
|
|
- self.agent.add_event(event)
|
|
|
- self._initialized = True
|
|
|
+ d = {"action": "think", "args": {"thought": thought}}
|
|
|
|
|
|
- def add_event(self, event: Event) -> None:
|
|
|
- if self.agent:
|
|
|
- self.agent.add_event(event)
|
|
|
+ self._add_event(d)
|
|
|
+ self._initialized = True
|
|
|
|
|
|
- def step(self, cmd_mgr) -> Event:
|
|
|
+ def step(self, state: State) -> Action:
|
|
|
self._initialize()
|
|
|
- return self.agent.get_next_action(cmd_mgr)
|
|
|
+ # TODO: make langchains agent use Action & Observation
|
|
|
+ # completly from ground up
|
|
|
|
|
|
- def search_memory(self, query: str) -> List[str]:
|
|
|
- return self.agent.memory.search(query)
|
|
|
+ # Translate state to action_dict
|
|
|
+ for prev_action, obs in state.updated_info:
|
|
|
+ if isinstance(obs, CmdOutputObservation):
|
|
|
+ if obs.error:
|
|
|
+ d = {"action": "error", "args": {"output": obs.content}}
|
|
|
+ else:
|
|
|
+ d = {"action": "output", "args": {"output": obs.content}}
|
|
|
+ # elif isinstance(obs, UserMessageObservation):
|
|
|
+ # d = {"action": "output", "args": {"output": obs.message}}
|
|
|
+ # elif isinstance(obs, AgentMessageObservation):
|
|
|
+ # d = {"action": "output", "args": {"output": obs.message}}
|
|
|
+ elif isinstance(obs, (BrowserOutputObservation, Observation)):
|
|
|
+ d = {"action": "output", "args": {"output": obs.content}}
|
|
|
+ else:
|
|
|
+ raise NotImplementedError(f"Unknown observation type: {obs}")
|
|
|
+ self._add_event(d)
|
|
|
|
|
|
- def chat(self, message: str) -> None:
|
|
|
- """
|
|
|
- Optional method for interactive communication with the agent during its execution. Implementations
|
|
|
- can use this method to modify the agent's behavior or state based on chat inputs.
|
|
|
|
|
|
- Parameters:
|
|
|
- - message (str): The chat message or command.
|
|
|
- """
|
|
|
- raise NotImplementedError
|
|
|
+ if isinstance(prev_action, CmdRunAction):
|
|
|
+ d = {"action": "run", "args": {"command": prev_action.command}}
|
|
|
+ elif isinstance(prev_action, CmdKillAction):
|
|
|
+ d = {"action": "kill", "args": {"id": prev_action.id}}
|
|
|
+ elif isinstance(prev_action, BrowseURLAction):
|
|
|
+ d = {"action": "browse", "args": {"url": prev_action.url}}
|
|
|
+ elif isinstance(prev_action, FileReadAction):
|
|
|
+ d = {"action": "read", "args": {"file": prev_action.path}}
|
|
|
+ elif isinstance(prev_action, FileWriteAction):
|
|
|
+ d = {"action": "write", "args": {"file": prev_action.path, "content": prev_action.contents}}
|
|
|
+ elif isinstance(prev_action, AgentRecallAction):
|
|
|
+ d = {"action": "recall", "args": {"query": prev_action.query}}
|
|
|
+ elif isinstance(prev_action, AgentThinkAction):
|
|
|
+ d = {"action": "think", "args": {"thought": prev_action.thought}}
|
|
|
+ elif isinstance(prev_action, AgentFinishAction):
|
|
|
+ d = {"action": "finish"}
|
|
|
+ else:
|
|
|
+ raise NotImplementedError(f"Unknown action type: {prev_action}")
|
|
|
+ self._add_event(d)
|
|
|
+
|
|
|
+ state.updated_info = []
|
|
|
+
|
|
|
+ action_dict = llm.request_action(
|
|
|
+ self.instruction,
|
|
|
+ self.monologue.get_thoughts(),
|
|
|
+ self.model_name,
|
|
|
+ state.background_commands_obs,
|
|
|
+ )
|
|
|
+ if action_dict is None:
|
|
|
+ action_dict = {"action": "think", "args": {"thought": "..."}}
|
|
|
+
|
|
|
+ # Translate action_dict to Action
|
|
|
+ action = ACTION_TYPE_TO_CLASS[action_dict["action"]](**action_dict["args"])
|
|
|
+ self.latest_action = action
|
|
|
+ return action
|
|
|
+
|
|
|
+ def search_memory(self, query: str) -> List[str]:
|
|
|
+ return self.memory.search(query)
|
|
|
+
|
|
|
|
|
|
Agent.register("LangchainsAgent", LangchainsAgent)
|