letta-nightly 0.12.1.dev20251023104211__py3-none-any.whl → 0.13.0.dev20251024223017__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

Files changed (159) hide show
  1. letta/__init__.py +2 -3
  2. letta/adapters/letta_llm_adapter.py +1 -0
  3. letta/adapters/simple_llm_request_adapter.py +8 -5
  4. letta/adapters/simple_llm_stream_adapter.py +22 -6
  5. letta/agents/agent_loop.py +10 -3
  6. letta/agents/base_agent.py +4 -1
  7. letta/agents/helpers.py +41 -9
  8. letta/agents/letta_agent.py +11 -10
  9. letta/agents/letta_agent_v2.py +47 -37
  10. letta/agents/letta_agent_v3.py +395 -300
  11. letta/agents/voice_agent.py +8 -6
  12. letta/agents/voice_sleeptime_agent.py +3 -3
  13. letta/constants.py +30 -7
  14. letta/errors.py +20 -0
  15. letta/functions/function_sets/base.py +55 -3
  16. letta/functions/mcp_client/types.py +33 -57
  17. letta/functions/schema_generator.py +135 -23
  18. letta/groups/sleeptime_multi_agent_v3.py +6 -11
  19. letta/groups/sleeptime_multi_agent_v4.py +227 -0
  20. letta/helpers/converters.py +78 -4
  21. letta/helpers/crypto_utils.py +6 -2
  22. letta/interfaces/anthropic_parallel_tool_call_streaming_interface.py +9 -11
  23. letta/interfaces/anthropic_streaming_interface.py +3 -4
  24. letta/interfaces/gemini_streaming_interface.py +4 -6
  25. letta/interfaces/openai_streaming_interface.py +63 -28
  26. letta/llm_api/anthropic_client.py +7 -4
  27. letta/llm_api/deepseek_client.py +6 -4
  28. letta/llm_api/google_ai_client.py +3 -12
  29. letta/llm_api/google_vertex_client.py +1 -1
  30. letta/llm_api/helpers.py +90 -61
  31. letta/llm_api/llm_api_tools.py +4 -1
  32. letta/llm_api/openai.py +12 -12
  33. letta/llm_api/openai_client.py +53 -16
  34. letta/local_llm/constants.py +4 -3
  35. letta/local_llm/json_parser.py +5 -2
  36. letta/local_llm/utils.py +2 -3
  37. letta/log.py +171 -7
  38. letta/orm/agent.py +43 -9
  39. letta/orm/archive.py +4 -0
  40. letta/orm/custom_columns.py +15 -0
  41. letta/orm/identity.py +11 -11
  42. letta/orm/mcp_server.py +9 -0
  43. letta/orm/message.py +6 -1
  44. letta/orm/run_metrics.py +7 -2
  45. letta/orm/sqlalchemy_base.py +2 -2
  46. letta/orm/tool.py +3 -0
  47. letta/otel/tracing.py +2 -0
  48. letta/prompts/prompt_generator.py +7 -2
  49. letta/schemas/agent.py +41 -10
  50. letta/schemas/agent_file.py +3 -0
  51. letta/schemas/archive.py +4 -2
  52. letta/schemas/block.py +2 -1
  53. letta/schemas/enums.py +36 -3
  54. letta/schemas/file.py +3 -3
  55. letta/schemas/folder.py +2 -1
  56. letta/schemas/group.py +2 -1
  57. letta/schemas/identity.py +18 -9
  58. letta/schemas/job.py +3 -1
  59. letta/schemas/letta_message.py +71 -12
  60. letta/schemas/letta_request.py +7 -3
  61. letta/schemas/letta_stop_reason.py +0 -25
  62. letta/schemas/llm_config.py +8 -2
  63. letta/schemas/mcp.py +80 -83
  64. letta/schemas/mcp_server.py +349 -0
  65. letta/schemas/memory.py +20 -8
  66. letta/schemas/message.py +212 -67
  67. letta/schemas/providers/anthropic.py +13 -6
  68. letta/schemas/providers/azure.py +6 -4
  69. letta/schemas/providers/base.py +8 -4
  70. letta/schemas/providers/bedrock.py +6 -2
  71. letta/schemas/providers/cerebras.py +7 -3
  72. letta/schemas/providers/deepseek.py +2 -1
  73. letta/schemas/providers/google_gemini.py +15 -6
  74. letta/schemas/providers/groq.py +2 -1
  75. letta/schemas/providers/lmstudio.py +9 -6
  76. letta/schemas/providers/mistral.py +2 -1
  77. letta/schemas/providers/openai.py +7 -2
  78. letta/schemas/providers/together.py +9 -3
  79. letta/schemas/providers/xai.py +7 -3
  80. letta/schemas/run.py +7 -2
  81. letta/schemas/run_metrics.py +2 -1
  82. letta/schemas/sandbox_config.py +2 -2
  83. letta/schemas/secret.py +3 -158
  84. letta/schemas/source.py +2 -2
  85. letta/schemas/step.py +2 -2
  86. letta/schemas/tool.py +24 -1
  87. letta/schemas/usage.py +0 -1
  88. letta/server/rest_api/app.py +123 -7
  89. letta/server/rest_api/dependencies.py +3 -0
  90. letta/server/rest_api/interface.py +7 -4
  91. letta/server/rest_api/redis_stream_manager.py +16 -1
  92. letta/server/rest_api/routers/v1/__init__.py +7 -0
  93. letta/server/rest_api/routers/v1/agents.py +332 -322
  94. letta/server/rest_api/routers/v1/archives.py +127 -40
  95. letta/server/rest_api/routers/v1/blocks.py +54 -6
  96. letta/server/rest_api/routers/v1/chat_completions.py +146 -0
  97. letta/server/rest_api/routers/v1/folders.py +27 -35
  98. letta/server/rest_api/routers/v1/groups.py +23 -35
  99. letta/server/rest_api/routers/v1/identities.py +24 -10
  100. letta/server/rest_api/routers/v1/internal_runs.py +107 -0
  101. letta/server/rest_api/routers/v1/internal_templates.py +162 -179
  102. letta/server/rest_api/routers/v1/jobs.py +15 -27
  103. letta/server/rest_api/routers/v1/mcp_servers.py +309 -0
  104. letta/server/rest_api/routers/v1/messages.py +23 -34
  105. letta/server/rest_api/routers/v1/organizations.py +6 -27
  106. letta/server/rest_api/routers/v1/providers.py +35 -62
  107. letta/server/rest_api/routers/v1/runs.py +30 -43
  108. letta/server/rest_api/routers/v1/sandbox_configs.py +6 -4
  109. letta/server/rest_api/routers/v1/sources.py +26 -42
  110. letta/server/rest_api/routers/v1/steps.py +16 -29
  111. letta/server/rest_api/routers/v1/tools.py +17 -13
  112. letta/server/rest_api/routers/v1/users.py +5 -17
  113. letta/server/rest_api/routers/v1/voice.py +18 -27
  114. letta/server/rest_api/streaming_response.py +5 -2
  115. letta/server/rest_api/utils.py +187 -25
  116. letta/server/server.py +27 -22
  117. letta/server/ws_api/server.py +5 -4
  118. letta/services/agent_manager.py +148 -26
  119. letta/services/agent_serialization_manager.py +6 -1
  120. letta/services/archive_manager.py +168 -15
  121. letta/services/block_manager.py +14 -4
  122. letta/services/file_manager.py +33 -29
  123. letta/services/group_manager.py +10 -0
  124. letta/services/helpers/agent_manager_helper.py +65 -11
  125. letta/services/identity_manager.py +105 -4
  126. letta/services/job_manager.py +11 -1
  127. letta/services/mcp/base_client.py +2 -2
  128. letta/services/mcp/oauth_utils.py +33 -8
  129. letta/services/mcp_manager.py +174 -78
  130. letta/services/mcp_server_manager.py +1331 -0
  131. letta/services/message_manager.py +109 -4
  132. letta/services/organization_manager.py +4 -4
  133. letta/services/passage_manager.py +9 -25
  134. letta/services/provider_manager.py +91 -15
  135. letta/services/run_manager.py +72 -15
  136. letta/services/sandbox_config_manager.py +45 -3
  137. letta/services/source_manager.py +15 -8
  138. letta/services/step_manager.py +24 -1
  139. letta/services/streaming_service.py +581 -0
  140. letta/services/summarizer/summarizer.py +1 -1
  141. letta/services/tool_executor/core_tool_executor.py +111 -0
  142. letta/services/tool_executor/files_tool_executor.py +5 -3
  143. letta/services/tool_executor/sandbox_tool_executor.py +2 -2
  144. letta/services/tool_executor/tool_execution_manager.py +1 -1
  145. letta/services/tool_manager.py +10 -3
  146. letta/services/tool_sandbox/base.py +61 -1
  147. letta/services/tool_sandbox/local_sandbox.py +1 -3
  148. letta/services/user_manager.py +2 -2
  149. letta/settings.py +49 -5
  150. letta/system.py +14 -5
  151. letta/utils.py +73 -1
  152. letta/validators.py +105 -0
  153. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/METADATA +4 -2
  154. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/RECORD +157 -151
  155. letta/schemas/letta_ping.py +0 -28
  156. letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
  157. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/WHEEL +0 -0
  158. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/entry_points.txt +0 -0
  159. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/licenses/LICENSE +0 -0
letta/orm/identity.py CHANGED
@@ -44,15 +44,15 @@ class Identity(SqlalchemyBase, OrganizationMixin, ProjectMixin):
44
44
  "Block", secondary="identities_blocks", lazy="selectin", passive_deletes=True, back_populates="identities"
45
45
  )
46
46
 
47
- @property
48
- def agent_ids(self) -> List[str]:
49
- """Get just the agent IDs without loading the full agent objects"""
50
- return [agent.id for agent in self.agents]
47
+ # @property
48
+ # def agent_ids(self) -> List[str]:
49
+ # """Get just the agent IDs without loading the full agent objects"""
50
+ # return [agent.id for agent in self.agents]
51
51
 
52
- @property
53
- def block_ids(self) -> List[str]:
54
- """Get just the block IDs without loading the full agent objects"""
55
- return [block.id for block in self.blocks]
52
+ # @property
53
+ # def block_ids(self) -> List[str]:
54
+ # """Get just the block IDs without loading the full agent objects"""
55
+ # return [block.id for block in self.blocks]
56
56
 
57
57
  def to_pydantic(self) -> PydanticIdentity:
58
58
  state = {
@@ -61,9 +61,9 @@ class Identity(SqlalchemyBase, OrganizationMixin, ProjectMixin):
61
61
  "name": self.name,
62
62
  "identity_type": self.identity_type,
63
63
  "project_id": self.project_id,
64
- "agent_ids": self.agent_ids,
65
- "block_ids": self.block_ids,
64
+ "agent_ids": [],
65
+ "block_ids": [],
66
66
  "organization_id": self.organization_id,
67
- "properties": self.properties,
67
+ "properties": self.properties or [],
68
68
  }
69
69
  return PydanticIdentity(**state)
letta/orm/mcp_server.py CHANGED
@@ -56,3 +56,12 @@ class MCPServer(SqlalchemyBase, OrganizationMixin):
56
56
  metadata_: Mapped[Optional[dict]] = mapped_column(
57
57
  JSON, default=lambda: {}, doc="A dictionary of additional metadata for the MCP server."
58
58
  )
59
+
60
+
61
+ class MCPTools(SqlalchemyBase, OrganizationMixin):
62
+ """Represents a mapping of MCP server ID to tool ID"""
63
+
64
+ __tablename__ = "mcp_tools"
65
+
66
+ mcp_server_id: Mapped[str] = mapped_column(String, doc="The ID of the MCP server")
67
+ tool_id: Mapped[str] = mapped_column(String, doc="The ID of the tool")
letta/orm/message.py CHANGED
@@ -4,10 +4,11 @@ from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMe
4
4
  from sqlalchemy import BigInteger, FetchedValue, ForeignKey, Index, event, text
5
5
  from sqlalchemy.orm import Mapped, Session, mapped_column, relationship
6
6
 
7
- from letta.orm.custom_columns import MessageContentColumn, ToolCallColumn, ToolReturnColumn
7
+ from letta.orm.custom_columns import ApprovalsColumn, MessageContentColumn, ToolCallColumn, ToolReturnColumn
8
8
  from letta.orm.mixins import AgentMixin, OrganizationMixin
9
9
  from letta.orm.sqlalchemy_base import SqlalchemyBase
10
10
  from letta.schemas.enums import MessageRole
11
+ from letta.schemas.letta_message import ApprovalReturn
11
12
  from letta.schemas.letta_message_content import MessageContent, TextContent, TextContent as PydanticTextContent
12
13
  from letta.schemas.message import Message as PydanticMessage, ToolReturn
13
14
  from letta.settings import DatabaseChoice, settings
@@ -63,6 +64,9 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
63
64
  )
64
65
  approve: Mapped[Optional[bool]] = mapped_column(nullable=True, doc="Whether tool call is approved.")
65
66
  denial_reason: Mapped[Optional[str]] = mapped_column(nullable=True, doc="The reason the tool call request was denied.")
67
+ approvals: Mapped[Optional[List[ApprovalReturn | ToolReturn]]] = mapped_column(
68
+ ApprovalsColumn, nullable=True, doc="Approval responses for tool call requests"
69
+ )
66
70
 
67
71
  # Monotonically increasing sequence for efficient/correct listing
68
72
  sequence_id: Mapped[int] = mapped_column(
@@ -100,6 +104,7 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
100
104
  and len(self.content) == 1
101
105
  and isinstance(self.content[0], TextContent)
102
106
  ):
107
+ self.tool_call_id = self.tool_returns[0].tool_call_id
103
108
  self.tool_returns[0].func_response = self.content[0].text
104
109
 
105
110
  return model
letta/orm/run_metrics.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from datetime import datetime, timezone
2
- from typing import TYPE_CHECKING, Optional
2
+ from typing import TYPE_CHECKING, List, Optional
3
3
 
4
- from sqlalchemy import BigInteger, ForeignKey, Integer, String
4
+ from sqlalchemy import JSON, BigInteger, ForeignKey, Integer, String
5
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
6
  from sqlalchemy.orm import Mapped, Session, mapped_column, relationship
7
7
 
@@ -43,6 +43,11 @@ class RunMetrics(SqlalchemyBase, ProjectMixin, AgentMixin, OrganizationMixin, Te
43
43
  nullable=True,
44
44
  doc="The number of steps in the run",
45
45
  )
46
+ tools_used: Mapped[Optional[List[str]]] = mapped_column(
47
+ JSON,
48
+ nullable=True,
49
+ doc="List of tool IDs that were used in this run",
50
+ )
46
51
  run: Mapped[Optional["Run"]] = relationship("Run", foreign_keys=[id])
47
52
  agent: Mapped[Optional["Agent"]] = relationship("Agent")
48
53
 
@@ -269,14 +269,14 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
269
269
  if before_obj:
270
270
  conditions.append(
271
271
  or_(
272
- cls.created_at < before_obj.created_at,
272
+ cls.created_at < before_obj.created_at if ascending else cls.created_at > before_obj.created_at,
273
273
  and_(cls.created_at == before_obj.created_at, cls.id < before_obj.id),
274
274
  )
275
275
  )
276
276
  if after_obj:
277
277
  conditions.append(
278
278
  or_(
279
- cls.created_at > after_obj.created_at,
279
+ cls.created_at > after_obj.created_at if ascending else cls.created_at < after_obj.created_at,
280
280
  and_(cls.created_at == after_obj.created_at, cls.id > after_obj.id),
281
281
  )
282
282
  )
letta/orm/tool.py CHANGED
@@ -50,6 +50,9 @@ class Tool(SqlalchemyBase, OrganizationMixin):
50
50
  )
51
51
  npm_requirements: Mapped[list | None] = mapped_column(JSON, doc="Optional list of npm packages required by this tool.")
52
52
  default_requires_approval: Mapped[bool] = mapped_column(nullable=True, doc="Whether or not to require approval.")
53
+ enable_parallel_execution: Mapped[bool] = mapped_column(
54
+ nullable=True, doc="If set to True, then this tool will potentially be executed concurrently with other tools. Default False."
55
+ )
53
56
  metadata_: Mapped[Optional[dict]] = mapped_column(JSON, default=lambda: {}, doc="A dictionary of additional metadata for the tool.")
54
57
  # relationships
55
58
  organization: Mapped["Organization"] = relationship("Organization", back_populates="tools", lazy="selectin")
letta/otel/tracing.py CHANGED
@@ -87,6 +87,8 @@ async def _update_trace_attributes(request: Request):
87
87
  "x-template-id": "template.id",
88
88
  "x-base-template-id": "base_template.id",
89
89
  "user-agent": "client",
90
+ "x-stainless-package-version": "sdk.version",
91
+ "x-stainless-lang": "sdk.language",
90
92
  }
91
93
  for header_key, span_key in header_attributes.items():
92
94
  header_value = request.headers.get(header_key)
@@ -1,6 +1,10 @@
1
1
  from datetime import datetime
2
2
  from typing import List, Literal, Optional
3
3
 
4
+ from letta.log import get_logger
5
+
6
+ logger = get_logger(__name__)
7
+
4
8
  from letta.constants import IN_CONTEXT_MEMORY_KEYWORD
5
9
  from letta.helpers import ToolRulesSolver
6
10
  from letta.helpers.datetime_helpers import format_datetime, get_local_time_fast
@@ -137,7 +141,7 @@ class PromptGenerator:
137
141
  if append_icm_if_missing:
138
142
  if memory_variable_string not in system_prompt:
139
143
  # In this case, append it to the end to make sure memory is still injected
140
- # warnings.warn(f"{IN_CONTEXT_MEMORY_KEYWORD} variable was missing from system prompt, appending instead")
144
+ # logger.warning(f"{IN_CONTEXT_MEMORY_KEYWORD} variable was missing from system prompt, appending instead")
141
145
  system_prompt += "\n\n" + memory_variable_string
142
146
 
143
147
  # render the variables using the built-in templater
@@ -170,6 +174,7 @@ class PromptGenerator:
170
174
  tool_rules_solver: Optional[ToolRulesSolver] = None,
171
175
  sources: Optional[List] = None,
172
176
  max_files_open: Optional[int] = None,
177
+ llm_config: Optional[object] = None,
173
178
  ) -> str:
174
179
  tool_constraint_block = None
175
180
  if tool_rules_solver is not None:
@@ -182,7 +187,7 @@ class PromptGenerator:
182
187
  pass
183
188
 
184
189
  memory_with_sources = in_context_memory.compile(
185
- tool_usage_rules=tool_constraint_block, sources=sources, max_files_open=max_files_open
190
+ tool_usage_rules=tool_constraint_block, sources=sources, max_files_open=max_files_open, llm_config=llm_config
186
191
  )
187
192
 
188
193
  return PromptGenerator.get_system_message_from_compiled_memory(
letta/schemas/agent.py CHANGED
@@ -1,15 +1,18 @@
1
1
  from datetime import datetime
2
2
  from enum import Enum
3
- from typing import Dict, List, Optional
3
+ from typing import Dict, List, Literal, Optional
4
4
 
5
5
  from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
6
6
 
7
7
  from letta.constants import CORE_MEMORY_LINE_NUMBER_WARNING, DEFAULT_EMBEDDING_CHUNK_SIZE
8
- from letta.schemas.block import CreateBlock
8
+ from letta.errors import AgentExportProcessingError
9
+ from letta.schemas.block import Block, CreateBlock
9
10
  from letta.schemas.embedding_config import EmbeddingConfig
11
+ from letta.schemas.enums import PrimitiveType
10
12
  from letta.schemas.environment_variables import AgentEnvironmentVariable
11
13
  from letta.schemas.file import FileStatus
12
14
  from letta.schemas.group import Group
15
+ from letta.schemas.identity import Identity
13
16
  from letta.schemas.letta_base import OrmMetadataBase
14
17
  from letta.schemas.llm_config import LLMConfig
15
18
  from letta.schemas.memory import Memory
@@ -40,6 +43,18 @@ class AgentType(str, Enum):
40
43
  voice_sleeptime_agent = "voice_sleeptime_agent"
41
44
 
42
45
 
46
+ # Relationship field literal type for AgentState include field to join related objects
47
+ AgentRelationships = Literal[
48
+ "agent.blocks",
49
+ "agent.identities",
50
+ "agent.managed_group",
51
+ "agent.secrets",
52
+ "agent.sources",
53
+ "agent.tags",
54
+ "agent.tools",
55
+ ]
56
+
57
+
43
58
  class AgentState(OrmMetadataBase, validate_assignment=True):
44
59
  """
45
60
  Representation of an agent's state. This is the state of the agent at a given time, and is persisted in the DB backend. The state has all the information needed to recreate a persisted agent.
@@ -56,7 +71,7 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
56
71
  embedding_config (EmbeddingConfig): The embedding configuration used by the agent.
57
72
  """
58
73
 
59
- __id_prefix__ = "agent"
74
+ __id_prefix__ = PrimitiveType.AGENT.value
60
75
 
61
76
  # NOTE: this is what is returned to the client and also what is used to initialize `Agent`
62
77
  id: str = Field(..., description="The id of the agent. Assigned by the database.")
@@ -84,7 +99,8 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
84
99
  description: Optional[str] = Field(None, description="The description of the agent.")
85
100
  metadata: Optional[Dict] = Field(None, description="The metadata of the agent.")
86
101
 
87
- memory: Memory = Field(..., description="The in-context memory of the agent.")
102
+ memory: Memory = Field(..., description="The in-context memory of the agent.", deprecated=True)
103
+ blocks: List[Block] = Field(..., description="The memory blocks used by the agent.")
88
104
  tools: List[Tool] = Field(..., description="The tools used by the agent.")
89
105
  sources: List[Source] = Field(..., description="The sources used by the agent.")
90
106
  tags: List[str] = Field(..., description="The tags associated with the agent.")
@@ -101,7 +117,8 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
101
117
  base_template_id: Optional[str] = Field(None, description="The base template id of the agent.")
102
118
  deployment_id: Optional[str] = Field(None, description="The id of the deployment.")
103
119
  entity_id: Optional[str] = Field(None, description="The id of the entity within the template.")
104
- identity_ids: List[str] = Field([], description="The ids of the identities associated with this agent.")
120
+ identity_ids: List[str] = Field([], description="The ids of the identities associated with this agent.", deprecated=True)
121
+ identities: List[Identity] = Field([], description="The identities associated with this agent.")
105
122
 
106
123
  # An advanced configuration that makes it so this agent does not remember any previous messages
107
124
  message_buffer_autoclear: bool = Field(
@@ -113,8 +130,8 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
113
130
  description="If set to True, memory management will move to a background agent thread.",
114
131
  )
115
132
 
116
- multi_agent_group: Optional[Group] = Field(None, description="The multi-agent group that this agent manages")
117
-
133
+ multi_agent_group: Optional[Group] = Field(None, description="The multi-agent group that this agent manages", deprecated=True)
134
+ managed_group: Optional[Group] = Field(None, description="The multi-agent group that this agent manages")
118
135
  # Run metrics
119
136
  last_run_completion: Optional[datetime] = Field(None, description="The timestamp when the agent last completed a run.")
120
137
  last_run_duration_ms: Optional[int] = Field(None, description="The duration in milliseconds of the agent's last run.")
@@ -256,6 +273,7 @@ class CreateAgent(BaseModel, validate_assignment=True): #
256
273
  None,
257
274
  description="If set to True, the agent will be hidden.",
258
275
  )
276
+ parallel_tool_calls: Optional[bool] = Field(False, description="If set to True, enables parallel tool calling. Defaults to False.")
259
277
 
260
278
  @field_validator("name")
261
279
  @classmethod
@@ -268,9 +286,16 @@ class CreateAgent(BaseModel, validate_assignment=True): #
268
286
  # don't check if not provided
269
287
  return name
270
288
 
271
- # Regex for allowed characters (alphanumeric, spaces, hyphens, underscores)
272
- if not re.match("^[A-Za-z0-9 _-]+$", name):
273
- raise ValueError("Name contains invalid characters.")
289
+ # Regex for allowed characters (Unicode letters, digits, spaces, hyphens, underscores, apostrophes)
290
+ # \w in Python 3 with re.UNICODE matches Unicode letters, digits, and underscores
291
+ # We explicitly allow: letters (any language), digits, spaces, hyphens, underscores, apostrophes
292
+ # We block filesystem-unsafe characters: / \ : * ? " < > |
293
+ if not re.match(r"^[\w '\-]+$", name, re.UNICODE):
294
+ raise AgentExportProcessingError(
295
+ f"Agent name '{name}' contains invalid characters. Only letters (any language), digits, spaces, "
296
+ f"hyphens, underscores, and apostrophes are allowed. Please avoid filesystem-unsafe characters "
297
+ f'like: / \\ : * ? " < > |'
298
+ )
274
299
 
275
300
  # Further checks can be added here...
276
301
  # TODO
@@ -353,6 +378,11 @@ class UpdateAgent(BaseModel):
353
378
  embedding: Optional[str] = Field(
354
379
  None, description="The embedding configuration handle used by the agent, specified in the format provider/model-name."
355
380
  )
381
+ context_window_limit: Optional[int] = Field(None, description="The context window limit used by the agent.")
382
+ max_tokens: Optional[int] = Field(
383
+ None,
384
+ description="The maximum number of tokens to generate, including reasoning step. If not set, the model will use its default value.",
385
+ )
356
386
  reasoning: Optional[bool] = Field(None, description="Whether to enable reasoning for this agent.")
357
387
  enable_sleeptime: Optional[bool] = Field(None, description="If set to True, memory management will move to a background agent thread.")
358
388
  response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the agent.")
@@ -371,6 +401,7 @@ class UpdateAgent(BaseModel):
371
401
  None,
372
402
  description="If set to True, the agent will be hidden.",
373
403
  )
404
+ parallel_tool_calls: Optional[bool] = Field(False, description="If set to True, enables parallel tool calling. Defaults to False.")
374
405
 
375
406
  model_config = ConfigDict(extra="ignore") # Ignores extra fields
376
407
 
@@ -10,6 +10,7 @@ from letta.schemas.block import Block, CreateBlock
10
10
  from letta.schemas.enums import MessageRole
11
11
  from letta.schemas.file import FileAgent, FileAgentBase, FileMetadata, FileMetadataBase
12
12
  from letta.schemas.group import Group, GroupCreate
13
+ from letta.schemas.letta_message import ApprovalReturn
13
14
  from letta.schemas.mcp import MCPServer
14
15
  from letta.schemas.message import Message, MessageCreate, ToolReturn
15
16
  from letta.schemas.source import Source, SourceCreate
@@ -59,6 +60,7 @@ class MessageSchema(MessageCreate):
59
60
  approve: Optional[bool] = Field(None, description="Whether the tool has been approved")
60
61
  approval_request_id: Optional[str] = Field(None, description="The message ID of the approval request")
61
62
  denial_reason: Optional[str] = Field(None, description="An optional explanation for the provided approval status")
63
+ approvals: Optional[List[ApprovalReturn | ToolReturn]] = Field(None, description="Approval returns for the message")
62
64
 
63
65
  # TODO: Should we also duplicate the steps here?
64
66
  # TODO: What about tool_return?
@@ -87,6 +89,7 @@ class MessageSchema(MessageCreate):
87
89
  approve=message.approve,
88
90
  approval_request_id=message.approval_request_id,
89
91
  denial_reason=message.denial_reason,
92
+ approvals=message.approvals,
90
93
  )
91
94
 
92
95
 
letta/schemas/archive.py CHANGED
@@ -3,12 +3,13 @@ from typing import Dict, Optional
3
3
 
4
4
  from pydantic import Field
5
5
 
6
- from letta.schemas.enums import VectorDBProvider
6
+ from letta.schemas.embedding_config import EmbeddingConfig
7
+ from letta.schemas.enums import PrimitiveType, VectorDBProvider
7
8
  from letta.schemas.letta_base import OrmMetadataBase
8
9
 
9
10
 
10
11
  class ArchiveBase(OrmMetadataBase):
11
- __id_prefix__ = "archive"
12
+ __id_prefix__ = PrimitiveType.ARCHIVE.value
12
13
 
13
14
  name: str = Field(..., description="The name of the archive")
14
15
  description: Optional[str] = Field(None, description="A description of the archive")
@@ -16,6 +17,7 @@ class ArchiveBase(OrmMetadataBase):
16
17
  vector_db_provider: VectorDBProvider = Field(
17
18
  default=VectorDBProvider.NATIVE, description="The vector database provider used for this archive's passages"
18
19
  )
20
+ embedding_config: EmbeddingConfig = Field(..., description="Embedding configuration for passages in this archive")
19
21
  metadata: Optional[Dict] = Field(default_factory=dict, validation_alias="metadata_", description="Additional metadata")
20
22
 
21
23
 
letta/schemas/block.py CHANGED
@@ -4,6 +4,7 @@ from typing import Any, Optional
4
4
  from pydantic import ConfigDict, Field, model_validator
5
5
 
6
6
  from letta.constants import CORE_MEMORY_BLOCK_CHAR_LIMIT, DEFAULT_HUMAN_BLOCK_DESCRIPTION, DEFAULT_PERSONA_BLOCK_DESCRIPTION
7
+ from letta.schemas.enums import PrimitiveType
7
8
  from letta.schemas.letta_base import LettaBase
8
9
 
9
10
  # block of the LLM context
@@ -12,7 +13,7 @@ from letta.schemas.letta_base import LettaBase
12
13
  class BaseBlock(LettaBase, validate_assignment=True):
13
14
  """Base block of the LLM context"""
14
15
 
15
- __id_prefix__ = "block"
16
+ __id_prefix__ = PrimitiveType.BLOCK.value
16
17
 
17
18
  # data value
18
19
  value: str = Field(..., description="Value of the block.")
letta/schemas/enums.py CHANGED
@@ -1,6 +1,32 @@
1
1
  from enum import Enum, StrEnum
2
2
 
3
3
 
4
+ class PrimitiveType(str, Enum):
5
+ """
6
+ Enum for all primitive resource types in Letta.
7
+
8
+ The enum values ARE the actual ID prefixes used in the system.
9
+ This serves as the single source of truth for all ID prefixes.
10
+ """
11
+
12
+ AGENT = "agent"
13
+ MESSAGE = "message"
14
+ RUN = "run"
15
+ JOB = "job"
16
+ GROUP = "group"
17
+ BLOCK = "block"
18
+ FILE = "file"
19
+ FOLDER = "source" # Note: folder IDs use "source" prefix for historical reasons
20
+ SOURCE = "source"
21
+ TOOL = "tool"
22
+ ARCHIVE = "archive"
23
+ PASSAGE = "passage"
24
+ PROVIDER = "provider"
25
+ SANDBOX_CONFIG = "sandbox" # Note: sandbox_config IDs use "sandbox" prefix
26
+ STEP = "step"
27
+ IDENTITY = "identity"
28
+
29
+
4
30
  class ProviderType(str, Enum):
5
31
  anthropic = "anthropic"
6
32
  azure = "azure"
@@ -152,13 +178,12 @@ class ToolType(str, Enum):
152
178
  LETTA_VOICE_SLEEPTIME_CORE = "letta_voice_sleeptime_core"
153
179
  LETTA_BUILTIN = "letta_builtin"
154
180
  LETTA_FILES_CORE = "letta_files_core"
155
- EXTERNAL_LANGCHAIN = "external_langchain" # DEPRECATED
156
- EXTERNAL_COMPOSIO = "external_composio" # DEPRECATED
181
+ EXTERNAL_LANGCHAIN = "external_langchain" # DEPRECATED
182
+ EXTERNAL_COMPOSIO = "external_composio" # DEPRECATED
157
183
  # TODO is "external" the right name here? Since as of now, MCP is local / doesn't support remote?
158
184
  EXTERNAL_MCP = "external_mcp"
159
185
 
160
186
 
161
-
162
187
  class JobType(str, Enum):
163
188
  JOB = "job"
164
189
  RUN = "run"
@@ -222,3 +247,11 @@ class TagMatchMode(str, Enum):
222
247
 
223
248
  ANY = "any"
224
249
  ALL = "all"
250
+
251
+
252
+ class ComparisonOperator(str, Enum):
253
+ """Comparison operators for filtering numeric values"""
254
+
255
+ EQ = "eq" # equals
256
+ GTE = "gte" # greater than or equal
257
+ LTE = "lte" # less than or equal
letta/schemas/file.py CHANGED
@@ -4,7 +4,7 @@ from typing import List, Optional
4
4
 
5
5
  from pydantic import Field
6
6
 
7
- from letta.schemas.enums import FileProcessingStatus
7
+ from letta.schemas.enums import FileProcessingStatus, PrimitiveType
8
8
  from letta.schemas.letta_base import LettaBase
9
9
 
10
10
 
@@ -20,7 +20,7 @@ class FileStatus(str, Enum):
20
20
  class FileMetadataBase(LettaBase):
21
21
  """Base class for FileMetadata schemas"""
22
22
 
23
- __id_prefix__ = "file"
23
+ __id_prefix__ = PrimitiveType.FILE.value
24
24
 
25
25
  # Core file metadata fields
26
26
  source_id: str = Field(..., description="The unique identifier of the source associated with the document.")
@@ -61,7 +61,7 @@ class FileMetadata(FileMetadataBase):
61
61
  class FileAgentBase(LettaBase):
62
62
  """Base class for the FileMetadata-⇄-Agent association schemas"""
63
63
 
64
- __id_prefix__ = "file_agent"
64
+ __id_prefix__ = PrimitiveType.FILE.value
65
65
 
66
66
  # Core file-agent association fields
67
67
  agent_id: str = Field(..., description="Unique identifier of the agent.")
letta/schemas/folder.py CHANGED
@@ -4,6 +4,7 @@ from typing import Optional
4
4
  from pydantic import Field
5
5
 
6
6
  from letta.schemas.embedding_config import EmbeddingConfig
7
+ from letta.schemas.enums import PrimitiveType
7
8
  from letta.schemas.letta_base import LettaBase
8
9
 
9
10
 
@@ -12,7 +13,7 @@ class BaseFolder(LettaBase):
12
13
  Shared attributes across all folder schemas.
13
14
  """
14
15
 
15
- __id_prefix__ = "source" # TODO: change to "folder"
16
+ __id_prefix__ = PrimitiveType.FOLDER.value # TODO: change to "folder"
16
17
 
17
18
  # Core folder fields
18
19
  name: str = Field(..., description="The name of the folder.")
letta/schemas/group.py CHANGED
@@ -3,6 +3,7 @@ from typing import Annotated, List, Literal, Optional, Union
3
3
 
4
4
  from pydantic import BaseModel, Field
5
5
 
6
+ from letta.schemas.enums import PrimitiveType
6
7
  from letta.schemas.letta_base import LettaBase
7
8
 
8
9
 
@@ -20,7 +21,7 @@ class ManagerConfig(BaseModel):
20
21
 
21
22
 
22
23
  class GroupBase(LettaBase):
23
- __id_prefix__ = "group"
24
+ __id_prefix__ = PrimitiveType.GROUP.value
24
25
 
25
26
 
26
27
  class Group(GroupBase):
letta/schemas/identity.py CHANGED
@@ -3,6 +3,7 @@ from typing import List, Optional, Union
3
3
 
4
4
  from pydantic import Field
5
5
 
6
+ from letta.schemas.enums import PrimitiveType
6
7
  from letta.schemas.letta_base import LettaBase
7
8
 
8
9
 
@@ -28,7 +29,7 @@ class IdentityPropertyType(str, Enum):
28
29
 
29
30
 
30
31
  class IdentityBase(LettaBase):
31
- __id_prefix__ = "identity"
32
+ __id_prefix__ = PrimitiveType.IDENTITY.value
32
33
 
33
34
 
34
35
  class IdentityProperty(LettaBase):
@@ -45,8 +46,8 @@ class Identity(IdentityBase):
45
46
  name: str = Field(..., description="The name of the identity.")
46
47
  identity_type: IdentityType = Field(..., description="The type of the identity.")
47
48
  project_id: Optional[str] = Field(None, description="The project id of the identity, if applicable.")
48
- agent_ids: List[str] = Field(..., description="The IDs of the agents associated with the identity.")
49
- block_ids: List[str] = Field(..., description="The IDs of the blocks associated with the identity.")
49
+ agent_ids: List[str] = Field(..., description="The IDs of the agents associated with the identity.", deprecated=True)
50
+ block_ids: List[str] = Field(..., description="The IDs of the blocks associated with the identity.", deprecated=True)
50
51
  organization_id: Optional[str] = Field(None, description="The organization id of the user")
51
52
  properties: List[IdentityProperty] = Field(default_factory=list, description="List of properties associated with the identity")
52
53
 
@@ -56,8 +57,8 @@ class IdentityCreate(LettaBase):
56
57
  name: str = Field(..., description="The name of the identity.")
57
58
  identity_type: IdentityType = Field(..., description="The type of the identity.")
58
59
  project_id: Optional[str] = Field(None, description="The project id of the identity, if applicable.")
59
- agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.")
60
- block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.")
60
+ agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.", deprecated=True)
61
+ block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.", deprecated=True)
61
62
  properties: Optional[List[IdentityProperty]] = Field(None, description="List of properties associated with the identity.")
62
63
 
63
64
 
@@ -66,8 +67,8 @@ class IdentityUpsert(LettaBase):
66
67
  name: str = Field(..., description="The name of the identity.")
67
68
  identity_type: IdentityType = Field(..., description="The type of the identity.")
68
69
  project_id: Optional[str] = Field(None, description="The project id of the identity, if applicable.")
69
- agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.")
70
- block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.")
70
+ agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.", deprecated=True)
71
+ block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.", deprecated=True)
71
72
  properties: Optional[List[IdentityProperty]] = Field(None, description="List of properties associated with the identity.")
72
73
 
73
74
 
@@ -75,6 +76,14 @@ class IdentityUpdate(LettaBase):
75
76
  identifier_key: Optional[str] = Field(None, description="External, user-generated identifier key of the identity.")
76
77
  name: Optional[str] = Field(None, description="The name of the identity.")
77
78
  identity_type: Optional[IdentityType] = Field(None, description="The type of the identity.")
78
- agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.")
79
- block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.")
79
+ agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.", deprecated=True)
80
+ block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.", deprecated=True)
80
81
  properties: Optional[List[IdentityProperty]] = Field(None, description="List of properties associated with the identity.")
82
+
83
+
84
+ class PaginatedIdentities(LettaBase):
85
+ """Paginated response for identities"""
86
+
87
+ data: List[Identity] = Field(..., description="List of identities")
88
+ next_cursor: Optional[str] = Field(None, description="Cursor for fetching the next page")
89
+ has_more: bool = Field(..., description="Whether more results exist after this page")
letta/schemas/job.py CHANGED
@@ -3,6 +3,8 @@ from typing import TYPE_CHECKING, List, Optional
3
3
 
4
4
  from pydantic import BaseModel, ConfigDict, Field
5
5
 
6
+ from letta.schemas.enums import PrimitiveType
7
+
6
8
  if TYPE_CHECKING:
7
9
  from letta.schemas.letta_request import LettaRequest
8
10
 
@@ -15,7 +17,7 @@ from letta.schemas.letta_stop_reason import StopReasonType
15
17
 
16
18
 
17
19
  class JobBase(OrmMetadataBase):
18
- __id_prefix__ = "job"
20
+ __id_prefix__ = PrimitiveType.JOB.value
19
21
  status: JobStatus = Field(default=JobStatus.created, description="The status of the job.")
20
22
  created_at: datetime = Field(default_factory=get_utc_time, description="The unix timestamp of when the job was created.")
21
23