Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrating MemGPT-like Functionality #2937

Draft
wants to merge 42 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fd5adae
Edited Makefile
khushvind Jul 6, 2024
78be88f
updated num_retries
khushvind Jul 6, 2024
efc1c55
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 7, 2024
9ee63e1
Fix ruff issues and added summarizer
khushvind Jul 9, 2024
a4e2a18
Merge remote-tracking branch 'upstream/main'
khushvind Jul 10, 2024
507a67e
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 10, 2024
ccffdcc
added summarize function
khushvind Jul 14, 2024
4c3482f
Integrated MemGPT like functionality
khushvind Jul 14, 2024
4b1bf53
Integrated MemGPT like Functionality
khushvind Jul 14, 2024
de1b94b
Merge remote-tracking branch 'upstream/main'
khushvind Jul 15, 2024
5fc4ce4
Merge remote-tracking branch 'upstream/main' into MemGPT_Summarize_St…
khushvind Jul 15, 2024
85a8f4b
Retriving Summary
khushvind Jul 15, 2024
2845c2c
removed bugs
khushvind Jul 15, 2024
7a8299b
removed pip install -q -U google-generativeai from Makefile
khushvind Jul 16, 2024
a7a4c8a
removed bugs
khushvind Jul 18, 2024
0428dcc
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 19, 2024
bd9d14f
corrected the max_input_token
khushvind Jul 19, 2024
22e92d7
moved condenser configs to LLMConfig
khushvind Jul 19, 2024
d2b1ae1
fixed issue causing error in test on linux
khushvind Jul 20, 2024
1afd574
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 20, 2024
c43ed97
converted each message to Message class with additional attributes
khushvind Jul 21, 2024
6080071
Moved condenser functions to LLM class
khushvind Jul 22, 2024
515e038
removed condenser.py file
khushvind Jul 23, 2024
44d3c9d
Removed ContextWindowExceededError - TokenLimitExceededError already …
khushvind Jul 23, 2024
d93f5ee
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 25, 2024
0fece3f
Merge branch 'main' into MemGPT
khushvind Jul 25, 2024
d59be73
build condenser as mixin class
khushvind Jul 26, 2024
85b715d
build condenser as mixin class
khushvind Jul 26, 2024
7c5606d
Merge remote-tracking branch 'origin' into MemGPT
khushvind Jul 26, 2024
d9b3aae
Merge remote-tracking branch 'origin/MemGPT' into MemGPT
khushvind Jul 26, 2024
2a81073
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 26, 2024
140253c
replaced get_response with the original llm.completion
khushvind Jul 27, 2024
c7a3713
returning summarize_action to agent controller to add to memory
khushvind Jul 28, 2024
754a9c3
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 28, 2024
490b192
removed bug - pass summary in prompt
khushvind Jul 30, 2024
2162c91
modified summarize_messages
khushvind Jul 30, 2024
a90edc8
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 31, 2024
8d7bc30
Merge remote-tracking branch 'upstream/main' into MemGPT
khushvind Jul 31, 2024
34caa62
Merged with latest main
khushvind Aug 20, 2024
f30a572
updated prompt for condenser
khushvind Aug 21, 2024
13a9f64
removed print summary message
khushvind Aug 22, 2024
d25e19e
Modified how agent_controller handles summarization actions
khushvind Aug 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 69 additions & 36 deletions agenthub/codeact_agent/codeact_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Action,
AgentDelegateAction,
AgentFinishAction,
AgentSummarizeAction,
CmdRunAction,
IPythonRunCellAction,
MessageAction,
Expand All @@ -24,6 +25,7 @@
from opendevin.events.observation.observation import Observation
from opendevin.events.serialization.event import truncate_content
from opendevin.llm.llm import LLM
from opendevin.llm.messages import Message
from opendevin.runtime.plugins import (
AgentSkillsRequirement,
JupyterRequirement,
Expand Down Expand Up @@ -121,22 +123,36 @@ def action_to_str(self, action: Action) -> str:
return f'{action.thought}\n<execute_browse>\n{action.inputs["task"]}\n</execute_browse>'
elif isinstance(action, MessageAction):
return action.content
elif isinstance(action, AgentSummarizeAction):
return (
'Summary of all Action and Observations till now. \n'
+ 'Action: '
+ action.summarized_actions
+ '\n Observation: '
+ action.summarized_observations
)
return ''

def get_action_message(self, action: Action) -> dict[str, str] | None:
def get_action_message(self, action: Action) -> Message | None:
message = None
if (
isinstance(action, AgentDelegateAction)
or isinstance(action, CmdRunAction)
or isinstance(action, IPythonRunCellAction)
or isinstance(action, MessageAction)
or isinstance(action, AgentSummarizeAction)
):
return {
message = {
'role': 'user' if action.source == 'user' else 'assistant',
'content': self.action_to_str(action),
}
return None
if message:
return Message(message=message, condensable=True, event_id=action.id)
else:
return None

def get_observation_message(self, obs: Observation) -> dict[str, str] | None:
def get_observation_message(self, obs: Observation) -> Message | None:
message = None
max_message_chars = self.llm.config.max_message_chars
if isinstance(obs, CmdOutputObservation):
content = 'OBSERVATION:\n' + truncate_content(
Expand All @@ -145,7 +161,7 @@ def get_observation_message(self, obs: Observation) -> dict[str, str] | None:
content += (
f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]'
)
return {'role': 'user', 'content': content}
message = {'role': 'user', 'content': content}
elif isinstance(obs, IPythonRunCellObservation):
content = 'OBSERVATION:\n' + obs.content
# replace base64 images with a placeholder
Expand All @@ -157,13 +173,16 @@ def get_observation_message(self, obs: Observation) -> dict[str, str] | None:
)
content = '\n'.join(splitted)
content = truncate_content(content, max_message_chars)
return {'role': 'user', 'content': content}
message = {'role': 'user', 'content': content}
elif isinstance(obs, AgentDelegateObservation):
content = 'OBSERVATION:\n' + truncate_content(
str(obs.outputs), max_message_chars
)
return {'role': 'user', 'content': content}
return None
message = {'role': 'user', 'content': content}
if message:
return Message(message=message, condensable=True, event_id=obs.id)
else:
return None

def reset(self) -> None:
"""Resets the CodeAct Agent."""
Expand All @@ -188,48 +207,62 @@ def step(self, state: State) -> Action:
if latest_user_message and latest_user_message.strip() == '/exit':
return AgentFinishAction()

# prepare what we want to send to the LLM
messages: list[dict[str, str]] = self._get_messages(state)

response = self.llm.completion(
khushvind marked this conversation as resolved.
Show resolved Hide resolved
messages=messages,
stop=[
'</execute_ipython>',
'</execute_bash>',
'</execute_browse>',
],
temperature=0.0,
enyst marked this conversation as resolved.
Show resolved Hide resolved
)
response = None
# give it multiple chances to get a response
# if it fails, we'll try to condense memory
attempt = 0
while not response and attempt < self.llm.config.attempts_to_condense:
# prepare what we want to send to the LLM
messages: list[Message] = self._get_messages(state)
print('No of tokens, ' + str(self.llm.get_token_count(messages)) + '\n')
response = self.llm.get_response(messages=messages, state=state)
attempt += 1

return self.action_parser.parse(response)

def _get_messages(self, state: State) -> list[dict[str, str]]:
def search_memory(self, query: str) -> list[str]:
raise NotImplementedError('Implement this abstract method')
enyst marked this conversation as resolved.
Show resolved Hide resolved

def _get_messages(self, state: State) -> list[Message]:
messages = [
{'role': 'system', 'content': self.system_message},
{'role': 'user', 'content': self.in_context_example},
Message(
message={'role': 'system', 'content': self.system_message},
condensable=False,
),
Message(
message={'role': 'user', 'content': self.in_context_example},
condensable=False,
),
]

for event in state.history.get_events():
# create a regular message from an event
if isinstance(event, Action):
message = self.get_action_message(event)
elif isinstance(event, Observation):
message = self.get_observation_message(event)
else:
raise ValueError(f'Unknown event type: {type(event)}')

# add regular message
if message:
messages.append(message)
if event.id >= state.history.last_summarized_event_id:
if isinstance(event, AgentSummarizeAction):
khushvind marked this conversation as resolved.
Show resolved Hide resolved
action_message = self.get_action_message(event)
if action_message:
messages.append(action_message)
else:
# create a regular message from an event
if isinstance(event, Action):
message = self.get_action_message(event)
elif isinstance(event, Observation):
message = self.get_observation_message(event)
else:
raise ValueError(f'Unknown event type: {type(event)}')

# add regular message
if message:
messages.append(message)

# the latest user message is important:
# we want to remind the agent of the environment constraints
latest_user_message = next(
(m for m in reversed(messages) if m['role'] == 'user'), None
(m for m in reversed(messages) if m.message['role'] == 'user'), None
)

# add a reminder to the prompt
if latest_user_message:
latest_user_message['content'] += (
latest_user_message.message['content'] += (
f'\n\nENVIRONMENT REMINDER: You have {state.max_iterations - state.iteration} turns left to complete the task. When finished reply with <finish></finish>'
)

Expand Down
2 changes: 2 additions & 0 deletions opendevin/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class LLMConfig:
input_cost_per_token: float | None = None
output_cost_per_token: float | None = None
ollama_base_url: str | None = None
message_summary_trunc_tokens_frac: float = 0.75
attempts_to_condense: int = 2

def defaults_to_dict(self) -> dict:
"""Serialize fields to a dict for the frontend, including type hints, defaults, and whether it's optional."""
Expand Down
26 changes: 26 additions & 0 deletions opendevin/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,29 @@ def __init__(self, message='Agent must return an action'):
class LLMResponseError(Exception):
def __init__(self, message='Failed to retrieve action from LLM response'):
super().__init__(message)


class TokenLimitExceededError(Exception):
"""Exception raised when the user-defined max_input_tokens limit is exceeded."""

def __init__(self, message='User-defined token limit exceeded. Condensing memory.'):
super().__init__(message)


class ContextWindowLimitExceededError(Exception):
def __init__(
self, message='Context window limit exceeded. Unable to condense memory.'
):
super().__init__(message)


class SummarizeError(Exception):
"""Exception raised when message can't be Summarized."""

def __init__(self, message='Error Summarizing The Memory'):
super().__init__(message)


class InvalidSummaryResponseError(Exception):
def __init__(self, message='Invalid summary response'):
super().__init__(message)
37 changes: 34 additions & 3 deletions opendevin/events/action/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,49 @@ def message(self) -> str:
return f'Agent state changed to {self.agent_state}'


# @dataclass
# class AgentSummarizeAction(Action):
# summary: str
# action: str = ActionType.SUMMARIZE
# _chunk_start: int = -1
# _chunk_end: int = -1

# @property
# def message(self) -> str:
# return self.summary

# def __str__(self) -> str:
# ret = '**AgentSummarizeAction**\n'
# ret += f'SUMMARY: {self.summary}'
# return


@dataclass
class AgentSummarizeAction(Action):
summary: str
"""
Action to summarize a list of events.

Attributes:
- summarized_actions: A sentence summarizing all the actions.
- summarized_observations: A few sentences summarizing all the observations.
"""

summarized_actions: str = ''
summarized_observations: str = ''
action: str = ActionType.SUMMARIZE
# _chunk_start: int = -1
# _chunk_end: int = -1
last_summarized_event_id = -1
is_delegate_summary: bool = False
khushvind marked this conversation as resolved.
Show resolved Hide resolved

@property
def message(self) -> str:
return self.summary
return self.summarized_observations

def __str__(self) -> str:
ret = '**AgentSummarizeAction**\n'
ret += f'SUMMARY: {self.summary}'
ret += f'SUMMARIZED ACTIONS: {self.summarized_actions}\n'
ret += f'SUMMARIZED OBSERVATIONS: {self.summarized_observations}\n'
return ret


Expand Down
2 changes: 2 additions & 0 deletions opendevin/events/serialization/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
AgentDelegateAction,
AgentFinishAction,
AgentRejectAction,
AgentSummarizeAction,
ChangeAgentStateAction,
)
from opendevin.events.action.browse import BrowseInteractiveAction, BrowseURLAction
Expand Down Expand Up @@ -31,6 +32,7 @@
ModifyTaskAction,
ChangeAgentStateAction,
MessageAction,
AgentSummarizeAction,
)

ACTION_TYPE_TO_CLASS = {action_class.action: action_class for action_class in actions} # type: ignore[attr-defined]
Expand Down
Loading