Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
dict/string payloads. Execute-tool payload serialization is non-throwing: unserializable payloads record
`{"serialization_error": "Failed to serialize execute tool payload."}` instead of failing the span.
- Add explicit custom baggage APIs and propagate opted-in attributes to supported GenAI spans, including unmodeled operations. ([#264](https://github.com/microsoft/opentelemetry-distro-python/pull/264))
- Enable Agent Framework GenAI message events by default when supported, with
an `instrumentation_options["agent_framework"]["enable_message_events"]`
override.
Comment thread
hectorhdzg marked this conversation as resolved.
([#270](https://github.com/microsoft/opentelemetry-distro-python/pull/270))
- Add Python-native `InvokeAgentScope` request and response parameter models
that emit OpenTelemetry GenAI semantic attributes, including structured
system instructions and cache read/write token counts, introduced by .NET
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,26 @@ Toggle individual instrumentations:

```python
use_microsoft_opentelemetry(
enable_sensitive_data=True,
instrumentation_options={
"flask": {"enabled": False},
"openai": {"enabled": True},
"agent_framework": {
"enabled": True,
"enable_message_events": True,
"force": True,
},
},
)
```

For Agent Framework, `enable_sensitive_data` controls prompt, completion,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They’re separate settings, but message events are only emitted when sensitive-data capture is also enabled. So setting enable_message_events=True by itself won’t emit message content if enable_sensitive_data=False

tool argument, and tool result capture. Message events are enabled by default
when the installed Agent Framework version supports them and can be disabled
with `instrumentation_options["agent_framework"]["enable_message_events"]`.
Set `instrumentation_options["agent_framework"]["force"]` to `True` to
re-enable Agent Framework instrumentation after it was previously disabled.

### Default Instrumentations When `enable_a365=True`

Microsoft OpenTelemetry **automatically disables the
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dependencies = [

[project.optional-dependencies]
agent-framework = [
"agent-framework>=1.4.0",
"agent-framework>=1.19.0",
]
openai-agents = [
"openai-agents>=0.0.7",
Expand Down
5 changes: 5 additions & 0 deletions samples/microsoft_agent_framework/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ Microsoft Agent Framework is natively instrumented with OpenTelemetry, so you ca
```bash
python sample_maf_agent.py
```

The sample enables sensitive-data capture and Agent Framework GenAI message
events. Sensitive data can include prompts, completions, tool arguments, and
tool results. Review your data-handling requirements before enabling it in
production.
2 changes: 1 addition & 1 deletion samples/microsoft_agent_framework/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
agent-framework-core
agent-framework-core>=1.19.0
agent-framework-foundry
microsoft-opentelemetry
10 changes: 9 additions & 1 deletion samples/microsoft_agent_framework/sample_maf_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ async def get_weather(
async def main():
# Set up Azure monitor exporters for telemetry
# This will automatically enable instrumentation for Agent Framework
use_microsoft_opentelemetry(enable_azure_monitor=True)
use_microsoft_opentelemetry(
enable_azure_monitor=True,
enable_sensitive_data=True,
instrumentation_options={
"agent_framework": {
"enable_message_events": True,
},
},
)

questions = [
"What's the weather in Amsterdam?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import logging
from collections.abc import Collection
from inspect import Parameter, signature
from typing import Any

from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore[attr-defined]
Expand All @@ -13,7 +14,7 @@
from microsoft.opentelemetry._agent_framework._span_processor import AgentFrameworkSpanProcessor

_logger = logging.getLogger(__name__)
_instruments = ("agent-framework >= 1.0.0",)
_instruments = ("agent-framework >= 1.19.0",)


class AgentFrameworkInstrumentor(BaseInstrumentor):
Expand All @@ -36,10 +37,24 @@ 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)
enable_message_events = kwargs.get("enable_message_events", True)
force = kwargs.get("force", False)
try:
from agent_framework.observability import enable_instrumentation

enable_instrumentation(enable_sensitive_data=enable_sensitive_data)
enable_kwargs = {"enable_sensitive_data": enable_sensitive_data}
parameters = signature(enable_instrumentation).parameters
accepts_kwargs = any(parameter.kind == Parameter.VAR_KEYWORD for parameter in parameters.values())
optional_kwargs = {
"enable_message_events": enable_message_events,
"force": force,
}
for name, value in optional_kwargs.items():
if name in parameters or accepts_kwargs:
enable_kwargs[name] = value
else:
_logger.debug("Agent Framework SDK does not support configuring %s.", name)
enable_instrumentation(**enable_kwargs)
self._af_instrumentation_enabled = True
except ImportError as exc:
_logger.debug(
Expand Down
63 changes: 60 additions & 3 deletions tests/agent_framework/test_trace_instrumentor.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def test_instrument_calls_enable_instrumentation_when_available(self, mock_get_p
instrumentor = AgentFrameworkInstrumentor()
instrumentor._instrument()

mock_enable.assert_called_once_with(enable_sensitive_data=False)
mock_enable.assert_called_once_with(enable_sensitive_data=False, enable_message_events=True, force=False)
self.assertTrue(instrumentor._af_instrumentation_enabled)

@patch("microsoft.opentelemetry._agent_framework._trace_instrumentor.get_tracer_provider")
Expand All @@ -114,7 +114,64 @@ def test_instrument_enables_sensitive_data_when_kwarg_set(self, mock_get_provide
instrumentor = AgentFrameworkInstrumentor()
instrumentor._instrument(enable_sensitive_data=True)

mock_enable.assert_called_once_with(enable_sensitive_data=True)
mock_enable.assert_called_once_with(enable_sensitive_data=True, enable_message_events=True, force=False)
self.assertTrue(instrumentor._af_instrumentation_enabled)

@patch("microsoft.opentelemetry._agent_framework._trace_instrumentor.get_tracer_provider")
def test_instrument_can_disable_message_events(self, mock_get_provider):
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_message_events=False)

mock_enable.assert_called_once_with(enable_sensitive_data=False, enable_message_events=False, force=False)
self.assertTrue(instrumentor._af_instrumentation_enabled)

@patch("microsoft.opentelemetry._agent_framework._trace_instrumentor.get_tracer_provider")
def test_instrument_can_force_reenable_instrumentation(self, mock_get_provider):
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(force=True)

mock_enable.assert_called_once_with(enable_sensitive_data=False, enable_message_events=True, force=True)
self.assertTrue(instrumentor._af_instrumentation_enabled)

@patch("microsoft.opentelemetry._agent_framework._trace_instrumentor.get_tracer_provider")
def test_instrument_supports_legacy_enable_instrumentation_signature(self, mock_get_provider):
mock_get_provider.return_value = MagicMock()
mock_enable = MagicMock()

def legacy_enable_instrumentation(*, enable_sensitive_data=None, force=False):
mock_enable(enable_sensitive_data=enable_sensitive_data, force=force)

with patch.dict(
"sys.modules",
{
"agent_framework": MagicMock(),
"agent_framework.observability": MagicMock(enable_instrumentation=legacy_enable_instrumentation),
},
):
instrumentor = AgentFrameworkInstrumentor()
instrumentor._instrument(enable_sensitive_data=True)

mock_enable.assert_called_once_with(enable_sensitive_data=True, force=False)
self.assertTrue(instrumentor._af_instrumentation_enabled)

@patch("microsoft.opentelemetry._agent_framework._trace_instrumentor.get_tracer_provider")
Expand Down Expand Up @@ -214,7 +271,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_with(enable_sensitive_data=False)
mock_enable.assert_called_once_with(enable_sensitive_data=False, enable_message_events=True, force=False)
self.assertTrue(instrumentor._af_instrumentation_enabled)
mock_provider.add_span_processor.assert_called_once()
self.assertFalse(instrumentor._owns_enricher)
Expand Down
6 changes: 5 additions & 1 deletion tests/test_agent_framework_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ def test_enable_sensitive_data_kwarg(self, mock_get_provider):
with patch.dict("sys.modules", {"agent_framework.observability": mock_af_obs}):
inst = AgentFrameworkInstrumentor()
inst._instrument(enable_sensitive_data=True)
mock_af_obs.enable_instrumentation.assert_called_once_with(enable_sensitive_data=True)
mock_af_obs.enable_instrumentation.assert_called_once_with(
enable_sensitive_data=True,
enable_message_events=True,
force=False,
)


class TestAgentFrameworkSpanProcessor(unittest.TestCase):
Expand Down
Loading