|
| 1 | +""" |
| 2 | +Customer chatbot. |
| 3 | +
|
| 4 | +Input: |
| 5 | +Users can submit queries that describe the type of books they are looking for, e.g., suggest fiction novels. |
| 6 | +Users can also ask the chat assistant for books by specific author, e.g., recommend books by Dan Brown. |
| 7 | +Answers to user's queries will be based on the Goodreads dataset: |
| 8 | +https://www.kaggle.com/datasets/cristaliss/ultimate-book-collection-top-100-books-up-to-2023 |
| 9 | +
|
| 10 | +Application logic: |
| 11 | +The app determines the closest matches by comparing user query's embedding to the available |
| 12 | +book embeddings. Embeddings of books are pre-computed on the description column of every book |
| 13 | +and stored in the assets/ folder. |
| 14 | +
|
| 15 | +Response: |
| 16 | +The chat assistant then determines the top relevant answers shortlisted by comparing embeddings and |
| 17 | +provides the top 5 recommendations. |
| 18 | +""" |
| 19 | + |
| 20 | +import json |
| 21 | + |
| 22 | +import panel as pn |
| 23 | +from openai import OpenAI |
| 24 | +from scipy.spatial import KDTree |
| 25 | +import numpy as np |
| 26 | +import pandas as pd |
| 27 | +from pathlib import Path |
| 28 | +from _wandb import WeightsBiasesTracking |
| 29 | +import datetime |
| 30 | + |
| 31 | +from util import get_embedding_from_text |
| 32 | + |
| 33 | +ORDER_CANCEL = False |
| 34 | + |
| 35 | +WEIGHTS_AND_BIASES_TRACKING = False |
| 36 | + |
| 37 | +if WEIGHTS_AND_BIASES_TRACKING: |
| 38 | + wandb_client = WeightsBiasesTracking() |
| 39 | + |
| 40 | + |
| 41 | +client = OpenAI() |
| 42 | + |
| 43 | +df = pd.read_csv("orders.csv") |
| 44 | + |
| 45 | + |
| 46 | +def detect_order_number(user_query): |
| 47 | + system_prompt = f""" |
| 48 | + You're a system that determines the invoice number or the order number in the user query. |
| 49 | +
|
| 50 | + You need to return only the order number.If no invoice number or order number is found |
| 51 | + return the string None. |
| 52 | +""" |
| 53 | + response = client.chat.completions.create( |
| 54 | + model="gpt-3.5-turbo", |
| 55 | + messages=[ |
| 56 | + {"role": "system", "content": system_prompt}, |
| 57 | + {"role": "user", "content": "What were the items in order 536365?"}, |
| 58 | + {"role": "system", "content": "536365"}, |
| 59 | + {"role": "user", "content": user_query}, |
| 60 | + ], |
| 61 | + seed=42, |
| 62 | + n=1, |
| 63 | + ) |
| 64 | + invoice_number = response.choices[0].message.content |
| 65 | + print(f"invoice_number: {invoice_number}") |
| 66 | + return invoice_number if invoice_number != "None" else "" |
| 67 | + |
| 68 | + |
| 69 | +def detect_intent(user_query): |
| 70 | + system_prompt = f""" |
| 71 | + You're a system that determines the intent of the user query. |
| 72 | +
|
| 73 | + You need to return only the intent and no additional sentences. |
| 74 | + If relevant intent is not found then return the string None. |
| 75 | +
|
| 76 | + Valid intents = ["TOTAL_ORDER_COST", "CANCEL", "NUMBER_OF_ITEMS", "ORDER_ITEM_DETAILS", "CUSTOMER_ORDERS"] |
| 77 | +""" |
| 78 | + response = client.chat.completions.create( |
| 79 | + model="gpt-3.5-turbo", |
| 80 | + messages=[ |
| 81 | + {"role": "system", "content": system_prompt}, |
| 82 | + {"role": "user", "content": "What is the total cost of the order 536365"}, |
| 83 | + {"role": "system", "content": "TOTAL_ORDER_COST"}, |
| 84 | + {"role": "user", "content": "total cost of all items in the order 536365"}, |
| 85 | + {"role": "system", "content": "TOTAL_ORDER_COST"}, |
| 86 | + {"role": "user", "content": "Which items were ordered in 536364"}, |
| 87 | + {"role": "system", "content": "ORDER_ITEM_DETAILS"}, |
| 88 | + {"role": "user", "content": "Can i cancel the order 458891"}, |
| 89 | + {"role": "system", "content": "CANCEL"}, |
| 90 | + {"role": "user", "content": "How many items were ordered in Invoice 558420"}, |
| 91 | + {"role": "system", "content": "NUMBER_OF_ITEMS"}, |
| 92 | + {"role": "user", "content": "Purchases made by Customer ID 17850"}, |
| 93 | + {"role": "system", "content": "CUSTOMER_ORDERS"}, |
| 94 | + {"role": "user", "content": user_query}, |
| 95 | + ], |
| 96 | + seed=42, |
| 97 | + n=1, |
| 98 | + ) |
| 99 | + intent = response.choices[0].message.content |
| 100 | + return intent if intent != None else "" |
| 101 | + |
| 102 | + |
| 103 | +def cancel_order(invoice_number): |
| 104 | + cancel = {} |
| 105 | + print(df.head()) |
| 106 | + invoice_details = df.loc[df['InvoiceNo'] == invoice_number] |
| 107 | + print(len(invoice_details)) |
| 108 | + if len(invoice_details) == 0: |
| 109 | + cancel['eligible'] = False |
| 110 | + cancel['reason'] = f"Order not found: {invoice_number}" |
| 111 | + ORDER_CANCEL = False |
| 112 | + return cancel |
| 113 | + |
| 114 | + invoice_date = invoice_details.iloc[0]['InvoiceDate'] |
| 115 | + print(f"invoice_date: {invoice_date}") |
| 116 | + date_object = datetime.datetime.strptime(invoice_date.split()[0], "%d/%m/%y") |
| 117 | + print(date_object) |
| 118 | + start_date = datetime.datetime(2010, 11, 1) |
| 119 | + end_date = datetime.datetime(2010, 12, 2) |
| 120 | + if start_date < date_object < end_date: |
| 121 | + cancel['eligible'] = True |
| 122 | + ORDER_CANCEL = True |
| 123 | + return cancel |
| 124 | + else: |
| 125 | + cancel['eligible'] = False |
| 126 | + cancel['reason'] = f"Order {invoice_number} not eligible for cancellation." |
| 127 | + ORDER_CANCEL = False |
| 128 | + return cancel |
| 129 | + |
| 130 | + |
| 131 | +def customer_chatbot_agent(user_query, verbose=False, tracking=False): |
| 132 | + """An agent that can respond to customer's queries regarding orders""" |
| 133 | + |
| 134 | + if ORDER_CANCEL and "yes" in user_query: |
| 135 | + ORDER_CANCEL = False |
| 136 | + return f"Order successfully cancelled" |
| 137 | + |
| 138 | + invoice_number = detect_order_number(user_query) |
| 139 | + if not invoice_number: |
| 140 | + return "Please provide an Order ID" |
| 141 | + intent = detect_intent(user_query) |
| 142 | + print(intent) |
| 143 | + if not intent: |
| 144 | + return "Please provide more details regarding the action you want to take on the order." |
| 145 | + |
| 146 | + if intent != "CANCEL": |
| 147 | + return "We only support order cancellation requests. " |
| 148 | + |
| 149 | + return cancel_order(invoice_number) |
| 150 | + |
| 151 | + return "Sorry, we couldn't understand your query!" |
| 152 | + |
| 153 | + # start_time_ms = datetime.datetime.now().timestamp() * 1000 |
| 154 | + # |
| 155 | + # response = client.chat.completions.create( |
| 156 | + # model="gpt-3.5-turbo", |
| 157 | + # messages=[ |
| 158 | + # {"role": "system", "content": system_prompt}, |
| 159 | + # {"role": "user", "content": user_query}, |
| 160 | + # ], |
| 161 | + # seed=42, |
| 162 | + # n=1, |
| 163 | + # ) |
| 164 | + # |
| 165 | + # end_time_ms = round(datetime.datetime.now().timestamp() * 1000) |
| 166 | + # |
| 167 | + # if tracking: |
| 168 | + # wandb_client.create_trace(system_prompt, response, user_query, start_time_ms, end_time_ms) |
| 169 | + |
| 170 | + # return response.choices[0].message.content |
| 171 | + |
| 172 | + |
| 173 | +def callback(contents: str, user: str, instance: pn.chat.ChatInterface): |
| 174 | + return customer_chatbot_agent(contents, tracking=WEIGHTS_AND_BIASES_TRACKING) |
| 175 | + |
| 176 | + |
| 177 | +chat_interface = pn.chat.ChatInterface(callback=callback, callback_exception='verbose') |
| 178 | +chat_interface.send( |
| 179 | + "I am a customer chat assistant! " |
| 180 | + "Currently we support only order cancellations. Please mention Order ID in your request." |
| 181 | + "You can deploy your own by signing up at https://ploomber.io", |
| 182 | + user="System", |
| 183 | + respond=False, |
| 184 | +) |
| 185 | + |
| 186 | +pn.template.MaterialTemplate( |
| 187 | + title="Customer Chatbot", |
| 188 | + main=[chat_interface], |
| 189 | +).servable() |
0 commit comments