Shanmuganathan75 commited on
Commit
9dc514a
·
verified ·
1 Parent(s): 146bd73

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +45 -170
app.py CHANGED
@@ -3,10 +3,6 @@ FoodHub - AI-Powered Food Delivery Chatbot with Streamlit UI
3
  Complete implementation with SQL Agent, Guardrails, and Interactive Interface
4
  """
5
 
6
- # ==============================================
7
- # 📦 Install Required Libraries for SQL + LLM Agent
8
- # ==============================================
9
-
10
  import streamlit as st
11
  import json
12
  import os
@@ -21,8 +17,6 @@ from langchain_core.messages import SystemMessage, HumanMessage
21
  from langchain.sql_database import SQLDatabase
22
  from langchain.agents.agent_toolkits import SQLDatabaseToolkit
23
  from langchain_groq import ChatGroq
24
- from langchain.agents import Tool
25
- from langchain.agents import initialize_agent
26
 
27
  # ============================================================================
28
  # CONFIGURATION & SETUP
@@ -208,13 +202,7 @@ def guardrail_with_llm(user_query: str, llm) -> str:
208
  return "unsafe" if any(word in result for word in unsafe_keywords) else "safe"
209
 
210
  def simple_authenticate(cust_id: str, db_agent) -> bool:
211
- """
212
- Authenticate a customer by checking if cust_id exists in the orders table.
213
- Returns True if the customer exists, False otherwise.
214
- Validation rule:
215
- - Output must match the same cust_id as in the query result.
216
- - Any other type of output or structure → invalid (False).
217
- """
218
  try:
219
  query = f"SELECT cust_id FROM orders WHERE cust_id = '{cust_id}';"
220
  result = db_agent.invoke({"input": query})
@@ -261,197 +249,86 @@ def handle_cancellation(user_query: str, raw_orders: str, order_status: str) ->
261
 
262
  return "Your order cannot be canceled. We hope to serve you again!"
263
 
264
- #######################################################################################################################
265
-
266
- # --- TOOL 1: Order Query Tool ---
267
- def order_chatbot(input_string: str) -> str:
268
- """
269
- Accepts a stringified dict input like:
270
- "{'cust_id': 'C1016', 'user_message': 'Where is my order?'}"
271
- Parses it, authenticates, fetches data, and returns structured info.
272
- """
273
- try:
274
- data = ast.literal_eval(input_string)
275
- cust_id = data.get("cust_id")
276
- user_message = data.get("user_message")
277
- except Exception:
278
- return "⚠️ Invalid input format for OrderQueryTool."
279
-
280
- # Step 1:Fetch order details
281
- try:
282
- order_result = db_agent.invoke(f"SELECT * FROM orders WHERE cust_id = '{cust_id}';")
283
- raw_orders = order_result.get("output") if order_result else None
284
- except Exception:
285
- return "🚫 Sorry, we cannot fetch your order details right now. Please try again later."
286
-
287
- # ✅ Return structured dictionary string for next tool
288
- print(raw_orders)
289
- return str({
290
- "cust_id": cust_id,
291
- "user_query": user_message,
292
- "raw_orders": raw_orders
293
- })
294
-
295
- # --- Register Tools ---
296
-
297
- Order_Query_Tool = Tool(
298
- name="OrderQueryTool",
299
- func=order_chatbot,
300
- description="Fetches order details safely and returns structured output as a stringified dictionary."
301
- )
302
-
303
- #######################################################################################################################
304
- # --- TOOL 2: Answer Tool ---
305
- def format_customer_response(input_string: str) -> str:
306
- """
307
- Receives the output from OrderQueryTool as stringified dict,
308
- parses it, and generates the final friendly message.
309
- """
310
- try:
311
- data = ast.literal_eval(input_string)
312
- cust_id = data.get("cust_id", "Unknown")
313
- user_query = data.get("user_query", "")
314
- raw_orders = data.get("raw_orders", "No order details found.")
315
- except Exception:
316
- return "⚠️ Error: Could not parse order data properly."
317
-
318
  order_status = None
319
- item_in_order = None
320
  preparing_eta = None
321
  delivery_time = None
322
 
323
- # 🔹 Parse the raw order details line by line
324
  for line in raw_orders.splitlines():
325
- if "Order Status" in line:
326
  order_status = line.split(":", 1)[1].strip()
327
- elif "Preparing ETA" in line:
328
  preparing_eta = line.split(":", 1)[1].strip()
329
- elif "Delivery Time" in line:
330
  delivery_time = line.split(":", 1)[1].strip()
331
 
332
- # 🔹 Check if user query needs escalation (e.g., delayed order or major issue)
333
  escalation_var = detect_escalation(user_query)
334
  if escalation_var == "Escalated":
335
  return (
336
- f"Present status of your order is : {order_status.lower()}." +
337
- "⚠️ Your issue requires immediate attention. " +
338
  "We have escalated your query to a human agent who will contact you shortly."
339
  )
340
 
341
- # 🔹 Handle cancellation requests (calls the function)
342
  cancel_response = handle_cancellation(user_query, raw_orders, order_status)
343
- if cancel_response: # If function returns a valid message
344
  return cancel_response
345
 
346
-
347
- # 🔹 Format normal order response using LLM
348
  system_prompt = f"""
349
  You are a friendly customer support assistant for FoodHub.
 
350
  Customer ID: {cust_id}
351
- Here is the customer's order data from the database:
352
- {raw_orders}
353
- Sample of raw_orders :
354
- order_id: O12501,
355
- cust_id: C1026,
356
- order_time: 12:59,
357
- order_status: preparing food,
358
- payment_status: COD,
359
- item_in_order: Burger, Fries, Soda,
360
- preparing_eta: 13:14,
361
- prepared_time: None,
362
- delivery_eta: None,
363
- delivery_time: None
364
  Instructions:
365
- 1. Respond naturally and conversationally in a very short response.
366
- 2. Use only raw_oreders data to answer the user's query.
367
- 2. Interpret the raw_orders into polite, concise, and customer-friendly responses.
368
- 3. If order_status = 'preparing food', include ETA from 'preparing_eta' and also include ETA from 'delivery_eta'.
369
- - If 'delivery_eta' is missing or None, say: "Your order is being prepared, and Delivery ETA will be available soon."
370
- 4. If order_status = 'delivered', mention 'delivery_time'.
371
- 5. If order_status = 'canceled', explain politely.
372
- 6. If order_status = 'picked up', include ETA from 'delivery_eta'.
373
- - If 'delivery_eta' is missing or None, say: "Your order has been picked up, delivery ETA will be available soon."
374
- 7. If user_query contains 'Where is my order' then provide the order_status from the raw_orders.
375
- 8. If user_query contains 'How many items' then provide the 'Items in Order' from the raw_orders and return only that number in a friendly way (e.g., “Your order contains 3 items.”).
376
  """
377
 
378
- # Build user-specific prompt
379
  user_prompt = f"User Query: {user_query}"
380
 
381
- # 🔹 Generate response using LLM (system + user messages)
382
  response_msg = llm.predict_messages([
383
  SystemMessage(content=system_prompt),
384
  HumanMessage(content=user_prompt)
385
  ])
386
 
387
- # Clean and return the final LLM response
388
  response = response_msg.content.strip()
 
389
 
390
- # Return fallback message if no response is generated
391
- if not response:
392
- return "Sorry, we could not retrieve your order details at this time."
393
- return response
394
-
395
- # # --- Register Tools ---
396
-
397
- Answer_Tool = Tool(
398
- name="AnswerTool",
399
- func=format_customer_response,
400
- description="Takes the output from OrderQueryTool and returns a customer-facing message."
401
- )
402
-
403
- #######################################################################################################################
404
- import os
405
-
406
- # Fetch the API key from Hugging Face Secrets
407
- api_key = os.getenv("GROQ_API_KEY")
408
- if api_key is None:
409
- raise ValueError("GROQ_API_KEY not found. Please set it in Hugging Face Secrets.")
410
-
411
- # Now you can initialize your LLM
412
- llm = initialize_llm(api_key)
413
-
414
- #######################################################################################################################
415
- # --- Initialize Agent ---
416
- tools = [Order_Query_Tool, Answer_Tool]
417
-
418
- agent = initialize_agent(
419
- tools=tools,
420
- llm=llm,
421
- #llm=st.session_state.llm,
422
- agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
423
- verbose=True,
424
- max_iterations=10,
425
- max_execution_time=60
426
- )
427
 
428
- #######################################################################################################################
 
 
 
429
 
430
- # --- AGENT CONTROLLER ---
431
- def agent_tool_response(cust_id: str, user_query: str, llm, db_agent) -> str:
 
432
 
433
- """
434
- Executes tools in correct sequence: OrderQueryTool → AnswerTool.
435
- """
436
- agent_prompt = f"""
437
- You are FoodHub's Order Assistant.
438
- Sequence of execution:
439
- 1️⃣ Use 'OrderQueryTool' with:
440
- input_string = str({{"cust_id": "{cust_id}", "user_message": "{user_query}"}})
441
- → Output: stringified dict containing 'cust_id', 'user_query', and 'raw_orders'.
442
- 2️⃣ Use 'AnswerTool' with:
443
- input_string = output of OrderQueryTool.
444
- 3️⃣ Return **only** the exact output from AnswerTool as the final user response — no rewording or summary.
445
- """
446
-
447
- final_answer = agent.run(agent_prompt)
448
-
449
- print("\n======= ✅ FINAL RESPONSE =======")
450
- print(final_answer)
451
- return final_answer
452
-
453
- #######################################################################################################################
454
 
 
 
 
455
 
456
  # ============================================================================
457
  # STREAMLIT UI COMPONENTS
@@ -473,8 +350,6 @@ def render_sidebar():
473
  help="Enter your Groq API key to use the chatbot"
474
  )
475
 
476
-
477
-
478
  # Database Path Input
479
  db_path = st.text_input(
480
  "Database Path",
@@ -605,7 +480,7 @@ def render_chat_interface():
605
 
606
  # Get bot response
607
  with st.spinner("🤔 Thinking..."):
608
- bot_response = agent_tool_response(
609
  st.session_state.customer_id,
610
  user_input,
611
  st.session_state.llm,
 
3
  Complete implementation with SQL Agent, Guardrails, and Interactive Interface
4
  """
5
 
 
 
 
 
6
  import streamlit as st
7
  import json
8
  import os
 
17
  from langchain.sql_database import SQLDatabase
18
  from langchain.agents.agent_toolkits import SQLDatabaseToolkit
19
  from langchain_groq import ChatGroq
 
 
20
 
21
  # ============================================================================
22
  # CONFIGURATION & SETUP
 
202
  return "unsafe" if any(word in result for word in unsafe_keywords) else "safe"
203
 
204
  def simple_authenticate(cust_id: str, db_agent) -> bool:
205
+ """Authenticate customer by checking if cust_id exists"""
 
 
 
 
 
 
206
  try:
207
  query = f"SELECT cust_id FROM orders WHERE cust_id = '{cust_id}';"
208
  result = db_agent.invoke({"input": query})
 
249
 
250
  return "Your order cannot be canceled. We hope to serve you again!"
251
 
252
+ def format_customer_response(cust_id: str, raw_orders: str, user_query: str, llm) -> str:
253
+ """Format final customer-facing response"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  order_status = None
 
255
  preparing_eta = None
256
  delivery_time = None
257
 
258
+ # Parse order details
259
  for line in raw_orders.splitlines():
260
+ if "Order Status" in line or "order_status" in line:
261
  order_status = line.split(":", 1)[1].strip()
262
+ elif "Preparing ETA" in line or "preparing_eta" in line:
263
  preparing_eta = line.split(":", 1)[1].strip()
264
+ elif "Delivery Time" in line or "delivery_time" in line:
265
  delivery_time = line.split(":", 1)[1].strip()
266
 
267
+ # Check escalation
268
  escalation_var = detect_escalation(user_query)
269
  if escalation_var == "Escalated":
270
  return (
271
+ f"Present status of your order is: {order_status.lower()}. "
272
+ "⚠️ Your issue requires immediate attention. "
273
  "We have escalated your query to a human agent who will contact you shortly."
274
  )
275
 
276
+ # Check cancellation
277
  cancel_response = handle_cancellation(user_query, raw_orders, order_status)
278
+ if cancel_response:
279
  return cancel_response
280
 
281
+ # Generate normal response using LLM
 
282
  system_prompt = f"""
283
  You are a friendly customer support assistant for FoodHub.
284
+
285
  Customer ID: {cust_id}
286
+ Order data: {raw_orders}
287
+
 
 
 
 
 
 
 
 
 
 
 
288
  Instructions:
289
+ 1. Respond naturally and conversationally in a short response
290
+ 2. Use only the order data to answer
291
+ 3. If order_status = 'preparing food', include preparing_eta and delivery_eta
292
+ 4. If order_status = 'delivered', mention delivery_time
293
+ 5. If order_status = 'picked up', include delivery_eta
294
+ 6. For "Where is my order" queries, provide order_status
295
+ 7. For "How many items" queries, count items in item_in_order
 
 
 
 
296
  """
297
 
 
298
  user_prompt = f"User Query: {user_query}"
299
 
 
300
  response_msg = llm.predict_messages([
301
  SystemMessage(content=system_prompt),
302
  HumanMessage(content=user_prompt)
303
  ])
304
 
 
305
  response = response_msg.content.strip()
306
+ return response if response else "Sorry, we could not retrieve your order details at this time."
307
 
308
+ def order_chatbot(cust_id: str, user_message: str, llm, db_agent) -> str:
309
+ """Main chatbot function to handle customer queries"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
+ # Step 1: Security guardrail
312
+ guardrail_response = guardrail_with_llm(user_message, llm)
313
+ if "unsafe" in guardrail_response.lower():
314
+ return "🚫 Unauthorized or irrelevant query. Please ask something related to your order only."
315
 
316
+ # Step 2: Authentication
317
+ if not simple_authenticate(cust_id, db_agent):
318
+ return "🚫 Invalid customer ID. Please provide a valid customer ID."
319
 
320
+ # Step 3: Fetch order details
321
+ try:
322
+ order_result = db_agent.invoke(
323
+ f"SELECT * FROM orders WHERE cust_id = '{cust_id}';"
324
+ )
325
+ raw_orders = order_result.get("output") if order_result else None
326
+ except Exception:
327
+ return "🚫 Sorry, we cannot fetch your order details right now. Please try again later."
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
+ # Step 4: Generate response
330
+ final_response = format_customer_response(cust_id, raw_orders, user_message, llm)
331
+ return final_response
332
 
333
  # ============================================================================
334
  # STREAMLIT UI COMPONENTS
 
350
  help="Enter your Groq API key to use the chatbot"
351
  )
352
 
 
 
353
  # Database Path Input
354
  db_path = st.text_input(
355
  "Database Path",
 
480
 
481
  # Get bot response
482
  with st.spinner("🤔 Thinking..."):
483
+ bot_response = order_chatbot(
484
  st.session_state.customer_id,
485
  user_input,
486
  st.session_state.llm,