boxcars 0.7.6 → 0.8.0

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -3
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +41 -0
  5. data/Gemfile +3 -13
  6. data/Gemfile.lock +29 -25
  7. data/POSTHOG_TEST_README.md +118 -0
  8. data/README.md +305 -0
  9. data/boxcars.gemspec +1 -2
  10. data/lib/boxcars/boxcar/active_record.rb +9 -10
  11. data/lib/boxcars/boxcar/calculator.rb +2 -2
  12. data/lib/boxcars/boxcar/engine_boxcar.rb +4 -4
  13. data/lib/boxcars/boxcar/google_search.rb +2 -2
  14. data/lib/boxcars/boxcar/json_engine_boxcar.rb +1 -1
  15. data/lib/boxcars/boxcar/ruby_calculator.rb +1 -1
  16. data/lib/boxcars/boxcar/sql_base.rb +4 -4
  17. data/lib/boxcars/boxcar/swagger.rb +3 -3
  18. data/lib/boxcars/boxcar/vector_answer.rb +3 -3
  19. data/lib/boxcars/boxcar/xml_engine_boxcar.rb +1 -1
  20. data/lib/boxcars/boxcar.rb +6 -6
  21. data/lib/boxcars/conversation_prompt.rb +3 -3
  22. data/lib/boxcars/engine/anthropic.rb +121 -23
  23. data/lib/boxcars/engine/cerebras.rb +2 -2
  24. data/lib/boxcars/engine/cohere.rb +135 -9
  25. data/lib/boxcars/engine/gemini_ai.rb +151 -76
  26. data/lib/boxcars/engine/google.rb +2 -2
  27. data/lib/boxcars/engine/gpt4all_eng.rb +92 -34
  28. data/lib/boxcars/engine/groq.rb +124 -73
  29. data/lib/boxcars/engine/intelligence_base.rb +52 -17
  30. data/lib/boxcars/engine/ollama.rb +127 -47
  31. data/lib/boxcars/engine/openai.rb +186 -103
  32. data/lib/boxcars/engine/perplexityai.rb +116 -136
  33. data/lib/boxcars/engine/together.rb +2 -2
  34. data/lib/boxcars/engine/unified_observability.rb +430 -0
  35. data/lib/boxcars/engine.rb +4 -3
  36. data/lib/boxcars/engines.rb +74 -0
  37. data/lib/boxcars/observability.rb +44 -0
  38. data/lib/boxcars/observability_backend.rb +17 -0
  39. data/lib/boxcars/observability_backends/multi_backend.rb +42 -0
  40. data/lib/boxcars/observability_backends/posthog_backend.rb +89 -0
  41. data/lib/boxcars/observation.rb +8 -8
  42. data/lib/boxcars/prompt.rb +16 -4
  43. data/lib/boxcars/result.rb +7 -12
  44. data/lib/boxcars/ruby_repl.rb +1 -1
  45. data/lib/boxcars/train/train_action.rb +1 -1
  46. data/lib/boxcars/train/xml_train.rb +3 -3
  47. data/lib/boxcars/train/xml_zero_shot.rb +1 -1
  48. data/lib/boxcars/train/zero_shot.rb +3 -3
  49. data/lib/boxcars/train.rb +1 -1
  50. data/lib/boxcars/vector_search.rb +5 -5
  51. data/lib/boxcars/vector_store/pgvector/build_from_array.rb +116 -88
  52. data/lib/boxcars/vector_store/pgvector/build_from_files.rb +106 -80
  53. data/lib/boxcars/vector_store/pgvector/save_to_database.rb +148 -122
  54. data/lib/boxcars/vector_store/pgvector/search.rb +157 -131
  55. data/lib/boxcars/vector_store.rb +4 -4
  56. data/lib/boxcars/version.rb +1 -1
  57. data/lib/boxcars.rb +31 -20
  58. metadata +11 -21
data/README.md CHANGED
@@ -149,6 +149,311 @@ Also, if you set this flag: `Boxcars.configuration.log_prompts = true`
149
149
  The actual prompts handed to the connected Engine will be logged. This is off by default because it is very wordy, but handy if you are debugging prompts.
150
150
 
151
151
  Otherwise, we print to standard out.
152
+
153
+ ### Engine Factory (Engines)
154
+
155
+ Boxcars provides a convenient factory class `Boxcars::Engines` that simplifies creating engine instances using model names and aliases instead of remembering full class names and model strings.
156
+
157
+ #### Basic Usage
158
+
159
+ ```ruby
160
+ # Using default model (gemini-2.5-flash-preview-05-20)
161
+ engine = Boxcars::Engines.engine
162
+
163
+ # Using specific models with convenient aliases
164
+ gpt_engine = Boxcars::Engines.engine(model: "gpt-4o")
165
+ claude_engine = Boxcars::Engines.engine(model: "sonnet")
166
+ gemini_engine = Boxcars::Engines.engine(model: "flash")
167
+ groq_engine = Boxcars::Engines.engine(model: "groq")
168
+ ```
169
+
170
+ #### Supported Model Aliases
171
+
172
+ **OpenAI Models:**
173
+ - `"gpt-4o"`, `"gpt-3.5-turbo"`, `"o1-preview"` - Creates `Boxcars::Openai` engines
174
+
175
+ **Anthropic Models:**
176
+ - `"anthropic"`, `"sonnet"` - Creates `Boxcars::Anthropic` with Claude Sonnet
177
+ - `"opus"` - Creates `Boxcars::Anthropic` with Claude Opus
178
+ - `"claude-3-5-sonnet"`, etc. - Any model starting with "claude-"
179
+
180
+ **Groq Models:**
181
+ - `"groq"` - Creates `Boxcars::Groq` with Llama 3.3 70B
182
+ - `"deepseek"` - Creates `Boxcars::Groq` with DeepSeek R1
183
+ - `"mistral"` - Creates `Boxcars::Groq` with Mistral
184
+ - Models starting with `"mistral-"`, `"meta-llama/"`, or `"deepseek-"`
185
+
186
+ **Gemini Models:**
187
+ - `"flash"`, `"gemini-flash"` - Creates `Boxcars::GeminiAi` with Gemini 2.5 Flash
188
+ - `"gemini-pro"` - Creates `Boxcars::GeminiAi` with Gemini 2.5 Pro
189
+ - Any model starting with `"gemini-"`
190
+
191
+ **Perplexity Models:**
192
+ - `"online"`, `"sonar"` - Creates `Boxcars::Perplexityai` with Sonar
193
+ - `"sonar-pro"`, `"huge"` - Creates `Boxcars::Perplexityai` with Sonar Pro
194
+ - Models containing `"-sonar-"`
195
+
196
+ **Together AI Models:**
197
+ - `"together-model-name"` - Creates `Boxcars::Together` (strips "together-" prefix)
198
+
199
+ #### JSON-Optimized Engines
200
+
201
+ For applications requiring JSON responses, use the `json_engine` method:
202
+
203
+ ```ruby
204
+ # Creates engine optimized for JSON output
205
+ json_engine = Boxcars::Engines.json_engine(model: "gpt-4o")
206
+
207
+ # Automatically removes response_format for models that don't support it
208
+ json_claude = Boxcars::Engines.json_engine(model: "sonnet")
209
+ ```
210
+
211
+ #### Passing Additional Parameters
212
+
213
+ ```ruby
214
+ # Pass any additional parameters to the underlying engine
215
+ engine = Boxcars::Engines.engine(
216
+ model: "gpt-4o",
217
+ temperature: 0.7,
218
+ max_tokens: 1000,
219
+ top_p: 0.9
220
+ )
221
+ ```
222
+
223
+ #### Using with Boxcars
224
+
225
+ ```ruby
226
+ # Use the factory with any Boxcar
227
+ engine = Boxcars::Engines.engine(model: "sonnet")
228
+ calc = Boxcars::Calculator.new(engine: engine)
229
+ result = calc.run "What is 15 * 23?"
230
+
231
+ # Or in a Train
232
+ boxcars = [
233
+ Boxcars::Calculator.new(engine: Boxcars::Engines.engine(model: "gpt-4o")),
234
+ Boxcars::GoogleSearch.new(engine: Boxcars::Engines.engine(model: "flash"))
235
+ ]
236
+ train = Boxcars.train.new(boxcars: boxcars)
237
+ ```
238
+
239
+ ### Overriding the Default Engine Model
240
+
241
+ Boxcars provides several ways to override the default engine model used throughout your application. The default model is currently `"gemini-2.5-flash-preview-05-20"`, but you can customize this behavior.
242
+
243
+ #### Global Configuration
244
+
245
+ Set a global default model that will be used by `Boxcars::Engines.engine()` when no model is specified:
246
+
247
+ ```ruby
248
+ # Set the default model globally
249
+ Boxcars.configuration.default_model = "gpt-4o"
250
+
251
+ # Now all engines created without specifying a model will use GPT-4o
252
+ engine = Boxcars::Engines.engine # Uses gpt-4o
253
+ calc = Boxcars::Calculator.new # Uses gpt-4o via default engine
254
+ ```
255
+
256
+ #### Configuration Block
257
+
258
+ Use a configuration block for more organized setup:
259
+
260
+ ```ruby
261
+ Boxcars.configure do |config|
262
+ config.default_model = "sonnet" # Use Claude Sonnet as default
263
+ config.logger = Rails.logger # Set custom logger
264
+ config.log_prompts = true # Enable prompt logging
265
+ end
266
+ ```
267
+
268
+ #### Per-Instance Override
269
+
270
+ Override the model for specific engine instances:
271
+
272
+ ```ruby
273
+ # Global default is gemini-flash, but use different models per boxcar
274
+ default_engine = Boxcars::Engines.engine # Uses global default
275
+ gpt_engine = Boxcars::Engines.engine(model: "gpt-4o") # Uses GPT-4o
276
+ claude_engine = Boxcars::Engines.engine(model: "sonnet") # Uses Claude Sonnet
277
+
278
+ # Use different engines for different boxcars
279
+ calc = Boxcars::Calculator.new(engine: gpt_engine)
280
+ search = Boxcars::GoogleSearch.new(engine: claude_engine)
281
+ ```
282
+
283
+ #### Environment-Based Configuration
284
+
285
+ Set the default model via environment variables or initialization:
286
+
287
+ ```ruby
288
+ # In your application initialization (e.g., Rails initializer)
289
+ if Rails.env.production?
290
+ Boxcars.configuration.default_model = "gpt-4o" # Use GPT-4o in production
291
+ elsif Rails.env.development?
292
+ Boxcars.configuration.default_model = "flash" # Use faster Gemini Flash in development
293
+ else
294
+ Boxcars.configuration.default_model = "groq" # Use Groq for testing
295
+ end
296
+ ```
297
+
298
+ #### Model Resolution Priority
299
+
300
+ The `Boxcars::Engines.engine()` method resolves the model in this order:
301
+
302
+ 1. **Explicit model parameter**: `Boxcars::Engines.engine(model: "gpt-4o")`
303
+ 2. **Global configuration**: `Boxcars.configuration.default_model`
304
+ 3. **Built-in default**: `"gemini-2.5-flash-preview-05-20"`
305
+
306
+ #### Supported Model Aliases
307
+
308
+ When setting `default_model`, you can use any of the supported model aliases:
309
+
310
+ ```ruby
311
+ # These are all valid default_model values:
312
+ Boxcars.configuration.default_model = "gpt-4o" # OpenAI GPT-4o
313
+ Boxcars.configuration.default_model = "sonnet" # Claude Sonnet
314
+ Boxcars.configuration.default_model = "flash" # Gemini Flash
315
+ Boxcars.configuration.default_model = "groq" # Groq Llama
316
+ Boxcars.configuration.default_model = "online" # Perplexity Sonar
317
+ ```
318
+
319
+ #### Legacy Engine Configuration
320
+
321
+ You can also override the default engine class (though this is less common):
322
+
323
+ ```ruby
324
+ # Override the default engine class entirely
325
+ Boxcars.configuration.default_engine = Boxcars::Anthropic
326
+
327
+ # Now Boxcars.engine returns Anthropic instead of OpenAI
328
+ default_engine = Boxcars.engine # Returns Boxcars::Anthropic instance
329
+ ```
330
+
331
+ **Note**: When using `default_engine`, the `default_model` setting is ignored since you're specifying the engine class directly.
332
+
333
+ ### Observability
334
+
335
+ Boxcars includes a comprehensive observability system that allows you to track and monitor AI operations across your application. The system provides insights into LLM calls, performance metrics, errors, and usage patterns.
336
+
337
+ #### Core Components
338
+
339
+ **Observability Class**: The central tracking interface that provides a simple `track` method for recording events.
340
+
341
+ **ObservabilityBackend Module**: An interface that defines how tracking backends should be implemented. All backends must include this module and implement a `track` method.
342
+
343
+ **Built-in Backends**:
344
+ - **PosthogBackend**: Sends events to PostHog for analytics and user behavior tracking
345
+ - **MultiBackend**: Allows sending events to multiple backends simultaneously
346
+
347
+ #### Configuration
348
+
349
+ Set up observability by configuring a backend:
350
+
351
+ ```ruby
352
+ # Using PostHog backend
353
+ require 'boxcars/observability_backends/posthog_backend'
354
+
355
+ Boxcars.configure do |config|
356
+ config.observability_backend = Boxcars::PosthogBackend.new(
357
+ api_key: ENV['POSTHOG_API_KEY'] || 'your_posthog_api_key',
358
+ host: 'https://app.posthog.com' # or your self-hosted instance
359
+ )
360
+ end
361
+
362
+ # Using multiple backends
363
+ require 'boxcars/observability_backends/multi_backend'
364
+ backend1 = Boxcars::PosthogBackend.new(api_key: ENV['POSTHOG_API_KEY'])
365
+ backend2 = YourCustomBackend.new
366
+
367
+ Boxcars.configure do |config|
368
+ config.observability_backend = Boxcars::MultiBackend.new([backend1, backend2])
369
+ end
370
+ ```
371
+
372
+ #### Automatic Tracking
373
+
374
+ Boxcars automatically tracks LLM calls with detailed metrics:
375
+
376
+ ```ruby
377
+ # This automatically generates observability events
378
+ engine = Boxcars::Openai.new
379
+ calc = Boxcars::Calculator.new(engine: engine)
380
+ result = calc.run "what is 2 + 2?"
381
+ ```
382
+
383
+ **Tracked Properties Include**:
384
+ - `provider`: The LLM provider (e.g., "openai", "anthropic")
385
+ - `model_name`: The specific model used
386
+ - `prompt_content`: The conversation messages sent to the LLM
387
+ - `inputs`: Any template inputs provided
388
+ - `duration_ms`: Request duration in milliseconds
389
+ - `success`: Whether the call succeeded
390
+ - `status_code`: HTTP response status
391
+ - `error_message`: Error details if the call failed
392
+ - `response_raw_body`: Raw API response
393
+ - `api_call_parameters`: Parameters sent to the API
394
+
395
+ #### Manual Tracking
396
+
397
+ You can also track custom events:
398
+
399
+ ```ruby
400
+ Boxcars::Observability.track(
401
+ event: 'custom_operation',
402
+ properties: {
403
+ user_id: 'user_123',
404
+ operation_type: 'data_processing',
405
+ duration_ms: 150,
406
+ success: true
407
+ }
408
+ )
409
+ ```
410
+
411
+ #### Creating Custom Backends
412
+
413
+ Implement your own backend by including the `ObservabilityBackend` module:
414
+
415
+ ```ruby
416
+ class CustomBackend
417
+ include Boxcars::ObservabilityBackend
418
+
419
+ def track(event:, properties:)
420
+ # Your custom tracking logic here
421
+ puts "Event: #{event}, Properties: #{properties}"
422
+ end
423
+ end
424
+
425
+ Boxcars.configure do |config|
426
+ config.observability_backend = CustomBackend.new
427
+ end
428
+ ```
429
+
430
+ #### Error Handling
431
+
432
+ The observability system is designed to fail silently to prevent tracking issues from disrupting your main application flow. If a backend raises an error, it will be caught and ignored, ensuring your AI operations continue uninterrupted.
433
+
434
+ #### PostHog Integration
435
+
436
+ The PostHog backend requires the `posthog-ruby` gem:
437
+
438
+ ```ruby
439
+ # Add to your Gemfile
440
+ gem 'posthog-ruby'
441
+
442
+ # Configure the backend
443
+ Boxcars.configure do |config|
444
+ config.observability_backend = Boxcars::PosthogBackend.new(
445
+ api_key: ENV['POSTHOG_API_KEY'],
446
+ host: 'https://app.posthog.com',
447
+ on_error: proc { |status, body|
448
+ Rails.logger.warn "PostHog error: #{status} - #{body}"
449
+ }
450
+ )
451
+ end
452
+ ```
453
+
454
+ Events are automatically associated with users when a `user_id` property is provided. Anonymous events use a default identifier.
455
+
456
+
152
457
  ## Development
153
458
 
154
459
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/boxcars.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "You simply set an OpenAI key, give a number of Boxcars to a Train, and magic ensues when you run it."
13
13
  spec.homepage = "https://github.com/BoxcarsAI/boxcars"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 3.0"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = spec.homepage
@@ -36,7 +36,6 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency "hnswlib", "~> 0.9"
37
37
  spec.add_dependency "intelligence", ">= 0.8"
38
38
  spec.add_dependency "nokogiri", "~> 1.18"
39
- spec.add_dependency "pgvector", "~> 0.2"
40
39
  spec.add_dependency "ruby-anthropic", "~> 0.4"
41
40
  spec.add_dependency "ruby-openai", ">= 7.3"
42
41
 
@@ -23,14 +23,14 @@ module Boxcars
23
23
  @read_only = read_only.nil? ? !approval_callback : read_only
24
24
  @code_only = kwargs.delete(:code_only) || false
25
25
  kwargs[:name] ||= get_name
26
- kwargs[:description] ||= format(ARDESC, name: name)
26
+ kwargs[:description] ||= format(ARDESC, name:)
27
27
  kwargs[:prompt] ||= my_prompt
28
28
  super(**kwargs)
29
29
  end
30
30
 
31
31
  # @return Hash The additional variables for this boxcar.
32
32
  def prediction_additional(_inputs)
33
- { model_info: model_info }.merge super
33
+ { model_info: }.merge super
34
34
  end
35
35
 
36
36
  CTEMPLATE = [
@@ -150,16 +150,15 @@ module Boxcars
150
150
  # run the code in a safe environment
151
151
  # @param code [String] The code to run
152
152
  # @return [Object] The result of the code
153
- def eval_safe_wrapper(code)
153
+ def eval_safe_wrapper(code, binding = TOPLEVEL_BINDING)
154
154
  # if the code used ActiveRecord, we need to add :: in front of it to escape the module
155
155
  new_code = code.gsub(/\b(ActiveRecord::)/, '::\1')
156
-
157
156
  # sometimes the code will have a puts or print in it, which will miss. Remove them.
158
157
  new_code = new_code.gsub(/\b(puts|print)\b/, '')
158
+
159
159
  proc do
160
- $SAFE = 4
161
160
  # rubocop:disable Security/Eval
162
- eval new_code
161
+ eval(new_code, binding)
163
162
  # rubocop:enable Security/Eval
164
163
  end.call
165
164
  end
@@ -231,24 +230,24 @@ module Boxcars
231
230
  def get_active_record_answer(text)
232
231
  changes_code = extract_code text.split('ARCode:').first.split('ARChanges:').last.strip if text =~ /^ARChanges:/
233
232
  code = extract_code text.split('ARCode:').last.strip
234
- return Result.new(status: :ok, explanation: "code to run", code: code, changes_code: changes_code) if code_only?
233
+ return Result.new(status: :ok, explanation: "code to run", code:, changes_code:) if code_only?
235
234
 
236
235
  have_approval = false
237
236
  begin
238
237
  have_approval = approved?(changes_code, code)
239
238
  rescue NameError, Error => e
240
- return Result.new(status: :error, explanation: error_message(e, "ARChanges"), changes_code: changes_code)
239
+ return Result.new(status: :error, explanation: error_message(e, "ARChanges"), changes_code:)
241
240
  end
242
241
 
243
242
  raise SecurityError, "Permission to run code that makes changes denied" unless have_approval
244
243
 
245
244
  begin
246
245
  output = clean_up_output(run_active_record_code(code))
247
- Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
246
+ Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code:)
248
247
  rescue SecurityError => e
249
248
  raise e
250
249
  rescue ::StandardError => e
251
- Result.new(status: :error, answer: nil, explanation: error_message(e, "ARCode"), code: code)
250
+ Result.new(status: :error, answer: nil, explanation: error_message(e, "ARCode"), code:)
252
251
  end
253
252
  end
254
253
 
@@ -15,7 +15,7 @@ module Boxcars
15
15
  kwargs[:stop] ||= ["```output"]
16
16
  kwargs[:name] ||= "Calculator"
17
17
  kwargs[:description] ||= CALCDESC
18
- super(engine: engine, prompt: the_prompt, **kwargs)
18
+ super(engine:, prompt: the_prompt, **kwargs)
19
19
  end
20
20
 
21
21
  # our template
@@ -47,7 +47,7 @@ module Boxcars
47
47
  code = text.split("```ruby\n").last.split("```").first.strip
48
48
  # code = text[8..-4].split("```").first.strip
49
49
  ruby_executor = Boxcars::RubyREPL.new
50
- ruby_executor.call(code: code)
50
+ ruby_executor.call(code:)
51
51
  end
52
52
 
53
53
  def get_answer(text)
@@ -46,7 +46,7 @@ module Boxcars
46
46
  stop = input_list[0][:stop]
47
47
  the_prompt = current_conversation ? prompt.with_conversation(current_conversation) : prompt
48
48
  prompts = input_list.map { |inputs| [the_prompt, inputs] }
49
- engine.generate(prompts: prompts, stop: stop)
49
+ engine.generate(prompts:, stop:)
50
50
  end
51
51
 
52
52
  # apply a response from the engine
@@ -54,7 +54,7 @@ module Boxcars
54
54
  # @param current_conversation [Boxcars::Conversation] Optional ongoing conversation to use for the prompt.
55
55
  # @return [Hash] A hash of the output key and the output value.
56
56
  def apply(input_list:, current_conversation: nil)
57
- response = generate(input_list: input_list, current_conversation: current_conversation)
57
+ response = generate(input_list:, current_conversation:)
58
58
  response.generations.to_h do |generation|
59
59
  [output_key, generation[0].text]
60
60
  end
@@ -65,7 +65,7 @@ module Boxcars
65
65
  # @param kwargs [Hash] A hash of input values to use for the prompt.
66
66
  # @return [String] The output value.
67
67
  def predict(current_conversation: nil, **kwargs)
68
- prediction = apply(current_conversation: current_conversation, input_list: [kwargs])[output_key]
68
+ prediction = apply(current_conversation:, input_list: [kwargs])[output_key]
69
69
  Boxcars.debug(prediction, :white) if Boxcars.configuration.log_generated
70
70
  prediction
71
71
  end
@@ -115,7 +115,7 @@ module Boxcars
115
115
 
116
116
  # @return Hash The additional variables for this boxcar.
117
117
  def prediction_additional(_inputs)
118
- { stop: stop, top_k: top_k }
118
+ { stop:, top_k: }
119
119
  end
120
120
 
121
121
  # @param inputs [Hash] The inputs to the boxcar.
@@ -14,8 +14,8 @@ module Boxcars
14
14
  # @param description [String] A description of the boxcar. Defaults to SERPDESC.
15
15
  # @param serpapi_api_key [String] The API key to use for the SerpAPI. Defaults to Boxcars.configuration.serpapi_api_key.
16
16
  def initialize(name: "Search", description: SERPDESC, serpapi_api_key: nil)
17
- super(name: name, description: description)
18
- api_key = Boxcars.configuration.serpapi_api_key(serpapi_api_key: serpapi_api_key)
17
+ super(name:, description:)
18
+ api_key = Boxcars.configuration.serpapi_api_key(serpapi_api_key:)
19
19
  ::GoogleSearch.api_key = api_key
20
20
  end
21
21
 
@@ -37,7 +37,7 @@ module Boxcars
37
37
  SYSPR
38
38
  stock_prompt += "\n\nImportant:\n#{important}\n" unless important.to_s.empty?
39
39
 
40
- sprompt = format(stock_prompt, wanted_data: wanted_data, data_description: data_description)
40
+ sprompt = format(stock_prompt, wanted_data:, data_description:)
41
41
  ctemplate = [
42
42
  Boxcar.syst(sprompt),
43
43
  Boxcar.user("%<input>s")
@@ -30,7 +30,7 @@ module Boxcars
30
30
  def run(question)
31
31
  code = "puts(#{question})"
32
32
  ruby_executor = Boxcars::RubyREPL.new
33
- rv = ruby_executor.call(code: code)
33
+ rv = ruby_executor.call(code:)
34
34
  puts "Question: #{question}"
35
35
  puts "Answer: #{rv}"
36
36
  rv
@@ -20,7 +20,7 @@ module Boxcars
20
20
  @connection = connection
21
21
  check_tables(tables, except_tables)
22
22
  kwargs[:name] ||= "Database"
23
- kwargs[:description] ||= format(SQLDESC, name: name)
23
+ kwargs[:description] ||= format(SQLDESC, name:)
24
24
  kwargs[:prompt] ||= my_prompt
25
25
  kwargs[:stop] ||= ["SQLResult:"]
26
26
 
@@ -29,7 +29,7 @@ module Boxcars
29
29
 
30
30
  # @return Hash The additional variables for this boxcar.
31
31
  def prediction_additional(_inputs)
32
- { schema: schema, dialect: dialect }.merge super
32
+ { schema:, dialect: }.merge super
33
33
  end
34
34
 
35
35
  CTEMPLATE = [
@@ -107,9 +107,9 @@ module Boxcars
107
107
  code = extract_code text.split('SQLQuery:').last.strip
108
108
  Boxcars.debug code, :yellow
109
109
  output = clean_up_output(code)
110
- Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code: code)
110
+ Result.new(status: :ok, answer: output, explanation: "Answer: #{output.to_json}", code:)
111
111
  rescue StandardError => e
112
- Result.new(status: :error, answer: nil, explanation: "Error: #{e.message}", code: code)
112
+ Result.new(status: :error, answer: nil, explanation: "Error: #{e.message}", code:)
113
113
  end
114
114
 
115
115
  def get_answer(text)
@@ -21,12 +21,12 @@ module Boxcars
21
21
  kwargs[:stop] ||= ["```output"]
22
22
  kwargs[:name] ||= "Swagger API"
23
23
  kwargs[:description] ||= DESC
24
- super(engine: engine, prompt: the_prompt, **kwargs)
24
+ super(engine:, prompt: the_prompt, **kwargs)
25
25
  end
26
26
 
27
27
  # @return Hash The additional variables for this boxcar.
28
28
  def prediction_additional(_inputs)
29
- { swagger_url: swagger_url, context: context }.merge super
29
+ { swagger_url:, context: }.merge super
30
30
  end
31
31
 
32
32
  # our template
@@ -52,7 +52,7 @@ module Boxcars
52
52
  def get_embedded_ruby_answer(text)
53
53
  code = text.split("```ruby\n").last.split("```").first.strip
54
54
  ruby_executor = Boxcars::RubyREPL.new
55
- ruby_executor.call(code: code)
55
+ ruby_executor.call(code:)
56
56
  end
57
57
 
58
58
  def get_answer(text)
@@ -21,7 +21,7 @@ module Boxcars
21
21
  kwargs[:stop] ||= ["```output"]
22
22
  kwargs[:name] ||= "VectorAnswer"
23
23
  kwargs[:description] ||= DESC
24
- super(engine: engine, prompt: the_prompt, **kwargs)
24
+ super(engine:, prompt: the_prompt, **kwargs)
25
25
  end
26
26
 
27
27
  # @param inputs [Hash] The inputs to use for the prediction.
@@ -53,8 +53,8 @@ module Boxcars
53
53
  # @params count [Integer] The number of results to return.
54
54
  # @return [String] The content of the search results.
55
55
  def get_search_content(question, count: 1)
56
- search = Boxcars::VectorSearch.new(embeddings: embeddings, vector_documents: vector_documents)
57
- results = search.call query: question, count: count
56
+ search = Boxcars::VectorSearch.new(embeddings:, vector_documents:)
57
+ results = search.call(query: question, count:)
58
58
  @search_content = get_results_content(results)
59
59
  end
60
60
 
@@ -21,7 +21,7 @@ module Boxcars
21
21
  def xn_get_answer(xnode)
22
22
  reply = xnode.xtext("//reply")
23
23
 
24
- if reply.present?
24
+ if reply && !reply.to_s.strip.empty?
25
25
  Result.new(status: :ok, answer: reply, explanation: reply)
26
26
  else
27
27
  # we have an unexpected output from the engine
@@ -63,8 +63,8 @@ module Boxcars
63
63
  # @param kwargs [Hash] The keyword arguments to pass to the boxcar.
64
64
  # you can pass one or the other, but not both.
65
65
  # @return [String] The answer to the question.
66
- def run(*args, **kwargs)
67
- rv = conduct(*args, **kwargs)
66
+ def run(*, **)
67
+ rv = conduct(*, **)
68
68
  rv = rv[:answer] if rv.is_a?(Hash) && rv.key?(:answer)
69
69
  return rv.answer if rv.is_a?(Result)
70
70
  return rv[output_keys[0]] if rv.is_a?(Hash)
@@ -77,9 +77,9 @@ module Boxcars
77
77
  # @param kwargs [Hash] The keyword arguments to pass to the boxcar.
78
78
  # you can pass one or the other, but not both.
79
79
  # @return [Boxcars::Result] The answer to the question.
80
- def conduct(*args, **kwargs)
80
+ def conduct(*, **)
81
81
  Boxcars.info "> Entering #{name}#run", :gray, style: :bold
82
- rv = depart(*args, **kwargs)
82
+ rv = depart(*, **)
83
83
  remember_history(rv)
84
84
  Boxcars.info "< Exiting #{name}#run", :gray, style: :bold
85
85
  rv
@@ -163,7 +163,7 @@ module Boxcars
163
163
  inputs = our_inputs(inputs)
164
164
  output = nil
165
165
  begin
166
- output = call(inputs: inputs)
166
+ output = call(inputs:)
167
167
  rescue StandardError => e
168
168
  Boxcars.error "Error in #{name} boxcar#call: #{e}\nbt:#{e.backtrace[0..5].join("\n ")}", :red
169
169
  Boxcars.error("Response Body: #{e.response[:body]}", :red) if e.respond_to?(:response) && !e.response.nil?
@@ -199,7 +199,7 @@ module Boxcars
199
199
  end
200
200
  inputs = { input_keys.first => inputs }
201
201
  end
202
- validate_inputs(inputs: inputs)
202
+ validate_inputs(inputs:)
203
203
  end
204
204
 
205
205
  # the default answer is the text passed in
@@ -11,7 +11,7 @@ module Boxcars
11
11
  # @param output_variables [Array<Symbol>] The output vars to use for the prompt. Defaults to [:output]
12
12
  def initialize(conversation:, input_variables: nil, other_inputs: nil, output_variables: nil)
13
13
  @conversation = conversation
14
- super(template: template, input_variables: input_variables, other_inputs: other_inputs, output_variables: output_variables)
14
+ super(template:, input_variables:, other_inputs:, output_variables:)
15
15
  end
16
16
 
17
17
  # prompt for chatGPT params
@@ -25,7 +25,7 @@ module Boxcars
25
25
  # @param inputs [Hash] The inputs to use for the prompt.
26
26
  # @return [Hash] The formatted prompt.
27
27
  def as_prompt(inputs:, prefixes: default_prefixes, show_roles: false)
28
- { prompt: conversation.as_prompt(inputs: inputs, prefixes: prefixes, show_roles: show_roles) }
28
+ { prompt: conversation.as_prompt(inputs:, prefixes:, show_roles:) }
29
29
  end
30
30
 
31
31
  # tack on the ongoing conversation if present to the prompt
@@ -56,7 +56,7 @@ module Boxcars
56
56
  # @param inputs [Hash] The inputs to use for the prompt
57
57
  # @return [Intelligence::Conversation] The converted conversation
58
58
  def as_intelligence_conversation(inputs: nil)
59
- conversation.as_intelligence_conversation(inputs: inputs)
59
+ conversation.as_intelligence_conversation(inputs:)
60
60
  end
61
61
  end
62
62
  end