letta-nightly 0.5.4.dev20241127104220__py3-none-any.whl → 0.5.4.dev20241128104217__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 (38) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +102 -140
  3. letta/agent_store/chroma.py +2 -0
  4. letta/cli/cli.py +3 -5
  5. letta/client/client.py +360 -117
  6. letta/config.py +2 -2
  7. letta/constants.py +5 -0
  8. letta/functions/function_sets/base.py +38 -1
  9. letta/helpers/tool_rule_solver.py +6 -5
  10. letta/main.py +1 -1
  11. letta/metadata.py +39 -41
  12. letta/o1_agent.py +1 -4
  13. letta/persistence_manager.py +1 -0
  14. letta/schemas/agent.py +57 -52
  15. letta/schemas/block.py +69 -25
  16. letta/schemas/enums.py +14 -0
  17. letta/schemas/letta_base.py +1 -1
  18. letta/schemas/letta_request.py +11 -23
  19. letta/schemas/letta_response.py +1 -2
  20. letta/schemas/memory.py +31 -100
  21. letta/schemas/message.py +3 -3
  22. letta/schemas/tool_rule.py +13 -5
  23. letta/server/rest_api/interface.py +12 -19
  24. letta/server/rest_api/routers/openai/assistants/threads.py +2 -3
  25. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -2
  26. letta/server/rest_api/routers/v1/agents.py +90 -86
  27. letta/server/rest_api/routers/v1/blocks.py +50 -5
  28. letta/server/server.py +237 -459
  29. letta/server/static_files/assets/index-9fa459a2.js +1 -1
  30. letta/services/block_manager.py +6 -3
  31. letta/services/blocks_agents_manager.py +15 -0
  32. letta/services/tool_execution_sandbox.py +1 -1
  33. letta/services/tool_manager.py +2 -1
  34. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/METADATA +1 -1
  35. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/RECORD +38 -38
  36. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/LICENSE +0 -0
  37. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/WHEEL +0 -0
  38. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -18,24 +18,10 @@ from letta.agent_store.storage import StorageConnector, TableType
18
18
  from letta.credentials import LettaCredentials
19
19
  from letta.data_sources.connectors import DataConnector, load_data
20
20
 
21
- # from letta.data_types import (
22
- # AgentState,
23
- # EmbeddingConfig,
24
- # LLMConfig,
25
- # Message,
26
- # Preset,
27
- # Source,
28
- # Token,
29
- # User,
30
- # )
31
- from letta.functions.functions import generate_schema, parse_source_code
32
- from letta.functions.schema_generator import generate_schema
33
-
34
21
  # TODO use custom interface
35
22
  from letta.interface import AgentInterface # abstract
36
23
  from letta.interface import CLIInterface # for printing to terminal
37
24
  from letta.log import get_logger
38
- from letta.memory import get_memory_functions
39
25
  from letta.metadata import MetadataStore
40
26
  from letta.o1_agent import O1Agent
41
27
  from letta.orm import Base
@@ -54,8 +40,15 @@ from letta.providers import (
54
40
  VLLMChatCompletionsProvider,
55
41
  VLLMCompletionsProvider,
56
42
  )
57
- from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgentState
43
+ from letta.schemas.agent import (
44
+ AgentState,
45
+ AgentType,
46
+ CreateAgent,
47
+ PersistedAgentState,
48
+ UpdateAgentState,
49
+ )
58
50
  from letta.schemas.api_key import APIKey, APIKeyCreate
51
+ from letta.schemas.block import Block, BlockUpdate
59
52
  from letta.schemas.embedding_config import EmbeddingConfig
60
53
 
61
54
  # openai schemas
@@ -87,9 +80,6 @@ from letta.services.tool_manager import ToolManager
87
80
  from letta.services.user_manager import UserManager
88
81
  from letta.utils import create_random_username, json_dumps, json_loads
89
82
 
90
- # from letta.llm_api_tools import openai_get_model_list, azure_openai_get_model_list, smart_urljoin
91
-
92
-
93
83
  logger = get_logger(__name__)
94
84
 
95
85
 
@@ -129,10 +119,11 @@ class Server(object):
129
119
  @abstractmethod
130
120
  def create_agent(
131
121
  self,
132
- user_id: str,
133
- agent_config: Union[dict, AgentState],
134
- interface: Union[AgentInterface, None],
135
- ) -> str:
122
+ request: CreateAgent,
123
+ actor: User,
124
+ # interface
125
+ interface: Union[AgentInterface, None] = None,
126
+ ) -> AgentState:
136
127
  """Create a new agent using a config"""
137
128
  raise NotImplementedError
138
129
 
@@ -254,8 +245,8 @@ class SyncServer(Server):
254
245
  self.block_manager = BlockManager()
255
246
  self.source_manager = SourceManager()
256
247
  self.agents_tags_manager = AgentsTagsManager()
257
- self.blocks_agents_manager = BlocksAgentsManager()
258
248
  self.sandbox_config_manager = SandboxConfigManager(tool_settings)
249
+ self.blocks_agents_manager = BlocksAgentsManager()
259
250
 
260
251
  # Managers that interface with parallelism
261
252
  self.per_agent_lock_manager = PerAgentLockManager()
@@ -372,92 +363,23 @@ class SyncServer(Server):
372
363
  }
373
364
  )
374
365
 
375
- def _load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
376
- """Loads a saved agent into memory (if it doesn't exist, throw an error)"""
377
- assert isinstance(agent_id, str), agent_id
378
- user_id = actor.id
366
+ def load_agent(self, agent_id: str, interface: Union[AgentInterface, None] = None) -> Agent:
367
+ """Updated method to load agents from persisted storage"""
368
+ agent_state = self.get_agent(agent_id=agent_id)
369
+ actor = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
379
370
 
380
- # If an interface isn't specified, use the default
381
- if interface is None:
382
- interface = self.default_interface_factory()
383
-
384
- try:
385
- logger.debug(f"Grabbing agent user_id={user_id} agent_id={agent_id} from database")
386
- agent_state = self.ms.get_agent(agent_id=agent_id, user_id=user_id)
387
- if not agent_state:
388
- logger.exception(f"agent_id {agent_id} does not exist")
389
- raise ValueError(f"agent_id {agent_id} does not exist")
390
-
391
- # Instantiate an agent object using the state retrieved
392
- logger.debug(f"Creating an agent object")
393
- tool_objs = []
394
- for name in agent_state.tools:
395
- # TODO: This should be a hard failure, but for migration reasons, we patch it for now
396
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
397
- if tool_obj:
398
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
399
- tool_objs.append(tool_obj)
400
- else:
401
- warnings.warn(f"Tried to retrieve a tool with name {name} from the agent_state, but does not exist in tool db.")
402
-
403
- # set agent_state tools to only the names of the available tools
404
- agent_state.tools = [t.name for t in tool_objs]
405
-
406
- # Make sure the memory is a memory object
407
- assert isinstance(agent_state.memory, Memory)
408
-
409
- if agent_state.agent_type == AgentType.memgpt_agent:
410
- letta_agent = Agent(agent_state=agent_state, interface=interface, tools=tool_objs, user=actor)
411
- elif agent_state.agent_type == AgentType.o1_agent:
412
- letta_agent = O1Agent(agent_state=agent_state, interface=interface, tools=tool_objs, user=actor)
413
- else:
414
- raise NotImplementedError("Not a supported agent type")
415
-
416
- # Add the agent to the in-memory store and return its reference
417
- logger.debug(f"Adding agent to the agent cache: user_id={user_id}, agent_id={agent_id}")
418
- self._add_agent(user_id=user_id, agent_id=agent_id, agent_obj=letta_agent)
419
- return letta_agent
420
-
421
- except Exception as e:
422
- logger.exception(f"Error occurred while trying to get agent {agent_id}:\n{e}")
423
- raise
424
-
425
- def _get_or_load_agent(self, agent_id: str, caching: bool = True) -> Agent:
426
- """Check if the agent is in-memory, then load"""
427
-
428
- # Gets the agent state
429
- agent_state = self.ms.get_agent(agent_id=agent_id)
430
- if not agent_state:
431
- raise ValueError(f"Agent does not exist")
432
- user_id = agent_state.user_id
433
- actor = self.user_manager.get_user_by_id(user_id)
434
-
435
- logger.debug(f"Checking for agent user_id={user_id} agent_id={agent_id}")
436
- if caching:
437
- # TODO: consider disabling loading cached agents due to potential concurrency issues
438
- letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
439
- if not letta_agent:
440
- logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
441
- letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
371
+ interface = interface or self.default_interface_factory()
372
+ if agent_state.agent_type == AgentType.memgpt_agent:
373
+ return Agent(agent_state=agent_state, interface=interface, user=actor)
442
374
  else:
443
- # This breaks unit tests in test_local_client.py
444
- letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
445
-
446
- # letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
447
- # if not letta_agent:
448
- # logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
449
-
450
- # NOTE: no longer caching, always forcing a lot from the database
451
- # Loads the agent objects
452
- # letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
453
-
454
- return letta_agent
375
+ return O1Agent(agent_state=agent_state, interface=interface, user=actor)
455
376
 
456
377
  def _step(
457
378
  self,
458
379
  user_id: str,
459
380
  agent_id: str,
460
381
  input_messages: Union[Message, List[Message]],
382
+ interface: Union[AgentInterface, None] = None, # needed to getting responses
461
383
  # timestamp: Optional[datetime],
462
384
  ) -> LettaUsageStatistics:
463
385
  """Send the input message through the agent"""
@@ -473,7 +395,8 @@ class SyncServer(Server):
473
395
  try:
474
396
 
475
397
  # Get the agent object (loaded in memory)
476
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
398
+ # letta_agent = self._get_or_load_agent(agent_id=agent_id)
399
+ letta_agent = self.load_agent(agent_id=agent_id, interface=interface)
477
400
  if letta_agent is None:
478
401
  raise KeyError(f"Agent (user={user_id}, agent={agent_id}) is not loaded")
479
402
 
@@ -490,6 +413,9 @@ class SyncServer(Server):
490
413
  skip_verify=True,
491
414
  )
492
415
 
416
+ # save agent after step
417
+ save_agent(letta_agent, self.ms)
418
+
493
419
  except Exception as e:
494
420
  logger.error(f"Error in server._step: {e}")
495
421
  print(traceback.print_exc())
@@ -507,7 +433,7 @@ class SyncServer(Server):
507
433
  logger.debug(f"Got command: {command}")
508
434
 
509
435
  # Get the agent object (loaded in memory)
510
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
436
+ letta_agent = self.load_agent(agent_id=agent_id)
511
437
  usage = None
512
438
 
513
439
  if command.lower() == "exit":
@@ -544,7 +470,7 @@ class SyncServer(Server):
544
470
  elif command.lower() == "memory":
545
471
  ret_str = (
546
472
  f"\nDumping memory contents:\n"
547
- + f"\n{str(letta_agent.memory)}"
473
+ + f"\n{str(letta_agent.agent_state.memory)}"
548
474
  + f"\n{str(letta_agent.persistence_manager.archival_memory)}"
549
475
  + f"\n{str(letta_agent.persistence_manager.recall_memory)}"
550
476
  )
@@ -736,6 +662,7 @@ class SyncServer(Server):
736
662
  # whether or not to wrap user and system message as MemGPT-style stringified JSON
737
663
  wrap_user_message: bool = True,
738
664
  wrap_system_message: bool = True,
665
+ interface: Union[AgentInterface, None] = None, # needed to getting responses
739
666
  ) -> LettaUsageStatistics:
740
667
  """Send a list of messages to the agent
741
668
 
@@ -788,7 +715,7 @@ class SyncServer(Server):
788
715
  raise ValueError(f"All messages must be of type Message or MessageCreate, got {[type(message) for message in messages]}")
789
716
 
790
717
  # Run the agent state forward
791
- return self._step(user_id=user_id, agent_id=agent_id, input_messages=message_objects)
718
+ return self._step(user_id=user_id, agent_id=agent_id, input_messages=message_objects, interface=interface)
792
719
 
793
720
  # @LockingServer.agent_lock_decorator
794
721
  def run_command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
@@ -836,129 +763,109 @@ class SyncServer(Server):
836
763
  else:
837
764
  raise ValueError(f"Invalid agent type: {request.agent_type}")
838
765
 
766
+ # create blocks (note: cannot be linked into the agent_id is created)
767
+ blocks = []
768
+ for create_block in request.memory_blocks:
769
+ block = self.block_manager.create_or_update_block(Block(**create_block.model_dump()), actor=actor)
770
+ blocks.append(block)
771
+
772
+ # get tools + only add if they exist
773
+ tool_objs = []
774
+ if request.tools:
775
+ for tool_name in request.tools:
776
+ tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
777
+ if tool_obj:
778
+ tool_objs.append(tool_obj)
779
+ else:
780
+ warnings.warn(f"Attempted to add a nonexistent tool {tool_name} to agent {request.name}, skipping.")
781
+ # reset the request.tools to only valid tools
782
+ request.tools = [t.name for t in tool_objs]
783
+
784
+ # get the user
839
785
  logger.debug(f"Attempting to find user: {user_id}")
840
786
  user = self.user_manager.get_user_by_id(user_id=user_id)
841
787
  if not user:
842
788
  raise ValueError(f"cannot find user with associated client id: {user_id}")
843
789
 
844
- try:
845
- # model configuration
846
- llm_config = request.llm_config
847
- embedding_config = request.embedding_config
848
-
849
- # get tools + only add if they exist
850
- tool_objs = []
851
- if request.tools:
852
- for tool_name in request.tools:
853
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
854
- if tool_obj:
855
- tool_objs.append(tool_obj)
856
- else:
857
- warnings.warn(f"Attempted to add a nonexistent tool {tool_name} to agent {request.name}, skipping.")
858
- # reset the request.tools to only valid tools
859
- request.tools = [t.name for t in tool_objs]
860
-
861
- assert request.memory is not None
862
- memory_functions = get_memory_functions(request.memory)
863
- for func_name, func in memory_functions.items():
864
-
865
- if request.tools and func_name in request.tools:
866
- # tool already added
867
- continue
868
- source_code = parse_source_code(func)
869
- # memory functions are not terminal
870
- json_schema = generate_schema(func, name=func_name)
871
- source_type = "python"
872
- tags = ["memory", "memgpt-base"]
873
- tool = self.tool_manager.create_or_update_tool(
874
- Tool(
875
- source_code=source_code,
876
- source_type=source_type,
877
- tags=tags,
878
- json_schema=json_schema,
879
- ),
880
- actor=actor,
881
- )
882
- tool_objs.append(tool)
883
- if not request.tools:
884
- request.tools = []
885
- request.tools.append(tool.name)
886
-
887
- # TODO: save the agent state
888
- agent_state = AgentState(
889
- name=request.name,
890
- user_id=user_id,
891
- tools=request.tools if request.tools else [],
892
- tool_rules=request.tool_rules if request.tool_rules else [],
893
- agent_type=request.agent_type or AgentType.memgpt_agent,
894
- llm_config=llm_config,
895
- embedding_config=embedding_config,
896
- system=request.system,
897
- memory=request.memory,
898
- description=request.description,
899
- metadata_=request.metadata_,
900
- tags=request.tags,
901
- )
902
- if request.agent_type == AgentType.memgpt_agent:
903
- agent = Agent(
904
- interface=interface,
905
- agent_state=agent_state,
906
- tools=tool_objs,
907
- # gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
908
- first_message_verify_mono=(
909
- True if (llm_config and llm_config.model is not None and "gpt-4" in llm_config.model) else False
910
- ),
911
- user=actor,
912
- initial_message_sequence=request.initial_message_sequence,
913
- )
914
- elif request.agent_type == AgentType.o1_agent:
915
- agent = O1Agent(
916
- interface=interface,
917
- agent_state=agent_state,
918
- tools=tool_objs,
919
- # gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
920
- first_message_verify_mono=(
921
- True if (llm_config and llm_config.model is not None and "gpt-4" in llm_config.model) else False
922
- ),
923
- user=actor,
924
- )
925
- # rebuilding agent memory on agent create in case shared memory blocks
926
- # were specified in the new agent's memory config. we're doing this for two reasons:
927
- # 1. if only the ID of the shared memory block was specified, we can fetch its most recent value
928
- # 2. if the shared block state changed since this agent initialization started, we can be sure to have the latest value
929
- agent.rebuild_memory(force=True, ms=self.ms)
930
- # FIXME: this is a hacky way to get the system prompts injected into agent into the DB
931
- # self.ms.update_agent(agent.agent_state)
932
- except Exception as e:
933
- logger.exception(e)
934
- try:
935
- if agent:
936
- self.ms.delete_agent(agent_id=agent.agent_state.id, per_agent_lock_manager=self.per_agent_lock_manager)
937
- except Exception as delete_e:
938
- logger.exception(f"Failed to delete_agent:\n{delete_e}")
939
- raise e
790
+ # TODO: create the message objects (NOTE: do this after we migrate to `CreateMessage`)
791
+
792
+ # created and persist the agent state in the DB
793
+ agent_state = PersistedAgentState(
794
+ name=request.name,
795
+ user_id=user_id,
796
+ tool_names=request.tools if request.tools else [],
797
+ tool_rules=request.tool_rules,
798
+ agent_type=request.agent_type or AgentType.memgpt_agent,
799
+ llm_config=request.llm_config,
800
+ embedding_config=request.embedding_config,
801
+ system=request.system,
802
+ # other metadata
803
+ description=request.description,
804
+ metadata_=request.metadata_,
805
+ )
806
+ # TODO: move this to agent ORM
807
+ # this saves the agent ID and state into the DB
808
+ self.ms.create_agent(agent_state)
940
809
 
941
- # save agent
942
- save_agent(agent, self.ms)
943
- logger.debug(f"Created new agent from config: {agent}")
810
+ # Note: mappings (e.g. tags, blocks) are created after the agent is persisted
811
+ # TODO: add source mappings here as well
944
812
 
945
- # TODO: move this into save_agent. save_agent should be moved to server.py
813
+ # create the tags
946
814
  if request.tags:
947
815
  for tag in request.tags:
948
- self.agents_tags_manager.add_tag_to_agent(agent_id=agent.agent_state.id, tag=tag, actor=actor)
816
+ self.agents_tags_manager.add_tag_to_agent(agent_id=agent_state.id, tag=tag, actor=actor)
949
817
 
950
- assert isinstance(agent.agent_state.memory, Memory), f"Invalid memory type: {type(agent_state.memory)}"
818
+ # create block mappins (now that agent is persisted)
819
+ for block in blocks:
820
+ # this links the created block to the agent
821
+ self.blocks_agents_manager.add_block_to_agent(block_id=block.id, agent_id=agent_state.id, block_label=block.label)
951
822
 
952
- # TODO: remove (hacky)
953
- agent.agent_state.tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent.agent_state.id, actor=actor)
823
+ in_memory_agent_state = self.get_agent(agent_state.id)
824
+ return in_memory_agent_state
954
825
 
955
- return agent.agent_state
826
+ def get_agent(self, agent_id: str) -> AgentState:
827
+ """
828
+ Retrieve the full agent state from the DB.
829
+ This gathers data accross multiple tables to provide the full state of an agent, which is passed into the `Agent` object for creation.
830
+ """
831
+
832
+ # get data persisted from the DB
833
+ agent_state = self.ms.get_agent(agent_id=agent_id)
834
+ if agent_state is None:
835
+ # agent does not exist
836
+ return None
837
+ user = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
838
+
839
+ # construct the in-memory, full agent state - this gather data stored in different tables but that needs to be passed to `Agent`
840
+ # we also return this data to the user to provide all the state related to an agent
841
+
842
+ # get `Memory` object by getting the linked block IDs and fetching the blocks, then putting that into a `Memory` object
843
+ # this is the "in memory" representation of the in-context memory
844
+ block_ids = self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id)
845
+ blocks = []
846
+ for block_id in block_ids:
847
+ block = self.block_manager.get_block_by_id(block_id=block_id, actor=user)
848
+ assert block, f"Block with ID {block_id} does not exist"
849
+ blocks.append(block)
850
+ memory = Memory(blocks=blocks)
851
+
852
+ # get `Tool` objects
853
+ tools = [self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=user) for tool_name in agent_state.tool_names]
854
+
855
+ # get `Source` objects
856
+ sources = self.list_attached_sources(agent_id=agent_id)
857
+
858
+ # get the tags
859
+ tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
860
+
861
+ # return the full agent state - this contains all data needed to recreate the agent
862
+ return AgentState(**agent_state.model_dump(), memory=memory, tools=tools, sources=sources, tags=tags)
956
863
 
957
864
  def update_agent(
958
865
  self,
959
866
  request: UpdateAgentState,
960
867
  actor: User,
961
- ):
868
+ ) -> AgentState:
962
869
  """Update the agents core memory block, return the new state"""
963
870
  try:
964
871
  self.user_manager.get_user_by_id(user_id=actor.id)
@@ -969,13 +876,7 @@ class SyncServer(Server):
969
876
  raise ValueError(f"Agent agent_id={request.id} does not exist")
970
877
 
971
878
  # Get the agent object (loaded in memory)
972
- letta_agent = self._get_or_load_agent(agent_id=request.id)
973
-
974
- # update the core memory of the agent
975
- if request.memory:
976
- assert isinstance(request.memory, Memory), type(request.memory)
977
- new_memory_contents = request.memory.to_flat_dict()
978
- _ = self.update_agent_core_memory(user_id=actor.id, agent_id=request.id, new_memory_contents=new_memory_contents)
879
+ letta_agent = self.load_agent(agent_id=request.id)
979
880
 
980
881
  # update the system prompt
981
882
  if request.system:
@@ -989,13 +890,13 @@ class SyncServer(Server):
989
890
  letta_agent.set_message_buffer(message_ids=request.message_ids)
990
891
 
991
892
  # tools
992
- if request.tools:
893
+ if request.tool_names:
993
894
  # Replace tools and also re-link
994
895
 
995
896
  # (1) get tools + make sure they exist
996
897
  # Current and target tools as sets of tool names
997
- current_tools = set(letta_agent.agent_state.tools)
998
- target_tools = set(request.tools)
898
+ current_tools = set(letta_agent.agent_state.tool_names)
899
+ target_tools = set(request.tool_names)
999
900
 
1000
901
  # Calculate tools to add and remove
1001
902
  tools_to_add = target_tools - current_tools
@@ -1012,7 +913,7 @@ class SyncServer(Server):
1012
913
  self.add_tool_to_agent(agent_id=request.id, tool_id=tool.id, user_id=actor.id)
1013
914
 
1014
915
  # reload agent
1015
- letta_agent = self._get_or_load_agent(agent_id=request.id)
916
+ letta_agent = self.load_agent(agent_id=request.id)
1016
917
 
1017
918
  # configs
1018
919
  if request.llm_config:
@@ -1040,7 +941,6 @@ class SyncServer(Server):
1040
941
  self.agents_tags_manager.delete_tag_from_agent(agent_id=letta_agent.agent_state.id, tag=tag, actor=actor)
1041
942
 
1042
943
  # save the agent
1043
- assert isinstance(letta_agent.memory, Memory)
1044
944
  save_agent(letta_agent, self.ms)
1045
945
  # TODO: probably reload the agent somehow?
1046
946
  return letta_agent.agent_state
@@ -1053,8 +953,8 @@ class SyncServer(Server):
1053
953
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1054
954
 
1055
955
  # Get the agent object (loaded in memory)
1056
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1057
- return letta_agent.tools
956
+ letta_agent = self.load_agent(agent_id=agent_id)
957
+ return letta_agent.agent_state.tools
1058
958
 
1059
959
  def add_tool_to_agent(
1060
960
  self,
@@ -1072,7 +972,7 @@ class SyncServer(Server):
1072
972
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1073
973
 
1074
974
  # Get the agent object (loaded in memory)
1075
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
975
+ letta_agent = self.load_agent(agent_id=agent_id)
1076
976
 
1077
977
  # Get all the tool objects from the request
1078
978
  tool_objs = []
@@ -1080,7 +980,7 @@ class SyncServer(Server):
1080
980
  assert tool_obj, f"Tool with id={tool_id} does not exist"
1081
981
  tool_objs.append(tool_obj)
1082
982
 
1083
- for tool in letta_agent.tools:
983
+ for tool in letta_agent.agent_state.tools:
1084
984
  tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
1085
985
  assert tool_obj, f"Tool with id={tool.id} does not exist"
1086
986
 
@@ -1089,7 +989,7 @@ class SyncServer(Server):
1089
989
  tool_objs.append(tool_obj)
1090
990
 
1091
991
  # replace the list of tool names ("ids") inside the agent state
1092
- letta_agent.agent_state.tools = [tool.name for tool in tool_objs]
992
+ letta_agent.agent_state.tool_names = [tool.name for tool in tool_objs]
1093
993
 
1094
994
  # then attempt to link the tools modules
1095
995
  letta_agent.link_tools(tool_objs)
@@ -1114,11 +1014,11 @@ class SyncServer(Server):
1114
1014
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1115
1015
 
1116
1016
  # Get the agent object (loaded in memory)
1117
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1017
+ letta_agent = self.load_agent(agent_id=agent_id)
1118
1018
 
1119
1019
  # Get all the tool_objs
1120
1020
  tool_objs = []
1121
- for tool in letta_agent.tools:
1021
+ for tool in letta_agent.agent_state.tools:
1122
1022
  tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
1123
1023
  assert tool_obj, f"Tool with id={tool.id} does not exist"
1124
1024
 
@@ -1127,7 +1027,7 @@ class SyncServer(Server):
1127
1027
  tool_objs.append(tool_obj)
1128
1028
 
1129
1029
  # replace the list of tool names ("ids") inside the agent state
1130
- letta_agent.agent_state.tools = [tool.name for tool in tool_objs]
1030
+ letta_agent.agent_state.tool_names = [tool.name for tool in tool_objs]
1131
1031
 
1132
1032
  # then attempt to link the tools modules
1133
1033
  letta_agent.link_tools(tool_objs)
@@ -1136,18 +1036,9 @@ class SyncServer(Server):
1136
1036
  save_agent(letta_agent, self.ms)
1137
1037
  return letta_agent.agent_state
1138
1038
 
1139
- def _agent_state_to_config(self, agent_state: AgentState) -> dict:
1140
- """Convert AgentState to a dict for a JSON response"""
1141
- assert agent_state is not None
1142
-
1143
- agent_config = {
1144
- "id": agent_state.id,
1145
- "name": agent_state.name,
1146
- "human": agent_state._metadata.get("human", None),
1147
- "persona": agent_state._metadata.get("persona", None),
1148
- "created_at": agent_state.created_at.isoformat(),
1149
- }
1150
- return agent_config
1039
+ def get_agent_state(self, user_id: str, agent_id: str) -> AgentState:
1040
+ # TODO: duplicate, remove
1041
+ return self.get_agent(agent_id=agent_id)
1151
1042
 
1152
1043
  def list_agents(self, user_id: str, tags: Optional[List[str]] = None) -> List[AgentState]:
1153
1044
  """List all available agents to a user"""
@@ -1155,13 +1046,13 @@ class SyncServer(Server):
1155
1046
 
1156
1047
  if tags is None:
1157
1048
  agents_states = self.ms.list_agents(user_id=user_id)
1158
- return agents_states
1049
+ agent_ids = [agent.id for agent in agents_states]
1159
1050
  else:
1160
1051
  agent_ids = []
1161
1052
  for tag in tags:
1162
1053
  agent_ids += self.agents_tags_manager.get_agents_by_tag(tag=tag, actor=user)
1163
1054
 
1164
- return [self.get_agent_state(user_id=user.id, agent_id=agent_id) for agent_id in agent_ids]
1055
+ return [self.get_agent(agent_id=agent_id) for agent_id in agent_ids]
1165
1056
 
1166
1057
  # convert name->id
1167
1058
 
@@ -1185,34 +1076,34 @@ class SyncServer(Server):
1185
1076
 
1186
1077
  def get_agent_memory(self, agent_id: str) -> Memory:
1187
1078
  """Return the memory of an agent (core memory)"""
1188
- agent = self._get_or_load_agent(agent_id=agent_id)
1189
- return agent.memory
1079
+ agent = self.load_agent(agent_id=agent_id)
1080
+ return agent.agent_state.memory
1190
1081
 
1191
1082
  def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1192
- agent = self._get_or_load_agent(agent_id=agent_id)
1083
+ agent = self.load_agent(agent_id=agent_id)
1193
1084
  return ArchivalMemorySummary(size=len(agent.persistence_manager.archival_memory))
1194
1085
 
1195
1086
  def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1196
- agent = self._get_or_load_agent(agent_id=agent_id)
1087
+ agent = self.load_agent(agent_id=agent_id)
1197
1088
  return RecallMemorySummary(size=len(agent.persistence_manager.recall_memory))
1198
1089
 
1199
1090
  def get_in_context_message_ids(self, agent_id: str) -> List[str]:
1200
1091
  """Get the message ids of the in-context messages in the agent's memory"""
1201
1092
  # Get the agent object (loaded in memory)
1202
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1203
- return [m.id for m in letta_agent._messages]
1093
+ agent = self.load_agent(agent_id=agent_id)
1094
+ return [m.id for m in agent._messages]
1204
1095
 
1205
1096
  def get_in_context_messages(self, agent_id: str) -> List[Message]:
1206
1097
  """Get the in-context messages in the agent's memory"""
1207
1098
  # Get the agent object (loaded in memory)
1208
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1209
- return letta_agent._messages
1099
+ agent = self.load_agent(agent_id=agent_id)
1100
+ return agent._messages
1210
1101
 
1211
1102
  def get_agent_message(self, agent_id: str, message_id: str) -> Message:
1212
1103
  """Get a single message from the agent's memory"""
1213
1104
  # Get the agent object (loaded in memory)
1214
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1215
- message = letta_agent.persistence_manager.recall_memory.storage.get(id=message_id)
1105
+ agent = self.load_agent(agent_id=agent_id)
1106
+ message = agent.persistence_manager.recall_memory.storage.get(id=message_id)
1216
1107
  return message
1217
1108
 
1218
1109
  def get_agent_messages(
@@ -1220,11 +1111,10 @@ class SyncServer(Server):
1220
1111
  agent_id: str,
1221
1112
  start: int,
1222
1113
  count: int,
1223
- return_message_object: bool = True,
1224
1114
  ) -> Union[List[Message], List[LettaMessage]]:
1225
1115
  """Paginated query of all messages in agent message queue"""
1226
1116
  # Get the agent object (loaded in memory)
1227
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1117
+ letta_agent = self.load_agent(agent_id=agent_id)
1228
1118
 
1229
1119
  if start < 0 or count < 0:
1230
1120
  raise ValueError("Start and count values should be non-negative")
@@ -1242,10 +1132,6 @@ class SyncServer(Server):
1242
1132
  # Slice the list for pagination
1243
1133
  messages = reversed_messages[start:end_index]
1244
1134
 
1245
- ## Convert to json
1246
- ## Add a tag indicating in-context or not
1247
- # json_messages = [{**record.to_json(), "in_context": True} for record in messages]
1248
-
1249
1135
  else:
1250
1136
  # need to access persistence manager for additional messages
1251
1137
  db_iterator = letta_agent.persistence_manager.recall_memory.storage.get_all_paginated(page_size=count, offset=start)
@@ -1265,9 +1151,6 @@ class SyncServer(Server):
1265
1151
  # for d in json_messages:
1266
1152
  # d["in_context"] = True if str(d["id"]) in in_context_message_ids else False
1267
1153
 
1268
- if not return_message_object:
1269
- messages = [msg for m in messages for msg in m.to_letta_message()]
1270
-
1271
1154
  return messages
1272
1155
 
1273
1156
  def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
@@ -1278,7 +1161,7 @@ class SyncServer(Server):
1278
1161
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1279
1162
 
1280
1163
  # Get the agent object (loaded in memory)
1281
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1164
+ letta_agent = self.load_agent(agent_id=agent_id)
1282
1165
 
1283
1166
  # iterate over records
1284
1167
  db_iterator = letta_agent.persistence_manager.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
@@ -1303,7 +1186,7 @@ class SyncServer(Server):
1303
1186
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1304
1187
 
1305
1188
  # Get the agent object (loaded in memory)
1306
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1189
+ letta_agent = self.load_agent(agent_id=agent_id)
1307
1190
 
1308
1191
  # iterate over recorde
1309
1192
  cursor, records = letta_agent.persistence_manager.archival_memory.storage.get_all_cursor(
@@ -1318,11 +1201,15 @@ class SyncServer(Server):
1318
1201
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1319
1202
 
1320
1203
  # Get the agent object (loaded in memory)
1321
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1204
+ letta_agent = self.load_agent(agent_id=agent_id)
1322
1205
 
1323
1206
  # Insert into archival memory
1324
1207
  passage_ids = letta_agent.persistence_manager.archival_memory.insert(memory_string=memory_contents, return_ids=True)
1325
1208
 
1209
+ # Update the agent
1210
+ # TODO: should this update the system prompt?
1211
+ save_agent(letta_agent, self.ms)
1212
+
1326
1213
  # TODO: this is gross, fix
1327
1214
  return [letta_agent.persistence_manager.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1328
1215
 
@@ -1335,7 +1222,7 @@ class SyncServer(Server):
1335
1222
  # TODO: should return a passage
1336
1223
 
1337
1224
  # Get the agent object (loaded in memory)
1338
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1225
+ letta_agent = self.load_agent(agent_id=agent_id)
1339
1226
 
1340
1227
  # Delete by ID
1341
1228
  # TODO check if it exists first, and throw error if not
@@ -1354,9 +1241,8 @@ class SyncServer(Server):
1354
1241
  order: Optional[str] = "asc",
1355
1242
  reverse: Optional[bool] = False,
1356
1243
  return_message_object: bool = True,
1357
- use_assistant_message: bool = False,
1358
- assistant_message_function_name: str = constants.DEFAULT_MESSAGE_TOOL,
1359
- assistant_message_function_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
1244
+ assistant_message_tool_name: str = constants.DEFAULT_MESSAGE_TOOL,
1245
+ assistant_message_tool_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
1360
1246
  ) -> Union[List[Message], List[LettaMessage]]:
1361
1247
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1362
1248
  raise ValueError(f"User user_id={user_id} does not exist")
@@ -1364,7 +1250,7 @@ class SyncServer(Server):
1364
1250
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1365
1251
 
1366
1252
  # Get the agent object (loaded in memory)
1367
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1253
+ letta_agent = self.load_agent(agent_id=agent_id)
1368
1254
 
1369
1255
  # iterate over records
1370
1256
  cursor, records = letta_agent.persistence_manager.recall_memory.storage.get_all_cursor(
@@ -1375,50 +1261,19 @@ class SyncServer(Server):
1375
1261
 
1376
1262
  if not return_message_object:
1377
1263
  # If we're GETing messages in reverse, we need to reverse the inner list (generated by to_letta_message)
1378
- if reverse:
1379
- records = [
1380
- msg
1381
- for m in records
1382
- for msg in m.to_letta_message(
1383
- assistant_message=use_assistant_message,
1384
- assistant_message_function_name=assistant_message_function_name,
1385
- assistant_message_function_kwarg=assistant_message_function_kwarg,
1386
- )[::-1]
1387
- ]
1388
- else:
1389
- records = [
1390
- msg
1391
- for m in records
1392
- for msg in m.to_letta_message(
1393
- assistant_message=use_assistant_message,
1394
- assistant_message_function_name=assistant_message_function_name,
1395
- assistant_message_function_kwarg=assistant_message_function_kwarg,
1396
- )
1397
- ]
1398
-
1399
- return records
1400
-
1401
- def get_agent_state(self, user_id: str, agent_id: Optional[str], agent_name: Optional[str] = None) -> Optional[AgentState]:
1402
- """Return the config of an agent"""
1403
- user = self.user_manager.get_user_by_id(user_id=user_id)
1404
- if agent_id:
1405
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1406
- return None
1407
- else:
1408
- agent_state = self.ms.get_agent(agent_name=agent_name, user_id=user_id)
1409
- if agent_state is None:
1410
- raise ValueError(f"Agent agent_name={agent_name} does not exist")
1411
- agent_id = agent_state.id
1264
+ records = [
1265
+ msg
1266
+ for m in records
1267
+ for msg in m.to_letta_message(
1268
+ assistant_message_tool_name=assistant_message_tool_name,
1269
+ assistant_message_tool_kwarg=assistant_message_tool_kwarg,
1270
+ )
1271
+ ]
1412
1272
 
1413
- # Get the agent object (loaded in memory)
1414
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1415
- assert isinstance(letta_agent.memory, Memory)
1273
+ if reverse:
1274
+ records = records[::-1]
1416
1275
 
1417
- letta_agent.update_memory_blocks_from_db()
1418
- agent_state = letta_agent.agent_state.model_copy(deep=True)
1419
- # Load the tags in for the agent_state
1420
- agent_state.tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
1421
- return agent_state
1276
+ return records
1422
1277
 
1423
1278
  def get_server_config(self, include_defaults: bool = False) -> dict:
1424
1279
  """Return the base config"""
@@ -1443,39 +1298,23 @@ class SyncServer(Server):
1443
1298
 
1444
1299
  return response
1445
1300
 
1446
- def update_agent_core_memory(self, user_id: str, agent_id: str, new_memory_contents: dict) -> Memory:
1447
- """Update the agents core memory block, return the new state"""
1448
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1449
- raise ValueError(f"User user_id={user_id} does not exist")
1450
- if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1451
- raise ValueError(f"Agent agent_id={agent_id} does not exist")
1301
+ def update_agent_core_memory(self, user_id: str, agent_id: str, label: str, value: str) -> Memory:
1302
+ """Update the value of a block in the agent's memory"""
1452
1303
 
1453
- # Get the agent object (loaded in memory)
1454
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1455
-
1456
- # old_core_memory = self.get_agent_memory(agent_id=agent_id)
1457
-
1458
- modified = False
1459
- for key, value in new_memory_contents.items():
1460
- if letta_agent.memory.get_block(key) is None:
1461
- # raise ValueError(f"Key {key} not found in agent memory {list(letta_agent.memory.list_block_names())}")
1462
- raise ValueError(f"Key {key} not found in agent memory {str(letta_agent.memory.memory)}")
1463
- if value is None:
1464
- continue
1465
- if letta_agent.memory.get_block(key) != value:
1466
- letta_agent.memory.update_block_value(label=key, value=value) # update agent memory
1467
- modified = True
1468
-
1469
- # If we modified the memory contents, we need to rebuild the memory block inside the system message
1470
- if modified:
1471
- letta_agent.rebuild_memory()
1472
- # letta_agent.rebuild_memory(force=True, ms=self.ms) # This breaks unit tests in test_local_client.py
1473
- # save agent
1474
- save_agent(letta_agent, self.ms)
1304
+ # get the block id
1305
+ block = self.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=label)
1306
+ block_id = block.id
1307
+
1308
+ # update the block
1309
+ self.block_manager.update_block(
1310
+ block_id=block_id, block_update=BlockUpdate(value=value), actor=self.user_manager.get_user_by_id(user_id=user_id)
1311
+ )
1475
1312
 
1476
- return self.ms.get_agent(agent_id=agent_id).memory
1313
+ # load agent
1314
+ letta_agent = self.load_agent(agent_id=agent_id)
1315
+ return letta_agent.agent_state.memory
1477
1316
 
1478
- def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> AgentState:
1317
+ def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> PersistedAgentState:
1479
1318
  """Update the name of the agent in the database"""
1480
1319
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1481
1320
  raise ValueError(f"User user_id={user_id} does not exist")
@@ -1483,7 +1322,7 @@ class SyncServer(Server):
1483
1322
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1484
1323
 
1485
1324
  # Get the agent object (loaded in memory)
1486
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1325
+ letta_agent = self.load_agent(agent_id=agent_id)
1487
1326
 
1488
1327
  current_name = letta_agent.agent_state.name
1489
1328
  if current_name == new_agent_name:
@@ -1505,6 +1344,7 @@ class SyncServer(Server):
1505
1344
  # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM MODEL
1506
1345
  # TODO: EVENTUALLY WE GET AUTO-DELETES WHEN WE SPECIFY RELATIONSHIPS IN THE ORM
1507
1346
  self.agents_tags_manager.delete_all_tags_from_agent(agent_id=agent_id, actor=actor)
1347
+ self.blocks_agents_manager.remove_all_agent_blocks(agent_id=agent_id)
1508
1348
 
1509
1349
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1510
1350
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1672,9 +1512,10 @@ class SyncServer(Server):
1672
1512
  raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
1673
1513
  # get connection to data source storage
1674
1514
  source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1515
+ assert data_source, f"Data source with id={source_id} or name={source_name} does not exist"
1675
1516
 
1676
1517
  # load agent
1677
- agent = self._get_or_load_agent(agent_id=agent_id)
1518
+ agent = self.load_agent(agent_id=agent_id)
1678
1519
 
1679
1520
  # attach source to agent
1680
1521
  agent.attach_source(data_source.id, source_connector, self.ms)
@@ -1699,7 +1540,7 @@ class SyncServer(Server):
1699
1540
  source_id = source.id
1700
1541
 
1701
1542
  # delete all Passage objects with source_id==source_id from agent's archival memory
1702
- agent = self._get_or_load_agent(agent_id=agent_id)
1543
+ agent = self.load_agent(agent_id=agent_id)
1703
1544
  archival_memory = agent.persistence_manager.archival_memory
1704
1545
  archival_memory.storage.delete({"source_id": source_id})
1705
1546
 
@@ -1778,34 +1619,43 @@ class SyncServer(Server):
1778
1619
  def get_agent_message(self, agent_id: str, message_id: str) -> Optional[Message]:
1779
1620
  """Get a single message from the agent's memory"""
1780
1621
  # Get the agent object (loaded in memory)
1781
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1622
+ letta_agent = self.load_agent(agent_id=agent_id)
1782
1623
  message = letta_agent.persistence_manager.recall_memory.storage.get(id=message_id)
1624
+ save_agent(letta_agent, self.ms)
1783
1625
  return message
1784
1626
 
1785
1627
  def update_agent_message(self, agent_id: str, request: UpdateMessage) -> Message:
1786
1628
  """Update the details of a message associated with an agent"""
1787
1629
 
1788
1630
  # Get the current message
1789
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1790
- return letta_agent.update_message(request=request)
1631
+ letta_agent = self.load_agent(agent_id=agent_id)
1632
+ response = letta_agent.update_message(request=request)
1633
+ save_agent(letta_agent, self.ms)
1634
+ return response
1791
1635
 
1792
1636
  def rewrite_agent_message(self, agent_id: str, new_text: str) -> Message:
1793
1637
 
1794
1638
  # Get the current message
1795
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1796
- return letta_agent.rewrite_message(new_text=new_text)
1639
+ letta_agent = self.load_agent(agent_id=agent_id)
1640
+ response = letta_agent.rewrite_message(new_text=new_text)
1641
+ save_agent(letta_agent, self.ms)
1642
+ return response
1797
1643
 
1798
1644
  def rethink_agent_message(self, agent_id: str, new_thought: str) -> Message:
1799
1645
 
1800
1646
  # Get the current message
1801
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1802
- return letta_agent.rethink_message(new_thought=new_thought)
1647
+ letta_agent = self.load_agent(agent_id=agent_id)
1648
+ response = letta_agent.rethink_message(new_thought=new_thought)
1649
+ save_agent(letta_agent, self.ms)
1650
+ return response
1803
1651
 
1804
1652
  def retry_agent_message(self, agent_id: str) -> List[Message]:
1805
1653
 
1806
1654
  # Get the current message
1807
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1808
- return letta_agent.retry_message()
1655
+ letta_agent = self.load_agent(agent_id=agent_id)
1656
+ response = letta_agent.retry_message()
1657
+ save_agent(letta_agent, self.ms)
1658
+ return response
1809
1659
 
1810
1660
  def get_user_or_default(self, user_id: Optional[str]) -> User:
1811
1661
  """Get the user object for user_id if it exists, otherwise return the default user object"""
@@ -1854,121 +1704,49 @@ class SyncServer(Server):
1854
1704
  agent_id: str,
1855
1705
  ) -> ContextWindowOverview:
1856
1706
  # Get the current message
1857
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1707
+ letta_agent = self.load_agent(agent_id=agent_id)
1858
1708
  return letta_agent.get_context_window()
1859
1709
 
1860
- def update_agent_memory_label(self, user_id: str, agent_id: str, current_block_label: str, new_block_label: str) -> Memory:
1861
- """Update the label of a block in an agent's memory"""
1862
-
1863
- # Get the user
1864
- user = self.user_manager.get_user_by_id(user_id=user_id)
1865
-
1866
- # Link a block to an agent's memory
1867
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1868
- letta_agent.memory.update_block_label(current_label=current_block_label, new_label=new_block_label)
1869
- assert new_block_label in letta_agent.memory.list_block_labels()
1870
- self.block_manager.create_or_update_block(block=letta_agent.memory.get_block(new_block_label), actor=user)
1871
-
1872
- # check that the block was updated
1873
- updated_block = self.block_manager.get_block_by_id(block_id=letta_agent.memory.get_block(new_block_label).id, actor=user)
1874
-
1875
- # Recompile the agent memory
1876
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1877
-
1878
- # save agent
1879
- save_agent(letta_agent, self.ms)
1880
-
1881
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1882
- if updated_agent is None:
1883
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1884
- assert new_block_label in updated_agent.memory.list_block_labels()
1885
- assert current_block_label not in updated_agent.memory.list_block_labels()
1886
- return updated_agent.memory
1887
-
1888
1710
  def link_block_to_agent_memory(self, user_id: str, agent_id: str, block_id: str) -> Memory:
1889
1711
  """Link a block to an agent's memory"""
1890
-
1891
- # Get the user
1892
- user = self.user_manager.get_user_by_id(user_id=user_id)
1893
-
1894
- # Get the block first
1895
- block = self.block_manager.get_block_by_id(block_id=block_id, actor=user)
1712
+ block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
1896
1713
  if block is None:
1897
1714
  raise ValueError(f"Block with id {block_id} not found")
1715
+ self.blocks_agents_manager.add_block_to_agent(agent_id, block_id, block_label=block.label)
1898
1716
 
1899
- # Link a block to an agent's memory
1900
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1901
- letta_agent.memory.link_block(block=block)
1902
- assert block.label in letta_agent.memory.list_block_labels()
1903
-
1904
- # Recompile the agent memory
1905
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1906
-
1907
- # save agent
1908
- save_agent(letta_agent, self.ms)
1909
-
1910
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1911
- if updated_agent is None:
1912
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1913
- assert block.label in updated_agent.memory.list_block_labels()
1914
-
1915
- return updated_agent.memory
1717
+ # get agent memory
1718
+ memory = self.load_agent(agent_id=agent_id).agent_state.memory
1719
+ return memory
1916
1720
 
1917
1721
  def unlink_block_from_agent_memory(self, user_id: str, agent_id: str, block_label: str, delete_if_no_ref: bool = True) -> Memory:
1918
1722
  """Unlink a block from an agent's memory. If the block is not linked to any agent, delete it."""
1723
+ self.blocks_agents_manager.remove_block_with_label_from_agent(agent_id=agent_id, block_label=block_label)
1919
1724
 
1920
- # Get the user
1921
- user = self.user_manager.get_user_by_id(user_id=user_id)
1922
-
1923
- # Link a block to an agent's memory
1924
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1925
- unlinked_block = letta_agent.memory.unlink_block(block_label=block_label)
1926
- assert unlinked_block.label not in letta_agent.memory.list_block_labels()
1927
-
1928
- # Check if the block is linked to any other agent
1929
- # TODO needs reference counting GC to handle loose blocks
1930
- # block = self.block_manager.get_block_by_id(block_id=unlinked_block.id, actor=user)
1931
- # if block is None:
1932
- # raise ValueError(f"Block with id {block_id} not found")
1933
-
1934
- # Recompile the agent memory
1935
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1936
-
1937
- # save agent
1938
- save_agent(letta_agent, self.ms)
1939
-
1940
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1941
- if updated_agent is None:
1942
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1943
- assert unlinked_block.label not in updated_agent.memory.list_block_labels()
1944
- return updated_agent.memory
1725
+ # get agent memory
1726
+ memory = self.load_agent(agent_id=agent_id).agent_state.memory
1727
+ return memory
1945
1728
 
1946
1729
  def update_agent_memory_limit(self, user_id: str, agent_id: str, block_label: str, limit: int) -> Memory:
1947
1730
  """Update the limit of a block in an agent's memory"""
1731
+ block = self.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=block_label)
1732
+ self.block_manager.update_block(
1733
+ block_id=block.id, block_update=BlockUpdate(limit=limit), actor=self.user_manager.get_user_by_id(user_id=user_id)
1734
+ )
1735
+ # get agent memory
1736
+ memory = self.load_agent(agent_id=agent_id).agent_state.memory
1737
+ return memory
1738
+
1739
+ def upate_block(self, user_id: str, block_id: str, block_update: BlockUpdate) -> Block:
1740
+ """Update a block"""
1741
+ return self.block_manager.update_block(
1742
+ block_id=block_id, block_update=block_update, actor=self.user_manager.get_user_by_id(user_id=user_id)
1743
+ )
1948
1744
 
1949
- # Get the user
1950
- user = self.user_manager.get_user_by_id(user_id=user_id)
1951
-
1952
- # Link a block to an agent's memory
1953
- letta_agent = self._get_or_load_agent(agent_id=agent_id)
1954
- letta_agent.memory.update_block_limit(label=block_label, limit=limit)
1955
- assert block_label in letta_agent.memory.list_block_labels()
1956
-
1957
- # write out the update the database
1958
- self.block_manager.create_or_update_block(block=letta_agent.memory.get_block(block_label), actor=user)
1959
-
1960
- # check that the block was updated
1961
- updated_block = self.block_manager.get_block_by_id(block_id=letta_agent.memory.get_block(block_label).id, actor=user)
1962
- assert updated_block and updated_block.limit == limit
1963
-
1964
- # Recompile the agent memory
1965
- letta_agent.rebuild_memory(force=True, ms=self.ms)
1966
-
1967
- # save agent
1968
- save_agent(letta_agent, self.ms)
1969
-
1970
- updated_agent = self.ms.get_agent(agent_id=agent_id)
1971
- if updated_agent is None:
1972
- raise ValueError(f"Agent with id {agent_id} not found after linking block")
1973
- assert updated_agent.memory.get_block(label=block_label).limit == limit
1974
- return updated_agent.memory
1745
+ def get_agent_block_by_label(self, user_id: str, agent_id: str, label: str) -> Block:
1746
+ """Get a block by label"""
1747
+ # TODO: implement at ORM?
1748
+ for block_id in self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id):
1749
+ block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
1750
+ if block.label == label:
1751
+ return block
1752
+ return None