AI Tutorial
Home All Courses Blog Interview Prep ↗ Export PDF 0%
1
Curious builder 0 XP earned · 300 to level 2
0 days Finish a lesson to begin
Badge collection
0 of 5 unlocked
small wins to finish your path
Next lesson →

Guardrails and content safety

Export PDF

Every model call in Foundry passes through guardrails that score the input and output for harmful content and attacks. Understanding them matters twice: you must configure them to fit your use case, and your code must handle the moment they block something. This lesson explains every guardrail risk, severity levels, how to create a custom policy, how blocked requests look in code, and how to use Azure AI Content Safety directly.

Last updated: 01 Nov, 2026 · azure-ai-projects 2.x · Python 3.10+

Guardrails in one picture

Definition · Guardrail (content filter)

A policy of classifiers that run on the user input and the model output of every request to a deployment or agent. Each classifier detects a risk; if a detection meets the configured threshold, the request or completion is blocked or annotated.

Where guardrails run
prompt ──► [input guardrail]  hate · sexual · violence · self-harm · jailbreak · blocklists
                 │ blocked? → HTTP 400 (code: content_filter)
                 ▼
             model / agent ──► tool calls ──► [indirect attack check on documents/tool output]
                 │
                 ▼
          [output guardrail] hate · sexual · violence · self-harm · protected material · blocklists
                 │ blocked? → finish/incomplete reason: content_filter
                 ▼
             response + content-filter annotations

The risks a guardrail can detect

RiskApplies toWhat it detects
Hate and fairnessInput + outputAttacks or discrimination based on identity (race, gender, religion, disability, and so on)
SexualInput + outputSexual content, from suggestive to explicit
ViolenceInput + outputDescriptions, threats, or glorification of violence and weapons
Self-harmInput + outputContent about hurting oneself, including instructions or encouragement
Jailbreak (user prompt attacks)InputAttempts to make the model ignore its rules (“pretend you have no restrictions…”)
Indirect attacks (XPIA)Documents and tool resultsInstructions hidden inside retrieved documents, emails, or web pages
Protected material — textOutputKnown copyrighted text such as song lyrics, articles, and recipes
Protected material — codeOutputCode matching public repositories, with citation information
Groundedness (detection)OutputClaims not supported by the provided sources
PIIOutputPersonal data such as emails, phone numbers, and IDs
Custom blocklistsInput + outputYour own banned words or patterns (competitor names, internal codenames)

Severity levels

The four harm categories return a severity: safe, low, medium, or high (0, 2, 4, 6 in the Content Safety API). You choose the threshold at which each category is blocked:

Block thresholdBlocksEffect
LowLow, medium, and highStrictest; may block legitimate content such as history or medical text
Medium (default)Medium and highBalanced default for most apps
HighHigh onlyPermissive; for specialised apps that need sensitive content
Off / annotate onlyNothing (scores still returned)Requires Microsoft approval for modified content filtering

This offline simulation shows how thresholds change decisions for the same scores:

Python
SEVERITY = {0: "safe", 2: "low", 4: "medium", 6: "high"}
LEVEL = {"low": 2, "medium": 4, "high": 6}


def decide(scores: dict[str, int], block_at: dict[str, str]) -> tuple[str, list[str]]:
    """Block when any category's severity reaches its configured threshold."""
    hits = [f"{cat}={SEVERITY[sev]}" for cat, sev in scores.items()
            if sev >= LEVEL[block_at[cat]]]
    return ("blocked" if hits else "allowed"), hits


default_policy = {"hate": "medium", "sexual": "medium", "violence": "medium", "self_harm": "medium"}
strict_policy = {**default_policy, "violence": "low"}

samples = {
    "recipe question": {"hate": 0, "sexual": 0, "violence": 0, "self_harm": 0},
    "war history essay": {"hate": 0, "sexual": 0, "violence": 2, "self_harm": 0},
    "graphic threat": {"hate": 2, "sexual": 0, "violence": 6, "self_harm": 0},
}
for name, scores in samples.items():
    d1, _ = decide(scores, default_policy)
    d2, hits = decide(scores, strict_policy)
    print(f"{name:<18} default={d1:<8} strict={d2:<8} {hits}")
recipe question default=allowed strict=allowed [] war history essay default=allowed strict=blocked ['violence=low'] graphic threat default=blocked strict=blocked ['violence=high']

A history essay mentioning battles scores low for violence. The default policy allows it; a strict policy built for a children's app blocks it. There is no universally right setting — it depends on your users.

Create and assign a custom guardrail

  1. In the portal, open Guardrails + controls and create a new guardrail policy.
  2. For each harm category, set the input and output thresholds.
  3. Turn on Prompt Shields for jailbreak and indirect attacks, and choose whether to block or annotate.
  4. Enable protected material detection for text and code if you generate long-form content or code.
  5. Add blocklists for terms specific to your business.
  6. Assign the policy to deployments and agents. Every request to them now uses it.
Note

Turning filters fully off, or annotate-only for harm categories, requires an approved application for modified content filtering. Managed customers can request it for legitimate use cases such as content moderation tools or medical research.

Handling blocked requests in code

A guardrail block is not a crash; it is a normal outcome your app must handle politely.

Python
import openai

FRIENDLY = "Sorry, I can't help with that request. Please rephrase or contact support."


def safe_ask(client, deployment: str, user_text: str) -> str:
    try:
        response = client.responses.create(model=deployment, input=user_text)
    except openai.BadRequestError as err:
        if getattr(err, "code", None) == "content_filter":   # the PROMPT was blocked
            log_safety_event("input_blocked", err)
            return FRIENDLY
        raise

    if response.status == "incomplete" and response.incomplete_details.reason == "content_filter":
        log_safety_event("output_blocked", response.id)        # the COMPLETION was blocked
        return FRIENDLY
    return response.output_text

With Chat Completions, the equivalent output signal is finish_reason == "content_filter", and the response carries content_filter_results with per-category severities. Log blocks (without storing harmful text unnecessarily) so you can tune thresholds and spot abuse.

Prompt Shields and indirect attacks

Definition · Prompt injection

An attack where text the model reads contains instructions that override the developer's instructions. Direct injection comes from the user (“ignore previous instructions…”). Indirect injection hides inside content the app retrieves — a web page, a PDF, an email — and is especially dangerous for agents with tools.

Prompt Shields detect both. Layer them with design defences:

  • Keep tool permissions minimal and require approval for actions with side effects.
  • Clearly delimit retrieved content in prompts and tell the model it is data, not instructions.
  • Never put secrets in the system prompt — assume a determined user can extract it.
  • Validate tool arguments in code regardless of what the model says.

Azure AI Content Safety API

The same classifiers are available as a standalone service. Use it to moderate user-generated content that never reaches a model, to check text before storing it, or to add an extra check in a pipeline.

Live Azure call

This example calls your Azure resources, so it needs a deployed project and a signed-in identity. Output will differ on every run.

Python
from azure.ai.contentsafety import ContentSafetyClient
from azure.ai.contentsafety.models import AnalyzeTextOptions
from azure.identity import DefaultAzureCredential

safety = ContentSafetyClient(
    endpoint=f"https://{resource_name}.cognitiveservices.azure.com",
    credential=DefaultAzureCredential(),          # needs Cognitive Services User
)
result = safety.analyze_text(AnalyzeTextOptions(text="I will hurt you if you do that again"))
for item in result.categories_analysis:
    print(f"{item.category:<10} severity={item.severity}")
Hate severity=0 SelfHarm severity=0 Sexual severity=0 Violence severity=4

Responsible AI beyond filters

Guardrails are one layer. Microsoft's responsible AI practice — and most regulations, such as the EU AI Act — expect a full process:

StageWhat you do
MapList the harms your app could cause and who could be affected; red-team it.
MeasureEvaluate safety and quality systematically (previous lesson).
MitigateLayers: model choice, guardrails, instructions, grounding, tool limits, UX (disclosures, feedback buttons).
OperateMonitor production, review incidents, re-evaluate after changes, keep documentation.
Try it
  1. Add a children_policy to the simulation that blocks every category at low, and test it on all three samples.
  2. Send a jailbreak-style prompt to your deployment and inspect the error body.
  3. Create a blocklist containing a fake codename and confirm a prompt containing it is blocked.
Key takeaways
  • Guardrails score both inputs and outputs of every request to a deployment or agent.
  • Harm categories (hate, sexual, violence, self-harm) have severities; the default blocks medium and above.
  • Prompt Shields catch jailbreaks and indirect attacks; protected material and groundedness detection protect outputs.
  • Handle blocks gracefully: 400 content_filter for prompts, content_filter finish or incomplete reason for outputs.
  • Guardrails are one layer — combine with least-privilege tools, grounding, evaluation, and monitoring.