diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/__init__.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/__init__.py index e19cf8e9..0ea6776a 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/__init__.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/__init__.py @@ -18,7 +18,7 @@ register_span_enricher, unregister_span_enricher, ) -from .inference_call_details import InferenceCallDetails +from .inference_call_details import InferenceCallDetails, ServiceEndpoint from .inference_operation_type import InferenceOperationType from .inference_scope import InferenceScope from .invoke_agent_details import InvokeAgentDetails @@ -61,6 +61,7 @@ "SourceMetadata", "Request", "InferenceCallDetails", + "ServiceEndpoint", # Enums "ExecutionType", "InferenceOperationType", diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/agent_details.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/agent_details.py index b09aa67f..4219a1bc 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/agent_details.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/agent_details.py @@ -4,8 +4,6 @@ from dataclasses import dataclass from typing import Optional -from .models.agent_type import AgentType - @dataclass class AgentDetails: @@ -29,8 +27,8 @@ class AgentDetails: agent_blueprint_id: Optional[str] = None """Blueprint/Application ID for the agent.""" - agent_type: Optional[AgentType] = None - """The agent type.""" + agent_platform_id: Optional[str] = None + """Platform ID for the agent.""" tenant_id: Optional[str] = None """Tenant ID for the agent.""" @@ -41,5 +39,8 @@ class AgentDetails: icon_uri: Optional[str] = None """Optional icon URI for the agent.""" - agent_client_ip: Optional[str] = None - """Client IP address of the agent user.""" + provider_name: Optional[str] = None + """The provider name (e.g., openai, anthropic).""" + + service_name: Optional[str] = None + """The service name for the agent.""" diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/constants.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/constants.py index 35ad5321..174e3c92 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/constants.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/constants.py @@ -6,6 +6,8 @@ # Span operation names INVOKE_AGENT_OPERATION_NAME = "invoke_agent" EXECUTE_TOOL_OPERATION_NAME = "execute_tool" +OUTPUT_MESSAGES_OPERATION_NAME = "output_messages" +CHAT_OPERATION_NAME = "chat" # OpenTelemetry semantic conventions ERROR_TYPE_KEY = "error.type" @@ -30,81 +32,78 @@ GEN_AI_REQUEST_MODEL_KEY = "gen_ai.request.model" GEN_AI_REQUEST_TEMPERATURE_KEY = "gen_ai.request.temperature" GEN_AI_REQUEST_TOP_P_KEY = "gen_ai.request.top_p" -GEN_AI_RESPONSE_ID_KEY = "gen_ai.response.id" GEN_AI_RESPONSE_FINISH_REASONS_KEY = "gen_ai.response.finish_reasons" GEN_AI_RESPONSE_MODEL_KEY = "gen_ai.response.model" -GEN_AI_SYSTEM_KEY = "gen_ai.system" -GEN_AI_SYSTEM_VALUE = "az.ai.agent365" -GEN_AI_THOUGHT_PROCESS_KEY = "gen_ai.agent.thought.process" - GEN_AI_AGENT_ID_KEY = "gen_ai.agent.id" GEN_AI_AGENT_NAME_KEY = "gen_ai.agent.name" GEN_AI_AGENT_DESCRIPTION_KEY = "gen_ai.agent.description" +GEN_AI_AGENT_PLATFORM_ID_KEY = "microsoft.a365.agent.platform.id" +GEN_AI_AGENT_THOUGHT_PROCESS_KEY = "microsoft.a365.agent.thought.process" GEN_AI_CONVERSATION_ID_KEY = "gen_ai.conversation.id" -GEN_AI_CONVERSATION_ITEM_LINK_KEY = "gen_ai.conversation.item.link" +GEN_AI_CONVERSATION_ITEM_LINK_KEY = "microsoft.conversation.item.link" GEN_AI_TOKEN_TYPE_KEY = "gen_ai.token.type" GEN_AI_USAGE_INPUT_TOKENS_KEY = "gen_ai.usage.input_tokens" GEN_AI_USAGE_OUTPUT_TOKENS_KEY = "gen_ai.usage.output_tokens" GEN_AI_CHOICE = "gen_ai.choice" GEN_AI_PROVIDER_NAME_KEY = "gen_ai.provider.name" -GEN_AI_AGENT_TYPE_KEY = "gen_ai.agent.type" GEN_AI_SYSTEM_INSTRUCTIONS_KEY = "gen_ai.system_instructions" GEN_AI_INPUT_MESSAGES_KEY = "gen_ai.input.messages" GEN_AI_OUTPUT_MESSAGES_KEY = "gen_ai.output.messages" -GEN_AI_EVENT_CONTENT = "gen_ai.event.content" # Tool execution constants GEN_AI_TOOL_CALL_ID_KEY = "gen_ai.tool.call.id" GEN_AI_TOOL_NAME_KEY = "gen_ai.tool.name" GEN_AI_TOOL_DESCRIPTION_KEY = "gen_ai.tool.description" -GEN_AI_TOOL_ARGS_KEY = "gen_ai.tool.arguments" -GEN_AI_TOOL_CALL_RESULT_KEY = GEN_AI_EVENT_CONTENT # GEN_AI_EVENT_CONTENT +GEN_AI_TOOL_ARGS_KEY = "gen_ai.tool.call.arguments" +GEN_AI_TOOL_CALL_RESULT_KEY = "gen_ai.tool.call.result" GEN_AI_TOOL_TYPE_KEY = "gen_ai.tool.type" -# Agent user(user tied to agent instance during creation) or caller dimensions -GEN_AI_AGENT_USER_ID_KEY = "gen_ai.agent.userid" -GEN_AI_CALLER_USER_ID_KEY = "gen_ai.caller.userid" -GEN_AI_CALLER_TENANT_ID_KEY = "gen_ai.caller.tenantid" -GEN_AI_CALLER_ID_KEY = "gen_ai.caller.id" -GEN_AI_CALLER_NAME_KEY = "gen_ai.caller.name" -GEN_AI_CALLER_UPN_KEY = "gen_ai.caller.upn" -GEN_AI_CALLER_CLIENT_IP_KEY = "gen_ai.caller.client.ip" +# Agent user (user tied to agent instance during creation) or caller dimensions +GEN_AI_CALLER_ID_KEY = "microsoft.caller.id" +GEN_AI_CALLER_NAME_KEY = "microsoft.caller.name" +GEN_AI_CALLER_UPN_KEY = "microsoft.caller.upn" +GEN_AI_CALLER_CLIENT_IP_KEY = "client.address" # Agent to Agent caller agent dimensions -GEN_AI_CALLER_AGENT_USER_ID_KEY = "gen_ai.caller.agent.userid" -GEN_AI_CALLER_AGENT_UPN_KEY = "gen_ai.caller.agent.upn" -GEN_AI_CALLER_AGENT_TENANT_ID_KEY = "gen_ai.caller.agent.tenantid" -GEN_AI_CALLER_AGENT_NAME_KEY = "gen_ai.caller.agent.name" -GEN_AI_CALLER_AGENT_ID_KEY = "gen_ai.caller.agent.id" -GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = "gen_ai.caller.agent.applicationid" -GEN_AI_CALLER_AGENT_TYPE_KEY = "gen_ai.caller.agent.type" -GEN_AI_CALLER_AGENT_USER_CLIENT_IP = "gen_ai.caller.agent.user.client.ip" +GEN_AI_CALLER_AGENT_USER_ID_KEY = "microsoft.a365.caller.agent.user.id" +GEN_AI_CALLER_AGENT_UPN_KEY = "microsoft.a365.caller.agent.user.upn" +GEN_AI_CALLER_AGENT_NAME_KEY = "microsoft.a365.caller.agent.name" +GEN_AI_CALLER_AGENT_ID_KEY = "microsoft.a365.caller.agent.id" +GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = "microsoft.a365.caller.agent.blueprint.id" +GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY = "microsoft.a365.caller.agent.platform.id" # Agent-specific dimensions AGENT_ID_KEY = "gen_ai.agent.id" GEN_AI_TASK_ID_KEY = "gen_ai.task.id" -SESSION_ID_KEY = "session.id" +SESSION_ID_KEY = "microsoft.session.id" +SESSION_DESCRIPTION_KEY = "microsoft.session.description" GEN_AI_ICON_URI_KEY = "gen_ai.agent365.icon_uri" -TENANT_ID_KEY = "tenant.id" +TENANT_ID_KEY = "microsoft.tenant.id" # Baggage keys -OPERATION_SOURCE_KEY = "operation.source" -GEN_AI_AGENT_AUID_KEY = "gen_ai.agent.user.id" -GEN_AI_AGENT_UPN_KEY = "gen_ai.agent.upn" -GEN_AI_AGENT_BLUEPRINT_ID_KEY = "gen_ai.agent.applicationid" -CORRELATION_ID_KEY = "correlation.id" -HIRING_MANAGER_ID_KEY = "hiring.manager.id" -SESSION_DESCRIPTION_KEY = "session.description" +GEN_AI_AGENT_AUID_KEY = "microsoft.agent.user.id" +GEN_AI_AGENT_UPN_KEY = "microsoft.agent.user.upn" +GEN_AI_AGENT_BLUEPRINT_ID_KEY = "microsoft.a365.agent.blueprint.id" # Execution context dimensions GEN_AI_EXECUTION_TYPE_KEY = "gen_ai.execution.type" GEN_AI_EXECUTION_PAYLOAD_KEY = "gen_ai.execution.payload" -# Source metadata dimensions -GEN_AI_EXECUTION_SOURCE_NAME_KEY = "gen_ai.channel.name" -GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY = "gen_ai.channel.link" +# Channel dimensions +CHANNEL_NAME_KEY = "microsoft.channel.name" +CHANNEL_LINK_KEY = "microsoft.channel.link" -# custom parent id and parent name key +# Custom parent id and parent name key CUSTOM_PARENT_SPAN_ID_KEY = "custom.parent.span.id" CUSTOM_SPAN_NAME_KEY = "custom.span.name" + +# Service attributes +SERVICE_NAME_KEY = "service.name" + +# Telemetry SDK attributes +TELEMETRY_SDK_NAME_KEY = "telemetry.sdk.name" +TELEMETRY_SDK_LANGUAGE_KEY = "telemetry.sdk.language" +TELEMETRY_SDK_VERSION_KEY = "telemetry.sdk.version" +TELEMETRY_SDK_NAME_VALUE = "A365ObservabilitySDK" +TELEMETRY_SDK_LANGUAGE_VALUE = "python" diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_call_details.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_call_details.py index d62c1eab..a8af76c2 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_call_details.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_call_details.py @@ -6,6 +6,17 @@ from .inference_operation_type import InferenceOperationType +@dataclass +class ServiceEndpoint: + """Represents a service endpoint with hostname and optional port.""" + + hostname: str + """The hostname of the service endpoint.""" + + port: int | None = None + """The port of the service endpoint.""" + + @dataclass class InferenceCallDetails: """Details of an inference call for generative AI operations.""" @@ -16,4 +27,5 @@ class InferenceCallDetails: inputTokens: int | None = None outputTokens: int | None = None finishReasons: list[str] | None = None - responseId: str | None = None + thoughtProcess: str | None = None + endpoint: ServiceEndpoint | None = None diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py index a137ff14..cbe67c7b 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py @@ -6,18 +6,19 @@ from .agent_details import AgentDetails from .constants import ( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, + GEN_AI_AGENT_THOUGHT_PROCESS_KEY, GEN_AI_INPUT_MESSAGES_KEY, GEN_AI_OPERATION_NAME_KEY, GEN_AI_OUTPUT_MESSAGES_KEY, GEN_AI_PROVIDER_NAME_KEY, GEN_AI_REQUEST_MODEL_KEY, GEN_AI_RESPONSE_FINISH_REASONS_KEY, - GEN_AI_RESPONSE_ID_KEY, - GEN_AI_THOUGHT_PROCESS_KEY, GEN_AI_USAGE_INPUT_TOKENS_KEY, GEN_AI_USAGE_OUTPUT_TOKENS_KEY, + SERVER_ADDRESS_KEY, + SERVER_PORT_KEY, ) from .inference_call_details import InferenceCallDetails from .opentelemetry_scope import OpenTelemetryScope @@ -100,23 +101,29 @@ def __init__( self.set_tag_maybe(GEN_AI_PROVIDER_NAME_KEY, details.providerName) self.set_tag_maybe( GEN_AI_USAGE_INPUT_TOKENS_KEY, - str(details.inputTokens) if details.inputTokens is not None else None, + details.inputTokens if details.inputTokens is not None else None, ) self.set_tag_maybe( GEN_AI_USAGE_OUTPUT_TOKENS_KEY, - str(details.outputTokens) if details.outputTokens is not None else None, + details.outputTokens if details.outputTokens is not None else None, ) self.set_tag_maybe( GEN_AI_RESPONSE_FINISH_REASONS_KEY, safe_json_dumps(details.finishReasons) if details.finishReasons else None, ) - self.set_tag_maybe(GEN_AI_RESPONSE_ID_KEY, details.responseId) + self.set_tag_maybe(GEN_AI_AGENT_THOUGHT_PROCESS_KEY, details.thoughtProcess) + + # Set endpoint information if provided + if details.endpoint: + self.set_tag_maybe(SERVER_ADDRESS_KEY, details.endpoint.hostname) + if details.endpoint.port: + self.set_tag_maybe(SERVER_PORT_KEY, str(details.endpoint.port)) # Set request metadata if provided if request and request.source_metadata: - self.set_tag_maybe(GEN_AI_EXECUTION_SOURCE_NAME_KEY, request.source_metadata.name) + self.set_tag_maybe(CHANNEL_NAME_KEY, request.source_metadata.name) self.set_tag_maybe( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, request.source_metadata.description + CHANNEL_LINK_KEY, request.source_metadata.description ) def record_input_messages(self, messages: List[str]) -> None: @@ -141,7 +148,7 @@ def record_input_tokens(self, input_tokens: int) -> None: Args: input_tokens: Number of input tokens """ - self.set_tag_maybe(GEN_AI_USAGE_INPUT_TOKENS_KEY, str(input_tokens)) + self.set_tag_maybe(GEN_AI_USAGE_INPUT_TOKENS_KEY, input_tokens) def record_output_tokens(self, output_tokens: int) -> None: """Records the number of output tokens for telemetry tracking. @@ -149,7 +156,7 @@ def record_output_tokens(self, output_tokens: int) -> None: Args: output_tokens: Number of output tokens """ - self.set_tag_maybe(GEN_AI_USAGE_OUTPUT_TOKENS_KEY, str(output_tokens)) + self.set_tag_maybe(GEN_AI_USAGE_OUTPUT_TOKENS_KEY, output_tokens) def record_finish_reasons(self, finish_reasons: List[str]) -> None: """Records the finish reasons for telemetry tracking. @@ -166,4 +173,4 @@ def record_thought_process(self, thought_process: str) -> None: Args: thought_process: The thought process to record """ - self.set_tag_maybe(GEN_AI_THOUGHT_PROCESS_KEY, thought_process) + self.set_tag_maybe(GEN_AI_AGENT_THOUGHT_PROCESS_KEY, thought_process) diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/invoke_agent_scope.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/invoke_agent_scope.py index fa8b8abe..b3496bd7 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/invoke_agent_scope.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/invoke_agent_scope.py @@ -8,21 +8,18 @@ from .agent_details import AgentDetails from .constants import ( + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, GEN_AI_CALLER_AGENT_ID_KEY, GEN_AI_CALLER_AGENT_NAME_KEY, - GEN_AI_CALLER_AGENT_TENANT_ID_KEY, - GEN_AI_CALLER_AGENT_TYPE_KEY, + GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY, GEN_AI_CALLER_AGENT_UPN_KEY, - GEN_AI_CALLER_AGENT_USER_CLIENT_IP, GEN_AI_CALLER_AGENT_USER_ID_KEY, + GEN_AI_CALLER_CLIENT_IP_KEY, GEN_AI_CALLER_ID_KEY, GEN_AI_CALLER_NAME_KEY, - GEN_AI_CALLER_TENANT_ID_KEY, GEN_AI_CALLER_UPN_KEY, - GEN_AI_CALLER_USER_ID_KEY, - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, GEN_AI_EXECUTION_TYPE_KEY, GEN_AI_INPUT_MESSAGES_KEY, GEN_AI_OUTPUT_MESSAGES_KEY, @@ -133,9 +130,9 @@ def __init__( # Set request metadata if provided if request: if request.source_metadata: - self.set_tag_maybe(GEN_AI_EXECUTION_SOURCE_NAME_KEY, request.source_metadata.name) + self.set_tag_maybe(CHANNEL_NAME_KEY, request.source_metadata.name) self.set_tag_maybe( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, request.source_metadata.description + CHANNEL_LINK_KEY, request.source_metadata.description ) self.set_tag_maybe( @@ -149,27 +146,23 @@ def __init__( self.set_tag_maybe(GEN_AI_CALLER_ID_KEY, caller_details.caller_id) self.set_tag_maybe(GEN_AI_CALLER_UPN_KEY, caller_details.caller_upn) self.set_tag_maybe(GEN_AI_CALLER_NAME_KEY, caller_details.caller_name) - self.set_tag_maybe(GEN_AI_CALLER_USER_ID_KEY, caller_details.caller_user_id) - self.set_tag_maybe(GEN_AI_CALLER_TENANT_ID_KEY, caller_details.tenant_id) + # Validate and set caller client IP + self.set_tag_maybe( + GEN_AI_CALLER_CLIENT_IP_KEY, + validate_and_normalize_ip(caller_details.caller_client_ip), + ) # Set caller agent details tags if caller_agent_details: self.set_tag_maybe(GEN_AI_CALLER_AGENT_NAME_KEY, caller_agent_details.agent_name) self.set_tag_maybe(GEN_AI_CALLER_AGENT_ID_KEY, caller_agent_details.agent_id) - self.set_tag_maybe( - GEN_AI_CALLER_AGENT_TYPE_KEY, - caller_agent_details.agent_type.value if caller_agent_details.agent_type else None, - ) self.set_tag_maybe( GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, caller_agent_details.agent_blueprint_id ) self.set_tag_maybe(GEN_AI_CALLER_AGENT_USER_ID_KEY, caller_agent_details.agent_auid) self.set_tag_maybe(GEN_AI_CALLER_AGENT_UPN_KEY, caller_agent_details.agent_upn) - self.set_tag_maybe(GEN_AI_CALLER_AGENT_TENANT_ID_KEY, caller_agent_details.tenant_id) - # Validate and set caller agent client IP self.set_tag_maybe( - GEN_AI_CALLER_AGENT_USER_CLIENT_IP, - validate_and_normalize_ip(caller_agent_details.agent_client_ip), + GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY, caller_agent_details.agent_platform_id ) def record_response(self, response: str) -> None: diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/middleware/baggage_builder.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/middleware/baggage_builder.py index 368f7119..d227445f 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/middleware/baggage_builder.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/middleware/baggage_builder.py @@ -9,7 +9,8 @@ from opentelemetry import baggage, context from ..constants import ( - CORRELATION_ID_KEY, + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, GEN_AI_AGENT_AUID_KEY, GEN_AI_AGENT_BLUEPRINT_ID_KEY, GEN_AI_AGENT_DESCRIPTION_KEY, @@ -22,15 +23,10 @@ GEN_AI_CALLER_UPN_KEY, GEN_AI_CONVERSATION_ID_KEY, GEN_AI_CONVERSATION_ITEM_LINK_KEY, - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, - HIRING_MANAGER_ID_KEY, - OPERATION_SOURCE_KEY, SESSION_DESCRIPTION_KEY, SESSION_ID_KEY, TENANT_ID_KEY, ) -from ..models.operation_source import OperationSource from ..utils import deprecated, validate_and_normalize_ip logger = logging.getLogger(__name__) @@ -47,8 +43,7 @@ class BaggageBuilder: builder = (BaggageBuilder() .tenant_id("tenant-123") - .agent_id("agent-456") - .correlation_id("corr-789")) + .agent_id("agent-456")) with builder.build(): # Baggage is set in this context @@ -60,20 +55,6 @@ def __init__(self): """Initialize the baggage builder.""" self._pairs: dict[str, str] = {} - def operation_source(self, value: OperationSource | None) -> "BaggageBuilder": - """Set the operation source baggage value. - - Args: - value: The operation source enum value - - Returns: - Self for method chaining - """ - # Convert enum to string value for baggage storage - str_value = value.value if value is not None else None - self._set(OPERATION_SOURCE_KEY, str_value) - return self - def tenant_id(self, value: str | None) -> "BaggageBuilder": """Set the tenant ID baggage value. @@ -134,18 +115,6 @@ def agent_blueprint_id(self, value: str | None) -> "BaggageBuilder": self._set(GEN_AI_AGENT_BLUEPRINT_ID_KEY, value) return self - def correlation_id(self, value: str | None) -> "BaggageBuilder": - """Set the correlation ID baggage value. - - Args: - value: The correlation ID - - Returns: - Self for method chaining - """ - self._set(CORRELATION_ID_KEY, value) - return self - def caller_id(self, value: str | None) -> "BaggageBuilder": """Set the caller ID baggage value. @@ -158,18 +127,6 @@ def caller_id(self, value: str | None) -> "BaggageBuilder": self._set(GEN_AI_CALLER_ID_KEY, value) return self - def hiring_manager_id(self, value: str | None) -> "BaggageBuilder": - """Set the hiring manager ID baggage value. - - Args: - value: The hiring manager ID - - Returns: - Self for method chaining - """ - self._set(HIRING_MANAGER_ID_KEY, value) - return self - def agent_name(self, value: str | None) -> "BaggageBuilder": """Set the agent name baggage value.""" self._set(GEN_AI_AGENT_NAME_KEY, value) @@ -227,12 +184,12 @@ def session_description(self, value: str | None) -> "BaggageBuilder": def channel_name(self, value: str | None) -> "BaggageBuilder": """Sets the channel name baggage value (e.g., 'Teams', 'msteams').""" - self._set(GEN_AI_EXECUTION_SOURCE_NAME_KEY, value) + self._set(CHANNEL_NAME_KEY, value) return self def channel_links(self, value: str | None) -> "BaggageBuilder": """Sets the channel link baggage value. (e.g., channel links or description).""" - self._set(GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, value) + self._set(CHANNEL_LINK_KEY, value) return self def set_pairs(self, pairs: Any) -> "BaggageBuilder": @@ -273,14 +230,12 @@ def _set(self, key: str, value: str | None) -> None: def set_request_context( tenant_id: str | None = None, agent_id: str | None = None, - correlation_id: str | None = None, ) -> "BaggageScope": """Convenience method to begin a request baggage scope with common fields. Args: tenant_id: The tenant ID agent_id: The agent ID - correlation_id: The correlation ID Returns: A context manager that restores the previous baggage on exit @@ -289,7 +244,6 @@ def set_request_context( BaggageBuilder() .tenant_id(tenant_id) .agent_id(agent_id) - .correlation_id(correlation_id) .build() ) diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/models/caller_details.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/models/caller_details.py index 38bb1113..eee3fc9d 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/models/caller_details.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/models/caller_details.py @@ -18,8 +18,5 @@ class CallerDetails: caller_name: Optional[str] = None """The human-readable name of the caller.""" - caller_user_id: Optional[str] = None - """The user ID of the caller.""" - - tenant_id: Optional[str] = None - """The tenant ID of the caller.""" + caller_client_ip: Optional[str] = None + """The client IP address of the caller.""" diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/opentelemetry_scope.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/opentelemetry_scope.py index e0365e9a..d415f765 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/opentelemetry_scope.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/opentelemetry_scope.py @@ -28,15 +28,20 @@ GEN_AI_AGENT_DESCRIPTION_KEY, GEN_AI_AGENT_ID_KEY, GEN_AI_AGENT_NAME_KEY, - GEN_AI_AGENT_TYPE_KEY, + GEN_AI_AGENT_PLATFORM_ID_KEY, GEN_AI_AGENT_UPN_KEY, GEN_AI_CONVERSATION_ID_KEY, - GEN_AI_EVENT_CONTENT, GEN_AI_ICON_URI_KEY, GEN_AI_OPERATION_NAME_KEY, - GEN_AI_SYSTEM_KEY, - GEN_AI_SYSTEM_VALUE, + GEN_AI_OUTPUT_MESSAGES_KEY, + GEN_AI_PROVIDER_NAME_KEY, + SERVICE_NAME_KEY, SOURCE_NAME, + TELEMETRY_SDK_LANGUAGE_KEY, + TELEMETRY_SDK_LANGUAGE_VALUE, + TELEMETRY_SDK_NAME_KEY, + TELEMETRY_SDK_NAME_VALUE, + TELEMETRY_SDK_VERSION_KEY, TENANT_ID_KEY, ) from .utils import parse_parent_id_to_context @@ -86,6 +91,20 @@ def _datetime_to_ns(dt: datetime | None) -> int | None: return None return int(dt.timestamp() * 1_000_000_000) + @staticmethod + def _get_sdk_version() -> str: + """Get the SDK version from package metadata. + + Returns: + The SDK version string, or "0.0.0-unknown" if not found + """ + try: + from importlib.metadata import version + + return version("microsoft-agents-a365-observability-core") + except Exception: + return "0.0.0-unknown" + def __init__( self, kind: str, @@ -160,9 +179,13 @@ def __init__( # Set common tags if self._span: - self._span.set_attribute(GEN_AI_SYSTEM_KEY, GEN_AI_SYSTEM_VALUE) self._span.set_attribute(GEN_AI_OPERATION_NAME_KEY, operation_name) + # Set telemetry SDK attributes + self._span.set_attribute(TELEMETRY_SDK_NAME_KEY, TELEMETRY_SDK_NAME_VALUE) + self._span.set_attribute(TELEMETRY_SDK_LANGUAGE_KEY, TELEMETRY_SDK_LANGUAGE_VALUE) + self._span.set_attribute(TELEMETRY_SDK_VERSION_KEY, self._get_sdk_version()) + # Set agent details if provided if agent_details: self.set_tag_maybe(GEN_AI_AGENT_ID_KEY, agent_details.agent_id) @@ -176,12 +199,15 @@ def __init__( GEN_AI_AGENT_BLUEPRINT_ID_KEY, agent_details.agent_blueprint_id ) self.set_tag_maybe( - GEN_AI_AGENT_TYPE_KEY, - agent_details.agent_type.value if agent_details.agent_type else None, + GEN_AI_AGENT_PLATFORM_ID_KEY, agent_details.agent_platform_id ) self.set_tag_maybe(TENANT_ID_KEY, agent_details.tenant_id) self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, agent_details.conversation_id) self.set_tag_maybe(GEN_AI_ICON_URI_KEY, agent_details.icon_uri) + # Set provider name dynamically from agent details + self.set_tag_maybe(GEN_AI_PROVIDER_NAME_KEY, agent_details.provider_name) + # Set service name from agent details if available + self.set_tag_maybe(SERVICE_NAME_KEY, agent_details.service_name) # Set tenant details if provided if tenant_details: @@ -207,7 +233,7 @@ def record_response(self, response: str) -> None: response: The response content to record """ if self._span and self._is_telemetry_enabled(): - self._span.set_attribute(GEN_AI_EVENT_CONTENT, response) + self._span.set_attribute(GEN_AI_OUTPUT_MESSAGES_KEY, response) def record_cancellation(self) -> None: """Record task cancellation.""" diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/span_processor.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/span_processor.py index ed1d331a..351e45e9 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/span_processor.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/span_processor.py @@ -17,8 +17,7 @@ from opentelemetry import baggage, context from opentelemetry.sdk.trace import SpanProcessor as BaseSpanProcessor -from ..constants import GEN_AI_OPERATION_NAME_KEY, INVOKE_AGENT_OPERATION_NAME, OPERATION_SOURCE_KEY -from ..models.operation_source import OperationSource +from ..constants import GEN_AI_OPERATION_NAME_KEY, INVOKE_AGENT_OPERATION_NAME from .util import COMMON_ATTRIBUTES, INVOKE_AGENT_ATTRIBUTES @@ -43,14 +42,6 @@ def on_start(self, span, parent_context=None): except Exception: baggage_map = {} - # Set operation source - coalesce baggage value with SDK default - if OPERATION_SOURCE_KEY not in existing: - operation_source = baggage_map.get(OPERATION_SOURCE_KEY) or OperationSource.SDK.value - try: - span.set_attribute(OPERATION_SOURCE_KEY, operation_source) - except Exception: - pass - operation_name = existing.get(GEN_AI_OPERATION_NAME_KEY) is_invoke_agent = False if operation_name == INVOKE_AGENT_OPERATION_NAME: diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/util.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/util.py index fa50a110..b176d22b 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/util.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/trace_processor/util.py @@ -5,46 +5,39 @@ # Generic / common tracing attributes COMMON_ATTRIBUTES = [ - consts.TENANT_ID_KEY, # tenant.id + consts.TENANT_ID_KEY, # microsoft.tenant.id consts.CUSTOM_PARENT_SPAN_ID_KEY, # custom.parent.span.id consts.CUSTOM_SPAN_NAME_KEY, # custom.span.name - consts.CORRELATION_ID_KEY, # correlation.id - consts.GEN_AI_CONVERSATION_ID_KEY, # conversation.id - consts.GEN_AI_CONVERSATION_ITEM_LINK_KEY, # conversation.itemLink + consts.GEN_AI_CONVERSATION_ID_KEY, # gen_ai.conversation.id + consts.GEN_AI_CONVERSATION_ITEM_LINK_KEY, # microsoft.conversation.item.link consts.GEN_AI_OPERATION_NAME_KEY, # gen_ai.operation.name consts.GEN_AI_AGENT_ID_KEY, # gen_ai.agent.id consts.GEN_AI_AGENT_NAME_KEY, # gen_ai.agent.name consts.GEN_AI_AGENT_DESCRIPTION_KEY, # gen_ai.agent.description - consts.GEN_AI_AGENT_USER_ID_KEY, # gen_ai.agent.userid - consts.GEN_AI_AGENT_UPN_KEY, # gen_ai.agent.upn - consts.GEN_AI_AGENT_BLUEPRINT_ID_KEY, # gen_ai.agent.applicationid - consts.GEN_AI_AGENT_AUID_KEY, - consts.GEN_AI_AGENT_TYPE_KEY, - consts.OPERATION_SOURCE_KEY, # operation.source - consts.SESSION_ID_KEY, - consts.SESSION_DESCRIPTION_KEY, - consts.HIRING_MANAGER_ID_KEY, - consts.GEN_AI_CALLER_CLIENT_IP_KEY, # gen_ai.caller.client.ip - # Execution context - consts.GEN_AI_EXECUTION_SOURCE_NAME_KEY, # gen_ai.channel.name - consts.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, # gen_ai.channel.link + consts.GEN_AI_AGENT_UPN_KEY, # microsoft.agent.user.upn + consts.GEN_AI_AGENT_BLUEPRINT_ID_KEY, # microsoft.a365.agent.blueprint.id + consts.GEN_AI_AGENT_AUID_KEY, # microsoft.agent.user.id + consts.GEN_AI_AGENT_PLATFORM_ID_KEY, # microsoft.a365.agent.platform.id + consts.SESSION_ID_KEY, # microsoft.session.id + consts.SESSION_DESCRIPTION_KEY, # microsoft.session.description + consts.GEN_AI_CALLER_CLIENT_IP_KEY, # client.address + # Channel dimensions + consts.CHANNEL_NAME_KEY, # microsoft.channel.name + consts.CHANNEL_LINK_KEY, # microsoft.channel.link ] # Invoke Agent–specific attributes INVOKE_AGENT_ATTRIBUTES = [ # Caller / Invoker attributes - consts.GEN_AI_CALLER_ID_KEY, # gen_ai.caller.id - consts.GEN_AI_CALLER_NAME_KEY, # gen_ai.caller.name - consts.GEN_AI_CALLER_UPN_KEY, # gen_ai.caller.upn - consts.GEN_AI_CALLER_USER_ID_KEY, # gen_ai.caller.userid - consts.GEN_AI_CALLER_TENANT_ID_KEY, # gen_ai.caller.tenantid + consts.GEN_AI_CALLER_ID_KEY, # microsoft.caller.id + consts.GEN_AI_CALLER_NAME_KEY, # microsoft.caller.name + consts.GEN_AI_CALLER_UPN_KEY, # microsoft.caller.upn # Caller Agent (A2A) attributes - consts.GEN_AI_CALLER_AGENT_ID_KEY, # gen_ai.caller.agent.id - consts.GEN_AI_CALLER_AGENT_NAME_KEY, # gen_ai.caller.agent.name - consts.GEN_AI_CALLER_AGENT_TYPE_KEY, # gen_ai.caller.agent.type - consts.GEN_AI_CALLER_AGENT_USER_ID_KEY, # gen_ai.caller.agent.userid - consts.GEN_AI_CALLER_AGENT_UPN_KEY, # gen_ai.caller.agent.upn - consts.GEN_AI_CALLER_AGENT_TENANT_ID_KEY, # gen_ai.caller.agent.tenantid - consts.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, # gen_ai.caller.agent.applicationid + consts.GEN_AI_CALLER_AGENT_ID_KEY, # microsoft.a365.caller.agent.id + consts.GEN_AI_CALLER_AGENT_NAME_KEY, # microsoft.a365.caller.agent.name + consts.GEN_AI_CALLER_AGENT_USER_ID_KEY, # microsoft.a365.caller.agent.user.id + consts.GEN_AI_CALLER_AGENT_UPN_KEY, # microsoft.a365.caller.agent.user.upn + consts.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, # microsoft.a365.caller.agent.blueprint.id + consts.GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY, # microsoft.a365.caller.agent.platform.id consts.GEN_AI_EXECUTION_TYPE_KEY, # gen_ai.execution.type ] diff --git a/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/trace_processor.py b/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/trace_processor.py index a9c775b3..777d3f0d 100644 --- a/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/trace_processor.py +++ b/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/trace_processor.py @@ -26,8 +26,8 @@ GEN_AI_INPUT_MESSAGES_KEY, GEN_AI_OPERATION_NAME_KEY, GEN_AI_OUTPUT_MESSAGES_KEY, + GEN_AI_PROVIDER_NAME_KEY, GEN_AI_REQUEST_MODEL_KEY, - GEN_AI_SYSTEM_KEY, GEN_AI_TOOL_CALL_ID_KEY, GEN_AI_TOOL_TYPE_KEY, INVOKE_AGENT_OPERATION_NAME, @@ -149,7 +149,7 @@ def on_span_start(self, span: Span[Any]) -> None: start_time=as_utc_nano(start_time), attributes={ GEN_AI_OPERATION_NAME_KEY: get_span_kind(span.span_data), - GEN_AI_SYSTEM_KEY: "openai", + GEN_AI_PROVIDER_NAME_KEY: "openai", }, ) self._otel_spans[span.span_id] = otel_span diff --git a/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/utils.py b/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/utils.py index eb9c0b07..e01043e8 100644 --- a/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/utils.py +++ b/libraries/microsoft-agents-a365-observability-extensions-openai/microsoft_agents_a365/observability/extensions/openai/utils.py @@ -7,7 +7,6 @@ from collections.abc import Iterable, Iterator, Mapping from typing import TYPE_CHECKING, Any, assert_never -from urllib.parse import urlparse from agents import MCPListToolsSpanData from agents.tracing import Span @@ -23,15 +22,12 @@ ) from microsoft_agents_a365.observability.core.constants import ( GEN_AI_CHOICE, - GEN_AI_EVENT_CONTENT, GEN_AI_EXECUTION_PAYLOAD_KEY, GEN_AI_INPUT_MESSAGES_KEY, GEN_AI_OUTPUT_MESSAGES_KEY, GEN_AI_PROVIDER_NAME_KEY, GEN_AI_REQUEST_MODEL_KEY, GEN_AI_RESPONSE_FINISH_REASONS_KEY, - GEN_AI_RESPONSE_ID_KEY, - GEN_AI_SYSTEM_KEY, GEN_AI_TOOL_ARGS_KEY, GEN_AI_TOOL_CALL_ID_KEY, GEN_AI_TOOL_CALL_RESULT_KEY, @@ -218,10 +214,6 @@ def get_attributes_from_generation_span_data( param := {k: v for k, v in obj.model_config.items() if v is not None} ): yield GEN_AI_EXECUTION_PAYLOAD_KEY, safe_json_dumps(param) - if base_url := param.get("base_url"): - parsed = urlparse(base_url) - if parsed.hostname == "api.openai.com": - yield GEN_AI_SYSTEM_KEY, "openai" yield from _get_attributes_from_chat_completions_input(obj.input) yield from _get_attributes_from_chat_completions_output(obj.output) yield from _get_attributes_from_chat_completions_usage(obj.usage) @@ -258,9 +250,6 @@ def _get_attributes_from_chat_completions_output( except Exception: pass - if isinstance(obj, Mapping) and "id" in obj: - yield GEN_AI_RESPONSE_ID_KEY, obj["id"] - # Collect all finish_reason values finish_reasons = [ message.get("finish_reason") for message in obj if message.get("finish_reason") is not None @@ -368,7 +357,7 @@ def get_attributes_from_function_span_data( if obj.input: yield GEN_AI_TOOL_ARGS_KEY, obj.input if obj.output is not None: - yield GEN_AI_EVENT_CONTENT, _convert_to_primitive(obj.output) + yield GEN_AI_TOOL_CALL_RESULT_KEY, _convert_to_primitive(obj.output) def get_attributes_from_message_content_list( diff --git a/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/middleware/output_logging_middleware.py b/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/middleware/output_logging_middleware.py index 5bba6045..36730755 100644 --- a/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/middleware/output_logging_middleware.py +++ b/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/middleware/output_logging_middleware.py @@ -12,13 +12,12 @@ from microsoft_agents.hosting.core.turn_context import TurnContext from microsoft_agents_a365.observability.core.agent_details import AgentDetails from microsoft_agents_a365.observability.core.constants import ( + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, GEN_AI_CALLER_ID_KEY, GEN_AI_CALLER_NAME_KEY, - GEN_AI_CALLER_TENANT_ID_KEY, GEN_AI_CALLER_UPN_KEY, GEN_AI_CONVERSATION_ID_KEY, - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, GEN_AI_EXECUTION_TYPE_KEY, ) from microsoft_agents_a365.observability.core.models.caller_details import CallerDetails @@ -74,7 +73,6 @@ def _derive_caller_details(context: TurnContext) -> CallerDetails | None: caller_id=getattr(frm, "aad_object_id", None), caller_upn=getattr(frm, "agentic_user_id", None), caller_name=getattr(frm, "name", None), - tenant_id=getattr(frm, "tenant_id", None), ) @@ -196,17 +194,16 @@ async def handler( output_scope.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, conversation_id) output_scope.set_tag_maybe(GEN_AI_EXECUTION_TYPE_KEY, execution_type) output_scope.set_tag_maybe( - GEN_AI_EXECUTION_SOURCE_NAME_KEY, source_metadata.get("name") + CHANNEL_NAME_KEY, source_metadata.get("name") ) output_scope.set_tag_maybe( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, source_metadata.get("description") + CHANNEL_LINK_KEY, source_metadata.get("description") ) if caller_details: output_scope.set_tag_maybe(GEN_AI_CALLER_ID_KEY, caller_details.caller_id) output_scope.set_tag_maybe(GEN_AI_CALLER_UPN_KEY, caller_details.caller_upn) output_scope.set_tag_maybe(GEN_AI_CALLER_NAME_KEY, caller_details.caller_name) - output_scope.set_tag_maybe(GEN_AI_CALLER_TENANT_ID_KEY, caller_details.tenant_id) try: await send_next() diff --git a/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/scope_helpers/utils.py b/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/scope_helpers/utils.py index b2b5eb9a..a875bd06 100644 --- a/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/scope_helpers/utils.py +++ b/libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/scope_helpers/utils.py @@ -5,6 +5,8 @@ from microsoft_agents.activity import Activity from microsoft_agents_a365.observability.core.constants import ( + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, GEN_AI_AGENT_AUID_KEY, GEN_AI_AGENT_DESCRIPTION_KEY, GEN_AI_AGENT_ID_KEY, @@ -12,12 +14,9 @@ GEN_AI_AGENT_UPN_KEY, GEN_AI_CALLER_ID_KEY, GEN_AI_CALLER_NAME_KEY, - GEN_AI_CALLER_TENANT_ID_KEY, GEN_AI_CALLER_UPN_KEY, GEN_AI_CONVERSATION_ID_KEY, GEN_AI_CONVERSATION_ITEM_LINK_KEY, - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, GEN_AI_EXECUTION_TYPE_KEY, TENANT_ID_KEY, ) @@ -42,7 +41,6 @@ def get_caller_pairs(activity: Activity) -> Iterator[tuple[str, Any]]: yield GEN_AI_CALLER_ID_KEY, frm.aad_object_id yield GEN_AI_CALLER_NAME_KEY, frm.name yield GEN_AI_CALLER_UPN_KEY, frm.agentic_user_id - yield GEN_AI_CALLER_TENANT_ID_KEY, frm.tenant_id def get_execution_type_pair(activity: Activity) -> Iterator[tuple[str, Any]]: @@ -100,8 +98,8 @@ def get_source_metadata_pairs(activity: Activity) -> Iterator[tuple[str, Any]]: sub_channel = channel_id.sub_channel # Yield channel name as source name - yield GEN_AI_EXECUTION_SOURCE_NAME_KEY, channel_name - yield GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, sub_channel + yield CHANNEL_NAME_KEY, channel_name + yield CHANNEL_LINK_KEY, sub_channel def get_conversation_pairs(activity: Activity) -> Iterator[tuple[str, Any]]: diff --git a/tests/observability/core/test_baggage_builder.py b/tests/observability/core/test_baggage_builder.py index 937b3085..93acfaab 100644 --- a/tests/observability/core/test_baggage_builder.py +++ b/tests/observability/core/test_baggage_builder.py @@ -5,23 +5,19 @@ import unittest from microsoft_agents_a365.observability.core.constants import ( - CORRELATION_ID_KEY, + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, GEN_AI_AGENT_AUID_KEY, GEN_AI_AGENT_BLUEPRINT_ID_KEY, GEN_AI_AGENT_ID_KEY, GEN_AI_AGENT_UPN_KEY, GEN_AI_CALLER_CLIENT_IP_KEY, GEN_AI_CALLER_ID_KEY, - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, - HIRING_MANAGER_ID_KEY, - OPERATION_SOURCE_KEY, SESSION_DESCRIPTION_KEY, SESSION_ID_KEY, TENANT_ID_KEY, ) from microsoft_agents_a365.observability.core.middleware.baggage_builder import BaggageBuilder -from microsoft_agents_a365.observability.core.models.operation_source import OperationSource from opentelemetry import baggage, context, trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor @@ -66,50 +62,41 @@ def test_baggage_builder_sets_values(self): """Test that BaggageBuilder sets baggage values correctly.""" tenant = "tenant-1" agent = "agent-1" - corr = "corr-1" # Use the baggage builder within a context - with BaggageBuilder().tenant_id(tenant).agent_id(agent).correlation_id(corr).build(): + with BaggageBuilder().tenant_id(tenant).agent_id(agent).build(): # Assert inside scope - baggage should be set current_baggage = baggage.get_all() self.assertEqual(current_baggage.get(TENANT_ID_KEY), tenant) self.assertEqual(current_baggage.get(GEN_AI_AGENT_ID_KEY), agent) - self.assertEqual(current_baggage.get(CORRELATION_ID_KEY), corr) # Assert after exiting scope - baggage should be restored/cleared current_baggage = baggage.get_all() self.assertIsNone(current_baggage.get(TENANT_ID_KEY)) self.assertIsNone(current_baggage.get(GEN_AI_AGENT_ID_KEY)) - self.assertIsNone(current_baggage.get(CORRELATION_ID_KEY)) print("✅ BaggageBuilder sets and restores values correctly!") def test_all_baggage_keys(self): """Test all baggage key setter methods.""" with ( BaggageBuilder() - .operation_source(OperationSource.SDK) .tenant_id("tenant-1") .agent_id("agent-1") .agent_auid("auid-1") .agent_upn("upn-1") .agent_blueprint_id("blueprint-1") - .correlation_id("corr-1") .caller_id("caller-1") .caller_client_ip("192.168.1.100") - .hiring_manager_id("manager-1") .build() ): current_baggage = baggage.get_all() - self.assertEqual(current_baggage.get(OPERATION_SOURCE_KEY), OperationSource.SDK.value) self.assertEqual(current_baggage.get(TENANT_ID_KEY), "tenant-1") self.assertEqual(current_baggage.get(GEN_AI_AGENT_ID_KEY), "agent-1") self.assertEqual(current_baggage.get(GEN_AI_AGENT_AUID_KEY), "auid-1") self.assertEqual(current_baggage.get(GEN_AI_AGENT_UPN_KEY), "upn-1") self.assertEqual(current_baggage.get(GEN_AI_AGENT_BLUEPRINT_ID_KEY), "blueprint-1") - self.assertEqual(current_baggage.get(CORRELATION_ID_KEY), "corr-1") self.assertEqual(current_baggage.get(GEN_AI_CALLER_ID_KEY), "caller-1") self.assertEqual(current_baggage.get(GEN_AI_CALLER_CLIENT_IP_KEY), "192.168.1.100") - self.assertEqual(current_baggage.get(HIRING_MANAGER_ID_KEY), "manager-1") print("✅ All baggage keys work correctly!") def test_baggage_propagates_to_child_spans(self): @@ -163,28 +150,22 @@ def test_baggage_reset_after_scope_exit(self): # Use BaggageBuilder to set all possible values with ( BaggageBuilder() - .operation_source(OperationSource.SDK) .tenant_id("test-tenant") .agent_id("test-agent") .agent_auid("test-auid") .agent_upn("test-upn") .agent_blueprint_id("test-blueprint") - .correlation_id("test-correlation") .caller_id("test-caller") - .hiring_manager_id("test-manager") .build() ): # Inside scope - verify all baggage values are set scoped_baggage = baggage.get_all() - self.assertEqual(scoped_baggage.get(OPERATION_SOURCE_KEY), OperationSource.SDK.value) self.assertEqual(scoped_baggage.get(TENANT_ID_KEY), "test-tenant") self.assertEqual(scoped_baggage.get(GEN_AI_AGENT_ID_KEY), "test-agent") self.assertEqual(scoped_baggage.get(GEN_AI_AGENT_AUID_KEY), "test-auid") self.assertEqual(scoped_baggage.get(GEN_AI_AGENT_UPN_KEY), "test-upn") self.assertEqual(scoped_baggage.get(GEN_AI_AGENT_BLUEPRINT_ID_KEY), "test-blueprint") - self.assertEqual(scoped_baggage.get(CORRELATION_ID_KEY), "test-correlation") self.assertEqual(scoped_baggage.get(GEN_AI_CALLER_ID_KEY), "test-caller") - self.assertEqual(scoped_baggage.get(HIRING_MANAGER_ID_KEY), "test-manager") # Original baggage should still exist self.assertEqual(scoped_baggage.get("existing_key"), "existing_value") @@ -192,15 +173,12 @@ def test_baggage_reset_after_scope_exit(self): final_baggage = baggage.get_all() # All BaggageBuilder keys should be None/cleared - self.assertIsNone(final_baggage.get(OPERATION_SOURCE_KEY)) self.assertIsNone(final_baggage.get(TENANT_ID_KEY)) self.assertIsNone(final_baggage.get(GEN_AI_AGENT_ID_KEY)) self.assertIsNone(final_baggage.get(GEN_AI_AGENT_AUID_KEY)) self.assertIsNone(final_baggage.get(GEN_AI_AGENT_UPN_KEY)) self.assertIsNone(final_baggage.get(GEN_AI_AGENT_BLUEPRINT_ID_KEY)) - self.assertIsNone(final_baggage.get(CORRELATION_ID_KEY)) self.assertIsNone(final_baggage.get(GEN_AI_CALLER_ID_KEY)) - self.assertIsNone(final_baggage.get(HIRING_MANAGER_ID_KEY)) # Original baggage should be restored self.assertEqual(final_baggage.get("existing_key"), "existing_value") @@ -212,7 +190,6 @@ def test_set_pairs_accepts_dict_and_iterable(self): dict_pairs = { TENANT_ID_KEY: "tenant-x", GEN_AI_AGENT_ID_KEY: "agent-x", - CORRELATION_ID_KEY: "corr-x", } iter_pairs = [ (GEN_AI_AGENT_AUID_KEY, "auid-x"), @@ -221,11 +198,10 @@ def test_set_pairs_accepts_dict_and_iterable(self): # Also verify that None / whitespace values are ignored dict_pairs_with_ignored = { - OPERATION_SOURCE_KEY: OperationSource.SDK.value, GEN_AI_CALLER_ID_KEY: None, # ignored } iter_pairs_with_ignored = [ - (HIRING_MANAGER_ID_KEY, " "), # ignored (whitespace) + (SESSION_ID_KEY, " "), # ignored (whitespace) ] with ( @@ -239,13 +215,11 @@ def test_set_pairs_accepts_dict_and_iterable(self): baggage_contents = baggage.get_all() self.assertEqual(baggage_contents.get(TENANT_ID_KEY), "tenant-x") self.assertEqual(baggage_contents.get(GEN_AI_AGENT_ID_KEY), "agent-x") - self.assertEqual(baggage_contents.get(CORRELATION_ID_KEY), "corr-x") self.assertEqual(baggage_contents.get(GEN_AI_AGENT_AUID_KEY), "auid-x") self.assertEqual(baggage_contents.get(GEN_AI_AGENT_UPN_KEY), "upn-x") - self.assertEqual(baggage_contents.get(OPERATION_SOURCE_KEY), OperationSource.SDK.value) # Ignored values should not be present self.assertIsNone(baggage_contents.get(GEN_AI_CALLER_ID_KEY)) - self.assertIsNone(baggage_contents.get(HIRING_MANAGER_ID_KEY)) + self.assertIsNone(baggage_contents.get(SESSION_ID_KEY)) def test_source_metadata_name_method(self): """Test deprecated source_metadata_name method - should delegate to channel_name.""" @@ -256,7 +230,7 @@ def test_source_metadata_name_method(self): # Should set channel name baggage through delegation with self.builder.source_metadata_name("test-channel").build(): current_baggage = baggage.get_all() - self.assertEqual(current_baggage.get(GEN_AI_EXECUTION_SOURCE_NAME_KEY), "test-channel") + self.assertEqual(current_baggage.get(CHANNEL_NAME_KEY), "test-channel") def test_source_metadata_description_method(self): """Test deprecated source_metadata_description method - should delegate to channel_links.""" @@ -268,7 +242,7 @@ def test_source_metadata_description_method(self): with self.builder.source_metadata_description("test-description").build(): current_baggage = baggage.get_all() self.assertEqual( - current_baggage.get(GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY), "test-description" + current_baggage.get(CHANNEL_LINK_KEY), "test-description" ) def test_session_id_method(self): @@ -304,7 +278,7 @@ def test_channel_name_method(self): # Should set channel name baggage with self.builder.channel_name("Teams Channel").build(): current_baggage = baggage.get_all() - self.assertEqual(current_baggage.get(GEN_AI_EXECUTION_SOURCE_NAME_KEY), "Teams Channel") + self.assertEqual(current_baggage.get(CHANNEL_NAME_KEY), "Teams Channel") def test_channel_links_method(self): """Test channel_links method sets channel description baggage.""" @@ -316,7 +290,7 @@ def test_channel_links_method(self): with self.builder.channel_links("https://teams.microsoft.com/channel/123").build(): current_baggage = baggage.get_all() self.assertEqual( - current_baggage.get(GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY), + current_baggage.get(CHANNEL_LINK_KEY), "https://teams.microsoft.com/channel/123", ) diff --git a/tests/observability/core/test_execute_tool_scope.py b/tests/observability/core/test_execute_tool_scope.py index 82f073d5..9df83e1a 100644 --- a/tests/observability/core/test_execute_tool_scope.py +++ b/tests/observability/core/test_execute_tool_scope.py @@ -20,8 +20,8 @@ ) from microsoft_agents_a365.observability.core.config import _telemetry_manager from microsoft_agents_a365.observability.core.constants import ( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, ) from microsoft_agents_a365.observability.core.opentelemetry_scope import OpenTelemetryScope from opentelemetry.sdk.trace.export import SimpleSpanProcessor @@ -112,22 +112,22 @@ def test_request_metadata_set_on_span(self): span_attributes = getattr(span, "attributes", {}) or {} self.assertIn( - GEN_AI_EXECUTION_SOURCE_NAME_KEY, + CHANNEL_NAME_KEY, span_attributes, "Expected source name to be set on span", ) self.assertEqual( - span_attributes[GEN_AI_EXECUTION_SOURCE_NAME_KEY], + span_attributes[CHANNEL_NAME_KEY], request.source_metadata.name, ) self.assertIn( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, + CHANNEL_LINK_KEY, span_attributes, "Expected source description to be set on span", ) self.assertEqual( - span_attributes[GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY], + span_attributes[CHANNEL_LINK_KEY], request.source_metadata.description, ) diff --git a/tests/observability/core/test_inference_scope.py b/tests/observability/core/test_inference_scope.py index adf935ba..6861f05b 100644 --- a/tests/observability/core/test_inference_scope.py +++ b/tests/observability/core/test_inference_scope.py @@ -21,8 +21,8 @@ from microsoft_agents_a365.observability.core.agent_details import AgentDetails from microsoft_agents_a365.observability.core.config import _telemetry_manager from microsoft_agents_a365.observability.core.constants import ( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, ) from microsoft_agents_a365.observability.core.opentelemetry_scope import OpenTelemetryScope from opentelemetry.sdk.trace.export import SimpleSpanProcessor @@ -178,22 +178,22 @@ def test_request_metadata_set_on_span(self): span_attributes = getattr(span, "attributes", {}) or {} self.assertIn( - GEN_AI_EXECUTION_SOURCE_NAME_KEY, + CHANNEL_NAME_KEY, span_attributes, "Expected source name to be set on span", ) self.assertEqual( - span_attributes[GEN_AI_EXECUTION_SOURCE_NAME_KEY], + span_attributes[CHANNEL_NAME_KEY], request.source_metadata.name, ) self.assertIn( - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, + CHANNEL_LINK_KEY, span_attributes, "Expected source description to be set on span", ) self.assertEqual( - span_attributes[GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY], + span_attributes[CHANNEL_LINK_KEY], request.source_metadata.description, ) diff --git a/tests/observability/core/test_invoke_agent_scope.py b/tests/observability/core/test_invoke_agent_scope.py index e6313e5d..dde229c0 100644 --- a/tests/observability/core/test_invoke_agent_scope.py +++ b/tests/observability/core/test_invoke_agent_scope.py @@ -21,14 +21,11 @@ ) from microsoft_agents_a365.observability.core.config import _telemetry_manager from microsoft_agents_a365.observability.core.constants import ( - GEN_AI_CALLER_AGENT_TYPE_KEY, - GEN_AI_CALLER_AGENT_USER_CLIENT_IP, - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, GEN_AI_EXECUTION_TYPE_KEY, GEN_AI_INPUT_MESSAGES_KEY, ) -from microsoft_agents_a365.observability.core.models.agent_type import AgentType from microsoft_agents_a365.observability.core.models.caller_details import CallerDetails from microsoft_agents_a365.observability.core.opentelemetry_scope import OpenTelemetryScope from opentelemetry.sdk.trace.export import SimpleSpanProcessor @@ -82,8 +79,7 @@ def setUpClass(cls): caller_id="user-123", caller_upn="user@contoso.com", caller_name="John Doe", - caller_user_id="user-id-456", - tenant_id="tenant-789", + caller_client_ip="192.168.1.100", ) # Create caller agent details (agentic caller) @@ -95,8 +91,7 @@ def setUpClass(cls): agent_auid="auid-123", agent_upn="agent@contoso.com", tenant_id="tenant-789", - agent_client_ip="192.168.1.100", - agent_type=AgentType.DECLARATIVE_AGENT, + agent_platform_id="platform-123", ) def setUp(self): @@ -176,16 +171,16 @@ def test_request_attributes_set_on_span(self): # Verify mock data request parameters are in span attributes # Check source channel name from mock data - if GEN_AI_EXECUTION_SOURCE_NAME_KEY in span_attributes: + if CHANNEL_NAME_KEY in span_attributes: self.assertEqual( - span_attributes[GEN_AI_EXECUTION_SOURCE_NAME_KEY], + span_attributes[CHANNEL_NAME_KEY], self.source_metadata.name, # From cls.source_metadata.name ) # Check source channel description from mock data - if GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY in span_attributes: + if CHANNEL_LINK_KEY in span_attributes: self.assertEqual( - span_attributes[GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY], + span_attributes[CHANNEL_LINK_KEY], self.source_metadata.description, # From cls.source_metadata.description ) @@ -204,70 +199,6 @@ def test_request_attributes_set_on_span(self): input_messages, ) - def test_caller_agent_client_ip_in_scope(self): - """Test that caller agent client IP is properly handled when creating InvokeAgentScope.""" - # Set up tracer to capture spans - span_exporter = InMemorySpanExporter() - tracer_provider = get_tracer_provider() - tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter)) - - # Create scope with caller agent details that include client IP - scope = InvokeAgentScope.start( - invoke_agent_details=self.invoke_details, - tenant_details=self.tenant_details, - caller_agent_details=self.caller_agent_details, # Contains agent_client_ip="192.168.1.100" - ) - - if scope is not None: - # Verify the caller agent details contain the expected IP - self.assertEqual(self.caller_agent_details.agent_client_ip, "192.168.1.100") - scope.dispose() - - # Verify the IP is set as a span attribute - finished_spans = span_exporter.get_finished_spans() - if finished_spans: - span = finished_spans[-1] - span_attributes = getattr(span, "attributes", {}) or {} - - # Verify the caller agent client IP is set as a span attribute - if GEN_AI_CALLER_AGENT_USER_CLIENT_IP in span_attributes: - self.assertEqual( - span_attributes[GEN_AI_CALLER_AGENT_USER_CLIENT_IP], "192.168.1.100" - ) - - def test_caller_agent_type_in_scope(self): - """Test that caller agent type is properly set when creating InvokeAgentScope.""" - # Set up tracer to capture spans - span_exporter = InMemorySpanExporter() - tracer_provider = get_tracer_provider() - tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter)) - - # Create scope with caller agent details that include agent_type - scope = InvokeAgentScope.start( - invoke_agent_details=self.invoke_details, - tenant_details=self.tenant_details, - caller_agent_details=self.caller_agent_details, - ) - - # Verify scope was created and caller agent details contain the expected type - self.assertIsNotNone(scope) - self.assertEqual(self.caller_agent_details.agent_type, AgentType.DECLARATIVE_AGENT) - scope.dispose() - - # Verify the agent type is set as a span attribute - finished_spans = span_exporter.get_finished_spans() - self.assertTrue(len(finished_spans) > 0, "Expected at least one span to be created") - - span = finished_spans[-1] - span_attributes = getattr(span, "attributes", {}) or {} - - # Verify the caller agent type is set as a span attribute - self.assertIn(GEN_AI_CALLER_AGENT_TYPE_KEY, span_attributes) - self.assertEqual( - span_attributes[GEN_AI_CALLER_AGENT_TYPE_KEY], - AgentType.DECLARATIVE_AGENT.value, - ) - if __name__ == "__main__": # Run pytest only on the current file diff --git a/tests/observability/core/test_span_processor.py b/tests/observability/core/test_span_processor.py index f7bd4f5a..2e59126b 100644 --- a/tests/observability/core/test_span_processor.py +++ b/tests/observability/core/test_span_processor.py @@ -4,9 +4,11 @@ import unittest from unittest.mock import MagicMock -from microsoft_agents_a365.observability.core.constants import OPERATION_SOURCE_KEY +from microsoft_agents_a365.observability.core.constants import ( + GEN_AI_AGENT_ID_KEY, + TENANT_ID_KEY, +) from microsoft_agents_a365.observability.core.middleware.baggage_builder import BaggageBuilder -from microsoft_agents_a365.observability.core.models.operation_source import OperationSource from microsoft_agents_a365.observability.core.trace_processor.span_processor import SpanProcessor from opentelemetry import context @@ -20,33 +22,21 @@ def setUp(self): self.mock_span = MagicMock() self.mock_context = None # Root span - def test_operation_source_defaults_to_sdk(self): - """Test that operation source is set to SDK by default when not in baggage.""" + def test_baggage_propagates_to_span(self): + """Test that baggage values are propagated to span attributes.""" # Mock span with no existing attributes self.mock_span.attributes = {} - # Call on_start with no baggage - self.processor.on_start(self.mock_span, self.mock_context) - - # Verify SDK was set as default operation source - self.mock_span.set_attribute.assert_called_with( - OPERATION_SOURCE_KEY, OperationSource.SDK.value - ) - - def test_operation_source_honors_baggage_value(self): - """Test that operation source from baggage is used when available.""" - # Mock span with no existing attributes - self.mock_span.attributes = {} - - # Set operation source in baggage using BaggageBuilder - with BaggageBuilder().operation_source(OperationSource.GATEWAY).build(): - # Call on_start - should use baggage value + # Set values in baggage using BaggageBuilder + with BaggageBuilder().tenant_id("test-tenant").agent_id("test-agent").build(): + # Call on_start - should propagate baggage values self.processor.on_start(self.mock_span, context.get_current()) - # Verify GATEWAY was used from baggage - self.mock_span.set_attribute.assert_called_with( - OPERATION_SOURCE_KEY, OperationSource.GATEWAY.value - ) + # Verify baggage values were set on the span + calls = self.mock_span.set_attribute.call_args_list + call_dict = {call[0][0]: call[0][1] for call in calls} + self.assertEqual(call_dict.get(TENANT_ID_KEY), "test-tenant") + self.assertEqual(call_dict.get(GEN_AI_AGENT_ID_KEY), "test-agent") def test_on_end_calls_super(self): try: diff --git a/tests/observability/extensions/agentframework/integration/test_agentframework_trace_processor.py b/tests/observability/extensions/agentframework/integration/test_agentframework_trace_processor.py index 800282d3..4bfb6188 100644 --- a/tests/observability/extensions/agentframework/integration/test_agentframework_trace_processor.py +++ b/tests/observability/extensions/agentframework/integration/test_agentframework_trace_processor.py @@ -8,8 +8,8 @@ from microsoft_agents_a365.observability.core.constants import ( GEN_AI_INPUT_MESSAGES_KEY, GEN_AI_OUTPUT_MESSAGES_KEY, + GEN_AI_PROVIDER_NAME_KEY, GEN_AI_REQUEST_MODEL_KEY, - GEN_AI_SYSTEM_KEY, TENANT_ID_KEY, ) from microsoft_agents_a365.observability.extensions.agentframework.trace_instrumentor import ( @@ -201,7 +201,7 @@ def _validate_span_attributes(self, agent365_config): assert attributes[TENANT_ID_KEY] == agent365_config["tenant_id"] # Check for LLM spans (generation spans) - if GEN_AI_SYSTEM_KEY in attributes and attributes[GEN_AI_SYSTEM_KEY] == "openai": + if GEN_AI_PROVIDER_NAME_KEY in attributes and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai": if GEN_AI_REQUEST_MODEL_KEY in attributes: llm_spans_found += 1 # Validate LLM span attributes diff --git a/tests/observability/extensions/openai/integration/test_openai_trace_processor.py b/tests/observability/extensions/openai/integration/test_openai_trace_processor.py index 95598420..23977b8f 100644 --- a/tests/observability/extensions/openai/integration/test_openai_trace_processor.py +++ b/tests/observability/extensions/openai/integration/test_openai_trace_processor.py @@ -12,8 +12,8 @@ GEN_AI_INPUT_MESSAGES_KEY, GEN_AI_OPERATION_NAME_KEY, GEN_AI_OUTPUT_MESSAGES_KEY, + GEN_AI_PROVIDER_NAME_KEY, GEN_AI_REQUEST_MODEL_KEY, - GEN_AI_SYSTEM_KEY, INVOKE_AGENT_OPERATION_NAME, TENANT_ID_KEY, ) @@ -322,7 +322,7 @@ def _validate_span_attributes(self, agent365_config): assert attributes[GEN_AI_AGENT_ID_KEY] == agent365_config["agent_id"] # Check for LLM spans (generation spans) - if GEN_AI_SYSTEM_KEY in attributes and attributes[GEN_AI_SYSTEM_KEY] == "openai": + if GEN_AI_PROVIDER_NAME_KEY in attributes and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai": if GEN_AI_REQUEST_MODEL_KEY in attributes: llm_spans_found += 1 # Validate LLM span attributes @@ -369,7 +369,7 @@ def _validate_tool_span_attributes(self, agent365_config): assert attributes[GEN_AI_AGENT_ID_KEY] == agent365_config["agent_id"] # Check for LLM spans (generation spans) - if GEN_AI_SYSTEM_KEY in attributes and attributes[GEN_AI_SYSTEM_KEY] == "openai": + if GEN_AI_PROVIDER_NAME_KEY in attributes and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai": if GEN_AI_REQUEST_MODEL_KEY in attributes: llm_spans_found += 1 print(f"✓ Found LLM span with model: {attributes[GEN_AI_REQUEST_MODEL_KEY]}") diff --git a/tests/observability/hosting/scope_helpers/test_populate_invoke_agent_scope.py b/tests/observability/hosting/scope_helpers/test_populate_invoke_agent_scope.py index 6aa06b40..805a119c 100644 --- a/tests/observability/hosting/scope_helpers/test_populate_invoke_agent_scope.py +++ b/tests/observability/hosting/scope_helpers/test_populate_invoke_agent_scope.py @@ -9,9 +9,9 @@ from microsoft_agents.hosting.core import TurnContext from microsoft_agents_a365.observability.core.agent_details import AgentDetails from microsoft_agents_a365.observability.core.constants import ( + CHANNEL_NAME_KEY, GEN_AI_CALLER_ID_KEY, GEN_AI_CONVERSATION_ID_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, GEN_AI_EXECUTION_TYPE_KEY, GEN_AI_INPUT_MESSAGES_KEY, ) @@ -80,8 +80,8 @@ def test_populate(): assert GEN_AI_EXECUTION_TYPE_KEY in attributes # Check execution source - assert GEN_AI_EXECUTION_SOURCE_NAME_KEY in attributes - assert attributes[GEN_AI_EXECUTION_SOURCE_NAME_KEY] == "test-channel" + assert CHANNEL_NAME_KEY in attributes + assert attributes[CHANNEL_NAME_KEY] == "test-channel" # Check conversation ID assert GEN_AI_CONVERSATION_ID_KEY in attributes diff --git a/tests/observability/hosting/scope_helpers/test_scope_helper_utils.py b/tests/observability/hosting/scope_helpers/test_scope_helper_utils.py index c1816ba9..e6f5199b 100644 --- a/tests/observability/hosting/scope_helpers/test_scope_helper_utils.py +++ b/tests/observability/hosting/scope_helpers/test_scope_helper_utils.py @@ -3,6 +3,8 @@ from microsoft_agents.activity import Activity, ChannelAccount, ConversationAccount from microsoft_agents_a365.observability.core.constants import ( + CHANNEL_LINK_KEY, + CHANNEL_NAME_KEY, GEN_AI_AGENT_AUID_KEY, GEN_AI_AGENT_DESCRIPTION_KEY, GEN_AI_AGENT_ID_KEY, @@ -10,12 +12,9 @@ GEN_AI_AGENT_UPN_KEY, GEN_AI_CALLER_ID_KEY, GEN_AI_CALLER_NAME_KEY, - GEN_AI_CALLER_TENANT_ID_KEY, GEN_AI_CALLER_UPN_KEY, GEN_AI_CONVERSATION_ID_KEY, GEN_AI_CONVERSATION_ITEM_LINK_KEY, - GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, - GEN_AI_EXECUTION_SOURCE_NAME_KEY, GEN_AI_EXECUTION_TYPE_KEY, TENANT_ID_KEY, ) @@ -45,7 +44,6 @@ def test_get_caller_pairs(): assert (GEN_AI_CALLER_ID_KEY, "caller-aad-id") in result assert (GEN_AI_CALLER_NAME_KEY, "Test Caller") in result assert (GEN_AI_CALLER_UPN_KEY, "caller-upn") in result - assert (GEN_AI_CALLER_TENANT_ID_KEY, "caller-tenant-id") in result def test_get_execution_type_pair(): @@ -95,8 +93,8 @@ def test_get_source_metadata_pairs(): result = list(get_source_metadata_pairs(activity)) - assert (GEN_AI_EXECUTION_SOURCE_NAME_KEY, "test-channel") in result - assert (GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, None) in result + assert (CHANNEL_NAME_KEY, "test-channel") in result + assert (CHANNEL_LINK_KEY, None) in result def test_get_conversation_pairs():