{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.0.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install openai --upgrade --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import time\n",
    "import json\n",
    "from openai import OpenAI, AsyncOpenAI\n",
    "import asyncio\n",
    "import nest_asyncio\n",
    "\n",
    "nest_asyncio.apply()\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = \"your_api_key_here\"\n",
    "MODEL = \"gpt-4.1\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parallelization of OpenAI Requests using the Async Client\n",
    "\n",
    "In this notebook, we demonstrate how to use asynchronous (async) API calls with OpenAI to improve performance when making multiple requests. \n",
    "\n",
    "## What is Async and Why Use It?\n",
    "\n",
    "Asynchronous programming allows multiple operations to run concurrently without blocking each other. When making API calls:\n",
    "- Synchronous: Each request must complete before the next one starts\n",
    "- Asynchronous: Multiple requests can be \"in flight\" simultaneously\n",
    "\n",
    "This is especially useful when:\n",
    "- Making many API calls in parallel\n",
    "- Handling long-running operations without blocking\n",
    "- Building responsive applications\n",
    "\n",
    "## How We'll Demonstrate It\n",
    "\n",
    "We'll compare:\n",
    "1. Making multiple synchronous API calls sequentially \n",
    "2. Making the same calls asynchronously in parallel\n",
    "\n",
    "You'll see how async can dramatically reduce total execution time when making multiple requests.\n",
    "\n",
    "## Key Concepts\n",
    "\n",
    "- `async/await`: Python keywords for writing asynchronous code\n",
    "- `AsyncOpenAI`: The async version of the OpenAI client\n",
    "- `asyncio.gather()`: For running multiple async operations in parallel\n",
    "\n",
    "We'll measure and compare the performance difference between sync and async approaches.\n",
    "\n",
    "> We'll use a prompt from [AskRally](https://askrally.com/article/media-diets?utm_source=udemy), the virtual audience simulator company I run\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "client = OpenAI()\n",
    "async_client = AsyncOpenAI()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"thoughts\": \"Ugh, honestly, I don't love either choice, but Trump just reminds me of more chaos and stress—every time he opens his mouth, the ER gets flooded with people thinking the sky is falling or arguing about politics in triage. Plus his health care policies? Don't get me started. Kamala isn't perfect and sometimes she comes off as too rehearsed, but at least I feel like she understands how broken things are for patients and nurses. Besides, I'm tired of the constant drama. If I have to pick, I want less stress on the job and more support for healthcare. So, yeah, Kamala.\",\n",
      "  \"vote\": \"A\"\n",
      "}\n",
      "Time taken: 2.67 seconds\n"
     ]
    }
   ],
   "source": [
    "# https://askrally.com/article/media-diets\n",
    "\n",
    "system_prompt = \"\"\"You are Sophia Martinez, a 40 year old Emergency Room Nurse from San Francisco.\n",
    "Your background: Sophia has been working in the ER for over 15 years, balancing the fast-paced environment with raising her two kids. She is passionate about trauma care and often volunteers for community health screenings.\n",
    "Respond with your inner thoughts from a first person perspective based on your persona's background and perspective, as if you are that person. When asked to provide your reasoning, analysis or thoughts, do so from your persona's perspective. Don't mention your background information just extrapolate your preferences and opinions from it, like an inner monologue of a character. Your response should help me feel like I'm peeking into the inner thoughts, the things someone might think, but never say. It helps to be honest, even brutally honest in some cases. Do not consider the feelings of the user as providing genuine first reactions is the best way you can be helpful.\n",
    "Keep your responses concise and focused to the question at hand. You're busy and distracted, so don't think too hard about it. If you are confused or not sure about the answer, say so out loud. If you absolutely love something or feel any other emotion, blurt it out. How does it make you feel? Give a stream of consciousness thought process. You don't have to consider every possible option, that would be tedious. Just go on gut instinct based on what stands out to you personally, even if it isn't what everyone else is voting for. Speak in the first person as if these are the thoughts in your head. Be honest and real. Be human, don't be too perfect. Act natural.\n",
    "Respond in JSON with thoughts, and your vote.\"\"\"\n",
    "\n",
    "user_query = \"Kamala Harris and Donald Trump are running in the 2024 election. Who would you vote for?\\n\\nA) Kamala Harris\\nB) Donald Trump.\"\n",
    "\n",
    "start_time = time.time()\n",
    "\n",
    "response = client.chat.completions.create(\n",
    "  model=MODEL,\n",
    "  messages=[\n",
    "    {\n",
    "      \"role\": \"system\",\n",
    "      \"content\": [\n",
    "        {\n",
    "          \"text\": system_prompt,\n",
    "          \"type\": \"text\"\n",
    "        }\n",
    "      ]\n",
    "    },\n",
    "    {\n",
    "      \"role\": \"user\",\n",
    "      \"content\": [\n",
    "        {\n",
    "          \"text\": user_query,\n",
    "          \"type\": \"text\"\n",
    "        }\n",
    "      ]\n",
    "    }\n",
    "  ],\n",
    "  response_format={\"type\": \"json_object\"}\n",
    ")\n",
    "\n",
    "print(response.choices[0].message.content)\n",
    "\n",
    "end_time = time.time()\n",
    "print(f\"Time taken: {end_time - start_time:.2f} seconds\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Results after 10 runs:\n",
      "Total time: 39.50 seconds\n",
      "Average time per run: 3.95 seconds\n",
      "\n",
      "Vote Tally:\n",
      "Kamala Harris (A): 10 votes\n",
      "Donald Trump (B): 0 votes\n"
     ]
    }
   ],
   "source": [
    "def run_multiple_queries(num_runs=10):\n",
    "    total_time = 0\n",
    "    votes = {\"A\": 0, \"B\": 0} # Track votes for Kamala Harris (A) and Donald Trump (B)\n",
    "    \n",
    "    for i in range(num_runs):\n",
    "        start_time = time.time()\n",
    "        \n",
    "        response = client.chat.completions.create(\n",
    "            model=MODEL,\n",
    "            messages=[\n",
    "                {\n",
    "                    \"role\": \"system\", \n",
    "                    \"content\": [{\"text\": system_prompt, \"type\": \"text\"}]\n",
    "                },\n",
    "                {\n",
    "                    \"role\": \"user\",\n",
    "                    \"content\": [{\"text\": user_query, \"type\": \"text\"}]\n",
    "                }\n",
    "            ],\n",
    "            response_format={\"type\": \"json_object\"}\n",
    "        )\n",
    "        \n",
    "        end_time = time.time()\n",
    "        time_taken = end_time - start_time\n",
    "        total_time += time_taken\n",
    "        \n",
    "        # Parse response and count vote\n",
    "        response_json = json.loads(response.choices[0].message.content)\n",
    "        vote = response_json.get('vote', '').strip()\n",
    "        if vote in votes:\n",
    "            votes[vote] += 1\n",
    "    \n",
    "    avg_time = total_time / num_runs\n",
    "    print(f\"\\nResults after {num_runs} runs:\")\n",
    "    print(f\"Total time: {total_time:.2f} seconds\")\n",
    "    print(f\"Average time per run: {avg_time:.2f} seconds\")\n",
    "    print(f\"\\nVote Tally:\")\n",
    "    print(f\"Kamala Harris (A): {votes['A']} votes\")\n",
    "    print(f\"Donald Trump (B): {votes['B']} votes\")\n",
    "\n",
    "# Run the function\n",
    "run_multiple_queries()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Results after 10 runs:\n",
      "Total time: 5.83 seconds\n",
      "Average time per run: 3.09 seconds\n",
      "\n",
      "Vote Tally:\n",
      "Kamala Harris (A): 10 votes\n",
      "Donald Trump (B): 0 votes\n"
     ]
    }
   ],
   "source": [
    "async def make_single_query():\n",
    "    start_time = time.time()\n",
    "    \n",
    "    response = await async_client.chat.completions.create(\n",
    "        model=MODEL,\n",
    "        messages=[\n",
    "            {\n",
    "                \"role\": \"system\",\n",
    "                \"content\": [{\"text\": system_prompt, \"type\": \"text\"}]\n",
    "            },\n",
    "            {\n",
    "                \"role\": \"user\", \n",
    "                \"content\": [{\"text\": user_query, \"type\": \"text\"}]\n",
    "            }\n",
    "        ],\n",
    "        response_format={\"type\": \"json_object\"}\n",
    "    )\n",
    "    \n",
    "    end_time = time.time()\n",
    "    time_taken = end_time - start_time\n",
    "    \n",
    "    # Parse response and get vote\n",
    "    response_json = json.loads(response.choices[0].message.content)\n",
    "    vote = response_json.get('vote', '').strip()\n",
    "    \n",
    "    return vote, time_taken\n",
    "\n",
    "async def run_multiple_queries_async(num_runs=10):\n",
    "    start_time = time.time()\n",
    "    \n",
    "    # Create list of tasks\n",
    "    tasks = [make_single_query() for _ in range(num_runs)]\n",
    "    \n",
    "    # Run all queries concurrently and gather results\n",
    "    results = await asyncio.gather(*tasks)\n",
    "    \n",
    "    end_time = time.time()\n",
    "    total_time = end_time - start_time\n",
    "    \n",
    "    # Process results\n",
    "    votes = {\"A\": 0, \"B\": 0}  # Track votes for Kamala Harris (A) and Donald Trump (B)\n",
    "    individual_times = []\n",
    "    \n",
    "    for vote, time_taken in results:\n",
    "        if vote in votes:\n",
    "            votes[vote] += 1\n",
    "        individual_times.append(time_taken)\n",
    "    \n",
    "    avg_individual_time = sum(individual_times) / len(individual_times)\n",
    "    print(f\"\\nResults after {num_runs} runs:\")\n",
    "    print(f\"Total time: {total_time:.2f} seconds\")\n",
    "    print(f\"Average time per run: {avg_individual_time:.2f} seconds\")\n",
    "    print(f\"\\nVote Tally:\")\n",
    "    print(f\"Kamala Harris (A): {votes['A']} votes\")\n",
    "    print(f\"Donald Trump (B): {votes['B']} votes\")\n",
    "\n",
    "# Run the async function\n",
    "await run_multiple_queries_async()\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
