letta-nightly 0.11.7.dev20251007104119__py3-none-any.whl → 0.11.7.dev20251008104128__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.
Files changed (145) hide show
  1. letta/adapters/letta_llm_adapter.py +1 -0
  2. letta/adapters/letta_llm_request_adapter.py +0 -1
  3. letta/adapters/letta_llm_stream_adapter.py +7 -2
  4. letta/adapters/simple_llm_request_adapter.py +88 -0
  5. letta/adapters/simple_llm_stream_adapter.py +192 -0
  6. letta/agents/agent_loop.py +6 -0
  7. letta/agents/ephemeral_summary_agent.py +2 -1
  8. letta/agents/helpers.py +142 -6
  9. letta/agents/letta_agent.py +13 -33
  10. letta/agents/letta_agent_batch.py +2 -4
  11. letta/agents/letta_agent_v2.py +87 -77
  12. letta/agents/letta_agent_v3.py +899 -0
  13. letta/agents/voice_agent.py +2 -6
  14. letta/constants.py +8 -4
  15. letta/errors.py +40 -0
  16. letta/functions/function_sets/base.py +84 -4
  17. letta/functions/function_sets/multi_agent.py +0 -3
  18. letta/functions/schema_generator.py +113 -71
  19. letta/groups/dynamic_multi_agent.py +3 -2
  20. letta/groups/helpers.py +1 -2
  21. letta/groups/round_robin_multi_agent.py +3 -2
  22. letta/groups/sleeptime_multi_agent.py +3 -2
  23. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  24. letta/groups/sleeptime_multi_agent_v3.py +17 -17
  25. letta/groups/supervisor_multi_agent.py +84 -80
  26. letta/helpers/converters.py +3 -0
  27. letta/helpers/message_helper.py +4 -0
  28. letta/helpers/tool_rule_solver.py +92 -5
  29. letta/interfaces/anthropic_streaming_interface.py +409 -0
  30. letta/interfaces/gemini_streaming_interface.py +296 -0
  31. letta/interfaces/openai_streaming_interface.py +752 -1
  32. letta/llm_api/anthropic_client.py +126 -16
  33. letta/llm_api/bedrock_client.py +4 -2
  34. letta/llm_api/deepseek_client.py +4 -1
  35. letta/llm_api/google_vertex_client.py +123 -42
  36. letta/llm_api/groq_client.py +4 -1
  37. letta/llm_api/llm_api_tools.py +11 -4
  38. letta/llm_api/llm_client_base.py +6 -2
  39. letta/llm_api/openai.py +32 -2
  40. letta/llm_api/openai_client.py +423 -18
  41. letta/llm_api/xai_client.py +4 -1
  42. letta/main.py +9 -5
  43. letta/memory.py +1 -0
  44. letta/orm/__init__.py +1 -1
  45. letta/orm/agent.py +10 -0
  46. letta/orm/block.py +7 -16
  47. letta/orm/blocks_agents.py +8 -2
  48. letta/orm/files_agents.py +2 -0
  49. letta/orm/job.py +7 -5
  50. letta/orm/mcp_oauth.py +1 -0
  51. letta/orm/message.py +21 -6
  52. letta/orm/organization.py +2 -0
  53. letta/orm/provider.py +6 -2
  54. letta/orm/run.py +71 -0
  55. letta/orm/sandbox_config.py +7 -1
  56. letta/orm/sqlalchemy_base.py +0 -306
  57. letta/orm/step.py +6 -5
  58. letta/orm/step_metrics.py +5 -5
  59. letta/otel/tracing.py +28 -3
  60. letta/plugins/defaults.py +4 -4
  61. letta/prompts/system_prompts/__init__.py +2 -0
  62. letta/prompts/system_prompts/letta_v1.py +25 -0
  63. letta/schemas/agent.py +3 -2
  64. letta/schemas/agent_file.py +9 -3
  65. letta/schemas/block.py +23 -10
  66. letta/schemas/enums.py +21 -2
  67. letta/schemas/job.py +17 -4
  68. letta/schemas/letta_message_content.py +71 -2
  69. letta/schemas/letta_stop_reason.py +5 -5
  70. letta/schemas/llm_config.py +53 -3
  71. letta/schemas/memory.py +1 -1
  72. letta/schemas/message.py +504 -117
  73. letta/schemas/openai/responses_request.py +64 -0
  74. letta/schemas/providers/__init__.py +2 -0
  75. letta/schemas/providers/anthropic.py +16 -0
  76. letta/schemas/providers/ollama.py +115 -33
  77. letta/schemas/providers/openrouter.py +52 -0
  78. letta/schemas/providers/vllm.py +2 -1
  79. letta/schemas/run.py +48 -42
  80. letta/schemas/step.py +2 -2
  81. letta/schemas/step_metrics.py +1 -1
  82. letta/schemas/tool.py +15 -107
  83. letta/schemas/tool_rule.py +88 -5
  84. letta/serialize_schemas/marshmallow_agent.py +1 -0
  85. letta/server/db.py +86 -408
  86. letta/server/rest_api/app.py +61 -10
  87. letta/server/rest_api/dependencies.py +14 -0
  88. letta/server/rest_api/redis_stream_manager.py +19 -8
  89. letta/server/rest_api/routers/v1/agents.py +364 -292
  90. letta/server/rest_api/routers/v1/blocks.py +14 -20
  91. letta/server/rest_api/routers/v1/identities.py +45 -110
  92. letta/server/rest_api/routers/v1/internal_templates.py +21 -0
  93. letta/server/rest_api/routers/v1/jobs.py +23 -6
  94. letta/server/rest_api/routers/v1/messages.py +1 -1
  95. letta/server/rest_api/routers/v1/runs.py +126 -85
  96. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
  97. letta/server/rest_api/routers/v1/tools.py +281 -594
  98. letta/server/rest_api/routers/v1/voice.py +1 -1
  99. letta/server/rest_api/streaming_response.py +29 -29
  100. letta/server/rest_api/utils.py +122 -64
  101. letta/server/server.py +160 -887
  102. letta/services/agent_manager.py +236 -919
  103. letta/services/agent_serialization_manager.py +16 -0
  104. letta/services/archive_manager.py +0 -100
  105. letta/services/block_manager.py +211 -168
  106. letta/services/file_manager.py +1 -1
  107. letta/services/files_agents_manager.py +24 -33
  108. letta/services/group_manager.py +0 -142
  109. letta/services/helpers/agent_manager_helper.py +7 -2
  110. letta/services/helpers/run_manager_helper.py +85 -0
  111. letta/services/job_manager.py +96 -411
  112. letta/services/lettuce/__init__.py +6 -0
  113. letta/services/lettuce/lettuce_client_base.py +86 -0
  114. letta/services/mcp_manager.py +38 -6
  115. letta/services/message_manager.py +165 -362
  116. letta/services/organization_manager.py +0 -36
  117. letta/services/passage_manager.py +0 -345
  118. letta/services/provider_manager.py +0 -80
  119. letta/services/run_manager.py +301 -0
  120. letta/services/sandbox_config_manager.py +0 -234
  121. letta/services/step_manager.py +62 -39
  122. letta/services/summarizer/summarizer.py +9 -7
  123. letta/services/telemetry_manager.py +0 -16
  124. letta/services/tool_executor/builtin_tool_executor.py +35 -0
  125. letta/services/tool_executor/core_tool_executor.py +397 -2
  126. letta/services/tool_executor/files_tool_executor.py +3 -3
  127. letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
  128. letta/services/tool_executor/tool_execution_manager.py +6 -8
  129. letta/services/tool_executor/tool_executor_base.py +3 -3
  130. letta/services/tool_manager.py +85 -339
  131. letta/services/tool_sandbox/base.py +24 -13
  132. letta/services/tool_sandbox/e2b_sandbox.py +16 -1
  133. letta/services/tool_schema_generator.py +123 -0
  134. letta/services/user_manager.py +0 -99
  135. letta/settings.py +20 -4
  136. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/METADATA +3 -5
  137. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/RECORD +140 -132
  138. letta/agents/temporal/activities/__init__.py +0 -4
  139. letta/agents/temporal/activities/example_activity.py +0 -7
  140. letta/agents/temporal/activities/prepare_messages.py +0 -10
  141. letta/agents/temporal/temporal_agent_workflow.py +0 -56
  142. letta/agents/temporal/types.py +0 -25
  143. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/WHEEL +0 -0
  144. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/entry_points.txt +0 -0
  145. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/licenses/LICENSE +0 -0
letta/server/server.py CHANGED
@@ -10,19 +10,16 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
10
10
 
11
11
  import httpx
12
12
  from anthropic import AsyncAnthropic
13
- from composio.client import Composio
14
- from composio.client.collections import ActionModel, AppModel
15
13
  from fastapi import HTTPException
16
14
  from fastapi.responses import StreamingResponse
17
15
 
18
16
  import letta.constants as constants
19
17
  import letta.server.utils as server_utils
20
18
  import letta.system as system
21
- from letta.agent import Agent, save_agent
22
19
  from letta.config import LettaConfig
23
20
  from letta.constants import LETTA_TOOL_EXECUTION_DIR
24
21
  from letta.data_sources.connectors import DataConnector, load_data
25
- from letta.errors import HandleNotFoundError
22
+ from letta.errors import HandleNotFoundError, LettaInvalidArgumentError, LettaMCPConnectionError, LettaMCPTimeoutError
26
23
  from letta.functions.mcp_client.types import MCPServerType, MCPTool, MCPToolHealth, SSEServerConfig, StdioServerConfig
27
24
  from letta.functions.schema_validator import validate_complete_json_schema
28
25
  from letta.groups.helpers import load_multi_agent
@@ -68,6 +65,7 @@ from letta.schemas.providers import (
68
65
  LMStudioOpenAIProvider,
69
66
  OllamaProvider,
70
67
  OpenAIProvider,
68
+ OpenRouterProvider,
71
69
  Provider,
72
70
  TogetherProvider,
73
71
  VLLMProvider,
@@ -100,6 +98,7 @@ from letta.services.message_manager import MessageManager
100
98
  from letta.services.organization_manager import OrganizationManager
101
99
  from letta.services.passage_manager import PassageManager
102
100
  from letta.services.provider_manager import ProviderManager
101
+ from letta.services.run_manager import RunManager
103
102
  from letta.services.sandbox_config_manager import SandboxConfigManager
104
103
  from letta.services.source_manager import SourceManager
105
104
  from letta.services.step_manager import StepManager
@@ -115,65 +114,7 @@ config = LettaConfig.load()
115
114
  logger = get_logger(__name__)
116
115
 
117
116
 
118
- class Server(object):
119
- """Abstract server class that supports multi-agent multi-user"""
120
-
121
- @abstractmethod
122
- def list_agents(self, user_id: str) -> dict:
123
- """List all available agents to a user"""
124
- raise NotImplementedError
125
-
126
- @abstractmethod
127
- def get_agent_memory(self, user_id: str, agent_id: str) -> dict:
128
- """Return the memory of an agent (core memory + non-core statistics)"""
129
- raise NotImplementedError
130
-
131
- @abstractmethod
132
- def get_server_config(self, user_id: str) -> dict:
133
- """Return the base config"""
134
- raise NotImplementedError
135
-
136
- @abstractmethod
137
- def update_agent_core_memory(self, user_id: str, agent_id: str, label: str, actor: User) -> Memory:
138
- """Update the agents core memory block, return the new state"""
139
- raise NotImplementedError
140
-
141
- @abstractmethod
142
- def create_agent(
143
- self,
144
- request: CreateAgent,
145
- actor: User,
146
- # interface
147
- interface: Union[AgentInterface, None] = None,
148
- ) -> AgentState:
149
- """Create a new agent using a config"""
150
- raise NotImplementedError
151
-
152
- @abstractmethod
153
- def user_message(self, user_id: str, agent_id: str, message: str) -> None:
154
- """Process a message from the user, internally calls step"""
155
- raise NotImplementedError
156
-
157
- @abstractmethod
158
- def system_message(self, user_id: str, agent_id: str, message: str) -> None:
159
- """Process a message from the system, internally calls step"""
160
- raise NotImplementedError
161
-
162
- @abstractmethod
163
- def send_messages(self, user_id: str, agent_id: str, input_messages: List[MessageCreate]) -> None:
164
- """Send a list of messages to the agent"""
165
- raise NotImplementedError
166
-
167
- @abstractmethod
168
- def run_command(self, user_id: str, agent_id: str, command: str) -> Union[str, None]:
169
- """Run a command on the agent, e.g. /memory
170
-
171
- May return a string with a message generated by the command
172
- """
173
- raise NotImplementedError
174
-
175
-
176
- class SyncServer(Server):
117
+ class SyncServer(object):
177
118
  """Simple single-threaded / blocking server process"""
178
119
 
179
120
  def __init__(
@@ -218,6 +159,7 @@ class SyncServer(Server):
218
159
  self.sandbox_config_manager = SandboxConfigManager()
219
160
  self.message_manager = MessageManager()
220
161
  self.job_manager = JobManager()
162
+ self.run_manager = RunManager()
221
163
  self.agent_manager = AgentManager()
222
164
  self.archive_manager = ArchiveManager()
223
165
  self.provider_manager = ProviderManager()
@@ -246,58 +188,17 @@ class SyncServer(Server):
246
188
  limits = httpx.Limits(max_connections=100, max_keepalive_connections=80, keepalive_expiry=300)
247
189
  self.httpx_client = httpx.AsyncClient(timeout=timeout, follow_redirects=True, limits=limits)
248
190
 
249
- # Make default user and org
250
- if init_with_default_org_and_user:
251
- self.default_org = self.organization_manager.create_default_organization()
252
- self.default_user = self.user_manager.create_default_user()
253
- self.tool_manager.upsert_base_tools(actor=self.default_user)
254
-
255
- # Add composio keys to the tool sandbox env vars of the org
256
- if tool_settings.composio_api_key:
257
- manager = SandboxConfigManager()
258
- sandbox_config = manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.LOCAL, actor=self.default_user)
259
-
260
- manager.create_sandbox_env_var(
261
- SandboxEnvironmentVariableCreate(key="COMPOSIO_API_KEY", value=tool_settings.composio_api_key),
262
- sandbox_config_id=sandbox_config.id,
263
- actor=self.default_user,
264
- )
265
-
266
- # For OSS users, create a local sandbox config
267
- oss_default_user = self.user_manager.get_default_user()
268
- use_venv = False if not tool_settings.tool_exec_venv_name else True
269
- venv_name = tool_settings.tool_exec_venv_name or "venv"
270
- tool_dir = tool_settings.tool_exec_dir or LETTA_TOOL_EXECUTION_DIR
271
-
272
- venv_dir = Path(tool_dir) / venv_name
273
- tool_path = Path(tool_dir)
274
-
275
- if tool_path.exists() and not tool_path.is_dir():
276
- logger.error(f"LETTA_TOOL_SANDBOX_DIR exists but is not a directory: {tool_dir}")
277
- else:
278
- if not tool_path.exists():
279
- logger.warning(f"LETTA_TOOL_SANDBOX_DIR does not exist, creating now: {tool_dir}")
280
- tool_path.mkdir(parents=True, exist_ok=True)
281
-
282
- if tool_settings.tool_exec_venv_name and not venv_dir.is_dir():
283
- logger.warning(
284
- f"Provided LETTA_TOOL_SANDBOX_VENV_NAME is not a valid venv ({venv_dir}), one will be created for you during tool execution."
285
- )
191
+ # For MCP
192
+ # TODO: remove this
193
+ """Initialize the MCP clients (there may be multiple)"""
194
+ self.mcp_clients: Dict[str, AsyncBaseMCPClient] = {}
286
195
 
287
- sandbox_config_create = SandboxConfigCreate(
288
- config=LocalSandboxConfig(sandbox_dir=tool_settings.tool_exec_dir, use_venv=use_venv, venv_name=venv_name)
289
- )
290
- sandbox_config = self.sandbox_config_manager.create_or_update_sandbox_config(
291
- sandbox_config_create=sandbox_config_create, actor=oss_default_user
292
- )
293
- logger.info(f"Successfully created default local sandbox config:\n{sandbox_config.get_local_config().model_dump()}")
196
+ # TODO: Remove these in memory caches
197
+ self._llm_config_cache = {}
198
+ self._embedding_config_cache = {}
294
199
 
295
- if use_venv and tool_settings.tool_exec_autoreload_venv:
296
- prepare_local_sandbox(
297
- sandbox_config.get_local_config(),
298
- env=os.environ.copy(),
299
- force_recreate=True,
300
- )
200
+ # TODO: Replace this with the Anthropic client we have in house
201
+ self.anthropic_async_client = AsyncAnthropic()
301
202
 
302
203
  # collect providers (always has Letta as a default)
303
204
  self._enabled_providers: List[Provider] = [LettaProvider(name="letta")]
@@ -375,6 +276,7 @@ class SyncServer(Server):
375
276
  name="vllm",
376
277
  base_url=model_settings.vllm_api_base,
377
278
  default_prompt_formatter=model_settings.default_prompt_formatter,
279
+ handle_base=model_settings.vllm_handle_base,
378
280
  )
379
281
  )
380
282
 
@@ -398,18 +300,58 @@ class SyncServer(Server):
398
300
  self._enabled_providers.append(DeepSeekProvider(name="deepseek", api_key=model_settings.deepseek_api_key))
399
301
  if model_settings.xai_api_key:
400
302
  self._enabled_providers.append(XAIProvider(name="xai", api_key=model_settings.xai_api_key))
303
+ if model_settings.openrouter_api_key:
304
+ self._enabled_providers.append(
305
+ OpenRouterProvider(
306
+ name="openrouter",
307
+ api_key=model_settings.openrouter_api_key,
308
+ handle_base=model_settings.openrouter_handle_base,
309
+ )
310
+ )
401
311
 
402
- # For MCP
403
- # TODO: remove this
404
- """Initialize the MCP clients (there may be multiple)"""
405
- self.mcp_clients: Dict[str, AsyncBaseMCPClient] = {}
312
+ async def init_async(self, init_with_default_org_and_user: bool = True):
313
+ # Make default user and org
314
+ if init_with_default_org_and_user:
315
+ self.default_org = await self.organization_manager.create_default_organization_async()
316
+ self.default_user = await self.user_manager.create_default_actor_async()
317
+ print(f"Default user: {self.default_user} and org: {self.default_org}")
318
+ await self.tool_manager.upsert_base_tools_async(actor=self.default_user)
406
319
 
407
- # TODO: Remove these in memory caches
408
- self._llm_config_cache = {}
409
- self._embedding_config_cache = {}
320
+ # For OSS users, create a local sandbox config
321
+ oss_default_user = await self.user_manager.get_default_actor_async()
322
+ use_venv = False if not tool_settings.tool_exec_venv_name else True
323
+ venv_name = tool_settings.tool_exec_venv_name or "venv"
324
+ tool_dir = tool_settings.tool_exec_dir or LETTA_TOOL_EXECUTION_DIR
410
325
 
411
- # TODO: Replace this with the Anthropic client we have in house
412
- self.anthropic_async_client = AsyncAnthropic()
326
+ venv_dir = Path(tool_dir) / venv_name
327
+ tool_path = Path(tool_dir)
328
+
329
+ if tool_path.exists() and not tool_path.is_dir():
330
+ logger.error(f"LETTA_TOOL_SANDBOX_DIR exists but is not a directory: {tool_dir}")
331
+ else:
332
+ if not tool_path.exists():
333
+ logger.warning(f"LETTA_TOOL_SANDBOX_DIR does not exist, creating now: {tool_dir}")
334
+ tool_path.mkdir(parents=True, exist_ok=True)
335
+
336
+ if tool_settings.tool_exec_venv_name and not venv_dir.is_dir():
337
+ logger.warning(
338
+ f"Provided LETTA_TOOL_SANDBOX_VENV_NAME is not a valid venv ({venv_dir}), one will be created for you during tool execution."
339
+ )
340
+
341
+ sandbox_config_create = SandboxConfigCreate(
342
+ config=LocalSandboxConfig(sandbox_dir=tool_settings.tool_exec_dir, use_venv=use_venv, venv_name=venv_name)
343
+ )
344
+ sandbox_config = await self.sandbox_config_manager.create_or_update_sandbox_config_async(
345
+ sandbox_config_create=sandbox_config_create, actor=oss_default_user
346
+ )
347
+ logger.debug(f"Successfully created default local sandbox config:\n{sandbox_config.get_local_config().model_dump()}")
348
+
349
+ if use_venv and tool_settings.tool_exec_autoreload_venv:
350
+ prepare_local_sandbox(
351
+ sandbox_config.get_local_config(),
352
+ env=os.environ.copy(),
353
+ force_recreate=True,
354
+ )
413
355
 
414
356
  async def init_mcp_clients(self):
415
357
  # TODO: remove this
@@ -421,7 +363,7 @@ class SyncServer(Server):
421
363
  elif server_config.type == MCPServerType.STDIO:
422
364
  self.mcp_clients[server_name] = AsyncStdioMCPClient(server_config)
423
365
  else:
424
- raise ValueError(f"Invalid MCP server config: {server_config}")
366
+ raise LettaInvalidArgumentError(f"Invalid MCP server config: {server_config}", argument_name="server_config")
425
367
 
426
368
  try:
427
369
  await self.mcp_clients[server_name].connect_to_server()
@@ -436,329 +378,6 @@ class SyncServer(Server):
436
378
  logger.info(f"MCP tools connected: {', '.join([t.name for t in mcp_tools])}")
437
379
  logger.debug(f"MCP tools: {', '.join([str(t) for t in mcp_tools])}")
438
380
 
439
- def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
440
- """Updated method to load agents from persisted storage"""
441
- agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
442
- # TODO: Think about how to integrate voice sleeptime into sleeptime
443
- # TODO: Voice sleeptime agents turn into normal agents when being messaged
444
- if agent_state.multi_agent_group and agent_state.multi_agent_group.manager_type != ManagerType.voice_sleeptime:
445
- return load_multi_agent(
446
- group=agent_state.multi_agent_group, agent_state=agent_state, actor=actor, interface=interface, mcp_clients=self.mcp_clients
447
- )
448
-
449
- interface = interface or self.default_interface_factory()
450
- return Agent(agent_state=agent_state, interface=interface, user=actor, mcp_clients=self.mcp_clients)
451
-
452
- def _step(
453
- self,
454
- actor: User,
455
- agent_id: str,
456
- input_messages: List[MessageCreate],
457
- interface: Union[AgentInterface, None] = None, # needed to getting responses
458
- put_inner_thoughts_first: bool = True,
459
- # timestamp: Optional[datetime],
460
- ) -> LettaUsageStatistics:
461
- """Send the input message through the agent"""
462
- # TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
463
- logger.debug(f"Got input messages: {input_messages}")
464
- letta_agent = None
465
- try:
466
- letta_agent = self.load_agent(agent_id=agent_id, interface=interface, actor=actor)
467
- if letta_agent is None:
468
- raise KeyError(f"Agent (user={actor.id}, agent={agent_id}) is not loaded")
469
-
470
- # Determine whether or not to token stream based on the capability of the interface
471
- token_streaming = letta_agent.interface.streaming_mode if hasattr(letta_agent.interface, "streaming_mode") else False
472
-
473
- logger.debug("Starting agent step")
474
- if interface:
475
- metadata = interface.metadata if hasattr(interface, "metadata") else None
476
- else:
477
- metadata = None
478
-
479
- usage_stats = letta_agent.step(
480
- input_messages=input_messages,
481
- chaining=self.chaining,
482
- max_chaining_steps=self.max_chaining_steps,
483
- stream=token_streaming,
484
- skip_verify=True,
485
- metadata=metadata,
486
- put_inner_thoughts_first=put_inner_thoughts_first,
487
- )
488
-
489
- except Exception as e:
490
- logger.error(f"Error in server._step: {e}")
491
- print(traceback.print_exc())
492
- raise
493
- finally:
494
- logger.debug("Calling step_yield()")
495
- if letta_agent:
496
- letta_agent.interface.step_yield()
497
-
498
- return usage_stats
499
-
500
- def _command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
501
- """Process a CLI command"""
502
- # TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
503
- actor = self.user_manager.get_user_or_default(user_id=user_id)
504
-
505
- logger.debug(f"Got command: {command}")
506
-
507
- # Get the agent object (loaded in memory)
508
- letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
509
- usage = None
510
-
511
- if command.lower() == "exit":
512
- # exit not supported on server.py
513
- raise ValueError(command)
514
-
515
- elif command.lower() == "save" or command.lower() == "savechat":
516
- save_agent(letta_agent)
517
-
518
- elif command.lower() == "attach":
519
- # Different from CLI, we extract the data source name from the command
520
- command = command.strip().split()
521
- try:
522
- data_source = int(command[1])
523
- except:
524
- raise ValueError(command)
525
-
526
- # attach data to agent from source
527
- letta_agent.attach_source(
528
- user=self.user_manager.get_user_by_id(user_id=user_id),
529
- source_id=data_source,
530
- source_manager=self.source_manager,
531
- agent_manager=self.agent_manager,
532
- )
533
-
534
- elif command.lower() == "dump" or command.lower().startswith("dump "):
535
- # Check if there's an additional argument that's an integer
536
- command = command.strip().split()
537
- amount = int(command[1]) if len(command) > 1 and command[1].isdigit() else 0
538
- if amount == 0:
539
- letta_agent.interface.print_messages(letta_agent.messages, dump=True)
540
- else:
541
- letta_agent.interface.print_messages(letta_agent.messages[-min(amount, len(letta_agent.messages)) :], dump=True)
542
-
543
- elif command.lower() == "dumpraw":
544
- letta_agent.interface.print_messages_raw(letta_agent.messages)
545
-
546
- elif command.lower() == "memory":
547
- ret_str = "\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.passage_manager)}"
548
- return ret_str
549
-
550
- elif command.lower() == "pop" or command.lower().startswith("pop "):
551
- # Check if there's an additional argument that's an integer
552
- command = command.strip().split()
553
- pop_amount = int(command[1]) if len(command) > 1 and command[1].isdigit() else 3
554
- n_messages = len(letta_agent.messages)
555
- MIN_MESSAGES = 2
556
- if n_messages <= MIN_MESSAGES:
557
- logger.debug(f"Agent only has {n_messages} messages in stack, none left to pop")
558
- elif n_messages - pop_amount < MIN_MESSAGES:
559
- logger.debug(f"Agent only has {n_messages} messages in stack, cannot pop more than {n_messages - MIN_MESSAGES}")
560
- else:
561
- logger.debug(f"Popping last {pop_amount} messages from stack")
562
- for _ in range(min(pop_amount, len(letta_agent.messages))):
563
- letta_agent.messages.pop()
564
-
565
- elif command.lower() == "retry":
566
- # TODO this needs to also modify the persistence manager
567
- logger.debug("Retrying for another answer")
568
- while len(letta_agent.messages) > 0:
569
- if letta_agent.messages[-1].get("role") == "user":
570
- # we want to pop up to the last user message and send it again
571
- letta_agent.messages[-1].get("content")
572
- letta_agent.messages.pop()
573
- break
574
- letta_agent.messages.pop()
575
-
576
- elif command.lower() == "rethink" or command.lower().startswith("rethink "):
577
- # TODO this needs to also modify the persistence manager
578
- if len(command) < len("rethink "):
579
- logger.warning("Missing text after the command")
580
- else:
581
- for x in range(len(letta_agent.messages) - 1, 0, -1):
582
- if letta_agent.messages[x].get("role") == "assistant":
583
- text = command[len("rethink ") :].strip()
584
- letta_agent.messages[x].update({"content": text})
585
- break
586
-
587
- elif command.lower() == "rewrite" or command.lower().startswith("rewrite "):
588
- # TODO this needs to also modify the persistence manager
589
- if len(command) < len("rewrite "):
590
- logger.warning("Missing text after the command")
591
- else:
592
- for x in range(len(letta_agent.messages) - 1, 0, -1):
593
- if letta_agent.messages[x].get("role") == "assistant":
594
- text = command[len("rewrite ") :].strip()
595
- args = json_loads(letta_agent.messages[x].get("function_call").get("arguments"))
596
- args["message"] = text
597
- letta_agent.messages[x].get("function_call").update({"arguments": json_dumps(args)})
598
- break
599
-
600
- # No skip options
601
- elif command.lower() == "wipe":
602
- # exit not supported on server.py
603
- raise ValueError(command)
604
-
605
- elif command.lower() == "heartbeat":
606
- input_message = system.get_heartbeat()
607
- usage = self._step(actor=actor, agent_id=agent_id, input_message=input_message)
608
-
609
- elif command.lower() == "memorywarning":
610
- input_message = system.get_token_limit_warning()
611
- usage = self._step(actor=actor, agent_id=agent_id, input_message=input_message)
612
-
613
- if not usage:
614
- usage = LettaUsageStatistics()
615
-
616
- return usage
617
-
618
- def user_message(
619
- self,
620
- user_id: str,
621
- agent_id: str,
622
- message: Union[str, Message],
623
- timestamp: Optional[datetime] = None,
624
- ) -> LettaUsageStatistics:
625
- """Process an incoming user message and feed it through the Letta agent"""
626
- try:
627
- actor = self.user_manager.get_user_by_id(user_id=user_id)
628
- except NoResultFound:
629
- raise ValueError(f"User user_id={user_id} does not exist")
630
-
631
- try:
632
- agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
633
- except NoResultFound:
634
- raise ValueError(f"Agent agent_id={agent_id} does not exist")
635
-
636
- # Basic input sanitization
637
- if isinstance(message, str):
638
- if len(message) == 0:
639
- raise ValueError(f"Invalid input: '{message}'")
640
-
641
- # If the input begins with a command prefix, reject
642
- elif message.startswith("/"):
643
- raise ValueError(f"Invalid input: '{message}'")
644
-
645
- packaged_user_message = system.package_user_message(
646
- user_message=message,
647
- timezone=agent.timezone,
648
- )
649
-
650
- # NOTE: eventually deprecate and only allow passing Message types
651
- message = MessageCreate(
652
- agent_id=agent_id,
653
- role="user",
654
- content=[TextContent(text=packaged_user_message)],
655
- )
656
-
657
- # Run the agent state forward
658
- usage = self._step(actor=actor, agent_id=agent_id, input_messages=[message])
659
- return usage
660
-
661
- def system_message(
662
- self,
663
- user_id: str,
664
- agent_id: str,
665
- message: Union[str, Message],
666
- timestamp: Optional[datetime] = None,
667
- ) -> LettaUsageStatistics:
668
- """Process an incoming system message and feed it through the Letta agent"""
669
- try:
670
- actor = self.user_manager.get_user_by_id(user_id=user_id)
671
- except NoResultFound:
672
- raise ValueError(f"User user_id={user_id} does not exist")
673
-
674
- try:
675
- agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
676
- except NoResultFound:
677
- raise ValueError(f"Agent agent_id={agent_id} does not exist")
678
-
679
- # Basic input sanitization
680
- if isinstance(message, str):
681
- if len(message) == 0:
682
- raise ValueError(f"Invalid input: '{message}'")
683
-
684
- # If the input begins with a command prefix, reject
685
- elif message.startswith("/"):
686
- raise ValueError(f"Invalid input: '{message}'")
687
-
688
- packaged_system_message = system.package_system_message(system_message=message)
689
-
690
- # NOTE: eventually deprecate and only allow passing Message types
691
- # Convert to a Message object
692
-
693
- if timestamp:
694
- message = Message(
695
- agent_id=agent_id,
696
- role="system",
697
- content=[TextContent(text=packaged_system_message)],
698
- created_at=timestamp,
699
- )
700
- else:
701
- message = Message(
702
- agent_id=agent_id,
703
- role="system",
704
- content=[TextContent(text=packaged_system_message)],
705
- )
706
-
707
- if isinstance(message, Message):
708
- # Can't have a null text field
709
- message_text = message.content[0].text
710
- if message_text is None or len(message_text) == 0:
711
- raise ValueError(f"Invalid input: '{message_text}'")
712
- # If the input begins with a command prefix, reject
713
- elif message_text.startswith("/"):
714
- raise ValueError(f"Invalid input: '{message_text}'")
715
-
716
- else:
717
- raise TypeError(f"Invalid input: '{message}' - type {type(message)}")
718
-
719
- if timestamp:
720
- # Override the timestamp with what the caller provided
721
- message.created_at = timestamp
722
-
723
- # Run the agent state forward
724
- return self._step(actor=actor, agent_id=agent_id, input_messages=message)
725
-
726
- # TODO: Deprecate this
727
- def send_messages(
728
- self,
729
- actor: User,
730
- agent_id: str,
731
- input_messages: List[MessageCreate],
732
- wrap_user_message: bool = True,
733
- wrap_system_message: bool = True,
734
- interface: Union[AgentInterface, ChatCompletionsStreamingInterface, None] = None, # needed for responses
735
- metadata: Optional[dict] = None, # Pass through metadata to interface
736
- put_inner_thoughts_first: bool = True,
737
- ) -> LettaUsageStatistics:
738
- """Send a list of messages to the agent."""
739
-
740
- # Store metadata in interface if provided
741
- if metadata and hasattr(interface, "metadata"):
742
- interface.metadata = metadata
743
-
744
- # Run the agent state forward
745
- return self._step(
746
- actor=actor,
747
- agent_id=agent_id,
748
- input_messages=input_messages,
749
- interface=interface,
750
- put_inner_thoughts_first=put_inner_thoughts_first,
751
- )
752
-
753
- # @LockingServer.agent_lock_decorator
754
- def run_command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
755
- """Run a command on the agent"""
756
- # If the input begins with a command prefix, attempt to process it as a command
757
- if command.startswith("/"):
758
- if len(command) > 1:
759
- command = command[1:] # strip the prefix
760
- return self._command(user_id=user_id, agent_id=agent_id, command=command)
761
-
762
381
  @trace_method
763
382
  def get_cached_llm_config(self, actor: User, **kwargs):
764
383
  key = make_key(**kwargs)
@@ -788,54 +407,6 @@ class SyncServer(Server):
788
407
  self._embedding_config_cache[key] = await self.get_embedding_config_from_handle_async(actor=actor, **kwargs)
789
408
  return self._embedding_config_cache[key]
790
409
 
791
- @trace_method
792
- def create_agent(
793
- self,
794
- request: CreateAgent,
795
- actor: User,
796
- interface: AgentInterface | None = None,
797
- ) -> AgentState:
798
- warnings.warn("This method is deprecated, use create_agent_async where possible.", DeprecationWarning, stacklevel=2)
799
- if request.llm_config is None:
800
- if request.model is None:
801
- raise ValueError("Must specify either model or llm_config in request")
802
- config_params = {
803
- "handle": request.model,
804
- "context_window_limit": request.context_window_limit,
805
- "max_tokens": request.max_tokens,
806
- "max_reasoning_tokens": request.max_reasoning_tokens,
807
- "enable_reasoner": request.enable_reasoner,
808
- }
809
- log_event(name="start get_cached_llm_config", attributes=config_params)
810
- request.llm_config = self.get_cached_llm_config(actor=actor, **config_params)
811
- log_event(name="end get_cached_llm_config", attributes=config_params)
812
-
813
- if request.embedding_config is None:
814
- if request.embedding is None:
815
- raise ValueError("Must specify either embedding or embedding_config in request")
816
- embedding_config_params = {
817
- "handle": request.embedding,
818
- "embedding_chunk_size": request.embedding_chunk_size or constants.DEFAULT_EMBEDDING_CHUNK_SIZE,
819
- }
820
- log_event(name="start get_cached_embedding_config", attributes=embedding_config_params)
821
- request.embedding_config = self.get_cached_embedding_config(actor=actor, **embedding_config_params)
822
- log_event(name="end get_cached_embedding_config", attributes=embedding_config_params)
823
-
824
- log_event(name="start create_agent db")
825
- main_agent = self.agent_manager.create_agent(
826
- agent_create=request,
827
- actor=actor,
828
- )
829
- log_event(name="end create_agent db")
830
-
831
- if request.enable_sleeptime:
832
- if request.agent_type == AgentType.voice_convo_agent:
833
- main_agent = self.create_voice_sleeptime_agent(main_agent=main_agent, actor=actor)
834
- else:
835
- main_agent = self.create_sleeptime_agent(main_agent=main_agent, actor=actor)
836
-
837
- return main_agent
838
-
839
410
  @trace_method
840
411
  async def create_agent_async(
841
412
  self,
@@ -845,7 +416,7 @@ class SyncServer(Server):
845
416
  if request.llm_config is None:
846
417
  if request.model is None:
847
418
  if settings.default_llm_handle is None:
848
- raise ValueError("Must specify either model or llm_config in request")
419
+ raise LettaInvalidArgumentError("Must specify either model or llm_config in request", argument_name="model")
849
420
  else:
850
421
  request.model = settings.default_llm_handle
851
422
  config_params = {
@@ -865,7 +436,9 @@ class SyncServer(Server):
865
436
  if request.embedding_config is None:
866
437
  if request.embedding is None:
867
438
  if settings.default_embedding_handle is None:
868
- raise ValueError("Must specify either embedding or embedding_config in request")
439
+ raise LettaInvalidArgumentError(
440
+ "Must specify either embedding or embedding_config in request", argument_name="embedding"
441
+ )
869
442
  else:
870
443
  request.embedding = settings.default_embedding_handle
871
444
  embedding_config_params = {
@@ -903,32 +476,6 @@ class SyncServer(Server):
903
476
 
904
477
  return main_agent
905
478
 
906
- def update_agent(
907
- self,
908
- agent_id: str,
909
- request: UpdateAgent,
910
- actor: User,
911
- ) -> AgentState:
912
- if request.model is not None:
913
- request.llm_config = self.get_llm_config_from_handle(handle=request.model, actor=actor)
914
-
915
- if request.embedding is not None:
916
- request.embedding_config = self.get_embedding_config_from_handle(handle=request.embedding, actor=actor)
917
-
918
- if request.enable_sleeptime:
919
- agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
920
- if agent.multi_agent_group is None:
921
- if agent.agent_type == AgentType.voice_convo_agent:
922
- self.create_voice_sleeptime_agent(main_agent=agent, actor=actor)
923
- else:
924
- self.create_sleeptime_agent(main_agent=agent, actor=actor)
925
-
926
- return self.agent_manager.update_agent(
927
- agent_id=agent_id,
928
- agent_update=request,
929
- actor=actor,
930
- )
931
-
932
479
  async def update_agent_async(
933
480
  self,
934
481
  agent_id: str,
@@ -955,38 +502,6 @@ class SyncServer(Server):
955
502
  actor=actor,
956
503
  )
957
504
 
958
- def create_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
959
- request = CreateAgent(
960
- name=main_agent.name + "-sleeptime",
961
- agent_type=AgentType.sleeptime_agent,
962
- block_ids=[block.id for block in main_agent.memory.blocks],
963
- memory_blocks=[
964
- CreateBlock(
965
- label="memory_persona",
966
- value=get_persona_text("sleeptime_memory_persona"),
967
- ),
968
- ],
969
- llm_config=main_agent.llm_config,
970
- embedding_config=main_agent.embedding_config,
971
- project_id=main_agent.project_id,
972
- )
973
- sleeptime_agent = self.agent_manager.create_agent(
974
- agent_create=request,
975
- actor=actor,
976
- )
977
- self.group_manager.create_group(
978
- group=GroupCreate(
979
- description="",
980
- agent_ids=[sleeptime_agent.id],
981
- manager_config=SleeptimeManager(
982
- manager_agent_id=main_agent.id,
983
- sleeptime_agent_frequency=5,
984
- ),
985
- ),
986
- actor=actor,
987
- )
988
- return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
989
-
990
505
  async def create_sleeptime_agent_async(self, main_agent: AgentState, actor: User) -> AgentState:
991
506
  request = CreateAgent(
992
507
  name=main_agent.name + "-sleeptime",
@@ -1019,40 +534,6 @@ class SyncServer(Server):
1019
534
  )
1020
535
  return await self.agent_manager.get_agent_by_id_async(agent_id=main_agent.id, actor=actor)
1021
536
 
1022
- def create_voice_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
1023
- # TODO: Inject system
1024
- request = CreateAgent(
1025
- name=main_agent.name + "-sleeptime",
1026
- agent_type=AgentType.voice_sleeptime_agent,
1027
- block_ids=[block.id for block in main_agent.memory.blocks],
1028
- memory_blocks=[
1029
- CreateBlock(
1030
- label="memory_persona",
1031
- value=get_persona_text("voice_memory_persona"),
1032
- ),
1033
- ],
1034
- llm_config=LLMConfig.default_config("gpt-4.1"),
1035
- embedding_config=main_agent.embedding_config,
1036
- project_id=main_agent.project_id,
1037
- )
1038
- voice_sleeptime_agent = self.agent_manager.create_agent(
1039
- agent_create=request,
1040
- actor=actor,
1041
- )
1042
- self.group_manager.create_group(
1043
- group=GroupCreate(
1044
- description="Low latency voice chat with async memory management.",
1045
- agent_ids=[voice_sleeptime_agent.id],
1046
- manager_config=VoiceSleeptimeManager(
1047
- manager_agent_id=main_agent.id,
1048
- max_message_buffer_length=constants.DEFAULT_MAX_MESSAGE_BUFFER_LENGTH,
1049
- min_message_buffer_length=constants.DEFAULT_MIN_MESSAGE_BUFFER_LENGTH,
1050
- ),
1051
- ),
1052
- actor=actor,
1053
- )
1054
- return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
1055
-
1056
537
  async def create_voice_sleeptime_agent_async(self, main_agent: AgentState, actor: User) -> AgentState:
1057
538
  # TODO: Inject system
1058
539
  request = CreateAgent(
@@ -1087,24 +568,11 @@ class SyncServer(Server):
1087
568
  )
1088
569
  return await self.agent_manager.get_agent_by_id_async(agent_id=main_agent.id, actor=actor)
1089
570
 
1090
- # convert name->id
1091
-
1092
- # TODO: These can be moved to agent_manager
1093
- def get_agent_memory(self, agent_id: str, actor: User) -> Memory:
1094
- """Return the memory of an agent (core memory)"""
1095
- return self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor).memory
1096
-
1097
571
  async def get_agent_memory_async(self, agent_id: str, actor: User) -> Memory:
1098
572
  """Return the memory of an agent (core memory)"""
1099
573
  agent = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
1100
574
  return agent.memory
1101
575
 
1102
- def get_archival_memory_summary(self, agent_id: str, actor: User) -> ArchivalMemorySummary:
1103
- return ArchivalMemorySummary(size=self.agent_manager.passage_size(actor=actor, agent_id=agent_id))
1104
-
1105
- def get_recall_memory_summary(self, agent_id: str, actor: User) -> RecallMemorySummary:
1106
- return RecallMemorySummary(size=self.message_manager.size(actor=actor, agent_id=agent_id))
1107
-
1108
576
  async def get_agent_archival_async(
1109
577
  self,
1110
578
  agent_id: str,
@@ -1149,7 +617,7 @@ class SyncServer(Server):
1149
617
  # delete the passage
1150
618
  await self.passage_manager.delete_passage_by_id_async(passage_id=memory_id, actor=actor)
1151
619
 
1152
- def get_agent_recall(
620
+ async def get_agent_recall(
1153
621
  self,
1154
622
  user_id: str,
1155
623
  agent_id: str,
@@ -1165,9 +633,9 @@ class SyncServer(Server):
1165
633
  ) -> Union[List[Message], List[LettaMessage]]:
1166
634
  # TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
1167
635
 
1168
- actor = self.user_manager.get_user_or_default(user_id=user_id)
636
+ actor = await self.user_manager.get_actor_or_default_async(actor_id=user_id)
1169
637
 
1170
- records = self.message_manager.list_messages_for_agent(
638
+ records = await self.message_manager.list_messages(
1171
639
  agent_id=agent_id,
1172
640
  actor=actor,
1173
641
  after=after,
@@ -1206,7 +674,7 @@ class SyncServer(Server):
1206
674
  assistant_message_tool_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
1207
675
  include_err: Optional[bool] = None,
1208
676
  ) -> Union[List[Message], List[LettaMessage]]:
1209
- records = await self.message_manager.list_messages_for_agent_async(
677
+ records = await self.message_manager.list_messages(
1210
678
  agent_id=agent_id,
1211
679
  actor=actor,
1212
680
  after=after,
@@ -1218,6 +686,10 @@ class SyncServer(Server):
1218
686
  )
1219
687
 
1220
688
  if not return_message_object:
689
+ # Get agent state to determine if it's a react agent
690
+ agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
691
+ text_is_assistant_message = agent_state.agent_type == AgentType.letta_v1_agent
692
+
1221
693
  records = Message.to_letta_messages_from_list(
1222
694
  messages=records,
1223
695
  use_assistant_message=use_assistant_message,
@@ -1225,6 +697,7 @@ class SyncServer(Server):
1225
697
  assistant_message_tool_kwarg=assistant_message_tool_kwarg,
1226
698
  reverse=reverse,
1227
699
  include_err=include_err,
700
+ text_is_assistant_message=text_is_assistant_message,
1228
701
  )
1229
702
 
1230
703
  if reverse:
@@ -1289,7 +762,7 @@ class SyncServer(Server):
1289
762
  # TODO: move this into a thread
1290
763
  source = await self.source_manager.get_source_by_id(source_id=source_id)
1291
764
  if source is None:
1292
- raise ValueError(f"Source {source_id} does not exist")
765
+ raise NoResultFound(f"Source {source_id} does not exist")
1293
766
  connector = DirectoryConnector(input_files=[file_path])
1294
767
  num_passages, num_documents = await self.load_data(user_id=source.created_by_id, source_name=source.name, connector=connector)
1295
768
 
@@ -1423,78 +896,12 @@ class SyncServer(Server):
1423
896
  actor = await self.user_manager.get_actor_by_id_async(actor_id=user_id)
1424
897
  source = await self.source_manager.get_source_by_name(source_name=source_name, actor=actor)
1425
898
  if source is None:
1426
- raise ValueError(f"Data source {source_name} does not exist for user {user_id}")
899
+ raise NoResultFound(f"Data source {source_name} does not exist for user {user_id}")
1427
900
 
1428
901
  # load data into the document store
1429
902
  passage_count, document_count = await load_data(connector, source, self.passage_manager, self.file_manager, actor=actor)
1430
903
  return passage_count, document_count
1431
904
 
1432
- def list_all_sources(self, actor: User) -> List[Source]:
1433
- # TODO: legacy: remove
1434
- """List all sources (w/ extra metadata) belonging to a user"""
1435
-
1436
- sources = self.source_manager.list_sources(actor=actor)
1437
-
1438
- # Add extra metadata to the sources
1439
- sources_with_metadata = []
1440
- for source in sources:
1441
- # count number of passages
1442
- num_passages = self.agent_manager.passage_size(actor=actor, source_id=source.id)
1443
-
1444
- # TODO: add when files table implemented
1445
- ## count number of files
1446
- # document_conn = StorageConnector.get_storage_connector(TableType.FILES, self.config, user_id=user_id)
1447
- # num_documents = document_conn.size({"data_source": source.name})
1448
- num_documents = 0
1449
-
1450
- agents = self.source_manager.list_attached_agents(source_id=source.id, actor=actor)
1451
- # add the agent name information
1452
- attached_agents = [{"id": agent.id, "name": agent.name} for agent in agents]
1453
-
1454
- # Overwrite metadata field, should be empty anyways
1455
- source.metadata = dict(
1456
- num_documents=num_documents,
1457
- num_passages=num_passages,
1458
- attached_agents=attached_agents,
1459
- )
1460
-
1461
- sources_with_metadata.append(source)
1462
-
1463
- return sources_with_metadata
1464
-
1465
- def update_agent_message(self, message_id: str, request: MessageUpdate, actor: User) -> Message:
1466
- """Update the details of a message associated with an agent"""
1467
-
1468
- # Get the current message
1469
- return self.message_manager.update_message_by_id(message_id=message_id, message_update=request, actor=actor)
1470
-
1471
- def list_llm_models(
1472
- self,
1473
- actor: User,
1474
- provider_category: Optional[List[ProviderCategory]] = None,
1475
- provider_name: Optional[str] = None,
1476
- provider_type: Optional[ProviderType] = None,
1477
- ) -> List[LLMConfig]:
1478
- """List available models"""
1479
- llm_models = []
1480
- for provider in self.get_enabled_providers(
1481
- provider_category=provider_category,
1482
- provider_name=provider_name,
1483
- provider_type=provider_type,
1484
- actor=actor,
1485
- ):
1486
- try:
1487
- llm_models.extend(provider.list_llm_models())
1488
- except Exception as e:
1489
- import traceback
1490
-
1491
- traceback.print_exc()
1492
- warnings.warn(f"An error occurred while listing LLM models for provider {provider}: {e}")
1493
-
1494
- llm_models.extend(self.get_local_llm_configs())
1495
-
1496
- return llm_models
1497
-
1498
905
  @trace_method
1499
906
  async def list_llm_models_async(
1500
907
  self,
@@ -1548,16 +955,6 @@ class SyncServer(Server):
1548
955
 
1549
956
  return unique_models
1550
957
 
1551
- def list_embedding_models(self, actor: User) -> List[EmbeddingConfig]:
1552
- """List available embedding models"""
1553
- embedding_models = []
1554
- for provider in self.get_enabled_providers(actor):
1555
- try:
1556
- embedding_models.extend(provider.list_embedding_models())
1557
- except Exception as e:
1558
- warnings.warn(f"An error occurred while listing embedding models for provider {provider}: {e}")
1559
- return embedding_models
1560
-
1561
958
  async def list_embedding_models_async(self, actor: User) -> List[EmbeddingConfig]:
1562
959
  """Asynchronously list available embedding models with maximum concurrency"""
1563
960
  import asyncio
@@ -1587,35 +984,6 @@ class SyncServer(Server):
1587
984
 
1588
985
  return embedding_models
1589
986
 
1590
- def get_enabled_providers(
1591
- self,
1592
- actor: User,
1593
- provider_category: Optional[List[ProviderCategory]] = None,
1594
- provider_name: Optional[str] = None,
1595
- provider_type: Optional[ProviderType] = None,
1596
- ) -> List[Provider]:
1597
- providers = []
1598
- if not provider_category or ProviderCategory.base in provider_category:
1599
- providers_from_env = [p for p in self._enabled_providers]
1600
- providers.extend(providers_from_env)
1601
-
1602
- if not provider_category or ProviderCategory.byok in provider_category:
1603
- providers_from_db = self.provider_manager.list_providers(
1604
- name=provider_name,
1605
- provider_type=provider_type,
1606
- actor=actor,
1607
- )
1608
- providers_from_db = [p.cast_to_subtype() for p in providers_from_db]
1609
- providers.extend(providers_from_db)
1610
-
1611
- if provider_name is not None:
1612
- providers = [p for p in providers if p.name == provider_name]
1613
-
1614
- if provider_type is not None:
1615
- providers = [p for p in providers if p.provider_type == provider_type]
1616
-
1617
- return providers
1618
-
1619
987
  async def get_enabled_providers_async(
1620
988
  self,
1621
989
  actor: User,
@@ -1645,60 +1013,6 @@ class SyncServer(Server):
1645
1013
 
1646
1014
  return providers
1647
1015
 
1648
- @trace_method
1649
- def get_llm_config_from_handle(
1650
- self,
1651
- actor: User,
1652
- handle: str,
1653
- context_window_limit: Optional[int] = None,
1654
- max_tokens: Optional[int] = None,
1655
- max_reasoning_tokens: Optional[int] = None,
1656
- enable_reasoner: Optional[bool] = None,
1657
- ) -> LLMConfig:
1658
- try:
1659
- provider_name, model_name = handle.split("/", 1)
1660
- provider = self.get_provider_from_name(provider_name, actor)
1661
-
1662
- llm_configs = [config for config in provider.list_llm_models() if config.handle == handle]
1663
- if not llm_configs:
1664
- llm_configs = [config for config in provider.list_llm_models() if config.model == model_name]
1665
- if not llm_configs:
1666
- available_handles = [config.handle for config in provider.list_llm_models()]
1667
- raise HandleNotFoundError(handle, available_handles)
1668
- except ValueError as e:
1669
- llm_configs = [config for config in self.get_local_llm_configs() if config.handle == handle]
1670
- if not llm_configs:
1671
- llm_configs = [config for config in self.get_local_llm_configs() if config.model == model_name]
1672
- if not llm_configs:
1673
- raise e
1674
-
1675
- if len(llm_configs) == 1:
1676
- llm_config = llm_configs[0]
1677
- elif len(llm_configs) > 1:
1678
- raise ValueError(f"Multiple LLM models with name {model_name} supported by {provider_name}")
1679
- else:
1680
- llm_config = llm_configs[0]
1681
-
1682
- if context_window_limit is not None:
1683
- if context_window_limit > llm_config.context_window:
1684
- raise ValueError(f"Context window limit ({context_window_limit}) is greater than maximum of ({llm_config.context_window})")
1685
- llm_config.context_window = context_window_limit
1686
- else:
1687
- llm_config.context_window = min(llm_config.context_window, model_settings.global_max_context_window_limit)
1688
-
1689
- if max_tokens is not None:
1690
- llm_config.max_tokens = max_tokens
1691
- if max_reasoning_tokens is not None:
1692
- if not max_tokens or max_reasoning_tokens > max_tokens:
1693
- raise ValueError(f"Max reasoning tokens ({max_reasoning_tokens}) must be less than max tokens ({max_tokens})")
1694
- llm_config.max_reasoning_tokens = max_reasoning_tokens
1695
- if enable_reasoner is not None:
1696
- llm_config.enable_reasoner = enable_reasoner
1697
- if enable_reasoner and llm_config.model_endpoint_type == "anthropic":
1698
- llm_config.put_inner_thoughts_in_kwargs = False
1699
-
1700
- return llm_config
1701
-
1702
1016
  @trace_method
1703
1017
  async def get_llm_config_from_handle_async(
1704
1018
  self,
@@ -1730,13 +1044,18 @@ class SyncServer(Server):
1730
1044
  if len(llm_configs) == 1:
1731
1045
  llm_config = llm_configs[0]
1732
1046
  elif len(llm_configs) > 1:
1733
- raise ValueError(f"Multiple LLM models with name {model_name} supported by {provider_name}")
1047
+ raise LettaInvalidArgumentError(
1048
+ f"Multiple LLM models with name {model_name} supported by {provider_name}", argument_name="model_name"
1049
+ )
1734
1050
  else:
1735
1051
  llm_config = llm_configs[0]
1736
1052
 
1737
1053
  if context_window_limit is not None:
1738
1054
  if context_window_limit > llm_config.context_window:
1739
- raise ValueError(f"Context window limit ({context_window_limit}) is greater than maximum of ({llm_config.context_window})")
1055
+ raise LettaInvalidArgumentError(
1056
+ f"Context window limit ({context_window_limit}) is greater than maximum of ({llm_config.context_window})",
1057
+ argument_name="context_window_limit",
1058
+ )
1740
1059
  llm_config.context_window = context_window_limit
1741
1060
  else:
1742
1061
  llm_config.context_window = min(llm_config.context_window, model_settings.global_max_context_window_limit)
@@ -1745,7 +1064,10 @@ class SyncServer(Server):
1745
1064
  llm_config.max_tokens = max_tokens
1746
1065
  if max_reasoning_tokens is not None:
1747
1066
  if not max_tokens or max_reasoning_tokens > max_tokens:
1748
- raise ValueError(f"Max reasoning tokens ({max_reasoning_tokens}) must be less than max tokens ({max_tokens})")
1067
+ raise LettaInvalidArgumentError(
1068
+ f"Max reasoning tokens ({max_reasoning_tokens}) must be less than max tokens ({max_tokens})",
1069
+ argument_name="max_reasoning_tokens",
1070
+ )
1749
1071
  llm_config.max_reasoning_tokens = max_reasoning_tokens
1750
1072
  if enable_reasoner is not None:
1751
1073
  llm_config.enable_reasoner = enable_reasoner
@@ -1754,35 +1076,6 @@ class SyncServer(Server):
1754
1076
 
1755
1077
  return llm_config
1756
1078
 
1757
- @trace_method
1758
- def get_embedding_config_from_handle(
1759
- self, actor: User, handle: str, embedding_chunk_size: int = constants.DEFAULT_EMBEDDING_CHUNK_SIZE
1760
- ) -> EmbeddingConfig:
1761
- try:
1762
- provider_name, model_name = handle.split("/", 1)
1763
- provider = self.get_provider_from_name(provider_name, actor)
1764
-
1765
- embedding_configs = [config for config in provider.list_embedding_models() if config.handle == handle]
1766
- if not embedding_configs:
1767
- raise ValueError(f"Embedding model {model_name} is not supported by {provider_name}")
1768
- except ValueError as e:
1769
- # search local configs
1770
- embedding_configs = [config for config in self.get_local_embedding_configs() if config.handle == handle]
1771
- if not embedding_configs:
1772
- raise e
1773
-
1774
- if len(embedding_configs) == 1:
1775
- embedding_config = embedding_configs[0]
1776
- elif len(embedding_configs) > 1:
1777
- raise ValueError(f"Multiple embedding models with name {model_name} supported by {provider_name}")
1778
- else:
1779
- embedding_config = embedding_configs[0]
1780
-
1781
- if embedding_chunk_size:
1782
- embedding_config.embedding_chunk_size = embedding_chunk_size
1783
-
1784
- return embedding_config
1785
-
1786
1079
  @trace_method
1787
1080
  async def get_embedding_config_from_handle_async(
1788
1081
  self, actor: User, handle: str, embedding_chunk_size: int = constants.DEFAULT_EMBEDDING_CHUNK_SIZE
@@ -1794,8 +1087,10 @@ class SyncServer(Server):
1794
1087
  all_embedding_configs = await provider.list_embedding_models_async()
1795
1088
  embedding_configs = [config for config in all_embedding_configs if config.handle == handle]
1796
1089
  if not embedding_configs:
1797
- raise ValueError(f"Embedding model {model_name} is not supported by {provider_name}")
1798
- except ValueError as e:
1090
+ raise LettaInvalidArgumentError(
1091
+ f"Embedding model {model_name} is not supported by {provider_name}", argument_name="model_name"
1092
+ )
1093
+ except LettaInvalidArgumentError as e:
1799
1094
  # search local configs
1800
1095
  embedding_configs = [config for config in self.get_local_embedding_configs() if config.handle == handle]
1801
1096
  if not embedding_configs:
@@ -1804,7 +1099,9 @@ class SyncServer(Server):
1804
1099
  if len(embedding_configs) == 1:
1805
1100
  embedding_config = embedding_configs[0]
1806
1101
  elif len(embedding_configs) > 1:
1807
- raise ValueError(f"Multiple embedding models with name {model_name} supported by {provider_name}")
1102
+ raise LettaInvalidArgumentError(
1103
+ f"Multiple embedding models with name {model_name} supported by {provider_name}", argument_name="model_name"
1104
+ )
1808
1105
  else:
1809
1106
  embedding_config = embedding_configs[0]
1810
1107
 
@@ -1813,28 +1110,16 @@ class SyncServer(Server):
1813
1110
 
1814
1111
  return embedding_config
1815
1112
 
1816
- def get_provider_from_name(self, provider_name: str, actor: User) -> Provider:
1817
- providers = [provider for provider in self.get_enabled_providers(actor) if provider.name == provider_name]
1818
- if not providers:
1819
- raise ValueError(
1820
- f"Provider {provider_name} is not supported (supported providers: {', '.join([provider.name for provider in self._enabled_providers])})"
1821
- )
1822
- elif len(providers) > 1:
1823
- raise ValueError(f"Multiple providers with name {provider_name} supported")
1824
- else:
1825
- provider = providers[0]
1826
-
1827
- return provider
1828
-
1829
1113
  async def get_provider_from_name_async(self, provider_name: str, actor: User) -> Provider:
1830
1114
  all_providers = await self.get_enabled_providers_async(actor)
1831
1115
  providers = [provider for provider in all_providers if provider.name == provider_name]
1832
1116
  if not providers:
1833
- raise ValueError(
1834
- f"Provider {provider_name} is not supported (supported providers: {', '.join([provider.name for provider in self._enabled_providers])})"
1117
+ raise LettaInvalidArgumentError(
1118
+ f"Provider {provider_name} is not supported (supported providers: {', '.join([provider.name for provider in self._enabled_providers])})",
1119
+ argument_name="provider_name",
1835
1120
  )
1836
1121
  elif len(providers) > 1:
1837
- raise ValueError(f"Multiple providers with name {provider_name} supported")
1122
+ raise LettaInvalidArgumentError(f"Multiple providers with name {provider_name} supported", argument_name="provider_name")
1838
1123
  else:
1839
1124
  provider = providers[0]
1840
1125
 
@@ -1842,40 +1127,42 @@ class SyncServer(Server):
1842
1127
 
1843
1128
  def get_local_llm_configs(self):
1844
1129
  llm_models = []
1845
- try:
1846
- llm_configs_dir = os.path.expanduser("~/.letta/llm_configs")
1847
- if os.path.exists(llm_configs_dir):
1848
- for filename in os.listdir(llm_configs_dir):
1849
- if filename.endswith(".json"):
1850
- filepath = os.path.join(llm_configs_dir, filename)
1851
- try:
1852
- with open(filepath, "r") as f:
1853
- config_data = json.load(f)
1854
- llm_config = LLMConfig(**config_data)
1855
- llm_models.append(llm_config)
1856
- except (json.JSONDecodeError, ValueError) as e:
1857
- warnings.warn(f"Error parsing LLM config file {filename}: {e}")
1858
- except Exception as e:
1859
- warnings.warn(f"Error reading LLM configs directory: {e}")
1130
+ # NOTE: deprecated
1131
+ # try:
1132
+ # llm_configs_dir = os.path.expanduser("~/.letta/llm_configs")
1133
+ # if os.path.exists(llm_configs_dir):
1134
+ # for filename in os.listdir(llm_configs_dir):
1135
+ # if filename.endswith(".json"):
1136
+ # filepath = os.path.join(llm_configs_dir, filename)
1137
+ # try:
1138
+ # with open(filepath, "r") as f:
1139
+ # config_data = json.load(f)
1140
+ # llm_config = LLMConfig(**config_data)
1141
+ # llm_models.append(llm_config)
1142
+ # except (json.JSONDecodeError, ValueError) as e:
1143
+ # warnings.warn(f"Error parsing LLM config file {filename}: {e}")
1144
+ # except Exception as e:
1145
+ # warnings.warn(f"Error reading LLM configs directory: {e}")
1860
1146
  return llm_models
1861
1147
 
1862
1148
  def get_local_embedding_configs(self):
1863
1149
  embedding_models = []
1864
- try:
1865
- embedding_configs_dir = os.path.expanduser("~/.letta/embedding_configs")
1866
- if os.path.exists(embedding_configs_dir):
1867
- for filename in os.listdir(embedding_configs_dir):
1868
- if filename.endswith(".json"):
1869
- filepath = os.path.join(embedding_configs_dir, filename)
1870
- try:
1871
- with open(filepath, "r") as f:
1872
- config_data = json.load(f)
1873
- embedding_config = EmbeddingConfig(**config_data)
1874
- embedding_models.append(embedding_config)
1875
- except (json.JSONDecodeError, ValueError) as e:
1876
- warnings.warn(f"Error parsing embedding config file {filename}: {e}")
1877
- except Exception as e:
1878
- warnings.warn(f"Error reading embedding configs directory: {e}")
1150
+ # NOTE: deprecated
1151
+ # try:
1152
+ # embedding_configs_dir = os.path.expanduser("~/.letta/embedding_configs")
1153
+ # if os.path.exists(embedding_configs_dir):
1154
+ # for filename in os.listdir(embedding_configs_dir):
1155
+ # if filename.endswith(".json"):
1156
+ # filepath = os.path.join(embedding_configs_dir, filename)
1157
+ # try:
1158
+ # with open(filepath, "r") as f:
1159
+ # config_data = json.load(f)
1160
+ # embedding_config = EmbeddingConfig(**config_data)
1161
+ # embedding_models.append(embedding_config)
1162
+ # except (json.JSONDecodeError, ValueError) as e:
1163
+ # warnings.warn(f"Error parsing embedding config file {filename}: {e}")
1164
+ # except Exception as e:
1165
+ # warnings.warn(f"Error reading embedding configs directory: {e}")
1879
1166
  return embedding_models
1880
1167
 
1881
1168
  def add_llm_model(self, request: LLMConfig) -> LLMConfig:
@@ -1898,8 +1185,12 @@ class SyncServer(Server):
1898
1185
  ) -> ToolReturnMessage:
1899
1186
  """Run a tool from source code"""
1900
1187
 
1188
+ from letta.services.tool_schema_generator import generate_schema_for_tool_creation, generate_schema_for_tool_update
1189
+
1901
1190
  if tool_source_type not in (None, ToolSourceType.python, ToolSourceType.typescript):
1902
- raise ValueError("Tool source type is not supported at this time. Found {tool_source_type}")
1191
+ raise LettaInvalidArgumentError(
1192
+ f"Tool source type is not supported at this time. Found {tool_source_type}", argument_name="tool_source_type"
1193
+ )
1903
1194
 
1904
1195
  # If tools_json_schema is explicitly passed in, override it on the created Tool object
1905
1196
  if tool_json_schema:
@@ -1920,6 +1211,11 @@ class SyncServer(Server):
1920
1211
  source_type=tool_source_type,
1921
1212
  )
1922
1213
 
1214
+ # try to get the schema
1215
+ if not tool.name:
1216
+ if not tool.json_schema:
1217
+ tool.json_schema = generate_schema_for_tool_creation(tool)
1218
+ tool.name = tool.json_schema.get("name")
1923
1219
  assert tool.name is not None, "Failed to create tool object"
1924
1220
 
1925
1221
  # TODO eventually allow using agent state in tools
@@ -1932,7 +1228,7 @@ class SyncServer(Server):
1932
1228
  message_manager=self.message_manager,
1933
1229
  agent_manager=self.agent_manager,
1934
1230
  block_manager=self.block_manager,
1935
- job_manager=self.job_manager,
1231
+ run_manager=self.run_manager,
1936
1232
  passage_manager=self.passage_manager,
1937
1233
  actor=actor,
1938
1234
  sandbox_env_vars=tool_env_vars,
@@ -1965,33 +1261,6 @@ class SyncServer(Server):
1965
1261
  stderr=[traceback.format_exc()],
1966
1262
  )
1967
1263
 
1968
- # Composio wrappers
1969
- @staticmethod
1970
- def get_composio_client(api_key: Optional[str] = None):
1971
- if api_key:
1972
- return Composio(api_key=api_key)
1973
- elif tool_settings.composio_api_key:
1974
- return Composio(api_key=tool_settings.composio_api_key)
1975
- else:
1976
- return Composio()
1977
-
1978
- @staticmethod
1979
- def get_composio_apps(api_key: Optional[str] = None) -> List["AppModel"]:
1980
- """Get a list of all Composio apps with actions"""
1981
- apps = SyncServer.get_composio_client(api_key=api_key).apps.get()
1982
- apps_with_actions = []
1983
- for app in apps:
1984
- # A bit of hacky logic until composio patches this
1985
- if app.meta["actionsCount"] > 0 and not app.name.lower().endswith("_beta"):
1986
- apps_with_actions.append(app)
1987
-
1988
- return apps_with_actions
1989
-
1990
- def get_composio_actions_from_app_name(self, composio_app_name: str, api_key: Optional[str] = None) -> List["ActionModel"]:
1991
- actions = self.get_composio_client(api_key=api_key).actions.get(apps=[composio_app_name])
1992
- # Filter out deprecated composio actions
1993
- return [action for action in actions if "deprecated" not in action.description.lower()]
1994
-
1995
1264
  # MCP wrappers
1996
1265
  # TODO support both command + SSE servers (via config)
1997
1266
  def get_mcp_servers(self) -> dict[str, Union[SSEServerConfig, StdioServerConfig]]:
@@ -2055,7 +1324,7 @@ class SyncServer(Server):
2055
1324
  async def get_tools_from_mcp_server(self, mcp_server_name: str) -> List[MCPTool]:
2056
1325
  """List the tools in an MCP server. Requires a client to be created."""
2057
1326
  if mcp_server_name not in self.mcp_clients:
2058
- raise ValueError(f"No client was created for MCP server: {mcp_server_name}")
1327
+ raise LettaInvalidArgumentError(f"No client was created for MCP server: {mcp_server_name}", argument_name="mcp_server_name")
2059
1328
 
2060
1329
  tools = await self.mcp_clients[mcp_server_name].list_tools()
2061
1330
  # Add health information to each tool
@@ -2087,11 +1356,13 @@ class SyncServer(Server):
2087
1356
  except Exception as e:
2088
1357
  # Raise an error telling the user to fix the config file
2089
1358
  logger.error(f"Failed to parse MCP config file at {mcp_config_path}: {e}")
2090
- raise ValueError(f"Failed to parse MCP config file {mcp_config_path}")
1359
+ raise LettaInvalidArgumentError(f"Failed to parse MCP config file {mcp_config_path}")
2091
1360
 
2092
1361
  # Check if the server name is already in the config
2093
1362
  if server_config.server_name in current_mcp_servers and not allow_upsert:
2094
- raise ValueError(f"Server name {server_config.server_name} is already in the config file")
1363
+ raise LettaInvalidArgumentError(
1364
+ f"Server name {server_config.server_name} is already in the config file", argument_name="server_name"
1365
+ )
2095
1366
 
2096
1367
  # Attempt to initialize the connection to the server
2097
1368
  if server_config.type == MCPServerType.SSE:
@@ -2099,7 +1370,7 @@ class SyncServer(Server):
2099
1370
  elif server_config.type == MCPServerType.STDIO:
2100
1371
  new_mcp_client = AsyncStdioMCPClient(server_config)
2101
1372
  else:
2102
- raise ValueError(f"Invalid MCP server config: {server_config}")
1373
+ raise LettaInvalidArgumentError(f"Invalid MCP server config: {server_config}", argument_name="server_config")
2103
1374
  try:
2104
1375
  await new_mcp_client.connect_to_server()
2105
1376
  except:
@@ -2124,7 +1395,7 @@ class SyncServer(Server):
2124
1395
  json.dump(new_mcp_file, f, indent=4)
2125
1396
  except Exception as e:
2126
1397
  logger.error(f"Failed to write MCP config file at {mcp_config_path}: {e}")
2127
- raise ValueError(f"Failed to write MCP config file {mcp_config_path}")
1398
+ raise LettaInvalidArgumentError(f"Failed to write MCP config file {mcp_config_path}")
2128
1399
 
2129
1400
  return list(current_mcp_servers.values())
2130
1401
 
@@ -2147,12 +1418,12 @@ class SyncServer(Server):
2147
1418
  except Exception as e:
2148
1419
  # Raise an error telling the user to fix the config file
2149
1420
  logger.error(f"Failed to parse MCP config file at {mcp_config_path}: {e}")
2150
- raise ValueError(f"Failed to parse MCP config file {mcp_config_path}")
1421
+ raise LettaInvalidArgumentError(f"Failed to parse MCP config file {mcp_config_path}")
2151
1422
 
2152
1423
  # Check if the server name is already in the config
2153
1424
  # If it's not, throw an error
2154
1425
  if server_name not in current_mcp_servers:
2155
- raise ValueError(f"Server name {server_name} not found in MCP config file")
1426
+ raise LettaInvalidArgumentError(f"Server name {server_name} not found in MCP config file", argument_name="server_name")
2156
1427
 
2157
1428
  # Remove from the server file
2158
1429
  del current_mcp_servers[server_name]
@@ -2164,7 +1435,7 @@ class SyncServer(Server):
2164
1435
  json.dump(new_mcp_file, f, indent=4)
2165
1436
  except Exception as e:
2166
1437
  logger.error(f"Failed to write MCP config file at {mcp_config_path}: {e}")
2167
- raise ValueError(f"Failed to write MCP config file {mcp_config_path}")
1438
+ raise LettaInvalidArgumentError(f"Failed to write MCP config file {mcp_config_path}")
2168
1439
 
2169
1440
  return list(current_mcp_servers.values())
2170
1441
 
@@ -2226,7 +1497,9 @@ class SyncServer(Server):
2226
1497
  )
2227
1498
  streaming_interface = letta_agent.interface
2228
1499
  if not isinstance(streaming_interface, StreamingServerInterface):
2229
- raise ValueError(f"Agent has wrong type of interface: {type(streaming_interface)}")
1500
+ raise LettaInvalidArgumentError(
1501
+ f"Agent has wrong type of interface: {type(streaming_interface)}", argument_name="interface"
1502
+ )
2230
1503
 
2231
1504
  # Enable token-streaming within the request if desired
2232
1505
  streaming_interface.streaming_mode = stream_tokens
@@ -2331,7 +1604,7 @@ class SyncServer(Server):
2331
1604
  ) -> Union[StreamingResponse, LettaResponse]:
2332
1605
  include_final_message = True
2333
1606
  if not stream_steps and stream_tokens:
2334
- raise ValueError("stream_steps must be 'true' if stream_tokens is 'true'")
1607
+ raise LettaInvalidArgumentError("stream_steps must be 'true' if stream_tokens is 'true'", argument_name="stream_steps")
2335
1608
 
2336
1609
  group = await self.group_manager.retrieve_group_async(group_id=group_id, actor=actor)
2337
1610
  agent_state_id = group.manager_agent_id or (group.agent_ids[0] if len(group.agent_ids) > 0 else None)
@@ -2357,7 +1630,7 @@ class SyncServer(Server):
2357
1630
  )
2358
1631
  streaming_interface = letta_multi_agent.interface
2359
1632
  if not isinstance(streaming_interface, StreamingServerInterface):
2360
- raise ValueError(f"Agent has wrong type of interface: {type(streaming_interface)}")
1633
+ raise LettaInvalidArgumentError(f"Agent has wrong type of interface: {type(streaming_interface)}", argument_name="interface")
2361
1634
  streaming_interface.streaming_mode = stream_tokens
2362
1635
  streaming_interface.streaming_chat_completion_mode = chat_completion_mode
2363
1636
  if metadata and hasattr(streaming_interface, "metadata"):