didi-lot1-ai/ai_platform/modules/forensic_features/docs/INTEGRATION.md

12 KiB
Raw Permalink Blame History

Integrare în aplicația ta LLM

Document care arată EXACT cum apelezi acest serviciu din aplicația ta care folosește deja un LLM multimodal (Qwen Vision, GPT-4V, Claude Sonnet).

Pattern de bază

┌─────────────────────────┐
│ User uploadează video   │
│ în aplicația ta         │
└───────────┬─────────────┘
            │
            ├─ Pas 1: Trimite videoul la Forensic Features API
            │         → primești evidence_text + base64 imagini
            │
            ├─ Pas 2: Construiește prompt-ul TĂU existent
            │         (cu tipologiile tale, instrucțiunile tale)
            │         + APPEND evidence_text
            │
            ├─ Pas 3: Atașează la apelul LLM:
            │         - imaginile originale ale userului
            │         - imaginile noastre (heatmap-uri)
            │
            └─ Pas 4: LLM-ul tău returnează verdictul
                      cu signal îmbogățit de la noi

Exemple complete

Python — Qwen Vision (compatible cu OpenAI ChatCompletions API)

import requests
import base64
from pathlib import Path

FORENSIC_API = "http://localhost:8085"
QWEN_API     = "http://your-qwen-host:14011/v1/chat/completions"

def analyze_video(video_path: str, your_typologies: list[str]) -> dict:
    """
    Apelează Forensic Features API → construiește prompt → apelează Qwen.
    """

    # ── Pas 1: Forensic Features API ──────────────────────────────────
    with open(video_path, "rb") as f:
        resp = requests.post(
            f"{FORENSIC_API}/api/forensic-evidence",
            files={"video": f},
            data={
                "encode_images": "1",   # cu base64 pentru atașare directă
                # Optional: "modules": "m25,m27,m28" pt doar 3 module
            },
            timeout=300,
        )
    resp.raise_for_status()
    forensic = resp.json()

    # ── Pas 2: Construiește mesajul multimodal pentru LLM ─────────────
    # PROMPT-UL TĂU EXISTENT — așa cum îl ai acum
    your_prompt = f"""
    Ești un analist video forensic. Analizează acest video și verifică:

    Tipologii de elemente vizuale de căutat:
    {chr(10).join(f"- {t}" for t in your_typologies)}

    Răspunde structurat...
    """

    # Append evidence-ul nostru
    augmented_prompt = your_prompt + "\n\n" + forensic["evidence_text"]

    # ── Pas 3: Construiește content multimodal ───────────────────────
    # Lista de imagini originale ale userului (din aplicația ta) +
    # imaginile noastre forensice (heatmap-uri, plot-uri)
    content = [
        {"type": "text", "text": augmented_prompt},
    ]

    # Imaginile TALE existente — pe care le aveai deja în pipeline
    your_keyframes = extract_keyframes_yourself(video_path)  # funcția ta existentă
    for img_path in your_keyframes:
        img_b64 = base64.b64encode(open(img_path, "rb").read()).decode()
        content.append({
            "type": "image_url",
            "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"},
        })

    # Imaginile NOASTRE forensice — heatmap-uri zone suspect, lighting arrows
    for img in forensic["images"]:
        if "data_url" in img:
            content.append({
                "type": "image_url",
                "image_url": {"url": img["data_url"]},
            })

    # ── Pas 4: Apelează LLM-ul tău ───────────────────────────────────
    qwen_payload = {
        "model": "qwen3.5",
        "messages": [
            {"role": "system", "content": "Ești analist forensic. Răspunzi în JSON valid."},
            {"role": "user", "content": content},
        ],
        "max_tokens": 1500,
        "temperature": 0.1,
    }

    qwen_resp = requests.post(QWEN_API, json=qwen_payload, timeout=120)
    qwen_resp.raise_for_status()

    # Parse JSON din răspuns
    import json
    llm_text = qwen_resp.json()["choices"][0]["message"]["content"]
    verdict = json.loads(llm_text)

    return {
        "verdict":            verdict,        # ce decide LLM-ul tău
        "forensic_evidence":  forensic,       # pentru debug / audit
        "augmented_prompt":   augmented_prompt,  # pentru replicare
    }


# Usage
result = analyze_video(
    "user_uploaded.mp4",
    your_typologies=["face_swap_visible", "background_anomaly", "logo_overlay"],
)
print(result["verdict"])

JavaScript / Node — direct fetch

async function analyzeWithForensic(videoFile) {
    // Pas 1: Forensic Features
    const fd = new FormData();
    fd.append("video", videoFile);
    fd.append("encode_images", "1");

    const forensicResp = await fetch("http://localhost:8085/api/forensic-evidence", {
        method: "POST",
        body: fd,
    });
    const forensic = await forensicResp.json();

    // Pas 2: Construiește content multimodal pentru LLM-ul tău
    const content = [
        { type: "text", text: yourExistingPrompt + "\n\n" + forensic.evidence_text }
    ];

    // Imaginile tale + ale noastre
    for (const img of yourKeyframes) {
        content.push({ type: "image_url", image_url: { url: img.dataUrl } });
    }
    for (const img of forensic.images) {
        if (img.data_url) {
            content.push({ type: "image_url", image_url: { url: img.data_url } });
        }
    }

    // Pas 3: Apelează LLM-ul tău
    const llmResp = await callYourLLM({ content });
    return llmResp;
}

Pattern async pentru video lung

Pe video >30 secunde, procesarea poate dura 1-5 minute. Folosește async mode:

import time

def analyze_long_video_async(video_path):
    # Submit
    with open(video_path, "rb") as f:
        resp = requests.post(
            f"{FORENSIC_API}/api/forensic-evidence",
            files={"video": f},
            data={"async_mode": "1"},
        )
    job_id = resp.json()["job_id"]
    print(f"Job submitted: {job_id}")

    # Poll
    while True:
        status_resp = requests.get(f"{FORENSIC_API}/api/status/{job_id}")
        status = status_resp.json()
        print(f"Status: {status['status']}{status['progress']}")

        if status["status"] == "done":
            break
        if status["status"] == "error":
            raise Exception(f"Forensic pipeline failed: {status}")

        time.sleep(5)

    # Retrieve
    result_resp = requests.get(f"{FORENSIC_API}/api/result/{job_id}")
    return result_resp.json()

Cum interpretează LLM-ul tău evidence-ul

Când inserezi evidence_text în prompt, LLM-ul vede ceva de genul:

FORENSIC EVIDENCE (objective measurements you cannot recompute)
======================================================================

OVERALL VERDICT: FAKE (score=0.67, confidence=0.78)
  Detectors active: 4, no signal: 0, disagreement: 0.005

Individual detectors:
----------------------------------------------------------------------
[m25 Physiology] score=0.63 (INCERT) confidence=0.41 contrib=+0.091
  - Pulse: 82 BPM, SNR=1.1 dB (no plausible cardiac signal)
  - Blink count: 1 over 5.1s (natural)
  Visuals: m25_pulse_signal.png, m25_blink_timeline.png

[m27 AI-Generated Image Detector] score=0.78 (FAKE) confidence=1.00 contrib=+0.231
  - JPEG-recon: 0.78 (above 0.65 = AI suspect)
  - NPR: 0.48
  Visuals: m27_score_timeline.png

[m28 Forgery Localization Heatmap] score=0.62 (INCERT) confidence=1.00 contrib=+0.237
  - Forgery score: 0.62 (peak=0.90, boundary_mean=0.137) — intermediate
  - Frames with face: 5/5
  Visuals: m28_heatmap_0000.png, ..., m28_heatmap_0040.png

[m29 Lighting 3D Consistency] score=0.61 (INCERT) confidence=0.76
  - Face vs scene lighting: 95° (mismatch >90°, suspect compus)
  - Catchlights: nedetectabile (ochi închiși/ochelari/rezoluție mică)
  Visuals: m29_lighting_0000.png, ..., m29_lighting_0030.png

======================================================================
HOW TO USE FORENSIC EVIDENCE ABOVE:
- These are objective numerical measurements that you CANNOT recompute from
  images alone. They are produced by classical signal-processing detectors...
- For each detector that flags FAKE, search the keyframes for the visual
  artifact that explains the score...
======================================================================

LLM-ul are toate aceste informații + imaginile reale + tipologiile tale. Combinat cu reasoning-ul lui semantic, decide singur cu signal MULT mai bogat decât doar din imagine.

Best practices

1. Cache rezultatul forensic per video

Forensic features sunt deterministe pe același video. Cache prin hash SHA256 al fișierului — economie de zeci de secunde per request repetat.

import hashlib

def video_hash(path):
    with open(path, "rb") as f:
        return hashlib.sha256(f.read()).hexdigest()[:16]

# Cache in Redis sau SQLite cu key = video_hash

2. Selectează module relevante per tip conținut

Nu rula toate cele 5 mereu:

  • Imagine statică (jpg/png) → m27 + m28 (rest sunt no-op temporale)
  • Talking head video → m25 + m26 + m28 + m29
  • AI-generated landscape (fără față) → m27 doar
  • Screen recording → m24 (din pipeline vechi v3, NU în această versiune)

Setezi cu -F "modules=m27,m28".

3. Truncare evidence_text pe LLM cu context mic

Dacă LLM-ul tău are context window mic (<8K), poți cere doar summary:

# În prompt, în loc de evidence_text complet (1500-3000 chars), folosește:
short_evidence = f"""
Forensic signals on this video:
- Overall: {forensic['fusion']['label']} (score={forensic['fusion']['score']:.2f})
- m25 Physiology: {forensic['modules']['m25']['summary']['primary_label']}
- m27 AI Detector: {forensic['modules']['m27']['summary']['primary_label']}
- m28 Blending: {forensic['modules']['m28']['summary']['primary_label']}
"""

4. Atașează DOAR cele mai relevante PNG-uri

Pe LLM cu limite multimodal (4-6 imagini per call), nu trimite toate 12-15 din output. Filtrează:

# Doar PNG-uri din module flagged FAKE
relevant_images = [
    img for img in forensic["images"]
    if forensic["modules"][img["tool_id"]]["summary"]["primary_label"] == "FAKE"
]

5. Loghează verdictul + evidence pentru audit

# Salvează atât verdictul LLM cât și evidence-ul nostru
# pentru audit ulterior și calibrare
audit_log.write({
    "video_id":         video_hash(video_path),
    "forensic_fusion":  forensic["fusion"],
    "llm_verdict":      verdict,
    "timestamp":        datetime.utcnow().isoformat(),
})

Health check și monitoring

# Verifică serviciul e disponibil înainte de a procesa
def is_forensic_healthy():
    try:
        r = requests.get(f"{FORENSIC_API}/health", timeout=5)
        return r.status_code == 200 and r.json().get("status") == "ok"
    except Exception:
        return False

# Fallback graceful dacă serviciul e down
if not is_forensic_healthy():
    logger.warning("Forensic features API down, proceeding without augmentation")
    # Doar apel LLM cu prompt-ul tău original, fără evidence
else:
    # Apel complet cu augmentation

Limitări la integrare

  1. Cost de timp: +5-90s pe request (depinde de durata video). Pentru UX, folosește async mode cu indicator de progres.

  2. Cost de tokens LLM: evidence_text adaugă ~500-1000 tokens. Imaginile noastre ~12-15 imagini × cost per image LLM.

  3. Determinism: forensic features sunt deterministe, dar LLM-ul nu. Pe același video, evidence e mereu același, dar verdict LLM poate varia.

  4. Limită upload: 2 GB max. Video peste asta — split sau downscale înainte.

Test live

# Verifică serviciu
curl http://localhost:8085/health

# Test cu un video real
curl -X POST http://localhost:8085/api/forensic-evidence \
     -F "video=@your_test_video.mp4" \
     -o response.json

# Extrage doar partea text pentru LLM
python -c "import json; print(json.load(open('response.json'))['evidence_text'])"

# Numără imaginile generate
python -c "import json; print(f'{len(json.load(open(\"response.json\"))[\"images\"])} PNG-uri pentru LLM')"