# llm-cost
**Plug-and-play LLM token/cost tracking SDK** with multiple sinks (SQLite, Postgres, Supabase, HTTP collector) and comprehensive audit metadata.
## Features
- 🎯 **Decorator-first DX**: `@track_cost` for non-streaming, `finalize_llm_call` for streaming
- 🔒 **Multi-tenant safe**: Idempotent upserts scoped to `workspace_id` or `project_id`
- 📊 **Audit-ready**: Every row includes `usage_raw` and `rates_used` for provable cost recomputation
- 🚀 **Non-blocking**: Background batcher with bounded queue and outbox fallback
- 💰 **Dynamic pricing**: Fetch live rates from OpenRouter with local cache
- 🔌 **Pluggable sinks**: SQLite (default), Postgres/Supabase, HTTP collector
- 🛡️ **Privacy by default**: No prompt/response content captured
## Quick Start
```python
import llm_cost as cost
# Initialize with Supabase (or SQLite, Postgres, HTTP)
cost.init_supabase(
supabase_url="https://your-project.supabase.co",
supabase_key="your-service-role-key",
)
# Set sticky context (workspace, session, user)
cost.set_context({
"workspace_id": "ws-123",
"session_id": "sess-456",
"user_id": "user-789",
})
# Track non-streaming calls
from openai import OpenAI
client = OpenAI()
@cost.track_cost(model_arg='model', provider='openai')
def run_completion(model: str, prompt: str):
return client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
response = run_completion(model="gpt-4o", prompt="Hello!")
# Track streaming calls
@cost.track_cost(mode='defer')
def run_streaming(model: str, prompt: str):
return client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
stream=True,
)
stream = run_streaming(model="gpt-4o", prompt="Hello!")
tokens_in, tokens_out = 0, 0
for chunk in stream:
# ... process chunk
pass
# Finalize with actual token counts
cost.finalize_llm_call(
provider="openai",
model="gpt-4o",
tokens_in=tokens_in,
tokens_out=tokens_out,
request_id=cost.new_request_id(),
)
```
## Configuration
All config can be set via environment variables or passed to `init()`:
```bash
# Supabase mode
export SUPABASE_URL=https://your-project.supabase.co
export SUPABASE_SERVICE_ROLE_KEY=your-key
# SQLite mode (default)
export COST_SINK_DSN=sqlite:///./llm_cost.db
# HTTP collector mode
export COST_COLLECTOR_ENDPOINT=https://your-collector.com/v1/batch
export COST_WRITE_KEY=your-write-key
# Flush behavior
export COST_FLUSH_AT=20
export COST_FLUSH_INTERVAL_MS=3000
```
## Audit Metadata
Every ledger row includes:
```json
{
"context": {
"metadata": {
"billing": {
"usage_raw": {
"prompt_tokens": 123,
"completion_tokens": 456,
"reasoning_tokens": 100,
"cached_input_tokens": 50
},
"rates_used": {
"input_rate": 1.25,
"cached_input_rate": 0.125,
"output_rate": 10.0,
"reasoning_rate": 10.0,
"model_resolved": "gpt-4o",
"pricing_source": "default",
"pricing_version": "abc123"
}
}
}
}
}
```
This enables:
- Row-by-row cost recomputation
- Audit trails for billing disputes
- Reconciliation jobs to detect drift
## Installation
```bash
pip install llm-cost
```
## License
MIT
## Links
- [GitHub](https://github.com/orchestra-ai/llm-cost)
- [Documentation](https://github.com/orchestra-ai/llm-cost)
- [PyPI](https://pypi.org/project/llm-cost/)
Raw data
{
"_id": null,
"home_page": null,
"name": "orchestra-llm-cost",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "llm, pricing, tokens, billing, supabase, openai, cost-tracking",
"author": null,
"author_email": "Orchestra <team@orchestra.space>",
"download_url": "https://files.pythonhosted.org/packages/23/d7/ca8de79da2958706344293668216b4dbea05864dee3876f4446a62299281/orchestra_llm_cost-0.1.4.tar.gz",
"platform": null,
"description": "# llm-cost\n\n**Plug-and-play LLM token/cost tracking SDK** with multiple sinks (SQLite, Postgres, Supabase, HTTP collector) and comprehensive audit metadata.\n\n## Features\n\n- \ud83c\udfaf **Decorator-first DX**: `@track_cost` for non-streaming, `finalize_llm_call` for streaming\n- \ud83d\udd12 **Multi-tenant safe**: Idempotent upserts scoped to `workspace_id` or `project_id`\n- \ud83d\udcca **Audit-ready**: Every row includes `usage_raw` and `rates_used` for provable cost recomputation\n- \ud83d\ude80 **Non-blocking**: Background batcher with bounded queue and outbox fallback\n- \ud83d\udcb0 **Dynamic pricing**: Fetch live rates from OpenRouter with local cache\n- \ud83d\udd0c **Pluggable sinks**: SQLite (default), Postgres/Supabase, HTTP collector\n- \ud83d\udee1\ufe0f **Privacy by default**: No prompt/response content captured\n\n## Quick Start\n\n```python\nimport llm_cost as cost\n\n# Initialize with Supabase (or SQLite, Postgres, HTTP)\ncost.init_supabase(\n supabase_url=\"https://your-project.supabase.co\",\n supabase_key=\"your-service-role-key\",\n)\n\n# Set sticky context (workspace, session, user)\ncost.set_context({\n \"workspace_id\": \"ws-123\",\n \"session_id\": \"sess-456\",\n \"user_id\": \"user-789\",\n})\n\n# Track non-streaming calls\nfrom openai import OpenAI\nclient = OpenAI()\n\n@cost.track_cost(model_arg='model', provider='openai')\ndef run_completion(model: str, prompt: str):\n return client.chat.completions.create(\n model=model,\n messages=[{\"role\": \"user\", \"content\": prompt}],\n )\n\nresponse = run_completion(model=\"gpt-4o\", prompt=\"Hello!\")\n\n# Track streaming calls\n@cost.track_cost(mode='defer')\ndef run_streaming(model: str, prompt: str):\n return client.chat.completions.create(\n model=model,\n messages=[{\"role\": \"user\", \"content\": prompt}],\n stream=True,\n )\n\nstream = run_streaming(model=\"gpt-4o\", prompt=\"Hello!\")\ntokens_in, tokens_out = 0, 0\nfor chunk in stream:\n # ... process chunk\n pass\n\n# Finalize with actual token counts\ncost.finalize_llm_call(\n provider=\"openai\",\n model=\"gpt-4o\",\n tokens_in=tokens_in,\n tokens_out=tokens_out,\n request_id=cost.new_request_id(),\n)\n```\n\n## Configuration\n\nAll config can be set via environment variables or passed to `init()`:\n\n```bash\n# Supabase mode\nexport SUPABASE_URL=https://your-project.supabase.co\nexport SUPABASE_SERVICE_ROLE_KEY=your-key\n\n# SQLite mode (default)\nexport COST_SINK_DSN=sqlite:///./llm_cost.db\n\n# HTTP collector mode\nexport COST_COLLECTOR_ENDPOINT=https://your-collector.com/v1/batch\nexport COST_WRITE_KEY=your-write-key\n\n# Flush behavior\nexport COST_FLUSH_AT=20\nexport COST_FLUSH_INTERVAL_MS=3000\n```\n\n## Audit Metadata\n\nEvery ledger row includes:\n\n```json\n{\n \"context\": {\n \"metadata\": {\n \"billing\": {\n \"usage_raw\": {\n \"prompt_tokens\": 123,\n \"completion_tokens\": 456,\n \"reasoning_tokens\": 100,\n \"cached_input_tokens\": 50\n },\n \"rates_used\": {\n \"input_rate\": 1.25,\n \"cached_input_rate\": 0.125,\n \"output_rate\": 10.0,\n \"reasoning_rate\": 10.0,\n \"model_resolved\": \"gpt-4o\",\n \"pricing_source\": \"default\",\n \"pricing_version\": \"abc123\"\n }\n }\n }\n }\n}\n```\n\nThis enables:\n- Row-by-row cost recomputation\n- Audit trails for billing disputes\n- Reconciliation jobs to detect drift\n\n## Installation\n\n```bash\npip install llm-cost\n```\n\n## License\n\nMIT\n\n## Links\n\n- [GitHub](https://github.com/orchestra-ai/llm-cost)\n- [Documentation](https://github.com/orchestra-ai/llm-cost)\n- [PyPI](https://pypi.org/project/llm-cost/)\n",
"bugtrack_url": null,
"license": null,
"summary": "Plug-and-play LLM token/cost tracking SDK with multiple sinks and audit metadata",
"version": "0.1.4",
"project_urls": {
"Homepage": "https://github.com/orchestra-ai/llm-cost",
"Repository": "https://github.com/orchestra-ai/llm-cost"
},
"split_keywords": [
"llm",
" pricing",
" tokens",
" billing",
" supabase",
" openai",
" cost-tracking"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "b7d8db1fcef1f80ee88e619981ea0b3077ff7bb8e169d4f2aa21029c91192582",
"md5": "34a2fb16d0cfece1117161c69603fe10",
"sha256": "f982fe6d16fb5b7d9ec08b04d8e4d499b5f8b1874c2fe6662fe25f2ffd9f7e32"
},
"downloads": -1,
"filename": "orchestra_llm_cost-0.1.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "34a2fb16d0cfece1117161c69603fe10",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 18909,
"upload_time": "2025-10-08T02:04:16",
"upload_time_iso_8601": "2025-10-08T02:04:16.563110Z",
"url": "https://files.pythonhosted.org/packages/b7/d8/db1fcef1f80ee88e619981ea0b3077ff7bb8e169d4f2aa21029c91192582/orchestra_llm_cost-0.1.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "23d7ca8de79da2958706344293668216b4dbea05864dee3876f4446a62299281",
"md5": "e958c1b862919f7741758f8fb4d4c36e",
"sha256": "dae8c96bb3d916a5890e0ddc5055128efca2303b34fcfe5fa7b00fb28752aa02"
},
"downloads": -1,
"filename": "orchestra_llm_cost-0.1.4.tar.gz",
"has_sig": false,
"md5_digest": "e958c1b862919f7741758f8fb4d4c36e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 17840,
"upload_time": "2025-10-08T02:04:17",
"upload_time_iso_8601": "2025-10-08T02:04:17.600926Z",
"url": "https://files.pythonhosted.org/packages/23/d7/ca8de79da2958706344293668216b4dbea05864dee3876f4446a62299281/orchestra_llm_cost-0.1.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-08 02:04:17",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "orchestra-ai",
"github_project": "llm-cost",
"github_not_found": true,
"lcname": "orchestra-llm-cost"
}