diff --git a/packages/agents-a365-observability-extensions-langchain/src/tracer.ts b/packages/agents-a365-observability-extensions-langchain/src/tracer.ts index 2fdb64d8..4af3ee00 100644 --- a/packages/agents-a365-observability-extensions-langchain/src/tracer.ts +++ b/packages/agents-a365-observability-extensions-langchain/src/tracer.ts @@ -63,7 +63,7 @@ export class LangChainTracer extends BaseTracer { const span = this.tracer.startSpan(spanName, { kind: SpanKind.INTERNAL, startTime, - attributes: { [OpenTelemetryConstants.GEN_AI_SYSTEM_KEY]: "langchain" }, + attributes: { [OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY]: "langchain" }, }, activeContext); this.runs[run.id] = { run, span, startTime, lastAccessTime: startTime }; diff --git a/packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts b/packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts index 68ce01ed..4f69662b 100644 --- a/packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts +++ b/packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts @@ -40,9 +40,9 @@ export class OpenAIAgentsTraceProcessor implements TracingProcessor { private readonly spanNames: Map = new Map(); private readonly keyMappings: Map = new Map([ - ['mcp_tools' + Constants.GEN_AI_RESPONSE_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_EVENT_CONTENT], + ['mcp_tools' + Constants.GEN_AI_RESPONSE_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_TOOL_CALL_RESULT_KEY], ['mcp_tools' + Constants.GEN_AI_REQUEST_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_TOOL_ARGS_KEY], - ['function' + Constants.GEN_AI_RESPONSE_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_EVENT_CONTENT], + ['function' + Constants.GEN_AI_RESPONSE_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_TOOL_CALL_RESULT_KEY], ['function' + Constants.GEN_AI_REQUEST_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_TOOL_ARGS_KEY], ['generation' + Constants.GEN_AI_RESPONSE_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_OUTPUT_MESSAGES_KEY], ['generation' + Constants.GEN_AI_REQUEST_CONTENT_KEY, OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY], @@ -117,7 +117,7 @@ export class OpenAIAgentsTraceProcessor implements TracingProcessor { startTime, attributes: { [OpenTelemetryConstants.GEN_AI_OPERATION_NAME_KEY]: Utils.getSpanKind(spanData), - [OpenTelemetryConstants.GEN_AI_SYSTEM_KEY]: 'openai', + [OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY]: 'openai', }, }, parentContext diff --git a/packages/agents-a365-observability-extensions-openai/src/Utils.ts b/packages/agents-a365-observability-extensions-openai/src/Utils.ts index 54331ea7..6d8061e6 100644 --- a/packages/agents-a365-observability-extensions-openai/src/Utils.ts +++ b/packages/agents-a365-observability-extensions-openai/src/Utils.ts @@ -94,10 +94,6 @@ export function getAttributesFromGenerationSpanData(data: SpanData): Record; - if (output.id) { - attributes[OpenTelemetryConstants.GEN_AI_RESPONSE_ID_KEY] = output.id; - } } if (genData.usage) { diff --git a/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts b/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts index bc2225ed..7a2e0271 100644 --- a/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts +++ b/packages/agents-a365-observability-hosting/src/middleware/OutputLoggingMiddleware.ts @@ -82,7 +82,6 @@ export class OutputLoggingMiddleware implements Middleware { callerDetails, conversationId, sourceMetadata, - undefined, parentSpanRef, ); try { diff --git a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts index 981d4ead..aa1a1143 100644 --- a/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts +++ b/packages/agents-a365-observability-hosting/src/utils/TurnContextUtils.ts @@ -20,7 +20,7 @@ function normalizePairs(pairs: Array<[string, string | undefined]>): Array<[stri /** * Extracts caller-related OpenTelemetry baggage pairs from the TurnContext. * @param turnContext The current TurnContext (activity context) - * @returns Array of [key, value] pairs for caller identity and tenant + * @returns Array of [key, value] pairs for caller identity */ export function getCallerBaggagePairs(turnContext: TurnContext): Array<[string, string]> { if (!turnContext|| !turnContext.activity?.from) { @@ -33,7 +33,6 @@ export function getCallerBaggagePairs(turnContext: TurnContext): Array<[string, [OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, from.aadObjectId], [OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, from.name], [OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, upn], - [OpenTelemetryConstants.GEN_AI_CALLER_TENANT_ID_KEY, from.tenantId], [OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY, from.agenticAppBlueprintId] ]; return normalizePairs(pairs); @@ -124,8 +123,8 @@ export function getSourceMetadataBaggagePairs(turnContext: TurnContext): Array<[ return []; } const pairs: Array<[string, string | undefined]> = [ - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, turnContext.activity?.channelId], - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, turnContext.activity?.channelIdSubChannel as string | undefined] + [OpenTelemetryConstants.CHANNEL_NAME_KEY, turnContext.activity?.channelId], + [OpenTelemetryConstants.CHANNEL_LINK_KEY, turnContext.activity?.channelIdSubChannel as string | undefined] ]; return normalizePairs(pairs); } diff --git a/packages/agents-a365-observability/.gitignore b/packages/agents-a365-observability/.gitignore new file mode 100644 index 00000000..5d015ab0 --- /dev/null +++ b/packages/agents-a365-observability/.gitignore @@ -0,0 +1,2 @@ +# The file "src/version.ts" is generated by a prebuild script and should not be committed. +src/version.ts diff --git a/packages/agents-a365-observability/package.json b/packages/agents-a365-observability/package.json index 4c6028c8..74e02e11 100644 --- a/packages/agents-a365-observability/package.json +++ b/packages/agents-a365-observability/package.json @@ -6,6 +6,7 @@ "module": "dist/esm/index.js", "types": "dist/esm/index.d.ts", "scripts": { + "prebuild": "node -p \"'export const LIB_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts", "build:cjs": "npx tsc --project tsconfig.cjs.json", "build:esm": "npx tsc --project tsconfig.esm.json", "build": "npm run build:cjs && npm run build:esm", diff --git a/packages/agents-a365-observability/src/tracing/constants.ts b/packages/agents-a365-observability/src/tracing/constants.ts index c7e88b19..d6fd4168 100644 --- a/packages/agents-a365-observability/src/tracing/constants.ts +++ b/packages/agents-a365-observability/src/tracing/constants.ts @@ -1,6 +1,7 @@ -// ------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { LIB_VERSION } from '../version'; /** * OpenTelemetry constants for Agent 365 @@ -36,19 +37,15 @@ export class OpenTelemetryConstants { public static readonly GEN_AI_REQUEST_MODEL_KEY = 'gen_ai.request.model'; public static readonly GEN_AI_REQUEST_TEMPERATURE_KEY = 'gen_ai.request.temperature'; public static readonly GEN_AI_REQUEST_TOP_P_KEY = 'gen_ai.request.top_p'; - public static readonly GEN_AI_RESPONSE_ID_KEY = 'gen_ai.response.id'; public static readonly GEN_AI_RESPONSE_FINISH_REASONS_KEY = 'gen_ai.response.finish_reasons'; public static readonly GEN_AI_RESPONSE_MODEL_KEY = 'gen_ai.response.model'; - public static readonly GEN_AI_SYSTEM_KEY = 'gen_ai.system'; - public static readonly GEN_AI_SYSTEM_VALUE = 'az.ai.agent365'; - public static readonly GEN_AI_AGENT_ID_KEY = 'gen_ai.agent.id'; public static readonly GEN_AI_AGENT_NAME_KEY = 'gen_ai.agent.name'; - public static readonly GEN_AI_AGENT_TYPE_KEY = 'gen_ai.agent.type'; public static readonly GEN_AI_AGENT_DESCRIPTION_KEY = 'gen_ai.agent.description'; - public static readonly GEN_AI_AGENT_PLATFORM_ID_KEY = 'gen_ai.agent.platformid'; + public static readonly GEN_AI_AGENT_PLATFORM_ID_KEY = 'microsoft.a365.agent.platform.id'; + public static readonly GEN_AI_AGENT_THOUGHT_PROCESS_KEY = 'microsoft.a365.agent.thought.process'; public static readonly GEN_AI_CONVERSATION_ID_KEY = 'gen_ai.conversation.id'; - public static readonly GEN_AI_CONVERSATION_ITEM_LINK_KEY = 'gen_ai.conversation.item.link'; + public static readonly GEN_AI_CONVERSATION_ITEM_LINK_KEY = 'microsoft.conversation.item.link'; public static readonly GEN_AI_TOKEN_TYPE_KEY = 'gen_ai.token.type'; public static readonly GEN_AI_USAGE_INPUT_TOKENS_KEY = 'gen_ai.usage.input_tokens'; public static readonly GEN_AI_USAGE_OUTPUT_TOKENS_KEY = 'gen_ai.usage.output_tokens'; @@ -58,80 +55,60 @@ export class OpenTelemetryConstants { public static readonly GEN_AI_SYSTEM_INSTRUCTIONS_KEY = 'gen_ai.system_instructions'; public static readonly GEN_AI_INPUT_MESSAGES_KEY = 'gen_ai.input.messages'; public static readonly GEN_AI_OUTPUT_MESSAGES_KEY = 'gen_ai.output.messages'; - public static readonly GEN_AI_EVENT_CONTENT = 'gen_ai.event.content'; - // Tool execution constants public static readonly GEN_AI_TOOL_CALL_ID_KEY = 'gen_ai.tool.call.id'; public static readonly GEN_AI_TOOL_NAME_KEY = 'gen_ai.tool.name'; public static readonly GEN_AI_TOOL_DESCRIPTION_KEY = 'gen_ai.tool.description'; public static readonly GEN_AI_TOOL_ARGS_KEY = 'gen_ai.tool.call.arguments'; - public static readonly GEN_AI_TOOL_CALL_RESULT_KEY = 'gen_ai.event.content'; // GEN_AI_EVENT_CONTENT + public static readonly GEN_AI_TOOL_CALL_RESULT_KEY = 'gen_ai.tool.call.result'; public static readonly GEN_AI_TOOL_TYPE_KEY = 'gen_ai.tool.type'; // Agent user (user tied to agent instance during creation) or caller dimensions - public static readonly GEN_AI_AGENT_USER_ID_KEY = 'gen_ai.agent.userid'; - public static readonly GEN_AI_CALLER_TENANT_ID_KEY = 'gen_ai.caller.tenantid'; - public static readonly GEN_AI_CALLER_ID_KEY = 'gen_ai.caller.id'; - public static readonly GEN_AI_CALLER_NAME_KEY = 'gen_ai.caller.name'; - public static readonly GEN_AI_CALLER_UPN_KEY = 'gen_ai.caller.upn'; - public static readonly GEN_AI_CALLER_CLIENT_IP_KEY = 'gen_ai.caller.client.ip'; + public static readonly GEN_AI_CALLER_ID_KEY = 'microsoft.caller.id'; + public static readonly GEN_AI_CALLER_NAME_KEY = 'microsoft.caller.name'; + public static readonly GEN_AI_CALLER_UPN_KEY = 'microsoft.caller.upn'; + public static readonly GEN_AI_CALLER_CLIENT_IP_KEY = 'client.address'; // Agent to Agent caller agent dimensions - public static readonly GEN_AI_CALLER_AGENT_USER_ID_KEY = 'gen_ai.caller.agent.userid'; - public static readonly GEN_AI_CALLER_AGENT_UPN_KEY = 'gen_ai.caller.agent.upn'; - public static readonly GEN_AI_CALLER_AGENT_TENANT_ID_KEY = 'gen_ai.caller.agent.tenantid'; - public static readonly GEN_AI_CALLER_AGENT_NAME_KEY = 'gen_ai.caller.agent.name'; - public static readonly GEN_AI_CALLER_AGENT_ID_KEY = 'gen_ai.caller.agent.id'; - public static readonly GEN_AI_CALLER_AGENT_TYPE_KEY = 'gen_ai.caller.agent.type'; - public static readonly GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = 'gen_ai.caller.agent.applicationid'; - public static readonly GEN_AI_CALLER_AGENT_CLIENT_IP_KEY = 'gen_ai.caller.agent.user.client.ip'; - public static readonly GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY = 'gen_ai.caller.agent.platformid'; + public static readonly GEN_AI_CALLER_AGENT_USER_ID_KEY = 'microsoft.a365.caller.agent.user.id'; + public static readonly GEN_AI_CALLER_AGENT_UPN_KEY = 'microsoft.a365.caller.agent.user.upn'; + public static readonly GEN_AI_CALLER_AGENT_NAME_KEY = 'microsoft.a365.caller.agent.name'; + public static readonly GEN_AI_CALLER_AGENT_ID_KEY = 'microsoft.a365.caller.agent.id'; + public static readonly GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = 'microsoft.a365.caller.agent.blueprint.id'; + public static readonly GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY = 'microsoft.a365.caller.agent.platform.id'; // Agent-specific dimensions public static readonly AGENT_ID_KEY = 'gen_ai.agent.id'; public static readonly GEN_AI_TASK_ID_KEY = 'gen_ai.task.id'; - public static readonly SESSION_ID_KEY = 'session.id'; - public static readonly SESSION_DESCRIPTION_KEY = 'session.description'; + public static readonly SESSION_ID_KEY = 'microsoft.session.id'; + public static readonly SESSION_DESCRIPTION_KEY = 'microsoft.session.description'; public static readonly GEN_AI_ICON_URI_KEY = 'gen_ai.agent365.icon_uri'; - public static readonly TENANT_ID_KEY = 'tenant.id'; + public static readonly TENANT_ID_KEY = 'microsoft.tenant.id'; // Baggage keys - public static readonly OPERATION_SOURCE_KEY = 'operation.source'; - public static readonly GEN_AI_AGENT_AUID_KEY = 'gen_ai.agent.user.id'; - public static readonly GEN_AI_AGENT_UPN_KEY = 'gen_ai.agent.upn'; - public static readonly GEN_AI_AGENT_BLUEPRINT_ID_KEY = 'gen_ai.agent.applicationid'; - public static readonly CORRELATION_ID_KEY = 'correlation.id'; - public static readonly HIRING_MANAGER_ID_KEY = 'hiring.manager.id'; + public static readonly GEN_AI_AGENT_AUID_KEY = 'microsoft.agent.user.id'; + public static readonly GEN_AI_AGENT_UPN_KEY = 'microsoft.agent.user.upn'; + public static readonly GEN_AI_AGENT_BLUEPRINT_ID_KEY = 'microsoft.a365.agent.blueprint.id'; // Execution context dimensions public static readonly GEN_AI_EXECUTION_TYPE_KEY = 'gen_ai.execution.type'; public static readonly GEN_AI_EXECUTION_PAYLOAD_KEY = 'gen_ai.execution.payload'; - // Source metadata dimensions - public static readonly GEN_AI_EXECUTION_SOURCE_ID_KEY = 'gen_ai.execution.sourceMetadata.id'; - public static readonly GEN_AI_EXECUTION_SOURCE_NAME_KEY = 'gen_ai.channel.name'; - public static readonly GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY = 'gen_ai.channel.link'; + // Channel dimensions + public static readonly CHANNEL_NAME_KEY = 'microsoft.channel.name'; + public static readonly CHANNEL_LINK_KEY = 'microsoft.channel.link'; // Custom parent id and parent name key public static readonly CUSTOM_PARENT_SPAN_ID_KEY = 'custom.parent.span.id'; public static readonly CUSTOM_SPAN_NAME_KEY = 'custom.span.name'; -} - -/** - * Enumeration representing the source of an operation. - */ -export enum OperationSource { - /** - * Operation executed by SDK. - */ - SDK = 'SDK', - /** - * Operation executed by Gateway. - */ - GATEWAY = 'Gateway', + // Service attributes + public static readonly SERVICE_NAME_KEY = 'service.name'; - /** - * Operation executed by MCP Server. - */ - MCP_SERVER = 'MCPServer', + // Telemetry SDK attributes + public static readonly TELEMETRY_SDK_NAME_KEY = 'telemetry.sdk.name'; + public static readonly TELEMETRY_SDK_LANGUAGE_KEY = 'telemetry.sdk.language'; + public static readonly TELEMETRY_SDK_VERSION_KEY = 'telemetry.sdk.version'; + public static readonly TELEMETRY_SDK_NAME_VALUE = 'A365ObservabilitySDK'; + public static readonly TELEMETRY_SDK_LANGUAGE_VALUE = 'nodejs'; + public static readonly TELEMETRY_SDK_VERSION_VALUE = LIB_VERSION; } diff --git a/packages/agents-a365-observability/src/tracing/contracts.ts b/packages/agents-a365-observability/src/tracing/contracts.ts index 2a327312..ae499d27 100644 --- a/packages/agents-a365-observability/src/tracing/contracts.ts +++ b/packages/agents-a365-observability/src/tracing/contracts.ts @@ -105,9 +105,6 @@ export interface AgentDetails { /** The human-readable name of the AI agent */ agentName?: string; - /** Optional type of the AI agent */ - agentType?: string; - /** A description of the AI agent's purpose or capabilities */ agentDescription?: string; @@ -129,8 +126,8 @@ export interface AgentDetails { /** The tenant ID for the agent */ tenantId?: string; - /** The client IP address for the agent user */ - agentClientIP?: string; + /** The provider name (e.g., az.ai.agent365, openai, anthropic) */ + providerName?: string; } /** @@ -235,8 +232,11 @@ export interface InferenceDetails { /** Array of finish reasons */ finishReasons?: string[]; - /** Response ID from the model provider */ - responseId?: string; + /** The thought process used by the agent */ + thoughtProcess?: string; + + /** The endpoint for the inference call */ + endpoint?: ServiceEndpoint; } /** diff --git a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts index 7a6f49f4..6bf4fa13 100644 --- a/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts +++ b/packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts @@ -16,7 +16,6 @@ import { OpenTelemetryConstants } from '../constants'; * const scope = new BaggageBuilder() * .tenantId("tenant-123") * .agentId("agent-456") - * .correlationId("corr-789") * .build(); * * scope.enter(); @@ -30,12 +29,13 @@ export class BaggageBuilder { private pairs: Map = new Map(); /** - * Set the operation source baggage value. - * @param value The operation source value + * Set the service name baggage value. + * Used for server spans to identify the service (e.g., ATG, ACF). + * @param value The service name * @returns Self for method chaining */ - operationSource(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.OPERATION_SOURCE_KEY, value); + serviceName(value: string | null | undefined): BaggageBuilder { + this.set(OpenTelemetryConstants.SERVICE_NAME_KEY, value); return this; } @@ -89,16 +89,6 @@ export class BaggageBuilder { return this; } - /** - * Set the correlation ID baggage value. - * @param value The correlation ID - * @returns Self for method chaining - */ - correlationId(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.CORRELATION_ID_KEY, value); - return this; - } - /** * Set the session ID baggage value. * @param value The session ID @@ -119,16 +109,6 @@ export class BaggageBuilder { return this; } - /** - * Set the hiring manager ID baggage value. - * @param value The hiring manager ID - * @returns Self for method chaining - */ - hiringManagerId(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.HIRING_MANAGER_ID_KEY, value); - return this; - } - /** * Set the agent name baggage value. * @param value The agent name @@ -139,16 +119,6 @@ export class BaggageBuilder { return this; } - /** - * Set the agent type baggage value. - * @param value The agent type - * @returns Self for method chaining - */ - agentType(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_AGENT_TYPE_KEY, value); - return this; - } - /** * Set the agent description baggage value. * @param value The agent description @@ -242,32 +212,22 @@ export class BaggageBuilder { } /** - * Set the execution source metadata ID (e.g., channel ID). - * @param value The source metadata ID - * @returns Self for method chaining - */ - sourceMetadataId(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_ID_KEY, value); - return this; - } - - /** - * Set the execution source metadata name (e.g., channel name). - * @param value The source metadata name + * Set the channel name (e.g., Teams, Slack). + * @param value The channel name * @returns Self for method chaining */ sourceMetadataName(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, value); + this.set(OpenTelemetryConstants.CHANNEL_NAME_KEY, value); return this; } /** - * Set the execution source metadata description (e.g., channel description). - * @param value The source metadata description + * Set the channel link/URL. + * @param value The channel link * @returns Self for method chaining */ sourceMetadataDescription(value: string | null | undefined): BaggageBuilder { - this.set(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, value); + this.set(OpenTelemetryConstants.CHANNEL_LINK_KEY, value); return this; } @@ -326,18 +286,15 @@ export class BaggageBuilder { * Convenience method to begin a request baggage scope with common fields. * @param tenantId The tenant ID * @param agentId The agent ID - * @param correlationId The correlation ID * @returns A context manager that restores the previous baggage on exit */ static setRequestContext( tenantId?: string | null, agentId?: string | null, - correlationId?: string | null, ): BaggageScope { return new BaggageBuilder() .tenantId(tenantId) .agentId(agentId) - .correlationId(correlationId) .build(); } } diff --git a/packages/agents-a365-observability/src/tracing/processors/SpanProcessor.ts b/packages/agents-a365-observability/src/tracing/processors/SpanProcessor.ts index cd48f57e..843c2cb0 100644 --- a/packages/agents-a365-observability/src/tracing/processors/SpanProcessor.ts +++ b/packages/agents-a365-observability/src/tracing/processors/SpanProcessor.ts @@ -1,11 +1,10 @@ -// ------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. import { Context, propagation, Span } from '@opentelemetry/api'; import { SpanProcessor as BaseSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { OpenTelemetryConstants, OperationSource } from '../constants'; +import { OpenTelemetryConstants } from '../constants'; import { GENERIC_ATTRIBUTES, INVOKE_AGENT_ATTRIBUTES } from './util'; /** @@ -69,12 +68,15 @@ export class SpanProcessor implements BaseSpanProcessor { INVOKE_AGENT_ATTRIBUTES.forEach(key => targetKeys.add(key)); } - // Set operation source - coalesce baggage value with SDK default - if (!existingAttrs.has(OpenTelemetryConstants.OPERATION_SOURCE_KEY)) { - const operationSource = - baggageMap.get(OpenTelemetryConstants.OPERATION_SOURCE_KEY) || - OperationSource.SDK; - span.setAttribute(OpenTelemetryConstants.OPERATION_SOURCE_KEY, operationSource); + // Set telemetry SDK attributes + if (!existingAttrs.has(OpenTelemetryConstants.TELEMETRY_SDK_NAME_KEY)) { + span.setAttribute(OpenTelemetryConstants.TELEMETRY_SDK_NAME_KEY, OpenTelemetryConstants.TELEMETRY_SDK_NAME_VALUE); + } + if (!existingAttrs.has(OpenTelemetryConstants.TELEMETRY_SDK_LANGUAGE_KEY)) { + span.setAttribute(OpenTelemetryConstants.TELEMETRY_SDK_LANGUAGE_KEY, OpenTelemetryConstants.TELEMETRY_SDK_LANGUAGE_VALUE); + } + if (!existingAttrs.has(OpenTelemetryConstants.TELEMETRY_SDK_VERSION_KEY)) { + span.setAttribute(OpenTelemetryConstants.TELEMETRY_SDK_VERSION_KEY, OpenTelemetryConstants.TELEMETRY_SDK_VERSION_VALUE); } // Copy baggage to span attributes diff --git a/packages/agents-a365-observability/src/tracing/processors/util.ts b/packages/agents-a365-observability/src/tracing/processors/util.ts index 6c70fe06..5994a953 100644 --- a/packages/agents-a365-observability/src/tracing/processors/util.ts +++ b/packages/agents-a365-observability/src/tracing/processors/util.ts @@ -1,6 +1,5 @@ -// ------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. import { OpenTelemetryConstants as consts } from '../constants'; @@ -11,46 +10,38 @@ export const GENERIC_ATTRIBUTES: readonly string[] = [ consts.TENANT_ID_KEY, consts.CUSTOM_PARENT_SPAN_ID_KEY, consts.CUSTOM_SPAN_NAME_KEY, - consts.CORRELATION_ID_KEY, consts.SESSION_ID_KEY, consts.GEN_AI_CONVERSATION_ID_KEY, consts.GEN_AI_CONVERSATION_ITEM_LINK_KEY, consts.GEN_AI_OPERATION_NAME_KEY, consts.GEN_AI_AGENT_ID_KEY, consts.GEN_AI_AGENT_NAME_KEY, - consts.GEN_AI_AGENT_TYPE_KEY, consts.GEN_AI_AGENT_DESCRIPTION_KEY, consts.SESSION_DESCRIPTION_KEY, - consts.GEN_AI_AGENT_USER_ID_KEY, consts.GEN_AI_AGENT_UPN_KEY, - consts.GEN_AI_AGENT_BLUEPRINT_ID_KEY, consts.GEN_AI_AGENT_AUID_KEY, consts.GEN_AI_AGENT_PLATFORM_ID_KEY, + consts.GEN_AI_AGENT_BLUEPRINT_ID_KEY, + consts.SERVICE_NAME_KEY, + // Caller / Invoker attributes + consts.GEN_AI_CALLER_ID_KEY, + consts.GEN_AI_CALLER_NAME_KEY, + consts.GEN_AI_CALLER_UPN_KEY, + consts.GEN_AI_CALLER_CLIENT_IP_KEY, + // Channel attributes + consts.CHANNEL_NAME_KEY, + consts.CHANNEL_LINK_KEY, ]; /** * Invoke Agent-specific attributes */ export const INVOKE_AGENT_ATTRIBUTES: readonly string[] = [ - // Caller / Invoker attributes - consts.GEN_AI_CALLER_ID_KEY, - consts.GEN_AI_CALLER_NAME_KEY, - consts.GEN_AI_CALLER_UPN_KEY, - consts.GEN_AI_CALLER_TENANT_ID_KEY, - consts.GEN_AI_CALLER_CLIENT_IP_KEY, // Caller Agent (A2A) attributes consts.GEN_AI_CALLER_AGENT_ID_KEY, consts.GEN_AI_CALLER_AGENT_NAME_KEY, - consts.GEN_AI_CALLER_AGENT_TYPE_KEY, consts.GEN_AI_CALLER_AGENT_USER_ID_KEY, consts.GEN_AI_CALLER_AGENT_UPN_KEY, - consts.GEN_AI_CALLER_AGENT_TENANT_ID_KEY, consts.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, - consts.GEN_AI_CALLER_AGENT_CLIENT_IP_KEY, consts.GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY, - // Execution context - consts.GEN_AI_EXECUTION_TYPE_KEY, - consts.GEN_AI_EXECUTION_SOURCE_ID_KEY, - consts.GEN_AI_EXECUTION_SOURCE_NAME_KEY, - consts.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, ]; diff --git a/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts b/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts index ed2c3a48..5cf16670 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts @@ -3,7 +3,7 @@ import { SpanKind, TimeInput } from '@opentelemetry/api'; import { OpenTelemetryScope } from './OpenTelemetryScope'; -import { ToolCallDetails, AgentDetails, TenantDetails, SourceMetadata } from '../contracts'; +import { ToolCallDetails, AgentDetails, TenantDetails, SourceMetadata, CallerDetails } from '../contracts'; import { ParentContext } from '../context/trace-context-propagation'; import { OpenTelemetryConstants } from '../constants'; @@ -24,6 +24,7 @@ export class ExecuteToolScope extends OpenTelemetryScope { * tool call after execution has already completed. * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). When provided, the span will * use this timestamp when disposed instead of the current wall-clock time. + * @param callerDetails Optional caller details. * @returns A new ExecuteToolScope instance. */ public static start( @@ -34,9 +35,10 @@ export class ExecuteToolScope extends OpenTelemetryScope { sourceMetadata?: Pick, parentContext?: ParentContext, startTime?: TimeInput, - endTime?: TimeInput + endTime?: TimeInput, + callerDetails?: CallerDetails ): ExecuteToolScope { - return new ExecuteToolScope(details, agentDetails, tenantDetails, conversationId, sourceMetadata, parentContext, startTime, endTime); + return new ExecuteToolScope(details, agentDetails, tenantDetails, conversationId, sourceMetadata, parentContext, startTime, endTime, callerDetails); } private constructor( @@ -47,7 +49,8 @@ export class ExecuteToolScope extends OpenTelemetryScope { sourceMetadata?: Pick, parentContext?: ParentContext, startTime?: TimeInput, - endTime?: TimeInput + endTime?: TimeInput, + callerDetails?: CallerDetails ) { super( SpanKind.INTERNAL, @@ -57,7 +60,8 @@ export class ExecuteToolScope extends OpenTelemetryScope { tenantDetails, parentContext, startTime, - endTime + endTime, + callerDetails ); // Destructure the details object to match C# pattern @@ -69,8 +73,8 @@ export class ExecuteToolScope extends OpenTelemetryScope { this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_CALL_ID_KEY, toolCallId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_DESCRIPTION_KEY, description); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, conversationId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, sourceMetadata?.name); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, sourceMetadata?.description); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, sourceMetadata?.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, sourceMetadata?.description); // Set endpoint information if provided @@ -79,7 +83,7 @@ export class ExecuteToolScope extends OpenTelemetryScope { // Only record port if it is different from 443 (default HTTPS port) if (endpoint.port && endpoint.port !== 443) { - this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, endpoint.port); + this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, endpoint.port.toString()); } } } @@ -89,6 +93,6 @@ export class ExecuteToolScope extends OpenTelemetryScope { * @param response The tool execution response */ public recordResponse(response: string): void { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EVENT_CONTENT, response); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_TOOL_CALL_RESULT_KEY, response); } } diff --git a/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts b/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts index 57e1d3ff..569d6792 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts @@ -8,7 +8,8 @@ import { InferenceDetails, AgentDetails, TenantDetails, - SourceMetadata + SourceMetadata, + CallerDetails } from '../contracts'; import { ParentContext } from '../context/trace-context-propagation'; @@ -27,6 +28,7 @@ export class InferenceScope extends OpenTelemetryScope { * Accepts a ParentSpanRef (manual traceId/spanId) or an OTel Context (e.g. from extractTraceContext). * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). + * @param callerDetails Optional caller details. * @returns A new InferenceScope instance */ public static start( @@ -37,9 +39,10 @@ export class InferenceScope extends OpenTelemetryScope { sourceMetadata?: Pick, parentContext?: ParentContext, startTime?: TimeInput, - endTime?: TimeInput + endTime?: TimeInput, + callerDetails?: CallerDetails ): InferenceScope { - return new InferenceScope(details, agentDetails, tenantDetails, conversationId, sourceMetadata, parentContext, startTime, endTime); + return new InferenceScope(details, agentDetails, tenantDetails, conversationId, sourceMetadata, parentContext, startTime, endTime, callerDetails); } private constructor( @@ -50,7 +53,8 @@ export class InferenceScope extends OpenTelemetryScope { sourceMetadata?: Pick, parentContext?: ParentContext, startTime?: TimeInput, - endTime?: TimeInput + endTime?: TimeInput, + callerDetails?: CallerDetails ) { super( SpanKind.CLIENT, @@ -60,20 +64,31 @@ export class InferenceScope extends OpenTelemetryScope { tenantDetails, parentContext, startTime, - endTime + endTime, + callerDetails ); // Set core inference information matching C# implementation this.setTagMaybe(OpenTelemetryConstants.GEN_AI_OPERATION_NAME_KEY, details.operationName.toString()); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_REQUEST_MODEL_KEY, details.model); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY, details.providerName); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_INPUT_TOKENS_KEY, details.inputTokens?.toString()); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_OUTPUT_TOKENS_KEY, details.outputTokens?.toString()); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_RESPONSE_FINISH_REASONS_KEY, details.finishReasons?.join(',')); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_RESPONSE_ID_KEY, details.responseId); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_INPUT_TOKENS_KEY, details.inputTokens); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_OUTPUT_TOKENS_KEY, details.outputTokens); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_RESPONSE_FINISH_REASONS_KEY, details.finishReasons); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_THOUGHT_PROCESS_KEY, details.thoughtProcess); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, conversationId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, sourceMetadata?.name); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, sourceMetadata?.description); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, sourceMetadata?.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, sourceMetadata?.description); + + // Set endpoint information if provided + if (details.endpoint) { + this.setTagMaybe(OpenTelemetryConstants.SERVER_ADDRESS_KEY, details.endpoint.host); + + // Only record port if it is different from 443 (default HTTPS port) + if (details.endpoint.port && details.endpoint.port !== 443) { + this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, details.endpoint.port.toString()); + } + } } /** @@ -97,7 +112,7 @@ export class InferenceScope extends OpenTelemetryScope { * @param inputTokens Number of input tokens */ public recordInputTokens(inputTokens: number): void { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_INPUT_TOKENS_KEY, inputTokens.toString()); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_INPUT_TOKENS_KEY, inputTokens); } /** @@ -105,17 +120,7 @@ export class InferenceScope extends OpenTelemetryScope { * @param outputTokens Number of output tokens */ public recordOutputTokens(outputTokens: number): void { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_OUTPUT_TOKENS_KEY, outputTokens.toString()); - } - - /** - * Records the response id for telemetry tracking. - * @param responseId The response ID - */ - public recordResponseId(responseId: string): void { - if (responseId && responseId.trim()) { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_RESPONSE_ID_KEY, responseId); - } + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_USAGE_OUTPUT_TOKENS_KEY, outputTokens); } /** @@ -124,7 +129,7 @@ export class InferenceScope extends OpenTelemetryScope { */ public recordFinishReasons(finishReasons: string[]): void { if (finishReasons && finishReasons.length > 0) { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_RESPONSE_FINISH_REASONS_KEY, finishReasons.join(',')); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_RESPONSE_FINISH_REASONS_KEY, finishReasons); } } diff --git a/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts b/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts index 3e4b5692..be52b863 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts @@ -59,55 +59,45 @@ export class InvokeAgentScope extends OpenTelemetryScope { tenantDetails, parentContext, startTime, - endTime + endTime, + callerDetails ); + // Set provider name for agent invocation + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY, invokeAgentDetails.providerName); + // Set session ID and endpoint information this.setTagMaybe(OpenTelemetryConstants.SESSION_ID_KEY, invokeAgentDetails.sessionId); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY, invokeAgentDetails.agentBlueprintId); + if (invokeAgentDetails.endpoint) { this.setTagMaybe(OpenTelemetryConstants.SERVER_ADDRESS_KEY, invokeAgentDetails.endpoint.host); // Only record port if it is different from 443 (default HTTPS port) if (invokeAgentDetails.endpoint.port && invokeAgentDetails.endpoint.port !== 443) { - this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, invokeAgentDetails.endpoint.port); + this.setTagMaybe(OpenTelemetryConstants.SERVER_PORT_KEY, invokeAgentDetails.endpoint.port.toString()); } } // Set request-related tags const requestToUse = invokeAgentDetails.request; if (requestToUse) { - if (requestToUse.executionType) { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_TYPE_KEY, requestToUse.executionType.toString()); - } if (requestToUse.sourceMetadata) { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_ID_KEY, requestToUse.sourceMetadata.id); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, requestToUse.sourceMetadata.name); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, requestToUse.sourceMetadata.description); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, requestToUse.sourceMetadata.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, requestToUse.sourceMetadata.description); } } this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, invokeAgentDetails.conversationId); - // Set caller details tags - if (callerDetails) { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, callerDetails.callerId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, callerDetails.callerUpn); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, callerDetails.callerName); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_TENANT_ID_KEY, callerDetails.tenantId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, callerDetails.callerClientIp); - } - // Set caller agent details tags if (callerAgentDetails) { this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_NAME_KEY, callerAgentDetails.agentName); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY, callerAgentDetails.agentId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_TYPE_KEY, callerAgentDetails.agentType); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, callerAgentDetails.agentBlueprintId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_USER_ID_KEY, callerAgentDetails.agentAUID); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_UPN_KEY, callerAgentDetails.agentUPN); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_TENANT_ID_KEY, callerAgentDetails.tenantId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_CLIENT_IP_KEY, callerAgentDetails.agentClientIP); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY, callerAgentDetails.platformId); } } diff --git a/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts b/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts index b539af14..6e514cd0 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts @@ -3,7 +3,7 @@ import { trace, SpanKind, Span, SpanStatusCode, Attributes, context, AttributeValue, SpanContext, TimeInput } from '@opentelemetry/api'; import { OpenTelemetryConstants } from '../constants'; -import { AgentDetails, TenantDetails } from '../contracts'; +import { AgentDetails, TenantDetails, CallerDetails } from '../contracts'; import { createContextWithParentSpanRef } from '../context/parent-span-context'; import { ParentContext, isParentSpanRef } from '../context/trace-context-propagation'; import logger from '../../utils/logging'; @@ -37,6 +37,7 @@ export abstract class OpenTelemetryScope implements Disposable { * has already completed (e.g. a tool call whose start time was captured earlier). * @param endTime Optional explicit end time (ms epoch, Date, or HrTime). When provided the span will * use this timestamp when {@link dispose} is called instead of the current wall-clock time. + * @param callerDetails Optional caller identity details (id, upn, name, client ip). */ protected constructor( kind: SpanKind, @@ -46,7 +47,8 @@ export abstract class OpenTelemetryScope implements Disposable { tenantDetails?: TenantDetails, parentContext?: ParentContext, startTime?: TimeInput, - endTime?: TimeInput + endTime?: TimeInput, + callerDetails?: CallerDetails ) { // Determine the context to use for span creation let currentContext = context.active(); @@ -68,7 +70,6 @@ export abstract class OpenTelemetryScope implements Disposable { kind, startTime, attributes: { - [OpenTelemetryConstants.GEN_AI_SYSTEM_KEY]: OpenTelemetryConstants.GEN_AI_SYSTEM_VALUE, [OpenTelemetryConstants.GEN_AI_OPERATION_NAME_KEY]: operationName, }, }, currentContext); @@ -85,7 +86,6 @@ export abstract class OpenTelemetryScope implements Disposable { if (agentDetails) { this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, agentDetails.agentId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY, agentDetails.agentName); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_TYPE_KEY, agentDetails.agentType); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_DESCRIPTION_KEY, agentDetails.agentDescription); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_AGENT_PLATFORM_ID_KEY, agentDetails.platformId); this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, agentDetails.conversationId); @@ -99,6 +99,14 @@ export abstract class OpenTelemetryScope implements Disposable { if (tenantDetails) { this.setTagMaybe(OpenTelemetryConstants.TENANT_ID_KEY, tenantDetails.tenantId); } + + // Set caller details if provided + if (callerDetails) { + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, callerDetails.callerId); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, callerDetails.callerUpn); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, callerDetails.callerName); + this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, callerDetails.callerClientIp); + } } /** @@ -176,9 +184,9 @@ export abstract class OpenTelemetryScope implements Disposable { * @param name The tag name * @param value The tag value */ - protected setTagMaybe(name: string, value: T | null | undefined): void { + protected setTagMaybe(name: string, value: T | null | undefined): void { if (value != null) { - this.span.setAttributes({ [name]: value as string | number | boolean }); + this.span.setAttributes({ [name]: value as string | number | boolean | string[] | number[] }); } } @@ -240,10 +248,6 @@ export abstract class OpenTelemetryScope implements Disposable { this.span.setAttributes({ [OpenTelemetryConstants.ERROR_TYPE_KEY]: this.errorType }); } - // Record duration metric (would typically use a meter here) - // For now, we'll add it as a span attribute - this.span.setAttributes({ 'operation.duration': duration }); - this.hasEnded = true; logger.info(`[A365Observability] Ending span[${this.span.spanContext().spanId}], duration: ${duration}s`); } diff --git a/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts b/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts index 145e31b5..9e50c119 100644 --- a/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts +++ b/packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts @@ -3,7 +3,7 @@ import { SpanKind, TimeInput } from '@opentelemetry/api'; import { OpenTelemetryScope } from './OpenTelemetryScope'; -import { AgentDetails, TenantDetails, CallerDetails, OutputResponse, SourceMetadata, ExecutionType } from '../contracts'; +import { AgentDetails, TenantDetails, CallerDetails, OutputResponse, SourceMetadata } from '../contracts'; import { ParentContext } from '../context/trace-context-propagation'; import { OpenTelemetryConstants } from '../constants'; @@ -19,10 +19,9 @@ export class OutputScope extends OpenTelemetryScope { * @param response The response containing initial output messages. * @param agentDetails The details of the agent producing the output. * @param tenantDetails The tenant details. - * @param callerDetails Optional caller identity details (id, upn, name, tenant, client ip). + * @param callerDetails Optional caller identity details (id, upn, name, client ip). * @param conversationId Optional conversation identifier. * @param sourceMetadata Optional source metadata; only `name` and `description` are used for tagging. - * @param executionType Optional execution type (HumanToAgent, Agent2Agent, etc.). * @param parentContext Optional parent context for cross-async-boundary tracing. * Accepts a ParentSpanRef (manual traceId/spanId) or an OTel Context (e.g. from extractTraceContext). * @param startTime Optional explicit start time (ms epoch, Date, or HrTime). @@ -36,12 +35,11 @@ export class OutputScope extends OpenTelemetryScope { callerDetails?: CallerDetails, conversationId?: string, sourceMetadata?: Pick, - executionType?: ExecutionType, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput ): OutputScope { - return new OutputScope(response, agentDetails, tenantDetails, callerDetails, conversationId, sourceMetadata, executionType, parentContext, startTime, endTime); + return new OutputScope(response, agentDetails, tenantDetails, callerDetails, conversationId, sourceMetadata, parentContext, startTime, endTime); } private constructor( @@ -51,7 +49,6 @@ export class OutputScope extends OpenTelemetryScope { callerDetails?: CallerDetails, conversationId?: string, sourceMetadata?: Pick, - executionType?: ExecutionType, parentContext?: ParentContext, startTime?: TimeInput, endTime?: TimeInput @@ -66,7 +63,8 @@ export class OutputScope extends OpenTelemetryScope { tenantDetails, parentContext, startTime, - endTime + endTime, + callerDetails ); // Initialize accumulated messages list from the response @@ -80,18 +78,9 @@ export class OutputScope extends OpenTelemetryScope { // Set conversation, execution type, and source metadata this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, conversationId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_TYPE_KEY, executionType); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, sourceMetadata?.name); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, sourceMetadata?.description); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_NAME_KEY, sourceMetadata?.name); + this.setTagMaybe(OpenTelemetryConstants.CHANNEL_LINK_KEY, sourceMetadata?.description); - // Set caller details if provided - if (callerDetails) { - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, callerDetails.callerId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, callerDetails.callerUpn); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, callerDetails.callerName); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_TENANT_ID_KEY, callerDetails.tenantId); - this.setTagMaybe(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, callerDetails.callerClientIp); - } } /** diff --git a/tests/observability/core/BaggageBuilder.test.ts b/tests/observability/core/BaggageBuilder.test.ts index b4522772..0c1dacf0 100644 --- a/tests/observability/core/BaggageBuilder.test.ts +++ b/tests/observability/core/BaggageBuilder.test.ts @@ -25,21 +25,11 @@ describe('BaggageBuilder', () => { expect(scope).toBeInstanceOf(BaggageScope); }); - it('should set correlation ID', () => { - const builder = new BaggageBuilder(); - builder.correlationId('corr-789'); - - const scope = builder.build(); - expect(scope).toBeInstanceOf(BaggageScope); - }); - it('should chain multiple setters', () => { const builder = new BaggageBuilder() .tenantId('tenant-123') .agentId('agent-456') - .correlationId('corr-789') .agentName('TestAgent') - .agentType('assistant') .agentPlatformId('platform-xyz-123') .conversationId('conv-001'); @@ -58,17 +48,6 @@ describe('BaggageBuilder', () => { expect(bag?.getEntry(OpenTelemetryConstants.GEN_AI_AGENT_PLATFORM_ID_KEY)?.value).toBe('platform-abc-456'); }); - it('should set agent type', () => { - const builder = new BaggageBuilder(); - builder.agentType('assistant'); - - const scope = builder.build(); - expect(scope).toBeInstanceOf(BaggageScope); - - const bag = propagation.getBaggage((scope as any).contextWithBaggage); - expect(bag?.getEntry(OpenTelemetryConstants.GEN_AI_AGENT_TYPE_KEY)?.value).toBe('assistant'); - }); - it('should set caller agent platform ID via fluent API', () => { const builder = new BaggageBuilder(); builder.callerAgentPlatformId('caller-platform-xyz'); @@ -115,8 +94,7 @@ describe('BaggageBuilder', () => { const builder = new BaggageBuilder(); builder.setPairs({ [OpenTelemetryConstants.TENANT_ID_KEY]: 'tenant-123', - [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: null, - [OpenTelemetryConstants.CORRELATION_ID_KEY]: undefined + [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: null }); const scope = builder.build(); @@ -156,8 +134,7 @@ describe('BaggageBuilder', () => { it('should create scope with common fields', () => { const scope = BaggageBuilder.setRequestContext( 'tenant-123', - 'agent-456', - 'corr-789' + 'agent-456' ); expect(scope).toBeInstanceOf(BaggageScope); @@ -166,8 +143,7 @@ describe('BaggageBuilder', () => { it('should handle null values', () => { const scope = BaggageBuilder.setRequestContext( null, - 'agent-456', - null + 'agent-456' ); expect(scope).toBeInstanceOf(BaggageScope); @@ -179,7 +155,6 @@ describe('BaggageBuilder', () => { const scope = new BaggageBuilder() .tenantId('tenant-123') .agentId('agent-456') - .correlationId('corr-789') .sessionId('session-0001') .sessionDescription('My session desc') .build(); diff --git a/tests/observability/core/SpanProcessor.test.ts b/tests/observability/core/SpanProcessor.test.ts index eb4444d0..25c5890c 100644 --- a/tests/observability/core/SpanProcessor.test.ts +++ b/tests/observability/core/SpanProcessor.test.ts @@ -29,7 +29,6 @@ describe('SpanProcessor', () => { it('should copy generic attributes from baggage to span', () => { const baggageEntries = { [OpenTelemetryConstants.TENANT_ID_KEY]: 'tenant-123', - [OpenTelemetryConstants.CORRELATION_ID_KEY]: 'corr-456', [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: 'agent-789' }; @@ -162,14 +161,23 @@ describe('SpanProcessor', () => { describe('attribute registry application', () => { it('should apply all generic attributes', () => { expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.TENANT_ID_KEY); - expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.CORRELATION_ID_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY); expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.SESSION_ID_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.CHANNEL_NAME_KEY); + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.CHANNEL_LINK_KEY); + expect(GENERIC_ATTRIBUTES).not.toContain('correlation.id'); }); it('should apply invoke agent specific attributes', () => { - expect(INVOKE_AGENT_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY); - expect(INVOKE_AGENT_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_EXECUTION_TYPE_KEY); + expect(INVOKE_AGENT_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY); + }); + + it('should include blueprint ID in generic attributes', () => { + expect(GENERIC_ATTRIBUTES).toContain(OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY); }); }); diff --git a/tests/observability/core/agent365-exporter.test.ts b/tests/observability/core/agent365-exporter.test.ts index 778ea104..fd194ca5 100644 --- a/tests/observability/core/agent365-exporter.test.ts +++ b/tests/observability/core/agent365-exporter.test.ts @@ -90,8 +90,7 @@ describe('Agent365Exporter', () => { makeSpan({ [OpenTelemetryConstants.TENANT_ID_KEY]: tenantId, [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: agentId, - [OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY]: '10.0.0.5', - [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_CLIENT_IP_KEY]: '1.0.0.5' + [OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY]: '10.0.0.5' }) ]; @@ -111,7 +110,6 @@ describe('Agent365Exporter', () => { expect(exportedSpan.attributes[OpenTelemetryConstants.TENANT_ID_KEY]).toBe(tenantId); expect(exportedSpan.attributes[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBe(agentId); expect(exportedSpan.attributes[OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY]).toBe('10.0.0.5'); - expect(exportedSpan.attributes[OpenTelemetryConstants.GEN_AI_CALLER_AGENT_CLIENT_IP_KEY]).toBe('1.0.0.5'); }); it.each([ diff --git a/tests/observability/core/output-scope.test.ts b/tests/observability/core/output-scope.test.ts index a706931a..3dba8e0e 100644 --- a/tests/observability/core/output-scope.test.ts +++ b/tests/observability/core/output-scope.test.ts @@ -111,7 +111,7 @@ describe('OutputScope', () => { const scope = OutputScope.start( { messages: ['Test'] }, testAgentDetails, testTenantDetails, - undefined, undefined, undefined, undefined, + undefined, undefined, undefined, { traceId: parentTraceId, spanId: parentSpanId } as ParentSpanRef ); scope.dispose(); diff --git a/tests/observability/core/scopes.test.ts b/tests/observability/core/scopes.test.ts index 14d7fad1..ef0240d2 100644 --- a/tests/observability/core/scopes.test.ts +++ b/tests/observability/core/scopes.test.ts @@ -213,7 +213,6 @@ describe('Scopes', () => { const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); expect(calls).toEqual(expect.arrayContaining([ expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, val: '10.0.0.5' }), - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_AGENT_CLIENT_IP_KEY, val: '192.168.1.100' }) ])); scope1?.dispose(); @@ -224,16 +223,36 @@ describe('Scopes', () => { describe('ExecuteToolScope', () => { it('should create scope with tool details', () => { + const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); + const callerDetails: CallerDetails = { + callerId: 'caller-tool-1', + callerUpn: 'tool.user@contoso.com', + callerName: 'Tool User', + tenantId: 'tool-tenant', + callerClientIp: '10.0.0.10' + }; const scope = ExecuteToolScope.start({ toolName: 'test-tool', arguments: '{"param": "value"}', toolCallId: 'call-123', description: 'A test tool', toolType: 'test' - }, testAgentDetails, testTenantDetails); + }, testAgentDetails, testTenantDetails, undefined, undefined, undefined, undefined, undefined, callerDetails); expect(scope).toBeInstanceOf(ExecuteToolScope); + const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); + expect(calls).toEqual(expect.arrayContaining([ + expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, val: 'caller-tool-1' }), + expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, val: 'Tool User' }), + expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, val: '10.0.0.10' }) + ])); + // Validate raw attribute key strings for schema correctness + const keySet = new Set(calls.map(c => c.key)); + expect(keySet).toContain('microsoft.caller.id'); + expect(keySet).toContain('microsoft.caller.name'); + expect(keySet).toContain('client.address'); scope?.dispose(); + spy.mockRestore(); }); it('should record response', () => { @@ -264,8 +283,8 @@ describe('Scopes', () => { const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); expect(calls).toEqual(expect.arrayContaining([ - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, val: 'ChannelTool' }), - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, val: 'https://channel/tool' }) + expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_NAME_KEY, val: 'ChannelTool' }), + expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_LINK_KEY, val: 'https://channel/tool' }) ])); scope?.dispose(); @@ -277,20 +296,39 @@ describe('Scopes', () => { describe('InferenceScope', () => { it('should create scope with inference details', () => { + const spy = jest.spyOn(OpenTelemetryScope.prototype as any, 'setTagMaybe'); + const callerDetails: CallerDetails = { + callerId: 'caller-inf-1', + callerUpn: 'inf.user@contoso.com', + callerName: 'Inf User', + tenantId: 'inf-tenant', + callerClientIp: '10.0.0.20' + }; const inferenceDetails: InferenceDetails = { operationName: InferenceOperationType.CHAT, model: 'gpt-4', providerName: 'openai', inputTokens: 100, outputTokens: 150, - responseId: 'resp-123', finishReasons: ['stop'] }; - - const scope = InferenceScope.start(inferenceDetails, testAgentDetails, testTenantDetails); + + const scope = InferenceScope.start(inferenceDetails, testAgentDetails, testTenantDetails, undefined, undefined, undefined, undefined, undefined, callerDetails); expect(scope).toBeInstanceOf(InferenceScope); + const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); + expect(calls).toEqual(expect.arrayContaining([ + expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, val: 'caller-inf-1' }), + expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, val: 'Inf User' }), + expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY, val: '10.0.0.20' }) + ])); + // Validate raw attribute key strings for schema correctness + const keySet = new Set(calls.map(c => c.key)); + expect(keySet).toContain('microsoft.caller.id'); + expect(keySet).toContain('microsoft.caller.name'); + expect(keySet).toContain('client.address'); scope?.dispose(); + spy.mockRestore(); }); it('should create scope with minimal details', () => { @@ -317,7 +355,6 @@ describe('Scopes', () => { expect(() => scope?.recordOutputMessages(['Generated response'])).not.toThrow(); expect(() => scope?.recordInputTokens(50)).not.toThrow(); expect(() => scope?.recordOutputTokens(100)).not.toThrow(); - expect(() => scope?.recordResponseId('resp-456')).not.toThrow(); expect(() => scope?.recordFinishReasons(['stop', 'length'])).not.toThrow(); scope?.dispose(); }); @@ -353,8 +390,8 @@ describe('Scopes', () => { const calls = spy.mock.calls.map(args => ({ key: args[0], val: args[1] })); expect(calls).toEqual(expect.arrayContaining([ - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, val: 'ChannelInf' }), - expect.objectContaining({ key: OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, val: 'https://channel/inf' }) + expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_NAME_KEY, val: 'ChannelInf' }), + expect.objectContaining({ key: OpenTelemetryConstants.CHANNEL_LINK_KEY, val: 'https://channel/inf' }) ])); scope?.dispose(); @@ -440,7 +477,6 @@ describe('Scopes', () => { const span = getFinishedSpan(); expect(hrtimeToMs(span.startTime as [number, number])).toBeCloseTo(customStart, -1); expect(hrtimeToMs(span.endTime as [number, number])).toBeCloseTo(customEnd, -1); - expect(span.attributes['operation.duration']).toBeCloseTo(5.0, 1); }); it('setEndTime should override end time when called before dispose', () => { @@ -458,7 +494,6 @@ describe('Scopes', () => { const span = getFinishedSpan(); expect(hrtimeToMs(span.startTime as [number, number])).toBeCloseTo(customStart, -1); expect(hrtimeToMs(span.endTime as [number, number])).toBeCloseTo(laterEnd, -1); - expect(span.attributes['operation.duration']).toBeCloseTo(8.0, 1); }); it('should support Date objects as start and end times', () => { @@ -475,7 +510,6 @@ describe('Scopes', () => { const span = getFinishedSpan(); expect(hrtimeToMs(span.startTime as [number, number])).toBeCloseTo(customStart.getTime(), -1); expect(hrtimeToMs(span.endTime as [number, number])).toBeCloseTo(customEnd.getTime(), -1); - expect(span.attributes['operation.duration']).toBeCloseTo(5.0, 1); }); it('should support HrTime tuples as start and end times', () => { @@ -493,7 +527,6 @@ describe('Scopes', () => { const span = getFinishedSpan(); expect(hrtimeToMs(span.startTime as [number, number])).toBeCloseTo(1700000000000, -1); expect(hrtimeToMs(span.endTime as [number, number])).toBeCloseTo(1700000005500, -1); - expect(span.attributes['operation.duration']).toBeCloseTo(5.5, 1); }); it('should use wall-clock time when no custom times are provided', () => { @@ -511,3 +544,30 @@ describe('Scopes', () => { }); }); }); + +// Validate attribute key constant values use the new schema namespace. +describe('Attribute key schema values', () => { + it('caller keys use microsoft.* / client.* namespace', () => { + expect(OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY).toBe('microsoft.caller.id'); + expect(OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY).toBe('microsoft.caller.name'); + expect(OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY).toBe('microsoft.caller.upn'); + expect(OpenTelemetryConstants.GEN_AI_CALLER_CLIENT_IP_KEY).toBe('client.address'); + }); + + it('caller agent keys use microsoft.a365.* namespace', () => { + expect(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY).toBe('microsoft.a365.caller.agent.id'); + expect(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_NAME_KEY).toBe('microsoft.a365.caller.agent.name'); + expect(OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY).toBe('microsoft.a365.caller.agent.blueprint.id'); + }); + + it('channel keys use microsoft.channel.* namespace', () => { + expect(OpenTelemetryConstants.CHANNEL_NAME_KEY).toBe('microsoft.channel.name'); + expect(OpenTelemetryConstants.CHANNEL_LINK_KEY).toBe('microsoft.channel.link'); + }); + + it('session and tenant keys use microsoft.* namespace', () => { + expect(OpenTelemetryConstants.SESSION_ID_KEY).toBe('microsoft.session.id'); + expect(OpenTelemetryConstants.SESSION_DESCRIPTION_KEY).toBe('microsoft.session.description'); + expect(OpenTelemetryConstants.TENANT_ID_KEY).toBe('microsoft.tenant.id'); + }); +}); diff --git a/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts b/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts index f6d65821..37e117e7 100644 --- a/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts +++ b/tests/observability/extension/hosting/BaggageBuilderUtils.test.ts @@ -44,7 +44,6 @@ describe('BaggageBuilderUtils', () => { expect(asObj[OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY]).toBe('aad-object-1'); expect(asObj[OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY]).toBe('User One'); expect(asObj[OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY]).toBe('agentic-user-1'); - expect(asObj[OpenTelemetryConstants.GEN_AI_CALLER_TENANT_ID_KEY]).toBe('tenant1'); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBe('agent-app-1'); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY]).toBe('Agent One'); expect(asObj[OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY]).toBe('aad-object-2'); diff --git a/tests/observability/extension/hosting/baggage-middleware.test.ts b/tests/observability/extension/hosting/baggage-middleware.test.ts index f2d1907b..4590890d 100644 --- a/tests/observability/extension/hosting/baggage-middleware.test.ts +++ b/tests/observability/extension/hosting/baggage-middleware.test.ts @@ -98,7 +98,7 @@ describe('BaggageMiddleware', () => { expect(capturedBaggage[OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY]).toBe('user-oid'); expect(capturedBaggage[OpenTelemetryConstants.TENANT_ID_KEY]).toBe('tenant-123'); expect(capturedBaggage[OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]).toBe('agent-1'); - expect(capturedBaggage[OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY]).toBe('web'); + expect(capturedBaggage[OpenTelemetryConstants.CHANNEL_NAME_KEY]).toBe('web'); expect(capturedBaggage[OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY]).toBe('conv-001'); }); diff --git a/tests/observability/extension/hosting/output-logging-middleware.test.ts b/tests/observability/extension/hosting/output-logging-middleware.test.ts index 448384bb..8add1359 100644 --- a/tests/observability/extension/hosting/output-logging-middleware.test.ts +++ b/tests/observability/extension/hosting/output-logging-middleware.test.ts @@ -175,7 +175,7 @@ describe('OutputLoggingMiddleware', () => { expect(outputSpan!.attributes[OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY]).toBe('user-oid'); expect(outputSpan!.attributes[OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY]).toBe('Test User'); - expect(outputSpan!.attributes[OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY]).toBe('teams'); + expect(outputSpan!.attributes[OpenTelemetryConstants.CHANNEL_NAME_KEY]).toBe('teams'); }); it('should link OutputScope to parent when parentSpanRef is set in turnState', async () => { diff --git a/tests/observability/extension/hosting/scope-utils.test.ts b/tests/observability/extension/hosting/scope-utils.test.ts index 73b5e69d..e379aaea 100644 --- a/tests/observability/extension/hosting/scope-utils.test.ts +++ b/tests/observability/extension/hosting/scope-utils.test.ts @@ -61,21 +61,20 @@ describe('ScopeUtils.populateFromTurnContext', () => { const scope = ScopeUtils.populateInferenceScopeFromTurnContext(details, ctx) as InferenceScope; expect(scope).toBeInstanceOf(InferenceScope); const calls = spy.mock.calls.map(args => [args[0], args[1]]); - expect(calls).toEqual( - expect.arrayContaining([ - [OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, 'conv-A'], - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, 'web'], - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, 'https://web'], - [OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY, 'Agent One'], - [OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY, 'agent-oid'], - [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], - [OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY, 'agent-blueprint-1'], - [OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY, 'agent-upn@contoso.com'], - [OpenTelemetryConstants.GEN_AI_AGENT_DESCRIPTION_KEY, 'assistant'], - [OpenTelemetryConstants.TENANT_ID_KEY, 'tenant-123'], - [OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, JSON.stringify(['input text'])] - ]) - ); + const expected = [ + [OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, 'conv-A'], + [OpenTelemetryConstants.CHANNEL_NAME_KEY, 'web'], + [OpenTelemetryConstants.CHANNEL_LINK_KEY, 'https://web'], + [OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY, 'Agent One'], + [OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY, 'agent-oid'], + [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], + [OpenTelemetryConstants.GEN_AI_AGENT_BLUEPRINT_ID_KEY, 'agent-blueprint-1'], + [OpenTelemetryConstants.GEN_AI_AGENT_UPN_KEY, 'agent-upn@contoso.com'], + [OpenTelemetryConstants.GEN_AI_AGENT_DESCRIPTION_KEY, 'assistant'], + [OpenTelemetryConstants.TENANT_ID_KEY, 'tenant-123'], + [OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, JSON.stringify(['input text'])] + ]; + expect(calls).toEqual(expect.arrayContaining(expected)); scope?.dispose(); }); @@ -124,26 +123,24 @@ describe('ScopeUtils.populateFromTurnContext', () => { const scope = ScopeUtils.populateInvokeAgentScopeFromTurnContext(details, ctx) as InvokeAgentScope; expect(scope).toBeInstanceOf(InvokeAgentScope); const calls = spy.mock.calls.map(args => [args[0], args[1]]); - expect(calls).toEqual( - expect.arrayContaining([ - [OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, 'conv-B'], - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, 'teams'], - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, 'https://teams'], - [OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, 'user-oid'], - [OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, 'Test User'], - [OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, 'user@contoso.com'], - [OpenTelemetryConstants.GEN_AI_CALLER_TENANT_ID_KEY, 'tenant-xyz'], - [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_USER_ID_KEY, 'user-oid'], - [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_NAME_KEY, 'Test User'], - [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY, 'callerAgent-1'], - [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, 'caller-agentBlueprintId'], - [OpenTelemetryConstants.TENANT_ID_KEY, 'tenant-123'], - [OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, JSON.stringify(['invoke message'])], - [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], - [OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY, 'Agent One'], - [OpenTelemetryConstants.GEN_AI_AGENT_DESCRIPTION_KEY, 'assistant'] - ]) - ); + const expected = [ + [OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, 'conv-B'], + [OpenTelemetryConstants.CHANNEL_NAME_KEY, 'teams'], + [OpenTelemetryConstants.CHANNEL_LINK_KEY, 'https://teams'], + [OpenTelemetryConstants.GEN_AI_CALLER_ID_KEY, 'user-oid'], + [OpenTelemetryConstants.GEN_AI_CALLER_NAME_KEY, 'Test User'], + [OpenTelemetryConstants.GEN_AI_CALLER_UPN_KEY, 'user@contoso.com'], + [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_USER_ID_KEY, 'user-oid'], + [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_NAME_KEY, 'Test User'], + [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_ID_KEY, 'callerAgent-1'], + [OpenTelemetryConstants.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, 'caller-agentBlueprintId'], + [OpenTelemetryConstants.TENANT_ID_KEY, 'tenant-123'], + [OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, JSON.stringify(['invoke message'])], + [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], + [OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY, 'Agent One'], + [OpenTelemetryConstants.GEN_AI_AGENT_DESCRIPTION_KEY, 'assistant'] + ]; + expect(calls).toEqual(expect.arrayContaining(expected)); scope?.dispose(); }); @@ -156,8 +153,8 @@ describe('ScopeUtils.populateFromTurnContext', () => { expect(calls).toEqual( expect.arrayContaining([ [OpenTelemetryConstants.GEN_AI_CONVERSATION_ID_KEY, 'conv-C'], - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_NAME_KEY, 'cli'], - [OpenTelemetryConstants.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, 'https://cli'], + [OpenTelemetryConstants.CHANNEL_NAME_KEY, 'cli'], + [OpenTelemetryConstants.CHANNEL_LINK_KEY, 'https://cli'], [OpenTelemetryConstants.GEN_AI_AGENT_AUID_KEY, 'agent-oid'], [OpenTelemetryConstants.GEN_AI_AGENT_NAME_KEY, 'Agent One'], [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY, 'agent-1'], diff --git a/tests/observability/integration/openai-agent-instrument.test.ts b/tests/observability/integration/openai-agent-instrument.test.ts index 30b70366..eb4e9120 100644 --- a/tests/observability/integration/openai-agent-instrument.test.ts +++ b/tests/observability/integration/openai-agent-instrument.test.ts @@ -141,7 +141,7 @@ describe("OpenAI Trace Processor Integration Tests", () => { ], ).toBe("chat"); expect( - generationSpan.attributes[OpenTelemetryConstants.GEN_AI_SYSTEM_KEY], + generationSpan.attributes[OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY], ).toBe("openai"); expect( generationSpan.attributes[ @@ -199,7 +199,7 @@ describe("OpenAI Trace Processor Integration Tests", () => { ], ).toBe("invoke_agent"); expect( - agentSpan.attributes[OpenTelemetryConstants.GEN_AI_SYSTEM_KEY], + agentSpan.attributes[OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY], ).toBe("openai"); expect( agentSpan?.attributes[ @@ -310,7 +310,7 @@ describe("OpenAI Trace Processor Integration Tests", () => { ], ).toBe("invoke_agent"); expect( - agentSpan.attributes[OpenTelemetryConstants.GEN_AI_SYSTEM_KEY], + agentSpan.attributes[OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY], ).toBe("openai"); console.log("✅ Agent span validated"); } @@ -335,7 +335,7 @@ describe("OpenAI Trace Processor Integration Tests", () => { toolSpan.attributes[OpenTelemetryConstants.GEN_AI_OPERATION_NAME_KEY], ).toBe("execute_tool"); expect( - toolSpan.attributes[OpenTelemetryConstants.GEN_AI_SYSTEM_KEY], + toolSpan.attributes[OpenTelemetryConstants.GEN_AI_PROVIDER_NAME_KEY], ).toBe("openai"); expect( toolSpan.attributes[OpenTelemetryConstants.GEN_AI_TOOL_NAME_KEY], @@ -347,7 +347,7 @@ describe("OpenAI Trace Processor Integration Tests", () => { toolSpan.attributes[OpenTelemetryConstants.GEN_AI_TOOL_ARGS_KEY], ).toBe('{"a":15,"b":27}'); expect( - toolSpan.attributes[OpenTelemetryConstants.GEN_AI_EVENT_CONTENT], + toolSpan.attributes[OpenTelemetryConstants.GEN_AI_TOOL_CALL_RESULT_KEY], ).toBe("The sum of 15 and 27 is 42"); validateParentChildRelationship(toolSpan, agentSpan!); diff --git a/tests/observability/tracing/exporter-utils.test.ts b/tests/observability/tracing/exporter-utils.test.ts index d9a9fcc1..2c783cf5 100644 --- a/tests/observability/tracing/exporter-utils.test.ts +++ b/tests/observability/tracing/exporter-utils.test.ts @@ -6,6 +6,7 @@ import { SpanKind, SpanStatusCode } from '@opentelemetry/api'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { ClusterCategory, DefaultConfigurationProvider } from '@microsoft/agents-a365-runtime'; import { ObservabilityConfiguration } from '@microsoft/agents-a365-observability/src/configuration/ObservabilityConfiguration'; +import { OpenTelemetryConstants } from '@microsoft/agents-a365-observability/src/tracing/constants'; describe('exporter/utils', () => { const originalEnv = process.env; @@ -399,8 +400,8 @@ describe('exporter/utils', () => { ended: true, status: { code: SpanStatusCode.OK }, attributes: { - ...(tenantId !== undefined && { 'tenant.id': tenantId }), - ...(agentId !== undefined && { 'gen_ai.agent.id': agentId }), + ...(tenantId !== undefined && { [OpenTelemetryConstants.TENANT_ID_KEY]: tenantId }), + ...(agentId !== undefined && { [OpenTelemetryConstants.GEN_AI_AGENT_ID_KEY]: agentId }), }, links: [], events: [],