deepdetection / modules /m5_explain.py
akagtag's picture
Fix Gradio entrypoint and switch explainability to Gemini
604836a
from __future__ import annotations
import os
class ExplainModule:
"""Google Gemini text explainer."""
def __init__(self):
self.model = os.environ.get("GEMINI_EXPLAIN_MODEL", "gemini-3.1-flash-lite-preview")
self.client = None
try:
from google import genai
self.client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY", ""))
except Exception as exc:
print(f"ExplainModule using Gemini fallback: {exc}")
def explain(
self,
fakescore,
s1,
s2,
s3,
weights,
attribution,
segments,
top_generator,
) -> str:
verdict = "FAKE" if fakescore > 0.5 else "REAL"
confidence = (
"high"
if abs(fakescore - 0.5) > 0.3
else "moderate"
if abs(fakescore - 0.5) > 0.15
else "low"
)
seg_text = ""
if segments:
seg_text = "Flagged timestamps: " + ", ".join(
[f"{segment['time']}s (score={segment['score']})" for segment in segments[:5]]
)
attr_text = ""
if attribution:
top3 = sorted(attribution.items(), key=lambda item: -item[1])[:3]
attr_text = "Top generators: " + ", ".join(
[f"{name}: {prob * 100:.1f}%" for name, prob in top3]
)
prompt = f"""You are a forensic AI analyst. Analyze these deepfake detection results. Be specific about evidence.
Results:
- Verdict: {verdict} (FakeScore: {fakescore:.3f}, confidence: {confidence})
- Lip-Sync (M1): {s1:.3f} (weight: {weights.get('lip_sync', 'N/A')})
- Fingerprint (M2): {s2:.3f} (weight: {weights.get('fingerprint', 'N/A')})
- Graph-GNN (M3): {s3:.3f} (weight: {weights.get('graph_gnn', 'N/A')})
{seg_text}
{attr_text}
- Most likely generator: {top_generator}
Write 3-5 sentences. Reference specific scores and timestamps."""
try:
if self.client is None:
raise RuntimeError("Gemini client unavailable")
from google.genai import types
response = self.client.models.generate_content(
model=self.model,
contents=prompt,
config=types.GenerateContentConfig(
system_instruction="You are a forensic deepfake analyst. Be precise.",
max_output_tokens=300,
temperature=0.3,
),
)
return (response.text or "").strip()
except Exception:
return self._fallback(verdict, fakescore, s1, s2, s3, top_generator, confidence)
def _fallback(self, verdict, fakescore, s1, s2, s3, top_gen, conf):
if verdict == "FAKE":
return (
f"Video classified as {verdict} with {conf} confidence "
f"(FakeScore: {fakescore:.3f}). "
f"Lip-sync scored {s1:.2f}, indicating "
f"{'significant' if s1 > 0.7 else 'moderate' if s1 > 0.5 else 'minimal'} "
f"audio-visual inconsistency. "
f"Style fingerprinting scored {s2:.2f}, top attribution: {top_gen}. "
f"Graph analysis scored {s3:.2f}."
)
return (
f"Video classified as {verdict} with {conf} confidence "
f"(FakeScore: {fakescore:.3f}). "
f"All modules returned scores below detection threshold."
)