boxcars 0.8.7 → 0.8.8

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: 8d461b03fdcd5b35e5cf0d236917f5a1a875bc5ba1d0d87bcc51ac25d3060fbc
4
- data.tar.gz: fab5696e09771665769e639c0bdffa4a48033d9befa0ddf4b95863bd1074f1b4
3
+ metadata.gz: 8261b5fd1cf581d141ae39fd3d567abd9751a98c6227c3f56c3d221275eb08d1
4
+ data.tar.gz: 5bb1a1f94a6076fa9f72542aa7117bd981d7f5191781cea917822109e93ace6c
5
5
  SHA512:
6
- metadata.gz: 1ccdc812780d55b34f4e7d9f2990f66f3cb9d90ba603052e0f867c4fec08e93e0c015fd6d9fc502bd0857a9a4f7230102fd5566c0fb384f9e2c98e3d0e0c9566
7
- data.tar.gz: 8b1e234aeba1a85d6551b7bce2dc81c1b37447a6553df6f11e72228315391b00482647e0fed27aa919557a6016ee70ab03d4fc19fdb686d277188b555890339c
6
+ metadata.gz: b763fabf8eb07f1d52dc558d51af423ebd96761fece00850862c41fefa1c342524ace6a733e097e4cd6696b80d3b6baabfb043a81c3734fa2f4b8f8f45b58a07
7
+ data.tar.gz: 264fcc8c5ba7e064a5f295ee1b8427ffaeccb072eb8bf89ac64e146214ce3485a317ba9a92ed8a874ae62dc86b5f973b1cea6d71f0361e8300c46ae956bd2151
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boxcars (0.8.7)
4
+ boxcars (0.8.8)
5
5
  faraday-retry (~> 2.0)
6
6
  google_search_results (~> 2.2)
7
7
  gpt4all (~> 0.0.5)
@@ -14,13 +14,13 @@ PATH
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- activemodel (7.2.2.1)
18
- activesupport (= 7.2.2.1)
19
- activerecord (7.2.2.1)
20
- activemodel (= 7.2.2.1)
21
- activesupport (= 7.2.2.1)
17
+ activemodel (7.2.2.2)
18
+ activesupport (= 7.2.2.2)
19
+ activerecord (7.2.2.2)
20
+ activemodel (= 7.2.2.2)
21
+ activesupport (= 7.2.2.2)
22
22
  timeout (>= 0.4.0)
23
- activesupport (7.2.2.1)
23
+ activesupport (7.2.2.2)
24
24
  base64
25
25
  benchmark (>= 0.3)
26
26
  bigdecimal
@@ -47,7 +47,7 @@ GEM
47
47
  protocol-http1 (~> 0.19.0)
48
48
  protocol-http2 (~> 0.16.0)
49
49
  traces (>= 0.10.0)
50
- async-http-faraday (0.22.0)
50
+ async-http-faraday (0.22.1)
51
51
  async-http (~> 0.42)
52
52
  faraday
53
53
  async-io (1.43.2)
@@ -59,7 +59,7 @@ GEM
59
59
  bigdecimal (3.2.2)
60
60
  concurrent-ruby (1.3.5)
61
61
  connection_pool (2.5.3)
62
- console (1.31.0)
62
+ console (1.33.0)
63
63
  fiber-annotation
64
64
  fiber-local (~> 1.1)
65
65
  json
@@ -74,10 +74,10 @@ GEM
74
74
  domain_name (0.6.20240107)
75
75
  dotenv (3.1.8)
76
76
  drb (2.2.3)
77
- dynamicschema (1.0.0)
78
- erb (5.0.1)
77
+ dynamicschema (1.0.1)
78
+ erb (5.0.2)
79
79
  event_stream_parser (1.0.0)
80
- faraday (2.13.1)
80
+ faraday (2.13.4)
81
81
  faraday-net_http (>= 2.0, < 3.5)
82
82
  json
83
83
  logger
@@ -119,12 +119,12 @@ GEM
119
119
  faraday (~> 2.7)
120
120
  json-repair (~> 0.2)
121
121
  mime-types (~> 3.6)
122
- io-console (0.8.0)
122
+ io-console (0.8.1)
123
123
  irb (1.15.2)
124
124
  pp (>= 0.6.0)
125
125
  rdoc (>= 4.0.0)
126
126
  reline (>= 0.4.2)
127
- json (2.12.2)
127
+ json (2.13.2)
128
128
  json-repair (0.2.0)
129
129
  language_server-protocol (3.17.0.5)
130
130
  lint_roller (1.1.0)
@@ -132,41 +132,47 @@ GEM
132
132
  mime-types (3.7.0)
133
133
  logger
134
134
  mime-types-data (~> 3.2025, >= 3.2025.0507)
135
- mime-types-data (3.2025.0701)
135
+ mime-types-data (3.2025.0819)
136
136
  minitest (5.25.5)
137
- multi_json (1.15.0)
137
+ multi_json (1.17.0)
138
138
  multipart-post (2.4.1)
139
139
  net-http (0.6.0)
140
140
  uri
141
141
  netrc (0.11.0)
142
142
  nio4r (2.7.4)
143
- nokogiri (1.18.8-aarch64-linux-gnu)
143
+ nokogiri (1.18.9-aarch64-linux-gnu)
144
144
  racc (~> 1.4)
145
- nokogiri (1.18.8-aarch64-linux-musl)
145
+ nokogiri (1.18.9-aarch64-linux-musl)
146
146
  racc (~> 1.4)
147
- nokogiri (1.18.8-arm-linux-gnu)
147
+ nokogiri (1.18.9-arm-linux-gnu)
148
148
  racc (~> 1.4)
149
- nokogiri (1.18.8-arm-linux-musl)
149
+ nokogiri (1.18.9-arm-linux-musl)
150
150
  racc (~> 1.4)
151
- nokogiri (1.18.8-arm64-darwin)
151
+ nokogiri (1.18.9-arm64-darwin)
152
152
  racc (~> 1.4)
153
- nokogiri (1.18.8-x86_64-darwin)
153
+ nokogiri (1.18.9-x86_64-darwin)
154
154
  racc (~> 1.4)
155
- nokogiri (1.18.8-x86_64-linux-gnu)
155
+ nokogiri (1.18.9-x86_64-linux-gnu)
156
156
  racc (~> 1.4)
157
- nokogiri (1.18.8-x86_64-linux-musl)
157
+ nokogiri (1.18.9-x86_64-linux-musl)
158
158
  racc (~> 1.4)
159
159
  octokit (4.25.1)
160
160
  faraday (>= 1, < 3)
161
161
  sawyer (~> 0.9)
162
162
  os (1.1.4)
163
163
  parallel (1.27.0)
164
- parser (3.3.8.0)
164
+ parser (3.3.9.0)
165
165
  ast (~> 2.4.1)
166
166
  racc
167
- pg (1.5.9)
167
+ pg (1.6.1)
168
+ pg (1.6.1-aarch64-linux)
169
+ pg (1.6.1-aarch64-linux-musl)
170
+ pg (1.6.1-arm64-darwin)
171
+ pg (1.6.1-x86_64-darwin)
172
+ pg (1.6.1-x86_64-linux)
173
+ pg (1.6.1-x86_64-linux-musl)
168
174
  pgvector (0.2.2)
169
- posthog-ruby (3.0.1)
175
+ posthog-ruby (3.1.2)
170
176
  concurrent-ruby (~> 1)
171
177
  pp (0.6.2)
172
178
  prettyprint
@@ -186,11 +192,11 @@ GEM
186
192
  racc (1.8.1)
187
193
  rainbow (3.1.1)
188
194
  rake (13.3.0)
189
- rdoc (6.14.1)
195
+ rdoc (6.14.2)
190
196
  erb
191
197
  psych (>= 4.0.0)
192
- regexp_parser (2.10.0)
193
- reline (0.6.1)
198
+ regexp_parser (2.11.2)
199
+ reline (0.6.2)
194
200
  io-console (~> 0.5)
195
201
  rest-client (2.1.0)
196
202
  http-accept (>= 1.7.0, < 2.0)
@@ -210,8 +216,8 @@ GEM
210
216
  rspec-mocks (3.13.5)
211
217
  diff-lcs (>= 1.2.0, < 2.0)
212
218
  rspec-support (~> 3.13.0)
213
- rspec-support (3.13.4)
214
- rubocop (1.77.0)
219
+ rspec-support (3.13.5)
220
+ rubocop (1.80.0)
215
221
  json (~> 2.3)
216
222
  language_server-protocol (~> 3.17.0.2)
217
223
  lint_roller (~> 1.1.0)
@@ -219,10 +225,10 @@ GEM
219
225
  parser (>= 3.3.0.2)
220
226
  rainbow (>= 2.2.2, < 4.0)
221
227
  regexp_parser (>= 2.9.3, < 3.0)
222
- rubocop-ast (>= 1.45.1, < 2.0)
228
+ rubocop-ast (>= 1.46.0, < 2.0)
223
229
  ruby-progressbar (~> 1.7)
224
230
  unicode-display_width (>= 2.4.0, < 4.0)
225
- rubocop-ast (1.45.1)
231
+ rubocop-ast (1.46.0)
226
232
  parser (>= 3.3.7.2)
227
233
  prism (~> 1.4)
228
234
  rubocop-rake (0.6.0)
@@ -234,7 +240,7 @@ GEM
234
240
  event_stream_parser (>= 0.3.0, < 2.0.0)
235
241
  faraday (>= 1)
236
242
  faraday-multipart (>= 1)
237
- ruby-openai (8.1.0)
243
+ ruby-openai (8.2.0)
238
244
  event_stream_parser (>= 0.3.0, < 2.0.0)
239
245
  faraday (>= 1)
240
246
  faraday-multipart (>= 1)
@@ -243,19 +249,19 @@ GEM
243
249
  addressable (>= 2.3.5)
244
250
  faraday (>= 0.17.3, < 3)
245
251
  securerandom (0.4.1)
246
- sqlite3 (2.7.1-aarch64-linux-gnu)
247
- sqlite3 (2.7.1-aarch64-linux-musl)
248
- sqlite3 (2.7.1-arm-linux-gnu)
249
- sqlite3 (2.7.1-arm-linux-musl)
250
- sqlite3 (2.7.1-arm64-darwin)
251
- sqlite3 (2.7.1-x86_64-darwin)
252
- sqlite3 (2.7.1-x86_64-linux-gnu)
253
- sqlite3 (2.7.1-x86_64-linux-musl)
252
+ sqlite3 (2.7.3-aarch64-linux-gnu)
253
+ sqlite3 (2.7.3-aarch64-linux-musl)
254
+ sqlite3 (2.7.3-arm-linux-gnu)
255
+ sqlite3 (2.7.3-arm-linux-musl)
256
+ sqlite3 (2.7.3-arm64-darwin)
257
+ sqlite3 (2.7.3-x86_64-darwin)
258
+ sqlite3 (2.7.3-x86_64-linux-gnu)
259
+ sqlite3 (2.7.3-x86_64-linux-musl)
254
260
  stringio (3.1.7)
255
261
  strings-ansi (0.2.0)
256
262
  timeout (0.4.3)
257
263
  timers (4.4.0)
258
- traces (0.15.2)
264
+ traces (0.17.0)
259
265
  tty-cursor (0.7.1)
260
266
  tty-progressbar (0.18.3)
261
267
  strings-ansi (~> 0.2)
@@ -32,7 +32,7 @@ module Boxcars
32
32
 
33
33
  Output Format:
34
34
  {
35
- %<wanted_data>s
35
+ %<wanted_data>s
36
36
  }
37
37
  SYSPR
38
38
  stock_prompt += "\n\nImportant:\n#{important}\n" unless important.to_s.empty?
@@ -7,6 +7,7 @@ module Boxcars
7
7
  # rubocop:disable Metrics/ClassLength
8
8
  class Anthropic < Engine
9
9
  include UnifiedObservability
10
+
10
11
  attr_reader :prompts, :llm_params, :model_kwargs, :batch_size
11
12
 
12
13
  # The default parameters to use when asking the engine.
@@ -5,6 +5,7 @@ module Boxcars
5
5
  # A engine that uses Cohere's API.
6
6
  class Cohere < Engine
7
7
  include UnifiedObservability
8
+
8
9
  attr_reader :prompts, :llm_params, :model_kwargs, :batch_size
9
10
 
10
11
  # The default parameters to use when asking the engine.
@@ -7,6 +7,7 @@ module Boxcars
7
7
  # A engine that uses GeminiAI's API via an OpenAI-compatible interface.
8
8
  class GeminiAi < Engine
9
9
  include UnifiedObservability
10
+
10
11
  attr_reader :prompts, :llm_params, :model_kwargs, :batch_size # Corrected typo llm_parmas to llm_params
11
12
 
12
13
  DEFAULT_PARAMS = {
@@ -7,6 +7,7 @@ module Boxcars
7
7
  # A engine that uses local GPT4All API.
8
8
  class Gpt4allEng < Engine
9
9
  include UnifiedObservability
10
+
10
11
  attr_reader :prompts, :model_kwargs, :batch_size, :gpt4all_params # Added gpt4all_params
11
12
 
12
13
  DEFAULT_NAME = "Gpt4all engine"
@@ -7,6 +7,7 @@ module Boxcars
7
7
  # A engine that uses Groq's API.
8
8
  class Groq < Engine
9
9
  include UnifiedObservability
10
+
10
11
  attr_reader :prompts, :groq_params, :model_kwargs, :batch_size
11
12
 
12
13
  DEFAULT_PARAMS = {
@@ -7,6 +7,7 @@ module Boxcars
7
7
  # A Base class for all Intelligence Engines
8
8
  class IntelligenceBase < Engine
9
9
  include Boxcars::UnifiedObservability
10
+
10
11
  attr_reader :provider, :all_params
11
12
 
12
13
  # The base Intelligence Engine is used by other engines to generate output from prompts
@@ -7,6 +7,7 @@ module Boxcars
7
7
  # A engine that uses a local Ollama API (OpenAI-compatible).
8
8
  class Ollama < Engine
9
9
  include UnifiedObservability
10
+
10
11
  attr_reader :prompts, :model_kwargs, :batch_size, :ollama_params
11
12
 
12
13
  DEFAULT_PARAMS = {
@@ -6,11 +6,13 @@ require "securerandom"
6
6
 
7
7
  module Boxcars
8
8
  # Engine that talks to OpenAI’s REST API.
9
- class Openai < Engine
9
+ class Openai < Engine # rubocop:disable Metrics/ClassLength
10
+ # include Boxcars::EngineHelpers
10
11
  include UnifiedObservability
11
12
 
12
13
  CHAT_MODEL_REGEX = /(^gpt-4)|(-turbo\b)|(^o\d)|(gpt-3\.5-turbo)/
13
14
  O_SERIES_REGEX = /^o/
15
+ GPT5_MODEL_REGEX = /\Agpt-[56].*/
14
16
 
15
17
  DEFAULT_PARAMS = {
16
18
  model: "gpt-4o-mini",
@@ -80,9 +82,9 @@ module Boxcars
80
82
  def run(question, **)
81
83
  prompt = Prompt.new(template: question)
82
84
  raw_json = client(prompt:, inputs: {}, **)
83
- extract_answer_from_choices(raw_json["choices"]).tap do |ans|
84
- Boxcars.debug("Answer: #{ans}", :cyan)
85
- end
85
+ ans = extract_answer(raw_json)
86
+ Boxcars.debug("Answer: #{ans}", :cyan)
87
+ ans
86
88
  end
87
89
 
88
90
  # Expose the defaults so callers can introspect or dup/merge them
@@ -103,14 +105,20 @@ module Boxcars
103
105
  # Some callers outside this class still invoke `validate_response!` directly.
104
106
  # It simply raises if the JSON body contains an "error" payload.
105
107
  def validate_response!(response, must_haves: %w[choices])
106
- super
108
+ if response.is_a?(Hash) && response.key?("output")
109
+ super(response, must_haves: %w[output])
110
+ else
111
+ super
112
+ end
107
113
  end
108
114
 
109
115
  private
110
116
 
111
117
  # -- Request construction ---------------------------------------------------
112
118
  def build_api_request(prompt_object, inputs, params, chat:)
113
- if chat
119
+ if gpt5_model?(params[:model])
120
+ build_responses_params(prompt_object, inputs, params.dup)
121
+ elsif chat
114
122
  build_chat_params(prompt_object, inputs, params.dup)
115
123
  else
116
124
  build_completion_params(prompt_object, inputs, params.dup)
@@ -133,9 +141,51 @@ module Boxcars
133
141
  { prompt: prompt_txt }.merge(params).tap { |h| h.delete(:messages) }
134
142
  end
135
143
 
144
+ def build_responses_params(prompt_object, inputs, params)
145
+ po = if prompt_object.is_a?(Boxcars::Prompt)
146
+ prompt_object
147
+ else
148
+ Boxcars::Prompt.new(template: prompt_object.to_s)
149
+ end
150
+
151
+ msg_hash = po.as_messages(inputs)
152
+ messages = msg_hash[:messages].is_a?(Array) ? msg_hash[:messages] : []
153
+ input_str = messages_to_input(messages)
154
+
155
+ p = params.dup
156
+ p.delete(:messages)
157
+ p.delete(:response_format)
158
+ p.delete(:stop)
159
+ p.delete(:temperature)
160
+ p[:max_output_tokens] = p.delete(:max_tokens) if p.key?(:max_tokens) && !p.key?(:max_output_tokens)
161
+ if (effort = p.delete(:reasoning_effort))
162
+ p[:reasoning] = { effort: effort }
163
+ end
164
+
165
+ formatted = { model: p[:model], input: input_str, _use_responses_api: true }
166
+ p.each { |k, v| formatted[k] = v unless k == :model }
167
+ formatted
168
+ end
169
+
170
+ def messages_to_input(messages)
171
+ return "" unless messages.is_a?(Array)
172
+
173
+ messages.map { |m| "#{m[:role]}: #{m[:content]}" }.join("\n")
174
+ end
175
+
136
176
  # -- API call / response ----------------------------------------------------
137
177
  def execute_api_call(client, chat_mode, api_request)
138
- if chat_mode
178
+ if api_request[:_use_responses_api]
179
+ call_params = api_request.dup
180
+ call_params.delete(:_use_responses_api)
181
+ Boxcars.debug("Input after formatting:\n#{call_params[:input]}", :cyan) if Boxcars.configuration.log_prompts
182
+ unless client.respond_to?(:responses)
183
+ raise StandardError,
184
+ "OpenAI Responses API not supported by installed ruby-openai gem. Upgrade ruby-openai to >7.0 version."
185
+ end
186
+
187
+ client.responses.create(parameters: call_params)
188
+ elsif chat_mode
139
189
  log_messages_debug(api_request[:messages]) if Boxcars.configuration.log_prompts
140
190
  client.chat(parameters: api_request)
141
191
  else
@@ -187,8 +237,117 @@ module Boxcars
187
237
  raise Error, "OpenAI: Could not extract answer from choices"
188
238
  end
189
239
 
240
+ def extract_answer_from_output(output_items) # rubocop:disable Metrics/PerceivedComplexity,Metrics/MethodLength
241
+ return nil unless output_items.is_a?(Array) && output_items.any?
242
+
243
+ texts = []
244
+
245
+ output_items.each do |i| # rubocop:disable Metrics/BlockLength
246
+ next unless i.is_a?(Hash)
247
+
248
+ case i["type"]
249
+ when "output_text"
250
+ content = i["content"]
251
+ if content.is_a?(Array)
252
+ # rubocop:disable Metrics/BlockNesting
253
+ texts << content.filter_map { |c|
254
+ if c.is_a?(Hash)
255
+ if c["text"].is_a?(String)
256
+ c["text"]
257
+ else
258
+ (c["text"].is_a?(Hash) ? (c["text"]["value"] || c["text"]["text"]) : nil)
259
+ end
260
+ end
261
+ }.join
262
+ # rubocop:enable Metrics/BlockNesting
263
+ elsif content.is_a?(String)
264
+ texts << content
265
+ end
266
+ # Some Responses payloads may include a direct "text" field.
267
+ if i["text"].is_a?(String)
268
+ texts << i["text"]
269
+ elsif i["text"].is_a?(Hash)
270
+ texts << (i["text"]["value"] || i["text"]["text"])
271
+ end
272
+ when "message"
273
+ content = i["content"]
274
+ if content.is_a?(Array)
275
+ parts = content.filter_map do |c|
276
+ next unless c.is_a?(Hash) && ["output_text", "text"].include?(c["type"])
277
+
278
+ t = c["text"]
279
+ if t.is_a?(String)
280
+ t
281
+ elsif t.is_a?(Hash)
282
+ t["value"] || t["text"]
283
+ elsif c["content"].is_a?(String)
284
+ c["content"]
285
+ end
286
+ end
287
+ texts << parts.join
288
+ end
289
+ end
290
+ end
291
+
292
+ return nil if texts.empty?
293
+
294
+ texts.join("\n").strip
295
+ end
296
+
297
+ def extract_answer(json)
298
+ if json.is_a?(Hash)
299
+ if json["output_text"].is_a?(String) && !json["output_text"].strip.empty?
300
+ return json["output_text"].strip
301
+ elsif json["output_text"].is_a?(Array)
302
+ joined = json["output_text"].map do |t|
303
+ if t.is_a?(String)
304
+ t
305
+ elsif t.is_a?(Hash)
306
+ t["value"] || t["text"] || t["content"]
307
+ end
308
+ end.compact.join("\n").strip
309
+ return joined unless joined.empty?
310
+ end
311
+
312
+ if json["output"].is_a?(Array)
313
+ out = extract_answer_from_output(json["output"])
314
+ return out unless out.nil? || out.strip.empty?
315
+ end
316
+ end
317
+
318
+ choices = json["choices"]
319
+ return extract_answer_from_choices(choices) if choices
320
+
321
+ # Fallback: attempt to find any text in nested Responses payloads
322
+ fallback = deep_extract_texts(json)
323
+ return fallback unless fallback.nil? || fallback.strip.empty?
324
+
325
+ raise Error, "OpenAI: Could not extract answer"
326
+ end
327
+
328
+ def deep_extract_texts(obj)
329
+ texts = []
330
+ stack = [obj]
331
+ while (cur = stack.pop)
332
+ case cur
333
+ when Hash
334
+ texts << cur["output_text"] if cur["output_text"].is_a?(String)
335
+ texts << cur["text"] if cur["text"].is_a?(String)
336
+ texts << cur["content"] if cur["content"].is_a?(String)
337
+ cur.each_value do |v|
338
+ stack << v if v.is_a?(Hash) || v.is_a?(Array)
339
+ end
340
+ when Array
341
+ cur.each { |v| stack << v if v.is_a?(Hash) || v.is_a?(Array) }
342
+ end
343
+ end
344
+ aggregated = texts.map { |t| t.to_s.strip }.reject(&:empty?).join("\n")
345
+ aggregated.empty? ? nil : aggregated
346
+ end
347
+
190
348
  # -- Utility helpers --------------------------------------------------------
191
349
  def chat_model?(model_name) = CHAT_MODEL_REGEX.match?(model_name)
350
+ def gpt5_model?(model_name) = GPT5_MODEL_REGEX.match?(model_name.to_s)
192
351
 
193
352
  def openai_error_message(json)
194
353
  err = json&.dig("error")
@@ -244,7 +403,13 @@ module Boxcars
244
403
  prompt: call_ctx[:prompt_object],
245
404
  inputs: call_ctx[:inputs],
246
405
  user_id: user_id,
247
- conversation_for_api: call_ctx[:is_chat_model] ? api_req[:messages] : api_req[:prompt]
406
+ conversation_for_api: if api_req.key?(:input)
407
+ api_req[:input]
408
+ elsif call_ctx[:is_chat_model]
409
+ api_req[:messages]
410
+ else
411
+ api_req[:prompt]
412
+ end
248
413
  },
249
414
  response_data: response_data,
250
415
  provider: :openai
@@ -8,6 +8,7 @@ module Boxcars
8
8
  # A engine that uses PerplexityAI's API.
9
9
  class Perplexityai < Engine
10
10
  include UnifiedObservability
11
+
11
12
  attr_reader :prompts, :perplexity_params, :model_kwargs, :batch_size
12
13
 
13
14
  DEFAULT_PARAMS = { # Renamed from DEFAULT_PER_PARAMS for consistency
@@ -75,8 +75,12 @@ module Boxcars
75
75
  current_choices = api_response_hash["choices"]
76
76
  if current_choices.is_a?(Array)
77
77
  choices.concat(current_choices)
78
+ elsif api_response_hash["output"]
79
+ # Synthesize a choice from non-Chat providers (e.g., OpenAI Responses API for GPT-5)
80
+ synthesized_text = extract_answer(api_response_hash)
81
+ choices << { "message" => { "content" => synthesized_text }, "finish_reason" => "stop" }
78
82
  else
79
- Boxcars.logger&.warn "No 'choices' found in API response: #{api_response_hash.inspect}"
83
+ Boxcars.logger&.warn "No 'choices' or 'output' found in API response: #{api_response_hash.inspect}"
80
84
  end
81
85
 
82
86
  api_usage = api_response_hash["usage"]
@@ -4,7 +4,7 @@ module Boxcars
4
4
  # Factory class for creating engine instances based on model names
5
5
  # Provides convenient shortcuts and aliases for different AI models
6
6
  class Engines
7
- DEFAULT_MODEL = "gemini-2.5-flash-preview-05-20"
7
+ DEFAULT_MODEL = "gemini-2.5-flash"
8
8
 
9
9
  # Create an engine instance based on the model name
10
10
  # @param model [String] The model name or alias
@@ -36,9 +36,9 @@ module Boxcars
36
36
  when "huge", "online_huge", "sonar-huge", "sonar-pro", "sonar_pro"
37
37
  Boxcars::Perplexityai.new(model: "sonar-pro", **kw_args)
38
38
  when "flash", "gemini-flash"
39
- Boxcars::GeminiAi.new(model: "gemini-2.5-flash-preview-05-20", **kw_args)
39
+ Boxcars::GeminiAi.new(model: "gemini-2.5-flash", **kw_args)
40
40
  when "gemini-pro"
41
- Boxcars::GeminiAi.new(model: "gemini-2.5-pro-preview-05-06", **kw_args)
41
+ Boxcars::GeminiAi.new(model: "gemini-2.5-pro", **kw_args)
42
42
  when /gemini-/
43
43
  Boxcars::GeminiAi.new(model:, **kw_args)
44
44
  when /-sonar-/
@@ -59,8 +59,11 @@ module Boxcars
59
59
  # @param kw_args [Hash] Additional arguments to pass to the engine
60
60
  # @return [Boxcars::Engine] An instance of the appropriate engine class
61
61
  def self.json_engine(model: nil, **kw_args)
62
- options = { temperature: 0.1, response_format: { type: "json_object" } }.merge(kw_args)
63
- options.delete(:response_format) if model.to_s =~ /sonnet|opus|sonar/ || model.to_s.start_with?("llama")
62
+ default_options = { temperature: 0.1 }
63
+ name = model.to_s
64
+ blocked = name.start_with?("gpt-5", "llama") || name.match?(/sonnet|opus|sonar/)
65
+ default_options[:response_format] = { type: "json_object" } unless blocked
66
+ options = default_options.merge(kw_args)
64
67
  engine(model:, **options)
65
68
  end
66
69
 
@@ -4,6 +4,7 @@ module Boxcars
4
4
  module VectorStore
5
5
  class EmbedViaTensorflow
6
6
  include VectorStore
7
+
7
8
  def call
8
9
  raise NotImplementedError
9
10
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Boxcars
4
4
  # The current version of the gem.
5
- VERSION = "0.8.7"
5
+ VERSION = "0.8.8"
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.7
4
+ version: 0.8.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francis Sullivan