Rails 的 LLM 成本追踪工具 llm_cost_tracker:按用户、功能或标签记录花费
sergey-homenko/llm_cost_tracker
llm_cost_tracker 是一个面向 Rails 单体的 LLM 花费追踪与预算工具,按用户、功能或任意标签归因成本,数据存在自有数据库中,无需代理,也不记录提示词、trace 或回放。
LLM Cost Tracker
LLM spend tracking and budgets for Rails: by user, feature, or any tag, in your own database, no proxy.
No prompts, no traces, no replay. Spend attribution only.
Requires Ruby 3.3+, Rails 8.0+, PostgreSQL or MySQL. Built for a Rails monolith, not multiple services.
Quickstart
Shown with RubyLLM; the flow is identical for the official OpenAI and Anthropic SDKs: swap the gem and the instrument name (see the cookbook).
# Gemfile gem "llm_cost_tracker" gem "ruby_llm"
bin/rails llm_cost_tracker:setup
Runs the install generator, writes a local price file, migrates the database, and verifies via llm_cost_tracker:doctor. Then enable the integration in the generated config/initializers/llm_cost_tracker.rb:
LlmCostTracker.configure do |config| config.tags.default = -> { { environment: Rails.env } } config.instrument :ruby_llm end
Your RubyLLM calls stay unchanged, and every chat, embedding, transcription, image, and moderation call now lands in the ledger. Tag them to attribute spend:
LlmCostTracker.with_tags(user_id: Current.user&.id, feature: "chat") do RubyLLM.chat.ask("Hello") end
Mount the dashboard in config/routes.rb. The engine ships without authentication, so put it behind yours:
authenticate :admin do mount LlmCostTracker::Engine => "/llm-costs" end
What it records
- Calls. Provider, model, total tokens, total cost, latency, status.
- Line items. Per-component breakdown: text/audio/cached tokens, tool charges (web search, grounding, container sessions).
- Tags. Whatever attribution you pass: user, feature, tenant, env.
- Provider IDs. Response, plus project, API key and workspace when you pass them to
trackortrack_stream, for downstream audits. - Pricing snapshot. So historical numbers don't drift when prices change.
Daily, monthly, and per-call limits, plus per-tag limits such as one monthly budget per tenant_id, or a lifetime cap in dollars or calls per agent run (run_id, which RubyLLM.workflow sets on its calls). A crossed limit calls your on_exceeded hook, raises, or blocks the next call before it is sent. See Budgets.
Supported clients
| Client | Captured through |
|---|---|
| RubyLLM | Instrumentation events (2.x) or the provider layer (1.x) |
| OpenAI, Anthropic | Official SDK or Faraday |
| Azure OpenAI | Official SDK or Faraday, on *.openai.azure.com, and Foundry *.services.ai.azure.com or *.cognitiveservices.azure.com |
Google Gemini, ruby-openai |
Faraday |
| OpenRouter, DeepSeek, Groq, xAI, Mistral, Perplexity | Faraday, or the official OpenAI SDK with base_url on that host |
| Other OpenAI-compatible gateways | The same, once the host is added to config.capture.openai_compatible_providers |
| Anything else | LlmCostTracker.track |
Streams are recorded from the provider's final usage; see Streaming.
Pricing
Captured does not always mean priced:
| Cost comes from | Calls |
|---|---|
The billed amount in the response or final stream chunk: usage.cost, xAI's usage.cost_in_usd_ticks, or Perplexity's usage.cost.total_cost |
OpenRouter, xAI, Perplexity, and other OpenAI-compatible gateways that return one (through RubyLLM 1.x, OpenRouter, xAI, and Perplexity chats only) |
Bundled prices.json, read from each provider's own pricing page or API |
The OpenAI, Anthropic, Gemini, Groq, OpenRouter, xAI, Mistral, DeepSeek, and Cohere models it lists, DeepSeek at its off-peak rates in its off-peak hours, and the same Claude models on Bedrock through RubyLLM |
| The OpenAI, Anthropic, or Gemini price for the same model name | Azure OpenAI (by the model in the response, not the deployment name), Vertex AI through RubyLLM, gateways that pass a listed model name through |
Nothing: recorded with cost_status: unknown |
Perplexity without a billed amount (its Router) when no price lists its model, calls through the official OpenAI SDK's Bedrock provider, and through RubyLLM also Ollama, Cohere models without a bundled price (such as Command A+, Embed 4, and Rerank 3.5), other Bedrock models, and Claude on GovCloud (us-gov. profiles) |
Add missing prices to config.pricing.file or config.pricing.overrides (Pricing), then run bin/rails llm_cost_tracker:backfill_unknown_pricing to price the calls already recorded. bin/rails llm_cost_tracker:prices:refresh runs it after writing a new config.pricing.file if the database is reachable.
Manual tracking
For batch jobs, internal gateways, or anything without an SDK or Faraday hook:
LlmCostTracker.track( provider: :anthropic, model: "claude-sonnet-4-6", tokens: { input_tokens: 1500, output_tokens: 320 }, tags: { feature: "summarizer", user_id: current_user.id } )
Docs
- Configuration
- Pricing
- Budgets
- Data model
- Querying
- Dashboard
- Streaming
- Cookbook
- Extending
- Operations
- Architecture
- EU AI Act record-keeping
- Upgrading
- Changelog
Development
bundle install bin/check
License
MIT. See LICENSE.txt.
来源:GitHub · github.com