Address Copilot comments

This commit is contained in:
Tao Chen
2026-05-14 16:56:29 -07:00
Unverified
parent 07ea764469
commit 13b8e68503
12 changed files with 4092 additions and 3925 deletions
@@ -3,6 +3,7 @@
"""Observability and OpenTelemetry helpers for Agent Framework.
Commonly used exports:
- enable_instrumentation
- enable_sensitive_telemetry
- configure_otel_providers
- AgentTelemetryLayer
@@ -80,6 +81,7 @@ __all__ = [
"configure_otel_providers",
"create_metric_views",
"create_resource",
"enable_instrumentation",
"enable_sensitive_telemetry",
"get_meter",
"get_tracer",
@@ -972,6 +974,27 @@ def enable_sensitive_telemetry() -> None:
OBSERVABILITY_SETTINGS.enable_sensitive_data = True
def enable_instrumentation(
*,
enable_sensitive_data: bool | None = None,
) -> None:
"""Enable instrumentation for Microsoft Agent Framework.
Note that instrumentation is enabled by default, so this method is only necessary
if you need a programmatic way to enable it (e.g., if you are not sure whether the
environment variable ENABLE_INSTRUMENTATION is set to True or False and want to
ensure it is enabled).
Keyword Args:
enable_sensitive_data: Enable OpenTelemetry sensitive events. Overrides
the environment variable ENABLE_SENSITIVE_DATA if set. Default is None.
"""
global OBSERVABILITY_SETTINGS
OBSERVABILITY_SETTINGS.enable_instrumentation = True
if enable_sensitive_data is not None:
OBSERVABILITY_SETTINGS.enable_sensitive_data = enable_sensitive_data
def configure_otel_providers(
*,
enable_sensitive_data: bool | None = None,
@@ -1032,6 +1032,154 @@ def test_enable_sensitive_telemetry_function(monkeypatch):
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is True
def test_enable_instrumentation_function(monkeypatch):
"""Test enable_instrumentation function enables instrumentation when disabled via env."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.setenv("ENABLE_SENSITIVE_DATA", "false")
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is False
observability.enable_instrumentation()
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True
# Sensitive data should remain False when not explicitly enabled
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is False
def test_enable_instrumentation_with_sensitive_data(monkeypatch):
"""Test enable_instrumentation function with explicit sensitive_data parameter."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.setenv("ENABLE_SENSITIVE_DATA", "false")
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
observability.enable_instrumentation(enable_sensitive_data=True)
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is True
def test_enable_instrumentation_explicit_param_overrides_env(monkeypatch):
"""Test that explicit enable_sensitive_data parameter to enable_instrumentation overrides env var."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.setenv("ENABLE_SENSITIVE_DATA", "true")
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
# Explicit False should override the env var True
observability.enable_instrumentation(enable_sensitive_data=False)
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is False
def test_enable_instrumentation_does_not_touch_console_exporters(monkeypatch):
"""Test enable_instrumentation does not modify enable_console_exporters (it is an exporter concern)."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.delenv("ENABLE_CONSOLE_EXPORTERS", raising=False)
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is False
# Simulate load_dotenv() setting env var after import
monkeypatch.setenv("ENABLE_CONSOLE_EXPORTERS", "true")
observability.enable_instrumentation()
# enable_console_exporters is not managed by enable_instrumentation;
# it is only read by configure_otel_providers.
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is False
def test_enable_instrumentation_does_not_clobber_console_exporters(monkeypatch):
"""Test enable_instrumentation does not reset enable_console_exporters set by prior configure call."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.delenv("ENABLE_CONSOLE_EXPORTERS", raising=False)
monkeypatch.delenv("ENABLE_SENSITIVE_DATA", raising=False)
monkeypatch.delenv("VS_CODE_EXTENSION_PORT", raising=False)
for key in [
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
]:
monkeypatch.delenv(key, raising=False)
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
# Set console exporters via configure_otel_providers
with patch.object(observability.OBSERVABILITY_SETTINGS, "_configure"):
observability.configure_otel_providers(enable_console_exporters=True)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
# Calling enable_instrumentation should not clobber the value
observability.enable_instrumentation()
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
def test_enable_instrumentation_with_sensitive_data_does_not_touch_console_exporters(monkeypatch):
"""Test enable_console_exporters is untouched even when enable_sensitive_data is explicitly passed."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.delenv("ENABLE_CONSOLE_EXPORTERS", raising=False)
monkeypatch.delenv("ENABLE_SENSITIVE_DATA", raising=False)
monkeypatch.delenv("VS_CODE_EXTENSION_PORT", raising=False)
for key in [
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
]:
monkeypatch.delenv(key, raising=False)
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
# Set console exporters via configure_otel_providers
with patch.object(observability.OBSERVABILITY_SETTINGS, "_configure"):
observability.configure_otel_providers(enable_console_exporters=True)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
# Calling enable_instrumentation with explicit sensitive_data should not clobber console exporters
observability.enable_instrumentation(enable_sensitive_data=True)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
def test_enable_instrumentation_preserves_console_exporters_after_env_removed(monkeypatch):
"""Test enable_instrumentation preserves enable_console_exporters when env var is removed after reload."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.setenv("ENABLE_CONSOLE_EXPORTERS", "true")
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
# Remove the env var after reload
monkeypatch.delenv("ENABLE_CONSOLE_EXPORTERS", raising=False)
# enable_instrumentation should not reset the value
observability.enable_instrumentation()
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
def test_configure_otel_providers_reads_env_sensitive_data(monkeypatch):
"""Test configure_otel_providers re-reads ENABLE_SENSITIVE_DATA from os.environ when not explicitly passed."""
import importlib
@@ -94,6 +94,7 @@ def serve(
auto_open: bool = False,
cors_origins: list[str] | None = None,
ui_enabled: bool = True,
instrumentation_enabled: bool = False,
mode: str = "developer",
auth_enabled: bool = True,
auth_token: str | None = None,
@@ -108,6 +109,7 @@ def serve(
auto_open: Whether to automatically open browser
cors_origins: List of allowed CORS origins
ui_enabled: Whether to enable the UI
instrumentation_enabled: Whether to enable OpenTelemetry instrumentation
mode: Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors)
auth_enabled: Whether to enable Bearer token authentication
auth_token: Custom authentication token (auto-generated if not provided with auth_enabled=True)
@@ -124,6 +126,13 @@ def serve(
if not isinstance(port, int) or not (1 <= port <= 65535):
raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535")
# Enable instrumentation if requested
if instrumentation_enabled:
from agent_framework.observability import enable_instrumentation
enable_instrumentation(enable_sensitive_data=True)
logger.info("Enabled Agent Framework instrumentation with sensitive data")
# Create server with direct parameters
server = DevServer(
entities_dir=entities_dir,
@@ -98,7 +98,7 @@ class AgentFrameworkExecutor:
try:
from agent_framework.observability import OBSERVABILITY_SETTINGS, configure_otel_providers
# Configure if instrumentation is enabled (on by default, via env var, or enable_sensitive_telemetry())
# Configure if instrumentation is enabled (via enable_instrumentation() or env var)
if OBSERVABILITY_SETTINGS.ENABLED:
# Call configure_otel_providers to set up exporters.
# If OTEL_EXPORTER_OTLP_ENDPOINT is set, exporters will be created automatically.
@@ -518,7 +518,7 @@ class DevServer:
framework="agent_framework",
runtime="python", # Python DevUI backend
capabilities={
"instrumentation": os.getenv("ENABLE_INSTRUMENTATION", "true").lower() != "false",
"instrumentation": os.getenv("ENABLE_INSTRUMENTATION") == "true",
"openai_proxy": openai_executor.is_configured,
"deployment": True, # Deployment feature is available
},
@@ -248,8 +248,8 @@ services:
- AZURE_OPENAI_API_KEY=\${AZURE_OPENAI_API_KEY}
- AZURE_OPENAI_ENDPOINT=\${AZURE_OPENAI_ENDPOINT}
- AZURE_OPENAI_MODEL=\${AZURE_OPENAI_MODEL}
# Optional: Disable instrumentation (enabled by default)
- ENABLE_INSTRUMENTATION=\${ENABLE_INSTRUMENTATION:-true}
# Optional: Enable instrumentation
- ENABLE_INSTRUMENTATION=\${ENABLE_INSTRUMENTATION:-false}
ports:
- "8080:8080"
restart: unless-stopped
@@ -817,7 +817,7 @@ class RawFoundryAgent( # type: ignore[misc]
"Install it with: pip install azure-monitor-opentelemetry"
) from exc
from agent_framework.observability import create_metric_views, create_resource, enable_sensitive_telemetry
from agent_framework.observability import create_metric_views, create_resource, enable_instrumentation
if "resource" not in kwargs:
kwargs["resource"] = create_resource()
@@ -828,8 +828,7 @@ class RawFoundryAgent( # type: ignore[misc]
**kwargs,
)
if enable_sensitive_data:
enable_sensitive_telemetry()
enable_instrumentation(enable_sensitive_data=enable_sensitive_data)
class FoundryAgent( # type: ignore[misc]
@@ -291,7 +291,7 @@ class RawFoundryChatClient( # type: ignore[misc]
"Install it with: pip install azure-monitor-opentelemetry"
) from exc
from agent_framework.observability import create_metric_views, create_resource, enable_sensitive_telemetry
from agent_framework.observability import create_metric_views, create_resource, enable_instrumentation
if "resource" not in kwargs:
kwargs["resource"] = create_resource()
@@ -302,8 +302,7 @@ class RawFoundryChatClient( # type: ignore[misc]
**kwargs,
)
if enable_sensitive_data:
enable_sensitive_telemetry()
enable_instrumentation(enable_sensitive_data=enable_sensitive_data)
# region Tool factory methods (override OpenAI defaults with Foundry versions)
@@ -699,7 +699,7 @@ async def test_foundry_agent_configure_azure_monitor() -> None:
),
patch("agent_framework.observability.create_metric_views", mock_views),
patch("agent_framework.observability.create_resource", return_value=mock_resource),
patch("agent_framework.observability.enable_sensitive_telemetry", mock_enable),
patch("agent_framework.observability.enable_instrumentation", mock_enable),
):
await agent.configure_azure_monitor(enable_sensitive_data=True)
@@ -708,7 +708,7 @@ async def test_foundry_agent_configure_azure_monitor() -> None:
assert call_kwargs["connection_string"] == "InstrumentationKey=test-key;IngestionEndpoint=https://test.endpoint"
assert call_kwargs["views"] == []
assert call_kwargs["resource"] is mock_resource
mock_enable.assert_called_once()
mock_enable.assert_called_once_with(enable_sensitive_data=True)
async def test_foundry_agent_configure_azure_monitor_resource_not_found() -> None:
@@ -249,7 +249,7 @@ async def test_configure_azure_monitor() -> None:
),
patch("agent_framework.observability.create_metric_views", mock_views),
patch("agent_framework.observability.create_resource", return_value=mock_resource),
patch("agent_framework.observability.enable_sensitive_telemetry", mock_enable),
patch("agent_framework.observability.enable_instrumentation", mock_enable),
):
await client.configure_azure_monitor(enable_sensitive_data=True)
@@ -259,7 +259,7 @@ async def test_configure_azure_monitor() -> None:
assert call_kwargs["connection_string"] == "InstrumentationKey=test-key;IngestionEndpoint=https://test.endpoint"
assert call_kwargs["views"] == []
assert call_kwargs["resource"] is mock_resource
mock_enable.assert_called_once()
mock_enable.assert_called_once_with(enable_sensitive_data=True)
async def test_configure_azure_monitor_resource_not_found() -> None:
@@ -324,7 +324,7 @@ async def test_configure_azure_monitor_with_custom_resource() -> None:
),
patch("agent_framework.observability.create_metric_views", return_value=[]),
patch("agent_framework.observability.create_resource") as mock_create_resource,
patch("agent_framework.observability.enable_sensitive_telemetry"),
patch("agent_framework.observability.enable_instrumentation"),
):
await client.configure_azure_monitor(resource=custom_resource)
@@ -6,6 +6,7 @@ from __future__ import annotations
import importlib.metadata
from agent_framework.observability import enable_instrumentation
from agentlightning.tracer import ( # type: ignore[reportMissingImports]
AgentOpsTracer, # type: ignore[reportMissingImports, import-not-found]
)
@@ -25,6 +26,7 @@ class AgentFrameworkTracer(AgentOpsTracer): # type: ignore
def init(self) -> None:
"""Initialize the agent-framework-lab-lightning for training."""
enable_instrumentation()
super().init() # pyright: ignore[reportUnknownMemberType]
def teardown(self) -> None:
+3897 -3910
View File
File diff suppressed because it is too large Load Diff