Skip to main content

llmbelt 🧰

A tiny, zero-dependency tool belt for working with LLMs. The small utilities you end up re-writing on every project — token counting, cost estimation, retries, prompt templates, and text chunking — in one clean import.

CI PyPI Python License: MIT

  • 🪶 Zero required dependencies — pure standard library.
  • 🔌 Provider-agnostic — works with Anthropic, OpenAI, Gemini, or anything else.
  • 🧪 Fully tested across Python 3.9–3.12.

Install

pip install llmbelt

# Optional: exact token counts for OpenAI-family models
pip install "llmbelt[tiktoken]"

Usage

Count tokens

from llmbelt import count_tokens, truncate_to_tokens

count_tokens("Hello, world!")              # exact if tiktoken installed, else estimated
count_tokens("Hello", model="gpt-4o")      # use a model-specific encoding

# Trim text to fit a budget (great before sending context to an API)
truncate_to_tokens(long_document, max_tokens=4000)

Estimate cost

from llmbelt import estimate_cost, Price

estimate_cost(input_tokens=1_500, output_tokens=800, model="gpt-4o-mini")   # -> USD

# Bring your own prices (the built-in table is approximate — always verify):
my_prices = {"my-model": Price(input_per_1m=2.0, output_per_1m=6.0)}
estimate_cost(1000, 500, "my-model", pricing=my_prices)

Retry with backoff

from llmbelt import retry

@retry(attempts=5, exceptions=(ConnectionError, TimeoutError))
def call_api():
    ...   # retried with exponential backoff + jitter on failure

# Works on async functions too — awaited, with non-blocking asyncio.sleep backoff
@retry(attempts=5, exceptions=(ConnectionError, TimeoutError))
async def call_api_async():
    ...

Extract JSON from a model reply

from llmbelt import extract_json

extract_json('Sure!\n```json\n{"ok": true}\n```')   # -> {"ok": True}
extract_json('The score is {"value": 0.9}.')         # -> {"value": 0.9}
extract_json("no json here", default=None)           # -> None (else raises ValueError)

Prompt templates

from llmbelt import PromptTemplate

t = PromptTemplate("Translate {text} into {language}.")
t.render(text="hello", language="French")   # "Translate hello into French."
t.render(text="hello")                      # KeyError: Missing template variables: ['language']

Fit a conversation into the context window

from llmbelt import count_message_tokens, trim_messages

messages = [
    {"role": "system", "content": "You are concise."},
    {"role": "user", "content": "..."},
    # ... a long history ...
]

count_message_tokens(messages)                      # total tokens of the chat
trim_messages(messages, max_tokens=8000)            # drop oldest turns, keep the system prompt

Cache calls so you don't pay twice

from llmbelt import cached

@cached(ttl=3600)            # remember results for an hour; unhashable args are fine
def ask(prompt: str):
    ...                      # identical prompt -> served from cache, no API call

# works on async functions too
@cached()
async def ask_async(prompt): ...

Stay under rate limits

from llmbelt import RateLimiter

limiter = RateLimiter(rate=60, per=60)   # 60 requests per minute

@limiter                                  # decorator
def call_api(): ...

with limiter:                             # or a context manager
    call_api()

Chunk text for RAG

from llmbelt import chunk_text, chunk_by_tokens, split_text

chunks = chunk_text(document, chunk_size=1000, overlap=100)
# overlapping chunks so answers aren't split across a boundary

# Budget by tokens instead of characters (exact with tiktoken installed):
chunks = chunk_by_tokens(document, chunk_size=500, overlap=50)

# Smarter: break on paragraph/sentence/word boundaries instead of mid-word
chunks = split_text(document, chunk_size=1000, overlap=100)

Track spend across calls

from llmbelt import CostTracker

tracker = CostTracker()
tracker.add(input_tokens=1_500, output_tokens=800, model="gpt-4o-mini")
tracker.add(2_000, 1_200, "gpt-4o-mini")

print(tracker)          # "2 calls, 5,500 tokens, $0.0019"
tracker.summary()       # {"calls": 2, "input_tokens": ..., "cost_usd": ...}

API reference

Function Description
count_tokens(text, model=None) Exact (tiktoken) or estimated token count
estimate_tokens(text) Dependency-free heuristic count
truncate_to_tokens(text, max_tokens, model=None) Trim text to a token budget
estimate_cost(input_tokens, output_tokens, model, pricing=None) USD cost estimate
CostTracker(pricing=None) Accumulate tokens + USD cost across many calls
retry(attempts, base_delay, backoff, jitter, exceptions, ...) Backoff retry decorator (sync and async)
PromptTemplate(template) Templating with missing-variable validation
chunk_text(text, chunk_size, overlap) Overlapping text chunks (by character)
chunk_by_tokens(text, chunk_size, overlap, model=None) Overlapping text chunks (by token budget)
split_text(text, chunk_size, overlap, separators=None) Boundary-aware chunks (paragraph/sentence/word)
extract_json(text, default=...) Parse the first JSON value out of an LLM reply
count_message_tokens(messages, model=None) Token count of a chat-format message list
trim_messages(messages, max_tokens, model=None, keep_system=True) Trim a conversation to a token budget
cached(maxsize, ttl) Memoize calls on an args hash (sync + async)
RateLimiter(rate, per, capacity=None) Token-bucket throttle (gate / context manager / decorator)

Development

git clone https://github.com/YoungAlpaccino/llmbelt
cd llmbelt
pip install -e ".[dev]"
pytest          # run tests
ruff check .    # lint

Publishing to PyPI (maintainer notes)

python -m build
twine upload dist/*

Before first publish: confirm the name llmbelt is free on PyPI. If taken, rename in pyproject.toml, the src/ folder, and imports (a single find-and-replace).


License

MIT — see LICENSE. Use it anywhere, including commercially.

Download files

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

Source Distribution

llmbelt-0.4.0.tar.gz (22.3 kB view details)

Uploaded Source

Built Distribution

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

llmbelt-0.4.0-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

Details for the file llmbelt-0.4.0.tar.gz.

File metadata

  • Download URL: llmbelt-0.4.0.tar.gz
  • Upload date:
  • Size: 22.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for llmbelt-0.4.0.tar.gz
Algorithm Hash digest
SHA256 bdc1d83d3ce5c2e7ddd9597e15b6d97a7d69aa9f63828b3746f40ff0180b73b7
MD5 d8fd80b93d8964f9f2df74117f62afeb
BLAKE2b-256 5a5fd3226e20d7d0545da5d12b70b655a0e5d383516288ea662f3a0e71fde815

See more details on using hashes here.

File details

Details for the file llmbelt-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: llmbelt-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 18.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for llmbelt-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 49b5295f42afbba442e0ac7dcf8f36923a2ba6ccdaa3bec05d84d6b94abfe6e3
MD5 fd03494b9b71b3ce5e1df0a5c672bb64
BLAKE2b-256 268ff4533b4fe8e29128676feb48dc7677a4443a2336db62e43cc098db215e39

See more details on using hashes here.

Supported by

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