diff --git a/core/providers/utils.go b/core/providers/utils.go index a9d6b3df4..ce8477083 100644 --- a/core/providers/utils.go +++ b/core/providers/utils.go @@ -616,11 +616,7 @@ func getResponsesChunkConverterCombinedPostHookRunner(postHookRunner schemas.Pos if result != nil { if result.ChatResponse != nil { result.ResponsesStreamResponse = result.ChatResponse.ToBifrostResponsesStreamResponse() - if result.ResponsesResponse == nil { - result.ResponsesResponse = &schemas.BifrostResponsesResponse{} - } - result.ResponsesResponse.ExtraFields = result.ResponsesStreamResponse.ExtraFields - result.ResponsesResponse.ExtraFields.RequestType = schemas.ResponsesRequest + result.ChatResponse = nil } } else if err != nil { // Ensure downstream knows this is a Responses stream even on errors diff --git a/framework/logstore/migrations.go b/framework/logstore/migrations.go index 5cdab8de6..4b4f722b6 100644 --- a/framework/logstore/migrations.go +++ b/framework/logstore/migrations.go @@ -25,6 +25,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error { if err := migrationAddCostAndCacheDebugColumn(ctx, db); err != nil { return err } + if err := migrationAddResponsesInputHistoryColumn(ctx, db); err != nil { + return err + } return nil } @@ -272,3 +275,34 @@ func migrationAddCostAndCacheDebugColumn(ctx context.Context, db *gorm.DB) error } return nil } + +func migrationAddResponsesInputHistoryColumn(ctx context.Context, db *gorm.DB) error { + opts := *migrator.DefaultOptions + opts.UseTransaction = true + m := migrator.New(db, &opts, []*migrator.Migration{{ + ID: "logs_init_add_responses_input_history_column", + Migrate: func(tx *gorm.DB) error { + tx = tx.WithContext(ctx) + migrator := tx.Migrator() + if !migrator.HasColumn(&Log{}, "responses_input_history") { + if err := migrator.AddColumn(&Log{}, "responses_input_history"); err != nil { + return err + } + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + tx = tx.WithContext(ctx) + migrator := tx.Migrator() + if err := migrator.DropColumn(&Log{}, "responses_input_history"); err != nil { + return err + } + return nil + }, + }}) + err := m.Migrate() + if err != nil { + return fmt.Errorf("error while adding responses_input_history column: %s", err.Error()) + } + return nil +} \ No newline at end of file diff --git a/framework/logstore/tables.go b/framework/logstore/tables.go index 0bae12e9c..70e05c34f 100644 --- a/framework/logstore/tables.go +++ b/framework/logstore/tables.go @@ -75,6 +75,7 @@ type Log struct { Provider string `gorm:"type:varchar(255);index;not null" json:"provider"` Model string `gorm:"type:varchar(255);index;not null" json:"model"` 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 @@ -103,20 +104,21 @@ type Log struct { CreatedAt time.Time `gorm:"index;not null" json:"created_at"` // Virtual fields for JSON output - these will be populated when needed - InputHistoryParsed []schemas.ChatMessage `gorm:"-" json:"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"` - 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"` - SpeechOutputParsed *schemas.BifrostSpeechResponse `gorm:"-" json:"speech_output,omitempty"` - TranscriptionOutputParsed *schemas.BifrostTranscriptionResponse `gorm:"-" json:"transcription_output,omitempty"` - CacheDebugParsed *schemas.BifrostCacheDebug `gorm:"-" json:"cache_debug,omitempty"` + 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"` + 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"` + SpeechOutputParsed *schemas.BifrostSpeechResponse `gorm:"-" json:"speech_output,omitempty"` + TranscriptionOutputParsed *schemas.BifrostTranscriptionResponse `gorm:"-" json:"transcription_output,omitempty"` + CacheDebugParsed *schemas.BifrostCacheDebug `gorm:"-" json:"cache_debug,omitempty"` } // TableName sets the table name for GORM @@ -152,6 +154,14 @@ func (l *Log) SerializeFields() error { } } + if l.ResponsesInputHistoryParsed != nil { + if data, err := json.Marshal(l.ResponsesInputHistoryParsed); err != nil { + return err + } else { + l.ResponsesInputHistory = string(data) + } + } + if l.OutputMessageParsed != nil { if data, err := json.Marshal(l.OutputMessageParsed); err != nil { return err @@ -275,6 +285,13 @@ func (l *Log) DeserializeFields() error { } } + if l.ResponsesInputHistory != "" { + if err := json.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 := json.Unmarshal([]byte(l.OutputMessage), &l.OutputMessageParsed); err != nil { // Log error but don't fail the operation - initialize as nil @@ -392,6 +409,30 @@ func (l *Log) BuildContentSummary() string { } } + // 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 { diff --git a/plugins/logging/main.go b/plugins/logging/main.go index 1a2303b1b..0882e42ac 100644 --- a/plugins/logging/main.go +++ b/plugins/logging/main.go @@ -59,14 +59,15 @@ type LogMessage struct { // InitialLogData contains data for initial log entry creation type InitialLogData struct { - Provider string - Model string - Object string - InputHistory []schemas.ChatMessage - Params interface{} - SpeechInput *schemas.SpeechInput - TranscriptionInput *schemas.TranscriptionInput - Tools []schemas.ChatTool + Provider string + Model string + Object string + InputHistory []schemas.ChatMessage + ResponsesInputHistory []schemas.ResponsesMessage + Params interface{} + SpeechInput *schemas.SpeechInput + TranscriptionInput *schemas.TranscriptionInput + Tools []schemas.ChatTool } // LogCallback is a function that gets called when a new log entry is created @@ -192,7 +193,7 @@ func (p *LoggerPlugin) PreHook(ctx *context.Context, req *schemas.BifrostRequest if bifrost.IsStreamRequestType(req.RequestType) { p.accumulator.CreateStreamAccumulator(requestID, createdTimestamp) } - inputHistory := p.extractInputHistory(req) + inputHistory, responsesInputHistory := p.extractInputHistory(req) provider, model, _ := req.GetRequestFields() @@ -200,7 +201,8 @@ func (p *LoggerPlugin) PreHook(ctx *context.Context, req *schemas.BifrostRequest Provider: string(provider), Model: model, Object: string(req.RequestType), - InputHistory: inputHistory, + InputHistory: inputHistory, + ResponsesInputHistory: responsesInputHistory, } switch req.RequestType { @@ -257,17 +259,18 @@ func (p *LoggerPlugin) PreHook(ctx *context.Context, req *schemas.BifrostRequest p.mu.Lock() if p.logCallback != nil { initialEntry := &logstore.Log{ - ID: logMsg.RequestID, - Timestamp: logMsg.Timestamp, - Object: logMsg.InitialData.Object, - Provider: logMsg.InitialData.Provider, - Model: logMsg.InitialData.Model, - InputHistoryParsed: logMsg.InitialData.InputHistory, - ParamsParsed: logMsg.InitialData.Params, - ToolsParsed: logMsg.InitialData.Tools, - Status: "processing", - Stream: false, // Initially false, will be updated if streaming - CreatedAt: logMsg.Timestamp, + ID: logMsg.RequestID, + Timestamp: logMsg.Timestamp, + Object: logMsg.InitialData.Object, + Provider: logMsg.InitialData.Provider, + Model: logMsg.InitialData.Model, + InputHistoryParsed: logMsg.InitialData.InputHistory, + ResponsesInputHistoryParsed: logMsg.InitialData.ResponsesInputHistory, + ParamsParsed: logMsg.InitialData.Params, + ToolsParsed: logMsg.InitialData.Tools, + Status: "processing", + Stream: false, // Initially false, will be updated if streaming + CreatedAt: logMsg.Timestamp, } p.logCallback(initialEntry) } diff --git a/plugins/logging/operations.go b/plugins/logging/operations.go index 431eaefbc..094323609 100644 --- a/plugins/logging/operations.go +++ b/plugins/logging/operations.go @@ -24,11 +24,12 @@ func (p *LoggerPlugin) insertInitialLogEntry(ctx context.Context, requestID stri Stream: false, CreatedAt: timestamp, // Set parsed fields for serialization - InputHistoryParsed: data.InputHistory, - ParamsParsed: data.Params, - ToolsParsed: data.Tools, - SpeechInputParsed: data.SpeechInput, - TranscriptionInputParsed: data.TranscriptionInput, + InputHistoryParsed: data.InputHistory, + ResponsesInputHistoryParsed: data.ResponsesInputHistory, + ParamsParsed: data.Params, + ToolsParsed: data.Tools, + SpeechInputParsed: data.SpeechInput, + TranscriptionInputParsed: data.TranscriptionInput, } if parentRequestID != "" { diff --git a/plugins/logging/utils.go b/plugins/logging/utils.go index db5150c96..a0c96a27b 100644 --- a/plugins/logging/utils.go +++ b/plugins/logging/utils.go @@ -86,15 +86,12 @@ func retryOnNotFound(ctx context.Context, operation func() error) error { } // extractInputHistory extracts input history from request input -func (p *LoggerPlugin) extractInputHistory(request *schemas.BifrostRequest) []schemas.ChatMessage { +func (p *LoggerPlugin) extractInputHistory(request *schemas.BifrostRequest) ([]schemas.ChatMessage, []schemas.ResponsesMessage) { if request.ChatRequest != nil { - return request.ChatRequest.Input + return request.ChatRequest.Input, []schemas.ResponsesMessage{} } - if request.ResponsesRequest != nil { - messages := schemas.ToChatMessages(request.ResponsesRequest.Input) - if len(messages) > 0 { - return messages - } + if request.ResponsesRequest != nil && len(request.ResponsesRequest.Input) > 0 { + return []schemas.ChatMessage{}, request.ResponsesRequest.Input } if request.TextCompletionRequest != nil { var text string @@ -114,7 +111,7 @@ func (p *LoggerPlugin) extractInputHistory(request *schemas.BifrostRequest) []sc ContentStr: &text, }, }, - } + }, []schemas.ResponsesMessage{} } if request.EmbeddingRequest != nil { texts := request.EmbeddingRequest.Input.Texts @@ -139,7 +136,7 @@ func (p *LoggerPlugin) extractInputHistory(request *schemas.BifrostRequest) []sc ContentBlocks: contentBlocks, }, }, - } + }, []schemas.ResponsesMessage{} } - return []schemas.ChatMessage{} + return []schemas.ChatMessage{}, []schemas.ResponsesMessage{} } diff --git a/transports/bifrost-http/integrations/utils.go b/transports/bifrost-http/integrations/utils.go index 5e53c4187..4c1c562ab 100644 --- a/transports/bifrost-http/integrations/utils.go +++ b/transports/bifrost-http/integrations/utils.go @@ -722,10 +722,10 @@ func (g *GenericRouter) handleStreaming(ctx *fasthttp.RequestCtx, config RouteCo switch { case chunk.BifrostTextCompletionResponse != nil: convertedResponse, err = config.StreamConfig.TextStreamResponseConverter(chunk.BifrostTextCompletionResponse) - case chunk.BifrostResponsesStreamResponse != nil: - convertedResponse, err = config.StreamConfig.ResponsesStreamResponseConverter(chunk.BifrostResponsesStreamResponse) case chunk.BifrostChatResponse != nil: convertedResponse, err = config.StreamConfig.ChatStreamResponseConverter(chunk.BifrostChatResponse) + case chunk.BifrostResponsesStreamResponse != nil: + convertedResponse, err = config.StreamConfig.ResponsesStreamResponseConverter(chunk.BifrostResponsesStreamResponse) case chunk.BifrostSpeechStreamResponse != nil: convertedResponse, err = config.StreamConfig.SpeechStreamResponseConverter(chunk.BifrostSpeechStreamResponse) case chunk.BifrostTranscriptionStreamResponse != nil: diff --git a/ui/app/logs/views/logDetailsSheet.tsx b/ui/app/logs/views/logDetailsSheet.tsx index 67f6ea926..8b848309a 100644 --- a/ui/app/logs/views/logDetailsSheet.tsx +++ b/ui/app/logs/views/logDetailsSheet.tsx @@ -13,7 +13,7 @@ import LogEntryDetailsView from "./logEntryDetailsView"; import LogChatMessageView from "./logChatMessageView"; import SpeechView from "./speechView"; import TranscriptionView from "./transcriptionView"; -import LogResponsesOutputView from "./logResponsesOutputView"; +import LogResponsesMessageView from "./logResponsesMessageView"; interface LogDetailSheetProps { log: LogEntry | null; @@ -300,15 +300,23 @@ export function LogDetailSheet({ log, open, onOpenChange }: LogDetailSheetProps) > )} - {/* Show input for chat/text completions */} - {log.input_history && log.input_history.length > 0 && ( - <> -