{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Termination \n",
    "\n",
    "In the previous section, we explored how to define agents, and organize them into teams that can solve tasks. However, a run can go on forever, and in many cases, we need to know _when_ to stop them. This is the role of the termination condition.\n",
    "\n",
    "AgentChat supports several termination condition by providing a base {py:class}`~autogen_agentchat.base.TerminationCondition` class and several implementations that inherit from it.\n",
    "\n",
    "A termination condition is a callable that takes a sequence of {py:class}`~autogen_agentchat.messages.AgentEvent` or {py:class}`~autogen_agentchat.messages.ChatMessage` objects **since the last time the condition was called**, and returns a {py:class}`~autogen_agentchat.messages.StopMessage` if the conversation should be terminated, or `None` otherwise.\n",
    "Once a termination condition has been reached, it must be reset by calling {py:meth}`~autogen_agentchat.base.TerminationCondition.reset` before it can be used again.\n",
    "\n",
    "Some important things to note about termination conditions: \n",
    "- They are stateful but reset automatically after each run ({py:meth}`~autogen_agentchat.base.TaskRunner.run` or {py:meth}`~autogen_agentchat.base.TaskRunner.run_stream`) is finished.\n",
    "- They can be combined using the AND and OR operators.\n",
    "\n",
    "```{note}\n",
    "For group chat teams (i.e., {py:class}`~autogen_agentchat.teams.RoundRobinGroupChat`,\n",
    "{py:class}`~autogen_agentchat.teams.SelectorGroupChat`, and {py:class}`~autogen_agentchat.teams.Swarm`),\n",
    "the termination condition is called after each agent responds.\n",
    "While a response may contain multiple inner messages, the team calls its termination condition just once for all the messages from a single response.\n",
    "So the condition is called with the \"delta sequence\" of messages since the last time it was called.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Built-In Termination Conditions: \n",
    "1. {py:class}`~autogen_agentchat.conditions.MaxMessageTermination`: Stops after a specified number of messages have been produced, including both agent and task messages.\n",
    "2. {py:class}`~autogen_agentchat.conditions.TextMentionTermination`: Stops when specific text or string is mentioned in a message (e.g., \"TERMINATE\").\n",
    "3. {py:class}`~autogen_agentchat.conditions.TokenUsageTermination`: Stops when a certain number of prompt or completion tokens are used. This requires the agents to report token usage in their messages.\n",
    "4. {py:class}`~autogen_agentchat.conditions.TimeoutTermination`: Stops after a specified duration in seconds.\n",
    "5. {py:class}`~autogen_agentchat.conditions.HandoffTermination`: Stops when a handoff to a specific target is requested. Handoff messages can be used to build patterns such as {py:class}`~autogen_agentchat.teams.Swarm`. This is useful when you want to pause the run and allow application or user to provide input when an agent hands off to them.\n",
    "6. {py:class}`~autogen_agentchat.conditions.SourceMatchTermination`: Stops after a specific agent responds.\n",
    "7. {py:class}`~autogen_agentchat.conditions.ExternalTermination`: Enables programmatic control of termination from outside the run. This is useful for UI integration (e.g., \"Stop\" buttons in chat interfaces).\n",
    "8. {py:class}`~autogen_agentchat.conditions.StopMessageTermination`: Stops when a {py:class}`~autogen_agentchat.messages.StopMessage` is produced by an agent.\n",
    "9. {py:class}`~autogen_agentchat.conditions.TextMessageTermination`: Stops when a {py:class}`~autogen_agentchat.messages.TextMessage` is produced by an agent.\n",
    "10. {py:class}`~autogen_agentchat.conditions.FunctionCallTermination`: Stops when a {py:class}`~autogen_agentchat.messages.ToolCallExecutionEvent` containing a {py:class}`~autogen_core.models.FunctionExecutionResult` with a matching name is produced by an agent."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic Usage\n",
    "\n",
    "To demonstrate the characteristics of termination conditions, we'll create a team consisting of two agents: a primary agent responsible for text generation and a critic agent that reviews and provides feedback on the generated text."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from autogen_agentchat.agents import AssistantAgent\n",
    "from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination\n",
    "from autogen_agentchat.teams import RoundRobinGroupChat\n",
    "from autogen_agentchat.ui import Console\n",
    "from autogen_ext.models.openai import OpenAIChatCompletionClient\n",
    "\n",
    "model_client = OpenAIChatCompletionClient(\n",
    "    model=\"gpt-4o\",\n",
    "    temperature=1,\n",
    "    # api_key=\"sk-...\", # Optional if you have an OPENAI_API_KEY env variable set.\n",
    ")\n",
    "\n",
    "# Create the primary agent.\n",
    "primary_agent = AssistantAgent(\n",
    "    \"primary\",\n",
    "    model_client=model_client,\n",
    "    system_message=\"You are a helpful AI assistant.\",\n",
    ")\n",
    "\n",
    "# Create the critic agent.\n",
    "critic_agent = AssistantAgent(\n",
    "    \"critic\",\n",
    "    model_client=model_client,\n",
    "    system_message=\"Provide constructive feedback for every message. Respond with 'APPROVE' to when your feedbacks are addressed.\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's explore how termination conditions automatically reset after each `run` or `run_stream` call, allowing the team to resume its conversation from where it left off."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---------- user ----------\n",
      "Write a unique, Haiku about the weather in Paris\n",
      "---------- primary ----------\n",
      "Gentle rain whispers,  \n",
      "Cobblestones glisten softly—  \n",
      "Paris dreams in gray.\n",
      "[Prompt tokens: 30, Completion tokens: 19]\n",
      "---------- critic ----------\n",
      "The Haiku captures the essence of a rainy day in Paris beautifully, and the imagery is vivid. However, it's important to ensure the use of the traditional 5-7-5 syllable structure for Haikus. Your current Haiku lines are composed of 4-7-5 syllables, which slightly deviates from the form. Consider revising the first line to fit the structure.\n",
      "\n",
      "For example:\n",
      "Soft rain whispers down,  \n",
      "Cobblestones glisten softly —  \n",
      "Paris dreams in gray.\n",
      "\n",
      "This revision maintains the essence of your original lines while adhering to the traditional Haiku structure.\n",
      "[Prompt tokens: 70, Completion tokens: 120]\n",
      "---------- Summary ----------\n",
      "Number of messages: 3\n",
      "Finish reason: Maximum number of messages 3 reached, current message count: 3\n",
      "Total prompt tokens: 100\n",
      "Total completion tokens: 139\n",
      "Duration: 3.34 seconds\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a unique, Haiku about the weather in Paris'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=30, completion_tokens=19), content='Gentle rain whispers,  \\nCobblestones glisten softly—  \\nParis dreams in gray.'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=70, completion_tokens=120), content=\"The Haiku captures the essence of a rainy day in Paris beautifully, and the imagery is vivid. However, it's important to ensure the use of the traditional 5-7-5 syllable structure for Haikus. Your current Haiku lines are composed of 4-7-5 syllables, which slightly deviates from the form. Consider revising the first line to fit the structure.\\n\\nFor example:\\nSoft rain whispers down,  \\nCobblestones glisten softly —  \\nParis dreams in gray.\\n\\nThis revision maintains the essence of your original lines while adhering to the traditional Haiku structure.\")], stop_reason='Maximum number of messages 3 reached, current message count: 3')"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max_msg_termination = MaxMessageTermination(max_messages=3)\n",
    "round_robin_team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=max_msg_termination)\n",
    "\n",
    "# Use asyncio.run(...) if you are running this script as a standalone script.\n",
    "await Console(round_robin_team.run_stream(task=\"Write a unique, Haiku about the weather in Paris\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The conversation stopped after reaching the maximum message limit. Since the primary agent didn't get to respond to the feedback, let's continue the conversation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---------- primary ----------\n",
      "Thank you for your feedback. Here is the revised Haiku:\n",
      "\n",
      "Soft rain whispers down,  \n",
      "Cobblestones glisten softly —  \n",
      "Paris dreams in gray.\n",
      "[Prompt tokens: 181, Completion tokens: 32]\n",
      "---------- critic ----------\n",
      "The revised Haiku now follows the traditional 5-7-5 syllable pattern, and it still beautifully captures the atmospheric mood of Paris in the rain. The imagery and flow are both clear and evocative. Well done on making the adjustment! \n",
      "\n",
      "APPROVE\n",
      "[Prompt tokens: 234, Completion tokens: 54]\n",
      "---------- primary ----------\n",
      "Thank you for your kind words and approval. I'm glad the revision meets your expectations and captures the essence of Paris. If you have any more requests or need further assistance, feel free to ask!\n",
      "[Prompt tokens: 279, Completion tokens: 39]\n",
      "---------- Summary ----------\n",
      "Number of messages: 3\n",
      "Finish reason: Maximum number of messages 3 reached, current message count: 3\n",
      "Total prompt tokens: 694\n",
      "Total completion tokens: 125\n",
      "Duration: 6.43 seconds\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "TaskResult(messages=[TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=181, completion_tokens=32), content='Thank you for your feedback. Here is the revised Haiku:\\n\\nSoft rain whispers down,  \\nCobblestones glisten softly —  \\nParis dreams in gray.'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=234, completion_tokens=54), content='The revised Haiku now follows the traditional 5-7-5 syllable pattern, and it still beautifully captures the atmospheric mood of Paris in the rain. The imagery and flow are both clear and evocative. Well done on making the adjustment! \\n\\nAPPROVE'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=279, completion_tokens=39), content=\"Thank you for your kind words and approval. I'm glad the revision meets your expectations and captures the essence of Paris. If you have any more requests or need further assistance, feel free to ask!\")], stop_reason='Maximum number of messages 3 reached, current message count: 3')"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Use asyncio.run(...) if you are running this script as a standalone script.\n",
    "await Console(round_robin_team.run_stream())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The team continued from where it left off, allowing the primary agent to respond to the feedback."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Combining Termination Conditions\n",
    "\n",
    "Let's show how termination conditions can be combined using the AND (`&`) and OR (`|`) operators to create more complex termination logic. For example, we'll create a team that stops either after 10 messages are generated or when the critic agent approves a message.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---------- user ----------\n",
      "Write a unique, Haiku about the weather in Paris\n",
      "---------- primary ----------\n",
      "Spring breeze gently hums,  \n",
      "Cherry blossoms in full bloom—  \n",
      "Paris wakes to life.\n",
      "[Prompt tokens: 467, Completion tokens: 19]\n",
      "---------- critic ----------\n",
      "The Haiku beautifully captures the awakening of Paris in the spring. The imagery of a gentle spring breeze and cherry blossoms in full bloom effectively conveys the rejuvenating feel of the season. The final line, \"Paris wakes to life,\" encapsulates the renewed energy and vibrancy of the city. The Haiku adheres to the 5-7-5 syllable structure and portrays a vivid seasonal transformation in a concise and poetic manner. Excellent work!\n",
      "\n",
      "APPROVE\n",
      "[Prompt tokens: 746, Completion tokens: 93]\n",
      "---------- Summary ----------\n",
      "Number of messages: 3\n",
      "Finish reason: Text 'APPROVE' mentioned\n",
      "Total prompt tokens: 1213\n",
      "Total completion tokens: 112\n",
      "Duration: 2.75 seconds\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a unique, Haiku about the weather in Paris'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=467, completion_tokens=19), content='Spring breeze gently hums,  \\nCherry blossoms in full bloom—  \\nParis wakes to life.'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=746, completion_tokens=93), content='The Haiku beautifully captures the awakening of Paris in the spring. The imagery of a gentle spring breeze and cherry blossoms in full bloom effectively conveys the rejuvenating feel of the season. The final line, \"Paris wakes to life,\" encapsulates the renewed energy and vibrancy of the city. The Haiku adheres to the 5-7-5 syllable structure and portrays a vivid seasonal transformation in a concise and poetic manner. Excellent work!\\n\\nAPPROVE')], stop_reason=\"Text 'APPROVE' mentioned\")"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max_msg_termination = MaxMessageTermination(max_messages=10)\n",
    "text_termination = TextMentionTermination(\"APPROVE\")\n",
    "combined_termination = max_msg_termination | text_termination\n",
    "\n",
    "round_robin_team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=combined_termination)\n",
    "\n",
    "# Use asyncio.run(...) if you are running this script as a standalone script.\n",
    "await Console(round_robin_team.run_stream(task=\"Write a unique, Haiku about the weather in Paris\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The conversation stopped after the critic agent approved the message, although it could have also stopped if 10 messages were generated.\n",
    "\n",
    "Alternatively, if we want to stop the run only when both conditions are met, we can use the AND (`&`) operator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "combined_termination = max_msg_termination & text_termination"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Custom Termination Condition\n",
    "\n",
    "The built-in termination conditions are sufficient for most use cases.\n",
    "However, there may be cases where you need to implement a custom termination condition that doesn't fit into the existing ones.\n",
    "You can do this by subclassing the {py:class}`~autogen_agentchat.base.TerminationCondition` class.\n",
    "\n",
    "In this example, we create a custom termination condition that stops the conversation when\n",
    "a specific function call is made."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Sequence\n",
    "\n",
    "from autogen_agentchat.base import TerminatedException, TerminationCondition\n",
    "from autogen_agentchat.messages import AgentEvent, ChatMessage, StopMessage, ToolCallExecutionEvent\n",
    "from autogen_core import Component\n",
    "from pydantic import BaseModel\n",
    "from typing_extensions import Self\n",
    "\n",
    "\n",
    "class FunctionCallTerminationConfig(BaseModel):\n",
    "    \"\"\"Configuration for the termination condition to allow for serialization\n",
    "    and deserialization of the component.\n",
    "    \"\"\"\n",
    "\n",
    "    function_name: str\n",
    "\n",
    "\n",
    "class FunctionCallTermination(TerminationCondition, Component[FunctionCallTerminationConfig]):\n",
    "    \"\"\"Terminate the conversation if a FunctionExecutionResult with a specific name is received.\"\"\"\n",
    "\n",
    "    component_config_schema = FunctionCallTerminationConfig\n",
    "    \"\"\"The schema for the component configuration.\"\"\"\n",
    "\n",
    "    def __init__(self, function_name: str) -> None:\n",
    "        self._terminated = False\n",
    "        self._function_name = function_name\n",
    "\n",
    "    @property\n",
    "    def terminated(self) -> bool:\n",
    "        return self._terminated\n",
    "\n",
    "    async def __call__(self, messages: Sequence[AgentEvent | ChatMessage]) -> StopMessage | None:\n",
    "        if self._terminated:\n",
    "            raise TerminatedException(\"Termination condition has already been reached\")\n",
    "        for message in messages:\n",
    "            if isinstance(message, ToolCallExecutionEvent):\n",
    "                for execution in message.content:\n",
    "                    if execution.name == self._function_name:\n",
    "                        self._terminated = True\n",
    "                        return StopMessage(\n",
    "                            content=f\"Function '{self._function_name}' was executed.\",\n",
    "                            source=\"FunctionCallTermination\",\n",
    "                        )\n",
    "        return None\n",
    "\n",
    "    async def reset(self) -> None:\n",
    "        self._terminated = False\n",
    "\n",
    "    def _to_config(self) -> FunctionCallTerminationConfig:\n",
    "        return FunctionCallTerminationConfig(\n",
    "            function_name=self._function_name,\n",
    "        )\n",
    "\n",
    "    @classmethod\n",
    "    def _from_config(cls, config: FunctionCallTerminationConfig) -> Self:\n",
    "        return cls(\n",
    "            function_name=config.function_name,\n",
    "        )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's use this new termination condition to stop the conversation when the critic agent approves a message\n",
    "using the `approve` function call.\n",
    "\n",
    "First we create a simple function that will be called when the critic agent approves a message."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def approve() -> None:\n",
    "    \"\"\"Approve the message when all feedbacks have been addressed.\"\"\"\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we create the agents. The critic agent is equipped with the `approve` tool."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from autogen_agentchat.agents import AssistantAgent\n",
    "from autogen_agentchat.teams import RoundRobinGroupChat\n",
    "from autogen_agentchat.ui import Console\n",
    "from autogen_ext.models.openai import OpenAIChatCompletionClient\n",
    "\n",
    "model_client = OpenAIChatCompletionClient(\n",
    "    model=\"gpt-4o\",\n",
    "    temperature=1,\n",
    "    # api_key=\"sk-...\", # Optional if you have an OPENAI_API_KEY env variable set.\n",
    ")\n",
    "\n",
    "# Create the primary agent.\n",
    "primary_agent = AssistantAgent(\n",
    "    \"primary\",\n",
    "    model_client=model_client,\n",
    "    system_message=\"You are a helpful AI assistant.\",\n",
    ")\n",
    "\n",
    "# Create the critic agent with the approve function as a tool.\n",
    "critic_agent = AssistantAgent(\n",
    "    \"critic\",\n",
    "    model_client=model_client,\n",
    "    tools=[approve],  # Register the approve function as a tool.\n",
    "    system_message=\"Provide constructive feedback. Use the approve tool to approve when all feedbacks are addressed.\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we create the termination condition and the team.\n",
    "We run the team with the poem-writing task."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---------- user ----------\n",
      "Write a unique, Haiku about the weather in Paris\n",
      "---------- primary ----------\n",
      "Raindrops gently fall,  \n",
      "Cobblestones shine in dim light—  \n",
      "Paris dreams in grey.  \n",
      "---------- critic ----------\n",
      "This Haiku beautifully captures a melancholic yet romantic image of Paris in the rain. The use of sensory imagery like \"Raindrops gently fall\" and \"Cobblestones shine\" effectively paints a vivid picture. It could be interesting to experiment with more distinct seasonal elements of Paris, such as incorporating the Seine River or iconic landmarks in the context of the weather. Overall, it successfully conveys the atmosphere of Paris in subtle, poetic imagery.\n",
      "---------- primary ----------\n",
      "Thank you for your feedback! I’m glad you enjoyed the imagery. Here’s another Haiku that incorporates iconic Parisian elements:\n",
      "\n",
      "Eiffel stands in mist,  \n",
      "Seine's ripple mirrors the sky—  \n",
      "Spring whispers anew.  \n",
      "---------- critic ----------\n",
      "[FunctionCall(id='call_QEWJZ873EG4UIEpsQHi1HsAu', arguments='{}', name='approve')]\n",
      "---------- critic ----------\n",
      "[FunctionExecutionResult(content='None', name='approve', call_id='call_QEWJZ873EG4UIEpsQHi1HsAu', is_error=False)]\n",
      "---------- critic ----------\n",
      "None\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "TaskResult(messages=[TextMessage(source='user', models_usage=None, metadata={}, content='Write a unique, Haiku about the weather in Paris', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=30, completion_tokens=23), metadata={}, content='Raindrops gently fall,  \\nCobblestones shine in dim light—  \\nParis dreams in grey.  ', type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=99, completion_tokens=90), metadata={}, content='This Haiku beautifully captures a melancholic yet romantic image of Paris in the rain. The use of sensory imagery like \"Raindrops gently fall\" and \"Cobblestones shine\" effectively paints a vivid picture. It could be interesting to experiment with more distinct seasonal elements of Paris, such as incorporating the Seine River or iconic landmarks in the context of the weather. Overall, it successfully conveys the atmosphere of Paris in subtle, poetic imagery.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=152, completion_tokens=48), metadata={}, content=\"Thank you for your feedback! I’m glad you enjoyed the imagery. Here’s another Haiku that incorporates iconic Parisian elements:\\n\\nEiffel stands in mist,  \\nSeine's ripple mirrors the sky—  \\nSpring whispers anew.  \", type='TextMessage'), ToolCallRequestEvent(source='critic', models_usage=RequestUsage(prompt_tokens=246, completion_tokens=11), metadata={}, content=[FunctionCall(id='call_QEWJZ873EG4UIEpsQHi1HsAu', arguments='{}', name='approve')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='critic', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='None', name='approve', call_id='call_QEWJZ873EG4UIEpsQHi1HsAu', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='critic', models_usage=None, metadata={}, content='None', type='ToolCallSummaryMessage')], stop_reason=\"Function 'approve' was executed.\")"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function_call_termination = FunctionCallTermination(function_name=\"approve\")\n",
    "round_robin_team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=function_call_termination)\n",
    "\n",
    "# Use asyncio.run(...) if you are running this script as a standalone script.\n",
    "await Console(round_robin_team.run_stream(task=\"Write a unique, Haiku about the weather in Paris\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can see that the conversation stopped when the critic agent approved the message using the `approve` function call."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}