Hugo Guarin
commited on
Commit
·
c169262
1
Parent(s):
23b2a3d
Update space
Browse files- .DS_Store +0 -0
- README.md +37 -13
- gradio_app.py +41 -0
- gradio_cached_examples/15/log.csv +4 -0
- img/fastlane.jpg +0 -0
- main.py +20 -0
- models/.DS_Store +0 -0
- models/customer.py +11 -0
- models/order.py +8 -0
- models/product.py +11 -0
- models/query.py +10 -0
- requirements.txt +11 -0
- routes/.DS_Store +0 -0
- routes/__init__.py +0 -0
- routes/account_management.py +33 -0
- routes/customer_support.py +27 -0
- routes/order_management.py +23 -0
- routes/purchase.py +55 -0
- routes/query_handler.py +50 -0
- routes/search_products.py +12 -0
- services/.DS_Store +0 -0
- services/__init__.py +0 -0
- services/elasticsearch.py +137 -0
- services/nlp.py +71 -0
- services/utils.py +37 -0
.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
README.md
CHANGED
@@ -1,13 +1,37 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## FastLane E-commerce Chatbot
|
2 |
+
|
3 |
+
This project provides a Retrieval-Augmented Generation (RAG) system that involves Elastisearch, OpenAI, FastAPI and Gradio components. The system is about an e-commerce chatbot for product and orders searches and leverages Natural Language Processing (NLP) for understanding user intent.
|
4 |
+
|
5 |
+
**Modular Structure for Maintainability:**
|
6 |
+
|
7 |
+
The system was designed with a focus on maintainability and scalability. Here's a breakdown of the core components:
|
8 |
+
|
9 |
+
* **main.py:** The entry point for the application, where the FastAPI app is defined and run.
|
10 |
+
* **models/ (Directory):** Contains Pydantic models for data validation and defining data structures used throughout the application.
|
11 |
+
- `query.py`: Defines models for user queries.
|
12 |
+
- `product.py`: Defines models for representing product data.
|
13 |
+
- `customer.py`: Defines models for representing customer data.
|
14 |
+
- `order.py`: Defines models for representing order data.
|
15 |
+
* **services/ (Directory):** Encapsulates the core business logic of the application.
|
16 |
+
- `elasticsearch.py`: Handles interactions with the Elasticsearch instance for product searches.
|
17 |
+
- `nlp.py`: Provides functionalities for Natural Language Processing (NLP) to understand user intent.
|
18 |
+
- `utils.py`: Provides functions that are used by several components of the system.
|
19 |
+
* **routes/ (Directory):** Defines API endpoints for interacting with the chatbot functionalities. Each feature has its dedicated module:
|
20 |
+
- `search_products.py`: Handles routes related to product search functionalities.
|
21 |
+
- `query_handler`: Handles routes related to the processing of the query introduced by users.
|
22 |
+
- `purchase.py`: Handles routes related to purchase functionalities (add to cart, checkout, etc.).
|
23 |
+
- `order_management.py`: Handles routes related to managing orders (tracking, history).
|
24 |
+
- `account_management.py`: Handles routes related to account management (sign-in, update information).
|
25 |
+
- `customer_support.py`: Handles routes related to customer support functionalities (returns, payments, etc.).
|
26 |
+
|
27 |
+
This modular structure promotes separation of concerns, making the code easier to understand, maintain, and extend in the future.
|
28 |
+
|
29 |
+
**Getting Started:**
|
30 |
+
|
31 |
+
(Provide instructions on how to set up and run the application, including any dependencies or environment variables required.)
|
32 |
+
|
33 |
+
**Further Development:**
|
34 |
+
|
35 |
+
* Generating more comprehensive responses based on intent, entities, and search results.
|
36 |
+
* Enhance security features for handling sensitive user data.
|
37 |
+
* Implement user authentication and authorization mechanisms.
|
gradio_app.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests
|
3 |
+
|
4 |
+
|
5 |
+
def fastlane_agent(message, history):
|
6 |
+
|
7 |
+
history_openai_format = [
|
8 |
+
{"role": "system", "content": "You are an assistant for an eCommerce store."}]
|
9 |
+
for human, assistant in history:
|
10 |
+
history_openai_format.append({"role": "user", "content": human})
|
11 |
+
history_openai_format.append(
|
12 |
+
{"role": "assistant", "content": assistant})
|
13 |
+
history_openai_format.append({"role": "user", "content": message})
|
14 |
+
|
15 |
+
response = requests.post(
|
16 |
+
"http://localhost:8000/query-handler/", json={"text": message, "history": history_openai_format})
|
17 |
+
if response.status_code == 200:
|
18 |
+
return response.json().get("generative response")
|
19 |
+
else:
|
20 |
+
return "Error: Could not fetch response."
|
21 |
+
|
22 |
+
|
23 |
+
iface = gr.ChatInterface(
|
24 |
+
fn=fastlane_agent,
|
25 |
+
chatbot=gr.Chatbot(height=400),
|
26 |
+
textbox=gr.Textbox(
|
27 |
+
placeholder="How can I help you?", container=False, scale=7
|
28 |
+
),
|
29 |
+
title="Fastlane Chat GPT",
|
30 |
+
description="AI sales assistance for e-commmerce",
|
31 |
+
theme="soft",
|
32 |
+
examples=["Hello", "What is the status of my order?",
|
33 |
+
"Recommend me products"],
|
34 |
+
cache_examples=True,
|
35 |
+
retry_btn=None,
|
36 |
+
undo_btn="Delete Previous",
|
37 |
+
clear_btn="Clear"
|
38 |
+
)
|
39 |
+
|
40 |
+
if __name__ == "__main__":
|
41 |
+
iface.launch()
|
gradio_cached_examples/15/log.csv
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
component 0,flag,username,timestamp
|
2 |
+
"[[""Hello"", ""Hello! How can I assist you today?""]]",,,2024-06-11 20:45:46.202912
|
3 |
+
"[[""What is the status of my order?"", ""Could you please provide me with your order number so I can check the status for you?""]]",,,2024-06-11 20:45:50.095791
|
4 |
+
"[[""Recommend me products"", ""Error: Could not fetch response.""]]",,,2024-06-11 20:46:01.780475
|
img/fastlane.jpg
ADDED
![]() |
main.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from routes import query_handler, purchase, order_management, account_management, customer_support, search_products
|
3 |
+
|
4 |
+
app = FastAPI()
|
5 |
+
|
6 |
+
app.include_router(purchase.router, prefix="/purchase", tags=["purchase"])
|
7 |
+
app.include_router(order_management.router,
|
8 |
+
prefix="/order-management", tags=["order-management"])
|
9 |
+
app.include_router(account_management.router,
|
10 |
+
prefix="/account-management", tags=["account-management"])
|
11 |
+
app.include_router(customer_support.router,
|
12 |
+
prefix="/customer-support", tags=["customer-support"])
|
13 |
+
app.include_router(search_products.router,
|
14 |
+
prefix="/search-products", tags=["search-products"])
|
15 |
+
app.include_router(query_handler.router,
|
16 |
+
prefix="/query-handler", tags=["query-handler"])
|
17 |
+
|
18 |
+
if __name__ == "__main__":
|
19 |
+
import uvicorn
|
20 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
models/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
models/customer.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
from typing import List, Optional
|
3 |
+
|
4 |
+
|
5 |
+
class Customer(BaseModel):
|
6 |
+
customer_id: str
|
7 |
+
name: str
|
8 |
+
email: str
|
9 |
+
address: str
|
10 |
+
phone: Optional[str] = None
|
11 |
+
orders: Optional[List[str]] = None
|
models/order.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
|
4 |
+
class Order(BaseModel):
|
5 |
+
order_id: str
|
6 |
+
product_ids: list
|
7 |
+
quantities: list
|
8 |
+
status: str
|
models/product.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
from typing import List, Optional
|
3 |
+
|
4 |
+
|
5 |
+
class Product(BaseModel):
|
6 |
+
product_id: str
|
7 |
+
title: str
|
8 |
+
description: str
|
9 |
+
price: float
|
10 |
+
category: str
|
11 |
+
tags: Optional[List[str]] = None
|
models/query.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Optional, List, Dict
|
2 |
+
from pydantic import BaseModel
|
3 |
+
|
4 |
+
|
5 |
+
class Query(BaseModel):
|
6 |
+
text: str
|
7 |
+
intent: Optional[str] = None
|
8 |
+
entities: Optional[Dict] = None
|
9 |
+
keywords: Optional[List] = None
|
10 |
+
history: Optional[List[Dict[str, str]]] = None
|
requirements.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
elasticsearch
|
2 |
+
en-core-web-lg @ https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.0.0/en_core_web_lg-3.0.0.tar.gz
|
3 |
+
fastapi
|
4 |
+
gradio
|
5 |
+
nltk
|
6 |
+
openai
|
7 |
+
pydantic
|
8 |
+
spacy
|
9 |
+
torch
|
10 |
+
transformers
|
11 |
+
uvicorn[standard]
|
routes/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
routes/__init__.py
ADDED
File without changes
|
routes/account_management.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, status, HTTPException
|
2 |
+
from models.query import Query
|
3 |
+
from services.elasticsearch import get_customer, update_customer
|
4 |
+
|
5 |
+
router = APIRouter()
|
6 |
+
|
7 |
+
|
8 |
+
@router.post("/sign-in-up")
|
9 |
+
def handle_sign_in_up(query: Query):
|
10 |
+
customer_id = next(
|
11 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'customer_id'), None)
|
12 |
+
if customer_id:
|
13 |
+
customer = get_customer(customer_id)
|
14 |
+
if not customer:
|
15 |
+
raise HTTPException(
|
16 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found")
|
17 |
+
return customer
|
18 |
+
else:
|
19 |
+
raise HTTPException(
|
20 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Customer ID not provided.")
|
21 |
+
|
22 |
+
|
23 |
+
@router.post("/update-account")
|
24 |
+
def handle_update_account(query: Query):
|
25 |
+
customer_id = next(
|
26 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'customer_id'), None)
|
27 |
+
updates = {entity['entity']: entity['value'] for entity in query.entities}
|
28 |
+
if customer_id:
|
29 |
+
update_customer(customer_id, updates)
|
30 |
+
return {"message": "Account information updated successfully"}
|
31 |
+
else:
|
32 |
+
raise HTTPException(
|
33 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Customer ID not provided.")
|
routes/customer_support.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from models.query import Query
|
3 |
+
from services.nlp import generate_response
|
4 |
+
|
5 |
+
router = APIRouter()
|
6 |
+
|
7 |
+
|
8 |
+
@router.post("/get-help")
|
9 |
+
def handle_get_help(query: Query):
|
10 |
+
response_text = generate_response(
|
11 |
+
{"query": query.text, "intent": "Get help"})
|
12 |
+
return {"response": response_text[0]['generated_text']}
|
13 |
+
|
14 |
+
|
15 |
+
@router.get("/return-exchange-policy")
|
16 |
+
def handle_return_exchange_policy():
|
17 |
+
return {"policy": "Our return/exchange policy details go here"}
|
18 |
+
|
19 |
+
|
20 |
+
@router.get("/payment-options")
|
21 |
+
def handle_payment_options():
|
22 |
+
return {"payment_options": ["Credit Card", "PayPal", "Bank Transfer"]}
|
23 |
+
|
24 |
+
|
25 |
+
@router.get("/shipping-information")
|
26 |
+
def handle_shipping_information():
|
27 |
+
return {"shipping_info": "Shipping costs and delivery times details go here"}
|
routes/order_management.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, status, HTTPException
|
2 |
+
from services.elasticsearch import get_order_details, get_customer
|
3 |
+
|
4 |
+
router = APIRouter()
|
5 |
+
|
6 |
+
|
7 |
+
@router.get("/track-order/{order_id}")
|
8 |
+
def handle_track_order(order_id: str):
|
9 |
+
order = get_order_details(order_id)
|
10 |
+
if not order:
|
11 |
+
raise HTTPException(
|
12 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")
|
13 |
+
return order
|
14 |
+
|
15 |
+
|
16 |
+
@router.get("/order-history")
|
17 |
+
def view_order_history(customer_id: str):
|
18 |
+
customer = get_customer(customer_id)
|
19 |
+
if not customer:
|
20 |
+
raise HTTPException(
|
21 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found")
|
22 |
+
# Retrieve order history from customer data
|
23 |
+
return {"order_history": customer.get("orders", [])}
|
routes/purchase.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, status, HTTPException
|
2 |
+
from models.query import Query
|
3 |
+
from services.elasticsearch import add_to_cart, view_cart, remove_from_cart, checkout
|
4 |
+
|
5 |
+
router = APIRouter()
|
6 |
+
|
7 |
+
|
8 |
+
@router.post("/add-to-cart")
|
9 |
+
def handle_add_to_cart(query: Query):
|
10 |
+
customer_id = next(
|
11 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'customer_id'), None)
|
12 |
+
product_id = next(
|
13 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'product_id'), None)
|
14 |
+
quantity = next(
|
15 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'quantity'), 1)
|
16 |
+
if customer_id and product_id:
|
17 |
+
cart = add_to_cart(customer_id, product_id, int(quantity))
|
18 |
+
return cart
|
19 |
+
else:
|
20 |
+
raise HTTPException(
|
21 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Customer ID or Product ID not provided.")
|
22 |
+
|
23 |
+
|
24 |
+
@router.get("/view-cart/{customer_id}")
|
25 |
+
def handle_view_cart(customer_id: str):
|
26 |
+
cart = view_cart(customer_id)
|
27 |
+
if not cart:
|
28 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cart not found")
|
29 |
+
return cart
|
30 |
+
|
31 |
+
|
32 |
+
@router.post("/remove-from-cart")
|
33 |
+
def handle_remove_from_cart(query: Query):
|
34 |
+
customer_id = next(
|
35 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'customer_id'), None)
|
36 |
+
product_id = next(
|
37 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'product_id'), None)
|
38 |
+
if customer_id and product_id:
|
39 |
+
cart = remove_from_cart(customer_id, product_id)
|
40 |
+
return cart
|
41 |
+
else:
|
42 |
+
raise HTTPException(
|
43 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Customer ID or Product ID not provided.")
|
44 |
+
|
45 |
+
|
46 |
+
@router.post("/checkout")
|
47 |
+
def handle_checkout(query: Query):
|
48 |
+
customer_id = next(
|
49 |
+
(entity['value'] for entity in query.entities if entity['entity'] == 'customer_id'), None)
|
50 |
+
if customer_id:
|
51 |
+
order = checkout(customer_id)
|
52 |
+
return order
|
53 |
+
else:
|
54 |
+
raise HTTPException(
|
55 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Customer ID not provided.")
|
routes/query_handler.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, status, HTTPException
|
2 |
+
from models.query import Query
|
3 |
+
from routes import search_products, purchase, order_management, account_management, customer_support
|
4 |
+
from services.nlp import recognize_intent, recognize_entities, extract_keywords, generate_response
|
5 |
+
from services.utils import clean_text, encode_and_normalize, extract_order_id_from_query
|
6 |
+
|
7 |
+
|
8 |
+
router = APIRouter()
|
9 |
+
|
10 |
+
|
11 |
+
FUNCTION_DESCRIPTIONS_FOR_PRODUCTS = {
|
12 |
+
"search_products_by_keywords": "User wants to find products based on keywords",
|
13 |
+
"search_products_by_filters": "User wants to refine search results with filters",
|
14 |
+
"get_product_details": "User wants detailed information about a specific product"
|
15 |
+
}
|
16 |
+
|
17 |
+
FUNCTION_DESCRIPTIONS_FOR_ORDERS = {
|
18 |
+
"get_order_location": "Find the location (city or state) of a specific order using an identification number order",
|
19 |
+
"get_recent_order": "Track the most recent order of a customer",
|
20 |
+
"get_order_details": "Get details about a specific order using an identification number order",
|
21 |
+
"get_order_quantity": "Calculate the total number of products in a specific order",
|
22 |
+
"get_order_amount": "Calculate the total amount spent in a specific order",
|
23 |
+
"cancel_order": "Process order cancellation requests"
|
24 |
+
}
|
25 |
+
|
26 |
+
|
27 |
+
def query_processing(query: Query):
|
28 |
+
cleaned_text = clean_text(query.text)
|
29 |
+
query.intent = recognize_intent(cleaned_text)
|
30 |
+
query.entities = recognize_entities(cleaned_text)
|
31 |
+
query.keywords = extract_keywords(cleaned_text)
|
32 |
+
encoded_query = encode_and_normalize(cleaned_text)
|
33 |
+
|
34 |
+
if query.intent == "search for products":
|
35 |
+
return {"products": search_products.handle_search_products_by_keywords(encoded_query)}
|
36 |
+
|
37 |
+
elif query.intent == "order management":
|
38 |
+
order_id = extract_order_id_from_query(query.text)
|
39 |
+
if order_id:
|
40 |
+
return order_management.handle_track_order(order_id)
|
41 |
+
else:
|
42 |
+
return "Please provide an Order Number"
|
43 |
+
else:
|
44 |
+
return None
|
45 |
+
|
46 |
+
|
47 |
+
@router.post("/")
|
48 |
+
async def handle_response(query: Query):
|
49 |
+
context_from_elasticsearch = query_processing(query)
|
50 |
+
return {"generative response": generate_response(query, context_from_elasticsearch)}
|
routes/search_products.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, HTTPException
|
2 |
+
from services.elasticsearch import search_products_by_keywords, search_products_by_filters, get_product_details
|
3 |
+
|
4 |
+
router = APIRouter()
|
5 |
+
|
6 |
+
|
7 |
+
@router.post("/")
|
8 |
+
def handle_search_products_by_keywords(encoded_query: list):
|
9 |
+
products = search_products_by_keywords(encoded_query)
|
10 |
+
if not products:
|
11 |
+
raise HTTPException(status_code=404, detail="No products to recommend")
|
12 |
+
return products
|
services/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
services/__init__.py
ADDED
File without changes
|
services/elasticsearch.py
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from elasticsearch import Elasticsearch
|
3 |
+
from elasticsearch_dsl import Search
|
4 |
+
from models.product import Product
|
5 |
+
from models.customer import Customer
|
6 |
+
from models.order import Order
|
7 |
+
|
8 |
+
cloud_id = os.getenv("ELASTICSEARCH_CLOUD_ID")
|
9 |
+
api_key = os.getenv("ELASTICSEARCH_API_KEY")
|
10 |
+
|
11 |
+
try:
|
12 |
+
es = Elasticsearch(cloud_id=cloud_id, api_key=api_key)
|
13 |
+
|
14 |
+
except ConnectionError as e:
|
15 |
+
print("Error:", e)
|
16 |
+
|
17 |
+
|
18 |
+
def search_elasticsearch(index, query):
|
19 |
+
response = es.search(index=index, body={
|
20 |
+
"query": {
|
21 |
+
"multi_match": {
|
22 |
+
"query": query,
|
23 |
+
"fields": ["title", "description", "category", "tags"]
|
24 |
+
}
|
25 |
+
}
|
26 |
+
})
|
27 |
+
return response['hits']['hits']
|
28 |
+
|
29 |
+
|
30 |
+
def search_products_by_keywords(encoded_query):
|
31 |
+
body_query = {
|
32 |
+
"_source": ["ProductName", "Description", "Gender", "Price (INR)", "PrimaryColor"],
|
33 |
+
"knn": {
|
34 |
+
"field": "DescriptionVector",
|
35 |
+
"query_vector": encoded_query,
|
36 |
+
"k": 4,
|
37 |
+
"num_candidates": 500
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
response = es.search(index='products', body=body_query)
|
42 |
+
return response['hits']['hits']
|
43 |
+
|
44 |
+
|
45 |
+
def search_products_by_filters(filters):
|
46 |
+
response = es.search(index='products', body={
|
47 |
+
"query": {
|
48 |
+
"bool": {
|
49 |
+
"filter": filters
|
50 |
+
}
|
51 |
+
}
|
52 |
+
})
|
53 |
+
return response['hits']['hits']
|
54 |
+
|
55 |
+
|
56 |
+
def get_product_details(product_id):
|
57 |
+
response = es.get(index='products', id=product_id)
|
58 |
+
return response['_source']
|
59 |
+
|
60 |
+
|
61 |
+
def index_product(product: Product):
|
62 |
+
es.index(index="products", id=product.product_id, body=product.dict())
|
63 |
+
|
64 |
+
|
65 |
+
def index_customer(customer: Customer):
|
66 |
+
es.index(index="customers", id=customer.customer_id, body=customer.dict())
|
67 |
+
|
68 |
+
|
69 |
+
def get_customer(customer_id: str):
|
70 |
+
response = es.get(index="customers", id=customer_id)
|
71 |
+
return response['_source']
|
72 |
+
|
73 |
+
|
74 |
+
def update_customer(customer_id: str, updates: dict):
|
75 |
+
es.update(index="customers", id=customer_id, body={"doc": updates})
|
76 |
+
|
77 |
+
|
78 |
+
def index_order(order: Order):
|
79 |
+
es.index(index="orders", id=order.order_id, body=order.dict())
|
80 |
+
|
81 |
+
|
82 |
+
def track_order(order_id: str):
|
83 |
+
response = es.get(index="orders", id=order_id)
|
84 |
+
return response['_source']
|
85 |
+
|
86 |
+
|
87 |
+
def get_order_details(order_id):
|
88 |
+
s_order = Search(using=es, index='orders').query(
|
89 |
+
'match', **{'Order ID': order_id})
|
90 |
+
s_details = Search(using=es, index='order_details').query(
|
91 |
+
'match', **{'Order ID': order_id})
|
92 |
+
order_response = s_order.execute()
|
93 |
+
details_response = s_details.execute()
|
94 |
+
|
95 |
+
if order_response.hits.total.value > 0 and details_response.hits.total.value > 0:
|
96 |
+
order = order_response.hits[0].to_dict()
|
97 |
+
details = [detail.to_dict() for detail in details_response.hits]
|
98 |
+
return {
|
99 |
+
"order": order,
|
100 |
+
"details": details
|
101 |
+
}
|
102 |
+
else:
|
103 |
+
return "Order not found"
|
104 |
+
|
105 |
+
|
106 |
+
def add_to_cart(customer_id: str, product_id: str, quantity: int):
|
107 |
+
cart = es.get(index="carts", id=customer_id)['_source']
|
108 |
+
if 'items' not in cart:
|
109 |
+
cart['items'] = []
|
110 |
+
cart['items'].append({"product_id": product_id, "quantity": quantity})
|
111 |
+
es.index(index="carts", id=customer_id, body=cart)
|
112 |
+
return cart
|
113 |
+
|
114 |
+
|
115 |
+
def view_cart(customer_id: str):
|
116 |
+
response = es.get(index="carts", id=customer_id)
|
117 |
+
return response['_source']
|
118 |
+
|
119 |
+
|
120 |
+
def remove_from_cart(customer_id: str, product_id: str):
|
121 |
+
cart = es.get(index="carts", id=customer_id)['_source']
|
122 |
+
cart['items'] = [item for item in cart['items']
|
123 |
+
if item['product_id'] != product_id]
|
124 |
+
es.index(index="carts", id=customer_id, body=cart)
|
125 |
+
return cart
|
126 |
+
|
127 |
+
|
128 |
+
def checkout(customer_id: str):
|
129 |
+
cart = es.get(index="carts", id=customer_id)['_source']
|
130 |
+
order = {
|
131 |
+
"customer_id": customer_id,
|
132 |
+
"items": cart['items'],
|
133 |
+
"status": "processing"
|
134 |
+
}
|
135 |
+
es.index(index="orders", body=order)
|
136 |
+
es.delete(index="carts", id=customer_id)
|
137 |
+
return order
|
services/nlp.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from openai import OpenAI
|
3 |
+
from models.query import Query
|
4 |
+
import re
|
5 |
+
from transformers import pipeline
|
6 |
+
from services.utils import clean_text, encode_and_normalize
|
7 |
+
|
8 |
+
intent_recognizer = pipeline(
|
9 |
+
"zero-shot-classification", model="facebook/bart-large-mnli")
|
10 |
+
|
11 |
+
ner_recognizer = pipeline(
|
12 |
+
'ner', model='dbmdz/bert-large-cased-finetuned-conll03-english')
|
13 |
+
|
14 |
+
openai_key = os.environ.get("OPENAI_KEY")
|
15 |
+
openai_client = OpenAI(api_key=openai_key)
|
16 |
+
|
17 |
+
# Define regex patterns for entities
|
18 |
+
patterns = {
|
19 |
+
'Product': r'\b(iphone|samsung|macbook|ps5|galaxy|pixel|shoes|shampoo|cellphone|smartphone|tablet|laptop|headphones|console|tv|camera)\b',
|
20 |
+
'Brand': r'\b(apple|samsung|google|sony|microsoft|dell|hp|lenovo|asus|nintendo|canon|nikon)\b',
|
21 |
+
'Category': r'\b(laptops|dresses|phones|electronics|clothing|footwear|accessories|home appliances|furniture)\b',
|
22 |
+
'Color': r'\b(red|black|yellow|blue|green|white|grey|pink|purple|orange|brown)\b',
|
23 |
+
'Price Range': r'\b(under \$?\d+|below \$?\d+|less than \$?\d+|more than \$?\d+|above \$?\d+|between \$?\d+ and \$?\d+)\b',
|
24 |
+
'Quantity': r'\b(\d+ bottles|\d+ items|\d+ pieces|\d+ units|\d+)\b',
|
25 |
+
'Order Number': r'\bB-\d+\b',
|
26 |
+
'Issue': r'\b(account help|payment issue|order problem|shipping delay|return request|product complaint|technical support)\b'
|
27 |
+
}
|
28 |
+
|
29 |
+
INTENTS = [
|
30 |
+
"search for products",
|
31 |
+
"order management",
|
32 |
+
"checkout",
|
33 |
+
"customer support",
|
34 |
+
"account management"
|
35 |
+
]
|
36 |
+
|
37 |
+
|
38 |
+
def recognize_intent(text):
|
39 |
+
cleaned_text = clean_text(text)
|
40 |
+
intent = intent_recognizer(cleaned_text, INTENTS)
|
41 |
+
return intent['labels'][0]
|
42 |
+
|
43 |
+
|
44 |
+
def recognize_entities(text):
|
45 |
+
cleaned_text = clean_text(text)
|
46 |
+
entities_from_ner = ner_recognizer(cleaned_text)
|
47 |
+
entities_from_re = {entity: re.findall(pattern, text.lower(
|
48 |
+
)) for entity, pattern in patterns.items() if re.findall(pattern, text.lower())}
|
49 |
+
|
50 |
+
return entities_from_re
|
51 |
+
|
52 |
+
|
53 |
+
def extract_keywords(text):
|
54 |
+
cleaned_text = clean_text(text)
|
55 |
+
return cleaned_text.split()
|
56 |
+
|
57 |
+
|
58 |
+
def generate_response(query: Query, context_from_elasticsearch):
|
59 |
+
prompt = query.history
|
60 |
+
prompt.append({"role": "assistant", "content": str(context_from_elasticsearch)})
|
61 |
+
|
62 |
+
print(prompt)
|
63 |
+
|
64 |
+
response = openai_client.chat.completions.create(
|
65 |
+
model="gpt-3.5-turbo",
|
66 |
+
messages=prompt
|
67 |
+
)
|
68 |
+
|
69 |
+
return response.choices[0].message.content
|
70 |
+
|
71 |
+
|
services/utils.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import numpy as np
|
3 |
+
from nltk.tokenize import word_tokenize
|
4 |
+
from nltk.corpus import stopwords
|
5 |
+
from nltk.stem import WordNetLemmatizer
|
6 |
+
from sentence_transformers import SentenceTransformer
|
7 |
+
|
8 |
+
stop_words = set(stopwords.words('english'))
|
9 |
+
lemmatizer = WordNetLemmatizer()
|
10 |
+
model = SentenceTransformer('all-mpnet-base-v2')
|
11 |
+
|
12 |
+
|
13 |
+
def clean_text(text):
|
14 |
+
# Lowercase
|
15 |
+
text = text.lower()
|
16 |
+
# Remove special characters and digits
|
17 |
+
text = re.sub(r'[^a-z\s]', '', text)
|
18 |
+
# Tokenize
|
19 |
+
words = word_tokenize(text)
|
20 |
+
# Remove stopwords and lemmatize
|
21 |
+
words = [lemmatizer.lemmatize(word)
|
22 |
+
for word in words if word not in stop_words]
|
23 |
+
# Join words back to a single string
|
24 |
+
cleaned_text = ' '.join(words)
|
25 |
+
return cleaned_text
|
26 |
+
|
27 |
+
|
28 |
+
def encode_and_normalize(text):
|
29 |
+
vector = model.encode(text)
|
30 |
+
normalized_vector = vector / np.linalg.norm(vector)
|
31 |
+
return normalized_vector
|
32 |
+
|
33 |
+
def extract_order_id_from_query(text):
|
34 |
+
match = re.search(r'\bB-\d+\b', text)
|
35 |
+
if match:
|
36 |
+
return match.group(0)
|
37 |
+
return None
|