Spaces:
Running
Running
File size: 2,814 Bytes
2e175db | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | """
API schemas (v1).
These are the public contract for the /v1/scan/image endpoint. Treat any change
to a field name, type, or required-ness as a breaking change.
Forward-compatibility notes
---------------------------
• `probabilities` is a fixed 4-class vector. Stage 1 only meaningfully populates
`authentic` and `ai_generated`; `deepfake` and `edited` start at 0.0 until
the corresponding detectors come online in Stage 2. Clients should treat 0.0
as "not assessed" for an unsigned-classifier slot — but the field is always
present so the schema itself never changes.
• `signals` is a list, currently with a single entry for the CLIP detector.
More detectors (frequency, face-swap) append to this list in later stages.
• `provenance` is always present. When C2PA is disabled or unavailable,
`c2pa_present` is False and `c2pa_valid` is None.
"""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
# The 4-class taxonomy. New classes must NOT be added without bumping API to v2.
Verdict = Literal["authentic", "ai_generated", "deepfake", "edited", "uncertain"]
class Probabilities(BaseModel):
"""Probability mass over the 4 mutually-exclusive classes.
Sums to 1.0 (allowing for floating-point rounding within ±1e-3).
"""
authentic: float = Field(ge=0.0, le=1.0)
ai_generated: float = Field(ge=0.0, le=1.0)
deepfake: float = Field(ge=0.0, le=1.0)
edited: float = Field(ge=0.0, le=1.0)
class DetectorSignal(BaseModel):
"""Per-detector contribution, surfaced for transparency / debugging.
`score` is the detector's own internal "fakeness" estimate in [0, 1];
its meaning depends on the detector. Aggregation into `probabilities`
happens in the ensemble layer.
"""
name: str
score: float = Field(ge=0.0, le=1.0)
notes: str | None = None
class Provenance(BaseModel):
"""C2PA / Content Credentials check.
A trustworthy C2PA manifest from a known camera or generator can short-
circuit the model — those signals are currently advisory only.
"""
c2pa_present: bool
c2pa_valid: bool | None = None # None → not checked / unverifiable
issuer: str | None = None # e.g. "Sony", "OpenAI", "Adobe"
claim_generator: str | None = None # raw claim_generator string from the manifest
class ScanResponse(BaseModel):
"""The /v1/scan/image response. This is the public contract."""
verdict: Verdict
confidence: float = Field(ge=0.0, le=1.0)
probabilities: Probabilities
signals: list[DetectorSignal]
provenance: Provenance
model_version: str
scan_id: str
latency_ms: float
class ErrorResponse(BaseModel):
"""Uniform error shape for non-2xx responses."""
error: str
detail: str | None = None
|