feat: add profiling and structured logging (#5690)

* test dd build

* dd agent in cluster

* quick poc

* refactor and add logging

* remove tracing etc.

* add changes to otel logging config

* refactor to accept my feedback

* finishing touches
This commit is contained in:
Kian Jones
2025-10-23 15:50:30 -07:00
committed by Caren Thomas
parent 8872a3b954
commit 1577a261d8
7 changed files with 365 additions and 34 deletions

View File

@@ -1,14 +1,136 @@
import json
import logging
import traceback
from datetime import datetime, timezone
from logging.config import dictConfig
from pathlib import Path
from sys import stdout
from typing import Optional
from typing import Any, Optional
from letta.settings import settings
from letta.settings import log_settings, settings, telemetry_settings
selected_log_level = logging.DEBUG if settings.debug else logging.INFO
class JSONFormatter(logging.Formatter):
"""
Custom JSON formatter for structured logging with Datadog integration.
Outputs logs in JSON format with fields compatible with Datadog log ingestion.
Automatically includes trace correlation fields when Datadog tracing is enabled.
Usage:
Enable JSON logging by setting the environment variable:
LETTA_LOGGING_JSON_LOGGING=true
Add custom structured fields to logs using the 'extra' parameter:
logger.info("User action", extra={"user_id": "123", "action": "login"})
These fields will be automatically included in the JSON output and
indexed by Datadog for filtering and analysis.
Output format:
{
"timestamp": "2025-10-23T18:34:24.931739+00:00",
"level": "INFO",
"logger": "Letta.module",
"message": "Log message",
"module": "module_name",
"function": "function_name",
"line": 123,
"dd.trace_id": "1234567890", # Added when Datadog tracing is enabled
"dd.span_id": "9876543210", # Added when Datadog tracing is enabled
"custom_field": "custom_value" # Any extra fields you provide
}
"""
def format(self, record: logging.LogRecord) -> str:
"""Format log record as JSON with Datadog-compatible fields."""
# Base log structure
log_data: dict[str, Any] = {
"timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
"line": record.lineno,
}
# Add Datadog trace correlation if available
# ddtrace automatically injects these attributes when logging is patched
if hasattr(record, "dd.trace_id"):
log_data["dd.trace_id"] = getattr(record, "dd.trace_id")
if hasattr(record, "dd.span_id"):
log_data["dd.span_id"] = getattr(record, "dd.span_id")
if hasattr(record, "dd.service"):
log_data["dd.service"] = getattr(record, "dd.service")
if hasattr(record, "dd.env"):
log_data["dd.env"] = getattr(record, "dd.env")
if hasattr(record, "dd.version"):
log_data["dd.version"] = getattr(record, "dd.version")
# Add exception info if present
if record.exc_info:
log_data["exception"] = {
"type": record.exc_info[0].__name__ if record.exc_info[0] else None,
"message": str(record.exc_info[1]) if record.exc_info[1] else None,
"stacktrace": "".join(traceback.format_exception(*record.exc_info)),
}
# Add any extra fields from the log record
# These are custom fields passed via logging.info("msg", extra={...})
for key, value in record.__dict__.items():
if key not in [
"name",
"msg",
"args",
"created",
"filename",
"funcName",
"levelname",
"levelno",
"lineno",
"module",
"msecs",
"message",
"pathname",
"process",
"processName",
"relativeCreated",
"thread",
"threadName",
"exc_info",
"exc_text",
"stack_info",
"dd_env",
"dd_service",
] and not key.startswith("dd."):
log_data[key] = value
return json.dumps(log_data, default=str)
class DatadogEnvFilter(logging.Filter):
"""
Logging filter that adds Datadog-specific attributes to log records.
This enables log-trace correlation by injecting environment and service metadata
that Datadog can use to link logs with traces and other telemetry data.
"""
def filter(self, record: logging.LogRecord) -> bool:
"""Add Datadog attributes to log record if Datadog is enabled."""
if telemetry_settings.enable_datadog:
record.dd_env = telemetry_settings.datadog_env
record.dd_service = "letta-server"
else:
# Provide defaults to prevent attribute errors if filter is applied incorrectly
record.dd_env = ""
record.dd_service = ""
return True
def _setup_logfile() -> "Path":
"""ensure the logger filepath is in place
@@ -20,28 +142,65 @@ def _setup_logfile() -> "Path":
return logfile
# TODO: production logging should be much less invasive
# Determine which formatter to use based on configuration
def _get_console_formatter() -> str:
"""Determine the appropriate console formatter based on settings."""
if log_settings.json_logging:
return "json"
elif telemetry_settings.enable_datadog:
return "datadog"
else:
return "no_datetime"
def _get_file_formatter() -> str:
"""Determine the appropriate file formatter based on settings."""
if log_settings.json_logging:
return "json"
elif telemetry_settings.enable_datadog:
return "datadog"
else:
return "standard"
# Logging configuration with optional Datadog integration and JSON support
DEVELOPMENT_LOGGING = {
"version": 1,
"disable_existing_loggers": False, # Allow capturing from all loggers
"formatters": {
"standard": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"},
"no_datetime": {"format": "%(name)s - %(levelname)s - %(message)s"},
"datadog": {
# Datadog-compatible format with key=value pairs for better parsing
# ddtrace's log injection will add dd.trace_id, dd.span_id automatically when logging is patched
"format": "%(asctime)s - %(name)s - %(levelname)s - [dd.env=%(dd_env)s dd.service=%(dd_service)s] - %(message)s"
},
"json": {
# JSON formatter for structured logging with full Datadog integration
"()": JSONFormatter,
},
},
"filters": {
"datadog_env": {
"()": DatadogEnvFilter,
},
},
"handlers": {
"console": {
"level": selected_log_level,
"class": "logging.StreamHandler",
"stream": stdout,
"formatter": "no_datetime",
"formatter": _get_console_formatter(),
"filters": ["datadog_env"] if telemetry_settings.enable_datadog and not log_settings.json_logging else [],
},
"file": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"filename": _setup_logfile(),
"maxBytes": 1024**2 * 10,
"backupCount": 3,
"formatter": "standard",
"maxBytes": 1024**2 * 10, # 10 MB per file
"backupCount": 3, # Keep 3 backup files
"formatter": _get_file_formatter(),
"filters": ["datadog_env"] if telemetry_settings.enable_datadog and not log_settings.json_logging else [],
},
},
"root": { # Root logger handles all logs
@@ -58,6 +217,11 @@ DEVELOPMENT_LOGGING = {
"handlers": ["console"],
"propagate": True,
},
# Reduce noise from ddtrace internal logging
"ddtrace": {
"level": "WARNING",
"propagate": True,
},
},
}

View File

@@ -204,6 +204,49 @@ def create_application() -> "FastAPI":
},
)
if telemetry_settings.enable_datadog:
try:
dd_env = settings.environment or "development"
print(f"▶ Initializing Datadog profiling (env={dd_env})")
# Configure environment variables before importing ddtrace (must be set in environment before importing ddtrace)
os.environ.setdefault("DD_ENV", dd_env)
os.environ.setdefault("DD_SERVICE", telemetry_settings.datadog_service_name)
os.environ.setdefault("DD_VERSION", letta_version)
os.environ.setdefault("DD_AGENT_HOST", telemetry_settings.datadog_agent_host)
os.environ.setdefault("DD_TRACE_AGENT_PORT", str(telemetry_settings.datadog_agent_port))
os.environ.setdefault("DD_PROFILING_ENABLED", "true")
os.environ.setdefault("DD_PROFILING_MEMORY_ENABLED", str(telemetry_settings.datadog_profiling_memory_enabled).lower())
os.environ.setdefault("DD_PROFILING_HEAP_ENABLED", str(telemetry_settings.datadog_profiling_heap_enabled).lower())
from ddtrace.profiling import Profiler
# Initialize and start profiler
profiler = Profiler(
env=dd_env,
service=telemetry_settings.datadog_service_name,
version=letta_version,
)
profiler.start()
# Log Git metadata for source code integration
git_info = ""
if telemetry_settings.datadog_git_commit_sha:
git_info = f", commit={telemetry_settings.datadog_git_commit_sha[:8]}"
if telemetry_settings.datadog_git_repository_url:
git_info += f", repo={telemetry_settings.datadog_git_repository_url}"
logger.info(
f"Datadog profiling enabled: env={dd_env}, "
f"service={telemetry_settings.datadog_service_name}, "
f"agent={telemetry_settings.datadog_agent_host}:{telemetry_settings.datadog_agent_port}{git_info}"
)
except Exception as e:
logger.error(f"Failed to initialize Datadog profiling: {e}", exc_info=True)
if SENTRY_ENABLED:
sentry_sdk.capture_exception(e)
# Don't fail application startup if Datadog initialization fails
debug_mode = "--debug" in sys.argv
app = FastAPI(
swagger_ui_parameters={"docExpansion": "none"},

View File

@@ -379,15 +379,52 @@ class TestSettings(Settings):
class LogSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="letta_logging_", extra="ignore")
debug: bool | None = Field(False, description="Enable debugging for logging")
json_logging: bool = Field(False, description="Enable json logging instead of text logging")
json_logging: bool = Field(
False,
description="Enable structured JSON logging (recommended).",
)
log_level: str | None = Field("WARNING", description="Logging level")
letta_log_path: Path | None = Field(Path.home() / ".letta" / "logs" / "Letta.log")
verbose_telemetry_logging: bool = Field(False)
class TelemetrySettings(BaseSettings):
"""Configuration for telemetry and observability integrations."""
model_config = SettingsConfigDict(env_prefix="letta_telemetry_", extra="ignore")
profiler: bool | None = Field(False, description="Enable use of the profiler.")
# Google Cloud Profiler
profiler: bool | None = Field(False, description="Enable Google Cloud Profiler.")
# Datadog APM and Profiling
enable_datadog: bool | None = Field(False, description="Enable Datadog profiling. Environment is pulled from settings.environment.")
datadog_agent_host: str = Field(
default="localhost",
description="Datadog agent hostname or IP address. Use service name for Kubernetes (e.g., 'datadog-cluster-agent').",
)
datadog_agent_port: int = Field(default=8126, ge=1, le=65535, description="Datadog trace agent port (typically 8126 for traces).")
datadog_service_name: str = Field(default="letta-server", description="Service name for Datadog profiling.")
datadog_profiling_memory_enabled: bool = Field(default=True, description="Enable memory profiling in Datadog.")
datadog_profiling_heap_enabled: bool = Field(default=True, description="Enable heap profiling in Datadog.")
# Datadog Source Code Integration (optional, tightly coupled with profiling)
# These settings link profiling data and traces to specific Git commits,
# enabling code navigation directly from Datadog UI to GitHub/GitLab.
datadog_git_repository_url: str | None = Field(
default=None,
validation_alias=AliasChoices("DD_GIT_REPOSITORY_URL", "datadog_git_repository_url"),
description="Git repository URL (e.g., 'https://github.com/org/repo'). Set at build time.",
)
datadog_git_commit_sha: str | None = Field(
default=None,
validation_alias=AliasChoices("DD_GIT_COMMIT_SHA", "datadog_git_commit_sha"),
description="Git commit SHA for the deployed code. Set at build time with 'git rev-parse HEAD'.",
)
datadog_main_package: str = Field(
default="letta",
validation_alias=AliasChoices("DD_MAIN_PACKAGE", "datadog_main_package"),
description="Primary Python package name for source code linking. Datadog uses this setting to determine which code is 'yours' vs. third-party dependencies.",
)
# singleton

View File

@@ -8,15 +8,15 @@ receivers:
filelog:
include:
- /root/.letta/logs/Letta.log
multiline:
line_start_pattern: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}
operators:
# Extract timestamp and other fields
- type: regex_parser
regex: '^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})\s+.*'
# Parse JSON logs
- type: json_parser
parse_from: body
parse_to: attributes
- type: time_parser
parse_from: attributes.timestamp
layout: '%Y-%m-%d %H:%M:%S,%L'
layout_type: gotime
layout: '2006-01-02T15:04:05.999999Z07:00'
processors:
memory_limiter:

View File

@@ -8,15 +8,15 @@ receivers:
filelog:
include:
- /root/.letta/logs/Letta.log
multiline:
line_start_pattern: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}
operators:
# Extract timestamp and other fields
- type: regex_parser
regex: '^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})\s+.*'
# Parse JSON logs
- type: json_parser
parse_from: body
parse_to: attributes
- type: time_parser
parse_from: attributes.timestamp
layout: '%Y-%m-%d %H:%M:%S,%L'
layout_type: gotime
layout: '2006-01-02T15:04:05.999999Z07:00'
processors:
memory_limiter:

View File

@@ -89,7 +89,10 @@ pinecone = ["pinecone[asyncio]>=7.3.0"]
sqlite = ["aiosqlite>=0.21.0", "sqlite-vec>=0.1.7a2"]
# ====== Server ======
experimental = ["uvloop>=0.21.0", "granian[uvloop,reload]>=2.3.2", "google-cloud-profiler>=4.1.0"]
experimental = [
"uvloop>=0.21.0",
"granian[uvloop,reload]>=2.3.2",
]
server = [
"websockets",
"fastapi>=0.115.6",
@@ -144,6 +147,10 @@ desktop = [
"magika>=0.6.2",
#"pgserver>=0.1.4",
]
profiling = [
"ddtrace>=2.18.2",
"google-cloud-profiler>=4.1.0",
]
[build-system]
requires = ["hatchling"]

104
uv.lock generated
View File

@@ -518,6 +518,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" },
]
[[package]]
name = "bytecode"
version = "0.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/c4/4818b392104bd426171fc2ce9c79c8edb4019ba6505747626d0f7107766c/bytecode-0.17.0.tar.gz", hash = "sha256:0c37efa5bd158b1b873f530cceea2c645611d55bd2dc2a4758b09f185749b6fd", size = 105863, upload-time = "2025-09-03T19:55:45.703Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/80/379e685099841f8501a19fb58b496512ef432331fed38276c3938ab09d8e/bytecode-0.17.0-py3-none-any.whl", hash = "sha256:64fb10cde1db7ef5cc39bd414ecebd54ba3b40e1c4cf8121ca5e72f170916ff8", size = 43045, upload-time = "2025-09-03T19:55:43.879Z" },
]
[[package]]
name = "cachetools"
version = "5.5.2"
@@ -888,6 +897,55 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/87/22/f020c047ae1346613db9322638186468238bcfa8849b4668a22b97faad65/dateparser-1.2.2-py3-none-any.whl", hash = "sha256:5a5d7211a09013499867547023a2a0c91d5a27d15dd4dbcea676ea9fe66f2482", size = 315453, upload-time = "2025-06-26T09:29:21.412Z" },
]
[[package]]
name = "ddtrace"
version = "3.16.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bytecode" },
{ name = "envier" },
{ name = "legacy-cgi", marker = "python_full_version >= '3.13'" },
{ name = "opentelemetry-api" },
{ name = "protobuf" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/35/028fe174ec1a1da8977d4900297f4493a77e93dee1af700f473e692d010e/ddtrace-3.16.2.tar.gz", hash = "sha256:cfef021790635b6dda949e89298b7fed3b5e686c55b46afe9483cebcc0f10a86", size = 7408082, upload-time = "2025-10-21T19:29:32.004Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/7f/55a8753b6ee574b34ee9c3ae48f6b35f4b04de3ef0e4044ad1adb6e7831b/ddtrace-3.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8d644051c265be9865e68274ebbc4a17934a78fc447bf7d6611ecbe49fbe4b7a", size = 6334301, upload-time = "2025-10-21T19:26:27.261Z" },
{ url = "https://files.pythonhosted.org/packages/37/a9/a58ae59088f00e237068c4522bb23b12d93e03a9e76cf73f2a357c963533/ddtrace-3.16.2-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:9a51665c563f2cc56ccc29f3404e68618bbfd70e60983de239ba8d2edc5ce7e8", size = 6679330, upload-time = "2025-10-21T19:26:28.867Z" },
{ url = "https://files.pythonhosted.org/packages/81/48/df6dc3e2b7fff37ad813ddc5651a3e1f8240757b46f464d0e4b3ccf58a11/ddtrace-3.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:527d257f8b020d61f53686fa8791529d1a5b5c33719ed47b28110b8449f14257", size = 7402656, upload-time = "2025-10-21T19:26:30.865Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d3/377f88a42b9df3bb12fe8c11dfc46548b996dec13abea23fbf0714994a32/ddtrace-3.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2eb5a196f74fa5fd8e62105a4faeb4b89e58ed2e00401b182888e407c8d23e94", size = 7668527, upload-time = "2025-10-21T19:26:32.663Z" },
{ url = "https://files.pythonhosted.org/packages/e6/01/8ee880b739afb614b2268c5e86cb039d509b3947aa6669ad861130fc62a0/ddtrace-3.16.2-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:59bb645bd5f58465df651e3ae9809bdc2fed339e9c7eae53a41b941d40497508", size = 5521123, upload-time = "2025-10-21T19:26:35.209Z" },
{ url = "https://files.pythonhosted.org/packages/ed/60/cd88ae82999fe8e532259289f37f5afa08e9fae2204464372cc08859bf60/ddtrace-3.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b2e752daab0206c3abd118621851cc39673f9ed5c2180264ffa4fd147b6351be", size = 8415337, upload-time = "2025-10-21T19:26:37.117Z" },
{ url = "https://files.pythonhosted.org/packages/f6/7d/5ec01e65bf3a5c022439292e65c23ce80cd0c4726daba8bc7cfa12252665/ddtrace-3.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f9cd3345d686369254e072f049974386f58ee583a24be99862c480d7347819e", size = 6609971, upload-time = "2025-10-21T19:26:39.462Z" },
{ url = "https://files.pythonhosted.org/packages/ba/1f/8df630beb55734d46f4340144c7350c0c820eb4fedc0d3c38f5a694b4eea/ddtrace-3.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ed11dfc53359e2c0d8aa377ceddd1409520dafc7d41c3e5e3432897160c2b23", size = 8744007, upload-time = "2025-10-21T19:26:41.421Z" },
{ url = "https://files.pythonhosted.org/packages/5c/eb/70f518a5da3d0b1ca5585e81f3af1ce5119cee9cb4289134ce76d4614ad0/ddtrace-3.16.2-cp311-cp311-win32.whl", hash = "sha256:5c64499f3c2cd906be1f01f880b631157c0a8d4f0b074164efead09c0bd22c6d", size = 5043118, upload-time = "2025-10-21T19:26:43.527Z" },
{ url = "https://files.pythonhosted.org/packages/5c/29/2a4b0cd621151063912f8919f03cc1f3cbf494097861de73d9c000d8f1b7/ddtrace-3.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:53110eca9026052c37751018e1a2d7ce6631fe54d456ebf92e575e6f75d14781", size = 5603897, upload-time = "2025-10-21T19:26:45.52Z" },
{ url = "https://files.pythonhosted.org/packages/18/90/a2f44bdac307ed2f428ac16168d57d601586489e2e31cdb7b8360c5a1981/ddtrace-3.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:0246ccbdf2b4f393410cb798219737fff106161941c4aa9048ce7d812192806c", size = 5326912, upload-time = "2025-10-21T19:26:47.654Z" },
{ url = "https://files.pythonhosted.org/packages/e0/18/763b25401be47ede7b6118f0470ad081c051e8ed60e8beeed0c2b0f4c7ef/ddtrace-3.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4c3f770eee6085155c52f24e9d14e413977a3ea2eb0f0338e17bf5e87159a11c", size = 6335314, upload-time = "2025-10-21T19:26:50.051Z" },
{ url = "https://files.pythonhosted.org/packages/3d/3a/4be0d1ad80384b888c0959e0198a6413b5bcfd17da189289f27b487bfa26/ddtrace-3.16.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:296c820c6612fbf863534f638c10d811b31b7985cc30c8f20679d4d70249464d", size = 6684784, upload-time = "2025-10-21T19:26:53.557Z" },
{ url = "https://files.pythonhosted.org/packages/88/6e/5bc4ec8404a65832dd5f045064f7af41a02183dbd3f7f7c3e13276d75874/ddtrace-3.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:867101dc679c6b77ce77524e434ebe87b22bbece04f10226960fbd38811f4022", size = 7382532, upload-time = "2025-10-21T19:26:55.623Z" },
{ url = "https://files.pythonhosted.org/packages/61/e2/b0b220b76fb90a91c8a322a1511ff6f28f0e7c9f5174bd2492dd28392bfb/ddtrace-3.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35dce94534d5a13914ca943f4dd718f76c941f50f99dcfab729b0b9ec3587e0a", size = 7656342, upload-time = "2025-10-21T19:26:57.636Z" },
{ url = "https://files.pythonhosted.org/packages/f1/ad/f9dbfd6be8fb032d087b3362558947bb39a0329a30b84ead30fcf2c9668e/ddtrace-3.16.2-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:975de343cf9c643a5d7b0006d36fe716cdf9957012faea66c1001c6195a964f8", size = 5504371, upload-time = "2025-10-21T19:27:00.054Z" },
{ url = "https://files.pythonhosted.org/packages/d0/e1/c7375cffa27f4558d2b3e5cba043a9770bce9373f0b9fd2c2441992e7dc9/ddtrace-3.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:053d9311c4db88f91b197209be945694afb360dc12153c9fc9eee676db1c2ea3", size = 8398772, upload-time = "2025-10-21T19:27:02.101Z" },
{ url = "https://files.pythonhosted.org/packages/21/3a/3f6eccf9ddf65bc8dd6fef5c94d742df6cbe3a112dab53f60da4fa691420/ddtrace-3.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f3b94723d50ab235608c1e4d216c4ac22dfb9a21a253a488097648e456f1d82b", size = 6588990, upload-time = "2025-10-21T19:27:04.512Z" },
{ url = "https://files.pythonhosted.org/packages/cc/35/32bdb07845720a2b694495253fb341506deb6b745ed674b5281516824273/ddtrace-3.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b07c930bf83ed54996656faafbe065f96402d73231c7005b97f98cd0f002713a", size = 8731538, upload-time = "2025-10-21T19:27:07.007Z" },
{ url = "https://files.pythonhosted.org/packages/fb/ad/e6fe3e4316191fa54c1211e553e6ee20aa0f3c148096001c55e74b556c39/ddtrace-3.16.2-cp312-cp312-win32.whl", hash = "sha256:c778dfd7bf839ca94b815fcf1985b390060694806535bdd69641986b76915894", size = 5036303, upload-time = "2025-10-21T19:27:09.755Z" },
{ url = "https://files.pythonhosted.org/packages/d0/20/3fa392f338cfd22c1c8229e6b260c1ab67a01d6e4507cae3c4de7d720e5b/ddtrace-3.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:3a81183b1681ddc04062dbe990770b50b1062bceb5b1780f2526daa6ecd3a909", size = 5594871, upload-time = "2025-10-21T19:27:11.857Z" },
{ url = "https://files.pythonhosted.org/packages/39/65/3d08e2e6ac26e8f016e6ced86c070d200380e7d37c73de7f22986c162be5/ddtrace-3.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:1b74d506e660244f7df29e2cda643f058e591da66ef8d2a71435c78eda3b47ce", size = 5313712, upload-time = "2025-10-21T19:27:14.084Z" },
{ url = "https://files.pythonhosted.org/packages/13/2e/a7dde061252cd565f92d627b0c372db2747874bdbc17333f154b2be6bb18/ddtrace-3.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:74c7b6c2ef0043b902c6cd7eda3cad5a866d1251c83f14b8c49abb8e1e67a2b1", size = 6329879, upload-time = "2025-10-21T19:27:21.968Z" },
{ url = "https://files.pythonhosted.org/packages/9e/ba/d6e486dc27f9ba04be4ad90789dfbceb394ea5960219ed83aef5c7878634/ddtrace-3.16.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:adfe6373014b9f37a99d894986c6117b7fb0486d52b4990c2647f93e838c540a", size = 6679188, upload-time = "2025-10-21T19:27:24.339Z" },
{ url = "https://files.pythonhosted.org/packages/06/d5/2090abf84fe9cfc941b3a903c638b8157d7a15d016348df6ce7cad733e1c/ddtrace-3.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:66e68846b9b617a1311d8b76c1c5d7bee552cb0af68dc2ccf9af91abc55a74af", size = 7377794, upload-time = "2025-10-21T19:27:26.986Z" },
{ url = "https://files.pythonhosted.org/packages/b6/88/07bccc2d9b22ec6114015468772a3451697fcc3c1a39bfb357bdbdfb43e2/ddtrace-3.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6822b1b9cd2f6ad3db4d76a05e22adad21878bcff6bd9ba7b7cd581c9136c00c", size = 7649664, upload-time = "2025-10-21T19:27:29.294Z" },
{ url = "https://files.pythonhosted.org/packages/6f/e7/433d8d2d5a9614a1adcadf54cf3c21afe151fa714208ff7fd40e3435496c/ddtrace-3.16.2-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:9f4f5adedcfb1f42a02ac7c7370887001ce71826141885da49bb2da39a440125", size = 5498780, upload-time = "2025-10-21T19:27:31.703Z" },
{ url = "https://files.pythonhosted.org/packages/4c/a1/1339416eeeb39dab87f750038460f922e40681b3ca0089d6c6a5139c00ff/ddtrace-3.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:070cf3f7571a2b8640a6663f476c157b110b3a88b1c7f7941615b746bc4c6c99", size = 8394692, upload-time = "2025-10-21T19:27:34.52Z" },
{ url = "https://files.pythonhosted.org/packages/75/24/a380640e605daebfb7791bdcc92bef8d7474ad913807276f9f21117aa323/ddtrace-3.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:873a06e21bd00a20d4453cea30bf44e877b657d0cf911aabbf6c8945cfbb31b6", size = 6584925, upload-time = "2025-10-21T19:27:37.209Z" },
{ url = "https://files.pythonhosted.org/packages/15/80/6126563c16d9a28cb03f4318bde899e402662e14e97351569640ff833608/ddtrace-3.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:af28ecad6a6379bb5e18979baeb3defe5dea101bc0608e5032a18aa4c38340b2", size = 8727833, upload-time = "2025-10-21T19:27:39.584Z" },
{ url = "https://files.pythonhosted.org/packages/ce/9c/1b0aa8e0984e3d4f58e7ee9e6a5b938ae45645847f7ae52583fbaff1314e/ddtrace-3.16.2-cp313-cp313-win32.whl", hash = "sha256:a42fc81e7bd6a80c297ee891ac8169a0d1efcdd6b7b0ea5478f3bc3deb1aa8af", size = 5033513, upload-time = "2025-10-21T19:27:44.02Z" },
{ url = "https://files.pythonhosted.org/packages/6b/66/dfb088db59b580a6e1ff0a180029ff90dab684b6a60abfe4a5dad0f8e19b/ddtrace-3.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:f61291e94d37ae1de5456e11e1f55a56eda6a622a0b4e6a1e59b481cc87e29ba", size = 5592118, upload-time = "2025-10-21T19:27:47.486Z" },
{ url = "https://files.pythonhosted.org/packages/8a/f4/164c40d8cd0392e86ea58194428fa1bd0fff7224ee01d5fa77abe4ae467c/ddtrace-3.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:c3dedde96f9906556c20c76ef0e7f8a2fc24db394b5a095ce6c6dab21facf20f", size = 5311416, upload-time = "2025-10-21T19:27:50.107Z" },
]
[[package]]
name = "debugpy"
version = "1.8.16"
@@ -1027,6 +1085,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/f1/135acbaffe4b2e63addecfc2a6c2ecf9ea3e5394aa2a9a829e3eb6f2098d/e2b_code_interpreter-2.0.0-py3-none-any.whl", hash = "sha256:273642d4dd78f09327fb1553fe4f7ddcf17892b78f98236e038d29985e42dca5", size = 12939, upload-time = "2025-08-22T10:16:55.698Z" },
]
[[package]]
name = "envier"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/19/e7/4fe4d3f6e21213cea9bcddc36ba60e6ae4003035f9ce8055e6a9f0322ddb/envier-0.6.1.tar.gz", hash = "sha256:3309a01bb3d8850c9e7a31a5166d5a836846db2faecb79b9cb32654dd50ca9f9", size = 10063, upload-time = "2024-10-22T09:56:47.226Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/e9/30493b1cc967f7c07869de4b2ab3929151a58e6bb04495015554d24b61db/envier-0.6.1-py3-none-any.whl", hash = "sha256:73609040a76be48bbcb97074d9969666484aa0de706183a6e9ef773156a8a6a9", size = 10638, upload-time = "2024-10-22T09:56:45.968Z" },
]
[[package]]
name = "eval-type-backport"
version = "0.2.2"
@@ -1393,7 +1460,7 @@ wheels = [
[[package]]
name = "google-api-core"
version = "2.25.1"
version = "2.27.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "google-auth" },
@@ -1402,14 +1469,14 @@ dependencies = [
{ name = "protobuf" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443, upload-time = "2025-06-12T20:52:20.439Z" }
sdist = { url = "https://files.pythonhosted.org/packages/da/99/6c8b44ecc28026fd9441d7fcc5434ee1b3976c491f2f810b464c4702c975/google_api_core-2.27.0.tar.gz", hash = "sha256:d32e2f5dd0517e91037169e75bf0a9783b255aff1d11730517c0b2b29e9db06a", size = 168851, upload-time = "2025-10-22T23:54:14.195Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" },
{ url = "https://files.pythonhosted.org/packages/77/93/ecf9f7caa99c71e969091e9a78789f11b2dea5c684917eab7c54a8d13560/google_api_core-2.27.0-py3-none-any.whl", hash = "sha256:779a380db4e21a4ee3d717cf8efbf324e53900bf37e1ffb273e5348a9916dd42", size = 167110, upload-time = "2025-10-22T23:54:12.805Z" },
]
[[package]]
name = "google-api-python-client"
version = "2.179.0"
version = "2.185.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "google-api-core" },
@@ -1418,9 +1485,9 @@ dependencies = [
{ name = "httplib2" },
{ name = "uritemplate" },
]
sdist = { url = "https://files.pythonhosted.org/packages/73/ed/6e7865324252ea0a9f7c8171a3a00439a1e8447a5dc08e6d6c483777bb38/google_api_python_client-2.179.0.tar.gz", hash = "sha256:76a774a49dd58af52e74ce7114db387e58f0aaf6760c9cf9201ab6d731d8bd8d", size = 13397672, upload-time = "2025-08-13T18:45:28.838Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/5a/6f9b49d67ea91376305fdb8bbf2877c746d756e45fd8fb7d2e32d6dad19b/google_api_python_client-2.185.0.tar.gz", hash = "sha256:aa1b338e4bb0f141c2df26743f6b46b11f38705aacd775b61971cbc51da089c3", size = 13885609, upload-time = "2025-10-17T15:00:35.623Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/d4/2568d5d907582cc145f3ffede43879746fd4b331308088a0fc57f7ecdbca/google_api_python_client-2.179.0-py3-none-any.whl", hash = "sha256:79ab5039d70c59dab874fd18333fca90fb469be51c96113cb133e5fc1f0b2a79", size = 13955142, upload-time = "2025-08-13T18:45:25.944Z" },
{ url = "https://files.pythonhosted.org/packages/fa/28/be3b17bd6a190c8c2ec9e4fb65d43e6ecd7b7a1bb19ccc1d9ab4f687a58c/google_api_python_client-2.185.0-py3-none-any.whl", hash = "sha256:00fe173a4b346d2397fbe0d37ac15368170dfbed91a0395a66ef2558e22b93fc", size = 14453595, upload-time = "2025-10-17T15:00:33.176Z" },
]
[[package]]
@@ -1771,14 +1838,14 @@ wheels = [
[[package]]
name = "httplib2"
version = "0.22.0"
version = "0.31.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyparsing" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116, upload-time = "2023-03-21T22:29:37.214Z" }
sdist = { url = "https://files.pythonhosted.org/packages/52/77/6653db69c1f7ecfe5e3f9726fdadc981794656fcd7d98c4209fecfea9993/httplib2-0.31.0.tar.gz", hash = "sha256:ac7ab497c50975147d4f7b1ade44becc7df2f8954d42b38b3d69c515f531135c", size = 250759, upload-time = "2025-09-11T12:16:03.403Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854, upload-time = "2023-03-21T22:29:35.683Z" },
{ url = "https://files.pythonhosted.org/packages/8c/a2/0d269db0f6163be503775dc8b6a6fa15820cc9fdc866f6ba608d86b721f2/httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24", size = 91148, upload-time = "2025-09-11T12:16:01.803Z" },
]
[[package]]
@@ -2308,6 +2375,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/73/91a506e17bb1bc6d20c2c04cf7b459dc58951bfbfe7f97f2c952646b4500/langsmith-0.4.18-py3-none-any.whl", hash = "sha256:ad63154f503678356aadf5b999f40393b4bbd332aee2d04cde3e431c61f2e1c2", size = 376444, upload-time = "2025-08-26T17:00:03.564Z" },
]
[[package]]
name = "legacy-cgi"
version = "2.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a6/ed/300cabc9693209d5a03e2ebc5eb5c4171b51607c08ed84a2b71c9015e0f3/legacy_cgi-2.6.3.tar.gz", hash = "sha256:4c119d6cb8e9d8b6ad7cc0ddad880552c62df4029622835d06dfd18f438a8154", size = 24401, upload-time = "2025-03-27T00:48:56.957Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/33/68c6c38193684537757e0d50a7ccb4f4656e5c2f7cd2be737a9d4a1bff71/legacy_cgi-2.6.3-py3-none-any.whl", hash = "sha256:6df2ea5ae14c71ef6f097f8b6372b44f6685283dc018535a75c924564183cdab", size = 19851, upload-time = "2025-03-27T00:48:55.366Z" },
]
[[package]]
name = "letta"
version = "0.12.1"
@@ -2413,7 +2489,6 @@ dev = [
{ name = "pytest-order" },
]
experimental = [
{ name = "google-cloud-profiler" },
{ name = "granian", extra = ["reload", "uvloop"] },
{ name = "uvloop" },
]
@@ -2438,6 +2513,10 @@ postgres = [
{ name = "psycopg2" },
{ name = "psycopg2-binary" },
]
profiling = [
{ name = "ddtrace" },
{ name = "google-cloud-profiler" },
]
redis = [
{ name = "redis" },
]
@@ -2468,6 +2547,7 @@ requires-dist = [
{ name = "certifi", specifier = ">=2025.6.15" },
{ name = "colorama", specifier = ">=0.4.6" },
{ name = "datamodel-code-generator", extras = ["http"], specifier = ">=0.25.0" },
{ name = "ddtrace", marker = "extra == 'profiling'", specifier = ">=2.18.2" },
{ name = "demjson3", specifier = ">=3.0.6" },
{ name = "docker", marker = "extra == 'desktop'", specifier = ">=7.1.0" },
{ name = "docker", marker = "extra == 'external-tools'", specifier = ">=7.1.0" },
@@ -2478,7 +2558,7 @@ requires-dist = [
{ name = "faker", specifier = ">=36.1.0" },
{ name = "fastapi", marker = "extra == 'desktop'", specifier = ">=0.115.6" },
{ name = "fastapi", marker = "extra == 'server'", specifier = ">=0.115.6" },
{ name = "google-cloud-profiler", marker = "extra == 'experimental'", specifier = ">=4.1.0" },
{ name = "google-cloud-profiler", marker = "extra == 'profiling'", specifier = ">=4.1.0" },
{ name = "google-genai", specifier = ">=1.15.0" },
{ name = "granian", extras = ["uvloop", "reload"], marker = "extra == 'experimental'", specifier = ">=2.3.2" },
{ name = "grpcio", specifier = ">=1.68.1" },
@@ -2564,7 +2644,7 @@ requires-dist = [
{ name = "wikipedia", marker = "extra == 'desktop'", specifier = ">=1.4.0" },
{ name = "wikipedia", marker = "extra == 'external-tools'", specifier = ">=1.4.0" },
]
provides-extras = ["postgres", "redis", "pinecone", "sqlite", "experimental", "server", "bedrock", "dev", "cloud-tool-sandbox", "modal", "external-tools", "desktop"]
provides-extras = ["postgres", "redis", "pinecone", "sqlite", "experimental", "server", "bedrock", "dev", "cloud-tool-sandbox", "modal", "external-tools", "desktop", "profiling"]
[[package]]
name = "letta-client"