Skip to content

Commit 6e322c8

Browse files
authored
fixing a bug with tracing paged lists (#41778)
* fixing a bug with tracing paged lists * updating tests * updating assests.json * fixing code checker issues * updating assest.json * updating tests * updating assets.json * updating version * updating assets.json * insignificant change to retrigger build * updating changelog
1 parent 8a057cf commit 6e322c8

File tree

8 files changed

+213
-97
lines changed

8 files changed

+213
-97
lines changed

sdk/ai/azure-ai-agents/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Release History
22

3+
## 1.0.2 (Unreleased)
4+
5+
### Bugs Fixed
6+
- Fixed a tracing related bug that caused an error when process was ending if messages or run steps were listed and the resulting list was not iterated completely.
7+
38
## 1.0.1 (2025-06-09)
49

510
### Bugs Fixed

sdk/ai/azure-ai-agents/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/ai/azure-ai-agents",
5-
"Tag": "python/ai/azure-ai-agents_dab4ae41b1"
5+
"Tag": "python/ai/azure-ai-agents_d7b5157bbd"
66
}

sdk/ai/azure-ai-agents/azure/ai/agents/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
77
# --------------------------------------------------------------------------
88

9-
VERSION = "1.0.1"
9+
VERSION = "1.0.2"

sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ def _create_event_attributes(
317317
if isinstance(end_timestamp, datetime):
318318
attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = end_timestamp.isoformat()
319319
elif end_timestamp:
320-
# fallback in case int or string gets passed
320+
# fallback in case string or int string gets passed
321321
attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = str(end_timestamp)
322322

323323
if run_step_last_error:
@@ -1324,37 +1324,68 @@ def trace_list_messages(self, function, *args, **kwargs):
13241324
server_address = self.get_server_address_from_arg(args[0])
13251325
thread_id = kwargs.get("thread_id")
13261326

1327-
span = self.start_list_messages_span(server_address=server_address, thread_id=thread_id)
1328-
1329-
return _InstrumentedItemPaged(function(*args, **kwargs), self.add_thread_message_event, span)
1327+
return _InstrumentedItemPaged(
1328+
function(*args, **kwargs),
1329+
start_span_function=self.start_trace_list_messages,
1330+
item_instrumentation_function=self.add_thread_message_event,
1331+
server_address=server_address,
1332+
thread_id=thread_id,
1333+
run_id=None,
1334+
)
13301335

13311336
def trace_list_messages_async(self, function, *args, **kwargs):
13321337
# Note that this method is not async, but it operates on AsyncIterable.
13331338
server_address = self.get_server_address_from_arg(args[0])
13341339
thread_id = kwargs.get("thread_id")
13351340

1336-
span = self.start_list_messages_span(server_address=server_address, thread_id=thread_id)
1341+
return _AsyncInstrumentedItemPaged(
1342+
function(*args, **kwargs),
1343+
start_span_function=self.start_trace_list_messages,
1344+
item_instrumentation_function=self.add_thread_message_event,
1345+
server_address=server_address,
1346+
thread_id=thread_id,
1347+
run_id=None,
1348+
)
13371349

1338-
return _AsyncInstrumentedItemPaged(function(*args, **kwargs), self.add_thread_message_event, span)
1350+
def start_trace_list_messages(
1351+
self, server_address: Optional[str] = None, thread_id: Optional[str] = None, run_id: Optional[str] = None
1352+
):
1353+
_ = run_id # Unused parameter, but kept for compatibility.
1354+
return self.start_list_messages_span(server_address=server_address, thread_id=thread_id)
13391355

13401356
def trace_list_run_steps(self, function, *args, **kwargs):
13411357
server_address = self.get_server_address_from_arg(args[0])
13421358
run_id = kwargs.get("run_id")
13431359
thread_id = kwargs.get("thread_id")
13441360

1345-
span = self.start_list_run_steps_span(server_address=server_address, run_id=run_id, thread_id=thread_id)
1346-
1347-
return _InstrumentedItemPaged(function(*args, **kwargs), self.add_run_step_event, span)
1361+
return _InstrumentedItemPaged(
1362+
function(*args, **kwargs),
1363+
start_span_function=self.start_list_run_steps_span,
1364+
item_instrumentation_function=self.add_run_step_event,
1365+
server_address=server_address,
1366+
thread_id=thread_id,
1367+
run_id=run_id,
1368+
)
13481369

13491370
def trace_list_run_steps_async(self, function, *args, **kwargs):
13501371
# Note that this method is not async, but it operates on AsyncIterable.
13511372
server_address = self.get_server_address_from_arg(args[0])
13521373
run_id = kwargs.get("run_id")
13531374
thread_id = kwargs.get("thread_id")
13541375

1355-
span = self.start_list_run_steps_span(server_address=server_address, run_id=run_id, thread_id=thread_id)
1376+
return _AsyncInstrumentedItemPaged(
1377+
function(*args, **kwargs),
1378+
start_span_function=self.start_list_run_steps_span,
1379+
item_instrumentation_function=self.add_run_step_event,
1380+
server_address=server_address,
1381+
thread_id=thread_id,
1382+
run_id=run_id,
1383+
)
13561384

1357-
return _AsyncInstrumentedItemPaged(function(*args, **kwargs), self.add_run_step_event, span)
1385+
def start_trace_list_run_steps(
1386+
self, server_address: Optional[str] = None, thread_id: Optional[str] = None, run_id: Optional[str] = None
1387+
):
1388+
return self.start_list_run_steps_span(server_address=server_address, thread_id=thread_id, run_id=run_id)
13581389

13591390
def handle_run_stream_exit(self, _function, *args, **kwargs):
13601391
agent_run_stream = args[0]
@@ -2058,6 +2089,7 @@ def on_unhandled_event(self, event_type: str, event_data: Any) -> None: # type:
20582089
if self.inner_handler:
20592090
return self.inner_handler.on_unhandled_event(event_type, event_data) # type: ignore
20602091
return super().on_unhandled_event(event_type, event_data) # type: ignore
2092+
20612093
# pylint: enable=R1710
20622094

20632095
def __exit__(self, exc_type, exc_val, exc_tb):

sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_instrument_paged_wrappers.py

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,19 @@ class _AsyncInstrumentedItemPaged(AsyncItemPaged, _SpanLogger):
5454
def __init__(
5555
self,
5656
async_iter: AsyncItemPaged,
57-
instrumentation_fun: Callable[[AbstractSpan, Any], None],
58-
span: Optional[AbstractSpan],
57+
start_span_function: Callable[[Optional[str], Optional[str], Optional[str]], AbstractSpan],
58+
item_instrumentation_function: Callable[[AbstractSpan, Any], None],
59+
server_address: Optional[str] = None,
60+
thread_id: Optional[str] = None,
61+
run_id: Optional[str] = None,
5962
) -> None:
6063
super().__init__()
6164
self._iter = async_iter
62-
self._inst_fun = instrumentation_fun
63-
self._span = span
65+
self._server_address = server_address
66+
self._thread_id = thread_id
67+
self._run_id = run_id
68+
self._start_span_function = start_span_function
69+
self._item_instrumentation_function = item_instrumentation_function
6470
self._gen: Optional[AsyncIterator[Any]] = None
6571

6672
def __getattr__(self, name: str) -> Any:
@@ -77,20 +83,19 @@ def __getattr__(self, name: str) -> Any:
7783

7884
def __aiter__(self) -> AsyncIterator[Any]:
7985
async def _gen() -> AsyncIterator[Any]:
80-
try:
81-
async for val in self._iter:
82-
if self._span is not None:
83-
self.log_to_span_safe(val, self._inst_fun, self._span)
84-
yield val
85-
finally:
86-
if self._span is not None:
86+
async for val in self._iter:
87+
span: Optional[AbstractSpan] = self._start_span_function(
88+
self._server_address, self._thread_id, self._run_id
89+
)
90+
if span is not None:
91+
self.log_to_span_safe(val, self._item_instrumentation_function, span)
8792
# We cast None to TracebackType, because traceback is
8893
# not used in the downstream code.
89-
self._span.__exit__(None, None, cast(TracebackType, None))
94+
span.__exit__(None, None, cast(TracebackType, None))
95+
span = None
96+
yield val
9097

9198
if self._gen is None:
92-
if self._span is not None:
93-
self._span.__enter__()
9499
self._gen = _gen()
95100
return self._gen
96101

@@ -101,13 +106,19 @@ class _InstrumentedItemPaged(ItemPaged, _SpanLogger):
101106
def __init__(
102107
self,
103108
iter_val: ItemPaged,
104-
instrumentation_fun: Callable[[AbstractSpan, Any], None],
105-
span: Optional[AbstractSpan],
109+
start_span_function: Callable[[Optional[str], Optional[str], Optional[str]], AbstractSpan],
110+
item_instrumentation_function: Callable[[AbstractSpan, Any], None],
111+
server_address: Optional[str] = None,
112+
thread_id: Optional[str] = None,
113+
run_id: Optional[str] = None,
106114
) -> None:
107115
super().__init__()
108116
self._iter = iter_val
109-
self._inst_fun = instrumentation_fun
110-
self._span = span
117+
self._server_address = server_address
118+
self._thread_id = thread_id
119+
self._run_id = run_id
120+
self._start_span_function = start_span_function
121+
self._item_instrumentation_function = item_instrumentation_function
111122
self._gen: Optional[Iterator[Any]] = None
112123

113124
def __getattr__(self, name: str) -> Any:
@@ -124,19 +135,18 @@ def __getattr__(self, name: str) -> Any:
124135

125136
def __iter__(self) -> Iterator[Any]:
126137
def _gen() -> Iterator[Any]:
127-
try:
128-
for val in self._iter:
129-
if self._span is not None:
130-
self.log_to_span_safe(val, self._inst_fun, self._span)
131-
yield val
132-
finally:
133-
if self._span is not None:
138+
for val in self._iter:
139+
span: Optional[AbstractSpan] = self._start_span_function(
140+
self._server_address, self._thread_id, self._run_id
141+
)
142+
if span is not None:
143+
self.log_to_span_safe(val, self._item_instrumentation_function, span)
134144
# We cast None to TracebackType, because traceback is
135145
# not used in the downstream code.
136-
self._span.__exit__(None, None, cast(TracebackType, None))
146+
span.__exit__(None, None, cast(TracebackType, None))
147+
span = None
148+
yield val
137149

138150
if self._gen is None:
139-
if self._span is not None:
140-
self._span.__enter__()
141151
self._gen = _gen()
142152
return self._gen

sdk/ai/azure-ai-agents/tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ def azure_workspace_triad_sanitizer():
9797
group_for_replace="1",
9898
)
9999

100+
add_general_regex_sanitizer(
101+
regex=r"api/projects/([-\w\._\(\)]+)",
102+
value=mock_project_scope["project_name"],
103+
group_for_replace="1",
104+
)
105+
100106
azure_workspace_triad_sanitizer()
101107

102108
add_general_regex_sanitizer(regex=r"/runs/([-\w\._\(\)]+)", value="Sanitized", group_for_replace="1")

0 commit comments

Comments
 (0)