boxcars 0.8.1 → 0.8.3
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/CHANGELOG.md +8 -0
- data/Gemfile.lock +8 -8
- data/README.md +29 -12
- data/lib/boxcars/engine/gemini_ai.rb +6 -2
- data/lib/boxcars/engine/groq.rb +6 -2
- data/lib/boxcars/engine/openai.rb +2 -0
- data/lib/boxcars/engine.rb +33 -6
- data/lib/boxcars/observability_backends/posthog_backend.rb +8 -19
- data/lib/boxcars/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 93e000d3596a656454fb5c76becff8c5d5b4bbb33fe75f91598aceb0526c8d48
|
4
|
+
data.tar.gz: 87d3db350a3faa94cb367fbd600749ed584f0868cd8298469b7bef6c9434e93e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3028c6cbaec10e84597c3a68e2f8f6c88b336b4be50144b04349662afa647d9c266d00debc14c0370bda0bb2150472cf69e347def8fcdc5d3682fc6c2d19512
|
7
|
+
data.tar.gz: 98a10191a8cd96cf85d91f9d5f1f38f07fa29fc7521f55bfd67c500a21fb23b3ef7d4ba7a16df1cc1d35f2a7486fe9ba18f11ab922e288895533ed2197766601
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.8.2](https://github.com/BoxcarsAI/boxcars/tree/v0.8.2) (2025-06-04)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.8.1...v0.8.2)
|
6
|
+
|
7
|
+
## [v0.8.1](https://github.com/BoxcarsAI/boxcars/tree/v0.8.1) (2025-06-03)
|
8
|
+
|
9
|
+
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.8.0...v0.8.1)
|
10
|
+
|
3
11
|
## [v0.8.0](https://github.com/BoxcarsAI/boxcars/tree/v0.8.0) (2025-06-03)
|
4
12
|
|
5
13
|
[Full Changelog](https://github.com/BoxcarsAI/boxcars/compare/v0.7.7...v0.8.0)
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
boxcars (0.8.
|
4
|
+
boxcars (0.8.3)
|
5
5
|
faraday-retry (~> 2.0)
|
6
6
|
google_search_results (~> 2.2)
|
7
7
|
gpt4all (~> 0.0.5)
|
@@ -56,10 +56,10 @@ GEM
|
|
56
56
|
async (>= 1.25)
|
57
57
|
base64 (0.3.0)
|
58
58
|
benchmark (0.4.1)
|
59
|
-
bigdecimal (3.2.
|
59
|
+
bigdecimal (3.2.2)
|
60
60
|
concurrent-ruby (1.3.5)
|
61
61
|
connection_pool (2.5.3)
|
62
|
-
console (1.
|
62
|
+
console (1.31.0)
|
63
63
|
fiber-annotation
|
64
64
|
fiber-local (~> 1.1)
|
65
65
|
json
|
@@ -74,7 +74,7 @@ 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
|
77
|
+
dynamicschema (1.0.0)
|
78
78
|
erb (5.0.1)
|
79
79
|
event_stream_parser (1.0.0)
|
80
80
|
faraday (2.13.1)
|
@@ -132,7 +132,7 @@ 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.
|
135
|
+
mime-types-data (3.2025.0603)
|
136
136
|
minitest (5.25.5)
|
137
137
|
multi_json (1.15.0)
|
138
138
|
multipart-post (2.4.1)
|
@@ -211,7 +211,7 @@ GEM
|
|
211
211
|
diff-lcs (>= 1.2.0, < 2.0)
|
212
212
|
rspec-support (~> 3.13.0)
|
213
213
|
rspec-support (3.13.4)
|
214
|
-
rubocop (1.
|
214
|
+
rubocop (1.76.1)
|
215
215
|
json (~> 2.3)
|
216
216
|
language_server-protocol (~> 3.17.0.2)
|
217
217
|
lint_roller (~> 1.1.0)
|
@@ -219,10 +219,10 @@ GEM
|
|
219
219
|
parser (>= 3.3.0.2)
|
220
220
|
rainbow (>= 2.2.2, < 4.0)
|
221
221
|
regexp_parser (>= 2.9.3, < 3.0)
|
222
|
-
rubocop-ast (>= 1.
|
222
|
+
rubocop-ast (>= 1.45.0, < 2.0)
|
223
223
|
ruby-progressbar (~> 1.7)
|
224
224
|
unicode-display_width (>= 2.4.0, < 4.0)
|
225
|
-
rubocop-ast (1.
|
225
|
+
rubocop-ast (1.45.1)
|
226
226
|
parser (>= 3.3.7.2)
|
227
227
|
prism (~> 1.4)
|
228
228
|
rubocop-rake (0.6.0)
|
data/README.md
CHANGED
@@ -351,17 +351,30 @@ Set up observability by configuring a backend:
|
|
351
351
|
```ruby
|
352
352
|
# Using PostHog backend
|
353
353
|
require 'boxcars/observability_backends/posthog_backend'
|
354
|
+
require 'posthog'
|
355
|
+
|
356
|
+
# Create a PostHog client with your desired configuration
|
357
|
+
posthog_client = PostHog::Client.new(
|
358
|
+
api_key: ENV['POSTHOG_API_KEY'] || 'your_posthog_api_key',
|
359
|
+
host: 'https://app.posthog.com', # or your self-hosted instance
|
360
|
+
on_error: proc { |status, body|
|
361
|
+
Rails.logger.warn "PostHog error: #{status} - #{body}"
|
362
|
+
}
|
363
|
+
)
|
354
364
|
|
355
365
|
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
|
-
)
|
366
|
+
config.observability_backend = Boxcars::PosthogBackend.new(client: posthog_client)
|
360
367
|
end
|
361
368
|
|
362
369
|
# Using multiple backends
|
363
370
|
require 'boxcars/observability_backends/multi_backend'
|
364
|
-
|
371
|
+
|
372
|
+
# Create PostHog client
|
373
|
+
posthog_client = PostHog::Client.new(
|
374
|
+
api_key: ENV['POSTHOG_API_KEY'],
|
375
|
+
host: 'https://app.posthog.com'
|
376
|
+
)
|
377
|
+
backend1 = Boxcars::PosthogBackend.new(client: posthog_client)
|
365
378
|
backend2 = YourCustomBackend.new
|
366
379
|
|
367
380
|
Boxcars.configure do |config|
|
@@ -440,14 +453,18 @@ The PostHog backend requires the `posthog-ruby` gem:
|
|
440
453
|
gem 'posthog-ruby'
|
441
454
|
|
442
455
|
# Configure the backend
|
456
|
+
require 'posthog'
|
457
|
+
|
458
|
+
posthog_client = PostHog::Client.new(
|
459
|
+
api_key: ENV['POSTHOG_API_KEY'],
|
460
|
+
host: 'https://app.posthog.com',
|
461
|
+
on_error: proc { |status, body|
|
462
|
+
Rails.logger.warn "PostHog error: #{status} - #{body}"
|
463
|
+
}
|
464
|
+
)
|
465
|
+
|
443
466
|
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
|
-
)
|
467
|
+
config.observability_backend = Boxcars::PosthogBackend.new(client: posthog_client)
|
451
468
|
end
|
452
469
|
```
|
453
470
|
|
@@ -79,12 +79,16 @@ module Boxcars
|
|
79
79
|
)
|
80
80
|
end
|
81
81
|
|
82
|
-
|
82
|
+
# If there's an error, raise it to maintain backward compatibility with existing tests
|
83
|
+
raise response_data[:error] if response_data[:error]
|
84
|
+
|
85
|
+
response_data
|
83
86
|
end
|
84
87
|
|
85
88
|
def run(question, **)
|
86
89
|
prompt = Prompt.new(template: question)
|
87
|
-
|
90
|
+
response_data = client(prompt:, inputs: {}, **)
|
91
|
+
answer = _gemini_handle_call_outcome(response_data:)
|
88
92
|
Boxcars.debug("Answer: #{answer}", :cyan)
|
89
93
|
answer
|
90
94
|
end
|
data/lib/boxcars/engine/groq.rb
CHANGED
@@ -71,12 +71,16 @@ module Boxcars
|
|
71
71
|
)
|
72
72
|
end
|
73
73
|
|
74
|
-
|
74
|
+
# If there's an error, raise it to maintain backward compatibility with existing tests
|
75
|
+
raise response_data[:error] if response_data[:error]
|
76
|
+
|
77
|
+
response_data
|
75
78
|
end
|
76
79
|
|
77
80
|
def run(question, **)
|
78
81
|
prompt = Prompt.new(template: question)
|
79
|
-
|
82
|
+
response_data = client(prompt:, inputs: {}, **)
|
83
|
+
answer = _groq_handle_call_outcome(response_data:)
|
80
84
|
Boxcars.debug("Answer: #{answer}", :cyan)
|
81
85
|
answer
|
82
86
|
end
|
@@ -128,6 +128,7 @@ module Boxcars
|
|
128
128
|
# Called by Engine#generate to check the response from the client.
|
129
129
|
# @param response [Hash] The parsed JSON response from the OpenAI API.
|
130
130
|
# @raise [Boxcars::Error] if the response contains an error.
|
131
|
+
# rubocop:disable Naming/PredicateMethod
|
131
132
|
def check_response(response)
|
132
133
|
if response.is_a?(Hash) && response["error"]
|
133
134
|
err_details = response["error"]
|
@@ -136,6 +137,7 @@ module Boxcars
|
|
136
137
|
end
|
137
138
|
true
|
138
139
|
end
|
140
|
+
# rubocop:enable Naming/PredicateMethod
|
139
141
|
|
140
142
|
def run(question, **)
|
141
143
|
prompt = Prompt.new(template: question)
|
data/lib/boxcars/engine.rb
CHANGED
@@ -57,12 +57,39 @@ module Boxcars
|
|
57
57
|
# Includes prompt, completion, and total tokens used.
|
58
58
|
inkeys = %w[completion_tokens prompt_tokens total_tokens].freeze
|
59
59
|
prompts.each_slice(batch_size) do |sub_prompts|
|
60
|
-
sub_prompts.each do |
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
60
|
+
sub_prompts.each do |sprompt, inputs|
|
61
|
+
client_response = client(prompt: sprompt, inputs:, **params)
|
62
|
+
|
63
|
+
# Handle different response formats:
|
64
|
+
# - New format: response_data hash with :parsed_json key (Groq, Gemini)
|
65
|
+
# - Legacy format: direct API response hash (OpenAI, others)
|
66
|
+
api_response_hash = if client_response.is_a?(Hash) && client_response.key?(:parsed_json)
|
67
|
+
client_response[:parsed_json]
|
68
|
+
else
|
69
|
+
client_response
|
70
|
+
end
|
71
|
+
|
72
|
+
# Ensure we have a hash to work with
|
73
|
+
unless api_response_hash.is_a?(Hash)
|
74
|
+
raise TypeError, "Expected Hash from client method, got #{api_response_hash.class}: #{api_response_hash.inspect}"
|
75
|
+
end
|
76
|
+
|
77
|
+
check_response(api_response_hash)
|
78
|
+
|
79
|
+
current_choices = api_response_hash["choices"]
|
80
|
+
if current_choices.is_a?(Array)
|
81
|
+
choices.concat(current_choices)
|
82
|
+
else
|
83
|
+
Boxcars.logger&.warn "No 'choices' found in API response: #{api_response_hash.inspect}"
|
84
|
+
end
|
85
|
+
|
86
|
+
api_usage = api_response_hash["usage"]
|
87
|
+
if api_usage.is_a?(Hash)
|
88
|
+
usage_keys = inkeys & api_usage.keys
|
89
|
+
usage_keys.each { |key| token_usage[key] = token_usage[key].to_i + api_usage[key] }
|
90
|
+
else
|
91
|
+
Boxcars.logger&.warn "No 'usage' data found in API response: #{api_response_hash.inspect}"
|
92
|
+
end
|
66
93
|
end
|
67
94
|
end
|
68
95
|
|
@@ -9,10 +9,13 @@ module Boxcars
|
|
9
9
|
#
|
10
10
|
# Example Usage:
|
11
11
|
# require 'boxcars/observability_backends/posthog_backend'
|
12
|
-
#
|
12
|
+
# require 'posthog'
|
13
|
+
#
|
14
|
+
# client = PostHog::Client.new(
|
13
15
|
# api_key: 'YOUR_POSTHOG_API_KEY',
|
14
16
|
# host: 'https://app.posthog.com' # or your self-hosted instance
|
15
17
|
# )
|
18
|
+
# Boxcars::Observability.backend = Boxcars::PosthogBackend.new(client: client)
|
16
19
|
#
|
17
20
|
# # To track user-specific events, ensure :user_id is present in properties
|
18
21
|
# Boxcars::Observability.track(
|
@@ -23,32 +26,18 @@ module Boxcars
|
|
23
26
|
include Boxcars::ObservabilityBackend
|
24
27
|
|
25
28
|
# Initializes the PosthogBackend.
|
26
|
-
#
|
29
|
+
# Accepts a pre-configured PostHog client instance.
|
27
30
|
#
|
28
|
-
# @param
|
29
|
-
# @param host [String] The PostHog API host. Defaults to 'https://app.posthog.com'.
|
30
|
-
# @param _personal_api_key [String, nil] Optional: A personal API key for server-side operations if needed.
|
31
|
-
# @param on_error [Proc, nil] Optional: A lambda/proc to call when an error occurs during event capture.
|
32
|
-
# It receives the error code and error body as arguments.
|
33
|
-
# Defaults to a proc that logs the error to stderr.
|
31
|
+
# @param client [PostHog::Client] A configured PostHog client instance.
|
34
32
|
# @raise [LoadError] if the 'posthog-ruby' gem is not available.
|
35
|
-
def initialize(
|
33
|
+
def initialize(client:)
|
36
34
|
begin
|
37
35
|
require 'posthog'
|
38
36
|
rescue LoadError
|
39
37
|
raise LoadError, "The 'posthog-ruby' gem is required to use PosthogBackend. Please add it to your Gemfile."
|
40
38
|
end
|
41
39
|
|
42
|
-
@
|
43
|
-
Boxcars.error("PostHog error: Status #{status}, Body: #{body}", :red)
|
44
|
-
end
|
45
|
-
|
46
|
-
# The posthog-ruby gem uses a simpler API
|
47
|
-
@posthog_client = PostHog::Client.new(
|
48
|
-
api_key:,
|
49
|
-
host:,
|
50
|
-
on_error: @on_error_proc
|
51
|
-
)
|
40
|
+
@posthog_client = client
|
52
41
|
end
|
53
42
|
|
54
43
|
# Tracks an event with PostHog.
|
data/lib/boxcars/version.rb
CHANGED