Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Der schwierigste Teil der Arbeit mit Diagrammen ist das Erstellen des Diagramms an erster Stelle. Das manuelle Zusammenstellen von Entitäten und Beziehungen aus Tausenden von Dokumenten ist unertragbar teuer. KI-Funktionen in Azure HorizonDB lösen dieses Problem, indem LLM-basierte Intelligenz direkt in SQL integriert wird, sodass Sie Wissensdiagramme extrahieren, strukturieren und abfragen können, ohne die Datenbank verlassen zu müssen.
azure_ai.extract() ermittelt ausgeblendete Beziehungen und Entitäten aus unstrukturiertem Text direkt in einer SQL-Abfrage. Füttern Sie es mit Verträgen, Support-Tickets, Forschungsarbeiten oder beliebigen anderen textlastigen Daten, und es extrahiert die strukturierten Beziehungen, die Sie zum Füllen Ihres Wissensgraphen benötigen.
In diesem Artikel wird ein konkretes End-to-End-Beispiel erläutert:
- Extraktion von Entitäten aus IT-Vorfall-Tickets.
- Sie in einen Apache AGE-Graphen überführen.
- Abfragen des Diagramms, um kaskadierende Fehlerketten zu finden.
Voraussetzungen
Bevor Sie dieses Lernprogramm ausführen, benötigen Sie eine Azure HorizonDB-Instanz, ein KI-Modell, das über die Modellregistrierung konfiguriert ist, und die erforderlichen PostgreSQL-Erweiterungen.
- Azure HorizonDB mit einer Firewallregel, die Verbindungen von Ihrer Client-IP zulässt. Konfigurieren Sie diese Regel im Azure-Portal unter Networking>Firewall-Regeln oder über CLI.
Aktivieren von Erweiterungen
Setzen Sie die Erweiterungen vector, azure_ai und age auf die Zulassungsliste, und fügen Sie age über das Azure-Portal oder die CLI zu shared_preload_libraries hinzu. Führen Sie dann Folgendes aus:
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;
Konfigurieren von KI-Modellen
Sie benötigen ein Chat- oder Generierungsmodell, wie z. B. gpt-5.4, das die Erweiterung azure_ai aufrufen kann. Sie haben zwei Möglichkeiten:
Option 1: KI-Modellmanagement
Wenn ai Model Management (eingeschränkte Vorschau) in Ihrer HorizonDB-Instanz aktiviert ist, stellt der Dienst automatisch Modelle in der Modellregistrierung bereit und registriert sie. Sie müssen keinen Endpunkt oder Schlüssel verwalten. KI-Funktionen verwenden standardmäßig die verwalteten Modelle.
Sie können die Quelldaten verwenden.
Option 2: Manuelles Registrieren eines Modells in der Modellregistrierung
Wenn Sie lieber eigene Microsoft Foundry-Modelle (Bring Your Own Model) verwenden möchten, führen Sie die folgenden Schritte aus:
Stellen Sie ein Modell über Microsoft Foundry bereit. Wählen Sie das Modell aus, das Sie verwenden möchten, wie z. B.
gpt-5.4, und schließen Sie die Bereitstellung ab.Navigieren Sie im Microsoft Foundry-Dashboard zu Ihrem Projekt, und notieren Sie sich den API-Schlüssel und die Endpunkt-URL.
Navigieren Sie zu Ihrer Modellbereitstellung, und notieren Sie sich die folgenden Werte:
-
Bereitstellungsname: Der Name, den Sie während der Bereitstellung zugewiesen haben, z. B.
gpt-5-deployment. -
Modellname: Der zugrunde liegende Modellname, z. B.
gpt-5.4.
-
Bereitstellungsname: Der Name, den Sie während der Bereitstellung zugewiesen haben, z. B.
Registrieren Sie das Modell in der Modellregistrierung:
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
);
Ausführliche Informationen zur Modellregistrierung und unterstützten Endpunkt-URL-Formaten finden Sie unter Manuelles Setup mit Modellregistrierung.
Die Quelldaten
Erstellen Sie die Beispieltabelle, und fügen Sie einige Incident-Tickets ein, mit denen Sie arbeiten können:
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.');
Schritt 1 : Extrahieren von Entitäten und Beziehungen
Verwenden Sie azure_ai.extract(), um strukturierte Beziehungs-Tripel aus jedem Dokument zu extrahieren. Die Extraktionsaufforderung weist das Modell an, alle sinnvollen Beziehungen zu erfassen, einschließlich operativer Verknüpfungen (OPERATES_ON), Verknüpfungen zwischen Dokumenten und Entitäten (INVOLVES) und Kausal- oder Auflösungsverknüpfungen. Durch das Erfassen aller Elemente als Dreifache im Voraus erfordert Schritt 3 keinen domänenspezifischen Code.
Übergeben Sie die Ticket-ID als Teil des Eingabetexts, damit das Modell als Quellentität darauf verweisen kann:
Tip
Anpassen der Extraktionsaufforderung: Das einzige, was Sie für eine andere Domäne ändern, sind die Beispielbeziehungstypen im ARRAY-Hinweis:
-
Verträge:
BINDS, REFERENCES, AMENDS, GOVERNS -
Wissenschaftliche Arbeiten:
AUTHORED, CITES, EVALUATES, CONTRADICTS -
Gesundheitswesen:
DIAGNOSED_WITH, PRESCRIBED, CONTRAINDICATED_BY -
Materialbeschaffung:
SUPPLIES, ASSEMBLED_IN, DEPENDS_ON
Die dreispaltige Struktur (relationship_sources, relationship_types, relationship_targets) bleibt unabhängig von der Domäne gleich.
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;
Dieser Schritt gibt strukturierten JSON-Code zurück:
{
"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"
}
Schritt 2: Extrahierte Entitäten deduplizieren
Wenn Sie azure_ai.extract() auf Tausende von Tickets anwenden, tritt dieselbe Entität in unterschiedlichen Oberflächenformen auf: „API-Gateway“, „api-gateway“, „der Gateway-Dienst“. Ohne Deduplizierung füllt sich Ihr Graph mit Beinahe-Duplikaten von Knoten, die Ihre Traversierungen fragmentieren.
Verwenden Sie azure_ai.generate(), um Entitätsnamen in kanonische Form zu normalisieren, bevor sie in den Graphen eingefügt werden.
Tip
Gehen Sie wie folgt vor, um diesen Schritt zu überspringen: Wenn Ihre Quelldaten kontrolliertes Vokabular verwenden (z. B. Dienstnamen aus einer CMDB oder Produkt-SKUs aus einem Katalog), sind Entitäten bereits kanonisch. Überspringen Sie die Deduplizierung, und wechseln Sie direkt zu Schritt 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) <> '';
Verwenden Sie anschließend azure_ai.generate() mit strukturierter Ausgabe, um kanonische Namen zu erzeugen und eine Nachschlagetabelle zu erstellen, die Aliasse kanonischen Formen zuordnet:
-- 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;
Sehen Sie sich in der Vorschau an, was das Modell gruppiert hat:
SELECT * FROM entity_canonical ORDER BY canonical, alias;
Beispielausgabe:
| kanonisch | alias |
|---|---|
| API-Gateway | API-Gateway |
| API-Gateway | API-Gateway |
| API-Gateway | Der Gatewaydienst |
| Authentifizierungsdienst | Authentifizierungsdienst |
| Authentifizierungsdienst | Authentifizierungsdienst |
| Zahlungsdienst | Zahlungsservice |
| Zahlungsdienst | Zahlungsdienst |
-- 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) <> '';
Überprüfen Sie, ob die Deduplizierung funktioniert hat. Wenn kanonische Namen korrekt aufgelöst werden, werden Aliase wie „api-gateway“ und „API Gateway“ unter demselben kanonischen Namen angezeigt:
-- 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;
Vergleichen Sie diese Liste mit raw_entities. Es sollten weniger unterschiedliche Namen angezeigt werden (Aliase reduziert). Wenn weiterhin rohe Aliasse angezeigt werden, überprüfen Sie entity_canonical auf fehlende Zuordnungen.
Schritt 3: Fluss deduplizierter Entitäten in ein AGE Graph
Nehmen Sie die normalisierte Ausgabe von Schritt 2, und laden Sie sie in ein Apache AGE-Diagramm. Die generische Pipeline (3a + 3b) funktioniert für jede Domäne, da sie vollständig auf normalized_rels basiert, das ein universelles Schema aufweist: source, relationship, target. Es ist keine Anpassung erforderlich.
Important
Die DO Blöcke in den folgenden Abschnitten verwenden den Typ agtype, der sich im Schema ag_catalog befindet. Stellen Sie sicher, dass Ihr Suchpfad sie enthält, bevor Sie Schritt 3 ausführen. Wenn Sie sie während der Aktivierung von Erweiterungen festlegen, führen Sie sie erneut aus, wenn Die Sitzung zurückgesetzt wurde.
SET search_path = ag_catalog, "$user", public;
3a: Erstellen von Entitätsknoten
Erstellen Sie für jede eindeutige Entität, die in normalized_rels gefunden wurde, einen Graphknoten. Dieser Block ist domänenunabhängig: Er liest die source und target spalten, ohne zu wissen, welche Art von Entitäten sie darstellen. Der MERGE Befehl erstellt den Knoten nur, wenn er noch nicht vorhanden ist, sodass dieser Block sicher erneut ausgeführt werden kann.
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: Erstellen von Beziehungsrändern
Fügen Sie eine gerichtete Kante pro extrahierter Beziehung ein. Dieser Block ist auch domänenunabhängig: Welche Beziehungstypen das LLM in Schritt 1 auch extrahiert hat (CAUSED_FAILURE_IN, PRESCRIBED, REFERENCES, SUPPLIES usw.), sie werden automatisch als Randbeschriftungen verwendet. Die regexp_replace-Funktion bereinigt den Beziehungstyp zu einem gültigen Cypher-Label (nur Großbuchstaben und Unterstriche).
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 $$;
An diesem Punkt ist Ihr Diagramm vollständig. Die Schritte 3a und 3b sind alles, was Sie für ein funktionierendes Wissensdiagramm aus einer beliebigen Domäne benötigen. Überprüfen:
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);
Schritt 4 – Abfragen des Diagramms
Wenn das Diagramm ausgefüllt ist, verwenden Sie Cypher-Traversale, um operative Fragen zu beantworten, die allein mit flachen Tabellen schwierig sind. Jede folgende Abfrage stellt eine echte Frage dar, die ein On-Call-Techniker oder Vorfallprüfer stellen würde.
"Welche nachgeschalteten Dienste hat dieser Fehler abgebrochen?"
Verfolgen Sie kaskadierende Fehler über bis zu drei Hops hinweg. Das Pfadmuster mit variabler Länge *1..3 folgt transitiv den CAUSED_FAILURE_IN-Rändern und macht die Auswirkungen sichtbar, die in einer Einzelticketansicht verborgen bleiben:
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
);
"Welche Dienste sind die riskantesten Einzelpunkte eines Ausfalls?"
Zählen Sie, wie viele andere Dienste von den einzelnen Knoten abhängig sind oder davon betroffen sind. Dienste mit der höchsten Eingehenden Edgeanzahl sind Ihre Zuverlässigkeits-Hotspots:
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
Die Randbeschriftungen in Ihrem Diagramm hängen davon ab, was die LLM extrahiert hat. Führen Sie diese Abfrage aus, um alle verfügbaren Edgetypen anzuzeigen, und passen Sie dann die Muster im vorherigen Code an:
SELECT * FROM ag_catalog.cypher('incident_graph', $$
MATCH ()-[r]->()
RETURN DISTINCT label(r) AS edge_type
$$) AS (edge_type agtype);
Schritt 5 – Visualisieren des Diagramms mit Visual Studio Code
Mit der PostgreSQL-Erweiterung für Visual Studio Code können Sie Apache AGE Cypher-Abfragen ausführen und die Ergebnisse als interaktives Knotenranddiagramm untersuchen. Die Erweiterung erkennt automatisch die Ergebnisse von Graphabfragen und stellt sie in einem visuellen Explorer mit Beschriftungen pro Knoten, Zoom- und Schwenkfunktionen, Exportfunktion und themenabhängiger Formatierung dar. Weitere Informationen zur Visualisierungsfunktionalität in der Erweiterung finden Sie unter What is the PostgreSQL extension for Visual Studio Code?
Diese Abfrage findet alle Knoten, die über CAUSED_FAILURE_IN-Ketten erreichbar sind, und erweitert die Nachbarschaft jedes Knotens:
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);
Skalieren des Musters
Wenden Sie dieses Muster auf Tausende von Tickets an, erhalten Sie einen Wissensgraphen für Vorfälle, den ein KI-Agent abfragen kann, um Fragen wie die folgenden zu beantworten:
"Welche Upstreamdienste verursachen am häufigsten Fehler, die das API-Gateway erreichen?"
"Zeigen Sie mir jede kaskadierende Fehlerkette an, die den Zahlungsdienst in den letzten 90 Tagen berührt hat."
"Welches Team löst die dienstübergreifenden Vorfälle auf?"
Produktionsüberlegungen
Dieses Tutorial führt Extraktion, Deduplizierung und das Laden des Graphen in Form interaktiver SQL-Anweisungen aus. Für Produktionsworkloads:
- Azure Batch-Verarbeitung. Fassen Sie die Schritte 1–3 in einer PL/pgSQL-Funktion zusammen oder verwenden Sie
pg_cron, um die Extraktion nach einem Zeitplan auszuführen, sobald neue Tickets eingehen. - Inkrementelle Aktualisierungen. Verfolgen Sie ein
last_processed_idWasserzeichen, anstatt die vollständige Tabelle erneut zu extrahieren. Verwenden SieMERGE(wie in Schritt 3 dargestellt) für idempotente Diagrammaktualisierungen. - Fehlerbehandlung. LLM-Aufrufe können fehlschlagen oder falsch formatierten JSON-Code zurückgeben. Verpacken Sie
azure_ai.extract()undazure_ai.generate()inBEGIN ... EXCEPTION-Blöcke und protokollieren Sie Fehler für einen erneuten Versuch in einer Dead-Letter-Tabelle.