Spaces:
Running on Zero
Running on Zero
|
Download README.md from legitcoconut/droid-code-checker: direct link, hf CLI and curl.
- Browser
- Download file 8.11 kB
-
https://huggingface.co/spaces/legitcoconut/droid-code-checker/resolve/main/README.md
- Command line
-
hf download hf://spaces/legitcoconut/droid-code-checker/README.md
-
curl -L -o README.md https://huggingface.co/spaces/legitcoconut/droid-code-checker/resolve/main/README.md
8.11 kB
| 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. | |
| ```json | |
| { | |
| "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. | |
| ```python | |
| 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: | |
| ```bash | |
| 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 | | |