llm_cost_tracker 0.9.0 → 0.11.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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/README.md +6 -2
  4. data/app/assets/llm_cost_tracker/application.css +782 -801
  5. data/app/controllers/llm_cost_tracker/application_controller.rb +15 -3
  6. data/app/controllers/llm_cost_tracker/calls_controller.rb +39 -20
  7. data/app/controllers/llm_cost_tracker/dashboard_controller.rb +0 -3
  8. data/app/controllers/llm_cost_tracker/models_controller.rb +3 -1
  9. data/app/controllers/llm_cost_tracker/pricing_controller.rb +16 -0
  10. data/app/controllers/llm_cost_tracker/reconciliation_controller.rb +13 -19
  11. data/app/controllers/llm_cost_tracker/tags_controller.rb +3 -1
  12. data/app/helpers/llm_cost_tracker/application_helper.rb +16 -4
  13. data/app/helpers/llm_cost_tracker/chart_helper.rb +22 -6
  14. data/app/helpers/llm_cost_tracker/sortable_table_helper.rb +41 -0
  15. data/app/models/llm_cost_tracker/provider_invoice_import.rb +9 -4
  16. data/app/services/llm_cost_tracker/dashboard/pricing_overview.rb +95 -0
  17. data/app/services/llm_cost_tracker/dashboard/setup_state.rb +104 -0
  18. data/app/services/llm_cost_tracker/dashboard/sort.rb +9 -0
  19. data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +19 -5
  20. data/app/services/llm_cost_tracker/dashboard/top_models.rb +34 -19
  21. data/app/views/layouts/llm_cost_tracker/application.html.erb +80 -17
  22. data/app/views/llm_cost_tracker/calls/index.html.erb +69 -90
  23. data/app/views/llm_cost_tracker/calls/show.html.erb +119 -120
  24. data/app/views/llm_cost_tracker/dashboard/index.html.erb +119 -158
  25. data/app/views/llm_cost_tracker/data_quality/index.html.erb +109 -108
  26. data/app/views/llm_cost_tracker/errors/database.html.erb +2 -2
  27. data/app/views/llm_cost_tracker/models/index.html.erb +39 -59
  28. data/app/views/llm_cost_tracker/pricing/index.html.erb +93 -0
  29. data/app/views/llm_cost_tracker/reconciliation/index.html.erb +49 -58
  30. data/app/views/llm_cost_tracker/shared/_filter_pill_date.html.erb +19 -0
  31. data/app/views/llm_cost_tracker/shared/_filter_pill_model.html.erb +22 -0
  32. data/app/views/llm_cost_tracker/shared/_filter_pill_provider.html.erb +22 -0
  33. data/app/views/llm_cost_tracker/shared/_filter_pill_stream.html.erb +23 -0
  34. data/app/views/llm_cost_tracker/shared/_spend_chart.html.erb +3 -13
  35. data/app/views/llm_cost_tracker/shared/_tag_chips.html.erb +1 -1
  36. data/app/views/llm_cost_tracker/shared/setup_required.html.erb +16 -15
  37. data/app/views/llm_cost_tracker/tags/index.html.erb +27 -32
  38. data/app/views/llm_cost_tracker/tags/show.html.erb +83 -102
  39. data/config/routes.rb +1 -0
  40. data/lib/llm_cost_tracker/billing/cost_status.rb +21 -25
  41. data/lib/llm_cost_tracker/billing/line_item.rb +15 -49
  42. data/lib/llm_cost_tracker/budget.rb +29 -8
  43. data/lib/llm_cost_tracker/{parsers → capture}/sse.rb +1 -1
  44. data/lib/llm_cost_tracker/capture/stream_collector.rb +34 -42
  45. data/lib/llm_cost_tracker/capture/stream_tracker.rb +2 -6
  46. data/lib/llm_cost_tracker/configuration.rb +30 -44
  47. data/lib/llm_cost_tracker/doctor/capture_verifier.rb +1 -1
  48. data/lib/llm_cost_tracker/doctor/ingestion_check.rb +8 -8
  49. data/lib/llm_cost_tracker/doctor/legacy_audit_check.rb +0 -2
  50. data/lib/llm_cost_tracker/doctor/legacy_billing_status_check.rb +0 -2
  51. data/lib/llm_cost_tracker/doctor.rb +80 -25
  52. data/lib/llm_cost_tracker/engine.rb +1 -2
  53. data/lib/llm_cost_tracker/errors.rb +3 -2
  54. data/lib/llm_cost_tracker/event.rb +47 -0
  55. data/lib/llm_cost_tracker/generators/llm_cost_tracker/{durable_ingestion_generator.rb → async_ingestion_generator.rb} +8 -8
  56. data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +4 -23
  57. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/{create_llm_cost_tracker_durable_ingestion.rb.erb → create_llm_cost_tracker_async_ingestion.rb.erb} +3 -3
  58. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_reconciliation.rb.erb +6 -1
  59. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +14 -7
  60. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_call_rollups_provider.rb.erb +27 -8
  61. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_call_tags_key_value_index.rb.erb +5 -5
  62. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoice_imports_provider.rb.erb +36 -0
  63. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoices_metadata_index.rb.erb +27 -0
  64. data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_call_rollups_provider_generator.rb +0 -9
  65. data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoice_imports_provider_generator.rb +31 -0
  66. data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoices_metadata_index_generator.rb +31 -0
  67. data/lib/llm_cost_tracker/ingestion/batch.rb +5 -2
  68. data/lib/llm_cost_tracker/ingestion/inbox.rb +4 -25
  69. data/lib/llm_cost_tracker/ingestion/pool.rb +44 -0
  70. data/lib/llm_cost_tracker/ingestion/worker.rb +22 -36
  71. data/lib/llm_cost_tracker/ingestion.rb +8 -9
  72. data/lib/llm_cost_tracker/integrations/anthropic.rb +46 -68
  73. data/lib/llm_cost_tracker/integrations/base.rb +14 -11
  74. data/lib/llm_cost_tracker/integrations/openai.rb +104 -131
  75. data/lib/llm_cost_tracker/integrations/ruby_llm.rb +27 -73
  76. data/lib/llm_cost_tracker/integrations.rb +14 -13
  77. data/lib/llm_cost_tracker/ledger/period/totals.rb +5 -3
  78. data/lib/llm_cost_tracker/ledger/rollups.rb +4 -13
  79. data/lib/llm_cost_tracker/ledger/schema/call_line_items.rb +11 -0
  80. data/lib/llm_cost_tracker/ledger/schema/call_rollups.rb +13 -3
  81. data/lib/llm_cost_tracker/ledger/schema/call_tags.rb +11 -0
  82. data/lib/llm_cost_tracker/ledger/schema/calls.rb +0 -4
  83. data/lib/llm_cost_tracker/ledger/schema/ingestion_inbox_entries.rb +13 -3
  84. data/lib/llm_cost_tracker/ledger/schema/ingestion_leases.rb +13 -3
  85. data/lib/llm_cost_tracker/ledger/schema/provider_invoice_imports.rb +19 -9
  86. data/lib/llm_cost_tracker/ledger/schema/provider_invoices.rb +26 -11
  87. data/lib/llm_cost_tracker/ledger/store.rb +21 -18
  88. data/lib/llm_cost_tracker/ledger/tags/query.rb +0 -1
  89. data/lib/llm_cost_tracker/ledger.rb +13 -0
  90. data/lib/llm_cost_tracker/logging.rb +0 -4
  91. data/lib/llm_cost_tracker/middleware/faraday.rb +46 -17
  92. data/lib/llm_cost_tracker/parsers/anthropic.rb +35 -59
  93. data/lib/llm_cost_tracker/parsers/azure.rb +46 -0
  94. data/lib/llm_cost_tracker/parsers/base.rb +53 -47
  95. data/lib/llm_cost_tracker/parsers/gemini.rb +23 -27
  96. data/lib/llm_cost_tracker/parsers/openai.rb +8 -40
  97. data/lib/llm_cost_tracker/parsers/openai_compatible.rb +26 -49
  98. data/lib/llm_cost_tracker/parsers/openai_usage.rb +19 -23
  99. data/lib/llm_cost_tracker/parsers.rb +29 -4
  100. data/lib/llm_cost_tracker/prices.json +567 -579
  101. data/lib/llm_cost_tracker/pricing/backfill.rb +140 -0
  102. data/lib/llm_cost_tracker/pricing/effective_prices.rb +2 -4
  103. data/lib/llm_cost_tracker/pricing/estimator.rb +33 -0
  104. data/lib/llm_cost_tracker/pricing/explainer.rb +5 -2
  105. data/lib/llm_cost_tracker/pricing/lookup.rb +37 -2
  106. data/lib/llm_cost_tracker/pricing/mode.rb +34 -4
  107. data/lib/llm_cost_tracker/pricing/registry.rb +0 -7
  108. data/lib/llm_cost_tracker/pricing/service_charges.rb +6 -10
  109. data/lib/llm_cost_tracker/pricing/{sync_change_printer.rb → sync/change_printer.rb} +3 -3
  110. data/lib/llm_cost_tracker/pricing/sync/registry_writer.rb +14 -2
  111. data/lib/llm_cost_tracker/pricing/sync.rb +1 -9
  112. data/lib/llm_cost_tracker/pricing/unknown.rb +5 -2
  113. data/lib/llm_cost_tracker/pricing.rb +71 -43
  114. data/lib/llm_cost_tracker/providers/anthropic/server_tools.rb +15 -0
  115. data/lib/llm_cost_tracker/providers/anthropic/tier_classification.rb +22 -0
  116. data/lib/llm_cost_tracker/providers/azure/hosts.rb +17 -0
  117. data/lib/llm_cost_tracker/providers/gemini/model_families.rb +17 -0
  118. data/lib/llm_cost_tracker/providers/openai/hosts.rb +35 -0
  119. data/lib/llm_cost_tracker/providers/openai/model_families.rb +51 -0
  120. data/lib/llm_cost_tracker/providers/openai/service_charges.rb +157 -0
  121. data/lib/llm_cost_tracker/railtie.rb +3 -5
  122. data/lib/llm_cost_tracker/reconcile_tasks.rb +18 -21
  123. data/lib/llm_cost_tracker/reconciliation/diff.rb +26 -45
  124. data/lib/llm_cost_tracker/reconciliation/diff_result.rb +0 -4
  125. data/lib/llm_cost_tracker/reconciliation/importer.rb +3 -7
  126. data/lib/llm_cost_tracker/reconciliation/sources/anthropic_usage.rb +10 -33
  127. data/lib/llm_cost_tracker/reconciliation/sources/coercion.rb +40 -0
  128. data/lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb +7 -31
  129. data/lib/llm_cost_tracker/report/formatter.rb +32 -19
  130. data/lib/llm_cost_tracker/report.rb +0 -4
  131. data/lib/llm_cost_tracker/retention.rb +20 -8
  132. data/lib/llm_cost_tracker/tags/sanitizer.rb +13 -17
  133. data/lib/llm_cost_tracker/token_usage.rb +4 -0
  134. data/lib/llm_cost_tracker/tracker.rb +33 -74
  135. data/lib/llm_cost_tracker/version.rb +1 -1
  136. data/lib/llm_cost_tracker.rb +11 -15
  137. data/lib/tasks/llm_cost_tracker.rake +16 -2
  138. metadata +31 -12
  139. data/app/views/llm_cost_tracker/shared/_active_filters.html.erb +0 -16
  140. data/app/views/llm_cost_tracker/shared/_filters.html.erb +0 -66
  141. data/app/views/llm_cost_tracker/shared/_sort.html.erb +0 -13
  142. data/lib/llm_cost_tracker/dashboard_setup_state.rb +0 -109
  143. data/lib/llm_cost_tracker/ingestion/inline.rb +0 -22
  144. data/lib/llm_cost_tracker/parsers/openai_service_charges.rb +0 -126
  145. data/lib/llm_cost_tracker/usage_capture.rb +0 -58
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f17f618b28473afa871c9961a443a34152f4de81f7026d676d62b7e2bd1396d8
4
- data.tar.gz: d024b23f0ca6cd117afa5d10faa0a0b96374391a4741ea330b924b5091f665f7
3
+ metadata.gz: 547223e1b3bb49e347aee3b92c0734baf563e83c62c64a97356f7b0c0d175e85
4
+ data.tar.gz: 2d7601383de690b491b65a2aa5c33185dca6a6d7e9aefb53310eff7dc70102e6
5
5
  SHA512:
6
- metadata.gz: 0abf684c595b7bc84dfda26ffc62eaabc0c6d91d0b93f1065bf6e824c7867326b7978875d845d3df8be25bfa04ff9091150e0a4cac7f84d835ceaf2f1e2996bb
7
- data.tar.gz: 5b9405bf332b2e9e1eae05f0e7d107d4bb76ea71a6602846a16198540f3e0f315f48dbb316bbda24812d4d66c56ba837a42bfe81aeea502238f09e1f0202c6b4
6
+ metadata.gz: d88856b8451a27b706c28f153f8e4a68b7ec610a7a8052d6335481754c670785f2e87d970df913369f5c9d8de9a0716e11d7b60bc5f851ae2b617dcb386ecfd9
7
+ data.tar.gz: d31b56366d4550f7c8ec4786e8029d566dd8742591ddd3ae61def4a0ee571df98338219a7b0d154f413ee345df3b84b28f645f217a64765e20ad98483e87496d
data/CHANGELOG.md CHANGED
@@ -4,6 +4,61 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.11.0] - 2026-05-21
8
+
9
+ ### Added
10
+
11
+ - A "Pricing" page under the dashboard sidebar's new "Reference" group lists every model's rates from `pricing_overrides`, your `prices_file`, and the bundled fallback as separate tabs; the active source (first non-empty in priority order) is highlighted, with last-updated date and currency next to the row count.
12
+ - The Overview page now has Provider and Model filter pills next to Date, so cost slices can be scoped without leaving Overview.
13
+ - The Calls page now has a Stream filter pill for narrowing to streaming or non-streaming calls.
14
+
15
+ ### Changed
16
+
17
+ - Models, Calls, and Tags-breakdown tables sort by clicking a column header — ▲/▼ shows direction and clicking again reverses it. URLs use `?sort=<column>&dir=asc|desc`. The previous "Recent / Most expensive / Largest input / Slowest" sort buttons on Calls are gone; use `?sort=cost&dir=desc`, `?sort=latency&dir=desc`, etc. instead.
18
+ - `?sort=unknown_pricing` on `/calls` is replaced by the `?cost_status=incomplete` filter — the Data Quality "Incomplete pricing by model" panel's "Calls" button uses the new URL.
19
+ - The dashboard sidebar stays sticky while you scroll, with pages grouped into "Insights" (Overview / Models / Calls / Tags / Data Quality) and "Reference" (Pricing / Reconciliation if enabled).
20
+ - Dashboard pages use native `<details>` filter popovers instead of inline form rows. Only one popover stays open at a time, and Esc closes the open one.
21
+ - The Calls show page tucks the pricing snapshot and metadata JSON behind expandable `<details>` blocks; the redundant per-component "Tokens" and "Cost" lists are dropped — the Token mix / Cost mix bars already carry the breakdown.
22
+ - The Data Quality page groups stat cards under "Volume" and "Issues" headers, hides zero-count issue cards, and replaces the whole "Issues" block with a single "No data-quality issues in this slice" message when nothing is wrong.
23
+ - The daily spend chart text no longer stretches horizontally on wide screens.
24
+ - `bin/rails llm_cost_tracker:doctor` groups checks under Setup / Schema / Data integrity / Operations headers, renders each row with a `[✓]` / `[!]` / `[x]` status icon (green / yellow / red on a TTY), and aligns the columns so the message stays readable.
25
+ - The dashboard "Setup required" screen clears after `bin/rails db:migrate` without a Rails server restart, and the schema-drift details render as a monospaced block instead of indented bullets.
26
+
27
+ ### Fixed
28
+
29
+ - The `upgrade_call_rollups_provider`, `upgrade_provider_invoice_imports_provider`, and `upgrade_provider_invoices_metadata_index` migrations no-op when their target table doesn't exist (installs that never opted into `cache_rollups` or reconciliation) instead of crashing.
30
+ - `llm_cost_tracker:*` rake tasks ran their body twice on each invocation, so `doctor` and `report` printed every line twice and `prices:refresh` re-scraped on each run.
31
+
32
+ ## [0.10.0] - 2026-05-17
33
+
34
+ ### Added
35
+
36
+ - `budget_exceeded_behavior = :block_requests` now also blocks before send when an estimate of the call's input cost plus prior spend would cross the daily / monthly limit, or when the estimate alone crosses `per_call_budget`. `BudgetExceededError` and `on_budget_exceeded` payloads gain a `stage` field (`:pre_send` or `:post_spend`). Calls to models with no pricing match skip the pre-send check. See [Budgets](docs/budgets.md).
37
+ - `bin/rails llm_cost_tracker:backfill_unknown_pricing` rake task recomputes cost, pricing snapshot, line-item costs, and rollup buckets for calls that landed with no pricing (e.g. a new model recorded before the next scraper refresh added its rates). The Data Quality dashboard's "Unknown pricing by model" panel points at this task. Idempotent — re-running only touches calls still missing a cost.
38
+ - Azure OpenAI Service is captured out of the box across both Azure OpenAI (`*.openai.azure.com`) and Microsoft Foundry (`*.services.ai.azure.com`) hostnames, and on both the classic `/openai/deployments/{name}/{operation}` path and the v1 `/openai/v1/{operation}` path. OpenAI Ruby SDK calls made with either base URL also tag as `provider: "azure_openai"`. Pricing resolves to the matching `openai/<model>` entry; regional / Data Zone uplifts are configurable via `config.pricing_overrides` with the `azure_openai/<model>` prefix. See [Configuration → Azure OpenAI Service](docs/configuration.md#azure-openai-service).
39
+ - `bin/rails generate llm_cost_tracker:upgrade_provider_invoice_imports_provider` writes the migration so two reconciliation importers sharing a `source` (e.g. `csv/openai` and `csv/anthropic`) no longer cross-pollute resume cursors.
40
+ - `bin/rails generate llm_cost_tracker:upgrade_provider_invoices_metadata_index` writes a migration adding a GIN index on `llm_cost_tracker_provider_invoices.metadata` (PostgreSQL only) so reconciliation metadata lookups don't seq-scan on large invoice sets. No-op on MySQL.
41
+ - A `prices_file` with `metadata.currency: "EUR"` (or any non-USD code) now flows through to the call's `pricing_snapshot.currency`, the `call_rollups.currency` bucket, every `call_line_items.currency` row (token and service-charge alike), and the header `cost.currency` instead of being hardcoded to USD. The bundled price snapshot and `pricing_overrides` still default to USD; mixed-currency line items continue to drop from the header total with a warning.
42
+
43
+ ### Fixed
44
+
45
+ - OpenAI Chat Completions calls to the specialized search models (`gpt-4o-search-preview`, `gpt-4o-mini-search-preview`, `gpt-5-search-api`) record the per-call web-search fee as a line item at OpenAI's "Web search preview" rate (non-reasoning $25/1k, reasoning $10/1k). These models always search before responding, so the fee applies on every non-streaming call.
46
+ - Async ingestion writes through its own dedicated connection pool instead of competing with request-handling threads, so tracking an LLM call from inside a caller transaction no longer deadlocks under burst load and the inbox row still persists when the caller transaction rolls back. Tune via `config.ingestion_pool_size` if your PG / PgBouncer budget is tight.
47
+ - `mount LlmCostTracker::Engine => "/llm-costs"` works without adding `require "llm_cost_tracker/engine"` to `config/application.rb` — the engine is autoloaded.
48
+ - Upgrade migrations for the call_rollups and call_tags indexes build concurrently on PostgreSQL, so the upgrade no longer takes a long table-write lock on installs with millions of rows. The rollups upgrade keeps pre-upgrade aggregates (bucketed under empty provider) instead of wiping them.
49
+ - CSV exports stream in 500-row batches so peak memory stays flat at large export sizes while the user-selected sort order is preserved (`?sort=expensive`, `?sort=slow`, etc.).
50
+ - `bin/rails generate llm_cost_tracker:install` skips `config/initializers/llm_cost_tracker.rb` when it already exists instead of prompting Thor's "overwrite?" dialog, so re-running the generator in CI no longer hangs waiting on stdin.
51
+ - Async inbox entries no longer truncate on MySQL — large pricing snapshots and stack traces stay intact (MEDIUMTEXT instead of TEXT cap at 64 KB). PostgreSQL is unaffected.
52
+ - Logs no longer warn `unknown pricing for model X` when the call has no token pricing but at least one service charge was successfully priced. Misleading false-positive is gone for Anthropic web-search-style calls on models missing from the token-pricing table.
53
+ - `llm_cost_tracker:prune` rake task also prunes async inbox entries and finished provider invoice imports past the `DAYS` cutoff, so pending or quarantined rows no longer flush retroactively and old import-cursor rows don't linger forever.
54
+ - Azure OpenAI deployments using `audio/speech`, `images/edits`, `images/variations`, `moderations`, or `responses` endpoints are now captured by the Faraday parser; previously only `chat/completions`, `completions`, `embeddings`, `audio/transcriptions`, `audio/translations`, and `images/generations` matched.
55
+ - `prices:refresh` drops service-charge keys when a scraper stops emitting them, so stale charges no longer linger in the local price snapshot. Scrapers that don't parse a charges section (groq, gemini) preserve existing entries.
56
+
57
+ ### Changed
58
+
59
+ - Schema: `llm_cost_tracker_provider_invoice_imports` gains a `provider` column and replaces the `(source, started_at)` index with `(source, provider, started_at)`. Existing installs run `bin/rails generate llm_cost_tracker:upgrade_provider_invoice_imports_provider && bin/rails db:migrate`.
60
+ - BREAKING: `config.durable_ingestion = true/false` is replaced by `config.ingestion = :inline | :async` (default `:inline`). `config.durable_ingestion_pool_size` is renamed to `config.ingestion_pool_size`, and the install generator is now `bin/rails generate llm_cost_tracker:async_ingestion`. Update `config/initializers/llm_cost_tracker.rb` accordingly.
61
+
7
62
  ## [0.9.0] - 2026-05-12
8
63
 
9
64
  0.9 leans the default install: only `calls`, `call_line_items`, and `call_tags`
data/README.md CHANGED
@@ -15,7 +15,10 @@ attribution only.
15
15
 
16
16
  Requires Ruby 3.4+, Rails 7.1+, PostgreSQL or MySQL.
17
17
 
18
- ![Dashboard overview](docs/dashboard-overview.png)
18
+ <picture>
19
+ <source media="(prefers-color-scheme: dark)" srcset="docs/dashboard-overview-dark.png">
20
+ <img alt="LLM Cost Tracker dashboard" src="docs/dashboard-overview-light.png">
21
+ </picture>
19
22
 
20
23
  ## Quickstart
21
24
 
@@ -39,7 +42,7 @@ LlmCostTracker.configure do |config|
39
42
  end
40
43
  ```
41
44
 
42
- Tag your calls that's how you find out who burned the money:
45
+ Tag your calls to attribute spend:
43
46
 
44
47
  ```ruby
45
48
  LlmCostTracker.with_tags(user_id: Current.user&.id, feature: "chat") do
@@ -74,6 +77,7 @@ The engine ships without authentication on purpose.
74
77
  | --- | --- |
75
78
  | OpenAI | Official SDK or Faraday |
76
79
  | Anthropic | Official SDK or Faraday |
80
+ | Azure OpenAI | Faraday or official SDK (auto-detected on `*.openai.azure.com` and Foundry `*.services.ai.azure.com`, both deployments and `/openai/v1/...`) |
77
81
  | Google Gemini | Faraday |
78
82
  | RubyLLM | Provider layer |
79
83
  | `ruby-openai` | Faraday |