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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -3
- data/.ruby-version +1 -1
- data/CHANGELOG.md +41 -0
- data/Gemfile +3 -13
- data/Gemfile.lock +29 -25
- data/POSTHOG_TEST_README.md +118 -0
- data/README.md +305 -0
- data/boxcars.gemspec +1 -2
- data/lib/boxcars/boxcar/active_record.rb +9 -10
- data/lib/boxcars/boxcar/calculator.rb +2 -2
- data/lib/boxcars/boxcar/engine_boxcar.rb +4 -4
- data/lib/boxcars/boxcar/google_search.rb +2 -2
- data/lib/boxcars/boxcar/json_engine_boxcar.rb +1 -1
- data/lib/boxcars/boxcar/ruby_calculator.rb +1 -1
- data/lib/boxcars/boxcar/sql_base.rb +4 -4
- data/lib/boxcars/boxcar/swagger.rb +3 -3
- data/lib/boxcars/boxcar/vector_answer.rb +3 -3
- data/lib/boxcars/boxcar/xml_engine_boxcar.rb +1 -1
- data/lib/boxcars/boxcar.rb +6 -6
- data/lib/boxcars/conversation_prompt.rb +3 -3
- data/lib/boxcars/engine/anthropic.rb +121 -23
- data/lib/boxcars/engine/cerebras.rb +2 -2
- data/lib/boxcars/engine/cohere.rb +135 -9
- data/lib/boxcars/engine/gemini_ai.rb +151 -76
- data/lib/boxcars/engine/google.rb +2 -2
- data/lib/boxcars/engine/gpt4all_eng.rb +92 -34
- data/lib/boxcars/engine/groq.rb +124 -73
- data/lib/boxcars/engine/intelligence_base.rb +52 -17
- data/lib/boxcars/engine/ollama.rb +127 -47
- data/lib/boxcars/engine/openai.rb +186 -103
- data/lib/boxcars/engine/perplexityai.rb +116 -136
- data/lib/boxcars/engine/together.rb +2 -2
- data/lib/boxcars/engine/unified_observability.rb +430 -0
- data/lib/boxcars/engine.rb +4 -3
- data/lib/boxcars/engines.rb +74 -0
- data/lib/boxcars/observability.rb +44 -0
- data/lib/boxcars/observability_backend.rb +17 -0
- data/lib/boxcars/observability_backends/multi_backend.rb +42 -0
- data/lib/boxcars/observability_backends/posthog_backend.rb +89 -0
- data/lib/boxcars/observation.rb +8 -8
- data/lib/boxcars/prompt.rb +16 -4
- data/lib/boxcars/result.rb +7 -12
- data/lib/boxcars/ruby_repl.rb +1 -1
- data/lib/boxcars/train/train_action.rb +1 -1
- data/lib/boxcars/train/xml_train.rb +3 -3
- data/lib/boxcars/train/xml_zero_shot.rb +1 -1
- data/lib/boxcars/train/zero_shot.rb +3 -3
- data/lib/boxcars/train.rb +1 -1
- data/lib/boxcars/vector_search.rb +5 -5
- data/lib/boxcars/vector_store/pgvector/build_from_array.rb +116 -88
- data/lib/boxcars/vector_store/pgvector/build_from_files.rb +106 -80
- data/lib/boxcars/vector_store/pgvector/save_to_database.rb +148 -122
- data/lib/boxcars/vector_store/pgvector/search.rb +157 -131
- data/lib/boxcars/vector_store.rb +4 -4
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +31 -20
- 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:
|
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:
|
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
|
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
|
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:
|
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:
|
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:
|
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
|
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:
|
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
|
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
|
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
|
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
|
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
|
18
|
-
api_key = Boxcars.configuration.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
|
40
|
+
sprompt = format(stock_prompt, wanted_data:, data_description:)
|
41
41
|
ctemplate = [
|
42
42
|
Boxcar.syst(sprompt),
|
43
43
|
Boxcar.user("%<input>s")
|
@@ -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:
|
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
|
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:
|
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:
|
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
|
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
|
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:
|
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
|
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
|
57
|
-
results = search.call
|
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.
|
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
|
data/lib/boxcars/boxcar.rb
CHANGED
@@ -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(
|
67
|
-
rv = conduct(
|
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(
|
80
|
+
def conduct(*, **)
|
81
81
|
Boxcars.info "> Entering #{name}#run", :gray, style: :bold
|
82
|
-
rv = depart(
|
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:
|
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:
|
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
|
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
|
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:
|
59
|
+
conversation.as_intelligence_conversation(inputs:)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|