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.

Files changed (62) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +54 -45
  3. letta/chat_only_agent.py +6 -8
  4. letta/cli/cli.py +2 -10
  5. letta/client/client.py +121 -138
  6. letta/config.py +0 -161
  7. letta/main.py +3 -8
  8. letta/memory.py +3 -14
  9. letta/o1_agent.py +1 -5
  10. letta/offline_memory_agent.py +2 -6
  11. letta/orm/__init__.py +2 -0
  12. letta/orm/agent.py +109 -0
  13. letta/orm/agents_tags.py +10 -18
  14. letta/orm/block.py +29 -4
  15. letta/orm/blocks_agents.py +5 -11
  16. letta/orm/custom_columns.py +152 -0
  17. letta/orm/message.py +3 -38
  18. letta/orm/organization.py +2 -7
  19. letta/orm/passage.py +10 -32
  20. letta/orm/source.py +5 -25
  21. letta/orm/sources_agents.py +13 -0
  22. letta/orm/sqlalchemy_base.py +54 -30
  23. letta/orm/tool.py +1 -19
  24. letta/orm/tools_agents.py +7 -24
  25. letta/orm/user.py +3 -4
  26. letta/schemas/agent.py +48 -65
  27. letta/schemas/memory.py +2 -1
  28. letta/schemas/sandbox_config.py +12 -1
  29. letta/server/rest_api/app.py +0 -5
  30. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +1 -1
  31. letta/server/rest_api/routers/v1/agents.py +99 -78
  32. letta/server/rest_api/routers/v1/blocks.py +22 -25
  33. letta/server/rest_api/routers/v1/jobs.py +4 -4
  34. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -10
  35. letta/server/rest_api/routers/v1/sources.py +12 -12
  36. letta/server/rest_api/routers/v1/tools.py +35 -15
  37. letta/server/rest_api/routers/v1/users.py +0 -46
  38. letta/server/server.py +172 -716
  39. letta/server/ws_api/server.py +0 -5
  40. letta/services/agent_manager.py +405 -0
  41. letta/services/block_manager.py +13 -21
  42. letta/services/helpers/agent_manager_helper.py +90 -0
  43. letta/services/organization_manager.py +0 -1
  44. letta/services/passage_manager.py +62 -62
  45. letta/services/sandbox_config_manager.py +3 -3
  46. letta/services/source_manager.py +22 -1
  47. letta/services/user_manager.py +11 -6
  48. letta/utils.py +2 -2
  49. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/METADATA +1 -1
  50. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/RECORD +53 -57
  51. letta/metadata.py +0 -407
  52. letta/schemas/agents_tags.py +0 -33
  53. letta/schemas/api_key.py +0 -21
  54. letta/schemas/blocks_agents.py +0 -32
  55. letta/schemas/tools_agents.py +0 -32
  56. letta/server/rest_api/routers/openai/assistants/threads.py +0 -338
  57. letta/services/agents_tags_manager.py +0 -64
  58. letta/services/blocks_agents_manager.py +0 -106
  59. letta/services/tools_agents_manager.py +0 -94
  60. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/LICENSE +0 -0
  61. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/WHEEL +0 -0
  62. {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, LettaUserNotFoundError
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
- AgentState,
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 as PydanticPassage
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 as PydanticUser
76
- from letta.services.agents_tags_manager import AgentsTagsManager
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 create_random_username, get_utc_time, json_dumps, json_loads
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, new_memory_contents: dict) -> dict:
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: PydanticUser,
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 save_agents(self):
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.get_agent(agent_id=agent_id)
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, self.ms)
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.get_agent(agent_id=agent_id)
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, self.ms)
421
+ save_agent(agent)
480
422
  return agent
481
423
 
482
424
  def _step(
483
425
  self,
484
- user_id: str,
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={user_id}, agent={agent_id}) is not loaded")
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, self.ms)
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, self.ms)
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=letta_agent.source_manager,
564
- ms=self.ms,
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(user_id=user_id, agent_id=agent_id, input_message=input_message)
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(user_id=user_id, agent_id=agent_id, input_message=input_message)
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
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
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
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
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(user_id=user_id, agent_id=agent_id, input_messages=message)
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
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
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
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
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(user_id=user_id, agent_id=agent_id, input_messages=message)
705
+ return self._step(actor=actor, agent_id=agent_id, input_messages=message)
756
706
 
757
707
  def send_messages(
758
708
  self,
759
- user_id: str,
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(user_id=user_id, agent_id=agent_id, input_messages=message_objects, interface=interface)
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: PydanticUser,
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
- user_id = actor.id
842
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
843
- raise ValueError(f"User user_id={user_id} does not exist")
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
- self.initialize_agent(agent_id=agent_state.id, interface=interface, initial_message_sequence=init_messages)
941
-
942
- # Note: mappings (e.g. tags, blocks) are created after the agent is persisted
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.get_agent(agent_state.id)
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
- def get_agent(self, agent_id: str) -> Optional[AgentState]:
959
- """
960
- Retrieve the full agent state from the DB.
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
- request: UpdateAgentState,
1002
- actor: PydanticUser,
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=request.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.tool_names:
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 = set(letta_agent.agent_state.tool_names)
1034
- target_tools = set(request.tool_names)
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
- tools_to_add = target_tools - current_tools
1038
- tools_to_remove = current_tools - target_tools
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 tool in tools_to_remove:
1046
- self.remove_tool_from_agent(agent_id=request.id, tool_id=tool.id, user_id=actor.id)
1047
- for tool in tools_to_add:
1048
- self.add_tool_to_agent(agent_id=request.id, tool_id=tool.id, user_id=actor.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=request.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, self.ms)
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
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1086
- raise ValueError(f"User user_id={user_id} does not exist")
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
- try:
1102
- user = self.user_manager.get_user_by_id(user_id=user_id)
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=user)
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=user)
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.tool_names = [tool.name for tool in tool_objs]
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, self.ms)
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
- try:
1144
- user = self.user_manager.get_user_by_id(user_id=user_id)
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=user)
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.tool_names = [tool.name for tool in tool_objs]
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, self.ms)
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 get_agent_id(self, name: str, user_id: str):
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 get_in_context_message_ids(self, agent_id: str) -> List[str]:
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 get_agent_message(self, agent_id: str, message_id: str) -> Message:
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
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1296
- raise ValueError(f"User user_id={user_id} does not exist")
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=self.default_user,
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
- ) -> List[PydanticPassage]:
1320
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1321
- raise LettaUserNotFoundError(f"User user_id={user_id} does not exist")
1322
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1323
- raise LettaAgentNotFoundError(f"Agent agent_id={agent_id} does not exist")
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, user_id: str, agent_id: str, memory_contents: str) -> List[PydanticPassage]:
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
- return self.passage_manager.insert_passage(
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
- def delete_archival_memory(self, user_id: str, agent_id: str, memory_id: str):
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
- # TODO: should return a passage
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 = self.user_manager.get_user_by_id(user_id=user_id)
1383
- if actor is None:
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, user_id: str, agent_id: str, label: str, value: str) -> Memory:
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.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=label)
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 rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> PersistedAgentState:
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: PydanticUser) -> Job:
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
- agent_ids = self.ms.list_attached_agents(source_id=source_id)
1593
- for agent_id in agent_ids:
1594
- agent = self.load_agent(agent_id=agent_id)
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, ms=self.ms)
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 # in case empty files are added
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
- user = self.user_manager.get_user_by_id(user_id=user_id)
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=user)
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=user)
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=user, source_id=data_source.id, source_manager=self.source_manager, ms=self.ms)
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
- user = self.user_manager.get_user_by_id(user_id=user_id)
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=user)
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=user)
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=user, limit=100, source_id=source_id)
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.ms.detach_source(agent_id=agent_id, source_id=source_id)
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 list_attached_sources(self, agent_id: str) -> List[Source]:
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: PydanticUser) -> List[Source]:
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
- agent_ids = self.ms.list_attached_agents(source_id=source.id)
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: PydanticUser) -> bool:
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 get_agent_message(self, agent_id: str, message_id: str) -> Optional[Message]:
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, self.ms)
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, self.ms)
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, self.ms)
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, self.ms)
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: