diff --git a/.github/workflows/test_ollama.yml b/.github/workflows/test_ollama.yml
index 449979e03e..baccde40b0 100644
--- a/.github/workflows/test_ollama.yml
+++ b/.github/workflows/test_ollama.yml
@@ -13,13 +13,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
-
+
- name: Start Ollama Server
run: |
curl -fsSL https://ollama.com/install.sh | sh
ollama serve &
sleep 10 # wait for server
- ollama pull dolphin2.2-mistral:7b-q6_K
+ ollama pull dolphin2.2-mistral:7b-q6_K
ollama pull mxbai-embed-large
- name: "Setup Python, Poetry and Dependencies"
diff --git a/README.md b/README.md
index 9d2bcb6393..fad4cd4699 100644
--- a/README.md
+++ b/README.md
@@ -23,13 +23,13 @@ You can also use MemGPT to deploy agents as a *service*. You can use a MemGPT se
-## Installation & Setup
+## Installation & Setup
Install MemGPT:
```sh
pip install -U pymemgpt
```
-To use MemGPT with OpenAI, set the environment variable `OPENAI_API_KEY` to your OpenAI key then run:
+To use MemGPT with OpenAI, set the environment variable `OPENAI_API_KEY` to your OpenAI key then run:
```
memgpt quickstart --backend openai
```
@@ -54,20 +54,20 @@ MemGPT provides a developer portal that enables you to easily create, edit, moni
-## Quickstart (Server)
+## Quickstart (Server)
-**Option 1 (Recommended)**: Run with docker compose
+**Option 1 (Recommended)**: Run with docker compose
1. [Install docker on your system](https://docs.docker.com/get-docker/)
2. Clone the repo: `git clone https://github.com/cpacker/MemGPT.git`
3. Copy-paste `.env.example` to `.env` and optionally modify
4. Run `docker compose up`
-5. Go to `memgpt.localhost` in the browser to view the developer portal
+5. Go to `memgpt.localhost` in the browser to view the developer portal
**Option 2:** Run with the CLI:
1. Run `memgpt server`
2. Go to `localhost:8283` in the browser to view the developer portal
-Once the server is running, you can use the [Python client](https://memgpt.readme.io/docs/admin-client) or [REST API](https://memgpt.readme.io/reference/api) to connect to `memgpt.localhost` (if you're running with docker compose) or `localhost:8283` (if you're running with the CLI) to create users, agents, and more. The service requires authentication with a MemGPT admin password; it is the value of `MEMGPT_SERVER_PASS` in `.env`.
+Once the server is running, you can use the [Python client](https://memgpt.readme.io/docs/admin-client) or [REST API](https://memgpt.readme.io/reference/api) to connect to `memgpt.localhost` (if you're running with docker compose) or `localhost:8283` (if you're running with the CLI) to create users, agents, and more. The service requires authentication with a MemGPT admin password; it is the value of `MEMGPT_SERVER_PASS` in `.env`.
## Supported Endpoints & Backends
MemGPT is designed to be model and provider agnostic. The following LLM and embedding endpoints are supported:
@@ -96,7 +96,7 @@ When using MemGPT with open LLMs (such as those downloaded from HuggingFace), th
* **Report Issues or Suggest Features**: Have an issue or a feature request? Please submit them through our [GitHub Issues page](https://github.com/cpacker/MemGPT/issues).
* **Explore the Roadmap**: Curious about future developments? View and comment on our [project roadmap](https://github.com/cpacker/MemGPT/issues/1200).
* **Benchmark the Performance**: Want to benchmark the performance of a model on MemGPT? Follow our [Benchmarking Guidance](#benchmarking-guidance).
-* **Join Community Events**: Stay updated with the [MemGPT event calendar](https://lu.ma/berkeley-llm-meetup) or follow our [Twitter account](https://twitter.com/MemGPT).
+* **Join Community Events**: Stay updated with the [MemGPT event calendar](https://lu.ma/berkeley-llm-meetup) or follow our [Twitter account](https://twitter.com/MemGPT).
## Benchmarking Guidance
@@ -104,4 +104,3 @@ To evaluate the performance of a model on MemGPT, simply configure the appropria
## Legal notices
By using MemGPT and related MemGPT services (such as the MemGPT endpoint or hosted service), you agree to our [privacy policy](https://github.com/cpacker/MemGPT/tree/main/PRIVACY.md) and [terms of service](https://github.com/cpacker/MemGPT/tree/main/TERMS.md).
-
diff --git a/examples/resend_example/README.md b/examples/resend_example/README.md
index 2adfc18bdc..2709efc676 100644
--- a/examples/resend_example/README.md
+++ b/examples/resend_example/README.md
@@ -90,4 +90,3 @@ memgpt run --preset resend_preset --persona sam_pov --human cs_phd --stream
Waiting in our inbox:
-
diff --git a/memgpt/cli/cli_config.py b/memgpt/cli/cli_config.py
index 55d74ad8f4..188a7b307c 100644
--- a/memgpt/cli/cli_config.py
+++ b/memgpt/cli/cli_config.py
@@ -1101,9 +1101,12 @@ class ListChoice(str, Enum):
@app.command()
def list(arg: Annotated[ListChoice, typer.Argument]):
+ from memgpt.client.client import create_client
+
config = MemGPTConfig.load()
ms = MetadataStore(config)
user_id = uuid.UUID(config.anon_clientid)
+ client = create_client(base_url=os.getenv("MEMGPT_BASE_URL"), token=os.getenv("MEMGPT_SERVER_PASS"))
table = ColorTable(theme=Themes.OCEAN)
if arg == ListChoice.agents:
"""List all agents"""
@@ -1130,7 +1133,7 @@ def list(arg: Annotated[ListChoice, typer.Argument]):
elif arg == ListChoice.humans:
"""List all humans"""
table.field_names = ["Name", "Text"]
- for human in ms.list_humans(user_id=user_id):
+ for human in client.list_humans(user_id=user_id):
table.add_row([human.name, human.text.replace("\n", "")[:100]])
print(table)
elif arg == ListChoice.personas:
@@ -1194,9 +1197,12 @@ def add(
filename: Annotated[Optional[str], typer.Option("-f", help="Specify filename")] = None,
):
"""Add a person/human"""
+ from memgpt.client.client import create_client
+
config = MemGPTConfig.load()
user_id = uuid.UUID(config.anon_clientid)
ms = MetadataStore(config)
+ client = create_client(base_url=os.getenv("MEMGPT_BASE_URL"), token=os.getenv("MEMGPT_SERVER_PASS"))
if filename: # read from file
assert text is None, "Cannot specify both text and filename"
with open(filename, "r", encoding="utf-8") as f:
@@ -1214,16 +1220,16 @@ def add(
ms.add_persona(persona)
elif option == "human":
- human = ms.get_human(name=name, user_id=user_id)
+ human = client.get_human(name=name, user_id=user_id)
if human:
# config if user wants to overwrite
if not questionary.confirm(f"Human {name} already exists. Overwrite?").ask():
return
human.text = text
- ms.update_human(human)
+ client.update_human(human)
else:
human = HumanModel(name=name, text=text, user_id=user_id)
- ms.add_human(HumanModel(name=name, text=text, user_id=user_id))
+ client.add_human(HumanModel(name=name, text=text, user_id=user_id))
elif option == "preset":
assert filename, "Must specify filename for preset"
create_preset_from_file(filename, name, user_id, ms)
@@ -1234,9 +1240,11 @@ def add(
@app.command()
def delete(option: str, name: str):
"""Delete a source from the archival memory."""
+ from memgpt.client.client import create_client
config = MemGPTConfig.load()
user_id = uuid.UUID(config.anon_clientid)
+ client = create_client(base_url=os.getenv("MEMGPT_BASE_URL"), token=os.getenv("MEMGPT_API_KEY"))
ms = MetadataStore(config)
assert ms.get_user(user_id=user_id), f"User {user_id} does not exist"
@@ -1273,9 +1281,9 @@ def delete(option: str, name: str):
ms.delete_agent(agent_id=agent.id)
elif option == "human":
- human = ms.get_human(name=name, user_id=user_id)
+ human = client.get_human(name=name, user_id=user_id)
assert human is not None, f"Human {name} does not exist"
- ms.delete_human(name=name, user_id=user_id)
+ client.delete_human(name=name, user_id=user_id)
elif option == "persona":
persona = ms.get_persona(name=name, user_id=user_id)
assert persona is not None, f"Persona {name} does not exist"
diff --git a/memgpt/client/client.py b/memgpt/client/client.py
index bd452363e2..f7ffc912a3 100644
--- a/memgpt/client/client.py
+++ b/memgpt/client/client.py
@@ -630,6 +630,8 @@ def __init__(
self.interface = QueuingInterface(debug=debug)
self.server = SyncServer(default_interface=self.interface)
+ # agents
+
def list_agents(self):
self.interface.clear()
return self.server.list_agents(user_id=self.user_id)
@@ -665,6 +667,14 @@ def create_agent(
)
return agent_state
+ def delete_agent(self, agent_id: uuid.UUID):
+ self.server.delete_agent(user_id=self.user_id, agent_id=agent_id)
+
+ def get_agent_config(self, agent_id: str) -> AgentState:
+ self.interface.clear()
+ return self.server.get_agent_config(user_id=self.user_id, agent_id=agent_id)
+
+ # presets
def create_preset(self, preset: Preset) -> Preset:
if preset.user_id is None:
preset.user_id = self.user_id
@@ -677,9 +687,7 @@ def delete_preset(self, preset_id: uuid.UUID):
def list_presets(self) -> List[PresetModel]:
return self.server.list_presets(user_id=self.user_id)
- def get_agent_config(self, agent_id: str) -> AgentState:
- self.interface.clear()
- return self.server.get_agent_config(user_id=self.user_id, agent_id=agent_id)
+ # memory
def get_agent_memory(self, agent_id: str) -> Dict:
self.interface.clear()
@@ -689,6 +697,8 @@ def update_agent_core_memory(self, agent_id: str, new_memory_contents: 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)
+ # agent interactions
+
def user_message(self, agent_id: str, message: str) -> Union[List[Dict], Tuple[List[Dict], int]]:
self.interface.clear()
self.server.user_message(user_id=self.user_id, agent_id=agent_id, message=message)
@@ -704,17 +714,7 @@ def run_command(self, agent_id: str, command: str) -> Union[str, None]:
def save(self):
self.server.save_agents()
- def load_data(self, connector: DataConnector, source_name: str):
- self.server.load_data(user_id=self.user_id, connector=connector, source_name=source_name)
-
- def create_source(self, name: str):
- self.server.create_source(user_id=self.user_id, name=name)
-
- def attach_source_to_agent(self, source_id: uuid.UUID, agent_id: uuid.UUID):
- self.server.attach_source_to_agent(user_id=self.user_id, source_id=source_id, agent_id=agent_id)
-
- def delete_agent(self, agent_id: uuid.UUID):
- self.server.delete_agent(user_id=self.user_id, agent_id=agent_id)
+ # archival memory
def get_agent_archival_memory(
self, agent_id: uuid.UUID, before: Optional[uuid.UUID] = None, after: Optional[uuid.UUID] = None, limit: Optional[int] = 1000
@@ -727,3 +727,35 @@ def get_agent_archival_memory(
limit=limit,
)
return archival_json_records
+
+ # messages
+
+ # humans / personas
+
+ def list_humans(self, user_id: uuid.UUID):
+ return self.server.list_humans(user_id=user_id if user_id else self.user_id)
+
+ def get_human(self, name: str, user_id: uuid.UUID):
+ return self.server.get_human(name=name, user_id=user_id)
+
+ def add_human(self, human: HumanModel):
+ return self.server.add_human(human=human)
+
+ def update_human(self, human: HumanModel):
+ return self.server.update_human(human=human)
+
+ def delete_human(self, name: str, user_id: uuid.UUID):
+ return self.server.delete_human(name, user_id)
+
+ # tools
+
+ # data sources
+
+ def load_data(self, connector: DataConnector, source_name: str):
+ self.server.load_data(user_id=self.user_id, connector=connector, source_name=source_name)
+
+ def create_source(self, name: str):
+ self.server.create_source(user_id=self.user_id, name=name)
+
+ def attach_source_to_agent(self, source_id: uuid.UUID, agent_id: uuid.UUID):
+ self.server.attach_source_to_agent(user_id=self.user_id, source_id=source_id, agent_id=agent_id)
diff --git a/memgpt/server/server.py b/memgpt/server/server.py
index 01cb65655b..c214848c27 100644
--- a/memgpt/server/server.py
+++ b/memgpt/server/server.py
@@ -40,6 +40,7 @@
from memgpt.metadata import MetadataStore
from memgpt.models.pydantic_models import (
DocumentModel,
+ HumanModel,
PassageModel,
PresetModel,
SourceModel,
@@ -897,6 +898,21 @@ def list_agents(
"agents": agents_states_dicts,
}
+ def list_humans(self, user_id: uuid.UUID):
+ return self.ms.list_humans(user_id=user_id)
+
+ def get_human(self, name: str, user_id: uuid.UUID):
+ return self.ms.get_human(name=name, user_id=user_id)
+
+ def add_human(self, human: HumanModel):
+ return self.ms.add_human(human=human)
+
+ def update_human(self, human: HumanModel):
+ return self.ms.update_human(human=human)
+
+ def delete_human(self, name: str, user_id: uuid.UUID):
+ return self.ms.delete_human(name, user_id)
+
def get_agent(self, user_id: uuid.UUID, agent_id: uuid.UUID):
"""Get the agent state"""
return self.ms.get_agent(agent_id=agent_id, user_id=user_id)
diff --git a/tests/test_new_cli.py b/tests/test_new_cli.py
new file mode 100644
index 0000000000..dafe062dc0
--- /dev/null
+++ b/tests/test_new_cli.py
@@ -0,0 +1,102 @@
+import os
+import random
+import string
+import unittest.mock
+
+import pytest
+
+from memgpt.cli.cli_config import add, delete, list
+
+
+@pytest.mark.skip(reason="This is a helper function.")
+def generate_random_string(length):
+ characters = string.ascii_letters + string.digits
+ random_string = "".join(random.choices(characters, k=length))
+ return random_string
+
+
+@pytest.mark.skip(reason="Ensures LocalClient is used during testing.")
+def unset_env_variables():
+ server_url = os.environ.pop("MEMGPT_BASE_URL", None)
+ token = os.environ.pop("MEMGPT_SERVER_PASS", None)
+ return server_url, token
+
+
+@pytest.mark.skip(reason="Set env variables back to values before test.")
+def reset_env_variables(server_url, token):
+ if server_url is not None:
+ os.environ["MEMGPT_BASE_URL"] = server_url
+ if token is not None:
+ os.environ["MEMGPT_SERVER_PASS"] = token
+
+
+def test_crud_human(capsys):
+
+ server_url, token = unset_env_variables()
+
+ # Initialize values that won't interfere with existing ones
+ human_1 = generate_random_string(16)
+ text_1 = generate_random_string(32)
+ human_2 = generate_random_string(16)
+ text_2 = generate_random_string(32)
+ text_3 = generate_random_string(32)
+
+ # Add inital human
+ add("human", human_1, text_1)
+
+ # Expect inital human to be listed
+ list("humans")
+ captured = capsys.readouterr()
+ output = captured.out[captured.out.find(human_1) :]
+
+ assert human_1 in output
+ assert text_1 in output
+
+ # Add second human
+ add("human", human_2, text_2)
+
+ # Expect to see second human
+ list("humans")
+ captured = capsys.readouterr()
+ output = captured.out[captured.out.find(human_1) :]
+
+ assert human_1 in output
+ assert text_1 in output
+ assert human_2 in output
+ assert text_2 in output
+
+ with unittest.mock.patch("questionary.confirm") as mock_confirm:
+ mock_confirm.return_value.ask.return_value = True
+
+ # Update second human
+ add("human", human_2, text_3)
+
+ # Expect to see update text
+ list("humans")
+ captured = capsys.readouterr()
+ output = captured.out[captured.out.find(human_1) :]
+
+ assert human_1 in output
+ assert text_1 in output
+ assert human_2 in output
+ assert output.count(human_2) == 1
+ assert text_3 in output
+ assert text_2 not in output
+
+ # Delete second human
+ delete("human", human_2)
+
+ # Expect second human to be deleted
+ list("humans")
+ captured = capsys.readouterr()
+ output = captured.out[captured.out.find(human_1) :]
+
+ assert human_1 in output
+ assert text_1 in output
+ assert human_2 not in output
+ assert text_2 not in output
+
+ # Clean up
+ delete("human", human_1)
+
+ reset_env_variables(server_url, token)