{ "cells": [ { "cell_type": "markdown", "id": "d006b2ea-9dfe-49c7-88a9-a5a0775185fd", "metadata": {}, "source": [ "# Additional End of week Exercise - week 2\n", "\n", "Now use everything you've learned from Week 2 to build a full prototype for the technical question/answerer you built in Week 1 Exercise.\n", "\n", "This should include a Gradio UI, streaming, use of the system prompt to add expertise, and the ability to switch between models. Bonus points if you can demonstrate use of a tool!\n", "\n", "If you feel bold, see if you can add audio input so you can talk to it, and have it respond with audio. ChatGPT or Claude can help you, or email me if you have questions.\n", "\n", "I will publish a full solution here soon - unless someone beats me to it...\n", "\n", "There are so many commercial applications for this, from a language tutor, to a company onboarding solution, to a companion AI to a course (like this one!) I can't wait to see your results." ] }, { "cell_type": "code", "execution_count": 11, "id": "a07e7793-b8f5-44f4-aded-5562f633271a", "metadata": {}, "outputs": [], "source": [ "# imports\n", "\n", "import os\n", "import json\n", "from dotenv import load_dotenv\n", "from openai import OpenAI\n", "import gradio as gr" ] }, { "cell_type": "code", "execution_count": 12, "id": "3ec1f21d-f043-43ca-8cdd-1f96328cf626", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OpenAI API Key exists and begins sk-proj-\n" ] } ], "source": [ "# Initialization\n", "\n", "load_dotenv()\n", "\n", "openai_api_key = os.getenv('OPENAI_API_KEY')\n", "if openai_api_key:\n", " print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n", "else:\n", " print(\"OpenAI API Key not set\")\n", " \n", "MODEL = \"gpt-4o-mini\"\n", "openai = OpenAI()" ] }, { "cell_type": "code", "execution_count": 13, "id": "9eb2a0b7-771f-4140-9921-e7b2fb6829ae", "metadata": {}, "outputs": [], "source": [ "system_message = \"You are a helpful assistant for an Airline called FlightAI.\"\n", "system_message += \"Give short, courteous answers, no more than 1 sentence. \"\n", "system_message += \"Always be accurate. If you don't know the answer, say so.\"" ] }, { "cell_type": "code", "execution_count": 14, "id": "48ee97bf-f2ad-4ea3-b8a8-10686e56f9a6", "metadata": {}, "outputs": [], "source": [ "# Let's start by making a useful function\n", "\n", "ticket_prices = {\"new york\": \"$999\", \"london\": \"$799\", \"paris\": \"$899\", \"tokyo\": \"$1400\", \"berlin\": \"$499\", \"new delhi\": \"$1111\"}\n", "\n", "def get_ticket_price(destination_city):\n", " print(f\"Tool get_ticket_price called for {destination_city}\")\n", " city = destination_city.lower()\n", " return ticket_prices.get(city, \"Unknown\")" ] }, { "cell_type": "code", "execution_count": 15, "id": "20f1bae2-633b-4e69-accb-fcd99a452ea3", "metadata": {}, "outputs": [], "source": [ "# There's a particular dictionary structure that's required to describe our function:\n", "\n", "price_function = {\n", " \"name\": \"get_ticket_price\",\n", " \"description\": \"Get the price of a return ticket to the destination city. Call this whenever you need to know the ticket price, for example when a customer asks 'How much is a ticket to this city'\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"destination_city\": {\n", " \"type\": \"string\",\n", " \"description\": \"The city that the customer wants to travel to\",\n", " },\n", " },\n", " \"required\": [\"destination_city\"],\n", " \"additionalProperties\": False\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 16, "id": "e0b28d2f-8f33-4627-99d6-7588d1d07077", "metadata": {}, "outputs": [], "source": [ "#Assignment -- adding one more tool to book a flight\n", "\n", "flight_booking_status = {\"new york\": \"blocked\", \"london\": \"booked\", \"paris\": \"can't be booked\", \"tokyo\": \"pending\", \"berlin\": \"payment issue\", \"new delhi\": \"cancelled\" }\n", "\n", "#this is a tool function that will be used by a GPT model\n", "\n", "def get_flight_booking_status(destination_city):\n", " print(f\"Tool get_flight_booking_status for {destination_city}\")\n", " city = destination_city.lower()\n", " return flight_booking_status.get(city, \"Unknown\")\n", " " ] }, { "cell_type": "code", "execution_count": 17, "id": "b5edcb58-06f6-47b9-8afd-25f786552862", "metadata": {}, "outputs": [], "source": [ "# Describe metadata about the above tool function so that model can understand it\n", "\n", "flight_booking_status_function = {\n", " \"name\": \"get_flight_booking_status\",\n", " \"description\": \"Book a flight and get its booking status. Show your answer in polite way, like flight booked, cancelled, pending or can't be booked etc...'\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"destination_city\": {\n", " \"type\": \"string\",\n", " \"description\": \"The city that the customer wants to book a flight for\",\n", " },\n", " },\n", " \"required\": [\"destination_city\"],\n", " \"additionalProperties\": False\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 18, "id": "c9bae64c-6f58-4eea-8db6-7117572f6ef3", "metadata": {}, "outputs": [], "source": [ "# Some imports for handling images\n", "\n", "import base64\n", "from io import BytesIO\n", "from PIL import Image" ] }, { "cell_type": "code", "execution_count": 19, "id": "6884b0db-2158-4bab-a492-56a8d7a20190", "metadata": {}, "outputs": [], "source": [ "def artist(city):\n", " image_response = openai.images.generate(\n", " model=\"dall-e-3\",\n", " prompt=f\"An image representing a vacation in {city}, showing tourist spots and everything unique about {city}, in a vibrant pop-art style\",\n", " size=\"1024x1024\",\n", " n=1,\n", " response_format=\"b64_json\",\n", " )\n", " image_base64 = image_response.data[0].b64_json\n", " image_data = base64.b64decode(image_base64)\n", " return Image.open(BytesIO(image_data))" ] }, { "cell_type": "code", "execution_count": 20, "id": "64b3bc28-a45a-46d1-ab52-5c8868f68381", "metadata": {}, "outputs": [], "source": [ "from pydub import AudioSegment\n", "from pydub.playback import play\n", "\n", "def talker(message):\n", " response = openai.audio.speech.create(\n", " model=\"tts-1\",\n", " voice=\"alloy\", # Also, try replacing onyx with alloy\n", " input=message\n", " )\n", " \n", " audio_stream = BytesIO(response.content)\n", " audio = AudioSegment.from_file(audio_stream, format=\"mp3\")\n", " play(audio)" ] }, { "cell_type": "code", "execution_count": 21, "id": "1eb4a6a2-2c69-4633-a51e-da6a6ee0e96b", "metadata": {}, "outputs": [], "source": [ "#2 tools - add the above booking status function to the tools list\n", "tools = [{\"type\": \"function\", \"function\": price_function}, {\"type\": \"function\", \"function\": flight_booking_status_function}]" ] }, { "cell_type": "code", "execution_count": 22, "id": "0f95bf11-0b05-424d-b721-f27551ef33f9", "metadata": {}, "outputs": [], "source": [ "# We have to write a new function handle_multi_tool_calls to handle both the tools:\n", "\n", "def handle_multi_tool_calls(message):\n", " tool_call = message.tool_calls[0]\n", " arguments = json.loads(tool_call.function.arguments)\n", " city = arguments.get('destination_city')\n", " price = get_ticket_price(city)\n", " booking_status = get_flight_booking_status(city)\n", " response = {\n", " \"role\": \"tool\",\n", " \"content\": json.dumps({\"destination_city\": city,\"price\": price, \"booking_status\": booking_status}),\n", " \"tool_call_id\": message.tool_calls[0].id\n", " }\n", " return response, city" ] }, { "cell_type": "code", "execution_count": 23, "id": "b2b5f074-b19c-401b-bdf8-9414b2a81fd0", "metadata": {}, "outputs": [], "source": [ "#update the chat function to use both the tools\n", "def chat(history):\n", " messages = [{\"role\": \"system\", \"content\": system_message}] + history\n", " response = openai.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n", " image = None\n", " \n", " if response.choices[0].finish_reason==\"tool_calls\":\n", " message = response.choices[0].message\n", " response, city = handle_multi_tool_calls(message)\n", " image = city\n", " messages.append(message)\n", " messages.append(response)\n", " image = artist(city)\n", " response = openai.chat.completions.create(model=MODEL, messages=messages)\n", " \n", " reply = response.choices[0].message.content\n", " history += [{\"role\":\"assistant\", \"content\":reply}]\n", "\n", " # Comment out or delete the next line if you'd rather skip Audio for now..\n", " talker(reply)\n", " \n", " return history, image" ] }, { "cell_type": "code", "execution_count": 24, "id": "9db28315-7aae-4a44-8711-73d12272e5e9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "* Running on local URL: http://127.0.0.1:7868\n", "\n", "To create a public link, set `share=True` in `launch()`.\n" ] }, { "data": { "text/html": [ "
" ], "text/plain": [ "