Metadata-Version: 2.4
Name: llmbelt
Version: 0.2.0
Summary: A tiny, dependency-free tool belt for LLMs: token counting, cost estimation, retries, prompt templates, and text chunking.
Author: YoungAlpaccino
License: MIT
Project-URL: Homepage, https://github.com/YoungAlpaccino/llmbelt
Project-URL: Repository, https://github.com/YoungAlpaccino/llmbelt
Project-URL: Issues, https://github.com/YoungAlpaccino/llmbelt/issues
Keywords: llm,ai,tokens,tiktoken,prompt,rag,openai,anthropic,gpt
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: tiktoken
Requires-Dist: tiktoken>=0.5; extra == "tiktoken"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# 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](https://github.com/YoungAlpaccino/llmbelt/actions/workflows/ci.yml/badge.svg)](https://github.com/YoungAlpaccino/llmbelt/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/llmbelt.svg)](https://pypi.org/project/llmbelt/)
[![Python](https://img.shields.io/pypi/pyversions/llmbelt.svg)](https://pypi.org/project/llmbelt/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

- 🪶 **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

```bash
pip install llmbelt

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

---

## Usage

### Count tokens

```python
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

```python
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

```python
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

```python
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

```python
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']
```

### Chunk text for RAG

```python
from llmbelt import chunk_text, chunk_by_tokens

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)
```

---

## 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 |
| `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) |
| `extract_json(text, default=...)` | Parse the first JSON value out of an LLM reply |

---

## Development

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

### Publishing to PyPI (maintainer notes)

```bash
python -m build
twine upload dist/*
```

> **Before first publish:** confirm the name `llmbelt` is free on [PyPI](https://pypi.org/project/llmbelt/).
> If taken, rename in `pyproject.toml`, the `src/` folder, and imports (a single find-and-replace).

---

## License

MIT — see [LICENSE](./LICENSE). Use it anywhere, including commercially.
