letta-nightly 0.7.30.dev20250603104343__py3-none-any.whl → 0.8.0.dev20250604201135__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.
- letta/__init__.py +7 -1
- letta/agent.py +14 -7
- letta/agents/base_agent.py +1 -0
- letta/agents/ephemeral_summary_agent.py +104 -0
- letta/agents/helpers.py +35 -3
- letta/agents/letta_agent.py +492 -176
- letta/agents/letta_agent_batch.py +22 -16
- letta/agents/prompts/summary_system_prompt.txt +62 -0
- letta/agents/voice_agent.py +22 -7
- letta/agents/voice_sleeptime_agent.py +13 -8
- letta/constants.py +33 -1
- letta/data_sources/connectors.py +52 -36
- letta/errors.py +4 -0
- letta/functions/ast_parsers.py +13 -30
- letta/functions/function_sets/base.py +3 -1
- letta/functions/functions.py +2 -0
- letta/functions/mcp_client/base_client.py +151 -97
- letta/functions/mcp_client/sse_client.py +49 -31
- letta/functions/mcp_client/stdio_client.py +107 -106
- letta/functions/schema_generator.py +22 -22
- letta/groups/helpers.py +3 -4
- letta/groups/sleeptime_multi_agent.py +4 -4
- letta/groups/sleeptime_multi_agent_v2.py +22 -0
- letta/helpers/composio_helpers.py +16 -0
- letta/helpers/converters.py +20 -0
- letta/helpers/datetime_helpers.py +1 -6
- letta/helpers/tool_rule_solver.py +2 -1
- letta/interfaces/anthropic_streaming_interface.py +17 -2
- letta/interfaces/openai_chat_completions_streaming_interface.py +1 -0
- letta/interfaces/openai_streaming_interface.py +18 -2
- letta/llm_api/anthropic_client.py +24 -3
- letta/llm_api/google_ai_client.py +0 -15
- letta/llm_api/google_vertex_client.py +6 -5
- letta/llm_api/llm_client_base.py +15 -0
- letta/llm_api/openai.py +2 -2
- letta/llm_api/openai_client.py +60 -8
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +45 -43
- letta/orm/base.py +0 -2
- letta/orm/block.py +1 -0
- letta/orm/custom_columns.py +13 -0
- letta/orm/enums.py +5 -0
- letta/orm/file.py +3 -1
- letta/orm/files_agents.py +68 -0
- letta/orm/mcp_server.py +48 -0
- letta/orm/message.py +1 -0
- letta/orm/organization.py +11 -2
- letta/orm/passage.py +25 -10
- letta/orm/sandbox_config.py +5 -2
- letta/orm/sqlalchemy_base.py +171 -110
- letta/prompts/system/memgpt_base.txt +6 -1
- letta/prompts/system/memgpt_v2_chat.txt +57 -0
- letta/prompts/system/sleeptime.txt +2 -0
- letta/prompts/system/sleeptime_v2.txt +28 -0
- letta/schemas/agent.py +87 -20
- letta/schemas/block.py +7 -1
- letta/schemas/file.py +57 -0
- letta/schemas/mcp.py +74 -0
- letta/schemas/memory.py +5 -2
- letta/schemas/message.py +9 -0
- letta/schemas/openai/openai.py +0 -6
- letta/schemas/providers.py +33 -4
- letta/schemas/tool.py +26 -21
- letta/schemas/tool_execution_result.py +5 -0
- letta/server/db.py +23 -8
- letta/server/rest_api/app.py +73 -56
- letta/server/rest_api/interface.py +4 -4
- letta/server/rest_api/routers/v1/agents.py +132 -47
- letta/server/rest_api/routers/v1/blocks.py +3 -2
- letta/server/rest_api/routers/v1/embeddings.py +3 -3
- letta/server/rest_api/routers/v1/groups.py +3 -3
- letta/server/rest_api/routers/v1/jobs.py +14 -17
- letta/server/rest_api/routers/v1/organizations.py +10 -10
- letta/server/rest_api/routers/v1/providers.py +12 -10
- letta/server/rest_api/routers/v1/runs.py +3 -3
- letta/server/rest_api/routers/v1/sandbox_configs.py +12 -12
- letta/server/rest_api/routers/v1/sources.py +108 -43
- letta/server/rest_api/routers/v1/steps.py +8 -6
- letta/server/rest_api/routers/v1/tools.py +134 -95
- letta/server/rest_api/utils.py +12 -1
- letta/server/server.py +272 -73
- letta/services/agent_manager.py +246 -313
- letta/services/block_manager.py +30 -9
- letta/services/context_window_calculator/__init__.py +0 -0
- letta/services/context_window_calculator/context_window_calculator.py +150 -0
- letta/services/context_window_calculator/token_counter.py +82 -0
- letta/services/file_processor/__init__.py +0 -0
- letta/services/file_processor/chunker/__init__.py +0 -0
- letta/services/file_processor/chunker/llama_index_chunker.py +29 -0
- letta/services/file_processor/embedder/__init__.py +0 -0
- letta/services/file_processor/embedder/openai_embedder.py +84 -0
- letta/services/file_processor/file_processor.py +123 -0
- letta/services/file_processor/parser/__init__.py +0 -0
- letta/services/file_processor/parser/base_parser.py +9 -0
- letta/services/file_processor/parser/mistral_parser.py +54 -0
- letta/services/file_processor/types.py +0 -0
- letta/services/files_agents_manager.py +184 -0
- letta/services/group_manager.py +118 -0
- letta/services/helpers/agent_manager_helper.py +76 -21
- letta/services/helpers/tool_execution_helper.py +3 -0
- letta/services/helpers/tool_parser_helper.py +100 -0
- letta/services/identity_manager.py +44 -42
- letta/services/job_manager.py +21 -10
- letta/services/mcp/base_client.py +5 -2
- letta/services/mcp/sse_client.py +3 -5
- letta/services/mcp/stdio_client.py +3 -5
- letta/services/mcp_manager.py +281 -0
- letta/services/message_manager.py +40 -26
- letta/services/organization_manager.py +55 -19
- letta/services/passage_manager.py +211 -13
- letta/services/provider_manager.py +48 -2
- letta/services/sandbox_config_manager.py +105 -0
- letta/services/source_manager.py +4 -5
- letta/services/step_manager.py +9 -6
- letta/services/summarizer/summarizer.py +50 -23
- letta/services/telemetry_manager.py +7 -0
- letta/services/tool_executor/tool_execution_manager.py +11 -52
- letta/services/tool_executor/tool_execution_sandbox.py +4 -34
- letta/services/tool_executor/tool_executor.py +107 -105
- letta/services/tool_manager.py +56 -17
- letta/services/tool_sandbox/base.py +39 -92
- letta/services/tool_sandbox/e2b_sandbox.py +16 -11
- letta/services/tool_sandbox/local_sandbox.py +51 -23
- letta/services/user_manager.py +36 -3
- letta/settings.py +10 -3
- letta/templates/__init__.py +0 -0
- letta/templates/sandbox_code_file.py.j2 +47 -0
- letta/templates/template_helper.py +16 -0
- letta/tracing.py +30 -1
- letta/types/__init__.py +7 -0
- letta/utils.py +25 -1
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/METADATA +7 -2
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/RECORD +136 -110
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/entry_points.txt +0 -0
letta/server/server.py
CHANGED
@@ -19,13 +19,11 @@ import letta.constants as constants
|
|
19
19
|
import letta.server.utils as server_utils
|
20
20
|
import letta.system as system
|
21
21
|
from letta.agent import Agent, save_agent
|
22
|
+
from letta.agents.letta_agent import LettaAgent
|
22
23
|
from letta.config import LettaConfig
|
23
|
-
from letta.constants import LETTA_TOOL_EXECUTION_DIR
|
24
|
+
from letta.constants import CORE_MEMORY_SOURCE_CHAR_LIMIT, LETTA_TOOL_EXECUTION_DIR
|
24
25
|
from letta.data_sources.connectors import DataConnector, load_data
|
25
26
|
from letta.errors import HandleNotFoundError
|
26
|
-
from letta.functions.mcp_client.base_client import BaseMCPClient
|
27
|
-
from letta.functions.mcp_client.sse_client import MCP_CONFIG_TOPLEVEL_KEY, SSEMCPClient
|
28
|
-
from letta.functions.mcp_client.stdio_client import StdioMCPClient
|
29
27
|
from letta.functions.mcp_client.types import MCPServerType, MCPTool, SSEServerConfig, StdioServerConfig
|
30
28
|
from letta.groups.helpers import load_multi_agent
|
31
29
|
from letta.helpers.datetime_helpers import get_utc_time
|
@@ -52,7 +50,6 @@ from letta.schemas.letta_response import LettaResponse
|
|
52
50
|
from letta.schemas.llm_config import LLMConfig
|
53
51
|
from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySummary
|
54
52
|
from letta.schemas.message import Message, MessageCreate, MessageUpdate
|
55
|
-
from letta.schemas.organization import Organization
|
56
53
|
from letta.schemas.passage import Passage, PassageUpdate
|
57
54
|
from letta.schemas.providers import (
|
58
55
|
AnthropicBedrockProvider,
|
@@ -82,11 +79,16 @@ from letta.server.rest_api.interface import StreamingServerInterface
|
|
82
79
|
from letta.server.rest_api.utils import sse_async_generator
|
83
80
|
from letta.services.agent_manager import AgentManager
|
84
81
|
from letta.services.block_manager import BlockManager
|
82
|
+
from letta.services.files_agents_manager import FileAgentManager
|
85
83
|
from letta.services.group_manager import GroupManager
|
86
84
|
from letta.services.helpers.tool_execution_helper import prepare_local_sandbox
|
87
85
|
from letta.services.identity_manager import IdentityManager
|
88
86
|
from letta.services.job_manager import JobManager
|
89
87
|
from letta.services.llm_batch_manager import LLMBatchManager
|
88
|
+
from letta.services.mcp.base_client import AsyncBaseMCPClient
|
89
|
+
from letta.services.mcp.sse_client import MCP_CONFIG_TOPLEVEL_KEY, AsyncSSEMCPClient
|
90
|
+
from letta.services.mcp.stdio_client import AsyncStdioMCPClient
|
91
|
+
from letta.services.mcp_manager import MCPManager
|
90
92
|
from letta.services.message_manager import MessageManager
|
91
93
|
from letta.services.organization_manager import OrganizationManager
|
92
94
|
from letta.services.passage_manager import PassageManager
|
@@ -94,8 +96,8 @@ from letta.services.provider_manager import ProviderManager
|
|
94
96
|
from letta.services.sandbox_config_manager import SandboxConfigManager
|
95
97
|
from letta.services.source_manager import SourceManager
|
96
98
|
from letta.services.step_manager import StepManager
|
97
|
-
from letta.services.telemetry_manager import TelemetryManager
|
98
|
-
from letta.services.tool_executor.
|
99
|
+
from letta.services.telemetry_manager import NoopTelemetryManager, TelemetryManager
|
100
|
+
from letta.services.tool_executor.tool_execution_manager import ToolExecutionManager
|
99
101
|
from letta.services.tool_manager import ToolManager
|
100
102
|
from letta.services.user_manager import UserManager
|
101
103
|
from letta.settings import model_settings, settings, tool_settings
|
@@ -203,6 +205,7 @@ class SyncServer(Server):
|
|
203
205
|
self.passage_manager = PassageManager()
|
204
206
|
self.user_manager = UserManager()
|
205
207
|
self.tool_manager = ToolManager()
|
208
|
+
self.mcp_manager = MCPManager()
|
206
209
|
self.block_manager = BlockManager()
|
207
210
|
self.source_manager = SourceManager()
|
208
211
|
self.sandbox_config_manager = SandboxConfigManager()
|
@@ -215,6 +218,7 @@ class SyncServer(Server):
|
|
215
218
|
self.group_manager = GroupManager()
|
216
219
|
self.batch_manager = LLMBatchManager()
|
217
220
|
self.telemetry_manager = TelemetryManager()
|
221
|
+
self.file_agent_manager = FileAgentManager()
|
218
222
|
|
219
223
|
# A resusable httpx client
|
220
224
|
timeout = httpx.Timeout(connect=10.0, read=20.0, write=10.0, pool=10.0)
|
@@ -380,20 +384,31 @@ class SyncServer(Server):
|
|
380
384
|
self._enabled_providers.append(XAIProvider(name="xai", api_key=model_settings.xai_api_key))
|
381
385
|
|
382
386
|
# For MCP
|
387
|
+
# TODO: remove this
|
383
388
|
"""Initialize the MCP clients (there may be multiple)"""
|
389
|
+
self.mcp_clients: Dict[str, AsyncBaseMCPClient] = {}
|
390
|
+
|
391
|
+
# TODO: Remove these in memory caches
|
392
|
+
self._llm_config_cache = {}
|
393
|
+
self._embedding_config_cache = {}
|
394
|
+
|
395
|
+
# TODO: Replace this with the Anthropic client we have in house
|
396
|
+
self.anthropic_async_client = AsyncAnthropic()
|
397
|
+
|
398
|
+
async def init_mcp_clients(self):
|
399
|
+
# TODO: remove this
|
384
400
|
mcp_server_configs = self.get_mcp_servers()
|
385
|
-
self.mcp_clients: Dict[str, BaseMCPClient] = {}
|
386
401
|
|
387
402
|
for server_name, server_config in mcp_server_configs.items():
|
388
403
|
if server_config.type == MCPServerType.SSE:
|
389
|
-
self.mcp_clients[server_name] =
|
404
|
+
self.mcp_clients[server_name] = AsyncSSEMCPClient(server_config)
|
390
405
|
elif server_config.type == MCPServerType.STDIO:
|
391
|
-
self.mcp_clients[server_name] =
|
406
|
+
self.mcp_clients[server_name] = AsyncStdioMCPClient(server_config)
|
392
407
|
else:
|
393
408
|
raise ValueError(f"Invalid MCP server config: {server_config}")
|
394
409
|
|
395
410
|
try:
|
396
|
-
self.mcp_clients[server_name].connect_to_server()
|
411
|
+
await self.mcp_clients[server_name].connect_to_server()
|
397
412
|
except Exception as e:
|
398
413
|
logger.error(e)
|
399
414
|
self.mcp_clients.pop(server_name)
|
@@ -401,17 +416,10 @@ class SyncServer(Server):
|
|
401
416
|
# Print out the tools that are connected
|
402
417
|
for server_name, client in self.mcp_clients.items():
|
403
418
|
logger.info(f"Attempting to fetch tools from MCP server: {server_name}")
|
404
|
-
mcp_tools = client.list_tools()
|
419
|
+
mcp_tools = await client.list_tools()
|
405
420
|
logger.info(f"MCP tools connected: {', '.join([t.name for t in mcp_tools])}")
|
406
421
|
logger.debug(f"MCP tools: {', '.join([str(t) for t in mcp_tools])}")
|
407
422
|
|
408
|
-
# TODO: Remove these in memory caches
|
409
|
-
self._llm_config_cache = {}
|
410
|
-
self._embedding_config_cache = {}
|
411
|
-
|
412
|
-
# TODO: Replace this with the Anthropic client we have in house
|
413
|
-
self.anthropic_async_client = AsyncAnthropic()
|
414
|
-
|
415
423
|
def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
|
416
424
|
"""Updated method to load agents from persisted storage"""
|
417
425
|
agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
@@ -820,7 +828,10 @@ class SyncServer(Server):
|
|
820
828
|
) -> AgentState:
|
821
829
|
if request.llm_config is None:
|
822
830
|
if request.model is None:
|
823
|
-
|
831
|
+
if settings.default_llm_handle is None:
|
832
|
+
raise ValueError("Must specify either model or llm_config in request")
|
833
|
+
else:
|
834
|
+
request.model = settings.default_llm_handle
|
824
835
|
config_params = {
|
825
836
|
"handle": request.model,
|
826
837
|
"context_window_limit": request.context_window_limit,
|
@@ -834,7 +845,10 @@ class SyncServer(Server):
|
|
834
845
|
|
835
846
|
if request.embedding_config is None:
|
836
847
|
if request.embedding is None:
|
837
|
-
|
848
|
+
if settings.default_embedding_handle is None:
|
849
|
+
raise ValueError("Must specify either embedding or embedding_config in request")
|
850
|
+
else:
|
851
|
+
request.embedding = settings.default_embedding_handle
|
838
852
|
embedding_config_params = {
|
839
853
|
"handle": request.embedding,
|
840
854
|
"embedding_chunk_size": request.embedding_chunk_size or constants.DEFAULT_EMBEDDING_CHUNK_SIZE,
|
@@ -852,9 +866,9 @@ class SyncServer(Server):
|
|
852
866
|
|
853
867
|
if request.enable_sleeptime:
|
854
868
|
if request.agent_type == AgentType.voice_convo_agent:
|
855
|
-
main_agent = self.
|
869
|
+
main_agent = await self.create_voice_sleeptime_agent_async(main_agent=main_agent, actor=actor)
|
856
870
|
else:
|
857
|
-
main_agent = self.
|
871
|
+
main_agent = await self.create_sleeptime_agent_async(main_agent=main_agent, actor=actor)
|
858
872
|
|
859
873
|
return main_agent
|
860
874
|
|
@@ -897,12 +911,12 @@ class SyncServer(Server):
|
|
897
911
|
request.embedding_config = await self.get_embedding_config_from_handle_async(handle=request.embedding, actor=actor)
|
898
912
|
|
899
913
|
if request.enable_sleeptime:
|
900
|
-
agent = self.agent_manager.
|
914
|
+
agent = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
901
915
|
if agent.multi_agent_group is None:
|
902
916
|
if agent.agent_type == AgentType.voice_convo_agent:
|
903
|
-
self.
|
917
|
+
await self.create_voice_sleeptime_agent_async(main_agent=agent, actor=actor)
|
904
918
|
else:
|
905
|
-
self.
|
919
|
+
await self.create_sleeptime_agent_async(main_agent=agent, actor=actor)
|
906
920
|
|
907
921
|
return await self.agent_manager.update_agent_async(
|
908
922
|
agent_id=agent_id,
|
@@ -942,6 +956,38 @@ class SyncServer(Server):
|
|
942
956
|
)
|
943
957
|
return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
|
944
958
|
|
959
|
+
async def create_sleeptime_agent_async(self, main_agent: AgentState, actor: User) -> AgentState:
|
960
|
+
request = CreateAgent(
|
961
|
+
name=main_agent.name + "-sleeptime",
|
962
|
+
agent_type=AgentType.sleeptime_agent,
|
963
|
+
block_ids=[block.id for block in main_agent.memory.blocks],
|
964
|
+
memory_blocks=[
|
965
|
+
CreateBlock(
|
966
|
+
label="memory_persona",
|
967
|
+
value=get_persona_text("sleeptime_memory_persona"),
|
968
|
+
),
|
969
|
+
],
|
970
|
+
llm_config=main_agent.llm_config,
|
971
|
+
embedding_config=main_agent.embedding_config,
|
972
|
+
project_id=main_agent.project_id,
|
973
|
+
)
|
974
|
+
sleeptime_agent = await self.agent_manager.create_agent_async(
|
975
|
+
agent_create=request,
|
976
|
+
actor=actor,
|
977
|
+
)
|
978
|
+
await self.group_manager.create_group_async(
|
979
|
+
group=GroupCreate(
|
980
|
+
description="",
|
981
|
+
agent_ids=[sleeptime_agent.id],
|
982
|
+
manager_config=SleeptimeManager(
|
983
|
+
manager_agent_id=main_agent.id,
|
984
|
+
sleeptime_agent_frequency=5,
|
985
|
+
),
|
986
|
+
),
|
987
|
+
actor=actor,
|
988
|
+
)
|
989
|
+
return await self.agent_manager.get_agent_by_id_async(agent_id=main_agent.id, actor=actor)
|
990
|
+
|
945
991
|
def create_voice_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
|
946
992
|
# TODO: Inject system
|
947
993
|
request = CreateAgent(
|
@@ -976,6 +1022,40 @@ class SyncServer(Server):
|
|
976
1022
|
)
|
977
1023
|
return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
|
978
1024
|
|
1025
|
+
async def create_voice_sleeptime_agent_async(self, main_agent: AgentState, actor: User) -> AgentState:
|
1026
|
+
# TODO: Inject system
|
1027
|
+
request = CreateAgent(
|
1028
|
+
name=main_agent.name + "-sleeptime",
|
1029
|
+
agent_type=AgentType.voice_sleeptime_agent,
|
1030
|
+
block_ids=[block.id for block in main_agent.memory.blocks],
|
1031
|
+
memory_blocks=[
|
1032
|
+
CreateBlock(
|
1033
|
+
label="memory_persona",
|
1034
|
+
value=get_persona_text("voice_memory_persona"),
|
1035
|
+
),
|
1036
|
+
],
|
1037
|
+
llm_config=LLMConfig.default_config("gpt-4.1"),
|
1038
|
+
embedding_config=main_agent.embedding_config,
|
1039
|
+
project_id=main_agent.project_id,
|
1040
|
+
)
|
1041
|
+
voice_sleeptime_agent = await self.agent_manager.create_agent_async(
|
1042
|
+
agent_create=request,
|
1043
|
+
actor=actor,
|
1044
|
+
)
|
1045
|
+
await self.group_manager.create_group_async(
|
1046
|
+
group=GroupCreate(
|
1047
|
+
description="Low latency voice chat with async memory management.",
|
1048
|
+
agent_ids=[voice_sleeptime_agent.id],
|
1049
|
+
manager_config=VoiceSleeptimeManager(
|
1050
|
+
manager_agent_id=main_agent.id,
|
1051
|
+
max_message_buffer_length=constants.DEFAULT_MAX_MESSAGE_BUFFER_LENGTH,
|
1052
|
+
min_message_buffer_length=constants.DEFAULT_MIN_MESSAGE_BUFFER_LENGTH,
|
1053
|
+
),
|
1054
|
+
),
|
1055
|
+
actor=actor,
|
1056
|
+
)
|
1057
|
+
return await self.agent_manager.get_agent_by_id_async(agent_id=main_agent.id, actor=actor)
|
1058
|
+
|
979
1059
|
# convert name->id
|
980
1060
|
|
981
1061
|
# TODO: These can be moved to agent_manager
|
@@ -1057,6 +1137,20 @@ class SyncServer(Server):
|
|
1057
1137
|
|
1058
1138
|
return passages
|
1059
1139
|
|
1140
|
+
async def insert_archival_memory_async(self, agent_id: str, memory_contents: str, actor: User) -> List[Passage]:
|
1141
|
+
# Get the agent object (loaded in memory)
|
1142
|
+
agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
1143
|
+
# Insert into archival memory
|
1144
|
+
# TODO: @mindy look at moving this to agent_manager to avoid above extra call
|
1145
|
+
passages = await self.passage_manager.insert_passage_async(
|
1146
|
+
agent_state=agent_state, agent_id=agent_id, text=memory_contents, actor=actor
|
1147
|
+
)
|
1148
|
+
|
1149
|
+
# rebuild agent system prompt - force since no archival change
|
1150
|
+
await self.agent_manager.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True)
|
1151
|
+
|
1152
|
+
return passages
|
1153
|
+
|
1060
1154
|
def modify_archival_memory(self, agent_id: str, memory_id: str, passage: PassageUpdate, actor: User) -> List[Passage]:
|
1061
1155
|
passage = Passage(**passage.model_dump(exclude_unset=True, exclude_none=True))
|
1062
1156
|
passages = self.passage_manager.update_passage_by_id(passage_id=memory_id, passage=passage, actor=actor)
|
@@ -1073,6 +1167,17 @@ class SyncServer(Server):
|
|
1073
1167
|
# rebuild system prompt and force
|
1074
1168
|
self.agent_manager.rebuild_system_prompt(agent_id=passage.agent_id, actor=actor, force=True)
|
1075
1169
|
|
1170
|
+
async def delete_archival_memory_async(self, memory_id: str, actor: User):
|
1171
|
+
# TODO check if it exists first, and throw error if not
|
1172
|
+
# TODO: need to also rebuild the prompt here
|
1173
|
+
passage = await self.passage_manager.get_passage_by_id_async(passage_id=memory_id, actor=actor)
|
1174
|
+
|
1175
|
+
# delete the passage
|
1176
|
+
await self.passage_manager.delete_passage_by_id_async(passage_id=memory_id, actor=actor)
|
1177
|
+
|
1178
|
+
# rebuild system prompt and force
|
1179
|
+
await self.agent_manager.rebuild_system_prompt_async(agent_id=passage.agent_id, actor=actor, force=True)
|
1180
|
+
|
1076
1181
|
def get_agent_recall(
|
1077
1182
|
self,
|
1078
1183
|
user_id: str,
|
@@ -1193,20 +1298,17 @@ class SyncServer(Server):
|
|
1193
1298
|
await self.source_manager.delete_source(source_id=source_id, actor=actor)
|
1194
1299
|
|
1195
1300
|
# delete data from passage store
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
# TODO: make this async
|
1200
|
-
self.passage_manager.delete_passages(actor=actor, passages=passages_to_be_deleted)
|
1301
|
+
passages_to_be_deleted = await self.agent_manager.list_passages_async(actor=actor, source_id=source_id, limit=None)
|
1302
|
+
await self.passage_manager.delete_source_passages_async(actor=actor, passages=passages_to_be_deleted)
|
1201
1303
|
|
1202
1304
|
# TODO: delete data from agent passage stores (?)
|
1203
1305
|
|
1204
1306
|
async def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
|
1205
1307
|
|
1206
1308
|
# update job
|
1207
|
-
job = self.job_manager.
|
1309
|
+
job = await self.job_manager.get_job_by_id_async(job_id, actor=actor)
|
1208
1310
|
job.status = JobStatus.running
|
1209
|
-
self.job_manager.
|
1311
|
+
await self.job_manager.update_job_by_id_async(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
|
1210
1312
|
|
1211
1313
|
# try:
|
1212
1314
|
from letta.data_sources.connectors import DirectoryConnector
|
@@ -1225,43 +1327,137 @@ class SyncServer(Server):
|
|
1225
1327
|
|
1226
1328
|
# Attach source to agent
|
1227
1329
|
curr_passage_size = await self.agent_manager.passage_size_async(actor=actor, agent_id=agent_id)
|
1228
|
-
agent_state = self.agent_manager.
|
1330
|
+
agent_state = await self.agent_manager.attach_source_async(agent_id=agent_state.id, source_id=source_id, actor=actor)
|
1229
1331
|
new_passage_size = await self.agent_manager.passage_size_async(actor=actor, agent_id=agent_id)
|
1230
1332
|
assert new_passage_size >= curr_passage_size # in case empty files are added
|
1231
1333
|
|
1232
1334
|
# rebuild system prompt and force
|
1233
|
-
agent_state = self.agent_manager.
|
1335
|
+
agent_state = await self.agent_manager.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True)
|
1234
1336
|
|
1235
1337
|
# update job status
|
1236
1338
|
job.status = JobStatus.completed
|
1237
1339
|
job.metadata["num_passages"] = num_passages
|
1238
1340
|
job.metadata["num_documents"] = num_documents
|
1239
|
-
self.job_manager.
|
1341
|
+
await self.job_manager.update_job_by_id_async(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
|
1240
1342
|
|
1241
1343
|
return job
|
1242
1344
|
|
1243
|
-
def
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1345
|
+
async def load_file_to_source_via_mistral(self):
|
1346
|
+
pass
|
1347
|
+
|
1348
|
+
async def sleeptime_document_ingest_async(
|
1349
|
+
self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False
|
1350
|
+
) -> None:
|
1351
|
+
sleeptime_agent_state = await self.create_document_sleeptime_agent_async(main_agent, source, actor, clear_history)
|
1352
|
+
sleeptime_agent = LettaAgent(
|
1353
|
+
agent_id=sleeptime_agent_state.id,
|
1354
|
+
message_manager=self.message_manager,
|
1355
|
+
agent_manager=self.agent_manager,
|
1356
|
+
block_manager=self.block_manager,
|
1357
|
+
passage_manager=self.passage_manager,
|
1358
|
+
actor=actor,
|
1359
|
+
step_manager=self.step_manager,
|
1360
|
+
telemetry_manager=self.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1361
|
+
)
|
1362
|
+
passages = await self.agent_manager.list_passages_async(actor=actor, source_id=source.id)
|
1363
|
+
for passage in passages:
|
1364
|
+
await sleeptime_agent.step(
|
1248
1365
|
input_messages=[
|
1249
1366
|
MessageCreate(role="user", content=passage.text),
|
1250
1367
|
]
|
1251
1368
|
)
|
1252
|
-
self.agent_manager.
|
1369
|
+
await self.agent_manager.delete_agent_async(agent_id=sleeptime_agent_state.id, actor=actor)
|
1370
|
+
|
1371
|
+
async def _upsert_file_to_agent(self, agent_id: str, text: str, file_id: str, actor: User) -> None:
|
1372
|
+
"""
|
1373
|
+
Internal method to create or update a file <-> agent association
|
1374
|
+
"""
|
1375
|
+
truncated_text = text[:CORE_MEMORY_SOURCE_CHAR_LIMIT]
|
1376
|
+
await self.file_agent_manager.attach_file(agent_id=agent_id, file_id=file_id, actor=actor, visible_content=truncated_text)
|
1377
|
+
|
1378
|
+
async def _remove_file_from_agent(self, agent_id: str, file_id: str, actor: User) -> None:
|
1379
|
+
"""
|
1380
|
+
Internal method to remove a document block for an agent.
|
1381
|
+
"""
|
1382
|
+
try:
|
1383
|
+
await self.file_agent_manager.detach_file(
|
1384
|
+
agent_id=agent_id,
|
1385
|
+
file_id=file_id,
|
1386
|
+
actor=actor,
|
1387
|
+
)
|
1388
|
+
except NoResultFound:
|
1389
|
+
logger.info(f"File {file_id} already removed from agent {agent_id}, skipping...")
|
1390
|
+
|
1391
|
+
async def insert_file_into_context_windows(
|
1392
|
+
self, source_id: str, text: str, file_id: str, actor: User, agent_states: Optional[List[AgentState]] = None
|
1393
|
+
) -> List[AgentState]:
|
1394
|
+
"""
|
1395
|
+
Insert the uploaded document into the context window of all agents
|
1396
|
+
attached to the given source.
|
1397
|
+
"""
|
1398
|
+
agent_states = agent_states or await self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
1399
|
+
|
1400
|
+
# Return early
|
1401
|
+
if not agent_states:
|
1402
|
+
return []
|
1403
|
+
|
1404
|
+
logger.info(f"Inserting document into context window for source: {source_id}")
|
1405
|
+
logger.info(f"Attached agents: {[a.id for a in agent_states]}")
|
1406
|
+
|
1407
|
+
await asyncio.gather(*(self._upsert_file_to_agent(agent_state.id, text, file_id, actor) for agent_state in agent_states))
|
1408
|
+
|
1409
|
+
return agent_states
|
1410
|
+
|
1411
|
+
async def insert_files_into_context_window(self, agent_state: AgentState, texts: List[str], file_ids: List[str], actor: User) -> None:
|
1412
|
+
"""
|
1413
|
+
Insert the uploaded documents into the context window of an agent
|
1414
|
+
attached to the given source.
|
1415
|
+
"""
|
1416
|
+
logger.info(f"Inserting documents into context window for agent_state: {agent_state.id}")
|
1417
|
+
|
1418
|
+
if len(texts) != len(file_ids):
|
1419
|
+
raise ValueError(f"Mismatch between number of texts ({len(texts)}) and file ids ({len(file_ids)})")
|
1420
|
+
|
1421
|
+
await asyncio.gather(*(self._upsert_file_to_agent(agent_state.id, text, file_id, actor) for text, file_id in zip(texts, file_ids)))
|
1422
|
+
|
1423
|
+
async def remove_file_from_context_windows(self, source_id: str, file_id: str, actor: User) -> None:
|
1424
|
+
"""
|
1425
|
+
Remove the document from the context window of all agents
|
1426
|
+
attached to the given source.
|
1427
|
+
"""
|
1428
|
+
# TODO: We probably do NOT need to get the entire agent state, we can just get the IDs
|
1429
|
+
agent_states = await self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
1430
|
+
|
1431
|
+
# Return early
|
1432
|
+
if not agent_states:
|
1433
|
+
return
|
1434
|
+
|
1435
|
+
logger.info(f"Removing file from context window for source: {source_id}")
|
1436
|
+
logger.info(f"Attached agents: {[a.id for a in agent_states]}")
|
1437
|
+
|
1438
|
+
await asyncio.gather(*(self._remove_file_from_agent(agent_state.id, file_id, actor) for agent_state in agent_states))
|
1439
|
+
|
1440
|
+
async def remove_files_from_context_window(self, agent_state: AgentState, file_ids: List[str], actor: User) -> None:
|
1441
|
+
"""
|
1442
|
+
Remove multiple documents from the context window of an agent
|
1443
|
+
attached to the given source.
|
1444
|
+
"""
|
1445
|
+
logger.info(f"Removing files from context window for agent_state: {agent_state.id}")
|
1446
|
+
logger.info(f"Files to remove: {file_ids}")
|
1253
1447
|
|
1254
|
-
|
1448
|
+
await asyncio.gather(*(self._remove_file_from_agent(agent_state.id, file_id, actor) for file_id in file_ids))
|
1449
|
+
|
1450
|
+
async def create_document_sleeptime_agent_async(
|
1255
1451
|
self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False
|
1256
1452
|
) -> AgentState:
|
1257
1453
|
try:
|
1258
|
-
block = self.agent_manager.
|
1454
|
+
block = await self.agent_manager.get_block_with_label_async(agent_id=main_agent.id, block_label=source.name, actor=actor)
|
1259
1455
|
except:
|
1260
|
-
block = self.block_manager.
|
1261
|
-
self.agent_manager.
|
1456
|
+
block = await self.block_manager.create_or_update_block_async(Block(label=source.name, value=""), actor=actor)
|
1457
|
+
await self.agent_manager.attach_block_async(agent_id=main_agent.id, block_id=block.id, actor=actor)
|
1262
1458
|
|
1263
1459
|
if clear_history and block.value != "":
|
1264
|
-
block = self.block_manager.
|
1460
|
+
block = await self.block_manager.update_block_async(block_id=block.id, block=BlockUpdate(value=""))
|
1265
1461
|
|
1266
1462
|
request = CreateAgent(
|
1267
1463
|
name=main_agent.name + "-doc-sleeptime",
|
@@ -1284,7 +1480,7 @@ class SyncServer(Server):
|
|
1284
1480
|
include_base_tools=False,
|
1285
1481
|
tools=constants.BASE_SLEEPTIME_TOOLS,
|
1286
1482
|
)
|
1287
|
-
return self.agent_manager.
|
1483
|
+
return await self.agent_manager.create_agent_async(
|
1288
1484
|
agent_create=request,
|
1289
1485
|
actor=actor,
|
1290
1486
|
)
|
@@ -1299,13 +1495,13 @@ class SyncServer(Server):
|
|
1299
1495
|
# TODO: this should be implemented as a batch job or at least async, since it may take a long time
|
1300
1496
|
|
1301
1497
|
# load data from a data source into the document store
|
1302
|
-
|
1303
|
-
source = await self.source_manager.get_source_by_name(source_name=source_name, actor=
|
1498
|
+
actor = await self.user_manager.get_actor_by_id_async(actor_id=user_id)
|
1499
|
+
source = await self.source_manager.get_source_by_name(source_name=source_name, actor=actor)
|
1304
1500
|
if source is None:
|
1305
1501
|
raise ValueError(f"Data source {source_name} does not exist for user {user_id}")
|
1306
1502
|
|
1307
1503
|
# load data into the document store
|
1308
|
-
passage_count, document_count = await load_data(connector, source, self.passage_manager, self.source_manager, actor=
|
1504
|
+
passage_count, document_count = await load_data(connector, source, self.passage_manager, self.source_manager, actor=actor)
|
1309
1505
|
return passage_count, document_count
|
1310
1506
|
|
1311
1507
|
def list_data_source_passages(self, user_id: str, source_id: str) -> List[Passage]:
|
@@ -1352,16 +1548,6 @@ class SyncServer(Server):
|
|
1352
1548
|
# Get the current message
|
1353
1549
|
return self.message_manager.update_message_by_id(message_id=message_id, message_update=request, actor=actor)
|
1354
1550
|
|
1355
|
-
def get_organization_or_default(self, org_id: Optional[str]) -> Organization:
|
1356
|
-
"""Get the organization object for org_id if it exists, otherwise return the default organization object"""
|
1357
|
-
if org_id is None:
|
1358
|
-
org_id = self.organization_manager.DEFAULT_ORG_ID
|
1359
|
-
|
1360
|
-
try:
|
1361
|
-
return self.organization_manager.get_organization_by_id(org_id=org_id)
|
1362
|
-
except NoResultFound:
|
1363
|
-
raise HTTPException(status_code=404, detail=f"Organization with id {org_id} not found")
|
1364
|
-
|
1365
1551
|
def list_llm_models(
|
1366
1552
|
self,
|
1367
1553
|
actor: User,
|
@@ -1407,7 +1593,7 @@ class SyncServer(Server):
|
|
1407
1593
|
actor=actor,
|
1408
1594
|
)
|
1409
1595
|
|
1410
|
-
async def get_provider_models(provider):
|
1596
|
+
async def get_provider_models(provider: Provider) -> list[LLMConfig]:
|
1411
1597
|
try:
|
1412
1598
|
return await provider.list_llm_models_async()
|
1413
1599
|
except Exception as e:
|
@@ -1763,7 +1949,7 @@ class SyncServer(Server):
|
|
1763
1949
|
def add_embedding_model(self, request: EmbeddingConfig) -> EmbeddingConfig:
|
1764
1950
|
"""Add a new embedding model"""
|
1765
1951
|
|
1766
|
-
def run_tool_from_source(
|
1952
|
+
async def run_tool_from_source(
|
1767
1953
|
self,
|
1768
1954
|
actor: User,
|
1769
1955
|
tool_args: Dict[str, str],
|
@@ -1796,8 +1982,20 @@ class SyncServer(Server):
|
|
1796
1982
|
|
1797
1983
|
# Next, attempt to run the tool with the sandbox
|
1798
1984
|
try:
|
1799
|
-
|
1800
|
-
agent_state=agent_state,
|
1985
|
+
tool_execution_manager = ToolExecutionManager(
|
1986
|
+
agent_state=agent_state,
|
1987
|
+
message_manager=self.message_manager,
|
1988
|
+
agent_manager=self.agent_manager,
|
1989
|
+
block_manager=self.block_manager,
|
1990
|
+
passage_manager=self.passage_manager,
|
1991
|
+
actor=actor,
|
1992
|
+
sandbox_env_vars=tool_env_vars,
|
1993
|
+
)
|
1994
|
+
# TODO: Integrate sandbox result
|
1995
|
+
tool_execution_result = await tool_execution_manager.execute_tool_async(
|
1996
|
+
function_name=tool_name,
|
1997
|
+
function_args=tool_args,
|
1998
|
+
tool=tool,
|
1801
1999
|
)
|
1802
2000
|
return ToolReturnMessage(
|
1803
2001
|
id="null",
|
@@ -1852,7 +2050,8 @@ class SyncServer(Server):
|
|
1852
2050
|
|
1853
2051
|
# TODO implement non-flatfile mechanism
|
1854
2052
|
if not tool_settings.mcp_read_from_config:
|
1855
|
-
|
2053
|
+
return {}
|
2054
|
+
# raise RuntimeError("MCP config file disabled. Enable it in settings.")
|
1856
2055
|
|
1857
2056
|
mcp_server_list = {}
|
1858
2057
|
|
@@ -1906,14 +2105,14 @@ class SyncServer(Server):
|
|
1906
2105
|
# If the file doesn't exist, return empty dictionary
|
1907
2106
|
return mcp_server_list
|
1908
2107
|
|
1909
|
-
def get_tools_from_mcp_server(self, mcp_server_name: str) -> List[MCPTool]:
|
2108
|
+
async def get_tools_from_mcp_server(self, mcp_server_name: str) -> List[MCPTool]:
|
1910
2109
|
"""List the tools in an MCP server. Requires a client to be created."""
|
1911
2110
|
if mcp_server_name not in self.mcp_clients:
|
1912
2111
|
raise ValueError(f"No client was created for MCP server: {mcp_server_name}")
|
1913
2112
|
|
1914
|
-
return self.mcp_clients[mcp_server_name].list_tools()
|
2113
|
+
return await self.mcp_clients[mcp_server_name].list_tools()
|
1915
2114
|
|
1916
|
-
def add_mcp_server_to_config(
|
2115
|
+
async def add_mcp_server_to_config(
|
1917
2116
|
self, server_config: Union[SSEServerConfig, StdioServerConfig], allow_upsert: bool = True
|
1918
2117
|
) -> List[Union[SSEServerConfig, StdioServerConfig]]:
|
1919
2118
|
"""Add a new server config to the MCP config file"""
|
@@ -1942,19 +2141,19 @@ class SyncServer(Server):
|
|
1942
2141
|
|
1943
2142
|
# Attempt to initialize the connection to the server
|
1944
2143
|
if server_config.type == MCPServerType.SSE:
|
1945
|
-
new_mcp_client =
|
2144
|
+
new_mcp_client = AsyncSSEMCPClient(server_config)
|
1946
2145
|
elif server_config.type == MCPServerType.STDIO:
|
1947
|
-
new_mcp_client =
|
2146
|
+
new_mcp_client = AsyncStdioMCPClient(server_config)
|
1948
2147
|
else:
|
1949
2148
|
raise ValueError(f"Invalid MCP server config: {server_config}")
|
1950
2149
|
try:
|
1951
|
-
new_mcp_client.connect_to_server()
|
2150
|
+
await new_mcp_client.connect_to_server()
|
1952
2151
|
except:
|
1953
2152
|
logger.exception(f"Failed to connect to MCP server: {server_config.server_name}")
|
1954
2153
|
raise RuntimeError(f"Failed to connect to MCP server: {server_config.server_name}")
|
1955
2154
|
# Print out the tools that are connected
|
1956
2155
|
logger.info(f"Attempting to fetch tools from MCP server: {server_config.server_name}")
|
1957
|
-
new_mcp_tools = new_mcp_client.list_tools()
|
2156
|
+
new_mcp_tools = await new_mcp_client.list_tools()
|
1958
2157
|
logger.info(f"MCP tools connected: {', '.join([t.name for t in new_mcp_tools])}")
|
1959
2158
|
logger.debug(f"MCP tools: {', '.join([str(t) for t in new_mcp_tools])}")
|
1960
2159
|
|