File size: 4,391 Bytes
71c1ad2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# app/api/schemas/responses.py
# Pydantic response models for API endpoints

from pydantic import BaseModel, Field
from typing import Optional, Literal, Any
from datetime import datetime


class DecisionDetail(BaseModel):
    """Details of the moderation decision."""
    action: Literal["ALLOWED", "WARNING", "BLOCKED"]
    reason: str
    severity: str
    should_alert_parent: bool = False
    escalation_notes: Optional[str] = None


class DeepAnalysisDetail(BaseModel):
    """Details from the deep analysis stage (only present for HIGH risk)."""
    is_confirmed: bool
    severity: str
    reasoning: str
    categories: list[str] = []
    recommended_action: str
    confidence: float
    clip_scores: dict = {}


class RiskDetail(BaseModel):
    """Breakdown of risk scoring."""
    score: float
    level: Literal["LOW", "MEDIUM", "HIGH"]
    components: dict = {}
    repeat_offender: bool = False


class FilterDetail(BaseModel):
    """Fast filter stage output."""
    is_flagged: bool
    scores: dict[str, float] = {}
    max_score: float
    max_label: str
    categories: list[str] = []


class AnalysisResponse(BaseModel):
    """
    Unified response for all /analyze/* endpoints.

    This is the primary contract between the AI engine
    and the Node.js backend (and any external consumers).
    """
    request_id: str = Field(..., description="Unique request identifier")
    input_type: Literal["text", "image", "video"]
    status: Literal["ALLOWED", "WARNING", "BLOCKED"]
    risk_level: Literal["LOW", "MEDIUM", "HIGH"]
    risk_score: float = Field(..., ge=0, le=100, description="Composite risk score 0-100")
    categories: list[str] = Field(default_factory=list, description="Detected abuse categories")
    confidence: float = Field(..., ge=0, le=1, description="Overall confidence 0-1")

    # Detailed breakdowns
    decision: DecisionDetail
    risk_detail: RiskDetail
    filter_detail: FilterDetail
    deep_analysis: Optional[DeepAnalysisDetail] = None

    # Metadata
    processing_time_ms: int = Field(..., description="Total processing time in milliseconds")
    trace_id: Optional[str] = Field(None, description="LangSmith trace ID for observability")
    cached: bool = Field(False, description="Whether this result was served from cache")

    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "request_id": "req_abc123",
                    "input_type": "text",
                    "status": "WARNING",
                    "risk_level": "MEDIUM",
                    "risk_score": 45.2,
                    "categories": ["insult", "toxic"],
                    "confidence": 0.82,
                    "decision": {
                        "action": "WARNING",
                        "reason": "Content flagged as potentially harmful",
                        "severity": "medium",
                        "should_alert_parent": False,
                    },
                    "risk_detail": {
                        "score": 45.2,
                        "level": "MEDIUM",
                        "components": {
                            "base_score": 42.0,
                            "multi_category_penalty": 3.2,
                            "repeat_offender_boost": 0.0,
                        },
                        "repeat_offender": False,
                    },
                    "filter_detail": {
                        "is_flagged": True,
                        "scores": {"toxic": 0.78, "insult": 0.65},
                        "max_score": 0.78,
                        "max_label": "toxic",
                        "categories": ["toxic", "insult"],
                    },
                    "deep_analysis": None,
                    "processing_time_ms": 156,
                    "trace_id": None,
                    "cached": False,
                }
            ]
        }
    }


class HealthResponse(BaseModel):
    """Health check response."""
    status: str
    version: str
    models: dict[str, bool]
    services: dict[str, bool]
    uptime_seconds: float


class HistoryResponse(BaseModel):
    """Moderation history response."""
    user_id: str
    total: int
    results: list[dict[str, Any]]


class ErrorResponse(BaseModel):
    """Standard error response."""
    error: str
    detail: str
    request_id: Optional[str] = None