diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb index 928100c05..a1471bc76 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb @@ -298,7 +298,127 @@ "it will return the tool's output as a string in {py:class}`~autogen_agentchat.messages.ToolCallSummaryMessage` in its response.\n", "If your tool does not return a well-formed string in natural language, you\n", "can add a reflection step to have the model summarize the tool's output,\n", - "by setting the `reflect_on_tool_use=True` parameter in the {py:class}`~autogen_agentchat.agents.AssistantAgent` constructor." + "by setting the `reflect_on_tool_use=True` parameter in the {py:class}`~autogen_agentchat.agents.AssistantAgent` constructor.\n", + "\n", + "### Built-in Tools\n", + "\n", + "AutoGen Extension provides a set of built-in tools that can be used with the Assistant Agent.\n", + "Head over to the [API documentation](../../../reference/index.md) for all the available tools\n", + "under the `autogen_ext.tools` namespace. For example, you can find the following tools:\n", + "\n", + "- {py:mod}`~autogen_ext.tools.graphrag`: Tools for using GraphRAG index.\n", + "- {py:mod}`~autogen_ext.tools.http`: Tools for making HTTP requests.\n", + "- {py:mod}`~autogen_ext.tools.langchain`: Adaptor for using LangChain tools.\n", + "- {py:mod}`~autogen_ext.tools.mcp`: Tools for using Model Chat Protocol (MCP) servers." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Function Tool\n", + "\n", + "The {py:class}`~autogen_agentchat.agents.AssistantAgent` automatically\n", + "converts a Python function into a {py:class}`~autogen_core.tools.FunctionTool`\n", + "which can be used as a tool by the agent and automatically generates the tool schema\n", + "from the function signature and docstring.\n", + "\n", + "The `web_search_func` tool is an example of a function tool.\n", + "The schema is automatically generated." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'web_search_func',\n", + " 'description': 'Find information on the web',\n", + " 'parameters': {'type': 'object',\n", + " 'properties': {'query': {'description': 'query',\n", + " 'title': 'Query',\n", + " 'type': 'string'}},\n", + " 'required': ['query'],\n", + " 'additionalProperties': False},\n", + " 'strict': False}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from autogen_core.tools import FunctionTool\n", + "\n", + "\n", + "# Define a tool using a Python function.\n", + "async def web_search_func(query: str) -> str:\n", + " \"\"\"Find information on the web\"\"\"\n", + " return \"AutoGen is a programming framework for building multi-agent applications.\"\n", + "\n", + "\n", + "# This step is automatically performed inside the AssistantAgent if the tool is a Python function.\n", + "web_search_function_tool = FunctionTool(web_search_func, description=\"Find information on the web\")\n", + "# The schema is provided to the model during AssistantAgent's on_messages call.\n", + "web_search_function_tool.schema" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model Context Protocol Tools\n", + "\n", + "The {py:class}`~autogen_agentchat.agents.AssistantAgent` can also use tools that are\n", + "served from a Model Context Protocol (MCP) server\n", + "using {py:func}`~autogen_ext.tools.mcp.mcp_server_tools`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Seattle, located in Washington state, is the most populous city in the state and a major city in the Pacific Northwest region of the United States. It's known for its vibrant cultural scene, significant economic presence, and rich history. Here are some key points about Seattle from the Wikipedia page:\n", + "\n", + "1. **History and Geography**: Seattle is situated between Puget Sound and Lake Washington, with the Cascade Range to the east and the Olympic Mountains to the west. Its history is deeply rooted in Native American heritage and its development was accelerated with the arrival of settlers in the 19th century. The city was officially incorporated in 1869.\n", + "\n", + "2. **Economy**: Seattle is a major economic hub with a diverse economy anchored by sectors like aerospace, technology, and retail. It's home to influential companies such as Amazon and Starbucks, and has a significant impact on the tech industry due to companies like Microsoft and other technology enterprises in the surrounding area.\n", + "\n", + "3. **Cultural Significance**: Known for its music scene, Seattle was the birthplace of grunge music in the early 1990s. It also boasts significant attractions like the Space Needle, Pike Place Market, and the Seattle Art Museum. \n", + "\n", + "4. **Education and Innovation**: The city hosts important educational institutions, with the University of Washington being a leading research university. Seattle is recognized for fostering innovation and is a leader in environmental sustainability efforts.\n", + "\n", + "5. **Demographics and Diversity**: Seattle is noted for its diverse population, reflected in its rich cultural tapestry. It has seen a significant increase in population, leading to urban development and changes in its social landscape.\n", + "\n", + "These points highlight Seattle as a dynamic city with a significant cultural, economic, and educational influence within the United States and beyond.\n" + ] + } + ], + "source": [ + "from autogen_agentchat.agents import AssistantAgent\n", + "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", + "from autogen_ext.tools.mcp import StdioServerParams, mcp_server_tools\n", + "\n", + "# Get the fetch tool from mcp-server-fetch.\n", + "fetch_mcp_server = StdioServerParams(command=\"uvx\", args=[\"mcp-server-fetch\"])\n", + "tools = await mcp_server_tools(fetch_mcp_server)\n", + "\n", + "# Create an agent that can use the fetch tool.\n", + "model_client = OpenAIChatCompletionClient(model=\"gpt-4o\")\n", + "agent = AssistantAgent(name=\"fetcher\", model_client=model_client, tools=tools, reflect_on_tool_use=True) # type: ignore\n", + "\n", + "# Let the agent fetch the content of a URL and summarize it.\n", + "result = await agent.run(task=\"Summarize the content of https://en.wikipedia.org/wiki/Seattle\")\n", + "print(result.messages[-1].content)" ] }, { @@ -307,7 +427,7 @@ "source": [ "### Langchain Tools\n", "\n", - "In addition to custom tools, you can also use tools from the Langchain library\n", + "You can also use tools from the Langchain library\n", "by wrapping them in {py:class}`~autogen_ext.tools.langchain.LangChainToolAdapter`." ] }, @@ -400,6 +520,20 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running an Agent in a Loop\n", + "\n", + "The {py:class}`~autogen_agentchat.agents.AssistantAgent` executes one\n", + "step at a time: one model call, followed by one tool call (or parallel tool calls), and then\n", + "an optional reflection.\n", + "\n", + "To run it in a loop, for example, running it until it stops producing\n", + "tool calls, please refer to [Single-Agent Team](./teams.ipynb#single-agent-team)." + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb index 0ddaf0539..ebdf72c38 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb @@ -1,599 +1,708 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Teams\n", - "\n", - "In this section you'll learn how to create a _multi-agent team_ (or simply team) using AutoGen. A team is a group of agents that work together to achieve a common goal.\n", - "\n", - "We'll first show you how to create and run a team. We'll then explain how to observe the team's behavior, which is crucial for debugging and understanding the team's performance, and common operations to control the team's behavior.\n", - "\n", - "```{note}\n", - "When should you use a team?\n", - "Teams are for complex tasks that require collaboration and diverse expertise.\n", - "However, they also demand more scaffolding to steer compared to single agents.\n", - "While AutoGen simplifies the process of working with teams, start with\n", - "a single agent for simpler tasks, and transition to a multi-agent team when a single agent proves inadequate.\n", - "Ensure that you have optimized your single agent with the appropriate tools\n", - "and instructions before moving to a team-based approach.\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating a Team\n", - "\n", - "{py:class}`~autogen_agentchat.teams.RoundRobinGroupChat` is a simple yet effective team configuration where all agents share the same context and take turns responding in a round-robin fashion. Each agent, during its turn, broadcasts its response to all other agents, ensuring that the entire team maintains a consistent context.\n", - "\n", - "We will begin by creating a team with two {py:class}`~autogen_agentchat.agents.AssistantAgent` and a {py:class}`~autogen_agentchat.conditions.TextMentionTermination` condition that stops the team when a specific word is detected in the agent's response.\n", - "\n", - "The two-agent team implements the _reflection_ pattern, a multi-agent design pattern where a critic agent evaluates the responses of a primary agent. Learn more about the reflection pattern using the [Core API](../../core-user-guide/design-patterns/reflection.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "import asyncio\n", - "\n", - "from autogen_agentchat.agents import AssistantAgent\n", - "from autogen_agentchat.base import TaskResult\n", - "from autogen_agentchat.conditions import ExternalTermination, TextMentionTermination\n", - "from autogen_agentchat.teams import RoundRobinGroupChat\n", - "from autogen_agentchat.ui import Console\n", - "from autogen_core import CancellationToken\n", - "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", - "\n", - "# Create an OpenAI model client.\n", - "model_client = OpenAIChatCompletionClient(\n", - " model=\"gpt-4o-2024-08-06\",\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. Respond with 'APPROVE' to when your feedbacks are addressed.\",\n", - ")\n", - "\n", - "# Define a termination condition that stops the task if the critic approves.\n", - "text_termination = TextMentionTermination(\"APPROVE\")\n", - "\n", - "# Create a team with the primary and critic agents.\n", - "team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=text_termination)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Running a Team\n", - "\n", - "Let's call the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run` method\n", - "to start the team with a task." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a short poem about the fall season.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=28, completion_tokens=109), content=\"Leaves of amber, gold, and rust, \\nDance upon the gentle gust. \\nCrisp air whispers tales of old, \\nAs daylight wanes, the night grows bold. \\n\\nPumpkin patch and apple treats, \\nLaughter in the street repeats. \\nSweaters warm and fires aglow, \\nIt's time for nature's vibrant show. \\n\\nThe harvest moon ascends the sky, \\nWhile geese in formation start to fly. \\nAutumn speaks in colors bright, \\nA fleeting grace, a pure delight. \", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=154, completion_tokens=200), content='Your poem beautifully captures the essence of the fall season with vivid imagery and a rhythmic flow. The use of descriptive language like \"amber, gold, and rust\" effectively paints a visual picture of the changing leaves. Phrases such as \"crisp air whispers tales of old\" and \"daylight wanes, the night grows bold\" add a poetic touch by incorporating seasonal characteristics.\\n\\nHowever, you might consider exploring other sensory details to deepen the reader\\'s immersion. For example, mentioning the sound of crunching leaves underfoot or the scent of cinnamon and spices in the air could enhance the sensory experience.\\n\\nAdditionally, while the mention of \"pumpkin patch and apple treats\" is evocative of fall, expanding on these elements or including more personal experiences or emotions associated with the season might make the poem more relatable and engaging.\\n\\nOverall, you\\'ve crafted a lovely poem that celebrates the beauty and traditions of autumn with grace and warmth. A few tweaks to include multisensory details could elevate it even further.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=347, completion_tokens=178), content=\"Thank you for the thoughtful feedback. Here's a revised version of the poem with additional sensory details:\\n\\nLeaves of amber, gold, and rust, \\nDance upon the gentle gust. \\nCrisp air whispers tales of old, \\nAs daylight wanes, the night grows bold. \\n\\nCrunch beneath the wandering feet, \\nA melody of autumn's beat. \\nCinnamon and spices blend, \\nIn every breeze, nostalgia sends. \\n\\nPumpkin patch and apple treats, \\nLaughter in the street repeats. \\nSweaters warm and fires aglow, \\nIt's time for nature's vibrant show. \\n\\nThe harvest moon ascends the sky, \\nWhile geese in formation start to fly. \\nAutumn speaks in colors bright, \\nA fleeting grace, a pure delight. \\n\\nI hope this version resonates even more with the spirit of fall. Thank you again for your suggestions!\", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=542, completion_tokens=3), content='APPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")\n" - ] - } - ], - "source": [ - "# Use `asyncio.run(...)` when running in a script.\n", - "result = await team.run(task=\"Write a short poem about the fall season.\")\n", - "print(result)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The team runs the agents until the termination condition was met.\n", - "In this case, the team ran agents following a round-robin order until the the\n", - "termination condition was met when the word \"APPROVE\" was detected in the\n", - "agent's response.\n", - "When the team stops, it returns a {py:class}`~autogen_agentchat.base.TaskResult` object with all the messages produced by the agents in the team." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Observing a Team\n", - "\n", - "Similar to the agent's {py:meth}`~autogen_agentchat.agents.BaseChatAgent.on_messages_stream` method, you can stream the team's messages while it is running by calling the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream` method. This method returns a generator that yields messages produced by the agents in the team as they are generated, with the final item being the {py:class}`~autogen_agentchat.base.TaskResult` object." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "source='user' models_usage=None content='Write a short poem about the fall season.' type='TextMessage'\n", - "source='primary' models_usage=RequestUsage(prompt_tokens=28, completion_tokens=105) content=\"Leaves descend in golden dance, \\nWhispering secrets as they fall, \\nCrisp air brings a gentle trance, \\nHeralding Autumn's call. \\n\\nPumpkins glow with orange light, \\nFields wear a cloak of amber hue, \\nDays retreat to longer night, \\nSkies shift to deeper blue. \\n\\nWinds carry scents of earth and pine, \\nSweaters wrap us, warm and tight, \\nNature's canvas, bold design, \\nIn Fall's embrace, we find delight. \" type='TextMessage'\n", - "source='critic' models_usage=RequestUsage(prompt_tokens=150, completion_tokens=226) content='Your poem beautifully captures the essence of fall with vivid imagery and a soothing rhythm. The imagery of leaves descending, pumpkins glowing, and fields cloaked in amber hues effectively paints a picture of the autumn season. The use of contrasting elements like \"Days retreat to longer night\" and \"Sweaters wrap us, warm and tight\" provides a nice balance between the cold and warmth associated with the season. Additionally, the personification of autumn through phrases like \"Autumn\\'s call\" and \"Nature\\'s canvas, bold design\" adds depth to the depiction of fall.\\n\\nTo enhance the poem further, you might consider focusing on the soundscape of fall, such as the rustling of leaves or the distant call of migrating birds, to engage readers\\' auditory senses. Also, varying the line lengths slightly could add a dynamic flow to the reading experience.\\n\\nOverall, your poem is engaging and effectively encapsulates the beauty and transition of fall. With a few adjustments to explore other sensory details, it could become even more immersive. \\n\\nIf you incorporate some of these suggestions or find another way to expand the sensory experience, please share your update!' type='TextMessage'\n", - "source='primary' models_usage=RequestUsage(prompt_tokens=369, completion_tokens=143) content=\"Thank you for the thoughtful critique and suggestions. Here's a revised version of the poem with added attention to auditory senses and varied line lengths:\\n\\nLeaves descend in golden dance, \\nWhisper secrets in their fall, \\nBreezes hum a gentle trance, \\nHeralding Autumn's call. \\n\\nPumpkins glow with orange light, \\nAmber fields beneath wide skies, \\nDays retreat to longer night, \\nChill winds and distant cries. \\n\\nRustling whispers of the trees, \\nSweaters wrap us, snug and tight, \\nNature's canvas, bold and free, \\nIn Fall's embrace, pure delight. \\n\\nI appreciate your feedback and hope this version better captures the sensory richness of the season!\" type='TextMessage'\n", - "source='critic' models_usage=RequestUsage(prompt_tokens=529, completion_tokens=160) content='Your revised poem is a beautiful enhancement of the original. By incorporating auditory elements such as \"Breezes hum\" and \"Rustling whispers of the trees,\" you\\'ve added an engaging soundscape that draws the reader deeper into the experience of fall. The varied line lengths work well to create a more dynamic rhythm throughout the poem, adding interest and variety to each stanza.\\n\\nThe succinct, yet vivid, lines of \"Chill winds and distant cries\" wonderfully evoke the atmosphere of the season, adding a touch of mystery and depth. The final stanza wraps up the poem nicely, celebrating the complete sensory embrace of fall with lines like \"Nature\\'s canvas, bold and free.\"\\n\\nYou\\'ve successfully infused more sensory richness into the poem, enhancing its overall emotional and atmospheric impact. Great job on the revisions!\\n\\nAPPROVE' type='TextMessage'\n", - "Stop Reason: Text 'APPROVE' mentioned\n" - ] - } - ], - "source": [ - "# When running inside a script, use a async main function and call it from `asyncio.run(...)`.\n", - "await team.reset() # Reset the team for a new task.\n", - "async for message in team.run_stream(task=\"Write a short poem about the fall season.\"): # type: ignore\n", - " if isinstance(message, TaskResult):\n", - " print(\"Stop Reason:\", message.stop_reason)\n", - " else:\n", - " print(message)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As demonstrated in the example above, you can determine the reason why the team stopped by checking the {py:attr}`~autogen_agentchat.base.TaskResult.stop_reason` attribute.\n", - "\n", - "The {py:meth}`~autogen_agentchat.ui.Console` method provides a convenient way to print messages to the console with proper formatting.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---------- user ----------\n", - "Write a short poem about the fall season.\n", - "---------- primary ----------\n", - "Golden leaves in crisp air dance, \n", - "Whispering tales as they prance. \n", - "Amber hues paint the ground, \n", - "Nature's symphony all around. \n", - "\n", - "Sweaters hug with tender grace, \n", - "While pumpkins smile, a warm embrace. \n", - "Chill winds hum through towering trees, \n", - "A vibrant tapestry in the breeze. \n", - "\n", - "Harvest moons in twilight glow, \n", - "Casting magic on fields below. \n", - "Fall's embrace, a gentle call, \n", - "To savor beauty before snowfalls. \n", - "[Prompt tokens: 28, Completion tokens: 99]\n", - "---------- critic ----------\n", - "Your poem beautifully captures the essence of the fall season, creating a vivid and cozy atmosphere. The imagery of golden leaves and amber hues paints a picturesque scene that many can easily relate to. I particularly appreciate the personification of pumpkins and the gentle embrace of sweaters, which adds warmth to your verses. \n", - "\n", - "To enhance the poem further, you might consider adding more sensory details to make the reader feel even more immersed in the experience. For example, including specific sounds, scents, or textures could deepen the connection to autumn's ambiance. Additionally, you could explore the emotional transitions as the season prepares for winter to provide a reflective element to the piece.\n", - "\n", - "Overall, it's a lovely and evocative depiction of fall, evoking feelings of comfort and appreciation for nature's changing beauty. Great work!\n", - "[Prompt tokens: 144, Completion tokens: 157]\n", - "---------- primary ----------\n", - "Thank you for your thoughtful feedback! I'm glad you enjoyed the imagery and warmth in the poem. To enhance the sensory experience and emotional depth, here's a revised version incorporating your suggestions:\n", - "\n", - "---\n", - "\n", - "Golden leaves in crisp air dance, \n", - "Whispering tales as they prance. \n", - "Amber hues paint the crunchy ground, \n", - "Nature's symphony all around. \n", - "\n", - "Sweaters hug with tender grace, \n", - "While pumpkins grin, a warm embrace. \n", - "Chill winds hum through towering trees, \n", - "Crackling fires warm the breeze. \n", - "\n", - "Apples in the orchard's glow, \n", - "Sweet cider scents that overflow. \n", - "Crunch of paths beneath our feet, \n", - "Cinnamon spice and toasty heat. \n", - "\n", - "Harvest moons in twilight's glow, \n", - "Casting magic on fields below. \n", - "Fall's embrace, a gentle call, \n", - "Reflects on life's inevitable thaw. \n", - "\n", - "--- \n", - "\n", - "I hope this version enhances the sensory and emotional elements of the season. Thank you again for your insights!\n", - "[Prompt tokens: 294, Completion tokens: 195]\n", - "---------- critic ----------\n", - "APPROVE\n", - "[Prompt tokens: 506, Completion tokens: 4]\n", - "---------- Summary ----------\n", - "Number of messages: 5\n", - "Finish reason: Text 'APPROVE' mentioned\n", - "Total prompt tokens: 972\n", - "Total completion tokens: 455\n", - "Duration: 11.78 seconds\n" - ] - }, - { - "data": { - "text/plain": [ - "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a short poem about the fall season.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=28, completion_tokens=99), content=\"Golden leaves in crisp air dance, \\nWhispering tales as they prance. \\nAmber hues paint the ground, \\nNature's symphony all around. \\n\\nSweaters hug with tender grace, \\nWhile pumpkins smile, a warm embrace. \\nChill winds hum through towering trees, \\nA vibrant tapestry in the breeze. \\n\\nHarvest moons in twilight glow, \\nCasting magic on fields below. \\nFall's embrace, a gentle call, \\nTo savor beauty before snowfalls. \", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=144, completion_tokens=157), content=\"Your poem beautifully captures the essence of the fall season, creating a vivid and cozy atmosphere. The imagery of golden leaves and amber hues paints a picturesque scene that many can easily relate to. I particularly appreciate the personification of pumpkins and the gentle embrace of sweaters, which adds warmth to your verses. \\n\\nTo enhance the poem further, you might consider adding more sensory details to make the reader feel even more immersed in the experience. For example, including specific sounds, scents, or textures could deepen the connection to autumn's ambiance. Additionally, you could explore the emotional transitions as the season prepares for winter to provide a reflective element to the piece.\\n\\nOverall, it's a lovely and evocative depiction of fall, evoking feelings of comfort and appreciation for nature's changing beauty. Great work!\", type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=294, completion_tokens=195), content=\"Thank you for your thoughtful feedback! I'm glad you enjoyed the imagery and warmth in the poem. To enhance the sensory experience and emotional depth, here's a revised version incorporating your suggestions:\\n\\n---\\n\\nGolden leaves in crisp air dance, \\nWhispering tales as they prance. \\nAmber hues paint the crunchy ground, \\nNature's symphony all around. \\n\\nSweaters hug with tender grace, \\nWhile pumpkins grin, a warm embrace. \\nChill winds hum through towering trees, \\nCrackling fires warm the breeze. \\n\\nApples in the orchard's glow, \\nSweet cider scents that overflow. \\nCrunch of paths beneath our feet, \\nCinnamon spice and toasty heat. \\n\\nHarvest moons in twilight's glow, \\nCasting magic on fields below. \\nFall's embrace, a gentle call, \\nReflects on life's inevitable thaw. \\n\\n--- \\n\\nI hope this version enhances the sensory and emotional elements of the season. Thank you again for your insights!\", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=506, completion_tokens=4), content='APPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "await team.reset() # Reset the team for a new task.\n", - "await Console(team.run_stream(task=\"Write a short poem about the fall season.\")) # Stream the messages to the console." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resetting a Team\n", - "\n", - "You can reset the team by calling the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.reset` method. This method will clear the team's state, including all agents.\n", - "It will call the each agent's {py:meth}`~autogen_agentchat.base.ChatAgent.on_reset` method to clear the agent's state." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "await team.reset() # Reset the team for the next run." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It is usually a good idea to reset the team if the next task is not related to the previous task.\n", - "However, if the next task is related to the previous task, you don't need to reset and you can instead\n", - "resume the team." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Stopping a Team\n", - "\n", - "Apart from automatic termination conditions such as {py:class}`~autogen_agentchat.conditions.TextMentionTermination`\n", - "that stops the team based on the internal state of the team, you can also stop the team\n", - "from outside by using the {py:class}`~autogen_agentchat.conditions.ExternalTermination`.\n", - "\n", - "Calling {py:meth}`~autogen_agentchat.conditions.ExternalTermination.set` \n", - "on {py:class}`~autogen_agentchat.conditions.ExternalTermination` will stop\n", - "the team when the current agent's turn is over.\n", - "Thus, the team may not stop immediately.\n", - "This allows the current agent to finish its turn and broadcast the final message to the team\n", - "before the team stops, keeping the team's state consistent." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---------- user ----------\n", - "Write a short poem about the fall season.\n", - "---------- primary ----------\n", - "Leaves of amber, gold, and red, \n", - "Gently drifting from trees overhead. \n", - "Whispers of wind through the crisp, cool air, \n", - "Nature's canvas painted with care. \n", - "\n", - "Harvest moons and evenings that chill, \n", - "Fields of plenty on every hill. \n", - "Sweaters wrapped tight as twilight nears, \n", - "Fall's charming embrace, as warm as it appears. \n", - "\n", - "Pumpkins aglow with autumn's light, \n", - "Harvest feasts and stars so bright. \n", - "In every leaf and breeze that calls, \n", - "We find the magic of glorious fall. \n", - "[Prompt tokens: 28, Completion tokens: 114]\n", - "---------- Summary ----------\n", - "Number of messages: 2\n", - "Finish reason: External termination requested\n", - "Total prompt tokens: 28\n", - "Total completion tokens: 114\n", - "Duration: 1.71 seconds\n" - ] - }, - { - "data": { - "text/plain": [ - "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a short poem about the fall season.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=28, completion_tokens=114), content=\"Leaves of amber, gold, and red, \\nGently drifting from trees overhead. \\nWhispers of wind through the crisp, cool air, \\nNature's canvas painted with care. \\n\\nHarvest moons and evenings that chill, \\nFields of plenty on every hill. \\nSweaters wrapped tight as twilight nears, \\nFall's charming embrace, as warm as it appears. \\n\\nPumpkins aglow with autumn's light, \\nHarvest feasts and stars so bright. \\nIn every leaf and breeze that calls, \\nWe find the magic of glorious fall. \", type='TextMessage')], stop_reason='External termination requested')" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create a new team with an external termination condition.\n", - "external_termination = ExternalTermination()\n", - "team = RoundRobinGroupChat(\n", - " [primary_agent, critic_agent],\n", - " termination_condition=external_termination | text_termination, # Use the bitwise OR operator to combine conditions.\n", - ")\n", - "\n", - "# Run the team in a background task.\n", - "run = asyncio.create_task(Console(team.run_stream(task=\"Write a short poem about the fall season.\")))\n", - "\n", - "# Wait for some time.\n", - "await asyncio.sleep(0.1)\n", - "\n", - "# Stop the team.\n", - "external_termination.set()\n", - "\n", - "# Wait for the team to finish.\n", - "await run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "From the ouput above, you can see the team stopped because the external termination condition was met,\n", - "but the speaking agent was able to finish its turn before the team stopped." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resuming a Team\n", - "\n", - "Teams are stateful and maintains the conversation history and context\n", - "after each run, unless you reset the team.\n", - "\n", - "You can resume a team to continue from where it left off by calling the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run` or {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream` method again\n", - "without a new task.\n", - "{py:class}`~autogen_agentchat.teams.RoundRobinGroupChat` will continue from the next agent in the round-robin order." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---------- critic ----------\n", - "This poem beautifully captures the essence of the fall season with vivid imagery and a soothing rhythm. The descriptions of the changing leaves, cool air, and various autumn traditions make it easy for readers to envision and feel the charm of fall. Here are a few suggestions to enhance its impact:\n", - "\n", - "1. **Structure Variation**: Consider breaking some lines with a hyphen or ellipsis for dramatic effect or emphasis. For instance, “Sweaters wrapped tight as twilight nears— / Fall’s charming embrace, as warm as it appears.\"\n", - "\n", - "2. **Sensory Details**: While the poem already evokes visual and tactile senses, incorporating other senses such as sound or smell could deepen the immersion. For example, include the scent of wood smoke or the crunch of leaves underfoot.\n", - "\n", - "3. **Metaphorical Language**: Adding metaphors or similes can further enrich the imagery. For example, you might compare the leaves falling to a golden rain or the chill in the air to a gentle whisper.\n", - "\n", - "Overall, it’s a lovely depiction of fall. These suggestions are minor tweaks that might elevate the reader's experience even further. Nice work!\n", - "\n", - "Let me know if these feedbacks are addressed.\n", - "[Prompt tokens: 159, Completion tokens: 237]\n", - "---------- primary ----------\n", - "Thank you for the thoughtful feedback! Here’s a revised version, incorporating your suggestions: \n", - "\n", - "Leaves of amber, gold—drifting like dreams, \n", - "A golden rain from trees’ canopies. \n", - "Whispers of wind—a gentle breath, \n", - "Nature’s scented tapestry embracing earth. \n", - "\n", - "Harvest moons rise as evenings chill, \n", - "Fields of plenty paint every hill. \n", - "Sweaters wrapped tight as twilight nears— \n", - "Fall’s embrace, warm as whispered years. \n", - "\n", - "Pumpkins aglow with autumn’s light, \n", - "Crackling leaves underfoot in flight. \n", - "In every leaf and breeze that calls, \n", - "We find the magic of glorious fall. \n", - "\n", - "I hope these changes enhance the imagery and sensory experience. Thank you again for your feedback!\n", - "[Prompt tokens: 389, Completion tokens: 150]\n", - "---------- critic ----------\n", - "Your revisions have made the poem even more evocative and immersive. The use of sensory details, such as \"whispers of wind\" and \"crackling leaves,\" beautifully enriches the poem, engaging multiple senses. The metaphorical language, like \"a golden rain from trees’ canopies\" and \"Fall’s embrace, warm as whispered years,\" adds depth and enhances the emotional warmth of the poem. The structural variation with the inclusion of dashes effectively adds emphasis and flow. \n", - "\n", - "Overall, these changes bring greater vibrancy and life to the poem, allowing readers to truly experience the wonders of fall. Excellent work on the revisions!\n", - "\n", - "APPROVE\n", - "[Prompt tokens: 556, Completion tokens: 132]\n", - "---------- Summary ----------\n", - "Number of messages: 3\n", - "Finish reason: Text 'APPROVE' mentioned\n", - "Total prompt tokens: 1104\n", - "Total completion tokens: 519\n", - "Duration: 9.79 seconds\n" - ] - }, - { - "data": { - "text/plain": [ - "TaskResult(messages=[TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=159, completion_tokens=237), content='This poem beautifully captures the essence of the fall season with vivid imagery and a soothing rhythm. The descriptions of the changing leaves, cool air, and various autumn traditions make it easy for readers to envision and feel the charm of fall. Here are a few suggestions to enhance its impact:\\n\\n1. **Structure Variation**: Consider breaking some lines with a hyphen or ellipsis for dramatic effect or emphasis. For instance, “Sweaters wrapped tight as twilight nears— / Fall’s charming embrace, as warm as it appears.\"\\n\\n2. **Sensory Details**: While the poem already evokes visual and tactile senses, incorporating other senses such as sound or smell could deepen the immersion. For example, include the scent of wood smoke or the crunch of leaves underfoot.\\n\\n3. **Metaphorical Language**: Adding metaphors or similes can further enrich the imagery. For example, you might compare the leaves falling to a golden rain or the chill in the air to a gentle whisper.\\n\\nOverall, it’s a lovely depiction of fall. These suggestions are minor tweaks that might elevate the reader\\'s experience even further. Nice work!\\n\\nLet me know if these feedbacks are addressed.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=389, completion_tokens=150), content='Thank you for the thoughtful feedback! Here’s a revised version, incorporating your suggestions: \\n\\nLeaves of amber, gold—drifting like dreams, \\nA golden rain from trees’ canopies. \\nWhispers of wind—a gentle breath, \\nNature’s scented tapestry embracing earth. \\n\\nHarvest moons rise as evenings chill, \\nFields of plenty paint every hill. \\nSweaters wrapped tight as twilight nears— \\nFall’s embrace, warm as whispered years. \\n\\nPumpkins aglow with autumn’s light, \\nCrackling leaves underfoot in flight. \\nIn every leaf and breeze that calls, \\nWe find the magic of glorious fall. \\n\\nI hope these changes enhance the imagery and sensory experience. Thank you again for your feedback!', type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=556, completion_tokens=132), content='Your revisions have made the poem even more evocative and immersive. The use of sensory details, such as \"whispers of wind\" and \"crackling leaves,\" beautifully enriches the poem, engaging multiple senses. The metaphorical language, like \"a golden rain from trees’ canopies\" and \"Fall’s embrace, warm as whispered years,\" adds depth and enhances the emotional warmth of the poem. The structural variation with the inclusion of dashes effectively adds emphasis and flow. \\n\\nOverall, these changes bring greater vibrancy and life to the poem, allowing readers to truly experience the wonders of fall. Excellent work on the revisions!\\n\\nAPPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "await Console(team.run_stream()) # Resume the team to continue the last task." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can see the team resumed from where it left off in the output above,\n", - "and the first message is from the next agent after the last agent that spoke\n", - "before the team stopped.\n", - "\n", - "Let's resume the team again with a new task while keeping the context about the previous task." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---------- user ----------\n", - "将这首诗用中文唐诗风格写一遍。\n", - "---------- primary ----------\n", - "朔风轻拂叶飘金, \n", - "枝上斜阳染秋林。 \n", - "满山丰收人欢喜, \n", - "月明归途衣渐紧。 \n", - "\n", - "南瓜影映灯火中, \n", - "落叶沙沙伴归程。 \n", - "片片秋意随风起, \n", - "秋韵悠悠心自明。 \n", - "[Prompt tokens: 700, Completion tokens: 77]\n", - "---------- critic ----------\n", - "这首改编的唐诗风格诗作成功地保留了原诗的意境与情感,体现出秋季特有的氛围和美感。通过“朔风轻拂叶飘金”、“枝上斜阳染秋林”等意象,生动地描绘出了秋天的景色,与唐诗中的自然意境相呼应。且“月明归途衣渐紧”、“落叶沙沙伴归程”让人感受到秋天的安宁与温暖。\n", - "\n", - "通过这些诗句,读者能够感受到秋天的惬意与宁静,勾起丰收与团圆的画面,是一次成功的翻译改编。\n", - "\n", - "APPROVE\n", - "[Prompt tokens: 794, Completion tokens: 161]\n", - "---------- Summary ----------\n", - "Number of messages: 3\n", - "Finish reason: Text 'APPROVE' mentioned\n", - "Total prompt tokens: 1494\n", - "Total completion tokens: 238\n", - "Duration: 3.89 seconds\n" - ] - }, - { - "data": { - "text/plain": [ - "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='将这首诗用中文唐诗风格写一遍。', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=700, completion_tokens=77), content='朔风轻拂叶飘金, \\n枝上斜阳染秋林。 \\n满山丰收人欢喜, \\n月明归途衣渐紧。 \\n\\n南瓜影映灯火中, \\n落叶沙沙伴归程。 \\n片片秋意随风起, \\n秋韵悠悠心自明。 ', type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=794, completion_tokens=161), content='这首改编的唐诗风格诗作成功地保留了原诗的意境与情感,体现出秋季特有的氛围和美感。通过“朔风轻拂叶飘金”、“枝上斜阳染秋林”等意象,生动地描绘出了秋天的景色,与唐诗中的自然意境相呼应。且“月明归途衣渐紧”、“落叶沙沙伴归程”让人感受到秋天的安宁与温暖。\\n\\n通过这些诗句,读者能够感受到秋天的惬意与宁静,勾起丰收与团圆的画面,是一次成功的翻译改编。\\n\\nAPPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# The new task is to translate the same poem to Chinese Tang-style poetry.\n", - "await Console(team.run_stream(task=\"将这首诗用中文唐诗风格写一遍。\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Aborting a Team\n", - "\n", - "You can abort a call to {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run` or {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream`\n", - "during execution by setting a {py:class}`~autogen_core.CancellationToken` passed to the `cancellation_token` parameter.\n", - "\n", - "Different from stopping a team, aborting a team will immediately stop the team and raise a {py:class}`~asyncio.CancelledError` exception.\n", - "\n", - "```{note}\n", - "The caller will get a {py:class}`~asyncio.CancelledError` exception when the team is aborted.\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Task was cancelled.\n" - ] - } - ], - "source": [ - "# Create a cancellation token.\n", - "cancellation_token = CancellationToken()\n", - "\n", - "# Use another coroutine to run the team.\n", - "run = asyncio.create_task(\n", - " team.run(\n", - " task=\"Translate the poem to Spanish.\",\n", - " cancellation_token=cancellation_token,\n", - " )\n", - ")\n", - "\n", - "# Cancel the run.\n", - "cancellation_token.cancel()\n", - "\n", - "try:\n", - " result = await run # This will raise a CancelledError.\n", - "except asyncio.CancelledError:\n", - " print(\"Task was cancelled.\")" - ] - } - ], - "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" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Teams\n", + "\n", + "In this section you'll learn how to create a _multi-agent team_ (or simply team) using AutoGen. A team is a group of agents that work together to achieve a common goal.\n", + "\n", + "We'll first show you how to create and run a team. We'll then explain how to observe the team's behavior, which is crucial for debugging and understanding the team's performance, and common operations to control the team's behavior.\n", + "\n", + "```{note}\n", + "When should you use a team?\n", + "Teams are for complex tasks that require collaboration and diverse expertise.\n", + "However, they also demand more scaffolding to steer compared to single agents.\n", + "While AutoGen simplifies the process of working with teams, start with\n", + "a single agent for simpler tasks, and transition to a multi-agent team when a single agent proves inadequate.\n", + "Ensure that you have optimized your single agent with the appropriate tools\n", + "and instructions before moving to a team-based approach.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating a Team\n", + "\n", + "{py:class}`~autogen_agentchat.teams.RoundRobinGroupChat` is a simple yet effective team configuration where all agents share the same context and take turns responding in a round-robin fashion. Each agent, during its turn, broadcasts its response to all other agents, ensuring that the entire team maintains a consistent context.\n", + "\n", + "We will begin by creating a team with two {py:class}`~autogen_agentchat.agents.AssistantAgent` and a {py:class}`~autogen_agentchat.conditions.TextMentionTermination` condition that stops the team when a specific word is detected in the agent's response.\n", + "\n", + "The two-agent team implements the _reflection_ pattern, a multi-agent design pattern where a critic agent evaluates the responses of a primary agent. Learn more about the reflection pattern using the [Core API](../../core-user-guide/design-patterns/reflection.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "\n", + "from autogen_agentchat.agents import AssistantAgent\n", + "from autogen_agentchat.base import TaskResult\n", + "from autogen_agentchat.conditions import ExternalTermination, TextMentionTermination\n", + "from autogen_agentchat.teams import RoundRobinGroupChat\n", + "from autogen_agentchat.ui import Console\n", + "from autogen_core import CancellationToken\n", + "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", + "\n", + "# Create an OpenAI model client.\n", + "model_client = OpenAIChatCompletionClient(\n", + " model=\"gpt-4o-2024-08-06\",\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. Respond with 'APPROVE' to when your feedbacks are addressed.\",\n", + ")\n", + "\n", + "# Define a termination condition that stops the task if the critic approves.\n", + "text_termination = TextMentionTermination(\"APPROVE\")\n", + "\n", + "# Create a team with the primary and critic agents.\n", + "team = RoundRobinGroupChat([primary_agent, critic_agent], termination_condition=text_termination)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running a Team\n", + "\n", + "Let's call the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run` method\n", + "to start the team with a task." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a short poem about the fall season.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=28, completion_tokens=109), content=\"Leaves of amber, gold, and rust, \\nDance upon the gentle gust. \\nCrisp air whispers tales of old, \\nAs daylight wanes, the night grows bold. \\n\\nPumpkin patch and apple treats, \\nLaughter in the street repeats. \\nSweaters warm and fires aglow, \\nIt's time for nature's vibrant show. \\n\\nThe harvest moon ascends the sky, \\nWhile geese in formation start to fly. \\nAutumn speaks in colors bright, \\nA fleeting grace, a pure delight. \", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=154, completion_tokens=200), content='Your poem beautifully captures the essence of the fall season with vivid imagery and a rhythmic flow. The use of descriptive language like \"amber, gold, and rust\" effectively paints a visual picture of the changing leaves. Phrases such as \"crisp air whispers tales of old\" and \"daylight wanes, the night grows bold\" add a poetic touch by incorporating seasonal characteristics.\\n\\nHowever, you might consider exploring other sensory details to deepen the reader\\'s immersion. For example, mentioning the sound of crunching leaves underfoot or the scent of cinnamon and spices in the air could enhance the sensory experience.\\n\\nAdditionally, while the mention of \"pumpkin patch and apple treats\" is evocative of fall, expanding on these elements or including more personal experiences or emotions associated with the season might make the poem more relatable and engaging.\\n\\nOverall, you\\'ve crafted a lovely poem that celebrates the beauty and traditions of autumn with grace and warmth. A few tweaks to include multisensory details could elevate it even further.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=347, completion_tokens=178), content=\"Thank you for the thoughtful feedback. Here's a revised version of the poem with additional sensory details:\\n\\nLeaves of amber, gold, and rust, \\nDance upon the gentle gust. \\nCrisp air whispers tales of old, \\nAs daylight wanes, the night grows bold. \\n\\nCrunch beneath the wandering feet, \\nA melody of autumn's beat. \\nCinnamon and spices blend, \\nIn every breeze, nostalgia sends. \\n\\nPumpkin patch and apple treats, \\nLaughter in the street repeats. \\nSweaters warm and fires aglow, \\nIt's time for nature's vibrant show. \\n\\nThe harvest moon ascends the sky, \\nWhile geese in formation start to fly. \\nAutumn speaks in colors bright, \\nA fleeting grace, a pure delight. \\n\\nI hope this version resonates even more with the spirit of fall. Thank you again for your suggestions!\", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=542, completion_tokens=3), content='APPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")\n" + ] + } + ], + "source": [ + "# Use `asyncio.run(...)` when running in a script.\n", + "result = await team.run(task=\"Write a short poem about the fall season.\")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The team runs the agents until the termination condition was met.\n", + "In this case, the team ran agents following a round-robin order until the the\n", + "termination condition was met when the word \"APPROVE\" was detected in the\n", + "agent's response.\n", + "When the team stops, it returns a {py:class}`~autogen_agentchat.base.TaskResult` object with all the messages produced by the agents in the team." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Observing a Team\n", + "\n", + "Similar to the agent's {py:meth}`~autogen_agentchat.agents.BaseChatAgent.on_messages_stream` method, you can stream the team's messages while it is running by calling the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream` method. This method returns a generator that yields messages produced by the agents in the team as they are generated, with the final item being the {py:class}`~autogen_agentchat.base.TaskResult` object." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "source='user' models_usage=None content='Write a short poem about the fall season.' type='TextMessage'\n", + "source='primary' models_usage=RequestUsage(prompt_tokens=28, completion_tokens=105) content=\"Leaves descend in golden dance, \\nWhispering secrets as they fall, \\nCrisp air brings a gentle trance, \\nHeralding Autumn's call. \\n\\nPumpkins glow with orange light, \\nFields wear a cloak of amber hue, \\nDays retreat to longer night, \\nSkies shift to deeper blue. \\n\\nWinds carry scents of earth and pine, \\nSweaters wrap us, warm and tight, \\nNature's canvas, bold design, \\nIn Fall's embrace, we find delight. \" type='TextMessage'\n", + "source='critic' models_usage=RequestUsage(prompt_tokens=150, completion_tokens=226) content='Your poem beautifully captures the essence of fall with vivid imagery and a soothing rhythm. The imagery of leaves descending, pumpkins glowing, and fields cloaked in amber hues effectively paints a picture of the autumn season. The use of contrasting elements like \"Days retreat to longer night\" and \"Sweaters wrap us, warm and tight\" provides a nice balance between the cold and warmth associated with the season. Additionally, the personification of autumn through phrases like \"Autumn\\'s call\" and \"Nature\\'s canvas, bold design\" adds depth to the depiction of fall.\\n\\nTo enhance the poem further, you might consider focusing on the soundscape of fall, such as the rustling of leaves or the distant call of migrating birds, to engage readers\\' auditory senses. Also, varying the line lengths slightly could add a dynamic flow to the reading experience.\\n\\nOverall, your poem is engaging and effectively encapsulates the beauty and transition of fall. With a few adjustments to explore other sensory details, it could become even more immersive. \\n\\nIf you incorporate some of these suggestions or find another way to expand the sensory experience, please share your update!' type='TextMessage'\n", + "source='primary' models_usage=RequestUsage(prompt_tokens=369, completion_tokens=143) content=\"Thank you for the thoughtful critique and suggestions. Here's a revised version of the poem with added attention to auditory senses and varied line lengths:\\n\\nLeaves descend in golden dance, \\nWhisper secrets in their fall, \\nBreezes hum a gentle trance, \\nHeralding Autumn's call. \\n\\nPumpkins glow with orange light, \\nAmber fields beneath wide skies, \\nDays retreat to longer night, \\nChill winds and distant cries. \\n\\nRustling whispers of the trees, \\nSweaters wrap us, snug and tight, \\nNature's canvas, bold and free, \\nIn Fall's embrace, pure delight. \\n\\nI appreciate your feedback and hope this version better captures the sensory richness of the season!\" type='TextMessage'\n", + "source='critic' models_usage=RequestUsage(prompt_tokens=529, completion_tokens=160) content='Your revised poem is a beautiful enhancement of the original. By incorporating auditory elements such as \"Breezes hum\" and \"Rustling whispers of the trees,\" you\\'ve added an engaging soundscape that draws the reader deeper into the experience of fall. The varied line lengths work well to create a more dynamic rhythm throughout the poem, adding interest and variety to each stanza.\\n\\nThe succinct, yet vivid, lines of \"Chill winds and distant cries\" wonderfully evoke the atmosphere of the season, adding a touch of mystery and depth. The final stanza wraps up the poem nicely, celebrating the complete sensory embrace of fall with lines like \"Nature\\'s canvas, bold and free.\"\\n\\nYou\\'ve successfully infused more sensory richness into the poem, enhancing its overall emotional and atmospheric impact. Great job on the revisions!\\n\\nAPPROVE' type='TextMessage'\n", + "Stop Reason: Text 'APPROVE' mentioned\n" + ] + } + ], + "source": [ + "# When running inside a script, use a async main function and call it from `asyncio.run(...)`.\n", + "await team.reset() # Reset the team for a new task.\n", + "async for message in team.run_stream(task=\"Write a short poem about the fall season.\"): # type: ignore\n", + " if isinstance(message, TaskResult):\n", + " print(\"Stop Reason:\", message.stop_reason)\n", + " else:\n", + " print(message)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As demonstrated in the example above, you can determine the reason why the team stopped by checking the {py:attr}`~autogen_agentchat.base.TaskResult.stop_reason` attribute.\n", + "\n", + "The {py:meth}`~autogen_agentchat.ui.Console` method provides a convenient way to print messages to the console with proper formatting.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---------- user ----------\n", + "Write a short poem about the fall season.\n", + "---------- primary ----------\n", + "Golden leaves in crisp air dance, \n", + "Whispering tales as they prance. \n", + "Amber hues paint the ground, \n", + "Nature's symphony all around. \n", + "\n", + "Sweaters hug with tender grace, \n", + "While pumpkins smile, a warm embrace. \n", + "Chill winds hum through towering trees, \n", + "A vibrant tapestry in the breeze. \n", + "\n", + "Harvest moons in twilight glow, \n", + "Casting magic on fields below. \n", + "Fall's embrace, a gentle call, \n", + "To savor beauty before snowfalls. \n", + "[Prompt tokens: 28, Completion tokens: 99]\n", + "---------- critic ----------\n", + "Your poem beautifully captures the essence of the fall season, creating a vivid and cozy atmosphere. The imagery of golden leaves and amber hues paints a picturesque scene that many can easily relate to. I particularly appreciate the personification of pumpkins and the gentle embrace of sweaters, which adds warmth to your verses. \n", + "\n", + "To enhance the poem further, you might consider adding more sensory details to make the reader feel even more immersed in the experience. For example, including specific sounds, scents, or textures could deepen the connection to autumn's ambiance. Additionally, you could explore the emotional transitions as the season prepares for winter to provide a reflective element to the piece.\n", + "\n", + "Overall, it's a lovely and evocative depiction of fall, evoking feelings of comfort and appreciation for nature's changing beauty. Great work!\n", + "[Prompt tokens: 144, Completion tokens: 157]\n", + "---------- primary ----------\n", + "Thank you for your thoughtful feedback! I'm glad you enjoyed the imagery and warmth in the poem. To enhance the sensory experience and emotional depth, here's a revised version incorporating your suggestions:\n", + "\n", + "---\n", + "\n", + "Golden leaves in crisp air dance, \n", + "Whispering tales as they prance. \n", + "Amber hues paint the crunchy ground, \n", + "Nature's symphony all around. \n", + "\n", + "Sweaters hug with tender grace, \n", + "While pumpkins grin, a warm embrace. \n", + "Chill winds hum through towering trees, \n", + "Crackling fires warm the breeze. \n", + "\n", + "Apples in the orchard's glow, \n", + "Sweet cider scents that overflow. \n", + "Crunch of paths beneath our feet, \n", + "Cinnamon spice and toasty heat. \n", + "\n", + "Harvest moons in twilight's glow, \n", + "Casting magic on fields below. \n", + "Fall's embrace, a gentle call, \n", + "Reflects on life's inevitable thaw. \n", + "\n", + "--- \n", + "\n", + "I hope this version enhances the sensory and emotional elements of the season. Thank you again for your insights!\n", + "[Prompt tokens: 294, Completion tokens: 195]\n", + "---------- critic ----------\n", + "APPROVE\n", + "[Prompt tokens: 506, Completion tokens: 4]\n", + "---------- Summary ----------\n", + "Number of messages: 5\n", + "Finish reason: Text 'APPROVE' mentioned\n", + "Total prompt tokens: 972\n", + "Total completion tokens: 455\n", + "Duration: 11.78 seconds\n" + ] }, - "nbformat": 4, - "nbformat_minor": 2 + { + "data": { + "text/plain": [ + "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a short poem about the fall season.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=28, completion_tokens=99), content=\"Golden leaves in crisp air dance, \\nWhispering tales as they prance. \\nAmber hues paint the ground, \\nNature's symphony all around. \\n\\nSweaters hug with tender grace, \\nWhile pumpkins smile, a warm embrace. \\nChill winds hum through towering trees, \\nA vibrant tapestry in the breeze. \\n\\nHarvest moons in twilight glow, \\nCasting magic on fields below. \\nFall's embrace, a gentle call, \\nTo savor beauty before snowfalls. \", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=144, completion_tokens=157), content=\"Your poem beautifully captures the essence of the fall season, creating a vivid and cozy atmosphere. The imagery of golden leaves and amber hues paints a picturesque scene that many can easily relate to. I particularly appreciate the personification of pumpkins and the gentle embrace of sweaters, which adds warmth to your verses. \\n\\nTo enhance the poem further, you might consider adding more sensory details to make the reader feel even more immersed in the experience. For example, including specific sounds, scents, or textures could deepen the connection to autumn's ambiance. Additionally, you could explore the emotional transitions as the season prepares for winter to provide a reflective element to the piece.\\n\\nOverall, it's a lovely and evocative depiction of fall, evoking feelings of comfort and appreciation for nature's changing beauty. Great work!\", type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=294, completion_tokens=195), content=\"Thank you for your thoughtful feedback! I'm glad you enjoyed the imagery and warmth in the poem. To enhance the sensory experience and emotional depth, here's a revised version incorporating your suggestions:\\n\\n---\\n\\nGolden leaves in crisp air dance, \\nWhispering tales as they prance. \\nAmber hues paint the crunchy ground, \\nNature's symphony all around. \\n\\nSweaters hug with tender grace, \\nWhile pumpkins grin, a warm embrace. \\nChill winds hum through towering trees, \\nCrackling fires warm the breeze. \\n\\nApples in the orchard's glow, \\nSweet cider scents that overflow. \\nCrunch of paths beneath our feet, \\nCinnamon spice and toasty heat. \\n\\nHarvest moons in twilight's glow, \\nCasting magic on fields below. \\nFall's embrace, a gentle call, \\nReflects on life's inevitable thaw. \\n\\n--- \\n\\nI hope this version enhances the sensory and emotional elements of the season. Thank you again for your insights!\", type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=506, completion_tokens=4), content='APPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await team.reset() # Reset the team for a new task.\n", + "await Console(team.run_stream(task=\"Write a short poem about the fall season.\")) # Stream the messages to the console." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Resetting a Team\n", + "\n", + "You can reset the team by calling the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.reset` method. This method will clear the team's state, including all agents.\n", + "It will call the each agent's {py:meth}`~autogen_agentchat.base.ChatAgent.on_reset` method to clear the agent's state." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "await team.reset() # Reset the team for the next run." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is usually a good idea to reset the team if the next task is not related to the previous task.\n", + "However, if the next task is related to the previous task, you don't need to reset and you can instead\n", + "resume the team." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stopping a Team\n", + "\n", + "Apart from automatic termination conditions such as {py:class}`~autogen_agentchat.conditions.TextMentionTermination`\n", + "that stops the team based on the internal state of the team, you can also stop the team\n", + "from outside by using the {py:class}`~autogen_agentchat.conditions.ExternalTermination`.\n", + "\n", + "Calling {py:meth}`~autogen_agentchat.conditions.ExternalTermination.set` \n", + "on {py:class}`~autogen_agentchat.conditions.ExternalTermination` will stop\n", + "the team when the current agent's turn is over.\n", + "Thus, the team may not stop immediately.\n", + "This allows the current agent to finish its turn and broadcast the final message to the team\n", + "before the team stops, keeping the team's state consistent." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---------- user ----------\n", + "Write a short poem about the fall season.\n", + "---------- primary ----------\n", + "Leaves of amber, gold, and red, \n", + "Gently drifting from trees overhead. \n", + "Whispers of wind through the crisp, cool air, \n", + "Nature's canvas painted with care. \n", + "\n", + "Harvest moons and evenings that chill, \n", + "Fields of plenty on every hill. \n", + "Sweaters wrapped tight as twilight nears, \n", + "Fall's charming embrace, as warm as it appears. \n", + "\n", + "Pumpkins aglow with autumn's light, \n", + "Harvest feasts and stars so bright. \n", + "In every leaf and breeze that calls, \n", + "We find the magic of glorious fall. \n", + "[Prompt tokens: 28, Completion tokens: 114]\n", + "---------- Summary ----------\n", + "Number of messages: 2\n", + "Finish reason: External termination requested\n", + "Total prompt tokens: 28\n", + "Total completion tokens: 114\n", + "Duration: 1.71 seconds\n" + ] + }, + { + "data": { + "text/plain": [ + "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='Write a short poem about the fall season.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=28, completion_tokens=114), content=\"Leaves of amber, gold, and red, \\nGently drifting from trees overhead. \\nWhispers of wind through the crisp, cool air, \\nNature's canvas painted with care. \\n\\nHarvest moons and evenings that chill, \\nFields of plenty on every hill. \\nSweaters wrapped tight as twilight nears, \\nFall's charming embrace, as warm as it appears. \\n\\nPumpkins aglow with autumn's light, \\nHarvest feasts and stars so bright. \\nIn every leaf and breeze that calls, \\nWe find the magic of glorious fall. \", type='TextMessage')], stop_reason='External termination requested')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a new team with an external termination condition.\n", + "external_termination = ExternalTermination()\n", + "team = RoundRobinGroupChat(\n", + " [primary_agent, critic_agent],\n", + " termination_condition=external_termination | text_termination, # Use the bitwise OR operator to combine conditions.\n", + ")\n", + "\n", + "# Run the team in a background task.\n", + "run = asyncio.create_task(Console(team.run_stream(task=\"Write a short poem about the fall season.\")))\n", + "\n", + "# Wait for some time.\n", + "await asyncio.sleep(0.1)\n", + "\n", + "# Stop the team.\n", + "external_termination.set()\n", + "\n", + "# Wait for the team to finish.\n", + "await run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the ouput above, you can see the team stopped because the external termination condition was met,\n", + "but the speaking agent was able to finish its turn before the team stopped." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Resuming a Team\n", + "\n", + "Teams are stateful and maintains the conversation history and context\n", + "after each run, unless you reset the team.\n", + "\n", + "You can resume a team to continue from where it left off by calling the {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run` or {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream` method again\n", + "without a new task.\n", + "{py:class}`~autogen_agentchat.teams.RoundRobinGroupChat` will continue from the next agent in the round-robin order." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---------- critic ----------\n", + "This poem beautifully captures the essence of the fall season with vivid imagery and a soothing rhythm. The descriptions of the changing leaves, cool air, and various autumn traditions make it easy for readers to envision and feel the charm of fall. Here are a few suggestions to enhance its impact:\n", + "\n", + "1. **Structure Variation**: Consider breaking some lines with a hyphen or ellipsis for dramatic effect or emphasis. For instance, “Sweaters wrapped tight as twilight nears— / Fall’s charming embrace, as warm as it appears.\"\n", + "\n", + "2. **Sensory Details**: While the poem already evokes visual and tactile senses, incorporating other senses such as sound or smell could deepen the immersion. For example, include the scent of wood smoke or the crunch of leaves underfoot.\n", + "\n", + "3. **Metaphorical Language**: Adding metaphors or similes can further enrich the imagery. For example, you might compare the leaves falling to a golden rain or the chill in the air to a gentle whisper.\n", + "\n", + "Overall, it’s a lovely depiction of fall. These suggestions are minor tweaks that might elevate the reader's experience even further. Nice work!\n", + "\n", + "Let me know if these feedbacks are addressed.\n", + "[Prompt tokens: 159, Completion tokens: 237]\n", + "---------- primary ----------\n", + "Thank you for the thoughtful feedback! Here’s a revised version, incorporating your suggestions: \n", + "\n", + "Leaves of amber, gold—drifting like dreams, \n", + "A golden rain from trees’ canopies. \n", + "Whispers of wind—a gentle breath, \n", + "Nature’s scented tapestry embracing earth. \n", + "\n", + "Harvest moons rise as evenings chill, \n", + "Fields of plenty paint every hill. \n", + "Sweaters wrapped tight as twilight nears— \n", + "Fall’s embrace, warm as whispered years. \n", + "\n", + "Pumpkins aglow with autumn’s light, \n", + "Crackling leaves underfoot in flight. \n", + "In every leaf and breeze that calls, \n", + "We find the magic of glorious fall. \n", + "\n", + "I hope these changes enhance the imagery and sensory experience. Thank you again for your feedback!\n", + "[Prompt tokens: 389, Completion tokens: 150]\n", + "---------- critic ----------\n", + "Your revisions have made the poem even more evocative and immersive. The use of sensory details, such as \"whispers of wind\" and \"crackling leaves,\" beautifully enriches the poem, engaging multiple senses. The metaphorical language, like \"a golden rain from trees’ canopies\" and \"Fall’s embrace, warm as whispered years,\" adds depth and enhances the emotional warmth of the poem. The structural variation with the inclusion of dashes effectively adds emphasis and flow. \n", + "\n", + "Overall, these changes bring greater vibrancy and life to the poem, allowing readers to truly experience the wonders of fall. Excellent work on the revisions!\n", + "\n", + "APPROVE\n", + "[Prompt tokens: 556, Completion tokens: 132]\n", + "---------- Summary ----------\n", + "Number of messages: 3\n", + "Finish reason: Text 'APPROVE' mentioned\n", + "Total prompt tokens: 1104\n", + "Total completion tokens: 519\n", + "Duration: 9.79 seconds\n" + ] + }, + { + "data": { + "text/plain": [ + "TaskResult(messages=[TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=159, completion_tokens=237), content='This poem beautifully captures the essence of the fall season with vivid imagery and a soothing rhythm. The descriptions of the changing leaves, cool air, and various autumn traditions make it easy for readers to envision and feel the charm of fall. Here are a few suggestions to enhance its impact:\\n\\n1. **Structure Variation**: Consider breaking some lines with a hyphen or ellipsis for dramatic effect or emphasis. For instance, “Sweaters wrapped tight as twilight nears— / Fall’s charming embrace, as warm as it appears.\"\\n\\n2. **Sensory Details**: While the poem already evokes visual and tactile senses, incorporating other senses such as sound or smell could deepen the immersion. For example, include the scent of wood smoke or the crunch of leaves underfoot.\\n\\n3. **Metaphorical Language**: Adding metaphors or similes can further enrich the imagery. For example, you might compare the leaves falling to a golden rain or the chill in the air to a gentle whisper.\\n\\nOverall, it’s a lovely depiction of fall. These suggestions are minor tweaks that might elevate the reader\\'s experience even further. Nice work!\\n\\nLet me know if these feedbacks are addressed.', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=389, completion_tokens=150), content='Thank you for the thoughtful feedback! Here’s a revised version, incorporating your suggestions: \\n\\nLeaves of amber, gold—drifting like dreams, \\nA golden rain from trees’ canopies. \\nWhispers of wind—a gentle breath, \\nNature’s scented tapestry embracing earth. \\n\\nHarvest moons rise as evenings chill, \\nFields of plenty paint every hill. \\nSweaters wrapped tight as twilight nears— \\nFall’s embrace, warm as whispered years. \\n\\nPumpkins aglow with autumn’s light, \\nCrackling leaves underfoot in flight. \\nIn every leaf and breeze that calls, \\nWe find the magic of glorious fall. \\n\\nI hope these changes enhance the imagery and sensory experience. Thank you again for your feedback!', type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=556, completion_tokens=132), content='Your revisions have made the poem even more evocative and immersive. The use of sensory details, such as \"whispers of wind\" and \"crackling leaves,\" beautifully enriches the poem, engaging multiple senses. The metaphorical language, like \"a golden rain from trees’ canopies\" and \"Fall’s embrace, warm as whispered years,\" adds depth and enhances the emotional warmth of the poem. The structural variation with the inclusion of dashes effectively adds emphasis and flow. \\n\\nOverall, these changes bring greater vibrancy and life to the poem, allowing readers to truly experience the wonders of fall. Excellent work on the revisions!\\n\\nAPPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await Console(team.run_stream()) # Resume the team to continue the last task." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can see the team resumed from where it left off in the output above,\n", + "and the first message is from the next agent after the last agent that spoke\n", + "before the team stopped.\n", + "\n", + "Let's resume the team again with a new task while keeping the context about the previous task." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---------- user ----------\n", + "将这首诗用中文唐诗风格写一遍。\n", + "---------- primary ----------\n", + "朔风轻拂叶飘金, \n", + "枝上斜阳染秋林。 \n", + "满山丰收人欢喜, \n", + "月明归途衣渐紧。 \n", + "\n", + "南瓜影映灯火中, \n", + "落叶沙沙伴归程。 \n", + "片片秋意随风起, \n", + "秋韵悠悠心自明。 \n", + "[Prompt tokens: 700, Completion tokens: 77]\n", + "---------- critic ----------\n", + "这首改编的唐诗风格诗作成功地保留了原诗的意境与情感,体现出秋季特有的氛围和美感。通过“朔风轻拂叶飘金”、“枝上斜阳染秋林”等意象,生动地描绘出了秋天的景色,与唐诗中的自然意境相呼应。且“月明归途衣渐紧”、“落叶沙沙伴归程”让人感受到秋天的安宁与温暖。\n", + "\n", + "通过这些诗句,读者能够感受到秋天的惬意与宁静,勾起丰收与团圆的画面,是一次成功的翻译改编。\n", + "\n", + "APPROVE\n", + "[Prompt tokens: 794, Completion tokens: 161]\n", + "---------- Summary ----------\n", + "Number of messages: 3\n", + "Finish reason: Text 'APPROVE' mentioned\n", + "Total prompt tokens: 1494\n", + "Total completion tokens: 238\n", + "Duration: 3.89 seconds\n" + ] + }, + { + "data": { + "text/plain": [ + "TaskResult(messages=[TextMessage(source='user', models_usage=None, content='将这首诗用中文唐诗风格写一遍。', type='TextMessage'), TextMessage(source='primary', models_usage=RequestUsage(prompt_tokens=700, completion_tokens=77), content='朔风轻拂叶飘金, \\n枝上斜阳染秋林。 \\n满山丰收人欢喜, \\n月明归途衣渐紧。 \\n\\n南瓜影映灯火中, \\n落叶沙沙伴归程。 \\n片片秋意随风起, \\n秋韵悠悠心自明。 ', type='TextMessage'), TextMessage(source='critic', models_usage=RequestUsage(prompt_tokens=794, completion_tokens=161), content='这首改编的唐诗风格诗作成功地保留了原诗的意境与情感,体现出秋季特有的氛围和美感。通过“朔风轻拂叶飘金”、“枝上斜阳染秋林”等意象,生动地描绘出了秋天的景色,与唐诗中的自然意境相呼应。且“月明归途衣渐紧”、“落叶沙沙伴归程”让人感受到秋天的安宁与温暖。\\n\\n通过这些诗句,读者能够感受到秋天的惬意与宁静,勾起丰收与团圆的画面,是一次成功的翻译改编。\\n\\nAPPROVE', type='TextMessage')], stop_reason=\"Text 'APPROVE' mentioned\")" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The new task is to translate the same poem to Chinese Tang-style poetry.\n", + "await Console(team.run_stream(task=\"将这首诗用中文唐诗风格写一遍。\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Aborting a Team\n", + "\n", + "You can abort a call to {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run` or {py:meth}`~autogen_agentchat.teams.BaseGroupChat.run_stream`\n", + "during execution by setting a {py:class}`~autogen_core.CancellationToken` passed to the `cancellation_token` parameter.\n", + "\n", + "Different from stopping a team, aborting a team will immediately stop the team and raise a {py:class}`~asyncio.CancelledError` exception.\n", + "\n", + "```{note}\n", + "The caller will get a {py:class}`~asyncio.CancelledError` exception when the team is aborted.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Task was cancelled.\n" + ] + } + ], + "source": [ + "# Create a cancellation token.\n", + "cancellation_token = CancellationToken()\n", + "\n", + "# Use another coroutine to run the team.\n", + "run = asyncio.create_task(\n", + " team.run(\n", + " task=\"Translate the poem to Spanish.\",\n", + " cancellation_token=cancellation_token,\n", + " )\n", + ")\n", + "\n", + "# Cancel the run.\n", + "cancellation_token.cancel()\n", + "\n", + "try:\n", + " result = await run # This will raise a CancelledError.\n", + "except asyncio.CancelledError:\n", + " print(\"Task was cancelled.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single-Agent Team\n", + "\n", + "Often, you may want to run a single agent in a team configuration.\n", + "This is useful for running the {py:class}`~autogen_agentchat.agents.AssistantAgent` in a loop\n", + "until a termination condition is met.\n", + "\n", + "This is different from running the {py:class}`~autogen_agentchat.agents.AssistantAgent` using\n", + "its {py:meth}`~autogen_agentchat.agents.BaseChatAgent.run` or {py:meth}`~autogen_agentchat.agents.BaseChatAgent.run_stream` method,\n", + "which only runs the agent for one step and returns the result.\n", + "See {py:class}`~autogen_agentchat.agents.AssistantAgent` for more details about a single step.\n", + "\n", + "Here is an example of running a single agent in a {py:class}`~autogen_agentchat.teams.RoundRobinGroupChat` team configuration\n", + "with a {py:class}`~autogen_agentchat.conditions.TextMessageTermination` condition.\n", + "The task is to increment a number until it reaches 10 using a tool.\n", + "The agent will keep calling the tool until the number reaches 10,\n", + "and then it will return a final {py:class}`~autogen_agentchat.messages.TextMessage`\n", + "which will stop the run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TextMessage source='user' models_usage=None metadata={} content='Increment the number 5 to 10.' type='TextMessage'\n", + "ToolCallRequestEvent source='looped_assistant' models_usage=RequestUsage(prompt_tokens=75, completion_tokens=15) metadata={} content=[FunctionCall(id='call_qTDXSouN3MtGDqa8l0DM1ciD', arguments='{\"number\":5}', name='increment_number')] type='ToolCallRequestEvent'\n", + "ToolCallExecutionEvent source='looped_assistant' models_usage=None metadata={} content=[FunctionExecutionResult(content='6', name='increment_number', call_id='call_qTDXSouN3MtGDqa8l0DM1ciD', is_error=False)] type='ToolCallExecutionEvent'\n", + "ToolCallSummaryMessage source='looped_assistant' models_usage=None metadata={} content='6' type='ToolCallSummaryMessage'\n", + "ToolCallRequestEvent source='looped_assistant' models_usage=RequestUsage(prompt_tokens=103, completion_tokens=15) metadata={} content=[FunctionCall(id='call_VGZPlsFVVdyxutR63Yr087pt', arguments='{\"number\":6}', name='increment_number')] type='ToolCallRequestEvent'\n", + "ToolCallExecutionEvent source='looped_assistant' models_usage=None metadata={} content=[FunctionExecutionResult(content='7', name='increment_number', call_id='call_VGZPlsFVVdyxutR63Yr087pt', is_error=False)] type='ToolCallExecutionEvent'\n", + "ToolCallSummaryMessage source='looped_assistant' models_usage=None metadata={} content='7' type='ToolCallSummaryMessage'\n", + "ToolCallRequestEvent source='looped_assistant' models_usage=RequestUsage(prompt_tokens=131, completion_tokens=15) metadata={} content=[FunctionCall(id='call_VRKGPqPM9AHoef2g2kgsKwZe', arguments='{\"number\":7}', name='increment_number')] type='ToolCallRequestEvent'\n", + "ToolCallExecutionEvent source='looped_assistant' models_usage=None metadata={} content=[FunctionExecutionResult(content='8', name='increment_number', call_id='call_VRKGPqPM9AHoef2g2kgsKwZe', is_error=False)] type='ToolCallExecutionEvent'\n", + "ToolCallSummaryMessage source='looped_assistant' models_usage=None metadata={} content='8' type='ToolCallSummaryMessage'\n", + "ToolCallRequestEvent source='looped_assistant' models_usage=RequestUsage(prompt_tokens=159, completion_tokens=15) metadata={} content=[FunctionCall(id='call_TOUMjSCG2kVdFcw2CMeb5DYX', arguments='{\"number\":8}', name='increment_number')] type='ToolCallRequestEvent'\n", + "ToolCallExecutionEvent source='looped_assistant' models_usage=None metadata={} content=[FunctionExecutionResult(content='9', name='increment_number', call_id='call_TOUMjSCG2kVdFcw2CMeb5DYX', is_error=False)] type='ToolCallExecutionEvent'\n", + "ToolCallSummaryMessage source='looped_assistant' models_usage=None metadata={} content='9' type='ToolCallSummaryMessage'\n", + "ToolCallRequestEvent source='looped_assistant' models_usage=RequestUsage(prompt_tokens=187, completion_tokens=15) metadata={} content=[FunctionCall(id='call_wjq7OO9Kf5YYurWGc5lsqttJ', arguments='{\"number\":9}', name='increment_number')] type='ToolCallRequestEvent'\n", + "ToolCallExecutionEvent source='looped_assistant' models_usage=None metadata={} content=[FunctionExecutionResult(content='10', name='increment_number', call_id='call_wjq7OO9Kf5YYurWGc5lsqttJ', is_error=False)] type='ToolCallExecutionEvent'\n", + "ToolCallSummaryMessage source='looped_assistant' models_usage=None metadata={} content='10' type='ToolCallSummaryMessage'\n", + "TextMessage source='looped_assistant' models_usage=RequestUsage(prompt_tokens=215, completion_tokens=15) metadata={} content='The number 5 incremented to 10 is 10.' type='TextMessage'\n", + "TaskResult TaskResult(messages=[TextMessage(source='user', models_usage=None, metadata={}, content='Increment the number 5 to 10.', type='TextMessage'), ToolCallRequestEvent(source='looped_assistant', models_usage=RequestUsage(prompt_tokens=75, completion_tokens=15), metadata={}, content=[FunctionCall(id='call_qTDXSouN3MtGDqa8l0DM1ciD', arguments='{\"number\":5}', name='increment_number')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='looped_assistant', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='6', name='increment_number', call_id='call_qTDXSouN3MtGDqa8l0DM1ciD', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='looped_assistant', models_usage=None, metadata={}, content='6', type='ToolCallSummaryMessage'), ToolCallRequestEvent(source='looped_assistant', models_usage=RequestUsage(prompt_tokens=103, completion_tokens=15), metadata={}, content=[FunctionCall(id='call_VGZPlsFVVdyxutR63Yr087pt', arguments='{\"number\":6}', name='increment_number')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='looped_assistant', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='7', name='increment_number', call_id='call_VGZPlsFVVdyxutR63Yr087pt', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='looped_assistant', models_usage=None, metadata={}, content='7', type='ToolCallSummaryMessage'), ToolCallRequestEvent(source='looped_assistant', models_usage=RequestUsage(prompt_tokens=131, completion_tokens=15), metadata={}, content=[FunctionCall(id='call_VRKGPqPM9AHoef2g2kgsKwZe', arguments='{\"number\":7}', name='increment_number')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='looped_assistant', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='8', name='increment_number', call_id='call_VRKGPqPM9AHoef2g2kgsKwZe', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='looped_assistant', models_usage=None, metadata={}, content='8', type='ToolCallSummaryMessage'), ToolCallRequestEvent(source='looped_assistant', models_usage=RequestUsage(prompt_tokens=159, completion_tokens=15), metadata={}, content=[FunctionCall(id='call_TOUMjSCG2kVdFcw2CMeb5DYX', arguments='{\"number\":8}', name='increment_number')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='looped_assistant', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='9', name='increment_number', call_id='call_TOUMjSCG2kVdFcw2CMeb5DYX', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='looped_assistant', models_usage=None, metadata={}, content='9', type='ToolCallSummaryMessage'), ToolCallRequestEvent(source='looped_assistant', models_usage=RequestUsage(prompt_tokens=187, completion_tokens=15), metadata={}, content=[FunctionCall(id='call_wjq7OO9Kf5YYurWGc5lsqttJ', arguments='{\"number\":9}', name='increment_number')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='looped_assistant', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='10', name='increment_number', call_id='call_wjq7OO9Kf5YYurWGc5lsqttJ', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='looped_assistant', models_usage=None, metadata={}, content='10', type='ToolCallSummaryMessage'), TextMessage(source='looped_assistant', models_usage=RequestUsage(prompt_tokens=215, completion_tokens=15), metadata={}, content='The number 5 incremented to 10 is 10.', type='TextMessage')], stop_reason=\"Text message received from 'looped_assistant'\")\n" + ] + } + ], + "source": [ + "from autogen_agentchat.agents import AssistantAgent\n", + "from autogen_agentchat.conditions import TextMessageTermination\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", + " # api_key=\"sk-...\", # Optional if you have an OPENAI_API_KEY env variable set.\n", + " # Disable parallel tool calls for this example.\n", + " parallel_tool_calls=False, # type: ignore\n", + ")\n", + "\n", + "\n", + "# Create a tool for incrementing a number.\n", + "def increment_number(number: int) -> int:\n", + " \"\"\"Increment a number by 1.\"\"\"\n", + " return number + 1\n", + "\n", + "\n", + "# Create a tool agent that uses the increment_number function.\n", + "looped_assistant = AssistantAgent(\n", + " \"looped_assistant\",\n", + " model_client=model_client,\n", + " tools=[increment_number], # Register the tool.\n", + " system_message=\"You are a helpful AI assistant, use the tool to increment the number.\",\n", + ")\n", + "\n", + "# Termination condition that stops the task if the agent responds with a text message.\n", + "termination_condition = TextMessageTermination(\"looped_assistant\")\n", + "\n", + "# Create a team with the looped assistant agent and the termination condition.\n", + "team = RoundRobinGroupChat(\n", + " [looped_assistant],\n", + " termination_condition=termination_condition,\n", + ")\n", + "\n", + "# Run the team with a task and print the messages to the console.\n", + "async for message in team.run_stream(task=\"Increment the number 5 to 10.\"): # type: ignore\n", + " print(type(message).__name__, message)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The key is to focus on the termination condition.\n", + "In this example, we use a {py:class}`~autogen_agentchat.conditions.TextMessageTermination` condition\n", + "that stops the team when the agent stop producing {py:class}`~autogen_agentchat.messages.ToolCallSummaryMessage`.\n", + "The team will keep running until the agent produces a {py:class}`~autogen_agentchat.messages.TextMessage` with the final result.\n", + "\n", + "You can also use other termination conditions to control the agent.\n", + "See [Termination Conditions](./termination.ipynb) for more details." + ] + } + ], + "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.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb index d3e3ef7fd..72b606193 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb @@ -1,304 +1,515 @@ { - "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." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "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": [ - "Next, 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" - ] - } - ], - "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.11.5" - } + "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." + ] + }, + { + "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" + ] }, - "nbformat": 4, - "nbformat_minor": 2 + { + "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.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 }