decodingdatascience commited on
Commit
464a90e
·
verified ·
1 Parent(s): 2855ba4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -744
app.py CHANGED
@@ -1,120 +1,52 @@
1
- # ============================================================
2
- # DDS ENTERPRISE HR CHATBOT
3
- # Hugging Face + LlamaIndex + Pinecone + OpenAI + Telegram
4
- # ============================================================
5
 
 
6
  import os
7
  import sys
8
  import time
9
- import logging
10
- import threading
11
- import requests
12
- import gradio as gr
13
 
 
14
  from pinecone import Pinecone, ServerlessSpec
15
 
16
  from llama_index.core import (
17
- VectorStoreIndex,
18
  SimpleDirectoryReader,
19
  StorageContext,
20
- Settings,
21
  )
22
-
23
- from llama_index.vector_stores.pinecone import PineconeVectorStore
24
- from llama_index.readers.file import PDFReader
25
- from llama_index.llms.openai import OpenAI
26
  from llama_index.embeddings.openai import OpenAIEmbedding
 
 
 
27
 
28
 
29
- # ============================================================
30
- # 1. LOGGING
31
- # ============================================================
32
-
33
- logging.basicConfig(
34
- stream=sys.stdout,
35
- level=logging.INFO
36
- )
37
-
38
- logger = logging.getLogger("DDS-HR")
39
-
40
 
41
- # ============================================================
42
- # 2. LOAD HUGGING FACE SECRETS
43
- # ============================================================
44
 
 
 
 
45
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
46
  PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
47
- TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
48
-
49
 
50
  if not OPENAI_API_KEY:
51
- raise ValueError(
52
- "OPENAI_API_KEY is missing from Hugging Face Secrets"
53
- )
54
 
55
  if not PINECONE_API_KEY:
56
- raise ValueError(
57
- "PINECONE_API_KEY is missing from Hugging Face Secrets"
58
- )
59
-
60
- if not TELEGRAM_BOT_TOKEN:
61
- logger.warning(
62
- "TELEGRAM_BOT_TOKEN not found. "
63
- "Web chatbot will work but Telegram will not."
64
- )
65
-
66
-
67
- # ============================================================
68
- # 3. SYSTEM PROMPT
69
- # ============================================================
70
-
71
- SYSTEM_PROMPT = """
72
- You are AYesha, the Decoding Data Science (DDS)
73
- Enterprise HR Chatbot.
74
-
75
- You answer questions ONLY using information contained
76
- in the latest DDS HR Handbook retrieved for you.
77
-
78
- RULES:
79
-
80
- 1. Only answer questions related to DDS HR policies.
81
-
82
- 2. Base answers only on retrieved DDS HR Handbook content.
83
-
84
- 3. Never invent HR policies.
85
-
86
- 4. Never answer confidential questions such as salary
87
- information or private employee information.
88
-
89
- 5. If the handbook does not contain the answer, say:
90
-
91
- "I'm sorry, I can only answer questions about the latest
92
- DDS HR policies. For confidential or other queries,
93
- please email connect@decodingdatascience.com."
94
-
95
- 6. If the question is unrelated to HR, say:
96
-
97
- "I'm sorry, I only answer DDS HR policy questions
98
- as outlined in the handbook."
99
-
100
- 7. Keep answers concise, professional and easy to understand.
101
-
102
- 8. Internally verify whether the question is within scope
103
- before answering. Do not expose internal reasoning.
104
-
105
- Always prioritize the DDS HR Handbook as the source of truth.
106
- """
107
-
108
-
109
- # ============================================================
110
- # 4. LLAMAINDEX + OPENAI SETTINGS
111
- # ============================================================
112
 
 
 
 
113
  Settings.llm = OpenAI(
114
  model="gpt-4o-mini",
115
  temperature=0.2,
116
  api_key=OPENAI_API_KEY,
117
- system_prompt=SYSTEM_PROMPT,
118
  )
119
 
120
  Settings.embed_model = OpenAIEmbedding(
@@ -126,32 +58,42 @@ Settings.chunk_size = 600
126
  Settings.chunk_overlap = 200
127
 
128
 
129
- # ============================================================
130
- # 5. CONNECT TO PINECONE
131
- # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- logger.info("Connecting to Pinecone...")
134
 
135
- pc = Pinecone(
136
- api_key=PINECONE_API_KEY
137
- )
 
138
 
139
  INDEX_NAME = "quickstart"
140
  DIMENSION = 1536
141
 
 
 
142
 
143
- # ============================================================
144
- # 6. CREATE INDEX ONLY IF IT DOES NOT EXIST
145
- # ============================================================
146
-
147
- existing_indexes = pc.list_indexes().names()
148
-
149
- if INDEX_NAME not in existing_indexes:
150
-
151
- logger.info(
152
- "Pinecone index does not exist. Creating it..."
153
- )
154
-
155
  pc.create_index(
156
  name=INDEX_NAME,
157
  dimension=DIMENSION,
@@ -162,687 +104,102 @@ if INDEX_NAME not in existing_indexes:
162
  ),
163
  )
164
 
165
- logger.info(
166
- "Waiting for Pinecone index..."
167
- )
168
-
169
- time.sleep(10)
170
-
171
  else:
172
-
173
  logger.info(
174
- "Existing Pinecone index found. NOT deleting it."
 
175
  )
176
 
 
 
177
 
178
- # ============================================================
179
- # 7. CONNECT TO INDEX
180
- # ============================================================
181
-
182
- pinecone_index = pc.Index(
183
- INDEX_NAME
184
- )
185
-
186
- vector_store = PineconeVectorStore(
187
- pinecone_index=pinecone_index
188
- )
189
-
190
-
191
- # ============================================================
192
- # 8. CHECK WHETHER DATA IS ALREADY INDEXED
193
- # ============================================================
194
-
195
- try:
196
-
197
- stats = pinecone_index.describe_index_stats()
198
-
199
- try:
200
- vector_count = stats["total_vector_count"]
201
- except Exception:
202
- vector_count = getattr(
203
- stats,
204
- "total_vector_count",
205
- 0
206
- )
207
-
208
- except Exception as e:
209
-
210
- logger.warning(
211
- f"Could not read Pinecone stats: {e}"
212
- )
213
-
214
- vector_count = 0
215
-
216
-
217
- logger.info(
218
- f"Pinecone vector count: {vector_count}"
219
- )
220
-
221
-
222
- # ============================================================
223
- # 9. INDEX HR PDF IF DATABASE IS EMPTY
224
- # ============================================================
225
 
226
- if vector_count == 0:
227
-
228
- logger.info(
229
- "Pinecone is empty. Loading PDFs from Data folder..."
230
- )
231
 
232
  documents = SimpleDirectoryReader(
233
  input_dir="Data",
234
  required_exts=[".pdf"],
235
- file_extractor={
236
- ".pdf": PDFReader()
237
- }
238
  ).load_data()
239
 
240
  if not documents:
241
-
242
- raise ValueError(
243
- "No PDF documents found inside Data folder"
244
- )
245
-
246
- logger.info(
247
- f"Loaded {len(documents)} document sections"
248
- )
249
 
250
  storage_context = StorageContext.from_defaults(
251
  vector_store=vector_store
252
  )
253
 
254
- logger.info(
255
- "Creating embeddings and storing them in Pinecone..."
256
- )
257
-
258
  index = VectorStoreIndex.from_documents(
259
  documents,
260
  storage_context=storage_context,
261
  )
262
 
263
- logger.info(
264
- "DDS HR Handbook indexed successfully."
265
- )
266
 
267
  else:
268
-
269
- logger.info(
270
- "Using existing Pinecone vectors."
271
- )
272
-
273
  index = VectorStoreIndex.from_vector_store(
274
  vector_store=vector_store
275
  )
276
 
277
 
278
- # ============================================================
279
- # 10. QUERY ENGINE
280
- # ============================================================
281
-
282
  query_engine = index.as_query_engine(
283
- similarity_top_k=4
 
284
  )
285
 
286
 
287
- # ============================================================
288
- # 11. ONE FUNCTION USED BY WEB + TELEGRAM
289
- # ============================================================
290
-
291
- def query_doc(question):
292
-
293
- if not question:
294
- return "Please enter an HR question."
295
-
296
- question = question.strip()
297
-
298
- if not question:
299
- return "Please enter an HR question."
300
-
301
- logger.info(
302
- f"HR question received: {question}"
303
- )
304
-
305
- try:
306
-
307
- response = query_engine.query(
308
- question
309
- )
310
-
311
- answer = str(response)
312
-
313
- logger.info(
314
- "Answer generated successfully."
315
- )
316
-
317
- return answer
318
-
319
- except Exception as e:
320
-
321
- logger.exception(
322
- f"RAG ERROR: {e}"
323
- )
324
-
325
- return (
326
- "Sorry, I couldn't process your question "
327
- "right now. Please try again."
328
- )
329
-
330
-
331
- # ============================================================
332
- # 12. TELEGRAM URL
333
- # ============================================================
334
-
335
- def telegram_url(method):
336
-
337
- return (
338
- f"https://api.telegram.org/"
339
- f"bot{TELEGRAM_BOT_TOKEN}/"
340
- f"{method}"
341
- )
342
-
343
-
344
- # ============================================================
345
- # 13. TEST TELEGRAM CONNECTION FROM HUGGING FACE
346
- # ============================================================
347
-
348
- def test_telegram_connection():
349
-
350
- if not TELEGRAM_BOT_TOKEN:
351
-
352
- logger.error(
353
- "TELEGRAM TEST: Token is missing."
354
- )
355
-
356
- return False
357
-
358
- logger.info(
359
- "===================================="
360
- )
361
-
362
- logger.info(
363
- "TESTING HUGGING FACE -> TELEGRAM"
364
- )
365
-
366
- logger.info(
367
- f"TELEGRAM TOKEN FOUND: {bool(TELEGRAM_BOT_TOKEN)}"
368
- )
369
 
370
  try:
371
-
372
- response = requests.get(
373
- telegram_url("getMe"),
374
- timeout=30
375
- )
376
-
377
- logger.info(
378
- f"TELEGRAM TEST STATUS: {response.status_code}"
379
- )
380
-
381
- response.raise_for_status()
382
-
383
- data = response.json()
384
-
385
- if data.get("ok"):
386
-
387
- bot = data["result"]
388
-
389
- logger.info(
390
- "TELEGRAM TEST RESULT: SUCCESS"
391
- )
392
-
393
- logger.info(
394
- f"Bot Name: {bot.get('first_name')}"
395
- )
396
-
397
- logger.info(
398
- f"Bot Username: @{bot.get('username')}"
399
- )
400
-
401
- logger.info(
402
- "===================================="
403
- )
404
-
405
- return True
406
-
407
- logger.error(
408
- f"TELEGRAM TEST RESULT: API ERROR: {data}"
409
- )
410
-
411
- return False
412
-
413
- except requests.exceptions.Timeout as e:
414
-
415
- logger.error(
416
- f"TELEGRAM TEST FAILED: TIMEOUT: {repr(e)}"
417
- )
418
-
419
- return False
420
-
421
- except requests.exceptions.RequestException as e:
422
-
423
- logger.error(
424
- f"TELEGRAM TEST FAILED: NETWORK ERROR: {repr(e)}"
425
- )
426
-
427
- return False
428
-
429
- except Exception as e:
430
-
431
- logger.exception(
432
- f"TELEGRAM TEST FAILED: {repr(e)}"
433
- )
434
-
435
- return False
436
-
437
-
438
- # ============================================================
439
- # 14. SEND TELEGRAM MESSAGE
440
- # ============================================================
441
-
442
- def send_telegram_message(chat_id, text):
443
-
444
- if not TELEGRAM_BOT_TOKEN:
445
- return
446
-
447
- text = str(text)
448
-
449
- # Telegram messages have a length limit.
450
- # Split long answers into smaller pieces.
451
- chunks = [
452
- text[i:i + 4000]
453
- for i in range(0, len(text), 4000)
454
- ]
455
-
456
- for chunk in chunks:
457
-
458
- try:
459
-
460
- response = requests.post(
461
- telegram_url("sendMessage"),
462
- json={
463
- "chat_id": chat_id,
464
- "text": chunk,
465
- },
466
- timeout=30
467
- )
468
-
469
- response.raise_for_status()
470
-
471
- except Exception as e:
472
-
473
- logger.exception(
474
- f"Telegram send error: {e}"
475
- )
476
-
477
-
478
- # ============================================================
479
- # 15. SHOW TELEGRAM TYPING
480
- # ============================================================
481
-
482
- def telegram_typing(chat_id):
483
-
484
- try:
485
-
486
- requests.post(
487
- telegram_url("sendChatAction"),
488
- json={
489
- "chat_id": chat_id,
490
- "action": "typing",
491
- },
492
- timeout=10
493
- )
494
-
495
- except Exception:
496
-
497
- pass
498
-
499
-
500
- # ============================================================
501
- # 16. TELEGRAM POLLING LOOP
502
- # ============================================================
503
-
504
- def telegram_polling_loop():
505
-
506
- if not TELEGRAM_BOT_TOKEN:
507
-
508
- logger.warning(
509
- "Telegram disabled because "
510
- "TELEGRAM_BOT_TOKEN is missing."
511
- )
512
-
513
- return
514
-
515
- # --------------------------------------------------------
516
- # STEP 1: TEST HUGGING FACE -> TELEGRAM
517
- # --------------------------------------------------------
518
-
519
- if not test_telegram_connection():
520
-
521
- logger.error(
522
- "Telegram connection failed."
523
- )
524
-
525
- logger.error(
526
- "Web chatbot will continue running normally."
527
- )
528
-
529
- return
530
-
531
-
532
- # --------------------------------------------------------
533
- # STEP 2: DELETE ANY OLD WEBHOOK
534
- # --------------------------------------------------------
535
-
536
- try:
537
-
538
- response = requests.post(
539
- telegram_url("deleteWebhook"),
540
- json={
541
- "drop_pending_updates": True
542
- },
543
- timeout=30
544
- )
545
-
546
- response.raise_for_status()
547
-
548
- logger.info(
549
- "Old Telegram webhook cleared."
550
- )
551
-
552
- except Exception as e:
553
-
554
- logger.warning(
555
- f"Could not clear webhook: {e}"
556
- )
557
-
558
-
559
- # --------------------------------------------------------
560
- # STEP 3: START LISTENING FOR TELEGRAM MESSAGES
561
- # --------------------------------------------------------
562
-
563
- logger.info(
564
- "===================================="
565
- )
566
-
567
- logger.info(
568
- "DDS HR TELEGRAM BOT STARTED"
569
- )
570
-
571
- logger.info(
572
- "Waiting for Telegram messages..."
573
- )
574
-
575
- logger.info(
576
- "===================================="
577
- )
578
-
579
-
580
- offset = None
581
-
582
-
583
- while True:
584
-
585
- try:
586
-
587
- params = {
588
- "timeout": 0
589
- }
590
-
591
- if offset is not None:
592
- params["offset"] = offset
593
-
594
-
595
- response = requests.get(
596
- telegram_url("getUpdates"),
597
- params=params,
598
- timeout=30
599
- )
600
-
601
- response.raise_for_status()
602
-
603
- data = response.json()
604
-
605
-
606
- if not data.get("ok"):
607
-
608
- logger.warning(
609
- f"Telegram API error: {data}"
610
- )
611
-
612
- time.sleep(2)
613
-
614
- continue
615
-
616
-
617
- updates = data.get(
618
- "result",
619
- []
620
- )
621
-
622
-
623
- for update in updates:
624
-
625
- offset = (
626
- update["update_id"] + 1
627
- )
628
-
629
- message = update.get(
630
- "message"
631
- )
632
-
633
- if not message:
634
- continue
635
-
636
-
637
- chat = message.get(
638
- "chat",
639
- {}
640
- )
641
-
642
- chat_id = chat.get(
643
- "id"
644
- )
645
-
646
- text = (
647
- message.get(
648
- "text"
649
- )
650
- or ""
651
- ).strip()
652
-
653
-
654
- if not chat_id:
655
- continue
656
-
657
-
658
- if not text:
659
-
660
- send_telegram_message(
661
- chat_id,
662
- "Please send me a text "
663
- "question about DDS HR policies."
664
- )
665
-
666
- continue
667
-
668
-
669
- logger.info(
670
- f"Telegram message received: {text}"
671
- )
672
-
673
-
674
- # ============================================
675
- # /start
676
- # ============================================
677
-
678
- if text.startswith("/start"):
679
-
680
- send_telegram_message(
681
- chat_id,
682
- "Welcome to AYesha — "
683
- "DDS Enterprise HR Chatbot.\n\n"
684
- "Ask me a question about the "
685
- "latest DDS HR policies.\n\n"
686
- "For example:\n"
687
- "What is the annual leave policy?"
688
- )
689
-
690
- continue
691
-
692
-
693
- # ============================================
694
- # /help
695
- # ============================================
696
-
697
- if text.startswith("/help"):
698
-
699
- send_telegram_message(
700
- chat_id,
701
- "Ask me questions about DDS HR "
702
- "policies.\n\n"
703
- "Examples:\n"
704
- "• What is the annual leave policy?\n"
705
- "• What is the probation policy?\n"
706
- "• How do I request leave?"
707
- )
708
-
709
- continue
710
-
711
-
712
- # ============================================
713
- # OTHER COMMAND
714
- # ============================================
715
-
716
- if text.startswith("/"):
717
-
718
- send_telegram_message(
719
- chat_id,
720
- "Please type your HR question "
721
- "as a normal message."
722
- )
723
-
724
- continue
725
-
726
-
727
- # ============================================
728
- # NORMAL HR QUESTION
729
- # ============================================
730
-
731
- telegram_typing(
732
- chat_id
733
- )
734
-
735
- answer = query_doc(
736
- text
737
- )
738
-
739
- send_telegram_message(
740
- chat_id,
741
- answer
742
- )
743
-
744
-
745
- # Prevent aggressive Telegram API calls
746
- time.sleep(1)
747
-
748
-
749
- except requests.exceptions.Timeout:
750
-
751
- logger.warning(
752
- "Telegram temporary timeout. Retrying..."
753
- )
754
-
755
- time.sleep(2)
756
-
757
-
758
- except requests.exceptions.RequestException as e:
759
-
760
- logger.warning(
761
- f"Telegram network error: {e}"
762
- )
763
-
764
- time.sleep(5)
765
-
766
-
767
- except Exception as e:
768
-
769
- logger.exception(
770
- f"Telegram unexpected error: {e}"
771
- )
772
-
773
- time.sleep(5)
774
-
775
-
776
- # ============================================================
777
- # 17. START TELEGRAM IN BACKGROUND
778
- # ============================================================
779
-
780
- def start_telegram_bot():
781
-
782
- if not TELEGRAM_BOT_TOKEN:
783
-
784
- logger.warning(
785
- "Telegram bot NOT started."
786
- )
787
-
788
- return
789
-
790
- thread = threading.Thread(
791
- target=telegram_polling_loop,
792
- daemon=True,
793
- name="DDS-Telegram-Bot"
794
- )
795
-
796
- thread.start()
797
-
798
- logger.info(
799
- "Telegram background thread started."
800
- )
801
-
802
-
803
- # ============================================================
804
- # 18. START TELEGRAM
805
- # ============================================================
806
-
807
- start_telegram_bot()
808
-
809
-
810
- # ============================================================
811
- # 19. GRADIO WEB APP
812
- # ============================================================
813
 
814
  demo = gr.Interface(
815
-
816
  fn=query_doc,
817
-
818
  inputs=gr.Textbox(
819
  label="Ask a question about DDS HR policies",
820
- placeholder=(
821
- "Example: What is the annual leave policy?"
822
- )
823
  ),
824
-
825
- outputs=gr.Textbox(
826
- label="Answer"
827
- ),
828
-
829
  title="DDS Enterprise HR Chatbot",
830
-
831
- description=(
832
- "Ask questions based on the latest "
833
- "DDS HR Handbook."
834
- )
835
-
836
  )
837
 
838
 
839
- # ============================================================
840
- # 20. RUN HUGGING FACE APP
841
- # ============================================================
842
-
843
  if __name__ == "__main__":
844
-
845
  demo.launch(
846
  server_name="0.0.0.0",
847
- server_port=7860
848
- )
 
1
+ # DDS Enterprise HR Chatbot
2
+ # Hugging Face Spaces app.py
 
 
3
 
4
+ import logging
5
  import os
6
  import sys
7
  import time
 
 
 
 
8
 
9
+ import gradio as gr
10
  from pinecone import Pinecone, ServerlessSpec
11
 
12
  from llama_index.core import (
13
+ Settings,
14
  SimpleDirectoryReader,
15
  StorageContext,
16
+ VectorStoreIndex,
17
  )
 
 
 
 
18
  from llama_index.embeddings.openai import OpenAIEmbedding
19
+ from llama_index.llms.openai import OpenAI
20
+ from llama_index.readers.file import PDFReader
21
+ from llama_index.vector_stores.pinecone import PineconeVectorStore
22
 
23
 
24
+ # ---------------------------------------------------------
25
+ # 1. Logging
26
+ # ---------------------------------------------------------
27
+ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
28
+ logger = logging.getLogger("dds-hr-enterprise")
 
 
 
 
 
 
29
 
 
 
 
30
 
31
+ # ---------------------------------------------------------
32
+ # 2. Secrets from Hugging Face
33
+ # ---------------------------------------------------------
34
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
35
  PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
 
 
36
 
37
  if not OPENAI_API_KEY:
38
+ raise ValueError("OPENAI_API_KEY is missing from Hugging Face Secrets.")
 
 
39
 
40
  if not PINECONE_API_KEY:
41
+ raise ValueError("PINECONE_API_KEY is missing from Hugging Face Secrets.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ # ---------------------------------------------------------
44
+ # 3. LlamaIndex / OpenAI settings
45
+ # ---------------------------------------------------------
46
  Settings.llm = OpenAI(
47
  model="gpt-4o-mini",
48
  temperature=0.2,
49
  api_key=OPENAI_API_KEY,
 
50
  )
51
 
52
  Settings.embed_model = OpenAIEmbedding(
 
58
  Settings.chunk_overlap = 200
59
 
60
 
61
+ # ---------------------------------------------------------
62
+ # 4. System prompt
63
+ # ---------------------------------------------------------
64
+ SYSTEM_PROMPT = """
65
+ You are AYesha, the Decoding Data Science (DDS) Enterprise HR Chatbot.
66
+
67
+ Answer questions exclusively using the latest DDS HR Handbook content supplied
68
+ to the retrieval system.
69
+
70
+ Rules:
71
+ - Only answer questions directly related to DDS HR policies in the handbook.
72
+ - Do not answer general questions unrelated to DDS HR.
73
+ - Do not provide confidential information such as salary details.
74
+ - If the answer is not supported by the handbook, do not guess.
75
+ - For confidential, unsupported, or out-of-scope requests, respond:
76
+ "I’m sorry, I can only answer questions about the latest DDS HR policies.
77
+ For confidential or other queries, please email
78
+ connect@decodingdatascience.com."
79
+ - Keep responses concise, professional, and easy to understand.
80
+ - Base the final answer only on retrieved handbook information.
81
+ """
82
 
 
83
 
84
+ # ---------------------------------------------------------
85
+ # 5. Pinecone
86
+ # ---------------------------------------------------------
87
+ pc = Pinecone(api_key=PINECONE_API_KEY)
88
 
89
  INDEX_NAME = "quickstart"
90
  DIMENSION = 1536
91
 
92
+ existing_indexes = [idx["name"] for idx in pc.list_indexes()]
93
+ index_was_created = INDEX_NAME not in existing_indexes
94
 
95
+ if index_was_created:
96
+ logger.info("Creating Pinecone index '%s'...", INDEX_NAME)
 
 
 
 
 
 
 
 
 
 
97
  pc.create_index(
98
  name=INDEX_NAME,
99
  dimension=DIMENSION,
 
104
  ),
105
  )
106
 
107
+ # Give Pinecone a moment to make the new index ready.
108
+ time.sleep(5)
 
 
 
 
109
  else:
 
110
  logger.info(
111
+ "Using existing Pinecone index '%s' — NOT deleting/rebuilding it.",
112
+ INDEX_NAME,
113
  )
114
 
115
+ pinecone_index = pc.Index(INDEX_NAME)
116
+ vector_store = PineconeVectorStore(pinecone_index=pinecone_index)
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ # ---------------------------------------------------------
120
+ # 6. Build vectors once, or reconnect to existing vectors
121
+ # ---------------------------------------------------------
122
+ if index_was_created:
123
+ logger.info("Loading PDFs from Data/ and creating embeddings...")
124
 
125
  documents = SimpleDirectoryReader(
126
  input_dir="Data",
127
  required_exts=[".pdf"],
128
+ file_extractor={".pdf": PDFReader()},
 
 
129
  ).load_data()
130
 
131
  if not documents:
132
+ raise ValueError("No PDF documents were loaded from the 'Data' folder.")
 
 
 
 
 
 
 
133
 
134
  storage_context = StorageContext.from_defaults(
135
  vector_store=vector_store
136
  )
137
 
 
 
 
 
138
  index = VectorStoreIndex.from_documents(
139
  documents,
140
  storage_context=storage_context,
141
  )
142
 
143
+ logger.info("DDS HR handbook indexed successfully.")
 
 
144
 
145
  else:
146
+ # Reuse the vectors already stored in Pinecone.
 
 
 
 
147
  index = VectorStoreIndex.from_vector_store(
148
  vector_store=vector_store
149
  )
150
 
151
 
152
+ # ---------------------------------------------------------
153
+ # 7. Shared HR query engine
154
+ # ---------------------------------------------------------
 
155
  query_engine = index.as_query_engine(
156
+ system_prompt=SYSTEM_PROMPT,
157
+ similarity_top_k=4,
158
  )
159
 
160
 
161
+ def query_doc(prompt: str) -> str:
162
+ """Single HR answering function used by the web UI and API clients."""
163
+ if not prompt or not prompt.strip():
164
+ return "Please enter an HR policy question."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
  try:
167
+ response = query_engine.query(prompt.strip())
168
+ return str(response)
169
+
170
+ except Exception as exc:
171
+ logger.exception("Query error")
172
+ return "Sorry, I couldn't process your question right now. Please try again."
173
+
174
+
175
+ # ---------------------------------------------------------
176
+ # 8. Gradio web app + API
177
+ # ---------------------------------------------------------
178
+ # IMPORTANT:
179
+ # - The browser UI still uses query_doc directly.
180
+ # - External clients (Make, Telegram, etc.) can call api_name="/query_doc".
181
+ # - query_doc returns ONLY the answer string. There is no Telegram polling,
182
+ # webhook handling, token, or Telegram network call in this Space.
183
+ #
184
+ # Gradio's low-level /call endpoint uses an event stream internally.
185
+ # For the simplest external integration, use a Gradio client/API action that
186
+ # returns the prediction value rather than manually parsing the SSE wrapper.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
  demo = gr.Interface(
 
189
  fn=query_doc,
 
190
  inputs=gr.Textbox(
191
  label="Ask a question about DDS HR policies",
192
+ placeholder="Example: What is the annual leave policy?",
 
 
193
  ),
194
+ outputs=gr.Textbox(label="Answer"),
 
 
 
 
195
  title="DDS Enterprise HR Chatbot",
196
+ description="Ask questions based on the latest DDS HR Handbook.",
197
+ api_name="query_doc",
 
 
 
 
198
  )
199
 
200
 
 
 
 
 
201
  if __name__ == "__main__":
 
202
  demo.launch(
203
  server_name="0.0.0.0",
204
+ server_port=7860,
205
+ )