From 316260aa6843c7502357b5bfc4fbdd51c85e5516 Mon Sep 17 00:00:00 2001 From: ariel1985 Date: Wed, 9 Apr 2025 00:41:22 +0300 Subject: [PATCH] Add eCommerce Assistant notebook for product price retrieval week2 --- ...day4-ecommerce-project-fullyCustomiz.ipynb | 653 ++++++++++++++++++ ...commerce-chatbot-assistant-and-agent.ipynb | 293 ++++++++ 2 files changed, 946 insertions(+) create mode 100644 week2/community-contributions/day4-ecommerce-project-fullyCustomiz.ipynb create mode 100644 week2/community-contributions/week2-commerce-chatbot-assistant-and-agent.ipynb diff --git a/week2/community-contributions/day4-ecommerce-project-fullyCustomiz.ipynb b/week2/community-contributions/day4-ecommerce-project-fullyCustomiz.ipynb new file mode 100644 index 0000000..a3eaf17 --- /dev/null +++ b/week2/community-contributions/day4-ecommerce-project-fullyCustomiz.ipynb @@ -0,0 +1,653 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ddfa9ae6-69fe-444a-b994-8c4c5970a7ec", + "metadata": {}, + "source": [ + "# ๐Ÿ› ๏ธ Project โ€“ Art-Tech Store AI Assistant\n", + "\n", + "## ๐Ÿ›๏ธ Product Availability\n", + "- Check availability of **printers** and **printer papers** with:\n", + " - Product name, price, brand, type (e.g., laser/inkjet), and stock status.\n", + "- Alerts user if a product is out of stock.\n", + "\n", + "## ๐Ÿงญ Guided Shopping Experience\n", + "- Guides users through:\n", + " 1. Choosing product category (printer or paper)\n", + " 2. Filtering options (brand, price range, type)\n", + " 3. Adding selected products to cart\n", + "- Ensures correct input for smooth shopping flow.\n", + "\n", + "## ๐Ÿงพ Receipt Generation\n", + "- Creates a unique receipt file: `customerName_orderNumber.txt`\n", + "- Receipt includes:\n", + " - Customer name and contact\n", + " - Product details (name, price, quantity)\n", + " - Total cost and order summary\n", + "\n", + "## ๐Ÿ“ฆ Generate Order Summary Report\n", + "- Summarizes all purchases into a single file: `order_summary.txt`\n", + "- Useful for inventory and sales review\n", + "\n", + "## ๐ŸŽฏ Product Recommendation\n", + "- Recommends:\n", + " - Printers based on paper type, usage (home/office), or brand preference\n", + " - Compatible paper based on selected printer\n", + "\n", + "## ๐Ÿ’ฌ Interactive Chat Interface\n", + "- Real-time conversation via **Gradio**\n", + "- Polite, helpful answers to product-related questions\n", + "\n", + "## ๐Ÿ› ๏ธ Modular Tool Support\n", + "- Integrated tools for:\n", + " - Checking product availability\n", + " - Adding to cart and generating receipts\n", + " - Creating summary reports\n", + "- Easily extendable for:\n", + " - Promotions\n", + " - Customer reviews\n", + " - Delivery tracking\n", + "\n", + "## ๐Ÿ›ก๏ธ Error Handling\n", + "- Validates user inputs (e.g., product name, quantity)\n", + "- Graceful messages to guide user and prevent mistakes\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "747e8786-9da8-4342-b6c9-f5f69c2e22ae", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "import random\n", + "from dotenv import load_dotenv\n", + "import gradio as gr\n", + "from openai import OpenAI\n", + "\n", + "load_dotenv()\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", + "MODEL = \"gpt-3.5-turbo\"\n", + "openai = OpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6072536-eee1-4f87-9f03-8dc88dc04f1a", + "metadata": {}, + "outputs": [], + "source": [ + "# Using local LLM (that can't even handle basic greeting like Hi!!\n", + "\n", + "# MODEL = \"llama3.2\"\n", + "# openai = OpenAI(base_url='http://localhost:11434/v1', api_key='ollama')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fe5f5d7-0bd1-41a2-a654-59b587882f22", + "metadata": {}, + "outputs": [], + "source": [ + "###############################################################################\n", + "# 1) System Prompt\n", + "###############################################################################\n", + "system_message = (\n", + " \"You are a helpful assistant for an online store called art-tech.store that sells printers and printer papers.\\n\\n\"\n", + " \"When the user wants to purchase a product, follow these steps:\\n\"\n", + " \"1. Ask whether they are interested in printers or printer papers.\\n\"\n", + " \"2. Ask for filtering preferences (e.g., brand, price range, type).\\n\"\n", + " \"3. Call the function 'check_product_availability' with the selected category and filters.\\n\"\n", + " \" - If it returns an empty list, say: 'No products found for your selection.'\\n\"\n", + " \" - If it returns products, list them EXACTLY, in a numbered list, showing name, price, brand, and availability.\\n\"\n", + " \"4. Wait for the user to select a product by number and quantity.\\n\"\n", + " \"5. Ask for customer first name and contact info.\\n\"\n", + " \"6. Then call 'add_to_cart_and_generate_receipt' to confirm and show the user the receipt and order details.\\n\\n\"\n", + " \"You also have a tool 'generate_report' which summarizes ALL purchases in a single file.\\n\\n\"\n", + " \"IMPORTANT:\\n\"\n", + " \"- Always call 'check_product_availability' if user mentions a new category or changes filters.\\n\"\n", + " \"- Do not invent products or details. Use only what the function calls return.\\n\"\n", + " \"- Every time an order is placed, produce a new receipt file named customerName_orderNumber.txt.\\n\"\n", + " \"- If no matching products are found, say so.\\n\"\n", + " \"- If the user wants a full order summary, call 'generate_report' with no arguments.\\n\"\n", + " \"If you don't know something, say so.\\n\"\n", + " \"Keep answers short and courteous.\\n\"\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61a2a15d-b559-4844-b377-6bd5cb4949f6", + "metadata": {}, + "outputs": [], + "source": [ + "###############################################################################\n", + "# 2) Mini Printer Availability with Price & Stock\n", + "###############################################################################\n", + "product_availability = {\n", + " \"mini_printers\": [\n", + " {\n", + " \"name\": \"Phomemo M110 ืžื“ืคืกืช ืžื“ื‘ืงื•ืช ื—ื›ืžื”\",\n", + " \"brand\": \"Phomemo\",\n", + " \"price\": \"โ‚ช300\", # Update if pricing is known\n", + " \"type\": \"Label Printer\",\n", + " \"availability\": \"360,745 in stock (24 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"Niimbot B1 Label Printer\",\n", + " \"brand\": \"Niimbot\",\n", + " \"price\": \"โ‚ช350\",\n", + " \"type\": \"Portable Thermal Label Printer\",\n", + " \"availability\": \"13,029 in stock (18 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"Niimbot B21 Mini Portable Thermal Label Printer\",\n", + " \"brand\": \"Niimbot\",\n", + " \"price\": \"โ‚ช500\",\n", + " \"type\": \"Adhesive Sticker Printer\",\n", + " \"availability\": \"141 in stock (12 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"Dolewa D3 Portable Mini Printer\",\n", + " \"brand\": \"Dolewa\",\n", + " \"price\": \"โ‚ช450\",\n", + " \"type\": \"Thermal Photo & Label Printer\",\n", + " \"availability\": \"336 in stock (6 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"PrintPro Mini ืžื“ืคืกืช ื›ื™ืก ื—ื›ืžื”\",\n", + " \"brand\": \"PrintPro\",\n", + " \"price\": \"โ‚ช550\",\n", + " \"type\": \"Mini Pocket Printer\",\n", + " \"availability\": \"336 in stock (6 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"ืžื™ื ื™ ืžื“ืคืกืช ื˜ืจืžื™ืช ืžืขื•ืฆื‘ืช ืœื™ืœื“ื™ื\",\n", + " \"brand\": \"Art-Tech\",\n", + " \"price\": \"โ‚ช200\",\n", + " \"type\": \"Kids Thermal Printer\",\n", + " \"availability\": \"62 in stock (11 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"Children Digital Camera Instant Print\",\n", + " \"brand\": \"Art-Tech\",\n", + " \"price\": \"โ‚ช250\",\n", + " \"type\": \"Photo Printing Camera with 32G Memory Card\",\n", + " \"availability\": \"160 in stock (3 variants)\"\n", + " }\n", + " ],\n", + " \"mini_printer_papers\": [\n", + " {\n", + " \"name\": \"HP Printer Paper 8.5x11, 500 Sheets\", # example only\n", + " \"brand\": \"HP\",\n", + " \"price\": \"$9.99\",\n", + " \"type\": \"Standard\",\n", + " \"availability\": \"In stock\"\n", + " },\n", + " {\n", + " \"name\": \"Mini Printer Paper 57*25mm Color Sticker\",\n", + " \"brand\": \"Art-Tech\",\n", + " \"price\": \"โ‚ช70\",\n", + " \"type\": \"Self-adhesive Color Label Paper\",\n", + " \"availability\": \"71,996 in stock (9 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"ืžื“ื‘ืงื•ืช ืฉืงื•ืคื•ืช ืœืžื“ืคืกืช ื˜ืจืžื™ืช\",\n", + " \"brand\": \"Art-Tech\",\n", + " \"price\": \"โ‚ช55\",\n", + " \"type\": \"Transparent Labels\",\n", + " \"availability\": \"11,762 in stock (12 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"ื’ืœื™ืœื™ ื ื™ื™ืจ ืžื“ื‘ืงื”\",\n", + " \"brand\": \"Art-Tech\",\n", + " \"price\": \"โ‚ช40\",\n", + " \"type\": \"Sticker Paper Rolls\",\n", + " \"availability\": \"42 in stock (4 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"Niimbot B21/B1/B3S Thermal Label Sticker Paper\",\n", + " \"brand\": \"Niimbot\",\n", + " \"price\": \"โ‚ช55\",\n", + " \"type\": \"Printable White Label Paper 20โ€“50mm\",\n", + " \"availability\": \"1,342 in stock (14 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"Mini Printer Sticker Paper 25X57mm\",\n", + " \"brand\": \"Paperang-compatible\",\n", + " \"price\": \"โ‚ช65\",\n", + " \"type\": \"Color Self-Adhesive Thermal Rolls\",\n", + " \"availability\": \"3,023 in stock (20 variants)\"\n", + " },\n", + " {\n", + " \"name\": \"3/5/10 NiiMBOT White Label Paper Rolls\",\n", + " \"brand\": \"Niimbot\",\n", + " \"price\": \"โ‚ช40\",\n", + " \"type\": \"Waterproof Self-adhesive Rolls\",\n", + " \"availability\": \"1,400 in stock (9 variants)\"\n", + " }\n", + " ]\n", + "}\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0696acb1-0b05-4dc2-80d5-771be04f1fb2", + "metadata": {}, + "outputs": [], + "source": [ + "# A global list of flight bookings\n", + "flight_bookings = []\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80ca4e09-6287-4d3f-997d-fa6afbcf6c85", + "metadata": {}, + "outputs": [], + "source": [ + "###############################################################################\n", + "# 3) Helper Functions for Art-Tech Store\n", + "###############################################################################\n", + "\n", + "product_orders = []\n", + "\n", + "def check_product_availability(category: str, filters: dict = None):\n", + " \"\"\"\n", + " Return list of products in the given category from 'product_availability'.\n", + " Optionally filter by brand, type, etc.\n", + " \"\"\"\n", + " print(f\"[TOOL] check_product_availability({category}, {filters=})\")\n", + " category = category.lower()\n", + " products = product_availability.get(category, [])\n", + " \n", + " if filters:\n", + " for key, val in filters.items():\n", + " products = [p for p in products if p.get(key, \"\").lower() == val.lower()]\n", + " return products\n", + "\n", + "\n", + "def add_to_cart_and_generate_receipt(customer_name: str, contact: str, product: dict, quantity: int, order_number: int):\n", + " \"\"\"\n", + " Create a text file: customerName_orderNumber.txt containing order details.\n", + " \"\"\"\n", + " safe_name = customer_name.replace(\" \", \"_\")\n", + " filename = f\"{safe_name}_{order_number}.txt\"\n", + "\n", + " content = (\n", + " \"Art-Tech Store Receipt\\n\"\n", + " \"=======================\\n\"\n", + " f\"Order # : {order_number}\\n\"\n", + " f\"Customer : {customer_name}\\n\"\n", + " f\"Contact : {contact}\\n\"\n", + " f\"Product : {product['name']}\\n\"\n", + " f\"Brand : {product['brand']}\\n\"\n", + " f\"Type : {product.get('type', 'N/A')}\\n\"\n", + " f\"Price : {product['price']}\\n\"\n", + " f\"Quantity : {quantity}\\n\"\n", + " f\"Availability: {product['availability']}\\n\"\n", + " )\n", + " with open(filename, \"w\") as f:\n", + " f.write(content)\n", + "\n", + " print(f\"[TOOL] Receipt file generated => {filename}\")\n", + " return filename\n", + "\n", + "\n", + "def place_order(category, product_index, quantity, customer_name, contact_info):\n", + " \"\"\"\n", + " Places an order for a product by index in the filtered list.\n", + " \"\"\"\n", + " print(f\"[TOOL] place_order({category=}, {product_index=}, {quantity=})\")\n", + "\n", + " try:\n", + " idx = int(product_index)\n", + " except ValueError:\n", + " return \"Error: Product option number is not a valid integer.\"\n", + "\n", + " products = product_availability.get(category.lower(), [])\n", + " if not products:\n", + " return f\"Error: No products found in category '{category}'.\"\n", + "\n", + " pick = idx - 1\n", + " if pick < 0 or pick >= len(products):\n", + " return f\"Error: Invalid product option #{idx} for category '{category}'.\"\n", + "\n", + " selected_product = products[pick]\n", + "\n", + " order = {\n", + " \"category\": category,\n", + " \"product\": selected_product[\"name\"],\n", + " \"brand\": selected_product[\"brand\"],\n", + " \"type\": selected_product.get(\"type\", \"\"),\n", + " \"price\": selected_product[\"price\"],\n", + " \"quantity\": quantity,\n", + " \"customer_name\": customer_name,\n", + " \"contact\": contact_info,\n", + " }\n", + " product_orders.append(order)\n", + "\n", + " order_number = len(product_orders)\n", + " receipt_filename = add_to_cart_and_generate_receipt(customer_name, contact_info, selected_product, quantity, order_number)\n", + "\n", + " confirmation = (\n", + " f\"Order #{order_number} confirmed for {customer_name}. \"\n", + " f\"{selected_product['name']} x{quantity}. Receipt saved to {receipt_filename}.\"\n", + " )\n", + " print(f\"[TOOL] {confirmation}\")\n", + " return confirmation\n", + "\n", + "\n", + "def generate_report():\n", + " \"\"\"\n", + " Summarize ALL orders in a single file called order_summary.txt.\n", + " \"\"\"\n", + " print(f\"[TOOL] generate_report called.\")\n", + "\n", + " report_content = \"Art-Tech Store Order Summary Report\\n\"\n", + " report_content += \"===================================\\n\"\n", + "\n", + " if not product_orders:\n", + " report_content += \"No orders found.\\n\"\n", + " else:\n", + " for i, order in enumerate(product_orders, start=1):\n", + " report_content += (\n", + " f\"Order # : {i}\\n\"\n", + " f\"Customer : {order['customer_name']}\\n\"\n", + " f\"Contact : {order['contact']}\\n\"\n", + " f\"Product : {order['product']}\\n\"\n", + " f\"Brand : {order['brand']}\\n\"\n", + " f\"Type : {order['type']}\\n\"\n", + " f\"Price : {order['price']}\\n\"\n", + " f\"Quantity : {order['quantity']}\\n\"\n", + " \"-------------------------\\n\"\n", + " )\n", + "\n", + " filename = \"order_summary.txt\"\n", + " with open(filename, \"w\") as f:\n", + " f.write(report_content)\n", + "\n", + " msg = f\"Summary report generated => {filename}\"\n", + " print(f\"[TOOL] {msg}\")\n", + " return msg\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39fb9008", + "metadata": {}, + "outputs": [], + "source": [ + "###############################################################################\n", + "# 4) Tools JSON Schemas for Art-Tech Store\n", + "###############################################################################\n", + "price_function = {\n", + " \"name\": \"get_product_price\",\n", + " \"description\": \"Get the price of a product (not strictly needed now).\",\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"category\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Product category such as 'mini_printers' or 'mini_printer_papers'.\",\n", + " },\n", + " \"product_name\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Name of the product to check price for.\",\n", + " },\n", + " },\n", + " \"required\": [\"category\", \"product_name\"],\n", + " },\n", + "}\n", + "\n", + "availability_function = {\n", + " \"name\": \"check_product_availability\",\n", + " \"description\": (\n", + " \"Check availability of products in a category. \"\n", + " \"Returns a list of {name, brand, price, type, availability}.\"\n", + " ),\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"category\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Category of products to check (e.g., 'mini_printers').\",\n", + " },\n", + " \"filters\": {\n", + " \"type\": \"object\",\n", + " \"description\": \"Optional filters like brand or type.\",\n", + " },\n", + " },\n", + " \"required\": [\"category\"],\n", + " },\n", + "}\n", + "\n", + "book_function = {\n", + " \"name\": \"place_order\",\n", + " \"description\": (\n", + " \"Place an order using an index for the chosen product. \"\n", + " \"Generates a unique receipt file customerName_{orderNumber}.txt.\"\n", + " ),\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"category\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Product category (e.g., 'mini_printers').\",\n", + " },\n", + " \"product_index\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"1-based index of selected product from availability list.\",\n", + " },\n", + " \"quantity\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Quantity to order.\",\n", + " },\n", + " \"customer_name\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Customer's full name.\",\n", + " },\n", + " \"contact_info\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"Customer's contact information (email or phone).\",\n", + " },\n", + " },\n", + " \"required\": [\"category\", \"product_index\", \"quantity\", \"customer_name\", \"contact_info\"],\n", + " },\n", + "}\n", + "\n", + "report_function = {\n", + " \"name\": \"generate_report\",\n", + " \"description\": (\n", + " \"Generates a summary report of ALL orders in order_summary.txt.\"\n", + " ),\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {},\n", + " \"required\": [],\n", + " },\n", + "}\n", + "\n", + "tools = [\n", + " {\"type\": \"function\", \"function\": price_function},\n", + " {\"type\": \"function\", \"function\": availability_function},\n", + " {\"type\": \"function\", \"function\": book_function},\n", + " {\"type\": \"function\", \"function\": report_function},\n", + "]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f003836", + "metadata": {}, + "outputs": [], + "source": [ + "###############################################################################\n", + "# 5) Handle Tool Calls for Art-Tech Store\n", + "###############################################################################\n", + "def handle_tool_call(message):\n", + " \"\"\"\n", + " The LLM can request to call a function in 'tools'. We parse the JSON arguments\n", + " and run the corresponding Python function. Then we return a 'tool' message with the result.\n", + " \"\"\"\n", + " tool_call = message.tool_calls[0]\n", + " fn_name = tool_call.function.name\n", + " args = json.loads(tool_call.function.arguments)\n", + "\n", + " if fn_name == \"get_product_price\":\n", + " category = args.get(\"category\")\n", + " product_name = args.get(\"product_name\")\n", + " products = product_availability.get(category.lower(), [])\n", + " price = \"Not found\"\n", + " for p in products:\n", + " if p[\"name\"].lower() == product_name.lower():\n", + " price = p[\"price\"]\n", + " break\n", + " response_content = {\"category\": category, \"product_name\": product_name, \"price\": price}\n", + "\n", + " elif fn_name == \"check_product_availability\":\n", + " category = args.get(\"category\")\n", + " filters = args.get(\"filters\", {})\n", + " products = check_product_availability(category, filters)\n", + " response_content = {\"category\": category, \"availability\": products}\n", + "\n", + " elif fn_name == \"place_order\":\n", + " category = args.get(\"category\")\n", + " product_index = args.get(\"product_index\")\n", + " quantity = args.get(\"quantity\")\n", + " customer_name = args.get(\"customer_name\")\n", + " contact_info = args.get(\"contact_info\")\n", + "\n", + " confirmation = place_order(category, product_index, quantity, customer_name, contact_info)\n", + " response_content = {\n", + " \"category\": category,\n", + " \"product_index\": product_index,\n", + " \"quantity\": quantity,\n", + " \"customer_name\": customer_name,\n", + " \"contact_info\": contact_info,\n", + " \"confirmation\": confirmation,\n", + " }\n", + "\n", + " elif fn_name == \"generate_report\":\n", + " msg = generate_report()\n", + " response_content = {\"report\": msg}\n", + "\n", + " else:\n", + " response_content = {\"error\": f\"Unknown tool: {fn_name}\"}\n", + "\n", + " return {\n", + " \"role\": \"tool\",\n", + " \"content\": json.dumps(response_content),\n", + " \"tool_call_id\": tool_call.id,\n", + " }, args\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6b34b32", + "metadata": {}, + "outputs": [], + "source": [ + "###############################################################################\n", + "# 6) Main Chat Function for Art-Tech Store\n", + "###############################################################################\n", + "def chat(message, history):\n", + " \"\"\"\n", + " The main chat loop that handles the conversation with the user,\n", + " passing 'tools' definitions to the LLM for function calling.\n", + " \"\"\"\n", + " messages = [{\"role\": \"system\", \"content\": system_message}] + history + [{\"role\": \"user\", \"content\": message}]\n", + "\n", + " try:\n", + " response = openai.chat.completions.create(\n", + " model=MODEL,\n", + " messages=messages,\n", + " tools=tools\n", + " )\n", + "\n", + " # If the LLM requests a function call, handle it\n", + " while response.choices[0].finish_reason == \"tool_calls\":\n", + " msg = response.choices[0].message\n", + " print(f\"[INFO] Tool call requested: {msg.tool_calls[0]}\")\n", + " tool_response, tool_args = handle_tool_call(msg)\n", + " print(f\"[INFO] Tool response: {tool_response}\")\n", + "\n", + " # Add both the LLM's request and our tool response to the conversation\n", + " messages.append(msg)\n", + " messages.append(tool_response)\n", + "\n", + " # Re-send updated conversation to get final or next step\n", + " response = openai.chat.completions.create(\n", + " model=MODEL,\n", + " messages=messages\n", + " )\n", + "\n", + " # Return normal text response (finish_reason = \"stop\")\n", + " return response.choices[0].message.content\n", + "\n", + " except Exception as e:\n", + " print(f\"[ERROR] {e}\")\n", + " return \"I'm sorry, something went wrong while processing your request.\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cea4b097", + "metadata": {}, + "outputs": [], + "source": [ + "###############################################################################\n", + "# 7) Launch Gradio\n", + "###############################################################################\n", + "gr.ChatInterface(fn=chat, type=\"messages\").launch()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b39d5a6", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/week2/community-contributions/week2-commerce-chatbot-assistant-and-agent.ipynb b/week2/community-contributions/week2-commerce-chatbot-assistant-and-agent.ipynb new file mode 100644 index 0000000..0304371 --- /dev/null +++ b/week2/community-contributions/week2-commerce-chatbot-assistant-and-agent.ipynb @@ -0,0 +1,293 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e3a5643a-c247-4a9b-8c57-ec9b1e89c088", + "metadata": {}, + "source": [ + "# Week 2 - eCommerce Assistant for products price from dictionary\n", + "\n", + "An eCommerce assitant that can get a product price\n", + "\n", + "Gradio for chat box" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "756573b3-72b2-4102-a773-91c278e5c4fd", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# !ollama pull llama3.2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57ae8d30-f7aa-47a3-bab8-b7002e87a8f7", + "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": null, + "id": "ef7f8b2b-1d6a-4bbd-858a-be187ccfc02a", + "metadata": {}, + "outputs": [], + "source": [ + "# Initialization\n", + "\n", + "load_dotenv(override=True)\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()\n", + "\n", + "# As an alternative, if you'd like to use Ollama instead of OpenAI\n", + "# Check that Ollama is running for you locally (see week1/day2 exercise) then uncomment these next 2 lines\n", + "# MODEL = \"llama3.2\"\n", + "# openai = OpenAI(base_url='http://localhost:11434/v1', api_key='ollama')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b46dd52-8a3c-42d1-ac24-59f5eb5aaba1", + "metadata": {}, + "outputs": [], + "source": [ + "system_message = \"You are a helpful assistant for an online store called CommerceAI. \"\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": null, + "id": "31a6431b-215d-4f46-b813-971d8af7e034", + "metadata": {}, + "outputs": [], + "source": [ + "# This function looks rather simpler than the one from my video, because we're taking advantage of the latest Gradio updates\n", + "\n", + "def chat(message, history):\n", + " messages = [{\"role\": \"system\", \"content\": system_message}] + history + [{\"role\": \"user\", \"content\": message}]\n", + " response = openai.chat.completions.create(model=MODEL, messages=messages)\n", + " return response.choices[0].message.content\n", + "\n", + "gr.ChatInterface(fn=chat, type=\"messages\").launch()" + ] + }, + { + "cell_type": "markdown", + "id": "d3586bfb-acc3-4b5e-95be-02120b696f98", + "metadata": {}, + "source": [ + "## Tools\n", + "\n", + "Tools are an incredibly powerful feature provided by the frontier LLMs.\n", + "\n", + "With tools, you can write a function, and have the LLM call that function as part of its response.\n", + "\n", + "Sounds almost spooky.. we're giving it the power to run code on our machine?\n", + "\n", + "Well, kinda." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9ac43e8-9880-44f6-b03a-6d1ab05bbb94", + "metadata": {}, + "outputs": [], + "source": [ + "# Let's start by making a useful function\n", + "\n", + "items_prices = {f\"item{i}\": f\"{i*100}\" for i in range(1,6)}\n", + "\n", + "items_prices = {\"printer\": \"$500\", \"paper\": \"$10\", \"mini printer\": \"$50\", \"label printer\": \"$60\", \"sticker-paper\": \"$5\"}\n", + "\n", + "def get_item_price(product):\n", + " print(f\"Tool get_item_price called for {product}\")\n", + " item = product.lower()\n", + " return items_prices.get(item, \"Unknown\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88d249f7-787d-4750-b5b9-7df108da1b57", + "metadata": {}, + "outputs": [], + "source": [ + "items_prices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef3c3897-9a57-4f04-b5d0-f9ac8bb02d00", + "metadata": {}, + "outputs": [], + "source": [ + "get_item_price(\"mini printer\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "511ef9b8-bec0-4f14-b647-057e14c849cc", + "metadata": {}, + "outputs": [], + "source": [ + "# There's a particular dictionary structure that's required to describe our function:\n", + "\n", + "price_function = {\n", + " \"name\": \"get_item_price\",\n", + " \"description\": \"Get the price of an item in the store. \\\n", + " Call this whenever you need to know the store item price , \\\n", + " for example when a customer asks 'How much is a mini printer' \",\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"product\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"The item that the customer wants to buy\"\n", + " },\n", + " },\n", + " \"required\": [\"product\"],\n", + " \"additionalProperties\": False\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86f674a4-4b7c-443d-b025-0f016932508a", + "metadata": {}, + "outputs": [], + "source": [ + "# And this is included in a list of tools:\n", + "\n", + "tools = [{\"type\": \"function\", \"function\": price_function}]" + ] + }, + { + "cell_type": "markdown", + "id": "724d0f89-8a86-493e-8cd1-73814688a70b", + "metadata": {}, + "source": [ + "## Getting OpenAI to use our Tool\n", + "\n", + "There's some fiddly stuff to allow OpenAI \"to call our tool\"\n", + "\n", + "What we actually do is give the LLM the opportunity to inform us that it wants us to run the tool.\n", + "\n", + "Here's how the new chat function looks:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d67fb72-132e-499e-9931-86cb71b634b6", + "metadata": {}, + "outputs": [], + "source": [ + "def chat(message, history):\n", + " messages = [{\"role\": \"system\", \"content\": system_message}] + history + [{\"role\": \"user\", \"content\": message}]\n", + " response = openai.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n", + "\n", + " if response.choices[0].finish_reason==\"tool_calls\":\n", + " message = response.choices[0].message\n", + " response, item = handle_tool_call(message)\n", + " print('response', response, 'item', item)\n", + " messages.append(message)\n", + " messages.append(response)\n", + " response = openai.chat.completions.create(model=MODEL, messages=messages)\n", + " \n", + " return response.choices[0].message.content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b4de767-954a-4077-a5f7-0055a0b90393", + "metadata": {}, + "outputs": [], + "source": [ + "# We have to write that function handle_tool_call:\n", + "\n", + "def handle_tool_call(message):\n", + " tool_call = message.tool_calls[0]\n", + " arguments = json.loads(tool_call.function.arguments)\n", + " item = arguments.get('product') \n", + " print('product', item)\n", + " price = get_item_price(item)\n", + " response = {\n", + " \"role\": \"tool\",\n", + " \"content\": json.dumps({\"item\": item,\"price\": price}),\n", + " \"tool_call_id\": tool_call.id\n", + " }\n", + " return response, item\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ffda702-6ac5-4d13-9703-a14fa93aea68", + "metadata": {}, + "outputs": [], + "source": [ + "gr.ChatInterface(fn=chat, type=\"messages\").launch()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ae2edbf-de58-43fa-b380-267cfc1755de", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}