planetLLM / main.py
abdulrhmanHelmy's picture
Create main.py
2de2573 verified
Raw
History Blame Contribute Delete
4.77 kB
import torch
import faiss
import numpy as np
import os
import re
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Dict
from transformers import AutoTokenizer, AutoModelForCausalLM
from sentence_transformers import SentenceTransformer
# --- 1. إعداد الـ API ---
app = FastAPI(title="Agricultural Expert API (Free Tier)")
# --- 2. تحميل الموديلات (نسخة خفيفة للـ CPU) ---
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
print("⏳ Loading Models on CPU (This might take a minute)...")
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map="cpu" # إجبار الموديل يشتغل على البروسيسور
)
embedding_model = SentenceTransformer("BAAI/bge-m3", device="cpu") # الـ Embedding كمان على الـ CPU
# --- 3. بناء الـ FAISS Index ---
DISEASES_FOLDER = "diseases"
all_chunks = []
all_embeddings = []
def split_text(text, chunk_size=600, chunk_overlap=100):
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end])
start += chunk_size - chunk_overlap
return chunks
if os.path.exists(DISEASES_FOLDER):
for filename in os.listdir(DISEASES_FOLDER):
if filename.endswith(".txt"):
with open(os.path.join(DISEASES_FOLDER, filename), "r", encoding="utf-8") as f:
text = f.read()
chunks = split_text(text)
embeddings = embedding_model.encode(chunks, convert_to_numpy=True)
all_chunks.extend(chunks)
all_embeddings.append(embeddings)
if all_embeddings:
all_embeddings = np.vstack(all_embeddings)
index = faiss.IndexFlatL2(all_embeddings.shape[1])
index.add(all_embeddings.astype('float32'))
print(f"✅ Index built successfully with {len(all_chunks)} chunks.")
else:
print("⚠️ Folder 'diseases' not found!")
# --- 4. هيكل الـ Request اللي الفرونت إند هيبعته ---
class ChatRequest(BaseModel):
question: str
chat_history: List[Dict[str, str]] = [] # الفرونت إند هيبعت الذاكرة هنا
k_results: int = 3
# --- 5. الـ Endpoint اللي هيستقبل الأسئلة ---
@app.post("/ask")
async def ask_ai_api(request: ChatRequest):
# 1. البحث في السياق
question_embedding = embedding_model.encode([request.question], convert_to_numpy=True)
distances, indices = index.search(question_embedding.astype('float32'), request.k_results)
context = "\n\n".join([all_chunks[i] for i in indices[0]])
# 2. بناء الرسائل بنظام "مرآة اللغة"
messages = [
{
"role": "system",
"content": (
"You are a professional agricultural assistant.\n"
"STRICT RULE: Always respond in the SAME LANGUAGE as the user's current question.\n"
"- If the user asks in English, your entire response MUST be in English.\n"
"- If the user asks in Arabic, your entire response MUST be in Arabic.\n"
"Use the provided context to answer accurately. Don't use Chinese."
)
}
]
# إضافة الذاكرة الجاية من الفرونت إند
messages.extend(request.chat_history)
# إضافة السؤال الحالي مع السياق
messages.append({
"role": "user",
"content": f"Context: {context}\n\nQuestion: {request.question}\n(أجب بشرح مفصل وجمل واضحة)"
})
# 3. تجهيز الـ Prompt للموديل
text_input = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer([text_input], return_tensors="pt").to(model.device)
# 4. التوليد (بإعدادات تمنع التقطيع)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=800,
temperature=0.5,
top_p=0.92,
repetition_penalty=1.05,
do_sample=True,
pad_token_id=tokenizer.eos_token_id
)
# استخراج النص
generated_ids = outputs[0][inputs.input_ids.shape[1]:]
answer = tokenizer.decode(generated_ids, skip_special_tokens=True).strip()
# فلتر اللغات الغريبة
answer = re.sub(r'[^\u0600-\u06FF\u0660-\u0669a-zA-Z0-9\s\.,!\?؟\-\%\(\)\:\/\*]', '', answer)
# الرد النهائي اللي هيرجع للفرونت إند كـ JSON
return {
"question": request.question,
"answer": answer,
"retrieved_context": context
}