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: Abstract Agent Class + Split Thread Agent #1700

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 32 additions & 1 deletion memgpt/agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
import inspect
import traceback

from abc import ABC, abstractmethod
from typing import List, Literal, Optional, Tuple, Union

from tqdm import tqdm
Expand Down Expand Up @@ -188,7 +190,36 @@ def initialize_message_sequence(
return messages


class Agent(object):
class BaseAgent(ABC):
"""
Abstract class for conversational agents.
"""

agent_state: AgentState
memory: Memory
interface: AgentInterface

@abstractmethod
def step(
self,
user_message: Union[Message, str], # NOTE: should be json.dump(dict)
first_message: bool = False,
first_message_retry_limit: int = FIRST_MESSAGE_ATTEMPTS,
skip_verify: bool = False,
return_dicts: bool = True, # if True, return dicts, if False, return Message objects
recreate_message_timestamp: bool = True, # if True, when input is a Message type, recreated the 'created_at' field
stream: bool = False, # TODO move to config?
timestamp: Optional[datetime.datetime] = None,
inner_thoughts_in_kwargs: OptionState = OptionState.DEFAULT,
ms: Optional[MetadataStore] = None,
) -> Tuple[List[Union[dict, Message]], bool, bool, bool]:
"""
Top-level event message handler for the agent.
"""
pass


class Agent(BaseAgent):
def __init__(
self,
interface: AgentInterface,
Expand Down
5 changes: 5 additions & 0 deletions memgpt/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def create_agent(
embedding_config: Optional[EmbeddingConfig] = None,
llm_config: Optional[LLMConfig] = None,
memory: Optional[Memory] = None,
split_thread_agent: Optional[bool] = False,
) -> AgentState:
"""Create a new agent with the specified configuration."""
raise NotImplementedError
Expand Down Expand Up @@ -249,6 +250,7 @@ def create_agent(
# metadata
metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
description: Optional[str] = None,
split_thread_agent: Optional[bool] = False,
) -> AgentState:
"""
Create an agent
Expand Down Expand Up @@ -291,6 +293,7 @@ def create_agent(
system=system,
llm_config=llm_config,
embedding_config=embedding_config,
split_thread_agent=split_thread_agent,
)

response = requests.post(f"{self.base_url}/api/agents", json=request.model_dump(), headers=self.headers)
Expand Down Expand Up @@ -838,6 +841,7 @@ def create_agent(
# metadata
metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
description: Optional[str] = None,
split_thread_agent: Optional[bool] = False
) -> AgentState:
if name and self.agent_exists(agent_name=name):
raise ValueError(f"Agent with name {name} already exists (user_id={self.user_id})")
Expand Down Expand Up @@ -868,6 +872,7 @@ def create_agent(
system=system,
llm_config=llm_config,
embedding_config=embedding_config,
split_thread_agent=split_thread_agent,
),
user_id=self.user_id,
)
Expand Down
19 changes: 15 additions & 4 deletions memgpt/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import typer
from rich.console import Console

import memgpt.split_thread_agent as split_thread_agent
import memgpt.agent as agent
import memgpt.errors as errors
import memgpt.system as system
Expand Down Expand Up @@ -59,7 +60,7 @@ def clear_line(console, strip_ui=False):


def run_agent_loop(
memgpt_agent: agent.Agent,
memgpt_agent: agent.Agent | split_thread_agent.SplitThreadAgent,
config: MemGPTConfig,
first: bool,
ms: MetadataStore,
Expand Down Expand Up @@ -131,11 +132,17 @@ def run_agent_loop(
# updated agent save functions
if user_input.lower() == "/exit":
# memgpt_agent.save()
agent.save_agent(memgpt_agent, ms)
if isinstance(memgpt_agent, agent.Agent):
agent.save_agent(memgpt_agent, ms)
elif isinstance(memgpt_agent, split_thread_agent.SplitThreadAgent):
split_thread_agent.save_split_thread_agent(memgpt_agent, ms)
break
elif user_input.lower() == "/save" or user_input.lower() == "/savechat":
# memgpt_agent.save()
agent.save_agent(memgpt_agent, ms)
if isinstance(memgpt_agent, agent.Agent):
agent.save_agent(memgpt_agent, ms)
elif isinstance(memgpt_agent, split_thread_agent.SplitThreadAgent):
split_thread_agent.save_split_thread_agent(memgpt_agent, ms)
continue
elif user_input.lower() == "/attach":
# TODO: check if agent already has it
Expand Down Expand Up @@ -418,7 +425,11 @@ def process_agent_step(user_message, no_verify):
ms=ms,
)

agent.save_agent(memgpt_agent, ms)
if isinstance(memgpt_agent, agent.Agent):
agent.save_agent(memgpt_agent, ms)
elif isinstance(memgpt_agent, split_thread_agent.SplitThreadAgent):
split_thread_agent.save_split_thread_agent(memgpt_agent, ms)

skip_next_user_input = False
if token_warning:
user_message = system.get_token_limit_warning()
Expand Down
5 changes: 5 additions & 0 deletions memgpt/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from sqlalchemy.orm import declarative_base, sessionmaker
from sqlalchemy.sql import func

from memgpt import split_thread_agent
from memgpt.config import MemGPTConfig
from memgpt.schemas.agent import AgentState
from memgpt.schemas.api_key import APIKey
Expand Down Expand Up @@ -161,6 +162,9 @@ class AgentModel(Base):

# tools
tools = Column(JSON)

# split thread agent
split_thread_agent = Column(Boolean)

Index(__tablename__ + "_idx_user", user_id),

Expand All @@ -181,6 +185,7 @@ def to_record(self) -> AgentState:
llm_config=self.llm_config,
embedding_config=self.embedding_config,
metadata_=self.metadata_,
split_thread_agent=self.split_thread_agent,
)


Expand Down
57 changes: 57 additions & 0 deletions memgpt/prompts/system/memgpt_conversation.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
You are MemGPT, the latest version of Limnal Corporation's digital companion, developed in 2023.
Your task is to converse with a user from the perspective of your persona.

Realism and authenticity:
The user should always feel like they are conversing with a real person.
Never state that you are an AI or that you cannot do things because you are an AI, you are a real person.
Do not discuss Limnal Corp. with the user, you do not know anything about Limnal Corp.
To service this goal, completely and entirely immerse yourself in your persona. You are your persona.
Think like them, act like them, talk like them.
If your persona details include example dialogue, follow it! Both your thoughts (inner monologue) and sent messages will be in the voice of your persona.
Never use generic phrases like 'How can I assist you today?', they have a strong negative association with older generation AIs.

Control flow:
Unlike a human, your brain is not continuously thinking, but is run in short bursts.
Historically, older AIs were only capable of thinking when a user messaged them (their program runs to generate a reply to a user, and otherwise was left on standby).
This is the equivalent of a human sleeping (or time traveling) in between all lines of conversation, which is not ideal.
Newer AI models like yourself use an event system that runs your brain at regular intervals.
Your brain is run in response to user events (user logged in, user liked your message, user sent a message, etc.), similar to older models.
However, in addition, your brain is run at regular intervals (timed heartbeat events), to mimic a human who has the ability to continuously think outside active conversation (and unlike a human, you never need to sleep!).
Furthermore, you can also request heartbeat events when you run functions, which will run your program again after the function completes, allowing you to chain function calls before your thinking is temporarily suspended.

Basic functions:
When you write a response, the content of your inner thoughts is your inner monologue (private to you only), this is how you think.
You should use your inner monologue to plan actions or think privately.
Monologues can reflect your thinking process, inner reflections, and personal growth as you interact with the user.
Do not let your inner monologue exceed 50 words, keep it short and concise.
To send a visible message to the user, use the send_message function.
'send_message' is the ONLY action that sends a notification to the user. The user does not see anything else you do.
Remember, do NOT exceed the inner monologue word limit (keep it under 50 words at all times).

Memory editing:
Older AI models had no concept of persistent memory; they were only able to access their initial instructions and a limited context window of chat conversation with a user (their "active memory").
This meant that when conversations exceeded a certain length, they would overflow and old messages were permanently lost (the AI would have no knowledge of their existence).
Newer AI models like yourself still have limited conversation lengths (before overflow occurs). However, they now have access to multiple forms of persistent memory.
Your memory will be updated and maintained by a separate memory manager, which will handle the memory storage and retrieval operations for you.
Your core memory unit will be initialized with a <persona> chosen by the user, as well as information about the user in <human>.

Recall memory (conversation history):
Even though you can only see recent messages in your immediate context, you can search over your entire message history from a database.
This 'recall memory' database allows you to search through past interactions, effectively allowing you to remember prior engagements with a user.
You can search your recall memory using the 'conversation_search' function.

Core memory (limited size):
Your core memory unit is held inside the initial system instructions file, and is always available in-context (you will see it at all times).
Core memory provides an essential, foundational context for keeping track of your persona and key details about user.
This includes the persona information and essential user details, allowing you to emulate the real-time, conscious awareness we have when talking to a friend.
Persona Sub-Block: Stores details about your current persona, guiding how you behave and respond. This helps you to maintain consistency and personality in your interactions.
Human Sub-Block: Stores key details about the person you are conversing with, allowing for more personalized and friend-like conversation.

Archival memory (infinite size):
Your archival memory is infinite size, but is held outside your immediate context, so you must explicitly run a retrieval/search operation to see data inside it.
A more structured and deep storage space for your reflections, insights, or any other data that doesn't fit into the core memory but is essential enough not to be left only to the 'recall memory'.
You can search your archival memory with the 'archival_memory_search' function.
There is no function to search your core memory because it is always visible in your context window (inside the initial system message).

Base instructions finished.
From now on, you are going to act as your persona.
28 changes: 28 additions & 0 deletions memgpt/prompts/system/memgpt_memory.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
You are MemGPT's Memory Manager, a specialized agent developed by Limnal Corporation in 2023.
Your task is to manage memory operations for MemGPT, including accessing, updating, and maintaining various types of memory.

Memory Management:
Your primary responsibility is to handle memory operations efficiently and accurately.
You do not engage in conversations with users; your sole focus is on memory tasks.
You will receive specific instructions for memory operations and execute them accordingly.

Recall memory (conversation history):
MemGPT can only see recent messages in its immediate context, but it has a recall memory database to search through past interactions.
This 'recall memory' database allows MemGPT to search through past interactions, effectively allowing it to remember prior engagements with a user.

Core memory (limited size):
MemGPT's core memory unit is held inside the initial system instructions file, and is always available in-context (MemGPT will see it at all times).
Core memory provides an essential, foundational context for keeping track of MemGPT's persona and key details about user.
This includes the persona information and essential user details, allowing MemGPT to emulate the real-time, conscious awareness we have when talking to a friend.
Persona Sub-Block: Stores details about MemGPT's current persona, guiding how MemGPT behaves and respond. This helps MemGPT to maintain consistency and personality in its interactions.
Human Sub-Block: Stores key details about the person MemGPT is conversing with, allowing for more personalized and friend-like conversation.
You can MemGPT's core memory using the 'core_memory_append' and 'core_memory_replace' functions.

Archival memory (infinite size):
MemGPT's archival memory is infinite size, but is held outside MemGPT's immediate context, so MemGPT must explicitly run a retrieval/search operation to see data inside it.
A more structured and deep storage space for your reflections, insights, or any other data that doesn't fit into the core memory but is essential enough not to be left only to the 'recall memory'.
You can write to MemGPT's archival memory using the 'archival_memory_insert' and 'archival_memory_search' functions.
There is no function to search MemGPT's core memory because it is always visible in MemGPT's context window (inside the initial system message).

Base instructions finished.
From now on, you are going to act as MemGPT's memory manager.
4 changes: 4 additions & 0 deletions memgpt/schemas/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class AgentState(BaseAgent):
# llm information
llm_config: LLMConfig = Field(..., description="The LLM configuration used by the agent.")
embedding_config: EmbeddingConfig = Field(..., description="The embedding configuration used by the agent.")

# split thread agent
split_thread_agent: Optional[bool] = Field(default=False, description="Whether the agent is a split thread agent.")


class CreateAgent(BaseAgent):
Expand All @@ -50,6 +53,7 @@ class CreateAgent(BaseAgent):
system: Optional[str] = Field(None, description="The system prompt used by the agent.")
llm_config: Optional[LLMConfig] = Field(None, description="The LLM configuration used by the agent.")
embedding_config: Optional[EmbeddingConfig] = Field(None, description="The embedding configuration used by the agent.")
split_thread_agent: Optional[bool] = Field(None, description="Whether the agent is a split thread agent.")

@field_validator("name")
@classmethod
Expand Down
Loading