File size: 35,826 Bytes
16cceb0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857

# Setup and Installation

import torch
print("๐Ÿ–ฅ๏ธ System Check:")
print(f"CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
    print(f"GPU device: {torch.cuda.get_device_name(0)}")
    print(f"GPU memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
else:
    print("โš ๏ธ No GPU detected - BioGPT will run on CPU (much slower)")

print("\n๐Ÿ”ง Installing required packages...")

# Import Libraries

import os
import re
import torch
import warnings
import numpy as np
import faiss  # FAISS for vector search
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    pipeline,
    BitsAndBytesConfig
)
from sentence_transformers import SentenceTransformer
from typing import List, Dict, Optional
import time
from datetime import datetime
import json
import pickle

# Suppress warnings for cleaner output
warnings.filterwarnings('ignore')

print("๐Ÿ“š Libraries imported successfully!")
print(f"๐Ÿ” FAISS version: {faiss.__version__}")
print("๐ŸŽฏ Using FAISS for vector search (ChromaDB completely removed)")

# File Upload Helper

from google.colab import files
import io

def upload_medical_data():
    """Upload your Pediatric_cleaned.txt file"""
    print("๐Ÿ“ Please upload your Pediatric_cleaned.txt file:")
    uploaded = files.upload()

    # Get the uploaded file
    filename = list(uploaded.keys())[0]
    print(f"โœ… File '{filename}' uploaded successfully!")

    # Read the content
    content = uploaded[filename].decode('utf-8')

    # Save it locally in Colab
    with open('Pediatric_cleaned.txt', 'w', encoding='utf-8') as f:
        f.write(content)

    print(f"๐Ÿ“ File saved as 'Pediatric_cleaned.txt' ({len(content)} characters)")
    return 'Pediatric_cleaned.txt'

medical_file = 'Pediatric_cleaned.txt'

# BioGPT Medical Chatbot Class

from typing import List, Dict, Optional # Import List, Dict, Optional

class ColabBioGPTChatbot:
    def setup_biogpt(self):
        """Setup BioGPT model with fallback to base BioGPT if Large fails"""
        print("๐Ÿง  Attempting to load BioGPT-Large...")
        model_name = "microsoft/BioGPT-Large"

        try:
            if self.use_8bit:
                quantization_config = BitsAndBytesConfig(
                    load_in_8bit=True,
                    llm_int8_threshold=6.0,
                    llm_int8_has_fp16_weight=False,
                )
            else:
                quantization_config = None

            self.tokenizer = AutoTokenizer.from_pretrained(model_name)
            if self.tokenizer.pad_token is None:
                self.tokenizer.pad_token = self.tokenizer.eos_token

            self.model = AutoModelForCausalLM.from_pretrained(
                model_name,
                quantization_config=quantization_config,
                torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
                device_map="auto" if self.device == "cuda" else None,
                trust_remote_code=True
            )

            if self.device == "cuda" and quantization_config is None:
                self.model = self.model.to(self.device)

            print("โœ… BioGPT-Large loaded successfully!")
            self.test_biogpt()

        except Exception as e:
            print(f"โŒ BioGPT-Large loading failed: {e}")
            print("๐Ÿ” Falling back to base model: microsoft/BioGPT")
            self.setup_fallback_biogpt()

        def fallback_biogpt():
    """Fallback to microsoft/BioGPT if BioGPT-Large fails"""
    print("Loading fallback model: microsoft/BioGPT")
    # You can implement the actual fallback loading here later
    pass

        model_name = "microsoft/BioGPT"

        try:
            self.tokenizer = AutoTokenizer.from_pretrained(model_name)
            if self.tokenizer.pad_token is None:
                self.tokenizer.pad_token = self.tokenizer.eos_token

            self.model = AutoModelForCausalLM.from_pretrained(
                model_name,
                torch_dtype=torch.float32,
                trust_remote_code=True
            )
            self.model = self.model.to(self.device)

            print("โœ… Base BioGPT model loaded as fallback.")
            self.test_biogpt()

        except Exception as e:
            print(f"โŒ Failed to load fallback BioGPT: {e}")
            self.model = None
            self.tokenizer = None

(self):
        if self.tokenizer is None or self.model is None:
            print("โš ๏ธ Skipping test โ€” model or tokenizer not available.")
            return
        print("๐Ÿงช Testing BioGPT...")
        try:
            input_text = "What is pneumonia?"
            inputs = self.tokenizer(input_text, return_tensors="pt").to(self.device)
            outputs = self.model.generate(**inputs, max_new_tokens=30)
            response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
            print(f"๐Ÿง  Model output: {response}")
        except Exception as e:
            print(f"โš ๏ธ BioGPT test failed: {e}")

        def __init__(self, use_gpu=True, use_8bit=True):
            """Initialize BioGPT chatbot optimized for Google Colab"""
        print("๐Ÿฅ Initializing...")
        self.use_gpu = use_gpu
        self.use_8bit = use_8bit
        self.device = "cuda" if torch.cuda.is_available() and use_gpu else "cpu"
        print(f"๐Ÿ–ฅ๏ธ System Check:\nCUDA available: {torch.cuda.is_available()}\nUsing device: {self.device}")
        self.tokenizer = None
        self.model = None
        self.vectorstore = None
        self.history = []
        self.embeddings = None

        print("๐Ÿ”ง Installing required packages...")

        try:
                import transformers
                import faiss
                print("๐Ÿ“š Libraries imported successfully!")
                print(f"๐Ÿ” FAISS version: {faiss.__version__}")
        except ImportError as e:
                print(f"โŒ Missing package: {e}")

                self.setup_biogpt()
                self.load_sentence_transformer()
                self.vectorstore = None

        def setup_biogpt(self):
            """Setup BioGPT model with fallback to base BioGPT if Large fails"""
            print("๐Ÿง  Attempting to load BioGPT-Large...")
            model_name = "microsoft/BioGPT-Large"
        try:
            if self.use_8bit and self.device == "cuda":
                from transformers import BitsAndBytesConfig
                bnb_config = BitsAndBytesConfig(load_in_8bit=True)
                self.model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config, device_map="auto")
            else:
                self.model = AutoModelForCausalLM.from_pretrained(model_name).to(self.device)

            self.tokenizer = AutoTokenizer.from_pretrained(model_name)
            print("โœ… BioGPT-Large loaded successfully.")
        except Exception as e:
            print(f"โŒ BioGPT-Large loading failed: {e}")
            print("๐Ÿ” Falling back to base model: microsoft/BioGPT")
            self.setup_fallback_biogpt()

        def setup_fallback_biogpt(self):
            """Fallback to microsoft/BioGPT if BioGPT-Large fails"""
            print("Loading fallback model: microsoft/BioGPT")
            model_name = "microsoft/BioGPT"
            self.model = AutoModelForCausalLM.from_pretrained(model_name).to(self.device)
            self.tokenizer = AutoTokenizer.from_pretrained(model_name)
            print("โœ… Fallback BioGPT model loaded.")

        def test_biogpt(self):
            """Test BioGPT with a simple medical query"""
            print("๐Ÿงช Testing BioGPT...")
            if not self.model or not self.tokenizer:
                print("โš ๏ธ BioGPT not properly initialized.")
                return

            input_text = "What are symptoms of measles?"
            inputs = self.tokenizer(input_text, return_tensors="pt").to(self.device)
            outputs = self.model.generate(**inputs, max_new_tokens=50)
            response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
            print(f"๐Ÿ’ฌ Test response: {response}")


        def load_medical_data(self, file_path):
            """Load and process medical data with progress tracking"""
            print(f"๐Ÿ“– Loading medical data from {file_path}...")

            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    text = f.read()
                print(f"๐Ÿ“„ File loaded: {len(text):,} characters")
            except FileNotFoundError:
                print(f"โŒ File {file_path} not found!")
                raise ValueError("Failed to load medical data")

            # Create chunks optimized for medical content
            print("๐Ÿ“ Creating medical-optimized chunks...")
            chunks = self.create_medical_chunks(text)
            print(f"๐Ÿ“‹ Created {len(chunks)} medical chunks")

            self.knowledge_chunks = chunks

            # Generate embeddings with progress and add to FAISS index
            if self.use_embeddings and self.embedding_model and self.faiss_ready:
                return self.generate_embeddings_with_progress(chunks)

            print("โœ… Medical data loaded (text search mode)")
            return

    def create_medical_chunks(self, text: str, chunk_size: int = 400) -> List[Dict]:
        """Create medically-optimized text chunks"""
        chunks = []

        # Split by medical sections first
        medical_sections = self.split_by_medical_sections(text)

        chunk_id = 0
        for section in medical_sections:
            if len(section.split()) > chunk_size:
                # Split large sections by sentences
                sentences = re.split(r'[.!?]+', section)
                current_chunk = ""

                for sentence in sentences:
                    sentence = sentence.strip()
                    if not sentence:
                        continue

                    if len(current_chunk.split()) + len(sentence.split()) < chunk_size:
                        current_chunk += sentence + ". "
                    else:
                        if current_chunk.strip():
                            chunks.append({
                                'id': chunk_id,
                                'text': current_chunk.strip(),
                                'medical_focus': self.identify_medical_focus(current_chunk)
                            })
                            chunk_id += 1
                        current_chunk = sentence + ". "

                if current_chunk.strip():
                    chunks.append({
                        'id': chunk_id,
                        'text': current_chunk.strip(),
                        'medical_focus': self.identify_medical_focus(current_chunk)
                    })
                    chunk_id += 1
            else:
                chunks.append({
                    'id': chunk_id,
                    'text': section,
                    'medical_focus': self.identify_medical_focus(section)
                })
                chunk_id += 1

        return chunks

    def split_by_medical_sections(self, text: str) -> List[str]:
        """Split text by medical sections"""
        # Look for medical section headers
        section_patterns = [
            r'\n\s*(?:SYMPTOMS?|TREATMENT|DIAGNOSIS|CAUSES?|PREVENTION|MANAGEMENT).*?\n',
            r'\n\s*\d+\.\s+',  # Numbered sections
            r'\n\n+'  # Paragraph breaks
        ]

        sections = [text]
        for pattern in section_patterns:
            new_sections = []
            for section in sections:
                splits = re.split(pattern, section, flags=re.IGNORECASE)
                new_sections.extend([s.strip() for s in splits if len(s.strip()) > 100])
            sections = new_sections

        return sections

    def identify_medical_focus(self, text: str) -> str:
        """Identify the medical focus of a text chunk"""
        text_lower = text.lower()

        # Medical categories
        categories = {
            'pediatric_symptoms': ['fever', 'cough', 'rash', 'vomiting', 'diarrhea'],
            'treatments': ['treatment', 'therapy', 'medication', 'antibiotics'],
            'diagnosis': ['diagnosis', 'diagnostic', 'symptoms', 'signs'],
            'emergency': ['emergency', 'urgent', 'serious', 'hospital'],
            'prevention': ['prevention', 'vaccine', 'immunization', 'avoid']
        }

        for category, keywords in categories.items():
            if any(keyword in text_lower for keyword in keywords):
                return category

        return 'general_medical'

    def generate_embeddings_with_progress(self, chunks: List[Dict]) -> bool:
        """Generate embeddings with progress tracking and add to FAISS index"""
        print("๐Ÿ”ฎ Generating medical embeddings and adding to FAISS index...")

        if not self.embedding_model or not self.faiss_index:
            print("โŒ Embedding model or FAISS index not available.")
            raise ValueError("Failed to load medical data")

        try:
            texts = [chunk['text'] for chunk in chunks]

            # Generate embeddings in batches with progress
            batch_size = 32
            all_embeddings = []

            for i in range(0, len(texts), batch_size):
                batch_texts = texts[i:i+batch_size]
                batch_embeddings = self.embedding_model.encode(batch_texts, show_progress_bar=False)
                all_embeddings.extend(batch_embeddings)

                # Show progress
                progress = min(i + batch_size, len(texts))
                print(f"   Progress: {progress}/{len(texts)} chunks processed", end='\r')

            print(f"\n   โœ… Generated embeddings for {len(texts)} chunks")

            # Add embeddings to FAISS index
            print("๐Ÿ’พ Adding embeddings to FAISS index...")
            self.faiss_index.add(np.array(all_embeddings))

            print("โœ… Medical embeddings added to FAISS index successfully!")
            return True

        except Exception as e:
            print(f"โŒ Embedding generation or FAISS add failed: {e}")
            raise ValueError("Failed to load medical data")


    def retrieve_medical_context(self, query: str, n_results: int = 3) -> List[str]:
        """Retrieve relevant medical context using embeddings or keyword search"""
        if self.use_embeddings and self.embedding_model and self.faiss_ready:
            try:
                # Generate query embedding
                query_embedding = self.embedding_model.encode([query])

                # Search for similar content in FAISS index
                distances, indices = self.faiss_index.search(np.array(query_embedding), n_results)

                # Retrieve the corresponding chunks
                context_chunks = [self.knowledge_chunks[i]['text'] for i in indices[0] if i != -1]

                if context_chunks:
                    return context_chunks

            except Exception as e:
                print(f"โš ๏ธ Embedding search failed: {e}")

        # Fallback to keyword search
        print("โš ๏ธ Falling back to keyword search.")
        return self.keyword_search_medical(query, n_results)


    def keyword_search_medical(self, query: str, n_results: int) -> List[str]:
        """Medical-focused keyword search"""
        if not self.knowledge_chunks:
            return []

        query_words = set(query.lower().split())
        chunk_scores = []

        for chunk_info in self.knowledge_chunks:
            chunk_text = chunk_info['text']
            chunk_words = set(chunk_text.lower().split())

            # Calculate relevance score
            word_overlap = len(query_words.intersection(chunk_words))
            base_score = word_overlap / len(query_words) if query_words else 0

            # Boost medical content
            medical_boost = 0
            if chunk_info.get('medical_focus') in ['pediatric_symptoms', 'treatments', 'diagnosis']:
                medical_boost = 0.5

            final_score = base_score + medical_boost

            if final_score > 0:
                chunk_scores.append((final_score, chunk_text))

        # Return top matches
        chunk_scores.sort(reverse=True)
        return [chunk for _, chunk in chunk_scores[:n_results]]

    def generate_biogpt_response(self, context: str, query: str) -> str:
        """Generate medical response using BioGPT"""
        if not self.model or not self.tokenizer:
            return "Medical model not available. Please check the setup."

        try:
            # Create medical-focused prompt
            prompt = f"""Medical Context: {context[:800]}

Question: {query}

Medical Answer:"""

            # Tokenize input
            inputs = self.tokenizer(
                prompt,
                return_tensors="pt",
                truncation=True,
                max_length=1024
            )

            # Move inputs to the correct device
            if self.device == "cuda":
                inputs = {k: v.to(self.device) for k, v in inputs.items()}

            # Generate response
            with torch.no_grad():
                outputs = self.model.generate(
                    **inputs,
                    max_new_tokens=150,
                    do_sample=True,
                    temperature=0.7,
                    top_p=0.9,
                    pad_token_id=self.tokenizer.eos_token_id,
                    repetition_penalty=1.1
                )

            # Decode response
            full_response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)

            # Extract just the generated part
            if "Medical Answer:" in full_response:
                generated_response = full_response.split("Medical Answer:")[-1].strip()
            else:
                generated_response = full_response[len(prompt):].strip()

            # Clean up response
            cleaned_response = self.clean_medical_response(generated_response)

            return cleaned_response

        except Exception as e:
            print(f"โš ๏ธ BioGPT generation failed: {e}")
            return self.fallback_response(context, query)

    def clean_medical_response(self, response: str) -> str:
        """Clean and format medical response"""
        # Remove incomplete sentences and limit length
        sentences = re.split(r'[.!?]+', response)
        clean_sentences = []

        for sentence in sentences:
            sentence = sentence.strip()
            if len(sentence) > 10 and not sentence.endswith(('and', 'or', 'but', 'however')):
                clean_sentences.append(sentence)
            if len(clean_sentences) >= 3:  # Limit to 3 sentences
                break

        if clean_sentences:
            cleaned = '. '.join(clean_sentences) + '.'
        else:
            cleaned = response[:200] + '...' if len(response) > 200 else response

        return cleaned

    def fallback_response(self, context: str, query: str) -> str:
        """Fallback response when BioGPT fails"""
        # Extract key sentences from context
        sentences = [s.strip() for s in context.split('.') if len(s.strip()) > 20]

        if sentences:
            response = sentences[0] + '.'
            if len(sentences) > 1:
                response += ' ' + sentences[1] + '.'
        else:
            response = context[:300] + '...'

        return response

    def handle_conversational_interactions(self, query: str) -> Optional[str]:
        """Handle comprehensive conversational interactions"""
        query_lower = query.lower().strip()

        # Use more specific patterns for greetings
        greeting_patterns = [
            r'^\s*(hello|hi|hey|hiya|howdy)\s*$',
            r'^\s*(good morning|good afternoon|good evening|good day)\s*$',
            r'^\s*(what\'s up|whats up|sup|yo)\s*$',
            r'^\s*(greetings|salutations)\s*$',
            r'^\s*(how are you|how are you doing|how\'s it going|hows it going)\s*$',
            r'^\s*(good to meet you|nice to meet you|pleased to meet you)\s*$'
        ]

        for pattern in greeting_patterns:
            if re.match(pattern, query_lower):
                responses = [
                    "๐Ÿ‘‹ Hello! I'm BioGPT, your professional medical AI assistant specialized in pediatric medicine. I'm here to provide evidence-based medical information. What health concern can I help you with today?",
                    "๐Ÿฅ Hi there! I'm a medical AI assistant powered by BioGPT, trained on medical literature. I can help answer questions about children's health and medical conditions. How can I assist you?",
                    "๐Ÿ‘‹ Greetings! I'm your AI medical consultant, ready to help with pediatric health questions using the latest medical knowledge. What would you like to know about?"
                ]
                return np.random.choice(responses)

        # ===== THANKS & APPRECIATION =====
        thanks_patterns = [
            ['thank you', 'thanks', 'thx', 'ty', 'thank you so much', 'thanks a lot', 'much appreciated', 'really appreciate it', 'i appreciate it', 'grateful', 'that was helpful', 'very helpful', 'awesome', 'perfect', 'great', 'excellent', 'wonderful', 'that helped', 'exactly what i needed', 'very informative', 'good information']
        ]

        for pattern_group in thanks_patterns:
            if any(keyword in query_lower for keyword in pattern_group):
                responses = [
                    "๐Ÿ™ You're very welcome! I'm glad I could provide helpful medical information. Remember, this is educational guidance - always consult your healthcare provider for personalized medical advice. Feel free to ask more questions!",
                    "๐Ÿ˜Š Happy to help! Providing accurate medical information is what I'm here for. If you have any other pediatric health questions, don't hesitate to ask.",
                    "๐Ÿค— You're most welcome! I'm pleased the medical information was useful. Please remember to consult with healthcare professionals for any medical decisions. What else can I help you with?"
                ]
                return np.random.choice(responses)

        # ===== GOODBYES =====
        goodbye_patterns = [
            ['bye', 'goodbye', 'farewell', 'see you', 'later', 'see ya', 'catch you later', 'talk to you later', 'ttyl', 'have a good day', 'have a great day', 'take care', 'until next time', 'i need to go', 'that\'s all for now', 'no more questions']
        ]

        for pattern_group in goodbye_patterns:
            if any(keyword in query_lower for keyword in pattern_group):
                responses = [
                    "๐Ÿ‘‹ Goodbye! Take excellent care of yourself and your little ones. Remember, I'm here whenever you need reliable pediatric medical information. Stay healthy! ๐Ÿฅ",
                    "๐ŸŒŸ Farewell! Wishing you and your family good health. Don't hesitate to return if you have more medical questions. Take care!",
                    "๐Ÿ‘‹ See you later! Hope the medical information was helpful. Remember to always consult healthcare professionals for medical decisions. Stay well!"
                ]
                return np.random.choice(responses)

        # ===== ABOUT/HELP QUESTIONS =====
        about_patterns = [
            ['what are you', 'who are you', 'tell me about yourself', 'what do you do', 'what can you help with', 'what can you do', 'how can you help', 'what are your capabilities', 'help', 'help me', 'i need help', 'can you help', 'how do i use this', 'how does this work', 'what should i ask']
        ]

        for pattern_group in about_patterns:
            if any(keyword in query_lower for keyword in pattern_group):
                return """๐Ÿค– **About BioGPT Medical Assistant**

I'm an AI medical assistant powered by BioGPT-Large, a specialized medical AI model trained on extensive medical literature. Here's what I can help you with:

๐Ÿฉบ **Medical Specialties:**
โ€ข Pediatric medicine and children's health
โ€ข Symptom explanation and medical conditions
โ€ข Treatment options and medical procedures
โ€ข When to seek medical care
โ€ข Prevention and wellness guidance

๐ŸŽฏ **How to Use Me:**
โ€ข Ask specific medical questions: "What causes fever in children?"
โ€ข Describe symptoms: "My child has a persistent cough"
โ€ข Seek guidance: "When should I call the doctor?"
โ€ข Get information: "How do I treat dehydration?"

โš ๏ธ **Important Reminder:**
I provide educational medical information based on medical literature, but I'm not a substitute for professional medical advice. Always consult qualified healthcare providers for:
โ€ข Medical emergencies
โ€ข Diagnosis and treatment decisions
โ€ข Personalized medical advice
โ€ข Medication guidance

๐Ÿ’ก **Tip:** Be specific in your questions for the most helpful responses!

What pediatric health topic would you like to explore?"""

        # ===== SMALL TALK & PERSONAL QUESTIONS =====
        personal_patterns = [
            ['how are you feeling', 'are you okay', 'how\'s your day', 'are you smart', 'are you intelligent', 'do you know everything', 'are you human', 'are you real', 'are you a robot', 'are you ai', 'you\'re smart', 'you\'re helpful', 'good job', 'well done', 'impressive']
        ]

        for pattern_group in personal_patterns:
            if any(keyword in query_lower for keyword in pattern_group):
                responses = [
                    "๐Ÿค– I'm an AI medical assistant, so I don't have feelings, but I'm functioning well and ready to help with medical questions! My purpose is to provide reliable pediatric health information. What can I help you with?",
                    "๐Ÿ˜Š Thank you for asking! As an AI, I'm always ready to assist with medical information. I'm designed to help with pediatric health questions using evidence-based medical knowledge. How can I help you today?",
                    "๐ŸŽฏ I'm doing what I do best - providing medical information! I'm an AI trained on medical literature to help with pediatric health questions. What medical topic interests you?"
                ]
                return np.random.choice(responses)

        # ===== CONFUSED/UNCLEAR INPUT =====
        confusion_patterns = [
            ['i don\'t know', 'not sure', 'confused', 'unclear', 'help me understand', 'what do you mean', 'i don\'t understand', 'can you explain', 'huh', 'i\'m lost', 'i\'m confused', 'this is confusing']
        ]

        for pattern_group in confusion_patterns:
            if any(keyword in query_lower for keyword in pattern_group):
                return """๐Ÿค” **I understand it can be confusing!** Let me help you get started.

๐Ÿ’ก **Try asking questions like:**

๐Ÿฉบ **Symptoms:**
โ€ข "What causes [symptom] in children?"
โ€ข "My child has [symptom], what should I do?"

๐Ÿ’Š **Treatments:**
โ€ข "How do I treat [condition] in children?"
โ€ข "What are treatment options for [condition]?"

๐Ÿšจ **Urgency:**
โ€ข "When should I call the doctor about [symptom]?"
โ€ข "Is [symptom] serious in children?"

๐Ÿ›ก๏ธ **Prevention:**
โ€ข "How can I prevent [condition]?"
โ€ข "What are the warning signs of [condition]?"

**What specific aspect of your child's health would you like to understand better?**"""

        # ===== APOLOGIES & POLITENESS =====
        polite_patterns = [
            ['sorry', 'excuse me', 'pardon me', 'my apologies', 'please help', 'could you please', 'would you mind', 'if you don\'t mind', 'sorry to bother you']
        ]

        for pattern_group in polite_patterns:
            if any(keyword in query_lower for keyword in pattern_group):
                return "๐Ÿ˜Š No need to apologize! I'm here to help with medical questions. Please feel free to ask anything about pediatric health - that's exactly what I'm designed for. What can I help you with?"

        # ===== TESTING & VERIFICATION =====
        test_patterns = [
            ['test', 'testing', 'hello world', 'can you hear me', 'are you working', 'do you work', 'are you there', 'are you online', 'check', 'verify', 'ping']
        ]

        for pattern_group in test_patterns:
            if any(keyword in query_lower for keyword in pattern_group):
                return "โœ… **System Check:** I'm working perfectly and ready to assist! BioGPT medical AI is online and functioning optimally. Ready to help with pediatric medical questions. What would you like to know?"

        # Return None if no conversational pattern matches
        return None


    def chat(self, query: str) -> str:
        """Main chat function with BioGPT and comprehensive conversational handling"""
        if not query.strip():
            return "Hello! I'm BioGPT, your professional medical AI assistant. How can I help you with pediatric medical questions today?"

        # Handle comprehensive conversational interactions first
        conversational_response = self.handle_conversational_interactions(query)
        if conversational_response:
            # Add to conversation history
            self.conversation_history.append({
                'query': query,
                'response': conversational_response,
                'timestamp': datetime.now().isoformat(),
                'type': 'conversational'
            })
            return conversational_response

        if not self.knowledge_chunks:
            return "Please load medical data first to access the medical knowledge base."

        if not self.model or not self.tokenizer:
            return "Medical model not available. Please check the setup."

        print(f"๐Ÿ” Processing medical query: {query}")

        # Retrieve relevant medical context using FAISS or keyword search
        start_time = time.time()
        context = self.retrieve_medical_context(query)
        retrieval_time = time.time() - start_time

        if not context:
            return "I don't have specific information about this topic in my medical database. Please consult with a healthcare professional for personalized medical advice."

        print(f"   ๐Ÿ“š Context retrieved ({retrieval_time:.2f}s)")

        # Generate response with BioGPT
        start_time = time.time()
        main_context = '\n\n'.join(context)
        response = self.generate_biogpt_response(main_context, query)
        generation_time = time.time() - start_time

        print(f"   ๐Ÿง  Response generated ({generation_time:.2f}s)")

        # Format final response
        final_response = f"๐Ÿฉบ **Medical Information:** {response}\n\nโš ๏ธ **Important:** This information is for educational purposes only. Always consult with qualified healthcare professionals for medical diagnosis, treatment, and personalized advice."

        # Add to conversation history
        self.conversation_history.append({
            'query': query,
            'response': final_response,
            'timestamp': datetime.now().isoformat(),
            'retrieval_time': retrieval_time,
            'generation_time': generation_time,
            'type': 'medical'
        })

        return final_response

    def get_conversation_summary(self) -> Dict:
        """Get conversation statistics"""
        if not self.conversation_history:
            return {"message": "No conversations yet"}

        # Filter medical conversations for performance stats
        medical_conversations = [h for h in self.conversation_history if h.get('type') == 'medical']

        model_info = "BioGPT-Large" if self.model else "Model Not Loaded"

        if not medical_conversations:
            return {
                "total_conversations": len(self.conversation_history),
                "medical_conversations": 0,
                "conversational_interactions": len(self.conversation_history),
                "model_info": model_info,
                "vector_search": "FAISS CPU" if self.faiss_ready else "Keyword Search",
                "device": self.device
            }

        avg_retrieval_time = sum(h.get('retrieval_time', 0) for h in medical_conversations) / len(medical_conversations)
        avg_generation_time = sum(h.get('generation_time', 0) for h in medical_conversations) / len(medical_conversations)

        return {
            "total_conversations": len(self.conversation_history),
            "medical_conversations": len(medical_conversations),
            "conversational_interactions": len(self.conversation_history) - len(medical_conversations),
            "avg_retrieval_time": f"{avg_retrieval_time:.2f}s",
            "avg_generation_time": f"{avg_generation_time:.2f}s",
            "model_info": model_info,
            "vector_search": "FAISS CPU" if self.faiss_ready else "Keyword Search",
            "device": self.device,
            "quantization": "8-bit" if self.use_8bit else "16-bit/32-bit"
        }

# Create and Test BioGPT Chatbot

def create_biogpt_chatbot():
    """Create and initialize the BioGPT chatbot"""
    print("๐Ÿš€ Creating Professional BioGPT Medical Chatbot")
    print("=" * 60)

    # Create chatbot
    chatbot = ColabBioGPTChatbot(use_gpu=True, use_8bit=True)

    return chatbot

def test_biogpt_chatbot(chatbot, test_file='Pediatric_cleaned.txt'):
    """Test the BioGPT chatbot"""
    print("\n๐Ÿ“š Loading medical data...")
    success = chatbot.load_medical_data(test_file)

    if not success:
        print("โŒ Failed to load medical data. Please check the file.")
        return None

    print("\n๐Ÿงช Testing BioGPT Medical Chatbot:")
    print("=" * 50)

    # Test queries
    test_queries = [
        "What causes fever in children?",
        "How should I treat my child's cough?",
        "When should I be concerned about my baby's breathing?",
        "What are the signs of dehydration in infants?"
    ]

    for i, query in enumerate(test_queries, 1):
        print(f"\n{i}๏ธโƒฃ Testing: {query}")
        print("-" * 40)

        response = chatbot.chat(query)
        print(f"๐Ÿค– BioGPT Response:\n{response}")
        print("=" * 50)

    # Show conversation summary
    summary = chatbot.get_conversation_summary()
    print("\n๐Ÿ“Š Performance Summary:")
    for key, value in summary.items():
        print(f"   {key}: {value}")

    return chatbot

# Interactive Chat Interface

def interactive_biogpt_chat(chatbot):
    """Interactive chat with BioGPT"""
    print("\n๐Ÿ’ฌ Interactive BioGPT Medical Chat")
    print("=" * 50)
    print("You're now chatting with BioGPT, a professional medical AI!")
    print("Type 'quit' to exit, 'summary' to see stats")
    print("-" * 50)

    while True:
        user_input = input("\n๐Ÿ‘ค You: ").strip()

        if user_input.lower() in ['quit', 'exit', 'bye']:
            print("\n๐Ÿ‘‹ Thank you for using BioGPT Medical Assistant!")
            # Show final summary
            summary = chatbot.get_conversation_summary()
            print("\n๐Ÿ“Š Final Session Summary:")
            for key, value in summary.items():
                print(f"   {key}: {value}")
            break

        elif user_input.lower() == 'summary':
            summary = chatbot.get_conversation_summary()
            print("\n๐Ÿ“Š Current Session Summary:")
            for key, value in summary.items():
                print(f"   {key}: {value}")
            continue

        elif not user_input:
            continue

        print(f"\n๐Ÿค– BioGPT: ", end="")
        response = chatbot.chat(user_input)
        print(response)

# Main Execution - Initialization

import torch

# Create the BioGPT chatbot
chatbot = create_biogpt_chatbot()

print("\n" + "="*60)
print("๐ŸŽฏ NEXT STEPS:")
print("1. Upload your medical data file by running the next cell.")
print("2. Test the chatbot by running the cell after the upload.")
print("3. Start interactive chat by running the final cell.")
print("="*60)