Implement filters to classify unsafe or disallowed visual content
AI-103 › Unit 3: Implement computer vision solutions › Implement responsible AI for multimodal content › Implement filters to classify unsafe or disallowed visual content
Implement filters to classify unsafe or disallowed visual content
Visual content moderation inherits the text filtering model and changes three things: the severity scale is coarser for images, the per-request override does not apply, and blocklists — being text — cannot see pixels at all. Knowing which of those bites in a given scenario is most of this objective.
Why This Matters
Image severities are trimmed. Image analysis "only returns severities 0, 2, 4, and 6", while image with text supports the full 0–7. Thresholding logic written for text breaks silently.
x-policy-id stops working. The per-request filter override "is not available for image input (chat with images) scenarios" — so per-caller variation must move to separate deployments.
Blocklists are a text control. They match terms. A prohibited symbol in an image is not a term, which is why that requirement needs a different instrument entirely.
Prerequisites
- The four harm categories and the four severity levels.
- That filters are created at the resource level and associated with deployments.
- Annotate-only as a measurement mode, and the approval rule on completions.
- That image generation moderates the prompt and the output.
Learning Objectives
By the end of this lesson you will be able to:
- Apply the severity scale correct to each modality.
- Configure filtering for image input and image output.
- Explain why
x-policy-idcannot be used and what replaces it. - Use annotate only to measure before enforcing.
- Recognise requirements no built-in filter satisfies.
Building Blocks
The categories. Content Safety "recognizes four distinct categories of objectionable content": Hate and Fairness, Sexual, Violence, Self-Harm, and "classification can be multi-labeled".
The severity scales by modality.
| Modality | Scale |
|---|---|
| Text | Full 0–7; can return trimmed 0/2/4/6 if requested |
| Image | Trimmed only — 0, 2, 4, 6 |
| Image with text (multimodal) | Full 0–7 |
The filtering system. Four levels — safe, low, medium, high — with the default configuration filtering at the medium threshold for all four categories on both prompts and completions. Safe-level content "is labeled in annotation output but isn't subject to filtering and isn't configurable".
Annotate only. "Runs the respective model and returns annotations via API response, but it will not filter content." Available for prompts; for completions if approved, since turning filters "partially or fully off on completions" requires approval.
Scope. Filters are configured at the resource level and "associated with one or more deployments". The x-policy-id request override exists for text but "is not available for image input (chat with images) scenarios".
Generation moderation. Image and video generation moderate the prompt and the output. Sora 2 additionally "blocks all IP and photorealistic content", rejects copyrighted characters and music, cannot generate real people including public figures, rejects input images with faces of humans, and produces only content suitable for audiences under 18.
In Content Understanding. Filter results are surfaced "directly from the Foundry model deployment it uses", included as a content_filters array, and tuned on the Guardrails instance of that deployment — including switching "from blocking to annotating mode".
Where the filter sits
| Attribute | |||
|---|---|---|---|
| Configured on | The deployment's filter | The deployment's filter | The Guardrails instance of your model deployment |
| Per-request override | Not available | Not applicable | Not applicable |
| Result surfaced as | A block or annotations | No image returned |
|
| Vary per caller by | Separate deployments | Separate deployments | Separate deployments |
Deep Dive
The scale that changes, and why it is not a downgrade
Text supports the full 0–7 and can optionally return a trimmed 0/2/4/6 where "each two adjacent levels are mapped to a single level". Image supports only the trimmed scale. Image with text supports the full 0–7.
The reasoning trap is assuming the ceiling was lowered for images. It was not — multimodal returns 7, so nothing is being reserved. The image classifier reports at coarser resolution, which is a different statement.
The practical consequence is in code. A pipeline that routes on severity >= 5 works on text and never fires on images, because 5 is not a value the image classifier returns. Thresholds must be expressed against the values the modality actually produces — and a system handling both should not share one numeric threshold across them.
Note also that the filtering system's four named levels (safe, low, medium, high) are a separate vocabulary from the API's numbers. You set thresholds in the named levels; you receive numbers from the API. Conflating them produces confidently wrong answers.
Configuring visual content filtering
Start from the defaults
Four categories at the medium threshold on both prompts and completions — no configuration required.
Losing the per-request override
For text, x-policy-id names a custom filter configuration and "will override the deployment-level content filtering configuration for the specific API call". It is how a multi-tenant service applies stricter settings for one customer without duplicating deployments.
That mechanism disappears with images: it "is not available for image input (chat with images) scenarios", and the default deployment filter is used instead.
Two consequences follow.
Per-caller variation becomes a deployment concern. If customers genuinely require different thresholds and images are in scope, you need separate deployments, each with its own associated filter configuration, and routing logic in front.
A working text design can break on adding images. The failure is quiet — the header is accepted, the call succeeds, and the stricter policy simply did not apply. Any scenario that describes per-tenant filtering working "until we added image upload" is describing this.
Annotate only, as a measurement tool
Enforcement decisions are better made with data, and annotate-only is how you get it.
It "runs the respective model and returns annotations via API response, but it will not filter content". Running a representative sample through it shows the severity distribution — which categories appear, at what levels, and how much traffic a stricter threshold would block.
Two details matter. Safe-level content is annotated but not filtered and "isn't configurable", so annotations exist even where nothing would be blocked — making this a distribution signal rather than a block log. And annotate-only on completions requires approval, since it is a form of turning filtering off there; tightening thresholds afterwards is self-service.
The same idea appears inside Content Understanding: the Guardrails instance can be switched "from blocking to annotating mode", so the analyze response carries a content_filters array your workflow can act on rather than the platform refusing outright.
What the built-in filters do not cover
The four harm categories are fixed, and this is the boundary most scenarios probe.
They cannot be extended. There is no way to add "our prohibited symbol list" as a fifth category. Partner-specific, brand-specific, and regulatory-specific visual rules sit outside the harm taxonomy entirely.
Protected material is a different question. It detects known copyrighted content in output — text and code, both GA and on by default. It is not a brand-compliance control and not a provenance control.
Provenance is separate again. Content Credentials, implementing C2PA, record that content was AI-generated and by which model, embedded so they travel with the file.
Generation-side policy is model-level. Sora 2's restrictions — no real people including public figures, input images with faces rejected, no copyrighted characters or music, under-18-suitable only, blocks all IP and photorealistic content — are properties of the model, not settings you configure. A scenario asking to relax them is describing something outside the configuration surface.
Worked Examples
Example 1 — a threshold that never fires. A moderation pipeline routes anything with severity 5 or above to human review. It works for text; images never trigger it.
The image classifier returns only 0, 2, 4, and 6, so 5 is never produced. Express thresholds against values the modality actually returns. Text supports the full 0–7, and image with text does too — so nothing is reserved for multimodal; the image model simply reports at coarser resolution.
Example 2 — per-tenant strictness, then images. A service applies stricter filtering for one customer using x-policy-id, then adds image upload and the stricter policy stops applying.
x-policy-id "is not available for image input (chat with images) scenarios", so the deployment's filter governs. The call still succeeds, which is why it fails quietly. Move the variation to separate deployments with different associated filter configurations.
Example 3 — a prohibited symbol list. A partner requires that a defined set of symbols never appears in generated images.
Not a filter configuration. The harm categories cannot be extended, and a blocklist matches terms, not pixels. This needs a classifier over the output — an Azure ML AutoML detection model, or a Content Understanding classify field — plus review. Pair with Content Credentials if provenance is also required.
Visual Explanations
The three scales:
What applies where:
Common Mistakes
Sharing one numeric threshold across text and image. Image returns only 0/2/4/6.
Reading the image trim as a lowered ceiling. Multimodal returns 7.
Confusing the four named levels with the API's numbers.
Assuming x-policy-id works with image input. It does not, and it fails quietly.
Expecting a blocklist to catch a symbol or logo. Blocklists match terms.
Trying to add a fifth harm category. The taxonomy is fixed.
Confusing protected material with brand compliance or provenance.
Forgetting that annotate-only on completions needs approval.
Retrying a refused generation unchanged. The same prompt gives the same result.
Practice Exercises
- Give the severity scale for each of the three modalities, and say what the image trim does not mean.
- Why does per-tenant filtering break when image input is added, and what is the fix?
- What does annotate-only do, and what approval does it need?
- A partner supplies a list of prohibited symbols. Why is no filter setting sufficient?
- Name three visual policy needs the built-in filters do not cover.
▶Answers
- Text — full 0–7, trimmed optional. Image — trimmed only: 0, 2, 4, 6. Image with text — full 0–7. The trim does not mean a lowered ceiling: multimodal returns 7, so nothing is reserved; the image classifier simply reports at coarser resolution.
- The
x-policy-idper-request override "is not available for image input (chat with images) scenarios", so the deployment's filter governs and the call still succeeds — a quiet failure. Fix by using separate deployments, each with its own associated filter configuration, and routing in front. - It "runs the respective model and returns annotations via API response, but it will not filter content" — used to measure the severity distribution before tightening a threshold, or to let the application decide. On completions it requires approval, since it is a form of turning filtering off there.
- Because the harm categories cannot be extended and a blocklist matches terms, not pixels. A prohibited symbol is visual, so it needs a classifier — an Azure ML AutoML detection model or a Content Understanding
classifyfield — with review. - Brand and partner-specific visual rules (symbols, logos, mark usage); provenance — which needs Content Credentials (C2PA); and model-level generation policy such as Sora 2's blocking of real people, faces in input images, copyrighted characters and music — properties of the model, not settings.
Summary & Concept Map
Visual filtering inherits the text model and diverges in three places. The four harm categories filter at the medium threshold on both prompts and completions by default, configured at the resource and associated with deployments — but the x-policy-id per-request override is not available for image input, so per-caller variation becomes separate deployments, and its absence fails quietly. Severity vocabularies must be kept straight: the filtering system uses safe/low/medium/high, while the API returns numbers where image is trimmed to 0/2/4/6 and image with text is full 0–7 — a coarser report, not a lower ceiling. Annotate only measures the distribution before enforcement, needing approval on completions, and appears inside Content Understanding as a Guardrails setting surfacing a content_filters array. And the boundary matters: blocklists match terms, not pixels, the taxonomy cannot be extended, and brand rules, provenance, and model-level generation policy all live outside it.
Sources and freshness
Written against current Microsoft Learn documentation for the AI-103 skills measured (16 April 2026), reviewed 2026-08-20. Microsoft Learn controls every changing product contract — availability, preview status, quotas, limits, regional support, naming, and retirement dates all move independently of this lesson. Where a scenario turns on a specific number or a GA/preview boundary, confirm it against the product's own page before relying on it.