letta-nightly 0.8.5.dev20250625104328__py3-none-any.whl → 0.8.6.dev20250626104326__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 (78) hide show
  1. letta/agent.py +16 -12
  2. letta/agents/base_agent.py +4 -1
  3. letta/agents/helpers.py +35 -3
  4. letta/agents/letta_agent.py +132 -106
  5. letta/agents/letta_agent_batch.py +4 -3
  6. letta/agents/voice_agent.py +12 -2
  7. letta/agents/voice_sleeptime_agent.py +12 -2
  8. letta/constants.py +24 -3
  9. letta/data_sources/redis_client.py +6 -0
  10. letta/errors.py +5 -0
  11. letta/functions/function_sets/files.py +10 -3
  12. letta/functions/function_sets/multi_agent.py +0 -32
  13. letta/groups/sleeptime_multi_agent_v2.py +6 -0
  14. letta/helpers/converters.py +4 -1
  15. letta/helpers/datetime_helpers.py +16 -23
  16. letta/helpers/message_helper.py +5 -2
  17. letta/helpers/tool_rule_solver.py +29 -2
  18. letta/interfaces/openai_streaming_interface.py +9 -2
  19. letta/llm_api/anthropic.py +11 -1
  20. letta/llm_api/anthropic_client.py +14 -3
  21. letta/llm_api/aws_bedrock.py +29 -15
  22. letta/llm_api/bedrock_client.py +74 -0
  23. letta/llm_api/google_ai_client.py +7 -3
  24. letta/llm_api/google_vertex_client.py +18 -4
  25. letta/llm_api/llm_client.py +7 -0
  26. letta/llm_api/openai_client.py +13 -0
  27. letta/orm/agent.py +5 -0
  28. letta/orm/block_history.py +1 -1
  29. letta/orm/enums.py +6 -25
  30. letta/orm/job.py +1 -2
  31. letta/orm/llm_batch_items.py +1 -1
  32. letta/orm/mcp_server.py +1 -1
  33. letta/orm/passage.py +7 -1
  34. letta/orm/sqlalchemy_base.py +7 -5
  35. letta/orm/tool.py +2 -1
  36. letta/schemas/agent.py +34 -10
  37. letta/schemas/enums.py +42 -1
  38. letta/schemas/job.py +6 -3
  39. letta/schemas/letta_request.py +4 -0
  40. letta/schemas/llm_batch_job.py +7 -2
  41. letta/schemas/memory.py +2 -2
  42. letta/schemas/providers.py +32 -6
  43. letta/schemas/run.py +1 -1
  44. letta/schemas/tool_rule.py +40 -12
  45. letta/serialize_schemas/pydantic_agent_schema.py +9 -2
  46. letta/server/rest_api/app.py +3 -2
  47. letta/server/rest_api/routers/v1/agents.py +25 -22
  48. letta/server/rest_api/routers/v1/runs.py +2 -3
  49. letta/server/rest_api/routers/v1/sources.py +31 -0
  50. letta/server/rest_api/routers/v1/voice.py +1 -0
  51. letta/server/rest_api/utils.py +38 -13
  52. letta/server/server.py +52 -21
  53. letta/services/agent_manager.py +58 -7
  54. letta/services/block_manager.py +1 -1
  55. letta/services/file_processor/chunker/line_chunker.py +2 -1
  56. letta/services/file_processor/file_processor.py +2 -9
  57. letta/services/files_agents_manager.py +177 -37
  58. letta/services/helpers/agent_manager_helper.py +77 -48
  59. letta/services/helpers/tool_parser_helper.py +2 -1
  60. letta/services/job_manager.py +33 -2
  61. letta/services/llm_batch_manager.py +1 -1
  62. letta/services/provider_manager.py +6 -4
  63. letta/services/tool_executor/core_tool_executor.py +1 -1
  64. letta/services/tool_executor/files_tool_executor.py +99 -30
  65. letta/services/tool_executor/multi_agent_tool_executor.py +1 -17
  66. letta/services/tool_executor/tool_execution_manager.py +6 -0
  67. letta/services/tool_executor/tool_executor_base.py +3 -0
  68. letta/services/tool_sandbox/base.py +39 -1
  69. letta/services/tool_sandbox/e2b_sandbox.py +7 -0
  70. letta/services/user_manager.py +3 -2
  71. letta/settings.py +8 -14
  72. letta/system.py +17 -17
  73. letta/templates/sandbox_code_file_async.py.j2 +59 -0
  74. {letta_nightly-0.8.5.dev20250625104328.dist-info → letta_nightly-0.8.6.dev20250626104326.dist-info}/METADATA +3 -2
  75. {letta_nightly-0.8.5.dev20250625104328.dist-info → letta_nightly-0.8.6.dev20250626104326.dist-info}/RECORD +78 -76
  76. {letta_nightly-0.8.5.dev20250625104328.dist-info → letta_nightly-0.8.6.dev20250626104326.dist-info}/LICENSE +0 -0
  77. {letta_nightly-0.8.5.dev20250625104328.dist-info → letta_nightly-0.8.6.dev20250626104326.dist-info}/WHEEL +0 -0
  78. {letta_nightly-0.8.5.dev20250625104328.dist-info → letta_nightly-0.8.6.dev20250626104326.dist-info}/entry_points.txt +0 -0
@@ -13,7 +13,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError
13
13
  from starlette.responses import Response, StreamingResponse
14
14
 
15
15
  from letta.agents.letta_agent import LettaAgent
16
- from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
16
+ from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, LETTA_MODEL_ENDPOINT
17
17
  from letta.groups.sleeptime_multi_agent_v2 import SleeptimeMultiAgentV2
18
18
  from letta.helpers.datetime_helpers import get_utc_timestamp_ns
19
19
  from letta.log import get_logger
@@ -25,7 +25,7 @@ from letta.schemas.block import Block, BlockUpdate
25
25
  from letta.schemas.group import Group
26
26
  from letta.schemas.job import JobStatus, JobUpdate, LettaRequestConfig
27
27
  from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion, MessageType
28
- from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest
28
+ from letta.schemas.letta_request import LettaAsyncRequest, LettaRequest, LettaStreamingRequest
29
29
  from letta.schemas.letta_response import LettaResponse
30
30
  from letta.schemas.memory import ContextWindowOverview, CreateArchivalMemory, Memory
31
31
  from letta.schemas.message import MessageCreate
@@ -323,17 +323,7 @@ async def attach_source(
323
323
  agent_state = await server.agent_manager.attach_missing_files_tools_async(agent_state=agent_state, actor=actor)
324
324
 
325
325
  files = await server.file_manager.list_files(source_id, actor, include_content=True)
326
- texts = []
327
- file_ids = []
328
- file_names = []
329
- for f in files:
330
- texts.append(f.content if f.content else "")
331
- file_ids.append(f.id)
332
- file_names.append(f.file_name)
333
-
334
- await server.insert_files_into_context_window(
335
- agent_state=agent_state, texts=texts, file_ids=file_ids, file_names=file_names, actor=actor
336
- )
326
+ await server.insert_files_into_context_window(agent_state=agent_state, file_metadata_with_content=files, actor=actor)
337
327
 
338
328
  if agent_state.enable_sleeptime:
339
329
  source = await server.source_manager.get_source_by_id(source_id=source_id)
@@ -686,7 +676,7 @@ async def send_message(
686
676
  # TODO: This is redundant, remove soon
687
677
  agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
688
678
  agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
689
- model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
679
+ model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
690
680
 
691
681
  if agent_eligible and model_compatible:
692
682
  if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
@@ -707,6 +697,7 @@ async def send_message(
707
697
  message_manager=server.message_manager,
708
698
  agent_manager=server.agent_manager,
709
699
  block_manager=server.block_manager,
700
+ job_manager=server.job_manager,
710
701
  passage_manager=server.passage_manager,
711
702
  actor=actor,
712
703
  step_manager=server.step_manager,
@@ -768,9 +759,9 @@ async def send_message_streaming(
768
759
  # TODO: This is redundant, remove soon
769
760
  agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
770
761
  agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
771
- model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
772
- model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
773
- not_letta_endpoint = not ("inference.letta.com" in agent.llm_config.model_endpoint)
762
+ model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
763
+ model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "bedrock"]
764
+ not_letta_endpoint = LETTA_MODEL_ENDPOINT != agent.llm_config.model_endpoint
774
765
 
775
766
  if agent_eligible and model_compatible:
776
767
  if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
@@ -793,6 +784,7 @@ async def send_message_streaming(
793
784
  message_manager=server.message_manager,
794
785
  agent_manager=server.agent_manager,
795
786
  block_manager=server.block_manager,
787
+ job_manager=server.job_manager,
796
788
  passage_manager=server.passage_manager,
797
789
  actor=actor,
798
790
  step_manager=server.step_manager,
@@ -857,7 +849,14 @@ async def process_message_background(
857
849
  try:
858
850
  agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
859
851
  agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
860
- model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
852
+ model_compatible = agent.llm_config.model_endpoint_type in [
853
+ "anthropic",
854
+ "openai",
855
+ "together",
856
+ "google_ai",
857
+ "google_vertex",
858
+ "bedrock",
859
+ ]
861
860
  if agent_eligible and model_compatible:
862
861
  if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
863
862
  agent_loop = SleeptimeMultiAgentV2(
@@ -877,6 +876,7 @@ async def process_message_background(
877
876
  message_manager=server.message_manager,
878
877
  agent_manager=server.agent_manager,
879
878
  block_manager=server.block_manager,
879
+ job_manager=server.job_manager,
880
880
  passage_manager=server.passage_manager,
881
881
  actor=actor,
882
882
  step_manager=server.step_manager,
@@ -886,6 +886,7 @@ async def process_message_background(
886
886
  result = await agent_loop.step(
887
887
  messages,
888
888
  max_steps=max_steps,
889
+ run_id=job_id,
889
890
  use_assistant_message=use_assistant_message,
890
891
  request_start_timestamp_ns=request_start_timestamp_ns,
891
892
  include_return_message_types=include_return_message_types,
@@ -897,6 +898,7 @@ async def process_message_background(
897
898
  input_messages=messages,
898
899
  stream_steps=False,
899
900
  stream_tokens=False,
901
+ metadata={"job_id": job_id},
900
902
  # Support for AssistantMessage
901
903
  use_assistant_message=use_assistant_message,
902
904
  assistant_message_tool_name=assistant_message_tool_name,
@@ -929,9 +931,8 @@ async def process_message_background(
929
931
  async def send_message_async(
930
932
  agent_id: str,
931
933
  server: SyncServer = Depends(get_letta_server),
932
- request: LettaRequest = Body(...),
934
+ request: LettaAsyncRequest = Body(...),
933
935
  actor_id: Optional[str] = Header(None, alias="user_id"),
934
- callback_url: Optional[str] = Query(None, description="Optional callback URL to POST to when the job completes"),
935
936
  ):
936
937
  """
937
938
  Asynchronously process a user message and return a run object.
@@ -944,7 +945,7 @@ async def send_message_async(
944
945
  run = Run(
945
946
  user_id=actor.id,
946
947
  status=JobStatus.created,
947
- callback_url=callback_url,
948
+ callback_url=request.callback_url,
948
949
  metadata={
949
950
  "job_type": "send_message_async",
950
951
  "agent_id": agent_id,
@@ -953,6 +954,7 @@ async def send_message_async(
953
954
  use_assistant_message=request.use_assistant_message,
954
955
  assistant_message_tool_name=request.assistant_message_tool_name,
955
956
  assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
957
+ include_return_message_types=request.include_return_message_types,
956
958
  ),
957
959
  )
958
960
  run = await server.job_manager.create_job_async(pydantic_job=run, actor=actor)
@@ -1021,7 +1023,7 @@ async def summarize_agent_conversation(
1021
1023
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
1022
1024
  agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
1023
1025
  agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
1024
- model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
1026
+ model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
1025
1027
 
1026
1028
  if agent_eligible and model_compatible:
1027
1029
  agent = LettaAgent(
@@ -1029,6 +1031,7 @@ async def summarize_agent_conversation(
1029
1031
  message_manager=server.message_manager,
1030
1032
  agent_manager=server.agent_manager,
1031
1033
  block_manager=server.block_manager,
1034
+ job_manager=server.job_manager,
1032
1035
  passage_manager=server.passage_manager,
1033
1036
  actor=actor,
1034
1037
  step_manager=server.step_manager,
@@ -3,9 +3,8 @@ from typing import Annotated, List, Optional
3
3
  from fastapi import APIRouter, Depends, Header, HTTPException, Query
4
4
  from pydantic import Field
5
5
 
6
- from letta.orm.enums import JobType
7
6
  from letta.orm.errors import NoResultFound
8
- from letta.schemas.enums import JobStatus, MessageRole
7
+ from letta.schemas.enums import JobStatus, JobType, MessageRole
9
8
  from letta.schemas.letta_message import LettaMessageUnion
10
9
  from letta.schemas.openai.chat_completion_response import UsageStatistics
11
10
  from letta.schemas.run import Run
@@ -92,7 +91,7 @@ async def list_run_messages(
92
91
  after: Optional[str] = Query(None, description="Cursor for pagination"),
93
92
  limit: Optional[int] = Query(100, description="Maximum number of messages to return"),
94
93
  order: str = Query(
95
- "desc", description="Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order."
94
+ "asc", description="Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order."
96
95
  ),
97
96
  role: Optional[MessageRole] = Query(None, description="Filter by role"),
98
97
  ):
@@ -304,6 +304,37 @@ async def list_source_files(
304
304
  )
305
305
 
306
306
 
307
+ @router.get("/{source_id}/files/{file_id}", response_model=FileMetadata, operation_id="get_file_metadata")
308
+ async def get_file_metadata(
309
+ source_id: str,
310
+ file_id: str,
311
+ include_content: bool = Query(False, description="Whether to include full file content"),
312
+ server: "SyncServer" = Depends(get_letta_server),
313
+ actor_id: Optional[str] = Header(None, alias="user_id"),
314
+ ):
315
+ """
316
+ Retrieve metadata for a specific file by its ID.
317
+ """
318
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
319
+
320
+ # Verify the source exists and user has access
321
+ source = await server.source_manager.get_source_by_id(source_id=source_id, actor=actor)
322
+ if not source:
323
+ raise HTTPException(status_code=404, detail=f"Source with id={source_id} not found.")
324
+
325
+ # Get file metadata using the file manager
326
+ file_metadata = await server.file_manager.get_file_by_id(file_id=file_id, actor=actor, include_content=include_content)
327
+
328
+ if not file_metadata:
329
+ raise HTTPException(status_code=404, detail=f"File with id={file_id} not found.")
330
+
331
+ # Verify the file belongs to the specified source
332
+ if file_metadata.source_id != source_id:
333
+ raise HTTPException(status_code=404, detail=f"File with id={file_id} not found in source {source_id}.")
334
+
335
+ return file_metadata
336
+
337
+
307
338
  # it's redundant to include /delete in the URL path. The HTTP verb DELETE already implies that action.
308
339
  # it's still good practice to return a status indicating the success or failure of the deletion
309
340
  @router.delete("/{source_id}/{file_id}", status_code=204, operation_id="delete_file_from_source")
@@ -51,6 +51,7 @@ async def create_voice_chat_completions(
51
51
  message_manager=server.message_manager,
52
52
  agent_manager=server.agent_manager,
53
53
  block_manager=server.block_manager,
54
+ job_manager=server.job_manager,
54
55
  passage_manager=server.passage_manager,
55
56
  actor=actor,
56
57
  )
@@ -13,7 +13,13 @@ from openai.types.chat.chat_completion_message_tool_call import Function as Open
13
13
  from openai.types.chat.completion_create_params import CompletionCreateParams
14
14
  from pydantic import BaseModel
15
15
 
16
- from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, FUNC_FAILED_HEARTBEAT_MESSAGE, REQ_HEARTBEAT_MESSAGE
16
+ from letta.constants import (
17
+ DEFAULT_MESSAGE_TOOL,
18
+ DEFAULT_MESSAGE_TOOL_KWARG,
19
+ FUNC_FAILED_HEARTBEAT_MESSAGE,
20
+ REQ_HEARTBEAT_MESSAGE,
21
+ REQUEST_HEARTBEAT_PARAM,
22
+ )
17
23
  from letta.errors import ContextWindowExceededError, RateLimitExceededError
18
24
  from letta.helpers.datetime_helpers import get_utc_time, get_utc_timestamp_ns, ns_to_ms
19
25
  from letta.helpers.message_helper import convert_message_creates_to_messages
@@ -169,7 +175,7 @@ def log_error_to_sentry(e):
169
175
  sentry_sdk.capture_exception(e)
170
176
 
171
177
 
172
- def create_input_messages(input_messages: List[MessageCreate], agent_id: str, actor: User) -> List[Message]:
178
+ def create_input_messages(input_messages: List[MessageCreate], agent_id: str, timezone: str, actor: User) -> List[Message]:
173
179
  """
174
180
  Converts a user input message into the internal structured format.
175
181
 
@@ -177,7 +183,7 @@ def create_input_messages(input_messages: List[MessageCreate], agent_id: str, ac
177
183
  we should unify this when it's clear what message attributes we need.
178
184
  """
179
185
 
180
- messages = convert_message_creates_to_messages(input_messages, agent_id, wrap_user_message=False, wrap_system_message=False)
186
+ messages = convert_message_creates_to_messages(input_messages, agent_id, timezone, wrap_user_message=False, wrap_system_message=False)
181
187
  for message in messages:
182
188
  message.organization_id = actor.organization_id
183
189
  return messages
@@ -192,17 +198,19 @@ def create_letta_messages_from_llm_response(
192
198
  tool_call_id: str,
193
199
  function_call_success: bool,
194
200
  function_response: Optional[str],
201
+ timezone: str,
195
202
  actor: User,
196
- add_heartbeat_request_system_message: bool = False,
203
+ continue_stepping: bool = False,
204
+ heartbeat_reason: Optional[str] = None,
197
205
  reasoning_content: Optional[List[Union[TextContent, ReasoningContent, RedactedReasoningContent, OmittedReasoningContent]]] = None,
198
206
  pre_computed_assistant_message_id: Optional[str] = None,
199
207
  llm_batch_item_id: Optional[str] = None,
200
208
  step_id: str | None = None,
201
209
  ) -> List[Message]:
202
210
  messages = []
203
-
204
211
  # Construct the tool call with the assistant's message
205
- function_arguments["request_heartbeat"] = True
212
+ # Force set request_heartbeat in tool_args to calculated continue_stepping
213
+ function_arguments[REQUEST_HEARTBEAT_PARAM] = continue_stepping
206
214
  tool_call = OpenAIToolCall(
207
215
  id=tool_call_id,
208
216
  function=OpenAIFunction(
@@ -232,7 +240,7 @@ def create_letta_messages_from_llm_response(
232
240
  # TODO: This helps preserve ordering
233
241
  tool_message = Message(
234
242
  role=MessageRole.tool,
235
- content=[TextContent(text=package_function_response(function_call_success, function_response))],
243
+ content=[TextContent(text=package_function_response(function_call_success, function_response, timezone))],
236
244
  organization_id=actor.organization_id,
237
245
  agent_id=agent_id,
238
246
  model=model,
@@ -252,9 +260,14 @@ def create_letta_messages_from_llm_response(
252
260
  )
253
261
  messages.append(tool_message)
254
262
 
255
- if add_heartbeat_request_system_message:
263
+ if continue_stepping:
256
264
  heartbeat_system_message = create_heartbeat_system_message(
257
- agent_id=agent_id, model=model, function_call_success=function_call_success, actor=actor, llm_batch_item_id=llm_batch_item_id
265
+ agent_id=agent_id,
266
+ model=model,
267
+ function_call_success=function_call_success,
268
+ actor=actor,
269
+ timezone=timezone,
270
+ heartbeat_reason=heartbeat_reason,
258
271
  )
259
272
  messages.append(heartbeat_system_message)
260
273
 
@@ -265,12 +278,22 @@ def create_letta_messages_from_llm_response(
265
278
 
266
279
 
267
280
  def create_heartbeat_system_message(
268
- agent_id: str, model: str, function_call_success: bool, actor: User, llm_batch_item_id: Optional[str] = None
281
+ agent_id: str,
282
+ model: str,
283
+ function_call_success: bool,
284
+ timezone: str,
285
+ actor: User,
286
+ llm_batch_item_id: Optional[str] = None,
287
+ heartbeat_reason: Optional[str] = None,
269
288
  ) -> Message:
270
- text_content = REQ_HEARTBEAT_MESSAGE if function_call_success else FUNC_FAILED_HEARTBEAT_MESSAGE
289
+ if heartbeat_reason:
290
+ text_content = heartbeat_reason
291
+ else:
292
+ text_content = REQ_HEARTBEAT_MESSAGE if function_call_success else FUNC_FAILED_HEARTBEAT_MESSAGE
293
+
271
294
  heartbeat_system_message = Message(
272
295
  role=MessageRole.user,
273
- content=[TextContent(text=get_heartbeat(text_content))],
296
+ content=[TextContent(text=get_heartbeat(timezone, text_content))],
274
297
  organization_id=actor.organization_id,
275
298
  agent_id=agent_id,
276
299
  model=model,
@@ -287,6 +310,7 @@ def create_assistant_messages_from_openai_response(
287
310
  agent_id: str,
288
311
  model: str,
289
312
  actor: User,
313
+ timezone: str,
290
314
  ) -> List[Message]:
291
315
  """
292
316
  Converts an OpenAI response into Messages that follow the internal
@@ -303,8 +327,9 @@ def create_assistant_messages_from_openai_response(
303
327
  tool_call_id=tool_call_id,
304
328
  function_call_success=True,
305
329
  function_response=None,
330
+ timezone=timezone,
306
331
  actor=actor,
307
- add_heartbeat_request_system_message=False,
332
+ continue_stepping=False,
308
333
  )
309
334
 
310
335
 
letta/server/server.py CHANGED
@@ -43,6 +43,7 @@ from letta.schemas.embedding_config import EmbeddingConfig
43
43
  # openai schemas
44
44
  from letta.schemas.enums import JobStatus, MessageStreamStatus, ProviderCategory, ProviderType
45
45
  from letta.schemas.environment_variables import SandboxEnvironmentVariableCreate
46
+ from letta.schemas.file import FileMetadata
46
47
  from letta.schemas.group import GroupCreate, ManagerType, SleeptimeManager, VoiceSleeptimeManager
47
48
  from letta.schemas.job import Job, JobUpdate
48
49
  from letta.schemas.letta_message import LegacyLettaMessage, LettaMessage, MessageType, ToolReturnMessage
@@ -54,9 +55,9 @@ from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySumm
54
55
  from letta.schemas.message import Message, MessageCreate, MessageUpdate
55
56
  from letta.schemas.passage import Passage, PassageUpdate
56
57
  from letta.schemas.providers import (
57
- AnthropicBedrockProvider,
58
58
  AnthropicProvider,
59
59
  AzureProvider,
60
+ BedrockProvider,
60
61
  DeepSeekProvider,
61
62
  GoogleAIProvider,
62
63
  GoogleVertexProvider,
@@ -82,6 +83,7 @@ from letta.server.rest_api.utils import sse_async_generator
82
83
  from letta.services.agent_manager import AgentManager
83
84
  from letta.services.block_manager import BlockManager
84
85
  from letta.services.file_manager import FileManager
86
+ from letta.services.file_processor.chunker.line_chunker import LineChunker
85
87
  from letta.services.files_agents_manager import FileAgentManager
86
88
  from letta.services.group_manager import GroupManager
87
89
  from letta.services.helpers.tool_execution_helper import prepare_local_sandbox
@@ -365,11 +367,11 @@ class SyncServer(Server):
365
367
  base_url=model_settings.vllm_api_base,
366
368
  )
367
369
  )
368
- if model_settings.aws_access_key and model_settings.aws_secret_access_key and model_settings.aws_region:
370
+ if model_settings.aws_access_key_id and model_settings.aws_secret_access_key and model_settings.aws_default_region:
369
371
  self._enabled_providers.append(
370
- AnthropicBedrockProvider(
372
+ BedrockProvider(
371
373
  name="bedrock",
372
- aws_region=model_settings.aws_region,
374
+ region=model_settings.aws_default_region,
373
375
  )
374
376
  )
375
377
  # Attempt to enable LM Studio by default
@@ -631,7 +633,7 @@ class SyncServer(Server):
631
633
 
632
634
  packaged_user_message = system.package_user_message(
633
635
  user_message=message,
634
- time=timestamp.isoformat() if timestamp else None,
636
+ timezone=agent.timezone,
635
637
  )
636
638
 
637
639
  # NOTE: eventually deprecate and only allow passing Message types
@@ -710,6 +712,7 @@ class SyncServer(Server):
710
712
  # Run the agent state forward
711
713
  return self._step(actor=actor, agent_id=agent_id, input_messages=message)
712
714
 
715
+ # TODO: Deprecate this
713
716
  def send_messages(
714
717
  self,
715
718
  actor: User,
@@ -826,8 +829,6 @@ class SyncServer(Server):
826
829
  self,
827
830
  request: CreateAgent,
828
831
  actor: User,
829
- # interface
830
- interface: Union[AgentInterface, None] = None,
831
832
  ) -> AgentState:
832
833
  if request.llm_config is None:
833
834
  if request.model is None:
@@ -867,6 +868,16 @@ class SyncServer(Server):
867
868
  )
868
869
  log_event(name="end create_agent db")
869
870
 
871
+ log_event(name="start insert_files_into_context_window db")
872
+ if request.source_ids:
873
+ for source_id in request.source_ids:
874
+ files = await self.file_manager.list_files(source_id, actor, include_content=True)
875
+ await self.insert_files_into_context_window(agent_state=main_agent, file_metadata_with_content=files, actor=actor)
876
+
877
+ main_agent = await self.agent_manager.refresh_file_blocks(agent_state=main_agent, actor=actor)
878
+ main_agent = await self.agent_manager.attach_missing_files_tools_async(agent_state=main_agent, actor=actor)
879
+ log_event(name="end insert_files_into_context_window db")
880
+
870
881
  if request.enable_sleeptime:
871
882
  if request.agent_type == AgentType.voice_convo_agent:
872
883
  main_agent = await self.create_voice_sleeptime_agent_async(main_agent=main_agent, actor=actor)
@@ -1355,6 +1366,7 @@ class SyncServer(Server):
1355
1366
  message_manager=self.message_manager,
1356
1367
  agent_manager=self.agent_manager,
1357
1368
  block_manager=self.block_manager,
1369
+ job_manager=self.job_manager,
1358
1370
  passage_manager=self.passage_manager,
1359
1371
  actor=actor,
1360
1372
  step_manager=self.step_manager,
@@ -1369,13 +1381,25 @@ class SyncServer(Server):
1369
1381
  )
1370
1382
  await self.agent_manager.delete_agent_async(agent_id=sleeptime_agent_state.id, actor=actor)
1371
1383
 
1372
- async def _upsert_file_to_agent(self, agent_id: str, text: str, file_id: str, file_name: str, actor: User) -> None:
1384
+ async def _upsert_file_to_agent(self, agent_id: str, file_metadata_with_content: FileMetadata, actor: User) -> List[str]:
1373
1385
  """
1374
1386
  Internal method to create or update a file <-> agent association
1387
+
1388
+ Returns:
1389
+ List of file names that were closed due to LRU eviction
1375
1390
  """
1376
- await self.file_agent_manager.attach_file(
1377
- agent_id=agent_id, file_id=file_id, file_name=file_name, actor=actor, visible_content=text
1391
+ # TODO: Maybe have LineChunker object be on the server level?
1392
+ content_lines = LineChunker().chunk_text(file_metadata=file_metadata_with_content)
1393
+ visible_content = "\n".join(content_lines)
1394
+
1395
+ file_agent, closed_files = await self.file_agent_manager.attach_file(
1396
+ agent_id=agent_id,
1397
+ file_id=file_metadata_with_content.id,
1398
+ file_name=file_metadata_with_content.file_name,
1399
+ actor=actor,
1400
+ visible_content=visible_content,
1378
1401
  )
1402
+ return closed_files
1379
1403
 
1380
1404
  async def _remove_file_from_agent(self, agent_id: str, file_id: str, actor: User) -> None:
1381
1405
  """
@@ -1391,7 +1415,7 @@ class SyncServer(Server):
1391
1415
  logger.info(f"File {file_id} already removed from agent {agent_id}, skipping...")
1392
1416
 
1393
1417
  async def insert_file_into_context_windows(
1394
- self, source_id: str, text: str, file_id: str, file_name: str, actor: User, agent_states: Optional[List[AgentState]] = None
1418
+ self, source_id: str, file_metadata_with_content: FileMetadata, actor: User, agent_states: Optional[List[AgentState]] = None
1395
1419
  ) -> List[AgentState]:
1396
1420
  """
1397
1421
  Insert the uploaded document into the context window of all agents
@@ -1406,12 +1430,19 @@ class SyncServer(Server):
1406
1430
  logger.info(f"Inserting document into context window for source: {source_id}")
1407
1431
  logger.info(f"Attached agents: {[a.id for a in agent_states]}")
1408
1432
 
1409
- await asyncio.gather(*(self._upsert_file_to_agent(agent_state.id, text, file_id, file_name, actor) for agent_state in agent_states))
1433
+ # Collect any files that were closed due to LRU eviction during bulk attach
1434
+ all_closed_files = await asyncio.gather(
1435
+ *(self._upsert_file_to_agent(agent_state.id, file_metadata_with_content, actor) for agent_state in agent_states)
1436
+ )
1437
+ # Flatten and log if any files were closed
1438
+ closed_files = [file for closed_list in all_closed_files for file in closed_list]
1439
+ if closed_files:
1440
+ logger.info(f"LRU eviction closed {len(closed_files)} files during bulk attach: {closed_files}")
1410
1441
 
1411
1442
  return agent_states
1412
1443
 
1413
1444
  async def insert_files_into_context_window(
1414
- self, agent_state: AgentState, texts: List[str], file_ids: List[str], file_names: List[str], actor: User
1445
+ self, agent_state: AgentState, file_metadata_with_content: List[FileMetadata], actor: User
1415
1446
  ) -> None:
1416
1447
  """
1417
1448
  Insert the uploaded documents into the context window of an agent
@@ -1419,15 +1450,14 @@ class SyncServer(Server):
1419
1450
  """
1420
1451
  logger.info(f"Inserting documents into context window for agent_state: {agent_state.id}")
1421
1452
 
1422
- if len(texts) != len(file_ids):
1423
- raise ValueError(f"Mismatch between number of texts ({len(texts)}) and file ids ({len(file_ids)})")
1424
-
1425
- await asyncio.gather(
1426
- *(
1427
- self._upsert_file_to_agent(agent_state.id, text, file_id, file_name, actor)
1428
- for text, file_id, file_name in zip(texts, file_ids, file_names)
1429
- )
1453
+ # Collect any files that were closed due to LRU eviction during bulk insert
1454
+ all_closed_files = await asyncio.gather(
1455
+ *(self._upsert_file_to_agent(agent_state.id, file_metadata, actor) for file_metadata in file_metadata_with_content)
1430
1456
  )
1457
+ # Flatten and log if any files were closed
1458
+ closed_files = [file for closed_list in all_closed_files for file in closed_list]
1459
+ if closed_files:
1460
+ logger.info(f"LRU eviction closed {len(closed_files)} files during bulk insert: {closed_files}")
1431
1461
 
1432
1462
  async def remove_file_from_context_windows(self, source_id: str, file_id: str, actor: User) -> None:
1433
1463
  """
@@ -1996,6 +2026,7 @@ class SyncServer(Server):
1996
2026
  message_manager=self.message_manager,
1997
2027
  agent_manager=self.agent_manager,
1998
2028
  block_manager=self.block_manager,
2029
+ job_manager=self.job_manager,
1999
2030
  passage_manager=self.passage_manager,
2000
2031
  actor=actor,
2001
2032
  sandbox_env_vars=tool_env_vars,