agno 2.3.12__py3-none-any.whl → 2.3.14__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 (55) hide show
  1. agno/agent/agent.py +1125 -1401
  2. agno/eval/__init__.py +21 -8
  3. agno/knowledge/embedder/azure_openai.py +0 -1
  4. agno/knowledge/embedder/google.py +1 -1
  5. agno/models/anthropic/claude.py +4 -1
  6. agno/models/azure/openai_chat.py +11 -5
  7. agno/models/base.py +8 -4
  8. agno/models/openai/chat.py +0 -2
  9. agno/models/openai/responses.py +2 -2
  10. agno/os/app.py +112 -5
  11. agno/os/auth.py +190 -3
  12. agno/os/config.py +9 -0
  13. agno/os/interfaces/a2a/router.py +619 -9
  14. agno/os/interfaces/a2a/utils.py +31 -32
  15. agno/os/middleware/__init__.py +2 -0
  16. agno/os/middleware/jwt.py +670 -108
  17. agno/os/router.py +0 -1
  18. agno/os/routers/agents/router.py +22 -4
  19. agno/os/routers/agents/schema.py +14 -1
  20. agno/os/routers/teams/router.py +20 -4
  21. agno/os/routers/teams/schema.py +14 -1
  22. agno/os/routers/workflows/router.py +88 -9
  23. agno/os/scopes.py +469 -0
  24. agno/os/utils.py +86 -53
  25. agno/reasoning/anthropic.py +85 -1
  26. agno/reasoning/azure_ai_foundry.py +93 -1
  27. agno/reasoning/deepseek.py +91 -1
  28. agno/reasoning/gemini.py +81 -1
  29. agno/reasoning/groq.py +103 -1
  30. agno/reasoning/manager.py +1244 -0
  31. agno/reasoning/ollama.py +93 -1
  32. agno/reasoning/openai.py +113 -1
  33. agno/reasoning/vertexai.py +85 -1
  34. agno/run/agent.py +11 -0
  35. agno/run/base.py +1 -1
  36. agno/run/team.py +11 -0
  37. agno/session/team.py +0 -3
  38. agno/team/team.py +1204 -1452
  39. agno/tools/postgres.py +1 -1
  40. agno/utils/cryptography.py +22 -0
  41. agno/utils/events.py +69 -2
  42. agno/utils/hooks.py +4 -10
  43. agno/utils/print_response/agent.py +52 -2
  44. agno/utils/print_response/team.py +141 -10
  45. agno/utils/prompts.py +8 -6
  46. agno/utils/string.py +46 -0
  47. agno/utils/team.py +1 -1
  48. agno/vectordb/chroma/chromadb.py +1 -0
  49. agno/vectordb/milvus/milvus.py +32 -3
  50. agno/vectordb/redis/redisdb.py +16 -2
  51. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/METADATA +3 -2
  52. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/RECORD +55 -52
  53. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/WHEEL +0 -0
  54. {agno-2.3.12.dist-info → agno-2.3.14.dist-info}/licenses/LICENSE +0 -0
  55. {agno-2.3.12.dist-info → agno-2.3.14.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,
@@ -173,7 +172,7 @@ from agno.utils.response import (
173
172
  get_paused_content,
174
173
  )
175
174
  from agno.utils.safe_formatter import SafeFormatter
176
- from agno.utils.string import generate_id_from_name, parse_response_model_str
175
+ from agno.utils.string import generate_id_from_name, parse_response_dict_str, parse_response_model_str
177
176
  from agno.utils.timer import Timer
178
177
 
179
178
 
@@ -280,9 +279,9 @@ class Agent:
280
279
 
281
280
  # --- Agent Hooks ---
282
281
  # Functions called right after agent-session is loaded, before processing starts
283
- pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None
282
+ pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
284
283
  # Functions called after output is generated but before the response is returned
285
- post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None
284
+ post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
286
285
  # If True, run hooks as FastAPI background tasks (non-blocking). Set by AgentOS.
287
286
  _run_hooks_in_background: Optional[bool] = None
288
287
 
@@ -369,8 +368,9 @@ class Agent:
369
368
  # --- Agent Response Model Settings ---
370
369
  # Provide an input schema to validate the input
371
370
  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
371
+ # Provide a response model to get the response in the implied format.
372
+ # You can use a Pydantic model or a JSON fitting the provider's expected schema.
373
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None
374
374
  # Provide a secondary model to parse the response from the primary model
375
375
  parser_model: Optional[Model] = None
376
376
  # Provide a prompt for the parser model
@@ -487,8 +487,8 @@ class Agent:
487
487
  tool_call_limit: Optional[int] = None,
488
488
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
489
489
  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,
490
+ pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
491
+ post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
492
492
  reasoning: bool = False,
493
493
  reasoning_model: Optional[Union[Model, str]] = None,
494
494
  reasoning_agent: Optional[Agent] = None,
@@ -522,7 +522,7 @@ class Agent:
522
522
  parser_model: Optional[Union[Model, str]] = None,
523
523
  parser_model_prompt: Optional[str] = None,
524
524
  input_schema: Optional[Type[BaseModel]] = None,
525
- output_schema: Optional[Type[BaseModel]] = None,
525
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
526
526
  parse_response: bool = True,
527
527
  output_model: Optional[Union[Model, str]] = None,
528
528
  output_model_prompt: Optional[str] = None,
@@ -1041,90 +1041,91 @@ class Agent:
1041
1041
  12. Create session summary
1042
1042
  13. Cleanup and store the run response and session
1043
1043
  """
1044
+ memory_future = None
1045
+ cultural_knowledge_future = None
1044
1046
 
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
1047
+ try:
1048
+ # 1. Execute pre-hooks
1049
+ run_input = cast(RunInput, run_response.input)
1050
+ self.model = cast(Model, self.model)
1051
+ if self.pre_hooks is not None:
1052
+ # Can modify the run input
1053
+ pre_hook_iterator = self._execute_pre_hooks(
1054
+ hooks=self.pre_hooks, # type: ignore
1055
+ run_response=run_response,
1056
+ run_input=run_input,
1057
+ run_context=run_context,
1058
+ session=session,
1059
+ user_id=user_id,
1060
+ debug_mode=debug_mode,
1061
+ background_tasks=background_tasks,
1062
+ **kwargs,
1063
+ )
1064
+ # Consume the generator without yielding
1065
+ deque(pre_hook_iterator, maxlen=0)
1066
+
1067
+ # 2. Determine tools for model
1068
+ processed_tools = self.get_tools(
1052
1069
  run_response=run_response,
1053
- run_input=run_input,
1054
1070
  run_context=run_context,
1055
1071
  session=session,
1056
1072
  user_id=user_id,
1057
- debug_mode=debug_mode,
1058
- background_tasks=background_tasks,
1059
- **kwargs,
1060
1073
  )
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
- )
1074
+ _tools = self._determine_tools_for_model(
1075
+ model=self.model,
1076
+ processed_tools=processed_tools,
1077
+ run_response=run_response,
1078
+ session=session,
1079
+ run_context=run_context,
1080
+ )
1078
1081
 
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.")
1082
+ # 3. Prepare run messages
1083
+ run_messages: RunMessages = self._get_run_messages(
1084
+ run_response=run_response,
1085
+ run_context=run_context,
1086
+ input=run_input.input_content,
1087
+ session=session,
1088
+ user_id=user_id,
1089
+ audio=run_input.audios,
1090
+ images=run_input.images,
1091
+ videos=run_input.videos,
1092
+ files=run_input.files,
1093
+ add_history_to_context=add_history_to_context,
1094
+ add_dependencies_to_context=add_dependencies_to_context,
1095
+ add_session_state_to_context=add_session_state_to_context,
1096
+ tools=_tools,
1097
+ **kwargs,
1098
+ )
1099
+ if len(run_messages.messages) == 0:
1100
+ log_error("No messages to be sent to the model.")
1098
1101
 
1099
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1102
+ log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1100
1103
 
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
- )
1104
+ # Start memory creation on a separate thread (runs concurrently with the main execution loop)
1105
+ memory_future = None
1106
+ # 4. Start memory creation in background thread if memory manager is enabled and agentic memory is disabled
1107
+ if (
1108
+ run_messages.user_message is not None
1109
+ and self.memory_manager is not None
1110
+ and self.enable_user_memories
1111
+ and not self.enable_agentic_memory
1112
+ ):
1113
+ log_debug("Starting memory creation in background thread.")
1114
+ memory_future = self.background_executor.submit(
1115
+ self._make_memories, run_messages=run_messages, user_id=user_id
1116
+ )
1114
1117
 
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
- )
1118
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1119
+ if (
1120
+ run_messages.user_message is not None
1121
+ and self.culture_manager is not None
1122
+ and self.update_cultural_knowledge
1123
+ ):
1124
+ log_debug("Starting cultural knowledge creation in background thread.")
1125
+ cultural_knowledge_future = self.background_executor.submit(
1126
+ self._make_cultural_knowledge, run_messages=run_messages
1127
+ )
1126
1128
 
1127
- try:
1128
1129
  raise_if_cancelled(run_response.run_id) # type: ignore
1129
1130
 
1130
1131
  # 5. Reason about the task
@@ -1215,18 +1216,6 @@ class Agent:
1215
1216
 
1216
1217
  log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
1217
1218
 
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
1219
  return run_response
1231
1220
  finally:
1232
1221
  # Always disconnect connectable tools
@@ -1264,91 +1253,92 @@ class Agent:
1264
1253
  9. Create session summary
1265
1254
  10. Cleanup and store the run response and session
1266
1255
  """
1256
+ memory_future = None
1257
+ cultural_knowledge_future = None
1267
1258
 
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
1259
+ try:
1260
+ # 1. Execute pre-hooks
1261
+ run_input = cast(RunInput, run_response.input)
1262
+ self.model = cast(Model, self.model)
1263
+ if self.pre_hooks is not None:
1264
+ # Can modify the run input
1265
+ pre_hook_iterator = self._execute_pre_hooks(
1266
+ hooks=self.pre_hooks, # type: ignore
1267
+ run_response=run_response,
1268
+ run_input=run_input,
1269
+ run_context=run_context,
1270
+ session=session,
1271
+ user_id=user_id,
1272
+ debug_mode=debug_mode,
1273
+ stream_events=stream_events,
1274
+ background_tasks=background_tasks,
1275
+ **kwargs,
1276
+ )
1277
+ for event in pre_hook_iterator:
1278
+ yield event
1279
+
1280
+ # 2. Determine tools for model
1281
+ processed_tools = self.get_tools(
1275
1282
  run_response=run_response,
1276
- run_input=run_input,
1277
1283
  run_context=run_context,
1278
1284
  session=session,
1279
1285
  user_id=user_id,
1280
- debug_mode=debug_mode,
1281
- stream_events=stream_events,
1282
- background_tasks=background_tasks,
1283
- **kwargs,
1284
1286
  )
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
- )
1287
+ _tools = self._determine_tools_for_model(
1288
+ model=self.model,
1289
+ processed_tools=processed_tools,
1290
+ run_response=run_response,
1291
+ session=session,
1292
+ run_context=run_context,
1293
+ )
1302
1294
 
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.")
1295
+ # 3. Prepare run messages
1296
+ run_messages: RunMessages = self._get_run_messages(
1297
+ run_response=run_response,
1298
+ input=run_input.input_content,
1299
+ session=session,
1300
+ run_context=run_context,
1301
+ user_id=user_id,
1302
+ audio=run_input.audios,
1303
+ images=run_input.images,
1304
+ videos=run_input.videos,
1305
+ files=run_input.files,
1306
+ add_history_to_context=add_history_to_context,
1307
+ add_dependencies_to_context=add_dependencies_to_context,
1308
+ add_session_state_to_context=add_session_state_to_context,
1309
+ tools=_tools,
1310
+ **kwargs,
1311
+ )
1312
+ if len(run_messages.messages) == 0:
1313
+ log_error("No messages to be sent to the model.")
1322
1314
 
1323
- log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1315
+ log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1324
1316
 
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
- )
1317
+ # Start memory creation on a separate thread (runs concurrently with the main execution loop)
1318
+ memory_future = None
1319
+ # 4. Start memory creation in background thread if memory manager is enabled and agentic memory is disabled
1320
+ if (
1321
+ run_messages.user_message is not None
1322
+ and self.memory_manager is not None
1323
+ and self.enable_user_memories
1324
+ and not self.enable_agentic_memory
1325
+ ):
1326
+ log_debug("Starting memory creation in background thread.")
1327
+ memory_future = self.background_executor.submit(
1328
+ self._make_memories, run_messages=run_messages, user_id=user_id
1329
+ )
1338
1330
 
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
- )
1331
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1332
+ if (
1333
+ run_messages.user_message is not None
1334
+ and self.culture_manager is not None
1335
+ and self.update_cultural_knowledge
1336
+ ):
1337
+ log_debug("Starting cultural knowledge creation in background thread.")
1338
+ cultural_knowledge_future = self.background_executor.submit(
1339
+ self._make_cultural_knowledge, run_messages=run_messages
1340
+ )
1350
1341
 
1351
- try:
1352
1342
  # Start the Run by yielding a RunStarted event
1353
1343
  if stream_events:
1354
1344
  yield handle_event( # type: ignore
@@ -1531,28 +1521,6 @@ class Agent:
1531
1521
  self._log_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1532
1522
 
1533
1523
  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
1524
  finally:
1557
1525
  # Always disconnect connectable tools
1558
1526
  self._disconnect_connectable_tools()
@@ -1582,7 +1550,7 @@ class Agent:
1582
1550
  add_session_state_to_context: Optional[bool] = None,
1583
1551
  dependencies: Optional[Dict[str, Any]] = None,
1584
1552
  metadata: Optional[Dict[str, Any]] = None,
1585
- output_schema: Optional[Type[BaseModel]] = None,
1553
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
1586
1554
  debug_mode: Optional[bool] = None,
1587
1555
  **kwargs: Any,
1588
1556
  ) -> RunOutput: ...
@@ -1610,7 +1578,7 @@ class Agent:
1610
1578
  add_session_state_to_context: Optional[bool] = None,
1611
1579
  dependencies: Optional[Dict[str, Any]] = None,
1612
1580
  metadata: Optional[Dict[str, Any]] = None,
1613
- output_schema: Optional[Type[BaseModel]] = None,
1581
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
1614
1582
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
1615
1583
  yield_run_output: bool = False,
1616
1584
  debug_mode: Optional[bool] = None,
@@ -1639,7 +1607,7 @@ class Agent:
1639
1607
  add_session_state_to_context: Optional[bool] = None,
1640
1608
  dependencies: Optional[Dict[str, Any]] = None,
1641
1609
  metadata: Optional[Dict[str, Any]] = None,
1642
- output_schema: Optional[Type[BaseModel]] = None,
1610
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
1643
1611
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
1644
1612
  yield_run_output: Optional[bool] = None,
1645
1613
  debug_mode: Optional[bool] = None,
@@ -1651,9 +1619,10 @@ class Agent:
1651
1619
  "`run` method is not supported with an async database. Please use `arun` method instead."
1652
1620
  )
1653
1621
 
1654
- # Set the id for the run and register it immediately for cancellation tracking
1622
+ # Initialize session early for error handling
1623
+ session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
1624
+ # Set the id for the run
1655
1625
  run_id = run_id or str(uuid4())
1656
- register_run(run_id)
1657
1626
 
1658
1627
  if (add_history_to_context or self.add_history_to_context) and not self.db and not self.team_id:
1659
1628
  log_warning(
@@ -1667,82 +1636,84 @@ class Agent:
1667
1636
  stacklevel=2,
1668
1637
  )
1669
1638
 
1670
- background_tasks = kwargs.pop("background_tasks", None)
1671
- if background_tasks is not None:
1672
- from fastapi import BackgroundTasks
1639
+ # Set up retry logic
1640
+ num_attempts = self.retries + 1
1641
+ for attempt in range(num_attempts):
1642
+ if num_attempts > 1:
1643
+ log_debug(f"Retrying Agent run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
1673
1644
 
1674
- background_tasks: BackgroundTasks = background_tasks # type: ignore
1645
+ try:
1646
+ # Register run for cancellation tracking
1647
+ register_run(run_id)
1675
1648
 
1676
- # Validate input against input_schema if provided
1677
- validated_input = self._validate_input(input)
1649
+ background_tasks = kwargs.pop("background_tasks", None)
1650
+ if background_tasks is not None:
1651
+ from fastapi import BackgroundTasks
1678
1652
 
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
1653
+ background_tasks: BackgroundTasks = background_tasks # type: ignore
1686
1654
 
1687
- session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
1655
+ # Validate input against input_schema if provided
1656
+ validated_input = self._validate_input(input)
1688
1657
 
1689
- # Initialize the Agent
1690
- self.initialize_agent(debug_mode=debug_mode)
1658
+ # Normalise hook & guardails
1659
+ if not self._hooks_normalised:
1660
+ if self.pre_hooks:
1661
+ self.pre_hooks = normalize_pre_hooks(self.pre_hooks) # type: ignore
1662
+ if self.post_hooks:
1663
+ self.post_hooks = normalize_post_hooks(self.post_hooks) # type: ignore
1664
+ self._hooks_normalised = True
1691
1665
 
1692
- image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
1693
- images=images, videos=videos, audios=audio, files=files
1694
- )
1666
+ session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
1695
1667
 
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
- )
1668
+ # Initialize the Agent
1669
+ self.initialize_agent(debug_mode=debug_mode)
1704
1670
 
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)
1671
+ image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
1672
+ images=images, videos=videos, audios=audio, files=files
1673
+ )
1708
1674
 
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)
1675
+ # Create RunInput to capture the original user input
1676
+ run_input = RunInput(
1677
+ input_content=validated_input,
1678
+ images=image_artifacts,
1679
+ videos=video_artifacts,
1680
+ audios=audio_artifacts,
1681
+ files=file_artifacts,
1682
+ )
1718
1683
 
1719
- # Determine runtime dependencies
1720
- dependencies = dependencies if dependencies is not None else self.dependencies
1684
+ # Read existing session from database
1685
+ agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
1686
+ self._update_metadata(session=agent_session)
1721
1687
 
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
1688
+ # Initialize session state
1689
+ session_state = self._initialize_session_state(
1690
+ session_state=session_state if session_state is not None else {},
1691
+ user_id=user_id,
1692
+ session_id=session_id,
1693
+ run_id=run_id,
1694
+ )
1695
+ # Update session state from DB
1696
+ session_state = self._load_session_state(session=agent_session, session_state=session_state)
1725
1697
 
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
1698
+ # Determine runtime dependencies
1699
+ dependencies = dependencies if dependencies is not None else self.dependencies
1737
1700
 
1738
- # Set up retry logic
1739
- num_attempts = self.retries + 1
1701
+ # Resolve output_schema parameter takes precedence, then fall back to self.output_schema
1702
+ if output_schema is None:
1703
+ output_schema = self.output_schema
1740
1704
 
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}...")
1705
+ # Initialize run context
1706
+ run_context = run_context or RunContext(
1707
+ run_id=run_id,
1708
+ session_id=session_id,
1709
+ user_id=user_id,
1710
+ session_state=session_state,
1711
+ dependencies=dependencies,
1712
+ output_schema=output_schema,
1713
+ )
1714
+ # output_schema parameter takes priority, even if run_context was provided
1715
+ run_context.output_schema = output_schema
1744
1716
 
1745
- try:
1746
1717
  # Resolve dependencies
1747
1718
  if run_context.dependencies is not None:
1748
1719
  self._resolve_run_dependencies(run_context=run_context)
@@ -1849,23 +1820,64 @@ class Agent:
1849
1820
  )
1850
1821
  return response
1851
1822
  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"
1823
+ # Handle exceptions during streaming
1824
+ run_response.status = RunStatus.error
1825
+ # Add error event to list of events
1826
+ run_error = create_run_error_event(
1827
+ run_response,
1828
+ error=str(e),
1829
+ error_id=e.error_id,
1830
+ error_type=e.type,
1831
+ additional_data=e.additional_data,
1832
+ )
1833
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
1834
+
1835
+ # If the content is None, set it to the error message
1836
+ if run_response.content is None:
1837
+ run_response.content = str(e)
1838
+
1839
+ log_error(f"Validation failed: {str(e)} | Check trigger: {e.check_trigger}")
1840
+
1841
+ self._cleanup_and_store(
1842
+ run_response=run_response, session=agent_session, run_context=run_context, user_id=user_id
1843
+ )
1844
+
1845
+ if stream:
1846
+ return generator_wrapper(run_error) # type: ignore
1847
+ else:
1848
+ return run_response
1849
+ except RunCancelledException as e:
1850
+ # Handle run cancellation during streaming
1851
+ log_info(f"Run {run_response.run_id} was cancelled during streaming")
1852
+ run_response.content = str(e)
1856
1853
  run_response.status = RunStatus.cancelled
1854
+ cancelled_run_error = handle_event(
1855
+ create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
1856
+ run_response,
1857
+ events_to_skip=self.events_to_skip, # type: ignore
1858
+ store_events=self.store_events,
1859
+ )
1860
+
1861
+ # Cleanup and store the run response and session
1862
+ self._cleanup_and_store(
1863
+ run_response=run_response, session=agent_session, run_context=run_context, user_id=user_id
1864
+ )
1857
1865
 
1866
+ if stream:
1867
+ return generator_wrapper(cancelled_run_error) # type: ignore
1868
+ else:
1869
+ return run_response
1870
+ except KeyboardInterrupt:
1858
1871
  if stream:
1859
1872
  return generator_wrapper( # type: ignore
1860
- create_run_cancelled_event(
1861
- from_run_response=run_response,
1862
- reason="Operation cancelled by user",
1863
- )
1873
+ create_run_cancelled_event(run_response, "Operation cancelled by user") # type: ignore
1864
1874
  )
1865
1875
  else:
1866
- return run_response
1876
+ run_response.content = "Operation cancelled by user" # type: ignore
1877
+ run_response.status = RunStatus.cancelled # type: ignore
1878
+ return run_response # type: ignore
1879
+
1867
1880
  except Exception as e:
1868
- # Check if this is the last attempt
1869
1881
  if attempt < num_attempts - 1:
1870
1882
  # Calculate delay with exponential backoff if enabled
1871
1883
  if self.exponential_backoff:
@@ -1876,12 +1888,27 @@ class Agent:
1876
1888
  log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
1877
1889
  time.sleep(delay)
1878
1890
  continue
1891
+
1892
+ run_response.status = RunStatus.error
1893
+ # Add error event to list of events
1894
+ run_error = create_run_error_event(run_response, error=str(e))
1895
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
1896
+
1897
+ # If the content is None, set it to the error message
1898
+ if run_response.content is None:
1899
+ run_response.content = str(e)
1900
+
1901
+ log_error(f"Error in Agent run: {str(e)}")
1902
+
1903
+ # Cleanup and store the run response and session
1904
+ self._cleanup_and_store(
1905
+ run_response=run_response, session=agent_session, run_context=run_context, user_id=user_id
1906
+ )
1907
+
1908
+ if stream:
1909
+ return generator_wrapper(run_error) # type: ignore
1879
1910
  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
1911
+ return run_response
1885
1912
 
1886
1913
  # If we get here, all retries failed (shouldn't happen with current logic)
1887
1914
  raise Exception(f"Failed after {num_attempts} attempts.")
@@ -1922,246 +1949,321 @@ class Agent:
1922
1949
  """
1923
1950
  log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
1924
1951
 
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)
1952
+ cultural_knowledge_task = None
1953
+ memory_task = None
1927
1954
 
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
- )
1955
+ # Set up retry logic
1956
+ num_attempts = self.retries + 1
1942
1957
 
1943
- # 3. Resolve dependencies
1944
- if run_context.dependencies is not None:
1945
- await self._aresolve_run_dependencies(run_context=run_context)
1958
+ for attempt in range(num_attempts):
1959
+ if num_attempts > 1:
1960
+ log_debug(f"Retrying Agent run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
1946
1961
 
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
1962
+ try:
1963
+ # 1. Read or create session. Reads from the database if provided.
1964
+ agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
1965
+
1966
+ # 2. Update metadata and session state
1967
+ self._update_metadata(session=agent_session)
1968
+ # Initialize session state
1969
+ run_context.session_state = self._initialize_session_state(
1970
+ session_state=run_context.session_state if run_context.session_state is not None else {},
1971
+ user_id=user_id,
1972
+ session_id=session_id,
1973
+ run_id=run_response.run_id,
1974
+ )
1975
+ # Update session state from DB
1976
+ if run_context.session_state is not None:
1977
+ run_context.session_state = self._load_session_state(
1978
+ session=agent_session, session_state=run_context.session_state
1979
+ )
1966
1980
 
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
- )
1981
+ # 3. Resolve dependencies
1982
+ if run_context.dependencies is not None:
1983
+ await self._aresolve_run_dependencies(run_context=run_context)
1975
1984
 
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
- )
1985
+ # 4. Execute pre-hooks
1986
+ run_input = cast(RunInput, run_response.input)
1987
+ self.model = cast(Model, self.model)
1988
+ if self.pre_hooks is not None:
1989
+ # Can modify the run input
1990
+ pre_hook_iterator = self._aexecute_pre_hooks(
1991
+ hooks=self.pre_hooks, # type: ignore
1992
+ run_response=run_response,
1993
+ run_context=run_context,
1994
+ run_input=run_input,
1995
+ session=agent_session,
1996
+ user_id=user_id,
1997
+ debug_mode=debug_mode,
1998
+ background_tasks=background_tasks,
1999
+ **kwargs,
2000
+ )
2001
+ # Consume the async iterator without yielding
2002
+ async for _ in pre_hook_iterator:
2003
+ pass
1983
2004
 
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.")
2005
+ # 5. Determine tools for model
2006
+ self.model = cast(Model, self.model)
2007
+ processed_tools = await self.aget_tools(
2008
+ run_response=run_response,
2009
+ run_context=run_context,
2010
+ session=agent_session,
2011
+ user_id=user_id,
2012
+ )
2003
2013
 
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))
2014
+ _tools = self._determine_tools_for_model(
2015
+ model=self.model,
2016
+ processed_tools=processed_tools,
2017
+ run_response=run_response,
2018
+ run_context=run_context,
2019
+ session=agent_session,
2020
+ )
2014
2021
 
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))
2022
+ # 6. Prepare run messages
2023
+ run_messages: RunMessages = await self._aget_run_messages(
2024
+ run_response=run_response,
2025
+ run_context=run_context,
2026
+ input=run_input.input_content,
2027
+ session=agent_session,
2028
+ user_id=user_id,
2029
+ audio=run_input.audios,
2030
+ images=run_input.images,
2031
+ videos=run_input.videos,
2032
+ files=run_input.files,
2033
+ add_history_to_context=add_history_to_context,
2034
+ add_dependencies_to_context=add_dependencies_to_context,
2035
+ add_session_state_to_context=add_session_state_to_context,
2036
+ tools=_tools,
2037
+ **kwargs,
2038
+ )
2039
+ if len(run_messages.messages) == 0:
2040
+ log_error("No messages to be sent to the model.")
2024
2041
 
2025
- try:
2026
- # Check for cancellation before model call
2027
- raise_if_cancelled(run_response.run_id) # type: ignore
2042
+ # 7. Start memory creation as a background task (runs concurrently with the main execution)
2043
+ memory_task = None
2044
+ if (
2045
+ run_messages.user_message is not None
2046
+ and self.memory_manager is not None
2047
+ and self.enable_user_memories
2048
+ and not self.enable_agentic_memory
2049
+ ):
2050
+ log_debug("Starting memory creation in background task.")
2051
+ memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2028
2052
 
2029
- # 8. Reason about the task if reasoning is enabled
2030
- await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2053
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2054
+ if (
2055
+ run_messages.user_message is not None
2056
+ and self.culture_manager is not None
2057
+ and self.update_cultural_knowledge
2058
+ ):
2059
+ log_debug("Starting cultural knowledge creation in background thread.")
2060
+ cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2031
2061
 
2032
- # Check for cancellation before model call
2033
- raise_if_cancelled(run_response.run_id) # type: ignore
2062
+ # Check for cancellation before model call
2063
+ raise_if_cancelled(run_response.run_id) # type: ignore
2034
2064
 
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
- )
2065
+ # 8. Reason about the task if reasoning is enabled
2066
+ await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2046
2067
 
2047
- # Check for cancellation after model call
2048
- raise_if_cancelled(run_response.run_id) # type: ignore
2068
+ # Check for cancellation before model call
2069
+ raise_if_cancelled(run_response.run_id) # type: ignore
2049
2070
 
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)
2071
+ # 9. Generate a response from the Model (includes running function calls)
2072
+ model_response: ModelResponse = await self.model.aresponse(
2073
+ messages=run_messages.messages,
2074
+ tools=_tools,
2075
+ tool_choice=self.tool_choice,
2076
+ tool_call_limit=self.tool_call_limit,
2077
+ response_format=response_format,
2078
+ send_media_to_model=self.send_media_to_model,
2079
+ run_response=run_response,
2080
+ compression_manager=self.compression_manager if self.compress_tool_results else None,
2081
+ )
2052
2082
 
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
- )
2083
+ # Check for cancellation after model call
2084
+ raise_if_cancelled(run_response.run_id) # type: ignore
2057
2085
 
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
- )
2086
+ # If an output model is provided, generate output using the output model
2087
+ await self._agenerate_response_with_output_model(
2088
+ model_response=model_response, run_messages=run_messages
2089
+ )
2065
2090
 
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
2091
+ # If a parser model is provided, structure the response separately
2092
+ await self._aparse_response_with_parser_model(
2093
+ model_response=model_response, run_messages=run_messages, run_context=run_context
2071
2094
  )
2072
2095
 
2073
- # 11. Convert the response to the structured format if needed
2074
- self._convert_response_to_structured_format(run_response, run_context=run_context)
2096
+ # 10. Update the RunOutput with the model response
2097
+ self._update_run_response(
2098
+ model_response=model_response,
2099
+ run_response=run_response,
2100
+ run_messages=run_messages,
2101
+ run_context=run_context,
2102
+ )
2075
2103
 
2076
- # 12. Store media if enabled
2077
- if self.store_media:
2078
- store_media_util(run_response, model_response)
2104
+ # We should break out of the run function
2105
+ if any(tool_call.is_paused for tool_call in run_response.tools or []):
2106
+ await await_for_open_threads(
2107
+ memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task
2108
+ )
2109
+ return await self._ahandle_agent_run_paused(
2110
+ run_response=run_response, session=agent_session, user_id=user_id
2111
+ )
2079
2112
 
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
2113
+ # 11. Convert the response to the structured format if needed
2114
+ self._convert_response_to_structured_format(run_response, run_context=run_context)
2093
2115
 
2094
- # Check for cancellation
2095
- raise_if_cancelled(run_response.run_id) # type: ignore
2116
+ # 12. Store media if enabled
2117
+ if self.store_media:
2118
+ store_media_util(run_response, model_response)
2096
2119
 
2097
- # 14. Wait for background memory creation
2098
- await await_for_open_threads(memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task)
2120
+ # 13. Execute post-hooks (after output is generated but before response is returned)
2121
+ if self.post_hooks is not None:
2122
+ async for _ in self._aexecute_post_hooks(
2123
+ hooks=self.post_hooks, # type: ignore
2124
+ run_output=run_response,
2125
+ run_context=run_context,
2126
+ session=agent_session,
2127
+ user_id=user_id,
2128
+ debug_mode=debug_mode,
2129
+ background_tasks=background_tasks,
2130
+ **kwargs,
2131
+ ):
2132
+ pass
2099
2133
 
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)}")
2134
+ # Check for cancellation
2135
+ raise_if_cancelled(run_response.run_id) # type: ignore
2108
2136
 
2109
- run_response.status = RunStatus.completed
2137
+ # 14. Wait for background memory creation
2138
+ await await_for_open_threads(memory_task=memory_task, cultural_knowledge_task=cultural_knowledge_task)
2110
2139
 
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
- )
2140
+ # 15. Create session summary
2141
+ if self.session_summary_manager is not None and self.enable_session_summaries:
2142
+ # Upsert the RunOutput to Agent Session before creating the session summary
2143
+ agent_session.upsert_run(run=run_response)
2144
+ try:
2145
+ await self.session_summary_manager.acreate_session_summary(session=agent_session)
2146
+ except Exception as e:
2147
+ log_warning(f"Error in session summary creation: {str(e)}")
2118
2148
 
2119
- # Log Agent Telemetry
2120
- await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2149
+ run_response.status = RunStatus.completed
2121
2150
 
2122
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2151
+ # 16. Cleanup and store the run response and session
2152
+ await self._acleanup_and_store(
2153
+ run_response=run_response,
2154
+ session=agent_session,
2155
+ run_context=run_context,
2156
+ user_id=user_id,
2157
+ )
2123
2158
 
2124
- return run_response
2159
+ # Log Agent Telemetry
2160
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2125
2161
 
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
2162
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2131
2163
 
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
- )
2164
+ return run_response
2139
2165
 
2140
- return run_response
2166
+ except RunCancelledException as e:
2167
+ # Handle run cancellation
2168
+ log_info(f"Run {run_response.run_id} was cancelled")
2169
+ run_response.content = str(e)
2170
+ run_response.status = RunStatus.cancelled
2141
2171
 
2142
- finally:
2143
- # Always disconnect connectable tools
2144
- self._disconnect_connectable_tools()
2145
- # Always disconnect MCP tools
2146
- await self._disconnect_mcp_tools()
2172
+ # Cleanup and store the run response and session
2173
+ await self._acleanup_and_store(
2174
+ run_response=run_response,
2175
+ session=agent_session,
2176
+ run_context=run_context,
2177
+ user_id=user_id,
2178
+ )
2147
2179
 
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
2180
+ return run_response
2181
+ except (InputCheckError, OutputCheckError) as e:
2182
+ # Handle exceptions during streaming
2183
+ run_response.status = RunStatus.error
2184
+ # Add error event to list of events
2185
+ run_error = create_run_error_event(
2186
+ run_response,
2187
+ error=str(e),
2188
+ error_id=e.error_id,
2189
+ error_type=e.type,
2190
+ additional_data=e.additional_data,
2191
+ )
2192
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2162
2193
 
2163
- # Always clean up the run tracking
2164
- cleanup_run(run_response.run_id) # type: ignore
2194
+ # If the content is None, set it to the error message
2195
+ if run_response.content is None:
2196
+ run_response.content = str(e)
2197
+
2198
+ log_error(f"Validation failed: {str(e)} | Check trigger: {e.check_trigger}")
2199
+
2200
+ await self._acleanup_and_store(
2201
+ run_response=run_response,
2202
+ session=agent_session,
2203
+ run_context=run_context,
2204
+ user_id=user_id,
2205
+ )
2206
+
2207
+ return run_response
2208
+ except Exception as e:
2209
+ # Check if this is the last attempt
2210
+ if attempt < num_attempts - 1:
2211
+ # Calculate delay with exponential backoff if enabled
2212
+ if self.exponential_backoff:
2213
+ delay = self.delay_between_retries * (2**attempt)
2214
+ else:
2215
+ delay = self.delay_between_retries
2216
+
2217
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2218
+ time.sleep(delay)
2219
+ continue
2220
+
2221
+ run_response.status = RunStatus.error
2222
+ # Add error event to list of events
2223
+ run_error = create_run_error_event(run_response, error=str(e))
2224
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2225
+
2226
+ # If the content is None, set it to the error message
2227
+ if run_response.content is None:
2228
+ run_response.content = str(e)
2229
+
2230
+ log_error(f"Error in Agent run: {str(e)}")
2231
+
2232
+ # Cleanup and store the run response and session
2233
+ await self._acleanup_and_store(
2234
+ run_response=run_response,
2235
+ session=agent_session,
2236
+ run_context=run_context,
2237
+ user_id=user_id,
2238
+ )
2239
+
2240
+ return run_response
2241
+
2242
+ finally:
2243
+ # Always disconnect connectable tools
2244
+ self._disconnect_connectable_tools()
2245
+ # Always disconnect MCP tools
2246
+ await self._disconnect_mcp_tools()
2247
+
2248
+ # Cancel the memory task if it's still running
2249
+ if memory_task is not None and not memory_task.done():
2250
+ memory_task.cancel()
2251
+ try:
2252
+ await memory_task
2253
+ except CancelledError:
2254
+ pass
2255
+ # Cancel the cultural knowledge task if it's still running
2256
+ if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2257
+ cultural_knowledge_task.cancel()
2258
+ try:
2259
+ await cultural_knowledge_task
2260
+ except CancelledError:
2261
+ pass
2262
+
2263
+ # Always clean up the run tracking
2264
+ cleanup_run(run_response.run_id) # type: ignore
2265
+
2266
+ return run_response
2165
2267
 
2166
2268
  async def _arun_stream(
2167
2269
  self,
@@ -2198,344 +2300,422 @@ class Agent:
2198
2300
  """
2199
2301
  log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
2200
2302
 
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
- )
2303
+ memory_task = None
2304
+ cultural_knowledge_task = None
2209
2305
 
2210
2306
  # 1. Read or create session. Reads from the database if provided.
2211
- agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2212
-
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
2307
+ agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2251
2308
 
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
- )
2309
+ # Set up retry logic
2310
+ num_attempts = self.retries + 1
2260
2311
 
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
- )
2312
+ for attempt in range(num_attempts):
2313
+ if num_attempts > 1:
2314
+ log_debug(f"Retrying Agent run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2268
2315
 
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.")
2316
+ try:
2317
+ # Start the Run by yielding a RunStarted event
2318
+ if stream_events:
2319
+ yield handle_event( # type: ignore
2320
+ create_run_started_event(run_response),
2321
+ run_response,
2322
+ events_to_skip=self.events_to_skip, # type: ignore
2323
+ store_events=self.store_events,
2324
+ )
2288
2325
 
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))
2326
+ # 2. Update metadata and session state
2327
+ self._update_metadata(session=agent_session)
2328
+ # Initialize session state
2329
+ run_context.session_state = self._initialize_session_state(
2330
+ session_state=run_context.session_state if run_context.session_state is not None else {},
2331
+ user_id=user_id,
2332
+ session_id=session_id,
2333
+ run_id=run_response.run_id,
2334
+ )
2335
+ # Update session state from DB
2336
+ if run_context.session_state is not None:
2337
+ run_context.session_state = self._load_session_state(
2338
+ session=agent_session, session_state=run_context.session_state
2339
+ )
2299
2340
 
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))
2341
+ # 3. Resolve dependencies
2342
+ if run_context.dependencies is not None:
2343
+ await self._aresolve_run_dependencies(run_context=run_context)
2309
2344
 
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
2345
+ # 4. Execute pre-hooks
2346
+ run_input = cast(RunInput, run_response.input)
2347
+ self.model = cast(Model, self.model)
2348
+ if self.pre_hooks is not None:
2349
+ pre_hook_iterator = self._aexecute_pre_hooks(
2350
+ hooks=self.pre_hooks, # type: ignore
2351
+ run_response=run_response,
2352
+ run_context=run_context,
2353
+ run_input=run_input,
2354
+ session=agent_session,
2355
+ user_id=user_id,
2356
+ debug_mode=debug_mode,
2357
+ stream_events=stream_events,
2358
+ background_tasks=background_tasks,
2359
+ **kwargs,
2360
+ )
2361
+ async for event in pre_hook_iterator:
2362
+ yield event
2319
2363
 
2320
- raise_if_cancelled(run_response.run_id) # type: ignore
2364
+ # 5. Determine tools for model
2365
+ self.model = cast(Model, self.model)
2366
+ processed_tools = await self.aget_tools(
2367
+ run_response=run_response,
2368
+ run_context=run_context,
2369
+ session=agent_session,
2370
+ user_id=user_id,
2371
+ )
2321
2372
 
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(
2373
+ _tools = self._determine_tools_for_model(
2374
+ model=self.model,
2375
+ processed_tools=processed_tools,
2376
+ run_response=run_response,
2377
+ run_context=run_context,
2325
2378
  session=agent_session,
2379
+ )
2380
+
2381
+ # 6. Prepare run messages
2382
+ run_messages: RunMessages = await self._aget_run_messages(
2326
2383
  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
2384
  run_context=run_context,
2385
+ input=run_input.input_content,
2386
+ session=agent_session,
2387
+ user_id=user_id,
2388
+ audio=run_input.audios,
2389
+ images=run_input.images,
2390
+ videos=run_input.videos,
2391
+ files=run_input.files,
2392
+ add_history_to_context=add_history_to_context,
2393
+ add_dependencies_to_context=add_dependencies_to_context,
2394
+ add_session_state_to_context=add_session_state_to_context,
2395
+ tools=_tools,
2396
+ **kwargs,
2397
+ )
2398
+ if len(run_messages.messages) == 0:
2399
+ log_error("No messages to be sent to the model.")
2400
+
2401
+ # 7. Start memory creation as a background task (runs concurrently with the main execution)
2402
+ memory_task = None
2403
+ if (
2404
+ run_messages.user_message is not None
2405
+ and self.memory_manager is not None
2406
+ and self.enable_user_memories
2407
+ and not self.enable_agentic_memory
2333
2408
  ):
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
2409
+ log_debug("Starting memory creation in background task.")
2410
+ memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2341
2411
 
2342
- async for event in self._ahandle_model_response_stream(
2343
- session=agent_session,
2412
+ # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2413
+ if (
2414
+ run_messages.user_message is not None
2415
+ and self.culture_manager is not None
2416
+ and self.update_cultural_knowledge
2417
+ ):
2418
+ log_debug("Starting cultural knowledge creation in background task.")
2419
+ cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2420
+
2421
+ # 8. Reason about the task if reasoning is enabled
2422
+ async for item in self._ahandle_reasoning_stream(
2344
2423
  run_response=run_response,
2345
2424
  run_messages=run_messages,
2346
- tools=_tools,
2347
- response_format=response_format,
2348
2425
  stream_events=stream_events,
2349
- session_state=run_context.session_state,
2350
- run_context=run_context,
2351
2426
  ):
2352
2427
  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:
2428
+ yield item
2429
+
2430
+ raise_if_cancelled(run_response.run_id) # type: ignore
2431
+
2432
+ # 9. Generate a response from the Model
2433
+ if self.output_model is None:
2434
+ async for event in self._ahandle_model_response_stream(
2435
+ session=agent_session,
2436
+ run_response=run_response,
2437
+ run_messages=run_messages,
2438
+ tools=_tools,
2439
+ response_format=response_format,
2440
+ stream_events=stream_events,
2441
+ session_state=run_context.session_state,
2442
+ run_context=run_context,
2443
+ ):
2444
+ raise_if_cancelled(run_response.run_id) # type: ignore
2360
2445
  yield event
2446
+ else:
2447
+ from agno.run.agent import (
2448
+ IntermediateRunContentEvent,
2449
+ RunContentEvent,
2450
+ ) # type: ignore
2361
2451
 
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(
2452
+ async for event in self._ahandle_model_response_stream(
2453
+ session=agent_session,
2454
+ run_response=run_response,
2455
+ run_messages=run_messages,
2456
+ tools=_tools,
2457
+ response_format=response_format,
2458
+ stream_events=stream_events,
2459
+ session_state=run_context.session_state,
2460
+ run_context=run_context,
2461
+ ):
2462
+ raise_if_cancelled(run_response.run_id) # type: ignore
2463
+ if isinstance(event, RunContentEvent):
2464
+ if stream_events:
2465
+ yield IntermediateRunContentEvent(
2466
+ content=event.content,
2467
+ content_type=event.content_type,
2468
+ )
2469
+ else:
2470
+ yield event
2471
+
2472
+ # If an output model is provided, generate output using the output model
2473
+ async for event in self._agenerate_response_with_output_model_stream(
2474
+ session=agent_session,
2475
+ run_response=run_response,
2476
+ run_messages=run_messages,
2477
+ stream_events=stream_events,
2478
+ ):
2479
+ raise_if_cancelled(run_response.run_id) # type: ignore
2480
+ yield event
2481
+
2482
+ # Check for cancellation after model processing
2483
+ raise_if_cancelled(run_response.run_id) # type: ignore
2484
+
2485
+ # 10. Parse response with parser model if provided
2486
+ async for event in self._aparse_response_with_parser_model_stream(
2364
2487
  session=agent_session,
2365
2488
  run_response=run_response,
2366
- run_messages=run_messages,
2367
2489
  stream_events=stream_events,
2490
+ run_context=run_context,
2368
2491
  ):
2369
- raise_if_cancelled(run_response.run_id) # type: ignore
2370
2492
  yield event
2371
2493
 
2372
- # Check for cancellation after model processing
2373
- raise_if_cancelled(run_response.run_id) # type: ignore
2494
+ if stream_events:
2495
+ yield handle_event( # type: ignore
2496
+ create_run_content_completed_event(from_run_response=run_response),
2497
+ run_response,
2498
+ events_to_skip=self.events_to_skip, # type: ignore
2499
+ store_events=self.store_events,
2500
+ )
2374
2501
 
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
2502
+ # Break out of the run function if a tool call is paused
2503
+ if any(tool_call.is_paused for tool_call in run_response.tools or []):
2504
+ async for item in await_for_thread_tasks_stream(
2505
+ memory_task=memory_task,
2506
+ cultural_knowledge_task=cultural_knowledge_task,
2507
+ stream_events=stream_events,
2508
+ run_response=run_response,
2509
+ ):
2510
+ yield item
2380
2511
 
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
- )
2512
+ async for item in self._ahandle_agent_run_paused_stream(
2513
+ run_response=run_response, session=agent_session, user_id=user_id
2514
+ ):
2515
+ yield item
2516
+ return
2517
+
2518
+ # Execute post-hooks (after output is generated but before response is returned)
2519
+ if self.post_hooks is not None:
2520
+ async for event in self._aexecute_post_hooks(
2521
+ hooks=self.post_hooks, # type: ignore
2522
+ run_output=run_response,
2523
+ run_context=run_context,
2524
+ session=agent_session,
2525
+ user_id=user_id,
2526
+ debug_mode=debug_mode,
2527
+ stream_events=stream_events,
2528
+ background_tasks=background_tasks,
2529
+ **kwargs,
2530
+ ):
2531
+ yield event
2388
2532
 
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 []):
2533
+ # 11. Wait for background memory creation
2391
2534
  async for item in await_for_thread_tasks_stream(
2392
2535
  memory_task=memory_task,
2393
2536
  cultural_knowledge_task=cultural_knowledge_task,
2394
2537
  stream_events=stream_events,
2395
2538
  run_response=run_response,
2539
+ events_to_skip=self.events_to_skip,
2540
+ store_events=self.store_events,
2396
2541
  ):
2397
2542
  yield item
2398
2543
 
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
2544
+ # 12. Create session summary
2545
+ if self.session_summary_manager is not None and self.enable_session_summaries:
2546
+ # Upsert the RunOutput to Agent Session before creating the session summary
2547
+ agent_session.upsert_run(run=run_response)
2404
2548
 
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,
2549
+ if stream_events:
2550
+ yield handle_event( # type: ignore
2551
+ create_session_summary_started_event(from_run_response=run_response),
2552
+ run_response,
2553
+ events_to_skip=self.events_to_skip, # type: ignore
2554
+ store_events=self.store_events,
2555
+ )
2556
+ try:
2557
+ await self.session_summary_manager.acreate_session_summary(session=agent_session)
2558
+ except Exception as e:
2559
+ log_warning(f"Error in session summary creation: {str(e)}")
2560
+ if stream_events:
2561
+ yield handle_event( # type: ignore
2562
+ create_session_summary_completed_event(
2563
+ from_run_response=run_response, session_summary=agent_session.summary
2564
+ ),
2565
+ run_response,
2566
+ events_to_skip=self.events_to_skip, # type: ignore
2567
+ store_events=self.store_events,
2568
+ )
2569
+
2570
+ # Update run_response.session_state before creating RunCompletedEvent
2571
+ # This ensures the event has the final state after all tool modifications
2572
+ if agent_session.session_data is not None and "session_state" in agent_session.session_data:
2573
+ run_response.session_state = agent_session.session_data["session_state"]
2574
+
2575
+ # Create the run completed event
2576
+ completed_event = handle_event(
2577
+ create_run_completed_event(from_run_response=run_response),
2578
+ run_response,
2579
+ events_to_skip=self.events_to_skip, # type: ignore
2580
+ store_events=self.store_events,
2581
+ )
2582
+
2583
+ # Set the run status to completed
2584
+ run_response.status = RunStatus.completed
2585
+
2586
+ # 13. Cleanup and store the run response and session
2587
+ await self._acleanup_and_store(
2588
+ run_response=run_response,
2411
2589
  session=agent_session,
2590
+ run_context=run_context,
2412
2591
  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
2592
+ )
2419
2593
 
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
2594
+ if stream_events:
2595
+ yield completed_event # type: ignore
2430
2596
 
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)
2597
+ if yield_run_output:
2598
+ yield run_response
2435
2599
 
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
- )
2600
+ # Log Agent Telemetry
2601
+ await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2456
2602
 
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"]
2603
+ log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2461
2604
 
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
- )
2605
+ except RunCancelledException as e:
2606
+ # Handle run cancellation during async streaming
2607
+ log_info(f"Run {run_response.run_id} was cancelled during async streaming")
2608
+ run_response.status = RunStatus.cancelled
2609
+ # Don't overwrite content - preserve any partial content that was streamed
2610
+ # Only set content if it's empty
2611
+ if not run_response.content:
2612
+ run_response.content = str(e)
2469
2613
 
2470
- # Set the run status to completed
2471
- run_response.status = RunStatus.completed
2614
+ # Yield the cancellation event
2615
+ yield handle_event( # type: ignore
2616
+ create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2617
+ run_response,
2618
+ events_to_skip=self.events_to_skip, # type: ignore
2619
+ store_events=self.store_events,
2620
+ )
2472
2621
 
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
- )
2622
+ # Cleanup and store the run response and session
2623
+ await self._acleanup_and_store(
2624
+ run_response=run_response,
2625
+ session=agent_session,
2626
+ run_context=run_context,
2627
+ user_id=user_id,
2628
+ )
2480
2629
 
2481
- if stream_events:
2482
- yield completed_event # type: ignore
2630
+ except (InputCheckError, OutputCheckError) as e:
2631
+ # Handle exceptions during async streaming
2632
+ run_response.status = RunStatus.error
2633
+ # Add error event to list of events
2634
+ run_error = create_run_error_event(
2635
+ run_response,
2636
+ error=str(e),
2637
+ error_id=e.error_id,
2638
+ error_type=e.type,
2639
+ additional_data=e.additional_data,
2640
+ )
2641
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2483
2642
 
2484
- if yield_run_output:
2485
- yield run_response
2643
+ # If the content is None, set it to the error message
2644
+ if run_response.content is None:
2645
+ run_response.content = str(e)
2486
2646
 
2487
- # Log Agent Telemetry
2488
- await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
2647
+ log_error(f"Validation failed: {str(e)} | Check trigger: {e.check_trigger}")
2489
2648
 
2490
- log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
2649
+ # Cleanup and store the run response and session
2650
+ await self._acleanup_and_store(
2651
+ run_response=run_response,
2652
+ session=agent_session,
2653
+ run_context=run_context,
2654
+ user_id=user_id,
2655
+ )
2491
2656
 
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)
2657
+ # Yield the error event
2658
+ yield run_error
2659
+ break
2500
2660
 
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
- )
2661
+ except Exception as e:
2662
+ # Check if this is the last attempt
2663
+ if attempt < num_attempts - 1:
2664
+ # Calculate delay with exponential backoff if enabled
2665
+ if self.exponential_backoff:
2666
+ delay = self.delay_between_retries * (2**attempt)
2667
+ else:
2668
+ delay = self.delay_between_retries
2508
2669
 
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()
2670
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2671
+ time.sleep(delay)
2672
+ continue
2521
2673
 
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
2674
+ # Handle exceptions during async streaming
2675
+ run_response.status = RunStatus.error
2676
+ # Add error event to list of events
2677
+ run_error = create_run_error_event(run_response, error=str(e))
2678
+ run_response.events = add_error_event(error=run_error, events=run_response.events)
2529
2679
 
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
2680
+ # If the content is None, set it to the error message
2681
+ if run_response.content is None:
2682
+ run_response.content = str(e)
2536
2683
 
2537
- # Always clean up the run tracking
2538
- cleanup_run(run_response.run_id) # type: ignore
2684
+ log_error(f"Error in Agent run: {str(e)}")
2685
+
2686
+ # Cleanup and store the run response and session
2687
+ await self._acleanup_and_store(
2688
+ run_response=run_response,
2689
+ session=agent_session,
2690
+ run_context=run_context,
2691
+ user_id=user_id,
2692
+ )
2693
+
2694
+ # Yield the error event
2695
+ yield run_error
2696
+ finally:
2697
+ # Always disconnect connectable tools
2698
+ self._disconnect_connectable_tools()
2699
+ # Always disconnect MCP tools
2700
+ await self._disconnect_mcp_tools()
2701
+
2702
+ # Cancel the memory task if it's still running
2703
+ if memory_task is not None and not memory_task.done():
2704
+ memory_task.cancel()
2705
+ try:
2706
+ await memory_task
2707
+ except CancelledError:
2708
+ pass
2709
+
2710
+ if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2711
+ cultural_knowledge_task.cancel()
2712
+ try:
2713
+ await cultural_knowledge_task
2714
+ except CancelledError:
2715
+ pass
2716
+
2717
+ # Always clean up the run tracking
2718
+ cleanup_run(run_response.run_id) # type: ignore
2539
2719
 
2540
2720
  @overload
2541
2721
  async def arun(
@@ -2560,7 +2740,7 @@ class Agent:
2560
2740
  add_session_state_to_context: Optional[bool] = None,
2561
2741
  dependencies: Optional[Dict[str, Any]] = None,
2562
2742
  metadata: Optional[Dict[str, Any]] = None,
2563
- output_schema: Optional[Type[BaseModel]] = None,
2743
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2564
2744
  debug_mode: Optional[bool] = None,
2565
2745
  **kwargs: Any,
2566
2746
  ) -> RunOutput: ...
@@ -2587,7 +2767,7 @@ class Agent:
2587
2767
  add_session_state_to_context: Optional[bool] = None,
2588
2768
  dependencies: Optional[Dict[str, Any]] = None,
2589
2769
  metadata: Optional[Dict[str, Any]] = None,
2590
- output_schema: Optional[Type[BaseModel]] = None,
2770
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2591
2771
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2592
2772
  yield_run_output: Optional[bool] = None,
2593
2773
  debug_mode: Optional[bool] = None,
@@ -2616,7 +2796,7 @@ class Agent:
2616
2796
  add_session_state_to_context: Optional[bool] = None,
2617
2797
  dependencies: Optional[Dict[str, Any]] = None,
2618
2798
  metadata: Optional[Dict[str, Any]] = None,
2619
- output_schema: Optional[Type[BaseModel]] = None,
2799
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2620
2800
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2621
2801
  yield_run_output: Optional[bool] = None,
2622
2802
  debug_mode: Optional[bool] = None,
@@ -2764,82 +2944,37 @@ class Agent:
2764
2944
 
2765
2945
  yield_run_output = yield_run_output or yield_run_response # For backwards compatibility
2766
2946
 
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.")
2947
+ # Pass the new run_response to _arun
2948
+ if stream:
2949
+ return self._arun_stream( # type: ignore
2950
+ run_response=run_response,
2951
+ run_context=run_context,
2952
+ user_id=user_id,
2953
+ response_format=response_format,
2954
+ stream_events=stream_events,
2955
+ yield_run_output=yield_run_output,
2956
+ session_id=session_id,
2957
+ add_history_to_context=add_history,
2958
+ add_dependencies_to_context=add_dependencies,
2959
+ add_session_state_to_context=add_session_state,
2960
+ debug_mode=debug_mode,
2961
+ background_tasks=background_tasks,
2962
+ **kwargs,
2963
+ ) # type: ignore[assignment]
2964
+ else:
2965
+ return self._arun( # type: ignore
2966
+ run_response=run_response,
2967
+ run_context=run_context,
2968
+ user_id=user_id,
2969
+ response_format=response_format,
2970
+ session_id=session_id,
2971
+ add_history_to_context=add_history,
2972
+ add_dependencies_to_context=add_dependencies,
2973
+ add_session_state_to_context=add_session_state,
2974
+ debug_mode=debug_mode,
2975
+ background_tasks=background_tasks,
2976
+ **kwargs,
2977
+ )
2843
2978
 
2844
2979
  @overload
2845
2980
  def continue_run(
@@ -2970,7 +3105,7 @@ class Agent:
2970
3105
  num_attempts = self.retries + 1
2971
3106
 
2972
3107
  for attempt in range(num_attempts):
2973
- if attempt > 0:
3108
+ if num_attempts > 1:
2974
3109
  log_debug(f"Retrying Agent continue_run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
2975
3110
 
2976
3111
  try:
@@ -3611,7 +3746,7 @@ class Agent:
3611
3746
  )
3612
3747
 
3613
3748
  for attempt in range(num_attempts):
3614
- if attempt > 0:
3749
+ if num_attempts > 1:
3615
3750
  log_debug(f"Retrying Agent acontinue_run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
3616
3751
 
3617
3752
  try:
@@ -4689,22 +4824,34 @@ class Agent:
4689
4824
  output_schema = run_context.output_schema if run_context else None
4690
4825
 
4691
4826
  # 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
4827
+ if output_schema is not None:
4828
+ # If the output schema is a dict, do not convert it into a BaseModel
4829
+ if isinstance(output_schema, dict):
4830
+ if isinstance(run_response.content, str):
4831
+ parsed_dict = parse_response_dict_str(run_response.content)
4832
+ if parsed_dict is not None:
4833
+ run_response.content = parsed_dict
4700
4834
  if isinstance(run_response, RunOutput):
4701
- run_response.content_type = output_schema.__name__
4835
+ run_response.content_type = "dict"
4702
4836
  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")
4837
+ log_warning("Failed to parse JSON response against the provided output schema.")
4838
+ # If the output schema is a Pydantic model and parse_response is True, parse it into a BaseModel
4839
+ elif not isinstance(run_response.content, output_schema):
4840
+ if isinstance(run_response.content, str) and self.parse_response:
4841
+ try:
4842
+ structured_output = parse_response_model_str(run_response.content, output_schema)
4843
+
4844
+ # Update RunOutput
4845
+ if structured_output is not None:
4846
+ run_response.content = structured_output
4847
+ if isinstance(run_response, RunOutput):
4848
+ run_response.content_type = output_schema.__name__
4849
+ else:
4850
+ log_warning("Failed to convert response to output_schema")
4851
+ except Exception as e:
4852
+ log_warning(f"Failed to convert response to output model: {e}")
4853
+ else:
4854
+ log_warning("Something went wrong. Run response content is not a string")
4708
4855
 
4709
4856
  def _handle_external_execution_update(self, run_messages: RunMessages, tool: ToolExecution):
4710
4857
  self.model = cast(Model, self.model)
@@ -5053,7 +5200,7 @@ class Agent:
5053
5200
  # Update the run_response content with the structured output
5054
5201
  run_response.content = model_response.parsed
5055
5202
  # Update the run_response content_type with the structured output class name
5056
- run_response.content_type = output_schema.__name__
5203
+ run_response.content_type = "dict" if isinstance(output_schema, dict) else output_schema.__name__
5057
5204
  else:
5058
5205
  # Update the run_response content with the model response content
5059
5206
  run_response.content = model_response.content
@@ -5339,7 +5486,7 @@ class Agent:
5339
5486
 
5340
5487
  # Get output_schema from run_context
5341
5488
  output_schema = run_context.output_schema if run_context else None
5342
- content_type = output_schema.__name__ # type: ignore
5489
+ content_type = "dict" if isinstance(output_schema, dict) else output_schema.__name__ # type: ignore
5343
5490
  run_response.content = model_response.content
5344
5491
  run_response.content_type = content_type
5345
5492
  else:
@@ -6109,6 +6256,10 @@ class Agent:
6109
6256
  elif model.supports_json_schema_outputs:
6110
6257
  if self.use_json_mode or (not self.structured_outputs):
6111
6258
  log_debug("Setting Model.response_format to JSON response mode")
6259
+ # Handle JSON schema - pass through directly (user provides full provider format)
6260
+ if isinstance(output_schema, dict):
6261
+ return output_schema
6262
+ # Handle Pydantic schema
6112
6263
  return {
6113
6264
  "type": "json_schema",
6114
6265
  "json_schema": {
@@ -7521,8 +7672,8 @@ class Agent:
7521
7672
  ):
7522
7673
  system_message_content += f"{get_json_output_prompt(output_schema)}" # type: ignore
7523
7674
 
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:
7675
+ # 3.3.14 Add the response model format prompt if output_schema is provided (Pydantic only)
7676
+ if output_schema is not None and self.parser_model is not None and not isinstance(output_schema, dict):
7526
7677
  system_message_content += f"{get_response_model_format_prompt(output_schema)}"
7527
7678
 
7528
7679
  # 3.3.15 Add the session state to the system message
@@ -7868,8 +8019,8 @@ class Agent:
7868
8019
  ):
7869
8020
  system_message_content += f"{get_json_output_prompt(output_schema)}" # type: ignore
7870
8021
 
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:
8022
+ # 3.3.14 Add the response model format prompt if output_schema is provided (Pydantic only)
8023
+ if output_schema is not None and self.parser_model is not None and not isinstance(output_schema, dict):
7873
8024
  system_message_content += f"{get_response_model_format_prompt(output_schema)}"
7874
8025
 
7875
8026
  # 3.3.15 Add the session state to the system message
@@ -9176,292 +9327,78 @@ class Agent:
9176
9327
 
9177
9328
  return updated_reasoning_content
9178
9329
 
9179
- def _reason(
9180
- self, run_response: RunOutput, run_messages: RunMessages, stream_events: Optional[bool] = None
9330
+ def _handle_reasoning_event(
9331
+ self,
9332
+ event: "ReasoningEvent", # type: ignore # noqa: F821
9333
+ run_response: RunOutput,
9334
+ stream_events: Optional[bool] = None,
9181
9335
  ) -> 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
9336
+ """
9337
+ Convert a ReasoningEvent from the ReasoningManager to Agent-specific RunOutputEvents.
9287
9338
 
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
9339
+ This method handles the conversion of generic reasoning events to Agent events,
9340
+ keeping the Agent._reason() method clean and simple.
9341
+ """
9342
+ from agno.reasoning.manager import ReasoningEventType
9295
9343
 
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
9344
+ if event.event_type == ReasoningEventType.started:
9345
+ if stream_events:
9346
+ yield handle_event( # type: ignore
9347
+ create_reasoning_started_event(from_run_response=run_response),
9348
+ run_response,
9349
+ events_to_skip=self.events_to_skip, # type: ignore
9350
+ store_events=self.store_events,
9351
+ )
9303
9352
 
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
- )
9353
+ elif event.event_type == ReasoningEventType.content_delta:
9354
+ if stream_events and event.reasoning_content:
9355
+ yield handle_event( # type: ignore
9356
+ create_reasoning_content_delta_event(
9357
+ from_run_response=run_response,
9358
+ reasoning_content=event.reasoning_content,
9359
+ ),
9360
+ run_response,
9361
+ events_to_skip=self.events_to_skip, # type: ignore
9362
+ store_events=self.store_events,
9363
+ )
9309
9364
 
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
9365
+ elif event.event_type == ReasoningEventType.step:
9366
+ if event.reasoning_step:
9367
+ # Update run_response with this step
9315
9368
  update_run_output_with_reasoning(
9316
9369
  run_response=run_response,
9317
- reasoning_steps=[ReasoningStep(result=reasoning_message.content)],
9318
- reasoning_agent_messages=[reasoning_message],
9370
+ reasoning_steps=[event.reasoning_step],
9371
+ reasoning_agent_messages=[],
9319
9372
  )
9320
9373
  if stream_events:
9374
+ updated_reasoning_content = self._format_reasoning_step_content(
9375
+ run_response=run_response,
9376
+ reasoning_step=event.reasoning_step,
9377
+ )
9321
9378
  yield handle_event( # type: ignore
9322
- create_reasoning_completed_event(
9379
+ create_reasoning_step_event(
9323
9380
  from_run_response=run_response,
9324
- content=ReasoningSteps(reasoning_steps=[ReasoningStep(result=reasoning_message.content)]),
9325
- content_type=ReasoningSteps.__name__,
9381
+ reasoning_step=event.reasoning_step,
9382
+ reasoning_content=updated_reasoning_content,
9326
9383
  ),
9327
9384
  run_response,
9328
9385
  events_to_skip=self.events_to_skip, # type: ignore
9329
9386
  store_events=self.store_events,
9330
9387
  )
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
9388
 
9459
- # Yield the final reasoning completed event
9389
+ elif event.event_type == ReasoningEventType.completed:
9390
+ if event.message and event.reasoning_steps:
9391
+ # This is from native reasoning - update with the message and steps
9392
+ update_run_output_with_reasoning(
9393
+ run_response=run_response,
9394
+ reasoning_steps=event.reasoning_steps,
9395
+ reasoning_agent_messages=event.reasoning_messages,
9396
+ )
9460
9397
  if stream_events:
9461
9398
  yield handle_event( # type: ignore
9462
9399
  create_reasoning_completed_event(
9463
9400
  from_run_response=run_response,
9464
- content=ReasoningSteps(reasoning_steps=all_reasoning_steps),
9401
+ content=ReasoningSteps(reasoning_steps=event.reasoning_steps),
9465
9402
  content_type=ReasoningSteps.__name__,
9466
9403
  ),
9467
9404
  run_response,
@@ -9469,45 +9406,37 @@ class Agent:
9469
9406
  store_events=self.store_events,
9470
9407
  )
9471
9408
 
9472
- async def _areason(
9409
+ elif event.event_type == ReasoningEventType.error:
9410
+ log_warning(f"Reasoning error. {event.error}, continuing regular session...")
9411
+
9412
+ def _reason(
9473
9413
  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
- )
9414
+ ) -> Iterator[RunOutputEvent]:
9415
+ """
9416
+ Run reasoning using the ReasoningManager.
9483
9417
 
9484
- use_default_reasoning = False
9418
+ Handles both native reasoning models (DeepSeek, Anthropic, etc.) and
9419
+ default Chain-of-Thought reasoning with a clean, unified interface.
9420
+ """
9421
+ from agno.reasoning.manager import ReasoningConfig, ReasoningManager
9485
9422
 
9486
- # Get the reasoning model
9423
+ # Get the reasoning model (use copy of main model if not provided)
9487
9424
  reasoning_model: Optional[Model] = self.reasoning_model
9488
- reasoning_model_provided = reasoning_model is not None
9489
9425
  if reasoning_model is None and self.model is not None:
9490
9426
  from copy import deepcopy
9491
9427
 
9492
9428
  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
9429
 
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(
9430
+ # Create reasoning manager with config
9431
+ manager = ReasoningManager(
9432
+ ReasoningConfig(
9510
9433
  reasoning_model=reasoning_model,
9434
+ reasoning_agent=self.reasoning_agent,
9435
+ min_steps=self.reasoning_min_steps,
9436
+ max_steps=self.reasoning_max_steps,
9437
+ tools=self.tools,
9438
+ tool_call_limit=self.tool_call_limit,
9439
+ use_json_mode=self.use_json_mode,
9511
9440
  telemetry=self.telemetry,
9512
9441
  debug_mode=self.debug_mode,
9513
9442
  debug_level=self.debug_level,
@@ -9515,252 +9444,53 @@ class Agent:
9515
9444
  dependencies=self.dependencies,
9516
9445
  metadata=self.metadata,
9517
9446
  )
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
- )
9447
+ )
9707
9448
 
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
- )
9449
+ # Use the unified reason() method and convert events
9450
+ for event in manager.reason(run_messages, stream=bool(stream_events)):
9451
+ yield from self._handle_reasoning_event(event, run_response, stream_events)
9719
9452
 
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:]
9453
+ async def _areason(
9454
+ self, run_response: RunOutput, run_messages: RunMessages, stream_events: Optional[bool] = None
9455
+ ) -> Any:
9456
+ """
9457
+ Run reasoning asynchronously using the ReasoningManager.
9727
9458
 
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
- )
9459
+ Handles both native reasoning models (DeepSeek, Anthropic, etc.) and
9460
+ default Chain-of-Thought reasoning with a clean, unified interface.
9461
+ """
9462
+ from agno.reasoning.manager import ReasoningConfig, ReasoningManager
9734
9463
 
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
9464
+ # Get the reasoning model (use copy of main model if not provided)
9465
+ reasoning_model: Optional[Model] = self.reasoning_model
9466
+ if reasoning_model is None and self.model is not None:
9467
+ from copy import deepcopy
9742
9468
 
9743
- log_debug(f"Total Reasoning steps: {len(all_reasoning_steps)}")
9744
- log_debug("Reasoning finished", center=True, symbol="=")
9469
+ reasoning_model = deepcopy(self.model)
9745
9470
 
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,
9471
+ # Create reasoning manager with config
9472
+ manager = ReasoningManager(
9473
+ ReasoningConfig(
9474
+ reasoning_model=reasoning_model,
9475
+ reasoning_agent=self.reasoning_agent,
9476
+ min_steps=self.reasoning_min_steps,
9477
+ max_steps=self.reasoning_max_steps,
9478
+ tools=self.tools,
9479
+ tool_call_limit=self.tool_call_limit,
9480
+ use_json_mode=self.use_json_mode,
9481
+ telemetry=self.telemetry,
9482
+ debug_mode=self.debug_mode,
9483
+ debug_level=self.debug_level,
9484
+ session_state=self.session_state,
9485
+ dependencies=self.dependencies,
9486
+ metadata=self.metadata,
9750
9487
  )
9488
+ )
9751
9489
 
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
- )
9490
+ # Use the unified areason() method and convert events
9491
+ async for event in manager.areason(run_messages, stream=bool(stream_events)):
9492
+ for output_event in self._handle_reasoning_event(event, run_response, stream_events):
9493
+ yield output_event
9764
9494
 
9765
9495
  def _process_parser_response(
9766
9496
  self,
@@ -10408,9 +10138,8 @@ class Agent:
10408
10138
  """Use this function to add information to the knowledge base for future use.
10409
10139
 
10410
10140
  Args:
10411
- query: The query to add.
10412
- result: The result of the query.
10413
-
10141
+ query (str): The query or topic to add.
10142
+ result (str): The actual content or information to store.
10414
10143
  Returns:
10415
10144
  str: A string indicating the status of the addition.
10416
10145
  """
@@ -10421,13 +10150,9 @@ class Agent:
10421
10150
  document_name = query.replace(" ", "_").replace("?", "").replace("!", "").replace(".", "")
10422
10151
  document_content = json.dumps({"query": query, "result": result})
10423
10152
  log_info(f"Adding document to Knowledge: {document_name}: {document_content}")
10424
- import asyncio
10425
-
10426
10153
  from agno.knowledge.reader.text_reader import TextReader
10427
10154
 
10428
- asyncio.run(
10429
- self.knowledge.add_content_async(name=document_name, text_content=document_content, reader=TextReader())
10430
- )
10155
+ self.knowledge.add_content(name=document_name, text_content=document_content, reader=TextReader())
10431
10156
  return "Successfully added to knowledge base"
10432
10157
 
10433
10158
  def _get_previous_sessions_messages_function(
@@ -10917,8 +10642,7 @@ class Agent:
10917
10642
  session: AgentSession,
10918
10643
  run_context: Optional[RunContext] = None,
10919
10644
  user_id: Optional[str] = None,
10920
- ) -> None:
10921
- # Scrub the stored run based on storage flags
10645
+ ) -> None: # Scrub the stored run based on storage flags
10922
10646
  self._scrub_run_output_for_storage(run_response)
10923
10647
 
10924
10648
  # Stop the timer for the Run duration