18
18
McpTool ,
19
19
MessageDeltaChunk ,
20
20
MessageDeltaTextContent ,
21
+ MessageInputTextBlock ,
21
22
OpenApiAnonymousAuthDetails ,
22
23
OpenApiTool ,
23
24
RequiredMcpToolCall ,
28
29
RunStepToolCallDetails ,
29
30
SubmitToolApprovalAction ,
30
31
ThreadMessage ,
32
+ ThreadMessageOptions ,
31
33
ThreadRun ,
32
34
Tool ,
33
35
ToolApproval ,
36
38
from azure .ai .agents .telemetry ._ai_agents_instrumentor import _AIAgentsInstrumentorPreview
37
39
from azure .ai .agents .telemetry import AIAgentsInstrumentor , _utils
38
40
from azure .core .settings import settings
39
- from memory_trace_exporter import MemoryTraceExporter
40
41
from gen_ai_trace_verifier import GenAiTraceVerifier
41
- from opentelemetry import trace
42
- from opentelemetry .sdk .trace import TracerProvider
43
- from opentelemetry .sdk .trace .export import SimpleSpanProcessor
44
42
from azure .ai .agents import AgentsClient
45
43
46
44
from devtools_testutils import (
47
45
recorded_by_proxy ,
48
46
)
49
47
50
48
from test_agents_client_base import agentClientPreparer
51
- from test_ai_instrumentor_base import TestAiAgentsInstrumentorBase
49
+ from test_ai_instrumentor_base import TestAiAgentsInstrumentorBase , MessageCreationMode , CONTENT_TRACING_ENV_VARIABLE
52
50
53
- CONTENT_TRACING_ENV_VARIABLE = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
54
51
settings .tracing_implementation = "OpenTelemetry"
55
52
_utils ._span_impl_type = settings .tracing_implementation ()
56
53
57
54
58
55
class TestAiAgentsInstrumentor (TestAiAgentsInstrumentorBase ):
56
+
59
57
"""Tests for AI agents instrumentor."""
60
58
61
59
@pytest .fixture (scope = "function" )
@@ -72,19 +70,6 @@ def instrument_without_content(self):
72
70
yield
73
71
self .cleanup ()
74
72
75
- def setup_telemetry (self ):
76
- trace ._TRACER_PROVIDER = TracerProvider ()
77
- self .exporter = MemoryTraceExporter ()
78
- span_processor = SimpleSpanProcessor (self .exporter )
79
- trace .get_tracer_provider ().add_span_processor (span_processor )
80
- AIAgentsInstrumentor ().instrument ()
81
-
82
- def cleanup (self ):
83
- self .exporter .shutdown ()
84
- AIAgentsInstrumentor ().uninstrument ()
85
- trace ._TRACER_PROVIDER = None
86
- os .environ .pop (CONTENT_TRACING_ENV_VARIABLE , None )
87
-
88
73
# helper function: create client and using environment variables
89
74
def create_client (self , ** kwargs ):
90
75
# fetch environment variables
@@ -227,13 +212,38 @@ def set_env_var(var_name, value):
227
212
@agentClientPreparer ()
228
213
@recorded_by_proxy
229
214
def test_agent_chat_with_tracing_content_recording_enabled (self , ** kwargs ):
215
+ # Note: The proper way to invoke the same test over and over again with different parameter values is to use @pytest.mark.parametrize. However,
216
+ # this does not work together with @recorded_by_proxy. So we call the helper function 4 times instead in a single recorded test.
217
+ self ._agent_chat_with_tracing_content_recording_enabled (message_creation_mode = MessageCreationMode .MESSAGE_CREATE_STR , ** kwargs )
218
+ self ._agent_chat_with_tracing_content_recording_enabled (message_creation_mode = MessageCreationMode .MESSAGE_CREATE_INPUT_TEXT_BLOCK , ** kwargs )
219
+ self ._agent_chat_with_tracing_content_recording_enabled (message_creation_mode = MessageCreationMode .THREAD_CREATE_STR , ** kwargs )
220
+ self ._agent_chat_with_tracing_content_recording_enabled (message_creation_mode = MessageCreationMode .THREAD_CREATE_INPUT_TEXT_BLOCK , ** kwargs )
221
+
222
+ def _agent_chat_with_tracing_content_recording_enabled (self , message_creation_mode : MessageCreationMode , ** kwargs ):
223
+ self .cleanup ()
224
+ os .environ .update ({CONTENT_TRACING_ENV_VARIABLE : "True" })
225
+ self .setup_telemetry ()
230
226
assert True == AIAgentsInstrumentor ().is_content_recording_enabled ()
231
227
assert True == AIAgentsInstrumentor ().is_instrumented ()
232
228
233
229
client = self .create_client (** kwargs )
234
230
agent = client .create_agent (model = "gpt-4o-mini" , name = "my-agent" , instructions = "You are helpful agent" )
235
- thread = client .threads .create ()
236
- client .messages .create (thread_id = thread .id , role = "user" , content = "Hello, tell me a joke" )
231
+ user_content = "Hello, tell me a joke"
232
+
233
+ # Test 4 different patterns of thread & message creation
234
+ if message_creation_mode == MessageCreationMode .MESSAGE_CREATE_STR :
235
+ thread = client .threads .create ()
236
+ client .messages .create (thread_id = thread .id , role = "user" , content = user_content )
237
+ elif message_creation_mode == MessageCreationMode .MESSAGE_CREATE_INPUT_TEXT_BLOCK :
238
+ thread = client .threads .create ()
239
+ client .messages .create (thread_id = thread .id , role = "user" , content = [MessageInputTextBlock (text = user_content )])
240
+ elif message_creation_mode == MessageCreationMode .THREAD_CREATE_STR :
241
+ thread = client .threads .create (messages = [ThreadMessageOptions (role = "user" , content = user_content )])
242
+ elif message_creation_mode == MessageCreationMode .THREAD_CREATE_INPUT_TEXT_BLOCK :
243
+ thread = client .threads .create (messages = [ThreadMessageOptions (role = "user" , content = [MessageInputTextBlock (text = user_content )])])
244
+ else :
245
+ assert False , f"Unknown message creation mode: { message_creation_mode } "
246
+
237
247
run = client .runs .create (thread_id = thread .id , agent_id = agent .id )
238
248
239
249
while run .status in ["queued" , "in_progress" , "requires_action" ]:
@@ -250,6 +260,7 @@ def test_agent_chat_with_tracing_content_recording_enabled(self, **kwargs):
250
260
assert len (messages ) > 1
251
261
client .close ()
252
262
263
+ # ------------------------- Validate "create_agent" span ---------------------------------
253
264
self .exporter .force_flush ()
254
265
spans = self .exporter .get_spans_by_name ("create_agent my-agent" )
255
266
assert len (spans ) == 1
@@ -277,6 +288,7 @@ def test_agent_chat_with_tracing_content_recording_enabled(self, **kwargs):
277
288
events_match = GenAiTraceVerifier ().check_span_events (span , expected_events )
278
289
assert events_match == True
279
290
291
+ # ------------------------- Validate "create_thread" span ---------------------------------
280
292
spans = self .exporter .get_spans_by_name ("create_thread" )
281
293
assert len (spans ) == 1
282
294
span = spans [0 ]
@@ -289,32 +301,48 @@ def test_agent_chat_with_tracing_content_recording_enabled(self, **kwargs):
289
301
attributes_match = GenAiTraceVerifier ().check_span_attributes (span , expected_attributes )
290
302
assert attributes_match == True
291
303
292
- spans = self .exporter .get_spans_by_name ("create_message" )
293
- assert len (spans ) == 1
294
- span = spans [0 ]
295
- expected_attributes = [
296
- ("gen_ai.system" , "az.ai.agents" ),
297
- ("gen_ai.operation.name" , "create_message" ),
298
- ("server.address" , "" ),
299
- ("gen_ai.thread.id" , "" ),
300
- ("gen_ai.message.id" , "" ),
301
- ]
302
- attributes_match = GenAiTraceVerifier ().check_span_attributes (span , expected_attributes )
303
- assert attributes_match == True
304
-
305
- expected_events = [
306
- {
307
- "name" : "gen_ai.user.message" ,
308
- "attributes" : {
309
- "gen_ai.system" : "az.ai.agents" ,
310
- "gen_ai.thread.id" : "*" ,
311
- "gen_ai.event.content" : '{"content": "Hello, tell me a joke", "role": "user"}' ,
312
- },
313
- }
314
- ]
315
- events_match = GenAiTraceVerifier ().check_span_events (span , expected_events )
316
- assert events_match == True
317
-
304
+ if message_creation_mode in (MessageCreationMode .THREAD_CREATE_STR , MessageCreationMode .THREAD_CREATE_INPUT_TEXT_BLOCK ):
305
+ expected_events = [
306
+ {
307
+ "name" : "gen_ai.user.message" ,
308
+ "attributes" : {
309
+ "gen_ai.system" : "az.ai.agents" ,
310
+ "gen_ai.event.content" : '{"content": "Hello, tell me a joke", "role": "user"}' ,
311
+ },
312
+ }
313
+ ]
314
+ events_match = GenAiTraceVerifier ().check_span_events (span , expected_events )
315
+ assert events_match == True
316
+
317
+ # ------------------------- Validate "create_message" span ---------------------------------
318
+ if message_creation_mode in (MessageCreationMode .MESSAGE_CREATE_STR , MessageCreationMode .MESSAGE_CREATE_INPUT_TEXT_BLOCK ):
319
+ spans = self .exporter .get_spans_by_name ("create_message" )
320
+ assert len (spans ) == 1
321
+ span = spans [0 ]
322
+ expected_attributes = [
323
+ ("gen_ai.system" , "az.ai.agents" ),
324
+ ("gen_ai.operation.name" , "create_message" ),
325
+ ("server.address" , "" ),
326
+ ("gen_ai.thread.id" , "" ),
327
+ ("gen_ai.message.id" , "" ),
328
+ ]
329
+ attributes_match = GenAiTraceVerifier ().check_span_attributes (span , expected_attributes )
330
+ assert attributes_match == True
331
+
332
+ expected_events = [
333
+ {
334
+ "name" : "gen_ai.user.message" ,
335
+ "attributes" : {
336
+ "gen_ai.system" : "az.ai.agents" ,
337
+ "gen_ai.thread.id" : "*" ,
338
+ "gen_ai.event.content" : '{"content": "Hello, tell me a joke", "role": "user"}' ,
339
+ },
340
+ }
341
+ ]
342
+ events_match = GenAiTraceVerifier ().check_span_events (span , expected_events )
343
+ assert events_match == True
344
+
345
+ # ------------------------- Validate "start_thread_run" span ---------------------------------
318
346
spans = self .exporter .get_spans_by_name ("start_thread_run" )
319
347
assert len (spans ) == 1
320
348
span = spans [0 ]
@@ -332,6 +360,7 @@ def test_agent_chat_with_tracing_content_recording_enabled(self, **kwargs):
332
360
attributes_match = GenAiTraceVerifier ().check_span_attributes (span , expected_attributes )
333
361
assert attributes_match == True
334
362
363
+ # ------------------------- Validate "get_thread_run" span ---------------------------------
335
364
spans = self .exporter .get_spans_by_name ("get_thread_run" )
336
365
assert len (spans ) >= 1
337
366
span = spans [- 1 ]
@@ -350,6 +379,7 @@ def test_agent_chat_with_tracing_content_recording_enabled(self, **kwargs):
350
379
attributes_match = GenAiTraceVerifier ().check_span_attributes (span , expected_attributes )
351
380
assert attributes_match == True
352
381
382
+ # ------------------------- Validate "list_messages" span ---------------------------------
353
383
spans = self .exporter .get_spans_by_name ("list_messages" )
354
384
assert len (spans ) == 2
355
385
span = spans [0 ]
0 commit comments