letta-nightly 0.5.1.dev20241106104104__py3-none-any.whl → 0.5.2.dev20241107104040__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 (33) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +13 -7
  3. letta/agent_store/db.py +3 -1
  4. letta/cli/cli.py +14 -1
  5. letta/client/client.py +16 -8
  6. letta/constants.py +3 -0
  7. letta/functions/functions.py +1 -1
  8. letta/helpers/tool_rule_solver.py +1 -2
  9. letta/log.py +1 -1
  10. letta/main.py +3 -0
  11. letta/metadata.py +2 -0
  12. letta/orm/agents_tags.py +28 -0
  13. letta/orm/organization.py +1 -0
  14. letta/orm/sqlalchemy_base.py +16 -0
  15. letta/schemas/agent.py +5 -0
  16. letta/schemas/agents_tags.py +33 -0
  17. letta/schemas/block.py +3 -3
  18. letta/schemas/letta_response.py +110 -0
  19. letta/schemas/llm_config.py +7 -1
  20. letta/schemas/tool.py +6 -2
  21. letta/schemas/tool_rule.py +12 -2
  22. letta/server/rest_api/app.py +4 -1
  23. letta/server/rest_api/routers/v1/agents.py +2 -122
  24. letta/server/rest_api/routers/v1/tools.py +1 -1
  25. letta/server/rest_api/routers/v1/users.py +13 -1
  26. letta/server/server.py +73 -42
  27. letta/services/agents_tags_manager.py +64 -0
  28. letta/services/tool_manager.py +7 -16
  29. {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/METADATA +4 -1
  30. {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/RECORD +33 -30
  31. {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/LICENSE +0 -0
  32. {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/WHEEL +0 -0
  33. {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/entry_points.txt +0 -0
@@ -41,6 +41,7 @@ router = APIRouter(prefix="/agents", tags=["agents"])
41
41
  @router.get("/", response_model=List[AgentState], operation_id="list_agents")
42
42
  def list_agents(
43
43
  name: Optional[str] = Query(None, description="Name of the agent"),
44
+ tags: Optional[List[str]] = Query(None, description="List of tags to filter agents by"),
44
45
  server: "SyncServer" = Depends(get_letta_server),
45
46
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
46
47
  ):
@@ -50,7 +51,7 @@ def list_agents(
50
51
  """
51
52
  actor = server.get_user_or_default(user_id=user_id)
52
53
 
53
- agents = server.list_agents(user_id=actor.id)
54
+ agents = server.list_agents(user_id=actor.id, tags=tags)
54
55
  # TODO: move this logic to the ORM
55
56
  if name:
56
57
  agents = [a for a in agents if a.name == name]
@@ -534,124 +535,3 @@ async def send_message_to_agent(
534
535
 
535
536
  traceback.print_exc()
536
537
  raise HTTPException(status_code=500, detail=f"{e}")
537
-
538
-
539
- ##### MISSING #######
540
-
541
- # @router.post("/{agent_id}/command")
542
- # def run_command(
543
- # agent_id: "UUID",
544
- # command: "AgentCommandRequest",
545
- #
546
- # server: "SyncServer" = Depends(get_letta_server),
547
- # ):
548
- # """
549
- # Execute a command on a specified agent.
550
-
551
- # This endpoint receives a command to be executed on an agent. It uses the user and agent identifiers to authenticate and route the command appropriately.
552
-
553
- # Raises an HTTPException for any processing errors.
554
- # """
555
- # actor = server.get_current_user()
556
- #
557
- # response = server.run_command(user_id=actor.id,
558
- # agent_id=agent_id,
559
- # command=command.command)
560
-
561
- # return AgentCommandResponse(response=response)
562
-
563
- # @router.get("/{agent_id}/config")
564
- # def get_agent_config(
565
- # agent_id: "UUID",
566
- #
567
- # server: "SyncServer" = Depends(get_letta_server),
568
- # ):
569
- # """
570
- # Retrieve the configuration for a specific agent.
571
-
572
- # This endpoint fetches the configuration details for a given agent, identified by the user and agent IDs.
573
- # """
574
- # actor = server.get_current_user()
575
- #
576
- # if not server.ms.get_agent(user_id=actor.id, agent_id=agent_id):
577
- ## agent does not exist
578
- # raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found.")
579
-
580
- # agent_state = server.get_agent_config(user_id=actor.id, agent_id=agent_id)
581
- ## get sources
582
- # attached_sources = server.list_attached_sources(agent_id=agent_id)
583
-
584
- ## configs
585
- # llm_config = LLMConfig(**vars(agent_state.llm_config))
586
- # embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
587
-
588
- # return GetAgentResponse(
589
- # agent_state=AgentState(
590
- # id=agent_state.id,
591
- # name=agent_state.name,
592
- # user_id=agent_state.user_id,
593
- # llm_config=llm_config,
594
- # embedding_config=embedding_config,
595
- # state=agent_state.state,
596
- # created_at=int(agent_state.created_at.timestamp()),
597
- # tools=agent_state.tools,
598
- # system=agent_state.system,
599
- # metadata=agent_state._metadata,
600
- # ),
601
- # last_run_at=None, # TODO
602
- # sources=attached_sources,
603
- # )
604
-
605
- # @router.patch("/{agent_id}/rename", response_model=GetAgentResponse)
606
- # def update_agent_name(
607
- # agent_id: "UUID",
608
- # agent_rename: AgentRenameRequest,
609
- #
610
- # server: "SyncServer" = Depends(get_letta_server),
611
- # ):
612
- # """
613
- # Updates the name of a specific agent.
614
-
615
- # This changes the name of the agent in the database but does NOT edit the agent's persona.
616
- # """
617
- # valid_name = agent_rename.agent_name
618
- # actor = server.get_current_user()
619
- #
620
- # agent_state = server.rename_agent(user_id=actor.id, agent_id=agent_id, new_agent_name=valid_name)
621
- ## get sources
622
- # attached_sources = server.list_attached_sources(agent_id=agent_id)
623
- # llm_config = LLMConfig(**vars(agent_state.llm_config))
624
- # embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
625
-
626
- # return GetAgentResponse(
627
- # agent_state=AgentState(
628
- # id=agent_state.id,
629
- # name=agent_state.name,
630
- # user_id=agent_state.user_id,
631
- # llm_config=llm_config,
632
- # embedding_config=embedding_config,
633
- # state=agent_state.state,
634
- # created_at=int(agent_state.created_at.timestamp()),
635
- # tools=agent_state.tools,
636
- # system=agent_state.system,
637
- # ),
638
- # last_run_at=None, # TODO
639
- # sources=attached_sources,
640
- # )
641
-
642
-
643
- # @router.get("/{agent_id}/archival/all", response_model=GetAgentArchivalMemoryResponse)
644
- # def get_agent_archival_memory_all(
645
- # agent_id: "UUID",
646
- #
647
- # server: "SyncServer" = Depends(get_letta_server),
648
- # ):
649
- # """
650
- # Retrieve the memories in an agent's archival memory store (non-paginated, returns all entries at once).
651
- # """
652
- # actor = server.get_current_user()
653
- #
654
- # archival_memories = server.get_all_archival_memories(user_id=actor.id, agent_id=agent_id)
655
- # print("archival_memories:", archival_memories)
656
- # archival_memory_objects = [ArchivalMemoryObject(id=passage["id"], contents=passage["contents"]) for passage in archival_memories]
657
- # return GetAgentArchivalMemoryResponse(archival_memory=archival_memory_objects)
@@ -104,7 +104,7 @@ def update_tool(
104
104
  Update an existing tool
105
105
  """
106
106
  actor = server.get_user_or_default(user_id=user_id)
107
- return server.tool_manager.update_tool_by_id(tool_id, actor.id, request)
107
+ return server.tool_manager.update_tool_by_id(tool_id=tool_id, tool_update=request, actor=actor)
108
108
 
109
109
 
110
110
  @router.post("/add-base-tools", response_model=List[Tool], operation_id="add_base_tools")
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, List, Optional
3
3
  from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
4
 
5
5
  from letta.schemas.api_key import APIKey, APIKeyCreate
6
- from letta.schemas.user import User, UserCreate
6
+ from letta.schemas.user import User, UserCreate, UserUpdate
7
7
  from letta.server.rest_api.utils import get_letta_server
8
8
 
9
9
  # from letta.server.schemas.users import (
@@ -56,6 +56,18 @@ def create_user(
56
56
  return user
57
57
 
58
58
 
59
+ @router.put("/", tags=["admin"], response_model=User, operation_id="update_user")
60
+ def update_user(
61
+ user: UserUpdate = Body(...),
62
+ server: "SyncServer" = Depends(get_letta_server),
63
+ ):
64
+ """
65
+ Update a user in the database
66
+ """
67
+ user = server.user_manager.update_user(user)
68
+ return user
69
+
70
+
59
71
  @router.delete("/", tags=["admin"], response_model=User, operation_id="delete_user")
60
72
  def delete_user(
61
73
  user_id: str = Query(..., description="The user_id key to be deleted."),
letta/server/server.py CHANGED
@@ -82,6 +82,7 @@ from letta.schemas.source import Source, SourceCreate, SourceUpdate
82
82
  from letta.schemas.tool import Tool, ToolCreate
83
83
  from letta.schemas.usage import LettaUsageStatistics
84
84
  from letta.schemas.user import User
85
+ from letta.services.agents_tags_manager import AgentsTagsManager
85
86
  from letta.services.organization_manager import OrganizationManager
86
87
  from letta.services.tool_manager import ToolManager
87
88
  from letta.services.user_manager import UserManager
@@ -248,6 +249,7 @@ class SyncServer(Server):
248
249
  self.organization_manager = OrganizationManager()
249
250
  self.user_manager = UserManager()
250
251
  self.tool_manager = ToolManager()
252
+ self.agents_tags_manager = AgentsTagsManager()
251
253
 
252
254
  # Make default user and org
253
255
  if init_with_default_org_and_user:
@@ -370,11 +372,15 @@ class SyncServer(Server):
370
372
  logger.debug(f"Creating an agent object")
371
373
  tool_objs = []
372
374
  for name in agent_state.tools:
373
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
374
- if not tool_obj:
375
- logger.exception(f"Tool {name} does not exist for user {user_id}")
376
- raise ValueError(f"Tool {name} does not exist for user {user_id}")
377
- tool_objs.append(tool_obj)
375
+ # TODO: This should be a hard failure, but for migration reasons, we patch it for now
376
+ try:
377
+ tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
378
+ tool_objs.append(tool_obj)
379
+ except NoResultFound:
380
+ warnings.warn(f"Tried to retrieve a tool with name {name} from the agent_state, but does not exist in tool db.")
381
+
382
+ # set agent_state tools to only the names of the available tools
383
+ agent_state.tools = [t.name for t in tool_objs]
378
384
 
379
385
  # Make sure the memory is a memory object
380
386
  assert isinstance(agent_state.memory, Memory)
@@ -804,12 +810,17 @@ class SyncServer(Server):
804
810
  llm_config = request.llm_config
805
811
  embedding_config = request.embedding_config
806
812
 
807
- # get tools + make sure they exist
813
+ # get tools + only add if they exist
808
814
  tool_objs = []
809
815
  if request.tools:
810
816
  for tool_name in request.tools:
811
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
812
- tool_objs.append(tool_obj)
817
+ try:
818
+ tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
819
+ tool_objs.append(tool_obj)
820
+ except NoResultFound:
821
+ warnings.warn(f"Attempted to add a nonexistent tool {tool_name} to agent {request.name}, skipping.")
822
+ # reset the request.tools to only valid tools
823
+ request.tools = [t.name for t in tool_objs]
813
824
 
814
825
  assert request.memory is not None
815
826
  memory_functions = get_memory_functions(request.memory)
@@ -894,6 +905,7 @@ class SyncServer(Server):
894
905
 
895
906
  assert isinstance(agent.agent_state.memory, Memory), f"Invalid memory type: {type(agent_state.memory)}"
896
907
  # return AgentState
908
+
897
909
  return agent.agent_state
898
910
 
899
911
  def update_agent(
@@ -935,17 +947,26 @@ class SyncServer(Server):
935
947
  # Replace tools and also re-link
936
948
 
937
949
  # (1) get tools + make sure they exist
938
- tool_objs = []
939
- for tool_name in request.tools:
940
- tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
941
- assert tool_obj, f"Tool {tool_name} does not exist"
942
- tool_objs.append(tool_obj)
950
+ # Current and target tools as sets of tool names
951
+ current_tools = set(letta_agent.agent_state.tools)
952
+ target_tools = set(request.tools)
943
953
 
944
- # (2) replace the list of tool names ("ids") inside the agent state
945
- letta_agent.agent_state.tools = request.tools
954
+ # Calculate tools to add and remove
955
+ tools_to_add = target_tools - current_tools
956
+ tools_to_remove = current_tools - target_tools
946
957
 
947
- # (3) then attempt to link the tools modules
948
- letta_agent.link_tools(tool_objs)
958
+ # Fetch tool objects for those to add and remove
959
+ tools_to_add = [self.tool_manager.get_tool_by_name(tool_name=tool, actor=actor) for tool in tools_to_add]
960
+ tools_to_remove = [self.tool_manager.get_tool_by_name(tool_name=tool, actor=actor) for tool in tools_to_remove]
961
+
962
+ # update agent tool list
963
+ for tool in tools_to_remove:
964
+ self.remove_tool_from_agent(agent_id=request.id, tool_id=tool.id, user_id=actor.id)
965
+ for tool in tools_to_add:
966
+ self.add_tool_to_agent(agent_id=request.id, tool_id=tool.id, user_id=actor.id)
967
+
968
+ # reload agent
969
+ letta_agent = self._get_or_load_agent(agent_id=request.id)
949
970
 
950
971
  # configs
951
972
  if request.llm_config:
@@ -959,6 +980,19 @@ class SyncServer(Server):
959
980
  if request.metadata_:
960
981
  letta_agent.agent_state.metadata_ = request.metadata_
961
982
 
983
+ # Manage tag state
984
+ if request.tags is not None:
985
+ current_tags = set(self.agents_tags_manager.get_tags_for_agent(agent_id=letta_agent.agent_state.id, actor=actor))
986
+ target_tags = set(request.tags)
987
+
988
+ tags_to_add = target_tags - current_tags
989
+ tags_to_remove = current_tags - target_tags
990
+
991
+ for tag in tags_to_add:
992
+ self.agents_tags_manager.add_tag_to_agent(agent_id=letta_agent.agent_state.id, tag=tag, actor=actor)
993
+ for tag in tags_to_remove:
994
+ self.agents_tags_manager.delete_tag_from_agent(agent_id=letta_agent.agent_state.id, tag=tag, actor=actor)
995
+
962
996
  # save the agent
963
997
  assert isinstance(letta_agent.memory, Memory)
964
998
  save_agent(letta_agent, self.ms)
@@ -1069,16 +1103,19 @@ class SyncServer(Server):
1069
1103
  }
1070
1104
  return agent_config
1071
1105
 
1072
- def list_agents(
1073
- self,
1074
- user_id: str,
1075
- ) -> List[AgentState]:
1106
+ def list_agents(self, user_id: str, tags: Optional[List[str]] = None) -> List[AgentState]:
1076
1107
  """List all available agents to a user"""
1077
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1078
- raise ValueError(f"User user_id={user_id} does not exist")
1108
+ user = self.user_manager.get_user_by_id(user_id=user_id)
1079
1109
 
1080
- agents_states = self.ms.list_agents(user_id=user_id)
1081
- return agents_states
1110
+ if tags is None:
1111
+ agents_states = self.ms.list_agents(user_id=user_id)
1112
+ return agents_states
1113
+ else:
1114
+ agent_ids = []
1115
+ for tag in tags:
1116
+ agent_ids += self.agents_tags_manager.get_agents_by_tag(tag=tag, actor=user)
1117
+
1118
+ return [self.get_agent_state(user_id=user.id, agent_id=agent_id) for agent_id in agent_ids]
1082
1119
 
1083
1120
  def get_blocks(
1084
1121
  self,
@@ -1150,18 +1187,6 @@ class SyncServer(Server):
1150
1187
  raise ValueError("Source does not exist")
1151
1188
  return existing_source.id
1152
1189
 
1153
- def get_agent(self, user_id: str, agent_id: Optional[str] = None, agent_name: Optional[str] = None):
1154
- """Get the agent state"""
1155
- return self.ms.get_agent(agent_id=agent_id, agent_name=agent_name, user_id=user_id)
1156
-
1157
- # def get_user(self, user_id: str) -> User:
1158
- # """Get the user"""
1159
- # user = self.user_manager.get_user_by_id(user_id=user_id)
1160
- # if user is None:
1161
- # raise ValueError(f"User with user_id {user_id} does not exist")
1162
- # else:
1163
- # return user
1164
-
1165
1190
  def get_agent_memory(self, agent_id: str) -> Memory:
1166
1191
  """Return the memory of an agent (core memory)"""
1167
1192
  agent = self._get_or_load_agent(agent_id=agent_id)
@@ -1379,8 +1404,7 @@ class SyncServer(Server):
1379
1404
 
1380
1405
  def get_agent_state(self, user_id: str, agent_id: Optional[str], agent_name: Optional[str] = None) -> Optional[AgentState]:
1381
1406
  """Return the config of an agent"""
1382
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1383
- raise ValueError(f"User user_id={user_id} does not exist")
1407
+ user = self.user_manager.get_user_by_id(user_id=user_id)
1384
1408
  if agent_id:
1385
1409
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1386
1410
  return None
@@ -1393,7 +1417,11 @@ class SyncServer(Server):
1393
1417
  # Get the agent object (loaded in memory)
1394
1418
  letta_agent = self._get_or_load_agent(agent_id=agent_id)
1395
1419
  assert isinstance(letta_agent.memory, Memory)
1396
- return letta_agent.agent_state.model_copy(deep=True)
1420
+ agent_state = letta_agent.agent_state.model_copy(deep=True)
1421
+
1422
+ # Load the tags in for the agent_state
1423
+ agent_state.tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
1424
+ return agent_state
1397
1425
 
1398
1426
  def get_server_config(self, include_defaults: bool = False) -> dict:
1399
1427
  """Return the base config"""
@@ -1475,8 +1503,11 @@ class SyncServer(Server):
1475
1503
 
1476
1504
  def delete_agent(self, user_id: str, agent_id: str):
1477
1505
  """Delete an agent in the database"""
1478
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1479
- raise ValueError(f"User user_id={user_id} does not exist")
1506
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1507
+ # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM MODEL
1508
+ # TODO: EVENTUALLY WE GET AUTO-DELETES WHEN WE SPECIFY RELATIONSHIPS IN THE ORM
1509
+ self.agents_tags_manager.delete_all_tags_from_agent(agent_id=agent_id, actor=actor)
1510
+
1480
1511
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1481
1512
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
1482
1513
 
@@ -0,0 +1,64 @@
1
+ from typing import List
2
+
3
+ from letta.orm.agents_tags import AgentsTags as AgentsTagsModel
4
+ from letta.orm.errors import NoResultFound
5
+ from letta.schemas.agents_tags import AgentsTags as PydanticAgentsTags
6
+ from letta.schemas.user import User as PydanticUser
7
+ from letta.utils import enforce_types
8
+
9
+
10
+ class AgentsTagsManager:
11
+ """Manager class to handle business logic related to Tags."""
12
+
13
+ def __init__(self):
14
+ from letta.server.server import db_context
15
+
16
+ self.session_maker = db_context
17
+
18
+ @enforce_types
19
+ def add_tag_to_agent(self, agent_id: str, tag: str, actor: PydanticUser) -> PydanticAgentsTags:
20
+ """Add a tag to an agent."""
21
+ with self.session_maker() as session:
22
+ # Check if the tag already exists for this agent
23
+ try:
24
+ agents_tags_model = AgentsTagsModel.read(db_session=session, agent_id=agent_id, tag=tag, actor=actor)
25
+ return agents_tags_model.to_pydantic()
26
+ except NoResultFound:
27
+ agents_tags = PydanticAgentsTags(agent_id=agent_id, tag=tag).model_dump(exclude_none=True)
28
+ new_tag = AgentsTagsModel(**agents_tags, organization_id=actor.organization_id)
29
+ new_tag.create(session, actor=actor)
30
+ return new_tag.to_pydantic()
31
+
32
+ @enforce_types
33
+ def delete_all_tags_from_agent(self, agent_id: str, actor: PydanticUser):
34
+ """Delete a tag from an agent. This is a permanent hard delete."""
35
+ tags = self.get_tags_for_agent(agent_id=agent_id, actor=actor)
36
+ for tag in tags:
37
+ self.delete_tag_from_agent(agent_id=agent_id, tag=tag, actor=actor)
38
+
39
+ @enforce_types
40
+ def delete_tag_from_agent(self, agent_id: str, tag: str, actor: PydanticUser):
41
+ """Delete a tag from an agent."""
42
+ with self.session_maker() as session:
43
+ try:
44
+ # Retrieve and delete the tag association
45
+ tag_association = AgentsTagsModel.read(db_session=session, agent_id=agent_id, tag=tag, actor=actor)
46
+ tag_association.hard_delete(session, actor=actor)
47
+ except NoResultFound:
48
+ raise ValueError(f"Tag '{tag}' not found for agent '{agent_id}'.")
49
+
50
+ @enforce_types
51
+ def get_agents_by_tag(self, tag: str, actor: PydanticUser) -> List[str]:
52
+ """Retrieve all agent IDs associated with a specific tag."""
53
+ with self.session_maker() as session:
54
+ # Query for all agents with the given tag
55
+ agents_with_tag = AgentsTagsModel.list(db_session=session, tag=tag, organization_id=actor.organization_id)
56
+ return [record.agent_id for record in agents_with_tag]
57
+
58
+ @enforce_types
59
+ def get_tags_for_agent(self, agent_id: str, actor: PydanticUser) -> List[str]:
60
+ """Retrieve all tags associated with a specific agent."""
61
+ with self.session_maker() as session:
62
+ # Query for all tags associated with the given agent
63
+ tags_for_agent = AgentsTagsModel.list(db_session=session, agent_id=agent_id, organization_id=actor.organization_id)
64
+ return [record.tag for record in tags_for_agent]
@@ -35,9 +35,7 @@ class ToolManager:
35
35
  def create_or_update_tool(self, pydantic_tool: PydanticTool, actor: PydanticUser) -> PydanticTool:
36
36
  """Create a new tool based on the ToolCreate schema."""
37
37
  # Derive json_schema
38
- derived_json_schema = pydantic_tool.json_schema or derive_openai_json_schema(
39
- source_code=pydantic_tool.source_code, name=pydantic_tool.name
40
- )
38
+ derived_json_schema = pydantic_tool.json_schema or derive_openai_json_schema(source_code=pydantic_tool.source_code)
41
39
  derived_name = pydantic_tool.name or derived_json_schema["name"]
42
40
 
43
41
  try:
@@ -105,7 +103,7 @@ class ToolManager:
105
103
  return [tool.to_pydantic() for tool in tools]
106
104
 
107
105
  @enforce_types
108
- def update_tool_by_id(self, tool_id: str, tool_update: ToolUpdate, actor: PydanticUser) -> None:
106
+ def update_tool_by_id(self, tool_id: str, tool_update: ToolUpdate, actor: PydanticUser) -> PydanticTool:
109
107
  """Update a tool by its ID with the given ToolUpdate object."""
110
108
  with self.session_maker() as session:
111
109
  # Fetch the tool by ID
@@ -116,31 +114,24 @@ class ToolManager:
116
114
  for key, value in update_data.items():
117
115
  setattr(tool, key, value)
118
116
 
119
- # If source code is changed and a new json_schema is not provided, we want to auto-refresh the name and schema
120
- # CAUTION: This will override any name/schema values the user passed in
117
+ # If source code is changed and a new json_schema is not provided, we want to auto-refresh the schema
121
118
  if "source_code" in update_data.keys() and "json_schema" not in update_data.keys():
122
119
  pydantic_tool = tool.to_pydantic()
123
120
 
124
- # Decide whether or not to reset name
125
- # If name was not explicitly passed in as part of the update, then we auto-generate a new name based on source code
126
- name = None
127
- if "name" in update_data.keys():
128
- name = update_data["name"]
129
- new_schema = derive_openai_json_schema(source_code=pydantic_tool.source_code, name=name)
121
+ update_data["name"] if "name" in update_data.keys() else None
122
+ new_schema = derive_openai_json_schema(source_code=pydantic_tool.source_code)
130
123
 
131
- # The name will either be set (if explicit) or autogenerated from the source code
132
- tool.name = new_schema["name"]
133
124
  tool.json_schema = new_schema
134
125
 
135
126
  # Save the updated tool to the database
136
- tool.update(db_session=session, actor=actor)
127
+ return tool.update(db_session=session, actor=actor)
137
128
 
138
129
  @enforce_types
139
130
  def delete_tool_by_id(self, tool_id: str, actor: PydanticUser) -> None:
140
131
  """Delete a tool by its ID."""
141
132
  with self.session_maker() as session:
142
133
  try:
143
- tool = ToolModel.read(db_session=session, identifier=tool_id)
134
+ tool = ToolModel.read(db_session=session, identifier=tool_id, actor=actor)
144
135
  tool.delete(db_session=session, actor=actor)
145
136
  except NoResultFound:
146
137
  raise ValueError(f"Tool with id {tool_id} not found.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.5.1.dev20241106104104
3
+ Version: 0.5.2.dev20241107104040
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  License: Apache License
6
6
  Author: Letta Team
@@ -103,10 +103,13 @@ Description-Content-Type: text/markdown
103
103
  [![Discord](https://img.shields.io/discord/1161736243340640419?label=Discord&logo=discord&logoColor=5865F2&style=flat-square&color=5865F2)](https://discord.gg/letta)
104
104
  [![Twitter Follow](https://img.shields.io/badge/Follow-%40Letta__AI-1DA1F2?style=flat-square&logo=x&logoColor=white)](https://twitter.com/Letta_AI)
105
105
  [![arxiv 2310.08560](https://img.shields.io/badge/Research-2310.08560-B31B1B?logo=arxiv&style=flat-square)](https://arxiv.org/abs/2310.08560)
106
+
106
107
  [![Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-silver?style=flat-square)](LICENSE)
107
108
  [![Release](https://img.shields.io/github/v/release/cpacker/MemGPT?style=flat-square&label=Release&color=limegreen)](https://github.com/cpacker/MemGPT/releases)
108
109
  [![GitHub](https://img.shields.io/github/stars/cpacker/MemGPT?style=flat-square&logo=github&label=Stars&color=gold)](https://github.com/cpacker/MemGPT)
109
110
 
111
+ <a href="https://trendshift.io/repositories/3612" target="_blank"><img src="https://trendshift.io/api/badge/repositories/3612" alt="cpacker%2FMemGPT | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
112
+
110
113
  </div>
111
114
 
112
115
  > [!NOTE]