Accounting for LLM token usage
Project description
datasette-llm-accountant
Budget management and cost tracking for LLM usage in Datasette.
Installation
Install this plugin in the same environment as Datasette:
datasette install datasette-llm-accountant
This plugin works alongside datasette-llm to provide automatic cost tracking and budget enforcement for LLM prompts.
Overview
This plugin provides:
- Automatic cost calculation based on token usage and model pricing
- Reserve/settle pattern for budget enforcement
- Accountant plugin system for custom spending trackers
- Hook integration with datasette-llm for transparent accounting
When installed, all prompts made through datasette-llm are automatically wrapped with accounting logic. Accountants can enforce spending limits, log usage, and track costs.
How It Works
- When a prompt is made via
datasette-llm, this plugin's hooks intercept the call - A reservation is made with all registered accountants for the estimated cost
- The prompt executes
- The actual cost is calculated from token usage
- Accountants are settled with the real cost (refunding any unused reservation)
Configuration
Configure reservation amounts in datasette.yaml:
plugins:
datasette-llm-accountant:
# Default reservation for single prompts
auto_reservation_usd: 0.10
# Default reservation for grouped prompts
default_reservation_usd: 0.50
# Purpose-specific reservations
purposes:
enrichments:
reservation_usd: 5.00
query-assistant:
reservation_usd: 0.25
Creating an Accountant Plugin
Accountants track and enforce LLM spending. Create a plugin that implements the register_llm_accountants hook:
from datasette import hookimpl
from datasette_llm_accountant import Accountant, Tx, InsufficientBalanceError
class MyAccountant(Accountant):
"""Custom accountant that tracks spending."""
def __init__(self, datasette):
self.datasette = datasette
async def reserve(
self,
nanocents: int,
model_id: str = None,
purpose: str = None,
) -> Tx:
"""
Reserve the specified amount.
Args:
nanocents: Amount to reserve (1 USD = 100,000,000,000 nanocents)
model_id: The model being used (e.g., "gpt-4o-mini")
purpose: The purpose of the request (e.g., "enrichments")
Returns:
A transaction ID for settlement
Raises:
InsufficientBalanceError: If reservation cannot be made
"""
# Check balance, create reservation, return transaction ID
if not await self.has_sufficient_balance(nanocents):
raise InsufficientBalanceError("Insufficient balance")
tx_id = await self.create_reservation(nanocents, model_id, purpose)
return Tx(tx_id)
async def settle(
self,
tx: Tx,
nanocents: int,
model_id: str = None,
purpose: str = None,
):
"""
Settle a transaction for the actual amount spent.
Args:
tx: Transaction ID from reserve()
nanocents: Actual amount spent
model_id: The model that was used
purpose: The purpose of the request
"""
await self.record_settlement(tx, nanocents, model_id, purpose)
async def rollback(self, tx: Tx):
"""Optional: Release a reservation without charging."""
await self.settle(tx, 0)
@hookimpl
def register_llm_accountants(datasette):
return [MyAccountant(datasette)]
See datasette-llm-allowance for a complete implementation that uses Datasette's internal database to track a spending allowance.
Multiple Accountants
Multiple accountants can be registered. When a reservation is made:
- All accountants are called in sequence to reserve the amount
- If any accountant fails (e.g.,
InsufficientBalanceError), previous reservations are rolled back - When the prompt completes, all accountants are settled with the actual cost
This enables layered accounting (per-user limits, per-project budgets, global caps, etc.).
Cost Calculation
Costs are calculated using pricing data from llm-prices.com:
from datasette_llm_accountant import calculate_cost_nanocents
cost = calculate_cost_nanocents(
model_id="gpt-4o-mini",
input_tokens=1000,
output_tokens=500,
cached_input_tokens=200, # Optional
)
# Returns cost in nanocents
Pricing Utilities
from datasette_llm_accountant import (
usd_to_nanocents,
nanocents_to_usd,
get_model_pricing,
)
# Convert between USD and nanocents
nanocents = usd_to_nanocents(1.50) # 150,000,000,000
usd = nanocents_to_usd(150_000_000_000) # 1.5
# Get pricing for a model
pricing = get_model_pricing("gpt-4o-mini")
# Returns: {"input": 0.15, "output": 0.6, "cached_input": 0.075}
# Prices are per million tokens
API Reference
Accountant Base Class
class Accountant(ABC):
@abstractmethod
async def reserve(
self,
nanocents: int,
model_id: str = None,
purpose: str = None,
) -> Tx:
"""Reserve an amount, return transaction ID."""
pass
@abstractmethod
async def settle(
self,
tx: Tx,
nanocents: int,
model_id: str = None,
purpose: str = None,
):
"""Settle a transaction for the actual amount."""
pass
async def rollback(self, tx: Tx):
"""Release a reservation (default: settle for 0)."""
await self.settle(tx, 0)
Exceptions
InsufficientBalanceError- Raised when an accountant cannot reserve the requested amountReservationExceededError- Raised when actual cost exceeds the reserved amountModelPricingNotFoundError- Raised when pricing data is not available for a model
Nanocents
All amounts use nanocents for precision:
- 1 nanocent = 1/1,000,000,000 of a cent
- 1 USD = 100 cents = 100,000,000,000 nanocents
- This allows tracking costs down to fractions of a cent without floating-point errors
Development
cd datasette-llm-accountant
python -m venv venv
source venv/bin/activate
pip install -e '.[test]'
python -m pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file datasette_llm_accountant-0.1a2.tar.gz.
File metadata
- Download URL: datasette_llm_accountant-0.1a2.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
109e0526a8d2e1fa8c08343b55e772e7e069d7c4e3de179e7eb915e43ceb1d11
|
|
| MD5 |
c8635e0edead98da11a7734be1bb02a0
|
|
| BLAKE2b-256 |
8a4305b872e1e03b4f441a49b7338350218baeadca1b6ff212615174de256ecf
|
Provenance
The following attestation bundles were made for datasette_llm_accountant-0.1a2.tar.gz:
Publisher:
publish.yml on datasette/datasette-llm-accountant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
datasette_llm_accountant-0.1a2.tar.gz -
Subject digest:
109e0526a8d2e1fa8c08343b55e772e7e069d7c4e3de179e7eb915e43ceb1d11 - Sigstore transparency entry: 1139033543
- Sigstore integration time:
-
Permalink:
datasette/datasette-llm-accountant@7571eb08f3bd1ca63e8a6c7b49668d2f49ddeda4 -
Branch / Tag:
refs/tags/0.1a2 - Owner: https://github.com/datasette
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7571eb08f3bd1ca63e8a6c7b49668d2f49ddeda4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file datasette_llm_accountant-0.1a2-py3-none-any.whl.
File metadata
- Download URL: datasette_llm_accountant-0.1a2-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d7675b49e60c5455134867a81991b4458ed886df87b9f317a78c7664822985b
|
|
| MD5 |
51799ae1e791d34661448e9c046a78f6
|
|
| BLAKE2b-256 |
f873c713d72157d8ecd87276fbfb2645611ba84342ec21402f2d73ae2c8317d8
|
Provenance
The following attestation bundles were made for datasette_llm_accountant-0.1a2-py3-none-any.whl:
Publisher:
publish.yml on datasette/datasette-llm-accountant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
datasette_llm_accountant-0.1a2-py3-none-any.whl -
Subject digest:
1d7675b49e60c5455134867a81991b4458ed886df87b9f317a78c7664822985b - Sigstore transparency entry: 1139033636
- Sigstore integration time:
-
Permalink:
datasette/datasette-llm-accountant@7571eb08f3bd1ca63e8a6c7b49668d2f49ddeda4 -
Branch / Tag:
refs/tags/0.1a2 - Owner: https://github.com/datasette
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7571eb08f3bd1ca63e8a6c7b49668d2f49ddeda4 -
Trigger Event:
release
-
Statement type: