open_router_enhanced 1.0.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 +7 -0
- data/.env.example +1 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +130 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +41 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +384 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +138 -0
- data/LICENSE.txt +21 -0
- data/MIGRATION.md +556 -0
- data/README.md +1660 -0
- data/Rakefile +334 -0
- data/SECURITY.md +150 -0
- data/VCR_CONFIGURATION.md +80 -0
- data/docs/model_selection.md +637 -0
- data/docs/observability.md +430 -0
- data/docs/prompt_templates.md +422 -0
- data/docs/streaming.md +467 -0
- data/docs/structured_outputs.md +466 -0
- data/docs/tools.md +1016 -0
- data/examples/basic_completion.rb +122 -0
- data/examples/model_selection_example.rb +141 -0
- data/examples/observability_example.rb +199 -0
- data/examples/prompt_template_example.rb +184 -0
- data/examples/smart_completion_example.rb +89 -0
- data/examples/streaming_example.rb +176 -0
- data/examples/structured_outputs_example.rb +191 -0
- data/examples/tool_calling_example.rb +149 -0
- data/lib/open_router/client.rb +552 -0
- data/lib/open_router/http.rb +118 -0
- data/lib/open_router/json_healer.rb +263 -0
- data/lib/open_router/model_registry.rb +378 -0
- data/lib/open_router/model_selector.rb +462 -0
- data/lib/open_router/prompt_template.rb +290 -0
- data/lib/open_router/response.rb +371 -0
- data/lib/open_router/schema.rb +288 -0
- data/lib/open_router/streaming_client.rb +210 -0
- data/lib/open_router/tool.rb +221 -0
- data/lib/open_router/tool_call.rb +180 -0
- data/lib/open_router/usage_tracker.rb +277 -0
- data/lib/open_router/version.rb +5 -0
- data/lib/open_router.rb +123 -0
- data/sig/open_router.rbs +20 -0
- metadata +186 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Observability & Analytics
|
|
2
|
+
|
|
3
|
+
The OpenRouter gem provides comprehensive observability features to help you monitor usage, costs, performance, and errors in your AI applications.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Create client with automatic usage tracking
|
|
9
|
+
client = OpenRouter::Client.new(track_usage: true)
|
|
10
|
+
|
|
11
|
+
# Make some API calls
|
|
12
|
+
response = client.complete(
|
|
13
|
+
[{ role: "user", content: "Hello world" }],
|
|
14
|
+
model: "openai/gpt-4o-mini"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# View usage summary
|
|
18
|
+
puts "Used #{response.total_tokens} tokens"
|
|
19
|
+
puts "Cost: $#{response.cost_estimate}"
|
|
20
|
+
puts "Cache hit rate: #{client.usage_tracker.cache_hit_rate}%"
|
|
21
|
+
|
|
22
|
+
# Print detailed usage report
|
|
23
|
+
client.usage_tracker.print_summary
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features Overview
|
|
27
|
+
|
|
28
|
+
### 1. Response Metadata
|
|
29
|
+
Enhanced response objects provide detailed metadata:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
response = client.complete(messages, model: "openai/gpt-4o-mini")
|
|
33
|
+
|
|
34
|
+
# Token usage
|
|
35
|
+
response.prompt_tokens # => 150
|
|
36
|
+
response.completion_tokens # => 75
|
|
37
|
+
response.total_tokens # => 225
|
|
38
|
+
response.cached_tokens # => 50
|
|
39
|
+
|
|
40
|
+
# Cost information
|
|
41
|
+
response.cost_estimate # => 0.0023
|
|
42
|
+
|
|
43
|
+
# Provider and model info
|
|
44
|
+
response.provider # => "OpenAI"
|
|
45
|
+
response.model # => "openai/gpt-4o-mini"
|
|
46
|
+
response.system_fingerprint # => "fp_abc123"
|
|
47
|
+
response.finish_reason # => "stop"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Usage Tracking
|
|
51
|
+
Automatic tracking of token usage and costs across all API calls:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
# Enable usage tracking (default: true)
|
|
55
|
+
client = OpenRouter::Client.new(track_usage: true)
|
|
56
|
+
|
|
57
|
+
# Access usage tracker
|
|
58
|
+
tracker = client.usage_tracker
|
|
59
|
+
|
|
60
|
+
# Key metrics
|
|
61
|
+
tracker.total_tokens # Total tokens across all requests
|
|
62
|
+
tracker.total_cost # Total estimated cost
|
|
63
|
+
tracker.request_count # Number of API calls made
|
|
64
|
+
tracker.cache_hit_rate # Percentage of tokens served from cache
|
|
65
|
+
tracker.session_duration # Time since tracker initialization
|
|
66
|
+
|
|
67
|
+
# Performance metrics
|
|
68
|
+
tracker.tokens_per_second # Processing speed
|
|
69
|
+
tracker.average_cost_per_request
|
|
70
|
+
tracker.average_tokens_per_request
|
|
71
|
+
|
|
72
|
+
# Model insights
|
|
73
|
+
tracker.most_used_model # Model with most requests
|
|
74
|
+
tracker.most_expensive_model # Model with highest total cost
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Callback System
|
|
78
|
+
Event-driven observability with callbacks for key events:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
client = OpenRouter::Client.new
|
|
82
|
+
|
|
83
|
+
# Register callbacks for different events
|
|
84
|
+
client.on(:before_request) do |params|
|
|
85
|
+
puts "Making request to model: #{params[:model]}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
client.on(:after_response) do |response|
|
|
89
|
+
puts "Response received: #{response.total_tokens} tokens"
|
|
90
|
+
puts "Cost: $#{response.cost_estimate}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
client.on(:on_tool_call) do |tool_calls|
|
|
94
|
+
tool_calls.each do |tc|
|
|
95
|
+
puts "Tool called: #{tc.name}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
client.on(:on_error) do |error|
|
|
100
|
+
puts "Error occurred: #{error.message}"
|
|
101
|
+
# Log to monitoring system, send alerts, etc.
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
client.on(:on_healing) do |healing_data|
|
|
105
|
+
if healing_data[:healed]
|
|
106
|
+
puts "Successfully healed JSON response"
|
|
107
|
+
else
|
|
108
|
+
puts "JSON healing failed: #{healing_data[:error]}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 4. Streaming Observability
|
|
114
|
+
Enhanced streaming support with detailed event callbacks:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
streaming_client = OpenRouter::StreamingClient.new
|
|
118
|
+
|
|
119
|
+
streaming_client.on_stream(:on_start) do |data|
|
|
120
|
+
puts "Starting stream with model: #{data[:model]}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
streaming_client.on_stream(:on_chunk) do |chunk|
|
|
124
|
+
# Log chunk details, measure latency, etc.
|
|
125
|
+
puts "Chunk received: #{chunk.dig('choices', 0, 'delta', 'content')}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
streaming_client.on_stream(:on_finish) do |final_response|
|
|
129
|
+
puts "Stream completed. Total tokens: #{final_response&.total_tokens}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
streaming_client.on_stream(:on_tool_call_chunk) do |chunk|
|
|
133
|
+
# Track tool calling progress
|
|
134
|
+
puts "Tool call chunk received"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Stream with accumulation
|
|
138
|
+
response = streaming_client.stream_complete(
|
|
139
|
+
messages,
|
|
140
|
+
model: "openai/gpt-4o-mini",
|
|
141
|
+
accumulate_response: true
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Usage Analytics
|
|
146
|
+
|
|
147
|
+
### Detailed Reporting
|
|
148
|
+
Get comprehensive usage summaries:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
summary = client.usage_tracker.summary
|
|
152
|
+
|
|
153
|
+
# Returns structured data:
|
|
154
|
+
{
|
|
155
|
+
session: {
|
|
156
|
+
start: Time,
|
|
157
|
+
duration_seconds: Float,
|
|
158
|
+
requests: Integer
|
|
159
|
+
},
|
|
160
|
+
tokens: {
|
|
161
|
+
total: Integer,
|
|
162
|
+
prompt: Integer,
|
|
163
|
+
completion: Integer,
|
|
164
|
+
cached: Integer,
|
|
165
|
+
cache_hit_rate: String # "15.2%"
|
|
166
|
+
},
|
|
167
|
+
cost: {
|
|
168
|
+
total: Float,
|
|
169
|
+
average_per_request: Float
|
|
170
|
+
},
|
|
171
|
+
performance: {
|
|
172
|
+
tokens_per_second: Float,
|
|
173
|
+
average_tokens_per_request: Float
|
|
174
|
+
},
|
|
175
|
+
models: {
|
|
176
|
+
most_used: String,
|
|
177
|
+
most_expensive: String,
|
|
178
|
+
breakdown: Hash # Per-model stats
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Model Breakdown
|
|
184
|
+
Analyze usage by model:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
breakdown = client.usage_tracker.model_breakdown
|
|
188
|
+
|
|
189
|
+
# Returns:
|
|
190
|
+
{
|
|
191
|
+
"openai/gpt-4o-mini" => {
|
|
192
|
+
requests: 15,
|
|
193
|
+
tokens: 2500,
|
|
194
|
+
cost: 0.025,
|
|
195
|
+
cached_tokens: 200
|
|
196
|
+
},
|
|
197
|
+
"anthropic/claude-3-haiku" => {
|
|
198
|
+
requests: 8,
|
|
199
|
+
tokens: 1800,
|
|
200
|
+
cost: 0.018,
|
|
201
|
+
cached_tokens: 150
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Export Options
|
|
207
|
+
Export usage data for external analysis:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# Export as CSV
|
|
211
|
+
csv_data = client.usage_tracker.export_csv
|
|
212
|
+
File.write("usage_report.csv", csv_data)
|
|
213
|
+
|
|
214
|
+
# Get raw history
|
|
215
|
+
history = client.usage_tracker.history(limit: 100)
|
|
216
|
+
history.each do |entry|
|
|
217
|
+
puts "#{entry[:timestamp]}: #{entry[:model]} - #{entry[:tokens]} tokens"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Reset tracking
|
|
221
|
+
client.usage_tracker.reset!
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Performance Monitoring
|
|
225
|
+
|
|
226
|
+
### Cache Optimization
|
|
227
|
+
Monitor cache effectiveness:
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
# Check cache performance
|
|
231
|
+
tracker = client.usage_tracker
|
|
232
|
+
puts "Cache hit rate: #{tracker.cache_hit_rate}%"
|
|
233
|
+
puts "Cached tokens: #{tracker.total_cached_tokens}"
|
|
234
|
+
puts "Savings: #{tracker.cache_savings_estimate}"
|
|
235
|
+
|
|
236
|
+
# Per-model cache analysis
|
|
237
|
+
tracker.model_breakdown.each do |model, stats|
|
|
238
|
+
cache_rate = (stats[:cached_tokens].to_f / stats[:prompt_tokens]) * 100
|
|
239
|
+
puts "#{model}: #{cache_rate.round(1)}% cache hit rate"
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Response Time Tracking
|
|
244
|
+
Monitor API response times:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
client.on(:before_request) do |params|
|
|
248
|
+
@request_start = Time.now
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
client.on(:after_response) do |response|
|
|
252
|
+
duration = Time.now - @request_start
|
|
253
|
+
puts "Request took #{duration.round(3)}s"
|
|
254
|
+
puts "Processing speed: #{response.total_tokens / duration} tokens/sec"
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Error Monitoring
|
|
259
|
+
|
|
260
|
+
### Comprehensive Error Tracking
|
|
261
|
+
Monitor and respond to different error types:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
client.on(:on_error) do |error|
|
|
265
|
+
case error
|
|
266
|
+
when Faraday::UnauthorizedError
|
|
267
|
+
# Handle authentication issues
|
|
268
|
+
alert_system("Authentication failed")
|
|
269
|
+
when Faraday::TooManyRequestsError
|
|
270
|
+
# Handle rate limiting
|
|
271
|
+
exponential_backoff()
|
|
272
|
+
when OpenRouter::ServerError
|
|
273
|
+
# Handle server errors
|
|
274
|
+
log_error(error)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
client.on(:on_healing) do |healing_data|
|
|
279
|
+
if healing_data[:healed] == false
|
|
280
|
+
# JSON healing failed - may indicate model issues
|
|
281
|
+
alert_system("JSON healing failed for #{healing_data[:original]}")
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Health Checks
|
|
287
|
+
Monitor system health:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
def health_check
|
|
291
|
+
tracker = client.usage_tracker
|
|
292
|
+
|
|
293
|
+
health_status = {
|
|
294
|
+
uptime: tracker.session_duration,
|
|
295
|
+
total_requests: tracker.request_count,
|
|
296
|
+
error_rate: calculate_error_rate(),
|
|
297
|
+
avg_response_time: calculate_avg_response_time(),
|
|
298
|
+
cache_hit_rate: tracker.cache_hit_rate,
|
|
299
|
+
total_cost: tracker.total_cost
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# Alert if metrics exceed thresholds
|
|
303
|
+
alert_if_unhealthy(health_status)
|
|
304
|
+
|
|
305
|
+
health_status
|
|
306
|
+
end
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Integration Examples
|
|
310
|
+
|
|
311
|
+
### Datadog Integration
|
|
312
|
+
Send metrics to Datadog:
|
|
313
|
+
|
|
314
|
+
```ruby
|
|
315
|
+
require 'dogapi'
|
|
316
|
+
|
|
317
|
+
dog = Dogapi::Client.new(api_key, app_key)
|
|
318
|
+
|
|
319
|
+
client.on(:after_response) do |response|
|
|
320
|
+
dog.emit_point('openrouter.tokens.total', response.total_tokens)
|
|
321
|
+
dog.emit_point('openrouter.cost.estimate', response.cost_estimate)
|
|
322
|
+
dog.emit_point('openrouter.cache.hit_rate',
|
|
323
|
+
response.cached_tokens.to_f / response.prompt_tokens * 100)
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Prometheus Metrics
|
|
328
|
+
Export Prometheus metrics:
|
|
329
|
+
|
|
330
|
+
```ruby
|
|
331
|
+
require 'prometheus/client'
|
|
332
|
+
|
|
333
|
+
prometheus = Prometheus::Client.registry
|
|
334
|
+
|
|
335
|
+
tokens_counter = prometheus.counter(
|
|
336
|
+
:openrouter_tokens_total,
|
|
337
|
+
docstring: 'Total tokens processed'
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
cost_counter = prometheus.counter(
|
|
341
|
+
:openrouter_cost_total,
|
|
342
|
+
docstring: 'Total estimated cost'
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
client.on(:after_response) do |response|
|
|
346
|
+
tokens_counter.increment(by: response.total_tokens,
|
|
347
|
+
labels: { model: response.model })
|
|
348
|
+
cost_counter.increment(by: response.cost_estimate || 0,
|
|
349
|
+
labels: { model: response.model })
|
|
350
|
+
end
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Custom Analytics Dashboard
|
|
354
|
+
Build real-time analytics:
|
|
355
|
+
|
|
356
|
+
```ruby
|
|
357
|
+
class OpenRouterAnalytics
|
|
358
|
+
def initialize(client)
|
|
359
|
+
@client = client
|
|
360
|
+
@metrics = {}
|
|
361
|
+
|
|
362
|
+
setup_callbacks
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
private
|
|
366
|
+
|
|
367
|
+
def setup_callbacks
|
|
368
|
+
@client.on(:after_response) do |response|
|
|
369
|
+
update_metrics(response)
|
|
370
|
+
broadcast_update(response)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
@client.on(:on_error) do |error|
|
|
374
|
+
track_error(error)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def update_metrics(response)
|
|
379
|
+
@metrics[:requests] ||= 0
|
|
380
|
+
@metrics[:tokens] ||= 0
|
|
381
|
+
@metrics[:cost] ||= 0
|
|
382
|
+
|
|
383
|
+
@metrics[:requests] += 1
|
|
384
|
+
@metrics[:tokens] += response.total_tokens
|
|
385
|
+
@metrics[:cost] += response.cost_estimate || 0
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def broadcast_update(response)
|
|
389
|
+
# Send to WebSocket, EventSource, etc.
|
|
390
|
+
ActionCable.server.broadcast("analytics_channel", {
|
|
391
|
+
type: "response_completed",
|
|
392
|
+
tokens: response.total_tokens,
|
|
393
|
+
cost: response.cost_estimate,
|
|
394
|
+
model: response.model,
|
|
395
|
+
timestamp: Time.now.iso8601
|
|
396
|
+
})
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Usage
|
|
401
|
+
analytics = OpenRouterAnalytics.new(client)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Best Practices
|
|
405
|
+
|
|
406
|
+
### 1. Monitoring Setup
|
|
407
|
+
- Enable usage tracking in production
|
|
408
|
+
- Set up alerts for cost thresholds
|
|
409
|
+
- Monitor cache hit rates for optimization opportunities
|
|
410
|
+
- Track error rates and response times
|
|
411
|
+
|
|
412
|
+
### 2. Cost Management
|
|
413
|
+
- Set budget alerts using usage tracker
|
|
414
|
+
- Monitor per-model costs to optimize model selection
|
|
415
|
+
- Track cache effectiveness to reduce costs
|
|
416
|
+
- Use fallback models for cost optimization
|
|
417
|
+
|
|
418
|
+
### 3. Performance Optimization
|
|
419
|
+
- Monitor tokens per second for performance
|
|
420
|
+
- Track cache hit rates by model
|
|
421
|
+
- Use streaming for better perceived performance
|
|
422
|
+
- Monitor healing frequency to identify model issues
|
|
423
|
+
|
|
424
|
+
### 4. Error Handling
|
|
425
|
+
- Implement comprehensive error callbacks
|
|
426
|
+
- Set up alerting for authentication failures
|
|
427
|
+
- Monitor rate limiting and implement backoff
|
|
428
|
+
- Track healing failures as quality indicators
|
|
429
|
+
|
|
430
|
+
The OpenRouter gem's observability features provide everything you need to monitor, optimize, and maintain reliable AI applications at scale.
|