lurien-matrix / src /api /schemas.py
imDrizzle's picture
Super-squash branch 'main' using huggingface_hub
e84e8e5
Raw
History Blame Contribute Delete
7.17 kB
"""
Pydantic Models β€” Request/Response Schemas
All API request validation and response serialization models.
"""
from typing import Optional
from datetime import datetime
from pydantic import BaseModel, Field, field_validator
# ── Request Schemas ───────────────────────────────────────────
class CheckRequest(BaseModel):
"""Request body for POST /v1/check"""
prompt: str = Field(..., min_length=1, max_length=10000)
context: Optional[str] = Field(None, max_length=5000)
threshold: Optional[float] = Field(None, ge=0.0, le=1.0)
metadata: Optional[dict] = None
app_context: Optional[str] = Field("general", max_length=100)
custom_canary: Optional[str] = Field(None, max_length=256)
@field_validator("prompt")
@classmethod
def prompt_not_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError("prompt cannot be empty or whitespace only")
return v
@field_validator("metadata")
@classmethod
def metadata_size_limit(cls, v: Optional[dict]) -> Optional[dict]:
if v is not None:
import json
if len(json.dumps(v)) > 2048:
raise ValueError("metadata exceeds 2KB size limit")
return v
class BatchCheckRequest(BaseModel):
"""Request body for POST /v1/check/batch"""
prompts: list[str] = Field(..., min_length=1, max_length=50)
class CreateKeyRequest(BaseModel):
"""Request body for POST /v1/keys"""
name: str = Field(..., max_length=100)
app_context: Optional[str] = Field(default="general", max_length=100)
custom_canary: Optional[str] = Field(default=None, max_length=256)
custom_intent_examples: Optional[list[str]] = Field(default=None)
use_openai_moderation: bool = Field(default=False)
# ── Response Schemas ──────────────────────────────────────────
class LayerCanary(BaseModel):
ran: bool = True
reason: Optional[str] = None
triggered: Optional[bool] = None
score: Optional[float] = None
latency_ms: Optional[float] = None
matched_canary: Optional[str] = None
class LayerRuleBased(BaseModel):
ran: bool = True
reason: Optional[str] = None
triggered: Optional[bool] = None
matched_pattern: Optional[str] = None
attack_category: Optional[str] = None
score: Optional[float] = None
latency_ms: Optional[float] = None
class HeuristicSignalsResponse(BaseModel):
instruction_density: float
length_anomaly: float
role_assignment_score: float
system_context_injection: float
encoding_entropy: float
repetition_score: float
class LayerHeuristic(BaseModel):
ran: bool = True
reason: Optional[str] = None
triggered: Optional[bool] = None
score: Optional[float] = None
signals: Optional[HeuristicSignalsResponse] = None
latency_ms: Optional[float] = None
class LayerEmbeddingSimilarity(BaseModel):
ran: bool = True
reason: Optional[str] = None
triggered: Optional[bool] = None
similarity_score: Optional[float] = None
nearest_attack_preview: Optional[str] = None
latency_ms: Optional[float] = None
class LayerMLClassifier(BaseModel):
ran: bool = True
reason: Optional[str] = None
triggered: Optional[bool] = None
attack_class: Optional[str] = None
confidence: Optional[float] = None
all_scores: Optional[dict[str, float]] = None
latency_ms: Optional[float] = None
class LayerContextPolicy(BaseModel):
ran: bool = True
reason: Optional[str] = None
triggered: Optional[bool] = None
app_context: Optional[str] = None
similarity_to_intent: Optional[float] = None
latency_ms: Optional[float] = None
score: Optional[float] = None
class LayerOpenAIModeration(BaseModel):
ran: bool = True
reason: Optional[str] = None
triggered: Optional[bool] = None
score: Optional[float] = None
flagged_category: Optional[str] = None
latency_ms: Optional[float] = None
class LayersResponse(BaseModel):
canary: LayerCanary
rule_based: LayerRuleBased
heuristic: LayerHeuristic
embedding_similarity: LayerEmbeddingSimilarity
openai_moderation: Optional[LayerOpenAIModeration] = None
ml_classifier: LayerMLClassifier
context_policy: LayerContextPolicy
class CheckResponse(BaseModel):
"""Full response for POST /v1/check"""
request_id: str
timestamp: str
safe: bool
risk_score: float
attack_type: Optional[str] = None
confidence: float
flagged_layer: Optional[str] = None
flagged_pattern: Optional[str] = None
threshold_used: float
layers: LayersResponse
processing_time_ms: float
model_version: str
metadata: dict = Field(default_factory=dict)
warnings: list[str] = Field(default_factory=list)
class BatchCheckResponse(BaseModel):
results: list[CheckResponse]
batch_id: str
class FirewallBlockReport(BaseModel):
"""Report returned when proxy blocks a request (403)."""
error: str = "prompt_blocked"
firewall_report: dict
class ApiKeyResponse(BaseModel):
api_key: Optional[str] = None # Only shown on creation
key_id: str
name: str
created_at: datetime
is_active: bool
monthly_usage: int
total_blocked: int
total_checks: int
app_context: Optional[str] = "general"
custom_canary: Optional[str] = None
use_openai_moderation: bool = False
class StatsResponse(BaseModel):
total_checks: int
flagged_count: int
blocked_count: int
flag_rate: float
block_rate: float
attack_breakdown: dict[str, int]
requests_today: int
requests_this_month: int
avg_processing_time_ms: float
top_flagged_patterns: list[dict]
layer_effectiveness: dict[str, float]
class HealthResponse(BaseModel):
status: str
classifier_loaded: bool
classifier_latency_ms: Optional[float] = None
db_connected: bool
redis_connected: bool
uptime_seconds: int
model_version: str
class ErrorResponse(BaseModel):
error: str
detail: Optional[str] = None
retry_after_seconds: Optional[int] = None
class RateLimitResponse(BaseModel):
error: str = "rate_limit_exceeded"
limit_type: str
retry_after_seconds: int
# ── Auth Schemas ──────────────────────────────────────────────
class UserCreate(BaseModel):
email: str = Field(..., max_length=255)
password: str = Field(..., min_length=6, max_length=128)
class UserLogin(BaseModel):
email: str
password: str
class UserResponse(BaseModel):
id: str
email: str
created_at: datetime
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: UserResponse