Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -61,6 +61,7 @@
"SourceMetadata",
"Request",
"InferenceCallDetails",
"ServiceEndpoint",
# Enums
"ExecutionType",
"InferenceOperationType",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from dataclasses import dataclass
from typing import Optional

from .models.agent_type import AgentType


@dataclass
class AgentDetails:
Expand All @@ -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."""
Expand All @@ -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."""
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Comment on lines +119 to +120
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

server.port should be recorded as an integer attribute (OpenTelemetry semconv expects an int), but this converts it to str(...). Also, if details.endpoint.port: will skip valid ports like 0; use an explicit is not None check and set the numeric value.

Suggested change
if details.endpoint.port:
self.set_tag_maybe(SERVER_PORT_KEY, str(details.endpoint.port))
if details.endpoint.port is not None:
self.set_tag_maybe(SERVER_PORT_KEY, details.endpoint.port)

Copilot uses AI. Check for mistakes.

# 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:
Expand All @@ -141,15 +148,15 @@ 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.

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.
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -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:
Expand Down
Loading
Loading