agno 2.1.3__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 (94) hide show
  1. agno/agent/agent.py +1779 -577
  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 +10 -8
  8. agno/db/dynamo/schemas.py +1 -10
  9. agno/db/dynamo/utils.py +2 -2
  10. agno/db/firestore/firestore.py +2 -2
  11. agno/db/firestore/utils.py +4 -2
  12. agno/db/gcs_json/gcs_json_db.py +2 -2
  13. agno/db/in_memory/in_memory_db.py +2 -2
  14. agno/db/json/json_db.py +2 -2
  15. agno/db/migrations/v1_to_v2.py +30 -13
  16. agno/db/mongo/mongo.py +18 -6
  17. agno/db/mysql/mysql.py +35 -13
  18. agno/db/postgres/postgres.py +29 -6
  19. agno/db/redis/redis.py +2 -2
  20. agno/db/singlestore/singlestore.py +2 -2
  21. agno/db/sqlite/sqlite.py +34 -12
  22. agno/db/sqlite/utils.py +8 -3
  23. agno/eval/accuracy.py +50 -43
  24. agno/eval/performance.py +6 -3
  25. agno/eval/reliability.py +6 -3
  26. agno/eval/utils.py +33 -16
  27. agno/exceptions.py +8 -2
  28. agno/knowledge/embedder/fastembed.py +1 -1
  29. agno/knowledge/knowledge.py +260 -46
  30. agno/knowledge/reader/pdf_reader.py +4 -6
  31. agno/knowledge/reader/reader_factory.py +2 -3
  32. agno/memory/manager.py +241 -33
  33. agno/models/anthropic/claude.py +37 -0
  34. agno/os/app.py +15 -10
  35. agno/os/interfaces/a2a/router.py +3 -5
  36. agno/os/interfaces/agui/router.py +4 -1
  37. agno/os/interfaces/agui/utils.py +33 -6
  38. agno/os/interfaces/slack/router.py +2 -4
  39. agno/os/mcp.py +98 -41
  40. agno/os/router.py +23 -0
  41. agno/os/routers/evals/evals.py +52 -20
  42. agno/os/routers/evals/utils.py +14 -14
  43. agno/os/routers/knowledge/knowledge.py +130 -9
  44. agno/os/routers/knowledge/schemas.py +57 -0
  45. agno/os/routers/memory/memory.py +116 -44
  46. agno/os/routers/metrics/metrics.py +16 -6
  47. agno/os/routers/session/session.py +65 -22
  48. agno/os/schema.py +38 -0
  49. agno/os/utils.py +69 -13
  50. agno/reasoning/anthropic.py +80 -0
  51. agno/reasoning/gemini.py +73 -0
  52. agno/reasoning/openai.py +5 -0
  53. agno/reasoning/vertexai.py +76 -0
  54. agno/session/workflow.py +69 -1
  55. agno/team/team.py +934 -241
  56. agno/tools/function.py +36 -18
  57. agno/tools/google_drive.py +270 -0
  58. agno/tools/googlesheets.py +20 -5
  59. agno/tools/mcp_toolbox.py +3 -3
  60. agno/tools/scrapegraph.py +1 -1
  61. agno/utils/models/claude.py +3 -1
  62. agno/utils/print_response/workflow.py +112 -12
  63. agno/utils/streamlit.py +1 -1
  64. agno/vectordb/base.py +22 -1
  65. agno/vectordb/cassandra/cassandra.py +9 -0
  66. agno/vectordb/chroma/chromadb.py +26 -6
  67. agno/vectordb/clickhouse/clickhousedb.py +9 -1
  68. agno/vectordb/couchbase/couchbase.py +11 -0
  69. agno/vectordb/lancedb/lance_db.py +20 -0
  70. agno/vectordb/langchaindb/langchaindb.py +11 -0
  71. agno/vectordb/lightrag/lightrag.py +9 -0
  72. agno/vectordb/llamaindex/llamaindexdb.py +15 -1
  73. agno/vectordb/milvus/milvus.py +23 -0
  74. agno/vectordb/mongodb/mongodb.py +22 -0
  75. agno/vectordb/pgvector/pgvector.py +19 -0
  76. agno/vectordb/pineconedb/pineconedb.py +35 -4
  77. agno/vectordb/qdrant/qdrant.py +24 -0
  78. agno/vectordb/singlestore/singlestore.py +25 -17
  79. agno/vectordb/surrealdb/surrealdb.py +18 -1
  80. agno/vectordb/upstashdb/upstashdb.py +26 -1
  81. agno/vectordb/weaviate/weaviate.py +18 -0
  82. agno/workflow/condition.py +29 -0
  83. agno/workflow/loop.py +29 -0
  84. agno/workflow/parallel.py +141 -113
  85. agno/workflow/router.py +29 -0
  86. agno/workflow/step.py +146 -25
  87. agno/workflow/steps.py +29 -0
  88. agno/workflow/types.py +26 -1
  89. agno/workflow/workflow.py +507 -22
  90. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
  91. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/RECORD +94 -86
  92. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
  93. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
  94. {agno-2.1.3.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
@@ -957,7 +971,6 @@ class Agent:
957
971
  dependencies: Optional[Dict[str, Any]] = None,
958
972
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
959
973
  stream_intermediate_steps: bool = False,
960
- workflow_context: Optional[Dict] = None,
961
974
  yield_run_response: bool = False,
962
975
  debug_mode: Optional[bool] = None,
963
976
  **kwargs: Any,
@@ -973,7 +986,8 @@ class Agent:
973
986
  6. Optional: Save output to file if save_response_to_file is set
974
987
  7. Add the RunOutput to the Agent Session
975
988
  8. Update Agent Memory
976
- 9. Save session to storage
989
+ 9. Create the run completed event
990
+ 10. Save session to storage
977
991
  """
978
992
 
979
993
  # Register run for cancellation tracking
@@ -1034,7 +1048,7 @@ class Agent:
1034
1048
  try:
1035
1049
  # Start the Run by yielding a RunStarted event
1036
1050
  if stream_intermediate_steps:
1037
- yield self._handle_event(create_run_started_event(run_response), run_response, workflow_context)
1051
+ yield self._handle_event(create_run_started_event(run_response), run_response)
1038
1052
 
1039
1053
  # 3. Reason about the task if reasoning is enabled
1040
1054
  yield from self._handle_reasoning_stream(run_response=run_response, run_messages=run_messages)
@@ -1050,7 +1064,6 @@ class Agent:
1050
1064
  run_messages=run_messages,
1051
1065
  response_format=response_format,
1052
1066
  stream_intermediate_steps=stream_intermediate_steps,
1053
- workflow_context=workflow_context,
1054
1067
  ):
1055
1068
  raise_if_cancelled(run_response.run_id) # type: ignore
1056
1069
  yield event
@@ -1066,7 +1079,6 @@ class Agent:
1066
1079
  run_messages=run_messages,
1067
1080
  response_format=response_format,
1068
1081
  stream_intermediate_steps=stream_intermediate_steps,
1069
- workflow_context=workflow_context,
1070
1082
  ):
1071
1083
  raise_if_cancelled(run_response.run_id) # type: ignore
1072
1084
  if isinstance(event, RunContentEvent):
@@ -1084,7 +1096,6 @@ class Agent:
1084
1096
  run_response=run_response,
1085
1097
  run_messages=run_messages,
1086
1098
  stream_intermediate_steps=stream_intermediate_steps,
1087
- workflow_context=workflow_context,
1088
1099
  ):
1089
1100
  raise_if_cancelled(run_response.run_id) # type: ignore
1090
1101
  yield event
@@ -1115,9 +1126,6 @@ class Agent:
1115
1126
  # 5. Calculate session metrics
1116
1127
  self._update_session_metrics(session=session, run_response=run_response)
1117
1128
 
1118
- completed_event = self._handle_event(
1119
- create_run_completed_event(from_run_response=run_response), run_response, workflow_context
1120
- )
1121
1129
  # 6. Optional: Save output to file if save_response_to_file is set
1122
1130
  self.save_run_response_to_file(
1123
1131
  run_response=run_response,
@@ -1134,7 +1142,16 @@ class Agent:
1134
1142
  run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
1135
1143
  )
1136
1144
 
1137
- # 9. Save session to storage
1145
+ # 9. Create the run completed event
1146
+ completed_event = self._handle_event(
1147
+ create_run_completed_event(from_run_response=run_response), run_response
1148
+ )
1149
+
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
1138
1155
  self.save_session(session=session)
1139
1156
 
1140
1157
  if stream_intermediate_steps:
@@ -1158,7 +1175,6 @@ class Agent:
1158
1175
  yield self._handle_event(
1159
1176
  create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
1160
1177
  run_response,
1161
- workflow_context,
1162
1178
  )
1163
1179
 
1164
1180
  # Add the RunOutput to Agent Session even when cancelled
@@ -1244,6 +1260,10 @@ class Agent:
1244
1260
  **kwargs: Any,
1245
1261
  ) -> Union[RunOutput, Iterator[Union[RunOutputEvent, RunOutput]]]:
1246
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
+ )
1247
1267
 
1248
1268
  # Create a run_id for this specific run
1249
1269
  run_id = str(uuid4())
@@ -1302,9 +1322,6 @@ class Agent:
1302
1322
  )
1303
1323
  add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
1304
1324
 
1305
- # Extract workflow context from kwargs if present
1306
- workflow_context = kwargs.pop("workflow_context", None)
1307
-
1308
1325
  # Initialize Knowledge Filters
1309
1326
  effective_filters = knowledge_filters
1310
1327
 
@@ -1380,7 +1397,6 @@ class Agent:
1380
1397
  dependencies=run_dependencies,
1381
1398
  response_format=response_format,
1382
1399
  stream_intermediate_steps=stream_intermediate_steps,
1383
- workflow_context=workflow_context,
1384
1400
  yield_run_response=yield_run_response,
1385
1401
  debug_mode=debug_mode,
1386
1402
  **kwargs,
@@ -1457,8 +1473,9 @@ class Agent:
1457
1473
 
1458
1474
  async def _arun(
1459
1475
  self,
1476
+ input: Union[str, List, Dict, Message, BaseModel, List[Message]],
1460
1477
  run_response: RunOutput,
1461
- session: AgentSession,
1478
+ session_id: str,
1462
1479
  session_state: Optional[Dict[str, Any]] = None,
1463
1480
  user_id: Optional[str] = None,
1464
1481
  knowledge_filters: Optional[Dict[str, Any]] = None,
@@ -1474,27 +1491,42 @@ class Agent:
1474
1491
  """Run the Agent and yield the RunOutput.
1475
1492
 
1476
1493
  Steps:
1477
- 1. Resolve dependencies
1478
- 2. Execute pre-hooks
1479
- 3. Prepare run messages
1480
- 4. Reason about the task if reasoning is enabled
1481
- 5. Generate a response from the Model (includes running function calls)
1482
- 6. Update the RunOutput with the model response
1483
- 7. Execute post-hooks
1484
- 8. Calculate session metrics
1485
- 9. Save output to file
1486
- 10. Add RunOutput to Agent Session
1487
- 11. Update Agent Memory
1488
- 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
1489
1508
  """
1509
+ log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1510
+
1490
1511
  # Register run for cancellation tracking
1491
1512
  register_run(run_response.run_id) # type: ignore
1492
1513
 
1493
- # 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
1494
1526
  if dependencies is not None:
1495
- await self._aresolve_run_dependencies(dependencies)
1527
+ await self._aresolve_run_dependencies(dependencies=dependencies)
1496
1528
 
1497
- # 2. Execute pre-hooks
1529
+ # 4. Execute pre-hooks
1498
1530
  run_input = cast(RunInput, run_response.input)
1499
1531
  self.model = cast(Model, self.model)
1500
1532
  if self.pre_hooks is not None:
@@ -1503,7 +1535,7 @@ class Agent:
1503
1535
  hooks=self.pre_hooks, # type: ignore
1504
1536
  run_response=run_response,
1505
1537
  run_input=run_input,
1506
- session=session,
1538
+ session=agent_session,
1507
1539
  user_id=user_id,
1508
1540
  debug_mode=debug_mode,
1509
1541
  **kwargs,
@@ -1512,10 +1544,12 @@ class Agent:
1512
1544
  async for _ in pre_hook_iterator:
1513
1545
  pass
1514
1546
 
1515
- 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(
1516
1550
  model=self.model,
1517
1551
  run_response=run_response,
1518
- session=session,
1552
+ session=agent_session,
1519
1553
  session_state=session_state,
1520
1554
  dependencies=dependencies,
1521
1555
  user_id=user_id,
@@ -1523,11 +1557,11 @@ class Agent:
1523
1557
  knowledge_filters=knowledge_filters,
1524
1558
  )
1525
1559
 
1526
- # 3. Prepare run messages
1527
- run_messages: RunMessages = self._get_run_messages(
1560
+ # 6. Prepare run messages
1561
+ run_messages: RunMessages = await self._aget_run_messages(
1528
1562
  run_response=run_response,
1529
1563
  input=run_input.input_content,
1530
- session=session,
1564
+ session=agent_session,
1531
1565
  session_state=session_state,
1532
1566
  user_id=user_id,
1533
1567
  audio=run_input.audios,
@@ -1545,113 +1579,136 @@ class Agent:
1545
1579
  if len(run_messages.messages) == 0:
1546
1580
  log_error("No messages to be sent to the model.")
1547
1581
 
1548
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1549
-
1550
- # 4. Reason about the task if reasoning is enabled
1551
- await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
1552
-
1553
- # Check for cancellation before model call
1554
- 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
1555
1586
 
1556
- # 5. Generate a response from the Model (includes running function calls)
1557
- model_response: ModelResponse = await self.model.aresponse(
1558
- messages=run_messages.messages,
1559
- tools=self._tools_for_model,
1560
- functions=self._functions_for_model,
1561
- tool_choice=self.tool_choice,
1562
- tool_call_limit=self.tool_call_limit,
1563
- response_format=response_format,
1564
- send_media_to_model=self.send_media_to_model,
1565
- )
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
1566
1598
 
1567
- # Check for cancellation after model call
1568
- 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)
1569
1603
 
1570
- # If an output model is provided, generate output using the output model
1571
- 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
+ )
1572
1608
 
1573
- # If a parser model is provided, structure the response separately
1574
- 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)
1575
1612
 
1576
- # 6. Update the RunOutput with the model response
1577
- 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
1578
1619
 
1579
- if self.store_media:
1580
- self._store_media(run_response, model_response)
1581
- else:
1582
- 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)
1583
1622
 
1584
- # We should break out of the run function
1585
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
1586
- return self._handle_agent_run_paused(
1587
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
1588
- )
1623
+ # Set the run status to completed
1624
+ run_response.status = RunStatus.completed
1589
1625
 
1590
- run_response.status = RunStatus.completed
1626
+ # Convert the response to the structured format if needed
1627
+ self._convert_response_to_structured_format(run_response)
1591
1628
 
1592
- # Convert the response to the structured format if needed
1593
- self._convert_response_to_structured_format(run_response)
1629
+ # Set the run duration
1630
+ if run_response.metrics:
1631
+ run_response.metrics.stop_timer()
1594
1632
 
1595
- # Set the run duration
1596
- if run_response.metrics:
1597
- 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
+ )
1598
1643
 
1599
- # 7. Execute post-hooks after output is generated but before response is returned
1600
- if self.post_hooks is not None:
1601
- await self._aexecute_post_hooks(
1602
- hooks=self.post_hooks, # type: ignore
1603
- run_output=run_response,
1604
- 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,
1605
1649
  user_id=user_id,
1606
- debug_mode=debug_mode,
1607
- **kwargs,
1608
1650
  )
1609
1651
 
1610
- # 8. Calculate session metrics
1611
- 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)
1612
1654
 
1613
- # 9. Optional: Save output to file if save_response_to_file is set
1614
- self.save_run_response_to_file(
1615
- run_response=run_response, input=run_messages.user_message, session_id=session.session_id, user_id=user_id
1616
- )
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
1617
1660
 
1618
- # 10. Add RunOutput to Agent Session
1619
- 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)
1620
1664
 
1621
- # 11. Update Agent Memory
1622
- async for _ in self._amake_memories_and_summaries(
1623
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
1624
- ):
1625
- 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)
1626
1670
 
1627
- # 12. Save session to storage
1628
- 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)
1629
1673
 
1630
- # Log Agent Telemetry
1631
- 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="*")
1632
1675
 
1633
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1676
+ return run_response
1634
1677
 
1635
- # Always clean up the run tracking
1636
- 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
1637
1683
 
1638
- 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
1639
1696
 
1640
1697
  async def _arun_stream(
1641
1698
  self,
1699
+ input: Union[str, List, Dict, Message, BaseModel, List[Message]],
1642
1700
  run_response: RunOutput,
1643
- session: AgentSession,
1701
+ session_id: str,
1644
1702
  session_state: Optional[Dict[str, Any]] = None,
1645
1703
  user_id: Optional[str] = None,
1646
1704
  knowledge_filters: Optional[Dict[str, Any]] = None,
1705
+ dependencies: Optional[Dict[str, Any]] = None,
1647
1706
  add_history_to_context: Optional[bool] = None,
1648
1707
  add_dependencies_to_context: Optional[bool] = None,
1649
1708
  add_session_state_to_context: Optional[bool] = None,
1650
1709
  metadata: Optional[Dict[str, Any]] = None,
1651
- dependencies: Optional[Dict[str, Any]] = None,
1652
1710
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
1653
1711
  stream_intermediate_steps: bool = False,
1654
- workflow_context: Optional[Dict] = None,
1655
1712
  yield_run_response: Optional[bool] = None,
1656
1713
  debug_mode: Optional[bool] = None,
1657
1714
  **kwargs: Any,
@@ -1659,32 +1716,51 @@ class Agent:
1659
1716
  """Run the Agent and yield the RunOutput.
1660
1717
 
1661
1718
  Steps:
1662
- 1. Resolve dependencies
1663
- 2. Execute pre-hooks
1664
- 3. Prepare run messages
1665
- 4. Reason about the task if reasoning is enabled
1666
- 5. Generate a response from the Model (includes running function calls)
1667
- 6. Calculate session metrics
1668
- 7. Add RunOutput to Agent Session
1669
- 8. Update Agent Memory
1670
- 9. 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
1671
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)
1672
1749
 
1673
- # 1. Resolving here for async requirement
1750
+ # 3. Resolve dependencies
1674
1751
  if dependencies is not None:
1675
1752
  await self._aresolve_run_dependencies(dependencies=dependencies)
1676
1753
 
1677
- # 2. Execute pre-hooks
1754
+ # 4. Execute pre-hooks
1678
1755
  run_input = cast(RunInput, run_response.input)
1679
1756
  self.model = cast(Model, self.model)
1680
-
1681
1757
  if self.pre_hooks is not None:
1682
1758
  # Can modify the run input
1683
1759
  pre_hook_iterator = self._aexecute_pre_hooks(
1684
1760
  hooks=self.pre_hooks, # type: ignore
1685
1761
  run_response=run_response,
1686
1762
  run_input=run_input,
1687
- session=session,
1763
+ session=agent_session,
1688
1764
  user_id=user_id,
1689
1765
  debug_mode=debug_mode,
1690
1766
  **kwargs,
@@ -1692,22 +1768,24 @@ class Agent:
1692
1768
  async for event in pre_hook_iterator:
1693
1769
  yield event
1694
1770
 
1771
+ # 5. Determine tools for model
1772
+ self.model = cast(Model, self.model)
1695
1773
  self._determine_tools_for_model(
1696
1774
  model=self.model,
1697
1775
  run_response=run_response,
1698
- session=session,
1776
+ session=agent_session,
1699
1777
  session_state=session_state,
1700
- dependencies=dependencies,
1701
1778
  user_id=user_id,
1702
1779
  async_mode=True,
1703
1780
  knowledge_filters=knowledge_filters,
1781
+ dependencies=dependencies,
1704
1782
  )
1705
1783
 
1706
- # 3. Prepare run messages
1707
- run_messages: RunMessages = self._get_run_messages(
1784
+ # 6. Prepare run messages
1785
+ run_messages: RunMessages = await self._aget_run_messages(
1708
1786
  run_response=run_response,
1709
1787
  input=run_input.input_content,
1710
- session=session,
1788
+ session=agent_session,
1711
1789
  session_state=session_state,
1712
1790
  user_id=user_id,
1713
1791
  audio=run_input.audios,
@@ -1722,34 +1800,27 @@ class Agent:
1722
1800
  metadata=metadata,
1723
1801
  **kwargs,
1724
1802
  )
1725
-
1726
- 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.")
1727
1805
 
1728
1806
  # Register run for cancellation tracking
1729
1807
  register_run(run_response.run_id) # type: ignore
1730
1808
 
1731
1809
  try:
1732
- # Start the Run by yielding a RunStarted event
1733
- if stream_intermediate_steps:
1734
- yield self._handle_event(create_run_started_event(run_response), run_response, workflow_context)
1735
-
1736
- # 4. Reason about the task if reasoning is enabled
1810
+ # 7. Reason about the task if reasoning is enabled
1737
1811
  async for item in self._ahandle_reasoning_stream(run_response=run_response, run_messages=run_messages):
1738
1812
  raise_if_cancelled(run_response.run_id) # type: ignore
1739
1813
  yield item
1740
-
1741
- # Check for cancellation before model processing
1742
1814
  raise_if_cancelled(run_response.run_id) # type: ignore
1743
1815
 
1744
- # 5. Generate a response from the Model
1816
+ # 8. Generate a response from the Model
1745
1817
  if self.output_model is None:
1746
1818
  async for event in self._ahandle_model_response_stream(
1747
- session=session,
1819
+ session=agent_session,
1748
1820
  run_response=run_response,
1749
1821
  run_messages=run_messages,
1750
1822
  response_format=response_format,
1751
1823
  stream_intermediate_steps=stream_intermediate_steps,
1752
- workflow_context=workflow_context,
1753
1824
  ):
1754
1825
  raise_if_cancelled(run_response.run_id) # type: ignore
1755
1826
  yield event
@@ -1760,12 +1831,11 @@ class Agent:
1760
1831
  ) # type: ignore
1761
1832
 
1762
1833
  async for event in self._ahandle_model_response_stream(
1763
- session=session,
1834
+ session=agent_session,
1764
1835
  run_response=run_response,
1765
1836
  run_messages=run_messages,
1766
1837
  response_format=response_format,
1767
1838
  stream_intermediate_steps=stream_intermediate_steps,
1768
- workflow_context=workflow_context,
1769
1839
  ):
1770
1840
  raise_if_cancelled(run_response.run_id) # type: ignore
1771
1841
  if isinstance(event, RunContentEvent):
@@ -1779,10 +1849,9 @@ class Agent:
1779
1849
 
1780
1850
  # If an output model is provided, generate output using the output model
1781
1851
  async for event in self._agenerate_response_with_output_model_stream(
1782
- session=session,
1852
+ session=agent_session,
1783
1853
  run_response=run_response,
1784
1854
  run_messages=run_messages,
1785
- workflow_context=workflow_context,
1786
1855
  stream_intermediate_steps=stream_intermediate_steps,
1787
1856
  ):
1788
1857
  raise_if_cancelled(run_response.run_id) # type: ignore
@@ -1793,50 +1862,59 @@ class Agent:
1793
1862
 
1794
1863
  # If a parser model is provided, structure the response separately
1795
1864
  async for event in self._aparse_response_with_parser_model_stream(
1796
- 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
1797
1866
  ):
1798
1867
  yield event
1799
1868
 
1800
- # We should break out of the run function
1869
+ # Break out of the run function if a tool call is paused
1801
1870
  if any(tool_call.is_paused for tool_call in run_response.tools or []):
1802
1871
  for item in self._handle_agent_run_paused_stream(
1803
- 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
1804
1873
  ):
1805
1874
  yield item
1806
1875
  return
1807
1876
 
1877
+ # Set the run status to completed
1808
1878
  run_response.status = RunStatus.completed
1809
1879
 
1810
1880
  # Set the run duration
1811
1881
  if run_response.metrics:
1812
1882
  run_response.metrics.stop_timer()
1813
1883
 
1814
- completed_event = self._handle_event(
1815
- create_run_completed_event(from_run_response=run_response), run_response, workflow_context
1816
- )
1817
-
1818
- # 6. Calculate session metrics
1819
- 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)
1820
1886
 
1821
1887
  # Optional: Save output to file if save_response_to_file is set
1822
1888
  self.save_run_response_to_file(
1823
1889
  run_response=run_response,
1824
1890
  input=run_messages.user_message,
1825
- session_id=session.session_id,
1891
+ session_id=agent_session.session_id,
1826
1892
  user_id=user_id,
1827
1893
  )
1828
1894
 
1829
- # 7. Add RunOutput to Agent Session
1830
- session.upsert_run(run=run_response)
1895
+ # 10. Add RunOutput to Agent Session
1896
+ agent_session.upsert_run(run=run_response)
1831
1897
 
1832
- # 8. Update Agent Memory
1898
+ # 11. Update Agent Memory
1833
1899
  async for event in self._amake_memories_and_summaries(
1834
- 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
1835
1901
  ):
1836
1902
  yield event
1837
1903
 
1838
- # 9. Save session to storage
1839
- self.save_session(session=session)
1904
+ # 12. Create the run completed event
1905
+ completed_event = self._handle_event(
1906
+ create_run_completed_event(from_run_response=run_response), run_response
1907
+ )
1908
+
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)
1840
1918
 
1841
1919
  if stream_intermediate_steps:
1842
1920
  yield completed_event
@@ -1845,7 +1923,7 @@ class Agent:
1845
1923
  yield run_response
1846
1924
 
1847
1925
  # Log Agent Telemetry
1848
- 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)
1849
1927
 
1850
1928
  log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1851
1929
 
@@ -1859,12 +1937,14 @@ class Agent:
1859
1937
  yield self._handle_event(
1860
1938
  create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
1861
1939
  run_response,
1862
- workflow_context,
1863
1940
  )
1864
1941
 
1865
1942
  # Add the RunOutput to Agent Session even when cancelled
1866
- session.upsert_run(run=run_response)
1867
- 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)
1868
1948
  finally:
1869
1949
  # Always clean up the run tracking
1870
1950
  cleanup_run(run_response.run_id) # type: ignore
@@ -1948,7 +2028,7 @@ class Agent:
1948
2028
  # Create a run_id for this specific run
1949
2029
  run_id = str(uuid4())
1950
2030
 
1951
- # Validate input against input_schema if provided
2031
+ # 2. Validate input against input_schema if provided
1952
2032
  validated_input = self._validate_input(input)
1953
2033
 
1954
2034
  # Normalise hook & guardails
@@ -1959,6 +2039,7 @@ class Agent:
1959
2039
  self.post_hooks = normalize_hooks(self.post_hooks, async_mode=True)
1960
2040
  self._hooks_normalised = True
1961
2041
 
2042
+ # Initialize session
1962
2043
  session_id, user_id, session_state = self._initialize_session(
1963
2044
  run_id=run_id, session_id=session_id, user_id=user_id, session_state=session_state
1964
2045
  )
@@ -1970,25 +2051,8 @@ class Agent:
1970
2051
  images=images, videos=videos, audios=audio, files=files
1971
2052
  )
1972
2053
 
1973
- # Create RunInput to capture the original user input
1974
- run_input = RunInput(
1975
- input_content=validated_input,
1976
- images=image_artifacts,
1977
- videos=video_artifacts,
1978
- audios=audio_artifacts,
1979
- files=file_artifacts,
1980
- )
1981
-
1982
- # Read existing session from storage
1983
- agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
1984
- self._update_metadata(session=agent_session)
1985
-
1986
- # Update session state from DB
1987
- session_state = self._load_session_state(session=agent_session, session_state=session_state)
1988
-
1989
- # Determine run dependencies
2054
+ # Resolve variables
1990
2055
  run_dependencies = dependencies if dependencies is not None else self.dependencies
1991
-
1992
2056
  add_dependencies = (
1993
2057
  add_dependencies_to_context if add_dependencies_to_context is not None else self.add_dependencies_to_context
1994
2058
  )
@@ -1999,13 +2063,14 @@ class Agent:
1999
2063
  )
2000
2064
  add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
2001
2065
 
2002
- # Extract workflow context from kwargs if present
2003
- workflow_context = kwargs.pop("workflow_context", None)
2004
-
2005
- effective_filters = knowledge_filters
2006
- # When filters are passed manually
2007
- if self.knowledge_filters or knowledge_filters:
2008
- 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
+ )
2009
2074
 
2010
2075
  # Use stream override value when necessary
2011
2076
  if stream is None:
@@ -2027,6 +2092,11 @@ class Agent:
2027
2092
  response_format = self._get_response_format() if self.parser_model is None else None
2028
2093
  self.model = cast(Model, self.model)
2029
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
+
2030
2100
  # Merge agent metadata with run metadata
2031
2101
  if self.metadata is not None:
2032
2102
  if metadata is None:
@@ -2063,39 +2133,37 @@ class Agent:
2063
2133
  # Pass the new run_response to _arun
2064
2134
  if stream:
2065
2135
  return self._arun_stream( # type: ignore
2136
+ input=validated_input,
2066
2137
  run_response=run_response,
2067
- session=agent_session,
2068
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,
2069
2144
  session_state=session_state,
2070
2145
  knowledge_filters=effective_filters,
2071
2146
  add_history_to_context=add_history,
2072
2147
  add_dependencies_to_context=add_dependencies,
2073
2148
  add_session_state_to_context=add_session_state,
2074
2149
  metadata=metadata,
2075
- response_format=response_format,
2076
- stream_intermediate_steps=stream_intermediate_steps,
2077
- workflow_context=workflow_context,
2078
- yield_run_response=yield_run_response,
2079
- dependencies=run_dependencies,
2080
2150
  debug_mode=debug_mode,
2081
2151
  **kwargs,
2082
2152
  ) # type: ignore[assignment]
2083
2153
  else:
2084
2154
  return self._arun( # type: ignore
2155
+ input=validated_input,
2085
2156
  run_response=run_response,
2086
2157
  user_id=user_id,
2087
- session=agent_session,
2158
+ response_format=response_format,
2159
+ dependencies=run_dependencies,
2160
+ session_id=session_id,
2088
2161
  session_state=session_state,
2089
- knowledge_filters=knowledge_filters,
2162
+ knowledge_filters=effective_filters,
2090
2163
  add_history_to_context=add_history,
2091
2164
  add_dependencies_to_context=add_dependencies,
2092
2165
  add_session_state_to_context=add_session_state,
2093
2166
  metadata=metadata,
2094
- response_format=response_format,
2095
- stream_intermediate_steps=stream_intermediate_steps,
2096
- workflow_context=workflow_context,
2097
- yield_run_response=yield_run_response,
2098
- dependencies=run_dependencies,
2099
2167
  debug_mode=debug_mode,
2100
2168
  **kwargs,
2101
2169
  )
@@ -2116,17 +2184,6 @@ class Agent:
2116
2184
  import time
2117
2185
 
2118
2186
  time.sleep(delay)
2119
- except RunCancelledException as e:
2120
- # Handle run cancellation
2121
- log_info(f"Run {run_response.run_id} was cancelled")
2122
- run_response.content = str(e)
2123
- run_response.status = RunStatus.cancelled
2124
-
2125
- # Add the RunOutput to Agent Session even when cancelled
2126
- agent_session.upsert_run(run=run_response)
2127
- self.save_session(session=agent_session)
2128
-
2129
- return run_response
2130
2187
  except KeyboardInterrupt:
2131
2188
  run_response.content = "Operation cancelled by user"
2132
2189
  run_response.status = RunStatus.cancelled
@@ -2223,6 +2280,9 @@ class Agent:
2223
2280
  if run_response is None and (run_id is not None and (session_id is None and self.session_id is None)):
2224
2281
  raise ValueError("Session ID is required to continue a run from a run_id.")
2225
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
+
2226
2286
  session_id = run_response.session_id if run_response else session_id
2227
2287
 
2228
2288
  session_id, user_id, session_state = self._initialize_session(
@@ -2484,11 +2544,12 @@ class Agent:
2484
2544
  Steps:
2485
2545
  1. Handle any updated tools
2486
2546
  2. Generate a response from the Model
2487
- 3. Update Agent Memory
2488
- 4. Calculate session metrics
2489
- 5. Save output to file if save_response_to_file is set
2490
- 6. Add RunOutput to Agent Session
2491
- 7. Save session to storage
2547
+ 3. Calculate session metrics
2548
+ 4. Save output to file if save_response_to_file is set
2549
+ 5. Add the run to memory
2550
+ 6. Update Agent Memory
2551
+ 7. Create the run completed event
2552
+ 8. Save session to storage
2492
2553
  """
2493
2554
 
2494
2555
  if dependencies is not None:
@@ -2523,8 +2584,6 @@ class Agent:
2523
2584
 
2524
2585
  run_response.status = RunStatus.completed
2525
2586
 
2526
- completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
2527
-
2528
2587
  # Set the run duration
2529
2588
  if run_response.metrics:
2530
2589
  run_response.metrics.stop_timer()
@@ -2542,7 +2601,10 @@ class Agent:
2542
2601
  run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2543
2602
  )
2544
2603
 
2545
- # 7. Save session to storage
2604
+ # 7. Create the run completed event
2605
+ completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
2606
+
2607
+ # 8. Save session to storage
2546
2608
  self.save_session(session=session)
2547
2609
 
2548
2610
  if stream_intermediate_steps:
@@ -2601,6 +2663,7 @@ class Agent:
2601
2663
  knowledge_filters: Optional[Dict[str, Any]] = None,
2602
2664
  dependencies: Optional[Dict[str, Any]] = None,
2603
2665
  debug_mode: Optional[bool] = None,
2666
+ yield_run_response: bool = False,
2604
2667
  **kwargs,
2605
2668
  ) -> Union[RunOutput, AsyncIterator[Union[RunOutputEvent, RunOutput]]]:
2606
2669
  """Continue a previous run.
@@ -2633,21 +2696,8 @@ class Agent:
2633
2696
  # Initialize the Agent
2634
2697
  self.initialize_agent(debug_mode=debug_mode)
2635
2698
 
2636
- # Read existing session from storage
2637
- agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2638
- self._update_metadata(session=agent_session)
2639
-
2640
- # Update session state from DB
2641
- session_state = self._load_session_state(session=agent_session, session_state=session_state)
2642
-
2643
2699
  run_dependencies = dependencies if dependencies is not None else self.dependencies
2644
2700
 
2645
- effective_filters = knowledge_filters
2646
-
2647
- # When filters are passed manually
2648
- if self.knowledge_filters or knowledge_filters:
2649
- effective_filters = self._get_effective_filters(knowledge_filters)
2650
-
2651
2701
  # If no retries are set, use the agent's default retries
2652
2702
  retries = retries if retries is not None else self.retries
2653
2703
 
@@ -2667,70 +2717,42 @@ class Agent:
2667
2717
  self.stream = self.stream or stream
2668
2718
  self.stream_intermediate_steps = self.stream_intermediate_steps or (stream_intermediate_steps and self.stream)
2669
2719
 
2670
- # Run can be continued from previous run response or from passed run_response context
2671
- if run_response is not None:
2672
- # The run is continued from a provided run_response. This contains the updated tools.
2673
- input = run_response.messages or []
2674
- elif run_id is not None:
2675
- # The run is continued from a run_id. This requires the updated tools to be passed.
2676
- if updated_tools is None:
2677
- raise ValueError("Updated tools are required to continue a run from a run_id.")
2678
-
2679
- runs = agent_session.runs
2680
- run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
2681
- if run_response is None:
2682
- raise RuntimeError(f"No runs found for run ID {run_id}")
2683
- run_response.tools = updated_tools
2684
- input = run_response.messages or []
2685
- else:
2686
- 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)
2687
2724
 
2688
2725
  # Prepare arguments for the model
2689
2726
  response_format = self._get_response_format()
2690
2727
  self.model = cast(Model, self.model)
2691
2728
 
2692
- self._determine_tools_for_model(
2693
- model=self.model,
2694
- run_response=run_response,
2695
- session=agent_session,
2696
- session_state=session_state,
2697
- user_id=user_id,
2698
- async_mode=True,
2699
- knowledge_filters=effective_filters,
2700
- )
2701
-
2702
2729
  last_exception = None
2703
2730
  num_attempts = retries + 1
2704
2731
  for attempt in range(num_attempts):
2705
- run_response = cast(RunOutput, run_response)
2706
-
2707
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
2708
-
2709
- # Prepare run messages
2710
- run_messages: RunMessages = self._get_continue_run_messages(
2711
- input=input,
2712
- )
2713
-
2714
- # Reset the run paused state
2715
- run_response.status = RunStatus.running
2716
-
2717
2732
  try:
2718
2733
  if stream:
2719
2734
  return self._acontinue_run_stream(
2720
2735
  run_response=run_response,
2721
- run_messages=run_messages,
2736
+ updated_tools=updated_tools,
2737
+ knowledge_filters=effective_filters,
2738
+ session_state=session_state,
2739
+ run_id=run_id,
2722
2740
  user_id=user_id,
2723
- session=agent_session,
2741
+ session_id=session_id,
2724
2742
  response_format=response_format,
2725
- stream_intermediate_steps=stream_intermediate_steps,
2726
2743
  dependencies=run_dependencies,
2744
+ stream_intermediate_steps=stream_intermediate_steps,
2745
+ yield_run_response=yield_run_response,
2727
2746
  )
2728
2747
  else:
2729
2748
  return self._acontinue_run( # type: ignore
2749
+ session_id=session_id,
2730
2750
  run_response=run_response,
2731
- run_messages=run_messages,
2751
+ updated_tools=updated_tools,
2752
+ knowledge_filters=effective_filters,
2753
+ session_state=session_state,
2754
+ run_id=run_id,
2732
2755
  user_id=user_id,
2733
- session=agent_session,
2734
2756
  response_format=response_format,
2735
2757
  dependencies=run_dependencies,
2736
2758
  debug_mode=debug_mode,
@@ -2750,6 +2772,7 @@ class Agent:
2750
2772
 
2751
2773
  time.sleep(delay)
2752
2774
  except KeyboardInterrupt:
2775
+ run_response = cast(RunOutput, run_response)
2753
2776
  if stream:
2754
2777
  return async_generator_wrapper( # type: ignore
2755
2778
  create_run_cancelled_event(run_response, "Operation cancelled by user")
@@ -2774,9 +2797,12 @@ class Agent:
2774
2797
 
2775
2798
  async def _acontinue_run(
2776
2799
  self,
2777
- run_response: RunOutput,
2778
- run_messages: RunMessages,
2779
- 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,
2780
2806
  user_id: Optional[str] = None,
2781
2807
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
2782
2808
  dependencies: Optional[Dict[str, Any]] = None,
@@ -2786,173 +2812,408 @@ class Agent:
2786
2812
  """Continue a previous run.
2787
2813
 
2788
2814
  Steps:
2789
- 1. Handle any updated tools
2790
- 2. Generate a response from the Model
2791
- 3. Add the run to memory
2792
- 4. Update Agent Memory
2793
- 5. Calculate session metrics
2794
- 6. Save session to storage
2795
- 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
2796
2828
  """
2797
- # 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
2798
2838
  if dependencies is not None:
2799
- await self._aresolve_run_dependencies(dependencies)
2839
+ await self._aresolve_run_dependencies(dependencies=dependencies)
2800
2840
 
2801
- 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)
2802
2845
 
2803
- # 1. Handle the updated tools
2804
- 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.")
2805
2854
 
2806
- # 2. Generate a response from the Model (includes running function calls)
2807
- model_response: ModelResponse = await self.model.aresponse(
2808
- messages=run_messages.messages,
2809
- response_format=response_format,
2810
- tools=self._tools_for_model,
2811
- functions=self._functions_for_model,
2812
- tool_choice=self.tool_choice,
2813
- 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,
2814
2878
  )
2815
2879
 
2816
- 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
+ )
2817
2884
 
2818
- # We should break out of the run function
2819
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
2820
- return self._handle_agent_run_paused(
2821
- 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,
2822
2900
  )
2901
+ # Check for cancellation after model call
2902
+ raise_if_cancelled(run_response.run_id) # type: ignore
2823
2903
 
2824
- # 3. Calculate session metrics
2825
- 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)
2826
2906
 
2827
- 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)
2828
2909
 
2829
- # Convert the response to the structured format if needed
2830
- 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
+ )
2831
2914
 
2832
- # Set the run duration
2833
- if run_response.metrics:
2834
- 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)
2835
2919
 
2836
- if self.post_hooks is not None:
2837
- await self._aexecute_post_hooks(
2838
- hooks=self.post_hooks, # type: ignore
2839
- run_output=run_response,
2840
- 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,
2841
2960
  user_id=user_id,
2842
- debug_mode=debug_mode,
2843
- **kwargs,
2844
2961
  )
2845
2962
 
2846
- # 4. Save output to file if save_response_to_file is set
2847
- self.save_run_response_to_file(
2848
- run_response=run_response, input=run_messages.user_message, session_id=session.session_id, user_id=user_id
2849
- )
2963
+ agent_session.upsert_run(run=run_response)
2850
2964
 
2851
- # 5. Add the run to memory
2852
- 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)
2853
2970
 
2854
- # 6. Update Agent Memory
2855
- async for _ in self._amake_memories_and_summaries(
2856
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2857
- ):
2858
- pass
2971
+ # Log Agent Telemetry
2972
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2859
2973
 
2860
- # 7. Save session to storage
2861
- self.save_session(session=session)
2974
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2862
2975
 
2863
- # Log Agent Telemetry
2864
- await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
2976
+ return run_response
2865
2977
 
2866
- 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
2867
2983
 
2868
- 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
2869
2995
 
2870
2996
  async def _acontinue_run_stream(
2871
2997
  self,
2872
- run_response: RunOutput,
2873
- run_messages: RunMessages,
2874
- 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,
2875
3004
  user_id: Optional[str] = None,
2876
3005
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
2877
3006
  stream_intermediate_steps: bool = False,
3007
+ yield_run_response: Optional[bool] = None,
2878
3008
  dependencies: Optional[Dict[str, Any]] = None,
2879
3009
  ) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]:
2880
3010
  """Continue a previous run.
2881
3011
 
2882
3012
  Steps:
2883
- 1. Handle any updated tools
2884
- 2. Generate a response from the Model
2885
- 3. Add the run to memory
2886
- 4. Update Agent Memory
2887
- 5. Calculate session metrics
2888
- 6. Save output to file if save_response_to_file is set
2889
- 7. 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
2890
3027
  """
2891
- # Resolve dependencies
3028
+ log_debug(f"Agent Run Continue: {run_response.run_id}", center=True) # type: ignore
3029
+
3030
+ # 1. Resolve dependencies
2892
3031
  if dependencies is not None:
2893
3032
  await self._aresolve_run_dependencies(dependencies=dependencies)
2894
3033
 
2895
- # Start the Run by yielding a RunContinued event
2896
- if stream_intermediate_steps:
2897
- yield self._handle_event(create_run_continued_event(run_response), run_response)
2898
-
2899
- # 1. Handle the updated tools
2900
- async for event in self._ahandle_tool_call_updates_stream(run_response=run_response, run_messages=run_messages):
2901
- yield event
2902
-
2903
- # 2. Process model response
2904
- async for event in self._ahandle_model_response_stream(
2905
- session=session,
2906
- run_response=run_response,
2907
- run_messages=run_messages,
2908
- response_format=response_format,
2909
- stream_intermediate_steps=stream_intermediate_steps,
2910
- ):
2911
- 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)
2912
3036
 
2913
- # We should break out of the run function
2914
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
2915
- for item in self._handle_agent_run_paused_stream(
2916
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2917
- ):
2918
- yield item
2919
- return
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)
2920
3041
 
2921
- # 3. Calculate session metrics
2922
- self._update_session_metrics(session=session, run_response=run_response)
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.")
2923
3050
 
2924
- run_response.status = RunStatus.completed
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.")
2925
3059
 
2926
- completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
3060
+ run_response = cast(RunOutput, run_response)
3061
+ run_response.status = RunStatus.running
2927
3062
 
2928
- # Set the run duration
2929
- if run_response.metrics:
2930
- 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
+ )
2931
3075
 
2932
- # 4. Save output to file if save_response_to_file is set
2933
- self.save_run_response_to_file(
2934
- 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,
2935
3079
  )
2936
3080
 
2937
- # 5. Add the run to memory
2938
- session.upsert_run(run=run_response)
3081
+ # Register run for cancellation tracking
3082
+ register_run(run_response.run_id) # type: ignore
2939
3083
 
2940
- # 6. Update Agent Memory
2941
- async for event in self._amake_memories_and_summaries(
2942
- run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
2943
- ):
2944
- 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)
2945
3088
 
2946
- # 7. Save session to storage
2947
- self.save_session(session=session)
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
2948
3095
 
2949
- if stream_intermediate_steps:
2950
- yield completed_event
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
2951
3112
 
2952
- # Log Agent Telemetry
2953
- await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
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
2954
3129
 
2955
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
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
3139
+
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
2956
3217
 
2957
3218
  def _execute_pre_hooks(
2958
3219
  self,
@@ -3608,7 +3869,6 @@ class Agent:
3608
3869
  run_messages: RunMessages,
3609
3870
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
3610
3871
  stream_intermediate_steps: bool = False,
3611
- workflow_context: Optional[Dict] = None,
3612
3872
  ) -> Iterator[RunOutputEvent]:
3613
3873
  self.model = cast(Model, self.model)
3614
3874
 
@@ -3642,7 +3902,6 @@ class Agent:
3642
3902
  reasoning_state=reasoning_state,
3643
3903
  parse_structured_output=self.should_parse_structured_output,
3644
3904
  stream_intermediate_steps=stream_intermediate_steps,
3645
- workflow_context=workflow_context,
3646
3905
  )
3647
3906
 
3648
3907
  # Determine reasoning completed
@@ -3685,7 +3944,6 @@ class Agent:
3685
3944
  run_messages: RunMessages,
3686
3945
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
3687
3946
  stream_intermediate_steps: bool = False,
3688
- workflow_context: Optional[Dict] = None,
3689
3947
  ) -> AsyncIterator[RunOutputEvent]:
3690
3948
  self.model = cast(Model, self.model)
3691
3949
 
@@ -3721,7 +3979,6 @@ class Agent:
3721
3979
  reasoning_state=reasoning_state,
3722
3980
  parse_structured_output=self.should_parse_structured_output,
3723
3981
  stream_intermediate_steps=stream_intermediate_steps,
3724
- workflow_context=workflow_context,
3725
3982
  ):
3726
3983
  yield event
3727
3984
 
@@ -3766,7 +4023,6 @@ class Agent:
3766
4023
  reasoning_state: Optional[Dict[str, Any]] = None,
3767
4024
  parse_structured_output: bool = False,
3768
4025
  stream_intermediate_steps: bool = False,
3769
- workflow_context: Optional[Dict] = None,
3770
4026
  ) -> Iterator[RunOutputEvent]:
3771
4027
  if isinstance(model_response_event, tuple(get_args(RunOutputEvent))) or isinstance(
3772
4028
  model_response_event, tuple(get_args(TeamRunOutputEvent))
@@ -3830,7 +4086,6 @@ class Agent:
3830
4086
  content_type=content_type,
3831
4087
  ),
3832
4088
  run_response,
3833
- workflow_context=workflow_context,
3834
4089
  )
3835
4090
  elif (
3836
4091
  model_response_event.content is not None
@@ -3849,7 +4104,6 @@ class Agent:
3849
4104
  model_provider_data=model_response_event.provider_data,
3850
4105
  ),
3851
4106
  run_response,
3852
- workflow_context=workflow_context,
3853
4107
  )
3854
4108
 
3855
4109
  # Process audio
@@ -3910,7 +4164,6 @@ class Agent:
3910
4164
  response_audio=run_response.response_audio,
3911
4165
  ),
3912
4166
  run_response,
3913
- workflow_context=workflow_context,
3914
4167
  )
3915
4168
 
3916
4169
  if model_response_event.images is not None:
@@ -3920,7 +4173,6 @@ class Agent:
3920
4173
  image=model_response_event.images[-1],
3921
4174
  ),
3922
4175
  run_response,
3923
- workflow_context=workflow_context,
3924
4176
  )
3925
4177
 
3926
4178
  if model_response.images is None:
@@ -4144,7 +4396,7 @@ class Agent:
4144
4396
 
4145
4397
  tasks.append(
4146
4398
  self.memory_manager.acreate_user_memories(
4147
- 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
4148
4400
  )
4149
4401
  )
4150
4402
 
@@ -4168,7 +4420,9 @@ class Agent:
4168
4420
  continue
4169
4421
 
4170
4422
  if len(parsed_messages) > 0:
4171
- 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
+ )
4172
4426
  else:
4173
4427
  log_warning("Unable to add messages to memory")
4174
4428
 
@@ -4317,6 +4571,100 @@ class Agent:
4317
4571
 
4318
4572
  return agent_tools
4319
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
+
4320
4668
  def _collect_joint_images(
4321
4669
  self,
4322
4670
  run_input: Optional[RunInput] = None,
@@ -4570,30 +4918,152 @@ class Agent:
4570
4918
  func._audios = joint_audios
4571
4919
  func._videos = joint_videos
4572
4920
 
4573
- def _model_should_return_structured_output(self):
4574
- self.model = cast(Model, self.model)
4575
- return bool(
4576
- self.model.supports_native_structured_outputs
4577
- and self.output_schema is not None
4578
- and (not self.use_json_mode or self.structured_outputs)
4579
- )
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
4580
4934
 
4581
- def _get_response_format(self, model: Optional[Model] = None) -> Optional[Union[Dict, Type[BaseModel]]]:
4582
- model = cast(Model, model or self.model)
4583
- if self.output_schema is None:
4584
- return None
4585
- else:
4586
- 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
+ )
4587
4942
 
4588
- if model.supports_native_structured_outputs:
4589
- if not self.use_json_mode or self.structured_outputs:
4590
- log_debug("Setting Model.response_format to Agent.output_schema")
4591
- return self.output_schema
4592
- else:
4593
- log_debug(
4594
- "Model supports native structured outputs but it is not enabled. Using JSON mode instead."
4595
- )
4596
- return json_response_format
4943
+ self._tools_for_model = []
4944
+ self._functions_for_model = {}
4945
+ self._tool_instructions = []
4946
+
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
4597
5067
 
4598
5068
  elif model.supports_json_schema_outputs:
4599
5069
  if self.use_json_mode or (not self.structured_outputs):
@@ -4679,6 +5149,16 @@ class Agent:
4679
5149
  log_warning(f"Error getting session from db: {e}")
4680
5150
  return None
4681
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
+
4682
5162
  def _upsert_session(self, session: AgentSession) -> Optional[AgentSession]:
4683
5163
  """Upsert a Session into the database."""
4684
5164
 
@@ -4690,6 +5170,16 @@ class Agent:
4690
5170
  log_warning(f"Error upserting session into db: {e}")
4691
5171
  return None
4692
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
+
4693
5183
  def _load_session_state(self, session: AgentSession, session_state: Dict[str, Any]):
4694
5184
  """Load and return the stored session_state from the database, optionally merging it with the given one"""
4695
5185
 
@@ -4775,6 +5265,42 @@ class Agent:
4775
5265
 
4776
5266
  return agent_session
4777
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
+
4778
5304
  def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
4779
5305
  """
4780
5306
  Get a RunOutput from the database.
@@ -4895,6 +5421,27 @@ class Agent:
4895
5421
  self._upsert_session(session=session)
4896
5422
  log_debug(f"Created or updated AgentSession record: {session.session_id}")
4897
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
+
4898
5445
  def get_chat_history(self, session_id: Optional[str] = None) -> List[Message]:
4899
5446
  """Read the chat history from the session"""
4900
5447
  if not session_id and not self.session_id:
@@ -5053,8 +5600,7 @@ class Agent:
5053
5600
  session = self.get_session(session_id=session_id) # type: ignore
5054
5601
 
5055
5602
  if session is None:
5056
- log_warning(f"Session {session_id} not found")
5057
- return []
5603
+ raise Exception("Session not found")
5058
5604
 
5059
5605
  # Only filter by agent_id if this is part of a team
5060
5606
  return session.get_messages_from_last_n_runs(
@@ -5084,6 +5630,16 @@ class Agent:
5084
5630
 
5085
5631
  return self.memory_manager.get_user_memories(user_id=user_id)
5086
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
+
5087
5643
  def _format_message_with_state_variables(
5088
5644
  self,
5089
5645
  message: Any,
@@ -5097,40 +5653,313 @@ class Agent:
5097
5653
  import string
5098
5654
  from copy import deepcopy
5099
5655
 
5100
- if not isinstance(message, str):
5101
- 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"
5102
5948
 
5103
- # Should already be resolved and passed from run() method
5104
- format_variables = ChainMap(
5105
- session_state or {},
5106
- dependencies or {},
5107
- metadata or {},
5108
- {"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
5109
5954
  )
5110
- converted_msg = deepcopy(message)
5111
- for var_name in format_variables.keys():
5112
- # Only convert standalone {var_name} patterns, not nested ones
5113
- pattern = r"\{" + re.escape(var_name) + r"\}"
5114
- replacement = "${" + var_name + "}"
5115
- converted_msg = re.sub(pattern, replacement, converted_msg)
5116
-
5117
- # Use Template to safely substitute variables
5118
- template = string.Template(converted_msg)
5119
- try:
5120
- result = template.safe_substitute(format_variables)
5121
- return result
5122
- except Exception as e:
5123
- log_warning(f"Template substitution failed: {e}")
5124
- return message
5125
5955
 
5126
- def get_system_message(
5956
+ async def aget_system_message(
5127
5957
  self,
5128
5958
  session: AgentSession,
5129
5959
  session_state: Optional[Dict[str, Any]] = None,
5130
5960
  user_id: Optional[str] = None,
5131
5961
  dependencies: Optional[Dict[str, Any]] = None,
5132
5962
  metadata: Optional[Dict[str, Any]] = None,
5133
- add_session_state_to_context: Optional[bool] = None,
5134
5963
  ) -> Optional[Message]:
5135
5964
  """Return the system message for the Agent.
5136
5965
 
@@ -5244,7 +6073,7 @@ class Agent:
5244
6073
 
5245
6074
  # 3.2.5 Add information about agentic filters if enabled
5246
6075
  if self.knowledge is not None and self.enable_agentic_knowledge_filters:
5247
- valid_filters = self.knowledge.get_valid_filters()
6076
+ valid_filters = getattr(self.knowledge, "valid_metadata_filters", None)
5248
6077
  if valid_filters:
5249
6078
  valid_filters_str = ", ".join(valid_filters)
5250
6079
  additional_information.append(
@@ -5319,7 +6148,12 @@ class Agent:
5319
6148
  if self.memory_manager is None:
5320
6149
  self._set_memory_manager()
5321
6150
  _memory_manager_not_set = True
5322
- 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
+
5323
6157
  if user_memories and len(user_memories) > 0:
5324
6158
  system_message_content += (
5325
6159
  "You have access to memories from previous interactions with the user that you can use:\n\n"
@@ -5385,7 +6219,7 @@ class Agent:
5385
6219
  system_message_content += f"{get_response_model_format_prompt(self.output_schema)}"
5386
6220
 
5387
6221
  # 3.3.15 Add the session state to the system message
5388
- 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:
5389
6223
  system_message_content += self._get_formatted_session_state_for_system_message(session_state)
5390
6224
 
5391
6225
  # Return the system message
@@ -5482,90 +6316,291 @@ class Agent:
5482
6316
  log_warning(f"Failed to validate message: {e}")
5483
6317
  raise Exception(f"Failed to validate message: {e}")
5484
6318
 
5485
- # If message is provided as a BaseModel, convert it to a Message
5486
- elif isinstance(input, BaseModel):
5487
- try:
5488
- # Create a user message with the BaseModel content
5489
- content = input.model_dump_json(indent=2, exclude_none=True)
5490
- return Message(role=self.user_message_role, content=content)
5491
- except Exception as e:
5492
- log_warning(f"Failed to convert BaseModel to message: {e}")
5493
- raise Exception(f"Failed to convert BaseModel to message: {e}")
5494
- else:
5495
- user_msg_content = input
5496
- if self.add_knowledge_to_context:
5497
- if isinstance(input, str):
5498
- user_msg_content = input
5499
- elif callable(input):
5500
- user_msg_content = input(agent=self)
5501
- else:
5502
- 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
5503
6551
 
5504
- try:
5505
- retrieval_timer = Timer()
5506
- retrieval_timer.start()
5507
- docs_from_knowledge = self.get_relevant_docs_from_knowledge(
5508
- query=user_msg_content, filters=knowledge_filters, **kwargs
5509
- )
5510
- if docs_from_knowledge is not None:
5511
- references = MessageReferences(
5512
- query=user_msg_content,
5513
- references=docs_from_knowledge,
5514
- time=round(retrieval_timer.elapsed, 4),
5515
- )
5516
- # Add the references to the run_response
5517
- if run_response.references is None:
5518
- run_response.references = []
5519
- run_response.references.append(references)
5520
- retrieval_timer.stop()
5521
- log_debug(f"Time to get references: {retrieval_timer.elapsed:.4f}s")
5522
- except Exception as e:
5523
- 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
5524
6557
 
5525
- if self.resolve_in_context:
5526
- user_msg_content = self._format_message_with_state_variables(
5527
- user_msg_content,
5528
- user_id=user_id,
5529
- session_state=session_state,
5530
- dependencies=dependencies,
5531
- metadata=metadata,
5532
- )
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}")
5533
6564
 
5534
- # Convert to string for concatenation operations
5535
- 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}")
5536
6573
 
5537
- # 4.1 Add knowledge references to user message
5538
- if (
5539
- self.add_knowledge_to_context
5540
- and references is not None
5541
- and references.references is not None
5542
- and len(references.references) > 0
5543
- ):
5544
- user_msg_content_str += "\n\nUse the following references from the knowledge base if it helps:\n"
5545
- user_msg_content_str += "<references>\n"
5546
- user_msg_content_str += self._convert_documents_to_string(references.references) + "\n"
5547
- user_msg_content_str += "</references>"
5548
- # 4.2 Add context to user message
5549
- if add_dependencies_to_context and dependencies is not None:
5550
- user_msg_content_str += "\n\n<additional context>\n"
5551
- user_msg_content_str += self._convert_dependencies_to_string(dependencies) + "\n"
5552
- 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}")
5553
6595
 
5554
- # Use the string version for the final content
5555
- 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)
5556
6600
 
5557
- # Return the user message
5558
- return Message(
5559
- role=self.user_message_role,
5560
- content=user_msg_content,
5561
- audio=None if not self.send_media_to_model else audio,
5562
- images=None if not self.send_media_to_model else images,
5563
- videos=None if not self.send_media_to_model else videos,
5564
- files=None if not self.send_media_to_model else files,
5565
- **kwargs,
5566
- )
6601
+ return run_messages
5567
6602
 
5568
- def _get_run_messages(
6603
+ async def _aget_run_messages(
5569
6604
  self,
5570
6605
  *,
5571
6606
  run_response: RunOutput,
@@ -5579,7 +6614,7 @@ class Agent:
5579
6614
  files: Optional[Sequence[File]] = None,
5580
6615
  knowledge_filters: Optional[Dict[str, Any]] = None,
5581
6616
  add_history_to_context: Optional[bool] = None,
5582
- dependencies: Optional[Dict[str, Any]] = None,
6617
+ run_dependencies: Optional[Dict[str, Any]] = None,
5583
6618
  add_dependencies_to_context: Optional[bool] = None,
5584
6619
  add_session_state_to_context: Optional[bool] = None,
5585
6620
  metadata: Optional[Dict[str, Any]] = None,
@@ -5613,13 +6648,12 @@ class Agent:
5613
6648
  run_messages = RunMessages()
5614
6649
 
5615
6650
  # 1. Add system message to run_messages
5616
- system_message = self.get_system_message(
6651
+ system_message = await self.aget_system_message(
5617
6652
  session=session,
5618
6653
  session_state=session_state,
5619
6654
  user_id=user_id,
5620
- dependencies=dependencies,
6655
+ dependencies=run_dependencies,
5621
6656
  metadata=metadata,
5622
- add_session_state_to_context=add_session_state_to_context,
5623
6657
  )
5624
6658
  if system_message is not None:
5625
6659
  run_messages.system_message = system_message
@@ -5656,16 +6690,9 @@ class Agent:
5656
6690
  if add_history_to_context:
5657
6691
  from copy import deepcopy
5658
6692
 
5659
- # Only skip messages from history when system_message_role is NOT a standard conversation role.
5660
- # Standard conversation roles ("user", "assistant", "tool") should never be filtered
5661
- # to preserve conversation continuity.
5662
- skip_role = (
5663
- self.system_message_role if self.system_message_role not in ["user", "assistant", "tool"] else None
5664
- )
5665
-
5666
6693
  history: List[Message] = session.get_messages_from_last_n_runs(
5667
6694
  last_n=self.num_history_runs,
5668
- skip_role=skip_role,
6695
+ skip_role=self.system_message_role,
5669
6696
  agent_id=self.id if self.team_id is not None else None,
5670
6697
  )
5671
6698
 
@@ -5705,7 +6732,7 @@ class Agent:
5705
6732
  videos=videos,
5706
6733
  files=files,
5707
6734
  knowledge_filters=knowledge_filters,
5708
- dependencies=dependencies,
6735
+ run_dependencies=run_dependencies,
5709
6736
  add_dependencies_to_context=add_dependencies_to_context,
5710
6737
  metadata=metadata,
5711
6738
  **kwargs,
@@ -5718,13 +6745,7 @@ class Agent:
5718
6745
  # 4.3 If input is provided as a dict, try to validate it as a Message
5719
6746
  elif isinstance(input, dict):
5720
6747
  try:
5721
- if self.input_schema and is_typed_dict(self.input_schema):
5722
- import json
5723
-
5724
- content = json.dumps(input, indent=2, ensure_ascii=False)
5725
- user_message = Message(role=self.user_message_role, content=content)
5726
- else:
5727
- user_message = Message.model_validate(input)
6748
+ user_message = Message.model_validate(input)
5728
6749
  except Exception as e:
5729
6750
  log_warning(f"Failed to validate message: {e}")
5730
6751
 
@@ -6283,12 +7304,15 @@ class Agent:
6283
7304
 
6284
7305
  # If a reasoning model is provided, use it to generate reasoning
6285
7306
  if reasoning_model_provided:
7307
+ from agno.reasoning.anthropic import is_anthropic_reasoning_model
6286
7308
  from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
6287
7309
  from agno.reasoning.deepseek import is_deepseek_reasoning_model
7310
+ from agno.reasoning.gemini import is_gemini_reasoning_model
6288
7311
  from agno.reasoning.groq import is_groq_reasoning_model
6289
7312
  from agno.reasoning.helpers import get_reasoning_agent
6290
7313
  from agno.reasoning.ollama import is_ollama_reasoning_model
6291
7314
  from agno.reasoning.openai import is_openai_reasoning_model
7315
+ from agno.reasoning.vertexai import is_vertexai_reasoning_model
6292
7316
 
6293
7317
  reasoning_agent = self.reasoning_agent or get_reasoning_agent(
6294
7318
  reasoning_model=reasoning_model,
@@ -6304,8 +7328,20 @@ class Agent:
6304
7328
  is_openai = is_openai_reasoning_model(reasoning_model)
6305
7329
  is_ollama = is_ollama_reasoning_model(reasoning_model)
6306
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)
6307
7334
 
6308
- 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
+ ):
6309
7345
  reasoning_message: Optional[Message] = None
6310
7346
  if is_deepseek:
6311
7347
  from agno.reasoning.deepseek import get_deepseek_reasoning
@@ -6342,6 +7378,27 @@ class Agent:
6342
7378
  reasoning_message = get_ai_foundry_reasoning(
6343
7379
  reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
6344
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
+ )
6345
7402
 
6346
7403
  if reasoning_message is None:
6347
7404
  log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
@@ -6515,12 +7572,15 @@ class Agent:
6515
7572
 
6516
7573
  # If a reasoning model is provided, use it to generate reasoning
6517
7574
  if reasoning_model_provided:
7575
+ from agno.reasoning.anthropic import is_anthropic_reasoning_model
6518
7576
  from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
6519
7577
  from agno.reasoning.deepseek import is_deepseek_reasoning_model
7578
+ from agno.reasoning.gemini import is_gemini_reasoning_model
6520
7579
  from agno.reasoning.groq import is_groq_reasoning_model
6521
7580
  from agno.reasoning.helpers import get_reasoning_agent
6522
7581
  from agno.reasoning.ollama import is_ollama_reasoning_model
6523
7582
  from agno.reasoning.openai import is_openai_reasoning_model
7583
+ from agno.reasoning.vertexai import is_vertexai_reasoning_model
6524
7584
 
6525
7585
  reasoning_agent = self.reasoning_agent or get_reasoning_agent(
6526
7586
  reasoning_model=reasoning_model,
@@ -6536,8 +7596,20 @@ class Agent:
6536
7596
  is_openai = is_openai_reasoning_model(reasoning_model)
6537
7597
  is_ollama = is_ollama_reasoning_model(reasoning_model)
6538
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)
6539
7602
 
6540
- 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
+ ):
6541
7613
  reasoning_message: Optional[Message] = None
6542
7614
  if is_deepseek:
6543
7615
  from agno.reasoning.deepseek import aget_deepseek_reasoning
@@ -6574,6 +7646,27 @@ class Agent:
6574
7646
  reasoning_message = get_ai_foundry_reasoning(
6575
7647
  reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
6576
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
+ )
6577
7670
 
6578
7671
  if reasoning_message is None:
6579
7672
  log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
@@ -6892,7 +7985,6 @@ class Agent:
6892
7985
  run_response: RunOutput,
6893
7986
  run_messages: RunMessages,
6894
7987
  stream_intermediate_steps: bool = False,
6895
- workflow_context: Optional[Dict] = None,
6896
7988
  ):
6897
7989
  """Parse the model response using the output model."""
6898
7990
  from agno.utils.events import (
@@ -6916,7 +8008,6 @@ class Agent:
6916
8008
  run_response=run_response,
6917
8009
  model_response=model_response,
6918
8010
  model_response_event=model_response_event,
6919
- workflow_context=workflow_context,
6920
8011
  stream_intermediate_steps=stream_intermediate_steps,
6921
8012
  )
6922
8013
 
@@ -6945,7 +8036,6 @@ class Agent:
6945
8036
  run_response: RunOutput,
6946
8037
  run_messages: RunMessages,
6947
8038
  stream_intermediate_steps: bool = False,
6948
- workflow_context: Optional[Dict] = None,
6949
8039
  ):
6950
8040
  """Parse the model response using the output model."""
6951
8041
  from agno.utils.events import (
@@ -6971,7 +8061,6 @@ class Agent:
6971
8061
  run_response=run_response,
6972
8062
  model_response=model_response,
6973
8063
  model_response_event=model_response_event,
6974
- workflow_context=workflow_context,
6975
8064
  stream_intermediate_steps=stream_intermediate_steps,
6976
8065
  ):
6977
8066
  yield event
@@ -6986,14 +8075,7 @@ class Agent:
6986
8075
  # Update the RunResponse metrics
6987
8076
  run_response.metrics = self._calculate_run_metrics(messages_for_run_response)
6988
8077
 
6989
- def _handle_event(self, event: RunOutputEvent, run_response: RunOutput, workflow_context: Optional[Dict] = None):
6990
- if workflow_context:
6991
- event.workflow_id = workflow_context.get("workflow_id")
6992
- event.workflow_run_id = workflow_context.get("workflow_run_id")
6993
- event.step_id = workflow_context.get("step_id")
6994
- event.step_name = workflow_context.get("step_name")
6995
- event.step_index = workflow_context.get("step_index")
6996
-
8078
+ def _handle_event(self, event: RunOutputEvent, run_response: RunOutput):
6997
8079
  # We only store events that are not run_response_content events
6998
8080
  events_to_skip = [event.value for event in self.events_to_skip] if self.events_to_skip else []
6999
8081
  if self.store_events and event.event not in events_to_skip:
@@ -7314,6 +8396,8 @@ class Agent:
7314
8396
  if self.db is None:
7315
8397
  return "Previous session messages not available"
7316
8398
 
8399
+ self.db = cast(BaseDb, self.db)
8400
+
7317
8401
  selected_sessions = self.db.get_sessions(
7318
8402
  session_type=SessionType.AGENT, limit=num_history_sessions, user_id=user_id
7319
8403
  )
@@ -7351,6 +8435,69 @@ class Agent:
7351
8435
 
7352
8436
  return get_previous_session_messages
7353
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
+
7354
8501
  ###########################################################################
7355
8502
  # Print Response
7356
8503
  ###########################################################################
@@ -7384,6 +8531,11 @@ class Agent:
7384
8531
  tags_to_include_in_markdown: Optional[Set[str]] = None,
7385
8532
  **kwargs: Any,
7386
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
+
7387
8539
  if not tags_to_include_in_markdown:
7388
8540
  tags_to_include_in_markdown = {"think", "thinking"}
7389
8541
 
@@ -7715,6 +8867,56 @@ class Agent:
7715
8867
  message.image_output = None
7716
8868
  message.video_output = None
7717
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
+
7718
8920
  def _validate_media_object_id(
7719
8921
  self,
7720
8922
  images: Optional[Sequence[Image]] = None,