feat: add ID format validation to agent and user schemas (#9151)
* feat: add ID format validation to agent and user schemas Reuse existing validator types (ToolId, SourceId, BlockId, MessageId, IdentityId, UserId) from letta.validators to enforce ID format validation at the schema level. This ensures malformed IDs are rejected with a 422 validation error instead of causing 500 database errors. Changes: - CreateAgent: validate tool_ids, source_ids, folder_ids, block_ids, identity_ids - UpdateAgent: validate tool_ids, source_ids, folder_ids, block_ids, message_ids, identity_ids - UserUpdate: validate id 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * chore: regenerate API spec and SDK * fix: override ID validation in AgentSchema for agent file portability AgentSchema extends CreateAgent but needs to allow arbitrary short IDs (e.g., tool-0, block-0) for portable agent files. Override the validated ID fields to use plain List[str] instead of the validated types. Also fix test_agent.af to use proper UUID-format IDs. 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * chore: regenerate API spec and SDK 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * fix: revert test_agent.af - short IDs are valid for agent files 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * fix openapi schema --------- Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -30546,7 +30546,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 41,
|
||||
"minLength": 41,
|
||||
"pattern": "^tool-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the tool in the format 'tool-<uuid4>'",
|
||||
"examples": ["tool-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -30561,7 +30566,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 43,
|
||||
"minLength": 43,
|
||||
"pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the source in the format 'source-<uuid4>'",
|
||||
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -30577,7 +30587,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 43,
|
||||
"minLength": 43,
|
||||
"pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the source in the format 'source-<uuid4>'",
|
||||
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -30592,7 +30607,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 42,
|
||||
"minLength": 42,
|
||||
"pattern": "^block-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the block in the format 'block-<uuid4>'",
|
||||
"examples": ["block-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -31093,7 +31113,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 45,
|
||||
"minLength": 45,
|
||||
"pattern": "^identity-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the identity in the format 'identity-<uuid4>'",
|
||||
"examples": ["identity-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -34958,7 +34983,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 41,
|
||||
"minLength": 41,
|
||||
"pattern": "^tool-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the tool in the format 'tool-<uuid4>'",
|
||||
"examples": ["tool-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -34973,7 +35003,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 43,
|
||||
"minLength": 43,
|
||||
"pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the source in the format 'source-<uuid4>'",
|
||||
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -34989,7 +35024,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 43,
|
||||
"minLength": 43,
|
||||
"pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the source in the format 'source-<uuid4>'",
|
||||
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -35004,7 +35044,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 42,
|
||||
"minLength": 42,
|
||||
"pattern": "^block-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the block in the format 'block-<uuid4>'",
|
||||
"examples": ["block-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -35489,7 +35534,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 45,
|
||||
"minLength": 45,
|
||||
"pattern": "^identity-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the identity in the format 'identity-<uuid4>'",
|
||||
"examples": ["identity-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -44947,7 +44997,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 41,
|
||||
"minLength": 41,
|
||||
"pattern": "^tool-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the tool in the format 'tool-<uuid4>'",
|
||||
"examples": ["tool-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -44962,7 +45017,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 43,
|
||||
"minLength": 43,
|
||||
"pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the source in the format 'source-<uuid4>'",
|
||||
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -44978,7 +45038,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 43,
|
||||
"minLength": 43,
|
||||
"pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the source in the format 'source-<uuid4>'",
|
||||
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -44993,7 +45058,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 42,
|
||||
"minLength": 42,
|
||||
"pattern": "^block-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the block in the format 'block-<uuid4>'",
|
||||
"examples": ["block-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -45092,7 +45162,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 44,
|
||||
"minLength": 44,
|
||||
"pattern": "^message-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the message in the format 'message-<uuid4>'",
|
||||
"examples": ["message-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -45198,7 +45273,12 @@
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 45,
|
||||
"minLength": 45,
|
||||
"pattern": "^identity-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the identity in the format 'identity-<uuid4>'",
|
||||
"examples": ["identity-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@@ -46051,8 +46131,12 @@
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"maxLength": 41,
|
||||
"minLength": 41,
|
||||
"pattern": "^user-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"title": "Id",
|
||||
"description": "The id of the user to update."
|
||||
"description": "The id of the user to update.",
|
||||
"examples": ["user-123e4567-e89b-42d3-8456-426614174000"]
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [
|
||||
@@ -46468,8 +46552,7 @@
|
||||
}
|
||||
],
|
||||
"title": "Source Ids",
|
||||
"description": "Deprecated: Use `folder_ids` field instead. The ids of the sources used by the agent.",
|
||||
"deprecated": true
|
||||
"description": "The ids of the sources used by the agent."
|
||||
},
|
||||
"folder_ids": {
|
||||
"anyOf": [
|
||||
|
||||
@@ -32,6 +32,7 @@ from letta.schemas.tool import Tool
|
||||
from letta.schemas.tool_rule import ToolRule
|
||||
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||
from letta.utils import calculate_file_defaults_based_on_context_window, create_random_username
|
||||
from letta.validators import BlockId, IdentityId, MessageId, SourceId, ToolId
|
||||
|
||||
|
||||
# TODO: Remove this upon next OSS release, there's a duplicate AgentType in enums
|
||||
@@ -215,12 +216,12 @@ class CreateAgent(BaseModel, validate_assignment=True): #
|
||||
)
|
||||
# TODO: This is a legacy field and should be removed ASAP to force `tool_ids` usage
|
||||
tools: Optional[List[str]] = Field(None, description="The tools used by the agent.")
|
||||
tool_ids: Optional[List[str]] = Field(None, description="The ids of the tools used by the agent.")
|
||||
source_ids: Optional[List[str]] = Field(
|
||||
tool_ids: Optional[List[ToolId]] = Field(None, description="The ids of the tools used by the agent.")
|
||||
source_ids: Optional[List[SourceId]] = Field(
|
||||
None, description="Deprecated: Use `folder_ids` field instead. The ids of the sources used by the agent.", deprecated=True
|
||||
)
|
||||
folder_ids: Optional[List[str]] = Field(None, description="The ids of the folders used by the agent.")
|
||||
block_ids: Optional[List[str]] = Field(None, description="The ids of the blocks used by the agent.")
|
||||
folder_ids: Optional[List[SourceId]] = Field(None, description="The ids of the folders used by the agent.")
|
||||
block_ids: Optional[List[BlockId]] = Field(None, description="The ids of the blocks used by the agent.")
|
||||
tool_rules: Optional[List[ToolRule]] = Field(None, description="The tool rules governing the agent.")
|
||||
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
||||
system: Optional[str] = Field(None, description="The system prompt used by the agent.")
|
||||
@@ -311,7 +312,7 @@ class CreateAgent(BaseModel, validate_assignment=True): #
|
||||
base_template_id: Optional[str] = Field(
|
||||
None, description="Deprecated: No longer used. The base template id of the agent.", deprecated=True
|
||||
)
|
||||
identity_ids: Optional[List[str]] = Field(None, description="The ids of the identities associated with this agent.")
|
||||
identity_ids: Optional[List[IdentityId]] = Field(None, description="The ids of the identities associated with this agent.")
|
||||
message_buffer_autoclear: bool = Field(
|
||||
False,
|
||||
description="If set to True, the agent will not remember previous messages (though the agent will still retain state via core memory blocks and archival/recall memory). Not recommended unless you have an advanced use case.",
|
||||
@@ -444,16 +445,16 @@ class InternalTemplateAgentCreate(CreateAgent):
|
||||
|
||||
class UpdateAgent(BaseModel):
|
||||
name: Optional[str] = Field(None, description="The name of the agent.")
|
||||
tool_ids: Optional[List[str]] = Field(None, description="The ids of the tools used by the agent.")
|
||||
source_ids: Optional[List[str]] = Field(
|
||||
tool_ids: Optional[List[ToolId]] = Field(None, description="The ids of the tools used by the agent.")
|
||||
source_ids: Optional[List[SourceId]] = Field(
|
||||
None, description="Deprecated: Use `folder_ids` field instead. The ids of the sources used by the agent.", deprecated=True
|
||||
)
|
||||
folder_ids: Optional[List[str]] = Field(None, description="The ids of the folders used by the agent.")
|
||||
block_ids: Optional[List[str]] = Field(None, description="The ids of the blocks used by the agent.")
|
||||
folder_ids: Optional[List[SourceId]] = Field(None, description="The ids of the folders used by the agent.")
|
||||
block_ids: Optional[List[BlockId]] = Field(None, description="The ids of the blocks used by the agent.")
|
||||
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
||||
system: Optional[str] = Field(None, description="The system prompt used by the agent.")
|
||||
tool_rules: Optional[List[ToolRule]] = Field(None, description="The tool rules governing the agent.")
|
||||
message_ids: Optional[List[str]] = Field(None, description="The ids of the messages in the agent's in-context memory.")
|
||||
message_ids: Optional[List[MessageId]] = Field(None, description="The ids of the messages in the agent's in-context memory.")
|
||||
description: Optional[str] = Field(None, description="The description of the agent.")
|
||||
metadata: Optional[Dict] = Field(None, description="The metadata of the agent.")
|
||||
tool_exec_environment_variables: Optional[Dict[str, str]] = Field(None, description="Deprecated: use `secrets` field instead")
|
||||
@@ -461,7 +462,7 @@ class UpdateAgent(BaseModel):
|
||||
project_id: Optional[str] = Field(None, description="The id of the project the agent belongs to.")
|
||||
template_id: Optional[str] = Field(None, description="The id of the template the agent belongs to.")
|
||||
base_template_id: Optional[str] = Field(None, description="The base template id of the agent.")
|
||||
identity_ids: Optional[List[str]] = Field(None, description="The ids of the identities associated with this agent.")
|
||||
identity_ids: Optional[List[IdentityId]] = Field(None, description="The ids of the identities associated with this agent.")
|
||||
message_buffer_autoclear: Optional[bool] = Field(
|
||||
None,
|
||||
description="If set to True, the agent will not remember previous messages (though the agent will still retain state via core memory blocks and archival/recall memory). Not recommended unless you have an advanced use case.",
|
||||
|
||||
@@ -135,6 +135,12 @@ class AgentSchema(CreateAgent):
|
||||
files_agents: List[FileAgentSchema] = Field(default_factory=list, description="List of file-agent relationships for this agent")
|
||||
group_ids: List[str] = Field(default_factory=list, description="List of groups that the agent manages")
|
||||
|
||||
tool_ids: Optional[List[str]] = Field(None, description="The ids of the tools used by the agent.")
|
||||
source_ids: Optional[List[str]] = Field(None, description="The ids of the sources used by the agent.")
|
||||
folder_ids: Optional[List[str]] = Field(None, description="The ids of the folders used by the agent.")
|
||||
block_ids: Optional[List[str]] = Field(None, description="The ids of the blocks used by the agent.")
|
||||
identity_ids: Optional[List[str]] = Field(None, description="The ids of the identities associated with this agent.")
|
||||
|
||||
@classmethod
|
||||
async def from_agent_state(
|
||||
cls, agent_state: AgentState, message_manager: MessageManager, files_agents: List[FileAgent], actor: User
|
||||
|
||||
@@ -6,6 +6,7 @@ from pydantic import Field
|
||||
from letta.constants import DEFAULT_ORG_ID
|
||||
from letta.schemas.enums import PrimitiveType
|
||||
from letta.schemas.letta_base import LettaBase
|
||||
from letta.validators import UserId
|
||||
|
||||
|
||||
class UserBase(LettaBase):
|
||||
@@ -29,6 +30,6 @@ class UserCreate(UserBase):
|
||||
|
||||
|
||||
class UserUpdate(UserBase):
|
||||
id: str = Field(..., description="The id of the user to update.")
|
||||
id: UserId = Field(..., description="The id of the user to update.")
|
||||
name: Optional[str] = Field(None, description="The new name of the user.")
|
||||
organization_id: Optional[str] = Field(None, description="The new organization id of the user.")
|
||||
|
||||
@@ -691,7 +691,7 @@ async def test_update_agent(server: SyncServer, comprehensive_test_agent_fixture
|
||||
system="train system",
|
||||
llm_config=LLMConfig.default_config("gpt-4o-mini"),
|
||||
embedding_config=EmbeddingConfig.default_config(model_name="letta"),
|
||||
message_ids=["10", "20"],
|
||||
message_ids=[f"message-{uuid.uuid4()}", f"message-{uuid.uuid4()}"],
|
||||
metadata={"train_key": "train_value"},
|
||||
tool_exec_environment_variables={"test_env_var_key_a": "a", "new_tool_exec_key": "n"},
|
||||
message_buffer_autoclear=False,
|
||||
|
||||
Reference in New Issue
Block a user