letta-nightly 0.6.14.dev20250123104106__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.
- letta/__init__.py +1 -1
- letta/client/client.py +144 -68
- letta/client/streaming.py +1 -1
- letta/functions/function_sets/extras.py +8 -3
- letta/functions/function_sets/multi_agent.py +1 -1
- letta/functions/helpers.py +2 -2
- letta/llm_api/llm_api_tools.py +2 -2
- letta/llm_api/openai.py +30 -138
- letta/memory.py +4 -4
- letta/offline_memory_agent.py +10 -10
- letta/orm/agent.py +10 -2
- letta/orm/block.py +14 -3
- letta/orm/job.py +2 -1
- letta/orm/message.py +12 -1
- letta/orm/passage.py +6 -2
- letta/orm/source.py +6 -1
- letta/orm/sqlalchemy_base.py +80 -32
- letta/orm/tool.py +5 -2
- letta/schemas/embedding_config_overrides.py +3 -0
- letta/schemas/enums.py +4 -0
- letta/schemas/job.py +1 -1
- letta/schemas/letta_message.py +22 -5
- letta/schemas/llm_config.py +5 -0
- letta/schemas/llm_config_overrides.py +38 -0
- letta/schemas/message.py +61 -15
- letta/schemas/openai/chat_completions.py +1 -1
- letta/schemas/passage.py +1 -1
- letta/schemas/providers.py +24 -8
- letta/schemas/source.py +1 -1
- letta/server/rest_api/app.py +12 -3
- letta/server/rest_api/interface.py +5 -7
- letta/server/rest_api/routers/v1/agents.py +7 -12
- letta/server/rest_api/routers/v1/blocks.py +19 -0
- letta/server/rest_api/routers/v1/organizations.py +2 -2
- letta/server/rest_api/routers/v1/providers.py +2 -2
- letta/server/rest_api/routers/v1/runs.py +15 -7
- letta/server/rest_api/routers/v1/sandbox_configs.py +4 -4
- letta/server/rest_api/routers/v1/sources.py +2 -2
- letta/server/rest_api/routers/v1/tags.py +2 -2
- letta/server/rest_api/routers/v1/tools.py +2 -2
- letta/server/rest_api/routers/v1/users.py +2 -2
- letta/server/server.py +62 -34
- letta/services/agent_manager.py +80 -33
- letta/services/block_manager.py +15 -2
- letta/services/helpers/agent_manager_helper.py +11 -4
- letta/services/job_manager.py +19 -9
- letta/services/message_manager.py +14 -8
- letta/services/organization_manager.py +8 -4
- letta/services/provider_manager.py +8 -4
- letta/services/sandbox_config_manager.py +16 -8
- letta/services/source_manager.py +4 -4
- letta/services/tool_manager.py +3 -3
- letta/services/user_manager.py +9 -5
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/METADATA +2 -1
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/RECORD +58 -57
- letta/orm/job_usage_statistics.py +0 -30
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.14.dev20250123104106.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.
|
|
724
|
+
message.content = system.package_user_message(user_message=message.content)
|
|
724
725
|
elif message.role == MessageRole.system and wrap_system_message:
|
|
725
|
-
message.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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.
|
|
1101
|
-
if
|
|
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
|
|
letta/services/agent_manager.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
|
838
|
-
if
|
|
839
|
-
|
|
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
|
|
842
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
1073
|
-
query = query.filter(AgentsTags.tag >
|
|
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()]
|
letta/services/block_manager.py
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|
letta/services/job_manager.py
CHANGED
|
@@ -78,10 +78,12 @@ class JobManager:
|
|
|
78
78
|
def list_jobs(
|
|
79
79
|
self,
|
|
80
80
|
actor: PydanticUser,
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
265
|
+
def get_run_messages(
|
|
259
266
|
self,
|
|
260
267
|
run_id: str,
|
|
261
268
|
actor: PydanticUser,
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
+
before=before,
|
|
210
|
+
after=after,
|
|
205
211
|
start_date=start_date,
|
|
206
212
|
end_date=end_date,
|
|
207
213
|
limit=limit,
|