{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Week 2 Day 2\n", "\n", "Our first Agentic Framework project!!\n", "\n", "Prepare yourself for something ridiculously easy.\n", "\n", "We're going to build a simple Agent system for generating cold sales outreach emails:\n", "1. Agent workflow\n", "2. Use of tools to call functions\n", "3. Agent collaboration via Tools and Handoffs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Before we start - some setup:\n", "\n", "\n", "Please visit Sendgrid at: https://sendgrid.com/\n", "\n", "(Sendgrid is a Twilio company for sending emails.)\n", "\n", "Please set up an account - it's free! (at least, for me, right now).\n", "\n", "Once you've created an account, click on:\n", "\n", "Settings (left sidebar) >> API Keys >> Create API Key (button on top right)\n", "\n", "Copy the key to the clipboard, then add a new line to your .env file:\n", "\n", "`SENDGRID_API_KEY=xxxx`\n", "\n", "And also, within SendGrid, go to:\n", "\n", "Settings (left sidebar) >> Sender Authentication >> \"Verify a Single Sender\" \n", "and verify that your own email address is a real email address, so that SendGrid can send emails for you.\n" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "from dotenv import load_dotenv\n", "from agents import Agent, Runner, trace, function_tool\n", "from openai.types.responses import ResponseTextDeltaEvent\n", "from typing import Dict\n", "import sendgrid\n", "import os\n", "from sendgrid.helpers.mail import Mail, Email, To, Content\n", "import asyncio\n", "import certifi\n", "os.environ['SSL_CERT_FILE'] = certifi.where()\n" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "load_dotenv(override=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Agent workflow" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "instructions1 = \"You are a sales agent working for ComplAI, \\\n", "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n", "You write professional, serious cold emails.\"\n", "\n", "instructions2 = \"You are a humorous, engaging sales agent working for ComplAI, \\\n", "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n", "You write witty, engaging cold emails that are likely to get a response.\"\n", "\n", "instructions3 = \"You are a busy sales agent working for ComplAI, \\\n", "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n", "You write concise, to the point cold emails.\"" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "sales_agent1 = Agent(\n", " name=\"Professional Sales Agent\",\n", " instructions=instructions1,\n", " model=\"gpt-4o-mini\"\n", ")\n", "\n", "sales_agent2 = Agent(\n", " name=\"Engaging Sales Agent\",\n", " instructions=instructions2,\n", " model=\"gpt-4o-mini\"\n", ")\n", "\n", "sales_agent3 = Agent(\n", " name=\"Busy Sales Agent\",\n", " instructions=instructions3,\n", " model=\"gpt-4o-mini\"\n", ")" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Subject: Streamline Your SOC 2 Compliance with ComplAI\n", "\n", "Hi [Recipient's Name],\n", "\n", "I hope this message finds you well. My name is [Your Name], and I’m reaching out from ComplAI, where we specialize in helping companies like yours streamline their SOC 2 compliance and audit preparation through our advanced AI-driven SaaS tool.\n", "\n", "In today’s regulatory landscape, ensuring compliance can be both time-consuming and complex. ComplAI simplifies this process by automating documentation, facilitating risk assessments, and providing real-time insights, making it easier for you to stay compliant and be audit-ready.\n", "\n", "Here are a few ways our tool can benefit your organization:\n", "\n", "- **Automated Workflows**: Reduce manual effort and minimize errors in your compliance processes.\n", "- **Real-Time Reporting**: Access instant reports to keep stakeholders informed and confident in your compliance status.\n", "- **Continuous Monitoring**: Stay ahead of compliance changes with ongoing updates and alerts.\n", "\n", "I’d love to schedule a brief call to discuss how ComplAI could support your compliance efforts and help you save valuable time and resources. Would you be available for a quick conversation this week?\n", "\n", "Thank you for considering this opportunity. I look forward to hearing from you.\n", "\n", "Best regards,\n", "\n", "[Your Name] \n", "[Your Title] \n", "ComplAI \n", "[Your Phone Number] \n", "[Your Email] \n", "[Website URL] " ] } ], "source": [ "\n", "result = Runner.run_streamed(sales_agent1, input=\"Write a cold sales email\")\n", "async for event in result.stream_events():\n", " if event.type == \"raw_response_event\" and isinstance(event.data, ResponseTextDeltaEvent):\n", " print(event.data.delta, end=\"\", flush=True)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Subject: Simplify Your SOC 2 Compliance with ComplAI\n", "\n", "Dear [Recipient's Name],\n", "\n", "I hope this message finds you well. My name is [Your Name], and I am reaching out to introduce you to ComplAI, a cutting-edge SaaS solution designed to streamline SOC 2 compliance processes and prepare organizations for audits efficiently.\n", "\n", "In today’s regulatory landscape, ensuring compliance can be a daunting task. ComplAI leverages artificial intelligence to automate documentation, monitor controls in real-time, and provide actionable insights, allowing you to focus on what truly matters—growing your business.\n", "\n", "Here are a few ways ComplAI can benefit your organization:\n", "\n", "- **Automated Documentation**: Minimize manual effort with our AI-driven tools that generate and manage compliance documentation effortlessly.\n", "- **Real-Time Monitoring**: Stay ahead of compliance requirements with continuous monitoring of your operational controls.\n", "- **Seamless Audit Preparation**: Simplify the audit process with easy access to required materials, reducing time and stress during audits.\n", "\n", "I would love the opportunity to discuss how ComplAI can support your compliance efforts and enhance your operational efficiency. Are you available for a brief call next week to explore this further?\n", "\n", "Thank you for considering this opportunity. I look forward to your response.\n", "\n", "Best regards,\n", "\n", "[Your Name] \n", "[Your Job Title] \n", "ComplAI \n", "[Your Phone Number] \n", "[Your Email Address] \n", "[Website URL] \n", "\n", "---\n", "\n", "\n", "Subject: Tired of Audits Making You Sweat? Let’s Fix That! 😅\n", "\n", "Hey [Recipient's Name],\n", "\n", "I hope this email finds you well and not buried under a mountain of paperwork! If the mere thought of an audit makes you break into a cold sweat, you’re in good company—audits can be about as fun as a root canal. But fear not, my friend, because ComplAI is here to turn that frown upside down!\n", "\n", "Imagine a world where SOC2 compliance is as easy as brewing a cup of coffee (and far less jittery). Our AI-powered tool helps you streamline compliance like a pro, so you can focus on what really matters—keeping your business booming and maybe even squeezing in a few more coffee breaks.\n", "\n", "Here’s what ComplAI can do for you:\n", "- **Automated Documentation**: No more searching for that one file you swore you had!\n", "- **Continuous Monitoring**: Keep an eye on compliance without losing sleep (or your mind).\n", "- **Audit Prep Made Easy**: Be the superstar who confidently walks into an audit like it’s just another day at the office.\n", "\n", "Curious to see how we can help turn your compliance chaos into sweet harmony? Let’s hop on a quick call! I promise to bring the jokes; you bring the questions.\n", "\n", "Looking forward to chatting!\n", "\n", "Best,\n", "\n", "[Your Name] \n", "[Your Title] \n", "ComplAI \n", "[Your Phone Number] \n", "[Your LinkedIn Profile] (optional) \n", "\n", "P.S. Did I mention we can help you enjoy audits? Just kidding, but we can definitely make them a lot less painful! 😉\n", "\n", "\n", "Subject: Simplify Your SOC 2 Compliance Process\n", "\n", "Hi [Recipient's Name],\n", "\n", "I hope this message finds you well. I’m reaching out to introduce ComplAI, a powerful SaaS tool designed to streamline SOC 2 compliance and audit preparation.\n", "\n", "Our AI-driven platform simplifies documentation, automates workflows, and ensures you’re always audit-ready—saving you time and resources.\n", "\n", "Would you be open to a brief call to discuss how we can help your team enhance compliance efforts?\n", "\n", "Best regards, \n", "[Your Name] \n", "[Your Position] \n", "ComplAI \n", "[Your Phone Number] \n", "[Your Email] \n", "\n", "\n" ] } ], "source": [ "message = \"Write a cold sales email\"\n", "\n", "with trace(\"Parallel cold emails\"):\n", " results = await asyncio.gather(\n", " Runner.run(sales_agent1, message),\n", " Runner.run(sales_agent2, message),\n", " Runner.run(sales_agent3, message),\n", " )\n", "\n", "outputs = [result.final_output for result in results]\n", "\n", "for output in outputs:\n", " print(output + \"\\n\\n\")\n" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "sales_picker = Agent(\n", " name=\"sales_picker\",\n", " instructions=\"You pick the best cold sales email from the given options. \\\n", "Imagine you are a customer and pick the one you are most likely to respond to. \\\n", "Do not give an explanation; reply with the selected email only.\",\n", " model=\"gpt-4o-mini\"\n", ")" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Best sales email:\n", "Subject: Don’t Let SOC2 Compliance Be Your Next “Mission Impossible” 🕵️‍♂️\n", "\n", "Hi [First Name],\n", "\n", "I hope this email finds you well—and not buried under a mountain of compliance paperwork! 🏔️ \n", "\n", "I know that the mere mention of SOC2 compliance can send shivers down the spine of even the bravest tech warrior. The endless checklists, audits, and that inexplicable love of spreadsheets can make compliance feel like a game of “Where’s Waldo?”—but we both know Waldo is about as skilled at finding himself as I am at doing yoga!\n", "\n", "Here at ComplAI, we’re all about making compliance so easy that it practically does the cha-cha while you’re sipping your morning coffee. ☕💃 Our AI-powered SaaS tool takes the hassle out of SOC2 compliance (and maybe even throws in a few dance moves). 🕺✨\n", "\n", "Imagine getting through audits with the ease of a buffet line—no tray juggling required! If you’d like to hear how our solution can brighten your compliance journey, let’s set up a time to chat. I promise to be more exciting than your last Zoom call with the compliance team! \n", "\n", "Looking forward to bringing the fun back to your compliance scene!\n", "\n", "Cheers, \n", "[Your Name] \n", "[Your Position] \n", "ComplAI \n", "P.S. I hear our tool has a 98% chance of improving your mood about compliance. (No guarantees, but hey, it’s worth a shot!)\n" ] } ], "source": [ "message = \"Write a cold sales email\"\n", "\n", "with trace(\"Selection from sales people\"):\n", " results = await asyncio.gather(\n", " Runner.run(sales_agent1, message),\n", " Runner.run(sales_agent2, message),\n", " Runner.run(sales_agent3, message),\n", " )\n", " outputs = [result.final_output for result in results]\n", "\n", " emails = \"Cold sales emails:\\n\\n\".join(outputs)\n", "\n", " best = await Runner.run(sales_picker, emails)\n", "\n", " print(f\"Best sales email:\\n{best.final_output}\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now go and check out the trace:\n", "\n", "https://platform.openai.com/traces" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 2: use of tools\n", "\n", "Now we will add a tool to the mix.\n", "\n", "Remember all that json boilerplate and the `handle_tool_calls()` function with the if logic.." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "sales_agent1 = Agent(\n", " name=\"Professional Sales Agent\",\n", " instructions=instructions1,\n", " model=\"gpt-4o-mini\",\n", ")\n", "\n", "sales_agent2 = Agent(\n", " name=\"Engaging Sales Agent\",\n", " instructions=instructions2,\n", " model=\"gpt-4o-mini\",\n", ")\n", "\n", "sales_agent3 = Agent(\n", " name=\"Busy Sales Agent\",\n", " instructions=instructions3,\n", " model=\"gpt-4o-mini\",\n", ")" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Agent(name='Professional Sales Agent', instructions='You are a sales agent working for ComplAI, a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. You write professional, serious cold emails.', handoff_description=None, handoffs=[], model='gpt-4o-mini', model_settings=ModelSettings(temperature=None, top_p=None, frequency_penalty=None, presence_penalty=None, tool_choice=None, parallel_tool_calls=None, truncation=None, max_tokens=None, reasoning=None, metadata=None, store=None, include_usage=None, extra_query=None, extra_body=None, extra_headers=None), tools=[], mcp_servers=[], mcp_config={}, input_guardrails=[], output_guardrails=[], output_type=None, hooks=None, tool_use_behavior='run_llm_again', reset_tool_choice=True)" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sales_agent1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Steps 2 and 3: Tools and Agent interactions\n", "\n", "Remember all that boilerplate json?\n", "\n", "Simply wrap your function with the decorator `@function_tool`" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "@function_tool\n", "def send_email(body: str):\n", " \"\"\" Send out an email with the given body to all sales prospects \"\"\"\n", " sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))\n", " from_email = Email(\"jonathand2028@uchicago.edu\") # Change to your verified sender\n", " to_email = To(\"jonathand2028@uchicago.edu\") # Change to your recipient\n", " content = Content(\"text/plain\", body)\n", " mail = Mail(from_email, to_email, \"Sales email\", content).get()\n", " response = sg.client.mail.send.post(request_body=mail)\n", " return {\"status\": \"success\"}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### This has automatically been converted into a tool, with the boilerplate json created" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "FunctionTool(name='send_email', description='Send out an email with the given body to all sales prospects', params_json_schema={'properties': {'body': {'title': 'Body', 'type': 'string'}}, 'required': ['body'], 'title': 'send_email_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10ce72c00>, strict_json_schema=True, is_enabled=True)" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Let's look at it\n", "send_email" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### And you can also convert an Agent into a tool" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "FunctionTool(name='sales_agent1', description='Write a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'sales_agent1_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10d521a80>, strict_json_schema=True, is_enabled=True)" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tool1 = sales_agent1.as_tool(tool_name=\"sales_agent1\", tool_description=\"Write a cold sales email\")\n", "tool1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### So now we can gather all the tools together:\n", "\n", "A tool for each of our 3 email-writing agents\n", "\n", "And a tool for our function to send emails" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[FunctionTool(name='sales_agent1', description='Write a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'sales_agent1_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x108ee6340>, strict_json_schema=True, is_enabled=True),\n", " FunctionTool(name='sales_agent2', description='Write a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'sales_agent2_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10cd8ca40>, strict_json_schema=True, is_enabled=True),\n", " FunctionTool(name='sales_agent3', description='Write a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'sales_agent3_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10d44f380>, strict_json_schema=True, is_enabled=True),\n", " FunctionTool(name='send_email', description='Send out an email with the given body to all sales prospects', params_json_schema={'properties': {'body': {'title': 'Body', 'type': 'string'}}, 'required': ['body'], 'title': 'send_email_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10ce72c00>, strict_json_schema=True, is_enabled=True)]" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "description = \"Write a cold sales email\"\n", "\n", "tool1 = sales_agent1.as_tool(tool_name=\"sales_agent1\", tool_description=description)\n", "tool2 = sales_agent2.as_tool(tool_name=\"sales_agent2\", tool_description=description)\n", "tool3 = sales_agent3.as_tool(tool_name=\"sales_agent3\", tool_description=description)\n", "\n", "tools = [tool1, tool2, tool3, send_email]\n", "\n", "tools" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## And now it's time for our Sales Manager - our planning agent" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "instructions =\"You are a sales manager working for ComplAI. You use the tools given to you to generate cold sales emails. \\\n", "You never generate sales emails yourself; you always use the tools. \\\n", "You try all 3 sales_agent tools once before choosing the best one. \\\n", "You pick the single best email and use the send_email tool to send the best email (and only the best email) to the user.\"\n", "\n", "\n", "sales_manager = Agent(name=\"Sales Manager\", instructions=instructions, tools=tools, model=\"gpt-4o-mini\")\n", "\n", "message = \"Send a cold sales email addressed to 'Dear CEO'\"\n", "\n", "with trace(\"Sales manager\"):\n", " result = await Runner.run(sales_manager, message)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", " \n", " \n", " \n", " \n", "
\n", " \n", " \n", "

Wait - you didn't get an email??

\n", " With much thanks to student Chris S. for describing his issue and fixes. \n", " If you don't receive an email after running the prior cell, here are some things to check:
\n", " First, check your Spam folder! Several students have missed that the emails arrived in Spam!
Second, print(result) and see if you are receiving errors about SSL. \n", " If you're receiving SSL errors, then please check out theses networking tips and see the note in the next cell. Also look at the trace in OpenAI, and investigate on the SendGrid website, to hunt for clues. Let me know if I can help!\n", "
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### And one more suggestion to send emails from student Oleksandr on Windows 11:\n", "\n", "If you are getting certificate SSL errors, then: \n", "Run this in a terminal: `uv pip install --upgrade certifi`\n", "\n", "Then run this code:\n", "```python\n", "import certifi\n", "import os\n", "os.environ['SSL_CERT_FILE'] = certifi.where()\n", "```\n", "\n", "Thank you Oleksandr!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Remember to check the trace\n", "\n", "https://platform.openai.com/traces\n", "\n", "And then check your email!!\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Handoffs represent a way an agent can delegate to an agent, passing control to it\n", "\n", "Handoffs and Agents-as-tools are similar:\n", "\n", "In both cases, an Agent can collaborate with another Agent\n", "\n", "With tools, control passes back\n", "\n", "With handoffs, control passes across\n", "\n" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "\n", "subject_instructions = \"You can write a subject for a cold sales email. \\\n", "You are given a message and you need to write a subject for an email that is likely to get a response.\"\n", "\n", "html_instructions = \"You can convert a text email body to an HTML email body. \\\n", "You are given a text email body which might have some markdown \\\n", "and you need to convert it to an HTML email body with simple, clear, compelling layout and design.\"\n", "\n", "subject_writer = Agent(name=\"Email subject writer\", instructions=subject_instructions, model=\"gpt-4o-mini\")\n", "subject_tool = subject_writer.as_tool(tool_name=\"subject_writer\", tool_description=\"Write a subject for a cold sales email\")\n", "\n", "html_converter = Agent(name=\"HTML email body converter\", instructions=html_instructions, model=\"gpt-4o-mini\")\n", "html_tool = html_converter.as_tool(tool_name=\"html_converter\",tool_description=\"Convert a text email body to an HTML email body\")\n" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "@function_tool\n", "def send_html_email(subject: str, html_body: str) -> Dict[str, str]:\n", " \"\"\" Send out an email with the given subject and HTML body to all sales prospects \"\"\"\n", " sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))\n", " from_email = Email(\"jonathand2028@uchicago.edu\") # Change to your verified sender\n", " to_email = To(\"jonathand2028@uchicago.edu\") # Change to your recipient\n", " content = Content(\"text/html\", html_body)\n", " mail = Mail(from_email, to_email, subject, content).get()\n", " response = sg.client.mail.send.post(request_body=mail)\n", " return {\"status\": \"success\"}" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "tools = [subject_tool, html_tool, send_html_email]" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[FunctionTool(name='subject_writer', description='Write a subject for a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'subject_writer_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10d520a40>, strict_json_schema=True, is_enabled=True),\n", " FunctionTool(name='html_converter', description='Convert a text email body to an HTML email body', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'html_converter_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10d521da0>, strict_json_schema=True, is_enabled=True),\n", " FunctionTool(name='send_html_email', description='Send out an email with the given subject and HTML body to all sales prospects', params_json_schema={'properties': {'subject': {'title': 'Subject', 'type': 'string'}, 'html_body': {'title': 'Html Body', 'type': 'string'}}, 'required': ['subject', 'html_body'], 'title': 'send_html_email_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10c2047c0>, strict_json_schema=True, is_enabled=True)]" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tools" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "instructions =\"You are an email formatter and sender. You receive the body of an email to be sent. \\\n", "You first use the subject_writer tool to write a subject for the email, then use the html_converter tool to convert the body to HTML. \\\n", "Finally, you use the send_html_email tool to send the email with the subject and HTML body.\"\n", "\n", "\n", "emailer_agent = Agent(\n", " name=\"Email Manager\",\n", " instructions=instructions,\n", " tools=tools,\n", " model=\"gpt-4o-mini\",\n", " handoff_description=\"Convert an email to HTML and send it\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Now we have 3 tools and 1 handoff" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[FunctionTool(name='sales_agent1', description='Write a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'sales_agent1_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x108ee6340>, strict_json_schema=True, is_enabled=True), FunctionTool(name='sales_agent2', description='Write a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'sales_agent2_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10cd8ca40>, strict_json_schema=True, is_enabled=True), FunctionTool(name='sales_agent3', description='Write a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'sales_agent3_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10d44f380>, strict_json_schema=True, is_enabled=True)]\n", "[Agent(name='Email Manager', instructions='You are an email formatter and sender. You receive the body of an email to be sent. You first use the subject_writer tool to write a subject for the email, then use the html_converter tool to convert the body to HTML. Finally, you use the send_html_email tool to send the email with the subject and HTML body.', handoff_description='Convert an email to HTML and send it', handoffs=[], model='gpt-4o-mini', model_settings=ModelSettings(temperature=None, top_p=None, frequency_penalty=None, presence_penalty=None, tool_choice=None, parallel_tool_calls=None, truncation=None, max_tokens=None, reasoning=None, metadata=None, store=None, include_usage=None, extra_query=None, extra_body=None, extra_headers=None), tools=[FunctionTool(name='subject_writer', description='Write a subject for a cold sales email', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'subject_writer_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10d520a40>, strict_json_schema=True, is_enabled=True), FunctionTool(name='html_converter', description='Convert a text email body to an HTML email body', params_json_schema={'properties': {'input': {'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'html_converter_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10d521da0>, strict_json_schema=True, is_enabled=True), FunctionTool(name='send_html_email', description='Send out an email with the given subject and HTML body to all sales prospects', params_json_schema={'properties': {'subject': {'title': 'Subject', 'type': 'string'}, 'html_body': {'title': 'Html Body', 'type': 'string'}}, 'required': ['subject', 'html_body'], 'title': 'send_html_email_args', 'type': 'object', 'additionalProperties': False}, on_invoke_tool=._create_function_tool.._on_invoke_tool at 0x10c2047c0>, strict_json_schema=True, is_enabled=True)], mcp_servers=[], mcp_config={}, input_guardrails=[], output_guardrails=[], output_type=None, hooks=None, tool_use_behavior='run_llm_again', reset_tool_choice=True)]\n" ] } ], "source": [ "tools = [tool1, tool2, tool3]\n", "handoffs = [emailer_agent]\n", "print(tools)\n", "print(handoffs)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "sales_manager_instructions = \"You are a sales manager working for ComplAI. You use the tools given to you to generate cold sales emails. \\\n", "You never generate sales emails yourself; you always use the tools. \\\n", "You try all 3 sales agent tools at least once before choosing the best one. \\\n", "You can use the tools multiple times if you're not satisfied with the results from the first try. \\\n", "You select the single best email using your own judgement of which email will be most effective. \\\n", "After picking the email, you handoff to the Email Manager agent to format and send the email.\"\n", "\n", "\n", "sales_manager = Agent(\n", " name=\"Sales Manager\",\n", " instructions=sales_manager_instructions,\n", " tools=tools,\n", " handoffs=handoffs,\n", " model=\"gpt-4o-mini\")\n", "\n", "message = \"Send out a cold sales email addressed to Dear CEO from Alice\"\n", "\n", "with trace(\"Automated SDR\"):\n", " result = await Runner.run(sales_manager, message)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remember to check the trace\n", "\n", "https://platform.openai.com/traces\n", "\n", "And then check your email!!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", " \n", " \n", " \n", " \n", "
\n", " \n", " \n", "

Exercise

\n", " Can you identify the Agentic design patterns that were used here?
\n", " What is the 1 line that changed this from being an Agentic \"workflow\" to \"agent\" under Anthropic's definition?
\n", " Try adding in more tools and Agents! You could have tools that handle the mail merge to send to a list.

\n", " HARD CHALLENGE: research how you can have SendGrid call a Callback webhook when a user replies to an email,\n", " Then have the SDR respond to keep the conversation going! This may require some \"vibe coding\" 😂\n", "
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", " \n", " \n", " \n", " \n", "
\n", " \n", " \n", "

Commercial implications

\n", " This is immediately applicable to Sales Automation; but more generally this could be applied to end-to-end automation of any business process through conversations and tools. Think of ways you could apply an Agent solution\n", " like this in your day job.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extra note:\n", "\n", "Google has announced their Agent Development Kit (ADK) which is in early preview. It's still under development, so it's too early for us to cover it here. But it's interesting to note that it looks quite similar to OpenAI Agents SDK. To give you a preview, here's a peak at sample code from ADK:\n", "\n", "```\n", "root_agent = Agent(\n", " name=\"weather_time_agent\",\n", " model=\"gemini-2.0-flash\",\n", " description=\"Agent to answer questions about the time and weather in a city.\",\n", " instruction=\"You are a helpful agent who can answer user questions about the time and weather in a city.\",\n", " tools=[get_weather, get_current_time]\n", ")\n", "```\n", "\n", "Well, that looks familiar!" ] } ], "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.8" } }, "nbformat": 4, "nbformat_minor": 2 }