agno 2.3.13__py3-none-any.whl → 2.3.15__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 (49) hide show
  1. agno/agent/agent.py +1149 -1392
  2. agno/db/migrations/manager.py +3 -3
  3. agno/eval/__init__.py +21 -8
  4. agno/knowledge/embedder/azure_openai.py +0 -1
  5. agno/knowledge/embedder/google.py +1 -1
  6. agno/models/anthropic/claude.py +9 -4
  7. agno/models/base.py +8 -4
  8. agno/models/metrics.py +12 -0
  9. agno/models/openai/chat.py +2 -0
  10. agno/models/openai/responses.py +2 -2
  11. agno/os/app.py +59 -2
  12. agno/os/auth.py +40 -3
  13. agno/os/interfaces/a2a/router.py +619 -9
  14. agno/os/interfaces/a2a/utils.py +31 -32
  15. agno/os/middleware/jwt.py +5 -5
  16. agno/os/router.py +1 -57
  17. agno/os/routers/agents/schema.py +14 -1
  18. agno/os/routers/database.py +150 -0
  19. agno/os/routers/teams/schema.py +14 -1
  20. agno/os/settings.py +3 -0
  21. agno/os/utils.py +61 -53
  22. agno/reasoning/anthropic.py +85 -1
  23. agno/reasoning/azure_ai_foundry.py +93 -1
  24. agno/reasoning/deepseek.py +91 -1
  25. agno/reasoning/gemini.py +81 -1
  26. agno/reasoning/groq.py +103 -1
  27. agno/reasoning/manager.py +1244 -0
  28. agno/reasoning/ollama.py +93 -1
  29. agno/reasoning/openai.py +113 -1
  30. agno/reasoning/vertexai.py +85 -1
  31. agno/run/agent.py +21 -0
  32. agno/run/base.py +20 -1
  33. agno/run/team.py +21 -0
  34. agno/session/team.py +0 -3
  35. agno/team/team.py +1211 -1445
  36. agno/tools/toolkit.py +119 -8
  37. agno/utils/events.py +99 -4
  38. agno/utils/hooks.py +4 -10
  39. agno/utils/print_response/agent.py +26 -0
  40. agno/utils/print_response/team.py +11 -0
  41. agno/utils/prompts.py +8 -6
  42. agno/utils/string.py +46 -0
  43. agno/utils/team.py +1 -1
  44. agno/vectordb/milvus/milvus.py +32 -3
  45. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/METADATA +3 -2
  46. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/RECORD +49 -47
  47. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/WHEEL +0 -0
  48. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/licenses/LICENSE +0 -0
  49. {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -9,7 +9,6 @@ from inspect import iscoroutinefunction
9
9
  from os import getenv
10
10
  from textwrap import dedent
11
11
  from typing import (
12
- TYPE_CHECKING,
13
12
  Any,
14
13
  AsyncIterator,
15
14
  Callable,
@@ -31,13 +30,11 @@ from uuid import uuid4
31
30
 
32
31
  from pydantic import BaseModel
33
32
 
34
- if TYPE_CHECKING:
35
- from agno.eval.base import BaseEval
36
-
37
33
  from agno.compression.manager import CompressionManager
38
34
  from agno.culture.manager import CultureManager
39
35
  from agno.db.base import AsyncBaseDb, BaseDb, SessionType, UserMemory
40
36
  from agno.db.schemas.culture import CulturalKnowledge
37
+ from agno.eval.base import BaseEval
41
38
  from agno.exceptions import (
42
39
  InputCheckError,
43
40
  OutputCheckError,
@@ -112,6 +109,7 @@ from agno.utils.agent import (
112
109
  )
113
110
  from agno.utils.common import is_typed_dict, validate_typed_dict
114
111
  from agno.utils.events import (
112
+ add_error_event,
115
113
  create_parser_model_response_completed_event,
116
114
  create_parser_model_response_started_event,
117
115
  create_post_hook_completed_event,
@@ -119,6 +117,7 @@ from agno.utils.events import (
119
117
  create_pre_hook_completed_event,
120
118
  create_pre_hook_started_event,
121
119
  create_reasoning_completed_event,
120
+ create_reasoning_content_delta_event,
122
121
  create_reasoning_started_event,
123
122
  create_reasoning_step_event,
124
123
  create_run_cancelled_event,
@@ -132,6 +131,7 @@ from agno.utils.events import (
132
131
  create_session_summary_completed_event,
133
132
  create_session_summary_started_event,
134
133
  create_tool_call_completed_event,
134
+ create_tool_call_error_event,
135
135
  create_tool_call_started_event,
136
136
  handle_event,
137
137
  )
@@ -173,7 +173,7 @@ from agno.utils.response import (
173
173
  get_paused_content,
174
174
  )
175
175
  from agno.utils.safe_formatter import SafeFormatter
176
- from agno.utils.string import generate_id_from_name, parse_response_model_str
176
+ from agno.utils.string import generate_id_from_name, parse_response_dict_str, parse_response_model_str
177
177
  from agno.utils.timer import Timer
178
178
 
179
179
 
@@ -280,9 +280,9 @@ class Agent:
280
280
 
281
281
  # --- Agent Hooks ---
282
282
  # Functions called right after agent-session is loaded, before processing starts
283
- pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None
283
+ pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
284
284
  # Functions called after output is generated but before the response is returned
285
- post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None
285
+ post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
286
286
  # If True, run hooks as FastAPI background tasks (non-blocking). Set by AgentOS.
287
287
  _run_hooks_in_background: Optional[bool] = None
288
288
 
@@ -369,8 +369,9 @@ class Agent:
369
369
  # --- Agent Response Model Settings ---
370
370
  # Provide an input schema to validate the input
371
371
  input_schema: Optional[Type[BaseModel]] = None
372
- # Provide a response model to get the response as a Pydantic model
373
- output_schema: Optional[Type[BaseModel]] = None
372
+ # Provide a response model to get the response in the implied format.
373
+ # You can use a Pydantic model or a JSON fitting the provider's expected schema.
374
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None
374
375
  # Provide a secondary model to parse the response from the primary model
375
376
  parser_model: Optional[Model] = None
376
377
  # Provide a prompt for the parser model
@@ -487,8 +488,8 @@ class Agent:
487
488
  tool_call_limit: Optional[int] = None,
488
489
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
489
490
  tool_hooks: Optional[List[Callable]] = None,
490
- pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None,
491
- post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None,
491
+ pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
492
+ post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
492
493
  reasoning: bool = False,
493
494
  reasoning_model: Optional[Union[Model, str]] = None,
494
495
  reasoning_agent: Optional[Agent] = None,
@@ -522,7 +523,7 @@ class Agent:
522
523
  parser_model: Optional[Union[Model, str]] = None,
523
524
  parser_model_prompt: Optional[str] = None,
524
525
  input_schema: Optional[Type[BaseModel]] = None,
525
- output_schema: Optional[Type[BaseModel]] = None,
526
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
526
527
  parse_response: bool = True,
527
528
  output_model: Optional[Union[Model, str]] = None,
528
529
  output_model_prompt: Optional[str] = None,
@@ -1041,90 +1042,91 @@ class Agent:
1041
1042
  12. Create session summary
1042
1043
  13. Cleanup and store the run response and session
1043
1044
  """
1045
+ memory_future = None
1046
+ cultural_knowledge_future = None
1044
1047
 
1045
- # 1. Execute pre-hooks
1046
- run_input = cast(RunInput, run_response.input)
1047
- self.model = cast(Model, self.model)
1048
- if self.pre_hooks is not None:
1049
- # Can modify the run input
1050
- pre_hook_iterator = self._execute_pre_hooks(
1051
- hooks=self.pre_hooks, # type: ignore
1048
+ try:
1049
+ # 1. Execute pre-hooks
1050
+ run_input = cast(RunInput, run_response.input)
1051
+ self.model = cast(Model, self.model)
1052
+ if self.pre_hooks is not None:
1053
+ # Can modify the run input
1054
+ pre_hook_iterator = self._execute_pre_hooks(
1055
+ hooks=self.pre_hooks, # type: ignore
1056
+ run_response=run_response,
1057
+ run_input=run_input,
1058
+ run_context=run_context,
1059
+ session=session,
1060
+ user_id=user_id,
1061
+ debug_mode=debug_mode,
1062
+ background_tasks=background_tasks,
1063
+ **kwargs,
1064
+ )
1065
+ # Consume the generator without yielding
1066
+ deque(pre_hook_iterator, maxlen=0)
1067
+
1068
+ # 2. Determine tools for model
1069
+ processed_tools = self.get_tools(
1052
1070
  run_response=run_response,
1053
- run_input=run_input,
1054
1071
  run_context=run_context,
1055
1072
  session=session,
1056
1073
  user_id=user_id,
1057
- debug_mode=debug_mode,
1058
- background_tasks=background_tasks,
1059
- **kwargs,
1060
1074
  )
1061
- # Consume the generator without yielding
1062
- deque(pre_hook_iterator, maxlen=0)
1063
-
1064
- # 2. Determine tools for model
1065
- processed_tools = self.get_tools(
1066
- run_response=run_response,
1067
- run_context=run_context,
1068
- session=session,
1069
- user_id=user_id,
1070
- )
1071
- _tools = self._determine_tools_for_model(
1072
- model=self.model,
1073
- processed_tools=processed_tools,
1074
- run_response=run_response,
1075
- session=session,
1076
- run_context=run_context,
1077
- )
1075
+ _tools = self._determine_tools_for_model(
1076
+ model=self.model,
1077
+ processed_tools=processed_tools,
1078
+ run_response=run_response,
1079
+ session=session,
1080
+ run_context=run_context,
1081
+ )
1078
1082
 
1079
- # 3. Prepare run messages
1080
- run_messages: RunMessages = self._get_run_messages(
1081
- run_response=run_response,
1082
- run_context=run_context,
1083
- input=run_input.input_content,
1084
- session=session,
1085
- user_id=user_id,
1086
- audio=run_input.audios,
1087
- images=run_input.images,
1088
- videos=run_input.videos,
1089
- files=run_input.files,
1090
- add_history_to_context=add_history_to_context,
1091
- add_dependencies_to_context=add_dependencies_to_context,
1092
- add_session_state_to_context=add_session_state_to_context,
1093
- tools=_tools,
1094
- **kwargs,
1095
- )
1096
- if len(run_messages.messages) == 0:
1097
- log_error("No messages to be sent to the model.")
1083
+ # 3. Prepare run messages
1084
+ run_messages: RunMessages = self._get_run_messages(
1085
+ run_response=run_response,
1086
+ run_context=run_context,
1087
+ input=run_input.input_content,
1088
+ session=session,
1089
+ user_id=user_id,
1090
+ audio=run_input.audios,
1091
+ images=run_input.images,
1092
+ videos=run_input.videos,
1093
+ files=run_input.files,
1094
+ add_history_to_context=add_history_to_context,
1095
+ add_dependencies_to_context=add_dependencies_to_context,
1096
+ add_session_state_to_context=add_session_state_to_context,
1097
+ tools=_tools,
1098
+ **kwargs,
1099
+ )
1100
+ if len(run_messages.messages) == 0:
1101
+ log_error("No messages to be sent to the model.")
1098
1102
 
1099
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1103
+ log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1100
1104
 
1101
- # Start memory creation on a separate thread (runs concurrently with the main execution loop)
1102
- memory_future = None
1103
- # 4. Start memory creation in background thread if memory manager is enabled and agentic memory is disabled
1104
- if (
1105
- run_messages.user_message is not None
1106
- and self.memory_manager is not None
1107
- and self.enable_user_memories
1108
- and not self.enable_agentic_memory
1109
- ):
1110
- log_debug("Starting memory creation in background thread.")
1111
- memory_future = self.background_executor.submit(
1112
- self._make_memories, run_messages=run_messages, user_id=user_id
1113
- )
1105
+ # Start memory creation on a separate thread (runs concurrently with the main execution loop)
1106
+ memory_future = None
1107
+ # 4. Start memory creation in background thread if memory manager is enabled and agentic memory is disabled
1108
+ if (
1109
+ run_messages.user_message is not None
1110
+ and self.memory_manager is not None
1111
+ and self.enable_user_memories
1112
+ and not self.enable_agentic_memory
1113
+ ):
1114
+ log_debug("Starting memory creation in background thread.")
1115
+ memory_future = self.background_executor.submit(
1116
+ self._make_memories, run_messages=run_messages, user_id=user_id
1117
+ )
1114
1118
 
1115
- # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1116
- cultural_knowledge_future = None
1117
- if (
1118
- run_messages.user_message is not None
1119
- and self.culture_manager is not None
1120
- and self.update_cultural_knowledge
1121
- ):
1122
- log_debug("Starting cultural knowledge creation in background thread.")
1123
- cultural_knowledge_future = self.background_executor.submit(
1124
- self._make_cultural_knowledge, run_messages=run_messages
1125
- )
1119
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1120
+ if (
1121
+ run_messages.user_message is not None
1122
+ and self.culture_manager is not None
1123
+ and self.update_cultural_knowledge
1124
+ ):
1125
+ log_debug("Starting cultural knowledge creation in background thread.")
1126
+ cultural_knowledge_future = self.background_executor.submit(
1127
+ self._make_cultural_knowledge, run_messages=run_messages
1128
+ )
1126
1129
 
1127
- try:
1128
1130
  raise_if_cancelled(run_response.run_id) # type: ignore
1129
1131
 
1130
1132
  # 5. Reason about the task
@@ -1215,18 +1217,6 @@ class Agent:
1215
1217
 
1216
1218
  log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1217
1219
 
1218
- return run_response
1219
- except RunCancelledException as e:
1220
- # Handle run cancellation
1221
- log_info(f"Run {run_response.run_id} was cancelled")
1222
- run_response.content = str(e)
1223
- run_response.status = RunStatus.cancelled
1224
-
1225
- # Cleanup and store the run response and session
1226
- self._cleanup_and_store(
1227
- run_response=run_response, session=session, run_context=run_context, user_id=user_id
1228
- )
1229
-
1230
1220
  return run_response
1231
1221
  finally:
1232
1222
  # Always disconnect connectable tools
@@ -1264,91 +1254,92 @@ class Agent:
1264
1254
  9. Create session summary
1265
1255
  10. Cleanup and store the run response and session
1266
1256
  """
1257
+ memory_future = None
1258
+ cultural_knowledge_future = None
1267
1259
 
1268
- # 1. Execute pre-hooks
1269
- run_input = cast(RunInput, run_response.input)
1270
- self.model = cast(Model, self.model)
1271
- if self.pre_hooks is not None:
1272
- # Can modify the run input
1273
- pre_hook_iterator = self._execute_pre_hooks(
1274
- hooks=self.pre_hooks, # type: ignore
1260
+ try:
1261
+ # 1. Execute pre-hooks
1262
+ run_input = cast(RunInput, run_response.input)
1263
+ self.model = cast(Model, self.model)
1264
+ if self.pre_hooks is not None:
1265
+ # Can modify the run input
1266
+ pre_hook_iterator = self._execute_pre_hooks(
1267
+ hooks=self.pre_hooks, # type: ignore
1268
+ run_response=run_response,
1269
+ run_input=run_input,
1270
+ run_context=run_context,
1271
+ session=session,
1272
+ user_id=user_id,
1273
+ debug_mode=debug_mode,
1274
+ stream_events=stream_events,
1275
+ background_tasks=background_tasks,
1276
+ **kwargs,
1277
+ )
1278
+ for event in pre_hook_iterator:
1279
+ yield event
1280
+
1281
+ # 2. Determine tools for model
1282
+ processed_tools = self.get_tools(
1275
1283
  run_response=run_response,
1276
- run_input=run_input,
1277
1284
  run_context=run_context,
1278
1285
  session=session,
1279
1286
  user_id=user_id,
1280
- debug_mode=debug_mode,
1281
- stream_events=stream_events,
1282
- background_tasks=background_tasks,
1283
- **kwargs,
1284
1287
  )
1285
- for event in pre_hook_iterator:
1286
- yield event
1287
-
1288
- # 2. Determine tools for model
1289
- processed_tools = self.get_tools(
1290
- run_response=run_response,
1291
- run_context=run_context,
1292
- session=session,
1293
- user_id=user_id,
1294
- )
1295
- _tools = self._determine_tools_for_model(
1296
- model=self.model,
1297
- processed_tools=processed_tools,
1298
- run_response=run_response,
1299
- session=session,
1300
- run_context=run_context,
1301
- )
1288
+ _tools = self._determine_tools_for_model(
1289
+ model=self.model,
1290
+ processed_tools=processed_tools,
1291
+ run_response=run_response,
1292
+ session=session,
1293
+ run_context=run_context,
1294
+ )
1302
1295
 
1303
- # 3. Prepare run messages
1304
- run_messages: RunMessages = self._get_run_messages(
1305
- run_response=run_response,
1306
- input=run_input.input_content,
1307
- session=session,
1308
- run_context=run_context,
1309
- user_id=user_id,
1310
- audio=run_input.audios,
1311
- images=run_input.images,
1312
- videos=run_input.videos,
1313
- files=run_input.files,
1314
- add_history_to_context=add_history_to_context,
1315
- add_dependencies_to_context=add_dependencies_to_context,
1316
- add_session_state_to_context=add_session_state_to_context,
1317
- tools=_tools,
1318
- **kwargs,
1319
- )
1320
- if len(run_messages.messages) == 0:
1321
- log_error("No messages to be sent to the model.")
1296
+ # 3. Prepare run messages
1297
+ run_messages: RunMessages = self._get_run_messages(
1298
+ run_response=run_response,
1299
+ input=run_input.input_content,
1300
+ session=session,
1301
+ run_context=run_context,
1302
+ user_id=user_id,
1303
+ audio=run_input.audios,
1304
+ images=run_input.images,
1305
+ videos=run_input.videos,
1306
+ files=run_input.files,
1307
+ add_history_to_context=add_history_to_context,
1308
+ add_dependencies_to_context=add_dependencies_to_context,
1309
+ add_session_state_to_context=add_session_state_to_context,
1310
+ tools=_tools,
1311
+ **kwargs,
1312
+ )
1313
+ if len(run_messages.messages) == 0:
1314
+ log_error("No messages to be sent to the model.")
1322
1315
 
1323
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1316
+ log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1324
1317
 
1325
- # Start memory creation on a separate thread (runs concurrently with the main execution loop)
1326
- memory_future = None
1327
- # 4. Start memory creation in background thread if memory manager is enabled and agentic memory is disabled
1328
- if (
1329
- run_messages.user_message is not None
1330
- and self.memory_manager is not None
1331
- and self.enable_user_memories
1332
- and not self.enable_agentic_memory
1333
- ):
1334
- log_debug("Starting memory creation in background thread.")
1335
- memory_future = self.background_executor.submit(
1336
- self._make_memories, run_messages=run_messages, user_id=user_id
1337
- )
1318
+ # Start memory creation on a separate thread (runs concurrently with the main execution loop)
1319
+ memory_future = None
1320
+ # 4. Start memory creation in background thread if memory manager is enabled and agentic memory is disabled
1321
+ if (
1322
+ run_messages.user_message is not None
1323
+ and self.memory_manager is not None
1324
+ and self.enable_user_memories
1325
+ and not self.enable_agentic_memory
1326
+ ):
1327
+ log_debug("Starting memory creation in background thread.")
1328
+ memory_future = self.background_executor.submit(
1329
+ self._make_memories, run_messages=run_messages, user_id=user_id
1330
+ )
1338
1331
 
1339
- # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1340
- cultural_knowledge_future = None
1341
- if (
1342
- run_messages.user_message is not None
1343
- and self.culture_manager is not None
1344
- and self.update_cultural_knowledge
1345
- ):
1346
- log_debug("Starting cultural knowledge creation in background thread.")
1347
- cultural_knowledge_future = self.background_executor.submit(
1348
- self._make_cultural_knowledge, run_messages=run_messages
1349
- )
1332
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1333
+ if (
1334
+ run_messages.user_message is not None
1335
+ and self.culture_manager is not None
1336
+ and self.update_cultural_knowledge
1337
+ ):
1338
+ log_debug("Starting cultural knowledge creation in background thread.")
1339
+ cultural_knowledge_future = self.background_executor.submit(
1340
+ self._make_cultural_knowledge, run_messages=run_messages
1341
+ )
1350
1342
 
1351
- try:
1352
1343
  # Start the Run by yielding a RunStarted event
1353
1344
  if stream_events:
1354
1345
  yield handle_event( # type: ignore
@@ -1531,28 +1522,6 @@ class Agent:
1531
1522
  self._log_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1532
1523
 
1533
1524
  log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1534
-
1535
- except RunCancelledException as e:
1536
- # Handle run cancellation during streaming
1537
- log_info(f"Run {run_response.run_id} was cancelled during streaming")
1538
- run_response.status = RunStatus.cancelled
1539
- # Don't overwrite content - preserve any partial content that was streamed
1540
- # Only set content if it's empty
1541
- if not run_response.content:
1542
- run_response.content = str(e)
1543
-
1544
- # Yield the cancellation event
1545
- yield handle_event( # type: ignore
1546
- create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
1547
- run_response,
1548
- events_to_skip=self.events_to_skip, # type: ignore
1549
- store_events=self.store_events,
1550
- )
1551
-
1552
- # Cleanup and store the run response and session
1553
- self._cleanup_and_store(
1554
- run_response=run_response, session=session, run_context=run_context, user_id=user_id
1555
- )
1556
1525
  finally:
1557
1526
  # Always disconnect connectable tools
1558
1527
  self._disconnect_connectable_tools()
@@ -1582,7 +1551,7 @@ class Agent:
1582
1551
  add_session_state_to_context: Optional[bool] = None,
1583
1552
  dependencies: Optional[Dict[str, Any]] = None,
1584
1553
  metadata: Optional[Dict[str, Any]] = None,
1585
- output_schema: Optional[Type[BaseModel]] = None,
1554
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
1586
1555
  debug_mode: Optional[bool] = None,
1587
1556
  **kwargs: Any,
1588
1557
  ) -> RunOutput: ...
@@ -1610,7 +1579,7 @@ class Agent:
1610
1579
  add_session_state_to_context: Optional[bool] = None,
1611
1580
  dependencies: Optional[Dict[str, Any]] = None,
1612
1581
  metadata: Optional[Dict[str, Any]] = None,
1613
- output_schema: Optional[Type[BaseModel]] = None,
1582
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
1614
1583
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
1615
1584
  yield_run_output: bool = False,
1616
1585
  debug_mode: Optional[bool] = None,
@@ -1639,7 +1608,7 @@ class Agent:
1639
1608
  add_session_state_to_context: Optional[bool] = None,
1640
1609
  dependencies: Optional[Dict[str, Any]] = None,
1641
1610
  metadata: Optional[Dict[str, Any]] = None,
1642
- output_schema: Optional[Type[BaseModel]] = None,
1611
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
1643
1612
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
1644
1613
  yield_run_output: Optional[bool] = None,
1645
1614
  debug_mode: Optional[bool] = None,
@@ -1651,9 +1620,10 @@ class Agent:
1651
1620
  "`run` method is not supported with an async database. Please use `arun` method instead."
1652
1621
  )
1653
1622
 
1654
- # Set the id for the run and register it immediately for cancellation tracking
1623
+ # Initialize session early for error handling
1624
+ session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
1625
+ # Set the id for the run
1655
1626
  run_id = run_id or str(uuid4())
1656
- register_run(run_id)
1657
1627
 
1658
1628
  if (add_history_to_context or self.add_history_to_context) and not self.db and not self.team_id:
1659
1629
  log_warning(
@@ -1667,82 +1637,84 @@ class Agent:
1667
1637
  stacklevel=2,
1668
1638
  )
1669
1639
 
1670
- background_tasks = kwargs.pop("background_tasks", None)
1671
- if background_tasks is not None:
1672
- from fastapi import BackgroundTasks
1640
+ # Set up retry logic
1641
+ num_attempts = self.retries + 1
1642
+ for attempt in range(num_attempts):
1643
+ if num_attempts > 1:
1644
+ log_debug(f"Retrying Agent run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
1673
1645
 
1674
- background_tasks: BackgroundTasks = background_tasks # type: ignore
1646
+ try:
1647
+ # Register run for cancellation tracking
1648
+ register_run(run_id)
1675
1649
 
1676
- # Validate input against input_schema if provided
1677
- validated_input = self._validate_input(input)
1650
+ background_tasks = kwargs.pop("background_tasks", None)
1651
+ if background_tasks is not None:
1652
+ from fastapi import BackgroundTasks
1678
1653
 
1679
- # Normalise hook & guardails
1680
- if not self._hooks_normalised:
1681
- if self.pre_hooks:
1682
- self.pre_hooks = normalize_pre_hooks(self.pre_hooks) # type: ignore
1683
- if self.post_hooks:
1684
- self.post_hooks = normalize_post_hooks(self.post_hooks) # type: ignore
1685
- self._hooks_normalised = True
1654
+ background_tasks: BackgroundTasks = background_tasks # type: ignore
1686
1655
 
1687
- session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
1656
+ # Validate input against input_schema if provided
1657
+ validated_input = self._validate_input(input)
1688
1658
 
1689
- # Initialize the Agent
1690
- self.initialize_agent(debug_mode=debug_mode)
1659
+ # Normalise hook & guardails
1660
+ if not self._hooks_normalised:
1661
+ if self.pre_hooks:
1662
+ self.pre_hooks = normalize_pre_hooks(self.pre_hooks) # type: ignore
1663
+ if self.post_hooks:
1664
+ self.post_hooks = normalize_post_hooks(self.post_hooks) # type: ignore
1665
+ self._hooks_normalised = True
1691
1666
 
1692
- image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
1693
- images=images, videos=videos, audios=audio, files=files
1694
- )
1667
+ session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
1695
1668
 
1696
- # Create RunInput to capture the original user input
1697
- run_input = RunInput(
1698
- input_content=validated_input,
1699
- images=image_artifacts,
1700
- videos=video_artifacts,
1701
- audios=audio_artifacts,
1702
- files=file_artifacts,
1703
- )
1669
+ # Initialize the Agent
1670
+ self.initialize_agent(debug_mode=debug_mode)
1704
1671
 
1705
- # Read existing session from database
1706
- agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
1707
- self._update_metadata(session=agent_session)
1672
+ image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
1673
+ images=images, videos=videos, audios=audio, files=files
1674
+ )
1708
1675
 
1709
- # Initialize session state
1710
- session_state = self._initialize_session_state(
1711
- session_state=session_state if session_state is not None else {},
1712
- user_id=user_id,
1713
- session_id=session_id,
1714
- run_id=run_id,
1715
- )
1716
- # Update session state from DB
1717
- session_state = self._load_session_state(session=agent_session, session_state=session_state)
1676
+ # Create RunInput to capture the original user input
1677
+ run_input = RunInput(
1678
+ input_content=validated_input,
1679
+ images=image_artifacts,
1680
+ videos=video_artifacts,
1681
+ audios=audio_artifacts,
1682
+ files=file_artifacts,
1683
+ )
1718
1684
 
1719
- # Determine runtime dependencies
1720
- dependencies = dependencies if dependencies is not None else self.dependencies
1685
+ # Read existing session from database
1686
+ agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
1687
+ self._update_metadata(session=agent_session)
1721
1688
 
1722
- # Resolve output_schema parameter takes precedence, then fall back to self.output_schema
1723
- if output_schema is None:
1724
- output_schema = self.output_schema
1689
+ # Initialize session state
1690
+ session_state = self._initialize_session_state(
1691
+ session_state=session_state if session_state is not None else {},
1692
+ user_id=user_id,
1693
+ session_id=session_id,
1694
+ run_id=run_id,
1695
+ )
1696
+ # Update session state from DB
1697
+ session_state = self._load_session_state(session=agent_session, session_state=session_state)
1725
1698
 
1726
- # Initialize run context
1727
- run_context = run_context or RunContext(
1728
- run_id=run_id,
1729
- session_id=session_id,
1730
- user_id=user_id,
1731
- session_state=session_state,
1732
- dependencies=dependencies,
1733
- output_schema=output_schema,
1734
- )
1735
- # output_schema parameter takes priority, even if run_context was provided
1736
- run_context.output_schema = output_schema
1699
+ # Determine runtime dependencies
1700
+ dependencies = dependencies if dependencies is not None else self.dependencies
1737
1701
 
1738
- # Set up retry logic
1739
- num_attempts = self.retries + 1
1702
+ # Resolve output_schema parameter takes precedence, then fall back to self.output_schema
1703
+ if output_schema is None:
1704
+ output_schema = self.output_schema
1740
1705
 
1741
- for attempt in range(num_attempts):
1742
- if attempt > 0:
1743
- log_debug(f"Retrying Agent run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
1706
+ # Initialize run context
1707
+ run_context = run_context or RunContext(
1708
+ run_id=run_id,
1709
+ session_id=session_id,
1710
+ user_id=user_id,
1711
+ session_state=session_state,
1712
+ dependencies=dependencies,
1713
+ output_schema=output_schema,
1714
+ )
1715
+ # output_schema parameter takes priority, even if run_context was provided
1716
+ run_context.output_schema = output_schema
1744
1717
 
1745
- try:
1746
1718
  # Resolve dependencies
1747
1719
  if run_context.dependencies is not None:
1748
1720
  self._resolve_run_dependencies(run_context=run_context)
@@ -1849,23 +1821,64 @@ class Agent:
1849
1821
  )
1850
1822
  return response
1851
1823
  except (InputCheckError, OutputCheckError) as e:
1852
- log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
1853
- raise e
1854
- except KeyboardInterrupt:
1855
- run_response.content = "Operation cancelled by user"
1824
+ # Handle exceptions during streaming
1825
+ run_response.status = RunStatus.error
1826
+ # Add error event to list of events
1827
+ run_error = create_run_error_event(
1828
+ run_response,
1829
+ error=str(e),
1830
+ error_id=e.error_id,
1831
+ error_type=e.type,
1832
+ additional_data=e.additional_data,
1833
+ )
1834
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
1835
+
1836
+ # If the content is None, set it to the error message
1837
+ if run_response.content is None:
1838
+ run_response.content = str(e)
1839
+
1840
+ log_error(f"Validation failed: {str(e)} | Check trigger: {e.check_trigger}")
1841
+
1842
+ self._cleanup_and_store(
1843
+ run_response=run_response, session=agent_session, run_context=run_context, user_id=user_id
1844
+ )
1845
+
1846
+ if stream:
1847
+ return generator_wrapper(run_error) # type: ignore
1848
+ else:
1849
+ return run_response
1850
+ except RunCancelledException as e:
1851
+ # Handle run cancellation during streaming
1852
+ log_info(f"Run {run_response.run_id} was cancelled during streaming")
1853
+ run_response.content = str(e)
1856
1854
  run_response.status = RunStatus.cancelled
1855
+ cancelled_run_error = handle_event(
1856
+ create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
1857
+ run_response,
1858
+ events_to_skip=self.events_to_skip, # type: ignore
1859
+ store_events=self.store_events,
1860
+ )
1861
+
1862
+ # Cleanup and store the run response and session
1863
+ self._cleanup_and_store(
1864
+ run_response=run_response, session=agent_session, run_context=run_context, user_id=user_id
1865
+ )
1857
1866
 
1867
+ if stream:
1868
+ return generator_wrapper(cancelled_run_error) # type: ignore
1869
+ else:
1870
+ return run_response
1871
+ except KeyboardInterrupt:
1858
1872
  if stream:
1859
1873
  return generator_wrapper( # type: ignore
1860
- create_run_cancelled_event(
1861
- from_run_response=run_response,
1862
- reason="Operation cancelled by user",
1863
- )
1874
+ create_run_cancelled_event(run_response, "Operation cancelled by user") # type: ignore
1864
1875
  )
1865
1876
  else:
1866
- return run_response
1877
+ run_response.content = "Operation cancelled by user" # type: ignore
1878
+ run_response.status = RunStatus.cancelled # type: ignore
1879
+ return run_response # type: ignore
1880
+
1867
1881
  except Exception as e:
1868
- # Check if this is the last attempt
1869
1882
  if attempt < num_attempts - 1:
1870
1883
  # Calculate delay with exponential backoff if enabled
1871
1884
  if self.exponential_backoff:
@@ -1876,12 +1889,27 @@ class Agent:
1876
1889
  log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
1877
1890
  time.sleep(delay)
1878
1891
  continue
1892
+
1893
+ run_response.status = RunStatus.error
1894
+ # Add error event to list of events
1895
+ run_error = create_run_error_event(run_response, error=str(e))
1896
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
1897
+
1898
+ # If the content is None, set it to the error message
1899
+ if run_response.content is None:
1900
+ run_response.content = str(e)
1901
+
1902
+ log_error(f"Error in Agent run: {str(e)}")
1903
+
1904
+ # Cleanup and store the run response and session
1905
+ self._cleanup_and_store(
1906
+ run_response=run_response, session=agent_session, run_context=run_context, user_id=user_id
1907
+ )
1908
+
1909
+ if stream:
1910
+ return generator_wrapper(run_error) # type: ignore
1879
1911
  else:
1880
- # Final attempt failed - re-raise the exception
1881
- log_error(f"All {num_attempts} attempts failed. Final error: {str(e)}")
1882
- if stream:
1883
- return generator_wrapper(create_run_error_event(run_response, error=str(e))) # type: ignore
1884
- raise e
1912
+ return run_response
1885
1913
 
1886
1914
  # If we get here, all retries failed (shouldn't happen with current logic)
1887
1915
  raise Exception(f"Failed after {num_attempts} attempts.")
@@ -1922,246 +1950,321 @@ class Agent:
1922
1950
  """
1923
1951
  log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1924
1952
 
1925
- # 1. Read or create session. Reads from the database if provided.
1926
- agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
1953
+ cultural_knowledge_task = None
1954
+ memory_task = None
1927
1955
 
1928
- # 2. Update metadata and session state
1929
- self._update_metadata(session=agent_session)
1930
- # Initialize session state
1931
- run_context.session_state = self._initialize_session_state(
1932
- session_state=run_context.session_state if run_context.session_state is not None else {},
1933
- user_id=user_id,
1934
- session_id=session_id,
1935
- run_id=run_response.run_id,
1936
- )
1937
- # Update session state from DB
1938
- if run_context.session_state is not None:
1939
- run_context.session_state = self._load_session_state(
1940
- session=agent_session, session_state=run_context.session_state
1941
- )
1956
+ # Set up retry logic
1957
+ num_attempts = self.retries + 1
1942
1958
 
1943
- # 3. Resolve dependencies
1944
- if run_context.dependencies is not None:
1945
- await self._aresolve_run_dependencies(run_context=run_context)
1959
+ for attempt in range(num_attempts):
1960
+ if num_attempts > 1:
1961
+ log_debug(f"Retrying Agent run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
1946
1962
 
1947
- # 4. Execute pre-hooks
1948
- run_input = cast(RunInput, run_response.input)
1949
- self.model = cast(Model, self.model)
1950
- if self.pre_hooks is not None:
1951
- # Can modify the run input
1952
- pre_hook_iterator = self._aexecute_pre_hooks(
1953
- hooks=self.pre_hooks, # type: ignore
1954
- run_response=run_response,
1955
- run_context=run_context,
1956
- run_input=run_input,
1957
- session=agent_session,
1958
- user_id=user_id,
1959
- debug_mode=debug_mode,
1960
- background_tasks=background_tasks,
1961
- **kwargs,
1962
- )
1963
- # Consume the async iterator without yielding
1964
- async for _ in pre_hook_iterator:
1965
- pass
1963
+ try:
1964
+ # 1. Read or create session. Reads from the database if provided.
1965
+ agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
1966
+
1967
+ # 2. Update metadata and session state
1968
+ self._update_metadata(session=agent_session)
1969
+ # Initialize session state
1970
+ run_context.session_state = self._initialize_session_state(
1971
+ session_state=run_context.session_state if run_context.session_state is not None else {},
1972
+ user_id=user_id,
1973
+ session_id=session_id,
1974
+ run_id=run_response.run_id,
1975
+ )
1976
+ # Update session state from DB
1977
+ if run_context.session_state is not None:
1978
+ run_context.session_state = self._load_session_state(
1979
+ session=agent_session, session_state=run_context.session_state
1980
+ )
1966
1981
 
1967
- # 5. Determine tools for model
1968
- self.model = cast(Model, self.model)
1969
- processed_tools = await self.aget_tools(
1970
- run_response=run_response,
1971
- run_context=run_context,
1972
- session=agent_session,
1973
- user_id=user_id,
1974
- )
1982
+ # 3. Resolve dependencies
1983
+ if run_context.dependencies is not None:
1984
+ await self._aresolve_run_dependencies(run_context=run_context)
1975
1985
 
1976
- _tools = self._determine_tools_for_model(
1977
- model=self.model,
1978
- processed_tools=processed_tools,
1979
- run_response=run_response,
1980
- run_context=run_context,
1981
- session=agent_session,
1982
- )
1986
+ # 4. Execute pre-hooks
1987
+ run_input = cast(RunInput, run_response.input)
1988
+ self.model = cast(Model, self.model)
1989
+ if self.pre_hooks is not None:
1990
+ # Can modify the run input
1991
+ pre_hook_iterator = self._aexecute_pre_hooks(
1992
+ hooks=self.pre_hooks, # type: ignore
1993
+ run_response=run_response,
1994
+ run_context=run_context,
1995
+ run_input=run_input,
1996
+ session=agent_session,
1997
+ user_id=user_id,
1998
+ debug_mode=debug_mode,
1999
+ background_tasks=background_tasks,
2000
+ **kwargs,
2001
+ )
2002
+ # Consume the async iterator without yielding
2003
+ async for _ in pre_hook_iterator:
2004
+ pass
1983
2005
 
1984
- # 6. Prepare run messages
1985
- run_messages: RunMessages = await self._aget_run_messages(
1986
- run_response=run_response,
1987
- run_context=run_context,
1988
- input=run_input.input_content,
1989
- session=agent_session,
1990
- user_id=user_id,
1991
- audio=run_input.audios,
1992
- images=run_input.images,
1993
- videos=run_input.videos,
1994
- files=run_input.files,
1995
- add_history_to_context=add_history_to_context,
1996
- add_dependencies_to_context=add_dependencies_to_context,
1997
- add_session_state_to_context=add_session_state_to_context,
1998
- tools=_tools,
1999
- **kwargs,
2000
- )
2001
- if len(run_messages.messages) == 0:
2002
- log_error("No messages to be sent to the model.")
2006
+ # 5. Determine tools for model
2007
+ self.model = cast(Model, self.model)
2008
+ processed_tools = await self.aget_tools(
2009
+ run_response=run_response,
2010
+ run_context=run_context,
2011
+ session=agent_session,
2012
+ user_id=user_id,
2013
+ )
2003
2014
 
2004
- # 7. Start memory creation as a background task (runs concurrently with the main execution)
2005
- memory_task = None
2006
- if (
2007
- run_messages.user_message is not None
2008
- and self.memory_manager is not None
2009
- and self.enable_user_memories
2010
- and not self.enable_agentic_memory
2011
- ):
2012
- log_debug("Starting memory creation in background task.")
2013
- memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2015
+ _tools = self._determine_tools_for_model(
2016
+ model=self.model,
2017
+ processed_tools=processed_tools,
2018
+ run_response=run_response,
2019
+ run_context=run_context,
2020
+ session=agent_session,
2021
+ )
2014
2022
 
2015
- # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2016
- cultural_knowledge_task = None
2017
- if (
2018
- run_messages.user_message is not None
2019
- and self.culture_manager is not None
2020
- and self.update_cultural_knowledge
2021
- ):
2022
- log_debug("Starting cultural knowledge creation in background thread.")
2023
- cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2023
+ # 6. Prepare run messages
2024
+ run_messages: RunMessages = await self._aget_run_messages(
2025
+ run_response=run_response,
2026
+ run_context=run_context,
2027
+ input=run_input.input_content,
2028
+ session=agent_session,
2029
+ user_id=user_id,
2030
+ audio=run_input.audios,
2031
+ images=run_input.images,
2032
+ videos=run_input.videos,
2033
+ files=run_input.files,
2034
+ add_history_to_context=add_history_to_context,
2035
+ add_dependencies_to_context=add_dependencies_to_context,
2036
+ add_session_state_to_context=add_session_state_to_context,
2037
+ tools=_tools,
2038
+ **kwargs,
2039
+ )
2040
+ if len(run_messages.messages) == 0:
2041
+ log_error("No messages to be sent to the model.")
2024
2042
 
2025
- try:
2026
- # Check for cancellation before model call
2027
- raise_if_cancelled(run_response.run_id) # type: ignore
2043
+ # 7. Start memory creation as a background task (runs concurrently with the main execution)
2044
+ memory_task = None
2045
+ if (
2046
+ run_messages.user_message is not None
2047
+ and self.memory_manager is not None
2048
+ and self.enable_user_memories
2049
+ and not self.enable_agentic_memory
2050
+ ):
2051
+ log_debug("Starting memory creation in background task.")
2052
+ memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2028
2053
 
2029
- # 8. Reason about the task if reasoning is enabled
2030
- await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2054
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2055
+ if (
2056
+ run_messages.user_message is not None
2057
+ and self.culture_manager is not None
2058
+ and self.update_cultural_knowledge
2059
+ ):
2060
+ log_debug("Starting cultural knowledge creation in background thread.")
2061
+ cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2031
2062
 
2032
- # Check for cancellation before model call
2033
- raise_if_cancelled(run_response.run_id) # type: ignore
2063
+ # Check for cancellation before model call
2064
+ raise_if_cancelled(run_response.run_id) # type: ignore
2034
2065
 
2035
- # 9. Generate a response from the Model (includes running function calls)
2036
- model_response: ModelResponse = await self.model.aresponse(
2037
- messages=run_messages.messages,
2038
- tools=_tools,
2039
- tool_choice=self.tool_choice,
2040
- tool_call_limit=self.tool_call_limit,
2041
- response_format=response_format,
2042
- send_media_to_model=self.send_media_to_model,
2043
- run_response=run_response,
2044
- compression_manager=self.compression_manager if self.compress_tool_results else None,
2045
- )
2066
+ # 8. Reason about the task if reasoning is enabled
2067
+ await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2046
2068
 
2047
- # Check for cancellation after model call
2048
- raise_if_cancelled(run_response.run_id) # type: ignore
2069
+ # Check for cancellation before model call
2070
+ raise_if_cancelled(run_response.run_id) # type: ignore
2049
2071
 
2050
- # If an output model is provided, generate output using the output model
2051
- await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
2072
+ # 9. Generate a response from the Model (includes running function calls)
2073
+ model_response: ModelResponse = await self.model.aresponse(
2074
+ messages=run_messages.messages,
2075
+ tools=_tools,
2076
+ tool_choice=self.tool_choice,
2077
+ tool_call_limit=self.tool_call_limit,
2078
+ response_format=response_format,
2079
+ send_media_to_model=self.send_media_to_model,
2080
+ run_response=run_response,
2081
+ compression_manager=self.compression_manager if self.compress_tool_results else None,
2082
+ )
2052
2083
 
2053
- # If a parser model is provided, structure the response separately
2054
- await self._aparse_response_with_parser_model(
2055
- model_response=model_response, run_messages=run_messages, run_context=run_context
2056
- )
2084
+ # Check for cancellation after model call
2085
+ raise_if_cancelled(run_response.run_id) # type: ignore
2057
2086
 
2058
- # 10. Update the RunOutput with the model response
2059
- self._update_run_response(
2060
- model_response=model_response,
2061
- run_response=run_response,
2062
- run_messages=run_messages,
2063
- run_context=run_context,
2064
- )
2087
+ # If an output model is provided, generate output using the output model
2088
+ await self._agenerate_response_with_output_model(
2089
+ model_response=model_response, run_messages=run_messages
2090
+ )
2065
2091
 
2066
- # We should break out of the run function
2067
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
2068
- await await_for_open_threads(memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task)
2069
- return await self._ahandle_agent_run_paused(
2070
- run_response=run_response, session=agent_session, user_id=user_id
2092
+ # If a parser model is provided, structure the response separately
2093
+ await self._aparse_response_with_parser_model(
2094
+ model_response=model_response, run_messages=run_messages, run_context=run_context
2071
2095
  )
2072
2096
 
2073
- # 11. Convert the response to the structured format if needed
2074
- self._convert_response_to_structured_format(run_response, run_context=run_context)
2097
+ # 10. Update the RunOutput with the model response
2098
+ self._update_run_response(
2099
+ model_response=model_response,
2100
+ run_response=run_response,
2101
+ run_messages=run_messages,
2102
+ run_context=run_context,
2103
+ )
2075
2104
 
2076
- # 12. Store media if enabled
2077
- if self.store_media:
2078
- store_media_util(run_response, model_response)
2105
+ # We should break out of the run function
2106
+ if any(tool_call.is_paused for tool_call in run_response.tools or []):
2107
+ await await_for_open_threads(
2108
+ memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task
2109
+ )
2110
+ return await self._ahandle_agent_run_paused(
2111
+ run_response=run_response, session=agent_session, user_id=user_id
2112
+ )
2079
2113
 
2080
- # 13. Execute post-hooks (after output is generated but before response is returned)
2081
- if self.post_hooks is not None:
2082
- async for _ in self._aexecute_post_hooks(
2083
- hooks=self.post_hooks, # type: ignore
2084
- run_output=run_response,
2085
- run_context=run_context,
2086
- session=agent_session,
2087
- user_id=user_id,
2088
- debug_mode=debug_mode,
2089
- background_tasks=background_tasks,
2090
- **kwargs,
2091
- ):
2092
- pass
2114
+ # 11. Convert the response to the structured format if needed
2115
+ self._convert_response_to_structured_format(run_response, run_context=run_context)
2093
2116
 
2094
- # Check for cancellation
2095
- raise_if_cancelled(run_response.run_id) # type: ignore
2117
+ # 12. Store media if enabled
2118
+ if self.store_media:
2119
+ store_media_util(run_response, model_response)
2096
2120
 
2097
- # 14. Wait for background memory creation
2098
- await await_for_open_threads(memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task)
2121
+ # 13. Execute post-hooks (after output is generated but before response is returned)
2122
+ if self.post_hooks is not None:
2123
+ async for _ in self._aexecute_post_hooks(
2124
+ hooks=self.post_hooks, # type: ignore
2125
+ run_output=run_response,
2126
+ run_context=run_context,
2127
+ session=agent_session,
2128
+ user_id=user_id,
2129
+ debug_mode=debug_mode,
2130
+ background_tasks=background_tasks,
2131
+ **kwargs,
2132
+ ):
2133
+ pass
2099
2134
 
2100
- # 15. Create session summary
2101
- if self.session_summary_manager is not None and self.enable_session_summaries:
2102
- # Upsert the RunOutput to Agent Session before creating the session summary
2103
- agent_session.upsert_run(run=run_response)
2104
- try:
2105
- await self.session_summary_manager.acreate_session_summary(session=agent_session)
2106
- except Exception as e:
2107
- log_warning(f"Error in session summary creation: {str(e)}")
2135
+ # Check for cancellation
2136
+ raise_if_cancelled(run_response.run_id) # type: ignore
2108
2137
 
2109
- run_response.status = RunStatus.completed
2138
+ # 14. Wait for background memory creation
2139
+ await await_for_open_threads(memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task)
2110
2140
 
2111
- # 16. Cleanup and store the run response and session
2112
- await self._acleanup_and_store(
2113
- run_response=run_response,
2114
- session=agent_session,
2115
- run_context=run_context,
2116
- user_id=user_id,
2117
- )
2141
+ # 15. Create session summary
2142
+ if self.session_summary_manager is not None and self.enable_session_summaries:
2143
+ # Upsert the RunOutput to Agent Session before creating the session summary
2144
+ agent_session.upsert_run(run=run_response)
2145
+ try:
2146
+ await self.session_summary_manager.acreate_session_summary(session=agent_session)
2147
+ except Exception as e:
2148
+ log_warning(f"Error in session summary creation: {str(e)}")
2118
2149
 
2119
- # Log Agent Telemetry
2120
- await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2150
+ run_response.status = RunStatus.completed
2121
2151
 
2122
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2152
+ # 16. Cleanup and store the run response and session
2153
+ await self._acleanup_and_store(
2154
+ run_response=run_response,
2155
+ session=agent_session,
2156
+ run_context=run_context,
2157
+ user_id=user_id,
2158
+ )
2123
2159
 
2124
- return run_response
2160
+ # Log Agent Telemetry
2161
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2125
2162
 
2126
- except RunCancelledException as e:
2127
- # Handle run cancellation
2128
- log_info(f"Run {run_response.run_id} was cancelled")
2129
- run_response.content = str(e)
2130
- run_response.status = RunStatus.cancelled
2163
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2131
2164
 
2132
- # Cleanup and store the run response and session
2133
- await self._acleanup_and_store(
2134
- run_response=run_response,
2135
- session=agent_session,
2136
- run_context=run_context,
2137
- user_id=user_id,
2138
- )
2165
+ return run_response
2139
2166
 
2140
- return run_response
2167
+ except RunCancelledException as e:
2168
+ # Handle run cancellation
2169
+ log_info(f"Run {run_response.run_id} was cancelled")
2170
+ run_response.content = str(e)
2171
+ run_response.status = RunStatus.cancelled
2141
2172
 
2142
- finally:
2143
- # Always disconnect connectable tools
2144
- self._disconnect_connectable_tools()
2145
- # Always disconnect MCP tools
2146
- await self._disconnect_mcp_tools()
2173
+ # Cleanup and store the run response and session
2174
+ await self._acleanup_and_store(
2175
+ run_response=run_response,
2176
+ session=agent_session,
2177
+ run_context=run_context,
2178
+ user_id=user_id,
2179
+ )
2147
2180
 
2148
- # Cancel the memory task if it's still running
2149
- if memory_task is not None and not memory_task.done():
2150
- memory_task.cancel()
2151
- try:
2152
- await memory_task
2153
- except CancelledError:
2154
- pass
2155
- # Cancel the cultural knowledge task if it's still running
2156
- if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2157
- cultural_knowledge_task.cancel()
2158
- try:
2159
- await cultural_knowledge_task
2160
- except CancelledError:
2161
- pass
2181
+ return run_response
2182
+ except (InputCheckError, OutputCheckError) as e:
2183
+ # Handle exceptions during streaming
2184
+ run_response.status = RunStatus.error
2185
+ # Add error event to list of events
2186
+ run_error = create_run_error_event(
2187
+ run_response,
2188
+ error=str(e),
2189
+ error_id=e.error_id,
2190
+ error_type=e.type,
2191
+ additional_data=e.additional_data,
2192
+ )
2193
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2162
2194
 
2163
- # Always clean up the run tracking
2164
- cleanup_run(run_response.run_id) # type: ignore
2195
+ # If the content is None, set it to the error message
2196
+ if run_response.content is None:
2197
+ run_response.content = str(e)
2198
+
2199
+ log_error(f"Validation failed: {str(e)} | Check trigger: {e.check_trigger}")
2200
+
2201
+ await self._acleanup_and_store(
2202
+ run_response=run_response,
2203
+ session=agent_session,
2204
+ run_context=run_context,
2205
+ user_id=user_id,
2206
+ )
2207
+
2208
+ return run_response
2209
+ except Exception as e:
2210
+ # Check if this is the last attempt
2211
+ if attempt < num_attempts - 1:
2212
+ # Calculate delay with exponential backoff if enabled
2213
+ if self.exponential_backoff:
2214
+ delay = self.delay_between_retries * (2**attempt)
2215
+ else:
2216
+ delay = self.delay_between_retries
2217
+
2218
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2219
+ time.sleep(delay)
2220
+ continue
2221
+
2222
+ run_response.status = RunStatus.error
2223
+ # Add error event to list of events
2224
+ run_error = create_run_error_event(run_response, error=str(e))
2225
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2226
+
2227
+ # If the content is None, set it to the error message
2228
+ if run_response.content is None:
2229
+ run_response.content = str(e)
2230
+
2231
+ log_error(f"Error in Agent run: {str(e)}")
2232
+
2233
+ # Cleanup and store the run response and session
2234
+ await self._acleanup_and_store(
2235
+ run_response=run_response,
2236
+ session=agent_session,
2237
+ run_context=run_context,
2238
+ user_id=user_id,
2239
+ )
2240
+
2241
+ return run_response
2242
+
2243
+ finally:
2244
+ # Always disconnect connectable tools
2245
+ self._disconnect_connectable_tools()
2246
+ # Always disconnect MCP tools
2247
+ await self._disconnect_mcp_tools()
2248
+
2249
+ # Cancel the memory task if it's still running
2250
+ if memory_task is not None and not memory_task.done():
2251
+ memory_task.cancel()
2252
+ try:
2253
+ await memory_task
2254
+ except CancelledError:
2255
+ pass
2256
+ # Cancel the cultural knowledge task if it's still running
2257
+ if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2258
+ cultural_knowledge_task.cancel()
2259
+ try:
2260
+ await cultural_knowledge_task
2261
+ except CancelledError:
2262
+ pass
2263
+
2264
+ # Always clean up the run tracking
2265
+ cleanup_run(run_response.run_id) # type: ignore
2266
+
2267
+ return run_response
2165
2268
 
2166
2269
  async def _arun_stream(
2167
2270
  self,
@@ -2198,344 +2301,422 @@ class Agent:
2198
2301
  """
2199
2302
  log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
2200
2303
 
2201
- # Start the Run by yielding a RunStarted event
2202
- if stream_events:
2203
- yield handle_event( # type: ignore
2204
- create_run_started_event(run_response),
2205
- run_response,
2206
- events_to_skip=self.events_to_skip, # type: ignore
2207
- store_events=self.store_events,
2208
- )
2304
+ memory_task = None
2305
+ cultural_knowledge_task = None
2209
2306
 
2210
2307
  # 1. Read or create session. Reads from the database if provided.
2211
2308
  agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2212
2309
 
2213
- # 2. Update metadata and session state
2214
- self._update_metadata(session=agent_session)
2215
- # Initialize session state
2216
- run_context.session_state = self._initialize_session_state(
2217
- session_state=run_context.session_state if run_context.session_state is not None else {},
2218
- user_id=user_id,
2219
- session_id=session_id,
2220
- run_id=run_response.run_id,
2221
- )
2222
- # Update session state from DB
2223
- if run_context.session_state is not None:
2224
- run_context.session_state = self._load_session_state(
2225
- session=agent_session, session_state=run_context.session_state
2226
- )
2227
-
2228
- # 3. Resolve dependencies
2229
- if run_context.dependencies is not None:
2230
- await self._aresolve_run_dependencies(run_context=run_context)
2231
-
2232
- # 4. Execute pre-hooks
2233
- run_input = cast(RunInput, run_response.input)
2234
- self.model = cast(Model, self.model)
2235
- if self.pre_hooks is not None:
2236
- # Can modify the run input
2237
- pre_hook_iterator = self._aexecute_pre_hooks(
2238
- hooks=self.pre_hooks, # type: ignore
2239
- run_response=run_response,
2240
- run_context=run_context,
2241
- run_input=run_input,
2242
- session=agent_session,
2243
- user_id=user_id,
2244
- debug_mode=debug_mode,
2245
- stream_events=stream_events,
2246
- background_tasks=background_tasks,
2247
- **kwargs,
2248
- )
2249
- async for event in pre_hook_iterator:
2250
- yield event
2251
-
2252
- # 5. Determine tools for model
2253
- self.model = cast(Model, self.model)
2254
- processed_tools = await self.aget_tools(
2255
- run_response=run_response,
2256
- run_context=run_context,
2257
- session=agent_session,
2258
- user_id=user_id,
2259
- )
2310
+ # Set up retry logic
2311
+ num_attempts = self.retries + 1
2260
2312
 
2261
- _tools = self._determine_tools_for_model(
2262
- model=self.model,
2263
- processed_tools=processed_tools,
2264
- run_response=run_response,
2265
- run_context=run_context,
2266
- session=agent_session,
2267
- )
2313
+ for attempt in range(num_attempts):
2314
+ if num_attempts > 1:
2315
+ log_debug(f"Retrying Agent run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2268
2316
 
2269
- # 6. Prepare run messages
2270
- run_messages: RunMessages = await self._aget_run_messages(
2271
- run_response=run_response,
2272
- run_context=run_context,
2273
- input=run_input.input_content,
2274
- session=agent_session,
2275
- user_id=user_id,
2276
- audio=run_input.audios,
2277
- images=run_input.images,
2278
- videos=run_input.videos,
2279
- files=run_input.files,
2280
- add_history_to_context=add_history_to_context,
2281
- add_dependencies_to_context=add_dependencies_to_context,
2282
- add_session_state_to_context=add_session_state_to_context,
2283
- tools=_tools,
2284
- **kwargs,
2285
- )
2286
- if len(run_messages.messages) == 0:
2287
- log_error("No messages to be sent to the model.")
2317
+ try:
2318
+ # Start the Run by yielding a RunStarted event
2319
+ if stream_events:
2320
+ yield handle_event( # type: ignore
2321
+ create_run_started_event(run_response),
2322
+ run_response,
2323
+ events_to_skip=self.events_to_skip, # type: ignore
2324
+ store_events=self.store_events,
2325
+ )
2288
2326
 
2289
- # 7. Start memory creation as a background task (runs concurrently with the main execution)
2290
- memory_task = None
2291
- if (
2292
- run_messages.user_message is not None
2293
- and self.memory_manager is not None
2294
- and self.enable_user_memories
2295
- and not self.enable_agentic_memory
2296
- ):
2297
- log_debug("Starting memory creation in background task.")
2298
- memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2327
+ # 2. Update metadata and session state
2328
+ self._update_metadata(session=agent_session)
2329
+ # Initialize session state
2330
+ run_context.session_state = self._initialize_session_state(
2331
+ session_state=run_context.session_state if run_context.session_state is not None else {},
2332
+ user_id=user_id,
2333
+ session_id=session_id,
2334
+ run_id=run_response.run_id,
2335
+ )
2336
+ # Update session state from DB
2337
+ if run_context.session_state is not None:
2338
+ run_context.session_state = self._load_session_state(
2339
+ session=agent_session, session_state=run_context.session_state
2340
+ )
2299
2341
 
2300
- # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2301
- cultural_knowledge_task = None
2302
- if (
2303
- run_messages.user_message is not None
2304
- and self.culture_manager is not None
2305
- and self.update_cultural_knowledge
2306
- ):
2307
- log_debug("Starting cultural knowledge creation in background task.")
2308
- cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2342
+ # 3. Resolve dependencies
2343
+ if run_context.dependencies is not None:
2344
+ await self._aresolve_run_dependencies(run_context=run_context)
2309
2345
 
2310
- try:
2311
- # 8. Reason about the task if reasoning is enabled
2312
- async for item in self._ahandle_reasoning_stream(
2313
- run_response=run_response,
2314
- run_messages=run_messages,
2315
- stream_events=stream_events,
2316
- ):
2317
- raise_if_cancelled(run_response.run_id) # type: ignore
2318
- yield item
2346
+ # 4. Execute pre-hooks
2347
+ run_input = cast(RunInput, run_response.input)
2348
+ self.model = cast(Model, self.model)
2349
+ if self.pre_hooks is not None:
2350
+ pre_hook_iterator = self._aexecute_pre_hooks(
2351
+ hooks=self.pre_hooks, # type: ignore
2352
+ run_response=run_response,
2353
+ run_context=run_context,
2354
+ run_input=run_input,
2355
+ session=agent_session,
2356
+ user_id=user_id,
2357
+ debug_mode=debug_mode,
2358
+ stream_events=stream_events,
2359
+ background_tasks=background_tasks,
2360
+ **kwargs,
2361
+ )
2362
+ async for event in pre_hook_iterator:
2363
+ yield event
2319
2364
 
2320
- raise_if_cancelled(run_response.run_id) # type: ignore
2365
+ # 5. Determine tools for model
2366
+ self.model = cast(Model, self.model)
2367
+ processed_tools = await self.aget_tools(
2368
+ run_response=run_response,
2369
+ run_context=run_context,
2370
+ session=agent_session,
2371
+ user_id=user_id,
2372
+ )
2321
2373
 
2322
- # 9. Generate a response from the Model
2323
- if self.output_model is None:
2324
- async for event in self._ahandle_model_response_stream(
2374
+ _tools = self._determine_tools_for_model(
2375
+ model=self.model,
2376
+ processed_tools=processed_tools,
2377
+ run_response=run_response,
2378
+ run_context=run_context,
2325
2379
  session=agent_session,
2380
+ )
2381
+
2382
+ # 6. Prepare run messages
2383
+ run_messages: RunMessages = await self._aget_run_messages(
2326
2384
  run_response=run_response,
2327
- run_messages=run_messages,
2328
- tools=_tools,
2329
- response_format=response_format,
2330
- stream_events=stream_events,
2331
- session_state=run_context.session_state,
2332
2385
  run_context=run_context,
2386
+ input=run_input.input_content,
2387
+ session=agent_session,
2388
+ user_id=user_id,
2389
+ audio=run_input.audios,
2390
+ images=run_input.images,
2391
+ videos=run_input.videos,
2392
+ files=run_input.files,
2393
+ add_history_to_context=add_history_to_context,
2394
+ add_dependencies_to_context=add_dependencies_to_context,
2395
+ add_session_state_to_context=add_session_state_to_context,
2396
+ tools=_tools,
2397
+ **kwargs,
2398
+ )
2399
+ if len(run_messages.messages) == 0:
2400
+ log_error("No messages to be sent to the model.")
2401
+
2402
+ # 7. Start memory creation as a background task (runs concurrently with the main execution)
2403
+ memory_task = None
2404
+ if (
2405
+ run_messages.user_message is not None
2406
+ and self.memory_manager is not None
2407
+ and self.enable_user_memories
2408
+ and not self.enable_agentic_memory
2333
2409
  ):
2334
- raise_if_cancelled(run_response.run_id) # type: ignore
2335
- yield event
2336
- else:
2337
- from agno.run.agent import (
2338
- IntermediateRunContentEvent,
2339
- RunContentEvent,
2340
- ) # type: ignore
2410
+ log_debug("Starting memory creation in background task.")
2411
+ memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2341
2412
 
2342
- async for event in self._ahandle_model_response_stream(
2343
- session=agent_session,
2413
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2414
+ if (
2415
+ run_messages.user_message is not None
2416
+ and self.culture_manager is not None
2417
+ and self.update_cultural_knowledge
2418
+ ):
2419
+ log_debug("Starting cultural knowledge creation in background task.")
2420
+ cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2421
+
2422
+ # 8. Reason about the task if reasoning is enabled
2423
+ async for item in self._ahandle_reasoning_stream(
2344
2424
  run_response=run_response,
2345
2425
  run_messages=run_messages,
2346
- tools=_tools,
2347
- response_format=response_format,
2348
2426
  stream_events=stream_events,
2349
- session_state=run_context.session_state,
2350
- run_context=run_context,
2351
2427
  ):
2352
2428
  raise_if_cancelled(run_response.run_id) # type: ignore
2353
- if isinstance(event, RunContentEvent):
2354
- if stream_events:
2355
- yield IntermediateRunContentEvent(
2356
- content=event.content,
2357
- content_type=event.content_type,
2358
- )
2359
- else:
2429
+ yield item
2430
+
2431
+ raise_if_cancelled(run_response.run_id) # type: ignore
2432
+
2433
+ # 9. Generate a response from the Model
2434
+ if self.output_model is None:
2435
+ async for event in self._ahandle_model_response_stream(
2436
+ session=agent_session,
2437
+ run_response=run_response,
2438
+ run_messages=run_messages,
2439
+ tools=_tools,
2440
+ response_format=response_format,
2441
+ stream_events=stream_events,
2442
+ session_state=run_context.session_state,
2443
+ run_context=run_context,
2444
+ ):
2445
+ raise_if_cancelled(run_response.run_id) # type: ignore
2360
2446
  yield event
2447
+ else:
2448
+ from agno.run.agent import (
2449
+ IntermediateRunContentEvent,
2450
+ RunContentEvent,
2451
+ ) # type: ignore
2361
2452
 
2362
- # If an output model is provided, generate output using the output model
2363
- async for event in self._agenerate_response_with_output_model_stream(
2453
+ async for event in self._ahandle_model_response_stream(
2454
+ session=agent_session,
2455
+ run_response=run_response,
2456
+ run_messages=run_messages,
2457
+ tools=_tools,
2458
+ response_format=response_format,
2459
+ stream_events=stream_events,
2460
+ session_state=run_context.session_state,
2461
+ run_context=run_context,
2462
+ ):
2463
+ raise_if_cancelled(run_response.run_id) # type: ignore
2464
+ if isinstance(event, RunContentEvent):
2465
+ if stream_events:
2466
+ yield IntermediateRunContentEvent(
2467
+ content=event.content,
2468
+ content_type=event.content_type,
2469
+ )
2470
+ else:
2471
+ yield event
2472
+
2473
+ # If an output model is provided, generate output using the output model
2474
+ async for event in self._agenerate_response_with_output_model_stream(
2475
+ session=agent_session,
2476
+ run_response=run_response,
2477
+ run_messages=run_messages,
2478
+ stream_events=stream_events,
2479
+ ):
2480
+ raise_if_cancelled(run_response.run_id) # type: ignore
2481
+ yield event
2482
+
2483
+ # Check for cancellation after model processing
2484
+ raise_if_cancelled(run_response.run_id) # type: ignore
2485
+
2486
+ # 10. Parse response with parser model if provided
2487
+ async for event in self._aparse_response_with_parser_model_stream(
2364
2488
  session=agent_session,
2365
2489
  run_response=run_response,
2366
- run_messages=run_messages,
2367
2490
  stream_events=stream_events,
2491
+ run_context=run_context,
2368
2492
  ):
2369
- raise_if_cancelled(run_response.run_id) # type: ignore
2370
2493
  yield event
2371
2494
 
2372
- # Check for cancellation after model processing
2373
- raise_if_cancelled(run_response.run_id) # type: ignore
2495
+ if stream_events:
2496
+ yield handle_event( # type: ignore
2497
+ create_run_content_completed_event(from_run_response=run_response),
2498
+ run_response,
2499
+ events_to_skip=self.events_to_skip, # type: ignore
2500
+ store_events=self.store_events,
2501
+ )
2374
2502
 
2375
- # 10. Parse response with parser model if provided
2376
- async for event in self._aparse_response_with_parser_model_stream(
2377
- session=agent_session, run_response=run_response, stream_events=stream_events, run_context=run_context
2378
- ):
2379
- yield event
2503
+ # Break out of the run function if a tool call is paused
2504
+ if any(tool_call.is_paused for tool_call in run_response.tools or []):
2505
+ async for item in await_for_thread_tasks_stream(
2506
+ memory_task=memory_task,
2507
+ cultural_knowledge_task=cultural_knowledge_task,
2508
+ stream_events=stream_events,
2509
+ run_response=run_response,
2510
+ ):
2511
+ yield item
2380
2512
 
2381
- if stream_events:
2382
- yield handle_event( # type: ignore
2383
- create_run_content_completed_event(from_run_response=run_response),
2384
- run_response,
2385
- events_to_skip=self.events_to_skip, # type: ignore
2386
- store_events=self.store_events,
2387
- )
2513
+ async for item in self._ahandle_agent_run_paused_stream(
2514
+ run_response=run_response, session=agent_session, user_id=user_id
2515
+ ):
2516
+ yield item
2517
+ return
2518
+
2519
+ # Execute post-hooks (after output is generated but before response is returned)
2520
+ if self.post_hooks is not None:
2521
+ async for event in self._aexecute_post_hooks(
2522
+ hooks=self.post_hooks, # type: ignore
2523
+ run_output=run_response,
2524
+ run_context=run_context,
2525
+ session=agent_session,
2526
+ user_id=user_id,
2527
+ debug_mode=debug_mode,
2528
+ stream_events=stream_events,
2529
+ background_tasks=background_tasks,
2530
+ **kwargs,
2531
+ ):
2532
+ yield event
2388
2533
 
2389
- # Break out of the run function if a tool call is paused
2390
- if any(tool_call.is_paused for tool_call in run_response.tools or []):
2534
+ # 11. Wait for background memory creation
2391
2535
  async for item in await_for_thread_tasks_stream(
2392
2536
  memory_task=memory_task,
2393
2537
  cultural_knowledge_task=cultural_knowledge_task,
2394
2538
  stream_events=stream_events,
2395
2539
  run_response=run_response,
2540
+ events_to_skip=self.events_to_skip,
2541
+ store_events=self.store_events,
2396
2542
  ):
2397
2543
  yield item
2398
2544
 
2399
- async for item in self._ahandle_agent_run_paused_stream(
2400
- run_response=run_response, session=agent_session, user_id=user_id
2401
- ):
2402
- yield item
2403
- return
2545
+ # 12. Create session summary
2546
+ if self.session_summary_manager is not None and self.enable_session_summaries:
2547
+ # Upsert the RunOutput to Agent Session before creating the session summary
2548
+ agent_session.upsert_run(run=run_response)
2404
2549
 
2405
- # Execute post-hooks (after output is generated but before response is returned)
2406
- if self.post_hooks is not None:
2407
- async for event in self._aexecute_post_hooks(
2408
- hooks=self.post_hooks, # type: ignore
2409
- run_output=run_response,
2410
- run_context=run_context,
2550
+ if stream_events:
2551
+ yield handle_event( # type: ignore
2552
+ create_session_summary_started_event(from_run_response=run_response),
2553
+ run_response,
2554
+ events_to_skip=self.events_to_skip, # type: ignore
2555
+ store_events=self.store_events,
2556
+ )
2557
+ try:
2558
+ await self.session_summary_manager.acreate_session_summary(session=agent_session)
2559
+ except Exception as e:
2560
+ log_warning(f"Error in session summary creation: {str(e)}")
2561
+ if stream_events:
2562
+ yield handle_event( # type: ignore
2563
+ create_session_summary_completed_event(
2564
+ from_run_response=run_response, session_summary=agent_session.summary
2565
+ ),
2566
+ run_response,
2567
+ events_to_skip=self.events_to_skip, # type: ignore
2568
+ store_events=self.store_events,
2569
+ )
2570
+
2571
+ # Update run_response.session_state before creating RunCompletedEvent
2572
+ # This ensures the event has the final state after all tool modifications
2573
+ if agent_session.session_data is not None and "session_state" in agent_session.session_data:
2574
+ run_response.session_state = agent_session.session_data["session_state"]
2575
+
2576
+ # Create the run completed event
2577
+ completed_event = handle_event(
2578
+ create_run_completed_event(from_run_response=run_response),
2579
+ run_response,
2580
+ events_to_skip=self.events_to_skip, # type: ignore
2581
+ store_events=self.store_events,
2582
+ )
2583
+
2584
+ # Set the run status to completed
2585
+ run_response.status = RunStatus.completed
2586
+
2587
+ # 13. Cleanup and store the run response and session
2588
+ await self._acleanup_and_store(
2589
+ run_response=run_response,
2411
2590
  session=agent_session,
2591
+ run_context=run_context,
2412
2592
  user_id=user_id,
2413
- debug_mode=debug_mode,
2414
- stream_events=stream_events,
2415
- background_tasks=background_tasks,
2416
- **kwargs,
2417
- ):
2418
- yield event
2593
+ )
2419
2594
 
2420
- # 11. Wait for background memory creation
2421
- async for item in await_for_thread_tasks_stream(
2422
- memory_task=memory_task,
2423
- cultural_knowledge_task=cultural_knowledge_task,
2424
- stream_events=stream_events,
2425
- run_response=run_response,
2426
- events_to_skip=self.events_to_skip,
2427
- store_events=self.store_events,
2428
- ):
2429
- yield item
2595
+ if stream_events:
2596
+ yield completed_event # type: ignore
2430
2597
 
2431
- # 12. Create session summary
2432
- if self.session_summary_manager is not None and self.enable_session_summaries:
2433
- # Upsert the RunOutput to Agent Session before creating the session summary
2434
- agent_session.upsert_run(run=run_response)
2598
+ if yield_run_output:
2599
+ yield run_response
2435
2600
 
2436
- if stream_events:
2437
- yield handle_event( # type: ignore
2438
- create_session_summary_started_event(from_run_response=run_response),
2439
- run_response,
2440
- events_to_skip=self.events_to_skip, # type: ignore
2441
- store_events=self.store_events,
2442
- )
2443
- try:
2444
- await self.session_summary_manager.acreate_session_summary(session=agent_session)
2445
- except Exception as e:
2446
- log_warning(f"Error in session summary creation: {str(e)}")
2447
- if stream_events:
2448
- yield handle_event( # type: ignore
2449
- create_session_summary_completed_event(
2450
- from_run_response=run_response, session_summary=agent_session.summary
2451
- ),
2452
- run_response,
2453
- events_to_skip=self.events_to_skip, # type: ignore
2454
- store_events=self.store_events,
2455
- )
2601
+ # Log Agent Telemetry
2602
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2456
2603
 
2457
- # Update run_response.session_state before creating RunCompletedEvent
2458
- # This ensures the event has the final state after all tool modifications
2459
- if agent_session.session_data is not None and "session_state" in agent_session.session_data:
2460
- run_response.session_state = agent_session.session_data["session_state"]
2604
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2461
2605
 
2462
- # Create the run completed event
2463
- completed_event = handle_event(
2464
- create_run_completed_event(from_run_response=run_response),
2465
- run_response,
2466
- events_to_skip=self.events_to_skip, # type: ignore
2467
- store_events=self.store_events,
2468
- )
2606
+ except RunCancelledException as e:
2607
+ # Handle run cancellation during async streaming
2608
+ log_info(f"Run {run_response.run_id} was cancelled during async streaming")
2609
+ run_response.status = RunStatus.cancelled
2610
+ # Don't overwrite content - preserve any partial content that was streamed
2611
+ # Only set content if it's empty
2612
+ if not run_response.content:
2613
+ run_response.content = str(e)
2469
2614
 
2470
- # Set the run status to completed
2471
- run_response.status = RunStatus.completed
2615
+ # Yield the cancellation event
2616
+ yield handle_event( # type: ignore
2617
+ create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2618
+ run_response,
2619
+ events_to_skip=self.events_to_skip, # type: ignore
2620
+ store_events=self.store_events,
2621
+ )
2472
2622
 
2473
- # 13. Cleanup and store the run response and session
2474
- await self._acleanup_and_store(
2475
- run_response=run_response,
2476
- session=agent_session,
2477
- run_context=run_context,
2478
- user_id=user_id,
2479
- )
2623
+ # Cleanup and store the run response and session
2624
+ await self._acleanup_and_store(
2625
+ run_response=run_response,
2626
+ session=agent_session,
2627
+ run_context=run_context,
2628
+ user_id=user_id,
2629
+ )
2480
2630
 
2481
- if stream_events:
2482
- yield completed_event # type: ignore
2631
+ except (InputCheckError, OutputCheckError) as e:
2632
+ # Handle exceptions during async streaming
2633
+ run_response.status = RunStatus.error
2634
+ # Add error event to list of events
2635
+ run_error = create_run_error_event(
2636
+ run_response,
2637
+ error=str(e),
2638
+ error_id=e.error_id,
2639
+ error_type=e.type,
2640
+ additional_data=e.additional_data,
2641
+ )
2642
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2483
2643
 
2484
- if yield_run_output:
2485
- yield run_response
2644
+ # If the content is None, set it to the error message
2645
+ if run_response.content is None:
2646
+ run_response.content = str(e)
2486
2647
 
2487
- # Log Agent Telemetry
2488
- await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2648
+ log_error(f"Validation failed: {str(e)} | Check trigger: {e.check_trigger}")
2489
2649
 
2490
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2650
+ # Cleanup and store the run response and session
2651
+ await self._acleanup_and_store(
2652
+ run_response=run_response,
2653
+ session=agent_session,
2654
+ run_context=run_context,
2655
+ user_id=user_id,
2656
+ )
2491
2657
 
2492
- except RunCancelledException as e:
2493
- # Handle run cancellation during async streaming
2494
- log_info(f"Run {run_response.run_id} was cancelled during async streaming")
2495
- run_response.status = RunStatus.cancelled
2496
- # Don't overwrite content - preserve any partial content that was streamed
2497
- # Only set content if it's empty
2498
- if not run_response.content:
2499
- run_response.content = str(e)
2658
+ # Yield the error event
2659
+ yield run_error
2660
+ break
2500
2661
 
2501
- # Yield the cancellation event
2502
- yield handle_event( # type: ignore
2503
- create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2504
- run_response,
2505
- events_to_skip=self.events_to_skip, # type: ignore
2506
- store_events=self.store_events,
2507
- )
2662
+ except Exception as e:
2663
+ # Check if this is the last attempt
2664
+ if attempt < num_attempts - 1:
2665
+ # Calculate delay with exponential backoff if enabled
2666
+ if self.exponential_backoff:
2667
+ delay = self.delay_between_retries * (2**attempt)
2668
+ else:
2669
+ delay = self.delay_between_retries
2508
2670
 
2509
- # Cleanup and store the run response and session
2510
- await self._acleanup_and_store(
2511
- run_response=run_response,
2512
- session=agent_session,
2513
- run_context=run_context,
2514
- user_id=user_id,
2515
- )
2516
- finally:
2517
- # Always disconnect connectable tools
2518
- self._disconnect_connectable_tools()
2519
- # Always disconnect MCP tools
2520
- await self._disconnect_mcp_tools()
2671
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2672
+ time.sleep(delay)
2673
+ continue
2521
2674
 
2522
- # Cancel the memory task if it's still running
2523
- if memory_task is not None and not memory_task.done():
2524
- memory_task.cancel()
2525
- try:
2526
- await memory_task
2527
- except CancelledError:
2528
- pass
2675
+ # Handle exceptions during async streaming
2676
+ run_response.status = RunStatus.error
2677
+ # Add error event to list of events
2678
+ run_error = create_run_error_event(run_response, error=str(e))
2679
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2529
2680
 
2530
- if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2531
- cultural_knowledge_task.cancel()
2532
- try:
2533
- await cultural_knowledge_task
2534
- except CancelledError:
2535
- pass
2681
+ # If the content is None, set it to the error message
2682
+ if run_response.content is None:
2683
+ run_response.content = str(e)
2536
2684
 
2537
- # Always clean up the run tracking
2538
- cleanup_run(run_response.run_id) # type: ignore
2685
+ log_error(f"Error in Agent run: {str(e)}")
2686
+
2687
+ # Cleanup and store the run response and session
2688
+ await self._acleanup_and_store(
2689
+ run_response=run_response,
2690
+ session=agent_session,
2691
+ run_context=run_context,
2692
+ user_id=user_id,
2693
+ )
2694
+
2695
+ # Yield the error event
2696
+ yield run_error
2697
+ finally:
2698
+ # Always disconnect connectable tools
2699
+ self._disconnect_connectable_tools()
2700
+ # Always disconnect MCP tools
2701
+ await self._disconnect_mcp_tools()
2702
+
2703
+ # Cancel the memory task if it's still running
2704
+ if memory_task is not None and not memory_task.done():
2705
+ memory_task.cancel()
2706
+ try:
2707
+ await memory_task
2708
+ except CancelledError:
2709
+ pass
2710
+
2711
+ if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2712
+ cultural_knowledge_task.cancel()
2713
+ try:
2714
+ await cultural_knowledge_task
2715
+ except CancelledError:
2716
+ pass
2717
+
2718
+ # Always clean up the run tracking
2719
+ cleanup_run(run_response.run_id) # type: ignore
2539
2720
 
2540
2721
  @overload
2541
2722
  async def arun(
@@ -2560,7 +2741,7 @@ class Agent:
2560
2741
  add_session_state_to_context: Optional[bool] = None,
2561
2742
  dependencies: Optional[Dict[str, Any]] = None,
2562
2743
  metadata: Optional[Dict[str, Any]] = None,
2563
- output_schema: Optional[Type[BaseModel]] = None,
2744
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2564
2745
  debug_mode: Optional[bool] = None,
2565
2746
  **kwargs: Any,
2566
2747
  ) -> RunOutput: ...
@@ -2587,7 +2768,7 @@ class Agent:
2587
2768
  add_session_state_to_context: Optional[bool] = None,
2588
2769
  dependencies: Optional[Dict[str, Any]] = None,
2589
2770
  metadata: Optional[Dict[str, Any]] = None,
2590
- output_schema: Optional[Type[BaseModel]] = None,
2771
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2591
2772
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2592
2773
  yield_run_output: Optional[bool] = None,
2593
2774
  debug_mode: Optional[bool] = None,
@@ -2616,7 +2797,7 @@ class Agent:
2616
2797
  add_session_state_to_context: Optional[bool] = None,
2617
2798
  dependencies: Optional[Dict[str, Any]] = None,
2618
2799
  metadata: Optional[Dict[str, Any]] = None,
2619
- output_schema: Optional[Type[BaseModel]] = None,
2800
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2620
2801
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2621
2802
  yield_run_output: Optional[bool] = None,
2622
2803
  debug_mode: Optional[bool] = None,
@@ -2764,82 +2945,37 @@ class Agent:
2764
2945
 
2765
2946
  yield_run_output = yield_run_output or yield_run_response # For backwards compatibility
2766
2947
 
2767
- # Set up retry logic
2768
- num_attempts = self.retries + 1
2769
-
2770
- for attempt in range(num_attempts):
2771
- if attempt > 0:
2772
- log_debug(f"Retrying Agent run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
2773
-
2774
- try:
2775
- # Pass the new run_response to _arun
2776
- if stream:
2777
- return self._arun_stream( # type: ignore
2778
- run_response=run_response,
2779
- run_context=run_context,
2780
- user_id=user_id,
2781
- response_format=response_format,
2782
- stream_events=stream_events,
2783
- yield_run_output=yield_run_output,
2784
- session_id=session_id,
2785
- add_history_to_context=add_history,
2786
- add_dependencies_to_context=add_dependencies,
2787
- add_session_state_to_context=add_session_state,
2788
- debug_mode=debug_mode,
2789
- background_tasks=background_tasks,
2790
- **kwargs,
2791
- ) # type: ignore[assignment]
2792
- else:
2793
- return self._arun( # type: ignore
2794
- run_response=run_response,
2795
- run_context=run_context,
2796
- user_id=user_id,
2797
- response_format=response_format,
2798
- session_id=session_id,
2799
- add_history_to_context=add_history,
2800
- add_dependencies_to_context=add_dependencies,
2801
- add_session_state_to_context=add_session_state,
2802
- debug_mode=debug_mode,
2803
- background_tasks=background_tasks,
2804
- **kwargs,
2805
- )
2806
- except (InputCheckError, OutputCheckError) as e:
2807
- log_error(f"Validation failed: {str(e)} | Check trigger: {e.check_trigger}")
2808
- raise e
2809
- except KeyboardInterrupt:
2810
- run_response.content = "Operation cancelled by user"
2811
- run_response.status = RunStatus.cancelled
2812
-
2813
- if stream:
2814
- return async_generator_wrapper( # type: ignore
2815
- create_run_cancelled_event(
2816
- from_run_response=run_response,
2817
- reason="Operation cancelled by user",
2818
- )
2819
- )
2820
- else:
2821
- return run_response
2822
- except Exception as e:
2823
- # Check if this is the last attempt
2824
- if attempt < num_attempts - 1:
2825
- # Calculate delay with exponential backoff if enabled
2826
- if self.exponential_backoff:
2827
- delay = self.delay_between_retries * (2**attempt)
2828
- else:
2829
- delay = self.delay_between_retries
2830
-
2831
- log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2832
- time.sleep(delay)
2833
- continue
2834
- else:
2835
- # Final attempt failed - re-raise the exception
2836
- log_error(f"All {num_attempts} attempts failed. Final error: {str(e)}")
2837
- if stream:
2838
- return async_generator_wrapper(create_run_error_event(run_response, error=str(e))) # type: ignore
2839
- raise e
2840
-
2841
- # If we get here, all retries failed
2842
- raise Exception(f"Failed after {num_attempts} attempts.")
2948
+ # Pass the new run_response to _arun
2949
+ if stream:
2950
+ return self._arun_stream( # type: ignore
2951
+ run_response=run_response,
2952
+ run_context=run_context,
2953
+ user_id=user_id,
2954
+ response_format=response_format,
2955
+ stream_events=stream_events,
2956
+ yield_run_output=yield_run_output,
2957
+ session_id=session_id,
2958
+ add_history_to_context=add_history,
2959
+ add_dependencies_to_context=add_dependencies,
2960
+ add_session_state_to_context=add_session_state,
2961
+ debug_mode=debug_mode,
2962
+ background_tasks=background_tasks,
2963
+ **kwargs,
2964
+ ) # type: ignore[assignment]
2965
+ else:
2966
+ return self._arun( # type: ignore
2967
+ run_response=run_response,
2968
+ run_context=run_context,
2969
+ user_id=user_id,
2970
+ response_format=response_format,
2971
+ session_id=session_id,
2972
+ add_history_to_context=add_history,
2973
+ add_dependencies_to_context=add_dependencies,
2974
+ add_session_state_to_context=add_session_state,
2975
+ debug_mode=debug_mode,
2976
+ background_tasks=background_tasks,
2977
+ **kwargs,
2978
+ )
2843
2979
 
2844
2980
  @overload
2845
2981
  def continue_run(
@@ -2970,7 +3106,7 @@ class Agent:
2970
3106
  num_attempts = self.retries + 1
2971
3107
 
2972
3108
  for attempt in range(num_attempts):
2973
- if attempt > 0:
3109
+ if num_attempts > 1:
2974
3110
  log_debug(f"Retrying Agent continue_run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
2975
3111
 
2976
3112
  try:
@@ -3611,7 +3747,7 @@ class Agent:
3611
3747
  )
3612
3748
 
3613
3749
  for attempt in range(num_attempts):
3614
- if attempt > 0:
3750
+ if num_attempts > 1:
3615
3751
  log_debug(f"Retrying Agent acontinue_run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
3616
3752
 
3617
3753
  try:
@@ -4689,22 +4825,34 @@ class Agent:
4689
4825
  output_schema = run_context.output_schema if run_context else None
4690
4826
 
4691
4827
  # Convert the response to the structured format if needed
4692
- if output_schema is not None and not isinstance(run_response.content, output_schema):
4693
- if isinstance(run_response.content, str) and self.parse_response:
4694
- try:
4695
- structured_output = parse_response_model_str(run_response.content, output_schema)
4696
-
4697
- # Update RunOutput
4698
- if structured_output is not None:
4699
- run_response.content = structured_output
4828
+ if output_schema is not None:
4829
+ # If the output schema is a dict, do not convert it into a BaseModel
4830
+ if isinstance(output_schema, dict):
4831
+ if isinstance(run_response.content, str):
4832
+ parsed_dict = parse_response_dict_str(run_response.content)
4833
+ if parsed_dict is not None:
4834
+ run_response.content = parsed_dict
4700
4835
  if isinstance(run_response, RunOutput):
4701
- run_response.content_type = output_schema.__name__
4836
+ run_response.content_type = "dict"
4702
4837
  else:
4703
- log_warning("Failed to convert response to output_schema")
4704
- except Exception as e:
4705
- log_warning(f"Failed to convert response to output model: {e}")
4706
- else:
4707
- log_warning("Something went wrong. Run response content is not a string")
4838
+ log_warning("Failed to parse JSON response against the provided output schema.")
4839
+ # If the output schema is a Pydantic model and parse_response is True, parse it into a BaseModel
4840
+ elif not isinstance(run_response.content, output_schema):
4841
+ if isinstance(run_response.content, str) and self.parse_response:
4842
+ try:
4843
+ structured_output = parse_response_model_str(run_response.content, output_schema)
4844
+
4845
+ # Update RunOutput
4846
+ if structured_output is not None:
4847
+ run_response.content = structured_output
4848
+ if isinstance(run_response, RunOutput):
4849
+ run_response.content_type = output_schema.__name__
4850
+ else:
4851
+ log_warning("Failed to convert response to output_schema")
4852
+ except Exception as e:
4853
+ log_warning(f"Failed to convert response to output model: {e}")
4854
+ else:
4855
+ log_warning("Something went wrong. Run response content is not a string")
4708
4856
 
4709
4857
  def _handle_external_execution_update(self, run_messages: RunMessages, tool: ToolExecution):
4710
4858
  self.model = cast(Model, self.model)
@@ -4799,6 +4947,15 @@ class Agent:
4799
4947
  events_to_skip=self.events_to_skip, # type: ignore
4800
4948
  store_events=self.store_events,
4801
4949
  )
4950
+ if tool.tool_call_error:
4951
+ yield handle_event( # type: ignore
4952
+ create_tool_call_error_event(
4953
+ from_run_response=run_response, tool=tool, error=str(tool.result)
4954
+ ),
4955
+ run_response,
4956
+ events_to_skip=self.events_to_skip, # type: ignore
4957
+ store_events=self.store_events,
4958
+ )
4802
4959
 
4803
4960
  if len(function_call_results) > 0:
4804
4961
  run_messages.messages.extend(function_call_results)
@@ -4856,6 +5013,15 @@ class Agent:
4856
5013
  events_to_skip=self.events_to_skip, # type: ignore
4857
5014
  store_events=self.store_events,
4858
5015
  )
5016
+ if tool.tool_call_error:
5017
+ yield handle_event( # type: ignore
5018
+ create_tool_call_error_event(
5019
+ from_run_response=run_response, tool=tool, error=str(tool.result)
5020
+ ),
5021
+ run_response,
5022
+ events_to_skip=self.events_to_skip, # type: ignore
5023
+ store_events=self.store_events,
5024
+ )
4859
5025
  if len(function_call_results) > 0:
4860
5026
  run_messages.messages.extend(function_call_results)
4861
5027
 
@@ -5053,7 +5219,7 @@ class Agent:
5053
5219
  # Update the run_response content with the structured output
5054
5220
  run_response.content = model_response.parsed
5055
5221
  # Update the run_response content_type with the structured output class name
5056
- run_response.content_type = output_schema.__name__
5222
+ run_response.content_type = "dict" if isinstance(output_schema, dict) else output_schema.__name__
5057
5223
  else:
5058
5224
  # Update the run_response content with the model response content
5059
5225
  run_response.content = model_response.content
@@ -5339,7 +5505,7 @@ class Agent:
5339
5505
 
5340
5506
  # Get output_schema from run_context
5341
5507
  output_schema = run_context.output_schema if run_context else None
5342
- content_type = output_schema.__name__ # type: ignore
5508
+ content_type = "dict" if isinstance(output_schema, dict) else output_schema.__name__ # type: ignore
5343
5509
  run_response.content = model_response.content
5344
5510
  run_response.content_type = content_type
5345
5511
  else:
@@ -5609,6 +5775,15 @@ class Agent:
5609
5775
  events_to_skip=self.events_to_skip, # type: ignore
5610
5776
  store_events=self.store_events,
5611
5777
  )
5778
+ if tool_call.tool_call_error:
5779
+ yield handle_event( # type: ignore
5780
+ create_tool_call_error_event(
5781
+ from_run_response=run_response, tool=tool_call, error=str(tool_call.result)
5782
+ ),
5783
+ run_response,
5784
+ events_to_skip=self.events_to_skip, # type: ignore
5785
+ store_events=self.store_events,
5786
+ )
5612
5787
 
5613
5788
  if stream_events:
5614
5789
  if reasoning_step is not None:
@@ -6109,6 +6284,10 @@ class Agent:
6109
6284
  elif model.supports_json_schema_outputs:
6110
6285
  if self.use_json_mode or (not self.structured_outputs):
6111
6286
  log_debug("Setting Model.response_format to JSON response mode")
6287
+ # Handle JSON schema - pass through directly (user provides full provider format)
6288
+ if isinstance(output_schema, dict):
6289
+ return output_schema
6290
+ # Handle Pydantic schema
6112
6291
  return {
6113
6292
  "type": "json_schema",
6114
6293
  "json_schema": {
@@ -7521,8 +7700,8 @@ class Agent:
7521
7700
  ):
7522
7701
  system_message_content += f"{get_json_output_prompt(output_schema)}" # type: ignore
7523
7702
 
7524
- # 3.3.14 Add the response model format prompt if output_schema is provided
7525
- if output_schema is not None and self.parser_model is not None:
7703
+ # 3.3.14 Add the response model format prompt if output_schema is provided (Pydantic only)
7704
+ if output_schema is not None and self.parser_model is not None and not isinstance(output_schema, dict):
7526
7705
  system_message_content += f"{get_response_model_format_prompt(output_schema)}"
7527
7706
 
7528
7707
  # 3.3.15 Add the session state to the system message
@@ -7868,8 +8047,8 @@ class Agent:
7868
8047
  ):
7869
8048
  system_message_content += f"{get_json_output_prompt(output_schema)}" # type: ignore
7870
8049
 
7871
- # 3.3.14 Add the response model format prompt if output_schema is provided
7872
- if output_schema is not None and self.parser_model is not None:
8050
+ # 3.3.14 Add the response model format prompt if output_schema is provided (Pydantic only)
8051
+ if output_schema is not None and self.parser_model is not None and not isinstance(output_schema, dict):
7873
8052
  system_message_content += f"{get_response_model_format_prompt(output_schema)}"
7874
8053
 
7875
8054
  # 3.3.15 Add the session state to the system message
@@ -9176,292 +9355,78 @@ class Agent:
9176
9355
 
9177
9356
  return updated_reasoning_content
9178
9357
 
9179
- def _reason(
9180
- self, run_response: RunOutput, run_messages: RunMessages, stream_events: Optional[bool] = None
9358
+ def _handle_reasoning_event(
9359
+ self,
9360
+ event: "ReasoningEvent", # type: ignore # noqa: F821
9361
+ run_response: RunOutput,
9362
+ stream_events: Optional[bool] = None,
9181
9363
  ) -> Iterator[RunOutputEvent]:
9182
- # Yield a reasoning started event
9183
- if stream_events:
9184
- yield handle_event( # type: ignore
9185
- create_reasoning_started_event(from_run_response=run_response),
9186
- run_response,
9187
- events_to_skip=self.events_to_skip, # type: ignore
9188
- store_events=self.store_events,
9189
- )
9190
-
9191
- use_default_reasoning = False
9192
-
9193
- # Get the reasoning model
9194
- reasoning_model: Optional[Model] = self.reasoning_model
9195
- reasoning_model_provided = reasoning_model is not None
9196
- if reasoning_model is None and self.model is not None:
9197
- from copy import deepcopy
9198
-
9199
- reasoning_model = deepcopy(self.model)
9200
- if reasoning_model is None:
9201
- log_warning("Reasoning error. Reasoning model is None, continuing regular session...")
9202
- return
9203
-
9204
- # If a reasoning model is provided, use it to generate reasoning
9205
- if reasoning_model_provided:
9206
- from agno.reasoning.anthropic import is_anthropic_reasoning_model
9207
- from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
9208
- from agno.reasoning.deepseek import is_deepseek_reasoning_model
9209
- from agno.reasoning.gemini import is_gemini_reasoning_model
9210
- from agno.reasoning.groq import is_groq_reasoning_model
9211
- from agno.reasoning.helpers import get_reasoning_agent
9212
- from agno.reasoning.ollama import is_ollama_reasoning_model
9213
- from agno.reasoning.openai import is_openai_reasoning_model
9214
- from agno.reasoning.vertexai import is_vertexai_reasoning_model
9215
-
9216
- reasoning_agent = self.reasoning_agent or get_reasoning_agent(
9217
- reasoning_model=reasoning_model,
9218
- telemetry=self.telemetry,
9219
- debug_mode=self.debug_mode,
9220
- debug_level=self.debug_level,
9221
- session_state=self.session_state,
9222
- dependencies=self.dependencies,
9223
- metadata=self.metadata,
9224
- )
9225
- is_deepseek = is_deepseek_reasoning_model(reasoning_model)
9226
- is_groq = is_groq_reasoning_model(reasoning_model)
9227
- is_openai = is_openai_reasoning_model(reasoning_model)
9228
- is_ollama = is_ollama_reasoning_model(reasoning_model)
9229
- is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
9230
- is_gemini = is_gemini_reasoning_model(reasoning_model)
9231
- is_anthropic = is_anthropic_reasoning_model(reasoning_model)
9232
- is_vertexai = is_vertexai_reasoning_model(reasoning_model)
9233
-
9234
- if (
9235
- is_deepseek
9236
- or is_groq
9237
- or is_openai
9238
- or is_ollama
9239
- or is_ai_foundry
9240
- or is_gemini
9241
- or is_anthropic
9242
- or is_vertexai
9243
- ):
9244
- reasoning_message: Optional[Message] = None
9245
- if is_deepseek:
9246
- from agno.reasoning.deepseek import get_deepseek_reasoning
9247
-
9248
- log_debug("Starting DeepSeek Reasoning", center=True, symbol="=")
9249
- reasoning_message = get_deepseek_reasoning(
9250
- reasoning_agent=reasoning_agent,
9251
- messages=run_messages.get_input_messages(),
9252
- )
9253
- elif is_groq:
9254
- from agno.reasoning.groq import get_groq_reasoning
9255
-
9256
- log_debug("Starting Groq Reasoning", center=True, symbol="=")
9257
- reasoning_message = get_groq_reasoning(
9258
- reasoning_agent=reasoning_agent,
9259
- messages=run_messages.get_input_messages(),
9260
- )
9261
- elif is_openai:
9262
- from agno.reasoning.openai import get_openai_reasoning
9263
-
9264
- log_debug("Starting OpenAI Reasoning", center=True, symbol="=")
9265
- reasoning_message = get_openai_reasoning(
9266
- reasoning_agent=reasoning_agent,
9267
- messages=run_messages.get_input_messages(),
9268
- )
9269
- elif is_ollama:
9270
- from agno.reasoning.ollama import get_ollama_reasoning
9271
-
9272
- log_debug("Starting Ollama Reasoning", center=True, symbol="=")
9273
- reasoning_message = get_ollama_reasoning(
9274
- reasoning_agent=reasoning_agent,
9275
- messages=run_messages.get_input_messages(),
9276
- )
9277
- elif is_ai_foundry:
9278
- from agno.reasoning.azure_ai_foundry import get_ai_foundry_reasoning
9279
-
9280
- log_debug("Starting Azure AI Foundry Reasoning", center=True, symbol="=")
9281
- reasoning_message = get_ai_foundry_reasoning(
9282
- reasoning_agent=reasoning_agent,
9283
- messages=run_messages.get_input_messages(),
9284
- )
9285
- elif is_gemini:
9286
- from agno.reasoning.gemini import get_gemini_reasoning
9364
+ """
9365
+ Convert a ReasoningEvent from the ReasoningManager to Agent-specific RunOutputEvents.
9287
9366
 
9288
- log_debug("Starting Gemini Reasoning", center=True, symbol="=")
9289
- reasoning_message = get_gemini_reasoning(
9290
- reasoning_agent=reasoning_agent,
9291
- messages=run_messages.get_input_messages(),
9292
- )
9293
- elif is_anthropic:
9294
- from agno.reasoning.anthropic import get_anthropic_reasoning
9367
+ This method handles the conversion of generic reasoning events to Agent events,
9368
+ keeping the Agent._reason() method clean and simple.
9369
+ """
9370
+ from agno.reasoning.manager import ReasoningEventType
9295
9371
 
9296
- log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
9297
- reasoning_message = get_anthropic_reasoning(
9298
- reasoning_agent=reasoning_agent,
9299
- messages=run_messages.get_input_messages(),
9300
- )
9301
- elif is_vertexai:
9302
- from agno.reasoning.vertexai import get_vertexai_reasoning
9372
+ if event.event_type == ReasoningEventType.started:
9373
+ if stream_events:
9374
+ yield handle_event( # type: ignore
9375
+ create_reasoning_started_event(from_run_response=run_response),
9376
+ run_response,
9377
+ events_to_skip=self.events_to_skip, # type: ignore
9378
+ store_events=self.store_events,
9379
+ )
9303
9380
 
9304
- log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
9305
- reasoning_message = get_vertexai_reasoning(
9306
- reasoning_agent=reasoning_agent,
9307
- messages=run_messages.get_input_messages(),
9308
- )
9381
+ elif event.event_type == ReasoningEventType.content_delta:
9382
+ if stream_events and event.reasoning_content:
9383
+ yield handle_event( # type: ignore
9384
+ create_reasoning_content_delta_event(
9385
+ from_run_response=run_response,
9386
+ reasoning_content=event.reasoning_content,
9387
+ ),
9388
+ run_response,
9389
+ events_to_skip=self.events_to_skip, # type: ignore
9390
+ store_events=self.store_events,
9391
+ )
9309
9392
 
9310
- if reasoning_message is None:
9311
- log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
9312
- return
9313
- run_messages.messages.append(reasoning_message)
9314
- # Add reasoning step to the Agent's run_response
9393
+ elif event.event_type == ReasoningEventType.step:
9394
+ if event.reasoning_step:
9395
+ # Update run_response with this step
9315
9396
  update_run_output_with_reasoning(
9316
9397
  run_response=run_response,
9317
- reasoning_steps=[ReasoningStep(result=reasoning_message.content)],
9318
- reasoning_agent_messages=[reasoning_message],
9398
+ reasoning_steps=[event.reasoning_step],
9399
+ reasoning_agent_messages=[],
9319
9400
  )
9320
9401
  if stream_events:
9402
+ updated_reasoning_content = self._format_reasoning_step_content(
9403
+ run_response=run_response,
9404
+ reasoning_step=event.reasoning_step,
9405
+ )
9321
9406
  yield handle_event( # type: ignore
9322
- create_reasoning_completed_event(
9407
+ create_reasoning_step_event(
9323
9408
  from_run_response=run_response,
9324
- content=ReasoningSteps(reasoning_steps=[ReasoningStep(result=reasoning_message.content)]),
9325
- content_type=ReasoningSteps.__name__,
9409
+ reasoning_step=event.reasoning_step,
9410
+ reasoning_content=updated_reasoning_content,
9326
9411
  ),
9327
9412
  run_response,
9328
9413
  events_to_skip=self.events_to_skip, # type: ignore
9329
9414
  store_events=self.store_events,
9330
9415
  )
9331
- else:
9332
- log_info(
9333
- f"Reasoning model: {reasoning_model.__class__.__name__} is not a native reasoning model, defaulting to manual Chain-of-Thought reasoning"
9334
- )
9335
- use_default_reasoning = True
9336
- # If no reasoning model is provided, use default reasoning
9337
- else:
9338
- use_default_reasoning = True
9339
-
9340
- if use_default_reasoning:
9341
- from agno.reasoning.default import get_default_reasoning_agent
9342
- from agno.reasoning.helpers import (
9343
- get_next_action,
9344
- update_messages_with_reasoning,
9345
- )
9346
-
9347
- # Get default reasoning agent
9348
- reasoning_agent: Optional[Agent] = self.reasoning_agent # type: ignore
9349
- if reasoning_agent is None:
9350
- reasoning_agent = get_default_reasoning_agent(
9351
- reasoning_model=reasoning_model,
9352
- min_steps=self.reasoning_min_steps,
9353
- max_steps=self.reasoning_max_steps,
9354
- tools=self.tools,
9355
- tool_call_limit=self.tool_call_limit,
9356
- use_json_mode=self.use_json_mode,
9357
- telemetry=self.telemetry,
9358
- debug_mode=self.debug_mode,
9359
- debug_level=self.debug_level,
9360
- session_state=self.session_state,
9361
- dependencies=self.dependencies,
9362
- metadata=self.metadata,
9363
- )
9364
-
9365
- # Validate reasoning agent
9366
- if reasoning_agent is None:
9367
- log_warning("Reasoning error. Reasoning agent is None, continuing regular session...")
9368
- return
9369
- # Ensure the reasoning agent response model is ReasoningSteps
9370
- if (
9371
- reasoning_agent.output_schema is not None
9372
- and not isinstance(reasoning_agent.output_schema, type)
9373
- and not issubclass(reasoning_agent.output_schema, ReasoningSteps)
9374
- ):
9375
- log_warning("Reasoning agent response model should be `ReasoningSteps`, continuing regular session...")
9376
- return
9377
-
9378
- step_count = 1
9379
- next_action = NextAction.CONTINUE
9380
- reasoning_messages: List[Message] = []
9381
- all_reasoning_steps: List[ReasoningStep] = []
9382
- log_debug("Starting Reasoning", center=True, symbol="=")
9383
- while next_action == NextAction.CONTINUE and step_count < self.reasoning_max_steps:
9384
- log_debug(f"Step {step_count}", center=True, symbol="=")
9385
- try:
9386
- # Run the reasoning agent
9387
- reasoning_agent_response: RunOutput = reasoning_agent.run(input=run_messages.get_input_messages())
9388
- if reasoning_agent_response.content is None or reasoning_agent_response.messages is None:
9389
- log_warning("Reasoning error. Reasoning response is empty, continuing regular session...")
9390
- break
9391
-
9392
- if isinstance(reasoning_agent_response.content, str):
9393
- log_warning(
9394
- "Reasoning error. Content is a string, not structured output. Continuing regular session..."
9395
- )
9396
- break
9397
-
9398
- if reasoning_agent_response.content is not None and (
9399
- reasoning_agent_response.content.reasoning_steps is None
9400
- or len(reasoning_agent_response.content.reasoning_steps) == 0
9401
- ):
9402
- log_warning("Reasoning error. Reasoning steps are empty, continuing regular session...")
9403
- break
9404
-
9405
- reasoning_steps: List[ReasoningStep] = reasoning_agent_response.content.reasoning_steps
9406
- all_reasoning_steps.extend(reasoning_steps)
9407
- # Yield reasoning steps
9408
- if stream_events:
9409
- for reasoning_step in reasoning_steps:
9410
- updated_reasoning_content = self._format_reasoning_step_content(
9411
- run_response=run_response,
9412
- reasoning_step=reasoning_step,
9413
- )
9414
-
9415
- yield handle_event( # type: ignore
9416
- create_reasoning_step_event(
9417
- from_run_response=run_response,
9418
- reasoning_step=reasoning_step,
9419
- reasoning_content=updated_reasoning_content,
9420
- ),
9421
- run_response,
9422
- events_to_skip=self.events_to_skip, # type: ignore
9423
- store_events=self.store_events,
9424
- )
9425
-
9426
- # Find the index of the first assistant message
9427
- first_assistant_index = next(
9428
- (i for i, m in enumerate(reasoning_agent_response.messages) if m.role == "assistant"),
9429
- len(reasoning_agent_response.messages),
9430
- )
9431
- # Extract reasoning messages starting from the message after the first assistant message
9432
- reasoning_messages = reasoning_agent_response.messages[first_assistant_index:]
9433
-
9434
- # Add reasoning step to the Agent's run_response
9435
- update_run_output_with_reasoning(
9436
- run_response=run_response,
9437
- reasoning_steps=reasoning_steps,
9438
- reasoning_agent_messages=reasoning_agent_response.messages,
9439
- )
9440
- # Get the next action
9441
- next_action = get_next_action(reasoning_steps[-1])
9442
- if next_action == NextAction.FINAL_ANSWER:
9443
- break
9444
- except Exception as e:
9445
- log_error(f"Reasoning error: {e}")
9446
- break
9447
-
9448
- step_count += 1
9449
-
9450
- log_debug(f"Total Reasoning steps: {len(all_reasoning_steps)}")
9451
- log_debug("Reasoning finished", center=True, symbol="=")
9452
-
9453
- # Update the messages_for_model to include reasoning messages
9454
- update_messages_with_reasoning(
9455
- run_messages=run_messages,
9456
- reasoning_messages=reasoning_messages,
9457
- )
9458
9416
 
9459
- # Yield the final reasoning completed event
9417
+ elif event.event_type == ReasoningEventType.completed:
9418
+ if event.message and event.reasoning_steps:
9419
+ # This is from native reasoning - update with the message and steps
9420
+ update_run_output_with_reasoning(
9421
+ run_response=run_response,
9422
+ reasoning_steps=event.reasoning_steps,
9423
+ reasoning_agent_messages=event.reasoning_messages,
9424
+ )
9460
9425
  if stream_events:
9461
9426
  yield handle_event( # type: ignore
9462
9427
  create_reasoning_completed_event(
9463
9428
  from_run_response=run_response,
9464
- content=ReasoningSteps(reasoning_steps=all_reasoning_steps),
9429
+ content=ReasoningSteps(reasoning_steps=event.reasoning_steps),
9465
9430
  content_type=ReasoningSteps.__name__,
9466
9431
  ),
9467
9432
  run_response,
@@ -9469,45 +9434,37 @@ class Agent:
9469
9434
  store_events=self.store_events,
9470
9435
  )
9471
9436
 
9472
- async def _areason(
9437
+ elif event.event_type == ReasoningEventType.error:
9438
+ log_warning(f"Reasoning error. {event.error}, continuing regular session...")
9439
+
9440
+ def _reason(
9473
9441
  self, run_response: RunOutput, run_messages: RunMessages, stream_events: Optional[bool] = None
9474
- ) -> Any:
9475
- # Yield a reasoning started event
9476
- if stream_events:
9477
- yield handle_event( # type: ignore
9478
- create_reasoning_started_event(from_run_response=run_response),
9479
- run_response,
9480
- events_to_skip=self.events_to_skip, # type: ignore
9481
- store_events=self.store_events,
9482
- )
9442
+ ) -> Iterator[RunOutputEvent]:
9443
+ """
9444
+ Run reasoning using the ReasoningManager.
9483
9445
 
9484
- use_default_reasoning = False
9446
+ Handles both native reasoning models (DeepSeek, Anthropic, etc.) and
9447
+ default Chain-of-Thought reasoning with a clean, unified interface.
9448
+ """
9449
+ from agno.reasoning.manager import ReasoningConfig, ReasoningManager
9485
9450
 
9486
- # Get the reasoning model
9451
+ # Get the reasoning model (use copy of main model if not provided)
9487
9452
  reasoning_model: Optional[Model] = self.reasoning_model
9488
- reasoning_model_provided = reasoning_model is not None
9489
9453
  if reasoning_model is None and self.model is not None:
9490
9454
  from copy import deepcopy
9491
9455
 
9492
9456
  reasoning_model = deepcopy(self.model)
9493
- if reasoning_model is None:
9494
- log_warning("Reasoning error. Reasoning model is None, continuing regular session...")
9495
- return
9496
9457
 
9497
- # If a reasoning model is provided, use it to generate reasoning
9498
- if reasoning_model_provided:
9499
- from agno.reasoning.anthropic import is_anthropic_reasoning_model
9500
- from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
9501
- from agno.reasoning.deepseek import is_deepseek_reasoning_model
9502
- from agno.reasoning.gemini import is_gemini_reasoning_model
9503
- from agno.reasoning.groq import is_groq_reasoning_model
9504
- from agno.reasoning.helpers import get_reasoning_agent
9505
- from agno.reasoning.ollama import is_ollama_reasoning_model
9506
- from agno.reasoning.openai import is_openai_reasoning_model
9507
- from agno.reasoning.vertexai import is_vertexai_reasoning_model
9508
-
9509
- reasoning_agent = self.reasoning_agent or get_reasoning_agent(
9458
+ # Create reasoning manager with config
9459
+ manager = ReasoningManager(
9460
+ ReasoningConfig(
9510
9461
  reasoning_model=reasoning_model,
9462
+ reasoning_agent=self.reasoning_agent,
9463
+ min_steps=self.reasoning_min_steps,
9464
+ max_steps=self.reasoning_max_steps,
9465
+ tools=self.tools,
9466
+ tool_call_limit=self.tool_call_limit,
9467
+ use_json_mode=self.use_json_mode,
9511
9468
  telemetry=self.telemetry,
9512
9469
  debug_mode=self.debug_mode,
9513
9470
  debug_level=self.debug_level,
@@ -9515,252 +9472,53 @@ class Agent:
9515
9472
  dependencies=self.dependencies,
9516
9473
  metadata=self.metadata,
9517
9474
  )
9518
- is_deepseek = is_deepseek_reasoning_model(reasoning_model)
9519
- is_groq = is_groq_reasoning_model(reasoning_model)
9520
- is_openai = is_openai_reasoning_model(reasoning_model)
9521
- is_ollama = is_ollama_reasoning_model(reasoning_model)
9522
- is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
9523
- is_gemini = is_gemini_reasoning_model(reasoning_model)
9524
- is_anthropic = is_anthropic_reasoning_model(reasoning_model)
9525
- is_vertexai = is_vertexai_reasoning_model(reasoning_model)
9526
-
9527
- if (
9528
- is_deepseek
9529
- or is_groq
9530
- or is_openai
9531
- or is_ollama
9532
- or is_ai_foundry
9533
- or is_gemini
9534
- or is_anthropic
9535
- or is_vertexai
9536
- ):
9537
- reasoning_message: Optional[Message] = None
9538
- if is_deepseek:
9539
- from agno.reasoning.deepseek import aget_deepseek_reasoning
9540
-
9541
- log_debug("Starting DeepSeek Reasoning", center=True, symbol="=")
9542
- reasoning_message = await aget_deepseek_reasoning(
9543
- reasoning_agent=reasoning_agent,
9544
- messages=run_messages.get_input_messages(),
9545
- )
9546
- elif is_groq:
9547
- from agno.reasoning.groq import aget_groq_reasoning
9548
-
9549
- log_debug("Starting Groq Reasoning", center=True, symbol="=")
9550
- reasoning_message = await aget_groq_reasoning(
9551
- reasoning_agent=reasoning_agent,
9552
- messages=run_messages.get_input_messages(),
9553
- )
9554
- elif is_openai:
9555
- from agno.reasoning.openai import aget_openai_reasoning
9556
-
9557
- log_debug("Starting OpenAI Reasoning", center=True, symbol="=")
9558
- reasoning_message = await aget_openai_reasoning(
9559
- reasoning_agent=reasoning_agent,
9560
- messages=run_messages.get_input_messages(),
9561
- )
9562
- elif is_ollama:
9563
- from agno.reasoning.ollama import get_ollama_reasoning
9564
-
9565
- log_debug("Starting Ollama Reasoning", center=True, symbol="=")
9566
- reasoning_message = get_ollama_reasoning(
9567
- reasoning_agent=reasoning_agent,
9568
- messages=run_messages.get_input_messages(),
9569
- )
9570
- elif is_ai_foundry:
9571
- from agno.reasoning.azure_ai_foundry import get_ai_foundry_reasoning
9572
-
9573
- log_debug("Starting Azure AI Foundry Reasoning", center=True, symbol="=")
9574
- reasoning_message = get_ai_foundry_reasoning(
9575
- reasoning_agent=reasoning_agent,
9576
- messages=run_messages.get_input_messages(),
9577
- )
9578
- elif is_gemini:
9579
- from agno.reasoning.gemini import aget_gemini_reasoning
9580
-
9581
- log_debug("Starting Gemini Reasoning", center=True, symbol="=")
9582
- reasoning_message = await aget_gemini_reasoning(
9583
- reasoning_agent=reasoning_agent,
9584
- messages=run_messages.get_input_messages(),
9585
- )
9586
- elif is_anthropic:
9587
- from agno.reasoning.anthropic import aget_anthropic_reasoning
9588
-
9589
- log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
9590
- reasoning_message = await aget_anthropic_reasoning(
9591
- reasoning_agent=reasoning_agent,
9592
- messages=run_messages.get_input_messages(),
9593
- )
9594
- elif is_vertexai:
9595
- from agno.reasoning.vertexai import aget_vertexai_reasoning
9596
-
9597
- log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
9598
- reasoning_message = await aget_vertexai_reasoning(
9599
- reasoning_agent=reasoning_agent,
9600
- messages=run_messages.get_input_messages(),
9601
- )
9602
-
9603
- if reasoning_message is None:
9604
- log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
9605
- return
9606
- run_messages.messages.append(reasoning_message)
9607
- # Add reasoning step to the Agent's run_response
9608
- update_run_output_with_reasoning(
9609
- run_response=run_response,
9610
- reasoning_steps=[ReasoningStep(result=reasoning_message.content)],
9611
- reasoning_agent_messages=[reasoning_message],
9612
- )
9613
- if stream_events:
9614
- yield handle_event(
9615
- create_reasoning_completed_event(
9616
- from_run_response=run_response,
9617
- content=ReasoningSteps(reasoning_steps=[ReasoningStep(result=reasoning_message.content)]),
9618
- content_type=ReasoningSteps.__name__,
9619
- ),
9620
- run_response,
9621
- events_to_skip=self.events_to_skip, # type: ignore
9622
- store_events=self.store_events,
9623
- )
9624
- else:
9625
- log_info(
9626
- f"Reasoning model: {reasoning_model.__class__.__name__} is not a native reasoning model, defaulting to manual Chain-of-Thought reasoning"
9627
- )
9628
- use_default_reasoning = True
9629
- # If no reasoning model is provided, use default reasoning
9630
- else:
9631
- use_default_reasoning = True
9632
-
9633
- if use_default_reasoning:
9634
- from agno.reasoning.default import get_default_reasoning_agent
9635
- from agno.reasoning.helpers import (
9636
- get_next_action,
9637
- update_messages_with_reasoning,
9638
- )
9639
-
9640
- # Get default reasoning agent
9641
- reasoning_agent: Optional[Agent] = self.reasoning_agent # type: ignore
9642
- if reasoning_agent is None:
9643
- reasoning_agent = get_default_reasoning_agent(
9644
- reasoning_model=reasoning_model,
9645
- min_steps=self.reasoning_min_steps,
9646
- max_steps=self.reasoning_max_steps,
9647
- tools=self.tools,
9648
- tool_call_limit=self.tool_call_limit,
9649
- use_json_mode=self.use_json_mode,
9650
- telemetry=self.telemetry,
9651
- debug_mode=self.debug_mode,
9652
- debug_level=self.debug_level,
9653
- session_state=self.session_state,
9654
- dependencies=self.dependencies,
9655
- metadata=self.metadata,
9656
- )
9657
-
9658
- # Validate reasoning agent
9659
- if reasoning_agent is None:
9660
- log_warning("Reasoning error. Reasoning agent is None, continuing regular session...")
9661
- return
9662
- # Ensure the reasoning agent response model is ReasoningSteps
9663
- if (
9664
- reasoning_agent.output_schema is not None
9665
- and not isinstance(reasoning_agent.output_schema, type)
9666
- and not issubclass(reasoning_agent.output_schema, ReasoningSteps)
9667
- ):
9668
- log_warning("Reasoning agent response model should be `ReasoningSteps`, continuing regular session...")
9669
- return
9670
-
9671
- step_count = 1
9672
- next_action = NextAction.CONTINUE
9673
- reasoning_messages: List[Message] = []
9674
- all_reasoning_steps: List[ReasoningStep] = []
9675
- log_debug("Starting Reasoning", center=True, symbol="=")
9676
- while next_action == NextAction.CONTINUE and step_count < self.reasoning_max_steps:
9677
- log_debug(f"Step {step_count}", center=True, symbol="=")
9678
- step_count += 1
9679
- try:
9680
- # Run the reasoning agent
9681
- reasoning_agent_response: RunOutput = await reasoning_agent.arun(
9682
- input=run_messages.get_input_messages()
9683
- )
9684
- if reasoning_agent_response.content is None or reasoning_agent_response.messages is None:
9685
- log_warning("Reasoning error. Reasoning response is empty, continuing regular session...")
9686
- break
9687
-
9688
- if isinstance(reasoning_agent_response.content, str):
9689
- log_warning(
9690
- "Reasoning error. Content is a string, not structured output. Continuing regular session..."
9691
- )
9692
- break
9693
-
9694
- if reasoning_agent_response.content.reasoning_steps is None:
9695
- log_warning("Reasoning error. Reasoning steps are empty, continuing regular session...")
9696
- break
9697
-
9698
- reasoning_steps: List[ReasoningStep] = reasoning_agent_response.content.reasoning_steps
9699
- all_reasoning_steps.extend(reasoning_steps)
9700
- # Yield reasoning steps
9701
- if stream_events:
9702
- for reasoning_step in reasoning_steps:
9703
- updated_reasoning_content = self._format_reasoning_step_content(
9704
- run_response=run_response,
9705
- reasoning_step=reasoning_step,
9706
- )
9475
+ )
9707
9476
 
9708
- # Yield the response with the updated reasoning_content
9709
- yield handle_event(
9710
- create_reasoning_step_event(
9711
- from_run_response=run_response,
9712
- reasoning_step=reasoning_step,
9713
- reasoning_content=updated_reasoning_content,
9714
- ),
9715
- run_response,
9716
- events_to_skip=self.events_to_skip, # type: ignore
9717
- store_events=self.store_events,
9718
- )
9477
+ # Use the unified reason() method and convert events
9478
+ for event in manager.reason(run_messages, stream=bool(stream_events)):
9479
+ yield from self._handle_reasoning_event(event, run_response, stream_events)
9719
9480
 
9720
- # Find the index of the first assistant message
9721
- first_assistant_index = next(
9722
- (i for i, m in enumerate(reasoning_agent_response.messages) if m.role == "assistant"),
9723
- len(reasoning_agent_response.messages),
9724
- )
9725
- # Extract reasoning messages starting from the message after the first assistant message
9726
- reasoning_messages = reasoning_agent_response.messages[first_assistant_index:]
9481
+ async def _areason(
9482
+ self, run_response: RunOutput, run_messages: RunMessages, stream_events: Optional[bool] = None
9483
+ ) -> Any:
9484
+ """
9485
+ Run reasoning asynchronously using the ReasoningManager.
9727
9486
 
9728
- # Add reasoning step to the Agent's run_response
9729
- update_run_output_with_reasoning(
9730
- run_response=run_response,
9731
- reasoning_steps=reasoning_steps,
9732
- reasoning_agent_messages=reasoning_agent_response.messages,
9733
- )
9487
+ Handles both native reasoning models (DeepSeek, Anthropic, etc.) and
9488
+ default Chain-of-Thought reasoning with a clean, unified interface.
9489
+ """
9490
+ from agno.reasoning.manager import ReasoningConfig, ReasoningManager
9734
9491
 
9735
- # Get the next action
9736
- next_action = get_next_action(reasoning_steps[-1])
9737
- if next_action == NextAction.FINAL_ANSWER:
9738
- break
9739
- except Exception as e:
9740
- log_error(f"Reasoning error: {e}")
9741
- break
9492
+ # Get the reasoning model (use copy of main model if not provided)
9493
+ reasoning_model: Optional[Model] = self.reasoning_model
9494
+ if reasoning_model is None and self.model is not None:
9495
+ from copy import deepcopy
9742
9496
 
9743
- log_debug(f"Total Reasoning steps: {len(all_reasoning_steps)}")
9744
- log_debug("Reasoning finished", center=True, symbol="=")
9497
+ reasoning_model = deepcopy(self.model)
9745
9498
 
9746
- # Update the messages_for_model to include reasoning messages
9747
- update_messages_with_reasoning(
9748
- run_messages=run_messages,
9749
- reasoning_messages=reasoning_messages,
9499
+ # Create reasoning manager with config
9500
+ manager = ReasoningManager(
9501
+ ReasoningConfig(
9502
+ reasoning_model=reasoning_model,
9503
+ reasoning_agent=self.reasoning_agent,
9504
+ min_steps=self.reasoning_min_steps,
9505
+ max_steps=self.reasoning_max_steps,
9506
+ tools=self.tools,
9507
+ tool_call_limit=self.tool_call_limit,
9508
+ use_json_mode=self.use_json_mode,
9509
+ telemetry=self.telemetry,
9510
+ debug_mode=self.debug_mode,
9511
+ debug_level=self.debug_level,
9512
+ session_state=self.session_state,
9513
+ dependencies=self.dependencies,
9514
+ metadata=self.metadata,
9750
9515
  )
9516
+ )
9751
9517
 
9752
- # Yield the final reasoning completed event
9753
- if stream_events:
9754
- yield handle_event(
9755
- create_reasoning_completed_event(
9756
- from_run_response=run_response,
9757
- content=ReasoningSteps(reasoning_steps=all_reasoning_steps),
9758
- content_type=ReasoningSteps.__name__,
9759
- ),
9760
- run_response,
9761
- events_to_skip=self.events_to_skip, # type: ignore
9762
- store_events=self.store_events,
9763
- )
9518
+ # Use the unified areason() method and convert events
9519
+ async for event in manager.areason(run_messages, stream=bool(stream_events)):
9520
+ for output_event in self._handle_reasoning_event(event, run_response, stream_events):
9521
+ yield output_event
9764
9522
 
9765
9523
  def _process_parser_response(
9766
9524
  self,
@@ -10912,8 +10670,7 @@ class Agent:
10912
10670
  session: AgentSession,
10913
10671
  run_context: Optional[RunContext] = None,
10914
10672
  user_id: Optional[str] = None,
10915
- ) -> None:
10916
- # Scrub the stored run based on storage flags
10673
+ ) -> None: # Scrub the stored run based on storage flags
10917
10674
  self._scrub_run_output_for_storage(run_response)
10918
10675
 
10919
10676
  # Stop the timer for the Run duration