chore: bump version and fix deps

This commit is contained in:
Sarah Wooders
2025-02-20 08:18:37 -08:00
9 changed files with 267 additions and 75 deletions

View File

@@ -1,19 +0,0 @@
name: Notify Letta Cloud
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

@@ -5,7 +5,7 @@ repos:
- id: check-yaml
exclude: 'docs/.*|tests/data/.*|configs/.*|helm/.*'
- id: end-of-file-fixer
exclude: 'docs/.*|tests/data/.*|letta/server/static_files/.*|.*/.*\.(scss|css|html)'
exclude: 'docs/.*|tests/data/.*|letta/server/static_files/.*'
- id: trailing-whitespace
exclude: 'docs/.*|tests/data/.*|letta/server/static_files/.*'
@@ -16,15 +16,18 @@ repos:
entry: bash -c '[ -d "apps/core" ] && cd apps/core; poetry run autoflake --remove-all-unused-imports --remove-unused-variables --in-place --recursive --ignore-init-module-imports .'
language: system
types: [python]
args: ['--remove-all-unused-imports', '--remove-unused-variables', '--in-place', '--recursive', '--ignore-init-module-imports']
- id: isort
name: isort
entry: bash -c '[ -d "apps/core" ] && cd apps/core; poetry run isort --profile black .'
language: system
types: [python]
args: ['--profile', 'black']
exclude: ^docs/
- id: black
name: black
entry: bash -c '[ -d "apps/core" ] && cd apps/core; poetry run black --line-length 140 --target-version py310 --target-version py311 .'
language: system
types: [python]
args: ['--line-length', '140', '--target-version', 'py310', '--target-version', 'py311']
exclude: ^docs/

View File

@@ -1,4 +1,4 @@
__version__ = "0.6.26"
__version__ = "0.6.28"
# import clients
from letta.client.client import LocalClient, RESTClient, create_client

View File

@@ -6,5 +6,6 @@ AZURE_MODEL_TO_CONTEXT_LENGTH = {
"gpt-35-turbo-0125": 16385,
"gpt-4-0613": 8192,
"gpt-4o-mini-2024-07-18": 128000,
"gpt-4o-mini": 128000,
"gpt-4o": 128000,
}

82
poetry.lock generated
View File

@@ -834,13 +834,13 @@ test = ["pytest"]
[[package]]
name = "composio-core"
version = "0.7.2"
version = "0.7.4"
description = "Core package to act as a bridge between composio platform and other services."
optional = false
python-versions = "<4,>=3.9"
files = [
{file = "composio_core-0.7.2-py3-none-any.whl", hash = "sha256:b44a9078b44337f39c5236ef8a2f59624f1d7d07b1ac0ad29e9aedbecfce5065"},
{file = "composio_core-0.7.2.tar.gz", hash = "sha256:0775661b11ff6cb0ed946c2695046009e6ac0e14678adb1a665c00538bb03ad3"},
{file = "composio_core-0.7.4-py3-none-any.whl", hash = "sha256:fcd0e50b2aff5b932491d532cc63d28d3b1018aeb633ae8ea96002ba75b307e7"},
{file = "composio_core-0.7.4.tar.gz", hash = "sha256:e09f80a9dfcbd187d73174bd5fb83e25a5935347149e63d62294f0646b931bc2"},
]
[package.dependencies]
@@ -871,13 +871,13 @@ tools = ["diskcache", "flake8", "networkx", "pathspec", "pygments", "ruff", "tra
[[package]]
name = "composio-langchain"
version = "0.7.2"
version = "0.7.4"
description = "Use Composio to get an array of tools with your LangChain agent."
optional = false
python-versions = "<4,>=3.9"
files = [
{file = "composio_langchain-0.7.2-py3-none-any.whl", hash = "sha256:b25341f41df533834c914fe4bd8ee623f5b439d8eff96f992a24976e8b2bf60f"},
{file = "composio_langchain-0.7.2.tar.gz", hash = "sha256:5088283a4a9295634e6272dafd8e478338c942bad305689bef9265bdf3685b48"},
{file = "composio_langchain-0.7.4-py3-none-any.whl", hash = "sha256:21a730214b33e63714991a0c4e14d58218fd164e3bee1e8a0ff974f5859d6e41"},
{file = "composio_langchain-0.7.4.tar.gz", hash = "sha256:801ec3ae8c73bca75087d2524cdd24dde7a8cc7729b251bee8ca021b08f1b323"},
]
[package.dependencies]
@@ -1700,7 +1700,7 @@ websockets = ">=13.0,<15.0dev"
name = "googleapis-common-protos"
version = "1.67.0"
description = "Common protobufs used in Google APIs"
optional = false
optional = true
python-versions = ">=3.7"
files = [
{file = "googleapis_common_protos-1.67.0-py2.py3-none-any.whl", hash = "sha256:579de760800d13616f51cf8be00c876f00a9f146d3e6510e19d1f4111758b741"},
@@ -2042,13 +2042,13 @@ files = [
[[package]]
name = "huggingface-hub"
version = "0.29.0"
version = "0.29.1"
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
optional = true
python-versions = ">=3.8.0"
files = [
{file = "huggingface_hub-0.29.0-py3-none-any.whl", hash = "sha256:c02daa0b6bafbdacb1320fdfd1dc7151d0940825c88c4ef89837fdb1f6ea0afe"},
{file = "huggingface_hub-0.29.0.tar.gz", hash = "sha256:64034c852be270cac16c5743fe1f659b14515a9de6342d6f42cbb2ede191fc80"},
{file = "huggingface_hub-0.29.1-py3-none-any.whl", hash = "sha256:352f69caf16566c7b6de84b54a822f6238e17ddd8ae3da4f8f2272aea5b198d5"},
{file = "huggingface_hub-0.29.1.tar.gz", hash = "sha256:9524eae42077b8ff4fc459ceb7a514eca1c1232b775276b009709fe2a084f250"},
]
[package.dependencies]
@@ -2695,13 +2695,13 @@ pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"]
[[package]]
name = "letta-client"
version = "0.1.39"
version = "0.1.40"
description = ""
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "letta_client-0.1.39-py3-none-any.whl", hash = "sha256:0644643031f45ca6306e9690f9c75c8077e1d66a9b0247cbf45e018e13313312"},
{file = "letta_client-0.1.39.tar.gz", hash = "sha256:d4ac3aef4c6b33a6281df3751a2b9279c91448ebd75f55f0304c605317aa938a"},
{file = "letta_client-0.1.40-py3-none-any.whl", hash = "sha256:8585bdc7cbb736590105a8e27692842c0987350c24a9bb5f74a165a5e66b7bfd"},
{file = "letta_client-0.1.40.tar.gz", hash = "sha256:c1d2afaeb5519a36b622675e14b548d388a82d8e4b1eb470bb2a641a11d471ed"},
]
[package.dependencies]
@@ -2854,17 +2854,17 @@ openai = ">=1.1.0"
[[package]]
name = "llama-index-indices-managed-llama-cloud"
version = "0.6.7"
version = "0.6.8"
description = "llama-index indices llama-cloud integration"
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "llama_index_indices_managed_llama_cloud-0.6.7-py3-none-any.whl", hash = "sha256:7cbe280ab03407f07a9ac034acf3bf2627a95d3868245f07c6242ce7ede264a8"},
{file = "llama_index_indices_managed_llama_cloud-0.6.7.tar.gz", hash = "sha256:b2a9020352b08e992327b25a3a9a056cb0d9397bc037aa498e5cfe451a0f07d9"},
{file = "llama_index_indices_managed_llama_cloud-0.6.8-py3-none-any.whl", hash = "sha256:b741fa3c286fb91600d8e54a4c62084b5e230ea624c2a778a202ed4abf6a8e9b"},
{file = "llama_index_indices_managed_llama_cloud-0.6.8.tar.gz", hash = "sha256:6581a1a4e966c80d108706880dc39a12e38634eddff9e859f2cc0d4bb11c6483"},
]
[package.dependencies]
llama-cloud = ">=0.1.8,<0.2.0"
llama-cloud = ">=0.1.13,<0.2.0"
llama-index-core = ">=0.12.0,<0.13.0"
[[package]]
@@ -3539,7 +3539,7 @@ realtime = ["websockets (>=13,<15)"]
name = "opentelemetry-api"
version = "1.30.0"
description = "OpenTelemetry Python API"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_api-1.30.0-py3-none-any.whl", hash = "sha256:d5f5284890d73fdf47f843dda3210edf37a38d66f44f2b5aedc1e89ed455dc09"},
@@ -3554,7 +3554,7 @@ importlib-metadata = ">=6.0,<=8.5.0"
name = "opentelemetry-exporter-otlp"
version = "1.30.0"
description = "OpenTelemetry Collector Exporters"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_exporter_otlp-1.30.0-py3-none-any.whl", hash = "sha256:44e11054ec571ccfed73a83c6429dee5d334d061d0e0572e3160d6de97156dbc"},
@@ -3569,7 +3569,7 @@ opentelemetry-exporter-otlp-proto-http = "1.30.0"
name = "opentelemetry-exporter-otlp-proto-common"
version = "1.30.0"
description = "OpenTelemetry Protobuf encoding"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_exporter_otlp_proto_common-1.30.0-py3-none-any.whl", hash = "sha256:5468007c81aa9c44dc961ab2cf368a29d3475977df83b4e30aeed42aa7bc3b38"},
@@ -3583,7 +3583,7 @@ opentelemetry-proto = "1.30.0"
name = "opentelemetry-exporter-otlp-proto-grpc"
version = "1.30.0"
description = "OpenTelemetry Collector Protobuf over gRPC Exporter"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_exporter_otlp_proto_grpc-1.30.0-py3-none-any.whl", hash = "sha256:2906bcae3d80acc54fd1ffcb9e44d324e8631058b502ebe4643ca71d1ff30830"},
@@ -3603,7 +3603,7 @@ opentelemetry-sdk = ">=1.30.0,<1.31.0"
name = "opentelemetry-exporter-otlp-proto-http"
version = "1.30.0"
description = "OpenTelemetry Collector Protobuf over HTTP Exporter"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_exporter_otlp_proto_http-1.30.0-py3-none-any.whl", hash = "sha256:9578e790e579931c5ffd50f1e6975cbdefb6a0a0a5dea127a6ae87df10e0a589"},
@@ -3623,7 +3623,7 @@ requests = ">=2.7,<3.0"
name = "opentelemetry-instrumentation"
version = "0.51b0"
description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_instrumentation-0.51b0-py3-none-any.whl", hash = "sha256:c6de8bd26b75ec8b0e54dff59e198946e29de6a10ec65488c357d4b34aa5bdcf"},
@@ -3640,7 +3640,7 @@ wrapt = ">=1.0.0,<2.0.0"
name = "opentelemetry-instrumentation-requests"
version = "0.51b0"
description = "OpenTelemetry requests instrumentation"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_instrumentation_requests-0.51b0-py3-none-any.whl", hash = "sha256:0723aaafaeb2a825723f31c0bf644f9642377046063d1a52fc86571ced87feac"},
@@ -3660,7 +3660,7 @@ instruments = ["requests (>=2.0,<3.0)"]
name = "opentelemetry-proto"
version = "1.30.0"
description = "OpenTelemetry Python Proto"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_proto-1.30.0-py3-none-any.whl", hash = "sha256:c6290958ff3ddacc826ca5abbeb377a31c2334387352a259ba0df37c243adc11"},
@@ -3674,7 +3674,7 @@ protobuf = ">=5.0,<6.0"
name = "opentelemetry-sdk"
version = "1.30.0"
description = "OpenTelemetry Python SDK"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_sdk-1.30.0-py3-none-any.whl", hash = "sha256:14fe7afc090caad881addb6926cec967129bd9260c4d33ae6a217359f6b61091"},
@@ -3690,7 +3690,7 @@ typing-extensions = ">=3.7.4"
name = "opentelemetry-semantic-conventions"
version = "0.51b0"
description = "OpenTelemetry Semantic Conventions"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_semantic_conventions-0.51b0-py3-none-any.whl", hash = "sha256:fdc777359418e8d06c86012c3dc92c88a6453ba662e941593adb062e48c2eeae"},
@@ -3705,7 +3705,7 @@ opentelemetry-api = "1.30.0"
name = "opentelemetry-util-http"
version = "0.51b0"
description = "Web util for OpenTelemetry"
optional = false
optional = true
python-versions = ">=3.8"
files = [
{file = "opentelemetry_util_http-0.51b0-py3-none-any.whl", hash = "sha256:0561d7a6e9c422b9ef9ae6e77eafcfcd32a2ab689f5e801475cbb67f189efa20"},
@@ -6066,28 +6066,21 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,
[[package]]
name = "typer"
version = "0.9.4"
version = "0.15.1"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
files = [
{file = "typer-0.9.4-py3-none-any.whl", hash = "sha256:aa6c4a4e2329d868b80ecbaf16f807f2b54e192209d7ac9dd42691d63f7a54eb"},
{file = "typer-0.9.4.tar.gz", hash = "sha256:f714c2d90afae3a7929fcd72a3abb08df305e1ff61719381384211c4070af57f"},
{file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"},
{file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"},
]
[package.dependencies]
click = ">=7.1.1,<9.0.0"
colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"all\""}
rich = {version = ">=10.11.0,<14.0.0", optional = true, markers = "extra == \"all\""}
shellingham = {version = ">=1.3.0,<2.0.0", optional = true, markers = "extra == \"all\""}
click = ">=8.0.0"
rich = ">=10.11.0"
shellingham = ">=1.3.0"
typing-extensions = ">=3.7.4.3"
[package.extras]
all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"]
doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"]
test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.971)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
[[package]]
name = "types-requests"
version = "2.32.0.20241016"
@@ -6851,7 +6844,7 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\
cffi = ["cffi (>=1.11)"]
[extras]
all = ["autoflake", "black", "datasets", "docker", "fastapi", "isort", "langchain", "langchain-community", "locust", "pexpect", "pg8000", "pgvector", "pre-commit", "psycopg2", "psycopg2-binary", "pyright", "pytest-asyncio", "pytest-order", "uvicorn", "wikipedia"]
all = ["autoflake", "black", "boto3", "datasets", "docker", "fastapi", "google-genai", "isort", "langchain", "langchain-community", "locust", "opentelemetry-api", "opentelemetry-exporter-otlp", "opentelemetry-instrumentation-requests", "opentelemetry-sdk", "pexpect", "pg8000", "pgvector", "pre-commit", "psycopg2", "psycopg2-binary", "pyright", "pytest-asyncio", "pytest-order", "uvicorn", "wikipedia"]
bedrock = ["boto3"]
cloud-tool-sandbox = ["e2b-code-interpreter"]
dev = ["autoflake", "black", "datasets", "isort", "locust", "pexpect", "pre-commit", "pyright", "pytest-asyncio", "pytest-order"]
@@ -6860,9 +6853,10 @@ google = ["google-genai"]
postgres = ["pg8000", "pgvector", "psycopg2", "psycopg2-binary"]
qdrant = ["qdrant-client"]
server = ["fastapi", "uvicorn"]
telemetry = ["opentelemetry-api", "opentelemetry-exporter-otlp", "opentelemetry-instrumentation-requests", "opentelemetry-sdk"]
tests = ["wikipedia"]
[metadata]
lock-version = "2.0"
python-versions = "<3.14,>=3.10"
content-hash = "55cce79796d9e1265865b8bfc9a5b4aaa959705401054553b8bf39fe2f5c27f9"
content-hash = "eeca4050161bf468417f9ed7934e1e03b9ed6d79fc85038ad7b2c770f2be7f26"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "letta"
version = "0.6.26"
version = "0.6.28"
packages = [
{include = "letta"},
]
@@ -16,7 +16,7 @@ letta = "letta.main:app"
[tool.poetry.dependencies]
python = "<3.14,>=3.10"
typer = {extras = ["all"], version = "^0.9.0"}
typer = ">=0.12,<1.0"
questionary = "^2.0.1"
pytz = "^2023.3.post1"
tqdm = "^4.66.1"
@@ -78,10 +78,10 @@ e2b-code-interpreter = {version = "^1.0.3", optional = true}
anthropic = "^0.43.0"
letta_client = "^0.1.23"
openai = "^1.60.0"
opentelemetry-api = "1.30.0"
opentelemetry-sdk = "1.30.0"
opentelemetry-instrumentation-requests = "0.51b0"
opentelemetry-exporter-otlp = "1.30.0"
opentelemetry-api = {version = "1.30.0", optional = true}
opentelemetry-sdk = {version = "1.30.0", optional = true}
opentelemetry-instrumentation-requests = {version = "0.51b0", optional = true}
opentelemetry-exporter-otlp = {version = "1.30.0", optional = true}
google-genai = {version = "^1.1.0", optional = true}
faker = "^36.1.0"
colorama = "^0.4.6"
@@ -97,9 +97,10 @@ qdrant = ["qdrant-client"]
cloud-tool-sandbox = ["e2b-code-interpreter"]
external-tools = ["docker", "langchain", "wikipedia", "langchain-community"]
tests = ["wikipedia"]
all = ["pgvector", "pg8000", "psycopg2-binary", "psycopg2", "pytest", "pytest-asyncio", "pexpect", "black", "pre-commit", "datasets", "pyright", "pytest-order", "autoflake", "isort", "websockets", "fastapi", "uvicorn", "docker", "langchain", "wikipedia", "langchain-community", "locust"]
bedrock = ["boto3"]
google = ["google-genai"]
telemetry = ["opentelemetry-api", "opentelemetry-sdk", "opentelemetry-instrumentation-requests", "opentelemetry-exporter-otlp"]
all = ["pgvector", "pg8000", "psycopg2-binary", "psycopg2", "pytest", "pytest-asyncio", "pexpect", "black", "pre-commit", "datasets", "pyright", "pytest-order", "autoflake", "isort", "websockets", "fastapi", "uvicorn", "docker", "langchain", "wikipedia", "langchain-community", "locust", "boto3", "google-genai", "opentelemetry-api", "opentelemetry-sdk", "opentelemetry-instrumentation-requests", "opentelemetry-exporter-otlp"]
[tool.poetry.group.dev.dependencies]
black = "^24.4.2"

View File

@@ -98,3 +98,216 @@ def test_recall(client, agent_obj):
# Conversation search
result = base_functions.conversation_search(agent_obj, "banana")
assert keyword in result
# This test is nondeterministic, so we retry until we get the perfect behavior from the LLM
@retry_until_success(max_attempts=2, sleep_time_seconds=2)
def test_send_message_to_agent(client, agent_obj, other_agent_obj):
secret_word = "banana"
# Encourage the agent to send a message to the other agent_obj with the secret string
client.send_message(
agent_id=agent_obj.agent_state.id,
role="user",
message=f"Use your tool to send a message to another agent with id {other_agent_obj.agent_state.id} to share the secret word: {secret_word}!",
)
# Conversation search the other agent
messages = client.get_messages(other_agent_obj.agent_state.id)
# Check for the presence of system message
for m in reversed(messages):
print(f"\n\n {other_agent_obj.agent_state.id} -> {m.model_dump_json(indent=4)}")
if isinstance(m, SystemMessage):
assert secret_word in m.content
break
# Search the sender agent for the response from another agent
in_context_messages = agent_obj.agent_manager.get_in_context_messages(agent_id=agent_obj.agent_state.id, actor=agent_obj.user)
found = False
target_snippet = f"{other_agent_obj.agent_state.id} said:"
for m in in_context_messages:
if target_snippet in m.text:
found = True
break
# Compute the joined string first
joined_messages = "\n".join([m.text for m in in_context_messages[1:]])
print(f"In context messages of the sender agent (without system):\n\n{joined_messages}")
if not found:
raise Exception(f"Was not able to find an instance of the target snippet: {target_snippet}")
# Test that the agent can still receive messages fine
response = client.send_message(agent_id=agent_obj.agent_state.id, role="user", message="So what did the other agent say?")
print(response.messages)
@retry_until_success(max_attempts=2, sleep_time_seconds=2)
def test_send_message_to_agents_with_tags_simple(client):
worker_tags = ["worker", "user-456"]
# Clean up first from possibly failed tests
prev_worker_agents = client.server.agent_manager.list_agents(client.user, tags=worker_tags, match_all_tags=True)
for agent in prev_worker_agents:
client.delete_agent(agent.id)
secret_word = "banana"
# Create "manager" agent
send_message_to_agents_matching_all_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_all_tags")
manager_agent_state = client.create_agent(tool_ids=[send_message_to_agents_matching_all_tags_tool_id])
manager_agent = client.server.load_agent(agent_id=manager_agent_state.id, actor=client.user)
# Create 3 non-matching worker agents (These should NOT get the message)
worker_agents = []
worker_tags = ["worker", "user-123"]
for _ in range(3):
worker_agent_state = client.create_agent(include_multi_agent_tools=False, tags=worker_tags)
worker_agent = client.server.load_agent(agent_id=worker_agent_state.id, actor=client.user)
worker_agents.append(worker_agent)
# Create 3 worker agents that should get the message
worker_agents = []
worker_tags = ["worker", "user-456"]
for _ in range(3):
worker_agent_state = client.create_agent(include_multi_agent_tools=False, tags=worker_tags)
worker_agent = client.server.load_agent(agent_id=worker_agent_state.id, actor=client.user)
worker_agents.append(worker_agent)
# Encourage the manager to send a message to the other agent_obj with the secret string
response = client.send_message(
agent_id=manager_agent.agent_state.id,
role="user",
message=f"Send a message to all agents with tags {worker_tags} informing them of the secret word: {secret_word}!",
)
for m in response.messages:
if isinstance(m, ToolReturnMessage):
tool_response = eval(json.loads(m.tool_return)["message"])
print(f"\n\nManager agent tool response: \n{tool_response}\n\n")
assert len(tool_response) == len(worker_agents)
# We can break after this, the ToolReturnMessage after is not related
break
# Conversation search the worker agents
for agent in worker_agents:
messages = client.get_messages(agent.agent_state.id)
# Check for the presence of system message
for m in reversed(messages):
print(f"\n\n {agent.agent_state.id} -> {m.model_dump_json(indent=4)}")
if isinstance(m, SystemMessage):
assert secret_word in m.content
break
# Test that the agent can still receive messages fine
response = client.send_message(agent_id=manager_agent.agent_state.id, role="user", message="So what did the other agents say?")
print("Manager agent followup message: \n\n" + "\n".join([str(m) for m in response.messages]))
# Clean up agents
client.delete_agent(manager_agent_state.id)
for agent in worker_agents:
client.delete_agent(agent.agent_state.id)
# This test is nondeterministic, so we retry until we get the perfect behavior from the LLM
@retry_until_success(max_attempts=2, sleep_time_seconds=2)
def test_send_message_to_agents_with_tags_complex_tool_use(client, roll_dice_tool):
worker_tags = ["dice-rollers"]
# Clean up first from possibly failed tests
prev_worker_agents = client.server.agent_manager.list_agents(client.user, tags=worker_tags, match_all_tags=True)
for agent in prev_worker_agents:
client.delete_agent(agent.id)
# Create "manager" agent
send_message_to_agents_matching_all_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_all_tags")
manager_agent_state = client.create_agent(tool_ids=[send_message_to_agents_matching_all_tags_tool_id])
manager_agent = client.server.load_agent(agent_id=manager_agent_state.id, actor=client.user)
# Create 3 worker agents
worker_agents = []
worker_tags = ["dice-rollers"]
for _ in range(2):
worker_agent_state = client.create_agent(include_multi_agent_tools=False, tags=worker_tags, tool_ids=[roll_dice_tool.id])
worker_agent = client.server.load_agent(agent_id=worker_agent_state.id, actor=client.user)
worker_agents.append(worker_agent)
# Encourage the manager to send a message to the other agent_obj with the secret string
broadcast_message = f"Send a message to all agents with tags {worker_tags} asking them to roll a dice for you!"
response = client.send_message(
agent_id=manager_agent.agent_state.id,
role="user",
message=broadcast_message,
)
for m in response.messages:
if isinstance(m, ToolReturnMessage):
tool_response = eval(json.loads(m.tool_return)["message"])
print(f"\n\nManager agent tool response: \n{tool_response}\n\n")
assert len(tool_response) == len(worker_agents)
# We can break after this, the ToolReturnMessage after is not related
break
# Test that the agent can still receive messages fine
response = client.send_message(agent_id=manager_agent.agent_state.id, role="user", message="So what did the other agents say?")
print("Manager agent followup message: \n\n" + "\n".join([str(m) for m in response.messages]))
# Clean up agents
client.delete_agent(manager_agent_state.id)
for agent in worker_agents:
client.delete_agent(agent.agent_state.id)
@retry_until_success(max_attempts=5, sleep_time_seconds=2)
def test_agents_async_simple(client):
"""
Test two agents with multi-agent tools sending messages back and forth to count to 5.
The chain is started by prompting one of the agents.
"""
# Cleanup from potentially failed previous runs
existing_agents = client.server.agent_manager.list_agents(client.user)
for agent in existing_agents:
client.delete_agent(agent.id)
# Create two agents with multi-agent tools
send_message_to_agent_async_tool_id = client.get_tool_id(name="send_message_to_agent_async")
memory_a = ChatMemory(
human="Chad - I'm interested in hearing poem.",
persona="You are an AI agent that can communicate with your agent buddy using `send_message_to_agent_async`, who has some great poem ideas (so I've heard).",
)
charles_state = client.create_agent(name="charles", memory=memory_a, tool_ids=[send_message_to_agent_async_tool_id])
charles = client.server.load_agent(agent_id=charles_state.id, actor=client.user)
memory_b = ChatMemory(
human="No human - you are to only communicate with the other AI agent.",
persona="You are an AI agent that can communicate with your agent buddy using `send_message_to_agent_async`, who is interested in great poem ideas.",
)
sarah_state = client.create_agent(name="sarah", memory=memory_b, tool_ids=[send_message_to_agent_async_tool_id])
# Start the count chain with Agent1
initial_prompt = f"I want you to talk to the other agent with ID {sarah_state.id} using `send_message_to_agent_async`. Specifically, I want you to ask him for a poem idea, and then craft a poem for me."
client.send_message(
agent_id=charles.agent_state.id,
role="user",
message=initial_prompt,
)
found_in_charles = wait_for_incoming_message(
client=client,
agent_id=charles_state.id,
substring="[Incoming message from agent with ID",
max_wait_seconds=10,
sleep_interval=0.5,
)
assert found_in_charles, "Charles never received the system message from Sarah (timed out)."
found_in_sarah = wait_for_incoming_message(
client=client,
agent_id=sarah_state.id,
substring="[Incoming message from agent with ID",
max_wait_seconds=10,
sleep_interval=0.5,
)
assert found_in_sarah, "Sarah never received the system message from Charles (timed out)."

View File

@@ -117,7 +117,7 @@ def test_shared_blocks(client: LettaSDKClient):
)
assert (
"charles" in client.agents.core_memory.retrieve_block(agent_id=agent_state2.id, block_label="human").value.lower()
), f"Shared block update failed {client.agents.core_memory.retrieve_block(agent_id=agent_state2.id, block_label="human").value}"
), f"Shared block update failed {client.agents.core_memory.retrieve_block(agent_id=agent_state2.id, block_label='human').value}"
# cleanup
client.agents.delete(agent_state1.id)

View File

@@ -8,10 +8,9 @@ def adjust_menu_prices(percentage: float) -> str:
str: A formatted string summarizing the price adjustments.
"""
import cowsay
from tqdm import tqdm
from core.menu import Menu, MenuItem # Import a class from the codebase
from core.utils import format_currency # Use a utility function to test imports
from tqdm import tqdm
if not isinstance(percentage, (int, float)):
raise TypeError("percentage must be a number")