Procedura per far leggere un documento a un modello di intelligenza artificiale senza dargli i dati personali che contiene. Il servizio Azure AI Language trova i dati, il programma li sostituisce con segnaposto del tipo [PERSONA_1], al modello arriva solo il testo mascherato, e i dati veri rientrano nella risposta alla fine, sul computer di chi ha il documento.
Questo è un esempio, non una soluzione pronta. Serve a mostrare come si incastrano i pezzi. Quali dati coprire, quali lasciare e quali controlli aggiungere dipende dal documento, dal settore e da cosa se ne fa: va deciso caso per caso e va verificato sui documenti veri, non su quello di esempio.
Occorre: un account Azure con una sottoscrizione attiva, .NET 10 e l'interfaccia a riga di comando di Azure sul computer, un browser. Durata: circa trenta minuti. Costo: la risorsa Language nel piano gratuito F0 costa 0 USD e accetta 5000 chiamate ogni 30 giorni. Il modello si paga a consumo, a token: il giro di prova di questa guida usa poche migliaia di caratteri.
Il portale delle schermate è in inglese: i nomi dei comandi sono riportati come appaiono a schermo.
Preparare i servizi
1. Crea la risorsa Language
Nel portale, Create a resource, cerca Language service e premi Create. Il portale propone le funzioni aggiuntive: non serve selezionarne nessuna, premi Continue to create your resource.
Nella scheda Basics, accanto a Resource group, premi Create new e scrivi rg-guide-pii-01.

2. Scegli il piano gratuito
Compila il resto della scheda:
- Region: West Europe
- Name:
lang-guide-pii-01 - Pricing tier: Free F0 (5K Transactions per 30 days)
In fondo c'è una casella da spuntare: è la presa visione della nota di Microsoft sull'uso responsabile dell'IA, e senza quella il bottone di creazione resta spento.

3. Controlla e crea
Premi Review + create, controlla il riepilogo e premi Create.

Quando finisce, apri la risorsa: nella panoramica c'è l'indirizzo del servizio, che serve al programma.

4. Crea la risorsa per il modello
Il modello sta su una risorsa diversa. Nel portale, Create a resource, cerca Azure AI Foundry e premi Create. Nella scheda Basics:
- Resource group:
rg-guide-pii-01, lo stesso di prima - Name:
ai-guide-pii-01 - Region: West Europe
- Default project name: lascia
proj-default

Premi Review + create e poi Create.


5. Distribuisci il modello
Dalla riga di comando, con un comando solo:
az cognitiveservices account deployment create \
--resource-group rg-guide-pii-01 \
--name ai-guide-pii-01 \
--deployment-name gpt-5.4-mini \
--model-name gpt-5.4-mini \
--model-version 2026-03-17 \
--model-format OpenAI \
--sku-name GlobalStandard \
--sku-capacity 10I modelli disponibili cambiano nel tempo e per regione. Per vedere quelli che la tua risorsa può ospitare:
az cognitiveservices account list-models \
--resource-group rg-guide-pii-01 \
--name ai-guide-pii-01 \
--query "[?format=='OpenAI'].{nome:name, versione:version}" -o tableUn modello in via di ritiro non si può più distribuire: il comando risponde ServiceModelDeprecating e si sceglie quello dopo.
6. Dai i permessi a te stesso
Il programma non usa chiavi: entra con l'utente con cui hai fatto l'accesso ad Azure. Servono due ruoli, uno per servizio.
UTENTE=$(az ad signed-in-user show --query id -o tsv)
LINGUA=$(az cognitiveservices account show \
-g rg-guide-pii-01 -n lang-guide-pii-01 --query id -o tsv)
MODELLI=$(az cognitiveservices account show \
-g rg-guide-pii-01 -n ai-guide-pii-01 --query id -o tsv)
az role assignment create \
--assignee-object-id $UTENTE --assignee-principal-type User \
--role "Cognitive Services Language Reader" \
--scope $LINGUA
az role assignment create \
--assignee-object-id $UTENTE --assignee-principal-type User \
--role "Cognitive Services OpenAI User" \
--scope $MODELLIIl programma
7. Crea il progetto
dotnet new console -n pii-demo
cd pii-demo
dotnet add package Azure.AI.TextAnalytics --version 5.3.0
dotnet add package Azure.Identity --version 1.21.0
dotnet add package OpenAI --version 2.14.08. Metti il documento di esempio
Nella cartella del progetto, un file documento.txt:
Spett.le Ufficio Sinistri,
con la presente il sottoscritto Marco Bianchi, nato a Verona il 12 marzo 1981,
codice fiscale BNCMRC81C12L781D, residente in via Giuseppe Mazzini 14, 37121 Verona,
telefono 345 7781234, posta elettronica marco.bianchi@esempio.it,
chiede il rimborso delle spese sostenute per il sinistro del 3 ottobre 2026.
L'importo di 1.240,50 euro va accreditato sul conto IT60X0542811101000000123456
intestato al sottoscritto. Allego la fattura 2026/417 dell'officina Rossi S.r.l.
e il verbale redatto dall'agente Laura Verdi.
Resto a disposizione per chiarimenti.
Marco BianchiPersone, luoghi, recapiti e codici sono inventati. Perché il file finisca accanto al programma, aggiungi al .csproj:
<ItemGroup>
<None Update="documento.txt" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>9. Scrivi il programma
Sostituisci Program.cs:
using Azure.AI.TextAnalytics;
using Azure.Identity;
using OpenAI;
using OpenAI.Chat;
using System.ClientModel.Primitives;
using System.Text;
#pragma warning disable OPENAI001
const string lingua = "https://lang-guide-pii-01.cognitiveservices.azure.com/";
const string modelli = "https://ai-guide-pii-01.openai.azure.com/openai/v1";
const string modello = "gpt-5.4-mini";
Dictionary<string, string> nomi = new()
{
["Person"] = "PERSONA",
["Address"] = "INDIRIZZO",
["PhoneNumber"] = "TELEFONO",
["Email"] = "EMAIL",
["DateTime"] = "DATA",
["Organization"] = "AZIENDA",
["InternationalBankingAccountNumber"] = "IBAN",
["ITFiscalCode"] = "CODICEFISCALE",
};
DefaultAzureCredential credenziale = new();
string documento = File.ReadAllText("documento.txt");
// 1. Il servizio trova i dati personali.
TextAnalyticsClient cliente = new(new Uri(lingua), credenziale);
PiiEntityCollection trovati = await cliente.RecognizePiiEntitiesAsync(documento, "it");
// 2. Ogni dato diventa un segnaposto; si parte dal fondo
// per non spostare le posizioni di quelli che restano.
Dictionary<string, string> perTesto = [];
Dictionary<string, string> originali = [];
Dictionary<string, int> contatori = [];
StringBuilder mascherato = new(documento);
foreach (PiiEntity dato in trovati.OrderByDescending(e => e.Offset))
{
if (!nomi.TryGetValue(dato.Category.ToString(), out string? etichetta))
{
continue;
}
if (!perTesto.TryGetValue(dato.Text, out string? segnaposto))
{
contatori[etichetta] = contatori.GetValueOrDefault(etichetta) + 1;
segnaposto = $"[{etichetta}_{contatori[etichetta]}]";
perTesto[dato.Text] = segnaposto;
originali[segnaposto] = dato.Text;
}
mascherato.Remove(dato.Offset, dato.Length).Insert(dato.Offset, segnaposto);
}
string perIlModello = mascherato.ToString();
Console.WriteLine($"Dati sostituiti: {originali.Count}\n");
Console.WriteLine(perIlModello);
// 3. Al modello arriva solo il testo mascherato.
BearerTokenPolicy tokenPolicy = new(
credenziale,
"https://cognitiveservices.azure.com/.default");
ChatClient chat = new(
modello,
tokenPolicy,
new OpenAIClientOptions { Endpoint = new Uri(modelli) });
ChatCompletion risposta = await chat.CompleteChatAsync(
new SystemChatMessage(
"Rispondi in italiano. I segnaposto fra parentesi quadre vanno "
+ "riportati tali e quali, senza inventare nomi."),
new UserChatMessage(
"Riassumi in tre righe chi scrive, cosa chiede e dove vuole "
+ $"il pagamento.\n\n{perIlModello}"));
string testoRisposta = risposta.Content[0].Text;
Console.WriteLine("\n--- risposta del modello ---\n");
Console.WriteLine(testoRisposta);
// 4. I dati veri tornano al loro posto, solo qui.
foreach ((string segnaposto, string vero) in originali)
{
testoRisposta = testoRisposta.Replace(segnaposto, vero);
}
Console.WriteLine("\n--- risposta con i dati rimessi ---\n");
Console.WriteLine(testoRisposta);Lo stesso testo trovato due volte prende lo stesso segnaposto: Marco Bianchi in testa e in calce diventano tutti e due [PERSONA_1]. Le sostituzioni partono dal fondo del documento perché ogni sostituzione cambia la lunghezza del testo, e partendo dall'inizio le posizioni successive non tornerebbero più.
10. Esegui
dotnet runIl documento che parte verso il modello:
Dati sostituiti: 11
Spett.le [AZIENDA_2],
con la presente il sottoscritto [PERSONA_1], nato a Verona il [DATA_2],
codice fiscale [CODICEFISCALE_1], residente in [INDIRIZZO_1],
telefono [TELEFONO_1], posta elettronica [EMAIL_1],
chiede il rimborso delle spese sostenute per il sinistro del [DATA_1].
L'importo di 1.240,50 euro va accreditato sul conto [IBAN_1]
intestato al sottoscritto. Allego la fattura 2026/417 dell'officina [AZIENDA_1]
e il verbale redatto dall'agente [PERSONA_2].La risposta del modello, che i dati veri non li ha mai visti:
Scrive [PERSONA_1], nato a Verona il [DATA_2], codice fiscale
[CODICEFISCALE_1], residente in [INDIRIZZO_1], con telefono [TELEFONO_1]
ed email [EMAIL_1].
Chiede il rimborso delle spese sostenute per il sinistro del [DATA_1],
allegando la fattura 2026/417 dell'officina [AZIENDA_1] e il verbale
redatto dall'agente [PERSONA_2].
Vuole che l'importo di 1.240,50 euro sia accreditato sul conto [IBAN_1].E la stessa risposta dopo il reinserimento, che avviene sul computer di chi ha il documento:
Scrive Marco Bianchi, nato a Verona il 12 marzo 1981, codice fiscale
BNCMRC81C12L781D, residente in via Giuseppe Mazzini 14, 37121 Verona,
con telefono 345 7781234 ed email marco.bianchi@esempio.it.
Chiede il rimborso delle spese sostenute per il sinistro del 3 ottobre 2026,
allegando la fattura 2026/417 dell'officina Rossi S.r.l. e il verbale
redatto dall'agente Laura Verdi.
Vuole che l'importo di 1.240,50 euro sia accreditato sul conto
IT60X0542811101000000123456.Quello che il servizio non copre
Queste tre cose sono emerse provando la procedura, e sono il motivo per cui un lavoro vero non finisce qui.
Il codice fiscale viene controllato. Il servizio non si limita alla forma: verifica il carattere di controllo. Il primo documento di prova aveva un codice fiscale inventato male e il servizio non lo riconosceva, lasciandolo in chiaro; con un codice formalmente valido lo trova subito. Un refuso in un documento vero, quindi, può far passare il codice al modello. Se il documento deve essere coperto comunque, serve una regola propria accanto al servizio.
«Verona» resta in chiaro. Il nome di una città da solo non è un dato personale, ma lì è il luogo di nascita, e insieme alla data identifica una persona. Il servizio non può saperlo: lo deve decidere chi conosce il documento.
I dati biometrici non c'entrano. Il servizio legge testo. Una foto del volto, un'impronta, una firma autografa non sono testo e restano dove sono: per quelli servono strumenti diversi, e per i volti Microsoft richiede un'autorizzazione a parte.
Inoltre la scelta di cosa mascherare è una scelta, non un risultato. Qui le date sono coperte, e infatti il riassunto non sa dire quando è successo il sinistro: se al modello quella data serve, si toglie DateTime dall'elenco. La tabella in cima al programma è lì per questo.
Microsoft stessa scrive che il servizio si basa su modelli addestrati e che non c'è garanzia che trovi tutti i dati sensibili: vanno previsti altri controlli. Vale per il programma di questa guida come per qualunque altro.
Se i documenti non devono proprio uscire dal computer, lo stesso schema si può fare in locale con Presidio, che è sempre di Microsoft e gira in un contenitore: cambia chi trova i dati, non il resto della procedura.
Pulire
11. Elimina il gruppo di risorse
Apri rg-guide-pii-01: contiene la risorsa Language, quella Foundry e il progetto che Foundry si crea da solo.

Premi Delete resource group, riscrivi il nome del gruppo e premi Delete.

Conferma nella finestra che compare.

Nomi dei comandi e schermate rilevati sul portale di Azure il 5 novembre 2026, regione West Europe, con .NET 10.0.401 e l'interfaccia a riga di comando di Azure 2.91.0.