Skip to content

Commit cff467f

Browse files
Refactor: Use parent span operation name check instead of tracking dictionary
Co-authored-by: sergioescalera <8428450+sergioescalera@users.noreply.github.com>
1 parent a44a56d commit cff467f

2 files changed

Lines changed: 29 additions & 57 deletions

File tree

libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/trace_processor.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ def __init__(self, tracer: Tracer, suppress_invoke_agent_input: bool = False) ->
8080
# Use an OrderedDict and _MAX_HANDOFFS_IN_FLIGHT to cap the size of the dict
8181
# in case there are large numbers of orphaned handoffs
8282
self._reverse_handoffs_dict: OrderedDict[str, str] = OrderedDict()
83-
# Track active agent spans per trace to determine if we're in an InvokeAgent scope
84-
self._active_agent_spans: dict[str, set[str]] = {}
8583

8684
# helper
8785
def _stamp_custom_parent(self, otel_span: OtelSpan, trace_id: str) -> None:
@@ -92,14 +90,27 @@ def _stamp_custom_parent(self, otel_span: OtelSpan, trace_id: str) -> None:
9290
pid_hex = "0x" + ot_trace.format_span_id(sc.span_id)
9391
otel_span.set_attribute(CUSTOM_PARENT_SPAN_ID_KEY, pid_hex)
9492

95-
def _should_suppress_input(self, trace_id: str) -> bool:
96-
"""Check if input messages should be suppressed for the given trace."""
97-
return self._suppress_invoke_agent_input and self._is_in_invoke_agent_scope(trace_id)
98-
99-
def _is_in_invoke_agent_scope(self, trace_id: str) -> bool:
100-
"""Check if we're currently inside an InvokeAgent scope for the given trace."""
101-
agent_spans = self._active_agent_spans.get(trace_id, set())
102-
return len(agent_spans) > 0
93+
def _should_suppress_input(self, span: Span[Any]) -> bool:
94+
"""Check if input messages should be suppressed for the given span.
95+
96+
Args:
97+
span: The span to check.
98+
99+
Returns:
100+
True if suppression is enabled and the span has an InvokeAgent parent.
101+
"""
102+
if not self._suppress_invoke_agent_input:
103+
return False
104+
105+
# Check if the parent span is an InvokeAgent span by looking at its operation name
106+
if span.parent_id:
107+
parent_otel_span = self._otel_spans.get(span.parent_id)
108+
if parent_otel_span and hasattr(parent_otel_span, 'attributes'):
109+
operation_name = parent_otel_span.attributes.get(GEN_AI_OPERATION_NAME_KEY)
110+
if operation_name == INVOKE_AGENT_OPERATION_NAME:
111+
return True
112+
113+
return False
103114

104115
def on_trace_start(self, trace: Trace) -> None:
105116
"""Called when a trace is started.
@@ -146,12 +157,6 @@ def on_span_start(self, span: Span[Any]) -> None:
146157
self._otel_spans[span.span_id] = otel_span
147158
self._tokens[span.span_id] = attach(set_span_in_context(otel_span))
148159

149-
# Track agent spans for InvokeAgent scope detection
150-
if isinstance(span.span_data, AgentSpanData):
151-
if span.trace_id not in self._active_agent_spans:
152-
self._active_agent_spans[span.trace_id] = set()
153-
self._active_agent_spans[span.trace_id].add(span.span_id)
154-
155160
def on_span_end(self, span: Span[Any]) -> None:
156161
"""Called when a span is finished. Should not block or raise exceptions.
157162
@@ -173,7 +178,7 @@ def on_span_end(self, span: Span[Any]) -> None:
173178
for k, v in get_attributes_from_response(response):
174179
otel_span.set_attribute(k, v)
175180
# Only record input messages if not suppressing or not in InvokeAgent scope
176-
if not self._should_suppress_input(span.trace_id) and hasattr(data, "input") and (input := data.input):
181+
if not self._should_suppress_input(span) and hasattr(data, "input") and (input := data.input):
177182
if isinstance(input, str):
178183
otel_span.set_attribute(GEN_AI_INPUT_MESSAGES_KEY, input)
179184
elif isinstance(input, list):
@@ -184,7 +189,7 @@ def on_span_end(self, span: Span[Any]) -> None:
184189
assert_never(input)
185190
elif isinstance(data, GenerationSpanData):
186191
# Collect all attributes once and filter if suppression is enabled
187-
should_suppress = self._should_suppress_input(span.trace_id)
192+
should_suppress = self._should_suppress_input(span)
188193
for k, v in get_attributes_from_generation_span_data(data):
189194
# Skip input messages if suppression is enabled and in InvokeAgent scope
190195
if should_suppress and k == GEN_AI_INPUT_MESSAGES_KEY:
@@ -218,12 +223,6 @@ def on_span_end(self, span: Span[Any]) -> None:
218223
otel_span.set_attribute(GEN_AI_GRAPH_NODE_PARENT_ID, parent_node)
219224
otel_span.update_name(f"{INVOKE_AGENT_OPERATION_NAME} {get_span_name(span)}")
220225

221-
# Clean up agent span tracking
222-
if span.trace_id in self._active_agent_spans:
223-
self._active_agent_spans[span.trace_id].discard(span.span_id)
224-
if not self._active_agent_spans[span.trace_id]:
225-
del self._active_agent_spans[span.trace_id]
226-
227226
end_time: int | None = None
228227
if span.ended_at:
229228
try:

tests/observability/extensions/openai/test_prompt_suppression.py

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -49,45 +49,18 @@ def test_can_disable_suppression(self):
4949
"suppress_invoke_agent_input should be False when explicitly set",
5050
)
5151

52-
def test_has_active_agent_spans_tracking(self):
53-
"""Test that the processor has the required tracking data structure."""
52+
def test_has_should_suppress_input_method(self):
53+
"""Test that the processor has the helper method for suppression logic."""
5454
tracer = get_tracer()
5555
processor = OpenAIAgentsTraceProcessor(tracer, suppress_invoke_agent_input=True)
5656

5757
self.assertTrue(
58-
hasattr(processor, "_active_agent_spans"),
59-
"Processor should have _active_agent_spans attribute",
60-
)
61-
self.assertIsInstance(
62-
processor._active_agent_spans,
63-
dict,
64-
"_active_agent_spans should be a dictionary",
65-
)
66-
67-
def test_has_is_in_invoke_agent_scope_method(self):
68-
"""Test that the processor has the helper method for scope detection."""
69-
tracer = get_tracer()
70-
processor = OpenAIAgentsTraceProcessor(tracer, suppress_invoke_agent_input=True)
71-
72-
self.assertTrue(
73-
hasattr(processor, "_is_in_invoke_agent_scope"),
74-
"Processor should have _is_in_invoke_agent_scope method",
58+
hasattr(processor, "_should_suppress_input"),
59+
"Processor should have _should_suppress_input method",
7560
)
7661
self.assertTrue(
77-
callable(processor._is_in_invoke_agent_scope),
78-
"_is_in_invoke_agent_scope should be callable",
79-
)
80-
81-
def test_is_in_invoke_agent_scope_returns_false_for_empty_trace(self):
82-
"""Test that _is_in_invoke_agent_scope returns False for unknown trace."""
83-
tracer = get_tracer()
84-
processor = OpenAIAgentsTraceProcessor(tracer, suppress_invoke_agent_input=True)
85-
86-
result = processor._is_in_invoke_agent_scope("unknown-trace-id")
87-
88-
self.assertFalse(
89-
result,
90-
"_is_in_invoke_agent_scope should return False for traces with no active agent spans",
62+
callable(processor._should_suppress_input),
63+
"_should_suppress_input should be callable",
9164
)
9265

9366

0 commit comments

Comments
 (0)