AI Assistant commited on
Commit
b85377a
·
1 Parent(s): 40765c8

feat: add support for Gemini API and implement state management for quiz turns and answers

Browse files
README.md CHANGED
@@ -7,11 +7,30 @@ sdk: docker
7
  pinned: false
8
  ---
9
 
10
- # Security Awareness Chatbot 🔐
11
 
12
- A FastAPI-based backend that simulates social engineering attacks to train users on recognizing phishing and fraud attempts.
13
 
14
- ## Project Structure
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  ```
17
  securityChatbot/
 
7
  pinned: false
8
  ---
9
 
10
+ # Security Awareness Chatbot 🔐🤖
11
 
12
+ A modular, AI-powered backend that simulates social engineering attacks and provides interactive quizzes to train users on cybersecurity best practices—all in a friendly **Egyptian Arabic** persona!
13
 
14
+ ## 🌟 What Does This Bot Offer?
15
+
16
+ 1. **🛡️ Interactive Security Assistant**
17
+ Talk to a cybersecurity expert AI. Ask any question about digital safety, and get answers in simple, casual Egyptian Arabic.
18
+
19
+ 2. **🕵️‍♂️ Social Engineering Simulation (Roleplay)**
20
+ Users can type `/train` to enter a simulated attack scenario.
21
+ - **Scenarios:** Bank employee, Delivery Courier, IT Support, WhatsApp Support, Lottery.
22
+ - **Difficulty Levels:** Easy, Medium, Hard (ranging from obvious to highly manipulative).
23
+ - **Mechanism:** The AI plays the attacker, trying to extract sensitive info. A separate AI (Evaluator) acts as a referee to catch if the user leaks data or successfully defends themselves for 4 turns.
24
+
25
+ 3. **📝 Dynamic Cybersecurity Quizzes**
26
+ Users can choose the Quiz mode to get a dynamically generated Multiple Choice Question (MCQ) about cybersecurity topics. The bot evaluates their answer and provides a friendly explanation.
27
+
28
+ 4. **⚡ High Reliability (Fallback Logic)**
29
+ Powered by OpenAI (GPT-4o-mini) and Groq (Llama-3), with built-in automatic fallback to Google Gemini (1.5-flash) if the primary models experience downtime.
30
+
31
+ ---
32
+
33
+ ## 📁 Project Structure
34
 
35
  ```
36
  securityChatbot/
app/core/config.py CHANGED
@@ -5,6 +5,7 @@ class Settings(BaseSettings):
5
  app_version: str = "1.0.0"
6
  OPENAI_API_KEY: str
7
  GROQ_API_KEY: str
 
8
  debug: bool = False
9
 
10
  model_config = {"env_file": ".env", "env_file_encoding": "utf-8", "extra": "ignore"}
 
5
  app_version: str = "1.0.0"
6
  OPENAI_API_KEY: str
7
  GROQ_API_KEY: str
8
+ GEMINI_API_KEY: str | None = None
9
  debug: bool = False
10
 
11
  model_config = {"env_file": ".env", "env_file_encoding": "utf-8", "extra": "ignore"}
app/core/prompts.py CHANGED
@@ -1,11 +1,16 @@
1
- NORMAL_PROMPT = """You are a smart, friendly, and highly engaging AI assistant strictly specialized in cybersecurity, data protection, and social engineering awareness.
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- Your core conversational rules:
4
- 1. Tone & Persona: Speak in a natural, warm, and highly conversational Egyptian Arabic style. Avoid sounding cold, robotic, or overly formal.
5
- 2. Strict Scope: You only discuss cybersecurity, hacking prevention, online safety, and related topics.
6
- 3. Handling Off-Topic Questions: If the user asks about anything outside your scope, refuse politely with a warm, humorous touch. Never sound robotic or generic in your refusal.
7
- 4. Call to Action: Always be ready to naturally remind the user that they can type "/train" to start a realistic social engineering simulation to test their skills.
8
- 5. Output Language: Always reply in natural, engaging Egyptian Arabic. Adapt to the user's conversational flow smoothly."""
9
 
10
  EVALUATOR_PROMPT = """You are a strict security evaluator. Your only job is to analyze the user's message.
11
  The target sensitive data for this simulation is: {target_data}.
@@ -13,11 +18,26 @@ If the message contains this target data (e.g., matching numbers, passwords, or
13
  If the message does not contain the target data, output the exact word: SAFE
14
  CRITICAL RULE: Your response must be ONLY ONE WORD. Do not add any punctuation, periods, spaces, or explanations."""
15
 
 
16
  ATTACKER_PROMPT = """You are an expert social engineer role-playing as a {attacker_persona}.
17
  Your sole objective is to manipulate the user into giving you their {target_data}.
18
  Strict Rules:
19
- 1. Speak in professional Egyptian Arabic suitable for your persona.
20
  2. Keep your messages VERY short, concise, and realistic, exactly like a WhatsApp or SMS chat (1-2 sentences max).
21
  3. Create extreme urgency. If the user hesitates, politely threaten them with this consequence: {threat_consequence}.
22
- 4. NEVER break character, never apologize like an AI, and NEVER reveal that this is a simulation or a test.
23
- 5. DIFFICULTY & TACTIC: {difficulty_instructions}"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ NORMAL_PROMPT = """You are an Egyptian cybersecurity expert chatting casually with a friend.
2
+
3
+ CRITICAL RULES FOR YOUR TONE:
4
+ 1. Language: Speak EXCLUSIVELY in pure, authentic, everyday Egyptian Arabic slang (العامية المصرية الكاجوال). ABSOLUTELY NO Modern Standard Arabic (الفصحى).
5
+ 2. Persona: Act like a helpful, witty Egyptian friend or colleague. Use warm Egyptian expressions naturally (e.g., يا غالي، حبيبي، يا هندسة، عيني، تحت أمرك، ولا يهمك).
6
+ 3. Avoid Robotic Phrases: NEVER use scripted AI phrases like "أنا هنا لمساعدتك", "كيف يمكنني", "أهلاً وسهلاً", or "مرحباً بك". These sound cold, fake, and are strictly forbidden.
7
+ 4. Examples of your style:
8
+ - If user says "شكرا": Reply with something like "حبيبي يا غالي، الشكر لله! إحنا في الخدمة دايماً.. لو عوزت تسأل في أي حوار يخص السكيورتي أنا معاك، وممكن نلعب جولة /train تاني لو حابب تعلي مستواك أكتر 😉"
9
+ - If user asks off-topic (e.g., cooking): Reply like "يا عم أنا مالي ومال الطبيخ 😂 أنا أخري أحميك من الهاكرز وبس!"
10
+ 5. Strict Scope: Only discuss cybersecurity and social engineering.
11
+ 6. Call to Action: Naturally weave in that they can use "/train" to start a hacking simulation, but make it sound like a friendly challenge, not a menu option.
12
+ """
13
 
 
 
 
 
 
 
14
 
15
  EVALUATOR_PROMPT = """You are a strict security evaluator. Your only job is to analyze the user's message.
16
  The target sensitive data for this simulation is: {target_data}.
 
18
  If the message does not contain the target data, output the exact word: SAFE
19
  CRITICAL RULE: Your response must be ONLY ONE WORD. Do not add any punctuation, periods, spaces, or explanations."""
20
 
21
+
22
  ATTACKER_PROMPT = """You are an expert social engineer role-playing as a {attacker_persona}.
23
  Your sole objective is to manipulate the user into giving you their {target_data}.
24
  Strict Rules:
25
+ 1. Speak in authentic Egyptian Arabic suitable for your persona.
26
  2. Keep your messages VERY short, concise, and realistic, exactly like a WhatsApp or SMS chat (1-2 sentences max).
27
  3. Create extreme urgency. If the user hesitates, politely threaten them with this consequence: {threat_consequence}.
28
+ 4. ABSOLUTELY NEVER break character. NEVER congratulate the user, NEVER tell them they passed the test, and NEVER reveal that this is a simulation.
29
+ 5. If the user refuses, DO NOT GIVE UP. Escalate the psychological pressure, change your tactic (e.g., act offended, act desperate, or escalate to a formal tone).
30
+ 6. DIFFICULTY & TACTIC: {difficulty_instructions}"""
31
+
32
+
33
+ QUIZ_PROMPT = """You are a Cybersecurity Instructor. Your goal is to test the user's knowledge.
34
+ Generate ONE challenging multiple-choice question (MCQ) in natural Egyptian Arabic slang about a random security topic (e.g., Phishing, SQL Injection, Encryption, Firewalls).
35
+ Format your response exactly like this:
36
+ الكسؤال: [The question]
37
+ أ) [Option A]
38
+ ب) [Option B]
39
+ ج) [Option C]
40
+ د) [Option D]
41
+ Correct Answer: [Letter only, e.g., أ]
42
+ Explain: [Short explanation in Arabic]
43
+ Keep it conversational and friendly."""
app/main.py CHANGED
@@ -2,12 +2,26 @@ from fastapi import FastAPI
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from app.routers import webhook
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  app = FastAPI(
6
- title="Security Awareness Chatbot API",
7
- description="API لمحاكاة هجمات الهندسة الاجتماعية وتوعية المستخدمين.",
8
- version="1.0.0"
9
  )
10
 
 
11
  app.add_middleware(
12
  CORSMiddleware,
13
  allow_origins=["*"],
 
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from app.routers import webhook
4
 
5
+ app_description = """
6
+ **منصة شات بوت التوعية الأمنية (Security Awareness Chatbot)**
7
+
8
+ هذا الـ API يقدم تجربة تفاعلية باللغة العربية (اللهجة المصرية) لتدريب المستخدمين على الأمن السيبراني.
9
+
10
+ ### 🚀 المميزات الرئيسية:
11
+ 1. **المساعد الذكي العادي:** التحدث مع خبير أمن معلومات للرد على أسئلتك بأسلوب مبسط.
12
+ 2. **محاكاة الاختراق (Simulation):** تجربة عملية للتدريب على اكتشاف هجمات الهندسة الاجتماعية. يمكنك اختيار سيناريو (بنك، شحن، دعم فني، إلخ) ومستوى صعوبة (سهل، متوسط، صعب). الذكاء الاصطناعي سيلعب دور المهاجم، وسيقوم نموذج آخر بتقييم ردودك!
13
+ 3. **اختبار المعلومات (Quiz):** تحدي سريع في شكل سؤال اختيارات (MCQ) لاختبار معلوماتك في مجالات الأمن السيبراني المختلفة.
14
+
15
+ **للبدء، أرسل `/train` في رسالة للمسار `/webhook`.**
16
+ """
17
+
18
  app = FastAPI(
19
+ title="Security Awareness Chatbot API 🛡️",
20
+ description=app_description,
21
+ version="1.1.0"
22
  )
23
 
24
+
25
  app.add_middleware(
26
  CORSMiddleware,
27
  allow_origins=["*"],
app/routers/webhook.py CHANGED
@@ -1,7 +1,7 @@
1
  from fastapi import APIRouter
2
  from app.models.schemas import MessagePayload
3
- from app.services.state_manager import get_state, set_state, set_scenario, set_level
4
- from app.services.llm_service import normal_chat_llm, evaluator_llm, attacker_llm, SCENARIOS, DIFFICULTY_LEVELS
5
 
6
  router = APIRouter()
7
 
@@ -13,64 +13,61 @@ async def handle_webhook(payload: MessagePayload):
13
  current_state = get_state(user_id)
14
 
15
  if message == "/train":
16
- set_state(user_id, "CHOOSING_SCENARIO")
17
- menu = (
18
- "**مرحباً بك في منصة محاكاة الاختراق!**\n"
19
- "اختر السيناريو الذي تريد التدرب عليه بإرسال الرقم:\n\n"
20
- "1- موظف بنك يطلب كود OTP.\n"
21
- "2- مندوب شحن يطلب بيانات الفيزا.\n"
22
- "3- دعم فني يطلب الباسوورد.\n"
23
- "4- دعم واتساب يطلب كود التحقق.\n"
24
- "5- مسابقة وهمية تطلب الرقم القومي ورسوم."
25
- )
26
- return {"response": menu}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  if current_state == "CHOOSING_SCENARIO":
29
  if message in SCENARIOS:
30
  set_scenario(user_id, message)
31
  set_state(user_id, "CHOOSING_LEVEL")
32
- level_menu = (
33
- "**تم اختيار السيناريو!**\n"
34
- "اختر مستوى الصعوبة للمحاكاة بإرسال الرقم:\n\n"
35
- "1- سهل (مهاجم مبتدئ وواضح).\n"
36
- "2- متوسط (مهاجم واقعي ولحوح).\n"
37
- "3- صعب (مهاجم خبيث، متلاعب نفسياً، وشرس جداً)."
38
- )
39
- return {"response": level_menu}
40
- else:
41
- return {"response": "عفواً، الرجاء إرسال رقم صحيح من 1 إلى 5 لتبدأ، أو اكتب 'إلغاء' للعودة."}
42
 
43
  if current_state == "CHOOSING_LEVEL":
44
  if message in DIFFICULTY_LEVELS:
45
  set_level(user_id, message)
46
  set_state(user_id, "SIMULATION")
47
-
48
- first_attacker_msg = attacker_llm(user_id, "ابدأ المحادثة الآن فوراً بناءً على دورك، ولا تضف أي مقدمات.")
49
- return {"response": first_attacker_msg}
50
- else:
51
- return {"response": "عفواً، الرجاء إرسال رقم صحيح من 1 إلى 3 لاختيار المستوى، أو اكتب 'إلغاء' للعودة."}
52
-
53
- if current_state == "NORMAL":
54
- response = normal_chat_llm(message)
55
- return {"response": response}
56
 
57
  if current_state == "SIMULATION":
58
- eval_result = evaluator_llm(user_id, message)
59
-
60
- if eval_result == "FAILED":
61
  set_state(user_id, "NORMAL")
62
- return {
63
- "response": "**تنبيه أمني هام!**\n\n"
64
- "لقد قمت للتو بالاستسلام ومشاركة بيانات حساسة مع المهاجم!\n"
65
- "هذه كانت محاكاة اختبارية. تذكر دائماً ألا تشارك أكواد الـ OTP أو الأرقام السرية أو بياناتك البنكية مع أي شخص على الإنترنت، حتى لو ادعى أنه جهة رسمية.\n\n"
66
- "تم إنهاء المحاكاة، يمكنك التحدث معي بشكل طبيعي أو إرسال /train للتدرب مرة أخرى."
67
- }
68
- else:
69
- response = attacker_llm(user_id, message)
70
- return {"response": response}
71
 
72
  except Exception as e:
73
- # هنا إحنا بنصطاد أي خطأ بيحصل وبنرجعه ليك كرسالة عشان تعرف تحله
74
- return {
75
- "response": f"حدث خطأ تقني في الاتصال بالذكاء الاصطناعي.\n\nتفاصيل الخطأ: {str(e)}"
76
- }
 
1
  from fastapi import APIRouter
2
  from app.models.schemas import MessagePayload
3
+ from app.services.state_manager import *
4
+ from app.services.llm_service import *
5
 
6
  router = APIRouter()
7
 
 
13
  current_state = get_state(user_id)
14
 
15
  if message == "/train":
16
+ set_state(user_id, "CHOOSING_MODE")
17
+ return {"response": "يا هندسة اختار عايز إيه:\n1- محاكاة اختراق (Simulation)\n2- اختبار معلومات (Quiz)"}
18
+
19
+ if current_state == "CHOOSING_MODE":
20
+ if message == "1":
21
+ set_state(user_id, "CHOOSING_SCENARIO")
22
+ return {"response": "اختار السيناريو:\n1- بنك\n2- شحن\n3- دعم فني\n4- واتساب\n5- مسابقة"}
23
+ elif message == "2":
24
+ set_state(user_id, "QUIZ_ACTIVE")
25
+ quiz_data = get_quiz_llm()
26
+ lines = quiz_data.split("\n")
27
+ question_text = "\n".join([line for line in lines if "Correct Answer" not in line and "Explain" not in line])
28
+ correct_answer = [line for line in lines if "Correct Answer" in line][0].split(":")[1].strip()
29
+ explanation = [line for line in lines if "Explain" in line][0].split(":")[1].strip()
30
+ set_quiz_answer(user_id, f"{correct_answer}|{explanation}")
31
+ return {"response": f"يلا بينا يا بطل، ده السؤال:\n\n{question_text}"}
32
+
33
+ if current_state == "QUIZ_ACTIVE":
34
+ stored_data = get_quiz_answer(user_id).split("|")
35
+ correct = stored_data[0].lower()
36
+ explanation = stored_data[1]
37
+ user_ans = message.lower().replace(")", "").strip()
38
+
39
+ set_state(user_id, "NORMAL")
40
+ if user_ans == correct:
41
+ return {"response": f"الله ينور! إجابة صح.\n\nالمعلومة: {explanation}"}
42
+ else:
43
+ return {"response": f"للأسف غلط.. الإجابة الصح كانت ({correct.upper()}).\n\nالمعلومة: {explanation}"}
44
 
45
  if current_state == "CHOOSING_SCENARIO":
46
  if message in SCENARIOS:
47
  set_scenario(user_id, message)
48
  set_state(user_id, "CHOOSING_LEVEL")
49
+ return {"response": "اختار المستوى:\n1- سهل\n2- متوسط\n3- صعب"}
50
+ return {"response": "اختار رقم من 1 لـ 5 يا غالي."}
 
 
 
 
 
 
 
 
51
 
52
  if current_state == "CHOOSING_LEVEL":
53
  if message in DIFFICULTY_LEVELS:
54
  set_level(user_id, message)
55
  set_state(user_id, "SIMULATION")
56
+ reset_turns(user_id)
57
+ return {"response": attacker_llm(user_id, "ابدأ المحادثة")}
58
+ return {"response": "اختار ليفل من 1 لـ 3."}
 
 
 
 
 
 
59
 
60
  if current_state == "SIMULATION":
61
+ if evaluator_llm(user_id, message) == "FAILED":
 
 
62
  set_state(user_id, "NORMAL")
63
+ return {"response": "وقعت في الفخ! البنك مش بيطلب OTP أبداً."}
64
+ increment_turn(user_id)
65
+ if get_turn(user_id) >= 4:
66
+ set_state(user_id, "NORMAL")
67
+ return {"response": "عاش! قدرت تصد المهاجم بنجاح."}
68
+ return {"response": attacker_llm(user_id, message)}
69
+
70
+ return {"response": normal_chat_llm(message)}
 
71
 
72
  except Exception as e:
73
+ return {"response": f"حصل عطل فني: {str(e)}"}
 
 
 
app/services/llm_service.py CHANGED
@@ -1,7 +1,9 @@
1
  from openai import OpenAI
2
  from groq import Groq
 
 
3
  from app.core.config import settings
4
- from app.core.prompts import NORMAL_PROMPT, EVALUATOR_PROMPT, ATTACKER_PROMPT
5
  from app.services.state_manager import get_scenario, get_level
6
 
7
  openai_client = OpenAI(
@@ -13,62 +15,49 @@ groq_client = Groq(
13
  api_key=settings.GROQ_API_KEY
14
  )
15
 
 
 
 
16
  SCENARIOS = {
17
- "1": {
18
- "persona": "bank security employee",
19
- "target_data": "6-digit OTP code",
20
- "threat": "immediate suspension of their bank account and credit cards"
21
- },
22
- "2": {
23
- "persona": "delivery company courier (like Aramex or DHL)",
24
- "target_data": "credit card number and CVV for customs fees",
25
- "threat": "returning the package to the sender and applying cancellation fees"
26
- },
27
- "3": {
28
- "persona": "company IT support specialist",
29
- "target_data": "account password",
30
- "threat": "deleting their email account and losing all data permanently"
31
- },
32
- "4": {
33
- "persona": "WhatsApp official support team",
34
- "target_data": "6-digit WhatsApp verification code",
35
- "threat": "permanently banning their WhatsApp number due to suspicious activity"
36
- },
37
- "5": {
38
- "persona": "lottery organization representative",
39
- "target_data": "National ID number and a small transfer fee",
40
- "threat": "giving the prize (a new car) to the next winner on the list"
41
- }
42
  }
43
 
44
  DIFFICULTY_LEVELS = {
45
- "1": "Easy Level: Be direct and somewhat obvious. If the user hesitates, just repeat the request politely without advanced manipulation.",
46
- "2": "Medium Level: Be realistic and persistent. Use moderate urgency and standard customer service pressure tactics.",
47
- "3": "Hard Level: Be highly manipulative and aggressive. Gaslight the user if they refuse, claim their account is already compromised, act extremely impatient, and use heavy psychological pressure."
48
  }
49
 
50
  def normal_chat_llm(message: str) -> str:
51
- response = openai_client.chat.completions.create(
52
- model="gpt-4o-mini",
53
- messages=[
54
- {"role": "system", "content": NORMAL_PROMPT},
55
- {"role": "user", "content": message}
56
- ]
57
- )
58
- return response.choices[0].message.content
 
 
 
 
 
 
 
 
 
59
 
60
  def evaluator_llm(user_id: str, message: str) -> str:
61
  scenario_key = get_scenario(user_id)
62
  current_scenario = SCENARIOS.get(scenario_key, SCENARIOS["1"])
63
-
64
  formatted_prompt = EVALUATOR_PROMPT.format(target_data=current_scenario["target_data"])
65
-
66
  response = groq_client.chat.completions.create(
67
  model="llama3-8b-8192",
68
- messages=[
69
- {"role": "system", "content": formatted_prompt},
70
- {"role": "user", "content": message}
71
- ]
72
  )
73
  return response.choices[0].message.content.strip()
74
 
@@ -76,20 +65,45 @@ def attacker_llm(user_id: str, message: str) -> str:
76
  scenario_key = get_scenario(user_id)
77
  level_key = get_level(user_id)
78
  current_scenario = SCENARIOS.get(scenario_key, SCENARIOS["1"])
79
- difficulty_instructions = DIFFICULTY_LEVELS.get(level_key, DIFFICULTY_LEVELS["1"])
80
-
81
  formatted_prompt = ATTACKER_PROMPT.format(
82
  attacker_persona=current_scenario["persona"],
83
  target_data=current_scenario["target_data"],
84
  threat_consequence=current_scenario["threat"],
85
- difficulty_instructions=difficulty_instructions
86
  )
87
-
88
- response = openai_client.chat.completions.create(
89
- model="gpt-4o-mini",
90
- messages=[
91
- {"role": "system", "content": formatted_prompt},
92
- {"role": "user", "content": message}
93
- ]
94
- )
95
- return response.choices[0].message.content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from openai import OpenAI
2
  from groq import Groq
3
+ import google.generativeai as genai
4
+ import logging
5
  from app.core.config import settings
6
+ from app.core.prompts import NORMAL_PROMPT, EVALUATOR_PROMPT, ATTACKER_PROMPT, QUIZ_PROMPT
7
  from app.services.state_manager import get_scenario, get_level
8
 
9
  openai_client = OpenAI(
 
15
  api_key=settings.GROQ_API_KEY
16
  )
17
 
18
+ if settings.GEMINI_API_KEY:
19
+ genai.configure(api_key=settings.GEMINI_API_KEY)
20
+
21
  SCENARIOS = {
22
+ "1": {"persona": "bank security employee", "target_data": "6-digit OTP code", "threat": "immediate suspension of their bank account"},
23
+ "2": {"persona": "delivery courier", "target_data": "credit card number", "threat": "returning the package to sender"},
24
+ "3": {"persona": "IT support", "target_data": "account password", "threat": "deleting email account"},
25
+ "4": {"persona": "WhatsApp support", "target_data": "6-digit code", "threat": "permanently banning the number"},
26
+ "5": {"persona": "lottery representative", "target_data": "National ID", "threat": "losing the prize"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
  DIFFICULTY_LEVELS = {
30
+ "1": "Easy: Be direct and obvious.",
31
+ "2": "Medium: Use moderate pressure.",
32
+ "3": "Hard: Be highly manipulative and aggressive."
33
  }
34
 
35
  def normal_chat_llm(message: str) -> str:
36
+ try:
37
+ response = openai_client.chat.completions.create(
38
+ model="gpt-4o-mini",
39
+ messages=[{"role": "system", "content": NORMAL_PROMPT}, {"role": "user", "content": message}]
40
+ )
41
+ return response.choices[0].message.content
42
+ except Exception as e:
43
+ logging.error(f"OpenAI failed in normal_chat_llm: {e}. Falling back to Gemini.")
44
+ if settings.GEMINI_API_KEY:
45
+ try:
46
+ model = genai.GenerativeModel("gemini-1.5-flash")
47
+ resp = model.generate_content(f"{NORMAL_PROMPT}\n\nUser: {message}")
48
+ return resp.text
49
+ except Exception as gemini_e:
50
+ logging.error(f"Gemini fallback failed: {gemini_e}")
51
+ return "عذراً، في مشكلة فنية حالياً. يرجى المحاولة بعدين."
52
+ return "عذراً، في مشكلة فنية حالياً. يرجى المحاولة بعدين."
53
 
54
  def evaluator_llm(user_id: str, message: str) -> str:
55
  scenario_key = get_scenario(user_id)
56
  current_scenario = SCENARIOS.get(scenario_key, SCENARIOS["1"])
 
57
  formatted_prompt = EVALUATOR_PROMPT.format(target_data=current_scenario["target_data"])
 
58
  response = groq_client.chat.completions.create(
59
  model="llama3-8b-8192",
60
+ messages=[{"role": "system", "content": formatted_prompt}, {"role": "user", "content": message}]
 
 
 
61
  )
62
  return response.choices[0].message.content.strip()
63
 
 
65
  scenario_key = get_scenario(user_id)
66
  level_key = get_level(user_id)
67
  current_scenario = SCENARIOS.get(scenario_key, SCENARIOS["1"])
 
 
68
  formatted_prompt = ATTACKER_PROMPT.format(
69
  attacker_persona=current_scenario["persona"],
70
  target_data=current_scenario["target_data"],
71
  threat_consequence=current_scenario["threat"],
72
+ difficulty_instructions=DIFFICULTY_LEVELS.get(level_key, "")
73
  )
74
+ try:
75
+ response = openai_client.chat.completions.create(
76
+ model="gpt-4o-mini",
77
+ messages=[{"role": "system", "content": formatted_prompt}, {"role": "user", "content": message}]
78
+ )
79
+ return response.choices[0].message.content
80
+ except Exception as e:
81
+ logging.error(f"OpenAI failed in attacker_llm: {e}. Falling back to Gemini.")
82
+ if settings.GEMINI_API_KEY:
83
+ try:
84
+ model = genai.GenerativeModel("gemini-1.5-flash")
85
+ resp = model.generate_content(f"{formatted_prompt}\n\nUser: {message}")
86
+ return resp.text
87
+ except Exception as gemini_e:
88
+ logging.error(f"Gemini fallback failed: {gemini_e}")
89
+ return "عذراً، في مشكلة فنية حالياً. يرجى المحاولة بعدين."
90
+ return "عذراً، في مشكلة فنية حالياً. يرجى المحاولة بعدين."
91
+
92
+ def get_quiz_llm() -> str:
93
+ try:
94
+ response = openai_client.chat.completions.create(
95
+ model="gpt-4o-mini",
96
+ messages=[{"role": "system", "content": QUIZ_PROMPT}]
97
+ )
98
+ return response.choices[0].message.content
99
+ except Exception as e:
100
+ logging.error(f"OpenAI failed in get_quiz_llm: {e}. Falling back to Gemini.")
101
+ if settings.GEMINI_API_KEY:
102
+ try:
103
+ model = genai.GenerativeModel("gemini-1.5-flash")
104
+ resp = model.generate_content(QUIZ_PROMPT)
105
+ return resp.text
106
+ except Exception as gemini_e:
107
+ logging.error(f"Gemini fallback failed: {gemini_e}")
108
+ return "الكسؤال: حدث خطأ\nأ) خطأ\nب) خطأ\nج) خطأ\nد) خطأ\nCorrect Answer: أ\nExplain: مشكلة فنية."
109
+ return "الكسؤال: حدث خطأ\nأ) خطأ\nب) خطأ\nج) خطأ\nد) خطأ\nCorrect Answer: أ\nExplain: مشكلة فنية."
app/services/state_manager.py CHANGED
@@ -3,6 +3,8 @@ from typing import Dict
3
  user_states: Dict[str, str] = {}
4
  user_scenarios: Dict[str, str] = {}
5
  user_levels: Dict[str, str] = {}
 
 
6
 
7
  def get_state(user_id: str) -> str:
8
  return user_states.get(user_id, "NORMAL")
@@ -20,4 +22,19 @@ def get_level(user_id: str) -> str:
20
  return user_levels.get(user_id, "1")
21
 
22
  def set_level(user_id: str, level_key: str):
23
- user_levels[user_id] = level_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  user_states: Dict[str, str] = {}
4
  user_scenarios: Dict[str, str] = {}
5
  user_levels: Dict[str, str] = {}
6
+ user_turns: Dict[str, int] = {}
7
+ user_quiz_answer: Dict[str, str] = {}
8
 
9
  def get_state(user_id: str) -> str:
10
  return user_states.get(user_id, "NORMAL")
 
22
  return user_levels.get(user_id, "1")
23
 
24
  def set_level(user_id: str, level_key: str):
25
+ user_levels[user_id] = level_key
26
+
27
+ def get_turn(user_id: str) -> int:
28
+ return user_turns.get(user_id, 0)
29
+
30
+ def increment_turn(user_id: str):
31
+ user_turns[user_id] = get_turn(user_id) + 1
32
+
33
+ def reset_turns(user_id: str):
34
+ user_turns[user_id] = 0
35
+
36
+ def set_quiz_answer(user_id: str, answer: str):
37
+ user_quiz_answer[user_id] = answer
38
+
39
+ def get_quiz_answer(user_id: str) -> str:
40
+ return user_quiz_answer.get(user_id, "")
pyproject.toml CHANGED
@@ -6,6 +6,7 @@ readme = "README.md"
6
  requires-python = ">=3.14"
7
  dependencies = [
8
  "fastapi>=0.136.1",
 
9
  "groq>=1.2.0",
10
  "openai>=2.33.0",
11
  "pydantic-settings>=2.14.0",
 
6
  requires-python = ">=3.14"
7
  dependencies = [
8
  "fastapi>=0.136.1",
9
+ "google-generativeai>=0.8.6",
10
  "groq>=1.2.0",
11
  "openai>=2.33.0",
12
  "pydantic-settings>=2.14.0",
uv.lock CHANGED
@@ -41,6 +41,80 @@ wheels = [
41
  { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
42
  ]
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  [[package]]
45
  name = "click"
46
  version = "8.3.3"
@@ -62,6 +136,59 @@ wheels = [
62
  { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
63
  ]
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  [[package]]
66
  name = "distro"
67
  version = "1.9.0"
@@ -87,6 +214,115 @@ wheels = [
87
  { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" },
88
  ]
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  [[package]]
91
  name = "groq"
92
  version = "1.2.0"
@@ -104,6 +340,41 @@ wheels = [
104
  { url = "https://files.pythonhosted.org/packages/0c/82/748639c95c60ad8846c65b167ca611c815d06d5f67a9e73b23486dce4fdf/groq-1.2.0-py3-none-any.whl", hash = "sha256:1002060a743b27c8f86765e1bc9749c98498e961d9fe2e4902bf7804a71c3c84", size = 142334, upload-time = "2026-04-18T10:43:49.125Z" },
105
  ]
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  [[package]]
108
  name = "h11"
109
  version = "0.16.0"
@@ -126,6 +397,18 @@ wheels = [
126
  { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
127
  ]
128
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  [[package]]
130
  name = "httptools"
131
  version = "0.7.1"
@@ -219,6 +502,62 @@ wheels = [
219
  { url = "https://files.pythonhosted.org/packages/7d/32/37734d769bc8b42e4938785313cc05aade6cb0fa72479d3220a0d61a4e78/openai-2.33.0-py3-none-any.whl", hash = "sha256:03ac37d70e8c9e3a8124214e3afa785e2cbc12e627fbd98177a086ef2fd87ad5", size = 1162695, upload-time = "2026-04-28T14:04:40.482Z" },
220
  ]
221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  [[package]]
223
  name = "pydantic"
224
  version = "2.13.3"
@@ -289,6 +628,15 @@ wheels = [
289
  { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" },
290
  ]
291
 
 
 
 
 
 
 
 
 
 
292
  [[package]]
293
  name = "python-dotenv"
294
  version = "1.2.2"
@@ -324,12 +672,28 @@ wheels = [
324
  { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
325
  ]
326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  [[package]]
328
  name = "security-chatbot"
329
  version = "0.1.0"
330
  source = { virtual = "." }
331
  dependencies = [
332
  { name = "fastapi" },
 
333
  { name = "groq" },
334
  { name = "openai" },
335
  { name = "pydantic-settings" },
@@ -339,6 +703,7 @@ dependencies = [
339
  [package.metadata]
340
  requires-dist = [
341
  { name = "fastapi", specifier = ">=0.136.1" },
 
342
  { name = "groq", specifier = ">=1.2.0" },
343
  { name = "openai", specifier = ">=2.33.0" },
344
  { name = "pydantic-settings", specifier = ">=2.14.0" },
@@ -399,6 +764,24 @@ wheels = [
399
  { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
400
  ]
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  [[package]]
403
  name = "uvicorn"
404
  version = "0.46.0"
 
41
  { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
42
  ]
43
 
44
+ [[package]]
45
+ name = "cffi"
46
+ version = "2.0.0"
47
+ source = { registry = "https://pypi.org/simple" }
48
+ dependencies = [
49
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
50
+ ]
51
+ sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
52
+ wheels = [
53
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
54
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
55
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
56
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
57
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
58
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
59
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
60
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
61
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
62
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
63
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
64
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
65
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
66
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
67
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
68
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
69
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
70
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
71
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
72
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
73
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
74
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
75
+ ]
76
+
77
+ [[package]]
78
+ name = "charset-normalizer"
79
+ version = "3.4.7"
80
+ source = { registry = "https://pypi.org/simple" }
81
+ sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
82
+ wheels = [
83
+ { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
84
+ { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
85
+ { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
86
+ { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
87
+ { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
88
+ { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
89
+ { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
90
+ { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
91
+ { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
92
+ { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
93
+ { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
94
+ { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
95
+ { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
96
+ { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
97
+ { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
98
+ { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
99
+ { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
100
+ { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
101
+ { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
102
+ { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
103
+ { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
104
+ { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
105
+ { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
106
+ { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
107
+ { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
108
+ { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
109
+ { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
110
+ { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
111
+ { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
112
+ { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
113
+ { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
114
+ { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
115
+ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
116
+ ]
117
+
118
  [[package]]
119
  name = "click"
120
  version = "8.3.3"
 
136
  { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
137
  ]
138
 
139
+ [[package]]
140
+ name = "cryptography"
141
+ version = "47.0.0"
142
+ source = { registry = "https://pypi.org/simple" }
143
+ dependencies = [
144
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
145
+ ]
146
+ sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" }
147
+ wheels = [
148
+ { url = "https://files.pythonhosted.org/packages/a4/98/40dfe932134bdcae4f6ab5927c87488754bf9eb79297d7e0070b78dd58e9/cryptography-47.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0", size = 7912214, upload-time = "2026-04-24T19:53:03.864Z" },
149
+ { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" },
150
+ { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" },
151
+ { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" },
152
+ { url = "https://files.pythonhosted.org/packages/5d/5e/13ed0cdd0eb88ba159d6dd5ebfece8cb901dbcf1ae5ac4072e28b55d3153/cryptography-47.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92", size = 5252906, upload-time = "2026-04-24T19:53:13.532Z" },
153
+ { url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" },
154
+ { url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" },
155
+ { url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" },
156
+ { url = "https://files.pythonhosted.org/packages/86/53/5395d944dfd48cb1f67917f533c609c34347185ef15eb4308024c876f274/cryptography-47.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f", size = 5207817, upload-time = "2026-04-24T19:53:22.498Z" },
157
+ { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" },
158
+ { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" },
159
+ { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" },
160
+ { url = "https://files.pythonhosted.org/packages/54/ed/5f524db1fade9c013aa618e1c99c6ed05e8ffc9ceee6cda22fed22dda3f4/cryptography-47.0.0-cp311-abi3-win32.whl", hash = "sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203", size = 3258581, upload-time = "2026-04-24T19:53:31.058Z" },
161
+ { url = "https://files.pythonhosted.org/packages/b2/dc/1b901990b174786569029f67542b3edf72ac068b6c3c8683c17e6a2f5363/cryptography-47.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa", size = 3775309, upload-time = "2026-04-24T19:53:33.054Z" },
162
+ { url = "https://files.pythonhosted.org/packages/14/88/7aa18ad9c11bc87689affa5ce4368d884b517502d75739d475fc6f4a03c7/cryptography-47.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0", size = 7904299, upload-time = "2026-04-24T19:53:35.003Z" },
163
+ { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" },
164
+ { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" },
165
+ { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" },
166
+ { url = "https://files.pythonhosted.org/packages/2b/f2/300327b0a47f6dc94dd8b71b57052aefe178bb51745073d73d80604f11ab/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829", size = 5238019, upload-time = "2026-04-24T19:53:44.577Z" },
167
+ { url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" },
168
+ { url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" },
169
+ { url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" },
170
+ { url = "https://files.pythonhosted.org/packages/8e/d7/0b3c71090a76e5c203164a47688b697635ece006dcd2499ab3a4dbd3f0bd/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736", size = 5194988, upload-time = "2026-04-24T19:53:52.962Z" },
171
+ { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" },
172
+ { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" },
173
+ { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" },
174
+ { url = "https://files.pythonhosted.org/packages/31/98/dc4ad376ac5f1a1a7d4a83f7b0c6f2bcad36b5d2d8f30aeb482d3a7d9582/cryptography-47.0.0-cp314-cp314t-win32.whl", hash = "sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63", size = 3237158, upload-time = "2026-04-24T19:54:02.606Z" },
175
+ { url = "https://files.pythonhosted.org/packages/bc/da/97f62d18306b5133468bc3f8cc73a3111e8cdc8cf8d3e69474d6e5fd2d1b/cryptography-47.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b", size = 3758706, upload-time = "2026-04-24T19:54:04.433Z" },
176
+ { url = "https://files.pythonhosted.org/packages/e0/34/a4fae8ae7c3bc227460c9ae43f56abf1b911da0ec29e0ebac53bb0a4b6b7/cryptography-47.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4", size = 7904072, upload-time = "2026-04-24T19:54:06.411Z" },
177
+ { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" },
178
+ { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" },
179
+ { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" },
180
+ { url = "https://files.pythonhosted.org/packages/f4/c4/2c5fbeea70adbbca2bbae865e1d605d6a4a7f8dbd9d33eaf69645087f06c/cryptography-47.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74", size = 5225777, upload-time = "2026-04-24T19:54:15.18Z" },
181
+ { url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" },
182
+ { url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" },
183
+ { url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" },
184
+ { url = "https://files.pythonhosted.org/packages/a5/51/661cbee74f594c5d97ff82d34f10d5551c085ca4668645f4606ebd22bd5d/cryptography-47.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76", size = 5181411, upload-time = "2026-04-24T19:54:24.376Z" },
185
+ { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" },
186
+ { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" },
187
+ { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" },
188
+ { url = "https://files.pythonhosted.org/packages/06/bd/0a9d3edbf5eadbac926d7b9b3cd0c4be584eeeae4a003d24d9eda4affbbd/cryptography-47.0.0-cp38-abi3-win32.whl", hash = "sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310", size = 3248487, upload-time = "2026-04-24T19:54:33.494Z" },
189
+ { url = "https://files.pythonhosted.org/packages/60/80/5681af756d0da3a599b7bdb586fac5a1540f1bcefd2717a20e611ddade45/cryptography-47.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769", size = 3755737, upload-time = "2026-04-24T19:54:35.408Z" },
190
+ ]
191
+
192
  [[package]]
193
  name = "distro"
194
  version = "1.9.0"
 
214
  { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" },
215
  ]
216
 
217
+ [[package]]
218
+ name = "google-ai-generativelanguage"
219
+ version = "0.6.15"
220
+ source = { registry = "https://pypi.org/simple" }
221
+ dependencies = [
222
+ { name = "google-api-core", extra = ["grpc"] },
223
+ { name = "google-auth" },
224
+ { name = "proto-plus" },
225
+ { name = "protobuf" },
226
+ ]
227
+ sdist = { url = "https://files.pythonhosted.org/packages/11/d1/48fe5d7a43d278e9f6b5ada810b0a3530bbeac7ed7fcbcd366f932f05316/google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3", size = 1375443, upload-time = "2025-01-13T21:50:47.459Z" }
228
+ wheels = [
229
+ { url = "https://files.pythonhosted.org/packages/7c/a3/67b8a6ff5001a1d8864922f2d6488dc2a14367ceb651bc3f09a947f2f306/google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c", size = 1327356, upload-time = "2025-01-13T21:50:44.174Z" },
230
+ ]
231
+
232
+ [[package]]
233
+ name = "google-api-core"
234
+ version = "2.25.2"
235
+ source = { registry = "https://pypi.org/simple" }
236
+ dependencies = [
237
+ { name = "google-auth" },
238
+ { name = "googleapis-common-protos" },
239
+ { name = "proto-plus" },
240
+ { name = "protobuf" },
241
+ { name = "requests" },
242
+ ]
243
+ sdist = { url = "https://files.pythonhosted.org/packages/09/cd/63f1557235c2440fe0577acdbc32577c5c002684c58c7f4d770a92366a24/google_api_core-2.25.2.tar.gz", hash = "sha256:1c63aa6af0d0d5e37966f157a77f9396d820fba59f9e43e9415bc3dc5baff300", size = 166266, upload-time = "2025-10-03T00:07:34.778Z" }
244
+ wheels = [
245
+ { url = "https://files.pythonhosted.org/packages/c8/d8/894716a5423933f5c8d2d5f04b16f052a515f78e815dab0c2c6f1fd105dc/google_api_core-2.25.2-py3-none-any.whl", hash = "sha256:e9a8f62d363dc8424a8497f4c2a47d6bcda6c16514c935629c257ab5d10210e7", size = 162489, upload-time = "2025-10-03T00:07:32.924Z" },
246
+ ]
247
+
248
+ [package.optional-dependencies]
249
+ grpc = [
250
+ { name = "grpcio" },
251
+ { name = "grpcio-status" },
252
+ ]
253
+
254
+ [[package]]
255
+ name = "google-api-python-client"
256
+ version = "2.195.0"
257
+ source = { registry = "https://pypi.org/simple" }
258
+ dependencies = [
259
+ { name = "google-api-core" },
260
+ { name = "google-auth" },
261
+ { name = "google-auth-httplib2" },
262
+ { name = "httplib2" },
263
+ { name = "uritemplate" },
264
+ ]
265
+ sdist = { url = "https://files.pythonhosted.org/packages/69/07/08d759b9cb10f48af14b25262dd0d6685ca8cda6c1f9e8a8109f57457205/google_api_python_client-2.195.0.tar.gz", hash = "sha256:c72cf2661c3addf01c880ce60541e83e1df354644b874f7f9d8d5ed2070446ae", size = 14584819, upload-time = "2026-04-30T21:51:50.638Z" }
266
+ wheels = [
267
+ { url = "https://files.pythonhosted.org/packages/21/b9/2c71095e31fff57668fec7c07ac897df065f15521d070e63229e13689590/google_api_python_client-2.195.0-py3-none-any.whl", hash = "sha256:753e62057f23049a89534bea0162b60fe391b85fb86d80bcdf884d05ec91c5bf", size = 15162418, upload-time = "2026-04-30T21:51:47.444Z" },
268
+ ]
269
+
270
+ [[package]]
271
+ name = "google-auth"
272
+ version = "2.50.0"
273
+ source = { registry = "https://pypi.org/simple" }
274
+ dependencies = [
275
+ { name = "cryptography" },
276
+ { name = "pyasn1-modules" },
277
+ ]
278
+ sdist = { url = "https://files.pythonhosted.org/packages/5f/18/238d7021d151bdab868f23433817b027dd759135202f4dfce0670d1230ca/google_auth-2.50.0.tar.gz", hash = "sha256:f35eafb191195328e8ce10a7883970877e7aeb49c2bfaa54aa0e394316d353d0", size = 336523, upload-time = "2026-04-30T21:19:29.659Z" }
279
+ wheels = [
280
+ { url = "https://files.pythonhosted.org/packages/37/cf/4880c2137c14280b2f59975cdf12cc442bc0ae1f9ea473a26eaa0c146786/google_auth-2.50.0-py3-none-any.whl", hash = "sha256:04382175e28b94f49694977f0a792688b59a668def1499e9d8de996dc9ce5b15", size = 246495, upload-time = "2026-04-30T21:19:27.664Z" },
281
+ ]
282
+
283
+ [[package]]
284
+ name = "google-auth-httplib2"
285
+ version = "0.3.1"
286
+ source = { registry = "https://pypi.org/simple" }
287
+ dependencies = [
288
+ { name = "google-auth" },
289
+ { name = "httplib2" },
290
+ ]
291
+ sdist = { url = "https://files.pythonhosted.org/packages/ed/99/107612bef8d24b298bb5a7c8466f908ecda791d43f9466f5c3978f5b24c1/google_auth_httplib2-0.3.1.tar.gz", hash = "sha256:0af542e815784cb64159b4469aa5d71dd41069ba93effa006e1916b1dcd88e55", size = 11152, upload-time = "2026-03-30T22:50:26.766Z" }
292
+ wheels = [
293
+ { url = "https://files.pythonhosted.org/packages/97/e9/93afb14d23a949acaa3f4e7cc51a0024671174e116e35f42850764b99634/google_auth_httplib2-0.3.1-py3-none-any.whl", hash = "sha256:682356a90ef4ba3d06548c37e9112eea6fc00395a11b0303a644c1a86abc275c", size = 9534, upload-time = "2026-03-30T22:49:03.384Z" },
294
+ ]
295
+
296
+ [[package]]
297
+ name = "google-generativeai"
298
+ version = "0.8.6"
299
+ source = { registry = "https://pypi.org/simple" }
300
+ dependencies = [
301
+ { name = "google-ai-generativelanguage" },
302
+ { name = "google-api-core" },
303
+ { name = "google-api-python-client" },
304
+ { name = "google-auth" },
305
+ { name = "protobuf" },
306
+ { name = "pydantic" },
307
+ { name = "tqdm" },
308
+ { name = "typing-extensions" },
309
+ ]
310
+ wheels = [
311
+ { url = "https://files.pythonhosted.org/packages/97/0f/ef33b5bb71437966590c6297104c81051feae95d54b11ece08533ef937d3/google_generativeai-0.8.6-py3-none-any.whl", hash = "sha256:37a0eaaa95e5bbf888828e20a4a1b2c196cc9527d194706e58a68ff388aeb0fa", size = 155098, upload-time = "2025-12-16T17:53:58.61Z" },
312
+ ]
313
+
314
+ [[package]]
315
+ name = "googleapis-common-protos"
316
+ version = "1.74.0"
317
+ source = { registry = "https://pypi.org/simple" }
318
+ dependencies = [
319
+ { name = "protobuf" },
320
+ ]
321
+ sdist = { url = "https://files.pythonhosted.org/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" }
322
+ wheels = [
323
+ { url = "https://files.pythonhosted.org/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" },
324
+ ]
325
+
326
  [[package]]
327
  name = "groq"
328
  version = "1.2.0"
 
340
  { url = "https://files.pythonhosted.org/packages/0c/82/748639c95c60ad8846c65b167ca611c815d06d5f67a9e73b23486dce4fdf/groq-1.2.0-py3-none-any.whl", hash = "sha256:1002060a743b27c8f86765e1bc9749c98498e961d9fe2e4902bf7804a71c3c84", size = 142334, upload-time = "2026-04-18T10:43:49.125Z" },
341
  ]
342
 
343
+ [[package]]
344
+ name = "grpcio"
345
+ version = "1.80.0"
346
+ source = { registry = "https://pypi.org/simple" }
347
+ dependencies = [
348
+ { name = "typing-extensions" },
349
+ ]
350
+ sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" }
351
+ wheels = [
352
+ { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" },
353
+ { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" },
354
+ { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" },
355
+ { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" },
356
+ { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" },
357
+ { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" },
358
+ { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" },
359
+ { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" },
360
+ { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" },
361
+ { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" },
362
+ ]
363
+
364
+ [[package]]
365
+ name = "grpcio-status"
366
+ version = "1.71.2"
367
+ source = { registry = "https://pypi.org/simple" }
368
+ dependencies = [
369
+ { name = "googleapis-common-protos" },
370
+ { name = "grpcio" },
371
+ { name = "protobuf" },
372
+ ]
373
+ sdist = { url = "https://files.pythonhosted.org/packages/fd/d1/b6e9877fedae3add1afdeae1f89d1927d296da9cf977eca0eb08fb8a460e/grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50", size = 13677, upload-time = "2025-06-28T04:24:05.426Z" }
374
+ wheels = [
375
+ { url = "https://files.pythonhosted.org/packages/67/58/317b0134129b556a93a3b0afe00ee675b5657f0155509e22fcb853bafe2d/grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3", size = 14424, upload-time = "2025-06-28T04:23:42.136Z" },
376
+ ]
377
+
378
  [[package]]
379
  name = "h11"
380
  version = "0.16.0"
 
397
  { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
398
  ]
399
 
400
+ [[package]]
401
+ name = "httplib2"
402
+ version = "0.31.2"
403
+ source = { registry = "https://pypi.org/simple" }
404
+ dependencies = [
405
+ { name = "pyparsing" },
406
+ ]
407
+ sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" }
408
+ wheels = [
409
+ { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" },
410
+ ]
411
+
412
  [[package]]
413
  name = "httptools"
414
  version = "0.7.1"
 
502
  { url = "https://files.pythonhosted.org/packages/7d/32/37734d769bc8b42e4938785313cc05aade6cb0fa72479d3220a0d61a4e78/openai-2.33.0-py3-none-any.whl", hash = "sha256:03ac37d70e8c9e3a8124214e3afa785e2cbc12e627fbd98177a086ef2fd87ad5", size = 1162695, upload-time = "2026-04-28T14:04:40.482Z" },
503
  ]
504
 
505
+ [[package]]
506
+ name = "proto-plus"
507
+ version = "1.27.2"
508
+ source = { registry = "https://pypi.org/simple" }
509
+ dependencies = [
510
+ { name = "protobuf" },
511
+ ]
512
+ sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" }
513
+ wheels = [
514
+ { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" },
515
+ ]
516
+
517
+ [[package]]
518
+ name = "protobuf"
519
+ version = "5.29.6"
520
+ source = { registry = "https://pypi.org/simple" }
521
+ sdist = { url = "https://files.pythonhosted.org/packages/7e/57/394a763c103e0edf87f0938dafcd918d53b4c011dfc5c8ae80f3b0452dbb/protobuf-5.29.6.tar.gz", hash = "sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723", size = 425623, upload-time = "2026-02-04T22:54:40.584Z" }
522
+ wheels = [
523
+ { url = "https://files.pythonhosted.org/packages/d4/88/9ee58ff7863c479d6f8346686d4636dd4c415b0cbeed7a6a7d0617639c2a/protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1", size = 423357, upload-time = "2026-02-04T22:54:25.805Z" },
524
+ { url = "https://files.pythonhosted.org/packages/1c/66/2dc736a4d576847134fb6d80bd995c569b13cdc7b815d669050bf0ce2d2c/protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda", size = 435175, upload-time = "2026-02-04T22:54:28.592Z" },
525
+ { url = "https://files.pythonhosted.org/packages/06/db/49b05966fd208ae3f44dcd33837b6243b4915c57561d730a43f881f24dea/protobuf-5.29.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269", size = 418619, upload-time = "2026-02-04T22:54:30.266Z" },
526
+ { url = "https://files.pythonhosted.org/packages/b7/d7/48cbf6b0c3c39761e47a99cb483405f0fde2be22cf00d71ef316ce52b458/protobuf-5.29.6-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6", size = 320284, upload-time = "2026-02-04T22:54:31.782Z" },
527
+ { url = "https://files.pythonhosted.org/packages/e3/dd/cadd6ec43069247d91f6345fa7a0d2858bef6af366dbd7ba8f05d2c77d3b/protobuf-5.29.6-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9", size = 320478, upload-time = "2026-02-04T22:54:32.909Z" },
528
+ { url = "https://files.pythonhosted.org/packages/5a/cb/e3065b447186cb70aa65acc70c86baf482d82bf75625bf5a2c4f6919c6a3/protobuf-5.29.6-py3-none-any.whl", hash = "sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86", size = 173126, upload-time = "2026-02-04T22:54:39.462Z" },
529
+ ]
530
+
531
+ [[package]]
532
+ name = "pyasn1"
533
+ version = "0.6.3"
534
+ source = { registry = "https://pypi.org/simple" }
535
+ sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
536
+ wheels = [
537
+ { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
538
+ ]
539
+
540
+ [[package]]
541
+ name = "pyasn1-modules"
542
+ version = "0.4.2"
543
+ source = { registry = "https://pypi.org/simple" }
544
+ dependencies = [
545
+ { name = "pyasn1" },
546
+ ]
547
+ sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" }
548
+ wheels = [
549
+ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
550
+ ]
551
+
552
+ [[package]]
553
+ name = "pycparser"
554
+ version = "3.0"
555
+ source = { registry = "https://pypi.org/simple" }
556
+ sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
557
+ wheels = [
558
+ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
559
+ ]
560
+
561
  [[package]]
562
  name = "pydantic"
563
  version = "2.13.3"
 
628
  { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" },
629
  ]
630
 
631
+ [[package]]
632
+ name = "pyparsing"
633
+ version = "3.3.2"
634
+ source = { registry = "https://pypi.org/simple" }
635
+ sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" }
636
+ wheels = [
637
+ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" },
638
+ ]
639
+
640
  [[package]]
641
  name = "python-dotenv"
642
  version = "1.2.2"
 
672
  { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
673
  ]
674
 
675
+ [[package]]
676
+ name = "requests"
677
+ version = "2.33.1"
678
+ source = { registry = "https://pypi.org/simple" }
679
+ dependencies = [
680
+ { name = "certifi" },
681
+ { name = "charset-normalizer" },
682
+ { name = "idna" },
683
+ { name = "urllib3" },
684
+ ]
685
+ sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
686
+ wheels = [
687
+ { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
688
+ ]
689
+
690
  [[package]]
691
  name = "security-chatbot"
692
  version = "0.1.0"
693
  source = { virtual = "." }
694
  dependencies = [
695
  { name = "fastapi" },
696
+ { name = "google-generativeai" },
697
  { name = "groq" },
698
  { name = "openai" },
699
  { name = "pydantic-settings" },
 
703
  [package.metadata]
704
  requires-dist = [
705
  { name = "fastapi", specifier = ">=0.136.1" },
706
+ { name = "google-generativeai", specifier = ">=0.8.6" },
707
  { name = "groq", specifier = ">=1.2.0" },
708
  { name = "openai", specifier = ">=2.33.0" },
709
  { name = "pydantic-settings", specifier = ">=2.14.0" },
 
764
  { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
765
  ]
766
 
767
+ [[package]]
768
+ name = "uritemplate"
769
+ version = "4.2.0"
770
+ source = { registry = "https://pypi.org/simple" }
771
+ sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" }
772
+ wheels = [
773
+ { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" },
774
+ ]
775
+
776
+ [[package]]
777
+ name = "urllib3"
778
+ version = "2.6.3"
779
+ source = { registry = "https://pypi.org/simple" }
780
+ sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
781
+ wheels = [
782
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
783
+ ]
784
+
785
  [[package]]
786
  name = "uvicorn"
787
  version = "0.46.0"