ai newsMi., 21. Okt.
EilmeldungOpenAI's GPT-5.6 Sol and Google's Gemini 4 Argon found critical PHP flaw affecting millions of websites

Observability of Multi-Agent AI Systems with OpenTelemetry, Microsoft Agent Framework, and Azure Monitor

This blog post is part of the Agentic Azure Insights community initiative created by Nicholas Chang . Take a look at other sessions that are part of the event. Building, tracing, monitoring, and troubleshooting intelligent multi-agent applications with Python, Microsoft Foundry, and Azure. Introduc

dev.toMittwoch 20:40 · 21.10.19 Min. Lesezeit
Bild: dev.to

Dieser Blogbeitrag ist Teil der Community-Initiative Agentic Azure Insights von Nicholas Chang. Werfen Sie einen Blick auf andere Sitzungen, die Teil der Veranstaltung sind.

Intelligente Multi-Agent-Anwendungen mit Python, Microsoft Foundry und Azure erstellen, nachverfolgen, überwachen und Fehler beheben.

Einen KI-Agenten zu bauen, der auf einen Prompt reagiert, ist vergleichsweise unkompliziert. Eine produktionsreife Multi-Agent-Anwendung zu entwickeln ist eine völlig andere Herausforderung.

Da sich KI-Systeme von einzelnen Agenten zu kollaborativen Architekturen weiterentwickeln, wird es zunehmend schwierig, ihr Verhalten zu verstehen.

Stellen Sie sich eine Anwendung vor, in der mehrere spezialisierte KI-Agenten zusammenarbeiten, um eine Kundenserviceanfrage zu bearbeiten. Ein Agent ruft Kundeninformationen ab, ein weiterer bewertet Geschäftsrichtlinien und ein dritter erstellt die endgültige Lösung.

Was passiert, wenn die Anwendung langsam wird? Wie können wir feststellen, welcher Agent die Verzögerung verursacht hat? Was ist, wenn alle Agenten erfolgreich ausgeführt werden, aber die endgültige Entscheidung falsch ist?

Traditionelle Anwendungslogs liefern einige Antworten, reichen jedoch häufig nicht aus, um komplexe, verteilte KI-Workflows nachzuvollziehen.

An dieser Stelle erweisen sich OpenTelemetry, Microsoft Agent Framework und Azure Monitor als wertvoll.

In diesem Tutorial bauen wir eine beobachtbare Multi-Agent-Anwendung, die zeigt, wie man:

Das Wichtigste dabei: Wir untersuchen tatsächliche Szenarien, in denen etwas schiefgeht.

Im Tutorial verwendete Technologien: Python, FastAPI, Microsoft Agent Framework, Microsoft Foundry, OpenTelemetry, Azure Monitor, Application Insights, KQL, Azure Container Apps, Bicep und Azure Developer CLI.

Observability ist die Fähigkeit, das interne Verhalten eines Systems zu verstehen, indem man die von ihm erzeugten Signale analysiert.

In traditionellen verteilten Anwendungen stützt sich Observability auf drei grundlegende Säulen:

Für agentische KI-Anwendungen werden diese Fähigkeiten noch wichtiger.

Ein Multi-Agent-Workflow kann mehrere Modellaufrufe, externe APIs, Datenbankabfragen und Entscheidungen umfassen, die auf Informationen basieren, die von anderen Agenten erzeugt wurden.

Eine einzelne Benutzeranfrage kann Dutzende von Operationen über verschiedene Komponenten hinweg auslösen.

Ohne verteiltes Tracing wird es schwierig, den Ausführungspfad nachzuvollziehen.

OpenTelemetry bietet ein herstellerneutrales Framework zum Erzeugen, Sammeln und Exportieren von Telemetrie.

Sein Tracing-Modell ist besonders nützlich für Multi-Agent-Systeme.

Ein Trace repräsentiert eine End-to-End-Operation, während ein Span eine einzelne Arbeitseinheit innerhalb dieser Operation darstellt.

Betrachten Sie zum Beispiel die folgende Ausführung:

Jeder Span enthält Informationen wie den Namen seiner Operation, die Ausführungszeit, den Status, Attribute und die Beziehung zu anderen Spans.

Alle Spans innerhalb derselben verteilten Operation teilen sich eine Trace-ID.

Auf diese Weise können wir nachvollziehen, wie die Anwendung ein bestimmtes Ergebnis erzeugt hat.

Bauen wir eine kleine Multi-Agent-Anwendung, die Kundenanfragen zu Rückerstattungen untersucht.

Die Architektur umfasst einen Koordinator und drei spezialisierte Agenten.

CustomerAgent analysiert Kundeninformationen, einschließlich Konto-Stufe und Kaufhistorie.

PolicyAgent prüft die Belege zur Rückerstattungsrichtlinie und bestimmt, ob die verfügbaren Informationen eine Rückerstattung unterstützen.

ResolutionAgent kombiniert die Ergebnisse und erstellt eine empfohlene Lösung.

Der Koordinator steuert die Ausführungsreihenfolge, ruft Tools auf und stellt die Belege bereit, die jeder Agent benötigt.

Unsere Anwendung enthält außerdem zwei simulierte externe Dienste: eine Kundeninformations-API und eine Rückerstattungsrichtlinien-Nachschlage-API.

Die Anwendung unterstützt zwei Ausführungsmodi.

Im Mock-Modus sind die Agentenantworten deterministisch, sodass sich Observability demonstrieren lässt, ohne ein bereitgestelltes KI-Modell zu benötigen.

Im Foundry-Modus erstellt die Anwendung echte Microsoft Agent Framework Agenten, die mit dem Microsoft Foundry Projekt und der Modellbereitstellung verbunden sind, die im Rahmen der Azure-Bereitstellung angelegt werden. In der Standardvorlage verweist das Projekt auf eine bereitgestellte gpt-4o-mini-Bereitstellung, damit die App mit echter Modellinferenz laufen kann, ohne dass eine separate manuelle Einrichtung erforderlich ist.

Dieser Unterschied ist wichtig, weil der Mock-Modus die Instrumentierung des Workflows demonstriert, während der Foundry-Modus die echte Modellausführung und die zugehörige Telemetrie hinzufügt, sofern das installierte SDK dies unterstützt.

fastapi uvicorn opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-fastapi azure-monitor-opentelemetry azure-identity agent-framework-core agent-framework-foundry python-dotenv jinja2 Enter fullscreen mode Exit fullscreen mode Wir können die Umgebung erstellen und die Abhängigkeiten installieren:

python3 -m venv.venv source.venv/bin/activate pip install -r requirements.txt cp.env.example.env Enter fullscreen mode Exit fullscreen mode Zunächst verwenden wir die folgende Konfiguration:

MODEL_MODE = mock APPLICATIONINSIGHTS_CONNECTION_STRING = OTEL_SERVICE_NAME = multi-agent-investigator OTEL_CONSOLE = true ENABLE_SENSITIVE_DATA = false Enter fullscreen mode Exit fullscreen mode Dies ermöglicht der Anwendung den lokalen Betrieb ohne jegliche Azure-Ressourcen.

Beginnen wir mit der wichtigsten technischen Komponente: der Konfiguration des OpenTelemetry SDK.

In app/telemetry.py unterstützt unsere Implementierung sowohl lokales Console-Tracing als auch Azure Monitor.

Zuerst importieren wir die notwendigen Komponenten:

from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ( BatchSpanProcessor, ConsoleSpanExporter ) Enter fullscreen mode Exit fullscreen mode Als Nächstes konfigurieren wir einen Tracing-Provider für die lokale Ausführung:

provider = TracerProvider ( resource = Resource. create ({ " service.name ": " multi-agent-investigator " }) ) provider. add_span_processor ( BatchSpanProcessor ( ConsoleSpanExporter ()) ) trace. set_tracer_provider ( provider ) Enter fullscreen mode Exit fullscreen mode Der TracerProvider verwaltet die Tracing-Konfiguration, während der BatchSpanProcessor die Spans verarbeitet und sie an den konfigurierten Exporter sendet.

Der ConsoleSpanExporter gibt die erzeugten Spans im Terminal aus.

Das ist besonders nützlich während der lokalen Entwicklung und bei Live-Demonstrationen.

Für Azure Monitor verwendet das Projekt einen anderen Konfigurationsweg:

from azure.monitor.opentelemetry import configure_azure_monitor configure_azure_monitor ( connection_string = connection, resource = Resource. create ({ " service.name ": service }), sampling_ratio = 1.0, logger_name = " agent_demo ", enable_live_metrics = True ) Enter fullscreen mode Exit fullscreen mode Die Anwendung wählt diese Konfiguration, wenn APPLICATIONINSIGHTS_CONNECTION_STRING vorhanden ist.

Sie kann zudem den Console-Exporter an denselben Tracing-Provider anhängen, sodass wir die Telemetrie lokal beobachten können, während sie gleichzeitig an Azure exportiert wird.

Wir konfigurieren bewusst nur einen einzigen globalen Tracing-Provider.

Nun folgt eine weitere wichtige Integration:

from agent_framework.observability import enable_instrumentation enable_instrumentation ( enable_sensitive_data = False ) Enter fullscreen mode Exit fullscreen mode Microsoft Agent Framework kann OpenTelemetry-Signale für instrumentierte Agent-Operationen erzeugen.

Dies ergänzt die benutzerdefinierten Spans, die wir für unseren Coordinator, den Workflow und die simulierten externen Tools erstellen.

Die Einstellung enable_sensitive_data=False ist besonders wichtig, da Agent-Eingaben und -Ausgaben vertrauliche oder personenbezogene Informationen enthalten können.

In Produktionsumgebungen sollte die Erfassung vollständiger Prompts, Kundendaten und Modellantworten niemals ohne geeignete Datenschutzkontrollen aktiviert werden.

In app/agents.py definieren wir die Anweisungen für jeden Spezialisten:

AGENT_INSTRUCTIONS = { " CustomerAgent ": ( " You are a customer record specialist. " " Summarize the provided account facts " " in one sentence. Do not invent additional facts. " ), " PolicyAgent ": ( " You review refund policy evidence. " " Report whether the supplied policy " " evidence allows refund. " ), " ResolutionAgent ": ( " Provide a concise support resolution " " based ONLY on the supplied customer " " and policy evidence. " ) } Enter fullscreen mode Exit fullscreen mode Wenn der Foundry-Modus aktiviert ist, initialisiert die Anwendung einen Foundry-Client:

from agent_framework import Agent from agent_framework.foundry import FoundryChatClient from azure.identity import DefaultAzureCredential client = FoundryChatClient ( project_endpoint = endpoint, model = model, credential = DefaultAzureCredential () ) Enter fullscreen mode Exit fullscreen mode Anschließend erstellen wir die spezialisierten Agenten:

agents = { name: Agent ( client = client, name = name, instructions = instructions ) for name, instructions in AGENT_INSTRUCTIONS. items () } Enter fullscreen mode Exit fullscreen mode Jeder Agent ist für eine bestimmte Aufgabe verantwortlich.

Der Coordinator liefert die Beweise und ruft die Agenten nacheinander auf.

result = await agents [ " PolicyAgent " ]. run ( policy_evidence ) Enter fullscreen mode Exit fullscreen mode Man beachte, dass unsere Orchestrierung nicht erfordert, dass die Agenten direkt miteinander kommunizieren.

Der Python-Coordinator steuert die Ausführung und den Informationsaustausch explizit.

Das macht den Workflow leichter verständlich und ermöglicht es uns, um jede Operation eigene Tracing-Grenzen zu legen.

In app/workflow.py erstellen wir einen Root-Span, der die gesamte Untersuchung repräsentiert.

with tracer (). start_as_current_span ( " workflow.investigate ", attributes = { " demo.scenario ": scenario, " demo.request_id ": request_id, " workflow.name ": " customer_investigation " } ) as root: ctx = root. get_span_context () trace_id = f " { ctx. trace_id: 032 x } " Enter fullscreen mode Exit fullscreen mode Dies ist der Ausgangspunkt unseres beobachtbaren Workflows.

Jeder untergeordnete Span, der in diesem Kontext erstellt wird, erbt den aktuellen Tracing-Kontext.

Der Code extrahiert außerdem die Trace-ID und gibt sie an die Weboberfläche zurück.

Damit hat der Nutzer eine direkte Möglichkeit, eine Anwendungsantwort mit ihrer Telemetrie zu verknüpfen.

with tracer (). start_as_current_span ( " agent.Coordinator ", attributes = { " agent.name ": " Coordinator ", " demo.scenario ": scenario } ): # Execute the workflow... Enter fullscreen mode Exit fullscreen mode Danach erstellen wir einzelne Spans für die spezialisierten Agenten:

with tracer (). start_as_current_span ( " agent.CustomerAgent ", attributes = { " agent.name ": " CustomerAgent " } ): customer_out = await runner. run ( " CustomerAgent ", customer_evidence ) Enter fullscreen mode Exit fullscreen mode Dasselbe Muster wird auf PolicyAgent und ResolutionAgent angewendet.

So können wir die Workflow-Hierarchie visualisieren und Agent-Operationen von externen Tool-Aufrufen unterscheiden.

OpenTelemetry-Spans unterstützen benutzerdefinierte Attribute, die einer Operation Kontext hinzufügen.

In unserer Anwendung verwenden wir Attribute wie:

demo.scenario demo.request_id agent.name tool.name demo.outcome demo.policy_version demo.answer_quality Enter fullscreen mode Exit fullscreen mode Diese Attribute sind besonders nützlich, wenn man Telemetriedaten in Azure Monitor analysiert.

Statt sich durch unstrukturierte Logmeldungen zu suchen, können wir Operationen nach ihrer fachlichen Bedeutung filtern.

Beispielsweise können wir nur die Ausführungen untersuchen, bei denen die Policy-Version veraltet war oder der Workflow ein unerwartetes Ergebnis lieferte.

Um die Demo interaktiv zu machen, verwenden wir FastAPI und Bootstrap.

Die Webanwendung stellt einen Endpunkt bereit, um ein ausgewähltes Szenario auszuführen:

@app.post ( " /api/scenarios/{scenario} " ) async def run_scenario ( scenario: str ): if scenario not in VALID_SCENARIOS: raise HTTPException ( status_code = 404, detail = " Unknown scenario " ) return await investigate ( scenario, app. state. runner ) Enter fullscreen mode Exit fullscreen mode Das vollständige Projekt bietet zudem einen Health-Endpunkt und eine HTML-Oberfläche.

Nach der Ausführung eines Szenarios zeigt die Webanwendung die abschließende Empfehlung, die Zwischenergebnisse der Agenten, die Ausführungsdauer, die Request-ID und die Trace-ID an.

Die Trace-ID ist besonders wertvoll, weil wir sie direkt in unsere KQL-Abfragen übernehmen können.

Fuehren wir zunaechst das gesunde Szenario aus.

Dafuer koennen wir die Weboberflaeche nutzen oder die API aufrufen:

curl -X POST \ http://localhost:9001/api/scenarios/healthy Enter fullscreen mode Exit fullscreen mode Der Coordinator fuehrt die folgenden Operationen aus:

Der simulierte Kunde ist ein Premium-Kunde, dessen Kauf 12 Tage zurueckliegt.

Die Erstattungsrichtlinie erlaubt es Premium-Kunden, innerhalb von 30 Tagen eine Rückerstattung zu beantragen.

Folglich lautet die erwartete Empfehlung, die Rückerstattung zu genehmigen.

Der Console Exporter erzeugt Spans für den Workflow, den Coordinator, die Tools und die Agents.

Diese Spans teilen sich dieselbe Trace ID und enthalten Timing-Informationen.

Im Foundry-Modus fuegt die unterstuetzte automatische Agent-Instrumentierung Details zur Modellausfuehrung hinzu.

Die zentrale Erkenntnis ist, dass wir nun viel mehr verstehen als nur die endgueltige Antwort.

Wir koennen die Ausfuehrungsreihenfolge rekonstruieren und sehen, wo Zeit verbracht wurde.

Dies ist die Basislinie, mit der wir die naechsten Szenarien vergleichen werden.

Stellen wir uns vor, dass Kunden ueber langsame Antworten unserer KI-Anwendung berichten.

Die erste Annahme waere moeglicherweise, dass das Modell zu lange fuer die Antwort benoetigt.

Das eigentliche Problem koennte jedoch ein externer Dienst sein.

Um dies zu demonstrieren, injizieren wir Latenz in die Kundenabruf-Operation.

async def _customer_tool ( scenario: str ): with tracer (). start_as_current_span ( " tool.get_customer ", attributes = { " tool.name ": " get_customer ", " demo.scenario ": scenario } ): if scenario == " slow_api ": await asyncio. sleep ( 2.5 ) else: await asyncio. sleep ( 0.08 ) return { " customer_id ": " C-1001 ", " tier ": " premium ", " order_id ": " O-2048 ", " days_since_order ": 12 } Enter fullscreen mode Exit fullscreen mode Fuehren wir das Szenario aus:

curl -X POST \ http://localhost:9001/api/scenarios/slow_api Enter fullscreen mode Exit fullscreen mode Der Workflow liefert weiterhin die erwartete Antwort, aber seine Gesamtdauer steigt.

Der Trace ermoeglicht es, die Quelle dieser Latenz zu identifizieren.

Statt dem Modell die Schuld zu geben, koennen wir erkennen, dass tool.get_customer etwa 2,5 Sekunden verbraucht hat.

Leistungsprobleme in agentischen Anwendungen sind nicht zwingend Modellprobleme.

Sie koennen aus Datenbanken, Tool-Servern, Netzwerkaufrufen, Authentifizierungsdiensten oder anderen Abhaengigkeiten entstehen.

Sobald die Telemetrie Application Insights erreicht, koennen wir die langsamsten Operationen identifizieren:

dependencies | where timestamp > ago(24h) | where name has "tool." or name has "agent." or name has "workflow." | summarize runs = count(), p50_ms = percentile(duration / 1ms, 50), p95_ms = percentile(duration / 1ms, 95), failures = countif(success == false) by name | order by p95_ms desc Enter fullscreen mode Exit fullscreen mode Diese Abfrage misst die Latenz von Operationen und deren Fehleranzahl.

Bei wiederholten Ausfuehrungen helfen Perzentile wie P95, Leistungsmuster aufzudecken, die Durchschnittswerte verbergen koennen.

Fuer diese Demonstration erwarten wir, dass die Kundenabruf-Operation im Slow-API-Szenario eine deutlich hoehere Latenz aufweist.

Dies ist wahrscheinlich das interessanteste Szenario der Demonstration.

Was passiert, wenn alle Komponenten erfolgreich ausgefuehrt werden, die endgueltige Antwort aber falsch ist?

Distributed Tracing kann uns sagen, ob eine Operation abgeschlossen wurde, aber es legt nicht automatisch fest, ob die Antwort inhaltlich korrekt ist.

Denken wir an einen Richtliniendienst fuer Rückerstattungen, der veraltete Informationen zurueckgibt.

Unsere Anwendung simuliert dieses Verhalten:

if scenario == " incorrect_decision ": span. set_attribute ( " demo.fault ", " stale_policy_data " ) return ( " REFUND_DENIED: stale cached policy " " says all refunds are disabled " ) Enter fullscreen mode Exit fullscreen mode Der Kunde hat gemaeß der aktuellen Richtlinie Anspruch auf eine Rückerstattung, doch das Tool liefert eine veraltete Regel.

Im deterministischen Mock-Modus folgt der PolicyAgent dieser Information, und der ResolutionAgent empfiehlt, die Rückerstattung abzulehnen und eine manuelle Pruefung anzufordern.

Technisch gesehen wird der Workflow ohne Ausnahmen abgeschlossen.

Um dieses Problem beobachtbar zu machen, haengt unsere Demo eigene Attribute an den Root Span an:

incorrect = scenario == " incorrect_decision " root. set_attribute ( " demo.answer_quality ", " incorrect_due_to_stale_tool " if incorrect else " expected " ) root. set_attribute ( " demo.policy_version ", " stale " if incorrect else " current " ) root. set_attribute ( " demo.outcome ", " incorrect " if incorrect else " success " ) Enter fullscreen mode Exit fullscreen mode Dadurch koennen wir technischen Erfolg vom erwarteten Geschaefstsverhalten unterscheiden.

Es gibt eine wichtige Einschraenkung: Diese Labels werden durch das bekannte Stoerungs-Injektionsszenario der Demonstration erzeugt.

Sie stellen keine automatische KI-Evaluierung dar.

Ein Produktionssystem waere auf zusaetzliche Validierung, Pruefungen der Richtlinienaktualitaet, regelbasierte Verifikation oder modellbasierte Evaluierung angewiesen, um unerwartet falsche Entscheidungen zu erkennen.

Im Foundry-Modus erkennt das Modell den Konflikt moeglicherweise sogar selbst und vermeidet die falsche Empfehlung.

dependencies | where timestamp > ago(24h) | where name has "workflow.investigate" | extend outcome = tostring( customDimensions["demo.outcome"] ), scenario = tostring( customDimensions["demo.scenario"] ), policyVersion = tostring( customDimensions["demo.policy_version"] ) | summarize runs = count(), avg_ms = avg(duration / 1ms) by scenario, outcome, policyVersion Enter fullscreen mode Exit fullscreen mode Diese Abfrage hilft dabei, Ausführungen mit absichtlich gekennzeichneten falschen Ergebnissen zu identifizieren.

Die grundlegendere Erkenntnis daraus ist, dass AI Observability die Kombination von operativer Telemetrie mit anwendungsseitigen Qualitätssignalen erfordert.

Eine erfolgreiche HTTP-Anfrage oder ein abgeschlossener Span garantiert nicht, dass eine KI-Entscheidung vertrauenswürdig ist.

Unser letztes Szenario simuliert einen Ausfall des Policy-Dienstes.

Statt eine Policy zurückzugeben, löst das Werkzeug eine Ausnahme aus.

if scenario == " tool_error ": span. set_attribute ( " demo.fault ", " upstream_timeout " ) raise TimeoutError ( " Policy service timed out " " (intentional demo fault) " ) Enter fullscreen mode Exit fullscreen mode Auf der Ebene des Workflows fangen wir die Ausnahme ab und markieren den Root-Span als fehlgeschlagen:

except Exception as exc: root. record_exception ( exc ) root. set_status ( Status ( StatusCode. ERROR, str ( exc )) ) root. set_attribute ( " demo.outcome ", " error " ) Enter fullscreen mode Exit fullscreen mode Das gibt uns diagnostische Informationen über den Fehler, einschließlich seines Zusammenhangs mit dem Workflow.

Ein interessantes Detail ist, dass die Demo eine strukturierte JSON-Antwort zurückgibt, die die fehlgeschlagene Untersuchung beschreibt.

Das bedeutet, dass der Web-Endpunkt selbst weiterhin HTTP 200 zurückgeben kann.

Folglich würde eine reine Überwachung der HTTP-Statuscodes kein vollständiges Bild der Anwendungsfehler liefern.

Explizite Fehler-Signale auf Workflow-Ebene sind unverzichtbar.

union isfuzzy=true dependencies, exceptions | where timestamp > ago(24h) | where success == false or itemType == "exception" | project timestamp, itemType, operation_Id, name, type, outerMessage, customDimensions | order by timestamp desc Enter fullscreen mode Exit fullscreen mode Damit können wir fehlgeschlagene Dependencies und Ausnahme-Telemetrie untersuchen.

Die Trace-ID verbindet das Problem mit der entsprechenden Kundenuntersuchung.

Bisher haben wir die Telemetrie lokal betrachtet.

Jetzt zentralisieren wir die Informationen in Azure.

Unsere Bicep-Infrastruktur definiert mehrere Ressourcen:

Application Insights ist mit dem Log Analytics Workspace verbunden, und die App erhält den bereitgestellten Foundry-Endpunkt sowie die Model Deployment Einstellungen als Umgebungsvariablen.

Die Container App erhält den Application Insights Connection String über ihre Umgebungskonfiguration.

Beispielsweise enthält die Bicep-Vorlage Folgendes:

resource insights 'Microsoft.Insights/components@2020-02-02' = { name: appInsightsName location: location kind: 'web' properties: { Application_Type: 'web' WorkspaceResourceId: logs.id } } Enter fullscreen mode Exit fullscreen mode Die Deployment-Konfiguration ist in azure.yaml definiert:

name: multi-agent-observability-lab infra: provider: bicep path: infra services: web: project:. language: py host: containerapp docker: path: Dockerfile Enter fullscreen mode Exit fullscreen mode Wir können die Infrastruktur mit der Azure Developer CLI bereitstellen und deployen:

az login azd auth login azd env new agent-otel-demo azd env set AZURE_LOCATION eastus2 azd up Enter fullscreen mode Exit fullscreen mode Dieser Workflow ist darauf ausgelegt, die Hosting- und Observability-Ressourcen bereitzustellen, das Container-Image zu bauen und die Anwendung zu deployen.

Die Bicep-Vorlage verwendet bei der anfänglichen Infrastrukturbereitstellung ein Platzhalter-Image, das durch das Deployment der Anwendung ersetzt werden soll.

Ein paar Überlegungen sind erwähnenswert.

Erstens stellt die Azure-Bereitstellung das Microsoft Foundry-Konto, das Projekt und das Modell gpt-4o-mini automatisch als Teil desselben Infrastruktur-Stacks bereit, sodass das Beispiel mit Live-Inferenz laufen kann, ohne dass eine separat verwaltete Foundry-Einrichtung erforderlich ist.

Zweitens wurde die Azure-Deployment-Vorlage im Rahmen dieses Tutorials nicht end-to-end gegen ein Live-Abonnement validiert. Azure-Berechtigungen, regionale Verfügbarkeit, SDK-Änderungen und das Verhalten des Container-Deployments sollten getestet werden, bevor die Demo in einem Live-Event verwendet wird.

Für eine zuverlässige Präsentation können wir die Python-Anwendung auch lokal weiterlaufen lassen, während die Telemetrie an eine bereitgestellte Application Insights-Ressource exportiert wird.

So wird vermieden, dass die gesamte Demonstration von einem erfolgreichen Cloud-Deployment der Anwendung abhängt.

Eines der nützlichsten Observability-Features ist die Möglichkeit, eine vollständige Ausführung anhand eines einzelnen Bezeichners zu rekonstruieren.

Unsere Weboberfläche zeigt die vom Workflow zurückgegebene Trace-ID an.

Wir können diesen Bezeichner kopieren und die folgende Abfrage in Application Insights ausführen:

let traceId = "REPLACE_WITH_TRACE_ID"; union isfuzzy=true requests, dependencies, traces, exceptions | where operation_Id == traceId | project timestamp, itemType, name, duration, success, message, operation_Id, parentId = operation_ParentId, customDimensions | order by timestamp asc Enter fullscreen mode Exit fullscreen mode Damit werden die Telemetriedaten zusammengeführt, die zu einer einzelnen Untersuchung gehören.

Da die Vorgänge denselben Korrelationskontext teilen, können wir die Ereignisse, Spans und Fehler identifizieren, die zum selben Workflow gehören.

Das ist besonders nützlich, wenn Multi-Agent-Architekturen über eine einzige Anwendung hinauswachsen.

Beispielsweise können dieselben Prinzipien die Korrelation über HTTP-Dienste, Agent-Tools, Datenbanken und andere verteilte Komponenten hinweg unterstützen, sofern der Trace-Kontext über diese Grenzen hinweg weitergegeben wird.

Ein weiterer wichtiger Aspekt der AI Observability sind die Kosten.

Bei traditionellen Anwendungen analysieren wir typischerweise die Infrastrukturnutzung, CPU, Speicher und die Anzahl der Serviceanfragen.

Generative KI bringt zusätzliche Verbrauchskennzahlen mit sich, darunter Input-Tokens, Output-Tokens und Modellaufrufe.

Die Instrumentierung des Microsoft Agent Framework kann Informationen zur Modellnutzung bereitstellen, sofern dies von der jeweiligen SDK-Version, dem Modellanbieter und dem Exporter unterstützt wird.

Unser Projekt enthält eine Abfrage, die nach häufigen Telemetrie-Attributen für generative KI sucht:

dependencies | where timestamp > ago(24h) | extend inputTokens = tolong( customDimensions["gen_ai.usage.input_tokens"] ), outputTokens = tolong( customDimensions["gen_ai.usage.output_tokens"] ) | where isnotnull(inputTokens) or isnotnull(outputTokens) | summarize input_tokens = sum(inputTokens), output_tokens = sum(outputTokens), calls = count() by name | order by input_tokens desc Enter fullscreen mode Exit fullscreen mode Diese Abfrage ist relevant, wenn echte Modellinferenz über Foundry ausgeführt wird.

Im Mock-Modus werden keine Modellaufrufe ausgeführt, daher wird keine Token-Nutzung erzeugt.

Außerdem können sich die genauen Namen der Telemetrie-Attribute und deren Speicherorte je nach Instrumentierungs-Version und Azure Monitor Zuordnungen unterscheiden.

Bevor wir Produktions-Dashboards erstellen, sollten wir die tatsächlich exportierten Modell-Spans untersuchen und die verfügbaren Attribute überprüfen.

Sobald zuverlässige Nutzungsdaten vorliegen, können wir geschätzte Modellkosten berechnen, indem wir den Token-Verbrauch mit den Preisen der ausgewählten Modellbereitstellung kombinieren.

Das eröffnet Möglichkeiten für eine Kostenverteilung auf Agent-Ebene, Budgetüberwachung und operative Optimierung.

Unsere Demonstration konzentriert sich auf das Verständnis von Observability-Konzepten, doch produktionsreife Multi-Agent-Anwendungen erfordern zusätzliche Überlegungen.

Verteilte Kontextweitergabe: Wenn Agents und Tools über mehrere Dienste oder Prozesse hinweg ausgeführt werden, muss der Tracing-Kontext zwischen ihnen weitergegeben werden. Das Erstellen lokaler Child-Spans ist nur der Ausgangspunkt.

Sampling und Telemetrie-Volumen: Unsere Demo zeichnet alle Traces auf, um die Untersuchung zu erleichtern. Produktionsanwendungen sollten diagnostische Anforderungen, Sampling-Entscheidungen, Ingestion-Kosten und Aufbewahrungsrichtlinien gegeneinander abwägen.

Sicherheit und Datenschutz: Telemetrie darf nicht wahllos Nutzer-Prompts, vertrauliche Dokumente, Zugangsdaten oder persönliche Informationen erfassen.

Automatisierte Anomalieerkennung: KQL-Abfragen und Azure Monitor-Warnregeln können Muster wie erhöhte Fehlerraten, ungewöhnliche Latenz oder unerwartete Nutzung erkennen.

KI-Evaluation: Operatives Tracing sollte durch Evaluationstechniken ergänzt werden, die Genauigkeit, Fundiertheit, Richtlinienkonformität und andere anwendungsspezifische Qualitätsdimensionen bewerten.

Kostengovernance: Token-Nutzung, Modellaufrufe, Infrastrukturverbrauch und Telemetrie-Ingestion sollten in die nicht-funktionalen Anforderungen und operativen Budgets der Anwendung einbezogen werden.

Observability ist nicht einfach nur eine Debugging-Funktion. Sie ist Teil des verantwortungsvollen Betriebs von KI-Systemen im großen Maßstab.

Fassen wir die empfohlene Ausführungsreihenfolge zusammen.

python -m uvicorn app.main:app --reload Enter fullscreen mode Exit fullscreen mode Öffne:

Führe dann die Szenarien in dieser Reihenfolge aus:

Untersuche bei jedem Szenario die Konsolen-Traces, kopiere die Trace-ID und führe die entsprechenden KQL-Abfragen in Azure Monitor aus.

Wenn die Application Insights-Telemetrie nicht sofort sichtbar ist, nutze den lokalen Console Exporter, während die Ingestion abgeschlossen wird.

Wechsle abschließend in den Foundry-Modus, um echte Agent-Ausführung und modellbezogene Telemetrie zu demonstrieren.

Die Standard-Azure-Bereitstellung erstellt bereits den Projekt-Endpoint und das gpt-4o-mini Modell, sodass die App auf die bereitgestellten Werte verweisen kann, ohne dass eine manuelle Azure AI Einrichtung erforderlich ist. Wenn du sie manuell überschreiben möchtest, lauten die Umgebungsvariablen:

MODEL_MODE = foundry FOUNDRY_PROJECT_ENDPOINT = https://YOUR-PROJECT.services.ai.azure.com FOUNDRY_MODEL_DEPLOYMENT = YOUR_MODEL_DEPLOYMENT Enter fullscreen mode Exit fullscreen mode Authentifiziere dich bei Azure und stelle sicher, dass deine Identität über die erforderlichen Berechtigungen verfügt, um das bereitgestellte Modell aufzurufen.

Starte die Anwendung neu und führe dieselben Szenarien aus.

Nun verwenden die Agents anstelle von deterministischen Mock-Antworten die von der Infrastruktur bereitgestellte Microsoft Foundry-Bereitstellung.

Multi-Agent-KI-Anwendungen bringen neue operative Herausforderungen mit sich, da ihr Verhalten von mehreren Agents, Modellen, Tools und externen Diensten abhängt.

Eine finale Antwort zeigt uns, was die Anwendung erzeugt hat, erklärt aber selten den vollständigen Ausführungspfad, der zu ihr geführt hat.

OpenTelemetry bietet die Tracing-Grundlage, die zum Verstehen dieses Ausführungspfads erforderlich ist, während Microsoft Agent Framework Instrumentierung für Agent-Operationen bereitstellt und Azure Monitor eine zentralisierte Analyse ermöglicht.

Anhand unserer vier Demo-Szenarien haben wir gelernt, wie man einen Performance-Engpass identifiziert, Fehler korreliert, fehlerhafte Entscheidungen untersucht und die Modellnutzung auswertet.

Die vielleicht wichtigste Erkenntnis ist, dass Observability und Evaluation komplementäre Probleme lösen.

Observability hilft uns zu verstehen, was im System passiert ist. Evaluation hilft uns festzustellen, ob das Verhalten und die Ergebnisse angemessen waren.

Die Kombination beider Fähigkeiten ist entscheidend für den Aufbau zuverlässiger, erklärbbarer und operativ beherrschbarer agenter KI-Systeme.

Vergiss nicht, dir auch die übrigen Community-Sessions von Agentic Azure Insights anzusehen.

Danke fürs Lesen, bis zum nächsten Mal! Luis

Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.

Original (Englisch) anzeigen

This blog post is part of the Agentic Azure Insights community initiative created by Nicholas Chang. Take a look at other sessions that are part of the event.

Building, tracing, monitoring, and troubleshooting intelligent multi-agent applications with Python, Microsoft Foundry, and Azure.

Building an AI agent that responds to a prompt is relatively straightforward. Building a production-ready multi-agent application is a completely different challenge.

As AI systems evolve from individual agents into collaborative architectures, understanding their behavior becomes increasingly difficult.

Imagine an application where several specialized AI agents collaborate to solve a customer support request. One agent retrieves customer information, another evaluates business policies, and a third generates the final resolution.

What happens when the application becomes slow? How do we identify which agent caused the delay? What if all agents execute successfully, but the final decision is incorrect?

Traditional application logs provide some answers, but they are often insufficient for reconstructing complex, distributed AI workflows.

This is where OpenTelemetry, Microsoft Agent Framework, and Azure Monitor become valuable.

In this tutorial, we'll build an observable multi-agent application that demonstrates how to:

Most importantly, we'll investigate actual scenarios where something goes wrong.

Technologies that will be used in the tutorial: Python, FastAPI, Microsoft Agent Framework, Microsoft Foundry, OpenTelemetry, Azure Monitor, Application Insights, KQL, Azure Container Apps, Bicep, and Azure Developer CLI.

Observability is the ability to understand the internal behavior of a system by analyzing the signals it produces.

In traditional distributed applications, observability relies on three fundamental pillars:

For agentic AI applications, these capabilities become even more important.

A multi-agent workflow might involve multiple model invocations, external APIs, database queries, and decisions based on information produced by other agents.

A single user request can generate dozens of operations across different components.

Without distributed tracing, reconstructing the execution path becomes difficult.

OpenTelemetry provides a vendor-neutral framework for generating, collecting, and exporting telemetry.

Its tracing model is particularly useful for multi-agent systems.

A trace represents an end-to-end operation, while a span represents an individual unit of work within that operation.

For example, consider the following execution:

Every span contains information such as its operation name, execution time, status, attributes, and relationship with other spans.

All spans within the same distributed operation share a trace ID.

This allows us to reconstruct how the application produced a particular result.

Let's build a small multi-agent application that investigates customer refund requests.

The architecture includes a coordinator and three specialized agents.

CustomerAgent analyzes customer information, including account tier and purchase history.

PolicyAgent examines refund policy evidence and determines whether the available information supports a refund.

ResolutionAgent combines the findings and generates a recommended resolution.

The coordinator handles the execution sequence, invokes tools, and supplies the evidence required by each agent.

Our application also includes two simulated external services: a customer information API and a refund policy lookup API.

The application supports two execution modes.

In mock mode, agent responses are deterministic, making it possible to demonstrate observability without a deployed AI model.

In Foundry mode, the application creates real Microsoft Agent Framework agents connected to the Microsoft Foundry project and model deployment that are created as part of the Azure deployment. In the default template, the project points to a provisioned gpt-4o-mini deployment so the app can run with real model inference without requiring separate manual setup.

This distinction is important because the mock mode demonstrates workflow instrumentation, while Foundry mode adds real model execution and associated telemetry when supported by the installed SDK.

fastapi uvicorn opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-fastapi azure-monitor-opentelemetry azure-identity agent-framework-core agent-framework-foundry python-dotenv jinja2 Enter fullscreen mode Exit fullscreen mode We can create the environment and install the dependencies:

python3 -m venv.venv source.venv/bin/activate pip install -r requirements.txt cp.env.example.env Enter fullscreen mode Exit fullscreen mode Initially, we'll use the following configuration:

MODEL_MODE = mock APPLICATIONINSIGHTS_CONNECTION_STRING = OTEL_SERVICE_NAME = multi-agent-investigator OTEL_CONSOLE = true ENABLE_SENSITIVE_DATA = false Enter fullscreen mode Exit fullscreen mode This allows the application to operate locally without any Azure resources.

Let's begin with the most important technical component: configuring the OpenTelemetry SDK.

In app/telemetry.py, our implementation supports both local console tracing and Azure Monitor.

First, we import the necessary components:

from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ( BatchSpanProcessor, ConsoleSpanExporter ) Enter fullscreen mode Exit fullscreen mode Next, we configure a tracing provider for local execution:

provider = TracerProvider ( resource = Resource. create ({ " service.name ": " multi-agent-investigator " }) ) provider. add_span_processor ( BatchSpanProcessor ( ConsoleSpanExporter ()) ) trace. set_tracer_provider ( provider ) Enter fullscreen mode Exit fullscreen mode The TracerProvider manages tracing configuration, while the BatchSpanProcessor processes spans and sends them to the configured exporter.

The ConsoleSpanExporter prints the generated spans to the terminal.

This is especially useful during local development and live demonstrations.

For Azure Monitor, the project uses a different configuration path:

from azure.monitor.opentelemetry import configure_azure_monitor configure_azure_monitor ( connection_string = connection, resource = Resource. create ({ " service.name ": service }), sampling_ratio = 1.0, logger_name = " agent_demo ", enable_live_metrics = True ) Enter fullscreen mode Exit fullscreen mode The application selects this configuration when APPLICATIONINSIGHTS_CONNECTION_STRING is present.

It can also attach the console exporter to the same tracing provider, allowing us to observe telemetry locally while exporting it to Azure.

We deliberately configure only one global tracing provider.

Now comes another important integration:

from agent_framework.observability import enable_instrumentation enable_instrumentation ( enable_sensitive_data = False ) Enter fullscreen mode Exit fullscreen mode Microsoft Agent Framework can produce OpenTelemetry signals for instrumented agent operations.

This complements the custom spans we create for our coordinator, workflow, and simulated external tools.

The enable_sensitive_data=False setting is particularly important because agent inputs and outputs may contain confidential or personally identifiable information.

In production environments, capturing entire prompts, customer records, and model responses should never be enabled without appropriate privacy controls.

In app/agents.py, we define the instructions for each specialist:

AGENT_INSTRUCTIONS = { " CustomerAgent ": ( " You are a customer record specialist. " " Summarize the provided account facts " " in one sentence. Do not invent additional facts. " ), " PolicyAgent ": ( " You review refund policy evidence. " " Report whether the supplied policy " " evidence allows refund. " ), " ResolutionAgent ": ( " Provide a concise support resolution " " based ONLY on the supplied customer " " and policy evidence. " ) } Enter fullscreen mode Exit fullscreen mode When Foundry mode is enabled, the application initializes a Foundry client:

from agent_framework import Agent from agent_framework.foundry import FoundryChatClient from azure.identity import DefaultAzureCredential client = FoundryChatClient ( project_endpoint = endpoint, model = model, credential = DefaultAzureCredential () ) Enter fullscreen mode Exit fullscreen mode Then we create the specialized agents:

agents = { name: Agent ( client = client, name = name, instructions = instructions ) for name, instructions in AGENT_INSTRUCTIONS. items () } Enter fullscreen mode Exit fullscreen mode Each agent is responsible for a particular task.

The coordinator supplies the evidence and invokes the agents sequentially.

result = await agents [ " PolicyAgent " ]. run ( policy_evidence ) Enter fullscreen mode Exit fullscreen mode Notice that our orchestration does not require the agents to communicate directly with each other.

The Python coordinator explicitly controls execution and information exchange.

This makes the workflow easier to understand and allows us to create custom tracing boundaries around every operation.

In app/workflow.py, we create a root span to represent the entire investigation.

with tracer (). start_as_current_span ( " workflow.investigate ", attributes = { " demo.scenario ": scenario, " demo.request_id ": request_id, " workflow.name ": " customer_investigation " } ) as root: ctx = root. get_span_context () trace_id = f " { ctx. trace_id: 032 x } " Enter fullscreen mode Exit fullscreen mode This is the starting point of our observable workflow.

Every child span created within this context inherits the current tracing context.

The code also extracts the trace ID and returns it to the web interface.

This gives the user a direct way to correlate an application response with its telemetry.

with tracer (). start_as_current_span ( " agent.Coordinator ", attributes = { " agent.name ": " Coordinator ", " demo.scenario ": scenario } ): # Execute the workflow... Enter fullscreen mode Exit fullscreen mode We then create individual spans for the specialized agents:

with tracer (). start_as_current_span ( " agent.CustomerAgent ", attributes = { " agent.name ": " CustomerAgent " } ): customer_out = await runner. run ( " CustomerAgent ", customer_evidence ) Enter fullscreen mode Exit fullscreen mode The same pattern is applied to PolicyAgent and ResolutionAgent.

This allows us to visualize the workflow hierarchy and distinguish agent operations from external tool calls.

OpenTelemetry spans support custom attributes that add context to an operation.

In our application, we use attributes such as:

demo.scenario demo.request_id agent.name tool.name demo.outcome demo.policy_version demo.answer_quality Enter fullscreen mode Exit fullscreen mode These attributes are particularly useful when analyzing telemetry in Azure Monitor.

Instead of searching through unstructured log messages, we can filter operations by their business meaning.

For example, we can investigate only the executions where the policy version was stale or where the workflow produced an unexpected outcome.

To make the demo interactive, we use FastAPI and Bootstrap.

The web application exposes an endpoint to execute a selected scenario:

@app.post ( " /api/scenarios/{scenario} " ) async def run_scenario ( scenario: str ): if scenario not in VALID_SCENARIOS: raise HTTPException ( status_code = 404, detail = " Unknown scenario " ) return await investigate ( scenario, app. state. runner ) Enter fullscreen mode Exit fullscreen mode The full project also provides a health endpoint and an HTML interface.

After executing a scenario, the web application displays the final recommendation, intermediate agent outputs, execution duration, request ID, and trace ID.

The trace ID is particularly valuable because we can copy it directly into our KQL queries.

First, let's execute the healthy scenario.

We can use the web interface or invoke the API:

curl -X POST \ http://localhost:9001/api/scenarios/healthy Enter fullscreen mode Exit fullscreen mode The coordinator performs the following operations:

The simulated customer is a premium customer whose purchase occurred 12 days ago.

The refund policy allows premium customers to request a refund within 30 days.

Consequently, the expected recommendation is to approve the refund.

The console exporter generates spans for the workflow, coordinator, tools, and agents.

These spans share the same trace ID and contain timing information.

When using Foundry mode, supported automatic agent instrumentation adds model execution details.

The key observation is that we now understand much more than the final answer.

We can reconstruct the execution sequence and see where time was spent.

This is the baseline against which we will compare our next scenarios.

Imagine that customers are reporting slow responses from our AI application.

The first assumption might be that the model is taking too long to respond.

However, the actual problem could be an external service.

To demonstrate this, we inject latency into the customer lookup operation.

async def _customer_tool ( scenario: str ): with tracer (). start_as_current_span ( " tool.get_customer ", attributes = { " tool.name ": " get_customer ", " demo.scenario ": scenario } ): if scenario == " slow_api ": await asyncio. sleep ( 2.5 ) else: await asyncio. sleep ( 0.08 ) return { " customer_id ": " C-1001 ", " tier ": " premium ", " order_id ": " O-2048 ", " days_since_order ": 12 } Enter fullscreen mode Exit fullscreen mode Execute the scenario:

curl -X POST \ http://localhost:9001/api/scenarios/slow_api Enter fullscreen mode Exit fullscreen mode The workflow still produces the expected answer, but its total duration increases.

The trace makes it possible to identify the source of this latency.

Rather than blaming the model, we can see that tool.get_customer consumed approximately 2.5 seconds.

Performance problems in agentic applications are not necessarily model problems.

They can originate from databases, tool servers, network calls, authentication services, or other dependencies.

Once telemetry reaches Application Insights, we can identify the slowest operations:

dependencies | where timestamp > ago(24h) | where name has "tool." or name has "agent." or name has "workflow." | summarize runs = count(), p50_ms = percentile(duration / 1ms, 50), p95_ms = percentile(duration / 1ms, 95), failures = countif(success == false) by name | order by p95_ms desc Enter fullscreen mode Exit fullscreen mode This query measures operation latency and failure counts.

With repeated executions, percentiles such as P95 help reveal performance patterns that averages can hide.

For this demonstration, we expect the customer lookup operation to exhibit significantly higher latency during the slow API scenario.

This is probably the most interesting scenario in the demonstration.

What happens when all the components execute successfully, but the final answer is incorrect?

Distributed tracing can tell us whether an operation completed, but it does not automatically establish whether the answer is factually correct.

Consider a refund policy service that returns outdated information.

Our application simulates this behavior:

if scenario == " incorrect_decision ": span. set_attribute ( " demo.fault ", " stale_policy_data " ) return ( " REFUND_DENIED: stale cached policy " " says all refunds are disabled " ) Enter fullscreen mode Exit fullscreen mode The customer is eligible for a refund according to the current policy, but the tool returns an outdated rule.

In deterministic mock mode, PolicyAgent follows that information and ResolutionAgent recommends declining the refund and requesting manual review.

Technically, the workflow completes without exceptions.

To make this problem observable, our demo attaches custom attributes to the root span:

incorrect = scenario == " incorrect_decision " root. set_attribute ( " demo.answer_quality ", " incorrect_due_to_stale_tool " if incorrect else " expected " ) root. set_attribute ( " demo.policy_version ", " stale " if incorrect else " current " ) root. set_attribute ( " demo.outcome ", " incorrect " if incorrect else " success " ) Enter fullscreen mode Exit fullscreen mode This allows us to distinguish technical success from expected business behavior.

There is an important limitation: these labels are generated by the demonstration's known fault-injection scenario.

They do not represent an automatic AI evaluation.

A production system would need additional validation, policy freshness checks, rule-based verification, or model-based evaluation to detect unexpected incorrect decisions.

In Foundry mode, the model may even recognize the conflict and avoid the incorrect recommendation.

dependencies | where timestamp > ago(24h) | where name has "workflow.investigate" | extend outcome = tostring( customDimensions["demo.outcome"] ), scenario = tostring( customDimensions["demo.scenario"] ), policyVersion = tostring( customDimensions["demo.policy_version"] ) | summarize runs = count(), avg_ms = avg(duration / 1ms) by scenario, outcome, policyVersion Enter fullscreen mode Exit fullscreen mode This query helps identify executions with intentionally labeled incorrect outcomes.

The broader lesson is that AI observability requires combining operational telemetry with application-level quality signals.

A successful HTTP request or completed span does not guarantee a trustworthy AI decision.

Our final scenario simulates a failure in the policy service.

Instead of returning a policy, the tool raises an exception.

if scenario == " tool_error ": span. set_attribute ( " demo.fault ", " upstream_timeout " ) raise TimeoutError ( " Policy service timed out " " (intentional demo fault) " ) Enter fullscreen mode Exit fullscreen mode At the workflow level, we capture the exception and mark the root span as failed:

except Exception as exc: root. record_exception ( exc ) root. set_status ( Status ( StatusCode. ERROR, str ( exc )) ) root. set_attribute ( " demo.outcome ", " error " ) Enter fullscreen mode Exit fullscreen mode This gives us diagnostic information about the failure, including its relationship with the workflow.

An interesting detail is that the demo returns a structured JSON response describing the failed investigation.

This means the web endpoint itself may still return HTTP 200.

Consequently, simply monitoring HTTP status codes would not provide a complete picture of application failures.

Explicit workflow-level error signals are essential.

union isfuzzy=true dependencies, exceptions | where timestamp > ago(24h) | where success == false or itemType == "exception" | project timestamp, itemType, operation_Id, name, type, outerMessage, customDimensions | order by timestamp desc Enter fullscreen mode Exit fullscreen mode This allows us to investigate failed dependencies and exception telemetry.

The trace ID connects the problem to the corresponding customer investigation.

So far, we have been examining telemetry locally.

Now let's centralize the information in Azure.

Our Bicep infrastructure defines several resources:

Application Insights is connected to the Log Analytics workspace, and the app receives the provisioned Foundry endpoint and model deployment settings as environment variables.

The Container App receives an Application Insights connection string through its environment configuration.

For example, the Bicep template includes:

resource insights 'Microsoft.Insights/components@2020-02-02' = { name: appInsightsName location: location kind: 'web' properties: { Application_Type: 'web' WorkspaceResourceId: logs.id } } Enter fullscreen mode Exit fullscreen mode The deployment configuration is defined in azure.yaml:

name: multi-agent-observability-lab infra: provider: bicep path: infra services: web: project:. language: py host: containerapp docker: path: Dockerfile Enter fullscreen mode Exit fullscreen mode We can provision and deploy the infrastructure using Azure Developer CLI:

az login azd auth login azd env new agent-otel-demo azd env set AZURE_LOCATION eastus2 azd up Enter fullscreen mode Exit fullscreen mode This workflow is designed to provision the hosting and observability resources, build the container image, and deploy the application.

The Bicep template uses a placeholder image during initial infrastructure provisioning, which the application deployment is intended to replace.

A couple of considerations are worth mentioning.

First, the Azure deployment provisions the Microsoft Foundry account, project, and gpt-4o-mini model automatically as part of the same infrastructure stack, so the sample can run with live inference without a separately managed Foundry setup.

Second, the Azure deployment template has not been end-to-end validated against a live subscription as part of this tutorial. Azure permissions, regional availability, SDK changes, and container deployment behavior should be tested before using the demo in a live event.

For a reliable presentation, we can also keep the Python application running locally while exporting telemetry to a provisioned Application Insights resource.

This avoids making the entire demonstration dependent on a successful cloud application deployment.

One of the most useful observability features is the ability to reconstruct a complete execution using a single identifier.

Our web interface displays the trace ID returned by the workflow.

We can copy that identifier and execute the following query in Application Insights:

let traceId = "REPLACE_WITH_TRACE_ID"; union isfuzzy=true requests, dependencies, traces, exceptions | where operation_Id == traceId | project timestamp, itemType, name, duration, success, message, operation_Id, parentId = operation_ParentId, customDimensions | order by timestamp asc Enter fullscreen mode Exit fullscreen mode This brings together the telemetry associated with a single investigation.

Because the operations share the same correlation context, we can identify the events, spans, and failures that belong to the same workflow.

This is especially useful as multi-agent architectures grow beyond one application.

For example, the same principles can support correlation across HTTP services, agent tools, databases, and other distributed components, provided trace context is propagated across those boundaries.

Another important aspect of AI observability is cost.

With traditional applications, we typically analyze infrastructure usage, CPU, memory, and service request counts.

Generative AI introduces additional consumption indicators, including input tokens, output tokens, and model invocations.

Microsoft Agent Framework instrumentation can expose model usage information when supported by the specific SDK version, model provider, and exporter.

Our project includes a query that looks for common generative AI telemetry attributes:

dependencies | where timestamp > ago(24h) | extend inputTokens = tolong( customDimensions["gen_ai.usage.input_tokens"] ), outputTokens = tolong( customDimensions["gen_ai.usage.output_tokens"] ) | where isnotnull(inputTokens) or isnotnull(outputTokens) | summarize input_tokens = sum(inputTokens), output_tokens = sum(outputTokens), calls = count() by name | order by input_tokens desc Enter fullscreen mode Exit fullscreen mode This query is relevant when running real model inference through Foundry.

In mock mode, no model calls are executed, so token usage is not generated.

Additionally, the exact telemetry attribute names and storage locations may vary depending on instrumentation versions and Azure Monitor mappings.

Before building production dashboards, we should inspect actual exported model spans and validate the available attributes.

Once reliable usage data is available, we can calculate estimated model costs by combining token consumption with the pricing of the selected model deployment.

This creates opportunities for agent-level cost allocation, budget monitoring, and operational optimization.

Our demonstration focuses on understanding observability concepts, but production-ready multi-agent applications require additional considerations.

Distributed context propagation: When agents and tools execute across multiple services or processes, tracing context must be propagated between them. Creating local child spans is only the starting point.

Sampling and telemetry volume: Our demo records all traces to make investigation easier. Production applications should balance diagnostic requirements, sampling decisions, ingestion costs, and retention policies.

Security and privacy: Telemetry must not indiscriminately capture user prompts, confidential documents, credentials, or personal information.

Automated anomaly detection: KQL queries and Azure Monitor alert rules can identify patterns such as increased failure rates, abnormal latency, or unexpected usage.

AI evaluation: Operational tracing should be complemented by evaluation techniques that assess accuracy, groundedness, policy compliance, and other application-specific quality dimensions.

Cost governance: Token usage, model calls, infrastructure consumption, and telemetry ingestion should be incorporated into the application's non-functional requirements and operational budgets.

Observability is not simply a debugging feature. It is part of operating AI systems responsibly at scale.

Let's summarize the recommended execution sequence.

python -m uvicorn app.main:app --reload Enter fullscreen mode Exit fullscreen mode Open:

Then execute the scenarios in this order:

For each scenario, examine the console traces, copy the trace ID, and run the corresponding KQL queries in Azure Monitor.

If Application Insights telemetry is not immediately visible, use the local console exporter while ingestion completes.

Finally, switch to Foundry mode to demonstrate real agent execution and model-related telemetry.

The default Azure deployment already creates the project endpoint and gpt-4o-mini model, so the app can be pointed to the provisioned values without manual Azure AI setup. If you want to override them manually, the environment variables are:

MODEL_MODE = foundry FOUNDRY_PROJECT_ENDPOINT = https://YOUR-PROJECT.services.ai.azure.com FOUNDRY_MODEL_DEPLOYMENT = YOUR_MODEL_DEPLOYMENT Enter fullscreen mode Exit fullscreen mode Authenticate with Azure and ensure that your identity has the necessary permissions to invoke the deployed model.

Restart the application and execute the same scenarios.

Now, instead of deterministic mock responses, the agents use the Microsoft Foundry deployment provisioned by the infrastructure.

Multi-agent AI applications introduce new operational challenges because their behavior depends on multiple agents, models, tools, and external services.

A final response tells us what the application produced, but rarely explains the complete execution path that led to it.

OpenTelemetry provides the tracing foundation needed to understand that execution path, while Microsoft Agent Framework offers instrumentation for agent operations and Azure Monitor enables centralized analysis.

Through our four demo scenarios, we learned how to identify a performance bottleneck, correlate failures, investigate incorrect decisions, and explore model usage.

Perhaps the most important lesson is that observability and evaluation solve complementary problems.

Observability helps us understand what happened inside the system. Evaluation helps us determine whether the behavior and outcomes were appropriate.

Combining both capabilities is essential for building reliable, explainable, and operationally manageable agentic AI systems.

Don't forget to check the rest of Agentic Azure Insights community sessions.

Thank you for reading, see you next time! Luis

Quelle: dev.to (dev.to). Original öffnen