from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Any, Protocol class ModerationError(RuntimeError): """Base error for content-moderation failures.""" class ModerationUnavailableError(ModerationError): """Provider could not return a verdict (down / timeout / not implemented). The orchestration layer converts this into a fail-closed BLOCK or a fail-open PASS depending on ``MODERATION_FAIL_CLOSED`` policy. """ class ModerationConfigError(ModerationError): """Provider is misconfigured (missing credentials / biz type / endpoint).""" class Verdict(str, Enum): PASS = "pass" # 放行 REVIEW = "review" # 建议人工复审(是否等同拦截由 policy 决定) BLOCK = "block" # 拦截 @dataclass(frozen=True) class ModerationResult: """Normalized moderation verdict, decoupled from any vendor's response shape. NOTE: ``raw`` is only for debugging in-process; never persist it verbatim (may contain the moderated content / vendor internals). Compliance logging stores a summary (verdict + labels + request_id), not ``raw``. """ verdict: Verdict labels: tuple[str, ...] = () score: int | None = None keywords: tuple[str, ...] = () request_id: str = "" raw: Any = field(default=None, repr=False, compare=False) class ModerationProvider(Protocol): def moderate_text(self, text: str, *, biz_type: str = "") -> ModerationResult: ...