Skip to content
Merged
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
21 changes: 2 additions & 19 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -6121,7 +6121,7 @@ func executeRequestWithRetries[T any](
keyTracer.SetAttribute(keyHandle, schemas.AttrBifrostProviderName, string(providerKey)) // raw Bifrost short name, mirrors canonical gen_ai.provider.name
keyTracer.SetAttribute(keyHandle, schemas.AttrRequestModel, model)
if attempts > 0 {
keyTracer.SetAttribute(keyHandle, schemas.AttrLegacyRetryCount, attempts)
keyTracer.SetAttribute(keyHandle, schemas.AttrBifrostRetries, attempts)
}
}

Expand Down Expand Up @@ -6293,46 +6293,31 @@ func executeRequestWithRetries[T any](
span.SetAttribute(schemas.AttrBifrostProviderName, string(providerKey)) // raw Bifrost short name, mirrors canonical gen_ai.provider.name
span.SetAttribute(schemas.AttrRequestModel, model)
span.SetAttribute(schemas.AttrOperationName, otelOp)
span.SetAttribute(schemas.AttrLegacyRequestType, string(requestType)) // legacy: replaced by gen_ai.operation.name
if attempts > 0 {
span.SetAttribute(schemas.AttrLegacyRetryCount, attempts) // legacy: bare key with no semconv prefix
}
span.SetAttribute(schemas.AttrLegacyRequestType, string(requestType))

// Add context-related attributes (selected key, virtual key, team, customer, etc.)
// Each AttrXxx (gen_ai.*) emission below is LEGACY namespace pollution: the
// Bifrost-internal concept does not belong under gen_ai.*. The bifrost.* mirrors
// are the canonical home going forward; once all dashboards migrate, drop the
// gen_ai.* lines (grep for "// legacy:" in this block).
if selectedKeyID, ok := ctx.Value(schemas.BifrostContextKeySelectedKeyID).(string); ok && selectedKeyID != "" {
span.SetAttribute(schemas.AttrSelectedKeyID, selectedKeyID) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostSelectedKeyID, selectedKeyID)
}
if selectedKeyName, ok := ctx.Value(schemas.BifrostContextKeySelectedKeyName).(string); ok && selectedKeyName != "" {
span.SetAttribute(schemas.AttrSelectedKeyName, selectedKeyName) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostSelectedKeyName, selectedKeyName)
}
if virtualKeyID, ok := ctx.Value(schemas.BifrostContextKeyGovernanceVirtualKeyID).(string); ok && virtualKeyID != "" {
span.SetAttribute(schemas.AttrVirtualKeyID, virtualKeyID) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostVirtualKeyID, virtualKeyID)
}
if virtualKeyName, ok := ctx.Value(schemas.BifrostContextKeyGovernanceVirtualKeyName).(string); ok && virtualKeyName != "" {
span.SetAttribute(schemas.AttrVirtualKeyName, virtualKeyName) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostVirtualKeyName, virtualKeyName)
}
if teamID, ok := ctx.Value(schemas.BifrostContextKeyGovernanceTeamID).(string); ok && teamID != "" {
span.SetAttribute(schemas.AttrTeamID, teamID) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostTeamID, teamID)
}
if teamName, ok := ctx.Value(schemas.BifrostContextKeyGovernanceTeamName).(string); ok && teamName != "" {
span.SetAttribute(schemas.AttrTeamName, teamName) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostTeamName, teamName)
}
if customerID, ok := ctx.Value(schemas.BifrostContextKeyGovernanceCustomerID).(string); ok && customerID != "" {
span.SetAttribute(schemas.AttrCustomerID, customerID) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostCustomerID, customerID)
}
if customerName, ok := ctx.Value(schemas.BifrostContextKeyGovernanceCustomerName).(string); ok && customerName != "" {
span.SetAttribute(schemas.AttrCustomerName, customerName) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostCustomerName, customerName)
}
if businessUnitID, ok := ctx.Value(schemas.BifrostContextKeyGovernanceBusinessUnitID).(string); ok && businessUnitID != "" {
Expand Down Expand Up @@ -6369,10 +6354,8 @@ func executeRequestWithRetries[T any](
span.SetAttribute(schemas.AttrBifrostUserEmail, userEmail)
}
if fallbackIndex, ok := ctx.Value(schemas.BifrostContextKeyFallbackIndex).(int); ok {
span.SetAttribute(schemas.AttrFallbackIndex, fallbackIndex) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostFallbackIndex, fallbackIndex)
}
span.SetAttribute(schemas.AttrNumberOfRetries, attempts) // legacy: gen_ai.* placement of bifrost-internal attr
span.SetAttribute(schemas.AttrBifrostRetries, attempts)

// Surface caller-supplied extra headers (from x-bf-eh-* and direct-allowlist
Expand Down
1 change: 0 additions & 1 deletion core/providers/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3830,7 +3830,6 @@ func completeDeferredSpan(ctx *schemas.BifrostContext, result *schemas.BifrostRe
// Set TTFT and chunk count attributes regardless of accumulated response availability
// (GetAccumulatedChunks may return nil response while still providing valid metrics)
if ttftNs > 0 {
tracer.SetAttribute(handle, schemas.AttrTimeToFirstToken, ttftNs) // legacy: nanoseconds; replaced by gen_ai.response.time_to_first_chunk
tracer.SetAttribute(handle, schemas.AttrTimeToFirstChunk, float64(ttftNs)/1e9) // spec: seconds
}
if chunkCount > 0 {
Expand Down
65 changes: 14 additions & 51 deletions core/schemas/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,15 +710,11 @@ const (
AttrEcho = "gen_ai.request.echo"
AttrLogitBias = "gen_ai.request.logit_bias"
AttrLogProbs = "gen_ai.request.logprobs"
AttrN = "gen_ai.request.n" // legacy: replaced by AttrChoiceCount
AttrChoiceCount = "gen_ai.request.choice.count"
// AttrEmbeddingsDimensionCount is the OTel spec key for embedding dimensions
// (Bifrost historically emitted AttrDimensions = gen_ai.request.dimensions).
// AttrEmbeddingsDimensionCount is the OTel spec key for embedding dimensions.
AttrEmbeddingsDimensionCount = "gen_ai.embeddings.dimension.count"
AttrSeed = "gen_ai.request.seed"
AttrSuffix = "gen_ai.request.suffix"
AttrDimensions = "gen_ai.request.dimensions" // legacy: replaced by AttrEmbeddingsDimensionCount
AttrEncodingFormat = "gen_ai.request.encoding_format" // legacy: singular form; replaced by AttrEncodingFormats (string[])
AttrEncodingFormats = "gen_ai.request.encoding_formats"
AttrLanguage = "gen_ai.request.language"
AttrPrompt = "gen_ai.request.prompt"
Expand All @@ -739,7 +735,6 @@ const (
AttrServiceTier = "gen_ai.response.service_tier"
AttrCreated = "gen_ai.response.created"
AttrObject = "gen_ai.response.object"
AttrTimeToFirstToken = "gen_ai.response.time_to_first_token" // legacy: nanoseconds; replaced by gen_ai.response.time_to_first_chunk (seconds)
AttrTimeToFirstChunk = "gen_ai.response.time_to_first_chunk"
AttrTotalChunks = "gen_ai.response.total_chunks"

Expand All @@ -750,28 +745,21 @@ const (
AttrPluginErrorCount = "plugin.error_count"

// Usage Attributes
// legacy: AttrPromptTokens / AttrCompletionTokens are the deprecated OTel names;
// new code should use AttrInputTokens / AttrOutputTokens. Kept for dashboards.
AttrPromptTokens = "gen_ai.usage.prompt_tokens"
AttrCompletionTokens = "gen_ai.usage.completion_tokens"
AttrTotalTokens = "gen_ai.usage.total_tokens"
AttrInputTokens = "gen_ai.usage.input_tokens"
AttrOutputTokens = "gen_ai.usage.output_tokens"
AttrUsageCost = "gen_ai.usage.cost"
AttrTotalTokens = "gen_ai.usage.total_tokens"
AttrInputTokens = "gen_ai.usage.input_tokens"
AttrOutputTokens = "gen_ai.usage.output_tokens"
AttrUsageCost = "gen_ai.usage.cost"
// OTel GenAI spec keys for cache tokens (flat namespace).
AttrUsageCacheReadInputTokens = "gen_ai.usage.cache_read.input_tokens"
AttrUsageCacheCreationInputTokens = "gen_ai.usage.cache_creation.input_tokens"
// OTel GenAI spec key for reasoning tokens (flat namespace).
AttrUsageReasoningOutputTokens = "gen_ai.usage.reasoning.output_tokens"
// Chat completion usage detail attributes
// legacy: nested namespace; OTel spec uses flat gen_ai.usage.cache_read.input_tokens
// and gen_ai.usage.cache_creation.input_tokens for the cached_* entries. The
// non-cached fields below have no spec equivalent and stay as-is.
// Chat completion usage detail attributes. These non-cached fields have no OTel
// spec equivalent and stay as-is; cache tokens use the flat gen_ai.usage.cache_*
// keys (AttrUsageCacheReadInputTokens / AttrUsageCacheCreationInputTokens).
AttrPromptTokenDetailsText = "gen_ai.usage.prompt_token_details.text_tokens"
AttrPromptTokenDetailsAudio = "gen_ai.usage.prompt_token_details.audio_tokens"
AttrPromptTokenDetailsImage = "gen_ai.usage.prompt_token_details.image_tokens"
AttrPromptTokenDetailsCachedRead = "gen_ai.usage.prompt_token_details.cached_read_tokens" // legacy: see AttrUsageCacheReadInputTokens
AttrPromptTokenDetailsCachedWrite = "gen_ai.usage.prompt_token_details.cached_write_tokens" // legacy: see AttrUsageCacheCreationInputTokens
AttrPromptTokenDetailsCachedWrite5m = "gen_ai.usage.prompt_token_details.cached_write_tokens_5m"
AttrPromptTokenDetailsCachedWrite1h = "gen_ai.usage.prompt_token_details.cached_write_tokens_1h"
AttrCompletionTokenDetailsText = "gen_ai.usage.completion_token_details.text_tokens"
Expand All @@ -784,10 +772,7 @@ const (
AttrCompletionTokenDetailsSearch = "gen_ai.usage.completion_token_details.num_search_queries"

// Error Attributes
AttrError = "gen_ai.error"
// legacy: AttrErrorType is the gen_ai.* placement; OTel general semconv uses the
// unprefixed "error.type". Emitted in parallel from PopulateErrorAttributes.
AttrErrorType = "gen_ai.error.type"
AttrError = "gen_ai.error"
AttrErrorCode = "gen_ai.error.code"
// AttrHTTPResponseStatusCode is the OTel semconv HTTP response status code (e.g. 400).
// Sourced from BifrostError.StatusCode; used as the status_code dimension on error metrics.
Expand All @@ -800,24 +785,6 @@ const (
AttrInputEmbedding = "gen_ai.input.embedding"
AttrOutputMessages = "gen_ai.output.messages"

// Bifrost Context Attributes
// legacy: every key below sits under gen_ai.* but represents a Bifrost-internal
// concept (governance / routing). The bifrost.* mirrors are the canonical home
// going forward; these will be dropped once dashboards migrate.
AttrRequestID = "gen_ai.request_id"
AttrVirtualKeyID = "gen_ai.virtual_key_id"
AttrVirtualKeyName = "gen_ai.virtual_key_name"
AttrSelectedKeyID = "gen_ai.selected_key_id"
AttrSelectedKeyName = "gen_ai.selected_key_name"
AttrRoutingRuleID = "gen_ai.routing_rule_id"
AttrRoutingRuleName = "gen_ai.routing_rule_name"
AttrTeamID = "gen_ai.team_id"
AttrTeamName = "gen_ai.team_name"
AttrCustomerID = "gen_ai.customer_id"
AttrCustomerName = "gen_ai.customer_name"
AttrNumberOfRetries = "gen_ai.number_of_retries"
AttrFallbackIndex = "gen_ai.fallback_index"

// Extra Header Attributes
AttrExtraHeaderPrefix = "gen_ai.request.extra_header."

Expand Down Expand Up @@ -936,9 +903,6 @@ const (
// - Bifrost-internal concepts (routing/governance, request id, retry counters)
// - Raw Bifrost short names that mirror canonicalized gen_ai.* values
// - Back-compat fallbacks for shape changes (e.g. comma-joined stop_sequences)
//
// The corresponding legacy gen_ai.* emissions are tagged "// legacy:" at their
// call sites and will be removed once dashboards migrate over.
// =====================================================================
// Cumulative time (float64 ms) the request spent blocked on sockets outside
// Bifrost — every provider attempt, plus MCP tool calls and media fetches.
Expand Down Expand Up @@ -983,15 +947,14 @@ const (
AttrBifrostRoutingEngineUsed = "bifrost.routing_engine_used" // comma-joined routing engines that handled the request
AttrBifrostStopSequencesJoined = "bifrost.request.stop_sequences"

// OTel general semconv (no gen_ai prefix). Emitted alongside the legacy
// gen_ai.error.type from PopulateErrorAttributes.
// OTel general semconv (no gen_ai prefix). The canonical error-type key,
// emitted from PopulateErrorAttributes.
AttrErrorTypeSpec = "error.type"

// legacy: bare unprefixed keys retained for back-compat with existing dashboards.
// "request.type" is superseded by AttrOperationName; "retry.count" has no spec
// equivalent but stays under bifrost.retries going forward.
// legacy: bare unprefixed key retained for back-compat with existing dashboards.
// "request.type" is superseded by AttrOperationName, but still drives the live
// "method" metric label and request_type column via EnrichmentDims.
AttrLegacyRequestType = "request.type"
AttrLegacyRetryCount = "retry.count"

// File Operation Attributes
AttrFileID = "gen_ai.file.id"
Expand Down
Loading
Loading