diff --git a/README.md b/README.md index 5fbbb8d8..945f3ef1 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ See the [A365 guide](A365_DOCUMENTATION.md) for A365-specific configuration. | `instrumentation_options` | `dict` | `None` | Per-library instrumentation enable/disable options. | | `enable_trace_based_sampling_for_logs` | `bool` | `False` | Enable trace-based sampling for logs. | | `enable_console` | `bool` | `False` | Console exporter (dev only). Auto-enables when no other exporter is active. | +| `enable_sensitive_data` | `bool` | `False` | Enable sensitive data recording (prompts, tool arguments, results) for Agent Framework SDK instrumentation. | | **Azure Monitor** | | | | | `enable_azure_monitor` | `bool` | `False` | Enable Azure Monitor export. | | `azure_monitor_connection_string` | `str` | `None` | Connection string. Also read from `APPLICATIONINSIGHTS_CONNECTION_STRING`. | diff --git a/src/microsoft/opentelemetry/_agent_framework/_trace_instrumentor.py b/src/microsoft/opentelemetry/_agent_framework/_trace_instrumentor.py index f8651a8d..9e87068d 100644 --- a/src/microsoft/opentelemetry/_agent_framework/_trace_instrumentor.py +++ b/src/microsoft/opentelemetry/_agent_framework/_trace_instrumentor.py @@ -35,10 +35,11 @@ def instrumentation_dependencies(self) -> Collection[str]: def _instrument(self, **kwargs: Any) -> None: # Enable the Agent Framework SDK's built-in span generation so users # don't need to call enable_instrumentation() manually. + enable_sensitive_data = kwargs.get("enable_sensitive_data", False) try: from agent_framework.observability import enable_instrumentation - enable_instrumentation() + enable_instrumentation(enable_sensitive_data=enable_sensitive_data) self._af_instrumentation_enabled = True except ImportError as exc: _logger.debug( diff --git a/src/microsoft/opentelemetry/_constants.py b/src/microsoft/opentelemetry/_constants.py index b1047c77..ce61c90d 100644 --- a/src/microsoft/opentelemetry/_constants.py +++ b/src/microsoft/opentelemetry/_constants.py @@ -86,6 +86,8 @@ # --- Spectra Sidecar Constants --- +ENABLE_SENSITIVE_DATA_ARG = "enable_sensitive_data" + ENABLE_SPECTRA_ARG = "enable_spectra" SPECTRA_ENDPOINT_ARG = "spectra_endpoint" SPECTRA_PROTOCOL_ARG = "spectra_protocol" diff --git a/src/microsoft/opentelemetry/_distro.py b/src/microsoft/opentelemetry/_distro.py index e181034d..ae96cf54 100644 --- a/src/microsoft/opentelemetry/_distro.py +++ b/src/microsoft/opentelemetry/_distro.py @@ -29,6 +29,7 @@ DISABLE_METRICS_ARG, DISABLE_TRACING_ARG, ENABLE_A365_ARG, + ENABLE_SENSITIVE_DATA_ARG, ENABLE_SPECTRA_ARG, SPECTRA_ENDPOINT_ARG, SPECTRA_PROTOCOL_ARG, @@ -186,6 +187,9 @@ def use_microsoft_opentelemetry(**kwargs: object) -> None: # pylint: disable=to ``SPECTRA_PROTOCOL`` env var. Defaults to ``"grpc"``. :keyword bool spectra_insecure: Use insecure (no TLS) connection. Defaults to True (localhost sidecar). + :keyword bool enable_sensitive_data: + Enable sensitive data recording (prompts, tool arguments, results) for + the Agent Framework SDK instrumentation. Defaults to False. :rtype: None """ @@ -209,6 +213,8 @@ def use_microsoft_opentelemetry(**kwargs: object) -> None: # pylint: disable=to spectra_protocol = kwargs.pop(SPECTRA_PROTOCOL_ARG, None) spectra_insecure = kwargs.pop(SPECTRA_INSECURE_ARG, None) + enable_sensitive_data: bool = bool(kwargs.pop(ENABLE_SENSITIVE_DATA_ARG, False)) + # Separate Azure Monitor kwargs from generic OTel kwargs otel_kwargs: Dict[str, Any] = {k: v for k, v in kwargs.items() if k not in _AZURE_MONITOR_KWARG_MAP} azure_monitor_kwargs: Dict[str, Any] = { @@ -311,7 +317,7 @@ def use_microsoft_opentelemetry(**kwargs: object) -> None: # pylint: disable=to set_logger_provider(logger_provider) # ---- Instrumentations (always, after providers are set) ---- - _setup_instrumentations(otel_kwargs) + _setup_instrumentations(otel_kwargs, **{ENABLE_SENSITIVE_DATA_ARG: enable_sensitive_data}) # ---- SDKStats manager (after providers, before returning) ---- _initialize_sdkstats(enable_azure_monitor) @@ -670,7 +676,7 @@ def _is_instrumentation_enabled(otel_kwargs: Dict[str, Any], lib_name: str) -> b return lib_options["enabled"] is True -def _setup_instrumentations(otel_kwargs: Dict[str, Any]) -> None: +def _setup_instrumentations(otel_kwargs: Dict[str, Any], **kwargs: Any) -> None: """Discover and activate OTel instrumentations for supported libraries.""" entry_point_finder = _EntryPointDistFinder() for entry_point in entry_points(group="opentelemetry_instrumentor"): @@ -691,7 +697,7 @@ def _setup_instrumentations(otel_kwargs: Dict[str, Any]) -> None: ) continue instrumentor: Any = entry_point.load() - instrumentor().instrument(skip_dep_check=True) + instrumentor().instrument(skip_dep_check=True, **kwargs) set_sdkstats_instrumentation_by_name(lib_name) except Exception as ex: # pylint: disable=broad-except _logger.warning( diff --git a/tests/agent_framework/test_trace_instrumentor.py b/tests/agent_framework/test_trace_instrumentor.py index 95d5ee88..af95175f 100644 --- a/tests/agent_framework/test_trace_instrumentor.py +++ b/tests/agent_framework/test_trace_instrumentor.py @@ -88,7 +88,27 @@ def test_instrument_calls_enable_instrumentation_when_available(self, mock_get_p instrumentor = AgentFrameworkInstrumentor() instrumentor._instrument() - mock_enable.assert_called_once() + mock_enable.assert_called_once_with(enable_sensitive_data=False) + self.assertTrue(instrumentor._af_instrumentation_enabled) + + @patch("microsoft.opentelemetry._agent_framework._trace_instrumentor.get_tracer_provider") + def test_instrument_enables_sensitive_data_when_kwarg_set(self, mock_get_provider): + """When enable_sensitive_data=True is passed as kwarg, + enable_instrumentation must be called with enable_sensitive_data=True.""" + mock_get_provider.return_value = MagicMock() + mock_enable = MagicMock() + + with patch.dict( + "sys.modules", + { + "agent_framework": MagicMock(), + "agent_framework.observability": MagicMock(enable_instrumentation=mock_enable), + }, + ): + instrumentor = AgentFrameworkInstrumentor() + instrumentor._instrument(enable_sensitive_data=True) + + mock_enable.assert_called_once_with(enable_sensitive_data=True) self.assertTrue(instrumentor._af_instrumentation_enabled) @patch("microsoft.opentelemetry._agent_framework._trace_instrumentor.get_tracer_provider") @@ -188,7 +208,7 @@ def test_enable_instrumentation_called_in_azure_monitor_only_scenario(self, mock instrumentor._instrument() # AF SDK enabled, span processor added, enricher NOT registered. - mock_enable.assert_called_once() + mock_enable.assert_called_once_with(enable_sensitive_data=False) self.assertTrue(instrumentor._af_instrumentation_enabled) mock_provider.add_span_processor.assert_called_once() self.assertFalse(instrumentor._owns_enricher)