import asyncio
import json
from collections import ChainMap, defaultdict, deque
from dataclasses import asdict, dataclass, replace
from os import getenv
from typing import (
    Any,
    AsyncIterator,
    Callable,
    Dict,
    Iterator,
    List,
    Literal,
    Optional,
    Sequence,
    Set,
    Tuple,
    Type,
    Union,
    cast,
    overload,
)
from uuid import uuid4

from pydantic import BaseModel

from agno.agent import Agent
from agno.agent.metrics import SessionMetrics
from agno.exceptions import ModelProviderError, RunCancelledException
from agno.media import Audio, AudioArtifact, AudioResponse, File, Image, ImageArtifact, Video, VideoArtifact
from agno.memory.memory import Memory
from agno.memory.team import TeamMemory, TeamRun
from agno.models.base import Model
from agno.models.message import Citations, Message
from agno.models.response import ModelResponse, ModelResponseEvent
from agno.reasoning.step import NextAction, ReasoningStep, ReasoningSteps
from agno.run.messages import RunMessages
from agno.run.response import RunEvent, RunResponse, RunResponseExtraData
from agno.run.team import TeamRunResponse
from agno.storage.base import Storage
from agno.storage.session.team import TeamSession
from agno.tools.function import Function, get_entrypoint_docstring
from agno.tools.toolkit import Toolkit
from agno.utils.log import (
    log_debug,
    log_error,
    log_exception,
    log_info,
    log_warning,
    set_log_level_to_debug,
    set_log_level_to_info,
    use_agent_logger,
    use_team_logger,
)
from agno.utils.message import get_text_from_message
from agno.utils.response import (
    check_if_run_cancelled,
    create_panel,
    escape_markdown_tags,
    update_run_response_with_reasoning,
)
from agno.utils.safe_formatter import SafeFormatter
from agno.utils.string import parse_response_model
from agno.utils.timer import Timer


@dataclass(init=False)
class Team:
    """
    A class representing a team of agents.
    """

    members: List[Union[Agent, "Team"]]

    mode: Literal["route", "coordinate", "collaborate"] = "coordinate"

    # Model for this Team
    model: Optional[Model] = None

    # --- Team settings ---
    # Name of the team
    name: Optional[str] = None
    # Team UUID (autogenerated if not set)
    team_id: Optional[str] = None

    # --- User settings ---
    # ID of the user interacting with this team
    user_id: Optional[str] = None

    # --- Session settings ---
    # Team Session UUID (autogenerated if not set)
    session_id: Optional[str] = None
    # Session name
    session_name: Optional[str] = None
    # Session state (stored in the database to persist across runs)
    session_state: Optional[Dict[str, Any]] = None
    # If True, add the session state variables in the user and system messages
    add_state_in_messages: bool = False

    # --- System message settings ---
    # A description of the Team that is added to the start of the system message.
    description: Optional[str] = None
    # List of instructions for the team.
    instructions: Optional[Union[str, List[str], Callable]] = None
    # Provide the expected output from the Team.
    expected_output: Optional[str] = None
    # If markdown=true, add instructions to format the output using markdown
    markdown: bool = False
    # If True, add the current datetime to the instructions to give the team a sense of time
    # This allows for relative times like "tomorrow" to be used in the prompt
    add_datetime_to_instructions: bool = False

    # --- Success criteria ---
    # Define the success criteria for the team
    success_criteria: Optional[str] = None

    # --- User provided context ---
    # User provided context
    context: Optional[Dict[str, Any]] = None
    # If True, add the context to the user prompt
    add_context: bool = False

    # --- Tools ---
    # If True, enable the team agent to update the team context and automatically send the team context to the members
    enable_agentic_context: bool = False
    # If True, send all previous member interactions to members
    share_member_interactions: bool = False

    # If True, read the team history
    read_team_history: bool = False

    # Show tool calls in Team response. This sets the default for the team.
    show_tool_calls: bool = False

    # --- Structured output ---
    # Response model for the team response
    response_model: Optional[Type[BaseModel]] = None
    # If `response_model` is set, sets the response mode of the model, i.e. if the model should explicitly respond with a JSON object instead of a Pydantic model
    use_json_mode: bool = False
    # If True, parse the response
    parse_response: bool = True

    # --- History ---
    # Memory for the team
    memory: Optional[TeamMemory] = None
    # If True, enable the team history
    enable_team_history: bool = False
    # Number of interactions from history
    num_of_interactions_from_history: int = 3

    # --- Team Storage ---
    storage: Optional[Storage] = None
    # Extra data stored with this team
    extra_data: Optional[Dict[str, Any]] = None

    # --- Team Reasoning ---
    reasoning: bool = False
    reasoning_model: Optional[Model] = None
    reasoning_min_steps: int = 1
    reasoning_max_steps: int = 10

    # --- Debug & Monitoring ---
    # Enable debug logs
    debug_mode: bool = False
    # Enable member logs - Sets the debug_mode for team and members
    show_members_responses: bool = False
    # monitoring=True logs Team information to agno.com for monitoring
    monitoring: bool = False
    # telemetry=True logs minimal telemetry for analytics
    # This helps us improve the Teams implementation and provide better support
    telemetry: bool = True

    def __init__(
        self,
        members: List[Union[Agent, "Team"]],
        mode: Literal["route", "coordinate", "collaborate"] = "coordinate",
        model: Optional[Model] = None,
        name: Optional[str] = None,
        team_id: Optional[str] = None,
        user_id: Optional[str] = None,
        session_id: Optional[str] = None,
        session_name: Optional[str] = None,
        session_state: Optional[Dict[str, Any]] = None,
        add_state_in_messages: bool = False,
        description: Optional[str] = None,
        instructions: Optional[Union[str, List[str], Callable]] = None,
        expected_output: Optional[str] = None,
        success_criteria: Optional[str] = None,
        markdown: bool = False,
        add_datetime_to_instructions: bool = False,
        context: Optional[Dict[str, Any]] = None,
        add_context: bool = False,
        enable_agentic_context: bool = False,
        share_member_interactions: bool = False,
        read_team_history: bool = False,
        show_tool_calls: bool = False,
        response_model: Optional[Type[BaseModel]] = None,
        use_json_mode: bool = False,
        parse_response: bool = True,
        memory: Optional[TeamMemory] = None,
        enable_team_history: bool = False,
        num_of_interactions_from_history: int = 3,
        storage: Optional[Storage] = None,
        extra_data: Optional[Dict[str, Any]] = None,
        debug_mode: bool = False,
        show_members_responses: bool = False,
        monitoring: bool = False,
        telemetry: bool = True,
    ):
        self.members = members

        self.mode = mode

        self.model = model

        self.name = name
        self.team_id = team_id

        self.user_id = user_id
        self.session_id = session_id
        self.session_name = session_name
        self.session_state = session_state
        self.add_state_in_messages = add_state_in_messages

        self.description = description
        self.instructions = instructions
        self.expected_output = expected_output
        self.markdown = markdown
        self.add_datetime_to_instructions = add_datetime_to_instructions
        self.success_criteria = success_criteria

        self.context = context
        self.add_context = add_context

        self.enable_agentic_context = enable_agentic_context
        self.share_member_interactions = share_member_interactions

        self.read_team_history = read_team_history
        self.show_tool_calls = show_tool_calls

        self.response_model = response_model
        self.use_json_mode = use_json_mode
        self.parse_response = parse_response

        self.memory = memory
        self.enable_team_history = enable_team_history
        self.num_of_interactions_from_history = num_of_interactions_from_history

        self.storage = storage
        self.extra_data = extra_data

        # self.reasoning = reasoning
        # self.reasoning_model = reasoning_model
        # self.reasoning_min_steps = reasoning_min_steps
        # self.reasoning_max_steps = reasoning_max_steps

        self.debug_mode = debug_mode
        self.show_members_responses = show_members_responses

        self.monitoring = monitoring
        self.telemetry = telemetry

        # --- Params not to be set by user ---
        self.session_metrics: Optional[SessionMetrics] = None
        self.full_team_session_metrics: Optional[SessionMetrics] = None

        self.run_id: Optional[str] = None
        self.run_input: Optional[Union[str, List, Dict]] = None
        self.run_messages: Optional[RunMessages] = None
        self.run_response: Optional[TeamRunResponse] = None

        # Images generated during this session
        self.images: Optional[List[ImageArtifact]] = None
        # Audio generated during this session
        self.audio: Optional[List[AudioArtifact]] = None
        # Videos generated during this session
        self.videos: Optional[List[VideoArtifact]] = None

        # Team session
        self.team_session: Optional[TeamSession] = None

        self._formatter: Optional[SafeFormatter] = None

        self._tools_for_model: Optional[List[Dict]] = None
        self._functions_for_model: Optional[Dict[str, Function]] = None

        # True if we should parse a member response model
        self._member_response_model: Optional[Type[BaseModel]] = None

    def _set_team_id(self) -> str:
        if self.team_id is None:
            self.team_id = str(uuid4())
        return self.team_id

    def _set_session_id(self) -> str:
        if self.session_id is None or self.session_id == "":
            self.session_id = str(uuid4())
        return self.session_id

    def _set_debug(self) -> None:
        if self.debug_mode or getenv("AGNO_DEBUG", "false").lower() == "true":
            self.debug_mode = True
            set_log_level_to_debug(source_type="team")
        else:
            set_log_level_to_info(source_type="team")

    def _set_storage_mode(self):
        if self.storage is not None:
            if self.storage.mode in ["workflow", "agent"]:
                log_warning(f"You shouldn't use storage in multiple modes. Current mode is {self.storage.mode}.")

            self.storage.mode = "team"

    def _set_monitoring(self) -> None:
        """Override monitoring and telemetry settings based on environment variables."""

        # Only override if the environment variable is set
        monitor_env = getenv("AGNO_MONITOR")
        if monitor_env is not None:
            self.monitoring = monitor_env.lower() == "true"

        telemetry_env = getenv("AGNO_TELEMETRY")
        if telemetry_env is not None:
            self.telemetry = telemetry_env.lower() == "true"

    def _initialize_team(self) -> None:
        self._set_storage_mode()

        # Set debug mode
        self._set_debug()

        # Make sure for the team, we are using the team logger
        use_team_logger()

        # Set monitoring and telemetry
        self._set_monitoring()

        # Set the team ID if not yet set
        self._set_team_id()

        # Set the session ID if not yet set
        self._set_session_id()

        log_debug(f"Team ID: {self.team_id}", center=True)
        log_debug(f"Session ID: {self.session_id}", center=True)

        # Initialize formatter
        if self._formatter is None:
            self._formatter = SafeFormatter()

        for member in self.members:
            # Set debug mode for all members
            if self.debug_mode:
                member.debug_mode = True
            if self.markdown:
                member.markdown = True

            # All members have the same session ID
            member.session_id = self.session_id

            # Set the team ID of members
            member.team_id = self.team_id

    @overload
    def run(
        self,
        message: Union[str, List, Dict, Message],
        *,
        stream: Literal[False] = False,
        stream_intermediate_steps: bool = False,
        retries: Optional[int] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs: Any,
    ) -> TeamRunResponse: ...

    @overload
    def run(
        self,
        message: Union[str, List, Dict, Message],
        *,
        stream: Literal[True] = True,
        stream_intermediate_steps: bool = False,
        retries: Optional[int] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs: Any,
    ) -> Iterator[TeamRunResponse]: ...

    def run(
        self,
        message: Union[str, List, Dict, Message],
        *,
        stream: bool = False,
        stream_intermediate_steps: bool = False,
        retries: Optional[int] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs: Any,
    ) -> Union[TeamRunResponse, Iterator[TeamRunResponse]]:
        """Run the Team and return the response."""
        self._initialize_team()

        retries = retries or 3
        if retries < 1:
            raise ValueError("Retries must be at least 1")

        show_tool_calls = self.show_tool_calls

        # Read existing session from storage
        self.read_from_storage()

        # Initialize memory if not yet set
        if self.memory is None:
            self.memory = TeamMemory()

        # Read existing session from storage
        if self.context is not None:
            self._resolve_run_context()

        if self.response_model is not None and self.parse_response:
            # Disable stream if response_model is set
            stream = False
            log_warning("Disabling stream as response_model is set")

            show_tool_calls = False

        # Configure the model for runs
        self._configure_model(show_tool_calls=show_tool_calls)

        # Run the team
        last_exception = None
        num_attempts = retries + 1

        for attempt in range(num_attempts):
            # Initialize the current run
            run_id = str(uuid4())
            self.run_id = run_id

            log_debug(f" Team Run Start: {self.run_id} ", center=True)
            log_debug(f" Mode: '{self.mode}' ", center=True)
            log_debug("")

            # Set run_input
            if message is not None:
                if isinstance(message, str):
                    self.run_input = message
                elif isinstance(message, Message):
                    self.run_input = message.to_dict()
                else:
                    self.run_input = message

            # Prepare built-in tools
            _built_in_tools: List[Union[Function, Callable]] = []

            if self.read_team_history:
                _built_in_tools.append(self.get_team_history)

            if self.mode == "route":
                user_message = self._get_user_message(message, audio=audio, images=images, videos=videos, files=files)
                forward_task_func: Function = self.get_forward_task_function(
                    message=user_message,
                    stream=stream,
                    async_mode=False,
                    images=images,  # type: ignore
                    videos=videos,  # type: ignore
                    audio=audio,  # type: ignore
                    files=files,  # type: ignore
                )
                _built_in_tools.append(forward_task_func)
                self.model.tool_choice = "required"  # type: ignore
            elif self.mode == "coordinate":
                _built_in_tools.append(
                    self.get_transfer_task_function(
                        stream=stream,
                        async_mode=False,
                        images=images,  # type: ignore
                        videos=videos,  # type: ignore
                        audio=audio,  # type: ignore
                        files=files,  # type: ignore
                    )
                )
                self.model.tool_choice = "auto"  # type: ignore

                if self.enable_agentic_context:
                    _built_in_tools.append(self.set_team_context)
            elif self.mode == "collaborate":
                run_member_agents_func = self.get_run_member_agents_function(
                    stream=stream,
                    async_mode=False,
                    images=images,  # type: ignore
                    videos=videos,  # type: ignore
                    audio=audio,  # type: ignore
                    files=files,  # type: ignore
                )
                _built_in_tools.append(run_member_agents_func)
                self.model.tool_choice = "auto"  # type: ignore

                if self.enable_agentic_context:
                    _built_in_tools.append(self.set_team_context)

            self._add_tools_to_model(self.model, tools=_built_in_tools)  # type: ignore

            # Run the team
            try:
                self.run_response = TeamRunResponse(
                    run_id=self.run_id, session_id=self.session_id, team_id=self.team_id
                )
                # Configure the team leader model
                self.run_response.model = self.model.id if self.model is not None else None

                # Prepare run messages
                if self.mode == "route":
                    # In route mode the model shouldn't get images/audio/video
                    run_messages: RunMessages = self.get_run_messages(
                        run_response=self.run_response,
                        message=message,
                        **kwargs,
                    )
                else:
                    run_messages = self.get_run_messages(
                        run_response=self.run_response,
                        message=message,
                        audio=audio,
                        images=images,
                        videos=videos,
                        files=files,
                        **kwargs,
                    )

                if stream:
                    resp = self._run_stream(
                        run_response=self.run_response,
                        run_messages=run_messages,
                        stream_intermediate_steps=stream_intermediate_steps,
                    )

                    return resp
                else:
                    self._run(
                        run_response=self.run_response,
                        run_messages=run_messages,
                    )

                    return self.run_response

            except ModelProviderError as e:
                import time

                log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}")
                last_exception = e
                if attempt < num_attempts - 1:
                    time.sleep(2**attempt)
            except (KeyboardInterrupt, RunCancelledException):
                return TeamRunResponse(
                    run_id=self.run_id or str(uuid4()),
                    session_id=self.session_id,
                    team_id=self.team_id,
                    content="Operation cancelled by user",
                    event=RunEvent.run_cancelled,
                )

        # If we get here, all retries failed
        if last_exception is not None:
            log_error(
                f"Failed after {num_attempts} attempts. Last error using {last_exception.model_name}({last_exception.model_id})"
            )
            raise last_exception
        else:
            raise Exception(f"Failed after {num_attempts} attempts.")

    def _run(
        self,
        run_response: TeamRunResponse,
        run_messages: RunMessages,
    ) -> None:
        """Run the Team and return the response.

        Steps:
        1. Reason about the task(s) if reasoning is enabled
        2. Get a response from the model
        3. Update the run_response
        4. Update Team Memory
        5. Calculate session metrics
        6. Save session to storage
        7. Parse any structured outputs
        8. Log the team run
        """

        self.memory = cast(TeamMemory, self.memory)
        self.model = cast(Model, self.model)

        # 1. Reason about the task(s) if reasoning is enabled
        if self.reasoning or self.reasoning_model is not None:
            reasoning_generator = self._reason(run_response=run_response, run_messages=run_messages)

            # Consume the generator without yielding
            deque(reasoning_generator, maxlen=0)

        # Update agent state
        self.run_messages = run_messages
        index_of_last_user_message = len(run_messages.messages)

        # 2. Get the model response for the team leader
        model_response = self.model.response(messages=run_messages.messages)  # type: ignore

        # 3. Update TeamRunResponse
        # Handle structured outputs
        if (self.response_model is not None) and not self.use_json_mode and (model_response.parsed is not None):
            # Update the run_response content with the structured output
            run_response.content = model_response.parsed
            # Update the run_response content_type with the structured output class name
            run_response.content_type = self.response_model.__name__
        else:
            # Update the run_response content with the model response content
            if not run_response.content:
                run_response.content = model_response.content
            else:
                run_response.content += model_response.content

        # Update the run_response thinking with the model response thinking
        if model_response.thinking is not None:
            if not run_response.thinking:
                run_response.thinking = model_response.thinking
            else:
                run_response.thinking += model_response.thinking

        # Update citations
        if model_response.citations is not None:
            run_response.citations = model_response.citations

        # Update the run_response tools with the model response tools
        if model_response.tool_calls is not None:
            if run_response.tools is None:
                run_response.tools = model_response.tool_calls
            else:
                run_response.tools.extend(model_response.tool_calls)

        # Update the run_response audio with the model response audio
        if model_response.audio is not None:
            run_response.response_audio = model_response.audio

        # Update the run_response created_at with the model response created_at
        run_response.created_at = model_response.created_at

        # Build a list of messages that should be added to the RunResponse
        messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
        # Update the TeamRunResponse messages
        run_response.messages = messages_for_run_response
        # Update the TeamRunResponse metrics
        run_response.metrics = self._aggregate_metrics_from_messages(messages_for_run_response)

        # 4. Update Team Memory
        # Add the system message to the memory
        if run_messages.system_message is not None:
            self.memory.add_system_message(run_messages.system_message, system_message_role="system")  # type: ignore

        # Build a list of messages that should be added to the AgentMemory
        messages_for_memory: List[Message] = (
            [run_messages.user_message] if run_messages.user_message is not None else []
        )

        for _rm in run_messages.messages[index_of_last_user_message:]:
            if _rm.add_to_agent_memory:
                messages_for_memory.append(_rm)
        if len(messages_for_memory) > 0:
            self.memory.add_messages(messages=messages_for_memory)  # type: ignore

        team_run = TeamRun(response=run_response)
        team_run.message = run_messages.user_message

        # Update the memories with the user message if needed
        if (
            self.memory is not None
            and self.memory.create_user_memories
            and self.memory.update_user_memories_after_run
            and run_messages.user_message is not None
        ):
            self.memory.update_memory(input=run_messages.user_message.get_content_string())  # type: ignore

        # Add AgentRun to memory
        self.memory.add_team_run(team_run)  # type: ignore

        # 5. Calculate session metrics
        self.session_metrics = self._calculate_session_metrics()
        self.full_team_session_metrics = self._calculate_full_team_session_metrics()

        # 6. Save session to storage
        self.write_to_storage()

        # 7. Parse team response model
        if self.response_model is not None:
            if isinstance(run_response.content, str) and self.parse_response:
                try:
                    parsed_response_content = parse_response_model(run_response.content, self.response_model)

                    # Update TeamRunResponse
                    if parsed_response_content is not None:
                        run_response.content = parsed_response_content
                        run_response.content_type = self.response_model.__name__
                    else:
                        log_warning("Failed to convert response to response_model")
                except Exception as e:
                    log_warning(f"Failed to convert response to output model: {e}")
            else:
                log_warning("Something went wrong. Run response content is not a string")
        elif self._member_response_model is not None:
            if isinstance(run_response.content, str):
                try:
                    parsed_response_content = parse_response_model(run_response.content, self._member_response_model)
                    # Update TeamRunResponse
                    if parsed_response_content is not None:
                        run_response.content = parsed_response_content
                        run_response.content_type = self._member_response_model.__name__
                    else:
                        log_warning("Failed to convert response to response_model")
                except Exception as e:
                    log_warning(f"Failed to convert response to output model: {e}")
            else:
                log_warning("Something went wrong. Run response content is not a string")

        # 8. Log Team Run
        self._log_team_run()

        log_debug(f" Team Run End: {self.run_id} ", center=True, symbol="*")
        log_debug("")

    def _run_stream(
        self,
        run_response: TeamRunResponse,
        run_messages: RunMessages,
        stream_intermediate_steps: bool = False,
    ) -> Iterator[TeamRunResponse]:
        """Run the Team and return the response iterator.

        Steps:
        1. Reason about the task(s) if reasoning is enabled
        2. Get a response from the model
        3. Update the run_response
        4. Update Team Memory
        5. Calculate session metrics
        6. Save session to storage
        7. Log Team Run
        """

        self.memory = cast(TeamMemory, self.memory)
        self.model = cast(Model, self.model)

        # 1. Reason about the task(s) if reasoning is enabled
        if self.reasoning or self.reasoning_model is not None:
            reasoning_generator = self._reason(run_response=run_response, run_messages=run_messages)

            yield from reasoning_generator  # type: ignore

        # Update agent state
        self.run_messages = run_messages
        index_of_last_user_message = len(run_messages.messages)

        # Start the Run by yielding a RunStarted event
        if stream_intermediate_steps:
            yield self._create_run_response(content="Run started", event=RunEvent.run_started)

        # 2. Get a response from the model
        full_model_response = ModelResponse()
        model_stream = self.model.response_stream(messages=run_messages.messages)  # type: ignore
        for model_response_chunk in model_stream:
            # If the model response is an assistant_response, yield a RunResponse
            if model_response_chunk.event == ModelResponseEvent.assistant_response.value:
                should_yield = False
                # Process content and thinking
                if model_response_chunk.content is not None:
                    if not full_model_response.content:
                        full_model_response.content = model_response_chunk.content
                    else:
                        full_model_response.content += model_response_chunk.content
                    should_yield = True

                # Process thinking
                if model_response_chunk.thinking is not None:
                    if not full_model_response.thinking:
                        full_model_response.thinking = model_response_chunk.thinking
                    else:
                        full_model_response.thinking += model_response_chunk.thinking
                    should_yield = True

                if model_response_chunk.citations is not None:
                    # We get citations in one chunk
                    full_model_response.citations = model_response_chunk.citations
                    should_yield = True

                # Process audio
                if model_response_chunk.audio is not None:
                    if full_model_response.audio is None:
                        full_model_response.audio = AudioResponse(id=str(uuid4()), content="", transcript="")

                    if model_response_chunk.audio.id is not None:
                        full_model_response.audio.id = model_response_chunk.audio.id  # type: ignore
                    if model_response_chunk.audio.content is not None:
                        full_model_response.audio.content += model_response_chunk.audio.content  # type: ignore
                    if model_response_chunk.audio.transcript is not None:
                        full_model_response.audio.transcript += model_response_chunk.audio.transcript  # type: ignore
                    if model_response_chunk.audio.expires_at is not None:
                        full_model_response.audio.expires_at = model_response_chunk.audio.expires_at  # type: ignore
                    if model_response_chunk.audio.mime_type is not None:
                        full_model_response.audio.mime_type = model_response_chunk.audio.mime_type  # type: ignore
                    if model_response_chunk.audio.sample_rate is not None:
                        full_model_response.audio.sample_rate = model_response_chunk.audio.sample_rate
                    if model_response_chunk.audio.channels is not None:
                        full_model_response.audio.channels = model_response_chunk.audio.channels

                    # Yield the audio and transcript bit by bit
                    should_yield = True

                # Only yield the chunk
                if should_yield:
                    yield self._create_run_response(
                        content=model_response_chunk.content,
                        thinking=model_response_chunk.thinking,
                        response_audio=model_response_chunk.audio,
                        citations=model_response_chunk.citations,
                        created_at=model_response_chunk.created_at,
                    )

            # If the model response is a tool_call_started, add the tool call to the run_response
            elif model_response_chunk.event == ModelResponseEvent.tool_call_started.value:
                # Add tool calls to the run_response
                tool_calls_list = model_response_chunk.tool_calls
                if tool_calls_list is not None:
                    # Add tool calls to the agent.run_response
                    if run_response.tools is None:
                        run_response.tools = tool_calls_list
                    else:
                        run_response.tools.extend(tool_calls_list)

                # If the agent is streaming intermediate steps, yield a RunResponse with the tool_call_started event
                if stream_intermediate_steps:
                    yield self._create_run_response(
                        content=model_response_chunk.content,
                        event=RunEvent.tool_call_started,
                        from_run_response=run_response,
                    )

            # If the model response is a tool_call_completed, update the existing tool call in the run_response
            elif model_response_chunk.event == ModelResponseEvent.tool_call_completed.value:
                tool_calls_list = model_response_chunk.tool_calls
                if tool_calls_list is not None:
                    # Update the existing tool call in the run_response
                    if run_response.tools:
                        # Create a mapping of tool_call_id to index
                        tool_call_index_map = {
                            tc["tool_call_id"]: i
                            for i, tc in enumerate(run_response.tools)
                            if tc.get("tool_call_id") is not None
                        }
                        # Process tool calls
                        for tool_call_dict in tool_calls_list:
                            tool_call_id = tool_call_dict.get("tool_call_id")
                            index = tool_call_index_map.get(tool_call_id)
                            if index is not None:
                                run_response.tools[index] = tool_call_dict
                    else:
                        run_response.tools = tool_calls_list

                    if stream_intermediate_steps:
                        yield self._create_run_response(
                            content=model_response_chunk.content,
                            event=RunEvent.tool_call_completed,
                            from_run_response=run_response,
                        )

        # 3. Update TeamRunResponse
        run_response.created_at = full_model_response.created_at
        if full_model_response.content is not None:
            run_response.content = full_model_response.content
        if full_model_response.thinking is not None:
            run_response.thinking = full_model_response.thinking
        if full_model_response.audio is not None:
            run_response.response_audio = full_model_response.audio
        if full_model_response.citations is not None:
            run_response.citations = full_model_response.citations

        # Build a list of messages that should be added to the RunResponse
        messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
        # Update the TeamRunResponse messages
        run_response.messages = messages_for_run_response
        # Update the TeamRunResponse metrics
        run_response.metrics = self._aggregate_metrics_from_messages(messages_for_run_response)

        # 4. Update Team Memory
        # Add the system message to the memory
        if run_messages.system_message is not None:
            self.memory.add_system_message(run_messages.system_message, system_message_role="system")

        # Build a list of messages that should be added to the AgentMemory
        messages_for_memory: List[Message] = (
            [run_messages.user_message] if run_messages.user_message is not None else []
        )

        for _rm in run_messages.messages[index_of_last_user_message:]:
            if _rm.add_to_agent_memory:
                messages_for_memory.append(_rm)
        if len(messages_for_memory) > 0:
            self.memory.add_messages(messages=messages_for_memory)
        team_run = TeamRun(response=run_response)
        team_run.message = run_messages.user_message

        # Update the memories with the user message if needed
        if (
            self.memory is not None
            and self.memory.create_user_memories
            and self.memory.update_user_memories_after_run
            and run_messages.user_message is not None
        ):
            self.memory.update_memory(input=run_messages.user_message.get_content_string())

        # Add AgentRun to memory
        self.memory.add_team_run(team_run)

        # 5. Calculate session metrics
        self.session_metrics = self._calculate_session_metrics()
        self.full_team_session_metrics = self._calculate_full_team_session_metrics()

        # 6. Save session to storage
        self.write_to_storage()

        # Log Team Run
        self._log_team_run()

        if stream_intermediate_steps:
            yield self._create_run_response(
                from_run_response=run_response,
                event=RunEvent.run_completed,
            )

        log_debug(f" Team Run End: {self.run_id} ", center=True, symbol="*")
        log_debug("")

    @overload
    async def arun(
        self,
        message: Union[str, List, Dict, Message],
        *,
        stream: Literal[False] = False,
        stream_intermediate_steps: bool = False,
        retries: Optional[int] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs: Any,
    ) -> TeamRunResponse: ...

    @overload
    async def arun(
        self,
        message: Union[str, List, Dict, Message],
        *,
        stream: Literal[True] = True,
        stream_intermediate_steps: bool = False,
        retries: Optional[int] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs: Any,
    ) -> AsyncIterator[TeamRunResponse]: ...

    async def arun(
        self,
        message: Union[str, List, Dict, Message],
        *,
        stream: bool = False,
        stream_intermediate_steps: bool = False,
        retries: Optional[int] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs: Any,
    ) -> Union[TeamRunResponse, AsyncIterator[TeamRunResponse]]:
        """Run the Team asynchronously and return the response."""
        self._initialize_team()

        retries = retries or 3
        if retries < 1:
            raise ValueError("Retries must be at least 1")

        show_tool_calls = self.show_tool_calls

        # Read existing session from storage
        self.read_from_storage()

        # Initialize memory if not yet set
        if self.memory is None:
            self.memory = TeamMemory()

        # Read existing session from storage
        if self.context is not None:
            self._resolve_run_context()

        if self.response_model is not None and self.parse_response:
            # Disable stream if response_model is set
            stream = False
            log_warning("Disabling stream as response_model is set")

            show_tool_calls = False

        # Configure the model for runs
        self._configure_model(show_tool_calls=show_tool_calls)

        # Run the team
        last_exception = None
        num_attempts = retries + 1

        for attempt in range(num_attempts):
            # Initialize the current run
            run_id = str(uuid4())
            self.run_id = run_id

            log_debug(f" Team Run Start: {self.run_id} ", center=True)
            log_debug(f" Mode: '{self.mode}' ", center=True)
            log_debug("")

            # Set run_input
            if message is not None:
                if isinstance(message, str):
                    self.run_input = message
                elif isinstance(message, Message):
                    self.run_input = message.to_dict()
                else:
                    self.run_input = message

            # Prepare built-in tools
            _built_in_tools: List[Union[Function, Callable]] = []

            if self.read_team_history:
                _built_in_tools.append(self.get_team_history)

            if self.mode == "route":
                user_message = self._get_user_message(message, audio=audio, images=images, videos=videos, files=files)
                forward_task_func: Function = self.get_forward_task_function(
                    message=user_message,
                    stream=stream,
                    async_mode=True,  # Set to True for async mode
                    images=images,  # type: ignore
                    videos=videos,  # type: ignore
                    audio=audio,  # type: ignore
                    files=files,  # type: ignore
                )
                _built_in_tools.append(forward_task_func)
                self.model.tool_choice = "required"  # type: ignore
            elif self.mode == "coordinate":
                _built_in_tools.append(
                    self.get_transfer_task_function(
                        stream=stream,
                        async_mode=True,
                        images=images,  # type: ignore
                        videos=videos,  # type: ignore
                        audio=audio,  # type: ignore
                        files=files,  # type: ignore
                    )
                )
                self.model.tool_choice = "auto"  # type: ignore

                if self.enable_agentic_context:
                    _built_in_tools.append(self.set_team_context)
            elif self.mode == "collaborate":
                run_member_agents_func = self.get_run_member_agents_function(
                    stream=stream,
                    async_mode=True,
                    images=images,  # type: ignore
                    videos=videos,  # type: ignore
                    audio=audio,  # type: ignore
                    files=files,  # type: ignore
                )
                _built_in_tools.append(run_member_agents_func)
                self.model.tool_choice = "auto"  # type: ignore

                if self.enable_agentic_context:
                    _built_in_tools.append(self.set_team_context)

            self._add_tools_to_model(self.model, tools=_built_in_tools)  # type: ignore

            # Run the team
            try:
                self.run_response = TeamRunResponse(
                    run_id=self.run_id, session_id=self.session_id, team_id=self.team_id
                )
                # Configure the team leader model
                self.run_response.model = self.model.id if self.model is not None else None

                # Prepare run messages
                if self.mode == "route":
                    # In route mode the model shouldn't get images/audio/video
                    run_messages: RunMessages = self.get_run_messages(
                        run_response=self.run_response,
                        message=message,
                        **kwargs,
                    )
                else:
                    run_messages = self.get_run_messages(
                        run_response=self.run_response,
                        message=message,
                        audio=audio,
                        images=images,
                        videos=videos,
                        files=files,
                        **kwargs,
                    )

                if stream:
                    resp = self._arun_stream(
                        run_response=self.run_response,
                        run_messages=run_messages,
                        stream_intermediate_steps=stream_intermediate_steps,
                    )
                    return resp
                else:
                    await self._arun(
                        run_response=self.run_response,
                        run_messages=run_messages,
                    )

                    return self.run_response

            except ModelProviderError as e:
                log_warning(f"Attempt {attempt + 1}/{num_attempts} failed: {str(e)}")
                last_exception = e
                if attempt < num_attempts - 1:
                    await asyncio.sleep(2**attempt)
            except (KeyboardInterrupt, RunCancelledException):
                return TeamRunResponse(
                    run_id=self.run_id or str(uuid4()),
                    session_id=self.session_id,
                    team_id=self.team_id,
                    content="Operation cancelled by user",
                    event=RunEvent.run_cancelled,
                )

        # If we get here, all retries failed
        if last_exception is not None:
            log_error(
                f"Failed after {num_attempts} attempts. Last error using {last_exception.model_name}({last_exception.model_id})"
            )
            raise last_exception
        else:
            raise Exception(f"Failed after {num_attempts} attempts.")

    async def _arun(
        self,
        run_response: TeamRunResponse,
        run_messages: RunMessages,
    ) -> None:
        """Run the Team and return the response.

        Steps:
        1. Reason about the task(s) if reasoning is enabled
        2. Get a response from the model
        3. Update the run_response
        4. Update Team Memory
        5. Calculate session metrics
        6. Save session to storage
        7. Parse any structured outputs
        8. Log the team run
        """

        self.memory = cast(TeamMemory, self.memory)
        self.model = cast(Model, self.model)

        # 1. Reason about the task(s) if reasoning is enabled
        if self.reasoning or self.reasoning_model is not None:
            reasoning_generator = self._areason(run_response=run_response, run_messages=run_messages)

            # Consume the generator without yielding
            async for _ in reasoning_generator:
                pass

        # Update agent state
        self.run_messages = run_messages
        index_of_last_user_message = len(run_messages.messages)

        # 2. Get the model response for the team leader
        model_response = await self.model.aresponse(messages=run_messages.messages)  # type: ignore

        # 3. Update TeamRunResponse
        # Handle structured outputs
        if (self.response_model is not None) and not self.use_json_mode and (model_response.parsed is not None):
            # Update the run_response content with the structured output
            run_response.content = model_response.parsed
            # Update the run_response content_type with the structured output class name
            run_response.content_type = self.response_model.__name__
        else:
            # Update the run_response content with the model response content
            if not run_response.content:
                run_response.content = model_response.content
            else:
                run_response.content += model_response.content

        # Update the run_response thinking with the model response thinking
        if model_response.thinking is not None:
            if not run_response.thinking:
                run_response.thinking = model_response.thinking
            else:
                run_response.thinking += model_response.thinking

        # Update citations
        if model_response.citations is not None:
            run_response.citations = model_response.citations

        # Update the run_response tools with the model response tools
        if model_response.tool_calls is not None:
            if run_response.tools is None:
                run_response.tools = model_response.tool_calls
            else:
                run_response.tools.extend(model_response.tool_calls)

        # Update the run_response audio with the model response audio
        if model_response.audio is not None:
            run_response.response_audio = model_response.audio

        # Update the run_response created_at with the model response created_at
        run_response.created_at = model_response.created_at

        # Build a list of messages that should be added to the RunResponse
        messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
        # Update the TeamRunResponse messages
        run_response.messages = messages_for_run_response
        # Update the TeamRunResponse metrics
        run_response.metrics = self._aggregate_metrics_from_messages(messages_for_run_response)

        # 4. Update Team Memory
        # Add the system message to the memory
        if run_messages.system_message is not None:
            self.memory.add_system_message(run_messages.system_message, system_message_role="system")  # type: ignore

        # Build a list of messages that should be added to the AgentMemory
        messages_for_memory: List[Message] = (
            [run_messages.user_message] if run_messages.user_message is not None else []
        )

        for _rm in run_messages.messages[index_of_last_user_message:]:
            if _rm.add_to_agent_memory:
                messages_for_memory.append(_rm)
        if len(messages_for_memory) > 0:
            self.memory.add_messages(messages=messages_for_memory)  # type: ignore

        team_run = TeamRun(response=run_response)
        team_run.message = run_messages.user_message

        # Update the memories with the user message if needed
        if (
            self.memory is not None
            and self.memory.create_user_memories
            and self.memory.update_user_memories_after_run
            and run_messages.user_message is not None
        ):
            await self.memory.aupdate_memory(input=run_messages.user_message.get_content_string())

        # Add AgentRun to memory
        self.memory.add_team_run(team_run)

        # 5. Calculate session metrics
        self.session_metrics = self._calculate_session_metrics()
        self.full_team_session_metrics = self._calculate_full_team_session_metrics()

        # 6. Save session to storage
        self.write_to_storage()

        # 7. Parse team response model
        if self.response_model is not None:
            if isinstance(run_response.content, str) and self.parse_response:
                try:
                    parsed_response_content = parse_response_model(run_response.content, self.response_model)

                    # Update TeamRunResponse
                    if parsed_response_content is not None:
                        run_response.content = parsed_response_content
                        run_response.content_type = self.response_model.__name__
                    else:
                        log_warning("Failed to convert response to response_model")
                except Exception as e:
                    log_warning(f"Failed to convert response to output model: {e}")
            else:
                log_warning("Something went wrong. Run response content is not a string")
        elif self._member_response_model is not None:
            if isinstance(run_response.content, str):
                try:
                    parsed_response_content = parse_response_model(run_response.content, self._member_response_model)
                    # Update TeamRunResponse
                    if parsed_response_content is not None:
                        run_response.content = parsed_response_content
                        run_response.content_type = self._member_response_model.__name__
                    else:
                        log_warning("Failed to convert response to response_model")
                except Exception as e:
                    log_warning(f"Failed to convert response to output model: {e}")
            else:
                log_warning("Something went wrong. Run response content is not a string")

        # 8. Log Team Run
        await self._alog_team_run()

        log_debug(f" Team Run End: {self.run_id} ", center=True, symbol="*")
        log_debug("")

    async def _arun_stream(
        self,
        run_response: TeamRunResponse,
        run_messages: RunMessages,
        stream_intermediate_steps: bool = False,
    ) -> AsyncIterator[TeamRunResponse]:
        """Run the Team and return the response.

        Steps:
        1. Reason about the task(s) if reasoning is enabled
        2. Get a response from the model
        3. Update the run_response
        4. Update Team Memory
        5. Calculate session metrics
        6. Save session to storage
        7. Log Team Run
        """

        self.memory = cast(TeamMemory, self.memory)
        self.model = cast(Model, self.model)

        # 1. Reason about the task(s) if reasoning is enabled
        if self.reasoning or self.reasoning_model is not None:
            reasoning_generator = self._areason(run_response=run_response, run_messages=run_messages)

            async for reasoning_response in reasoning_generator:
                yield reasoning_response  # type: ignore

        # Update agent state
        self.run_messages = run_messages
        index_of_last_user_message = len(run_messages.messages)

        # Start the Run by yielding a RunStarted event
        if stream_intermediate_steps:
            yield self._create_run_response(content="Run started", event=RunEvent.run_started)

        # 2. Get a response from the model
        full_model_response = ModelResponse()
        model_stream = self.model.aresponse_stream(messages=run_messages.messages)
        async for model_response_chunk in model_stream:
            # If the model response is an assistant_response, yield a RunResponse
            if model_response_chunk.event == ModelResponseEvent.assistant_response.value:
                should_yield = False
                # Process content and thinking
                if model_response_chunk.content is not None:
                    if not full_model_response.content:
                        full_model_response.content = model_response_chunk.content
                    else:
                        full_model_response.content += model_response_chunk.content
                    should_yield = True

                # Process thinking
                if model_response_chunk.thinking is not None:
                    if not full_model_response.thinking:
                        full_model_response.thinking = model_response_chunk.thinking
                    else:
                        full_model_response.thinking += model_response_chunk.thinking
                    should_yield = True

                if model_response_chunk.citations is not None:
                    # We get citations in one chunk
                    full_model_response.citations = model_response_chunk.citations
                    should_yield = True

                # Process audio
                if model_response_chunk.audio is not None:
                    if full_model_response.audio is None:
                        full_model_response.audio = AudioResponse(id=str(uuid4()), content="", transcript="")

                    if model_response_chunk.audio.id is not None:
                        full_model_response.audio.id = model_response_chunk.audio.id
                    if model_response_chunk.audio.content is not None:
                        full_model_response.audio.content += model_response_chunk.audio.content  # type: ignore
                    if model_response_chunk.audio.transcript is not None:
                        full_model_response.audio.transcript += model_response_chunk.audio.transcript  # type: ignore
                    if model_response_chunk.audio.expires_at is not None:
                        full_model_response.audio.expires_at = model_response_chunk.audio.expires_at
                    if model_response_chunk.audio.mime_type is not None:
                        full_model_response.audio.mime_type = model_response_chunk.audio.mime_type
                    if model_response_chunk.audio.sample_rate is not None:
                        full_model_response.audio.sample_rate = model_response_chunk.audio.sample_rate
                    if model_response_chunk.audio.channels is not None:
                        full_model_response.audio.channels = model_response_chunk.audio.channels

                    # Yield the audio and transcript bit by bit
                    should_yield = True

                # Only yield the chunk
                if should_yield:
                    yield self._create_run_response(
                        content=model_response_chunk.content,
                        thinking=model_response_chunk.thinking,
                        response_audio=model_response_chunk.audio,
                        citations=model_response_chunk.citations,
                        created_at=model_response_chunk.created_at,
                    )

            # If the model response is a tool_call_started, add the tool call to the run_response
            elif model_response_chunk.event == ModelResponseEvent.tool_call_started.value:
                # Add tool calls to the run_response
                tool_calls_list = model_response_chunk.tool_calls
                if tool_calls_list is not None:
                    # Add tool calls to the agent.run_response
                    if run_response.tools is None:
                        run_response.tools = tool_calls_list
                    else:
                        run_response.tools.extend(tool_calls_list)

                # If the agent is streaming intermediate steps, yield a RunResponse with the tool_call_started event
                if stream_intermediate_steps:
                    yield self._create_run_response(
                        content=model_response_chunk.content,
                        event=RunEvent.tool_call_started,
                        from_run_response=run_response,
                    )

            # If the model response is a tool_call_completed, update the existing tool call in the run_response
            elif model_response_chunk.event == ModelResponseEvent.tool_call_completed.value:
                tool_calls_list = model_response_chunk.tool_calls
                if tool_calls_list is not None:
                    # Update the existing tool call in the run_response
                    if run_response.tools:
                        # Create a mapping of tool_call_id to index
                        tool_call_index_map = {
                            tc["tool_call_id"]: i
                            for i, tc in enumerate(run_response.tools)
                            if tc.get("tool_call_id") is not None
                        }
                        # Process tool calls
                        for tool_call_dict in tool_calls_list:
                            tool_call_id = tool_call_dict.get("tool_call_id")
                            index = tool_call_index_map.get(tool_call_id)
                            if index is not None:
                                run_response.tools[index] = tool_call_dict
                    else:
                        run_response.tools = tool_calls_list

                    if stream_intermediate_steps:
                        yield self._create_run_response(
                            content=model_response_chunk.content,
                            event=RunEvent.tool_call_completed,
                            from_run_response=run_response,
                        )

        # 3. Update the run_response
        # Handle structured outputs
        if (self.response_model is not None) and not self.use_json_mode and (full_model_response.parsed is not None):
            # Update the run_response content with the structured output
            run_response.content = full_model_response.parsed

        # 3. Update TeamRunResponse
        run_response.created_at = full_model_response.created_at
        if full_model_response.content is not None:
            run_response.content = full_model_response.content
        if full_model_response.thinking is not None:
            run_response.thinking = full_model_response.thinking
        if full_model_response.audio is not None:
            run_response.response_audio = full_model_response.audio
        if full_model_response.citations is not None:
            run_response.citations = full_model_response.citations

        # Build a list of messages that should be added to the RunResponse
        messages_for_run_response = [m for m in run_messages.messages if m.add_to_agent_memory]
        # Update the TeamRunResponse messages
        run_response.messages = messages_for_run_response
        # Update the TeamRunResponse metrics
        run_response.metrics = self._aggregate_metrics_from_messages(messages_for_run_response)

        # 4. Update Team Memory
        # Add the system message to the memory
        if run_messages.system_message is not None:
            self.memory.add_system_message(run_messages.system_message, system_message_role="system")

        # Build a list of messages that should be added to the AgentMemory
        messages_for_memory: List[Message] = (
            [run_messages.user_message] if run_messages.user_message is not None else []
        )

        for _rm in run_messages.messages[index_of_last_user_message:]:
            if _rm.add_to_agent_memory:
                messages_for_memory.append(_rm)
        if len(messages_for_memory) > 0:
            self.memory.add_messages(messages=messages_for_memory)

        team_run = TeamRun(response=run_response)
        team_run.message = run_messages.user_message

        # Update the memories with the user message if needed
        if (
            self.memory is not None
            and self.memory.create_user_memories
            and self.memory.update_user_memories_after_run
            and run_messages.user_message is not None
        ):
            await self.memory.aupdate_memory(input=run_messages.user_message.get_content_string())

        # Add AgentRun to memory
        self.memory.add_team_run(team_run)

        # 5. Calculate session metrics
        self.session_metrics = self._calculate_session_metrics()
        self.full_team_session_metrics = self._calculate_full_team_session_metrics()

        # 6. Save session to storage
        self.write_to_storage()

        # Log Team Run
        await self._alog_team_run()

        if stream_intermediate_steps:
            yield self._create_run_response(
                from_run_response=run_response,
                event=RunEvent.run_completed,
            )

        log_debug(f" Team Run End: {self.run_id} ", center=True, symbol="*")
        log_debug("")

    ###########################################################################
    # Print Response
    ###########################################################################

    def print_response(
        self,
        message: Optional[Union[List, Dict, str, Message]] = None,
        *,
        stream: bool = False,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_reasoning_verbose: bool = False,
        console: Optional[Any] = None,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        markdown: Optional[bool] = None,
        **kwargs: Any,
    ) -> None:
        if not tags_to_include_in_markdown:
            tags_to_include_in_markdown = {"think", "thinking"}

        if markdown is None:
            markdown = self.markdown
        else:
            self.markdown = markdown

        if self.response_model is not None:
            stream = False

        if stream:
            self._print_response_stream(
                message=message,
                console=console,
                show_message=show_message,
                show_reasoning=show_reasoning,
                show_reasoning_verbose=show_reasoning_verbose,
                tags_to_include_in_markdown=tags_to_include_in_markdown,
                audio=audio,
                images=images,
                videos=videos,
                files=files,
                markdown=markdown,
                **kwargs,
            )
        else:
            self._print_response(
                message=message,
                console=console,
                show_message=show_message,
                show_reasoning=show_reasoning,
                show_reasoning_verbose=show_reasoning_verbose,
                tags_to_include_in_markdown=tags_to_include_in_markdown,
                audio=audio,
                images=images,
                videos=videos,
                files=files,
                markdown=markdown,
                **kwargs,
            )

    def _print_response(
        self,
        message: Optional[Union[List, Dict, str, Message]] = None,
        console: Optional[Any] = None,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_reasoning_verbose: bool = False,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        markdown: bool = False,
        **kwargs: Any,
    ) -> None:
        from rich.console import Group
        from rich.json import JSON
        from rich.live import Live
        from rich.markdown import Markdown
        from rich.status import Status
        from rich.text import Text

        if not tags_to_include_in_markdown:
            tags_to_include_in_markdown = {"think", "thinking"}

        with Live(console=console) as live_console:
            status = Status("Thinking...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
            live_console.update(status)

            response_timer = Timer()
            response_timer.start()
            # Panels to be rendered
            panels = [status]
            # First render the message panel if the message is not None
            if message and show_message:
                # Convert message to a panel
                message_content = get_text_from_message(message)
                message_panel = create_panel(
                    content=Text(message_content, style="green"),
                    title="Message",
                    border_style="cyan",
                )
                panels.append(message_panel)
                live_console.update(Group(*panels))

            # Run the agent
            run_response: TeamRunResponse = self.run(  # type: ignore
                message=message,
                images=images,
                audio=audio,
                videos=videos,
                files=files,
                stream=False,
                **kwargs,
            )
            response_timer.stop()

            if markdown:
                member_markdown = {}
                for member in self.members:
                    if isinstance(member, Agent) and member.agent_id is not None:
                        member_markdown[member.agent_id] = True
                    if isinstance(member, Team) and member.team_id is not None:
                        member_markdown[member.team_id] = True
                team_markdown = True

            if self.response_model is not None:
                team_markdown = False

            for member in self.members:
                if member.response_model is not None and isinstance(member, Agent) and member.agent_id is not None:
                    member_markdown[member.agent_id] = False  # type: ignore
                if member.response_model is not None and isinstance(member, Team) and member.team_id is not None:
                    member_markdown[member.team_id] = False  # type: ignore

            # Handle reasoning
            reasoning_steps = []
            if (
                isinstance(run_response, TeamRunResponse)
                and run_response.extra_data is not None
                and run_response.extra_data.reasoning_steps is not None
            ):
                reasoning_steps = run_response.extra_data.reasoning_steps

            if len(reasoning_steps) > 0 and show_reasoning:
                # Create panels for reasoning steps
                for i, step in enumerate(reasoning_steps, 1):
                    reasoning_panel = self._build_reasoning_step_panel(i, step, show_reasoning_verbose)
                    panels.append(reasoning_panel)
                live_console.update(Group(*panels))

            if isinstance(run_response, TeamRunResponse) and run_response.thinking is not None:
                # Create panel for thinking
                thinking_panel = create_panel(
                    content=Text(run_response.thinking),
                    title=f"Thinking ({response_timer.elapsed:.1f}s)",
                    border_style="green",
                )
                panels.append(thinking_panel)
                live_console.update(Group(*panels))

            if isinstance(run_response, TeamRunResponse):
                # Handle member responses
                if self.show_members_responses:
                    for member_response in run_response.member_responses:
                        # Handle member reasoning
                        reasoning_steps = []
                        if (
                            isinstance(member_response, RunResponse)
                            and member_response.extra_data is not None
                            and member_response.extra_data.reasoning_steps is not None
                        ):
                            reasoning_steps.extend(member_response.extra_data.reasoning_steps)

                        if len(reasoning_steps) > 0 and show_reasoning:
                            # Create panels for reasoning steps
                            for i, step in enumerate(reasoning_steps, 1):
                                member_reasoning_panel = self._build_reasoning_step_panel(
                                    i, step, show_reasoning_verbose, color="magenta"
                                )
                                panels.append(member_reasoning_panel)

                        show_markdown = False
                        if isinstance(member_response, RunResponse) and member_response.agent_id is not None:
                            show_markdown = member_markdown.get(member_response.agent_id, False)
                        elif isinstance(member_response, TeamRunResponse) and member_response.team_id is not None:
                            show_markdown = member_markdown.get(member_response.team_id, False)

                        member_response_content: Union[str, JSON, Markdown] = self._parse_response_content(
                            member_response,
                            tags_to_include_in_markdown,
                            show_markdown=show_markdown,
                        )

                        # Create panel for member response
                        if isinstance(member_response, RunResponse) and member_response.agent_id is not None:
                            member_response_panel = create_panel(
                                content=member_response_content,
                                title=f"{self._get_member_name(member_response.agent_id)} Response",
                                border_style="magenta",
                            )
                        elif isinstance(member_response, TeamRunResponse) and member_response.team_id is not None:
                            member_response_panel = create_panel(
                                content=member_response_content,
                                title=f"{self._get_member_name(member_response.team_id)} Response",
                                border_style="magenta",
                            )
                        panels.append(member_response_panel)

                        if member_response.citations is not None and member_response.citations.urls is not None:
                            md_content = "\n".join(
                                f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                                for i, citation in enumerate(member_response.citations.urls)
                                if citation.url  # Only include citations with valid URLs
                            )
                            if md_content:  # Only create panel if there are citations
                                citations_panel = create_panel(
                                    content=Markdown(md_content),
                                    title="Citations",
                                    border_style="magenta",
                                )
                                panels.append(citations_panel)

                    live_console.update(Group(*panels))

                response_content_batch: Union[str, JSON, Markdown] = self._parse_response_content(
                    run_response, tags_to_include_in_markdown, show_markdown=team_markdown
                )

                # Create panel for response
                response_panel = create_panel(
                    content=response_content_batch,
                    title=f"Response ({response_timer.elapsed:.1f}s)",
                    border_style="blue",
                )
                panels.append(response_panel)

                # Add citations
                if run_response.citations is not None and run_response.citations.urls is not None:
                    md_content = "\n".join(
                        f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                        for i, citation in enumerate(run_response.citations.urls)
                        if citation.url  # Only include citations with valid URLs
                    )
                    if md_content:  # Only create panel if there are citations
                        citations_panel = create_panel(
                            content=Markdown(md_content),
                            title="Citations",
                            border_style="green",
                        )
                        panels.append(citations_panel)

            # Final update to remove the "Thinking..." status
            panels = [p for p in panels if not isinstance(p, Status)]
            live_console.update(Group(*panels))

    def _print_response_stream(
        self,
        message: Optional[Union[List, Dict, str, Message]] = None,
        console: Optional[Any] = None,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_reasoning_verbose: bool = False,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        markdown: bool = False,
        **kwargs: Any,
    ) -> None:
        from rich.console import Group
        from rich.json import JSON
        from rich.live import Live
        from rich.markdown import Markdown
        from rich.status import Status
        from rich.text import Text

        if not tags_to_include_in_markdown:
            tags_to_include_in_markdown = {"think", "thinking"}

        _response_content: str = ""
        _response_thinking: str = ""
        reasoning_steps: List[ReasoningStep] = []

        with Live(console=console) as live_console:
            status = Status("Thinking...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
            live_console.update(status)
            response_timer = Timer()
            response_timer.start()
            # Flag which indicates if the panels should be rendered
            render = False
            # Panels to be rendered
            panels = [status]
            # First render the message panel if the message is not None
            if message and show_message:
                render = True
                # Convert message to a panel
                message_content = get_text_from_message(message)
                message_panel = create_panel(
                    content=Text(message_content, style="green"),
                    title="Message",
                    border_style="cyan",
                )
                panels.append(message_panel)
            if render:
                live_console.update(Group(*panels))

            # Get response from the team
            stream_resp = self.run(  # type: ignore
                message=message, audio=audio, images=images, videos=videos, files=files, stream=True, **kwargs
            )

            team_markdown = None
            member_markdown = {}

            for resp in stream_resp:
                if team_markdown is None:
                    if markdown:
                        team_markdown = True
                    else:
                        team_markdown = False

                    if self.response_model is not None:
                        team_markdown = False

                if isinstance(resp, TeamRunResponse):
                    if resp.event == RunEvent.run_response:
                        if isinstance(resp.content, str):
                            _response_content += resp.content
                        if resp.thinking is not None:
                            _response_thinking += resp.thinking
                    if resp.extra_data is not None and resp.extra_data.reasoning_steps is not None:
                        reasoning_steps = resp.extra_data.reasoning_steps

                response_content_stream: Union[str, Markdown] = _response_content
                # Escape special tags before markdown conversion
                if team_markdown:
                    escaped_content = escape_markdown_tags(_response_content, tags_to_include_in_markdown)
                    response_content_stream = Markdown(escaped_content)

                # Create new panels for each chunk
                panels = [status]

                if message and show_message:
                    render = True
                    # Convert message to a panel
                    message_content = get_text_from_message(message)
                    message_panel = create_panel(
                        content=Text(message_content, style="green"),
                        title="Message",
                        border_style="cyan",
                    )
                    panels.append(message_panel)
                if render:
                    live_console.update(Group(*panels))

                if len(reasoning_steps) > 0 and show_reasoning:
                    render = True
                    # Create panels for reasoning steps
                    for i, step in enumerate(reasoning_steps, 1):
                        reasoning_panel = self._build_reasoning_step_panel(i, step, show_reasoning_verbose)
                        panels.append(reasoning_panel)
                if render:
                    live_console.update(Group(*panels))

                if len(_response_thinking) > 0:
                    render = True
                    # Create panel for thinking
                    thinking_panel = create_panel(
                        content=Text(_response_thinking),
                        title=f"Thinking ({response_timer.elapsed:.1f}s)",
                        border_style="green",
                    )
                    panels.append(thinking_panel)
                if render:
                    live_console.update(Group(*panels))

                if len(_response_content) > 0:
                    render = True
                    # Create panel for response
                    response_panel = create_panel(
                        content=response_content_stream,
                        title=f"Response ({response_timer.elapsed:.1f}s)",
                        border_style="blue",
                    )
                    panels.append(response_panel)
                if render:
                    live_console.update(Group(*panels))
            response_timer.stop()

            # Add citations
            if resp.citations is not None and resp.citations.urls is not None:
                md_content = "\n".join(
                    f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                    for i, citation in enumerate(resp.citations.urls)
                    if citation.url  # Only include citations with valid URLs
                )
                if md_content:  # Only create panel if there are citations
                    citations_panel = create_panel(
                        content=Markdown(md_content),
                        title="Citations",
                        border_style="green",
                    )
                    panels.append(citations_panel)
                    live_console.update(Group(*panels))

            # Final update to remove the "Thinking..." status
            panels = [p for p in panels if not isinstance(p, Status)]

            if markdown:
                for member in self.members:
                    if isinstance(member, Agent) and member.agent_id is not None:
                        member_markdown[member.agent_id] = True
                    if isinstance(member, Team) and member.team_id is not None:
                        member_markdown[member.team_id] = True

            for member in self.members:
                if member.response_model is not None and isinstance(member, Agent) and member.agent_id is not None:
                    member_markdown[member.agent_id] = False  # type: ignore
                if member.response_model is not None and isinstance(member, Team) and member.team_id is not None:
                    member_markdown[member.team_id] = False  # type: ignore

            # Create panel for member responses
            for i, member_response in enumerate(self.run_response.member_responses):  # type: ignore
                reasoning_steps = []
                if member_response.extra_data is not None and member_response.extra_data.reasoning_steps is not None:
                    reasoning_steps = member_response.extra_data.reasoning_steps
                if len(reasoning_steps) > 0 and show_reasoning:
                    # Create panels for reasoning steps
                    for j, step in enumerate(reasoning_steps, 1):
                        member_reasoning_panel = self._build_reasoning_step_panel(
                            j, step, show_reasoning_verbose, color="magenta"
                        )
                        panels.insert(i + 1, member_reasoning_panel)

                show_markdown = False
                if isinstance(member_response, RunResponse) and member_response.agent_id is not None:
                    show_markdown = member_markdown.get(member_response.agent_id, False)
                elif isinstance(member_response, TeamRunResponse) and member_response.team_id is not None:
                    show_markdown = member_markdown.get(member_response.team_id, False)

                member_response_content: Union[str, JSON, Markdown] = self._parse_response_content(
                    member_response,
                    tags_to_include_in_markdown,
                    show_markdown=show_markdown,
                )

                if isinstance(member_response, RunResponse) and member_response.agent_id is not None:
                    member_response_panel = create_panel(
                        content=member_response_content,
                        title=f"{self._get_member_name(member_response.agent_id)} Response",
                        border_style="magenta",
                    )
                elif isinstance(member_response, TeamRunResponse) and member_response.team_id is not None:
                    member_response_panel = create_panel(
                        content=member_response_content,
                        title=f"{self._get_member_name(member_response.team_id)} Response",
                        border_style="magenta",
                    )
                panels.insert(i + 1, member_response_panel)

                if member_response.citations is not None and member_response.citations.urls is not None:
                    md_content = "\n".join(
                        f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                        for i, citation in enumerate(member_response.citations.urls)
                        if citation.url  # Only include citations with valid URLs
                    )
                    if md_content:  # Only create panel if there are citations
                        citations_panel = create_panel(
                            content=Markdown(md_content),
                            title="Citations",
                            border_style="magenta",
                        )
                        panels.insert(i + 2, citations_panel)

            live_console.update(Group(*panels))

    async def aprint_response(
        self,
        message: Optional[Union[List, Dict, str, Message]] = None,
        *,
        stream: bool = False,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_reasoning_verbose: bool = False,
        console: Optional[Any] = None,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        markdown: Optional[bool] = None,
        **kwargs: Any,
    ) -> None:
        if not tags_to_include_in_markdown:
            tags_to_include_in_markdown = {"think", "thinking"}

        if markdown is None:
            markdown = self.markdown
        else:
            self.markdown = markdown

        if self.response_model is not None:
            stream = False

        if stream:
            await self._aprint_response_stream(
                message=message,
                console=console,
                show_message=show_message,
                show_reasoning=show_reasoning,
                show_reasoning_verbose=show_reasoning_verbose,
                tags_to_include_in_markdown=tags_to_include_in_markdown,
                audio=audio,
                images=images,
                videos=videos,
                files=files,
                markdown=markdown,
                **kwargs,
            )
        else:
            await self._aprint_response(
                message=message,
                console=console,
                show_message=show_message,
                show_reasoning=show_reasoning,
                show_reasoning_verbose=show_reasoning_verbose,
                tags_to_include_in_markdown=tags_to_include_in_markdown,
                audio=audio,
                images=images,
                videos=videos,
                files=files,
                markdown=markdown,
                **kwargs,
            )

    async def _aprint_response(
        self,
        message: Optional[Union[List, Dict, str, Message]] = None,
        console: Optional[Any] = None,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_reasoning_verbose: bool = False,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        markdown: bool = False,
        **kwargs: Any,
    ) -> None:
        from rich.console import Group
        from rich.json import JSON
        from rich.live import Live
        from rich.markdown import Markdown
        from rich.status import Status
        from rich.text import Text

        if not tags_to_include_in_markdown:
            tags_to_include_in_markdown = {"think", "thinking"}

        self.run_response = cast(TeamRunResponse, self.run_response)

        with Live(console=console) as live_console:
            status = Status("Thinking...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
            live_console.update(status)

            response_timer = Timer()
            response_timer.start()
            # Panels to be rendered
            panels = [status]
            # First render the message panel if the message is not None
            if message and show_message:
                # Convert message to a panel
                message_content = get_text_from_message(message)
                message_panel = create_panel(
                    content=Text(message_content, style="green"),
                    title="Message",
                    border_style="cyan",
                )
                panels.append(message_panel)
                live_console.update(Group(*panels))

            # Run the agent
            run_response: TeamRunResponse = await self.arun(  # type: ignore
                message=message,
                audio=audio,
                images=images,
                videos=videos,
                files=files,
                stream=False,
                **kwargs,
            )
            response_timer.stop()

            if markdown:
                member_markdown = {}
                for member in self.members:
                    if isinstance(member, Agent) and member.agent_id is not None:
                        member_markdown[member.agent_id] = True
                    if isinstance(member, Team) and member.team_id is not None:
                        member_markdown[member.team_id] = True
                team_markdown = True

            if self.response_model is not None:
                team_markdown = False

            for member in self.members:
                if member.response_model is not None and isinstance(member, Agent) and member.agent_id is not None:
                    member_markdown[member.agent_id] = False  # type: ignore
                if member.response_model is not None and isinstance(member, Team) and member.team_id is not None:
                    member_markdown[member.team_id] = False  # type: ignore

            # Handle reasoning
            reasoning_steps = []
            if (
                isinstance(run_response, TeamRunResponse)
                and run_response.extra_data is not None
                and run_response.extra_data.reasoning_steps is not None
            ):
                reasoning_steps = run_response.extra_data.reasoning_steps

            if len(reasoning_steps) > 0 and show_reasoning:
                # Create panels for reasoning steps
                for i, step in enumerate(reasoning_steps, 1):
                    reasoning_panel = self._build_reasoning_step_panel(i, step, show_reasoning_verbose)
                    panels.append(reasoning_panel)
                live_console.update(Group(*panels))

            if isinstance(run_response, TeamRunResponse) and run_response.thinking is not None:
                # Create panel for thinking
                thinking_panel = create_panel(
                    content=Text(run_response.thinking),
                    title=f"Thinking ({response_timer.elapsed:.1f}s)",
                    border_style="green",
                )
                panels.append(thinking_panel)
                live_console.update(Group(*panels))

            if isinstance(run_response, TeamRunResponse):
                # Handle member responses
                if self.show_members_responses:
                    for member_response in run_response.member_responses:
                        member_response = cast(RunResponse, member_response)
                        # Handle member reasoning
                        reasoning_steps = []
                        if (
                            isinstance(member_response, RunResponse)
                            and member_response.extra_data is not None
                            and member_response.extra_data.reasoning_steps is not None
                        ):
                            reasoning_steps = member_response.extra_data.reasoning_steps

                        if len(reasoning_steps) > 0 and show_reasoning:
                            # Create panels for reasoning steps
                            for i, step in enumerate(reasoning_steps, 1):
                                member_reasoning_panel = self._build_reasoning_step_panel(
                                    i, step, show_reasoning_verbose, color="magenta"
                                )
                                panels.append(member_reasoning_panel)

                        show_markdown = False
                        if isinstance(member_response, RunResponse) and member_response.agent_id is not None:
                            show_markdown = member_markdown.get(member_response.agent_id, False)  # type: ignore
                        elif isinstance(member_response, TeamRunResponse) and member_response.team_id is not None:
                            show_markdown = member_markdown.get(member_response.team_id, False)  # type: ignore

                        member_response_content: Union[str, JSON, Markdown] = self._parse_response_content(
                            member_response,
                            tags_to_include_in_markdown,
                            show_markdown=show_markdown,
                        )

                        # Create panel for member response
                        if member_response.agent_id:
                            member_response_panel = create_panel(
                                content=member_response_content,
                                title=f"{self._get_member_name(member_response.agent_id)} Response",
                                border_style="magenta",
                            )
                        panels.append(member_response_panel)

                        if member_response.citations is not None and member_response.citations.urls is not None:
                            md_content = "\n".join(
                                f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                                for i, citation in enumerate(member_response.citations.urls)
                                if citation.url  # Only include citations with valid URLs
                            )
                            if md_content:  # Only create panel if there are citations
                                citations_panel = create_panel(
                                    content=Markdown(md_content),
                                    title="Citations",
                                    border_style="magenta",
                                )
                                panels.append(citations_panel)

                    live_console.update(Group(*panels))

                response_content_batch: Union[str, JSON, Markdown] = self._parse_response_content(
                    run_response, tags_to_include_in_markdown, show_markdown=team_markdown
                )

                # Create panel for response
                response_panel = create_panel(
                    content=response_content_batch,
                    title=f"Response ({response_timer.elapsed:.1f}s)",
                    border_style="blue",
                )
                panels.append(response_panel)

                # Add citations
                if run_response.citations is not None and run_response.citations.urls is not None:
                    md_content = "\n".join(
                        f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                        for i, citation in enumerate(run_response.citations.urls)
                        if citation.url  # Only include citations with valid URLs
                    )
                    if md_content:  # Only create panel if there are citations
                        citations_panel = create_panel(
                            content=Markdown(md_content),
                            title="Citations",
                            border_style="green",
                        )
                        panels.append(citations_panel)

            # Final update to remove the "Thinking..." status
            panels = [p for p in panels if not isinstance(p, Status)]
            live_console.update(Group(*panels))

    async def _aprint_response_stream(
        self,
        message: Optional[Union[List, Dict, str, Message]] = None,
        console: Optional[Any] = None,
        show_message: bool = True,
        show_reasoning: bool = True,
        show_reasoning_verbose: bool = False,
        tags_to_include_in_markdown: Optional[Set[str]] = None,
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        markdown: bool = False,
        **kwargs: Any,
    ) -> None:
        from rich.console import Group
        from rich.json import JSON
        from rich.live import Live
        from rich.markdown import Markdown
        from rich.status import Status
        from rich.text import Text

        if not tags_to_include_in_markdown:
            tags_to_include_in_markdown = {"think", "thinking"}

        self.run_response = cast(TeamRunResponse, self.run_response)

        _response_content: str = ""
        _response_thinking: str = ""
        reasoning_steps: List[ReasoningStep] = []

        with Live(console=console) as live_console:
            status = Status("Thinking...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
            live_console.update(status)
            response_timer = Timer()
            response_timer.start()
            # Flag which indicates if the panels should be rendered
            render = False
            # Panels to be rendered
            panels = [status]
            # First render the message panel if the message is not None
            if message and show_message:
                render = True
                # Convert message to a panel
                message_content = get_text_from_message(message)
                message_panel = create_panel(
                    content=Text(message_content, style="green"),
                    title="Message",
                    border_style="cyan",
                )
                panels.append(message_panel)
            if render:
                live_console.update(Group(*panels))

            # Get response from the team
            stream_resp = await self.arun(  # type: ignore
                message=message, audio=audio, images=images, videos=videos, files=files, stream=True, **kwargs
            )
            team_markdown = None
            member_markdown = {}

            async for resp in stream_resp:
                if team_markdown is None:
                    if markdown:
                        team_markdown = True
                    else:
                        team_markdown = False

                    if self.response_model is not None:
                        team_markdown = False

                if isinstance(resp, TeamRunResponse):
                    if resp.event == RunEvent.run_response:
                        if isinstance(resp.content, str):
                            _response_content += resp.content
                        if resp.thinking is not None:
                            _response_thinking += resp.thinking
                    if resp.extra_data is not None and resp.extra_data.reasoning_steps is not None:
                        reasoning_steps = resp.extra_data.reasoning_steps

                response_content_stream: Union[str, Markdown] = _response_content
                # Escape special tags before markdown conversion
                if team_markdown:
                    escaped_content = escape_markdown_tags(_response_content, tags_to_include_in_markdown)
                    response_content_stream = Markdown(escaped_content)

                # Create new panels for each chunk
                panels = [status]

                if message and show_message:
                    render = True
                    # Convert message to a panel
                    message_content = get_text_from_message(message)
                    message_panel = create_panel(
                        content=Text(message_content, style="green"),
                        title="Message",
                        border_style="cyan",
                    )
                    panels.append(message_panel)
                if render:
                    live_console.update(Group(*panels))

                if len(reasoning_steps) > 0 and show_reasoning:
                    render = True
                    # Create panels for reasoning steps
                    for i, step in enumerate(reasoning_steps, 1):
                        reasoning_panel = self._build_reasoning_step_panel(i, step, show_reasoning_verbose)
                        panels.append(reasoning_panel)
                if render:
                    live_console.update(Group(*panels))

                if len(_response_thinking) > 0:
                    render = True
                    # Create panel for thinking
                    thinking_panel = create_panel(
                        content=Text(_response_thinking),
                        title=f"Thinking ({response_timer.elapsed:.1f}s)",
                        border_style="green",
                    )
                    panels.append(thinking_panel)
                if render:
                    live_console.update(Group(*panels))

                if len(_response_content) > 0:
                    render = True
                    # Create panel for response
                    response_panel = create_panel(
                        content=response_content_stream,
                        title=f"Response ({response_timer.elapsed:.1f}s)",
                        border_style="blue",
                    )
                    panels.append(response_panel)
                if render:
                    live_console.update(Group(*panels))
            response_timer.stop()

            # Add citations
            if resp.citations is not None and resp.citations.urls is not None:
                md_content = "\n".join(
                    f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                    for i, citation in enumerate(resp.citations.urls)
                    if citation.url  # Only include citations with valid URLs
                )
                if md_content:  # Only create panel if there are citations
                    citations_panel = create_panel(
                        content=Markdown(md_content),
                        title="Citations",
                        border_style="green",
                    )
                    panels.append(citations_panel)
                    live_console.update(Group(*panels))

            # Final update to remove the "Thinking..." status
            panels = [p for p in panels if not isinstance(p, Status)]

            if markdown:
                for member in self.members:
                    if isinstance(member, Agent) and member.agent_id is not None:
                        member_markdown[member.agent_id] = True  # type: ignore
                    if isinstance(member, Team) and member.team_id is not None:
                        member_markdown[member.team_id] = True  # type: ignore

            for member in self.members:
                if member.response_model is not None and isinstance(member, Agent) and member.agent_id is not None:
                    member_markdown[member.agent_id] = False  # type: ignore
                if member.response_model is not None and isinstance(member, Team) and member.team_id is not None:
                    member_markdown[member.team_id] = False  # type: ignore

            # Create panel for member responses
            for i, member_response in enumerate(self.run_response.member_responses):
                reasoning_steps = []
                if member_response.extra_data is not None and member_response.extra_data.reasoning_steps is not None:
                    reasoning_steps = member_response.extra_data.reasoning_steps
                if len(reasoning_steps) > 0 and show_reasoning:
                    # Create panels for reasoning steps
                    for j, step in enumerate(reasoning_steps, 1):
                        member_reasoning_panel = self._build_reasoning_step_panel(
                            j, step, show_reasoning_verbose, color="magenta"
                        )
                        panels.insert(i + 1, member_reasoning_panel)

                show_markdown = False
                if isinstance(member_response, RunResponse) and member_response.agent_id is not None:
                    show_markdown = member_markdown.get(member_response.agent_id, False)
                elif isinstance(member_response, TeamRunResponse) and member_response.team_id is not None:
                    show_markdown = member_markdown.get(member_response.team_id, False)

                member_response_content: Union[str, JSON, Markdown] = self._parse_response_content(
                    member_response,
                    tags_to_include_in_markdown,
                    show_markdown=show_markdown,
                )

                if isinstance(member_response, RunResponse) and member_response.agent_id is not None:
                    member_response_panel = create_panel(
                        content=member_response_content,
                        title=f"{self._get_member_name(member_response.agent_id)} Response",
                        border_style="magenta",
                    )
                elif isinstance(member_response, TeamRunResponse) and member_response.team_id is not None:
                    member_response_panel = create_panel(
                        content=member_response_content,
                        title=f"{self._get_member_name(member_response.team_id)} Response",
                        border_style="magenta",
                    )
                panels.insert(i + 1, member_response_panel)

                if member_response.citations is not None and member_response.citations.urls is not None:
                    md_content = "\n".join(
                        f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
                        for i, citation in enumerate(member_response.citations.urls)
                        if citation.url  # Only include citations with valid URLs
                    )
                    if md_content:  # Only create panel if there are citations
                        citations_panel = create_panel(
                            content=Markdown(md_content),
                            title="Citations",
                            border_style="magenta",
                        )
                        panels.insert(i + 2, citations_panel)

            live_console.update(Group(*panels))

    def _build_reasoning_step_panel(
        self, step_idx: int, step: ReasoningStep, show_reasoning_verbose: bool = False, color: str = "green"
    ):
        from rich.text import Text

        # Build step content
        step_content = Text.assemble()
        if step.title is not None:
            step_content.append(f"{step.title}\n", "bold")
        if step.action is not None:
            step_content.append(f"{step.action}\n", "dim")
        if step.result is not None:
            step_content.append(Text.from_markup(step.result, style="dim"))

        if show_reasoning_verbose:
            # Add detailed reasoning information if available
            if step.reasoning is not None:
                step_content.append(Text.from_markup(f"\n[bold]Reasoning:[/bold] {step.reasoning}", style="dim"))
            if step.confidence is not None:
                step_content.append(Text.from_markup(f"\n[bold]Confidence:[/bold] {step.confidence}", style="dim"))
        return create_panel(content=step_content, title=f"Reasoning step {step_idx}", border_style=color)

    def _get_member_name(self, entity_id: str) -> str:
        for member in self.members:
            if isinstance(member, Agent):
                if member.agent_id == entity_id:
                    return member.name or entity_id
            elif isinstance(member, Team):
                if member.team_id == entity_id:
                    return member.name or entity_id
        return entity_id

    def _parse_response_content(
        self,
        run_response: Union[TeamRunResponse, RunResponse],
        tags_to_include_in_markdown: Set[str],
        show_markdown: bool = True,
    ) -> Any:
        from rich.json import JSON
        from rich.markdown import Markdown

        if isinstance(run_response.content, str):
            if show_markdown:
                escaped_content = escape_markdown_tags(run_response.content, tags_to_include_in_markdown)
                return Markdown(escaped_content)
            else:
                return run_response.get_content_as_string(indent=4)
        elif isinstance(run_response.content, BaseModel):
            try:
                return JSON(run_response.content.model_dump_json(exclude_none=True), indent=2)
            except Exception as e:
                log_warning(f"Failed to convert response to JSON: {e}")
        else:
            try:
                return JSON(json.dumps(run_response.content), indent=4)
            except Exception as e:
                log_warning(f"Failed to convert response to JSON: {e}")

    def cli_app(
        self,
        message: Optional[str] = None,
        user: str = "User",
        emoji: str = ":sunglasses:",
        stream: bool = False,
        markdown: bool = False,
        exit_on: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> None:
        from rich.prompt import Prompt

        if message:
            self.print_response(message=message, stream=stream, markdown=markdown, **kwargs)

        _exit_on = exit_on or ["exit", "quit", "bye"]
        while True:
            message = Prompt.ask(f"[bold] {emoji} {user} [/bold]")
            if message in _exit_on:
                break

            self.print_response(message=message, stream=stream, markdown=markdown, **kwargs)

    ###########################################################################
    # Helpers
    ###########################################################################

    def _calculate_session_metrics(self) -> SessionMetrics:
        self.memory = cast(TeamMemory, self.memory)
        session_metrics = SessionMetrics()
        assistant_message_role = self.model.assistant_message_role if self.model is not None else "assistant"

        # Get metrics of the team-agent's messages
        for m in self.memory.messages:
            if m.role == assistant_message_role and m.metrics is not None:
                session_metrics += m.metrics

        return session_metrics

    def _calculate_full_team_session_metrics(self) -> SessionMetrics:
        current_session_metrics = self.session_metrics or self._calculate_session_metrics()
        current_session_metrics = replace(current_session_metrics)

        assistant_message_role = self.model.assistant_message_role if self.model is not None else "assistant"

        # Get metrics of the team-agent's messages
        for member in self.members:
            # Only members that ran has memory
            if member.memory is not None:
                for m in member.memory.messages:
                    if m.role == assistant_message_role and m.metrics is not None:
                        current_session_metrics += m.metrics
        return current_session_metrics

    def _aggregate_metrics_from_messages(self, messages: List[Message]) -> Dict[str, Any]:
        aggregated_metrics: Dict[str, Any] = defaultdict(list)
        assistant_message_role = self.model.assistant_message_role if self.model is not None else "assistant"
        for m in messages:
            if m.role == assistant_message_role and m.metrics is not None:
                for k, v in asdict(m.metrics).items():  # type: ignore
                    if k == "timer":
                        continue
                    if v is not None:
                        aggregated_metrics[k].append(v)
        if aggregated_metrics is not None:
            aggregated_metrics = dict(aggregated_metrics)
        return aggregated_metrics

    def _get_reasoning_agent(self, reasoning_model: Model) -> Optional[Agent]:
        return Agent(
            model=reasoning_model, monitoring=self.monitoring, telemetry=self.telemetry, debug_mode=self.debug_mode
        )

    def _reason(
        self, run_response: TeamRunResponse, run_messages: RunMessages, stream_intermediate_steps: bool = False
    ) -> Iterator[TeamRunResponse]:
        if stream_intermediate_steps:
            yield self._create_run_response(
                from_run_response=run_response, content="Reasoning started", event=RunEvent.reasoning_started
            )

        # Get the reasoning model
        reasoning_model: Optional[Model] = self.reasoning_model
        reasoning_model_provided = reasoning_model is not None
        if reasoning_model is None and self.model is not None:
            reasoning_model = self.model.__class__(id=self.model.id)  # type: ignore
        if reasoning_model is None:
            log_warning("Reasoning error. Reasoning model is None, continuing regular session...")
            return

        # If a reasoning model is provided, use it to generate reasoning
        if reasoning_model_provided:
            # Use DeepSeek for reasoning
            if (
                self.reasoning_model is not None
                and self.reasoning_model.__class__.__name__ == "DeepSeek"
                and self.reasoning_model.id.lower() == "deepseek-reasoner"
            ):
                from agno.reasoning.deepseek import get_deepseek_reasoning

                reasoning_agent = self._get_reasoning_agent(self.reasoning_model)

                reasoning_message: Optional[Message] = get_deepseek_reasoning(
                    reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
                )
                if reasoning_message is None:
                    log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
                    return
            # Use Groq for reasoning
            elif (
                self.reasoning_model is not None
                and reasoning_model.__class__.__name__ == "Groq"
                and "deepseek" in reasoning_model.id.lower()
            ):
                from agno.reasoning.groq import get_groq_reasoning

                reasoning_agent = self._get_reasoning_agent(self.reasoning_model)

                reasoning_message = get_groq_reasoning(
                    reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
                )
                if reasoning_message is None:
                    log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
                    return
            # Use OpenAI o3 for reasoning
            elif (
                self.reasoning_model is not None
                and reasoning_model.__class__.__name__ == "OpenAIChat"
                and reasoning_model.id.startswith("o3")
            ):
                from agno.reasoning.openai import get_openai_reasoning

                reasoning_agent = self._get_reasoning_agent(self.reasoning_model)

                reasoning_message = get_openai_reasoning(
                    reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
                )
                if reasoning_message is None:
                    log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
                    return
            else:
                log_info(
                    f"Reasoning model: {reasoning_model.__class__.__name__} is not a native reasoning model,  using default chain-of-thought reasoning."
                )
                yield from self._chain_of_thought_reason(
                    run_response, run_messages, reasoning_model, stream_intermediate_steps
                )
                return

            run_messages.messages.append(reasoning_message)
            # Add reasoning step to the Agent's run_response
            update_run_response_with_reasoning(
                run_response=run_response,
                reasoning_steps=[ReasoningStep(result=reasoning_message.content)],
                reasoning_agent_messages=[reasoning_message],
            )
            # Yield the final reasoning completed event
            if stream_intermediate_steps:
                yield self._create_run_response(
                    from_run_response=run_response,
                    content=ReasoningSteps(reasoning_steps=[ReasoningStep(result=reasoning_message.content)]),
                    content_type=ReasoningSteps.__class__.__name__,
                    event=RunEvent.reasoning_completed,
                )
        else:
            log_warning("Reasoning model is not provided, using default chain-of-thought reasoning.")
            yield from self._chain_of_thought_reason(
                run_response, run_messages, reasoning_model, stream_intermediate_steps
            )
            return

    async def _areason(
        self, run_response: TeamRunResponse, run_messages: RunMessages, stream_intermediate_steps: bool = False
    ) -> AsyncIterator[TeamRunResponse]:
        if stream_intermediate_steps:
            yield self._create_run_response(
                from_run_response=run_response, content="Reasoning started", event=RunEvent.reasoning_started
            )

        # Get the reasoning model
        reasoning_model: Optional[Model] = self.reasoning_model
        reasoning_model_provided = reasoning_model is not None
        if reasoning_model is None and self.model is not None:
            reasoning_model = self.model.__class__(id=self.model.id)  # type: ignore
        if reasoning_model is None:
            log_warning("Reasoning error. Reasoning model is None, continuing regular session...")
            return

        # If a reasoning model is provided, use it to generate reasoning
        if reasoning_model_provided:
            # Use DeepSeek for reasoning
            if (
                self.reasoning_model is not None
                and self.reasoning_model.__class__.__name__ == "DeepSeek"
                and self.reasoning_model.id.lower() == "deepseek-reasoner"
            ):
                from agno.reasoning.deepseek import aget_deepseek_reasoning

                reasoning_agent = self._get_reasoning_agent(self.reasoning_model)

                reasoning_message: Optional[Message] = await aget_deepseek_reasoning(
                    reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
                )
                if reasoning_message is None:
                    log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
                    return
            # Use Groq for reasoning
            elif (
                self.reasoning_model is not None
                and reasoning_model.__class__.__name__ == "Groq"
                and "deepseek" in reasoning_model.id.lower()
            ):
                from agno.reasoning.groq import aget_groq_reasoning

                reasoning_agent = self._get_reasoning_agent(self.reasoning_model)

                reasoning_message = await aget_groq_reasoning(
                    reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
                )
                if reasoning_message is None:
                    log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
                    return
            # Use OpenAI o3 for reasoning
            elif (
                self.reasoning_model is not None
                and reasoning_model.__class__.__name__ == "OpenAIChat"
                and reasoning_model.id.startswith("o3")
            ):
                from agno.reasoning.openai import aget_openai_reasoning

                reasoning_agent = self._get_reasoning_agent(self.reasoning_model)

                reasoning_message = await aget_openai_reasoning(
                    reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
                )
                if reasoning_message is None:
                    log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
                    return
            else:
                log_info(
                    f"Reasoning model: {reasoning_model.__class__.__name__} is not a native reasoning model,  using default chain-of-thought reasoning."
                )
                async for response in self._achain_of_thought_reason(
                    run_response, run_messages, reasoning_model, stream_intermediate_steps
                ):
                    yield response
                return

            run_messages.messages.append(reasoning_message)
            # Add reasoning step to the Agent's run_response
            update_run_response_with_reasoning(
                run_response=run_response,
                reasoning_steps=[ReasoningStep(result=reasoning_message.content)],
                reasoning_agent_messages=[reasoning_message],
            )
            # Yield the final reasoning completed event
            if stream_intermediate_steps:
                yield self._create_run_response(
                    from_run_response=run_response,
                    content=ReasoningSteps(reasoning_steps=[ReasoningStep(result=reasoning_message.content)]),
                    content_type=ReasoningSteps.__class__.__name__,
                    event=RunEvent.reasoning_completed,
                )
        else:
            log_warning("Reasoning model is not provided, using default chain-of-thought reasoning.")
            async for response in self._achain_of_thought_reason(
                run_response, run_messages, reasoning_model, stream_intermediate_steps
            ):
                yield response
            return

    def _chain_of_thought_reason(
        self,
        run_response: TeamRunResponse,
        run_messages: RunMessages,
        reasoning_model: Model,
        stream_intermediate_steps: bool = False,
    ) -> Iterator[TeamRunResponse]:
        from agno.reasoning.default import get_default_reasoning_agent
        from agno.reasoning.helpers import get_next_action, update_messages_with_reasoning

        # Get default reasoning agent
        use_json_mode: bool = self.use_json_mode
        reasoning_agent: Agent = get_default_reasoning_agent(  # type: ignore
            reasoning_model=reasoning_model,
            min_steps=self.reasoning_min_steps,
            max_steps=self.reasoning_max_steps,
            monitoring=self.monitoring,
            telemetry=self.telemetry,
            debug_mode=self.debug_mode,
            use_json_mode=use_json_mode,
        )

        step_count = 1
        next_action = NextAction.CONTINUE
        reasoning_messages: List[Message] = []
        all_reasoning_steps: List[ReasoningStep] = []
        log_debug("==== Starting Reasoning ====")
        while next_action == NextAction.CONTINUE and step_count < self.reasoning_max_steps:
            log_debug(f"==== Step {step_count} ====")
            step_count += 1
            try:
                # Run the reasoning agent
                reasoning_agent_response: RunResponse = reasoning_agent.run(messages=run_messages.get_input_messages())
                if reasoning_agent_response.content is None or reasoning_agent_response.messages is None:
                    log_warning("Reasoning error. Reasoning response is empty, continuing regular session...")
                    break

                if reasoning_agent_response.content.reasoning_steps is None:
                    log_warning("Reasoning error. Reasoning steps are empty, continuing regular session...")
                    break

                reasoning_steps: List[ReasoningStep] = reasoning_agent_response.content.reasoning_steps
                all_reasoning_steps.extend(reasoning_steps)
                # Yield reasoning steps
                if stream_intermediate_steps:
                    for reasoning_step in reasoning_steps:
                        yield self._create_run_response(
                            from_run_response=run_response,
                            content=reasoning_step,
                            content_type=reasoning_step.__class__.__name__,
                            event=RunEvent.reasoning_step,
                        )

                # Find the index of the first assistant message
                first_assistant_index = next(
                    (i for i, m in enumerate(reasoning_agent_response.messages) if m.role == "assistant"),
                    len(reasoning_agent_response.messages),
                )
                # Extract reasoning messages starting from the message after the first assistant message
                reasoning_messages = reasoning_agent_response.messages[first_assistant_index:]

                # Add reasoning step to the Agent's run_response
                update_run_response_with_reasoning(
                    run_response=run_response,
                    reasoning_steps=reasoning_steps,
                    reasoning_agent_messages=reasoning_agent_response.messages,
                )

                # Get the next action
                next_action = get_next_action(reasoning_steps[-1])
                if next_action == NextAction.FINAL_ANSWER:
                    break
            except Exception as e:
                log_error(f"Reasoning error: {e}")
                break

        log_debug(f"Total Reasoning steps: {len(all_reasoning_steps)}")
        log_debug("==== Reasoning finished====")

        # Update the messages_for_model to include reasoning messages
        update_messages_with_reasoning(
            run_messages=run_messages,
            reasoning_messages=reasoning_messages,
        )

        # Yield the final reasoning completed event
        if stream_intermediate_steps:
            yield self._create_run_response(
                from_run_response=run_response,
                content=ReasoningSteps(reasoning_steps=all_reasoning_steps),
                content_type=ReasoningSteps.__class__.__name__,
                event=RunEvent.reasoning_completed,
            )

    async def _achain_of_thought_reason(
        self,
        run_response: TeamRunResponse,
        run_messages: RunMessages,
        reasoning_model: Model,
        stream_intermediate_steps: bool = False,
    ) -> AsyncIterator[TeamRunResponse]:
        from agno.reasoning.default import get_default_reasoning_agent
        from agno.reasoning.helpers import get_next_action, update_messages_with_reasoning

        # Get default reasoning agent
        use_json_mode: bool = self.use_json_mode
        reasoning_agent: Agent = get_default_reasoning_agent(  # type: ignore
            reasoning_model=reasoning_model,
            min_steps=self.reasoning_min_steps,
            max_steps=self.reasoning_max_steps,
            monitoring=self.monitoring,
            telemetry=self.telemetry,
            debug_mode=self.debug_mode,
            use_json_mode=use_json_mode,
        )

        step_count = 1
        next_action = NextAction.CONTINUE
        reasoning_messages: List[Message] = []
        all_reasoning_steps: List[ReasoningStep] = []
        log_debug("==== Starting Reasoning ====")
        while next_action == NextAction.CONTINUE and step_count < self.reasoning_max_steps:
            log_debug(f"==== Step {step_count} ====")
            step_count += 1
            try:
                # Run the reasoning agent
                reasoning_agent_response: RunResponse = await reasoning_agent.arun(
                    messages=run_messages.get_input_messages()
                )
                if reasoning_agent_response.content is None or reasoning_agent_response.messages is None:
                    log_warning("Reasoning error. Reasoning response is empty, continuing regular session...")
                    break

                if reasoning_agent_response.content.reasoning_steps is None:
                    log_warning("Reasoning error. Reasoning steps are empty, continuing regular session...")
                    break

                reasoning_steps: List[ReasoningStep] = reasoning_agent_response.content.reasoning_steps
                all_reasoning_steps.extend(reasoning_steps)
                # Yield reasoning steps
                if stream_intermediate_steps:
                    for reasoning_step in reasoning_steps:
                        yield self._create_run_response(
                            from_run_response=run_response,
                            content=reasoning_step,
                            content_type=reasoning_step.__class__.__name__,
                            event=RunEvent.reasoning_step,
                        )

                # Find the index of the first assistant message
                first_assistant_index = next(
                    (i for i, m in enumerate(reasoning_agent_response.messages) if m.role == "assistant"),
                    len(reasoning_agent_response.messages),
                )
                # Extract reasoning messages starting from the message after the first assistant message
                reasoning_messages = reasoning_agent_response.messages[first_assistant_index:]

                # Add reasoning step to the Agent's run_response
                update_run_response_with_reasoning(
                    run_response=run_response,
                    reasoning_steps=reasoning_steps,
                    reasoning_agent_messages=reasoning_agent_response.messages,
                )

                # Get the next action
                next_action = get_next_action(reasoning_steps[-1])
                if next_action == NextAction.FINAL_ANSWER:
                    break
            except Exception as e:
                log_error(f"Reasoning error: {e}")
                break

        log_debug(f"Total Reasoning steps: {len(all_reasoning_steps)}")
        log_debug("==== Reasoning finished====")

        # Update the messages_for_model to include reasoning messages
        update_messages_with_reasoning(
            run_messages=run_messages,
            reasoning_messages=reasoning_messages,
        )

        # Yield the final reasoning completed event
        if stream_intermediate_steps:
            yield self._create_run_response(
                from_run_response=run_response,
                content=ReasoningSteps(reasoning_steps=all_reasoning_steps),
                content_type=ReasoningSteps.__class__.__name__,
                event=RunEvent.reasoning_completed,
            )

    def _create_run_response(
        self,
        content: Optional[Any] = None,
        content_type: Optional[str] = None,
        thinking: Optional[str] = None,
        event: RunEvent = RunEvent.run_response,
        tools: Optional[List[Dict[str, Any]]] = None,
        audio: Optional[List[AudioArtifact]] = None,
        images: Optional[List[ImageArtifact]] = None,
        videos: Optional[List[VideoArtifact]] = None,
        response_audio: Optional[AudioResponse] = None,
        citations: Optional[Citations] = None,
        model: Optional[str] = None,
        messages: Optional[List[Message]] = None,
        created_at: Optional[int] = None,
        from_run_response: Optional[TeamRunResponse] = None,
    ) -> TeamRunResponse:
        extra_data = None
        member_responses = None
        if from_run_response:
            content = from_run_response.content
            content_type = from_run_response.content_type
            audio = from_run_response.audio
            images = from_run_response.images
            videos = from_run_response.videos
            response_audio = from_run_response.response_audio
            model = from_run_response.model
            messages = from_run_response.messages
            extra_data = from_run_response.extra_data
            member_responses = from_run_response.member_responses
            citations = from_run_response.citations

        rr = TeamRunResponse(
            run_id=self.run_id,
            session_id=self.session_id,
            team_id=self.team_id,
            content=content,
            thinking=thinking,
            tools=tools,
            audio=audio,
            images=images,
            videos=videos,
            response_audio=response_audio,
            citations=citations,
            model=model,
            messages=messages,
            extra_data=extra_data,
            event=event.value,
        )
        if member_responses:
            rr.member_responses = member_responses
        if content_type is not None:
            rr.content_type = content_type
        if created_at is not None:
            rr.created_at = created_at
        return rr

    def _resolve_run_context(self) -> None:
        from inspect import signature

        log_debug("Resolving context")
        if self.context is not None:
            for ctx_key, ctx_value in self.context.items():
                if callable(ctx_value):
                    try:
                        sig = signature(ctx_value)
                        if "agent" in sig.parameters:
                            resolved_ctx_value = ctx_value(agent=self)
                        else:
                            resolved_ctx_value = ctx_value()
                        if resolved_ctx_value is not None:
                            self.context[ctx_key] = resolved_ctx_value
                    except Exception as e:
                        log_warning(f"Failed to resolve context for {ctx_key}: {e}")
                else:
                    self.context[ctx_key] = ctx_value

    def _configure_model(self, show_tool_calls: bool = False) -> None:
        # Set the default model
        if self.model is None:
            try:
                from agno.models.openai import OpenAIChat
            except ModuleNotFoundError as e:
                log_exception(e)
                log_error(
                    "Agno agents use `openai` as the default model provider. "
                    "Please provide a `model` or install `openai`."
                )
                exit(1)

            log_info("Setting default model to OpenAI Chat")
            self.model = OpenAIChat(id="gpt-4o")

        # Update the response_format on the Model
        if self.response_model is None:
            self.model.response_format = None
        else:
            json_response_format = {"type": "json_object"}

            if self.model.supports_native_structured_outputs:
                if self.use_json_mode:
                    self.model.response_format = json_response_format
                    self.model.structured_outputs = False
                else:
                    log_debug("Setting Model.response_format to Agent.response_model")
                    self.model.response_format = self.response_model
                    self.model.structured_outputs = True

            elif self.model.supports_json_schema_outputs:
                if self.use_json_mode:
                    log_debug("Setting Model.response_format to JSON response mode")
                    self.model.response_format = {
                        "type": "json_schema",
                        "json_schema": {
                            "name": self.response_model.__name__,
                            "schema": self.response_model.model_json_schema(),
                        },
                    }
                else:
                    self.model.response_format = None
                self.model.structured_outputs = False

            else:  # Model does not support structured or JSON schema outputs
                self.model.response_format = json_response_format if self.use_json_mode else None
                self.model.structured_outputs = False

        # Set show_tool_calls on the Model
        self.model.show_tool_calls = show_tool_calls

    def _add_tools_to_model(self, model: Model, tools: List[Union[Function, Callable]]) -> None:
        # We have to reset for every run, because we will have new images/audio/video to attach
        self._functions_for_model = {}
        self._tools_for_model = []
        # Get Agent tools
        if len(tools) > 0:
            log_debug("Processing tools for model")

            # Check if we need strict mode for the model
            strict = False
            if self.response_model is not None and not self.use_json_mode and model.supports_native_structured_outputs:
                strict = True

            self._tools_for_model = []
            self._functions_for_model = {}

            for tool in tools:
                if isinstance(tool, Function):
                    if tool.name not in self._functions_for_model:
                        tool._agent = self
                        tool.process_entrypoint(strict=strict)
                        if strict and tool.strict is None:
                            tool.strict = True
                        self._functions_for_model[tool.name] = tool
                        self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
                        log_debug(f"Included function {tool.name}")

                elif callable(tool):
                    # We add the tools, which are callable functions
                    try:
                        func = Function.from_callable(tool, strict=strict)
                        func._agent = self
                        if strict:
                            func.strict = True
                        self._functions_for_model[func.name] = func
                        self._tools_for_model.append({"type": "function", "function": func.to_dict()})
                        log_debug(f"Included function {func.name}")
                    except Exception as e:
                        log_warning(f"Could not add function {tool}: {e}")

            # Set tools on the model
            model.set_tools(tools=self._tools_for_model)
            # Set functions on the model
            model.set_functions(functions=self._functions_for_model)

    def get_members_system_message_content(self, indent: int = 0) -> str:
        system_message_content = ""
        for idx, member in enumerate(self.members):
            if isinstance(member, Team):
                system_message_content += f"{indent * ' '} - Team: {member.name}\n"
                if member.members is not None:
                    system_message_content += member.get_members_system_message_content(indent=indent + 2)
            else:
                system_message_content += f"{indent * ' '} - Agent {idx + 1}:\n"
                if member.name is not None:
                    system_message_content += f"{indent * ' '}   - Name: {member.name}\n"
                if member.role is not None:
                    system_message_content += f"{indent * ' '}   - Role: {member.role}\n"
                if member.tools is not None:
                    system_message_content += f"{indent * ' '}   - Available tools:\n"
                    tool_name_and_description = []

                    for _tool in member.tools:
                        if isinstance(_tool, Toolkit):
                            for _func in _tool.functions.values():
                                if _func.entrypoint:
                                    tool_name_and_description.append(
                                        (_func.name, get_entrypoint_docstring(_func.entrypoint))
                                    )
                        elif isinstance(_tool, Function) and _tool.entrypoint:
                            tool_name_and_description.append((_tool.name, get_entrypoint_docstring(_tool.entrypoint)))
                        elif callable(_tool):
                            tool_name_and_description.append((_tool.__name__, get_entrypoint_docstring(_tool)))

                    for _tool_name, _tool_description in tool_name_and_description:
                        system_message_content += f"{indent * ' '}    - {_tool_name}: {_tool_description}\n"

        return system_message_content

    def get_system_message(self) -> Optional[Message]:
        """Get the system message for the team."""
        # 1. Build and return the default system message for the Agent.
        # 1.1 Build the list of instructions for the system message
        self.model = cast(Model, self.model)
        instructions: List[str] = []
        if self.instructions is not None:
            _instructions = self.instructions
            if callable(self.instructions):
                _instructions = self.instructions(agent=self)

            if isinstance(_instructions, str):
                instructions.append(_instructions)
            elif isinstance(_instructions, list):
                instructions.extend(_instructions)

        # 1.2 Add instructions from the Model
        _model_instructions = self.model.get_instructions_for_model()
        if _model_instructions is not None:
            instructions.extend(_model_instructions)

        # 1.3 Build a list of additional information for the system message
        additional_information: List[str] = []
        # 1.3.1 Add instructions for using markdown
        if self.markdown and self.response_model is None:
            additional_information.append("Use markdown to format your answers.")
        # 1.3.2 Add the current datetime
        if self.add_datetime_to_instructions:
            from datetime import datetime

            additional_information.append(f"The current time is {datetime.now()}")

        # 2 Build the default system message for the Agent.
        system_message_content: str = ""
        if self.mode == "coordinate":
            system_message_content += "You are the leader of a team of AI Agents and possible Sub-Teams:\n"
            system_message_content += self.get_members_system_message_content()

            system_message_content += (
                "\n"
                "- You can either respond directly or transfer tasks to other Agents in your team depending on the tools available to them and their roles.\n"
                "- If you transfer a task to another Agent, make sure to include:\n"
                "  - agent_name (str): The name of the Agent to transfer the task to.\n"
                "  - task_description (str): A clear description of the task.\n"
                "  - expected_output (str): The expected output.\n"
                "- You can pass tasks to multiple members at once.\n"
                "- You must always validate the output of the other Agents before responding to the user.\n"
                "- Evaluate the response from other agents. If you feel the task has been completed, you can stop and respond to the user.\n"
                "- You can re-assign the task if you are not satisfied with the result.\n"
                "\n"
            )
        elif self.mode == "route":
            system_message_content += "You are the leader of a team of AI Agents and possible Sub-Teams:\n"
            system_message_content += self.get_members_system_message_content()
            system_message_content += (
                "- You act as a router for the user's request. You have to choose the correct agent(s) to forward the user's request to. This should be the agent that has the highest likelihood of completing the task.\n"
                "- When you forward a task to another Agent, make sure to include:\n"
                "  - agent_name (str): The name of the Agent to transfer the task to.\n"
                "  - expected_output (str): The expected output.\n"
                "- You should do your best to forward the task to a single agent.\n"
                "- If the user request requires it (e.g. if they are asking for multiple things), you can forward to multiple agents at once.\n"
                "\n"
            )

        elif self.mode == "collaborate":
            system_message_content += "You are leading a collaborative team of Agents and possible Sub-Teams:\n"
            system_message_content += self.get_members_system_message_content()
            system_message_content += (
                "- Only call run_member_agent once for all agents in the team.\n"
                "- Take all the responses from the other Agents into account and evaluate whether the task has been completed.\n"
                "- If you feel the task has been completed, you can stop and respond to the user.\n"
            )
            system_message_content += "\n"

        if self.enable_agentic_context:
            system_message_content += "You can and should update the context of the team. Use the `set_team_context` tool to update the shared team context.\n"

        if self.name is not None:
            system_message_content += f"Your name is: {self.name}.\n\n"

        if self.success_criteria:
            system_message_content += f"<success_criteria>\nThe team will be considered successful if the following criteria are met: {self.success_criteria}\nStop the team run when the criteria are met.\n</success_criteria>\n\n"

        if self.description is not None:
            system_message_content += f"<description>{self.description}</description>\n\n"

        # 3.3.5 Then add instructions for the Agent
        if len(instructions) > 0:
            system_message_content += "<instructions>"
            if len(instructions) > 1:
                for _upi in instructions:
                    system_message_content += f"\n- {_upi}"
            else:
                system_message_content += "\n" + instructions[0]
            system_message_content += "\n</instructions>\n\n"
        # 3.3.6 Add additional information
        if len(additional_information) > 0:
            system_message_content += "<additional_information>"
            for _ai in additional_information:
                system_message_content += f"\n- {_ai}"
            system_message_content += "\n</additional_information>\n\n"

        # Format the system message with the session state variables
        if self.add_state_in_messages:
            system_message_content = self._format_message_with_state_variables(system_message_content)

        system_message_from_model = self.model.get_system_message_for_model()
        if system_message_from_model is not None:
            system_message_content += system_message_from_model

        if self.expected_output is not None:
            system_message_content += f"<expected_output>\n{self.expected_output.strip()}\n</expected_output>\n\n"

        # Add the JSON output prompt if response_model is provided and structured_outputs is False
        if (
            self.response_model is not None
            and self.use_json_mode
            and self.model
            and self.model.supports_native_structured_outputs
        ):
            system_message_content += f"{self._get_json_output_prompt()}"

        return Message(role="system", content=system_message_content.strip())

    def get_run_messages(
        self,
        *,
        run_response: TeamRunResponse,
        message: Union[str, List, Dict, Message],
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs: Any,
    ) -> RunMessages:
        """This function returns a RunMessages object with the following attributes:
            - system_message: The system message for this run
            - user_message: The user message for this run
            - messages: List of messages to send to the model

        To build the RunMessages object:
        1. Add system message to run_messages
        2. Add history to run_messages
        3. Add user message to run_messages

        """
        self.memory = cast(TeamMemory, self.memory)
        # Initialize the RunMessages object
        run_messages = RunMessages()

        # 1. Add system message to run_messages
        system_message = self.get_system_message()
        if system_message is not None:
            run_messages.system_message = system_message
            run_messages.messages.append(system_message)

        # 2. Add history to run_messages
        if self.enable_team_history:
            from copy import deepcopy

            history: List[Message] = self.memory.get_messages_from_last_n_runs(
                last_n=self.num_of_interactions_from_history, skip_role="system"
            )
            if len(history) > 0:
                # Create a deep copy of the history messages to avoid modifying the original messages
                history_copy = [deepcopy(msg) for msg in history]

                # Tag each message as coming from history
                for _msg in history_copy:
                    _msg.from_history = True

                log_debug(f"Adding {len(history_copy)} messages from history")

                if run_response.extra_data is None:
                    run_response.extra_data = RunResponseExtraData(history=history_copy)
                else:
                    if run_response.extra_data.history is None:
                        run_response.extra_data.history = history_copy
                    else:
                        run_response.extra_data.history.extend(history_copy)

                # Extend the messages with the history
                run_messages.messages += history_copy

        # 3.Add user message to run_messages
        user_message = self._get_user_message(message, audio=audio, images=images, videos=videos, files=files, **kwargs)

        # Add user message to run_messages
        if user_message is not None:
            run_messages.user_message = user_message
            run_messages.messages.append(user_message)

        return run_messages

    def _get_user_message(
        self,
        message: Union[str, List, Dict, Message],
        audio: Optional[Sequence[Audio]] = None,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        files: Optional[Sequence[File]] = None,
        **kwargs,
    ):
        # Build user message if message is None, str or list
        user_message_content: str = ""
        if isinstance(message, str) or isinstance(message, list):
            if self.add_state_in_messages:
                if isinstance(message, str):
                    user_message_content = self._format_message_with_state_variables(message)
                elif isinstance(message, list):
                    user_message_content = "\n".join(
                        [self._format_message_with_state_variables(msg) for msg in message]
                    )
            else:
                if isinstance(message, str):
                    user_message_content = message
                else:
                    user_message_content = "\n".join(message)

            # Add context to user message
            if self.add_context and self.context is not None:
                user_message_content += "\n\n<context>\n"
                user_message_content += self._convert_context_to_string(self.context) + "\n"
                user_message_content += "</context>"

            return Message(
                role="user",
                content=user_message_content,
                audio=audio,
                images=images,
                videos=videos,
                files=files,
                **kwargs,
            )

        # 3.2 If message is provided as a Message, use it directly
        elif isinstance(message, Message):
            return message
        # 3.3 If message is provided as a dict, try to validate it as a Message
        elif isinstance(message, dict):
            try:
                return Message.model_validate(message)
            except Exception as e:
                log_warning(f"Failed to validate message: {e}")

    def _format_message_with_state_variables(self, message: str) -> Any:
        """Format a message with the session state variables."""
        format_variables = ChainMap(
            self.session_state or {},
            self.context or {},
            self.extra_data or {},
            {"user_id": self.user_id} if self.user_id is not None else {},
        )
        return self._formatter.format(message, **format_variables)  # type: ignore

    def _convert_context_to_string(self, context: Dict[str, Any]) -> str:
        """Convert the context dictionary to a string representation.

        Args:
            context: Dictionary containing context data

        Returns:
            String representation of the context, or empty string if conversion fails
        """

        if context is None:
            return ""

        import json

        try:
            return json.dumps(context, indent=2, default=str)
        except (TypeError, ValueError, OverflowError) as e:
            log_warning(f"Failed to convert context to JSON: {e}")
            # Attempt a fallback conversion for non-serializable objects
            sanitized_context = {}
            for key, value in context.items():
                try:
                    # Try to serialize each value individually
                    json.dumps({key: value}, default=str)
                    sanitized_context[key] = value
                except Exception as e:
                    log_error(f"Failed to serialize to JSON: {e}")
                    # If serialization fails, convert to string representation
                    sanitized_context[key] = str(value)

            try:
                return json.dumps(sanitized_context, indent=2)
            except Exception as e:
                log_error(f"Failed to convert sanitized context to JSON: {e}")
                return str(context)

    def _get_json_output_prompt(self) -> str:
        """Return the JSON output prompt for the Agent.

        This is added to the system prompt when the response_model is set and structured_outputs is False.
        """
        import json

        json_output_prompt = "Provide your output as a JSON containing the following fields:"
        if self.response_model is not None:
            if isinstance(self.response_model, str):
                json_output_prompt += "\n<json_fields>"
                json_output_prompt += f"\n{self.response_model}"
                json_output_prompt += "\n</json_fields>"
            elif isinstance(self.response_model, list):
                json_output_prompt += "\n<json_fields>"
                json_output_prompt += f"\n{json.dumps(self.response_model)}"
                json_output_prompt += "\n</json_fields>"
            elif issubclass(self.response_model, BaseModel):
                json_schema = self.response_model.model_json_schema()
                if json_schema is not None:
                    response_model_properties = {}
                    json_schema_properties = json_schema.get("properties")
                    if json_schema_properties is not None:
                        for field_name, field_properties in json_schema_properties.items():
                            formatted_field_properties = {
                                prop_name: prop_value
                                for prop_name, prop_value in field_properties.items()
                                if prop_name != "title"
                            }
                            response_model_properties[field_name] = formatted_field_properties
                    json_schema_defs = json_schema.get("$defs")
                    if json_schema_defs is not None:
                        response_model_properties["$defs"] = {}
                        for def_name, def_properties in json_schema_defs.items():
                            def_fields = def_properties.get("properties")
                            formatted_def_properties = {}
                            if def_fields is not None:
                                for field_name, field_properties in def_fields.items():
                                    formatted_field_properties = {
                                        prop_name: prop_value
                                        for prop_name, prop_value in field_properties.items()
                                        if prop_name != "title"
                                    }
                                    formatted_def_properties[field_name] = formatted_field_properties
                            if len(formatted_def_properties) > 0:
                                response_model_properties["$defs"][def_name] = formatted_def_properties

                    if len(response_model_properties) > 0:
                        json_output_prompt += "\n<json_fields>"
                        json_output_prompt += (
                            f"\n{json.dumps([key for key in response_model_properties.keys() if key != '$defs'])}"
                        )
                        json_output_prompt += "\n</json_fields>"
                        json_output_prompt += "\n\nHere are the properties for each field:"
                        json_output_prompt += "\n<json_field_properties>"
                        json_output_prompt += f"\n{json.dumps(response_model_properties, indent=2)}"
                        json_output_prompt += "\n</json_field_properties>"
            else:
                log_warning(f"Could not build json schema for {self.response_model}")
        else:
            json_output_prompt += "Provide the output as JSON."

        json_output_prompt += "\nStart your response with `{` and end it with `}`."
        json_output_prompt += "\nYour output will be passed to json.loads() to convert it to a Python object."
        json_output_prompt += "\nMake sure it only contains valid JSON."
        return json_output_prompt

    def _update_team_state(self, run_response: Union[TeamRunResponse, RunResponse]) -> None:
        """Update the team state with the run response."""
        if run_response.images is not None:
            if self.images is None:
                self.images = []
            self.images.extend(run_response.images)
        if run_response.videos is not None:
            if self.videos is None:
                self.videos = []
            self.videos.extend(run_response.videos)
        if run_response.audio is not None:
            if self.audio is None:
                self.audio = []
            self.audio.extend(run_response.audio)

    ###########################################################################
    # Built-in Tools
    ###########################################################################

    def get_team_history(self, num_chats: Optional[int] = None) -> str:
        """
        Use this function to get the team chat history.

        Args:
            num_chats: The number of chats to return.
                Each chat contains 2 messages. One from the team and one from the user.
                Default: None

        Returns:
            str: A JSON string containing a list of dictionaries representing the team chat history.

        Example:
            - To get the last chat, use num_chats=1
            - To get the last 5 chats, use num_chats=5
            - To get all chats, use num_chats=None
            - To get the first chat, use num_chats=None and take the first message
        """
        import json

        history: List[Dict[str, Any]] = []
        all_chats = self.memory.get_all_messages()  # type: ignore
        if len(all_chats) == 0:
            return ""

        chats_added = 0
        for chat in all_chats[::-1]:
            history.insert(0, chat[1].to_dict())
            history.insert(0, chat[0].to_dict())
            chats_added += 1
            if num_chats is not None and chats_added >= num_chats:
                break
        return json.dumps(history)

    def set_team_context(self, state: str) -> str:
        """
        Set the team's shared context with the given state.

        Args:
            state (str): The state to set as the team context.
        """
        self.memory.set_team_context_text(state)  # type: ignore
        return "Team context updated."

    def get_run_member_agents_function(
        self,
        stream: bool = False,
        async_mode: bool = False,
        images: Optional[List[Image]] = None,
        videos: Optional[List[Video]] = None,
        audio: Optional[List[Audio]] = None,
        files: Optional[List[File]] = None,
    ) -> Function:
        if not images:
            images = []
        if not videos:
            videos = []
        if not audio:
            audio = []
        if not files:
            files = []

        def run_member_agents(task_description: str, expected_output: Optional[str] = None) -> Iterator[str]:
            """
            Send the same task to all the member agents and return the responses.

            Args:
                task_description (str): The task description to send to the member agents.
                expected_output (str): The expected output from the member agents.

            Returns:
                str: The responses from the member agents.
            """
            # Make sure for the member agent, we are using the agent logger
            use_agent_logger()
            self.memory = cast(TeamMemory, self.memory)

            # 2. Determine team context to send
            team_context_str = None
            if self.enable_agentic_context:
                team_context_str = self.memory.get_team_context_str()

            team_member_interactions_str = None
            if self.share_member_interactions:
                team_member_interactions_str = self.memory.get_team_member_interactions_str()
                if context_images := self.memory.get_team_context_images():
                    images.extend([Image.from_artifact(img) for img in context_images])
                if context_videos := self.memory.get_team_context_videos():
                    videos.extend([Video.from_artifact(vid) for vid in context_videos])
                if context_audio := self.memory.get_team_context_audio():
                    audio.extend([Audio.from_artifact(aud) for aud in context_audio])

            # 3. Create the member agent task
            member_agent_task = "You are a member of a team of agents that collaborate to complete a task."

            if expected_output is not None:
                member_agent_task += f"\n\n<expected_output>\n{expected_output}\n</expected_output>"

            if team_context_str:
                member_agent_task += f"\n\n{team_context_str}"
            if team_member_interactions_str:
                member_agent_task += f"\n\n{team_member_interactions_str}"

            member_agent_task += f"\n\n<task>\n{task_description}\n</task>"

            for member_agent_index, member_agent in enumerate(self.members):
                if stream:
                    member_agent_run_response_stream = member_agent.run(
                        member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=True
                    )
                    for member_agent_run_response_chunk in member_agent_run_response_stream:
                        check_if_run_cancelled(member_agent_run_response_chunk)
                        yield member_agent_run_response_chunk.content or ""
                else:
                    member_agent_run_response = member_agent.run(
                        member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=False
                    )

                    check_if_run_cancelled(member_agent_run_response)

                    if member_agent_run_response.content is None:
                        yield "No response from the member agent."
                    elif isinstance(member_agent_run_response.content, str):
                        yield member_agent_run_response.content
                    elif issubclass(type(member_agent_run_response.content), BaseModel):
                        try:
                            yield member_agent_run_response.content.model_dump_json(indent=2)  # type: ignore
                        except Exception as e:
                            yield str(e)
                    else:
                        try:
                            import json

                            yield json.dumps(member_agent_run_response.content, indent=2)
                        except Exception as e:
                            yield str(e)

                # Update the memory
                member_name = member_agent.name if member_agent.name else f"agent_{member_agent_index}"
                self.memory = cast(TeamMemory, self.memory)
                self.memory.add_interaction_to_team_context(
                    member_name=member_name,
                    task=task_description,
                    run_response=member_agent.run_response,  # type: ignore
                )

                # Add the member run to the team run response
                self.run_response = cast(TeamRunResponse, self.run_response)
                self.run_response.add_member_run(member_agent.run_response)  # type: ignore

                # Update the team state
                self._update_team_state(member_agent.run_response)  # type: ignore

            # Afterward, switch back to the team logger
            use_team_logger()

        async def arun_member_agents(
            task_description: str, expected_output: Optional[str] = None
        ) -> AsyncIterator[str]:
            """
            Send the same task to all the member agents and return the responses.

            Args:
                task_description (str): The task description to send to the member agents.
                expected_output (str): The expected output from the member agents.

            Returns:
                str: The responses from the member agents.
            """
            # Make sure for the member agent, we are using the agent logger
            use_agent_logger()
            self.memory = cast(TeamMemory, self.memory)

            # 2. Determine team context to send
            team_context_str = None
            if self.enable_agentic_context:
                team_context_str = self.memory.get_team_context_str()

            team_member_interactions_str = None
            if self.share_member_interactions:
                team_member_interactions_str = self.memory.get_team_member_interactions_str()
                if context_images := self.memory.get_team_context_images():
                    images.extend([Image.from_artifact(img) for img in context_images])
                if context_videos := self.memory.get_team_context_videos():
                    videos.extend([Video.from_artifact(vid) for vid in context_videos])
                if context_audio := self.memory.get_team_context_audio():
                    audio.extend([Audio.from_artifact(aud) for aud in context_audio])

            # 3. Create the member agent task
            member_agent_task = "You are a member of a team of agents that collaborate to complete a task."

            if expected_output is not None:
                member_agent_task += f"\n\n<expected_output>\n{expected_output}\n</expected_output>"

            if team_context_str:
                member_agent_task += f"\n\n{team_context_str}"
            if team_member_interactions_str:
                member_agent_task += f"\n\n{team_member_interactions_str}"

            member_agent_task += f"\n\n<task>\n{task_description}\n</task>"

            # Create tasks for all member agents
            tasks = []
            for member_agent_index, member_agent in enumerate(self.members):
                # We cannot stream responses with async gather
                current_agent = member_agent  # Create a reference to the current agent
                current_index = member_agent_index  # Create a reference to the current index

                async def run_member_agent(agent=current_agent, idx=current_index) -> str:
                    response = await agent.arun(
                        member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=False
                    )
                    check_if_run_cancelled(response)

                    member_name = agent.name if agent.name else f"agent_{idx}"
                    self.memory = cast(TeamMemory, self.memory)
                    self.memory.add_interaction_to_team_context(
                        member_name=member_name, task=task_description, run_response=agent.run_response
                    )

                    # Add the member run to the team run response
                    self.run_response = cast(TeamRunResponse, self.run_response)
                    self.run_response.add_member_run(agent.run_response)

                    # Update the team state
                    self._update_team_state(agent.run_response)

                    if response.content is None:
                        return f"Agent {member_name}: No response from the member agent."
                    elif isinstance(response.content, str):
                        return f"Agent {member_name}: {response.content}"
                    elif issubclass(type(response.content), BaseModel):
                        try:
                            return f"Agent {member_name}: {response.content.model_dump_json(indent=2)}"  # type: ignore
                        except Exception as e:
                            return f"Agent {member_name}: Error - {str(e)}"
                    else:
                        try:
                            import json

                            return f"Agent {member_name}: {json.dumps(response.content, indent=2)}"
                        except Exception as e:
                            return f"Agent {member_name}: Error - {str(e)}"

                tasks.append(run_member_agent)

            # Need to collect and process yielded values from each task
            results = await asyncio.gather(*[task() for task in tasks])
            for result in results:
                yield result

            # Afterward, switch back to the team logger
            use_team_logger()

        if async_mode:
            run_member_agents_function = arun_member_agents  # type: ignore
        else:
            run_member_agents_function = run_member_agents  # type: ignore

        run_member_agents_func = Function.from_callable(run_member_agents_function, strict=True)

        return run_member_agents_func

    def get_transfer_task_function(
        self,
        stream: bool = False,
        async_mode: bool = False,
        images: Optional[List[Image]] = None,
        videos: Optional[List[Video]] = None,
        audio: Optional[List[Audio]] = None,
        files: Optional[List[File]] = None,
    ) -> Function:
        if not images:
            images = []
        if not videos:
            videos = []
        if not audio:
            audio = []
        if not files:
            files = []

        def transfer_task_to_member(agent_name: str, task_description: str, expected_output: str) -> Iterator[str]:
            """
            Use this function to transfer a task to the nominated agent.
            You must provide a clear and concise description of the task the agent should achieve AND the expected output.
            Args:
                agent_name (str): The name of the agent to transfer the task to.
                task_description (str): A clear and concise description of the task the agent should achieve.
                expected_output (str): The expected output from the agent.
            Returns:
                str: The result of the delegated task.
            """
            self.memory = cast(TeamMemory, self.memory)
            # 1. Find the member agent
            member_agent_tuple: Optional[Tuple[int, Union[Agent, "Team"]]] = next(
                ((i, member_agent) for i, member_agent in enumerate(self.members) if member_agent.name == agent_name),
                None,
            )
            if member_agent_tuple is not None:
                member_agent_index, member_agent = member_agent_tuple
            else:
                member_agent_index = -1
                member_agent = None
            if member_agent is None:
                raise ValueError(f"Agent with name {agent_name} not found in the team.")

            # 2. Determine team context to send
            team_context_str = None
            if self.enable_agentic_context:
                team_context_str = self.memory.get_team_context_str()

            team_member_interactions_str = None
            if self.share_member_interactions:
                team_member_interactions_str = self.memory.get_team_member_interactions_str()
                if context_images := self.memory.get_team_context_images():
                    images.extend([Image.from_artifact(img) for img in context_images])
                if context_videos := self.memory.get_team_context_videos():
                    videos.extend([Video.from_artifact(vid) for vid in context_videos])
                if context_audio := self.memory.get_team_context_audio():
                    audio.extend([Audio.from_artifact(aud) for aud in context_audio])

            # 3. Create the member agent task
            member_agent_task = f"You are a member of a team of agents. Your goal is to complete the following task:\n\n{task_description}\n\n<expected_output>\n{expected_output}\n</expected_output>"

            if team_context_str:
                member_agent_task += f"\n\n{team_context_str}"
            if team_member_interactions_str:
                member_agent_task += f"\n\n{team_member_interactions_str}"

            # Make sure for the member agent, we are using the agent logger
            use_agent_logger()

            if stream:
                member_agent_run_response_stream = member_agent.run(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=True
                )
                for member_agent_run_response_chunk in member_agent_run_response_stream:
                    check_if_run_cancelled(member_agent_run_response_chunk)
                    yield member_agent_run_response_chunk.content or ""
            else:
                member_agent_run_response = member_agent.run(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=False
                )

                check_if_run_cancelled(member_agent_run_response)

                if member_agent_run_response.content is None:
                    yield "No response from the member agent."
                elif isinstance(member_agent_run_response.content, str):
                    yield member_agent_run_response.content
                elif issubclass(type(member_agent_run_response.content), BaseModel):
                    try:
                        yield member_agent_run_response.content.model_dump_json(indent=2)
                    except Exception as e:
                        yield str(e)
                else:
                    try:
                        import json

                        yield json.dumps(member_agent_run_response.content, indent=2)
                    except Exception as e:
                        yield str(e)

            # Afterward, switch back to the team logger
            use_team_logger()

            # Update the memory
            member_name = member_agent.name if member_agent.name else f"agent_{member_agent_index}"
            self.memory = cast(TeamMemory, self.memory)
            self.memory.add_interaction_to_team_context(
                member_name=member_name,
                task=task_description,
                run_response=member_agent.run_response,  # type: ignore
            )

            # Add the member run to the team run response
            self.run_response = cast(TeamRunResponse, self.run_response)
            self.run_response.add_member_run(member_agent.run_response)  # type: ignore

            # Update the team state
            self._update_team_state(member_agent.run_response)  # type: ignore

        async def atransfer_task_to_member(
            agent_name: str, task_description: str, expected_output: str
        ) -> AsyncIterator[str]:
            """
            Use this function to transfer a task to the nominated agent.
            You must provide a clear and concise description of the task the agent should achieve AND the expected output.
            Args:
                agent_name (str): The name of the agent to transfer the task to.
                task_description (str): A clear and concise description of the task the agent should achieve.
                expected_output (str): The expected output from the agent.
            Returns:
                str: The result of the delegated task.
            """
            self.memory = cast(TeamMemory, self.memory)
            # 1. Find the member agent
            member_agent_tuple: Optional[Tuple[int, Union[Agent, "Team"]]] = next(
                ((i, member_agent) for i, member_agent in enumerate(self.members) if member_agent.name == agent_name),
                None,
            )
            if member_agent_tuple is not None:
                member_agent_index, member_agent = member_agent_tuple
            else:
                member_agent_index = -1
                member_agent = None
            if member_agent is None:
                raise ValueError(f"Agent with name {agent_name} not found in the team.")

            # 2. Determine team context to send
            team_context_str = None
            if self.enable_agentic_context:
                team_context_str = self.memory.get_team_context_str()

            team_member_interactions_str = None
            if self.share_member_interactions:
                team_member_interactions_str = self.memory.get_team_member_interactions_str()
                if context_images := self.memory.get_team_context_images():
                    images.extend([Image.from_artifact(img) for img in context_images])
                if context_videos := self.memory.get_team_context_videos():
                    videos.extend([Video.from_artifact(vid) for vid in context_videos])
                if context_audio := self.memory.get_team_context_audio():
                    audio.extend([Audio.from_artifact(aud) for aud in context_audio])

            # 3. Create the member agent task
            member_agent_task = f"You are a member of a team of agents. Your goal is to complete the following task:\n\n{task_description}\n\n<expected_output>\n{expected_output}\n</expected_output>"

            if team_context_str:
                member_agent_task += f"\n\n{team_context_str}"
            if team_member_interactions_str:
                member_agent_task += f"\n\n{team_member_interactions_str}"

            # Make sure for the member agent, we are using the agent logger
            use_agent_logger()

            if stream:
                member_agent_run_response_stream = await member_agent.arun(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=True
                )
                async for member_agent_run_response_chunk in member_agent_run_response_stream:
                    check_if_run_cancelled(member_agent_run_response_chunk)
                    yield member_agent_run_response_chunk.content or ""
            else:
                member_agent_run_response = await member_agent.arun(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=False
                )
                check_if_run_cancelled(member_agent_run_response)
                if member_agent_run_response.content is None:
                    yield "No response from the member agent."
                elif isinstance(member_agent_run_response.content, str):
                    yield member_agent_run_response.content
                elif issubclass(type(member_agent_run_response.content), BaseModel):
                    try:
                        yield member_agent_run_response.content.model_dump_json(indent=2)
                    except Exception as e:
                        yield str(e)
                else:
                    try:
                        import json

                        yield json.dumps(member_agent_run_response.content, indent=2)
                    except Exception as e:
                        yield str(e)

            # Afterward, switch back to the team logger
            use_team_logger()

            # Update the memory
            member_name = member_agent.name if member_agent.name else f"agent_{member_agent_index}"
            self.memory.add_interaction_to_team_context(
                member_name=member_name,
                task=task_description,
                run_response=member_agent.run_response,  # type: ignore
            )

            # Add the member run to the team run response
            self.run_response = cast(TeamRunResponse, self.run_response)
            self.run_response.add_member_run(member_agent.run_response)  # type: ignore

            # Update the team state
            self._update_team_state(member_agent.run_response)  # type: ignore

        if async_mode:
            transfer_function = atransfer_task_to_member  # type: ignore
        else:
            transfer_function = transfer_task_to_member  # type: ignore

        transfer_func = Function.from_callable(transfer_function, strict=True)

        return transfer_func

    def _find_member_by_name(self, agent_name: str) -> Optional[Tuple[int, Union[Agent, "Team"], Optional[str]]]:
        """
        Recursively search through team members and subteams to find an agent by name.

        Args:
            agent_name (str): Name of the agent to find

        Returns:
            Optional[Tuple[int, Union[Agent, "Team"], Optional[str]]]: Tuple containing:
                - Index of the member in its immediate parent team
                - The found agent/team
                - The name of the top-level team member (if found in a subteam), or None if direct member
        """
        # First check direct members
        for i, member in enumerate(self.members):
            if member.name == agent_name:
                return (i, member, None)

            # If this member is a team, search its members recursively
            if isinstance(member, Team):
                result = member._find_member_by_name(agent_name)
                if result is not None:
                    # Found in subteam, return with the top-level team member's name
                    return (i, result[1], member.name)

        return None

    def get_forward_task_function(
        self,
        message: Message,
        stream: bool = False,
        async_mode: bool = False,
        images: Optional[Sequence[Image]] = None,
        videos: Optional[Sequence[Video]] = None,
        audio: Optional[Sequence[Audio]] = None,
        files: Optional[Sequence[File]] = None,
    ) -> Function:
        if not images:
            images = []
        if not videos:
            videos = []
        if not audio:
            audio = []
        if not files:
            files = []

        def forward_task_to_member(agent_name: str, expected_output: Optional[str] = None) -> Iterator[str]:
            """
            Use this function to forward the request to the nominated agent.
            Args:
                agent_name (str): The name of the agent to transfer the task to.
                expected_output (str): The expected output from the agent.
            Returns:
                str: The result of the delegated task.
            """
            self.memory = cast(TeamMemory, self.memory)
            self._member_response_model = None

            # Find the member agent using the helper function
            result = self._find_member_by_name(agent_name)
            if result is None:
                raise ValueError(f"Agent with name {agent_name} not found in the team or any subteams.")

            member_agent_index, member_agent, top_level_member_name = result

            # If the member will produce structured output, we need to parse the response
            if member_agent.response_model is not None:
                self._member_response_model = member_agent.response_model

            # Make sure for the member agent, we are using the agent logger
            use_agent_logger()

            # If found in subteam, include the path in the task description
            member_agent_task = message.get_content_string()
            if top_level_member_name:
                member_agent_task += f"\n\n[Via team member: {top_level_member_name}]"

            if expected_output:
                member_agent_task += f"\n\n<expected_output>\n{expected_output}\n</expected_output>"

            # 2. Get the response from the member agent
            if stream:
                member_agent_run_response_stream = member_agent.run(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=True
                )
                for member_agent_run_response_chunk in member_agent_run_response_stream:
                    yield member_agent_run_response_chunk.content or ""
            else:
                member_agent_run_response = member_agent.run(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=False
                )

                if member_agent_run_response.content is None:
                    yield "No response from the member agent."
                elif isinstance(member_agent_run_response.content, str):
                    yield member_agent_run_response.content
                elif issubclass(type(member_agent_run_response.content), BaseModel):
                    try:
                        yield member_agent_run_response.content.model_dump_json(indent=2)
                    except Exception as e:
                        yield str(e)
                else:
                    try:
                        import json

                        yield json.dumps(member_agent_run_response.content, indent=2)
                    except Exception as e:
                        yield str(e)

            # Afterward, switch back to the team logger
            use_team_logger()

            # Update the memory
            member_name = member_agent.name if member_agent.name else f"agent_{member_agent_index}"
            self.memory = cast(TeamMemory, self.memory)
            self.memory.add_interaction_to_team_context(
                member_name=member_name,
                task=message.get_content_string(),
                run_response=member_agent.run_response,  # type: ignore
            )

            # Add the member run to the team run response
            self.run_response = cast(TeamRunResponse, self.run_response)
            self.run_response.add_member_run(member_agent.run_response)  # type: ignore

            # Update the team state
            self._update_team_state(member_agent.run_response)  # type: ignore

        async def aforward_task_to_member(agent_name: str, expected_output: Optional[str] = None) -> AsyncIterator[str]:
            """
            Use this function to forward a message to the nominated agent.
            Args:
                agent_name (str): The name of the agent to transfer the task to.
                expected_output (str): The expected output from the agent.
            Returns:
                str: The result of the delegated task.
            """
            self.memory = cast(TeamMemory, self.memory)

            self._member_response_model = None

            # Find the member agent using the helper function
            result = self._find_member_by_name(agent_name)
            if result is None:
                raise ValueError(f"Agent with name {agent_name} not found in the team or any subteams.")

            member_agent_index, member_agent, top_level_member_name = result

            # If the member will produce structured output, we need to parse the response
            if member_agent.response_model is not None:
                self._member_response_model = member_agent.response_model

            # Make sure for the member agent, we are using the agent logger
            use_agent_logger()

            # If found in subteam, include the path in the task description
            member_agent_task = message.get_content_string()
            if top_level_member_name:
                member_agent_task += f"\n\n[Via team member: {top_level_member_name}]"

            if expected_output:
                member_agent_task += f"\n\n<expected_output>\n{expected_output}\n</expected_output>"

            # 2. Get the response from the member agent
            if stream:
                member_agent_run_response_stream = await member_agent.arun(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=True
                )
                async for member_agent_run_response_chunk in member_agent_run_response_stream:
                    check_if_run_cancelled(member_agent_run_response_chunk)
                    yield member_agent_run_response_chunk.content or ""
            else:
                member_agent_run_response = await member_agent.arun(
                    member_agent_task, images=images, videos=videos, audio=audio, files=files, stream=False
                )

                if member_agent_run_response.content is None:
                    yield "No response from the member agent."
                elif isinstance(member_agent_run_response.content, str):
                    yield member_agent_run_response.content
                elif issubclass(type(member_agent_run_response.content), BaseModel):
                    try:
                        yield member_agent_run_response.content.model_dump_json(indent=2)
                    except Exception as e:
                        yield str(e)
                else:
                    try:
                        import json

                        yield json.dumps(member_agent_run_response.content, indent=2)
                    except Exception as e:
                        yield str(e)

            # Afterward, switch back to the team logger
            use_team_logger()

            # Update the memory
            member_name = member_agent.name if member_agent.name else f"agent_{member_agent_index}"
            self.memory = cast(TeamMemory, self.memory)
            self.memory.add_interaction_to_team_context(
                member_name=member_name,
                task=message.get_content_string(),
                run_response=member_agent.run_response,  # type: ignore
            )

            # Add the member run to the team run response
            self.run_response = cast(TeamRunResponse, self.run_response)
            self.run_response.add_member_run(member_agent.run_response)  # type: ignore

            # Update the team state
            self._update_team_state(member_agent.run_response)  # type: ignore

        if async_mode:
            forward_function = aforward_task_to_member  # type: ignore
        else:
            forward_function = forward_task_to_member  # type: ignore

        forward_func = Function.from_callable(forward_function, strict=True)

        forward_func.stop_after_tool_call = True
        forward_func.show_result = True

        return forward_func

    ###########################################################################
    # Storage
    ###########################################################################

    def load_user_memories(self) -> None:
        self.memory = cast(TeamMemory, self.memory)
        if self.memory and self.memory.create_user_memories:
            if self.user_id is not None:
                self.memory.user_id = self.user_id

            self.memory.load_user_memories()
            if self.user_id is not None:
                log_debug(f"Memories loaded for user: {self.user_id}")
            else:
                log_debug("Memories loaded")

    def read_from_storage(self) -> Optional[TeamSession]:
        """Load the TeamSession from storage

        Returns:
            Optional[TeamSession]: The loaded TeamSession or None if not found.
        """
        if self.storage is not None and self.session_id is not None:
            self.team_session = cast(TeamSession, self.storage.read(session_id=self.session_id))
            if self.team_session is not None:
                self.load_team_session(session=self.team_session)
            self.load_user_memories()
        return self.team_session

    def write_to_storage(self) -> Optional[TeamSession]:
        """Save the TeamSession to storage

        Returns:
            Optional[TeamSession]: The saved TeamSession or None if not saved.
        """
        if self.storage is not None:
            self.team_session = cast(TeamSession, self.storage.upsert(session=self._get_team_session()))
        return self.team_session

    def load_team_session(self, session: TeamSession):
        """Load the existing TeamSession from an TeamSession (from the database)"""
        from agno.utils.merge_dict import merge_dictionaries

        # Get the team_id, user_id and session_id from the database
        if self.team_id is None and session.team_id is not None:
            self.team_id = session.team_id
        if self.user_id is None and session.user_id is not None:
            self.user_id = session.user_id
        if self.session_id is None and session.session_id is not None:
            self.session_id = session.session_id

        # Read team_data from the database
        if session.team_data is not None:
            # Get name from database and update the team name if not set
            if self.name is None and "name" in session.team_data:
                self.name = session.team_data.get("name")

        # Read session_data from the database
        if session.session_data is not None:
            # Get the session_name from database and update the current session_name if not set
            if self.session_name is None and "session_name" in session.session_data:
                self.session_name = session.session_data.get("session_name")

            # Get the session_state from the database and update the current session_state
            if "session_state" in session.session_data:
                session_state_from_db = session.session_data.get("session_state")
                if (
                    session_state_from_db is not None
                    and isinstance(session_state_from_db, dict)
                    and len(session_state_from_db) > 0
                ):
                    # If the session_state is already set, merge the session_state from the database with the current session_state
                    if self.session_state is not None and len(self.session_state) > 0:
                        # This updates session_state_from_db
                        merge_dictionaries(session_state_from_db, self.session_state)
                    # Update the current session_state
                    self.session_state = session_state_from_db

            # Get the session_metrics from the database
            if "session_metrics" in session.session_data:
                session_metrics_from_db = session.session_data.get("session_metrics")
                if session_metrics_from_db is not None and isinstance(session_metrics_from_db, dict):
                    self.session_metrics = SessionMetrics(**session_metrics_from_db)

            # Get images, videos, and audios from the database
            if "images" in session.session_data:
                images_from_db = session.session_data.get("images")
                if images_from_db is not None and isinstance(images_from_db, list):
                    if self.images is None:
                        self.images = []
                    self.images.extend([ImageArtifact.model_validate(img) for img in images_from_db])
            if "videos" in session.session_data:
                videos_from_db = session.session_data.get("videos")
                if videos_from_db is not None and isinstance(videos_from_db, list):
                    if self.videos is None:
                        self.videos = []
                    self.videos.extend([VideoArtifact.model_validate(vid) for vid in videos_from_db])
            if "audio" in session.session_data:
                audio_from_db = session.session_data.get("audio")
                if audio_from_db is not None and isinstance(audio_from_db, list):
                    if self.audio is None:
                        self.audio = []
                    self.audio.extend([AudioArtifact.model_validate(aud) for aud in audio_from_db])

        # Read extra_data from the database
        if session.extra_data is not None:
            # If extra_data is set in the agent, update the database extra_data with the agent's extra_data
            if self.extra_data is not None:
                # Updates agent_session.extra_data in place
                merge_dictionaries(session.extra_data, self.extra_data)
            # Update the current extra_data with the extra_data from the database which is updated in place
            self.extra_data = session.extra_data

        if self.memory is None:
            self.memory = session.memory  # type: ignore

        if not isinstance(self.memory, TeamMemory):
            if isinstance(self.memory, dict):
                # Convert dict to TeamMemory
                self.memory = TeamMemory(**self.memory)
            else:
                raise TypeError(f"Expected memory to be a dict or TeamMemory, but got {type(self.memory)}")

        if session.memory is not None:
            try:
                if "runs" in session.memory:
                    try:
                        self.memory.runs = [TeamRun(**m) for m in session.memory["runs"]]
                    except Exception as e:
                        log_warning(f"Failed to load runs from memory: {e}")
                if "messages" in session.memory:
                    try:
                        self.memory.messages = [Message(**m) for m in session.memory["messages"]]
                    except Exception as e:
                        log_warning(f"Failed to load messages from memory: {e}")
                if "memories" in session.memory:
                    try:
                        self.memory.memories = [Memory(**m) for m in session.memory["memories"]]
                    except Exception as e:
                        log_warning(f"Failed to load user memories: {e}")
            except Exception as e:
                log_warning(f"Failed to load AgentMemory: {e}")
        log_debug(f"-*- TeamSession loaded: {session.session_id}")

    ###########################################################################
    # Logging
    ###########################################################################

    def _create_run_data(self) -> Dict[str, Any]:
        """Create and return the run data dictionary."""
        run_response_format = "text"
        if self.response_model is not None:
            run_response_format = "json"
        elif self.markdown:
            run_response_format = "markdown"

        functions = {}
        if self.model is not None and self.model._functions is not None:
            functions = {
                f_name: func.to_dict() for f_name, func in self.model._functions.items() if isinstance(func, Function)
            }

        run_data: Dict[str, Any] = {
            "functions": functions,
            "metrics": self.run_response.metrics,  # type: ignore
        }

        if self.monitoring:
            run_data.update(
                {
                    "run_input": self.run_input,
                    "run_response": self.run_response.to_dict(),  # type: ignore
                    "run_response_format": run_response_format,
                }
            )

        return run_data

    def _get_team_data(self) -> Dict[str, Any]:
        team_data: Dict[str, Any] = {}
        if self.name is not None:
            team_data["name"] = self.name
        if self.team_id is not None:
            team_data["team_id"] = self.team_id
        if self.model is not None:
            team_data["model"] = self.model.to_dict()
        return team_data

    def _get_session_data(self) -> Dict[str, Any]:
        session_data: Dict[str, Any] = {}
        if self.session_name is not None:
            session_data["session_name"] = self.session_name
        if self.session_state is not None and len(self.session_state) > 0:
            session_data["session_state"] = self.session_state
        if self.session_metrics is not None:
            session_data["session_metrics"] = asdict(self.session_metrics) if self.session_metrics is not None else None
        if self.images is not None:
            session_data["images"] = [img.model_dump() for img in self.images]  # type: ignore
        if self.videos is not None:
            session_data["videos"] = [vid.model_dump() for vid in self.videos]  # type: ignore
        if self.audio is not None:
            session_data["audio"] = [aud.model_dump() for aud in self.audio]  # type: ignore
        return session_data

    def _get_team_session(self) -> TeamSession:
        from time import time

        """Get an TeamSession object, which can be saved to the database"""
        return TeamSession(
            session_id=self.session_id,  # type: ignore
            team_id=self.team_id,
            user_id=self.user_id,
            memory=self.memory.to_dict() if self.memory is not None else None,
            team_data=self._get_team_data(),
            session_data=self._get_session_data(),
            extra_data=self.extra_data,
            created_at=int(time()),
        )

    def _log_team_run(self) -> None:
        if not self.telemetry and not self.monitoring:
            return

        from agno.api.team import TeamRunCreate, create_team_run

        try:
            run_data = self._create_run_data()
            team_session: TeamSession = self.team_session or self._get_team_session()

            create_team_run(
                run=TeamRunCreate(
                    run_id=self.run_id,
                    run_data=run_data,
                    session_id=team_session.session_id,
                    team_data=team_session.to_dict() if self.monitoring else team_session.telemetry_data(),
                ),
                monitor=self.monitoring,
            )
        except Exception as e:
            log_debug(f"Could not create team event: {e}")

    async def _alog_team_run(self) -> None:
        if not self.telemetry and not self.monitoring:
            return

        from agno.api.team import TeamRunCreate, acreate_team_run

        try:
            run_data = self._create_run_data()
            team_session: TeamSession = self.team_session or self._get_team_session()

            await acreate_team_run(
                run=TeamRunCreate(
                    run_id=self.run_id,
                    run_data=run_data,
                    session_id=team_session.session_id,
                    team_data=team_session.to_dict() if self.monitoring else team_session.telemetry_data(),
                ),
                monitor=self.monitoring,
            )
        except Exception as e:
            log_debug(f"Could not create team event: {e}")
