Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
La parte più difficile del lavorare sui grafi è costruire il grafo fin dall’inizio. La gestione manuale di entità e relazioni da migliaia di documenti è eccessivamente costosa. Funzioni di intelligenza artificiale in Azure HorizonDB risolve questo problema portando l'intelligenza basata su LLM direttamente in SQL, in modo da poter estrarre, strutturare ed eseguire query sui grafici delle informazioni senza uscire dal database.
azure_ai.extract() individua le relazioni nascoste e le entità dal testo non strutturato, direttamente all'interno di una query SQL. Inserisci contratti, ticket di assistenza, articoli di ricerca o qualsiasi dato ricco di testo e lo strumento estrae le relazioni strutturate necessarie per popolare il tuo grafo della conoscenza.
Questo articolo illustra passo dopo passo un esempio concreto, dall'inizio alla fine:
- Estrazione di entità dai ticket di incidente IT.
- Inserirli in un grafico Apache AGE.
- Esecuzione di query sul grafico per trovare catene di errori a catena.
Prerequisiti
Prima di eseguire questa esercitazione, è necessaria un'istanza di Azure HorizonDB, un modello di intelligenza artificiale configurato tramite il Registro di sistema dei modelli e le estensioni PostgreSQL necessarie.
- Azure HorizonDB con una regola del firewall che consente le connessioni dall'IP client. Configurare questa regola nel portale di Azure in Networking>Firewall rules o tramite l'interfaccia della riga di comando.
Abilitare le estensioni
Inserisci nell'elenco consentito le estensioni vector, azure_ai e age e aggiungi age a shared_preload_libraries tramite il portale di Azure o CLI, quindi esegui:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS azure_ai;
CREATE EXTENSION IF NOT EXISTS age;
SET search_path = ag_catalog, "$user", public;
Configurare i modelli di intelligenza artificiale
È necessario un modello per chat o generazione, ad esempio gpt-5.4, che l'estensione azure_ai può richiamare. È possibile procedere in due modi:
Opzione 1: Gestione modelli di intelligenza artificiale
Se gestione modelli di intelligenza artificiale (anteprima limitata) è abilitata nell'istanza di HorizonDB, il servizio effettua automaticamente il provisioning e registra i modelli nel registro dei modelli. Non è necessario gestire un endpoint o una chiave. Le funzioni di intelligenza artificiale usano i modelli gestiti per impostazione predefinita.
È possibile usare i dati di origine.
Opzione 2: Registrare manualmente un modello nel registro dei modelli
Se si preferisce usare i propri modelli di Microsoft Foundry (Bring Your Own Model), seguire questa procedura:
Distribuire un modello tramite Microsoft Foundry. Selezionare il modello da usare, ad esempio
gpt-5.4, e completare la distribuzione.Nel dashboard di Microsoft Foundry passare al progetto e prendere nota della chiave API e dell'URL dell'endpoint.
Passare alla distribuzione del modello e prendere nota dei valori seguenti:
-
Nome distribuzione: nome assegnato durante la distribuzione, ad esempio
gpt-5-deployment. -
Nome modello: nome del modello sottostante, ad esempio
gpt-5.4.
-
Nome distribuzione: nome assegnato durante la distribuzione, ad esempio
Registrare il modello nel registro dei modelli:
SELECT model_registry.model_add(
'my-gpt', -- a unique alias for your model
'https://my-endpoint.services.ai.azure.com/', -- your model endpoint URL
'gpt-5-deployment', -- deployment name
'gpt-5', -- model name
'2025-01-01-preview', -- API version (NULL for latest)
'subscription-key', -- auth type
'<your-endpoint-key>' -- endpoint key
);
Per informazioni dettagliate dettagliate sulla registrazione del modello e sui formati di URL dell'endpoint supportati, vedere Configurazione manuale con il Registro di sistema dei modelli.
Dati di origine
Creare la tabella di esempio e inserire alcuni ticket per gli eventi imprevisti da usare:
CREATE TABLE public.support_tickets (
ticket_id INT PRIMARY KEY,
severity TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
description TEXT NOT NULL
);
INSERT INTO support_tickets (ticket_id, severity, created_at, description)
VALUES
(4012, 'SEV1', '2025-03-03 14:22:00+00',
'The API gateway update on March 3rd caused the auth service to return 503 errors. The auth service failure cascaded into the payment service, which timed out all requests. The checkout workflow went down because it depends on the payment service. The Platform team rolled back the API gateway config to resolve the outage.'),
(4013, 'SEV2', '2025-03-05 09:15:00+00',
'The cache layer restart broke the invalidation hook in the event bus. The search service started returning stale results because it reads from the cache layer. The payment service also received stale fraud-check scores from the cache layer, causing intermittent transaction declines. The data pipeline team patched the event bus hook and the Infra team flushed the cache layer.'),
(4014, 'SEV1', '2025-03-07 03:41:00+00',
'A DNS resolution failure in the service mesh caused the payment service to lose connectivity to the fraud detection API. The checkout workflow went down again because it depends on the payment service. The Network team fixed the service mesh config to restore the payment service.'),
(4015, 'SEV3', '2025-03-08 11:30:00+00',
'The API gateway started rate-limiting the auth service token refresh endpoint after a config change. The auth service returned 401 errors to the mobile app. The Platform team raised the API gateway rate limit threshold to fix the auth service.'),
(4016, 'SEV2', '2025-03-10 16:05:00+00',
'The email provider API started throttling requests, causing the notification service to back up. The checkout workflow confirmation emails were delayed by 4 hours because the checkout workflow sends confirmations through the notification service. The messaging team added retry backoff to the notification service.');
Passaggio 1- Estrarre entità e relazioni
Usare azure_ai.extract() per estrarre triple di relazioni strutturate da ciascun documento. Il prompt di estrazione indica al modello di acquisire tutte le relazioni significative, inclusi i collegamenti operativi (OPERATES_ON), i collegamenti da documento a entità (INVOLVES) e i collegamenti causali o di risoluzione. Acquisendo tutto in anticipo come triple, il passaggio 3 non richiede alcun codice specifico per il dominio.
Passate l'ID del ticket come parte del testo di input in modo che il modello possa farvi riferimento come entità sorgente:
Tip
Adattamento della richiesta di estrazione: L'unica cosa che si cambia per un dominio diverso è il tipo di relazione di esempio nell'hint ARRAY:
-
Contratti:
BINDS, REFERENCES, AMENDS, GOVERNS -
Documenti di ricerca:
AUTHORED, CITES, EVALUATES, CONTRADICTS -
Sanità:
DIAGNOSED_WITH, PRESCRIBED, CONTRAINDICATED_BY -
Catena di approvvigionamento:
SUPPLIES, ASSEMBLED_IN, DEPENDS_ON
La struttura a tre colonne (relationship_sources, relationship_types, relationship_targets) rimane invariata indipendentemente dal dominio.
SELECT ticket_id,
azure_ai.extract(
format('Ticket %s: %s', ticket_id, description),
ARRAY[
'root_cause: string - the root cause of the incident',
'resolution: string - how the issue was resolved',
'relationship_sources: string - comma separated source entities (include the Ticket ID, team names, and service names as sources where appropriate), one per relationship',
'relationship_types: string - comma separated relationship types (e.g. CAUSED_FAILURE_IN, OPERATES_ON, INVOLVES, RESOLVED, PART_OF)',
'relationship_targets: string - comma separated target entities, one per relationship'
],
'my-gpt' -- model alias (omit if using AI Model Management)
) AS extracted
FROM support_tickets
WHERE ticket_id = 4012;
Questo passaggio restituisce json strutturato:
{
"root_cause": "API gateway update on March 3rd",
"resolution": "rolled back the gateway config",
"relationship_sources": "API gateway, auth service, payment service, Platform team, Platform team, Ticket 4012, Ticket 4012, Ticket 4012, Ticket 4012",
"relationship_types": "CAUSED_FAILURE_IN, CAUSED_FAILURE_IN, PART_OF, RESOLVED, OPERATES_ON, INVOLVES, INVOLVES, INVOLVES, INVOLVES",
"relationship_targets": "auth service, payment service, checkout workflow, API gateway, payment service, payment service, API gateway, auth service, checkout workflow"
}
Passaggio 2: Deduplicare le entità estratte
Quando si eseguono azure_ai.extract() in migliaia di ticket, la stessa entità viene visualizzata in forme di superficie diverse: "Gateway API", "api-gateway", "servizio gateway". Senza deduplicazione, il grafo si riempie di nodi quasi duplicati che frammentano i percorsi di attraversamento.
Usare azure_ai.generate() per normalizzare i nomi di entità in moduli canonici prima di inserirli nel grafico.
Tip
Quando ignorare questo passaggio: Se i dati di origine usano un vocabolario controllato (ad esempio, nomi di servizio di CMDB o SKU di prodotto da un catalogo), le entità sono già canoniche. Ignorare la deduplicazione e passare direttamente al passaggio 3.
-- Materialize azure_ai.extract() results for all tickets
CREATE TEMP TABLE extracted_tickets AS
SELECT ticket_id,
azure_ai.extract(
format('Ticket %s: %s', ticket_id, description),
ARRAY[
'root_cause: string - the root cause of the incident',
'resolution: string - how the issue was resolved',
'relationship_sources: string - comma separated source entities (include the Ticket ID, team names, and service names as sources where appropriate), one per relationship',
'relationship_types: string - comma separated relationship types (e.g. CAUSED_FAILURE_IN, OPERATES_ON, INVOLVES, RESOLVED, PART_OF)',
'relationship_targets: string - comma separated target entities, one per relationship'
],
'my-gpt' -- model alias (omit if using AI Model Management)
) AS data
FROM support_tickets;
-- Stage ALL extracted entity names into a temp table.
-- Split on comma, then trim whitespace from each element.
-- The LLM may return "A, B" or "A,B" inconsistently;
-- trim() handles both.
CREATE TEMP TABLE raw_entities AS
SELECT DISTINCT trim(entity_name) AS entity_name
FROM (
SELECT unnest(string_to_array(data->>'relationship_sources', ',')) AS entity_name
FROM extracted_tickets
UNION ALL
SELECT unnest(string_to_array(data->>'relationship_targets', ','))
FROM extracted_tickets
) sub
WHERE entity_name IS NOT NULL AND trim(entity_name) <> '';
Successivamente, usa azure_ai.generate() con l'output strutturato per generare nomi canonici e creare una tabella di ricerca che associ gli alias alle forme canoniche:
-- Build a lookup table mapping every alias to its canonical name.
-- azure_ai.generate() with json_schema returns reliable structured JSON.
CREATE TEMP TABLE entity_canonical AS
SELECT item->>'canonical' AS canonical, alias
FROM jsonb_array_elements(
(SELECT azure_ai.generate(
prompt => format(
'Given these entity names from incident reports, group names that refer to the same thing.
Treat partial names as aliases (e.g. "cache" and "cache layer" are the same,
"notification service queue" and "notification service" are the same).
Pick the most descriptive name as canonical.
Entities: %s',
(SELECT string_agg(DISTINCT entity_name, ', ') FROM raw_entities)
),
json_schema => '{
"name": "dedup_response",
"strict": true,
"schema": {
"type": "object",
"properties": {
"groups": {
"type": "array",
"items": {
"type": "object",
"properties": {
"canonical": { "type": "string" },
"aliases": { "type": "array", "items": { "type": "string" } }
},
"required": ["canonical", "aliases"],
"additionalProperties": false
}
}
},
"required": ["groups"],
"additionalProperties": false
}
}',
model => 'my-gpt' -- model alias (omit if using AI Model Management)
)->'groups')
) AS item,
jsonb_array_elements_text(item->'aliases') AS alias;
Visualizzare in anteprima il raggruppamento del modello:
SELECT * FROM entity_canonical ORDER BY canonical, alias;
Output di esempio:
| canonico | alias |
|---|---|
| API Gateway | Gateway API |
| API Gateway | api-gateway |
| API Gateway | il servizio gateway |
| Servizio di autenticazione | Servizio di autenticazione |
| Servizio di autenticazione | servizio di autenticazione |
| Servizio di pagamento | servizio di pagamento |
| Servizio di pagamento | svc per pagamenti |
-- Normalize relationships to use canonical names.
-- Uses FROM unnest() to zip the three arrays positionally:
-- source[1] pairs with type[1] pairs with target[1], etc.
CREATE TEMP TABLE normalized_rels AS
WITH raw_rels AS (
SELECT ticket_id, trim(source) AS source, trim(relationship) AS relationship, trim(target) AS target
FROM extracted_tickets,
LATERAL unnest(
string_to_array(data->>'relationship_sources', ','),
string_to_array(data->>'relationship_types', ','),
string_to_array(data->>'relationship_targets', ',')
) AS t(source, relationship, target)
)
SELECT
COALESCE(c1.canonical, r.source) AS source,
trim(r.relationship) AS relationship,
COALESCE(c2.canonical, r.target) AS target,
r.ticket_id
FROM raw_rels r
LEFT JOIN entity_canonical c1 ON lower(r.source) = lower(c1.alias)
LEFT JOIN entity_canonical c2 ON lower(r.target) = lower(c2.alias)
WHERE r.source IS NOT NULL AND trim(r.source) <> ''
AND r.target IS NOT NULL AND trim(r.target) <> ''
AND r.relationship IS NOT NULL AND trim(r.relationship) <> '';
Verificare che la deduplicazione funzioni. Se i nomi canonici vengono risolti correttamente, gli alias come "api-gateway" e "Gateway API" vengono visualizzati con lo stesso nome canonico:
-- Check: every source and target should be a canonical name (not an alias)
SELECT DISTINCT source FROM normalized_rels
UNION
SELECT DISTINCT target FROM normalized_rels
ORDER BY 1;
Confrontare questo elenco con raw_entities. Dovrebbero essere visualizzati meno nomi distinti (alias compressi). Se vengono ancora visualizzati alias grezzi, controllare entity_canonical se mancano delle mappature.
Passaggio 3: eseguire il flusso di entità deduplicate in un grafico AGE
Prendere l'output normalizzato del passaggio 2 e caricarlo in un grafico Apache AGE. La pipeline generica (3a + 3b) funziona per qualsiasi dominio perché opera interamente su normalized_rels, che ha uno schema universale: source, , relationshiptarget. Non è necessaria alcuna personalizzazione.
Importante
I DO blocchi nelle sezioni seguenti usano il agtype tipo , che risiede nello ag_catalog schema. Assicurarsi che il percorso di ricerca lo includa prima di eseguire il passaggio 3. Se la si imposta durante l'abilitazione delle estensioni, eseguirla di nuovo nel caso in cui la sessione sia stata reimpostata.
SET search_path = ag_catalog, "$user", public;
3a: Creare nodi di entità
Creare un nodo del grafo per ogni entità univoca presente in normalized_rels. Questo blocco è indipendente dal dominio: legge le source colonne e target senza conoscere il tipo di entità che rappresentano. Il MERGE comando crea il nodo solo se non esiste già, quindi questo blocco è sicuro da rieseguire.
SELECT ag_catalog.create_graph('incident_graph');
DO $$
DECLARE rec RECORD;
BEGIN
FOR rec IN
SELECT DISTINCT name FROM (
SELECT source AS name FROM normalized_rels
UNION
SELECT target AS name FROM normalized_rels
) all_names
WHERE name IS NOT NULL AND name <> ''
LOOP
EXECUTE format(
'SELECT * FROM ag_catalog.cypher(''incident_graph'', $q$ MERGE ({name: %s}) $q$) AS (v agtype)',
quote_literal(rec.name)
);
END LOOP;
END $$;
3b: Creare bordi delle relazioni
Inserire un bordo diretto per ogni relazione estratta. Questo blocco è anche indipendente dal dominio: qualsiasi tipo di relazione estratto dal modello LLM nel passaggio 1 (CAUSED_FAILURE_IN, PRESCRIBED, REFERENCES, SUPPLIES e così via) diventa automaticamente un'etichetta sui connettori. La funzione regexp_replace normalizza il tipo di relazione in un'etichetta Cypher valida (solo maiuscole e caratteri di sottolineatura).
DO $$
DECLARE rec RECORD;
BEGIN
FOR rec IN SELECT DISTINCT source, relationship, target FROM normalized_rels
WHERE source IS NOT NULL AND source <> ''
AND target IS NOT NULL AND target <> ''
AND relationship IS NOT NULL AND relationship <> ''
LOOP
EXECUTE format(
'SELECT * FROM ag_catalog.cypher(''incident_graph'', $q$
MATCH (a {name: %s})
MATCH (b {name: %s})
MERGE (a)-[:%s]->(b)
$q$) AS (v agtype)',
quote_literal(rec.source),
quote_literal(rec.target),
upper(regexp_replace(trim(rec.relationship), '[^a-zA-Z0-9_]', '_', 'g'))
);
END LOOP;
END $$;
A questo punto, il grafico è completo. I passaggi 3a e 3b sono tutti necessari per un grafico delle conoscenze funzionante da qualsiasi dominio. Verificare:
SELECT * FROM ag_catalog.cypher('incident_graph', $$
MATCH (a)-[r]->(b)
RETURN a.name, label(r), b.name
$$) AS (source agtype, edge_type agtype, target agtype);
Passaggio 4: Eseguire una query sul grafo
Con il grafo popolato, usa le traversal di Cypher per rispondere a domande operative a cui è difficile rispondere usando solo tabelle piatte. Ciascuna query riportata di seguito rappresenta una domanda reale che un ingegnere reperibile o un revisore degli incidenti farebbe.
Quali servizi downstream sono stati interrotti da questo errore?
Individua i guasti a cascata fino a tre hop di profondità. Il modello di percorso a lunghezza variabile *1..3 segue transitivamente i connettori CAUSED_FAILURE_IN, rivelando l'impatto che una vista di un singolo ticket non riesce a cogliere:
SELECT * FROM ag_catalog.cypher(
'incident_graph', $$
MATCH (root)-[:CAUSED_FAILURE_IN*1..3]->(affected)
RETURN root.name AS root_cause,
affected.name AS impacted_service
$$) AS (
root_cause agtype,
impacted_service agtype
);
"Quali servizi sono i singoli punti di errore più rischiosi?"
Contare il numero di altri servizi da cui dipendono o sono interessati da ogni nodo. I servizi con il numero massimo di connettori in ingresso sono gli hotspot di affidabilità:
SELECT * FROM ag_catalog.cypher(
'incident_graph', $$
MATCH (a)-[r]->(target)
RETURN target.name AS service,
count(*) AS incoming_edges
$$) AS (
service agtype,
incoming_edges agtype
)
ORDER BY incoming_edges DESC;
Tip
Le etichette dei connettori nel grafico dipendono da ciò che l'LLM ha estratto. Eseguire questa query per visualizzare tutti i tipi di arco disponibili e quindi modificare i modelli nel codice precedente:
SELECT * FROM ag_catalog.cypher('incident_graph', $$
MATCH ()-[r]->()
RETURN DISTINCT label(r) AS edge_type
$$) AS (edge_type agtype);
Passaggio 5: Visualizzare il grafico con Visual Studio Code
L'estensione PostgreSQL per Visual Studio Code consente di eseguire query apache AGE cypher ed esplorare i risultati come grafico interattivo dei bordi dei nodi. L'estensione rileva automaticamente i risultati della query del grafo e li visualizza in un esploratore visivo con callout su ogni nodo, controlli di zoom e pan, supporto per l'esportazione e stili che si adattano al tema. Per ulteriori informazioni sulla funzionalità di visualizzazione nell'estensione, vedi Che cos'è l'estensione PostgreSQL per Visual Studio Code?
Questa interrogazione trova tutti i nodi raggiungibili tramite catene di CAUSED_FAILURE_IN ed espande il vicinato attorno a ciascun nodo:
SELECT * FROM ag_catalog.cypher('incident_graph', $$
MATCH (upstream)-[r:CAUSED_FAILURE_IN*1..3]->(target)
WITH upstream, target
MATCH (a)-[r2]->(b)
WHERE a.name = upstream.name OR a.name = target.name
OR b.name = upstream.name OR b.name = target.name
SET a.disp_label = a.name
SET b.disp_label = b.name
RETURN DISTINCT a, r2, b
$$) AS (a agtype, r agtype, b agtype);
Ridimensionare il modello
Estendi questo schema su migliaia di ticket e otterrai un grafo della conoscenza degli incidenti che un agente di intelligenza artificiale può interrogare per rispondere a domande come:
"Quali servizi upstream causano più comunemente errori che raggiungono il gateway API?"
"Mostra ogni catena di errori a catena che ha toccato il servizio di pagamento negli ultimi 90 giorni".
"Quale team risolve il maggior numero di incidenti relativi a più servizi?"
Considerazioni sulla produzione
Questo tutorial esegue l'estrazione, la deduplicazione e il caricamento del grafo tramite istruzioni SQL interattive. Per i carichi di lavoro di produzione:
- Elaborazione Azure Batch. Racchiudi i passaggi da 1 a 3 in una funzione PL/pgSQL oppure usa
pg_cronper eseguire l'estrazione in modo pianificato, man mano che arrivano nuovi ticket. - Aggiornamenti incrementali. Tracciare una filigrana
last_processed_idinvece di estrarre nuovamente l'intera tabella. UsareMERGE(come illustrato nel passaggio 3) per gli aggiornamenti idempotenti del grafo. - Gestione degli errori. Le chiamate LLM possono avere esito negativo o restituire codice JSON in formato non valido. Racchiudere
azure_ai.extract()eazure_ai.generate()in blocchiBEGIN ... EXCEPTIONe registrare gli errori in una tabella di messaggi non recapitabili per ritentarli.