chore: Move environment to settings [LET-4080] (#4265)

Move environment to settings
This commit is contained in:
Matthew Zhou
2025-08-27 14:52:35 -07:00
committed by GitHub
parent 9f919cf925
commit 2fa546e15d
9 changed files with 24 additions and 32 deletions

View File

@@ -1,6 +1,5 @@
import asyncio import asyncio
import json import json
import os
import time import time
import traceback import traceback
import warnings import warnings
@@ -71,7 +70,7 @@ from letta.services.step_manager import StepManager
from letta.services.telemetry_manager import NoopTelemetryManager, TelemetryManager from letta.services.telemetry_manager import NoopTelemetryManager, TelemetryManager
from letta.services.tool_executor.tool_execution_sandbox import ToolExecutionSandbox from letta.services.tool_executor.tool_execution_sandbox import ToolExecutionSandbox
from letta.services.tool_manager import ToolManager from letta.services.tool_manager import ToolManager
from letta.settings import settings, summarizer_settings from letta.settings import model_settings, settings, summarizer_settings
from letta.streaming_interface import StreamingRefreshCLIInterface from letta.streaming_interface import StreamingRefreshCLIInterface
from letta.system import get_heartbeat, get_token_limit_warning, package_function_response, package_summarize_message, package_user_message from letta.system import get_heartbeat, get_token_limit_warning, package_function_response, package_summarize_message, package_user_message
from letta.utils import count_tokens, get_friendly_error_msg, get_tool_call_id, log_telemetry, parse_json, validate_function_response from letta.utils import count_tokens, get_friendly_error_msg, get_tool_call_id, log_telemetry, parse_json, validate_function_response
@@ -1304,7 +1303,7 @@ class Agent(BaseAgent):
) )
async def get_context_window_async(self) -> ContextWindowOverview: async def get_context_window_async(self) -> ContextWindowOverview:
if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION" and os.getenv("ANTHROPIC_API_KEY"): if settings.environment == "PRODUCTION" and model_settings.anthropic_api_key:
return await self.get_context_window_from_anthropic_async() return await self.get_context_window_from_anthropic_async()
return await self.get_context_window_from_tiktoken_async() return await self.get_context_window_from_tiktoken_async()

View File

@@ -1,6 +1,5 @@
import asyncio import asyncio
import json import json
import os
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, List
@@ -138,7 +137,7 @@ def send_message_to_agent_async(self: "Agent", message: str, other_agent_id: str
Returns: Returns:
str: A confirmation message indicating the message was successfully sent. str: A confirmation message indicating the message was successfully sent.
""" """
if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION": if settings.environment == "PRODUCTION":
raise RuntimeError("This tool is not allowed to be run on Letta Cloud.") raise RuntimeError("This tool is not allowed to be run on Letta Cloud.")
message = ( message = (

View File

@@ -1,16 +1,16 @@
import os
import sys import sys
import uuid import uuid
from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.resources import Resource
from letta import __version__ as letta_version from letta import __version__ as letta_version
from letta.settings import settings
_resources = {} _resources = {}
def get_resource(service_name: str) -> Resource: def get_resource(service_name: str) -> Resource:
_env = os.getenv("LETTA_ENVIRONMENT") _env = settings.environment
if service_name not in _resources: if service_name not in _resources:
resource_dict = { resource_dict = {
"service.name": service_name, "service.name": service_name,

View File

@@ -1,5 +1,4 @@
import asyncio import asyncio
import os
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Set, Tuple from typing import Any, Dict, List, Optional, Set, Tuple
@@ -3423,7 +3422,7 @@ class AgentManager:
) )
calculator = ContextWindowCalculator() calculator = ContextWindowCalculator()
if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION" or agent_state.llm_config.model_endpoint_type == "anthropic": if settings.environment == "PRODUCTION" or agent_state.llm_config.model_endpoint_type == "anthropic":
anthropic_client = LLMClient.create(provider_type=ProviderType.anthropic, actor=actor) anthropic_client = LLMClient.create(provider_type=ProviderType.anthropic, actor=actor)
model = agent_state.llm_config.model if agent_state.llm_config.model_endpoint_type == "anthropic" else None model = agent_state.llm_config.model if agent_state.llm_config.model_endpoint_type == "anthropic" else None

View File

@@ -1,4 +1,3 @@
import os
import uuid import uuid
from datetime import datetime from datetime import datetime
from typing import List, Literal, Optional, Set from typing import List, Literal, Optional, Set
@@ -1208,7 +1207,7 @@ def calculate_base_tools(is_v2: bool) -> Set[str]:
def calculate_multi_agent_tools() -> Set[str]: def calculate_multi_agent_tools() -> Set[str]:
"""Calculate multi-agent tools, excluding local-only tools in production environment.""" """Calculate multi-agent tools, excluding local-only tools in production environment."""
if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION": if settings.environment == "PRODUCTION":
return set(MULTI_AGENT_TOOLS) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS) return set(MULTI_AGENT_TOOLS) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS)
else: else:
return set(MULTI_AGENT_TOOLS) return set(MULTI_AGENT_TOOLS)

View File

@@ -1,5 +1,4 @@
import asyncio import asyncio
import os
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from letta.log import get_logger from letta.log import get_logger
@@ -13,6 +12,7 @@ from letta.schemas.tool import Tool
from letta.schemas.tool_execution_result import ToolExecutionResult from letta.schemas.tool_execution_result import ToolExecutionResult
from letta.schemas.user import User from letta.schemas.user import User
from letta.services.tool_executor.tool_executor_base import ToolExecutor from letta.services.tool_executor.tool_executor_base import ToolExecutor
from letta.settings import settings
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -112,7 +112,7 @@ class LettaMultiAgentToolExecutor(ToolExecutor):
} }
async def send_message_to_agent_async(self, agent_state: AgentState, message: str, other_agent_id: str) -> str: async def send_message_to_agent_async(self, agent_state: AgentState, message: str, other_agent_id: str) -> str:
if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION": if settings.environment == "PRODUCTION":
raise RuntimeError("This tool is not allowed to be run on Letta Cloud.") raise RuntimeError("This tool is not allowed to be run on Letta Cloud.")
# 1) Build the prefixed systemmessage # 1) Build the prefixed systemmessage

View File

@@ -1,5 +1,4 @@
import importlib import importlib
import os
import warnings import warnings
from typing import List, Optional, Set, Union from typing import List, Optional, Set, Union
@@ -331,9 +330,7 @@ class ToolManager:
# TODO: This requires a deeper rethink about how we keep all our internal tools up-to-date # TODO: This requires a deeper rethink about how we keep all our internal tools up-to-date
if not after and upsert_base_tools: if not after and upsert_base_tools:
existing_tool_names = {tool.name for tool in tools} existing_tool_names = {tool.name for tool in tools}
base_tool_names = ( base_tool_names = LETTA_TOOL_SET - set(LOCAL_ONLY_MULTI_AGENT_TOOLS) if settings.environment == "PRODUCTION" else LETTA_TOOL_SET
LETTA_TOOL_SET - set(LOCAL_ONLY_MULTI_AGENT_TOOLS) if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION" else LETTA_TOOL_SET
)
missing_base_tools = base_tool_names - existing_tool_names missing_base_tools = base_tool_names - existing_tool_names
# If any base tools are missing, upsert all base tools # If any base tools are missing, upsert all base tools

View File

@@ -205,6 +205,7 @@ class Settings(BaseSettings):
letta_dir: Optional[Path] = Field(Path.home() / ".letta", alias="LETTA_DIR") letta_dir: Optional[Path] = Field(Path.home() / ".letta", alias="LETTA_DIR")
debug: Optional[bool] = False debug: Optional[bool] = False
cors_origins: Optional[list] = cors_origins cors_origins: Optional[list] = cors_origins
environment: Optional[str] = Field(default=None, description="Application environment (PRODUCTION, DEV, etc.)")
# SSE Streaming keepalive settings # SSE Streaming keepalive settings
enable_keepalive: bool = Field(True, description="Enable keepalive messages in SSE streams to prevent timeouts") enable_keepalive: bool = Field(True, description="Enable keepalive messages in SSE streams to prevent timeouts")

View File

@@ -101,7 +101,7 @@ from letta.server.server import SyncServer
from letta.services.block_manager import BlockManager from letta.services.block_manager import BlockManager
from letta.services.helpers.agent_manager_helper import calculate_base_tools, calculate_multi_agent_tools, validate_agent_exists_async from letta.services.helpers.agent_manager_helper import calculate_base_tools, calculate_multi_agent_tools, validate_agent_exists_async
from letta.services.step_manager import FeedbackType from letta.services.step_manager import FeedbackType
from letta.settings import tool_settings from letta.settings import settings, tool_settings
from letta.utils import calculate_file_defaults_based_on_context_window from letta.utils import calculate_file_defaults_based_on_context_window
from tests.helpers.utils import comprehensive_agent_checks, validate_context_window_overview from tests.helpers.utils import comprehensive_agent_checks, validate_context_window_overview
from tests.utils import random_string from tests.utils import random_string
@@ -862,7 +862,7 @@ def test_calculate_multi_agent_tools(set_letta_environment):
"""Test that calculate_multi_agent_tools excludes local-only tools in production.""" """Test that calculate_multi_agent_tools excludes local-only tools in production."""
result = calculate_multi_agent_tools() result = calculate_multi_agent_tools()
if set_letta_environment == "PRODUCTION": if settings.environment == "PRODUCTION":
# Production environment should exclude local-only tools # Production environment should exclude local-only tools
expected_tools = set(MULTI_AGENT_TOOLS) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS) expected_tools = set(MULTI_AGENT_TOOLS) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS)
assert result == expected_tools, "Production should exclude local-only multi-agent tools" assert result == expected_tools, "Production should exclude local-only multi-agent tools"
@@ -889,7 +889,7 @@ async def test_upsert_base_tools_excludes_local_only_in_production(server: SyncS
tools = await server.tool_manager.upsert_base_tools_async(actor=default_user) tools = await server.tool_manager.upsert_base_tools_async(actor=default_user)
tool_names = {tool.name for tool in tools} tool_names = {tool.name for tool in tools}
if set_letta_environment == "PRODUCTION": if settings.environment == "PRODUCTION":
# Production environment should exclude local-only multi-agent tools # Production environment should exclude local-only multi-agent tools
for local_only_tool in LOCAL_ONLY_MULTI_AGENT_TOOLS: for local_only_tool in LOCAL_ONLY_MULTI_AGENT_TOOLS:
assert local_only_tool not in tool_names, f"Local-only tool '{local_only_tool}' should not be upserted in production" assert local_only_tool not in tool_names, f"Local-only tool '{local_only_tool}' should not be upserted in production"
@@ -912,7 +912,7 @@ async def test_upsert_multi_agent_tools_only(server: SyncServer, default_user, s
tools = await server.tool_manager.upsert_base_tools_async(actor=default_user, allowed_types={ToolType.LETTA_MULTI_AGENT_CORE}) tools = await server.tool_manager.upsert_base_tools_async(actor=default_user, allowed_types={ToolType.LETTA_MULTI_AGENT_CORE})
tool_names = {tool.name for tool in tools} tool_names = {tool.name for tool in tools}
if set_letta_environment == "PRODUCTION": if settings.environment == "PRODUCTION":
# Should only have non-local multi-agent tools # Should only have non-local multi-agent tools
expected_tools = set(MULTI_AGENT_TOOLS) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS) expected_tools = set(MULTI_AGENT_TOOLS) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS)
assert tool_names == expected_tools, "Production multi-agent upsert should exclude local-only tools" assert tool_names == expected_tools, "Production multi-agent upsert should exclude local-only tools"
@@ -991,16 +991,14 @@ async def test_create_agent_with_default_source(server: SyncServer, default_user
server.agent_manager.delete_agent(created_agent_no_source.id, default_user) server.agent_manager.delete_agent(created_agent_no_source.id, default_user)
@pytest.fixture(params=["", "PRODUCTION"]) @pytest.fixture(params=[None, "PRODUCTION"])
def set_letta_environment(request): def set_letta_environment(request, monkeypatch):
original = os.environ.get("LETTA_ENVIRONMENT") # Patch the settings.environment attribute
os.environ["LETTA_ENVIRONMENT"] = request.param original = settings.environment
monkeypatch.setattr(settings, "environment", request.param)
yield request.param yield request.param
# Restore original environment variable # Restore original environment
if original is not None: monkeypatch.setattr(settings, "environment", original)
os.environ["LETTA_ENVIRONMENT"] = original
else:
os.environ.pop("LETTA_ENVIRONMENT", None)
async def test_get_context_window_basic( async def test_get_context_window_basic(
@@ -3801,7 +3799,7 @@ async def test_upsert_base_tools(server: SyncServer, default_user):
tools = await server.tool_manager.upsert_base_tools_async(actor=default_user) tools = await server.tool_manager.upsert_base_tools_async(actor=default_user)
# Calculate expected tools accounting for production filtering # Calculate expected tools accounting for production filtering
if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION": if settings.environment == "PRODUCTION":
expected_tool_names = sorted(LETTA_TOOL_SET - set(LOCAL_ONLY_MULTI_AGENT_TOOLS)) expected_tool_names = sorted(LETTA_TOOL_SET - set(LOCAL_ONLY_MULTI_AGENT_TOOLS))
else: else:
expected_tool_names = sorted(LETTA_TOOL_SET) expected_tool_names = sorted(LETTA_TOOL_SET)
@@ -3853,7 +3851,7 @@ async def test_upsert_filtered_base_tools(server: SyncServer, default_user, tool
tool_names = sorted([t.name for t in tools]) tool_names = sorted([t.name for t in tools])
# Adjust expected names for multi-agent tools in production # Adjust expected names for multi-agent tools in production
if tool_type == ToolType.LETTA_MULTI_AGENT_CORE and os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION": if tool_type == ToolType.LETTA_MULTI_AGENT_CORE and settings.environment == "PRODUCTION":
expected_sorted = sorted(set(expected_names) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS)) expected_sorted = sorted(set(expected_names) - set(LOCAL_ONLY_MULTI_AGENT_TOOLS))
else: else:
expected_sorted = sorted(expected_names) expected_sorted = sorted(expected_names)