Skip to main content

Accounting for LLM token usage

Project description

datasette-llm-accountant

PyPI Changelog Tests License

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

  1. When a prompt is made via datasette-llm, this plugin's hooks intercept the call
  2. A reservation is made with all registered accountants for the estimated cost
  3. The prompt executes
  4. The actual cost is calculated from token usage
  5. 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:

  1. All accountants are called in sequence to reserve the amount
  2. If any accountant fails (e.g., InsufficientBalanceError), previous reservations are rolled back
  3. 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 amount
  • ReservationExceededError - Raised when actual cost exceeds the reserved amount
  • ModelPricingNotFoundError - 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

datasette_llm_accountant-0.1a2.tar.gz (18.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

datasette_llm_accountant-0.1a2-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

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

Hashes for datasette_llm_accountant-0.1a2.tar.gz
Algorithm Hash digest
SHA256 109e0526a8d2e1fa8c08343b55e772e7e069d7c4e3de179e7eb915e43ceb1d11
MD5 c8635e0edead98da11a7734be1bb02a0
BLAKE2b-256 8a4305b872e1e03b4f441a49b7338350218baeadca1b6ff212615174de256ecf

See more details on using hashes here.

Provenance

The following attestation bundles were made for datasette_llm_accountant-0.1a2.tar.gz:

Publisher: publish.yml on datasette/datasette-llm-accountant

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file datasette_llm_accountant-0.1a2-py3-none-any.whl.

File metadata

File hashes

Hashes for datasette_llm_accountant-0.1a2-py3-none-any.whl
Algorithm Hash digest
SHA256 1d7675b49e60c5455134867a81991b4458ed886df87b9f317a78c7664822985b
MD5 51799ae1e791d34661448e9c046a78f6
BLAKE2b-256 f873c713d72157d8ecd87276fbfb2645611ba84342ec21402f2d73ae2c8317d8

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page