Skip to content

Commit b7bb283

Browse files
TaoChenOSUmoonbox3
andauthored
Python: Input & output attributes in invoke_agent span (#12834)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> The OTel workgroup has been thinking about enhancing tracing for agents. This is a proposal from our tracing team: open-telemetry/semantic-conventions#2528. This PR implements part of the proposal so that when the proposal gets merged, we can quickly follow. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Two new attributes are added to the `invoke_agent` span, namely the `gen_ai.agent.invocation_input` and `gen_ai.agent.invocation_output`. <img width="747" height="583" alt="image" src="https://github.com/user-attachments/assets/d8b81f68-915b-48ee-8f86-66a0d4f686cc" /> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 --------- Co-authored-by: Evan Mattson <[email protected]>
1 parent 358b0f3 commit b7bb283

File tree

13 files changed

+426
-61
lines changed

13 files changed

+426
-61
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import logging
4+
import os
5+
6+
from azure.monitor.opentelemetry.exporter import AzureMonitorLogExporter, AzureMonitorTraceExporter
7+
from dotenv import load_dotenv
8+
from opentelemetry import trace
9+
from opentelemetry._logs import set_logger_provider
10+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
11+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
12+
from opentelemetry.sdk.resources import Resource
13+
from opentelemetry.sdk.trace import TracerProvider
14+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
15+
from opentelemetry.semconv.resource import ResourceAttributes
16+
from opentelemetry.trace import set_tracer_provider
17+
from opentelemetry.trace.span import format_trace_id
18+
19+
load_dotenv()
20+
21+
APPINSIGHTS_CONNECTION_STRING = os.getenv("APPINSIGHTS_CONNECTION_STRING")
22+
23+
resource = Resource.create({ResourceAttributes.SERVICE_NAME: "multi_agent_orchestration_sample"})
24+
25+
26+
def set_up_logging():
27+
class KernelFilter(logging.Filter):
28+
"""A filter to not process records from semantic_kernel."""
29+
30+
# These are the namespaces that we want to exclude from logging for the purposes of this demo.
31+
namespaces_to_exclude: list[str] = [
32+
"semantic_kernel.functions.kernel_plugin",
33+
"semantic_kernel.prompt_template.kernel_prompt_template",
34+
]
35+
36+
def filter(self, record):
37+
return not any([record.name.startswith(namespace) for namespace in self.namespaces_to_exclude])
38+
39+
exporters = []
40+
exporters.append(AzureMonitorLogExporter(connection_string=APPINSIGHTS_CONNECTION_STRING))
41+
42+
# Create and set a global logger provider for the application.
43+
logger_provider = LoggerProvider(resource=resource)
44+
# Log processors are initialized with an exporter which is responsible
45+
# for sending the telemetry data to a particular backend.
46+
for log_exporter in exporters:
47+
logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
48+
# Sets the global default logger provider
49+
set_logger_provider(logger_provider)
50+
51+
# Create a logging handler to write logging records, in OTLP format, to the exporter.
52+
handler = LoggingHandler()
53+
# Add filters to the handler to only process records from semantic_kernel.
54+
handler.addFilter(logging.Filter("semantic_kernel"))
55+
handler.addFilter(KernelFilter())
56+
# Attach the handler to the root logger. `getLogger()` with no arguments returns the root logger.
57+
# Events from all child loggers will be processed by this handler.
58+
logger = logging.getLogger()
59+
logger.addHandler(handler)
60+
# Set the logging level to NOTSET to allow all records to be processed by the handler.
61+
logger.setLevel(logging.NOTSET)
62+
63+
64+
def set_up_tracing():
65+
exporters = []
66+
exporters.append(AzureMonitorTraceExporter(connection_string=APPINSIGHTS_CONNECTION_STRING))
67+
68+
# Initialize a trace provider for the application. This is a factory for creating tracers.
69+
tracer_provider = TracerProvider(resource=resource)
70+
# Span processors are initialized with an exporter which is responsible
71+
# for sending the telemetry data to a particular backend.
72+
for exporter in exporters:
73+
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
74+
# Sets the global default tracer provider
75+
set_tracer_provider(tracer_provider)
76+
77+
78+
def enable_observability(func):
79+
"""A decorator to enable observability for the samples."""
80+
81+
async def wrapper(*args, **kwargs):
82+
if not APPINSIGHTS_CONNECTION_STRING:
83+
# If the connection string is not set, skip observability setup.
84+
return await func(*args, **kwargs)
85+
86+
set_up_logging()
87+
set_up_tracing()
88+
89+
tracer = trace.get_tracer(__name__)
90+
with tracer.start_as_current_span("main") as current_span:
91+
print(f"Trace ID: {format_trace_id(current_span.get_span_context().trace_id)}")
92+
return await func(*args, **kwargs)
93+
94+
return wrapper

python/samples/getting_started_with_agents/multi_agent_orchestration/step4c_handoff_mix_agent_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from azure.ai.projects.aio import AIProjectClient
66
from azure.identity.aio import DefaultAzureCredential
77

8+
from samples.getting_started_with_agents.multi_agent_orchestration.observability import enable_observability
89
from semantic_kernel.agents import (
910
Agent,
1011
AzureAIAgent,
@@ -180,6 +181,7 @@ def human_response_function() -> ChatMessageContent:
180181
return ChatMessageContent(role=AuthorRole.USER, content=user_input)
181182

182183

184+
@enable_observability
183185
async def main():
184186
"""Main function to run the agents."""
185187
# 0. Initialize the Azure AI agent clients

python/semantic_kernel/agents/azure_ai/azure_ai_agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
5959
trace_agent_get_response,
6060
trace_agent_invocation,
61+
trace_agent_streaming_invocation,
6162
)
6263
from semantic_kernel.utils.telemetry.user_agent import APP_INFO, SEMANTIC_KERNEL_USER_AGENT
6364

@@ -832,7 +833,7 @@ async def invoke(
832833
# Emit tool-related messages only via callback
833834
await on_intermediate_message(message)
834835

835-
@trace_agent_invocation
836+
@trace_agent_streaming_invocation
836837
@override
837838
async def invoke_stream(
838839
self,

python/semantic_kernel/agents/bedrock/bedrock_agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
4545
trace_agent_get_response,
4646
trace_agent_invocation,
47+
trace_agent_streaming_invocation,
4748
)
4849

4950
logger = logging.getLogger(__name__)
@@ -458,7 +459,7 @@ async def invoke(
458459
"Failed to get a response from the agent. Please consider increasing the auto invoke attempts."
459460
)
460461

461-
@trace_agent_invocation
462+
@trace_agent_streaming_invocation
462463
@override
463464
async def invoke_stream(
464465
self,

python/semantic_kernel/agents/chat_completion/chat_completion_agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
3535
trace_agent_get_response,
3636
trace_agent_invocation,
37+
trace_agent_streaming_invocation,
3738
)
3839

3940
if TYPE_CHECKING:
@@ -370,7 +371,7 @@ async def invoke(
370371
):
371372
yield AgentResponseItem(message=response, thread=thread)
372373

373-
@trace_agent_invocation
374+
@trace_agent_streaming_invocation
374375
@override
375376
async def invoke_stream(
376377
self,

python/semantic_kernel/agents/copilot_studio/copilot_studio_agent.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,10 @@
77
from pathlib import Path
88
from typing import TYPE_CHECKING, Any, ClassVar, Literal
99

10-
from microsoft.agents.copilotstudio.client import (
11-
AgentType,
12-
CopilotClient,
13-
PowerPlatformCloud,
14-
)
10+
from microsoft.agents.copilotstudio.client import AgentType, CopilotClient, PowerPlatformCloud
1511
from microsoft.agents.core.models import ActivityTypes
16-
from msal import (
17-
ConfidentialClientApplication,
18-
PublicClientApplication,
19-
)
20-
from msal_extensions import (
21-
FilePersistence,
22-
PersistedTokenCache,
23-
build_encrypted_persistence,
24-
)
12+
from msal import ConfidentialClientApplication, PublicClientApplication
13+
from msal_extensions import FilePersistence, PersistedTokenCache, build_encrypted_persistence
2514
from pydantic import ValidationError
2615

2716
from semantic_kernel.agents import Agent
@@ -47,6 +36,7 @@
4736
from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
4837
trace_agent_get_response,
4938
trace_agent_invocation,
39+
trace_agent_streaming_invocation,
5040
)
5141

5242
if sys.version_info >= (3, 12):
@@ -509,6 +499,7 @@ async def invoke(
509499
):
510500
yield AgentResponseItem(message=response, thread=thread)
511501

502+
@trace_agent_streaming_invocation
512503
@override
513504
async def invoke_stream(
514505
self,

python/semantic_kernel/agents/open_ai/openai_assistant_agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
5555
trace_agent_get_response,
5656
trace_agent_invocation,
57+
trace_agent_streaming_invocation,
5758
)
5859
from semantic_kernel.utils.telemetry.user_agent import APP_INFO, prepend_semantic_kernel_to_user_agent
5960

@@ -947,7 +948,7 @@ async def invoke(
947948
# Emit tool-related messages only via callback
948949
await on_intermediate_message(message)
949950

950-
@trace_agent_invocation
951+
@trace_agent_streaming_invocation
951952
@override
952953
async def invoke_stream(
953954
self,

python/semantic_kernel/agents/open_ai/openai_responses_agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from semantic_kernel.utils.telemetry.agent_diagnostics.decorators import (
4747
trace_agent_get_response,
4848
trace_agent_invocation,
49+
trace_agent_streaming_invocation,
4950
)
5051
from semantic_kernel.utils.telemetry.user_agent import APP_INFO, prepend_semantic_kernel_to_user_agent
5152

@@ -1040,7 +1041,7 @@ async def invoke(
10401041
# Emit tool-related messages only via callback
10411042
await on_intermediate_message(message)
10421043

1043-
@trace_agent_invocation
1044+
@trace_agent_streaming_invocation
10441045
@override
10451046
async def invoke_stream(
10461047
self,

0 commit comments

Comments
 (0)