Преглед на файлове

Small refactoring (#1614)

* move MemoryCondenser, LongTermMemory, json, out of the monologue

* PlannerAgent and Microagents use the custom json.loads/dumps

* Move short term history out of monologue agent...

* move memory in their package

* add __init__
Engel Nyst преди 1 година
родител
ревизия
98adbf54ec

+ 4 - 17
agenthub/micro/agent.py

@@ -1,10 +1,11 @@
-import json
+from json import JSONDecodeError
 
 from jinja2 import BaseLoader, Environment
 
 from opendevin.controller.agent import Agent
 from opendevin.controller.state.state import State
 from opendevin.core.exceptions import LLMOutputError
+from opendevin.core.utils import json
 from opendevin.events.action import Action, action_from_dict
 from opendevin.llm.llm import LLM
 
@@ -28,32 +29,18 @@ def parse_response(orig_response: str) -> Action:
                     action_dict = json.loads(response)
                     action = action_from_dict(action_dict)
                     return action
-                except json.JSONDecodeError as e:
+                except JSONDecodeError as e:
                     raise LLMOutputError(
                         'Invalid JSON in response. Please make sure the response is a valid JSON object.'
                     ) from e
     raise LLMOutputError('No valid JSON object found in response.')
 
 
-def my_encoder(obj):
-    """
-    Encodes objects as dictionaries
-
-    Parameters:
-    - obj (Object): An object that will be converted
-
-    Returns:
-    - dict: If the object can be converted it is returned in dict format
-    """
-    if hasattr(obj, 'to_dict'):
-        return obj.to_dict()
-
-
 def to_json(obj, **kwargs):
     """
     Serialize an object to str format
     """
-    return json.dumps(obj, default=my_encoder, **kwargs)
+    return json.dumps(obj, **kwargs)
 
 
 class MicroAgent(Agent):

+ 23 - 15
agenthub/monologue_agent/agent.py

@@ -1,5 +1,4 @@
 import agenthub.monologue_agent.utils.prompts as prompts
-from agenthub.monologue_agent.utils.monologue import Monologue
 from opendevin.controller.agent import Agent
 from opendevin.controller.state.state import State
 from opendevin.core.config import config
@@ -24,9 +23,11 @@ from opendevin.events.observation import (
     Observation,
 )
 from opendevin.llm.llm import LLM
+from opendevin.memory.condenser import MemoryCondenser
+from opendevin.memory.history import ShortTermHistory
 
 if config.agent.memory_enabled:
-    from agenthub.monologue_agent.utils.memory import LongTermMemory
+    from opendevin.memory.memory import LongTermMemory
 
 MAX_TOKEN_COUNT_PADDING = 512
 MAX_OUTPUT_LENGTH = 5000
@@ -85,8 +86,9 @@ class MonologueAgent(Agent):
     """
 
     _initialized = False
-    monologue: Monologue
+    monologue: ShortTermHistory
     memory: 'LongTermMemory | None'
+    memory_condenser: MemoryCondenser
 
     def __init__(self, llm: LLM):
         """
@@ -97,7 +99,7 @@ class MonologueAgent(Agent):
         """
         super().__init__(llm)
 
-    def _add_event(self, event: dict):
+    def _add_event(self, event_dict: dict):
         """
         Adds a new event to the agent's monologue and memory.
         Monologue automatically condenses when it gets too large.
@@ -107,29 +109,33 @@ class MonologueAgent(Agent):
         """
 
         if (
-            'args' in event
-            and 'output' in event['args']
-            and len(event['args']['output']) > MAX_OUTPUT_LENGTH
+            'args' in event_dict
+            and 'output' in event_dict['args']
+            and len(event_dict['args']['output']) > MAX_OUTPUT_LENGTH
         ):
-            event['args']['output'] = (
-                event['args']['output'][:MAX_OUTPUT_LENGTH] + '...'
+            event_dict['args']['output'] = (
+                event_dict['args']['output'][:MAX_OUTPUT_LENGTH] + '...'
             )
 
-        self.monologue.add_event(event)
+        self.monologue.add_event(event_dict)
         if self.memory is not None:
-            self.memory.add_event(event)
+            self.memory.add_event(event_dict)
 
         # Test monologue token length
         prompt = prompts.get_request_action_prompt(
             '',
-            self.monologue.get_thoughts(),
+            self.monologue.get_events(),
             [],
         )
         messages = [{'content': prompt, 'role': 'user'}]
         token_count = self.llm.get_token_count(messages)
 
         if token_count + MAX_TOKEN_COUNT_PADDING > self.llm.max_input_tokens:
-            self.monologue.condense(self.llm)
+            prompt = prompts.get_summarize_monologue_prompt(self.monologue.events)
+            summary_response = self.memory_condenser.condense(
+                summarize_prompt=prompt, llm=self.llm
+            )
+            self.monologue.events = prompts.parse_summary_response(summary_response)
 
     def _initialize(self, task: str):
         """
@@ -151,12 +157,14 @@ class MonologueAgent(Agent):
         if task is None or task == '':
             raise AgentNoInstructionError()
 
-        self.monologue = Monologue()
+        self.monologue = ShortTermHistory()
         if config.agent.memory_enabled:
             self.memory = LongTermMemory()
         else:
             self.memory = None
 
+        self.memory_condenser = MemoryCondenser()
+
         self._add_initial_thoughts(task)
         self._initialized = True
 
@@ -226,7 +234,7 @@ class MonologueAgent(Agent):
 
         prompt = prompts.get_request_action_prompt(
             state.plan.main_goal,
-            self.monologue.get_thoughts(),
+            self.monologue.get_events(),
             state.background_commands_obs,
         )
         messages = [{'content': prompt, 'role': 'user'}]

+ 0 - 79
agenthub/monologue_agent/utils/monologue.py

@@ -1,79 +0,0 @@
-import agenthub.monologue_agent.utils.json as json
-import agenthub.monologue_agent.utils.prompts as prompts
-from opendevin.core.exceptions import AgentEventTypeError
-from opendevin.core.logger import opendevin_logger as logger
-from opendevin.llm.llm import LLM
-
-
-class Monologue:
-    """
-    The monologue is a representation for the agent's internal monologue where it can think.
-    The agent has the capability of using this monologue for whatever it wants.
-    """
-
-    def __init__(self):
-        """
-        Initialize the empty list of thoughts
-        """
-        self.thoughts = []
-
-    def add_event(self, t: dict):
-        """
-        Adds an event to memory if it is a valid event.
-
-        Parameters:
-        - t (dict): The thought that we want to add to memory
-
-        Raises:
-        - AgentEventTypeError: If t is not a dict
-        """
-        if not isinstance(t, dict):
-            raise AgentEventTypeError()
-        self.thoughts.append(t)
-
-    def get_thoughts(self):
-        """
-        Get the current thoughts of the agent.
-
-        Returns:
-        - list: The list of thoughts that the agent has.
-        """
-        return self.thoughts
-
-    def get_total_length(self):
-        """
-        Gives the total number of characters in all thoughts
-
-        Returns:
-        - Int: Total number of chars in thoughts.
-        """
-        total_length = 0
-        for t in self.thoughts:
-            try:
-                total_length += len(json.dumps(t))
-            except TypeError as e:
-                logger.error('Error serializing thought: %s', str(e), exc_info=False)
-        return total_length
-
-    def condense(self, llm: LLM):
-        """
-        Attempts to condense the monologue by using the llm
-
-        Parameters:
-        - llm (LLM): llm to be used for summarization
-
-        Raises:
-        - Exception: the same exception as it got from the llm or processing the response
-        """
-
-        try:
-            prompt = prompts.get_summarize_monologue_prompt(self.thoughts)
-            messages = [{'content': prompt, 'role': 'user'}]
-            resp = llm.completion(messages=messages)
-            summary_resp = resp['choices'][0]['message']['content']
-            self.thoughts = prompts.parse_summary_response(summary_resp)
-        except Exception as e:
-            logger.error('Error condensing thoughts: %s', str(e), exc_info=False)
-
-            # TODO If the llm fails with ContextWindowExceededError, we can try to condense the monologue chunk by chunk
-            raise

+ 1 - 2
agenthub/monologue_agent/utils/prompts.py

@@ -3,6 +3,7 @@ from json import JSONDecodeError
 
 from opendevin.core.config import config
 from opendevin.core.exceptions import LLMOutputError
+from opendevin.core.utils import json
 from opendevin.events.action import (
     Action,
     action_from_dict,
@@ -11,8 +12,6 @@ from opendevin.events.observation import (
     CmdOutputObservation,
 )
 
-from . import json
-
 ACTION_PROMPT = """
 You're a thoughtful robot. Your main task is this:
 %(task)s

+ 1 - 5
agenthub/planner_agent/prompt.py

@@ -1,8 +1,7 @@
-import json
-
 from opendevin.controller.state.plan import Plan
 from opendevin.core.logger import opendevin_logger as logger
 from opendevin.core.schema import ActionType
+from opendevin.core.utils import json
 from opendevin.events.action import (
     Action,
     NullAction,
@@ -176,9 +175,6 @@ def parse_response(response: str) -> Action:
     Returns:
     - Action: A valid next action to perform from model output
     """
-    json_start = response.find('{')
-    json_end = response.rfind('}') + 1
-    response = response[json_start:json_end]
     action_dict = json.loads(response)
     if 'contents' in action_dict:
         # The LLM gets confused here. Might as well be robust

+ 0 - 0
agenthub/monologue_agent/utils/json.py → opendevin/core/utils/json.py


+ 5 - 0
opendevin/memory/__init__.py

@@ -0,0 +1,5 @@
+from .condenser import MemoryCondenser
+from .history import ShortTermHistory
+from .memory import LongTermMemory
+
+__all__ = ['LongTermMemory', 'ShortTermHistory', 'MemoryCondenser']

+ 26 - 0
opendevin/memory/condenser.py

@@ -0,0 +1,26 @@
+from opendevin.core.logger import opendevin_logger as logger
+from opendevin.llm.llm import LLM
+
+
+class MemoryCondenser:
+    def condense(self, summarize_prompt: str, llm: LLM):
+        """
+        Attempts to condense the monologue by using the llm
+
+        Parameters:
+        - llm (LLM): llm to be used for summarization
+
+        Raises:
+        - Exception: the same exception as it got from the llm or processing the response
+        """
+
+        try:
+            messages = [{'content': summarize_prompt, 'role': 'user'}]
+            resp = llm.completion(messages=messages)
+            summary_response = resp['choices'][0]['message']['content']
+            return summary_response
+        except Exception as e:
+            logger.error('Error condensing thoughts: %s', str(e), exc_info=False)
+
+            # TODO If the llm fails with ContextWindowExceededError, we can try to condense the monologue chunk by chunk
+            raise

+ 54 - 0
opendevin/memory/history.py

@@ -0,0 +1,54 @@
+import opendevin.core.utils.json as json
+from opendevin.core.exceptions import AgentEventTypeError
+from opendevin.core.logger import opendevin_logger as logger
+
+
+class ShortTermHistory:
+    """
+    The short term history is the most recent series of events.
+    An agent can send this in the prompt or use it for other purpose.
+    """
+
+    def __init__(self):
+        """
+        Initialize the empty list of events
+        """
+        self.events = []
+
+    def add_event(self, event_dict: dict):
+        """
+        Adds an event to memory if it is a valid event.
+
+        Parameters:
+        - event_dict (dict): The event that we want to add to memory
+
+        Raises:
+        - AgentEventTypeError: If event_dict is not a dict
+        """
+        if not isinstance(event_dict, dict):
+            raise AgentEventTypeError()
+        self.events.append(event_dict)
+
+    def get_events(self):
+        """
+        Get the events in the agent's recent history.
+
+        Returns:
+        - List: The list of events that the agent remembers easily.
+        """
+        return self.events
+
+    def get_total_length(self):
+        """
+        Gives the total number of characters in all history
+
+        Returns:
+        - Int: Total number of characters of the recent history.
+        """
+        total_length = 0
+        for t in self.events:
+            try:
+                total_length += len(json.dumps(t))
+            except TypeError as e:
+                logger.error('Error serializing event: %s', str(e), exc_info=False)
+        return total_length

+ 1 - 2
agenthub/monologue_agent/utils/memory.py → opendevin/memory/memory.py

@@ -15,8 +15,7 @@ from tenacity import (
 
 from opendevin.core.config import config
 from opendevin.core.logger import opendevin_logger as logger
-
-from . import json
+from opendevin.core.utils import json
 
 num_retries = config.llm.num_retries
 retry_min_wait = config.llm.retry_min_wait