diff --git a/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/GenAi/ActivityExtensions.cs b/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/GenAi/ActivityExtensions.cs
new file mode 100644
index 0000000..8f984af
--- /dev/null
+++ b/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/GenAi/ActivityExtensions.cs
@@ -0,0 +1,167 @@
+using System.Diagnostics;
+
+namespace ANcpSdk.AspNetCore.ServiceDefaults.Instrumentation.GenAi;
+
+///
+/// Extension methods for setting GenAI semantic convention attributes on Activities.
+///
+///
+/// Provides fluent API for OTel 1.39 GenAI semantic conventions.
+///
+public static class GenAiActivityExtensions
+{
+ ///
+ /// Sets GenAI request attributes on the activity.
+ ///
+ /// The activity to set tags on.
+ /// The model requested (e.g., "gpt-4o", "claude-3-opus").
+ /// Temperature setting (0.0-2.0).
+ /// Maximum tokens to generate.
+ /// Nucleus sampling threshold.
+ /// Top-k sampling parameter.
+ /// The activity for fluent chaining.
+ public static Activity SetGenAiRequest(
+ this Activity activity,
+ string? model = null,
+ double? temperature = null,
+ int? maxTokens = null,
+ double? topP = null,
+ int? topK = null)
+ {
+ ArgumentNullException.ThrowIfNull(activity);
+
+ if (model is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.RequestModel, model);
+
+ if (temperature.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.RequestTemperature, temperature.Value);
+
+ if (maxTokens.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.RequestMaxTokens, maxTokens.Value);
+
+ if (topP.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.RequestTopP, topP.Value);
+
+ if (topK.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.RequestTopK, topK.Value);
+
+ return activity;
+ }
+
+ ///
+ /// Sets GenAI token usage attributes on the activity.
+ ///
+ /// The activity to set tags on.
+ /// Number of input/prompt tokens.
+ /// Number of output/completion tokens.
+ /// Number of cached input tokens (Anthropic).
+ /// Number of reasoning tokens (o1-style models).
+ /// The activity for fluent chaining.
+ public static Activity SetGenAiUsage(
+ this Activity activity,
+ long? inputTokens = null,
+ long? outputTokens = null,
+ long? cachedTokens = null,
+ long? reasoningTokens = null)
+ {
+ ArgumentNullException.ThrowIfNull(activity);
+
+ if (inputTokens.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.UsageInputTokens, inputTokens.Value);
+
+ if (outputTokens.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.UsageOutputTokens, outputTokens.Value);
+
+ if (cachedTokens.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.UsageInputTokensCached, cachedTokens.Value);
+
+ if (reasoningTokens.HasValue)
+ activity.SetTag(SemanticConventions.GenAi.UsageOutputTokensReasoning, reasoningTokens.Value);
+
+ return activity;
+ }
+
+ ///
+ /// Sets GenAI response attributes on the activity.
+ ///
+ /// The activity to set tags on.
+ /// The model that generated the response.
+ /// Unique completion identifier.
+ /// Reasons the model stopped generating.
+ /// The activity for fluent chaining.
+ public static Activity SetGenAiResponse(
+ this Activity activity,
+ string? model = null,
+ string? responseId = null,
+ string[]? finishReasons = null)
+ {
+ ArgumentNullException.ThrowIfNull(activity);
+
+ if (model is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.ResponseModel, model);
+
+ if (responseId is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.ResponseId, responseId);
+
+ if (finishReasons is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.ResponseFinishReasons, finishReasons);
+
+ return activity;
+ }
+
+ ///
+ /// Sets GenAI agent attributes on the activity (OTel 1.38+).
+ ///
+ /// The activity to set tags on.
+ /// Unique agent identifier.
+ /// Human-readable agent name.
+ /// Agent description.
+ /// The activity for fluent chaining.
+ public static Activity SetGenAiAgent(
+ this Activity activity,
+ string? agentId = null,
+ string? agentName = null,
+ string? agentDescription = null)
+ {
+ ArgumentNullException.ThrowIfNull(activity);
+
+ if (agentId is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.AgentId, agentId);
+
+ if (agentName is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.AgentName, agentName);
+
+ if (agentDescription is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.AgentDescription, agentDescription);
+
+ return activity;
+ }
+
+ ///
+ /// Sets GenAI tool attributes on the activity (OTel 1.39).
+ ///
+ /// The activity to set tags on.
+ /// Tool name.
+ /// Tool call identifier.
+ /// Session/thread identifier for multi-turn conversations.
+ /// The activity for fluent chaining.
+ public static Activity SetGenAiTool(
+ this Activity activity,
+ string? toolName = null,
+ string? toolCallId = null,
+ string? conversationId = null)
+ {
+ ArgumentNullException.ThrowIfNull(activity);
+
+ if (toolName is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.ToolName, toolName);
+
+ if (toolCallId is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.ToolCallId, toolCallId);
+
+ if (conversationId is { Length: > 0 })
+ activity.SetTag(SemanticConventions.GenAi.ConversationId, conversationId);
+
+ return activity;
+ }
+}
diff --git a/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/GenAi/GenAiInstrumentation.cs b/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/GenAi/GenAiInstrumentation.cs
index 5b60a9f..e17accd 100644
--- a/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/GenAi/GenAiInstrumentation.cs
+++ b/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/GenAi/GenAiInstrumentation.cs
@@ -90,7 +90,8 @@ public static TResponse Execute(
private static void SetRequestTags(Activity activity, string provider, string operation, string? model)
{
- activity.SetTag(SemanticConventions.GenAi.System, provider);
+ // OTel 1.37+: gen_ai.system → gen_ai.provider.name
+ activity.SetTag(SemanticConventions.GenAi.ProviderName, provider);
activity.SetTag(SemanticConventions.GenAi.OperationName, operation);
if (model is { Length: > 0 })
@@ -108,11 +109,15 @@ private static void SetResponseTags(
try
{
var usage = extractUsage(response);
- activity.SetTag(SemanticConventions.GenAi.InputTokens, usage.InputTokens);
- activity.SetTag(SemanticConventions.GenAi.OutputTokens, usage.OutputTokens);
+ // OTel 1.37+: Explicit Usage prefix
+ activity.SetTag(SemanticConventions.GenAi.UsageInputTokens, usage.InputTokens);
+ activity.SetTag(SemanticConventions.GenAi.UsageOutputTokens, usage.OutputTokens);
}
- catch
+ catch (Exception ex)
{
+ // Record extraction failure as event for debugging
+ activity.AddEvent(new ActivityEvent("gen_ai.usage.extraction_failed",
+ tags: new ActivityTagsCollection { ["exception.message"] = ex.Message }));
}
}
diff --git a/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/SemanticConventions.cs b/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/SemanticConventions.cs
index 71c43eb..70bbfe4 100644
--- a/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/SemanticConventions.cs
+++ b/eng/ANcpSdk.AspNetCore.ServiceDefaults/Instrumentation/SemanticConventions.cs
@@ -9,33 +9,131 @@ namespace ANcpSdk.AspNetCore.ServiceDefaults.Instrumentation;
internal static class SemanticConventions
{
///
- /// GenAI semantic conventions.
+ /// GenAI semantic conventions (OTel 1.39).
///
///
/// See: https://opentelemetry.io/docs/specs/semconv/gen-ai/
///
public static class GenAi
{
- /// The GenAI system (e.g., "openai", "anthropic", "ollama").
- public const string System = "gen_ai.system";
+ /// Schema URL for OTel 1.39 semantic conventions.
+ public const string SchemaUrl = "https://opentelemetry.io/schemas/1.39.0";
- /// The operation name (e.g., "chat", "embeddings").
+ // =====================================================================
+ // CORE ATTRIBUTES (Required/Conditionally Required)
+ // =====================================================================
+
+ /// gen_ai.provider.name - The GenAI provider (e.g., "openai", "anthropic").
+ /// OTel 1.37+: Replaces deprecated gen_ai.system
+ public const string ProviderName = "gen_ai.provider.name";
+
+ /// gen_ai.operation.name - The operation being performed (e.g., "chat", "embeddings").
public const string OperationName = "gen_ai.operation.name";
- /// The model requested (e.g., "gpt-4o", "claude-3-opus").
+ // =====================================================================
+ // REQUEST PARAMETERS
+ // =====================================================================
+
+ /// gen_ai.request.model - The model requested (e.g., "gpt-4o", "claude-3-opus").
public const string RequestModel = "gen_ai.request.model";
- /// The model that generated the response.
+ /// gen_ai.request.temperature - Temperature setting (0.0-2.0).
+ public const string RequestTemperature = "gen_ai.request.temperature";
+
+ /// gen_ai.request.max_tokens - Maximum tokens to generate.
+ public const string RequestMaxTokens = "gen_ai.request.max_tokens";
+
+ /// gen_ai.request.top_p - Nucleus sampling threshold.
+ public const string RequestTopP = "gen_ai.request.top_p";
+
+ /// gen_ai.request.top_k - Top-k sampling parameter.
+ public const string RequestTopK = "gen_ai.request.top_k";
+
+ /// gen_ai.request.stop_sequences - Stop sequence array.
+ public const string RequestStopSequences = "gen_ai.request.stop_sequences";
+
+ /// gen_ai.request.frequency_penalty - Frequency penalty (-2.0 to 2.0).
+ public const string RequestFrequencyPenalty = "gen_ai.request.frequency_penalty";
+
+ /// gen_ai.request.presence_penalty - Presence penalty (-2.0 to 2.0).
+ public const string RequestPresencePenalty = "gen_ai.request.presence_penalty";
+
+ /// gen_ai.request.seed - Reproducibility seed.
+ public const string RequestSeed = "gen_ai.request.seed";
+
+ // =====================================================================
+ // RESPONSE ATTRIBUTES
+ // =====================================================================
+
+ /// gen_ai.response.model - The model that generated the response.
public const string ResponseModel = "gen_ai.response.model";
- /// Number of tokens in the input/prompt.
- public const string InputTokens = "gen_ai.usage.input_tokens";
+ /// gen_ai.response.id - Unique completion identifier.
+ public const string ResponseId = "gen_ai.response.id";
+
+ /// gen_ai.response.finish_reasons - Reasons the model stopped generating.
+ public const string ResponseFinishReasons = "gen_ai.response.finish_reasons";
+
+ // =====================================================================
+ // USAGE/TOKENS (OTel 1.37+ naming)
+ // =====================================================================
+
+ /// gen_ai.usage.input_tokens - Number of tokens in the input/prompt.
+ public const string UsageInputTokens = "gen_ai.usage.input_tokens";
+
+ /// gen_ai.usage.output_tokens - Number of tokens in the output/completion.
+ public const string UsageOutputTokens = "gen_ai.usage.output_tokens";
+
+ /// gen_ai.usage.input_tokens.cached - Cached input tokens (Anthropic).
+ public const string UsageInputTokensCached = "gen_ai.usage.input_tokens.cached";
+
+ /// gen_ai.usage.output_tokens.reasoning - Reasoning output tokens (o1-style models).
+ public const string UsageOutputTokensReasoning = "gen_ai.usage.output_tokens.reasoning";
+
+ // =====================================================================
+ // AGENT ATTRIBUTES (OTel 1.38+)
+ // =====================================================================
+
+ /// gen_ai.agent.id - Unique agent identifier.
+ public const string AgentId = "gen_ai.agent.id";
+
+ /// gen_ai.agent.name - Human-readable agent name.
+ public const string AgentName = "gen_ai.agent.name";
+
+ /// gen_ai.agent.description - Agent description.
+ public const string AgentDescription = "gen_ai.agent.description";
+
+ // =====================================================================
+ // TOOL ATTRIBUTES (OTel 1.39)
+ // =====================================================================
+
+ /// gen_ai.tool.name - Tool name.
+ public const string ToolName = "gen_ai.tool.name";
+
+ /// gen_ai.tool.call.id - Tool call identifier.
+ public const string ToolCallId = "gen_ai.tool.call.id";
+
+ /// gen_ai.conversation.id - Session/thread identifier.
+ public const string ConversationId = "gen_ai.conversation.id";
+
+ // =====================================================================
+ // DEPRECATED (for backward compatibility)
+ // =====================================================================
+
+ ///
+ /// Deprecated attribute names (pre-1.37). Use for migration/normalization.
+ ///
+ public static class Deprecated
+ {
+ /// gen_ai.system - DEPRECATED: Use ProviderName instead.
+ public const string System = "gen_ai.system";
- /// Number of tokens in the output/completion.
- public const string OutputTokens = "gen_ai.usage.output_tokens";
+ /// gen_ai.usage.prompt_tokens - DEPRECATED: Use UsageInputTokens instead.
+ public const string UsagePromptTokens = "gen_ai.usage.prompt_tokens";
- /// Reasons the model stopped generating (e.g., "stop", "length").
- public const string FinishReasons = "gen_ai.response.finish_reasons";
+ /// gen_ai.usage.completion_tokens - DEPRECATED: Use UsageOutputTokens instead.
+ public const string UsageCompletionTokens = "gen_ai.usage.completion_tokens";
+ }
}
///