From 43eed01bbccfc55d6379ffcb4c68a4d400652a0f Mon Sep 17 00:00:00 2001 From: Eric Zhu Date: Mon, 16 Dec 2024 09:12:26 -0800 Subject: [PATCH] Update custom agent doc with arithmetic agent example (#4720) * Update custom agent doc with arithmetic agent example * fix mypy * fix model capabilities usage --- .../tutorial/custom-agents.ipynb | 193 +++++++++++++++--- .../tutorial/models.ipynb | 8 +- .../cookbook/local-llms-ollama-litellm.ipynb | 15 +- .../design-patterns/sequential-workflow.svg | 2 +- 4 files changed, 185 insertions(+), 33 deletions(-) diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/custom-agents.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/custom-agents.ipynb index a3282aaf1..67705a2cf 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/custom-agents.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/custom-agents.ipynb @@ -34,9 +34,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3...\n", + "2...\n", + "1...\n", + "Done!\n" + ] + } + ], "source": [ "from typing import AsyncGenerator, List, Sequence\n", "\n", @@ -100,12 +111,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## UserProxyAgent \n", + "## ArithmeticAgent\n", "\n", - "A common use case for building a custom agent is to create an agent that acts as a proxy for the user.\n", + "In this example, we create an agent class that can perform simple arithmetic operations\n", + "on a given integer. Then, we will use different instances of this agent class\n", + "in a {py:class}`~autogen_agentchat.teams.SelectorGroupChat`\n", + "to transform a given integer into another integer by applying a sequence of arithmetic operations.\n", "\n", - "In the example below we show how to implement a `UserProxyAgent` - an agent that asks the user to enter\n", - "some text through console and then returns that message as a response." + "The `ArithmeticAgent` class takes an `operator_func` that takes an integer and returns an integer,\n", + "after applying an arithmetic operation to the integer.\n", + "In its `on_messages` method, it applies the `operator_func` to the integer in the input message,\n", + "and returns a response with the result." ] }, { @@ -114,39 +130,162 @@ "metadata": {}, "outputs": [], "source": [ - "import asyncio\n", - "from typing import List, Sequence\n", + "from typing import Callable, List, Sequence\n", "\n", "from autogen_agentchat.agents import BaseChatAgent\n", "from autogen_agentchat.base import Response\n", + "from autogen_agentchat.conditions import MaxMessageTermination\n", "from autogen_agentchat.messages import ChatMessage\n", + "from autogen_agentchat.teams import SelectorGroupChat\n", + "from autogen_agentchat.ui import Console\n", "from autogen_core import CancellationToken\n", + "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", "\n", "\n", - "class UserProxyAgent(BaseChatAgent):\n", - " def __init__(self, name: str) -> None:\n", - " super().__init__(name, \"A human user.\")\n", + "class ArithmeticAgent(BaseChatAgent):\n", + " def __init__(self, name: str, description: str, operator_func: Callable[[int], int]) -> None:\n", + " super().__init__(name, description=description)\n", + " self._operator_func = operator_func\n", + " self._message_history: List[ChatMessage] = []\n", "\n", " @property\n", " def produced_message_types(self) -> List[type[ChatMessage]]:\n", " return [TextMessage]\n", "\n", " async def on_messages(self, messages: Sequence[ChatMessage], cancellation_token: CancellationToken) -> Response:\n", - " user_input = await asyncio.get_event_loop().run_in_executor(None, input, \"Enter your response: \")\n", - " return Response(chat_message=TextMessage(content=user_input, source=self.name))\n", + " # Update the message history.\n", + " # NOTE: it is possible the messages is an empty list, which means the agent was selected previously.\n", + " self._message_history.extend(messages)\n", + " # Parse the number in the last message.\n", + " assert isinstance(self._message_history[-1], TextMessage)\n", + " number = int(self._message_history[-1].content)\n", + " # Apply the operator function to the number.\n", + " result = self._operator_func(number)\n", + " # Create a new message with the result.\n", + " response_message = TextMessage(content=str(result), source=self.name)\n", + " # Update the message history.\n", + " self._message_history.append(response_message)\n", + " # Return the response.\n", + " return Response(chat_message=response_message)\n", "\n", " async def on_reset(self, cancellation_token: CancellationToken) -> None:\n", - " pass\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{note}\n", + "The `on_messages` method may be called with an empty list of messages, in which\n", + "case it means the agent was called previously and is now being called again,\n", + "without any new messages from the caller. So it is important to keep a history\n", + "of the previous messages received by the agent, and use that history to generate\n", + "the response.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can create a {py:class}`~autogen_agentchat.teams.SelectorGroupChat` with 5 instances of `ArithmeticAgent`:\n", + "\n", + "- one that adds 1 to the input integer,\n", + "- one that subtracts 1 from the input integer,\n", + "- one that multiplies the input integer by 2,\n", + "- one that divides the input integer by 2 and rounds down to the nearest integer, and\n", + "- one that returns the input integer unchanged.\n", + "\n", + "We then create a {py:class}`~autogen_agentchat.teams.SelectorGroupChat` with these agents,\n", + "and set the appropriate selector settings:\n", + "\n", + "- allow the same agent to be selected consecutively to allow for repeated operations, and\n", + "- customize the selector prompt to tailor the model's response to the specific task." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---------- user ----------\n", + "Apply the operations to turn the given number into 25.\n", + "---------- user ----------\n", + "10\n", + "---------- multiply_agent ----------\n", + "20\n", + "---------- add_agent ----------\n", + "21\n", + "---------- multiply_agent ----------\n", + "42\n", + "---------- divide_agent ----------\n", + "21\n", + "---------- add_agent ----------\n", + "22\n", + "---------- add_agent ----------\n", + "23\n", + "---------- add_agent ----------\n", + "24\n", + "---------- add_agent ----------\n", + "25\n", + "---------- Summary ----------\n", + "Number of messages: 10\n", + "Finish reason: Maximum number of messages 10 reached, current message count: 10\n", + "Total prompt tokens: 0\n", + "Total completion tokens: 0\n", + "Duration: 2.40 seconds\n" + ] + } + ], + "source": [ + "async def run_number_agents() -> None:\n", + " # Create agents for number operations.\n", + " add_agent = ArithmeticAgent(\"add_agent\", \"Adds 1 to the number.\", lambda x: x + 1)\n", + " multiply_agent = ArithmeticAgent(\"multiply_agent\", \"Multiplies the number by 2.\", lambda x: x * 2)\n", + " subtract_agent = ArithmeticAgent(\"subtract_agent\", \"Subtracts 1 from the number.\", lambda x: x - 1)\n", + " divide_agent = ArithmeticAgent(\"divide_agent\", \"Divides the number by 2 and rounds down.\", lambda x: x // 2)\n", + " identity_agent = ArithmeticAgent(\"identity_agent\", \"Returns the number as is.\", lambda x: x)\n", + "\n", + " # The termination condition is to stop after 10 messages.\n", + " termination_condition = MaxMessageTermination(10)\n", + "\n", + " # Create a selector group chat.\n", + " selector_group_chat = SelectorGroupChat(\n", + " [add_agent, multiply_agent, subtract_agent, divide_agent, identity_agent],\n", + " model_client=OpenAIChatCompletionClient(model=\"gpt-4o\"),\n", + " termination_condition=termination_condition,\n", + " allow_repeated_speaker=True, # Allow the same agent to speak multiple times, necessary for this task.\n", + " selector_prompt=(\n", + " \"Available roles:\\n{roles}\\nTheir job descriptions:\\n{participants}\\n\"\n", + " \"Current conversation history:\\n{history}\\n\"\n", + " \"Please select the most appropriate role for the next message, and only return the role name.\"\n", + " ),\n", + " )\n", + "\n", + " # Run the selector group chat with a given task and stream the response.\n", + " task: List[ChatMessage] = [\n", + " TextMessage(content=\"Apply the operations to turn the given number into 25.\", source=\"user\"),\n", + " TextMessage(content=\"10\", source=\"user\"),\n", + " ]\n", + " stream = selector_group_chat.run_stream(task=task)\n", + " await Console(stream)\n", "\n", "\n", - "async def run_user_proxy_agent() -> None:\n", - " user_proxy_agent = UserProxyAgent(name=\"user_proxy_agent\")\n", - " response = await user_proxy_agent.on_messages([], CancellationToken())\n", - " print(response.chat_message.content)\n", - "\n", - "\n", - "# Use asyncio.run(run_user_proxy_agent()) when running in a script.\n", - "await run_user_proxy_agent()" + "# Use asyncio.run(run_number_agents()) when running in a script.\n", + "await run_number_agents()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the output, we can see that the agents have successfully transformed the input integer\n", + "from 10 to 25 by choosing appropriate agents that apply the arithmetic operations in sequence." ] } ], @@ -157,8 +296,16 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", - "version": "3.11.5" + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/models.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/models.ipynb index 0bafc7705..3020cbe9f 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/models.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/models.ipynb @@ -15,7 +15,7 @@ "source": [ "## OpenAI\n", "\n", - "To access OpenAI models, install the `openai` extension, which allows you to use the {py:class}`~autogen_ext.models.OpenAIChatCompletionClient`." + "To access OpenAI models, install the `openai` extension, which allows you to use the {py:class}`~autogen_ext.models.openai.OpenAIChatCompletionClient`." ] }, { @@ -85,7 +85,7 @@ "source": [ "```{note}\n", "You can use this client with models hosted on OpenAI-compatible endpoints, however, we have not tested this functionality.\n", - "See {py:class}`~autogen_ext.models.OpenAIChatCompletionClient` for more information.\n", + "See {py:class}`~autogen_ext.models.openai.OpenAIChatCompletionClient` for more information.\n", "```" ] }, @@ -95,7 +95,7 @@ "source": [ "## Azure OpenAI\n", "\n", - "Similarly, install the `azure` and `openai` extensions to use the {py:class}`~autogen_ext.models.AzureOpenAIChatCompletionClient`." + "Similarly, install the `azure` and `openai` extensions to use the {py:class}`~autogen_ext.models.openai.AzureOpenAIChatCompletionClient`." ] }, { @@ -179,7 +179,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/local-llms-ollama-litellm.ipynb b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/local-llms-ollama-litellm.ipynb index 7e236d100..400985577 100644 --- a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/local-llms-ollama-litellm.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/cookbook/local-llms-ollama-litellm.ipynb @@ -15,10 +15,10 @@ "```\n", "curl -fsSL https://ollama.com/install.sh | sh\n", "\n", - "ollama pull llama3:instruct\n", + "ollama pull llama3.2:1b\n", "\n", "pip install 'litellm[proxy]'\n", - "litellm --model ollama/llama3:instruct\n", + "litellm --model ollama/llama3.2:1b\n", "``` \n", "\n", "This will run the proxy server and it will be available at 'http://0.0.0.0:4000/'." @@ -48,7 +48,7 @@ " default_subscription,\n", " message_handler,\n", ")\n", - "from autogen_core.components.model_context import BufferedChatCompletionContext\n", + "from autogen_core.model_context import BufferedChatCompletionContext\n", "from autogen_core.models import (\n", " AssistantMessage,\n", " ChatCompletionClient,\n", @@ -74,9 +74,14 @@ "def get_model_client() -> OpenAIChatCompletionClient: # type: ignore\n", " \"Mimic OpenAI API using Local LLM Server.\"\n", " return OpenAIChatCompletionClient(\n", - " model=\"gpt-4o\", # Need to use one of the OpenAI models as a placeholder for now.\n", + " model=\"llama3.2:1b\",\n", " api_key=\"NotRequiredSinceWeAreLocal\",\n", " base_url=\"http://0.0.0.0:4000\",\n", + " model_capabilities={\n", + " \"json_output\": False,\n", + " \"vision\": False,\n", + " \"function_calling\": True,\n", + " },\n", " )" ] }, @@ -225,7 +230,7 @@ ], "metadata": { "kernelspec": { - "display_name": "pyautogen", + "display_name": ".venv", "language": "python", "name": "python3" }, diff --git a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/design-patterns/sequential-workflow.svg b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/design-patterns/sequential-workflow.svg index d68612ec3..f14aa379a 100644 --- a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/design-patterns/sequential-workflow.svg +++ b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/design-patterns/sequential-workflow.svg @@ -1,3 +1,3 @@ -
Concept Extractor
Agent
Concept Extractor...
Writer Agent
Writer Agent
Format Proof
Agent
Format Proof...
User Agent
User Agent
Product
Concepts
Product...
Marketing 
Copy
Marketing...
Final 
Copy
Final...
Product Description
Product Description
\ No newline at end of file +
Concept Extractor
Agent
Concept Extractor...
Writer Agent
Writer Agent
Format Proof
Agent
Format Proof...
User Agent
User Agent
Product
Concepts
Product...
Marketing 
Copy
Marketing...
Final 
Copy
Final...
Product Description
Product Description
\ No newline at end of file