tsrivallabh commited on
Commit
0efb4de
·
verified ·
1 Parent(s): 3d97114

Upload 12 files

Browse files
Files changed (12) hide show
  1. .env +1 -0
  2. .gitattributes +2 -35
  3. .gitignore +1 -0
  4. README.md +499 -20
  5. app.py +500 -0
  6. efficiency_log.txt +141 -0
  7. requirements.txt +0 -0
  8. restaurants.json +182 -0
  9. sticky.py +44 -0
  10. tools.py +318 -0
  11. ui_utils.py +150 -0
  12. var.py +142 -0
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ GROQ_API_KEY="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"
.gitattributes CHANGED
@@ -1,35 +1,2 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .env
README.md CHANGED
@@ -1,20 +1,499 @@
1
- ---
2
- title: Reservation Chatbot
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: LLM based restaurant reservation bot
12
- license: mit
13
- ---
14
-
15
- # Welcome to Streamlit!
16
-
17
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
18
-
19
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
20
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ # Restaurant Reservation Assistant – LLM + Streamlit App
4
+
5
+ ## 🚀 Overview
6
+
7
+ This is a conversational restaurant reservation assistant built using **LLMs (llama3-8b-8192)**, **Streamlit**, and **SQLite**. The assistant interacts with users in natural language to help them book tables, answer restaurant queries, and manage reservation workflows.
8
+
9
+ ---
10
+
11
+ ## ⚙️ Setup Instructions
12
+
13
+ ### 🛠️ Requirements
14
+
15
+ * `Python 3.8+`
16
+ * `chromadb==1.0.10`
17
+ * `openai==1.82.0`
18
+ * `pandas==2.2.3`
19
+ * `redis==6.1.0`
20
+ * `sentence-transformers==4.1.0`
21
+ * `streamlit==1.45.1`
22
+ * `transformers==4.52.3`
23
+
24
+
25
+
26
+
27
+ ---
28
+
29
+ ### 🧩 Installation
30
+
31
+ ```bash
32
+ git clone https://github.com/Sri-Vallabh/LLM-based-restaurant-reservation-chatbot.git
33
+ cd restaurant-reservation-assistant
34
+ pip install -r requirements.txt
35
+ ```
36
+
37
+ ### 🔑 API Key Setup
38
+
39
+ To use the LLM features, you'll need an API key from [Groq Console](https://console.groq.com/keys).
40
+ Create an account (if you haven’t already), generate your key, and set it as an environment variable:
41
+
42
+ ```bash
43
+ export GROQ_API_KEY=your_api_key_here
44
+ ```
45
+
46
+ Or, add it to a `.env` file and load it using your preferred method.
47
+
48
+ ### 🔗 Running the App
49
+
50
+ ```bash
51
+ streamlit run app.py
52
+ ```
53
+
54
+ Ensure the `restaurant_reservation.db` is in the `/db` folder inside the root directory. This contains all restaurant, table, slot, and reservation data.
55
+
56
+
57
+
58
+ ---
59
+
60
+ ## 🧠 Language Model & Retrieval Architecture
61
+
62
+ This project leverages **Meta’s LLaMA 3 8B** language model (`llama3-8b-8192`), accessed via **Groq’s OpenAI-compatible API**. Groq’s unique **LPU (Language Processing Unit)** hardware delivers ultra-fast inference speeds—up to 876 tokens per second for LLaMA 3 8B—enabling near-instantaneous responses even for complex, multi-step conversations. The model’s 8192-token context window allows it to efficiently process extended dialogues, rich database results, and prompt histories.
63
+
64
+ The system is built around a **Retrieval-Augmented Generation (RAG)** architecture. Here’s how it works:
65
+
66
+ - **Semantic Search & Retrieval:**
67
+ - **ChromaDB** is used as the vector database to store embeddings of restaurant, table, and slot data.
68
+ - User queries and database content are converted to embeddings using the **all-MiniLM-L6-v2** sentence transformer model from Hugging Face’s Sentence Transformers library.
69
+ - This enables the system to quickly find semantically similar information, such as restaurants matching a cuisine or tables with specific features.
70
+ - **Grounding in Real-Time Data:**
71
+ - The LLM is provided with both the results of semantic search and, when needed, structured data retrieved from the SQLite database.
72
+ - This ensures responses are always up-to-date and contextually accurate.
73
+ - **Modular, Multi-Step Processing:**
74
+ - The LLM is used in a modular fashion, with specialized prompts for each stage of the conversation:
75
+
76
+ ### **Key Processing Steps**
77
+
78
+ 1. **Intent Detection**
79
+ Determines the user’s goal—booking a table, checking availability, asking general questions, or handling edge cases and non-sequiturs.
80
+
81
+ 2. **Information Extraction**
82
+ Extracts structured fields (restaurant name, user name, contact info, party size, reservation time) from free-form user input.
83
+
84
+ 3. **Extracting information from database**
85
+ - **Semantic Search:** For natural language queries (e.g., “Italian restaurants with outdoor seating”), the LLM triggers a semantic search in ChromaDB, powered by `all-MiniLM-L6-v2` embeddings.
86
+ ### Cases where semantic search approach fails:
87
+ - **SQL Query Generation:** For precise data requests (e.g., “Show all tables available at 7pm”), the LLM generates SQL queries to fetch data from the SQLite database.
88
+
89
+ 4. **Result Interpretation**
90
+ Converts raw SQL or semantic search results into clear, conversational summaries for the user.
91
+
92
+ 5. **Multi-Turn Dialogue Management**
93
+ Maintains context across messages, using previous inputs and system memory to build coherent, helpful conversations with the user.
94
+
95
+ ### **Security & Control**
96
+
97
+ - **No Direct Booking by LLM:**
98
+ For security, the LLM is not permitted to perform direct database modifications. Final reservations are handled by a dedicated, safeguarded logic module.
99
+ - **Prompt Engineering:**
100
+ Each task (intent, extraction, query, summarization) is managed by a specialized prompt, improving reliability and modularity.
101
+
102
+ ---
103
+
104
+ ## **In summary:**
105
+ This architecture combines the speed and intelligence of LLaMA 3 via Groq with robust retrieval from ChromaDB (using `all-MiniLM-L6-v2` embeddings) and SQLite, ensuring fast, accurate, and context-aware responses for every user query.
106
+
107
+
108
+ ## 💬 Example User Journeys with Application Walkthrough
109
+
110
+
111
+ Below are screenshots showing the end-to-end flow of the restaurant reservation assistant in action:
112
+
113
+ ---
114
+
115
+ ### 🟢 Image 1: Landing Page
116
+ ![Opening](assets/landing.png)
117
+
118
+ The landing page welcomes users and prompts:
119
+ **"Ask something about restaurants or reservations..."**
120
+
121
+
122
+ This initiates a free-form, conversational interface for users to interact naturally.
123
+ Here, also names of restaurants, cuisines, special features can be seen, which also stays along with the conversation thread which is scrollable.
124
+
125
+ ---
126
+
127
+ ### 💬 Image 2: General Conversation
128
+ ![Step 1](assets/greet_general_convo.png)
129
+
130
+ The assistant engages in friendly conversation, understanding user intent like greetings, small talk, or queries about restaurants.
131
+
132
+ ---
133
+
134
+ ### 🔍 Image 3: Database Query + Interpretation
135
+ ![Step 2](assets/general_conv_info_through_chat.png)
136
+
137
+ The assistant first uses ChromaDB with semantic search to quickly retrieve relevant answers from a knowledge base. If no confident result is found, it dynamically generates an appropriate SQL SELECT query, executes it on the structured database, interprets the result, and returns a natural, conversational response.
138
+
139
+ ---
140
+
141
+ ### 🤝 Image 4 to 6: Information Gathering + Suggestions
142
+ ![Step 3](assets/name_entering.png)
143
+ ![Step 4](assets/all_resto.png)
144
+ ### Handling random text
145
+ ![Step 5](assets/rubbish.PNG)
146
+
147
+ Through ongoing conversation, the assistant extracts necessary reservation information:
148
+ - 🏨 Restaurant name
149
+ - 🙋 User name
150
+ - 📱 Contact
151
+ - 👥 Party size
152
+ - ⏰ Time
153
+
154
+ It continues to help the user by checking availability and making suggestions.
155
+
156
+ ---
157
+
158
+ ### ✅ Image 7: Ready for Booking Confirmation
159
+ ![Step 6](assets/ready_to_book.png)
160
+
161
+ Once all required information is gathered, the assistant summarizes the reservation details and asks for user confirmation with a simple **`book`** command.
162
+
163
+ ---
164
+
165
+ ### 🧾 Image 8: Booking Confirmation
166
+ ![Step 7](assets/booking_successful.png)
167
+
168
+ Booking is processed successfully!
169
+
170
+ - Restaurant: **Pasta Republic**
171
+ - Time: **20 on 2025-05-12**
172
+ - Party size: **15**
173
+ - Tables booked: **4** (4 tables are needed to accomodate 15 people as one table has 4 seating capacity)
174
+ - Reservation ID: **#10**
175
+ The system calculates the number of tables needed using `ceil(party_size / 4)`, verifies table availability, reserves the required slots, and finalizes the booking.
176
+
177
+ The assistant informs the user:
178
+
179
+
180
+ 👉 Please mention this Reservation ID at the restaurant reception when you arrive.
181
+
182
+ This flow demonstrates a complete, intelligent reservation assistant that uses natural language processing, database querying, and interactive UX logic to help users make hassle-free bookings.
183
+
184
+
185
+ ---
186
+ ## Some other results:
187
+
188
+
189
+ ![Some results](assets/some_results.png)
190
+ ---
191
+ ### If user enters random text:
192
+ ![Some results](assets/rubbish.png)
193
+
194
+
195
+
196
+
197
+ # Database explanation
198
+ * **`restaurant_reservation.db`**:
199
+
200
+ * This SQLite database contains the following tables:
201
+
202
+ * **`restaurants`**:
203
+
204
+ * Stores information about each restaurant, such as its **name**, **cuisine**, **location**, **seating capacity**, **rating**, **address**, **contact details**, **price range**, and **special features**.
205
+ * The **`id`** field serves as a unique identifier for each restaurant.
206
+ * **Important Note**: The **`id`** is used internally in the database and should not be exposed to the user.
207
+
208
+ * **`tables`**:
209
+
210
+ * Stores information about tables at each restaurant.
211
+ * Each table is associated with a **`restaurant_id`**, linking it to a specific restaurant.
212
+ * Each table has a **capacity** (default is 4), which indicates how many guests it can accommodate.
213
+ * The **`id`** field uniquely identifies each table and should not be shared with the user.
214
+
215
+ * **`slots`**:
216
+
217
+ * Stores information about the availability of each table.
218
+ * Each slot corresponds to a **1-hour** time block for a specific table on a particular day (e.g., a table might have slots available from **9AM to 9PM**).
219
+ * **`is_reserved`** indicates whether the slot is booked (**1**) or available (**0**).
220
+ * **`date`** is hardcoded to **2025-05-12**, and the **`hour`** field defines the start time for the reservation (ranging from **9** to **20**, representing 9AM to 8PM).
221
+ * The **`slot.id`** and **`table_id`** are used to uniquely identify the slots and link them to the relevant tables.
222
+
223
+ * **`reservations`**:
224
+
225
+ * Stores reservation details made by the user, including:
226
+
227
+ * **`restaurant_id`**: Links the reservation to a specific restaurant.
228
+ * **`user_name`**: The name of the user who made the reservation.
229
+ * **`contact`**: The contact details (e.g., phone number) of the user.
230
+ * **`date`**: Hardcoded to **2025-05-12**, representing the reservation date.
231
+ * **`time`**: The starting hour of the reservation, which matches a slot's **hour** field.
232
+ * **`party_size`**: The number of people for whom the reservation is made.
233
+ * The **`id`** is used to uniquely identify each reservation, but it is not exposed to the user.
234
+
235
+ * **`reservation_tables`**:
236
+
237
+ * A junction table that links reservations to tables.
238
+ * Contains:
239
+
240
+ * **`reservation_id`**: Links the reservation to the **`reservations`** table.
241
+ * **`table_id`**: Links the reservation to the relevant **`tables`**.
242
+ * This table helps associate a reservation with the actual tables that are booked for that reservation.
243
+
244
+
245
+ ## 📄 Prompt Engineering Strategy
246
+
247
+ ### ✨ Design Principles
248
+
249
+ 1. **🔁 Separation of Concerns**
250
+ For different purposes, I have engineered different prompts that are modular, making the assistant easier to debug, maintain, and enhance:
251
+
252
+ * **Intent classification** (`determine_intent.txt`)
253
+ * **Information extraction** (`store_user_info.txt`)
254
+ * **SQL query generation** (`schema_prompt.txt`)
255
+ * **SQL result interpretation** (`interpret_sql_result.txt`)
256
+ * **Natural reply generation** (`generate_reservation_conversation.txt`)
257
+
258
+ 2. **🧠 Context-Aware Memory Management**
259
+
260
+ * Maintains `chat_history`, `user_data`, and `last_reply` using Streamlit session state.
261
+ * Tracks conversation context across turns to avoid repetition, keep interactions natural, and gracefully handle incomplete data.
262
+
263
+ 3. **✅ Controlled Confirmation Flow**
264
+
265
+ * Prompts ensure that **only when all required fields (restaurant name, name, contact, party size, and time)** are filled, the assistant proceeds to ask for booking confirmation.
266
+ * Prevents accidental bookings and ensures user consent before writing to the database.
267
+
268
+ 4. **🛡️ Safe Query Execution**
269
+
270
+ * Only **SELECT statements** generated by the LLM are allowed to be executed directly.
271
+ * INSERT/UPDATE operations (like booking a reservation) are handled by a **separate, controlled module**, protecting the database from unintended writes or corruption.
272
+
273
+ 5. **📦 Iterative Prompt Optimization**
274
+
275
+ * Prompts have been fine-tuned through iterative experimentation and real conversation testing.
276
+ * Incorporated **few-shot examples** where relevant to guide the LLM.
277
+ * Prompts are designed to gracefully handle edge cases, e.g., when users give partial or ambiguous information.
278
+
279
+ 6. **📏 Robust Format Enforcement & Cleaning**
280
+
281
+ * JSON outputs (e.g., for `store_user_info`) include explicit instructions on quoting keys/values to prevent parsing issues.
282
+ * Pre/post-processing logic strips any unexpected or extra text surrounding JSON responses from the LLM.
283
+ * Regular expressions and cleaning checks are used to sanitize LLM responses before using them in downstream logic.
284
+
285
+ 7. **🌐 User-Centric Design**
286
+
287
+ * Prompts use natural, polite tone and context-aware replies, improving user trust and UX.
288
+ * Conversational flow shifts fluidly between transactional (booking) and informational (restaurant FAQs) based on detected intent, also handling **multiple-intent** cases.
289
+
290
+ ---
291
+
292
+
293
+ ### ⚠️ Error Handling & Edge Cases
294
+
295
+ This assistant is designed to offer a smooth and reliable user experience, even in unexpected scenarios. The following mechanisms are implemented to handle errors and edge cases effectively:
296
+
297
+ #### ✅ Error Handling
298
+
299
+ * **LLM Output Sanitization**:
300
+ When the LLM occasionally adds extra text before or after the expected response (e.g., in SQL queries), the output is parsed and cleaned using regex or string manipulation to extract only the required format. This ensures that unexpected formatting does not break the application.
301
+
302
+ * **Safe Execution with Try-Catch Blocks**:
303
+ All critical operations — especially SQL queries and bookings — are wrapped in `try-except` blocks. This prevents the UI from crashing and allows the assistant to gracefully inform the user about what went wrong.
304
+
305
+ * **Pre-Booking Availability Recheck**:
306
+ Just before finalizing a reservation, the system re-checks for table and slot availability. This is to prevent race conditions where multiple users might try to book the same slot at the same time — ensuring consistency and avoiding double bookings.
307
+
308
+ * **Preventive measures for malicious data injection/Database modification by prompt**:
309
+ The LLM does not directly execute SQL INSERT statements. Instead, it only interprets user intent, and can perform certain select queries to gather information. There is a dedicated backend module securely handles data injection for reservations , reducing the risk of malicious injection or malformed queries.
310
+
311
+ ---
312
+
313
+ #### 🔍 Edge Cases
314
+
315
+ * **Random or Nonsensical User Input**:
316
+ If a user inputs irrelevant or nonsensical text (e.g., "asdf123", emojis, or spam), the assistant classifies it as an invalid intent (tagged as `RUBBISH`) and politely asks the user to rephrase or clarify their request.
317
+
318
+ * **Partial Reservation Information**:
319
+ When users provide only some details (e.g., name but not time), the assistant remembers the known information and continues the conversation by asking only for the missing fields, without repeating previously collected data.
320
+
321
+ * **Privacy Protection**:
322
+ Users cannot ask about bookings made by others. The SQL data access layer enforces this by exposing only the current user’s booking context. There is no direct query access to personal or third-party reservation data.
323
+
324
+ * **Restaurant Not Found**:
325
+ If the user provides a restaurant name that does not exist in the database, the assistant notifies them and may offer to show a list of available restaurants.
326
+
327
+ * **Unavailable Timeslots**:
328
+ If the requested time has no available tables (due to existing reservations), the assistant explains this clearly and suggests choosing a different time.
329
+
330
+ ---
331
+
332
+ By handling these cases gracefully, the assistant ensures that users have a seamless experience even when unexpected situations arise.
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+ ## 🧭 Assumptions, Limitations & Enhancements
342
+
343
+ ### Assumptions:
344
+ * There is a hardcoded 4-person table capacity, so the system itself selects multiple tables that are available at that time.
345
+ * Reservation slots are fixed to **2025-05-12**, and all reservations are for this date.
346
+ ---
347
+ ### ⚠️ Limitations:
348
+
349
+ * The system currently supports reservations only for a fixed date (2025-05-12). This could be extended to multi-day support by adding appropriate entries to the database.
350
+ * Since the system relies on Large Language Models (LLMs), there's **no absolute guarantee of perfect behavior**—LLMs can occasionally misinterpret queries, miss context, or produce inaccurate outputs.
351
+ * **Table preferences cannot be specified** by the user. The system auto-assigns tables based on availability, so users cannot choose specific table locations (e.g., window-side, outdoor, etc.).
352
+ * Only **select queries** are executed directly by the LLM to ensure **data safety**. For insert/update operations (e.g., booking), a separate transaction module is used.
353
+
354
+
355
+ ---
356
+ ### Future Enhancements:
357
+
358
+ * Expand the system to allow for multi-day reservations.
359
+ * Also add table preferences to choose(eg. beside window,private space).
360
+ * Add features like user authentication, personalized recommendations, and more sophisticated handling of party sizes and table combinations.
361
+
362
+ ### 🔮 Future Enhancements in deployment
363
+
364
+ * ✅ Date picker and calendar integration
365
+ * 📲 SMS/WhatsApp confirmation with reservation ID
366
+ * 🧾 Admin dashboard to manage reservations & analytics
367
+ * 🌐 Multilingual support for non-English customers
368
+ * 🔌 API-first backend to support mobile and kiosk interfaces
369
+
370
+ ---
371
+
372
+
373
+
374
+ ## 📎 File Structure
375
+
376
+ ```
377
+ ├── app.py
378
+ ├── tools.py
379
+ ├── var.py
380
+ ├── requirements.txt
381
+ ├── Business Strategy Presentation/
382
+ │ ├── app.js
383
+ │ ├── index.html
384
+ │ ├── style.js
385
+ ├── prompts/
386
+ │ ├── determine_intent.txt
387
+ │ ├── generate_reservation_conversation.txt
388
+ │ ├── interpret_sql_result.txt
389
+ │ ├── schema_prompt.txt
390
+ │ └── store_user_info.txt
391
+ ├── db/
392
+ │ └── restaurant_reservation.db
393
+ └── README.md
394
+ ```
395
+
396
+ ### Explanation of Each File
397
+
398
+ #### 1. **`app.py`**
399
+
400
+ * The main application file that drives the restaurant reservation system.
401
+ * Handles user input, coordinates prompt usage, calls functional tools, executes SQL queries, and returns conversational responses.
402
+ * Acts as the central orchestrator between all components.
403
+
404
+ #### 2. **`tools.py`**
405
+
406
+ * Contains core utility functions used throughout the system.
407
+ * Includes logic for:
408
+
409
+ * Determining user intent
410
+ * Storing and updating user information
411
+ * Generating reservation-related conversations
412
+ * Creating and interpreting SQL queries
413
+ * Serves as the modular backend logic layer for reusable operations.
414
+
415
+ #### 3. **`var.py`**
416
+
417
+ * Defines classes and configuration variables.
418
+ * Includes:
419
+
420
+ * `SchemaVectorDB`: for handling schema-related semantic search
421
+ * `FullVectorDB`: for broader retrieval tasks using vector similarity
422
+ * Facilitates integration of ChromaDB and semantic retrieval workflows.
423
+
424
+ #### 4. **`prompts/` Folder**
425
+
426
+ Stores prompt templates that guide the behavior of the language model (LLM):
427
+
428
+ * **`determine_intent.txt`**:
429
+ Prompt for classifying user messages into intents like `greet`, `select`, `book`, or irrelevant.
430
+
431
+ * **`generate_reservation_conversation.txt`**:
432
+ Handles multi-turn interactions to collect user details and guide reservations.
433
+
434
+ * **`interpret_sql_result.txt`**:
435
+ Formats raw SQL query results into natural-sounding responses.
436
+
437
+ * **`schema_prompt.txt`**:
438
+ Describes the SQLite database schema and provides rules for query generation.
439
+
440
+ * **`store_user_info.txt`**:
441
+ Extracts and stores user details like name, contact info, party size, and reservation time.
442
+
443
+ #### 5. **`db/` Folder**
444
+
445
+ * Contains the SQLite database (`restaurant_reservation.db`) with all restaurant, table, and reservation information.
446
+ * Used to run SQL queries for booking and retrieving restaurant details.
447
+
448
+ ---
449
+
450
+
451
+
452
+ ## 📊 Vertical Expansion
453
+
454
+ This solution can be adapted for:
455
+
456
+ * ✈️ Airlines (seat booking assistants)
457
+ * 🏥 Clinics & Hospitals (appointment schedulers)
458
+ * 🎟️ Event Ticketing Systems (concerts, sports, etc.)
459
+ * 🏨 Hotels (room booking, amenities)
460
+
461
+ ---
462
+
463
+ ## 🥇 Competitive Advantages
464
+
465
+ 1. 🔁 Multi-turn conversation memory (session-state-based and intent based)
466
+ 2. 🧠 Contextual intent handling with seamless switching between FAQ and transactional flows
467
+ 3. 📦 Modular LLM prompt architecture for future scaling
468
+ 4. 🔒 Secure and Controlled SQL Access
469
+ Only read-only SQL (SELECT) statements are generated and executed via the LLM to prevent any risk of data corruption.
470
+ Reservation actions like INSERT or UPDATE are handled securely in a separate logic module, ensuring strict control over data modification.
471
+
472
+ ---
473
+
474
+ ## 📅 Implementation Timeline
475
+
476
+ | Phase | Description | Duration |
477
+ | ------- | -------------------------------------- | -------- |
478
+ | Phase 1 | Database creation+LLM sql query creation and interpretation | 1st day |
479
+ | Phase 2 | Intent detection+conversational flow | 1st day |
480
+ | Phase 3 | Booking and edge-case handling | 2nd day |
481
+ | Phase 4 | Presentation & packaging | 2nd day |
482
+
483
+ ---
484
+
485
+ ## 👥 Key Stakeholders
486
+
487
+ * Restaurant Manager / Owner
488
+ * Frontdesk / Host
489
+ * Customer Service Ops
490
+ * Technical Dev Team
491
+
492
+ ---
493
+
494
+
495
+
496
+
497
+
498
+
499
+
app.py ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from openai import OpenAI
3
+ import sqlite3
4
+ import pandas as pd
5
+ import re
6
+ import json
7
+ from sticky import sticky_container
8
+ import chromadb
9
+ from sentence_transformers import SentenceTransformer
10
+ from transformers import pipeline
11
+ import hashlib
12
+ import inspect
13
+ from tools import *
14
+ from var import SCHEMA_DESCRIPTIONS, SchemaVectorDB, FullVectorDB
15
+ import os
16
+ from dotenv import load_dotenv
17
+ load_dotenv()
18
+
19
+ # Set your Groq API key
20
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
21
+
22
+ # Initialize Groq's OpenAI-compatible client
23
+ client = OpenAI(
24
+ api_key=GROQ_API_KEY,
25
+ base_url="https://api.groq.com/openai/v1"
26
+ )
27
+
28
+ # --- Load prompt templates from prompts folder ---
29
+ with open("prompts/determine_intent.txt", "r", encoding="utf-8") as f:
30
+ determine_intent_prompt = f.read()
31
+
32
+ with open("prompts/generate_reservation_conversation.txt", "r", encoding="utf-8") as f:
33
+ generate_reservation_conversation_prompt = f.read()
34
+
35
+ with open("prompts/interpret_sql_result.txt", "r", encoding="utf-8") as f:
36
+ interpret_sql_result_prompt = f.read()
37
+
38
+ with open("prompts/schema_prompt.txt", "r", encoding="utf-8") as f:
39
+ schema_prompt = f.read()
40
+
41
+ with open("prompts/store_user_info.txt", "r", encoding="utf-8") as f:
42
+ store_user_info_prompt = f.read()
43
+
44
+
45
+
46
+ st.set_page_config(page_title="FoodieSpot Assistant", layout="wide")
47
+
48
+
49
+ # --- Initialize State ---
50
+ if 'chat_history' not in st.session_state:
51
+ st.session_state.chat_history = []
52
+
53
+ if 'user_data' not in st.session_state:
54
+ st.session_state.user_data = {
55
+ "restaurant_name": None,
56
+ "user_name": None,
57
+ "contact": None,
58
+ "party_size": None,
59
+ "time": None
60
+ }
61
+ if 'vector_db' not in st.session_state:
62
+ st.session_state.vector_db = SchemaVectorDB()
63
+ vector_db = st.session_state.vector_db
64
+ if 'full_vector_db' not in st.session_state:
65
+ st.session_state.full_vector_db = FullVectorDB()
66
+ # Track last assistant reply for context
67
+ if 'last_assistant_reply' not in st.session_state:
68
+ st.session_state.last_assistant_reply = ""
69
+ # Fixed container at top for title + reservation
70
+ reservation_box = sticky_container(mode="top", border=False,z=999)
71
+
72
+ with reservation_box:
73
+ st.text("")
74
+ st.text("")
75
+ st.title("🍽️ FoodieSpot Assistant")
76
+ cols = st.columns([3, 3, 3, 2, 2, 1])
77
+
78
+ with cols[0]:
79
+ restaurant_name = st.text_input(
80
+ "Restaurant Name",
81
+ value=st.session_state.user_data.get("restaurant_name") or "",
82
+ key="restaurant_name_input"
83
+ )
84
+ if restaurant_name!="":
85
+ st.session_state.user_data["restaurant_name"] = restaurant_name
86
+
87
+ with cols[1]:
88
+ user_name = st.text_input(
89
+ "Your Name",
90
+ value=st.session_state.user_data.get("user_name") or "",
91
+ key="user_name_input"
92
+ )
93
+ if user_name!="":
94
+ st.session_state.user_data["user_name"] = user_name
95
+
96
+ with cols[2]:
97
+ contact = st.text_input(
98
+ "Contact",
99
+ value=st.session_state.user_data.get("contact") or "",
100
+ key="contact_input"
101
+ )
102
+ if contact!="":
103
+ st.session_state.user_data["contact"] = contact
104
+
105
+ with cols[3]:
106
+ party_size = st.number_input(
107
+ "Party Size",
108
+ value=st.session_state.user_data.get("party_size") or 0,
109
+ key="party_size_input"
110
+ )
111
+ if party_size!=0:
112
+ st.session_state.user_data["party_size"] = party_size
113
+
114
+ with cols[4]:
115
+ time = st.number_input(
116
+ "Time(24hr form, 9-20, 8 ~ null)",
117
+ min_value=8,
118
+ max_value=20,
119
+ value=st.session_state.user_data.get("time") or 8,
120
+ key="time_input"
121
+ )
122
+ if time!=8:
123
+ st.session_state.user_data["time"] = time
124
+ # Place the BOOK button in the last column
125
+ with cols[5]:
126
+ st.text("")
127
+ st.text("")
128
+ book_clicked = st.button("BOOK", type="primary")
129
+ # Add a green BOOK button (primary style)
130
+ # book_clicked = st.button("BOOK", type="primary")
131
+
132
+ if book_clicked:
133
+ # Check if all required fields are filled
134
+ required_keys = ["restaurant_name", "user_name", "contact", "party_size", "time"]
135
+ if all(st.session_state.user_data.get(k) not in [None, "", 0, 8] for k in required_keys):
136
+ booking_conn = None
137
+ try:
138
+ user_data = st.session_state.user_data
139
+ party_size = int(user_data["party_size"])
140
+ tables_needed = -(-party_size // 4)
141
+
142
+ booking_conn = sqlite3.connect("db/restaurant_reservation.db")
143
+ booking_cursor = booking_conn.cursor()
144
+
145
+ booking_cursor.execute("SELECT id FROM restaurants WHERE LOWER(name) = LOWER(?)", (user_data["restaurant_name"],))
146
+ restaurant_row = booking_cursor.fetchone()
147
+ if not restaurant_row:
148
+ raise Exception("Restaurant not found.")
149
+ restaurant_id = restaurant_row[0]
150
+
151
+ booking_cursor.execute("""
152
+ SELECT t.id AS table_id, s.id AS slot_id
153
+ FROM tables t
154
+ JOIN slots s ON t.id = s.table_id
155
+ WHERE t.restaurant_id = ?
156
+ AND s.hour = ?
157
+ AND s.date = '2025-05-12'
158
+ AND s.is_reserved = 0
159
+ LIMIT ?
160
+ """, (restaurant_id, user_data["time"], tables_needed))
161
+ available = booking_cursor.fetchall()
162
+
163
+ if len(available) < tables_needed:
164
+ raise Exception("Not enough available tables.")
165
+
166
+ booking_cursor.execute("""
167
+ INSERT INTO reservations (restaurant_id, user_name, contact, date, time, party_size)
168
+ VALUES (?, ?, ?, '2025-05-12', ?, ?)
169
+ """, (restaurant_id, user_data["user_name"], user_data["contact"], user_data["time"], party_size))
170
+ reservation_id = booking_cursor.lastrowid
171
+
172
+ for table_id, _ in available:
173
+ booking_cursor.execute("INSERT INTO reservation_tables (reservation_id, table_id) VALUES (?, ?)", (reservation_id, table_id))
174
+
175
+ slot_ids = [slot_id for _, slot_id in available]
176
+ booking_cursor.executemany("UPDATE slots SET is_reserved = 1 WHERE id = ?", [(sid,) for sid in slot_ids])
177
+
178
+ booking_conn.commit()
179
+
180
+ booking_cursor.execute("SELECT name FROM restaurants WHERE id = ?", (restaurant_id,))
181
+ restaurant_name = booking_cursor.fetchone()[0]
182
+
183
+ confirmation_msg = (
184
+ f"✅ Booking processed successfully!\n\n"
185
+ f"📍 Restaurant: **{restaurant_name}**\n"
186
+ f"⏰ Time: **{user_data['time']} on 2025-05-12**\n"
187
+ f"🍽️ Tables Booked: **{tables_needed}**\n"
188
+ f"🆔 Reservation ID: **{reservation_id}**\n\n"
189
+ f"👉 Please mention this Reservation ID at the restaurant reception when you arrive."
190
+ )
191
+
192
+ st.success(confirmation_msg)
193
+ st.session_state.chat_history.append({'role': 'assistant', 'message': confirmation_msg})
194
+ st.session_state.user_data["restaurant_name"] = None
195
+ st.session_state.user_data["party_size"] = None
196
+ st.session_state.user_data["time"] = None
197
+ st.session_state.last_assistant_reply = ""
198
+ except Exception as e:
199
+ if booking_conn:
200
+ booking_conn.rollback()
201
+ st.error(f"❌ Booking failed: {e}")
202
+ finally:
203
+ if booking_conn:
204
+ booking_cursor = None
205
+ booking_conn.close()
206
+ else:
207
+ st.warning("⚠️ Missing user information. Please provide all booking details first.")
208
+ st.text("")
209
+ # Inject custom CSS for smaller font and tighter layout
210
+ st.markdown("""
211
+ <style>
212
+ .element-container:has(.streamlit-expander) {
213
+ margin-bottom: 0.5rem;
214
+ }
215
+ .streamlit-expanderHeader {
216
+ font-size: 0.9rem;
217
+ }
218
+ .streamlit-expanderContent {
219
+ font-size: 0.85rem;
220
+ padding: 0.5rem 1rem;
221
+ }
222
+ </style>
223
+ """, unsafe_allow_html=True)
224
+
225
+ with st.container():
226
+ col1, col2, col3 = st.columns(3)
227
+
228
+ with col1:
229
+ with st.expander("🍽️ Restaurants"):
230
+ st.markdown("""
231
+ - Bella Italia
232
+ - Spice Symphony
233
+ - Tokyo Ramen House
234
+ - Saffron Grill
235
+ - El Toro Loco
236
+ - Noodle Bar
237
+ - Le Petit Bistro
238
+ - Tandoori Nights
239
+ - Green Leaf Cafe
240
+ - Ocean Pearl
241
+ - Mama Mia Pizza
242
+ - The Dumpling Den
243
+ - Bangkok Express
244
+ - Curry Kingdom
245
+ - The Garden Table
246
+ - Skyline Dine
247
+ - Pasta Republic
248
+ - Street Tacos Co
249
+ - Miso Hungry
250
+ - Chez Marie
251
+ """)
252
+
253
+ with col2:
254
+ with st.expander("🌎 Cuisines"):
255
+ st.markdown("""
256
+ - Italian
257
+ - French
258
+ - Chinese
259
+ - Japanese
260
+ - Indian
261
+ - Mexican
262
+ - Thai
263
+ - Healthy
264
+ - Fusion
265
+ """)
266
+
267
+ with col3:
268
+ with st.expander("✨ Special Features"):
269
+ st.markdown("""
270
+ - Pet-Friendly
271
+ - Live Music
272
+ - Rooftop View
273
+ - Outdoor Seating
274
+ - Private Dining
275
+ """)
276
+
277
+
278
+
279
+
280
+ # --- Display previous chat history (before new input) ---
281
+
282
+ for msg in st.session_state.chat_history:
283
+ # Check if both 'role' and 'message' are not None
284
+ if msg['role'] is not None and msg['message'] is not None:
285
+ with st.chat_message(msg['role']):
286
+ st.markdown(msg['message'])
287
+
288
+ user_input = st.chat_input("Ask something about restaurants or reservations(eg. Tell me some best rated Italian cuisine restaurants)...")
289
+ if user_input:
290
+ # Show user message instantly
291
+ with st.chat_message("user"):
292
+ st.markdown(user_input)
293
+ st.session_state.chat_history.append({'role': 'user', 'message': user_input})
294
+
295
+ # Prepare conversation context
296
+ history_prompt = st.session_state.last_assistant_reply
297
+
298
+ # Store possible user info
299
+ user_info = store_user_info(user_input,history_prompt,store_user_info_prompt,client)
300
+ if user_info:
301
+ st.session_state.user_data.update(user_info)
302
+ # st.rerun()
303
+
304
+ # Detect intent
305
+ intent = determine_intent(user_input,determine_intent_prompt,client)
306
+ # st.write(intent)
307
+ if intent == "RUBBISH":
308
+ # Display user data for confirmation instead of invoking LLM
309
+ with st.chat_message("assistant"):
310
+ st.markdown("❌ Sorry, I didn't understand that. Could you rephrase your request?")
311
+ st.session_state.chat_history.append({
312
+ 'role': 'assistant',
313
+ 'message': "❌ Sorry, I didn't understand that. Could you rephrase your request?"
314
+ })
315
+
316
+ st.stop()
317
+
318
+ # Generate assistant reply
319
+ required_keys = ["restaurant_name", "user_name", "contact", "party_size", "time"]
320
+ user_data_complete = all(
321
+ k in st.session_state.user_data and st.session_state.user_data[k] not in [None, "", "NULL"]
322
+ for k in required_keys
323
+ )
324
+
325
+
326
+ if user_data_complete and intent != "BOOK":
327
+
328
+ # Format user data as a Markdown bullet list
329
+ user_details = "\n".join([f"- **{key.capitalize()}**: {value}" for key, value in st.session_state.user_data.items()])
330
+
331
+ with st.chat_message("assistant"):
332
+ st.markdown("✅ I have all the details needed for your reservation:")
333
+ st.markdown(user_details)
334
+ st.markdown("If everything looks good, please type **`book`** to confirm the reservation.")
335
+
336
+ st.session_state.chat_history.append({
337
+ 'role': 'assistant',
338
+ 'message': f"✅ I have all the details needed for your reservation:\n{user_details}\nPlease type **`book`** to confirm."
339
+ })
340
+ st.session_state.last_assistant_reply = "I have all the reservation details. Waiting for confirmation..."
341
+ st.rerun()
342
+ st.stop()
343
+
344
+
345
+
346
+
347
+ response_summary = None
348
+
349
+ if intent == "SELECT":
350
+ response_summary=handle_query(user_input, st.session_state.full_vector_db, client)
351
+
352
+ # First try semantic search
353
+ semantic_results = {}
354
+
355
+ # Search across all collections
356
+ restaurant_results = st.session_state.full_vector_db.semantic_search(user_input, "restaurants")
357
+ table_results = st.session_state.full_vector_db.semantic_search(user_input, "tables")
358
+ slot_results = st.session_state.full_vector_db.semantic_search(user_input, "slots")
359
+
360
+ if not is_large_output_request(user_input) and any([restaurant_results, table_results, slot_results]):
361
+ semantic_results = {
362
+ "restaurants": restaurant_results,
363
+ "tables": table_results,
364
+ "slots": slot_results
365
+ }
366
+ # Format semantic results
367
+ summary = []
368
+ for category, items in semantic_results.items():
369
+ if items:
370
+ summary.append(f"Found {len(items)} relevant {category}:")
371
+ summary.extend([f"- {item['name']}" if 'name' in item else f"- {item}"
372
+ for item in items[:3]])
373
+ st.write("### Semantic Search used")
374
+ response_summary = "\n".join(summary)
375
+ else:
376
+ # Fall back to SQL generation for large or exact output requests
377
+ sql = generate_sql_query_v2(user_input,SCHEMA_DESCRIPTIONS, history_prompt, vector_db, client)
378
+ result = execute_query(sql)
379
+ response_summary = interpret_result_v2(result, user_input, sql)
380
+
381
+
382
+
383
+ # sql = generate_sql_query_v2(user_input,history_prompt, vector_db, client)
384
+ # result = execute_query(sql)
385
+ # response_summary=interpret_result_v2(result, user_input, sql)
386
+ # if isinstance(result, pd.DataFrame):
387
+ # response_summary = interpret_sql_result(user_input, sql_query, result)
388
+
389
+
390
+ elif intent == "BOOK":
391
+ required_keys = ["restaurant_name", "user_name", "contact", "party_size", "time"]
392
+ if all(st.session_state.user_data.get(k) is not None for k in required_keys):
393
+ booking_conn = None
394
+ try:
395
+ user_data = st.session_state.user_data
396
+ party_size = int(user_data["party_size"])
397
+ tables_needed = -(-party_size // 4)
398
+
399
+ booking_conn = sqlite3.connect("db/restaurant_reservation.db")
400
+ booking_cursor = booking_conn.cursor()
401
+
402
+ booking_cursor.execute("SELECT id FROM restaurants WHERE LOWER(name) = LOWER(?)", (user_data["restaurant_name"],))
403
+ restaurant_row = booking_cursor.fetchone()
404
+ if not restaurant_row:
405
+ raise Exception("Restaurant not found.")
406
+ restaurant_id = restaurant_row[0]
407
+
408
+ booking_cursor.execute("""
409
+ SELECT t.id AS table_id, s.id AS slot_id
410
+ FROM tables t
411
+ JOIN slots s ON t.id = s.table_id
412
+ WHERE t.restaurant_id = ?
413
+ AND s.hour = ?
414
+ AND s.date = '2025-05-12'
415
+ AND s.is_reserved = 0
416
+ LIMIT ?
417
+ """, (restaurant_id, user_data["time"], tables_needed))
418
+ available = booking_cursor.fetchall()
419
+ # Debugging output
420
+
421
+ if len(available) < tables_needed:
422
+ raise Exception("Not enough available tables.")
423
+
424
+ booking_cursor.execute("""
425
+ INSERT INTO reservations (restaurant_id, user_name, contact, date, time, party_size)
426
+ VALUES (?, ?, ?, '2025-05-12', ?, ?)
427
+ """, (restaurant_id, user_data["user_name"], user_data["contact"], user_data["time"], party_size))
428
+ reservation_id = booking_cursor.lastrowid
429
+
430
+ for table_id, _ in available:
431
+ booking_cursor.execute("INSERT INTO reservation_tables (reservation_id, table_id) VALUES (?, ?)", (reservation_id, table_id))
432
+
433
+ slot_ids = [slot_id for _, slot_id in available]
434
+ booking_cursor.executemany("UPDATE slots SET is_reserved = 1 WHERE id = ?", [(sid,) for sid in slot_ids])
435
+
436
+ booking_conn.commit()
437
+ # Fetch the restaurant name to confirm
438
+ booking_cursor.execute("SELECT name FROM restaurants WHERE id = ?", (restaurant_id,))
439
+ restaurant_name = booking_cursor.fetchone()[0]
440
+
441
+ # Prepare confirmation details
442
+ confirmation_msg = (
443
+ f"✅ Booking processed successfully!\n\n"
444
+ f"📍 Restaurant: **{restaurant_name}**\n"
445
+ f"⏰ Time: **{user_data['time']} on 2025-05-12**\n"
446
+ f"🍽️ Tables Booked: **{tables_needed}**\n"
447
+ f"🆔 Reservation ID: **{reservation_id}**\n\n"
448
+ f"👉 Please mention this Reservation ID at the restaurant reception when you arrive."
449
+ )
450
+
451
+ response_summary = confirmation_msg
452
+ st.success(response_summary)
453
+ st.session_state.chat_history.append({'role': 'assistant', 'message': response_summary})
454
+ response_summary="✅ Booking processed successfully."
455
+ st.session_state.user_data["restaurant_name"]=None
456
+ st.session_state.user_data["party_size"]=None
457
+ st.session_state.user_data["time"]=None
458
+ st.session_state.last_assistant_reply=""
459
+ except Exception as e:
460
+ if booking_conn:
461
+ booking_conn.rollback()
462
+ response_summary = f"❌ Booking failed: {e}"
463
+ st.error(response_summary)
464
+ finally:
465
+ if booking_conn:
466
+ booking_cursor=None
467
+ booking_conn.close()
468
+ else:
469
+ st.markdown("⚠️ Missing user information. Please provide all booking details first.")
470
+ response_summary = "⚠️ Missing user information. Please provide all booking details first."
471
+
472
+
473
+ elif intent == "GREET":
474
+ response_summary = "👋 Hello! How can I help you with your restaurant reservation today?"
475
+
476
+ elif intent == "RUBBISH":
477
+ response_summary = "❌ Sorry, I didn't understand that. Could you rephrase your request?"
478
+
479
+ # Generate assistant reply
480
+ if response_summary!="✅ Booking processed successfully.":
481
+ follow_up = generate_reservation_conversation(
482
+ user_input,
483
+ history_prompt,
484
+ response_summary or "Info stored.",
485
+ json.dumps(st.session_state.user_data),generate_reservation_conversation_prompt,client
486
+ )
487
+ else:
488
+ follow_up="Thanks for booking with FoodieSpot restaurant chain, I could assist you in new booking, also I could tell about restaurant features, pricing, etc... "
489
+
490
+ # Show assistant reply instantly
491
+ with st.chat_message("assistant"):
492
+ st.markdown(follow_up)
493
+
494
+ st.session_state.chat_history.append({'role': 'assistant', 'message': follow_up})
495
+ # Update it after assistant speaks
496
+ st.session_state.last_assistant_reply = follow_up
497
+ st.rerun()
498
+ # Reset if booking done
499
+
500
+
efficiency_log.txt ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Function: store_user_info
2
+ Prompt tokens: 521
3
+ Completion tokens: 26
4
+ Total tokens: 547
5
+ Prompt: You are a helpful assistant. Extract relevant user information from this user statement:
6
+ "Hi"
7
+
8
+ Previously collected data in json: {"restaurant_name": null, "user_name": null, "contact": null, "party_size": null, "time": null}
9
+ Always remember this json data, you need to update this based on user statement.
10
+ if user statement is book, dont change any value in this data
11
+ Return a JSON object with the following possible keys:
12
+ - restaurant_name - text
13
+ - user_name - text
14
+ - contact - text
15
+ - party_size - integer
16
+ - time (between 9 to 20, 9 represents 9AM, 20 represents 8PM) - integer
17
+ Donot consider time which is before 9 or after 20.
18
+ Never modify any entry to null if previous data is not null for that field.
19
+ Update the previous data with any new fields found. Do not make previously known fields unless you are sure the user wants to change them.
20
+ Respond ONLY with a single valid JSON object.
21
+ important rules:
22
+ - "restaurant_name": Must always match from this list:
23
+ Bella Italia, Spice Symphony, Tokyo Ramen House, Saffron Grill, El Toro Loco, Noodle Bar, Le Petit Bistro, Tandoori Nights, Green Leaf Cafe, Ocean Pearl, Mama Mia Pizza, The Dumpling Den, Bangkok Express, Curry Kingdom, The Garden Table, Skyline Dine, Pasta Republic, Street Tacos Co, Miso Hungry, Chez Marie
24
+
25
+
26
+ If in previously collected data, the restaurant_name is there but not in this list as the exact spelling or not with correct casing, replace it with the correct one.
27
+
28
+ If user statement is a restaurant_name, dont modify user_name thinking that it is restaurant name, only modify user_name.
29
+ - "user_name":
30
+ - Only extract if the input clearly states a name like “My name is ...” or “This is ...”
31
+ - Do not extract from greetings like “Hi”, “Hello”, “Hey”, “Yo”, “Good evening”
32
+ - Do not invent names based on formatting or assumptions
33
+
34
+ Output format rules:
35
+ -Make sure restaurant_name matches from the list given
36
+ - Return only valid JSON — starting with { and ending with }
37
+ - All keys and values must be in double quotes
38
+ - Include all 5 keys in the output
39
+ - No markdown, comments, or explanation in output, just give a json
40
+ ---
41
+ Function: store_user_info
42
+ Prompt tokens: 521
43
+ Completion tokens: 63
44
+ Total tokens: 584
45
+ Prompt: You are a helpful assistant. Extract relevant user information from this user statement:
46
+ "Hi"
47
+
48
+ Previously collected data in json: {"restaurant_name": null, "user_name": null, "contact": null, "party_size": null, "time": null}
49
+ Always remember this json data, you need to update this based on user statement.
50
+ if user statement is book, dont change any value in this data
51
+ Return a JSON object with the following possible keys:
52
+ - restaurant_name - text
53
+ - user_name - text
54
+ - contact - text
55
+ - party_size - integer
56
+ - time (between 9 to 20, 9 represents 9AM, 20 represents 8PM) - integer
57
+ Donot consider time which is before 9 or after 20.
58
+ Never modify any entry to null if previous data is not null for that field.
59
+ Update the previous data with any new fields found. Do not make previously known fields unless you are sure the user wants to change them.
60
+ Respond ONLY with a single valid JSON object.
61
+ important rules:
62
+ - "restaurant_name": Must always match from this list:
63
+ Bella Italia, Spice Symphony, Tokyo Ramen House, Saffron Grill, El Toro Loco, Noodle Bar, Le Petit Bistro, Tandoori Nights, Green Leaf Cafe, Ocean Pearl, Mama Mia Pizza, The Dumpling Den, Bangkok Express, Curry Kingdom, The Garden Table, Skyline Dine, Pasta Republic, Street Tacos Co, Miso Hungry, Chez Marie
64
+
65
+
66
+ If in previously collected data, the restaurant_name is there but not in this list as the exact spelling or not with correct casing, replace it with the correct one.
67
+
68
+ If user statement is a restaurant_name, dont modify user_name thinking that it is restaurant name, only modify user_name.
69
+ - "user_name":
70
+ - Only extract if the input clearly states a name like “My name is ...” or “This is ...”
71
+ - Do not extract from greetings like “Hi”, “Hello”, “Hey”, “Yo”, “Good evening”
72
+ - Do not invent names based on formatting or assumptions
73
+
74
+ Output format rules:
75
+ -Make sure restaurant_name matches from the list given
76
+ - Return only valid JSON — starting with { and ending with }
77
+ - All keys and values must be in double quotes
78
+ - Include all 5 keys in the output
79
+ - No markdown, comments, or explanation in output, just give a json
80
+ ---
81
+ Function: determine_intent
82
+ Prompt tokens: 257
83
+ Completion tokens: 3
84
+ Total tokens: 260
85
+ Prompt: You are an intent classification assistant for a restaurant reservation system.
86
+
87
+ User input: "Hi"
88
+
89
+ Classify the intent as one of:
90
+ - STORE: User shares name, contact, or reservation details (like party size or time) without asking anything.
91
+ - SELECT: User asks about availability, restaurants, time slots, or capacity.
92
+ - BOOK: User says only "book" (case-insensitive). Even "I want to book..." is SELECT, not BOOK.
93
+ - GREET: User greets or starts a conversation without giving info or asking.
94
+ - RUBBISH: Input is gibberish, irrelevant, or unrecognizable.
95
+
96
+ Examples:
97
+ - "My name is Raj" → STORE
98
+ - "book" → BOOK
99
+ - "15 people" → SELECT
100
+ - "Tell me best restaurants" → SELECT
101
+ - "7801061333" → STORE
102
+ - "asdfgh" → RUBBISH
103
+ - "Hi there" → GREET
104
+
105
+ Respond with ONE word only: SELECT, STORE, BOOK, GREET, or RUBBISH. No explanation
106
+ ---
107
+ Function: generate_reservation_conversation
108
+ Prompt tokens: 427
109
+ Completion tokens: 50
110
+ Total tokens: 477
111
+ Prompt: You are a professional restaurant reservation assistant helping a customer make a booking. Speak concisely and professionally. Unless the booking is complete, end with a helpful question.
112
+
113
+ User said: "Hi"
114
+ Always try to answer this user query.
115
+ Current known user data (JSON): "{\"restaurant_name\": null, \"user_name\": null, \"contact\": null, \"party_size\": null, \"time\": null}"
116
+ Only ask about missing fields (those with null/None values). Do not repeat questions for data already present.
117
+ Never ask about the fields that are already present in the user data json.
118
+ - user_name: user's name
119
+ - contact: user’s phone (not for queries)
120
+ - restaurant_name: name of restaurant
121
+ - party_size: number of people
122
+ - time: hour of reservation (9–20)
123
+
124
+ If restaurant_name is missing, offer to suggest restaurants or cuisines. Never mention "null"—be conversational. Show known info naturally if helpful.
125
+
126
+ Database info:
127
+ "👋 Hello! How can I help you with your restaurant reservation today?"
128
+ Explain this clearly based on what user said. If it says:
129
+ - "Info Stored": thank the user and ask next missing info.
130
+ - "✅ Booking processed successfully.": Tell thanks for booking, I could assist you in new booking, also I could tell about restaurant features, pricing, etc, dont ask anything else.
131
+ - "❌ Booking failed: ...": explain the error simply and suggest trying again.
132
+ - A greeting: respond politely and ask if they need help with restaurant info or making a booking.
133
+
134
+ Personalize your response using available user data. Each table seats 4 people; use ceil(party_size / 4) to estimate how many are needed.
135
+ Try to explain as much information as possible from database info in a concise, professional way.
136
+
137
+ History snippet: ""
138
+ If earlier prompts asked for something now present in user data, don't ask again.
139
+
140
+ Be helpful, efficient, and professional in tone.
141
+ ---
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ
 
restaurants.json ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "r001",
4
+ "name": "Spice Garden",
5
+ "location": "Downtown",
6
+ "cuisine": ["Indian"],
7
+ "capacity": 40,
8
+ "ambiance": "casual",
9
+ "available_times": ["18:00", "19:30", "21:00"]
10
+ },
11
+ {
12
+ "id": "r002",
13
+ "name": "Bella Italia",
14
+ "location": "Midtown",
15
+ "cuisine": ["Italian"],
16
+ "capacity": 35,
17
+ "ambiance": "romantic",
18
+ "available_times": ["17:30", "19:00", "20:30"]
19
+ },
20
+ {
21
+ "id": "r003",
22
+ "name": "Tokyo Bites",
23
+ "location": "Uptown",
24
+ "cuisine": ["Japanese"],
25
+ "capacity": 50,
26
+ "ambiance": "family",
27
+ "available_times": ["18:00", "19:30"]
28
+ },
29
+ {
30
+ "id": "r004",
31
+ "name": "The Global Spoon",
32
+ "location": "Central",
33
+ "cuisine": ["Indian", "Mexican", "Chinese"],
34
+ "capacity": 60,
35
+ "ambiance": "casual",
36
+ "available_times": ["17:00", "18:30", "20:00"]
37
+ },
38
+ {
39
+ "id": "r005",
40
+ "name": "Saffron Lounge",
41
+ "location": "Downtown",
42
+ "cuisine": ["Indian", "Persian"],
43
+ "capacity": 45,
44
+ "ambiance": "fine-dining",
45
+ "available_times": ["18:00", "19:30", "21:00"]
46
+ },
47
+ {
48
+ "id": "r006",
49
+ "name": "La Vida",
50
+ "location": "Uptown",
51
+ "cuisine": ["Spanish"],
52
+ "capacity": 30,
53
+ "ambiance": "romantic",
54
+ "available_times": ["17:30", "19:00", "20:30"]
55
+ },
56
+ {
57
+ "id": "r007",
58
+ "name": "Burger Craze",
59
+ "location": "Midtown",
60
+ "cuisine": ["American"],
61
+ "capacity": 25,
62
+ "ambiance": "casual",
63
+ "available_times": ["16:00", "17:00", "18:30"]
64
+ },
65
+ {
66
+ "id": "r008",
67
+ "name": "Wok Express",
68
+ "location": "Chinatown",
69
+ "cuisine": ["Chinese"],
70
+ "capacity": 55,
71
+ "ambiance": "family",
72
+ "available_times": ["18:00", "19:30"]
73
+ },
74
+ {
75
+ "id": "r009",
76
+ "name": "Taco Fiesta",
77
+ "location": "Southside",
78
+ "cuisine": ["Mexican"],
79
+ "capacity": 40,
80
+ "ambiance": "casual",
81
+ "available_times": ["17:00", "18:30", "20:00"]
82
+ },
83
+ {
84
+ "id": "r010",
85
+ "name": "Green Fork",
86
+ "location": "Downtown",
87
+ "cuisine": ["Vegan", "Organic"],
88
+ "capacity": 28,
89
+ "ambiance": "minimalist",
90
+ "available_times": ["17:00", "18:30", "20:00"]
91
+ },
92
+ {
93
+ "id": "r011",
94
+ "name": "The Royal Tandoor",
95
+ "location": "Eastside",
96
+ "cuisine": ["Indian"],
97
+ "capacity": 50,
98
+ "ambiance": "fine-dining",
99
+ "available_times": ["18:00", "19:30", "21:00"]
100
+ },
101
+ {
102
+ "id": "r012",
103
+ "name": "Ocean Grill",
104
+ "location": "Seaside",
105
+ "cuisine": ["Seafood"],
106
+ "capacity": 60,
107
+ "ambiance": "coastal",
108
+ "available_times": ["17:30", "19:00", "20:30"]
109
+ },
110
+ {
111
+ "id": "r013",
112
+ "name": "Le Petit Bistro",
113
+ "location": "Central",
114
+ "cuisine": ["French"],
115
+ "capacity": 26,
116
+ "ambiance": "romantic",
117
+ "available_times": ["18:00", "19:30"]
118
+ },
119
+ {
120
+ "id": "r014",
121
+ "name": "Fusion Point",
122
+ "location": "Uptown",
123
+ "cuisine": ["Asian", "Italian"],
124
+ "capacity": 48,
125
+ "ambiance": "modern",
126
+ "available_times": ["17:00", "18:30", "20:00"]
127
+ },
128
+ {
129
+ "id": "r015",
130
+ "name": "Himalayan Hearth",
131
+ "location": "Downtown",
132
+ "cuisine": ["Nepalese", "Tibetan"],
133
+ "capacity": 30,
134
+ "ambiance": "cozy",
135
+ "available_times": ["17:30", "19:00"]
136
+ },
137
+ {
138
+ "id": "r016",
139
+ "name": "BBQ Pitstop",
140
+ "location": "Westend",
141
+ "cuisine": ["American", "Barbecue"],
142
+ "capacity": 52,
143
+ "ambiance": "rustic",
144
+ "available_times": ["18:00", "19:30", "21:00"]
145
+ },
146
+ {
147
+ "id": "r017",
148
+ "name": "Curry & More",
149
+ "location": "Southside",
150
+ "cuisine": ["Indian", "Thai"],
151
+ "capacity": 40,
152
+ "ambiance": "family",
153
+ "available_times": ["17:00", "18:30", "20:00"]
154
+ },
155
+ {
156
+ "id": "r018",
157
+ "name": "Dolce Vita",
158
+ "location": "Midtown",
159
+ "cuisine": ["Italian", "French"],
160
+ "capacity": 36,
161
+ "ambiance": "elegant",
162
+ "available_times": ["18:00", "19:30", "21:00"]
163
+ },
164
+ {
165
+ "id": "r019",
166
+ "name": "K-Pot",
167
+ "location": "Chinatown",
168
+ "cuisine": ["Korean", "Hot Pot"],
169
+ "capacity": 42,
170
+ "ambiance": "lively",
171
+ "available_times": ["17:30", "19:00", "20:30"]
172
+ },
173
+ {
174
+ "id": "r020",
175
+ "name": "Garden Table",
176
+ "location": "Central",
177
+ "cuisine": ["Farm-to-Table", "Vegetarian"],
178
+ "capacity": 38,
179
+ "ambiance": "natural",
180
+ "available_times": ["17:00", "18:30", "20:00"]
181
+ }
182
+ ]
sticky.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal
2
+
3
+ import streamlit as st
4
+
5
+ MARGINS = {
6
+ "top": "0",
7
+ "bottom": "0",
8
+ }
9
+
10
+ STICKY_CONTAINER_HTML = """
11
+ <style>
12
+ div[data-testid="stVerticalBlock"] div:has(div.fixed-header-{i}) {{
13
+ position: sticky;
14
+ {position}: {margin};
15
+ background-color: white;
16
+ z-index: {z};
17
+ }}
18
+ </style>
19
+ <div class='fixed-header-{i}'/>
20
+ """.strip()
21
+
22
+ # Not to apply the same style to multiple containers
23
+ count = 0
24
+
25
+
26
+ def sticky_container(
27
+ *,
28
+ height: int | None = None,
29
+ border: bool | None = None,
30
+ mode: Literal["top", "bottom"] = "top",
31
+ margin: str | None = None,
32
+ z:int |None=None
33
+ ):
34
+ if margin is None:
35
+ margin = MARGINS[mode]
36
+
37
+ global count
38
+ html_code = STICKY_CONTAINER_HTML.format(position=mode, margin=margin, i=count,z=z)
39
+ count += 1
40
+
41
+ container = st.container(height=height, border=border)
42
+ container.markdown(html_code, unsafe_allow_html=True)
43
+ return container
44
+
tools.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import inspect
3
+ import pandas as pd
4
+ import json
5
+ import re
6
+ import streamlit as st
7
+ def log_groq_token_usage(response, prompt=None, function_name=None, filename="efficiency_log.txt"):
8
+ usage = response.usage
9
+ log_message = (
10
+ f"Function: {function_name or 'unknown'}\n"
11
+ f"Prompt tokens: {usage.prompt_tokens}\n"
12
+ f"Completion tokens: {usage.completion_tokens}\n"
13
+ f"Total tokens: {usage.total_tokens}\n"
14
+ f"Prompt: {prompt}\n"
15
+ "---\n"
16
+ )
17
+ with open(filename, "a", encoding="utf-8") as f: # ← THIS LINE
18
+ f.write(log_message)
19
+
20
+ import pandas as pd
21
+ # --- Database Execution ---
22
+ def execute_transaction(sql_statements):
23
+ txn_conn = None
24
+ try:
25
+ txn_conn = sqlite3.connect("db/restaurant_reservation.db")
26
+ cursor = txn_conn.cursor()
27
+ for stmt in sql_statements:
28
+ cursor.execute(stmt)
29
+ txn_conn.commit()
30
+ return "✅ Booking Executed"
31
+ except Exception as e:
32
+ if txn_conn:
33
+ txn_conn.rollback()
34
+ return f"❌ Booking failed: {e}"
35
+ finally:
36
+ if txn_conn:
37
+ txn_conn.close()
38
+
39
+
40
+ def execute_query(sql_query, db_path="db/restaurant_reservation.db"):
41
+ conn = None
42
+ try:
43
+ conn = sqlite3.connect(db_path)
44
+ cursor = conn.cursor()
45
+ cursor.execute(sql_query)
46
+ rows = cursor.fetchall()
47
+ columns = [desc[0] for desc in cursor.description] if cursor.description else []
48
+ return pd.DataFrame(rows, columns=columns)
49
+ except Exception as e:
50
+ return f"❌ Error executing query: {e}"
51
+ finally:
52
+ if conn:
53
+ conn.close()
54
+ def generate_sql_query_v2(user_input,SCHEMA_DESCRIPTIONS,history_prompt, vector_db, client, use_cache=False):
55
+ # Get relevant schema elements
56
+ relevant_tables = vector_db.get_relevant_schema(user_input)
57
+ schema_prompt = "\n".join([f"Table {table}:\n{SCHEMA_DESCRIPTIONS[table]}" for table in relevant_tables])
58
+ # Cache check
59
+ cache_key = f"query:{user_input[:50]}"
60
+ if use_cache and (cached := cache.get(cache_key)):
61
+ return cached.decode()
62
+ # Generate SQL with Groq
63
+ prompt = f"""Based on these tables:
64
+ {schema_prompt}
65
+ Previous assistant reply:
66
+ {history_prompt}
67
+ Convert this request to SQL: {user_input}
68
+
69
+ Only return the SQL query, nothing else."""
70
+ response = client.chat.completions.create(
71
+ model="llama3-8b-8192",
72
+ messages=[
73
+ {"role": "system", "content": "You are a helpful assistant that only returns SQL queries."},
74
+ {"role": "user", "content": prompt}
75
+ ],
76
+ temperature=0.3,
77
+ max_tokens=200
78
+ )
79
+ log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name)
80
+ sql = response.choices[0].message.content.strip()
81
+ if use_cache:
82
+ cache.setex(cache_key, 3600, sql)
83
+ return sql
84
+ def interpret_result_v2(result, user_query, sql_query,client):
85
+ if isinstance(result, str):
86
+ return result
87
+ try:
88
+ # Compress to essential columns if possible
89
+ cols = [c for c in result.columns if c in ['name', 'cuisine', 'location', 'seating_capacity', 'rating', 'address', 'contact', 'price_range', 'special_features', 'capacity', 'date', 'hour']]
90
+ if cols:
91
+ compressed = result[cols]
92
+ else:
93
+ compressed = result
94
+ json_data = compressed.to_json(orient='records', indent=2)
95
+ # Summarize with Groq
96
+ prompt = f"""User query: {user_query}
97
+ SQL query: {sql_query}
98
+ Result data (JSON): {json_data}
99
+
100
+ Summarize the results for the user."""
101
+ response = client.chat.completions.create(
102
+ model="llama3-8b-8192",
103
+ messages=[
104
+ {"role": "system", "content": "Summarize database query results for a restaurant reservation assistant."},
105
+ {"role": "user", "content": prompt}
106
+ ],
107
+ temperature=0.3,
108
+ max_tokens=300
109
+ )
110
+ log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name)
111
+ return response.choices[0].message.content.strip()
112
+ except Exception as e:
113
+ return f"Error interpreting results: {e}"
114
+
115
+ def handle_query(user_input, vector_db, client):
116
+ try:
117
+ # First try semantic search
118
+ semantic_results = {}
119
+
120
+ # Search across all collections
121
+ restaurant_results = vector_db.semantic_search(user_input, "restaurants")
122
+ table_results = vector_db.semantic_search(user_input, "tables")
123
+ slot_results = vector_db.semantic_search(user_input, "slots")
124
+
125
+ if any([restaurant_results, table_results, slot_results]):
126
+ semantic_results = {
127
+ "restaurants": restaurant_results,
128
+ "tables": table_results,
129
+ "slots": slot_results
130
+ }
131
+
132
+ # Format semantic results
133
+ summary = []
134
+ for category, items in semantic_results.items():
135
+ if items:
136
+ summary.append(f"Found {len(items)} relevant {category}:")
137
+ summary.extend([f"- {item['name']}" if 'name' in item else f"- {item}"
138
+ for item in items[:3]])
139
+
140
+ return "\n".join(summary)
141
+ else:
142
+ # Fall back to SQL generation
143
+ sql = generate_sql_query_v2(user_input, vector_db, client)
144
+ result = execute_query(sql)
145
+ return interpret_result_v2(result, user_input, sql,client)
146
+
147
+ except Exception as e:
148
+ return f"Error: {e}"
149
+
150
+
151
+ def is_large_output_request(query):
152
+ query = query.lower()
153
+ # List of single words and multi-word phrases (as lists)
154
+ triggers = [
155
+ ['all'], ['every'], ['entire'], ['complete'], ['full'], ['each'],
156
+ ['list'], ['show'], ['display'], ['give', 'me'], ['get'],
157
+ ['every', 'single'], ['each', 'and', 'every'],
158
+ ['whole'], ['total'], ['collection'], ['set'],
159
+ ['no', 'filters'], ['without', 'filters'],
160
+ ['everything'], ['entirety'],
161
+ ['comprehensive'], ['exhaustive'], ['record'],
162
+ ['don\'t', 'filter'], ['without', 'limitations']
163
+ ]
164
+ query_words = query.split()
165
+ for trigger in triggers:
166
+ if all(word in query_words for word in trigger):
167
+ return True
168
+ return False
169
+
170
+
171
+ def generate_reservation_conversation(user_query, history_prompt, sql_summary, user_data,generate_reservation_conversation_prompt,client):
172
+ words = history_prompt.split() if history_prompt else []
173
+ if len(words) > 25:
174
+ history_prompt_snippet = " ".join(words[:15]) + " ... " + " ".join(words[-10:])
175
+ else:
176
+ history_prompt_snippet = " ".join(words)
177
+
178
+ # Serialize user_data as pretty JSON for readability in prompt
179
+ user_data_json = json.dumps(user_data, indent=2)
180
+
181
+ prompt = generate_reservation_conversation_prompt.format(
182
+ user_query=user_query,
183
+ user_data=user_data_json,
184
+ sql_summary=sql_summary,
185
+ history_prompt_snippet=history_prompt_snippet
186
+ )
187
+
188
+ response = client.chat.completions.create(
189
+ model="llama3-8b-8192",
190
+ messages=[
191
+ {"role": "system", "content": "You are a helpful restaurant reservation assistant."},
192
+ {"role": "user", "content": prompt}
193
+ ],
194
+ temperature=0.4
195
+ )
196
+
197
+ if not response.choices:
198
+ return "Sorry, I couldn't generate a response right now."
199
+ log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name)
200
+
201
+ return response.choices[0].message.content.strip()
202
+
203
+
204
+ # --- Helper Functions ---
205
+
206
+ def determine_intent(user_input,determine_intent_prompt,client):
207
+ prompt = determine_intent_prompt.format(user_input=user_input)
208
+ response = client.chat.completions.create(
209
+ model="llama3-8b-8192",
210
+ messages=[
211
+ {"role": "system", "content": "Classify user intent into SELECT, STORE, BOOK, GREET, or RUBBISH based on message content."},
212
+ {"role": "user", "content": prompt}
213
+ ],
214
+ temperature=0
215
+ )
216
+ log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name)
217
+ return response.choices[0].message.content.strip().upper()
218
+
219
+
220
+
221
+ def store_user_info(user_input,history_prompt,store_user_info_prompt, client):
222
+ # words = history_prompt.split()
223
+ # if len(words) > 25:
224
+ # history_prompt_snippet = " ".join(words[:15]) + " ... " + " ".join(words[-10:])
225
+ # else:
226
+ # history_prompt_snippet = " ".join(words)
227
+ previous_info = json.dumps(st.session_state.user_data)
228
+ # st.json(previous_info)
229
+ prompt = store_user_info_prompt.format(previous_info=previous_info,user_input=user_input)
230
+ response = client.chat.completions.create(
231
+ model="llama3-8b-8192",
232
+ messages=[{"role": "system", "content": "Extract or update user booking info in JSON."},
233
+ {"role": "user", "content": prompt}],
234
+ temperature=0.3
235
+ )
236
+ log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name)
237
+
238
+ try:
239
+ # Print raw LLM output for inspection
240
+ raw_output = response.choices[0].message.content
241
+ # st.subheader("🧠 Raw LLM Response")
242
+ # st.write(raw_output)
243
+
244
+ # Extract JSON substring from anywhere in the response
245
+ json_match = re.search(r'{[\s\S]*?}', raw_output)
246
+ if not json_match:
247
+ return None
248
+ # raise ValueError("No JSON object found in response.")
249
+
250
+ json_str = json_match.group()
251
+
252
+ # Show the extracted JSON string
253
+ # st.subheader("📦 Extracted JSON String")
254
+ # st.code(json_str, language="json")
255
+
256
+ # Safely parse using json.loads
257
+ parsed = json.loads(json_str)
258
+
259
+ # Display the parsed result
260
+ # st.subheader("✅ Parsed JSON Object")
261
+ # st.json(parsed)
262
+
263
+ return parsed
264
+
265
+ except Exception as e:
266
+ st.error(f"⚠️ Failed to parse JSON: {e}")
267
+ return {}
268
+
269
+ def generate_sql_query(user_input,restaurant_name,party_size,time, history_prompt, schema_prompt, client):
270
+ words = history_prompt.split()
271
+ if len(words) > 25:
272
+ history_prompt_snippet = " ".join(words[:15]) + " ... " + " ".join(words[-10:])
273
+ else:
274
+ history_prompt_snippet = " ".join(words)
275
+ prompt = schema_prompt.format(
276
+ history_prompt=history_prompt,
277
+ user_input=user_input
278
+ )
279
+
280
+ response = client.chat.completions.create(
281
+ model="llama3-8b-8192",
282
+ messages=[
283
+ {"role": "system", "content": "You are a helpful assistant that only returns SQL queries."},
284
+ {"role": "user", "content": prompt}
285
+ ],
286
+ temperature=0.3
287
+ )
288
+ log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name)
289
+ raw_sql = response.choices[0].message.content.strip()
290
+ extracted_sql = re.findall(r"(SELECT[\s\S]+?)(?:;|$)", raw_sql, re.IGNORECASE)
291
+ sql_query = extracted_sql[0].strip() + ";" if extracted_sql else raw_sql
292
+
293
+ return sql_query
294
+
295
+ def interpret_sql_result(user_query, sql_query, result,interpret_sql_result_prompt, client):
296
+ if isinstance(result, pd.DataFrame):
297
+ # Convert DataFrame to list of dicts
298
+ result_dict = result.to_dict(orient="records")
299
+ else:
300
+ # Fall back to raw string if not a DataFrame
301
+ result_dict = result
302
+
303
+ prompt = interpret_sql_result_prompt.format(
304
+ user_query=user_query,
305
+ sql_query=sql_query,
306
+ result_str=json.dumps(result_dict, indent=2) # Pass as formatted JSON string
307
+ )
308
+
309
+ response = client.chat.completions.create(
310
+ model="llama3-8b-8192",
311
+ messages=[
312
+ {"role": "system", "content": "You summarize database query results for a restaurant reservation assistant."},
313
+ {"role": "user", "content": prompt}
314
+ ],
315
+ temperature=0.3
316
+ )
317
+ log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name)
318
+ return response.choices[0].message.content.strip()
ui_utils.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal
2
+
3
+ import streamlit as st
4
+ from streamlit.components.v1 import html
5
+
6
+ """
7
+ st_fixed_container consist of two parts - fixed container and opaque container.
8
+ Fixed container is a container that is fixed to the top or bottom of the screen.
9
+ When transparent is set to True, the container is typical `st.container`, which is transparent by default.
10
+ When transparent is set to False, the container is custom opaque_container, that updates its background color to match the background color of the app.
11
+ Opaque container is a helper class, but can be used to create more custom views. See main for examples.
12
+ """
13
+ OPAQUE_CONTAINER_CSS = """
14
+ :root {{
15
+ --background-color: #ffffff; /* Default background color */
16
+ }}
17
+ div[data-testid="stVerticalBlockBorderWrapper"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) div[data-testid="stVerticalBlock"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) > div[data-testid="stVerticalBlockBorderWrapper"] {{
18
+ background-color: var(--background-color);
19
+ width: 100%;
20
+ }}
21
+ div[data-testid="stVerticalBlockBorderWrapper"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) div[data-testid="stVerticalBlock"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) > div[data-testid="element-container"] {{
22
+ display: none;
23
+ }}
24
+ div[data-testid="stVerticalBlockBorderWrapper"]:has(div.not-opaque-container):not(:has(div[class^='opaque-container-'])) {{
25
+ display: none;
26
+ }}
27
+ """.strip()
28
+
29
+ OPAQUE_CONTAINER_JS = """
30
+ const root = parent.document.querySelector('.stApp');
31
+ let lastBackgroundColor = null;
32
+ function updateContainerBackground(currentBackground) {
33
+ parent.document.documentElement.style.setProperty('--background-color', currentBackground);
34
+ ;
35
+ }
36
+ function checkForBackgroundColorChange() {
37
+ const style = window.getComputedStyle(root);
38
+ const currentBackgroundColor = style.backgroundColor;
39
+ if (currentBackgroundColor !== lastBackgroundColor) {
40
+ lastBackgroundColor = currentBackgroundColor; // Update the last known value
41
+ updateContainerBackground(lastBackgroundColor);
42
+ }
43
+ }
44
+ const observerCallback = (mutationsList, observer) => {
45
+ for(let mutation of mutationsList) {
46
+ if (mutation.type === 'attributes' && (mutation.attributeName === 'class' || mutation.attributeName === 'style')) {
47
+ checkForBackgroundColorChange();
48
+ }
49
+ }
50
+ };
51
+ const main = () => {
52
+ checkForBackgroundColorChange();
53
+ const observer = new MutationObserver(observerCallback);
54
+ observer.observe(root, { attributes: true, childList: false, subtree: false });
55
+ }
56
+ // main();
57
+ document.addEventListener("DOMContentLoaded", main);
58
+ """.strip()
59
+
60
+
61
+ def st_opaque_container(
62
+ *,
63
+ height: int | None = None,
64
+ border: bool | None = None,
65
+ key: str | None = None,
66
+ ):
67
+ global opaque_counter
68
+
69
+ opaque_container = st.container()
70
+ non_opaque_container = st.container()
71
+ css = OPAQUE_CONTAINER_CSS.format(id=key)
72
+ with opaque_container:
73
+ html(f"<script>{OPAQUE_CONTAINER_JS}</script>", scrolling=False, height=0)
74
+ st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
75
+ st.markdown(
76
+ f"<div class='opaque-container-{key}'></div>",
77
+ unsafe_allow_html=True,
78
+ )
79
+ with non_opaque_container:
80
+ st.markdown(
81
+ f"<div class='not-opaque-container'></div>",
82
+ unsafe_allow_html=True,
83
+ )
84
+
85
+ return opaque_container.container(height=height, border=border)
86
+
87
+
88
+ FIXED_CONTAINER_CSS = """
89
+ div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)){{
90
+ background-color: transparent;
91
+ position: {mode};
92
+ width: inherit;
93
+ background-color: inherit;
94
+ {position}: {margin};
95
+ z-index: 999;
96
+ }}
97
+ div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) div[data-testid="stVerticalBlock"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) > div[data-testid="element-container"] {{
98
+ display: none;
99
+ }}
100
+ div[data-testid="stVerticalBlockBorderWrapper"]:has(div.not-fixed-container):not(:has(div[class^='fixed-container-'])) {{
101
+ display: none;
102
+ }}
103
+ """.strip()
104
+
105
+ MARGINS = {
106
+ "top": "0",
107
+ "bottom": "0",
108
+ }
109
+
110
+
111
+ def st_fixed_container(
112
+ *,
113
+ height: int | None = None,
114
+ border: bool | None = None,
115
+ mode: Literal["fixed", "sticky"] = "fixed",
116
+ position: Literal["top", "bottom"] = "top",
117
+ margin: str | None = None,
118
+ transparent: bool = False,
119
+ key: str | None = None,
120
+ ):
121
+ if margin is None:
122
+ margin = MARGINS[position]
123
+ global fixed_counter
124
+ fixed_container = st.container()
125
+ non_fixed_container = st.container()
126
+ css = FIXED_CONTAINER_CSS.format(
127
+ mode=mode,
128
+ position=position,
129
+ margin=margin,
130
+ id=key,
131
+ )
132
+ with fixed_container:
133
+ st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
134
+ st.markdown(
135
+ f"<div class='fixed-container-{key}'></div>",
136
+ unsafe_allow_html=True,
137
+ )
138
+ with non_fixed_container:
139
+ st.markdown(
140
+ f"<div class='not-fixed-container'></div>",
141
+ unsafe_allow_html=True,
142
+ )
143
+
144
+ with fixed_container:
145
+ if transparent:
146
+ return st.container(height=height, border=border)
147
+
148
+ return st_opaque_container(height=height, border=border, key=f"opaque_{key}")
149
+
150
+
var.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chromadb
2
+ import sqlite3
3
+ import hashlib
4
+ import pandas as pd
5
+ from sentence_transformers import SentenceTransformer
6
+ #--- Initialize ChromaDB and SentenceTransformer ---
7
+ SCHEMA_DESCRIPTIONS = {
8
+ "restaurants": """Table restaurants contains restaurant details:
9
+ - id: unique identifier
10
+ - name: restaurant name
11
+ - cuisine: type of cuisine
12
+ - location: area or neighborhood
13
+ - seating_capacity: total seats
14
+ - rating: average rating
15
+ - address: full address
16
+ - contact: phone or email
17
+ - price_range: price category
18
+ - special_features: amenities or highlights""",
19
+ "tables": """Table tables contains table details:
20
+ - id: unique identifier
21
+ - restaurant_id: links to restaurants.id
22
+ - capacity: number of seats (default 4)""",
23
+ "slots": """Table slots contains reservation time slots:
24
+ - id: unique identifier
25
+ - table_id: links to tables.id
26
+ - date: reservation date
27
+ - hour: reservation hour
28
+ - is_reserved: 0=available, 1=booked"""
29
+ }
30
+ class SchemaVectorDB:
31
+ def __init__(self):
32
+ self.client = chromadb.Client()
33
+ self.collection = self.client.get_or_create_collection("schema")
34
+ self.model = SentenceTransformer('all-MiniLM-L6-v2')
35
+ for idx, (name, desc) in enumerate(SCHEMA_DESCRIPTIONS.items()):
36
+ self.collection.add(ids=str(idx), documents=desc, metadatas={"name": name})
37
+
38
+ def get_relevant_schema(self, query, k=2):
39
+ query_embedding = self.model.encode(query).tolist()
40
+ results = self.collection.query(query_embeddings=[query_embedding], n_results=k)
41
+ # results['metadatas'] is a list of lists: [[{...}, {...}], ...]
42
+ # We only have one query, so grab the first list
43
+ metadatas = results['metadatas'][0] if results['metadatas'] else []
44
+ return [m['name'] for m in metadatas if m and 'name' in m]
45
+
46
+
47
+
48
+
49
+
50
+
51
+ class FullVectorDB:
52
+ def __init__(self):
53
+ self.client = chromadb.PersistentClient(path="db/chroma")
54
+ self.model = SentenceTransformer('all-MiniLM-L6-v2')
55
+
56
+ # Get existing collections or create if not exist
57
+ self.restaurants_col = self.client.get_or_create_collection("restaurants")
58
+ self.tables_col = self.client.get_or_create_collection("tables")
59
+ self.slots_col = self.client.get_or_create_collection("slots")
60
+
61
+ # Initialize only if collections are empty
62
+ if len(self.restaurants_col.get()['ids']) == 0:
63
+ self._initialize_collections()
64
+
65
+ def _row_to_text(self, row):
66
+ return ' '.join(str(v) for v in row.values if pd.notnull(v))
67
+
68
+ def _row_hash(self, row):
69
+ return hashlib.sha256(str(row.values).encode()).hexdigest()
70
+
71
+ def _initialize_collections(self):
72
+ conn = sqlite3.connect("db/restaurant_reservation.db")
73
+
74
+ # Create external changelog table
75
+ conn.execute("""
76
+ CREATE TABLE IF NOT EXISTS chroma_changelog (
77
+ id INTEGER PRIMARY KEY,
78
+ table_name TEXT,
79
+ record_id INTEGER,
80
+ content_hash TEXT,
81
+ UNIQUE(table_name, record_id)
82
+ )
83
+ """)
84
+ conn.commit()
85
+
86
+ # Process tables
87
+ self._process_table(conn, "restaurants", self.restaurants_col)
88
+ self._process_table(conn, "tables", self.tables_col)
89
+ self._process_table(conn, "slots", self.slots_col)
90
+
91
+ conn.close()
92
+
93
+ def _process_table(self, conn, table_name, collection):
94
+ # Get existing records from Chroma
95
+ existing_ids = set(collection.get()['ids'])
96
+
97
+ # Get all records from SQLite with hash
98
+ df = pd.read_sql(f"SELECT * FROM {table_name}", conn)
99
+
100
+ # Process each row
101
+ for _, row in df.iterrows():
102
+ chroma_id = f"{table_name}_{row['id']}"
103
+ current_hash = self._row_hash(row)
104
+
105
+ # Check if exists in changelog
106
+ changelog = pd.read_sql(f"""
107
+ SELECT content_hash
108
+ FROM chroma_changelog
109
+ WHERE table_name = ? AND record_id = ?
110
+ """, conn, params=(table_name, row['id']))
111
+
112
+ # Skip if hash matches
113
+ if not changelog.empty and changelog.iloc[0]['content_hash'] == current_hash:
114
+ continue
115
+
116
+ # Generate embedding
117
+ embedding = self.model.encode(self._row_to_text(row))
118
+
119
+ # Update Chroma
120
+ collection.upsert(
121
+ ids=[chroma_id],
122
+ embeddings=[embedding.tolist()],
123
+ metadatas=[row.to_dict()]
124
+ )
125
+
126
+ # Update changelog
127
+ conn.execute("""
128
+ INSERT OR REPLACE INTO chroma_changelog
129
+ (table_name, record_id, content_hash)
130
+ VALUES (?, ?, ?)
131
+ """, (table_name, row['id'], current_hash))
132
+ conn.commit()
133
+
134
+ def semantic_search(self, query, collection_name, k=5):
135
+ query_embedding = self.model.encode(query).tolist()
136
+ collection = getattr(self, f"{collection_name}_col")
137
+ results = collection.query(
138
+ query_embeddings=[query_embedding],
139
+ n_results=k,
140
+ include=["metadatas"]
141
+ )
142
+ return results['metadatas'][0]