diff --git a/examples/memgpt_rest_client.py b/examples/memgpt_rest_client.py index 52b39d84..e0f810f4 100644 --- a/examples/memgpt_rest_client.py +++ b/examples/memgpt_rest_client.py @@ -1,33 +1,19 @@ import json -from letta import Admin, create_client -from letta.memory import ChatMemory +from letta import create_client +from letta.schemas.memory import ChatMemory """ Make sure you run the Letta server before running this example. ``` -export MEMGPT_SERVER_PASS=your_token letta server ``` """ def main(): - # Create an admin client - admin = Admin(base_url="http://localhost:8283", token="your_token") - - # Create a user + token - create_user_response = admin.create_user() - user_id = create_user_response.user_id - token = create_user_response.api_key - print(f"Created user: {user_id} with token: {token}") - - # List available keys - get_keys_response = admin.get_keys(user_id=user_id) - print(f"User {user_id} has keys: {get_keys_response}") - # Connect to the server as a user - client = create_client(base_url="http://localhost:8283", token=token) + client = create_client(base_url="http://localhost:8283") # Create an agent agent_state = client.create_agent(name="my_agent", memory=ChatMemory(human="My name is Sarah.", persona="I am a friendly AI.")) @@ -42,10 +28,6 @@ def main(): client.delete_agent(agent_id=agent_state.id) print(f"Deleted agent: {agent_state.name} with ID {str(agent_state.id)}") - # Delete user - admin.delete_user(user_id=user_id) - print(f"Deleted user: {user_id} with token: {token}") - if __name__ == "__main__": main() diff --git a/examples/personal_assistant_demo/gmail_unread_polling_listener.py b/examples/personal_assistant_demo/gmail_unread_polling_listener.py index ca13533e..06670f73 100644 --- a/examples/personal_assistant_demo/gmail_unread_polling_listener.py +++ b/examples/personal_assistant_demo/gmail_unread_polling_listener.py @@ -11,6 +11,8 @@ from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError +# NOTE: THIS file it out of date for >=0.5.0 + # If modifying these scopes, delete the file token.json. SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"] TOKEN_PATH = os.path.expanduser("~/.letta/gmail_token.json") diff --git a/letta/__init__.py b/letta/__init__.py index b5ad618a..cc72e4a2 100644 --- a/letta/__init__.py +++ b/letta/__init__.py @@ -1,7 +1,6 @@ __version__ = "0.5.0" # import clients -from letta.client.admin import Admin from letta.client.client import LocalClient, RESTClient, create_client # imports for easier access @@ -13,7 +12,13 @@ from letta.schemas.file import FileMetadata from letta.schemas.job import Job from letta.schemas.letta_message import LettaMessage from letta.schemas.llm_config import LLMConfig -from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySummary +from letta.schemas.memory import ( + ArchivalMemorySummary, + BasicBlockMemory, + ChatMemory, + Memory, + RecallMemorySummary, +) from letta.schemas.message import Message from letta.schemas.openai.chat_completion_response import UsageStatistics from letta.schemas.organization import Organization diff --git a/letta/cli/cli_config.py b/letta/cli/cli_config.py index 43219f83..39ece633 100644 --- a/letta/cli/cli_config.py +++ b/letta/cli/cli_config.py @@ -105,7 +105,7 @@ def add_tool( """Add or update a tool from a Python file.""" from letta.client.client import create_client - client = create_client(base_url=os.getenv("MEMGPT_BASE_URL"), token=os.getenv("MEMGPT_SERVER_PASS")) + client = create_client() # 1. Parse the Python file with open(filename, "r", encoding="utf-8") as file: @@ -145,7 +145,7 @@ def list_tools(): """List all available tools.""" from letta.client.client import create_client - client = create_client(base_url=os.getenv("MEMGPT_BASE_URL"), token=os.getenv("MEMGPT_SERVER_PASS")) + client = create_client() tools = client.list_tools() for tool in tools: diff --git a/letta/client/admin.py b/letta/client/admin.py deleted file mode 100644 index 61f4c15a..00000000 --- a/letta/client/admin.py +++ /dev/null @@ -1,171 +0,0 @@ -from typing import List, Optional - -import requests -from requests import HTTPError - -from letta.functions.functions import parse_source_code -from letta.functions.schema_generator import generate_schema -from letta.schemas.api_key import APIKey, APIKeyCreate -from letta.schemas.organization import Organization, OrganizationCreate -from letta.schemas.user import User, UserCreate - - -class Admin: - """ - Admin client allows admin-level operations on the Letta server. - - Creating users - - Generating user keys - """ - - def __init__( - self, - base_url: str, - token: str, - api_prefix: str = "v1", - ): - self.base_url = base_url - self.api_prefix = api_prefix - self.token = token - self.headers = {"accept": "application/json", "content-type": "application/json", "authorization": f"Bearer {token}"} - - def get_users(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[User]: - params = {} - if cursor: - params["cursor"] = str(cursor) - if limit: - params["limit"] = limit - response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/users", params=params, headers=self.headers) - if response.status_code != 200: - raise HTTPError(response.json()) - return [User(**user) for user in response.json()] - - def create_key(self, user_id: str, key_name: Optional[str] = None) -> APIKey: - request = APIKeyCreate(user_id=user_id, name=key_name) - response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/users/keys", headers=self.headers, json=request.model_dump()) - if response.status_code != 200: - raise HTTPError(response.json()) - return APIKey(**response.json()) - - def get_keys(self, user_id: str) -> List[APIKey]: - params = {"user_id": str(user_id)} - response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/users/keys", params=params, headers=self.headers) - if response.status_code != 200: - raise HTTPError(response.json()) - return [APIKey(**key) for key in response.json()] - - def delete_key(self, api_key: str) -> APIKey: - params = {"api_key": api_key} - response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/users/keys", params=params, headers=self.headers) - if response.status_code != 200: - raise HTTPError(response.json()) - return APIKey(**response.json()) - - def create_user(self, name: Optional[str] = None, org_id: Optional[str] = None) -> User: - request = UserCreate(name=name, org_id=org_id) - response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/users", headers=self.headers, json=request.model_dump()) - if response.status_code != 200: - raise HTTPError(response.json()) - response_json = response.json() - return User(**response_json) - - def delete_user(self, user_id: str) -> User: - params = {"user_id": str(user_id)} - response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/users", params=params, headers=self.headers) - if response.status_code != 200: - raise HTTPError(response.json()) - return User(**response.json()) - - def create_organization(self, name: Optional[str] = None) -> Organization: - request = OrganizationCreate(name=name) - response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/orgs", headers=self.headers, json=request.model_dump()) - if response.status_code != 200: - raise HTTPError(response.json()) - response_json = response.json() - return Organization(**response_json) - - def delete_organization(self, org_id: str) -> Organization: - params = {"org_id": str(org_id)} - response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/orgs", params=params, headers=self.headers) - if response.status_code != 200: - raise HTTPError(response.json()) - return Organization(**response.json()) - - def get_organizations(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Organization]: - params = {} - if cursor: - params["cursor"] = str(cursor) - if limit: - params["limit"] = limit - response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/orgs", params=params, headers=self.headers) - if response.status_code != 200: - raise HTTPError(response.json()) - return [Organization(**org) for org in response.json()] - - def _reset_server(self): - # DANGER: this will delete all users and keys - # clear all state associated with users - # TODO: clear out all agents, presets, etc. - users = self.get_users() - for user in users: - keys = self.get_keys(user.id) - for key in keys: - self.delete_key(key.key) - self.delete_user(user.id) - - # tools - def create_tool( - self, - func, - name: Optional[str] = None, - update: Optional[bool] = True, # TODO: actually use this - tags: Optional[List[str]] = None, - ): - """Create a tool - Args: - func (callable): The function to create a tool for. - tags (Optional[List[str]], optional): Tags for the tool. Defaults to None. - update (bool, optional): Update the tool if it already exists. Defaults to True. - Returns: - Tool object - """ - - # TODO: check if tool already exists - # TODO: how to load modules? - # parse source code/schema - source_code = parse_source_code(func) - json_schema = generate_schema(func, name) - source_type = "python" - json_schema["name"] - - if "memory" in tags: - # special modifications to memory functions - # self.memory -> self.memory.memory, since Agent.memory.memory needs to be modified (not BaseMemory.memory) - source_code = source_code.replace("self.memory", "self.memory.memory") - - # create data - data = {"source_code": source_code, "source_type": source_type, "tags": tags, "json_schema": json_schema} - CreateToolRequest(**data) # validate - - # make REST request - response = requests.post(f"{self.base_url}/{self.api_prefix}/admin/tools", json=data, headers=self.headers) - if response.status_code != 200: - raise ValueError(f"Failed to create tool: {response.text}") - return ToolModel(**response.json()) - - def list_tools(self): - response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/tools", headers=self.headers) - return ListToolsResponse(**response.json()).tools - - def delete_tool(self, name: str): - response = requests.delete(f"{self.base_url}/{self.api_prefix}/admin/tools/{name}", headers=self.headers) - if response.status_code != 200: - raise ValueError(f"Failed to delete tool: {response.text}") - return response.json() - - def get_tool(self, name: str): - response = requests.get(f"{self.base_url}/{self.api_prefix}/admin/tools/{name}", headers=self.headers) - if response.status_code == 404: - return None - elif response.status_code != 200: - raise ValueError(f"Failed to get tool: {response.text}") - return ToolModel(**response.json()) diff --git a/letta/client/client.py b/letta/client/client.py index 79e6da4c..4f17e12a 100644 --- a/letta/client/client.py +++ b/letta/client/client.py @@ -1777,6 +1777,19 @@ class LocalClient(AbstractClient): """ self.server.delete_agent(user_id=self.user_id, agent_id=agent_id) + def get_agent_by_name(self, agent_name: str, user_id: str) -> AgentState: + """ + Get an agent by its name + + Args: + agent_name (str): Name of the agent + + Returns: + agent_state (AgentState): State of the agent + """ + self.interface.clear() + return self.server.get_agent(agent_name=agent_name, user_id=user_id, agent_id=None) + def get_agent(self, agent_id: str) -> AgentState: """ Get an agent's state by its ID. diff --git a/tests/test_client.py b/tests/test_client.py index 8ede7b7f..c9619c62 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -7,7 +7,7 @@ from typing import Union import pytest from dotenv import load_dotenv -from letta import Admin, create_client +from letta import create_client from letta.client.client import LocalClient, RESTClient from letta.constants import DEFAULT_PRESET from letta.schemas.agent import AgentState @@ -31,10 +31,6 @@ client = None test_agent_state_post_message = None -# admin credentials -test_server_token = "test_server_token" - - def run_server(): load_dotenv() @@ -58,7 +54,6 @@ def client(request): server_url = os.getenv("LETTA_SERVER_URL") if server_url is None: # run server in thread - # NOTE: must set MEMGPT_SERVER_PASS enviornment variable server_url = "http://localhost:8283" print("Starting server thread") thread = threading.Thread(target=run_server, daemon=True) @@ -66,10 +61,7 @@ def client(request): time.sleep(5) print("Running client tests with server:", server_url) # create user via admin client - admin = Admin(server_url, test_server_token) - user = admin.create_user() # Adjust as per your client's method - api_key = admin.create_key(user.id) - client = create_client(base_url=server_url, token=api_key.key) # This yields control back to the test function + client = create_client(base_url=server_url, token=None) # This yields control back to the test function else: # use local client (no server) server_url = None @@ -77,12 +69,7 @@ def client(request): client.set_default_llm_config(LLMConfig.default_config("gpt-4")) client.set_default_embedding_config(EmbeddingConfig.default_config(provider="openai")) - try: - yield client - finally: - # cleanup user - if server_url: - admin.delete_user(user.id) + yield client # Fixture for test agent diff --git a/tests/test_tools.py b/tests/test_tools.py index 8fe2c45b..c607aa96 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -7,7 +7,7 @@ from typing import Union import pytest from dotenv import load_dotenv -from letta import Admin, create_client +from letta import create_client from letta.agent import Agent from letta.client.client import LocalClient, RESTClient from letta.constants import DEFAULT_PRESET @@ -25,10 +25,6 @@ test_agent_state_post_message = None test_user_id = uuid.uuid4() -# admin credentials -test_server_token = "test_server_token" - - def run_server(): load_dotenv() @@ -53,33 +49,21 @@ def client(request): server_url = os.getenv("MEMGPT_SERVER_URL") if server_url is None: # run server in thread - # NOTE: must set MEMGPT_SERVER_PASS enviornment variable server_url = "http://localhost:8283" print("Starting server thread") thread = threading.Thread(target=run_server, daemon=True) thread.start() time.sleep(5) print("Running client tests with server:", server_url) - # create user via admin client - admin = Admin(server_url, test_server_token) - user = admin.create_user() # Adjust as per your client's method - api_key = admin.create_key(user.id) else: - # use local client (no server) - assert False, "Local client not implemented" server_url = None + assert False, "Local client not implemented" assert server_url is not None - assert api_key.key is not None - client = create_client(base_url=server_url, token=api_key.key) # This yields control back to the test function + client = create_client(base_url=server_url) # This yields control back to the test function client.set_default_llm_config(LLMConfig.default_config("gpt-4o-mini")) client.set_default_embedding_config(EmbeddingConfig.default_config(provider="openai")) - try: - yield client - finally: - # cleanup user - if server_url: - admin.delete_user(user.id) + yield client # Fixture for test agent @@ -127,37 +111,6 @@ def test_create_tool(client: Union[LocalClient, RESTClient]): response = client.user_message(agent_id=agent_state.id, message="hi") -# TODO: add back once we fix admin client tool creation -# def test_create_agent_tool_admin(admin_client): -# if admin_client is None: -# return -# -# def print_tool(message: str): -# """ -# Args: -# message (str): The message to print. -# -# Returns: -# str: The message that was printed. -# -# """ -# print(message) -# return message -# -# tools = admin_client.list_tools() -# print(f"Original tools {[t.name for t in tools]}") -# -# tool = admin_client.create_tool(print_tool, tags=["extras"]) -# -# tools = admin_client.list_tools() -# assert tool in tools, f"Expected {tool.name} in {[t.name for t in tools]}" -# print(f"Updated tools {[t.name for t in tools]}") -# -# # check tool id -# tool = admin_client.get_tool(tool.name) -# assert tool.user_id is None, f"Expected {tool.user_id} to be None" - - def test_create_agent_tool(client): """Test creation of a agent tool"""