boxcars 0.7.7 → 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/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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27e2420c9339daf37e4eb0c63f86fede297cf48931398a33b2a73054900b5288
|
4
|
+
data.tar.gz: f6fd74d8381fa7905715706d51fafdd179f47c4b021cb57caeacbadb9fdb40a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3844edd3c676e68aead5f2a461b401a11a7094d944b66f5b560d03f55a5ca49c936d0ddd8e4f58ac44a32300895c2d34fd5c52adc45fdfc489653b3cf5de2c78
|
7
|
+
data.tar.gz: f8844a5a5977a7769a495bc16b3bc1d6204501a42106fe2528ec6d60be559c08419eae242a1d3ed8b73c1dc9ff44e94c7f2e4e7dd06413483f4db3a66e299711
|
data/.rubocop.yml
CHANGED
@@ -6,7 +6,7 @@ plugins:
|
|
6
6
|
|
7
7
|
|
8
8
|
AllCops:
|
9
|
-
TargetRubyVersion: 3
|
9
|
+
TargetRubyVersion: 3.2
|
10
10
|
Exclude:
|
11
11
|
- 'bin/{rails,rake}'
|
12
12
|
- 'node_modules/**/*'
|
@@ -72,7 +72,7 @@ Metrics/BlockLength:
|
|
72
72
|
- 'spec/**/*'
|
73
73
|
|
74
74
|
Metrics/MethodLength:
|
75
|
-
Max:
|
75
|
+
Max: 40
|
76
76
|
Exclude:
|
77
77
|
- 'spec/**/*'
|
78
78
|
|
@@ -95,7 +95,7 @@ Naming/AccessorMethodName:
|
|
95
95
|
Enabled: false
|
96
96
|
|
97
97
|
Metrics/PerceivedComplexity:
|
98
|
-
Max:
|
98
|
+
Max: 20
|
99
99
|
|
100
100
|
Style/RescueStandardError:
|
101
101
|
Enabled: false
|
@@ -171,3 +171,6 @@ RSpec/ExampleLength:
|
|
171
171
|
|
172
172
|
RSpec/MultipleExpectations:
|
173
173
|
Enabled: false
|
174
|
+
|
175
|
+
RSpec/BeforeAfterAll:
|
176
|
+
Enabled: false
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.4.4
|
data/Gemfile
CHANGED
@@ -5,36 +5,26 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in boxcars.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "debug", "~> 1.9"
|
9
|
-
|
10
8
|
gem "dotenv", "~> 3.1"
|
11
|
-
|
12
9
|
gem "rake", "~> 13.2"
|
13
|
-
|
14
10
|
gem "sqlite3", "~> 2.0"
|
15
|
-
|
16
11
|
gem "async", "~>1.32.1"
|
17
|
-
|
18
12
|
gem "activerecord", "~> 7.1"
|
19
|
-
|
20
|
-
gem "github_changelog_generator", "~> 1.16"
|
21
|
-
|
22
13
|
gem "faraday-retry", "~> 2.0"
|
23
|
-
|
24
14
|
gem "activesupport", "~> 7.1"
|
25
|
-
|
26
15
|
gem "rest-client", "~> 2.1"
|
27
|
-
|
28
16
|
gem "hnswlib", "~> 0.9.0"
|
29
|
-
|
30
17
|
gem "pg", "~> 1.5"
|
31
18
|
gem "pgvector", "~> 0.2.2"
|
32
19
|
|
33
20
|
group :development, :test do
|
21
|
+
gem "debug", "~> 1.9"
|
34
22
|
gem "rspec", "~> 3.13"
|
35
23
|
gem "rubocop", "~> 1.67"
|
36
24
|
gem "vcr", "~> 6.3.1"
|
37
25
|
gem "webmock", "~> 3.24.0"
|
38
26
|
gem "rubocop-rake", "~> 0.6.0"
|
39
27
|
gem "rubocop-rspec", "~> 3.2"
|
28
|
+
gem "posthog-ruby", require: false
|
29
|
+
gem "github_changelog_generator", "~> 1.16", require: false
|
40
30
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boxcars (0.
|
4
|
+
boxcars (0.8.0)
|
5
5
|
google_search_results (~> 2.2)
|
6
6
|
gpt4all (~> 0.0.5)
|
7
7
|
hnswlib (~> 0.9)
|
8
8
|
intelligence (>= 0.8)
|
9
9
|
nokogiri (~> 1.18)
|
10
|
-
pgvector (~> 0.2)
|
11
10
|
ruby-anthropic (~> 0.4)
|
12
11
|
ruby-openai (>= 7.3)
|
13
12
|
|
@@ -54,11 +53,11 @@ GEM
|
|
54
53
|
async
|
55
54
|
async-pool (0.10.3)
|
56
55
|
async (>= 1.25)
|
57
|
-
base64 (0.
|
58
|
-
benchmark (0.4.
|
59
|
-
bigdecimal (3.1
|
56
|
+
base64 (0.3.0)
|
57
|
+
benchmark (0.4.1)
|
58
|
+
bigdecimal (3.2.1)
|
60
59
|
concurrent-ruby (1.3.5)
|
61
|
-
connection_pool (2.5.
|
60
|
+
connection_pool (2.5.3)
|
62
61
|
console (1.30.2)
|
63
62
|
fiber-annotation
|
64
63
|
fiber-local (~> 1.1)
|
@@ -70,13 +69,14 @@ GEM
|
|
70
69
|
debug (1.10.0)
|
71
70
|
irb (~> 1.10)
|
72
71
|
reline (>= 0.3.8)
|
73
|
-
diff-lcs (1.6.
|
72
|
+
diff-lcs (1.6.2)
|
74
73
|
domain_name (0.6.20240107)
|
75
74
|
dotenv (3.1.8)
|
76
|
-
drb (2.2.
|
75
|
+
drb (2.2.3)
|
77
76
|
dynamicschema (1.0.0.beta04)
|
77
|
+
erb (5.0.1)
|
78
78
|
event_stream_parser (1.0.0)
|
79
|
-
faraday (2.13.
|
79
|
+
faraday (2.13.1)
|
80
80
|
faraday-net_http (>= 2.0, < 3.5)
|
81
81
|
json
|
82
82
|
logger
|
@@ -106,7 +106,7 @@ GEM
|
|
106
106
|
faraday (~> 2.7)
|
107
107
|
os (~> 1.1)
|
108
108
|
tty-progressbar (~> 0.18.2)
|
109
|
-
hashdiff (1.
|
109
|
+
hashdiff (1.2.0)
|
110
110
|
hnswlib (0.9.0)
|
111
111
|
http-accept (1.7.0)
|
112
112
|
http-cookie (1.0.8)
|
@@ -123,15 +123,15 @@ GEM
|
|
123
123
|
pp (>= 0.6.0)
|
124
124
|
rdoc (>= 4.0.0)
|
125
125
|
reline (>= 0.4.2)
|
126
|
-
json (2.
|
126
|
+
json (2.12.2)
|
127
127
|
json-repair (0.2.0)
|
128
|
-
language_server-protocol (3.17.0.
|
128
|
+
language_server-protocol (3.17.0.5)
|
129
129
|
lint_roller (1.1.0)
|
130
130
|
logger (1.7.0)
|
131
|
-
mime-types (3.
|
131
|
+
mime-types (3.7.0)
|
132
132
|
logger
|
133
|
-
mime-types-data (~> 3.
|
134
|
-
mime-types-data (3.2025.
|
133
|
+
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
134
|
+
mime-types-data (3.2025.0527)
|
135
135
|
minitest (5.25.5)
|
136
136
|
multi_json (1.15.0)
|
137
137
|
multipart-post (2.4.1)
|
@@ -165,6 +165,8 @@ GEM
|
|
165
165
|
racc
|
166
166
|
pg (1.5.9)
|
167
167
|
pgvector (0.2.2)
|
168
|
+
posthog-ruby (3.0.1)
|
169
|
+
concurrent-ruby (~> 1)
|
168
170
|
pp (0.6.2)
|
169
171
|
prettyprint
|
170
172
|
prettyprint (0.2.0)
|
@@ -176,14 +178,15 @@ GEM
|
|
176
178
|
protocol-http2 (0.16.0)
|
177
179
|
protocol-hpack (~> 1.4)
|
178
180
|
protocol-http (~> 0.18)
|
179
|
-
psych (5.2.
|
181
|
+
psych (5.2.6)
|
180
182
|
date
|
181
183
|
stringio
|
182
|
-
public_suffix (6.0.
|
184
|
+
public_suffix (6.0.2)
|
183
185
|
racc (1.8.1)
|
184
186
|
rainbow (3.1.1)
|
185
|
-
rake (13.
|
186
|
-
rdoc (6.
|
187
|
+
rake (13.3.0)
|
188
|
+
rdoc (6.14.0)
|
189
|
+
erb
|
187
190
|
psych (>= 4.0.0)
|
188
191
|
regexp_parser (2.10.0)
|
189
192
|
reline (0.6.1)
|
@@ -194,20 +197,20 @@ GEM
|
|
194
197
|
mime-types (>= 1.16, < 4.0)
|
195
198
|
netrc (~> 0.8)
|
196
199
|
rexml (3.4.1)
|
197
|
-
rspec (3.13.
|
200
|
+
rspec (3.13.1)
|
198
201
|
rspec-core (~> 3.13.0)
|
199
202
|
rspec-expectations (~> 3.13.0)
|
200
203
|
rspec-mocks (~> 3.13.0)
|
201
|
-
rspec-core (3.13.
|
204
|
+
rspec-core (3.13.4)
|
202
205
|
rspec-support (~> 3.13.0)
|
203
|
-
rspec-expectations (3.13.
|
206
|
+
rspec-expectations (3.13.5)
|
204
207
|
diff-lcs (>= 1.2.0, < 2.0)
|
205
208
|
rspec-support (~> 3.13.0)
|
206
|
-
rspec-mocks (3.13.
|
209
|
+
rspec-mocks (3.13.5)
|
207
210
|
diff-lcs (>= 1.2.0, < 2.0)
|
208
211
|
rspec-support (~> 3.13.0)
|
209
|
-
rspec-support (3.13.
|
210
|
-
rubocop (1.75.
|
212
|
+
rspec-support (3.13.4)
|
213
|
+
rubocop (1.75.8)
|
211
214
|
json (~> 2.3)
|
212
215
|
language_server-protocol (~> 3.17.0.2)
|
213
216
|
lint_roller (~> 1.1.0)
|
@@ -292,6 +295,7 @@ DEPENDENCIES
|
|
292
295
|
hnswlib (~> 0.9.0)
|
293
296
|
pg (~> 1.5)
|
294
297
|
pgvector (~> 0.2.2)
|
298
|
+
posthog-ruby
|
295
299
|
rake (~> 13.2)
|
296
300
|
rest-client (~> 2.1)
|
297
301
|
rspec (~> 3.13)
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Boxcars Engines PostHog Observability Test
|
2
|
+
|
3
|
+
This test program demonstrates how to use the Boxcars library with PostHog observability backend to track AI engine usage.
|
4
|
+
|
5
|
+
## Prerequisites
|
6
|
+
|
7
|
+
1. **PostHog Ruby Gem**: Install the required gem
|
8
|
+
```bash
|
9
|
+
gem install posthog-ruby
|
10
|
+
```
|
11
|
+
|
12
|
+
2. **Environment Variables**: Ensure your `.env` file contains:
|
13
|
+
```
|
14
|
+
POSTHOG_API_KEY=your_posthog_project_api_key
|
15
|
+
POSTHOG_HOST=https://app.posthog.com # or your self-hosted instance
|
16
|
+
```
|
17
|
+
|
18
|
+
3. **AI Provider API Keys**: For testing different engines, you'll need:
|
19
|
+
```
|
20
|
+
openai_access_token=your_openai_token
|
21
|
+
GOOGLE_API_KEY=your_google_api_key
|
22
|
+
ANTHROPIC_API_KEY=your_anthropic_key
|
23
|
+
GROQ_API_KEY=your_groq_key
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
### Method 1: IRB Interactive Session (Recommended)
|
29
|
+
|
30
|
+
Start IRB with the required dependencies:
|
31
|
+
|
32
|
+
```bash
|
33
|
+
irb -r dotenv/load -r boxcars -r debug -r boxcars/observability_backends/posthog_backend
|
34
|
+
```
|
35
|
+
|
36
|
+
Then in the IRB session:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# Load and run the test
|
40
|
+
load 'test_engines_with_posthog.rb'
|
41
|
+
|
42
|
+
# Or set up PostHog backend manually:
|
43
|
+
Boxcars::Observability.backend = Boxcars::PosthogBackend.new(
|
44
|
+
api_key: ENV['POSTHOG_API_KEY'],
|
45
|
+
host: ENV['POSTHOG_HOST']
|
46
|
+
)
|
47
|
+
|
48
|
+
# Run manual tests
|
49
|
+
manual_test(model: 'gpt-4o', prompt: 'What is machine learning?')
|
50
|
+
manual_test(model: 'flash', prompt: 'Explain Ruby in one sentence')
|
51
|
+
manual_test(model: 'sonnet', prompt: 'Write a short poem about coding')
|
52
|
+
```
|
53
|
+
|
54
|
+
### Method 2: Direct Ruby Execution
|
55
|
+
|
56
|
+
```bash
|
57
|
+
ruby test_engines_with_posthog.rb
|
58
|
+
```
|
59
|
+
|
60
|
+
## What the Test Does
|
61
|
+
|
62
|
+
1. **Initializes PostHog Backend**: Sets up the PostHog observability backend with your API credentials
|
63
|
+
2. **Tests Multiple Engines**: Runs tests against various AI engines:
|
64
|
+
- Gemini Flash (Default)
|
65
|
+
- GPT-4o (OpenAI)
|
66
|
+
- Claude Sonnet (Anthropic)
|
67
|
+
- Groq Llama
|
68
|
+
3. **Tracks Observability Events**: Each API call generates PostHog events with AI-specific properties
|
69
|
+
4. **Provides Manual Testing**: Includes a `manual_test` function for interactive testing
|
70
|
+
|
71
|
+
## PostHog Events
|
72
|
+
|
73
|
+
The test will generate events in PostHog with properties like:
|
74
|
+
|
75
|
+
- `$ai_model`: The AI model used (e.g., "gpt-4o", "gemini-2.5-flash")
|
76
|
+
- `$ai_provider`: The provider (e.g., "openai", "google", "anthropic")
|
77
|
+
- `$ai_input_tokens`: Number of input tokens
|
78
|
+
- `$ai_output_tokens`: Number of output tokens
|
79
|
+
- `$ai_latency`: Response time in seconds
|
80
|
+
- `$ai_http_status`: HTTP status code
|
81
|
+
- `$ai_trace_id`: Unique trace identifier
|
82
|
+
- `$ai_is_error`: Boolean indicating if there was an error
|
83
|
+
|
84
|
+
## Viewing Results
|
85
|
+
|
86
|
+
After running the test:
|
87
|
+
|
88
|
+
1. Go to your PostHog dashboard
|
89
|
+
2. Navigate to Events or Live Events
|
90
|
+
3. Look for events with AI-related properties
|
91
|
+
4. You can create insights and dashboards to analyze AI usage patterns
|
92
|
+
|
93
|
+
## Troubleshooting
|
94
|
+
|
95
|
+
- **Missing PostHog gem**: Install with `gem install posthog-ruby`
|
96
|
+
- **Missing API keys**: Check your `.env` file has the required keys
|
97
|
+
- **Engine errors**: Some engines may fail if you don't have valid API keys for those providers
|
98
|
+
- **No events in PostHog**: Check your PostHog API key and host configuration
|
99
|
+
|
100
|
+
## Available Models
|
101
|
+
|
102
|
+
You can test with these model aliases:
|
103
|
+
|
104
|
+
- `flash` - Gemini 2.5 Flash
|
105
|
+
- `gpt-4o` - OpenAI GPT-4o
|
106
|
+
- `sonnet` - Claude Sonnet
|
107
|
+
- `groq` - Groq Llama
|
108
|
+
- `online` - Perplexity Sonar
|
109
|
+
- And many more (see `lib/boxcars/engines.rb`)
|
110
|
+
|
111
|
+
## Example Manual Tests
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
# Test different models
|
115
|
+
manual_test(model: 'flash', prompt: 'Explain quantum computing')
|
116
|
+
manual_test(model: 'gpt-4o', prompt: 'Write a Python function to sort a list')
|
117
|
+
manual_test(model: 'sonnet', prompt: 'What are the benefits of functional programming?')
|
118
|
+
manual_test(model: 'groq', prompt: 'Describe the difference between AI and ML')
|
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
|
|