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

1670 lines
51 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package tracing provides distributed tracing utilities for Bifrost.
package tracing
import (
"fmt"
"strings"
"github.com/maximhq/bifrost/core/schemas"
)
// PopulateRequestAttributes extracts common request attributes from a BifrostRequest.
// This is the main entry point for populating request attributes on a span.
func PopulateRequestAttributes(req *schemas.BifrostRequest) map[string]any {
attrs := make(map[string]any)
if req == nil {
return attrs
}
provider, model, _ := req.GetRequestFields()
attrs[schemas.AttrProviderName] = string(provider)
attrs[schemas.AttrRequestModel] = model
switch req.RequestType {
case schemas.ChatCompletionRequest, schemas.ChatCompletionStreamRequest:
PopulateChatRequestAttributes(req.ChatRequest, attrs)
case schemas.TextCompletionRequest, schemas.TextCompletionStreamRequest:
PopulateTextCompletionRequestAttributes(req.TextCompletionRequest, attrs)
case schemas.EmbeddingRequest:
PopulateEmbeddingRequestAttributes(req.EmbeddingRequest, attrs)
case schemas.TranscriptionRequest, schemas.TranscriptionStreamRequest:
PopulateTranscriptionRequestAttributes(req.TranscriptionRequest, attrs)
case schemas.SpeechRequest, schemas.SpeechStreamRequest:
PopulateSpeechRequestAttributes(req.SpeechRequest, attrs)
case schemas.ResponsesRequest, schemas.ResponsesStreamRequest:
PopulateResponsesRequestAttributes(req.ResponsesRequest, attrs)
case schemas.BatchCreateRequest:
PopulateBatchCreateRequestAttributes(req.BatchCreateRequest, attrs)
case schemas.BatchListRequest:
PopulateBatchListRequestAttributes(req.BatchListRequest, attrs)
case schemas.BatchRetrieveRequest:
PopulateBatchRetrieveRequestAttributes(req.BatchRetrieveRequest, attrs)
case schemas.BatchCancelRequest:
PopulateBatchCancelRequestAttributes(req.BatchCancelRequest, attrs)
case schemas.BatchResultsRequest:
PopulateBatchResultsRequestAttributes(req.BatchResultsRequest, attrs)
case schemas.FileUploadRequest:
PopulateFileUploadRequestAttributes(req.FileUploadRequest, attrs)
case schemas.FileListRequest:
PopulateFileListRequestAttributes(req.FileListRequest, attrs)
case schemas.FileRetrieveRequest:
PopulateFileRetrieveRequestAttributes(req.FileRetrieveRequest, attrs)
case schemas.FileDeleteRequest:
PopulateFileDeleteRequestAttributes(req.FileDeleteRequest, attrs)
case schemas.FileContentRequest:
PopulateFileContentRequestAttributes(req.FileContentRequest, attrs)
}
return attrs
}
// PopulateResponseAttributes extracts common response attributes from a BifrostResponse.
// This is the main entry point for populating response attributes on a span.
func PopulateResponseAttributes(resp *schemas.BifrostResponse) map[string]any {
attrs := make(map[string]any)
if resp == nil {
return attrs
}
switch {
case resp.ChatResponse != nil:
PopulateChatResponseAttributes(resp.ChatResponse, attrs)
case resp.TextCompletionResponse != nil:
PopulateTextCompletionResponseAttributes(resp.TextCompletionResponse, attrs)
case resp.EmbeddingResponse != nil:
PopulateEmbeddingResponseAttributes(resp.EmbeddingResponse, attrs)
case resp.TranscriptionResponse != nil:
PopulateTranscriptionResponseAttributes(resp.TranscriptionResponse, attrs)
case resp.SpeechResponse != nil:
PopulateSpeechResponseAttributes(resp.SpeechResponse, attrs)
case resp.ResponsesResponse != nil:
PopulateResponsesResponseAttributes(resp.ResponsesResponse, attrs)
case resp.BatchCreateResponse != nil:
PopulateBatchCreateResponseAttributes(resp.BatchCreateResponse, attrs)
case resp.BatchListResponse != nil:
PopulateBatchListResponseAttributes(resp.BatchListResponse, attrs)
case resp.BatchRetrieveResponse != nil:
PopulateBatchRetrieveResponseAttributes(resp.BatchRetrieveResponse, attrs)
case resp.BatchCancelResponse != nil:
PopulateBatchCancelResponseAttributes(resp.BatchCancelResponse, attrs)
case resp.BatchResultsResponse != nil:
PopulateBatchResultsResponseAttributes(resp.BatchResultsResponse, attrs)
case resp.FileUploadResponse != nil:
PopulateFileUploadResponseAttributes(resp.FileUploadResponse, attrs)
case resp.FileListResponse != nil:
PopulateFileListResponseAttributes(resp.FileListResponse, attrs)
case resp.FileRetrieveResponse != nil:
PopulateFileRetrieveResponseAttributes(resp.FileRetrieveResponse, attrs)
case resp.FileDeleteResponse != nil:
PopulateFileDeleteResponseAttributes(resp.FileDeleteResponse, attrs)
case resp.FileContentResponse != nil:
PopulateFileContentResponseAttributes(resp.FileContentResponse, attrs)
}
return attrs
}
// PopulateErrorAttributes extracts error attributes from a BifrostError.
func PopulateErrorAttributes(err *schemas.BifrostError) map[string]any {
attrs := make(map[string]any)
if err == nil || err.Error == nil {
return attrs
}
attrs[schemas.AttrError] = err.Error.Message
if err.Error.Type != nil {
attrs[schemas.AttrErrorType] = *err.Error.Type
}
if err.Error.Code != nil {
attrs[schemas.AttrErrorCode] = *err.Error.Code
}
return attrs
}
// PopulateContextAttributes extracts context-related attributes (virtual keys, retries, routing rules, etc.)
func PopulateContextAttributes(
attrs map[string]any,
virtualKeyID, virtualKeyName string,
selectedKeyID, selectedKeyName string,
routingRuleID, routingRuleName string,
teamID, teamName string,
customerID, customerName string,
numberOfRetries, fallbackIndex int,
) {
if virtualKeyID != "" {
attrs[schemas.AttrVirtualKeyID] = virtualKeyID
attrs[schemas.AttrVirtualKeyName] = virtualKeyName
}
if selectedKeyID != "" {
attrs[schemas.AttrSelectedKeyID] = selectedKeyID
attrs[schemas.AttrSelectedKeyName] = selectedKeyName
}
if routingRuleID != "" {
attrs[schemas.AttrRoutingRuleID] = routingRuleID
attrs[schemas.AttrRoutingRuleName] = routingRuleName
}
if teamID != "" {
attrs[schemas.AttrTeamID] = teamID
attrs[schemas.AttrTeamName] = teamName
}
if customerID != "" {
attrs[schemas.AttrCustomerID] = customerID
attrs[schemas.AttrCustomerName] = customerName
}
attrs[schemas.AttrNumberOfRetries] = numberOfRetries
attrs[schemas.AttrFallbackIndex] = fallbackIndex
}
// ===============================================
// Chat Completion Request/Response
// ===============================================
// PopulateChatRequestAttributes extracts chat completion request attributes.
func PopulateChatRequestAttributes(req *schemas.BifrostChatRequest, attrs map[string]any) {
if req == nil {
return
}
if req.Params != nil {
if req.Params.MaxCompletionTokens != nil {
attrs[schemas.AttrMaxTokens] = *req.Params.MaxCompletionTokens
}
if req.Params.Temperature != nil {
attrs[schemas.AttrTemperature] = *req.Params.Temperature
}
if req.Params.TopP != nil {
attrs[schemas.AttrTopP] = *req.Params.TopP
}
if req.Params.Stop != nil {
attrs[schemas.AttrStopSequences] = strings.Join(req.Params.Stop, ",")
}
if req.Params.PresencePenalty != nil {
attrs[schemas.AttrPresencePenalty] = *req.Params.PresencePenalty
}
if req.Params.FrequencyPenalty != nil {
attrs[schemas.AttrFrequencyPenalty] = *req.Params.FrequencyPenalty
}
if req.Params.ParallelToolCalls != nil {
attrs[schemas.AttrParallelToolCall] = *req.Params.ParallelToolCalls
}
if req.Params.User != nil {
attrs[schemas.AttrRequestUser] = *req.Params.User
}
// ExtraParams
for k, v := range req.Params.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// Extract input messages
if req.Input != nil {
attrs[schemas.AttrMessageCount] = len(req.Input)
messages := extractChatMessages(req.Input)
if len(messages) > 0 {
attrs[schemas.AttrInputMessages] = messages
}
}
}
// PopulateChatResponseAttributes extracts chat completion response attributes.
func PopulateChatResponseAttributes(resp *schemas.BifrostChatResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrResponseID] = resp.ID
attrs[schemas.AttrResponseModel] = resp.Model
if resp.Object != "" {
attrs[schemas.AttrObject] = resp.Object
}
if resp.SystemFingerprint != "" {
attrs[schemas.AttrSystemFprint] = resp.SystemFingerprint
}
attrs[schemas.AttrCreated] = resp.Created
if resp.ServiceTier != nil {
attrs[schemas.AttrServiceTier] = *resp.ServiceTier
}
// Extract output messages
outputMessages := extractChatResponseMessages(resp)
if len(outputMessages) > 0 {
attrs[schemas.AttrOutputMessages] = outputMessages
}
// Extract finish reason from first choice
if len(resp.Choices) > 0 && resp.Choices[0].FinishReason != nil {
attrs[schemas.AttrFinishReason] = *resp.Choices[0].FinishReason
}
// Usage
if resp.Usage != nil {
attrs[schemas.AttrPromptTokens] = resp.Usage.PromptTokens
attrs[schemas.AttrCompletionTokens] = resp.Usage.CompletionTokens
attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens
if resp.Usage.PromptTokensDetails != nil {
if resp.Usage.PromptTokensDetails.TextTokens > 0 {
attrs[schemas.AttrPromptTokenDetailsText] = resp.Usage.PromptTokensDetails.TextTokens
}
if resp.Usage.PromptTokensDetails.AudioTokens > 0 {
attrs[schemas.AttrPromptTokenDetailsAudio] = resp.Usage.PromptTokensDetails.AudioTokens
}
if resp.Usage.PromptTokensDetails.ImageTokens > 0 {
attrs[schemas.AttrPromptTokenDetailsImage] = resp.Usage.PromptTokensDetails.ImageTokens
}
if resp.Usage.PromptTokensDetails.CachedReadTokens > 0 {
attrs[schemas.AttrPromptTokenDetailsCachedRead] = resp.Usage.PromptTokensDetails.CachedReadTokens
}
if resp.Usage.PromptTokensDetails.CachedWriteTokens > 0 {
attrs[schemas.AttrPromptTokenDetailsCachedWrite] = resp.Usage.PromptTokensDetails.CachedWriteTokens
}
}
if resp.Usage.CompletionTokensDetails != nil {
if resp.Usage.CompletionTokensDetails.TextTokens > 0 {
attrs[schemas.AttrCompletionTokenDetailsText] = resp.Usage.CompletionTokensDetails.TextTokens
}
if resp.Usage.CompletionTokensDetails.AudioTokens > 0 {
attrs[schemas.AttrCompletionTokenDetailsAudio] = resp.Usage.CompletionTokensDetails.AudioTokens
}
if resp.Usage.CompletionTokensDetails.ImageTokens != nil && *resp.Usage.CompletionTokensDetails.ImageTokens > 0 {
attrs[schemas.AttrCompletionTokenDetailsImage] = *resp.Usage.CompletionTokensDetails.ImageTokens
}
if resp.Usage.CompletionTokensDetails.ReasoningTokens > 0 {
attrs[schemas.AttrCompletionTokenDetailsReason] = resp.Usage.CompletionTokensDetails.ReasoningTokens
}
if resp.Usage.CompletionTokensDetails.AcceptedPredictionTokens > 0 {
attrs[schemas.AttrCompletionTokenDetailsAccept] = resp.Usage.CompletionTokensDetails.AcceptedPredictionTokens
}
if resp.Usage.CompletionTokensDetails.RejectedPredictionTokens > 0 {
attrs[schemas.AttrCompletionTokenDetailsReject] = resp.Usage.CompletionTokensDetails.RejectedPredictionTokens
}
if resp.Usage.CompletionTokensDetails.CitationTokens != nil && *resp.Usage.CompletionTokensDetails.CitationTokens > 0 {
attrs[schemas.AttrCompletionTokenDetailsCite] = *resp.Usage.CompletionTokensDetails.CitationTokens
}
if resp.Usage.CompletionTokensDetails.NumSearchQueries != nil && *resp.Usage.CompletionTokensDetails.NumSearchQueries > 0 {
attrs[schemas.AttrCompletionTokenDetailsSearch] = *resp.Usage.CompletionTokensDetails.NumSearchQueries
}
}
}
}
// ===============================================
// Text Completion Request/Response
// ===============================================
// PopulateTextCompletionRequestAttributes extracts text completion request attributes.
func PopulateTextCompletionRequestAttributes(req *schemas.BifrostTextCompletionRequest, attrs map[string]any) {
if req == nil {
return
}
if req.Params != nil {
if req.Params.MaxTokens != nil {
attrs[schemas.AttrMaxTokens] = *req.Params.MaxTokens
}
if req.Params.Temperature != nil {
attrs[schemas.AttrTemperature] = *req.Params.Temperature
}
if req.Params.TopP != nil {
attrs[schemas.AttrTopP] = *req.Params.TopP
}
if req.Params.Stop != nil {
attrs[schemas.AttrStopSequences] = strings.Join(req.Params.Stop, ",")
}
if req.Params.PresencePenalty != nil {
attrs[schemas.AttrPresencePenalty] = *req.Params.PresencePenalty
}
if req.Params.FrequencyPenalty != nil {
attrs[schemas.AttrFrequencyPenalty] = *req.Params.FrequencyPenalty
}
if req.Params.BestOf != nil {
attrs[schemas.AttrBestOf] = *req.Params.BestOf
}
if req.Params.Echo != nil {
attrs[schemas.AttrEcho] = *req.Params.Echo
}
if req.Params.LogitBias != nil {
attrs[schemas.AttrLogitBias] = fmt.Sprintf("%v", req.Params.LogitBias)
}
if req.Params.LogProbs != nil {
attrs[schemas.AttrLogProbs] = *req.Params.LogProbs
}
if req.Params.N != nil {
attrs[schemas.AttrN] = *req.Params.N
}
if req.Params.Seed != nil {
attrs[schemas.AttrSeed] = *req.Params.Seed
}
if req.Params.Suffix != nil {
attrs[schemas.AttrSuffix] = *req.Params.Suffix
}
if req.Params.User != nil {
attrs[schemas.AttrRequestUser] = *req.Params.User
}
// ExtraParams
for k, v := range req.Params.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// Extract input text
if req.Input != nil {
if req.Input.PromptStr != nil {
attrs[schemas.AttrInputText] = *req.Input.PromptStr
} else if req.Input.PromptArray != nil {
attrs[schemas.AttrInputText] = strings.Join(req.Input.PromptArray, ",")
}
}
}
// PopulateTextCompletionResponseAttributes extracts text completion response attributes.
func PopulateTextCompletionResponseAttributes(resp *schemas.BifrostTextCompletionResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrResponseID] = resp.ID
attrs[schemas.AttrResponseModel] = resp.Model
if resp.Object != "" {
attrs[schemas.AttrObject] = resp.Object
}
if resp.SystemFingerprint != "" {
attrs[schemas.AttrSystemFprint] = resp.SystemFingerprint
}
// Extract output text
var outputs []string
for _, choice := range resp.Choices {
if choice.TextCompletionResponseChoice != nil && choice.TextCompletionResponseChoice.Text != nil {
outputs = append(outputs, *choice.TextCompletionResponseChoice.Text)
}
}
if len(outputs) > 0 {
attrs[schemas.AttrOutputMessages] = outputs
}
// Usage
if resp.Usage != nil {
attrs[schemas.AttrPromptTokens] = resp.Usage.PromptTokens
attrs[schemas.AttrCompletionTokens] = resp.Usage.CompletionTokens
attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens
}
}
// ===============================================
// Embedding Request/Response
// ===============================================
// PopulateEmbeddingRequestAttributes extracts embedding request attributes.
func PopulateEmbeddingRequestAttributes(req *schemas.BifrostEmbeddingRequest, attrs map[string]any) {
if req == nil {
return
}
if req.Params != nil {
if req.Params.Dimensions != nil {
attrs[schemas.AttrDimensions] = *req.Params.Dimensions
}
if req.Params.EncodingFormat != nil {
attrs[schemas.AttrEncodingFormat] = *req.Params.EncodingFormat
}
// ExtraParams
for k, v := range req.Params.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// Extract input
if req.Input != nil {
if req.Input.Text != nil {
attrs[schemas.AttrInputText] = *req.Input.Text
} else if req.Input.Texts != nil {
attrs[schemas.AttrInputText] = strings.Join(req.Input.Texts, ",")
} else if req.Input.Embedding != nil {
embedding := make([]string, len(req.Input.Embedding))
for i, v := range req.Input.Embedding {
// Use a floatsafe representation; adjust precision as needed.
embedding[i] = fmt.Sprintf("%v", v)
}
attrs[schemas.AttrInputEmbedding] = strings.Join(embedding, ",")
}
}
}
// PopulateEmbeddingResponseAttributes extracts embedding response attributes.
func PopulateEmbeddingResponseAttributes(resp *schemas.BifrostEmbeddingResponse, attrs map[string]any) {
if resp == nil {
return
}
// Usage
if resp.Usage != nil {
attrs[schemas.AttrPromptTokens] = resp.Usage.PromptTokens
attrs[schemas.AttrCompletionTokens] = resp.Usage.CompletionTokens
attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens
}
}
// ===============================================
// Transcription Request/Response
// ===============================================
// PopulateTranscriptionRequestAttributes extracts transcription request attributes.
func PopulateTranscriptionRequestAttributes(req *schemas.BifrostTranscriptionRequest, attrs map[string]any) {
if req == nil || req.Params == nil {
return
}
if req.Params.Language != nil {
attrs[schemas.AttrLanguage] = *req.Params.Language
}
if req.Params.Prompt != nil {
attrs[schemas.AttrPrompt] = *req.Params.Prompt
}
if req.Params.ResponseFormat != nil {
attrs[schemas.AttrResponseFormat] = *req.Params.ResponseFormat
}
if req.Params.Format != nil {
attrs[schemas.AttrFormat] = *req.Params.Format
}
}
// PopulateTranscriptionResponseAttributes extracts transcription response attributes.
func PopulateTranscriptionResponseAttributes(resp *schemas.BifrostTranscriptionResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrOutputMessages] = resp.Text
// Usage
if resp.Usage != nil {
if resp.Usage.InputTokens != nil {
attrs[schemas.AttrInputTokens] = *resp.Usage.InputTokens
}
if resp.Usage.OutputTokens != nil {
attrs[schemas.AttrOutputTokens] = *resp.Usage.OutputTokens
}
if resp.Usage.TotalTokens != nil {
attrs[schemas.AttrTotalTokens] = *resp.Usage.TotalTokens
}
if resp.Usage.InputTokenDetails != nil {
attrs[schemas.AttrInputTokenDetailsText] = resp.Usage.InputTokenDetails.TextTokens
attrs[schemas.AttrInputTokenDetailsAudio] = resp.Usage.InputTokenDetails.AudioTokens
}
}
}
// ===============================================
// Speech Request/Response
// ===============================================
// PopulateSpeechRequestAttributes extracts speech request attributes.
func PopulateSpeechRequestAttributes(req *schemas.BifrostSpeechRequest, attrs map[string]any) {
if req == nil {
return
}
if req.Params != nil {
if req.Params.VoiceConfig != nil {
if req.Params.VoiceConfig.Voice != nil {
attrs[schemas.AttrVoice] = *req.Params.VoiceConfig.Voice
}
if len(req.Params.VoiceConfig.MultiVoiceConfig) > 0 {
voices := make([]string, len(req.Params.VoiceConfig.MultiVoiceConfig))
for i, vc := range req.Params.VoiceConfig.MultiVoiceConfig {
voices[i] = vc.Voice
}
attrs[schemas.AttrMultiVoiceConfig] = strings.Join(voices, ",")
}
}
if req.Params.Instructions != "" {
attrs[schemas.AttrInstructions] = req.Params.Instructions
}
if req.Params.ResponseFormat != "" {
attrs[schemas.AttrResponseFormat] = req.Params.ResponseFormat
}
if req.Params.Speed != nil {
attrs[schemas.AttrSpeed] = *req.Params.Speed
}
}
if req.Input != nil && req.Input.Input != "" {
attrs[schemas.AttrInputSpeech] = req.Input.Input
}
}
// PopulateSpeechResponseAttributes extracts speech response attributes.
func PopulateSpeechResponseAttributes(resp *schemas.BifrostSpeechResponse, attrs map[string]any) {
if resp == nil {
return
}
// Usage
if resp.Usage != nil {
attrs[schemas.AttrInputTokens] = resp.Usage.InputTokens
attrs[schemas.AttrOutputTokens] = resp.Usage.OutputTokens
attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens
}
}
// ===============================================
// Responses API Request/Response
// ===============================================
// PopulateResponsesRequestAttributes extracts responses API request attributes.
func PopulateResponsesRequestAttributes(req *schemas.BifrostResponsesRequest, attrs map[string]any) {
if req == nil || req.Params == nil {
return
}
if req.Input != nil {
attrs[schemas.AttrMessageCount] = len(req.Input)
inputMessages := extractResponsesInputMessages(req.Input)
if len(inputMessages) > 0 {
attrs[schemas.AttrInputMessages] = inputMessages
}
}
if req.Params.ParallelToolCalls != nil {
attrs[schemas.AttrParallelToolCall] = *req.Params.ParallelToolCalls
}
if req.Params.PromptCacheKey != nil {
attrs[schemas.AttrPromptCacheKey] = *req.Params.PromptCacheKey
}
if req.Params.Reasoning != nil {
if req.Params.Reasoning.Effort != nil {
attrs[schemas.AttrReasoningEffort] = *req.Params.Reasoning.Effort
}
if req.Params.Reasoning.Summary != nil {
attrs[schemas.AttrReasoningSummary] = *req.Params.Reasoning.Summary
}
if req.Params.Reasoning.GenerateSummary != nil {
attrs[schemas.AttrReasoningGenSummary] = *req.Params.Reasoning.GenerateSummary
}
}
if req.Params.SafetyIdentifier != nil {
attrs[schemas.AttrSafetyIdentifier] = *req.Params.SafetyIdentifier
}
if req.Params.ServiceTier != nil {
attrs[schemas.AttrServiceTier] = *req.Params.ServiceTier
}
if req.Params.Store != nil {
attrs[schemas.AttrStore] = *req.Params.Store
}
if req.Params.Temperature != nil {
attrs[schemas.AttrTemperature] = *req.Params.Temperature
}
if req.Params.Text != nil {
if req.Params.Text.Verbosity != nil {
attrs[schemas.AttrTextVerbosity] = *req.Params.Text.Verbosity
}
if req.Params.Text.Format != nil {
attrs[schemas.AttrTextFormatType] = req.Params.Text.Format.Type
}
}
if req.Params.TopLogProbs != nil {
attrs[schemas.AttrTopLogProbs] = *req.Params.TopLogProbs
}
if req.Params.TopP != nil {
attrs[schemas.AttrTopP] = *req.Params.TopP
}
if req.Params.ToolChoice != nil {
if req.Params.ToolChoice.ResponsesToolChoiceStr != nil && *req.Params.ToolChoice.ResponsesToolChoiceStr != "" {
attrs[schemas.AttrToolChoiceType] = *req.Params.ToolChoice.ResponsesToolChoiceStr
}
if req.Params.ToolChoice.ResponsesToolChoiceStruct != nil && req.Params.ToolChoice.ResponsesToolChoiceStruct.Name != nil {
attrs[schemas.AttrToolChoiceName] = *req.Params.ToolChoice.ResponsesToolChoiceStruct.Name
}
}
if req.Params.Tools != nil {
tools := make([]string, len(req.Params.Tools))
for i, tool := range req.Params.Tools {
tools[i] = string(tool.Type)
}
attrs[schemas.AttrTools] = strings.Join(tools, ",")
}
if req.Params.Truncation != nil {
attrs[schemas.AttrTruncation] = *req.Params.Truncation
}
// ExtraParams
for k, v := range req.Params.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateResponsesResponseAttributes extracts responses API response attributes.
func PopulateResponsesResponseAttributes(resp *schemas.BifrostResponsesResponse, attrs map[string]any) {
if resp == nil {
return
}
if resp.ID != nil && *resp.ID != "" {
attrs[schemas.AttrResponseID] = *resp.ID
}
if resp.Model != "" {
attrs[schemas.AttrResponseModel] = resp.Model
}
if resp.ServiceTier != nil {
attrs[schemas.AttrServiceTier] = *resp.ServiceTier
}
// Extract output messages (includes reasoning)
outputMessages := extractResponsesOutputMessages(resp)
if len(outputMessages) > 0 {
attrs[schemas.AttrOutputMessages] = outputMessages
}
// Additional response fields
if resp.Include != nil {
attrs[schemas.AttrRespInclude] = strings.Join(resp.Include, ",")
}
if resp.MaxOutputTokens != nil {
attrs[schemas.AttrRespMaxOutputTokens] = *resp.MaxOutputTokens
}
if resp.MaxToolCalls != nil {
attrs[schemas.AttrRespMaxToolCalls] = *resp.MaxToolCalls
}
if resp.Metadata != nil {
attrs[schemas.AttrRespMetadata] = fmt.Sprintf("%v", resp.Metadata)
}
if resp.PreviousResponseID != nil {
attrs[schemas.AttrRespPreviousRespID] = *resp.PreviousResponseID
}
if resp.PromptCacheKey != nil {
attrs[schemas.AttrRespPromptCacheKey] = *resp.PromptCacheKey
}
if resp.Reasoning != nil {
if resp.Reasoning.Summary != nil {
attrs[schemas.AttrRespReasoningText] = *resp.Reasoning.Summary
}
if resp.Reasoning.Effort != nil {
attrs[schemas.AttrRespReasoningEffort] = *resp.Reasoning.Effort
}
if resp.Reasoning.GenerateSummary != nil {
attrs[schemas.AttrRespReasoningGenSum] = *resp.Reasoning.GenerateSummary
}
}
if resp.SafetyIdentifier != nil {
attrs[schemas.AttrRespSafetyIdentifier] = *resp.SafetyIdentifier
}
if resp.Store != nil {
attrs[schemas.AttrRespStore] = *resp.Store
}
if resp.Temperature != nil {
attrs[schemas.AttrRespTemperature] = *resp.Temperature
}
if resp.Text != nil {
if resp.Text.Verbosity != nil {
attrs[schemas.AttrRespTextVerbosity] = *resp.Text.Verbosity
}
if resp.Text.Format != nil {
attrs[schemas.AttrRespTextFormatType] = resp.Text.Format.Type
}
}
if resp.TopLogProbs != nil {
attrs[schemas.AttrRespTopLogProbs] = *resp.TopLogProbs
}
if resp.TopP != nil {
attrs[schemas.AttrRespTopP] = *resp.TopP
}
if resp.ToolChoice != nil {
if resp.ToolChoice.ResponsesToolChoiceStr != nil {
attrs[schemas.AttrRespToolChoiceType] = *resp.ToolChoice.ResponsesToolChoiceStr
}
if resp.ToolChoice.ResponsesToolChoiceStruct != nil && resp.ToolChoice.ResponsesToolChoiceStruct.Name != nil {
attrs[schemas.AttrRespToolChoiceName] = *resp.ToolChoice.ResponsesToolChoiceStruct.Name
}
}
if resp.Truncation != nil {
attrs[schemas.AttrRespTruncation] = *resp.Truncation
}
if resp.Tools != nil {
tools := make([]string, len(resp.Tools))
for i, tool := range resp.Tools {
tools[i] = string(tool.Type)
}
attrs[schemas.AttrRespTools] = strings.Join(tools, ",")
}
// Usage
if resp.Usage != nil {
attrs[schemas.AttrInputTokens] = resp.Usage.InputTokens
attrs[schemas.AttrOutputTokens] = resp.Usage.OutputTokens
attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens
}
}
// ===============================================
// Batch Operations Request/Response
// ===============================================
// PopulateBatchCreateRequestAttributes extracts batch create request attributes.
func PopulateBatchCreateRequestAttributes(req *schemas.BifrostBatchCreateRequest, attrs map[string]any) {
if req == nil {
return
}
if req.InputFileID != "" {
attrs[schemas.AttrBatchInputFileID] = req.InputFileID
}
if req.Endpoint != "" {
attrs[schemas.AttrBatchEndpoint] = string(req.Endpoint)
}
if req.CompletionWindow != "" {
attrs[schemas.AttrBatchCompletionWin] = req.CompletionWindow
}
if len(req.Requests) > 0 {
attrs[schemas.AttrBatchRequestsCount] = len(req.Requests)
}
if len(req.Metadata) > 0 {
attrs[schemas.AttrBatchMetadata] = fmt.Sprintf("%v", req.Metadata)
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateBatchListRequestAttributes extracts batch list request attributes.
func PopulateBatchListRequestAttributes(req *schemas.BifrostBatchListRequest, attrs map[string]any) {
if req == nil {
return
}
if req.Limit > 0 {
attrs[schemas.AttrBatchLimit] = req.Limit
}
if req.After != nil {
attrs[schemas.AttrBatchAfter] = *req.After
}
if req.BeforeID != nil {
attrs[schemas.AttrBatchBeforeID] = *req.BeforeID
}
if req.AfterID != nil {
attrs[schemas.AttrBatchAfterID] = *req.AfterID
}
if req.PageToken != nil {
attrs[schemas.AttrBatchPageToken] = *req.PageToken
}
if req.PageSize > 0 {
attrs[schemas.AttrBatchPageSize] = req.PageSize
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateBatchRetrieveRequestAttributes extracts batch retrieve request attributes.
func PopulateBatchRetrieveRequestAttributes(req *schemas.BifrostBatchRetrieveRequest, attrs map[string]any) {
if req == nil {
return
}
if req.BatchID != "" {
attrs[schemas.AttrBatchID] = req.BatchID
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateBatchCancelRequestAttributes extracts batch cancel request attributes.
func PopulateBatchCancelRequestAttributes(req *schemas.BifrostBatchCancelRequest, attrs map[string]any) {
if req == nil {
return
}
if req.BatchID != "" {
attrs[schemas.AttrBatchID] = req.BatchID
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateBatchResultsRequestAttributes extracts batch results request attributes.
func PopulateBatchResultsRequestAttributes(req *schemas.BifrostBatchResultsRequest, attrs map[string]any) {
if req == nil {
return
}
if req.BatchID != "" {
attrs[schemas.AttrBatchID] = req.BatchID
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateBatchCreateResponseAttributes extracts batch create response attributes.
func PopulateBatchCreateResponseAttributes(resp *schemas.BifrostBatchCreateResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrBatchID] = resp.ID
attrs[schemas.AttrBatchStatus] = string(resp.Status)
if resp.Object != "" {
attrs[schemas.AttrBatchObject] = resp.Object
}
if resp.Endpoint != "" {
attrs[schemas.AttrBatchEndpoint] = resp.Endpoint
}
if resp.InputFileID != "" {
attrs[schemas.AttrBatchInputFileID] = resp.InputFileID
}
if resp.CompletionWindow != "" {
attrs[schemas.AttrBatchCompletionWin] = resp.CompletionWindow
}
if resp.CreatedAt != 0 {
attrs[schemas.AttrBatchCreatedAt] = resp.CreatedAt
}
if resp.ExpiresAt != nil {
attrs[schemas.AttrBatchExpiresAt] = *resp.ExpiresAt
}
if resp.OutputFileID != nil {
attrs[schemas.AttrBatchOutputFileID] = *resp.OutputFileID
}
if resp.ErrorFileID != nil {
attrs[schemas.AttrBatchErrorFileID] = *resp.ErrorFileID
}
attrs[schemas.AttrBatchCountTotal] = resp.RequestCounts.Total
attrs[schemas.AttrBatchCountCompleted] = resp.RequestCounts.Completed
attrs[schemas.AttrBatchCountFailed] = resp.RequestCounts.Failed
}
// PopulateBatchListResponseAttributes extracts batch list response attributes.
func PopulateBatchListResponseAttributes(resp *schemas.BifrostBatchListResponse, attrs map[string]any) {
if resp == nil {
return
}
if resp.Object != "" {
attrs[schemas.AttrBatchObject] = resp.Object
}
attrs[schemas.AttrBatchDataCount] = len(resp.Data)
attrs[schemas.AttrBatchHasMore] = resp.HasMore
if resp.FirstID != nil {
attrs[schemas.AttrBatchFirstID] = *resp.FirstID
}
if resp.LastID != nil {
attrs[schemas.AttrBatchLastID] = *resp.LastID
}
}
// PopulateBatchRetrieveResponseAttributes extracts batch retrieve response attributes.
func PopulateBatchRetrieveResponseAttributes(resp *schemas.BifrostBatchRetrieveResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrBatchID] = resp.ID
attrs[schemas.AttrBatchStatus] = string(resp.Status)
if resp.Object != "" {
attrs[schemas.AttrBatchObject] = resp.Object
}
if resp.Endpoint != "" {
attrs[schemas.AttrBatchEndpoint] = resp.Endpoint
}
if resp.InputFileID != "" {
attrs[schemas.AttrBatchInputFileID] = resp.InputFileID
}
if resp.CompletionWindow != "" {
attrs[schemas.AttrBatchCompletionWin] = resp.CompletionWindow
}
if resp.CreatedAt != 0 {
attrs[schemas.AttrBatchCreatedAt] = resp.CreatedAt
}
if resp.ExpiresAt != nil {
attrs[schemas.AttrBatchExpiresAt] = *resp.ExpiresAt
}
if resp.InProgressAt != nil {
attrs[schemas.AttrBatchInProgressAt] = *resp.InProgressAt
}
if resp.FinalizingAt != nil {
attrs[schemas.AttrBatchFinalizingAt] = *resp.FinalizingAt
}
if resp.CompletedAt != nil {
attrs[schemas.AttrBatchCompletedAt] = *resp.CompletedAt
}
if resp.FailedAt != nil {
attrs[schemas.AttrBatchFailedAt] = *resp.FailedAt
}
if resp.ExpiredAt != nil {
attrs[schemas.AttrBatchExpiredAt] = *resp.ExpiredAt
}
if resp.CancellingAt != nil {
attrs[schemas.AttrBatchCancellingAt] = *resp.CancellingAt
}
if resp.CancelledAt != nil {
attrs[schemas.AttrBatchCancelledAt] = *resp.CancelledAt
}
if resp.OutputFileID != nil {
attrs[schemas.AttrBatchOutputFileID] = *resp.OutputFileID
}
if resp.ErrorFileID != nil {
attrs[schemas.AttrBatchErrorFileID] = *resp.ErrorFileID
}
attrs[schemas.AttrBatchCountTotal] = resp.RequestCounts.Total
attrs[schemas.AttrBatchCountCompleted] = resp.RequestCounts.Completed
attrs[schemas.AttrBatchCountFailed] = resp.RequestCounts.Failed
}
// PopulateBatchCancelResponseAttributes extracts batch cancel response attributes.
func PopulateBatchCancelResponseAttributes(resp *schemas.BifrostBatchCancelResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrBatchID] = resp.ID
attrs[schemas.AttrBatchStatus] = string(resp.Status)
if resp.Object != "" {
attrs[schemas.AttrBatchObject] = resp.Object
}
if resp.CancellingAt != nil {
attrs[schemas.AttrBatchCancellingAt] = *resp.CancellingAt
}
if resp.CancelledAt != nil {
attrs[schemas.AttrBatchCancelledAt] = *resp.CancelledAt
}
attrs[schemas.AttrBatchCountTotal] = resp.RequestCounts.Total
attrs[schemas.AttrBatchCountCompleted] = resp.RequestCounts.Completed
attrs[schemas.AttrBatchCountFailed] = resp.RequestCounts.Failed
}
// PopulateBatchResultsResponseAttributes extracts batch results response attributes.
func PopulateBatchResultsResponseAttributes(resp *schemas.BifrostBatchResultsResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrBatchID] = resp.BatchID
attrs[schemas.AttrBatchResultsCount] = len(resp.Results)
attrs[schemas.AttrBatchHasMore] = resp.HasMore
if resp.NextCursor != nil {
attrs[schemas.AttrBatchNextCursor] = *resp.NextCursor
}
}
// ===============================================
// File Operations Request/Response
// ===============================================
// PopulateFileUploadRequestAttributes extracts file upload request attributes.
func PopulateFileUploadRequestAttributes(req *schemas.BifrostFileUploadRequest, attrs map[string]any) {
if req == nil {
return
}
if req.Filename != "" {
attrs[schemas.AttrFileFilename] = req.Filename
}
if req.Purpose != "" {
attrs[schemas.AttrFilePurpose] = string(req.Purpose)
}
if len(req.File) > 0 {
attrs[schemas.AttrFileBytes] = len(req.File)
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateFileListRequestAttributes extracts file list request attributes.
func PopulateFileListRequestAttributes(req *schemas.BifrostFileListRequest, attrs map[string]any) {
if req == nil {
return
}
if req.Purpose != "" {
attrs[schemas.AttrFilePurpose] = string(req.Purpose)
}
if req.Limit > 0 {
attrs[schemas.AttrFileLimit] = req.Limit
}
if req.After != nil {
attrs[schemas.AttrFileAfter] = *req.After
}
if req.Order != nil {
attrs[schemas.AttrFileOrder] = *req.Order
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateFileRetrieveRequestAttributes extracts file retrieve request attributes.
func PopulateFileRetrieveRequestAttributes(req *schemas.BifrostFileRetrieveRequest, attrs map[string]any) {
if req == nil {
return
}
if req.FileID != "" {
attrs[schemas.AttrFileID] = req.FileID
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateFileDeleteRequestAttributes extracts file delete request attributes.
func PopulateFileDeleteRequestAttributes(req *schemas.BifrostFileDeleteRequest, attrs map[string]any) {
if req == nil {
return
}
if req.FileID != "" {
attrs[schemas.AttrFileID] = req.FileID
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateFileContentRequestAttributes extracts file content request attributes.
func PopulateFileContentRequestAttributes(req *schemas.BifrostFileContentRequest, attrs map[string]any) {
if req == nil {
return
}
if req.FileID != "" {
attrs[schemas.AttrFileID] = req.FileID
}
// ExtraParams
for k, v := range req.ExtraParams {
attrs[k] = fmt.Sprintf("%v", v)
}
}
// PopulateFileUploadResponseAttributes extracts file upload response attributes.
func PopulateFileUploadResponseAttributes(resp *schemas.BifrostFileUploadResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrFileID] = resp.ID
if resp.Object != "" {
attrs[schemas.AttrFileObject] = resp.Object
}
attrs[schemas.AttrFileBytes] = resp.Bytes
attrs[schemas.AttrFileCreatedAt] = resp.CreatedAt
attrs[schemas.AttrFileFilename] = resp.Filename
attrs[schemas.AttrFilePurpose] = string(resp.Purpose)
if resp.Status != "" {
attrs[schemas.AttrFileStatus] = string(resp.Status)
}
if resp.StorageBackend != "" {
attrs[schemas.AttrFileStorageBackend] = string(resp.StorageBackend)
}
}
// PopulateFileListResponseAttributes extracts file list response attributes.
func PopulateFileListResponseAttributes(resp *schemas.BifrostFileListResponse, attrs map[string]any) {
if resp == nil {
return
}
if resp.Object != "" {
attrs[schemas.AttrFileObject] = resp.Object
}
attrs[schemas.AttrFileDataCount] = len(resp.Data)
attrs[schemas.AttrFileHasMore] = resp.HasMore
}
// PopulateFileRetrieveResponseAttributes extracts file retrieve response attributes.
func PopulateFileRetrieveResponseAttributes(resp *schemas.BifrostFileRetrieveResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrFileID] = resp.ID
if resp.Object != "" {
attrs[schemas.AttrFileObject] = resp.Object
}
attrs[schemas.AttrFileBytes] = resp.Bytes
attrs[schemas.AttrFileCreatedAt] = resp.CreatedAt
attrs[schemas.AttrFileFilename] = resp.Filename
attrs[schemas.AttrFilePurpose] = string(resp.Purpose)
if resp.Status != "" {
attrs[schemas.AttrFileStatus] = string(resp.Status)
}
if resp.StorageBackend != "" {
attrs[schemas.AttrFileStorageBackend] = string(resp.StorageBackend)
}
}
// PopulateFileDeleteResponseAttributes extracts file delete response attributes.
func PopulateFileDeleteResponseAttributes(resp *schemas.BifrostFileDeleteResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrFileID] = resp.ID
if resp.Object != "" {
attrs[schemas.AttrFileObject] = resp.Object
}
attrs[schemas.AttrFileDeleted] = resp.Deleted
}
// PopulateFileContentResponseAttributes extracts file content response attributes.
func PopulateFileContentResponseAttributes(resp *schemas.BifrostFileContentResponse, attrs map[string]any) {
if resp == nil {
return
}
attrs[schemas.AttrFileID] = resp.FileID
if resp.ContentType != "" {
attrs[schemas.AttrFileContentType] = resp.ContentType
}
if len(resp.Content) > 0 {
attrs[schemas.AttrFileContentBytes] = len(resp.Content)
}
}
// ===============================================
// Helper functions for extracting messages
// ===============================================
// MessageSummary represents a summarized chat message for tracing
type MessageSummary struct {
Role string `json:"role"`
Content string `json:"content"`
ToolCalls []ToolCallSummary `json:"tool_calls,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
ReasoningDetails []ReasoningDetailSummary `json:"reasoning_details,omitempty"`
Audio *AudioSummary `json:"audio,omitempty"`
Refusal string `json:"refusal,omitempty"`
}
// ToolCallSummary represents a summarized tool call for tracing
type ToolCallSummary struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Args string `json:"args,omitempty"`
}
// ReasoningDetailSummary represents a summarized reasoning detail for tracing
type ReasoningDetailSummary struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
}
// AudioSummary represents summarized audio data for tracing
type AudioSummary struct {
ID string `json:"id,omitempty"`
Transcript string `json:"transcript,omitempty"`
}
// extractChatMessages extracts chat messages into a slice of MessageSummary
func extractChatMessages(messages []schemas.ChatMessage) []MessageSummary {
result := make([]MessageSummary, 0, len(messages))
for _, msg := range messages {
summary := extractMessageSummary(&msg)
result = append(result, summary)
}
return result
}
// extractChatResponseMessages extracts output messages from chat response
func extractChatResponseMessages(resp *schemas.BifrostChatResponse) []MessageSummary {
if resp == nil {
return nil
}
result := make([]MessageSummary, 0, len(resp.Choices))
for _, choice := range resp.Choices {
if choice.ChatNonStreamResponseChoice == nil || choice.ChatNonStreamResponseChoice.Message == nil {
continue
}
msg := choice.ChatNonStreamResponseChoice.Message
summary := extractMessageSummary(msg)
result = append(result, summary)
}
return result
}
// extractMessageSummary extracts a full MessageSummary from a ChatMessage
func extractMessageSummary(msg *schemas.ChatMessage) MessageSummary {
if msg == nil {
return MessageSummary{}
}
summary := MessageSummary{
Role: string(schemas.ChatMessageRoleAssistant),
Content: extractMessageContent(msg.Content),
}
if msg.Role != "" {
summary.Role = string(msg.Role)
}
// Extract assistant-specific fields
if msg.ChatAssistantMessage != nil {
am := msg.ChatAssistantMessage
// Extract refusal
if am.Refusal != nil && *am.Refusal != "" {
summary.Refusal = *am.Refusal
}
// Extract reasoning
if am.Reasoning != nil && *am.Reasoning != "" {
summary.Reasoning = *am.Reasoning
}
// Extract reasoning details
if len(am.ReasoningDetails) > 0 {
summary.ReasoningDetails = make([]ReasoningDetailSummary, 0, len(am.ReasoningDetails))
for _, rd := range am.ReasoningDetails {
detail := ReasoningDetailSummary{
Type: string(rd.Type),
}
if rd.Text != nil {
detail.Text = *rd.Text
}
summary.ReasoningDetails = append(summary.ReasoningDetails, detail)
}
}
// Extract audio
if am.Audio != nil {
summary.Audio = &AudioSummary{
ID: am.Audio.ID,
Transcript: am.Audio.Transcript,
}
}
// Extract tool calls
if len(am.ToolCalls) > 0 {
summary.ToolCalls = make([]ToolCallSummary, 0, len(am.ToolCalls))
for _, tc := range am.ToolCalls {
toolCall := ToolCallSummary{
Type: "function",
}
if tc.ID != nil {
toolCall.ID = *tc.ID
}
if tc.Type != nil {
toolCall.Type = *tc.Type
}
if tc.Function.Name != nil {
toolCall.Name = *tc.Function.Name
}
toolCall.Args = tc.Function.Arguments
summary.ToolCalls = append(summary.ToolCalls, toolCall)
}
}
}
return summary
}
// ResponsesMessageSummary extends MessageSummary with reasoning
type ResponsesMessageSummary struct {
Role string `json:"role"`
Content string `json:"content"`
Reasoning string `json:"reasoning,omitempty"`
ToolCalls []ToolCallSummary `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}
// extractResponsesOutputMessages extracts output messages from a Responses API response.
func extractResponsesOutputMessages(resp *schemas.BifrostResponsesResponse) []ResponsesMessageSummary {
if resp == nil {
return nil
}
result := make([]ResponsesMessageSummary, 0, len(resp.Output))
for _, msg := range resp.Output {
msgType := schemas.ResponsesMessageTypeMessage
if msg.Type != nil {
msgType = *msg.Type
}
switch msgType {
case schemas.ResponsesMessageTypeMessage:
if msg.Role == nil {
continue
}
result = append(result, ResponsesMessageSummary{
Role: string(*msg.Role),
Content: extractResponsesMessageTextContent(&msg),
Reasoning: extractResponsesReasoning(msg.ResponsesReasoning),
})
case schemas.ResponsesMessageTypeReasoning:
reasoning := extractResponsesReasoning(msg.ResponsesReasoning)
if reasoning == "" {
continue
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Reasoning: reasoning,
})
case schemas.ResponsesMessageTypeFunctionCall:
if msg.ResponsesToolMessage == nil || msg.ResponsesToolMessage.Name == nil {
continue
}
tc := ToolCallSummary{
Type: "function",
Name: *msg.ResponsesToolMessage.Name,
}
if msg.ID != nil {
tc.ID = *msg.ID
}
if msg.ResponsesToolMessage.Arguments != nil {
tc.Args = *msg.ResponsesToolMessage.Arguments
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
ToolCalls: []ToolCallSummary{tc},
})
case schemas.ResponsesMessageTypeMCPCall:
if msg.ResponsesToolMessage == nil || msg.ResponsesToolMessage.Name == nil {
continue
}
tc := ToolCallSummary{
Type: "mcp",
Name: *msg.ResponsesToolMessage.Name,
}
if msg.ID != nil {
tc.ID = *msg.ID
}
if msg.ResponsesToolMessage.Arguments != nil {
tc.Args = *msg.ResponsesToolMessage.Arguments
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
ToolCalls: []ToolCallSummary{tc},
})
case schemas.ResponsesMessageTypeWebSearchCall:
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Content: "[web_search_call]",
})
case schemas.ResponsesMessageTypeComputerCall:
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Content: "[computer_call]",
})
case schemas.ResponsesMessageTypeFileSearchCall,
schemas.ResponsesMessageTypeCodeInterpreterCall,
schemas.ResponsesMessageTypeLocalShellCall,
schemas.ResponsesMessageTypeCustomToolCall,
schemas.ResponsesMessageTypeImageGenerationCall:
name := ""
if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Name != nil {
name = *msg.ResponsesToolMessage.Name
}
content := "[" + string(msgType) + "]"
if name != "" {
content += " " + name
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Content: content,
})
default:
continue
}
}
return result
}
// extractResponsesInputMessages extracts input messages from a Responses API request.
func extractResponsesInputMessages(messages []schemas.ResponsesMessage) []ResponsesMessageSummary {
if len(messages) == 0 {
return nil
}
result := make([]ResponsesMessageSummary, 0, len(messages))
for _, msg := range messages {
// Nil Type is treated as "message", consistent with the provider converters.
msgType := schemas.ResponsesMessageTypeMessage
if msg.Type != nil {
msgType = *msg.Type
}
switch msgType {
case schemas.ResponsesMessageTypeMessage:
role := ""
if msg.Role != nil {
role = string(*msg.Role)
}
summary := ResponsesMessageSummary{
Role: role,
Content: extractResponsesMessageTextContent(&msg),
Reasoning: extractResponsesReasoning(msg.ResponsesReasoning),
}
result = append(result, summary)
case schemas.ResponsesMessageTypeReasoning:
reasoning := extractResponsesReasoning(msg.ResponsesReasoning)
if reasoning == "" {
continue
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Reasoning: reasoning,
})
case schemas.ResponsesMessageTypeFunctionCall:
if msg.ResponsesToolMessage == nil || msg.ResponsesToolMessage.Name == nil {
continue
}
tc := ToolCallSummary{
Type: "function",
Name: *msg.ResponsesToolMessage.Name,
}
if msg.ID != nil {
tc.ID = *msg.ID
}
if msg.ResponsesToolMessage.Arguments != nil {
tc.Args = *msg.ResponsesToolMessage.Arguments
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
ToolCalls: []ToolCallSummary{tc},
})
case schemas.ResponsesMessageTypeFunctionCallOutput:
if msg.ResponsesToolMessage == nil {
continue
}
summary := ResponsesMessageSummary{
Role: "tool",
Content: extractResponsesToolOutputContent(msg.ResponsesToolMessage.Output),
}
if msg.ResponsesToolMessage.CallID != nil {
summary.ToolCallID = *msg.ResponsesToolMessage.CallID
}
result = append(result, summary)
case schemas.ResponsesMessageTypeMCPCall:
if msg.ResponsesToolMessage == nil {
continue
}
if msg.ResponsesToolMessage.Name != nil {
// Outbound MCP tool call (assistant side)
tc := ToolCallSummary{
Type: "mcp",
Name: *msg.ResponsesToolMessage.Name,
}
if msg.ID != nil {
tc.ID = *msg.ID
}
if msg.ResponsesToolMessage.Arguments != nil {
tc.Args = *msg.ResponsesToolMessage.Arguments
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
ToolCalls: []ToolCallSummary{tc},
})
} else if msg.ResponsesToolMessage.CallID != nil {
// Inbound MCP tool result (user side)
result = append(result, ResponsesMessageSummary{
Role: "tool",
ToolCallID: *msg.ResponsesToolMessage.CallID,
Content: extractResponsesToolOutputContent(msg.ResponsesToolMessage.Output),
})
}
case schemas.ResponsesMessageTypeMCPApprovalRequest:
content := "[mcp_approval_request]"
if msg.ResponsesToolMessage != nil &&
msg.ResponsesToolMessage.Action != nil &&
msg.ResponsesToolMessage.Action.ResponsesMCPApprovalRequestAction != nil {
content = msg.ResponsesToolMessage.Action.ResponsesMCPApprovalRequestAction.Name
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Content: content,
})
case schemas.ResponsesMessageTypeWebSearchCall:
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Content: "[web_search_call]",
})
case schemas.ResponsesMessageTypeComputerCall:
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Content: "[computer_call]",
})
case schemas.ResponsesMessageTypeComputerCallOutput:
result = append(result, ResponsesMessageSummary{
Role: "tool",
Content: "[computer_call_output]",
})
case schemas.ResponsesMessageTypeFileSearchCall,
schemas.ResponsesMessageTypeCodeInterpreterCall,
schemas.ResponsesMessageTypeLocalShellCall,
schemas.ResponsesMessageTypeCustomToolCall,
schemas.ResponsesMessageTypeImageGenerationCall:
name := ""
if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Name != nil {
name = *msg.ResponsesToolMessage.Name
}
content := "[" + string(msgType) + "]"
if name != "" {
content += " " + name
}
result = append(result, ResponsesMessageSummary{
Role: "assistant",
Content: content,
})
case schemas.ResponsesMessageTypeLocalShellCallOutput,
schemas.ResponsesMessageTypeCustomToolCallOutput:
content := ""
if msg.ResponsesToolMessage != nil {
content = extractResponsesToolOutputContent(msg.ResponsesToolMessage.Output)
}
summary := ResponsesMessageSummary{
Role: "tool",
Content: content,
}
if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.CallID != nil {
summary.ToolCallID = *msg.ResponsesToolMessage.CallID
}
result = append(result, summary)
default:
continue
}
}
return result
}
// extractResponsesMessageTextContent extracts plain text from a ResponsesMessage's Content field.
func extractResponsesMessageTextContent(msg *schemas.ResponsesMessage) string {
if msg.Content == nil {
return ""
}
if msg.Content.ContentStr != nil && *msg.Content.ContentStr != "" {
return *msg.Content.ContentStr
}
var sb strings.Builder
for _, block := range msg.Content.ContentBlocks {
if block.Text != nil {
sb.WriteString(*block.Text)
} else if block.ResponsesOutputMessageContentRefusal != nil && block.ResponsesOutputMessageContentRefusal.Refusal != "" {
sb.WriteString(block.ResponsesOutputMessageContentRefusal.Refusal)
}
}
return sb.String()
}
// extractResponsesToolOutputContent extracts text from a ResponsesToolMessageOutputStruct,
func extractResponsesToolOutputContent(output *schemas.ResponsesToolMessageOutputStruct) string {
if output == nil {
return ""
}
if output.ResponsesToolCallOutputStr != nil {
return *output.ResponsesToolCallOutputStr
}
var sb strings.Builder
for _, block := range output.ResponsesFunctionToolCallOutputBlocks {
if block.Text != nil {
sb.WriteString(*block.Text)
}
}
return sb.String()
}
// extractResponsesReasoning concatenates all summary text blocks from a ResponsesReasoning.
func extractResponsesReasoning(r *schemas.ResponsesReasoning) string {
if r == nil {
return ""
}
var sb strings.Builder
for _, block := range r.Summary {
if block.Text != "" {
sb.WriteString(block.Text)
}
}
return sb.String()
}
// extractMessageContent extracts text content from ChatMessageContent
func extractMessageContent(content *schemas.ChatMessageContent) string {
if content == nil {
return ""
}
if content.ContentStr != nil {
return *content.ContentStr
}
if content.ContentBlocks != nil {
var builder strings.Builder
for _, block := range content.ContentBlocks {
if block.Text != nil {
builder.WriteString(*block.Text)
}
}
return builder.String()
}
return ""
}