Class: Boxcars::Perplexityai

Inherits:
Engine
  • Object
show all
Includes:
UnifiedObservability
Defined in:
lib/boxcars/engine/perplexityai.rb

Overview

A engine that uses PerplexityAI’s API.

Constant Summary collapse

DEFAULT_PARAMS =

Renamed from DEFAULT_PER_PARAMS for consistency

{ # Renamed from DEFAULT_PER_PARAMS for consistency
  model: "llama-3-sonar-large-32k-online", # Removed extra quotes
  temperature: 0.1
  # max_tokens can be part of kwargs if needed
}.freeze
DEFAULT_NAME =

max_tokens can be part of kwargs if needed

"PerplexityAI engine"
DEFAULT_DESCRIPTION =

Renamed from DEFAULT_PER_NAME

"useful for when you need to use Perplexity AI to answer questions. " \
"You should ask targeted questions"

Instance Attribute Summary collapse

Attributes inherited from Engine

#user_id

Instance Method Summary collapse

Methods inherited from Engine

#generate, #generation_info, #get_num_tokens

Constructor Details

#initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs) ⇒ Perplexityai

Returns a new instance of Perplexityai.



23
24
25
26
27
28
29
# File 'lib/boxcars/engine/perplexityai.rb', line 23

def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
  user_id = kwargs.delete(:user_id)
  @perplexity_params = DEFAULT_PARAMS.merge(kwargs)
  @prompts = prompts
  @batch_size = batch_size # Retain if used by generate
  super(description:, name:, user_id:)
end

Instance Attribute Details

#batch_sizeObject (readonly)

Returns the value of attribute batch_size.



12
13
14
# File 'lib/boxcars/engine/perplexityai.rb', line 12

def batch_size
  @batch_size
end

#model_kwargsObject (readonly)

Returns the value of attribute model_kwargs.



12
13
14
# File 'lib/boxcars/engine/perplexityai.rb', line 12

def model_kwargs
  @model_kwargs
end

#perplexity_paramsObject (readonly)

Returns the value of attribute perplexity_params.



12
13
14
# File 'lib/boxcars/engine/perplexityai.rb', line 12

def perplexity_params
  @perplexity_params
end

#promptsObject (readonly)

Returns the value of attribute prompts.



12
13
14
# File 'lib/boxcars/engine/perplexityai.rb', line 12

def prompts
  @prompts
end

Instance Method Details

#client(prompt:, inputs: {}, perplexity_api_key: nil, **kwargs) ⇒ Object

Main client method for interacting with the Perplexity API rubocop:disable Metrics/MethodLength



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/boxcars/engine/perplexityai.rb', line 38

def client(prompt:, inputs: {}, perplexity_api_key: nil, **kwargs)
  start_time = Time.now
  response_data = { response_obj: nil, parsed_json: nil, success: false, error: nil, status_code: nil }
  current_params = @perplexity_params.merge(kwargs)
  api_request_params = nil # Parameters actually sent to Perplexity API
  current_prompt_object = prompt.is_a?(Array) ? prompt.first : prompt

  begin
    api_key = perplexity_api_key || Boxcars.configuration.perplexity_api_key(**current_params.slice(:perplexity_api_key))
    raise Boxcars::ConfigurationError, "Perplexity API key not set" if api_key.nil? || api_key.strip.empty?

    conn = Faraday.new(url: "https://api.perplexity.ai") do |faraday|
      faraday.request :json
      faraday.response :json # Parse JSON response
      faraday.response :raise_error # Raise exceptions on 4xx/5xx
      faraday.adapter Faraday.default_adapter
    end

    messages_for_api = current_prompt_object.as_messages(inputs)[:messages]
    # Perplexity expects a 'model' and 'messages' structure.
    # Other params like temperature, max_tokens are top-level.
    # Filter out parameters that Perplexity doesn't support
    supported_params = filter_supported_params(current_params)
    api_request_params = {
      model: supported_params[:model],
      messages: messages_for_api
    }.merge(supported_params.except(:model, :messages, :perplexity_api_key))

    log_messages_debug(api_request_params[:messages]) if Boxcars.configuration.log_prompts && api_request_params[:messages]

    response = conn.post('/chat/completions') do |req|
      req.headers['Authorization'] = "Bearer #{api_key}"
      req.body = api_request_params
    end

    response_data[:response_obj] = response # Faraday response object
    response_data[:parsed_json] = response.body # Faraday with :json middleware parses body
    response_data[:status_code] = response.status

    if response.success? && response.body && response.body["choices"]
      response_data[:success] = true
    else
      response_data[:success] = false
      err_details = response.body["error"] if response.body.is_a?(Hash)
      msg = if err_details
              "#{err_details['type']}: #{err_details['message']}"
            else
              "Unknown Perplexity API Error (status: #{response.status})"
            end
      response_data[:error] = StandardError.new(msg)
    end
  rescue Faraday::Error => e # Catch Faraday specific errors (includes connection, timeout, 4xx/5xx)
    response_data[:error] = e
    response_data[:success] = false
    response_data[:status_code] = e.response_status if e.respond_to?(:response_status)
    response_data[:response_obj] = e.response if e.respond_to?(:response) # Store Faraday response if available
    response_data[:parsed_json] = e.response[:body] if e.respond_to?(:response) && e.response[:body].is_a?(Hash)
  rescue StandardError => e # Catch other unexpected errors
    response_data[:error] = e
    response_data[:success] = false
  ensure
    duration_ms = ((Time.now - start_time) * 1000).round
    request_context = {
      prompt: current_prompt_object,
      inputs:,
      user_id:,
      conversation_for_api: api_request_params&.dig(:messages)
    }
    track_ai_generation(
      duration_ms:,
      current_params:,
      request_context:,
      response_data:,
      provider: :perplexity_ai
    )
  end

  _perplexity_handle_call_outcome(response_data:)
end

#conversation_model?(_model_name) ⇒ Boolean

Perplexity models are conversational.

Returns:

  • (Boolean)


32
33
34
# File 'lib/boxcars/engine/perplexityai.rb', line 32

def conversation_model?(_model_name)
  true
end

#default_paramsObject



137
138
139
# File 'lib/boxcars/engine/perplexityai.rb', line 137

def default_params
  @perplexity_params
end

#extract_answer(response) ⇒ Object

Extract answer content from the API response



129
130
131
132
133
134
135
# File 'lib/boxcars/engine/perplexityai.rb', line 129

def extract_answer(response)
  if response.is_a?(Hash) && response["choices"]
    response["choices"].map { |c| c.dig("message", "content") }.join("\n").strip
  else
    response.to_s
  end
end

#run(question) ⇒ Object

rubocop:enable Metrics/MethodLength



119
120
121
122
123
124
125
126
# File 'lib/boxcars/engine/perplexityai.rb', line 119

def run(question, **)
  prompt = Prompt.new(template: question)
  response = client(prompt:, inputs: {}, **)
  # Extract the content from the response for the run method
  answer = extract_answer(response)
  Boxcars.debug("Answer: #{answer}", :cyan)
  answer
end

#validate_response!(response, must_haves: %w[choices])) ⇒ Object

validate_response! method uses the base implementation



142
143
144
# File 'lib/boxcars/engine/perplexityai.rb', line 142

def validate_response!(response, must_haves: %w[choices])
  super
end