letta-nightly 0.6.14.dev20250123041709__py3-none-any.whl → 0.6.15.dev20250124054224__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 (59) hide show
  1. letta/__init__.py +1 -1
  2. letta/client/client.py +144 -68
  3. letta/client/streaming.py +1 -1
  4. letta/functions/function_sets/extras.py +8 -3
  5. letta/functions/function_sets/multi_agent.py +1 -1
  6. letta/functions/helpers.py +2 -2
  7. letta/llm_api/llm_api_tools.py +2 -2
  8. letta/llm_api/openai.py +30 -138
  9. letta/memory.py +4 -4
  10. letta/offline_memory_agent.py +10 -10
  11. letta/orm/agent.py +10 -2
  12. letta/orm/block.py +14 -3
  13. letta/orm/job.py +2 -1
  14. letta/orm/message.py +12 -1
  15. letta/orm/passage.py +6 -2
  16. letta/orm/source.py +6 -1
  17. letta/orm/sqlalchemy_base.py +80 -32
  18. letta/orm/tool.py +5 -2
  19. letta/schemas/embedding_config_overrides.py +3 -0
  20. letta/schemas/enums.py +4 -0
  21. letta/schemas/job.py +1 -1
  22. letta/schemas/letta_message.py +22 -5
  23. letta/schemas/llm_config.py +5 -0
  24. letta/schemas/llm_config_overrides.py +38 -0
  25. letta/schemas/message.py +61 -15
  26. letta/schemas/openai/chat_completions.py +1 -1
  27. letta/schemas/passage.py +1 -1
  28. letta/schemas/providers.py +24 -8
  29. letta/schemas/source.py +1 -1
  30. letta/server/rest_api/app.py +12 -3
  31. letta/server/rest_api/interface.py +5 -7
  32. letta/server/rest_api/routers/v1/agents.py +7 -12
  33. letta/server/rest_api/routers/v1/blocks.py +19 -0
  34. letta/server/rest_api/routers/v1/organizations.py +2 -2
  35. letta/server/rest_api/routers/v1/providers.py +2 -2
  36. letta/server/rest_api/routers/v1/runs.py +15 -7
  37. letta/server/rest_api/routers/v1/sandbox_configs.py +4 -4
  38. letta/server/rest_api/routers/v1/sources.py +2 -2
  39. letta/server/rest_api/routers/v1/tags.py +2 -2
  40. letta/server/rest_api/routers/v1/tools.py +2 -2
  41. letta/server/rest_api/routers/v1/users.py +2 -2
  42. letta/server/server.py +62 -34
  43. letta/services/agent_manager.py +80 -33
  44. letta/services/block_manager.py +15 -2
  45. letta/services/helpers/agent_manager_helper.py +11 -4
  46. letta/services/job_manager.py +19 -9
  47. letta/services/message_manager.py +14 -8
  48. letta/services/organization_manager.py +8 -4
  49. letta/services/provider_manager.py +8 -4
  50. letta/services/sandbox_config_manager.py +16 -8
  51. letta/services/source_manager.py +4 -4
  52. letta/services/tool_manager.py +3 -3
  53. letta/services/user_manager.py +9 -5
  54. {letta_nightly-0.6.14.dev20250123041709.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/METADATA +2 -1
  55. {letta_nightly-0.6.14.dev20250123041709.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/RECORD +58 -57
  56. letta/orm/job_usage_statistics.py +0 -30
  57. {letta_nightly-0.6.14.dev20250123041709.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/LICENSE +0 -0
  58. {letta_nightly-0.6.14.dev20250123041709.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/WHEEL +0 -0
  59. {letta_nightly-0.6.14.dev20250123041709.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # inspecting tools
2
2
  import asyncio
3
+ import json
3
4
  import os
4
5
  import traceback
5
6
  import warnings
@@ -38,7 +39,7 @@ from letta.schemas.letta_message import LegacyLettaMessage, LettaMessage, ToolRe
38
39
  from letta.schemas.letta_response import LettaResponse
39
40
  from letta.schemas.llm_config import LLMConfig
40
41
  from letta.schemas.memory import ArchivalMemorySummary, ContextWindowOverview, Memory, RecallMemorySummary
41
- from letta.schemas.message import Message, MessageCreate, MessageRole, MessageUpdate
42
+ from letta.schemas.message import Message, MessageCreate, MessageRole, MessageUpdate, TextContent
42
43
  from letta.schemas.organization import Organization
43
44
  from letta.schemas.passage import Passage
44
45
  from letta.schemas.providers import (
@@ -616,14 +617,14 @@ class SyncServer(Server):
616
617
  message = Message(
617
618
  agent_id=agent_id,
618
619
  role="user",
619
- text=packaged_user_message,
620
+ content=[TextContent(text=packaged_user_message)],
620
621
  created_at=timestamp,
621
622
  )
622
623
  else:
623
624
  message = Message(
624
625
  agent_id=agent_id,
625
626
  role="user",
626
- text=packaged_user_message,
627
+ content=[TextContent(text=packaged_user_message)],
627
628
  )
628
629
 
629
630
  # Run the agent state forward
@@ -666,14 +667,14 @@ class SyncServer(Server):
666
667
  message = Message(
667
668
  agent_id=agent_id,
668
669
  role="system",
669
- text=packaged_system_message,
670
+ content=[TextContent(text=packaged_system_message)],
670
671
  created_at=timestamp,
671
672
  )
672
673
  else:
673
674
  message = Message(
674
675
  agent_id=agent_id,
675
676
  role="system",
676
- text=packaged_system_message,
677
+ content=[TextContent(text=packaged_system_message)],
677
678
  )
678
679
 
679
680
  if isinstance(message, Message):
@@ -720,9 +721,9 @@ class SyncServer(Server):
720
721
 
721
722
  # If wrapping is eanbled, wrap with metadata before placing content inside the Message object
722
723
  if message.role == MessageRole.user and wrap_user_message:
723
- message.text = system.package_user_message(user_message=message.text)
724
+ message.content = system.package_user_message(user_message=message.content)
724
725
  elif message.role == MessageRole.system and wrap_system_message:
725
- message.text = system.package_system_message(system_message=message.text)
726
+ message.content = system.package_system_message(system_message=message.content)
726
727
  else:
727
728
  raise ValueError(f"Invalid message role: {message.role}")
728
729
 
@@ -731,7 +732,7 @@ class SyncServer(Server):
731
732
  Message(
732
733
  agent_id=agent_id,
733
734
  role=message.role,
734
- text=message.text,
735
+ content=[TextContent(text=message.content)],
735
736
  name=message.name,
736
737
  # assigned later?
737
738
  model=None,
@@ -804,20 +805,12 @@ class SyncServer(Server):
804
805
  def get_recall_memory_summary(self, agent_id: str, actor: User) -> RecallMemorySummary:
805
806
  return RecallMemorySummary(size=self.message_manager.size(actor=actor, agent_id=agent_id))
806
807
 
807
- def get_agent_archival(self, user_id: str, agent_id: str, cursor: Optional[str] = None, limit: int = 50) -> List[Passage]:
808
- """Paginated query of all messages in agent archival memory"""
809
- # TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
810
- actor = self.user_manager.get_user_or_default(user_id=user_id)
811
-
812
- passages = self.agent_manager.list_passages(agent_id=agent_id, actor=actor)
813
-
814
- return passages
815
-
816
- def get_agent_archival_cursor(
808
+ def get_agent_archival(
817
809
  self,
818
810
  user_id: str,
819
811
  agent_id: str,
820
- cursor: Optional[str] = None,
812
+ after: Optional[str] = None,
813
+ before: Optional[str] = None,
821
814
  limit: Optional[int] = 100,
822
815
  order_by: Optional[str] = "created_at",
823
816
  reverse: Optional[bool] = False,
@@ -829,7 +822,8 @@ class SyncServer(Server):
829
822
  records = self.agent_manager.list_passages(
830
823
  actor=actor,
831
824
  agent_id=agent_id,
832
- cursor=cursor,
825
+ after=after,
826
+ before=before,
833
827
  limit=limit,
834
828
  ascending=not reverse,
835
829
  )
@@ -851,7 +845,7 @@ class SyncServer(Server):
851
845
 
852
846
  # TODO: return archival memory
853
847
 
854
- def get_agent_recall_cursor(
848
+ def get_agent_recall(
855
849
  self,
856
850
  user_id: str,
857
851
  agent_id: str,
@@ -1047,13 +1041,14 @@ class SyncServer(Server):
1047
1041
 
1048
1042
  def list_llm_models(self) -> List[LLMConfig]:
1049
1043
  """List available models"""
1050
-
1051
1044
  llm_models = []
1052
1045
  for provider in self.get_enabled_providers():
1053
1046
  try:
1054
1047
  llm_models.extend(provider.list_llm_models())
1055
1048
  except Exception as e:
1056
1049
  warnings.warn(f"An error occurred while listing LLM models for provider {provider}: {e}")
1050
+
1051
+ llm_models.extend(self.get_local_llm_configs())
1057
1052
  return llm_models
1058
1053
 
1059
1054
  def list_embedding_models(self) -> List[EmbeddingConfig]:
@@ -1073,12 +1068,22 @@ class SyncServer(Server):
1073
1068
  return {**providers_from_env, **providers_from_db}.values()
1074
1069
 
1075
1070
  def get_llm_config_from_handle(self, handle: str, context_window_limit: Optional[int] = None) -> LLMConfig:
1076
- provider_name, model_name = handle.split("/", 1)
1077
- provider = self.get_provider_from_name(provider_name)
1078
-
1079
- llm_configs = [config for config in provider.list_llm_models() if config.model == model_name]
1080
- if not llm_configs:
1081
- raise ValueError(f"LLM model {model_name} is not supported by {provider_name}")
1071
+ try:
1072
+ provider_name, model_name = handle.split("/", 1)
1073
+ provider = self.get_provider_from_name(provider_name)
1074
+
1075
+ llm_configs = [config for config in provider.list_llm_models() if config.handle == handle]
1076
+ if not llm_configs:
1077
+ llm_configs = [config for config in provider.list_llm_models() if config.model == model_name]
1078
+ if not llm_configs:
1079
+ raise ValueError(f"LLM model {model_name} is not supported by {provider_name}")
1080
+ except ValueError as e:
1081
+ llm_configs = [config for config in self.get_local_llm_configs() if config.handle == handle]
1082
+ if not llm_configs:
1083
+ raise e
1084
+
1085
+ if len(llm_configs) == 1:
1086
+ llm_config = llm_configs[0]
1082
1087
  elif len(llm_configs) > 1:
1083
1088
  raise ValueError(f"Multiple LLM models with name {model_name} supported by {provider_name}")
1084
1089
  else:
@@ -1097,13 +1102,17 @@ class SyncServer(Server):
1097
1102
  provider_name, model_name = handle.split("/", 1)
1098
1103
  provider = self.get_provider_from_name(provider_name)
1099
1104
 
1100
- embedding_configs = [config for config in provider.list_embedding_models() if config.embedding_model == model_name]
1101
- if not embedding_configs:
1102
- raise ValueError(f"Embedding model {model_name} is not supported by {provider_name}")
1103
- elif len(embedding_configs) > 1:
1104
- raise ValueError(f"Multiple embedding models with name {model_name} supported by {provider_name}")
1105
- else:
1105
+ embedding_configs = [config for config in provider.list_embedding_models() if config.handle == handle]
1106
+ if len(embedding_configs) == 1:
1106
1107
  embedding_config = embedding_configs[0]
1108
+ else:
1109
+ embedding_configs = [config for config in provider.list_embedding_models() if config.embedding_model == model_name]
1110
+ if not embedding_configs:
1111
+ raise ValueError(f"Embedding model {model_name} is not supported by {provider_name}")
1112
+ elif len(embedding_configs) > 1:
1113
+ raise ValueError(f"Multiple embedding models with name {model_name} supported by {provider_name}")
1114
+ else:
1115
+ embedding_config = embedding_configs[0]
1107
1116
 
1108
1117
  if embedding_chunk_size:
1109
1118
  embedding_config.embedding_chunk_size = embedding_chunk_size
@@ -1121,6 +1130,25 @@ class SyncServer(Server):
1121
1130
 
1122
1131
  return provider
1123
1132
 
1133
+ def get_local_llm_configs(self):
1134
+ llm_models = []
1135
+ try:
1136
+ llm_configs_dir = os.path.expanduser("~/.letta/llm_configs")
1137
+ if os.path.exists(llm_configs_dir):
1138
+ for filename in os.listdir(llm_configs_dir):
1139
+ if filename.endswith(".json"):
1140
+ filepath = os.path.join(llm_configs_dir, filename)
1141
+ try:
1142
+ with open(filepath, "r") as f:
1143
+ config_data = json.load(f)
1144
+ llm_config = LLMConfig(**config_data)
1145
+ llm_models.append(llm_config)
1146
+ except (json.JSONDecodeError, ValueError) as e:
1147
+ warnings.warn(f"Error parsing LLM config file {filename}: {e}")
1148
+ except Exception as e:
1149
+ warnings.warn(f"Error reading LLM configs directory: {e}")
1150
+ return llm_models
1151
+
1124
1152
  def add_llm_model(self, request: LLMConfig) -> LLMConfig:
1125
1153
  """Add a new LLM model"""
1126
1154
 
@@ -2,7 +2,7 @@ from datetime import datetime
2
2
  from typing import Dict, List, Optional
3
3
 
4
4
  import numpy as np
5
- from sqlalchemy import Select, func, literal, select, union_all
5
+ from sqlalchemy import Select, and_, func, literal, or_, select, union_all
6
6
 
7
7
  from letta.constants import BASE_MEMORY_TOOLS, BASE_TOOLS, MAX_EMBEDDING_DIM, MULTI_AGENT_TOOLS
8
8
  from letta.embeddings import embedding_model
@@ -271,10 +271,11 @@ class AgentManager:
271
271
  def list_agents(
272
272
  self,
273
273
  actor: PydanticUser,
274
+ before: Optional[str] = None,
275
+ after: Optional[str] = None,
276
+ limit: Optional[int] = 50,
274
277
  tags: Optional[List[str]] = None,
275
278
  match_all_tags: bool = False,
276
- cursor: Optional[str] = None,
277
- limit: Optional[int] = 50,
278
279
  query_text: Optional[str] = None,
279
280
  **kwargs,
280
281
  ) -> List[PydanticAgentState]:
@@ -284,10 +285,11 @@ class AgentManager:
284
285
  with self.session_maker() as session:
285
286
  agents = AgentModel.list(
286
287
  db_session=session,
288
+ before=before,
289
+ after=after,
290
+ limit=limit,
287
291
  tags=tags,
288
292
  match_all_tags=match_all_tags,
289
- cursor=cursor,
290
- limit=limit,
291
293
  organization_id=actor.organization_id if actor else None,
292
294
  query_text=query_text,
293
295
  **kwargs,
@@ -723,7 +725,8 @@ class AgentManager:
723
725
  query_text: Optional[str] = None,
724
726
  start_date: Optional[datetime] = None,
725
727
  end_date: Optional[datetime] = None,
726
- cursor: Optional[str] = None,
728
+ before: Optional[str] = None,
729
+ after: Optional[str] = None,
727
730
  source_id: Optional[str] = None,
728
731
  embed_query: bool = False,
729
732
  ascending: bool = True,
@@ -731,6 +734,7 @@ class AgentManager:
731
734
  agent_only: bool = False,
732
735
  ) -> Select:
733
736
  """Helper function to build the base passage query with all filters applied.
737
+ Supports both before and after pagination across merged source and agent passages.
734
738
 
735
739
  Returns the query before any limit or count operations are applied.
736
740
  """
@@ -818,30 +822,69 @@ class AgentManager:
818
822
  else:
819
823
  # SQLite with custom vector type
820
824
  query_embedding_binary = adapt_array(embedded_text)
821
- if ascending:
822
- main_query = main_query.order_by(
823
- func.cosine_distance(combined_query.c.embedding, query_embedding_binary).asc(),
824
- combined_query.c.created_at.asc(),
825
- combined_query.c.id.asc(),
826
- )
827
- else:
828
- main_query = main_query.order_by(
829
- func.cosine_distance(combined_query.c.embedding, query_embedding_binary).asc(),
830
- combined_query.c.created_at.desc(),
831
- combined_query.c.id.asc(),
832
- )
825
+ main_query = main_query.order_by(
826
+ func.cosine_distance(combined_query.c.embedding, query_embedding_binary).asc(),
827
+ combined_query.c.created_at.asc() if ascending else combined_query.c.created_at.desc(),
828
+ combined_query.c.id.asc(),
829
+ )
833
830
  else:
834
831
  if query_text:
835
832
  main_query = main_query.where(func.lower(combined_query.c.text).contains(func.lower(query_text)))
836
833
 
837
- # Handle cursor-based pagination
838
- if cursor:
839
- cursor_query = select(combined_query.c.created_at).where(combined_query.c.id == cursor).scalar_subquery()
834
+ # Handle pagination
835
+ if before or after:
836
+ # Create reference CTEs
837
+ if before:
838
+ before_ref = (
839
+ select(combined_query.c.created_at, combined_query.c.id).where(combined_query.c.id == before).cte("before_ref")
840
+ )
841
+ if after:
842
+ after_ref = (
843
+ select(combined_query.c.created_at, combined_query.c.id).where(combined_query.c.id == after).cte("after_ref")
844
+ )
840
845
 
841
- if ascending:
842
- main_query = main_query.where(combined_query.c.created_at > cursor_query)
846
+ if before and after:
847
+ # Window-based query (get records between before and after)
848
+ main_query = main_query.where(
849
+ or_(
850
+ combined_query.c.created_at < select(before_ref.c.created_at).scalar_subquery(),
851
+ and_(
852
+ combined_query.c.created_at == select(before_ref.c.created_at).scalar_subquery(),
853
+ combined_query.c.id < select(before_ref.c.id).scalar_subquery(),
854
+ ),
855
+ )
856
+ )
857
+ main_query = main_query.where(
858
+ or_(
859
+ combined_query.c.created_at > select(after_ref.c.created_at).scalar_subquery(),
860
+ and_(
861
+ combined_query.c.created_at == select(after_ref.c.created_at).scalar_subquery(),
862
+ combined_query.c.id > select(after_ref.c.id).scalar_subquery(),
863
+ ),
864
+ )
865
+ )
843
866
  else:
844
- main_query = main_query.where(combined_query.c.created_at < cursor_query)
867
+ # Pure pagination (only before or only after)
868
+ if before:
869
+ main_query = main_query.where(
870
+ or_(
871
+ combined_query.c.created_at < select(before_ref.c.created_at).scalar_subquery(),
872
+ and_(
873
+ combined_query.c.created_at == select(before_ref.c.created_at).scalar_subquery(),
874
+ combined_query.c.id < select(before_ref.c.id).scalar_subquery(),
875
+ ),
876
+ )
877
+ )
878
+ if after:
879
+ main_query = main_query.where(
880
+ or_(
881
+ combined_query.c.created_at > select(after_ref.c.created_at).scalar_subquery(),
882
+ and_(
883
+ combined_query.c.created_at == select(after_ref.c.created_at).scalar_subquery(),
884
+ combined_query.c.id > select(after_ref.c.id).scalar_subquery(),
885
+ ),
886
+ )
887
+ )
845
888
 
846
889
  # Add ordering if not already ordered by similarity
847
890
  if not embed_query:
@@ -856,7 +899,7 @@ class AgentManager:
856
899
  combined_query.c.id.asc(),
857
900
  )
858
901
 
859
- return main_query
902
+ return main_query
860
903
 
861
904
  @enforce_types
862
905
  def list_passages(
@@ -868,7 +911,8 @@ class AgentManager:
868
911
  query_text: Optional[str] = None,
869
912
  start_date: Optional[datetime] = None,
870
913
  end_date: Optional[datetime] = None,
871
- cursor: Optional[str] = None,
914
+ before: Optional[str] = None,
915
+ after: Optional[str] = None,
872
916
  source_id: Optional[str] = None,
873
917
  embed_query: bool = False,
874
918
  ascending: bool = True,
@@ -884,7 +928,8 @@ class AgentManager:
884
928
  query_text=query_text,
885
929
  start_date=start_date,
886
930
  end_date=end_date,
887
- cursor=cursor,
931
+ before=before,
932
+ after=after,
888
933
  source_id=source_id,
889
934
  embed_query=embed_query,
890
935
  ascending=ascending,
@@ -924,7 +969,8 @@ class AgentManager:
924
969
  query_text: Optional[str] = None,
925
970
  start_date: Optional[datetime] = None,
926
971
  end_date: Optional[datetime] = None,
927
- cursor: Optional[str] = None,
972
+ before: Optional[str] = None,
973
+ after: Optional[str] = None,
928
974
  source_id: Optional[str] = None,
929
975
  embed_query: bool = False,
930
976
  ascending: bool = True,
@@ -940,7 +986,8 @@ class AgentManager:
940
986
  query_text=query_text,
941
987
  start_date=start_date,
942
988
  end_date=end_date,
943
- cursor=cursor,
989
+ before=before,
990
+ after=after,
944
991
  source_id=source_id,
945
992
  embed_query=embed_query,
946
993
  ascending=ascending,
@@ -1044,14 +1091,14 @@ class AgentManager:
1044
1091
  # ======================================================================================================================
1045
1092
  @enforce_types
1046
1093
  def list_tags(
1047
- self, actor: PydanticUser, cursor: Optional[str] = None, limit: Optional[int] = 50, query_text: Optional[str] = None
1094
+ self, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50, query_text: Optional[str] = None
1048
1095
  ) -> List[str]:
1049
1096
  """
1050
1097
  Get all tags a user has created, ordered alphabetically.
1051
1098
 
1052
1099
  Args:
1053
1100
  actor: User performing the action.
1054
- cursor: Cursor for pagination.
1101
+ after: Cursor for forward pagination.
1055
1102
  limit: Maximum number of tags to return.
1056
1103
  query_text: Query text to filter tags by.
1057
1104
 
@@ -1069,8 +1116,8 @@ class AgentManager:
1069
1116
  if query_text:
1070
1117
  query = query.filter(AgentsTags.tag.ilike(f"%{query_text}%"))
1071
1118
 
1072
- if cursor:
1073
- query = query.filter(AgentsTags.tag > cursor)
1119
+ if after:
1120
+ query = query.filter(AgentsTags.tag > after)
1074
1121
 
1075
1122
  query = query.order_by(AgentsTags.tag).limit(limit)
1076
1123
  results = [tag[0] for tag in query.all()]
@@ -3,6 +3,7 @@ from typing import List, Optional
3
3
 
4
4
  from letta.orm.block import Block as BlockModel
5
5
  from letta.orm.errors import NoResultFound
6
+ from letta.schemas.agent import AgentState as PydanticAgentState
6
7
  from letta.schemas.block import Block
7
8
  from letta.schemas.block import Block as PydanticBlock
8
9
  from letta.schemas.block import BlockUpdate, Human, Persona
@@ -64,7 +65,7 @@ class BlockManager:
64
65
  is_template: Optional[bool] = None,
65
66
  template_name: Optional[str] = None,
66
67
  id: Optional[str] = None,
67
- cursor: Optional[str] = None,
68
+ after: Optional[str] = None,
68
69
  limit: Optional[int] = 50,
69
70
  ) -> List[PydanticBlock]:
70
71
  """Retrieve blocks based on various optional filters."""
@@ -80,7 +81,7 @@ class BlockManager:
80
81
  if id:
81
82
  filters["id"] = id
82
83
 
83
- blocks = BlockModel.list(db_session=session, cursor=cursor, limit=limit, **filters)
84
+ blocks = BlockModel.list(db_session=session, after=after, limit=limit, **filters)
84
85
 
85
86
  return [block.to_pydantic() for block in blocks]
86
87
 
@@ -114,3 +115,15 @@ class BlockManager:
114
115
  text = open(human_file, "r", encoding="utf-8").read()
115
116
  name = os.path.basename(human_file).replace(".txt", "")
116
117
  self.create_or_update_block(Human(template_name=name, value=text, is_template=True), actor=actor)
118
+
119
+ @enforce_types
120
+ def get_agents_for_block(self, block_id: str, actor: PydanticUser) -> List[PydanticAgentState]:
121
+ """
122
+ Retrieve all agents associated with a given block.
123
+ """
124
+ with self.session_maker() as session:
125
+ block = BlockModel.read(db_session=session, identifier=block_id, actor=actor)
126
+ agents_orm = block.agents
127
+ agents_pydantic = [agent.to_pydantic() for agent in agents_orm]
128
+
129
+ return agents_pydantic
@@ -11,7 +11,7 @@ from letta.prompts import gpt_system
11
11
  from letta.schemas.agent import AgentState, AgentType
12
12
  from letta.schemas.enums import MessageRole
13
13
  from letta.schemas.memory import Memory
14
- from letta.schemas.message import Message, MessageCreate
14
+ from letta.schemas.message import Message, MessageCreate, TextContent
15
15
  from letta.schemas.tool_rule import ToolRule
16
16
  from letta.schemas.user import User
17
17
  from letta.system import get_initial_boot_messages, get_login_event
@@ -234,17 +234,24 @@ def package_initial_message_sequence(
234
234
 
235
235
  if message_create.role == MessageRole.user:
236
236
  packed_message = system.package_user_message(
237
- user_message=message_create.text,
237
+ user_message=message_create.content,
238
238
  )
239
239
  elif message_create.role == MessageRole.system:
240
240
  packed_message = system.package_system_message(
241
- system_message=message_create.text,
241
+ system_message=message_create.content,
242
242
  )
243
243
  else:
244
244
  raise ValueError(f"Invalid message role: {message_create.role}")
245
245
 
246
246
  init_messages.append(
247
- Message(role=message_create.role, text=packed_message, organization_id=actor.organization_id, agent_id=agent_id, model=model)
247
+ Message(
248
+ role=message_create.role,
249
+ content=[TextContent(text=packed_message)],
250
+ name=message_create.name,
251
+ organization_id=actor.organization_id,
252
+ agent_id=agent_id,
253
+ model=model,
254
+ )
248
255
  )
249
256
  return init_messages
250
257
 
@@ -78,10 +78,12 @@ class JobManager:
78
78
  def list_jobs(
79
79
  self,
80
80
  actor: PydanticUser,
81
- cursor: Optional[str] = None,
81
+ before: Optional[str] = None,
82
+ after: Optional[str] = None,
82
83
  limit: Optional[int] = 50,
83
84
  statuses: Optional[List[JobStatus]] = None,
84
85
  job_type: JobType = JobType.JOB,
86
+ ascending: bool = True,
85
87
  ) -> List[PydanticJob]:
86
88
  """List all jobs with optional pagination and status filter."""
87
89
  with self.session_maker() as session:
@@ -93,8 +95,10 @@ class JobManager:
93
95
 
94
96
  jobs = JobModel.list(
95
97
  db_session=session,
96
- cursor=cursor,
98
+ before=before,
99
+ after=after,
97
100
  limit=limit,
101
+ ascending=ascending,
98
102
  **filter_kwargs,
99
103
  )
100
104
  return [job.to_pydantic() for job in jobs]
@@ -112,7 +116,8 @@ class JobManager:
112
116
  self,
113
117
  job_id: str,
114
118
  actor: PydanticUser,
115
- cursor: Optional[str] = None,
119
+ before: Optional[str] = None,
120
+ after: Optional[str] = None,
116
121
  limit: Optional[int] = 100,
117
122
  role: Optional[MessageRole] = None,
118
123
  ascending: bool = True,
@@ -123,7 +128,8 @@ class JobManager:
123
128
  Args:
124
129
  job_id: The ID of the job to get messages for
125
130
  actor: The user making the request
126
- cursor: Cursor for pagination
131
+ before: Cursor for pagination
132
+ after: Cursor for pagination
127
133
  limit: Maximum number of messages to return
128
134
  role: Optional filter for message role
129
135
  ascending: Optional flag to sort in ascending order
@@ -143,7 +149,8 @@ class JobManager:
143
149
  # Get messages
144
150
  messages = MessageModel.list(
145
151
  db_session=session,
146
- cursor=cursor,
152
+ before=before,
153
+ after=after,
147
154
  ascending=ascending,
148
155
  limit=limit,
149
156
  actor=actor,
@@ -255,11 +262,12 @@ class JobManager:
255
262
  session.commit()
256
263
 
257
264
  @enforce_types
258
- def get_run_messages_cursor(
265
+ def get_run_messages(
259
266
  self,
260
267
  run_id: str,
261
268
  actor: PydanticUser,
262
- cursor: Optional[str] = None,
269
+ before: Optional[str] = None,
270
+ after: Optional[str] = None,
263
271
  limit: Optional[int] = 100,
264
272
  role: Optional[MessageRole] = None,
265
273
  ascending: bool = True,
@@ -271,7 +279,8 @@ class JobManager:
271
279
  Args:
272
280
  job_id: The ID of the job to get messages for
273
281
  actor: The user making the request
274
- cursor: Message ID to get messages after or before
282
+ before: Message ID to get messages after
283
+ after: Message ID to get messages before
275
284
  limit: Maximum number of messages to return
276
285
  ascending: Whether to return messages in ascending order
277
286
  role: Optional role filter
@@ -285,7 +294,8 @@ class JobManager:
285
294
  messages = self.get_job_messages(
286
295
  job_id=run_id,
287
296
  actor=actor,
288
- cursor=cursor,
297
+ before=before,
298
+ after=after,
289
299
  limit=limit,
290
300
  role=role,
291
301
  ascending=ascending,
@@ -49,7 +49,7 @@ class MessageManager:
49
49
  with self.session_maker() as session:
50
50
  # Set the organization id of the Pydantic message
51
51
  pydantic_msg.organization_id = actor.organization_id
52
- msg_data = pydantic_msg.model_dump()
52
+ msg_data = pydantic_msg.model_dump(to_orm=True)
53
53
  msg = MessageModel(**msg_data)
54
54
  msg.create(session, actor=actor) # Persist to database
55
55
  return msg.to_pydantic()
@@ -83,7 +83,7 @@ class MessageManager:
83
83
  )
84
84
 
85
85
  # get update dictionary
86
- update_data = message_update.model_dump(exclude_unset=True, exclude_none=True)
86
+ update_data = message_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
87
87
  # Remove redundant update fields
88
88
  update_data = {key: value for key, value in update_data.items() if getattr(message, key) != value}
89
89
 
@@ -128,7 +128,8 @@ class MessageManager:
128
128
  self,
129
129
  agent_id: str,
130
130
  actor: Optional[PydanticUser] = None,
131
- cursor: Optional[str] = None,
131
+ before: Optional[str] = None,
132
+ after: Optional[str] = None,
132
133
  start_date: Optional[datetime] = None,
133
134
  end_date: Optional[datetime] = None,
134
135
  limit: Optional[int] = 50,
@@ -139,7 +140,8 @@ class MessageManager:
139
140
  """List user messages with flexible filtering and pagination options.
140
141
 
141
142
  Args:
142
- cursor: Cursor-based pagination - return records after this ID (exclusive)
143
+ before: Cursor-based pagination - return records before this ID (exclusive)
144
+ after: Cursor-based pagination - return records after this ID (exclusive)
143
145
  start_date: Filter records created after this date
144
146
  end_date: Filter records created before this date
145
147
  limit: Maximum number of records to return
@@ -156,7 +158,8 @@ class MessageManager:
156
158
  return self.list_messages_for_agent(
157
159
  agent_id=agent_id,
158
160
  actor=actor,
159
- cursor=cursor,
161
+ before=before,
162
+ after=after,
160
163
  start_date=start_date,
161
164
  end_date=end_date,
162
165
  limit=limit,
@@ -170,7 +173,8 @@ class MessageManager:
170
173
  self,
171
174
  agent_id: str,
172
175
  actor: Optional[PydanticUser] = None,
173
- cursor: Optional[str] = None,
176
+ before: Optional[str] = None,
177
+ after: Optional[str] = None,
174
178
  start_date: Optional[datetime] = None,
175
179
  end_date: Optional[datetime] = None,
176
180
  limit: Optional[int] = 50,
@@ -181,7 +185,8 @@ class MessageManager:
181
185
  """List messages with flexible filtering and pagination options.
182
186
 
183
187
  Args:
184
- cursor: Cursor-based pagination - return records after this ID (exclusive)
188
+ before: Cursor-based pagination - return records before this ID (exclusive)
189
+ after: Cursor-based pagination - return records after this ID (exclusive)
185
190
  start_date: Filter records created after this date
186
191
  end_date: Filter records created before this date
187
192
  limit: Maximum number of records to return
@@ -201,7 +206,8 @@ class MessageManager:
201
206
 
202
207
  results = MessageModel.list(
203
208
  db_session=session,
204
- cursor=cursor,
209
+ before=before,
210
+ after=after,
205
211
  start_date=start_date,
206
212
  end_date=end_date,
207
213
  limit=limit,