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.
- letta/__init__.py +1 -1
- letta/agent.py +14 -4
- letta/agent_store/db.py +22 -20
- letta/cli/cli.py +14 -1
- letta/client/client.py +27 -14
- letta/constants.py +3 -0
- letta/functions/functions.py +1 -1
- letta/helpers/tool_rule_solver.py +1 -2
- letta/log.py +1 -1
- letta/main.py +3 -0
- letta/metadata.py +2 -0
- letta/orm/agents_tags.py +28 -0
- letta/orm/base.py +5 -2
- letta/orm/mixins.py +2 -53
- letta/orm/organization.py +4 -1
- letta/orm/sqlalchemy_base.py +22 -45
- letta/orm/tool.py +3 -2
- letta/orm/user.py +3 -1
- letta/schemas/agent.py +5 -0
- letta/schemas/agents_tags.py +33 -0
- letta/schemas/block.py +3 -3
- letta/schemas/letta_response.py +110 -0
- letta/schemas/llm_config.py +7 -1
- letta/schemas/memory.py +4 -0
- letta/schemas/organization.py +4 -4
- letta/schemas/tool.py +13 -9
- letta/schemas/tool_rule.py +12 -2
- letta/schemas/user.py +1 -1
- letta/server/rest_api/app.py +4 -1
- letta/server/rest_api/routers/v1/agents.py +7 -122
- letta/server/rest_api/routers/v1/organizations.py +2 -1
- letta/server/rest_api/routers/v1/tools.py +3 -2
- letta/server/rest_api/routers/v1/users.py +14 -2
- letta/server/server.py +75 -44
- letta/services/agents_tags_manager.py +64 -0
- letta/services/organization_manager.py +4 -4
- letta/services/tool_manager.py +22 -30
- letta/services/user_manager.py +3 -3
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/METADATA +5 -2
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/RECORD +43 -40
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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 +
|
|
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
|
-
|
|
812
|
-
|
|
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
|
-
|
|
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
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
-
#
|
|
945
|
-
|
|
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
|
-
#
|
|
948
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1081
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1479
|
-
|
|
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
|
|
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 = "
|
|
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,
|
|
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(
|
|
43
|
+
org = OrganizationModel(**pydantic_org.model_dump())
|
|
44
44
|
org.create(session)
|
|
45
45
|
return org.to_pydantic()
|
|
46
46
|
|
letta/services/tool_manager.py
CHANGED
|
@@ -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
|
|
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,
|
|
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 =
|
|
40
|
-
|
|
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 =
|
|
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={
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
tool = self.create_tool(
|
|
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,
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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) ->
|
|
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
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
171
|
+
PydanticTool(
|
|
180
172
|
name=name,
|
|
181
173
|
tags=tags,
|
|
182
174
|
source_type="python",
|
letta/services/user_manager.py
CHANGED
|
@@ -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
|
|
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,
|
|
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(**
|
|
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.
|
|
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
|
[](https://discord.gg/letta)
|
|
104
104
|
[](https://twitter.com/Letta_AI)
|
|
105
105
|
[](https://arxiv.org/abs/2310.08560)
|
|
106
|
+
|
|
106
107
|
[](LICENSE)
|
|
107
108
|
[](https://github.com/cpacker/MemGPT/releases)
|
|
108
109
|
[](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]
|