Files
letta-server/letta/errors.py
Sarah Wooders acd8dd7bcf feat: make embedding_config optional on agent creation (#7553)
* feat: make embedding_config optional on agent creation

- Remove requirement for embedding_config in agent creation
- Add EmbeddingConfigRequiredError for operations that need embeddings
- Add null checks in sleeptime agent creation, passage insert, archive creation
- Register new error in app.py exception handlers

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: update API schemas for optional embedding_config

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-12 10:57:19 -08:00

400 lines
14 KiB
Python

import json
from enum import Enum
from typing import TYPE_CHECKING, Dict, List, Optional, Union
# Avoid circular imports
if TYPE_CHECKING:
from letta.schemas.message import Message
class ErrorCode(Enum):
"""Enum for error codes used by client."""
NOT_FOUND = "NOT_FOUND"
UNAUTHENTICATED = "UNAUTHENTICATED"
PERMISSION_DENIED = "PERMISSION_DENIED"
INVALID_ARGUMENT = "INVALID_ARGUMENT"
INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"
CONTEXT_WINDOW_EXCEEDED = "CONTEXT_WINDOW_EXCEEDED"
RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
TIMEOUT = "TIMEOUT"
CONFLICT = "CONFLICT"
EXPIRED = "EXPIRED"
class LettaError(Exception):
"""Base class for all Letta related errors."""
def __init__(self, message: str, code: Optional[ErrorCode] = None, details: Optional[Union[Dict, str, object]] = None):
if details is None:
details = {}
self.message = message
self.code = code
self.details = details
super().__init__(message)
def __str__(self) -> str:
if self.code:
return f"{self.code.value}: {self.message}"
return self.message
def __repr__(self) -> str:
return f"{self.__class__.__name__}(message='{self.message}', code='{self.code}', details={self.details})"
class PendingApprovalError(LettaError):
"""Error raised when attempting an operation while agent is waiting for tool approval."""
def __init__(self, pending_request_id: Optional[str] = None):
self.pending_request_id = pending_request_id
message = "Cannot send a new message: The agent is waiting for approval on a tool call. Please approve or deny the pending request before continuing."
code = ErrorCode.CONFLICT
details = {"error_code": "PENDING_APPROVAL", "pending_request_id": pending_request_id}
super().__init__(message=message, code=code, details=details)
class LettaToolCreateError(LettaError):
"""Error raised when a tool cannot be created."""
default_error_message = "Error creating tool."
def __init__(self, message=None):
super().__init__(message=message or self.default_error_message)
class LettaToolNameConflictError(LettaError):
"""Error raised when a tool name already exists."""
def __init__(self, tool_name: str):
super().__init__(
message=f"Tool with name '{tool_name}' already exists in your organization",
code=ErrorCode.INVALID_ARGUMENT,
details={"tool_name": tool_name},
)
class LettaToolNameSchemaMismatchError(LettaToolCreateError):
"""Error raised when a tool name our source codedoes not match the name in the JSON schema."""
def __init__(self, tool_name: str, json_schema_name: str, source_code: str):
super().__init__(
message=f"Tool name '{tool_name}' does not match the name in the JSON schema '{json_schema_name}' or in the source code `{source_code}`",
)
class LettaConfigurationError(LettaError):
"""Error raised when there are configuration-related issues."""
def __init__(self, message: str, missing_fields: Optional[List[str]] = None):
self.missing_fields = missing_fields or []
super().__init__(message=message, details={"missing_fields": self.missing_fields})
class EmbeddingConfigRequiredError(LettaError):
"""Error raised when an operation requires embedding_config but the agent doesn't have one configured."""
def __init__(self, agent_id: Optional[str] = None, operation: Optional[str] = None):
self.agent_id = agent_id
self.operation = operation
message = "This operation requires an embedding configuration, but the agent does not have one configured."
if operation:
message = f"Operation '{operation}' requires an embedding configuration, but the agent does not have one configured."
details = {"agent_id": agent_id, "operation": operation}
super().__init__(message=message, code=ErrorCode.INVALID_ARGUMENT, details=details)
class LettaAgentNotFoundError(LettaError):
"""Error raised when an agent is not found."""
class LettaUserNotFoundError(LettaError):
"""Error raised when a user is not found."""
class LettaUnsupportedFileUploadError(LettaError):
"""Error raised when an unsupported file upload is attempted."""
class LettaInvalidArgumentError(LettaError):
"""Error raised when an invalid argument is provided."""
def __init__(self, message: str, argument_name: Optional[str] = None):
details = {"argument_name": argument_name} if argument_name else {}
super().__init__(message=message, code=ErrorCode.INVALID_ARGUMENT, details=details)
class LettaImageFetchError(LettaError):
"""Error raised when fetching an image from a URL fails."""
def __init__(self, url: str, reason: str):
details = {"url": url, "reason": reason}
super().__init__(
message=f"Failed to fetch image from {url}: {reason}",
code=ErrorCode.INVALID_ARGUMENT,
details=details,
)
class LettaMCPError(LettaError):
"""Base error for MCP-related issues."""
class LettaInvalidMCPSchemaError(LettaMCPError):
"""Error raised when an invalid MCP schema is provided."""
def __init__(self, server_name: str, mcp_tool_name: str, reasons: List[str]):
details = {"server_name": server_name, "mcp_tool_name": mcp_tool_name, "reasons": reasons}
super().__init__(
message=f"MCP tool {mcp_tool_name} has an invalid schema and cannot be attached - reasons: {reasons}",
code=ErrorCode.INVALID_ARGUMENT,
details=details,
)
class LettaMCPConnectionError(LettaMCPError):
"""Error raised when unable to connect to MCP server."""
def __init__(self, message: str, server_name: Optional[str] = None):
details = {"server_name": server_name} if server_name else {}
super().__init__(message=message, code=ErrorCode.INTERNAL_SERVER_ERROR, details=details)
class LettaMCPTimeoutError(LettaMCPError):
"""Error raised when MCP server operation times out."""
def __init__(self, message: str, server_name: Optional[str] = None):
details = {"server_name": server_name} if server_name else {}
super().__init__(message=message, code=ErrorCode.TIMEOUT, details=details)
class LettaServiceUnavailableError(LettaError):
"""Error raised when a required service is unavailable."""
def __init__(self, message: str, service_name: Optional[str] = None):
details = {"service_name": service_name} if service_name else {}
super().__init__(message=message, code=ErrorCode.INTERNAL_SERVER_ERROR, details=details)
class LettaUnexpectedStreamCancellationError(LettaError):
"""Error raised when a streaming request is terminated unexpectedly."""
class LettaExpiredError(LettaError):
"""Error raised when a resource has expired."""
def __init__(self, message: str):
super().__init__(message=message, code=ErrorCode.EXPIRED)
class LLMError(LettaError):
pass
class LLMConnectionError(LLMError):
"""Error when unable to connect to LLM service"""
class LLMRateLimitError(LLMError):
"""Error when rate limited by LLM service"""
class LLMBadRequestError(LLMError):
"""Error when LLM service cannot process request"""
class LLMAuthenticationError(LLMError):
"""Error when authentication fails with LLM service"""
class LLMPermissionDeniedError(LLMError):
"""Error when permission is denied by LLM service"""
class LLMNotFoundError(LLMError):
"""Error when requested resource is not found"""
class LLMUnprocessableEntityError(LLMError):
"""Error when request is well-formed but semantically invalid"""
class LLMServerError(LLMError):
"""Error indicating an internal server error occurred within the LLM service itself
while processing the request."""
class LLMTimeoutError(LLMError):
"""Error when LLM request times out"""
class LLMProviderOverloaded(LLMError):
"""Error when LLM provider is overloaded"""
class BedrockPermissionError(LettaError):
"""Exception raised for errors in the Bedrock permission process."""
def __init__(self, message="User does not have access to the Bedrock model with the specified ID."):
super().__init__(message=message)
class BedrockError(LettaError):
"""Exception raised for errors in the Bedrock process."""
def __init__(self, message="Error with Bedrock model."):
super().__init__(message=message)
class LLMJSONParsingError(LettaError):
"""Exception raised for errors in the JSON parsing process."""
def __init__(self, message="Error parsing JSON generated by LLM"):
super().__init__(message=message)
class LocalLLMError(LettaError):
"""Generic catch-all error for local LLM problems"""
def __init__(self, message="Encountered an error while running local LLM"):
super().__init__(message=message)
class LocalLLMConnectionError(LettaError):
"""Error for when local LLM cannot be reached with provided IP/port"""
def __init__(self, message="Could not connect to local LLM"):
super().__init__(message=message)
class ContextWindowExceededError(LettaError):
"""Error raised when the context window is exceeded but further summarization fails."""
def __init__(self, message: str, details: dict = {}):
error_message = f"{message} ({details})"
super().__init__(
message=error_message,
code=ErrorCode.CONTEXT_WINDOW_EXCEEDED,
details=details,
)
class SystemPromptTokenExceededError(ContextWindowExceededError):
"""Error raised when the system prompt token estimate exceeds the context window."""
def __init__(self, system_prompt_token_estimate: int, context_window: int):
message = f"The system prompt tokens {system_prompt_token_estimate} exceeds the context window {context_window}. Please reduce the size of your system prompt, memory blocks, or increase the context window."
super().__init__(
message=message, details={"system_prompt_token_estimate": system_prompt_token_estimate, "context_window": context_window}
)
class RateLimitExceededError(LettaError):
"""Error raised when the llm rate limiter throttles api requests."""
def __init__(self, message: str, max_retries: int):
error_message = f"{message} ({max_retries})"
super().__init__(
message=error_message,
code=ErrorCode.RATE_LIMIT_EXCEEDED,
details={"max_retries": max_retries},
)
class LettaMessageError(LettaError):
"""Base error class for handling message-related errors."""
messages: List[Union["Message", "LettaMessage"]]
default_error_message: str = "An error occurred with the message."
def __init__(self, *, messages: List[Union["Message", "LettaMessage"]], explanation: Optional[str] = None) -> None:
error_msg = self.construct_error_message(messages, self.default_error_message, explanation)
super().__init__(error_msg)
self.messages = messages
@staticmethod
def construct_error_message(messages: List[Union["Message", "LettaMessage"]], error_msg: str, explanation: Optional[str] = None) -> str:
"""Helper method to construct a clean and formatted error message."""
if explanation:
error_msg += f" (Explanation: {explanation})"
# Pretty print out message JSON
message_json = json.dumps([message.model_dump() for message in messages], indent=4)
return f"{error_msg}\n\n{message_json}"
class MissingToolCallError(LettaMessageError):
"""Error raised when a message is missing a tool call."""
default_error_message = "The message is missing a tool call."
class InvalidToolCallError(LettaMessageError):
"""Error raised when a message uses an invalid tool call."""
default_error_message = "The message uses an invalid tool call or has improper usage of a tool call."
class MissingInnerMonologueError(LettaMessageError):
"""Error raised when a message is missing an inner monologue."""
default_error_message = "The message is missing an inner monologue."
class InvalidInnerMonologueError(LettaMessageError):
"""Error raised when a message has a malformed inner monologue."""
default_error_message = "The message has a malformed inner monologue."
class HandleNotFoundError(LettaError):
"""Error raised when a handle is not found."""
def __init__(self, handle: str, available_handles: List[str]):
super().__init__(
message=f"Handle {handle} not found, must be one of {available_handles}",
code=ErrorCode.NOT_FOUND,
)
class AgentFileExportError(Exception):
"""Exception raised during agent file export operations"""
class AgentNotFoundForExportError(AgentFileExportError):
"""Exception raised when requested agents are not found during export"""
def __init__(self, missing_ids: List[str]):
self.missing_ids = missing_ids
super().__init__(f"The following agent IDs were not found: {missing_ids}")
class AgentExportIdMappingError(AgentFileExportError):
"""Exception raised when ID mapping fails during export conversion"""
def __init__(self, db_id: str, entity_type: str):
self.db_id = db_id
self.entity_type = entity_type
super().__init__(
f"Unexpected new {entity_type} ID '{db_id}' encountered during conversion. "
f"All IDs should have been mapped during agent processing."
)
class AgentExportProcessingError(AgentFileExportError):
"""Exception raised when general export processing fails"""
def __init__(self, message: str, original_error: Optional[Exception] = None):
self.original_error = original_error
super().__init__(f"Export failed: {message}")
class AgentFileImportError(Exception):
"""Exception raised during agent file import operations"""
class RunCancelError(LettaError):
"""Error raised when a run cannot be cancelled."""
def __init__(self, message: str):
super().__init__(message=message)