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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1970a509d4c6eebaeaa06792993f7aaa1ba8d2d030e878138a8d6f821726fcc
|
4
|
+
data.tar.gz: 66ae9f13950ab9a0dd2369a1450fd14d4426faa95d8a62da7b4c7deeddbc6b3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e6d1f2f4788bb24de873a2e7270b52f44ce31017ccd39e1db321b91bd595d1f832c6c9aa4b9ca587626c3390928e70025014a360ba8052b9e870c50566ccb29
|
7
|
+
data.tar.gz: 4073745656e6944e8e686eee4502916038470ff32261990c1444e50d951a819288a24b993dc51659168b9246dab7020f40fd20f210b9421ab5890e6ec4402d66
|
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.5)
|
5
5
|
faraday-retry (~> 2.0)
|
6
6
|
google_search_results (~> 2.2)
|
7
7
|
gpt4all (~> 0.0.5)
|
@@ -47,7 +47,7 @@ GEM
|
|
47
47
|
protocol-http1 (~> 0.19.0)
|
48
48
|
protocol-http2 (~> 0.16.0)
|
49
49
|
traces (>= 0.10.0)
|
50
|
-
async-http-faraday (0.
|
50
|
+
async-http-faraday (0.22.0)
|
51
51
|
async-http (~> 0.42)
|
52
52
|
faraday
|
53
53
|
async-io (1.43.2)
|
@@ -67,7 +67,7 @@ GEM
|
|
67
67
|
bigdecimal
|
68
68
|
rexml
|
69
69
|
date (3.4.1)
|
70
|
-
debug (1.
|
70
|
+
debug (1.11.0)
|
71
71
|
irb (~> 1.10)
|
72
72
|
reline (>= 0.3.8)
|
73
73
|
diff-lcs (1.6.2)
|
@@ -83,11 +83,11 @@ GEM
|
|
83
83
|
logger
|
84
84
|
faraday-http-cache (2.5.1)
|
85
85
|
faraday (>= 0.8)
|
86
|
-
faraday-multipart (1.1.
|
86
|
+
faraday-multipart (1.1.1)
|
87
87
|
multipart-post (~> 2.0)
|
88
|
-
faraday-net_http (3.4.
|
88
|
+
faraday-net_http (3.4.1)
|
89
89
|
net-http (>= 0.5.0)
|
90
|
-
faraday-retry (2.3.
|
90
|
+
faraday-retry (2.3.2)
|
91
91
|
faraday (~> 2.0)
|
92
92
|
fiber-annotation (0.2.0)
|
93
93
|
fiber-local (1.1.0)
|
@@ -114,8 +114,8 @@ GEM
|
|
114
114
|
domain_name (~> 0.5)
|
115
115
|
i18n (1.14.7)
|
116
116
|
concurrent-ruby (~> 1.0)
|
117
|
-
intelligence (0.
|
118
|
-
dynamicschema (~> 1.0
|
117
|
+
intelligence (1.0.0)
|
118
|
+
dynamicschema (~> 1.0)
|
119
119
|
faraday (~> 2.7)
|
120
120
|
json-repair (~> 0.2)
|
121
121
|
mime-types (~> 3.6)
|
@@ -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.0624)
|
136
136
|
minitest (5.25.5)
|
137
137
|
multi_json (1.15.0)
|
138
138
|
multipart-post (2.4.1)
|
@@ -186,7 +186,7 @@ GEM
|
|
186
186
|
racc (1.8.1)
|
187
187
|
rainbow (3.1.1)
|
188
188
|
rake (13.3.0)
|
189
|
-
rdoc (6.14.
|
189
|
+
rdoc (6.14.1)
|
190
190
|
erb
|
191
191
|
psych (>= 4.0.0)
|
192
192
|
regexp_parser (2.10.0)
|
@@ -202,7 +202,7 @@ GEM
|
|
202
202
|
rspec-core (~> 3.13.0)
|
203
203
|
rspec-expectations (~> 3.13.0)
|
204
204
|
rspec-mocks (~> 3.13.0)
|
205
|
-
rspec-core (3.13.
|
205
|
+
rspec-core (3.13.5)
|
206
206
|
rspec-support (~> 3.13.0)
|
207
207
|
rspec-expectations (3.13.5)
|
208
208
|
diff-lcs (>= 1.2.0, < 2.0)
|
@@ -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.77.0)
|
215
215
|
json (~> 2.3)
|
216
216
|
language_server-protocol (~> 3.17.0.2)
|
217
217
|
lint_roller (~> 1.1.0)
|
@@ -219,7 +219,7 @@ 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.45.
|
222
|
+
rubocop-ast (>= 1.45.1, < 2.0)
|
223
223
|
ruby-progressbar (~> 1.7)
|
224
224
|
unicode-display_width (>= 2.4.0, < 4.0)
|
225
225
|
rubocop-ast (1.45.1)
|
@@ -243,14 +243,14 @@ GEM
|
|
243
243
|
addressable (>= 2.3.5)
|
244
244
|
faraday (>= 0.17.3, < 3)
|
245
245
|
securerandom (0.4.1)
|
246
|
-
sqlite3 (2.
|
247
|
-
sqlite3 (2.
|
248
|
-
sqlite3 (2.
|
249
|
-
sqlite3 (2.
|
250
|
-
sqlite3 (2.
|
251
|
-
sqlite3 (2.
|
252
|
-
sqlite3 (2.
|
253
|
-
sqlite3 (2.
|
246
|
+
sqlite3 (2.7.1-aarch64-linux-gnu)
|
247
|
+
sqlite3 (2.7.1-aarch64-linux-musl)
|
248
|
+
sqlite3 (2.7.1-arm-linux-gnu)
|
249
|
+
sqlite3 (2.7.1-arm-linux-musl)
|
250
|
+
sqlite3 (2.7.1-arm64-darwin)
|
251
|
+
sqlite3 (2.7.1-x86_64-darwin)
|
252
|
+
sqlite3 (2.7.1-x86_64-linux-gnu)
|
253
|
+
sqlite3 (2.7.1-x86_64-linux-musl)
|
254
254
|
stringio (3.1.7)
|
255
255
|
strings-ansi (0.2.0)
|
256
256
|
timeout (0.4.3)
|
data/README.md
CHANGED
@@ -388,7 +388,7 @@ Boxcars automatically tracks LLM calls with detailed metrics:
|
|
388
388
|
|
389
389
|
```ruby
|
390
390
|
# This automatically generates observability events
|
391
|
-
engine = Boxcars::Openai.new
|
391
|
+
engine = Boxcars::Openai.new(user_id: USER_ID) # optional user_id. All engines take this.
|
392
392
|
calc = Boxcars::Calculator.new(engine: engine)
|
393
393
|
result = calc.run "what is 2 + 2?"
|
394
394
|
```
|
@@ -404,6 +404,7 @@ result = calc.run "what is 2 + 2?"
|
|
404
404
|
- `error_message`: Error details if the call failed
|
405
405
|
- `response_raw_body`: Raw API response
|
406
406
|
- `api_call_parameters`: Parameters sent to the API
|
407
|
+
- `distinct_id`: If you specify a user_id to your engine, it will be passed up.
|
407
408
|
|
408
409
|
#### Manual Tracking
|
409
410
|
|
@@ -0,0 +1,435 @@
|
|
1
|
+
# User Context in Boxcars Observations
|
2
|
+
|
3
|
+
This guide explains how to use the new user context feature in Boxcars observations to track which user performed specific actions.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The user context feature allows you to associate user information with Boxcars observations, enabling better tracking, debugging, and analytics. This is particularly useful when using Boxcars in web applications where you want to tie AI operations to specific users.
|
8
|
+
|
9
|
+
## Key Features
|
10
|
+
|
11
|
+
- **Non-intrusive**: Uses the existing `added_context` mechanism
|
12
|
+
- **Backward compatible**: Existing code continues to work unchanged
|
13
|
+
- **Flexible**: You control what user data is included
|
14
|
+
- **Analytics-ready**: Automatically formats user data for analytics systems like PostHog
|
15
|
+
- **Privacy-conscious**: Only includes data you explicitly provide
|
16
|
+
|
17
|
+
## Enhanced Observation Class
|
18
|
+
|
19
|
+
### New Methods
|
20
|
+
|
21
|
+
#### `Observation.with_user(note, user_context:, status: :ok, **additional_context)`
|
22
|
+
|
23
|
+
Creates an observation with user context.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
user_context = {
|
27
|
+
id: current_user.id,
|
28
|
+
email: current_user.email,
|
29
|
+
role: current_user.role
|
30
|
+
}
|
31
|
+
|
32
|
+
observation = Boxcars::Observation.with_user(
|
33
|
+
"User performed search",
|
34
|
+
user_context: user_context,
|
35
|
+
query: "machine learning",
|
36
|
+
results_count: 42
|
37
|
+
)
|
38
|
+
```
|
39
|
+
|
40
|
+
#### `Observation.ok_with_user(note, user_context:, **additional_context)`
|
41
|
+
|
42
|
+
Creates a successful observation with user context.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
observation = Boxcars::Observation.ok_with_user(
|
46
|
+
"Search completed successfully",
|
47
|
+
user_context: user_context,
|
48
|
+
processing_time_ms: 150
|
49
|
+
)
|
50
|
+
```
|
51
|
+
|
52
|
+
#### `Observation.err_with_user(note, user_context:, **additional_context)`
|
53
|
+
|
54
|
+
Creates an error observation with user context.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
observation = Boxcars::Observation.err_with_user(
|
58
|
+
"Search failed due to timeout",
|
59
|
+
user_context: user_context,
|
60
|
+
error_code: "TIMEOUT"
|
61
|
+
)
|
62
|
+
```
|
63
|
+
|
64
|
+
### New Instance Methods
|
65
|
+
|
66
|
+
#### `#user_context`
|
67
|
+
|
68
|
+
Returns the user context hash if present, `nil` otherwise.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
observation.user_context
|
72
|
+
# => {id: 123, email: "user@example.com", role: "admin"}
|
73
|
+
```
|
74
|
+
|
75
|
+
#### `#user_context?`
|
76
|
+
|
77
|
+
Returns `true` if the observation has user context, `false` otherwise.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
observation.user_context?
|
81
|
+
# => true
|
82
|
+
```
|
83
|
+
|
84
|
+
## Enhanced Observability System
|
85
|
+
|
86
|
+
### Updated Track Method
|
87
|
+
|
88
|
+
The `Boxcars::Observability.track` method now accepts an optional `observation` parameter:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
Boxcars::Observability.track(
|
92
|
+
event: 'user_action',
|
93
|
+
properties: { action_type: 'search' },
|
94
|
+
observation: observation_with_user_context
|
95
|
+
)
|
96
|
+
```
|
97
|
+
|
98
|
+
When an observation with user context is provided, the user data is automatically merged into the tracking properties with `$user_` prefixes (PostHog compatible).
|
99
|
+
|
100
|
+
### New Track Observation Method
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Boxcars::Observability.track_observation(
|
104
|
+
observation,
|
105
|
+
event: 'custom_event_name', # optional, defaults to 'boxcar_observation'
|
106
|
+
additional_property: 'value'
|
107
|
+
)
|
108
|
+
```
|
109
|
+
|
110
|
+
This method automatically:
|
111
|
+
- Extracts observation details (note, status, timestamp)
|
112
|
+
- Includes all additional context from the observation
|
113
|
+
- Merges user context with `$user_` prefixes
|
114
|
+
- Sends everything to your configured observability backend
|
115
|
+
|
116
|
+
## Rails Integration Pattern
|
117
|
+
|
118
|
+
### Controller Helper Methods
|
119
|
+
|
120
|
+
Create helper methods in your `ApplicationController`:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class ApplicationController < ActionController::Base
|
124
|
+
private
|
125
|
+
|
126
|
+
# Helper method to create observations with user context
|
127
|
+
def create_observation_with_user(note, status: :ok, **additional_context)
|
128
|
+
if current_user
|
129
|
+
Boxcars::Observation.with_user(
|
130
|
+
note,
|
131
|
+
user_context: current_user.to_user_context,
|
132
|
+
status: status,
|
133
|
+
**additional_context
|
134
|
+
)
|
135
|
+
else
|
136
|
+
Boxcars::Observation.new(note: note, status: status, **additional_context)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Helper method for successful operations
|
141
|
+
def success_observation_with_user(note, **additional_context)
|
142
|
+
create_observation_with_user(note, status: :ok, **additional_context)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Helper method for error operations
|
146
|
+
def error_observation_with_user(note, **additional_context)
|
147
|
+
create_observation_with_user(note, status: :error, **additional_context)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### User Model Extension
|
153
|
+
|
154
|
+
Add a method to your User model to convert user data to context:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
class User < ApplicationRecord
|
158
|
+
def to_user_context
|
159
|
+
{
|
160
|
+
id: id,
|
161
|
+
email: email,
|
162
|
+
role: role,
|
163
|
+
# Add other relevant fields, but be mindful of privacy
|
164
|
+
# Don't include sensitive data like passwords, tokens, etc.
|
165
|
+
}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
### Usage in Controllers
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class SearchController < ApplicationController
|
174
|
+
def search
|
175
|
+
query = params[:query]
|
176
|
+
|
177
|
+
begin
|
178
|
+
# Perform search with Boxcars
|
179
|
+
results = perform_boxcar_search(query)
|
180
|
+
|
181
|
+
# Track successful search
|
182
|
+
observation = success_observation_with_user(
|
183
|
+
"Search completed successfully",
|
184
|
+
query: query,
|
185
|
+
results_count: results.count,
|
186
|
+
processing_time_ms: 250
|
187
|
+
)
|
188
|
+
|
189
|
+
Boxcars::Observability.track_observation(
|
190
|
+
observation,
|
191
|
+
event: 'search_completed',
|
192
|
+
controller: 'SearchController',
|
193
|
+
action: 'search'
|
194
|
+
)
|
195
|
+
|
196
|
+
render json: { results: results }
|
197
|
+
|
198
|
+
rescue StandardError => e
|
199
|
+
# Track search error
|
200
|
+
error_observation = error_observation_with_user(
|
201
|
+
"Search failed: #{e.message}",
|
202
|
+
query: query,
|
203
|
+
error_class: e.class.name
|
204
|
+
)
|
205
|
+
|
206
|
+
Boxcars::Observability.track_observation(
|
207
|
+
error_observation,
|
208
|
+
event: 'search_failed',
|
209
|
+
controller: 'SearchController',
|
210
|
+
action: 'search'
|
211
|
+
)
|
212
|
+
|
213
|
+
render json: { error: "Search failed" }, status: 500
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
## Analytics Integration
|
220
|
+
|
221
|
+
### PostHog Integration
|
222
|
+
|
223
|
+
When using PostHog as your observability backend, user context is automatically formatted with `$user_` prefixes:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
# This user context:
|
227
|
+
user_context = {
|
228
|
+
id: 123,
|
229
|
+
email: "user@example.com",
|
230
|
+
role: "admin"
|
231
|
+
}
|
232
|
+
|
233
|
+
# Becomes these PostHog properties:
|
234
|
+
{
|
235
|
+
"$user_id" => 123,
|
236
|
+
"$user_email" => "user@example.com",
|
237
|
+
"$user_role" => "admin"
|
238
|
+
}
|
239
|
+
```
|
240
|
+
|
241
|
+
This allows PostHog to automatically associate events with users and enables user-based analytics and segmentation.
|
242
|
+
|
243
|
+
### Custom Analytics Systems
|
244
|
+
|
245
|
+
For other analytics systems, you can access the user context directly:
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
observation = Boxcars::Observation.ok_with_user("Action completed", user_context: user_data)
|
249
|
+
|
250
|
+
# Access user context
|
251
|
+
user_info = observation.user_context
|
252
|
+
# => {id: 123, email: "user@example.com", role: "admin"}
|
253
|
+
|
254
|
+
# Include in your custom tracking
|
255
|
+
your_analytics.track(
|
256
|
+
event: 'boxcar_action',
|
257
|
+
user_id: user_info[:id],
|
258
|
+
user_email: user_info[:email],
|
259
|
+
properties: observation.to_h
|
260
|
+
)
|
261
|
+
```
|
262
|
+
|
263
|
+
## Privacy Considerations
|
264
|
+
|
265
|
+
### What to Include
|
266
|
+
|
267
|
+
**Safe to include:**
|
268
|
+
- User ID (for tracking purposes)
|
269
|
+
- Email (if needed for support)
|
270
|
+
- Role/permissions level
|
271
|
+
- Account type (free, premium, etc.)
|
272
|
+
- Tenant/organization ID
|
273
|
+
|
274
|
+
**Avoid including:**
|
275
|
+
- Passwords or password hashes
|
276
|
+
- API keys or tokens
|
277
|
+
- Personal identification numbers
|
278
|
+
- Credit card information
|
279
|
+
- Any other sensitive personal data
|
280
|
+
|
281
|
+
### Example Safe User Context
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
def to_user_context
|
285
|
+
{
|
286
|
+
id: id,
|
287
|
+
email: email,
|
288
|
+
role: role,
|
289
|
+
account_type: subscription&.plan_name,
|
290
|
+
organization_id: organization_id,
|
291
|
+
created_at: created_at.iso8601,
|
292
|
+
last_login: last_sign_in_at&.iso8601
|
293
|
+
}
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
## Testing
|
298
|
+
|
299
|
+
### RSpec Examples
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
RSpec.describe "User Context in Observations" do
|
303
|
+
let(:user_context) do
|
304
|
+
{
|
305
|
+
id: 123,
|
306
|
+
email: "test@example.com",
|
307
|
+
role: "admin"
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
311
|
+
it "creates observation with user context" do
|
312
|
+
observation = Boxcars::Observation.ok_with_user(
|
313
|
+
"Test action",
|
314
|
+
user_context: user_context
|
315
|
+
)
|
316
|
+
|
317
|
+
expect(observation.user_context?).to be true
|
318
|
+
expect(observation.user_context[:id]).to eq(123)
|
319
|
+
expect(observation.user_context[:email]).to eq("test@example.com")
|
320
|
+
end
|
321
|
+
|
322
|
+
it "tracks observation with user context" do
|
323
|
+
observation = Boxcars::Observation.ok_with_user(
|
324
|
+
"Test action",
|
325
|
+
user_context: user_context
|
326
|
+
)
|
327
|
+
|
328
|
+
expect(Boxcars::Observability).to receive(:track).with(
|
329
|
+
event: 'test_event',
|
330
|
+
properties: hash_including(
|
331
|
+
"$user_id" => 123,
|
332
|
+
"$user_email" => "test@example.com"
|
333
|
+
)
|
334
|
+
)
|
335
|
+
|
336
|
+
Boxcars::Observability.track(
|
337
|
+
event: 'test_event',
|
338
|
+
properties: {},
|
339
|
+
observation: observation
|
340
|
+
)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
```
|
344
|
+
|
345
|
+
## Migration Guide
|
346
|
+
|
347
|
+
### Existing Code
|
348
|
+
|
349
|
+
Your existing observation code continues to work unchanged:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
# This still works exactly as before
|
353
|
+
observation = Boxcars::Observation.ok("Action completed", extra: "data")
|
354
|
+
Boxcars::Observability.track(event: 'action', properties: { type: 'test' })
|
355
|
+
```
|
356
|
+
|
357
|
+
### Adding User Context
|
358
|
+
|
359
|
+
To add user context to existing observations, simply replace:
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
# Before
|
363
|
+
observation = Boxcars::Observation.ok("Action completed")
|
364
|
+
|
365
|
+
# After
|
366
|
+
observation = Boxcars::Observation.ok_with_user(
|
367
|
+
"Action completed",
|
368
|
+
user_context: current_user.to_user_context
|
369
|
+
)
|
370
|
+
```
|
371
|
+
|
372
|
+
### Updating Tracking Calls
|
373
|
+
|
374
|
+
To include user context in tracking:
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
# Before
|
378
|
+
Boxcars::Observability.track(
|
379
|
+
event: 'action_completed',
|
380
|
+
properties: { action_type: 'search' }
|
381
|
+
)
|
382
|
+
|
383
|
+
# After
|
384
|
+
Boxcars::Observability.track_observation(
|
385
|
+
observation_with_user_context,
|
386
|
+
event: 'action_completed',
|
387
|
+
action_type: 'search'
|
388
|
+
)
|
389
|
+
```
|
390
|
+
|
391
|
+
## Benefits
|
392
|
+
|
393
|
+
1. **Better Debugging**: Quickly identify which user encountered an issue
|
394
|
+
2. **User Analytics**: Segment usage patterns by user type, role, or other attributes
|
395
|
+
3. **Compliance**: Track user actions for audit trails
|
396
|
+
4. **Support**: Faster issue resolution with user context
|
397
|
+
5. **Product Insights**: Understand how different user segments use AI features
|
398
|
+
|
399
|
+
## Best Practices
|
400
|
+
|
401
|
+
1. **Consistent Context**: Use the same user context structure across your application
|
402
|
+
2. **Helper Methods**: Create controller helpers to reduce code duplication
|
403
|
+
3. **Privacy First**: Only include necessary user data
|
404
|
+
4. **Error Handling**: Always handle cases where `current_user` might be nil
|
405
|
+
5. **Testing**: Write tests for both user and anonymous scenarios
|
406
|
+
6. **Documentation**: Document what user data you're tracking and why
|
407
|
+
|
408
|
+
## Troubleshooting
|
409
|
+
|
410
|
+
### Common Issues
|
411
|
+
|
412
|
+
**User context not appearing in analytics:**
|
413
|
+
- Ensure your observability backend is configured
|
414
|
+
- Check that you're using `track_observation` or passing the `observation` parameter to `track`
|
415
|
+
- Verify the observation actually has user context with `user_context?`
|
416
|
+
|
417
|
+
**Anonymous users causing errors:**
|
418
|
+
- Always check for `current_user` presence before creating user context
|
419
|
+
- Use helper methods that handle nil users gracefully
|
420
|
+
|
421
|
+
**Missing user data:**
|
422
|
+
- Ensure your `to_user_context` method returns a hash
|
423
|
+
- Check that the user object has the expected attributes
|
424
|
+
|
425
|
+
### Debug Example
|
426
|
+
|
427
|
+
```ruby
|
428
|
+
# Debug user context
|
429
|
+
observation = Boxcars::Observation.ok_with_user("Test", user_context: user_data)
|
430
|
+
puts "Has user context: #{observation.user_context?}"
|
431
|
+
puts "User context: #{observation.user_context.inspect}"
|
432
|
+
puts "Full observation: #{observation.to_h.inspect}"
|
433
|
+
```
|
434
|
+
|
435
|
+
This comprehensive user context system provides a clean, privacy-conscious way to associate user information with Boxcars operations, enabling better tracking, debugging, and analytics while maintaining backward compatibility.
|
@@ -28,10 +28,11 @@ module Boxcars
|
|
28
28
|
# useful for when you need to use AI to answer questions. You should ask targeted questions".
|
29
29
|
# @param prompts [Array<String>] The prompts to use when asking the engine. Defaults to [].
|
30
30
|
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], **kwargs)
|
31
|
+
user_id = kwargs.delete(:user_id)
|
31
32
|
@llm_params = DEFAULT_PARAMS.merge(kwargs)
|
32
33
|
@prompts = prompts
|
33
34
|
@batch_size = 20
|
34
|
-
super(description:, name:)
|
35
|
+
super(description:, name:, user_id:)
|
35
36
|
end
|
36
37
|
|
37
38
|
def conversation_model?(_model)
|
@@ -278,7 +279,8 @@ module Boxcars
|
|
278
279
|
request_context = {
|
279
280
|
prompt: call_context[:prompt_object],
|
280
281
|
inputs: call_context[:inputs],
|
281
|
-
conversation_for_api: call_context[:api_request_params]
|
282
|
+
conversation_for_api: call_context[:api_request_params],
|
283
|
+
user_id:
|
282
284
|
}
|
283
285
|
|
284
286
|
track_ai_generation(
|
@@ -27,10 +27,11 @@ module Boxcars
|
|
27
27
|
# useful for when you need to use AI to answer questions. You should ask targeted questions".
|
28
28
|
# @param prompts [Array<String>] The prompts to use when asking the engine. Defaults to [].
|
29
29
|
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], **kwargs)
|
30
|
+
user_id = kwargs.delete(:user_id)
|
30
31
|
@llm_params = DEFAULT_PARAMS.merge(kwargs)
|
31
32
|
@prompts = prompts
|
32
33
|
@batch_size = 20
|
33
|
-
super(description:, name:)
|
34
|
+
super(description:, name:, user_id:)
|
34
35
|
end
|
35
36
|
|
36
37
|
def conversation_model?(_model)
|
@@ -211,7 +212,8 @@ module Boxcars
|
|
211
212
|
request_context = {
|
212
213
|
prompt: call_context[:prompt_object],
|
213
214
|
inputs: call_context[:inputs],
|
214
|
-
conversation_for_api: call_context[:api_request_params]
|
215
|
+
conversation_for_api: call_context[:api_request_params],
|
216
|
+
user_id:
|
215
217
|
}
|
216
218
|
|
217
219
|
track_ai_generation(
|
@@ -19,10 +19,11 @@ module Boxcars
|
|
19
19
|
"You should ask targeted questions"
|
20
20
|
|
21
21
|
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 20, **kwargs)
|
22
|
+
user_id = kwargs.delete(:user_id)
|
22
23
|
@llm_params = DEFAULT_PARAMS.merge(kwargs) # Corrected typo here
|
23
24
|
@prompts = prompts
|
24
25
|
@batch_size = batch_size
|
25
|
-
super(description:, name:)
|
26
|
+
super(description:, name:, user_id:)
|
26
27
|
end
|
27
28
|
|
28
29
|
# Renamed from open_ai_client to gemini_client for clarity
|
@@ -68,7 +69,8 @@ module Boxcars
|
|
68
69
|
request_context = {
|
69
70
|
prompt: current_prompt_object,
|
70
71
|
inputs:,
|
71
|
-
conversation_for_api: api_request_params&.dig(:messages) || []
|
72
|
+
conversation_for_api: api_request_params&.dig(:messages) || [],
|
73
|
+
user_id:
|
72
74
|
}
|
73
75
|
track_ai_generation(
|
74
76
|
duration_ms:,
|
@@ -82,13 +84,13 @@ module Boxcars
|
|
82
84
|
# If there's an error, raise it to maintain backward compatibility with existing tests
|
83
85
|
raise response_data[:error] if response_data[:error]
|
84
86
|
|
85
|
-
response_data
|
87
|
+
response_data[:parsed_json]
|
86
88
|
end
|
87
89
|
|
88
90
|
def run(question, **)
|
89
91
|
prompt = Prompt.new(template: question)
|
90
|
-
|
91
|
-
answer =
|
92
|
+
response = client(prompt:, inputs: {}, **)
|
93
|
+
answer = _extract_content_from_gemini_response(response)
|
92
94
|
Boxcars.debug("Answer: #{answer}", :cyan)
|
93
95
|
answer
|
94
96
|
end
|
@@ -19,10 +19,11 @@ module Boxcars
|
|
19
19
|
}.freeze
|
20
20
|
|
21
21
|
def initialize(name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompts: [], batch_size: 2, **kwargs)
|
22
|
+
user_id = kwargs.delete(:user_id)
|
22
23
|
@gpt4all_params = DEFAULT_PARAMS.merge(kwargs) # Store merged params
|
23
24
|
@prompts = prompts
|
24
25
|
@batch_size = batch_size # Retain if used by other methods
|
25
|
-
super(description:, name:)
|
26
|
+
super(description:, name:, user_id:)
|
26
27
|
end
|
27
28
|
|
28
29
|
def client(prompt:, inputs: {}, **kwargs)
|
@@ -31,10 +32,8 @@ module Boxcars
|
|
31
32
|
# current_params are the effective parameters for this call, including defaults and overrides
|
32
33
|
current_params = @gpt4all_params.merge(kwargs)
|
33
34
|
# api_request_params for GPT4All is just the input text.
|
34
|
-
api_request_params = nil
|
35
|
+
api_request_params, gpt4all_instance = nil
|
35
36
|
current_prompt_object = prompt.is_a?(Array) ? prompt.first : prompt
|
36
|
-
gpt4all_instance = nil # To ensure it's in scope for ensure block
|
37
|
-
|
38
37
|
begin
|
39
38
|
gpt4all_instance = Gpt4all::ConversationalAI.new
|
40
39
|
# prepare_resources might download models, could take time.
|
@@ -68,7 +67,8 @@ module Boxcars
|
|
68
67
|
request_context = {
|
69
68
|
prompt: current_prompt_object,
|
70
69
|
inputs:,
|
71
|
-
conversation_for_api: api_request_params&.dig(:prompt)
|
70
|
+
conversation_for_api: api_request_params&.dig(:prompt),
|
71
|
+
user_id:
|
72
72
|
}
|
73
73
|
|
74
74
|
track_ai_generation(
|