droid-code-checker / README.md
legitcoconut
Claude Opus 5 (1M context)
Add ZeroGPU support alongside CPU
5c893da
|
Raw History Blame Contribute Delete
8.11 kB

A newer version of the Gradio SDK is available: 6.31.0

Upgrade
metadata
title: Droid Code Checker
emoji: πŸ•΅οΈ
colorFrom: indigo
colorTo: pink
sdk: gradio
sdk_version: 6.26.0
python_version: '3.12'
app_file: app.py
pinned: false
license: apache-2.0
short_description: Code authorship detection for Code Arena

Code Authorship Checker

Code-authorship detection with project-droid/DroidDetect-Large (ModernBERT-large, Apache-2.0) plus a static forensics layer that looks for the physical traces of pasted text.

Runs unmodified on either free tier β€” CPU basic or ZeroGPU. detector.py switches on the SPACES_ZERO_GPU env var HF sets, so changing hardware needs no code edit. ZeroGPU is much faster per call but is capped by a daily GPU-seconds quota charged to the caller; for batch checking during a live event, CPU basic is the better choice. See DEPLOY.md for the numbers.

Replaces the CodeT5+ Space. Same endpoint name, same inputs, same four legacy response fields β€” Code Arena keeps working with no changes at all.

What it returns

Old CodeT5+ Space This
Classes human / AI human / AI / AI-refined / AI-adversarial
Verdict levels 1 (binary) 3 (binary, ternary, 4-way)
Context 512 tokens (truncated) 2048 (model ceiling is 8192)
Extras β€” forensic signals, style embedding, inconclusive flag

The three levels

The model makes one prediction over four classes. It's read back at three granularities, because they are not equally trustworthy:

Level Values Large's weighted F1
binary HUMAN Β· MACHINE ~97%
ternary HUMAN Β· MACHINE Β· REFINED ~88.8%
detailed the raw 4 classes lowest

Show moderators the ternary label. It answers the question a contest actually cares about β€” did the participant write this, did a model write it, or did they start it and let a model finish β€” and it's the deepest level the model is reliable at. The 4-way split additionally separates adversarial (model output deliberately styled to look hand-typed) from ordinary model output; that's the hardest distinction and the one to treat with most suspicion.

MACHINE_GENERATED_ADVERSARIAL folds into MACHINE at the ternary level, because model output styled to look hand-written is still model output. It's also the class where DroidDetect earns its keep: it holds ~0.92 recall there, where GPTZero drops to ~0.10.

{
  "verdict": "AI-Generated",
  "ai_probability": 0.18,
  "token_count": 412,
  "truncated": false,

  "binary":  { "label": "MACHINE", "confidence": 0.82,
               "probs": { "HUMAN": 0.18, "MACHINE": 0.82 } },
  "ternary": { "label": "REFINED", "confidence": 0.71,
               "probs": { "HUMAN": 0.18, "MACHINE": 0.11, "REFINED": 0.71 },
               "meaning": "Participant started it, a model completed or rewrote it" },
  "detailed": {
    "label": "MACHINE_REFINED",
    "confidence": 0.71,
    "probs": {
      "HUMAN_GENERATED": 0.18,
      "MACHINE_GENERATED": 0.09,
      "MACHINE_REFINED": 0.71,
      "MACHINE_GENERATED_ADVERSARIAL": 0.02
    }
  },
  "inconclusive": false,

  "label": "MACHINE_REFINED",
  "confidence": 0.71,
  "human_probability": 0.18,
  "model_ai_score": 82,
  "embedding": [0.031, -0.118, "…254 more"],

  "features": { "comment_ratio": 0.44, "ai_style_comments": 4, "smart_punct": 1,
                "mixed_indent": false, "crlf": false, "…": "20 fields" },
  "signals": [
    { "code": "unicode_punctuation", "weight": 35,
      "detail": "Curly quotes/dashes β€” pasted from a rendered page or chat UI, not typed" }
  ],
  "forensic_score": 75,

  "score": 80,
  "model": "project-droid/DroidDetect-Large",
  "pooling": "mean",
  "max_length": 2048
}

Field notes:

  • ai_probability is a human score. It's the probability of the HUMAN_GENERATED class, kept under the old (misleading) name so Code Arena keeps behaving identically. human_probability is the same number, honestly named.
  • model_ai_score is 100 Γ— P(MACHINE) from the binary collapse. Three of the four classes are machine, so unlike the CodeT5+ Space this is a genuine AI probability rather than a distance from a hand-tuned threshold.
  • inconclusive is true when the top class is under 0.45 β€” the model has nothing to say. Show it as "no signal", never as a verdict. A flat 0.3 / 0.3 / 0.2 / 0.2 is noise, not evidence.
  • embedding is a 256-d style vector (128-d on Base). Cosine-compare one participant's submissions against each other β€” the outlier is the answer they didn't write the way they write. It separates human from machine, not author from author, so it's a consistency signal, not a plagiarism matcher.
  • score fuses model and forensics for ranking only. Peer similarity and submission timing are deliberately absent: they belong to the platform, which holds every submission and every timestamp.

API

Every request must carry the token. An invalid or missing one returns {"error": "Invalid or missing token"} and the model never runs. Empty input returns {"error": "No code provided"}; oversized input is rejected by character count before tokenization.

from gradio_client import Client

client = Client("your-name/code-authorship-checker")

result = client.predict(
    code="int main(){return 0;}",
    token="YOUR_API_TOKEN",
    api_name="/detect",
)
print(result["ternary"]["label"], result["ternary"]["confidence"])

Or over plain HTTP β€” the two-step Gradio flow Code Arena uses:

EVENT=$(curl -s -X POST "$SPACE/gradio_api/call/detect" \
  -H 'content-type: application/json' \
  -d '{"data": ["print(1)", "YOUR_API_TOKEN"]}' | jq -r .event_id)

curl -s -N "$SPACE/gradio_api/call/detect/$EVENT"

Configuration

All optional β€” the Space boots and works with none of them set.

The API token is not one of these β€” it's hardcoded as API_TOKEN at the top of app.py. Change it there and push. Because it lives in the source, keep the Space private, and rotate the value if the repo is ever made public.

Variable Default Purpose
DROID_REPO project-droid/DroidDetect-Large …/DroidDetect-Base is ~4Γ— faster for ~2 F1 less
GPU_DURATION 30 ZeroGPU only: seconds the GPU is held per call
POOLING mean mean or cls β€” run smoke_test.py once to confirm
MAX_LENGTH 2048 Tokens per request (model ceiling 8192; higher = slower)
HUMAN_THRESHOLD 0.5 Below this p_human, the legacy verdict line says AI-Generated
INCONCLUSIVE_BELOW 0.45 Top-class probability under which inconclusive is set
MODEL_WEIGHT 0.7 Model vs forensics weighting in the fused score
MAX_CHARS 200000 Reject paste bombs before tokenizing
TORCH_THREADS 2 Matches the free tier's 2 vCPUs

Accuracy, honestly

Reported by the Droid paper for Large: 97.0% weighted F1 binary and 88.8% ternary β€” against 91.6% for GPTSniffer and 76.5% for Fast-DetectGPT.

Those are in-distribution. On unseen languages and domains this class of detector falls to roughly 0.85 macro-F1, and under combined domain and language shift, benchmarks put it near 0.21. The errors are asymmetric: detectors reliably spot AI code but flag unusual human code as AI β€” which in a contest means your strongest participants. Treat every number here as one input to a human decision, never as a verdict on its own.

Files

File Role
app.py Gradio UI + token-guarded detect endpoint
detector.py Checkpoint loader, TLModel wrapper, label collapsing
forensics.py Static paste-trace analysis β€” standard library only
labels.py Class taxonomy + the binary/ternary collapses
smoke_test.py First-run check; resolves the POOLING setting
test_forensics.py Self-check for the forensics layer, no torch needed
test_collapse.py Self-check for the ternary/binary collapse
test_auth.py Self-check for the API-token guard
DEPLOY.md Deployment walkthrough