Skip to content

Understanding Google’s Content Safety Filters for Gemini Outputs

Core Concept LearningAugust 3, 20267 min read

A Gemini response that stops mid-sentence with finishReason: SAFETY is not a bug — it's the model's built-in content filter deciding the output crossed a harm threshold before it finished generating. Code that only reads response.text and assumes it's always complete will render a truncated, occasionally nonsensical fragment to the user with no indication anything was blocked, which is worse for trust than a clear "this response was filtered" message.

This guide covers what Gemini's safety filters actually score, how to read and tune safetySettings per harm category, and where the line sits between what Google's general-purpose filters catch and what your own application needs a domain-specific classifier for. The running example is a parenting-advice chatbot, where a false block on a benign medical question is as costly a failure as a genuinely harmful response getting through. For the broader safety-layer pattern this fits into, see safety classifiers for LLM apps and content moderation pipelines for AI products.

Two safety layers around a Gemini response
Two safety layers around a Gemini response

What the built-in filters actually score

Gemini's safety filtering evaluates generated content against a fixed set of harm categories — harassment, hate speech, sexually explicit content, and dangerous content are the standard categories exposed through the API — and assigns each a probability/severity score. Each category has a configurable block threshold (BLOCK_NONE through BLOCK_LOW_AND_ABOVE), and content scoring above the active threshold for any category causes the response to be blocked or truncated, surfaced via finishReason: SAFETY and per-category safetyRatings in the response.

The categories are intentionally general-purpose — they catch broad classes of harmful content across any application built on the model, not your specific product's policy. That's the important boundary to internalize: the filters are a baseline safety net, not a substitute for a policy layer that understands what "harmful" means specifically for a parenting-advice bot, a legal-document summarizer, or a kids' education app.

Quick reference

  • Standard harm categories exposed via the API include harassment, hate speech, sexually explicit, and dangerous content — check current API docs for the authoritative, up-to-date category list.
  • Each category carries its own score and threshold — a response can be blocked for one category while scoring low on all others.
  • The default thresholds are Google's general-purpose safety defaults, not a guarantee they match your product's specific risk tolerance in either direction.
  • Safety filtering applies to generated output, not just user input — the same request can produce a safe completion on one sampling and an unsafe-scoring one on a retry, since generation is not fully deterministic.

Remember this

The built-in filters score fixed, general-purpose harm categories with a tunable threshold — they are a baseline, not a domain-specific policy, and treating them as sufficient for a specialized product skips the classification your product actually needs.

Reading finishReason and tuning safetySettings

The failure mode most integrations ship with is checking response.text for truthiness and nothing else. A blocked response can return an empty string, a partial fragment, or (depending on the SDK version) throw — code that doesn't branch on finishReason treats all three the same as a normal completion, which means a safety block renders as a confusing blank or truncated message with zero context for the user or your logs.

A response blocked mid-stream by the safety filter
A response blocked mid-stream by the safety filter

Quick reference

  • Always check finishReason before rendering response.textSAFETY, RECITATION, and MAX_TOKENS all need different user-facing handling, not one generic fallback.
  • Log safetyRatings for every blocked response during development and early production — reviewing real blocks is how you calibrate whether your thresholds are too strict or too loose for your domain.
  • Tune thresholds per category deliberately, not uniformly — a parenting-advice bot might loosen the medical/dangerous-content threshold slightly (to answer legitimate questions) while keeping harassment strict.
  • BLOCK_NONE disables filtering for a category — use it only with a clear policy reason and your own compensating classifier, not as a default fix for false positives.
Reading text with no finishReason check
1const res = await ai.models.generateContent({2  model: "gemini-2.5-flash",3  contents: userQuestion,4});5 6return res.text; // If finishReason is SAFETY, this may be empty7                  // or a truncated fragment — rendered as-is to the user.
Explicit finishReason handling and tuned thresholds
1const res = await ai.models.generateContent({2  model: "gemini-2.5-flash",3  config: {4    safetySettings: [5      { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_MEDIUM_AND_ABOVE" },6      { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_ONLY_HIGH" },7      // Tuned per category to this product's risk profile — verify8      // current category names and threshold enum against the API docs.9    ],10  },11  contents: userQuestion,12});13 14const candidate = res.candidates?.[0];15if (candidate?.finishReason === "SAFETY") {16  // Expected: log the safetyRatings for review, and show a safe,17  // specific fallback — not a blank response.18  logSafetyBlock(userQuestion, candidate.safetyRatings);19  return { text: "I can't help with that request. Try rephrasing it, or ask something else.", blocked: true };20}21return { text: res.text, blocked: false };22// Break it: send a borderline medical question worded urgently and23// check whether it's blocked at your chosen thresholds — tune from there.

Remember this

finishReason: SAFETY is a distinct outcome from a normal completion, not an edge case — code that doesn't branch on it renders blocked responses as broken ones instead of handled ones.

The false-positive problem in a specialized domain

General-purpose safety filters are tuned for broad harm avoidance across every application built on the model, which means they occasionally block genuinely benign, domain-appropriate content — a parenting bot answering a question about infant fever thresholds can trip a dangerous-content-adjacent signal meant to catch something else entirely. The realistic failure here isn't a harmful response getting through; it's a legitimate, safety-critical answer getting silently blocked, leaving a parent with no answer at exactly the moment they needed one.

The fix is not disabling the filter category — that reopens the door to genuinely harmful content the filter exists to catch. The fix is building your own narrower classifier or allowlist logic for the specific domain-legitimate phrasings your product needs, layered on top of (not instead of) the built-in filter, plus a human-reviewable log of blocks so you can see the false-positive rate and adjust.

Built-in filters vs. an application-level safety classifier
Built-in filters vs. an application-level safety classifier

Quick reference

  • Distinguish a false positive (benign content blocked) from a false negative (harmful content that got through) — they need opposite fixes, and conflating them leads to loosening a threshold that was actually working.
  • Build a domain allowlist or rephrasing layer for known-legitimate but filter-adjacent phrasings (medical dosage questions, historical violence in an education context) rather than lowering the category threshold globally.
  • Route blocked-but-plausibly-legitimate responses to a lightweight secondary check or a rephrase-and-retry step before falling back to a generic "can't help" message.
  • Track your false-positive rate over a real sample of production blocks — a filter tuned purely from vendor defaults, never measured against your own domain, is an assumption, not a calibrated setting.

Remember this

A false positive in a specialized domain isn't a sign the filter is broken — it's a sign the filter is doing its general-purpose job, and your product needs its own narrower layer for the legitimate edge cases that job doesn't know about.

Where the built-in filter ends and yours begins

The decision rule: rely on Gemini's built-in filters for broad, general harm categories you don't want to reimplement (hate speech, harassment, sexual content, dangerous content) — building your own classifier for these from scratch is redundant and likely worse-calibrated than Google's, which is trained on far more data than any single product team has. Build your own classifier or policy layer for anything domain-specific the built-in categories don't cover: a legal-advice disclaimer requirement, a specific list of medications your product shouldn't discuss dosing for, or a brand-safety rule unrelated to any general harm category.

This is the same layering principle as any defense-in-depth security design: the built-in filter is one layer, not the whole system. A product that relies on it alone for domain-specific policy will eventually ship a response that's technically "safe" by Google's general categories but wrong by your product's own rules.

Quick reference

  • Use built-in categories for general harm; use your own classifier for domain-specific policy the general categories were never designed to cover.
  • Keep a versioned log of your safetySettings configuration alongside your prompt versioning — a threshold change is a behavior change and belongs in the same review process.
  • Pair automated filtering with a human escalation path for ambiguous blocks in a high-stakes domain (medical, legal, financial) rather than a single automated fallback message for everything.
  • Re-test your thresholds whenever you change model version — category scoring can shift between model releases even if you didn't touch safetySettings.

Remember this

Built-in filters and a domain classifier are complementary layers, not alternatives — skipping the domain layer because the built-in filter exists leaves exactly the gap a specialized product needs covered.

Key takeaway

Add explicit finishReason handling and tuned per-category safetySettings to the parenting-advice bot, plus a logged fallback message for any SAFETY-blocked response. Verify success by sending a clearly benign question and confirming a normal response, then sending a question worded to plausibly trip the dangerous-content category (an urgent medication-dosage question) and confirming your code detects the block and returns your fallback message rather than a blank or truncated one.

Then check the false-positive side: if the medication question was blocked, add a narrow allowlist rule or a rephrase step for that specific legitimate case, and confirm it now answers correctly without loosening the category threshold globally. Pass criterion: genuinely unsafe content is still blocked at your configured threshold, the previously false-positive benign question now answers correctly, and every block is logged with its safetyRatings for review.

Share:

Related Articles

Running a multimodal model on a phone or in a browser tab sounds like a pure win: no round trip, no per-call bill, and t

Read

The gap between a chatbot demo and a chatbot in production is not the model — it is everything wrapped around the model

Read

Gemini CLI (@google/gemini-cli) is an open-source terminal AI agent that brings Google's Gemini models directly into you

Read

Explore this topic

Keep learning

Follow a structured path or browse all courses to go deeper.