letta-nightly 0.5.1.dev20241105104128__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 (43) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +14 -4
  3. letta/agent_store/db.py +22 -20
  4. letta/cli/cli.py +14 -1
  5. letta/client/client.py +27 -14
  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/base.py +5 -2
  14. letta/orm/mixins.py +2 -53
  15. letta/orm/organization.py +4 -1
  16. letta/orm/sqlalchemy_base.py +22 -45
  17. letta/orm/tool.py +3 -2
  18. letta/orm/user.py +3 -1
  19. letta/schemas/agent.py +5 -0
  20. letta/schemas/agents_tags.py +33 -0
  21. letta/schemas/block.py +3 -3
  22. letta/schemas/letta_response.py +110 -0
  23. letta/schemas/llm_config.py +7 -1
  24. letta/schemas/memory.py +4 -0
  25. letta/schemas/organization.py +4 -4
  26. letta/schemas/tool.py +13 -9
  27. letta/schemas/tool_rule.py +12 -2
  28. letta/schemas/user.py +1 -1
  29. letta/server/rest_api/app.py +4 -1
  30. letta/server/rest_api/routers/v1/agents.py +7 -122
  31. letta/server/rest_api/routers/v1/organizations.py +2 -1
  32. letta/server/rest_api/routers/v1/tools.py +3 -2
  33. letta/server/rest_api/routers/v1/users.py +14 -2
  34. letta/server/server.py +75 -44
  35. letta/services/agents_tags_manager.py +64 -0
  36. letta/services/organization_manager.py +4 -4
  37. letta/services/tool_manager.py +22 -30
  38. letta/services/user_manager.py +3 -3
  39. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/METADATA +5 -2
  40. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/RECORD +43 -40
  41. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/LICENSE +0 -0
  42. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/WHEEL +0 -0
  43. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/entry_points.txt +0 -0
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)
@@ -824,7 +835,7 @@ class SyncServer(Server):
824
835
  source_type = "python"
825
836
  tags = ["memory", "memgpt-base"]
826
837
  tool = self.tool_manager.create_or_update_tool(
827
- ToolCreate(
838
+ Tool(
828
839
  source_code=source_code,
829
840
  source_type=source_type,
830
841
  tags=tags,
@@ -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
 
@@ -1766,7 +1797,7 @@ class SyncServer(Server):
1766
1797
  tool_creates += ToolCreate.load_default_composio_tools()
1767
1798
  for tool_create in tool_creates:
1768
1799
  try:
1769
- self.tool_manager.create_or_update_tool(tool_create, actor=actor)
1800
+ self.tool_manager.create_or_update_tool(Tool(**tool_create.model_dump()), actor=actor)
1770
1801
  except Exception as e:
1771
1802
  warnings.warn(f"An error occurred while creating tool {tool_create}: {e}")
1772
1803
  warnings.warn(traceback.format_exc())
@@ -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]
@@ -3,13 +3,13 @@ from typing import List, Optional
3
3
  from letta.orm.errors import NoResultFound
4
4
  from letta.orm.organization import Organization as OrganizationModel
5
5
  from letta.schemas.organization import Organization as PydanticOrganization
6
- from letta.utils import create_random_username, enforce_types
6
+ from letta.utils import enforce_types
7
7
 
8
8
 
9
9
  class OrganizationManager:
10
10
  """Manager class to handle business logic related to Organizations."""
11
11
 
12
- DEFAULT_ORG_ID = "organization-00000000-0000-4000-8000-000000000000"
12
+ DEFAULT_ORG_ID = "org-00000000-0000-4000-8000-000000000000"
13
13
  DEFAULT_ORG_NAME = "default_org"
14
14
 
15
15
  def __init__(self):
@@ -37,10 +37,10 @@ class OrganizationManager:
37
37
  raise ValueError(f"Organization with id {org_id} not found.")
38
38
 
39
39
  @enforce_types
40
- def create_organization(self, name: Optional[str] = None) -> PydanticOrganization:
40
+ def create_organization(self, pydantic_org: PydanticOrganization) -> PydanticOrganization:
41
41
  """Create a new organization. If a name is provided, it is used, otherwise, a random one is generated."""
42
42
  with self.session_maker() as session:
43
- org = OrganizationModel(name=name if name else create_random_username())
43
+ org = OrganizationModel(**pydantic_org.model_dump())
44
44
  org.create(session)
45
45
  return org.to_pydantic()
46
46
 
@@ -7,10 +7,9 @@ from letta.functions.functions import derive_openai_json_schema, load_function_s
7
7
 
8
8
  # TODO: Remove this once we translate all of these to the ORM
9
9
  from letta.orm.errors import NoResultFound
10
- from letta.orm.organization import Organization as OrganizationModel
11
10
  from letta.orm.tool import Tool as ToolModel
12
11
  from letta.schemas.tool import Tool as PydanticTool
13
- from letta.schemas.tool import ToolCreate, ToolUpdate
12
+ from letta.schemas.tool import ToolUpdate
14
13
  from letta.schemas.user import User as PydanticUser
15
14
  from letta.utils import enforce_types, printd
16
15
 
@@ -33,20 +32,18 @@ class ToolManager:
33
32
  self.session_maker = db_context
34
33
 
35
34
  @enforce_types
36
- def create_or_update_tool(self, tool_create: ToolCreate, actor: PydanticUser) -> PydanticTool:
35
+ def create_or_update_tool(self, pydantic_tool: PydanticTool, actor: PydanticUser) -> PydanticTool:
37
36
  """Create a new tool based on the ToolCreate schema."""
38
37
  # Derive json_schema
39
- derived_json_schema = tool_create.json_schema or derive_openai_json_schema(
40
- source_code=tool_create.source_code, name=tool_create.name
41
- )
42
- derived_name = tool_create.name or derived_json_schema["name"]
38
+ derived_json_schema = pydantic_tool.json_schema or derive_openai_json_schema(source_code=pydantic_tool.source_code)
39
+ derived_name = pydantic_tool.name or derived_json_schema["name"]
43
40
 
44
41
  try:
45
42
  # NOTE: We use the organization id here
46
43
  # This is important, because even if it's a different user, adding the same tool to the org should not happen
47
44
  tool = self.get_tool_by_name(tool_name=derived_name, actor=actor)
48
45
  # Put to dict and remove fields that should not be reset
49
- update_data = tool_create.model_dump(exclude={"module"}, exclude_unset=True)
46
+ update_data = pydantic_tool.model_dump(exclude={"module"}, exclude_unset=True, exclude_none=True)
50
47
  # Remove redundant update fields
51
48
  update_data = {key: value for key, value in update_data.items() if getattr(tool, key) != value}
52
49
 
@@ -55,22 +52,24 @@ class ToolManager:
55
52
  self.update_tool_by_id(tool.id, ToolUpdate(**update_data), actor)
56
53
  else:
57
54
  printd(
58
- f"`create_or_update_tool` was called with user_id={actor.id}, organization_id={actor.organization_id}, name={tool_create.name}, but found existing tool with nothing to update."
55
+ f"`create_or_update_tool` was called with user_id={actor.id}, organization_id={actor.organization_id}, name={pydantic_tool.name}, but found existing tool with nothing to update."
59
56
  )
60
57
  except NoResultFound:
61
- tool_create.json_schema = derived_json_schema
62
- tool_create.name = derived_name
63
- tool = self.create_tool(tool_create, actor=actor)
58
+ pydantic_tool.json_schema = derived_json_schema
59
+ pydantic_tool.name = derived_name
60
+ tool = self.create_tool(pydantic_tool, actor=actor)
64
61
 
65
62
  return tool
66
63
 
67
64
  @enforce_types
68
- def create_tool(self, tool_create: ToolCreate, actor: PydanticUser) -> PydanticTool:
65
+ def create_tool(self, pydantic_tool: PydanticTool, actor: PydanticUser) -> PydanticTool:
69
66
  """Create a new tool based on the ToolCreate schema."""
70
67
  # Create the tool
71
68
  with self.session_maker() as session:
72
- create_data = tool_create.model_dump()
73
- tool = ToolModel(**create_data, organization_id=actor.organization_id) # Unpack everything directly into ToolModel
69
+ # Set the organization id at the ORM layer
70
+ pydantic_tool.organization_id = actor.organization_id
71
+ tool_data = pydantic_tool.model_dump()
72
+ tool = ToolModel(**tool_data)
74
73
  tool.create(session, actor=actor)
75
74
 
76
75
  return tool.to_pydantic()
@@ -99,12 +98,12 @@ class ToolManager:
99
98
  db_session=session,
100
99
  cursor=cursor,
101
100
  limit=limit,
102
- _organization_id=OrganizationModel.get_uid_from_identifier(actor.organization_id),
101
+ organization_id=actor.organization_id,
103
102
  )
104
103
  return [tool.to_pydantic() for tool in tools]
105
104
 
106
105
  @enforce_types
107
- 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:
108
107
  """Update a tool by its ID with the given ToolUpdate object."""
109
108
  with self.session_maker() as session:
110
109
  # Fetch the tool by ID
@@ -115,31 +114,24 @@ class ToolManager:
115
114
  for key, value in update_data.items():
116
115
  setattr(tool, key, value)
117
116
 
118
- # If source code is changed and a new json_schema is not provided, we want to auto-refresh the name and schema
119
- # 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
120
118
  if "source_code" in update_data.keys() and "json_schema" not in update_data.keys():
121
119
  pydantic_tool = tool.to_pydantic()
122
120
 
123
- # Decide whether or not to reset name
124
- # If name was not explicitly passed in as part of the update, then we auto-generate a new name based on source code
125
- name = None
126
- if "name" in update_data.keys():
127
- name = update_data["name"]
128
- 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)
129
123
 
130
- # The name will either be set (if explicit) or autogenerated from the source code
131
- tool.name = new_schema["name"]
132
124
  tool.json_schema = new_schema
133
125
 
134
126
  # Save the updated tool to the database
135
- tool.update(db_session=session, actor=actor)
127
+ return tool.update(db_session=session, actor=actor)
136
128
 
137
129
  @enforce_types
138
130
  def delete_tool_by_id(self, tool_id: str, actor: PydanticUser) -> None:
139
131
  """Delete a tool by its ID."""
140
132
  with self.session_maker() as session:
141
133
  try:
142
- tool = ToolModel.read(db_session=session, identifier=tool_id)
134
+ tool = ToolModel.read(db_session=session, identifier=tool_id, actor=actor)
143
135
  tool.delete(db_session=session, actor=actor)
144
136
  except NoResultFound:
145
137
  raise ValueError(f"Tool with id {tool_id} not found.")
@@ -176,7 +168,7 @@ class ToolManager:
176
168
  # create to tool
177
169
  tools.append(
178
170
  self.create_or_update_tool(
179
- ToolCreate(
171
+ PydanticTool(
180
172
  name=name,
181
173
  tags=tags,
182
174
  source_type="python",
@@ -4,7 +4,7 @@ from letta.orm.errors import NoResultFound
4
4
  from letta.orm.organization import Organization as OrganizationModel
5
5
  from letta.orm.user import User as UserModel
6
6
  from letta.schemas.user import User as PydanticUser
7
- from letta.schemas.user import UserCreate, UserUpdate
7
+ from letta.schemas.user import UserUpdate
8
8
  from letta.services.organization_manager import OrganizationManager
9
9
  from letta.utils import enforce_types
10
10
 
@@ -42,10 +42,10 @@ class UserManager:
42
42
  return user.to_pydantic()
43
43
 
44
44
  @enforce_types
45
- def create_user(self, user_create: UserCreate) -> PydanticUser:
45
+ def create_user(self, pydantic_user: PydanticUser) -> PydanticUser:
46
46
  """Create a new user if it doesn't already exist."""
47
47
  with self.session_maker() as session:
48
- new_user = UserModel(**user_create.model_dump())
48
+ new_user = UserModel(**pydantic_user.model_dump())
49
49
  new_user.create(session)
50
50
  return new_user.to_pydantic()
51
51
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.5.1.dev20241105104128
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
@@ -99,14 +99,17 @@ Description-Content-Type: text/markdown
99
99
  </h3>
100
100
 
101
101
  **👾 Letta** is an open source framework for building stateful LLM applications. You can use Letta to build **stateful agents** with advanced reasoning capabilities and transparent long-term memory. The Letta framework is white box and model-agnostic.
102
-
102
+
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]