diff --git a/python/packages/foundry/README.md b/python/packages/foundry/README.md index 94046a9ed36..457a22d100c 100644 --- a/python/packages/foundry/README.md +++ b/python/packages/foundry/README.md @@ -8,6 +8,19 @@ This package supports `azure-ai-projects>=2.2.0,<2.7.0`. Projects 2.5 and later `openai>=3.0.0`, so `agent-framework-foundry` requires `agent-framework-openai>=1.14.2`, which supports both OpenAI 2.x and 3.x. +## Tracing an existing Foundry agent + +Install Azure Monitor to connect client and service traces: + +```shell +pip install --upgrade agent-framework-foundry "azure-monitor-opentelemetry>=1.8.10,<2" +``` + +With Application Insights connected to your project, call +`await agent.configure_azure_monitor()` before invoking a `FoundryAgent`. +See [the tracing sample](../../samples/02-agents/observability/foundry_agent_tracing.py) +for streaming and non-streaming examples. + ## Evaluations `FoundryEvals` implements the provider-neutral `Evaluator` protocol with diff --git a/python/packages/foundry/agent_framework_foundry/_agent.py b/python/packages/foundry/agent_framework_foundry/_agent.py index 393a7109385..43374b85d29 100644 --- a/python/packages/foundry/agent_framework_foundry/_agent.py +++ b/python/packages/foundry/agent_framework_foundry/_agent.py @@ -856,6 +856,8 @@ async def configure_azure_monitor( This method configures Azure Monitor for telemetry collection using the connection string from the Foundry project client (accessed via the internal client). + Use azure-monitor-opentelemetry>=1.8.10,<2 for HTTPX/HTTPX2 + auto-instrumentation that connects client and service traces. Args: enable_sensitive_data: Enable sensitive data logging (prompts, responses). @@ -863,7 +865,7 @@ async def configure_azure_monitor( **kwargs: Additional arguments passed to configure_azure_monitor(). Raises: - ImportError: If azure-monitor-opentelemetry-exporter is not installed. + ImportError: If azure-monitor-opentelemetry is not installed. """ from agent_framework.observability import ( OBSERVABILITY_SETTINGS, @@ -900,7 +902,7 @@ async def configure_azure_monitor( except ImportError as exc: raise ImportError( "azure-monitor-opentelemetry is required for Azure Monitor integration. " - "Install it with: pip install azure-monitor-opentelemetry" + 'Install it with: pip install "azure-monitor-opentelemetry>=1.8.10,<2"' ) from exc if "resource" not in kwargs: diff --git a/python/packages/foundry/agent_framework_foundry/_chat_client.py b/python/packages/foundry/agent_framework_foundry/_chat_client.py index 6c0539bb981..8e42d56249c 100644 --- a/python/packages/foundry/agent_framework_foundry/_chat_client.py +++ b/python/packages/foundry/agent_framework_foundry/_chat_client.py @@ -308,6 +308,8 @@ async def configure_azure_monitor( This method configures Azure Monitor for telemetry collection using the connection string from the Foundry project client. + Use azure-monitor-opentelemetry>=1.8.10,<2 for HTTPX/HTTPX2 + auto-instrumentation that connects client and service traces. Args: enable_sensitive_data: Enable sensitive data logging (prompts, responses). @@ -319,7 +321,7 @@ async def configure_azure_monitor( - resource (Resource): Custom OpenTelemetry resource Raises: - ImportError: If azure-monitor-opentelemetry-exporter is not installed. + ImportError: If azure-monitor-opentelemetry is not installed. """ from agent_framework.observability import ( OBSERVABILITY_SETTINGS, @@ -352,7 +354,7 @@ async def configure_azure_monitor( except ImportError as exc: raise ImportError( "azure-monitor-opentelemetry is required for Azure Monitor integration. " - "Install it with: pip install azure-monitor-opentelemetry" + 'Install it with: pip install "azure-monitor-opentelemetry>=1.8.10,<2"' ) from exc if "resource" not in kwargs: diff --git a/python/pyproject.toml b/python/pyproject.toml index 58b1cefe148..1c762ead681 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -51,7 +51,7 @@ dev = [ "griffe==2.3.0", ] test = [ - "azure-monitor-opentelemetry", + "azure-monitor-opentelemetry>=1.8.10,<2", "mcp[ws]", # Optional SDK used by the agent-hooks tests and isolated source checks. "agent-hooks-sdk>=0.1.0a4,<0.2", diff --git a/python/samples/02-agents/observability/README.md b/python/samples/02-agents/observability/README.md index e69af64f684..903dc7982b4 100644 --- a/python/samples/02-agents/observability/README.md +++ b/python/samples/02-agents/observability/README.md @@ -115,7 +115,8 @@ configure_azure_monitor( enable_sensitive_telemetry() ``` -For Microsoft Foundry projects, use `client.configure_azure_monitor()` which retrieves the connection string from the project and configures everything: +For model calls through `FoundryChatClient`, use `client.configure_azure_monitor()` +to retrieve the connection string and configure Azure Monitor: ```python from agent_framework.foundry import FoundryChatClient @@ -131,6 +132,29 @@ client = FoundryChatClient( await client.configure_azure_monitor(enable_sensitive_data=True) ``` +For calls to an **existing prompt or hosted agent**, use +[`foundry_agent_tracing.py`](foundry_agent_tracing.py) and +`await agent.configure_azure_monitor()`. + +Install Azure Monitor 1.8.10 or later to connect client and service traces: + +```shell +pip install --upgrade "azure-monitor-opentelemetry>=1.8.10,<2" +``` + +Connect Application Insights to your project and set `FOUNDRY_PROJECT_ENDPOINT` +and `FOUNDRY_AGENT_NAME`. `FOUNDRY_AGENT_VERSION` is required for PromptAgents +and optional for HostedAgents. Run from `python/` using the workspace packages: + +```powershell +uv run --group test python samples\02-agents\observability\foundry_agent_tracing.py +uv run --group test python samples\02-agents\observability\foundry_agent_tracing.py --stream +``` + +View the connected trace under **Build > Agents > your agent > Traces** in +Foundry. Select the agent version and a time range covering the run, then open +the printed trace ID. + Or with [Langfuse](https://langfuse.com/integrations/frameworks/microsoft-agent-framework): ```python diff --git a/python/samples/02-agents/observability/foundry_agent_tracing.py b/python/samples/02-agents/observability/foundry_agent_tracing.py new file mode 100644 index 00000000000..83c6cf64bcf --- /dev/null +++ b/python/samples/02-agents/observability/foundry_agent_tracing.py @@ -0,0 +1,98 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "agent-framework-foundry", +# "azure-monitor-opentelemetry>=1.8.10,<2", +# ] +# /// +# Run from python/ with the workspace environment: +# uv run --group test python samples/02-agents/observability/foundry_agent_tracing.py + +# Copyright (c) Microsoft. All rights reserved. + +from __future__ import annotations + +import argparse +import asyncio +import os + +from agent_framework.foundry import FoundryAgent +from agent_framework.observability import get_tracer +from azure.identity.aio import AzureCliCredential +from dotenv import load_dotenv +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider + +""" +Trace calls to an existing Foundry agent, including the client and service spans. + +Azure Monitor 1.8.10 or later instruments HTTPX and HTTPX2, which the OpenAI SDK +uses to send requests. The resulting traceparent header connects client spans +to Foundry's service spans. Older Azure Monitor versions can export the client +spans to Application Insights while leaving them in a separate trace. + +The helper configures Azure Monitor using the project's connected Application +Insights resource. No project ARM ID or custom span attributes are needed. +Add --stream for streaming output. Message-content recording remains disabled. + +Environment variables: + FOUNDRY_PROJECT_ENDPOINT -- Foundry project endpoint. + FOUNDRY_AGENT_NAME -- Existing prompt or hosted agent name. + FOUNDRY_AGENT_VERSION -- Required for PromptAgents; optional for HostedAgents. + +After running, open Build > Agents > your agent > Traces in Foundry. Select the +agent version and a time range covering the run, then open the printed trace ID. +The waterfall should contain this application's parent span, client invoke_agent +and chat spans, the HTTP request, and the service spans in one connected tree. +The sample does not create or delete the agent, so it remains available for inspection. +""" + +load_dotenv() + + +async def main() -> None: + parser = argparse.ArgumentParser(description="Trace client and service calls to an existing Foundry agent.") + parser.add_argument("--stream", action="store_true", help="Stream the agent response.") + args = parser.parse_args() + + # 1. Connect to an existing agent without changing its definition. + async with ( + AzureCliCredential() as credential, + FoundryAgent( + project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], + agent_name=os.environ["FOUNDRY_AGENT_NAME"], + agent_version=os.getenv("FOUNDRY_AGENT_VERSION"), + credential=credential, + ) as agent, + ): + # 2. Configure export and HTTP trace-context propagation before invoking the agent. + await agent.configure_azure_monitor(enable_live_metrics=False) + + # 3. Group the client operation beneath an application span. + with get_tracer().start_as_current_span("foundry-agent-tracing") as span: + print(f"Trace ID: {span.get_span_context().trace_id:032x}") + if args.stream: + result_stream = agent.run("Say hello in one sentence.", stream=True) + async for update in result_stream: + if update.text: + print(update.text, end="", flush=True) + print() + response = await result_stream.get_final_response() + else: + response = await agent.run("Say hello in one sentence.") + print(response.text) + print(f"Agent response ID: {response.response_id}") + + # 4. Flush before exit; exporting does not by itself prove Foundry portal discovery. + provider = trace.get_tracer_provider() + if isinstance(provider, TracerProvider) and not provider.force_flush(): + raise TimeoutError("Trace export did not finish before the flush timeout.") + + +if __name__ == "__main__": + asyncio.run(main()) + +# Example output: +# Trace ID: +# Hello! How can I help you today? +# Agent response ID: diff --git a/python/samples/02-agents/observability/foundry_tracing.py b/python/samples/02-agents/observability/foundry_tracing.py index 7cefa111d9a..f72b1aa345a 100644 --- a/python/samples/02-agents/observability/foundry_tracing.py +++ b/python/samples/02-agents/observability/foundry_tracing.py @@ -2,7 +2,7 @@ # requires-python = ">=3.10" # dependencies = [ # "agent-framework-foundry", -# "azure-monitor-opentelemetry", +# "azure-monitor-opentelemetry>=1.8.10,<2", # ] # /// # Run with any PEP 723 compatible runner, e.g.: @@ -29,6 +29,10 @@ This sample shows how to setup telemetry in Microsoft Foundry for a custom agent using ``FoundryChatClient.configure_azure_monitor()``. +For an existing Foundry prompt or hosted agent, see ``foundry_agent_tracing.py`` +instead. Azure Monitor 1.8.10 or later instruments the HTTP transport so client +and service spans can share a trace. + First ensure you have a Foundry workspace with Application Insights enabled. And use the Operate tab to Register an Agent. Set the OpenTelemetry agent ID to the value used below in the Agent creation: ``weather-agent`` diff --git a/python/samples/02-agents/providers/foundry/README.md b/python/samples/02-agents/providers/foundry/README.md index 606b81af0ef..f4253d98861 100644 --- a/python/samples/02-agents/providers/foundry/README.md +++ b/python/samples/02-agents/providers/foundry/README.md @@ -10,6 +10,7 @@ This folder contains Microsoft Foundry and Foundry Local samples for Agent Frame | [`foundry_agent_custom_client.py`](foundry_agent_custom_client.py) | Foundry Agent custom client configuration | | [`foundry_agent_hosted.py`](foundry_agent_hosted.py) | Foundry Agent for hosted agents | | [`foundry_agent_with_function_tools.py`](foundry_agent_with_function_tools.py) | Foundry Agent with local function tools | +| [`foundry_agent_tracing.py`](../../observability/foundry_agent_tracing.py) | Connected client and service traces for an existing Foundry agent, with optional streaming | ## FoundryChatClient Samples diff --git a/python/uv.lock b/python/uv.lock index 6d615f5919e..560483c30d3 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -180,7 +180,7 @@ dev = [ ] test = [ { name = "agent-hooks-sdk", specifier = ">=0.1.0a4,<0.2" }, - { name = "azure-monitor-opentelemetry" }, + { name = "azure-monitor-opentelemetry", specifier = ">=1.8.10,<2" }, { name = "mcp", extras = ["ws"] }, ]