AI-103: Text Analysis, PII, Content Safety and Translation with Foundry
Language model text analysis is the work of pulling entities, topics, summaries, sentiment, safety and sensitive-data signals, and translations out of text, either with prompted Foundry models that return structured JSON or with Foundry Tools such as Azure Language, Azure AI Content Safety and Azure Translator. This lesson shows how to choose between a prebuilt tool, a custom model and a prompted model for each task, how to make generated JSON reliable with strict schemas, how to summarize documents larger than a context window, how to detect sentiment, tone, harmful content and PII, how Translator API version 2026-06-06 chooses between NMT, LLM and custom models, and how to customize model output for domain tasks such as compliance summarization.
On this page10 sections
- Which tool should extract entities: prebuilt NER, custom NER or a prompted model?
- How do you get reliable structured JSON from a language model?
- How do you summarize a document larger than the context window?
- When do you use Azure Language sentiment analysis versus a language model?
- How do you detect safety issues in text with Azure AI Content Safety?
- How do you detect and redact sensitive content (PII)?
- How does Azure Translator text translation work in API version 2026-06-06?
- How do you make translations follow your terminology?
- When should you build an LLM-powered translation flow instead of using Translator?
- How do you customize model output for domain tasks such as compliance summarization?
- Choose between prebuilt NER, custom NER and a prompted model with a strict JSON schema for entity extraction, based on categories, labelled data and the need for offsets.
- Write strict structured-output schemas that use enums for fixed taxonomies and null unions for absent values, and diagnose strict-mode validation errors.
- Summarize documents larger than the context window with chunked map-reduce and hierarchical reduction.
- Configure sentiment and tone detection, Content Safety text analysis (severity levels, blocklists) and Azure Language PII detection and redaction.
- Configure Azure Translator 2026-06-06 targets (deploymentName, tone, gender, allowFallback, textType) and choose between adaptive datasets, reference pairs and Custom Translator models.
- Decide when an LLM-powered translation flow beats Translator, and customize model output for domain tasks with instructions, examples, schemas or fine-tuning.
Which tool should extract entities: prebuilt NER, custom NER or a prompted model?
Use prebuilt named entity recognition (NER) in Azure Language when the entities are standard types, custom NER when they are your own categories and you need offsets and a retrainable model, and a prompted language model with a strict schema when the categories are your own but you have no labelled data and need only the values. Prebuilt and custom NER are both core Azure Language features, which Microsoft recommends for new development.
| Approach | Categories | Training data | Character offsets | Best when |
|---|---|---|---|---|
| Prebuilt NER | Predefined (Person, Organization, DateTime, Quantity and others) | None | Yes | Standard entity types, least effort |
| Custom NER project | Your own labelled categories | Labelled documents | Yes, each mention with offset and length | Own categories, audit needs exact spans, the model must be retrained as categories grow |
| Prompted Foundry model + strict JSON schema | Anything you can describe | None (a few examples help) | Not reliable | Own categories, no labels, a tight deadline, values only |
| Fine-tuned model | Anything in the training set | A labelled training set and a training job | Not reliable | Rarely the first choice for extraction; a base model can already follow a schema |
Two near-misses come up often. Custom text classification labels a whole document; it doesn't find mentions inside the text. Key phrase extraction returns the main phrases of the text with no categories and can't be trained on your labels. Neither is an entity extractor.
The deciding facts are usually three: are the categories standard or your own, does labelled data exist, and does a downstream system need exact character offsets? Generated JSON from a language model gives values, not dependable span positions, and a few-shot prompt is not a model a data team can retrain.
How do you get reliable structured JSON from a language model?
Use structured outputs: send a JSON schema with the request (in the Responses API, text.format with type set to json_schema and strict set to true) so decoding is constrained to the schema. Instructions in a prompt ask for a shape; a strict schema guarantees it.
Strict mode has rules that a schema must follow, or the request fails with a schema validation error:
- Every property is listed in
required, on every object, nested objects included. Removing a field fromrequireddoes not make it optional; it makes the schema invalid. additionalPropertiesisfalseon every object, including nested ones.- Optional values are null unions. Declare a field that may be absent as
{"type": ["string", "null"]}and tell the model to returnnullwhen the document doesn't state it. Without a null option, the model has no valid way to say "missing" and tends to invent a plausible value.
schema = {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"due_date": {"type": ["string", "null"]},
"currency": {"type": "string", "enum": ["USD", "EUR", "GBP"]},
},
"required": ["invoice_number", "due_date", "currency"],
"additionalProperties": False,
}
Match the fix to the failure:
| Symptom | Fix | What doesn't fix it |
|---|---|---|
| Labels outside a fixed list (topics, product names, taxonomy categories) | An enum in a strict schema | Listing the labels in the system message, temperature 0 |
| Spelling variants of known names | An enum of the canonical names | Key phrase extraction, which keeps the writer's spelling |
| A missing value replaced with a fabricated one | A null union plus an instruction to use null | A regex pattern, which shapes the invented value but doesn't stop it |
| Wrong handling of an edge case (for example, competitor products extracted as your own) | Few-shot examples that show the edge case | Higher temperature or a larger output token limit |
| Output must match a schema but JSON mode is in use | Switch to json_schema with strict | JSON mode (json_object), which guarantees valid JSON only, not the fields |
Lowering temperature makes output more repeatable but never restricts it to allowed values; raising temperature adds randomness and makes consistency worse. Turning strict off makes an invalid schema accepted but gives up the adherence guarantee.
How do you summarize a document larger than the context window?
Split it, summarize each part, then combine the partial summaries: a map-reduce pattern. A model can only read what fits in its context window, so every part of the document must go through some request if the summary has to cover all of it.
- Map: split on natural boundaries (sections, agenda items, chapters) so each chunk fits the window. Keep each chunk's reference (section number, paragraph) with its summary so points stay traceable.
- Reduce: combine the chunk summaries into one summary in a final request.
- Reduce in levels when needed: if all the chunk summaries together overflow the window, combine them in groups, then combine the group summaries. This hierarchical reduce scales with document size, because the number of levels grows with the input while each request stays within the window.
The test for any proposed shortcut is simple: does every part of the input still reach a model request? Anything that discards, samples or skips input, however it is labelled, produces a summary of part of the document. Settings on the output side, such as the output token limit, control how long the response may be and have no effect on how much input fits. A phrase list is not a summary either: key phrase extraction (a legacy Azure Language feature) returns topics, not what was discussed or decided.
When do you use Azure Language sentiment analysis versus a language model?
Use Azure Language sentiment analysis for an existing app that already calls it; use a language model with a schema for new builds and for labels the service doesn't have, such as tone or sarcasm. Microsoft's Azure Language overview classifies sentiment analysis and opinion mining as legacy capabilities: supported for existing implementations, not recommended for new development.
What the service returns: sentence-level sentiment (positive, negative or neutral) and a document-level label that can also be mixed, each with confidence scores. With the Python TextAnalyticsClient, calling analyze_sentiment(documents, show_opinion_mining=True) adds opinion mining: for each sentence, the targets (aspects such as "battery" or "screen") and the assessments about them, each with its own sentiment. For an existing app that needs per-feature sentiment, that one flag is the smallest change. Changing model_version doesn't add aspects or new label types.
| Requirement | Fits | Why the other fails |
|---|---|---|
| Per-aspect sentiment | Opinion mining links each assessment to its target in one call | Separate entity or phrase extraction plus sentence scoring doesn't link an opinion to the feature it is about |
| Tone labels (sarcastic, frustrated, appreciative) | A language model with a sentiment field and a tone enum | Sentiment analysis has a fixed label set and no tone output |
| Sarcasm ("Oh wonderful, another password reset email that never arrives") | A language model, which reads the comment in context | Sentiment analysis scores literal wording, so sarcastic praise reads positive |
| Sentiment that must avoid legacy features | A language model whose schema returns sentiment per aspect | Every Azure Language sentiment path (sentiment analysis, opinion mining, custom text classification) is legacy, and sentence-level scores can't separate two aspects in one sentence |
Sentiment is not safety. Azure AI Content Safety rates harm categories; it says nothing about whether a comment is positive or sarcastic.
How do you detect safety issues in text with Azure AI Content Safety?
Call the Content Safety text analysis API (text:analyze) on any text you want to screen: it returns a severity for each harm category (Hate, Sexual, Violence, SelfHarm) and can check custom blocklists in the same call. It works on arbitrary text, with no language model in the path.
categorieslimits which harm categories are analyzed. It doesn't change the severity scale.outputType:FourSeverityLevels(the default) returns 0, 2, 4 or 6;EightSeverityLevelsreturns 0 to 7. A policy that separates severity 4 from 5 needs the eight-level scale. On the default scale, low is 2, medium is 4 and high is 6.blocklistNamesnames custom text blocklists to check, for terms the classifiers score as safe but your policy bans: community slang, counterfeit brand names, banned product terms.haltOnBlocklistHit: when true, a blocklist match stops further harm analysis for that text (saving latency). It defaults to false, and it does nothing unless a blocklist is named.
{
"text": "<message>",
"blocklistNames": ["banned-terms"],
"outputType": "FourSeverityLevels"
}
Text analysis or model guardrails?
Guardrails (content filters) on a Foundry model deployment screen the requests and responses of that deployment. If text never goes to a model, such as listings published straight from a web form or a nightly export job, a guardrail never sees it. Calling Content Safety text analysis directly is cheaper and simpler than routing the text through a chatbot deployment just to trigger its guardrails. Configuring deployment guardrails, Prompt Shields and protected material detection is covered in the responsible AI lesson; note here only that Prompt Shields detects prompt injection and jailbreak attempts, not hate or sexual content.
How do you detect and redact sensitive content (PII)?
Use Azure Language PII detection, a core feature: it finds sensitive categories such as Person, PhoneNumber, Address, CreditCardNumber and US Social Security numbers, and returns redacted text alongside a list of entities, each with its category, offset, length and confidence score.
responses = text_client.recognize_pii_entities(
support_tickets,
categories_filter=[
PiiEntityCategory.PHONE_NUMBER,
PiiEntityCategory.EMAIL,
],
)
for ticket in responses:
print(ticket.redacted_text)
for entity in ticket.entities:
print(entity.category, entity.confidence_score)
- By default every supported category for the language is detected and masked, so names, addresses and organizations disappear too.
categories_filter(piiCategoriesin REST) restricts detection, and therefore redaction, to the categories you list. domain_filterset to protected health information (phi) adds health-domain entities. It doesn't narrow results to specific categories.- Filtering the returned entity list after the call doesn't change
redacted_text, which the service already masked for every detected category. disable_service_logscontrols whether the service logs input for troubleshooting; it doesn't change detection.- Confidence scores per entity let you route low-confidence detections to a human reviewer, something a prompted rewrite ("replace identifiers with [REDACTED]") can't give you.
| Option | Use it for | Not a substitute because |
|---|---|---|
| Azure Language PII detection | Masking sensitive data in any text, with categories and scores | — |
| Prebuilt NER | General entities | Not built for sensitive data; removing Person entities leaves phone, card and record numbers |
| PII guardrail on a model deployment | Model inputs and outputs | Sees only traffic to that deployment; a job that calls no model is never screened |
| Prompting a model to redact | Quick masking inside a model flow | No per-entity category or confidence score |
| Content Safety or Translator profanity settings | Harm and profanity | Don't find personal data |
How does Azure Translator text translation work in API version 2026-06-06?
In Azure Translator API version 2026-06-06 (GA, not backward compatible with v3), each entry in a request's targets array names a language and, optionally, the system that translates it through deploymentName:
deploymentName | System | Notes |
|---|---|---|
Omitted or general | Standard neural machine translation (NMT) | The default; character-priced, suited to high volume |
| An LLM deployment, such as GPT-5.1 | LLM-based translation | Needs a Foundry resource linked to Translator; enables tone and gender |
| A custom model's category ID | Your Custom Translator (custom NMT) model | Trained on your parallel sentences |
Having a GPT deployment or a custom model available changes nothing on its own: a target that sets only a language code goes to general.
Target parameters to know
tone(formal, informal, neutral) andgender(female, male, neutral) apply to LLM-based translation only. Requesting a formal register therefore needs both an LLMdeploymentNameand the parameter.allowFallbackdefaults to true: if the named model doesn't support the language pair, the service quietly uses the general system. Set it to false when only LLM output is acceptable, so the request fails instead.textTypedefaults to plain; sethtmlwhen the input contains markup so elements are preserved. Text marked as not to be translated stays as it is.- Settings such as
script(writing system) and profanity handling don't affect markup, tone or fallback.
Private endpoints
Microsoft documents that LLM-based translation isn't available when the Translator resource uses a private endpoint. NMT keeps working privately but has no tone control, and tone is an LLM-only parameter. Register control on private networking therefore can't come from Translator itself; it comes from instructions to a language model whose deployment is itself reachable privately (next sections).
What was removed
Version 2026-06-06 dropped the v3 Detect, BreakSentence and Dictionary methods. To identify a text's language without translating it, Microsoft points to Azure Language language detection, a core Azure Language feature. Leaving out the source language in a translate call does return a detected language, but only as part of a paid translation.
Translating documents with their formatting
Translator document translation keeps a file's structure and layout. It has two modes:
| Mode | Input and output | Storage | Fits |
|---|---|---|---|
| Synchronous (single file) | One file in the request, the translated file in the response | No Blob Storage needed | A user uploads one document and waits for it |
| Asynchronous batch | Many files read from and written to containers | Azure Blob Storage source and target containers | Bulk translation of document sets |
Translating extracted text as plain text loses the document's layout, and transliteration converts between scripts (for example into Latin script) without translating anything. For HTML strings sent to text translation, set textType to html rather than using document translation.
How do you make translations follow your terminology?
Pick the adaptation by the system you're allowed to use and how much data you have: adaptive custom translation and reference text pairs steer LLM translation without training; a Custom Translator model is a trained NMT model.
| Option | Translation system | Data | Training | How a target uses it |
|---|---|---|---|---|
| Adaptive custom translation dataset | LLM | Prealigned source/target sentence pairs, indexed in minutes | None | adaptiveDatasetId plus an LLM deploymentName |
| Reference text pairs | LLM | Up to five inline pairs per target | None | referenceTextPairs |
| Custom Translator model | NMT | Parallel sentences (thousands) | A training run | deploymentName set to the model's category ID |
| Fine-tuned GPT model | LLM | Training set | A training job to run and maintain | The tuned deployment's name |
- Precedence: the two LLM adaptation inputs aren't combined. When
referenceTextPairsis supplied,adaptiveDatasetIdis ignored. To keep a dataset's terminology and add a new term, add the new pairs to the dataset instead of passing them inline. - Volume and lead time: inline reference pairs are capped per target, so a large glossary belongs in an adaptive dataset, which is indexed rather than trained and is usable quickly. A Custom Translator model needs a training run.
- Which system is allowed: adaptive datasets, reference pairs and fine-tuned GPT models all steer a language model. Custom Translator is the only adaptation that stays on NMT.
- Neither adaptation adds language support: an unsupported pair still falls back to NMT unless
allowFallbackis false.
When should you build an LLM-powered translation flow instead of using Translator?
Build an LLM-powered flow, a Foundry model prompted to translate, when the requirement is something Translator's fixed parameters can't express; use Translator NMT when volume and cost dominate. Translator exposes settings (target languages, tone, gender, text type, adaptation), not free-form instructions.
| Requirement | Better fit |
|---|---|
| Throughput and cost per character matter most | Translator, general NMT, which is priced per character rather than per token |
| Creative adaptation (voice, idiom, wordplay) rather than literal meaning | A prompted model, because Translator offers no style instructions beyond tone and gender |
| Rules about the output itself (length budgets, tokens that must survive, a specific response shape) | A prompted model with those rules in the instructions and an output schema; Translator has no parameter for them |
| A capability Translator restricts in your configuration (for example LLM features behind a private endpoint) | A model deployment that meets the same network rule, with the behaviour set in instructions |
| Terminology control without any language model | Custom Translator model |
Translator can handle parts of these jobs: several targets in one request, text marked as not to be translated. The deciding requirement is usually the one Translator can't express. Prompted translation adds token cost at scale, so don't use it where NMT already meets the need.
How do you customize model output for domain tasks such as compliance summarization?
Start with system instructions and a few examples, enforce structure with a strict schema, and reach for supervised fine-tuning only when long few-shot prompts are needed on every call to keep a house style. Each layer fixes a different problem.
- Inconsistent format or invented categories mean the model hasn't been told what to produce. Define the types and the format in the system message and add a couple of worked examples. This is fast and needs no training set.
- Verifiable grounding for compliance work: a citation rule in the instructions (every finding cites the paragraph it comes from) plus a schema with a required
source_paragraphfield per finding, so a finding without a source can't be returned. A required field is what makes the rule enforceable: guidance in a prompt shapes output but can't reject a finding that lacks a source, and any tool that brings in outside content works against grounding in the supplied document. - A taxonomy that changes (for example quarterly) belongs in a schema
enum: decoding is constrained to current values and editing the enum takes effect on the next request. Fine-tuning would bake in old categories and need a new training run per change, and a fine-tuned model can still emit an off-list tag. - Fine-tuning fits when thousands of approved input and output pairs exist, the knowledge is already in the request, and the cost and latency of sending long examples with every call are the problem. It teaches format, tone and style so the prompt can shrink. The test for alternatives: any approach that still puts the examples into each request keeps their token cost, however the examples are chosen, cached or priced; only moving the behaviour into the model's weights removes them.
| Technique | Changes | Doesn't change |
|---|---|---|
| System instructions | What to produce, rules, register | Guarantees about structure |
| Few-shot examples | Edge-case behaviour, layout, boundaries | Guarantees; token cost per call |
| Strict schema (enum, required fields) | Guaranteed fields and allowed values | Style or judgement |
| Supervised fine-tuning | Built-in style and format, shorter prompts | Fresh facts; quick taxonomy updates |
Tip. Expect short scenarios that describe a company's text workload and one or two constraints, then ask what to use, change or configure. The deciding detail is usually a constraint: standard or custom categories, labelled data or none, offsets needed or not, an existing app or a new build that must avoid legacy features, whether a language model is in the path, a private endpoint, NMT-only compliance, a same-day or no-training rule, cost at volume, or a per-string length limit. Several items show short Python or JSON request bodies — TextAnalyticsClient calls, Responses API json_schema definitions, Content Safety analyze requests, Translator targets — and ask which parameter or schema change fixes a stated symptom, so know what each parameter does and what it doesn't. Some items ask you to select two actions that together meet a requirement.
- Standard entities: prebuilt NER. Own categories with labels, offsets and retraining: custom NER. Own categories, no labels, values only: a prompted model with a strict schema.
- Strict structured outputs need every property in required and additionalProperties false on every object; express optional values as a null union.
- Fixed label lists belong in an enum; instructions and temperature 0 reduce but never prevent off-list values.
- Content larger than the context window is chunked and summarized in levels; truncation, partial input and max_output_tokens all lose coverage.
- Sentiment analysis and opinion mining are legacy Azure Language features for existing apps; tone, sarcasm and new builds use a language model with a schema.
- Content Safety text analysis screens any text: EightSeverityLevels gives 0–7, blocklists catch policy terms, haltOnBlocklistHit (default false) skips further analysis on a hit. Model guardrails see only model traffic.
- PII detection returns redacted text plus each entity's category and confidence score; categories_filter narrows what is masked.
- In Translator 2026-06-06, deploymentName defaults to general NMT; tone and gender need an LLM deployment; allowFallback false makes unsupported pairs fail; LLM translation is unavailable behind a private endpoint.
- referenceTextPairs (up to five) override adaptiveDatasetId; a Custom Translator model is the NMT-only route to your terminology.
- Try instructions and examples first, enforce with a schema, and fine-tune only when long per-call examples are the cost problem.
Frequently asked questions
When should I use custom NER instead of prompting a language model to extract entities?
Use custom NER in Azure Language when you have labelled examples of your own entity categories, need the exact character offset of every mention, and want a model you can retrain as categories change. Prompt a Foundry model with a strict JSON schema when you have no labelled data, need results quickly and only need the extracted values, because generated JSON doesn't give dependable offsets.
How do I make a field optional in strict structured outputs?
Keep the field in the required list and give it a union type that includes null, such as ["string", "null"], then tell the model to return null when the source doesn't contain the value. In strict mode every property must be required and every object needs additionalProperties set to false, so removing a field from required makes the schema invalid.
Is Azure Language sentiment analysis still recommended for new apps?
No. Microsoft lists sentiment analysis and opinion mining as legacy Azure Language capabilities, supported for existing implementations but not recommended for new development. Existing apps can keep using them, including adding opinion mining with show_opinion_mining; new builds, and anything needing tone or sarcasm labels, use a language model with an output schema.
What is the difference between Content Safety text analysis and a model deployment guardrail?
Content Safety text analysis is an API you call on any text, returning harm-category severities and custom blocklist matches. A guardrail on a Foundry model deployment screens only the requests and responses of that deployment. Text that never goes to a model, such as form submissions or batch exports, needs the text analysis API.
How do I redact only some kinds of personal data with Azure Language?
Pass categories_filter (piiCategories in the REST API) to PII detection with the categories you want, such as phone numbers and email addresses. The service then detects and masks only those categories in the redacted text. Filtering the returned entity list afterwards doesn't change the redacted text.
Which model does Azure Translator 2026-06-06 use if I don't set deploymentName?
It uses general, the reserved name for Translator's standard neural machine translation system. LLM translation happens only when a target names an LLM deployment such as GPT-5.1, and a Custom Translator model only when the target names that model's category ID.
Why is Translator ignoring my adaptive dataset?
If a target also supplies referenceTextPairs, Translator uses the inline pairs and ignores adaptiveDatasetId; the two aren't combined. Add new terms to the adaptive dataset instead of passing them inline when you want the dataset's terminology applied.
How do I detect the language of text now that Translator removed the Detect method?
Use Azure Language language detection, which identifies the language of text without translating it. Translator API version 2026-06-06 removed the Detect, BreakSentence and Dictionary methods, and Microsoft points to Azure Language for detection.
Source
This lesson covers the "Implement text analysis solutions" domain of the official AI-103 exam guide. Vendors revise their guides — check the source for the current version.
- Microsoft AI-103 study guide — Microsoft Learn
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.