File size: 18,322 Bytes
23f1cab
5843ccb
 
 
 
e6e9663
5843ccb
3f8b540
 
a00ca40
23f1cab
 
5843ccb
23f1cab
 
 
3f8b540
 
 
39370a3
5843ccb
8b56583
3f8b540
39370a3
 
 
 
 
 
7351225
6dba5aa
46b0f24
a00ca40
2f102ab
807cb08
 
 
 
e0f119f
8b56583
9a5cb45
3f8b540
39370a3
eb4c258
 
39370a3
cbe212d
 
6dba5aa
 
b8c4bf7
eb4c258
 
 
 
 
 
39370a3
52e9be7
eb4c258
52e9be7
913593f
 
 
 
 
 
 
 
 
 
 
 
99590e3
6df4daa
e0f119f
 
 
 
 
 
 
bd759b7
e0f119f
ef63667
e0f119f
db8d990
fa0de40
52e9be7
8b56583
913593f
 
 
 
 
 
 
 
 
 
 
 
fa0de40
2f102ab
8b56583
52e9be7
39370a3
 
 
 
 
 
 
52e9be7
 
39370a3
52e9be7
 
 
e51b856
 
 
f73a224
6b87288
e51b856
40859fe
e51b856
 
6b87288
2f102ab
00dd968
6b87288
e51b856
 
 
 
 
 
3a715d2
00dd968
e51b856
 
 
 
 
6b87288
e51b856
 
6b87288
e51b856
 
 
6b87288
 
3a715d2
52e9be7
8b56583
6dba5aa
39370a3
8b56583
39370a3
6dba5aa
39370a3
c5911ea
f73a224
39370a3
 
6dba5aa
b7fc59e
6cdc714
39370a3
938cbdf
39370a3
6cdc714
39370a3
 
6cdc714
8b56583
6dba5aa
52e9be7
39370a3
 
6dba5aa
46b0f24
6dba5aa
4c54685
8b56583
4c54685
 
 
6cdc714
4c54685
52e9be7
d1a5593
2aea070
9a5cb45
9e62a69
 
 
 
 
 
9a5cb45
2aea070
9a5cb45
 
 
9e62a69
9a5cb45
 
cb20bb6
2aea070
9a5cb45
35d927a
 
2aea070
 
9a5cb45
 
 
 
 
cb20bb6
 
2aea070
9a5cb45
 
35d927a
9a5cb45
2aea070
034075e
b454840
984fb6f
3a715d2
44189e1
403d081
3922857
 
1d0bf87
3922857
8b3a3d5
13ed972
9a5cb45
13ed972
4c54685
b8c4bf7
6b87288
9a5cb45
54756f4
938cbdf
4c54685
6cdc714
4c54685
6cdc714
4c54685
07956b9
 
23f1cab
 
 
 
 
 
 
 
 
 
 
 
07956b9
23f1cab
52e9be7
07956b9
23f1cab
 
 
 
f24fc2c
23f1cab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39370a3
0fe5a37
6dba5aa
39370a3
 
 
8b58e08
0fe5a37
4c54685
8b58e08
4c54685
8b58e08
 
 
 
 
 
4c54685
 
8b58e08
39370a3
4c54685
 
 
8b58e08
4c54685
e51b856
8b58e08
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebae6f7
8b58e08
 
 
 
80ef227
8b58e08
 
 
d1a5593
8b58e08
 
4c54685
39370a3
 
8b56583
e56c61d
39370a3
 
 
 
 
 
 
 
8b56583
e56c61d
39370a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8b58e08
 
 
39370a3
8b58e08
 
 
4c54685
39370a3
23f1cab
8b58e08
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# Standard libraries
import os
import re
import time
import json
import asyncio
import requests
import numpy as np
import pandas as pd
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import lru_cache
from typing import Optional, List

# External libraries
import gradio as gr
from sqlalchemy import create_engine, inspect, text
import psycopg2
from groq import Groq
from dotenv import load_dotenv

#-----------------------

# Fetch variables
user = os.getenv("user")
password = os.getenv("password")
host = os.getenv("host")
port = os.getenv("port")
dbname = os.getenv("dbname")
GROQ_API_KEY = os.getenv("GROQ_API_KEY_PAID")
GROQ_API_KEY_PAID = os.getenv("GROQ_API_KEY_PAID")

# Define path for cache file in the current directory
CACHE_FILE = Path("/tmp/sql_prompt_cache1.json")
#-----------------------

# create db connection
engine = create_engine(f"postgresql+psycopg2://{user}:{password}@{host}:{port}/{dbname}")
# print(engine)

# -----------------------

# Groq API settings
GROQ_EMBED_URL = "https://api.groq.com/openai/v1/embeddings"
GROQ_CHAT_URL = "https://api.groq.com/openai/v1/chat/completions"
EMBEDDING_MODEL = "llama3-405b-8192-embed"
# LLM_MODEL = "llama-3.3-70b-versatile"
LLM_MODEL = "llama3-70b-8192"
cMODEL = "llama-3.1-8b-instant"
MODEL = "llama-3.3-70b-versatile"

#-----------------------

# Configure headers for Groq API requests
GROQ_HEADERS = {
    "Authorization": f"Bearer {GROQ_API_KEY}",
    "Content-Type": "application/json"}


#-----------------------

# # version 1 (tables)
# def get_db_schema(engine):
#     inspector = inspect(engine)
#     schema_dict = {}
#     for table in inspector.get_table_names():
#         columns = inspector.get_columns(table)
#         column_names = [col['name'] for col in columns]
#         schema_dict[table] = column_names
#     return schema_dict

# schema = get_db_schema(engine)
schema = {
         'rag_color_conversion_chart': ['brand_name','brand_color_code','brand_color','candle_shade_code','matching_type'],
        
         'rag_order_detail': ['orderno','customername', 'phone', 'email', 'orderdate', 'producttype', 'colorcode', 'orderedqty',
                              'status','tracking_number','shippingaddress', 'shipping_type', 'estimateddeliverydate', 
                              'delivery_date', 'total_amount'],
     
         'rag_promo_code_data': ['promocode','username','firstname','lastname','companyname','phone','issuancedate','expirydate','user_id'],

        
         'rag_color_conversion_charts_link': ['Conversion Chart Name', 'link'],
    
         'rag_certificate_link': ['Certificates Name', 'link']
        }
# print(schema)

# -----------------------

# # Bag of words from table
# def get_db_schema_words(engine):
#     inspector = inspect(engine)
#     words = []
#     for table in inspector.get_table_names():
#         words.extend(table.split("_"))  # Split table names into words
#         columns = inspector.get_columns(table)
#         for col in columns:
#             words.extend(col['name'].split("_"))  # Split column names into words
#     return list(set(words))  # Remove duplicates, if needed

# bag_of_words = get_db_schema_words(engine)
# bag_of_words =  schema
bag_of_words = schema['rag_color_conversion_chart'],schema['rag_order_detail'],schema['rag_promo_code_data'],schema['rag_color_conversion_charts_link'],schema['rag_certificate_link']

# -----------------------

# Query formatter
def clean_sql_output(raw_content: str) -> str:
    # Remove any markdown formatting and explanations
    raw_content = raw_content.replace("```sql", "").replace("```", "").strip()

    # Extract only the first valid SQL query using regex
    queries = re.findall(r"(SELECT\s+.+?;)", raw_content,
                         flags=re.IGNORECASE | re.DOTALL)
    return queries[0].strip() if queries else "-- Unable to extract valid SQL"

# -----------------------

def query_groq(
    user_prompt: str,
    schema: Optional[str] = None,
    system_prompt: str = "You are a PostgreSQL expert. Generate the best possible SQL queries for FILTERING based on the user's question. **FILTERING** must be done using **IN** or **OR**. Also If user asks for colors and matches and provides color codes, query for **brand_code** or **brand_color_code** by default. SQL queries must be design for **returning all attributes**. Return only the correct SQL query. Note: attributes datatypes are **text**.",

    model: str = LLM_MODEL,
    temperature: float = 0.3,
    # max_tokens: int = 8192
) -> str:
    
    full_usr_prompt = f"User Question: {user_prompt}\n\nRefer to these target keywords for SQL Queries:{bag_of_words}" if bag_of_words else user_prompt # user 
    full_sys_prompt = f"{system_prompt}\n\nRefer to this Schema for SQL Queries:{schema}" if schema else system_prompt # system 

    response = requests.post(
        GROQ_CHAT_URL,
        headers=GROQ_HEADERS,
        json={
            "model": model,
            "messages": [
                {"role": "system", "content": full_sys_prompt},
                {"role": "user", "content": full_usr_prompt}
            ],
            "temperature": temperature,
            # "max_tokens": max_tokens
        }
    )

    if response.status_code != 200:
        raise Exception(f"❌ Error querying Groq:\n{response.text}")

    # Clean output: remove triple backticks
    content = response.json()["choices"][0]["message"]["content"].strip()
    return clean_sql_output(content)



# -----------------------

client = Groq(api_key=GROQ_API_KEY)

def correct_spelling_with_groq(text, context):
    try:
        chat_completion = client.chat.completions.create(
            messages=[
                # {"role": "system", "content": f"You are a helpful assistant that uses this context of words: {context}, to correct spellings in user queries, keeping their intent intact. Also if user asks for thread, he is refering to to find candle color code match. If user asks for colors search for brand colors"},
                {"role": "system", "content": f"You are a helpful assistant that uses this context of words: {context}, to correct spellings in user queries, keeping their intent intact. Also if user asks for thread, he is refering to to find candle_shade_code match. Also If user asks for colors and matches and provides color codes, search for brand code or brand color code by default"},
                {"role": "user", "content": f"Correct the following query: {text}"}
            ],
            model=cMODEL,  # or the correct model name like llama-3.3-70b-versatile
            temperature=0.3,
            max_completion_tokens=256,
            top_p=1,
            stream=False,
        )
        return chat_completion.choices[0].message.content.strip()
    except Exception as e:
        print("Groq correction failed:", e)
        return text  # fallback to original if Groq fails


# -----------------------


# summarize = Groq(api_key=GROQ_API_KEY)

summarize = Groq(api_key=GROQ_API_KEY_PAID)

def summarize_with_groq(text, context):
    if not text.strip():
        return "No content provided to summarize."
    try:
        response = summarize.chat.completions.create(
            messages=[
                {"role": "system", "content": f"""You are a helpful assistant that organizes and numerically lists and sub-lists closely related important text and shows and handles all unique variations and handles variations duplicates from the user inputs and its **context : {context}** in a professional manner and Always presents the output in a clean, professional format..
                                                [specially When the input contains order-related information, follow this following **strict format** without changes or additions:
                                                ---
                                                Order: 2677
                                                Customer Name: Brenda Cole
                                                Customer Email: brendacole66@gmail.com
                                                Phone Number: 12316130502
                                                Order Date: 2024-01-02
                                                Shipping Address: Title: Default, Address: 2606 N Lakeshore Dr Suit/Apt # , Zip Code: 49431
                                                Status: Delivered
                                                Total Amount: 225.5
                                                ---
                                                A. 3-5 Days Shipping:
                                                   - Tracking Link: https://wwwapps.ups.com/WebTracking/track?track=yes&trackNums=1ZB1F8280339024054
                                                        a. Material: Polyester
                                                            1. Color Code: 7834 - Qty: 1, 
                                                            2. Color Code: 61060 - Qty: 5, 
                                                            3. Color Code: 60300 - Qty: 7, 
                                                            4. Color Code: 8717 - Qty: 4, 
                                                        ---   
                                                        b. Material: Trial Order
                                                            1. Color Code: 7834 - Qty: 1, 
                                                            2. Color Code: 6106 - Qty: 5,
                                          
                                                ---
                                                B. 10-15 Days Shipping:
                                                   - Tracking Link: Not Available
                                                        a. Material: Polyester
                                                            1. Color Code: 07834 - Qty: 1, 
                                                            2. Color Code: 61060 - Qty: 5, 
                                                            3. Color Code: 6030 - Qty: 7, 
                                                            4. Color Code: 8717 - Qty: 4, 
                                                        ---    
                                                        b. Material: Trial Order
                                                            1. Color Code: 07834 - Qty: 1, 
                                                            2. Color Code: 6106 - Qty: 5
                                                                     
                                                ---]
                                                
                                                **Important Guidelines:**  
                                                - Use **only** the information provided by the user — **do not** assume or add any details.
                                                - **Do not** omit or miss any information.
                                                - Use **Given format** for order related queries, and use normal formats for other queries. 
                                                - Enter all remaining items without stopping until complete.
                                                - **Do not** use tables.
                                                - **Do not** display processing information.
                                                - **Do not** display dictionary.
                                                - Always present the output in a **clean**, **professional** and **Customer friendly** format.
                                                """},
                # {"role": "user", "content": f"Please organize the following text and numerically lists and sub-lists closely related important text and show and handle all unique variations and handle duplicates variations professionally:\n\n{text} "}
                {"role": "user", "content": f"{text}"}

            ],
            model=MODEL,
            temperature=0.25,
            # max_completion_tokens=8192,
            top_p=1,
            stream=False,
        )
        return response.choices[0].message.content.strip()
    except Exception as e:
        print("Groq Summarization failed:", e)
        return text


# -----------------------


def load_cache():
    if os.path.exists(CACHE_FILE):
        with open(CACHE_FILE, "r") as f:
            return json.load(f)
    return {}

def save_cache(cache: dict):
    with open(CACHE_FILE, "w") as f:
        json.dump(cache, f, indent=2)
        

# -----------------------

def try_sql_parallel_with_disk_cache(
    prompt: str,
    engine,
    schema: Optional[str] = None,
    max_prompts: int = 6,
    max_retries: int = 2,
    verbose: bool = False
):
    SQL_CACHE = load_cache()

    def generate_variants(base_prompt: str, error: str, count: int) -> List[str]:
        return [
            f"{base_prompt}\n\nTry variation #{i+1}.\nPrevious error:\n{error}" if error else f"{base_prompt}\n\nVariation #{i+1}"
            for i in range(count)
        ]

    def try_sql(sql: str):
        try:
            df = pd.read_sql(sql, engine)
            return df if not df.empty else None
        except Exception:
            return None

    attempts = 0
    error_message = ""
    last_sql = ""

    # ✅ Check cache first
    if prompt in SQL_CACHE:
        if verbose:
            print("[Disk cache hit] Using cached SQL.")
        sql = SQL_CACHE[prompt]
        df = try_sql(sql)
        if df is not None:
            return {"success": True, "dataframe": df}

    while attempts < max_retries:
        attempts += 1
        prompt_variants = generate_variants(prompt, error_message, max_prompts)

        with ThreadPoolExecutor(max_workers=max_prompts) as executor:
            futures = {executor.submit(query_groq, p, schema=schema): p for p in prompt_variants}
            new_sqls = []
            for future in as_completed(futures):
                try:
                    new_sql = future.result()
                    new_sqls.append((futures[future], new_sql))
                except Exception:
                    continue

        for p, sql in new_sqls:
            last_sql = sql
            if not sql or not sql.lower().strip().startswith("select"):
                error_message = "-- Not a SELECT statement"
                continue

            df = try_sql(sql)
            if df is not None:
                # ✅ Cache only successful result
                SQL_CACHE[prompt] = sql
                save_cache(SQL_CACHE)
                return {"success": True, "dataframe": df}

            error_message = "-- Query failed or returned no rows"

    return {"success": False, "error": error_message}

    
# -----------------------


with gr.Blocks() as interface:
    gr.Markdown("<h1 style='text-align: center;'>🕯️ CANDLES A.I CHAT SUPPORT 🕯️</h1>")
    gr.Markdown("<h3 style='text-align: center;'>Please type your query below. For better results, include your full name or username, order no, brand color or candle color, user ID.</h3>")

    chat_history = gr.State([])

    # Chatbot full-width, larger, scrollable, and copyable
    chatbot = gr.Chatbot(label="Conversation", height=600, autoscroll=True, show_copy_button=True)

    # Input and submit row
    with gr.Row(equal_height=True):
        user_input = gr.Textbox(
            label="Your Question",
            placeholder="e.g., John Smith, 1800, show matching candles etc.",
            autofocus=True,
            scale=4
        )
        submit_btn = gr.Button("Submit", variant="primary", scale=1)

    # Undo and clear row
    with gr.Row():
        undo_btn = gr.Button("Undo Last")
        clear_btn = gr.Button("Clear")

    # Status feedback message
    status_msg = gr.Markdown("")

    # Main logic without SQL table
    def handle_submit(user_input, history):
        if not user_input.strip():
            return gr.update(), history, "⚠️ Please enter a valid query."
            

        # correct_user_input = correct_spelling_with_groq(user_input , f"{bag_of_words}")
        # history.append(("🧑 User", user_input))

        
        # sql_attempt = try_sql_parallel_with_disk_cache(user_input, engine, schema, max_prompts=7, max_retries=2)
        sql_attempt = try_sql_parallel_with_disk_cache(user_input, engine, schema)

        if sql_attempt["success"]:
            # summary = summarize_with_groq(f"{correct_spelling_with_groq}\n\n{sql_attempt['dataframe'].to_dict()}" , f"{sql_attempt['dataframe'].columns.to_list()}")
            summary = summarize_with_groq(f"{sql_attempt['dataframe'].to_dict()}" , f"{sql_attempt['dataframe'].columns.to_list()}")
            answer = f"✅ Query executed successfully.\n\n{summary}"
            status = "✅ Success"
        else:
            # summary = summarize_with_groq(f"User query: {user_input}", f"{correct_user_input}")
            correct_user_input = correct_spelling_with_groq(user_input , f"{bag_of_words}")
            answer = f"❌ Please try :\n\n{correct_user_input}"
            status = "❌ Error: Invalid query or no matching data."
        
        history.append(("🧑 User", user_input))
        history.append(("🤖 Assistant", answer))
        return "", history, status

    # Button bindings
    submit_btn.click(
        handle_submit,
        [user_input, chat_history],
        [user_input, chat_history, status_msg]
    ).then(
        lambda hist: hist,
        [chat_history],
        [chatbot]
    )

    user_input.submit(
        handle_submit,
        [user_input, chat_history],
        [user_input, chat_history, status_msg]
    ).then(
        lambda hist: hist,
        [chat_history],
        [chatbot]
    )

    undo_btn.click(
        lambda history: history[:-2] if len(history) >= 2 else [],
        [chat_history],
        [chat_history]
    ).then(
        lambda hist: hist,
        [chat_history],
        [chatbot]
    )

    clear_btn.click(
        lambda: [],
        None,
        [chat_history]
    ).then(
        lambda: ([], ""),
        None,
        [chatbot, status_msg]
    )

interface.launch(ssr_mode=False,share=True)


# -----------------------