package logstore import ( "strings" "time" "github.com/bytedance/sonic" "github.com/maximhq/bifrost/core/schemas" "github.com/maximhq/bifrost/framework/configstore/tables" "gorm.io/gorm" ) type SortBy string const ( SortByTimestamp SortBy = "timestamp" SortByLatency SortBy = "latency" SortByTokens SortBy = "tokens" SortByCost SortBy = "cost" ) type SortOrder string const ( SortAsc SortOrder = "asc" SortDesc SortOrder = "desc" ) // SearchFilters represents the available filters for log searches type SearchFilters struct { Providers []string `json:"providers,omitempty"` Models []string `json:"models,omitempty"` Aliases []string `json:"aliases,omitempty"` Status []string `json:"status,omitempty"` Objects []string `json:"objects,omitempty"` // For filtering by request type (chat.completion, text.completion, embedding) ParentRequestID string `json:"parent_request_id,omitempty"` SelectedKeyIDs []string `json:"selected_key_ids,omitempty"` VirtualKeyIDs []string `json:"virtual_key_ids,omitempty"` RoutingRuleIDs []string `json:"routing_rule_ids,omitempty"` TeamIDs []string `json:"team_ids,omitempty"` CustomerIDs []string `json:"customer_ids,omitempty"` UserIDs []string `json:"user_ids,omitempty"` BusinessUnitIDs []string `json:"business_unit_ids,omitempty"` RoutingEngineUsed []string `json:"routing_engine_used,omitempty"` // For filtering by routing engine (routing-rule, governance, loadbalancing) StartTime *time.Time `json:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty"` MinLatency *float64 `json:"min_latency,omitempty"` MaxLatency *float64 `json:"max_latency,omitempty"` MinTokens *int `json:"min_tokens,omitempty"` MaxTokens *int `json:"max_tokens,omitempty"` MinCost *float64 `json:"min_cost,omitempty"` MaxCost *float64 `json:"max_cost,omitempty"` MissingCostOnly bool `json:"missing_cost_only,omitempty"` ContentSearch string `json:"content_search,omitempty"` MetadataFilters map[string]string `json:"metadata_filters,omitempty"` // key=metadataKey, value=metadataValue for filtering by metadata } // PaginationOptions represents pagination parameters type PaginationOptions struct { Limit int `json:"limit"` Offset int `json:"offset"` SortBy string `json:"sort_by"` // "timestamp", "latency", "tokens", "cost" Order string `json:"order"` // "asc", "desc" TotalCount int64 `json:"total_count"` // Total number of items matching the query } // SearchResult represents the result of a log search type SearchResult struct { Logs []Log `json:"logs"` Pagination PaginationOptions `json:"pagination"` Stats SearchStats `json:"stats"` HasLogs bool `json:"has_logs"` } type SessionDetailResult struct { SessionID string `json:"session_id"` Logs []Log `json:"logs"` Pagination PaginationOptions `json:"pagination"` Count int64 `json:"count"` ReturnedCount int `json:"returned_count"` HasMore bool `json:"has_more"` } type SessionSummaryResult struct { SessionID string `json:"session_id"` Count int64 `json:"count"` TotalCost float64 `json:"total_cost"` TotalTokens int64 `json:"total_tokens"` StartedAt string `json:"started_at,omitempty"` LatestAt string `json:"latest_at,omitempty"` DurationMs int64 `json:"duration_ms"` } type SearchStats struct { TotalRequests int64 `json:"total_requests"` SuccessRate float64 `json:"success_rate"` // Percentage of individual attempts that succeeded UserFacingSuccessRate float64 `json:"user_facing_success_rate"` // Percentage of user requests that ultimately succeeded (fallback chains counted as one request) UserFacingTotalRequests int64 `json:"user_facing_total_requests"` // Count of root requests (fallback_index = 0) used as denominator for UserFacingSuccessRate AverageLatency float64 `json:"average_latency"` // Average latency in milliseconds TotalTokens int64 `json:"total_tokens"` // Total tokens used TotalCost float64 `json:"total_cost"` // Total cost in dollars } // Log represents a complete log entry for a request/response cycle // This is the GORM model with appropriate tags type Log struct { ID string `gorm:"primaryKey;type:varchar(255)" json:"id"` ParentRequestID *string `gorm:"type:varchar(255);index" json:"parent_request_id"` Timestamp time.Time `gorm:"index;index:idx_logs_ts_provider_status,priority:1;not null" json:"timestamp"` Object string `gorm:"type:varchar(255);index;not null;column:object_type" json:"object"` // text.completion, chat.completion, or embedding Provider string `gorm:"type:varchar(255);index;index:idx_logs_ts_provider_status,priority:2;not null" json:"provider"` Model string `gorm:"type:varchar(255);index;not null" json:"model"` Alias *string `gorm:"type:varchar(255);index" json:"alias,omitempty"` // Set when model was resolved via alias mapping; the original name the caller used NumberOfRetries int `gorm:"default:0" json:"number_of_retries"` FallbackIndex int `gorm:"default:0" json:"fallback_index"` SelectedKeyID string `gorm:"type:varchar(255);index:idx_logs_selected_key_id" json:"selected_key_id"` SelectedKeyName string `gorm:"type:varchar(255)" json:"selected_key_name"` AttemptTrail string `gorm:"type:text" json:"-"` // JSON serialized []schemas.KeyAttemptRecord VirtualKeyID *string `gorm:"type:varchar(255);index:idx_logs_virtual_key_id" json:"virtual_key_id"` VirtualKeyName *string `gorm:"type:varchar(255)" json:"virtual_key_name"` RoutingEnginesUsedStr *string `gorm:"type:varchar(255);column:routing_engines_used" json:"-"` // Comma-separated routing engines RoutingRuleID *string `gorm:"type:varchar(255);index:idx_logs_routing_rule_id" json:"routing_rule_id"` RoutingRuleName *string `gorm:"type:varchar(255)" json:"routing_rule_name"` SelectedPromptName *string `gorm:"type:varchar(255)" json:"selected_prompt_name"` SelectedPromptVersion *string `gorm:"type:varchar(64)" json:"selected_prompt_version"` SelectedPromptID *string `gorm:"type:varchar(36)" json:"selected_prompt_id"` UserID *string `gorm:"type:varchar(255);index:idx_logs_user_id" json:"user_id"` UserName *string `gorm:"type:varchar(255)" json:"user_name"` TeamID *string `gorm:"type:varchar(255);index:idx_logs_team_id" json:"team_id"` TeamName *string `gorm:"type:varchar(255)" json:"team_name"` CustomerID *string `gorm:"type:varchar(255);index:idx_logs_customer_id" json:"customer_id"` CustomerName *string `gorm:"type:varchar(255)" json:"customer_name"` BusinessUnitID *string `gorm:"type:varchar(255);index:idx_logs_business_unit_id" json:"business_unit_id"` BusinessUnitName *string `gorm:"type:varchar(255)" json:"business_unit_name"` InputHistory string `gorm:"type:text" json:"-"` // JSON serialized []schemas.ChatMessage ResponsesInputHistory string `gorm:"type:text" json:"-"` // JSON serialized []schemas.ResponsesMessage OutputMessage string `gorm:"type:text" json:"-"` // JSON serialized *schemas.ChatMessage ResponsesOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.ResponsesMessage EmbeddingOutput string `gorm:"type:text" json:"-"` // JSON serialized [][]float32 RerankOutput string `gorm:"type:text" json:"-"` // JSON serialized []schemas.RerankResult OCROutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostOCRResponse Params string `gorm:"type:text" json:"-"` // JSON serialized *schemas.ModelParameters Tools string `gorm:"type:text" json:"-"` // JSON serialized []schemas.Tool ToolCalls string `gorm:"type:text" json:"-"` // JSON serialized []schemas.ToolCall (For backward compatibility, tool calls are now in the content) SpeechInput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.SpeechInput TranscriptionInput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.TranscriptionInput OCRInput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.OCRDocument ImageGenerationInput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.ImageGenerationInput ImageEditInput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.ImageEditInput ImageVariationInput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.ImageVariationInput VideoGenerationInput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.VideoGenerationInput SpeechOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostSpeech TranscriptionOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostTranscribe ImageGenerationOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostImageGenerationResponse ListModelsOutput string `gorm:"type:text" json:"-"` // JSON serialized []schemas.Model VideoGenerationOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostVideoGenerationResponse VideoRetrieveOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostVideoRetrieveResponse VideoDownloadOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostVideoDownloadResponse VideoListOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostVideoListResponse VideoDeleteOutput string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostVideoDeleteResponse CacheDebug string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostCacheDebug Latency *float64 `gorm:"index:idx_logs_latency" json:"latency,omitempty"` TokenUsage string `gorm:"type:text" json:"-"` // JSON serialized *schemas.LLMUsage Cost *float64 `gorm:"index" json:"cost,omitempty"` // Cost in dollars (total cost of the request - includes cache lookup cost) Status string `gorm:"type:varchar(50);index;index:idx_logs_ts_provider_status,priority:3;not null" json:"status"` // "processing", "success", or "error" ErrorDetails string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostError Stream bool `gorm:"default:false" json:"stream"` // true if this was a streaming response ContentSummary string `gorm:"type:text" json:"-"` RawRequest string `gorm:"type:text" json:"raw_request"` // Populated when `send-back-raw-request` is on RawResponse string `gorm:"type:text" json:"raw_response"` // Populated when `send-back-raw-response` is on PassthroughRequestBody string `gorm:"type:text" json:"passthrough_request_body,omitempty"` // Raw body for passthrough requests (UTF-8) PassthroughResponseBody string `gorm:"type:text" json:"passthrough_response_body,omitempty"` // Raw body for passthrough responses (UTF-8) RoutingEngineLogs string `gorm:"type:text" json:"routing_engine_logs,omitempty"` // Formatted routing engine decision logs PluginLogs string `gorm:"type:text" json:"plugin_logs,omitempty"` // JSON serialized plugin log entries grouped by plugin name Metadata *string `gorm:"type:text" json:"-"` // JSON serialized map[string]interface{} IsLargePayloadRequest bool `gorm:"default:false" json:"is_large_payload_request"` IsLargePayloadResponse bool `gorm:"default:false" json:"is_large_payload_response"` HasObject bool `gorm:"default:false" json:"-"` // True when payload is stored in object storage // Denormalized token fields for easier querying PromptTokens int `gorm:"default:0" json:"-"` CompletionTokens int `gorm:"default:0" json:"-"` TotalTokens int `gorm:"index:idx_logs_total_tokens;default:0" json:"-"` CachedReadTokens int `gorm:"default:0" json:"-"` CreatedAt time.Time `gorm:"index;not null" json:"created_at"` // Virtual fields for JSON output - these will be populated when needed RoutingEnginesUsed []string `gorm:"-" json:"routing_engines_used,omitempty"` // Virtual field deserialized from JSON InputHistoryParsed []schemas.ChatMessage `gorm:"-" json:"input_history,omitempty"` ResponsesInputHistoryParsed []schemas.ResponsesMessage `gorm:"-" json:"responses_input_history,omitempty"` OutputMessageParsed *schemas.ChatMessage `gorm:"-" json:"output_message,omitempty"` ResponsesOutputParsed []schemas.ResponsesMessage `gorm:"-" json:"responses_output,omitempty"` EmbeddingOutputParsed []schemas.EmbeddingData `gorm:"-" json:"embedding_output,omitempty"` RerankOutputParsed []schemas.RerankResult `gorm:"-" json:"rerank_output,omitempty"` OCROutputParsed *schemas.BifrostOCRResponse `gorm:"-" json:"ocr_output,omitempty"` ParamsParsed interface{} `gorm:"-" json:"params,omitempty"` ToolsParsed []schemas.ChatTool `gorm:"-" json:"tools,omitempty"` ToolCallsParsed []schemas.ChatAssistantMessageToolCall `gorm:"-" json:"tool_calls,omitempty"` // For backward compatibility, tool calls are now in the content TokenUsageParsed *schemas.BifrostLLMUsage `gorm:"-" json:"token_usage,omitempty"` ErrorDetailsParsed *schemas.BifrostError `gorm:"-" json:"error_details,omitempty"` SpeechInputParsed *schemas.SpeechInput `gorm:"-" json:"speech_input,omitempty"` TranscriptionInputParsed *schemas.TranscriptionInput `gorm:"-" json:"transcription_input,omitempty"` OCRInputParsed *schemas.OCRDocument `gorm:"-" json:"ocr_input,omitempty"` ImageGenerationInputParsed *schemas.ImageGenerationInput `gorm:"-" json:"image_generation_input,omitempty"` ImageEditInputParsed *schemas.ImageEditInput `gorm:"-" json:"image_edit_input,omitempty"` ImageVariationInputParsed *schemas.ImageVariationInput `gorm:"-" json:"image_variation_input,omitempty"` SpeechOutputParsed *schemas.BifrostSpeechResponse `gorm:"-" json:"speech_output,omitempty"` TranscriptionOutputParsed *schemas.BifrostTranscriptionResponse `gorm:"-" json:"transcription_output,omitempty"` ImageGenerationOutputParsed *schemas.BifrostImageGenerationResponse `gorm:"-" json:"image_generation_output,omitempty"` CacheDebugParsed *schemas.BifrostCacheDebug `gorm:"-" json:"cache_debug,omitempty"` ListModelsOutputParsed []schemas.Model `gorm:"-" json:"list_models_output,omitempty"` MetadataParsed map[string]interface{} `gorm:"-" json:"metadata,omitempty"` VideoGenerationInputParsed *schemas.VideoGenerationInput `gorm:"-" json:"video_generation_input,omitempty"` VideoGenerationOutputParsed *schemas.BifrostVideoGenerationResponse `gorm:"-" json:"video_generation_output,omitempty"` VideoRetrieveOutputParsed *schemas.BifrostVideoGenerationResponse `gorm:"-" json:"video_retrieve_output,omitempty"` VideoDownloadOutputParsed *schemas.BifrostVideoDownloadResponse `gorm:"-" json:"video_download_output,omitempty"` VideoListOutputParsed *schemas.BifrostVideoListResponse `gorm:"-" json:"video_list_output,omitempty"` VideoDeleteOutputParsed *schemas.BifrostVideoDeleteResponse `gorm:"-" json:"video_delete_output,omitempty"` AttemptTrailParsed []schemas.KeyAttemptRecord `gorm:"-" json:"attempt_trail,omitempty"` // Populated in handlers after find using the virtual key id and key id VirtualKey *tables.TableVirtualKey `gorm:"-" json:"virtual_key,omitempty"` // redacted SelectedKey *schemas.Key `gorm:"-" json:"selected_key,omitempty"` // redacted RoutingRule *tables.TableRoutingRule `gorm:"-" json:"routing_rule,omitempty"` // redacted } // NewLogEntryFromMap creates a new Log from a map[string]interface{} func NewLogEntryFromMap(entry map[string]interface{}) *Log { var log Log data, err := sonic.Marshal(entry) if err != nil { return nil } err = sonic.Unmarshal(data, &log) if err != nil { return nil } return &log } // TableName sets the table name for GORM func (Log) TableName() string { return "logs" } // BeforeCreate GORM hook to set created_at and serialize JSON fields func (l *Log) BeforeCreate(tx *gorm.DB) error { if l.CreatedAt.IsZero() { l.CreatedAt = time.Now().UTC() } return l.SerializeFields() } // AfterFind GORM hook to deserialize JSON fields func (l *Log) AfterFind(tx *gorm.DB) error { return l.DeserializeFields() } // SerializeFields converts Go structs to JSON strings for storage func (l *Log) SerializeFields() error { // Serialize routing engines to comma-separated string if len(l.RoutingEnginesUsed) > 0 { engineStr := strings.Join(l.RoutingEnginesUsed, ",") l.RoutingEnginesUsedStr = &engineStr } else { l.RoutingEnginesUsedStr = nil } if l.InputHistoryParsed != nil { if data, err := sonic.Marshal(l.InputHistoryParsed); err != nil { return err } else { l.InputHistory = string(data) } } if l.ResponsesInputHistoryParsed != nil { if data, err := sonic.Marshal(l.ResponsesInputHistoryParsed); err != nil { return err } else { l.ResponsesInputHistory = string(data) } } if l.OutputMessageParsed != nil { if data, err := sonic.Marshal(l.OutputMessageParsed); err != nil { return err } else { l.OutputMessage = string(data) } } if l.ResponsesOutputParsed != nil { if data, err := sonic.Marshal(l.ResponsesOutputParsed); err != nil { return err } else { l.ResponsesOutput = string(data) } } if l.EmbeddingOutputParsed != nil { if data, err := sonic.Marshal(l.EmbeddingOutputParsed); err != nil { return err } else { l.EmbeddingOutput = string(data) } } if l.RerankOutputParsed != nil { if data, err := sonic.Marshal(l.RerankOutputParsed); err != nil { return err } else { l.RerankOutput = string(data) } } if l.OCROutputParsed != nil { if data, err := sonic.Marshal(l.OCROutputParsed); err != nil { return err } else { l.OCROutput = string(data) } } if l.SpeechInputParsed != nil { if data, err := sonic.Marshal(l.SpeechInputParsed); err != nil { return err } else { l.SpeechInput = string(data) } } if l.TranscriptionInputParsed != nil { if data, err := sonic.Marshal(l.TranscriptionInputParsed); err != nil { return err } else { l.TranscriptionInput = string(data) } } if l.OCRInputParsed != nil { if data, err := sonic.Marshal(l.OCRInputParsed); err != nil { return err } else { l.OCRInput = string(data) } } if l.ImageGenerationInputParsed != nil { if data, err := sonic.Marshal(l.ImageGenerationInputParsed); err != nil { return err } else { l.ImageGenerationInput = string(data) } } if l.ImageEditInputParsed != nil { if data, err := sonic.Marshal(l.ImageEditInputParsed); err != nil { return err } else { l.ImageEditInput = string(data) } } if l.ImageVariationInputParsed != nil { if data, err := sonic.Marshal(l.ImageVariationInputParsed); err != nil { return err } else { l.ImageVariationInput = string(data) } } if l.VideoGenerationInputParsed != nil { if data, err := sonic.Marshal(l.VideoGenerationInputParsed); err != nil { return err } else { l.VideoGenerationInput = string(data) } } if l.SpeechOutputParsed != nil { if data, err := sonic.Marshal(l.SpeechOutputParsed); err != nil { return err } else { l.SpeechOutput = string(data) } } if l.TranscriptionOutputParsed != nil { if data, err := sonic.Marshal(l.TranscriptionOutputParsed); err != nil { return err } else { l.TranscriptionOutput = string(data) } } if l.ImageGenerationOutputParsed != nil { if data, err := sonic.Marshal(l.ImageGenerationOutputParsed); err != nil { return err } else { l.ImageGenerationOutput = string(data) } } if l.VideoGenerationOutputParsed != nil { if data, err := sonic.Marshal(l.VideoGenerationOutputParsed); err != nil { return err } else { l.VideoGenerationOutput = string(data) } } if l.VideoRetrieveOutputParsed != nil { if data, err := sonic.Marshal(l.VideoRetrieveOutputParsed); err != nil { return err } else { l.VideoRetrieveOutput = string(data) } } if l.VideoDownloadOutputParsed != nil { if data, err := sonic.Marshal(l.VideoDownloadOutputParsed); err != nil { return err } else { l.VideoDownloadOutput = string(data) } } if l.VideoListOutputParsed != nil { if data, err := sonic.Marshal(l.VideoListOutputParsed); err != nil { return err } else { l.VideoListOutput = string(data) } } if l.VideoDeleteOutputParsed != nil { if data, err := sonic.Marshal(l.VideoDeleteOutputParsed); err != nil { return err } else { l.VideoDeleteOutput = string(data) } } if l.ListModelsOutputParsed != nil { if data, err := sonic.Marshal(l.ListModelsOutputParsed); err != nil { return err } else { l.ListModelsOutput = string(data) } } if l.ParamsParsed != nil { if data, err := sonic.Marshal(l.ParamsParsed); err != nil { return err } else { l.Params = string(data) } } if l.ToolsParsed != nil { if data, err := sonic.Marshal(l.ToolsParsed); err != nil { return err } else { l.Tools = string(data) } } if l.ToolCallsParsed != nil { if data, err := sonic.Marshal(l.ToolCallsParsed); err != nil { return err } else { l.ToolCalls = string(data) } } if l.TokenUsageParsed != nil { if data, err := sonic.Marshal(l.TokenUsageParsed); err != nil { return err } else { l.TokenUsage = string(data) } // Update denormalized fields for easier querying l.PromptTokens = l.TokenUsageParsed.PromptTokens l.CompletionTokens = l.TokenUsageParsed.CompletionTokens l.TotalTokens = l.TokenUsageParsed.TotalTokens if l.TokenUsageParsed.PromptTokensDetails != nil { l.CachedReadTokens = l.TokenUsageParsed.PromptTokensDetails.CachedReadTokens } } if l.ErrorDetailsParsed != nil { if data, err := sonic.Marshal(l.ErrorDetailsParsed); err != nil { return err } else { l.ErrorDetails = string(data) } } if l.CacheDebugParsed != nil { if data, err := sonic.Marshal(l.CacheDebugParsed); err != nil { return err } else { l.CacheDebug = string(data) } } if len(l.AttemptTrailParsed) > 0 { if data, err := sonic.Marshal(l.AttemptTrailParsed); err != nil { return err } else { l.AttemptTrail = string(data) } } else { l.AttemptTrail = "" } if l.MetadataParsed != nil { data, err := sonic.Marshal(l.MetadataParsed) if err != nil { // Metadata is supplementary — null it out rather than aborting the log write. l.Metadata = nil l.MetadataParsed = nil } else { l.Metadata = new(string(data)) } } // Build content summary for search. // Skip if already set (e.g., by the hybrid log store which builds input-only summaries). if l.ContentSummary == "" { l.ContentSummary = l.BuildContentSummary() } return nil } // DeserializeFields converts JSON strings back to Go structs func (l *Log) DeserializeFields() error { if l.InputHistory != "" { if err := sonic.Unmarshal([]byte(l.InputHistory), &l.InputHistoryParsed); err != nil { // Log error but don't fail the operation - initialize as empty slice l.InputHistoryParsed = []schemas.ChatMessage{} } } if l.ResponsesInputHistory != "" { if err := sonic.Unmarshal([]byte(l.ResponsesInputHistory), &l.ResponsesInputHistoryParsed); err != nil { // Log error but don't fail the operation - initialize as empty slice l.ResponsesInputHistoryParsed = []schemas.ResponsesMessage{} } } if l.OutputMessage != "" { if err := sonic.Unmarshal([]byte(l.OutputMessage), &l.OutputMessageParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.OutputMessageParsed = nil } } if l.ResponsesOutput != "" { if err := sonic.Unmarshal([]byte(l.ResponsesOutput), &l.ResponsesOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ResponsesOutputParsed = []schemas.ResponsesMessage{} } } if l.EmbeddingOutput != "" { if err := sonic.Unmarshal([]byte(l.EmbeddingOutput), &l.EmbeddingOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.EmbeddingOutputParsed = nil } } if l.RerankOutput != "" { if err := sonic.Unmarshal([]byte(l.RerankOutput), &l.RerankOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.RerankOutputParsed = nil } } if l.OCROutput != "" { if err := sonic.Unmarshal([]byte(l.OCROutput), &l.OCROutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.OCROutputParsed = nil } } if l.Params != "" { if err := sonic.Unmarshal([]byte(l.Params), &l.ParamsParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ParamsParsed = nil } } if l.Tools != "" { if err := sonic.Unmarshal([]byte(l.Tools), &l.ToolsParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ToolsParsed = nil } } if l.ToolCalls != "" { if err := sonic.Unmarshal([]byte(l.ToolCalls), &l.ToolCallsParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ToolCallsParsed = nil } } if l.TokenUsage != "" { if err := sonic.Unmarshal([]byte(l.TokenUsage), &l.TokenUsageParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.TokenUsageParsed = nil } } if l.ErrorDetails != "" { if err := sonic.Unmarshal([]byte(l.ErrorDetails), &l.ErrorDetailsParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ErrorDetailsParsed = nil } } if l.VideoGenerationOutput != "" { if err := sonic.Unmarshal([]byte(l.VideoGenerationOutput), &l.VideoGenerationOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.VideoGenerationOutputParsed = nil } } if l.VideoRetrieveOutput != "" { if err := sonic.Unmarshal([]byte(l.VideoRetrieveOutput), &l.VideoRetrieveOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.VideoRetrieveOutputParsed = nil } } if l.VideoDownloadOutput != "" { if err := sonic.Unmarshal([]byte(l.VideoDownloadOutput), &l.VideoDownloadOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.VideoDownloadOutputParsed = nil } } if l.VideoListOutput != "" { if err := sonic.Unmarshal([]byte(l.VideoListOutput), &l.VideoListOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.VideoListOutputParsed = nil } } if l.VideoDeleteOutput != "" { if err := sonic.Unmarshal([]byte(l.VideoDeleteOutput), &l.VideoDeleteOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.VideoDeleteOutputParsed = nil } } if l.VideoGenerationInput != "" { if err := sonic.Unmarshal([]byte(l.VideoGenerationInput), &l.VideoGenerationInputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.VideoGenerationInputParsed = nil } } if l.ListModelsOutput != "" { if err := sonic.Unmarshal([]byte(l.ListModelsOutput), &l.ListModelsOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ListModelsOutputParsed = nil } } // Deserialize speech and transcription fields if l.SpeechInput != "" { if err := sonic.Unmarshal([]byte(l.SpeechInput), &l.SpeechInputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.SpeechInputParsed = nil } } if l.TranscriptionInput != "" { if err := sonic.Unmarshal([]byte(l.TranscriptionInput), &l.TranscriptionInputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.TranscriptionInputParsed = nil } } if l.OCRInput != "" { if err := sonic.Unmarshal([]byte(l.OCRInput), &l.OCRInputParsed); err != nil { l.OCRInputParsed = nil } } if l.ImageGenerationInput != "" { if err := sonic.Unmarshal([]byte(l.ImageGenerationInput), &l.ImageGenerationInputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ImageGenerationInputParsed = nil } } if l.ImageEditInput != "" { if err := sonic.Unmarshal([]byte(l.ImageEditInput), &l.ImageEditInputParsed); err != nil { l.ImageEditInputParsed = nil } } if l.ImageVariationInput != "" { if err := sonic.Unmarshal([]byte(l.ImageVariationInput), &l.ImageVariationInputParsed); err != nil { l.ImageVariationInputParsed = nil } } if l.SpeechOutput != "" { if err := sonic.Unmarshal([]byte(l.SpeechOutput), &l.SpeechOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.SpeechOutputParsed = nil } } if l.TranscriptionOutput != "" { if err := sonic.Unmarshal([]byte(l.TranscriptionOutput), &l.TranscriptionOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.TranscriptionOutputParsed = nil } } if l.ImageGenerationOutput != "" { if err := sonic.Unmarshal([]byte(l.ImageGenerationOutput), &l.ImageGenerationOutputParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.ImageGenerationOutputParsed = nil } } if l.CacheDebug != "" { if err := sonic.Unmarshal([]byte(l.CacheDebug), &l.CacheDebugParsed); err != nil { // Log error but don't fail the operation - initialize as nil l.CacheDebugParsed = nil } } if l.AttemptTrail != "" { if err := sonic.Unmarshal([]byte(l.AttemptTrail), &l.AttemptTrailParsed); err != nil { l.AttemptTrailParsed = nil } } if l.Metadata != nil && *l.Metadata != "" { if err := sonic.Unmarshal([]byte(*l.Metadata), &l.MetadataParsed); err != nil { l.MetadataParsed = nil } } if l.RoutingEnginesUsedStr != nil && *l.RoutingEnginesUsedStr != "" { // Parse comma-separated routing engines l.RoutingEnginesUsed = strings.Split(*l.RoutingEnginesUsedStr, ",") } else { l.RoutingEnginesUsed = []string{} } return nil } // MCPToolLog represents a log entry for MCP tool executions // This is separate from the main Log table since MCP tool calls have different fields type MCPToolLog struct { ID string `gorm:"primaryKey;type:varchar(255)" json:"id"` RequestID string `gorm:"type:varchar(255);column:request_id;index:idx_mcp_logs_request_id" json:"request_id,omitempty"` // The original request ID from context LLMRequestID *string `gorm:"type:varchar(255);column:llm_request_id;index:idx_mcp_logs_llm_request_id" json:"llm_request_id,omitempty"` // Links to the LLM request that triggered this tool call Timestamp time.Time `gorm:"index;not null" json:"timestamp"` ToolName string `gorm:"type:varchar(255);index:idx_mcp_logs_tool_name;not null" json:"tool_name"` ServerLabel string `gorm:"type:varchar(255);index:idx_mcp_logs_server_label" json:"server_label,omitempty"` // MCP server that provided the tool VirtualKeyID *string `gorm:"type:varchar(255);index:idx_mcp_logs_virtual_key_id" json:"virtual_key_id"` VirtualKeyName *string `gorm:"type:varchar(255)" json:"virtual_key_name"` Arguments string `gorm:"type:text" json:"-"` // JSON serialized tool arguments Result string `gorm:"type:text" json:"-"` // JSON serialized tool result ErrorDetails string `gorm:"type:text" json:"-"` // JSON serialized *schemas.BifrostError Latency *float64 `gorm:"index:idx_mcp_logs_latency" json:"latency,omitempty"` // Execution time in milliseconds Cost *float64 `gorm:"index:idx_mcp_logs_cost" json:"cost,omitempty"` // Cost in dollars (per execution cost) Status string `gorm:"type:varchar(50);index:idx_mcp_logs_status;not null" json:"status"` // "processing", "success", or "error" Metadata string `gorm:"type:text" json:"-"` // JSON serialized map[string]interface{} CreatedAt time.Time `gorm:"index;not null" json:"created_at"` // Virtual fields for JSON output - populated when needed ArgumentsParsed interface{} `gorm:"-" json:"arguments,omitempty"` ResultParsed interface{} `gorm:"-" json:"result,omitempty"` ErrorDetailsParsed *schemas.BifrostError `gorm:"-" json:"error_details,omitempty"` MetadataParsed map[string]interface{} `gorm:"-" json:"metadata,omitempty"` VirtualKey *tables.TableVirtualKey `gorm:"-" json:"virtual_key,omitempty"` } // TableName sets the table name for GORM func (MCPToolLog) TableName() string { return "mcp_tool_logs" } // BeforeCreate GORM hook to set created_at and serialize JSON fields func (l *MCPToolLog) BeforeCreate(tx *gorm.DB) error { if l.CreatedAt.IsZero() { l.CreatedAt = time.Now().UTC() } if l.Timestamp.IsZero() { l.Timestamp = time.Now().UTC() } return l.SerializeFields() } // AfterFind GORM hook to deserialize JSON fields func (l *MCPToolLog) AfterFind(tx *gorm.DB) error { return l.DeserializeFields() } // SerializeFields converts Go structs to JSON strings for storage func (l *MCPToolLog) SerializeFields() error { if l.ArgumentsParsed != nil { if data, err := sonic.Marshal(l.ArgumentsParsed); err != nil { return err } else { l.Arguments = string(data) } } if l.ResultParsed != nil { if data, err := sonic.Marshal(l.ResultParsed); err != nil { return err } else { l.Result = string(data) } } if l.ErrorDetailsParsed != nil { if data, err := sonic.Marshal(l.ErrorDetailsParsed); err != nil { return err } else { l.ErrorDetails = string(data) } } if l.MetadataParsed != nil { data, err := sonic.Marshal(l.MetadataParsed) if err != nil { // Metadata is supplementary — null it out rather than aborting the log write. l.Metadata = "" l.MetadataParsed = nil } else { l.Metadata = string(data) } } return nil } // DeserializeFields converts JSON strings back to Go structs func (l *MCPToolLog) DeserializeFields() error { if l.Arguments != "" { if err := sonic.Unmarshal([]byte(l.Arguments), &l.ArgumentsParsed); err != nil { l.ArgumentsParsed = nil } } if l.Result != "" { if err := sonic.Unmarshal([]byte(l.Result), &l.ResultParsed); err != nil { l.ResultParsed = nil } } if l.ErrorDetails != "" { if err := sonic.Unmarshal([]byte(l.ErrorDetails), &l.ErrorDetailsParsed); err != nil { l.ErrorDetailsParsed = nil } } if l.Metadata != "" { if err := sonic.Unmarshal([]byte(l.Metadata), &l.MetadataParsed); err != nil { l.MetadataParsed = nil } } return nil } // AsyncJob represents an asynchronous job record in the database. // Jobs are created when requests are submitted to async endpoints and // updated when the background operation completes or fails. type AsyncJob struct { ID string `gorm:"primaryKey;type:varchar(255)" json:"id"` Status schemas.AsyncJobStatus `gorm:"type:varchar(50);index:idx_async_jobs_status;not null" json:"status"` RequestType schemas.RequestType `gorm:"type:varchar(50);index:idx_async_jobs_request_type;not null" json:"request_type"` Response string `gorm:"type:text" json:"response"` StatusCode int `gorm:"default:0" json:"status_code,omitempty"` Error string `gorm:"type:text" json:"error,omitempty"` VirtualKeyID *string `gorm:"type:varchar(255);index:idx_async_jobs_vk_id" json:"virtual_key_id,omitempty"` ResultTTL int `gorm:"default:3600" json:"-"` // TTL in seconds, used to calculate ExpiresAt on completion ExpiresAt *time.Time `gorm:"index:idx_async_jobs_expires_at" json:"expires_at,omitempty"` CreatedAt time.Time `gorm:"index;not null" json:"created_at"` CompletedAt *time.Time `json:"completed_at,omitempty"` } // TableName sets the table name for GORM func (AsyncJob) TableName() string { return "async_jobs" } // ToResponse converts an AsyncJob database record to an AsyncJobResponse for JSON output. func (j *AsyncJob) ToResponse() *schemas.AsyncJobResponse { resp := &schemas.AsyncJobResponse{ ID: j.ID, Status: j.Status, ExpiresAt: j.ExpiresAt, CreatedAt: j.CreatedAt, CompletedAt: j.CompletedAt, StatusCode: j.StatusCode, } if j.Response != "" { switch j.RequestType { case schemas.ResponsesRequest, schemas.ResponsesStreamRequest: var result schemas.BifrostResponsesResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.ChatCompletionRequest, schemas.ChatCompletionStreamRequest: var result schemas.BifrostChatResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.TextCompletionRequest, schemas.TextCompletionStreamRequest: var result schemas.BifrostTextCompletionResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.EmbeddingRequest: var result schemas.BifrostEmbeddingResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.SpeechRequest, schemas.SpeechStreamRequest: var result schemas.BifrostSpeechResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.TranscriptionRequest, schemas.TranscriptionStreamRequest: var result schemas.BifrostTranscriptionResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.ImageGenerationRequest, schemas.ImageGenerationStreamRequest: var result schemas.BifrostImageGenerationResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.ImageEditRequest, schemas.ImageEditStreamRequest: var result schemas.BifrostImageGenerationResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.ImageVariationRequest: var result schemas.BifrostImageGenerationResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } case schemas.CountTokensRequest: var result schemas.BifrostCountTokensResponse if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = &result } default: var result interface{} if err := sonic.Unmarshal([]byte(j.Response), &result); err == nil { resp.Result = result } } // Should never happen, but just in case if resp.Result == nil { var raw interface{} if err := sonic.Unmarshal([]byte(j.Response), &raw); err == nil { resp.Result = raw } } } if j.Error != "" { var bifrostErr schemas.BifrostError if err := sonic.Unmarshal([]byte(j.Error), &bifrostErr); err == nil { resp.Error = &bifrostErr } } return resp } // MCPToolLogSearchFilters represents the available filters for MCP tool log searches type MCPToolLogSearchFilters struct { ToolNames []string `json:"tool_names,omitempty"` ServerLabels []string `json:"server_labels,omitempty"` Status []string `json:"status,omitempty"` VirtualKeyIDs []string `json:"virtual_key_ids,omitempty"` LLMRequestIDs []string `json:"llm_request_ids,omitempty"` StartTime *time.Time `json:"start_time,omitempty"` EndTime *time.Time `json:"end_time,omitempty"` MinLatency *float64 `json:"min_latency,omitempty"` MaxLatency *float64 `json:"max_latency,omitempty"` ContentSearch string `json:"content_search,omitempty"` } // MCPToolLogSearchResult represents the result of an MCP tool log search type MCPToolLogSearchResult struct { Logs []MCPToolLog `json:"logs"` Pagination PaginationOptions `json:"pagination"` Stats MCPToolLogStats `json:"stats"` HasLogs bool `json:"has_logs"` } // MCPToolLogStats represents statistics for MCP tool log searches type MCPToolLogStats struct { TotalExecutions int64 `json:"total_executions"` SuccessRate float64 `json:"success_rate"` AverageLatency float64 `json:"average_latency"` TotalCost float64 `json:"total_cost"` // Total cost in dollars } // BuildContentSummary creates a searchable text summary func (l *Log) BuildContentSummary() string { var parts []string // Add input messages for _, msg := range l.InputHistoryParsed { if msg.Content != nil { // Access content through the Content field if msg.Content.ContentStr != nil && *msg.Content.ContentStr != "" { parts = append(parts, *msg.Content.ContentStr) } // If content blocks exist, extract text from them if msg.Content.ContentBlocks != nil { for _, block := range msg.Content.ContentBlocks { if block.Text != nil && *block.Text != "" { parts = append(parts, *block.Text) } } } } } // Add responses input history if l.ResponsesInputHistoryParsed != nil { for _, msg := range l.ResponsesInputHistoryParsed { if msg.Content != nil { if msg.Content.ContentStr != nil && *msg.Content.ContentStr != "" { parts = append(parts, *msg.Content.ContentStr) } // If content blocks exist, extract text from them if msg.Content.ContentBlocks != nil { for _, block := range msg.Content.ContentBlocks { if block.Text != nil && *block.Text != "" { parts = append(parts, *block.Text) } } } } if msg.ResponsesReasoning != nil { for _, summary := range msg.ResponsesReasoning.Summary { parts = append(parts, summary.Text) } } } } // Add output message if l.OutputMessageParsed != nil { if l.OutputMessageParsed.Content != nil { if l.OutputMessageParsed.Content.ContentStr != nil && *l.OutputMessageParsed.Content.ContentStr != "" { parts = append(parts, *l.OutputMessageParsed.Content.ContentStr) } // If content blocks exist, extract text from them if l.OutputMessageParsed.Content.ContentBlocks != nil { for _, block := range l.OutputMessageParsed.Content.ContentBlocks { if block.Text != nil && *block.Text != "" { parts = append(parts, *block.Text) } } } } } // Add responses output content if l.ResponsesOutputParsed != nil { for _, msg := range l.ResponsesOutputParsed { if msg.Content != nil { if msg.Content.ContentStr != nil && *msg.Content.ContentStr != "" { parts = append(parts, *msg.Content.ContentStr) } // If content blocks exist, extract text from them if msg.Content.ContentBlocks != nil { for _, block := range msg.Content.ContentBlocks { if block.Text != nil && *block.Text != "" { parts = append(parts, *block.Text) } } } } if msg.ResponsesReasoning != nil { for _, summary := range msg.ResponsesReasoning.Summary { parts = append(parts, summary.Text) } } } } // Add rerank output content if l.RerankOutputParsed != nil { for _, result := range l.RerankOutputParsed { if result.Document != nil && result.Document.Text != "" { parts = append(parts, result.Document.Text) } } } // Add OCR output content if l.OCROutputParsed != nil { for _, page := range l.OCROutputParsed.Pages { if page.Markdown != "" { parts = append(parts, page.Markdown) } } } // Add speech input content if l.SpeechInputParsed != nil && l.SpeechInputParsed.Input != "" { parts = append(parts, l.SpeechInputParsed.Input) } // Add transcription output content if l.TranscriptionOutputParsed != nil && l.TranscriptionOutputParsed.Text != "" { parts = append(parts, l.TranscriptionOutputParsed.Text) } // Add image generation input prompt if l.ImageGenerationInputParsed != nil && l.ImageGenerationInputParsed.Prompt != "" { parts = append(parts, l.ImageGenerationInputParsed.Prompt) } // Add image edit input prompt if l.ImageEditInputParsed != nil && l.ImageEditInputParsed.Prompt != "" { parts = append(parts, l.ImageEditInputParsed.Prompt) } // Add video generation input prompt if l.VideoGenerationInputParsed != nil && l.VideoGenerationInputParsed.Prompt != "" { parts = append(parts, l.VideoGenerationInputParsed.Prompt) } // Add error details if l.ErrorDetailsParsed != nil && l.ErrorDetailsParsed.Error != nil && l.ErrorDetailsParsed.Error.Message != "" { parts = append(parts, l.ErrorDetailsParsed.Error.Message) } return strings.Join(parts, " ") } // KeyPairResult represents an ID-Name pair returned from DISTINCT queries type KeyPairResult struct { ID string `gorm:"column:id"` Name string `gorm:"column:name"` } // HistogramBucket represents a single time bucket in the histogram type HistogramBucket struct { Timestamp time.Time `json:"timestamp"` Count int64 `json:"count"` Success int64 `json:"success"` Error int64 `json:"error"` } // HistogramResult represents the histogram query result type HistogramResult struct { Buckets []HistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` } // TokenHistogramBucket represents a single time bucket for token usage type TokenHistogramBucket struct { Timestamp time.Time `json:"timestamp"` PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` TotalTokens int64 `json:"total_tokens"` CachedReadTokens int64 `json:"cached_read_tokens"` } // TokenHistogramResult represents the token histogram query result type TokenHistogramResult struct { Buckets []TokenHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` } // CostHistogramBucket represents a single time bucket for cost data type CostHistogramBucket struct { Timestamp time.Time `json:"timestamp"` TotalCost float64 `json:"total_cost"` ByModel map[string]float64 `json:"by_model"` } // CostHistogramResult represents the cost histogram query result type CostHistogramResult struct { Buckets []CostHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Models []string `json:"models"` } // ModelUsageStats represents usage statistics for a single model type ModelUsageStats struct { Total int64 `json:"total"` Success int64 `json:"success"` Error int64 `json:"error"` } // ModelHistogramBucket represents a single time bucket for model usage type ModelHistogramBucket struct { Timestamp time.Time `json:"timestamp"` ByModel map[string]ModelUsageStats `json:"by_model"` } // ModelHistogramResult represents the model histogram query result type ModelHistogramResult struct { Buckets []ModelHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Models []string `json:"models"` } // LatencyHistogramBucket represents a single time bucket for latency data type LatencyHistogramBucket struct { Timestamp time.Time `json:"timestamp"` AvgLatency float64 `json:"avg_latency"` P90Latency float64 `json:"p90_latency"` P95Latency float64 `json:"p95_latency"` P99Latency float64 `json:"p99_latency"` TotalRequests int64 `json:"total_requests"` } // LatencyHistogramResult represents the latency histogram query result type LatencyHistogramResult struct { Buckets []LatencyHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` } // Provider-level histogram types // ProviderCostHistogramBucket represents a single time bucket for provider cost data type ProviderCostHistogramBucket struct { Timestamp time.Time `json:"timestamp"` TotalCost float64 `json:"total_cost"` ByProvider map[string]float64 `json:"by_provider"` } // ProviderCostHistogramResult represents the provider cost histogram query result type ProviderCostHistogramResult struct { Buckets []ProviderCostHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Providers []string `json:"providers"` } // ProviderTokenStats represents token statistics for a single provider type ProviderTokenStats struct { PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` TotalTokens int64 `json:"total_tokens"` } // ProviderTokenHistogramBucket represents a single time bucket for provider token data type ProviderTokenHistogramBucket struct { Timestamp time.Time `json:"timestamp"` ByProvider map[string]ProviderTokenStats `json:"by_provider"` } // ProviderTokenHistogramResult represents the provider token histogram query result type ProviderTokenHistogramResult struct { Buckets []ProviderTokenHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Providers []string `json:"providers"` } // ProviderLatencyStats represents latency statistics for a single provider type ProviderLatencyStats struct { AvgLatency float64 `json:"avg_latency"` P90Latency float64 `json:"p90_latency"` P95Latency float64 `json:"p95_latency"` P99Latency float64 `json:"p99_latency"` TotalRequests int64 `json:"total_requests"` } // ProviderLatencyHistogramBucket represents a single time bucket for provider latency data type ProviderLatencyHistogramBucket struct { Timestamp time.Time `json:"timestamp"` ByProvider map[string]ProviderLatencyStats `json:"by_provider"` } // ProviderLatencyHistogramResult represents the provider latency histogram query result type ProviderLatencyHistogramResult struct { Buckets []ProviderLatencyHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Providers []string `json:"providers"` } // HistogramDimension represents a column that can be used as a grouping dimension in histograms type HistogramDimension string const ( DimensionProvider HistogramDimension = "provider" DimensionTeam HistogramDimension = "team_id" DimensionCustomer HistogramDimension = "customer_id" DimensionUser HistogramDimension = "user_id" DimensionBusinessUnit HistogramDimension = "business_unit_id" ) // ValidHistogramDimensions is the set of allowed dimension values var ValidHistogramDimensions = map[HistogramDimension]bool{ DimensionProvider: true, DimensionTeam: true, DimensionCustomer: true, DimensionUser: true, DimensionBusinessUnit: true, } // Dimension-level histogram types (generic version of Provider histograms) // DimensionCostHistogramBucket represents a single time bucket for dimension-grouped cost data type DimensionCostHistogramBucket struct { Timestamp time.Time `json:"timestamp"` TotalCost float64 `json:"total_cost"` ByDimension map[string]float64 `json:"by_dimension"` } // DimensionCostHistogramResult represents the dimension cost histogram query result type DimensionCostHistogramResult struct { Buckets []DimensionCostHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Dimension HistogramDimension `json:"dimension"` DimensionValues []string `json:"dimension_values"` } // DimensionTokenStats represents token statistics for a single dimension value type DimensionTokenStats struct { PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` TotalTokens int64 `json:"total_tokens"` } // DimensionTokenHistogramBucket represents a single time bucket for dimension-grouped token data type DimensionTokenHistogramBucket struct { Timestamp time.Time `json:"timestamp"` ByDimension map[string]DimensionTokenStats `json:"by_dimension"` } // DimensionTokenHistogramResult represents the dimension token histogram query result type DimensionTokenHistogramResult struct { Buckets []DimensionTokenHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Dimension HistogramDimension `json:"dimension"` DimensionValues []string `json:"dimension_values"` } // DimensionLatencyStats represents latency statistics for a single dimension value type DimensionLatencyStats struct { AvgLatency float64 `json:"avg_latency"` P90Latency float64 `json:"p90_latency"` P95Latency float64 `json:"p95_latency"` P99Latency float64 `json:"p99_latency"` TotalRequests int64 `json:"total_requests"` } // DimensionLatencyHistogramBucket represents a single time bucket for dimension-grouped latency data type DimensionLatencyHistogramBucket struct { Timestamp time.Time `json:"timestamp"` ByDimension map[string]DimensionLatencyStats `json:"by_dimension"` } // DimensionLatencyHistogramResult represents the dimension latency histogram query result type DimensionLatencyHistogramResult struct { Buckets []DimensionLatencyHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` Dimension HistogramDimension `json:"dimension"` DimensionValues []string `json:"dimension_values"` } // MCPHistogramBucket represents a single time bucket for MCP tool call volume type MCPHistogramBucket struct { Timestamp time.Time `json:"timestamp"` Count int64 `json:"count"` Success int64 `json:"success"` Error int64 `json:"error"` } // MCPHistogramResult represents the MCP tool call volume histogram query result type MCPHistogramResult struct { Buckets []MCPHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` } // MCPCostHistogramBucket represents a single time bucket for MCP cost data type MCPCostHistogramBucket struct { Timestamp time.Time `json:"timestamp"` TotalCost float64 `json:"total_cost"` } // MCPCostHistogramResult represents the MCP cost histogram query result type MCPCostHistogramResult struct { Buckets []MCPCostHistogramBucket `json:"buckets"` BucketSizeSeconds int64 `json:"bucket_size_seconds"` } // MCPTopToolResult represents a single tool's aggregated stats type MCPTopToolResult struct { ToolName string `json:"tool_name"` Count int64 `json:"count"` Cost float64 `json:"cost"` } // MCPTopToolsResult represents the top N MCP tools by call count type MCPTopToolsResult struct { Tools []MCPTopToolResult `json:"tools"` } // ModelRankingEntry represents aggregated stats for a single model over a time period. type ModelRankingEntry struct { Model string `json:"model"` Provider string `json:"provider"` TotalRequests int64 `json:"total_requests"` SuccessCount int64 `json:"success_count"` SuccessRate float64 `json:"success_rate"` TotalTokens int64 `json:"total_tokens"` TotalCost float64 `json:"total_cost"` AvgLatency float64 `json:"avg_latency"` } // ModelRankingTrend represents the percentage change compared to the previous period. type ModelRankingTrend struct { HasPreviousPeriod bool `json:"has_previous_period"` RequestsTrend float64 `json:"requests_trend"` TokensTrend float64 `json:"tokens_trend"` CostTrend float64 `json:"cost_trend"` LatencyTrend float64 `json:"latency_trend"` } // ModelRankingWithTrend combines ranking entry with trend data. type ModelRankingWithTrend struct { ModelRankingEntry Trend ModelRankingTrend `json:"trend"` } // ModelRankingResult is the response for the model rankings endpoint. type ModelRankingResult struct { Rankings []ModelRankingWithTrend `json:"rankings"` } // UserRankingEntry represents a single user's usage statistics. type UserRankingEntry struct { UserID string `json:"user_id"` TotalRequests int64 `json:"total_requests"` TotalTokens int64 `json:"total_tokens"` TotalCost float64 `json:"total_cost"` } // UserRankingTrend represents the percentage change compared to the previous period. type UserRankingTrend struct { HasPreviousPeriod bool `json:"has_previous_period"` RequestsTrend float64 `json:"requests_trend"` TokensTrend float64 `json:"tokens_trend"` CostTrend float64 `json:"cost_trend"` } // UserRankingWithTrend combines ranking entry with trend data. type UserRankingWithTrend struct { UserRankingEntry Trend UserRankingTrend `json:"trend"` } // UserRankingResult is the response for the user rankings endpoint. type UserRankingResult struct { Rankings []UserRankingWithTrend `json:"rankings"` }