Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions core/providers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions framework/logstore/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
69 changes: 55 additions & 14 deletions framework/logstore/tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
45 changes: 24 additions & 21 deletions plugins/logging/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -192,15 +193,16 @@ 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()

initialData := &InitialLogData{
Provider: string(provider),
Model: model,
Object: string(req.RequestType),
InputHistory: inputHistory,
InputHistory: inputHistory,
ResponsesInputHistory: responsesInputHistory,
}

switch req.RequestType {
Expand Down Expand Up @@ -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)
}
Expand Down
11 changes: 6 additions & 5 deletions plugins/logging/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand Down
17 changes: 7 additions & 10 deletions plugins/logging/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -139,7 +136,7 @@ func (p *LoggerPlugin) extractInputHistory(request *schemas.BifrostRequest) []sc
ContentBlocks: contentBlocks,
},
},
}
}, []schemas.ResponsesMessage{}
}
return []schemas.ChatMessage{}
return []schemas.ChatMessage{}, []schemas.ResponsesMessage{}
}
4 changes: 2 additions & 2 deletions transports/bifrost-http/integrations/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
28 changes: 18 additions & 10 deletions ui/app/logs/views/logDetailsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 && (
<>
<div className="mt-4 w-full text-left text-sm font-medium">Input</div>
<LogChatMessageView message={log.input_history[log.input_history.length - 1]} />
</>
)}
{/* Show input for chat/text completions */}
{log.input_history && log.input_history.length > 0 && (
<>
<div className="mt-4 w-full text-left text-sm font-medium">Input</div>
<LogChatMessageView message={log.input_history[log.input_history.length - 1]} />
</>
)}

{/* Show input history for responses API */}
{log.responses_input_history && log.responses_input_history.length > 0 && (
<>
<div className="mt-4 w-full text-left text-sm font-medium">Input</div>
<LogResponsesMessageView messages={log.responses_input_history} />
</>
)}

{log.status !== "processing" && (
{log.status !== "processing" && (
<>
{log.output_message && !log.error_details?.error.message && (
<>
Expand All @@ -321,7 +329,7 @@ export function LogDetailSheet({ log, open, onOpenChange }: LogDetailSheetProps)
{log.responses_output && log.responses_output.length > 0 && !log.error_details?.error.message && (
<>
<div className="mt-4 w-full text-left text-sm font-medium">Response</div>
<LogResponsesOutputView messages={log.responses_output} />
<LogResponsesMessageView messages={log.responses_output} />
</>
)}
{log.embedding_output && log.embedding_output.length > 0 && !log.error_details?.error.message && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ResponsesMessage, ResponsesMessageContentBlock } from "@/lib/types/logs
import { CodeEditor } from "./codeEditor";
import { isJson, cleanJson } from "@/lib/utils/validation";

interface LogResponsesOutputViewProps {
interface LogResponsesMessageViewProps {
messages: ResponsesMessage[];
}

Expand Down Expand Up @@ -334,11 +334,11 @@ const renderMessage = (message: ResponsesMessage, index: number) => {
);
};

export default function LogResponsesOutputView({ messages }: LogResponsesOutputViewProps) {
export default function LogResponsesMessageView({ messages }: LogResponsesMessageViewProps) {
if (!messages || messages.length === 0) {
return (
<div className="w-full rounded-sm border">
<div className="text-muted-foreground px-6 py-4 text-center text-sm">No responses output available</div>
<div className="text-muted-foreground px-6 py-4 text-center text-sm">No responses messages available</div>
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions ui/lib/types/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export interface LogEntry {
provider: string;
model: string;
input_history: ChatMessage[];
responses_input_history: ResponsesMessage[];
output_message?: ChatMessage;
responses_output?: ResponsesMessage[];
embedding_output?: BifrostEmbedding[];
Expand Down