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.
- letta/__init__.py +1 -1
- letta/agent.py +13 -7
- letta/agent_store/db.py +3 -1
- letta/cli/cli.py +14 -1
- letta/client/client.py +16 -8
- 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/organization.py +1 -0
- letta/orm/sqlalchemy_base.py +16 -0
- 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/tool.py +6 -2
- letta/schemas/tool_rule.py +12 -2
- letta/server/rest_api/app.py +4 -1
- letta/server/rest_api/routers/v1/agents.py +2 -122
- letta/server/rest_api/routers/v1/tools.py +1 -1
- letta/server/rest_api/routers/v1/users.py +13 -1
- letta/server/server.py +73 -42
- letta/services/agents_tags_manager.py +64 -0
- letta/services/tool_manager.py +7 -16
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/METADATA +4 -1
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/RECORD +33 -30
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/WHEEL +0 -0
- {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,
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
|
|
@@ -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]
|
letta/services/tool_manager.py
CHANGED
|
@@ -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) ->
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
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.
|
|
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
|
[](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]
|