Quellcode durchsuchen

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 vor 1 Jahr
Ursprung
Commit
98adbf54ec

+ 4 - 17
agenthub/micro/agent.py

@@ -1,10 +1,11 @@
-import json
+from json import JSONDecodeError
 
 
 from jinja2 import BaseLoader, Environment
 from jinja2 import BaseLoader, Environment
 
 
 from opendevin.controller.agent import Agent
 from opendevin.controller.agent import Agent
 from opendevin.controller.state.state import State
 from opendevin.controller.state.state import State
 from opendevin.core.exceptions import LLMOutputError
 from opendevin.core.exceptions import LLMOutputError
+from opendevin.core.utils import json
 from opendevin.events.action import Action, action_from_dict
 from opendevin.events.action import Action, action_from_dict
 from opendevin.llm.llm import LLM
 from opendevin.llm.llm import LLM
 
 
@@ -28,32 +29,18 @@ def parse_response(orig_response: str) -> Action:
                     action_dict = json.loads(response)
                     action_dict = json.loads(response)
                     action = action_from_dict(action_dict)
                     action = action_from_dict(action_dict)
                     return action
                     return action
-                except json.JSONDecodeError as e:
+                except JSONDecodeError as e:
                     raise LLMOutputError(
                     raise LLMOutputError(
                         'Invalid JSON in response. Please make sure the response is a valid JSON object.'
                         'Invalid JSON in response. Please make sure the response is a valid JSON object.'
                     ) from e
                     ) from e
     raise LLMOutputError('No valid JSON object found in response.')
     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):
 def to_json(obj, **kwargs):
     """
     """
     Serialize an object to str format
     Serialize an object to str format
     """
     """
-    return json.dumps(obj, default=my_encoder, **kwargs)
+    return json.dumps(obj, **kwargs)
 
 
 
 
 class MicroAgent(Agent):
 class MicroAgent(Agent):

+ 23 - 15
agenthub/monologue_agent/agent.py

@@ -1,5 +1,4 @@
 import agenthub.monologue_agent.utils.prompts as prompts
 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.agent import Agent
 from opendevin.controller.state.state import State
 from opendevin.controller.state.state import State
 from opendevin.core.config import config
 from opendevin.core.config import config
@@ -24,9 +23,11 @@ from opendevin.events.observation import (
     Observation,
     Observation,
 )
 )
 from opendevin.llm.llm import LLM
 from opendevin.llm.llm import LLM
+from opendevin.memory.condenser import MemoryCondenser
+from opendevin.memory.history import ShortTermHistory
 
 
 if config.agent.memory_enabled:
 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_TOKEN_COUNT_PADDING = 512
 MAX_OUTPUT_LENGTH = 5000
 MAX_OUTPUT_LENGTH = 5000
@@ -85,8 +86,9 @@ class MonologueAgent(Agent):
     """
     """
 
 
     _initialized = False
     _initialized = False
-    monologue: Monologue
+    monologue: ShortTermHistory
     memory: 'LongTermMemory | None'
     memory: 'LongTermMemory | None'
+    memory_condenser: MemoryCondenser
 
 
     def __init__(self, llm: LLM):
     def __init__(self, llm: LLM):
         """
         """
@@ -97,7 +99,7 @@ class MonologueAgent(Agent):
         """
         """
         super().__init__(llm)
         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.
         Adds a new event to the agent's monologue and memory.
         Monologue automatically condenses when it gets too large.
         Monologue automatically condenses when it gets too large.
@@ -107,29 +109,33 @@ class MonologueAgent(Agent):
         """
         """
 
 
         if (
         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:
         if self.memory is not None:
-            self.memory.add_event(event)
+            self.memory.add_event(event_dict)
 
 
         # Test monologue token length
         # Test monologue token length
         prompt = prompts.get_request_action_prompt(
         prompt = prompts.get_request_action_prompt(
             '',
             '',
-            self.monologue.get_thoughts(),
+            self.monologue.get_events(),
             [],
             [],
         )
         )
         messages = [{'content': prompt, 'role': 'user'}]
         messages = [{'content': prompt, 'role': 'user'}]
         token_count = self.llm.get_token_count(messages)
         token_count = self.llm.get_token_count(messages)
 
 
         if token_count + MAX_TOKEN_COUNT_PADDING > self.llm.max_input_tokens:
         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):
     def _initialize(self, task: str):
         """
         """
@@ -151,12 +157,14 @@ class MonologueAgent(Agent):
         if task is None or task == '':
         if task is None or task == '':
             raise AgentNoInstructionError()
             raise AgentNoInstructionError()
 
 
-        self.monologue = Monologue()
+        self.monologue = ShortTermHistory()
         if config.agent.memory_enabled:
         if config.agent.memory_enabled:
             self.memory = LongTermMemory()
             self.memory = LongTermMemory()
         else:
         else:
             self.memory = None
             self.memory = None
 
 
+        self.memory_condenser = MemoryCondenser()
+
         self._add_initial_thoughts(task)
         self._add_initial_thoughts(task)
         self._initialized = True
         self._initialized = True
 
 
@@ -226,7 +234,7 @@ class MonologueAgent(Agent):
 
 
         prompt = prompts.get_request_action_prompt(
         prompt = prompts.get_request_action_prompt(
             state.plan.main_goal,
             state.plan.main_goal,
-            self.monologue.get_thoughts(),
+            self.monologue.get_events(),
             state.background_commands_obs,
             state.background_commands_obs,
         )
         )
         messages = [{'content': prompt, 'role': 'user'}]
         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.config import config
 from opendevin.core.exceptions import LLMOutputError
 from opendevin.core.exceptions import LLMOutputError
+from opendevin.core.utils import json
 from opendevin.events.action import (
 from opendevin.events.action import (
     Action,
     Action,
     action_from_dict,
     action_from_dict,
@@ -11,8 +12,6 @@ from opendevin.events.observation import (
     CmdOutputObservation,
     CmdOutputObservation,
 )
 )
 
 
-from . import json
-
 ACTION_PROMPT = """
 ACTION_PROMPT = """
 You're a thoughtful robot. Your main task is this:
 You're a thoughtful robot. Your main task is this:
 %(task)s
 %(task)s

+ 1 - 5
agenthub/planner_agent/prompt.py

@@ -1,8 +1,7 @@
-import json
-
 from opendevin.controller.state.plan import Plan
 from opendevin.controller.state.plan import Plan
 from opendevin.core.logger import opendevin_logger as logger
 from opendevin.core.logger import opendevin_logger as logger
 from opendevin.core.schema import ActionType
 from opendevin.core.schema import ActionType
+from opendevin.core.utils import json
 from opendevin.events.action import (
 from opendevin.events.action import (
     Action,
     Action,
     NullAction,
     NullAction,
@@ -176,9 +175,6 @@ def parse_response(response: str) -> Action:
     Returns:
     Returns:
     - Action: A valid next action to perform from model output
     - 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)
     action_dict = json.loads(response)
     if 'contents' in action_dict:
     if 'contents' in action_dict:
         # The LLM gets confused here. Might as well be robust
         # 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.config import config
 from opendevin.core.logger import opendevin_logger as logger
 from opendevin.core.logger import opendevin_logger as logger
-
-from . import json
+from opendevin.core.utils import json
 
 
 num_retries = config.llm.num_retries
 num_retries = config.llm.num_retries
 retry_min_wait = config.llm.retry_min_wait
 retry_min_wait = config.llm.retry_min_wait