letta-nightly 0.8.0.dev20250606195656__py3-none-any.whl → 0.8.2.dev20250606215616__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 (96) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +1 -1
  3. letta/agents/letta_agent.py +49 -29
  4. letta/agents/letta_agent_batch.py +1 -2
  5. letta/agents/voice_agent.py +19 -13
  6. letta/agents/voice_sleeptime_agent.py +11 -3
  7. letta/constants.py +18 -0
  8. letta/data_sources/__init__.py +0 -0
  9. letta/data_sources/redis_client.py +282 -0
  10. letta/errors.py +0 -4
  11. letta/functions/function_sets/files.py +58 -0
  12. letta/functions/schema_generator.py +18 -1
  13. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  14. letta/helpers/datetime_helpers.py +47 -3
  15. letta/helpers/decorators.py +69 -0
  16. letta/{services/helpers/noop_helper.py → helpers/singleton.py} +5 -0
  17. letta/interfaces/anthropic_streaming_interface.py +43 -24
  18. letta/interfaces/openai_streaming_interface.py +21 -19
  19. letta/llm_api/anthropic.py +1 -1
  20. letta/llm_api/anthropic_client.py +22 -14
  21. letta/llm_api/google_vertex_client.py +1 -1
  22. letta/llm_api/helpers.py +36 -30
  23. letta/llm_api/llm_api_tools.py +1 -1
  24. letta/llm_api/llm_client_base.py +29 -1
  25. letta/llm_api/openai.py +1 -1
  26. letta/llm_api/openai_client.py +6 -8
  27. letta/local_llm/chat_completion_proxy.py +1 -1
  28. letta/memory.py +1 -1
  29. letta/orm/enums.py +1 -0
  30. letta/orm/file.py +80 -3
  31. letta/orm/files_agents.py +13 -0
  32. letta/orm/sqlalchemy_base.py +34 -11
  33. letta/otel/__init__.py +0 -0
  34. letta/otel/context.py +25 -0
  35. letta/otel/events.py +0 -0
  36. letta/otel/metric_registry.py +122 -0
  37. letta/otel/metrics.py +66 -0
  38. letta/otel/resource.py +26 -0
  39. letta/{tracing.py → otel/tracing.py} +55 -78
  40. letta/plugins/README.md +22 -0
  41. letta/plugins/__init__.py +0 -0
  42. letta/plugins/defaults.py +11 -0
  43. letta/plugins/plugins.py +72 -0
  44. letta/schemas/enums.py +8 -0
  45. letta/schemas/file.py +12 -0
  46. letta/schemas/tool.py +4 -0
  47. letta/server/db.py +7 -7
  48. letta/server/rest_api/app.py +8 -6
  49. letta/server/rest_api/routers/v1/agents.py +37 -36
  50. letta/server/rest_api/routers/v1/groups.py +3 -3
  51. letta/server/rest_api/routers/v1/sources.py +26 -3
  52. letta/server/rest_api/utils.py +9 -6
  53. letta/server/server.py +18 -12
  54. letta/services/agent_manager.py +185 -193
  55. letta/services/block_manager.py +1 -1
  56. letta/services/context_window_calculator/token_counter.py +3 -2
  57. letta/services/file_processor/chunker/line_chunker.py +34 -0
  58. letta/services/file_processor/file_processor.py +40 -11
  59. letta/services/file_processor/parser/mistral_parser.py +11 -1
  60. letta/services/files_agents_manager.py +96 -7
  61. letta/services/group_manager.py +6 -6
  62. letta/services/helpers/agent_manager_helper.py +373 -3
  63. letta/services/identity_manager.py +1 -1
  64. letta/services/job_manager.py +1 -1
  65. letta/services/llm_batch_manager.py +1 -1
  66. letta/services/message_manager.py +1 -1
  67. letta/services/organization_manager.py +1 -1
  68. letta/services/passage_manager.py +1 -1
  69. letta/services/per_agent_lock_manager.py +1 -1
  70. letta/services/provider_manager.py +1 -1
  71. letta/services/sandbox_config_manager.py +1 -1
  72. letta/services/source_manager.py +178 -19
  73. letta/services/step_manager.py +2 -2
  74. letta/services/summarizer/summarizer.py +1 -1
  75. letta/services/telemetry_manager.py +1 -1
  76. letta/services/tool_executor/builtin_tool_executor.py +117 -0
  77. letta/services/tool_executor/composio_tool_executor.py +53 -0
  78. letta/services/tool_executor/core_tool_executor.py +474 -0
  79. letta/services/tool_executor/files_tool_executor.py +131 -0
  80. letta/services/tool_executor/mcp_tool_executor.py +45 -0
  81. letta/services/tool_executor/multi_agent_tool_executor.py +123 -0
  82. letta/services/tool_executor/tool_execution_manager.py +34 -14
  83. letta/services/tool_executor/tool_execution_sandbox.py +1 -1
  84. letta/services/tool_executor/tool_executor.py +3 -802
  85. letta/services/tool_executor/tool_executor_base.py +43 -0
  86. letta/services/tool_manager.py +55 -59
  87. letta/services/tool_sandbox/e2b_sandbox.py +1 -1
  88. letta/services/tool_sandbox/local_sandbox.py +6 -3
  89. letta/services/user_manager.py +6 -3
  90. letta/settings.py +21 -1
  91. letta/utils.py +7 -2
  92. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/METADATA +4 -2
  93. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/RECORD +96 -74
  94. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/LICENSE +0 -0
  95. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/WHEEL +0 -0
  96. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/entry_points.txt +0 -0
letta/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import os
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.8.2"
4
4
 
5
5
  if os.environ.get("LETTA_VERSION"):
6
6
  __version__ = os.environ["LETTA_VERSION"]
letta/agent.py CHANGED
@@ -41,6 +41,7 @@ from letta.log import get_logger
41
41
  from letta.memory import summarize_messages
42
42
  from letta.orm import User
43
43
  from letta.orm.enums import ToolType
44
+ from letta.otel.tracing import log_event, trace_method
44
45
  from letta.schemas.agent import AgentState, AgentStepResponse, UpdateAgent, get_prompt_template_for_agent_type
45
46
  from letta.schemas.block import BlockUpdate
46
47
  from letta.schemas.embedding_config import EmbeddingConfig
@@ -72,7 +73,6 @@ from letta.services.tool_manager import ToolManager
72
73
  from letta.settings import settings, summarizer_settings, model_settings
73
74
  from letta.streaming_interface import StreamingRefreshCLIInterface
74
75
  from letta.system import get_heartbeat, get_token_limit_warning, package_function_response, package_summarize_message, package_user_message
75
- from letta.tracing import log_event, trace_method
76
76
  from letta.utils import count_tokens, get_friendly_error_msg, get_tool_call_id, log_telemetry, parse_json, validate_function_response
77
77
 
78
78
  logger = get_logger(__name__)
@@ -14,9 +14,9 @@ from letta.agents.helpers import (
14
14
  _prepare_in_context_messages_no_persist_async,
15
15
  generate_step_id,
16
16
  )
17
- from letta.errors import LLMContextWindowExceededError
17
+ from letta.errors import ContextWindowExceededError
18
18
  from letta.helpers import ToolRulesSolver
19
- from letta.helpers.datetime_helpers import get_utc_timestamp_ns
19
+ from letta.helpers.datetime_helpers import AsyncTimer, get_utc_timestamp_ns, ns_to_ms
20
20
  from letta.helpers.tool_execution_helper import enable_strict_mode
21
21
  from letta.interfaces.anthropic_streaming_interface import AnthropicStreamingInterface
22
22
  from letta.interfaces.openai_streaming_interface import OpenAIStreamingInterface
@@ -25,6 +25,9 @@ from letta.llm_api.llm_client_base import LLMClientBase
25
25
  from letta.local_llm.constants import INNER_THOUGHTS_KWARG
26
26
  from letta.log import get_logger
27
27
  from letta.orm.enums import ToolType
28
+ from letta.otel.context import get_ctx_attributes
29
+ from letta.otel.metric_registry import MetricRegistry
30
+ from letta.otel.tracing import log_event, trace_method, tracer
28
31
  from letta.schemas.agent import AgentState
29
32
  from letta.schemas.enums import MessageRole, MessageStreamStatus
30
33
  from letta.schemas.letta_message_content import OmittedReasoningContent, ReasoningContent, RedactedReasoningContent, TextContent
@@ -48,7 +51,7 @@ from letta.services.telemetry_manager import NoopTelemetryManager, TelemetryMana
48
51
  from letta.services.tool_executor.tool_execution_manager import ToolExecutionManager
49
52
  from letta.settings import model_settings
50
53
  from letta.system import package_function_response
51
- from letta.tracing import log_event, trace_method, tracer
54
+ from letta.types import JsonDict
52
55
  from letta.utils import log_telemetry, validate_function_response
53
56
 
54
57
  logger = get_logger(__name__)
@@ -178,7 +181,7 @@ class LettaAgent(BaseAgent):
178
181
  # log llm request time
179
182
  now = get_utc_timestamp_ns()
180
183
  llm_request_ns = now - step_start
181
- agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": llm_request_ns // 1_000_000})
184
+ agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": ns_to_ms(llm_request_ns)})
182
185
 
183
186
  response = llm_client.convert_response_to_chat_completion(response_data, in_context_messages, agent_state.llm_config)
184
187
 
@@ -210,7 +213,7 @@ class LettaAgent(BaseAgent):
210
213
  # log LLM request time
211
214
  now = get_utc_timestamp_ns()
212
215
  llm_request_ns = now - step_start
213
- agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": llm_request_ns // 1_000_000})
216
+ agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": ns_to_ms(llm_request_ns)})
214
217
 
215
218
  persisted_messages, should_continue = await self._handle_ai_response(
216
219
  tool_call,
@@ -227,7 +230,7 @@ class LettaAgent(BaseAgent):
227
230
  # log step time
228
231
  now = get_utc_timestamp_ns()
229
232
  step_ns = now - step_start
230
- agent_step_span.add_event(name="step_ms", attributes={"duration_ms": step_ns // 1_000_000})
233
+ agent_step_span.add_event(name="step_ms", attributes={"duration_ms": ns_to_ms(step_ns)})
231
234
  agent_step_span.end()
232
235
 
233
236
  # Log LLM Trace
@@ -267,7 +270,7 @@ class LettaAgent(BaseAgent):
267
270
  if request_start_timestamp_ns:
268
271
  now = get_utc_timestamp_ns()
269
272
  request_ns = now - request_start_timestamp_ns
270
- request_span.add_event(name="letta_request_ms", attributes={"duration_ms": request_ns // 1_000_000})
273
+ request_span.add_event(name="letta_request_ms", attributes={"duration_ms": ns_to_ms(request_ns)})
271
274
  request_span.end()
272
275
 
273
276
  # Return back usage
@@ -321,7 +324,7 @@ class LettaAgent(BaseAgent):
321
324
  # log LLM request time
322
325
  now = get_utc_timestamp_ns()
323
326
  llm_request_ns = now - step_start
324
- agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": llm_request_ns // 1_000_000})
327
+ agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": ns_to_ms(llm_request_ns)})
325
328
 
326
329
  # TODO: add run_id
327
330
  usage.step_count += 1
@@ -363,7 +366,7 @@ class LettaAgent(BaseAgent):
363
366
  # log step time
364
367
  now = get_utc_timestamp_ns()
365
368
  step_ns = now - step_start
366
- agent_step_span.add_event(name="step_ms", attributes={"duration_ms": step_ns // 1_000_000})
369
+ agent_step_span.add_event(name="step_ms", attributes={"duration_ms": ns_to_ms(step_ns)})
367
370
  agent_step_span.end()
368
371
 
369
372
  # Log LLM Trace
@@ -384,7 +387,7 @@ class LettaAgent(BaseAgent):
384
387
  if request_start_timestamp_ns:
385
388
  now = get_utc_timestamp_ns()
386
389
  request_ns = now - request_start_timestamp_ns
387
- request_span.add_event(name="request_ms", attributes={"duration_ms": request_ns // 1_000_000})
390
+ request_span.add_event(name="request_ms", attributes={"duration_ms": ns_to_ms(request_ns)})
388
391
  request_span.end()
389
392
 
390
393
  # Extend the in context message ids
@@ -480,7 +483,7 @@ class LettaAgent(BaseAgent):
480
483
  if first_chunk and request_span is not None:
481
484
  now = get_utc_timestamp_ns()
482
485
  ttft_ns = now - request_start_timestamp_ns
483
- request_span.add_event(name="time_to_first_token_ms", attributes={"ttft_ms": ttft_ns // 1_000_000})
486
+ request_span.add_event(name="time_to_first_token_ms", attributes={"ttft_ms": ns_to_ms(ttft_ns)})
484
487
  first_chunk = False
485
488
 
486
489
  yield f"data: {chunk.model_dump_json()}\n\n"
@@ -490,6 +493,9 @@ class LettaAgent(BaseAgent):
490
493
  usage.completion_tokens += interface.output_tokens
491
494
  usage.prompt_tokens += interface.input_tokens
492
495
  usage.total_tokens += interface.input_tokens + interface.output_tokens
496
+ MetricRegistry().message_output_tokens.record(
497
+ interface.output_tokens, dict(get_ctx_attributes(), **{"model.name": agent_state.llm_config.model})
498
+ )
493
499
 
494
500
  # Persist input messages if not already
495
501
  # Special strategy to lower TTFT
@@ -500,7 +506,7 @@ class LettaAgent(BaseAgent):
500
506
  # log LLM request time
501
507
  now = get_utc_timestamp_ns()
502
508
  llm_request_ns = now - step_start
503
- agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": llm_request_ns // 1_000_000})
509
+ agent_step_span.add_event(name="llm_request_ms", attributes={"duration_ms": ns_to_ms(llm_request_ns)})
504
510
 
505
511
  # Process resulting stream content
506
512
  tool_call = interface.get_tool_call_object()
@@ -515,8 +521,7 @@ class LettaAgent(BaseAgent):
515
521
  total_tokens=interface.input_tokens + interface.output_tokens,
516
522
  ),
517
523
  reasoning_content=reasoning_content,
518
- pre_computed_assistant_message_id=interface.letta_assistant_message_id,
519
- pre_computed_tool_message_id=interface.letta_tool_message_id,
524
+ pre_computed_assistant_message_id=interface.letta_message_id,
520
525
  step_id=step_id,
521
526
  agent_step_span=agent_step_span,
522
527
  )
@@ -526,7 +531,7 @@ class LettaAgent(BaseAgent):
526
531
  # log total step time
527
532
  now = get_utc_timestamp_ns()
528
533
  step_ns = now - step_start
529
- agent_step_span.add_event(name="step_ms", attributes={"duration_ms": step_ns // 1_000_000})
534
+ agent_step_span.add_event(name="step_ms", attributes={"duration_ms": ns_to_ms(step_ns)})
530
535
  agent_step_span.end()
531
536
 
532
537
  # TODO (cliandy): the stream POST request span has ended at this point, we should tie this to the stream
@@ -556,8 +561,8 @@ class LettaAgent(BaseAgent):
556
561
  ),
557
562
  )
558
563
 
559
- if not use_assistant_message or should_continue:
560
- tool_return = [msg for msg in persisted_messages if msg.role == "tool"][-1].to_letta_messages()[0]
564
+ tool_return = [msg for msg in persisted_messages if msg.role == "tool"][-1].to_letta_messages()[0]
565
+ if not (use_assistant_message and tool_return.name == "send_message"):
561
566
  yield f"data: {tool_return.model_dump_json()}\n\n"
562
567
 
563
568
  if not should_continue:
@@ -577,7 +582,7 @@ class LettaAgent(BaseAgent):
577
582
  if request_start_timestamp_ns:
578
583
  now = get_utc_timestamp_ns()
579
584
  request_ns = now - request_start_timestamp_ns
580
- request_span.add_event(name="letta_request_ms", attributes={"duration_ms": request_ns // 1_000_000})
585
+ request_span.add_event(name="letta_request_ms", attributes={"duration_ms": ns_to_ms(request_ns)})
581
586
  request_span.end()
582
587
 
583
588
  # TODO: Also yield out a letta usage stats SSE
@@ -604,10 +609,16 @@ class LettaAgent(BaseAgent):
604
609
  )
605
610
  log_event("agent.stream_no_tokens.llm_request.created")
606
611
 
612
+ async with AsyncTimer() as timer:
613
+ response = await llm_client.request_async(request_data, agent_state.llm_config)
614
+ MetricRegistry().llm_execution_time_ms_histogram.record(
615
+ timer.elapsed_ms,
616
+ dict(get_ctx_attributes(), **{"model.name": agent_state.llm_config.model}),
617
+ )
607
618
  # Attempt LLM request
608
619
  return (
609
620
  request_data,
610
- await llm_client.request_async(request_data, agent_state.llm_config),
621
+ response,
611
622
  current_in_context_messages,
612
623
  new_in_context_messages,
613
624
  )
@@ -654,9 +665,7 @@ class LettaAgent(BaseAgent):
654
665
  if first_chunk and ttft_span is not None:
655
666
  provider_request_start_timestamp_ns = get_utc_timestamp_ns()
656
667
  provider_req_start_ns = provider_request_start_timestamp_ns - request_start_timestamp_ns
657
- ttft_span.add_event(
658
- name="provider_req_start_ns", attributes={"provider_req_start_ms": provider_req_start_ns // 1_000_000}
659
- )
668
+ ttft_span.add_event(name="provider_req_start_ns", attributes={"provider_req_start_ms": ns_to_ms(provider_req_start_ns)})
660
669
 
661
670
  # Attempt LLM request
662
671
  return (
@@ -692,7 +701,7 @@ class LettaAgent(BaseAgent):
692
701
  llm_config: LLMConfig,
693
702
  force: bool,
694
703
  ) -> List[Message]:
695
- if isinstance(e, LLMContextWindowExceededError):
704
+ if isinstance(e, ContextWindowExceededError):
696
705
  return await self._rebuild_context_window(
697
706
  in_context_messages=in_context_messages, new_letta_messages=new_letta_messages, llm_config=llm_config, force=force
698
707
  )
@@ -775,6 +784,7 @@ class LettaAgent(BaseAgent):
775
784
  ToolType.LETTA_SLEEPTIME_CORE,
776
785
  ToolType.LETTA_VOICE_SLEEPTIME_CORE,
777
786
  ToolType.LETTA_BUILTIN,
787
+ ToolType.LETTA_FILES_CORE,
778
788
  ToolType.EXTERNAL_COMPOSIO,
779
789
  ToolType.EXTERNAL_MCP,
780
790
  }
@@ -810,7 +820,6 @@ class LettaAgent(BaseAgent):
810
820
  usage: UsageStatistics,
811
821
  reasoning_content: Optional[List[Union[TextContent, ReasoningContent, RedactedReasoningContent, OmittedReasoningContent]]] = None,
812
822
  pre_computed_assistant_message_id: Optional[str] = None,
813
- pre_computed_tool_message_id: Optional[str] = None,
814
823
  step_id: str | None = None,
815
824
  new_in_context_messages: Optional[List[Message]] = None,
816
825
  agent_step_span: Optional["Span"] = None,
@@ -822,6 +831,9 @@ class LettaAgent(BaseAgent):
822
831
  """
823
832
  tool_call_name = tool_call.function.name
824
833
  tool_call_args_str = tool_call.function.arguments
834
+ # Temp hack to gracefully handle parallel tool calling attempt, only take first one
835
+ if "}{" in tool_call_args_str:
836
+ tool_call_args_str = tool_call_args_str.split("}{", 1)[0] + "}"
825
837
 
826
838
  try:
827
839
  tool_args = json.loads(tool_call_args_str)
@@ -859,6 +871,7 @@ class LettaAgent(BaseAgent):
859
871
  tool_args=tool_args,
860
872
  agent_state=agent_state,
861
873
  agent_step_span=agent_step_span,
874
+ step_id=step_id,
862
875
  )
863
876
  log_telemetry(
864
877
  self.logger, "_handle_ai_response execute tool finish", tool_execution_result=tool_execution_result, tool_call_id=tool_call_id
@@ -926,7 +939,6 @@ class LettaAgent(BaseAgent):
926
939
  add_heartbeat_request_system_message=continue_stepping,
927
940
  reasoning_content=reasoning_content,
928
941
  pre_computed_assistant_message_id=pre_computed_assistant_message_id,
929
- pre_computed_tool_message_id=pre_computed_tool_message_id,
930
942
  step_id=logged_step.id if logged_step else None, # TODO (cliandy): eventually move over other agent loops
931
943
  )
932
944
 
@@ -937,10 +949,15 @@ class LettaAgent(BaseAgent):
937
949
 
938
950
  @trace_method
939
951
  async def _execute_tool(
940
- self, tool_name: str, tool_args: dict, agent_state: AgentState, agent_step_span: Optional["Span"] = None
952
+ self,
953
+ tool_name: str,
954
+ tool_args: JsonDict,
955
+ agent_state: AgentState,
956
+ agent_step_span: Optional["Span"] = None,
957
+ step_id: str | None = None,
941
958
  ) -> "ToolExecutionResult":
942
959
  """
943
- Executes a tool and returns (result, success_flag).
960
+ Executes a tool and returns the ToolExecutionResult.
944
961
  """
945
962
  from letta.schemas.tool_execution_result import ToolExecutionResult
946
963
 
@@ -972,7 +989,10 @@ class LettaAgent(BaseAgent):
972
989
  # TODO: Integrate sandbox result
973
990
  log_event(name=f"start_{tool_name}_execution", attributes=tool_args)
974
991
  tool_execution_result = await tool_execution_manager.execute_tool_async(
975
- function_name=tool_name, function_args=tool_args, tool=target_tool
992
+ function_name=tool_name,
993
+ function_args=tool_args,
994
+ tool=target_tool,
995
+ step_id=step_id,
976
996
  )
977
997
  if agent_step_span:
978
998
  end_time = get_utc_timestamp_ns()
@@ -980,7 +1000,7 @@ class LettaAgent(BaseAgent):
980
1000
  name="tool_execution_completed",
981
1001
  attributes={
982
1002
  "tool_name": target_tool.name,
983
- "duration_ms": (end_time - start_time) // 1_000_000,
1003
+ "duration_ms": ns_to_ms((end_time - start_time)),
984
1004
  "success": tool_execution_result.success_flag,
985
1005
  "tool_type": target_tool.tool_type,
986
1006
  "tool_id": target_tool.id,
@@ -16,6 +16,7 @@ from letta.llm_api.llm_client import LLMClient
16
16
  from letta.local_llm.constants import INNER_THOUGHTS_KWARG
17
17
  from letta.log import get_logger
18
18
  from letta.orm.enums import ToolType
19
+ from letta.otel.tracing import log_event, trace_method
19
20
  from letta.schemas.agent import AgentState, AgentStepState
20
21
  from letta.schemas.enums import AgentStepStatus, JobStatus, MessageStreamStatus, ProviderType
21
22
  from letta.schemas.job import JobUpdate
@@ -39,7 +40,6 @@ from letta.services.passage_manager import PassageManager
39
40
  from letta.services.sandbox_config_manager import SandboxConfigManager
40
41
  from letta.services.tool_executor.tool_execution_manager import ToolExecutionManager
41
42
  from letta.settings import tool_settings
42
- from letta.tracing import log_event, trace_method
43
43
 
44
44
  logger = get_logger(__name__)
45
45
 
@@ -551,7 +551,6 @@ class LettaAgentBatch(BaseAgent):
551
551
  add_heartbeat_request_system_message=False,
552
552
  reasoning_content=reasoning_content,
553
553
  pre_computed_assistant_message_id=None,
554
- pre_computed_tool_message_id=None,
555
554
  llm_batch_item_id=llm_batch_item_id,
556
555
  )
557
556
 
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import json
2
3
  import uuid
3
4
  from datetime import datetime, timedelta, timezone
@@ -81,8 +82,8 @@ class VoiceAgent(BaseAgent):
81
82
  self.summary_block_label = "human"
82
83
 
83
84
  # Cached archival memory/message size
84
- self.num_messages = self.message_manager.size(actor=self.actor, agent_id=agent_id)
85
- self.num_archival_memories = self.passage_manager.size(actor=self.actor, agent_id=agent_id)
85
+ self.num_messages = None
86
+ self.num_archival_memories = None
86
87
 
87
88
  def init_summarizer(self, agent_state: AgentState) -> Summarizer:
88
89
  if not agent_state.multi_agent_group:
@@ -118,13 +119,12 @@ class VoiceAgent(BaseAgent):
118
119
  Main streaming loop that yields partial tokens.
119
120
  Whenever we detect a tool call, we yield from _handle_ai_response as well.
120
121
  """
121
- print("CALL STREAM")
122
122
  if len(input_messages) != 1 or input_messages[0].role != MessageRole.user:
123
123
  raise ValueError(f"Voice Agent was invoked with multiple input messages or message did not have role `user`: {input_messages}")
124
124
 
125
125
  user_query = input_messages[0].content[0].text
126
126
 
127
- agent_state = self.agent_manager.get_agent_by_id(self.agent_id, actor=self.actor)
127
+ agent_state = await self.agent_manager.get_agent_by_id_async(self.agent_id, actor=self.actor)
128
128
 
129
129
  # TODO: Refactor this so it uses our in-house clients
130
130
  # TODO: For now, piggyback off of OpenAI client for ease
@@ -140,7 +140,7 @@ class VoiceAgent(BaseAgent):
140
140
 
141
141
  summarizer = self.init_summarizer(agent_state=agent_state)
142
142
 
143
- in_context_messages = self.message_manager.get_messages_by_ids(message_ids=agent_state.message_ids, actor=self.actor)
143
+ in_context_messages = await self.message_manager.get_messages_by_ids_async(message_ids=agent_state.message_ids, actor=self.actor)
144
144
  memory_edit_timestamp = get_utc_time()
145
145
  in_context_messages[0].content[0].text = compile_system_message(
146
146
  system_prompt=agent_state.system,
@@ -183,10 +183,6 @@ class VoiceAgent(BaseAgent):
183
183
  # Rebuild context window if desired
184
184
  await self._rebuild_context_window(summarizer, in_context_messages, letta_message_db_queue)
185
185
 
186
- # TODO: This may be out of sync, if in between steps users add files
187
- self.num_messages = self.message_manager.size(actor=self.actor, agent_id=agent_state.id)
188
- self.num_archival_memories = self.passage_manager.size(actor=self.actor, agent_id=agent_state.id)
189
-
190
186
  yield "data: [DONE]\n\n"
191
187
 
192
188
  async def _handle_ai_response(
@@ -286,14 +282,14 @@ class VoiceAgent(BaseAgent):
286
282
  async def _rebuild_context_window(
287
283
  self, summarizer: Summarizer, in_context_messages: List[Message], letta_message_db_queue: List[Message]
288
284
  ) -> None:
289
- new_letta_messages = self.message_manager.create_many_messages(letta_message_db_queue, actor=self.actor)
285
+ new_letta_messages = await self.message_manager.create_many_messages_async(letta_message_db_queue, actor=self.actor)
290
286
 
291
287
  # TODO: Make this more general and configurable, less brittle
292
288
  new_in_context_messages, updated = summarizer.summarize(
293
289
  in_context_messages=in_context_messages, new_letta_messages=new_letta_messages
294
290
  )
295
291
 
296
- self.agent_manager.set_in_context_messages(
292
+ await self.agent_manager.set_in_context_messages_async(
297
293
  agent_id=self.agent_id, message_ids=[m.id for m in new_in_context_messages], actor=self.actor
298
294
  )
299
295
 
@@ -301,9 +297,19 @@ class VoiceAgent(BaseAgent):
301
297
  self,
302
298
  in_context_messages: List[Message],
303
299
  agent_state: AgentState,
304
- num_messages: int | None = None,
305
- num_archival_memories: int | None = None,
306
300
  ) -> List[Message]:
301
+ self.num_messages, self.num_archival_memories = await asyncio.gather(
302
+ (
303
+ self.message_manager.size_async(actor=self.actor, agent_id=agent_state.id)
304
+ if self.num_messages is None
305
+ else asyncio.sleep(0, result=self.num_messages)
306
+ ),
307
+ (
308
+ self.passage_manager.size_async(actor=self.actor, agent_id=agent_state.id)
309
+ if self.num_archival_memories is None
310
+ else asyncio.sleep(0, result=self.num_archival_memories)
311
+ ),
312
+ )
307
313
  return await super()._rebuild_memory_async(
308
314
  in_context_messages, agent_state, num_messages=self.num_messages, num_archival_memories=self.num_archival_memories
309
315
  )
@@ -3,6 +3,7 @@ from typing import AsyncGenerator, List, Optional, Tuple, Union
3
3
  from letta.agents.helpers import _create_letta_response, serialize_message_history
4
4
  from letta.agents.letta_agent import LettaAgent
5
5
  from letta.orm.enums import ToolType
6
+ from letta.otel.tracing import trace_method
6
7
  from letta.schemas.agent import AgentState
7
8
  from letta.schemas.block import BlockUpdate
8
9
  from letta.schemas.enums import MessageStreamStatus
@@ -17,7 +18,7 @@ from letta.services.message_manager import MessageManager
17
18
  from letta.services.passage_manager import PassageManager
18
19
  from letta.services.summarizer.enums import SummarizationMode
19
20
  from letta.services.summarizer.summarizer import Summarizer
20
- from letta.tracing import trace_method
21
+ from letta.types import JsonDict
21
22
 
22
23
 
23
24
  class VoiceSleeptimeAgent(LettaAgent):
@@ -89,9 +90,16 @@ class VoiceSleeptimeAgent(LettaAgent):
89
90
  )
90
91
 
91
92
  @trace_method
92
- async def _execute_tool(self, tool_name: str, tool_args: dict, agent_state: AgentState, agent_step_span: Optional["Span"] = None):
93
+ async def _execute_tool(
94
+ self,
95
+ tool_name: str,
96
+ tool_args: JsonDict,
97
+ agent_state: AgentState,
98
+ agent_step_span: Optional["Span"] = None,
99
+ step_id: str | None = None,
100
+ ) -> "ToolExecutionResult":
93
101
  """
94
- Executes a tool and returns (result, success_flag).
102
+ Executes a tool and returns the ToolExecutionResult
95
103
  """
96
104
  from letta.schemas.tool_execution_result import ToolExecutionResult
97
105
 
letta/constants.py CHANGED
@@ -21,6 +21,15 @@ LETTA_CORE_TOOL_MODULE_NAME = "letta.functions.function_sets.base"
21
21
  LETTA_MULTI_AGENT_TOOL_MODULE_NAME = "letta.functions.function_sets.multi_agent"
22
22
  LETTA_VOICE_TOOL_MODULE_NAME = "letta.functions.function_sets.voice"
23
23
  LETTA_BUILTIN_TOOL_MODULE_NAME = "letta.functions.function_sets.builtin"
24
+ LETTA_FILES_TOOL_MODULE_NAME = "letta.functions.function_sets.files"
25
+
26
+ LETTA_TOOL_MODULE_NAMES = [
27
+ LETTA_CORE_TOOL_MODULE_NAME,
28
+ LETTA_MULTI_AGENT_TOOL_MODULE_NAME,
29
+ LETTA_VOICE_TOOL_MODULE_NAME,
30
+ LETTA_BUILTIN_TOOL_MODULE_NAME,
31
+ LETTA_FILES_TOOL_MODULE_NAME,
32
+ ]
24
33
 
25
34
 
26
35
  # String in the error message for when the context window is too large
@@ -112,6 +121,9 @@ MEMORY_TOOLS_LINE_NUMBER_PREFIX_REGEX = re.compile(
112
121
  # Built in tools
113
122
  BUILTIN_TOOLS = ["run_code", "web_search"]
114
123
 
124
+ # Built in tools
125
+ FILES_TOOLS = ["open_file", "close_file", "grep", "search_files"]
126
+
115
127
  # Set of all built-in Letta tools
116
128
  LETTA_TOOL_SET = set(
117
129
  BASE_TOOLS
@@ -121,6 +133,7 @@ LETTA_TOOL_SET = set(
121
133
  + BASE_VOICE_SLEEPTIME_TOOLS
122
134
  + BASE_VOICE_SLEEPTIME_CHAT_TOOLS
123
135
  + BUILTIN_TOOLS
136
+ + FILES_TOOLS
124
137
  )
125
138
 
126
139
 
@@ -294,6 +307,7 @@ CORE_MEMORY_SOURCE_CHAR_LIMIT: int = 5000
294
307
  # Function return limits
295
308
  FUNCTION_RETURN_CHAR_LIMIT = 6000 # ~300 words
296
309
  BASE_FUNCTION_RETURN_CHAR_LIMIT = 1000000 # very high (we rely on implementation)
310
+ FILE_IS_TRUNCATED_WARNING = "# NOTE: This block is truncated, use functions to view the full content."
297
311
 
298
312
  MAX_PAUSE_HEARTBEATS = 360 # in min
299
313
 
@@ -316,3 +330,7 @@ RESERVED_FILENAMES = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "LPT1", "LPT2"
316
330
  WEB_SEARCH_CLIP_CONTENT = False
317
331
  WEB_SEARCH_INCLUDE_SCORE = False
318
332
  WEB_SEARCH_SEPARATOR = "\n" + "-" * 40 + "\n"
333
+
334
+ REDIS_INCLUDE = "INCLUDE"
335
+ REDIS_EXCLUDE = "EXCLUDE"
336
+ REDIS_SET_DEFAULT_VAL = "None"
File without changes