letta-nightly 0.6.49.dev20250408104230__py3-none-any.whl → 0.6.50.dev20250409104353__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 (33) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +8 -1
  3. letta/functions/function_sets/base.py +4 -1
  4. letta/functions/helpers.py +16 -2
  5. letta/jobs/__init__.py +0 -0
  6. letta/jobs/helpers.py +25 -0
  7. letta/jobs/llm_batch_job_polling.py +204 -0
  8. letta/jobs/scheduler.py +28 -0
  9. letta/jobs/types.py +10 -0
  10. letta/llm_api/anthropic.py +8 -3
  11. letta/llm_api/anthropic_client.py +5 -4
  12. letta/llm_api/llm_api_tools.py +2 -0
  13. letta/llm_api/openai_client.py +3 -1
  14. letta/memory.py +20 -4
  15. letta/orm/message.py +21 -5
  16. letta/schemas/enums.py +1 -0
  17. letta/schemas/llm_config.py +8 -4
  18. letta/schemas/message.py +8 -7
  19. letta/server/rest_api/app.py +11 -0
  20. letta/server/rest_api/chat_completions_interface.py +1 -0
  21. letta/server/rest_api/routers/v1/agents.py +16 -3
  22. letta/server/server.py +5 -1
  23. letta/services/agent_manager.py +34 -28
  24. letta/services/helpers/agent_manager_helper.py +3 -1
  25. letta/services/llm_batch_manager.py +97 -6
  26. letta/services/tool_sandbox/local_sandbox.py +2 -1
  27. letta/settings.py +4 -0
  28. letta/streaming_interface.py +2 -0
  29. {letta_nightly-0.6.49.dev20250408104230.dist-info → letta_nightly-0.6.50.dev20250409104353.dist-info}/METADATA +5 -4
  30. {letta_nightly-0.6.49.dev20250408104230.dist-info → letta_nightly-0.6.50.dev20250409104353.dist-info}/RECORD +33 -28
  31. {letta_nightly-0.6.49.dev20250408104230.dist-info → letta_nightly-0.6.50.dev20250409104353.dist-info}/LICENSE +0 -0
  32. {letta_nightly-0.6.49.dev20250408104230.dist-info → letta_nightly-0.6.50.dev20250409104353.dist-info}/WHEEL +0 -0
  33. {letta_nightly-0.6.49.dev20250408104230.dist-info → letta_nightly-0.6.50.dev20250409104353.dist-info}/entry_points.txt +0 -0
@@ -16,6 +16,7 @@ from starlette.middleware.cors import CORSMiddleware
16
16
  from letta.__init__ import __version__
17
17
  from letta.constants import ADMIN_PREFIX, API_PREFIX, OPENAI_API_PREFIX
18
18
  from letta.errors import BedrockPermissionError, LettaAgentNotFoundError, LettaUserNotFoundError
19
+ from letta.jobs.scheduler import shutdown_cron_scheduler, start_cron_jobs
19
20
  from letta.log import get_logger
20
21
  from letta.orm.errors import DatabaseTimeoutError, ForeignKeyConstraintViolationError, NoResultFound, UniqueConstraintViolationError
21
22
  from letta.schemas.letta_message import create_letta_message_union_schema
@@ -144,6 +145,12 @@ def create_application() -> "FastAPI":
144
145
  executor = concurrent.futures.ThreadPoolExecutor(max_workers=settings.event_loop_threadpool_max_workers)
145
146
  loop.set_default_executor(executor)
146
147
 
148
+ @app.on_event("startup")
149
+ def on_startup():
150
+ global server
151
+
152
+ start_cron_jobs(server)
153
+
147
154
  @app.on_event("shutdown")
148
155
  def shutdown_mcp_clients():
149
156
  global server
@@ -159,6 +166,10 @@ def create_application() -> "FastAPI":
159
166
  t.start()
160
167
  t.join()
161
168
 
169
+ @app.on_event("shutdown")
170
+ def shutdown_scheduler():
171
+ shutdown_cron_scheduler()
172
+
162
173
  @app.exception_handler(Exception)
163
174
  async def generic_error_handler(request: Request, exc: Exception):
164
175
  # Log the actual error for debugging
@@ -160,6 +160,7 @@ class ChatCompletionsStreamingInterface(AgentChunkStreamingInterface):
160
160
  message_id: str,
161
161
  message_date: datetime,
162
162
  expect_reasoning_content: bool = False,
163
+ name: Optional[str] = None,
163
164
  message_index: int = 0,
164
165
  ) -> None:
165
166
  """
@@ -14,7 +14,7 @@ from letta.agents.letta_agent import LettaAgent
14
14
  from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
15
15
  from letta.log import get_logger
16
16
  from letta.orm.errors import NoResultFound
17
- from letta.schemas.agent import AgentState, CreateAgent, UpdateAgent
17
+ from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgent
18
18
  from letta.schemas.block import Block, BlockUpdate
19
19
  from letta.schemas.job import JobStatus, JobUpdate, LettaRequestConfig
20
20
  from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion
@@ -31,6 +31,7 @@ from letta.schemas.user import User
31
31
  from letta.serialize_schemas.pydantic_agent_schema import AgentSchema
32
32
  from letta.server.rest_api.utils import get_letta_server
33
33
  from letta.server.server import SyncServer
34
+ from letta.settings import settings
34
35
 
35
36
  # These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
36
37
 
@@ -593,7 +594,13 @@ async def send_message(
593
594
  # TODO: This is redundant, remove soon
594
595
  agent = server.agent_manager.get_agent_by_id(agent_id, actor)
595
596
 
596
- if agent.llm_config.model_endpoint_type == "anthropic" and not agent.enable_sleeptime and not agent.multi_agent_group:
597
+ if (
598
+ agent.llm_config.model_endpoint_type == "anthropic"
599
+ and not agent.enable_sleeptime
600
+ and not agent.multi_agent_group
601
+ and not agent.agent_type == AgentType.sleeptime_agent
602
+ and settings.use_experimental
603
+ ):
597
604
  experimental_agent = LettaAgent(
598
605
  agent_id=agent_id,
599
606
  message_manager=server.message_manager,
@@ -649,7 +656,13 @@ async def send_message_streaming(
649
656
  # TODO: This is redundant, remove soon
650
657
  agent = server.agent_manager.get_agent_by_id(agent_id, actor)
651
658
 
652
- if agent.llm_config.model_endpoint_type == "anthropic" and not agent.enable_sleeptime and not agent.multi_agent_group:
659
+ if (
660
+ agent.llm_config.model_endpoint_type == "anthropic"
661
+ and not agent.enable_sleeptime
662
+ and not agent.multi_agent_group
663
+ and not agent.agent_type == AgentType.sleeptime_agent
664
+ and settings.use_experimental
665
+ ):
653
666
  experimental_agent = LettaAgent(
654
667
  agent_id=agent_id,
655
668
  message_manager=server.message_manager,
letta/server/server.py CHANGED
@@ -8,6 +8,7 @@ from abc import abstractmethod
8
8
  from datetime import datetime
9
9
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
10
10
 
11
+ from anthropic import AsyncAnthropic
11
12
  from composio.client import Composio
12
13
  from composio.client.collections import ActionModel, AppModel
13
14
  from fastapi import HTTPException
@@ -352,6 +353,9 @@ class SyncServer(Server):
352
353
  self._llm_config_cache = {}
353
354
  self._embedding_config_cache = {}
354
355
 
356
+ # TODO: Replace this with the Anthropic client we have in house
357
+ self.anthropic_async_client = AsyncAnthropic()
358
+
355
359
  def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
356
360
  """Updated method to load agents from persisted storage"""
357
361
  agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
@@ -775,7 +779,7 @@ class SyncServer(Server):
775
779
 
776
780
  def create_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
777
781
  request = CreateAgent(
778
- name=main_agent.name,
782
+ name=main_agent.name + "-sleeptime",
779
783
  agent_type=AgentType.sleeptime_agent,
780
784
  block_ids=[block.id for block in main_agent.memory.blocks],
781
785
  memory_blocks=[
@@ -38,7 +38,7 @@ from letta.schemas.group import ManagerType
38
38
  from letta.schemas.llm_config import LLMConfig
39
39
  from letta.schemas.memory import Memory
40
40
  from letta.schemas.message import Message as PydanticMessage
41
- from letta.schemas.message import MessageCreate
41
+ from letta.schemas.message import MessageCreate, MessageUpdate
42
42
  from letta.schemas.passage import Passage as PydanticPassage
43
43
  from letta.schemas.source import Source as PydanticSource
44
44
  from letta.schemas.tool import Tool as PydanticTool
@@ -115,9 +115,10 @@ class AgentManager:
115
115
  block = self.block_manager.create_or_update_block(PydanticBlock(**create_block.model_dump(to_orm=True)), actor=actor)
116
116
  block_ids.append(block.id)
117
117
 
118
- # TODO: Remove this block once we deprecate the legacy `tools` field
119
- # create passed in `tools`
120
- tool_names = []
118
+ # add passed in `tools`
119
+ tool_names = agent_create.tools or []
120
+
121
+ # add base tools
121
122
  if agent_create.include_base_tools:
122
123
  if agent_create.agent_type == AgentType.sleeptime_agent:
123
124
  tool_names.extend(BASE_SLEEPTIME_TOOLS)
@@ -128,42 +129,45 @@ class AgentManager:
128
129
  tool_names.extend(BASE_TOOLS + BASE_MEMORY_TOOLS)
129
130
  if agent_create.include_multi_agent_tools:
130
131
  tool_names.extend(MULTI_AGENT_TOOLS)
131
- if agent_create.tools:
132
- tool_names.extend(agent_create.tools)
133
- # Remove duplicates
132
+
133
+ # remove duplicates
134
134
  tool_names = list(set(tool_names))
135
135
 
136
+ # convert tool names to ids
137
+ tool_ids = []
138
+ for tool_name in tool_names:
139
+ tool = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
140
+ if not tool:
141
+ raise ValueError(f"Tool {tool_name} not found")
142
+ tool_ids.append(tool.id)
143
+
144
+ # add passed in `tool_ids`
145
+ for tool_id in agent_create.tool_ids or []:
146
+ if tool_id not in tool_ids:
147
+ tool = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=actor)
148
+ if tool:
149
+ tool_ids.append(tool.id)
150
+ tool_names.append(tool.name)
151
+ else:
152
+ raise ValueError(f"Tool {tool_id} not found")
153
+
136
154
  # add default tool rules
155
+ tool_rules = agent_create.tool_rules or []
137
156
  if agent_create.include_base_tool_rules:
138
- if not agent_create.tool_rules:
139
- tool_rules = []
140
- else:
141
- tool_rules = agent_create.tool_rules
142
-
143
157
  # apply default tool rules
144
158
  for tool_name in tool_names:
145
159
  if tool_name == "send_message" or tool_name == "send_message_to_agent_async" or tool_name == "finish_rethinking_memory":
146
160
  tool_rules.append(PydanticTerminalToolRule(tool_name=tool_name))
147
- elif tool_name in BASE_TOOLS:
161
+ elif tool_name in BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_SLEEPTIME_TOOLS:
148
162
  tool_rules.append(PydanticContinueToolRule(tool_name=tool_name))
149
163
 
150
164
  if agent_create.agent_type == AgentType.sleeptime_agent:
151
165
  tool_rules.append(PydanticChildToolRule(tool_name="view_core_memory_with_line_numbers", children=["core_memory_insert"]))
152
166
 
153
- else:
154
- tool_rules = agent_create.tool_rules
155
- # Check tool rules are valid
167
+ # if custom rules, check tool rules are valid
156
168
  if agent_create.tool_rules:
157
169
  check_supports_structured_output(model=agent_create.llm_config.model, tool_rules=agent_create.tool_rules)
158
170
 
159
- tool_ids = agent_create.tool_ids or []
160
- for tool_name in tool_names:
161
- tool = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
162
- if tool:
163
- tool_ids.append(tool.id)
164
- # Remove duplicates
165
- tool_ids = list(set(tool_ids))
166
-
167
171
  # Create the agent
168
172
  agent_state = self._create_agent(
169
173
  name=agent_create.name,
@@ -714,10 +718,12 @@ class AgentManager:
714
718
  model=agent_state.llm_config.model,
715
719
  openai_message_dict={"role": "system", "content": new_system_message_str},
716
720
  )
717
- # TODO: This seems kind of silly, why not just update the message?
718
- message = self.message_manager.create_message(message, actor=actor)
719
- message_ids = [message.id] + agent_state.message_ids[1:] # swap index 0 (system)
720
- return self.set_in_context_messages(agent_id=agent_id, message_ids=message_ids, actor=actor)
721
+ message = self.message_manager.update_message_by_id(
722
+ message_id=curr_system_message.id,
723
+ message_update=MessageUpdate(**message.model_dump()),
724
+ actor=actor,
725
+ )
726
+ return self.set_in_context_messages(agent_id=agent_id, message_ids=agent_state.message_ids, actor=actor)
721
727
  else:
722
728
  return agent_state
723
729
 
@@ -238,7 +238,9 @@ def initialize_message_sequence(
238
238
  first_user_message = get_login_event() # event letting Letta know the user just logged in
239
239
 
240
240
  if include_initial_boot_message:
241
- if agent_state.llm_config.model is not None and "gpt-3.5" in agent_state.llm_config.model:
241
+ if agent_state.agent_type == AgentType.sleeptime_agent:
242
+ initial_boot_messages = []
243
+ elif agent_state.llm_config.model is not None and "gpt-3.5" in agent_state.llm_config.model:
242
244
  initial_boot_messages = get_initial_boot_messages("startup_with_send_message_gpt35")
243
245
  else:
244
246
  initial_boot_messages = get_initial_boot_messages("startup_with_send_message")
@@ -1,13 +1,15 @@
1
1
  import datetime
2
- from typing import Optional
2
+ from typing import List, Optional
3
3
 
4
4
  from anthropic.types.beta.messages import BetaMessageBatch, BetaMessageBatchIndividualResponse
5
+ from sqlalchemy import tuple_
5
6
 
7
+ from letta.jobs.types import BatchPollingResult, ItemUpdateInfo
6
8
  from letta.log import get_logger
7
9
  from letta.orm.llm_batch_items import LLMBatchItem
8
10
  from letta.orm.llm_batch_job import LLMBatchJob
9
11
  from letta.schemas.agent import AgentStepState
10
- from letta.schemas.enums import AgentStepStatus, JobStatus
12
+ from letta.schemas.enums import AgentStepStatus, JobStatus, ProviderType
11
13
  from letta.schemas.llm_batch_job import LLMBatchItem as PydanticLLMBatchItem
12
14
  from letta.schemas.llm_batch_job import LLMBatchJob as PydanticLLMBatchJob
13
15
  from letta.schemas.llm_config import LLMConfig
@@ -28,7 +30,7 @@ class LLMBatchManager:
28
30
  @enforce_types
29
31
  def create_batch_request(
30
32
  self,
31
- llm_provider: str,
33
+ llm_provider: ProviderType,
32
34
  create_batch_response: BetaMessageBatch,
33
35
  actor: PydanticUser,
34
36
  status: JobStatus = JobStatus.created,
@@ -45,7 +47,7 @@ class LLMBatchManager:
45
47
  return batch.to_pydantic()
46
48
 
47
49
  @enforce_types
48
- def get_batch_request_by_id(self, batch_id: str, actor: PydanticUser) -> PydanticLLMBatchJob:
50
+ def get_batch_job_by_id(self, batch_id: str, actor: Optional[PydanticUser] = None) -> PydanticLLMBatchJob:
49
51
  """Retrieve a single batch job by ID."""
50
52
  with self.session_maker() as session:
51
53
  batch = LLMBatchJob.read(db_session=session, identifier=batch_id, actor=actor)
@@ -56,7 +58,7 @@ class LLMBatchManager:
56
58
  self,
57
59
  batch_id: str,
58
60
  status: JobStatus,
59
- actor: PydanticUser,
61
+ actor: Optional[PydanticUser] = None,
60
62
  latest_polling_response: Optional[BetaMessageBatch] = None,
61
63
  ) -> PydanticLLMBatchJob:
62
64
  """Update a batch job’s status and optionally its polling response."""
@@ -65,7 +67,34 @@ class LLMBatchManager:
65
67
  batch.status = status
66
68
  batch.latest_polling_response = latest_polling_response
67
69
  batch.last_polled_at = datetime.datetime.now(datetime.timezone.utc)
68
- return batch.update(db_session=session, actor=actor).to_pydantic()
70
+ batch = batch.update(db_session=session, actor=actor)
71
+ return batch.to_pydantic()
72
+
73
+ def bulk_update_batch_statuses(
74
+ self,
75
+ updates: List[BatchPollingResult],
76
+ ) -> None:
77
+ """
78
+ Efficiently update many LLMBatchJob rows. This is used by the cron jobs.
79
+
80
+ `updates` = [(batch_id, new_status, polling_response_or_None), …]
81
+ """
82
+ now = datetime.datetime.now(datetime.timezone.utc)
83
+
84
+ with self.session_maker() as session:
85
+ mappings = []
86
+ for batch_id, status, response in updates:
87
+ mappings.append(
88
+ {
89
+ "id": batch_id,
90
+ "status": status,
91
+ "latest_polling_response": response,
92
+ "last_polled_at": now,
93
+ }
94
+ )
95
+
96
+ session.bulk_update_mappings(LLMBatchJob, mappings)
97
+ session.commit()
69
98
 
70
99
  @enforce_types
71
100
  def delete_batch_request(self, batch_id: str, actor: PydanticUser) -> None:
@@ -74,6 +103,18 @@ class LLMBatchManager:
74
103
  batch = LLMBatchJob.read(db_session=session, identifier=batch_id, actor=actor)
75
104
  batch.hard_delete(db_session=session, actor=actor)
76
105
 
106
+ @enforce_types
107
+ def list_running_batches(self, actor: Optional[PydanticUser] = None) -> List[PydanticLLMBatchJob]:
108
+ """Return all running LLM batch jobs, optionally filtered by actor's organization."""
109
+ with self.session_maker() as session:
110
+ query = session.query(LLMBatchJob).filter(LLMBatchJob.status == JobStatus.running)
111
+
112
+ if actor is not None:
113
+ query = query.filter(LLMBatchJob.organization_id == actor.organization_id)
114
+
115
+ results = query.all()
116
+ return [batch.to_pydantic() for batch in results]
117
+
77
118
  @enforce_types
78
119
  def create_batch_item(
79
120
  self,
@@ -131,6 +172,56 @@ class LLMBatchManager:
131
172
 
132
173
  return item.update(db_session=session, actor=actor).to_pydantic()
133
174
 
175
+ def bulk_update_batch_items_by_agent(
176
+ self,
177
+ updates: List[ItemUpdateInfo],
178
+ ) -> None:
179
+ """
180
+ Efficiently update LLMBatchItem rows by (batch_id, agent_id).
181
+
182
+ Args:
183
+ updates: List of tuples:
184
+ (batch_id, agent_id, new_request_status, batch_request_result)
185
+ """
186
+ with self.session_maker() as session:
187
+ # For bulk_update_mappings, we need the primary key of each row
188
+ # So we must map (batch_id, agent_id) → actual PK (id)
189
+ # We'll do it in one DB query using the (batch_id, agent_id) sets
190
+
191
+ # 1. Gather the pairs
192
+ key_pairs = [(b_id, a_id) for (b_id, a_id, *_rest) in updates]
193
+
194
+ # 2. Query items in a single step
195
+ items = (
196
+ session.query(LLMBatchItem.id, LLMBatchItem.batch_id, LLMBatchItem.agent_id)
197
+ .filter(tuple_(LLMBatchItem.batch_id, LLMBatchItem.agent_id).in_(key_pairs))
198
+ .all()
199
+ )
200
+
201
+ # Build a map from (batch_id, agent_id) → PK id
202
+ pair_to_pk = {}
203
+ for row_id, row_batch_id, row_agent_id in items:
204
+ pair_to_pk[(row_batch_id, row_agent_id)] = row_id
205
+
206
+ # 3. Construct mappings for the PK-based bulk update
207
+ mappings = []
208
+ for batch_id, agent_id, new_status, new_result in updates:
209
+ pk_id = pair_to_pk.get((batch_id, agent_id))
210
+ if not pk_id:
211
+ # Nonexistent or mismatch → skip
212
+ continue
213
+ mappings.append(
214
+ {
215
+ "id": pk_id,
216
+ "request_status": new_status,
217
+ "batch_request_result": new_result,
218
+ }
219
+ )
220
+
221
+ if mappings:
222
+ session.bulk_update_mappings(LLMBatchItem, mappings)
223
+ session.commit()
224
+
134
225
  @enforce_types
135
226
  def delete_batch_item(self, item_id: str, actor: PydanticUser) -> None:
136
227
  """Hard delete a batch item by ID."""
@@ -12,6 +12,7 @@ from letta.services.helpers.tool_execution_helper import (
12
12
  install_pip_requirements_for_sandbox,
13
13
  )
14
14
  from letta.services.tool_sandbox.base import AsyncToolSandboxBase
15
+ from letta.settings import tool_settings
15
16
  from letta.tracing import log_event, trace_method
16
17
  from letta.utils import get_friendly_error_msg
17
18
 
@@ -152,7 +153,7 @@ class AsyncToolSandboxLocal(AsyncToolSandboxBase):
152
153
  )
153
154
 
154
155
  try:
155
- stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=60)
156
+ stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=tool_settings.local_sandbox_timeout)
156
157
  except asyncio.TimeoutError:
157
158
  # Terminate the process on timeout
158
159
  if process.returncode is None:
letta/settings.py CHANGED
@@ -17,6 +17,7 @@ class ToolSettings(BaseSettings):
17
17
 
18
18
  # Local Sandbox configurations
19
19
  local_sandbox_dir: Optional[str] = None
20
+ local_sandbox_timeout: float = 180
20
21
 
21
22
  # MCP settings
22
23
  mcp_connect_to_server_timeout: float = 30.0
@@ -203,6 +204,9 @@ class Settings(BaseSettings):
203
204
  httpx_max_keepalive_connections: int = 500
204
205
  httpx_keepalive_expiry: float = 120.0
205
206
 
207
+ # cron job parameters
208
+ poll_running_llm_batches_interval_seconds: int = 5 * 60
209
+
206
210
  @property
207
211
  def letta_pg_uri(self) -> str:
208
212
  if self.pg_uri:
@@ -54,6 +54,7 @@ class AgentChunkStreamingInterface(ABC):
54
54
  message_id: str,
55
55
  message_date: datetime,
56
56
  expect_reasoning_content: bool = False,
57
+ name: Optional[str] = None,
57
58
  message_index: int = 0,
58
59
  ):
59
60
  """Process a streaming chunk from an OpenAI-compatible server"""
@@ -105,6 +106,7 @@ class StreamingCLIInterface(AgentChunkStreamingInterface):
105
106
  message_id: str,
106
107
  message_date: datetime,
107
108
  expect_reasoning_content: bool = False,
109
+ name: Optional[str] = None,
108
110
  message_index: int = 0,
109
111
  ):
110
112
  assert len(chunk.choices) == 1, chunk
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.6.49.dev20250408104230
3
+ Version: 0.6.50.dev20250409104353
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  License: Apache License
6
6
  Author: Letta Team
@@ -24,6 +24,7 @@ Provides-Extra: server
24
24
  Provides-Extra: tests
25
25
  Requires-Dist: alembic (>=1.13.3,<2.0.0)
26
26
  Requires-Dist: anthropic (>=0.49.0,<0.50.0)
27
+ Requires-Dist: apscheduler (>=3.11.0,<4.0.0)
27
28
  Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev" or extra == "all"
28
29
  Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev" or extra == "all"
29
30
  Requires-Dist: boto3 (>=1.36.24,<2.0.0) ; extra == "bedrock"
@@ -31,7 +32,7 @@ Requires-Dist: brotli (>=1.1.0,<2.0.0)
31
32
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
32
33
  Requires-Dist: composio-core (>=0.7.7,<0.8.0)
33
34
  Requires-Dist: composio-langchain (>=0.7.7,<0.8.0)
34
- Requires-Dist: datamodel-code-generator[http] (>=0.25.0,<0.26.0) ; extra == "desktop" or extra == "all"
35
+ Requires-Dist: datamodel-code-generator[http] (>=0.25.0,<0.26.0)
35
36
  Requires-Dist: demjson3 (>=3.0.6,<4.0.0)
36
37
  Requires-Dist: docker (>=7.1.0,<8.0.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
37
38
  Requires-Dist: docstring-parser (>=0.16,<0.17)
@@ -49,12 +50,12 @@ Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev" or extra == "all"
49
50
  Requires-Dist: jinja2 (>=3.1.5,<4.0.0)
50
51
  Requires-Dist: langchain (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
51
52
  Requires-Dist: langchain-community (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
52
- Requires-Dist: letta_client (>=0.1.97,<0.2.0) ; extra == "desktop"
53
+ Requires-Dist: letta_client (>=0.1.97,<0.2.0)
53
54
  Requires-Dist: llama-index (>=0.12.2,<0.13.0)
54
55
  Requires-Dist: llama-index-embeddings-openai (>=0.3.1,<0.4.0)
55
56
  Requires-Dist: locust (>=2.31.5,<3.0.0) ; extra == "dev" or extra == "desktop" or extra == "all"
56
57
  Requires-Dist: marshmallow-sqlalchemy (>=1.4.1,<2.0.0)
57
- Requires-Dist: mcp (>=1.3.0,<2.0.0) ; extra == "desktop"
58
+ Requires-Dist: mcp (>=1.3.0,<2.0.0)
58
59
  Requires-Dist: nltk (>=3.8.1,<4.0.0)
59
60
  Requires-Dist: numpy (>=1.26.2,<2.0.0)
60
61
  Requires-Dist: openai (>=1.60.0,<2.0.0)