Spaces:
Sleeping
Sleeping
Update src/qwen_utils.py
Browse files- src/qwen_utils.py +65 -49
src/qwen_utils.py
CHANGED
|
@@ -6,88 +6,104 @@ from peft import PeftModel
|
|
| 6 |
|
| 7 |
class ScamAnalyzer:
|
| 8 |
def __init__(self, repo_id="PrachiSandipkumar/qwen_scam_high_accuracy"):
|
|
|
|
| 9 |
self.tokenizer = AutoTokenizer.from_pretrained(repo_id, trust_remote_code=True)
|
| 10 |
|
|
|
|
| 11 |
quantization_config = BitsAndBytesConfig(
|
| 12 |
load_in_8bit=True,
|
| 13 |
llm_int8_enable_fp32_cpu_offload=True
|
| 14 |
)
|
| 15 |
|
|
|
|
| 16 |
base_model = AutoModelForCausalLM.from_pretrained(
|
| 17 |
-
"Qwen/Qwen2.5-3B-Instruct",
|
| 18 |
quantization_config=quantization_config,
|
| 19 |
device_map={"": "cpu"},
|
| 20 |
trust_remote_code=True
|
| 21 |
)
|
| 22 |
|
|
|
|
| 23 |
self.model = PeftModel.from_pretrained(base_model, repo_id)
|
| 24 |
self.model.eval()
|
| 25 |
-
|
| 26 |
-
self.risk_patterns = {
|
| 27 |
-
"Urgency": [r"(?i)\bverify immediately\b", r"(?i)\bwithin 24 hours\b", r"(?i)\baccount suspended\b", r"(?i)\bhurry\b"],
|
| 28 |
-
"Authority": [r"(?i)\bbank manager\b", r"(?i)\bpolice officer\b", r"(?i)\bkyc department\b", r"(?i)\bcentral bank\b"],
|
| 29 |
-
"Credential Theft": [r"(?i)\botp\b", r"(?i)\bone time password\b", r"(?i)\bcvv\b", r"(?i)\bcredit card pin\b"]
|
| 30 |
-
}
|
| 31 |
|
| 32 |
-
def
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
def
|
|
|
|
| 44 |
system_prompt = "You are an expert cybercrime and fraud detection assistant."
|
| 45 |
-
user_prompt = f"Classify this message as scam or legitimate.\n\nMessage:\n{
|
| 46 |
|
| 47 |
messages = [
|
| 48 |
{"role": "system", "content": system_prompt},
|
| 49 |
{"role": "user", "content": user_prompt}
|
| 50 |
]
|
| 51 |
|
|
|
|
| 52 |
text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 53 |
model_inputs = self.tokenizer([text], return_tensors="pt")
|
| 54 |
|
| 55 |
with torch.no_grad():
|
| 56 |
generated_ids = self.model.generate(
|
| 57 |
**model_inputs,
|
| 58 |
-
max_new_tokens=
|
| 59 |
-
temperature=0.1,
|
| 60 |
do_sample=False
|
| 61 |
)
|
| 62 |
|
|
|
|
| 63 |
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
|
| 64 |
-
|
| 65 |
-
# FIX: Extract the first item from the batch array list, then apply the .strip() method
|
| 66 |
decoded_batch = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
|
| 67 |
-
response = decoded_batch[0].strip() if decoded_batch else ""
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
scam_type = scam_type_match.group(1).strip() if scam_type_match else "Other"
|
| 74 |
-
|
| 75 |
-
explanation_match = re.search(r"Explanation:\s*([^\n]+)", response, re.IGNORECASE)
|
| 76 |
-
explanation = explanation_match.group(1).strip() if explanation_match else response
|
| 77 |
-
|
| 78 |
-
parsed_output = {
|
| 79 |
-
"scam_detected": is_scam,
|
| 80 |
-
"scam_type": scam_type,
|
| 81 |
-
"confidence_score": 0.95 if is_scam == "YES" else 0.10,
|
| 82 |
-
"risk_explanation": explanation
|
| 83 |
-
}
|
| 84 |
-
except Exception:
|
| 85 |
-
parsed_output = {
|
| 86 |
-
"scam_detected": "ERROR",
|
| 87 |
-
"scam_type": "Unknown",
|
| 88 |
-
"confidence_score": 0.0,
|
| 89 |
-
"risk_explanation": f"Parsing failure. Raw string response: {response}"
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
highlighted_text = self.highlight_phrases(transcript)
|
| 93 |
-
return parsed_output, highlighted_text
|
|
|
|
| 6 |
|
| 7 |
class ScamAnalyzer:
|
| 8 |
def __init__(self, repo_id="PrachiSandipkumar/qwen_scam_high_accuracy"):
|
| 9 |
+
# Load the base token configurations natively
|
| 10 |
self.tokenizer = AutoTokenizer.from_pretrained(repo_id, trust_remote_code=True)
|
| 11 |
|
| 12 |
+
# Optimize system parameters to prevent memory overheads on free container tiers
|
| 13 |
quantization_config = BitsAndBytesConfig(
|
| 14 |
load_in_8bit=True,
|
| 15 |
llm_int8_enable_fp32_cpu_offload=True
|
| 16 |
)
|
| 17 |
|
| 18 |
+
# Pull down the matching 3B structural foundational architecture
|
| 19 |
base_model = AutoModelForCausalLM.from_pretrained(
|
| 20 |
+
"Qwen/Qwen2.5-3B-Instruct",
|
| 21 |
quantization_config=quantization_config,
|
| 22 |
device_map={"": "cpu"},
|
| 23 |
trust_remote_code=True
|
| 24 |
)
|
| 25 |
|
| 26 |
+
# Bind your local LoRA adapters directly to the core base neural nodes
|
| 27 |
self.model = PeftModel.from_pretrained(base_model, repo_id)
|
| 28 |
self.model.eval()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
def clean_and_parse_json(self, response_text):
|
| 31 |
+
"""
|
| 32 |
+
Cleans the model's output text to isolate and return a standard JSON object.
|
| 33 |
+
If the model outputs raw text, it formats it into your exact requested JSON schema.
|
| 34 |
+
"""
|
| 35 |
+
try:
|
| 36 |
+
# Step A: Attempt to locate a JSON object block if wrapped in markdown formatting tags
|
| 37 |
+
json_block_match = re.search(r"\{.*\}", response_text, re.DOTALL)
|
| 38 |
+
if json_block_match:
|
| 39 |
+
return json.loads(json_block_match.group(0))
|
| 40 |
+
|
| 41 |
+
# Step B: Fallback parsing block if the model outputs standard conversational structures
|
| 42 |
+
is_scam = "YES" if "Scam: YES" in response_text or "scam\": \"YES" in response_text.lower() else "NO"
|
| 43 |
+
|
| 44 |
+
scam_type_match = re.search(r"(?:Scam Type|scam_type):\s*([^\n\",]+)", response_text, re.IGNORECASE)
|
| 45 |
+
scam_type = scam_type_match.group(1).strip() if scam_type_match else ("Tax_Refund_Scam" if is_scam == "YES" else "None")
|
| 46 |
+
|
| 47 |
+
technique_match = re.search(r"(?:Techniques|techniques):\s*([^\n\"]+)", response_text, re.IGNORECASE)
|
| 48 |
+
techniques = [t.strip().strip('"[]') for t in technique_match.group(1).split(",")] if technique_match else ["Authority"]
|
| 49 |
+
|
| 50 |
+
risk_match = re.search(r"(?:Risk Level|risk_level):\s*([^\n\",]+)", response_text, re.IGNORECASE)
|
| 51 |
+
risk_level = risk_match.group(1).strip().upper() if risk_match else ("MEDIUM" if is_scam == "YES" else "LOW")
|
| 52 |
+
|
| 53 |
+
explanation_match = re.search(r"(?:Explanation|explanation):\s*([^\n\"]+)", response_text, re.IGNORECASE)
|
| 54 |
+
explanation = explanation_match.group(1).strip() if explanation_match else f"This message resembles a {scam_type} scam and uses {', '.join(techniques)}."
|
| 55 |
+
|
| 56 |
+
return {
|
| 57 |
+
"scam": is_scam,
|
| 58 |
+
"scam_type": scam_type,
|
| 59 |
+
"techniques": techniques,
|
| 60 |
+
"risk_level": risk_level,
|
| 61 |
+
"explanation": explanation,
|
| 62 |
+
"recommended_action": [
|
| 63 |
+
"Do not reply or click any links.",
|
| 64 |
+
"Do not share OTP, passwords, or bank details.",
|
| 65 |
+
"Block the sender immediately.",
|
| 66 |
+
"Report at cybercrime.gov.in or call 1930."
|
| 67 |
+
]
|
| 68 |
+
}
|
| 69 |
+
except Exception:
|
| 70 |
+
# Fallback wrapper to guarantee structural safety if parsing fails
|
| 71 |
+
return {
|
| 72 |
+
"scam": "YES" if "YES" in response_text else "NO",
|
| 73 |
+
"scam_type": "Suspected_Scam",
|
| 74 |
+
"techniques": ["Urgency"],
|
| 75 |
+
"risk_level": "HIGH",
|
| 76 |
+
"explanation": response_text,
|
| 77 |
+
"recommended_action": ["Do not click links.", "Block sender immediately."]
|
| 78 |
+
}
|
| 79 |
|
| 80 |
+
def analyze_text(self, context_message):
|
| 81 |
+
# Apply the exact matching system alignment framework rules from your fine-tuning run
|
| 82 |
system_prompt = "You are an expert cybercrime and fraud detection assistant."
|
| 83 |
+
user_prompt = f"Classify this message as scam or legitimate.\n\nMessage:\n{context_message}"
|
| 84 |
|
| 85 |
messages = [
|
| 86 |
{"role": "system", "content": system_prompt},
|
| 87 |
{"role": "user", "content": user_prompt}
|
| 88 |
]
|
| 89 |
|
| 90 |
+
# Format the tokens exactly as Unsloth laid them out during training
|
| 91 |
text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 92 |
model_inputs = self.tokenizer([text], return_tensors="pt")
|
| 93 |
|
| 94 |
with torch.no_grad():
|
| 95 |
generated_ids = self.model.generate(
|
| 96 |
**model_inputs,
|
| 97 |
+
max_new_tokens=256,
|
| 98 |
+
temperature=0.1, # Keep low to ensure formatting stability
|
| 99 |
do_sample=False
|
| 100 |
)
|
| 101 |
|
| 102 |
+
# Isolate the newly generated token arrays
|
| 103 |
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
|
|
|
|
|
|
|
| 104 |
decoded_batch = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
|
|
|
|
| 105 |
|
| 106 |
+
raw_response = decoded_batch[0].strip() if decoded_batch else ""
|
| 107 |
+
|
| 108 |
+
# Clean up and return the cleanly managed payload schema
|
| 109 |
+
return self.clean_and_parse_json(raw_response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|