chatbot_artifact / chatbot_updated.py
fatma812's picture
Update chatbot_updated.py
dae1d26 verified
Raw
History Blame Contribute Delete
11.4 kB
import json
import os
import uuid
import tempfile
import numpy as np
from PIL import Image
from deep_translator import GoogleTranslator
from ultralytics import YOLO
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from gtts import gTTS
import whisper as whisper_lib
import re
from rapidfuzz import fuzz
# ==============================
# Lazy Loaded Models (IMPORTANT)
# ==============================
embed_model = None
yolo_model = None
whisper_model = None
_model = None
_tokenizer = None
_index = None
_artifact_docs = None
_artifact_texts = None
# ==============================
# Lazy Getters
# ==============================
def get_embed_model():
global embed_model
if embed_model is None:
embed_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
return embed_model
def get_yolo_model():
global yolo_model
if yolo_model is None:
model_path = os.path.join("models", "best_egypt.pt")
yolo_model = YOLO(model_path)
return yolo_model
def get_whisper_model():
global whisper_model
if whisper_model is None:
whisper_model = whisper_lib.load_model("small")
return whisper_model
def get_llm():
global _model, _tokenizer
if _model is None:
print("Loading LLM...")
model_name = "google/flan-t5-base"
_tokenizer = AutoTokenizer.from_pretrained(model_name)
_model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
print("LLM loaded.")
return _model, _tokenizer
# ==============================
# Load Artifacts JSON (lazy index)
# ==============================
import os
import json
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(BASE_DIR, "data", "artifacts.json")
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
def build_index():
global _index, _artifact_docs, _artifact_texts
if _index is not None:
return _index
model = get_embed_model()
artifact_texts = []
artifact_docs = []
for doc in data:
name = doc.get("name", "")
keywords = " ".join(doc.get("keywords", []))
text = f"{name} {keywords}"
artifact_texts.append(text)
artifact_docs.append(doc)
embeddings = model.encode(artifact_texts, convert_to_numpy=True)
import faiss
_index = faiss.IndexFlatL2(embeddings.shape[1])
_index.add(np.array(embeddings))
_artifact_docs = artifact_docs
_artifact_texts = artifact_texts
return _index
# ==============================
# Helper Functions
# ==============================
def is_arabic(text):
return any('\u0600' <= c <= '\u06FF' for c in text)
def translate_to_en(text):
try:
return GoogleTranslator(source='auto', target='en').translate(text)
except:
return text
def translate_to_ar(text):
try:
return GoogleTranslator(source='auto', target='ar').translate(text)
except:
return text
# ==============================
# Intent Detection
# ==============================
INTENT_KEYWORDS = {
"creator": ["who built", "who made", "creator", "made by",
"من بناه", "من صنعه", "المنشئ", "من بنى"],
"built_year": ["when was it built", "year", "date",
"متى بني", "سنة", "تاريخ"],
"type": ["type", "what kind", "نوع", "ما نوع"],
"era": ["era", "period", "dynasty",
"العصر", "الفترة", "الحقبة", "عصر"],
"material": ["material", "made of",
"مم صنع", "مصنوع من", "المادة"],
"description": ["describe", "appearance", "look like",
"وصف", "كيف يبدو", "شكل"],
"importance": ["importance", "significance", "why important",
"الأهمية", "أهميته", "ليه مهم"],
"location_found": ["where was it found", "discovered",
"اين وجد", "مكان اكتشافه", "اكتشف"],
"current_location": ["where is", "current location", "located",
"اين يوجد", "يقع", "مكانه"],
"summary": ["tell me about", "overview", "summary", "brief",
"what is", "who is", "information",
"احكيلي", "أهم المعلومات", "نبذة", "معلومات"],
}
def detect_intents(q_en, q_ar=""):
q_en = (q_en or "").lower()
q_ar = (q_ar or "").lower()
detected_intents = []
for intent, keywords in INTENT_KEYWORDS.items():
if intent == "summary":
continue
for kw in keywords:
if kw in q_en or kw in q_ar:
detected_intents.append(intent)
break
if not detected_intents:
detected_intents.append("summary")
return detected_intents
# ==============================
# Response Generator
# ==============================
def generate_intent_response(artifact, intent, user_lang="en"):
name_en = artifact.get("name", "This artifact").replace("-", " ").replace("_", " ")
name = translate_to_ar(name_en) if user_lang == "ar" else name_en
value = artifact.get(intent, "Unknown")
if user_lang == "ar":
creator = translate_to_ar(str(artifact.get('creator', '')))
era = translate_to_ar(str(artifact.get('era', '')))
location = translate_to_ar(str(artifact.get('current_location', '')))
description = translate_to_ar(str(artifact.get('description', '')))
material = translate_to_ar(str(artifact.get('material', '')))
value = translate_to_ar(str(value))
templates = {
"creator": f"تم إنشاء {name} بواسطة {creator}.",
"built_year": f"تم بناء {name} في عام {value}.",
"type": f"{name} هو {value}.",
"era": f"يرجع {name} إلى عصر {era}.",
"material": f"{name} مصنوع من {material}.",
"description": f"يتميز {name} بأنه {description}، ويعكس أهمية كبيرة في تاريخ وحضارة مصر القديمة.",
"importance": f"تكمن أهمية {name} في أنه {value}.",
"location_found": f"تم اكتشاف {name} في {value}.",
"current_location": f"يوجد {name} حاليًا في {location}.",
"summary": f"يُعد {name} من أبرز المعالم الأثرية في مصر القديمة، حيث يتميز بأنه {description}. تم إنشاؤه بواسطة {creator}، ويرجع تاريخه إلى عصر {era}. ويقع حاليًا في {location}.",
}
else:
templates = {
"creator": f"{name} was created by {value}.",
"built_year": f"{name} was built around {value}.",
"type": f"{name} is a {value}.",
"era": f"{name} dates back to the {value}.",
"material": f"{name} is made of {value}.",
"description": f"{name} is characterized by {value}.",
"importance": f"The importance of {name}: {value}.",
"location_found": f"{name} was discovered in {value}.",
"current_location": f"{name} is currently located in {value}.",
"summary": f"{name} is one of the most significant Egyptian monuments.",
}
return templates.get(intent, f"{name}: {value}")
# ==============================
# Artifact Search (lazy index)
# ==============================
def find_artifact(q_ar, q_en):
model = get_embed_model()
index = build_index()
q_ar_n = q_ar.lower() if q_ar else ""
q_en_n = q_en.lower() if q_en else ""
query = q_en_n + " " + q_ar_n
q_vec = model.encode([query])
D, I = index.search(np.array(q_vec), k=5)
candidates = [_artifact_docs[i] for i in I[0]]
best_doc = None
best_score = 0
for doc in candidates:
name = doc.get("name", "").lower()
score = 0
if name in query:
score += 300
score += fuzz.ratio(name, query)
for token in name.split():
if token in query:
score += 50
for kw in doc.get("keywords", []):
if kw.lower() in query:
score += 80
if score > best_score:
best_score = score
best_doc = doc
if best_score < 70:
return None
return best_doc
# ==============================
# YOLO (lazy)
# ==============================
def detect_artifact(image):
model = get_yolo_model()
results = model(image)
result = results[0]
annotated = Image.fromarray(result.plot())
if result.boxes is None or len(result.boxes) == 0:
return None, annotated
best_idx = int(np.argmax(result.boxes.conf.cpu().numpy()))
class_id = int(result.boxes.cls[best_idx])
return result.names[class_id], annotated
# ==============================
# STT (lazy whisper)
# ==============================
def speech_to_text(audio_bytes):
if not audio_bytes:
return "", "en"
tmp_path = None
try:
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f:
f.write(audio_bytes)
tmp_path = f.name
model = get_whisper_model()
audio = whisper_lib.load_audio(tmp_path)
clip = whisper_lib.pad_or_trim(audio)
mel = whisper_lib.log_mel_spectrogram(clip).to(model.device)
_, probs = model.detect_language(mel)
det_lang = max(probs, key=probs.get)
res = model.transcribe(tmp_path, language=det_lang, fp16=False)
text = res.get("text", "")
return text, det_lang
finally:
if tmp_path and os.path.exists(tmp_path):
os.remove(tmp_path)
# ==============================
# TTS
# ==============================
def text_to_speech(text):
try:
path = os.path.join(tempfile.gettempdir(), f"tts_{uuid.uuid4().hex}.mp3")
gTTS(text=text, lang="ar" if is_arabic(text) else "en").save(path)
return path
except:
return None
def cleanup_audio_file(filepath):
if filepath and os.path.exists(filepath):
os.remove(filepath)
# ==============================
# Chatbot Core (UNCHANGED LOGIC)
# ==============================
def chatbot_updated(question, image=None):
if isinstance(question, bytes):
text, lang = speech_to_text(question)
else:
text = (question or "").strip()
lang = "ar" if is_arabic(text) else "en"
if not text:
return "Please provide a question." if lang == "en" else "من فضلك اكتب سؤالك."
q_en = translate_to_en(text) if lang == "ar" else text
artifact_from_image = None
detected_name = None
if image is not None:
detected_name, _ = detect_artifact(image)
if detected_name:
artifact_from_image = find_artifact("", detected_name)
artifact_from_text = find_artifact(text, q_en)
if artifact_from_image:
artifact = artifact_from_image if artifact_from_image else artifact_from_text
else:
artifact = artifact_from_text
if not artifact:
return "Artifact not found."
intents = detect_intents(q_en, text)
responses = []
for intent in intents:
responses.append(generate_intent_response(artifact, intent, lang))
final_response = " ".join(responses)
return final_response