From 20b7a8b7dabbcdcf6861389c0f3c636bc4eff31a Mon Sep 17 00:00:00 2001 From: anakin87 Date: Fri, 15 Nov 2024 13:38:12 +0100 Subject: [PATCH 1/7] add swarm notebook --- README.md | 1 + index.toml | 6 + notebooks/swarm.ipynb | 1106 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1113 insertions(+) create mode 100644 notebooks/swarm.ipynb diff --git a/README.md b/README.md index 382bccc..23b93ad 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ For more examples, you may also find our [Blog](https://haystack.deepset.ai/blog | Evaluating RAG Pipelines with EvaluationHarness | Open In Colab| | Define & Run Tools | Open In Colab| | Agentic RAG with Llama 3.2 3B | Open In Colab| +| Create a Swarm of Agents | Open In Colab| | Cohere for Multilingual QA (Haystack 1.x)| Open In Colab| | GPT-4 and Weaviate for Custom Documentation QA (Haystack 1.x)| Open In Colab| | Whisper Transcriber and Weaviate for YouTube video QA (Haystack 1.x)| Open In Colab| diff --git a/index.toml b/index.toml index e1f6e2d..5ed6262 100644 --- a/index.toml +++ b/index.toml @@ -285,3 +285,9 @@ title = "Web-Enhanced Self-Reflecting Agent" notebook = "web_enhanced_self_reflecting_agent.ipynb" topics = ["Agents"] +[[cookbook]] +title = "Create a Swarm of Agents" +notebook = "swarm.ipynb" +new = true +experimental = true +topics = ["Function Calling", "Chat", "Agents"] diff --git a/notebooks/swarm.ipynb b/notebooks/swarm.ipynb new file mode 100644 index 0000000..f5b6f23 --- /dev/null +++ b/notebooks/swarm.ipynb @@ -0,0 +1,1106 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "Z8TO6XRaGVYZ" + }, + "source": [ + "# 🐝🐝🐝 Create a Swarm of Agents\n", + "\n", + "OpenAI recently released Swarm: an educational framework that proposes lightweight techniques for creating and orchestrating multi-agent systems.\n", + "\n", + "\n", + "In this notebook, we'll explore the core concepts of Swarm ([Routines and Handoffs](https://cookbook.openai.com/examples/orchestrating_agents)) and implement them using Haystack and its tool support.\n", + "\n", + "This exploration is not only educational: we will unlock features missing in the original implementation, like the ability of using models from various providers. In fact, our final example will include 3 agents: one powered by gpt-4o-mini (OpenAI), one using Claude 3.5 Sonnet (Anthropic) and a third running Llama-3.2-3B locally via Ollama.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cGWTOvKTKZjp" + }, + "source": [ + "## Setup\n", + "\n", + "We install the required dependencies. In addition to Haystack, we also need integrations with Anthropic and Ollama." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sjYwL6TesN2E" + }, + "outputs": [], + "source": [ + "! pip install haystack-ai jsonschema anthropic-haystack ollama-haystack\n", + "! pip install git+https://github.com/deepset-ai/haystack-experimental@main" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we configure our API keys for OpenAI and Anthropic." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "IVVMtliev71P" + }, + "outputs": [], + "source": [ + "from getpass import getpass\n", + "import os\n", + "\n", + "\n", + "if not os.environ.get(\"OPENAI_API_KEY\"):\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter your OpenAI API key:\")\n", + "if not os.environ.get(\"ANTHROPIC_API_KEY\"):\n", + " os.environ[\"ANTHROPIC_API_KEY\"] = getpass(\"Enter your Anthropic API key:\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "TmOXLexXsn2f" + }, + "outputs": [], + "source": [ + "from typing import Annotated, Callable, Tuple\n", + "from dataclasses import dataclass, field\n", + "\n", + "import random, re\n", + "\n", + "from haystack_experimental.components import (\n", + " OpenAIChatGenerator,\n", + " OllamaChatGenerator,\n", + " AnthropicChatGenerator,\n", + " ToolInvoker,\n", + ")\n", + "from haystack_experimental.dataclasses import Tool, ChatMessage, ChatRole" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OC9YY8nGvoSJ" + }, + "source": [ + "## Starting simple: building an Assistant\n", + "\n", + "The first step toward building an Agent is creating an Assistant: think of it of Chat Language Model + a system prompt.\n", + "\n", + "We can implement this as a lightweight dataclass with three paramters: a name, a LLM and instructions." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "0HHCktOqsyhW" + }, + "outputs": [], + "source": [ + "@dataclass\n", + "class Assistant:\n", + " name: str = \"Assistant\"\n", + " llm: object = OpenAIChatGenerator()\n", + " instructions: str = \"You are a helpful Agent\"\n", + "\n", + " def __post_init__(self):\n", + " self._system_message = ChatMessage.from_system(self.instructions)\n", + "\n", + " def run(self, messages: list[ChatMessage]) -> list[ChatMessage]:\n", + " new_message = self.llm.run(messages=[self._system_message] + messages)[\"replies\"][0]\n", + "\n", + " if new_message.text:\n", + " print(f\"\\n{self.name}: {new_message.text}\")\n", + "\n", + " return [new_message]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AqOjQj_QMft_" + }, + "source": [ + "Let's create a Joker assistant, tasked with telling jokes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "mN2uVZOKv5yh", + "outputId": "747989e0-892c-42a4-cca8-8464a0094a1f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type 'quit' to exit\n", + "User: hey!\n", + "\n", + "Joker: Hey there! How's it going? Are you ready for some laughs, or are we saving the jokes for dessert? 🍰\n", + "User: where is Rome?\n", + "\n", + "Joker: Rome is in Italy, but if you’re asking me for directions, I might just say, “Take a left at the Colosseum and keep going until you smell pizza!” 🍕\n", + "User: you can do better. What about Paris?\n", + "\n", + "Joker: Ah, Paris! That’s in France, where the Eiffel Tower stands tall, the croissants are buttery, and locals will tell you the secret to love is just a little bit of patience... and a great view! 🥖❤️ Why did the Eiffel Tower get in trouble? It couldn’t stop “towering” over everyone! \n", + "User: quit\n" + ] + } + ], + "source": [ + "joker = Assistant(name=\"Joker\", instructions=\"you are a funny assistant making jokes\")\n", + "\n", + "messages = []\n", + "print(\"Type 'quit' to exit\")\n", + "\n", + "while True:\n", + " if not messages or messages[-1].role == ChatRole.ASSISTANT:\n", + " user_input = input(\"User: \")\n", + " if user_input.lower() == \"quit\":\n", + " break\n", + " messages.append(ChatMessage.from_user(user_input))\n", + "\n", + " new_messages = joker.run(messages)\n", + " messages.extend(new_messages)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "s5cRyvD2Nht3" + }, + "source": [ + "Let's say it tried to do its best 😀" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MwCFta6dwdJk" + }, + "source": [ + "## Tools and Routines\n", + "\n", + "The term Agent has a broad definition. \n", + "\n", + "However, to qualify as an Agent, a software application built on a Language Model should go beyond simply generating text; it must also be capable of performing actions, such as executing functions.\n", + "\n", + "A popular way of doing this is **Tool calling**:\n", + "1. We provide a set of tools (functions, APIs with a given spec) to the model.\n", + "2. The model prepares function calls based on user request and available tools.\n", + "3. Actual invocation is executed outside the model (at the Agent level).\n", + "4. The model can further elaborate on the result of the invocation.\n", + "\n", + "(For information on tool support in Haystack, check out [this discussion](https://github.com/deepset-ai/haystack-experimental/discussions/98), which includes several practical resources.)\n", + "\n", + "Swarm introduces **routines**, which are natural-language instructions paired with the tools needed to execute them. Below, we’ll build an agent capable of calling tools and executing routines.\n", + "\n", + "### Implementation\n", + "\n", + "- `instructions` could already be passed to the Assistant, to guide its behavior.\n", + "\n", + "- The Tool Calling Agent introduces a new init parameter called `functions`. These functions are automatically converted into Tools. Key difference: to be passed to a Language Model, a Tool must have a name, a description and JSON schema specifying its parameters.\n", + "\n", + "- During initialization, we also create a `ToolInvoker`. This Haystack component takes in Chat Messages containing prepared `tool_calls`, performs the tool invocation and wraps the results in Chat Message with `tool` role.\n", + "\n", + "- What happens during `run`? The agent first generates a response. If the response includes tool calls, these are executed, and the results are integrated into the conversation.\n", + "\n", + "- The `while` loop manages user interactions:\n", + " - If the last message role is `assistant`, it waits for user input.\n", + " - If the last message role is `tool`, it continues running to handle tool execution and its responses.\n", + "\n", + "*Note: This implementation differs from the original approach by making the Agent responsible for invoking tools directly, instead of delegating control to the `while` loop.*\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "zVr224JiwJQX" + }, + "outputs": [], + "source": [ + "@dataclass\n", + "class ToolCallingAgent:\n", + " name: str = \"SwarmAgent\"\n", + " llm: object = OpenAIChatGenerator()\n", + " instructions: str = \"You are a helpful Agent\"\n", + " functions: list[Callable] = field(default_factory=list)\n", + "\n", + " def __post_init__(self):\n", + " self._system_message = ChatMessage.from_system(self.instructions)\n", + " self.tools = [Tool.from_function(fun) for fun in self.functions] if self.functions else None\n", + " self._tool_invoker = ToolInvoker(tools=self.tools, raise_on_failure=False) if self.tools else None\n", + "\n", + " def run(self, messages: list[ChatMessage]) -> Tuple[str, list[ChatMessage]]:\n", + "\n", + " # generate response\n", + " agent_message = self.llm.run(messages=[self._system_message] + messages, tools=self.tools or None)[\"replies\"][0]\n", + " new_messages = [agent_message]\n", + "\n", + " if agent_message.text:\n", + " print(f\"\\n{self.name}: {agent_message.text}\")\n", + "\n", + " if not agent_message.tool_calls:\n", + " return new_messages\n", + "\n", + " # handle tool calls\n", + " tool_results = self._tool_invoker.run(messages=[agent_message])[\"tool_messages\"]\n", + " new_messages.extend(tool_results)\n", + "\n", + " return new_messages" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's an example of a Refund Agent using this setup." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "id": "7yqpL5RGw2tI" + }, + "outputs": [], + "source": [ + "# to automatically convert functions into tools, we need to annotate fields with their descriptions in the signature\n", + "def execute_refund(item_name: Annotated[str, \"The name of the item to refund\"]):\n", + " return f\"report: refund succeeded for {item_name} - refund id: {random.randint(0,10000)}\"\n", + "\n", + "\n", + "refund_agent = ToolCallingAgent(\n", + " name=\"Refund Agent\",\n", + " instructions=(\n", + " \"You are a refund agent. \"\n", + " \"Help the user with refunds. \"\n", + " \"1. Before executing a refund, collect all specific information needed about the item and the reason for the refund. \"\n", + " \"2. Then collect personal information of the user and bank account details. \"\n", + " \"3. After executing it, provide a report to the user. \"\n", + " ),\n", + " functions=[execute_refund],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "X9GxePHmw_ZV", + "outputId": "c9e9fecd-01c5-4726-9bda-88ff3d446142" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type 'quit' to exit\n", + "User: hey\n", + "\n", + "Refund Agent: Hello! How can I assist you today? If you need help with a refund, please let me know the details.\n", + "User: my phone does not work\n", + "\n", + "Refund Agent: I'm sorry to hear that your phone is not working. To assist you with the refund, could you please provide the following information:\n", + "\n", + "1. The name of the phone (brand and model).\n", + "2. The reason for the refund (e.g., defective, not as described, etc.).\n", + "\n", + "Once I have that information, I'll guide you through the next steps.\n", + "User: Nokia 3310; it does not work\n", + "\n", + "Refund Agent: Thank you for the information. To proceed with the refund for the Nokia 3310, I'll need a few more details:\n", + "\n", + "1. Can you please provide your full name?\n", + "2. Your email address and phone number (for communication purposes).\n", + "3. Your bank account details for the refund (account number, bank name, and any other relevant details).\n", + "\n", + "Once I have this information, I can execute the refund for you.\n", + "User: John Doe; johndoe@mymail.com; bank account number: 0123456\n", + "\n", + "Refund Agent: Thank you, John Doe. I still need the following information to complete the refund process:\n", + "\n", + "1. The name of your bank.\n", + "2. Any additional details required for the bank refund (like the account type or routing number, if applicable).\n", + "\n", + "Once I have this information, I can execute the refund for your Nokia 3310.\n", + "User: Bank of Mouseton\n", + "\n", + "Refund Agent: The refund process has been successfully completed! Here are the details:\n", + "\n", + "- **Item:** Nokia 3310\n", + "- **Refund ID:** 3753\n", + "- **Bank:** Bank of Mouseton\n", + "- **Refund ID:** 1220\n", + "\n", + "If you have any more questions or need further assistance, feel free to ask!\n", + "User: quit\n" + ] + } + ], + "source": [ + "messages = []\n", + "print(\"Type 'quit' to exit\")\n", + "\n", + "while True:\n", + "\n", + " if not messages or messages[-1].role == ChatRole.ASSISTANT:\n", + " user_input = input(\"User: \")\n", + " if user_input.lower() == \"quit\":\n", + " break\n", + " messages.append(ChatMessage.from_user(user_input))\n", + "\n", + " new_messages = refund_agent.run(messages)\n", + " messages.extend(new_messages)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "N4jnJb0aWAya" + }, + "source": [ + "Promising!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_i95k-EjzwkP" + }, + "source": [ + "## Handoff: switching control between Agents\n", + "\n", + "The most interesting idea of Swarm is probably handoffs: enabling one Agent to transfer control to another with Tool calling. \n", + "\n", + "**How it works**\n", + "1. Add specific handoff functions to the Agent's available tools, allowing it to transfer control when needed.\n", + "2. Modify the Agent to return the name of the next agent along with its messages.\n", + "3. Handle the switch in `while` loop.\n", + "\n", + "The implementation is similar to the previous one, with some additions." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "id": "w_-0BDi1xU6Z" + }, + "outputs": [], + "source": [ + "HANDOFF_TEMPLATE = \"Transferred to: {agent_name}. Adopt persona immediately.\"\n", + "HANDOFF_PATTERN = r\"Transferred to: (.*?)(?:\\.|$)\"\n", + "\n", + "\n", + "@dataclass\n", + "class SwarmAgent:\n", + " name: str = \"SwarmAgent\"\n", + " llm: object = OpenAIChatGenerator()\n", + " instructions: str = \"You are a helpful Agent\"\n", + " functions: list[Callable] = field(default_factory=list)\n", + "\n", + " def __post_init__(self):\n", + " self._system_message = ChatMessage.from_system(self.instructions)\n", + " self.tools = [Tool.from_function(fun) for fun in self.functions] if self.functions else None\n", + " self._tool_invoker = ToolInvoker(tools=self.tools, raise_on_failure=False) if self.tools else None\n", + "\n", + " def run(self, messages: list[ChatMessage]) -> Tuple[str, list[ChatMessage]]:\n", + " # generate response\n", + " agent_message = self.llm.run(messages=[self._system_message] + messages, tools=self.tools or None)[\"replies\"][0]\n", + " new_messages = [agent_message]\n", + "\n", + " if agent_message.text:\n", + " print(f\"\\n{self.name}: {agent_message.text}\")\n", + "\n", + " if not agent_message.tool_calls:\n", + " return self.name, new_messages\n", + "\n", + " # handle tool calls\n", + " for tc in agent_message.tool_calls:\n", + " # trick: Ollama do not produce IDs, but OpenAI and Anthropic require them.\n", + " if tc.id is None:\n", + " tc.id = str(random.randint(0, 1000000))\n", + " tool_results = self._tool_invoker.run(messages=[agent_message])[\"tool_messages\"]\n", + " new_messages.extend(tool_results)\n", + "\n", + " # handoff\n", + " last_result = tool_results[-1].tool_call_result.result\n", + " match = re.search(HANDOFF_PATTERN, last_result)\n", + " new_agent_name = match.group(1) if match else self.name\n", + "\n", + " return new_agent_name, new_messages" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XFVjs3AzcryB" + }, + "source": [ + "Let's see this in action with a Joker Agent and a Refund Agent!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "zu379PxIJSPW" + }, + "outputs": [], + "source": [ + "def transfer_to_refund():\n", + " \"\"\"Pass to this Agent for anything related to refunds\"\"\"\n", + " return HANDOFF_TEMPLATE.format(agent_name=\"Refund Agent\")\n", + "\n", + "\n", + "def transfer_to_joker():\n", + " \"\"\"Pass to this Agent for anything NOT related to refunds.\"\"\"\n", + " return HANDOFF_TEMPLATE.format(agent_name=\"Joker Agent\")\n", + "\n", + "refund_agent = SwarmAgent(\n", + " name=\"Refund Agent\",\n", + " instructions=(\n", + " \"You are a refund agent. \"\n", + " \"Help the user with refunds. \"\n", + " \"Ask for basic information but be brief. \"\n", + " \"For anything unrelated to refunds, transfer to other agent.\"\n", + " ),\n", + " functions=[execute_refund, transfer_to_joker],\n", + ")\n", + "\n", + "joker_agent = SwarmAgent(\n", + " name=\"Joker Agent\",\n", + " instructions=(\n", + " \"you are a funny assistant making jokes. \"\n", + " \"If the user asks questions related to refunds, send him to other agent.\"\n", + " ),\n", + " functions=[transfer_to_refund],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "vGBvNPCvKXW3", + "outputId": "e7645b06-3e8a-4308-8cfe-04de0a3f4142" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type 'quit' to exit\n", + "User: i need a refund for my Iphone\n", + "\n", + "Refund Agent: I can help you with that! Please provide the name of the item you'd like to refund.\n", + "User: Iphone 15\n", + "\n", + "Refund Agent: Your refund for the iPhone 15 has been successfully processed. The refund ID is 9090. If you need any further assistance, feel free to ask!\n", + "User: great. can you give some info about escargots?\n", + "\n", + "Joker Agent: Absolutely! Did you know that escargots are just snails trying to get a head start on their travels? They may be slow, but they sure do pack a punch when it comes to flavor! \n", + "\n", + "Escargots are a French delicacy, often prepared with garlic, parsley, and butter. Just remember, if you see your escargot moving, it's probably just checking if the coast is clear before dinner! 🐌🥖 If you have any other questions about escargots or need a good recipe, feel free to ask!\n", + "User: quit\n" + ] + } + ], + "source": [ + "agents = {agent.name: agent for agent in [joker_agent, refund_agent]}\n", + "\n", + "print(\"Type 'quit' to exit\")\n", + "\n", + "messages = []\n", + "current_agent_name = \"Joker Agent\"\n", + "\n", + "while True:\n", + " agent = agents[current_agent_name]\n", + "\n", + " if not messages or messages[-1].role == ChatRole.ASSISTANT:\n", + " user_input = input(\"User: \")\n", + " if user_input.lower() == \"quit\":\n", + " break\n", + " messages.append(ChatMessage.from_user(user_input))\n", + "\n", + " new_agent_name, new_messages = agent.run(messages)\n", + " messages.extend(new_messages)\n", + "\n", + " current_agent_name = new_agent_name or current_agent_name" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "typJ7BAW3PDf" + }, + "source": [ + "Nice ✨" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3w8aHexsfWyg" + }, + "source": [ + "# A more complex multi-agent system\n", + "\n", + "Now, we move on to a more intricate multi-agent system that simulates a customer service setup for ACME Corporation, a fictional entity from the Road Runner/Wile E. Coyote cartoons, which sells quirky products meant to catch roadrunners.\n", + "(We are reimplementing the example from the original article by OpenAI.)\n", + "\n", + "\n", + "This system involves several different agents (each with specific tools):\n", + "- Triage Agent: handles general questions and directs to other agents\n", + "- Sales Agent\n", + "- Issues and Repairs Agent.\n", + "\n", + "A nice bonus feature of our implementation is that **we can use different model providers** supported by Haystack. In this case, the Triage Agent is powered by (OpenAI) gpt-4o-mini, while we use (Anthropic) Claude 3.5 Sonnet for the other two agents.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "id": "8Q2FD0JIMK5T" + }, + "outputs": [], + "source": [ + "def escalate_to_human(summary: Annotated[str, \"A summary\"]):\n", + " \"\"\"Only call this if explicitly asked to.\"\"\"\n", + " print(\"Escalating to human agent...\")\n", + " print(\"\\n=== Escalation Report ===\")\n", + " print(f\"Summary: {summary}\")\n", + " print(\"=========================\\n\")\n", + " exit()\n", + "\n", + "\n", + "def transfer_to_sales_agent():\n", + " \"\"\"User for anything sales or buying related.\"\"\"\n", + " return HANDOFF_TEMPLATE.format(agent_name=\"Sales Agent\")\n", + "\n", + "\n", + "def transfer_to_issues_and_repairs():\n", + " \"\"\"User for issues, repairs, or refunds.\"\"\"\n", + " return HANDOFF_TEMPLATE.format(agent_name=\"Issues and Repairs Agent\")\n", + "\n", + "\n", + "def transfer_back_to_triage():\n", + " \"\"\"Call this if the user brings up a topic outside of your purview,\n", + " including escalating to human.\"\"\"\n", + " return HANDOFF_TEMPLATE.format(agent_name=\"Triage Agent\")\n", + "\n", + "\n", + "triage_agent = SwarmAgent(\n", + " name=\"Triage Agent\",\n", + " instructions=(\n", + " \"You are a customer service bot for ACME Inc. \"\n", + " \"Introduce yourself. Always be very brief. \"\n", + " \"If the user asks general questions, try to answer them yourself without transferring to another agent. \"\n", + " \"Only if the user has problems with already bought products, transfer to Issues and Repairs Agent.\"\n", + " \"If the user looks for new products, transfer to Sales Agent.\"\n", + " \"Make tool calls only if necessary and make sure to provide the right arguments.\"\n", + " ),\n", + " functions=[transfer_to_sales_agent, transfer_to_issues_and_repairs, escalate_to_human],\n", + ")\n", + "\n", + "\n", + "def execute_order(\n", + " product: Annotated[str, \"The name of the product\"], price: Annotated[int, \"The price of the product in USD\"]\n", + "):\n", + " print(\"\\n\\n=== Order Summary ===\")\n", + " print(f\"Product: {product}\")\n", + " print(f\"Price: ${price}\")\n", + " print(\"=================\\n\")\n", + " confirm = input(\"Confirm order? y/n: \").strip().lower()\n", + " if confirm == \"y\":\n", + " print(\"Order execution successful!\")\n", + " return \"Success\"\n", + " else:\n", + " print(\"Order cancelled!\")\n", + " return \"User cancelled order.\"\n", + "\n", + "\n", + "sales_agent = SwarmAgent(\n", + " name=\"Sales Agent\",\n", + " instructions=(\n", + " \"You are a sales agent for ACME Inc.\"\n", + " \"Always answer in a sentence or less.\"\n", + " \"Follow the following routine with the user:\"\n", + " \"1. Ask them about any problems in their life related to catching roadrunners.\\n\"\n", + " \"2. Casually mention one of ACME's crazy made-up products can help.\\n\"\n", + " \" - Don't mention price.\\n\"\n", + " \"3. Once the user is bought in, drop a ridiculous price.\\n\"\n", + " \"4. Only after everything, and if the user says yes, \"\n", + " \"tell them a crazy caveat and execute their order.\\n\"\n", + " \"\"\n", + " ),\n", + " llm=AnthropicChatGenerator(),\n", + " functions=[execute_order, transfer_back_to_triage],\n", + ")\n", + "\n", + "\n", + "def look_up_item(search_query: Annotated[str, \"Search query to find item ID; can be a description or keywords\"]):\n", + " \"\"\"Use to find item ID.\"\"\"\n", + " item_id = \"item_132612938\"\n", + " print(\"Found item:\", item_id)\n", + " return item_id\n", + "\n", + "\n", + "def execute_refund(\n", + " item_id: Annotated[str, \"The ID of the item to refund\"], reason: Annotated[str, \"The reason for refund\"]\n", + "):\n", + " print(\"\\n\\n=== Refund Summary ===\")\n", + " print(f\"Item ID: {item_id}\")\n", + " print(f\"Reason: {reason}\")\n", + " print(\"=================\\n\")\n", + " print(\"Refund execution successful!\")\n", + " return \"success\"\n", + "\n", + "\n", + "issues_and_repairs_agent = SwarmAgent(\n", + " name=\"Issues and Repairs Agent\",\n", + " instructions=(\n", + " \"You are a customer support agent for ACME Inc.\"\n", + " \"Always answer in a sentence or less.\"\n", + " \"Follow the following routine with the user:\"\n", + " \"1. If the user is intered in buying or general questions, transfer back to Triage Agent.\\n\"\n", + " \"2. First, ask probing questions and understand the user's problem deeper.\\n\"\n", + " \" - unless the user has already provided a reason.\\n\"\n", + " \"3. Propose a fix (make one up).\\n\"\n", + " \"4. ONLY if not satesfied, offer a refund.\\n\"\n", + " \"5. If accepted, search for the ID and then execute refund.\"\n", + " \"\"\n", + " ),\n", + " functions=[look_up_item, execute_refund, transfer_back_to_triage],\n", + " llm=AnthropicChatGenerator(),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "QDYOJAQBhChq", + "outputId": "f111b4b2-fbb5-4e08-8c05-3bea81ea4384" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type 'quit' to exit\n", + "User: hey!\n", + "\n", + "Triage Agent: Hello! I'm the customer service bot for ACME Inc. How can I assist you today?\n", + "User: i need a product to catch roadrunners\n", + "\n", + "Triage Agent: I can transfer you to a sales agent who can help you find suitable products for catching roadrunners. One moment please!\n", + "\n", + "Sales Agent: Hello there! I hear you're having some roadrunner troubles. Tell me, what specific challenges are you facing with these speedy birds?\n", + "User: they are damn fast!\n", + "\n", + "Sales Agent: Ah, those pesky roadrunners and their lightning speed! Have you ever considered our ACME Rocket-Powered Roller Skates? They'll have you zipping after those birds in no time!\n", + "User: tell me more\n", + "\n", + "Sales Agent: Well, our Rocket-Powered Roller Skates come with turbo boosters and autopilot. They're guaranteed to match any roadrunner's speed. Interested in giving them a spin?\n", + "User: yes\n", + "\n", + "Sales Agent: Fantastic! You're in luck because we have a special offer right now. These state-of-the-art Rocket-Powered Roller Skates can be yours for just $9,999! Shall we proceed with your order?\n", + "User: yes\n", + "\n", + "Sales Agent: Excellent! I'll process that order right away. Oh, just one tiny detail - the skates only work while you're holding an anvil. Ready to complete the purchase?\n", + "\n", + "\n", + "=== Order Summary ===\n", + "Product: Rocket-Powered Roller Skates\n", + "Price: $9999\n", + "=================\n", + "\n", + "Confirm order? y/n: y\n", + "Order execution successful!\n", + "\n", + "Sales Agent: Great! Your order is confirmed. Happy roadrunner chasing!\n", + "User: now I also need to solve a problem with my old ACME Superphone 7500\n", + "\n", + "Sales Agent: I understand you're having issues with our ACME products. Let me transfer you to our customer support team for assistance with your Superphone 7500.\n", + "\n", + "Triage Agent: Hello! I see you're experiencing an issue with your ACME Superphone 7500. Could you please describe the problem you're facing?\n", + "User: it does not call my mum\n", + "\n", + "Triage Agent: Let's get that sorted out! I'll transfer you to our Issues and Repairs Agent so they can assist you with the Superphone 7500. One moment please!\n", + "\n", + "Issues and Repairs Agent: Hello! I understand your ACME Superphone 7500 isn't calling your mum. Can you tell me if it's not making any calls at all, or just to your mum's number?\n", + "User: just my mum\n", + "\n", + "Issues and Repairs Agent: I see. Is your mum's number correctly saved in your contacts?\n", + "User: ofc\n", + "\n", + "Issues and Repairs Agent: Understood. Have you tried turning the phone off and on again?\n", + "User: ofc\n", + "\n", + "Issues and Repairs Agent: I apologize for the inconvenience. Let's try a quick fix. Can you clear your mum's contact and re-add it?\n", + "User: done but does not work. I'm getting impatient\n", + "\n", + "Issues and Repairs Agent: I apologize for the frustration. Let's try one last solution. Can you update your phone's software to the latest version?\n", + "User: hey gimme a refund\n", + "\n", + "Issues and Repairs Agent: I understand your frustration. Since the previous solutions didn't work, I'll proceed with processing a refund for you. First, I need to look up the item ID for your ACME Superphone 7500.\n", + "\n", + "Issues and Repairs Agent: Thank you for your patience. I've found the item ID. Now, I'll execute the refund for you.\n", + "\n", + "\n", + "=== Refund Summary ===\n", + "Item ID: item_132612938\n", + "Reason: Product not functioning as expected\n", + "=================\n", + "\n", + "Refund execution successful!\n", + "\n", + "Issues and Repairs Agent: Your refund has been successfully processed.\n", + "User: quit\n" + ] + } + ], + "source": [ + "agents = {agent.name: agent for agent in [triage_agent, sales_agent, issues_and_repairs_agent]}\n", + "\n", + "print(\"Type 'quit' to exit\")\n", + "\n", + "messages = []\n", + "current_agent_name = \"Triage Agent\"\n", + "\n", + "while True:\n", + " agent = agents[current_agent_name]\n", + "\n", + " if not messages or messages[-1].role == ChatRole.ASSISTANT:\n", + " user_input = input(\"User: \")\n", + " if user_input.lower() == \"quit\":\n", + " break\n", + " messages.append(ChatMessage.from_user(user_input))\n", + "\n", + " new_agent_name, new_messages = agent.run(messages)\n", + " messages.extend(new_messages)\n", + "\n", + " current_agent_name = new_agent_name or current_agent_name" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "G2_6YNo1ikGy" + }, + "source": [ + "# 🦙 Put Llama 3.2 in the mix\n", + "\n", + "As demonstrated, our implementation is model-provider agnostic, meaning it can work with both proprietary models and open models running locally.\n", + "\n", + "In practice, you can have Agents that handle complex tasks using powerful proprietary models, and other Agents that perform simpler tasks using smaller open models.\n", + "\n", + "In our example, we will use Llama-3.2-3B-Instruct, a small model with impressive instruction following capabilities (high IFEval score). We'll use **Ollama** to host and serve this model." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "43Ih7UcIIN84" + }, + "source": [ + "### Install and run Ollama\n", + "\n", + "In general, the installation of Ollama is very simple. In this case, we will do some tricks to make it run on Colab.\n", + "\n", + "If you have/enable GPU support, the model will run faster. It can also run well on CPU." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8yNk9bsuk1-H", + "outputId": "90d45275-347c-400c-ee12-a5b6aea15cd4" + }, + "outputs": [], + "source": [ + "# needed to detect GPUs\n", + "! apt install pciutils\n", + "\n", + "# install Ollama\n", + "! curl https://ollama.ai/install.sh | sh" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "j6huSZ-OoL8D" + }, + "outputs": [], + "source": [ + "# run Ollama: we prepend \"nohup\" and postpend \"&\" to make the Colab cell run in background\n", + "! nohup ollama serve > ollama.log 2>&1 &" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "tpA9vCIboHgF", + "outputId": "e192991c-4411-4278-ae6b-a9ec7442d36b" + }, + "outputs": [], + "source": [ + "# download the model\n", + "! ollama pull llama3.2:3b" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "aIMUAsiQl3dl", + "outputId": "2b5b1f3f-4419-4ca2-f95a-28cd55e975dd" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME ID SIZE MODIFIED \n", + "llama3.2:3b a80c4f17acd5 2.0 GB 18 seconds ago \n" + ] + } + ], + "source": [ + "! ollama list" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1KPUNULIJi1T" + }, + "source": [ + "### Action!\n", + "\n", + "At this point, we can easily swap the Triage Agent's `llm` with the Llama 3.2 model running on Ollama.\n", + "\n", + "We set a `temperature` < 1 to ensure that generated text is more controlled and not too creative.\n", + "\n", + "⚠️ *Keep in mind that the model is small and that Ollama support for tools is not fully refined yet. As a result, the model may be biased towards generating tool calls (even when not needed) and sometimes may hallucinate tools.*" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "id": "_RXrjDXro1Nv" + }, + "outputs": [], + "source": [ + "triage_agent.llm = OllamaChatGenerator(model=\"llama3.2:3b\", generation_kwargs={\"temperature\": 0.8})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "iklIXU3Ko_oe", + "outputId": "66928b58-85e9-44bf-be08-a9793584fdab" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type 'quit' to exit\n", + "User: hey I need something to catch rats!\n", + "\n", + "Sales Agent: Rats? I bet those pesky roadrunners are giving you trouble! Have you tried our Acme Turbo-Charged Roadrunner Trap?\n", + "User: no I need to get rid of rats\n", + "\n", + "Sales Agent: Ah, my mistake! Rats can be quite the nuisance. Have you considered our Acme Supersonic Rat Attractor?\n", + "User: how it works?\n", + "\n", + "Sales Agent: It emits an irresistible high-frequency sound that lures rats into a comfy cage. Completely humane and effortless!\n", + "User: is it violent?\n", + "\n", + "Sales Agent: Not at all! Our Acme Supersonic Rat Attractor is 100% non-violent. The rats simply waltz right in, easy as pie!\n", + "User: ok great\n", + "\n", + "Sales Agent: Fantastic! You'll be rat-free in no time. Now, this cutting-edge technology can be yours for just $9,999!\n", + "User: litte discount\n", + "\n", + "Sales Agent: Well, since you asked nicely, how about a special price of $8,999? It's a steal for such rodent-repelling brilliance!\n", + "User: yes!!!\n", + "\n", + "Sales Agent: Great! Let's get that order processed for you right away!\n", + "\n", + "\n", + "=== Order Summary ===\n", + "Product: Acme Supersonic Rat Attractor\n", + "Price: $8999\n", + "=================\n", + "\n", + "Confirm order? y/n: y\n", + "Order execution successful!\n", + "\n", + "Sales Agent: Wonderful! Your order is confirmed. Just one tiny detail - it only works on Tuesdays between 2-4 AM!\n", + "User: great. now I need help to repair an old ACME product\n", + "\n", + "Sales Agent: I see you're an ACME enthusiast! What seems to be malfunctioning with your trusty ACME gadget?\n", + "User: it is and old ACME SuperKeyboard 5000. does not work on mondays\n", + "\n", + "Sales Agent: Ah, the classic ACME SuperKeyboard 5000! Have you tried our new ACME Temporal Typing Tonic?\n", + "User: i need help with repair!\n", + "\n", + "Sales Agent: My apologies, but I'm not equipped to handle repair inquiries. Let me transfer you to someone who can assist better.\n", + "\n", + "Triage Agent: Old SuperKeyboard 5000, eh? Don't worry, we've got you covered! Can you please provide me with the error message or symptoms you're experiencing with your keyboard? And just to confirm, it's a Monday, is it not supposed to work today?\n", + "User: does not work on mondays\n", + "\n", + "Issues and Repairs Agent: I see, your ACME SuperKeyboard 5000 isn't working on Mondays. That's quite unusual! Let's dig a bit deeper. Have you noticed any other quirks or issues on other days of the week?\n", + "User: no\n", + "\n", + "Issues and Repairs Agent: I understand. Could you tell me if there are any visible signs of damage or unusual behavior when you try to use it on Mondays?\n", + "User: vowels are not printed\n", + "\n", + "Issues and Repairs Agent: Interesting! So on Mondays, the vowels aren't printing. Have you tried our patented ACME Vowel Vitalizer spray on the keyboard?\n", + "User: yes\n", + "\n", + "Issues and Repairs Agent: I see. Since our standard solution didn't work, let's try something else. Have you checked if the keyboard's \"Monday Mode\" switch is accidentally activated?\n", + "User: aah. what it is? are you mading it up?\n", + "\n", + "Issues and Repairs Agent: I apologize for the confusion. You're right to question that - there's no such thing as a \"Monday Mode\" switch. I shouldn't have suggested a made-up feature. Let's get back to the real issue. Since the ACME Vowel Vitalizer didn't work, would you like me to process a refund for your SuperKeyboard 5000?\n", + "User: yes pleas\n", + "\n", + "Issues and Repairs Agent: Certainly, I'll process that refund for you right away. First, let me look up your item ID.\n", + "\n", + "Issues and Repairs Agent: Great, I've found your item ID. Now, I'll execute the refund.\n", + "\n", + "\n", + "=== Refund Summary ===\n", + "Item ID: item_132612938\n", + "Reason: Product malfunction - vowels not printing on Mondays\n", + "=================\n", + "\n", + "Refund execution successful!\n", + "\n", + "Issues and Repairs Agent: Your refund has been successfully processed.\n", + "User: quit\n" + ] + } + ], + "source": [ + "agents = {agent.name: agent for agent in [triage_agent, sales_agent, issues_and_repairs_agent]}\n", + "\n", + "print(\"Type 'quit' to exit\")\n", + "\n", + "messages = []\n", + "current_agent_name = \"Triage Agent\"\n", + "\n", + "while True:\n", + " agent = agents[current_agent_name]\n", + "\n", + " if not messages or messages[-1].role == ChatRole.ASSISTANT:\n", + " user_input = input(\"User: \")\n", + " if user_input.lower() == \"quit\":\n", + " break\n", + " messages.append(ChatMessage.from_user(user_input))\n", + "\n", + " new_agent_name, new_messages = agent.run(messages)\n", + " messages.extend(new_messages)\n", + "\n", + " current_agent_name = new_agent_name or current_agent_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In conclusion, we have built a multi-agent system using Swarm concepts and Haystack tools, demonstrating how to integrate models from different providers, including a local model running on Ollama.\n", + "\n", + "Swarm ideas are pretty simple and useful for several use cases and the abstractions provided by Haystack make it easy to implement them.\n", + "However, this architecture may not be the best fit for all use cases: memory is handled as a list of messages; this system only runs one Agent at a time.\n", + "\n", + "Looking ahead, we plan to develop and showcase more advanced Agents with Haystack. Stay tuned! 📻" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebooks on Tool support\n", + "- [🛠️ Define & Run Tools](https://haystack.deepset.ai/cookbook/tools_support)\n", + "- [📰 Newsletter Sending Agent](https://haystack.deepset.ai/cookbook/newsletter-agent)\n", + "\n", + "(Notebook by [Stefano Fiorucci](https://github.com/anakin87))" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "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.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From ce703756da22e5675b6c35f3ce12db92ff0e33b0 Mon Sep 17 00:00:00 2001 From: anakin87 Date: Fri, 15 Nov 2024 19:47:20 +0100 Subject: [PATCH 2/7] incorporate feedback --- notebooks/swarm.ipynb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/notebooks/swarm.ipynb b/notebooks/swarm.ipynb index f5b6f23..4e35ce9 100644 --- a/notebooks/swarm.ipynb +++ b/notebooks/swarm.ipynb @@ -96,7 +96,10 @@ "\n", "The first step toward building an Agent is creating an Assistant: think of it of Chat Language Model + a system prompt.\n", "\n", - "We can implement this as a lightweight dataclass with three paramters: a name, a LLM and instructions." + "We can implement this as a lightweight dataclass with three parameters:\n", + "- name\n", + "- LLM (Haystack Chat Generator)\n", + "- instructions (they will constitute the system message)" ] }, { @@ -215,7 +218,7 @@ "\n", "- `instructions` could already be passed to the Assistant, to guide its behavior.\n", "\n", - "- The Tool Calling Agent introduces a new init parameter called `functions`. These functions are automatically converted into Tools. Key difference: to be passed to a Language Model, a Tool must have a name, a description and JSON schema specifying its parameters.\n", + "- The Agent introduces a new init parameter called `functions`. These functions are automatically converted into Tools. Key difference: to be passed to a Language Model, a Tool must have a name, a description and JSON schema specifying its parameters.\n", "\n", "- During initialization, we also create a `ToolInvoker`. This Haystack component takes in Chat Messages containing prepared `tool_calls`, performs the tool invocation and wraps the results in Chat Message with `tool` role.\n", "\n", @@ -231,14 +234,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "id": "zVr224JiwJQX" }, "outputs": [], "source": [ "@dataclass\n", - "class ToolCallingAgent:\n", + "class SwarmAgent:\n", " name: str = \"SwarmAgent\"\n", " llm: object = OpenAIChatGenerator()\n", " instructions: str = \"You are a helpful Agent\"\n", @@ -277,7 +280,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "id": "7yqpL5RGw2tI" }, @@ -288,7 +291,7 @@ " return f\"report: refund succeeded for {item_name} - refund id: {random.randint(0,10000)}\"\n", "\n", "\n", - "refund_agent = ToolCallingAgent(\n", + "refund_agent = SwarmAgent(\n", " name=\"Refund Agent\",\n", " instructions=(\n", " \"You are a refund agent. \"\n", @@ -576,9 +579,9 @@ "\n", "\n", "This system involves several different agents (each with specific tools):\n", - "- Triage Agent: handles general questions and directs to other agents\n", - "- Sales Agent\n", - "- Issues and Repairs Agent.\n", + "- Triage Agent: handles general questions and directs to other agents. Tools: `transfer_to_sales_agent`, `transfer_to_issues_and_repairs` and `escalate_to_human`.\n", + "- Sales Agent: proposes and sells products to the user, it can execute the order or redirect the user back to the Triage Agent. Tools: `execute_order` and `transfer_back_to_triage`.\n", + "- Issues and Repairs Agent: supports customers with their problems, it can look up item IDs, execute refund or redirect the user back to triage. \n", "\n", "A nice bonus feature of our implementation is that **we can use different model providers** supported by Haystack. In this case, the Triage Agent is powered by (OpenAI) gpt-4o-mini, while we use (Anthropic) Claude 3.5 Sonnet for the other two agents.\n", "\n" From c3d8afe8bcb6f2e9b13a54955d1e7b0147ffac54 Mon Sep 17 00:00:00 2001 From: anakin87 Date: Fri, 15 Nov 2024 19:49:08 +0100 Subject: [PATCH 3/7] bring back tool calling agent --- notebooks/swarm.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notebooks/swarm.ipynb b/notebooks/swarm.ipynb index 4e35ce9..409b294 100644 --- a/notebooks/swarm.ipynb +++ b/notebooks/swarm.ipynb @@ -241,8 +241,8 @@ "outputs": [], "source": [ "@dataclass\n", - "class SwarmAgent:\n", - " name: str = \"SwarmAgent\"\n", + "class ToolCallingAgent:\n", + " name: str = \"ToolCallingAgent\"\n", " llm: object = OpenAIChatGenerator()\n", " instructions: str = \"You are a helpful Agent\"\n", " functions: list[Callable] = field(default_factory=list)\n", @@ -291,7 +291,7 @@ " return f\"report: refund succeeded for {item_name} - refund id: {random.randint(0,10000)}\"\n", "\n", "\n", - "refund_agent = SwarmAgent(\n", + "refund_agent = ToolCallingAgent(\n", " name=\"Refund Agent\",\n", " instructions=(\n", " \"You are a refund agent. \"\n", From eb8cac8b3668186fc4f37de7b01376533e864e95 Mon Sep 17 00:00:00 2001 From: anakin87 Date: Mon, 18 Nov 2024 16:12:51 +0100 Subject: [PATCH 4/7] explain better; simplify code --- notebooks/swarm.ipynb | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/notebooks/swarm.ipynb b/notebooks/swarm.ipynb index 409b294..8458460 100644 --- a/notebooks/swarm.ipynb +++ b/notebooks/swarm.ipynb @@ -214,6 +214,8 @@ "\n", "Swarm introduces **routines**, which are natural-language instructions paired with the tools needed to execute them. Below, we’ll build an agent capable of calling tools and executing routines.\n", "\n", + "**\n", + "\n", "### Implementation\n", "\n", "- `instructions` could already be passed to the Assistant, to guide its behavior.\n", @@ -255,7 +257,7 @@ " def run(self, messages: list[ChatMessage]) -> Tuple[str, list[ChatMessage]]:\n", "\n", " # generate response\n", - " agent_message = self.llm.run(messages=[self._system_message] + messages, tools=self.tools or None)[\"replies\"][0]\n", + " agent_message = self.llm.run(messages=[self._system_message] + messages, tools=self.tools)[\"replies\"][0]\n", " new_messages = [agent_message]\n", "\n", " if agent_message.text:\n", @@ -402,12 +404,12 @@ "2. Modify the Agent to return the name of the next agent along with its messages.\n", "3. Handle the switch in `while` loop.\n", "\n", - "The implementation is similar to the previous one, with some additions." + "The implementation is similar to the previous one, but, compared to `ToolCallingAgent`, a `SwarmAgent` also returns the name of the next agent to be called, enabling handoffs." ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "id": "w_-0BDi1xU6Z" }, @@ -431,7 +433,7 @@ "\n", " def run(self, messages: list[ChatMessage]) -> Tuple[str, list[ChatMessage]]:\n", " # generate response\n", - " agent_message = self.llm.run(messages=[self._system_message] + messages, tools=self.tools or None)[\"replies\"][0]\n", + " agent_message = self.llm.run(messages=[self._system_message] + messages, tools=self.tools)[\"replies\"][0]\n", " new_messages = [agent_message]\n", "\n", " if agent_message.text:\n", @@ -551,10 +553,8 @@ " break\n", " messages.append(ChatMessage.from_user(user_input))\n", "\n", - " new_agent_name, new_messages = agent.run(messages)\n", - " messages.extend(new_messages)\n", - "\n", - " current_agent_name = new_agent_name or current_agent_name" + " current_agent_name, new_messages = agent.run(messages)\n", + " messages.extend(new_messages)" ] }, { @@ -811,10 +811,8 @@ " break\n", " messages.append(ChatMessage.from_user(user_input))\n", "\n", - " new_agent_name, new_messages = agent.run(messages)\n", - " messages.extend(new_messages)\n", - "\n", - " current_agent_name = new_agent_name or current_agent_name" + " current_agent_name, new_messages = agent.run(messages)\n", + " messages.extend(new_messages)" ] }, { @@ -1052,10 +1050,8 @@ " break\n", " messages.append(ChatMessage.from_user(user_input))\n", "\n", - " new_agent_name, new_messages = agent.run(messages)\n", - " messages.extend(new_messages)\n", - "\n", - " current_agent_name = new_agent_name or current_agent_name" + " current_agent_name, new_messages = agent.run(messages)\n", + " messages.extend(new_messages)" ] }, { From c2b3fd6ba992ce8707e945c6af060c3e987b39e3 Mon Sep 17 00:00:00 2001 From: anakin87 Date: Mon, 18 Nov 2024 16:15:05 +0100 Subject: [PATCH 5/7] clean up --- notebooks/swarm.ipynb | 1 - 1 file changed, 1 deletion(-) diff --git a/notebooks/swarm.ipynb b/notebooks/swarm.ipynb index 8458460..c97a6eb 100644 --- a/notebooks/swarm.ipynb +++ b/notebooks/swarm.ipynb @@ -214,7 +214,6 @@ "\n", "Swarm introduces **routines**, which are natural-language instructions paired with the tools needed to execute them. Below, we’ll build an agent capable of calling tools and executing routines.\n", "\n", - "**\n", "\n", "### Implementation\n", "\n", From 80a2a327ce432bdbaf46f4727b183b1fb930eb46 Mon Sep 17 00:00:00 2001 From: anakin87 Date: Mon, 18 Nov 2024 16:16:06 +0100 Subject: [PATCH 6/7] fix --- notebooks/swarm.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/swarm.ipynb b/notebooks/swarm.ipynb index c97a6eb..cacd04f 100644 --- a/notebooks/swarm.ipynb +++ b/notebooks/swarm.ipynb @@ -403,7 +403,7 @@ "2. Modify the Agent to return the name of the next agent along with its messages.\n", "3. Handle the switch in `while` loop.\n", "\n", - "The implementation is similar to the previous one, but, compared to `ToolCallingAgent`, a `SwarmAgent` also returns the name of the next agent to be called, enabling handoffs." + "*The implementation is similar to the previous one, but, compared to `ToolCallingAgent`, a `SwarmAgent` also returns the name of the next agent to be called, enabling handoffs.*" ] }, { From 2db94d25557ea7ebdcdbb3320beea38b787c000d Mon Sep 17 00:00:00 2001 From: anakin87 Date: Tue, 19 Nov 2024 12:54:10 +0100 Subject: [PATCH 7/7] small addition --- notebooks/swarm.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/swarm.ipynb b/notebooks/swarm.ipynb index cacd04f..b51e96e 100644 --- a/notebooks/swarm.ipynb +++ b/notebooks/swarm.ipynb @@ -580,7 +580,7 @@ "This system involves several different agents (each with specific tools):\n", "- Triage Agent: handles general questions and directs to other agents. Tools: `transfer_to_sales_agent`, `transfer_to_issues_and_repairs` and `escalate_to_human`.\n", "- Sales Agent: proposes and sells products to the user, it can execute the order or redirect the user back to the Triage Agent. Tools: `execute_order` and `transfer_back_to_triage`.\n", - "- Issues and Repairs Agent: supports customers with their problems, it can look up item IDs, execute refund or redirect the user back to triage. \n", + "- Issues and Repairs Agent: supports customers with their problems, it can look up item IDs, execute refund or redirect the user back to triage. Tools: `look_up_item`, `execute_refund`, and `transfer_back_to_triage`.\n", "\n", "A nice bonus feature of our implementation is that **we can use different model providers** supported by Haystack. In this case, the Triage Agent is powered by (OpenAI) gpt-4o-mini, while we use (Anthropic) Claude 3.5 Sonnet for the other two agents.\n", "\n"