letta-nightly 0.13.0.dev20251031104146__py3-none-any.whl → 0.13.1.dev20251101010313__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 (105) hide show
  1. letta/__init__.py +1 -1
  2. letta/adapters/simple_llm_stream_adapter.py +1 -0
  3. letta/agents/letta_agent_v2.py +8 -0
  4. letta/agents/letta_agent_v3.py +127 -27
  5. letta/agents/temporal/activities/__init__.py +25 -0
  6. letta/agents/temporal/activities/create_messages.py +26 -0
  7. letta/agents/temporal/activities/create_step.py +57 -0
  8. letta/agents/temporal/activities/example_activity.py +9 -0
  9. letta/agents/temporal/activities/execute_tool.py +130 -0
  10. letta/agents/temporal/activities/llm_request.py +114 -0
  11. letta/agents/temporal/activities/prepare_messages.py +27 -0
  12. letta/agents/temporal/activities/refresh_context.py +160 -0
  13. letta/agents/temporal/activities/summarize_conversation_history.py +77 -0
  14. letta/agents/temporal/activities/update_message_ids.py +25 -0
  15. letta/agents/temporal/activities/update_run.py +43 -0
  16. letta/agents/temporal/constants.py +59 -0
  17. letta/agents/temporal/temporal_agent_workflow.py +704 -0
  18. letta/agents/temporal/types.py +275 -0
  19. letta/constants.py +11 -0
  20. letta/errors.py +4 -0
  21. letta/functions/function_sets/base.py +0 -11
  22. letta/groups/helpers.py +7 -1
  23. letta/groups/sleeptime_multi_agent_v4.py +4 -3
  24. letta/interfaces/anthropic_streaming_interface.py +0 -1
  25. letta/interfaces/openai_streaming_interface.py +103 -100
  26. letta/llm_api/anthropic_client.py +57 -12
  27. letta/llm_api/bedrock_client.py +1 -0
  28. letta/llm_api/deepseek_client.py +3 -2
  29. letta/llm_api/google_vertex_client.py +5 -4
  30. letta/llm_api/groq_client.py +1 -0
  31. letta/llm_api/llm_client_base.py +15 -1
  32. letta/llm_api/openai.py +2 -2
  33. letta/llm_api/openai_client.py +17 -3
  34. letta/llm_api/xai_client.py +1 -0
  35. letta/orm/agent.py +3 -0
  36. letta/orm/organization.py +4 -0
  37. letta/orm/sqlalchemy_base.py +7 -0
  38. letta/otel/tracing.py +131 -4
  39. letta/schemas/agent.py +108 -40
  40. letta/schemas/agent_file.py +10 -10
  41. letta/schemas/block.py +22 -3
  42. letta/schemas/enums.py +21 -0
  43. letta/schemas/environment_variables.py +3 -2
  44. letta/schemas/group.py +3 -3
  45. letta/schemas/letta_response.py +36 -4
  46. letta/schemas/llm_batch_job.py +3 -3
  47. letta/schemas/llm_config.py +123 -4
  48. letta/schemas/mcp.py +3 -2
  49. letta/schemas/mcp_server.py +3 -2
  50. letta/schemas/message.py +167 -49
  51. letta/schemas/model.py +265 -0
  52. letta/schemas/organization.py +2 -1
  53. letta/schemas/passage.py +2 -1
  54. letta/schemas/provider_trace.py +2 -1
  55. letta/schemas/providers/openrouter.py +1 -2
  56. letta/schemas/run_metrics.py +2 -1
  57. letta/schemas/sandbox_config.py +3 -1
  58. letta/schemas/step_metrics.py +2 -1
  59. letta/schemas/tool_rule.py +2 -2
  60. letta/schemas/user.py +2 -1
  61. letta/server/rest_api/app.py +5 -1
  62. letta/server/rest_api/routers/v1/__init__.py +4 -0
  63. letta/server/rest_api/routers/v1/agents.py +71 -9
  64. letta/server/rest_api/routers/v1/blocks.py +7 -7
  65. letta/server/rest_api/routers/v1/groups.py +40 -0
  66. letta/server/rest_api/routers/v1/identities.py +2 -2
  67. letta/server/rest_api/routers/v1/internal_agents.py +31 -0
  68. letta/server/rest_api/routers/v1/internal_blocks.py +177 -0
  69. letta/server/rest_api/routers/v1/internal_runs.py +25 -1
  70. letta/server/rest_api/routers/v1/runs.py +2 -22
  71. letta/server/rest_api/routers/v1/tools.py +12 -1
  72. letta/server/server.py +20 -4
  73. letta/services/agent_manager.py +4 -4
  74. letta/services/archive_manager.py +16 -0
  75. letta/services/group_manager.py +44 -0
  76. letta/services/helpers/run_manager_helper.py +2 -2
  77. letta/services/lettuce/lettuce_client.py +148 -0
  78. letta/services/mcp/base_client.py +9 -3
  79. letta/services/run_manager.py +148 -37
  80. letta/services/source_manager.py +91 -3
  81. letta/services/step_manager.py +2 -3
  82. letta/services/streaming_service.py +52 -13
  83. letta/services/summarizer/summarizer.py +28 -2
  84. letta/services/tool_executor/builtin_tool_executor.py +1 -1
  85. letta/services/tool_executor/core_tool_executor.py +2 -117
  86. letta/services/tool_sandbox/e2b_sandbox.py +4 -1
  87. letta/services/tool_schema_generator.py +2 -2
  88. letta/validators.py +21 -0
  89. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/METADATA +1 -1
  90. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/RECORD +93 -87
  91. letta/agent.py +0 -1758
  92. letta/cli/cli_load.py +0 -16
  93. letta/client/__init__.py +0 -0
  94. letta/client/streaming.py +0 -95
  95. letta/client/utils.py +0 -78
  96. letta/functions/async_composio_toolset.py +0 -109
  97. letta/functions/composio_helpers.py +0 -96
  98. letta/helpers/composio_helpers.py +0 -38
  99. letta/orm/job_messages.py +0 -33
  100. letta/schemas/providers.py +0 -1617
  101. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -132
  102. letta/services/tool_executor/composio_tool_executor.py +0 -57
  103. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/WHEEL +0 -0
  104. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/entry_points.txt +0 -0
  105. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/licenses/LICENSE +0 -0
letta/schemas/model.py ADDED
@@ -0,0 +1,265 @@
1
+ from typing import Annotated, Literal, Optional, Union
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from letta.schemas.embedding_config import EmbeddingConfig
6
+ from letta.schemas.enums import ProviderType
7
+ from letta.schemas.llm_config import LLMConfig
8
+ from letta.schemas.response_format import ResponseFormatUnion
9
+
10
+
11
+ class ModelBase(BaseModel):
12
+ handle: str = Field(..., description="Unique handle for API reference (format: provider_display_name/model_display_name)")
13
+ name: str = Field(..., description="The actual model name used by the provider")
14
+ display_name: str = Field(..., description="Display name for the model shown in UI")
15
+ provider_type: ProviderType = Field(..., description="The type of the provider")
16
+ provider_name: str = Field(..., description="The name of the provider")
17
+ model_type: Literal["llm", "embedding"] = Field(..., description="Type of model (llm or embedding)")
18
+
19
+
20
+ class Model(ModelBase):
21
+ model_type: Literal["llm"] = Field("llm", description="Type of model (llm or embedding)")
22
+ max_context_window: int = Field(..., description="The maximum context window for the model")
23
+ # supports_token_streaming: Optional[bool] = Field(None, description="Whether token streaming is supported")
24
+ # supports_tool_calling: Optional[bool] = Field(None, description="Whether tool calling is supported")
25
+
26
+ def _from_llm_config(self, llm_config: LLMConfig) -> "Model":
27
+ return self(
28
+ handle=llm_config.handle,
29
+ name=llm_config.model,
30
+ display_name=llm_config.display_name,
31
+ provider_type=llm_config.model_endpoint_type,
32
+ provider_name=llm_config.provider_name,
33
+ )
34
+
35
+
36
+ class EmbeddingModel(ModelBase):
37
+ model_type: Literal["embedding"] = Field("embedding", description="Type of model (llm or embedding)")
38
+ embedding_dim: int = Field(..., description="The dimension of the embedding")
39
+
40
+ def _from_embedding_config(self, embedding_config: EmbeddingConfig) -> "Model":
41
+ return self(
42
+ handle=embedding_config.handle,
43
+ name=embedding_config.embedding_model,
44
+ display_name=embedding_config.embedding_model,
45
+ provider_type=embedding_config.embedding_endpoint_type,
46
+ provider_name=embedding_config.embedding_endpoint_type,
47
+ )
48
+
49
+
50
+ class ModelSettings(BaseModel):
51
+ """Schema for defining settings for a model"""
52
+
53
+ model: str = Field(..., description="The name of the model.")
54
+ max_output_tokens: int = Field(4096, description="The maximum number of tokens the model can generate.")
55
+
56
+
57
+ class OpenAIReasoning(BaseModel):
58
+ reasoning_effort: Literal["minimal", "low", "medium", "high"] = Field(
59
+ "minimal", description="The reasoning effort to use when generating text reasoning models"
60
+ )
61
+
62
+ # TODO: implement support for this
63
+ # summary: Optional[Literal["auto", "detailed"]] = Field(
64
+ # None, description="The reasoning summary level to use when generating text reasoning models"
65
+ # )
66
+
67
+
68
+ class OpenAIModelSettings(ModelSettings):
69
+ provider: Literal["openai"] = Field("openai", description="The provider of the model.")
70
+ temperature: float = Field(0.7, description="The temperature of the model.")
71
+ reasoning: OpenAIReasoning = Field(OpenAIReasoning(reasoning_effort="high"), description="The reasoning configuration for the model.")
72
+ response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.")
73
+
74
+ # TODO: implement support for these
75
+ # reasoning_summary: Optional[Literal["none", "short", "detailed"]] = Field(
76
+ # None, description="The reasoning summary level to use when generating text reasoning models"
77
+ # )
78
+ # max_tool_calls: int = Field(10, description="The maximum number of tool calls the model can make.")
79
+ # parallel_tool_calls: bool = Field(False, description="Whether the model supports parallel tool calls.")
80
+ # top_logprobs: int = Field(10, description="The number of top logprobs to return.")
81
+ # top_p: float = Field(1.0, description="The top-p value to use when generating text.")
82
+
83
+ def _to_legacy_config_params(self) -> dict:
84
+ return {
85
+ "temperature": self.temperature,
86
+ "max_tokens": self.max_output_tokens,
87
+ "reasoning_effort": self.reasoning.reasoning_effort,
88
+ "response_format": self.response_format,
89
+ }
90
+
91
+
92
+ # "thinking": {
93
+ # "type": "enabled",
94
+ # "budget_tokens": 10000
95
+ # }
96
+
97
+
98
+ class AnthropicThinking(BaseModel):
99
+ type: Literal["enabled", "disabled"] = Field("enabled", description="The type of thinking to use.")
100
+ budget_tokens: int = Field(1024, description="The maximum number of tokens the model can use for extended thinking.")
101
+
102
+
103
+ class AnthropicModelSettings(ModelSettings):
104
+ provider: Literal["anthropic"] = Field("anthropic", description="The provider of the model.")
105
+ temperature: float = Field(1.0, description="The temperature of the model.")
106
+ thinking: AnthropicThinking = Field(
107
+ AnthropicThinking(type="enabled", budget_tokens=1024), description="The thinking configuration for the model."
108
+ )
109
+
110
+ # gpt-5 models only
111
+ verbosity: Optional[Literal["low", "medium", "high"]] = Field(
112
+ None,
113
+ description="Soft control for how verbose model output should be, used for GPT-5 models.",
114
+ )
115
+
116
+ # TODO: implement support for these
117
+ # top_k: Optional[int] = Field(None, description="The number of top tokens to return.")
118
+ # top_p: Optional[float] = Field(None, description="The top-p value to use when generating text.")
119
+
120
+ def _to_legacy_config_params(self) -> dict:
121
+ return {
122
+ "temperature": self.temperature,
123
+ "max_tokens": self.max_output_tokens,
124
+ "extended_thinking": self.thinking.type == "enabled",
125
+ "thinking_budget_tokens": self.thinking.budget_tokens,
126
+ "verbosity": self.verbosity,
127
+ }
128
+
129
+
130
+ class GeminiThinkingConfig(BaseModel):
131
+ include_thoughts: bool = Field(True, description="Whether to include thoughts in the model's response.")
132
+ thinking_budget: int = Field(1024, description="The thinking budget for the model.")
133
+
134
+
135
+ class GoogleAIModelSettings(ModelSettings):
136
+ provider: Literal["google_ai"] = Field("google_ai", description="The provider of the model.")
137
+ temperature: float = Field(0.7, description="The temperature of the model.")
138
+ thinking_config: GeminiThinkingConfig = Field(
139
+ GeminiThinkingConfig(include_thoughts=True, thinking_budget=1024), description="The thinking configuration for the model."
140
+ )
141
+ response_schema: Optional[ResponseFormatUnion] = Field(None, description="The response schema for the model.")
142
+ max_output_tokens: int = Field(65536, description="The maximum number of tokens the model can generate.")
143
+
144
+ def _to_legacy_config_params(self) -> dict:
145
+ return {
146
+ "temperature": self.temperature,
147
+ "max_tokens": self.max_output_tokens,
148
+ "max_reasoning_tokens": self.thinking_config.thinking_budget if self.thinking_config.include_thoughts else 0,
149
+ }
150
+
151
+
152
+ class GoogleVertexModelSettings(GoogleAIModelSettings):
153
+ provider: Literal["google_vertex"] = Field("google_vertex", description="The provider of the model.")
154
+
155
+
156
+ class AzureModelSettings(ModelSettings):
157
+ """Azure OpenAI model configuration (OpenAI-compatible)."""
158
+
159
+ provider: Literal["azure"] = Field("azure", description="The provider of the model.")
160
+ temperature: float = Field(0.7, description="The temperature of the model.")
161
+ response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.")
162
+
163
+ def _to_legacy_config_params(self) -> dict:
164
+ return {
165
+ "temperature": self.temperature,
166
+ "max_tokens": self.max_output_tokens,
167
+ "response_format": self.response_format,
168
+ }
169
+
170
+
171
+ class XAIModelSettings(ModelSettings):
172
+ """xAI model configuration (OpenAI-compatible)."""
173
+
174
+ provider: Literal["xai"] = Field("xai", description="The provider of the model.")
175
+ temperature: float = Field(0.7, description="The temperature of the model.")
176
+ response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.")
177
+
178
+ def _to_legacy_config_params(self) -> dict:
179
+ return {
180
+ "temperature": self.temperature,
181
+ "max_tokens": self.max_output_tokens,
182
+ "response_format": self.response_format,
183
+ }
184
+
185
+
186
+ class GroqModelSettings(ModelSettings):
187
+ """Groq model configuration (OpenAI-compatible)."""
188
+
189
+ provider: Literal["groq"] = Field("groq", description="The provider of the model.")
190
+ temperature: float = Field(0.7, description="The temperature of the model.")
191
+ response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.")
192
+
193
+ def _to_legacy_config_params(self) -> dict:
194
+ return {
195
+ "temperature": self.temperature,
196
+ "max_tokens": self.max_output_tokens,
197
+ "response_format": self.response_format,
198
+ }
199
+
200
+
201
+ class DeepseekModelSettings(ModelSettings):
202
+ """Deepseek model configuration (OpenAI-compatible)."""
203
+
204
+ provider: Literal["deepseek"] = Field("deepseek", description="The provider of the model.")
205
+ temperature: float = Field(0.7, description="The temperature of the model.")
206
+ response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.")
207
+
208
+ def _to_legacy_config_params(self) -> dict:
209
+ return {
210
+ "temperature": self.temperature,
211
+ "max_tokens": self.max_output_tokens,
212
+ "response_format": self.response_format,
213
+ }
214
+
215
+
216
+ class TogetherModelSettings(ModelSettings):
217
+ """Together AI model configuration (OpenAI-compatible)."""
218
+
219
+ provider: Literal["together"] = Field("together", description="The provider of the model.")
220
+ temperature: float = Field(0.7, description="The temperature of the model.")
221
+ response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.")
222
+
223
+ def _to_legacy_config_params(self) -> dict:
224
+ return {
225
+ "temperature": self.temperature,
226
+ "max_tokens": self.max_output_tokens,
227
+ "response_format": self.response_format,
228
+ }
229
+
230
+
231
+ class BedrockModelSettings(ModelSettings):
232
+ """AWS Bedrock model configuration."""
233
+
234
+ provider: Literal["bedrock"] = Field("bedrock", description="The provider of the model.")
235
+ temperature: float = Field(0.7, description="The temperature of the model.")
236
+ response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.")
237
+
238
+ def _to_legacy_config_params(self) -> dict:
239
+ return {
240
+ "temperature": self.temperature,
241
+ "max_tokens": self.max_output_tokens,
242
+ "response_format": self.response_format,
243
+ }
244
+
245
+
246
+ ModelSettingsUnion = Annotated[
247
+ Union[
248
+ OpenAIModelSettings,
249
+ AnthropicModelSettings,
250
+ GoogleAIModelSettings,
251
+ GoogleVertexModelSettings,
252
+ AzureModelSettings,
253
+ XAIModelSettings,
254
+ GroqModelSettings,
255
+ DeepseekModelSettings,
256
+ TogetherModelSettings,
257
+ BedrockModelSettings,
258
+ ],
259
+ Field(discriminator="provider"),
260
+ ]
261
+
262
+
263
+ class EmbeddingModelSettings(BaseModel):
264
+ model: str = Field(..., description="The name of the model.")
265
+ provider: Literal["openai", "ollama"] = Field(..., description="The provider of the model.")
@@ -4,12 +4,13 @@ from typing import Optional
4
4
  from pydantic import Field
5
5
 
6
6
  from letta.helpers.datetime_helpers import get_utc_time
7
+ from letta.schemas.enums import PrimitiveType
7
8
  from letta.schemas.letta_base import LettaBase
8
9
  from letta.utils import create_random_username
9
10
 
10
11
 
11
12
  class OrganizationBase(LettaBase):
12
- __id_prefix__ = "org"
13
+ __id_prefix__ = PrimitiveType.ORGANIZATION.value
13
14
 
14
15
 
15
16
  class Organization(OrganizationBase):
letta/schemas/passage.py CHANGED
@@ -6,11 +6,12 @@ from pydantic import Field, field_validator
6
6
  from letta.constants import MAX_EMBEDDING_DIM
7
7
  from letta.helpers.datetime_helpers import get_utc_time
8
8
  from letta.schemas.embedding_config import EmbeddingConfig
9
+ from letta.schemas.enums import PrimitiveType
9
10
  from letta.schemas.letta_base import OrmMetadataBase
10
11
 
11
12
 
12
13
  class PassageBase(OrmMetadataBase):
13
- __id_prefix__ = "passage"
14
+ __id_prefix__ = PrimitiveType.PASSAGE.value
14
15
 
15
16
  is_deleted: bool = Field(False, description="Whether this passage is deleted or not.")
16
17
 
@@ -6,11 +6,12 @@ from typing import Any, Dict, Optional
6
6
  from pydantic import BaseModel, Field
7
7
 
8
8
  from letta.helpers.datetime_helpers import get_utc_time
9
+ from letta.schemas.enums import PrimitiveType
9
10
  from letta.schemas.letta_base import OrmMetadataBase
10
11
 
11
12
 
12
13
  class BaseProviderTrace(OrmMetadataBase):
13
- __id_prefix__ = "provider_trace"
14
+ __id_prefix__ = PrimitiveType.PROVIDER_TRACE.value
14
15
 
15
16
 
16
17
  class ProviderTraceCreate(BaseModel):
@@ -21,7 +21,6 @@ class OpenRouterProvider(OpenAIProvider):
21
21
  provider_category: ProviderCategory = Field(ProviderCategory.base, description="The category of the provider (base or byok)")
22
22
  api_key: str = Field(..., description="API key for the OpenRouter API.")
23
23
  base_url: str = Field("https://openrouter.ai/api/v1", description="Base URL for the OpenRouter API.")
24
- handle_base: str | None = Field(None, description="Custom handle base name for model handles (e.g., 'custom' instead of 'openrouter').")
25
24
 
26
25
  def _list_llm_models(self, data: list[dict]) -> list[LLMConfig]:
27
26
  """
@@ -34,7 +33,7 @@ class OpenRouterProvider(OpenAIProvider):
34
33
  continue
35
34
  model_name, context_window_size = check
36
35
 
37
- handle = self.get_handle(model_name, base_name=self.handle_base) if self.handle_base else self.get_handle(model_name)
36
+ handle = self.get_handle(model_name)
38
37
 
39
38
  config = LLMConfig(
40
39
  model=model_name,
@@ -2,11 +2,12 @@ from typing import List, Optional
2
2
 
3
3
  from pydantic import Field
4
4
 
5
+ from letta.schemas.enums import PrimitiveType
5
6
  from letta.schemas.letta_base import LettaBase
6
7
 
7
8
 
8
9
  class RunMetricsBase(LettaBase):
9
- __id_prefix__ = "run"
10
+ __id_prefix__ = PrimitiveType.RUN.value
10
11
 
11
12
 
12
13
  class RunMetrics(RunMetricsBase):
@@ -102,7 +102,9 @@ class SandboxConfig(SandboxConfigBase):
102
102
  config: Dict = Field(default_factory=lambda: {}, description="The JSON sandbox settings data.")
103
103
 
104
104
  def get_e2b_config(self) -> E2BSandboxConfig:
105
- return E2BSandboxConfig(**self.config)
105
+ config_dict = self.config.copy()
106
+ config_dict["template"] = tool_settings.e2b_sandbox_template_id
107
+ return E2BSandboxConfig(**config_dict)
106
108
 
107
109
  def get_local_config(self) -> LocalSandboxConfig:
108
110
  return LocalSandboxConfig(**self.config)
@@ -2,11 +2,12 @@ from typing import Optional
2
2
 
3
3
  from pydantic import Field
4
4
 
5
+ from letta.schemas.enums import PrimitiveType
5
6
  from letta.schemas.letta_base import LettaBase
6
7
 
7
8
 
8
9
  class StepMetricsBase(LettaBase):
9
- __id_prefix__ = "step"
10
+ __id_prefix__ = PrimitiveType.STEP.value
10
11
 
11
12
 
12
13
  class StepMetrics(StepMetricsBase):
@@ -4,14 +4,14 @@ from typing import Annotated, Any, Dict, List, Literal, Optional, Set, Union
4
4
 
5
5
  from pydantic import BaseModel, Field, field_validator, model_validator
6
6
 
7
- from letta.schemas.enums import ToolRuleType
7
+ from letta.schemas.enums import PrimitiveType, ToolRuleType
8
8
  from letta.schemas.letta_base import LettaBase
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
13
  class BaseToolRule(LettaBase):
14
- __id_prefix__ = "tool_rule"
14
+ __id_prefix__ = PrimitiveType.TOOL_RULE.value
15
15
  tool_name: str = Field(..., description="The name of the tool. Must exist in the database for the user's organization.")
16
16
  type: ToolRuleType = Field(..., description="The type of the message.")
17
17
  prompt_template: Optional[str] = Field(
letta/schemas/user.py CHANGED
@@ -4,11 +4,12 @@ from typing import Optional
4
4
  from pydantic import Field
5
5
 
6
6
  from letta.constants import DEFAULT_ORG_ID
7
+ from letta.schemas.enums import PrimitiveType
7
8
  from letta.schemas.letta_base import LettaBase
8
9
 
9
10
 
10
11
  class UserBase(LettaBase):
11
- __id_prefix__ = "user"
12
+ __id_prefix__ = PrimitiveType.USER.value
12
13
 
13
14
 
14
15
  class User(UserBase):
@@ -26,6 +26,7 @@ from letta.errors import (
26
26
  AgentFileImportError,
27
27
  AgentNotFoundForExportError,
28
28
  BedrockPermissionError,
29
+ HandleNotFoundError,
29
30
  LettaAgentNotFoundError,
30
31
  LettaExpiredError,
31
32
  LettaInvalidArgumentError,
@@ -39,6 +40,7 @@ from letta.errors import (
39
40
  LettaUserNotFoundError,
40
41
  LLMAuthenticationError,
41
42
  LLMError,
43
+ LLMProviderOverloaded,
42
44
  LLMRateLimitError,
43
45
  LLMTimeoutError,
44
46
  PendingApprovalError,
@@ -270,7 +272,7 @@ def create_application() -> "FastAPI":
270
272
  return JSONResponse(
271
273
  status_code=500,
272
274
  content={
273
- "detail": "An internal server error occurred",
275
+ "detail": "An unknown error occurred",
274
276
  # Only include error details in debug/development mode
275
277
  # "debug_info": str(exc) if settings.debug else None
276
278
  },
@@ -369,6 +371,7 @@ def create_application() -> "FastAPI":
369
371
  app.add_exception_handler(LettaAgentNotFoundError, _error_handler_404_agent)
370
372
  app.add_exception_handler(LettaUserNotFoundError, _error_handler_404_user)
371
373
  app.add_exception_handler(AgentNotFoundForExportError, _error_handler_404)
374
+ app.add_exception_handler(HandleNotFoundError, _error_handler_404)
372
375
 
373
376
  # 410 Expired errors
374
377
  app.add_exception_handler(LettaExpiredError, _error_handler_410)
@@ -396,6 +399,7 @@ def create_application() -> "FastAPI":
396
399
  # 503 Service Unavailable errors
397
400
  app.add_exception_handler(OperationalError, _error_handler_503)
398
401
  app.add_exception_handler(LettaServiceUnavailableError, _error_handler_503)
402
+ app.add_exception_handler(LLMProviderOverloaded, _error_handler_503)
399
403
 
400
404
  @app.exception_handler(IncompatibleAgentType)
401
405
  async def handle_incompatible_agent_type(request: Request, exc: IncompatibleAgentType):
@@ -7,6 +7,8 @@ from letta.server.rest_api.routers.v1.folders import router as folders_router
7
7
  from letta.server.rest_api.routers.v1.groups import router as groups_router
8
8
  from letta.server.rest_api.routers.v1.health import router as health_router
9
9
  from letta.server.rest_api.routers.v1.identities import router as identities_router
10
+ from letta.server.rest_api.routers.v1.internal_agents import router as internal_agents_router
11
+ from letta.server.rest_api.routers.v1.internal_blocks import router as internal_blocks_router
10
12
  from letta.server.rest_api.routers.v1.internal_runs import router as internal_runs_router
11
13
  from letta.server.rest_api.routers.v1.internal_templates import router as internal_templates_router
12
14
  from letta.server.rest_api.routers.v1.jobs import router as jobs_router
@@ -32,6 +34,8 @@ ROUTERS = [
32
34
  chat_completions_router,
33
35
  groups_router,
34
36
  identities_router,
37
+ internal_agents_router,
38
+ internal_blocks_router,
35
39
  internal_runs_router,
36
40
  internal_templates_router,
37
41
  llm_router,
@@ -25,21 +25,23 @@ from letta.errors import (
25
25
  AgentNotFoundForExportError,
26
26
  PendingApprovalError,
27
27
  )
28
- from letta.helpers.datetime_helpers import get_utc_timestamp_ns
28
+ from letta.groups.sleeptime_multi_agent_v4 import SleeptimeMultiAgentV4
29
+ from letta.helpers.datetime_helpers import get_utc_time, get_utc_timestamp_ns
29
30
  from letta.log import get_logger
30
31
  from letta.orm.errors import NoResultFound
31
32
  from letta.otel.context import get_ctx_attributes
32
33
  from letta.otel.metric_registry import MetricRegistry
33
34
  from letta.schemas.agent import AgentRelationships, AgentState, CreateAgent, UpdateAgent
34
35
  from letta.schemas.agent_file import AgentFileSchema
35
- from letta.schemas.block import BaseBlock, Block, BlockUpdate
36
- from letta.schemas.enums import AgentType, RunStatus
36
+ from letta.schemas.block import BaseBlock, Block, BlockResponse, BlockUpdate
37
+ from letta.schemas.enums import AgentType, MessageRole, RunStatus
37
38
  from letta.schemas.file import AgentFileAttachment, FileMetadataBase, PaginatedAgentFiles
38
39
  from letta.schemas.group import Group
39
40
  from letta.schemas.job import LettaRequestConfig
40
41
  from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion, MessageType
42
+ from letta.schemas.letta_message_content import TextContent
41
43
  from letta.schemas.letta_request import LettaAsyncRequest, LettaRequest, LettaStreamingRequest
42
- from letta.schemas.letta_response import LettaResponse
44
+ from letta.schemas.letta_response import LettaResponse, LettaStreamingResponse
43
45
  from letta.schemas.letta_stop_reason import StopReasonType
44
46
  from letta.schemas.memory import (
45
47
  ArchivalMemorySearchResponse,
@@ -48,7 +50,7 @@ from letta.schemas.memory import (
48
50
  CreateArchivalMemory,
49
51
  Memory,
50
52
  )
51
- from letta.schemas.message import BaseMessage, MessageCreate, MessageCreateType, MessageSearchRequest, MessageSearchResult
53
+ from letta.schemas.message import Message, MessageCreate, MessageCreateType, MessageSearchRequest, MessageSearchResult
52
54
  from letta.schemas.passage import Passage
53
55
  from letta.schemas.run import Run as PydanticRun, RunUpdate
54
56
  from letta.schemas.source import BaseSource, Source
@@ -915,7 +917,7 @@ async def retrieve_agent_memory(
915
917
  return await server.get_agent_memory_async(agent_id=agent_id, actor=actor)
916
918
 
917
919
 
918
- @router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block")
920
+ @router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=BlockResponse, operation_id="retrieve_core_memory_block")
919
921
  async def retrieve_block_for_agent(
920
922
  block_label: str,
921
923
  agent_id: AgentId,
@@ -930,7 +932,7 @@ async def retrieve_block_for_agent(
930
932
  return await server.agent_manager.get_block_with_label_async(agent_id=agent_id, block_label=block_label, actor=actor)
931
933
 
932
934
 
933
- @router.get("/{agent_id}/core-memory/blocks", response_model=list[Block], operation_id="list_core_memory_blocks")
935
+ @router.get("/{agent_id}/core-memory/blocks", response_model=list[BlockResponse], operation_id="list_core_memory_blocks")
934
936
  async def list_blocks_for_agent(
935
937
  agent_id: AgentId,
936
938
  server: "SyncServer" = Depends(get_letta_server),
@@ -962,7 +964,7 @@ async def list_blocks_for_agent(
962
964
  )
963
965
 
964
966
 
965
- @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block")
967
+ @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=BlockResponse, operation_id="modify_core_memory_block")
966
968
  async def modify_block_for_agent(
967
969
  block_label: str,
968
970
  agent_id: AgentId,
@@ -1394,7 +1396,7 @@ async def send_message(
1394
1396
  # noinspection PyInconsistentReturns
1395
1397
  @router.post(
1396
1398
  "/{agent_id}/messages/stream",
1397
- response_model=None,
1399
+ response_model=LettaStreamingResponse,
1398
1400
  operation_id="create_agent_message_stream",
1399
1401
  responses={
1400
1402
  200: {
@@ -1902,3 +1904,63 @@ async def summarize_messages(
1902
1904
  status_code=status.HTTP_403_FORBIDDEN,
1903
1905
  detail="Summarization is not currently supported for this agent configuration. Please contact Letta support.",
1904
1906
  )
1907
+
1908
+
1909
+ class CaptureMessagesRequest(BaseModel):
1910
+ provider: str
1911
+ model: str
1912
+ request_messages: list[dict[str, Any]]
1913
+ response_dict: dict[str, Any]
1914
+
1915
+
1916
+ @router.post("/{agent_id}/messages/capture", response_model=str, operation_id="capture_messages", include_in_schema=False)
1917
+ async def capture_messages(
1918
+ agent_id: AgentId,
1919
+ request: CaptureMessagesRequest = Body(...),
1920
+ server: "SyncServer" = Depends(get_letta_server),
1921
+ headers: HeaderParams = Depends(get_headers),
1922
+ ):
1923
+ """
1924
+ Capture a list of messages for an agent.
1925
+ """
1926
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
1927
+ agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
1928
+
1929
+ messages_to_persist = []
1930
+
1931
+ # Input user messages
1932
+ for message in request.request_messages:
1933
+ if message["role"] == "user":
1934
+ messages_to_persist.append(
1935
+ Message(
1936
+ role=MessageRole.user,
1937
+ content=[(TextContent(text=message["content"]))],
1938
+ agent_id=agent_id,
1939
+ tool_calls=None,
1940
+ tool_call_id=None,
1941
+ created_at=get_utc_time(),
1942
+ )
1943
+ )
1944
+
1945
+ # Assistant response
1946
+ messages_to_persist.append(
1947
+ Message(
1948
+ role=MessageRole.assistant,
1949
+ content=[(TextContent(text=request.response_dict["content"]))],
1950
+ agent_id=agent_id,
1951
+ model=request.model,
1952
+ tool_calls=None,
1953
+ tool_call_id=None,
1954
+ created_at=get_utc_time(),
1955
+ )
1956
+ )
1957
+
1958
+ response_messages = await server.message_manager.create_many_messages_async(messages_to_persist, actor=actor)
1959
+
1960
+ sleeptime_group = agent.multi_agent_group if agent.multi_agent_group and agent.multi_agent_group.manager_type == "sleeptime" else None
1961
+ if sleeptime_group:
1962
+ sleeptime_agent_loop = SleeptimeMultiAgentV4(agent_state=agent, actor=actor, group=sleeptime_group)
1963
+ sleeptime_agent_loop.response_messages = response_messages
1964
+ run_ids = await sleeptime_agent_loop.run_sleeptime_agents()
1965
+
1966
+ return JSONResponse({"success": True, "messages_created": len(response_messages), "run_ids": run_ids})
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
4
 
5
5
  from letta.orm.errors import NoResultFound
6
6
  from letta.schemas.agent import AgentRelationships, AgentState
7
- from letta.schemas.block import BaseBlock, Block, BlockUpdate, CreateBlock
7
+ from letta.schemas.block import BaseBlock, Block, BlockResponse, BlockUpdate, CreateBlock
8
8
  from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
9
9
  from letta.server.server import SyncServer
10
10
  from letta.utils import is_1_0_sdk_version
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
  router = APIRouter(prefix="/blocks", tags=["blocks"])
17
17
 
18
18
 
19
- @router.get("/", response_model=List[Block], operation_id="list_blocks")
19
+ @router.get("/", response_model=List[BlockResponse], operation_id="list_blocks")
20
20
  async def list_blocks(
21
21
  # query parameters
22
22
  label: Optional[str] = Query(None, description="Labels to include (e.g. human, persona)"),
@@ -117,7 +117,7 @@ async def count_blocks(
117
117
  return await server.block_manager.size_async(actor=actor)
118
118
 
119
119
 
120
- @router.post("/", response_model=Block, operation_id="create_block")
120
+ @router.post("/", response_model=BlockResponse, operation_id="create_block")
121
121
  async def create_block(
122
122
  create_block: CreateBlock = Body(...),
123
123
  server: SyncServer = Depends(get_letta_server),
@@ -128,7 +128,7 @@ async def create_block(
128
128
  return await server.block_manager.create_or_update_block_async(actor=actor, block=block)
129
129
 
130
130
 
131
- @router.patch("/{block_id}", response_model=Block, operation_id="modify_block")
131
+ @router.patch("/{block_id}", response_model=BlockResponse, operation_id="modify_block")
132
132
  async def modify_block(
133
133
  block_id: BlockId,
134
134
  block_update: BlockUpdate = Body(...),
@@ -149,7 +149,7 @@ async def delete_block(
149
149
  await server.block_manager.delete_block_async(block_id=block_id, actor=actor)
150
150
 
151
151
 
152
- @router.get("/{block_id}", response_model=Block, operation_id="retrieve_block")
152
+ @router.get("/{block_id}", response_model=BlockResponse, operation_id="retrieve_block")
153
153
  async def retrieve_block(
154
154
  block_id: BlockId,
155
155
  server: SyncServer = Depends(get_letta_server),
@@ -214,7 +214,7 @@ async def list_agents_for_block(
214
214
  return agents
215
215
 
216
216
 
217
- @router.patch("/{block_id}/identities/attach/{identity_id}", response_model=Block, operation_id="attach_identity_to_block")
217
+ @router.patch("/{block_id}/identities/attach/{identity_id}", response_model=BlockResponse, operation_id="attach_identity_to_block")
218
218
  async def attach_identity_to_block(
219
219
  identity_id: str,
220
220
  block_id: BlockId,
@@ -233,7 +233,7 @@ async def attach_identity_to_block(
233
233
  return await server.block_manager.get_block_by_id_async(block_id=block_id, actor=actor)
234
234
 
235
235
 
236
- @router.patch("/{block_id}/identities/detach/{identity_id}", response_model=Block, operation_id="detach_identity_from_block")
236
+ @router.patch("/{block_id}/identities/detach/{identity_id}", response_model=BlockResponse, operation_id="detach_identity_from_block")
237
237
  async def detach_identity_from_block(
238
238
  identity_id: str,
239
239
  block_id: BlockId,