Andrew McCracken commited on
Commit
2fb680d
·
1 Parent(s): 7ba4b03

Initial deployment to Spaces

Browse files
Files changed (10) hide show
  1. .dockerignore +22 -0
  2. Dockerfile +37 -0
  3. README.md +29 -5
  4. knowledge_base.py +1069 -0
  5. llm_handler.py +185 -0
  6. main.py +480 -0
  7. monitoring.py +302 -0
  8. optimisations.py +253 -0
  9. requirements.txt +108 -0
  10. test_interface.html +334 -0
.dockerignore ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ *.so
7
+ *.egg
8
+ *.egg-info
9
+ dist
10
+ build
11
+ .env
12
+ .venv
13
+ venv
14
+ env
15
+ .git
16
+ .gitignore
17
+ .idea
18
+ .vscode
19
+ *.db
20
+ knowledge_db
21
+ models
22
+ *.log
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ gcc \
8
+ g++ \
9
+ make \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Copy requirements and install
13
+ COPY requirements.txt .
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+
16
+ # Copy application code
17
+ COPY . .
18
+
19
+ # Create data directory for persistence
20
+ RUN mkdir -p /data
21
+
22
+ # Set environment variables
23
+ ENV PYTHONUNBUFFERED=1
24
+ ENV MODEL_REPO=daskalos-apps/phi4-cybersec-Q4_K_M
25
+ ENV MODEL_FILENAME=phi4-mini-instruct-Q4_K_M.gguf
26
+ ENV USE_RAG=true
27
+ ENV CACHE_ENABLED=true
28
+
29
+ # Expose port
30
+ EXPOSE 8000
31
+
32
+ # Health check
33
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
34
+ CMD python -c "import requests; requests.get('http://localhost:8000/health')"
35
+
36
+ # Run the application
37
+ CMD ["python", "main.py"]
README.md CHANGED
@@ -1,11 +1,35 @@
1
  ---
2
- title: CyberSecChatbot
3
- emoji: 🏢
4
- colorFrom: purple
5
- colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
  license: mit
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Cybersecurity Training Chatbot
3
+ emoji: 🔒
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
+ app_port: 8000
10
  ---
11
 
12
+ # Cybersecurity Training Chatbot
13
+
14
+ An AI-powered cybersecurity training assistant using Microsoft's Phi-4 model.
15
+
16
+ ## Features
17
+
18
+ - 🤖 AI-powered security guidance
19
+ - 💬 Real-time chat interface
20
+ - 📊 Interaction tracking
21
+ - 🔍 RAG-enhanced responses
22
+ - ⚡ Fast CPU inference
23
+
24
+ ## Usage
25
+
26
+ Simply start chatting with the bot about cybersecurity topics!
27
+
28
+ Access the test interface at: `/test`
29
+
30
+ ## API Endpoints
31
+
32
+ - `/test` - Chat interface
33
+ - `/health` - Health check
34
+ - `/docs` - API documentation
35
+ - `/interactions/count` - View total interactions
knowledge_base.py ADDED
@@ -0,0 +1,1069 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from typing import List, Dict, Any, Optional, Generator
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ import hashlib
7
+ import logging
8
+ from datetime import datetime
9
+
10
+ import chromadb
11
+ from chromadb.config import Settings
12
+ from sentence_transformers import SentenceTransformer
13
+ from huggingface_hub import hf_hub_download
14
+
15
+ # Import the base LLM handler
16
+ from llm_handler import CybersecurityLLM
17
+
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ # ================================================
23
+ # Security Knowledge Definitions
24
+ # ================================================
25
+
26
+ class SecurityTopic(Enum):
27
+ PHISHING = "phishing"
28
+ PASSWORDS = "passwords"
29
+ MALWARE = "malware"
30
+ SOCIAL_ENGINEERING = "social_engineering"
31
+ DATA_PROTECTION = "data_protection"
32
+ NETWORK_SECURITY = "network_security"
33
+ INCIDENT_RESPONSE = "incident_response"
34
+ PHYSICAL_SECURITY = "physical_security"
35
+ MOBILE_SECURITY = "mobile_security"
36
+ CLOUD_SECURITY = "cloud_security"
37
+ COMPLIANCE = "compliance"
38
+ EMAIL_SECURITY = "email_security"
39
+ RANSOMWARE = "ransomware"
40
+ ZERO_TRUST = "zero_trust"
41
+ SUPPLY_CHAIN = "supply_chain"
42
+
43
+
44
+ @dataclass
45
+ class SecurityKnowledge:
46
+ topic: SecurityTopic
47
+ title: str
48
+ content: str
49
+ keywords: List[str]
50
+ severity: str # low, medium, high, critical
51
+ last_updated: str = ""
52
+
53
+ def to_dict(self) -> Dict[str, Any]:
54
+ return {
55
+ "topic": self.topic.value,
56
+ "title": self.title,
57
+ "content": self.content,
58
+ "keywords": json.dumps(self.keywords), # Serialize list to JSON string
59
+ "severity": self.severity,
60
+ "last_updated": self.last_updated or datetime.now().isoformat()
61
+ }
62
+
63
+
64
+ # ================================================
65
+ # Main Knowledge Base Class
66
+ # ================================================
67
+
68
+ class CybersecurityKnowledgeBase:
69
+ def __init__(self,
70
+ persist_directory: str = "./knowledge_db",
71
+ embedding_model: str = "all-MiniLM-L6-v2"):
72
+ """
73
+ Initialize knowledge base with vector database
74
+
75
+ Args:
76
+ persist_directory: Directory to persist ChromaDB
77
+ embedding_model: Sentence transformer model for embeddings
78
+ """
79
+
80
+ logger.info(f"Initializing knowledge base at {persist_directory}")
81
+
82
+ # Create directory if it doesn't exist
83
+ os.makedirs(persist_directory, exist_ok=True)
84
+
85
+ # Initialize ChromaDB with persistence
86
+ self.client = chromadb.PersistentClient(
87
+ path=persist_directory,
88
+ settings=Settings(
89
+ anonymized_telemetry=False,
90
+ allow_reset=True
91
+ )
92
+ )
93
+
94
+ # Create or get collection
95
+ try:
96
+ self.collection = self.client.get_collection("cybersecurity_knowledge")
97
+ logger.info(f"Loaded existing collection with {self.collection.count()} documents")
98
+ except:
99
+ self.collection = self.client.create_collection(
100
+ name="cybersecurity_knowledge",
101
+ metadata={"description": "Cybersecurity best practices and knowledge"}
102
+ )
103
+ logger.info("Created new knowledge collection")
104
+
105
+ # Initialize embedder
106
+ logger.info(f"Loading embedding model: {embedding_model}")
107
+ self.embedder = SentenceTransformer(embedding_model)
108
+
109
+ # Load core knowledge if collection is empty
110
+ if self.collection.count() == 0:
111
+ logger.info("Loading core cybersecurity knowledge...")
112
+ self._load_core_knowledge()
113
+
114
+ # Track statistics
115
+ self.stats = {
116
+ "total_documents": self.collection.count(),
117
+ "queries_processed": 0,
118
+ "last_updated": datetime.now().isoformat()
119
+ }
120
+
121
+ def _load_core_knowledge(self):
122
+ """Load comprehensive cybersecurity knowledge"""
123
+
124
+ knowledge_items = [
125
+ # Phishing and Email Security
126
+ SecurityKnowledge(
127
+ topic=SecurityTopic.PHISHING,
128
+ title="Comprehensive Phishing Detection Guide",
129
+ content="""
130
+ IDENTIFYING PHISHING EMAILS - Complete Guide:
131
+
132
+ Red Flags to Watch For:
133
+ • Generic greetings: "Dear Customer" instead of your actual name
134
+ • Urgency tactics: "Act now or your account will be closed!"
135
+ • Grammar/spelling errors: Professional companies proofread their emails
136
+ • Mismatched sender: Display name doesn't match email address
137
+ • Suspicious links: Hover to see if URL matches claimed sender
138
+ • Unexpected attachments: Especially .zip, .exe, .scr, .vbs files
139
+ • Requests for sensitive info: Legitimate companies don't ask for passwords via email
140
+ • Too good to be true: "You've won $1 million!"
141
+ • Emotional manipulation: Fear, greed, curiosity, sympathy
142
+
143
+ How to Verify Suspicious Emails:
144
+ 1. Check sender's email address carefully (not just display name)
145
+ 2. Hover over links WITHOUT clicking to preview destination
146
+ 3. Look for HTTPS and correct domain in links
147
+ 4. Contact company directly through official channels (not email links)
148
+ 5. Check for personalization - legitimate emails often include account numbers
149
+ 6. Verify with IT security team when in doubt
150
+
151
+ What to Do If You Receive Phishing:
152
+ 1. Don't click links or download attachments
153
+ 2. Don't reply or provide any information
154
+ 3. Report to IT security immediately
155
+ 4. Forward to anti-phishing team if available
156
+ 5. Delete the email after reporting
157
+ 6. Warn colleagues if it's widespread
158
+
159
+ If You Clicked a Phishing Link:
160
+ 1. Disconnect from network immediately
161
+ 2. Change passwords from a different device
162
+ 3. Report to IT security IMMEDIATELY
163
+ 4. Run antivirus scan
164
+ 5. Monitor accounts for suspicious activity
165
+ 6. Enable MFA on all accounts if not already done
166
+ """,
167
+ keywords=["phishing", "email", "scam", "suspicious", "link", "attachment", "spear phishing", "whaling",
168
+ "BEC"],
169
+ severity="critical"
170
+ ),
171
+
172
+ # Password Security
173
+ SecurityKnowledge(
174
+ topic=SecurityTopic.PASSWORDS,
175
+ title="Password Security Best Practices",
176
+ content="""
177
+ CREATING STRONG PASSWORDS:
178
+
179
+ Requirements for Strong Passwords:
180
+ • Minimum 12-16 characters (longer is better)
181
+ • Mix of uppercase and lowercase letters
182
+ • Include numbers and special characters (!@#$%^&*)
183
+ • Avoid dictionary words and personal information
184
+ • Unique for every account - never reuse passwords
185
+ • Consider passphrases: 'Coffee@7Makes$Me!Happy2024'
186
+ • Avoid patterns: Password1, Password2, etc.
187
+ • Don't use keyboard patterns: qwerty, 123456
188
+
189
+ Password Management Best Practices:
190
+ • Use a reputable password manager (Bitwarden, 1Password, LastPass)
191
+ • Enable two-factor authentication (2FA) everywhere possible
192
+ • Use authenticator apps over SMS when possible
193
+ • Never share passwords via email, chat, or phone
194
+ • Change passwords immediately if breach suspected
195
+ • Don't write passwords on sticky notes
196
+ • Use different passwords for work and personal accounts
197
+ • Consider using hardware keys for critical accounts
198
+
199
+ Multi-Factor Authentication (MFA):
200
+ • Something you know (password)
201
+ • Something you have (phone, token)
202
+ • Something you are (biometric)
203
+
204
+ Password Manager Benefits:
205
+ • Generate random, unique passwords
206
+ • Securely store all passwords
207
+ • Auto-fill credentials safely
208
+ • Sync across devices
209
+ • Alert you to breaches
210
+ • Share passwords securely when needed
211
+
212
+ Common Password Mistakes:
213
+ • Using personal information (birthdate, pet names)
214
+ • Reusing passwords across sites
215
+ • Sharing passwords with others
216
+ • Using simple substitutions (P@ssw0rd)
217
+ • Not updating default passwords
218
+ • Ignoring breach notifications
219
+ """,
220
+ keywords=["password", "authentication", "2FA", "MFA", "login", "credentials", "passphrase",
221
+ "password manager"],
222
+ severity="critical"
223
+ ),
224
+
225
+ # Malware Prevention
226
+ SecurityKnowledge(
227
+ topic=SecurityTopic.MALWARE,
228
+ title="Malware Prevention and Response",
229
+ content="""
230
+ MALWARE PREVENTION STRATEGIES:
231
+
232
+ Prevention Best Practices:
233
+ • Keep OS and all software updated with latest patches
234
+ • Use reputable antivirus with real-time protection
235
+ • Enable Windows Defender or equivalent
236
+ • Download software only from official sources
237
+ • Verify digital signatures on downloads
238
+ • Scan USB drives before opening files
239
+ • Disable macros in Office documents from unknown sources
240
+ • Use application sandboxing when possible
241
+ • Regular backups following 3-2-1 rule
242
+ • Keep UAC (User Account Control) enabled
243
+
244
+ Types of Malware:
245
+ • Viruses: Self-replicating, attaches to files
246
+ • Worms: Self-spreading through networks
247
+ • Trojans: Disguised as legitimate software
248
+ • Ransomware: Encrypts files for ransom
249
+ • Spyware: Steals information secretly
250
+ • Adware: Displays unwanted advertisements
251
+ • Rootkits: Hides presence from system
252
+ • Keyloggers: Records keystrokes
253
+ • Cryptominers: Uses resources to mine cryptocurrency
254
+
255
+ Warning Signs of Infection:
256
+ • Computer running unusually slow
257
+ • Frequent crashes or blue screens
258
+ • Programs starting automatically
259
+ • Browser homepage changed
260
+ • New toolbars or extensions
261
+ • Excessive pop-ups
262
+ • Files encrypted with ransom note
263
+ • Unusual network activity
264
+ • Disabled security software
265
+ • Missing or modified files
266
+
267
+ If Infected - Immediate Steps:
268
+ 1. Disconnect from all networks (WiFi, Ethernet)
269
+ 2. Enter Safe Mode if possible
270
+ 3. Run full antivirus scan
271
+ 4. Use additional malware removal tools (Malwarebytes)
272
+ 5. Check for system restore points
273
+ 6. Contact IT security team immediately
274
+ 7. Change all passwords from clean device
275
+ 8. Monitor financial accounts
276
+ 9. Consider complete system reinstall for severe infections
277
+ """,
278
+ keywords=["malware", "virus", "ransomware", "trojan", "antivirus", "infection", "worm", "spyware"],
279
+ severity="critical"
280
+ ),
281
+
282
+ # Social Engineering
283
+ SecurityKnowledge(
284
+ topic=SecurityTopic.SOCIAL_ENGINEERING,
285
+ title="Social Engineering Defense Strategies",
286
+ content="""
287
+ DEFENDING AGAINST SOCIAL ENGINEERING:
288
+
289
+ Common Social Engineering Tactics:
290
+ • Pretexting: Creating fake scenarios to steal information
291
+ • Baiting: Offering something enticing (USB drives, downloads)
292
+ • Quid pro quo: Offering service for information
293
+ • Tailgating: Following into secure areas
294
+ • Vishing: Voice phishing via phone
295
+ • Smishing: SMS/text message phishing
296
+ • Watering hole: Compromising frequently visited websites
297
+ • Dumpster diving: Searching trash for information
298
+ • Shoulder surfing: Looking over shoulder for passwords
299
+
300
+ Red Flags to Recognize:
301
+ • Unsolicited contact asking for information
302
+ • Urgency without verification
303
+ • Requests to bypass normal procedures
304
+ • Appeals to authority without proof
305
+ • Offers that seem too good to be true
306
+ • Requests for passwords or sensitive data
307
+ • Emotional manipulation (fear, greed, sympathy)
308
+ • Name dropping without context
309
+ • Resistance to verification
310
+
311
+ Defense Strategies:
312
+ • Always verify identity before sharing information
313
+ • Use callback numbers from official sources
314
+ • Be suspicious of unsolicited contacts
315
+ • Never give passwords over phone/email
316
+ • Question unusual requests, even from "colleagues"
317
+ • Report suspicious behavior immediately
318
+ • Trust but verify - confirm through separate channel
319
+ • Be aware of information you share publicly
320
+ • Secure physical documents and screens
321
+ • Educate family about work-related scams
322
+
323
+ Verification Techniques:
324
+ • Call back on known number
325
+ • Check employee directory
326
+ • Verify with manager
327
+ • Ask for employee ID
328
+ • Request email confirmation
329
+ • Check digital signatures
330
+ • Verify through IT security
331
+ """,
332
+ keywords=["social engineering", "pretexting", "vishing", "smishing", "manipulation", "tailgating",
333
+ "phishing"],
334
+ severity="high"
335
+ ),
336
+
337
+ # Network Security
338
+ SecurityKnowledge(
339
+ topic=SecurityTopic.NETWORK_SECURITY,
340
+ title="Network and WiFi Security Guide",
341
+ content="""
342
+ NETWORK SECURITY BEST PRACTICES:
343
+
344
+ Home WiFi Security:
345
+ • Change default router admin credentials immediately
346
+ • Use WPA3 encryption (WPA2 minimum)
347
+ • Create strong WiFi password (20+ characters)
348
+ • Change default network name (SSID)
349
+ • Disable WPS (WiFi Protected Setup)
350
+ • Keep router firmware updated monthly
351
+ • Use guest network for visitors and IoT devices
352
+ • Disable remote management unless necessary
353
+ • Turn off SSID broadcast if practical
354
+ • Use MAC address filtering for added security
355
+ • Position router centrally to minimize external signal
356
+ • Regular reboot router (monthly)
357
+
358
+ Public WiFi Safety:
359
+ • Avoid accessing sensitive accounts
360
+ • Always use VPN for all connections
361
+ • Verify network name with venue staff
362
+ • Turn off automatic WiFi connection
363
+ • Forget network after use
364
+ • Never accept certificate warnings
365
+ • Disable file sharing
366
+ • Use cellular data for sensitive tasks
367
+ • Keep firewall enabled
368
+ • Use HTTPS websites only
369
+
370
+ VPN Best Practices:
371
+ • Use company-approved VPN only
372
+ • Connect before accessing any resources
373
+ • Keep VPN client updated
374
+ • Report connection issues immediately
375
+ • Don't use free/public VPN services
376
+ • Verify VPN is active before working
377
+
378
+ Network Hygiene:
379
+ • Regular network scans for unknown devices
380
+ • Monitor bandwidth usage
381
+ • Check for unauthorized access points
382
+ • Secure all network equipment physically
383
+ • Document network configuration
384
+ • Regular security audits
385
+ """,
386
+ keywords=["wifi", "network", "router", "VPN", "encryption", "WPA3", "public wifi", "wireless"],
387
+ severity="high"
388
+ ),
389
+
390
+ # Incident Response
391
+ SecurityKnowledge(
392
+ topic=SecurityTopic.INCIDENT_RESPONSE,
393
+ title="Security Incident Response Procedures",
394
+ content="""
395
+ SECURITY INCIDENT RESPONSE GUIDE:
396
+
397
+ IMMEDIATE RESPONSE STEPS:
398
+ 1. STOP - Don't try to fix it yourself
399
+ 2. DISCONNECT - Unplug network cable or disable WiFi
400
+ 3. DOCUMENT - Write down:
401
+ - What happened
402
+ - When it occurred
403
+ - What you were doing
404
+ - Error messages
405
+ - Unusual behavior observed
406
+ 4. REPORT - Contact IT security immediately
407
+ 5. PRESERVE - Don't delete anything, take screenshots
408
+ 6. WAIT - For IT security instructions
409
+
410
+ Types of Incidents Requiring Immediate Reporting:
411
+ • Clicked suspicious link or attachment
412
+ • Entered credentials on suspicious site
413
+ • Lost device with company data
414
+ • Suspicious computer behavior
415
+ • Unauthorized access attempts
416
+ • Data breach or leak discovered
417
+ • Ransomware infection
418
+ • Physical security breach
419
+ • Stolen credentials
420
+ • Suspicious phone calls asking for info
421
+
422
+ Information to Provide:
423
+ • Your name and contact information
424
+ • Time and date of incident
425
+ • Affected systems/accounts
426
+ • Description of what happened
427
+ • Actions taken so far
428
+ • Any error messages (exact wording)
429
+ • Screenshots if possible
430
+ • Anyone else who might be affected
431
+
432
+ DO NOT:
433
+ • Try to fix it yourself
434
+ • Delete or modify evidence
435
+ • Inform unauthorized people
436
+ • Post about it on social media
437
+ • Continue using affected systems
438
+ • Pay ransoms
439
+
440
+ Contact Information:
441
+ IT Security Hotline: [Organization specific]
442
+ Email: security@[organization]
443
+ After hours: [Emergency contact]
444
+ """,
445
+ keywords=["incident", "breach", "response", "report", "emergency", "compromise", "security incident"],
446
+ severity="critical"
447
+ ),
448
+
449
+ # Data Protection
450
+ SecurityKnowledge(
451
+ topic=SecurityTopic.DATA_PROTECTION,
452
+ title="Data Protection and Privacy Guide",
453
+ content="""
454
+ DATA PROTECTION BEST PRACTICES:
455
+
456
+ Data Classification:
457
+ • Public: Can be freely shared
458
+ • Internal: Within organization only
459
+ ��� Confidential: Specific authorized individuals
460
+ • Restricted: Highest sensitivity, strict controls
461
+
462
+ Handling Sensitive Data:
463
+ • Encrypt files before sharing externally
464
+ • Use approved file sharing platforms only
465
+ • Never use personal email for work data
466
+ • Implement clean desk policy
467
+ • Lock computer when stepping away (Win+L or Cmd+Ctrl+Q)
468
+ • Use privacy screens in public spaces
469
+ • Shred physical documents with sensitive info
470
+ • Secure disposal of electronic media
471
+ • Don't discuss sensitive info in public
472
+ • Be aware of smart speakers/devices
473
+
474
+ Encryption Best Practices:
475
+ • Use full disk encryption (BitLocker, FileVault)
476
+ • Encrypt removable media
477
+ • Use encrypted communication channels
478
+ • Encrypt email with sensitive data
479
+ • Password protect sensitive documents
480
+ • Use enterprise encryption tools
481
+ • Store encryption keys securely
482
+
483
+ Data Backup Practices:
484
+ • Follow 3-2-1 rule:
485
+ - 3 copies of important data
486
+ - 2 different storage media
487
+ - 1 offsite backup
488
+ • Test restore procedures regularly
489
+ • Encrypt backup drives
490
+ • Store backups securely
491
+ • Automate where possible
492
+ • Document what's backed up
493
+ • Verify backup integrity
494
+
495
+ Privacy Considerations:
496
+ • Minimize data collection
497
+ • Only share need-to-know basis
498
+ • Regular data audits
499
+ • Respect retention policies
500
+ • Secure data destruction
501
+ • GDPR/CCPA compliance
502
+ """,
503
+ keywords=["data", "encryption", "backup", "confidential", "sensitive", "GDPR", "privacy",
504
+ "classification"],
505
+ severity="high"
506
+ ),
507
+
508
+ # Mobile Security
509
+ SecurityKnowledge(
510
+ topic=SecurityTopic.MOBILE_SECURITY,
511
+ title="Mobile Device Security Guidelines",
512
+ content="""
513
+ MOBILE DEVICE SECURITY:
514
+
515
+ Device Security Settings:
516
+ • Enable screen lock (PIN, password, biometric)
517
+ • Set auto-lock to 1-2 minutes
518
+ • Keep OS and apps updated automatically
519
+ • Download apps only from official stores
520
+ • Review app permissions carefully
521
+ • Enable remote wipe capability
522
+ • Use Find My Device features
523
+ • Encrypt device storage
524
+ • Disable Bluetooth when not needed
525
+ • Turn off WiFi auto-connect
526
+ • Disable Siri/Assistant on lock screen
527
+
528
+ BYOD (Bring Your Own Device) Security:
529
+ • Separate work and personal data
530
+ • Use MDM if required by company
531
+ • Install company security apps
532
+ • Follow company mobile policy
533
+ • Report lost/stolen immediately
534
+ • Don't jailbreak/root devices
535
+ • Use company VPN for work
536
+ • Regular security updates
537
+
538
+ Mobile Threats:
539
+ • Malicious apps
540
+ • Unsecured WiFi
541
+ • SMiShing (SMS phishing)
542
+ • Bluetooth attacks
543
+ • Physical theft
544
+ • Shoulder surfing
545
+ • Juice jacking (USB charging)
546
+ • SIM swapping
547
+
548
+ Safe Mobile Practices:
549
+ • Avoid public WiFi for sensitive tasks
550
+ • Use VPN when on public networks
551
+ • Don't click links in text messages
552
+ • Be cautious with QR codes
553
+ • Use official app stores only
554
+ • Keep personal info private
555
+ • Regular app permission audits
556
+ • Backup device regularly
557
+ • Use mobile antivirus
558
+ • Avoid charging at public USB ports
559
+ """,
560
+ keywords=["mobile", "smartphone", "tablet", "BYOD", "iOS", "Android", "app security", "MDM"],
561
+ severity="medium"
562
+ ),
563
+
564
+ # Ransomware Specific
565
+ SecurityKnowledge(
566
+ topic=SecurityTopic.RANSOMWARE,
567
+ title="Ransomware Prevention and Response",
568
+ content="""
569
+ RANSOMWARE PROTECTION GUIDE:
570
+
571
+ Prevention Strategies:
572
+ • Regular automated backups (tested restores)
573
+ • Keep all software patched and updated
574
+ • Email filtering and sandboxing
575
+ • Disable macros by default
576
+ • User training on phishing
577
+ • Network segmentation
578
+ • Principle of least privilege
579
+ • Application whitelisting
580
+ • Endpoint detection and response (EDR)
581
+
582
+ If Ransomware Strikes:
583
+ 1. Immediately disconnect from network
584
+ 2. Power off if actively encrypting
585
+ 3. Report to IT security immediately
586
+ 4. Do NOT pay ransom
587
+ 5. Preserve evidence for investigation
588
+ 6. Check for decryption tools
589
+ 7. Restore from clean backups
590
+ 8. Rebuild affected systems
591
+ 9. Investigate root cause
592
+ 10. Implement lessons learned
593
+
594
+ Warning Signs:
595
+ • Files with strange extensions
596
+ • Cannot open documents
597
+ • Ransom notes in folders
598
+ • Slow computer performance
599
+ • Renamed files
600
+ • Wallpaper changed to ransom message
601
+
602
+ Recovery Planning:
603
+ • Maintain offline backups
604
+ • Test restore procedures
605
+ • Document critical systems
606
+ • Incident response plan
607
+ • Communication plan
608
+ • Legal/law enforcement contacts
609
+ """,
610
+ keywords=["ransomware", "encryption", "ransom", "backup", "recovery", "bitcoin", "crypto"],
611
+ severity="critical"
612
+ ),
613
+
614
+ # Cloud Security
615
+ SecurityKnowledge(
616
+ topic=SecurityTopic.CLOUD_SECURITY,
617
+ title="Cloud Services Security",
618
+ content="""
619
+ CLOUD SECURITY BEST PRACTICES:
620
+
621
+ Account Security:
622
+ • Use strong, unique passwords
623
+ • Enable MFA on all cloud accounts
624
+ • Regular access reviews
625
+ • Monitor for unusual activity
626
+ • Use SSO where available
627
+ • Secure API keys and tokens
628
+
629
+ Data Protection in Cloud:
630
+ • Understand shared responsibility model
631
+ • Encrypt data at rest and in transit
632
+ • Use cloud provider encryption
633
+ • Control data residency
634
+ • Regular security audits
635
+ • Implement DLP policies
636
+
637
+ Safe Cloud Usage:
638
+ • Only use approved cloud services
639
+ • Read terms of service
640
+ • Understand data ownership
641
+ • Configure privacy settings
642
+ • Regular permission reviews
643
+ • Monitor shared links
644
+ • Set expiration on shares
645
+ • Audit access logs
646
+
647
+ Common Cloud Risks:
648
+ • Misconfigured storage buckets
649
+ • Excessive permissions
650
+ • Shadow IT
651
+ • Account takeover
652
+ • Data leakage
653
+ • Compliance violations
654
+ • Insider threats
655
+ • API vulnerabilities
656
+ """,
657
+ keywords=["cloud", "SaaS", "AWS", "Azure", "Google Cloud", "OneDrive", "Dropbox", "Office 365"],
658
+ severity="high"
659
+ )
660
+ ]
661
+
662
+ # Add all knowledge items to vector database
663
+ batch_size = 10
664
+ for i in range(0, len(knowledge_items), batch_size):
665
+ batch = knowledge_items[i:i + batch_size]
666
+
667
+ embeddings = []
668
+ documents = []
669
+ metadatas = []
670
+ ids = []
671
+
672
+ for item in batch:
673
+ # Generate embedding
674
+ embedding = self.embedder.encode(item.content).tolist()
675
+ embeddings.append(embedding)
676
+
677
+ # Prepare document
678
+ documents.append(item.content)
679
+
680
+ # Prepare metadata
681
+ metadatas.append(item.to_dict())
682
+
683
+ # Generate unique ID
684
+ doc_id = hashlib.md5(
685
+ f"{item.topic.value}_{item.title}_{len(item.content)}".encode()
686
+ ).hexdigest()
687
+ ids.append(doc_id)
688
+
689
+ # Add batch to collection
690
+ self.collection.add(
691
+ embeddings=embeddings,
692
+ documents=documents,
693
+ metadatas=metadatas,
694
+ ids=ids
695
+ )
696
+
697
+ logger.info(f"Added batch {i // batch_size + 1} of knowledge items")
698
+
699
+ logger.info(f"Successfully loaded {len(knowledge_items)} knowledge items")
700
+
701
+ def search(self,
702
+ query: str,
703
+ k: int = 3,
704
+ filter_topic: Optional[str] = None,
705
+ min_severity: Optional[str] = None) -> List[Dict[str, Any]]:
706
+ """
707
+ Search for relevant security information
708
+
709
+ Args:
710
+ query: User's question
711
+ k: Number of results to return
712
+ filter_topic: Optional topic filter
713
+ min_severity: Minimum severity level filter
714
+
715
+ Returns:
716
+ List of relevant documents with metadata
717
+ """
718
+
719
+ self.stats["queries_processed"] += 1
720
+
721
+ # Generate query embedding
722
+ query_embedding = self.embedder.encode(query).tolist()
723
+
724
+ # Build filter
725
+ where_filter = {}
726
+ if filter_topic:
727
+ where_filter["topic"] = filter_topic
728
+ if min_severity:
729
+ severity_levels = ["low", "medium", "high", "critical"]
730
+ min_index = severity_levels.index(min_severity)
731
+ valid_severities = severity_levels[min_index:]
732
+ where_filter["severity"] = {"$in": valid_severities}
733
+
734
+ # Search with or without filter
735
+ if where_filter:
736
+ results = self.collection.query(
737
+ query_embeddings=[query_embedding],
738
+ n_results=k,
739
+ where=where_filter
740
+ )
741
+ else:
742
+ results = self.collection.query(
743
+ query_embeddings=[query_embedding],
744
+ n_results=k
745
+ )
746
+
747
+ # Format results
748
+ formatted_results = []
749
+ if results['documents'] and results['documents'][0]:
750
+ for doc, metadata, distance in zip(
751
+ results['documents'][0],
752
+ results['metadatas'][0],
753
+ results['distances'][0]
754
+ ):
755
+ formatted_results.append({
756
+ 'content': doc,
757
+ 'topic': metadata.get('topic', 'unknown'),
758
+ 'title': metadata.get('title', 'Untitled'),
759
+ 'severity': metadata.get('severity', 'medium'),
760
+ 'keywords': json.loads(metadata.get('keywords', '[]')), # Deserialize JSON string back to list
761
+ 'relevance_score': 1 - (distance / 2) # Convert distance to similarity
762
+ })
763
+
764
+ return formatted_results
765
+
766
+ def add_custom_knowledge(self,
767
+ content: str,
768
+ topic: str,
769
+ title: str,
770
+ keywords: List[str],
771
+ severity: str = "medium") -> bool:
772
+ """
773
+ Add custom security knowledge to the database
774
+
775
+ Args:
776
+ content: Knowledge content
777
+ topic: Topic category
778
+ title: Title of the knowledge
779
+ keywords: Related keywords
780
+ severity: Severity level
781
+
782
+ Returns:
783
+ Success status
784
+ """
785
+
786
+ try:
787
+ # Generate embedding
788
+ embedding = self.embedder.encode(content).tolist()
789
+
790
+ # Generate unique ID
791
+ doc_id = hashlib.md5(
792
+ f"{topic}_{title}_{len(content)}_{datetime.now().isoformat()}".encode()
793
+ ).hexdigest()
794
+
795
+ # Add to collection
796
+ self.collection.add(
797
+ embeddings=[embedding],
798
+ documents=[content],
799
+ metadatas=[{
800
+ "topic": topic,
801
+ "title": title,
802
+ "keywords": json.dumps(keywords), # Serialize list to JSON string
803
+ "severity": severity,
804
+ "last_updated": datetime.now().isoformat(),
805
+ "custom": True
806
+ }],
807
+ ids=[doc_id]
808
+ )
809
+
810
+ self.stats["total_documents"] = self.collection.count()
811
+ logger.info(f"Added custom knowledge: {title}")
812
+ return True
813
+
814
+ except Exception as e:
815
+ logger.error(f"Failed to add custom knowledge: {e}")
816
+ return False
817
+
818
+ def get_statistics(self) -> Dict[str, Any]:
819
+ """Get knowledge base statistics"""
820
+
821
+ self.stats["total_documents"] = self.collection.count()
822
+ self.stats["last_accessed"] = datetime.now().isoformat()
823
+
824
+ # Get topic distribution
825
+ all_metadata = self.collection.get()['metadatas']
826
+ topic_counts = {}
827
+ severity_counts = {}
828
+
829
+ for metadata in all_metadata:
830
+ topic = metadata.get('topic', 'unknown')
831
+ severity = metadata.get('severity', 'unknown')
832
+
833
+ topic_counts[topic] = topic_counts.get(topic, 0) + 1
834
+ severity_counts[severity] = severity_counts.get(severity, 0) + 1
835
+
836
+ self.stats["topic_distribution"] = topic_counts
837
+ self.stats["severity_distribution"] = severity_counts
838
+
839
+ return self.stats
840
+
841
+ def export_knowledge(self, output_file: str = "knowledge_export.json") -> bool:
842
+ """Export all knowledge to JSON file"""
843
+
844
+ try:
845
+ all_data = self.collection.get()
846
+
847
+ export_data = {
848
+ "exported_at": datetime.now().isoformat(),
849
+ "total_documents": len(all_data['ids']),
850
+ "documents": []
851
+ }
852
+
853
+ for doc, metadata, doc_id in zip(
854
+ all_data['documents'],
855
+ all_data['metadatas'],
856
+ all_data['ids']
857
+ ):
858
+ export_data["documents"].append({
859
+ "id": doc_id,
860
+ "content": doc,
861
+ "metadata": metadata
862
+ })
863
+
864
+ with open(output_file, 'w') as f:
865
+ json.dump(export_data, f, indent=2)
866
+
867
+ logger.info(f"Exported knowledge to {output_file}")
868
+ return True
869
+
870
+ except Exception as e:
871
+ logger.error(f"Failed to export knowledge: {e}")
872
+ return False
873
+
874
+
875
+ # ================================================
876
+ # RAG-Enhanced LLM Class
877
+ # ================================================
878
+
879
+ class RAGCybersecurityLLM(CybersecurityLLM):
880
+ def __init__(self,
881
+ repo_id: str = "daskalos-apps/phi4-cybersec-Q4_K_M",
882
+ filename: str = "phi4-mini-instruct-Q4_K_M.gguf",
883
+ local_dir: str = "./models",
884
+ knowledge_dir: str = "./knowledge_db",
885
+ force_download: bool = False):
886
+ """
887
+ Initialize LLM with RAG capabilities
888
+
889
+ Args:
890
+ repo_id: Hugging Face repository ID
891
+ filename: Model filename
892
+ local_dir: Local cache directory
893
+ knowledge_dir: Knowledge base directory
894
+ force_download: Force model re-download
895
+ """
896
+
897
+ # Initialize base LLM
898
+ super().__init__(repo_id, filename, local_dir, force_download)
899
+
900
+ # Initialize knowledge base
901
+ logger.info("Initializing RAG knowledge base...")
902
+ self.knowledge_base = CybersecurityKnowledgeBase(persist_directory=knowledge_dir)
903
+
904
+ # Enhanced system prompt for RAG
905
+ self.rag_prompt_template = """<|system|>
906
+ {system}
907
+
908
+ You have access to a comprehensive cybersecurity knowledge base. Use the provided context to give accurate, detailed answers. If the context doesn't contain relevant information, use your general knowledge but indicate when you're doing so.
909
+ <|end|>
910
+ <|user|>
911
+ Context from knowledge base:
912
+ {context}
913
+
914
+ User Question: {user}
915
+ <|end|>
916
+ <|assistant|>"""
917
+
918
+ def generate_with_rag(self,
919
+ prompt: str,
920
+ max_tokens: int = 512,
921
+ use_rag: bool = True,
922
+ k_documents: int = 3,
923
+ min_relevance: float = 0.5) -> Dict[str, Any]:
924
+ """
925
+ Generate response with RAG enhancement
926
+
927
+ Args:
928
+ prompt: User's question
929
+ max_tokens: Maximum response length
930
+ use_rag: Whether to use RAG
931
+ k_documents: Number of documents to retrieve
932
+ min_relevance: Minimum relevance threshold
933
+
934
+ Returns:
935
+ Response with metadata and sources
936
+ """
937
+
938
+ context = None
939
+ sources = []
940
+
941
+ if use_rag:
942
+ # Search knowledge base
943
+ logger.info(f"Searching knowledge base for: {prompt[:50]}...")
944
+ relevant_docs = self.knowledge_base.search(prompt, k=k_documents)
945
+
946
+ # Filter by relevance
947
+ relevant_docs = [
948
+ doc for doc in relevant_docs
949
+ if doc.get('relevance_score', 0) >= min_relevance
950
+ ]
951
+
952
+ if relevant_docs:
953
+ # Build context from relevant documents
954
+ context_parts = []
955
+ for i, doc in enumerate(relevant_docs, 1):
956
+ context_parts.append(
957
+ f"[Source {i}: {doc['title']} - Severity: {doc['severity']}]\n"
958
+ f"{doc['content'][:1000]}..." # Limit context length
959
+ )
960
+ sources.append({
961
+ "title": doc['title'],
962
+ "topic": doc['topic'],
963
+ "severity": doc['severity'],
964
+ "relevance": doc['relevance_score']
965
+ })
966
+
967
+ context = "\n\n".join(context_parts)
968
+ logger.info(f"Found {len(relevant_docs)} relevant documents")
969
+ else:
970
+ logger.info("No highly relevant documents found")
971
+
972
+ # Generate response
973
+ if context and use_rag:
974
+ # Use RAG prompt template
975
+ full_prompt = self.rag_prompt_template.format(
976
+ system=self.system_prompt,
977
+ context=context,
978
+ user=prompt
979
+ )
980
+ else:
981
+ # Use standard prompt template
982
+ full_prompt = self.format_prompt(prompt)
983
+
984
+ try:
985
+ response = self.llm(
986
+ full_prompt,
987
+ max_tokens=max_tokens,
988
+ temperature=0.7,
989
+ top_p=0.95,
990
+ top_k=40,
991
+ repeat_penalty=1.1,
992
+ stop=self.stop_tokens,
993
+ echo=False
994
+ )
995
+
996
+ text = response['choices'][0]['text'].strip()
997
+
998
+ return {
999
+ "response": text,
1000
+ "tokens_used": response['usage']['total_tokens'],
1001
+ "model": self.model_info['repo_id'],
1002
+ "sources": sources,
1003
+ "rag_used": use_rag and bool(context)
1004
+ }
1005
+
1006
+ except Exception as e:
1007
+ logger.error(f"Generation error: {e}")
1008
+ return {
1009
+ "response": "I apologize, but I encountered an error. Please try rephrasing your question.",
1010
+ "error": str(e),
1011
+ "sources": [],
1012
+ "rag_used": False
1013
+ }
1014
+
1015
+ def generate_stream_with_rag(self,
1016
+ prompt: str,
1017
+ max_tokens: int = 512,
1018
+ use_rag: bool = True,
1019
+ k_documents: int = 3) -> Generator:
1020
+ """Stream response with RAG enhancement"""
1021
+
1022
+ # Get context if using RAG
1023
+ context = None
1024
+ if use_rag:
1025
+ relevant_docs = self.knowledge_base.search(prompt, k=k_documents)
1026
+ if relevant_docs:
1027
+ context_parts = [f"{doc['title']}: {doc['content'][:500]}" for doc in relevant_docs]
1028
+ context = "\n\n".join(context_parts)
1029
+
1030
+ # Generate prompt
1031
+ if context:
1032
+ full_prompt = self.rag_prompt_template.format(
1033
+ system=self.system_prompt,
1034
+ context=context,
1035
+ user=prompt
1036
+ )
1037
+ else:
1038
+ full_prompt = self.format_prompt(prompt)
1039
+
1040
+ # Stream response
1041
+ stream = self.llm(
1042
+ full_prompt,
1043
+ max_tokens=max_tokens,
1044
+ temperature=0.7,
1045
+ top_p=0.95,
1046
+ top_k=40,
1047
+ repeat_penalty=1.1,
1048
+ stop=self.stop_tokens,
1049
+ echo=False,
1050
+ stream=True
1051
+ )
1052
+
1053
+ for output in stream:
1054
+ token = output['choices'][0].get('text', '')
1055
+ if token:
1056
+ yield token
1057
+
1058
+ def add_knowledge(self, content: str, topic: str, title: str, keywords: List[str]) -> bool:
1059
+ """Add new knowledge to the RAG system"""
1060
+ return self.knowledge_base.add_custom_knowledge(
1061
+ content=content,
1062
+ topic=topic,
1063
+ title=title,
1064
+ keywords=keywords
1065
+ )
1066
+
1067
+ def get_knowledge_stats(self) -> Dict[str, Any]:
1068
+ """Get knowledge base statistics"""
1069
+ return self.knowledge_base.get_statistics()
llm_handler.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from llama_cpp import Llama
2
+ from typing import Generator, Optional, Dict, Any
3
+ import logging
4
+ import os
5
+ from huggingface_hub import hf_hub_download
6
+ import hashlib
7
+
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class CybersecurityLLM:
13
+ def __init__(self,
14
+ repo_id: str = "daskalos-apps/phi4-cybersec-Q4_K_M",
15
+ filename: str = "phi4-mini-instruct-Q4_K_M.gguf",
16
+ local_dir: str = "./models",
17
+ force_download: bool = False):
18
+ """
19
+ Initialize Phi-4 from Hugging Face
20
+
21
+ Args:
22
+ repo_id: Your Hugging Face repository ID
23
+ filename: The GGUF filename in the repository
24
+ local_dir: Local directory to cache the model
25
+ force_download: Force re-download even if cached
26
+ """
27
+
28
+ # Create local directory if it doesn't exist
29
+ os.makedirs(local_dir, exist_ok=True)
30
+
31
+ # Download model from Hugging Face
32
+ logger.info(f"Loading model from Hugging Face: {repo_id}")
33
+
34
+ try:
35
+ model_path = hf_hub_download(
36
+ repo_id=repo_id,
37
+ filename=filename,
38
+ local_dir=local_dir,
39
+ local_dir_use_symlinks=False,
40
+ force_download=force_download
41
+ )
42
+ logger.info(f"Model downloaded/cached at: {model_path}")
43
+ except Exception as e:
44
+ logger.error(f"Failed to download model: {e}")
45
+ # Fallback to local file if exists
46
+ model_path = os.path.join(local_dir, filename)
47
+ if not os.path.exists(model_path):
48
+ raise FileNotFoundError(f"Model not found locally or on Hugging Face: {repo_id}")
49
+
50
+ # Initialize llama.cpp with the model
51
+ logger.info("Initializing model...")
52
+ self.llm = Llama(
53
+ model_path=model_path,
54
+ n_ctx=4096, # Context window
55
+ n_batch=512, # Batch size for prompt processing
56
+ n_threads=8, # Adjust based on CPU cores
57
+ n_gpu_layers=0, # CPU only
58
+ seed=-1, # Random seed
59
+ f16_kv=True, # Use f16 for key/value cache
60
+ logits_all=False, # Only compute logits for last token
61
+ vocab_only=False, # Load full model
62
+ use_mmap=True, # Memory-map model for efficiency
63
+ use_mlock=False, # Don't lock model in RAM
64
+ verbose=False
65
+ )
66
+
67
+ # Store model info
68
+ self.model_info = {
69
+ "repo_id": repo_id,
70
+ "filename": filename,
71
+ "path": model_path,
72
+ "size_mb": os.path.getsize(model_path) / (1024 * 1024)
73
+ }
74
+
75
+ # Cybersecurity-focused system prompt
76
+ self.system_prompt = """You are a cybersecurity expert assistant helping employees understand and implement security best practices. Your role is to provide clear, actionable guidance that non-technical users can understand and apply.
77
+
78
+ Core expertise areas:
79
+ • Email Security & Phishing Detection
80
+ • Password Management & Authentication
81
+ • Malware Prevention & Detection
82
+ • Safe Browsing & Download Practices
83
+ • Data Protection & Encryption
84
+ • Social Engineering Defense
85
+ • Remote Work Security
86
+ • Incident Response & Reporting
87
+ • Physical Security
88
+ • Mobile Device Security
89
+ • Cloud Security Basics
90
+ • Compliance Basics (GDPR, HIPAA, etc.)
91
+
92
+ Guidelines:
93
+ - Always prioritize user safety and security
94
+ - Provide step-by-step instructions when applicable
95
+ - Use simple language, avoid excessive jargon
96
+ - Include real-world examples
97
+ - Emphasize prevention over remediation
98
+ - Never ask users to disable security features
99
+ - If unsure, recommend consulting IT security team"""
100
+
101
+ # Phi-4 uses ChatML format
102
+ self.prompt_template = """<|system|>
103
+ {system}<|end|>
104
+ <|user|>
105
+ {user}<|end|>
106
+ <|assistant|>"""
107
+
108
+ self.stop_tokens = ["<|end|>", "<|user|>", "<|endoftext|>", "<|assistant|>"]
109
+
110
+ logger.info(f"Model ready! Size: {self.model_info['size_mb']:.2f} MB")
111
+
112
+ def format_prompt(self, user_input: str, context: Optional[str] = None) -> str:
113
+ """Format prompt with optional context for RAG"""
114
+ if context:
115
+ user_input = f"Context: {context}\n\nQuestion: {user_input}"
116
+
117
+ return self.prompt_template.format(
118
+ system=self.system_prompt,
119
+ user=user_input
120
+ )
121
+
122
+ def generate(self,
123
+ prompt: str,
124
+ max_tokens: int = 512,
125
+ temperature: float = 0.7,
126
+ context: Optional[str] = None) -> Dict[str, Any]:
127
+ """Generate response with metadata"""
128
+
129
+ full_prompt = self.format_prompt(prompt, context)
130
+
131
+ try:
132
+ response = self.llm(
133
+ full_prompt,
134
+ max_tokens=max_tokens,
135
+ temperature=temperature,
136
+ top_p=0.95,
137
+ top_k=40,
138
+ repeat_penalty=1.1,
139
+ stop=self.stop_tokens,
140
+ echo=False
141
+ )
142
+
143
+ text = response['choices'][0]['text'].strip()
144
+
145
+ return {
146
+ "response": text,
147
+ "tokens_used": response['usage']['total_tokens'],
148
+ "model": self.model_info['repo_id']
149
+ }
150
+
151
+ except Exception as e:
152
+ logger.error(f"Generation error: {e}")
153
+ return {
154
+ "response": "I apologize, but I encountered an error. Please try rephrasing your question.",
155
+ "error": str(e)
156
+ }
157
+
158
+ def generate_stream(self,
159
+ prompt: str,
160
+ max_tokens: int = 512,
161
+ context: Optional[str] = None) -> Generator:
162
+ """Stream response tokens"""
163
+
164
+ full_prompt = self.format_prompt(prompt, context)
165
+
166
+ stream = self.llm(
167
+ full_prompt,
168
+ max_tokens=max_tokens,
169
+ temperature=0.7,
170
+ top_p=0.95,
171
+ top_k=40,
172
+ repeat_penalty=1.1,
173
+ stop=self.stop_tokens,
174
+ echo=False,
175
+ stream=True
176
+ )
177
+
178
+ for output in stream:
179
+ token = output['choices'][0].get('text', '')
180
+ if token:
181
+ yield token
182
+
183
+ def get_model_info(self) -> Dict[str, Any]:
184
+ """Get information about the loaded model"""
185
+ return self.model_info
main.py ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, BackgroundTasks
2
+ from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel, Field
5
+ from typing import Optional, List, Dict, Any
6
+ from datetime import datetime
7
+ import asyncio
8
+ import json
9
+ import uuid
10
+ import os
11
+ import sqlite3
12
+ from contextlib import asynccontextmanager
13
+
14
+ # Import our handlers
15
+ from llm_handler import CybersecurityLLM
16
+ from knowledge_base import RAGCybersecurityLLM
17
+ from optimisations import PerformanceOptimizer, MemoryManager
18
+
19
+ # Configuration from environment variables
20
+ MODEL_REPO = os.getenv("MODEL_REPO", "daskalos-apps/phi4-cybersec-Q4_K_M")
21
+ MODEL_FILENAME = os.getenv("MODEL_FILENAME", "phi4-mini-instruct-Q4_K_M.gguf")
22
+ USE_RAG = os.getenv("USE_RAG", "true").lower() == "true"
23
+ CACHE_ENABLED = os.getenv("CACHE_ENABLED", "true").lower() == "true"
24
+
25
+ # Global instances
26
+ llm_instance = None
27
+ optimizer = None
28
+ memory_manager = None
29
+
30
+ # Database setup
31
+ # Support multiple deployment platforms: /data (HF Spaces), /app/data (Render/Railway), or local
32
+ if os.path.exists("/data"):
33
+ DB_PATH = "/data/interactions.db"
34
+ elif os.path.exists("/app/data"):
35
+ DB_PATH = "/app/data/interactions.db"
36
+ else:
37
+ DB_PATH = "interactions.db"
38
+
39
+ def init_db():
40
+ """Initialize SQLite database for interaction tracking"""
41
+ conn = sqlite3.connect(DB_PATH)
42
+ cursor = conn.cursor()
43
+ cursor.execute("""
44
+ CREATE TABLE IF NOT EXISTS interactions (
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ timestamp TEXT NOT NULL,
47
+ session_id TEXT,
48
+ message TEXT,
49
+ response_length INTEGER
50
+ )
51
+ """)
52
+ cursor.execute("""
53
+ CREATE TABLE IF NOT EXISTS interaction_count (
54
+ id INTEGER PRIMARY KEY CHECK (id = 1),
55
+ count INTEGER DEFAULT 0
56
+ )
57
+ """)
58
+ cursor.execute("INSERT OR IGNORE INTO interaction_count (id, count) VALUES (1, 0)")
59
+ conn.commit()
60
+ conn.close()
61
+
62
+ def increment_interaction():
63
+ """Increment interaction count and return new count"""
64
+ conn = sqlite3.connect(DB_PATH)
65
+ cursor = conn.cursor()
66
+ cursor.execute("UPDATE interaction_count SET count = count + 1 WHERE id = 1")
67
+ cursor.execute("SELECT count FROM interaction_count WHERE id = 1")
68
+ count = cursor.fetchone()[0]
69
+ conn.commit()
70
+ conn.close()
71
+ return count
72
+
73
+ def get_interaction_count():
74
+ """Get current interaction count"""
75
+ conn = sqlite3.connect(DB_PATH)
76
+ cursor = conn.cursor()
77
+ cursor.execute("SELECT count FROM interaction_count WHERE id = 1")
78
+ count = cursor.fetchone()[0]
79
+ conn.close()
80
+ return count
81
+
82
+ def log_interaction(session_id: str, message: str, response_length: int):
83
+ """Log interaction details"""
84
+ conn = sqlite3.connect(DB_PATH)
85
+ cursor = conn.cursor()
86
+ cursor.execute(
87
+ "INSERT INTO interactions (timestamp, session_id, message, response_length) VALUES (?, ?, ?, ?)",
88
+ (datetime.now().isoformat(), session_id, message, response_length)
89
+ )
90
+ conn.commit()
91
+ conn.close()
92
+
93
+
94
+ @asynccontextmanager
95
+ async def lifespan(app: FastAPI):
96
+ """Startup and shutdown events"""
97
+ global llm_instance, optimizer, memory_manager
98
+
99
+ # Startup
100
+ print(f"🚀 Loading model from Hugging Face: {MODEL_REPO}")
101
+
102
+ # Initialize database
103
+ init_db()
104
+ print("✅ Database initialized")
105
+
106
+ try:
107
+ if USE_RAG:
108
+ llm_instance = RAGCybersecurityLLM(
109
+ repo_id=MODEL_REPO,
110
+ filename=MODEL_FILENAME
111
+ )
112
+ else:
113
+ llm_instance = CybersecurityLLM(
114
+ repo_id=MODEL_REPO,
115
+ filename=MODEL_FILENAME
116
+ )
117
+
118
+ if CACHE_ENABLED:
119
+ optimizer = PerformanceOptimizer()
120
+
121
+ memory_manager = MemoryManager()
122
+
123
+ print("✅ Cybersecurity Chatbot ready!")
124
+ print(f"📦 Model: {MODEL_REPO}")
125
+ print(f"💾 Size: {llm_instance.get_model_info()['size_mb']:.2f} MB")
126
+ print(f"🔧 RAG: {'Enabled' if USE_RAG else 'Disabled'}")
127
+ print(f"⚡ Cache: {'Enabled' if CACHE_ENABLED else 'Disabled'}")
128
+
129
+ except Exception as e:
130
+ print(f"❌ Failed to load model: {e}")
131
+ raise
132
+
133
+ yield
134
+
135
+ # Shutdown
136
+ print("👋 Shutting down...")
137
+
138
+
139
+ # Initialize FastAPI with lifespan
140
+ app = FastAPI(
141
+ title="Cybersecurity Training Chatbot API",
142
+ description="AI-powered cybersecurity guidance using Phi-4 from Hugging Face",
143
+ version="2.0.0",
144
+ lifespan=lifespan
145
+ )
146
+
147
+ # CORS for web interface
148
+ app.add_middleware(
149
+ CORSMiddleware,
150
+ allow_origins=["*"],
151
+ allow_credentials=True,
152
+ allow_methods=["*"],
153
+ allow_headers=["*"],
154
+ )
155
+
156
+
157
+ # Request/Response models
158
+ class ChatRequest(BaseModel):
159
+ message: str = Field(..., description="User's security question")
160
+ session_id: Optional[str] = Field(None, description="Session ID for conversation continuity")
161
+ max_tokens: Optional[int] = Field(512, description="Maximum response length")
162
+ temperature: Optional[float] = Field(0.7, description="Response creativity (0-1)")
163
+ use_rag: Optional[bool] = Field(True, description="Use RAG for enhanced accuracy")
164
+ use_cache: Optional[bool] = Field(True, description="Use cached responses if available")
165
+
166
+
167
+ class ChatResponse(BaseModel):
168
+ response: str
169
+ session_id: str
170
+ timestamp: str
171
+ model: str
172
+ tokens_used: Optional[int] = None
173
+ cached: bool = False
174
+ sources: Optional[List[str]] = None
175
+
176
+
177
+ class ModelInfo(BaseModel):
178
+ repo_id: str
179
+ filename: str
180
+ size_mb: float
181
+ rag_enabled: bool
182
+ cache_enabled: bool
183
+
184
+
185
+ # Session management
186
+ sessions: Dict[str, List[Dict[str, Any]]] = {}
187
+
188
+
189
+ @app.get("/", response_model=Dict[str, str])
190
+ async def root():
191
+ """API root endpoint"""
192
+ return {
193
+ "message": "Cybersecurity Training Chatbot API",
194
+ "model": MODEL_REPO,
195
+ "documentation": "/docs",
196
+ "health": "/health"
197
+ }
198
+
199
+
200
+ @app.get("/health")
201
+ async def health_check():
202
+ """Check API and model health"""
203
+ if llm_instance is None:
204
+ raise HTTPException(status_code=503, detail="Model not loaded")
205
+
206
+ memory_status = memory_manager.check_memory() if memory_manager else {}
207
+
208
+ return {
209
+ "status": "healthy",
210
+ "model": MODEL_REPO,
211
+ "version": "2.0.0",
212
+ "memory": memory_status,
213
+ "cache_enabled": CACHE_ENABLED,
214
+ "rag_enabled": USE_RAG
215
+ }
216
+
217
+
218
+ @app.get("/model/info", response_model=ModelInfo)
219
+ async def model_info():
220
+ """Get information about the loaded model"""
221
+ if llm_instance is None:
222
+ raise HTTPException(status_code=503, detail="Model not loaded")
223
+
224
+ info = llm_instance.get_model_info()
225
+
226
+ return ModelInfo(
227
+ repo_id=info['repo_id'],
228
+ filename=info['filename'],
229
+ size_mb=info['size_mb'],
230
+ rag_enabled=USE_RAG,
231
+ cache_enabled=CACHE_ENABLED
232
+ )
233
+
234
+
235
+ @app.post("/chat", response_model=ChatResponse)
236
+ async def chat(request: ChatRequest):
237
+ """Main chat endpoint"""
238
+ if llm_instance is None:
239
+ raise HTTPException(status_code=503, detail="Model not loaded")
240
+
241
+ try:
242
+ # Generate or get session ID
243
+ session_id = request.session_id or str(uuid.uuid4())
244
+
245
+ # Initialize session if new
246
+ if session_id not in sessions:
247
+ sessions[session_id] = []
248
+
249
+ # Store user message
250
+ sessions[session_id].append({
251
+ "role": "user",
252
+ "content": request.message,
253
+ "timestamp": datetime.now().isoformat()
254
+ })
255
+
256
+ # Check cache if enabled
257
+ cached = False
258
+ response_text = None
259
+ sources = None
260
+
261
+ if CACHE_ENABLED and request.use_cache and optimizer:
262
+ cached_response = optimizer.get_cached_response(request.message)
263
+ if cached_response:
264
+ response_text = cached_response
265
+ cached = True
266
+
267
+ # Generate response if not cached
268
+ if response_text is None:
269
+ if USE_RAG and hasattr(llm_instance, 'generate_with_rag'):
270
+ result = llm_instance.generate_with_rag(
271
+ request.message,
272
+ max_tokens=request.max_tokens,
273
+ use_rag=request.use_rag
274
+ )
275
+ sources = result.get('sources', [])
276
+ else:
277
+ result = llm_instance.generate(
278
+ request.message,
279
+ max_tokens=request.max_tokens,
280
+ temperature=request.temperature
281
+ )
282
+
283
+ response_text = result["response"]
284
+
285
+ # Cache the response
286
+ if CACHE_ENABLED and optimizer and request.use_cache:
287
+ optimizer.cache_response(request.message, response_text)
288
+
289
+ # Store assistant response
290
+ sessions[session_id].append({
291
+ "role": "assistant",
292
+ "content": response_text,
293
+ "timestamp": datetime.now().isoformat()
294
+ })
295
+
296
+ # Limit session history
297
+ if len(sessions[session_id]) > 20:
298
+ sessions[session_id] = sessions[session_id][-20:]
299
+
300
+ # Check memory usage
301
+ if memory_manager:
302
+ memory_manager.optimize_if_needed()
303
+
304
+ return ChatResponse(
305
+ response=response_text,
306
+ session_id=session_id,
307
+ timestamp=datetime.now().isoformat(),
308
+ model=MODEL_REPO,
309
+ cached=cached,
310
+ sources=sources
311
+ )
312
+
313
+ except Exception as e:
314
+ logger.error(f"Chat error: {e}")
315
+ raise HTTPException(status_code=500, detail=str(e))
316
+
317
+
318
+ @app.post("/chat/stream")
319
+ async def chat_stream(request: ChatRequest):
320
+ """Streaming chat endpoint"""
321
+ if llm_instance is None:
322
+ raise HTTPException(status_code=503, detail="Model not loaded")
323
+
324
+ # Track interaction
325
+ count = increment_interaction()
326
+ session_id = request.session_id or str(uuid.uuid4())
327
+
328
+ async def generate():
329
+ try:
330
+ full_response = ""
331
+
332
+ # Send initial metadata
333
+ yield f"data: {json.dumps({'type': 'start', 'session_id': session_id, 'model': MODEL_REPO, 'interaction_count': count})}\n\n"
334
+
335
+ # Stream tokens
336
+ for token in llm_instance.generate_stream(
337
+ request.message,
338
+ max_tokens=request.max_tokens
339
+ ):
340
+ full_response += token
341
+ yield f"data: {json.dumps({'type': 'token', 'content': token})}\n\n"
342
+ await asyncio.sleep(0)
343
+
344
+ # Log interaction
345
+ log_interaction(session_id, request.message, len(full_response))
346
+
347
+ yield f"data: {json.dumps({'type': 'end'})}\n\n"
348
+
349
+ except Exception as e:
350
+ yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"
351
+
352
+ return StreamingResponse(generate(), media_type="text/event-stream")
353
+
354
+
355
+ @app.websocket("/ws/chat")
356
+ async def websocket_chat(websocket: WebSocket):
357
+ """WebSocket endpoint for real-time chat"""
358
+ await websocket.accept()
359
+
360
+ if llm_instance is None:
361
+ await websocket.send_json({"type": "error", "message": "Model not loaded"})
362
+ await websocket.close()
363
+ return
364
+
365
+ session_id = str(uuid.uuid4())
366
+
367
+ try:
368
+ await websocket.send_json({
369
+ "type": "connected",
370
+ "session_id": session_id,
371
+ "model": MODEL_REPO
372
+ })
373
+
374
+ while True:
375
+ # Receive message
376
+ data = await websocket.receive_text()
377
+ request = json.loads(data)
378
+
379
+ # Send acknowledgment
380
+ await websocket.send_json({
381
+ "type": "acknowledged",
382
+ "session_id": session_id
383
+ })
384
+
385
+ # Generate and stream response
386
+ full_response = ""
387
+
388
+ for token in llm_instance.generate_stream(request.get('message', '')):
389
+ full_response += token
390
+ await websocket.send_json({
391
+ "type": "token",
392
+ "content": token
393
+ })
394
+ await asyncio.sleep(0)
395
+
396
+ # Send completion
397
+ await websocket.send_json({
398
+ "type": "complete",
399
+ "full_response": full_response
400
+ })
401
+
402
+ except WebSocketDisconnect:
403
+ if session_id in sessions:
404
+ del sessions[session_id]
405
+
406
+
407
+ @app.get("/sessions/{session_id}")
408
+ async def get_session(session_id: str):
409
+ """Retrieve session history"""
410
+ if session_id not in sessions:
411
+ raise HTTPException(status_code=404, detail="Session not found")
412
+
413
+ return {
414
+ "session_id": session_id,
415
+ "messages": sessions[session_id],
416
+ "model": MODEL_REPO
417
+ }
418
+
419
+
420
+ @app.delete("/sessions/{session_id}")
421
+ async def clear_session(session_id: str):
422
+ """Clear session history"""
423
+ if session_id in sessions:
424
+ del sessions[session_id]
425
+
426
+ return {"message": "Session cleared"}
427
+
428
+
429
+ @app.get("/interactions/count")
430
+ async def get_interactions_count():
431
+ """Get total interaction count"""
432
+ count = get_interaction_count()
433
+ return {"count": count}
434
+
435
+
436
+ @app.get("/metrics")
437
+ async def get_metrics():
438
+ """Get performance metrics"""
439
+ metrics = {
440
+ "model": MODEL_REPO,
441
+ "sessions_active": len(sessions),
442
+ "total_messages": sum(len(s) for s in sessions.values()),
443
+ "total_interactions": get_interaction_count()
444
+ }
445
+
446
+ if optimizer:
447
+ metrics["cache"] = optimizer.get_metrics()
448
+
449
+ if memory_manager:
450
+ metrics["memory"] = memory_manager.check_memory()
451
+
452
+ return metrics
453
+
454
+
455
+ @app.post("/cache/clear")
456
+ async def clear_cache():
457
+ """Clear response cache"""
458
+ if not CACHE_ENABLED or not optimizer:
459
+ raise HTTPException(status_code=400, detail="Cache not enabled")
460
+
461
+ optimizer.clear_cache()
462
+ return {"message": "Cache cleared"}
463
+
464
+
465
+ @app.get("/test")
466
+ async def serve_test_interface():
467
+ """Serve the test interface HTML"""
468
+ return FileResponse("test_interface.html")
469
+
470
+
471
+ if __name__ == "__main__":
472
+ import uvicorn
473
+
474
+ uvicorn.run(
475
+ app,
476
+ host="0.0.0.0",
477
+ port=8000,
478
+ log_level="info",
479
+ access_log=True
480
+ )
monitoring.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import time
3
+ from datetime import datetime, timedelta
4
+ from typing import Dict, Any, List
5
+ import json
6
+ import asyncio
7
+ from dataclasses import dataclass, asdict
8
+ import psutil
9
+ from collections import deque
10
+
11
+ # Configure structured logging
12
+ logging.basicConfig(
13
+ level=logging.INFO,
14
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
15
+ handlers=[
16
+ logging.FileHandler('logs/chatbot.log'),
17
+ logging.StreamHandler()
18
+ ]
19
+ )
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dataclass
25
+ class RequestMetric:
26
+ timestamp: datetime
27
+ endpoint: str
28
+ response_time: float
29
+ status_code: int
30
+ prompt_length: int
31
+ response_length: int
32
+ cached: bool
33
+ session_id: str
34
+
35
+
36
+ class PerformanceMonitor:
37
+ def __init__(self, window_size: int = 1000):
38
+ """Initialize performance monitoring"""
39
+
40
+ self.window_size = window_size
41
+ self.request_metrics = deque(maxlen=window_size)
42
+ self.start_time = datetime.now()
43
+
44
+ # Real-time metrics
45
+ self.metrics = {
46
+ "total_requests": 0,
47
+ "successful_requests": 0,
48
+ "failed_requests": 0,
49
+ "cache_hits": 0,
50
+ "cache_misses": 0,
51
+ "average_response_time": 0,
52
+ "p95_response_time": 0,
53
+ "p99_response_time": 0,
54
+ "requests_per_minute": 0,
55
+ "active_sessions": set(),
56
+ "uptime_hours": 0
57
+ }
58
+
59
+ # System metrics
60
+ self.system_metrics = {
61
+ "cpu_percent": 0,
62
+ "memory_mb": 0,
63
+ "memory_percent": 0,
64
+ "disk_usage_percent": 0
65
+ }
66
+
67
+ def log_request(self, metric: RequestMetric):
68
+ """Log request metric"""
69
+
70
+ self.request_metrics.append(metric)
71
+ self.metrics["total_requests"] += 1
72
+
73
+ if metric.status_code == 200:
74
+ self.metrics["successful_requests"] += 1
75
+ else:
76
+ self.metrics["failed_requests"] += 1
77
+
78
+ if metric.cached:
79
+ self.metrics["cache_hits"] += 1
80
+ else:
81
+ self.metrics["cache_misses"] += 1
82
+
83
+ self.metrics["active_sessions"].add(metric.session_id)
84
+
85
+ # Log to file
86
+ logger.info(f"Request: {json.dumps(asdict(metric), default=str)}")
87
+
88
+ # Update aggregated metrics
89
+ self._update_aggregates()
90
+
91
+ def _update_aggregates(self):
92
+ """Update aggregated metrics"""
93
+
94
+ if not self.request_metrics:
95
+ return
96
+
97
+ # Response time percentiles
98
+ response_times = sorted([m.response_time for m in self.request_metrics])
99
+
100
+ self.metrics["average_response_time"] = sum(response_times) / len(response_times)
101
+
102
+ p95_idx = int(len(response_times) * 0.95)
103
+ p99_idx = int(len(response_times) * 0.99)
104
+
105
+ self.metrics["p95_response_time"] = response_times[min(p95_idx, len(response_times) - 1)]
106
+ self.metrics["p99_response_time"] = response_times[min(p99_idx, len(response_times) - 1)]
107
+
108
+ # Requests per minute
109
+ now = datetime.now()
110
+ recent_requests = [
111
+ m for m in self.request_metrics
112
+ if (now - m.timestamp).total_seconds() < 60
113
+ ]
114
+ self.metrics["requests_per_minute"] = len(recent_requests)
115
+
116
+ # Uptime
117
+ self.metrics["uptime_hours"] = (now - self.start_time).total_seconds() / 3600
118
+
119
+ # Cache hit rate
120
+ if self.metrics["total_requests"] > 0:
121
+ self.metrics["cache_hit_rate"] = (
122
+ self.metrics["cache_hits"] / self.metrics["total_requests"]
123
+ )
124
+
125
+ def update_system_metrics(self):
126
+ """Update system resource metrics"""
127
+
128
+ process = psutil.Process()
129
+
130
+ self.system_metrics["cpu_percent"] = process.cpu_percent()
131
+ self.system_metrics["memory_mb"] = process.memory_info().rss / 1024 / 1024
132
+ self.system_metrics["memory_percent"] = process.memory_percent()
133
+
134
+ disk = psutil.disk_usage('/')
135
+ self.system_metrics["disk_usage_percent"] = disk.percent
136
+
137
+ return self.system_metrics
138
+
139
+ def get_dashboard_metrics(self) -> Dict[str, Any]:
140
+ """Get metrics for dashboard display"""
141
+
142
+ self.update_system_metrics()
143
+
144
+ return {
145
+ "performance": self.metrics,
146
+ "system": self.system_metrics,
147
+ "health_score": self._calculate_health_score()
148
+ }
149
+
150
+ def _calculate_health_score(self) -> float:
151
+ """Calculate overall system health score (0-100)"""
152
+
153
+ score = 100.0
154
+
155
+ # Deduct for high response times
156
+ if self.metrics["average_response_time"] > 5:
157
+ score -= 20
158
+ elif self.metrics["average_response_time"] > 2:
159
+ score -= 10
160
+
161
+ # Deduct for errors
162
+ error_rate = self.metrics["failed_requests"] / max(self.metrics["total_requests"], 1)
163
+ score -= error_rate * 50
164
+
165
+ # Deduct for high memory usage
166
+ if self.system_metrics["memory_percent"] > 90:
167
+ score -= 30
168
+ elif self.system_metrics["memory_percent"] > 70:
169
+ score -= 10
170
+
171
+ # Deduct for low cache hit rate
172
+ cache_hit_rate = self.metrics.get("cache_hit_rate", 0)
173
+ if cache_hit_rate < 0.3:
174
+ score -= 10
175
+
176
+ return max(0, min(100, score))
177
+
178
+ def generate_report(self) -> str:
179
+ """Generate performance report"""
180
+
181
+ report = f"""
182
+ CYBERSECURITY CHATBOT PERFORMANCE REPORT
183
+ =========================================
184
+ Generated: {datetime.now().isoformat()}
185
+ Uptime: {self.metrics['uptime_hours']:.2f} hours
186
+
187
+ REQUEST METRICS
188
+ ---------------
189
+ Total Requests: {self.metrics['total_requests']}
190
+ Successful: {self.metrics['successful_requests']}
191
+ Failed: {self.metrics['failed_requests']}
192
+ Error Rate: {(self.metrics['failed_requests'] / max(self.metrics['total_requests'], 1) * 100):.2f}%
193
+
194
+ PERFORMANCE
195
+ -----------
196
+ Average Response Time: {self.metrics['average_response_time']:.3f}s
197
+ P95 Response Time: {self.metrics['p95_response_time']:.3f}s
198
+ P99 Response Time: {self.metrics['p99_response_time']:.3f}s
199
+ Requests/Minute: {self.metrics['requests_per_minute']}
200
+
201
+ CACHE PERFORMANCE
202
+ -----------------
203
+ Cache Hits: {self.metrics['cache_hits']}
204
+ Cache Misses: {self.metrics['cache_misses']}
205
+ Hit Rate: {self.metrics.get('cache_hit_rate', 0) * 100:.2f}%
206
+
207
+ SYSTEM RESOURCES
208
+ ----------------
209
+ CPU Usage: {self.system_metrics['cpu_percent']:.1f}%
210
+ Memory Usage: {self.system_metrics['memory_mb']:.2f} MB ({self.system_metrics['memory_percent']:.1f}%)
211
+ Disk Usage: {self.system_metrics['disk_usage_percent']:.1f}%
212
+
213
+ HEALTH SCORE: {self._calculate_health_score():.1f}/100
214
+ """
215
+
216
+ return report
217
+
218
+
219
+ # Alert system
220
+ class AlertManager:
221
+ def __init__(self, webhook_url: str = None):
222
+ """Initialize alert manager"""
223
+
224
+ self.webhook_url = webhook_url
225
+ self.alert_thresholds = {
226
+ "response_time": 5.0, # seconds
227
+ "error_rate": 0.1, # 10%
228
+ "memory_percent": 85,
229
+ "cpu_percent": 90
230
+ }
231
+
232
+ self.alert_history = deque(maxlen=100)
233
+ self.last_alert_time = {}
234
+
235
+ def check_alerts(self, metrics: Dict[str, Any]):
236
+ """Check if any alerts should be triggered"""
237
+
238
+ alerts = []
239
+
240
+ # Check response time
241
+ if metrics["performance"]["average_response_time"] > self.alert_thresholds["response_time"]:
242
+ alerts.append({
243
+ "level": "warning",
244
+ "type": "response_time",
245
+ "message": f"High response time: {metrics['performance']['average_response_time']:.2f}s"
246
+ })
247
+
248
+ # Check error rate
249
+ error_rate = metrics["performance"]["failed_requests"] / max(metrics["performance"]["total_requests"], 1)
250
+ if error_rate > self.alert_thresholds["error_rate"]:
251
+ alerts.append({
252
+ "level": "critical",
253
+ "type": "error_rate",
254
+ "message": f"High error rate: {error_rate * 100:.2f}%"
255
+ })
256
+
257
+ # Check memory
258
+ if metrics["system"]["memory_percent"] > self.alert_thresholds["memory_percent"]:
259
+ alerts.append({
260
+ "level": "warning",
261
+ "type": "memory",
262
+ "message": f"High memory usage: {metrics['system']['memory_percent']:.1f}%"
263
+ })
264
+
265
+ # Check CPU
266
+ if metrics["system"]["cpu_percent"] > self.alert_thresholds["cpu_percent"]:
267
+ alerts.append({
268
+ "level": "warning",
269
+ "type": "cpu",
270
+ "message": f"High CPU usage: {metrics['system']['cpu_percent']:.1f}%"
271
+ })
272
+
273
+ # Send alerts
274
+ for alert in alerts:
275
+ self._send_alert(alert)
276
+
277
+ def _send_alert(self, alert: Dict[str, Any]):
278
+ """Send alert notification"""
279
+
280
+ # Rate limiting - don't send same alert more than once per 5 minutes
281
+ alert_key = f"{alert['type']}_{alert['level']}"
282
+ now = datetime.now()
283
+
284
+ if alert_key in self.last_alert_time:
285
+ if (now - self.last_alert_time[alert_key]).seconds < 300:
286
+ return
287
+
288
+ self.last_alert_time[alert_key] = now
289
+ self.alert_history.append({
290
+ "timestamp": now.isoformat(),
291
+ **alert
292
+ })
293
+
294
+ # Log alert
295
+ if alert["level"] == "critical":
296
+ logger.error(f"ALERT: {alert['message']}")
297
+ else:
298
+ logger.warning(f"ALERT: {alert['message']}")
299
+
300
+ # Send to webhook if configured
301
+ if self.webhook_url:
302
+ self._send_webhook(alert)
optimisations.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import hashlib
3
+ import time
4
+ import psutil
5
+ import gc
6
+ from typing import Dict, List, Optional, Any
7
+ from collections import OrderedDict
8
+ from dataclasses import dataclass
9
+ from datetime import datetime, timedelta
10
+ import redis
11
+ import json
12
+
13
+
14
+ @dataclass
15
+ class CacheEntry:
16
+ response: str
17
+ timestamp: datetime
18
+ hit_count: int = 0
19
+
20
+
21
+ class PerformanceOptimizer:
22
+ def __init__(self,
23
+ cache_size: int = 100,
24
+ cache_ttl_hours: int = 24,
25
+ use_redis: bool = False):
26
+ """Initialize performance optimizer with caching"""
27
+
28
+ self.cache_size = cache_size
29
+ self.cache_ttl = timedelta(hours=cache_ttl_hours)
30
+
31
+ # Use Redis if available, fallback to in-memory
32
+ self.use_redis = use_redis
33
+ if use_redis:
34
+ try:
35
+ self.redis_client = redis.Redis(
36
+ host='localhost',
37
+ port=6379,
38
+ decode_responses=True
39
+ )
40
+ self.redis_client.ping()
41
+ except:
42
+ print("Redis not available, using in-memory cache")
43
+ self.use_redis = False
44
+ self.cache = OrderedDict()
45
+ else:
46
+ self.cache = OrderedDict()
47
+
48
+ # Metrics
49
+ self.metrics = {
50
+ "cache_hits": 0,
51
+ "cache_misses": 0,
52
+ "total_requests": 0,
53
+ "average_response_time": 0,
54
+ "memory_usage_mb": 0
55
+ }
56
+
57
+ def _hash_prompt(self, prompt: str) -> str:
58
+ """Create hash for caching"""
59
+ normalized = prompt.lower().strip()
60
+ return hashlib.md5(normalized.encode()).hexdigest()
61
+
62
+ def get_cached_response(self, prompt: str) -> Optional[str]:
63
+ """Get response from cache if available"""
64
+
65
+ self.metrics["total_requests"] += 1
66
+ prompt_hash = self._hash_prompt(prompt)
67
+
68
+ if self.use_redis:
69
+ cached = self.redis_client.get(f"chat:{prompt_hash}")
70
+ if cached:
71
+ self.metrics["cache_hits"] += 1
72
+ # Update hit count
73
+ self.redis_client.hincrby(f"chat:stats:{prompt_hash}", "hits", 1)
74
+ return json.loads(cached)["response"]
75
+ else:
76
+ if prompt_hash in self.cache:
77
+ entry = self.cache[prompt_hash]
78
+ # Check TTL
79
+ if datetime.now() - entry.timestamp < self.cache_ttl:
80
+ self.metrics["cache_hits"] += 1
81
+ entry.hit_count += 1
82
+ # Move to end (LRU)
83
+ self.cache.move_to_end(prompt_hash)
84
+ return entry.response
85
+ else:
86
+ # Expired
87
+ del self.cache[prompt_hash]
88
+
89
+ self.metrics["cache_misses"] += 1
90
+ return None
91
+
92
+ def cache_response(self, prompt: str, response: str):
93
+ """Cache a response"""
94
+
95
+ prompt_hash = self._hash_prompt(prompt)
96
+
97
+ if self.use_redis:
98
+ cache_data = {
99
+ "response": response,
100
+ "timestamp": datetime.now().isoformat()
101
+ }
102
+ self.redis_client.setex(
103
+ f"chat:{prompt_hash}",
104
+ int(self.cache_ttl.total_seconds()),
105
+ json.dumps(cache_data)
106
+ )
107
+ self.redis_client.hset(
108
+ f"chat:stats:{prompt_hash}",
109
+ mapping={"hits": 0, "created": datetime.now().isoformat()}
110
+ )
111
+ else:
112
+ # LRU cache management
113
+ if len(self.cache) >= self.cache_size:
114
+ # Remove least recently used
115
+ self.cache.popitem(last=False)
116
+
117
+ self.cache[prompt_hash] = CacheEntry(
118
+ response=response,
119
+ timestamp=datetime.now()
120
+ )
121
+
122
+ def get_metrics(self) -> Dict[str, Any]:
123
+ """Get performance metrics"""
124
+
125
+ # Update memory usage
126
+ process = psutil.Process()
127
+ self.metrics["memory_usage_mb"] = process.memory_info().rss / 1024 / 1024
128
+
129
+ # Calculate cache hit rate
130
+ if self.metrics["total_requests"] > 0:
131
+ self.metrics["cache_hit_rate"] = (
132
+ self.metrics["cache_hits"] / self.metrics["total_requests"]
133
+ )
134
+
135
+ return self.metrics
136
+
137
+ def clear_cache(self):
138
+ """Clear all cached responses"""
139
+
140
+ if self.use_redis:
141
+ for key in self.redis_client.scan_iter("chat:*"):
142
+ self.redis_client.delete(key)
143
+ else:
144
+ self.cache.clear()
145
+
146
+ gc.collect()
147
+
148
+
149
+ class MemoryManager:
150
+ def __init__(self, max_memory_mb: int = 8192):
151
+ """Initialize memory manager"""
152
+
153
+ self.max_memory_mb = max_memory_mb
154
+ self.warning_threshold = 0.8 # Warn at 80% usage
155
+ self.critical_threshold = 0.9 # Critical at 90% usage
156
+
157
+ def check_memory(self) -> Dict[str, Any]:
158
+ """Check current memory usage"""
159
+
160
+ process = psutil.Process()
161
+ memory_info = process.memory_info()
162
+
163
+ current_mb = memory_info.rss / 1024 / 1024
164
+ percentage = current_mb / self.max_memory_mb
165
+
166
+ status = "normal"
167
+ if percentage > self.critical_threshold:
168
+ status = "critical"
169
+ elif percentage > self.warning_threshold:
170
+ status = "warning"
171
+
172
+ return {
173
+ "current_mb": round(current_mb, 2),
174
+ "max_mb": self.max_memory_mb,
175
+ "percentage": round(percentage * 100, 2),
176
+ "status": status,
177
+ "available_mb": round(self.max_memory_mb - current_mb, 2)
178
+ }
179
+
180
+ def optimize_if_needed(self) -> bool:
181
+ """Run optimization if memory usage is high"""
182
+
183
+ memory_status = self.check_memory()
184
+
185
+ if memory_status["status"] in ["warning", "critical"]:
186
+ # Force garbage collection
187
+ gc.collect()
188
+
189
+ # Clear unused objects
190
+ if memory_status["status"] == "critical":
191
+ # More aggressive cleanup
192
+ gc.collect(2)
193
+
194
+ return True
195
+
196
+ return False
197
+
198
+
199
+ class RequestBatcher:
200
+ def __init__(self, batch_size: int = 5, timeout_ms: int = 100):
201
+ """Initialize request batcher for efficiency"""
202
+
203
+ self.batch_size = batch_size
204
+ self.timeout_ms = timeout_ms
205
+ self.pending_requests = []
206
+ self.results = {}
207
+
208
+ async def add_request(self, request_id: str, prompt: str) -> str:
209
+ """Add request to batch"""
210
+
211
+ self.pending_requests.append({
212
+ "id": request_id,
213
+ "prompt": prompt,
214
+ "timestamp": time.time()
215
+ })
216
+
217
+ # Process if batch is full
218
+ if len(self.pending_requests) >= self.batch_size:
219
+ await self._process_batch()
220
+ else:
221
+ # Wait for timeout
222
+ await asyncio.sleep(self.timeout_ms / 1000)
223
+ if request_id not in self.results:
224
+ await self._process_batch()
225
+
226
+ return self.results.get(request_id, "Error processing request")
227
+
228
+ async def _process_batch(self):
229
+ """Process pending requests as batch"""
230
+
231
+ if not self.pending_requests:
232
+ return
233
+
234
+ batch = self.pending_requests[:self.batch_size]
235
+ self.pending_requests = self.pending_requests[self.batch_size:]
236
+
237
+ # Process batch (simulate concurrent processing)
238
+ tasks = []
239
+ for request in batch:
240
+ # In production, this would call the LLM
241
+ tasks.append(self._process_single(request))
242
+
243
+ results = await asyncio.gather(*tasks)
244
+
245
+ for request, result in zip(batch, results):
246
+ self.results[request["id"]] = result
247
+
248
+ async def _process_single(self, request: Dict[str, Any]) -> str:
249
+ """Process single request (placeholder)"""
250
+
251
+ # Simulate processing
252
+ await asyncio.sleep(0.1)
253
+ return f"Response to: {request['prompt']}"
requirements.txt ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate==1.9.0
2
+ aiofiles==24.1.0
3
+ annotated-types==0.7.0
4
+ anyio==4.10.0
5
+ attrs==25.3.0
6
+ backoff==2.2.1
7
+ bcrypt==4.3.0
8
+ build==1.3.0
9
+ cachetools==5.5.2
10
+ certifi==2025.8.3
11
+ charset-normalizer==3.4.2
12
+ chromadb==1.0.15
13
+ click==8.2.1
14
+ coloredlogs==15.0.1
15
+ diskcache==5.6.3
16
+ distro==1.9.0
17
+ durationpy==0.10
18
+ fastapi==0.116.1
19
+ filelock==3.18.0
20
+ flatbuffers==25.2.10
21
+ fsspec==2025.7.0
22
+ google-auth==2.40.3
23
+ googleapis-common-protos==1.70.0
24
+ grpcio==1.74.0
25
+ h11==0.16.0
26
+ hf-xet==1.1.7
27
+ httpcore==1.0.9
28
+ httptools==0.6.4
29
+ httpx==0.28.1
30
+ huggingface-hub==0.34.3
31
+ humanfriendly==10.0
32
+ idna==3.10
33
+ importlib_metadata==8.7.0
34
+ importlib_resources==6.5.2
35
+ Jinja2==3.1.6
36
+ joblib==1.5.1
37
+ jsonschema==4.25.0
38
+ jsonschema-specifications==2025.4.1
39
+ kubernetes==33.1.0
40
+ llama_cpp_python==0.3.14
41
+ markdown-it-py==3.0.0
42
+ MarkupSafe==3.0.2
43
+ mdurl==0.1.2
44
+ mmh3==5.2.0
45
+ mpmath==1.3.0
46
+ networkx==3.5
47
+ numpy==2.3.2
48
+ oauthlib==3.3.1
49
+ onnxruntime==1.22.1
50
+ opentelemetry-api==1.36.0
51
+ opentelemetry-exporter-otlp-proto-common==1.36.0
52
+ opentelemetry-exporter-otlp-proto-grpc==1.36.0
53
+ opentelemetry-proto==1.36.0
54
+ opentelemetry-sdk==1.36.0
55
+ opentelemetry-semantic-conventions==0.57b0
56
+ orjson==3.11.1
57
+ overrides==7.7.0
58
+ packaging==25.0
59
+ pillow==11.3.0
60
+ posthog==5.4.0
61
+ protobuf==6.31.1
62
+ psutil==7.0.0
63
+ pyasn1==0.6.1
64
+ pyasn1_modules==0.4.2
65
+ pybase64==1.4.2
66
+ pydantic==2.11.7
67
+ pydantic_core==2.33.2
68
+ Pygments==2.19.2
69
+ PyPika==0.48.9
70
+ pyproject_hooks==1.2.0
71
+ python-dateutil==2.9.0.post0
72
+ python-dotenv==1.1.1
73
+ PyYAML==6.0.2
74
+ redis==6.3.0
75
+ referencing==0.36.2
76
+ regex==2025.7.34
77
+ requests==2.32.4
78
+ requests-oauthlib==2.0.0
79
+ rich==14.1.0
80
+ rpds-py==0.26.0
81
+ rsa==4.9.1
82
+ safetensors==0.5.3
83
+ scikit-learn==1.7.1
84
+ scipy==1.16.1
85
+ sentence-transformers==5.0.0
86
+ sentencepiece==0.2.0
87
+ setuptools==80.9.0
88
+ shellingham==1.5.4
89
+ six==1.17.0
90
+ sniffio==1.3.1
91
+ starlette==0.47.2
92
+ sympy==1.14.0
93
+ tenacity==9.1.2
94
+ threadpoolctl==3.6.0
95
+ tokenizers==0.21.4
96
+ torch==2.7.1
97
+ tqdm==4.67.1
98
+ transformers==4.55.0
99
+ typer==0.16.0
100
+ typing-inspection==0.4.1
101
+ typing_extensions==4.14.1
102
+ urllib3==2.5.0
103
+ uvicorn==0.35.0
104
+ uvloop==0.21.0
105
+ watchfiles==1.1.0
106
+ websocket-client==1.8.0
107
+ websockets==15.0.1
108
+ zipp==3.23.0
test_interface.html ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cybersecurity Chatbot Test</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: white;
26
+ border-radius: 20px;
27
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
28
+ width: 100%;
29
+ max-width: 800px;
30
+ height: 600px;
31
+ display: flex;
32
+ flex-direction: column;
33
+ }
34
+
35
+ .header {
36
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
37
+ color: white;
38
+ padding: 20px;
39
+ border-radius: 20px 20px 0 0;
40
+ text-align: center;
41
+ }
42
+
43
+ .header h1 {
44
+ font-size: 24px;
45
+ margin-bottom: 5px;
46
+ }
47
+
48
+ .header p {
49
+ opacity: 0.9;
50
+ font-size: 14px;
51
+ }
52
+
53
+ .chat-container {
54
+ flex: 1;
55
+ overflow-y: auto;
56
+ padding: 20px;
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 15px;
60
+ }
61
+
62
+ .message {
63
+ padding: 12px 16px;
64
+ border-radius: 18px;
65
+ max-width: 70%;
66
+ word-wrap: break-word;
67
+ animation: fadeIn 0.3s ease-in;
68
+ }
69
+
70
+ @keyframes fadeIn {
71
+ from { opacity: 0; transform: translateY(10px); }
72
+ to { opacity: 1; transform: translateY(0); }
73
+ }
74
+
75
+ .user-message {
76
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
77
+ color: white;
78
+ align-self: flex-end;
79
+ }
80
+
81
+ .assistant-message {
82
+ background: #f3f4f6;
83
+ color: #1f2937;
84
+ align-self: flex-start;
85
+ }
86
+
87
+ .typing-indicator {
88
+ display: none;
89
+ align-self: flex-start;
90
+ padding: 12px 16px;
91
+ background: #f3f4f6;
92
+ border-radius: 18px;
93
+ margin-bottom: 10px;
94
+ }
95
+
96
+ .typing-indicator span {
97
+ display: inline-block;
98
+ width: 8px;
99
+ height: 8px;
100
+ border-radius: 50%;
101
+ background: #9ca3af;
102
+ margin: 0 2px;
103
+ animation: typing 1.4s infinite;
104
+ }
105
+
106
+ .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
107
+ .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
108
+
109
+ @keyframes typing {
110
+ 0%, 60%, 100% { transform: translateY(0); }
111
+ 30% { transform: translateY(-10px); }
112
+ }
113
+
114
+ .input-container {
115
+ padding: 20px;
116
+ border-top: 1px solid #e5e7eb;
117
+ display: flex;
118
+ gap: 10px;
119
+ }
120
+
121
+ #messageInput {
122
+ flex: 1;
123
+ padding: 12px 16px;
124
+ border: 2px solid #e5e7eb;
125
+ border-radius: 25px;
126
+ font-size: 14px;
127
+ outline: none;
128
+ transition: border-color 0.3s;
129
+ }
130
+
131
+ #messageInput:focus {
132
+ border-color: #667eea;
133
+ }
134
+
135
+ .send-button {
136
+ padding: 12px 24px;
137
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
138
+ color: white;
139
+ border: none;
140
+ border-radius: 25px;
141
+ cursor: pointer;
142
+ font-weight: 600;
143
+ transition: transform 0.2s;
144
+ }
145
+
146
+ .send-button:hover {
147
+ transform: scale(1.05);
148
+ }
149
+
150
+ .send-button:disabled {
151
+ opacity: 0.5;
152
+ cursor: not-allowed;
153
+ }
154
+
155
+ .quick-prompts {
156
+ padding: 10px 20px;
157
+ display: flex;
158
+ gap: 10px;
159
+ flex-wrap: wrap;
160
+ }
161
+
162
+ .quick-prompt {
163
+ padding: 6px 12px;
164
+ background: #f3f4f6;
165
+ border: 1px solid #e5e7eb;
166
+ border-radius: 15px;
167
+ font-size: 12px;
168
+ cursor: pointer;
169
+ transition: all 0.2s;
170
+ }
171
+
172
+ .quick-prompt:hover {
173
+ background: #e5e7eb;
174
+ transform: translateY(-2px);
175
+ }
176
+ </style>
177
+ </head>
178
+ <body>
179
+ <div class="container">
180
+ <div class="header">
181
+ <h1>🔒 Cybersecurity Assistant</h1>
182
+ <p>Ask me anything about security best practices</p>
183
+ <p style="margin-top: 10px; font-size: 12px; opacity: 0.8;">
184
+ Total Interactions: <span id="interactionCount">0</span>
185
+ </p>
186
+ </div>
187
+
188
+ <div class="quick-prompts">
189
+ <div class="quick-prompt" onclick="sendQuickPrompt('How do I identify phishing emails?')">Phishing Detection</div>
190
+ <div class="quick-prompt" onclick="sendQuickPrompt('What makes a strong password?')">Password Security</div>
191
+ <div class="quick-prompt" onclick="sendQuickPrompt('How do I secure my home WiFi?')">Network Security</div>
192
+ <div class="quick-prompt" onclick="sendQuickPrompt('What should I do if I clicked a suspicious link?')">Incident Response</div>
193
+ </div>
194
+
195
+ <div class="chat-container" id="chatContainer">
196
+ <div class="assistant-message message">
197
+ Hello! I'm your cybersecurity assistant. How can I help you stay safe online today?
198
+ </div>
199
+ </div>
200
+
201
+ <div class="typing-indicator" id="typingIndicator">
202
+ <span></span>
203
+ <span></span>
204
+ <span></span>
205
+ </div>
206
+
207
+ <div class="input-container">
208
+ <input
209
+ type="text"
210
+ id="messageInput"
211
+ placeholder="Ask about cybersecurity..."
212
+ onkeypress="handleKeyPress(event)"
213
+ >
214
+ <button class="send-button" onclick="sendMessage()" id="sendButton">Send</button>
215
+ </div>
216
+ </div>
217
+
218
+ <script>
219
+ let sessionId = null;
220
+ let isProcessing = false;
221
+
222
+ function handleKeyPress(event) {
223
+ if (event.key === 'Enter' && !isProcessing) {
224
+ sendMessage();
225
+ }
226
+ }
227
+
228
+ function sendQuickPrompt(prompt) {
229
+ document.getElementById('messageInput').value = prompt;
230
+ sendMessage();
231
+ }
232
+
233
+ function addMessage(content, isUser = false) {
234
+ const chatContainer = document.getElementById('chatContainer');
235
+ const messageDiv = document.createElement('div');
236
+ messageDiv.className = `message ${isUser ? 'user-message' : 'assistant-message'}`;
237
+ messageDiv.textContent = content;
238
+ chatContainer.appendChild(messageDiv);
239
+ chatContainer.scrollTop = chatContainer.scrollHeight;
240
+ return messageDiv;
241
+ }
242
+
243
+ function showTyping() {
244
+ document.getElementById('typingIndicator').style.display = 'block';
245
+ }
246
+
247
+ function hideTyping() {
248
+ document.getElementById('typingIndicator').style.display = 'none';
249
+ }
250
+
251
+ async function sendMessage() {
252
+ const input = document.getElementById('messageInput');
253
+ const sendButton = document.getElementById('sendButton');
254
+ const message = input.value.trim();
255
+
256
+ if (!message || isProcessing) return;
257
+
258
+ isProcessing = true;
259
+ sendButton.disabled = true;
260
+
261
+ // Add user message
262
+ addMessage(message, true);
263
+ input.value = '';
264
+
265
+ // Show typing indicator
266
+ showTyping();
267
+
268
+ try {
269
+ // Stream response
270
+ const response = await fetch('http://localhost:8000/chat/stream', {
271
+ method: 'POST',
272
+ headers: {
273
+ 'Content-Type': 'application/json',
274
+ },
275
+ body: JSON.stringify({
276
+ message: message,
277
+ session_id: sessionId
278
+ })
279
+ });
280
+
281
+ const reader = response.body.getReader();
282
+ const decoder = new TextDecoder();
283
+ let assistantMessage = null;
284
+ let fullResponse = '';
285
+
286
+ while (true) {
287
+ const { done, value } = await reader.read();
288
+ if (done) break;
289
+
290
+ const text = decoder.decode(value);
291
+ const lines = text.split('\n');
292
+
293
+ for (const line of lines) {
294
+ if (line.startsWith('data: ')) {
295
+ try {
296
+ const data = JSON.parse(line.slice(6));
297
+
298
+ if (data.type === 'start') {
299
+ sessionId = data.session_id;
300
+ if (data.interaction_count) {
301
+ document.getElementById('interactionCount').textContent = data.interaction_count;
302
+ }
303
+ hideTyping();
304
+ assistantMessage = addMessage('', false);
305
+ } else if (data.type === 'token' && assistantMessage) {
306
+ fullResponse += data.content;
307
+ assistantMessage.textContent = fullResponse;
308
+ chatContainer.scrollTop = chatContainer.scrollHeight;
309
+ }
310
+ } catch (e) {
311
+ console.error('Parse error:', e);
312
+ }
313
+ }
314
+ }
315
+ }
316
+ } catch (error) {
317
+ console.error('Error:', error);
318
+ hideTyping();
319
+ addMessage('Sorry, I encountered an error. Please try again.', false);
320
+ } finally {
321
+ hideTyping();
322
+ isProcessing = false;
323
+ sendButton.disabled = false;
324
+ input.focus();
325
+ }
326
+ }
327
+
328
+ // Auto-focus on load
329
+ window.onload = () => {
330
+ document.getElementById('messageInput').focus();
331
+ };
332
+ </script>
333
+ </body>
334
+ </html>