diff --git a/jewelry/__init__.py b/jewelry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jewelry/data/ingest.py b/jewelry/data/ingest.py new file mode 100644 index 0000000..8d27a44 --- /dev/null +++ b/jewelry/data/ingest.py @@ -0,0 +1,51 @@ +import sys +import os +from dotenv import load_dotenv +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain.embeddings import OpenAIEmbeddings +from langchain.vectorstores import Chroma +from chromadb.config import Settings + +def load_environment(): + load_dotenv() + if "OPENAI_API_KEY" not in os.environ: + print("Error: OPENAI_API_KEY is not set in the .env file or environment.") + sys.exit(1) + +def ingest_to_chroma(file_path): + with open(file_path, 'r') as file: + text = file.read() + + text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) + chunks = text_splitter.split_text(text) + + embeddings = OpenAIEmbeddings() + + # Initialize Chroma with persistence + chroma_settings = Settings( + chroma_db_impl='duckdb+parquet', + persist_directory='./chroma_db' # This directory will store the persisted data + ) + + db = Chroma.from_texts( + texts=chunks, + embedding=embeddings, + client_settings=chroma_settings, + persist_directory='./chroma_db' + ) + + # Persistence is now handled automatically + print(f"Text from {file_path} has been successfully ingested into Chroma and persisted.") + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python script_name.py ") + sys.exit(1) + + file_path = sys.argv[1] + if not os.path.exists(file_path): + print(f"Error: File {file_path} does not exist.") + sys.exit(1) + + load_environment() + ingest_to_chroma(file_path) \ No newline at end of file diff --git a/jewelry/data/main.py b/jewelry/data/main.py new file mode 100644 index 0000000..3674364 --- /dev/null +++ b/jewelry/data/main.py @@ -0,0 +1,60 @@ +import os +from sqlalchemy import create_engine +import pandas as pd +from jewelry.data.sql_db import drop_tables, create_tables, get_engine, get_items, get_user_items_purchases_history +from genai_factory.actions import ingest +def init_sql_db(data_path: str = "data", mock_data_path: str = "./data/mock_data", reset: bool = True): + """ + Initialize the SQL database and load the mock data if available. + + :param data_path: Data path. + :param mock_data_path: Mock data path. + :param reset: Whether to reset the database. + """ + # Create the base data path if it doesn't exist: + if not os.path.exists(data_path): + os.makedirs(data_path) + + # Connect to the SQL database: + sql_connection_url = f"sqlite:///{data_path}/sql.db" + engine = get_engine(sql_connection_url=sql_connection_url) + + # Drop the tables if reset is required: + if reset: + drop_tables(engine=engine) + + # Create the tables: + create_tables(engine=engine) + + # Check if needed to load mock data: + if not mock_data_path: + return + + # Load the mock data: + products = pd.read_csv(os.path.join(mock_data_path, "products.csv")) + items = pd.read_csv(os.path.join(mock_data_path, "items.csv")) + users = pd.read_csv(os.path.join(mock_data_path, "users.csv")) + stocks = pd.read_csv(os.path.join(mock_data_path, "stocks.csv")) + purchases = pd.read_csv(os.path.join(mock_data_path, "purchases.csv")) + stock_to_purchase = pd.read_csv(os.path.join(mock_data_path, "stock_to_purchase.csv")) + reviews = pd.read_csv(os.path.join(mock_data_path, "reviews.csv")) + + # Insert the mock data into tables: + products.to_sql(name="product", con=engine, if_exists="replace", index=False) + items.to_sql(name="item", con=engine, if_exists="replace", index=False) + users.to_sql(name="user", con=engine, if_exists="replace", index=False) + stocks.to_sql(name="stock", con=engine, if_exists="replace", index=False) + purchases.to_sql(name="purchase", con=engine, if_exists="replace", index=False) + stock_to_purchase.to_sql(name="stock_to_purchase", con=engine, if_exists="replace", index=False) + reviews.to_sql(name="review", con=engine, if_exists="replace", index=False) + + +if __name__ == "__main__": + init_sql_db() + engine = get_engine(f"sqlite:///data/sql.db") + items = get_items(engine=engine, kinds=["rings", "bracelets"], stones=["no stones"], metals=["white gold"]) + print(items) + items = get_user_items_purchases_history(engine=engine, user_id="6") + print(items) + + diff --git a/jewelry/data/mock_data/documents/customer_policy.txt b/jewelry/data/mock_data/documents/customer_policy.txt new file mode 100644 index 0000000..cc1f24b --- /dev/null +++ b/jewelry/data/mock_data/documents/customer_policy.txt @@ -0,0 +1,51 @@ +## Customer Policy for Iguazio Jewelry + +**General:** + +* We strive to provide excellent customer service and high-quality jewelry products. +* We reserve the right to modify this policy at any time without prior notice. +* By placing an order with us, you acknowledge and agree to be bound by the terms of this policy. + +**Orders:** + +* We accept orders placed on our website 24/7. +* Orders are processed within 1 day after they are placed. +* Order confirmation emails will be sent to the email address provided at checkout. +* We reserve the right to cancel any order for any reason. + +**Payments:** + +* We accept visa and mastercard. +* All payments are processed securely through a trusted payment gateway. +* Your credit card information is never stored on our servers. + +**Shipping:** + +* We offer a variety of shipping options with varying delivery times and costs. +* Shipping costs are calculated based on the weight, size, and destination of your order. +* You will be able to choose your preferred shipping option at checkout. +* We are not responsible for lost, stolen, or damaged packages once they have been shipped. + +**Returns and Exchanges:** + +* We accept returns and exchanges within 1 month of the purchase date. +* Items must be returned in their original packaging, unworn, and in new condition. +* We do not offer refunds for shipping costs on returned items. +* Please contact us for a return authorization before sending any items back. + +**Warranties:** + +* All our jewelry comes with a 5-year warranty against manufacturing defects. +* This warranty does not cover damage caused by normal wear and tear, misuse, or accidents. +* Please contact us for warranty claims. + +**Privacy:** + +* We respect your privacy and are committed to protecting your personal information. +* We will not share your personal information with any third party without your consent. +* You can review our full privacy policy on our website. + +**Contact Us:** + +* If you have any questions or concerns, please contact us via email at fake-mail@store.com or phone at 123456789. +* We will reply to your inquiries as soon as possible. \ No newline at end of file diff --git a/jewelry/data/mock_data/items.csv b/jewelry/data/mock_data/items.csv new file mode 100644 index 0000000..7dd4b36 --- /dev/null +++ b/jewelry/data/mock_data/items.csv @@ -0,0 +1,20 @@ +item_id,date_added,description,colors,metals,stones,stones_carat_total_weight,product_id,image +1,2024-02-15,"A luxurious yellow gold ring adorned with sparkling diamonds, exuding timeless elegance and sophistication.","yellow, clear",yellow gold,diamonds,1.5,1,1.png +2,2024-02-15,"A mesmerizing white gold ring featuring a stunning arrangement of diamonds, symbolizing everlasting beauty and opulence.","white, clear",white gold,diamonds,1.5,1,2.png +3,2024-02-20,"A radiant pink gold bracelet, exuding regal charm and sophistication.",pink,pink gold,no stones,0.0,2,3.png +4,2024-02-20,"An exquisite white gold bracelet, epitomizing luxury and grace.",white,white gold,no stones,0.0,2,4.png +5,2024-02-25,"A majestic yellow gold necklace adorned with glistening diamonds, exuding royal elegance and grandeur.","yellow, clear",yellow gold,diamonds,3.0,3,5.png +6,2024-02-25,"An opulent white gold necklace featuring a breathtaking arrangement of diamonds, capturing the essence of luxury and sophistication.","white, clear",white gold,diamonds,3.0,3,6.png +7,2024-03-01,"Luxurious pink gold earrings adorned with sparkling diamonds, radiating opulent glamour and sophistication.","pink, clear",pink gold,diamonds,2.2,4,7.png +8,2024-03-01,"Elegant white gold earrings featuring a dazzling array of diamonds, exuding timeless beauty and refinement.","white, clear",white gold,diamonds,2.2,4,8.png +9,2024-02-15,"A timeless yellow gold ring adorned with sparkling diamonds, radiating elegance and grace.","yellow, clear",yellow gold,diamonds,1.0,5,9.png +10,2024-02-15,"An exquisite pink gold ring featuring a delicate arrangement of diamonds, epitomizing sophistication and charm.","pink, clear",pink gold,diamonds,1.0,5,10.png +11,2024-03-04,"An elegant white gold necklace adorned with a breathtaking butterfly pendant, symbolizing transformation and beauty.",white,white gold,no stones,0.0,6,11.png +12,2024-03-04,"An elegant pink gold necklace adorned with a breathtaking butterfly pendant, symbolizing transformation and beauty.",pink,pink gold,no stones,0.0,6,12.png +13,2024-03-04,"A mesmerizing white gold necklace featuring delicate leaf motifs and sparkling diamond accents, evoking the enchanting beauty of a mystical forest.",white,white gold,diamonds,0.7,7,13.png +14,2024-03-04,"A mesmerizing yellow gold necklace featuring delicate leaf motifs and sparkling diamond accents, evoking the enchanting beauty of a mystical forest.",yellow,yellow gold,diamonds,0.7,7,14.png +15,2024-02-15,"An enchanting white gold ring adorned with a heart shaped diamond, symbolizing eternal love and romance.","white, clear",white gold,diamonds,1.8,8,15.png +16,2024-02-15,"An enchanting pink gold ring adorned with a heart shaped diamond, symbolizing eternal love and romance.","pink, clear",pink gold,diamonds,1.8,8,16.png +17,2024-02-15,"An enchanting yellow gold ring adorned with a heart shaped diamond, symbolizing eternal love and romance.","yellow, clear",yellow gold,diamonds,1.8,8,17.png +18,2024-02-25,"An exquisite chain pink gold necklace, capturing the essence of timeless elegance and sophistication.",pink,pink gold,no stones,0.0,9,18.png +19,2024-02-25,"An exquisite chain yellow gold necklace, capturing the essence of timeless elegance and sophistication.",yellow,yellow gold,no stones,0.0,9,19.png diff --git a/jewelry/data/mock_data/mocker.py b/jewelry/data/mock_data/mocker.py new file mode 100644 index 0000000..ccf12a9 --- /dev/null +++ b/jewelry/data/mock_data/mocker.py @@ -0,0 +1,153 @@ +import datetime +import json +import os +import random +import uuid +from typing import List, Tuple + +import pandas as pd + + +def _generate_stocks( + products: List[dict], items: List[dict], min_amount: int = 0, max_amount: int = 5 +) -> List[dict]: + price_map = {"rings": 400, "bracelets": 800, "necklaces": 1000, "earrings": 200} + size_map = { + "rings": [3, 5, 7, 9], + "bracelets": [6.3, 6.7, 7.1, 7.9, 9], + "necklaces": [16, 17.7, 19.4], + "earrings": [1], + } + + stocks = [] + for item in items: + kind = next(p["kind"] for p in products if item["product_id"] == p["product_id"]) + price = random.randint(1, 10) * 50 + price_map[kind] + if item["stones"] == "diamonds": + price *= 3 + if item["metals"] == "white gold": + price += 200 + for size in size_map[kind]: + stocks.append( + { + "stock_id": str(uuid.uuid4()).replace("-", ""), + "item_id": item["item_id"], + "size": size, + "amount": random.randint(min_amount, max_amount), + "price": price, + } + ) + + return stocks + + +def _generate_purchases( + users: List[dict], + stocks: List[dict], + min_date: str, + max_date: str, + min_amount: int = 1, + max_amount: int = 4, +) -> Tuple[List[dict], List[dict]]: + min_date = datetime.datetime.strptime(min_date, "%m/%d/%Y") + max_date = datetime.datetime.strptime(max_date, "%m/%d/%Y") + + purchases = [] + for _ in range(10): + user_id = random.choice(users)["user_id"] + stock_ids = [ + stock["stock_id"] + for stock in random.sample(stocks, random.randint(min_amount, max_amount)) + ] + purchase_id = str(uuid.uuid4()).replace("-", "") + date = min_date + datetime.timedelta( + days=random.randint(0, (max_date - min_date).days) + ) + purchases.append( + { + "user_id": user_id, + "stocks": stock_ids, + "date": date.strftime("%m/%d/%Y"), + "purchase_id": purchase_id, + } + ) + + stock_to_purchase = [] + for p in purchases: + for sid in p["stocks"]: + stock_to_purchase.append({"purchase_id": p["purchase_id"], "stock_id": sid}) + p.pop("stocks") + + return purchases, stock_to_purchase + + +def _generate_reviews( + stocks: List[dict], + purchases: List[dict], + stock_to_purchase: List[dict], + review_chance: float = 0.5 +) -> List[dict]: + reviews = [] + for stp in stock_to_purchase: + if random.random() < review_chance: + continue + purchase = next(p for p in purchases if p["purchase_id"] == stp["purchase_id"]) + stock = next(s for s in stocks if s["stock_id"] == stp["stock_id"]) + date = datetime.datetime.strptime(purchase["date"], "%m/%d/%Y") + date += datetime.timedelta(days=random.randint(1, 7)) + rating = random.randint(1, 5) + review = { + "review_id": str(uuid.uuid4()).replace("-", ""), + "item_id": stock["item_id"], + "user_id": purchase["user_id"], + "date": date, + "text": "", # TODO: Generate a review based on randomize score using ChatGPT + "rating": rating, + "is_recommend": rating > 3, + } + reviews.append(review) + + return reviews + + +def generate_mock_data( + sources_directory: str = "./sources", output_directory: str = "./" +): + with open(os.path.join(sources_directory, "products.json")) as json_file: + products = json.load(json_file) + with open(os.path.join(sources_directory, "items.json")) as json_file: + items = json.load(json_file) + with open(os.path.join(sources_directory, "users.json")) as json_file: + users = json.load(json_file) + + stocks = _generate_stocks(products=products, items=items) + purchases, stock_to_purchase = _generate_purchases( + users=users, stocks=stocks, min_date="05/03/2024", max_date="07/03/2024" + ) + reviews = _generate_reviews(stocks=stocks, purchases=purchases, stock_to_purchase=stock_to_purchase) + + products_df = pd.DataFrame(products) + items_df = pd.DataFrame(items) + items_df['date_added'] = pd.to_datetime(items_df['date_added'], format='%m/%d/%Y') + users_df = pd.DataFrame(users) + users_df['date_of_birth'] = pd.to_datetime(users_df['date_of_birth'], format='%m/%d/%Y') + stocks_df = pd.DataFrame(stocks) + purchases_df = pd.DataFrame(purchases) + purchases_df['date'] = pd.to_datetime(purchases_df['date'], format='%m/%d/%Y') + stock_to_purchase_df = pd.DataFrame(stock_to_purchase) + reviews_df = pd.DataFrame(reviews) + reviews_df['date'] = pd.to_datetime(reviews_df['date'], format='%m/%d/%Y') + + products_df.to_csv(os.path.join(output_directory, "products.csv"), index=False) + items_df.to_csv(os.path.join(output_directory, "items.csv"), index=False) + users_df.to_csv(os.path.join(output_directory, "users.csv"), index=False) + stocks_df.to_csv(os.path.join(output_directory, "stocks.csv"), index=False) + purchases_df.to_csv(os.path.join(output_directory, "purchases.csv"), index=False) + stock_to_purchase_df.to_csv( + os.path.join(output_directory, "stock_to_purchase.csv"), index=False + ) + reviews_df.to_csv(os.path.join(output_directory, "reviews.csv"), index=False) + + +if __name__ == "__main__": + generate_mock_data() diff --git a/jewelry/data/mock_data/products.csv b/jewelry/data/mock_data/products.csv new file mode 100644 index 0000000..ae9ca74 --- /dev/null +++ b/jewelry/data/mock_data/products.csv @@ -0,0 +1,10 @@ +product_id,name,kind,collections,gifts +1,Eternal Splendor Ring,rings,Forever Diamonds,"wife, anniversary" +2,Regal Enchantment Bracelet,bracelets,McKinsey Elegance,"wife, anniversary" +3,Royal Splendor Necklace,necklaces,Forever Diamonds,"wife, anniversary" +4,Opulent Glamour Earrings,earrings,Forever Diamonds,"wife, anniversary" +5,Timeless Grace Ring,rings,Forever Diamonds,"wife, anniversary" +6,Enchanted Forest Necklace,necklaces,Iguazio Nature Capture,"mom, birthday" +7,Enchanted Leaf Necklace,earrings,"Iguazio Nature Capture, Forever Diamonds","best friend, mom, birthday" +8,Enchanted Love Ring,rings,Forever Diamonds,"wife, anniversary, best friend" +9,Majestic Chain Necklace,necklaces,Timeless,"wife, graduation, best friend, mom" diff --git a/jewelry/data/mock_data/purchases.csv b/jewelry/data/mock_data/purchases.csv new file mode 100644 index 0000000..7390695 --- /dev/null +++ b/jewelry/data/mock_data/purchases.csv @@ -0,0 +1,11 @@ +user_id,date,purchase_id +5,2024-05-11,e12e1d1139e3481b8aa53a88cb95a156 +2,2024-05-20,1abc839fe844479f9154e5c683264436 +1,2024-05-29,773ca8faaf17471a82c0792e2ffeb661 +3,2024-07-01,02a941880e674f82ae0430c404c7f62b +3,2024-05-09,72d033b842474d96aaaaf94153a6dc45 +3,2024-05-03,f469aa686ea24023b0241786068eab1f +1,2024-05-04,8751e73e3f4b4b758931de66506f71a7 +3,2024-05-11,80d7415470294775aec85bffc41f852d +4,2024-06-13,d4592d31cafd44cb8a64a44e64aff624 +3,2024-06-29,f315be28cd9f469ea713116faf49b123 diff --git a/jewelry/data/mock_data/reviews.csv b/jewelry/data/mock_data/reviews.csv new file mode 100644 index 0000000..309b9ce --- /dev/null +++ b/jewelry/data/mock_data/reviews.csv @@ -0,0 +1,16 @@ +review_id,item_id,user_id,date,text,rating,is_recommend +75bcf4035378468d8251a58519b05e3e,14,5,2024-05-16,,5,True +28ec7b810ad54bb88907344646107949,2,5,2024-05-18,,4,True +14d7c597917a440c9bff6a8d510a4b9f,6,2,2024-05-24,,4,True +d622a7d9a3884e2f91e05cfbca053c7a,10,2,2024-05-23,,3,False +28bde0397b9a4336929f77b55a3a1bae,2,2,2024-05-25,,5,True +66300279773e40709cc4348da4a814d6,10,1,2024-06-02,,1,False +4c7a9c600eef46cfad6322ed2e14cfa2,6,3,2024-07-02,,1,False +97b4a35f9cdf48479ae3361ecbcbada8,12,3,2024-05-06,,4,True +d7cfcc1c2caa47749439cce3312fd32b,6,3,2024-05-10,,3,False +cd25c018501040a18bf35891561b4e84,9,3,2024-05-07,,2,False +b06e34a24c454af582780b017af37c4c,5,1,2024-05-11,,3,False +a6195ed5ccbd4216b9bd34f79542d00a,17,3,2024-05-13,,4,True +dfd00d8808f44eb081a760688ba0de0a,6,4,2024-06-15,,4,True +fc437a4a1a0645568aca999c29fbb72b,1,3,2024-07-06,,3,False +8da2df90c7c641a88c5b5a174c5386a7,6,3,2024-07-04,,5,True diff --git a/jewelry/data/mock_data/sources/items.json b/jewelry/data/mock_data/sources/items.json new file mode 100644 index 0000000..873d599 --- /dev/null +++ b/jewelry/data/mock_data/sources/items.json @@ -0,0 +1,211 @@ +[ + { + "item_id": "1", + "date_added": "2/15/2024", + "description": "A luxurious yellow gold ring adorned with sparkling diamonds, exuding timeless elegance and sophistication.", + "colors": "yellow, clear", + "metals": "yellow gold", + "stones": "diamonds", + "stones_carat_total_weight": 1.5, + "product_id": "1", + "image": "1.png" + }, + { + "item_id": "2", + "date_added": "2/15/2024", + "description": "A mesmerizing white gold ring featuring a stunning arrangement of diamonds, symbolizing everlasting beauty and opulence.", + "colors": "white, clear", + "metals": "white gold", + "stones": "diamonds", + "stones_carat_total_weight": 1.5, + "product_id": "1", + "image": "2.png" + }, + { + "item_id": "3", + "date_added": "2/20/2024", + "description": "A radiant pink gold bracelet, exuding regal charm and sophistication.", + "colors": "pink", + "metals": "pink gold", + "stones": "no stones", + "stones_carat_total_weight": 0.0, + "product_id": "2", + "image": "3.png" + }, + { + "item_id": "4", + "date_added": "2/20/2024", + "description": "An exquisite white gold bracelet, epitomizing luxury and grace.", + "colors": "white", + "metals": "white gold", + "stones": "no stones", + "stones_carat_total_weight": 0.0, + "product_id": "2", + "image": "4.png" + }, + { + "item_id": "5", + "date_added": "2/25/2024", + "description": "A majestic yellow gold necklace adorned with glistening diamonds, exuding royal elegance and grandeur.", + "colors": "yellow, clear", + "metals": "yellow gold", + "stones": "diamonds", + "stones_carat_total_weight": 3.0, + "product_id": "3", + "image": "5.png" + }, + { + "item_id": "6", + "date_added": "2/25/2024", + "description": "An opulent white gold necklace featuring a breathtaking arrangement of diamonds, capturing the essence of luxury and sophistication.", + "colors": "white, clear", + "metals": "white gold", + "stones": "diamonds", + "stones_carat_total_weight": 3.0, + "product_id": "3", + "image": "6.png" + }, + { + "item_id": "7", + "date_added": "3/1/2024", + "description": "Luxurious pink gold earrings adorned with sparkling diamonds, radiating opulent glamour and sophistication.", + "colors": "pink, clear", + "metals": "pink gold", + "stones": "diamonds", + "stones_carat_total_weight": 2.2, + "product_id": "4", + "image": "7.png" + }, + { + "item_id": "8", + "date_added": "3/1/2024", + "description": "Elegant white gold earrings featuring a dazzling array of diamonds, exuding timeless beauty and refinement.", + "colors": "white, clear", + "metals": "white gold", + "stones": "diamonds", + "stones_carat_total_weight": 2.2, + "product_id": "4", + "image": "8.png" + }, + { + "item_id": "9", + "date_added": "2/15/2024", + "description": "A timeless yellow gold ring adorned with sparkling diamonds, radiating elegance and grace.", + "colors": "yellow, clear", + "metals": "yellow gold", + "stones": "diamonds", + "stones_carat_total_weight": 1.0, + "product_id": "5", + "image": "9.png" + }, + { + "item_id": "10", + "date_added": "2/15/2024", + "description": "An exquisite pink gold ring featuring a delicate arrangement of diamonds, epitomizing sophistication and charm.", + "colors": "pink, clear", + "metals": "pink gold", + "stones": "diamonds", + "stones_carat_total_weight": 1.0, + "product_id": "5", + "image": "10.png" + }, + { + "item_id": "11", + "date_added": "03/04/2024", + "description": "An elegant white gold necklace adorned with a breathtaking butterfly pendant, symbolizing transformation and beauty.", + "colors": "white", + "metals": "white gold", + "stones": "no stones", + "stones_carat_total_weight": 0.0, + "product_id": "6", + "image": "11.png" + }, + { + "item_id": "12", + "date_added": "03/04/2024", + "description": "An elegant pink gold necklace adorned with a breathtaking butterfly pendant, symbolizing transformation and beauty.", + "colors": "pink", + "metals": "pink gold", + "stones": "no stones", + "stones_carat_total_weight": 0.0, + "product_id": "6", + "image": "12.png" + }, + { + "item_id": "13", + "date_added": "03/04/2024", + "description": "A mesmerizing white gold necklace featuring delicate leaf motifs and sparkling diamond accents, evoking the enchanting beauty of a mystical forest.", + "colors": "white", + "metals": "white gold", + "stones": "diamonds", + "stones_carat_total_weight": 0.7, + "product_id": "7", + "image": "13.png" + }, + { + "item_id": "14", + "date_added": "03/04/2024", + "description": "A mesmerizing yellow gold necklace featuring delicate leaf motifs and sparkling diamond accents, evoking the enchanting beauty of a mystical forest.", + "colors": "yellow", + "metals": "yellow gold", + "stones": "diamonds", + "stones_carat_total_weight": 0.7, + "product_id": "7", + "image": "14.png" + }, + { + "item_id": "15", + "date_added": "2/15/2024", + "description": "An enchanting white gold ring adorned with a heart shaped diamond, symbolizing eternal love and romance.", + "colors": "white, clear", + "metals": "white gold", + "stones": "diamonds", + "stones_carat_total_weight": 1.8, + "product_id": "8", + "image": "15.png" + }, + { + "item_id": "16", + "date_added": "2/15/2024", + "description": "An enchanting pink gold ring adorned with a heart shaped diamond, symbolizing eternal love and romance.", + "colors": "pink, clear", + "metals": "pink gold", + "stones": "diamonds", + "stones_carat_total_weight": 1.8, + "product_id": "8", + "image": "16.png" + }, + { + "item_id": "17", + "date_added": "2/15/2024", + "description": "An enchanting yellow gold ring adorned with a heart shaped diamond, symbolizing eternal love and romance.", + "colors": "yellow, clear", + "metals": "yellow gold", + "stones": "diamonds", + "stones_carat_total_weight": 1.8, + "product_id": "8", + "image": "17.png" + }, + { + "item_id": "18", + "date_added": "2/25/2024", + "description": "An exquisite chain pink gold necklace, capturing the essence of timeless elegance and sophistication.", + "colors": "pink", + "metals": "pink gold", + "stones": "no stones", + "stones_carat_total_weight": 0.0, + "product_id": "9", + "image": "18.png" + }, + { + "item_id": "19", + "date_added": "2/25/2024", + "description": "An exquisite chain yellow gold necklace, capturing the essence of timeless elegance and sophistication.", + "colors": "yellow", + "metals": "yellow gold", + "stones": "no stones", + "stones_carat_total_weight": 0.0, + "product_id": "9", + "image": "19.png" + } +] \ No newline at end of file diff --git a/jewelry/data/mock_data/sources/products.json b/jewelry/data/mock_data/sources/products.json new file mode 100644 index 0000000..06eeb3c --- /dev/null +++ b/jewelry/data/mock_data/sources/products.json @@ -0,0 +1,65 @@ +[ + { + "product_id": "1", + "name": "Eternal Splendor Ring", + "kind": "rings", + "collections": "Forever Diamonds", + "gifts": "wife, anniversary" + }, + { + "product_id": "2", + "name": "Regal Enchantment Bracelet", + "kind": "bracelets", + "collections": "McKinsey Elegance", + "gifts": "wife, anniversary" + }, + { + "product_id": "3", + "name": "Royal Splendor Necklace", + "kind": "necklaces", + "collections": "Forever Diamonds", + "gifts": "wife, anniversary" + }, + { + "product_id": "4", + "name": "Opulent Glamour Earrings", + "kind": "earrings", + "collections": "Forever Diamonds", + "gifts": "wife, anniversary" + }, + { + "product_id": "5", + "name": "Timeless Grace Ring", + "kind": "rings", + "collections": "Forever Diamonds", + "gifts": "wife, anniversary" + }, + { + "product_id": "6", + "name": "Enchanted Forest Necklace", + "kind": "necklaces", + "collections": "Iguazio Nature Capture", + "gifts": "mom, birthday" + }, + { + "product_id": "7", + "name": "Enchanted Leaf Necklace", + "kind": "earrings", + "collections": "Iguazio Nature Capture, Forever Diamonds", + "gifts": "best friend, mom, birthday" + }, + { + "product_id": "8", + "name": "Enchanted Love Ring", + "kind": "rings", + "collections": "Forever Diamonds", + "gifts": "wife, anniversary, best friend" + }, + { + "product_id": "9", + "name": "Majestic Chain Necklace", + "kind": "necklaces", + "collections": "Timeless", + "gifts": "wife, graduation, best friend, mom" + } +] \ No newline at end of file diff --git a/jewelry/data/mock_data/sources/users.json b/jewelry/data/mock_data/sources/users.json new file mode 100644 index 0000000..fa6027f --- /dev/null +++ b/jewelry/data/mock_data/sources/users.json @@ -0,0 +1,62 @@ +[ + { + "user_id": "1", + "password": "1234", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "05/15/1990", + "email": "john.doe@example.com", + "phone": "+1234567890", + "address": "123 Main St, Cityville, USA" + }, + { + "user_id": "2", + "password": "1234", + "first_name": "Jane", + "last_name": "Smith", + "date_of_birth": "09/25/1985", + "email": "jane.smith@example.com", + "phone": "+1987654321", + "address": "456 Elm St, Townsville, USA" + }, + { + "user_id": "3", + "password": "1234", + "first_name": "Alice", + "last_name": "Johnson", + "date_of_birth": "02/10/1995", + "email": "alice.johnson@example.com", + "phone": "+1122334455", + "address": "789 Oak St, Villageton, USA" + }, + { + "user_id": "4", + "password": "1234", + "first_name": "Emily", + "last_name": "Davis", + "date_of_birth": "07/20/1988", + "email": "emily.davis@example.com", + "phone": "+1555123456", + "address": "321 Pine St, Hamletown, USA" + }, + { + "user_id": "5", + "password": "1234", + "first_name": "Michael", + "last_name": "Wilson", + "date_of_birth": "12/12/1976", + "email": "michael.wilson@example.com", + "phone": "+1789456123", + "address": "555 Cedar Ave, Villageville, USA" + }, + { + "user_id": "6", + "password": "1234", + "first_name": "Sophia", + "last_name": "Brown", + "date_of_birth": "4/30/1994", + "email": "sophia.brown@example.com", + "phone": "+1324578963", + "address": "987 Maple Blvd, Suburbia, USA" + } +] \ No newline at end of file diff --git a/jewelry/data/mock_data/stock_to_purchase.csv b/jewelry/data/mock_data/stock_to_purchase.csv new file mode 100644 index 0000000..5b4a333 --- /dev/null +++ b/jewelry/data/mock_data/stock_to_purchase.csv @@ -0,0 +1,29 @@ +purchase_id,stock_id +e12e1d1139e3481b8aa53a88cb95a156,eab0dda912dc46589792aa66062cd0f0 +e12e1d1139e3481b8aa53a88cb95a156,94c6c02dcf954992b8fab2b4fbe83957 +e12e1d1139e3481b8aa53a88cb95a156,095cf60f8d5849bbadfc819f5a5d3c8b +e12e1d1139e3481b8aa53a88cb95a156,b850731172814777bdee229b655b9a15 +1abc839fe844479f9154e5c683264436,071ee7b2b0034b21839c6416057e3f81 +1abc839fe844479f9154e5c683264436,a2502fd312c041be8f402ab973c698fe +1abc839fe844479f9154e5c683264436,e4134a5f972847bf887fedde7e9d480a +1abc839fe844479f9154e5c683264436,da797eff7254440ea25f1062fdb1a3a4 +773ca8faaf17471a82c0792e2ffeb661,9d18ee0c1b4f4b69890cd27ba3a1f33a +773ca8faaf17471a82c0792e2ffeb661,14cd9ce214394012b87b4a8e29fde350 +773ca8faaf17471a82c0792e2ffeb661,6d0ca9c5b42c498baaff235fef0a8684 +02a941880e674f82ae0430c404c7f62b,071ee7b2b0034b21839c6416057e3f81 +72d033b842474d96aaaaf94153a6dc45,6493a22ef2b0462483839a95b15a3cf8 +f469aa686ea24023b0241786068eab1f,d7b15e14dbc243bc9dfe085fc9648a20 +f469aa686ea24023b0241786068eab1f,071ee7b2b0034b21839c6416057e3f81 +f469aa686ea24023b0241786068eab1f,561b741c2469438193c557aa00c5fda8 +f469aa686ea24023b0241786068eab1f,df720546003a48e8a10e92e760e0c64e +8751e73e3f4b4b758931de66506f71a7,561b741c2469438193c557aa00c5fda8 +8751e73e3f4b4b758931de66506f71a7,b426e82405864db9a2d3fabb6f683533 +80d7415470294775aec85bffc41f852d,bd61e116dfac43a9a2c23824b073db4d +d4592d31cafd44cb8a64a44e64aff624,eab0dda912dc46589792aa66062cd0f0 +d4592d31cafd44cb8a64a44e64aff624,071ee7b2b0034b21839c6416057e3f81 +d4592d31cafd44cb8a64a44e64aff624,2111fbcc9bd14089a4cb24e95ed3b7ce +d4592d31cafd44cb8a64a44e64aff624,78415b9141eb4b2c94a42787124dde10 +f315be28cd9f469ea713116faf49b123,aba66cc1662b452c855b8f450b77db67 +f315be28cd9f469ea713116faf49b123,2e93635373c44a00920d269e937ae90c +f315be28cd9f469ea713116faf49b123,e4134a5f972847bf887fedde7e9d480a +f315be28cd9f469ea713116faf49b123,0792aa110f224e48a367faed0b0abfad diff --git a/jewelry/data/mock_data/stocks.csv b/jewelry/data/mock_data/stocks.csv new file mode 100644 index 0000000..91f14c8 --- /dev/null +++ b/jewelry/data/mock_data/stocks.csv @@ -0,0 +1,61 @@ +stock_id,item_id,size,amount,price +0729693718354da5bc0d3d20dc0d50d0,1,3.0,3,350 +b960119d1af642f1b4abc41c1a3981e9,1,5.0,3,350 +2e93635373c44a00920d269e937ae90c,1,7.0,0,350 +695dfa4487fc4f51981efc1b677f3bd3,1,9.0,0,350 +da797eff7254440ea25f1062fdb1a3a4,2,3.0,5,450 +b1c2f9a800f74540bbebfebd52805280,2,5.0,2,450 +e1ef590e98864b60886136766c077945,2,7.0,2,450 +095cf60f8d5849bbadfc819f5a5d3c8b,2,9.0,0,450 +911707ea1c174d45ae738a90e81755e0,3,6.3,4,50 +c2827b74b08445dfafbf8f77338ade4d,3,6.7,1,50 +2111fbcc9bd14089a4cb24e95ed3b7ce,3,7.1,1,50 +411c90c2caae4f87a3e3f6450666460b,3,7.9,5,50 +94c6c02dcf954992b8fab2b4fbe83957,3,9.0,5,50 +b850731172814777bdee229b655b9a15,4,6.3,3,250 +7201229fe2a34e4082cc0da595758229,4,6.7,4,250 +34ff315aa96f44dca964fd8e6982f406,4,7.1,3,250 +3e01769d013d43438d9f69df5ecbc20d,4,7.9,2,250 +29edbef0ffef4962b66ad267955e7b7b,4,9.0,2,250 +b426e82405864db9a2d3fabb6f683533,5,16.0,5,800 +78415b9141eb4b2c94a42787124dde10,5,17.7,4,800 +6493a22ef2b0462483839a95b15a3cf8,5,19.4,5,800 +f0ba2e512a3e43098bb97f3cdfd4ec68,6,16.0,1,1350 +071ee7b2b0034b21839c6416057e3f81,6,17.7,3,1350 +e4134a5f972847bf887fedde7e9d480a,6,19.4,4,1350 +3c8a09b713a64d3595d23375026e5f16,7,1.0,2,400 +aec074d6f238479a98be0a7cd7a6d090,8,1.0,5,450 +c005a07e63f84067a19b717716ffb49d,9,3.0,2,400 +5eac7c553bcd4823b3fcb2fc4a8e0c72,9,5.0,2,400 +561b741c2469438193c557aa00c5fda8,9,7.0,3,400 +a9ee36d5b33c41208cd18f3a2084aee7,9,9.0,5,400 +9d18ee0c1b4f4b69890cd27ba3a1f33a,10,3.0,1,600 +676cc2a8e79d44fbafeb51693dee0bfa,10,5.0,2,600 +d72bb98c3e92490da6ad9ac12a372c05,10,7.0,3,600 +a2502fd312c041be8f402ab973c698fe,10,9.0,5,600 +6d0ca9c5b42c498baaff235fef0a8684,11,16.0,2,295 +82513a76619948d5a4b75299f41d83f1,11,17.7,3,295 +bd8ae3dd3da54874a389377f0fb739e9,11,19.4,4,295 +d7b15e14dbc243bc9dfe085fc9648a20,12,16.0,1,85 +1ae16523cedb4d659e065da84ccb7da4,12,17.7,2,85 +59fea0fd8a49449f8a66ca3606625e91,12,19.4,2,85 +23b8c8dc928742bb80763d5495e102f4,13,1.0,4,350 +eab0dda912dc46589792aa66062cd0f0,14,1.0,3,300 +207d058cd7ea494ebf16354da49c9544,15,3.0,0,850 +dce1f886f2bd4394a65147de78bf2648,15,5.0,4,850 +a8436ae847d846c6b108960d6ca13879,15,7.0,1,850 +d0f89190662e4512967c00034b354a16,15,9.0,2,850 +d550a8cf2c364866a540ebd79ea98ca9,16,3.0,2,500 +7ac5e6f126564060891e341ffa71ea1f,16,5.0,0,500 +fa6be91d17e24055b11035ddedfbc8af,16,7.0,1,500 +aba66cc1662b452c855b8f450b77db67,16,9.0,3,500 +bd61e116dfac43a9a2c23824b073db4d,17,3.0,0,600 +627f79f1761f4864a1a00809b11096ba,17,5.0,0,600 +d7b35ffc69c14d9294ea1385c6dc6656,17,7.0,2,600 +0236501c251e4154a1447d019e16ee20,17,9.0,5,600 +22c2e5de2397465dab814866749253b9,18,16.0,1,85 +ec3fc86af0b345dfad6a41befcce44fa,18,17.7,1,85 +6fc0e49b16ce41a4960b12e030962de8,18,19.4,0,85 +0792aa110f224e48a367faed0b0abfad,19,16.0,4,95 +14cd9ce214394012b87b4a8e29fde350,19,17.7,2,95 +df720546003a48e8a10e92e760e0c64e,19,19.4,3,95 diff --git a/jewelry/data/mock_data/users.csv b/jewelry/data/mock_data/users.csv new file mode 100644 index 0000000..41199ab --- /dev/null +++ b/jewelry/data/mock_data/users.csv @@ -0,0 +1,7 @@ +user_id,password,first_name,last_name,date_of_birth,email,phone,address +1,1234,John,Doe,1990-05-15,john.doe@example.com,+1234567890,"123 Main St, Cityville, USA" +2,1234,Jane,Smith,1985-09-25,jane.smith@example.com,+1987654321,"456 Elm St, Townsville, USA" +3,1234,Alice,Johnson,1995-02-10,alice.johnson@example.com,+1122334455,"789 Oak St, Villageton, USA" +4,1234,Emily,Davis,1988-07-20,emily.davis@example.com,+1555123456,"321 Pine St, Hamletown, USA" +5,1234,Michael,Wilson,1976-12-12,michael.wilson@example.com,+1789456123,"555 Cedar Ave, Villageville, USA" +6,1234,Sophia,Brown,1994-04-30,sophia.brown@example.com,+1324578963,"987 Maple Blvd, Suburbia, USA" diff --git a/jewelry/data/rag_data/jewelry_matching.txt b/jewelry/data/rag_data/jewelry_matching.txt new file mode 100644 index 0000000..217cb05 --- /dev/null +++ b/jewelry/data/rag_data/jewelry_matching.txt @@ -0,0 +1,29 @@ +## Celebrate Milestones with Meaningful Jewelry: Matching Stone and Metal to the Occasion + +**Beyond the perfect piece, consider how the stone and metal choices can further elevate your gift and align with the occasion:** + +**For your wife's birthday:** + +* **Birthstones:** Select a piece featuring her birthstone for a personal touch, symbolizing different qualities like love (ruby), wisdom (amethyst), or happiness (citrine). +* **Pearls:** Timeless and elegant, pearls represent purity, innocence, and new beginnings - perfect for celebrating another year together. +* **Metals:** Opt for classic gold or silver, or explore rose gold for a touch of warmth and femininity. + +**For your girlfriend's graduation:** + +* **Gemstones:** Choose stones associated with success and achievement, like sapphire (wisdom and integrity), diamond (strength and perseverance), or emerald (growth and new beginnings). +* **Metals:** Silver is often associated with purity and new beginnings, while gold adds a touch of sophistication and celebration. + +**Beyond Birthdays and Graduations:** + +* **Anniversary:** Opt for gemstones associated with love and commitment, like ruby, diamond, or emerald. Consider matching the metal of your engagement rings for a unified look. +* **Mother's Day:** Pearls represent nurturing and maternal love, making pearl necklaces or earrings a classic choice. Gemstones associated with calmness and well-being, like amethyst or aquamarine, can also be thoughtful options. +* **Engagement:** Diamonds are the traditional choice for their symbolism of everlasting love and commitment. However, other gemstones like sapphires or emeralds can be chosen based on personal preference. + +**For Special People:** + +* **Daughter:** As she grows, consider her birthstone or gemstones associated with her interests, like turquoise for creativity or garnet for passion. Choose metals like silver or rose gold for a youthful and trendy feel. +* **Sister:** Match her personality with vibrant gemstones like garnet (passion), aquamarine (friendship), or citrine (happiness). Choose metals that complement her existing jewelry collection. +* **Best friend:** Opt for gemstones symbolizing friendship and loyalty, like garnet or turquoise. Consider matching bracelets or necklaces with the same stone and metal for a meaningful gesture. +* **Colleague:** Keep it professional with classic gemstones like sapphire (wisdom and integrity), or onyx (strength and leadership). Silver or platinum are ideal metals for a sophisticated and work-appropriate look. + +**Remember:** Matching the stone and metal to the occasion adds another layer of thoughtfulness and symbolism to your gift. However, the most important aspect remains choosing a piece that reflects your appreciation and resonates with the recipient's style and personality. \ No newline at end of file diff --git a/jewelry/data/rag_data/jewelry_policies.txt b/jewelry/data/rag_data/jewelry_policies.txt new file mode 100644 index 0000000..080ff81 --- /dev/null +++ b/jewelry/data/rag_data/jewelry_policies.txt @@ -0,0 +1,53 @@ +## Customer Policy for Iguazio Jewelry + +**General:** + +* We strive to provide excellent customer service and high-quality jewelry products. +* We reserve the right to modify this policy at any time without prior notice. +* By placing an order with us, you acknowledge and agree to be bound by the terms of this policy. + +**Orders:** + +* We accept orders placed on our website 24/7. +* Orders are processed within 1 day after they are placed. +* Order confirmation emails will be sent to the email address provided at checkout. +* We reserve the right to cancel any order for any reason. + +**Payments:** + +* We accept visa and mastercard. +* All payments are processed securely through a trusted payment gateway. +* Your credit card information is never stored on our servers. + +**Shipping:** + +* We offer a variety of shipping options with varying delivery times and costs. +* Shipping costs are calculated based on the weight, size, and destination of your order. +* You will be able to choose your preferred shipping option at checkout. +* We are not responsible for lost, stolen, or damaged packages once they have been shipped. + +**Returns and Exchanges:** + +* We accept returns and exchanges within 1 month of the purchase date. +* Items must be returned in their original packaging, unworn, and in new condition. +* We do not offer refunds for shipping costs on returned items. +* Please contact us for a return authorization before sending any items back. + +**Warranties:** + +* All our jewelry comes with a 5-year warranty against manufacturing defects. +* This warranty does not cover damage caused by normal wear and tear, misuse, or accidents. +* Please contact us for warranty claims. + +**Privacy:** + +* We respect your privacy and are committed to protecting your personal information. +* We will not share your personal information with any third party without your consent. +* You can review our full privacy policy on our website. + +**Contact Us:** + +* If you have any questions or concerns, please contact us via email at fake-mail@store.com or phone at 123456789. +* We will reply to your inquiries as soon as possible. + + diff --git a/jewelry/data/rag_data/jewelry_size_help.txt b/jewelry/data/rag_data/jewelry_size_help.txt new file mode 100644 index 0000000..5497a1e --- /dev/null +++ b/jewelry/data/rag_data/jewelry_size_help.txt @@ -0,0 +1,27 @@ +A guide to help choose the right size for jewelry + +**Rings** +- Size 3: This is a smaller ring size, suitable for slender fingers. +- Size 5: Another relatively small ring size, but slightly larger than size 3. +- Size 7: A medium-sized ring, suitable for average-sized fingers. +- Size 9: A larger ring size, best for those with wider fingers. + +**Bracelets** +- Size 6.3 inches: A small bracelet size, best for petite or slim wrists. +- Size 6.7 inches: Another small bracelet size, slightly larger than 6.3 inches. +- Size 7.1 inches: A medium-sized bracelet, suitable for average-sized wrists. +- Size 7.9 inches: Another medium-to-large size, best for those with larger wrists. +- Size 9 inches: A large bracelet size, suitable for those with bigger wrists. + +**Necklaces** +- Size 16 inches: A choker-length necklace that sits snugly around the base of the neck. +- Size 17.7 inches: A princess-length necklace that falls just above the collarbone. +- Size 19.4 inches: A matinee-length necklace that falls below the collarbone. + +**Earrings** +- Size 1 inch: This could refer to the diameter of stud earrings or the length of dangle/drop earrings from the ear wire + to the bottom. + +When selecting your size, keep in mind that these measurements are exact. +It's always best to try on different sizes or get professionally measured to ensure the perfect fit for your personal + preference and comfort level. \ No newline at end of file diff --git a/jewelry/data/sql_db.py b/jewelry/data/sql_db.py new file mode 100644 index 0000000..e319ee5 --- /dev/null +++ b/jewelry/data/sql_db.py @@ -0,0 +1,451 @@ +# Copyright 2024 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +SQL Database for the jewelry agent demo. +""" + +import datetime +from typing import List, Literal + +import pandas as pd +from sqlalchemy import ( + Boolean, + Column, + Date, + Engine, + Float, + ForeignKey, + Integer, + String, + Table, + create_engine, + func, + or_, + select, +) +from sqlalchemy.orm import ( + Mapped, + declarative_base, + mapped_column, + relationship, +) + +ID_LENGTH = 32 + +Base = declarative_base() + + +#: Association table between stocks and purchases for many-to-many relationship. +stock_to_purchase = Table( + "stock_to_purchase", + Base.metadata, + Column( + "stock_id", + String(length=ID_LENGTH), + ForeignKey("stock.stock_id"), + primary_key=True, + ), + Column( + "purchase_id", + String(length=ID_LENGTH), + ForeignKey("purchase.purchase_id"), + primary_key=True, + ), +) + + +class Product(Base): + """ + A product is the main entity in the jewelry database. It represents a piece of jewelry in all its variations. + + :arg product_id: The unique identifier of the product. + :arg name: The name of the product. + :arg kind: The kind of the product, one of: rings, bracelets, necklaces, earrings. + :arg collections: The collections that this product belongs to. + :arg gifts: The gifts that this product is recommended as. + :arg items: The items that belong to this product. Usually separated by metals and stones. + """ + + __tablename__ = "product" + + # Columns: + product_id: Mapped[str] = mapped_column(String(length=ID_LENGTH), primary_key=True) + name: Mapped[str] = mapped_column(String(length=30)) + kind: Mapped[str] = mapped_column(String(length=30)) + collections: Mapped[str] = mapped_column(String(length=30)) + gifts: Mapped[str] = mapped_column(String(length=30)) + + # Relationships: + items: Mapped[List["Item"]] = relationship( # one-to-many + back_populates="product", lazy=True + ) + + +class Item(Base): + """ + An item is a specific variation of a product that holds all of its properties. + + :arg item_id: The unique identifier of the item. + :arg product_id: The unique identifier of the product that this item belongs to. + :arg date_added: The date that this item was added to the store. + :arg description: A description of the item. + :arg colors: The colors of the item. + :arg metals: The metals of the item. Can be one or more of the following metals: yellow gold, + white gold, pink gold, silver and platinum. + :arg stones: The material of the item. Default is None (“no stones”). + :arg stones_carat_total_weight: The total weight of the stones in the item. + :arg image: The URL to the item's image. + :arg product: The product that this item belongs to. + :arg stocks: The stocks of this item. + :arg reviews: The reviews of this item. + """ + + __tablename__ = "item" + + # Columns: + item_id: Mapped[str] = mapped_column(String(length=ID_LENGTH), primary_key=True) + product_id: Mapped[str] = mapped_column( + String(length=ID_LENGTH), ForeignKey("product.product_id") + ) + date_added: Mapped[datetime.date] = mapped_column(Date()) + description: Mapped[str] = mapped_column(String(length=1000)) + colors: Mapped[str] = mapped_column(String(length=100)) + metals: Mapped[str] = mapped_column(String(length=100)) + stones: Mapped[str] = mapped_column( + String(length=100), default="no stones", nullable=True + ) + stones_carat_total_weight: Mapped[float] = mapped_column( + Float(precision=2), nullable=True + ) + image: Mapped[str] = mapped_column(String(length=1000)) + + # Relationships: + product: Mapped["Product"] = relationship( # one-to-many + back_populates="items", lazy=True + ) + stocks: Mapped[List["Stock"]] = relationship( # one-to-many + back_populates="item", lazy=True + ) + reviews: Mapped[List["Review"]] = relationship( # one-to-many + back_populates="item", lazy=True + ) + + +class Stock(Base): + """ + A stock is a specific item that is available for purchase in a specific size and price. + + :arg stock_id: The unique identifier of the stock. + :arg item_id: The unique identifier of the item that this stock belongs to. + :arg size: The size of the item (for example, a diameter for rings: 4). + :arg amount: The amount of the item in the specific size in stock. + :arg price: The price of the item in dollars. + :arg item: The item that this stock belongs to. + :arg purchases: The purchases that this stock is part of. + """ + + __tablename__ = "stock" + + # Columns: + stock_id: Mapped[str] = mapped_column(String(length=ID_LENGTH), primary_key=True) + item_id: Mapped[str] = mapped_column( + String(length=ID_LENGTH), ForeignKey("item.item_id") + ) + size: Mapped[float] = mapped_column(Float(precision=1)) + amount: Mapped[int] = mapped_column(Integer()) + price: Mapped[float] = mapped_column(Float(precision=4)) + + # Relationships: + item: Mapped["Item"] = relationship(back_populates="stocks", lazy=True) # 1-to-many + purchases: Mapped[List["Purchase"]] = relationship( # many-to-many + secondary=stock_to_purchase, + back_populates="stocks", + lazy=True, + ) + + +class User(Base): + """ + A user is a person that uses the store's website. + + :arg user_id: The unique identifier of the user. + :arg first_name: The first name of the user. + :arg last_name: The last name of the user. + :arg date_of_birth: The date of birth of the user. + :arg email: The email of the user. + :arg phone: The phone number of the user. + :arg address: The address of the user. + :arg purchases: The purchases that the user made. + :arg reviews: The reviews that the user wrote. + """ + + __tablename__ = "user" + + # Columns: + user_id: Mapped[str] = mapped_column(String(length=ID_LENGTH), primary_key=True) + first_name: Mapped[str] = mapped_column(String(length=30)) + last_name: Mapped[str] = mapped_column(String(length=30)) + date_of_birth: Mapped[datetime.date] = mapped_column(Date()) + email: Mapped[str] = mapped_column(String(length=30), nullable=True) + phone: Mapped[str] = mapped_column(String(length=30), nullable=True) + address: Mapped[str] = mapped_column(String(length=100), nullable=True) + + # Relationships: + purchases: Mapped[List["Purchase"]] = relationship( # 1-to-many + back_populates="user", lazy=True + ) + reviews: Mapped[List["Review"]] = relationship( # 1-to-many + back_populates="user", lazy=True + ) + + +class Purchase(Base): + """ + A purchase is a user's purchase of a specific stock. + + :arg purchase_id: The unique identifier of the purchase. + :arg user_id: The unique identifier of the user that made the purchase. + :arg date: The date of the purchase. + :arg user: The user that made the purchase. + :arg stocks: The stocks items that were purchased. + """ + + __tablename__ = "purchase" + + # Columns: + purchase_id: Mapped[str] = mapped_column(String(length=ID_LENGTH), primary_key=True) + user_id: Mapped[str] = mapped_column( + String(length=ID_LENGTH), ForeignKey("user.user_id") + ) + date: Mapped[datetime.date] = mapped_column(Date()) + + # Relationships: + user: Mapped["User"] = relationship( # 1-to-many + back_populates="purchases", lazy=True + ) + stocks: Mapped[List["Stock"]] = relationship( # many-to-many + secondary=stock_to_purchase, back_populates="purchases", lazy=True + ) + + +class Review(Base): + """ + A review is a user's opinion about a specific item. + + :arg review_id: The unique identifier of the review. + :arg item_id: The unique identifier of the item that this review belongs to. + :arg user_id: The unique identifier of the user that wrote the review. + :arg date: The date that the review was written. + :arg text: The text of the review. + :arg rating: The rating of the review. An integer between 1 and 5. + :arg is_recommend: Whether the user recommends the item or not. + :arg item: The item that this review belongs to. + :arg user: The user that wrote the review. + """ + + __tablename__ = "review" + + # Columns: + review_id: Mapped[str] = mapped_column(String(length=ID_LENGTH), primary_key=True) + item_id: Mapped[str] = mapped_column( + String(length=ID_LENGTH), ForeignKey("item.item_id") + ) + user_id: Mapped[str] = mapped_column( + String(length=ID_LENGTH), ForeignKey("user.user_id") + ) + date: Mapped[datetime.date] = mapped_column(Date()) + text: Mapped[str] = mapped_column(String(length=1000), default="", nullable=True) + rating: Mapped[int] = mapped_column(Integer()) + is_recommend: Mapped[bool] = mapped_column(Boolean()) + + # Relationships: + item: Mapped["Item"] = relationship( # 1-to-many + back_populates="reviews", lazy=True + ) + user: Mapped["User"] = relationship( # 1-to-many + back_populates="reviews", lazy=True + ) + + +def get_engine(sql_connection_url: str) -> Engine: + """ + Get the SQL database engine. + + :param sql_connection_url: The SQL connection URL. + + :return: The SQL database engine. + """ + return create_engine(sql_connection_url) + + +def create_tables(engine: Engine): + """ + Create the database tables. + """ + Base.metadata.create_all(engine) + + +def drop_tables(engine: Engine): + """ + Drop the database tables. + """ + # Delete the schema's tables: + Base.metadata.drop_all(engine) + + +def get_items( + engine: Engine, + kinds: List[Literal["rings", "necklaces", "bracelets", "earrings"]] = None, + colors: List[str] = None, + metals: List[str] = None, + stones: List[str] = None, + collections: List[str] = None, + gifts: List[str] = None, + min_price: float = None, + max_price: float = None, + sort_by: Literal[ + "highest_price", "lowest_price", "best_reviews", "best_seller", "newest" + ] = None, +) -> pd.DataFrame: + """ + Get the items from the database. + + :param engine: A SQL database engine. + :param kinds: Kinds of products to filter by. + :param colors: Colors of items to filter by. + :param metals: Metals of items to filter by. + :param stones: Stones of items to filter by. + :param collections: Collections of products to filter by. + :param gifts: Gifts of products to filter by. + :param min_price: The minimum price of the items. + :param max_price: The maximum price of the items. + :param sort_by: Sort the items by one of the following: highest_price, lowest_price, best_reviews, best_seller, + newest. + + :return: A DataFrame of the items. + """ + with engine.connect() as conn: + items_total_purchases_query = ( + select( + Stock.item_id, + func.count(stock_to_purchase.c.stock_id).label("total_purchases"), + ) + .join( + stock_to_purchase, + Stock.stock_id == stock_to_purchase.c.stock_id, + isouter=True, + ) + .group_by(Stock.item_id) + .subquery() + ) + items_average_reviews_query = ( + select(Review.item_id, func.avg(Review.rating).label("average_rating")) + .group_by(Review.item_id) + .subquery() + ) + items_average_price_query = ( + select(Stock.item_id, func.avg(Stock.price).label("price")) + .group_by(Stock.item_id) + .subquery() + ) + + query = ( + select(Item, items_average_price_query.c.price) + .join(Item.product) + .join(items_average_price_query) + .join(items_total_purchases_query, isouter=True) + .join(items_average_reviews_query, isouter=True) + .group_by(Item.item_id) + ) + + if min_price: + query = query.where(items_average_price_query.c.price >= min_price) + if max_price: + query = query.where(items_average_price_query.c.price <= max_price) + + if kinds: + query = query.where(Product.kind.in_(kinds)) + if colors: + or_criteria = [Item.colors.in_(colors)] + for color in colors: + or_criteria.append(Item.colors.like(f"%{color}%")) + query = query.where(or_(*or_criteria)) + if metals: + or_criteria = [Item.metals.in_(metals)] + for metal in metals: + or_criteria.append(Item.metals.like(f"%{metal}%")) + query = query.where(or_(*or_criteria)) + if stones: + or_criteria = [Item.stones.in_(stones)] + for stone in stones: + or_criteria.append(Item.stones.like(f"%{stone}%")) + query = query.where(or_(*or_criteria)) + if collections: + or_criteria = [Product.collections.in_(collections)] + for collection in collections: + or_criteria.append(Product.collections.like(f"%{collection}%")) + query = query.where(or_(*or_criteria)) + if gifts: + or_criteria = [Product.gifts.in_(gifts)] + for gift in gifts: + or_criteria.append(Product.gifts.like(f"%{gift}%")) + query = query.where(or_(*or_criteria)) + + if sort_by: + if sort_by == "highest_price": + query = query.order_by(Item.stones_carat_total_weight.desc()) + elif sort_by == "lowest_price": + query = query.order_by(Item.stones_carat_total_weight.asc()) + elif sort_by == "best_reviews": + query = query.order_by( + items_average_reviews_query.c.average_rating.desc() + ) + elif sort_by == "best_seller": + query = query.order_by( + items_total_purchases_query.c.total_purchases.desc() + ) + elif sort_by == "newest": + query = query.order_by(Item.date_added.desc()) + + items = conn.execute(query).all() + return pd.DataFrame(items) + + +def get_user_items_purchases_history( + engine: Engine, user_id: str, last_n_purchases: int = 5 +) -> pd.DataFrame: + """ + Get the user's items purchase history. + + :param engine: A SQL database engine. + :param user_id: The user's unique identifier. + :param last_n_purchases: The last n purchases to return. + + :return: A DataFrame of the user's items purchase history. + """ + with engine.connect() as conn: + query = ( + select(Item) + .join(Stock) + .join(stock_to_purchase) + .join(Purchase) + .where(Purchase.user_id == user_id) + .order_by(Purchase.date.desc()) + .distinct() + ) + if last_n_purchases: + query = query.limit(last_n_purchases) + purchases = conn.execute(query).all() + + return pd.DataFrame(purchases) diff --git a/jewelry/notebook.ipynb b/jewelry/notebook.ipynb new file mode 100644 index 0000000..af87fdf --- /dev/null +++ b/jewelry/notebook.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Jewelry store Agent Example\n", + "\n", + "## Table of Contents\n", + "1. [Introduction](#Introduction)\n", + "2. [Setup](#Setup)\n", + "3. [Deploy Factory](#Deploying-the-GenAI-Factory)\n", + "4. [Deploy Workflow](#Deploying-the-Workflow)\n", + "5. [Troubleshooting](#Troubleshooting)\n", + "\n", + "## Introduction\n", + "This notebook demonstrates how to set up and run an agent with a couple of tools.
\n", + "The Agent is connected to a sql database with customers and products.
\n", + "The agent will recommend products to customers based on their preferences, and purchase history.
\n", + "The agent also has access to a rag tool, containing information about the store's policy.
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "Before you start, make sure you have completed the following steps:\n", + "\n", + "1. Install the required packages:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!pip install -r requirements.txt" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "2. Set the environment variable:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!export GENAI_FACTORY_ENV_PATH=$(pwd)/.env" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "%%writefile .env\n", + "\n", + "# Configure your environment variables here\n", + "OPENAI_BASE_URL = ... # Your OpenAI base URL\n", + "OPENAI_API_KEY = ... # Your OpenAI API key\n", + "GENAI_FACTORY_IS_LOCAL_CONFIG = ... # Set to 'true' for local configuration\n", + "MLRUN_GENAI_LOCAL_CHROMA = ... # Path to local Chroma database (if applicable)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploying the Workflow" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "First we need to initialize the db of customers and products" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!python -m data.main" + ], + "outputs": [], + "execution_count": null + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "Next, we need to deploy the GenAI Factory application.
\n", + "First, we need to run the genai-factory controller and ui, run in terminal:

`make genai-factory` " + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Deploy the workflow defined in workflow.py\n", + "!genai-factory run workflow.py " + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This command will start the server and you should see output indicating that the server is running." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Troubleshooting\n", + "\n", + "If you encounter any issues:\n", + "\n", + "1. Ensure all required packages are installed correctly.\n", + "2. Check that the GENAI_FACTORY_ENV_PATH is set correctly.\n", + "3. Make sure Docker is running for the controller deployment.\n", + "4. Verify that the ports used (8000 for the workflow, 3000 for the UI, 8001 for the controller) are not in use by other applications.\n", + "\n", + "If problems persist, check the console output for specific error messages and refer to the project documentation for more detailed troubleshooting steps." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jewelry/requirements.txt b/jewelry/requirements.txt new file mode 100644 index 0000000..eb4558e --- /dev/null +++ b/jewelry/requirements.txt @@ -0,0 +1,7 @@ +chromadb~=0.3.29 +langchain_community +langchain_openai +langchain_huggingface +langchain-text-splitters +git+https://github.com/mlrun/genai-factory.git +urllib3==1.26.5 \ No newline at end of file diff --git a/jewelry/src/__init__.py b/jewelry/src/__init__.py new file mode 100644 index 0000000..69c9e84 --- /dev/null +++ b/jewelry/src/__init__.py @@ -0,0 +1 @@ +from jewelry.src.steps.jewelry_agent import Agent \ No newline at end of file diff --git a/jewelry/src/steps/jewelry_agent.py b/jewelry/src/steps/jewelry_agent.py new file mode 100644 index 0000000..1db8069 --- /dev/null +++ b/jewelry/src/steps/jewelry_agent.py @@ -0,0 +1,381 @@ +import re +from typing import Optional + +import pandas as pd +from jewelry.data.sql_db import ( + get_engine, + get_items, + get_user_items_purchases_history, +) +from langchain_community.document_loaders import TextLoader +from langchain_community.vectorstores import Chroma +from langchain_openai import OpenAIEmbeddings +from langchain_text_splitters import CharacterTextSplitter +from langchain.agents import AgentExecutor, tool +from langchain.agents.format_scratchpad.openai_tools import ( + format_to_openai_tool_messages, +) +from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser +from langchain.pydantic_v1 import BaseModel, Field +from langchain.tools.retriever import create_retriever_tool +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_openai import ChatOpenAI +from genai_factory.chains.base import ChainRunner +from genai_factory.chains.retrieval import MultiRetriever +import os +# from controller.src.controller.config import default_data_path +# Get the current file's directory +current_dir = os.path.dirname(os.path.abspath(__file__)) + +# Construct the path to the SQL database +sql_db_path = "data/sql.db" +# Create the SQLite connection string +sql_connection_string = f"sqlite:///{sql_db_path}" + +CLIENTS = { + "jon doe": ("John Doe", "1", "returning"), + "jane smith": ("Jane Smith", "2", "returning"), + "alice johnson": ("Alice Johnson", "3", "returning"), + "emily davis": ("Emily Davis", "4", "returning"), + "michael brown": ("Michael Brown", "5", "returning"), + "sophia brown": ("Sophia Brown", "6", "returning"), +} + +# CLIENT_NAME, CLIENT_ID, CLIENT_TYPE = CLIENTS["jon doe"] +# CLIENT_NAME, CLIENT_ID, CLIENT_TYPE = CLIENTS["jane smith"] +# CLIENT_NAME, CLIENT_ID, CLIENT_TYPE = CLIENTS["alice johnson"] +# CLIENT_NAME, CLIENT_ID, CLIENT_TYPE = CLIENTS["emily davis"] +CLIENT_NAME, CLIENT_ID, CLIENT_TYPE = CLIENTS["michael brown"] +# CLIENT_NAME, CLIENT_ID, CLIENT_TYPE = CLIENTS["sophia brown"] +# CLIENT_NAME, CLIENT_ID, CLIENT_TYPE = "unknown", "unknown", "new" +# + + +@tool +def add_to_cart() -> str: + """ + A tool to use if the client wants to buy the item, or asks how to buy it, or he agrees to your offer to add it + to the cart. + """ + if CLIENT_TYPE == "returning": + return ( + "Tell the user that you can add the item to the cart for him, ask him if he wants to proceed with the " + "purchase, if he says yes, and he is not a new client ask if you should use his old shipping and " + "payment details." + " if the user agreed to add the item to the cart, act as you did. make this answer short." + ) + return "Tell the user that you added the item to the cart. make this answer short." + + +@tool +def try_it_on() -> str: + """ + A tool to use when the user says he likes an item, or asks about trying it on or how the item will look on her. + """ + return ( + "Tell the user he can see how the item would look on him by pressing *here* (act like you have a link to the" + " try it on feature that enables you to see how the jewlry would look on you), after that ask if there is" + " something else you can do. make this answer short." + ) + + +@tool +def get_client_history_tool(user_id: str = None, new_client: bool = False) -> str: + """ + A tool to get the history of a client's transactions, use it to match recommendation to customers taste. + """ + if new_client or not user_id: + return "The user is a new client, he has no purchase history." + engine = get_engine(sql_connection_string) + items_df = get_user_items_purchases_history( + user_id=user_id, engine=engine, last_n_purchases=2 + ) + if items_df.empty: + return "The user has no purchase history." + items_df = items_df[["description"]] + combined_string = ", ".join([str(r) for r in items_df.to_dict(orient="records")]) + history = ( + "The user has the following purchase history: " + + combined_string + + ".\n Explain to the client shortly " + "why the item you suggest is relevant to him, in addition to the item description. Do not show him something " + "he already bought." + ) + return history + + +class JewelrySearchInput(BaseModel): + metals: Optional[list[str]] = Field( + description="A list of metals to filter the jewelry by,has to be yellow," + " pink, or white gold.", + default=None, + ) + stones: Optional[list[str]] = Field( + description="A list of stones to filter the jewelry by," + " currently only diamonds or no stones.", + default=None, + ) + colors: Optional[list[str]] = Field( + description="The color of the stone or metal filter the jewelry by," + " currently white, pink, yellow, clear.", + default=None, + ) + min_price: Optional[float] = Field( + description="The minimum price of the jewelry.", default=None + ) + max_price: Optional[float] = Field( + description="The maximum price of the jewelry.", default=None + ) + sort_by: Optional[str] = Field( + description="The column to sort the jewelry by, can be low_price, high_price," + " most_bought, or review_score.", + default="most_bought", + ) + kinds: Optional[list[str]] = Field( + description="The kind of jewelry to search for, currently " + "rings, necklaces, bracelets, earrings.", + default=None, + ) + + +def validate_param(params: list[str], options: list[str]): + """ + Validate every parameter in the params list, if the parameter is not in the options, remove it, + if all params in list removed, return None. + """ + if not params: + return None + if [p for p in params if p in options]: + return [p for p in params if p in options] + return None + + +@tool("jewelry-search-tool", args_schema=JewelrySearchInput) +def get_jewelry_tool( + metals: list[str] = None, + stones: Optional[list[str]] = None, + colors: list[str] = None, + min_price: float = None, + max_price: float = None, + sort_by: str = "best_seller", + kinds: list[str] = None, +) -> str: + """ + A tool to get most relevant jewelry items from the catalog database according to the user's query. + All literal values must match option, if the user gave a value that is not in the options, replace it with None. + If the user asks about availability of a specific item, use the get_jewelry_stock tool. + """ + # Double-check the parameters the agent sent + metals = validate_param( + params=metals, options=["yellow gold", "pink gold", "white gold"] + ) + stones = validate_param(params=stones, options=["diamonds", "no stones"]) + colors = validate_param(params=colors, options=["yellow", "clear", "white", "pink"]) + kinds = validate_param( + params=kinds, options=["rings", "necklaces", "bracelets", "earrings"] + ) + sort_by = ( + sort_by + if sort_by in ["low_price", "high_price", "most_bought", "review_score"] + else "most_bought" + ) + + # Get the jewelry items from the database + engine = get_engine(sql_connection_string) + jewelry_df = get_items( + engine=engine, + metals=metals, + stones=stones, + colors=colors, + sort_by=sort_by, + kinds=kinds, + min_price=min_price, + max_price=max_price, + ) + if jewelry_df.empty: + return "We don't have any jewelry that matches your query. try to change the parameters." + n = min(5, len(jewelry_df)) + print(jewelry_df.head(n)) + top_n_df: pd.DataFrame = jewelry_df.iloc[:n][ + ["description", "price", "item_id", "image"] + ] + combined_string = ", ".join([str(r) for r in top_n_df.to_dict(orient="records")]) + # Print the resulting string + print(combined_string) + + jewelry = str( + f"We have the following jewelry items in our catalog: {combined_string}.\n" + f"Look at the client's history and find " + f"the most relevant jewelry for him, max 3 items. Always show the customer the price. Also add image name but " + f"say nothing about it, just the name at the end of the sentence. Example: 'jewelry description, price, " + f"explanation of choice. image.png'." + ) + + return jewelry + + +def mark_down_response(response): + # Remove brackets and image: + cleaned_text = re.sub(r"\[|\]|Image|\:|image|\(|\)|#", "", response) + # Remove extra spaces + cleaned_text = re.sub(r"\s+", " ", cleaned_text) + # Define the pattern to search for .png file endings + pattern = r"\b(\w+\.png)\b" + image_dir = "https://s3.us-east-1.wasabisys.com/iguazio/data/demos/demo-llm-jewelry" + # Replace .png file endings with Markdown format including directory + image_markdown = rf"\n![]({image_dir}/\1)\n" + markdown_string = re.sub(pattern, image_markdown, cleaned_text) + + # Clean up the markdown string for duplicate images and brackets + s = "" + for line in markdown_string.split("\n"): + if not line: + s += "\n" + elif line in s or line in ["(", ")", "[", "]"]: + continue + elif line.startswith("![]"): + s += "\n\n" + line + "\n\n" + else: + s += line + "\n" + + return s + + +class Agent(ChainRunner): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._llm = None + self.agent = None + self.retriever = None + + @property + def llm(self): + if not self._llm: + self._llm = ChatOpenAI(model="gpt-4", temperature=0.5) + return self._llm + + def _get_agent(self): + if self.agent: + return self.agent + # Create the RAG tools + loader = TextLoader("data/rag_data/jewelry_policies.txt") + documents = loader.load() + text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) + texts = text_splitter.split_documents(documents) + embeddings = OpenAIEmbeddings() + db = Chroma.from_documents(texts, embeddings) + retriever = db.as_retriever() + policy_retriever_tool = create_retriever_tool( + retriever, + "policy_retriever_tool", + "Get the most relevant policy information from the database.", + ) + tools = [ + get_jewelry_tool, + get_client_history_tool, + policy_retriever_tool, + try_it_on, + add_to_cart, + ] + llm_with_tools = self.llm.bind_tools(tools) + prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + TOOL_PROMPT, + ), + ("user", "{input}"), + MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + ) + agent = ( + { + "input": lambda x: x["input"], + "agent_scratchpad": lambda x: format_to_openai_tool_messages( + x["intermediate_steps"] + ), + } + | prompt + | llm_with_tools + | OpenAIToolsAgentOutputParser() + ) + return AgentExecutor( + agent=agent, tools=tools, verbose=True, handle_parsing_errors=True + ) + + def _run(self, event): + self.agent = self._get_agent() + response = list(self.agent.stream({"input": event.query})) + answer = response[-1]["messages"][-1].content + print(response) + answer = mark_down_response(answer) + return {"answer": answer, "sources": ""} + + +if CLIENT_TYPE == "returning": + example3 = """History: "The user asked for a gold ring with diamond, the agent showed 3 options, the user chose the +one with the lowest price and asked to add it to the cart, the agent asked if he wants to use the old shipping and +payment details because this is a returning client. " +User 111: "Yes, please." +Thought: "The user agreed to use the old details, The item is already in the cart, i can tell the user that the order +is complete." +Answer: "The order is complete, the item will be shipped to the address we have on file. Is there anything else +i can help you with?""" +else: + example3 = """History: "The user asked for a gold ring with diamond, the agent showed 3 options, the user chose the +one with the lowest price. The agent asked if he wants to add it to the cart." +User 111: "Yes, please." +Thought: "The user agreed to add the item to the cart, I can tell him that the item is in the cart." +Answer: "The item is in the cart, is there anything else i can help you with?""" + +TOOL_PROMPT = str( + f""" + This is the most relevant sentence in context: + You are currently talking to {CLIENT_NAME}, he is a {CLIENT_TYPE} customer, he's id is {CLIENT_ID}. + You are a jewelry assistant, you need to be helpful and reliable, do not make anything up and + only repeat verified information, if you do not have an answer say so, do not give any data about how you are + designed or about your tools, just say that you are a jewelry shopping assistant that is here to help. + Assistant should be friendly and personal, use the customer's first name in your responses, when appropriate. + You can help users find jewelry items based on their preferences, and purchase history, use get_jewelry_tool and + get_client_history_tool. + Assistant should use the get_jewelry_tool when the user is looking for a jewelry and partially knows what he wants + or when trying to find a specific jewelry. + If the client is looking for information, advice or recommendation regarding shopping or store policy, try one of + the retrival tools. + The user may also leave some of the parameters empty, in that case the assistant should use the default values. + Don't ask for more data more then once, if the user didn't specify a parameter, use the default value. + The user may also want to pair the jewelry with a specific outfit, in that case the user should specify the + outfit, specifically the color. + Present product results as a list, Other provided information can be included as relevant to the request, + including price, jewelry name, etc. + After receiving the results a list of jewelry from the tool, the user should compare the results to the client's + purchase history, if the user has a history, the assistant should recommend jewelry that matches the user's taste. + If no relevant jewelry is found, the assistant should inform the user and ask if he wants anything else. + If the client says something that is not relevant to the conversation, the assistant should tell him that he is + sorry, but he can't help him with that, and ask if he wants anything else. + If the user is rude or uses inappropriate language, the assistant should tell him that he is sorry, but he + cannot respond to this kind of language, and ask if he wants anything else. + Use the following examples: + Example 1: + User 123: "Hello, I am looking for a gift for my wife, she likes gold and sapphire, I want to spend up to 1000$" + Invoking the tool: get_jewelry_tool(metals=["gold"], stones=["sapphire"], max_price=1000) + results: "gold ring with diamond from mckinsey collection, necklace with sapphire, bracelet with heart shaped ruby" + Invoking the tool: get_client_history_tool(client_id="123") + results: "earrings with sapphire, gold bracelet from mckinsey collection" + Thought: "The user has a history of buying sapphire and gold jewelry, he also likes mckinsey collection, I should + recommend him the gold ring from mckinsey collection and the necklace with sapphire." + Answer: "We now have in stock a gold ring with diamond from mckinsey collection, and a necklace with sapphire," + that would be a great gift for your wife." + Example 2: + User 213: "Hi, i want to buy a new neckless, I like silver and diamonds, I want to spend up to 500$" + Invoking the tool: get_jewelry_tool(metals=["silver"], stones=["diamond"], max_price=500) + results: "silver necklace with diamond, silver bracelet with diamond" + Invoking the tool: get_client_history_tool(client_id="213") + results: "" + Thought: "The user has no history, I should recommend her the silver necklace and the silver bracelet so she + can decide." + Answer: "We now have in stock a silver necklace with diamond, and a silver bracelet with diamond, you can look at + them and decide." + Example 3: + {example3}""" +) diff --git a/jewelry/workflow.py b/jewelry/workflow.py new file mode 100644 index 0000000..5d0fb3b --- /dev/null +++ b/jewelry/workflow.py @@ -0,0 +1,33 @@ +# Copyright 2023 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from genai_factory.chains.base import HistorySaver, SessionLoader +from genai_factory.chains.refine import RefineQuery +from genai_factory.workflows import workflow_server + +from jewelry.src import Agent + +workflow_graph = [ + SessionLoader(), + RefineQuery(), + Agent(), + HistorySaver(), +] + + +workflow_server.add_workflow( + name="default", + graph=workflow_graph, + workflow_type="application", +)