first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

390
core/schemas/trace.go Normal file
View 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"
)