agno 2.1.4__py3-none-any.whl → 2.1.5__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 (88) hide show
  1. agno/agent/agent.py +1767 -535
  2. agno/db/async_postgres/__init__.py +3 -0
  3. agno/db/async_postgres/async_postgres.py +1668 -0
  4. agno/db/async_postgres/schemas.py +124 -0
  5. agno/db/async_postgres/utils.py +289 -0
  6. agno/db/base.py +237 -2
  7. agno/db/dynamo/dynamo.py +2 -2
  8. agno/db/firestore/firestore.py +2 -2
  9. agno/db/firestore/utils.py +4 -2
  10. agno/db/gcs_json/gcs_json_db.py +2 -2
  11. agno/db/in_memory/in_memory_db.py +2 -2
  12. agno/db/json/json_db.py +2 -2
  13. agno/db/migrations/v1_to_v2.py +30 -13
  14. agno/db/mongo/mongo.py +18 -6
  15. agno/db/mysql/mysql.py +35 -13
  16. agno/db/postgres/postgres.py +29 -6
  17. agno/db/redis/redis.py +2 -2
  18. agno/db/singlestore/singlestore.py +2 -2
  19. agno/db/sqlite/sqlite.py +34 -12
  20. agno/db/sqlite/utils.py +8 -3
  21. agno/eval/accuracy.py +50 -43
  22. agno/eval/performance.py +6 -3
  23. agno/eval/reliability.py +6 -3
  24. agno/eval/utils.py +33 -16
  25. agno/exceptions.py +8 -2
  26. agno/knowledge/knowledge.py +260 -46
  27. agno/knowledge/reader/pdf_reader.py +4 -6
  28. agno/knowledge/reader/reader_factory.py +2 -3
  29. agno/memory/manager.py +241 -33
  30. agno/models/anthropic/claude.py +37 -0
  31. agno/os/app.py +8 -7
  32. agno/os/interfaces/a2a/router.py +3 -5
  33. agno/os/interfaces/agui/router.py +4 -1
  34. agno/os/interfaces/agui/utils.py +27 -6
  35. agno/os/interfaces/slack/router.py +2 -4
  36. agno/os/mcp.py +98 -41
  37. agno/os/router.py +23 -0
  38. agno/os/routers/evals/evals.py +52 -20
  39. agno/os/routers/evals/utils.py +14 -14
  40. agno/os/routers/knowledge/knowledge.py +130 -9
  41. agno/os/routers/knowledge/schemas.py +57 -0
  42. agno/os/routers/memory/memory.py +116 -44
  43. agno/os/routers/metrics/metrics.py +16 -6
  44. agno/os/routers/session/session.py +65 -22
  45. agno/os/schema.py +36 -0
  46. agno/os/utils.py +67 -12
  47. agno/reasoning/anthropic.py +80 -0
  48. agno/reasoning/gemini.py +73 -0
  49. agno/reasoning/openai.py +5 -0
  50. agno/reasoning/vertexai.py +76 -0
  51. agno/session/workflow.py +3 -3
  52. agno/team/team.py +918 -175
  53. agno/tools/googlesheets.py +20 -5
  54. agno/tools/mcp_toolbox.py +3 -3
  55. agno/tools/scrapegraph.py +1 -1
  56. agno/utils/models/claude.py +3 -1
  57. agno/utils/streamlit.py +1 -1
  58. agno/vectordb/base.py +22 -1
  59. agno/vectordb/cassandra/cassandra.py +9 -0
  60. agno/vectordb/chroma/chromadb.py +26 -6
  61. agno/vectordb/clickhouse/clickhousedb.py +9 -1
  62. agno/vectordb/couchbase/couchbase.py +11 -0
  63. agno/vectordb/lancedb/lance_db.py +20 -0
  64. agno/vectordb/langchaindb/langchaindb.py +11 -0
  65. agno/vectordb/lightrag/lightrag.py +9 -0
  66. agno/vectordb/llamaindex/llamaindexdb.py +15 -1
  67. agno/vectordb/milvus/milvus.py +23 -0
  68. agno/vectordb/mongodb/mongodb.py +22 -0
  69. agno/vectordb/pgvector/pgvector.py +19 -0
  70. agno/vectordb/pineconedb/pineconedb.py +35 -4
  71. agno/vectordb/qdrant/qdrant.py +24 -0
  72. agno/vectordb/singlestore/singlestore.py +25 -17
  73. agno/vectordb/surrealdb/surrealdb.py +18 -1
  74. agno/vectordb/upstashdb/upstashdb.py +26 -1
  75. agno/vectordb/weaviate/weaviate.py +18 -0
  76. agno/workflow/condition.py +4 -0
  77. agno/workflow/loop.py +4 -0
  78. agno/workflow/parallel.py +4 -0
  79. agno/workflow/router.py +4 -0
  80. agno/workflow/step.py +22 -14
  81. agno/workflow/steps.py +4 -0
  82. agno/workflow/types.py +2 -2
  83. agno/workflow/workflow.py +328 -61
  84. {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
  85. {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/RECORD +88 -81
  86. {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
  87. {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
  88. {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -27,7 +27,7 @@ from uuid import uuid4
27
27
 
28
28
  from pydantic import BaseModel
29
29
 
30
- from agno.db.base import BaseDb, SessionType, UserMemory
30
+ from agno.db.base import AsyncBaseDb, BaseDb, SessionType, UserMemory
31
31
  from agno.exceptions import (
32
32
  InputCheckError,
33
33
  ModelProviderError,
@@ -177,7 +177,7 @@ class Agent:
177
177
 
178
178
  # --- Database ---
179
179
  # Database to use for this agent
180
- db: Optional[BaseDb] = None
180
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
181
181
 
182
182
  # --- Agent History ---
183
183
  # add_history_to_context=true adds messages from the chat history to the messages list sent to the Model.
@@ -247,6 +247,10 @@ class Agent:
247
247
  send_media_to_model: bool = True
248
248
  # If True, store media in run output
249
249
  store_media: bool = True
250
+ # If True, store tool results in run output
251
+ store_tool_results: bool = True
252
+ # If True, store history messages in run output
253
+ store_history_messages: bool = True
250
254
 
251
255
  # --- System message settings ---
252
256
  # Provide the system message as a string or function
@@ -373,7 +377,7 @@ class Agent:
373
377
  num_history_sessions: Optional[int] = None,
374
378
  dependencies: Optional[Dict[str, Any]] = None,
375
379
  add_dependencies_to_context: bool = False,
376
- db: Optional[BaseDb] = None,
380
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
377
381
  memory_manager: Optional[MemoryManager] = None,
378
382
  enable_agentic_memory: bool = False,
379
383
  enable_user_memories: bool = False,
@@ -384,6 +388,8 @@ class Agent:
384
388
  add_history_to_context: bool = False,
385
389
  num_history_runs: int = 3,
386
390
  store_media: bool = True,
391
+ store_tool_results: bool = True,
392
+ store_history_messages: bool = True,
387
393
  knowledge: Optional[Knowledge] = None,
388
394
  knowledge_filters: Optional[Dict[str, Any]] = None,
389
395
  enable_agentic_knowledge_filters: Optional[bool] = None,
@@ -484,6 +490,8 @@ class Agent:
484
490
  )
485
491
 
486
492
  self.store_media = store_media
493
+ self.store_tool_results = store_tool_results
494
+ self.store_history_messages = store_history_messages
487
495
 
488
496
  self.knowledge = knowledge
489
497
  self.knowledge_filters = knowledge_filters
@@ -691,6 +699,10 @@ class Agent:
691
699
  self.enable_session_summaries or self.session_summary_manager is not None
692
700
  )
693
701
 
702
+ def _has_async_db(self) -> bool:
703
+ """Return True if the db the agent is equipped with is an Async implementation"""
704
+ return self.db is not None and isinstance(self.db, AsyncBaseDb)
705
+
694
706
  def initialize_agent(self, debug_mode: Optional[bool] = None) -> None:
695
707
  self._set_default_model()
696
708
  self._set_debug(debug_mode=debug_mode)
@@ -883,8 +895,6 @@ class Agent:
883
895
 
884
896
  if self.store_media:
885
897
  self._store_media(run_response, model_response)
886
- else:
887
- self._scrub_media_from_run_output(run_response)
888
898
 
889
899
  # We should break out of the run function
890
900
  if any(tool_call.is_paused for tool_call in run_response.tools or []):
@@ -930,7 +940,11 @@ class Agent:
930
940
  # Consume the response iterator to ensure the memory is updated before the run is completed
931
941
  deque(response_iterator, maxlen=0)
932
942
 
933
- # 11. Save session to memory
943
+ # 11. Scrub the stored run based on storage flags
944
+ if self._scrub_run_output_for_storage(run_response):
945
+ session.upsert_run(run=run_response)
946
+
947
+ # 12. Save session to memory
934
948
  self.save_session(session=session)
935
949
 
936
950
  # Log Agent Telemetry
@@ -1133,7 +1147,11 @@ class Agent:
1133
1147
  create_run_completed_event(from_run_response=run_response), run_response
1134
1148
  )
1135
1149
 
1136
- # 10. Save session to storage
1150
+ # 10. Scrub the stored run based on storage flags
1151
+ if self._scrub_run_output_for_storage(run_response):
1152
+ session.upsert_run(run=run_response)
1153
+
1154
+ # 11. Save session to storage
1137
1155
  self.save_session(session=session)
1138
1156
 
1139
1157
  if stream_intermediate_steps:
@@ -1242,6 +1260,10 @@ class Agent:
1242
1260
  **kwargs: Any,
1243
1261
  ) -> Union[RunOutput, Iterator[Union[RunOutputEvent, RunOutput]]]:
1244
1262
  """Run the Agent and return the response."""
1263
+ if self._has_async_db():
1264
+ raise RuntimeError(
1265
+ "`run` method is not supported with an async database. Please use `arun` method instead."
1266
+ )
1245
1267
 
1246
1268
  # Create a run_id for this specific run
1247
1269
  run_id = str(uuid4())
@@ -1451,8 +1473,9 @@ class Agent:
1451
1473
 
1452
1474
  async def _arun(
1453
1475
  self,
1476
+ input: Union[str, List, Dict, Message, BaseModel, List[Message]],
1454
1477
  run_response: RunOutput,
1455
- session: AgentSession,
1478
+ session_id: str,
1456
1479
  session_state: Optional[Dict[str, Any]] = None,
1457
1480
  user_id: Optional[str] = None,
1458
1481
  knowledge_filters: Optional[Dict[str, Any]] = None,
@@ -1468,27 +1491,42 @@ class Agent:
1468
1491
  """Run the Agent and yield the RunOutput.
1469
1492
 
1470
1493
  Steps:
1471
- 1. Resolve dependencies
1472
- 2. Execute pre-hooks
1473
- 3. Prepare run messages
1474
- 4. Reason about the task if reasoning is enabled
1475
- 5. Generate a response from the Model (includes running function calls)
1476
- 6. Update the RunOutput with the model response
1477
- 7. Execute post-hooks
1478
- 8. Calculate session metrics
1479
- 9. Save output to file
1480
- 10. Add RunOutput to Agent Session
1481
- 11. Update Agent Memory
1482
- 12. Save session to storage
1494
+ 1. Read or create session
1495
+ 2. Update metadata and session state
1496
+ 3. Resolve dependencies
1497
+ 4. Execute pre-hooks
1498
+ 5. Determine tools for model
1499
+ 6. Prepare run messages
1500
+ 7. Reason about the task if reasoning is enabled
1501
+ 8. Generate a response from the Model (includes running function calls)
1502
+ 9. Update the RunOutput with the model response
1503
+ 10. Execute post-hooks
1504
+ 11. Add RunOutput to Agent Session
1505
+ 12. Update Agent Memory
1506
+ 13. Scrub the stored run if needed
1507
+ 14. Save session to storage
1483
1508
  """
1509
+ log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1510
+
1484
1511
  # Register run for cancellation tracking
1485
1512
  register_run(run_response.run_id) # type: ignore
1486
1513
 
1487
- # 1. Resolving here for async requirement
1514
+ # 1. Read or create session. Reads from the database if provided.
1515
+ if self._has_async_db():
1516
+ agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
1517
+ else:
1518
+ agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
1519
+
1520
+ # 2. Update metadata and session state
1521
+ self._update_metadata(session=agent_session)
1522
+ if session_state is not None:
1523
+ session_state = self._load_session_state(session=agent_session, session_state=session_state)
1524
+
1525
+ # 3. Resolve dependencies
1488
1526
  if dependencies is not None:
1489
- await self._aresolve_run_dependencies(dependencies)
1527
+ await self._aresolve_run_dependencies(dependencies=dependencies)
1490
1528
 
1491
- # 2. Execute pre-hooks
1529
+ # 4. Execute pre-hooks
1492
1530
  run_input = cast(RunInput, run_response.input)
1493
1531
  self.model = cast(Model, self.model)
1494
1532
  if self.pre_hooks is not None:
@@ -1497,7 +1535,7 @@ class Agent:
1497
1535
  hooks=self.pre_hooks, # type: ignore
1498
1536
  run_response=run_response,
1499
1537
  run_input=run_input,
1500
- session=session,
1538
+ session=agent_session,
1501
1539
  user_id=user_id,
1502
1540
  debug_mode=debug_mode,
1503
1541
  **kwargs,
@@ -1506,10 +1544,12 @@ class Agent:
1506
1544
  async for _ in pre_hook_iterator:
1507
1545
  pass
1508
1546
 
1509
- self._determine_tools_for_model(
1547
+ # 5. Determine tools for model
1548
+ self.model = cast(Model, self.model)
1549
+ await self._adetermine_tools_for_model(
1510
1550
  model=self.model,
1511
1551
  run_response=run_response,
1512
- session=session,
1552
+ session=agent_session,
1513
1553
  session_state=session_state,
1514
1554
  dependencies=dependencies,
1515
1555
  user_id=user_id,
@@ -1517,11 +1557,11 @@ class Agent:
1517
1557
  knowledge_filters=knowledge_filters,
1518
1558
  )
1519
1559
 
1520
- # 3. Prepare run messages
1521
- run_messages: RunMessages = self._get_run_messages(
1560
+ # 6. Prepare run messages
1561
+ run_messages: RunMessages = await self._aget_run_messages(
1522
1562
  run_response=run_response,
1523
1563
  input=run_input.input_content,
1524
- session=session,
1564
+ session=agent_session,
1525
1565
  session_state=session_state,
1526
1566
  user_id=user_id,
1527
1567
  audio=run_input.audios,
@@ -1539,110 +1579,134 @@ class Agent:
1539
1579
  if len(run_messages.messages) == 0:
1540
1580
  log_error("No messages to be sent to the model.")
1541
1581
 
1542
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1543
-
1544
- # 4. Reason about the task if reasoning is enabled
1545
- await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
1546
-
1547
- # Check for cancellation before model call
1548
- raise_if_cancelled(run_response.run_id) # type: ignore
1582
+ try:
1583
+ # 7. Reason about the task if reasoning is enabled
1584
+ await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
1585
+ raise_if_cancelled(run_response.run_id) # type: ignore
1549
1586
 
1550
- # 5. Generate a response from the Model (includes running function calls)
1551
- model_response: ModelResponse = await self.model.aresponse(
1552
- messages=run_messages.messages,
1553
- tools=self._tools_for_model,
1554
- functions=self._functions_for_model,
1555
- tool_choice=self.tool_choice,
1556
- tool_call_limit=self.tool_call_limit,
1557
- response_format=response_format,
1558
- send_media_to_model=self.send_media_to_model,
1559
- )
1587
+ # 8. Generate a response from the Model (includes running function calls)
1588
+ model_response: ModelResponse = await self.model.aresponse(
1589
+ messages=run_messages.messages,
1590
+ tools=self._tools_for_model,
1591
+ functions=self._functions_for_model,
1592
+ tool_choice=self.tool_choice,
1593
+ tool_call_limit=self.tool_call_limit,
1594
+ response_format=response_format,
1595
+ send_media_to_model=self.send_media_to_model,
1596
+ )
1597
+ raise_if_cancelled(run_response.run_id) # type: ignore
1560
1598
 
1561
- # Check for cancellation after model call
1562
- raise_if_cancelled(run_response.run_id) # type: ignore
1599
+ # If an output model is provided, generate output using the output model
1600
+ await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
1601
+ # If a parser model is provided, structure the response separately
1602
+ await self._aparse_response_with_parser_model(model_response=model_response, run_messages=run_messages)
1563
1603
 
1564
- # If an output model is provided, generate output using the output model
1565
- await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
1604
+ # 9. Update the RunOutput with the model response
1605
+ self._update_run_response(
1606
+ model_response=model_response, run_response=run_response, run_messages=run_messages
1607
+ )
1566
1608
 
1567
- # If a parser model is provided, structure the response separately
1568
- await self._aparse_response_with_parser_model(model_response=model_response, run_messages=run_messages)
1609
+ # Optional: Store media
1610
+ if self.store_media:
1611
+ self._store_media(run_response, model_response)
1569
1612
 
1570
- # 6. Update the RunOutput with the model response
1571
- self._update_run_response(model_response=model_response, run_response=run_response, run_messages=run_messages)
1613
+ # Break out of the run function if a tool call is paused
1614
+ if any(tool_call.is_paused for tool_call in run_response.tools or []):
1615
+ return self._handle_agent_run_paused(
1616
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
1617
+ )
1618
+ raise_if_cancelled(run_response.run_id) # type: ignore
1572
1619
 
1573
- if self.store_media:
1574
- self._store_media(run_response, model_response)
1575
- else:
1576
- self._scrub_media_from_run_output(run_response)
1620
+ # 10. Calculate session metrics
1621
+ self._update_session_metrics(session=agent_session, run_response=run_response)
1577
1622
 
1578
- # We should break out of the run function
1579
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
1580
- return self._handle_agent_run_paused(
1581
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
1582
- )
1623
+ # Set the run status to completed
1624
+ run_response.status = RunStatus.completed
1583
1625
 
1584
- run_response.status = RunStatus.completed
1626
+ # Convert the response to the structured format if needed
1627
+ self._convert_response_to_structured_format(run_response)
1585
1628
 
1586
- # Convert the response to the structured format if needed
1587
- self._convert_response_to_structured_format(run_response)
1629
+ # Set the run duration
1630
+ if run_response.metrics:
1631
+ run_response.metrics.stop_timer()
1588
1632
 
1589
- # Set the run duration
1590
- if run_response.metrics:
1591
- run_response.metrics.stop_timer()
1633
+ # 10. Execute post-hooks (after output is generated but before response is returned)
1634
+ if self.post_hooks is not None:
1635
+ await self._aexecute_post_hooks(
1636
+ hooks=self.post_hooks, # type: ignore
1637
+ run_output=run_response,
1638
+ session=agent_session,
1639
+ user_id=user_id,
1640
+ debug_mode=debug_mode,
1641
+ **kwargs,
1642
+ )
1592
1643
 
1593
- # 7. Execute post-hooks after output is generated but before response is returned
1594
- if self.post_hooks is not None:
1595
- await self._aexecute_post_hooks(
1596
- hooks=self.post_hooks, # type: ignore
1597
- run_output=run_response,
1598
- session=session,
1644
+ # Optional: Save output to file if save_response_to_file is set
1645
+ self.save_run_response_to_file(
1646
+ run_response=run_response,
1647
+ input=run_messages.user_message,
1648
+ session_id=agent_session.session_id,
1599
1649
  user_id=user_id,
1600
- debug_mode=debug_mode,
1601
- **kwargs,
1602
1650
  )
1603
1651
 
1604
- # 8. Calculate session metrics
1605
- self._update_session_metrics(session=session, run_response=run_response)
1652
+ # 11. Add RunOutput to Agent Session
1653
+ agent_session.upsert_run(run=run_response)
1606
1654
 
1607
- # 9. Optional: Save output to file if save_response_to_file is set
1608
- self.save_run_response_to_file(
1609
- run_response=run_response, input=run_messages.user_message, session_id=session.session_id, user_id=user_id
1610
- )
1655
+ # 12. Update Agent Memory
1656
+ async for _ in self._amake_memories_and_summaries(
1657
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
1658
+ ):
1659
+ pass
1611
1660
 
1612
- # 10. Add RunOutput to Agent Session
1613
- session.upsert_run(run=run_response)
1661
+ # 13. Scrub the stored run based on storage flags
1662
+ if self._scrub_run_output_for_storage(run_response):
1663
+ agent_session.upsert_run(run=run_response)
1614
1664
 
1615
- # 11. Update Agent Memory
1616
- async for _ in self._amake_memories_and_summaries(
1617
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
1618
- ):
1619
- pass
1665
+ # 14. Save session to storage
1666
+ if self._has_async_db():
1667
+ await self.asave_session(session=agent_session)
1668
+ else:
1669
+ self.save_session(session=agent_session)
1620
1670
 
1621
- # 12. Save session to storage
1622
- self.save_session(session=session)
1671
+ # Log Agent Telemetry
1672
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
1623
1673
 
1624
- # Log Agent Telemetry
1625
- await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1674
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1626
1675
 
1627
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1676
+ return run_response
1628
1677
 
1629
- # Always clean up the run tracking
1630
- cleanup_run(run_response.run_id) # type: ignore
1678
+ except RunCancelledException as e:
1679
+ # Handle run cancellation
1680
+ log_info(f"Run {run_response.run_id} was cancelled")
1681
+ run_response.content = str(e)
1682
+ run_response.status = RunStatus.cancelled
1631
1683
 
1632
- return run_response
1684
+ # Update the Agent Session before exiting
1685
+ agent_session.upsert_run(run=run_response)
1686
+ if self._has_async_db():
1687
+ await self.asave_session(session=agent_session)
1688
+ else:
1689
+ self.save_session(session=agent_session)
1690
+
1691
+ return run_response
1692
+
1693
+ finally:
1694
+ # Always clean up the run tracking
1695
+ cleanup_run(run_response.run_id) # type: ignore
1633
1696
 
1634
1697
  async def _arun_stream(
1635
1698
  self,
1699
+ input: Union[str, List, Dict, Message, BaseModel, List[Message]],
1636
1700
  run_response: RunOutput,
1637
- session: AgentSession,
1701
+ session_id: str,
1638
1702
  session_state: Optional[Dict[str, Any]] = None,
1639
1703
  user_id: Optional[str] = None,
1640
1704
  knowledge_filters: Optional[Dict[str, Any]] = None,
1705
+ dependencies: Optional[Dict[str, Any]] = None,
1641
1706
  add_history_to_context: Optional[bool] = None,
1642
1707
  add_dependencies_to_context: Optional[bool] = None,
1643
1708
  add_session_state_to_context: Optional[bool] = None,
1644
1709
  metadata: Optional[Dict[str, Any]] = None,
1645
- dependencies: Optional[Dict[str, Any]] = None,
1646
1710
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
1647
1711
  stream_intermediate_steps: bool = False,
1648
1712
  yield_run_response: Optional[bool] = None,
@@ -1652,33 +1716,51 @@ class Agent:
1652
1716
  """Run the Agent and yield the RunOutput.
1653
1717
 
1654
1718
  Steps:
1655
- 1. Resolve dependencies
1656
- 2. Execute pre-hooks
1657
- 3. Prepare run messages
1658
- 4. Reason about the task if reasoning is enabled
1659
- 5. Generate a response from the Model (includes running function calls)
1660
- 6. Calculate session metrics
1661
- 7. Add RunOutput to Agent Session
1662
- 8. Update Agent Memory
1663
- 9. Create the run completed event
1664
- 10. Save session to storage
1719
+ 1. Read or create session
1720
+ 2. Update metadata and session state
1721
+ 3. Resolve dependencies
1722
+ 4. Execute pre-hooks
1723
+ 5. Determine tools for model
1724
+ 6. Prepare run messages
1725
+ 7. Reason about the task if reasoning is enabled
1726
+ 8. Generate a response from the Model (includes running function calls)
1727
+ 9. Calculate session metrics
1728
+ 10. Add RunOutput to Agent Session
1729
+ 11. Update Agent Memory
1730
+ 12. Create the run completed event
1731
+ 13. Save session to storage
1665
1732
  """
1733
+ log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1734
+
1735
+ # Start the Run by yielding a RunStarted event
1736
+ if stream_intermediate_steps:
1737
+ yield self._handle_event(create_run_started_event(run_response), run_response)
1738
+
1739
+ # 1. Read or create session. Reads from the database if provided.
1740
+ if self._has_async_db():
1741
+ agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
1742
+ else:
1743
+ agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
1744
+
1745
+ # 2. Update metadata and session state
1746
+ self._update_metadata(session=agent_session)
1747
+ if session_state is not None:
1748
+ session_state = self._load_session_state(session=agent_session, session_state=session_state)
1666
1749
 
1667
- # 1. Resolving here for async requirement
1750
+ # 3. Resolve dependencies
1668
1751
  if dependencies is not None:
1669
1752
  await self._aresolve_run_dependencies(dependencies=dependencies)
1670
1753
 
1671
- # 2. Execute pre-hooks
1754
+ # 4. Execute pre-hooks
1672
1755
  run_input = cast(RunInput, run_response.input)
1673
1756
  self.model = cast(Model, self.model)
1674
-
1675
1757
  if self.pre_hooks is not None:
1676
1758
  # Can modify the run input
1677
1759
  pre_hook_iterator = self._aexecute_pre_hooks(
1678
1760
  hooks=self.pre_hooks, # type: ignore
1679
1761
  run_response=run_response,
1680
1762
  run_input=run_input,
1681
- session=session,
1763
+ session=agent_session,
1682
1764
  user_id=user_id,
1683
1765
  debug_mode=debug_mode,
1684
1766
  **kwargs,
@@ -1686,22 +1768,24 @@ class Agent:
1686
1768
  async for event in pre_hook_iterator:
1687
1769
  yield event
1688
1770
 
1771
+ # 5. Determine tools for model
1772
+ self.model = cast(Model, self.model)
1689
1773
  self._determine_tools_for_model(
1690
1774
  model=self.model,
1691
1775
  run_response=run_response,
1692
- session=session,
1776
+ session=agent_session,
1693
1777
  session_state=session_state,
1694
- dependencies=dependencies,
1695
1778
  user_id=user_id,
1696
1779
  async_mode=True,
1697
1780
  knowledge_filters=knowledge_filters,
1781
+ dependencies=dependencies,
1698
1782
  )
1699
1783
 
1700
- # 3. Prepare run messages
1701
- run_messages: RunMessages = self._get_run_messages(
1784
+ # 6. Prepare run messages
1785
+ run_messages: RunMessages = await self._aget_run_messages(
1702
1786
  run_response=run_response,
1703
1787
  input=run_input.input_content,
1704
- session=session,
1788
+ session=agent_session,
1705
1789
  session_state=session_state,
1706
1790
  user_id=user_id,
1707
1791
  audio=run_input.audios,
@@ -1716,29 +1800,23 @@ class Agent:
1716
1800
  metadata=metadata,
1717
1801
  **kwargs,
1718
1802
  )
1719
-
1720
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1803
+ if len(run_messages.messages) == 0:
1804
+ log_error("No messages to be sent to the model.")
1721
1805
 
1722
1806
  # Register run for cancellation tracking
1723
1807
  register_run(run_response.run_id) # type: ignore
1724
1808
 
1725
1809
  try:
1726
- # Start the Run by yielding a RunStarted event
1727
- if stream_intermediate_steps:
1728
- yield self._handle_event(create_run_started_event(run_response), run_response)
1729
-
1730
- # 4. Reason about the task if reasoning is enabled
1810
+ # 7. Reason about the task if reasoning is enabled
1731
1811
  async for item in self._ahandle_reasoning_stream(run_response=run_response, run_messages=run_messages):
1732
1812
  raise_if_cancelled(run_response.run_id) # type: ignore
1733
1813
  yield item
1734
-
1735
- # Check for cancellation before model processing
1736
1814
  raise_if_cancelled(run_response.run_id) # type: ignore
1737
1815
 
1738
- # 5. Generate a response from the Model
1816
+ # 8. Generate a response from the Model
1739
1817
  if self.output_model is None:
1740
1818
  async for event in self._ahandle_model_response_stream(
1741
- session=session,
1819
+ session=agent_session,
1742
1820
  run_response=run_response,
1743
1821
  run_messages=run_messages,
1744
1822
  response_format=response_format,
@@ -1753,7 +1831,7 @@ class Agent:
1753
1831
  ) # type: ignore
1754
1832
 
1755
1833
  async for event in self._ahandle_model_response_stream(
1756
- session=session,
1834
+ session=agent_session,
1757
1835
  run_response=run_response,
1758
1836
  run_messages=run_messages,
1759
1837
  response_format=response_format,
@@ -1771,7 +1849,7 @@ class Agent:
1771
1849
 
1772
1850
  # If an output model is provided, generate output using the output model
1773
1851
  async for event in self._agenerate_response_with_output_model_stream(
1774
- session=session,
1852
+ session=agent_session,
1775
1853
  run_response=run_response,
1776
1854
  run_messages=run_messages,
1777
1855
  stream_intermediate_steps=stream_intermediate_steps,
@@ -1784,51 +1862,59 @@ class Agent:
1784
1862
 
1785
1863
  # If a parser model is provided, structure the response separately
1786
1864
  async for event in self._aparse_response_with_parser_model_stream(
1787
- session=session, run_response=run_response, stream_intermediate_steps=stream_intermediate_steps
1865
+ session=agent_session, run_response=run_response, stream_intermediate_steps=stream_intermediate_steps
1788
1866
  ):
1789
1867
  yield event
1790
1868
 
1791
- # We should break out of the run function
1869
+ # Break out of the run function if a tool call is paused
1792
1870
  if any(tool_call.is_paused for tool_call in run_response.tools or []):
1793
1871
  for item in self._handle_agent_run_paused_stream(
1794
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
1872
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
1795
1873
  ):
1796
1874
  yield item
1797
1875
  return
1798
1876
 
1877
+ # Set the run status to completed
1799
1878
  run_response.status = RunStatus.completed
1800
1879
 
1801
1880
  # Set the run duration
1802
1881
  if run_response.metrics:
1803
1882
  run_response.metrics.stop_timer()
1804
1883
 
1805
- # 6. Calculate session metrics
1806
- self._update_session_metrics(session=session, run_response=run_response)
1884
+ # 9. Calculate session metrics
1885
+ self._update_session_metrics(session=agent_session, run_response=run_response)
1807
1886
 
1808
1887
  # Optional: Save output to file if save_response_to_file is set
1809
1888
  self.save_run_response_to_file(
1810
1889
  run_response=run_response,
1811
1890
  input=run_messages.user_message,
1812
- session_id=session.session_id,
1891
+ session_id=agent_session.session_id,
1813
1892
  user_id=user_id,
1814
1893
  )
1815
1894
 
1816
- # 7. Add RunOutput to Agent Session
1817
- session.upsert_run(run=run_response)
1895
+ # 10. Add RunOutput to Agent Session
1896
+ agent_session.upsert_run(run=run_response)
1818
1897
 
1819
- # 8. Update Agent Memory
1898
+ # 11. Update Agent Memory
1820
1899
  async for event in self._amake_memories_and_summaries(
1821
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
1900
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
1822
1901
  ):
1823
1902
  yield event
1824
1903
 
1825
- # 9. Create the run completed event
1904
+ # 12. Create the run completed event
1826
1905
  completed_event = self._handle_event(
1827
1906
  create_run_completed_event(from_run_response=run_response), run_response
1828
1907
  )
1829
1908
 
1830
- # 10. Save session to storage
1831
- self.save_session(session=session)
1909
+ # 13. Scrub the stored run based on storage flags
1910
+ if self._scrub_run_output_for_storage(run_response):
1911
+ agent_session.upsert_run(run=run_response)
1912
+
1913
+ # 14. Save session to storage
1914
+ if self._has_async_db():
1915
+ await self.asave_session(session=agent_session)
1916
+ else:
1917
+ self.save_session(session=agent_session)
1832
1918
 
1833
1919
  if stream_intermediate_steps:
1834
1920
  yield completed_event
@@ -1837,7 +1923,7 @@ class Agent:
1837
1923
  yield run_response
1838
1924
 
1839
1925
  # Log Agent Telemetry
1840
- await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1926
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
1841
1927
 
1842
1928
  log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1843
1929
 
@@ -1854,8 +1940,11 @@ class Agent:
1854
1940
  )
1855
1941
 
1856
1942
  # Add the RunOutput to Agent Session even when cancelled
1857
- session.upsert_run(run=run_response)
1858
- self.save_session(session=session)
1943
+ agent_session.upsert_run(run=run_response)
1944
+ if self._has_async_db():
1945
+ await self.asave_session(session=agent_session)
1946
+ else:
1947
+ self.save_session(session=agent_session)
1859
1948
  finally:
1860
1949
  # Always clean up the run tracking
1861
1950
  cleanup_run(run_response.run_id) # type: ignore
@@ -1939,7 +2028,7 @@ class Agent:
1939
2028
  # Create a run_id for this specific run
1940
2029
  run_id = str(uuid4())
1941
2030
 
1942
- # Validate input against input_schema if provided
2031
+ # 2. Validate input against input_schema if provided
1943
2032
  validated_input = self._validate_input(input)
1944
2033
 
1945
2034
  # Normalise hook & guardails
@@ -1950,6 +2039,7 @@ class Agent:
1950
2039
  self.post_hooks = normalize_hooks(self.post_hooks, async_mode=True)
1951
2040
  self._hooks_normalised = True
1952
2041
 
2042
+ # Initialize session
1953
2043
  session_id, user_id, session_state = self._initialize_session(
1954
2044
  run_id=run_id, session_id=session_id, user_id=user_id, session_state=session_state
1955
2045
  )
@@ -1961,25 +2051,8 @@ class Agent:
1961
2051
  images=images, videos=videos, audios=audio, files=files
1962
2052
  )
1963
2053
 
1964
- # Create RunInput to capture the original user input
1965
- run_input = RunInput(
1966
- input_content=validated_input,
1967
- images=image_artifacts,
1968
- videos=video_artifacts,
1969
- audios=audio_artifacts,
1970
- files=file_artifacts,
1971
- )
1972
-
1973
- # Read existing session from storage
1974
- agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
1975
- self._update_metadata(session=agent_session)
1976
-
1977
- # Update session state from DB
1978
- session_state = self._load_session_state(session=agent_session, session_state=session_state)
1979
-
1980
- # Determine run dependencies
2054
+ # Resolve variables
1981
2055
  run_dependencies = dependencies if dependencies is not None else self.dependencies
1982
-
1983
2056
  add_dependencies = (
1984
2057
  add_dependencies_to_context if add_dependencies_to_context is not None else self.add_dependencies_to_context
1985
2058
  )
@@ -1990,10 +2063,14 @@ class Agent:
1990
2063
  )
1991
2064
  add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
1992
2065
 
1993
- effective_filters = knowledge_filters
1994
- # When filters are passed manually
1995
- if self.knowledge_filters or knowledge_filters:
1996
- effective_filters = self._get_effective_filters(knowledge_filters)
2066
+ # Create RunInput to capture the original user input
2067
+ run_input = RunInput(
2068
+ input_content=validated_input,
2069
+ images=image_artifacts,
2070
+ videos=video_artifacts,
2071
+ audios=audio_artifacts,
2072
+ files=files,
2073
+ )
1997
2074
 
1998
2075
  # Use stream override value when necessary
1999
2076
  if stream is None:
@@ -2015,6 +2092,11 @@ class Agent:
2015
2092
  response_format = self._get_response_format() if self.parser_model is None else None
2016
2093
  self.model = cast(Model, self.model)
2017
2094
 
2095
+ # Get knowledge filters
2096
+ effective_filters = knowledge_filters
2097
+ if self.knowledge_filters or knowledge_filters:
2098
+ effective_filters = self._get_effective_filters(knowledge_filters)
2099
+
2018
2100
  # Merge agent metadata with run metadata
2019
2101
  if self.metadata is not None:
2020
2102
  if metadata is None:
@@ -2051,37 +2133,37 @@ class Agent:
2051
2133
  # Pass the new run_response to _arun
2052
2134
  if stream:
2053
2135
  return self._arun_stream( # type: ignore
2136
+ input=validated_input,
2054
2137
  run_response=run_response,
2055
- session=agent_session,
2056
2138
  user_id=user_id,
2139
+ response_format=response_format,
2140
+ stream_intermediate_steps=stream_intermediate_steps,
2141
+ yield_run_response=yield_run_response,
2142
+ dependencies=run_dependencies,
2143
+ session_id=session_id,
2057
2144
  session_state=session_state,
2058
2145
  knowledge_filters=effective_filters,
2059
2146
  add_history_to_context=add_history,
2060
2147
  add_dependencies_to_context=add_dependencies,
2061
2148
  add_session_state_to_context=add_session_state,
2062
2149
  metadata=metadata,
2063
- response_format=response_format,
2064
- stream_intermediate_steps=stream_intermediate_steps,
2065
- yield_run_response=yield_run_response,
2066
- dependencies=run_dependencies,
2067
2150
  debug_mode=debug_mode,
2068
2151
  **kwargs,
2069
2152
  ) # type: ignore[assignment]
2070
2153
  else:
2071
2154
  return self._arun( # type: ignore
2155
+ input=validated_input,
2072
2156
  run_response=run_response,
2073
2157
  user_id=user_id,
2074
- session=agent_session,
2158
+ response_format=response_format,
2159
+ dependencies=run_dependencies,
2160
+ session_id=session_id,
2075
2161
  session_state=session_state,
2076
- knowledge_filters=knowledge_filters,
2162
+ knowledge_filters=effective_filters,
2077
2163
  add_history_to_context=add_history,
2078
2164
  add_dependencies_to_context=add_dependencies,
2079
2165
  add_session_state_to_context=add_session_state,
2080
2166
  metadata=metadata,
2081
- response_format=response_format,
2082
- stream_intermediate_steps=stream_intermediate_steps,
2083
- yield_run_response=yield_run_response,
2084
- dependencies=run_dependencies,
2085
2167
  debug_mode=debug_mode,
2086
2168
  **kwargs,
2087
2169
  )
@@ -2102,17 +2184,6 @@ class Agent:
2102
2184
  import time
2103
2185
 
2104
2186
  time.sleep(delay)
2105
- except RunCancelledException as e:
2106
- # Handle run cancellation
2107
- log_info(f"Run {run_response.run_id} was cancelled")
2108
- run_response.content = str(e)
2109
- run_response.status = RunStatus.cancelled
2110
-
2111
- # Add the RunOutput to Agent Session even when cancelled
2112
- agent_session.upsert_run(run=run_response)
2113
- self.save_session(session=agent_session)
2114
-
2115
- return run_response
2116
2187
  except KeyboardInterrupt:
2117
2188
  run_response.content = "Operation cancelled by user"
2118
2189
  run_response.status = RunStatus.cancelled
@@ -2209,6 +2280,9 @@ class Agent:
2209
2280
  if run_response is None and (run_id is not None and (session_id is None and self.session_id is None)):
2210
2281
  raise ValueError("Session ID is required to continue a run from a run_id.")
2211
2282
 
2283
+ if self._has_async_db():
2284
+ raise Exception("continue_run() is not supported with an async DB. Please use acontinue_arun() instead.")
2285
+
2212
2286
  session_id = run_response.session_id if run_response else session_id
2213
2287
 
2214
2288
  session_id, user_id, session_state = self._initialize_session(
@@ -2589,6 +2663,7 @@ class Agent:
2589
2663
  knowledge_filters: Optional[Dict[str, Any]] = None,
2590
2664
  dependencies: Optional[Dict[str, Any]] = None,
2591
2665
  debug_mode: Optional[bool] = None,
2666
+ yield_run_response: bool = False,
2592
2667
  **kwargs,
2593
2668
  ) -> Union[RunOutput, AsyncIterator[Union[RunOutputEvent, RunOutput]]]:
2594
2669
  """Continue a previous run.
@@ -2621,21 +2696,8 @@ class Agent:
2621
2696
  # Initialize the Agent
2622
2697
  self.initialize_agent(debug_mode=debug_mode)
2623
2698
 
2624
- # Read existing session from storage
2625
- agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2626
- self._update_metadata(session=agent_session)
2627
-
2628
- # Update session state from DB
2629
- session_state = self._load_session_state(session=agent_session, session_state=session_state)
2630
-
2631
2699
  run_dependencies = dependencies if dependencies is not None else self.dependencies
2632
2700
 
2633
- effective_filters = knowledge_filters
2634
-
2635
- # When filters are passed manually
2636
- if self.knowledge_filters or knowledge_filters:
2637
- effective_filters = self._get_effective_filters(knowledge_filters)
2638
-
2639
2701
  # If no retries are set, use the agent's default retries
2640
2702
  retries = retries if retries is not None else self.retries
2641
2703
 
@@ -2655,70 +2717,42 @@ class Agent:
2655
2717
  self.stream = self.stream or stream
2656
2718
  self.stream_intermediate_steps = self.stream_intermediate_steps or (stream_intermediate_steps and self.stream)
2657
2719
 
2658
- # Run can be continued from previous run response or from passed run_response context
2659
- if run_response is not None:
2660
- # The run is continued from a provided run_response. This contains the updated tools.
2661
- input = run_response.messages or []
2662
- elif run_id is not None:
2663
- # The run is continued from a run_id. This requires the updated tools to be passed.
2664
- if updated_tools is None:
2665
- raise ValueError("Updated tools are required to continue a run from a run_id.")
2666
-
2667
- runs = agent_session.runs
2668
- run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
2669
- if run_response is None:
2670
- raise RuntimeError(f"No runs found for run ID {run_id}")
2671
- run_response.tools = updated_tools
2672
- input = run_response.messages or []
2673
- else:
2674
- raise ValueError("Either run_response or run_id must be provided.")
2720
+ # Get knowledge filters
2721
+ effective_filters = knowledge_filters
2722
+ if self.knowledge_filters or knowledge_filters:
2723
+ effective_filters = self._get_effective_filters(knowledge_filters)
2675
2724
 
2676
2725
  # Prepare arguments for the model
2677
2726
  response_format = self._get_response_format()
2678
2727
  self.model = cast(Model, self.model)
2679
2728
 
2680
- self._determine_tools_for_model(
2681
- model=self.model,
2682
- run_response=run_response,
2683
- session=agent_session,
2684
- session_state=session_state,
2685
- user_id=user_id,
2686
- async_mode=True,
2687
- knowledge_filters=effective_filters,
2688
- )
2689
-
2690
2729
  last_exception = None
2691
2730
  num_attempts = retries + 1
2692
2731
  for attempt in range(num_attempts):
2693
- run_response = cast(RunOutput, run_response)
2694
-
2695
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
2696
-
2697
- # Prepare run messages
2698
- run_messages: RunMessages = self._get_continue_run_messages(
2699
- input=input,
2700
- )
2701
-
2702
- # Reset the run paused state
2703
- run_response.status = RunStatus.running
2704
-
2705
2732
  try:
2706
2733
  if stream:
2707
2734
  return self._acontinue_run_stream(
2708
2735
  run_response=run_response,
2709
- run_messages=run_messages,
2736
+ updated_tools=updated_tools,
2737
+ knowledge_filters=effective_filters,
2738
+ session_state=session_state,
2739
+ run_id=run_id,
2710
2740
  user_id=user_id,
2711
- session=agent_session,
2741
+ session_id=session_id,
2712
2742
  response_format=response_format,
2713
- stream_intermediate_steps=stream_intermediate_steps,
2714
2743
  dependencies=run_dependencies,
2744
+ stream_intermediate_steps=stream_intermediate_steps,
2745
+ yield_run_response=yield_run_response,
2715
2746
  )
2716
2747
  else:
2717
2748
  return self._acontinue_run( # type: ignore
2749
+ session_id=session_id,
2718
2750
  run_response=run_response,
2719
- run_messages=run_messages,
2751
+ updated_tools=updated_tools,
2752
+ knowledge_filters=effective_filters,
2753
+ session_state=session_state,
2754
+ run_id=run_id,
2720
2755
  user_id=user_id,
2721
- session=agent_session,
2722
2756
  response_format=response_format,
2723
2757
  dependencies=run_dependencies,
2724
2758
  debug_mode=debug_mode,
@@ -2738,6 +2772,7 @@ class Agent:
2738
2772
 
2739
2773
  time.sleep(delay)
2740
2774
  except KeyboardInterrupt:
2775
+ run_response = cast(RunOutput, run_response)
2741
2776
  if stream:
2742
2777
  return async_generator_wrapper( # type: ignore
2743
2778
  create_run_cancelled_event(run_response, "Operation cancelled by user")
@@ -2762,9 +2797,12 @@ class Agent:
2762
2797
 
2763
2798
  async def _acontinue_run(
2764
2799
  self,
2765
- run_response: RunOutput,
2766
- run_messages: RunMessages,
2767
- session: AgentSession,
2800
+ session_id: str,
2801
+ run_response: Optional[RunOutput] = None,
2802
+ updated_tools: Optional[List[ToolExecution]] = None,
2803
+ knowledge_filters: Optional[Dict[str, Any]] = None,
2804
+ session_state: Optional[Dict[str, Any]] = None,
2805
+ run_id: Optional[str] = None,
2768
2806
  user_id: Optional[str] = None,
2769
2807
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
2770
2808
  dependencies: Optional[Dict[str, Any]] = None,
@@ -2774,175 +2812,408 @@ class Agent:
2774
2812
  """Continue a previous run.
2775
2813
 
2776
2814
  Steps:
2777
- 1. Handle any updated tools
2778
- 2. Generate a response from the Model
2779
- 3. Add the run to memory
2780
- 4. Update Agent Memory
2781
- 5. Calculate session metrics
2782
- 6. Save session to storage
2783
- 7. Save output to file if save_response_to_file is set
2815
+ 1. Read existing session from db
2816
+ 2. Resolve dependencies
2817
+ 3. Update metadata and session state
2818
+ 4. Prepare run response
2819
+ 5. Determine tools for model
2820
+ 6. Prepare run messages
2821
+ 7. Handle the updated tools
2822
+ 8. Get model response
2823
+ 9. Update the RunOutput with the model response
2824
+ 10. Calculate session metrics
2825
+ 11. Execute post-hooks
2826
+ 12. Update Agent Memory
2827
+ 13. Save session to storage
2784
2828
  """
2785
- # Resolving dependencies for async requirement
2829
+ log_debug(f"Agent Run Continue: {run_response.run_id}", center=True) # type: ignore
2830
+
2831
+ # 1. Read existing session from db
2832
+ if self._has_async_db():
2833
+ agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2834
+ else:
2835
+ agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2836
+
2837
+ # 2. Resolve dependencies
2786
2838
  if dependencies is not None:
2787
- await self._aresolve_run_dependencies(dependencies)
2839
+ await self._aresolve_run_dependencies(dependencies=dependencies)
2788
2840
 
2789
- self.model = cast(Model, self.model)
2841
+ # 3. Update metadata and session state
2842
+ self._update_metadata(session=agent_session)
2843
+ if session_state is not None:
2844
+ session_state = self._load_session_state(session=agent_session, session_state=session_state)
2790
2845
 
2791
- # 1. Handle the updated tools
2792
- await self._ahandle_tool_call_updates(run_response=run_response, run_messages=run_messages)
2846
+ # 4. Prepare run response
2847
+ if run_response is not None:
2848
+ # The run is continued from a provided run_response. This contains the updated tools.
2849
+ input = run_response.messages or []
2850
+ elif run_id is not None:
2851
+ # The run is continued from a run_id. This requires the updated tools to be passed.
2852
+ if updated_tools is None:
2853
+ raise ValueError("Updated tools are required to continue a run from a run_id.")
2793
2854
 
2794
- # 2. Generate a response from the Model (includes running function calls)
2795
- model_response: ModelResponse = await self.model.aresponse(
2796
- messages=run_messages.messages,
2797
- response_format=response_format,
2798
- tools=self._tools_for_model,
2799
- functions=self._functions_for_model,
2800
- tool_choice=self.tool_choice,
2801
- tool_call_limit=self.tool_call_limit,
2855
+ runs = agent_session.runs
2856
+ run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
2857
+ if run_response is None:
2858
+ raise RuntimeError(f"No runs found for run ID {run_id}")
2859
+ run_response.tools = updated_tools
2860
+ input = run_response.messages or []
2861
+ else:
2862
+ raise ValueError("Either run_response or run_id must be provided.")
2863
+
2864
+ run_response = cast(RunOutput, run_response)
2865
+ run_response.status = RunStatus.running
2866
+
2867
+ # 5. Determine tools for model
2868
+ self.model = cast(Model, self.model)
2869
+ await self._adetermine_tools_for_model(
2870
+ model=self.model,
2871
+ run_response=run_response,
2872
+ session=agent_session,
2873
+ session_state=session_state,
2874
+ dependencies=dependencies,
2875
+ user_id=user_id,
2876
+ async_mode=True,
2877
+ knowledge_filters=knowledge_filters,
2802
2878
  )
2803
2879
 
2804
- self._update_run_response(model_response=model_response, run_response=run_response, run_messages=run_messages)
2880
+ # 6. Prepare run messages
2881
+ run_messages: RunMessages = self._get_continue_run_messages(
2882
+ input=input,
2883
+ )
2805
2884
 
2806
- # We should break out of the run function
2807
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
2808
- return self._handle_agent_run_paused(
2809
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2885
+ # Register run for cancellation tracking
2886
+ register_run(run_response.run_id) # type: ignore
2887
+
2888
+ try:
2889
+ # 7. Handle the updated tools
2890
+ await self._ahandle_tool_call_updates(run_response=run_response, run_messages=run_messages)
2891
+
2892
+ # 8. Get model response
2893
+ model_response: ModelResponse = await self.model.aresponse(
2894
+ messages=run_messages.messages,
2895
+ response_format=response_format,
2896
+ tools=self._tools_for_model,
2897
+ functions=self._functions_for_model,
2898
+ tool_choice=self.tool_choice,
2899
+ tool_call_limit=self.tool_call_limit,
2810
2900
  )
2901
+ # Check for cancellation after model call
2902
+ raise_if_cancelled(run_response.run_id) # type: ignore
2811
2903
 
2812
- # 3. Calculate session metrics
2813
- self._update_session_metrics(session=session, run_response=run_response)
2904
+ # If an output model is provided, generate output using the output model
2905
+ await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
2814
2906
 
2815
- run_response.status = RunStatus.completed
2907
+ # If a parser model is provided, structure the response separately
2908
+ await self._aparse_response_with_parser_model(model_response=model_response, run_messages=run_messages)
2816
2909
 
2817
- # Convert the response to the structured format if needed
2818
- self._convert_response_to_structured_format(run_response)
2910
+ # 9. Update the RunOutput with the model response
2911
+ self._update_run_response(
2912
+ model_response=model_response, run_response=run_response, run_messages=run_messages
2913
+ )
2819
2914
 
2820
- # Set the run duration
2821
- if run_response.metrics:
2822
- run_response.metrics.stop_timer()
2915
+ if self.store_media:
2916
+ self._store_media(run_response, model_response)
2917
+ else:
2918
+ self._scrub_media_from_run_output(run_response)
2823
2919
 
2824
- if self.post_hooks is not None:
2825
- await self._aexecute_post_hooks(
2826
- hooks=self.post_hooks, # type: ignore
2827
- run_output=run_response,
2828
- session=session,
2920
+ # Break out of the run function if a tool call is paused
2921
+ if any(tool_call.is_paused for tool_call in run_response.tools or []):
2922
+ return self._handle_agent_run_paused(
2923
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
2924
+ )
2925
+ raise_if_cancelled(run_response.run_id) # type: ignore
2926
+
2927
+ # 10. Calculate session metrics
2928
+ self._update_session_metrics(session=agent_session, run_response=run_response)
2929
+
2930
+ run_response.status = RunStatus.completed
2931
+
2932
+ # 11. Execute post-hooks
2933
+ if self.post_hooks is not None:
2934
+ await self._aexecute_post_hooks(
2935
+ hooks=self.post_hooks, # type: ignore
2936
+ run_output=run_response,
2937
+ session=agent_session,
2938
+ user_id=user_id,
2939
+ debug_mode=debug_mode,
2940
+ **kwargs,
2941
+ )
2942
+
2943
+ # Convert the response to the structured format if needed
2944
+ self._convert_response_to_structured_format(run_response)
2945
+
2946
+ if run_response.metrics:
2947
+ run_response.metrics.stop_timer()
2948
+
2949
+ # 12. Update Agent Memory
2950
+ async for _ in self._amake_memories_and_summaries(
2951
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
2952
+ ):
2953
+ pass
2954
+
2955
+ # Optional: Save output to file if save_response_to_file is set
2956
+ self.save_run_response_to_file(
2957
+ run_response=run_response,
2958
+ input=run_messages.user_message,
2959
+ session_id=agent_session.session_id,
2829
2960
  user_id=user_id,
2830
- debug_mode=debug_mode,
2831
- **kwargs,
2832
2961
  )
2833
2962
 
2834
- # 4. Save output to file if save_response_to_file is set
2835
- self.save_run_response_to_file(
2836
- run_response=run_response, input=run_messages.user_message, session_id=session.session_id, user_id=user_id
2837
- )
2963
+ agent_session.upsert_run(run=run_response)
2838
2964
 
2839
- # 5. Add the run to memory
2840
- session.upsert_run(run=run_response)
2965
+ # 13. Save session to storage
2966
+ if self._has_async_db():
2967
+ await self.asave_session(session=agent_session)
2968
+ else:
2969
+ self.save_session(session=agent_session)
2841
2970
 
2842
- # 6. Update Agent Memory
2843
- async for _ in self._amake_memories_and_summaries(
2844
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2845
- ):
2846
- pass
2971
+ # Log Agent Telemetry
2972
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2847
2973
 
2848
- # 7. Save session to storage
2849
- self.save_session(session=session)
2974
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2850
2975
 
2851
- # Log Agent Telemetry
2852
- await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
2976
+ return run_response
2853
2977
 
2854
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2978
+ except RunCancelledException as e:
2979
+ # Handle run cancellation
2980
+ log_info(f"Run {run_response.run_id} was cancelled")
2981
+ run_response.content = str(e)
2982
+ run_response.status = RunStatus.cancelled
2855
2983
 
2856
- return run_response
2984
+ # Update the Agent Session before exiting
2985
+ agent_session.upsert_run(run=run_response)
2986
+ if self._has_async_db():
2987
+ await self.asave_session(session=agent_session)
2988
+ else:
2989
+ self.save_session(session=agent_session)
2990
+
2991
+ return run_response
2992
+ finally:
2993
+ # Always clean up the run tracking
2994
+ cleanup_run(run_response.run_id) # type: ignore
2857
2995
 
2858
2996
  async def _acontinue_run_stream(
2859
2997
  self,
2860
- run_response: RunOutput,
2861
- run_messages: RunMessages,
2862
- session: AgentSession,
2998
+ session_id: str,
2999
+ run_response: Optional[RunOutput] = None,
3000
+ updated_tools: Optional[List[ToolExecution]] = None,
3001
+ knowledge_filters: Optional[Dict[str, Any]] = None,
3002
+ session_state: Optional[Dict[str, Any]] = None,
3003
+ run_id: Optional[str] = None,
2863
3004
  user_id: Optional[str] = None,
2864
3005
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
2865
3006
  stream_intermediate_steps: bool = False,
3007
+ yield_run_response: Optional[bool] = None,
2866
3008
  dependencies: Optional[Dict[str, Any]] = None,
2867
3009
  ) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]:
2868
3010
  """Continue a previous run.
2869
3011
 
2870
3012
  Steps:
2871
- 1. Handle any updated tools
2872
- 2. Generate a response from the Model
2873
- 3. Calculate session metrics
2874
- 4. Save output to file if save_response_to_file is set
2875
- 5. Add the run to memory
2876
- 6. Update Agent Memory
2877
- 7. Create the run completed event
2878
- 8. Save session to storage
3013
+ 1. Resolve dependencies
3014
+ 2. Read existing session from db
3015
+ 3. Update session state and metadata
3016
+ 4. Prepare run response
3017
+ 5. Determine tools for model
3018
+ 6. Prepare run messages
3019
+ 7. Handle the updated tools
3020
+ 8. Process model response
3021
+ 9. Add the run to memory
3022
+ 10. Update Agent Memory
3023
+ 11. Calculate session metrics
3024
+ 12. Create the run completed event
3025
+ 13. Add the RunOutput to Agent Session
3026
+ 14. Save session to storage
2879
3027
  """
2880
- # Resolve dependencies
3028
+ log_debug(f"Agent Run Continue: {run_response.run_id}", center=True) # type: ignore
3029
+
3030
+ # 1. Resolve dependencies
2881
3031
  if dependencies is not None:
2882
3032
  await self._aresolve_run_dependencies(dependencies=dependencies)
2883
3033
 
2884
- # Start the Run by yielding a RunContinued event
2885
- if stream_intermediate_steps:
2886
- yield self._handle_event(create_run_continued_event(run_response), run_response)
2887
-
2888
- # 1. Handle the updated tools
2889
- async for event in self._ahandle_tool_call_updates_stream(run_response=run_response, run_messages=run_messages):
2890
- yield event
3034
+ # 2. Read existing session from db
3035
+ agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2891
3036
 
2892
- # 2. Process model response
2893
- async for event in self._ahandle_model_response_stream(
2894
- session=session,
2895
- run_response=run_response,
2896
- run_messages=run_messages,
2897
- response_format=response_format,
2898
- stream_intermediate_steps=stream_intermediate_steps,
2899
- ):
2900
- yield event
3037
+ # 3. Update session state and metadata
3038
+ self._update_metadata(session=agent_session)
3039
+ if session_state is not None:
3040
+ session_state = self._load_session_state(session=agent_session, session_state=session_state)
2901
3041
 
2902
- # We should break out of the run function
2903
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
2904
- for item in self._handle_agent_run_paused_stream(
2905
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2906
- ):
2907
- yield item
2908
- return
3042
+ # 4. Prepare run response
3043
+ if run_response is not None:
3044
+ # The run is continued from a provided run_response. This contains the updated tools.
3045
+ input = run_response.messages or []
3046
+ elif run_id is not None:
3047
+ # The run is continued from a run_id. This requires the updated tools to be passed.
3048
+ if updated_tools is None:
3049
+ raise ValueError("Updated tools are required to continue a run from a run_id.")
2909
3050
 
2910
- # 3. Calculate session metrics
2911
- self._update_session_metrics(session=session, run_response=run_response)
3051
+ runs = agent_session.runs
3052
+ run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
3053
+ if run_response is None:
3054
+ raise RuntimeError(f"No runs found for run ID {run_id}")
3055
+ run_response.tools = updated_tools
3056
+ input = run_response.messages or []
3057
+ else:
3058
+ raise ValueError("Either run_response or run_id must be provided.")
2912
3059
 
2913
- run_response.status = RunStatus.completed
3060
+ run_response = cast(RunOutput, run_response)
3061
+ run_response.status = RunStatus.running
2914
3062
 
2915
- # Set the run duration
2916
- if run_response.metrics:
2917
- run_response.metrics.stop_timer()
3063
+ # 5. Determine tools for model
3064
+ self.model = cast(Model, self.model)
3065
+ await self._adetermine_tools_for_model(
3066
+ model=self.model,
3067
+ run_response=run_response,
3068
+ session=agent_session,
3069
+ session_state=session_state,
3070
+ dependencies=dependencies,
3071
+ user_id=user_id,
3072
+ async_mode=True,
3073
+ knowledge_filters=knowledge_filters,
3074
+ )
2918
3075
 
2919
- # 4. Save output to file if save_response_to_file is set
2920
- self.save_run_response_to_file(
2921
- run_response=run_response, input=run_messages.user_message, session_id=session.session_id, user_id=user_id
3076
+ # 6. Prepare run messages
3077
+ run_messages: RunMessages = self._get_continue_run_messages(
3078
+ input=input,
2922
3079
  )
2923
3080
 
2924
- # 5. Add the run to memory
2925
- session.upsert_run(run=run_response)
3081
+ # Register run for cancellation tracking
3082
+ register_run(run_response.run_id) # type: ignore
2926
3083
 
2927
- # 6. Update Agent Memory
2928
- async for event in self._amake_memories_and_summaries(
2929
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2930
- ):
2931
- yield event
3084
+ try:
3085
+ # Start the Run by yielding a RunContinued event
3086
+ if stream_intermediate_steps:
3087
+ yield self._handle_event(create_run_continued_event(run_response), run_response)
2932
3088
 
2933
- # 7. Create the run completed event
2934
- completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
3089
+ # 7. Handle the updated tools
3090
+ async for event in self._ahandle_tool_call_updates_stream(
3091
+ run_response=run_response, run_messages=run_messages
3092
+ ):
3093
+ raise_if_cancelled(run_response.run_id) # type: ignore
3094
+ yield event
2935
3095
 
2936
- # 8. Save session to storage
2937
- self.save_session(session=session)
3096
+ # 8. Process model response
3097
+ if self.output_model is None:
3098
+ async for event in self._ahandle_model_response_stream(
3099
+ session=agent_session,
3100
+ run_response=run_response,
3101
+ run_messages=run_messages,
3102
+ response_format=response_format,
3103
+ stream_intermediate_steps=stream_intermediate_steps,
3104
+ ):
3105
+ raise_if_cancelled(run_response.run_id) # type: ignore
3106
+ yield event
3107
+ else:
3108
+ from agno.run.agent import (
3109
+ IntermediateRunContentEvent,
3110
+ RunContentEvent,
3111
+ ) # type: ignore
2938
3112
 
2939
- if stream_intermediate_steps:
2940
- yield completed_event
3113
+ async for event in self._ahandle_model_response_stream(
3114
+ session=agent_session,
3115
+ run_response=run_response,
3116
+ run_messages=run_messages,
3117
+ response_format=response_format,
3118
+ stream_intermediate_steps=stream_intermediate_steps,
3119
+ ):
3120
+ raise_if_cancelled(run_response.run_id) # type: ignore
3121
+ if isinstance(event, RunContentEvent):
3122
+ if stream_intermediate_steps:
3123
+ yield IntermediateRunContentEvent(
3124
+ content=event.content,
3125
+ content_type=event.content_type,
3126
+ )
3127
+ else:
3128
+ yield event
2941
3129
 
2942
- # Log Agent Telemetry
2943
- await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
3130
+ # If an output model is provided, generate output using the output model
3131
+ async for event in self._agenerate_response_with_output_model_stream(
3132
+ session=agent_session,
3133
+ run_response=run_response,
3134
+ run_messages=run_messages,
3135
+ stream_intermediate_steps=stream_intermediate_steps,
3136
+ ):
3137
+ raise_if_cancelled(run_response.run_id) # type: ignore
3138
+ yield event
2944
3139
 
2945
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
3140
+ # Check for cancellation after model processing
3141
+ raise_if_cancelled(run_response.run_id) # type: ignore
3142
+
3143
+ # Break out of the run function if a tool call is paused
3144
+ if any(tool_call.is_paused for tool_call in run_response.tools or []):
3145
+ for item in self._handle_agent_run_paused_stream(
3146
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
3147
+ ):
3148
+ yield item
3149
+ return
3150
+
3151
+ run_response.status = RunStatus.completed
3152
+
3153
+ # 9. Create the run completed event
3154
+ completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
3155
+
3156
+ # Set the run duration
3157
+ if run_response.metrics:
3158
+ run_response.metrics.stop_timer()
3159
+
3160
+ # 10. Add the run to memory
3161
+ agent_session.upsert_run(run=run_response)
3162
+
3163
+ # Optional: Save output to file if save_response_to_file is set
3164
+ self.save_run_response_to_file(
3165
+ run_response=run_response,
3166
+ input=run_messages.user_message,
3167
+ session_id=agent_session.session_id,
3168
+ user_id=user_id,
3169
+ )
3170
+
3171
+ # 11. Calculate session metrics
3172
+ self._update_session_metrics(session=agent_session, run_response=run_response)
3173
+
3174
+ # 12. Update Agent Memory
3175
+ async for event in self._amake_memories_and_summaries(
3176
+ run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
3177
+ ):
3178
+ yield event
3179
+
3180
+ # 13. Save session to storage
3181
+ if self._has_async_db():
3182
+ await self.asave_session(session=agent_session)
3183
+ else:
3184
+ self.save_session(session=agent_session)
3185
+
3186
+ if stream_intermediate_steps:
3187
+ yield completed_event
3188
+
3189
+ if yield_run_response:
3190
+ yield run_response
3191
+
3192
+ # Log Agent Telemetry
3193
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
3194
+
3195
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
3196
+ except RunCancelledException as e:
3197
+ # Handle run cancellation during streaming
3198
+ log_info(f"Run {run_response.run_id} was cancelled during streaming")
3199
+ run_response.status = RunStatus.cancelled
3200
+ run_response.content = str(e)
3201
+
3202
+ # Yield the cancellation event
3203
+ yield self._handle_event(
3204
+ create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
3205
+ run_response,
3206
+ )
3207
+
3208
+ # Add the RunOutput to Agent Session even when cancelled
3209
+ agent_session.upsert_run(run=run_response)
3210
+ if self._has_async_db():
3211
+ await self.asave_session(session=agent_session)
3212
+ else:
3213
+ self.save_session(session=agent_session)
3214
+ finally:
3215
+ # Always clean up the run tracking
3216
+ cleanup_run(run_response.run_id) # type: ignore
2946
3217
 
2947
3218
  def _execute_pre_hooks(
2948
3219
  self,
@@ -4125,7 +4396,7 @@ class Agent:
4125
4396
 
4126
4397
  tasks.append(
4127
4398
  self.memory_manager.acreate_user_memories(
4128
- message=run_messages.user_message.get_content_string(), user_id=user_id
4399
+ message=run_messages.user_message.get_content_string(), user_id=user_id, agent_id=self.id
4129
4400
  )
4130
4401
  )
4131
4402
 
@@ -4149,7 +4420,9 @@ class Agent:
4149
4420
  continue
4150
4421
 
4151
4422
  if len(parsed_messages) > 0:
4152
- tasks.append(self.memory_manager.acreate_user_memories(messages=parsed_messages, user_id=user_id))
4423
+ tasks.append(
4424
+ self.memory_manager.acreate_user_memories(messages=parsed_messages, user_id=user_id, agent_id=self.id)
4425
+ )
4153
4426
  else:
4154
4427
  log_warning("Unable to add messages to memory")
4155
4428
 
@@ -4298,6 +4571,100 @@ class Agent:
4298
4571
 
4299
4572
  return agent_tools
4300
4573
 
4574
+ async def aget_tools(
4575
+ self,
4576
+ run_response: RunOutput,
4577
+ session: AgentSession,
4578
+ async_mode: bool = False,
4579
+ user_id: Optional[str] = None,
4580
+ knowledge_filters: Optional[Dict[str, Any]] = None,
4581
+ ) -> Optional[List[Union[Toolkit, Callable, Function, Dict]]]:
4582
+ agent_tools: List[Union[Toolkit, Callable, Function, Dict]] = []
4583
+
4584
+ # Add provided tools
4585
+ if self.tools is not None:
4586
+ agent_tools.extend(self.tools)
4587
+
4588
+ # If any of the tools has "agent" as parameter, set _rebuild_tools to True
4589
+ for tool in agent_tools:
4590
+ if isinstance(tool, Function):
4591
+ if "agent" in tool.parameters:
4592
+ self._rebuild_tools = True
4593
+ break
4594
+ if "team" in tool.parameters:
4595
+ self._rebuild_tools = True
4596
+ break
4597
+ if isinstance(tool, Toolkit):
4598
+ for func in tool.functions.values():
4599
+ if "agent" in func.parameters:
4600
+ self._rebuild_tools = True
4601
+ break
4602
+ if "team" in func.parameters:
4603
+ self._rebuild_tools = True
4604
+ break
4605
+ if callable(tool):
4606
+ from inspect import signature
4607
+
4608
+ sig = signature(tool)
4609
+ if "agent" in sig.parameters:
4610
+ self._rebuild_tools = True
4611
+ break
4612
+ if "team" in sig.parameters:
4613
+ self._rebuild_tools = True
4614
+ break
4615
+
4616
+ # Add tools for accessing memory
4617
+ if self.read_chat_history:
4618
+ agent_tools.append(self._get_chat_history_function(session=session))
4619
+ self._rebuild_tools = True
4620
+ if self.read_tool_call_history:
4621
+ agent_tools.append(self._get_tool_call_history_function(session=session))
4622
+ self._rebuild_tools = True
4623
+ if self.search_session_history:
4624
+ agent_tools.append(
4625
+ await self._aget_previous_sessions_messages_function(num_history_sessions=self.num_history_sessions)
4626
+ )
4627
+ self._rebuild_tools = True
4628
+
4629
+ if self.enable_agentic_memory:
4630
+ agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=async_mode))
4631
+ self._rebuild_tools = True
4632
+
4633
+ if self.enable_agentic_state:
4634
+ agent_tools.append(self.update_session_state)
4635
+
4636
+ # Add tools for accessing knowledge
4637
+ if self.knowledge is not None or self.knowledge_retriever is not None:
4638
+ # Check if knowledge retriever is an async function but used in sync mode
4639
+ from inspect import iscoroutinefunction
4640
+
4641
+ if not async_mode and self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
4642
+ log_warning(
4643
+ "Async knowledge retriever function is being used with synchronous agent.run() or agent.print_response(). "
4644
+ "It is recommended to use agent.arun() or agent.aprint_response() instead."
4645
+ )
4646
+
4647
+ if self.search_knowledge:
4648
+ # Use async or sync search based on async_mode
4649
+ if self.enable_agentic_knowledge_filters:
4650
+ agent_tools.append(
4651
+ self._search_knowledge_base_with_agentic_filters_function(
4652
+ run_response=run_response, async_mode=async_mode, knowledge_filters=knowledge_filters
4653
+ )
4654
+ )
4655
+ else:
4656
+ agent_tools.append(
4657
+ self._get_search_knowledge_base_function(
4658
+ run_response=run_response, async_mode=async_mode, knowledge_filters=knowledge_filters
4659
+ )
4660
+ )
4661
+ self._rebuild_tools = True
4662
+
4663
+ if self.update_knowledge:
4664
+ agent_tools.append(self.add_to_knowledge)
4665
+
4666
+ return agent_tools
4667
+
4301
4668
  def _collect_joint_images(
4302
4669
  self,
4303
4670
  run_input: Optional[RunInput] = None,
@@ -4551,43 +4918,165 @@ class Agent:
4551
4918
  func._audios = joint_audios
4552
4919
  func._videos = joint_videos
4553
4920
 
4554
- def _model_should_return_structured_output(self):
4555
- self.model = cast(Model, self.model)
4556
- return bool(
4557
- self.model.supports_native_structured_outputs
4558
- and self.output_schema is not None
4559
- and (not self.use_json_mode or self.structured_outputs)
4560
- )
4921
+ async def _adetermine_tools_for_model(
4922
+ self,
4923
+ model: Model,
4924
+ run_response: RunOutput,
4925
+ session: AgentSession,
4926
+ session_state: Optional[Dict[str, Any]] = None,
4927
+ dependencies: Optional[Dict[str, Any]] = None,
4928
+ user_id: Optional[str] = None,
4929
+ async_mode: bool = False,
4930
+ knowledge_filters: Optional[Dict[str, Any]] = None,
4931
+ ) -> None:
4932
+ if self._rebuild_tools:
4933
+ self._rebuild_tools = False
4561
4934
 
4562
- def _get_response_format(self, model: Optional[Model] = None) -> Optional[Union[Dict, Type[BaseModel]]]:
4563
- model = cast(Model, model or self.model)
4564
- if self.output_schema is None:
4565
- return None
4566
- else:
4567
- json_response_format = {"type": "json_object"}
4935
+ agent_tools = await self.aget_tools(
4936
+ run_response=run_response,
4937
+ session=session,
4938
+ async_mode=async_mode,
4939
+ user_id=user_id,
4940
+ knowledge_filters=knowledge_filters,
4941
+ )
4568
4942
 
4569
- if model.supports_native_structured_outputs:
4570
- if not self.use_json_mode or self.structured_outputs:
4571
- log_debug("Setting Model.response_format to Agent.output_schema")
4572
- return self.output_schema
4573
- else:
4574
- log_debug(
4575
- "Model supports native structured outputs but it is not enabled. Using JSON mode instead."
4576
- )
4577
- return json_response_format
4943
+ self._tools_for_model = []
4944
+ self._functions_for_model = {}
4945
+ self._tool_instructions = []
4578
4946
 
4579
- elif model.supports_json_schema_outputs:
4580
- if self.use_json_mode or (not self.structured_outputs):
4581
- log_debug("Setting Model.response_format to JSON response mode")
4582
- return {
4583
- "type": "json_schema",
4584
- "json_schema": {
4585
- "name": self.output_schema.__name__,
4586
- "schema": self.output_schema.model_json_schema(),
4587
- },
4588
- }
4589
- else:
4590
- return None
4947
+ # Get Agent tools
4948
+ if agent_tools is not None and len(agent_tools) > 0:
4949
+ log_debug("Processing tools for model")
4950
+
4951
+ # Check if we need strict mode for the functions for the model
4952
+ strict = False
4953
+ if (
4954
+ self.output_schema is not None
4955
+ and (self.structured_outputs or (not self.use_json_mode))
4956
+ and model.supports_native_structured_outputs
4957
+ ):
4958
+ strict = True
4959
+
4960
+ for tool in agent_tools:
4961
+ if isinstance(tool, Dict):
4962
+ # If a dict is passed, it is a builtin tool
4963
+ # that is run by the model provider and not the Agent
4964
+ self._tools_for_model.append(tool)
4965
+ log_debug(f"Included builtin tool {tool}")
4966
+
4967
+ elif isinstance(tool, Toolkit):
4968
+ # For each function in the toolkit and process entrypoint
4969
+ for name, func in tool.functions.items():
4970
+ # If the function does not exist in self.functions
4971
+ if name not in self._functions_for_model:
4972
+ func._agent = self
4973
+ func.process_entrypoint(strict=strict)
4974
+ if strict and func.strict is None:
4975
+ func.strict = True
4976
+ if self.tool_hooks is not None:
4977
+ func.tool_hooks = self.tool_hooks
4978
+ self._functions_for_model[name] = func
4979
+ self._tools_for_model.append({"type": "function", "function": func.to_dict()})
4980
+ log_debug(f"Added tool {name} from {tool.name}")
4981
+
4982
+ # Add instructions from the toolkit
4983
+ if tool.add_instructions and tool.instructions is not None:
4984
+ self._tool_instructions.append(tool.instructions)
4985
+
4986
+ elif isinstance(tool, Function):
4987
+ if tool.name not in self._functions_for_model:
4988
+ tool._agent = self
4989
+ tool.process_entrypoint(strict=strict)
4990
+ if strict and tool.strict is None:
4991
+ tool.strict = True
4992
+ if self.tool_hooks is not None:
4993
+ tool.tool_hooks = self.tool_hooks
4994
+ self._functions_for_model[tool.name] = tool
4995
+ self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
4996
+ log_debug(f"Added tool {tool.name}")
4997
+
4998
+ # Add instructions from the Function
4999
+ if tool.add_instructions and tool.instructions is not None:
5000
+ self._tool_instructions.append(tool.instructions)
5001
+
5002
+ elif callable(tool):
5003
+ try:
5004
+ function_name = tool.__name__
5005
+ if function_name not in self._functions_for_model:
5006
+ func = Function.from_callable(tool, strict=strict)
5007
+ func._agent = self
5008
+ if strict:
5009
+ func.strict = True
5010
+ if self.tool_hooks is not None:
5011
+ func.tool_hooks = self.tool_hooks
5012
+ self._functions_for_model[func.name] = func
5013
+ self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5014
+ log_debug(f"Added tool {func.name}")
5015
+ except Exception as e:
5016
+ log_warning(f"Could not add tool {tool}: {e}")
5017
+
5018
+ # Update the session state for the functions
5019
+ if self._functions_for_model:
5020
+ from inspect import signature
5021
+
5022
+ # Check if any functions need media before collecting
5023
+ needs_media = any(
5024
+ any(param in signature(func.entrypoint).parameters for param in ["images", "videos", "audios", "files"])
5025
+ for func in self._functions_for_model.values()
5026
+ if func.entrypoint is not None
5027
+ )
5028
+
5029
+ # Only collect media if functions actually need them
5030
+ joint_images = self._collect_joint_images(run_response.input, session) if needs_media else None
5031
+ joint_files = self._collect_joint_files(run_response.input) if needs_media else None
5032
+ joint_audios = self._collect_joint_audios(run_response.input, session) if needs_media else None
5033
+ joint_videos = self._collect_joint_videos(run_response.input, session) if needs_media else None
5034
+
5035
+ for func in self._functions_for_model.values():
5036
+ func._session_state = session_state
5037
+ func._dependencies = dependencies
5038
+ func._images = joint_images
5039
+ func._files = joint_files
5040
+ func._audios = joint_audios
5041
+ func._videos = joint_videos
5042
+
5043
+ def _model_should_return_structured_output(self):
5044
+ self.model = cast(Model, self.model)
5045
+ return bool(
5046
+ self.model.supports_native_structured_outputs
5047
+ and self.output_schema is not None
5048
+ and (not self.use_json_mode or self.structured_outputs)
5049
+ )
5050
+
5051
+ def _get_response_format(self, model: Optional[Model] = None) -> Optional[Union[Dict, Type[BaseModel]]]:
5052
+ model = cast(Model, model or self.model)
5053
+ if self.output_schema is None:
5054
+ return None
5055
+ else:
5056
+ json_response_format = {"type": "json_object"}
5057
+
5058
+ if model.supports_native_structured_outputs:
5059
+ if not self.use_json_mode or self.structured_outputs:
5060
+ log_debug("Setting Model.response_format to Agent.output_schema")
5061
+ return self.output_schema
5062
+ else:
5063
+ log_debug(
5064
+ "Model supports native structured outputs but it is not enabled. Using JSON mode instead."
5065
+ )
5066
+ return json_response_format
5067
+
5068
+ elif model.supports_json_schema_outputs:
5069
+ if self.use_json_mode or (not self.structured_outputs):
5070
+ log_debug("Setting Model.response_format to JSON response mode")
5071
+ return {
5072
+ "type": "json_schema",
5073
+ "json_schema": {
5074
+ "name": self.output_schema.__name__,
5075
+ "schema": self.output_schema.model_json_schema(),
5076
+ },
5077
+ }
5078
+ else:
5079
+ return None
4591
5080
 
4592
5081
  else:
4593
5082
  log_debug("Model does not support structured or JSON schema outputs.")
@@ -4660,6 +5149,16 @@ class Agent:
4660
5149
  log_warning(f"Error getting session from db: {e}")
4661
5150
  return None
4662
5151
 
5152
+ async def _aread_session(self, session_id: str) -> Optional[AgentSession]:
5153
+ """Get a Session from the database."""
5154
+ try:
5155
+ if not self.db:
5156
+ raise ValueError("Db not initialized")
5157
+ return await self.db.get_session(session_id=session_id, session_type=SessionType.AGENT) # type: ignore
5158
+ except Exception as e:
5159
+ log_warning(f"Error getting session from db: {e}")
5160
+ return None
5161
+
4663
5162
  def _upsert_session(self, session: AgentSession) -> Optional[AgentSession]:
4664
5163
  """Upsert a Session into the database."""
4665
5164
 
@@ -4671,6 +5170,16 @@ class Agent:
4671
5170
  log_warning(f"Error upserting session into db: {e}")
4672
5171
  return None
4673
5172
 
5173
+ async def _aupsert_session(self, session: AgentSession) -> Optional[AgentSession]:
5174
+ """Upsert a Session into the database."""
5175
+ try:
5176
+ if not self.db:
5177
+ raise ValueError("Db not initialized")
5178
+ return await self.db.upsert_session(session=session) # type: ignore
5179
+ except Exception as e:
5180
+ log_warning(f"Error upserting session into db: {e}")
5181
+ return None
5182
+
4674
5183
  def _load_session_state(self, session: AgentSession, session_state: Dict[str, Any]):
4675
5184
  """Load and return the stored session_state from the database, optionally merging it with the given one"""
4676
5185
 
@@ -4756,6 +5265,42 @@ class Agent:
4756
5265
 
4757
5266
  return agent_session
4758
5267
 
5268
+ async def _aread_or_create_session(
5269
+ self,
5270
+ session_id: str,
5271
+ user_id: Optional[str] = None,
5272
+ ) -> AgentSession:
5273
+ from time import time
5274
+
5275
+ # Returning cached session if we have one
5276
+ if self._agent_session is not None and self._agent_session.session_id == session_id:
5277
+ return self._agent_session
5278
+
5279
+ # Try to load from database
5280
+ agent_session = None
5281
+ if self.db is not None and self.team_id is None and self.workflow_id is None:
5282
+ log_debug(f"Reading AgentSession: {session_id}")
5283
+
5284
+ agent_session = cast(AgentSession, await self._aread_session(session_id=session_id))
5285
+
5286
+ if agent_session is None:
5287
+ # Creating new session if none found
5288
+ log_debug(f"Creating new AgentSession: {session_id}")
5289
+ agent_session = AgentSession(
5290
+ session_id=session_id,
5291
+ agent_id=self.id,
5292
+ user_id=user_id,
5293
+ agent_data=self._get_agent_data(),
5294
+ session_data={},
5295
+ metadata=self.metadata,
5296
+ created_at=int(time()),
5297
+ )
5298
+
5299
+ if self.cache_session:
5300
+ self._agent_session = agent_session
5301
+
5302
+ return agent_session
5303
+
4759
5304
  def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
4760
5305
  """
4761
5306
  Get a RunOutput from the database.
@@ -4876,6 +5421,27 @@ class Agent:
4876
5421
  self._upsert_session(session=session)
4877
5422
  log_debug(f"Created or updated AgentSession record: {session.session_id}")
4878
5423
 
5424
+ async def asave_session(self, session: AgentSession) -> None:
5425
+ """Save the AgentSession to storage
5426
+
5427
+ Returns:
5428
+ Optional[AgentSession]: The saved AgentSession or None if not saved.
5429
+ """
5430
+ # If the agent is a member of a team, do not save the session to the database
5431
+ if (
5432
+ self.db is not None
5433
+ and self.team_id is None
5434
+ and self.workflow_id is None
5435
+ and session.session_data is not None
5436
+ ):
5437
+ if session.session_data is not None and "session_state" in session.session_data:
5438
+ session.session_data["session_state"].pop("current_session_id", None)
5439
+ session.session_data["session_state"].pop("current_user_id", None)
5440
+ session.session_data["session_state"].pop("current_run_id", None)
5441
+
5442
+ await self._aupsert_session(session=session)
5443
+ log_debug(f"Created or updated AgentSession record: {session.session_id}")
5444
+
4879
5445
  def get_chat_history(self, session_id: Optional[str] = None) -> List[Message]:
4880
5446
  """Read the chat history from the session"""
4881
5447
  if not session_id and not self.session_id:
@@ -5034,8 +5600,7 @@ class Agent:
5034
5600
  session = self.get_session(session_id=session_id) # type: ignore
5035
5601
 
5036
5602
  if session is None:
5037
- log_warning(f"Session {session_id} not found")
5038
- return []
5603
+ raise Exception("Session not found")
5039
5604
 
5040
5605
  # Only filter by agent_id if this is part of a team
5041
5606
  return session.get_messages_from_last_n_runs(
@@ -5065,6 +5630,16 @@ class Agent:
5065
5630
 
5066
5631
  return self.memory_manager.get_user_memories(user_id=user_id)
5067
5632
 
5633
+ async def aget_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
5634
+ """Get the user memories for the given user ID."""
5635
+ if self.memory_manager is None:
5636
+ return None
5637
+ user_id = user_id if user_id is not None else self.user_id
5638
+ if user_id is None:
5639
+ user_id = "default"
5640
+
5641
+ return await self.memory_manager.aget_user_memories(user_id=user_id)
5642
+
5068
5643
  def _format_message_with_state_variables(
5069
5644
  self,
5070
5645
  message: Any,
@@ -5078,40 +5653,313 @@ class Agent:
5078
5653
  import string
5079
5654
  from copy import deepcopy
5080
5655
 
5081
- if not isinstance(message, str):
5082
- return message
5656
+ if not isinstance(message, str):
5657
+ return message
5658
+
5659
+ # Should already be resolved and passed from run() method
5660
+ format_variables = ChainMap(
5661
+ session_state or {},
5662
+ dependencies or {},
5663
+ metadata or {},
5664
+ {"user_id": user_id} if user_id is not None else {},
5665
+ )
5666
+ converted_msg = deepcopy(message)
5667
+ for var_name in format_variables.keys():
5668
+ # Only convert standalone {var_name} patterns, not nested ones
5669
+ pattern = r"\{" + re.escape(var_name) + r"\}"
5670
+ replacement = "${" + var_name + "}"
5671
+ converted_msg = re.sub(pattern, replacement, converted_msg)
5672
+
5673
+ # Use Template to safely substitute variables
5674
+ template = string.Template(converted_msg)
5675
+ try:
5676
+ result = template.safe_substitute(format_variables)
5677
+ return result
5678
+ except Exception as e:
5679
+ log_warning(f"Template substitution failed: {e}")
5680
+ return message
5681
+
5682
+ def get_system_message(
5683
+ self,
5684
+ session: AgentSession,
5685
+ session_state: Optional[Dict[str, Any]] = None,
5686
+ user_id: Optional[str] = None,
5687
+ dependencies: Optional[Dict[str, Any]] = None,
5688
+ metadata: Optional[Dict[str, Any]] = None,
5689
+ add_session_state_to_context: Optional[bool] = None,
5690
+ ) -> Optional[Message]:
5691
+ """Return the system message for the Agent.
5692
+
5693
+ 1. If the system_message is provided, use that.
5694
+ 2. If build_context is False, return None.
5695
+ 3. Build and return the default system message for the Agent.
5696
+ """
5697
+
5698
+ # 1. If the system_message is provided, use that.
5699
+ if self.system_message is not None:
5700
+ if isinstance(self.system_message, Message):
5701
+ return self.system_message
5702
+
5703
+ sys_message_content: str = ""
5704
+ if isinstance(self.system_message, str):
5705
+ sys_message_content = self.system_message
5706
+ elif callable(self.system_message):
5707
+ sys_message_content = self.system_message(agent=self)
5708
+ if not isinstance(sys_message_content, str):
5709
+ raise Exception("system_message must return a string")
5710
+
5711
+ # Format the system message with the session state variables
5712
+ if self.resolve_in_context:
5713
+ sys_message_content = self._format_message_with_state_variables(
5714
+ sys_message_content,
5715
+ user_id=user_id,
5716
+ dependencies=dependencies,
5717
+ metadata=metadata,
5718
+ session_state=session_state,
5719
+ )
5720
+
5721
+ # type: ignore
5722
+ return Message(role=self.system_message_role, content=sys_message_content)
5723
+
5724
+ # 2. If build_context is False, return None.
5725
+ if not self.build_context:
5726
+ return None
5727
+
5728
+ if self.model is None:
5729
+ raise Exception("model not set")
5730
+
5731
+ # 3. Build and return the default system message for the Agent.
5732
+ # 3.1 Build the list of instructions for the system message
5733
+ instructions: List[str] = []
5734
+ if self.instructions is not None:
5735
+ _instructions = self.instructions
5736
+ if callable(self.instructions):
5737
+ import inspect
5738
+
5739
+ signature = inspect.signature(self.instructions)
5740
+ instruction_args: Dict[str, Any] = {}
5741
+
5742
+ # Check for agent parameter
5743
+ if "agent" in signature.parameters:
5744
+ instruction_args["agent"] = self
5745
+
5746
+ # Check for session_state parameter
5747
+ if "session_state" in signature.parameters:
5748
+ instruction_args["session_state"] = session_state or {}
5749
+
5750
+ _instructions = self.instructions(**instruction_args)
5751
+
5752
+ if isinstance(_instructions, str):
5753
+ instructions.append(_instructions)
5754
+ elif isinstance(_instructions, list):
5755
+ instructions.extend(_instructions)
5756
+
5757
+ # 3.1.1 Add instructions from the Model
5758
+ _model_instructions = self.model.get_instructions_for_model(self._tools_for_model)
5759
+ if _model_instructions is not None:
5760
+ instructions.extend(_model_instructions)
5761
+
5762
+ # 3.2 Build a list of additional information for the system message
5763
+ additional_information: List[str] = []
5764
+ # 3.2.1 Add instructions for using markdown
5765
+ if self.markdown and self.output_schema is None:
5766
+ additional_information.append("Use markdown to format your answers.")
5767
+ # 3.2.2 Add the current datetime
5768
+ if self.add_datetime_to_context:
5769
+ from datetime import datetime
5770
+
5771
+ tz = None
5772
+
5773
+ if self.timezone_identifier:
5774
+ try:
5775
+ from zoneinfo import ZoneInfo
5776
+
5777
+ tz = ZoneInfo(self.timezone_identifier)
5778
+ except Exception:
5779
+ log_warning("Invalid timezone identifier")
5780
+
5781
+ time = datetime.now(tz) if tz else datetime.now()
5782
+
5783
+ additional_information.append(f"The current time is {time}.")
5784
+
5785
+ # 3.2.3 Add the current location
5786
+ if self.add_location_to_context:
5787
+ from agno.utils.location import get_location
5788
+
5789
+ location = get_location()
5790
+ if location:
5791
+ location_str = ", ".join(
5792
+ filter(None, [location.get("city"), location.get("region"), location.get("country")])
5793
+ )
5794
+ if location_str:
5795
+ additional_information.append(f"Your approximate location is: {location_str}.")
5796
+
5797
+ # 3.2.4 Add agent name if provided
5798
+ if self.name is not None and self.add_name_to_context:
5799
+ additional_information.append(f"Your name is: {self.name}.")
5800
+
5801
+ # 3.2.5 Add information about agentic filters if enabled
5802
+ if self.knowledge is not None and self.enable_agentic_knowledge_filters:
5803
+ valid_filters = self.knowledge.get_valid_filters()
5804
+ if valid_filters:
5805
+ valid_filters_str = ", ".join(valid_filters)
5806
+ additional_information.append(
5807
+ dedent(f"""
5808
+ The knowledge base contains documents with these metadata filters: {valid_filters_str}.
5809
+ Always use filters when the user query indicates specific metadata.
5810
+
5811
+ Examples:
5812
+ 1. If the user asks about a specific person like "Jordan Mitchell", you MUST use the search_knowledge_base tool with the filters parameter set to {{'<valid key like user_id>': '<valid value based on the user query>'}}.
5813
+ 2. If the user asks about a specific document type like "contracts", you MUST use the search_knowledge_base tool with the filters parameter set to {{'document_type': 'contract'}}.
5814
+ 4. If the user asks about a specific location like "documents from New York", you MUST use the search_knowledge_base tool with the filters parameter set to {{'<valid key like location>': 'New York'}}.
5815
+
5816
+ General Guidelines:
5817
+ - Always analyze the user query to identify relevant metadata.
5818
+ - Use the most specific filter(s) possible to narrow down results.
5819
+ - If multiple filters are relevant, combine them in the filters parameter (e.g., {{'name': 'Jordan Mitchell', 'document_type': 'contract'}}).
5820
+ - Ensure the filter keys match the valid metadata filters: {valid_filters_str}.
5821
+
5822
+ You can use the search_knowledge_base tool to search the knowledge base and get the most relevant documents. Make sure to pass the filters as [Dict[str: Any]] to the tool. FOLLOW THIS STRUCTURE STRICTLY.
5823
+ """)
5824
+ )
5825
+
5826
+ # 3.3 Build the default system message for the Agent.
5827
+ system_message_content: str = ""
5828
+ # 3.3.1 First add the Agent description if provided
5829
+ if self.description is not None:
5830
+ system_message_content += f"{self.description}\n"
5831
+ # 3.3.2 Then add the Agent role if provided
5832
+ if self.role is not None:
5833
+ system_message_content += f"\n<your_role>\n{self.role}\n</your_role>\n\n"
5834
+ # 3.3.4 Then add instructions for the Agent
5835
+ if len(instructions) > 0:
5836
+ system_message_content += "<instructions>"
5837
+ if len(instructions) > 1:
5838
+ for _upi in instructions:
5839
+ system_message_content += f"\n- {_upi}"
5840
+ else:
5841
+ system_message_content += "\n" + instructions[0]
5842
+ system_message_content += "\n</instructions>\n\n"
5843
+ # 3.3.6 Add additional information
5844
+ if len(additional_information) > 0:
5845
+ system_message_content += "<additional_information>"
5846
+ for _ai in additional_information:
5847
+ system_message_content += f"\n- {_ai}"
5848
+ system_message_content += "\n</additional_information>\n\n"
5849
+ # 3.3.7 Then add instructions for the tools
5850
+ if self._tool_instructions is not None:
5851
+ for _ti in self._tool_instructions:
5852
+ system_message_content += f"{_ti}\n"
5853
+
5854
+ # Format the system message with the session state variables
5855
+ if self.resolve_in_context:
5856
+ system_message_content = self._format_message_with_state_variables(
5857
+ system_message_content,
5858
+ user_id=user_id,
5859
+ session_state=session_state,
5860
+ dependencies=dependencies,
5861
+ metadata=metadata,
5862
+ )
5863
+
5864
+ # 3.3.7 Then add the expected output
5865
+ if self.expected_output is not None:
5866
+ system_message_content += f"<expected_output>\n{self.expected_output.strip()}\n</expected_output>\n\n"
5867
+ # 3.3.8 Then add additional context
5868
+ if self.additional_context is not None:
5869
+ system_message_content += f"{self.additional_context}\n"
5870
+ # 3.3.9 Then add memories to the system prompt
5871
+ if self.add_memories_to_context:
5872
+ _memory_manager_not_set = False
5873
+ if not user_id:
5874
+ user_id = "default"
5875
+ if self.memory_manager is None:
5876
+ self._set_memory_manager()
5877
+ _memory_manager_not_set = True
5878
+
5879
+ user_memories = self.memory_manager.get_user_memories(user_id=user_id) # type: ignore
5880
+
5881
+ if user_memories and len(user_memories) > 0:
5882
+ system_message_content += (
5883
+ "You have access to memories from previous interactions with the user that you can use:\n\n"
5884
+ )
5885
+ system_message_content += "<memories_from_previous_interactions>"
5886
+ for _memory in user_memories: # type: ignore
5887
+ system_message_content += f"\n- {_memory.memory}"
5888
+ system_message_content += "\n</memories_from_previous_interactions>\n\n"
5889
+ system_message_content += (
5890
+ "Note: this information is from previous interactions and may be updated in this conversation. "
5891
+ "You should always prefer information from this conversation over the past memories.\n"
5892
+ )
5893
+ else:
5894
+ system_message_content += (
5895
+ "You have the capability to retain memories from previous interactions with the user, "
5896
+ "but have not had any interactions with the user yet.\n"
5897
+ )
5898
+ if _memory_manager_not_set:
5899
+ self.memory_manager = None
5900
+
5901
+ if self.enable_agentic_memory:
5902
+ system_message_content += (
5903
+ "\n<updating_user_memories>\n"
5904
+ "- You have access to the `update_user_memory` tool that you can use to add new memories, update existing memories, delete memories, or clear all memories.\n"
5905
+ "- If the user's message includes information that should be captured as a memory, use the `update_user_memory` tool to update your memory database.\n"
5906
+ "- Memories should include details that could personalize ongoing interactions with the user.\n"
5907
+ "- Use this tool to add new memories or update existing memories that you identify in the conversation.\n"
5908
+ "- Use this tool if the user asks to update their memory, delete a memory, or clear all memories.\n"
5909
+ "- If you use the `update_user_memory` tool, remember to pass on the response to the user.\n"
5910
+ "</updating_user_memories>\n\n"
5911
+ )
5912
+
5913
+ # 3.3.11 Then add a summary of the interaction to the system prompt
5914
+ if self.add_session_summary_to_context and session.summary is not None:
5915
+ system_message_content += "Here is a brief summary of your previous interactions:\n\n"
5916
+ system_message_content += "<summary_of_previous_interactions>\n"
5917
+ system_message_content += session.summary.summary
5918
+ system_message_content += "\n</summary_of_previous_interactions>\n\n"
5919
+ system_message_content += (
5920
+ "Note: this information is from previous interactions and may be outdated. "
5921
+ "You should ALWAYS prefer information from this conversation over the past summary.\n\n"
5922
+ )
5923
+
5924
+ # 3.3.12 Add the system message from the Model
5925
+ system_message_from_model = self.model.get_system_message_for_model(self._tools_for_model)
5926
+ if system_message_from_model is not None:
5927
+ system_message_content += system_message_from_model
5928
+
5929
+ # 3.3.13 Add the JSON output prompt if output_schema is provided and the model does not support native structured outputs or JSON schema outputs
5930
+ # or if use_json_mode is True
5931
+ if (
5932
+ self.output_schema is not None
5933
+ and self.parser_model is None
5934
+ and not (
5935
+ (self.model.supports_native_structured_outputs or self.model.supports_json_schema_outputs)
5936
+ and (not self.use_json_mode or self.structured_outputs is True)
5937
+ )
5938
+ ):
5939
+ system_message_content += f"{get_json_output_prompt(self.output_schema)}" # type: ignore
5940
+
5941
+ # 3.3.14 Add the response model format prompt if output_schema is provided
5942
+ if self.output_schema is not None and self.parser_model is not None:
5943
+ system_message_content += f"{get_response_model_format_prompt(self.output_schema)}"
5944
+
5945
+ # 3.3.15 Add the session state to the system message
5946
+ if self.add_session_state_to_context and session_state is not None:
5947
+ system_message_content += f"\n<session_state>\n{session_state}\n</session_state>\n\n"
5083
5948
 
5084
- # Should already be resolved and passed from run() method
5085
- format_variables = ChainMap(
5086
- session_state or {},
5087
- dependencies or {},
5088
- metadata or {},
5089
- {"user_id": user_id} if user_id is not None else {},
5949
+ # Return the system message
5950
+ return (
5951
+ Message(role=self.system_message_role, content=system_message_content.strip()) # type: ignore
5952
+ if system_message_content
5953
+ else None
5090
5954
  )
5091
- converted_msg = deepcopy(message)
5092
- for var_name in format_variables.keys():
5093
- # Only convert standalone {var_name} patterns, not nested ones
5094
- pattern = r"\{" + re.escape(var_name) + r"\}"
5095
- replacement = "${" + var_name + "}"
5096
- converted_msg = re.sub(pattern, replacement, converted_msg)
5097
-
5098
- # Use Template to safely substitute variables
5099
- template = string.Template(converted_msg)
5100
- try:
5101
- result = template.safe_substitute(format_variables)
5102
- return result
5103
- except Exception as e:
5104
- log_warning(f"Template substitution failed: {e}")
5105
- return message
5106
5955
 
5107
- def get_system_message(
5956
+ async def aget_system_message(
5108
5957
  self,
5109
5958
  session: AgentSession,
5110
5959
  session_state: Optional[Dict[str, Any]] = None,
5111
5960
  user_id: Optional[str] = None,
5112
5961
  dependencies: Optional[Dict[str, Any]] = None,
5113
5962
  metadata: Optional[Dict[str, Any]] = None,
5114
- add_session_state_to_context: Optional[bool] = None,
5115
5963
  ) -> Optional[Message]:
5116
5964
  """Return the system message for the Agent.
5117
5965
 
@@ -5225,7 +6073,7 @@ class Agent:
5225
6073
 
5226
6074
  # 3.2.5 Add information about agentic filters if enabled
5227
6075
  if self.knowledge is not None and self.enable_agentic_knowledge_filters:
5228
- valid_filters = self.knowledge.get_valid_filters()
6076
+ valid_filters = getattr(self.knowledge, "valid_metadata_filters", None)
5229
6077
  if valid_filters:
5230
6078
  valid_filters_str = ", ".join(valid_filters)
5231
6079
  additional_information.append(
@@ -5300,7 +6148,12 @@ class Agent:
5300
6148
  if self.memory_manager is None:
5301
6149
  self._set_memory_manager()
5302
6150
  _memory_manager_not_set = True
5303
- user_memories = self.memory_manager.get_user_memories(user_id=user_id) # type: ignore
6151
+
6152
+ if self._has_async_db():
6153
+ user_memories = await self.memory_manager.aget_user_memories(user_id=user_id) # type: ignore
6154
+ else:
6155
+ user_memories = self.memory_manager.get_user_memories(user_id=user_id) # type: ignore
6156
+
5304
6157
  if user_memories and len(user_memories) > 0:
5305
6158
  system_message_content += (
5306
6159
  "You have access to memories from previous interactions with the user that you can use:\n\n"
@@ -5366,7 +6219,7 @@ class Agent:
5366
6219
  system_message_content += f"{get_response_model_format_prompt(self.output_schema)}"
5367
6220
 
5368
6221
  # 3.3.15 Add the session state to the system message
5369
- if add_session_state_to_context and session_state is not None:
6222
+ if self.add_session_state_to_context and session_state is not None:
5370
6223
  system_message_content += self._get_formatted_session_state_for_system_message(session_state)
5371
6224
 
5372
6225
  # Return the system message
@@ -5463,90 +6316,291 @@ class Agent:
5463
6316
  log_warning(f"Failed to validate message: {e}")
5464
6317
  raise Exception(f"Failed to validate message: {e}")
5465
6318
 
5466
- # If message is provided as a BaseModel, convert it to a Message
5467
- elif isinstance(input, BaseModel):
5468
- try:
5469
- # Create a user message with the BaseModel content
5470
- content = input.model_dump_json(indent=2, exclude_none=True)
5471
- return Message(role=self.user_message_role, content=content)
5472
- except Exception as e:
5473
- log_warning(f"Failed to convert BaseModel to message: {e}")
5474
- raise Exception(f"Failed to convert BaseModel to message: {e}")
5475
- else:
5476
- user_msg_content = input
5477
- if self.add_knowledge_to_context:
5478
- if isinstance(input, str):
5479
- user_msg_content = input
5480
- elif callable(input):
5481
- user_msg_content = input(agent=self)
5482
- else:
5483
- raise Exception("message must be a string or a callable when add_references is True")
6319
+ # If message is provided as a BaseModel, convert it to a Message
6320
+ elif isinstance(input, BaseModel):
6321
+ try:
6322
+ # Create a user message with the BaseModel content
6323
+ content = input.model_dump_json(indent=2, exclude_none=True)
6324
+ return Message(role=self.user_message_role, content=content)
6325
+ except Exception as e:
6326
+ log_warning(f"Failed to convert BaseModel to message: {e}")
6327
+ raise Exception(f"Failed to convert BaseModel to message: {e}")
6328
+ else:
6329
+ user_msg_content = input
6330
+ if self.add_knowledge_to_context:
6331
+ if isinstance(input, str):
6332
+ user_msg_content = input
6333
+ elif callable(input):
6334
+ user_msg_content = input(agent=self)
6335
+ else:
6336
+ raise Exception("message must be a string or a callable when add_references is True")
6337
+
6338
+ try:
6339
+ retrieval_timer = Timer()
6340
+ retrieval_timer.start()
6341
+ docs_from_knowledge = self.get_relevant_docs_from_knowledge(
6342
+ query=user_msg_content, filters=knowledge_filters, **kwargs
6343
+ )
6344
+ if docs_from_knowledge is not None:
6345
+ references = MessageReferences(
6346
+ query=user_msg_content,
6347
+ references=docs_from_knowledge,
6348
+ time=round(retrieval_timer.elapsed, 4),
6349
+ )
6350
+ # Add the references to the run_response
6351
+ if run_response.references is None:
6352
+ run_response.references = []
6353
+ run_response.references.append(references)
6354
+ retrieval_timer.stop()
6355
+ log_debug(f"Time to get references: {retrieval_timer.elapsed:.4f}s")
6356
+ except Exception as e:
6357
+ log_warning(f"Failed to get references: {e}")
6358
+
6359
+ if self.resolve_in_context:
6360
+ user_msg_content = self._format_message_with_state_variables(
6361
+ user_msg_content,
6362
+ user_id=user_id,
6363
+ session_state=session_state,
6364
+ dependencies=dependencies,
6365
+ metadata=metadata,
6366
+ )
6367
+
6368
+ # Convert to string for concatenation operations
6369
+ user_msg_content_str = get_text_from_message(user_msg_content) if user_msg_content is not None else ""
6370
+
6371
+ # 4.1 Add knowledge references to user message
6372
+ if (
6373
+ self.add_knowledge_to_context
6374
+ and references is not None
6375
+ and references.references is not None
6376
+ and len(references.references) > 0
6377
+ ):
6378
+ user_msg_content_str += "\n\nUse the following references from the knowledge base if it helps:\n"
6379
+ user_msg_content_str += "<references>\n"
6380
+ user_msg_content_str += self._convert_documents_to_string(references.references) + "\n"
6381
+ user_msg_content_str += "</references>"
6382
+ # 4.2 Add context to user message
6383
+ if add_dependencies_to_context and dependencies is not None:
6384
+ user_msg_content_str += "\n\n<additional context>\n"
6385
+ user_msg_content_str += self._convert_dependencies_to_string(dependencies) + "\n"
6386
+ user_msg_content_str += "</additional context>"
6387
+
6388
+ # Use the string version for the final content
6389
+ user_msg_content = user_msg_content_str
6390
+
6391
+ # Return the user message
6392
+ return Message(
6393
+ role=self.user_message_role,
6394
+ content=user_msg_content,
6395
+ audio=None if not self.send_media_to_model else audio,
6396
+ images=None if not self.send_media_to_model else images,
6397
+ videos=None if not self.send_media_to_model else videos,
6398
+ files=None if not self.send_media_to_model else files,
6399
+ **kwargs,
6400
+ )
6401
+
6402
+ def _get_run_messages(
6403
+ self,
6404
+ *,
6405
+ run_response: RunOutput,
6406
+ input: Union[str, List, Dict, Message, BaseModel, List[Message]],
6407
+ session: AgentSession,
6408
+ session_state: Optional[Dict[str, Any]] = None,
6409
+ user_id: Optional[str] = None,
6410
+ audio: Optional[Sequence[Audio]] = None,
6411
+ images: Optional[Sequence[Image]] = None,
6412
+ videos: Optional[Sequence[Video]] = None,
6413
+ files: Optional[Sequence[File]] = None,
6414
+ knowledge_filters: Optional[Dict[str, Any]] = None,
6415
+ add_history_to_context: Optional[bool] = None,
6416
+ dependencies: Optional[Dict[str, Any]] = None,
6417
+ add_dependencies_to_context: Optional[bool] = None,
6418
+ add_session_state_to_context: Optional[bool] = None,
6419
+ metadata: Optional[Dict[str, Any]] = None,
6420
+ **kwargs: Any,
6421
+ ) -> RunMessages:
6422
+ """This function returns a RunMessages object with the following attributes:
6423
+ - system_message: The system message for this run
6424
+ - user_message: The user message for this run
6425
+ - messages: List of messages to send to the model
6426
+
6427
+ To build the RunMessages object:
6428
+ 1. Add system message to run_messages
6429
+ 2. Add extra messages to run_messages if provided
6430
+ 3. Add history to run_messages
6431
+ 4. Add user message to run_messages (if input is single content)
6432
+ 5. Add input messages to run_messages if provided (if input is List[Message])
6433
+
6434
+ Returns:
6435
+ RunMessages object with the following attributes:
6436
+ - system_message: The system message for this run
6437
+ - user_message: The user message for this run
6438
+ - messages: List of all messages to send to the model
6439
+
6440
+ Typical usage:
6441
+ run_messages = self._get_run_messages(
6442
+ input=input, session_id=session_id, user_id=user_id, audio=audio, images=images, videos=videos, files=files, **kwargs
6443
+ )
6444
+ """
6445
+
6446
+ # Initialize the RunMessages object (no media here - that's in RunInput now)
6447
+ run_messages = RunMessages()
6448
+
6449
+ # 1. Add system message to run_messages
6450
+ system_message = self.get_system_message(
6451
+ session=session,
6452
+ session_state=session_state,
6453
+ user_id=user_id,
6454
+ dependencies=dependencies,
6455
+ metadata=metadata,
6456
+ add_session_state_to_context=add_session_state_to_context,
6457
+ )
6458
+ if system_message is not None:
6459
+ run_messages.system_message = system_message
6460
+ run_messages.messages.append(system_message)
6461
+
6462
+ # 2. Add extra messages to run_messages if provided
6463
+ if self.additional_input is not None:
6464
+ messages_to_add_to_run_response: List[Message] = []
6465
+ if run_messages.extra_messages is None:
6466
+ run_messages.extra_messages = []
6467
+
6468
+ for _m in self.additional_input:
6469
+ if isinstance(_m, Message):
6470
+ messages_to_add_to_run_response.append(_m)
6471
+ run_messages.messages.append(_m)
6472
+ run_messages.extra_messages.append(_m)
6473
+ elif isinstance(_m, dict):
6474
+ try:
6475
+ _m_parsed = Message.model_validate(_m)
6476
+ messages_to_add_to_run_response.append(_m_parsed)
6477
+ run_messages.messages.append(_m_parsed)
6478
+ run_messages.extra_messages.append(_m_parsed)
6479
+ except Exception as e:
6480
+ log_warning(f"Failed to validate message: {e}")
6481
+ # Add the extra messages to the run_response
6482
+ if len(messages_to_add_to_run_response) > 0:
6483
+ log_debug(f"Adding {len(messages_to_add_to_run_response)} extra messages")
6484
+ if run_response.additional_input is None:
6485
+ run_response.additional_input = messages_to_add_to_run_response
6486
+ else:
6487
+ run_response.additional_input.extend(messages_to_add_to_run_response)
6488
+
6489
+ # 3. Add history to run_messages
6490
+ if add_history_to_context:
6491
+ from copy import deepcopy
6492
+
6493
+ # Only skip messages from history when system_message_role is NOT a standard conversation role.
6494
+ # Standard conversation roles ("user", "assistant", "tool") should never be filtered
6495
+ # to preserve conversation continuity.
6496
+ skip_role = (
6497
+ self.system_message_role if self.system_message_role not in ["user", "assistant", "tool"] else None
6498
+ )
6499
+
6500
+ history: List[Message] = session.get_messages_from_last_n_runs(
6501
+ last_n=self.num_history_runs,
6502
+ skip_role=skip_role,
6503
+ agent_id=self.id if self.team_id is not None else None,
6504
+ )
6505
+
6506
+ if len(history) > 0:
6507
+ # Create a deep copy of the history messages to avoid modifying the original messages
6508
+ history_copy = [deepcopy(msg) for msg in history]
6509
+
6510
+ # Tag each message as coming from history
6511
+ for _msg in history_copy:
6512
+ _msg.from_history = True
6513
+
6514
+ log_debug(f"Adding {len(history_copy)} messages from history")
6515
+
6516
+ run_messages.messages += history_copy
6517
+
6518
+ # 4. Add user message to run_messages
6519
+ user_message: Optional[Message] = None
6520
+
6521
+ # 4.1 Build user message if input is None, str or list and not a list of Message/dict objects
6522
+ if (
6523
+ input is None
6524
+ or isinstance(input, str)
6525
+ or (
6526
+ isinstance(input, list)
6527
+ and not (
6528
+ len(input) > 0
6529
+ and (isinstance(input[0], Message) or (isinstance(input[0], dict) and "role" in input[0]))
6530
+ )
6531
+ )
6532
+ ):
6533
+ user_message = self._get_user_message(
6534
+ run_response=run_response,
6535
+ session_state=session_state,
6536
+ input=input,
6537
+ audio=audio,
6538
+ images=images,
6539
+ videos=videos,
6540
+ files=files,
6541
+ knowledge_filters=knowledge_filters,
6542
+ dependencies=dependencies,
6543
+ add_dependencies_to_context=add_dependencies_to_context,
6544
+ metadata=metadata,
6545
+ **kwargs,
6546
+ )
6547
+
6548
+ # 4.2 If input is provided as a Message, use it directly
6549
+ elif isinstance(input, Message):
6550
+ user_message = input
5484
6551
 
5485
- try:
5486
- retrieval_timer = Timer()
5487
- retrieval_timer.start()
5488
- docs_from_knowledge = self.get_relevant_docs_from_knowledge(
5489
- query=user_msg_content, filters=knowledge_filters, **kwargs
5490
- )
5491
- if docs_from_knowledge is not None:
5492
- references = MessageReferences(
5493
- query=user_msg_content,
5494
- references=docs_from_knowledge,
5495
- time=round(retrieval_timer.elapsed, 4),
5496
- )
5497
- # Add the references to the run_response
5498
- if run_response.references is None:
5499
- run_response.references = []
5500
- run_response.references.append(references)
5501
- retrieval_timer.stop()
5502
- log_debug(f"Time to get references: {retrieval_timer.elapsed:.4f}s")
5503
- except Exception as e:
5504
- log_warning(f"Failed to get references: {e}")
6552
+ # 4.3 If input is provided as a dict, try to validate it as a Message
6553
+ elif isinstance(input, dict):
6554
+ try:
6555
+ if self.input_schema and is_typed_dict(self.input_schema):
6556
+ import json
5505
6557
 
5506
- if self.resolve_in_context:
5507
- user_msg_content = self._format_message_with_state_variables(
5508
- user_msg_content,
5509
- user_id=user_id,
5510
- session_state=session_state,
5511
- dependencies=dependencies,
5512
- metadata=metadata,
5513
- )
6558
+ content = json.dumps(input, indent=2, ensure_ascii=False)
6559
+ user_message = Message(role=self.user_message_role, content=content)
6560
+ else:
6561
+ user_message = Message.model_validate(input)
6562
+ except Exception as e:
6563
+ log_warning(f"Failed to validate message: {e}")
5514
6564
 
5515
- # Convert to string for concatenation operations
5516
- user_msg_content_str = get_text_from_message(user_msg_content) if user_msg_content is not None else ""
6565
+ # 4.4 If input is provided as a BaseModel, convert it to a Message
6566
+ elif isinstance(input, BaseModel):
6567
+ try:
6568
+ # Create a user message with the BaseModel content
6569
+ content = input.model_dump_json(indent=2, exclude_none=True)
6570
+ user_message = Message(role=self.user_message_role, content=content)
6571
+ except Exception as e:
6572
+ log_warning(f"Failed to convert BaseModel to message: {e}")
5517
6573
 
5518
- # 4.1 Add knowledge references to user message
5519
- if (
5520
- self.add_knowledge_to_context
5521
- and references is not None
5522
- and references.references is not None
5523
- and len(references.references) > 0
5524
- ):
5525
- user_msg_content_str += "\n\nUse the following references from the knowledge base if it helps:\n"
5526
- user_msg_content_str += "<references>\n"
5527
- user_msg_content_str += self._convert_documents_to_string(references.references) + "\n"
5528
- user_msg_content_str += "</references>"
5529
- # 4.2 Add context to user message
5530
- if add_dependencies_to_context and dependencies is not None:
5531
- user_msg_content_str += "\n\n<additional context>\n"
5532
- user_msg_content_str += self._convert_dependencies_to_string(dependencies) + "\n"
5533
- user_msg_content_str += "</additional context>"
6574
+ # 5. Add input messages to run_messages if provided (List[Message] or List[Dict])
6575
+ if (
6576
+ isinstance(input, list)
6577
+ and len(input) > 0
6578
+ and (isinstance(input[0], Message) or (isinstance(input[0], dict) and "role" in input[0]))
6579
+ ):
6580
+ for _m in input:
6581
+ if isinstance(_m, Message):
6582
+ run_messages.messages.append(_m)
6583
+ if run_messages.extra_messages is None:
6584
+ run_messages.extra_messages = []
6585
+ run_messages.extra_messages.append(_m)
6586
+ elif isinstance(_m, dict):
6587
+ try:
6588
+ msg = Message.model_validate(_m)
6589
+ run_messages.messages.append(msg)
6590
+ if run_messages.extra_messages is None:
6591
+ run_messages.extra_messages = []
6592
+ run_messages.extra_messages.append(msg)
6593
+ except Exception as e:
6594
+ log_warning(f"Failed to validate message: {e}")
5534
6595
 
5535
- # Use the string version for the final content
5536
- user_msg_content = user_msg_content_str
6596
+ # Add user message to run_messages
6597
+ if user_message is not None:
6598
+ run_messages.user_message = user_message
6599
+ run_messages.messages.append(user_message)
5537
6600
 
5538
- # Return the user message
5539
- return Message(
5540
- role=self.user_message_role,
5541
- content=user_msg_content,
5542
- audio=None if not self.send_media_to_model else audio,
5543
- images=None if not self.send_media_to_model else images,
5544
- videos=None if not self.send_media_to_model else videos,
5545
- files=None if not self.send_media_to_model else files,
5546
- **kwargs,
5547
- )
6601
+ return run_messages
5548
6602
 
5549
- def _get_run_messages(
6603
+ async def _aget_run_messages(
5550
6604
  self,
5551
6605
  *,
5552
6606
  run_response: RunOutput,
@@ -5560,7 +6614,7 @@ class Agent:
5560
6614
  files: Optional[Sequence[File]] = None,
5561
6615
  knowledge_filters: Optional[Dict[str, Any]] = None,
5562
6616
  add_history_to_context: Optional[bool] = None,
5563
- dependencies: Optional[Dict[str, Any]] = None,
6617
+ run_dependencies: Optional[Dict[str, Any]] = None,
5564
6618
  add_dependencies_to_context: Optional[bool] = None,
5565
6619
  add_session_state_to_context: Optional[bool] = None,
5566
6620
  metadata: Optional[Dict[str, Any]] = None,
@@ -5594,13 +6648,12 @@ class Agent:
5594
6648
  run_messages = RunMessages()
5595
6649
 
5596
6650
  # 1. Add system message to run_messages
5597
- system_message = self.get_system_message(
6651
+ system_message = await self.aget_system_message(
5598
6652
  session=session,
5599
6653
  session_state=session_state,
5600
6654
  user_id=user_id,
5601
- dependencies=dependencies,
6655
+ dependencies=run_dependencies,
5602
6656
  metadata=metadata,
5603
- add_session_state_to_context=add_session_state_to_context,
5604
6657
  )
5605
6658
  if system_message is not None:
5606
6659
  run_messages.system_message = system_message
@@ -5637,16 +6690,9 @@ class Agent:
5637
6690
  if add_history_to_context:
5638
6691
  from copy import deepcopy
5639
6692
 
5640
- # Only skip messages from history when system_message_role is NOT a standard conversation role.
5641
- # Standard conversation roles ("user", "assistant", "tool") should never be filtered
5642
- # to preserve conversation continuity.
5643
- skip_role = (
5644
- self.system_message_role if self.system_message_role not in ["user", "assistant", "tool"] else None
5645
- )
5646
-
5647
6693
  history: List[Message] = session.get_messages_from_last_n_runs(
5648
6694
  last_n=self.num_history_runs,
5649
- skip_role=skip_role,
6695
+ skip_role=self.system_message_role,
5650
6696
  agent_id=self.id if self.team_id is not None else None,
5651
6697
  )
5652
6698
 
@@ -5686,7 +6732,7 @@ class Agent:
5686
6732
  videos=videos,
5687
6733
  files=files,
5688
6734
  knowledge_filters=knowledge_filters,
5689
- dependencies=dependencies,
6735
+ run_dependencies=run_dependencies,
5690
6736
  add_dependencies_to_context=add_dependencies_to_context,
5691
6737
  metadata=metadata,
5692
6738
  **kwargs,
@@ -5699,13 +6745,7 @@ class Agent:
5699
6745
  # 4.3 If input is provided as a dict, try to validate it as a Message
5700
6746
  elif isinstance(input, dict):
5701
6747
  try:
5702
- if self.input_schema and is_typed_dict(self.input_schema):
5703
- import json
5704
-
5705
- content = json.dumps(input, indent=2, ensure_ascii=False)
5706
- user_message = Message(role=self.user_message_role, content=content)
5707
- else:
5708
- user_message = Message.model_validate(input)
6748
+ user_message = Message.model_validate(input)
5709
6749
  except Exception as e:
5710
6750
  log_warning(f"Failed to validate message: {e}")
5711
6751
 
@@ -6264,12 +7304,15 @@ class Agent:
6264
7304
 
6265
7305
  # If a reasoning model is provided, use it to generate reasoning
6266
7306
  if reasoning_model_provided:
7307
+ from agno.reasoning.anthropic import is_anthropic_reasoning_model
6267
7308
  from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
6268
7309
  from agno.reasoning.deepseek import is_deepseek_reasoning_model
7310
+ from agno.reasoning.gemini import is_gemini_reasoning_model
6269
7311
  from agno.reasoning.groq import is_groq_reasoning_model
6270
7312
  from agno.reasoning.helpers import get_reasoning_agent
6271
7313
  from agno.reasoning.ollama import is_ollama_reasoning_model
6272
7314
  from agno.reasoning.openai import is_openai_reasoning_model
7315
+ from agno.reasoning.vertexai import is_vertexai_reasoning_model
6273
7316
 
6274
7317
  reasoning_agent = self.reasoning_agent or get_reasoning_agent(
6275
7318
  reasoning_model=reasoning_model,
@@ -6285,8 +7328,20 @@ class Agent:
6285
7328
  is_openai = is_openai_reasoning_model(reasoning_model)
6286
7329
  is_ollama = is_ollama_reasoning_model(reasoning_model)
6287
7330
  is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
7331
+ is_gemini = is_gemini_reasoning_model(reasoning_model)
7332
+ is_anthropic = is_anthropic_reasoning_model(reasoning_model)
7333
+ is_vertexai = is_vertexai_reasoning_model(reasoning_model)
6288
7334
 
6289
- if is_deepseek or is_groq or is_openai or is_ollama or is_ai_foundry:
7335
+ if (
7336
+ is_deepseek
7337
+ or is_groq
7338
+ or is_openai
7339
+ or is_ollama
7340
+ or is_ai_foundry
7341
+ or is_gemini
7342
+ or is_anthropic
7343
+ or is_vertexai
7344
+ ):
6290
7345
  reasoning_message: Optional[Message] = None
6291
7346
  if is_deepseek:
6292
7347
  from agno.reasoning.deepseek import get_deepseek_reasoning
@@ -6323,6 +7378,27 @@ class Agent:
6323
7378
  reasoning_message = get_ai_foundry_reasoning(
6324
7379
  reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
6325
7380
  )
7381
+ elif is_gemini:
7382
+ from agno.reasoning.gemini import get_gemini_reasoning
7383
+
7384
+ log_debug("Starting Gemini Reasoning", center=True, symbol="=")
7385
+ reasoning_message = get_gemini_reasoning(
7386
+ reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
7387
+ )
7388
+ elif is_anthropic:
7389
+ from agno.reasoning.anthropic import get_anthropic_reasoning
7390
+
7391
+ log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
7392
+ reasoning_message = get_anthropic_reasoning(
7393
+ reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
7394
+ )
7395
+ elif is_vertexai:
7396
+ from agno.reasoning.vertexai import get_vertexai_reasoning
7397
+
7398
+ log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
7399
+ reasoning_message = get_vertexai_reasoning(
7400
+ reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
7401
+ )
6326
7402
 
6327
7403
  if reasoning_message is None:
6328
7404
  log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
@@ -6496,12 +7572,15 @@ class Agent:
6496
7572
 
6497
7573
  # If a reasoning model is provided, use it to generate reasoning
6498
7574
  if reasoning_model_provided:
7575
+ from agno.reasoning.anthropic import is_anthropic_reasoning_model
6499
7576
  from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
6500
7577
  from agno.reasoning.deepseek import is_deepseek_reasoning_model
7578
+ from agno.reasoning.gemini import is_gemini_reasoning_model
6501
7579
  from agno.reasoning.groq import is_groq_reasoning_model
6502
7580
  from agno.reasoning.helpers import get_reasoning_agent
6503
7581
  from agno.reasoning.ollama import is_ollama_reasoning_model
6504
7582
  from agno.reasoning.openai import is_openai_reasoning_model
7583
+ from agno.reasoning.vertexai import is_vertexai_reasoning_model
6505
7584
 
6506
7585
  reasoning_agent = self.reasoning_agent or get_reasoning_agent(
6507
7586
  reasoning_model=reasoning_model,
@@ -6517,8 +7596,20 @@ class Agent:
6517
7596
  is_openai = is_openai_reasoning_model(reasoning_model)
6518
7597
  is_ollama = is_ollama_reasoning_model(reasoning_model)
6519
7598
  is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
7599
+ is_gemini = is_gemini_reasoning_model(reasoning_model)
7600
+ is_anthropic = is_anthropic_reasoning_model(reasoning_model)
7601
+ is_vertexai = is_vertexai_reasoning_model(reasoning_model)
6520
7602
 
6521
- if is_deepseek or is_groq or is_openai or is_ollama or is_ai_foundry:
7603
+ if (
7604
+ is_deepseek
7605
+ or is_groq
7606
+ or is_openai
7607
+ or is_ollama
7608
+ or is_ai_foundry
7609
+ or is_gemini
7610
+ or is_anthropic
7611
+ or is_vertexai
7612
+ ):
6522
7613
  reasoning_message: Optional[Message] = None
6523
7614
  if is_deepseek:
6524
7615
  from agno.reasoning.deepseek import aget_deepseek_reasoning
@@ -6555,6 +7646,27 @@ class Agent:
6555
7646
  reasoning_message = get_ai_foundry_reasoning(
6556
7647
  reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
6557
7648
  )
7649
+ elif is_gemini:
7650
+ from agno.reasoning.gemini import aget_gemini_reasoning
7651
+
7652
+ log_debug("Starting Gemini Reasoning", center=True, symbol="=")
7653
+ reasoning_message = await aget_gemini_reasoning(
7654
+ reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
7655
+ )
7656
+ elif is_anthropic:
7657
+ from agno.reasoning.anthropic import aget_anthropic_reasoning
7658
+
7659
+ log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
7660
+ reasoning_message = await aget_anthropic_reasoning(
7661
+ reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
7662
+ )
7663
+ elif is_vertexai:
7664
+ from agno.reasoning.vertexai import aget_vertexai_reasoning
7665
+
7666
+ log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
7667
+ reasoning_message = await aget_vertexai_reasoning(
7668
+ reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
7669
+ )
6558
7670
 
6559
7671
  if reasoning_message is None:
6560
7672
  log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
@@ -7284,6 +8396,8 @@ class Agent:
7284
8396
  if self.db is None:
7285
8397
  return "Previous session messages not available"
7286
8398
 
8399
+ self.db = cast(BaseDb, self.db)
8400
+
7287
8401
  selected_sessions = self.db.get_sessions(
7288
8402
  session_type=SessionType.AGENT, limit=num_history_sessions, user_id=user_id
7289
8403
  )
@@ -7321,6 +8435,69 @@ class Agent:
7321
8435
 
7322
8436
  return get_previous_session_messages
7323
8437
 
8438
+ async def _aget_previous_sessions_messages_function(self, num_history_sessions: Optional[int] = 2) -> Callable:
8439
+ """Factory function to create a get_previous_session_messages function.
8440
+
8441
+ Args:
8442
+ num_history_sessions: The last n sessions to be taken from db
8443
+
8444
+ Returns:
8445
+ Callable: A function that retrieves messages from previous sessions
8446
+ """
8447
+
8448
+ async def aget_previous_session_messages() -> str:
8449
+ """Use this function to retrieve messages from previous chat sessions.
8450
+ USE THIS TOOL ONLY WHEN THE QUESTION IS EITHER "What was my last conversation?" or "What was my last question?" and similar to it.
8451
+
8452
+ Returns:
8453
+ str: JSON formatted list of message pairs from previous sessions
8454
+ """
8455
+ # TODO: Review and Test this function
8456
+ import json
8457
+
8458
+ if self.db is None:
8459
+ return "Previous session messages not available"
8460
+
8461
+ if isinstance(self.db, AsyncBaseDb):
8462
+ selected_sessions = await self.db.get_sessions(
8463
+ session_type=SessionType.AGENT, limit=num_history_sessions
8464
+ )
8465
+ else:
8466
+ selected_sessions = self.db.get_sessions(session_type=SessionType.AGENT, limit=num_history_sessions)
8467
+
8468
+ all_messages = []
8469
+ seen_message_pairs = set()
8470
+
8471
+ for session in selected_sessions:
8472
+ if isinstance(session, AgentSession) and session.runs:
8473
+ message_count = 0
8474
+ for run in session.runs:
8475
+ messages = run.messages
8476
+ if messages is not None:
8477
+ for i in range(0, len(messages) - 1, 2):
8478
+ if i + 1 < len(messages):
8479
+ try:
8480
+ user_msg = messages[i]
8481
+ assistant_msg = messages[i + 1]
8482
+ user_content = user_msg.content
8483
+ assistant_content = assistant_msg.content
8484
+ if user_content is None or assistant_content is None:
8485
+ continue # Skip this pair if either message has no content
8486
+
8487
+ msg_pair_id = f"{user_content}:{assistant_content}"
8488
+ if msg_pair_id not in seen_message_pairs:
8489
+ seen_message_pairs.add(msg_pair_id)
8490
+ all_messages.append(Message.model_validate(user_msg))
8491
+ all_messages.append(Message.model_validate(assistant_msg))
8492
+ message_count += 1
8493
+ except Exception as e:
8494
+ log_warning(f"Error processing message pair: {e}")
8495
+ continue
8496
+
8497
+ return json.dumps([msg.to_dict() for msg in all_messages]) if all_messages else "No history found"
8498
+
8499
+ return aget_previous_session_messages
8500
+
7324
8501
  ###########################################################################
7325
8502
  # Print Response
7326
8503
  ###########################################################################
@@ -7354,6 +8531,11 @@ class Agent:
7354
8531
  tags_to_include_in_markdown: Optional[Set[str]] = None,
7355
8532
  **kwargs: Any,
7356
8533
  ) -> None:
8534
+ if self._has_async_db():
8535
+ raise Exception(
8536
+ "This method is not supported with an async DB. Please use the async version of this method."
8537
+ )
8538
+
7357
8539
  if not tags_to_include_in_markdown:
7358
8540
  tags_to_include_in_markdown = {"think", "thinking"}
7359
8541
 
@@ -7685,6 +8867,56 @@ class Agent:
7685
8867
  message.image_output = None
7686
8868
  message.video_output = None
7687
8869
 
8870
+ def _scrub_tool_results_from_run_output(self, run_response: RunOutput) -> None:
8871
+ """
8872
+ Remove all tool-related data from RunOutput when store_tool_results=False.
8873
+ This includes tool calls, tool results, and tool-related message fields.
8874
+ """
8875
+ # Remove tool results (messages with role="tool")
8876
+ if run_response.messages:
8877
+ run_response.messages = [msg for msg in run_response.messages if msg.role != "tool"]
8878
+ # Also scrub tool-related fields from remaining messages
8879
+ for message in run_response.messages:
8880
+ self._scrub_tool_data_from_message(message)
8881
+
8882
+ def _scrub_tool_data_from_message(self, message: Message) -> None:
8883
+ """Remove all tool-related data from a Message object."""
8884
+ message.tool_calls = None
8885
+ message.tool_call_id = None
8886
+ message.tool_name = None
8887
+ message.tool_args = None
8888
+ message.tool_call_error = None
8889
+
8890
+ def _scrub_history_messages_from_run_output(self, run_response: RunOutput) -> None:
8891
+ """
8892
+ Remove all history messages from RunOutput when store_history_messages=False.
8893
+ This removes messages that were loaded from the agent's memory.
8894
+ """
8895
+ # Remove messages with from_history=True
8896
+ if run_response.messages:
8897
+ run_response.messages = [msg for msg in run_response.messages if not msg.from_history]
8898
+
8899
+ def _scrub_run_output_for_storage(self, run_response: RunOutput) -> bool:
8900
+ """
8901
+ Scrub run output based on storage flags before persisting to database.
8902
+ Returns True if any scrubbing was done, False otherwise.
8903
+ """
8904
+ scrubbed = False
8905
+
8906
+ if not self.store_media:
8907
+ self._scrub_media_from_run_output(run_response)
8908
+ scrubbed = True
8909
+
8910
+ if not self.store_tool_results:
8911
+ self._scrub_tool_results_from_run_output(run_response)
8912
+ scrubbed = True
8913
+
8914
+ if not self.store_history_messages:
8915
+ self._scrub_history_messages_from_run_output(run_response)
8916
+ scrubbed = True
8917
+
8918
+ return scrubbed
8919
+
7688
8920
  def _validate_media_object_id(
7689
8921
  self,
7690
8922
  images: Optional[Sequence[Image]] = None,