llm_cost_tracker 0.6.0 → 0.7.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +2 -3
  4. data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +1 -17
  5. data/config/routes.rb +1 -1
  6. data/lib/llm_cost_tracker/active_record_adapter.rb +9 -5
  7. data/lib/llm_cost_tracker/assets.rb +0 -6
  8. data/lib/llm_cost_tracker/budget.rb +3 -5
  9. data/lib/llm_cost_tracker/capture_verifier.rb +3 -10
  10. data/lib/llm_cost_tracker/configuration.rb +2 -10
  11. data/lib/llm_cost_tracker/doctor/ingestion_check.rb +1 -3
  12. data/lib/llm_cost_tracker/doctor.rb +8 -13
  13. data/lib/llm_cost_tracker/engine.rb +0 -3
  14. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +2 -2
  15. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +7 -1
  16. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +1 -17
  17. data/lib/llm_cost_tracker/integrations/registry.rb +0 -2
  18. data/lib/llm_cost_tracker/period_grouping.rb +3 -5
  19. data/lib/llm_cost_tracker/railtie.rb +2 -4
  20. data/lib/llm_cost_tracker/report.rb +2 -4
  21. data/lib/llm_cost_tracker/storage/active_record_backend.rb +2 -1
  22. data/lib/llm_cost_tracker/storage/active_record_inbox.rb +1 -6
  23. data/lib/llm_cost_tracker/storage/active_record_rollup_upsert_sql.rb +1 -1
  24. data/lib/llm_cost_tracker/storage/{dispatcher.rb → writer.rb} +4 -14
  25. data/lib/llm_cost_tracker/tag_sql.rb +1 -1
  26. data/lib/llm_cost_tracker/tags_column.rb +2 -0
  27. data/lib/llm_cost_tracker/tracker.rb +2 -2
  28. data/lib/llm_cost_tracker/version.rb +1 -1
  29. data/lib/llm_cost_tracker.rb +2 -14
  30. data/lib/tasks/llm_cost_tracker.rake +1 -1
  31. metadata +33 -60
  32. data/docs/architecture.md +0 -28
  33. data/docs/budgets.md +0 -45
  34. data/docs/configuration.md +0 -65
  35. data/docs/cookbook.md +0 -185
  36. data/docs/dashboard-overview.png +0 -0
  37. data/docs/dashboard.md +0 -38
  38. data/docs/extending.md +0 -32
  39. data/docs/operations.md +0 -44
  40. data/docs/pricing.md +0 -94
  41. data/docs/querying.md +0 -36
  42. data/docs/streaming.md +0 -70
  43. data/docs/technical/README.md +0 -10
  44. data/docs/technical/data-flow.md +0 -70
  45. data/docs/technical/extension-points.md +0 -111
  46. data/docs/technical/module-map.md +0 -197
  47. data/docs/technical/operational-notes.md +0 -97
  48. data/docs/upgrading.md +0 -47
  49. data/lib/llm_cost_tracker/configuration/storage_backend.rb +0 -26
  50. data/lib/llm_cost_tracker/engine_compatibility.rb +0 -15
  51. data/lib/llm_cost_tracker/storage/custom_backend.rb +0 -32
  52. data/lib/llm_cost_tracker/storage/log_backend.rb +0 -38
  53. data/lib/llm_cost_tracker/storage/registry.rb +0 -63
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LlmCostTracker
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -69,8 +69,8 @@ module LlmCostTracker
69
69
  @configuration_generation = @configuration_generation.to_i + 1
70
70
  current
71
71
  end
72
- Integrations.install!
73
- warn_for_configuration!(config)
72
+ Integrations::Registry.install!
73
+ config
74
74
  end
75
75
 
76
76
  def reset_configuration!
@@ -151,18 +151,6 @@ module LlmCostTracker
151
151
  collector&.finish!(errored: true)
152
152
  raise
153
153
  end
154
-
155
- private
156
-
157
- def warn_for_configuration!(config = configuration)
158
- return unless config.budget_exceeded_behavior == :block_requests
159
- return if config.active_record?
160
-
161
- Logging.warn(
162
- ":block_requests requires storage_backend = :active_record for monthly and daily preflight; " \
163
- "preflight blocking will be skipped."
164
- )
165
- end
166
154
  end
167
155
  end
168
156
 
@@ -24,7 +24,7 @@ namespace :llm_cost_tracker do
24
24
 
25
25
  desc "Print an LLM cost report from ActiveRecord storage"
26
26
  task report: :environment do
27
- days = (ENV["DAYS"] || LlmCostTracker::Report::DEFAULT_DAYS).to_i
27
+ days = (ENV["DAYS"] || LlmCostTracker::ReportData::DEFAULT_DAYS).to_i
28
28
  puts LlmCostTracker::Report.generate(days: days)
29
29
  end
30
30
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_cost_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergii Khomenko
@@ -10,6 +10,26 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2026-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '7.1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9.0'
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: activesupport
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -65,7 +85,7 @@ dependencies:
65
85
  - !ruby/object:Gem::Version
66
86
  version: '3.0'
67
87
  - !ruby/object:Gem::Dependency
68
- name: activerecord
88
+ name: railties
69
89
  requirement: !ruby/object:Gem::Requirement
70
90
  requirements:
71
91
  - - ">="
@@ -74,7 +94,7 @@ dependencies:
74
94
  - - "<"
75
95
  - !ruby/object:Gem::Version
76
96
  version: '9.0'
77
- type: :development
97
+ type: :runtime
78
98
  prerelease: false
79
99
  version_requirements: !ruby/object:Gem::Requirement
80
100
  requirements:
@@ -99,25 +119,19 @@ dependencies:
99
119
  - !ruby/object:Gem::Version
100
120
  version: '1.16'
101
121
  - !ruby/object:Gem::Dependency
102
- name: railties
122
+ name: pg
103
123
  requirement: !ruby/object:Gem::Requirement
104
124
  requirements:
105
- - - ">="
106
- - !ruby/object:Gem::Version
107
- version: '7.1'
108
- - - "<"
125
+ - - "~>"
109
126
  - !ruby/object:Gem::Version
110
- version: '9.0'
127
+ version: '1.6'
111
128
  type: :development
112
129
  prerelease: false
113
130
  version_requirements: !ruby/object:Gem::Requirement
114
131
  requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '7.1'
118
- - - "<"
132
+ - - "~>"
119
133
  - !ruby/object:Gem::Version
120
- version: '9.0'
134
+ version: '1.6'
121
135
  - !ruby/object:Gem::Dependency
122
136
  name: rake
123
137
  requirement: !ruby/object:Gem::Requirement
@@ -188,26 +202,6 @@ dependencies:
188
202
  - - "~>"
189
203
  - !ruby/object:Gem::Version
190
204
  version: '0.8'
191
- - !ruby/object:Gem::Dependency
192
- name: sqlite3
193
- requirement: !ruby/object:Gem::Requirement
194
- requirements:
195
- - - ">="
196
- - !ruby/object:Gem::Version
197
- version: '1.4'
198
- - - "<"
199
- - !ruby/object:Gem::Version
200
- version: '3.0'
201
- type: :development
202
- prerelease: false
203
- version_requirements: !ruby/object:Gem::Requirement
204
- requirements:
205
- - - ">="
206
- - !ruby/object:Gem::Version
207
- version: '1.4'
208
- - - "<"
209
- - !ruby/object:Gem::Version
210
- version: '3.0'
211
205
  - !ruby/object:Gem::Dependency
212
206
  name: webmock
213
207
  requirement: !ruby/object:Gem::Requirement
@@ -224,8 +218,9 @@ dependencies:
224
218
  version: '3.0'
225
219
  description: Tracks token usage, latency, and estimated costs for RubyLLM, OpenAI,
226
220
  Anthropic, Google Gemini, OpenRouter, DeepSeek, and OpenAI-compatible APIs. Works
227
- through Faraday middleware or explicit track/track_stream helpers, with ActiveRecord
228
- storage, tag-based attribution, price sync tasks, and budget guardrails.
221
+ through Rails SDK integrations, Faraday middleware, or explicit track/track_stream
222
+ helpers, with ActiveRecord storage, tag-based attribution, price sync tasks, and
223
+ budget guardrails.
229
224
  email:
230
225
  - sergey@mm.st
231
226
  executables: []
@@ -283,23 +278,6 @@ files:
283
278
  - app/views/llm_cost_tracker/tags/index.html.erb
284
279
  - app/views/llm_cost_tracker/tags/show.html.erb
285
280
  - config/routes.rb
286
- - docs/architecture.md
287
- - docs/budgets.md
288
- - docs/configuration.md
289
- - docs/cookbook.md
290
- - docs/dashboard-overview.png
291
- - docs/dashboard.md
292
- - docs/extending.md
293
- - docs/operations.md
294
- - docs/pricing.md
295
- - docs/querying.md
296
- - docs/streaming.md
297
- - docs/technical/README.md
298
- - docs/technical/data-flow.md
299
- - docs/technical/extension-points.md
300
- - docs/technical/module-map.md
301
- - docs/technical/operational-notes.md
302
- - docs/upgrading.md
303
281
  - lib/llm_cost_tracker.rb
304
282
  - lib/llm_cost_tracker/active_record_adapter.rb
305
283
  - lib/llm_cost_tracker/assets.rb
@@ -307,13 +285,11 @@ files:
307
285
  - lib/llm_cost_tracker/capture_verifier.rb
308
286
  - lib/llm_cost_tracker/configuration.rb
309
287
  - lib/llm_cost_tracker/configuration/instrumentation.rb
310
- - lib/llm_cost_tracker/configuration/storage_backend.rb
311
288
  - lib/llm_cost_tracker/cost.rb
312
289
  - lib/llm_cost_tracker/doctor.rb
313
290
  - lib/llm_cost_tracker/doctor/capture_check.rb
314
291
  - lib/llm_cost_tracker/doctor/ingestion_check.rb
315
292
  - lib/llm_cost_tracker/engine.rb
316
- - lib/llm_cost_tracker/engine_compatibility.rb
317
293
  - lib/llm_cost_tracker/errors.rb
318
294
  - lib/llm_cost_tracker/event.rb
319
295
  - lib/llm_cost_tracker/event_metadata.rb
@@ -392,10 +368,7 @@ files:
392
368
  - lib/llm_cost_tracker/storage/active_record_rollup_upsert_sql.rb
393
369
  - lib/llm_cost_tracker/storage/active_record_rollups.rb
394
370
  - lib/llm_cost_tracker/storage/active_record_store.rb
395
- - lib/llm_cost_tracker/storage/custom_backend.rb
396
- - lib/llm_cost_tracker/storage/dispatcher.rb
397
- - lib/llm_cost_tracker/storage/log_backend.rb
398
- - lib/llm_cost_tracker/storage/registry.rb
371
+ - lib/llm_cost_tracker/storage/writer.rb
399
372
  - lib/llm_cost_tracker/stream_capture.rb
400
373
  - lib/llm_cost_tracker/stream_collector.rb
401
374
  - lib/llm_cost_tracker/tag_accessors.rb
@@ -438,5 +411,5 @@ requirements: []
438
411
  rubygems_version: 3.5.22
439
412
  signing_key:
440
413
  specification_version: 4
441
- summary: Self-hosted LLM usage and cost tracking for Ruby and Rails
414
+ summary: Rails-native LLM usage and cost tracking with ActiveRecord storage
442
415
  test_files: []
data/docs/architecture.md DELETED
@@ -1,28 +0,0 @@
1
- # Architecture
2
-
3
- LLM Cost Tracker is a provider-agnostic billing ledger. Core code should model durable billing concepts, not the naming quirks of one provider or one model family.
4
-
5
- Core vocabulary belongs in provider-neutral terms:
6
-
7
- - `input_tokens`
8
- - `cache_read_input_tokens`
9
- - `cache_write_input_tokens`
10
- - `output_tokens`
11
- - `hidden_output_tokens`
12
- - `pricing_mode`
13
- - `provider_response_id`
14
-
15
- Provider-specific names belong only at ingestion boundaries: parsers and stream adapters. Those adapters translate raw fields into the canonical ledger vocabulary before data reaches `Tracker`, `Pricing`, storage, dashboard services, or reports.
16
-
17
- Pricing logic should prefer generic mechanisms over provider branches. Use provider/model price entries only for lookup and rate selection. Use `pricing_mode` plus mode-prefixed price keys for alternate billing modes instead of adding model-specific conditionals.
18
-
19
- Tags remain the extension point for app-specific attribution such as tenant, user, feature, trace, job, workflow, or agent session. Do not promote those dimensions into first-class columns unless the ledger itself needs them for provider-agnostic billing behavior.
20
-
21
- Hot-path guardrails must not aggregate over the growing call ledger. ActiveRecord period budgets should read maintained rows in `llm_cost_tracker_period_totals`; dashboard analytics may run grouped queries because they are user-initiated reporting paths. Do not add dashboard-only aggregate tables until bounded indexed reads from `llm_api_calls` are no longer enough for the supported date range.
22
-
23
- ## Technical Docs
24
-
25
- - [Module map](technical/module-map.md)
26
- - [Data flow](technical/data-flow.md)
27
- - [Extension points](technical/extension-points.md)
28
- - [Operational notes](technical/operational-notes.md)
data/docs/budgets.md DELETED
@@ -1,45 +0,0 @@
1
- # Budgets and Guardrails
2
-
3
- Budgets are safety rails for a Rails app using LLMs in production. They are not
4
- invoice reconciliation and they are not a transactional quota system.
5
-
6
- The full behavior reference is moving here from the README: monthly, daily, and
7
- per-call budgets; notification payloads; preflight behavior; and failure modes.
8
-
9
- ## Canonical Sources
10
-
11
- Until this page is expanded, use:
12
-
13
- - [Budgets](../README.md#budgets)
14
- - [Known limitations](../README.md#known-limitations)
15
- - [Operations](operations.md)
16
-
17
- ## Behaviors
18
-
19
- - `:notify`: call `on_budget_exceeded` after a priced event crosses a limit.
20
- - `:raise`: record the event, then raise `BudgetExceededError`.
21
- - `:block_requests`: preflight future calls when stored period totals are
22
- already over budget.
23
-
24
- ```ruby
25
- config.monthly_budget = 500.00
26
- config.daily_budget = 50.00
27
- config.per_call_budget = 2.00
28
- config.budget_exceeded_behavior = :block_requests
29
- ```
30
-
31
- `:block_requests` needs ActiveRecord storage for shared period totals. Under
32
- concurrency it stops the next request after overspend is visible; it does not
33
- make provider spend transactional.
34
-
35
- ## Error Payload
36
-
37
- `BudgetExceededError` exposes:
38
-
39
- - `budget_type`
40
- - `total`
41
- - `budget`
42
- - `monthly_total`
43
- - `daily_total`
44
- - `call_cost`
45
- - `last_event`
@@ -1,65 +0,0 @@
1
- # Configuration
2
-
3
- Configuration is the contract between the host app and the ledger: where events
4
- go, which integrations are enabled, how attribution is attached, and how the app
5
- reacts when storage, pricing, or budgets need attention.
6
-
7
- The full option reference is moving here from the README. Until that migration is
8
- complete, the README anchors below remain canonical.
9
-
10
- ## Canonical Sources
11
-
12
- Until this page is expanded, use:
13
-
14
- - [Quickstart](../README.md#quickstart)
15
- - [Capturing calls](../README.md#capturing-calls)
16
- - [Tags](../README.md#tags-who-burned-this-money)
17
- - [Pricing](../README.md#pricing)
18
- - [Budgets](../README.md#budgets)
19
-
20
- ## Scope
21
-
22
- This page is scoped to:
23
-
24
- - `storage_backend`: `:log`, `:active_record`, and `:custom`; ActiveRecord capture uses a durable inbox when the ingestion migration is present
25
- - `default_tags`: static tags and per-request callable tags
26
- - `instrument`: RubyLLM and official SDK integrations
27
- - `prices_file` and `pricing_overrides`
28
- - `monthly_budget`, `daily_budget`, and `per_call_budget`
29
- - `budget_exceeded_behavior`
30
- - `storage_error_behavior`
31
- - `unknown_pricing_behavior`
32
- - `openai_compatible_providers`
33
- - `report_tag_breakdowns`
34
-
35
- ## Minimal Production Config
36
-
37
- ```ruby
38
- LlmCostTracker.configure do |config|
39
- config.storage_backend = :active_record
40
- config.default_tags = -> { { environment: Rails.env } }
41
- config.prices_file = Rails.root.join("config/llm_cost_tracker_prices.yml")
42
- config.instrument :openai
43
- end
44
- ```
45
-
46
- Keep configuration at boot. Mutable shared settings are frozen after
47
- `configure` returns so request-time code cannot silently change global tracking
48
- behavior.
49
-
50
- Enabled SDK integrations are fail-fast. The client gem must be loaded, meet the
51
- minimum supported version, and expose the expected classes and methods before
52
- LLM Cost Tracker installs its wrapper.
53
-
54
- ## Capture Verification
55
-
56
- After boot, run:
57
-
58
- ```bash
59
- bin/rails llm_cost_tracker:verify_capture
60
- ```
61
-
62
- For ActiveRecord storage, the task records a synthetic manual event inside a
63
- rollback and checks that notifications and persistence both work. For log and
64
- custom storage, it reports the configured capture path without invoking external
65
- sinks.
data/docs/cookbook.md DELETED
@@ -1,185 +0,0 @@
1
- # Cookbook
2
-
3
- Short integration recipes for common Ruby clients. Prefer SDK integrations or middleware. Use `track` and `track_stream` only as fallback helpers for unsupported clients.
4
-
5
- | Client | Best path | Why |
6
- |---|---|---|
7
- | RubyLLM | `config.instrument :ruby_llm` | The integration wraps RubyLLM's provider layer without adding a third-party instrumentation gem. |
8
- | Official `openai` gem | `config.instrument :openai` | The integration wraps SDK resource methods without changing call sites. |
9
- | Official `anthropic` gem | `config.instrument :anthropic` | The integration records returned message usage without changing call sites. |
10
- | `ruby-openai` | Faraday middleware | The client is built on Faraday and accepts middleware via the constructor block. |
11
- | OpenAI-compatible proxy | Faraday middleware | Use `ruby-openai` or a direct Faraday client against the proxy host. |
12
- | Custom Faraday client | Faraday middleware | The middleware can parse known provider responses automatically. |
13
- | Other clients | Adapter first, fallback helpers second | Add a stable integration instead of scattering per-call ledger code. |
14
-
15
- ## RubyLLM
16
-
17
- Enable the integration once, then keep normal RubyLLM calls unchanged.
18
-
19
- ```ruby
20
- LlmCostTracker.configure do |config|
21
- config.instrument :ruby_llm
22
- end
23
-
24
- LlmCostTracker.with_tags(feature: "support_chat") do
25
- RubyLLM.chat.ask("Hello")
26
- RubyLLM.embed("text to embed")
27
- end
28
- ```
29
-
30
- The RubyLLM integration supports `ruby_llm >= 1.14.1` and checks RubyLLM's provider contract at boot. Chat, embedding, and transcription calls are captured. Image generation, moderation, and tool execution are not recorded as separate ledger rows.
31
-
32
- ## Official OpenAI SDK
33
-
34
- Enable the integration once, then keep normal `openai` gem calls unchanged.
35
-
36
- ```ruby
37
- LlmCostTracker.configure do |config|
38
- config.instrument :openai
39
- end
40
-
41
- client = OpenAI::Client.new(api_key: ENV["OPENAI_API_KEY"])
42
-
43
- client.responses.create(model: "gpt-4o", input: "Hello")
44
- client.chat.completions.create(
45
- model: "gpt-4o",
46
- messages: [{ role: "user", content: "Hello" }]
47
- )
48
-
49
- client.responses.stream(model: "gpt-4o", input: "Hello").each do |event|
50
- puts event.type
51
- end
52
-
53
- client.responses.stream_raw(model: "gpt-4o", input: "Hello").each do |event|
54
- puts event.type
55
- end
56
-
57
- client.chat.completions.stream_raw(
58
- model: "gpt-4o",
59
- messages: [{ role: "user", content: "Hello" }],
60
- stream_options: { include_usage: true }
61
- ).each do |event|
62
- puts event
63
- end
64
- ```
65
-
66
- The OpenAI SDK integration supports `openai >= 0.59.0`. Streaming calls are recorded after the returned stream is consumed. Chat Completions streams need `stream_options: { include_usage: true }` for final usage.
67
-
68
- ## Official Anthropic SDK
69
-
70
- Enable the integration once, then keep normal `anthropic` gem calls unchanged.
71
-
72
- ```ruby
73
- LlmCostTracker.configure do |config|
74
- config.instrument :anthropic
75
- end
76
-
77
- client = Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
78
-
79
- client.messages.create(
80
- max_tokens: 1024,
81
- model: "claude-sonnet-4-5-20250929",
82
- messages: [{ role: "user", content: "Hello" }]
83
- )
84
-
85
- client.messages.stream(
86
- max_tokens: 1024,
87
- model: "claude-sonnet-4-5-20250929",
88
- messages: [{ role: "user", content: "Hello" }]
89
- ).each do |event|
90
- puts event.type
91
- end
92
-
93
- client.messages.stream_raw(
94
- max_tokens: 1024,
95
- model: "claude-sonnet-4-5-20250929",
96
- messages: [{ role: "user", content: "Hello" }]
97
- ).each do |event|
98
- puts event.type
99
- end
100
- ```
101
-
102
- The Anthropic SDK integration supports `anthropic >= 1.36.0`. Streaming calls are recorded after the returned stream is consumed.
103
-
104
- ## ruby-openai
105
-
106
- `ruby-openai` is a community client that occupies the same `OpenAI::Client` constant as the official gem; only one of the two can be loaded. `config.instrument :openai` is for the official gem. For `ruby-openai`, attach the Faraday middleware via the constructor block:
107
-
108
- ```ruby
109
- client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"]) do |f|
110
- f.use :llm_cost_tracker, tags: { feature: "chat" }
111
- end
112
-
113
- client.chat(
114
- parameters: {
115
- model: "gpt-4o",
116
- messages: [{ role: "user", content: "Hello" }],
117
- stream: proc { |chunk, _bytesize| puts chunk.dig("choices", 0, "delta", "content") },
118
- stream_options: { include_usage: true }
119
- }
120
- )
121
- ```
122
-
123
- Use the constructor block on every client you build, or wrap client creation in your own factory.
124
-
125
- ## Azure OpenAI
126
-
127
- Azure's v1 API works with OpenAI-compatible HTTP shapes, but pricing and deployment names are yours. Register the Azure host, use the Faraday middleware path, and keep Azure-specific prices in `prices_file` or `pricing_overrides`.
128
-
129
- ```ruby
130
- LlmCostTracker.configure do |config|
131
- config.openai_compatible_providers["my-resource.openai.azure.com"] = "azure_openai"
132
- end
133
-
134
- conn = Faraday.new(url: "https://my-resource.openai.azure.com") do |f|
135
- f.use :llm_cost_tracker, tags: { feature: "chat" }
136
- f.request :json
137
- f.response :json
138
- f.adapter Faraday.default_adapter
139
- end
140
-
141
- conn.post(
142
- "/openai/v1/responses",
143
- { model: "gpt-4o-prod", input: "Hello" },
144
- { "api-key" => ENV.fetch("AZURE_OPENAI_API_KEY") }
145
- )
146
- ```
147
-
148
- ## Gemini API
149
-
150
- Google's official Gemini SDKs do not include Ruby. Use a Faraday client against the REST API so the Gemini parser can capture usage automatically.
151
-
152
- ```ruby
153
- conn = Faraday.new(url: "https://generativelanguage.googleapis.com") do |f|
154
- f.use :llm_cost_tracker, tags: { feature: "chat" }
155
- f.request :json
156
- f.response :json
157
- f.adapter Faraday.default_adapter
158
- end
159
-
160
- conn.post(
161
- "/v1beta/models/gemini-2.5-flash:generateContent?key=#{ENV.fetch("GOOGLE_API_KEY")}",
162
- { contents: [{ role: "user", parts: [{ text: "Hello" }] }] }
163
- )
164
- ```
165
-
166
- ## LiteLLM proxy
167
-
168
- LiteLLM Proxy speaks an OpenAI-compatible HTTP shape, so register the proxy host once and keep using the normal middleware path.
169
-
170
- ```ruby
171
- LlmCostTracker.configure do |config|
172
- config.openai_compatible_providers["proxy.internal.example"] = "litellm"
173
- end
174
-
175
- client = OpenAI::Client.new(
176
- access_token: ENV["LITELLM_API_KEY"],
177
- uri_base: "https://proxy.internal.example"
178
- ) do |f|
179
- f.use :llm_cost_tracker, tags: { gateway: "litellm" }
180
- end
181
-
182
- client.chat(parameters: { model: "openai/gpt-5-mini", messages: [{ role: "user", content: "Hello" }] })
183
- ```
184
-
185
- If your proxy exposes custom model IDs or discounts, add them in `prices_file` or `pricing_overrides`.
Binary file
data/docs/dashboard.md DELETED
@@ -1,38 +0,0 @@
1
- # Dashboard
2
-
3
- The dashboard is a Rails Engine for humans reviewing spend, attribution, and data
4
- quality. It is server-rendered ERB, has no JavaScript bundle, and reads from the
5
- host app's `llm_api_calls` table.
6
-
7
- The detailed dashboard guide is moving here from the README: mounting, route
8
- constraints, authentication examples, page map, and operational notes.
9
-
10
- ## Canonical Sources
11
-
12
- Until this page is expanded, use:
13
-
14
- - [Dashboard](../README.md#dashboard)
15
- - [Privacy](../README.md#privacy)
16
- - [Operations](operations.md)
17
-
18
- ## Mounting
19
-
20
- ```ruby
21
- mount LlmCostTracker::Engine => "/llm-costs"
22
- ```
23
-
24
- Use `storage_backend = :active_record` for apps that mount the dashboard.
25
-
26
- ## Pages
27
-
28
- - Overview: spend trend, budget status, anomaly banner, provider rollup, top models
29
- - Models: spend and usage by provider and model
30
- - Calls: filterable call ledger with CSV export
31
- - Tags: tag keys and tag value breakdowns
32
- - Data Quality: unknown pricing, untagged calls, missing latency, incomplete streams
33
-
34
- ## Authentication
35
-
36
- The gem does not ship dashboard auth. Mount the engine behind the host app's
37
- existing authentication layer: Devise, basic auth, Cloudflare Access, or your own
38
- constraints.
data/docs/extending.md DELETED
@@ -1,32 +0,0 @@
1
- # Extending LLM Cost Tracker
2
-
3
- Extensions belong at clear boundaries: parsers for response shapes, integrations
4
- for SDK hooks, pricing files for rates, and custom storage for apps that own
5
- persistence themselves.
6
-
7
- The practical extension guide is moving here from the README. The lower-level
8
- contracts already live in the technical extension reference.
9
-
10
- ## Canonical Sources
11
-
12
- Until this page is expanded, use:
13
-
14
- - [Capturing calls](../README.md#capturing-calls)
15
- - [Pricing](pricing.md)
16
- - [Technical extension points](technical/extension-points.md)
17
-
18
- ## Extension Points
19
-
20
- - Custom parser: translate a provider response into `ParsedUsage`.
21
- - OpenAI-compatible host: register the host-to-provider mapping.
22
- - Custom storage: receive the canonical `Event` and write it elsewhere.
23
- - Notifications subscriber: observe `llm_request.llm_cost_tracker`.
24
- - Local price file: model gateway IDs, contract rates, or unsupported models.
25
-
26
- ## Parser Boundary
27
-
28
- A parser matches request URLs, translates provider response shapes into
29
- `ParsedUsage`, and returns `nil` when the response is outside its contract.
30
-
31
- Do provider-specific translation at this boundary. Keep `Tracker`, storage,
32
- dashboard, and pricing in canonical ledger terms.
data/docs/operations.md DELETED
@@ -1,44 +0,0 @@
1
- # Operations
2
-
3
- Production use is mostly about choosing the right storage backend, keeping the
4
- database healthy, and understanding where the gem is intentionally best effort.
5
-
6
- The operational guide is moving here from the README: retention, tag storage,
7
- thread safety, connection pools, and deployment notes.
8
-
9
- ## Canonical Sources
10
-
11
- Until this page is expanded, use:
12
-
13
- - [Privacy](../README.md#privacy)
14
- - [Known limitations](../README.md#known-limitations)
15
- - [Technical operational notes](technical/operational-notes.md)
16
-
17
- ## Production Defaults
18
-
19
- - Use `storage_backend = :active_record` for the shared ledger, dashboard, and
20
- cross-process budget guardrails.
21
- - Size the ActiveRecord connection pool for your app plus ledger writes.
22
- - Keep `storage_error_behavior = :warn` unless losing the LLM response is better
23
- than losing the ledger event.
24
- - Treat `:block_requests` as a guardrail, not a hard quota.
25
- - Keep `default_tags` callables fast and thread-safe.
26
-
27
- ## Retention
28
-
29
- Retention is explicit. Use the prune task when the ledger should not grow
30
- forever:
31
-
32
- ```bash
33
- DAYS=90 bin/rails llm_cost_tracker:prune
34
- ```
35
-
36
- When ActiveRecord period rollups are installed, pruning decrements the
37
- affected daily and monthly buckets in the same batch transaction as the ledger
38
- delete.
39
-
40
- ## Data Shape
41
-
42
- Tags are JSONB with a GIN index on PostgreSQL and JSON text elsewhere. The
43
- dashboard and query helpers work across supported adapters, but PostgreSQL is the
44
- strongest path for large tag-heavy ledgers.