Browse Source

Refactor monologue and SWE agent to use the messages in state history (#1863)

* Refactor monologue to use the messages in state history

* add messages, clean up

* fix monologue

* update integration tests

* move private method

* update SWE agent to use the history from State

* integration tests for SWE agent

* rename monologue to initial_thoughts, since that is what it is
Engel Nyst 1 year ago
parent
commit
0eccf31604

+ 18 - 13
agenthub/SWE_agent/agent.py

@@ -6,7 +6,6 @@ from opendevin.events.action import (
     FileWriteAction,
     MessageAction,
 )
-from opendevin.events.observation import Observation
 from opendevin.events.serialization.event import event_to_memory
 from opendevin.llm.llm import LLM
 
@@ -33,15 +32,9 @@ class SWEAgent(Agent):
         super().__init__(llm)
         self.memory_window = 4
         self.max_retries = 2
-        self.running_memory: list[str] = []
         self.cur_file: str = ''
         self.cur_line: int = 0
 
-    def _remember(self, action: Action, observation: Observation) -> None:
-        """Agent has a limited memory of the few steps implemented as a queue"""
-        memory = MEMORY_FORMAT(event_to_memory(action), event_to_memory(observation))
-        self.running_memory.append(memory)
-
     def _think_act(self, messages: list[dict]) -> tuple[Action, str]:
         resp = self.llm.do_completion(
             messages=messages,
@@ -69,24 +62,36 @@ class SWEAgent(Agent):
             2. Perform think-act - prompt model for action and reasoning
             3. Catch errors - ensure model takes action (5 attempts max)
         """
-        for prev_action, obs in state.updated_info:
-            self._remember(prev_action, obs)
+        # retrieve short term memories from state.history, up to memory_window
+        memory_window = min(self.memory_window, len(state.history))
+        running_memory: list[str] = []
+        for prev_action, obs in state.history[-memory_window:]:
+            running_memory.append(
+                MEMORY_FORMAT(event_to_memory(prev_action), event_to_memory(obs))
+            )
 
         goal = state.get_current_user_intent()
+
+        # always in the prompt if they exist: file and line
         prompt = STEP_PROMPT(goal, self.cur_file, self.cur_line)
 
+        # prepare messages
         msgs = [
             {'content': SYSTEM_MESSAGE, 'role': 'system'},
             {'content': prompt, 'role': 'user'},
         ]
 
-        if len(self.running_memory) > 0:
-            context = CONTEXT_PROMPT(self.running_memory, self.memory_window)
+        # insert memories
+        if len(running_memory) > 0:
+            context = CONTEXT_PROMPT(running_memory, self.memory_window)
             msgs.insert(1, {'content': context, 'role': 'user'})
         # clrs = [''] * (len(msgs)-2) + ['\033[0;36m', '\033[0;35m']
         # print('\n\n'.join([c+m['content']+'\033[0m' for c, m in zip(clrs, msgs)]))
+
+        # send it over
         action, thought = self._think_act(messages=msgs)
 
+        # be robust with malformed responses
         start_msg_len = len(msgs)
         while not action and len(msgs) < self.max_retries + start_msg_len:
             error = NO_ACTION(thought)
@@ -102,9 +107,9 @@ class SWEAgent(Agent):
         return action
 
     def search_memory(self, query: str) -> list[str]:
-        return [item for item in self.running_memory if query in item]
+        # return [item for item in self.running_memory if query in item]
+        raise NotImplementedError('Search_memory not implemented currently')
 
     def reset(self) -> None:
         """Used to reset the agent"""
-        self.running_memory = []
         super().reset()

+ 2 - 2
agenthub/SWE_agent/prompts.py

@@ -92,7 +92,7 @@ Notes:
 - To execute multiple commands you should write them down in your thoughts section so you can remember it on the next step and execute them then.
 - The only commands you are not capable of executing are interactive commands like `python` or `node` by themselves.
 - If you think that you have completed the task that has been given to you based on your previous actions and outputs then use ``` exit ``` as the command to let the system know that you are done.
-- DO NOT make any copies of your previous memories those will be provided to you at each step, making copies just wastes time and energy. Think smarter not harder.
+- DO NOT make any copies of your previous memories, those will be provided to you at each step, making copies just wastes time and energy. Think smarter not harder.
 - The write and edit commands requires proper indentation in the content section ex. `write hw.py def hello():\n    print(\'Hello World\')` this is how you would have to format your write command.
     - The white spaces matter as the code changes will be added to the code so they must have proper syntax.
 
@@ -115,7 +115,7 @@ Do not provide anything extra just your thought and action.
 
 SYSTEM_MESSAGE = f"""SYSTEM INFO:
 You are an autonomous coding agent, here to provide solutions for coding issues.
-You have been designed to assist you with a wide range of programming tasks, from code editing and debugging to testing and deployment.
+You have been designed to assist with a wide range of programming tasks, from code editing and debugging to testing and deployment.
 You have access to a variety of tools and commands that you can use to help you solve problems efficiently.
 
 {GENERAL_GUIDELINES}

+ 62 - 95
agenthub/monologue_agent/agent.py

@@ -1,4 +1,5 @@
 import agenthub.monologue_agent.utils.prompts as prompts
+from agenthub.monologue_agent.utils.prompts import INITIAL_THOUGHTS
 from opendevin.controller.agent import Agent
 from opendevin.controller.state.state import State
 from opendevin.core.config import config
@@ -25,7 +26,6 @@ from opendevin.events.observation import (
 from opendevin.events.serialization.event import event_to_memory
 from opendevin.llm.llm import LLM
 from opendevin.memory.condenser import MemoryCondenser
-from opendevin.memory.history import ShortTermHistory
 
 if config.agent.memory_enabled:
     from opendevin.memory.memory import LongTermMemory
@@ -33,51 +33,6 @@ if config.agent.memory_enabled:
 MAX_TOKEN_COUNT_PADDING = 512
 MAX_OUTPUT_LENGTH = 5000
 
-INITIAL_THOUGHTS = [
-    'I exist!',
-    'Hmm...looks like I can type in a command line prompt',
-    'Looks like I have a web browser too!',
-    "Here's what I want to do: $TASK",
-    'How am I going to get there though?',
-    'It seems like I have some kind of short term memory.',
-    'Each of my thoughts seems to be stored in a JSON array.',
-    'It seems whatever I say next will be added as an object to the list.',
-    'But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process.',
-    'Fortunately I have long term memory!',
-    'I can just perform a recall action, followed by the thing I want to remember. And then related thoughts just spill out!',
-    "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!",
-    "Let's try it out!",
-    'RECALL what it is I want to do',
-    "Here's what I want to do: $TASK",
-    'How am I going to get there though?',
-    "Neat! And it looks like it's easy for me to use the command line too! I just have to perform a run action and include the command I want to run in the command argument. The command output just jumps into my head!",
-    'RUN echo "hello world"',
-    'hello world',
-    'Cool! I bet I can write files too using the write action.',
-    'WRITE echo "console.log(\'hello world\')" > test.js',
-    '',
-    "I just created test.js. I'll try and run it now.",
-    'RUN node test.js',
-    'hello world',
-    'It works!',
-    "I'm going to try reading it now using the read action.",
-    'READ test.js',
-    "console.log('hello world')",
-    'Nice! I can read files too!',
-    'And if I want to use the browser, I just need to use the browse action and include the url I want to visit in the url argument',
-    "Let's try that...",
-    'BROWSE google.com',
-    '<form><input type="text"></input><button type="submit"></button></form>',
-    'I can browse the web too!',
-    'And once I have completed my task, I can use the finish action to stop working.',
-    "But I should only use the finish action when I'm absolutely certain that I've completed my task and have tested my work.",
-    'Very cool. Now to accomplish my task.',
-    "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.",
-    '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.',
-    "OK so my task is to $TASK. I haven't made any progress yet. Where should I start?",
-    'It seems like there might be an existing project here. I should probably start by running `pwd` and `ls` to orient myself.',
-]
-
 
 class MonologueAgent(Agent):
     VERSION = '1.0'
@@ -88,57 +43,19 @@ class MonologueAgent(Agent):
     """
 
     _initialized = False
-    monologue: ShortTermHistory
+    initial_thoughts: list[dict[str, str]]
     memory: 'LongTermMemory | None'
     memory_condenser: MemoryCondenser
 
     def __init__(self, llm: LLM):
         """
-        Initializes the Monologue Agent with an llm, monologue, and memory.
+        Initializes the Monologue Agent with an llm.
 
         Parameters:
         - llm (LLM): The llm to be used by this agent
         """
         super().__init__(llm)
 
-    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.
-
-        Parameters:
-        - event (dict): The event that will be added to monologue and memory
-        """
-
-        if (
-            'args' in event_dict
-            and 'output' in event_dict['args']
-            and len(event_dict['args']['output']) > MAX_OUTPUT_LENGTH
-        ):
-            event_dict['args']['output'] = (
-                event_dict['args']['output'][:MAX_OUTPUT_LENGTH] + '...'
-            )
-
-        self.monologue.add_event(event_dict)
-        if self.memory is not None:
-            self.memory.add_event(event_dict)
-
-        # Test monologue token length
-        prompt = prompts.get_request_action_prompt(
-            '',
-            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:
-            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):
         """
         Utilizes the INITIAL_THOUGHTS list to give the agent a context for its capabilities
@@ -159,7 +76,7 @@ class MonologueAgent(Agent):
         if task is None or task == '':
             raise AgentNoInstructionError()
 
-        self.monologue = ShortTermHistory()
+        self.initial_thoughts = []
         if config.agent.memory_enabled:
             self.memory = LongTermMemory()
         else:
@@ -188,7 +105,7 @@ class MonologueAgent(Agent):
                     observation = BrowserOutputObservation(
                         content=thought, url='', screenshot=''
                     )
-                self._add_event(event_to_memory(observation))
+                self.initial_thoughts.append(event_to_memory(observation))
                 previous_action = ''
             else:
                 action: Action = NullAction()
@@ -215,7 +132,7 @@ class MonologueAgent(Agent):
                     previous_action = ActionType.BROWSE
                 else:
                     action = MessageAction(thought)
-                self._add_event(event_to_memory(action))
+                self.initial_thoughts.append(event_to_memory(action))
 
     def step(self, state: State) -> Action:
         """
@@ -230,25 +147,75 @@ class MonologueAgent(Agent):
 
         goal = state.get_current_user_intent()
         self._initialize(goal)
-        for prev_action, obs in state.updated_info:
-            self._add_event(event_to_memory(prev_action))
-            self._add_event(event_to_memory(obs))
 
-        state.updated_info = []
+        recent_events: list[dict[str, str]] = []
+
+        # add the events from state.history
+        for prev_action, obs in state.history:
+            if not isinstance(prev_action, NullAction):
+                recent_events.append(event_to_memory(prev_action))
+            if not isinstance(obs, NullObservation):
+                recent_events.append(self._truncate_output(event_to_memory(obs)))
 
+        # add the last messages to long term memory
+        if self.memory is not None and state.history and len(state.history) > 0:
+            self.memory.add_event(event_to_memory(state.history[-1][0]))
+            self.memory.add_event(
+                self._truncate_output(event_to_memory(state.history[-1][1]))
+            )
+
+        # the action prompt with initial thoughts and recent events
         prompt = prompts.get_request_action_prompt(
             goal,
-            self.monologue.get_events(),
+            self.initial_thoughts,
+            recent_events,
             state.background_commands_obs,
         )
-        messages = [{'content': prompt, 'role': 'user'}]
+
+        messages: list[dict[str, str]] = [
+            {'role': 'user', 'content': prompt},
+        ]
+
+        # format all as a single message, a monologue
         resp = self.llm.do_completion(messages=messages)
+
+        # get the next action from the response
         action_resp = resp['choices'][0]['message']['content']
+
+        # keep track of max_chars fallback option
         state.num_of_chars += len(prompt) + len(action_resp)
+
         action = prompts.parse_action_response(action_resp)
         self.latest_action = action
         return action
 
+    def _truncate_output(
+        self, observation: dict, max_chars: int = MAX_OUTPUT_LENGTH
+    ) -> dict[str, str]:
+        """
+        Truncates the output of an observation to a maximum number of characters.
+
+        Parameters:
+        - output (str): The observation whose output to truncate
+        - max_chars (int): The maximum number of characters to allow
+
+        Returns:
+        - str: The truncated output
+        """
+        if (
+            'args' in observation
+            and 'output' in observation['args']
+            and len(observation['args']['output']) > max_chars
+        ):
+            output = observation['args']['output']
+            half = max_chars // 2
+            observation['args']['output'] = (
+                output[:half]
+                + '\n[... Output truncated due to length...]\n'
+                + output[-half:]
+            )
+        return observation
+
     def search_memory(self, query: str) -> list[str]:
         """
         Uses VectorIndexRetriever to find related memories within the long term memory.

+ 64 - 12
agenthub/monologue_agent/utils/prompts.py

@@ -18,7 +18,6 @@ This is your internal monologue, in JSON format:
 
 %(monologue)s
 
-
 Your most recent thought is at the bottom of that monologue. Continue your train of thought.
 What is your next single thought or action? Your response must be in JSON format.
 It must be a single object, and it must contain two fields:
@@ -92,6 +91,51 @@ The action key may be `summarize`, and `args.summary` should contain the summary
 You can also use the same action and args from the source monologue.
 """
 
+INITIAL_THOUGHTS = [
+    'I exist!',
+    'Hmm...looks like I can type in a command line prompt',
+    'Looks like I have a web browser too!',
+    "Here's what I want to do: $TASK",
+    'How am I going to get there though?',
+    'It seems like I have some kind of short term memory.',
+    'Each of my thoughts seems to be stored in a JSON array.',
+    'It seems whatever I say next will be added as an object to the list.',
+    'But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process.',
+    'Fortunately I have long term memory!',
+    'I can just perform a recall action, followed by the thing I want to remember. And then related thoughts just spill out!',
+    "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!",
+    "Let's try it out!",
+    'RECALL what it is I want to do',
+    "Here's what I want to do: $TASK",
+    'How am I going to get there though?',
+    "Neat! And it looks like it's easy for me to use the command line too! I just have to perform a run action and include the command I want to run in the command argument. The command output just jumps into my head!",
+    'RUN echo "hello world"',
+    'hello world',
+    'Cool! I bet I can write files too using the write action.',
+    'WRITE echo "console.log(\'hello world\')" > test.js',
+    '',
+    "I just created test.js. I'll try and run it now.",
+    'RUN node test.js',
+    'hello world',
+    'It works!',
+    "I'm going to try reading it now using the read action.",
+    'READ test.js',
+    "console.log('hello world')",
+    'Nice! I can read files too!',
+    'And if I want to use the browser, I just need to use the browse action and include the url I want to visit in the url argument',
+    "Let's try that...",
+    'BROWSE google.com',
+    '<form><input type="text"></input><button type="submit"></button></form>',
+    'I can browse the web too!',
+    'And once I have completed my task, I can use the finish action to stop working.',
+    "But I should only use the finish action when I'm absolutely certain that I've completed my task and have tested my work.",
+    'Very cool. Now to accomplish my task.',
+    "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.",
+    '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.',
+    "OK so my task is to $TASK. I haven't made any progress yet. Where should I start?",
+    'It seems like there might be an existing project here. I should probably start by running `pwd` and `ls` to orient myself.',
+]
+
 
 def get_summarize_monologue_prompt(thoughts: list[dict]):
     """
@@ -108,6 +152,7 @@ def get_summarize_monologue_prompt(thoughts: list[dict]):
 def get_request_action_prompt(
     task: str,
     thoughts: list[dict],
+    recent_events: list[dict],
     background_commands_obs: list[CmdOutputObservation] | None = None,
 ):
     """
@@ -119,23 +164,28 @@ def get_request_action_prompt(
     - background_commands_obs (list[CmdOutputObservation]): list of all observed background commands running
 
     Returns:
-    - str: Formatted prompt string with hint, task, monologue, and background included
+    - str: Formatted prompt string with hint, task, monologue, and background commands included
     """
 
     if background_commands_obs is None:
         background_commands_obs = []
 
     hint = ''
-    if len(thoughts) > 0:
-        latest_thought = thoughts[-1]
-        if 'action' in latest_thought:
-            if latest_thought['action'] == 'message':
-                if latest_thought['args']['content'].startswith('OK so my task is'):
-                    hint = "You're just getting started! What should you do first?"
-                else:
-                    hint = "You've been thinking a lot lately. Maybe it's time to take action?"
-            elif latest_thought['action'] == 'error':
+    if len(recent_events) > 0:
+        latest_event = recent_events[-1]
+        if 'action' in latest_event:
+            if (
+                latest_event['action'] == 'message'
+                and 'source' in latest_event
+                and latest_event['source'] == 'agent'
+            ):
+                hint = (
+                    "You've been thinking a lot lately. Maybe it's time to take action?"
+                )
+            elif latest_event['action'] == 'error':
                 hint = 'Looks like that last command failed. Maybe you need to fix it, or try something else.'
+    else:
+        hint = "You're just getting started! What should you do first?"
 
     bg_commands_message = ''
     if len(background_commands_obs) > 0:
@@ -148,9 +198,11 @@ def get_request_action_prompt(
 
     user = 'opendevin' if config.run_as_devin else 'root'
 
+    monologue = thoughts + recent_events
+
     return ACTION_PROMPT % {
         'task': task,
-        'monologue': json.dumps(thoughts, indent=2),
+        'monologue': json.dumps(monologue, indent=2),
         'background_commands': bg_commands_message,
         'hint': hint,
         'user': user,

+ 1 - 1
opendevin/controller/agent_controller.py

@@ -13,6 +13,7 @@ from opendevin.core.exceptions import (
 )
 from opendevin.core.logger import opendevin_logger as logger
 from opendevin.core.schema import AgentState
+from opendevin.events import EventSource, EventStream, EventStreamSubscriber
 from opendevin.events.action import (
     Action,
     AddTaskAction,
@@ -32,7 +33,6 @@ from opendevin.events.observation import (
     NullObservation,
     Observation,
 )
-from opendevin.events.stream import EventSource, EventStream, EventStreamSubscriber
 
 MAX_ITERATIONS = config.max_iterations
 MAX_CHARS = config.llm.max_chars

+ 1 - 1
opendevin/core/main.py

@@ -9,10 +9,10 @@ from opendevin.controller.state.state import State
 from opendevin.core.config import args, get_llm_config_arg
 from opendevin.core.logger import opendevin_logger as logger
 from opendevin.core.schema import AgentState
+from opendevin.events import EventSource, EventStream, EventStreamSubscriber
 from opendevin.events.action import ChangeAgentStateAction, MessageAction
 from opendevin.events.event import Event
 from opendevin.events.observation import AgentStateChangedObservation
-from opendevin.events.stream import EventSource, EventStream, EventStreamSubscriber
 from opendevin.llm.llm import LLM
 from opendevin.runtime.sandbox import Sandbox
 from opendevin.runtime.server.runtime import ServerRuntime

+ 9 - 0
opendevin/events/__init__.py

@@ -0,0 +1,9 @@
+from .event import Event, EventSource
+from .stream import EventStream, EventStreamSubscriber
+
+__all__ = [
+    'Event',
+    'EventSource',
+    'EventStream',
+    'EventStreamSubscriber',
+]

+ 10 - 8
opendevin/events/event.py

@@ -1,39 +1,41 @@
 import datetime
 from dataclasses import dataclass
-from typing import TYPE_CHECKING, Optional
+from enum import Enum
 
-if TYPE_CHECKING:
-    from opendevin.events.serialization.event import EventSource
+
+class EventSource(str, Enum):
+    AGENT = 'agent'
+    USER = 'user'
 
 
 @dataclass
 class Event:
     @property
-    def message(self) -> str:
+    def message(self) -> str | None:
         if hasattr(self, '_message'):
             return self._message  # type: ignore [attr-defined]
         return ''
 
     @property
-    def id(self) -> int:
+    def id(self) -> int | None:
         if hasattr(self, '_id'):
             return self._id  # type: ignore [attr-defined]
         return -1
 
     @property
-    def timestamp(self) -> Optional[datetime.datetime]:
+    def timestamp(self) -> datetime.datetime | None:
         if hasattr(self, '_timestamp'):
             return self._timestamp  # type: ignore [attr-defined]
         return None
 
     @property
-    def source(self) -> Optional['EventSource']:
+    def source(self) -> EventSource | None:
         if hasattr(self, '_source'):
             return self._source  # type: ignore [attr-defined]
         return None
 
     @property
-    def cause(self) -> Optional[int]:
+    def cause(self) -> int | None:
         if hasattr(self, '_cause'):
             return self._cause  # type: ignore [attr-defined]
         return None

+ 0 - 2
opendevin/events/serialization/__init__.py

@@ -2,7 +2,6 @@ from .action import (
     action_from_dict,
 )
 from .event import (
-    EventSource,
     event_from_dict,
     event_to_dict,
     event_to_memory,
@@ -17,5 +16,4 @@ __all__ = [
     'event_to_dict',
     'event_to_memory',
     'observation_from_dict',
-    'EventSource',
 ]

+ 2 - 11
opendevin/events/serialization/event.py

@@ -1,21 +1,12 @@
 from dataclasses import asdict
 from datetime import datetime
-from enum import Enum
-from typing import TYPE_CHECKING
+
+from opendevin.events import Event, EventSource
 
 from .action import action_from_dict
 from .observation import observation_from_dict
 from .utils import remove_fields
 
-if TYPE_CHECKING:
-    from opendevin.events.event import Event
-
-
-class EventSource(str, Enum):
-    AGENT = 'agent'
-    USER = 'user'
-
-
 # TODO: move `content` into `extras`
 TOP_KEYS = ['id', 'timestamp', 'source', 'message', 'cause', 'action', 'observation']
 UNDERSCORE_KEYS = ['id', 'timestamp', 'source', 'cause']

+ 5 - 3
opendevin/events/stream.py

@@ -8,8 +8,7 @@ from opendevin.core.logger import opendevin_logger as logger
 from opendevin.events.serialization.event import event_from_dict, event_to_dict
 from opendevin.storage import FileStore, get_file_store
 
-from .event import Event
-from .serialization.event import EventSource
+from .event import Event, EventSource
 
 
 class EventStreamSubscriber(str, Enum):
@@ -82,6 +81,9 @@ class EventStream:
         event._timestamp = datetime.now()  # type: ignore [attr-defined]
         event._source = source  # type: ignore [attr-defined]
         data = event_to_dict(event)
-        self._file_store.write(self._get_filename_for_id(event.id), json.dumps(data))
+        if event.id is not None:
+            self._file_store.write(
+                self._get_filename_for_id(event.id), json.dumps(data)
+            )
         for key, fn in self._subscribers.items():
             await fn(event)

+ 1 - 1
opendevin/runtime/runtime.py

@@ -2,6 +2,7 @@ import asyncio
 from abc import abstractmethod
 
 from opendevin.core.config import config
+from opendevin.events import EventSource, EventStream, EventStreamSubscriber
 from opendevin.events.action import (
     Action,
     AgentRecallAction,
@@ -21,7 +22,6 @@ from opendevin.events.observation import (
     Observation,
 )
 from opendevin.events.serialization.action import ACTION_TYPE_TO_CLASS
-from opendevin.events.stream import EventSource, EventStream, EventStreamSubscriber
 from opendevin.runtime import (
     DockerExecBox,
     DockerSSHBox,

+ 2 - 2
opendevin/server/session/session.py

@@ -8,9 +8,9 @@ from opendevin.core.logger import opendevin_logger as logger
 from opendevin.core.schema import AgentState
 from opendevin.core.schema.action import ActionType
 from opendevin.events.action import ChangeAgentStateAction, NullAction
-from opendevin.events.event import Event
+from opendevin.events.event import Event, EventSource
 from opendevin.events.observation import AgentStateChangedObservation, NullObservation
-from opendevin.events.serialization import EventSource, event_from_dict, event_to_dict
+from opendevin.events.serialization import event_from_dict, event_to_dict
 from opendevin.events.stream import EventStreamSubscriber
 
 from .agent import AgentSession

+ 0 - 6
tests/integration/mock/MonologueAgent/test_write_simple_script/prompt_001.log

@@ -326,15 +326,9 @@ This is your internal monologue, in JSON format:
       "content": "Write a shell script 'hello.sh' that prints 'hello'. Do not ask me for confirmation at any point.",
       "wait_for_response": false
     }
-  },
-  {
-    "observation": "null",
-    "content": "",
-    "extras": {}
   }
 ]
 
-
 Your most recent thought is at the bottom of that monologue. Continue your train of thought.
 What is your next single thought or action? Your response must be in JSON format.
 It must be a single object, and it must contain two fields:

+ 0 - 6
tests/integration/mock/MonologueAgent/test_write_simple_script/prompt_002.log

@@ -327,11 +327,6 @@ This is your internal monologue, in JSON format:
       "wait_for_response": false
     }
   },
-  {
-    "observation": "null",
-    "content": "",
-    "extras": {}
-  },
   {
     "source": "agent",
     "action": "write",
@@ -353,7 +348,6 @@ This is your internal monologue, in JSON format:
   }
 ]
 
-
 Your most recent thought is at the bottom of that monologue. Continue your train of thought.
 What is your next single thought or action? Your response must be in JSON format.
 It must be a single object, and it must contain two fields:

+ 0 - 6
tests/integration/mock/MonologueAgent/test_write_simple_script/prompt_003.log

@@ -327,11 +327,6 @@ This is your internal monologue, in JSON format:
       "wait_for_response": false
     }
   },
-  {
-    "observation": "null",
-    "content": "",
-    "extras": {}
-  },
   {
     "source": "agent",
     "action": "write",
@@ -372,7 +367,6 @@ This is your internal monologue, in JSON format:
   }
 ]
 
-
 Your most recent thought is at the bottom of that monologue. Continue your train of thought.
 What is your next single thought or action? Your response must be in JSON format.
 It must be a single object, and it must contain two fields:

+ 2 - 11
tests/integration/mock/MonologueAgent/test_write_simple_script/prompt_004.log

@@ -327,11 +327,6 @@ This is your internal monologue, in JSON format:
       "wait_for_response": false
     }
   },
-  {
-    "observation": "null",
-    "content": "",
-    "extras": {}
-  },
   {
     "source": "agent",
     "action": "write",
@@ -377,15 +372,9 @@ This is your internal monologue, in JSON format:
       "content": "I have successfully created and executed the 'hello.sh' script, which printed 'hello' as expected. I believe I have completed the task as specified.",
       "wait_for_response": false
     }
-  },
-  {
-    "observation": "null",
-    "content": "",
-    "extras": {}
   }
 ]
 
-
 Your most recent thought is at the bottom of that monologue. Continue your train of thought.
 What is your next single thought or action? Your response must be in JSON format.
 It must be a single object, and it must contain two fields:
@@ -432,3 +421,5 @@ Notes:
 * whenever an action fails, always send a `message` about why it may have happened before acting again.
 
 What is your next single thought or action? Again, you must reply with JSON, and only with JSON. You must respond with exactly one 'action' object.
+
+You've been thinking a lot lately. Maybe it's time to take action?

+ 1 - 1
tests/unit/test_event_stream.py

@@ -2,9 +2,9 @@ import json
 
 import pytest
 
+from opendevin.events import EventSource, EventStream
 from opendevin.events.action import NullAction
 from opendevin.events.observation import NullObservation
-from opendevin.events.stream import EventSource, EventStream
 
 
 def collect_events(stream):

+ 7 - 3
tests/unit/test_micro_agents.py

@@ -7,9 +7,9 @@ import yaml
 from agenthub.micro.registry import all_microagents
 from opendevin.controller.agent import Agent
 from opendevin.controller.state.state import State
+from opendevin.events import EventSource
 from opendevin.events.action import MessageAction
 from opendevin.events.observation import NullObservation
-from opendevin.events.serialization import EventSource
 
 
 def test_all_agents_are_loaded():
@@ -31,7 +31,9 @@ def test_coder_agent_with_summary():
     """
     mock_llm = MagicMock()
     content = json.dumps({'action': 'finish', 'args': {}})
-    mock_llm.do_completion.return_value = {'choices': [{'message': {'content': content}}]}
+    mock_llm.do_completion.return_value = {
+        'choices': [{'message': {'content': content}}]
+    }
 
     coder_agent = Agent.get_cls('CoderAgent')(llm=mock_llm)
     assert coder_agent is not None
@@ -58,7 +60,9 @@ def test_coder_agent_without_summary():
     """
     mock_llm = MagicMock()
     content = json.dumps({'action': 'finish', 'args': {}})
-    mock_llm.do_completion.return_value = {'choices': [{'message': {'content': content}}]}
+    mock_llm.do_completion.return_value = {
+        'choices': [{'message': {'content': content}}]
+    }
 
     coder_agent = Agent.get_cls('CoderAgent')(llm=mock_llm)
     assert coder_agent is not None