first commit
This commit is contained in:
390
core/schemas/trace.go
Normal file
390
core/schemas/trace.go
Normal file
@@ -0,0 +1,390 @@
|
||||
// Package schemas defines the core schemas and types used by the Bifrost system.
|
||||
package schemas
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Trace represents a distributed trace that captures the full lifecycle of a request
|
||||
type Trace struct {
|
||||
RequestID string // Request ID for the trace
|
||||
TraceID string // Unique identifier for this trace
|
||||
ParentID string // Parent trace ID from incoming W3C traceparent header
|
||||
RootSpan *Span // The root span of this trace
|
||||
Spans []*Span // All spans in this trace
|
||||
StartTime time.Time // When the trace started
|
||||
EndTime time.Time // When the trace completed
|
||||
Attributes map[string]any // Additional attributes for the trace
|
||||
PluginLogs []PluginLogEntry // Plugin log entries accumulated during request processing
|
||||
mu sync.Mutex // Mutex for thread-safe span operations
|
||||
}
|
||||
|
||||
// AddSpan adds a span to the trace in a thread-safe manner
|
||||
func (t *Trace) AddSpan(span *Span) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.Spans = append(t.Spans, span)
|
||||
}
|
||||
|
||||
// GetSpan retrieves a span by ID
|
||||
func (t *Trace) GetSpan(spanID string) *Span {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
for _, span := range t.Spans {
|
||||
if span.SpanID == spanID {
|
||||
return span
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequestID retrieves the request ID from the trace
|
||||
func (t *Trace) GetRequestID() string {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.RequestID
|
||||
}
|
||||
|
||||
// SetRequestID sets the request ID for the trace
|
||||
func (t *Trace) SetRequestID(requestID string) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.RequestID = requestID
|
||||
}
|
||||
|
||||
// Reset clears the trace for reuse from pool
|
||||
func (t *Trace) Reset() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.RequestID = ""
|
||||
t.TraceID = ""
|
||||
t.ParentID = ""
|
||||
t.RootSpan = nil
|
||||
for i := range t.Spans {
|
||||
t.Spans[i] = nil
|
||||
}
|
||||
t.Spans = t.Spans[:0]
|
||||
t.StartTime = time.Time{}
|
||||
t.EndTime = time.Time{}
|
||||
t.Attributes = nil
|
||||
for i := range t.PluginLogs {
|
||||
t.PluginLogs[i] = PluginLogEntry{}
|
||||
}
|
||||
t.PluginLogs = t.PluginLogs[:0]
|
||||
}
|
||||
|
||||
// AppendPluginLogs appends plugin log entries to the trace in a thread-safe manner.
|
||||
func (t *Trace) AppendPluginLogs(logs []PluginLogEntry) {
|
||||
if len(logs) == 0 {
|
||||
return
|
||||
}
|
||||
t.mu.Lock()
|
||||
t.PluginLogs = append(t.PluginLogs, logs...)
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
// Span represents a single operation within a trace
|
||||
type Span struct {
|
||||
SpanID string // Unique identifier for this span
|
||||
ParentID string // Parent span ID (empty for root span)
|
||||
TraceID string // The trace this span belongs to
|
||||
Name string // Name of the operation
|
||||
Kind SpanKind // Type of span (LLM call, plugin, etc.)
|
||||
StartTime time.Time // When the span started
|
||||
EndTime time.Time // When the span completed
|
||||
Status SpanStatus // Status of the operation
|
||||
StatusMsg string // Optional status message (for errors)
|
||||
Attributes map[string]any // Additional attributes for the span
|
||||
Events []SpanEvent // Events that occurred during the span
|
||||
mu sync.Mutex // Mutex for thread-safe attribute operations
|
||||
}
|
||||
|
||||
// SetAttribute sets an attribute on the span in a thread-safe manner
|
||||
func (s *Span) SetAttribute(key string, value any) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.Attributes == nil {
|
||||
s.Attributes = make(map[string]any)
|
||||
}
|
||||
s.Attributes[key] = value
|
||||
}
|
||||
|
||||
// AddEvent adds an event to the span in a thread-safe manner
|
||||
func (s *Span) AddEvent(event SpanEvent) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.Events = append(s.Events, event)
|
||||
}
|
||||
|
||||
// End marks the span as complete with the given status
|
||||
func (s *Span) End(status SpanStatus, statusMsg string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.EndTime = time.Now()
|
||||
s.Status = status
|
||||
s.StatusMsg = statusMsg
|
||||
}
|
||||
|
||||
// Reset clears the span for reuse from pool
|
||||
func (s *Span) Reset() {
|
||||
s.SpanID = ""
|
||||
s.ParentID = ""
|
||||
s.TraceID = ""
|
||||
s.Name = ""
|
||||
s.Kind = SpanKindUnspecified
|
||||
s.StartTime = time.Time{}
|
||||
s.EndTime = time.Time{}
|
||||
s.Status = SpanStatusUnset
|
||||
s.StatusMsg = ""
|
||||
s.Attributes = nil
|
||||
s.Events = s.Events[:0]
|
||||
}
|
||||
|
||||
// SpanEvent represents a time-stamped event within a span
|
||||
type SpanEvent struct {
|
||||
Name string // Name of the event
|
||||
Timestamp time.Time // When the event occurred
|
||||
Attributes map[string]any // Additional attributes for the event
|
||||
}
|
||||
|
||||
// SpanKind represents the type of operation a span represents
|
||||
// These are LLM-specific kinds designed for AI gateway observability
|
||||
type SpanKind string
|
||||
|
||||
const (
|
||||
// SpanKindUnspecified is the default span kind
|
||||
SpanKindUnspecified SpanKind = ""
|
||||
// SpanKindLLMCall represents a call to an LLM provider
|
||||
SpanKindLLMCall SpanKind = "llm.call"
|
||||
// SpanKindPlugin represents plugin execution (PreLLMHook/PostLLMHook)
|
||||
SpanKindPlugin SpanKind = "plugin"
|
||||
// SpanKindMCPTool represents an MCP tool invocation
|
||||
SpanKindMCPTool SpanKind = "mcp.tool"
|
||||
// SpanKindRetry represents a retry attempt
|
||||
SpanKindRetry SpanKind = "retry"
|
||||
// SpanKindFallback represents a fallback to another provider
|
||||
SpanKindFallback SpanKind = "fallback"
|
||||
// SpanKindHTTPRequest represents the root HTTP request span
|
||||
SpanKindHTTPRequest SpanKind = "http.request"
|
||||
// SpanKindEmbedding represents an embedding request
|
||||
SpanKindEmbedding SpanKind = "embedding"
|
||||
// SpanKindSpeech represents a text-to-speech request
|
||||
SpanKindSpeech SpanKind = "speech"
|
||||
// SpanKindTranscription represents a speech-to-text request
|
||||
SpanKindTranscription SpanKind = "transcription"
|
||||
// SpanKindInternal represents internal operations (key selection, etc.)
|
||||
SpanKindInternal SpanKind = "internal"
|
||||
)
|
||||
|
||||
// SpanStatus represents the status of a span's operation
|
||||
type SpanStatus string
|
||||
|
||||
const (
|
||||
// SpanStatusUnset indicates status has not been set
|
||||
SpanStatusUnset SpanStatus = "unset"
|
||||
// SpanStatusOk indicates the operation completed successfully
|
||||
SpanStatusOk SpanStatus = "ok"
|
||||
// SpanStatusError indicates the operation failed
|
||||
SpanStatusError SpanStatus = "error"
|
||||
)
|
||||
|
||||
// LLM Attribute Keys (gen_ai.* namespace)
|
||||
// These follow the OpenTelemetry semantic conventions for GenAI
|
||||
// and are compatible with both OTEL and Datadog backends.
|
||||
const (
|
||||
// Provider and Model Attributes
|
||||
AttrProviderName = "gen_ai.provider.name"
|
||||
AttrRequestModel = "gen_ai.request.model"
|
||||
|
||||
// Request Parameter Attributes
|
||||
AttrMaxTokens = "gen_ai.request.max_tokens"
|
||||
AttrTemperature = "gen_ai.request.temperature"
|
||||
AttrTopP = "gen_ai.request.top_p"
|
||||
AttrStopSequences = "gen_ai.request.stop_sequences"
|
||||
AttrPresencePenalty = "gen_ai.request.presence_penalty"
|
||||
AttrFrequencyPenalty = "gen_ai.request.frequency_penalty"
|
||||
AttrParallelToolCall = "gen_ai.request.parallel_tool_calls"
|
||||
AttrRequestUser = "gen_ai.request.user"
|
||||
AttrBestOf = "gen_ai.request.best_of"
|
||||
AttrEcho = "gen_ai.request.echo"
|
||||
AttrLogitBias = "gen_ai.request.logit_bias"
|
||||
AttrLogProbs = "gen_ai.request.logprobs"
|
||||
AttrN = "gen_ai.request.n"
|
||||
AttrSeed = "gen_ai.request.seed"
|
||||
AttrSuffix = "gen_ai.request.suffix"
|
||||
AttrDimensions = "gen_ai.request.dimensions"
|
||||
AttrEncodingFormat = "gen_ai.request.encoding_format"
|
||||
AttrLanguage = "gen_ai.request.language"
|
||||
AttrPrompt = "gen_ai.request.prompt"
|
||||
AttrResponseFormat = "gen_ai.request.response_format"
|
||||
AttrFormat = "gen_ai.request.format"
|
||||
AttrVoice = "gen_ai.request.voice"
|
||||
AttrMultiVoiceConfig = "gen_ai.request.multi_voice_config"
|
||||
AttrInstructions = "gen_ai.request.instructions"
|
||||
AttrSpeed = "gen_ai.request.speed"
|
||||
AttrMessageCount = "gen_ai.request.message_count"
|
||||
|
||||
// Response Attributes
|
||||
AttrResponseID = "gen_ai.response.id"
|
||||
AttrResponseModel = "gen_ai.response.model"
|
||||
AttrFinishReason = "gen_ai.response.finish_reason"
|
||||
AttrSystemFprint = "gen_ai.response.system_fingerprint"
|
||||
AttrServiceTier = "gen_ai.response.service_tier"
|
||||
AttrCreated = "gen_ai.response.created"
|
||||
AttrObject = "gen_ai.response.object"
|
||||
AttrTimeToFirstToken = "gen_ai.response.time_to_first_token"
|
||||
AttrTotalChunks = "gen_ai.response.total_chunks"
|
||||
|
||||
// Plugin Attributes (for aggregated streaming post-hook spans)
|
||||
AttrPluginInvocations = "plugin.invocation_count"
|
||||
AttrPluginAvgDurationMs = "plugin.avg_duration_ms"
|
||||
AttrPluginTotalDurationMs = "plugin.total_duration_ms"
|
||||
AttrPluginErrorCount = "plugin.error_count"
|
||||
|
||||
// Usage Attributes
|
||||
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"
|
||||
// Chat completion usage detail attributes
|
||||
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"
|
||||
AttrPromptTokenDetailsCachedWrite = "gen_ai.usage.prompt_token_details.cached_write_tokens"
|
||||
AttrCompletionTokenDetailsText = "gen_ai.usage.completion_token_details.text_tokens"
|
||||
AttrCompletionTokenDetailsAudio = "gen_ai.usage.completion_token_details.audio_tokens"
|
||||
AttrCompletionTokenDetailsImage = "gen_ai.usage.completion_token_details.image_tokens"
|
||||
AttrCompletionTokenDetailsReason = "gen_ai.usage.completion_token_details.reasoning_tokens"
|
||||
AttrCompletionTokenDetailsAccept = "gen_ai.usage.completion_token_details.accepted_prediction_tokens"
|
||||
AttrCompletionTokenDetailsReject = "gen_ai.usage.completion_token_details.rejected_prediction_tokens"
|
||||
AttrCompletionTokenDetailsCite = "gen_ai.usage.completion_token_details.citation_tokens"
|
||||
AttrCompletionTokenDetailsSearch = "gen_ai.usage.completion_token_details.num_search_queries"
|
||||
|
||||
// Error Attributes
|
||||
AttrError = "gen_ai.error"
|
||||
AttrErrorType = "gen_ai.error.type"
|
||||
AttrErrorCode = "gen_ai.error.code"
|
||||
|
||||
// Input/Output Attributes
|
||||
AttrInputText = "gen_ai.input.text"
|
||||
AttrInputMessages = "gen_ai.input.messages"
|
||||
AttrInputSpeech = "gen_ai.input.speech"
|
||||
AttrInputEmbedding = "gen_ai.input.embedding"
|
||||
AttrOutputMessages = "gen_ai.output.messages"
|
||||
|
||||
// Bifrost Context Attributes
|
||||
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"
|
||||
|
||||
// Responses API Request Attributes
|
||||
AttrPromptCacheKey = "gen_ai.request.prompt_cache_key"
|
||||
AttrReasoningEffort = "gen_ai.request.reasoning_effort"
|
||||
AttrReasoningSummary = "gen_ai.request.reasoning_summary"
|
||||
AttrReasoningGenSummary = "gen_ai.request.reasoning_generate_summary"
|
||||
AttrSafetyIdentifier = "gen_ai.request.safety_identifier"
|
||||
AttrStore = "gen_ai.request.store"
|
||||
AttrTextVerbosity = "gen_ai.request.text_verbosity"
|
||||
AttrTextFormatType = "gen_ai.request.text_format_type"
|
||||
AttrTopLogProbs = "gen_ai.request.top_logprobs"
|
||||
AttrToolChoiceType = "gen_ai.request.tool_choice_type"
|
||||
AttrToolChoiceName = "gen_ai.request.tool_choice_name"
|
||||
AttrTools = "gen_ai.request.tools"
|
||||
AttrTruncation = "gen_ai.request.truncation"
|
||||
|
||||
// Responses API Response Attributes
|
||||
AttrRespInclude = "gen_ai.responses.include"
|
||||
AttrRespMaxOutputTokens = "gen_ai.responses.max_output_tokens"
|
||||
AttrRespMaxToolCalls = "gen_ai.responses.max_tool_calls"
|
||||
AttrRespMetadata = "gen_ai.responses.metadata"
|
||||
AttrRespPreviousRespID = "gen_ai.responses.previous_response_id"
|
||||
AttrRespPromptCacheKey = "gen_ai.responses.prompt_cache_key"
|
||||
AttrRespReasoningText = "gen_ai.responses.reasoning"
|
||||
AttrRespReasoningEffort = "gen_ai.responses.reasoning_effort"
|
||||
AttrRespReasoningGenSum = "gen_ai.responses.reasoning_generate_summary"
|
||||
AttrRespSafetyIdentifier = "gen_ai.responses.safety_identifier"
|
||||
AttrRespStore = "gen_ai.responses.store"
|
||||
AttrRespTemperature = "gen_ai.responses.temperature"
|
||||
AttrRespTextVerbosity = "gen_ai.responses.text_verbosity"
|
||||
AttrRespTextFormatType = "gen_ai.responses.text_format_type"
|
||||
AttrRespTopLogProbs = "gen_ai.responses.top_logprobs"
|
||||
AttrRespTopP = "gen_ai.responses.top_p"
|
||||
AttrRespToolChoiceType = "gen_ai.responses.tool_choice_type"
|
||||
AttrRespToolChoiceName = "gen_ai.responses.tool_choice_name"
|
||||
AttrRespTruncation = "gen_ai.responses.truncation"
|
||||
AttrRespTools = "gen_ai.responses.tools"
|
||||
|
||||
// Batch Operation Attributes
|
||||
AttrBatchID = "gen_ai.batch.id"
|
||||
AttrBatchStatus = "gen_ai.batch.status"
|
||||
AttrBatchObject = "gen_ai.batch.object"
|
||||
AttrBatchEndpoint = "gen_ai.batch.endpoint"
|
||||
AttrBatchInputFileID = "gen_ai.batch.input_file_id"
|
||||
AttrBatchOutputFileID = "gen_ai.batch.output_file_id"
|
||||
AttrBatchErrorFileID = "gen_ai.batch.error_file_id"
|
||||
AttrBatchCompletionWin = "gen_ai.batch.completion_window"
|
||||
AttrBatchCreatedAt = "gen_ai.batch.created_at"
|
||||
AttrBatchExpiresAt = "gen_ai.batch.expires_at"
|
||||
AttrBatchRequestsCount = "gen_ai.batch.requests_count"
|
||||
AttrBatchDataCount = "gen_ai.batch.data_count"
|
||||
AttrBatchResultsCount = "gen_ai.batch.results_count"
|
||||
AttrBatchHasMore = "gen_ai.batch.has_more"
|
||||
AttrBatchMetadata = "gen_ai.batch.metadata"
|
||||
AttrBatchLimit = "gen_ai.batch.limit"
|
||||
AttrBatchAfter = "gen_ai.batch.after"
|
||||
AttrBatchBeforeID = "gen_ai.batch.before_id"
|
||||
AttrBatchAfterID = "gen_ai.batch.after_id"
|
||||
AttrBatchPageToken = "gen_ai.batch.page_token"
|
||||
AttrBatchPageSize = "gen_ai.batch.page_size"
|
||||
AttrBatchCountTotal = "gen_ai.batch.request_counts.total"
|
||||
AttrBatchCountCompleted = "gen_ai.batch.request_counts.completed"
|
||||
AttrBatchCountFailed = "gen_ai.batch.request_counts.failed"
|
||||
AttrBatchFirstID = "gen_ai.batch.first_id"
|
||||
AttrBatchLastID = "gen_ai.batch.last_id"
|
||||
AttrBatchInProgressAt = "gen_ai.batch.in_progress_at"
|
||||
AttrBatchFinalizingAt = "gen_ai.batch.finalizing_at"
|
||||
AttrBatchCompletedAt = "gen_ai.batch.completed_at"
|
||||
AttrBatchFailedAt = "gen_ai.batch.failed_at"
|
||||
AttrBatchExpiredAt = "gen_ai.batch.expired_at"
|
||||
AttrBatchCancellingAt = "gen_ai.batch.cancelling_at"
|
||||
AttrBatchCancelledAt = "gen_ai.batch.cancelled_at"
|
||||
AttrBatchNextCursor = "gen_ai.batch.next_cursor"
|
||||
|
||||
// Transcription Response Attributes
|
||||
AttrInputTokenDetailsText = "gen_ai.usage.input_token_details.text_tokens"
|
||||
AttrInputTokenDetailsAudio = "gen_ai.usage.input_token_details.audio_tokens"
|
||||
|
||||
// File Operation Attributes
|
||||
AttrFileID = "gen_ai.file.id"
|
||||
AttrFileObject = "gen_ai.file.object"
|
||||
AttrFileFilename = "gen_ai.file.filename"
|
||||
AttrFilePurpose = "gen_ai.file.purpose"
|
||||
AttrFileBytes = "gen_ai.file.bytes"
|
||||
AttrFileCreatedAt = "gen_ai.file.created_at"
|
||||
AttrFileStatus = "gen_ai.file.status"
|
||||
AttrFileStorageBackend = "gen_ai.file.storage_backend"
|
||||
AttrFileDataCount = "gen_ai.file.data_count"
|
||||
AttrFileHasMore = "gen_ai.file.has_more"
|
||||
AttrFileDeleted = "gen_ai.file.deleted"
|
||||
AttrFileContentType = "gen_ai.file.content_type"
|
||||
AttrFileContentBytes = "gen_ai.file.content_bytes"
|
||||
AttrFileLimit = "gen_ai.file.limit"
|
||||
AttrFileAfter = "gen_ai.file.after"
|
||||
AttrFileOrder = "gen_ai.file.order"
|
||||
)
|
||||
Reference in New Issue
Block a user