boriskwemo / llm-ledger
Framework-agnostic PHP package to call and track costs of LLMs (OpenAI, Anthropic, Google, Mistral, DeepSeek, Qwen and more) with a unified API, a bundled model registry, and Symfony 8 / Laravel integrations.
Requires
- php: >=8.2
- ext-json: *
- ext-pdo: *
- nyholm/psr7: ^1.8
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- symfony/http-client: ^6.4 || ^7.0 || ^8.0
- symfony/yaml: ^6.4 || ^7.0 || ^8.0
Requires (Dev)
- phpunit/phpunit: ^10.5 || ^11.0
Suggests
- illuminate/database: Required to persist usage via Eloquent in Laravel.
- illuminate/support: Required to use the Laravel integration.
- symfony/framework-bundle: Required to use the Symfony bundle integration (with symfony/twig-bundle and twig/twig for the dashboard).
- twig/twig: Renders the Symfony dashboard templates.
README
Track and control LLM spend from one PHP API. Call OpenAI, Anthropic, Google Gemini, Mistral, DeepSeek, Qwen, Kimi, GLM, Grok and any OpenAI-compatible endpoint, with automatic cost and usage tracking baked in.
use LlmCostTracker\Client\Llm; $llm = Llm::fromYaml('llm.yaml'); $result = $llm->chat('deepseek-chat', 'Explain quantum computing in one sentence.'); echo $result->text(); // the answer echo $result->usage->totalTokens; // 42 echo $result->usage->reasoningTokens; // hidden thinking tokens, exposed echo $result->cost->total; // 0.000123 USD
Every call is recorded and shown in a dashboard: cost per model, provider and day, plus thinking tokens and response previews.
Install
composer require boriskwemo/llm-ledger
Requires PHP 8.2+ with ext-json and ext-pdo.
Configure
Create llm.yaml:
providers: openai: { api_key: env(OPENAI_API_KEY) } deepseek: { api_key: env(DEEPSEEK_API_KEY) } # ...anthropic, google, mistral, qwen, xai and more tracking: enabled: true storage: sqlite://llm.db
That's it. Drop in your keys and go.
Why LLM Ledger?
- One API for 19 providers - OpenAI, Anthropic, Google, xAI, Mistral, DeepSeek, Qwen, Kimi, GLM and any OpenAI-compatible server (vLLM, LiteLLM, local).
- Costs you can't miss - reasoning tokens, cached input and per-call USD are normalized and stored, not just the end-of-month bill.
- Built-in dashboard + CLI - a themed admin UI (light/dark, fully responsive) plus
llm-trackerfor the terminal. - Deprecation-aware model registry - a YAML catalog marks retired models and their replacements before they break you.
- Batch processing - one
batch()call across OpenAI, Anthropic and Gemini. - Symfony 8 + Laravel - an idiomatic bundle and service provider, not a raw HTTP client.
- Secure by default - API keys live in memory only; never persisted or logged.
Quickstart
require 'vendor/autoload.php'; use LlmCostTracker\Client\Llm; $llm = Llm::fromYaml('llm.yaml'); $result = $llm->chat('gpt-5.2', 'Hello!', ['tags' => ['demo']]); echo $result->text(); printf("cost: $%.6f\n", $result->cost->total); $totals = $llm->tracker()->totals(); // lifetime aggregates
Symfony 8
// config/bundles.php LlmCostTracker\Symfony\LlmCostTrackerBundle::class => ['all' => true],
# config/routes/llm_cost_tracker.yaml llm_cost_tracker: resource: '@LlmCostTrackerBundle/Resources/config/routes.yaml' prefix: /llm
public function __construct(private \LlmCostTracker\Client\Llm $llm) {} $answer = $this->llm->complete('mistral-large-latest', 'Hello!');
Dashboard at /llm, model catalog at /llm/models.
Laravel
php artisan vendor:publish --provider="LlmCostTracker\Laravel\LlmCostTrackerServiceProvider"
php artisan migrate
use Llm; $answer = Llm::complete('qwen3-max', 'Hello!');
Dashboard at /llm.
CLI
vendor/bin/llm-tracker models:list
vendor/bin/llm-tracker usage:report --days=30
vendor/bin/llm-tracker call deepseek-chat "Hello!"
More
- Model registry:
config/models.yaml. Pricing is a bundled snapshot; override it in your own YAML. - Standalone dashboard:
examples/dashboard.php(no framework required).
License
MIT. See LICENSE. Contributions welcome - CONTRIBUTING.md; report vulnerabilities per SECURITY.md.