letta-nightly 0.7.30.dev20250603104343__py3-none-any.whl → 0.8.0.dev20250604201135__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.
Files changed (136) hide show
  1. letta/__init__.py +7 -1
  2. letta/agent.py +14 -7
  3. letta/agents/base_agent.py +1 -0
  4. letta/agents/ephemeral_summary_agent.py +104 -0
  5. letta/agents/helpers.py +35 -3
  6. letta/agents/letta_agent.py +492 -176
  7. letta/agents/letta_agent_batch.py +22 -16
  8. letta/agents/prompts/summary_system_prompt.txt +62 -0
  9. letta/agents/voice_agent.py +22 -7
  10. letta/agents/voice_sleeptime_agent.py +13 -8
  11. letta/constants.py +33 -1
  12. letta/data_sources/connectors.py +52 -36
  13. letta/errors.py +4 -0
  14. letta/functions/ast_parsers.py +13 -30
  15. letta/functions/function_sets/base.py +3 -1
  16. letta/functions/functions.py +2 -0
  17. letta/functions/mcp_client/base_client.py +151 -97
  18. letta/functions/mcp_client/sse_client.py +49 -31
  19. letta/functions/mcp_client/stdio_client.py +107 -106
  20. letta/functions/schema_generator.py +22 -22
  21. letta/groups/helpers.py +3 -4
  22. letta/groups/sleeptime_multi_agent.py +4 -4
  23. letta/groups/sleeptime_multi_agent_v2.py +22 -0
  24. letta/helpers/composio_helpers.py +16 -0
  25. letta/helpers/converters.py +20 -0
  26. letta/helpers/datetime_helpers.py +1 -6
  27. letta/helpers/tool_rule_solver.py +2 -1
  28. letta/interfaces/anthropic_streaming_interface.py +17 -2
  29. letta/interfaces/openai_chat_completions_streaming_interface.py +1 -0
  30. letta/interfaces/openai_streaming_interface.py +18 -2
  31. letta/llm_api/anthropic_client.py +24 -3
  32. letta/llm_api/google_ai_client.py +0 -15
  33. letta/llm_api/google_vertex_client.py +6 -5
  34. letta/llm_api/llm_client_base.py +15 -0
  35. letta/llm_api/openai.py +2 -2
  36. letta/llm_api/openai_client.py +60 -8
  37. letta/orm/__init__.py +2 -0
  38. letta/orm/agent.py +45 -43
  39. letta/orm/base.py +0 -2
  40. letta/orm/block.py +1 -0
  41. letta/orm/custom_columns.py +13 -0
  42. letta/orm/enums.py +5 -0
  43. letta/orm/file.py +3 -1
  44. letta/orm/files_agents.py +68 -0
  45. letta/orm/mcp_server.py +48 -0
  46. letta/orm/message.py +1 -0
  47. letta/orm/organization.py +11 -2
  48. letta/orm/passage.py +25 -10
  49. letta/orm/sandbox_config.py +5 -2
  50. letta/orm/sqlalchemy_base.py +171 -110
  51. letta/prompts/system/memgpt_base.txt +6 -1
  52. letta/prompts/system/memgpt_v2_chat.txt +57 -0
  53. letta/prompts/system/sleeptime.txt +2 -0
  54. letta/prompts/system/sleeptime_v2.txt +28 -0
  55. letta/schemas/agent.py +87 -20
  56. letta/schemas/block.py +7 -1
  57. letta/schemas/file.py +57 -0
  58. letta/schemas/mcp.py +74 -0
  59. letta/schemas/memory.py +5 -2
  60. letta/schemas/message.py +9 -0
  61. letta/schemas/openai/openai.py +0 -6
  62. letta/schemas/providers.py +33 -4
  63. letta/schemas/tool.py +26 -21
  64. letta/schemas/tool_execution_result.py +5 -0
  65. letta/server/db.py +23 -8
  66. letta/server/rest_api/app.py +73 -56
  67. letta/server/rest_api/interface.py +4 -4
  68. letta/server/rest_api/routers/v1/agents.py +132 -47
  69. letta/server/rest_api/routers/v1/blocks.py +3 -2
  70. letta/server/rest_api/routers/v1/embeddings.py +3 -3
  71. letta/server/rest_api/routers/v1/groups.py +3 -3
  72. letta/server/rest_api/routers/v1/jobs.py +14 -17
  73. letta/server/rest_api/routers/v1/organizations.py +10 -10
  74. letta/server/rest_api/routers/v1/providers.py +12 -10
  75. letta/server/rest_api/routers/v1/runs.py +3 -3
  76. letta/server/rest_api/routers/v1/sandbox_configs.py +12 -12
  77. letta/server/rest_api/routers/v1/sources.py +108 -43
  78. letta/server/rest_api/routers/v1/steps.py +8 -6
  79. letta/server/rest_api/routers/v1/tools.py +134 -95
  80. letta/server/rest_api/utils.py +12 -1
  81. letta/server/server.py +272 -73
  82. letta/services/agent_manager.py +246 -313
  83. letta/services/block_manager.py +30 -9
  84. letta/services/context_window_calculator/__init__.py +0 -0
  85. letta/services/context_window_calculator/context_window_calculator.py +150 -0
  86. letta/services/context_window_calculator/token_counter.py +82 -0
  87. letta/services/file_processor/__init__.py +0 -0
  88. letta/services/file_processor/chunker/__init__.py +0 -0
  89. letta/services/file_processor/chunker/llama_index_chunker.py +29 -0
  90. letta/services/file_processor/embedder/__init__.py +0 -0
  91. letta/services/file_processor/embedder/openai_embedder.py +84 -0
  92. letta/services/file_processor/file_processor.py +123 -0
  93. letta/services/file_processor/parser/__init__.py +0 -0
  94. letta/services/file_processor/parser/base_parser.py +9 -0
  95. letta/services/file_processor/parser/mistral_parser.py +54 -0
  96. letta/services/file_processor/types.py +0 -0
  97. letta/services/files_agents_manager.py +184 -0
  98. letta/services/group_manager.py +118 -0
  99. letta/services/helpers/agent_manager_helper.py +76 -21
  100. letta/services/helpers/tool_execution_helper.py +3 -0
  101. letta/services/helpers/tool_parser_helper.py +100 -0
  102. letta/services/identity_manager.py +44 -42
  103. letta/services/job_manager.py +21 -10
  104. letta/services/mcp/base_client.py +5 -2
  105. letta/services/mcp/sse_client.py +3 -5
  106. letta/services/mcp/stdio_client.py +3 -5
  107. letta/services/mcp_manager.py +281 -0
  108. letta/services/message_manager.py +40 -26
  109. letta/services/organization_manager.py +55 -19
  110. letta/services/passage_manager.py +211 -13
  111. letta/services/provider_manager.py +48 -2
  112. letta/services/sandbox_config_manager.py +105 -0
  113. letta/services/source_manager.py +4 -5
  114. letta/services/step_manager.py +9 -6
  115. letta/services/summarizer/summarizer.py +50 -23
  116. letta/services/telemetry_manager.py +7 -0
  117. letta/services/tool_executor/tool_execution_manager.py +11 -52
  118. letta/services/tool_executor/tool_execution_sandbox.py +4 -34
  119. letta/services/tool_executor/tool_executor.py +107 -105
  120. letta/services/tool_manager.py +56 -17
  121. letta/services/tool_sandbox/base.py +39 -92
  122. letta/services/tool_sandbox/e2b_sandbox.py +16 -11
  123. letta/services/tool_sandbox/local_sandbox.py +51 -23
  124. letta/services/user_manager.py +36 -3
  125. letta/settings.py +10 -3
  126. letta/templates/__init__.py +0 -0
  127. letta/templates/sandbox_code_file.py.j2 +47 -0
  128. letta/templates/template_helper.py +16 -0
  129. letta/tracing.py +30 -1
  130. letta/types/__init__.py +7 -0
  131. letta/utils.py +25 -1
  132. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/METADATA +7 -2
  133. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/RECORD +136 -110
  134. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/LICENSE +0 -0
  135. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/WHEEL +0 -0
  136. {letta_nightly-0.7.30.dev20250603104343.dist-info → letta_nightly-0.8.0.dev20250604201135.dist-info}/entry_points.txt +0 -0
@@ -5,12 +5,12 @@ from typing import Dict, List, Optional, Set, Tuple
5
5
 
6
6
  import numpy as np
7
7
  import sqlalchemy as sa
8
- from openai.types.beta.function_tool import FunctionTool as OpenAITool
9
8
  from sqlalchemy import Select, and_, delete, func, insert, literal, or_, select, union_all
10
9
  from sqlalchemy.dialects.postgresql import insert as pg_insert
11
10
 
12
11
  from letta.constants import (
13
12
  BASE_MEMORY_TOOLS,
13
+ BASE_MEMORY_TOOLS_V2,
14
14
  BASE_SLEEPTIME_CHAT_TOOLS,
15
15
  BASE_SLEEPTIME_TOOLS,
16
16
  BASE_TOOLS,
@@ -42,13 +42,13 @@ from letta.orm.sqlalchemy_base import AccessType
42
42
  from letta.orm.sqlite_functions import adapt_array
43
43
  from letta.schemas.agent import AgentState as PydanticAgentState
44
44
  from letta.schemas.agent import AgentType, CreateAgent, UpdateAgent, get_prompt_template_for_agent_type
45
+ from letta.schemas.block import DEFAULT_BLOCKS
45
46
  from letta.schemas.block import Block as PydanticBlock
46
47
  from letta.schemas.block import BlockUpdate
47
48
  from letta.schemas.embedding_config import EmbeddingConfig
48
- from letta.schemas.enums import MessageRole, ProviderType
49
+ from letta.schemas.enums import ProviderType
49
50
  from letta.schemas.group import Group as PydanticGroup
50
51
  from letta.schemas.group import ManagerType
51
- from letta.schemas.letta_message_content import TextContent
52
52
  from letta.schemas.memory import ContextWindowOverview, Memory
53
53
  from letta.schemas.message import Message
54
54
  from letta.schemas.message import Message as PydanticMessage
@@ -64,6 +64,8 @@ from letta.serialize_schemas.marshmallow_tool import SerializedToolSchema
64
64
  from letta.serialize_schemas.pydantic_agent_schema import AgentSchema
65
65
  from letta.server.db import db_registry
66
66
  from letta.services.block_manager import BlockManager
67
+ from letta.services.context_window_calculator.context_window_calculator import ContextWindowCalculator
68
+ from letta.services.context_window_calculator.token_counter import AnthropicTokenCounter, TiktokenCounter
67
69
  from letta.services.helpers.agent_manager_helper import (
68
70
  _apply_filters,
69
71
  _apply_identity_filters,
@@ -71,6 +73,7 @@ from letta.services.helpers.agent_manager_helper import (
71
73
  _apply_pagination_async,
72
74
  _apply_tag_filter,
73
75
  _process_relationship,
76
+ _process_relationship_async,
74
77
  check_supports_structured_output,
75
78
  compile_system_message,
76
79
  derive_system_message,
@@ -84,7 +87,7 @@ from letta.services.source_manager import SourceManager
84
87
  from letta.services.tool_manager import ToolManager
85
88
  from letta.settings import settings
86
89
  from letta.tracing import trace_method
87
- from letta.utils import count_tokens, enforce_types, united_diff
90
+ from letta.utils import enforce_types, united_diff
88
91
 
89
92
  logger = get_logger(__name__)
90
93
 
@@ -255,6 +258,8 @@ class AgentManager:
255
258
  tool_names |= set(BASE_SLEEPTIME_TOOLS)
256
259
  elif agent_create.enable_sleeptime:
257
260
  tool_names |= set(BASE_SLEEPTIME_CHAT_TOOLS)
261
+ elif agent_create.agent_type == AgentType.memgpt_v2_agent:
262
+ tool_names |= set(BASE_TOOLS + BASE_MEMORY_TOOLS_V2)
258
263
  else:
259
264
  tool_names |= set(BASE_TOOLS + BASE_MEMORY_TOOLS)
260
265
  if agent_create.include_multi_agent_tools:
@@ -386,8 +391,19 @@ class AgentManager:
386
391
  # blocks
387
392
  block_ids = list(agent_create.block_ids or [])
388
393
  if agent_create.memory_blocks:
394
+
389
395
  pydantic_blocks = [PydanticBlock(**b.model_dump(to_orm=True)) for b in agent_create.memory_blocks]
390
- created_blocks = self.block_manager.batch_create_blocks(
396
+
397
+ # Inject a description for the default blocks if the user didn't specify them
398
+ # Used for `persona`, `human`, etc
399
+ default_blocks = {block.label: block for block in DEFAULT_BLOCKS}
400
+ for block in pydantic_blocks:
401
+ if block.label in default_blocks:
402
+ if block.description is None:
403
+ block.description = default_blocks[block.label].description
404
+
405
+ # Actually create the blocks
406
+ created_blocks = await self.block_manager.batch_create_blocks_async(
391
407
  pydantic_blocks,
392
408
  actor=actor,
393
409
  )
@@ -404,6 +420,8 @@ class AgentManager:
404
420
  tool_names |= set(BASE_SLEEPTIME_TOOLS)
405
421
  elif agent_create.enable_sleeptime:
406
422
  tool_names |= set(BASE_SLEEPTIME_CHAT_TOOLS)
423
+ elif agent_create.agent_type == AgentType.memgpt_v2_agent:
424
+ tool_names |= set(BASE_TOOLS + BASE_MEMORY_TOOLS_V2)
407
425
  else:
408
426
  tool_names |= set(BASE_TOOLS + BASE_MEMORY_TOOLS)
409
427
  if agent_create.include_multi_agent_tools:
@@ -433,7 +451,7 @@ class AgentManager:
433
451
  for tn in tool_names:
434
452
  if tn in {"send_message", "send_message_to_agent_async", "memory_finish_edits"}:
435
453
  tool_rules.append(TerminalToolRule(tool_name=tn))
436
- elif tn in (BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_SLEEPTIME_TOOLS):
454
+ elif tn in (BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_MEMORY_TOOLS_V2 + BASE_SLEEPTIME_TOOLS):
437
455
  tool_rules.append(ContinueToolRule(tool_name=tn))
438
456
 
439
457
  if tool_rules:
@@ -520,11 +538,10 @@ class AgentManager:
520
538
  new_agent.message_ids = [msg.id for msg in init_messages]
521
539
 
522
540
  await session.refresh(new_agent)
541
+ result = await new_agent.to_pydantic_async()
523
542
 
524
- # Using the synchronous version since we don't have an async version yet
525
- # If you implement an async version of create_many_messages, you can switch to that
526
543
  await self.message_manager.create_many_messages_async(pydantic_msgs=init_messages, actor=actor)
527
- return await new_agent.to_pydantic_async()
544
+ return result
528
545
 
529
546
  @enforce_types
530
547
  def _generate_initial_message_sequence(
@@ -561,6 +578,14 @@ class AgentManager:
561
578
  init_messages = self._generate_initial_message_sequence(actor, agent_state, initial_message_sequence)
562
579
  return self.append_to_in_context_messages(init_messages, agent_id=agent_state.id, actor=actor)
563
580
 
581
+ @trace_method
582
+ @enforce_types
583
+ async def append_initial_message_sequence_to_in_context_messages_async(
584
+ self, actor: PydanticUser, agent_state: PydanticAgentState, initial_message_sequence: Optional[List[MessageCreate]] = None
585
+ ) -> PydanticAgentState:
586
+ init_messages = self._generate_initial_message_sequence(actor, agent_state, initial_message_sequence)
587
+ return await self.append_to_in_context_messages_async(init_messages, agent_id=agent_state.id, actor=actor)
588
+
564
589
  @trace_method
565
590
  @enforce_types
566
591
  def update_agent(
@@ -962,6 +987,50 @@ class AgentManager:
962
987
 
963
988
  return list(session.execute(query).scalars())
964
989
 
990
+ @enforce_types
991
+ @trace_method
992
+ async def list_agents_matching_tags_async(
993
+ self,
994
+ actor: PydanticUser,
995
+ match_all: List[str],
996
+ match_some: List[str],
997
+ limit: Optional[int] = 50,
998
+ ) -> List[PydanticAgentState]:
999
+ """
1000
+ Retrieves agents in the same organization that match all specified `match_all` tags
1001
+ and at least one tag from `match_some`. The query is optimized for efficiency by
1002
+ leveraging indexed filtering and aggregation.
1003
+
1004
+ Args:
1005
+ actor (PydanticUser): The user requesting the agent list.
1006
+ match_all (List[str]): Agents must have all these tags.
1007
+ match_some (List[str]): Agents must have at least one of these tags.
1008
+ limit (Optional[int]): Maximum number of agents to return.
1009
+
1010
+ Returns:
1011
+ List[PydanticAgentState: The filtered list of matching agents.
1012
+ """
1013
+ async with db_registry.async_session() as session:
1014
+ query = select(AgentModel).where(AgentModel.organization_id == actor.organization_id)
1015
+
1016
+ if match_all:
1017
+ # Subquery to find agent IDs that contain all match_all tags
1018
+ subquery = (
1019
+ select(AgentsTags.agent_id)
1020
+ .where(AgentsTags.tag.in_(match_all))
1021
+ .group_by(AgentsTags.agent_id)
1022
+ .having(func.count(AgentsTags.tag) == literal(len(match_all)))
1023
+ )
1024
+ query = query.where(AgentModel.id.in_(subquery))
1025
+
1026
+ if match_some:
1027
+ # Ensures agents match at least one tag in match_some
1028
+ query = query.join(AgentsTags).where(AgentsTags.tag.in_(match_some))
1029
+
1030
+ query = query.distinct(AgentModel.id).order_by(AgentModel.id).limit(limit)
1031
+ result = await session.execute(query)
1032
+ return await asyncio.gather(*[agent.to_pydantic_async() for agent in result.scalars()])
1033
+
965
1034
  @trace_method
966
1035
  def size(
967
1036
  self,
@@ -1286,12 +1355,6 @@ class AgentManager:
1286
1355
  message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
1287
1356
  return self.message_manager.get_messages_by_ids(message_ids=message_ids, actor=actor)
1288
1357
 
1289
- @trace_method
1290
- @enforce_types
1291
- async def get_in_context_messages_async(self, agent_id: str, actor: PydanticUser) -> List[PydanticMessage]:
1292
- agent = await self.get_agent_by_id_async(agent_id=agent_id, include_relationships=[], actor=actor)
1293
- return await self.message_manager.get_messages_by_ids_async(message_ids=agent.message_ids, actor=actor)
1294
-
1295
1358
  @trace_method
1296
1359
  @enforce_types
1297
1360
  def get_system_message(self, agent_id: str, actor: PydanticUser) -> PydanticMessage:
@@ -1349,7 +1412,6 @@ class AgentManager:
1349
1412
  system_prompt=agent_state.system,
1350
1413
  in_context_memory=agent_state.memory,
1351
1414
  in_context_memory_last_edit=memory_edit_timestamp,
1352
- recent_passages=self.list_passages(actor=actor, agent_id=agent_id, ascending=False, limit=10),
1353
1415
  previous_message_count=num_messages,
1354
1416
  archival_memory_size=num_archival_memories,
1355
1417
  )
@@ -1417,7 +1479,6 @@ class AgentManager:
1417
1479
  system_prompt=agent_state.system,
1418
1480
  in_context_memory=agent_state.memory,
1419
1481
  in_context_memory_last_edit=memory_edit_timestamp,
1420
- recent_passages=self.list_passages(actor=actor, agent_id=agent_id, ascending=False, limit=10),
1421
1482
  previous_message_count=num_messages,
1422
1483
  archival_memory_size=num_archival_memories,
1423
1484
  )
@@ -1484,7 +1545,20 @@ class AgentManager:
1484
1545
 
1485
1546
  @trace_method
1486
1547
  @enforce_types
1487
- def reset_messages(self, agent_id: str, actor: PydanticUser, add_default_initial_messages: bool = False) -> PydanticAgentState:
1548
+ async def append_to_in_context_messages_async(
1549
+ self, messages: List[PydanticMessage], agent_id: str, actor: PydanticUser
1550
+ ) -> PydanticAgentState:
1551
+ messages = await self.message_manager.create_many_messages_async(messages, actor=actor)
1552
+ agent = await self.get_agent_by_id_async(agent_id=agent_id, actor=actor)
1553
+ message_ids = agent.message_ids or []
1554
+ message_ids += [m.id for m in messages]
1555
+ return await self.set_in_context_messages_async(agent_id=agent_id, message_ids=message_ids, actor=actor)
1556
+
1557
+ @trace_method
1558
+ @enforce_types
1559
+ async def reset_messages_async(
1560
+ self, agent_id: str, actor: PydanticUser, add_default_initial_messages: bool = False
1561
+ ) -> PydanticAgentState:
1488
1562
  """
1489
1563
  Removes all in-context messages for the specified agent by:
1490
1564
  1) Clearing the agent.messages relationship (which cascades delete-orphans).
@@ -1501,22 +1575,22 @@ class AgentManager:
1501
1575
  Returns:
1502
1576
  PydanticAgentState: The updated agent state with no linked messages.
1503
1577
  """
1504
- with db_registry.session() as session:
1578
+ async with db_registry.async_session() as session:
1505
1579
  # Retrieve the existing agent (will raise NoResultFound if invalid)
1506
- agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
1580
+ agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
1507
1581
 
1508
1582
  # Also clear out the message_ids field to keep in-context memory consistent
1509
1583
  agent.message_ids = []
1510
1584
 
1511
1585
  # Commit the update
1512
- agent.update(db_session=session, actor=actor)
1586
+ await agent.update_async(db_session=session, actor=actor)
1513
1587
 
1514
- agent_state = agent.to_pydantic()
1588
+ agent_state = await agent.to_pydantic_async()
1515
1589
 
1516
- self.message_manager.delete_all_messages_for_agent(agent_id=agent_id, actor=actor)
1590
+ await self.message_manager.delete_all_messages_for_agent_async(agent_id=agent_id, actor=actor)
1517
1591
 
1518
1592
  if add_default_initial_messages:
1519
- return self.append_initial_message_sequence_to_in_context_messages(actor, agent_state)
1593
+ return await self.append_initial_message_sequence_to_in_context_messages_async(actor, agent_state)
1520
1594
  else:
1521
1595
  # We still want to always have a system message
1522
1596
  init_messages = initialize_message_sequence(
@@ -1527,12 +1601,11 @@ class AgentManager:
1527
1601
  model=agent_state.llm_config.model,
1528
1602
  openai_message_dict=init_messages[0],
1529
1603
  )
1530
- return self.append_to_in_context_messages([system_message], agent_id=agent_state.id, actor=actor)
1604
+ return await self.append_to_in_context_messages_async([system_message], agent_id=agent_state.id, actor=actor)
1531
1605
 
1532
- # TODO: I moved this from agent.py - replace all mentions of this with the agent_manager version
1533
1606
  @trace_method
1534
1607
  @enforce_types
1535
- def update_memory_if_changed(self, agent_id: str, new_memory: Memory, actor: PydanticUser) -> PydanticAgentState:
1608
+ async def update_memory_if_changed_async(self, agent_id: str, new_memory: Memory, actor: PydanticUser) -> PydanticAgentState:
1536
1609
  """
1537
1610
  Update internal memory object and system prompt if there have been modifications.
1538
1611
 
@@ -1544,8 +1617,8 @@ class AgentManager:
1544
1617
  Returns:
1545
1618
  modified (bool): whether the memory was updated
1546
1619
  """
1547
- agent_state = self.get_agent_by_id(agent_id=agent_id, actor=actor)
1548
- system_message = self.message_manager.get_message_by_id(message_id=agent_state.message_ids[0], actor=actor)
1620
+ agent_state = await self.get_agent_by_id_async(agent_id=agent_id, actor=actor)
1621
+ system_message = await self.message_manager.get_message_by_id_async(message_id=agent_state.message_ids[0], actor=actor)
1549
1622
  if new_memory.compile() not in system_message.content[0].text:
1550
1623
  # update the blocks (LRW) in the DB
1551
1624
  for label in agent_state.memory.list_block_labels():
@@ -1553,18 +1626,24 @@ class AgentManager:
1553
1626
  if updated_value != agent_state.memory.get_block(label).value:
1554
1627
  # update the block if it's changed
1555
1628
  block_id = agent_state.memory.get_block(label).id
1556
- self.block_manager.update_block(block_id=block_id, block_update=BlockUpdate(value=updated_value), actor=actor)
1629
+ await self.block_manager.update_block_async(
1630
+ block_id=block_id, block_update=BlockUpdate(value=updated_value), actor=actor
1631
+ )
1557
1632
 
1558
1633
  # refresh memory from DB (using block ids)
1634
+ blocks = await asyncio.gather(
1635
+ *[self.block_manager.get_block_by_id_async(block.id, actor=actor) for block in agent_state.memory.get_blocks()]
1636
+ )
1559
1637
  agent_state.memory = Memory(
1560
- blocks=[self.block_manager.get_block_by_id(block.id, actor=actor) for block in agent_state.memory.get_blocks()],
1638
+ blocks=blocks,
1639
+ file_blocks=agent_state.memory.file_blocks,
1561
1640
  prompt_template=get_prompt_template_for_agent_type(agent_state.agent_type),
1562
1641
  )
1563
1642
 
1564
1643
  # NOTE: don't do this since re-buildin the memory is handled at the start of the step
1565
1644
  # rebuild memory - this records the last edited timestamp of the memory
1566
1645
  # TODO: pass in update timestamp from block edit time
1567
- agent_state = self.rebuild_system_prompt(agent_id=agent_id, actor=actor)
1646
+ agent_state = await self.rebuild_system_prompt_async(agent_id=agent_id, actor=actor)
1568
1647
 
1569
1648
  return agent_state
1570
1649
 
@@ -1575,9 +1654,8 @@ class AgentManager:
1575
1654
  if not block_ids:
1576
1655
  return agent_state
1577
1656
 
1578
- agent_state.memory.blocks = await self.block_manager.get_all_blocks_by_ids_async(
1579
- block_ids=[b.id for b in agent_state.memory.blocks], actor=actor
1580
- )
1657
+ blocks = await self.block_manager.get_all_blocks_by_ids_async(block_ids=[b.id for b in agent_state.memory.blocks], actor=actor)
1658
+ agent_state.memory.blocks = [b for b in blocks if b is not None]
1581
1659
  return agent_state
1582
1660
 
1583
1661
  # ======================================================================================================================
@@ -1585,7 +1663,7 @@ class AgentManager:
1585
1663
  # ======================================================================================================================
1586
1664
  @trace_method
1587
1665
  @enforce_types
1588
- def attach_source(self, agent_id: str, source_id: str, actor: PydanticUser) -> PydanticAgentState:
1666
+ async def attach_source_async(self, agent_id: str, source_id: str, actor: PydanticUser) -> PydanticAgentState:
1589
1667
  """
1590
1668
  Attaches a source to an agent.
1591
1669
 
@@ -1599,12 +1677,12 @@ class AgentManager:
1599
1677
  IntegrityError: If the source is already attached to the agent
1600
1678
  """
1601
1679
 
1602
- with db_registry.session() as session:
1680
+ async with db_registry.async_session() as session:
1603
1681
  # Verify both agent and source exist and user has permission to access them
1604
- agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
1682
+ agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
1605
1683
 
1606
1684
  # The _process_relationship helper already handles duplicate checking via unique constraint
1607
- _process_relationship(
1685
+ await _process_relationship_async(
1608
1686
  session=session,
1609
1687
  agent=agent,
1610
1688
  relationship_name="sources",
@@ -1615,18 +1693,18 @@ class AgentManager:
1615
1693
  )
1616
1694
 
1617
1695
  # Commit the changes
1618
- agent.update(session, actor=actor)
1696
+ await agent.update_async(session, actor=actor)
1619
1697
 
1620
1698
  # Force rebuild of system prompt so that the agent is updated with passage count
1621
1699
  # and recent passages and add system message alert to agent
1622
- self.rebuild_system_prompt(agent_id=agent_id, actor=actor, force=True)
1623
- self.append_system_message(
1700
+ pydantic_agent = await self.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True)
1701
+ await self.append_system_message_async(
1624
1702
  agent_id=agent_id,
1625
1703
  content=DATA_SOURCE_ATTACH_ALERT,
1626
1704
  actor=actor,
1627
1705
  )
1628
1706
 
1629
- return agent.to_pydantic()
1707
+ return pydantic_agent
1630
1708
 
1631
1709
  @trace_method
1632
1710
  @enforce_types
@@ -1641,6 +1719,19 @@ class AgentManager:
1641
1719
  # update agent in-context message IDs
1642
1720
  self.append_to_in_context_messages(messages=[message], agent_id=agent_id, actor=actor)
1643
1721
 
1722
+ @trace_method
1723
+ @enforce_types
1724
+ async def append_system_message_async(self, agent_id: str, content: str, actor: PydanticUser):
1725
+
1726
+ # get the agent
1727
+ agent = await self.get_agent_by_id_async(agent_id=agent_id, actor=actor)
1728
+ message = PydanticMessage.dict_to_message(
1729
+ agent_id=agent.id, model=agent.llm_config.model, openai_message_dict={"role": "system", "content": content}
1730
+ )
1731
+
1732
+ # update agent in-context message IDs
1733
+ await self.append_to_in_context_messages_async(messages=[message], agent_id=agent_id, actor=actor)
1734
+
1644
1735
  @trace_method
1645
1736
  @enforce_types
1646
1737
  def list_attached_sources(self, agent_id: str, actor: PydanticUser) -> List[PydanticSource]:
@@ -1709,6 +1800,34 @@ class AgentManager:
1709
1800
  agent.update(session, actor=actor)
1710
1801
  return agent.to_pydantic()
1711
1802
 
1803
+ @trace_method
1804
+ @enforce_types
1805
+ async def detach_source_async(self, agent_id: str, source_id: str, actor: PydanticUser) -> PydanticAgentState:
1806
+ """
1807
+ Detaches a source from an agent.
1808
+
1809
+ Args:
1810
+ agent_id: ID of the agent to detach the source from
1811
+ source_id: ID of the source to detach
1812
+ actor: User performing the action
1813
+ """
1814
+ async with db_registry.async_session() as session:
1815
+ # Verify agent exists and user has permission to access it
1816
+ agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
1817
+
1818
+ # Remove the source from the relationship
1819
+ remaining_sources = [s for s in agent.sources if s.id != source_id]
1820
+
1821
+ if len(remaining_sources) == len(agent.sources): # Source ID was not in the relationship
1822
+ logger.warning(f"Attempted to remove unattached source id={source_id} from agent id={agent_id} by actor={actor}")
1823
+
1824
+ # Update the sources relationship
1825
+ agent.sources = remaining_sources
1826
+
1827
+ # Commit the changes
1828
+ await agent.update_async(session, actor=actor)
1829
+ return await agent.to_pydantic_async()
1830
+
1712
1831
  # ======================================================================================================================
1713
1832
  # Block management
1714
1833
  # ======================================================================================================================
@@ -2303,6 +2422,42 @@ class AgentManager:
2303
2422
  agent.update(session, actor=actor)
2304
2423
  return agent.to_pydantic()
2305
2424
 
2425
+ @trace_method
2426
+ @enforce_types
2427
+ async def attach_tool_async(self, agent_id: str, tool_id: str, actor: PydanticUser) -> PydanticAgentState:
2428
+ """
2429
+ Attaches a tool to an agent.
2430
+
2431
+ Args:
2432
+ agent_id: ID of the agent to attach the tool to.
2433
+ tool_id: ID of the tool to attach.
2434
+ actor: User performing the action.
2435
+
2436
+ Raises:
2437
+ NoResultFound: If the agent or tool is not found.
2438
+
2439
+ Returns:
2440
+ PydanticAgentState: The updated agent state.
2441
+ """
2442
+ async with db_registry.async_session() as session:
2443
+ # Verify the agent exists and user has permission to access it
2444
+ agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
2445
+
2446
+ # Use the _process_relationship helper to attach the tool
2447
+ await _process_relationship_async(
2448
+ session=session,
2449
+ agent=agent,
2450
+ relationship_name="tools",
2451
+ model_class=ToolModel,
2452
+ item_ids=[tool_id],
2453
+ allow_partial=False, # Ensure the tool exists
2454
+ replace=False, # Extend the existing tools
2455
+ )
2456
+
2457
+ # Commit and refresh the agent
2458
+ await agent.update_async(session, actor=actor)
2459
+ return await agent.to_pydantic_async()
2460
+
2306
2461
  @trace_method
2307
2462
  @enforce_types
2308
2463
  def detach_tool(self, agent_id: str, tool_id: str, actor: PydanticUser) -> PydanticAgentState:
@@ -2337,6 +2492,40 @@ class AgentManager:
2337
2492
  agent.update(session, actor=actor)
2338
2493
  return agent.to_pydantic()
2339
2494
 
2495
+ @trace_method
2496
+ @enforce_types
2497
+ async def detach_tool_async(self, agent_id: str, tool_id: str, actor: PydanticUser) -> PydanticAgentState:
2498
+ """
2499
+ Detaches a tool from an agent.
2500
+
2501
+ Args:
2502
+ agent_id: ID of the agent to detach the tool from.
2503
+ tool_id: ID of the tool to detach.
2504
+ actor: User performing the action.
2505
+
2506
+ Raises:
2507
+ NoResultFound: If the agent or tool is not found.
2508
+
2509
+ Returns:
2510
+ PydanticAgentState: The updated agent state.
2511
+ """
2512
+ async with db_registry.async_session() as session:
2513
+ # Verify the agent exists and user has permission to access it
2514
+ agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
2515
+
2516
+ # Filter out the tool to be detached
2517
+ remaining_tools = [tool for tool in agent.tools if tool.id != tool_id]
2518
+
2519
+ if len(remaining_tools) == len(agent.tools): # Tool ID was not in the relationship
2520
+ logger.warning(f"Attempted to remove unattached tool id={tool_id} from agent id={agent_id} by actor={actor}")
2521
+
2522
+ # Update the tools relationship
2523
+ agent.tools = remaining_tools
2524
+
2525
+ # Commit and refresh the agent
2526
+ await agent.update_async(session, actor=actor)
2527
+ return await agent.to_pydantic_async()
2528
+
2340
2529
  @trace_method
2341
2530
  @enforce_types
2342
2531
  def list_attached_tools(self, agent_id: str, actor: PydanticUser) -> List[PydanticTool]:
@@ -2430,280 +2619,24 @@ class AgentManager:
2430
2619
  result = await session.execute(query)
2431
2620
  # Extract the tag values from the result
2432
2621
  results = [row[0] for row in result.all()]
2433
- return results
2622
+ return results
2434
2623
 
2435
2624
  async def get_context_window(self, agent_id: str, actor: PydanticUser) -> ContextWindowOverview:
2436
- if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION":
2437
- return await self.get_context_window_from_anthropic_async(agent_id=agent_id, actor=actor)
2438
- return await self.get_context_window_from_tiktoken_async(agent_id=agent_id, actor=actor)
2439
-
2440
- async def get_context_window_from_anthropic_async(self, agent_id: str, actor: PydanticUser) -> ContextWindowOverview:
2441
- """Get the context window of the agent"""
2442
- agent_state = await self.get_agent_by_id_async(agent_id=agent_id, actor=actor)
2443
- anthropic_client = LLMClient.create(provider_type=ProviderType.anthropic, actor=actor)
2444
- model = agent_state.llm_config.model if agent_state.llm_config.model_endpoint_type == "anthropic" else None
2445
-
2446
- # Grab the in-context messages
2447
- # conversion of messages to anthropic dict format, which is passed to the token counter
2448
- (in_context_messages, passage_manager_size, message_manager_size) = await asyncio.gather(
2449
- self.get_in_context_messages_async(agent_id=agent_id, actor=actor),
2450
- self.passage_manager.size_async(actor=actor, agent_id=agent_id),
2451
- self.message_manager.size_async(actor=actor, agent_id=agent_id),
2452
- )
2453
- in_context_messages_anthropic = [m.to_anthropic_dict() for m in in_context_messages]
2454
-
2455
- # Extract system, memory and external summary
2456
- if (
2457
- len(in_context_messages) > 0
2458
- and in_context_messages[0].role == MessageRole.system
2459
- and in_context_messages[0].content
2460
- and len(in_context_messages[0].content) == 1
2461
- and isinstance(in_context_messages[0].content[0], TextContent)
2462
- ):
2463
- system_message = in_context_messages[0].content[0].text
2464
-
2465
- external_memory_marker_pos = system_message.find("###")
2466
- core_memory_marker_pos = system_message.find("<", external_memory_marker_pos)
2467
- if external_memory_marker_pos != -1 and core_memory_marker_pos != -1:
2468
- system_prompt = system_message[:external_memory_marker_pos].strip()
2469
- external_memory_summary = system_message[external_memory_marker_pos:core_memory_marker_pos].strip()
2470
- core_memory = system_message[core_memory_marker_pos:].strip()
2471
- else:
2472
- # if no markers found, put everything in system message
2473
- system_prompt = system_message
2474
- external_memory_summary = None
2475
- core_memory = None
2476
- else:
2477
- # if no system message, fall back on agent's system prompt
2478
- system_prompt = agent_state.system
2479
- external_memory_summary = None
2480
- core_memory = None
2481
-
2482
- num_tokens_system_coroutine = anthropic_client.count_tokens(model=model, messages=[{"role": "user", "content": system_prompt}])
2483
- num_tokens_core_memory_coroutine = (
2484
- anthropic_client.count_tokens(model=model, messages=[{"role": "user", "content": core_memory}])
2485
- if core_memory
2486
- else asyncio.sleep(0, result=0)
2487
- )
2488
- num_tokens_external_memory_summary_coroutine = (
2489
- anthropic_client.count_tokens(model=model, messages=[{"role": "user", "content": external_memory_summary}])
2490
- if external_memory_summary
2491
- else asyncio.sleep(0, result=0)
2492
- )
2625
+ agent_state = await self.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True)
2626
+ calculator = ContextWindowCalculator()
2493
2627
 
2494
- # Check if there's a summary message in the message queue
2495
- if (
2496
- len(in_context_messages) > 1
2497
- and in_context_messages[1].role == MessageRole.user
2498
- and in_context_messages[1].content
2499
- and len(in_context_messages[1].content) == 1
2500
- and isinstance(in_context_messages[1].content[0], TextContent)
2501
- # TODO remove hardcoding
2502
- and "The following is a summary of the previous " in in_context_messages[1].content[0].text
2503
- ):
2504
- # Summary message exists
2505
- text_content = in_context_messages[1].content[0].text
2506
- assert text_content is not None
2507
- summary_memory = text_content
2508
- num_tokens_summary_memory_coroutine = anthropic_client.count_tokens(
2509
- model=model, messages=[{"role": "user", "content": summary_memory}]
2510
- )
2511
- # with a summary message, the real messages start at index 2
2512
- num_tokens_messages_coroutine = (
2513
- anthropic_client.count_tokens(model=model, messages=in_context_messages_anthropic[2:])
2514
- if len(in_context_messages_anthropic) > 2
2515
- else asyncio.sleep(0, result=0)
2516
- )
2517
-
2518
- else:
2519
- summary_memory = None
2520
- num_tokens_summary_memory_coroutine = asyncio.sleep(0, result=0)
2521
- # with no summary message, the real messages start at index 1
2522
- num_tokens_messages_coroutine = (
2523
- anthropic_client.count_tokens(model=model, messages=in_context_messages_anthropic[1:])
2524
- if len(in_context_messages_anthropic) > 1
2525
- else asyncio.sleep(0, result=0)
2526
- )
2628
+ if os.getenv("LETTA_ENVIRONMENT") == "PRODUCTION" and agent_state.llm_config.model_endpoint_type == "anthropic":
2629
+ anthropic_client = LLMClient.create(provider_type=ProviderType.anthropic, actor=actor)
2630
+ model = agent_state.llm_config.model if agent_state.llm_config.model_endpoint_type == "anthropic" else None
2527
2631
 
2528
- # tokens taken up by function definitions
2529
- if agent_state.tools and len(agent_state.tools) > 0:
2530
- available_functions_definitions = [OpenAITool(type="function", function=f.json_schema) for f in agent_state.tools]
2531
- num_tokens_available_functions_definitions_coroutine = anthropic_client.count_tokens(
2532
- model=model,
2533
- tools=available_functions_definitions,
2534
- )
2632
+ token_counter = AnthropicTokenCounter(anthropic_client, model) # noqa
2535
2633
  else:
2536
- available_functions_definitions = []
2537
- num_tokens_available_functions_definitions_coroutine = asyncio.sleep(0, result=0)
2538
-
2539
- (
2540
- num_tokens_system,
2541
- num_tokens_core_memory,
2542
- num_tokens_external_memory_summary,
2543
- num_tokens_summary_memory,
2544
- num_tokens_messages,
2545
- num_tokens_available_functions_definitions,
2546
- ) = await asyncio.gather(
2547
- num_tokens_system_coroutine,
2548
- num_tokens_core_memory_coroutine,
2549
- num_tokens_external_memory_summary_coroutine,
2550
- num_tokens_summary_memory_coroutine,
2551
- num_tokens_messages_coroutine,
2552
- num_tokens_available_functions_definitions_coroutine,
2553
- )
2554
-
2555
- num_tokens_used_total = (
2556
- num_tokens_system # system prompt
2557
- + num_tokens_available_functions_definitions # function definitions
2558
- + num_tokens_core_memory # core memory
2559
- + num_tokens_external_memory_summary # metadata (statistics) about recall/archival
2560
- + num_tokens_summary_memory # summary of ongoing conversation
2561
- + num_tokens_messages # tokens taken by messages
2562
- )
2563
- assert isinstance(num_tokens_used_total, int)
2564
-
2565
- return ContextWindowOverview(
2566
- # context window breakdown (in messages)
2567
- num_messages=len(in_context_messages),
2568
- num_archival_memory=passage_manager_size,
2569
- num_recall_memory=message_manager_size,
2570
- num_tokens_external_memory_summary=num_tokens_external_memory_summary,
2571
- external_memory_summary=external_memory_summary,
2572
- # top-level information
2573
- context_window_size_max=agent_state.llm_config.context_window,
2574
- context_window_size_current=num_tokens_used_total,
2575
- # context window breakdown (in tokens)
2576
- num_tokens_system=num_tokens_system,
2577
- system_prompt=system_prompt,
2578
- num_tokens_core_memory=num_tokens_core_memory,
2579
- core_memory=core_memory,
2580
- num_tokens_summary_memory=num_tokens_summary_memory,
2581
- summary_memory=summary_memory,
2582
- num_tokens_messages=num_tokens_messages,
2583
- messages=in_context_messages,
2584
- # related to functions
2585
- num_tokens_functions_definitions=num_tokens_available_functions_definitions,
2586
- functions_definitions=available_functions_definitions,
2587
- )
2588
-
2589
- async def get_context_window_from_tiktoken_async(self, agent_id: str, actor: PydanticUser) -> ContextWindowOverview:
2590
- """Get the context window of the agent"""
2591
- from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
2634
+ token_counter = TiktokenCounter(agent_state.llm_config.model)
2592
2635
 
2593
- agent_state = await self.get_agent_by_id_async(agent_id=agent_id, actor=actor)
2594
- # Grab the in-context messages
2595
- # conversion of messages to OpenAI dict format, which is passed to the token counter
2596
- (in_context_messages, passage_manager_size, message_manager_size) = await asyncio.gather(
2597
- self.get_in_context_messages_async(agent_id=agent_id, actor=actor),
2598
- self.passage_manager.size_async(actor=actor, agent_id=agent_id),
2599
- self.message_manager.size_async(actor=actor, agent_id=agent_id),
2600
- )
2601
- in_context_messages_openai = [m.to_openai_dict() for m in in_context_messages]
2602
-
2603
- # Extract system, memory and external summary
2604
- if (
2605
- len(in_context_messages) > 0
2606
- and in_context_messages[0].role == MessageRole.system
2607
- and in_context_messages[0].content
2608
- and len(in_context_messages[0].content) == 1
2609
- and isinstance(in_context_messages[0].content[0], TextContent)
2610
- ):
2611
- system_message = in_context_messages[0].content[0].text
2612
-
2613
- external_memory_marker_pos = system_message.find("###")
2614
- core_memory_marker_pos = system_message.find("<", external_memory_marker_pos)
2615
- if external_memory_marker_pos != -1 and core_memory_marker_pos != -1:
2616
- system_prompt = system_message[:external_memory_marker_pos].strip()
2617
- external_memory_summary = system_message[external_memory_marker_pos:core_memory_marker_pos].strip()
2618
- core_memory = system_message[core_memory_marker_pos:].strip()
2619
- else:
2620
- # if no markers found, put everything in system message
2621
- system_prompt = system_message
2622
- external_memory_summary = ""
2623
- core_memory = ""
2624
- else:
2625
- # if no system message, fall back on agent's system prompt
2626
- system_prompt = agent_state.system
2627
- external_memory_summary = ""
2628
- core_memory = ""
2629
-
2630
- num_tokens_system = count_tokens(system_prompt)
2631
- num_tokens_core_memory = count_tokens(core_memory)
2632
- num_tokens_external_memory_summary = count_tokens(external_memory_summary)
2633
-
2634
- # Check if there's a summary message in the message queue
2635
- if (
2636
- len(in_context_messages) > 1
2637
- and in_context_messages[1].role == MessageRole.user
2638
- and in_context_messages[1].content
2639
- and len(in_context_messages[1].content) == 1
2640
- and isinstance(in_context_messages[1].content[0], TextContent)
2641
- # TODO remove hardcoding
2642
- and "The following is a summary of the previous " in in_context_messages[1].content[0].text
2643
- ):
2644
- # Summary message exists
2645
- text_content = in_context_messages[1].content[0].text
2646
- assert text_content is not None
2647
- summary_memory = text_content
2648
- num_tokens_summary_memory = count_tokens(text_content)
2649
- # with a summary message, the real messages start at index 2
2650
- num_tokens_messages = (
2651
- num_tokens_from_messages(messages=in_context_messages_openai[2:], model=agent_state.llm_config.model)
2652
- if len(in_context_messages_openai) > 2
2653
- else 0
2654
- )
2655
-
2656
- else:
2657
- summary_memory = None
2658
- num_tokens_summary_memory = 0
2659
- # with no summary message, the real messages start at index 1
2660
- num_tokens_messages = (
2661
- num_tokens_from_messages(messages=in_context_messages_openai[1:], model=agent_state.llm_config.model)
2662
- if len(in_context_messages_openai) > 1
2663
- else 0
2664
- )
2665
-
2666
- # tokens taken up by function definitions
2667
- agent_state_tool_jsons = [t.json_schema for t in agent_state.tools]
2668
- if agent_state_tool_jsons:
2669
- available_functions_definitions = [OpenAITool(type="function", function=f) for f in agent_state_tool_jsons]
2670
- num_tokens_available_functions_definitions = num_tokens_from_functions(
2671
- functions=agent_state_tool_jsons, model=agent_state.llm_config.model
2672
- )
2673
- else:
2674
- available_functions_definitions = []
2675
- num_tokens_available_functions_definitions = 0
2676
-
2677
- num_tokens_used_total = (
2678
- num_tokens_system # system prompt
2679
- + num_tokens_available_functions_definitions # function definitions
2680
- + num_tokens_core_memory # core memory
2681
- + num_tokens_external_memory_summary # metadata (statistics) about recall/archival
2682
- + num_tokens_summary_memory # summary of ongoing conversation
2683
- + num_tokens_messages # tokens taken by messages
2684
- )
2685
- assert isinstance(num_tokens_used_total, int)
2686
-
2687
- return ContextWindowOverview(
2688
- # context window breakdown (in messages)
2689
- num_messages=len(in_context_messages),
2690
- num_archival_memory=passage_manager_size,
2691
- num_recall_memory=message_manager_size,
2692
- num_tokens_external_memory_summary=num_tokens_external_memory_summary,
2693
- external_memory_summary=external_memory_summary,
2694
- # top-level information
2695
- context_window_size_max=agent_state.llm_config.context_window,
2696
- context_window_size_current=num_tokens_used_total,
2697
- # context window breakdown (in tokens)
2698
- num_tokens_system=num_tokens_system,
2699
- system_prompt=system_prompt,
2700
- num_tokens_core_memory=num_tokens_core_memory,
2701
- core_memory=core_memory,
2702
- num_tokens_summary_memory=num_tokens_summary_memory,
2703
- summary_memory=summary_memory,
2704
- num_tokens_messages=num_tokens_messages,
2705
- messages=in_context_messages,
2706
- # related to functions
2707
- num_tokens_functions_definitions=num_tokens_available_functions_definitions,
2708
- functions_definitions=available_functions_definitions,
2636
+ return await calculator.calculate_context_window(
2637
+ agent_state=agent_state,
2638
+ actor=actor,
2639
+ token_counter=token_counter,
2640
+ message_manager=self.message_manager,
2641
+ passage_manager=self.passage_manager,
2709
2642
  )