letta-nightly 0.8.0.dev20250606195656__py3-none-any.whl → 0.8.3.dev20250607000559__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 (105) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +16 -12
  3. letta/agents/base_agent.py +1 -1
  4. letta/agents/helpers.py +13 -2
  5. letta/agents/letta_agent.py +72 -34
  6. letta/agents/letta_agent_batch.py +1 -2
  7. letta/agents/voice_agent.py +19 -13
  8. letta/agents/voice_sleeptime_agent.py +23 -6
  9. letta/constants.py +18 -0
  10. letta/data_sources/__init__.py +0 -0
  11. letta/data_sources/redis_client.py +282 -0
  12. letta/errors.py +0 -4
  13. letta/functions/function_sets/files.py +58 -0
  14. letta/functions/schema_generator.py +18 -1
  15. letta/groups/sleeptime_multi_agent_v2.py +13 -3
  16. letta/helpers/datetime_helpers.py +47 -3
  17. letta/helpers/decorators.py +69 -0
  18. letta/{services/helpers/noop_helper.py → helpers/singleton.py} +5 -0
  19. letta/interfaces/anthropic_streaming_interface.py +43 -24
  20. letta/interfaces/openai_streaming_interface.py +21 -19
  21. letta/llm_api/anthropic.py +1 -1
  22. letta/llm_api/anthropic_client.py +30 -16
  23. letta/llm_api/google_vertex_client.py +1 -1
  24. letta/llm_api/helpers.py +36 -30
  25. letta/llm_api/llm_api_tools.py +1 -1
  26. letta/llm_api/llm_client_base.py +29 -1
  27. letta/llm_api/openai.py +1 -1
  28. letta/llm_api/openai_client.py +6 -8
  29. letta/local_llm/chat_completion_proxy.py +1 -1
  30. letta/memory.py +1 -1
  31. letta/orm/enums.py +1 -0
  32. letta/orm/file.py +80 -3
  33. letta/orm/files_agents.py +13 -0
  34. letta/orm/passage.py +2 -0
  35. letta/orm/sqlalchemy_base.py +34 -11
  36. letta/otel/__init__.py +0 -0
  37. letta/otel/context.py +25 -0
  38. letta/otel/events.py +0 -0
  39. letta/otel/metric_registry.py +122 -0
  40. letta/otel/metrics.py +66 -0
  41. letta/otel/resource.py +26 -0
  42. letta/{tracing.py → otel/tracing.py} +55 -78
  43. letta/plugins/README.md +22 -0
  44. letta/plugins/__init__.py +0 -0
  45. letta/plugins/defaults.py +11 -0
  46. letta/plugins/plugins.py +72 -0
  47. letta/schemas/enums.py +8 -0
  48. letta/schemas/file.py +12 -0
  49. letta/schemas/letta_request.py +6 -0
  50. letta/schemas/passage.py +1 -0
  51. letta/schemas/tool.py +4 -0
  52. letta/server/db.py +7 -7
  53. letta/server/rest_api/app.py +8 -6
  54. letta/server/rest_api/routers/v1/agents.py +46 -37
  55. letta/server/rest_api/routers/v1/groups.py +3 -3
  56. letta/server/rest_api/routers/v1/sources.py +26 -3
  57. letta/server/rest_api/routers/v1/tools.py +7 -2
  58. letta/server/rest_api/utils.py +9 -6
  59. letta/server/server.py +25 -13
  60. letta/services/agent_manager.py +186 -194
  61. letta/services/block_manager.py +1 -1
  62. letta/services/context_window_calculator/context_window_calculator.py +1 -1
  63. letta/services/context_window_calculator/token_counter.py +3 -2
  64. letta/services/file_processor/chunker/line_chunker.py +34 -0
  65. letta/services/file_processor/file_processor.py +43 -12
  66. letta/services/file_processor/parser/mistral_parser.py +11 -1
  67. letta/services/files_agents_manager.py +96 -7
  68. letta/services/group_manager.py +6 -6
  69. letta/services/helpers/agent_manager_helper.py +404 -3
  70. letta/services/identity_manager.py +1 -1
  71. letta/services/job_manager.py +1 -1
  72. letta/services/llm_batch_manager.py +1 -1
  73. letta/services/mcp/stdio_client.py +5 -1
  74. letta/services/mcp_manager.py +4 -4
  75. letta/services/message_manager.py +1 -1
  76. letta/services/organization_manager.py +1 -1
  77. letta/services/passage_manager.py +604 -19
  78. letta/services/per_agent_lock_manager.py +1 -1
  79. letta/services/provider_manager.py +1 -1
  80. letta/services/sandbox_config_manager.py +1 -1
  81. letta/services/source_manager.py +178 -19
  82. letta/services/step_manager.py +2 -2
  83. letta/services/summarizer/summarizer.py +1 -1
  84. letta/services/telemetry_manager.py +1 -1
  85. letta/services/tool_executor/builtin_tool_executor.py +117 -0
  86. letta/services/tool_executor/composio_tool_executor.py +53 -0
  87. letta/services/tool_executor/core_tool_executor.py +474 -0
  88. letta/services/tool_executor/files_tool_executor.py +138 -0
  89. letta/services/tool_executor/mcp_tool_executor.py +45 -0
  90. letta/services/tool_executor/multi_agent_tool_executor.py +123 -0
  91. letta/services/tool_executor/tool_execution_manager.py +34 -14
  92. letta/services/tool_executor/tool_execution_sandbox.py +1 -1
  93. letta/services/tool_executor/tool_executor.py +3 -802
  94. letta/services/tool_executor/tool_executor_base.py +43 -0
  95. letta/services/tool_manager.py +55 -59
  96. letta/services/tool_sandbox/e2b_sandbox.py +1 -1
  97. letta/services/tool_sandbox/local_sandbox.py +6 -3
  98. letta/services/user_manager.py +6 -3
  99. letta/settings.py +23 -2
  100. letta/utils.py +7 -2
  101. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/METADATA +4 -2
  102. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/RECORD +105 -83
  103. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/LICENSE +0 -0
  104. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/WHEEL +0 -0
  105. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/entry_points.txt +0 -0
@@ -1,27 +1,33 @@
1
1
  import datetime
2
2
  from typing import List, Literal, Optional
3
3
 
4
- from sqlalchemy import and_, asc, desc, or_, select
4
+ import numpy as np
5
+ from sqlalchemy import Select, and_, asc, desc, func, literal, or_, select, union_all
5
6
  from sqlalchemy.sql.expression import exists
6
7
 
7
8
  from letta import system
8
- from letta.constants import IN_CONTEXT_MEMORY_KEYWORD, STRUCTURED_OUTPUT_MODELS
9
+ from letta.constants import IN_CONTEXT_MEMORY_KEYWORD, MAX_EMBEDDING_DIM, STRUCTURED_OUTPUT_MODELS
10
+ from letta.embeddings import embedding_model
9
11
  from letta.helpers import ToolRulesSolver
10
12
  from letta.helpers.datetime_helpers import get_local_time, get_local_time_fast
13
+ from letta.orm import AgentPassage, SourcePassage, SourcesAgents
11
14
  from letta.orm.agent import Agent as AgentModel
12
15
  from letta.orm.agents_tags import AgentsTags
13
16
  from letta.orm.errors import NoResultFound
14
17
  from letta.orm.identity import Identity
18
+ from letta.orm.sqlite_functions import adapt_array
19
+ from letta.otel.tracing import trace_method
15
20
  from letta.prompts import gpt_system
16
21
  from letta.schemas.agent import AgentState, AgentType
22
+ from letta.schemas.embedding_config import EmbeddingConfig
17
23
  from letta.schemas.enums import MessageRole
18
24
  from letta.schemas.letta_message_content import TextContent
19
25
  from letta.schemas.memory import Memory
20
26
  from letta.schemas.message import Message, MessageCreate
21
27
  from letta.schemas.tool_rule import ToolRule
22
28
  from letta.schemas.user import User
29
+ from letta.settings import settings
23
30
  from letta.system import get_initial_boot_messages, get_login_event, package_function_response
24
- from letta.tracing import trace_method
25
31
 
26
32
 
27
33
  # Static methods
@@ -566,3 +572,398 @@ def _apply_filters(
566
572
  if base_template_id:
567
573
  query = query.where(AgentModel.base_template_id == base_template_id)
568
574
  return query
575
+
576
+
577
+ def build_passage_query(
578
+ actor: User,
579
+ agent_id: Optional[str] = None,
580
+ file_id: Optional[str] = None,
581
+ query_text: Optional[str] = None,
582
+ start_date: Optional[datetime] = None,
583
+ end_date: Optional[datetime] = None,
584
+ before: Optional[str] = None,
585
+ after: Optional[str] = None,
586
+ source_id: Optional[str] = None,
587
+ embed_query: bool = False,
588
+ ascending: bool = True,
589
+ embedding_config: Optional[EmbeddingConfig] = None,
590
+ agent_only: bool = False,
591
+ ) -> Select:
592
+ """Helper function to build the base passage query with all filters applied.
593
+ Supports both before and after pagination across merged source and agent passages.
594
+
595
+ Returns the query before any limit or count operations are applied.
596
+ """
597
+ embedded_text = None
598
+ if embed_query:
599
+ assert embedding_config is not None, "embedding_config must be specified for vector search"
600
+ assert query_text is not None, "query_text must be specified for vector search"
601
+ embedded_text = embedding_model(embedding_config).get_text_embedding(query_text)
602
+ embedded_text = np.array(embedded_text)
603
+ embedded_text = np.pad(embedded_text, (0, MAX_EMBEDDING_DIM - embedded_text.shape[0]), mode="constant").tolist()
604
+
605
+ # Start with base query for source passages
606
+ source_passages = None
607
+ if not agent_only: # Include source passages
608
+ if agent_id is not None:
609
+ source_passages = (
610
+ select(
611
+ SourcePassage.file_name,
612
+ SourcePassage.id,
613
+ SourcePassage.text,
614
+ SourcePassage.embedding_config,
615
+ SourcePassage.metadata_,
616
+ SourcePassage.embedding,
617
+ SourcePassage.created_at,
618
+ SourcePassage.updated_at,
619
+ SourcePassage.is_deleted,
620
+ SourcePassage._created_by_id,
621
+ SourcePassage._last_updated_by_id,
622
+ SourcePassage.organization_id,
623
+ SourcePassage.file_id,
624
+ SourcePassage.source_id,
625
+ literal(None).label("agent_id"),
626
+ )
627
+ .join(SourcesAgents, SourcesAgents.source_id == SourcePassage.source_id)
628
+ .where(SourcesAgents.agent_id == agent_id)
629
+ .where(SourcePassage.organization_id == actor.organization_id)
630
+ )
631
+ else:
632
+ source_passages = select(
633
+ SourcePassage.file_name,
634
+ SourcePassage.id,
635
+ SourcePassage.text,
636
+ SourcePassage.embedding_config,
637
+ SourcePassage.metadata_,
638
+ SourcePassage.embedding,
639
+ SourcePassage.created_at,
640
+ SourcePassage.updated_at,
641
+ SourcePassage.is_deleted,
642
+ SourcePassage._created_by_id,
643
+ SourcePassage._last_updated_by_id,
644
+ SourcePassage.organization_id,
645
+ SourcePassage.file_id,
646
+ SourcePassage.source_id,
647
+ literal(None).label("agent_id"),
648
+ ).where(SourcePassage.organization_id == actor.organization_id)
649
+
650
+ if source_id:
651
+ source_passages = source_passages.where(SourcePassage.source_id == source_id)
652
+ if file_id:
653
+ source_passages = source_passages.where(SourcePassage.file_id == file_id)
654
+
655
+ # Add agent passages query
656
+ agent_passages = None
657
+ if agent_id is not None:
658
+ agent_passages = (
659
+ select(
660
+ literal(None).label("file_name"),
661
+ AgentPassage.id,
662
+ AgentPassage.text,
663
+ AgentPassage.embedding_config,
664
+ AgentPassage.metadata_,
665
+ AgentPassage.embedding,
666
+ AgentPassage.created_at,
667
+ AgentPassage.updated_at,
668
+ AgentPassage.is_deleted,
669
+ AgentPassage._created_by_id,
670
+ AgentPassage._last_updated_by_id,
671
+ AgentPassage.organization_id,
672
+ literal(None).label("file_id"),
673
+ literal(None).label("source_id"),
674
+ AgentPassage.agent_id,
675
+ )
676
+ .where(AgentPassage.agent_id == agent_id)
677
+ .where(AgentPassage.organization_id == actor.organization_id)
678
+ )
679
+
680
+ # Combine queries
681
+ if source_passages is not None and agent_passages is not None:
682
+ combined_query = union_all(source_passages, agent_passages).cte("combined_passages")
683
+ elif agent_passages is not None:
684
+ combined_query = agent_passages.cte("combined_passages")
685
+ elif source_passages is not None:
686
+ combined_query = source_passages.cte("combined_passages")
687
+ else:
688
+ raise ValueError("No passages found")
689
+
690
+ # Build main query from combined CTE
691
+ main_query = select(combined_query)
692
+
693
+ # Apply filters
694
+ if start_date:
695
+ main_query = main_query.where(combined_query.c.created_at >= start_date)
696
+ if end_date:
697
+ main_query = main_query.where(combined_query.c.created_at <= end_date)
698
+ if source_id:
699
+ main_query = main_query.where(combined_query.c.source_id == source_id)
700
+ if file_id:
701
+ main_query = main_query.where(combined_query.c.file_id == file_id)
702
+
703
+ # Vector search
704
+ if embedded_text:
705
+ if settings.letta_pg_uri_no_default:
706
+ # PostgreSQL with pgvector
707
+ main_query = main_query.order_by(combined_query.c.embedding.cosine_distance(embedded_text).asc())
708
+ else:
709
+ # SQLite with custom vector type
710
+ query_embedding_binary = adapt_array(embedded_text)
711
+ main_query = main_query.order_by(
712
+ func.cosine_distance(combined_query.c.embedding, query_embedding_binary).asc(),
713
+ combined_query.c.created_at.asc() if ascending else combined_query.c.created_at.desc(),
714
+ combined_query.c.id.asc(),
715
+ )
716
+ else:
717
+ if query_text:
718
+ main_query = main_query.where(func.lower(combined_query.c.text).contains(func.lower(query_text)))
719
+
720
+ # Handle pagination
721
+ if before or after:
722
+ # Create reference CTEs
723
+ if before:
724
+ before_ref = select(combined_query.c.created_at, combined_query.c.id).where(combined_query.c.id == before).cte("before_ref")
725
+ if after:
726
+ after_ref = select(combined_query.c.created_at, combined_query.c.id).where(combined_query.c.id == after).cte("after_ref")
727
+
728
+ if before and after:
729
+ # Window-based query (get records between before and after)
730
+ main_query = main_query.where(
731
+ or_(
732
+ combined_query.c.created_at < select(before_ref.c.created_at).scalar_subquery(),
733
+ and_(
734
+ combined_query.c.created_at == select(before_ref.c.created_at).scalar_subquery(),
735
+ combined_query.c.id < select(before_ref.c.id).scalar_subquery(),
736
+ ),
737
+ )
738
+ )
739
+ main_query = main_query.where(
740
+ or_(
741
+ combined_query.c.created_at > select(after_ref.c.created_at).scalar_subquery(),
742
+ and_(
743
+ combined_query.c.created_at == select(after_ref.c.created_at).scalar_subquery(),
744
+ combined_query.c.id > select(after_ref.c.id).scalar_subquery(),
745
+ ),
746
+ )
747
+ )
748
+ else:
749
+ # Pure pagination (only before or only after)
750
+ if before:
751
+ main_query = main_query.where(
752
+ or_(
753
+ combined_query.c.created_at < select(before_ref.c.created_at).scalar_subquery(),
754
+ and_(
755
+ combined_query.c.created_at == select(before_ref.c.created_at).scalar_subquery(),
756
+ combined_query.c.id < select(before_ref.c.id).scalar_subquery(),
757
+ ),
758
+ )
759
+ )
760
+ if after:
761
+ main_query = main_query.where(
762
+ or_(
763
+ combined_query.c.created_at > select(after_ref.c.created_at).scalar_subquery(),
764
+ and_(
765
+ combined_query.c.created_at == select(after_ref.c.created_at).scalar_subquery(),
766
+ combined_query.c.id > select(after_ref.c.id).scalar_subquery(),
767
+ ),
768
+ )
769
+ )
770
+
771
+ # Add ordering if not already ordered by similarity
772
+ if not embed_query:
773
+ if ascending:
774
+ main_query = main_query.order_by(
775
+ combined_query.c.created_at.asc(),
776
+ combined_query.c.id.asc(),
777
+ )
778
+ else:
779
+ main_query = main_query.order_by(
780
+ combined_query.c.created_at.desc(),
781
+ combined_query.c.id.asc(),
782
+ )
783
+
784
+ return main_query
785
+
786
+
787
+ def build_source_passage_query(
788
+ actor: User,
789
+ agent_id: Optional[str] = None,
790
+ file_id: Optional[str] = None,
791
+ query_text: Optional[str] = None,
792
+ start_date: Optional[datetime] = None,
793
+ end_date: Optional[datetime] = None,
794
+ before: Optional[str] = None,
795
+ after: Optional[str] = None,
796
+ source_id: Optional[str] = None,
797
+ embed_query: bool = False,
798
+ ascending: bool = True,
799
+ embedding_config: Optional[EmbeddingConfig] = None,
800
+ ) -> Select:
801
+ """Build query for source passages with all filters applied."""
802
+
803
+ # Handle embedding for vector search
804
+ embedded_text = None
805
+ if embed_query:
806
+ assert embedding_config is not None, "embedding_config must be specified for vector search"
807
+ assert query_text is not None, "query_text must be specified for vector search"
808
+ embedded_text = embedding_model(embedding_config).get_text_embedding(query_text)
809
+ embedded_text = np.array(embedded_text)
810
+ embedded_text = np.pad(embedded_text, (0, MAX_EMBEDDING_DIM - embedded_text.shape[0]), mode="constant").tolist()
811
+
812
+ # Base query for source passages
813
+ query = select(SourcePassage).where(SourcePassage.organization_id == actor.organization_id)
814
+
815
+ # If agent_id is specified, join with SourcesAgents to get only passages linked to that agent
816
+ if agent_id is not None:
817
+ query = query.join(SourcesAgents, SourcesAgents.source_id == SourcePassage.source_id)
818
+ query = query.where(SourcesAgents.agent_id == agent_id)
819
+
820
+ # Apply filters
821
+ if source_id:
822
+ query = query.where(SourcePassage.source_id == source_id)
823
+ if file_id:
824
+ query = query.where(SourcePassage.file_id == file_id)
825
+ if start_date:
826
+ query = query.where(SourcePassage.created_at >= start_date)
827
+ if end_date:
828
+ query = query.where(SourcePassage.created_at <= end_date)
829
+
830
+ # Handle text search or vector search
831
+ if embedded_text:
832
+ if settings.letta_pg_uri_no_default:
833
+ # PostgreSQL with pgvector
834
+ query = query.order_by(SourcePassage.embedding.cosine_distance(embedded_text).asc())
835
+ else:
836
+ # SQLite with custom vector type
837
+ query_embedding_binary = adapt_array(embedded_text)
838
+ query = query.order_by(
839
+ func.cosine_distance(SourcePassage.embedding, query_embedding_binary).asc(),
840
+ SourcePassage.created_at.asc() if ascending else SourcePassage.created_at.desc(),
841
+ SourcePassage.id.asc(),
842
+ )
843
+ else:
844
+ if query_text:
845
+ query = query.where(func.lower(SourcePassage.text).contains(func.lower(query_text)))
846
+
847
+ # Handle pagination
848
+ if before or after:
849
+ if before:
850
+ # Get the reference record
851
+ before_subq = select(SourcePassage.created_at, SourcePassage.id).where(SourcePassage.id == before).subquery()
852
+ query = query.where(
853
+ or_(
854
+ SourcePassage.created_at < before_subq.c.created_at,
855
+ and_(
856
+ SourcePassage.created_at == before_subq.c.created_at,
857
+ SourcePassage.id < before_subq.c.id,
858
+ ),
859
+ )
860
+ )
861
+
862
+ if after:
863
+ # Get the reference record
864
+ after_subq = select(SourcePassage.created_at, SourcePassage.id).where(SourcePassage.id == after).subquery()
865
+ query = query.where(
866
+ or_(
867
+ SourcePassage.created_at > after_subq.c.created_at,
868
+ and_(
869
+ SourcePassage.created_at == after_subq.c.created_at,
870
+ SourcePassage.id > after_subq.c.id,
871
+ ),
872
+ )
873
+ )
874
+
875
+ # Apply ordering if not already ordered by similarity
876
+ if not embed_query:
877
+ if ascending:
878
+ query = query.order_by(SourcePassage.created_at.asc(), SourcePassage.id.asc())
879
+ else:
880
+ query = query.order_by(SourcePassage.created_at.desc(), SourcePassage.id.asc())
881
+
882
+ return query
883
+
884
+
885
+ def build_agent_passage_query(
886
+ actor: User,
887
+ agent_id: str, # Required for agent passages
888
+ query_text: Optional[str] = None,
889
+ start_date: Optional[datetime] = None,
890
+ end_date: Optional[datetime] = None,
891
+ before: Optional[str] = None,
892
+ after: Optional[str] = None,
893
+ embed_query: bool = False,
894
+ ascending: bool = True,
895
+ embedding_config: Optional[EmbeddingConfig] = None,
896
+ ) -> Select:
897
+ """Build query for agent passages with all filters applied."""
898
+
899
+ # Handle embedding for vector search
900
+ embedded_text = None
901
+ if embed_query:
902
+ assert embedding_config is not None, "embedding_config must be specified for vector search"
903
+ assert query_text is not None, "query_text must be specified for vector search"
904
+ embedded_text = embedding_model(embedding_config).get_text_embedding(query_text)
905
+ embedded_text = np.array(embedded_text)
906
+ embedded_text = np.pad(embedded_text, (0, MAX_EMBEDDING_DIM - embedded_text.shape[0]), mode="constant").tolist()
907
+
908
+ # Base query for agent passages
909
+ query = select(AgentPassage).where(AgentPassage.agent_id == agent_id, AgentPassage.organization_id == actor.organization_id)
910
+
911
+ # Apply filters
912
+ if start_date:
913
+ query = query.where(AgentPassage.created_at >= start_date)
914
+ if end_date:
915
+ query = query.where(AgentPassage.created_at <= end_date)
916
+
917
+ # Handle text search or vector search
918
+ if embedded_text:
919
+ if settings.letta_pg_uri_no_default:
920
+ # PostgreSQL with pgvector
921
+ query = query.order_by(AgentPassage.embedding.cosine_distance(embedded_text).asc())
922
+ else:
923
+ # SQLite with custom vector type
924
+ query_embedding_binary = adapt_array(embedded_text)
925
+ query = query.order_by(
926
+ func.cosine_distance(AgentPassage.embedding, query_embedding_binary).asc(),
927
+ AgentPassage.created_at.asc() if ascending else AgentPassage.created_at.desc(),
928
+ AgentPassage.id.asc(),
929
+ )
930
+ else:
931
+ if query_text:
932
+ query = query.where(func.lower(AgentPassage.text).contains(func.lower(query_text)))
933
+
934
+ # Handle pagination
935
+ if before or after:
936
+ if before:
937
+ # Get the reference record
938
+ before_subq = select(AgentPassage.created_at, AgentPassage.id).where(AgentPassage.id == before).subquery()
939
+ query = query.where(
940
+ or_(
941
+ AgentPassage.created_at < before_subq.c.created_at,
942
+ and_(
943
+ AgentPassage.created_at == before_subq.c.created_at,
944
+ AgentPassage.id < before_subq.c.id,
945
+ ),
946
+ )
947
+ )
948
+
949
+ if after:
950
+ # Get the reference record
951
+ after_subq = select(AgentPassage.created_at, AgentPassage.id).where(AgentPassage.id == after).subquery()
952
+ query = query.where(
953
+ or_(
954
+ AgentPassage.created_at > after_subq.c.created_at,
955
+ and_(
956
+ AgentPassage.created_at == after_subq.c.created_at,
957
+ AgentPassage.id > after_subq.c.id,
958
+ ),
959
+ )
960
+ )
961
+
962
+ # Apply ordering if not already ordered by similarity
963
+ if not embed_query:
964
+ if ascending:
965
+ query = query.order_by(AgentPassage.created_at.asc(), AgentPassage.id.asc())
966
+ else:
967
+ query = query.order_by(AgentPassage.created_at.desc(), AgentPassage.id.asc())
968
+
969
+ return query
@@ -7,11 +7,11 @@ from sqlalchemy.exc import NoResultFound
7
7
  from letta.orm.agent import Agent as AgentModel
8
8
  from letta.orm.block import Block as BlockModel
9
9
  from letta.orm.identity import Identity as IdentityModel
10
+ from letta.otel.tracing import trace_method
10
11
  from letta.schemas.identity import Identity as PydanticIdentity
11
12
  from letta.schemas.identity import IdentityCreate, IdentityProperty, IdentityType, IdentityUpdate, IdentityUpsert
12
13
  from letta.schemas.user import User as PydanticUser
13
14
  from letta.server.db import db_registry
14
- from letta.tracing import trace_method
15
15
  from letta.utils import enforce_types
16
16
 
17
17
 
@@ -14,6 +14,7 @@ from letta.orm.message import Message as MessageModel
14
14
  from letta.orm.sqlalchemy_base import AccessType
15
15
  from letta.orm.step import Step
16
16
  from letta.orm.step import Step as StepModel
17
+ from letta.otel.tracing import trace_method
17
18
  from letta.schemas.enums import JobStatus, MessageRole
18
19
  from letta.schemas.job import BatchJob as PydanticBatchJob
19
20
  from letta.schemas.job import Job as PydanticJob
@@ -25,7 +26,6 @@ from letta.schemas.step import Step as PydanticStep
25
26
  from letta.schemas.usage import LettaUsageStatistics
26
27
  from letta.schemas.user import User as PydanticUser
27
28
  from letta.server.db import db_registry
28
- from letta.tracing import trace_method
29
29
  from letta.utils import enforce_types
30
30
 
31
31
 
@@ -9,6 +9,7 @@ from letta.log import get_logger
9
9
  from letta.orm import Message as MessageModel
10
10
  from letta.orm.llm_batch_items import LLMBatchItem
11
11
  from letta.orm.llm_batch_job import LLMBatchJob
12
+ from letta.otel.tracing import trace_method
12
13
  from letta.schemas.agent import AgentStepState
13
14
  from letta.schemas.enums import AgentStepStatus, JobStatus, ProviderType
14
15
  from letta.schemas.llm_batch_job import LLMBatchItem as PydanticLLMBatchItem
@@ -17,7 +18,6 @@ from letta.schemas.llm_config import LLMConfig
17
18
  from letta.schemas.message import Message as PydanticMessage
18
19
  from letta.schemas.user import User as PydanticUser
19
20
  from letta.server.db import db_registry
20
- from letta.tracing import trace_method
21
21
  from letta.utils import enforce_types
22
22
 
23
23
  logger = get_logger(__name__)
@@ -11,7 +11,11 @@ logger = get_logger(__name__)
11
11
  # TODO: Get rid of Async prefix on this class name once we deprecate old sync code
12
12
  class AsyncStdioMCPClient(AsyncBaseMCPClient):
13
13
  async def _initialize_connection(self, server_config: StdioServerConfig) -> None:
14
- server_params = StdioServerParameters(command=server_config.command, args=server_config.args)
14
+
15
+ args = [arg.split() for arg in server_config.args]
16
+ # flatten
17
+ args = [arg for sublist in args for arg in sublist]
18
+ server_params = StdioServerParameters(command=server_config.command, args=args)
15
19
  stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
16
20
  self.stdio, self.write = stdio_transport
17
21
  self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
@@ -220,12 +220,12 @@ class MCPManager:
220
220
  # mcp_server.delete(session, actor=actor) # Re-raise other database-related errors
221
221
 
222
222
  @enforce_types
223
- def delete_mcp_server_by_id(self, mcp_server_id: str, actor: PydanticUser) -> None:
223
+ async def delete_mcp_server_by_id(self, mcp_server_id: str, actor: PydanticUser) -> None:
224
224
  """Delete a tool by its ID."""
225
- with db_registry.session() as session:
225
+ async with db_registry.async_session() as session:
226
226
  try:
227
- mcp_server = MCPServerModel.read(db_session=session, identifier=mcp_server_id, actor=actor)
228
- mcp_server.hard_delete(db_session=session, actor=actor)
227
+ mcp_server = await MCPServerModel.read_async(db_session=session, identifier=mcp_server_id, actor=actor)
228
+ await mcp_server.hard_delete_async(db_session=session, actor=actor)
229
229
  except NoResultFound:
230
230
  raise ValueError(f"MCP server with id {mcp_server_id} not found.")
231
231
 
@@ -7,13 +7,13 @@ from letta.log import get_logger
7
7
  from letta.orm.agent import Agent as AgentModel
8
8
  from letta.orm.errors import NoResultFound
9
9
  from letta.orm.message import Message as MessageModel
10
+ from letta.otel.tracing import trace_method
10
11
  from letta.schemas.enums import MessageRole
11
12
  from letta.schemas.letta_message import LettaMessageUpdateUnion
12
13
  from letta.schemas.message import Message as PydanticMessage
13
14
  from letta.schemas.message import MessageUpdate
14
15
  from letta.schemas.user import User as PydanticUser
15
16
  from letta.server.db import db_registry
16
- from letta.tracing import trace_method
17
17
  from letta.utils import enforce_types
18
18
 
19
19
  logger = get_logger(__name__)
@@ -2,10 +2,10 @@ from typing import List, Optional
2
2
 
3
3
  from letta.orm.errors import NoResultFound
4
4
  from letta.orm.organization import Organization as OrganizationModel
5
+ from letta.otel.tracing import trace_method
5
6
  from letta.schemas.organization import Organization as PydanticOrganization
6
7
  from letta.schemas.organization import OrganizationUpdate
7
8
  from letta.server.db import db_registry
8
- from letta.tracing import trace_method
9
9
  from letta.utils import enforce_types
10
10
 
11
11