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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/boxcars/engine/anthropic.rb +4 -18
- data/lib/boxcars/engine/cohere.rb +3 -17
- data/lib/boxcars/engine/gemini_ai.rb +3 -16
- data/lib/boxcars/engine/groq.rb +3 -14
- data/lib/boxcars/engine/intelligence_base.rb +2 -4
- data/lib/boxcars/engine/openai.rb +3 -7
- data/lib/boxcars/engine/perplexityai.rb +47 -6
- data/lib/boxcars/engine.rb +29 -1
- data/lib/boxcars/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d461b03fdcd5b35e5cf0d236917f5a1a875bc5ba1d0d87bcc51ac25d3060fbc
|
4
|
+
data.tar.gz: fab5696e09771665769e639c0bdffa4a48033d9befa0ddf4b95863bd1074f1b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ccdc812780d55b34f4e7d9f2990f66f3cb9d90ba603052e0f867c4fec08e93e0c015fd6d9fc502bd0857a9a4f7230102fd5566c0fb384f9e2c98e3d0e0c9566
|
7
|
+
data.tar.gz: 8b1e234aeba1a85d6551b7bce2dc81c1b37447a6553df6f11e72228315391b00482647e0fed27aa919557a6016ee70ab03d4fc19fdb686d277188b555890339c
|
data/Gemfile.lock
CHANGED
@@ -122,23 +122,9 @@ module Boxcars
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
125
|
-
#
|
126
|
-
|
127
|
-
|
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
|
-
|
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
|
-
#
|
118
|
-
|
119
|
-
|
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
|
-
#
|
182
|
-
|
183
|
-
|
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
|
data/lib/boxcars/engine/groq.rb
CHANGED
@@ -157,20 +157,9 @@ module Boxcars
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
-
#
|
161
|
-
def
|
162
|
-
|
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
|
123
|
-
|
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 `
|
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
|
106
|
-
|
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:
|
61
|
+
model: supported_params[:model],
|
60
62
|
messages: messages_for_api
|
61
|
-
}.merge(
|
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
|
-
|
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
|
-
|
145
|
-
|
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
|
-
|
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
|
|
data/lib/boxcars/engine.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/boxcars/version.rb
CHANGED