DEV Community

Cover image for Bug Smash: Fixing 5 Critical Bugs in My Django-Groq AI Chatbot
Hafsa Noor Muhammad
Hafsa Noor Muhammad

Posted on

Bug Smash: Fixing 5 Critical Bugs in My Django-Groq AI Chatbot

Summer Bug Smash: Clear the Lineup 🐛🛹

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.

Project Overview

I built a Legal & Wellness AI Chatbot using Django and Groq API (Llama 3.3-70B). The app helps users get information about legal topics and wellness tips with mode switching, PDF document analysis, chat history export, Sentry error monitoring, and Google Gemini AI for sentiment analysis.

Bug Fix or Performance Improvement

I fixed 5 critical bugs in my codebase covering security, logic, performance, and reliability issues.

Code

Bug 1: Duplicate SECRET_KEY (Security)

File: chatbot_project/settings.py

Before:

SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'fallback-key')
# ... some lines ...
SECRET_KEY = 'hardcoded-key'  # ❌ DUPLICATE!
DEBUG = True  # ❌ HARDCODED!
Enter fullscreen mode Exit fullscreen mode

After:

SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'fallback-key')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
Enter fullscreen mode Exit fullscreen mode

Bug 2: Mode Enforcement Bypass (Logic)

File: chatbot/utils.py

Before:

def generate_response(user_input, mode="legal", chat_history=None):
    detected_domain = detect_domain(user_input)
    # ❌ API call happens even if mode is wrong
    response = client.chat.completions.create(...)
    return response
Enter fullscreen mode Exit fullscreen mode

After:

def generate_response(user_input, mode="legal", chat_history=None):
    detected_domain = detect_domain(user_input)

    if mode == "legal" and detected_domain == "wellness":
        return "🌿 I'm in Legal Mode. Please switch to Wellness Mode."

    if mode == "wellness" and detected_domain == "legal":
        return "⚖️ I'm in Wellness Mode. Please switch to Legal Mode."

    if detected_domain == "unknown":
        return "🤔 I'm not sure. Could you please clarify?"

    # ✅ API call only if domain matches mode
    response = client.chat.completions.create(...)
    return response
Enter fullscreen mode Exit fullscreen mode

Screenshots:

Legal Mode rejecting wellness question:
Legal Mode rejecting wellness question

Wellness Mode rejecting legal question:
Wellness Mode rejecting legal question

Bug 3: Session Memory Leak (Performance)

File: chatbot/views.py

Before:

chat_sessions = {}  # ❌ Never cleans up!

def chat(request):
    if session_id not in chat_sessions:
        chat_sessions[session_id] = {'history': [], 'mode': mode}
    # ❌ No expiry, no limit!
Enter fullscreen mode Exit fullscreen mode

After:

import time

def cleanup_old_sessions():
    current_time = time.time()

    # Remove sessions older than 1 hour
    expired_sessions = [
        sid for sid, session in chat_sessions.items()
        if current_time - session.get('last_accessed', 0) > 3600
    ]
    for sid in expired_sessions:
        del chat_sessions[sid]

    # Limit to 100 sessions
    if len(chat_sessions) > 100:
        oldest_key = min(chat_sessions.keys(),
                         key=lambda k: chat_sessions[k].get('last_accessed', 0))
        del chat_sessions[oldest_key]

def chat(request):
    cleanup_old_sessions()

    if session_id not in chat_sessions:
        chat_sessions[session_id] = {
            'history': [],
            'mode': mode,
            'last_accessed': time.time()
        }

    session['last_accessed'] = time.time()
Enter fullscreen mode Exit fullscreen mode

Bug 4: PDF Emoji Export Bug (Reliability)

File: chatbot/views.py

Before:

def remove_emojis(text):
    emoji_pattern = re.compile(
        "["
        u"\U0001F600-\U0001F64F"  # emoticons
        u"\U0001F300-\U0001F5FF"  # symbols & pictographs
        # ❌ Missing many emoji ranges
        "]+",
        flags=re.UNICODE
    )
    return emoji_pattern.sub(r'', text).strip()
Enter fullscreen mode Exit fullscreen mode

After:

def remove_emojis(text):
    emoji_pattern = re.compile(
        "["
        u"\U0001F600-\U0001F64F"  # emoticons
        u"\U0001F300-\U0001F5FF"  # symbols & pictographs
        u"\U0001F680-\U0001F6FF"  # transport & map symbols
        u"\U0001F1E0-\U0001F1FF"  # flags
        u"\U00002702-\U000027B0"  # dingbats
        u"\U000024C2-\U0001F251"  # enclosed characters
        u"\U0001F900-\U0001F9FF"  # supplemental symbols
        u"\U0001FA70-\U0001FAFF"  # symbols extended-a
        u"\U00002600-\U000026FF"   # ✅ NEW: misc symbols
        u"\U00002B50-\U00002BFF"   # ✅ NEW: star/arrows
        u"\U000000A9-\U000000AE"   # ✅ NEW: copyright/trademark
        "]+",
        flags=re.UNICODE
    )

    # Handle common problematic characters
    text = text.replace('', '-')
    text = text.replace('', '*')
    text = text.replace('', '*')
    text = text.replace('', '->')
    text = text.replace('', '...')

    return emoji_pattern.sub(r'', text).strip()
Enter fullscreen mode Exit fullscreen mode

Bug 5: Duplicate PDF Extraction (Performance)

File: chatbot/views.py

Before:

def upload_and_analyze_pdf(request):
    # ❌ EXTRACTION #1: Direct extraction
    pdf_reader = PyPDF2.PdfReader(pdf_file)
    text = ""
    for page in pdf_reader.pages:
        text += page.extract_text() + "\n"
Enter fullscreen mode Exit fullscreen mode

After:

def upload_and_analyze_pdf(request):
    # ✅ Single extraction using helper function
    text = extract_text_from_pdf(pdf_file)

    if not text:
        return JsonResponse({'error': 'Could not extract text from PDF.'}, status=400)
Enter fullscreen mode Exit fullscreen mode

My Improvements

Technical Approach:

  1. Security: Replaced hardcoded SECRET_KEY with environment variable, added fallback for development
  2. Logic: Added early returns for mode mismatches to prevent unnecessary API calls and save costs
  3. Performance: Implemented session cleanup with 1-hour expiry, 100-session limit, and 20-message history
  4. Reliability: Expanded emoji regex pattern to cover all emoji ranges and added special character handling
  5. Code Quality: Replaced duplicate PDF extraction with reusable helper function

Impact:

  • SECRET_KEY now secure and production-ready
  • No wasted API calls when modes mismatch
  • Memory usage bounded and stable
  • PDF export works 100% with all messages
  • CPU usage reduced for PDF processing

Best Use of Sentry

I integrated Sentry to catch errors in real-time using Error Monitoring.

Sentry Middleware:

class SentryErrorMiddleware:
    def process_exception(self, request, exception):
        sentry_sdk.capture_exception(exception)
        sentry_sdk.set_context("request_details", {
            "method": request.method,
            "path": request.path,
            "user": str(request.user) if request.user.is_authenticated else "Anonymous",
        })
        return None
Enter fullscreen mode Exit fullscreen mode

Sentry Configuration:

import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn=os.environ.get("SENTRY_DSN"),
    integrations=[DjangoIntegration()],
    traces_sample_rate=1.0,
    send_default_pii=True,
)
Enter fullscreen mode Exit fullscreen mode

Sentry in Action:
When I triggered a test error, Sentry immediately caught it:

Sentry catching error

What Sentry showed me:

  • Error Type: ZeroDivisionError
  • File: views.py
  • Time: 10 minutes ago

Best Use of Google AI

I used Google Gemini AI to replace the placeholder sentiment analysis.

Before (Placeholder):

def get_sentiment(text):
    return "NEUTRAL", 0.5  # ❌ Always neutral!
Enter fullscreen mode Exit fullscreen mode

After (Gemini AI):

import google.genai as genai

def get_sentiment_with_gemini(text):
    client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
    response = client.models.generate_content(
        model="gemini-1.5-flash",
        contents=f"Analyze sentiment: '{text}'. Return POSITIVE, NEGATIVE, or NEUTRAL"
    )
    return response.text.strip()

def get_sentiment(text):
    if genai_client:
        return get_sentiment_with_gemini(text)
    return "NEUTRAL", 0.5
Enter fullscreen mode Exit fullscreen mode

Impact:

  • Accurate sentiment analysis instead of always returning NEUTRAL
  • Better user experience with fallback to neutral if Gemini fails

View all changes on GitHub

Top comments (0)