letta-nightly 0.6.4.dev20241213193437__py3-none-any.whl → 0.6.4.dev20241214104034__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/agent.py +54 -45
- letta/chat_only_agent.py +6 -8
- letta/cli/cli.py +2 -10
- letta/client/client.py +121 -138
- letta/config.py +0 -161
- letta/main.py +3 -8
- letta/memory.py +3 -14
- letta/o1_agent.py +1 -5
- letta/offline_memory_agent.py +2 -6
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +109 -0
- letta/orm/agents_tags.py +10 -18
- letta/orm/block.py +29 -4
- letta/orm/blocks_agents.py +5 -11
- letta/orm/custom_columns.py +152 -0
- letta/orm/message.py +3 -38
- letta/orm/organization.py +2 -7
- letta/orm/passage.py +10 -32
- letta/orm/source.py +5 -25
- letta/orm/sources_agents.py +13 -0
- letta/orm/sqlalchemy_base.py +54 -30
- letta/orm/tool.py +1 -19
- letta/orm/tools_agents.py +7 -24
- letta/orm/user.py +3 -4
- letta/schemas/agent.py +48 -65
- letta/schemas/memory.py +2 -1
- letta/schemas/sandbox_config.py +12 -1
- letta/server/rest_api/app.py +0 -5
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +1 -1
- letta/server/rest_api/routers/v1/agents.py +99 -78
- letta/server/rest_api/routers/v1/blocks.py +22 -25
- letta/server/rest_api/routers/v1/jobs.py +4 -4
- letta/server/rest_api/routers/v1/sandbox_configs.py +10 -10
- letta/server/rest_api/routers/v1/sources.py +12 -12
- letta/server/rest_api/routers/v1/tools.py +35 -15
- letta/server/rest_api/routers/v1/users.py +0 -46
- letta/server/server.py +172 -716
- letta/server/ws_api/server.py +0 -5
- letta/services/agent_manager.py +405 -0
- letta/services/block_manager.py +13 -21
- letta/services/helpers/agent_manager_helper.py +90 -0
- letta/services/organization_manager.py +0 -1
- letta/services/passage_manager.py +62 -62
- letta/services/sandbox_config_manager.py +3 -3
- letta/services/source_manager.py +22 -1
- letta/services/user_manager.py +11 -6
- letta/utils.py +2 -2
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/RECORD +53 -57
- letta/metadata.py +0 -407
- letta/schemas/agents_tags.py +0 -33
- letta/schemas/api_key.py +0 -21
- letta/schemas/blocks_agents.py +0 -32
- letta/schemas/tools_agents.py +0 -32
- letta/server/rest_api/routers/openai/assistants/threads.py +0 -338
- letta/services/agents_tags_manager.py +0 -64
- letta/services/blocks_agents_manager.py +0 -106
- letta/services/tools_agents_manager.py +0 -94
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/entry_points.txt +0 -0
letta/server/server.py
CHANGED
|
@@ -19,18 +19,16 @@ from letta.agent import Agent, save_agent
|
|
|
19
19
|
from letta.chat_only_agent import ChatOnlyAgent
|
|
20
20
|
from letta.credentials import LettaCredentials
|
|
21
21
|
from letta.data_sources.connectors import DataConnector, load_data
|
|
22
|
-
from letta.errors import LettaAgentNotFoundError
|
|
22
|
+
from letta.errors import LettaAgentNotFoundError
|
|
23
23
|
|
|
24
24
|
# TODO use custom interface
|
|
25
25
|
from letta.interface import AgentInterface # abstract
|
|
26
26
|
from letta.interface import CLIInterface # for printing to terminal
|
|
27
27
|
from letta.log import get_logger
|
|
28
|
-
from letta.metadata import MetadataStore
|
|
29
28
|
from letta.o1_agent import O1Agent
|
|
30
29
|
from letta.offline_memory_agent import OfflineMemoryAgent
|
|
31
30
|
from letta.orm import Base
|
|
32
31
|
from letta.orm.errors import NoResultFound
|
|
33
|
-
from letta.prompts import gpt_system
|
|
34
32
|
from letta.providers import (
|
|
35
33
|
AnthropicProvider,
|
|
36
34
|
AzureProvider,
|
|
@@ -44,15 +42,8 @@ from letta.providers import (
|
|
|
44
42
|
VLLMChatCompletionsProvider,
|
|
45
43
|
VLLMCompletionsProvider,
|
|
46
44
|
)
|
|
47
|
-
from letta.schemas.agent import
|
|
48
|
-
|
|
49
|
-
AgentType,
|
|
50
|
-
CreateAgent,
|
|
51
|
-
PersistedAgentState,
|
|
52
|
-
UpdateAgentState,
|
|
53
|
-
)
|
|
54
|
-
from letta.schemas.api_key import APIKey, APIKeyCreate
|
|
55
|
-
from letta.schemas.block import Block, BlockUpdate
|
|
45
|
+
from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgent
|
|
46
|
+
from letta.schemas.block import BlockUpdate
|
|
56
47
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
57
48
|
|
|
58
49
|
# openai schemas
|
|
@@ -68,14 +59,13 @@ from letta.schemas.memory import (
|
|
|
68
59
|
)
|
|
69
60
|
from letta.schemas.message import Message, MessageCreate, MessageRole, MessageUpdate
|
|
70
61
|
from letta.schemas.organization import Organization
|
|
71
|
-
from letta.schemas.passage import Passage
|
|
62
|
+
from letta.schemas.passage import Passage
|
|
72
63
|
from letta.schemas.source import Source
|
|
73
64
|
from letta.schemas.tool import Tool, ToolCreate
|
|
74
65
|
from letta.schemas.usage import LettaUsageStatistics
|
|
75
|
-
from letta.schemas.user import User
|
|
76
|
-
from letta.services.
|
|
66
|
+
from letta.schemas.user import User
|
|
67
|
+
from letta.services.agent_manager import AgentManager
|
|
77
68
|
from letta.services.block_manager import BlockManager
|
|
78
|
-
from letta.services.blocks_agents_manager import BlocksAgentsManager
|
|
79
69
|
from letta.services.job_manager import JobManager
|
|
80
70
|
from letta.services.message_manager import MessageManager
|
|
81
71
|
from letta.services.organization_manager import OrganizationManager
|
|
@@ -85,9 +75,8 @@ from letta.services.sandbox_config_manager import SandboxConfigManager
|
|
|
85
75
|
from letta.services.source_manager import SourceManager
|
|
86
76
|
from letta.services.tool_execution_sandbox import ToolExecutionSandbox
|
|
87
77
|
from letta.services.tool_manager import ToolManager
|
|
88
|
-
from letta.services.tools_agents_manager import ToolsAgentsManager
|
|
89
78
|
from letta.services.user_manager import UserManager
|
|
90
|
-
from letta.utils import
|
|
79
|
+
from letta.utils import get_utc_time, json_dumps, json_loads
|
|
91
80
|
|
|
92
81
|
logger = get_logger(__name__)
|
|
93
82
|
|
|
@@ -105,18 +94,13 @@ class Server(object):
|
|
|
105
94
|
"""Return the memory of an agent (core memory + non-core statistics)"""
|
|
106
95
|
raise NotImplementedError
|
|
107
96
|
|
|
108
|
-
@abstractmethod
|
|
109
|
-
def get_agent_state(self, user_id: str, agent_id: str) -> dict:
|
|
110
|
-
"""Return the config of an agent"""
|
|
111
|
-
raise NotImplementedError
|
|
112
|
-
|
|
113
97
|
@abstractmethod
|
|
114
98
|
def get_server_config(self, user_id: str) -> dict:
|
|
115
99
|
"""Return the base config"""
|
|
116
100
|
raise NotImplementedError
|
|
117
101
|
|
|
118
102
|
@abstractmethod
|
|
119
|
-
def update_agent_core_memory(self, user_id: str, agent_id: str,
|
|
103
|
+
def update_agent_core_memory(self, user_id: str, agent_id: str, label: str, actor: User) -> Memory:
|
|
120
104
|
"""Update the agents core memory block, return the new state"""
|
|
121
105
|
raise NotImplementedError
|
|
122
106
|
|
|
@@ -124,7 +108,7 @@ class Server(object):
|
|
|
124
108
|
def create_agent(
|
|
125
109
|
self,
|
|
126
110
|
request: CreateAgent,
|
|
127
|
-
actor:
|
|
111
|
+
actor: User,
|
|
128
112
|
# interface
|
|
129
113
|
interface: Union[AgentInterface, None] = None,
|
|
130
114
|
) -> AgentState:
|
|
@@ -270,10 +254,6 @@ class SyncServer(Server):
|
|
|
270
254
|
# auth_mode: str = "none", # "none, "jwt", "external"
|
|
271
255
|
):
|
|
272
256
|
"""Server process holds in-memory agents that are being run"""
|
|
273
|
-
|
|
274
|
-
# List of {'user_id': user_id, 'agent_id': agent_id, 'agent': agent_obj} dicts
|
|
275
|
-
self.active_agents = []
|
|
276
|
-
|
|
277
257
|
# chaining = whether or not to run again if request_heartbeat=true
|
|
278
258
|
self.chaining = chaining
|
|
279
259
|
|
|
@@ -298,7 +278,6 @@ class SyncServer(Server):
|
|
|
298
278
|
config.archival_storage_uri = settings.letta_pg_uri_no_default
|
|
299
279
|
config.save()
|
|
300
280
|
self.config = config
|
|
301
|
-
self.ms = MetadataStore(self.config)
|
|
302
281
|
|
|
303
282
|
# Managers that interface with data models
|
|
304
283
|
self.organization_manager = OrganizationManager()
|
|
@@ -307,12 +286,10 @@ class SyncServer(Server):
|
|
|
307
286
|
self.tool_manager = ToolManager()
|
|
308
287
|
self.block_manager = BlockManager()
|
|
309
288
|
self.source_manager = SourceManager()
|
|
310
|
-
self.agents_tags_manager = AgentsTagsManager()
|
|
311
289
|
self.sandbox_config_manager = SandboxConfigManager(tool_settings)
|
|
312
|
-
self.blocks_agents_manager = BlocksAgentsManager()
|
|
313
290
|
self.message_manager = MessageManager()
|
|
314
|
-
self.tools_agents_manager = ToolsAgentsManager()
|
|
315
291
|
self.job_manager = JobManager()
|
|
292
|
+
self.agent_manager = AgentManager()
|
|
316
293
|
|
|
317
294
|
# Managers that interface with parallelism
|
|
318
295
|
self.per_agent_lock_manager = PerAgentLockManager()
|
|
@@ -397,42 +374,9 @@ class SyncServer(Server):
|
|
|
397
374
|
)
|
|
398
375
|
)
|
|
399
376
|
|
|
400
|
-
def
|
|
401
|
-
"""Saves all the agents that are in the in-memory object store"""
|
|
402
|
-
for agent_d in self.active_agents:
|
|
403
|
-
try:
|
|
404
|
-
save_agent(agent_d["agent"], self.ms)
|
|
405
|
-
logger.info(f"Saved agent {agent_d['agent_id']}")
|
|
406
|
-
except Exception as e:
|
|
407
|
-
logger.exception(f"Error occurred while trying to save agent {agent_d['agent_id']}:\n{e}")
|
|
408
|
-
|
|
409
|
-
def _get_agent(self, user_id: str, agent_id: str) -> Union[Agent, None]:
|
|
410
|
-
"""Get the agent object from the in-memory object store"""
|
|
411
|
-
for d in self.active_agents:
|
|
412
|
-
if d["user_id"] == str(user_id) and d["agent_id"] == str(agent_id):
|
|
413
|
-
return d["agent"]
|
|
414
|
-
return None
|
|
415
|
-
|
|
416
|
-
def _add_agent(self, user_id: str, agent_id: str, agent_obj: Agent) -> None:
|
|
417
|
-
"""Put an agent object inside the in-memory object store"""
|
|
418
|
-
# Make sure the agent doesn't already exist
|
|
419
|
-
if self._get_agent(user_id=user_id, agent_id=agent_id) is not None:
|
|
420
|
-
# Can be triggered on concucrent request, so don't throw a full error
|
|
421
|
-
logger.exception(f"Agent (user={user_id}, agent={agent_id}) is already loaded")
|
|
422
|
-
return
|
|
423
|
-
# Add Agent instance to the in-memory list
|
|
424
|
-
self.active_agents.append(
|
|
425
|
-
{
|
|
426
|
-
"user_id": str(user_id),
|
|
427
|
-
"agent_id": str(agent_id),
|
|
428
|
-
"agent": agent_obj,
|
|
429
|
-
}
|
|
430
|
-
)
|
|
431
|
-
|
|
432
|
-
def initialize_agent(self, agent_id, interface: Union[AgentInterface, None] = None, initial_message_sequence=None) -> Agent:
|
|
377
|
+
def initialize_agent(self, agent_id, actor, interface: Union[AgentInterface, None] = None, initial_message_sequence=None) -> Agent:
|
|
433
378
|
"""Initialize an agent from the database"""
|
|
434
|
-
agent_state = self.
|
|
435
|
-
actor = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
|
|
379
|
+
agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
|
436
380
|
|
|
437
381
|
interface = interface or self.default_interface_factory()
|
|
438
382
|
if agent_state.agent_type == AgentType.memgpt_agent:
|
|
@@ -446,19 +390,17 @@ class SyncServer(Server):
|
|
|
446
390
|
agent = O1Agent(agent_state=agent_state, interface=interface, user=actor)
|
|
447
391
|
|
|
448
392
|
# Persist to agent
|
|
449
|
-
save_agent(agent
|
|
393
|
+
save_agent(agent)
|
|
450
394
|
return agent
|
|
451
395
|
|
|
452
|
-
def load_agent(self, agent_id: str, interface: Union[AgentInterface, None] = None) -> Agent:
|
|
396
|
+
def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
|
|
453
397
|
"""Updated method to load agents from persisted storage"""
|
|
454
398
|
agent_lock = self.per_agent_lock_manager.get_lock(agent_id)
|
|
455
399
|
with agent_lock:
|
|
456
|
-
agent_state = self.
|
|
400
|
+
agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
|
401
|
+
|
|
457
402
|
if agent_state is None:
|
|
458
403
|
raise LettaAgentNotFoundError(f"Agent (agent_id={agent_id}) does not exist")
|
|
459
|
-
elif agent_state.user_id is None:
|
|
460
|
-
raise ValueError(f"Agent (agent_id={agent_id}) does not have a user_id")
|
|
461
|
-
actor = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
|
|
462
404
|
|
|
463
405
|
interface = interface or self.default_interface_factory()
|
|
464
406
|
if agent_state.agent_type == AgentType.memgpt_agent:
|
|
@@ -476,19 +418,19 @@ class SyncServer(Server):
|
|
|
476
418
|
agent.rebuild_system_prompt()
|
|
477
419
|
|
|
478
420
|
# Persist to agent
|
|
479
|
-
save_agent(agent
|
|
421
|
+
save_agent(agent)
|
|
480
422
|
return agent
|
|
481
423
|
|
|
482
424
|
def _step(
|
|
483
425
|
self,
|
|
484
|
-
|
|
426
|
+
actor: User,
|
|
485
427
|
agent_id: str,
|
|
486
428
|
input_messages: Union[Message, List[Message]],
|
|
487
429
|
interface: Union[AgentInterface, None] = None, # needed to getting responses
|
|
488
430
|
# timestamp: Optional[datetime],
|
|
489
431
|
) -> LettaUsageStatistics:
|
|
490
432
|
"""Send the input message through the agent"""
|
|
491
|
-
|
|
433
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
492
434
|
# Input validation
|
|
493
435
|
if isinstance(input_messages, Message):
|
|
494
436
|
input_messages = [input_messages]
|
|
@@ -498,12 +440,9 @@ class SyncServer(Server):
|
|
|
498
440
|
logger.debug(f"Got input messages: {input_messages}")
|
|
499
441
|
letta_agent = None
|
|
500
442
|
try:
|
|
501
|
-
|
|
502
|
-
# Get the agent object (loaded in memory)
|
|
503
|
-
# letta_agent = self._get_or_load_agent(agent_id=agent_id)
|
|
504
|
-
letta_agent = self.load_agent(agent_id=agent_id, interface=interface)
|
|
443
|
+
letta_agent = self.load_agent(agent_id=agent_id, interface=interface, actor=actor)
|
|
505
444
|
if letta_agent is None:
|
|
506
|
-
raise KeyError(f"Agent (user={
|
|
445
|
+
raise KeyError(f"Agent (user={actor.id}, agent={agent_id}) is not loaded")
|
|
507
446
|
|
|
508
447
|
# Determine whether or not to token stream based on the capability of the interface
|
|
509
448
|
token_streaming = letta_agent.interface.streaming_mode if hasattr(letta_agent.interface, "streaming_mode") else False
|
|
@@ -514,12 +453,11 @@ class SyncServer(Server):
|
|
|
514
453
|
chaining=self.chaining,
|
|
515
454
|
max_chaining_steps=self.max_chaining_steps,
|
|
516
455
|
stream=token_streaming,
|
|
517
|
-
ms=self.ms,
|
|
518
456
|
skip_verify=True,
|
|
519
457
|
)
|
|
520
458
|
|
|
521
459
|
# save agent after step
|
|
522
|
-
save_agent(letta_agent
|
|
460
|
+
save_agent(letta_agent)
|
|
523
461
|
|
|
524
462
|
except Exception as e:
|
|
525
463
|
logger.error(f"Error in server._step: {e}")
|
|
@@ -534,11 +472,13 @@ class SyncServer(Server):
|
|
|
534
472
|
|
|
535
473
|
def _command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
|
|
536
474
|
"""Process a CLI command"""
|
|
475
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
476
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
537
477
|
|
|
538
478
|
logger.debug(f"Got command: {command}")
|
|
539
479
|
|
|
540
480
|
# Get the agent object (loaded in memory)
|
|
541
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
481
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
542
482
|
usage = None
|
|
543
483
|
|
|
544
484
|
if command.lower() == "exit":
|
|
@@ -546,7 +486,7 @@ class SyncServer(Server):
|
|
|
546
486
|
raise ValueError(command)
|
|
547
487
|
|
|
548
488
|
elif command.lower() == "save" or command.lower() == "savechat":
|
|
549
|
-
save_agent(letta_agent
|
|
489
|
+
save_agent(letta_agent)
|
|
550
490
|
|
|
551
491
|
elif command.lower() == "attach":
|
|
552
492
|
# Different from CLI, we extract the data source name from the command
|
|
@@ -560,8 +500,8 @@ class SyncServer(Server):
|
|
|
560
500
|
letta_agent.attach_source(
|
|
561
501
|
user=self.user_manager.get_user_by_id(user_id=user_id),
|
|
562
502
|
source_id=data_source,
|
|
563
|
-
source_manager=
|
|
564
|
-
|
|
503
|
+
source_manager=self.source_manager,
|
|
504
|
+
agent_manager=self.agent_manager,
|
|
565
505
|
)
|
|
566
506
|
|
|
567
507
|
elif command.lower() == "dump" or command.lower().startswith("dump "):
|
|
@@ -637,11 +577,11 @@ class SyncServer(Server):
|
|
|
637
577
|
|
|
638
578
|
elif command.lower() == "heartbeat":
|
|
639
579
|
input_message = system.get_heartbeat()
|
|
640
|
-
usage = self._step(
|
|
580
|
+
usage = self._step(actor=actor, agent_id=agent_id, input_message=input_message)
|
|
641
581
|
|
|
642
582
|
elif command.lower() == "memorywarning":
|
|
643
583
|
input_message = system.get_token_limit_warning()
|
|
644
|
-
usage = self._step(
|
|
584
|
+
usage = self._step(actor=actor, agent_id=agent_id, input_message=input_message)
|
|
645
585
|
|
|
646
586
|
if not usage:
|
|
647
587
|
usage = LettaUsageStatistics()
|
|
@@ -656,9 +596,14 @@ class SyncServer(Server):
|
|
|
656
596
|
timestamp: Optional[datetime] = None,
|
|
657
597
|
) -> LettaUsageStatistics:
|
|
658
598
|
"""Process an incoming user message and feed it through the Letta agent"""
|
|
659
|
-
|
|
599
|
+
try:
|
|
600
|
+
actor = self.user_manager.get_user_by_id(user_id=user_id)
|
|
601
|
+
except NoResultFound:
|
|
660
602
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
661
|
-
|
|
603
|
+
|
|
604
|
+
try:
|
|
605
|
+
agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
|
606
|
+
except NoResultFound:
|
|
662
607
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
663
608
|
|
|
664
609
|
# Basic input sanitization
|
|
@@ -692,7 +637,7 @@ class SyncServer(Server):
|
|
|
692
637
|
)
|
|
693
638
|
|
|
694
639
|
# Run the agent state forward
|
|
695
|
-
usage = self._step(
|
|
640
|
+
usage = self._step(actor=actor, agent_id=agent_id, input_messages=message)
|
|
696
641
|
return usage
|
|
697
642
|
|
|
698
643
|
def system_message(
|
|
@@ -703,9 +648,14 @@ class SyncServer(Server):
|
|
|
703
648
|
timestamp: Optional[datetime] = None,
|
|
704
649
|
) -> LettaUsageStatistics:
|
|
705
650
|
"""Process an incoming system message and feed it through the Letta agent"""
|
|
706
|
-
|
|
651
|
+
try:
|
|
652
|
+
actor = self.user_manager.get_user_by_id(user_id=user_id)
|
|
653
|
+
except NoResultFound:
|
|
707
654
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
708
|
-
|
|
655
|
+
|
|
656
|
+
try:
|
|
657
|
+
agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
|
658
|
+
except NoResultFound:
|
|
709
659
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
710
660
|
|
|
711
661
|
# Basic input sanitization
|
|
@@ -752,11 +702,11 @@ class SyncServer(Server):
|
|
|
752
702
|
message.created_at = timestamp
|
|
753
703
|
|
|
754
704
|
# Run the agent state forward
|
|
755
|
-
return self._step(
|
|
705
|
+
return self._step(actor=actor, agent_id=agent_id, input_messages=message)
|
|
756
706
|
|
|
757
707
|
def send_messages(
|
|
758
708
|
self,
|
|
759
|
-
|
|
709
|
+
actor: User,
|
|
760
710
|
agent_id: str,
|
|
761
711
|
messages: Union[List[MessageCreate], List[Message]],
|
|
762
712
|
# whether or not to wrap user and system message as MemGPT-style stringified JSON
|
|
@@ -771,11 +721,6 @@ class SyncServer(Server):
|
|
|
771
721
|
|
|
772
722
|
Otherwise, we can pass them in directly.
|
|
773
723
|
"""
|
|
774
|
-
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
775
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
776
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
777
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
778
|
-
|
|
779
724
|
message_objects: List[Message] = []
|
|
780
725
|
|
|
781
726
|
if all(isinstance(m, MessageCreate) for m in messages):
|
|
@@ -814,16 +759,11 @@ class SyncServer(Server):
|
|
|
814
759
|
raise ValueError(f"All messages must be of type Message or MessageCreate, got {[type(message) for message in messages]}")
|
|
815
760
|
|
|
816
761
|
# Run the agent state forward
|
|
817
|
-
return self._step(
|
|
762
|
+
return self._step(actor=actor, agent_id=agent_id, input_messages=message_objects, interface=interface)
|
|
818
763
|
|
|
819
764
|
# @LockingServer.agent_lock_decorator
|
|
820
765
|
def run_command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
|
|
821
766
|
"""Run a command on the agent"""
|
|
822
|
-
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
823
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
824
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
825
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
826
|
-
|
|
827
767
|
# If the input begins with a command prefix, attempt to process it as a command
|
|
828
768
|
if command.startswith("/"):
|
|
829
769
|
if len(command) > 1:
|
|
@@ -833,86 +773,16 @@ class SyncServer(Server):
|
|
|
833
773
|
def create_agent(
|
|
834
774
|
self,
|
|
835
775
|
request: CreateAgent,
|
|
836
|
-
actor:
|
|
776
|
+
actor: User,
|
|
837
777
|
# interface
|
|
838
778
|
interface: Union[AgentInterface, None] = None,
|
|
839
779
|
) -> AgentState:
|
|
840
780
|
"""Create a new agent using a config"""
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
if interface is None:
|
|
846
|
-
interface = self.default_interface_factory()
|
|
847
|
-
|
|
848
|
-
# create agent name
|
|
849
|
-
if request.name is None:
|
|
850
|
-
request.name = create_random_username()
|
|
851
|
-
|
|
852
|
-
if request.agent_type is None:
|
|
853
|
-
request.agent_type = AgentType.memgpt_agent
|
|
854
|
-
|
|
855
|
-
# system debug
|
|
856
|
-
if request.system is None:
|
|
857
|
-
# TODO: don't hardcode
|
|
858
|
-
if request.agent_type == AgentType.memgpt_agent:
|
|
859
|
-
request.system = gpt_system.get_system_text("memgpt_chat")
|
|
860
|
-
elif request.agent_type == AgentType.o1_agent:
|
|
861
|
-
request.system = gpt_system.get_system_text("memgpt_modified_o1")
|
|
862
|
-
elif request.agent_type == AgentType.offline_memory_agent:
|
|
863
|
-
request.system = gpt_system.get_system_text("memgpt_offline_memory")
|
|
864
|
-
elif request.agent_type == AgentType.chat_only_agent:
|
|
865
|
-
request.system = gpt_system.get_system_text("memgpt_convo_only")
|
|
866
|
-
else:
|
|
867
|
-
raise ValueError(f"Invalid agent type: {request.agent_type}")
|
|
868
|
-
|
|
869
|
-
# create blocks (note: cannot be linked into the agent_id is created)
|
|
870
|
-
blocks = []
|
|
871
|
-
for create_block in request.memory_blocks:
|
|
872
|
-
block = self.block_manager.create_or_update_block(Block(**create_block.model_dump()), actor=actor)
|
|
873
|
-
blocks.append(block)
|
|
874
|
-
|
|
875
|
-
# get tools + only add if they exist
|
|
876
|
-
tool_objs = []
|
|
877
|
-
if request.tools:
|
|
878
|
-
for tool_name in request.tools:
|
|
879
|
-
tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
|
880
|
-
if tool_obj:
|
|
881
|
-
tool_objs.append(tool_obj)
|
|
882
|
-
else:
|
|
883
|
-
warnings.warn(f"Attempted to add a nonexistent tool {tool_name} to agent {request.name}, skipping.")
|
|
884
|
-
# reset the request.tools to only valid tools
|
|
885
|
-
request.tools = [t.name for t in tool_objs]
|
|
886
|
-
|
|
887
|
-
# get the user
|
|
888
|
-
logger.debug(f"Attempting to find user: {user_id}")
|
|
889
|
-
user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
890
|
-
if not user:
|
|
891
|
-
raise ValueError(f"cannot find user with associated client id: {user_id}")
|
|
892
|
-
|
|
893
|
-
if request.llm_config is None:
|
|
894
|
-
raise ValueError("llm_config is required")
|
|
895
|
-
|
|
896
|
-
if request.embedding_config is None:
|
|
897
|
-
raise ValueError("embedding_config is required")
|
|
898
|
-
|
|
899
|
-
# created and persist the agent state in the DB
|
|
900
|
-
agent_state = PersistedAgentState(
|
|
901
|
-
name=request.name,
|
|
902
|
-
user_id=user_id,
|
|
903
|
-
tool_names=request.tools if request.tools else [],
|
|
904
|
-
tool_rules=request.tool_rules,
|
|
905
|
-
agent_type=request.agent_type or AgentType.memgpt_agent,
|
|
906
|
-
llm_config=request.llm_config,
|
|
907
|
-
embedding_config=request.embedding_config,
|
|
908
|
-
system=request.system,
|
|
909
|
-
# other metadata
|
|
910
|
-
description=request.description,
|
|
911
|
-
metadata_=request.metadata_,
|
|
781
|
+
# Invoke manager
|
|
782
|
+
agent_state = self.agent_manager.create_agent(
|
|
783
|
+
agent_create=request,
|
|
784
|
+
actor=actor,
|
|
912
785
|
)
|
|
913
|
-
# TODO: move this to agent ORM
|
|
914
|
-
# this saves the agent ID and state into the DB
|
|
915
|
-
self.ms.create_agent(agent_state)
|
|
916
786
|
|
|
917
787
|
# create the agent object
|
|
918
788
|
if request.initial_message_sequence is not None:
|
|
@@ -937,81 +807,29 @@ class SyncServer(Server):
|
|
|
937
807
|
init_messages = None
|
|
938
808
|
|
|
939
809
|
# initialize the agent (generates initial message list with system prompt)
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
# TODO: add source mappings here as well
|
|
944
|
-
|
|
945
|
-
# create the tags
|
|
946
|
-
if request.tags:
|
|
947
|
-
for tag in request.tags:
|
|
948
|
-
self.agents_tags_manager.add_tag_to_agent(agent_id=agent_state.id, tag=tag, actor=actor)
|
|
949
|
-
|
|
950
|
-
# create block mappins (now that agent is persisted)
|
|
951
|
-
for block in blocks:
|
|
952
|
-
# this links the created block to the agent
|
|
953
|
-
self.blocks_agents_manager.add_block_to_agent(block_id=block.id, agent_id=agent_state.id, block_label=block.label)
|
|
810
|
+
if interface is None:
|
|
811
|
+
interface = self.default_interface_factory()
|
|
812
|
+
self.initialize_agent(agent_id=agent_state.id, interface=interface, initial_message_sequence=init_messages, actor=actor)
|
|
954
813
|
|
|
955
|
-
in_memory_agent_state = self.
|
|
814
|
+
in_memory_agent_state = self.agent_manager.get_agent_by_id(agent_state.id, actor=actor)
|
|
956
815
|
return in_memory_agent_state
|
|
957
816
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
This gathers data accross multiple tables to provide the full state of an agent, which is passed into the `Agent` object for creation.
|
|
962
|
-
"""
|
|
963
|
-
|
|
964
|
-
# get data persisted from the DB
|
|
965
|
-
agent_state = self.ms.get_agent(agent_id=agent_id)
|
|
966
|
-
if agent_state is None:
|
|
967
|
-
# agent does not exist
|
|
968
|
-
return None
|
|
969
|
-
if agent_state.user_id is None:
|
|
970
|
-
raise ValueError(f"Agent {agent_id} does not have a user_id")
|
|
971
|
-
user = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
|
|
972
|
-
|
|
973
|
-
# construct the in-memory, full agent state - this gather data stored in different tables but that needs to be passed to `Agent`
|
|
974
|
-
# we also return this data to the user to provide all the state related to an agent
|
|
975
|
-
|
|
976
|
-
# get `Memory` object by getting the linked block IDs and fetching the blocks, then putting that into a `Memory` object
|
|
977
|
-
# this is the "in memory" representation of the in-context memory
|
|
978
|
-
block_ids = self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id)
|
|
979
|
-
blocks = []
|
|
980
|
-
for block_id in block_ids:
|
|
981
|
-
block = self.block_manager.get_block_by_id(block_id=block_id, actor=user)
|
|
982
|
-
assert block, f"Block with ID {block_id} does not exist"
|
|
983
|
-
blocks.append(block)
|
|
984
|
-
memory = Memory(blocks=blocks)
|
|
985
|
-
|
|
986
|
-
# get `Tool` objects
|
|
987
|
-
tools = [self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=user) for tool_name in agent_state.tool_names]
|
|
988
|
-
tools = [tool for tool in tools if tool is not None]
|
|
989
|
-
|
|
990
|
-
# get `Source` objects
|
|
991
|
-
sources = self.list_attached_sources(agent_id=agent_id)
|
|
992
|
-
|
|
993
|
-
# get the tags
|
|
994
|
-
tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
|
|
995
|
-
|
|
996
|
-
# return the full agent state - this contains all data needed to recreate the agent
|
|
997
|
-
return AgentState(**agent_state.model_dump(), memory=memory, tools=tools, sources=sources, tags=tags)
|
|
998
|
-
|
|
817
|
+
# TODO: This is not good!
|
|
818
|
+
# TODO: Ideally, this should ALL be handled by the ORM
|
|
819
|
+
# TODO: The main blocker here IS the _message updates
|
|
999
820
|
def update_agent(
|
|
1000
821
|
self,
|
|
1001
|
-
|
|
1002
|
-
|
|
822
|
+
agent_id: str,
|
|
823
|
+
request: UpdateAgent,
|
|
824
|
+
actor: User,
|
|
1003
825
|
) -> AgentState:
|
|
1004
826
|
"""Update the agents core memory block, return the new state"""
|
|
1005
|
-
try:
|
|
1006
|
-
self.user_manager.get_user_by_id(user_id=actor.id)
|
|
1007
|
-
except Exception:
|
|
1008
|
-
raise ValueError(f"User user_id={actor.id} does not exist")
|
|
1009
|
-
|
|
1010
|
-
if self.ms.get_agent(agent_id=request.id) is None:
|
|
1011
|
-
raise ValueError(f"Agent agent_id={request.id} does not exist")
|
|
1012
|
-
|
|
1013
827
|
# Get the agent object (loaded in memory)
|
|
1014
|
-
letta_agent = self.load_agent(agent_id=
|
|
828
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
829
|
+
|
|
830
|
+
# Update tags
|
|
831
|
+
if request.tags is not None: # Allow for empty list
|
|
832
|
+
letta_agent.agent_state.tags = request.tags
|
|
1015
833
|
|
|
1016
834
|
# update the system prompt
|
|
1017
835
|
if request.system:
|
|
@@ -1025,30 +843,27 @@ class SyncServer(Server):
|
|
|
1025
843
|
letta_agent.set_message_buffer(message_ids=request.message_ids)
|
|
1026
844
|
|
|
1027
845
|
# tools
|
|
1028
|
-
if request.
|
|
846
|
+
if request.tool_ids:
|
|
1029
847
|
# Replace tools and also re-link
|
|
1030
848
|
|
|
1031
849
|
# (1) get tools + make sure they exist
|
|
1032
850
|
# Current and target tools as sets of tool names
|
|
1033
|
-
current_tools =
|
|
1034
|
-
|
|
851
|
+
current_tools = letta_agent.agent_state.tools
|
|
852
|
+
current_tool_ids = set([t.id for t in current_tools])
|
|
853
|
+
target_tool_ids = set(request.tool_ids)
|
|
1035
854
|
|
|
1036
855
|
# Calculate tools to add and remove
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
# Fetch tool objects for those to add and remove
|
|
1041
|
-
tools_to_add = [self.tool_manager.get_tool_by_name(tool_name=tool, actor=actor) for tool in tools_to_add]
|
|
1042
|
-
tools_to_remove = [self.tool_manager.get_tool_by_name(tool_name=tool, actor=actor) for tool in tools_to_remove]
|
|
856
|
+
tool_ids_to_add = target_tool_ids - current_tool_ids
|
|
857
|
+
tools_ids_to_remove = current_tool_ids - target_tool_ids
|
|
1043
858
|
|
|
1044
859
|
# update agent tool list
|
|
1045
|
-
for
|
|
1046
|
-
self.remove_tool_from_agent(agent_id=
|
|
1047
|
-
for
|
|
1048
|
-
self.add_tool_to_agent(agent_id=
|
|
860
|
+
for tool_id in tools_ids_to_remove:
|
|
861
|
+
self.remove_tool_from_agent(agent_id=agent_id, tool_id=tool_id, user_id=actor.id)
|
|
862
|
+
for tool_id in tool_ids_to_add:
|
|
863
|
+
self.add_tool_to_agent(agent_id=agent_id, tool_id=tool_id, user_id=actor.id)
|
|
1049
864
|
|
|
1050
865
|
# reload agent
|
|
1051
|
-
letta_agent = self.load_agent(agent_id=
|
|
866
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1052
867
|
|
|
1053
868
|
# configs
|
|
1054
869
|
if request.llm_config:
|
|
@@ -1062,33 +877,18 @@ class SyncServer(Server):
|
|
|
1062
877
|
if request.metadata_:
|
|
1063
878
|
letta_agent.agent_state.metadata_ = request.metadata_
|
|
1064
879
|
|
|
1065
|
-
# Manage tag state
|
|
1066
|
-
if request.tags is not None:
|
|
1067
|
-
current_tags = set(self.agents_tags_manager.get_tags_for_agent(agent_id=letta_agent.agent_state.id, actor=actor))
|
|
1068
|
-
target_tags = set(request.tags)
|
|
1069
|
-
|
|
1070
|
-
tags_to_add = target_tags - current_tags
|
|
1071
|
-
tags_to_remove = current_tags - target_tags
|
|
1072
|
-
|
|
1073
|
-
for tag in tags_to_add:
|
|
1074
|
-
self.agents_tags_manager.add_tag_to_agent(agent_id=letta_agent.agent_state.id, tag=tag, actor=actor)
|
|
1075
|
-
for tag in tags_to_remove:
|
|
1076
|
-
self.agents_tags_manager.delete_tag_from_agent(agent_id=letta_agent.agent_state.id, tag=tag, actor=actor)
|
|
1077
|
-
|
|
1078
880
|
# save the agent
|
|
1079
|
-
save_agent(letta_agent
|
|
881
|
+
save_agent(letta_agent)
|
|
1080
882
|
# TODO: probably reload the agent somehow?
|
|
1081
883
|
return letta_agent.agent_state
|
|
1082
884
|
|
|
1083
885
|
def get_tools_from_agent(self, agent_id: str, user_id: Optional[str]) -> List[Tool]:
|
|
1084
886
|
"""Get tools from an existing agent"""
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
1088
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
887
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
888
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1089
889
|
|
|
1090
890
|
# Get the agent object (loaded in memory)
|
|
1091
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
891
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1092
892
|
return letta_agent.agent_state.tools
|
|
1093
893
|
|
|
1094
894
|
def add_tool_to_agent(
|
|
@@ -1098,25 +898,20 @@ class SyncServer(Server):
|
|
|
1098
898
|
user_id: str,
|
|
1099
899
|
):
|
|
1100
900
|
"""Add tools from an existing agent"""
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
except NoResultFound:
|
|
1104
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1105
|
-
|
|
1106
|
-
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
1107
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
901
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
902
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1108
903
|
|
|
1109
904
|
# Get the agent object (loaded in memory)
|
|
1110
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
905
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1111
906
|
|
|
1112
907
|
# Get all the tool objects from the request
|
|
1113
908
|
tool_objs = []
|
|
1114
|
-
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=
|
|
909
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=actor)
|
|
1115
910
|
assert tool_obj, f"Tool with id={tool_id} does not exist"
|
|
1116
911
|
tool_objs.append(tool_obj)
|
|
1117
912
|
|
|
1118
913
|
for tool in letta_agent.agent_state.tools:
|
|
1119
|
-
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=
|
|
914
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=actor)
|
|
1120
915
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
1121
916
|
|
|
1122
917
|
# If it's not the already added tool
|
|
@@ -1124,13 +919,13 @@ class SyncServer(Server):
|
|
|
1124
919
|
tool_objs.append(tool_obj)
|
|
1125
920
|
|
|
1126
921
|
# replace the list of tool names ("ids") inside the agent state
|
|
1127
|
-
letta_agent.agent_state.
|
|
922
|
+
letta_agent.agent_state.tools = tool_objs
|
|
1128
923
|
|
|
1129
924
|
# then attempt to link the tools modules
|
|
1130
925
|
letta_agent.link_tools(tool_objs)
|
|
1131
926
|
|
|
1132
927
|
# save the agent
|
|
1133
|
-
save_agent(letta_agent
|
|
928
|
+
save_agent(letta_agent)
|
|
1134
929
|
return letta_agent.agent_state
|
|
1135
930
|
|
|
1136
931
|
def remove_tool_from_agent(
|
|
@@ -1140,21 +935,16 @@ class SyncServer(Server):
|
|
|
1140
935
|
user_id: str,
|
|
1141
936
|
):
|
|
1142
937
|
"""Remove tools from an existing agent"""
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
except NoResultFound:
|
|
1146
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1147
|
-
|
|
1148
|
-
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
1149
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
938
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
939
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1150
940
|
|
|
1151
941
|
# Get the agent object (loaded in memory)
|
|
1152
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
942
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1153
943
|
|
|
1154
944
|
# Get all the tool_objs
|
|
1155
945
|
tool_objs = []
|
|
1156
946
|
for tool in letta_agent.agent_state.tools:
|
|
1157
|
-
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=
|
|
947
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=actor)
|
|
1158
948
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
1159
949
|
|
|
1160
950
|
# If it's not the tool we want to remove
|
|
@@ -1162,147 +952,47 @@ class SyncServer(Server):
|
|
|
1162
952
|
tool_objs.append(tool_obj)
|
|
1163
953
|
|
|
1164
954
|
# replace the list of tool names ("ids") inside the agent state
|
|
1165
|
-
letta_agent.agent_state.
|
|
955
|
+
letta_agent.agent_state.tools = tool_objs
|
|
1166
956
|
|
|
1167
957
|
# then attempt to link the tools modules
|
|
1168
958
|
letta_agent.link_tools(tool_objs)
|
|
1169
959
|
|
|
1170
960
|
# save the agent
|
|
1171
|
-
save_agent(letta_agent
|
|
961
|
+
save_agent(letta_agent)
|
|
1172
962
|
return letta_agent.agent_state
|
|
1173
963
|
|
|
1174
|
-
def get_agent_state(self, user_id: str, agent_id: str) -> AgentState:
|
|
1175
|
-
# TODO: duplicate, remove
|
|
1176
|
-
return self.get_agent(agent_id=agent_id)
|
|
1177
|
-
|
|
1178
|
-
def list_agents(self, user_id: str, tags: Optional[List[str]] = None) -> List[AgentState]:
|
|
1179
|
-
"""List all available agents to a user"""
|
|
1180
|
-
user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1181
|
-
|
|
1182
|
-
if tags is None:
|
|
1183
|
-
agents_states = self.ms.list_agents(user_id=user_id)
|
|
1184
|
-
agent_ids = [agent.id for agent in agents_states]
|
|
1185
|
-
else:
|
|
1186
|
-
agent_ids = []
|
|
1187
|
-
for tag in tags:
|
|
1188
|
-
agent_ids += self.agents_tags_manager.get_agents_by_tag(tag=tag, actor=user)
|
|
1189
|
-
|
|
1190
|
-
return [self.get_agent(agent_id=agent_id) for agent_id in agent_ids]
|
|
1191
|
-
|
|
1192
964
|
# convert name->id
|
|
1193
965
|
|
|
1194
|
-
def
|
|
1195
|
-
agent_state = self.ms.get_agent(agent_name=name, user_id=user_id)
|
|
1196
|
-
if not agent_state:
|
|
1197
|
-
return None
|
|
1198
|
-
return agent_state.id
|
|
1199
|
-
|
|
1200
|
-
def get_source(self, source_id: str, user_id: str) -> Source:
|
|
1201
|
-
existing_source = self.ms.get_source(source_id=source_id, user_id=user_id)
|
|
1202
|
-
if not existing_source:
|
|
1203
|
-
raise ValueError("Source does not exist")
|
|
1204
|
-
return existing_source
|
|
1205
|
-
|
|
1206
|
-
def get_source_id(self, source_name: str, user_id: str) -> str:
|
|
1207
|
-
existing_source = self.ms.get_source(source_name=source_name, user_id=user_id)
|
|
1208
|
-
if not existing_source:
|
|
1209
|
-
raise ValueError("Source does not exist")
|
|
1210
|
-
return existing_source.id
|
|
1211
|
-
|
|
1212
|
-
def get_agent_memory(self, agent_id: str) -> Memory:
|
|
966
|
+
def get_agent_memory(self, agent_id: str, actor: User) -> Memory:
|
|
1213
967
|
"""Return the memory of an agent (core memory)"""
|
|
1214
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
968
|
+
agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1215
969
|
return agent.agent_state.memory
|
|
1216
970
|
|
|
1217
|
-
def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
|
|
1218
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
971
|
+
def get_archival_memory_summary(self, agent_id: str, actor: User) -> ArchivalMemorySummary:
|
|
972
|
+
agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1219
973
|
return ArchivalMemorySummary(size=agent.passage_manager.size(actor=self.default_user))
|
|
1220
974
|
|
|
1221
|
-
def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
|
|
1222
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
975
|
+
def get_recall_memory_summary(self, agent_id: str, actor: User) -> RecallMemorySummary:
|
|
976
|
+
agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1223
977
|
return RecallMemorySummary(size=len(agent.message_manager))
|
|
1224
978
|
|
|
1225
|
-
def
|
|
1226
|
-
"""Get the message ids of the in-context messages in the agent's memory"""
|
|
1227
|
-
# Get the agent object (loaded in memory)
|
|
1228
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
1229
|
-
return [m.id for m in agent._messages]
|
|
1230
|
-
|
|
1231
|
-
def get_in_context_messages(self, agent_id: str) -> List[Message]:
|
|
979
|
+
def get_in_context_messages(self, agent_id: str, actor: User) -> List[Message]:
|
|
1232
980
|
"""Get the in-context messages in the agent's memory"""
|
|
1233
981
|
# Get the agent object (loaded in memory)
|
|
1234
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
982
|
+
agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1235
983
|
return agent._messages
|
|
1236
984
|
|
|
1237
|
-
def
|
|
1238
|
-
"""Get a single message from the agent's memory"""
|
|
1239
|
-
# Get the agent object (loaded in memory)
|
|
1240
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
1241
|
-
message = agent.message_manager.get_message_by_id(id=message_id, actor=self.default_user)
|
|
1242
|
-
return message
|
|
1243
|
-
|
|
1244
|
-
def get_agent_messages(
|
|
1245
|
-
self,
|
|
1246
|
-
agent_id: str,
|
|
1247
|
-
start: int,
|
|
1248
|
-
count: int,
|
|
1249
|
-
) -> Union[List[Message], List[LettaMessage]]:
|
|
1250
|
-
"""Paginated query of all messages in agent message queue"""
|
|
1251
|
-
# Get the agent object (loaded in memory)
|
|
1252
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1253
|
-
|
|
1254
|
-
if start < 0 or count < 0:
|
|
1255
|
-
raise ValueError("Start and count values should be non-negative")
|
|
1256
|
-
|
|
1257
|
-
if start + count < len(letta_agent._messages): # messages can be returned from whats in memory
|
|
1258
|
-
# Reverse the list to make it in reverse chronological order
|
|
1259
|
-
reversed_messages = letta_agent._messages[::-1]
|
|
1260
|
-
# Check if start is within the range of the list
|
|
1261
|
-
if start >= len(reversed_messages):
|
|
1262
|
-
raise IndexError("Start index is out of range")
|
|
1263
|
-
|
|
1264
|
-
# Calculate the end index, ensuring it does not exceed the list length
|
|
1265
|
-
end_index = min(start + count, len(reversed_messages))
|
|
1266
|
-
|
|
1267
|
-
# Slice the list for pagination
|
|
1268
|
-
messages = reversed_messages[start:end_index]
|
|
1269
|
-
|
|
1270
|
-
else:
|
|
1271
|
-
# need to access persistence manager for additional messages
|
|
1272
|
-
|
|
1273
|
-
# get messages using message manager
|
|
1274
|
-
page = letta_agent.message_manager.list_user_messages_for_agent(
|
|
1275
|
-
agent_id=agent_id,
|
|
1276
|
-
actor=self.default_user,
|
|
1277
|
-
cursor=start,
|
|
1278
|
-
limit=count,
|
|
1279
|
-
)
|
|
1280
|
-
|
|
1281
|
-
messages = page
|
|
1282
|
-
assert all(isinstance(m, Message) for m in messages)
|
|
1283
|
-
|
|
1284
|
-
## Convert to json
|
|
1285
|
-
## Add a tag indicating in-context or not
|
|
1286
|
-
# json_messages = [record.to_json() for record in messages]
|
|
1287
|
-
# in_context_message_ids = [str(m.id) for m in letta_agent._messages]
|
|
1288
|
-
# for d in json_messages:
|
|
1289
|
-
# d["in_context"] = True if str(d["id"]) in in_context_message_ids else False
|
|
1290
|
-
|
|
1291
|
-
return messages
|
|
1292
|
-
|
|
1293
|
-
def get_agent_archival(self, user_id: str, agent_id: str, cursor: Optional[str] = None, limit: int = 50) -> List[PydanticPassage]:
|
|
985
|
+
def get_agent_archival(self, user_id: str, agent_id: str, cursor: Optional[str] = None, limit: int = 50) -> List[Passage]:
|
|
1294
986
|
"""Paginated query of all messages in agent archival memory"""
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1298
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
987
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
988
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1299
989
|
|
|
1300
990
|
# Get the agent object (loaded in memory)
|
|
1301
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
991
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1302
992
|
|
|
1303
993
|
# iterate over records
|
|
1304
994
|
records = letta_agent.passage_manager.list_passages(
|
|
1305
|
-
actor=
|
|
995
|
+
actor=actor,
|
|
1306
996
|
agent_id=agent_id,
|
|
1307
997
|
cursor=cursor,
|
|
1308
998
|
limit=limit,
|
|
@@ -1316,14 +1006,14 @@ class SyncServer(Server):
|
|
|
1316
1006
|
agent_id: str,
|
|
1317
1007
|
cursor: Optional[str] = None,
|
|
1318
1008
|
limit: Optional[int] = 100,
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1009
|
+
order_by: Optional[str] = "created_at",
|
|
1010
|
+
reverse: Optional[bool] = False,
|
|
1011
|
+
) -> List[Passage]:
|
|
1012
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
1013
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1324
1014
|
|
|
1325
1015
|
# Get the agent object (loaded in memory)
|
|
1326
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1016
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1327
1017
|
|
|
1328
1018
|
# iterate over records
|
|
1329
1019
|
records = letta_agent.passage_manager.list_passages(
|
|
@@ -1334,32 +1024,22 @@ class SyncServer(Server):
|
|
|
1334
1024
|
)
|
|
1335
1025
|
return records
|
|
1336
1026
|
|
|
1337
|
-
def insert_archival_memory(self,
|
|
1338
|
-
actor = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1339
|
-
if actor is None:
|
|
1340
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1341
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1342
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1343
|
-
|
|
1027
|
+
def insert_archival_memory(self, agent_id: str, memory_contents: str, actor: User) -> List[Passage]:
|
|
1344
1028
|
# Get the agent object (loaded in memory)
|
|
1345
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1029
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1346
1030
|
|
|
1347
1031
|
# Insert into archival memory
|
|
1348
|
-
|
|
1032
|
+
passages = self.passage_manager.insert_passage(
|
|
1349
1033
|
agent_state=letta_agent.agent_state, agent_id=agent_id, text=memory_contents, actor=actor
|
|
1350
1034
|
)
|
|
1351
1035
|
|
|
1352
|
-
|
|
1353
|
-
actor = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1354
|
-
if actor is None:
|
|
1355
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1356
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1357
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1036
|
+
save_agent(letta_agent)
|
|
1358
1037
|
|
|
1359
|
-
|
|
1038
|
+
return passages
|
|
1360
1039
|
|
|
1040
|
+
def delete_archival_memory(self, agent_id: str, memory_id: str, actor: User):
|
|
1361
1041
|
# Get the agent object (loaded in memory)
|
|
1362
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1042
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1363
1043
|
|
|
1364
1044
|
# Delete by ID
|
|
1365
1045
|
# TODO check if it exists first, and throw error if not
|
|
@@ -1379,14 +1059,11 @@ class SyncServer(Server):
|
|
|
1379
1059
|
assistant_message_tool_name: str = constants.DEFAULT_MESSAGE_TOOL,
|
|
1380
1060
|
assistant_message_tool_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
|
|
1381
1061
|
) -> Union[List[Message], List[LettaMessage]]:
|
|
1382
|
-
actor
|
|
1383
|
-
|
|
1384
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1385
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1386
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1062
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
1063
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1387
1064
|
|
|
1388
1065
|
# Get the agent object (loaded in memory)
|
|
1389
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1066
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1390
1067
|
|
|
1391
1068
|
# iterate over records
|
|
1392
1069
|
start_date = self.message_manager.get_message_by_id(after, actor=actor).created_at if after else None
|
|
@@ -1441,123 +1118,20 @@ class SyncServer(Server):
|
|
|
1441
1118
|
|
|
1442
1119
|
return response
|
|
1443
1120
|
|
|
1444
|
-
def update_agent_core_memory(self,
|
|
1121
|
+
def update_agent_core_memory(self, agent_id: str, label: str, value: str, actor: User) -> Memory:
|
|
1445
1122
|
"""Update the value of a block in the agent's memory"""
|
|
1446
1123
|
|
|
1447
1124
|
# get the block id
|
|
1448
|
-
block = self.
|
|
1449
|
-
block_id = block.id
|
|
1125
|
+
block = self.agent_manager.get_block_with_label(agent_id=agent_id, block_label=label, actor=actor)
|
|
1450
1126
|
|
|
1451
1127
|
# update the block
|
|
1452
|
-
self.block_manager.update_block(
|
|
1453
|
-
block_id=block_id, block_update=BlockUpdate(value=value), actor=self.user_manager.get_user_by_id(user_id=user_id)
|
|
1454
|
-
)
|
|
1128
|
+
self.block_manager.update_block(block_id=block.id, block_update=BlockUpdate(value=value), actor=actor)
|
|
1455
1129
|
|
|
1456
1130
|
# load agent
|
|
1457
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1131
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1458
1132
|
return letta_agent.agent_state.memory
|
|
1459
1133
|
|
|
1460
|
-
def
|
|
1461
|
-
"""Update the name of the agent in the database"""
|
|
1462
|
-
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1463
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1464
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1465
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1466
|
-
|
|
1467
|
-
# Get the agent object (loaded in memory)
|
|
1468
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1469
|
-
|
|
1470
|
-
current_name = letta_agent.agent_state.name
|
|
1471
|
-
if current_name == new_agent_name:
|
|
1472
|
-
raise ValueError(f"New name ({new_agent_name}) is the same as the current name")
|
|
1473
|
-
|
|
1474
|
-
try:
|
|
1475
|
-
letta_agent.agent_state.name = new_agent_name
|
|
1476
|
-
self.ms.update_agent(agent=letta_agent.agent_state)
|
|
1477
|
-
except Exception as e:
|
|
1478
|
-
logger.exception(f"Failed to update agent name with:\n{str(e)}")
|
|
1479
|
-
raise ValueError(f"Failed to update agent name in database")
|
|
1480
|
-
|
|
1481
|
-
assert isinstance(letta_agent.agent_state.id, str)
|
|
1482
|
-
return letta_agent.agent_state
|
|
1483
|
-
|
|
1484
|
-
def delete_agent(self, user_id: str, agent_id: str):
|
|
1485
|
-
"""Delete an agent in the database"""
|
|
1486
|
-
actor = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1487
|
-
# TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM MODEL
|
|
1488
|
-
# TODO: EVENTUALLY WE GET AUTO-DELETES WHEN WE SPECIFY RELATIONSHIPS IN THE ORM
|
|
1489
|
-
self.agents_tags_manager.delete_all_tags_from_agent(agent_id=agent_id, actor=actor)
|
|
1490
|
-
self.blocks_agents_manager.remove_all_agent_blocks(agent_id=agent_id)
|
|
1491
|
-
|
|
1492
|
-
# Verify that the agent exists and belongs to the org of the user
|
|
1493
|
-
agent_state = self.ms.get_agent(agent_id=agent_id, user_id=user_id)
|
|
1494
|
-
if agent_state is None:
|
|
1495
|
-
raise ValueError(f"Could not find agent_id={agent_id} under user_id={user_id}")
|
|
1496
|
-
|
|
1497
|
-
# TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM MODEL
|
|
1498
|
-
messages = self.message_manager.list_messages_for_agent(agent_id=agent_state.id)
|
|
1499
|
-
for message in messages:
|
|
1500
|
-
self.message_manager.delete_message_by_id(message.id, actor=actor)
|
|
1501
|
-
|
|
1502
|
-
# TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM
|
|
1503
|
-
try:
|
|
1504
|
-
agent_state_user = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
|
|
1505
|
-
if agent_state_user.organization_id != actor.organization_id:
|
|
1506
|
-
raise ValueError(
|
|
1507
|
-
f"Could not authorize agent_id={agent_id} with user_id={user_id} because of differing organizations; agent_id was created in {agent_state_user.organization_id} while user belongs to {actor.organization_id}. How did they get the agent id?"
|
|
1508
|
-
)
|
|
1509
|
-
except NoResultFound:
|
|
1510
|
-
logger.error(f"Agent with id {agent_state.id} has nonexistent user {agent_state.user_id}")
|
|
1511
|
-
|
|
1512
|
-
# delete all passages associated with this agent
|
|
1513
|
-
# TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM
|
|
1514
|
-
passages = self.passage_manager.list_passages(actor=actor, agent_id=agent_state.id)
|
|
1515
|
-
for passage in passages:
|
|
1516
|
-
self.passage_manager.delete_passage_by_id(passage.id, actor=actor)
|
|
1517
|
-
|
|
1518
|
-
# First, if the agent is in the in-memory cache we should remove it
|
|
1519
|
-
# List of {'user_id': user_id, 'agent_id': agent_id, 'agent': agent_obj} dicts
|
|
1520
|
-
try:
|
|
1521
|
-
self.active_agents = [d for d in self.active_agents if str(d["agent_id"]) != str(agent_id)]
|
|
1522
|
-
except Exception as e:
|
|
1523
|
-
logger.exception(f"Failed to delete agent {agent_id} from cache via ID with:\n{str(e)}")
|
|
1524
|
-
raise ValueError(f"Failed to delete agent {agent_id} from cache")
|
|
1525
|
-
|
|
1526
|
-
# Next, attempt to delete it from the actual database
|
|
1527
|
-
try:
|
|
1528
|
-
self.ms.delete_agent(agent_id=agent_id, per_agent_lock_manager=self.per_agent_lock_manager)
|
|
1529
|
-
except Exception as e:
|
|
1530
|
-
logger.exception(f"Failed to delete agent {agent_id} via ID with:\n{str(e)}")
|
|
1531
|
-
raise ValueError(f"Failed to delete agent {agent_id} in database")
|
|
1532
|
-
|
|
1533
|
-
def api_key_to_user(self, api_key: str) -> str:
|
|
1534
|
-
"""Decode an API key to a user"""
|
|
1535
|
-
token = self.ms.get_api_key(api_key=api_key)
|
|
1536
|
-
user = self.user_manager.get_user_by_id(token.user_id)
|
|
1537
|
-
if user is None:
|
|
1538
|
-
raise HTTPException(status_code=403, detail="Invalid credentials")
|
|
1539
|
-
else:
|
|
1540
|
-
return user.id
|
|
1541
|
-
|
|
1542
|
-
def create_api_key(self, request: APIKeyCreate) -> APIKey: # TODO: add other fields
|
|
1543
|
-
"""Create a new API key for a user"""
|
|
1544
|
-
if request.name is None:
|
|
1545
|
-
request.name = f"API Key {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
1546
|
-
token = self.ms.create_api_key(user_id=request.user_id, name=request.name)
|
|
1547
|
-
return token
|
|
1548
|
-
|
|
1549
|
-
def list_api_keys(self, user_id: str) -> List[APIKey]:
|
|
1550
|
-
"""List all API keys for a user"""
|
|
1551
|
-
return self.ms.get_all_api_keys_for_user(user_id=user_id)
|
|
1552
|
-
|
|
1553
|
-
def delete_api_key(self, api_key: str) -> APIKey:
|
|
1554
|
-
api_key_obj = self.ms.get_api_key(api_key=api_key)
|
|
1555
|
-
if api_key_obj is None:
|
|
1556
|
-
raise ValueError("API key does not exist")
|
|
1557
|
-
self.ms.delete_api_key(api_key=api_key)
|
|
1558
|
-
return api_key_obj
|
|
1559
|
-
|
|
1560
|
-
def delete_source(self, source_id: str, actor: PydanticUser):
|
|
1134
|
+
def delete_source(self, source_id: str, actor: User):
|
|
1561
1135
|
"""Delete a data source"""
|
|
1562
1136
|
self.source_manager.delete_source(source_id=source_id, actor=actor)
|
|
1563
1137
|
|
|
@@ -1566,7 +1140,7 @@ class SyncServer(Server):
|
|
|
1566
1140
|
|
|
1567
1141
|
# TODO: delete data from agent passage stores (?)
|
|
1568
1142
|
|
|
1569
|
-
def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor:
|
|
1143
|
+
def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
|
|
1570
1144
|
|
|
1571
1145
|
# update job
|
|
1572
1146
|
job = self.job_manager.get_job_by_id(job_id, actor=actor)
|
|
@@ -1589,13 +1163,14 @@ class SyncServer(Server):
|
|
|
1589
1163
|
self.job_manager.update_job_by_id(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
|
|
1590
1164
|
|
|
1591
1165
|
# update all agents who have this source attached
|
|
1592
|
-
|
|
1593
|
-
for
|
|
1594
|
-
|
|
1166
|
+
agent_states = self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
|
1167
|
+
for agent_state in agent_states:
|
|
1168
|
+
agent_id = agent_state.id
|
|
1169
|
+
agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1595
1170
|
curr_passage_size = self.passage_manager.size(actor=actor, agent_id=agent_id, source_id=source_id)
|
|
1596
|
-
agent.attach_source(user=actor, source_id=source_id, source_manager=self.source_manager,
|
|
1171
|
+
agent.attach_source(user=actor, source_id=source_id, source_manager=self.source_manager, agent_manager=self.agent_manager)
|
|
1597
1172
|
new_passage_size = self.passage_manager.size(actor=actor, agent_id=agent_id, source_id=source_id)
|
|
1598
|
-
assert new_passage_size >= curr_passage_size
|
|
1173
|
+
assert new_passage_size >= curr_passage_size # in case empty files are added
|
|
1599
1174
|
|
|
1600
1175
|
return job
|
|
1601
1176
|
|
|
@@ -1626,21 +1201,22 @@ class SyncServer(Server):
|
|
|
1626
1201
|
source_name: Optional[str] = None,
|
|
1627
1202
|
) -> Source:
|
|
1628
1203
|
# attach a data source to an agent
|
|
1629
|
-
|
|
1204
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
1205
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1630
1206
|
if source_id:
|
|
1631
|
-
data_source = self.source_manager.get_source_by_id(source_id=source_id, actor=
|
|
1207
|
+
data_source = self.source_manager.get_source_by_id(source_id=source_id, actor=actor)
|
|
1632
1208
|
elif source_name:
|
|
1633
|
-
data_source = self.source_manager.get_source_by_name(source_name=source_name, actor=
|
|
1209
|
+
data_source = self.source_manager.get_source_by_name(source_name=source_name, actor=actor)
|
|
1634
1210
|
else:
|
|
1635
1211
|
raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
|
|
1636
1212
|
|
|
1637
1213
|
assert data_source, f"Data source with id={source_id} or name={source_name} does not exist"
|
|
1638
1214
|
|
|
1639
1215
|
# load agent
|
|
1640
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
1216
|
+
agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1641
1217
|
|
|
1642
1218
|
# attach source to agent
|
|
1643
|
-
agent.attach_source(user=
|
|
1219
|
+
agent.attach_source(user=actor, source_id=data_source.id, source_manager=self.source_manager, agent_manager=self.agent_manager)
|
|
1644
1220
|
|
|
1645
1221
|
return data_source
|
|
1646
1222
|
|
|
@@ -1648,40 +1224,35 @@ class SyncServer(Server):
|
|
|
1648
1224
|
self,
|
|
1649
1225
|
user_id: str,
|
|
1650
1226
|
agent_id: str,
|
|
1651
|
-
# source_id: str,
|
|
1652
1227
|
source_id: Optional[str] = None,
|
|
1653
1228
|
source_name: Optional[str] = None,
|
|
1654
1229
|
) -> Source:
|
|
1655
|
-
|
|
1230
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
1231
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1656
1232
|
if source_id:
|
|
1657
|
-
source = self.source_manager.get_source_by_id(source_id=source_id, actor=
|
|
1233
|
+
source = self.source_manager.get_source_by_id(source_id=source_id, actor=actor)
|
|
1658
1234
|
elif source_name:
|
|
1659
|
-
source = self.source_manager.get_source_by_name(source_name=source_name, actor=
|
|
1235
|
+
source = self.source_manager.get_source_by_name(source_name=source_name, actor=actor)
|
|
1660
1236
|
else:
|
|
1661
1237
|
raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
|
|
1662
1238
|
source_id = source.id
|
|
1663
1239
|
|
|
1240
|
+
# TODO: This should be done with the ORM?
|
|
1664
1241
|
# delete all Passage objects with source_id==source_id from agent's archival memory
|
|
1665
|
-
agent = self.load_agent(agent_id=agent_id)
|
|
1666
|
-
agent.passage_manager.delete_passages(actor=
|
|
1242
|
+
agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1243
|
+
agent.passage_manager.delete_passages(actor=actor, limit=100, source_id=source_id)
|
|
1667
1244
|
|
|
1668
1245
|
# delete agent-source mapping
|
|
1669
|
-
self.
|
|
1246
|
+
self.agent_manager.detach_source(agent_id=agent_id, source_id=source_id, actor=actor)
|
|
1670
1247
|
|
|
1671
1248
|
# return back source data
|
|
1672
1249
|
return source
|
|
1673
1250
|
|
|
1674
|
-
def
|
|
1675
|
-
# list all attached sources to an agent
|
|
1676
|
-
source_ids = self.ms.list_attached_source_ids(agent_id)
|
|
1677
|
-
|
|
1678
|
-
return [self.source_manager.get_source_by_id(source_id=id) for id in source_ids]
|
|
1679
|
-
|
|
1680
|
-
def list_data_source_passages(self, user_id: str, source_id: str) -> List[PydanticPassage]:
|
|
1251
|
+
def list_data_source_passages(self, user_id: str, source_id: str) -> List[Passage]:
|
|
1681
1252
|
warnings.warn("list_data_source_passages is not yet implemented, returning empty list.", category=UserWarning)
|
|
1682
1253
|
return []
|
|
1683
1254
|
|
|
1684
|
-
def list_all_sources(self, actor:
|
|
1255
|
+
def list_all_sources(self, actor: User) -> List[Source]:
|
|
1685
1256
|
"""List all sources (w/ extra metadata) belonging to a user"""
|
|
1686
1257
|
|
|
1687
1258
|
sources = self.source_manager.list_sources(actor=actor)
|
|
@@ -1699,15 +1270,9 @@ class SyncServer(Server):
|
|
|
1699
1270
|
# num_documents = document_conn.size({"data_source": source.name})
|
|
1700
1271
|
num_documents = 0
|
|
1701
1272
|
|
|
1702
|
-
|
|
1273
|
+
agents = self.source_manager.list_attached_agents(source_id=source.id, actor=actor)
|
|
1703
1274
|
# add the agent name information
|
|
1704
|
-
attached_agents = [
|
|
1705
|
-
{
|
|
1706
|
-
"id": str(a_id),
|
|
1707
|
-
"name": self.ms.get_agent(user_id=actor.id, agent_id=a_id).name,
|
|
1708
|
-
}
|
|
1709
|
-
for a_id in agent_ids
|
|
1710
|
-
]
|
|
1275
|
+
attached_agents = [{"id": agent.id, "name": agent.name} for agent in agents]
|
|
1711
1276
|
|
|
1712
1277
|
# Overwrite metadata field, should be empty anyways
|
|
1713
1278
|
source.metadata_ = dict(
|
|
@@ -1720,7 +1285,7 @@ class SyncServer(Server):
|
|
|
1720
1285
|
|
|
1721
1286
|
return sources_with_metadata
|
|
1722
1287
|
|
|
1723
|
-
def add_default_external_tools(self, actor:
|
|
1288
|
+
def add_default_external_tools(self, actor: User) -> bool:
|
|
1724
1289
|
"""Add default langchain tools. Return true if successful, false otherwise."""
|
|
1725
1290
|
success = True
|
|
1726
1291
|
tool_creates = ToolCreate.load_default_langchain_tools()
|
|
@@ -1736,57 +1301,37 @@ class SyncServer(Server):
|
|
|
1736
1301
|
|
|
1737
1302
|
return success
|
|
1738
1303
|
|
|
1739
|
-
def
|
|
1740
|
-
"""Get a single message from the agent's memory"""
|
|
1741
|
-
# Get the agent object (loaded in memory)
|
|
1742
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1743
|
-
message = letta_agent.message_manager.get_message_by_id(id=message_id)
|
|
1744
|
-
save_agent(letta_agent, self.ms)
|
|
1745
|
-
return message
|
|
1746
|
-
|
|
1747
|
-
def update_agent_message(self, agent_id: str, message_id: str, request: MessageUpdate) -> Message:
|
|
1304
|
+
def update_agent_message(self, agent_id: str, message_id: str, request: MessageUpdate, actor: User) -> Message:
|
|
1748
1305
|
"""Update the details of a message associated with an agent"""
|
|
1749
1306
|
|
|
1750
1307
|
# Get the current message
|
|
1751
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1308
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1752
1309
|
response = letta_agent.update_message(message_id=message_id, request=request)
|
|
1753
|
-
save_agent(letta_agent
|
|
1310
|
+
save_agent(letta_agent)
|
|
1754
1311
|
return response
|
|
1755
1312
|
|
|
1756
|
-
def rewrite_agent_message(self, agent_id: str, new_text: str) -> Message:
|
|
1313
|
+
def rewrite_agent_message(self, agent_id: str, new_text: str, actor: User) -> Message:
|
|
1757
1314
|
|
|
1758
1315
|
# Get the current message
|
|
1759
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1316
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1760
1317
|
response = letta_agent.rewrite_message(new_text=new_text)
|
|
1761
|
-
save_agent(letta_agent
|
|
1318
|
+
save_agent(letta_agent)
|
|
1762
1319
|
return response
|
|
1763
1320
|
|
|
1764
|
-
def rethink_agent_message(self, agent_id: str, new_thought: str) -> Message:
|
|
1765
|
-
|
|
1321
|
+
def rethink_agent_message(self, agent_id: str, new_thought: str, actor: User) -> Message:
|
|
1766
1322
|
# Get the current message
|
|
1767
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1323
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1768
1324
|
response = letta_agent.rethink_message(new_thought=new_thought)
|
|
1769
|
-
save_agent(letta_agent
|
|
1325
|
+
save_agent(letta_agent)
|
|
1770
1326
|
return response
|
|
1771
1327
|
|
|
1772
|
-
def retry_agent_message(self, agent_id: str) -> List[Message]:
|
|
1773
|
-
|
|
1328
|
+
def retry_agent_message(self, agent_id: str, actor: User) -> List[Message]:
|
|
1774
1329
|
# Get the current message
|
|
1775
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1330
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1776
1331
|
response = letta_agent.retry_message()
|
|
1777
|
-
save_agent(letta_agent
|
|
1332
|
+
save_agent(letta_agent)
|
|
1778
1333
|
return response
|
|
1779
1334
|
|
|
1780
|
-
def get_user_or_default(self, user_id: Optional[str]) -> PydanticUser:
|
|
1781
|
-
"""Get the user object for user_id if it exists, otherwise return the default user object"""
|
|
1782
|
-
if user_id is None:
|
|
1783
|
-
user_id = self.user_manager.DEFAULT_USER_ID
|
|
1784
|
-
|
|
1785
|
-
try:
|
|
1786
|
-
return self.user_manager.get_user_by_id(user_id=user_id)
|
|
1787
|
-
except NoResultFound:
|
|
1788
|
-
raise HTTPException(status_code=404, detail=f"User with id {user_id} not found")
|
|
1789
|
-
|
|
1790
1335
|
def get_organization_or_default(self, org_id: Optional[str]) -> Organization:
|
|
1791
1336
|
"""Get the organization object for org_id if it exists, otherwise return the default organization object"""
|
|
1792
1337
|
if org_id is None:
|
|
@@ -1829,100 +1374,13 @@ class SyncServer(Server):
|
|
|
1829
1374
|
user_id: str,
|
|
1830
1375
|
agent_id: str,
|
|
1831
1376
|
) -> ContextWindowOverview:
|
|
1377
|
+
# TODO: Thread actor directly through this function, since the top level caller most likely already retrieved the user
|
|
1378
|
+
actor = self.user_manager.get_user_or_default(user_id=user_id)
|
|
1379
|
+
|
|
1832
1380
|
# Get the current message
|
|
1833
|
-
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1381
|
+
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
|
1834
1382
|
return letta_agent.get_context_window()
|
|
1835
1383
|
|
|
1836
|
-
def link_block_to_agent_memory(self, user_id: str, agent_id: str, block_id: str) -> Memory:
|
|
1837
|
-
"""Link a block to an agent's memory"""
|
|
1838
|
-
block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
|
|
1839
|
-
if block is None:
|
|
1840
|
-
raise ValueError(f"Block with id {block_id} not found")
|
|
1841
|
-
self.blocks_agents_manager.add_block_to_agent(agent_id, block_id, block_label=block.label)
|
|
1842
|
-
|
|
1843
|
-
# get agent memory
|
|
1844
|
-
memory = self.get_agent(agent_id=agent_id).memory
|
|
1845
|
-
return memory
|
|
1846
|
-
|
|
1847
|
-
def unlink_block_from_agent_memory(self, user_id: str, agent_id: str, block_label: str, delete_if_no_ref: bool = True) -> Memory:
|
|
1848
|
-
"""Unlink a block from an agent's memory. If the block is not linked to any agent, delete it."""
|
|
1849
|
-
self.blocks_agents_manager.remove_block_with_label_from_agent(agent_id=agent_id, block_label=block_label)
|
|
1850
|
-
|
|
1851
|
-
# get agent memory
|
|
1852
|
-
memory = self.get_agent(agent_id=agent_id).memory
|
|
1853
|
-
return memory
|
|
1854
|
-
|
|
1855
|
-
def update_agent_memory_limit(self, user_id: str, agent_id: str, block_label: str, limit: int) -> Memory:
|
|
1856
|
-
"""Update the limit of a block in an agent's memory"""
|
|
1857
|
-
block = self.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=block_label)
|
|
1858
|
-
self.block_manager.update_block(
|
|
1859
|
-
block_id=block.id, block_update=BlockUpdate(limit=limit), actor=self.user_manager.get_user_by_id(user_id=user_id)
|
|
1860
|
-
)
|
|
1861
|
-
# get agent memory
|
|
1862
|
-
memory = self.get_agent(agent_id=agent_id).memory
|
|
1863
|
-
return memory
|
|
1864
|
-
|
|
1865
|
-
def upate_block(self, user_id: str, block_id: str, block_update: BlockUpdate) -> Block:
|
|
1866
|
-
"""Update a block"""
|
|
1867
|
-
return self.block_manager.update_block(
|
|
1868
|
-
block_id=block_id, block_update=block_update, actor=self.user_manager.get_user_by_id(user_id=user_id)
|
|
1869
|
-
)
|
|
1870
|
-
|
|
1871
|
-
def get_agent_block_by_label(self, user_id: str, agent_id: str, label: str) -> Block:
|
|
1872
|
-
"""Get a block by label"""
|
|
1873
|
-
# TODO: implement at ORM?
|
|
1874
|
-
for block_id in self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id):
|
|
1875
|
-
block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
|
|
1876
|
-
if block.label == label:
|
|
1877
|
-
return block
|
|
1878
|
-
return None
|
|
1879
|
-
|
|
1880
|
-
# def run_tool(self, tool_id: str, tool_args: str, user_id: str) -> FunctionReturn:
|
|
1881
|
-
# """Run a tool using the sandbox and return the result"""
|
|
1882
|
-
|
|
1883
|
-
# try:
|
|
1884
|
-
# tool_args_dict = json.loads(tool_args)
|
|
1885
|
-
# except json.JSONDecodeError:
|
|
1886
|
-
# raise ValueError("Invalid JSON string for tool_args")
|
|
1887
|
-
|
|
1888
|
-
# # Get the tool by ID
|
|
1889
|
-
# user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1890
|
-
# tool = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=user)
|
|
1891
|
-
# if tool.name is None:
|
|
1892
|
-
# raise ValueError(f"Tool with id {tool_id} does not have a name")
|
|
1893
|
-
|
|
1894
|
-
# # TODO eventually allow using agent state in tools
|
|
1895
|
-
# agent_state = None
|
|
1896
|
-
|
|
1897
|
-
# try:
|
|
1898
|
-
# sandbox_run_result = ToolExecutionSandbox(tool.name, tool_args_dict, user_id).run(agent_state=agent_state)
|
|
1899
|
-
# if sandbox_run_result is None:
|
|
1900
|
-
# raise ValueError(f"Tool with id {tool_id} returned execution with None")
|
|
1901
|
-
# function_response = str(sandbox_run_result.func_return)
|
|
1902
|
-
|
|
1903
|
-
# return FunctionReturn(
|
|
1904
|
-
# id="null",
|
|
1905
|
-
# function_call_id="null",
|
|
1906
|
-
# date=get_utc_time(),
|
|
1907
|
-
# status="success",
|
|
1908
|
-
# function_return=function_response,
|
|
1909
|
-
# )
|
|
1910
|
-
# except Exception as e:
|
|
1911
|
-
# # same as agent.py
|
|
1912
|
-
# from letta.constants import MAX_ERROR_MESSAGE_CHAR_LIMIT
|
|
1913
|
-
|
|
1914
|
-
# error_msg = f"Error executing tool {tool.name}: {e}"
|
|
1915
|
-
# if len(error_msg) > MAX_ERROR_MESSAGE_CHAR_LIMIT:
|
|
1916
|
-
# error_msg = error_msg[:MAX_ERROR_MESSAGE_CHAR_LIMIT]
|
|
1917
|
-
|
|
1918
|
-
# return FunctionReturn(
|
|
1919
|
-
# id="null",
|
|
1920
|
-
# function_call_id="null",
|
|
1921
|
-
# date=get_utc_time(),
|
|
1922
|
-
# status="error",
|
|
1923
|
-
# function_return=error_msg,
|
|
1924
|
-
# )
|
|
1925
|
-
|
|
1926
1384
|
def run_tool_from_source(
|
|
1927
1385
|
self,
|
|
1928
1386
|
user_id: str,
|
|
@@ -1994,7 +1452,6 @@ class SyncServer(Server):
|
|
|
1994
1452
|
stderr=[traceback.format_exc()],
|
|
1995
1453
|
)
|
|
1996
1454
|
|
|
1997
|
-
|
|
1998
1455
|
def get_error_msg_for_func_return(self, tool_name, exception_message):
|
|
1999
1456
|
# same as agent.py
|
|
2000
1457
|
from letta.constants import MAX_ERROR_MESSAGE_CHAR_LIMIT
|
|
@@ -2004,7 +1461,6 @@ class SyncServer(Server):
|
|
|
2004
1461
|
error_msg = error_msg[:MAX_ERROR_MESSAGE_CHAR_LIMIT]
|
|
2005
1462
|
return error_msg
|
|
2006
1463
|
|
|
2007
|
-
|
|
2008
1464
|
# Composio wrappers
|
|
2009
1465
|
def get_composio_client(self, api_key: Optional[str] = None):
|
|
2010
1466
|
if api_key:
|