跳到正文
GitHub·· 8 天前AI 评分30

Rails 的 LLM 成本追踪工具 llm_cost_tracker:按用户、功能或标签记录花费

sergey-homenko/llm_cost_tracker

AI 导读

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.

Gem Version CI codecov

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.

LLM Cost Tracker dashboard

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 track or track_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

Development

bundle install
bin/check

License

MIT. See LICENSE.txt.

来源:GitHub · github.com