The Microsoft Agent Framework provides built-in OpenTelemetry (OTel) support for AI agents. No additional instrumentation SDK is required.
When your agent runs, the framework automatically emits traces and metrics following the GenAI semantic conventions, which
AI Observability natively understands.
This guide shows you how to run a sample agent that calls Azure OpenAI and exports traces and metrics directly to Dynatrace via OTLP HTTP.
This getting started guide is for:
By following this guide, you'll learn:
AI Observability.To get started, you need to have:
Python 3.10 or later.
An Azure OpenAI endpoint and deployment key.
The sample app, or your own application using the Microsoft Agent Framework.
Dynatrace SaaS with a Dynatrace Platform Subscription (DPS) license that has Traces powered by Grail and Metrics powered by Grail enabled.
OTLP ingestion enabled, see OpenTelemetry and Dynatrace.
A Dynatrace API token with the following scopes, see Dynatrace API - Tokens and authentication.
openTelemetryTrace.ingest)metrics.ingest)It's helpful to have some basic knowledge of:
The Microsoft Agent Framework instruments itself via OTel natively.
Calling Agent.run() produces two nested spans that
AI Observability understands without any additional configuration:
invoke_agent is emitted by AgentTelemetryLayer, and carries gen_ai.agent.name and gen_ai.conversation.id.chat is emitted by ChatTelemetryLayer, and carries token counts, model name, and prompt and completion content.The following table lists the signals that the Microsoft Agent Framework exports, their endpoints, and their key attributes.
| Signal | Endpoint | Key attributes |
|---|---|---|
Traces |
|
|
Metrics |
| These populate the latency and cost dashboard views in Dynatrace.
|
Metrics are exported with OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta, which Dynatrace requires.
For more information, see Aggregation temporality.
This example runs an agent that calls Azure OpenAI to write a haiku about observability. The agent exports traces and metrics directly to Dynatrace, with no additional instrumentation code required.
Copy .env.sample to .env in the microsoft-agent-framework/opentelemetry directory and fill in your values.
OPENAI_API_KEY=...OPENAI_API_BASE=https://<resource>.openai.azure.com/openai/deployments/<deployment> # full deployment URL; the app strips the path to derive the Azure base endpointOPENAI_API_VERSION=2025-04-01-previewMODEL=<deployment>TEMPERATURE=0.7DT_ENDPOINT=https://<YOUR_ENV>.live.dynatrace.comDT_API_TOKEN=dt0c01....OTEL_SERVICE_NAME=microsoft-agent-frameworkOTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf # required by Dynatrace OTLP ingestOTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta # Dynatrace requires delta; the SDK default (cumulative) returns HTTP 400
Create a virtual environment and install the required packages.
python -m venv .venvsource .venv/bin/activatepip install -r requirements.txt
The required packages are:
agent-frameworkagent-framework-openaiopentelemetry-sdkopentelemetry-exporter-otlp-proto-httppython-dotenvThe code block below shows the sample application
It configures OTLP exports to Dynatrace, creates an agent, and runs the agent with a single prompt.
Copy the contents and save them to a local file, app.py.
import asyncioimport osimport uuidfrom agent_framework import Agentfrom agent_framework.observability import configure_otel_providersfrom agent_framework.openai import OpenAIChatCompletionClientfrom dotenv import load_dotenvfrom opentelemetry import metrics as otel_metricsfrom opentelemetry import trace as otel_tracedef _derive_azure_endpoint(base_url: str) -> str:marker = "/openai/"if marker in base_url:return base_url.split(marker, 1)[0]return base_url.rstrip("/")def _configure_dynatrace_otlp() -> None:dt_endpoint = os.environ["DT_ENDPOINT"].rstrip("/")dt_api_token = os.environ["DT_API_TOKEN"]auth_header = f"Authorization=Api-Token {dt_api_token}"os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = f"{dt_endpoint}/api/v2/otlp/v1/traces"os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = auth_headeros.environ["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] = f"{dt_endpoint}/api/v2/otlp/v1/metrics"os.environ["OTEL_EXPORTER_OTLP_METRICS_HEADERS"] = auth_headerasync def main() -> None:load_dotenv()_configure_dynatrace_otlp()configure_otel_providers(enable_sensitive_data=True)client = OpenAIChatCompletionClient(model=os.getenv("MODEL", "gpt-4o-mini"),azure_endpoint=_derive_azure_endpoint(os.environ["OPENAI_API_BASE"]),api_key=os.environ["OPENAI_API_KEY"],api_version=os.getenv("OPENAI_API_VERSION", "2025-04-01-preview"),)agent = Agent(client=client,name="observability-haiku-agent",description="Writes concise haikus about software observability.",instructions="You write concise haikus about software observability.",default_options={"temperature": float(os.getenv("TEMPERATURE", "0.7")),"conversation_id": str(uuid.uuid4()),},)result = await agent.run("Write a haiku about observability.")print(result.text)for provider in (otel_trace.get_tracer_provider(), otel_metrics.get_meter_provider()):if hasattr(provider, "force_flush"):provider.force_flush()if hasattr(provider, "shutdown"):provider.shutdown()if __name__ == "__main__":asyncio.run(main())
Run the sample with the following command.
python path/to/app.py
The agent then calls Azure OpenAI, writes a haiku about observability, and exports traces and metrics to your Dynatrace environment—all in a single prompt.
Once your agent has run, open
AI Observability to see the data.
observability-haiku-agent.invoke_agent and chat spans and their gen_ai.* attributes.Use the following views to explore the data further, or you can also view spans in Distributed Tracing.
| View | What to look for |
|---|---|
Overview → Response time per model | p99 and mean latency per model (requires metrics endpoint) |
Cost dashboard | Input and output token cost split by model (requires metrics endpoint) |
Prompts | Prompt and completion text, conversation grouping by |
Prompt and completion content (gen_ai.input.messages / gen_ai.output.messages) are captured as span attributes when enable_sensitive_data=True.
They travel with traces and don't require a separate logs endpoint.
Now that you've set up your AI agent to send observability data directly to Dynatrace, you can:
AI Observability to visualize your AI workloads.