fix: Improvements Jan 22 (#734)

Co-authored-by: Charles Packer <packercharles@gmail.com>
Co-authored-by: dboyliao <qmalliao@gmail.com>
Co-authored-by: Shubham Naik <shub@memgpt.ai>
Co-authored-by: Shubham Naik <shubham.naik10@gmail.com>
Co-authored-by: Caren Thomas <caren@letta.com>
Co-authored-by: Sarah Wooders <sarahwooders@gmail.com>
Co-authored-by: Nuno Rocha <nunuroxa@gmail.com>
Co-authored-by: Theo Conrads <theo.conrads@ella-lab.io>
Co-authored-by: Jyotirmaya Mahanta <jyotirmaya.mahanta@gmail.com>
Co-authored-by: Stephan Fitzpatrick <knowsuchagency@gmail.com>
Co-authored-by: Stephan Fitzpatrick <stephan@knowsuchagency.com>
Co-authored-by: mlong93 <35275280+mlong93@users.noreply.github.com>
Co-authored-by: Mindy Long <mindy@letta.com>
Co-authored-by: Krishnakumar R (KK) <65895020+kk-src@users.noreply.github.com>
This commit is contained in:
Matthew Zhou
2025-01-22 14:57:35 -10:00
committed by GitHub
parent ab955a46f3
commit 6a9dd05566
12 changed files with 134 additions and 32 deletions

View File

@@ -8,7 +8,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v3
@@ -17,7 +17,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -38,4 +38,3 @@ jobs:
letta/letta:latest
memgpt/letta:${{ env.CURRENT_VERSION }}
memgpt/letta:latest

19
.github/workflows/letta-code-sync.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Sync Code
on:
push:
branches:
- main
jobs:
notify:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '[sync-skip]') }}
steps:
- name: Trigger repository_dispatch
run: |
curl -X POST \
-H "Authorization: token ${{ secrets.SYNC_PAT }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/letta-ai/letta-cloud/dispatches \
-d '{"event_type":"oss-update"}'

View File

@@ -19,18 +19,46 @@ Now, let's bring your new playground to your local machine.
git clone https://github.com/your-username/letta.git
```
### 🧩 Install Dependencies
### 🧩 Install dependencies & configure environment
#### Install poetry and dependencies
First, install Poetry using [the official instructions here](https://python-poetry.org/docs/#installation).
Once Poetry is installed, navigate to the Letta directory and install the Letta project with Poetry:
Once Poetry is installed, navigate to the letta directory and install the Letta project with Poetry:
```shell
cd Letta
cd letta
poetry shell
poetry install --all-extras
```
#### Setup PostgreSQL environment (optional)
If you are planning to develop letta connected to PostgreSQL database, you need to take the following actions.
If you are not planning to use PostgreSQL database, you can skip to the step which talks about [running letta](#running-letta-with-poetry).
Assuming you have a running PostgreSQL instance, first you need to create the user, database and ensure the pgvector
extension is ready. Here are sample steps for a case where user and database name is letta and assumes no password is set:
```shell
createuser letta
createdb letta --owner=letta
psql -d letta -c 'CREATE EXTENSION IF NOT EXISTS vector'
```
Setup the environment variable to tell letta code to contact PostgreSQL database:
```shell
export LETTA_PG_URI="postgresql://${POSTGRES_USER:-letta}:${POSTGRES_PASSWORD:-letta}@localhost:5432/${POSTGRES_DB:-letta}"
```
After this you need to prep the database with initial content. You can use alembic upgrade to populate the initial
contents from template test data. Please ensure to activate poetry environment using `poetry shell`.
```shell
alembic upgrade head
```
#### Running letta with poetry
Now when you want to use `letta`, make sure you first activate the `poetry` environment using poetry shell:
```shell
$ poetry shell
(pyletta-py3.12) $ letta run

View File

@@ -9,7 +9,7 @@
<div align="center">
<h1>Letta (previously MemGPT)</h1>
**☄️ New release: Letta Agent Development Environment (_read more [here](#-access-the-letta-ade-agent-development-environment)_) ☄️**
**☄️ New release: Letta Agent Development Environment (_read more [here](#-access-the-ade-agent-development-environment)_) ☄️**
<p align="center">
<picture>
@@ -23,7 +23,7 @@
<h3>
[Homepage](https://letta.com) // [Documentation](https://docs.letta.com) // [ADE](https://app.letta.com) // [Letta Cloud](https://forms.letta.com/early-access)
[Homepage](https://letta.com) // [Documentation](https://docs.letta.com) // [ADE](https://docs.letta.com/agent-development-environment) // [Letta Cloud](https://forms.letta.com/early-access)
</h3>
@@ -80,12 +80,12 @@ docker run \
Once the Letta server is running, you can access it via port `8283` (e.g. sending REST API requests to `http://localhost:8283/v1`). You can also connect your server to the Letta ADE to access and manage your agents in a web interface.
### 👾 Access the [Letta ADE (Agent Development Environment)](https://app.letta.com)
### 👾 Access the ADE (Agent Development Environment)
> [!NOTE]
> The Letta ADE is a graphical user interface for creating, deploying, interacting and observing with your Letta agents.
>
> For example, if you're running a Letta server to power an end-user application (such as a customer support chatbot), you can use the ADE to test, debug, and observe the agents in your server. You can also use the ADE as a general chat interface to interact with your Letta agents.
> For a guided tour of the ADE, watch our [ADE walkthrough on YouTube](https://www.youtube.com/watch?v=OzSCFR0Lp5s), or read our [blog post](https://www.letta.com/blog/introducing-the-agent-development-environment) and [developer docs](https://docs.letta.com/agent-development-environment).
The Letta ADE is a graphical user interface for creating, deploying, interacting and observing with your Letta agents. For example, if you're running a Letta server to power an end-user application (such as a customer support chatbot), you can use the ADE to test, debug, and observe the agents in your server. You can also use the ADE as a general chat interface to interact with your Letta agents.
<p align="center">
<picture>

View File

@@ -1,4 +1,4 @@
__version__ = "0.6.9"
__version__ = "0.6.13"
# import clients

View File

@@ -442,7 +442,8 @@ class RESTClient(AbstractClient):
def __init__(
self,
base_url: str,
token: str,
token: Optional[str] = None,
password: Optional[str] = None,
api_prefix: str = "v1",
debug: bool = False,
default_llm_config: Optional[LLMConfig] = None,
@@ -458,11 +459,18 @@ class RESTClient(AbstractClient):
default_llm_config (Optional[LLMConfig]): The default LLM configuration.
default_embedding_config (Optional[EmbeddingConfig]): The default embedding configuration.
headers (Optional[Dict]): The additional headers for the REST API.
token (Optional[str]): The token for the REST API when using managed letta service.
password (Optional[str]): The password for the REST API when using self hosted letta service.
"""
super().__init__(debug=debug)
self.base_url = base_url
self.api_prefix = api_prefix
self.headers = {"accept": "application/json", "authorization": f"Bearer {token}"}
if token:
self.headers = {"accept": "application/json", "Authorization": f"Bearer {token}"}
elif password:
self.headers = {"accept": "application/json", "X-BARE-PASSWORD": f"password {password}"}
else:
self.headers = {"accept": "application/json"}
if headers:
self.headers.update(headers)
self._default_llm_config = default_llm_config

View File

@@ -0,0 +1,30 @@
from typing import TYPE_CHECKING, Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from letta.orm.sqlalchemy_base import SqlalchemyBase
if TYPE_CHECKING:
from letta.orm.job import Job
class JobUsageStatistics(SqlalchemyBase):
"""Tracks usage statistics for jobs, with future support for per-step tracking."""
__tablename__ = "job_usage_statistics"
id: Mapped[int] = mapped_column(primary_key=True, doc="Unique identifier for the usage statistics entry")
job_id: Mapped[str] = mapped_column(
ForeignKey("jobs.id", ondelete="CASCADE"), nullable=False, doc="ID of the job these statistics belong to"
)
step_id: Mapped[Optional[str]] = mapped_column(
nullable=True, doc="ID of the specific step within the job (for future per-step tracking)"
)
completion_tokens: Mapped[int] = mapped_column(default=0, doc="Number of tokens generated by the agent")
prompt_tokens: Mapped[int] = mapped_column(default=0, doc="Number of tokens in the prompt")
total_tokens: Mapped[int] = mapped_column(default=0, doc="Total number of tokens processed by the agent")
step_count: Mapped[int] = mapped_column(default=0, doc="Number of steps taken by the agent")
# Relationship back to the job
job: Mapped["Job"] = relationship("Job", back_populates="usage_statistics")

View File

@@ -560,6 +560,9 @@ async def process_message_background(
)
server.job_manager.update_job_by_id(job_id=job_id, job_update=job_update, actor=actor)
# Add job usage statistics
server.job_manager.add_job_usage(job_id=job_id, usage=result.usage, actor=actor)
except Exception as e:
# Update job status to failed
job_update = JobUpdate(

22
poetry.lock generated
View File

@@ -4507,18 +4507,15 @@ cli = ["click (>=5.0)"]
[[package]]
name = "python-multipart"
version = "0.0.9"
version = "0.0.19"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
{file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
{file = "python_multipart-0.0.19-py3-none-any.whl", hash = "sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d"},
{file = "python_multipart-0.0.19.tar.gz", hash = "sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc"},
]
[package.extras]
dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"]
[[package]]
name = "pytz"
version = "2023.4"
@@ -5172,19 +5169,18 @@ tornado = ["tornado (>=6)"]
[[package]]
name = "setuptools"
version = "68.2.2"
version = "70.3.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
{file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
{file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"},
{file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "shellingham"
@@ -6293,4 +6289,4 @@ tests = ["wikipedia"]
[metadata]
lock-version = "2.0"
python-versions = "<3.14,>=3.10"
content-hash = "2f552617ff233fe8b07bdec4dc1679935df30030046984962b69ebe625717815"
content-hash = "bfb2713daba35ef8c78ee1b568c35afe3f1d0c247ea58a58a079e1fb4d984c10"

View File

@@ -1,6 +1,7 @@
[tool.poetry]
name = "letta"
version = "0.6.9"
version = "0.6.13"
packages = [
{include = "letta"},
]
@@ -21,7 +22,7 @@ questionary = "^2.0.1"
pytz = "^2023.3.post1"
tqdm = "^4.66.1"
black = {extras = ["jupyter"], version = "^24.2.0"}
setuptools = "^68.2.2"
setuptools = "^70"
datasets = { version = "^2.14.6", optional = true}
prettytable = "^3.9.0"
pgvector = { version = "^0.2.3", optional = true }
@@ -47,7 +48,7 @@ qdrant-client = {version="^1.9.1", optional = true}
python-box = "^7.1.1"
sqlmodel = "^0.0.16"
autoflake = {version = "^2.3.0", optional = true}
python-multipart = "^0.0.9"
python-multipart = "^0.0.19"
sqlalchemy-utils = "^0.41.2"
pytest-order = {version = "^1.2.0", optional = true}
pytest-asyncio = {version = "^0.23.2", optional = true}
@@ -56,7 +57,7 @@ httpx-sse = "^0.4.0"
isort = { version = "^5.13.2", optional = true }
docker = {version = "^7.1.0", optional = true}
nltk = "^3.8.1"
jinja2 = "^3.1.4"
jinja2 = "^3.1.5"
locust = {version = "^2.31.5", optional = true}
wikipedia = {version = "^1.4.0", optional = true}
composio-langchain = "^0.6.15"
@@ -79,6 +80,7 @@ e2b-code-interpreter = {version = "^1.0.3", optional = true}
anthropic = "^0.43.0"
letta_client = "^0.1.16"
[tool.poetry.extras]
postgres = ["pgvector", "pg8000", "psycopg2-binary", "psycopg2"]
dev = ["pytest", "pytest-asyncio", "pexpect", "black", "pre-commit", "datasets", "pyright", "pytest-order", "autoflake", "isort", "locust"]

View File

@@ -195,6 +195,14 @@ def composio_gmail_get_profile_tool(test_user):
yield tool
@pytest.fixture
def composio_gmail_get_profile_tool(test_user):
tool_manager = ToolManager()
tool_create = ToolCreate.from_composio(action_name="GMAIL_GET_PROFILE")
tool = tool_manager.create_or_update_tool(pydantic_tool=Tool(**tool_create.model_dump()), actor=test_user)
yield tool
@pytest.fixture
def clear_core_memory_tool(test_user):
def clear_memory(agent_state: "AgentState"):
@@ -418,6 +426,14 @@ def test_local_sandbox_e2e_composio_star_github_without_setting_db_env_vars(
assert result.func_return["details"] == "Action executed successfully"
@pytest.mark.local_sandbox
def test_local_sandbox_e2e_composio_star_github_without_setting_db_env_vars(
mock_e2b_api_key_none, check_composio_key_set, composio_github_star_tool, test_user
):
result = ToolExecutionSandbox(composio_github_star_tool.name, {"owner": "letta-ai", "repo": "letta"}, user=test_user).run()
assert result.func_return["details"] == "Action executed successfully"
@pytest.mark.local_sandbox
def test_local_sandbox_external_codebase(mock_e2b_api_key_none, custom_test_sandbox_config, external_codebase_tool, test_user):
# Set the args

View File

@@ -203,4 +203,5 @@ def test_composio_tool_schema_generation(openai_model: str, structured_output: b
print(f"Successfully called OpenAI using schema {schema} generated from {action_name}\n\n")
except:
print(f"Failed to call OpenAI using schema {schema} generated from {action_name}\n\n")
raise