---
title: safety
---

<div class="python-reference">
<a id="fastvideo.entrypoints.streaming.prompt.safety-classes"></a>
<a id="fastvideo.entrypoints.streaming.prompt.safety-functions"></a>
<a id="fastvideo.entrypoints.streaming.prompt.safety.SafetyDecision-attributes"></a>
<a id="fastvideo.entrypoints.streaming.prompt.safety"></a>
<p>Optional prompt safety filter.</p>
<p>Uses a fastText classifier to score prompts against a banned-content
rubric. Only loaded when <code>ServeConfig.streaming.safety.enabled</code> is
True and fastText is installed — users who don't need it see no
runtime cost.</p>
<p>Install: <code>pip install fastvideo[prompt-safety]</code> (ships fasttext as an
optional extra) or install fasttext directly.</p>
<h2 id="fastvideo.entrypoints.streaming.prompt.safety.init_logger"><code>fastvideo.entrypoints.streaming.prompt.safety.init_logger</code></h2>
<pre class="doc-signature"><code>init_logger(name: str) -&gt; _FastvideoLogger</code></pre>
<p>The main purpose of this function is to ensure that loggers are
retrieved in such a way that we can be sure the root fastvideo logger has
already been configured.</p>
<h2 id="fastvideo.entrypoints.streaming.prompt.safety.SafetyDecision"><code>fastvideo.entrypoints.streaming.prompt.safety.SafetyDecision</code></h2>
<pre class="doc-signature"><code>SafetyDecision()</code></pre>

<details><summary>Source</summary><pre><code>class SafetyDecision(enum.Enum):
    ALLOW = &quot;allow&quot;
    BLOCK = &quot;block&quot;
    UNAVAILABLE = &quot;unavailable&quot;
    &quot;&quot;&quot;Returned when the classifier can&#x27;t run (not configured, fastText
    missing). Safety is opt-in; the server treats ``UNAVAILABLE`` as
    ``ALLOW`` but logs it so operators know the filter is off.&quot;&quot;&quot;</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.prompt.safety.SafetyDecision.UNAVAILABLE"><code>fastvideo.entrypoints.streaming.prompt.safety.SafetyDecision.UNAVAILABLE</code></h3>
<pre class="doc-signature"><code>UNAVAILABLE = &#x27;unavailable&#x27;</code></pre>
<p>Returned when the classifier can't run (not configured, fastText
missing). Safety is opt-in; the server treats <code>UNAVAILABLE</code> as
<code>ALLOW</code> but logs it so operators know the filter is off.</p>
<details><summary>Source</summary><pre><code>UNAVAILABLE = &quot;unavailable&quot;</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.prompt.safety.SafetyResult"><code>fastvideo.entrypoints.streaming.prompt.safety.SafetyResult</code></h2>
<pre class="doc-signature"><code>SafetyResult(prompt: str, decision: SafetyDecision, score: float = 0.0, label: str | None = None, reason: str | None = None)</code></pre>


<details><summary>Source</summary><pre><code>@dataclass
class SafetyResult:
    prompt: str
    decision: SafetyDecision
    score: float = 0.0
    label: str | None = None
    reason: str | None = None</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.prompt.safety.PromptSafetyFilter"><code>fastvideo.entrypoints.streaming.prompt.safety.PromptSafetyFilter</code></h2>
<pre class="doc-signature"><code>PromptSafetyFilter(*, classifier_path: str | None, enabled: bool = True, block_threshold: float = 0.5)</code></pre>
<p>Minimal fastText-backed prompt safety filter.</p>
<p>Loads the classifier lazily on first use so the streaming server
can construct the filter eagerly at startup without paying the
model-load cost when safety is disabled.</p>

<details><summary>Source</summary><pre><code>class PromptSafetyFilter:
    &quot;&quot;&quot;Minimal fastText-backed prompt safety filter.

    Loads the classifier lazily on first use so the streaming server
    can construct the filter eagerly at startup without paying the
    model-load cost when safety is disabled.
    &quot;&quot;&quot;

    def __init__(
        self,
        *,
        classifier_path: str | None,
        enabled: bool = True,
        block_threshold: float = 0.5,
    ) -&gt; None:
        self._classifier_path = classifier_path
        self._enabled = enabled
        self._block_threshold = block_threshold
        self._model: Any | None = None
        self._load_attempted = False
        self._load_lock = threading.Lock()

    @property
    def enabled(self) -&gt; bool:
        return self._enabled and self._classifier_path is not None

    def classify(self, prompt: str) -&gt; SafetyResult:
        if not self.enabled:
            return SafetyResult(
                prompt=prompt,
                decision=SafetyDecision.UNAVAILABLE,
                reason=&quot;safety filter not enabled&quot;,
            )
        model = self._ensure_loaded()
        if model is None:
            return SafetyResult(
                prompt=prompt,
                decision=SafetyDecision.UNAVAILABLE,
                reason=&quot;fastText model unavailable&quot;,
            )
        try:
            labels, probs = model.predict(prompt.replace(&quot;\n&quot;, &quot; &quot;), k=1)
        except Exception as exc:  # pragma: no cover - defensive
            logger.warning(&quot;safety: classifier failed: %s&quot;, exc)
            return SafetyResult(
                prompt=prompt,
                decision=SafetyDecision.UNAVAILABLE,
                reason=f&quot;classifier error: &#123;exc&#125;&quot;,
            )
        label = labels&#91;0&#93;.removeprefix(&quot;__label__&quot;) if labels else None
        score = float(probs&#91;0&#93;) if len(probs) else 0.0
        decision = (SafetyDecision.BLOCK if
                    (label == &quot;unsafe&quot; and score &gt;= self._block_threshold) else SafetyDecision.ALLOW)
        return SafetyResult(
            prompt=prompt,
            decision=decision,
            score=score,
            label=label,
        )

    def _ensure_loaded(self) -&gt; Any | None:
        if self._model is not None:
            return self._model
        if self._load_attempted:
            return None
        with self._load_lock:
            if self._model is not None:
                return self._model
            if self._load_attempted:
                return None
            self._load_attempted = True
            if self._classifier_path is None:
                return None
            try:
                import fasttext  # type: ignore&#91;import-not-found&#93;
            except ImportError:
                logger.warning(&quot;safety: fasttext not installed; safety filter disabled. &quot;
                               &quot;Install fastvideo&#91;prompt-safety&#93; to enable.&quot;)
                return None
            try:
                self._model = fasttext.load_model(self._classifier_path)
            except Exception as exc:  # pragma: no cover - requires real model
                logger.warning(&quot;safety: failed to load %s: %s&quot;, self._classifier_path, exc)
                return None
            return self._model</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.prompt.safety.PromptSafetyFilter.classify"><code>fastvideo.entrypoints.streaming.prompt.safety.PromptSafetyFilter.classify</code></h3>
<pre class="doc-signature"><code>classify(prompt: str) -&gt; SafetyResult</code></pre>

<details><summary>Source</summary><pre><code>def classify(self, prompt: str) -&gt; SafetyResult:
    if not self.enabled:
        return SafetyResult(
            prompt=prompt,
            decision=SafetyDecision.UNAVAILABLE,
            reason=&quot;safety filter not enabled&quot;,
        )
    model = self._ensure_loaded()
    if model is None:
        return SafetyResult(
            prompt=prompt,
            decision=SafetyDecision.UNAVAILABLE,
            reason=&quot;fastText model unavailable&quot;,
        )
    try:
        labels, probs = model.predict(prompt.replace(&quot;\n&quot;, &quot; &quot;), k=1)
    except Exception as exc:  # pragma: no cover - defensive
        logger.warning(&quot;safety: classifier failed: %s&quot;, exc)
        return SafetyResult(
            prompt=prompt,
            decision=SafetyDecision.UNAVAILABLE,
            reason=f&quot;classifier error: &#123;exc&#125;&quot;,
        )
    label = labels&#91;0&#93;.removeprefix(&quot;__label__&quot;) if labels else None
    score = float(probs&#91;0&#93;) if len(probs) else 0.0
    decision = (SafetyDecision.BLOCK if
                (label == &quot;unsafe&quot; and score &gt;= self._block_threshold) else SafetyDecision.ALLOW)
    return SafetyResult(
        prompt=prompt,
        decision=decision,
        score=score,
        label=label,
    )</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.prompt.safety.first_blocked"><code>fastvideo.entrypoints.streaming.prompt.safety.first_blocked</code></h2>
<pre class="doc-signature"><code>first_blocked(filter_: PromptSafetyFilter, prompts: list&#91;str&#93;) -&gt; SafetyResult | None</code></pre>
<p>Return the first prompt the filter blocks, or <code>None</code>.</p>
<details><summary>Source</summary><pre><code>def first_blocked(
    filter_: PromptSafetyFilter,
    prompts: list&#91;str&#93;,
) -&gt; SafetyResult | None:
    &quot;&quot;&quot;Return the first prompt the filter blocks, or ``None``.&quot;&quot;&quot;
    for prompt in prompts:
        result = filter_.classify(prompt)
        if result.decision is SafetyDecision.BLOCK:
            return result
    return None</code></pre></details>
</div>
