Skip to content

Commit 0bf19e6

Browse files
add test for invoke agent auto instrumented
1 parent b4299ec commit 0bf19e6

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

tests/observability/extensions/openai/integration/test_openai_trace_processor.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@
77
from microsoft_agents_a365.observability.core import configure, get_tracer_provider
88
from microsoft_agents_a365.observability.core.constants import (
99
GEN_AI_AGENT_ID_KEY,
10+
GEN_AI_AGENT_NAME_KEY,
11+
GEN_AI_EXECUTION_TYPE_KEY,
1012
GEN_AI_INPUT_MESSAGES_KEY,
13+
GEN_AI_OPERATION_NAME_KEY,
1114
GEN_AI_OUTPUT_MESSAGES_KEY,
1215
GEN_AI_REQUEST_MODEL_KEY,
1316
GEN_AI_SYSTEM_KEY,
17+
INVOKE_AGENT_OPERATION_NAME,
1418
TENANT_ID_KEY,
1519
)
20+
from microsoft_agents_a365.observability.core.middleware.baggage_builder import BaggageBuilder
1621
from microsoft_agents_a365.observability.extensions.openai.trace_instrumentor import (
1722
OpenAIAgentsTraceInstrumentor,
1823
)
@@ -184,6 +189,122 @@ async def run_agent_with_tool():
184189
# Clean up
185190
instrumentor.uninstrument()
186191

192+
def test_invoke_agent_span_required_attributes(self, azure_openai_config, agent365_config):
193+
"""Test that invoke_agent span has all required attributes per schema."""
194+
195+
# Configure observability
196+
configure(
197+
service_name="integration-test-invoke-agent",
198+
service_namespace="agent365-tests",
199+
logger_name="test-logger",
200+
)
201+
202+
# Get the tracer provider and add our mock exporter
203+
provider = get_tracer_provider()
204+
provider.add_span_processor(self.mock_exporter)
205+
206+
# Initialize the instrumentor
207+
instrumentor = OpenAIAgentsTraceInstrumentor()
208+
instrumentor.instrument()
209+
210+
try:
211+
# Create Azure OpenAI client
212+
openai_client = AsyncAzureOpenAI(
213+
api_key=azure_openai_config["api_key"],
214+
api_version=azure_openai_config["api_version"],
215+
azure_endpoint=azure_openai_config["endpoint"],
216+
)
217+
218+
# Create agent
219+
agent = Agent(
220+
name="TestAgent",
221+
instructions="You are a helpful assistant. Answer briefly.",
222+
model=OpenAIChatCompletionsModel(
223+
model=azure_openai_config["deployment"], openai_client=openai_client
224+
),
225+
)
226+
227+
# Execute agent wrapped with BaggageBuilder to provide required attributes
228+
import asyncio
229+
230+
async def run_agent():
231+
with (
232+
BaggageBuilder()
233+
.agent_id("test-agent-id")
234+
.agent_name("TestAgent")
235+
.agent_auid("test-agent-auid")
236+
.agent_upn("test-agent@test.com")
237+
.agent_blueprint_id("test-blueprint-id")
238+
.tenant_id("test-tenant-id")
239+
.caller_id("test-caller-id")
240+
.caller_name("Test Caller")
241+
.caller_upn("test-caller@test.com")
242+
.caller_client_ip("127.0.0.1")
243+
.conversation_id("test-conversation-id")
244+
.channel_name("test-channel")
245+
.correlation_id("test-correlation-id")
246+
.build()
247+
):
248+
result = await Runner.run(agent, "Say hello")
249+
return result.final_output
250+
251+
response = asyncio.run(run_agent())
252+
253+
# Give time for spans to be processed
254+
time.sleep(1)
255+
256+
# Find the invoke_agent span
257+
invoke_agent_span = None
258+
for span in self.captured_spans:
259+
if span.name.startswith(INVOKE_AGENT_OPERATION_NAME):
260+
invoke_agent_span = span
261+
break
262+
263+
assert invoke_agent_span is not None, "invoke_agent span not found"
264+
attributes = dict(invoke_agent_span.attributes or {})
265+
266+
print(f"invoke_agent span attributes: {list(attributes.keys())}")
267+
268+
# Validate REQUIRED attributes (must be present)
269+
required_attributes = [
270+
GEN_AI_OPERATION_NAME_KEY, # "gen_ai.operation.name" - Set by SDK
271+
GEN_AI_AGENT_ID_KEY, # "gen_ai.agent.id"
272+
GEN_AI_AGENT_NAME_KEY, # "gen_ai.agent.name"
273+
GEN_AI_EXECUTION_TYPE_KEY, # "gen_ai.execution.type"
274+
GEN_AI_INPUT_MESSAGES_KEY, # "gen_ai.input.messages"
275+
GEN_AI_OUTPUT_MESSAGES_KEY, # "gen_ai.output.messages"
276+
]
277+
278+
missing_required = []
279+
for attr in required_attributes:
280+
if attr not in attributes:
281+
missing_required.append(attr)
282+
else:
283+
print(f"✓ Required attribute present: {attr} = {str(attributes[attr])[:50]}...")
284+
285+
assert len(missing_required) == 0, f"Missing required attributes: {missing_required}"
286+
287+
# Validate operation name value
288+
assert attributes[GEN_AI_OPERATION_NAME_KEY] == INVOKE_AGENT_OPERATION_NAME, (
289+
f"Expected operation name '{INVOKE_AGENT_OPERATION_NAME}', "
290+
f"got '{attributes[GEN_AI_OPERATION_NAME_KEY]}'"
291+
)
292+
293+
# Validate agent name matches
294+
assert attributes[GEN_AI_AGENT_NAME_KEY] == "TestAgent", (
295+
f"Expected agent name 'TestAgent', got '{attributes[GEN_AI_AGENT_NAME_KEY]}'"
296+
)
297+
298+
# Validate input/output messages are non-empty
299+
assert attributes[GEN_AI_INPUT_MESSAGES_KEY], "Input messages should not be empty"
300+
assert attributes[GEN_AI_OUTPUT_MESSAGES_KEY], "Output messages should not be empty"
301+
302+
print("✓ All required invoke_agent span attributes validated")
303+
print(f"Agent response: {response}")
304+
305+
finally:
306+
instrumentor.uninstrument()
307+
187308
def _validate_span_attributes(self, agent365_config):
188309
"""Validate that spans have the expected attributes."""
189310
llm_spans_found = 0

0 commit comments

Comments
 (0)