Files
letta-server/letta/helpers/tool_helpers.py
Sarah Wooders 5730f69ecf feat: modal tool execution - NO FEATURE FLAGS USES MODAL [LET-4357] (#5120)
* initial commit

* add delay to deploy

* fix tests

* add tests

* passing tests

* cleanup

* and use modal

* working on modal

* gate on tool metadata

* agent state

* cleanup

---------

Co-authored-by: Letta Bot <noreply@letta.com>
2025-11-13 15:36:56 -08:00

70 lines
2.2 KiB
Python

import hashlib
from letta.constants import MODAL_VERSION_HASH_LENGTH
from letta.schemas.tool import Tool
def _serialize_dependencies(tool: Tool) -> str:
"""
Serialize dependencies in a consistent way for hashing.
TODO: This should be improved per LET-3770 to ensure consistent ordering.
For now, we convert to string representation.
"""
parts = []
if tool.pip_requirements:
# TODO: Sort these consistently
parts.append(f"pip:{str(tool.pip_requirements)}")
if tool.npm_requirements:
# TODO: Sort these consistently
parts.append(f"npm:{str(tool.npm_requirements)}")
return ";".join(parts)
def compute_tool_hash(tool: Tool):
"""
Calculate a hash representing the current version of the tool and configuration.
This hash changes when:
- Tool source code changes
- Tool dependencies change
- Sandbox configuration changes
- Language/runtime changes
"""
components = [
tool.source_code if tool.source_code else "",
tool.source_type if tool.source_type else "",
_serialize_dependencies(tool),
]
combined = "|".join(components)
return hashlib.sha256(combined.encode()).hexdigest()[:MODAL_VERSION_HASH_LENGTH]
def generate_modal_function_name(tool_name: str, organization_id: str, project_id: str = "default") -> str:
"""
Generate a Modal function name from tool name and project ID.
Shortens the project ID to just the prefix and first UUID segment.
Args:
tool_name: Name of the tool
organization_id: Full organization ID (not used in function name, but kept for future use)
project_id: Project ID (e.g., project-12345678-90ab-cdef-1234-567890abcdef or "default")
Returns:
Modal function name (e.g., tool_name_project-12345678 or tool_name_default)
"""
from letta.constants import MAX_TOOL_NAME_LENGTH
max_tool_name_length = 64
# Shorten the organization_id to just the first segment (e.g., project-12345678)
short_organization_id = organization_id[: (max_tool_name_length - MAX_TOOL_NAME_LENGTH - 1)]
# make extra sure the tool name is not too long
name = f"{tool_name[:MAX_TOOL_NAME_LENGTH]}_{short_organization_id}"
# safe fallback
return name