boxcars 0.8.6 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c999092bd0fd0799ee3c97e74b0551e23fa5318eeb746fb17f52ed8b8e8e395
4
- data.tar.gz: 9e4c76ab93ad3cf305af160679b05a46d4b72fb04d0ee40298b7e362d387e582
3
+ metadata.gz: 8d461b03fdcd5b35e5cf0d236917f5a1a875bc5ba1d0d87bcc51ac25d3060fbc
4
+ data.tar.gz: fab5696e09771665769e639c0bdffa4a48033d9befa0ddf4b95863bd1074f1b4
5
5
  SHA512:
6
- metadata.gz: 2d7a5e62a2f0de60d789c97801d3832b2bd5aee4ceb82be59a8c1f66d724e931819412dede12027019c7d98b46b2a90e22c17660ed03ecab7d822ffa8290d260
7
- data.tar.gz: a2b51cef624374b84506cb176a3ee5b5732db9fbd87ce4b824b803cf641a623c2e1bb9e22bcb65b7a230f261c913823f1fd65081ac6aa4e6c7068f620b82b63f
6
+ metadata.gz: 1ccdc812780d55b34f4e7d9f2990f66f3cb9d90ba603052e0f867c4fec08e93e0c015fd6d9fc502bd0857a9a4f7230102fd5566c0fb384f9e2c98e3d0e0c9566
7
+ data.tar.gz: 8b1e234aeba1a85d6551b7bce2dc81c1b37447a6553df6f11e72228315391b00482647e0fed27aa919557a6016ee70ab03d4fc19fdb686d277188b555890339c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.8.6)
4
+ boxcars (0.8.7)
5
5
  faraday-retry (~> 2.0)
6
6
  google_search_results (~> 2.2)
7
7
  gpt4all (~> 0.0.5)
@@ -122,23 +122,9 @@ module Boxcars
122
122
  end
123
123
  end
124
124
 
125
- # make sure we got a valid response
126
- # @param response [Hash] The response to check.
127
- # @param must_haves [Array<String>] The keys that must be in the response. Defaults to %w[choices].
128
- # @raise [KeyError] if there is an issue with the access token.
129
- # @raise [ValueError] if the response is not valid.
130
- def check_response(response, must_haves: %w[completion])
131
- if response['error']
132
- code = response.dig('error', 'code')
133
- msg = response.dig('error', 'message') || 'unknown error'
134
- raise KeyError, "ANTHOPIC_API_KEY not valid" if code == 'invalid_api_key'
135
-
136
- raise ValueError, "Anthropic error: #{msg}"
137
- end
138
-
139
- must_haves.each do |key|
140
- raise ValueError, "Expecting key #{key} in response" unless response.key?(key)
141
- end
125
+ # validate_response! method uses the base implementation with Anthropic-specific must_haves
126
+ def validate_response!(response, must_haves: %w[completion])
127
+ super
142
128
  end
143
129
 
144
130
  # Call out to OpenAI's endpoint with k unique prompts.
@@ -155,7 +141,7 @@ module Boxcars
155
141
  prompts.each_slice(batch_size) do |sub_prompts|
156
142
  sub_prompts.each do |sprompts, inputs|
157
143
  response = client(prompt: sprompts, inputs:, **params)
158
- check_response(response)
144
+ validate_response!(response)
159
145
  choices << response
160
146
  end
161
147
  end
@@ -114,23 +114,9 @@ module Boxcars
114
114
  llm_params
115
115
  end
116
116
 
117
- # make sure we got a valid response
118
- # @param response [Hash] The response to check.
119
- # @param must_haves [Array<String>] The keys that must be in the response. Defaults to %w[choices].
120
- # @raise [KeyError] if there is an issue with the access token.
121
- # @raise [ValueError] if the response is not valid.
122
- def check_response(response, must_haves: %w[completion])
123
- if response['error']
124
- code = response.dig('error', 'code')
125
- msg = response.dig('error', 'message') || 'unknown error'
126
- raise KeyError, "ANTHOPIC_API_KEY not valid" if code == 'invalid_api_key'
127
-
128
- raise ValueError, "Cohere error: #{msg}"
129
- end
130
-
131
- must_haves.each do |key|
132
- raise ValueError, "Expecting key #{key} in response" unless response.key?(key)
133
- end
117
+ # validate_response! method uses the base implementation with Cohere-specific must_haves
118
+ def validate_response!(response, must_haves: %w[completion])
119
+ super
134
120
  end
135
121
 
136
122
  # the engine type
@@ -178,22 +178,9 @@ module Boxcars
178
178
  end
179
179
  end
180
180
 
181
- # check_response method might be partially covered by _gemini_handle_call_outcome
182
- # Retaining it if run method still uses it explicitly.
183
- def check_response(response, must_haves: %w[choices candidates])
184
- if response['error'].is_a?(Hash)
185
- code = response.dig('error', 'code')
186
- msg = response.dig('error', 'message') || 'unknown error'
187
- # GEMINI_API_TOKEN is not standard, usually it's an API key.
188
- # This check might need to align with actual error codes from Gemini.
189
- raise KeyError, "Gemini API Key not valid or permission issue" if ['invalid_api_key', 'permission_denied'].include?(code)
190
-
191
- raise ValueError, "GeminiAI error: #{msg}"
192
- end
193
-
194
- # Check for either 'choices' (OpenAI style) or 'candidates' (Gemini native style)
195
- has_valid_content = must_haves.any? { |key| response.key?(key) && !response[key].empty? }
196
- raise ValueError, "Expecting key like 'choices' or 'candidates' in response" unless has_valid_content
181
+ # validate_response! method uses the base implementation with Gemini-specific must_haves
182
+ def validate_response!(response, must_haves: %w[choices candidates])
183
+ super
197
184
  end
198
185
  end
199
186
  end
@@ -157,20 +157,9 @@ module Boxcars
157
157
  end
158
158
  end
159
159
 
160
- # Retaining check_response if run method or other parts still use it.
161
- def check_response(response, must_haves: %w[choices])
162
- if response['error'].is_a?(Hash)
163
- code = response.dig('error', 'code')
164
- msg = response.dig('error', 'message') || 'unknown error'
165
- # GROQ_API_TOKEN is not standard, usually it's an API key.
166
- raise KeyError, "Groq API Key not valid or permission issue" if ['invalid_api_key', 'permission_denied'].include?(code)
167
-
168
- raise ValueError, "Groq error: #{msg}"
169
- end
170
-
171
- must_haves.each do |key|
172
- raise ValueError, "Expecting key #{key} in response" unless response.key?(key) && !response[key].empty?
173
- end
160
+ # validate_response! method uses the base implementation with Groq-specific must_haves
161
+ def validate_response!(response, must_haves: %w[choices])
162
+ super
174
163
  end
175
164
  end
176
165
  end
@@ -119,10 +119,8 @@ module Boxcars
119
119
 
120
120
  private
121
121
 
122
- def check_response(response)
123
- return if response.is_a?(Hash) && response.key?("choices")
124
-
125
- raise Error, "Invalid response from #{provider}: #{response}"
122
+ def validate_response!(response, must_haves: %w[choices])
123
+ super
126
124
  end
127
125
  end
128
126
  end
@@ -100,14 +100,10 @@ module Boxcars
100
100
  end
101
101
 
102
102
  # -- Public helper -------------------------------------------------------------
103
- # Some callers outside this class still invoke `check_response` directly.
103
+ # Some callers outside this class still invoke `validate_response!` directly.
104
104
  # It simply raises if the JSON body contains an "error" payload.
105
- def check_response(response) # rubocop:disable Naming/PredicateMethod
106
- if (msg = openai_error_message(response))
107
- raise Boxcars::Error, msg
108
- end
109
-
110
- true
105
+ def validate_response!(response, must_haves: %w[choices])
106
+ super
111
107
  end
112
108
 
113
109
  private
@@ -55,10 +55,12 @@ module Boxcars
55
55
  messages_for_api = current_prompt_object.as_messages(inputs)[:messages]
56
56
  # Perplexity expects a 'model' and 'messages' structure.
57
57
  # Other params like temperature, max_tokens are top-level.
58
+ # Filter out parameters that Perplexity doesn't support
59
+ supported_params = filter_supported_params(current_params)
58
60
  api_request_params = {
59
- model: current_params[:model],
61
+ model: supported_params[:model],
60
62
  messages: messages_for_api
61
- }.merge(current_params.except(:model, :messages, :perplexity_api_key)) # Add other relevant params
63
+ }.merge(supported_params.except(:model, :messages, :perplexity_api_key))
62
64
 
63
65
  log_messages_debug(api_request_params[:messages]) if Boxcars.configuration.log_prompts && api_request_params[:messages]
64
66
 
@@ -115,17 +117,52 @@ module Boxcars
115
117
 
116
118
  def run(question, **)
117
119
  prompt = Prompt.new(template: question)
118
- answer = client(prompt:, inputs: {}, **)
120
+ response = client(prompt:, inputs: {}, **)
121
+ # Extract the content from the response for the run method
122
+ answer = extract_answer(response)
119
123
  Boxcars.debug("Answer: #{answer}", :cyan)
120
124
  answer
121
125
  end
122
126
 
127
+ # Extract answer content from the API response
128
+ def extract_answer(response)
129
+ if response.is_a?(Hash) && response["choices"]
130
+ response["choices"].map { |c| c.dig("message", "content") }.join("\n").strip
131
+ else
132
+ response.to_s
133
+ end
134
+ end
135
+
123
136
  def default_params
124
137
  @perplexity_params
125
138
  end
126
139
 
140
+ # validate_response! method uses the base implementation
141
+ def validate_response!(response, must_haves: %w[choices])
142
+ super
143
+ end
144
+
127
145
  private
128
146
 
147
+ # Filter out parameters that Perplexity doesn't support
148
+ def filter_supported_params(params)
149
+ # Perplexity supports these parameters based on their API documentation
150
+ supported_keys = %i[
151
+ model
152
+ messages
153
+ temperature
154
+ max_tokens
155
+ top_p
156
+ top_k
157
+ stream
158
+ presence_penalty
159
+ frequency_penalty
160
+ ]
161
+
162
+ # Remove unsupported parameters like stop, response_format, etc.
163
+ params.select { |key, _| supported_keys.include?(key.to_sym) }
164
+ end
165
+
129
166
  def log_messages_debug(messages)
130
167
  return unless messages.is_a?(Array)
131
168
 
@@ -141,10 +178,14 @@ module Boxcars
141
178
  msg = err_details ? "#{err_details['type']}: #{err_details['message']}" : "Unknown error from PerplexityAI API"
142
179
  raise Error, msg
143
180
  else
144
- choices = response_data.dig(:parsed_json, "choices")
145
- raise Error, "PerplexityAI: No choices found in response" unless choices.is_a?(Array) && !choices.empty?
181
+ parsed_response = response_data[:parsed_json]
182
+ unless parsed_response["choices"].is_a?(Array) && !parsed_response["choices"].empty?
183
+ raise Error,
184
+ "PerplexityAI: No choices found in response"
185
+ end
146
186
 
147
- choices.map { |c| c.dig("message", "content") }.join("\n").strip
187
+ # Return the full parsed JSON response (Hash) as expected by the base Engine class
188
+ parsed_response
148
189
  end
149
190
  end
150
191
 
@@ -70,7 +70,7 @@ module Boxcars
70
70
  raise TypeError, "Expected Hash from client method, got #{api_response_hash.class}: #{api_response_hash.inspect}"
71
71
  end
72
72
 
73
- check_response(api_response_hash)
73
+ validate_response!(api_response_hash)
74
74
 
75
75
  current_choices = api_response_hash["choices"]
76
76
  if current_choices.is_a?(Array)
@@ -108,6 +108,34 @@ module Boxcars
108
108
  response["output"] || response.to_s
109
109
  end
110
110
  end
111
+
112
+ # Validate API response and raise appropriate errors
113
+ # @param response [Hash] The response to validate.
114
+ # @param must_haves [Array<String>] The keys that must be in the response.
115
+ # @raise [KeyError] if there is an issue with the API key.
116
+ # @raise [Boxcars::Error] if the response is not valid.
117
+ def validate_response!(response, must_haves: %w[choices])
118
+ # Check for API errors first
119
+ if response['error']
120
+ error_details = response['error']
121
+ raise Boxcars::Error, "API error: #{error_details}" unless error_details.is_a?(Hash)
122
+
123
+ code = error_details['code']
124
+ message = error_details['message'] || 'unknown error'
125
+
126
+ # Handle common API key errors
127
+ raise KeyError, "API key not valid or permission denied" if ['invalid_api_key', 'permission_denied'].include?(code)
128
+
129
+ raise Boxcars::Error, "API error: #{message}"
130
+
131
+ end
132
+
133
+ # Check for required keys in response
134
+ has_required_content = must_haves.any? { |key| response.key?(key) && !response[key].nil? }
135
+ return if has_required_content
136
+
137
+ raise Boxcars::Error, "Response missing required keys. Expected one of: #{must_haves.join(', ')}"
138
+ end
111
139
  end
112
140
  end
113
141
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.8.6"
5
+ VERSION = "0.8.7"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boxcars
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francis Sullivan