boxcars 0.8.3 → 0.8.5
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/Gemfile.lock +21 -21
- data/README.md +2 -1
- data/USER_CONTEXT_GUIDE.md +435 -0
- data/lib/boxcars/engine/anthropic.rb +4 -2
- data/lib/boxcars/engine/cohere.rb +4 -2
- data/lib/boxcars/engine/gemini_ai.rb +7 -5
- data/lib/boxcars/engine/gpt4all_eng.rb +5 -5
- data/lib/boxcars/engine/groq.rb +10 -8
- data/lib/boxcars/engine/intelligence_base.rb +3 -13
- data/lib/boxcars/engine/ollama.rb +4 -2
- data/lib/boxcars/engine/openai.rb +190 -178
- data/lib/boxcars/engine/perplexityai.rb +3 -1
- data/lib/boxcars/engine/unified_observability.rb +2 -1
- data/lib/boxcars/engine.rb +17 -10
- data/lib/boxcars/observability.rb +44 -2
- data/lib/boxcars/observability_backends/posthog_backend.rb +10 -15
- data/lib/boxcars/observation.rb +40 -0
- data/lib/boxcars/version.rb +1 -1
- data/lib/boxcars.rb +1 -0
- metadata +2 -2
- data/POSTHOG_TEST_README.md +0 -118
@@ -20,16 +20,58 @@ module Boxcars
|
|
20
20
|
#
|
21
21
|
# @param event [String, Symbol] The name of the event to track.
|
22
22
|
# @param properties [Hash] A hash of properties associated with the event.
|
23
|
-
|
23
|
+
# @param observation [Boxcars::Observation, nil] Optional observation object to extract user context from.
|
24
|
+
def track(event:, properties:, observation: nil)
|
24
25
|
return unless backend
|
25
26
|
|
26
|
-
|
27
|
+
# Merge user context from observation if present
|
28
|
+
final_properties = properties.dup
|
29
|
+
final_properties = merge_user_context(final_properties, observation.user_context) if observation&.user_context?
|
30
|
+
|
31
|
+
backend.track(event:, properties: final_properties)
|
27
32
|
rescue StandardError
|
28
33
|
# Fail silently as requested.
|
29
34
|
# Optionally, if Boxcars had a central logger:
|
30
35
|
# Boxcars.logger.warn "Boxcars::Observability: Backend error during track: #{e.message} (#{e.class.name})"
|
31
36
|
end
|
32
37
|
|
38
|
+
# Tracks an observation event, automatically extracting user context if present
|
39
|
+
# @param observation [Boxcars::Observation] The observation to track
|
40
|
+
# @param event [String, Symbol] The event name (defaults to 'boxcar_observation')
|
41
|
+
# @param additional_properties [Hash] Additional properties to include
|
42
|
+
def track_observation(observation, event: 'boxcar_observation', **additional_properties)
|
43
|
+
properties = {
|
44
|
+
observation_note: observation.note,
|
45
|
+
observation_status: observation.status,
|
46
|
+
timestamp: Time.now.iso8601
|
47
|
+
}.merge(additional_properties)
|
48
|
+
|
49
|
+
# Add all observation context (including user_context) to properties
|
50
|
+
properties.merge!(observation.added_context) if observation.added_context
|
51
|
+
|
52
|
+
track(event:, properties:, observation:)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Merge user context into properties with proper namespacing
|
58
|
+
# @param properties [Hash] The existing properties
|
59
|
+
# @param user_context [Hash] The user context to merge
|
60
|
+
# @return [Hash] The merged properties
|
61
|
+
def merge_user_context(properties, user_context)
|
62
|
+
return properties unless user_context.is_a?(Hash)
|
63
|
+
|
64
|
+
# Add user context with proper prefixing for analytics systems
|
65
|
+
user_properties = {}
|
66
|
+
user_context.each do |key, value|
|
67
|
+
# Use $user_ prefix for PostHog compatibility
|
68
|
+
user_key = key.to_s.start_with?('$user_') ? key : "$user_#{key}"
|
69
|
+
user_properties[user_key] = value
|
70
|
+
end
|
71
|
+
|
72
|
+
properties.merge(user_properties)
|
73
|
+
end
|
74
|
+
|
33
75
|
# Flushes any pending events if the backend supports it.
|
34
76
|
# This is useful for testing or when you need to ensure events are sent before the process exits.
|
35
77
|
def flush
|
@@ -52,21 +52,9 @@ module Boxcars
|
|
52
52
|
# @param properties [Hash] A hash of properties for the event.
|
53
53
|
# It's recommended to include a `:user_id` for user-specific tracking.
|
54
54
|
def track(event:, properties:)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
distinct_id = tracking_properties.delete(:user_id) || tracking_properties.delete('user_id') || "anonymous_user"
|
59
|
-
|
60
|
-
# The PostHog gem's capture method handles distinct_id and properties.
|
61
|
-
# It's important that distinct_id is a string.
|
62
|
-
@posthog_client.capture(
|
63
|
-
distinct_id: distinct_id.to_s, # Ensure distinct_id is a string
|
64
|
-
event: event.to_s, # Ensure event name is a string
|
65
|
-
properties: tracking_properties
|
66
|
-
)
|
67
|
-
# The posthog-ruby client handles flushing events asynchronously.
|
68
|
-
# If immediate flushing is needed for testing or specific scenarios:
|
69
|
-
# @posthog_client.flush
|
55
|
+
properties = {} unless properties.is_a?(Hash)
|
56
|
+
distinct_id = properties.delete(:user_id) || current_user_id || "anonymous_user"
|
57
|
+
@posthog_client.capture(distinct_id:, event:, properties:)
|
70
58
|
end
|
71
59
|
|
72
60
|
# Flushes any pending events to PostHog immediately.
|
@@ -74,5 +62,12 @@ module Boxcars
|
|
74
62
|
def flush
|
75
63
|
@posthog_client.flush if @posthog_client.respond_to?(:flush)
|
76
64
|
end
|
65
|
+
|
66
|
+
# in Rails, this is a way to find the current user id
|
67
|
+
def current_user_id
|
68
|
+
return unless defined?(::Current) && ::Current.respond_to?(:user)
|
69
|
+
|
70
|
+
::Current.user&.id
|
71
|
+
end
|
77
72
|
end
|
78
73
|
end
|
data/lib/boxcars/observation.rb
CHANGED
@@ -52,5 +52,45 @@ module Boxcars
|
|
52
52
|
def self.err(note, **)
|
53
53
|
new(note:, status: :error, **)
|
54
54
|
end
|
55
|
+
|
56
|
+
# create a new Observation with user context
|
57
|
+
# @param note [String] The text to use for the observation
|
58
|
+
# @param user_context [Hash] User information (e.g., { id: 123, email: "user@example.com", role: "admin" })
|
59
|
+
# @param status [Symbol] :ok or :error
|
60
|
+
# @param added_context [Hash] Any additional context to add to the result
|
61
|
+
# @return [Boxcars::Observation] The observation
|
62
|
+
def self.with_user(note, user_context:, status: :ok, **)
|
63
|
+
new(note:, status:, user_context:, **)
|
64
|
+
end
|
65
|
+
|
66
|
+
# create a new Observation with user context and status :ok
|
67
|
+
# @param note [String] The text to use for the observation
|
68
|
+
# @param user_context [Hash] User information (e.g., { id: 123, email: "user@example.com", role: "admin" })
|
69
|
+
# @param added_context [Hash] Any additional context to add to the result
|
70
|
+
# @return [Boxcars::Observation] The observation
|
71
|
+
def self.ok_with_user(note, user_context:, **)
|
72
|
+
with_user(note, user_context:, status: :ok, **)
|
73
|
+
end
|
74
|
+
|
75
|
+
# create a new Observation with user context and status :error
|
76
|
+
# @param note [String] The text to use for the observation
|
77
|
+
# @param user_context [Hash] User information (e.g., { id: 123, email: "user@example.com", role: "admin" })
|
78
|
+
# @param added_context [Hash] Any additional context to add to the result
|
79
|
+
# @return [Boxcars::Observation] The observation
|
80
|
+
def self.err_with_user(note, user_context:, **)
|
81
|
+
with_user(note, user_context:, status: :error, **)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Extract user context from the observation
|
85
|
+
# @return [Hash, nil] The user context if present
|
86
|
+
def user_context
|
87
|
+
added_context[:user_context]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check if this observation has user context
|
91
|
+
# @return [Boolean] true if user context is present
|
92
|
+
def user_context?
|
93
|
+
!user_context.nil?
|
94
|
+
end
|
55
95
|
end
|
56
96
|
end
|
data/lib/boxcars/version.rb
CHANGED
data/lib/boxcars.rb
CHANGED
@@ -218,6 +218,7 @@ module Boxcars
|
|
218
218
|
end
|
219
219
|
|
220
220
|
require_relative "boxcars/version"
|
221
|
+
require_relative "boxcars/observation"
|
221
222
|
require_relative "boxcars/observability_backend"
|
222
223
|
require_relative "boxcars/observability"
|
223
224
|
# If users want it, they can require 'boxcars/observability_backends/multi_backend'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boxcars
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francis Sullivan
|
@@ -139,9 +139,9 @@ files:
|
|
139
139
|
- Gemfile
|
140
140
|
- Gemfile.lock
|
141
141
|
- LICENSE.txt
|
142
|
-
- POSTHOG_TEST_README.md
|
143
142
|
- README.md
|
144
143
|
- Rakefile
|
144
|
+
- USER_CONTEXT_GUIDE.md
|
145
145
|
- bin/console
|
146
146
|
- bin/setup
|
147
147
|
- boxcars.gemspec
|
data/POSTHOG_TEST_README.md
DELETED
@@ -1,118 +0,0 @@
|
|
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')
|