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

feat: Dynamic template system with editable system prompt fields. #895

Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
19dcede
System message template system
Maximilian-Winter Jan 21, 2024
de15d3c
Update formatting
Maximilian-Winter Jan 21, 2024
c05c77e
Merge remote-tracking branch 'upstream/main' into DynamicTemplateSystem
Maximilian-Winter Jan 21, 2024
7c328e7
Merge remote-tracking branch 'upstream/main' into DynamicTemplateSystem
Maximilian-Winter Jan 22, 2024
e431af4
Implemented Dynamic Runtime Template system.
Maximilian-Winter Jan 22, 2024
9b0751a
Merge remote-tracking branch 'upstream/main' into DynamicTemplateSystem
Maximilian-Winter Jan 22, 2024
2a152c8
Update agent.py
Maximilian-Winter Jan 22, 2024
be2f8f0
Merge remote-tracking branch 'upstream/main' into DynamicTemplateSystem
Maximilian-Winter Jan 27, 2024
c5eb200
Added customizable core memory
Maximilian-Winter Jan 27, 2024
655045b
Update memory.py
Maximilian-Winter Jan 27, 2024
5855915
Update memory.py
Maximilian-Winter Jan 27, 2024
5ae2ae1
Merge remote-tracking branch 'upstream/main' into DynamicTemplateSystem
Maximilian-Winter Jan 28, 2024
6f19dda
Integrated customizable core memory into memgpt
Maximilian-Winter Jan 28, 2024
f7042fb
Update main.py
Maximilian-Winter Jan 28, 2024
1fc609d
Templated everything in the system message.
Maximilian-Winter Jan 28, 2024
85f0981
Update agent.py
Maximilian-Winter Jan 28, 2024
35716a4
Update prompt_template.py
Maximilian-Winter Jan 28, 2024
4a97eee
Merge remote-tracking branch 'upstream/main' into DynamicTemplateSystem
Maximilian-Winter Mar 10, 2024
7c1a2ca
Merge remote-tracking branch 'upstream/main' into DynamicTemplateSystem
Maximilian-Winter Apr 2, 2024
182e8f2
Try to make it work again
Maximilian-Winter Apr 2, 2024
5ac23fa
Update agent.py
Maximilian-Winter Apr 2, 2024
99eb353
Fixed formatting
Maximilian-Winter Apr 2, 2024
eb3e8a2
Update agent.py
Maximilian-Winter Apr 2, 2024
3b34123
Update presets.py
Maximilian-Winter Apr 2, 2024
250cb73
Update presets.py
Maximilian-Winter Apr 2, 2024
b52a0c5
Update generate_default_template_fields_yaml.py
Maximilian-Winter Apr 2, 2024
95b642d
Update presets.py
Maximilian-Winter Apr 2, 2024
f5cd107
Update default_templates.py
Maximilian-Winter Apr 2, 2024
bbf9ed7
Update initial_core_memory.yaml
Maximilian-Winter Apr 2, 2024
e141322
Update metadata.py
Maximilian-Winter Apr 2, 2024
2394371
Update data_types.py
Maximilian-Winter Apr 2, 2024
ca82ddd
Update agent.py
Maximilian-Winter Apr 2, 2024
e248945
Update default_templates.py
Maximilian-Winter Apr 2, 2024
e870ca0
Update presets.py
Maximilian-Winter Apr 3, 2024
cd98228
Update metadata.py
Maximilian-Winter Apr 3, 2024
c831c73
Update data_types.py
Maximilian-Winter Apr 3, 2024
bc07052
Update agent.py
Maximilian-Winter Apr 3, 2024
a4cbac3
Update presets.py
Maximilian-Winter Apr 3, 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
200 changes: 162 additions & 38 deletions memgpt/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import json
from pathlib import Path
import traceback
from typing import List, Tuple, Optional, cast
from typing import List, Tuple, Optional, cast, Union

from box import Box

Expand All @@ -16,7 +16,7 @@
from memgpt.persistence_manager import PersistenceManager, LocalStateManager
from memgpt.config import MemGPTConfig
from memgpt.system import get_login_event, package_function_response, package_summarize_message, get_initial_boot_messages
from memgpt.memory import CoreMemory as InContextMemory, summarize_messages
from memgpt.memory import CoreMemory as InContextMemory, summarize_messages, CustomizableCoreMemory as CustomizableInContextMemory
from memgpt.llm_api_tools import create, is_context_overflow_error
from memgpt.utils import (
get_tool_call_id,
Expand All @@ -42,6 +42,8 @@
)
from .errors import LLMError
from .functions.functions import USER_FUNCTIONS_DIR, load_all_function_sets
from .presets.default_templates import default_system_message_layout_template, default_core_memory_section_template
from .prompts.prompt_template import PromptTemplate


def link_functions(function_schemas):
Expand Down Expand Up @@ -92,6 +94,10 @@
return linked_function_set


def initialize_custom_memory(core_memory: dict, core_memory_limits: dict):
return CustomizableInContextMemory(core_memory, core_memory_limits)


def initialize_memory(ai_notes, human_notes):
if ai_notes is None:
raise ValueError(ai_notes)
Expand All @@ -103,30 +109,69 @@
return memory


def construct_system_with_memory(system, memory, memory_edit_timestamp, archival_memory=None, recall_memory=None, include_char_count=True):
full_system_message = "\n".join(
[
system,
"\n",
f"### Memory [last modified: {memory_edit_timestamp.strip()}]",
f"{len(recall_memory) if recall_memory else 0} previous messages between you and the user are stored in recall memory (use functions to access them)",
f"{len(archival_memory) if archival_memory else 0} total memories you created are stored in archival memory (use functions to access them)",
"\nCore memory shown below (limited in size, additional information stored in archival / recall memory):",
f'<persona characters="{len(memory.persona)}/{memory.persona_char_limit}">' if include_char_count else "<persona>",
memory.persona,
"</persona>",
f'<human characters="{len(memory.human)}/{memory.human_char_limit}">' if include_char_count else "<human>",
memory.human,
"</human>",
]
)
def construct_system_with_memory(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels to me like an increase in complexity to a very core component of the overall agent.

I think the templated core memory feature is a cool idea, but I think the templating handling should be encapsulated into a class and then passed into this function.

Something like
def construct_system_with_memory(system: SystemPromptHandler, ...

then the full system message gets handled via

system.get_message() or something similar.

This way if someone wants to experiment with a different approach, they can simply swap out the SystemHandler class, rather than needing to work around the templating system here

Copy link
Contributor Author

@Maximilian-Winter Maximilian-Winter Feb 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right about that. I wrote the code after I hadn't slept for a night. 😄 And it was a wild idea.

system,
memory,
memory_edit_timestamp,
system_message_layout_template,
core_memory_section_template,
archival_memory=None,
recall_memory=None,
include_char_count=True,
):
system_template = PromptTemplate.from_string(system_message_layout_template)

if isinstance(memory, InContextMemory):
core_memory_section_template = PromptTemplate.from_string(core_memory_section_template)
core_memory_content = (
core_memory_section_template.generate_prompt(
{
"memory_key": "persona",
"memory_value": memory.persona,
"memory_value_length": len(memory.persona),

Check failure on line 131 in memgpt/agent.py

View workflow job for this annotation

GitHub Actions / Pyright types check (3.11)

Argument of type "Unknown | None" cannot be assigned to parameter "__obj" of type "Sized" in function "len"   Type "Unknown | None" cannot be assigned to type "Sized"     "None" is incompatible with protocol "Sized"       "__len__" is not present (reportArgumentType)
"memory_value_limit": memory.persona_char_limit,
}
)
+ "\n"
)
core_memory_content += (
core_memory_section_template.generate_prompt(
{
"memory_key": "human",
"memory_value": memory.human,
"memory_value_length": len(memory.human),

Check failure on line 142 in memgpt/agent.py

View workflow job for this annotation

GitHub Actions / Pyright types check (3.11)

Argument of type "Unknown | None" cannot be assigned to parameter "__obj" of type "Sized" in function "len"   Type "Unknown | None" cannot be assigned to type "Sized"     "None" is incompatible with protocol "Sized"       "__len__" is not present (reportArgumentType)
"memory_value_limit": memory.human_char_limit,
}
)
+ "\n"
)
template_fields = {
"system": system,
"len_recall_memory": len(recall_memory) if recall_memory else 0,
"len_archival_memory": len(archival_memory) if archival_memory else 0,
"core_memory_content": core_memory_content,
"memory_edit_timestamp": memory_edit_timestamp.strip(),
}
full_system_message = system_template.generate_prompt(template_fields)
else:
core_memory_content = memory.get_memory_view(core_memory_section_template)
template_fields = {
"system": system,
"len_recall_memory": f"{len(recall_memory) if recall_memory else 0}",
"len_archival_memory": f"{len(archival_memory) if archival_memory else 0}",
"core_memory_content": core_memory_content,
"memory_edit_timestamp": memory_edit_timestamp.strip(),
}
full_system_message = system_template.generate_prompt(template_fields)
return full_system_message


def initialize_message_sequence(
model,
system,
memory,
system_message_layout_template,
core_memory_section_template,
archival_memory=None,
recall_memory=None,
memory_edit_timestamp=None,
Expand All @@ -136,7 +181,13 @@
memory_edit_timestamp = get_local_time()

full_system_message = construct_system_with_memory(
system, memory, memory_edit_timestamp, archival_memory=archival_memory, recall_memory=recall_memory
system,
memory,
memory_edit_timestamp,
system_message_layout_template,
core_memory_section_template,
archival_memory=archival_memory,
recall_memory=recall_memory,
)
first_user_message = get_login_event() # event letting MemGPT know the user just logged in

Expand Down Expand Up @@ -184,21 +235,40 @@
if "system" not in agent_state.state:
raise ValueError(f"'system' not found in provided AgentState")
self.system = agent_state.state["system"]

if "system_template" not in agent_state.state:
self.system_template = ""
else:
self.system_template = agent_state.state["system_template"]
if "system_message_layout_template" not in agent_state.state:
self.system_message_layout_template = default_system_message_layout_template
else:
self.system_message_layout_template = agent_state.state["system_message_layout_template"]
if "core_memory_section_template" not in agent_state.state:
self.core_memory_section_template = default_core_memory_section_template
else:
self.core_memory_section_template = agent_state.state["core_memory_section_template"]
if "system_template_fields" not in agent_state.state:
self.system_template_fields = {}
else:
self.system_template_fields = agent_state.state["system_template_fields"]
if "functions" not in agent_state.state:
raise ValueError(f"'functions' not found in provided AgentState")
# Store the functions schemas (this is passed as an argument to ChatCompletion)
self.functions = agent_state.state["functions"] # these are the schema
# Link the actual python functions corresponding to the schemas
self.functions_python = {k: v["python_function"] for k, v in link_functions(function_schemas=self.functions).items()}
assert all([callable(f) for k, f in self.functions_python.items()]), self.functions_python

# Initialize the memory object
if "persona" not in agent_state.state:
raise ValueError(f"'persona' not found in provided AgentState")
if "human" not in agent_state.state:
raise ValueError(f"'human' not found in provided AgentState")
self.memory = initialize_memory(ai_notes=agent_state.state["persona"], human_notes=agent_state.state["human"])
if "core_memory_type" in agent_state.state and agent_state.state["core_memory_type"] == "custom":
Maximilian-Winter marked this conversation as resolved.
Show resolved Hide resolved
if "core_memory" not in agent_state.state:
raise ValueError(f"'core_memory' not found in provided AgentState")
self.memory = initialize_custom_memory(agent_state.state["core_memory"], agent_state.state["core_memory_limits"])
else:
# Initialize the memory object
if "persona" not in agent_state.state:
raise ValueError(f"'persona' not found in provided AgentState")
if "human" not in agent_state.state:
raise ValueError(f"'human' not found in provided AgentState")
self.memory = initialize_memory(ai_notes=agent_state.state["persona"], human_notes=agent_state.state["human"])

# Interface must implement:
# - internal_monologue
Expand Down Expand Up @@ -252,9 +322,7 @@
else:
# print(f"Agent.__init__ :: creating, state={agent_state.state['messages']}")
init_messages = initialize_message_sequence(
self.model,
self.system,
self.memory,
self.model, self.system, self.memory, self.system_message_layout_template, self.core_memory_section_template
)
init_messages_objs = []
for msg in init_messages:
Expand Down Expand Up @@ -639,7 +707,7 @@

# Step 4: extend the message history
if user_message is not None:
all_new_messages = [packed_user_message_obj] + all_response_messages

Check failure on line 710 in memgpt/agent.py

View workflow job for this annotation

GitHub Actions / Pyright types check (3.11)

"packed_user_message_obj" is possibly unbound (reportPossiblyUnboundVariable)
else:
all_new_messages = all_response_messages

Expand Down Expand Up @@ -669,7 +737,7 @@

self._append_to_messages(all_new_messages)
all_new_messages_dicts = [msg.to_openai_dict() for msg in all_new_messages]
return all_new_messages_dicts, heartbeat_request, function_failed, active_memory_warning, response.usage.completion_tokens

Check failure on line 740 in memgpt/agent.py

View workflow job for this annotation

GitHub Actions / Pyright types check (3.11)

Expression of type "tuple[list[dict[str, str] | dict[str, Unknown]], bool, bool, bool, int]" cannot be assigned to return type "Tuple[List[dict[Unknown, Unknown]], bool, bool, bool]"   "tuple[list[dict[str, str] | dict[str, Unknown]], bool, bool, bool, int]" is incompatible with "Tuple[List[dict[Unknown, Unknown]], bool, bool, bool]"     Tuple size mismatch; expected 4 but received 5 (reportReturnType)

except Exception as e:
printd(f"step() failed\nuser_message = {user_message}\nerror = {e}")
Expand Down Expand Up @@ -816,6 +884,43 @@
self.model,
self.system,
self.memory,
self.system_message_layout_template,
self.core_memory_section_template,
archival_memory=self.persistence_manager.archival_memory,
recall_memory=self.persistence_manager.recall_memory,
)[0]

diff = united_diff(curr_system_message["content"], new_system_message["content"])
printd(f"Rebuilding system with new memory...\nDiff:\n{diff}")

# Swap the system message out
self._swap_system_message(
Message.dict_to_message(
agent_id=self.agent_state.id, user_id=self.agent_state.user_id, model=self.model, openai_message_dict=new_system_message
)
)

def edit_system_template_field(self, field_name: str, field_value: Union[str, float, int], rebuild_system_template: bool = True):
"""Edits a system template field"""
if field_name not in self.system_template_fields:
raise ValueError(f"'{field_name}' not found in system template fields")

self.system_template_fields[field_name] = field_value
if rebuild_system_template:
self.rebuild_system_template()

def rebuild_system_template(self):
"""Rebuilds the system message with the latest template field values"""
curr_system_message = self.messages[0] # this is the system + memory bank, not just the system prompt

template = PromptTemplate.from_string(self.system_template)

new_system_message = initialize_message_sequence(
self.model,
template.generate_prompt(self.system_template_fields),
self.memory,
self.system_message_layout_template,
self.core_memory_section_template,
archival_memory=self.persistence_manager.archival_memory,
recall_memory=self.persistence_manager.recall_memory,
)[0]
Expand Down Expand Up @@ -916,13 +1021,32 @@
# self.ms.update_agent(agent=new_agent_state)

def update_state(self) -> AgentState:
updated_state = {
"persona": self.memory.persona,
"human": self.memory.human,
"system": self.system,
"functions": self.functions,
"messages": [str(msg.id) for msg in self._messages],
}
if isinstance(self.memory, InContextMemory):
updated_state = {
"persona": self.memory.persona,
"human": self.memory.human,
"core_memory_type": "default",
"system_message_layout_template": self.system_message_layout_template,
"core_memory_section_template": self.core_memory_section_template,
"system": self.system,
"system_template": self.system_template,
"functions": self.functions,
"messages": [str(msg.id) for msg in self._messages],
}
elif isinstance(self.memory, CustomizableInContextMemory):
updated_state = {
"core_memory": self.memory.core_memory,
"core_memory_limits": self.memory.memory_field_limits,
"core_memory_type": "custom",
"system_message_layout_template": self.system_message_layout_template,
"core_memory_section_template": self.core_memory_section_template,
"system": self.system,
"system_template": self.system_template,
"functions": self.functions,
"messages": [str(msg.id) for msg in self._messages],
}
else:
updated_state = {}

self.agent_state = AgentState(
name=self.agent_state.name,
Expand Down
29 changes: 28 additions & 1 deletion memgpt/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,34 @@ def run_agent_loop(memgpt_agent, config: MemGPTConfig, first, ms: MetadataStore,
bold=True,
)
continue

elif user_input.lower().startswith("/edit_template_field"):
try:
field_name = questionary.text(
"Enter the template field name:",
multiline=multiline_input,
qmark=">",
).ask()
clear_line(strip_ui)

field_value = questionary.text(
"Enter the template field value:",
multiline=multiline_input,
qmark=">",
).ask()
memgpt_agent.edit_system_template_field(field_name, field_value)
clear_line(strip_ui)
typer.secho(
f"/edit_template_field succeeded: {field_value} : {field_value}",
fg=typer.colors.GREEN,
bold=True,
)
except ValueError as e:
typer.secho(
f"/edit_template_field failed:\n{e}",
fg=typer.colors.RED,
bold=True,
)
continue
# No skip options
elif user_input.lower() == "/wipe":
memgpt_agent = agent.Agent(interface)
Expand Down
Loading
Loading