agno 2.3.13__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 (41) hide show
  1. agno/agent/agent.py +1131 -1402
  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/base.py +8 -4
  7. agno/models/openai/responses.py +2 -2
  8. agno/os/app.py +39 -0
  9. agno/os/interfaces/a2a/router.py +619 -9
  10. agno/os/interfaces/a2a/utils.py +31 -32
  11. agno/os/middleware/jwt.py +5 -5
  12. agno/os/routers/agents/schema.py +14 -1
  13. agno/os/routers/teams/schema.py +14 -1
  14. agno/os/utils.py +61 -53
  15. agno/reasoning/anthropic.py +85 -1
  16. agno/reasoning/azure_ai_foundry.py +93 -1
  17. agno/reasoning/deepseek.py +91 -1
  18. agno/reasoning/gemini.py +81 -1
  19. agno/reasoning/groq.py +103 -1
  20. agno/reasoning/manager.py +1244 -0
  21. agno/reasoning/ollama.py +93 -1
  22. agno/reasoning/openai.py +113 -1
  23. agno/reasoning/vertexai.py +85 -1
  24. agno/run/agent.py +11 -0
  25. agno/run/base.py +1 -1
  26. agno/run/team.py +11 -0
  27. agno/session/team.py +0 -3
  28. agno/team/team.py +1201 -1445
  29. agno/utils/events.py +69 -2
  30. agno/utils/hooks.py +4 -10
  31. agno/utils/print_response/agent.py +26 -0
  32. agno/utils/print_response/team.py +11 -0
  33. agno/utils/prompts.py +8 -6
  34. agno/utils/string.py +46 -0
  35. agno/utils/team.py +1 -1
  36. agno/vectordb/milvus/milvus.py +32 -3
  37. {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/METADATA +3 -2
  38. {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/RECORD +41 -40
  39. {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/WHEEL +0 -0
  40. {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/licenses/LICENSE +0 -0
  41. {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/top_level.txt +0 -0
agno/team/team.py CHANGED
@@ -11,7 +11,6 @@ from dataclasses import dataclass
11
11
  from os import getenv
12
12
  from textwrap import dedent
13
13
  from typing import (
14
- TYPE_CHECKING,
15
14
  Any,
16
15
  AsyncIterator,
17
16
  Callable,
@@ -33,12 +32,10 @@ from uuid import uuid4
33
32
 
34
33
  from pydantic import BaseModel
35
34
 
36
- if TYPE_CHECKING:
37
- from agno.eval.base import BaseEval
38
-
39
35
  from agno.agent import Agent
40
36
  from agno.compression.manager import CompressionManager
41
37
  from agno.db.base import AsyncBaseDb, BaseDb, SessionType, UserMemory
38
+ from agno.eval.base import BaseEval
42
39
  from agno.exceptions import (
43
40
  InputCheckError,
44
41
  OutputCheckError,
@@ -67,7 +64,12 @@ from agno.run.cancel import (
67
64
  register_run,
68
65
  )
69
66
  from agno.run.messages import RunMessages
70
- from agno.run.team import TeamRunEvent, TeamRunInput, TeamRunOutput, TeamRunOutputEvent
67
+ from agno.run.team import (
68
+ TeamRunEvent,
69
+ TeamRunInput,
70
+ TeamRunOutput,
71
+ TeamRunOutputEvent,
72
+ )
71
73
  from agno.session import SessionSummaryManager, TeamSession, WorkflowSession
72
74
  from agno.session.summary import SessionSummary
73
75
  from agno.tools import Toolkit
@@ -107,6 +109,7 @@ from agno.utils.agent import (
107
109
  )
108
110
  from agno.utils.common import is_typed_dict, validate_typed_dict
109
111
  from agno.utils.events import (
112
+ add_team_error_event,
110
113
  create_team_parser_model_response_completed_event,
111
114
  create_team_parser_model_response_started_event,
112
115
  create_team_post_hook_completed_event,
@@ -114,11 +117,13 @@ from agno.utils.events import (
114
117
  create_team_pre_hook_completed_event,
115
118
  create_team_pre_hook_started_event,
116
119
  create_team_reasoning_completed_event,
120
+ create_team_reasoning_content_delta_event,
117
121
  create_team_reasoning_started_event,
118
122
  create_team_reasoning_step_event,
119
123
  create_team_run_cancelled_event,
120
124
  create_team_run_completed_event,
121
125
  create_team_run_content_completed_event,
126
+ create_team_run_error_event,
122
127
  create_team_run_output_content_event,
123
128
  create_team_run_started_event,
124
129
  create_team_session_summary_completed_event,
@@ -161,12 +166,11 @@ from agno.utils.reasoning import (
161
166
  update_run_output_with_reasoning,
162
167
  )
163
168
  from agno.utils.response import (
164
- async_generator_wrapper,
165
169
  check_if_run_cancelled,
166
170
  generator_wrapper,
167
171
  )
168
172
  from agno.utils.safe_formatter import SafeFormatter
169
- from agno.utils.string import generate_id_from_name, parse_response_model_str
173
+ from agno.utils.string import generate_id_from_name, parse_response_dict_str, parse_response_model_str
170
174
  from agno.utils.team import (
171
175
  add_interaction_to_team_run_context,
172
176
  format_member_agent_task,
@@ -354,17 +358,18 @@ class Team:
354
358
 
355
359
  # --- Team Hooks ---
356
360
  # Functions called right after team session is loaded, before processing starts
357
- pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None
361
+ pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
358
362
  # Functions called after output is generated but before the response is returned
359
- post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None
363
+ post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None
360
364
  # If True, run hooks as FastAPI background tasks (non-blocking). Set by AgentOS.
361
365
  _run_hooks_in_background: Optional[bool] = None
362
366
 
363
367
  # --- Structured output ---
364
368
  # Input schema for validating input
365
369
  input_schema: Optional[Type[BaseModel]] = None
366
- # Output schema for the team response
367
- output_schema: Optional[Type[BaseModel]] = None
370
+ # Provide a response model to get the response in the implied format.
371
+ # You can use a Pydantic model or a JSON fitting the provider's expected schema.
372
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None
368
373
  # Provide a secondary model to parse the response from the primary model
369
374
  parser_model: Optional[Model] = None
370
375
  # Provide a prompt for the parser model
@@ -525,10 +530,10 @@ class Team:
525
530
  tool_call_limit: Optional[int] = None,
526
531
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
527
532
  tool_hooks: Optional[List[Callable]] = None,
528
- pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None,
529
- post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]] = None,
533
+ pre_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
534
+ post_hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, BaseEval]]] = None,
530
535
  input_schema: Optional[Type[BaseModel]] = None,
531
- output_schema: Optional[Type[BaseModel]] = None,
536
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
532
537
  parser_model: Optional[Union[Model, str]] = None,
533
538
  parser_model_prompt: Optional[str] = None,
534
539
  output_model: Optional[Union[Model, str]] = None,
@@ -728,7 +733,7 @@ class Team:
728
733
  self._tool_instructions: Optional[List[str]] = None
729
734
 
730
735
  # True if we should parse a member response model
731
- self._member_response_model: Optional[Type[BaseModel]] = None
736
+ self._member_response_model: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None
732
737
 
733
738
  self._formatter: Optional[SafeFormatter] = None
734
739
 
@@ -1484,7 +1489,6 @@ class Team:
1484
1489
  **kwargs: Any,
1485
1490
  ) -> TeamRunOutput:
1486
1491
  """Run the Team and return the response.
1487
-
1488
1492
  Steps:
1489
1493
  1. Execute pre-hooks
1490
1494
  2. Determine tools for model
@@ -1500,7 +1504,6 @@ class Team:
1500
1504
  12. Create session summary
1501
1505
  13. Cleanup and store (scrub, stop timer, add to session, calculate metrics, save session)
1502
1506
  """
1503
-
1504
1507
  # 1. Execute pre-hooks
1505
1508
  run_input = cast(TeamRunInput, run_response.input)
1506
1509
  self.model = cast(Model, self.model)
@@ -1580,108 +1583,97 @@ class Team:
1580
1583
  self._make_memories, run_messages=run_messages, user_id=user_id
1581
1584
  )
1582
1585
 
1583
- try:
1584
- raise_if_cancelled(run_response.run_id) # type: ignore
1585
-
1586
- # 5. Reason about the task if reasoning is enabled
1587
- self._handle_reasoning(run_response=run_response, run_messages=run_messages)
1586
+ raise_if_cancelled(run_response.run_id) # type: ignore
1588
1587
 
1589
- # Check for cancellation before model call
1590
- raise_if_cancelled(run_response.run_id) # type: ignore
1588
+ # 5. Reason about the task if reasoning is enabled
1589
+ self._handle_reasoning(run_response=run_response, run_messages=run_messages)
1591
1590
 
1592
- # 6. Get the model response for the team leader
1593
- self.model = cast(Model, self.model)
1594
- model_response: ModelResponse = self.model.response(
1595
- messages=run_messages.messages,
1596
- response_format=response_format,
1597
- tools=_tools,
1598
- tool_choice=self.tool_choice,
1599
- tool_call_limit=self.tool_call_limit,
1600
- send_media_to_model=self.send_media_to_model,
1601
- compression_manager=self.compression_manager if self.compress_tool_results else None,
1602
- )
1591
+ # Check for cancellation before model call
1592
+ raise_if_cancelled(run_response.run_id) # type: ignore
1603
1593
 
1604
- # Check for cancellation after model call
1605
- raise_if_cancelled(run_response.run_id) # type: ignore
1594
+ # 6. Get the model response for the team leader
1595
+ self.model = cast(Model, self.model)
1596
+ model_response: ModelResponse = self.model.response(
1597
+ messages=run_messages.messages,
1598
+ response_format=response_format,
1599
+ tools=_tools,
1600
+ tool_choice=self.tool_choice,
1601
+ tool_call_limit=self.tool_call_limit,
1602
+ send_media_to_model=self.send_media_to_model,
1603
+ compression_manager=self.compression_manager if self.compress_tool_results else None,
1604
+ )
1606
1605
 
1607
- # If an output model is provided, generate output using the output model
1608
- self._parse_response_with_output_model(model_response, run_messages)
1606
+ # Check for cancellation after model call
1607
+ raise_if_cancelled(run_response.run_id) # type: ignore
1609
1608
 
1610
- # If a parser model is provided, structure the response separately
1611
- self._parse_response_with_parser_model(model_response, run_messages, run_context=run_context)
1609
+ # If an output model is provided, generate output using the output model
1610
+ self._parse_response_with_output_model(model_response, run_messages)
1612
1611
 
1613
- # 7. Update TeamRunOutput with the model response
1614
- self._update_run_response(
1615
- model_response=model_response,
1616
- run_response=run_response,
1617
- run_messages=run_messages,
1618
- run_context=run_context,
1619
- )
1612
+ # If a parser model is provided, structure the response separately
1613
+ self._parse_response_with_parser_model(model_response, run_messages, run_context=run_context)
1620
1614
 
1621
- # 8. Store media if enabled
1622
- if self.store_media:
1623
- store_media_util(run_response, model_response)
1615
+ # 7. Update TeamRunOutput with the model response
1616
+ self._update_run_response(
1617
+ model_response=model_response,
1618
+ run_response=run_response,
1619
+ run_messages=run_messages,
1620
+ run_context=run_context,
1621
+ )
1624
1622
 
1625
- # 9. Convert response to structured format
1626
- self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
1623
+ # 8. Store media if enabled
1624
+ if self.store_media:
1625
+ store_media_util(run_response, model_response)
1627
1626
 
1628
- # 10. Execute post-hooks after output is generated but before response is returned
1629
- if self.post_hooks is not None:
1630
- iterator = self._execute_post_hooks(
1631
- hooks=self.post_hooks, # type: ignore
1632
- run_output=run_response,
1633
- run_context=run_context,
1634
- session=session,
1635
- user_id=user_id,
1636
- debug_mode=debug_mode,
1637
- background_tasks=background_tasks,
1638
- **kwargs,
1639
- )
1640
- deque(iterator, maxlen=0)
1641
- raise_if_cancelled(run_response.run_id) # type: ignore
1627
+ # 9. Convert response to structured format
1628
+ self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
1642
1629
 
1643
- # 11. Wait for background memory creation
1644
- wait_for_open_threads(memory_future=memory_future)
1630
+ # 10. Execute post-hooks after output is generated but before response is returned
1631
+ if self.post_hooks is not None:
1632
+ iterator = self._execute_post_hooks(
1633
+ hooks=self.post_hooks, # type: ignore
1634
+ run_output=run_response,
1635
+ run_context=run_context,
1636
+ session=session,
1637
+ user_id=user_id,
1638
+ debug_mode=debug_mode,
1639
+ background_tasks=background_tasks,
1640
+ **kwargs,
1641
+ )
1642
+ deque(iterator, maxlen=0)
1643
+ raise_if_cancelled(run_response.run_id) # type: ignore
1645
1644
 
1646
- raise_if_cancelled(run_response.run_id) # type: ignore
1645
+ # 11. Wait for background memory creation
1646
+ wait_for_open_threads(memory_future=memory_future)
1647
1647
 
1648
- # 12. Create session summary
1649
- if self.session_summary_manager is not None:
1650
- # Upsert the RunOutput to Team Session before creating the session summary
1651
- session.upsert_run(run_response=run_response)
1652
- try:
1653
- self.session_summary_manager.create_session_summary(session=session)
1654
- except Exception as e:
1655
- log_warning(f"Error in session summary creation: {str(e)}")
1648
+ raise_if_cancelled(run_response.run_id) # type: ignore
1656
1649
 
1657
- raise_if_cancelled(run_response.run_id) # type: ignore
1650
+ # 12. Create session summary
1651
+ if self.session_summary_manager is not None:
1652
+ # Upsert the RunOutput to Team Session before creating the session summary
1653
+ session.upsert_run(run_response=run_response)
1654
+ try:
1655
+ self.session_summary_manager.create_session_summary(session=session)
1656
+ except Exception as e:
1657
+ log_warning(f"Error in session summary creation: {str(e)}")
1658
1658
 
1659
- # Set the run status to completed
1660
- run_response.status = RunStatus.completed
1659
+ raise_if_cancelled(run_response.run_id) # type: ignore
1661
1660
 
1662
- # 13. Cleanup and store the run response
1663
- self._cleanup_and_store(run_response=run_response, session=session)
1661
+ # Set the run status to completed
1662
+ run_response.status = RunStatus.completed
1664
1663
 
1665
- # Log Team Telemetry
1666
- self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1664
+ # 13. Cleanup and store the run response
1665
+ self._cleanup_and_store(run_response=run_response, session=session)
1667
1666
 
1668
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
1667
+ # Log Team Telemetry
1668
+ self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1669
1669
 
1670
- return run_response
1670
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
1671
1671
 
1672
- except RunCancelledException as e:
1673
- # Handle run cancellation during streaming
1674
- log_info(f"Team run {run_response.run_id} was cancelled")
1675
- run_response.status = RunStatus.cancelled
1676
- run_response.content = str(e)
1672
+ # Disconnect tools and clean up run tracking
1673
+ self._disconnect_connectable_tools()
1674
+ cleanup_run(run_response.run_id) # type: ignore
1677
1675
 
1678
- # Add the RunOutput to Team Session even when cancelled
1679
- self._cleanup_and_store(run_response=run_response, session=session)
1680
- return run_response
1681
- finally:
1682
- # Always disconnect connectable tools
1683
- self._disconnect_connectable_tools()
1684
- cleanup_run(run_response.run_id) # type: ignore
1676
+ return run_response
1685
1677
 
1686
1678
  def _run_stream(
1687
1679
  self,
@@ -1700,7 +1692,6 @@ class Team:
1700
1692
  **kwargs: Any,
1701
1693
  ) -> Iterator[Union[TeamRunOutputEvent, RunOutputEvent, TeamRunOutput]]:
1702
1694
  """Run the Team and return the response iterator.
1703
-
1704
1695
  Steps:
1705
1696
  1. Execute pre-hooks
1706
1697
  2. Determine tools for model
@@ -1794,190 +1785,171 @@ class Team:
1794
1785
  self._make_memories, run_messages=run_messages, user_id=user_id
1795
1786
  )
1796
1787
 
1797
- try:
1798
- # Start the Run by yielding a RunStarted event
1799
- if stream_events:
1800
- yield handle_event( # type: ignore
1801
- create_team_run_started_event(run_response),
1802
- run_response,
1803
- events_to_skip=self.events_to_skip,
1804
- store_events=self.store_events,
1805
- )
1788
+ # Start the Run by yielding a RunStarted event
1789
+ if stream_events:
1790
+ yield handle_event( # type: ignore
1791
+ create_team_run_started_event(run_response),
1792
+ run_response,
1793
+ events_to_skip=self.events_to_skip,
1794
+ store_events=self.store_events,
1795
+ )
1796
+
1797
+ raise_if_cancelled(run_response.run_id) # type: ignore
1798
+
1799
+ # 5. Reason about the task if reasoning is enabled
1800
+ yield from self._handle_reasoning_stream(
1801
+ run_response=run_response,
1802
+ run_messages=run_messages,
1803
+ stream_events=stream_events,
1804
+ )
1806
1805
 
1807
- raise_if_cancelled(run_response.run_id) # type: ignore
1806
+ # Check for cancellation before model processing
1807
+ raise_if_cancelled(run_response.run_id) # type: ignore
1808
1808
 
1809
- # 5. Reason about the task if reasoning is enabled
1810
- yield from self._handle_reasoning_stream(
1809
+ # 6. Get a response from the model
1810
+ if self.output_model is None:
1811
+ for event in self._handle_model_response_stream(
1812
+ session=session,
1811
1813
  run_response=run_response,
1812
1814
  run_messages=run_messages,
1815
+ tools=_tools,
1816
+ response_format=response_format,
1813
1817
  stream_events=stream_events,
1814
- )
1815
-
1816
- # Check for cancellation before model processing
1817
- raise_if_cancelled(run_response.run_id) # type: ignore
1818
+ session_state=run_context.session_state,
1819
+ run_context=run_context,
1820
+ ):
1821
+ raise_if_cancelled(run_response.run_id) # type: ignore
1822
+ yield event
1823
+ else:
1824
+ for event in self._handle_model_response_stream(
1825
+ session=session,
1826
+ run_response=run_response,
1827
+ run_messages=run_messages,
1828
+ tools=_tools,
1829
+ response_format=response_format,
1830
+ stream_events=stream_events,
1831
+ session_state=run_context.session_state,
1832
+ run_context=run_context,
1833
+ ):
1834
+ raise_if_cancelled(run_response.run_id) # type: ignore
1835
+ from agno.run.team import IntermediateRunContentEvent, RunContentEvent
1818
1836
 
1819
- # 6. Get a response from the model
1820
- if self.output_model is None:
1821
- for event in self._handle_model_response_stream(
1822
- session=session,
1823
- run_response=run_response,
1824
- run_messages=run_messages,
1825
- tools=_tools,
1826
- response_format=response_format,
1827
- stream_events=stream_events,
1828
- session_state=run_context.session_state,
1829
- run_context=run_context,
1830
- ):
1831
- raise_if_cancelled(run_response.run_id) # type: ignore
1837
+ if isinstance(event, RunContentEvent):
1838
+ if stream_events:
1839
+ yield IntermediateRunContentEvent(
1840
+ content=event.content,
1841
+ content_type=event.content_type,
1842
+ )
1843
+ else:
1832
1844
  yield event
1833
- else:
1834
- for event in self._handle_model_response_stream(
1835
- session=session,
1836
- run_response=run_response,
1837
- run_messages=run_messages,
1838
- tools=_tools,
1839
- response_format=response_format,
1840
- stream_events=stream_events,
1841
- session_state=run_context.session_state,
1842
- run_context=run_context,
1843
- ):
1844
- raise_if_cancelled(run_response.run_id) # type: ignore
1845
- from agno.run.team import IntermediateRunContentEvent, RunContentEvent
1846
1845
 
1847
- if isinstance(event, RunContentEvent):
1848
- if stream_events:
1849
- yield IntermediateRunContentEvent(
1850
- content=event.content,
1851
- content_type=event.content_type,
1852
- )
1853
- else:
1854
- yield event
1846
+ for event in self._generate_response_with_output_model_stream(
1847
+ session=session,
1848
+ run_response=run_response,
1849
+ run_messages=run_messages,
1850
+ stream_events=stream_events,
1851
+ ):
1852
+ raise_if_cancelled(run_response.run_id) # type: ignore
1853
+ yield event
1855
1854
 
1856
- for event in self._generate_response_with_output_model_stream(
1857
- session=session,
1858
- run_response=run_response,
1859
- run_messages=run_messages,
1860
- stream_events=stream_events,
1861
- ):
1862
- raise_if_cancelled(run_response.run_id) # type: ignore
1863
- yield event
1855
+ # Check for cancellation after model processing
1856
+ raise_if_cancelled(run_response.run_id) # type: ignore
1864
1857
 
1865
- # Check for cancellation after model processing
1866
- raise_if_cancelled(run_response.run_id) # type: ignore
1858
+ # 7. Parse response with parser model if provided
1859
+ yield from self._parse_response_with_parser_model_stream(
1860
+ session=session, run_response=run_response, stream_events=stream_events, run_context=run_context
1861
+ )
1867
1862
 
1868
- # 7. Parse response with parser model if provided
1869
- yield from self._parse_response_with_parser_model_stream(
1870
- session=session, run_response=run_response, stream_events=stream_events, run_context=run_context
1863
+ # Yield RunContentCompletedEvent
1864
+ if stream_events:
1865
+ yield handle_event( # type: ignore
1866
+ create_team_run_content_completed_event(from_run_response=run_response),
1867
+ run_response,
1868
+ events_to_skip=self.events_to_skip,
1869
+ store_events=self.store_events,
1870
+ )
1871
+ # Execute post-hooks after output is generated but before response is returned
1872
+ if self.post_hooks is not None:
1873
+ yield from self._execute_post_hooks(
1874
+ hooks=self.post_hooks, # type: ignore
1875
+ run_output=run_response,
1876
+ run_context=run_context,
1877
+ session=session,
1878
+ user_id=user_id,
1879
+ debug_mode=debug_mode,
1880
+ stream_events=stream_events,
1881
+ background_tasks=background_tasks,
1882
+ **kwargs,
1871
1883
  )
1884
+ raise_if_cancelled(run_response.run_id) # type: ignore
1885
+
1886
+ # 8. Wait for background memory creation
1887
+ yield from wait_for_thread_tasks_stream(
1888
+ run_response=run_response,
1889
+ memory_future=memory_future,
1890
+ stream_events=stream_events,
1891
+ events_to_skip=self.events_to_skip, # type: ignore
1892
+ store_events=self.store_events,
1893
+ )
1894
+
1895
+ raise_if_cancelled(run_response.run_id) # type: ignore
1896
+ # 9. Create session summary
1897
+ if self.session_summary_manager is not None:
1898
+ # Upsert the RunOutput to Team Session before creating the session summary
1899
+ session.upsert_run(run_response=run_response)
1872
1900
 
1873
- # Yield RunContentCompletedEvent
1874
1901
  if stream_events:
1875
1902
  yield handle_event( # type: ignore
1876
- create_team_run_content_completed_event(from_run_response=run_response),
1903
+ create_team_session_summary_started_event(from_run_response=run_response),
1877
1904
  run_response,
1878
1905
  events_to_skip=self.events_to_skip,
1879
1906
  store_events=self.store_events,
1880
1907
  )
1881
- # Execute post-hooks after output is generated but before response is returned
1882
- if self.post_hooks is not None:
1883
- yield from self._execute_post_hooks(
1884
- hooks=self.post_hooks, # type: ignore
1885
- run_output=run_response,
1886
- run_context=run_context,
1887
- session=session,
1888
- user_id=user_id,
1889
- debug_mode=debug_mode,
1890
- stream_events=stream_events,
1891
- background_tasks=background_tasks,
1892
- **kwargs,
1908
+ try:
1909
+ self.session_summary_manager.create_session_summary(session=session)
1910
+ except Exception as e:
1911
+ log_warning(f"Error in session summary creation: {str(e)}")
1912
+ if stream_events:
1913
+ yield handle_event( # type: ignore
1914
+ create_team_session_summary_completed_event(
1915
+ from_run_response=run_response, session_summary=session.summary
1916
+ ),
1917
+ run_response,
1918
+ events_to_skip=self.events_to_skip,
1919
+ store_events=self.store_events,
1893
1920
  )
1894
- raise_if_cancelled(run_response.run_id) # type: ignore
1895
-
1896
- # 8. Wait for background memory creation
1897
- yield from wait_for_thread_tasks_stream(
1898
- run_response=run_response,
1899
- memory_future=memory_future,
1900
- stream_events=stream_events,
1901
- events_to_skip=self.events_to_skip, # type: ignore
1902
- store_events=self.store_events,
1903
- )
1904
-
1905
- raise_if_cancelled(run_response.run_id) # type: ignore
1906
- # 9. Create session summary
1907
- if self.session_summary_manager is not None:
1908
- # Upsert the RunOutput to Team Session before creating the session summary
1909
- session.upsert_run(run_response=run_response)
1910
1921
 
1911
- if stream_events:
1912
- yield handle_event( # type: ignore
1913
- create_team_session_summary_started_event(from_run_response=run_response),
1914
- run_response,
1915
- events_to_skip=self.events_to_skip,
1916
- store_events=self.store_events,
1917
- )
1918
- try:
1919
- self.session_summary_manager.create_session_summary(session=session)
1920
- except Exception as e:
1921
- log_warning(f"Error in session summary creation: {str(e)}")
1922
- if stream_events:
1923
- yield handle_event( # type: ignore
1924
- create_team_session_summary_completed_event(
1925
- from_run_response=run_response, session_summary=session.summary
1926
- ),
1927
- run_response,
1928
- events_to_skip=self.events_to_skip,
1929
- store_events=self.store_events,
1930
- )
1931
-
1932
- raise_if_cancelled(run_response.run_id) # type: ignore
1933
- # Create the run completed event
1934
- completed_event = handle_event(
1935
- create_team_run_completed_event(
1936
- from_run_response=run_response,
1937
- ),
1938
- run_response,
1939
- events_to_skip=self.events_to_skip,
1940
- store_events=self.store_events,
1941
- )
1942
-
1943
- # Set the run status to completed
1944
- run_response.status = RunStatus.completed
1945
-
1946
- # 10. Cleanup and store the run response
1947
- self._cleanup_and_store(run_response=run_response, session=session)
1922
+ raise_if_cancelled(run_response.run_id) # type: ignore
1923
+ # Create the run completed event
1924
+ completed_event = handle_event(
1925
+ create_team_run_completed_event(
1926
+ from_run_response=run_response,
1927
+ ),
1928
+ run_response,
1929
+ events_to_skip=self.events_to_skip,
1930
+ store_events=self.store_events,
1931
+ )
1948
1932
 
1949
- if stream_events:
1950
- yield completed_event
1933
+ # Set the run status to completed
1934
+ run_response.status = RunStatus.completed
1951
1935
 
1952
- if yield_run_output:
1953
- yield run_response
1936
+ # 10. Cleanup and store the run response
1937
+ self._cleanup_and_store(run_response=run_response, session=session)
1954
1938
 
1955
- # Log Team Telemetry
1956
- self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1939
+ if stream_events:
1940
+ yield completed_event
1957
1941
 
1958
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
1942
+ if yield_run_output:
1943
+ yield run_response
1959
1944
 
1960
- except RunCancelledException as e:
1961
- # Handle run cancellation during streaming
1962
- log_info(f"Team run {run_response.run_id} was cancelled during streaming")
1963
- run_response.status = RunStatus.cancelled
1964
- run_response.content = str(e)
1945
+ # Log Team Telemetry
1946
+ self._log_team_telemetry(session_id=session.session_id, run_id=run_response.run_id)
1965
1947
 
1966
- # Yield the cancellation event
1967
- yield handle_event( # type: ignore
1968
- create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
1969
- run_response,
1970
- events_to_skip=self.events_to_skip,
1971
- store_events=self.store_events,
1972
- )
1948
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
1973
1949
 
1974
- # Add the RunOutput to Team Session even when cancelled
1975
- self._cleanup_and_store(run_response=run_response, session=session)
1976
- finally:
1977
- # Always disconnect connectable tools
1978
- self._disconnect_connectable_tools()
1979
- # Always clean up the run tracking
1980
- cleanup_run(run_response.run_id) # type: ignore
1950
+ # Disconnect tools and clean up run tracking
1951
+ self._disconnect_connectable_tools()
1952
+ cleanup_run(run_response.run_id) # type: ignore
1981
1953
 
1982
1954
  @overload
1983
1955
  def run(
@@ -2002,7 +1974,7 @@ class Team:
2002
1974
  dependencies: Optional[Dict[str, Any]] = None,
2003
1975
  metadata: Optional[Dict[str, Any]] = None,
2004
1976
  debug_mode: Optional[bool] = None,
2005
- output_schema: Optional[Type[BaseModel]] = None,
1977
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2006
1978
  **kwargs: Any,
2007
1979
  ) -> TeamRunOutput: ...
2008
1980
 
@@ -2032,7 +2004,7 @@ class Team:
2032
2004
  debug_mode: Optional[bool] = None,
2033
2005
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2034
2006
  yield_run_output: bool = False,
2035
- output_schema: Optional[Type[BaseModel]] = None,
2007
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2036
2008
  **kwargs: Any,
2037
2009
  ) -> Iterator[Union[RunOutputEvent, TeamRunOutputEvent]]: ...
2038
2010
 
@@ -2061,16 +2033,15 @@ class Team:
2061
2033
  debug_mode: Optional[bool] = None,
2062
2034
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2063
2035
  yield_run_output: bool = False,
2064
- output_schema: Optional[Type[BaseModel]] = None,
2036
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2065
2037
  **kwargs: Any,
2066
2038
  ) -> Union[TeamRunOutput, Iterator[Union[RunOutputEvent, TeamRunOutputEvent]]]:
2067
2039
  """Run the Team and return the response."""
2068
2040
  if self._has_async_db():
2069
2041
  raise Exception("run() is not supported with an async DB. Please use arun() instead.")
2070
2042
 
2071
- # Set the id for the run and register it immediately for cancellation tracking
2043
+ # Set the id for the run
2072
2044
  run_id = run_id or str(uuid4())
2073
- register_run(run_id)
2074
2045
 
2075
2046
  # Initialize Team
2076
2047
  self.initialize_team(debug_mode=debug_mode)
@@ -2088,148 +2059,155 @@ class Team:
2088
2059
  )
2089
2060
  yield_run_output = yield_run_output or yield_run_response # For backwards compatibility
2090
2061
 
2091
- background_tasks = kwargs.pop("background_tasks", None)
2092
- if background_tasks is not None:
2093
- from fastapi import BackgroundTasks
2094
-
2095
- background_tasks: BackgroundTasks = background_tasks # type: ignore
2096
-
2097
- # Validate input against input_schema if provided
2098
- validated_input = self._validate_input(input)
2062
+ # Set up retry logic
2063
+ num_attempts = self.retries + 1
2064
+ for attempt in range(num_attempts):
2065
+ if num_attempts > 1:
2066
+ log_debug(f"Retrying Team run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
2099
2067
 
2100
- # Normalise hook & guardails
2101
- if not self._hooks_normalised:
2102
- if self.pre_hooks:
2103
- self.pre_hooks = normalize_pre_hooks(self.pre_hooks) # type: ignore
2104
- if self.post_hooks:
2105
- self.post_hooks = normalize_post_hooks(self.post_hooks) # type: ignore
2106
- self._hooks_normalised = True
2068
+ try:
2069
+ # Register run for cancellation tracking
2070
+ register_run(run_id) # type: ignore
2107
2071
 
2108
- session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
2072
+ background_tasks = kwargs.pop("background_tasks", None)
2073
+ if background_tasks is not None:
2074
+ from fastapi import BackgroundTasks
2109
2075
 
2110
- image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
2111
- images=images, videos=videos, audios=audio, files=files
2112
- )
2076
+ background_tasks: BackgroundTasks = background_tasks # type: ignore
2113
2077
 
2114
- # Create RunInput to capture the original user input
2115
- run_input = TeamRunInput(
2116
- input_content=validated_input,
2117
- images=image_artifacts,
2118
- videos=video_artifacts,
2119
- audios=audio_artifacts,
2120
- files=file_artifacts,
2121
- )
2078
+ # Validate input against input_schema if provided
2079
+ validated_input = self._validate_input(input)
2122
2080
 
2123
- # Read existing session from database
2124
- team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2125
- self._update_metadata(session=team_session)
2081
+ # Normalise hook & guardails
2082
+ if not self._hooks_normalised:
2083
+ if self.pre_hooks:
2084
+ self.pre_hooks = normalize_pre_hooks(self.pre_hooks) # type: ignore
2085
+ if self.post_hooks:
2086
+ self.post_hooks = normalize_post_hooks(self.post_hooks) # type: ignore
2087
+ self._hooks_normalised = True
2126
2088
 
2127
- # Initialize session state
2128
- session_state = self._initialize_session_state(
2129
- session_state=session_state if session_state is not None else {},
2130
- user_id=user_id,
2131
- session_id=session_id,
2132
- run_id=run_id,
2133
- )
2134
- # Update session state from DB
2135
- session_state = self._load_session_state(session=team_session, session_state=session_state)
2089
+ session_id, user_id = self._initialize_session(session_id=session_id, user_id=user_id)
2136
2090
 
2137
- # Determine runtime dependencies
2138
- dependencies = dependencies if dependencies is not None else self.dependencies
2091
+ image_artifacts, video_artifacts, audio_artifacts, file_artifacts = validate_media_object_id(
2092
+ images=images, videos=videos, audios=audio, files=files
2093
+ )
2139
2094
 
2140
- # Resolve output_schema parameter takes precedence, then fall back to self.output_schema
2141
- if output_schema is None:
2142
- output_schema = self.output_schema
2095
+ # Create RunInput to capture the original user input
2096
+ run_input = TeamRunInput(
2097
+ input_content=validated_input,
2098
+ images=image_artifacts,
2099
+ videos=video_artifacts,
2100
+ audios=audio_artifacts,
2101
+ files=file_artifacts,
2102
+ )
2143
2103
 
2144
- # Initialize run context
2145
- run_context = run_context or RunContext(
2146
- run_id=run_id,
2147
- session_id=session_id,
2148
- user_id=user_id,
2149
- session_state=session_state,
2150
- dependencies=dependencies,
2151
- output_schema=output_schema,
2152
- )
2153
- # output_schema parameter takes priority, even if run_context was provided
2154
- run_context.output_schema = output_schema
2104
+ # Read existing session from database
2105
+ team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2106
+ self._update_metadata(session=team_session)
2155
2107
 
2156
- # Resolve callable dependencies if present
2157
- if run_context.dependencies is not None:
2158
- self._resolve_run_dependencies(run_context=run_context)
2108
+ # Initialize session state
2109
+ session_state = self._initialize_session_state(
2110
+ session_state=session_state if session_state is not None else {},
2111
+ user_id=user_id,
2112
+ session_id=session_id,
2113
+ run_id=run_id,
2114
+ )
2115
+ # Update session state from DB
2116
+ session_state = self._load_session_state(session=team_session, session_state=session_state)
2159
2117
 
2160
- # Determine runtime context parameters
2161
- add_dependencies = (
2162
- add_dependencies_to_context if add_dependencies_to_context is not None else self.add_dependencies_to_context
2163
- )
2164
- add_session_state = (
2165
- add_session_state_to_context
2166
- if add_session_state_to_context is not None
2167
- else self.add_session_state_to_context
2168
- )
2169
- add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
2118
+ # Determine runtime dependencies
2119
+ dependencies = dependencies if dependencies is not None else self.dependencies
2170
2120
 
2171
- # When filters are passed manually
2172
- if self.knowledge_filters or knowledge_filters:
2173
- run_context.knowledge_filters = self._get_effective_filters(knowledge_filters)
2121
+ # Resolve output_schema parameter takes precedence, then fall back to self.output_schema
2122
+ if output_schema is None:
2123
+ output_schema = self.output_schema
2174
2124
 
2175
- # Use stream override value when necessary
2176
- if stream is None:
2177
- stream = False if self.stream is None else self.stream
2125
+ # Initialize run context
2126
+ run_context = run_context or RunContext(
2127
+ run_id=run_id,
2128
+ session_id=session_id,
2129
+ user_id=user_id,
2130
+ session_state=session_state,
2131
+ dependencies=dependencies,
2132
+ output_schema=output_schema,
2133
+ )
2134
+ # output_schema parameter takes priority, even if run_context was provided
2135
+ run_context.output_schema = output_schema
2136
+
2137
+ # Resolve callable dependencies if present
2138
+ if run_context.dependencies is not None:
2139
+ self._resolve_run_dependencies(run_context=run_context)
2140
+
2141
+ # Determine runtime context parameters
2142
+ add_dependencies = (
2143
+ add_dependencies_to_context
2144
+ if add_dependencies_to_context is not None
2145
+ else self.add_dependencies_to_context
2146
+ )
2147
+ add_session_state = (
2148
+ add_session_state_to_context
2149
+ if add_session_state_to_context is not None
2150
+ else self.add_session_state_to_context
2151
+ )
2152
+ add_history = (
2153
+ add_history_to_context if add_history_to_context is not None else self.add_history_to_context
2154
+ )
2178
2155
 
2179
- # Considering both stream_events and stream_intermediate_steps (deprecated)
2180
- stream_events = stream_events or stream_intermediate_steps
2156
+ # When filters are passed manually
2157
+ if self.knowledge_filters or knowledge_filters:
2158
+ run_context.knowledge_filters = self._get_effective_filters(knowledge_filters)
2181
2159
 
2182
- # Can't stream events if streaming is disabled
2183
- if stream is False:
2184
- stream_events = False
2160
+ # Use stream override value when necessary
2161
+ if stream is None:
2162
+ stream = False if self.stream is None else self.stream
2185
2163
 
2186
- if stream_events is None:
2187
- stream_events = False if self.stream_events is None else self.stream_events
2164
+ # Considering both stream_events and stream_intermediate_steps (deprecated)
2165
+ stream_events = stream_events or stream_intermediate_steps
2188
2166
 
2189
- self.model = cast(Model, self.model)
2167
+ # Can't stream events if streaming is disabled
2168
+ if stream is False:
2169
+ stream_events = False
2190
2170
 
2191
- if self.metadata is not None:
2192
- if metadata is None:
2193
- metadata = self.metadata
2194
- else:
2195
- merge_dictionaries(metadata, self.metadata)
2171
+ if stream_events is None:
2172
+ stream_events = False if self.stream_events is None else self.stream_events
2196
2173
 
2197
- if metadata:
2198
- run_context.metadata = metadata
2174
+ self.model = cast(Model, self.model)
2199
2175
 
2200
- # Configure the model for runs
2201
- response_format: Optional[Union[Dict, Type[BaseModel]]] = (
2202
- self._get_response_format(run_context=run_context) if self.parser_model is None else None
2203
- )
2176
+ if self.metadata is not None:
2177
+ if metadata is None:
2178
+ metadata = self.metadata
2179
+ else:
2180
+ merge_dictionaries(metadata, self.metadata)
2204
2181
 
2205
- # Create a new run_response for this attempt
2206
- run_response = TeamRunOutput(
2207
- run_id=run_id,
2208
- session_id=session_id,
2209
- user_id=user_id,
2210
- team_id=self.id,
2211
- team_name=self.name,
2212
- metadata=run_context.metadata,
2213
- session_state=run_context.session_state,
2214
- input=run_input,
2215
- )
2182
+ if metadata:
2183
+ run_context.metadata = metadata
2216
2184
 
2217
- run_response.model = self.model.id if self.model is not None else None
2218
- run_response.model_provider = self.model.provider if self.model is not None else None
2185
+ # Configure the model for runs
2186
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = (
2187
+ self._get_response_format(run_context=run_context) if self.parser_model is None else None
2188
+ )
2219
2189
 
2220
- # Start the run metrics timer, to calculate the run duration
2221
- run_response.metrics = Metrics()
2222
- run_response.metrics.start_timer()
2190
+ # Create a new run_response for this attempt
2191
+ run_response = TeamRunOutput(
2192
+ run_id=run_id,
2193
+ session_id=session_id,
2194
+ user_id=user_id,
2195
+ team_id=self.id,
2196
+ team_name=self.name,
2197
+ metadata=run_context.metadata,
2198
+ session_state=run_context.session_state,
2199
+ input=run_input,
2200
+ )
2223
2201
 
2224
- # Set up retry logic
2225
- num_attempts = self.retries + 1
2202
+ run_response.model = self.model.id if self.model is not None else None
2203
+ run_response.model_provider = self.model.provider if self.model is not None else None
2204
+
2205
+ # Start the run metrics timer, to calculate the run duration
2206
+ run_response.metrics = Metrics()
2207
+ run_response.metrics.start_timer()
2226
2208
 
2227
- for attempt in range(num_attempts):
2228
- log_debug(f"Retrying Team run {run_id}. Attempt {attempt + 1} of {num_attempts}...")
2229
- # Run the team
2230
- try:
2231
2209
  if stream:
2232
- response_iterator = self._run_stream(
2210
+ return self._run_stream(
2233
2211
  run_response=run_response,
2234
2212
  run_context=run_context,
2235
2213
  session=team_session,
@@ -2243,9 +2221,8 @@ class Team:
2243
2221
  debug_mode=debug_mode,
2244
2222
  background_tasks=background_tasks,
2245
2223
  **kwargs,
2246
- )
2224
+ ) # type: ignore
2247
2225
 
2248
- return response_iterator # type: ignore
2249
2226
  else:
2250
2227
  return self._run(
2251
2228
  run_response=run_response,
@@ -2260,24 +2237,67 @@ class Team:
2260
2237
  background_tasks=background_tasks,
2261
2238
  **kwargs,
2262
2239
  )
2240
+ except InputCheckError as e:
2241
+ run_response.status = RunStatus.error
2242
+ if stream:
2243
+ run_error = create_team_run_error_event(
2244
+ run_response,
2245
+ error=str(e),
2246
+ error_id=e.error_id,
2247
+ error_type=e.type,
2248
+ additional_data=e.additional_data,
2249
+ )
2250
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2251
+ if run_response.content is None:
2252
+ run_response.content = str(e)
2263
2253
 
2264
- except (InputCheckError, OutputCheckError) as e:
2265
2254
  log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2266
- raise e
2267
- except KeyboardInterrupt:
2268
- run_response.content = "Operation cancelled by user"
2255
+
2256
+ if stream:
2257
+ return generator_wrapper(run_error) # type: ignore
2258
+ else:
2259
+ return run_response
2260
+ except RunCancelledException as e:
2261
+ # Handle run cancellation during streaming
2262
+ log_info(f"Team run {run_response.run_id} was cancelled during streaming")
2269
2263
  run_response.status = RunStatus.cancelled
2264
+ run_response.content = str(e)
2270
2265
 
2266
+ # Yield the cancellation event
2271
2267
  if stream:
2272
- return generator_wrapper( # type: ignore
2273
- create_team_run_cancelled_event(
2274
- from_run_response=run_response, reason="Operation cancelled by user"
2275
- )
2268
+ cancelled_run_error = handle_event(
2269
+ create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2270
+ run_response,
2271
+ events_to_skip=self.events_to_skip,
2272
+ store_events=self.store_events,
2276
2273
  )
2274
+ return generator_wrapper(cancelled_run_error) # type: ignore
2275
+ else:
2276
+ return run_response
2277
+ except (InputCheckError, OutputCheckError) as e:
2278
+ run_response.status = RunStatus.error
2279
+
2280
+ if stream:
2281
+ # Add error event to list of events
2282
+ run_error = create_team_run_error_event(
2283
+ run_response,
2284
+ error=str(e),
2285
+ error_id=e.error_id,
2286
+ error_type=e.type,
2287
+ additional_data=e.additional_data,
2288
+ )
2289
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2290
+
2291
+ if run_response.content is None:
2292
+ run_response.content = str(e)
2293
+
2294
+ log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2295
+
2296
+ if stream:
2297
+ return generator_wrapper(run_error) # type: ignore
2277
2298
  else:
2278
2299
  return run_response
2279
2300
  except Exception as e:
2280
- # Check if this is the last attempt
2281
2301
  if attempt < num_attempts - 1:
2282
2302
  # Calculate delay with exponential backoff if enabled
2283
2303
  if self.exponential_backoff:
@@ -2287,12 +2307,23 @@ class Team:
2287
2307
 
2288
2308
  log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2289
2309
  time.sleep(delay)
2310
+ continue
2311
+
2312
+ run_response.status = RunStatus.error
2313
+ if stream:
2314
+ run_error = create_team_run_error_event(run_response, error=str(e))
2315
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2316
+ if run_response.content is None:
2317
+ run_response.content = str(e)
2318
+
2319
+ log_error(f"Error in Team run: {str(e)}")
2320
+
2321
+ if stream:
2322
+ return generator_wrapper(run_error) # type: ignore
2290
2323
  else:
2291
- # Final attempt failed - re-raise the exception
2292
- log_error(f"All {num_attempts} attempts failed. Final error: {str(e)}")
2293
- raise e
2324
+ return run_response
2294
2325
 
2295
- # If we get here, all retries failed
2326
+ # If we get here, all retries failed (shouldn't happen with current logic)
2296
2327
  raise Exception(f"Failed after {num_attempts} attempts.")
2297
2328
 
2298
2329
  async def _arun(
@@ -2329,219 +2360,276 @@ class Team:
2329
2360
  15. Cleanup and store (scrub, add to session, calculate metrics, save session)
2330
2361
  """
2331
2362
  log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2363
+ memory_task = None
2332
2364
 
2333
- if run_context.dependencies is not None:
2334
- await self._aresolve_run_dependencies(run_context=run_context)
2365
+ # Set up retry logic
2366
+ num_attempts = self.retries + 1
2367
+ for attempt in range(num_attempts):
2368
+ if num_attempts > 1:
2369
+ log_debug(f"Retrying Team run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2335
2370
 
2336
- # 1. Read or create session. Reads from the database if provided.
2337
- if self._has_async_db():
2338
- team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2339
- else:
2340
- team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2371
+ try:
2372
+ if run_context.dependencies is not None:
2373
+ await self._aresolve_run_dependencies(run_context=run_context)
2341
2374
 
2342
- # 2. Update metadata and session state
2343
- self._update_metadata(session=team_session)
2344
- # Initialize session state
2345
- run_context.session_state = self._initialize_session_state(
2346
- session_state=run_context.session_state if run_context.session_state is not None else {},
2347
- user_id=user_id,
2348
- session_id=session_id,
2349
- run_id=run_response.run_id,
2350
- )
2351
- # Update session state from DB
2352
- if run_context.session_state is not None:
2353
- run_context.session_state = self._load_session_state(
2354
- session=team_session, session_state=run_context.session_state
2355
- )
2375
+ # 1. Read or create session. Reads from the database if provided.
2376
+ if self._has_async_db():
2377
+ team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2378
+ else:
2379
+ team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2356
2380
 
2357
- run_input = cast(TeamRunInput, run_response.input)
2381
+ # 2. Update metadata and session state
2382
+ self._update_metadata(session=team_session)
2383
+ # Initialize session state
2384
+ run_context.session_state = self._initialize_session_state(
2385
+ session_state=run_context.session_state if run_context.session_state is not None else {},
2386
+ user_id=user_id,
2387
+ session_id=session_id,
2388
+ run_id=run_response.run_id,
2389
+ )
2390
+ # Update session state from DB
2391
+ if run_context.session_state is not None:
2392
+ run_context.session_state = self._load_session_state(
2393
+ session=team_session, session_state=run_context.session_state
2394
+ )
2358
2395
 
2359
- # 3. Execute pre-hooks after session is loaded but before processing starts
2360
- if self.pre_hooks is not None:
2361
- pre_hook_iterator = self._aexecute_pre_hooks(
2362
- hooks=self.pre_hooks, # type: ignore
2363
- run_response=run_response,
2364
- run_context=run_context,
2365
- run_input=run_input,
2366
- session=team_session,
2367
- user_id=user_id,
2368
- debug_mode=debug_mode,
2369
- background_tasks=background_tasks,
2370
- **kwargs,
2371
- )
2396
+ run_input = cast(TeamRunInput, run_response.input)
2372
2397
 
2373
- # Consume the async iterator without yielding
2374
- async for _ in pre_hook_iterator:
2375
- pass
2398
+ # 3. Execute pre-hooks after session is loaded but before processing starts
2399
+ if self.pre_hooks is not None:
2400
+ pre_hook_iterator = self._aexecute_pre_hooks(
2401
+ hooks=self.pre_hooks, # type: ignore
2402
+ run_response=run_response,
2403
+ run_context=run_context,
2404
+ run_input=run_input,
2405
+ session=team_session,
2406
+ user_id=user_id,
2407
+ debug_mode=debug_mode,
2408
+ background_tasks=background_tasks,
2409
+ **kwargs,
2410
+ )
2376
2411
 
2377
- # 4. Determine tools for model
2378
- team_run_context: Dict[str, Any] = {}
2379
- self.model = cast(Model, self.model)
2380
- await self._check_and_refresh_mcp_tools()
2381
- _tools = self._determine_tools_for_model(
2382
- model=self.model,
2383
- run_response=run_response,
2384
- run_context=run_context,
2385
- team_run_context=team_run_context,
2386
- session=team_session,
2387
- user_id=user_id,
2388
- async_mode=True,
2389
- input_message=run_input.input_content,
2390
- images=run_input.images,
2391
- videos=run_input.videos,
2392
- audio=run_input.audios,
2393
- files=run_input.files,
2394
- debug_mode=debug_mode,
2395
- add_history_to_context=add_history_to_context,
2396
- add_dependencies_to_context=add_dependencies_to_context,
2397
- add_session_state_to_context=add_session_state_to_context,
2398
- stream=False,
2399
- stream_events=False,
2400
- )
2412
+ # Consume the async iterator without yielding
2413
+ async for _ in pre_hook_iterator:
2414
+ pass
2401
2415
 
2402
- # 5. Prepare run messages
2403
- run_messages = await self._aget_run_messages(
2404
- run_response=run_response,
2405
- run_context=run_context,
2406
- session=team_session, # type: ignore
2407
- user_id=user_id,
2408
- input_message=run_input.input_content,
2409
- audio=run_input.audios,
2410
- images=run_input.images,
2411
- videos=run_input.videos,
2412
- files=run_input.files,
2413
- add_history_to_context=add_history_to_context,
2414
- add_dependencies_to_context=add_dependencies_to_context,
2415
- add_session_state_to_context=add_session_state_to_context,
2416
- tools=_tools,
2417
- **kwargs,
2418
- )
2416
+ # 4. Determine tools for model
2417
+ team_run_context: Dict[str, Any] = {}
2418
+ self.model = cast(Model, self.model)
2419
+ await self._check_and_refresh_mcp_tools()
2420
+ _tools = self._determine_tools_for_model(
2421
+ model=self.model,
2422
+ run_response=run_response,
2423
+ run_context=run_context,
2424
+ team_run_context=team_run_context,
2425
+ session=team_session,
2426
+ user_id=user_id,
2427
+ async_mode=True,
2428
+ input_message=run_input.input_content,
2429
+ images=run_input.images,
2430
+ videos=run_input.videos,
2431
+ audio=run_input.audios,
2432
+ files=run_input.files,
2433
+ debug_mode=debug_mode,
2434
+ add_history_to_context=add_history_to_context,
2435
+ add_dependencies_to_context=add_dependencies_to_context,
2436
+ add_session_state_to_context=add_session_state_to_context,
2437
+ stream=False,
2438
+ stream_events=False,
2439
+ )
2419
2440
 
2420
- self.model = cast(Model, self.model)
2421
- log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2441
+ # 5. Prepare run messages
2442
+ run_messages = await self._aget_run_messages(
2443
+ run_response=run_response,
2444
+ run_context=run_context,
2445
+ session=team_session, # type: ignore
2446
+ user_id=user_id,
2447
+ input_message=run_input.input_content,
2448
+ audio=run_input.audios,
2449
+ images=run_input.images,
2450
+ videos=run_input.videos,
2451
+ files=run_input.files,
2452
+ add_history_to_context=add_history_to_context,
2453
+ add_dependencies_to_context=add_dependencies_to_context,
2454
+ add_session_state_to_context=add_session_state_to_context,
2455
+ tools=_tools,
2456
+ **kwargs,
2457
+ )
2422
2458
 
2423
- # 6. Start memory creation in background task
2424
- memory_task = None
2425
- if (
2426
- run_messages.user_message is not None
2427
- and self.memory_manager is not None
2428
- and self.enable_user_memories
2429
- and not self.enable_agentic_memory
2430
- ):
2431
- log_debug("Starting memory creation in background task.")
2432
- memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2459
+ self.model = cast(Model, self.model)
2460
+ log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2433
2461
 
2434
- try:
2435
- raise_if_cancelled(run_response.run_id) # type: ignore
2436
- # 7. Reason about the task if reasoning is enabled
2437
- await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2462
+ # 6. Start memory creation in background task
2463
+ memory_task = None
2464
+ if (
2465
+ run_messages.user_message is not None
2466
+ and self.memory_manager is not None
2467
+ and self.enable_user_memories
2468
+ and not self.enable_agentic_memory
2469
+ ):
2470
+ log_debug("Starting memory creation in background task.")
2471
+ memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2438
2472
 
2439
- # Check for cancellation before model call
2440
- raise_if_cancelled(run_response.run_id) # type: ignore
2473
+ raise_if_cancelled(run_response.run_id) # type: ignore
2474
+ # 7. Reason about the task if reasoning is enabled
2475
+ await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
2441
2476
 
2442
- # 8. Get the model response for the team leader
2443
- model_response = await self.model.aresponse(
2444
- messages=run_messages.messages,
2445
- tools=_tools,
2446
- tool_choice=self.tool_choice,
2447
- tool_call_limit=self.tool_call_limit,
2448
- response_format=response_format,
2449
- send_media_to_model=self.send_media_to_model,
2450
- run_response=run_response,
2451
- compression_manager=self.compression_manager if self.compress_tool_results else None,
2452
- ) # type: ignore
2477
+ # Check for cancellation before model call
2478
+ raise_if_cancelled(run_response.run_id) # type: ignore
2453
2479
 
2454
- # Check for cancellation after model call
2455
- raise_if_cancelled(run_response.run_id) # type: ignore
2480
+ # 8. Get the model response for the team leader
2481
+ model_response = await self.model.aresponse(
2482
+ messages=run_messages.messages,
2483
+ tools=_tools,
2484
+ tool_choice=self.tool_choice,
2485
+ tool_call_limit=self.tool_call_limit,
2486
+ response_format=response_format,
2487
+ send_media_to_model=self.send_media_to_model,
2488
+ run_response=run_response,
2489
+ compression_manager=self.compression_manager if self.compress_tool_results else None,
2490
+ ) # type: ignore
2456
2491
 
2457
- # If an output model is provided, generate output using the output model
2458
- await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
2492
+ # Check for cancellation after model call
2493
+ raise_if_cancelled(run_response.run_id) # type: ignore
2459
2494
 
2460
- # If a parser model is provided, structure the response separately
2461
- await self._aparse_response_with_parser_model(
2462
- model_response=model_response, run_messages=run_messages, run_context=run_context
2463
- )
2495
+ # If an output model is provided, generate output using the output model
2496
+ await self._agenerate_response_with_output_model(
2497
+ model_response=model_response, run_messages=run_messages
2498
+ )
2464
2499
 
2465
- # 9. Update TeamRunOutput with the model response
2466
- self._update_run_response(
2467
- model_response=model_response,
2468
- run_response=run_response,
2469
- run_messages=run_messages,
2470
- run_context=run_context,
2471
- )
2500
+ # If a parser model is provided, structure the response separately
2501
+ await self._aparse_response_with_parser_model(
2502
+ model_response=model_response, run_messages=run_messages, run_context=run_context
2503
+ )
2472
2504
 
2473
- # 10. Store media if enabled
2474
- if self.store_media:
2475
- store_media_util(run_response, model_response)
2505
+ # 9. Update TeamRunOutput with the model response
2506
+ self._update_run_response(
2507
+ model_response=model_response,
2508
+ run_response=run_response,
2509
+ run_messages=run_messages,
2510
+ run_context=run_context,
2511
+ )
2476
2512
 
2477
- # 11. Convert response to structured format
2478
- self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
2513
+ # 10. Store media if enabled
2514
+ if self.store_media:
2515
+ store_media_util(run_response, model_response)
2479
2516
 
2480
- # 12. Execute post-hooks after output is generated but before response is returned
2481
- if self.post_hooks is not None:
2482
- async for _ in self._aexecute_post_hooks(
2483
- hooks=self.post_hooks, # type: ignore
2484
- run_output=run_response,
2485
- run_context=run_context,
2486
- session=team_session,
2487
- user_id=user_id,
2488
- debug_mode=debug_mode,
2489
- background_tasks=background_tasks,
2490
- **kwargs,
2491
- ):
2492
- pass
2517
+ # 11. Convert response to structured format
2518
+ self._convert_response_to_structured_format(run_response=run_response, run_context=run_context)
2493
2519
 
2494
- raise_if_cancelled(run_response.run_id) # type: ignore
2520
+ # 12. Execute post-hooks after output is generated but before response is returned
2521
+ if self.post_hooks is not None:
2522
+ async for _ in self._aexecute_post_hooks(
2523
+ hooks=self.post_hooks, # type: ignore
2524
+ run_output=run_response,
2525
+ run_context=run_context,
2526
+ session=team_session,
2527
+ user_id=user_id,
2528
+ debug_mode=debug_mode,
2529
+ background_tasks=background_tasks,
2530
+ **kwargs,
2531
+ ):
2532
+ pass
2495
2533
 
2496
- # 13. Wait for background memory creation
2497
- await await_for_open_threads(memory_task=memory_task)
2534
+ raise_if_cancelled(run_response.run_id) # type: ignore
2498
2535
 
2499
- raise_if_cancelled(run_response.run_id) # type: ignore
2500
- # 14. Create session summary
2501
- if self.session_summary_manager is not None:
2502
- # Upsert the RunOutput to Team Session before creating the session summary
2503
- team_session.upsert_run(run_response=run_response)
2504
- try:
2505
- await self.session_summary_manager.acreate_session_summary(session=team_session)
2506
- except Exception as e:
2507
- log_warning(f"Error in session summary creation: {str(e)}")
2536
+ # 13. Wait for background memory creation
2537
+ await await_for_open_threads(memory_task=memory_task)
2508
2538
 
2509
- raise_if_cancelled(run_response.run_id) # type: ignore
2510
- run_response.status = RunStatus.completed
2539
+ raise_if_cancelled(run_response.run_id) # type: ignore
2540
+ # 14. Create session summary
2541
+ if self.session_summary_manager is not None:
2542
+ # Upsert the RunOutput to Team Session before creating the session summary
2543
+ team_session.upsert_run(run_response=run_response)
2544
+ try:
2545
+ await self.session_summary_manager.acreate_session_summary(session=team_session)
2546
+ except Exception as e:
2547
+ log_warning(f"Error in session summary creation: {str(e)}")
2511
2548
 
2512
- # 15. Cleanup and store the run response and session
2513
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2549
+ raise_if_cancelled(run_response.run_id) # type: ignore
2550
+ run_response.status = RunStatus.completed
2514
2551
 
2515
- # Log Team Telemetry
2516
- await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2552
+ # 15. Cleanup and store the run response and session
2553
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2517
2554
 
2518
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2555
+ # Log Team Telemetry
2556
+ await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2519
2557
 
2520
- return run_response
2521
- except RunCancelledException as e:
2522
- # Handle run cancellation
2523
- log_info(f"Run {run_response.run_id} was cancelled")
2524
- run_response.content = str(e)
2525
- run_response.status = RunStatus.cancelled
2558
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2526
2559
 
2527
- # Cleanup and store the run response and session
2528
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2560
+ return run_response
2529
2561
 
2530
- return run_response
2531
- finally:
2532
- # Always disconnect connectable tools
2533
- self._disconnect_connectable_tools()
2534
- await self._disconnect_mcp_tools()
2535
- # Cancel the memory task if it's still running
2536
- if memory_task is not None and not memory_task.done():
2537
- memory_task.cancel()
2538
- try:
2539
- await memory_task
2540
- except asyncio.CancelledError:
2541
- pass
2562
+ except RunCancelledException as e:
2563
+ # Handle run cancellation
2564
+ log_info(f"Run {run_response.run_id} was cancelled")
2565
+ run_response.content = str(e)
2566
+ run_response.status = RunStatus.cancelled
2567
+
2568
+ # Cleanup and store the run response and session
2569
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2570
+
2571
+ return run_response
2572
+
2573
+ except (InputCheckError, OutputCheckError) as e:
2574
+ run_response.status = RunStatus.error
2575
+ run_error = create_team_run_error_event(
2576
+ run_response,
2577
+ error=str(e),
2578
+ error_id=e.error_id,
2579
+ error_type=e.type,
2580
+ additional_data=e.additional_data,
2581
+ )
2582
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2583
+ if run_response.content is None:
2584
+ run_response.content = str(e)
2542
2585
 
2543
- # Always clean up the run tracking
2544
- cleanup_run(run_response.run_id) # type: ignore
2586
+ log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2587
+
2588
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2589
+
2590
+ return run_response
2591
+
2592
+ except Exception as e:
2593
+ if attempt < num_attempts - 1:
2594
+ # Calculate delay with exponential backoff if enabled
2595
+ if self.exponential_backoff:
2596
+ delay = self.delay_between_retries * (2**attempt)
2597
+ else:
2598
+ delay = self.delay_between_retries
2599
+
2600
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2601
+ time.sleep(delay)
2602
+ continue
2603
+
2604
+ run_error = create_team_run_error_event(run_response, error=str(e))
2605
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2606
+
2607
+ if run_response.content is None:
2608
+ run_response.content = str(e)
2609
+
2610
+ log_error(f"Error in Team run: {str(e)}")
2611
+
2612
+ # Cleanup and store the run response and session
2613
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2614
+
2615
+ return run_response
2616
+
2617
+ finally:
2618
+ # Always disconnect connectable tools
2619
+ self._disconnect_connectable_tools()
2620
+ await self._disconnect_mcp_tools()
2621
+ # Cancel the memory task if it's still running
2622
+ if memory_task is not None and not memory_task.done():
2623
+ memory_task.cancel()
2624
+ try:
2625
+ await memory_task
2626
+ except asyncio.CancelledError:
2627
+ pass
2628
+
2629
+ # Always clean up the run tracking
2630
+ cleanup_run(run_response.run_id) # type: ignore
2631
+
2632
+ return run_response
2545
2633
 
2546
2634
  async def _arun_stream(
2547
2635
  self,
@@ -2578,307 +2666,364 @@ class Team:
2578
2666
  13. Cleanup and store (scrub, add to session, calculate metrics, save session)
2579
2667
  """
2580
2668
 
2581
- # 1. Resolve dependencies
2582
- if run_context.dependencies is not None:
2583
- await self._aresolve_run_dependencies(run_context=run_context)
2584
-
2585
- # 2. Read or create session. Reads from the database if provided.
2586
- if self._has_async_db():
2587
- team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2588
- else:
2589
- team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2590
-
2591
- # 3. Update metadata and session state
2592
- self._update_metadata(session=team_session)
2593
- # Initialize session state
2594
- run_context.session_state = self._initialize_session_state(
2595
- session_state=run_context.session_state if run_context.session_state is not None else {},
2596
- user_id=user_id,
2597
- session_id=session_id,
2598
- run_id=run_response.run_id,
2599
- )
2600
- # Update session state from DB
2601
- if run_context.session_state is not None:
2602
- run_context.session_state = self._load_session_state(
2603
- session=team_session, session_state=run_context.session_state
2604
- ) # type: ignore
2669
+ memory_task = None
2605
2670
 
2606
- # 4. Execute pre-hooks
2607
- run_input = cast(TeamRunInput, run_response.input)
2608
- self.model = cast(Model, self.model)
2609
- if self.pre_hooks is not None:
2610
- pre_hook_iterator = self._aexecute_pre_hooks(
2611
- hooks=self.pre_hooks, # type: ignore
2612
- run_response=run_response,
2613
- run_context=run_context,
2614
- run_input=run_input,
2615
- session=team_session,
2616
- user_id=user_id,
2617
- debug_mode=debug_mode,
2618
- stream_events=stream_events,
2619
- background_tasks=background_tasks,
2620
- **kwargs,
2621
- )
2622
- async for pre_hook_event in pre_hook_iterator:
2623
- yield pre_hook_event
2671
+ # Set up retry logic
2672
+ num_attempts = self.retries + 1
2673
+ for attempt in range(num_attempts):
2674
+ if num_attempts > 1:
2675
+ log_debug(f"Retrying Team run {run_response.run_id}. Attempt {attempt + 1} of {num_attempts}...")
2624
2676
 
2625
- # 5. Determine tools for model
2626
- team_run_context: Dict[str, Any] = {}
2627
- self.model = cast(Model, self.model)
2628
- await self._check_and_refresh_mcp_tools()
2629
- _tools = self._determine_tools_for_model(
2630
- model=self.model,
2631
- run_response=run_response,
2632
- run_context=run_context,
2633
- team_run_context=team_run_context,
2634
- session=team_session, # type: ignore
2635
- user_id=user_id,
2636
- async_mode=True,
2637
- input_message=run_input.input_content,
2638
- images=run_input.images,
2639
- videos=run_input.videos,
2640
- audio=run_input.audios,
2641
- files=run_input.files,
2642
- debug_mode=debug_mode,
2643
- add_history_to_context=add_history_to_context,
2644
- add_dependencies_to_context=add_dependencies_to_context,
2645
- add_session_state_to_context=add_session_state_to_context,
2646
- stream=True,
2647
- stream_events=stream_events,
2648
- )
2677
+ try:
2678
+ # 1. Resolve dependencies
2679
+ if run_context.dependencies is not None:
2680
+ await self._aresolve_run_dependencies(run_context=run_context)
2649
2681
 
2650
- # 6. Prepare run messages
2651
- run_messages = await self._aget_run_messages(
2652
- run_response=run_response,
2653
- run_context=run_context,
2654
- session=team_session, # type: ignore
2655
- user_id=user_id,
2656
- input_message=run_input.input_content,
2657
- audio=run_input.audios,
2658
- images=run_input.images,
2659
- videos=run_input.videos,
2660
- files=run_input.files,
2661
- add_history_to_context=add_history_to_context,
2662
- add_dependencies_to_context=add_dependencies_to_context,
2663
- add_session_state_to_context=add_session_state_to_context,
2664
- tools=_tools,
2665
- **kwargs,
2666
- )
2682
+ # 2. Read or create session. Reads from the database if provided.
2683
+ if self._has_async_db():
2684
+ team_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
2685
+ else:
2686
+ team_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
2667
2687
 
2668
- log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2688
+ # 3. Update metadata and session state
2689
+ self._update_metadata(session=team_session)
2690
+ # Initialize session state
2691
+ run_context.session_state = self._initialize_session_state(
2692
+ session_state=run_context.session_state if run_context.session_state is not None else {},
2693
+ user_id=user_id,
2694
+ session_id=session_id,
2695
+ run_id=run_response.run_id,
2696
+ )
2697
+ # Update session state from DB
2698
+ if run_context.session_state is not None:
2699
+ run_context.session_state = self._load_session_state(
2700
+ session=team_session, session_state=run_context.session_state
2701
+ ) # type: ignore
2702
+
2703
+ # 4. Execute pre-hooks
2704
+ run_input = cast(TeamRunInput, run_response.input)
2705
+ self.model = cast(Model, self.model)
2706
+ if self.pre_hooks is not None:
2707
+ pre_hook_iterator = self._aexecute_pre_hooks(
2708
+ hooks=self.pre_hooks, # type: ignore
2709
+ run_response=run_response,
2710
+ run_context=run_context,
2711
+ run_input=run_input,
2712
+ session=team_session,
2713
+ user_id=user_id,
2714
+ debug_mode=debug_mode,
2715
+ stream_events=stream_events,
2716
+ background_tasks=background_tasks,
2717
+ **kwargs,
2718
+ )
2719
+ async for pre_hook_event in pre_hook_iterator:
2720
+ yield pre_hook_event
2721
+
2722
+ # 5. Determine tools for model
2723
+ team_run_context: Dict[str, Any] = {}
2724
+ self.model = cast(Model, self.model)
2725
+ await self._check_and_refresh_mcp_tools()
2726
+ _tools = self._determine_tools_for_model(
2727
+ model=self.model,
2728
+ run_response=run_response,
2729
+ run_context=run_context,
2730
+ team_run_context=team_run_context,
2731
+ session=team_session, # type: ignore
2732
+ user_id=user_id,
2733
+ async_mode=True,
2734
+ input_message=run_input.input_content,
2735
+ images=run_input.images,
2736
+ videos=run_input.videos,
2737
+ audio=run_input.audios,
2738
+ files=run_input.files,
2739
+ debug_mode=debug_mode,
2740
+ add_history_to_context=add_history_to_context,
2741
+ add_dependencies_to_context=add_dependencies_to_context,
2742
+ add_session_state_to_context=add_session_state_to_context,
2743
+ stream=True,
2744
+ stream_events=stream_events,
2745
+ )
2669
2746
 
2670
- # 7. Start memory creation in background task
2671
- memory_task = None
2672
- if (
2673
- run_messages.user_message is not None
2674
- and self.memory_manager is not None
2675
- and self.enable_user_memories
2676
- and not self.enable_agentic_memory
2677
- ):
2678
- log_debug("Starting memory creation in background task.")
2679
- memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2747
+ # 6. Prepare run messages
2748
+ run_messages = await self._aget_run_messages(
2749
+ run_response=run_response,
2750
+ run_context=run_context,
2751
+ session=team_session, # type: ignore
2752
+ user_id=user_id,
2753
+ input_message=run_input.input_content,
2754
+ audio=run_input.audios,
2755
+ images=run_input.images,
2756
+ videos=run_input.videos,
2757
+ files=run_input.files,
2758
+ add_history_to_context=add_history_to_context,
2759
+ add_dependencies_to_context=add_dependencies_to_context,
2760
+ add_session_state_to_context=add_session_state_to_context,
2761
+ tools=_tools,
2762
+ **kwargs,
2763
+ )
2680
2764
 
2681
- try:
2682
- # Considering both stream_events and stream_intermediate_steps (deprecated)
2683
- stream_events = stream_events or stream_intermediate_steps
2765
+ log_debug(f"Team Run Start: {run_response.run_id}", center=True)
2684
2766
 
2685
- # Yield the run started event
2686
- if stream_events:
2687
- yield handle_event( # type: ignore
2688
- create_team_run_started_event(from_run_response=run_response),
2689
- run_response,
2690
- events_to_skip=self.events_to_skip,
2691
- store_events=self.store_events,
2692
- )
2767
+ # 7. Start memory creation in background task
2768
+ memory_task = None
2769
+ if (
2770
+ run_messages.user_message is not None
2771
+ and self.memory_manager is not None
2772
+ and self.enable_user_memories
2773
+ and not self.enable_agentic_memory
2774
+ ):
2775
+ log_debug("Starting memory creation in background task.")
2776
+ memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2693
2777
 
2694
- # 8. Reason about the task if reasoning is enabled
2695
- async for item in self._ahandle_reasoning_stream(
2696
- run_response=run_response,
2697
- run_messages=run_messages,
2698
- stream_events=stream_events,
2699
- ):
2700
- raise_if_cancelled(run_response.run_id) # type: ignore
2701
- yield item
2778
+ # Considering both stream_events and stream_intermediate_steps (deprecated)
2779
+ stream_events = stream_events or stream_intermediate_steps
2702
2780
 
2703
- # Check for cancellation before model processing
2704
- raise_if_cancelled(run_response.run_id) # type: ignore
2781
+ # Yield the run started event
2782
+ if stream_events:
2783
+ yield handle_event( # type: ignore
2784
+ create_team_run_started_event(from_run_response=run_response),
2785
+ run_response,
2786
+ events_to_skip=self.events_to_skip,
2787
+ store_events=self.store_events,
2788
+ )
2705
2789
 
2706
- # 9. Get a response from the model
2707
- if self.output_model is None:
2708
- async for event in self._ahandle_model_response_stream(
2709
- session=team_session,
2790
+ # 8. Reason about the task if reasoning is enabled
2791
+ async for item in self._ahandle_reasoning_stream(
2710
2792
  run_response=run_response,
2711
2793
  run_messages=run_messages,
2712
- tools=_tools,
2713
- response_format=response_format,
2714
2794
  stream_events=stream_events,
2715
- session_state=run_context.session_state,
2716
- run_context=run_context,
2717
2795
  ):
2718
2796
  raise_if_cancelled(run_response.run_id) # type: ignore
2719
- yield event
2720
- else:
2721
- async for event in self._ahandle_model_response_stream(
2797
+ yield item
2798
+
2799
+ # Check for cancellation before model processing
2800
+ raise_if_cancelled(run_response.run_id) # type: ignore
2801
+
2802
+ # 9. Get a response from the model
2803
+ if self.output_model is None:
2804
+ async for event in self._ahandle_model_response_stream(
2805
+ session=team_session,
2806
+ run_response=run_response,
2807
+ run_messages=run_messages,
2808
+ tools=_tools,
2809
+ response_format=response_format,
2810
+ stream_events=stream_events,
2811
+ session_state=run_context.session_state,
2812
+ run_context=run_context,
2813
+ ):
2814
+ raise_if_cancelled(run_response.run_id) # type: ignore
2815
+ yield event
2816
+ else:
2817
+ async for event in self._ahandle_model_response_stream(
2818
+ session=team_session,
2819
+ run_response=run_response,
2820
+ run_messages=run_messages,
2821
+ tools=_tools,
2822
+ response_format=response_format,
2823
+ stream_events=stream_events,
2824
+ session_state=run_context.session_state,
2825
+ run_context=run_context,
2826
+ ):
2827
+ raise_if_cancelled(run_response.run_id) # type: ignore
2828
+ from agno.run.team import IntermediateRunContentEvent, RunContentEvent
2829
+
2830
+ if isinstance(event, RunContentEvent):
2831
+ if stream_events:
2832
+ yield IntermediateRunContentEvent(
2833
+ content=event.content,
2834
+ content_type=event.content_type,
2835
+ )
2836
+ else:
2837
+ yield event
2838
+
2839
+ async for event in self._agenerate_response_with_output_model_stream(
2840
+ session=team_session,
2841
+ run_response=run_response,
2842
+ run_messages=run_messages,
2843
+ stream_events=stream_events,
2844
+ ):
2845
+ raise_if_cancelled(run_response.run_id) # type: ignore
2846
+ yield event
2847
+
2848
+ # Check for cancellation after model processing
2849
+ raise_if_cancelled(run_response.run_id) # type: ignore
2850
+
2851
+ # 10. Parse response with parser model if provided
2852
+ async for event in self._aparse_response_with_parser_model_stream(
2722
2853
  session=team_session,
2723
2854
  run_response=run_response,
2724
- run_messages=run_messages,
2725
- tools=_tools,
2726
- response_format=response_format,
2727
2855
  stream_events=stream_events,
2728
- session_state=run_context.session_state,
2729
2856
  run_context=run_context,
2730
2857
  ):
2731
- raise_if_cancelled(run_response.run_id) # type: ignore
2732
- from agno.run.team import IntermediateRunContentEvent, RunContentEvent
2858
+ yield event
2733
2859
 
2734
- if isinstance(event, RunContentEvent):
2735
- if stream_events:
2736
- yield IntermediateRunContentEvent(
2737
- content=event.content,
2738
- content_type=event.content_type,
2739
- )
2740
- else:
2860
+ # Yield RunContentCompletedEvent
2861
+ if stream_events:
2862
+ yield handle_event( # type: ignore
2863
+ create_team_run_content_completed_event(from_run_response=run_response),
2864
+ run_response,
2865
+ events_to_skip=self.events_to_skip,
2866
+ store_events=self.store_events,
2867
+ )
2868
+
2869
+ # Execute post-hooks after output is generated but before response is returned
2870
+ if self.post_hooks is not None:
2871
+ async for event in self._aexecute_post_hooks(
2872
+ hooks=self.post_hooks, # type: ignore
2873
+ run_output=run_response,
2874
+ run_context=run_context,
2875
+ session=team_session,
2876
+ user_id=user_id,
2877
+ debug_mode=debug_mode,
2878
+ stream_events=stream_events,
2879
+ background_tasks=background_tasks,
2880
+ **kwargs,
2881
+ ):
2741
2882
  yield event
2742
2883
 
2743
- async for event in self._agenerate_response_with_output_model_stream(
2744
- session=team_session,
2884
+ raise_if_cancelled(run_response.run_id) # type: ignore
2885
+ # 11. Wait for background memory creation
2886
+ async for event in await_for_thread_tasks_stream(
2745
2887
  run_response=run_response,
2746
- run_messages=run_messages,
2888
+ memory_task=memory_task,
2747
2889
  stream_events=stream_events,
2890
+ events_to_skip=self.events_to_skip, # type: ignore
2891
+ store_events=self.store_events,
2748
2892
  ):
2749
- raise_if_cancelled(run_response.run_id) # type: ignore
2750
2893
  yield event
2751
2894
 
2752
- # Check for cancellation after model processing
2753
- raise_if_cancelled(run_response.run_id) # type: ignore
2895
+ raise_if_cancelled(run_response.run_id) # type: ignore
2754
2896
 
2755
- # 10. Parse response with parser model if provided
2756
- async for event in self._aparse_response_with_parser_model_stream(
2757
- session=team_session, run_response=run_response, stream_events=stream_events, run_context=run_context
2758
- ):
2759
- yield event
2897
+ # 12. Create session summary
2898
+ if self.session_summary_manager is not None:
2899
+ # Upsert the RunOutput to Team Session before creating the session summary
2900
+ team_session.upsert_run(run_response=run_response)
2760
2901
 
2761
- # Yield RunContentCompletedEvent
2762
- if stream_events:
2763
- yield handle_event( # type: ignore
2764
- create_team_run_content_completed_event(from_run_response=run_response),
2902
+ if stream_events:
2903
+ yield handle_event( # type: ignore
2904
+ create_team_session_summary_started_event(from_run_response=run_response),
2905
+ run_response,
2906
+ events_to_skip=self.events_to_skip,
2907
+ store_events=self.store_events,
2908
+ )
2909
+ try:
2910
+ await self.session_summary_manager.acreate_session_summary(session=team_session)
2911
+ except Exception as e:
2912
+ log_warning(f"Error in session summary creation: {str(e)}")
2913
+ if stream_events:
2914
+ yield handle_event( # type: ignore
2915
+ create_team_session_summary_completed_event(
2916
+ from_run_response=run_response, session_summary=team_session.summary
2917
+ ),
2918
+ run_response,
2919
+ events_to_skip=self.events_to_skip,
2920
+ store_events=self.store_events,
2921
+ )
2922
+
2923
+ raise_if_cancelled(run_response.run_id) # type: ignore
2924
+
2925
+ # Create the run completed event
2926
+ completed_event = handle_event(
2927
+ create_team_run_completed_event(from_run_response=run_response),
2765
2928
  run_response,
2766
2929
  events_to_skip=self.events_to_skip,
2767
2930
  store_events=self.store_events,
2768
2931
  )
2769
2932
 
2770
- # Execute post-hooks after output is generated but before response is returned
2771
- if self.post_hooks is not None:
2772
- async for event in self._aexecute_post_hooks(
2773
- hooks=self.post_hooks, # type: ignore
2774
- run_output=run_response,
2775
- run_context=run_context,
2776
- session=team_session,
2777
- user_id=user_id,
2778
- debug_mode=debug_mode,
2779
- stream_events=stream_events,
2780
- background_tasks=background_tasks,
2781
- **kwargs,
2782
- ):
2783
- yield event
2933
+ # Set the run status to completed
2934
+ run_response.status = RunStatus.completed
2784
2935
 
2785
- raise_if_cancelled(run_response.run_id) # type: ignore
2786
- # 11. Wait for background memory creation
2787
- async for event in await_for_thread_tasks_stream(
2788
- run_response=run_response,
2789
- memory_task=memory_task,
2790
- stream_events=stream_events,
2791
- events_to_skip=self.events_to_skip, # type: ignore
2792
- store_events=self.store_events,
2793
- ):
2794
- yield event
2936
+ # 13. Cleanup and store the run response and session
2937
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2795
2938
 
2796
- raise_if_cancelled(run_response.run_id) # type: ignore
2939
+ if stream_events:
2940
+ yield completed_event
2797
2941
 
2798
- # 12. Create session summary
2799
- if self.session_summary_manager is not None:
2800
- # Upsert the RunOutput to Team Session before creating the session summary
2801
- team_session.upsert_run(run_response=run_response)
2942
+ if yield_run_output:
2943
+ yield run_response
2802
2944
 
2803
- if stream_events:
2804
- yield handle_event( # type: ignore
2805
- create_team_session_summary_started_event(from_run_response=run_response),
2806
- run_response,
2807
- events_to_skip=self.events_to_skip,
2808
- store_events=self.store_events,
2809
- )
2810
- try:
2811
- await self.session_summary_manager.acreate_session_summary(session=team_session)
2812
- except Exception as e:
2813
- log_warning(f"Error in session summary creation: {str(e)}")
2814
- if stream_events:
2815
- yield handle_event( # type: ignore
2816
- create_team_session_summary_completed_event(
2817
- from_run_response=run_response, session_summary=team_session.summary
2818
- ),
2819
- run_response,
2820
- events_to_skip=self.events_to_skip,
2821
- store_events=self.store_events,
2822
- )
2945
+ # Log Team Telemetry
2946
+ await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2823
2947
 
2824
- raise_if_cancelled(run_response.run_id) # type: ignore
2948
+ log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2825
2949
 
2826
- # Create the run completed event
2827
- completed_event = handle_event(
2828
- create_team_run_completed_event(from_run_response=run_response),
2829
- run_response,
2830
- events_to_skip=self.events_to_skip,
2831
- store_events=self.store_events,
2832
- )
2950
+ except RunCancelledException as e:
2951
+ # Handle run cancellation during async streaming
2952
+ log_info(f"Team run {run_response.run_id} was cancelled during async streaming")
2953
+ run_response.status = RunStatus.cancelled
2954
+ run_response.content = str(e)
2833
2955
 
2834
- # Set the run status to completed
2835
- run_response.status = RunStatus.completed
2956
+ # Yield the cancellation event
2957
+ yield handle_event( # type: ignore
2958
+ create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2959
+ run_response,
2960
+ events_to_skip=self.events_to_skip,
2961
+ store_events=self.store_events,
2962
+ )
2836
2963
 
2837
- # 13. Cleanup and store the run response and session
2838
- await self._acleanup_and_store(run_response=run_response, session=team_session)
2964
+ # Cleanup and store the run response and session
2965
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2839
2966
 
2840
- if stream_events:
2841
- yield completed_event
2967
+ except (InputCheckError, OutputCheckError) as e:
2968
+ run_response.status = RunStatus.error
2969
+ run_error = create_team_run_error_event(
2970
+ run_response,
2971
+ error=str(e),
2972
+ error_id=e.error_id,
2973
+ error_type=e.type,
2974
+ additional_data=e.additional_data,
2975
+ )
2976
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
2977
+ if run_response.content is None:
2978
+ run_response.content = str(e)
2842
2979
 
2843
- if yield_run_output:
2844
- yield run_response
2980
+ log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
2845
2981
 
2846
- # Log Team Telemetry
2847
- await self._alog_team_telemetry(session_id=team_session.session_id, run_id=run_response.run_id)
2982
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
2848
2983
 
2849
- log_debug(f"Team Run End: {run_response.run_id}", center=True, symbol="*")
2984
+ yield run_error
2850
2985
 
2851
- except RunCancelledException as e:
2852
- # Handle run cancellation during async streaming
2853
- log_info(f"Team run {run_response.run_id} was cancelled during async streaming")
2854
- run_response.status = RunStatus.cancelled
2855
- run_response.content = str(e)
2986
+ break
2856
2987
 
2857
- # Yield the cancellation event
2858
- yield handle_event( # type: ignore
2859
- create_team_run_cancelled_event(from_run_response=run_response, reason=str(e)),
2860
- run_response,
2861
- events_to_skip=self.events_to_skip,
2862
- store_events=self.store_events,
2863
- )
2988
+ except Exception as e:
2989
+ if attempt < num_attempts - 1:
2990
+ # Calculate delay with exponential backoff if enabled
2991
+ if self.exponential_backoff:
2992
+ delay = self.delay_between_retries * (2**attempt)
2993
+ else:
2994
+ delay = self.delay_between_retries
2995
+
2996
+ log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
2997
+ time.sleep(delay)
2998
+ continue
2864
2999
 
2865
- # Cleanup and store the run response and session
2866
- await self._acleanup_and_store(run_response=run_response, session=team_session)
3000
+ run_response.status = RunStatus.error
3001
+ run_error = create_team_run_error_event(run_response, error=str(e))
3002
+ run_response.events = add_team_error_event(error=run_error, events=run_response.events)
3003
+ if run_response.content is None:
3004
+ run_response.content = str(e)
2867
3005
 
2868
- finally:
2869
- # Always disconnect connectable tools
2870
- self._disconnect_connectable_tools()
2871
- await self._disconnect_mcp_tools()
2872
- # Cancel the memory task if it's still running
2873
- if memory_task is not None and not memory_task.done():
2874
- memory_task.cancel()
2875
- try:
2876
- await memory_task
2877
- except asyncio.CancelledError:
2878
- pass
3006
+ log_error(f"Error in Team run: {str(e)}")
3007
+
3008
+ # Cleanup and store the run response and session
3009
+ await self._acleanup_and_store(run_response=run_response, session=team_session)
3010
+
3011
+ yield run_error
3012
+
3013
+ finally:
3014
+ # Always disconnect connectable tools
3015
+ self._disconnect_connectable_tools()
3016
+ await self._disconnect_mcp_tools()
3017
+ # Cancel the memory task if it's still running
3018
+ if memory_task is not None and not memory_task.done():
3019
+ memory_task.cancel()
3020
+ try:
3021
+ await memory_task
3022
+ except asyncio.CancelledError:
3023
+ pass
2879
3024
 
2880
- # Always clean up the run tracking
2881
- cleanup_run(run_response.run_id) # type: ignore
3025
+ # Always clean up the run tracking
3026
+ cleanup_run(run_response.run_id) # type: ignore
2882
3027
 
2883
3028
  @overload
2884
3029
  async def arun(
@@ -2904,7 +3049,7 @@ class Team:
2904
3049
  dependencies: Optional[Dict[str, Any]] = None,
2905
3050
  metadata: Optional[Dict[str, Any]] = None,
2906
3051
  debug_mode: Optional[bool] = None,
2907
- output_schema: Optional[Type[BaseModel]] = None,
3052
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2908
3053
  **kwargs: Any,
2909
3054
  ) -> TeamRunOutput: ...
2910
3055
 
@@ -2934,7 +3079,7 @@ class Team:
2934
3079
  debug_mode: Optional[bool] = None,
2935
3080
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2936
3081
  yield_run_output: bool = False,
2937
- output_schema: Optional[Type[BaseModel]] = None,
3082
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2938
3083
  **kwargs: Any,
2939
3084
  ) -> AsyncIterator[Union[RunOutputEvent, TeamRunOutputEvent]]: ...
2940
3085
 
@@ -2963,7 +3108,7 @@ class Team:
2963
3108
  debug_mode: Optional[bool] = None,
2964
3109
  yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2965
3110
  yield_run_output: bool = False,
2966
- output_schema: Optional[Type[BaseModel]] = None,
3111
+ output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
2967
3112
  **kwargs: Any,
2968
3113
  ) -> Union[TeamRunOutput, AsyncIterator[Union[RunOutputEvent, TeamRunOutputEvent]]]:
2969
3114
  """Run the Team asynchronously and return the response."""
@@ -3110,79 +3255,38 @@ class Team:
3110
3255
 
3111
3256
  yield_run_output = bool(yield_run_output or yield_run_response) # For backwards compatibility
3112
3257
 
3113
- # Resolve retry parameters
3114
- num_attempts = self.retries + 1
3115
-
3116
- for attempt in range(num_attempts):
3117
- # Run the team
3118
- try:
3119
- if stream:
3120
- return self._arun_stream( # type: ignore
3121
- input=validated_input,
3122
- run_response=run_response,
3123
- run_context=run_context,
3124
- session_id=session_id,
3125
- user_id=user_id,
3126
- add_history_to_context=add_history,
3127
- add_dependencies_to_context=add_dependencies,
3128
- add_session_state_to_context=add_session_state,
3129
- response_format=response_format,
3130
- stream_events=stream_events,
3131
- yield_run_output=yield_run_output,
3132
- debug_mode=debug_mode,
3133
- background_tasks=background_tasks,
3134
- **kwargs,
3135
- )
3136
- else:
3137
- return self._arun( # type: ignore
3138
- input=validated_input,
3139
- run_response=run_response,
3140
- run_context=run_context,
3141
- session_id=session_id,
3142
- user_id=user_id,
3143
- add_history_to_context=add_history,
3144
- add_dependencies_to_context=add_dependencies,
3145
- add_session_state_to_context=add_session_state,
3146
- response_format=response_format,
3147
- debug_mode=debug_mode,
3148
- background_tasks=background_tasks,
3149
- **kwargs,
3150
- )
3151
-
3152
- except (InputCheckError, OutputCheckError) as e:
3153
- log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
3154
- raise e
3155
- except KeyboardInterrupt:
3156
- run_response.content = "Operation cancelled by user"
3157
- run_response.status = RunStatus.cancelled
3158
-
3159
- if stream:
3160
- return async_generator_wrapper(
3161
- create_team_run_cancelled_event(
3162
- from_run_response=run_response, reason="Operation cancelled by user"
3163
- )
3164
- )
3165
- else:
3166
- return run_response
3167
- except Exception as e:
3168
- # Check if this is the last attempt
3169
- if attempt < num_attempts - 1:
3170
- # Calculate delay with exponential backoff if enabled
3171
- if self.exponential_backoff:
3172
- delay = self.delay_between_retries * (2**attempt)
3173
- else:
3174
- delay = self.delay_between_retries
3175
-
3176
- log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}. Retrying in {delay}s...")
3177
- time.sleep(delay)
3178
- continue
3179
- else:
3180
- # Final attempt failed - re-raise the exception
3181
- log_error(f"All {num_attempts} attempts failed. Final error: {str(e)}")
3182
- raise e
3183
-
3184
- # If we get here, all retries failed
3185
- raise Exception(f"Failed after {num_attempts} attempts.")
3258
+ if stream:
3259
+ return self._arun_stream( # type: ignore
3260
+ input=validated_input,
3261
+ run_response=run_response,
3262
+ run_context=run_context,
3263
+ session_id=session_id,
3264
+ user_id=user_id,
3265
+ add_history_to_context=add_history,
3266
+ add_dependencies_to_context=add_dependencies,
3267
+ add_session_state_to_context=add_session_state,
3268
+ response_format=response_format,
3269
+ stream_events=stream_events,
3270
+ yield_run_output=yield_run_output,
3271
+ debug_mode=debug_mode,
3272
+ background_tasks=background_tasks,
3273
+ **kwargs,
3274
+ )
3275
+ else:
3276
+ return self._arun( # type: ignore
3277
+ input=validated_input,
3278
+ run_response=run_response,
3279
+ run_context=run_context,
3280
+ session_id=session_id,
3281
+ user_id=user_id,
3282
+ add_history_to_context=add_history,
3283
+ add_dependencies_to_context=add_dependencies,
3284
+ add_session_state_to_context=add_session_state,
3285
+ response_format=response_format,
3286
+ debug_mode=debug_mode,
3287
+ background_tasks=background_tasks,
3288
+ **kwargs,
3289
+ )
3186
3290
 
3187
3291
  def _update_run_response(
3188
3292
  self,
@@ -3199,7 +3303,7 @@ class Team:
3199
3303
  # Update the run_response content with the structured output
3200
3304
  run_response.content = model_response.parsed
3201
3305
  # Update the run_response content_type with the structured output class name
3202
- run_response.content_type = output_schema.__name__
3306
+ run_response.content_type = "dict" if isinstance(output_schema, dict) else output_schema.__name__
3203
3307
  else:
3204
3308
  # Update the run_response content with the model response content
3205
3309
  if not run_response.content:
@@ -3498,12 +3602,16 @@ class Team:
3498
3602
  self._convert_response_to_structured_format(full_model_response, run_context=run_context)
3499
3603
  # Get output_schema from run_context
3500
3604
  output_schema = run_context.output_schema if run_context else None
3501
- content_type = output_schema.__name__ # type: ignore
3605
+ content_type = "dict" if isinstance(output_schema, dict) else output_schema.__name__ # type: ignore
3502
3606
  run_response.content_type = content_type
3503
3607
  elif self._member_response_model is not None:
3504
3608
  full_model_response.content = model_response_event.content
3505
3609
  self._convert_response_to_structured_format(full_model_response, run_context=run_context)
3506
- content_type = self._member_response_model.__name__ # type: ignore
3610
+ content_type = (
3611
+ "dict"
3612
+ if isinstance(self._member_response_model, dict)
3613
+ else self._member_response_model.__name__
3614
+ ) # type: ignore
3507
3615
  run_response.content_type = content_type
3508
3616
  elif isinstance(model_response_event.content, str):
3509
3617
  full_model_response.content = (full_model_response.content or "") + model_response_event.content
@@ -3755,41 +3863,71 @@ class Team:
3755
3863
  output_schema = run_context.output_schema if run_context else None
3756
3864
 
3757
3865
  # Convert the response to the structured format if needed
3758
- if output_schema is not None and not isinstance(run_response.content, output_schema):
3759
- if isinstance(run_response.content, str) and self.parse_response:
3760
- try:
3761
- parsed_response_content = parse_response_model_str(run_response.content, output_schema)
3762
-
3763
- # Update TeamRunOutput
3764
- if parsed_response_content is not None:
3765
- run_response.content = parsed_response_content
3866
+ if output_schema is not None:
3867
+ # If the output schema is a dict, do not convert it into a BaseModel
3868
+ if isinstance(output_schema, dict):
3869
+ if isinstance(run_response.content, dict):
3870
+ # Content is already a dict - just set content_type
3871
+ if hasattr(run_response, "content_type"):
3872
+ run_response.content_type = "dict"
3873
+ elif isinstance(run_response.content, str):
3874
+ parsed_dict = parse_response_dict_str(run_response.content)
3875
+ if parsed_dict is not None:
3876
+ run_response.content = parsed_dict
3766
3877
  if hasattr(run_response, "content_type"):
3767
- run_response.content_type = output_schema.__name__
3878
+ run_response.content_type = "dict"
3768
3879
  else:
3769
- log_warning("Failed to convert response to output_schema")
3770
- except Exception as e:
3771
- log_warning(f"Failed to convert response to output model: {e}")
3772
- else:
3773
- log_warning("Something went wrong. Team run response content is not a string")
3774
- elif self._member_response_model is not None and not isinstance(
3775
- run_response.content, self._member_response_model
3776
- ):
3777
- if isinstance(run_response.content, str):
3778
- try:
3779
- parsed_response_content = parse_response_model_str(
3780
- run_response.content, self._member_response_model
3781
- )
3782
- # Update TeamRunOutput
3783
- if parsed_response_content is not None:
3784
- run_response.content = parsed_response_content
3880
+ log_warning("Failed to parse JSON response")
3881
+ # If the output schema is a Pydantic model and parse_response is True, parse it into a BaseModel
3882
+ elif not isinstance(run_response.content, output_schema):
3883
+ if isinstance(run_response.content, str) and self.parse_response:
3884
+ try:
3885
+ parsed_response_content = parse_response_model_str(run_response.content, output_schema)
3886
+
3887
+ # Update TeamRunOutput
3888
+ if parsed_response_content is not None:
3889
+ run_response.content = parsed_response_content
3890
+ if hasattr(run_response, "content_type"):
3891
+ run_response.content_type = output_schema.__name__
3892
+ else:
3893
+ log_warning("Failed to convert response to output_schema")
3894
+ except Exception as e:
3895
+ log_warning(f"Failed to convert response to output model: {e}")
3896
+ else:
3897
+ log_warning("Something went wrong. Team run response content is not a string")
3898
+ elif self._member_response_model is not None:
3899
+ # Handle dict schema from member
3900
+ if isinstance(self._member_response_model, dict):
3901
+ if isinstance(run_response.content, dict):
3902
+ # Content is already a dict - just set content_type
3903
+ if hasattr(run_response, "content_type"):
3904
+ run_response.content_type = "dict"
3905
+ elif isinstance(run_response.content, str):
3906
+ parsed_dict = parse_response_dict_str(run_response.content)
3907
+ if parsed_dict is not None:
3908
+ run_response.content = parsed_dict
3785
3909
  if hasattr(run_response, "content_type"):
3786
- run_response.content_type = self._member_response_model.__name__
3910
+ run_response.content_type = "dict"
3787
3911
  else:
3788
- log_warning("Failed to convert response to output_schema")
3789
- except Exception as e:
3790
- log_warning(f"Failed to convert response to output model: {e}")
3791
- else:
3792
- log_warning("Something went wrong. Member run response content is not a string")
3912
+ log_warning("Failed to parse JSON response")
3913
+ # Handle Pydantic schema from member
3914
+ elif not isinstance(run_response.content, self._member_response_model):
3915
+ if isinstance(run_response.content, str):
3916
+ try:
3917
+ parsed_response_content = parse_response_model_str(
3918
+ run_response.content, self._member_response_model
3919
+ )
3920
+ # Update TeamRunOutput
3921
+ if parsed_response_content is not None:
3922
+ run_response.content = parsed_response_content
3923
+ if hasattr(run_response, "content_type"):
3924
+ run_response.content_type = self._member_response_model.__name__
3925
+ else:
3926
+ log_warning("Failed to convert response to output_schema")
3927
+ except Exception as e:
3928
+ log_warning(f"Failed to convert response to output model: {e}")
3929
+ else:
3930
+ log_warning("Something went wrong. Member run response content is not a string")
3793
3931
 
3794
3932
  def _cleanup_and_store(self, run_response: TeamRunOutput, session: TeamSession) -> None:
3795
3933
  # Scrub the stored run based on storage flags
@@ -3892,6 +4030,10 @@ class Team:
3892
4030
  elif model.supports_json_schema_outputs:
3893
4031
  if self.use_json_mode:
3894
4032
  log_debug("Setting Model.response_format to JSON response mode")
4033
+ # Handle JSON schema - pass through directly (user provides full provider format)
4034
+ if isinstance(output_schema, dict):
4035
+ return output_schema
4036
+ # Handle Pydantic schema
3895
4037
  return {
3896
4038
  "type": "json_schema",
3897
4039
  "json_schema": {
@@ -4750,281 +4892,77 @@ class Team:
4750
4892
 
4751
4893
  return updated_reasoning_content
4752
4894
 
4753
- def _reason(
4895
+ def _handle_reasoning_event(
4754
4896
  self,
4897
+ event: "ReasoningEvent", # type: ignore # noqa: F821
4755
4898
  run_response: TeamRunOutput,
4756
- run_messages: RunMessages,
4757
4899
  stream_events: bool,
4758
4900
  ) -> Iterator[TeamRunOutputEvent]:
4759
- if stream_events:
4760
- yield handle_event( # type: ignore
4761
- create_team_reasoning_started_event(from_run_response=run_response),
4762
- run_response,
4763
- events_to_skip=self.events_to_skip,
4764
- store_events=self.store_events,
4765
- )
4766
-
4767
- use_default_reasoning = False
4768
-
4769
- # Get the reasoning model
4770
- reasoning_model: Optional[Model] = self.reasoning_model
4771
- reasoning_model_provided = reasoning_model is not None
4772
- if reasoning_model is None and self.model is not None:
4773
- from copy import deepcopy
4774
-
4775
- reasoning_model = deepcopy(self.model)
4776
- if reasoning_model is None:
4777
- log_warning("Reasoning error. Reasoning model is None, continuing regular session...")
4778
- return
4779
-
4780
- # If a reasoning model is provided, use it to generate reasoning
4781
- if reasoning_model_provided:
4782
- from agno.reasoning.anthropic import is_anthropic_reasoning_model
4783
- from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
4784
- from agno.reasoning.deepseek import is_deepseek_reasoning_model
4785
- from agno.reasoning.gemini import is_gemini_reasoning_model
4786
- from agno.reasoning.groq import is_groq_reasoning_model
4787
- from agno.reasoning.helpers import get_reasoning_agent
4788
- from agno.reasoning.ollama import is_ollama_reasoning_model
4789
- from agno.reasoning.openai import is_openai_reasoning_model
4790
- from agno.reasoning.vertexai import is_vertexai_reasoning_model
4791
-
4792
- reasoning_agent = self.reasoning_agent or get_reasoning_agent(
4793
- reasoning_model=reasoning_model,
4794
- session_state=self.session_state,
4795
- dependencies=self.dependencies,
4796
- metadata=self.metadata,
4797
- )
4798
- is_deepseek = is_deepseek_reasoning_model(reasoning_model)
4799
- is_groq = is_groq_reasoning_model(reasoning_model)
4800
- is_openai = is_openai_reasoning_model(reasoning_model)
4801
- is_ollama = is_ollama_reasoning_model(reasoning_model)
4802
- is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
4803
- is_gemini = is_gemini_reasoning_model(reasoning_model)
4804
- is_anthropic = is_anthropic_reasoning_model(reasoning_model)
4805
- is_vertexai = is_vertexai_reasoning_model(reasoning_model)
4806
-
4807
- if (
4808
- is_deepseek
4809
- or is_groq
4810
- or is_openai
4811
- or is_ollama
4812
- or is_ai_foundry
4813
- or is_gemini
4814
- or is_anthropic
4815
- or is_vertexai
4816
- ):
4817
- reasoning_message: Optional[Message] = None
4818
- if is_deepseek:
4819
- from agno.reasoning.deepseek import get_deepseek_reasoning
4820
-
4821
- log_debug("Starting DeepSeek Reasoning", center=True, symbol="=")
4822
- reasoning_message = get_deepseek_reasoning(
4823
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4824
- )
4825
- elif is_groq:
4826
- from agno.reasoning.groq import get_groq_reasoning
4827
-
4828
- log_debug("Starting Groq Reasoning", center=True, symbol="=")
4829
- reasoning_message = get_groq_reasoning(
4830
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4831
- )
4832
- elif is_openai:
4833
- from agno.reasoning.openai import get_openai_reasoning
4834
-
4835
- log_debug("Starting OpenAI Reasoning", center=True, symbol="=")
4836
- reasoning_message = get_openai_reasoning(
4837
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4838
- )
4839
- elif is_ollama:
4840
- from agno.reasoning.ollama import get_ollama_reasoning
4841
-
4842
- log_debug("Starting Ollama Reasoning", center=True, symbol="=")
4843
- reasoning_message = get_ollama_reasoning(
4844
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4845
- )
4846
- elif is_ai_foundry:
4847
- from agno.reasoning.azure_ai_foundry import get_ai_foundry_reasoning
4848
-
4849
- log_debug("Starting Azure AI Foundry Reasoning", center=True, symbol="=")
4850
- reasoning_message = get_ai_foundry_reasoning(
4851
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4852
- )
4853
- elif is_gemini:
4854
- from agno.reasoning.gemini import get_gemini_reasoning
4855
-
4856
- log_debug("Starting Gemini Reasoning", center=True, symbol="=")
4857
- reasoning_message = get_gemini_reasoning(
4858
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4859
- )
4860
- elif is_anthropic:
4861
- from agno.reasoning.anthropic import get_anthropic_reasoning
4901
+ """
4902
+ Convert a ReasoningEvent from the ReasoningManager to Team-specific TeamRunOutputEvents.
4862
4903
 
4863
- log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
4864
- reasoning_message = get_anthropic_reasoning(
4865
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4866
- )
4867
- elif is_vertexai:
4868
- from agno.reasoning.vertexai import get_vertexai_reasoning
4904
+ This method handles the conversion of generic reasoning events to Team events,
4905
+ keeping the Team._reason() method clean and simple.
4906
+ """
4907
+ from agno.reasoning.manager import ReasoningEventType
4869
4908
 
4870
- log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
4871
- reasoning_message = get_vertexai_reasoning(
4872
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
4873
- )
4909
+ if event.event_type == ReasoningEventType.started:
4910
+ if stream_events:
4911
+ yield handle_event( # type: ignore
4912
+ create_team_reasoning_started_event(from_run_response=run_response),
4913
+ run_response,
4914
+ events_to_skip=self.events_to_skip,
4915
+ store_events=self.store_events,
4916
+ )
4874
4917
 
4875
- if reasoning_message is None:
4876
- log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
4877
- return
4918
+ elif event.event_type == ReasoningEventType.content_delta:
4919
+ if stream_events and event.reasoning_content:
4920
+ yield handle_event( # type: ignore
4921
+ create_team_reasoning_content_delta_event(
4922
+ from_run_response=run_response,
4923
+ reasoning_content=event.reasoning_content,
4924
+ ),
4925
+ run_response,
4926
+ events_to_skip=self.events_to_skip,
4927
+ store_events=self.store_events,
4928
+ )
4878
4929
 
4879
- run_messages.messages.append(reasoning_message)
4880
- # Add reasoning step to the Agent's run_response
4930
+ elif event.event_type == ReasoningEventType.step:
4931
+ if event.reasoning_step:
4932
+ # Update run_response with this step
4881
4933
  update_run_output_with_reasoning(
4882
4934
  run_response=run_response,
4883
- reasoning_steps=[ReasoningStep(result=reasoning_message.content)],
4884
- reasoning_agent_messages=[reasoning_message],
4935
+ reasoning_steps=[event.reasoning_step],
4936
+ reasoning_agent_messages=[],
4885
4937
  )
4886
4938
  if stream_events:
4939
+ updated_reasoning_content = self._format_reasoning_step_content(
4940
+ run_response=run_response,
4941
+ reasoning_step=event.reasoning_step,
4942
+ )
4887
4943
  yield handle_event( # type: ignore
4888
- create_team_reasoning_completed_event(
4944
+ create_team_reasoning_step_event(
4889
4945
  from_run_response=run_response,
4890
- content=ReasoningSteps(reasoning_steps=[ReasoningStep(result=reasoning_message.content)]),
4891
- content_type=ReasoningSteps.__name__,
4946
+ reasoning_step=event.reasoning_step,
4947
+ reasoning_content=updated_reasoning_content,
4892
4948
  ),
4893
4949
  run_response,
4894
4950
  events_to_skip=self.events_to_skip,
4895
4951
  store_events=self.store_events,
4896
4952
  )
4897
- else:
4898
- log_info(
4899
- f"Reasoning model: {reasoning_model.__class__.__name__} is not a native reasoning model, defaulting to manual Chain-of-Thought reasoning"
4900
- )
4901
- use_default_reasoning = True
4902
- # If no reasoning model is provided, use default reasoning
4903
- else:
4904
- use_default_reasoning = True
4905
-
4906
- if use_default_reasoning:
4907
- from agno.reasoning.default import get_default_reasoning_agent
4908
- from agno.reasoning.helpers import get_next_action, update_messages_with_reasoning
4909
-
4910
- # Get default reasoning agent
4911
- use_json_mode: bool = self.use_json_mode
4912
4953
 
4913
- reasoning_agent: Optional[Agent] = self.reasoning_agent # type: ignore
4914
- if reasoning_agent is None:
4915
- reasoning_agent = get_default_reasoning_agent(
4916
- reasoning_model=reasoning_model,
4917
- min_steps=self.reasoning_min_steps,
4918
- max_steps=self.reasoning_max_steps,
4919
- tool_call_limit=self.tool_call_limit,
4920
- telemetry=self.telemetry,
4921
- debug_mode=self.debug_mode,
4922
- debug_level=self.debug_level,
4923
- use_json_mode=use_json_mode,
4924
- session_state=self.session_state,
4925
- dependencies=self.dependencies,
4926
- metadata=self.metadata,
4954
+ elif event.event_type == ReasoningEventType.completed:
4955
+ if event.message and event.reasoning_steps:
4956
+ update_run_output_with_reasoning(
4957
+ run_response=run_response,
4958
+ reasoning_steps=event.reasoning_steps,
4959
+ reasoning_agent_messages=event.reasoning_messages,
4927
4960
  )
4928
-
4929
- # Validate reasoning agent
4930
- if reasoning_agent is None:
4931
- log_warning("Reasoning error. Reasoning agent is None, continuing regular session...")
4932
- return
4933
- # Ensure the reasoning agent response model is ReasoningSteps
4934
- if (
4935
- reasoning_agent.output_schema is not None
4936
- and not isinstance(reasoning_agent.output_schema, type)
4937
- and not issubclass(reasoning_agent.output_schema, ReasoningSteps)
4938
- ):
4939
- log_warning("Reasoning agent response model should be `ReasoningSteps`, continuing regular session...")
4940
- return
4941
- # Ensure the reasoning model and agent do not show tool calls
4942
-
4943
- step_count = 1
4944
- next_action = NextAction.CONTINUE
4945
- reasoning_messages: List[Message] = []
4946
- all_reasoning_steps: List[ReasoningStep] = []
4947
- log_debug("Starting Reasoning", center=True, symbol="=")
4948
- while next_action == NextAction.CONTINUE and step_count < self.reasoning_max_steps:
4949
- log_debug(f"Step {step_count}", center=True, symbol="-")
4950
- step_count += 1
4951
- try:
4952
- # Run the reasoning agent
4953
- reasoning_agent_response: RunOutput = reasoning_agent.run( # type: ignore
4954
- input=run_messages.get_input_messages()
4955
- )
4956
- if reasoning_agent_response.content is None or reasoning_agent_response.messages is None:
4957
- log_warning("Reasoning error. Reasoning response is empty, continuing regular session...")
4958
- break
4959
-
4960
- if isinstance(reasoning_agent_response.content, str):
4961
- log_warning(
4962
- "Reasoning error. Content is a string, not structured output. Continuing regular session..."
4963
- )
4964
- break
4965
-
4966
- if reasoning_agent_response.content.reasoning_steps is None:
4967
- log_warning("Reasoning error. Reasoning steps are empty, continuing regular session...")
4968
- break
4969
-
4970
- reasoning_steps: List[ReasoningStep] = reasoning_agent_response.content.reasoning_steps
4971
- all_reasoning_steps.extend(reasoning_steps)
4972
- # Yield reasoning steps
4973
- if stream_events:
4974
- for reasoning_step in reasoning_steps:
4975
- updated_reasoning_content = self._format_reasoning_step_content(
4976
- run_response, reasoning_step
4977
- )
4978
-
4979
- yield handle_event( # type: ignore
4980
- create_team_reasoning_step_event(
4981
- from_run_response=run_response,
4982
- reasoning_step=reasoning_step,
4983
- reasoning_content=updated_reasoning_content,
4984
- ),
4985
- run_response,
4986
- events_to_skip=self.events_to_skip,
4987
- store_events=self.store_events,
4988
- )
4989
-
4990
- # Find the index of the first assistant message
4991
- first_assistant_index = next(
4992
- (i for i, m in enumerate(reasoning_agent_response.messages) if m.role == "assistant"),
4993
- len(reasoning_agent_response.messages),
4994
- )
4995
- # Extract reasoning messages starting from the message after the first assistant message
4996
- reasoning_messages = reasoning_agent_response.messages[first_assistant_index:]
4997
-
4998
- # Add reasoning step to the Agent's run_response
4999
- update_run_output_with_reasoning(
5000
- run_response=run_response,
5001
- reasoning_steps=reasoning_steps,
5002
- reasoning_agent_messages=reasoning_agent_response.messages,
5003
- )
5004
-
5005
- # Get the next action
5006
- next_action = get_next_action(reasoning_steps[-1])
5007
- if next_action == NextAction.FINAL_ANSWER:
5008
- break
5009
- except Exception as e:
5010
- log_error(f"Reasoning error: {e}")
5011
- break
5012
-
5013
- log_debug(f"Total Reasoning steps: {len(all_reasoning_steps)}")
5014
- log_debug("Reasoning finished", center=True, symbol="=")
5015
-
5016
- # Update the messages_for_model to include reasoning messages
5017
- update_messages_with_reasoning(
5018
- run_messages=run_messages,
5019
- reasoning_messages=reasoning_messages,
5020
- )
5021
-
5022
- # Yield the final reasoning completed event
5023
4961
  if stream_events:
5024
4962
  yield handle_event( # type: ignore
5025
4963
  create_team_reasoning_completed_event(
5026
4964
  from_run_response=run_response,
5027
- content=ReasoningSteps(reasoning_steps=all_reasoning_steps),
4965
+ content=ReasoningSteps(reasoning_steps=event.reasoning_steps),
5028
4966
  content_type=ReasoningSteps.__name__,
5029
4967
  ),
5030
4968
  run_response,
@@ -5032,285 +4970,97 @@ class Team:
5032
4970
  store_events=self.store_events,
5033
4971
  )
5034
4972
 
5035
- async def _areason(
4973
+ elif event.event_type == ReasoningEventType.error:
4974
+ log_warning(f"Reasoning error. {event.error}, continuing regular session...")
4975
+
4976
+ def _reason(
5036
4977
  self,
5037
4978
  run_response: TeamRunOutput,
5038
4979
  run_messages: RunMessages,
5039
4980
  stream_events: bool,
5040
- ) -> AsyncIterator[TeamRunOutputEvent]:
5041
- if stream_events:
5042
- yield handle_event( # type: ignore
5043
- create_team_reasoning_started_event(from_run_response=run_response),
5044
- run_response,
5045
- events_to_skip=self.events_to_skip,
5046
- store_events=self.store_events,
5047
- )
4981
+ ) -> Iterator[TeamRunOutputEvent]:
4982
+ """
4983
+ Run reasoning using the ReasoningManager.
5048
4984
 
5049
- use_default_reasoning = False
4985
+ Handles both native reasoning models (DeepSeek, Anthropic, etc.) and
4986
+ default Chain-of-Thought reasoning with a clean, unified interface.
4987
+ """
4988
+ from agno.reasoning.manager import ReasoningConfig, ReasoningManager
5050
4989
 
5051
- # Get the reasoning model
4990
+ # Get the reasoning model (use copy of main model if not provided)
5052
4991
  reasoning_model: Optional[Model] = self.reasoning_model
5053
- reasoning_model_provided = reasoning_model is not None
5054
4992
  if reasoning_model is None and self.model is not None:
5055
4993
  from copy import deepcopy
5056
4994
 
5057
4995
  reasoning_model = deepcopy(self.model)
5058
- if reasoning_model is None:
5059
- log_warning("Reasoning error. Reasoning model is None, continuing regular session...")
5060
- return
5061
4996
 
5062
- # If a reasoning model is provided, use it to generate reasoning
5063
- if reasoning_model_provided:
5064
- from agno.reasoning.anthropic import is_anthropic_reasoning_model
5065
- from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
5066
- from agno.reasoning.deepseek import is_deepseek_reasoning_model
5067
- from agno.reasoning.gemini import is_gemini_reasoning_model
5068
- from agno.reasoning.groq import is_groq_reasoning_model
5069
- from agno.reasoning.helpers import get_reasoning_agent
5070
- from agno.reasoning.ollama import is_ollama_reasoning_model
5071
- from agno.reasoning.openai import is_openai_reasoning_model
5072
- from agno.reasoning.vertexai import is_vertexai_reasoning_model
5073
-
5074
- reasoning_agent = self.reasoning_agent or get_reasoning_agent(
4997
+ # Create reasoning manager with config
4998
+ manager = ReasoningManager(
4999
+ ReasoningConfig(
5075
5000
  reasoning_model=reasoning_model,
5001
+ reasoning_agent=self.reasoning_agent,
5002
+ min_steps=self.reasoning_min_steps,
5003
+ max_steps=self.reasoning_max_steps,
5004
+ tools=self.tools,
5005
+ tool_call_limit=self.tool_call_limit,
5006
+ use_json_mode=self.use_json_mode,
5007
+ telemetry=self.telemetry,
5008
+ debug_mode=self.debug_mode,
5009
+ debug_level=self.debug_level,
5076
5010
  session_state=self.session_state,
5077
5011
  dependencies=self.dependencies,
5078
5012
  metadata=self.metadata,
5079
5013
  )
5080
- is_deepseek = is_deepseek_reasoning_model(reasoning_model)
5081
- is_groq = is_groq_reasoning_model(reasoning_model)
5082
- is_openai = is_openai_reasoning_model(reasoning_model)
5083
- is_ollama = is_ollama_reasoning_model(reasoning_model)
5084
- is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
5085
- is_gemini = is_gemini_reasoning_model(reasoning_model)
5086
- is_anthropic = is_anthropic_reasoning_model(reasoning_model)
5087
- is_vertexai = is_vertexai_reasoning_model(reasoning_model)
5088
-
5089
- if (
5090
- is_deepseek
5091
- or is_groq
5092
- or is_openai
5093
- or is_ollama
5094
- or is_ai_foundry
5095
- or is_gemini
5096
- or is_anthropic
5097
- or is_vertexai
5098
- ):
5099
- reasoning_message: Optional[Message] = None
5100
- if is_deepseek:
5101
- from agno.reasoning.deepseek import aget_deepseek_reasoning
5102
-
5103
- log_debug("Starting DeepSeek Reasoning", center=True, symbol="=")
5104
- reasoning_message = await aget_deepseek_reasoning(
5105
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5106
- )
5107
- elif is_groq:
5108
- from agno.reasoning.groq import aget_groq_reasoning
5109
-
5110
- log_debug("Starting Groq Reasoning", center=True, symbol="=")
5111
- reasoning_message = await aget_groq_reasoning(
5112
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5113
- )
5114
- elif is_openai:
5115
- from agno.reasoning.openai import aget_openai_reasoning
5116
-
5117
- log_debug("Starting OpenAI Reasoning", center=True, symbol="=")
5118
- reasoning_message = await aget_openai_reasoning(
5119
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5120
- )
5121
- elif is_ollama:
5122
- from agno.reasoning.ollama import get_ollama_reasoning
5123
-
5124
- log_debug("Starting Ollama Reasoning", center=True, symbol="=")
5125
- reasoning_message = get_ollama_reasoning(
5126
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5127
- )
5128
- elif is_ai_foundry:
5129
- from agno.reasoning.azure_ai_foundry import get_ai_foundry_reasoning
5130
-
5131
- log_debug("Starting Azure AI Foundry Reasoning", center=True, symbol="=")
5132
- reasoning_message = get_ai_foundry_reasoning(
5133
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5134
- )
5135
- elif is_gemini:
5136
- from agno.reasoning.gemini import aget_gemini_reasoning
5137
-
5138
- log_debug("Starting Gemini Reasoning", center=True, symbol="=")
5139
- reasoning_message = await aget_gemini_reasoning(
5140
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5141
- )
5142
- elif is_anthropic:
5143
- from agno.reasoning.anthropic import aget_anthropic_reasoning
5144
-
5145
- log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
5146
- reasoning_message = await aget_anthropic_reasoning(
5147
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5148
- )
5149
- elif is_vertexai:
5150
- from agno.reasoning.vertexai import aget_vertexai_reasoning
5151
-
5152
- log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
5153
- reasoning_message = await aget_vertexai_reasoning(
5154
- reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
5155
- )
5156
-
5157
- if reasoning_message is None:
5158
- log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
5159
- return
5160
- run_messages.messages.append(reasoning_message)
5161
- # Add reasoning step to the Agent's run_response
5162
- update_run_output_with_reasoning(
5163
- run_response=run_response,
5164
- reasoning_steps=[ReasoningStep(result=reasoning_message.content)],
5165
- reasoning_agent_messages=[reasoning_message],
5166
- )
5167
- if stream_events:
5168
- yield handle_event( # type: ignore
5169
- create_team_reasoning_completed_event(
5170
- from_run_response=run_response,
5171
- content=ReasoningSteps(reasoning_steps=[ReasoningStep(result=reasoning_message.content)]),
5172
- content_type=ReasoningSteps.__name__,
5173
- ),
5174
- run_response,
5175
- events_to_skip=self.events_to_skip,
5176
- store_events=self.store_events,
5177
- )
5178
- else:
5179
- log_info(
5180
- f"Reasoning model: {reasoning_model.__class__.__name__} is not a native reasoning model, defaulting to manual Chain-of-Thought reasoning"
5181
- )
5182
- use_default_reasoning = True
5183
- # If no reasoning model is provided, use default reasoning
5184
- else:
5185
- use_default_reasoning = True
5186
-
5187
- if use_default_reasoning:
5188
- from agno.reasoning.default import get_default_reasoning_agent
5189
- from agno.reasoning.helpers import get_next_action, update_messages_with_reasoning
5190
-
5191
- # Get default reasoning agent
5192
- use_json_mode: bool = self.use_json_mode
5193
- reasoning_agent: Optional[Agent] = self.reasoning_agent # type: ignore
5194
- if reasoning_agent is None:
5195
- reasoning_agent = get_default_reasoning_agent( # type: ignore
5196
- reasoning_model=reasoning_model,
5197
- min_steps=self.reasoning_min_steps,
5198
- max_steps=self.reasoning_max_steps,
5199
- telemetry=self.telemetry,
5200
- debug_mode=self.debug_mode,
5201
- debug_level=self.debug_level,
5202
- use_json_mode=use_json_mode,
5203
- session_state=self.session_state,
5204
- dependencies=self.dependencies,
5205
- metadata=self.metadata,
5206
- )
5207
-
5208
- # Validate reasoning agent
5209
- if reasoning_agent is None:
5210
- log_warning("Reasoning error. Reasoning agent is None, continuing regular session...")
5211
- return
5212
- # Ensure the reasoning agent response model is ReasoningSteps
5213
- if (
5214
- reasoning_agent.output_schema is not None
5215
- and not isinstance(reasoning_agent.output_schema, type)
5216
- and not issubclass(reasoning_agent.output_schema, ReasoningSteps)
5217
- ):
5218
- log_warning("Reasoning agent response model should be `ReasoningSteps`, continuing regular session...")
5219
- return
5220
-
5221
- # Ensure the reasoning model and agent do not show tool calls
5222
-
5223
- step_count = 1
5224
- next_action = NextAction.CONTINUE
5225
- reasoning_messages: List[Message] = []
5226
- all_reasoning_steps: List[ReasoningStep] = []
5227
- log_debug("Starting Reasoning", center=True, symbol="=")
5228
- while next_action == NextAction.CONTINUE and step_count < self.reasoning_max_steps:
5229
- log_debug(f"Step {step_count}", center=True, symbol="-")
5230
- step_count += 1
5231
- try:
5232
- # Run the reasoning agent
5233
- reasoning_agent_response: RunOutput = await reasoning_agent.arun( # type: ignore
5234
- input=run_messages.get_input_messages()
5235
- )
5236
- if reasoning_agent_response.content is None or reasoning_agent_response.messages is None:
5237
- log_warning("Reasoning error. Reasoning response is empty, continuing regular session...")
5238
- break
5239
-
5240
- if isinstance(reasoning_agent_response.content, str):
5241
- log_warning(
5242
- "Reasoning error. Content is a string, not structured output. Continuing regular session..."
5243
- )
5244
- break
5245
-
5246
- if reasoning_agent_response.content.reasoning_steps is None:
5247
- log_warning("Reasoning error. Reasoning steps are empty, continuing regular session...")
5248
- break
5249
-
5250
- reasoning_steps: List[ReasoningStep] = reasoning_agent_response.content.reasoning_steps
5251
- all_reasoning_steps.extend(reasoning_steps)
5252
- # Yield reasoning steps
5253
- if stream_events:
5254
- for reasoning_step in reasoning_steps:
5255
- updated_reasoning_content = self._format_reasoning_step_content(
5256
- run_response, reasoning_step
5257
- )
5014
+ )
5258
5015
 
5259
- yield handle_event( # type: ignore
5260
- create_team_reasoning_step_event(
5261
- from_run_response=run_response,
5262
- reasoning_step=reasoning_step,
5263
- reasoning_content=updated_reasoning_content,
5264
- ),
5265
- run_response,
5266
- events_to_skip=self.events_to_skip,
5267
- store_events=self.store_events,
5268
- )
5016
+ # Use the unified reason() method and convert events
5017
+ for event in manager.reason(run_messages, stream=stream_events):
5018
+ yield from self._handle_reasoning_event(event, run_response, stream_events)
5269
5019
 
5270
- # Find the index of the first assistant message
5271
- first_assistant_index = next(
5272
- (i for i, m in enumerate(reasoning_agent_response.messages) if m.role == "assistant"),
5273
- len(reasoning_agent_response.messages),
5274
- )
5275
- # Extract reasoning messages starting from the message after the first assistant message
5276
- reasoning_messages = reasoning_agent_response.messages[first_assistant_index:]
5020
+ async def _areason(
5021
+ self,
5022
+ run_response: TeamRunOutput,
5023
+ run_messages: RunMessages,
5024
+ stream_events: bool,
5025
+ ) -> AsyncIterator[TeamRunOutputEvent]:
5026
+ """
5027
+ Run reasoning asynchronously using the ReasoningManager.
5277
5028
 
5278
- # Add reasoning step to the Agent's run_response
5279
- update_run_output_with_reasoning(
5280
- run_response=run_response,
5281
- reasoning_steps=reasoning_steps,
5282
- reasoning_agent_messages=reasoning_agent_response.messages,
5283
- )
5029
+ Handles both native reasoning models (DeepSeek, Anthropic, etc.) and
5030
+ default Chain-of-Thought reasoning with a clean, unified interface.
5031
+ """
5032
+ from agno.reasoning.manager import ReasoningConfig, ReasoningManager
5284
5033
 
5285
- # Get the next action
5286
- next_action = get_next_action(reasoning_steps[-1])
5287
- if next_action == NextAction.FINAL_ANSWER:
5288
- break
5289
- except Exception as e:
5290
- log_error(f"Reasoning error: {e}")
5291
- break
5034
+ # Get the reasoning model (use copy of main model if not provided)
5035
+ reasoning_model: Optional[Model] = self.reasoning_model
5036
+ if reasoning_model is None and self.model is not None:
5037
+ from copy import deepcopy
5292
5038
 
5293
- log_debug(f"Total Reasoning steps: {len(all_reasoning_steps)}")
5294
- log_debug("Reasoning finished", center=True, symbol="=")
5039
+ reasoning_model = deepcopy(self.model)
5295
5040
 
5296
- # Update the messages_for_model to include reasoning messages
5297
- update_messages_with_reasoning(
5298
- run_messages=run_messages,
5299
- reasoning_messages=reasoning_messages,
5041
+ # Create reasoning manager with config
5042
+ manager = ReasoningManager(
5043
+ ReasoningConfig(
5044
+ reasoning_model=reasoning_model,
5045
+ reasoning_agent=self.reasoning_agent,
5046
+ min_steps=self.reasoning_min_steps,
5047
+ max_steps=self.reasoning_max_steps,
5048
+ tools=self.tools,
5049
+ tool_call_limit=self.tool_call_limit,
5050
+ use_json_mode=self.use_json_mode,
5051
+ telemetry=self.telemetry,
5052
+ debug_mode=self.debug_mode,
5053
+ debug_level=self.debug_level,
5054
+ session_state=self.session_state,
5055
+ dependencies=self.dependencies,
5056
+ metadata=self.metadata,
5300
5057
  )
5058
+ )
5301
5059
 
5302
- # Yield the final reasoning completed event
5303
- if stream_events:
5304
- yield handle_event( # type: ignore # type: ignore
5305
- create_team_reasoning_completed_event(
5306
- from_run_response=run_response,
5307
- content=ReasoningSteps(reasoning_steps=all_reasoning_steps),
5308
- content_type=ReasoningSteps.__name__,
5309
- ),
5310
- run_response,
5311
- events_to_skip=self.events_to_skip,
5312
- store_events=self.store_events,
5313
- )
5060
+ # Use the unified areason() method and convert events
5061
+ async for event in manager.areason(run_messages, stream=stream_events):
5062
+ for output_event in self._handle_reasoning_event(event, run_response, stream_events):
5063
+ yield output_event
5314
5064
 
5315
5065
  def _resolve_run_dependencies(self, run_context: RunContext) -> None:
5316
5066
  from inspect import signature
@@ -7021,7 +6771,7 @@ class Team:
7021
6771
  log_error(f"Failed to convert sanitized context to JSON: {e}")
7022
6772
  return str(context)
7023
6773
 
7024
- def _get_json_output_prompt(self, output_schema: Optional[Type[BaseModel]] = None) -> str:
6774
+ def _get_json_output_prompt(self, output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None) -> str:
7025
6775
  """Return the JSON output prompt for the Agent.
7026
6776
 
7027
6777
  This is added to the system prompt when the output_schema is set and structured_outputs is False.
@@ -7038,7 +6788,11 @@ class Team:
7038
6788
  json_output_prompt += "\n<json_fields>"
7039
6789
  json_output_prompt += f"\n{json.dumps(output_schema)}"
7040
6790
  json_output_prompt += "\n</json_fields>"
7041
- elif issubclass(output_schema, BaseModel):
6791
+ elif isinstance(output_schema, dict):
6792
+ json_output_prompt += "\n<json_fields>"
6793
+ json_output_prompt += f"\n{json.dumps(output_schema)}"
6794
+ json_output_prompt += "\n</json_fields>"
6795
+ elif isinstance(output_schema, type) and issubclass(output_schema, BaseModel):
7042
6796
  json_schema = output_schema.model_json_schema()
7043
6797
  if json_schema is not None:
7044
6798
  response_model_properties = {}
@@ -7552,7 +7306,7 @@ class Team:
7552
7306
  member_agent_run_response.parent_run_id = run_response.run_id # type: ignore
7553
7307
 
7554
7308
  # Update the top-level team run_response tool call to have the run_id of the member run
7555
- if run_response.tools is not None:
7309
+ if run_response.tools is not None and member_agent_run_response is not None:
7556
7310
  for tool in run_response.tools:
7557
7311
  if tool.tool_name and tool.tool_name.lower() == "delegate_task_to_member":
7558
7312
  tool.child_run_id = member_agent_run_response.run_id # type: ignore
@@ -7782,9 +7536,9 @@ class Team:
7782
7536
  check_if_run_cancelled(member_agent_run_response_event)
7783
7537
 
7784
7538
  # Yield the member event directly
7785
- member_agent_run_response_event.parent_run_id = (
7786
- getattr(member_agent_run_response_event, "parent_run_id", None) or run_response.run_id
7787
- )
7539
+ member_agent_run_response_event.parent_run_id = getattr(
7540
+ member_agent_run_response_event, "parent_run_id", None
7541
+ ) or (run_response.run_id if run_response is not None else None)
7788
7542
  yield member_agent_run_response_event # type: ignore
7789
7543
  else:
7790
7544
  member_agent_run_response = await member_agent.arun( # type: ignore
@@ -7895,7 +7649,8 @@ class Team:
7895
7649
 
7896
7650
  # Yield the member event directly
7897
7651
  member_agent_run_response_chunk.parent_run_id = (
7898
- member_agent_run_response_chunk.parent_run_id or run_response.run_id
7652
+ member_agent_run_response_chunk.parent_run_id
7653
+ or (run_response.run_id if run_response is not None else None)
7899
7654
  )
7900
7655
  yield member_agent_run_response_chunk # type: ignore
7901
7656
 
@@ -8005,7 +7760,8 @@ class Team:
8005
7760
 
8006
7761
  check_if_run_cancelled(member_agent_run_output_event)
8007
7762
  member_agent_run_output_event.parent_run_id = (
8008
- member_agent_run_output_event.parent_run_id or run_response.run_id
7763
+ member_agent_run_output_event.parent_run_id
7764
+ or (run_response.run_id if run_response is not None else None)
8009
7765
  )
8010
7766
  await queue.put(member_agent_run_output_event)
8011
7767
  finally: