mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Optimization when span is not recording
This commit is contained in:
@@ -1275,7 +1275,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
if stream:
|
||||
span = _start_streaming_span(attributes, OtelAttr.REQUEST_MODEL)
|
||||
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
_capture_messages(
|
||||
span=span,
|
||||
provider_name=provider_name,
|
||||
@@ -1339,6 +1339,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED
|
||||
and isinstance(response, ChatResponse)
|
||||
and response.messages
|
||||
and span.is_recording()
|
||||
):
|
||||
_capture_messages(
|
||||
span=span,
|
||||
@@ -1369,7 +1370,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
|
||||
async def _get_response() -> ChatResponse:
|
||||
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.REQUEST_MODEL) as span:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
_capture_messages(
|
||||
span=span,
|
||||
provider_name=provider_name,
|
||||
@@ -1403,7 +1404,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
duration=duration,
|
||||
)
|
||||
_mark_inner_response_telemetry_captured(response)
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and response.messages:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and response.messages and span.is_recording():
|
||||
finish_reason = cast(
|
||||
"FinishReason | None",
|
||||
response.finish_reason if response.finish_reason in FINISH_REASON_MAP else None,
|
||||
@@ -1547,7 +1548,7 @@ class AgentTelemetryLayer:
|
||||
if stream:
|
||||
span = _start_streaming_span(attributes, OtelAttr.AGENT_NAME)
|
||||
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
_capture_messages(
|
||||
span=span,
|
||||
provider_name=provider_name,
|
||||
@@ -1608,6 +1609,7 @@ class AgentTelemetryLayer:
|
||||
OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED
|
||||
and isinstance(response, AgentResponse)
|
||||
and response.messages
|
||||
and span.is_recording()
|
||||
):
|
||||
_capture_messages(
|
||||
span=span,
|
||||
@@ -1640,7 +1642,7 @@ class AgentTelemetryLayer:
|
||||
async def _run() -> AgentResponse[Any]:
|
||||
try:
|
||||
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.AGENT_NAME) as span:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages and span.is_recording():
|
||||
_capture_messages(
|
||||
span=span,
|
||||
provider_name=provider_name,
|
||||
@@ -1664,7 +1666,7 @@ class AgentTelemetryLayer:
|
||||
)
|
||||
_apply_accumulated_usage(response_attributes, inner_response_telemetry_captured_fields)
|
||||
_capture_response(span=span, attributes=response_attributes, duration=duration)
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and response.messages:
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and response.messages and span.is_recording():
|
||||
_capture_messages(
|
||||
span=span,
|
||||
provider_name=provider_name,
|
||||
|
||||
@@ -3715,3 +3715,135 @@ async def test_agent_streaming_execute_failure_closes_span_and_resets_contextvar
|
||||
agent_spans = [s for s in spans if s.attributes.get(OtelAttr.OPERATION.value) == OtelAttr.AGENT_INVOKE_OPERATION]
|
||||
assert len(agent_spans) == 1
|
||||
assert agent_spans[0].status.status_code == StatusCode.ERROR
|
||||
|
||||
|
||||
# region Test heavy operations skipped when span is not recording
|
||||
#
|
||||
# When ``ENABLE_INSTRUMENTATION`` is on (the default) but no OpenTelemetry
|
||||
# tracer provider has been configured, the global provider is the
|
||||
# ``ProxyTracerProvider`` which returns non-recording spans. The telemetry
|
||||
# layers gate sensitive-data serialization (``_capture_messages``) on
|
||||
# ``span.is_recording()`` so that we don't pay the JSON-serialization cost
|
||||
# when the span is going to be dropped anyway. The tests below verify that
|
||||
# behavior by patching ``get_tracer`` to return a ``NoOpTracer``.
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_chat_capture_messages_skipped_when_span_not_recording(
|
||||
mock_chat_client, span_exporter: InMemorySpanExporter, enable_sensitive_data
|
||||
):
|
||||
"""Heavy message serialization is skipped when no provider is configured (non-streaming)."""
|
||||
from opentelemetry.trace import NoOpTracer
|
||||
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
span_exporter.clear()
|
||||
|
||||
with (
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_messages") as mock_capture_messages,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
response = await client.get_response(messages=messages, options={"model": "Test"})
|
||||
|
||||
assert response is not None
|
||||
# Sensitive-data serialization must be skipped because span.is_recording() is False.
|
||||
assert mock_capture_messages.call_count == 0
|
||||
# _capture_response still runs so that metric histograms continue to record.
|
||||
assert mock_capture_response.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_chat_streaming_capture_messages_skipped_when_span_not_recording(
|
||||
mock_chat_client, span_exporter: InMemorySpanExporter, enable_sensitive_data
|
||||
):
|
||||
"""Heavy message serialization is skipped when no provider is configured (streaming)."""
|
||||
from opentelemetry.trace import NoOpTracer
|
||||
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
span_exporter.clear()
|
||||
|
||||
with (
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_messages") as mock_capture_messages,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
updates: list[ChatResponseUpdate] = []
|
||||
stream = client.get_response(messages=messages, stream=True, options={"model": "Test"})
|
||||
async for update in stream:
|
||||
updates.append(update)
|
||||
await stream.get_final_response()
|
||||
|
||||
assert len(updates) == 2
|
||||
assert mock_capture_messages.call_count == 0
|
||||
assert mock_capture_response.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_agent_capture_messages_skipped_when_span_not_recording(
|
||||
mock_chat_agent, span_exporter: InMemorySpanExporter, enable_sensitive_data
|
||||
):
|
||||
"""Agent heavy serialization is skipped when no provider is configured (non-streaming)."""
|
||||
from opentelemetry.trace import NoOpTracer
|
||||
|
||||
agent = mock_chat_agent()
|
||||
span_exporter.clear()
|
||||
|
||||
with (
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_messages") as mock_capture_messages,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
response = await agent.run("Test message")
|
||||
|
||||
assert response is not None
|
||||
assert mock_capture_messages.call_count == 0
|
||||
assert mock_capture_response.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_agent_streaming_capture_messages_skipped_when_span_not_recording(
|
||||
mock_chat_agent, span_exporter: InMemorySpanExporter, enable_sensitive_data
|
||||
):
|
||||
"""Agent heavy serialization is skipped when no provider is configured (streaming)."""
|
||||
from opentelemetry.trace import NoOpTracer
|
||||
|
||||
agent = mock_chat_agent()
|
||||
span_exporter.clear()
|
||||
|
||||
with (
|
||||
patch("agent_framework.observability.get_tracer", return_value=NoOpTracer()),
|
||||
patch("agent_framework.observability._capture_messages") as mock_capture_messages,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
updates: list[Any] = []
|
||||
stream = agent.run("Test message", stream=True)
|
||||
async for update in stream:
|
||||
updates.append(update)
|
||||
await stream.get_final_response()
|
||||
|
||||
assert len(updates) == 2
|
||||
assert mock_capture_messages.call_count == 0
|
||||
assert mock_capture_response.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enable_sensitive_data", [True], indirect=True)
|
||||
async def test_chat_capture_messages_called_when_span_recording(
|
||||
mock_chat_client, span_exporter: InMemorySpanExporter, enable_sensitive_data
|
||||
):
|
||||
"""Sanity check: with a real recording provider, sensitive-data capture still runs."""
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
span_exporter.clear()
|
||||
|
||||
with (
|
||||
patch("agent_framework.observability._capture_messages") as mock_capture_messages,
|
||||
patch("agent_framework.observability._capture_response") as mock_capture_response,
|
||||
):
|
||||
response = await client.get_response(messages=messages, options={"model": "Test"})
|
||||
|
||||
assert response is not None
|
||||
# Two _capture_messages calls: one for input, one for output messages.
|
||||
assert mock_capture_messages.call_count == 2
|
||||
assert mock_capture_response.call_count == 1
|
||||
|
||||
Reference in New Issue
Block a user