-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



Visualizzazione post con etichetta AI. Mostra tutti i post
Visualizzazione post con etichetta AI. Mostra tutti i post

venerdì 16 ottobre 2026

Osservabilità degli agenti AI: Microsoft Agent Framework, OpenTelemetry e Azure Monitor

NicoGIS Tech Weekly | AI Engineering · .NET · Distributed Tracing · Azure

Osservabilità degli agenti AI: Microsoft Agent Framework, OpenTelemetry e Azure Monitor

Come ricostruire il percorso di esecuzione di un agente, correlare chiamate a LLM e tool, misurare latenza e token e individuare i colli di bottiglia nei sistemi agentici distribuiti.

Deep dive tecnico · 16 ottobre 2026 · Livello avanzato

Un agente AI non esegue semplicemente una richiesta verso un modello linguistico. Può pianificare operazioni, interrogare sistemi esterni, invocare strumenti, recuperare informazioni e ripetere le chiamate al modello prima di produrre una risposta. Questo cambia radicalmente il problema dell'osservabilità.

In una normale applicazione distribuita, il tracing permette di ricostruire il percorso di una richiesta HTTP attraverso servizi, database e code di messaggi. Nei sistemi agentici occorre estendere lo stesso modello a operazioni non necessariamente deterministiche, il cui numero e ordine possono variare tra un'esecuzione e l'altra.

La domanda non è soltanto quanto tempo abbia impiegato una richiesta, ma cosa abbia effettivamente fatto l'agente durante quel tempo.

1. Dall'osservabilità applicativa all'osservabilità agentica

Si consideri un agente incaricato di analizzare un documento aziendale e produrne una sintesi. Potrebbe essere necessaria una prima inferenza per definire il piano, una ricerca, il download del documento e una seconda inferenza per la risposta finale.

Una singola richiesta produce più operazioni correlate. Senza distributed tracing, eventi separati nei log non consentono sempre di ricostruire con certezza l'esecuzione. Con una trace distribuita si possono osservare modello invocato, tool, dipendenze HTTP e database, token, errori, retry e punti di maggiore latenza.

2. OpenTelemetry GenAI Semantic Conventions

OpenTelemetry organizza la telemetria in traces, metrics e logs. Le GenAI Semantic Conventions definiscono vocabolario e attributi per descrivere operazioni di modelli generativi, agenti e tool. Esempi di nomi di operazione includono:

OperazioneSignificato
invoke_agentInvocazione di un agente
chatInterazione conversazionale con un modello
execute_toolEsecuzione di uno strumento
invoke_workflowEsecuzione di un workflow agentico, se supportata dall'instrumentation adottata
planPianificazione, nei sistemi che la rappresentano esplicitamente
retrievalRecupero di informazioni, dove previsto dall'instrumentation

Alcuni attributi utili per analizzare un'esecuzione sono:

gen_ai.operation.name
gen_ai.agent.name
gen_ai.provider.name
gen_ai.request.model
gen_ai.tool.name
gen_ai.usage.input_tokens
gen_ai.usage.output_tokens

Attenzione alla compatibilità: le convenzioni agentiche sono un'area in evoluzione; denominazioni, stabilità e adozione degli attributi possono differire in base alla versione delle specifiche e dei pacchetti. Le operazioni sopra elencate non implicano che ogni runtime le emetta automaticamente. Verificare sempre le convenzioni ufficiali per gli agenti e quelle relative alle operazioni GenAI.

3. L'architettura: da Agent Framework ad Azure Monitor

Agent Framework e i client strumentati producono segnali. OpenTelemetry raccoglie e correla i dati. Gli exporter li inviano ai backend. Separare questi livelli riduce l'accoppiamento tra orchestrazione dell'agente e prodotto di monitoraggio.

4. Laboratorio .NET: configurare tracing e instrumentation

Un esempio ASP.NET Core può utilizzare la distribuzione Microsoft OpenTelemetry. Il comando per installare il pacchetto è:

dotnet add package Microsoft.OpenTelemetry

La configurazione esatta dipende dalla versione del pacchetto. Nelle versioni che espongono UseMicrosoftOpenTelemetry e ExportTarget, uno schema di configurazione è il seguente:

using Microsoft.OpenTelemetry;

var builder = WebApplication.CreateBuilder(args);

builder.UseMicrosoftOpenTelemetry(options =>
{
    options.Exporters =
        ExportTarget.AzureMonitor |
        ExportTarget.Otlp;
});

var app = builder.Build();
app.MapGet("/health", () => Results.Ok());
app.Run();

La destinazione Azure Monitor utilizza la connection string configurata nell'ambiente:

APPLICATIONINSIGHTS_CONNECTION_STRING

Per OTLP, quando usato, è possibile configurare:

OTEL_EXPORTER_OTLP_ENDPOINT

Questa configurazione abilita la pipeline, ma non crea alcun agente. Per generare span agentici occorre istanziare e invocare componenti effettivamente strumentati.

Instrumentare chat client e agente

Le versioni di Microsoft Agent Framework basate sull'API ChatClientAgent possono essere strumentate tramite UseOpenTelemetry() per il client e WithOpenTelemetry() per l'agente:

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

// chatClient è un IChatClient già configurato.
IChatClient instrumentedClient = chatClient
    .AsBuilder()
    .UseOpenTelemetry()
    .Build();

var agent = new ChatClientAgent(
    instrumentedClient,
    name: "DocumentAnalyzer",
    instructions:
        "Analyze documents and report verified findings."
).WithOpenTelemetry();

Frammento focalizzato sull'instrumentation, non un progetto completo eseguibile. Microsoft Agent Framework è in evoluzione: controllare overload, namespace, pacchetti e metodo di creazione dell'agente rispetto alla versione NuGet installata.

Span personalizzati con ActivitySource

Per correlare operazioni applicative si può utilizzare System.Diagnostics.ActivitySource:

using System.Diagnostics;

public static class AgentTelemetry
{
    public const string SourceName =
        "NicoGIS.TechWeekly.AgentDemo";

    public static readonly ActivitySource Source =
        new(SourceName);

    public static async Task<T> TraceDocumentLoadAsync<T>(
        Func<Task<T>> loadDocument)
    {
        using var activity = Source.StartActivity(
            "document.load", ActivityKind.Internal);

        try
        {
            var result = await loadDocument();
            activity?.SetTag("document.operation", "load");
            return result;
        }
        catch (Exception ex)
        {
            activity?.SetStatus(
                ActivityStatusCode.Error, ex.Message);
            activity?.AddException(ex);
            throw;
        }
    }
}

La ActivitySource personalizzata deve essere aggiunta alla configurazione del tracing:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
        tracing.AddSource(AgentTelemetry.SourceName));

Quando la nuova attività viene aperta in presenza di un contesto Activity.Current valido, il runtime stabilisce la relazione con lo span corrente. Le informazioni applicative possono usare attributi dedicati; le chiavi gen_ai.* vanno invece utilizzate nel rispetto delle convenzioni ufficiali.

5. TraceId, SpanId e propagazione del contesto

La correlazione si fonda sul contesto distribuito e, frequentemente, sugli header W3C Trace Context.

ElementoRuolo
TraceIdIdentifica l'intera esecuzione distribuita.
SpanIdIdentifica una singola operazione.
ParentSpanIdCollega lo span a quello padre.
traceparentPropaga il contesto tra servizi.
tracestateVeicola informazioni supplementari del contesto.
HTTP POST /analyze
└── invoke_agent DocumentAnalyzer
    ├── chat (planning)
    ├── execute_tool ReadDocument
    │   └── HTTP GET /api/documents/42
    │       └── SQL SELECT ...
    └── chat (synthesis)

Con le instrumentation HTTP corrette, il contesto attraversa il confine tra client e servizio ricevente. Nel caso dei tool MCP non bisogna dare per scontata la propagazione: trasporto, client, server e intermediari devono supportarla. Quando manca, due segmenti della stessa esecuzione possono comparire come trace indipendenti.

6. Azure Monitor: analisi con KQL

In Application Insights collegato a un workspace Log Analytics, le dipendenze possono essere interrogate tramite la tabella AppDependencies. Per ricostruire gli eventi relativi a un OperationId:

let traceId = "INSERT_TRACE_ID";
AppDependencies
| where OperationId == traceId
| project
    TimeGenerated,
    Name,
    DependencyType,
    DurationMs,
    Success,
    OperationId,
    ParentId,
    Id,
    Properties
| order by TimeGenerated asc

Per identificare le dipendenze con maggiore latenza al 95° percentile:

AppDependencies
| where TimeGenerated > ago(24h)
| summarize
    Calls = count(),
    Failures = countif(Success == false),
    AvgMs = avg(DurationMs),
    P95Ms = percentile(DurationMs, 95)
    by Name, DependencyType
| order by P95Ms desc

Queste query sono punti di partenza: il mapping delle operazioni GenAI in tabelle e proprietà di Application Insights dipende dall'exporter e dalla versione in uso. È quindi necessario verificare che gli span agentici siano realmente esportati in AppDependencies e che i loro attributi siano disponibili in Properties.

Le metriche GenAI consentono inoltre di osservare durata e consumo, ad esempio con nomi come gen_ai.client.operation.duration e gen_ai.client.token.usage quando emessi dalle librerie utilizzate. Le unità di misura non vanno confuse: DurationMs è espresso in millisecondi, mentre le convenzioni GenAI possono esprimere le durate in secondi.

7. Latenza, token e costo economico

Due esecuzioni dello stesso compito possono produrre una risposta comparabile con un consumo di risorse molto diverso.

IndicatoreEsecuzione AEsecuzione B
Chiamate LLM26
Tool eseguiti14
Token totali3.20011.800
Durata2,4 s8,7 s
Confronto dei token utilizzati (scenario illustrativo)
Esecuzione A · 3.200
Esecuzione B · 11.800

La seconda esecuzione utilizza circa 3,7 volte i token della prima. Questo non indica automaticamente un errore: può dipendere dalla complessità del compito. La trace permette però di verificare retry, retrieval ridondanti, contesti eccessivamente ampi e output dei tool poco pertinenti.

Il costo monetario non coincide con i token totali. Una stima credibile distingue token in ingresso e in uscita, modello, eventuale caching, tariffe, strumenti e risorse esterne.

8. Tracing e valutazione della qualità

L'osservabilità tecnica non equivale a una verifica della correttezza dei risultati. Un sistema agentico maturo dovrebbe rispondere a quattro domande complementari.

DimensioneDomanda
TecnicaDove si trova il collo di bottiglia?
EconomicaQuanto costa completare la richiesta?
ComportamentalePerché sono stati utilizzati determinati strumenti?
QualitativaIl risultato è corretto, pertinente e verificabile?

Il tracing è essenziale per ricostruire la traiettoria esecutiva. La valutazione della qualità richiede invece dataset di test, verifiche dei risultati e controlli sull'uso degli strumenti. Il fatto che una risposta sia stata generata senza errori tecnici non dimostra che sia corretta.

9. Sicurezza e privacy della telemetria

Prompt, risposte, argomenti dei tool e documenti possono contenere informazioni personali, riservate o segreti. Le funzionalità che acquisiscono contenuti sensibili, come EnableSensitiveData dove disponibili, non dovrebbero essere abilitate indiscriminatamente in produzione.

  • Redigere o escludere PII, credenziali e dati riservati prima dell'esportazione.
  • Applicare retention e sampling proporzionati alle necessità diagnostiche.
  • Limitare l'accesso ai workspace e alle dashboard.
  • Preferire identificatori tecnici non sensibili per la correlazione.
  • Controllare la cardinalità di tag e dimensioni metriche.

Inserire prompt interi e identificatori ad alta cardinalità nelle metriche può comportare rischi di sicurezza e costi elevati. I contenuti delle conversazioni richiedono un trattamento diverso da normali misure di durata o conteggi.

10. Conclusioni: verso l'osservabilità delle traiettorie agentiche

La principale evoluzione consiste nel passaggio dal monitoraggio di singole chiamate ai modelli all'analisi di traiettorie di esecuzione: la sequenza concreta di inferenze, tool e dipendenze attraversata dall'agente per raggiungere un risultato.

Microsoft Agent Framework fornisce i componenti di orchestrazione; OpenTelemetry introduce un modello interoperabile di tracing, metriche e log; Azure Monitor rende possibile la correlazione e l'analisi in produzione. Per ottenere un sistema affidabile occorre completare questa telemetria con controlli sulla qualità delle risposte e sulle decisioni operative.

Un agente osservabile non è automaticamente affidabile. Un agente non osservabile, però, è molto più difficile da rendere affidabile.

Fonti e documentazione

venerdì 9 ottobre 2026

Agentic Planning vs Deterministic Execution: architetture AI affidabili con Azure Durable Functions, DAG e GeoAgent

Quando l'intelligenza pianifica, ma non controlla l'esecuzione

Per molto tempo abbiamo progettato gli agenti AI secondo un modello relativamente semplice: un Large Language Model interpreta una richiesta, seleziona uno strumento, ne osserva il risultato e decide cosa fare successivamente.

Il modello funziona bene per task brevi. Diventa però progressivamente meno efficiente quando deve coordinare decine di strumenti, elaborare grandi volumi di dati, gestire retry, attendere eventi esterni e garantire che operazioni già completate non vengano ripetute accidentalmente.

Con Dynamic Workflows for Azure Functions Hosted Skills, annunciato da Microsoft l'8 ottobre 2026, emerge un'architettura differente: l'LLM genera un piano strutturato, mentre un runtime durevole e deterministico si occupa della sua esecuzione.

La distinzione è fondamentale. Il modello non diventa improvvisamente affidabile perché produce JSON anziché testo. È il sistema che interpreta quel JSON a dover imporre vincoli, autorizzazioni, dipendenze e garanzie operative.

Principio architetturale: un LLM può proporre una sequenza di azioni, ma non dovrebbe essere l'autorità finale che decide se quelle azioni sono consentite, sicure o già state eseguite.

1. Il problema del reasoning loop tradizionale

Consideriamo un agente che deve analizzare un evento meteorologico, interrogare servizi GIS, produrre una mappa di rischio e inviare una notifica.

User / Event
    |
    v
   LLM
    |
    +-- Tool: read rainfall
    |       |
    |       v
    |      LLM
    |
    +-- Tool: read terrain
    |       |
    |       v
    |      LLM
    |
    +-- Tool: run spatial analysis
    |       |
    |       v
    |      LLM
    |
    +-- Tool: generate map
    |       |
    |       v
    |      LLM
    |
    +-- Tool: publish result
            |
            v
           LLM

Ogni operazione comporta un nuovo passaggio attraverso il modello. Se gli strumenti restituiscono dati voluminosi, il contesto si riempie di risultati intermedi che il modello potrebbe non aver bisogno di interpretare direttamente.

Il problema non è soltanto economico. È anche architetturale:

  • Latenza: le chiamate al modello possono serializzare operazioni indipendenti.
  • Context pollution: risultati intermedi e log possono occupare una parte significativa della context window.
  • Recovery: un crash durante un'operazione lunga richiede meccanismi di ripresa espliciti.
  • Retry: ripetere un tool può duplicare effetti esterni.
  • Audit: ricostruire perché un'azione sia stata eseguita può diventare difficile.
  • Security: il contenuto restituito da un tool può influenzare impropriamente le decisioni successive dell'agente.

Un reasoning loop è appropriato quando ogni risultato richiede una nuova decisione semantica. Non è necessariamente la scelta migliore quando le operazioni sono note, verificabili e largamente indipendenti.

2. L'architettura emergente: Plan, Validate, Execute, Verify

La nuova separazione delle responsabilità può essere rappresentata così:

                 USER / EVENT
                      |
                      v
              +---------------+
              |  LLM PLANNER  |
              +---------------+
                      |
                 Proposed DAG
                      |
                      v
              +---------------+
              | DAG VALIDATOR |
              | Policy Engine |
              +---------------+
                      |
                Approved Plan
                      |
                      v
              +---------------+
              | DURABLE ENGINE|
              +---------------+
                /     |     \
               v      v      v
           Tool A   Tool B   Tool C
               \      |      /
                \     |     /
                 v    v    v
                AGGREGATION
                      |
                      v
                VERIFICATION
                      |
                      v
                FINAL RESULT

Il passaggio più importante è quello fra planner e runtime.

Il piano generato dal modello deve essere considerato un input non attendibile, anche quando il modello appartiene a un provider affidabile. La validazione non può essere una semplice verifica della sintassi JSON: deve includere controlli strutturali, semantici, autorizzativi e di consumo delle risorse.

Microsoft applica questo paradigma nella preview Dynamic Workflows di Azure Functions Hosted Skills. Il runtime utilizza Durable Functions per eseguire il piano senza riportare continuamente i risultati intermedi nel reasoning loop dell'LLM.

È importante distinguere la funzionalità specifica Microsoft, attualmente in preview e basata su tool Python, da un'implementazione equivalente realizzata autonomamente in C# e .NET con Durable Functions.

3. Il DAG come contratto eseguibile

Un Directed Acyclic Graph è un grafo diretto senza cicli. Ogni nodo rappresenta un'operazione; ogni arco rappresenta una dipendenza.

Consideriamo un piano per valutare il rischio associato a precipitazioni intense:

         ReadRainfall
              |
              +----------------+
              |                |
              v                v
        LoadTerrain      LoadAssets
              |                |
              v                |
        ComputeRunoff          |
              |                |
              +--------+-------+
                       |
                       v
                IntersectRisk
                       |
                       v
                 GenerateMap
                       |
                       v
                PublishResult

Il DAG esplicita immediatamente il parallelismo possibile: LoadTerrain e LoadAssets possono partire quando ReadRainfall è completato, se le rispettive operazioni non dipendono l'una dall'altra.

In un sistema reale conviene spingersi oltre, rendendo indipendenti tutte le acquisizioni che non richiedono il risultato delle precipitazioni.

Un contratto JSON minimale

{
  "schemaVersion": "1.0",
  "workflowId": "rainfall-risk-20261009",
  "nodes": [
    {
      "id": "rainfall",
      "tool": "gis.readRainfall",
      "dependsOn": []
    },
    {
      "id": "terrain",
      "tool": "gis.loadTerrain",
      "dependsOn": []
    },
    {
      "id": "assets",
      "tool": "gis.loadAssets",
      "dependsOn": []
    },
    {
      "id": "risk",
      "tool": "gis.computeRisk",
      "dependsOn": [
        "rainfall",
        "terrain",
        "assets"
      ]
    },
    {
      "id": "map",
      "tool": "gis.generateMap",
      "dependsOn": ["risk"]
    }
  ]
}

Questo JSON è una rappresentazione concettuale del nostro contratto applicativo, non lo schema ufficiale della preview Microsoft.

Un contratto production-grade dovrebbe inoltre includere versione del piano, input tipizzati, riferimenti agli output, classificazione dei tool, limiti di esecuzione e policy di autorizzazione. Gli argomenti non devono poter contenere endpoint arbitrari o istruzioni eseguibili non previste.

4. DAG validation: il vero confine di sicurezza

La validazione di un piano prodotto da un LLM deve essere eseguita prima che il workflow venga avviato.

I controlli minimi comprendono:

  • Identificatori univoci dei nodi.
  • Assenza di dipendenze inesistenti.
  • Assenza di cicli.
  • Limiti su numero di nodi, profondità e fan-out.
  • Tool presenti in una allowlist server-side.
  • Compatibilità tra input e output dei tool.
  • Autorizzazioni coerenti con utente, tenant e risorsa.
  • Limiti su costo, durata e concorrenza.
  • Approvazioni esplicite per operazioni con effetti esterni sensibili.

Validazione strutturale in C#

public sealed record WorkflowNode(
    string Id,
    string Tool,
    string[] DependsOn);

public sealed record WorkflowPlan(
    string SchemaVersion,
    WorkflowNode[] Nodes);

public static class DagValidator
{
    private static readonly HashSet<string> AllowedTools =
        new(StringComparer.Ordinal)
        {
            "gis.readRainfall",
            "gis.loadTerrain",
            "gis.loadAssets",
            "gis.computeRisk",
            "gis.generateMap"
        };

    public static void Validate(WorkflowPlan plan)
    {
        ArgumentNullException.ThrowIfNull(plan);

        if (plan.SchemaVersion != "1.0")
            throw new InvalidOperationException(
                "Unsupported schema version.");

        if (plan.Nodes is null ||
            plan.Nodes.Length is < 1 or > 50)
            throw new InvalidOperationException(
                "Invalid workflow size.");

        var nodes = new Dictionary<string, WorkflowNode>(
            StringComparer.Ordinal);

        foreach (var node in plan.Nodes)
        {
            if (string.IsNullOrWhiteSpace(node.Id) ||
                !nodes.TryAdd(node.Id, node))
                throw new InvalidOperationException(
                    "Invalid or duplicate node ID.");

            if (!AllowedTools.Contains(node.Tool))
                throw new InvalidOperationException(
                    $"Tool not allowed: {node.Tool}");
        }

        var indegree = nodes.Keys.ToDictionary(
            id => id,
            _ => 0,
            StringComparer.Ordinal);

        var children = nodes.Keys.ToDictionary(
            id => id,
            _ => new List<string>(),
            StringComparer.Ordinal);

        foreach (var node in nodes.Values)
        {
            var dependencies = node.DependsOn ??
                throw new InvalidOperationException(
                    "Dependencies cannot be null.");

            foreach (var dependency in dependencies.Distinct())
            {
                if (!nodes.ContainsKey(dependency))
                    throw new InvalidOperationException(
                        $"Unknown dependency: {dependency}");

                indegree[node.Id]++;
                children[dependency].Add(node.Id);
            }
        }

        var ready = new Queue<string>(
            indegree
                .Where(x => x.Value == 0)
                .Select(x => x.Key));

        var visited = 0;

        while (ready.Count > 0)
        {
            var id = ready.Dequeue();
            visited++;

            foreach (var child in children[id])
            {
                if (--indegree[child] == 0)
                    ready.Enqueue(child);
            }
        }

        if (visited != nodes.Count)
            throw new InvalidOperationException(
                "Workflow contains a cycle.");
    }
}

L'algoritmo utilizza una variante del topological sorting di Kahn. La complessità è O(V + E), dove V rappresenta i nodi ed E le dipendenze.

Questo codice valida soltanto la struttura e una allowlist di base. Non rappresenta un security validator completo. In produzione servono anche controllo degli argomenti, autorizzazione contestuale, compatibilità dei tipi, quote e validazione della provenienza del piano.

Un ulteriore accorgimento consiste nel calcolare un hash canonico del piano validato, registrando insieme ad esso identità del richiedente, versione delle policy e identificativo dell'esecuzione. Il runtime dovrebbe eseguire esattamente quel piano immutabile, evitando modifiche successive alla validazione.

5. Durable Functions: perché l'orchestrazione deve essere deterministica

Azure Durable Functions utilizza una forma di event sourcing per ricostruire lo stato delle orchestrazioni.

Quando un'orchestrazione riprende dopo un evento, il runtime può rieseguire il codice dell'orchestratore dall'inizio, recuperando dalla history i risultati delle attività già completate.

Questo meccanismo prende il nome di replay.

Execution history
       |
       +-- ActivityScheduled(A)
       +-- ActivityCompleted(A)
       +-- ActivityScheduled(B)
       +-- ActivityCompleted(B)
       |
       v
  Orchestrator Replay
       |
       +-- A: result from history
       +-- B: result from history
       +-- C: schedule new activity

Il codice dell'orchestratore deve quindi essere deterministico. Non dovrebbe effettuare direttamente chiamate HTTP, interrogazioni SQL, operazioni GIS o letture dell'orologio di sistema.

Le operazioni con effetti esterni appartengono alle Activity Functions.

Esempio di orchestrazione C# con .NET isolated worker

using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;

public static class GeoWorkflow
{
    [Function(nameof(RunGeoWorkflow))]
    public static async Task<string> RunGeoWorkflow(
        [OrchestrationTrigger]
        TaskOrchestrationContext context)
    {
        var rainfallTask =
            context.CallActivityAsync<string>(
                "ReadRainfall");

        var terrainTask =
            context.CallActivityAsync<string>(
                "LoadTerrain");

        var assetsTask =
            context.CallActivityAsync<string>(
                "LoadAssets");

        await Task.WhenAll(
            rainfallTask,
            terrainTask,
            assetsTask);

        var input = new RiskInput(
            rainfallTask.Result,
            terrainTask.Result,
            assetsTask.Result);

        var risk =
            await context.CallActivityAsync<string>(
                "ComputeRisk",
                input);

        return await context.CallActivityAsync<string>(
            "GenerateMap",
            risk);
    }
}

public sealed record RiskInput(
    string RainfallUri,
    string TerrainUri,
    string AssetsUri);

Questo è un esempio di orchestrazione statica. Non è ancora un interprete dinamico di DAG: serve a mostrare il modello Durable Functions e il parallelismo fan-out/fan-in.

Le tre Activity iniziali restituiscono riferimenti a dati persistenti, non interi raster o dataset serializzati nella history dell'orchestrazione.

Per interpretare DAG generati dinamicamente è preferibile utilizzare un executor generico che riceva un piano validato, identifichi i nodi pronti e li programmi attraverso un catalogo di Activity consentite.

6. Un DAG executor dinamico: la differenza fra scheduling e reasoning

Il runtime non deve chiedere all'LLM quale nodo eseguire dopo ogni completamento. Deve applicare una regola deterministica:

Ready(node) =
    AllDependenciesCompleted(node)
    AND PolicyAllows(node)
    AND ResourcesAvailable(node)

Un possibile algoritmo è:

completed = {}
pending = allNodes

while pending is not empty:

    ready = nodes where:
        every dependency is completed

    if ready is empty:
        fail invalid workflow

    execute ready nodes concurrently
    await completion

    persist outputs
    mark nodes completed
    remove nodes from pending

Questo pseudocodice illustra l'idea, ma in produzione richiede alcuni raffinamenti importanti.

Prima di tutto, i nodi devono essere ordinati stabilmente per evitare divergenze durante il replay. Inoltre la concorrenza deve essere limitata, gli errori devono essere gestiti per nodo e il risultato di ogni Activity deve essere registrato con un identificatore stabile.

Un semplice Task.WhenAll() su tutti i nodi pronti può produrre fan-out eccessivo. In un workflow geografico, cinquanta richieste simultanee a un servizio ArcGIS possono saturare il servizio, esaurire risorse o produrre throttling.

È quindi utile distinguere tre livelli di concorrenza:

  • Workflow concurrency: quante orchestrazioni possono essere attive.
  • Node concurrency: quanti nodi dello stesso DAG possono essere eseguiti contemporaneamente.
  • Resource concurrency: quante operazioni possono utilizzare uno specifico servizio, database o pool di worker.

Il terzo livello è particolarmente importante: limitare il fan-out di una singola orchestrazione non impedisce a cento orchestrazioni concorrenti di sovraccaricare lo stesso servizio.

7. Idempotenza: at-least-once non significa exactly-once

Uno dei problemi più pericolosi dei workflow distribuiti è la ripetizione delle operazioni.

Durable Functions può rieseguire un'Activity quando si verifica un errore o quando il completamento dell'attività non è stato registrato correttamente.

Supponiamo che un'Activity esegua:

POST /arcgis/rest/services/Analysis/GPServer/SubmitJob

Il servizio remoto riceve la richiesta e avvia il job. Subito dopo, il worker Azure Functions si interrompe prima di registrare il risultato.

Al riavvio, il runtime può ripetere l'Activity.

Il risultato potrebbe essere:

First attempt  --> ArcGIS Job 1001
Worker crash
Second attempt --> ArcGIS Job 1002

Il sistema ha prodotto due elaborazioni per una sola richiesta logica.

Il retry è quindi corretto dal punto di vista dell'infrastruttura, ma non necessariamente dal punto di vista del business.

La soluzione: Operation Ledger e Idempotency Key

Ogni operazione con effetti esterni dovrebbe avere una chiave stabile:

IdempotencyKey =
    WorkflowId + ":" + NodeId

Una tabella SQL può mantenere lo stato dell'operazione:

CREATE TABLE WorkflowOperations
(
    OperationKey NVARCHAR(200) NOT NULL
        PRIMARY KEY,

    State NVARCHAR(30) NOT NULL,

    ExternalJobId NVARCHAR(200) NULL,

    ResultUri NVARCHAR(2000) NULL,

    UpdatedAt DATETIMEOFFSET NOT NULL
);

Il pattern operativo diventa:

1. Reserve OperationKey atomically
2. Check previous execution state
3. Submit or reconcile external job
4. Persist ExternalJobId
5. Poll external job
6. Persist ResultUri
7. Mark operation Completed

È fondamentale riconoscere che il ledger, da solo, non garantisce exactly-once.

Esiste infatti una finestra critica fra l'invio della richiesta al sistema esterno e il salvataggio dell'ExternalJobId. Se il processo si interrompe in quel punto, il database locale non sa se l'operazione remota sia stata accettata.

Per eliminare questa ambiguità serve un meccanismo di riconciliazione: una chiave di idempotenza supportata dal servizio remoto, un identificatore applicativo ricercabile oppure un adapter che possa individuare un job già creato.

Se l'API esterna non supporta alcuna forma di deduplicazione o riconciliazione, la garanzia di exactly-once non può essere ottenuta semplicemente aggiungendo retry e una tabella SQL.

8. Retry, timeout, circuit breaker e compensazione

Un workflow distribuito affidabile deve distinguere gli errori transitori da quelli permanenti.

Un HTTP 429 o 503 può giustificare un retry con backoff. Un errore di autorizzazione, un input GIS non valido o un piano che viola una policy non dovrebbe invece essere ripetuto automaticamente.

Per gli errori transitori è possibile utilizzare le retry policy delle Activity Durable Functions.

Per operazioni che modificano sistemi esterni è inoltre utile introdurre una logica di compensazione.

Consideriamo:

CreateAnalysisJob
        |
        v
PublishFeatureLayer
        |
        v
UpdateWebMap
        |
        v
NotifyUsers

Se UpdateWebMap fallisce dopo la pubblicazione del layer, il sistema potrebbe dover eliminare il layer temporaneo o ripristinare la configurazione precedente.

Questo modello è vicino al pattern Saga: ogni azione può avere un'azione compensativa, ma la compensazione non equivale necessariamente a un rollback transazionale perfetto.

Per un workflow geografico occorre inoltre distinguere:

  • Risultati temporanei, eliminabili automaticamente.
  • Risultati pubblicati, che richiedono autorizzazioni specifiche per la rimozione.
  • Modifiche a dati autorevoli, che possono richiedere approvazione umana.
  • Notifiche già inviate, che non possono essere semplicemente annullate.

La progettazione delle compensazioni deve essere parte del catalogo dei tool, non una decisione improvvisata dall'LLM dopo un errore.

9. Isolamento dei sub-agent: un agente specializzato non è un processo fidato

Un workflow può delegare attività a sub-agent specializzati.

Per esempio:

GeoCoordinator
      |
      +-- RainfallAgent
      |
      +-- TerrainAgent
      |
      +-- InfrastructureAgent
      |
      +-- RiskVerificationAgent

Il vantaggio è la specializzazione. Il rischio è l'ampliamento della superficie di attacco.

Un sub-agent non dovrebbe ereditare automaticamente l'intera cronologia del coordinatore, tutte le sue credenziali o la capacità di invocare qualsiasi tool.

Nella preview Microsoft Dynamic Workflows, le invocazioni dei sub-agent utilizzano sessioni isolate e stateless, con istruzioni, modelli, strumenti e timeout propri. Non ricevono automaticamente la conversation history o la sandbox del coordinatore.

Questo è un buon punto di partenza, ma non sostituisce l'isolamento infrastrutturale.

Capability-based execution

Ogni sub-agent dovrebbe ricevere soltanto le capability necessarie.

RainfallAgent
    READ rainfall observations
    READ station metadata
    NO write access
    NO publishing permissions

TerrainAgent
    READ elevation datasets
    EXECUTE terrain analysis
    NO access to notifications

PublisherAgent
    READ approved outputs
    PUBLISH to approved folders
    NO arbitrary service access

Il controllo deve essere imposto dal gateway dei tool e dalle identità utilizzate verso i servizi, non soltanto da istruzioni testuali come "non pubblicare dati".

In Azure, Managed Identity, Azure RBAC, Key Vault e isolamento di rete possono contribuire a questo modello, purché le identità siano effettivamente separate secondo i confini di autorizzazione richiesti.

10. Context engineering: la memoria dell'agente non è lo stato del workflow

Un altro errore frequente consiste nell'utilizzare la conversation history come archivio dello stato operativo.

La memoria del modello è utile per mantenere obiettivi, vincoli e informazioni semantiche. Non dovrebbe essere la fonte autorevole per determinare se un job è completato, quale output è stato pubblicato o quali autorizzazioni sono state concesse.

Propongo di separare quattro livelli:

+----------------------------------+
| Semantic Context                 |
| Goal, assumptions, constraints   |
+----------------------------------+
| Execution State                  |
| Node status, retries, timestamps |
+----------------------------------+
| Artifact Store                   |
| Raster, JSON, logs, results      |
+----------------------------------+
| Audit & Authorization            |
| Identity, approvals, provenance  |
+----------------------------------+

Il modello dovrebbe ricevere soltanto il contesto necessario per decidere il piano e, alla fine, interpretare i risultati.

Gli output voluminosi dovrebbero essere conservati in storage e rappresentati attraverso riferimenti immutabili o versionati, accompagnati da metadata come checksum, schema, sistema di riferimento e provenienza.

Questo evita che raster, grandi FeatureSet, log o migliaia di record GIS vengano inseriti nella context window senza alcun beneficio.

11. GeoAgent: un laboratorio architetturale con ArcGIS e Azure

Immaginiamo di voler realizzare un agente che risponda alla richiesta:

Analizza le precipitazioni delle ultime 24 ore, individua le aree potenzialmente critiche e produci una mappa delle infrastrutture esposte.

Il modello non dovrebbe generare direttamente una mappa di rischio basandosi soltanto sul testo. Deve selezionare strumenti GIS deterministici, ciascuno con input, output e limiti ben definiti.

Pipeline proposta

Weather / Rainfall API
          |
          v
   Observation Validation
          |
          v
   Spatial Interpolation
      IDW / Spline
          |
          v
   Rainfall Raster
          |
          +------------------+
          |                  |
          v                  v
    Terrain Data       Asset Inventory
          |                  |
          +---------+--------+
                    |
                    v
             Spatial Analysis
                    |
                    v
             Exposure Overlay
                    |
                    v
             Result Validation
                    |
                    v
             ArcGIS Publishing
                    |
                    v
               WebMap / App

Una possibile implementazione utilizza:

  • C#/.NET: API di ingresso, validazione, orchestrazione e integrazione enterprise.
  • Azure Durable Functions: scheduling durevole, retry e recovery.
  • Azure SQL: operation ledger, metadata, policy e audit applicativo.
  • Azure Blob Storage: artifact store per input e output voluminosi.
  • ArcGIS Enterprise: geoprocessing, analisi raster, feature service e pubblicazione.
  • LLM: interpretazione della richiesta, costruzione del piano e sintesi finale.

È importante non confondere l'interpolazione delle precipitazioni con una simulazione idrologica o idraulica completa.

IDW e Spline producono superfici interpolate a partire dalle osservazioni. Per modellare runoff, propagazione delle piene o dinamiche fisiche occorrono modelli e dati aggiuntivi, opportunamente calibrati e validati.

Inoltre l'eventuale esecuzione di Spatial Analyst richiede di verificare disponibilità delle capability e licenze nella versione e nell'ambiente ArcGIS effettivamente distribuiti.

12. Un adapter ArcGIS robusto deve conoscere il ciclo di vita dei job

Un adapter per un servizio ArcGIS Geoprocessing asincrono non dovrebbe limitarsi a invocare SubmitJob e attendere indefinitamente.

Deve modellare esplicitamente gli stati del job remoto:

NotSubmitted
      |
      v
  Submitted
      |
      v
   Executing
      |
      +----> Succeeded
      |
      +----> Failed
      |
      +----> Cancelled
      |
      +----> TimedOut

La gestione deve distinguere lo stato del job ArcGIS dallo stato dell'Activity Azure Functions.

Un job ArcGIS può continuare a essere eseguito anche quando il worker che lo ha avviato non è più disponibile. Per questo è preferibile salvare l'identificatore del job e utilizzare un meccanismo di polling o riconciliazione durevole.

Per processi molto lunghi, il pattern può diventare:

Submit GP Job
     |
     v
Persist Job ID
     |
     v
Durable Timer
     |
     v
Check GP Status
     |
     +-- Running --> Durable Timer
     |
     +-- Success --> Fetch Results
     |
     +-- Failure --> Error Policy

Il polling non dovrebbe occupare un thread o una Function instance per tutta la durata del job.

Per i servizi GIS occorre inoltre definire timeout, frequenza di polling, politiche di cancellazione e comportamento in caso di perdita temporanea della connettività.

13. Observability: misurare il workflow, non soltanto i token

Un sistema agentico enterprise dovrebbe essere osservabile su più livelli.

Il livello LLM deve registrare modello, versione, token, latenza, versione del prompt e identificativo del piano generato.

Il livello workflow deve registrare durata dei nodi, retry, timeout, dipendenze, errori e stato complessivo.

Il livello GIS deve registrare servizio utilizzato, identificativo del job, tempi di elaborazione, parametri analitici, dataset e riferimenti agli output.

Una struttura di correlazione può essere:

TraceId
   |
   +-- PlanningSpan
   |
   +-- ValidationSpan
   |
   +-- WorkflowInstanceId
          |
          +-- Node: rainfall
          +-- Node: terrain
          +-- Node: risk
          +-- Node: publishing
                    |
                    +-- ArcGIS JobId

OpenTelemetry e Application Insights possono contribuire a questa osservabilità, ma occorre propagare esplicitamente gli identificatori di correlazione fra sistemi.

Il replay delle orchestrazioni richiede inoltre attenzione: log prodotti ingenuamente durante il replay possono essere duplicati. Dove disponibile, conviene utilizzare il replay-safe logger fornito dal runtime Durable Functions.

Metriche che misurerei

  • Planning latency e planning token cost.
  • Percentuale di piani rifiutati dal validator.
  • Numero di nodi e profondità media del DAG.
  • Parallelismo effettivo rispetto a quello teorico.
  • Retry per tool e per servizio esterno.
  • Numero di job remoti riconciliati dopo failure.
  • Workflow completion rate.
  • Tempo medio e percentile P95/P99 per nodo.
  • Costo per workflow completato.
  • Accuratezza e validità dei risultati GIS.

Una riduzione dei token non rappresenta automaticamente un miglioramento del sistema se aumentano i workflow falliti o diminuisce la qualità del risultato.

14. Sicurezza: il piano generato dall'LLM è una superficie di attacco

Un piano apparentemente valido può essere pericoloso anche senza contenere codice eseguibile.

Consideriamo alcuni scenari:

  • Tool escalation: il modello seleziona un tool di pubblicazione quando l'utente ha richiesto soltanto un'analisi.
  • Data exfiltration: un parametro contiene un endpoint esterno non autorizzato.
  • Resource exhaustion: il piano richiede centinaia di analisi raster molto costose.
  • Cross-tenant access: un nodo tenta di utilizzare dati appartenenti a un altro tenant.
  • Prompt injection: un dataset o documento esterno influenza il planner inducendolo a inserire operazioni non richieste.
  • Approval spoofing: un output intermedio dichiara falsamente che l'utente ha autorizzato un'azione.

Le contromisure devono essere applicate fuori dal modello:

Untrusted User / Tool Data
             |
             v
        LLM Planner
             |
             v
        Schema Validator
             |
             v
        Policy Decision
             |
             v
       Approval Gateway
             |
             v
        Tool Dispatcher
             |
             v
     Authorized Execution

Un'approvazione dovrebbe essere un evento autenticato e correlato a una specifica operazione, non una proprietà booleana modificabile dal modello.

Il tool dispatcher dovrebbe risolvere le credenziali dal contesto server-side, evitando che il piano possa scegliere liberamente identità, segreti o endpoint.

15. Benchmark Microsoft: cosa dimostrano realmente i numeri

Nel proprio annuncio, Microsoft ha confrontato un normale reasoning loop con Dynamic Workflows su un benchmark sintetico di analisi di dieci servizi.

I risultati riportati sono:

MetricaReasoning loopDynamic Workflow
Token utilizzati99.0976.781
Latenza247,1 s12,7 s
Riduzione token—93,1%
Riduzione latenza—95,1%

I numeri sono notevoli, ma vanno interpretati correttamente. Il benchmark riguarda un task specifico, strutturato e adatto alla parallelizzazione. Non significa che qualsiasi workflow agentico possa ottenere automaticamente riduzioni equivalenti.

Il vantaggio dipende soprattutto da tre fattori:

  • Le attività indipendenti possono essere eseguite in parallelo.
  • Gli output intermedi non devono attraversare continuamente il modello.
  • Il numero di round-trip LLM viene drasticamente ridotto.

Un workflow che richiede decisioni semantiche dopo ogni passaggio potrebbe invece beneficiare meno di questo modello.

16. Il trade-off: meno autonomia durante l'esecuzione, più affidabilità

Separare pianificazione ed esecuzione introduce anche un limite.

Un DAG statico non può adattarsi arbitrariamente a qualsiasi informazione inattesa. Se un risultato intermedio richiede una nuova strategia, il workflow deve prevedere una decisione condizionale oppure una fase esplicita di replanning.

Per questo considero particolarmente interessante un modello ibrido:

LLM Planning
      |
      v
Validated DAG
      |
      v
Deterministic Execution
      |
      +-- Expected Result
      |       |
      |       v
      |   Continue DAG
      |
      +-- Unexpected Result
              |
              v
        Controlled Replan
              |
              v
        New Validation
              |
              v
        Resume Execution

Il replanning deve essere limitato e tracciato. Ogni nuovo piano deve essere nuovamente validato, e il sistema deve conoscere quali operazioni precedenti siano già state completate.

Non dovrebbe essere possibile aggirare un rifiuto del validator semplicemente generando ripetutamente nuovi piani.

17. Verso una nuova architettura: il GeoAgent come compilatore di workflow geografici

La prospettiva più interessante è considerare l'LLM non come il motore di esecuzione, ma come un compilatore semantico.

L'utente esprime un obiettivo in linguaggio naturale. Il modello lo traduce in una rappresentazione intermedia tipizzata. Il sistema valida questa rappresentazione, la ottimizza e la esegue utilizzando strumenti GIS affidabili.

Natural Language Goal
          |
          v
      LLM Planner
          |
          v
    GeoWorkflow IR
          |
          v
   Semantic Validation
          |
          v
   Optimization Passes
          |
          v
    Execution Plan
          |
          v
 Durable GIS Runtime
          |
          v
 Verified GeoArtifacts

Questa analogia con un compilatore è particolarmente potente.

La rappresentazione intermedia potrebbe contenere informazioni tipizzate come:

  • Sistema di riferimento spaziale.
  • Tipo geometrico.
  • Risoluzione raster.
  • Unità di misura.
  • Estensione geografica.
  • Dipendenze tra dataset.
  • Requisiti di licenza e capability GIS.
  • Classificazione dei dati.
  • Stima del costo computazionale.

Il validator potrebbe rifiutare un'operazione che combina raster con sistemi di riferimento incompatibili, utilizza unità di misura non coerenti oppure richiede capability non disponibili.

Un optimizer potrebbe invece individuare acquisizioni parallelizzabili, eliminare elaborazioni duplicate, riutilizzare risultati già presenti nello storage o selezionare differenti backend di elaborazione.

È importante distinguere ciò che è già disponibile da ciò che rappresenta una possibile evoluzione progettuale: questa GeoWorkflow IR è una proposta architetturale, non un prodotto Esri o Microsoft attualmente disponibile con tale nome.

18. Oltre il DAG: workflow gerarchici, feedback loop e sistemi autonomi

Il DAG è ideale per rappresentare dipendenze acicliche. Non descrive però naturalmente sistemi che devono reagire indefinitamente a nuovi eventi.

Un sistema di monitoraggio territoriale continuo può richiedere cicli di osservazione, analisi, verifica e aggiornamento.

La soluzione non è introdurre cicli arbitrari dentro un DAG non controllato, ma comporre workflow durevoli di durata limitata attraverso eventi, sub-orchestrazioni e nuove istanze.

Observation Event
       |
       v
Analysis Workflow
       |
       v
Verification
       |
       +-- No Change --> Archive
       |
       +-- Relevant Change
                  |
                  v
             New Event
                  |
                  v
          Follow-up Workflow

In questo modo ogni esecuzione rimane identificabile, verificabile e soggetta a quote, mentre il sistema complessivo può funzionare continuamente.

È una distinzione importante per il futuro degli agenti persistenti: persistenza della responsabilità non significa esecuzione illimitata di un singolo processo o di una singola conversazione LLM.

19. Checklist di produzione

Prima di portare in produzione un'architettura agentic workflow, verificherei almeno questi aspetti:

  • Il piano viene validato prima dell'esecuzione.
  • Ogni tool è registrato in un catalogo server-side.
  • Gli input sono tipizzati e soggetti a limiti.
  • Le autorizzazioni vengono verificate sul backend.
  • Le operazioni con effetti esterni hanno idempotency key e riconciliazione.
  • Gli orchestratori rispettano i vincoli di determinismo del replay.
  • I job remoti possono essere recuperati dopo un crash.
  • Gli output voluminosi sono esternalizzati dalla history.
  • I sub-agent hanno capability limitate.
  • Le approvazioni sono eventi autenticati.
  • Il parallelismo è limitato anche a livello di risorsa condivisa.
  • Le modifiche ai workflow già attivi sono gestite con versioning.
  • Gli audit log sono protetti da modifiche non autorizzate.
  • Le metriche misurano correttezza, costo e affidabilità, non soltanto latenza.
  • Esistono test di fault injection per crash, timeout, duplicazioni e retry.

20. Conclusione: l'LLM non deve diventare il transaction manager

Dynamic Workflows rappresenta un passaggio significativo nell'evoluzione delle architetture agentiche.

Non perché renda i modelli più intelligenti, ma perché riconosce esplicitamente che intelligenza, autorizzazione ed esecuzione affidabile sono problemi differenti.

Il modello interpreta obiettivi ambigui e propone piani. Il validator controlla che quei piani siano ammissibili. Il workflow engine esegue le attività rispettando dipendenze, retry e checkpoint. I tool applicano le operazioni reali attraverso API e identità autorizzate.

Nel mondo GIS questa separazione è ancora più importante: un LLM può scegliere quali analisi geospaziali effettuare, ma la correttezza geometrica, statistica e fisica deve rimanere responsabilità di algoritmi, dati e procedure verificabili.

La prossima generazione di GeoAgent potrebbe quindi non essere un chatbot con accesso a qualche geoprocessing tool, ma un vero sistema capace di compilare richieste territoriali in workflow geografici tipizzati, verificabili, riproducibili e osservabili.

La direzione è chiara: l'LLM definisce l'intenzione, il piano diventa un contratto e il runtime garantisce l'esecuzione. L'autonomia utile nasce quando il ragionamento probabilistico incontra un'infrastruttura deterministica.

Riferimenti tecnici

mercoledì 30 settembre 2026

DiskANN in Azure SQL: Vector Search, RAG e GeoAI direttamente nel database relazionale

La ricerca vettoriale sta progressivamente entrando nel cuore dei database relazionali. Non si tratta semplicemente di aggiungere una nuova funzione SQL: significa poter interrogare gli stessi dati contemporaneamente nello spazio relazionale, nello spazio geografico e nello spazio semantico degli embedding.

Con DiskANN, Azure SQL può memorizzare embedding e utilizzare un indice Approximate Nearest Neighbor direttamente accanto a tabelle, chiavi, transazioni, metadati, filtri di sicurezza e, potenzialmente, dati spaziali.

Per un architect questo apre una domanda interessante: abbiamo davvero bisogno di introdurre un vector database separato per ogni applicazione RAG?

In molti scenari la risposta potrebbe essere no.

Cos'è DiskANN

DiskANN significa Disk-based Approximate Nearest Neighbor. È una famiglia di tecniche sviluppate per eseguire rapidamente ricerche di similarità su collezioni vettoriali di grandi dimensioni.

Non genera embedding, non interpreta documenti e non è un Large Language Model. DiskANN interviene dopo che i dati sono già stati trasformati in vettori.

Document
    ↓
Text extraction
    ↓
Chunking
    ↓
Embedding model
    ↓
Vector
    ↓
Azure SQL
    ↓
DiskANN
    ↓
Nearest neighbors

Il problema che risolve è apparentemente semplice: dato un vettore q, trovare i vettori del database che gli sono più vicini.

Dallo spazio semantico allo spazio vettoriale

Un embedding model trasforma un oggetto, ad esempio una frase:

ArcGIS Enterprise supports federated GIS servers.

in una sequenza numerica:

[0.0182, -0.0317, 0.0041, ..., 0.0227]

Formalmente possiamo pensare all'embedding come a una funzione:

f : X → ℝᵈ

dove X è lo spazio degli oggetti originali e d è la dimensionalità del vettore.

La singola coordinata non ha generalmente un significato interpretabile isolatamente. È la posizione complessiva del vettore nello spazio multidimensionale a rappresentare le caratteristiche apprese dal modello.

Se due contenuti hanno significato simile, il modello tende a collocarli in regioni vicine dello spazio vettoriale.

Exact Nearest Neighbor: perché non basta

Il modo più semplice di trovare i vettori più vicini consiste nel confrontare la query con ogni vettore presente nella tabella.

query
  │
  ├── distance(vector 1)
  ├── distance(vector 2)
  ├── distance(vector 3)
  ├── ...
  └── distance(vector N)

Con N vettori e dimensionalità D, il lavoro cresce approssimativamente come:

O(N × D)

Con poche migliaia di record può essere perfettamente accettabile.

Con:

10.000.000 vectors
×
1.536 dimensions

la scansione esaustiva diventa invece molto più costosa.

È qui che entra in gioco l'Approximate Nearest Neighbor Search.

ANN: sacrificare un po' di perfezione per molta velocità

Un algoritmo ANN non garantisce necessariamente di restituire sempre gli stessi vicini che otterremmo esaminando esaustivamente l'intero dataset.

Accetta un compromesso:

Exactness
    ↓ leggermente

Search space
    ↓ drasticamente

Latency
    ↓ drasticamente

Supponiamo che i veri dieci nearest neighbors siano:

A B C D E F G H I J

e che ANN restituisca:

A B C D E F G H I K

Il recall@10 sarebbe:

9 / 10 = 0.90

Per molte pipeline RAG questo trade-off è estremamente conveniente: non abbiamo bisogno di dimostrare matematicamente quale chunk sia il più vicino, ma di recuperare molto rapidamente un insieme di candidati altamente rilevanti.

DiskANN e grafi di prossimità

DiskANN utilizza un approccio graph-based. Possiamo immaginare ogni embedding come un nodo collegato ad altri nodi utili per navigare nello spazio vettoriale.

              ●──────●
             /        \
        ●───●          ●
        │    \        /
        ●─────●──────●
               \
                ●────●

La ricerca non deve quindi fare:

query → confronta tutti i vettori

ma può procedere concettualmente attraverso il grafo:

query
  ↓
entry point
  ↓
candidate neighbors
  ↓
better neighbors
  ↓
better neighbors
  ↓
Top-K

Il grafo consente di raggiungere rapidamente regioni promettenti dello spazio senza valutare ogni riga.

Perché si chiama DiskANN

Uno degli elementi caratteristici del progetto DiskANN è l'attenzione alla possibilità di lavorare con collezioni vettoriali molto grandi senza dipendere esclusivamente dalla RAM.

              RAM
       ┌────────────────┐
       │ search state   │
       │ cache          │
       │ graph metadata │
       └───────┬────────┘
               │
               │ selective access
               ▼
       ┌────────────────┐
       │ SSD / storage  │
       │ vector data    │
       └────────────────┘

Naturalmente, quando utilizziamo DiskANN tramite Azure SQL non dobbiamo confondere il principio algoritmico con l'implementazione interna del servizio managed: cache, storage engine, I/O scheduling, quantizzazione e query optimization sono responsabilità del database engine.

DiskANN è ora production-ready su Azure SQL

Il 29 settembre 2026 Microsoft ha annunciato la General Availability di DiskANN Vector Index & Search su Azure SQL Database, su Azure SQL Managed Instance configurato con la policy di aggiornamento always-up-to-date e su SQL database in Microsoft Fabric.

La versione GA include elementi particolarmente importanti per workload reali:

  • Approximate Nearest Neighbor Search basata su DiskANN;
  • supporto a INSERT, UPDATE e DELETE;
  • manutenzione asincrona dell'indice vettoriale;
  • iterative filtering;
  • integrazione con il query optimizer;
  • possibilità di combinare vector search e predicati relazionali.

Il tipo VECTOR di SQL

Il database può memorizzare direttamente un embedding in una colonna vettoriale.

Un modello dati orientato a RAG potrebbe essere:

CREATE TABLE dbo.Documents
(
    Id          BIGINT IDENTITY PRIMARY KEY,
    TenantId    INT NOT NULL,
    FileName    NVARCHAR(500) NOT NULL,
    Title       NVARCHAR(500) NULL,
    MimeType    NVARCHAR(100) NULL,
    CreatedAt   DATETIME2 NOT NULL
        DEFAULT SYSUTCDATETIME()
);

I chunk possono invece essere mantenuti separatamente:

CREATE TABLE dbo.DocumentChunks
(
    Id               BIGINT IDENTITY PRIMARY KEY,
    DocumentId       BIGINT NOT NULL,
    TenantId         INT NOT NULL,

    PageNumber       INT NULL,
    ChunkNumber      INT NOT NULL,

    Content          NVARCHAR(MAX) NOT NULL,

    EmbeddingModel   NVARCHAR(100) NOT NULL,
    ChunkingVersion  INT NOT NULL,

    Embedding        VECTOR(1536) NOT NULL,

    CONSTRAINT FK_DocumentChunks_Documents
        FOREIGN KEY (DocumentId)
        REFERENCES dbo.Documents(Id)
);

1536 è solamente un esempio. La dimensionalità deve essere coerente con il modello che genera gli embedding.

Versionare embedding e pipeline

Un embedding dovrebbe essere trattato come dato derivato, non come sorgente primaria.

Conviene quindi conservare almeno:

Original document
Extracted content
Chunk
ChunkingVersion
Embedding
EmbeddingModel
EmbeddingVersion
CreatedAt

Questa informazione diventa fondamentale quando cambiamo modello.

Due modelli differenti possono produrre spazi vettoriali differenti: non possiamo semplicemente mischiare embedding generati da modelli diversi e aspettarci che le distanze rimangano semanticamente confrontabili.

Creazione dell'indice DiskANN

Sulla colonna vettoriale possiamo creare un indice:

CREATE VECTOR INDEX IX_DocumentChunks_Embedding
ON dbo.DocumentChunks(Embedding)
WITH
(
    METRIC = 'cosine',
    TYPE = 'diskann'
);

Le metriche supportate dalla vector search SQL comprendono:

  • cosine;
  • dot;
  • euclidean.

La metrica non va scelta arbitrariamente: deve essere coerente con il modello di embedding e con il modo in cui è stato addestrato.

Exact search con VECTOR_DISTANCE

Possiamo eseguire una ricerca esatta calcolando la distanza:

DECLARE @query VECTOR(1536) = @QueryEmbedding;

SELECT TOP (10)
    Id,
    DocumentId,
    Content,
    VECTOR_DISTANCE(
        'cosine',
        @query,
        Embedding
    ) AS Distance
FROM dbo.DocumentChunks
ORDER BY Distance;

Questa strategia è utile anche come baseline per misurare la qualità dell'indice ANN.

Possiamo infatti confrontare:

Exact Top-K
     vs
DiskANN Top-K

e misurare il recall effettivo sul nostro dataset.

Approximate search con VECTOR_SEARCH

Con gli indici vettoriali di nuova generazione la sintassi corrente utilizza VECTOR_SEARCH insieme a:

TOP (N) WITH APPROXIMATE

Ad esempio:

DECLARE @query VECTOR(1536) = @QueryEmbedding;

SELECT TOP (10) WITH APPROXIMATE
    c.Id,
    c.DocumentId,
    c.Content,
    r.distance
FROM VECTOR_SEARCH(
    TABLE = dbo.DocumentChunks AS c,
    COLUMN = Embedding,
    SIMILAR_TO = @query,
    METRIC = 'cosine'
) AS r
ORDER BY r.distance;

La precedente forma basata sul parametro:

TOP_N = 10

appartiene alla generazione precedente dell'indice ed è deprecata per i nuovi indici.

WITH APPROXIMATE non è solo sintassi

Con:

SELECT TOP (10) WITH APPROXIMATE

stiamo dicendo esplicitamente al motore che accettiamo risultati ANN.

Senza WITH APPROXIMATE, VECTOR_SEARCH può eseguire una ricerca kNN esatta.

VECTOR_SEARCH
      │
      ├── exact kNN
      │
      └── approximate ANN / DiskANN

La distinzione è molto importante quando facciamo benchmarking.

Il query optimizer entra nella vector search

Uno degli aspetti architetturalmente più interessanti è che la ricerca vettoriale non viene trattata come una scatola completamente separata dal motore SQL.

Azure SQL può valutare se utilizzare la strategia ANN oppure altre strategie in funzione della query e della selettività.

Supponiamo di avere:

100.000.000 chunks

ma di applicare:

WHERE TenantId = 42

e che il tenant 42 contenga soltanto 5.000 chunk. In determinate condizioni un piano differente potrebbe risultare più conveniente rispetto all'attraversamento del grafo ANN globale.

FORCE_ANN_ONLY

Quando vogliamo imporre l'utilizzo dell'indice Approximate Nearest Neighbor, Azure SQL mette a disposizione anche l'hint:

FORCE_ANN_ONLY

Concettualmente:

SELECT TOP (10) WITH APPROXIMATE
    ...
FROM VECTOR_SEARCH(
    TABLE = dbo.DocumentChunks AS c,
    COLUMN = Embedding,
    SIMILAR_TO = @query,
    METRIC = 'cosine'
) AS r WITH (FORCE_ANN_ONLY)
ORDER BY r.distance;

È utile soprattutto per test, benchmark o casi in cui sappiamo esattamente quale strategia vogliamo utilizzare.

Come ogni hint SQL, non dovrebbe diventare automaticamente la scelta predefinita.

Iterative filtering: fondamentale per sistemi reali

Immaginiamo una piattaforma SaaS multi-tenant.

Il retrieval deve rispettare:

TenantId = 42

Una implementazione ANN ingenua potrebbe fare:

DiskANN
   ↓
10 nearest vectors
   ↓
WHERE TenantId = 42
   ↓
2 risultati validi

Gli altri otto vettori potrebbero appartenere ad altri tenant.

Con l'iterative filtering la ricerca può continuare nel grafo finché non vengono trovati abbastanza risultati che soddisfano il predicato:

DiskANN
   ↓
candidate set
   ↓
relational filter
   ↓
enough results?
   │
   ├── yes → return Top-K
   │
   └── no
        ↓
   continue graph traversal
        ↓
   more candidates
        ↓
   relational filter

Questa caratteristica è importantissima per:

  • multi-tenancy;
  • ACL;
  • categorie;
  • stati applicativi;
  • vincoli temporali;
  • filtri geografici o amministrativi;
  • security policies.

Vector search e security boundary

In un'applicazione RAG enterprise la sicurezza non deve essere applicata dopo il retrieval.

Il flusso corretto deve essere progettato come:

User
 ↓
Identity
 ↓
Tenant / ACL / Security Policy
 ↓
Vector retrieval
 ↓
Permitted chunks
 ↓
LLM

In altre parole: il retrieval fa parte del security boundary dell'applicazione.

Il vero RAG inizia prima di DiskANN

Una delle semplificazioni più pericolose è pensare che una pipeline RAG sia:

PDF → embedding → vector database

Una pipeline seria assomiglia molto di più a:

                   PDF
                    │
          ┌─────────┴──────────┐
          │                    │
     Native text          Scanned PDF
          │                    │
   Text extraction            OCR
          │                    │
          └─────────┬──────────┘
                    ↓
             normalization
                    ↓
          structure analysis
                    ↓
               chunking
                    ↓
               embedding
                    ↓
               Azure SQL
                    ↓
                DiskANN

PDF nativi e PDF scansionati

Un PDF può contenere un vero text layer. In questo caso è possibile estrarre direttamente:

Page
 ├── Heading
 ├── Paragraph
 ├── Paragraph
 └── Table

Un PDF scansionato può invece contenere essenzialmente immagini:

PDF
 ├── page-001 image
 ├── page-002 image
 └── page-003 image

In questo caso serve OCR.

In ambiente Azure una pipeline possibile è:

Azure Blob Storage
        ↓
Document ingestion service
        ↓
Azure AI Document Intelligence
        ↓
OCR + Layout + Tables + Structure
        ↓
Semantic chunking
        ↓
Embedding model
        ↓
Azure SQL
        ↓
DiskANN

Il punto importante è che non basta recuperare caratteri: serve preservare il più possibile la struttura semantica del documento.

Chunking: spesso più importante del vector database

Consideriamo un manuale tecnico di 500 pagine.

Creare:

manual.pdf → one embedding

è generalmente poco utile.

È molto più efficace ottenere:

manual.pdf
   │
   ├── chunk 001 → embedding
   ├── chunk 002 → embedding
   ├── chunk 003 → embedding
   ├── ...
   └── chunk 850 → embedding

Fixed-size chunking

L'approccio più semplice consiste nel suddividere il testo per numero di token:

chunk 1 → tokens   0 - 499
chunk 2 → tokens 450 - 949
chunk 3 → tokens 900 - 1399

L'overlap aiuta a evitare che un concetto venga spezzato esattamente sul confine tra due chunk.

Semantic chunking

Per documentazione tecnica possiamo però fare di meglio.

Section
 ├── paragraph
 ├── paragraph
 ├── code sample
 ├── table
 └── note

L'obiettivo è costruire unità informative semanticamente coerenti, non semplicemente blocchi di lunghezza costante.

Hierarchical chunking

Possiamo mantenere una gerarchia:

Document
   ↓
Chapter
   ↓
Section
   ↓
Chunk

Se DiskANN recupera il chunk 37 possiamo, ad esempio, recuperare anche:

chunk 36
chunk 37
chunk 38

oppure la sezione padre.

Per questo conviene conservare:

DocumentId
SectionId
ParentChunkId
PageNumber
ChunkNumber

Embedding generation lato applicazione

Una pipeline .NET può essere concettualmente molto semplice:

var chunks = chunker.Split(document);

foreach (var chunk in chunks)
{
    var embedding =
        await embeddingService.GenerateAsync(chunk.Text);

    db.DocumentChunks.Add(
        new DocumentChunk
        {
            DocumentId = documentId,
            TenantId = tenantId,
            PageNumber = chunk.Page,
            ChunkNumber = chunk.Index,
            Content = chunk.Text,
            EmbeddingModel = embedding.Model,
            ChunkingVersion = 1,
            Embedding = embedding.Vector
        });
}

await db.SaveChangesAsync();

Dal punto di vista architetturale terrei ben separati:

Extraction
Chunking
Embedding
Persistence
Indexing
Retrieval
Generation

Questo rende la pipeline versionabile, osservabile e riprocessabile.

Azure SQL può generare embedding direttamente

Lo stack SQL moderno consente anche di registrare modelli esterni e invocarli dal database.

Un external model può essere definito concettualmente così:

CREATE EXTERNAL MODEL MyEmbeddingModel
WITH
(
    LOCATION = '...',
    API_FORMAT = 'Azure OpenAI',
    MODEL_TYPE = EMBEDDINGS,
    MODEL = '...',
    CREDENTIAL = ...
);

e successivamente:

AI_GENERATE_EMBEDDINGS(
    N'ArcGIS Server federation'
    USE MODEL MyEmbeddingModel
)

Microsoft supporta inoltre scenari in cui AI_GENERATE_CHUNKS e AI_GENERATE_EMBEDDINGS vengono combinati direttamente da T-SQL.

Questo rende possibile una pipeline:

SQL row
   ↓
AI_GENERATE_CHUNKS
   ↓
AI_GENERATE_EMBEDDINGS
   ↓
VECTOR
   ↓
DiskANN

Embedding nel database o nell'applicazione?

Il fatto che qualcosa sia possibile in SQL non significa che debba necessariamente essere eseguito lì.

Generare embedding nell'application layer offre generalmente maggiore controllo su:

  • retry;
  • rate limiting;
  • batching;
  • fallback;
  • observability;
  • model routing;
  • versioning della pipeline.

Generarli direttamente nel database può invece semplificare alcune pipeline data-centric.

È quindi una decisione architetturale, non semplicemente sintattica.

RAG: Retrieve Wide, Rank Narrow

DiskANN non deve necessariamente restituire direttamente i chunk che finiranno nel prompt.

Una pipeline più sofisticata può essere:

10.000.000 chunks
       │
       │ DiskANN
       ↓
      50
       │
       │ reranker
       ↓
       8
       │
       │ context builder
       ↓
      LLM

Questo pattern viene spesso sintetizzato come:

Retrieve wide
     ↓
Rank narrow

Reranking

La prima fase privilegia throughput e recall.

Una seconda fase può utilizzare un modello più costoso per stimare meglio la rilevanza rispetto alla domanda originale:

Question
   +
50 candidate chunks
        ↓
     Reranker
        ↓
   Best 5 - 10

DiskANN e reranker risolvono quindi problemi differenti:

DiskANN:
find plausible candidates quickly

Reranker:
order those candidates accurately

Hybrid Search

Gli embedding non sostituiscono necessariamente la ricerca lessicale.

Una query come:

KB5034123 ArcGIS Server TLS

contiene token precisi, codici e identificativi che possono essere estremamente significativi.

Una strategia moderna può combinare:

Vector similarity
       +
Keyword search
       +
Relational filters
       +
Reranking

La vector search è quindi un ulteriore retrieval signal, non necessariamente l'unico.

.NET ed Entity Framework Core

Per chi sviluppa applicazioni .NET è importante distinguere le versioni.

In EF Core 10 Microsoft ha introdotto il supporto alla traduzione di VectorDistance(), quindi alla distanza vettoriale esatta.

In EF Core 11 Microsoft documenta inoltre le API dedicate a vector index e VECTOR_SEARCH.

Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<DocumentChunk>()
        .HasVectorIndex(
            x => x.Embedding,
            "cosine");
}

La ricerca ANN può essere espressa concettualmente come:

var results = await db.DocumentChunks
    .VectorSearch(
        x => x.Embedding,
        queryEmbedding,
        "cosine")
    .OrderBy(x => x.Distance)
    .Take(10)
    .WithApproximate()
    .ToListAsync();

WithApproximate() corrisponde semanticamente alla richiesta:

TOP (...) WITH APPROXIMATE

È però importante notare che Microsoft documenta attualmente queste API di EF Core 11 come funzionalità ancora soggette a evoluzione.

Per chi oggi usa .NET 10

In un'applicazione .NET 10 / EF Core 10 possiamo comunque utilizzare pienamente Azure SQL Vector Search attraverso T-SQL, stored procedure, raw SQL o VectorDistance() dove appropriato.

Non è quindi necessario aspettare EF Core 11 per costruire una soluzione DiskANN.

.NET 10
   ↓
EF Core / ADO.NET
   ↓
VECTOR_SEARCH T-SQL
   ↓
Azure SQL
   ↓
DiskANN

Architettura RAG completa su Azure

                 ┌────────────────────┐
                 │ Azure Blob Storage │
                 └─────────┬──────────┘
                           │
                           ▼
                 ┌────────────────────┐
                 │ .NET Ingestion     │
                 │ Worker / Service   │
                 └─────────┬──────────┘
                           │
            ┌──────────────┴──────────────┐
            │                             │
            ▼                             ▼
     Text extraction          Document Intelligence
                                     OCR/Layout
            │                             │
            └──────────────┬──────────────┘
                           ▼
                    Semantic Chunking
                           │
                           ▼
                     Embedding Model
                           │
                           ▼
                   ┌────────────────┐
                   │   Azure SQL    │
                   │                │
                   │ relational     │
                   │ metadata       │
                   │ security       │
                   │ chunks         │
                   │ vectors        │
                   │ DiskANN        │
                   └───────┬────────┘
                           │
                           ▼
                       Retrieval
                           │
                           ▼
                        Reranker
                           │
                           ▼
                    Context Builder
                           │
                           ▼
                          LLM
                           │
                           ▼
                       Response

Vector database dedicato o Azure SQL?

Non esiste una risposta universale.

Un vector database specializzato può essere preferibile quando:

  • la vector search domina completamente il workload;
  • la scala è estremamente elevata;
  • servono feature vector-native molto specializzate;
  • l'architettura richiede distribuzione specificamente progettata per ANN.

Azure SQL è invece particolarmente interessante quando:

  • i dati applicativi sono già relazionali;
  • servono transazioni;
  • la sicurezza dipende dal modello relazionale;
  • i filtri sui metadati sono importanti;
  • vogliamo evitare sincronizzazione tra database;
  • vector search integra, ma non sostituisce, il workload applicativo.

La domanda corretta diventa quindi:

Which vector database should I add?

oppure:

Do I actually need another database?

Sono due domande architetturalmente molto diverse.

DiskANN + Spatial SQL: il collegamento con il GIS

Per chi lavora con GIS la parte più interessante inizia probabilmente qui.

Una feature geografica potrebbe avere contemporaneamente:

Feature
 ├── Id
 ├── Geometry
 ├── Attributes
 ├── Description
 ├── Category
 ├── Temporal information
 └── Embedding

La stessa entità appartiene quindi a tre spazi differenti.

Relational space
Geographic space
Semantic vector space

Tre tipi diversi di distanza

Consideriamo due aree territoriali.

La distanza geografica può essere:

180 km

ma la loro distanza nello spazio degli embedding può indicare una fortissima similarità:

Semantic similarity = very high

Due territori possono essere lontani fisicamente ma molto simili rispetto a:

  • demografia;
  • uso del suolo;
  • mobilità;
  • densità commerciale;
  • accessibilità;
  • servizi;
  • indicatori economici;
  • caratteristiche ambientali.

GeoAI: da distanza geometrica a similarità funzionale

Una possibile query concettuale potrebbe essere:

Find areas

within 50 km

AND

land_use = 'industrial'

AND

area > 50000 m²

AND

semantically similar to:

"areas suitable for logistics,
close to major transport infrastructure,
with low residential pressure"

Qui stiamo combinando:

Spatial predicate
       +
Relational predicate
       +
Vector similarity

Spatial Index e Vector Index sono concetti differenti

Uno spatial index indicizza oggetti come:

POINT
LINESTRING
POLYGON

Un vector index indicizza invece:

[0.018, -0.032, 0.004, ..., 0.071]

Il fatto che entrambi utilizzino la parola "vector" in alcuni contesti non deve creare confusione.

Sono strutture pensate per risolvere problemi completamente differenti.

Ma possono lavorare insieme.

Un possibile modello GeoAI

CREATE TABLE GeoAssets
(
    Id           BIGINT PRIMARY KEY,
    TenantId     INT NOT NULL,

    Name         NVARCHAR(250),
    Category     NVARCHAR(100),

    Geometry     GEOGRAPHY,

    Description  NVARCHAR(MAX),

    Embedding    VECTOR(1536)
);

Concettualmente potremmo eseguire:

Relational filtering
        ↓
Spatial filtering
        ↓
Vector retrieval
        ↓
Reranking
        ↓
GeoAI result

Embedding territoriali

Possiamo andare ancora oltre e generare un embedding non da un semplice testo, ma da una rappresentazione complessa del territorio.

Territory
   │
   ├── Population
   ├── Income
   ├── Land use
   ├── Mobility
   ├── POIs
   ├── Accessibility
   ├── Satellite features
   └── Demographics
             │
             ▼
          Encoder
             │
             ▼
       Territory embedding

A quel punto possiamo chiedere:

Find areas similar to this area

senza limitarci a una distanza euclidea o geografica.

Passiamo dalla prossimità geometrica alla similarità funzionale appresa.

Osservabilità

Una pipeline RAG production-grade va misurata end-to-end.

Almeno:

Document extraction latency
OCR latency
Chunking throughput
Embedding latency
Embedding failures
Indexing throughput
ANN latency
Exact KNN latency
Recall@K
Reranking latency
Context size
LLM latency
Token usage
End-to-end latency

Possiamo modellare la latenza totale come:

Ttotal =
    Textract
  + Tembed
  + Tretrieve
  + Trerank
  + Tcontext
  + Tllm

Ottimizzare DiskANN di 10 ms ha poco senso se la generazione dell'LLM richiede diversi secondi.

Valutare scientificamente il retrieval

Un buon sistema RAG non dovrebbe essere valutato soltanto chiedendo:

La risposta sembra buona?

Dobbiamo costruire un dataset di valutazione:

Question
Expected relevant chunks
Expected source documents

e misurare:

  • Recall@K;
  • Precision@K;
  • MRR;
  • NDCG;
  • latency;
  • cost per query.

Possiamo così confrontare in modo ripetibile:

Embedding Model A vs B

Chunk size 300 vs 800

Overlap 50 vs 150

Exact KNN vs DiskANN

Top-10 vs Top-50

With reranker vs without reranker

Vector-only vs Hybrid Search

DiskANN non sostituisce il database design

La presenza di embedding non rende improvvisamente irrilevanti:

  • primary key;
  • foreign key;
  • indici B-tree;
  • statistics;
  • query plans;
  • partitioning;
  • security;
  • transactions;
  • data lifecycle;
  • backup e disaster recovery.

La vector search è un'altra modalità di accesso al dato, non un sostituto del modello relazionale.

La vera convergenza

L'aspetto più interessante di Azure SQL + DiskANN non è poter eseguire una query con cosine distance.

È poter mantenere nello stesso dominio dati:

Transactions
Business entities
Users
Security
Documents
Metadata
Spatial objects
Embeddings

e interrogarli utilizzando:

Relational Search
Full-Text Search
Spatial Search
Vector Search

L'AI non deve necessariamente possedere una copia parallela del database aziendale.

Può diventare un'altra modalità con cui l'applicazione interroga gli stessi dati operativi.

Conclusione

DiskANN è interessante dal punto di vista algoritmico perché rende possibile una Approximate Nearest Neighbor Search efficiente su collezioni vettoriali di grandi dimensioni.

Ma il cambiamento più importante è probabilmente architetturale.

Azure SQL
   │
   ├── Relational data
   ├── Transactions
   ├── Security
   ├── Documents
   ├── Spatial data
   ├── Vectors
   └── DiskANN

In una moderna applicazione AI questo può ridurre la necessità di introdurre un'infrastruttura dati parallela esclusivamente per il retrieval vettoriale.

E nel GIS il concetto diventa ancora più interessante.

Relational space
      +
Geographic space
      +
Semantic vector space
      ↓
     GeoAI

La domanda GIS tradizionale:

What is near this location?

può evolvere verso:

What is geographically, semantically and functionally similar to this location?

È qui che DiskANN smette di essere semplicemente una tecnologia per "fare RAG sui PDF" e diventa qualcosa di più generale: un nuovo strumento per interrogare dati, conoscenza e territorio.

Riferimenti tecnici

lunedì 7 settembre 2026

NicoGIS Tech Weekly — 7 settembre 2026 - #1

Agentic AI, C# 15, Native AOT, Azure AI Search, ArcGIS Pro e DevExpress v26.2

La settimana tra il 31 agosto e il 6 settembre 2026 ha un filo conduttore abbastanza evidente: stiamo passando dalla fase in cui l'AI veniva integrata come semplice endpoint generativo alla costruzione di runtime agentici veri e propri, con strumenti, isolamento, retrieval governato, autorizzazioni e osservabilità.

Parallelamente anche gli stack tradizionali stanno cambiando. C# introduce costrutti che portano più informazioni nel type system, .NET cerca di rendere i test coerenti con il deployment Native AOT, Azure AI Search sta diventando sempre più un vero knowledge runtime per agenti, mentre Esri e DevExpress stanno rendendo più prevedibili e strutturati i rispettivi cicli evolutivi.


GPT-6 Astra: il modello diventa un runtime per task end-to-end

OpenAI ha annunciato GPT-6 Astra il 3 settembre 2026. Il modello viene distribuito tramite API come gpt-6-astra e supporta sia la Responses API sia Chat Completions. OpenAI lo presenta come un modello orientato a coding, ricerca, computer use e attività complesse multi-step.

La tariffazione Standard pubblicata da OpenAI è di 10 dollari per milione di token di input e 50 dollari per milione di token di output.

Analisi tecnica. Il dato più interessante non è semplicemente che Astra sia "più intelligente". La vera evoluzione è l'accorpamento nello stesso modello di reasoning, coding, browsing, computer interaction e utilizzo di strumenti.

Questo riduce potenzialmente la necessità di pipeline molto frammentate:

planner
   ↓
coding model
   ↓
browser model
   ↓
executor
   ↓
synthesizer

a favore di architetture più compatte:

orchestrator
   ↓
GPT-6 Astra
   ↓
tools / MCP / browser / computer / files

Non significa però che convenga utilizzare il modello più potente per qualsiasi richiesta. In un'architettura enterprise ha molto più senso introdurre model routing: task semplici, classificazione, extraction e RAG elementare possono essere indirizzati verso modelli meno costosi, riservando il modello frontier ai task realmente complessi.

OpenAI ha inoltre dichiarato che GPT-6 Astra è il primo proprio modello a raggiungere il livello Critical per le capacità cyber secondo il Preparedness Framework. Da un punto di vista architetturale questo rafforza un principio importante: un agente più capace deve avere meno privilegi impliciti, non più privilegi.

Tool authorization, approval gates, identity propagation, least privilege e auditing non possono quindi essere considerati semplici elementi UX: diventano parte dell'architettura applicativa.

Fonti ufficiali:
OpenAI — GPT-6 Astra: A new generation of intelligence
OpenAI — Release Notes
OpenAI — Safety overview: GPT-6 Astra


Gemini 3.8 Flash: il vero benchmark diventa capability/costo per task

Google ha presentato Gemini 3.8 Flash il 2 settembre 2026, insieme a Gemini 3.8 Flash Cyber. Google posiziona 3.8 Flash come modello destinato in particolare a software engineering, agentic task e reasoning multi-step.

Il prezzo introduttivo indicato da Google è di 0,75 dollari per milione di token di input e 3,75 dollari per milione di token di output.

Analisi tecnica. Con gli agenti il confronto basato esclusivamente sul costo per milione di token diventa sempre meno significativo.

TotalTaskCost =
    ModelInputTokens
  + ModelOutputTokens
  + ReasoningOverhead
  + Retrieval
  + ToolCalls
  + Retries
  + ExecutionTime

Un modello nominalmente economico che richiede molti cicli agentici può risultare più costoso di un modello più caro che conclude correttamente il task in pochi step.

Nei benchmark interni inizierei quindi a misurare almeno:

  • Task Success Rate
  • token totali consumati per task
  • numero medio di tool call
  • numero di retry
  • wall-clock latency
  • costo medio per task completato

Questo è particolarmente importante perché stiamo passando dal benchmark del modello al benchmark dell'intero agent runtime.

Google ha inoltre introdotto Gemini 3.8 Flash Cyber all'interno di un programma a disponibilità limitata per scenari avanzati di cyber defense, ulteriore segnale della progressiva specializzazione dei modelli e delle relative policy di accesso.

Fonte ufficiale:
Google — Introducing Gemini 3.8 Flash and 3.8 Flash Cyber


C# 15: Union Types e Closed Hierarchies cambiano il domain modeling

Microsoft ha reso disponibili in .NET 11 Preview 7 diverse funzionalità previste per C# 15, tra cui Union Types, Closed Hierarchies, una nuova preview del modello unsafe, collection-expression arguments, extension indexers e labeled break/continue.

Una delle novità più interessanti è la possibilità di modellare direttamente un insieme chiuso di tipi:

public record Success(string Value);
public record Failure(string Error);

public union Result(Success, Failure);

Il compilatore conosce i casi che compongono l'unione e può quindi aiutare nella verifica dell'exhaustiveness durante il pattern matching.

Analisi tecnica. Questo può eliminare una quantità non trascurabile di codice infrastrutturale che oggi implementiamo tramite:

object
marker interfaces
abstract base classes
custom Result<T>
manual discriminators
OneOf-like libraries

Un domain result può diventare concettualmente:

CreateOrderResult
    ├── OrderCreated
    ├── ValidationError
    └── CreditRejected

Il vantaggio non è soltanto sintattico. Una nuova variante può rendere non esaustivo uno switch esistente, spostando il controllo da una convenzione progettuale a un vincolo verificato dal type system.

Le Closed Hierarchies seguono la stessa direzione: rendere esplicito quali tipi possono appartenere a una gerarchia invece di affidarsi esclusivamente alla disciplina dello sviluppatore.

Questa evoluzione rende C# decisamente più interessante per domain modeling, state machine, workflow e API che devono rappresentare esplicitamente un insieme finito di risultati.

Fonti ufficiali:
Microsoft .NET Blog — Explore new features available in C# 15 preview
Microsoft .NET Blog — Explore union types in C# 15


MSTest 4.4 + Native AOT: finalmente si può testare il deployment model reale

Microsoft ha documentato con MSTest 4.4 la possibilità di pubblicare ed eseguire progetti di test come executable Native AOT.

Il punto tecnico è importante: Native AOT esegue compilazione ahead-of-time e trimming, per cui un'applicazione pubblicata può comportarsi diversamente dallo stesso codice eseguito normalmente sotto runtime managed.

Una configurazione di base è:

<Project Sdk="MSTest.Sdk/4.4.0">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <PublishAot>true</PublishAot>
  </PropertyGroup>
</Project>

e quindi:

dotnet publish MyProject.Tests.csproj -c Release -r win-x64

MSTest utilizza source generation per registrare preventivamente test class e modalità di invocazione, prima che il trimming elimini metadata apparentemente non necessari.

Analisi tecnica. Questo risolve una differenza reale tra:

code tested under JIT/runtime reflection

e:

actual trimmed + AOT compiled artifact

Il caso classico riguarda reflection, serializer, dependency injection dinamica, plugin discovery o altri pattern che dipendono da metadata disponibili a runtime.

Non sostituirei comunque i test managed tradizionali. Una pipeline più sensata è:

Pull Request
   └── Managed tests
       └── feedback rapido

Release validation
   ├── Managed tests
   ├── Native AOT tests
   └── End-to-end test dell'artifact pubblicato

In questo modo il costo del publish AOT non rallenta necessariamente ogni singolo ciclo di sviluppo, ma viene utilizzato dove serve realmente: nella validazione dell'artifact destinato alla produzione.

Fonte ufficiale:
Microsoft .NET Blog — Test what you ship: MSTest and Native AOT


Azure AI Search 2026-08-01-preview: da vector search a knowledge runtime

Microsoft ha introdotto la REST API preview 2026-08-01-preview per Azure AI Search. Tra le funzionalità documentate figurano supporto private network per indexed knowledge source, default configurabili nelle knowledge base, controllo del reranking per singola sorgente, streaming dei risultati di retrieve e citation URL per indexed knowledge source.

Per esempio, il controllo:

"resultsProcessing": "none"

permette di bypassare il reranking per una knowledge source e mantenere l'ordinamento prodotto dalla sorgente sottostante.

Analisi tecnica. L'evoluzione interessante è concettuale.

Il modello tradizionale era:

User Query
    ↓
Embedding
    ↓
Vector Search
    ↓
Top K Documents
    ↓
LLM

Il modello verso cui Azure AI Search si sta muovendo è più vicino a:

User Query
    ↓
Knowledge Base
    ↓
Query Planning
    ↓
Knowledge Source Selection
    ↓
Permissions / Filters
    ↓
Retrieval
    ↓
Per-source Processing
    ↓
Reranking
    ↓
Answer / Grounding / Citations

Questa è una differenza importante: Azure AI Search non viene più utilizzato esclusivamente come vector database o motore di semantic search, ma tende a diventare un retrieval orchestration layer.

Particolarmente rilevante in ambito enterprise è il supporto private network per knowledge source indicizzate basate, tra le altre, su Blob, SharePoint e Azure SQL.

Per sistemi RAG aziendali, caratteristiche come networking, permission filtering, citation provenance e governance possono essere molto più importanti di qualche punto percentuale guadagnato su un benchmark di retrieval.

Va comunque ricordato che queste funzionalità appartengono a una API preview. Microsoft avverte inoltre che l'utilizzo di servizi Microsoft o di terze parti collegati può comportare data processing o storage al di fuori dell'Azure compliance boundary.

Quindi: interessante tecnologicamente, ma da sottoporre a una vera data-flow e governance review prima di utilizzarlo su dati sensibili.

Fonti ufficiali:
Microsoft Learn — What's New in Azure AI Search
Microsoft Learn — Azure AI Search REST API Versions


ArcGIS Pro 3.8: STS/LTS cambia la strategia di upgrade enterprise

Esri ha annunciato un cambio significativo al lifecycle di ArcGIS Pro a partire da ArcGIS Pro 3.8.

Il nuovo modello distingue tra release Short-Term Support (STS) e Long-Term Support (LTS).

Secondo Esri, le release LTS saranno supportate per circa due anni, mentre le STS per circa un anno.

Analisi tecnica. Questa modifica è molto più importante per le installazioni enterprise di quanto possa sembrare.

In ambienti GIS complessi una release ArcGIS Pro non vive isolatamente. La matrice reale comprende spesso:

ArcGIS Pro
   ↕
ArcGIS Enterprise
   ↕
Enterprise Geodatabase
   ↕
DBMS / Client Drivers
   ↕
ArcGIS Pro SDK Add-ins
   ↕
Citrix / RDS
   ↕
Python environments
   ↕
Business extensions

Per organizzazioni che devono validare tutto questo stack, aggiornare ogni release può essere costoso e rischioso.

Il pattern più naturale diventa quindi:

Innovation / Early Adoption
          ↓
        STS

Enterprise Baseline
          ↓
        LTS

Questo consente di distinguere molto meglio una release destinata a provare rapidamente nuove capability da quella utilizzata come baseline aziendale stabile.

Per chi sviluppa ArcGIS Pro Add-in, una conseguenza positiva potrebbe essere una test matrix più razionale: LTS come baseline stabile e STS utilizzata per verificare anticipatamente compatibilità e nuove API.

Fonte ufficiale:
Esri — Update to ArcGIS Pro Product Life Cycle


DevExpress v26.2: Blazor e XAF entrano in una fase più enterprise

DevExpress ha pubblicato le roadmap di fine 2026 per Blazor v26.2 e XAF v26.2, entrambe previste per dicembre 2026.

Per Blazor sono previste, tra le altre cose, supporto .NET 11, innalzamento a .NET 10 della versione minima supportata con v26.2, supporto RTL in CTP, release ufficiale della nuova Icon Library, nuove fondamenta di styling basate su CSS variable e diverse evoluzioni del componente AI Chat.

La roadmap documenta in particolare una funzionalità interessante per gli agenti: tool approval visualization.

L'AI Chat potrà presentare all'utente una UI di conferma prima dell'esecuzione di una tool call, ad esempio prima di aggiornare un record CRM o inviare un'email.

Analisi tecnica. Questo è un dettaglio importante perché sposta lo Human-in-the-Loop da implementazione custom a pattern supportato direttamente dal componente UI.

LLM
 ↓
Tool Call Request
 ↓
DevExpress AI Chat
 ↓
Human Approval
 ↓
Execute / Reject

È esattamente il pattern che serve quando un sistema AI passa dalla sola generazione di testo all'esecuzione di azioni con effetti reali.

Sul fronte XAF, DevExpress continua inoltre a lavorare sulla scalabilità Blazor, dichiarando test specifici per scenari Crowd/Peak Load, Activity e Requests Per Second, oltre a ottimizzazioni su XAF Core, consumo di memoria e rendering Blazor.

Questo è probabilmente uno dei fronti più interessanti per chi utilizza XAF in applicazioni aziendali con numerosi utenti concorrenti.

Con XAF, infatti, il punto centrale non è soltanto aggiungere una chat AI. L'architettura realmente interessante è:

XAF Security System
        ↓
Application User Context
        ↓
AI / Agent Layer
        ↓
Tools / RAG / MCP
        ↓
Business Objects
        ↓
Database / Services

La sfida enterprise sarà fare in modo che retrieval e tool invocation rispettino lo stesso security model applicato all'interfaccia XAF: object-level security, member-level security, ruoli, tenant e autorizzazioni.

Una chat integrata è semplice. Un agente che non possa oltrepassare i privilegi dell'utente è invece una caratteristica architetturale.

La roadmap XAF v26.2 merita inoltre attenzione per la parte scalability: DevExpress dichiara esplicitamente l'intenzione di pubblicare condizioni di test, configurazioni, risultati, limitazioni e metriche relative a concorrenza e carico.

Fonti ufficiali:
DevExpress — Blazor Year-End 2026 Roadmap v26.2
DevExpress — XAF Year-End Roadmap v26.2


Technical Takeaways

Questa settimana evidenzia almeno tre tendenze strutturali.

1. Il modello non è più l'architettura AI

GPT-6 Astra e Gemini 3.8 continuano ad aumentare le capability del modello, ma un sistema enterprise ha bisogno di molti altri componenti:

Identity
Authorization
Model Routing
Tools
MCP
Retrieval
Knowledge Sources
Observability
Auditing
Human Approval
Cost Governance

Il modello deve possibilmente rimanere un componente sostituibile dell'architettura, non diventare l'architettura stessa.

2. Sempre più regole vengono trasformate in vincoli espliciti

C# porta insiemi chiusi di alternative dentro il type system. Azure AI Search porta knowledge source, processing e retrieval policy nella configurazione. Le UI AI iniziano a incorporare esplicitamente approval e controllo dell'esecuzione.

Ogni volta che una regola passa da:

"il developer deve ricordarsene"

a:

"la piattaforma può verificarla o applicarla"

l'architettura diventa più robusta.

3. Deployment e produzione stanno tornando al centro

MSTest Native AOT testa ciò che viene realmente pubblicato. ArcGIS introduce un lifecycle più facilmente gestibile in ambiente enterprise. DevExpress parla apertamente di concurrency, peak load, RPS e memory usage.

È un'evoluzione positiva: meno demo tecnologiche e più production engineering.


Lab della settimana

Lab 1 — Native AOT Test Pipeline

Prendere un progetto .NET reale che utilizza System.Text.Json, dependency injection e qualche componente basato su reflection.

Eseguire prima:

dotnet test

e successivamente una pipeline separata:

dotnet publish -c Release -r win-x64

con PublishAot=true, verificando che numero di test e comportamento coincidano con l'esecuzione managed.

Documentazione: MSTest and Native AOT

Lab 2 — Domain Modeling con C# 15 Union Types

Creare un risultato applicativo:

public record OrderCreated(int Id);
public record ValidationError(string Message);
public record Unauthorized();

public union CreateOrderResult(
    OrderCreated,
    ValidationError,
    Unauthorized);

e utilizzare pattern matching esaustivo per trasformarlo in una risposta HTTP o in un risultato applicativo.

Documentazione: C# 15 Union Types

Lab 3 — Azure AI Search come Retrieval Orchestrator

Creare una knowledge base utilizzando la REST API 2026-08-01-preview e confrontare il comportamento con una pipeline RAG tradizionale.

Misurare almeno:

  • latency end-to-end;
  • numero di query eseguite;
  • qualità del grounding;
  • precisione delle citazioni;
  • token consumati;
  • costo per query;
  • comportamento con permission filtering.

Documentazione: Azure AI Search — What's New


NicoGIS Tech Weekly è una selezione tecnica delle novità che possono avere un impatto concreto su sviluppo software, architetture enterprise, cloud, GIS e applicazioni AI. I fatti sono collegati alle fonti originali; le considerazioni indicate come analisi rappresentano una valutazione tecnica delle possibili implicazioni architetturali.