Files
bifrost/framework/logstore/tables.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

1481 lines
58 KiB
Go

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"`
}