first commit
This commit is contained in:
337
core/providers/perplexity/chat.go
Normal file
337
core/providers/perplexity/chat.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package perplexity
|
||||
|
||||
import (
|
||||
schemas "github.com/maximhq/bifrost/core/schemas"
|
||||
)
|
||||
|
||||
// ToPerplexityChatCompletionRequest converts a Bifrost request to Perplexity chat completion request
|
||||
func ToPerplexityChatCompletionRequest(bifrostReq *schemas.BifrostChatRequest) *PerplexityChatRequest {
|
||||
if bifrostReq == nil || bifrostReq.Input == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
messages := bifrostReq.Input
|
||||
perplexityReq := &PerplexityChatRequest{
|
||||
Model: bifrostReq.Model,
|
||||
Messages: messages,
|
||||
}
|
||||
|
||||
// Map parameters if they exist
|
||||
if bifrostReq.Params != nil {
|
||||
// Core parameters
|
||||
perplexityReq.MaxTokens = bifrostReq.Params.MaxCompletionTokens
|
||||
perplexityReq.Temperature = bifrostReq.Params.Temperature
|
||||
perplexityReq.TopP = bifrostReq.Params.TopP
|
||||
perplexityReq.PresencePenalty = bifrostReq.Params.PresencePenalty
|
||||
perplexityReq.FrequencyPenalty = bifrostReq.Params.FrequencyPenalty
|
||||
perplexityReq.ResponseFormat = bifrostReq.Params.ResponseFormat
|
||||
|
||||
// Tool calling parameters
|
||||
perplexityReq.Tools = bifrostReq.Params.Tools
|
||||
perplexityReq.ToolChoice = bifrostReq.Params.ToolChoice
|
||||
perplexityReq.ParallelToolCalls = bifrostReq.Params.ParallelToolCalls
|
||||
|
||||
// Standard parameters
|
||||
perplexityReq.Stop = bifrostReq.Params.Stop
|
||||
perplexityReq.LogProbs = bifrostReq.Params.LogProbs
|
||||
perplexityReq.TopLogProbs = bifrostReq.Params.TopLogProbs
|
||||
|
||||
// Handle reasoning effort mapping
|
||||
if bifrostReq.Params.Reasoning != nil && bifrostReq.Params.Reasoning.Effort != nil {
|
||||
effort := *bifrostReq.Params.Reasoning.Effort
|
||||
switch effort {
|
||||
case "minimal":
|
||||
perplexityReq.ReasoningEffort = schemas.Ptr("low")
|
||||
case "xhigh", "max":
|
||||
perplexityReq.ReasoningEffort = schemas.Ptr("high")
|
||||
default:
|
||||
perplexityReq.ReasoningEffort = &effort
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra parameters for Perplexity-specific fields
|
||||
if bifrostReq.Params.ExtraParams != nil {
|
||||
perplexityReq.ExtraParams = bifrostReq.Params.ExtraParams
|
||||
// Search-related parameters
|
||||
if searchMode, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_mode"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "search_mode")
|
||||
perplexityReq.SearchMode = searchMode
|
||||
}
|
||||
|
||||
if languagePreference, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["language_preference"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "language_preference")
|
||||
perplexityReq.LanguagePreference = languagePreference
|
||||
}
|
||||
|
||||
if searchDomainFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["search_domain_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "search_domain_filter")
|
||||
perplexityReq.SearchDomainFilter = searchDomainFilter
|
||||
}
|
||||
|
||||
if returnImages, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["return_images"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "return_images")
|
||||
perplexityReq.ReturnImages = returnImages
|
||||
}
|
||||
|
||||
if returnRelatedQuestions, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["return_related_questions"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "return_related_questions")
|
||||
perplexityReq.ReturnRelatedQuestions = returnRelatedQuestions
|
||||
}
|
||||
|
||||
if searchRecencyFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_recency_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "search_recency_filter")
|
||||
perplexityReq.SearchRecencyFilter = searchRecencyFilter
|
||||
}
|
||||
|
||||
if searchAfterDateFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_after_date_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "search_after_date_filter")
|
||||
perplexityReq.SearchAfterDateFilter = searchAfterDateFilter
|
||||
}
|
||||
|
||||
if searchBeforeDateFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_before_date_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "search_before_date_filter")
|
||||
perplexityReq.SearchBeforeDateFilter = searchBeforeDateFilter
|
||||
}
|
||||
|
||||
if lastUpdatedAfterFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["last_updated_after_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "last_updated_after_filter")
|
||||
perplexityReq.LastUpdatedAfterFilter = lastUpdatedAfterFilter
|
||||
}
|
||||
|
||||
if lastUpdatedBeforeFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["last_updated_before_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "last_updated_before_filter")
|
||||
perplexityReq.LastUpdatedBeforeFilter = lastUpdatedBeforeFilter
|
||||
}
|
||||
|
||||
if topK, ok := schemas.SafeExtractIntPointer(bifrostReq.Params.ExtraParams["top_k"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "top_k")
|
||||
perplexityReq.TopK = topK
|
||||
}
|
||||
|
||||
if stream, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["stream"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "stream")
|
||||
perplexityReq.Stream = stream
|
||||
}
|
||||
|
||||
if disableSearch, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["disable_search"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "disable_search")
|
||||
perplexityReq.DisableSearch = disableSearch
|
||||
}
|
||||
|
||||
if enableSearchClassifier, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["enable_search_classifier"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "enable_search_classifier")
|
||||
perplexityReq.EnableSearchClassifier = enableSearchClassifier
|
||||
}
|
||||
|
||||
// Perplexity-specific request fields
|
||||
if numSearchResults, ok := schemas.SafeExtractIntPointer(bifrostReq.Params.ExtraParams["num_search_results"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "num_search_results")
|
||||
perplexityReq.NumSearchResults = numSearchResults
|
||||
}
|
||||
|
||||
if numImages, ok := schemas.SafeExtractIntPointer(bifrostReq.Params.ExtraParams["num_images"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "num_images")
|
||||
perplexityReq.NumImages = numImages
|
||||
}
|
||||
|
||||
if searchLanguageFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["search_language_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "search_language_filter")
|
||||
perplexityReq.SearchLanguageFilter = searchLanguageFilter
|
||||
}
|
||||
|
||||
if imageFormatFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["image_format_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "image_format_filter")
|
||||
perplexityReq.ImageFormatFilter = imageFormatFilter
|
||||
}
|
||||
|
||||
if imageDomainFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["image_domain_filter"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "image_domain_filter")
|
||||
perplexityReq.ImageDomainFilter = imageDomainFilter
|
||||
}
|
||||
|
||||
if safeSearch, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["safe_search"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "safe_search")
|
||||
perplexityReq.SafeSearch = safeSearch
|
||||
}
|
||||
|
||||
if streamMode, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["stream_mode"]); ok {
|
||||
delete(perplexityReq.ExtraParams, "stream_mode")
|
||||
perplexityReq.StreamMode = streamMode
|
||||
}
|
||||
|
||||
// Handle web_search_options
|
||||
if webSearchOptionsParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "web_search_options"); ok {
|
||||
if webSearchOptionsSlice, ok := webSearchOptionsParam.([]interface{}); ok {
|
||||
var webSearchOptions []WebSearchOption
|
||||
updatedWebSearchOptionsSlice := make([]interface{}, 0, len(webSearchOptionsSlice))
|
||||
for _, optionInterface := range webSearchOptionsSlice {
|
||||
if optionMap, ok := optionInterface.(map[string]interface{}); ok {
|
||||
option := WebSearchOption{}
|
||||
|
||||
if searchContextSize, ok := schemas.SafeExtractStringPointer(optionMap["search_context_size"]); ok {
|
||||
delete(optionMap, "search_context_size")
|
||||
option.SearchContextSize = searchContextSize
|
||||
}
|
||||
|
||||
if imageResultsEnhancedRelevance, ok := schemas.SafeExtractBoolPointer(optionMap["image_results_enhanced_relevance"]); ok {
|
||||
delete(optionMap, "image_results_enhanced_relevance")
|
||||
option.ImageResultsEnhancedRelevance = imageResultsEnhancedRelevance
|
||||
}
|
||||
|
||||
if searchType, ok := schemas.SafeExtractStringPointer(optionMap["search_type"]); ok {
|
||||
delete(optionMap, "search_type")
|
||||
option.SearchType = searchType
|
||||
}
|
||||
|
||||
// Handle user_location
|
||||
if userLocationParam, ok := schemas.SafeExtractFromMap(optionMap, "user_location"); ok {
|
||||
if userLocationMap, ok := userLocationParam.(map[string]interface{}); ok {
|
||||
userLocation := &WebSearchOptionUserLocation{}
|
||||
|
||||
if latitude, ok := schemas.SafeExtractFloat64Pointer(userLocationMap["latitude"]); ok {
|
||||
delete(userLocationMap, "latitude")
|
||||
userLocation.Latitude = latitude
|
||||
}
|
||||
if longitude, ok := schemas.SafeExtractFloat64Pointer(userLocationMap["longitude"]); ok {
|
||||
delete(userLocationMap, "longitude")
|
||||
userLocation.Longitude = longitude
|
||||
}
|
||||
if city, ok := schemas.SafeExtractStringPointer(userLocationMap["city"]); ok {
|
||||
delete(userLocationMap, "city")
|
||||
userLocation.City = city
|
||||
}
|
||||
if country, ok := schemas.SafeExtractStringPointer(userLocationMap["country"]); ok {
|
||||
delete(userLocationMap, "country")
|
||||
userLocation.Country = country
|
||||
}
|
||||
if region, ok := schemas.SafeExtractStringPointer(userLocationMap["region"]); ok {
|
||||
delete(userLocationMap, "region")
|
||||
userLocation.Region = region
|
||||
}
|
||||
if len(userLocationMap) == 0 {
|
||||
delete(optionMap, "user_location")
|
||||
} else {
|
||||
optionMap["user_location"] = userLocationMap
|
||||
}
|
||||
option.UserLocation = userLocation
|
||||
}
|
||||
}
|
||||
webSearchOptions = append(webSearchOptions, option)
|
||||
// Persist remaining custom fields from optionMap back to ExtraParams
|
||||
if len(optionMap) > 0 {
|
||||
updatedWebSearchOptionsSlice = append(updatedWebSearchOptionsSlice, optionMap)
|
||||
}
|
||||
} else {
|
||||
// Preserve non-map entries as-is
|
||||
updatedWebSearchOptionsSlice = append(updatedWebSearchOptionsSlice, optionInterface)
|
||||
}
|
||||
}
|
||||
perplexityReq.WebSearchOptions = webSearchOptions
|
||||
// Put remaining custom fields back into ExtraParams
|
||||
if len(updatedWebSearchOptionsSlice) > 0 {
|
||||
perplexityReq.ExtraParams["web_search_options"] = updatedWebSearchOptionsSlice
|
||||
} else {
|
||||
delete(perplexityReq.ExtraParams, "web_search_options")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle media_response
|
||||
if mediaResponseParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "media_response"); ok {
|
||||
if mediaResponseMap, ok := mediaResponseParam.(map[string]interface{}); ok {
|
||||
mediaResponse := &MediaResponse{}
|
||||
|
||||
if overridesParam, ok := schemas.SafeExtractFromMap(mediaResponseMap, "overrides"); ok {
|
||||
if overridesMap, ok := overridesParam.(map[string]interface{}); ok {
|
||||
overrides := MediaResponseOverrides{}
|
||||
|
||||
if returnVideos, ok := schemas.SafeExtractBoolPointer(overridesMap["return_videos"]); ok {
|
||||
delete(overridesMap, "return_videos")
|
||||
overrides.ReturnVideos = returnVideos
|
||||
}
|
||||
if returnImages, ok := schemas.SafeExtractBoolPointer(overridesMap["return_images"]); ok {
|
||||
delete(overridesMap, "return_images")
|
||||
overrides.ReturnImages = returnImages
|
||||
}
|
||||
// Put remaining overridesMap fields back into mediaResponseMap at correct nested location
|
||||
if len(overridesMap) > 0 {
|
||||
mediaResponseMap["overrides"] = overridesMap
|
||||
} else {
|
||||
delete(mediaResponseMap, "overrides")
|
||||
}
|
||||
mediaResponse.Overrides = overrides
|
||||
}
|
||||
}
|
||||
perplexityReq.ExtraParams["media_response"] = mediaResponseMap
|
||||
perplexityReq.MediaResponse = mediaResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return perplexityReq
|
||||
}
|
||||
|
||||
// ToBifrostChatResponse converts a Perplexity chat completion response to Bifrost format
|
||||
func (response *PerplexityChatResponse) ToBifrostChatResponse(model string) *schemas.BifrostChatResponse {
|
||||
if response == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
bifrostResponse := &schemas.BifrostChatResponse{
|
||||
ID: response.ID,
|
||||
Model: model,
|
||||
Object: response.Object,
|
||||
Created: response.Created,
|
||||
ExtraFields: schemas.BifrostResponseExtraFields{
|
||||
},
|
||||
SearchResults: response.SearchResults,
|
||||
Videos: response.Videos,
|
||||
Citations: response.Citations,
|
||||
}
|
||||
|
||||
// Map all response fields
|
||||
if len(response.Choices) > 0 {
|
||||
bifrostResponse.Choices = response.Choices
|
||||
}
|
||||
|
||||
// Convert usage information with all available fields
|
||||
if response.Usage != nil {
|
||||
usage := &schemas.BifrostLLMUsage{
|
||||
PromptTokens: response.Usage.PromptTokens,
|
||||
CompletionTokens: response.Usage.CompletionTokens,
|
||||
TotalTokens: response.Usage.TotalTokens,
|
||||
}
|
||||
|
||||
// Map Perplexity-specific usage details to CompletionTokensDetails
|
||||
completionDetails := &schemas.ChatCompletionTokensDetails{}
|
||||
hasCompletionDetails := false
|
||||
|
||||
if response.Usage.CitationTokens != nil {
|
||||
completionDetails.CitationTokens = response.Usage.CitationTokens
|
||||
hasCompletionDetails = true
|
||||
}
|
||||
|
||||
if response.Usage.NumSearchQueries != nil {
|
||||
completionDetails.NumSearchQueries = response.Usage.NumSearchQueries
|
||||
hasCompletionDetails = true
|
||||
}
|
||||
|
||||
if response.Usage.ReasoningTokens != nil {
|
||||
completionDetails.ReasoningTokens = *response.Usage.ReasoningTokens
|
||||
hasCompletionDetails = true
|
||||
}
|
||||
|
||||
if hasCompletionDetails {
|
||||
usage.CompletionTokensDetails = completionDetails
|
||||
}
|
||||
|
||||
if response.Usage.Cost != nil {
|
||||
usage.Cost = response.Usage.Cost
|
||||
}
|
||||
|
||||
bifrostResponse.Usage = usage
|
||||
}
|
||||
|
||||
return bifrostResponse
|
||||
}
|
||||
445
core/providers/perplexity/perplexity.go
Normal file
445
core/providers/perplexity/perplexity.go
Normal file
@@ -0,0 +1,445 @@
|
||||
// Package providers implements various LLM providers and their utility functions.
|
||||
// This file contains the Perplexity provider implementation.
|
||||
package perplexity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/maximhq/bifrost/core/providers/openai"
|
||||
providerUtils "github.com/maximhq/bifrost/core/providers/utils"
|
||||
schemas "github.com/maximhq/bifrost/core/schemas"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// PerplexityProvider implements the Provider interface for Perplexity's API.
|
||||
type PerplexityProvider struct {
|
||||
logger schemas.Logger // Logger for provider operations
|
||||
client *fasthttp.Client // HTTP client for unary API requests (ReadTimeout bounds overall response)
|
||||
streamingClient *fasthttp.Client // HTTP client for streaming API requests (no ReadTimeout; idle governed by NewIdleTimeoutReader)
|
||||
networkConfig schemas.NetworkConfig // Network configuration including extra headers
|
||||
sendBackRawRequest bool // Whether to include raw request in BifrostResponse
|
||||
sendBackRawResponse bool // Whether to include raw response in BifrostResponse
|
||||
}
|
||||
|
||||
// NewPerplexityProvider creates a new Perplexity provider instance.
|
||||
// It initializes the HTTP client with the provided configuration and sets up response pools.
|
||||
// The client is configured with timeouts, concurrency limits, and optional proxy settings.
|
||||
func NewPerplexityProvider(config *schemas.ProviderConfig, logger schemas.Logger) (*PerplexityProvider, error) {
|
||||
config.CheckAndSetDefaults()
|
||||
|
||||
requestTimeout := time.Second * time.Duration(config.NetworkConfig.DefaultRequestTimeoutInSeconds)
|
||||
client := &fasthttp.Client{
|
||||
ReadTimeout: requestTimeout,
|
||||
WriteTimeout: requestTimeout,
|
||||
MaxConnsPerHost: config.NetworkConfig.MaxConnsPerHost,
|
||||
MaxIdleConnDuration: 30 * time.Second,
|
||||
MaxConnWaitTimeout: requestTimeout,
|
||||
MaxConnDuration: time.Second * time.Duration(schemas.DefaultMaxConnDurationInSeconds),
|
||||
ConnPoolStrategy: fasthttp.FIFO,
|
||||
}
|
||||
|
||||
// Configure proxy and retry policy
|
||||
client = providerUtils.ConfigureProxy(client, config.ProxyConfig, logger)
|
||||
client = providerUtils.ConfigureDialer(client)
|
||||
client = providerUtils.ConfigureTLS(client, config.NetworkConfig, logger)
|
||||
streamingClient := providerUtils.BuildStreamingClient(client)
|
||||
// Set default BaseURL if not provided
|
||||
if config.NetworkConfig.BaseURL == "" {
|
||||
config.NetworkConfig.BaseURL = "https://api.perplexity.ai"
|
||||
}
|
||||
config.NetworkConfig.BaseURL = strings.TrimRight(config.NetworkConfig.BaseURL, "/")
|
||||
|
||||
return &PerplexityProvider{
|
||||
logger: logger,
|
||||
client: client,
|
||||
streamingClient: streamingClient,
|
||||
networkConfig: config.NetworkConfig,
|
||||
sendBackRawRequest: config.SendBackRawRequest,
|
||||
sendBackRawResponse: config.SendBackRawResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetProviderKey returns the provider identifier for Perplexity.
|
||||
func (provider *PerplexityProvider) GetProviderKey() schemas.ModelProvider {
|
||||
return schemas.Perplexity
|
||||
}
|
||||
|
||||
// completeRequest sends a request to Perplexity's API and handles the response.
|
||||
// It constructs the API URL, sets up authentication, and processes the response.
|
||||
// Returns the response body or an error if the request fails.
|
||||
func (provider *PerplexityProvider) completeRequest(ctx *schemas.BifrostContext, jsonData []byte, url string, key string, model string) ([]byte, time.Duration, map[string]string, *schemas.BifrostError) {
|
||||
// Create the request with the JSON body
|
||||
req := fasthttp.AcquireRequest()
|
||||
resp := fasthttp.AcquireResponse()
|
||||
defer fasthttp.ReleaseRequest(req)
|
||||
defer fasthttp.ReleaseResponse(resp)
|
||||
|
||||
// Set any extra headers from network config
|
||||
providerUtils.SetExtraHeaders(ctx, req, provider.networkConfig.ExtraHeaders, nil)
|
||||
|
||||
req.SetRequestURI(url)
|
||||
req.Header.SetMethod(http.MethodPost)
|
||||
req.Header.SetContentType("application/json")
|
||||
if key != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+key)
|
||||
}
|
||||
|
||||
if !providerUtils.ApplyLargePayloadRequestBodyWithModelNormalization(ctx, req, schemas.Perplexity) {
|
||||
req.SetBody(jsonData)
|
||||
}
|
||||
|
||||
// Send the request
|
||||
latency, bifrostErr, wait := providerUtils.MakeRequestWithContext(ctx, provider.client, req, resp)
|
||||
defer wait()
|
||||
if bifrostErr != nil {
|
||||
return nil, latency, nil, bifrostErr
|
||||
}
|
||||
|
||||
// Extract provider response headers before status check so error responses also forward them
|
||||
providerResponseHeaders := providerUtils.ExtractProviderResponseHeaders(resp)
|
||||
|
||||
// Handle error response
|
||||
if resp.StatusCode() != fasthttp.StatusOK {
|
||||
provider.logger.Debug(fmt.Sprintf("error from %s provider: %s", provider.GetProviderKey(), string(resp.Body())))
|
||||
return nil, latency, providerResponseHeaders, openai.ParseOpenAIError(resp)
|
||||
}
|
||||
|
||||
body, err := providerUtils.CheckAndDecodeBody(resp)
|
||||
if err != nil {
|
||||
return nil, latency, providerResponseHeaders, providerUtils.NewBifrostOperationError(schemas.ErrProviderResponseDecode, err)
|
||||
}
|
||||
|
||||
// Read the response body and copy it before releasing the response
|
||||
// to avoid use-after-free since resp.Body() references fasthttp's internal buffer
|
||||
bodyCopy := append([]byte(nil), body...)
|
||||
|
||||
return bodyCopy, latency, providerResponseHeaders, nil
|
||||
}
|
||||
|
||||
// ListModels performs a list models request to Perplexity's API.
|
||||
func (provider *PerplexityProvider) ListModels(ctx *schemas.BifrostContext, keys []schemas.Key, request *schemas.BifrostListModelsRequest) (*schemas.BifrostListModelsResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ListModelsRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// TextCompletion is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) TextCompletion(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostTextCompletionRequest) (*schemas.BifrostTextCompletionResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.TextCompletionRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// TextCompletionStream performs a streaming text completion request to Perplexity's API.
|
||||
// It formats the request, sends it to Perplexity, and processes the response.
|
||||
// Returns a channel of BifrostStreamChunk objects or an error if the request fails.
|
||||
func (provider *PerplexityProvider) TextCompletionStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostTextCompletionRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.TextCompletionStreamRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ChatCompletion performs a chat completion request to the Perplexity API.
|
||||
func (provider *PerplexityProvider) ChatCompletion(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostChatRequest) (*schemas.BifrostChatResponse, *schemas.BifrostError) {
|
||||
// Convert to Perplexity chat completion request
|
||||
jsonBody, err := providerUtils.CheckContextAndGetRequestBody(
|
||||
ctx,
|
||||
request,
|
||||
func() (providerUtils.RequestBodyWithExtraParams, error) {
|
||||
return ToPerplexityChatCompletionRequest(request), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responseBody, latency, providerResponseHeaders, err := provider.completeRequest(ctx, jsonBody, provider.networkConfig.BaseURL+providerUtils.GetPathFromContext(ctx, "/chat/completions"), key.Value.GetValue(), request.Model)
|
||||
if err != nil {
|
||||
return nil, providerUtils.EnrichError(ctx, err, jsonBody, nil, provider.sendBackRawRequest, provider.sendBackRawResponse)
|
||||
}
|
||||
|
||||
var response PerplexityChatResponse
|
||||
rawRequest, rawResponse, bifrostErr := providerUtils.HandleProviderResponse(responseBody, &response, jsonBody, providerUtils.ShouldSendBackRawRequest(ctx, provider.sendBackRawRequest), providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse))
|
||||
if bifrostErr != nil {
|
||||
return nil, providerUtils.EnrichError(ctx, bifrostErr, jsonBody, responseBody, provider.sendBackRawRequest, provider.sendBackRawResponse)
|
||||
}
|
||||
|
||||
bifrostResponse := response.ToBifrostChatResponse(request.Model)
|
||||
|
||||
// Set ExtraFields
|
||||
bifrostResponse.ExtraFields.Latency = latency.Milliseconds()
|
||||
bifrostResponse.ExtraFields.ProviderResponseHeaders = providerResponseHeaders
|
||||
|
||||
// Set raw request if enabled
|
||||
if providerUtils.ShouldSendBackRawRequest(ctx, provider.sendBackRawRequest) {
|
||||
bifrostResponse.ExtraFields.RawRequest = rawRequest
|
||||
}
|
||||
|
||||
// Set raw response if enabled
|
||||
if providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse) {
|
||||
bifrostResponse.ExtraFields.RawResponse = rawResponse
|
||||
}
|
||||
|
||||
return bifrostResponse, nil
|
||||
}
|
||||
|
||||
// ChatCompletionStream performs a streaming chat completion request to the Perplexity API.
|
||||
// It supports real-time streaming of responses using Server-Sent Events (SSE).
|
||||
// Uses Perplexity's OpenAI-compatible streaming format.
|
||||
// Returns a channel containing BifrostStreamChunk objects representing the stream or an error if the request fails.
|
||||
func (provider *PerplexityProvider) ChatCompletionStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostChatRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
var authHeader map[string]string
|
||||
if key.Value.GetValue() != "" {
|
||||
authHeader = map[string]string{"Authorization": "Bearer " + key.Value.GetValue()}
|
||||
}
|
||||
customRequestConverter := func(request *schemas.BifrostChatRequest) (providerUtils.RequestBodyWithExtraParams, error) {
|
||||
reqBody := ToPerplexityChatCompletionRequest(request)
|
||||
reqBody.Stream = schemas.Ptr(true)
|
||||
return reqBody, nil
|
||||
}
|
||||
// Use shared OpenAI-compatible streaming logic
|
||||
return openai.HandleOpenAIChatCompletionStreaming(
|
||||
ctx,
|
||||
provider.streamingClient,
|
||||
provider.networkConfig.BaseURL+"/chat/completions",
|
||||
request,
|
||||
authHeader,
|
||||
provider.networkConfig.ExtraHeaders,
|
||||
providerUtils.ShouldSendBackRawRequest(ctx, provider.sendBackRawRequest),
|
||||
providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse),
|
||||
schemas.Perplexity,
|
||||
postHookRunner,
|
||||
customRequestConverter,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
provider.logger,
|
||||
postHookSpanFinalizer,
|
||||
)
|
||||
}
|
||||
|
||||
// Responses performs a responses request to the Perplexity API.
|
||||
func (provider *PerplexityProvider) Responses(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostResponsesRequest) (*schemas.BifrostResponsesResponse, *schemas.BifrostError) {
|
||||
chatResponse, err := provider.ChatCompletion(ctx, key, request.ToChatRequest())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := chatResponse.ToBifrostResponsesResponse()
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ResponsesStream performs a streaming responses request to the Perplexity API.
|
||||
func (provider *PerplexityProvider) ResponsesStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostResponsesRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
ctx.SetValue(schemas.BifrostContextKeyIsResponsesToChatCompletionFallback, true)
|
||||
return provider.ChatCompletionStream(
|
||||
ctx,
|
||||
postHookRunner,
|
||||
postHookSpanFinalizer,
|
||||
key,
|
||||
request.ToChatRequest(),
|
||||
)
|
||||
}
|
||||
|
||||
// Embedding is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) Embedding(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostEmbeddingRequest) (*schemas.BifrostEmbeddingResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.EmbeddingRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// Speech is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) Speech(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostSpeechRequest) (*schemas.BifrostSpeechResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.SpeechRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// Rerank is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) Rerank(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostRerankRequest) (*schemas.BifrostRerankResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.RerankRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// OCR is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) OCR(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostOCRRequest) (*schemas.BifrostOCRResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.OCRRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// SpeechStream is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) SpeechStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostSpeechRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.SpeechStreamRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// Transcription is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) Transcription(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostTranscriptionRequest) (*schemas.BifrostTranscriptionResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.TranscriptionRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// TranscriptionStream is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) TranscriptionStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostTranscriptionRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.TranscriptionStreamRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ImageGeneration is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ImageGeneration(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostImageGenerationRequest) (*schemas.BifrostImageGenerationResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ImageGenerationRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ImageGenerationStream is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ImageGenerationStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostImageGenerationRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ImageGenerationStreamRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ImageEdit is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ImageEdit(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostImageEditRequest) (*schemas.BifrostImageGenerationResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ImageEditRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ImageEditStream is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ImageEditStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostImageEditRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ImageEditStreamRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ImageVariation is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ImageVariation(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostImageVariationRequest) (*schemas.BifrostImageGenerationResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ImageVariationRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// VideoGeneration is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) VideoGeneration(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostVideoGenerationRequest) (*schemas.BifrostVideoGenerationResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.VideoGenerationRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// VideoRetrieve is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) VideoRetrieve(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostVideoRetrieveRequest) (*schemas.BifrostVideoGenerationResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.VideoRetrieveRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// VideoDownload is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) VideoDownload(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostVideoDownloadRequest) (*schemas.BifrostVideoDownloadResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.VideoDownloadRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// VideoDelete is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) VideoDelete(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostVideoDeleteRequest) (*schemas.BifrostVideoDeleteResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.VideoDeleteRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// VideoList is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) VideoList(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostVideoListRequest) (*schemas.BifrostVideoListResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.VideoListRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// VideoRemix is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) VideoRemix(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostVideoRemixRequest) (*schemas.BifrostVideoGenerationResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.VideoRemixRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// BatchCreate is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) BatchCreate(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostBatchCreateRequest) (*schemas.BifrostBatchCreateResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchCreateRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// BatchList is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) BatchList(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostBatchListRequest) (*schemas.BifrostBatchListResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchListRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// BatchRetrieve is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) BatchRetrieve(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostBatchRetrieveRequest) (*schemas.BifrostBatchRetrieveResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchRetrieveRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// BatchCancel is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) BatchCancel(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostBatchCancelRequest) (*schemas.BifrostBatchCancelResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchCancelRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// BatchDelete is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) BatchDelete(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostBatchDeleteRequest) (*schemas.BifrostBatchDeleteResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchDeleteRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// BatchResults is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) BatchResults(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostBatchResultsRequest) (*schemas.BifrostBatchResultsResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchResultsRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// FileUpload is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) FileUpload(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostFileUploadRequest) (*schemas.BifrostFileUploadResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.FileUploadRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// FileList is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) FileList(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostFileListRequest) (*schemas.BifrostFileListResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.FileListRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// FileRetrieve is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) FileRetrieve(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostFileRetrieveRequest) (*schemas.BifrostFileRetrieveResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.FileRetrieveRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// FileDelete is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) FileDelete(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostFileDeleteRequest) (*schemas.BifrostFileDeleteResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.FileDeleteRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// FileContent is not supported by Perplexity provider.
|
||||
func (provider *PerplexityProvider) FileContent(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostFileContentRequest) (*schemas.BifrostFileContentResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.FileContentRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// CountTokens is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) CountTokens(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostResponsesRequest) (*schemas.BifrostCountTokensResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.CountTokensRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerCreate is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerCreate(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostContainerCreateRequest) (*schemas.BifrostContainerCreateResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerCreateRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerList is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerList(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostContainerListRequest) (*schemas.BifrostContainerListResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerListRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerRetrieve is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerRetrieve(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostContainerRetrieveRequest) (*schemas.BifrostContainerRetrieveResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerRetrieveRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerDelete is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerDelete(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostContainerDeleteRequest) (*schemas.BifrostContainerDeleteResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerDeleteRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerFileCreate is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerFileCreate(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostContainerFileCreateRequest) (*schemas.BifrostContainerFileCreateResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerFileCreateRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerFileList is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerFileList(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostContainerFileListRequest) (*schemas.BifrostContainerFileListResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerFileListRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerFileRetrieve is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerFileRetrieve(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostContainerFileRetrieveRequest) (*schemas.BifrostContainerFileRetrieveResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerFileRetrieveRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerFileContent is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerFileContent(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostContainerFileContentRequest) (*schemas.BifrostContainerFileContentResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerFileContentRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// ContainerFileDelete is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) ContainerFileDelete(_ *schemas.BifrostContext, _ []schemas.Key, _ *schemas.BifrostContainerFileDeleteRequest) (*schemas.BifrostContainerFileDeleteResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.ContainerFileDeleteRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
// Passthrough is not supported by the Perplexity provider.
|
||||
func (provider *PerplexityProvider) Passthrough(_ *schemas.BifrostContext, _ schemas.Key, _ *schemas.BifrostPassthroughRequest) (*schemas.BifrostPassthroughResponse, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.PassthroughRequest, provider.GetProviderKey())
|
||||
}
|
||||
|
||||
func (provider *PerplexityProvider) PassthroughStream(_ *schemas.BifrostContext, _ schemas.PostHookRunner, _ func(context.Context), _ schemas.Key, _ *schemas.BifrostPassthroughRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
|
||||
return nil, providerUtils.NewUnsupportedOperationError(schemas.PassthroughStreamRequest, provider.GetProviderKey())
|
||||
}
|
||||
603
core/providers/perplexity/perplexity_test.go
Normal file
603
core/providers/perplexity/perplexity_test.go
Normal file
@@ -0,0 +1,603 @@
|
||||
package perplexity_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/maximhq/bifrost/core/internal/llmtests"
|
||||
"github.com/maximhq/bifrost/core/providers/perplexity"
|
||||
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
)
|
||||
|
||||
func TestPerplexity(t *testing.T) {
|
||||
t.Parallel()
|
||||
if strings.TrimSpace(os.Getenv("PERPLEXITY_API_KEY")) == "" {
|
||||
t.Skip("Skipping Perplexity tests because PERPLEXITY_API_KEY is not set")
|
||||
}
|
||||
|
||||
client, ctx, cancel, err := llmtests.SetupTest()
|
||||
if err != nil {
|
||||
t.Fatalf("Error initializing test setup: %v", err)
|
||||
}
|
||||
defer cancel()
|
||||
defer client.Shutdown()
|
||||
|
||||
testConfig := llmtests.ComprehensiveTestConfig{
|
||||
Provider: schemas.Perplexity,
|
||||
ChatModel: "sonar-pro",
|
||||
TextModel: "", // Perplexity doesn't support text completion
|
||||
EmbeddingModel: "", // Perplexity doesn't support embedding
|
||||
Scenarios: llmtests.TestScenarios{
|
||||
TextCompletion: false, // Not supported
|
||||
SimpleChat: true,
|
||||
CompletionStream: true,
|
||||
MultiTurnConversation: true,
|
||||
ToolCalls: false,
|
||||
MultipleToolCalls: false,
|
||||
End2EndToolCalling: false,
|
||||
AutomaticFunctionCall: false,
|
||||
ImageURL: false, // Not supported yet
|
||||
ImageBase64: false, // Not supported yet
|
||||
MultipleImages: false, // Not supported yet
|
||||
CompleteEnd2End: false,
|
||||
FileBase64: false,
|
||||
FileURL: false,
|
||||
Embedding: false, // Not supported yet
|
||||
ListModels: false,
|
||||
PassThroughExtraParams: true,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("PerplexityTests", func(t *testing.T) {
|
||||
llmtests.RunAllComprehensiveTests(t, client, ctx, testConfig)
|
||||
})
|
||||
}
|
||||
|
||||
func TestToBifrostChatResponse_Citations(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("citations are mapped to bifrost response", func(t *testing.T) {
|
||||
response := &perplexity.PerplexityChatResponse{
|
||||
ID: "test-id",
|
||||
Model: "sonar-pro",
|
||||
Object: "chat.completion",
|
||||
Created: 1234567890,
|
||||
Citations: []string{
|
||||
"https://example.com/article1",
|
||||
"https://example.com/article2",
|
||||
"https://example.com/article3",
|
||||
},
|
||||
SearchResults: []schemas.SearchResult{
|
||||
{Title: "Article 1", URL: "https://example.com/article1"},
|
||||
},
|
||||
Videos: []schemas.VideoResult{
|
||||
{URL: "https://example.com/video1"},
|
||||
},
|
||||
Choices: []schemas.BifrostResponseChoice{
|
||||
{
|
||||
Index: 0,
|
||||
FinishReason: schemas.Ptr("stop"),
|
||||
ChatNonStreamResponseChoice: &schemas.ChatNonStreamResponseChoice{
|
||||
Message: &schemas.ChatMessage{
|
||||
Role: "assistant",
|
||||
Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("Here is the answer with citations [1][2][3].")},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Usage: &perplexity.Usage{
|
||||
PromptTokens: 10,
|
||||
CompletionTokens: 20,
|
||||
TotalTokens: 30,
|
||||
CitationTokens: schemas.Ptr(5),
|
||||
NumSearchQueries: schemas.Ptr(2),
|
||||
},
|
||||
}
|
||||
|
||||
bifrostResp := response.ToBifrostChatResponse("sonar-pro")
|
||||
|
||||
if bifrostResp == nil {
|
||||
t.Fatal("expected non-nil bifrost response")
|
||||
}
|
||||
|
||||
// Verify citations are preserved
|
||||
if len(bifrostResp.Citations) != 3 {
|
||||
t.Fatalf("expected 3 citations, got %d", len(bifrostResp.Citations))
|
||||
}
|
||||
if bifrostResp.Citations[0] != "https://example.com/article1" {
|
||||
t.Errorf("expected first citation to be 'https://example.com/article1', got '%s'", bifrostResp.Citations[0])
|
||||
}
|
||||
if bifrostResp.Citations[1] != "https://example.com/article2" {
|
||||
t.Errorf("expected second citation to be 'https://example.com/article2', got '%s'", bifrostResp.Citations[1])
|
||||
}
|
||||
if bifrostResp.Citations[2] != "https://example.com/article3" {
|
||||
t.Errorf("expected third citation to be 'https://example.com/article3', got '%s'", bifrostResp.Citations[2])
|
||||
}
|
||||
|
||||
// Verify search results are preserved
|
||||
if len(bifrostResp.SearchResults) != 1 {
|
||||
t.Fatalf("expected 1 search result, got %d", len(bifrostResp.SearchResults))
|
||||
}
|
||||
|
||||
// Verify videos are preserved
|
||||
if len(bifrostResp.Videos) != 1 {
|
||||
t.Fatalf("expected 1 video, got %d", len(bifrostResp.Videos))
|
||||
}
|
||||
|
||||
// Verify usage citation tokens are mapped
|
||||
if bifrostResp.Usage == nil || bifrostResp.Usage.CompletionTokensDetails == nil {
|
||||
t.Fatal("expected usage with completion token details")
|
||||
}
|
||||
if bifrostResp.Usage.CompletionTokensDetails.CitationTokens == nil || *bifrostResp.Usage.CompletionTokensDetails.CitationTokens != 5 {
|
||||
t.Error("expected citation_tokens to be 5")
|
||||
}
|
||||
if bifrostResp.Usage.CompletionTokensDetails.NumSearchQueries == nil || *bifrostResp.Usage.CompletionTokensDetails.NumSearchQueries != 2 {
|
||||
t.Error("expected num_search_queries to be 2")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil citations remain nil", func(t *testing.T) {
|
||||
response := &perplexity.PerplexityChatResponse{
|
||||
ID: "test-id-2",
|
||||
Model: "sonar-pro",
|
||||
Object: "chat.completion",
|
||||
Created: 1234567890,
|
||||
Choices: []schemas.BifrostResponseChoice{},
|
||||
}
|
||||
|
||||
bifrostResp := response.ToBifrostChatResponse("sonar-pro")
|
||||
|
||||
if bifrostResp == nil {
|
||||
t.Fatal("expected non-nil bifrost response")
|
||||
}
|
||||
if bifrostResp.Citations != nil {
|
||||
t.Errorf("expected nil citations, got %v", bifrostResp.Citations)
|
||||
}
|
||||
if bifrostResp.SearchResults != nil {
|
||||
t.Errorf("expected nil search results, got %v", bifrostResp.SearchResults)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty citations slice is preserved", func(t *testing.T) {
|
||||
response := &perplexity.PerplexityChatResponse{
|
||||
ID: "test-id-3",
|
||||
Model: "sonar-pro",
|
||||
Object: "chat.completion",
|
||||
Created: 1234567890,
|
||||
Citations: []string{},
|
||||
Choices: []schemas.BifrostResponseChoice{},
|
||||
}
|
||||
|
||||
bifrostResp := response.ToBifrostChatResponse("sonar-pro")
|
||||
|
||||
if bifrostResp == nil {
|
||||
t.Fatal("expected non-nil bifrost response")
|
||||
}
|
||||
if bifrostResp.Citations == nil {
|
||||
t.Error("expected non-nil (empty) citations slice")
|
||||
}
|
||||
if len(bifrostResp.Citations) != 0 {
|
||||
t.Errorf("expected 0 citations, got %d", len(bifrostResp.Citations))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWebSearchOption_JSONSerialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("corrected field name serializes as image_results_enhanced_relevance", func(t *testing.T) {
|
||||
option := perplexity.WebSearchOption{
|
||||
SearchContextSize: schemas.Ptr("high"),
|
||||
ImageResultsEnhancedRelevance: schemas.Ptr(true),
|
||||
SearchType: schemas.Ptr("news"),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(option)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal WebSearchOption: %v", err)
|
||||
}
|
||||
|
||||
jsonStr := string(data)
|
||||
|
||||
// Verify corrected field name
|
||||
if !strings.Contains(jsonStr, `"image_results_enhanced_relevance"`) {
|
||||
t.Errorf("expected JSON to contain 'image_results_enhanced_relevance', got: %s", jsonStr)
|
||||
}
|
||||
// Verify old incorrect field name is NOT present
|
||||
if strings.Contains(jsonStr, `"image_search_relevance_enhanced"`) {
|
||||
t.Errorf("JSON should not contain old field name 'image_search_relevance_enhanced', got: %s", jsonStr)
|
||||
}
|
||||
// Verify search_type is present
|
||||
if !strings.Contains(jsonStr, `"search_type"`) {
|
||||
t.Errorf("expected JSON to contain 'search_type', got: %s", jsonStr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deserialization with corrected field name", func(t *testing.T) {
|
||||
jsonStr := `{"search_context_size":"medium","image_results_enhanced_relevance":true,"search_type":"academic"}`
|
||||
|
||||
var option perplexity.WebSearchOption
|
||||
if err := json.Unmarshal([]byte(jsonStr), &option); err != nil {
|
||||
t.Fatalf("failed to unmarshal WebSearchOption: %v", err)
|
||||
}
|
||||
|
||||
if option.SearchContextSize == nil || *option.SearchContextSize != "medium" {
|
||||
t.Error("expected search_context_size to be 'medium'")
|
||||
}
|
||||
if option.ImageResultsEnhancedRelevance == nil || !*option.ImageResultsEnhancedRelevance {
|
||||
t.Error("expected image_results_enhanced_relevance to be true")
|
||||
}
|
||||
if option.SearchType == nil || *option.SearchType != "academic" {
|
||||
t.Error("expected search_type to be 'academic'")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("search_type extraction from ExtraParams", func(t *testing.T) {
|
||||
bifrostReq := &schemas.BifrostChatRequest{
|
||||
Model: "sonar-pro",
|
||||
Input: []schemas.ChatMessage{
|
||||
{Role: "user", Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("test")}},
|
||||
},
|
||||
Params: &schemas.ChatParameters{
|
||||
ExtraParams: map[string]interface{}{
|
||||
"web_search_options": []interface{}{
|
||||
map[string]interface{}{
|
||||
"search_context_size": "high",
|
||||
"image_results_enhanced_relevance": true,
|
||||
"search_type": "news",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := perplexity.ToPerplexityChatCompletionRequest(bifrostReq)
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
if len(result.WebSearchOptions) != 1 {
|
||||
t.Fatalf("expected 1 web search option, got %d", len(result.WebSearchOptions))
|
||||
}
|
||||
opt := result.WebSearchOptions[0]
|
||||
if opt.SearchContextSize == nil || *opt.SearchContextSize != "high" {
|
||||
t.Error("expected search_context_size to be 'high'")
|
||||
}
|
||||
if opt.ImageResultsEnhancedRelevance == nil || !*opt.ImageResultsEnhancedRelevance {
|
||||
t.Error("expected image_results_enhanced_relevance to be true")
|
||||
}
|
||||
if opt.SearchType == nil || *opt.SearchType != "news" {
|
||||
t.Error("expected search_type to be 'news'")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBifrostCost_DeserializationWithPerplexityFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("deserializes new cost breakdown fields", func(t *testing.T) {
|
||||
jsonStr := `{
|
||||
"input_tokens_cost": 0.001,
|
||||
"output_tokens_cost": 0.002,
|
||||
"reasoning_tokens_cost": 0.003,
|
||||
"citation_tokens_cost": 0.004,
|
||||
"search_queries_cost": 0.005,
|
||||
"request_cost": 0.006,
|
||||
"total_cost": 0.021
|
||||
}`
|
||||
|
||||
var cost schemas.BifrostCost
|
||||
if err := cost.UnmarshalJSON([]byte(jsonStr)); err != nil {
|
||||
t.Fatalf("failed to unmarshal BifrostCost: %v", err)
|
||||
}
|
||||
|
||||
if cost.InputTokensCost != 0.001 {
|
||||
t.Errorf("expected input_tokens_cost 0.001, got %f", cost.InputTokensCost)
|
||||
}
|
||||
if cost.OutputTokensCost != 0.002 {
|
||||
t.Errorf("expected output_tokens_cost 0.002, got %f", cost.OutputTokensCost)
|
||||
}
|
||||
if cost.ReasoningTokensCost != 0.003 {
|
||||
t.Errorf("expected reasoning_tokens_cost 0.003, got %f", cost.ReasoningTokensCost)
|
||||
}
|
||||
if cost.CitationTokensCost != 0.004 {
|
||||
t.Errorf("expected citation_tokens_cost 0.004, got %f", cost.CitationTokensCost)
|
||||
}
|
||||
if cost.SearchQueriesCost != 0.005 {
|
||||
t.Errorf("expected search_queries_cost 0.005, got %f", cost.SearchQueriesCost)
|
||||
}
|
||||
if cost.RequestCost != 0.006 {
|
||||
t.Errorf("expected request_cost 0.006, got %f", cost.RequestCost)
|
||||
}
|
||||
if cost.TotalCost != 0.021 {
|
||||
t.Errorf("expected total_cost 0.021, got %f", cost.TotalCost)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("still works with float-only cost", func(t *testing.T) {
|
||||
var cost schemas.BifrostCost
|
||||
if err := cost.UnmarshalJSON([]byte(`0.42`)); err != nil {
|
||||
t.Fatalf("failed to unmarshal float cost: %v", err)
|
||||
}
|
||||
if cost.TotalCost != 0.42 {
|
||||
t.Errorf("expected total_cost 0.42, got %f", cost.TotalCost)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("perplexity usage cost round-trip", func(t *testing.T) {
|
||||
jsonStr := `{
|
||||
"prompt_tokens": 100,
|
||||
"completion_tokens": 50,
|
||||
"total_tokens": 150,
|
||||
"citation_tokens": 10,
|
||||
"cost": {
|
||||
"input_tokens_cost": 0.01,
|
||||
"output_tokens_cost": 0.02,
|
||||
"citation_tokens_cost": 0.005,
|
||||
"search_queries_cost": 0.003,
|
||||
"total_cost": 0.038
|
||||
}
|
||||
}`
|
||||
|
||||
var usage perplexity.Usage
|
||||
if err := json.Unmarshal([]byte(jsonStr), &usage); err != nil {
|
||||
t.Fatalf("failed to unmarshal Usage: %v", err)
|
||||
}
|
||||
|
||||
if usage.Cost == nil {
|
||||
t.Fatal("expected non-nil cost")
|
||||
}
|
||||
if usage.Cost.CitationTokensCost != 0.005 {
|
||||
t.Errorf("expected citation_tokens_cost 0.005, got %f", usage.Cost.CitationTokensCost)
|
||||
}
|
||||
if usage.Cost.SearchQueriesCost != 0.003 {
|
||||
t.Errorf("expected search_queries_cost 0.003, got %f", usage.Cost.SearchQueriesCost)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestToPerplexityChatCompletionRequest_ToolCalling(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("tools and tool_choice are mapped", func(t *testing.T) {
|
||||
bifrostReq := &schemas.BifrostChatRequest{
|
||||
Model: "sonar-pro",
|
||||
Input: []schemas.ChatMessage{
|
||||
{Role: "user", Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("What's the weather?")}},
|
||||
},
|
||||
Params: &schemas.ChatParameters{
|
||||
Tools: []schemas.ChatTool{
|
||||
{
|
||||
Type: schemas.ChatToolTypeFunction,
|
||||
Function: &schemas.ChatToolFunction{
|
||||
Name: "get_weather",
|
||||
Description: schemas.Ptr("Get the weather for a location"),
|
||||
},
|
||||
},
|
||||
},
|
||||
ToolChoice: &schemas.ChatToolChoice{
|
||||
ChatToolChoiceStr: schemas.Ptr("auto"),
|
||||
},
|
||||
ParallelToolCalls: schemas.Ptr(true),
|
||||
},
|
||||
}
|
||||
|
||||
result := perplexity.ToPerplexityChatCompletionRequest(bifrostReq)
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
|
||||
// Verify tools
|
||||
if len(result.Tools) != 1 {
|
||||
t.Fatalf("expected 1 tool, got %d", len(result.Tools))
|
||||
}
|
||||
if result.Tools[0].Type != schemas.ChatToolTypeFunction {
|
||||
t.Errorf("expected tool type 'function', got '%s'", result.Tools[0].Type)
|
||||
}
|
||||
if result.Tools[0].Function == nil || result.Tools[0].Function.Name != "get_weather" {
|
||||
t.Error("expected tool function name 'get_weather'")
|
||||
}
|
||||
|
||||
// Verify tool_choice
|
||||
if result.ToolChoice == nil {
|
||||
t.Fatal("expected non-nil tool_choice")
|
||||
}
|
||||
if result.ToolChoice.ChatToolChoiceStr == nil || *result.ToolChoice.ChatToolChoiceStr != "auto" {
|
||||
t.Error("expected tool_choice to be 'auto'")
|
||||
}
|
||||
|
||||
// Verify parallel_tool_calls
|
||||
if result.ParallelToolCalls == nil || !*result.ParallelToolCalls {
|
||||
t.Error("expected parallel_tool_calls to be true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil tools remain nil", func(t *testing.T) {
|
||||
bifrostReq := &schemas.BifrostChatRequest{
|
||||
Model: "sonar-pro",
|
||||
Input: []schemas.ChatMessage{
|
||||
{Role: "user", Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("hello")}},
|
||||
},
|
||||
Params: &schemas.ChatParameters{},
|
||||
}
|
||||
|
||||
result := perplexity.ToPerplexityChatCompletionRequest(bifrostReq)
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
if result.Tools != nil {
|
||||
t.Errorf("expected nil tools, got %v", result.Tools)
|
||||
}
|
||||
if result.ToolChoice != nil {
|
||||
t.Error("expected nil tool_choice")
|
||||
}
|
||||
if result.ParallelToolCalls != nil {
|
||||
t.Error("expected nil parallel_tool_calls")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestToPerplexityChatCompletionRequest_NewFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("stop, logprobs, top_logprobs are mapped", func(t *testing.T) {
|
||||
bifrostReq := &schemas.BifrostChatRequest{
|
||||
Model: "sonar-pro",
|
||||
Input: []schemas.ChatMessage{
|
||||
{Role: "user", Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("test")}},
|
||||
},
|
||||
Params: &schemas.ChatParameters{
|
||||
Stop: []string{"END", "STOP"},
|
||||
LogProbs: schemas.Ptr(true),
|
||||
TopLogProbs: schemas.Ptr(5),
|
||||
},
|
||||
}
|
||||
|
||||
result := perplexity.ToPerplexityChatCompletionRequest(bifrostReq)
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
|
||||
// Verify stop
|
||||
if len(result.Stop) != 2 {
|
||||
t.Fatalf("expected 2 stop sequences, got %d", len(result.Stop))
|
||||
}
|
||||
if result.Stop[0] != "END" || result.Stop[1] != "STOP" {
|
||||
t.Errorf("expected stop sequences ['END', 'STOP'], got %v", result.Stop)
|
||||
}
|
||||
|
||||
// Verify logprobs
|
||||
if result.LogProbs == nil || !*result.LogProbs {
|
||||
t.Error("expected logprobs to be true")
|
||||
}
|
||||
|
||||
// Verify top_logprobs
|
||||
if result.TopLogProbs == nil || *result.TopLogProbs != 5 {
|
||||
t.Error("expected top_logprobs to be 5")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("perplexity-specific fields from ExtraParams", func(t *testing.T) {
|
||||
bifrostReq := &schemas.BifrostChatRequest{
|
||||
Model: "sonar-pro",
|
||||
Input: []schemas.ChatMessage{
|
||||
{Role: "user", Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("test")}},
|
||||
},
|
||||
Params: &schemas.ChatParameters{
|
||||
ExtraParams: map[string]interface{}{
|
||||
"num_search_results": 5,
|
||||
"num_images": 3,
|
||||
"search_language_filter": []interface{}{"en", "fr"},
|
||||
"image_format_filter": []interface{}{"png", "jpg"},
|
||||
"image_domain_filter": []interface{}{"example.com"},
|
||||
"safe_search": true,
|
||||
"stream_mode": "partial",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := perplexity.ToPerplexityChatCompletionRequest(bifrostReq)
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("expected non-nil result")
|
||||
}
|
||||
|
||||
// Verify num_search_results
|
||||
if result.NumSearchResults == nil || *result.NumSearchResults != 5 {
|
||||
t.Errorf("expected num_search_results to be 5, got %v", result.NumSearchResults)
|
||||
}
|
||||
|
||||
// Verify num_images
|
||||
if result.NumImages == nil || *result.NumImages != 3 {
|
||||
t.Errorf("expected num_images to be 3, got %v", result.NumImages)
|
||||
}
|
||||
|
||||
// Verify search_language_filter
|
||||
if len(result.SearchLanguageFilter) != 2 {
|
||||
t.Fatalf("expected 2 search language filters, got %d", len(result.SearchLanguageFilter))
|
||||
}
|
||||
if result.SearchLanguageFilter[0] != "en" || result.SearchLanguageFilter[1] != "fr" {
|
||||
t.Errorf("expected search_language_filter ['en', 'fr'], got %v", result.SearchLanguageFilter)
|
||||
}
|
||||
|
||||
// Verify image_format_filter
|
||||
if len(result.ImageFormatFilter) != 2 {
|
||||
t.Fatalf("expected 2 image format filters, got %d", len(result.ImageFormatFilter))
|
||||
}
|
||||
if result.ImageFormatFilter[0] != "png" || result.ImageFormatFilter[1] != "jpg" {
|
||||
t.Errorf("expected image_format_filter ['png', 'jpg'], got %v", result.ImageFormatFilter)
|
||||
}
|
||||
|
||||
// Verify image_domain_filter
|
||||
if len(result.ImageDomainFilter) != 1 {
|
||||
t.Fatalf("expected 1 image domain filter, got %d", len(result.ImageDomainFilter))
|
||||
}
|
||||
if result.ImageDomainFilter[0] != "example.com" {
|
||||
t.Errorf("expected image_domain_filter ['example.com'], got %v", result.ImageDomainFilter)
|
||||
}
|
||||
|
||||
// Verify safe_search
|
||||
if result.SafeSearch == nil || !*result.SafeSearch {
|
||||
t.Error("expected safe_search to be true")
|
||||
}
|
||||
|
||||
// Verify stream_mode
|
||||
if result.StreamMode == nil || *result.StreamMode != "partial" {
|
||||
t.Errorf("expected stream_mode to be 'partial', got %v", result.StreamMode)
|
||||
}
|
||||
|
||||
// Verify fields are removed from ExtraParams
|
||||
for _, key := range []string{"num_search_results", "num_images", "search_language_filter", "image_format_filter", "image_domain_filter", "safe_search", "stream_mode"} {
|
||||
if _, exists := result.ExtraParams[key]; exists {
|
||||
t.Errorf("expected '%s' to be removed from ExtraParams", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("new fields serialize correctly in JSON", func(t *testing.T) {
|
||||
req := perplexity.PerplexityChatRequest{
|
||||
Model: "sonar-pro",
|
||||
Messages: []schemas.ChatMessage{
|
||||
{Role: "user", Content: &schemas.ChatMessageContent{ContentStr: schemas.Ptr("test")}},
|
||||
},
|
||||
NumSearchResults: schemas.Ptr(10),
|
||||
NumImages: schemas.Ptr(5),
|
||||
SearchLanguageFilter: []string{"en"},
|
||||
SafeSearch: schemas.Ptr(true),
|
||||
StreamMode: schemas.Ptr("partial"),
|
||||
Stop: []string{"END"},
|
||||
LogProbs: schemas.Ptr(true),
|
||||
TopLogProbs: schemas.Ptr(3),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
jsonStr := string(data)
|
||||
|
||||
expectedFields := []string{
|
||||
`"num_search_results":10`,
|
||||
`"num_images":5`,
|
||||
`"search_language_filter":["en"]`,
|
||||
`"safe_search":true`,
|
||||
`"stream_mode":"partial"`,
|
||||
`"stop":["END"]`,
|
||||
`"logprobs":true`,
|
||||
`"top_logprobs":3`,
|
||||
}
|
||||
|
||||
for _, field := range expectedFields {
|
||||
if !strings.Contains(jsonStr, field) {
|
||||
t.Errorf("expected JSON to contain '%s', got: %s", field, jsonStr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
217
core/providers/perplexity/responses.go
Normal file
217
core/providers/perplexity/responses.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package perplexity
|
||||
|
||||
import (
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
)
|
||||
|
||||
// ToPerplexityResponsesRequest converts a BifrostResponsesRequest to PerplexityChatRequest
|
||||
func ToPerplexityResponsesRequest(bifrostReq *schemas.BifrostResponsesRequest) *PerplexityChatRequest {
|
||||
if bifrostReq == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
perplexityReq := &PerplexityChatRequest{
|
||||
Model: bifrostReq.Model,
|
||||
}
|
||||
|
||||
// Map basic parameters
|
||||
if bifrostReq.Params != nil {
|
||||
// Core parameters
|
||||
perplexityReq.MaxTokens = bifrostReq.Params.MaxOutputTokens
|
||||
perplexityReq.Temperature = bifrostReq.Params.Temperature
|
||||
perplexityReq.TopP = bifrostReq.Params.TopP
|
||||
|
||||
// Handle reasoning effort mapping
|
||||
if bifrostReq.Params.Reasoning != nil && bifrostReq.Params.Reasoning.Effort != nil {
|
||||
if *bifrostReq.Params.Reasoning.Effort == "minimal" {
|
||||
perplexityReq.ReasoningEffort = schemas.Ptr("low")
|
||||
} else {
|
||||
perplexityReq.ReasoningEffort = schemas.Ptr(*bifrostReq.Params.Reasoning.Effort)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra parameters for Perplexity-specific fields
|
||||
if bifrostReq.Params.ExtraParams != nil {
|
||||
// Search-related parameters
|
||||
if searchMode, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_mode"]); ok {
|
||||
perplexityReq.SearchMode = searchMode
|
||||
}
|
||||
|
||||
if languagePreference, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["language_preference"]); ok {
|
||||
perplexityReq.LanguagePreference = languagePreference
|
||||
}
|
||||
|
||||
if searchDomainFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["search_domain_filter"]); ok {
|
||||
perplexityReq.SearchDomainFilter = searchDomainFilter
|
||||
}
|
||||
|
||||
if returnImages, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["return_images"]); ok {
|
||||
perplexityReq.ReturnImages = returnImages
|
||||
}
|
||||
|
||||
if returnRelatedQuestions, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["return_related_questions"]); ok {
|
||||
perplexityReq.ReturnRelatedQuestions = returnRelatedQuestions
|
||||
}
|
||||
|
||||
if searchRecencyFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_recency_filter"]); ok {
|
||||
perplexityReq.SearchRecencyFilter = searchRecencyFilter
|
||||
}
|
||||
|
||||
if searchAfterDateFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_after_date_filter"]); ok {
|
||||
perplexityReq.SearchAfterDateFilter = searchAfterDateFilter
|
||||
}
|
||||
|
||||
if searchBeforeDateFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["search_before_date_filter"]); ok {
|
||||
perplexityReq.SearchBeforeDateFilter = searchBeforeDateFilter
|
||||
}
|
||||
|
||||
if lastUpdatedAfterFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["last_updated_after_filter"]); ok {
|
||||
perplexityReq.LastUpdatedAfterFilter = lastUpdatedAfterFilter
|
||||
}
|
||||
|
||||
if lastUpdatedBeforeFilter, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["last_updated_before_filter"]); ok {
|
||||
perplexityReq.LastUpdatedBeforeFilter = lastUpdatedBeforeFilter
|
||||
}
|
||||
|
||||
if topK, ok := schemas.SafeExtractIntPointer(bifrostReq.Params.ExtraParams["top_k"]); ok {
|
||||
perplexityReq.TopK = topK
|
||||
}
|
||||
|
||||
if stream, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["stream"]); ok {
|
||||
perplexityReq.Stream = stream
|
||||
}
|
||||
|
||||
if disableSearch, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["disable_search"]); ok {
|
||||
perplexityReq.DisableSearch = disableSearch
|
||||
}
|
||||
|
||||
if enableSearchClassifier, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["enable_search_classifier"]); ok {
|
||||
perplexityReq.EnableSearchClassifier = enableSearchClassifier
|
||||
}
|
||||
|
||||
if presencePenalty, ok := schemas.SafeExtractFloat64Pointer(bifrostReq.Params.ExtraParams["presence_penalty"]); ok {
|
||||
perplexityReq.PresencePenalty = presencePenalty
|
||||
}
|
||||
|
||||
if frequencyPenalty, ok := schemas.SafeExtractFloat64Pointer(bifrostReq.Params.ExtraParams["frequency_penalty"]); ok {
|
||||
perplexityReq.FrequencyPenalty = frequencyPenalty
|
||||
}
|
||||
|
||||
if responseFormat, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "response_format"); ok {
|
||||
perplexityReq.ResponseFormat = &responseFormat
|
||||
}
|
||||
|
||||
// Perplexity-specific request fields
|
||||
if numSearchResults, ok := schemas.SafeExtractIntPointer(bifrostReq.Params.ExtraParams["num_search_results"]); ok {
|
||||
perplexityReq.NumSearchResults = numSearchResults
|
||||
}
|
||||
|
||||
if numImages, ok := schemas.SafeExtractIntPointer(bifrostReq.Params.ExtraParams["num_images"]); ok {
|
||||
perplexityReq.NumImages = numImages
|
||||
}
|
||||
|
||||
if searchLanguageFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["search_language_filter"]); ok {
|
||||
perplexityReq.SearchLanguageFilter = searchLanguageFilter
|
||||
}
|
||||
|
||||
if imageFormatFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["image_format_filter"]); ok {
|
||||
perplexityReq.ImageFormatFilter = imageFormatFilter
|
||||
}
|
||||
|
||||
if imageDomainFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["image_domain_filter"]); ok {
|
||||
perplexityReq.ImageDomainFilter = imageDomainFilter
|
||||
}
|
||||
|
||||
if safeSearch, ok := schemas.SafeExtractBoolPointer(bifrostReq.Params.ExtraParams["safe_search"]); ok {
|
||||
perplexityReq.SafeSearch = safeSearch
|
||||
}
|
||||
|
||||
if streamMode, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["stream_mode"]); ok {
|
||||
perplexityReq.StreamMode = streamMode
|
||||
}
|
||||
|
||||
// Handle web_search_options
|
||||
if webSearchOptionsParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "web_search_options"); ok {
|
||||
if webSearchOptionsSlice, ok := webSearchOptionsParam.([]interface{}); ok {
|
||||
var webSearchOptions []WebSearchOption
|
||||
for _, optionInterface := range webSearchOptionsSlice {
|
||||
if optionMap, ok := optionInterface.(map[string]interface{}); ok {
|
||||
option := WebSearchOption{}
|
||||
|
||||
if searchContextSize, ok := schemas.SafeExtractStringPointer(optionMap["search_context_size"]); ok {
|
||||
option.SearchContextSize = searchContextSize
|
||||
}
|
||||
|
||||
if imageResultsEnhancedRelevance, ok := schemas.SafeExtractBoolPointer(optionMap["image_results_enhanced_relevance"]); ok {
|
||||
option.ImageResultsEnhancedRelevance = imageResultsEnhancedRelevance
|
||||
}
|
||||
|
||||
if searchType, ok := schemas.SafeExtractStringPointer(optionMap["search_type"]); ok {
|
||||
option.SearchType = searchType
|
||||
}
|
||||
|
||||
// Handle user_location
|
||||
if userLocationParam, ok := schemas.SafeExtractFromMap(optionMap, "user_location"); ok {
|
||||
if userLocationMap, ok := userLocationParam.(map[string]interface{}); ok {
|
||||
userLocation := &WebSearchOptionUserLocation{}
|
||||
|
||||
if latitude, ok := schemas.SafeExtractFloat64Pointer(userLocationMap["latitude"]); ok {
|
||||
userLocation.Latitude = latitude
|
||||
}
|
||||
if longitude, ok := schemas.SafeExtractFloat64Pointer(userLocationMap["longitude"]); ok {
|
||||
userLocation.Longitude = longitude
|
||||
}
|
||||
if city, ok := schemas.SafeExtractStringPointer(userLocationMap["city"]); ok {
|
||||
userLocation.City = city
|
||||
}
|
||||
if country, ok := schemas.SafeExtractStringPointer(userLocationMap["country"]); ok {
|
||||
userLocation.Country = country
|
||||
}
|
||||
if region, ok := schemas.SafeExtractStringPointer(userLocationMap["region"]); ok {
|
||||
userLocation.Region = region
|
||||
}
|
||||
|
||||
option.UserLocation = userLocation
|
||||
}
|
||||
}
|
||||
|
||||
webSearchOptions = append(webSearchOptions, option)
|
||||
}
|
||||
}
|
||||
perplexityReq.WebSearchOptions = webSearchOptions
|
||||
}
|
||||
}
|
||||
|
||||
// Handle media_response
|
||||
if mediaResponseParam, ok := schemas.SafeExtractFromMap(bifrostReq.Params.ExtraParams, "media_response"); ok {
|
||||
if mediaResponseMap, ok := mediaResponseParam.(map[string]interface{}); ok {
|
||||
mediaResponse := &MediaResponse{}
|
||||
|
||||
if overridesParam, ok := schemas.SafeExtractFromMap(mediaResponseMap, "overrides"); ok {
|
||||
if overridesMap, ok := overridesParam.(map[string]interface{}); ok {
|
||||
overrides := MediaResponseOverrides{}
|
||||
|
||||
if returnVideos, ok := schemas.SafeExtractBoolPointer(overridesMap["return_videos"]); ok {
|
||||
overrides.ReturnVideos = returnVideos
|
||||
}
|
||||
if returnImages, ok := schemas.SafeExtractBoolPointer(overridesMap["return_images"]); ok {
|
||||
overrides.ReturnImages = returnImages
|
||||
}
|
||||
|
||||
mediaResponse.Overrides = overrides
|
||||
}
|
||||
}
|
||||
|
||||
perplexityReq.MediaResponse = mediaResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process ResponsesInput (which contains the Responses messages)
|
||||
if bifrostReq.Input != nil {
|
||||
perplexityReq.Messages = schemas.ToChatMessages(bifrostReq.Input)
|
||||
}
|
||||
|
||||
return perplexityReq
|
||||
}
|
||||
98
core/providers/perplexity/types.go
Normal file
98
core/providers/perplexity/types.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package perplexity
|
||||
|
||||
import "github.com/maximhq/bifrost/core/schemas"
|
||||
|
||||
// PerplexityChatRequest represents a Perplexity chat completion request
|
||||
type PerplexityChatRequest struct {
|
||||
Model string `json:"model"` // Required: Model to use for chat completion
|
||||
Messages []schemas.ChatMessage `json:"messages"` // Required: Array of message objects
|
||||
SearchMode *string `json:"search_mode"` // Required: Search mode
|
||||
ReasoningEffort *string `json:"reasoning_effort"` // Required: Reasoning effort (low, medium, high)
|
||||
MaxTokens *int `json:"max_tokens,omitempty"` // Optional: Maximum tokens to generate
|
||||
Temperature *float64 `json:"temperature,omitempty"` // Optional: Sampling temperature
|
||||
TopP *float64 `json:"top_p,omitempty"` // Optional: Top-p sampling
|
||||
LanguagePreference *string `json:"language_preference,omitempty"` // Optional: Language preference
|
||||
SearchDomainFilter []string `json:"search_domain_filter,omitempty"` // Optional: Search domain filter
|
||||
ReturnImages *bool `json:"return_images,omitempty"` // Optional: Return images
|
||||
ReturnRelatedQuestions *bool `json:"return_related_questions,omitempty"` // Optional: Return related questions
|
||||
SearchRecencyFilter *string `json:"search_recency_filter,omitempty"` // Optional: Search recency filter
|
||||
SearchAfterDateFilter *string `json:"search_after_date_filter,omitempty"` // Optional: Search after date filter
|
||||
SearchBeforeDateFilter *string `json:"search_before_date_filter,omitempty"` // Optional: Search before date filter
|
||||
LastUpdatedAfterFilter *string `json:"last_updated_after_filter,omitempty"` // Optional: Last updated after filter
|
||||
LastUpdatedBeforeFilter *string `json:"last_updated_before_filter,omitempty"` // Optional: Last updated before filter
|
||||
TopK *int `json:"top_k,omitempty"` // Optional: Top-k sampling
|
||||
Stream *bool `json:"stream,omitempty"` // Optional: Enable streaming
|
||||
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // Optional: Presence penalty
|
||||
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // Optional: Frequency penalty
|
||||
ResponseFormat *interface{} `json:"response_format,omitempty"` // Format for the response
|
||||
DisableSearch *bool `json:"disable_search,omitempty"` // Optional: Disable search
|
||||
EnableSearchClassifier *bool `json:"enable_search_classifier,omitempty"` // Optional: Enable search classifier
|
||||
WebSearchOptions []WebSearchOption `json:"web_search_options,omitempty"` // Optional: Web search options
|
||||
MediaResponse *MediaResponse `json:"media_response,omitempty"` // Optional: Media response
|
||||
Tools []schemas.ChatTool `json:"tools,omitempty"` // Optional: Tools available for the model
|
||||
ToolChoice *schemas.ChatToolChoice `json:"tool_choice,omitempty"` // Optional: Whether to call a tool
|
||||
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"` // Optional: Enable parallel tool calls
|
||||
Stop []string `json:"stop,omitempty"` // Optional: Stop sequences
|
||||
LogProbs *bool `json:"logprobs,omitempty"` // Optional: Return log probabilities
|
||||
TopLogProbs *int `json:"top_logprobs,omitempty"` // Optional: Number of top log probabilities
|
||||
NumSearchResults *int `json:"num_search_results,omitempty"` // Optional: Number of search results
|
||||
NumImages *int `json:"num_images,omitempty"` // Optional: Number of images
|
||||
SearchLanguageFilter []string `json:"search_language_filter,omitempty"` // Optional: Search language filter
|
||||
ImageFormatFilter []string `json:"image_format_filter,omitempty"` // Optional: Image format filter
|
||||
ImageDomainFilter []string `json:"image_domain_filter,omitempty"` // Optional: Image domain filter
|
||||
SafeSearch *bool `json:"safe_search,omitempty"` // Optional: Enable safe search
|
||||
StreamMode *string `json:"stream_mode,omitempty"` // Optional: Stream mode
|
||||
ExtraParams map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// GetExtraParams implements the RequestBodyWithExtraParams interface
|
||||
func (r *PerplexityChatRequest) GetExtraParams() map[string]interface{} {
|
||||
return r.ExtraParams
|
||||
}
|
||||
|
||||
type WebSearchOption struct {
|
||||
SearchContextSize *string `json:"search_context_size,omitempty"` // "low" | "medium" | "high"
|
||||
UserLocation *WebSearchOptionUserLocation `json:"user_location,omitempty"` // The approximate location of the user
|
||||
ImageResultsEnhancedRelevance *bool `json:"image_results_enhanced_relevance,omitempty"` // Optional: Image results enhanced relevance
|
||||
SearchType *string `json:"search_type,omitempty"` // Optional: "general" | "news" | "academic" | "social" | "writing"
|
||||
}
|
||||
|
||||
type WebSearchOptionUserLocation struct {
|
||||
Latitude *float64 `json:"latitude,omitempty"`
|
||||
Longitude *float64 `json:"longitude,omitempty"`
|
||||
City *string `json:"city,omitempty"` // Free text input for the city
|
||||
Country *string `json:"country,omitempty"` // Two-letter ISO country code
|
||||
Region *string `json:"region,omitempty"` // Free text input for the region
|
||||
}
|
||||
|
||||
type MediaResponse struct {
|
||||
Overrides MediaResponseOverrides `json:"overrides,omitempty"` // Optional: Overrides for the media response
|
||||
}
|
||||
|
||||
type MediaResponseOverrides struct {
|
||||
ReturnVideos *bool `json:"return_videos,omitempty"` // Optional: Return videos
|
||||
ReturnImages *bool `json:"return_images,omitempty"` // Optional: Return images
|
||||
}
|
||||
|
||||
type PerplexityChatResponse struct {
|
||||
ID string `json:"id"`
|
||||
Choices []schemas.BifrostResponseChoice `json:"choices"`
|
||||
Created int `json:"created"` // The Unix timestamp (in seconds).
|
||||
Model string `json:"model"`
|
||||
Object string `json:"object"` // "chat.completion" or "chat.completion.chunk"
|
||||
Citations []string `json:"citations,omitempty"`
|
||||
SearchResults []schemas.SearchResult `json:"search_results,omitempty"`
|
||||
Videos []schemas.VideoResult `json:"videos,omitempty"`
|
||||
Usage *Usage `json:"usage,omitempty"`
|
||||
}
|
||||
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
SearchContextSize *string `json:"search_context_size,omitempty"`
|
||||
CitationTokens *int `json:"citation_tokens,omitempty"`
|
||||
NumSearchQueries *int `json:"num_search_queries,omitempty"`
|
||||
ReasoningTokens *int `json:"reasoning_tokens,omitempty"`
|
||||
Cost *schemas.BifrostCost `json:"cost,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user