Skip to content

Commit

Permalink
feat: Add MemGPT "Python Client" (cpacker#713)
Browse files Browse the repository at this point in the history
* First commit of memgpt client and some messy test code

* rolled back unnecessary changes to abstract interface; switched client to always use Queueing Interface

* Added missing interface clear() in run_command;  added convenience method for checking if an agent exists, used that in create_agent

* Formatting fixes

* Fixed incorrect naming of get_agent_memory in rest server

* Removed erroneous clear from client save method;  Replaced print statements with appropriate logger calls in server

* Updated readme with client usage instructions

* added tests for Client

* make printing to terminal togglable on queininginterface (should probably refactor this to a logger)

* turn off printing to stdout via interface by default

* allow importing the python client in a similar fashion to openai-python (see https://github.com/openai/openai-python)

* Allowed quickstart on init of client;  updated readme and test_client accordingly

* oops, fixed name of openai_api_key config key

* Fixed small typo

* Fixed broken test by adding memgpt hosted model details to agent config

* silence llamaindex 'LLM is explicitly disabled. Using MockLLM.' on server

* default to openai if user's memgpt directory is empty (first time)

* correct type hint

* updated section on client in readme

* added comment about how MemGPT config != Agent config

* patch unrelated test

* update wording on readme

* patch another unrelated test

* added python client to readme docs

* Changed 'user' to 'human' in example;  Defaulted AgentConfig.model to 'None';  Fixed issue in create_agent (accounting for dict config);  matched test code to example

* Fixed advanced example

* patch test

* patch

---------

Co-authored-by: cpacker <packercharles@gmail.com>
  • Loading branch information
2 people authored and norton120 committed Feb 15, 2024
1 parent 4768a18 commit 68a8603
Show file tree
Hide file tree
Showing 13 changed files with 387 additions and 32 deletions.
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,94 @@ poetry install
```
</details>

## Python integration (for developers)

The fastest way to integrate MemGPT with your own Python projects is through the `MemGPT` client class:
```python
from memgpt import MemGPT

# Create a MemGPT client object (sets up the persistent state)
client = MemGPT(
quickstart="openai",
config={
"openai_api_key": "YOUR_API_KEY"
}
)

# You can set many more parameters, this is just a basic example
agent_id = client.create_agent(
agent_config={
"persona": "sam_pov",
"human": "cs_phd",
}
)

# Now that we have an agent_name identifier, we can send it a message!
# The response will have data from the MemGPT agent
my_message = "Hi MemGPT! How's it going?"
response = client.user_message(agent_id=agent_id, message=my_message)
```

<details>
<summary>
<strong>More in-depth example of using MemGPT Client</strong>
</summary>

```python
from memgpt.config import AgentConfig
from memgpt import MemGPT
from memgpt.cli.cli import QuickstartChoice


client = MemGPT(
# When auto_save is 'True' then the agent(s) will be saved after every
# user message. This may have performance implications, so you
# can otherwise choose when to save explicitly using client.save().
auto_save=True,

# Quickstart will automatically configure MemGPT (without having to run `memgpt configure`
# If you choose 'openai' then you must set the api key (env or in config)
quickstart=QuickstartChoice.memgpt_hosted,

# Allows you to override default config generated by quickstart or `memgpt configure`
config={}
)

# Create an AgentConfig with default persona and human txt
# In this case, assume we wrote a custom persona file "my_persona.txt", located at ~/.memgpt/personas/my_persona.txt
# Same for a custom user file "my_user.txt", located at ~/.memgpt/humans/my_user.txt
agent_config = AgentConfig(
name="CustomAgent",
persona="my_persona",
human="my_user",
)

# Create the agent according to AgentConfig we set up. If an agent with
# the same name already exists it will simply return, unless you set
# throw_if_exists to 'True'
agent_id = client.create_agent(agent_config=agent_config)

# Create a helper that sends a message and prints the assistant response only
def send_message(message: str):
"""
sends a message and prints the assistant output only.
:param message: the message to send
"""
response = client.user_message(agent_id=agent_id, message=message)
for r in response:
# Can also handle other types "function_call", "function_return", "function_message"
if "assistant_message" in r:
print("ASSISTANT:", r["assistant_message"])
elif "thoughts" in r:
print("THOUGHTS:", r["internal_monologue"])

# Send a message and see the response
send_message("Please introduce yourself and tell me about your abilities!")
```

</details>


## Support
For issues and feature requests, please [open a GitHub issue](https://github.com/cpacker/MemGPT/issues) or message us on our `#support` channel on [Discord](https://discord.gg/9GEQrxmVyE).

Expand Down
88 changes: 88 additions & 0 deletions docs/python_client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: Python client
excerpt: Developing using the MemGPT Python client
category: 6580dab16cade8003f996d17
---

The fastest way to integrate MemGPT with your own Python projects is through the `MemGPT` client class:
```python
from memgpt import MemGPT

# Create a MemGPT client object (sets up the persistent state)
client = MemGPT(
quickstart="openai",
config={
"openai_api_key": "YOUR_API_KEY"
}
)

# You can set many more parameters, this is just a basic example
agent_id = client.create_agent(
agent_config={
"persona": "sam_pov",
"user": "cs_phd",
}
)

# Now that we have an agent_name identifier, we can send it a message!
# The response will have data from the MemGPT agent
my_message = "Hi MemGPT! How's it going?"
response = client.user_message(agent_id=agent_id, message=my_message)
```

## More in-depth example of using the MemGPT Python client

```python
from memgpt.config import AgentConfig
from memgpt import MemGPT
from memgpt import constants
from memgpt.cli.cli import QuickstartChoice


client = MemGPT(
# When auto_save is 'True' then the agent(s) will be saved after every
# user message. This may have performance implications, so you
# can otherwise choose when to save explicitly using client.save().
auto_save=True,

# Quickstart will automatically configure MemGPT (without having to run `memgpt configure`
# If you choose 'openai' then you must set the api key (env or in config)
quickstart=QuickstartChoice.memgpt_hosted,

# Allows you to override default config generated by quickstart or `memgpt configure`
config={}
)

# Create an AgentConfig with default persona and human txt
# In this case, assume we wrote a custom persona file "my_persona.txt", located at ~/.memgpt/personas/my_persona.txt
# Same for a custom user file "my_user.txt", located at ~/.memgpt/humans/my_user.txt
agent_config = AgentConfig(
name="CustomAgent",
persona="my_persona",
human="my_user",
preset="memgpt_chat",
model="gpt-4",
)

# Create the agent according to AgentConfig we set up. If an agent with
# the same name already exists it will simply return, unless you set
# throw_if_exists to 'True'
agent_id = client.create_agent(agent_config=agent_config)

# Create a helper that sends a message and prints the assistant response only
def send_message(message: str):
"""
sends a message and prints the assistant output only.
:param message: the message to send
"""
response = client.user_message(agent_id=agent_id, message=message)
for r in response:
# Can also handle other types "function_call", "function_return", "function_message"
if "assistant_message" in r:
print("ASSISTANT:", r["assistant_message"])
elif "thoughts" in r:
print("THOUGHTS:", r["internal_monologue"])

# Send a message and see the response
send_message("Please introduce yourself and tell me about your abilities!")
```
2 changes: 2 additions & 0 deletions memgpt/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
__version__ = "0.2.10"

from memgpt.client.client import Client as MemGPT
8 changes: 8 additions & 0 deletions memgpt/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ class QuickstartChoice(Enum):
memgpt_hosted = "memgpt"


def str_to_quickstart_choice(choice_str: str) -> QuickstartChoice:
try:
return QuickstartChoice[choice_str]
except KeyError:
valid_options = [choice.name for choice in QuickstartChoice]
raise ValueError(f"{choice_str} is not a valid QuickstartChoice. Valid options are: {valid_options}")


def set_config_with_dict(new_config: dict) -> bool:
"""Set the base config using a dict"""
from memgpt.utils import printd
Expand Down
Empty file added memgpt/client/__init__.py
Empty file.
114 changes: 114 additions & 0 deletions memgpt/client/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import os
from typing import Dict, List, Union

from memgpt.cli.cli import QuickstartChoice
from memgpt.cli.cli import set_config_with_dict, quickstart as quickstart_func, str_to_quickstart_choice
from memgpt.config import MemGPTConfig, AgentConfig
from memgpt.persistence_manager import PersistenceManager
from memgpt.server.rest_api.interface import QueuingInterface
from memgpt.server.server import SyncServer


class Client(object):
def __init__(
self,
auto_save: bool = False,
quickstart: Union[QuickstartChoice, str, None] = None,
config: Union[Dict, MemGPTConfig] = None, # not the same thing as AgentConfig
debug: bool = False,
):
"""
Initializes a new instance of Client class.
:param auto_save: indicates whether to automatically save after every message.
:param quickstart: allows running quickstart on client init.
:param config: optional config settings to apply after quickstart
:param debug: indicates whether to display debug messages.
"""
self.user_id = "null"
self.auto_save = auto_save

# make sure everything is set up properly
MemGPTConfig.create_config_dir()

# If this is the first ever start, do basic initialization
if not MemGPTConfig.exists() and config is None and quickstart is None:
# Default to openai
print("Detecting uninitialized MemGPT, defaulting to quickstart == openai")
quickstart = "openai"

if quickstart:
# api key passed in config has priority over env var
if isinstance(config, dict) and "openai_api_key" in config:
openai_key = config["openai_api_key"]
else:
openai_key = os.environ.get("OPENAI_API_KEY", None)

# throw an error if we can't resolve the key
if openai_key:
os.environ["OPENAI_API_KEY"] = openai_key
elif quickstart == QuickstartChoice.openai or quickstart == "openai":
raise ValueError("Please set OPENAI_API_KEY or pass 'openai_api_key' in config dict")

if isinstance(quickstart, str):
quickstart = str_to_quickstart_choice(quickstart)
quickstart_func(backend=quickstart, debug=debug)

if config is not None:
set_config_with_dict(config)

self.interface = QueuingInterface(debug=debug)
self.server = SyncServer(default_interface=self.interface)

def list_agents(self):
self.interface.clear()
return self.server.list_agents(user_id=self.user_id)

def agent_exists(self, agent_id: str) -> bool:
existing = self.list_agents()
return agent_id in existing["agent_names"]

def create_agent(
self,
agent_config: Union[Dict, AgentConfig],
persistence_manager: Union[PersistenceManager, None] = None,
throw_if_exists: bool = False,
) -> str:
if isinstance(agent_config, dict):
agent_name = agent_config.get("name")
else:
agent_name = agent_config.name

if not self.agent_exists(agent_id=agent_name):
self.interface.clear()
return self.server.create_agent(user_id=self.user_id, agent_config=agent_config, persistence_manager=persistence_manager)

if throw_if_exists:
raise ValueError(f"Agent {agent_config.name} already exists")

return agent_config.name

def get_agent_config(self, agent_id: str) -> Dict:
self.interface.clear()
return self.server.get_agent_config(user_id=self.user_id, agent_id=agent_id)

def get_agent_memory(self, agent_id: str) -> Dict:
self.interface.clear()
return self.server.get_agent_memory(user_id=self.user_id, agent_id=agent_id)

def update_agent_core_memory(self, agent_id: str, new_memory_contents: Dict) -> Dict:
self.interface.clear()
return self.server.update_agent_core_memory(user_id=self.user_id, agent_id=agent_id, new_memory_contents=new_memory_contents)

def user_message(self, agent_id: str, message: str) -> List[Dict]:
self.interface.clear()
self.server.user_message(user_id=self.user_id, agent_id=agent_id, message=message)
if self.auto_save:
self.save()
return self.interface.to_list()

def run_command(self, agent_id: str, command: str) -> Union[str, None]:
self.interface.clear()
return self.server.run_command(user_id=self.user_id, agent_id=agent_id, command=command)

def save(self):
self.server.save_agents()
2 changes: 1 addition & 1 deletion memgpt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def __init__(
persona,
human,
# model info
model,
model=None,
model_endpoint_type=None,
model_endpoint=None,
model_wrapper=None,
Expand Down
12 changes: 8 additions & 4 deletions memgpt/server/rest_api/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
class QueuingInterface(AgentInterface):
"""Messages are queued inside an internal buffer and manually flushed"""

def __init__(self):
def __init__(self, debug=True):
self.buffer = queue.Queue()
self.debug = debug

def to_list(self):
"""Convert queue to a list (empties it out at the same time)"""
Expand Down Expand Up @@ -48,17 +49,20 @@ def user_message(self, msg: str):

def internal_monologue(self, msg: str) -> None:
"""Handle the agent's internal monologue"""
print(msg)
if self.debug:
print(msg)
self.buffer.put({"internal_monologue": msg})

def assistant_message(self, msg: str) -> None:
"""Handle the agent sending a message"""
print(msg)
if self.debug:
print(msg)
self.buffer.put({"assistant_message": msg})

def function_message(self, msg: str) -> None:
"""Handle the agent calling a function"""
print(msg)
if self.debug:
print(msg)

if msg.startswith("Running "):
msg = msg.replace("Running ", "")
Expand Down
2 changes: 1 addition & 1 deletion memgpt/server/rest_api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def get_agent_memory(user_id: str, agent_id: str):


@app.put("/agents/memory")
def get_agent_memory(body: CoreMemory):
def put_agent_memory(body: CoreMemory):
interface.clear()
new_memory_contents = {"persona": body.persona, "human": body.human}
return server.update_agent_core_memory(user_id=body.user_id, agent_id=body.agent_id, new_memory_contents=new_memory_contents)
Expand Down
Loading

0 comments on commit 68a8603

Please sign in to comment.