391 lines
16 KiB
Go
391 lines
16 KiB
Go
// 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"
|
|
)
|