letta-nightly 0.8.13.dev20250714104447__py3-none-any.whl → 0.8.14.dev20250714180504__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/constants.py +6 -0
- letta/functions/function_sets/base.py +2 -2
- letta/helpers/pinecone_utils.py +164 -11
- letta/orm/file.py +2 -17
- letta/orm/files_agents.py +9 -10
- letta/orm/organization.py +0 -4
- letta/orm/passage.py +0 -10
- letta/orm/source.py +3 -20
- letta/schemas/file.py +1 -0
- letta/schemas/memory.py +2 -2
- letta/server/rest_api/routers/v1/agents.py +4 -4
- letta/server/rest_api/routers/v1/messages.py +2 -6
- letta/server/rest_api/routers/v1/sources.py +3 -3
- letta/server/server.py +0 -3
- letta/services/agent_manager.py +194 -147
- letta/services/block_manager.py +18 -18
- letta/services/context_window_calculator/context_window_calculator.py +15 -10
- letta/services/context_window_calculator/token_counter.py +40 -0
- letta/services/file_processor/chunker/line_chunker.py +17 -0
- letta/services/file_processor/embedder/openai_embedder.py +50 -5
- letta/services/files_agents_manager.py +12 -2
- letta/services/group_manager.py +11 -11
- letta/services/source_manager.py +19 -3
- letta/services/tool_executor/core_tool_executor.py +2 -2
- letta/services/tool_executor/files_tool_executor.py +6 -1
- {letta_nightly-0.8.13.dev20250714104447.dist-info → letta_nightly-0.8.14.dev20250714180504.dist-info}/METADATA +1 -1
- {letta_nightly-0.8.13.dev20250714104447.dist-info → letta_nightly-0.8.14.dev20250714180504.dist-info}/RECORD +31 -31
- {letta_nightly-0.8.13.dev20250714104447.dist-info → letta_nightly-0.8.14.dev20250714180504.dist-info}/LICENSE +0 -0
- {letta_nightly-0.8.13.dev20250714104447.dist-info → letta_nightly-0.8.14.dev20250714180504.dist-info}/WHEEL +0 -0
- {letta_nightly-0.8.13.dev20250714104447.dist-info → letta_nightly-0.8.14.dev20250714180504.dist-info}/entry_points.txt +0 -0
letta/services/agent_manager.py
CHANGED
|
@@ -107,6 +107,32 @@ class AgentManager:
|
|
|
107
107
|
self.identity_manager = IdentityManager()
|
|
108
108
|
self.file_agent_manager = FileAgentManager()
|
|
109
109
|
|
|
110
|
+
@trace_method
|
|
111
|
+
async def _validate_agent_exists_async(self, session, agent_id: str, actor: PydanticUser) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Validate that an agent exists and user has access to it using raw SQL for efficiency.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
session: Database session
|
|
117
|
+
agent_id: ID of the agent to validate
|
|
118
|
+
actor: User performing the action
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
NoResultFound: If agent doesn't exist or user doesn't have access
|
|
122
|
+
"""
|
|
123
|
+
agent_check_query = sa.text(
|
|
124
|
+
"""
|
|
125
|
+
SELECT 1 FROM agents
|
|
126
|
+
WHERE id = :agent_id
|
|
127
|
+
AND organization_id = :org_id
|
|
128
|
+
AND is_deleted = false
|
|
129
|
+
"""
|
|
130
|
+
)
|
|
131
|
+
agent_exists = await session.execute(agent_check_query, {"agent_id": agent_id, "org_id": actor.organization_id})
|
|
132
|
+
|
|
133
|
+
if not agent_exists.fetchone():
|
|
134
|
+
raise NoResultFound(f"Agent with ID {agent_id} not found")
|
|
135
|
+
|
|
110
136
|
@staticmethod
|
|
111
137
|
def _resolve_tools(session, names: Set[str], ids: Set[str], org_id: str) -> Tuple[Dict[str, str], Dict[str, str]]:
|
|
112
138
|
"""
|
|
@@ -635,24 +661,24 @@ class AgentManager:
|
|
|
635
661
|
|
|
636
662
|
return init_messages
|
|
637
663
|
|
|
638
|
-
@trace_method
|
|
639
664
|
@enforce_types
|
|
665
|
+
@trace_method
|
|
640
666
|
def append_initial_message_sequence_to_in_context_messages(
|
|
641
667
|
self, actor: PydanticUser, agent_state: PydanticAgentState, initial_message_sequence: Optional[List[MessageCreate]] = None
|
|
642
668
|
) -> PydanticAgentState:
|
|
643
669
|
init_messages = self._generate_initial_message_sequence(actor, agent_state, initial_message_sequence)
|
|
644
670
|
return self.append_to_in_context_messages(init_messages, agent_id=agent_state.id, actor=actor)
|
|
645
671
|
|
|
646
|
-
@trace_method
|
|
647
672
|
@enforce_types
|
|
673
|
+
@trace_method
|
|
648
674
|
async def append_initial_message_sequence_to_in_context_messages_async(
|
|
649
675
|
self, actor: PydanticUser, agent_state: PydanticAgentState, initial_message_sequence: Optional[List[MessageCreate]] = None
|
|
650
676
|
) -> PydanticAgentState:
|
|
651
677
|
init_messages = self._generate_initial_message_sequence(actor, agent_state, initial_message_sequence)
|
|
652
678
|
return await self.append_to_in_context_messages_async(init_messages, agent_id=agent_state.id, actor=actor)
|
|
653
679
|
|
|
654
|
-
@trace_method
|
|
655
680
|
@enforce_types
|
|
681
|
+
@trace_method
|
|
656
682
|
def update_agent(
|
|
657
683
|
self,
|
|
658
684
|
agent_id: str,
|
|
@@ -773,8 +799,8 @@ class AgentManager:
|
|
|
773
799
|
|
|
774
800
|
return agent.to_pydantic()
|
|
775
801
|
|
|
776
|
-
@trace_method
|
|
777
802
|
@enforce_types
|
|
803
|
+
@trace_method
|
|
778
804
|
async def update_agent_async(
|
|
779
805
|
self,
|
|
780
806
|
agent_id: str,
|
|
@@ -1125,16 +1151,16 @@ class AgentManager:
|
|
|
1125
1151
|
async with db_registry.async_session() as session:
|
|
1126
1152
|
return await AgentModel.size_async(db_session=session, actor=actor)
|
|
1127
1153
|
|
|
1128
|
-
@trace_method
|
|
1129
1154
|
@enforce_types
|
|
1155
|
+
@trace_method
|
|
1130
1156
|
def get_agent_by_id(self, agent_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1131
1157
|
"""Fetch an agent by its ID."""
|
|
1132
1158
|
with db_registry.session() as session:
|
|
1133
1159
|
agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
|
|
1134
1160
|
return agent.to_pydantic()
|
|
1135
1161
|
|
|
1136
|
-
@trace_method
|
|
1137
1162
|
@enforce_types
|
|
1163
|
+
@trace_method
|
|
1138
1164
|
async def get_agent_by_id_async(
|
|
1139
1165
|
self,
|
|
1140
1166
|
agent_id: str,
|
|
@@ -1147,8 +1173,8 @@ class AgentManager:
|
|
|
1147
1173
|
agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
|
|
1148
1174
|
return await agent.to_pydantic_async(include_relationships=include_relationships)
|
|
1149
1175
|
|
|
1150
|
-
@trace_method
|
|
1151
1176
|
@enforce_types
|
|
1177
|
+
@trace_method
|
|
1152
1178
|
async def get_agents_by_ids_async(
|
|
1153
1179
|
self,
|
|
1154
1180
|
agent_ids: list[str],
|
|
@@ -1164,16 +1190,16 @@ class AgentManager:
|
|
|
1164
1190
|
)
|
|
1165
1191
|
return await asyncio.gather(*[agent.to_pydantic_async(include_relationships=include_relationships) for agent in agents])
|
|
1166
1192
|
|
|
1167
|
-
@trace_method
|
|
1168
1193
|
@enforce_types
|
|
1194
|
+
@trace_method
|
|
1169
1195
|
def get_agent_by_name(self, agent_name: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1170
1196
|
"""Fetch an agent by its ID."""
|
|
1171
1197
|
with db_registry.session() as session:
|
|
1172
1198
|
agent = AgentModel.read(db_session=session, name=agent_name, actor=actor)
|
|
1173
1199
|
return agent.to_pydantic()
|
|
1174
1200
|
|
|
1175
|
-
@trace_method
|
|
1176
1201
|
@enforce_types
|
|
1202
|
+
@trace_method
|
|
1177
1203
|
def delete_agent(self, agent_id: str, actor: PydanticUser) -> None:
|
|
1178
1204
|
"""
|
|
1179
1205
|
Deletes an agent and its associated relationships.
|
|
@@ -1220,8 +1246,8 @@ class AgentManager:
|
|
|
1220
1246
|
else:
|
|
1221
1247
|
logger.debug(f"Agent with ID {agent_id} successfully hard deleted")
|
|
1222
1248
|
|
|
1223
|
-
@trace_method
|
|
1224
1249
|
@enforce_types
|
|
1250
|
+
@trace_method
|
|
1225
1251
|
async def delete_agent_async(self, agent_id: str, actor: PydanticUser) -> None:
|
|
1226
1252
|
"""
|
|
1227
1253
|
Deletes an agent and its associated relationships.
|
|
@@ -1270,8 +1296,8 @@ class AgentManager:
|
|
|
1270
1296
|
else:
|
|
1271
1297
|
logger.debug(f"Agent with ID {agent_id} successfully hard deleted")
|
|
1272
1298
|
|
|
1273
|
-
@trace_method
|
|
1274
1299
|
@enforce_types
|
|
1300
|
+
@trace_method
|
|
1275
1301
|
def serialize(self, agent_id: str, actor: PydanticUser) -> AgentSchema:
|
|
1276
1302
|
with db_registry.session() as session:
|
|
1277
1303
|
agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
|
|
@@ -1279,8 +1305,8 @@ class AgentManager:
|
|
|
1279
1305
|
data = schema.dump(agent)
|
|
1280
1306
|
return AgentSchema(**data)
|
|
1281
1307
|
|
|
1282
|
-
@trace_method
|
|
1283
1308
|
@enforce_types
|
|
1309
|
+
@trace_method
|
|
1284
1310
|
def deserialize(
|
|
1285
1311
|
self,
|
|
1286
1312
|
serialized_agent: AgentSchema,
|
|
@@ -1349,8 +1375,8 @@ class AgentManager:
|
|
|
1349
1375
|
# ======================================================================================================================
|
|
1350
1376
|
# Per Agent Environment Variable Management
|
|
1351
1377
|
# ======================================================================================================================
|
|
1352
|
-
@trace_method
|
|
1353
1378
|
@enforce_types
|
|
1379
|
+
@trace_method
|
|
1354
1380
|
def _set_environment_variables(
|
|
1355
1381
|
self,
|
|
1356
1382
|
agent_id: str,
|
|
@@ -1405,8 +1431,8 @@ class AgentManager:
|
|
|
1405
1431
|
# Return the updated agent state
|
|
1406
1432
|
return agent.to_pydantic()
|
|
1407
1433
|
|
|
1408
|
-
@trace_method
|
|
1409
1434
|
@enforce_types
|
|
1435
|
+
@trace_method
|
|
1410
1436
|
def list_groups(self, agent_id: str, actor: PydanticUser, manager_type: Optional[str] = None) -> List[PydanticGroup]:
|
|
1411
1437
|
with db_registry.session() as session:
|
|
1412
1438
|
agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
|
|
@@ -1422,20 +1448,20 @@ class AgentManager:
|
|
|
1422
1448
|
# TODO: 2) These messages are ordered from oldest to newest
|
|
1423
1449
|
# TODO: This can be fixed by having an actual relationship in the ORM for message_ids
|
|
1424
1450
|
# TODO: This can also be made more efficient, instead of getting, setting, we can do it all in one db session for one query.
|
|
1425
|
-
@trace_method
|
|
1426
1451
|
@enforce_types
|
|
1452
|
+
@trace_method
|
|
1427
1453
|
def get_in_context_messages(self, agent_id: str, actor: PydanticUser) -> List[PydanticMessage]:
|
|
1428
1454
|
message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
|
|
1429
1455
|
return self.message_manager.get_messages_by_ids(message_ids=message_ids, actor=actor)
|
|
1430
1456
|
|
|
1431
|
-
@trace_method
|
|
1432
1457
|
@enforce_types
|
|
1458
|
+
@trace_method
|
|
1433
1459
|
def get_system_message(self, agent_id: str, actor: PydanticUser) -> PydanticMessage:
|
|
1434
1460
|
message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
|
|
1435
1461
|
return self.message_manager.get_message_by_id(message_id=message_ids[0], actor=actor)
|
|
1436
1462
|
|
|
1437
|
-
@trace_method
|
|
1438
1463
|
@enforce_types
|
|
1464
|
+
@trace_method
|
|
1439
1465
|
async def get_system_message_async(self, agent_id: str, actor: PydanticUser) -> PydanticMessage:
|
|
1440
1466
|
agent = await self.get_agent_by_id_async(agent_id=agent_id, include_relationships=[], actor=actor)
|
|
1441
1467
|
return await self.message_manager.get_message_by_id_async(message_id=agent.message_ids[0], actor=actor)
|
|
@@ -1443,8 +1469,8 @@ class AgentManager:
|
|
|
1443
1469
|
# TODO: This is duplicated below
|
|
1444
1470
|
# TODO: This is legacy code and should be cleaned up
|
|
1445
1471
|
# TODO: A lot of the memory "compilation" should be offset to a separate class
|
|
1446
|
-
@trace_method
|
|
1447
1472
|
@enforce_types
|
|
1473
|
+
@trace_method
|
|
1448
1474
|
def rebuild_system_prompt(self, agent_id: str, actor: PydanticUser, force=False, update_timestamp=True) -> PydanticAgentState:
|
|
1449
1475
|
"""Rebuilds the system message with the latest memory object and any shared memory block updates
|
|
1450
1476
|
|
|
@@ -1515,29 +1541,42 @@ class AgentManager:
|
|
|
1515
1541
|
else:
|
|
1516
1542
|
return agent_state
|
|
1517
1543
|
|
|
1518
|
-
|
|
1544
|
+
# TODO: This is probably one of the worst pieces of code I've ever written please rip up as you see wish
|
|
1519
1545
|
@enforce_types
|
|
1546
|
+
@trace_method
|
|
1520
1547
|
async def rebuild_system_prompt_async(
|
|
1521
|
-
self,
|
|
1522
|
-
|
|
1548
|
+
self,
|
|
1549
|
+
agent_id: str,
|
|
1550
|
+
actor: PydanticUser,
|
|
1551
|
+
force=False,
|
|
1552
|
+
update_timestamp=True,
|
|
1553
|
+
tool_rules_solver: Optional[ToolRulesSolver] = None,
|
|
1554
|
+
dry_run: bool = False,
|
|
1555
|
+
) -> Tuple[PydanticAgentState, Optional[PydanticMessage], int, int]:
|
|
1523
1556
|
"""Rebuilds the system message with the latest memory object and any shared memory block updates
|
|
1524
1557
|
|
|
1525
1558
|
Updates to core memory blocks should trigger a "rebuild", which itself will create a new message object
|
|
1526
1559
|
|
|
1527
1560
|
Updates to the memory header should *not* trigger a rebuild, since that will simply flood recall storage with excess messages
|
|
1528
1561
|
"""
|
|
1529
|
-
|
|
1530
|
-
|
|
1562
|
+
num_messages_task = self.message_manager.size_async(actor=actor, agent_id=agent_id)
|
|
1563
|
+
num_archival_memories_task = self.passage_manager.agent_passage_size_async(actor=actor, agent_id=agent_id)
|
|
1564
|
+
agent_state_task = self.get_agent_by_id_async(agent_id=agent_id, include_relationships=["memory", "sources", "tools"], actor=actor)
|
|
1565
|
+
|
|
1566
|
+
num_messages, num_archival_memories, agent_state = await asyncio.gather(
|
|
1567
|
+
num_messages_task,
|
|
1568
|
+
num_archival_memories_task,
|
|
1569
|
+
agent_state_task,
|
|
1570
|
+
)
|
|
1571
|
+
|
|
1531
1572
|
if not tool_rules_solver:
|
|
1532
1573
|
tool_rules_solver = ToolRulesSolver(agent_state.tool_rules)
|
|
1533
1574
|
|
|
1534
|
-
curr_system_message = await self.
|
|
1535
|
-
agent_id=agent_id, actor=actor
|
|
1536
|
-
) # this is the system + memory bank, not just the system prompt
|
|
1575
|
+
curr_system_message = await self.message_manager.get_message_by_id_async(message_id=agent_state.message_ids[0], actor=actor)
|
|
1537
1576
|
|
|
1538
1577
|
if curr_system_message is None:
|
|
1539
1578
|
logger.warning(f"No system message found for agent {agent_state.id} and user {actor}")
|
|
1540
|
-
return agent_state
|
|
1579
|
+
return agent_state, curr_system_message, num_messages, num_archival_memories
|
|
1541
1580
|
|
|
1542
1581
|
curr_system_message_openai = curr_system_message.to_openai_dict()
|
|
1543
1582
|
|
|
@@ -1551,7 +1590,7 @@ class AgentManager:
|
|
|
1551
1590
|
logger.debug(
|
|
1552
1591
|
f"Memory hasn't changed for agent id={agent_id} and actor=({actor.id}, {actor.name}), skipping system prompt rebuild"
|
|
1553
1592
|
)
|
|
1554
|
-
return agent_state
|
|
1593
|
+
return agent_state, curr_system_message, num_messages, num_archival_memories
|
|
1555
1594
|
|
|
1556
1595
|
# If the memory didn't update, we probably don't want to update the timestamp inside
|
|
1557
1596
|
# For example, if we're doing a system prompt swap, this should probably be False
|
|
@@ -1561,9 +1600,6 @@ class AgentManager:
|
|
|
1561
1600
|
# NOTE: a bit of a hack - we pull the timestamp from the message created_by
|
|
1562
1601
|
memory_edit_timestamp = curr_system_message.created_at
|
|
1563
1602
|
|
|
1564
|
-
num_messages = await self.message_manager.size_async(actor=actor, agent_id=agent_id)
|
|
1565
|
-
num_archival_memories = await self.passage_manager.agent_passage_size_async(actor=actor, agent_id=agent_id)
|
|
1566
|
-
|
|
1567
1603
|
# update memory (TODO: potentially update recall/archival stats separately)
|
|
1568
1604
|
|
|
1569
1605
|
new_system_message_str = compile_system_message(
|
|
@@ -1582,63 +1618,67 @@ class AgentManager:
|
|
|
1582
1618
|
logger.debug(f"Rebuilding system with new memory...\nDiff:\n{diff}")
|
|
1583
1619
|
|
|
1584
1620
|
# Swap the system message out (only if there is a diff)
|
|
1585
|
-
|
|
1621
|
+
temp_message = PydanticMessage.dict_to_message(
|
|
1586
1622
|
agent_id=agent_id,
|
|
1587
1623
|
model=agent_state.llm_config.model,
|
|
1588
1624
|
openai_message_dict={"role": "system", "content": new_system_message_str},
|
|
1589
1625
|
)
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1626
|
+
temp_message.id = curr_system_message.id
|
|
1627
|
+
|
|
1628
|
+
if not dry_run:
|
|
1629
|
+
await self.message_manager.update_message_by_id_async(
|
|
1630
|
+
message_id=curr_system_message.id,
|
|
1631
|
+
message_update=MessageUpdate(**temp_message.model_dump()),
|
|
1632
|
+
actor=actor,
|
|
1633
|
+
)
|
|
1634
|
+
else:
|
|
1635
|
+
curr_system_message = temp_message
|
|
1636
|
+
|
|
1637
|
+
return agent_state, curr_system_message, num_messages, num_archival_memories
|
|
1598
1638
|
|
|
1599
|
-
@trace_method
|
|
1600
1639
|
@enforce_types
|
|
1640
|
+
@trace_method
|
|
1601
1641
|
def set_in_context_messages(self, agent_id: str, message_ids: List[str], actor: PydanticUser) -> PydanticAgentState:
|
|
1602
1642
|
return self.update_agent(agent_id=agent_id, agent_update=UpdateAgent(message_ids=message_ids), actor=actor)
|
|
1603
1643
|
|
|
1604
|
-
@trace_method
|
|
1605
1644
|
@enforce_types
|
|
1645
|
+
@trace_method
|
|
1606
1646
|
async def set_in_context_messages_async(self, agent_id: str, message_ids: List[str], actor: PydanticUser) -> PydanticAgentState:
|
|
1607
1647
|
return await self.update_agent_async(agent_id=agent_id, agent_update=UpdateAgent(message_ids=message_ids), actor=actor)
|
|
1608
1648
|
|
|
1609
|
-
@trace_method
|
|
1610
1649
|
@enforce_types
|
|
1650
|
+
@trace_method
|
|
1611
1651
|
def trim_older_in_context_messages(self, num: int, agent_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1612
1652
|
message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
|
|
1613
1653
|
new_messages = [message_ids[0]] + message_ids[num:] # 0 is system message
|
|
1614
1654
|
return self.set_in_context_messages(agent_id=agent_id, message_ids=new_messages, actor=actor)
|
|
1615
1655
|
|
|
1616
|
-
@trace_method
|
|
1617
1656
|
@enforce_types
|
|
1657
|
+
@trace_method
|
|
1618
1658
|
def trim_all_in_context_messages_except_system(self, agent_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1619
1659
|
message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
|
|
1620
1660
|
# TODO: How do we know this?
|
|
1621
1661
|
new_messages = [message_ids[0]] # 0 is system message
|
|
1622
1662
|
return self.set_in_context_messages(agent_id=agent_id, message_ids=new_messages, actor=actor)
|
|
1623
1663
|
|
|
1624
|
-
@trace_method
|
|
1625
1664
|
@enforce_types
|
|
1665
|
+
@trace_method
|
|
1626
1666
|
def prepend_to_in_context_messages(self, messages: List[PydanticMessage], agent_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1627
1667
|
message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
|
|
1628
1668
|
new_messages = self.message_manager.create_many_messages(messages, actor=actor)
|
|
1629
1669
|
message_ids = [message_ids[0]] + [m.id for m in new_messages] + message_ids[1:]
|
|
1630
1670
|
return self.set_in_context_messages(agent_id=agent_id, message_ids=message_ids, actor=actor)
|
|
1631
1671
|
|
|
1632
|
-
@trace_method
|
|
1633
1672
|
@enforce_types
|
|
1673
|
+
@trace_method
|
|
1634
1674
|
def append_to_in_context_messages(self, messages: List[PydanticMessage], agent_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1635
1675
|
messages = self.message_manager.create_many_messages(messages, actor=actor)
|
|
1636
1676
|
message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids or []
|
|
1637
1677
|
message_ids += [m.id for m in messages]
|
|
1638
1678
|
return self.set_in_context_messages(agent_id=agent_id, message_ids=message_ids, actor=actor)
|
|
1639
1679
|
|
|
1640
|
-
@trace_method
|
|
1641
1680
|
@enforce_types
|
|
1681
|
+
@trace_method
|
|
1642
1682
|
async def append_to_in_context_messages_async(
|
|
1643
1683
|
self, messages: List[PydanticMessage], agent_id: str, actor: PydanticUser
|
|
1644
1684
|
) -> PydanticAgentState:
|
|
@@ -1648,8 +1688,8 @@ class AgentManager:
|
|
|
1648
1688
|
message_ids += [m.id for m in messages]
|
|
1649
1689
|
return await self.set_in_context_messages_async(agent_id=agent_id, message_ids=message_ids, actor=actor)
|
|
1650
1690
|
|
|
1651
|
-
@trace_method
|
|
1652
1691
|
@enforce_types
|
|
1692
|
+
@trace_method
|
|
1653
1693
|
async def reset_messages_async(
|
|
1654
1694
|
self, agent_id: str, actor: PydanticUser, add_default_initial_messages: bool = False
|
|
1655
1695
|
) -> PydanticAgentState:
|
|
@@ -1712,8 +1752,8 @@ class AgentManager:
|
|
|
1712
1752
|
else:
|
|
1713
1753
|
return agent_state
|
|
1714
1754
|
|
|
1715
|
-
@trace_method
|
|
1716
1755
|
@enforce_types
|
|
1756
|
+
@trace_method
|
|
1717
1757
|
async def update_memory_if_changed_async(self, agent_id: str, new_memory: Memory, actor: PydanticUser) -> PydanticAgentState:
|
|
1718
1758
|
"""
|
|
1719
1759
|
Update internal memory object and system prompt if there have been modifications.
|
|
@@ -1756,12 +1796,12 @@ class AgentManager:
|
|
|
1756
1796
|
# NOTE: don't do this since re-buildin the memory is handled at the start of the step
|
|
1757
1797
|
# rebuild memory - this records the last edited timestamp of the memory
|
|
1758
1798
|
# TODO: pass in update timestamp from block edit time
|
|
1759
|
-
|
|
1799
|
+
await self.rebuild_system_prompt_async(agent_id=agent_id, actor=actor)
|
|
1760
1800
|
|
|
1761
1801
|
return agent_state
|
|
1762
1802
|
|
|
1763
|
-
@trace_method
|
|
1764
1803
|
@enforce_types
|
|
1804
|
+
@trace_method
|
|
1765
1805
|
async def refresh_memory_async(self, agent_state: PydanticAgentState, actor: PydanticUser) -> PydanticAgentState:
|
|
1766
1806
|
# TODO: This will NOT work for new blocks/file blocks added intra-step
|
|
1767
1807
|
block_ids = [b.id for b in agent_state.memory.blocks]
|
|
@@ -1779,8 +1819,8 @@ class AgentManager:
|
|
|
1779
1819
|
|
|
1780
1820
|
return agent_state
|
|
1781
1821
|
|
|
1782
|
-
@trace_method
|
|
1783
1822
|
@enforce_types
|
|
1823
|
+
@trace_method
|
|
1784
1824
|
async def refresh_file_blocks(self, agent_state: PydanticAgentState, actor: PydanticUser) -> PydanticAgentState:
|
|
1785
1825
|
file_blocks = await self.file_agent_manager.list_files_for_agent(agent_id=agent_state.id, actor=actor, return_as_blocks=True)
|
|
1786
1826
|
agent_state.memory.file_blocks = [b for b in file_blocks if b is not None]
|
|
@@ -1789,8 +1829,8 @@ class AgentManager:
|
|
|
1789
1829
|
# ======================================================================================================================
|
|
1790
1830
|
# Source Management
|
|
1791
1831
|
# ======================================================================================================================
|
|
1792
|
-
@trace_method
|
|
1793
1832
|
@enforce_types
|
|
1833
|
+
@trace_method
|
|
1794
1834
|
async def attach_source_async(self, agent_id: str, source_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1795
1835
|
"""
|
|
1796
1836
|
Attaches a source to an agent.
|
|
@@ -1820,15 +1860,11 @@ class AgentManager:
|
|
|
1820
1860
|
)
|
|
1821
1861
|
|
|
1822
1862
|
# Commit the changes
|
|
1823
|
-
await agent.update_async(session, actor=actor)
|
|
1824
|
-
|
|
1825
|
-
# Force rebuild of system prompt so that the agent is updated with passage count
|
|
1826
|
-
pydantic_agent = await self.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True)
|
|
1827
|
-
|
|
1828
|
-
return pydantic_agent
|
|
1863
|
+
agent = await agent.update_async(session, actor=actor)
|
|
1864
|
+
return await agent.to_pydantic_async()
|
|
1829
1865
|
|
|
1830
|
-
@trace_method
|
|
1831
1866
|
@enforce_types
|
|
1867
|
+
@trace_method
|
|
1832
1868
|
def append_system_message(self, agent_id: str, content: str, actor: PydanticUser):
|
|
1833
1869
|
|
|
1834
1870
|
# get the agent
|
|
@@ -1840,8 +1876,8 @@ class AgentManager:
|
|
|
1840
1876
|
# update agent in-context message IDs
|
|
1841
1877
|
self.append_to_in_context_messages(messages=[message], agent_id=agent_id, actor=actor)
|
|
1842
1878
|
|
|
1843
|
-
@trace_method
|
|
1844
1879
|
@enforce_types
|
|
1880
|
+
@trace_method
|
|
1845
1881
|
async def append_system_message_async(self, agent_id: str, content: str, actor: PydanticUser):
|
|
1846
1882
|
|
|
1847
1883
|
# get the agent
|
|
@@ -1853,28 +1889,8 @@ class AgentManager:
|
|
|
1853
1889
|
# update agent in-context message IDs
|
|
1854
1890
|
await self.append_to_in_context_messages_async(messages=[message], agent_id=agent_id, actor=actor)
|
|
1855
1891
|
|
|
1856
|
-
@trace_method
|
|
1857
1892
|
@enforce_types
|
|
1858
|
-
def list_attached_sources(self, agent_id: str, actor: PydanticUser) -> List[PydanticSource]:
|
|
1859
|
-
"""
|
|
1860
|
-
Lists all sources attached to an agent.
|
|
1861
|
-
|
|
1862
|
-
Args:
|
|
1863
|
-
agent_id: ID of the agent to list sources for
|
|
1864
|
-
actor: User performing the action
|
|
1865
|
-
|
|
1866
|
-
Returns:
|
|
1867
|
-
List[str]: List of source IDs attached to the agent
|
|
1868
|
-
"""
|
|
1869
|
-
with db_registry.session() as session:
|
|
1870
|
-
# Verify agent exists and user has permission to access it
|
|
1871
|
-
agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
|
|
1872
|
-
|
|
1873
|
-
# Use the lazy-loaded relationship to get sources
|
|
1874
|
-
return [source.to_pydantic() for source in agent.sources]
|
|
1875
|
-
|
|
1876
1893
|
@trace_method
|
|
1877
|
-
@enforce_types
|
|
1878
1894
|
async def list_attached_sources_async(self, agent_id: str, actor: PydanticUser) -> List[PydanticSource]:
|
|
1879
1895
|
"""
|
|
1880
1896
|
Lists all sources attached to an agent.
|
|
@@ -1885,44 +1901,34 @@ class AgentManager:
|
|
|
1885
1901
|
|
|
1886
1902
|
Returns:
|
|
1887
1903
|
List[str]: List of source IDs attached to the agent
|
|
1888
|
-
"""
|
|
1889
|
-
async with db_registry.async_session() as session:
|
|
1890
|
-
# Verify agent exists and user has permission to access it
|
|
1891
|
-
agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
|
|
1892
|
-
|
|
1893
|
-
# Use the lazy-loaded relationship to get sources
|
|
1894
|
-
return [source.to_pydantic() for source in agent.sources]
|
|
1895
|
-
|
|
1896
|
-
@trace_method
|
|
1897
|
-
@enforce_types
|
|
1898
|
-
def detach_source(self, agent_id: str, source_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1899
|
-
"""
|
|
1900
|
-
Detaches a source from an agent.
|
|
1901
1904
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
source_id: ID of the source to detach
|
|
1905
|
-
actor: User performing the action
|
|
1905
|
+
Raises:
|
|
1906
|
+
NoResultFound: If agent doesn't exist or user doesn't have access
|
|
1906
1907
|
"""
|
|
1907
|
-
with db_registry.
|
|
1908
|
-
#
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
# Remove the source from the relationship
|
|
1912
|
-
remaining_sources = [s for s in agent.sources if s.id != source_id]
|
|
1908
|
+
async with db_registry.async_session() as session:
|
|
1909
|
+
# Validate agent exists and user has access
|
|
1910
|
+
await self._validate_agent_exists_async(session, agent_id, actor)
|
|
1913
1911
|
|
|
1914
|
-
|
|
1915
|
-
|
|
1912
|
+
# Use raw SQL to efficiently fetch sources - much faster than lazy loading
|
|
1913
|
+
# Fast query without relationship loading
|
|
1914
|
+
query = (
|
|
1915
|
+
select(SourceModel)
|
|
1916
|
+
.join(SourcesAgents, SourceModel.id == SourcesAgents.source_id)
|
|
1917
|
+
.where(
|
|
1918
|
+
SourcesAgents.agent_id == agent_id,
|
|
1919
|
+
SourceModel.organization_id == actor.organization_id,
|
|
1920
|
+
SourceModel.is_deleted == False,
|
|
1921
|
+
)
|
|
1922
|
+
.order_by(SourceModel.created_at.desc(), SourceModel.id)
|
|
1923
|
+
)
|
|
1916
1924
|
|
|
1917
|
-
|
|
1918
|
-
|
|
1925
|
+
result = await session.execute(query)
|
|
1926
|
+
sources = result.scalars().all()
|
|
1919
1927
|
|
|
1920
|
-
|
|
1921
|
-
agent.update(session, actor=actor)
|
|
1922
|
-
return agent.to_pydantic()
|
|
1928
|
+
return [source.to_pydantic() for source in sources]
|
|
1923
1929
|
|
|
1924
|
-
@trace_method
|
|
1925
1930
|
@enforce_types
|
|
1931
|
+
@trace_method
|
|
1926
1932
|
async def detach_source_async(self, agent_id: str, source_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
1927
1933
|
"""
|
|
1928
1934
|
Detaches a source from an agent.
|
|
@@ -1931,29 +1937,36 @@ class AgentManager:
|
|
|
1931
1937
|
agent_id: ID of the agent to detach the source from
|
|
1932
1938
|
source_id: ID of the source to detach
|
|
1933
1939
|
actor: User performing the action
|
|
1940
|
+
|
|
1941
|
+
Raises:
|
|
1942
|
+
NoResultFound: If agent doesn't exist or user doesn't have access
|
|
1934
1943
|
"""
|
|
1935
1944
|
async with db_registry.async_session() as session:
|
|
1936
|
-
#
|
|
1937
|
-
|
|
1945
|
+
# Validate agent exists and user has access
|
|
1946
|
+
await self._validate_agent_exists_async(session, agent_id, actor)
|
|
1938
1947
|
|
|
1939
|
-
#
|
|
1940
|
-
|
|
1948
|
+
# Check if the source is actually attached to this agent using junction table
|
|
1949
|
+
attachment_check_query = select(SourcesAgents).where(SourcesAgents.agent_id == agent_id, SourcesAgents.source_id == source_id)
|
|
1950
|
+
attachment_result = await session.execute(attachment_check_query)
|
|
1951
|
+
attachment = attachment_result.scalar_one_or_none()
|
|
1941
1952
|
|
|
1942
|
-
if
|
|
1953
|
+
if not attachment:
|
|
1943
1954
|
logger.warning(f"Attempted to remove unattached source id={source_id} from agent id={agent_id} by actor={actor}")
|
|
1955
|
+
else:
|
|
1956
|
+
# Delete the association directly from the junction table
|
|
1957
|
+
delete_query = delete(SourcesAgents).where(SourcesAgents.agent_id == agent_id, SourcesAgents.source_id == source_id)
|
|
1958
|
+
await session.execute(delete_query)
|
|
1959
|
+
await session.commit()
|
|
1944
1960
|
|
|
1945
|
-
#
|
|
1946
|
-
agent.
|
|
1947
|
-
|
|
1948
|
-
# Commit the changes
|
|
1949
|
-
await agent.update_async(session, actor=actor)
|
|
1961
|
+
# Get agent without loading relationships for return value
|
|
1962
|
+
agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
|
|
1950
1963
|
return await agent.to_pydantic_async()
|
|
1951
1964
|
|
|
1952
1965
|
# ======================================================================================================================
|
|
1953
1966
|
# Block management
|
|
1954
1967
|
# ======================================================================================================================
|
|
1955
|
-
@trace_method
|
|
1956
1968
|
@enforce_types
|
|
1969
|
+
@trace_method
|
|
1957
1970
|
def get_block_with_label(
|
|
1958
1971
|
self,
|
|
1959
1972
|
agent_id: str,
|
|
@@ -1968,8 +1981,8 @@ class AgentManager:
|
|
|
1968
1981
|
return block.to_pydantic()
|
|
1969
1982
|
raise NoResultFound(f"No block with label '{block_label}' found for agent '{agent_id}'")
|
|
1970
1983
|
|
|
1971
|
-
@trace_method
|
|
1972
1984
|
@enforce_types
|
|
1985
|
+
@trace_method
|
|
1973
1986
|
async def get_block_with_label_async(
|
|
1974
1987
|
self,
|
|
1975
1988
|
agent_id: str,
|
|
@@ -1984,8 +1997,8 @@ class AgentManager:
|
|
|
1984
1997
|
return block.to_pydantic()
|
|
1985
1998
|
raise NoResultFound(f"No block with label '{block_label}' found for agent '{agent_id}'")
|
|
1986
1999
|
|
|
1987
|
-
@trace_method
|
|
1988
2000
|
@enforce_types
|
|
2001
|
+
@trace_method
|
|
1989
2002
|
async def modify_block_by_label_async(
|
|
1990
2003
|
self,
|
|
1991
2004
|
agent_id: str,
|
|
@@ -2012,8 +2025,8 @@ class AgentManager:
|
|
|
2012
2025
|
await block.update_async(session, actor=actor)
|
|
2013
2026
|
return block.to_pydantic()
|
|
2014
2027
|
|
|
2015
|
-
@trace_method
|
|
2016
2028
|
@enforce_types
|
|
2029
|
+
@trace_method
|
|
2017
2030
|
def update_block_with_label(
|
|
2018
2031
|
self,
|
|
2019
2032
|
agent_id: str,
|
|
@@ -2037,8 +2050,8 @@ class AgentManager:
|
|
|
2037
2050
|
agent.update(session, actor=actor)
|
|
2038
2051
|
return agent.to_pydantic()
|
|
2039
2052
|
|
|
2040
|
-
@trace_method
|
|
2041
2053
|
@enforce_types
|
|
2054
|
+
@trace_method
|
|
2042
2055
|
def attach_block(self, agent_id: str, block_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
2043
2056
|
"""Attaches a block to an agent. For sleeptime agents, also attaches to paired agents in the same group."""
|
|
2044
2057
|
with db_registry.session() as session:
|
|
@@ -2067,8 +2080,8 @@ class AgentManager:
|
|
|
2067
2080
|
|
|
2068
2081
|
return agent.to_pydantic()
|
|
2069
2082
|
|
|
2070
|
-
@trace_method
|
|
2071
2083
|
@enforce_types
|
|
2084
|
+
@trace_method
|
|
2072
2085
|
async def attach_block_async(self, agent_id: str, block_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
2073
2086
|
"""Attaches a block to an agent. For sleeptime agents, also attaches to paired agents in the same group."""
|
|
2074
2087
|
async with db_registry.async_session() as session:
|
|
@@ -2103,8 +2116,8 @@ class AgentManager:
|
|
|
2103
2116
|
|
|
2104
2117
|
return await agent.to_pydantic_async()
|
|
2105
2118
|
|
|
2106
|
-
@trace_method
|
|
2107
2119
|
@enforce_types
|
|
2120
|
+
@trace_method
|
|
2108
2121
|
def detach_block(
|
|
2109
2122
|
self,
|
|
2110
2123
|
agent_id: str,
|
|
@@ -2124,8 +2137,8 @@ class AgentManager:
|
|
|
2124
2137
|
agent.update(session, actor=actor)
|
|
2125
2138
|
return agent.to_pydantic()
|
|
2126
2139
|
|
|
2127
|
-
@trace_method
|
|
2128
2140
|
@enforce_types
|
|
2141
|
+
@trace_method
|
|
2129
2142
|
async def detach_block_async(
|
|
2130
2143
|
self,
|
|
2131
2144
|
agent_id: str,
|
|
@@ -2145,8 +2158,8 @@ class AgentManager:
|
|
|
2145
2158
|
await agent.update_async(session, actor=actor)
|
|
2146
2159
|
return await agent.to_pydantic_async()
|
|
2147
2160
|
|
|
2148
|
-
@trace_method
|
|
2149
2161
|
@enforce_types
|
|
2162
|
+
@trace_method
|
|
2150
2163
|
def detach_block_with_label(
|
|
2151
2164
|
self,
|
|
2152
2165
|
agent_id: str,
|
|
@@ -2170,8 +2183,8 @@ class AgentManager:
|
|
|
2170
2183
|
# Passage Management
|
|
2171
2184
|
# ======================================================================================================================
|
|
2172
2185
|
|
|
2173
|
-
@trace_method
|
|
2174
2186
|
@enforce_types
|
|
2187
|
+
@trace_method
|
|
2175
2188
|
def list_passages(
|
|
2176
2189
|
self,
|
|
2177
2190
|
actor: PydanticUser,
|
|
@@ -2231,8 +2244,8 @@ class AgentManager:
|
|
|
2231
2244
|
|
|
2232
2245
|
return [p.to_pydantic() for p in passages]
|
|
2233
2246
|
|
|
2234
|
-
@trace_method
|
|
2235
2247
|
@enforce_types
|
|
2248
|
+
@trace_method
|
|
2236
2249
|
async def list_passages_async(
|
|
2237
2250
|
self,
|
|
2238
2251
|
actor: PydanticUser,
|
|
@@ -2292,8 +2305,8 @@ class AgentManager:
|
|
|
2292
2305
|
|
|
2293
2306
|
return [p.to_pydantic() for p in passages]
|
|
2294
2307
|
|
|
2295
|
-
@trace_method
|
|
2296
2308
|
@enforce_types
|
|
2309
|
+
@trace_method
|
|
2297
2310
|
async def list_source_passages_async(
|
|
2298
2311
|
self,
|
|
2299
2312
|
actor: PydanticUser,
|
|
@@ -2340,8 +2353,8 @@ class AgentManager:
|
|
|
2340
2353
|
# Convert to Pydantic models
|
|
2341
2354
|
return [p.to_pydantic() for p in passages]
|
|
2342
2355
|
|
|
2343
|
-
@trace_method
|
|
2344
2356
|
@enforce_types
|
|
2357
|
+
@trace_method
|
|
2345
2358
|
async def list_agent_passages_async(
|
|
2346
2359
|
self,
|
|
2347
2360
|
actor: PydanticUser,
|
|
@@ -2384,8 +2397,8 @@ class AgentManager:
|
|
|
2384
2397
|
# Convert to Pydantic models
|
|
2385
2398
|
return [p.to_pydantic() for p in passages]
|
|
2386
2399
|
|
|
2387
|
-
@trace_method
|
|
2388
2400
|
@enforce_types
|
|
2401
|
+
@trace_method
|
|
2389
2402
|
def passage_size(
|
|
2390
2403
|
self,
|
|
2391
2404
|
actor: PydanticUser,
|
|
@@ -2465,8 +2478,8 @@ class AgentManager:
|
|
|
2465
2478
|
# ======================================================================================================================
|
|
2466
2479
|
# Tool Management
|
|
2467
2480
|
# ======================================================================================================================
|
|
2468
|
-
@trace_method
|
|
2469
2481
|
@enforce_types
|
|
2482
|
+
@trace_method
|
|
2470
2483
|
def attach_tool(self, agent_id: str, tool_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
2471
2484
|
"""
|
|
2472
2485
|
Attaches a tool to an agent.
|
|
@@ -2501,8 +2514,8 @@ class AgentManager:
|
|
|
2501
2514
|
agent.update(session, actor=actor)
|
|
2502
2515
|
return agent.to_pydantic()
|
|
2503
2516
|
|
|
2504
|
-
@trace_method
|
|
2505
2517
|
@enforce_types
|
|
2518
|
+
@trace_method
|
|
2506
2519
|
async def attach_tool_async(self, agent_id: str, tool_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
2507
2520
|
"""
|
|
2508
2521
|
Attaches a tool to an agent.
|
|
@@ -2537,8 +2550,8 @@ class AgentManager:
|
|
|
2537
2550
|
await agent.update_async(session, actor=actor)
|
|
2538
2551
|
return await agent.to_pydantic_async()
|
|
2539
2552
|
|
|
2540
|
-
@trace_method
|
|
2541
2553
|
@enforce_types
|
|
2554
|
+
@trace_method
|
|
2542
2555
|
async def attach_missing_files_tools_async(self, agent_state: PydanticAgentState, actor: PydanticUser) -> PydanticAgentState:
|
|
2543
2556
|
"""
|
|
2544
2557
|
Attaches missing core file tools to an agent.
|
|
@@ -2569,8 +2582,8 @@ class AgentManager:
|
|
|
2569
2582
|
|
|
2570
2583
|
return agent_state
|
|
2571
2584
|
|
|
2572
|
-
@trace_method
|
|
2573
2585
|
@enforce_types
|
|
2586
|
+
@trace_method
|
|
2574
2587
|
async def detach_all_files_tools_async(self, agent_state: PydanticAgentState, actor: PydanticUser) -> PydanticAgentState:
|
|
2575
2588
|
"""
|
|
2576
2589
|
Detach all core file tools from an agent.
|
|
@@ -2596,8 +2609,8 @@ class AgentManager:
|
|
|
2596
2609
|
|
|
2597
2610
|
return agent_state
|
|
2598
2611
|
|
|
2599
|
-
@trace_method
|
|
2600
2612
|
@enforce_types
|
|
2613
|
+
@trace_method
|
|
2601
2614
|
def detach_tool(self, agent_id: str, tool_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
2602
2615
|
"""
|
|
2603
2616
|
Detaches a tool from an agent.
|
|
@@ -2630,8 +2643,8 @@ class AgentManager:
|
|
|
2630
2643
|
agent.update(session, actor=actor)
|
|
2631
2644
|
return agent.to_pydantic()
|
|
2632
2645
|
|
|
2633
|
-
@trace_method
|
|
2634
2646
|
@enforce_types
|
|
2647
|
+
@trace_method
|
|
2635
2648
|
async def detach_tool_async(self, agent_id: str, tool_id: str, actor: PydanticUser) -> PydanticAgentState:
|
|
2636
2649
|
"""
|
|
2637
2650
|
Detaches a tool from an agent.
|
|
@@ -2664,8 +2677,8 @@ class AgentManager:
|
|
|
2664
2677
|
await agent.update_async(session, actor=actor)
|
|
2665
2678
|
return await agent.to_pydantic_async()
|
|
2666
2679
|
|
|
2667
|
-
@trace_method
|
|
2668
2680
|
@enforce_types
|
|
2681
|
+
@trace_method
|
|
2669
2682
|
def list_attached_tools(self, agent_id: str, actor: PydanticUser) -> List[PydanticTool]:
|
|
2670
2683
|
"""
|
|
2671
2684
|
List all tools attached to an agent.
|
|
@@ -2681,11 +2694,40 @@ class AgentManager:
|
|
|
2681
2694
|
agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
|
|
2682
2695
|
return [tool.to_pydantic() for tool in agent.tools]
|
|
2683
2696
|
|
|
2697
|
+
@enforce_types
|
|
2698
|
+
@trace_method
|
|
2699
|
+
async def list_attached_tools_async(self, agent_id: str, actor: PydanticUser) -> List[PydanticTool]:
|
|
2700
|
+
"""
|
|
2701
|
+
List all tools attached to an agent (async version with optimized performance).
|
|
2702
|
+
Uses direct SQL queries to avoid SqlAlchemyBase overhead.
|
|
2703
|
+
|
|
2704
|
+
Args:
|
|
2705
|
+
agent_id: ID of the agent to list tools for.
|
|
2706
|
+
actor: User performing the action.
|
|
2707
|
+
|
|
2708
|
+
Returns:
|
|
2709
|
+
List[PydanticTool]: List of tools attached to the agent.
|
|
2710
|
+
"""
|
|
2711
|
+
async with db_registry.async_session() as session:
|
|
2712
|
+
# lightweight check for agent access
|
|
2713
|
+
await self._validate_agent_exists_async(session, agent_id, actor)
|
|
2714
|
+
|
|
2715
|
+
# direct query for tools via join - much more performant
|
|
2716
|
+
query = (
|
|
2717
|
+
select(ToolModel)
|
|
2718
|
+
.join(ToolsAgents, ToolModel.id == ToolsAgents.tool_id)
|
|
2719
|
+
.where(ToolsAgents.agent_id == agent_id, ToolModel.organization_id == actor.organization_id)
|
|
2720
|
+
)
|
|
2721
|
+
|
|
2722
|
+
result = await session.execute(query)
|
|
2723
|
+
tools = result.scalars().all()
|
|
2724
|
+
return [tool.to_pydantic() for tool in tools]
|
|
2725
|
+
|
|
2684
2726
|
# ======================================================================================================================
|
|
2685
2727
|
# Tag Management
|
|
2686
2728
|
# ======================================================================================================================
|
|
2687
|
-
@trace_method
|
|
2688
2729
|
@enforce_types
|
|
2730
|
+
@trace_method
|
|
2689
2731
|
def list_tags(
|
|
2690
2732
|
self, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50, query_text: Optional[str] = None
|
|
2691
2733
|
) -> List[str]:
|
|
@@ -2719,8 +2761,8 @@ class AgentManager:
|
|
|
2719
2761
|
results = [tag[0] for tag in query.all()]
|
|
2720
2762
|
return results
|
|
2721
2763
|
|
|
2722
|
-
@trace_method
|
|
2723
2764
|
@enforce_types
|
|
2765
|
+
@trace_method
|
|
2724
2766
|
async def list_tags_async(
|
|
2725
2767
|
self, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50, query_text: Optional[str] = None
|
|
2726
2768
|
) -> List[str]:
|
|
@@ -2759,8 +2801,11 @@ class AgentManager:
|
|
|
2759
2801
|
results = [row[0] for row in result.all()]
|
|
2760
2802
|
return results
|
|
2761
2803
|
|
|
2804
|
+
@trace_method
|
|
2762
2805
|
async def get_context_window(self, agent_id: str, actor: PydanticUser) -> ContextWindowOverview:
|
|
2763
|
-
agent_state = await self.rebuild_system_prompt_async(
|
|
2806
|
+
agent_state, system_message, num_messages, num_archival_memories = await self.rebuild_system_prompt_async(
|
|
2807
|
+
agent_id=agent_id, actor=actor, force=True, dry_run=True
|
|
2808
|
+
)
|
|
2764
2809
|
calculator = ContextWindowCalculator()
|
|
2765
2810
|
|
|
2766
2811
|
if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION" and agent_state.llm_config.model_endpoint_type == "anthropic":
|
|
@@ -2776,5 +2821,7 @@ class AgentManager:
|
|
|
2776
2821
|
actor=actor,
|
|
2777
2822
|
token_counter=token_counter,
|
|
2778
2823
|
message_manager=self.message_manager,
|
|
2779
|
-
|
|
2824
|
+
system_message_compiled=system_message,
|
|
2825
|
+
num_archival_memories=num_archival_memories,
|
|
2826
|
+
num_messages=num_messages,
|
|
2780
2827
|
)
|