Metadata-Version: 2.4
Name: modelpricing-ai
Version: 2026.4.3
Summary: Python client for ModelPricing.ai cost estimates and tracking
Project-URL: Homepage, https://modelpricing.ai
Author: ModelPricing.ai
License: MIT
License-File: LICENSE
Keywords: ai,anthropic,cost,estimate,llm,model,openai,pricing,token,tracking,usage
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: pydantic>=2.3.0
Requires-Dist: requests>=2.31.0
Requires-Dist: tenacity>=8.0.0
Provides-Extra: async
Requires-Dist: aiohttp>=3.9.0; extra == 'async'
Provides-Extra: dev
Requires-Dist: aiohttp>=3.9.0; extra == 'dev'
Requires-Dist: build>=1.2.1; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: twine>=5.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# modelpricing-ai

[![licensie](https://licensie.com/badge/python/modelpricing-ai.svg)](https://licensie.com/python/packages/modelpricing-ai)

Python client for the [ModelPricing.ai](https://modelpricing.ai) API — estimate LLM usage costs and track spending with a single call.

## Installation

```bash
pip install modelpricing-ai
```

For async support (requires [aiohttp](https://docs.aiohttp.org)):

```bash
pip install modelpricing-ai[async]
```

## Quick Start

```python
from modelpricing_ai import ModelPricingClient

with ModelPricingClient(api_key="YOUR_API_KEY") as client:
    estimate = client.estimate(
        model="claude-haiku-4-5-20251001",
        tokens_in=1000,
        tokens_out=500,
        trace_id={"requestId": "abc-123"},
    )
    print(f"Cost: ${estimate.total:.6f}")
```

## From a provider SDK response

If you already have a response object from the Anthropic or OpenAI SDK, pass it directly — the client pulls the model name and token counts for you. Works with Anthropic Messages, OpenAI Chat Completions, and OpenAI Responses API (Pydantic objects or plain dicts).

```python
from anthropic import Anthropic
from modelpricing_ai import ModelPricingClient

anthropic = Anthropic()
response = anthropic.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=1024,
    messages=[{"role": "user", "content": "hello"}],
)

with ModelPricingClient(api_key="YOUR_API_KEY") as client:
    estimate = client.estimate_from_response(response)
    print(f"Cost: ${estimate.total:.6f}")
```

```python
from openai import OpenAI
from modelpricing_ai import ModelPricingClient

openai = OpenAI()
response = openai.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hello"}],
)

with ModelPricingClient(api_key="YOUR_API_KEY") as client:
    estimate = client.estimate_from_response(response)
```

The async client exposes the same method: `await client.estimate_from_response(response)`.

> Cache-read / cache-creation tokens are not yet priced separately — they're rolled into the input-token total. Raise an issue if you need finer-grained cache pricing.

## Async Usage

Install the `async` extra, then use `AsyncModelPricingClient` as an async context manager:

```python
import asyncio
from modelpricing_ai import AsyncModelPricingClient

async def main():
    async with AsyncModelPricingClient(api_key="YOUR_API_KEY") as client:
        estimate = await client.estimate(
            model="claude-haiku-4-5-20251001",
            tokens_in=1000,
            tokens_out=500,
            trace_id={"requestId": "abc-123"},
        )
        print(f"Cost: ${estimate.total:.6f}")

asyncio.run(main())
```

## Response Structure

All estimate methods (`estimate`, `estimate_from_response`, and their async counterparts) return an `EstimateResponse` object:

```python
estimate.total        # float — total USD cost
estimate.model        # str   — canonical model name
estimate.traceId      # dict | None — your pass-through trace ID
estimate.breakdown    # EstimateBreakdownGroup
  .input              # EstimateBreakdown
    .unit             #   str   — e.g. "token"
    .branch           #   str   — pricing tier that matched
    .qty              #   int   — number of input tokens
    .rate             #   float — per-unit rate
    .subtotal         #   float — input cost
  .output             # EstimateBreakdown (same fields for output tokens)
```

## Configuration

| Parameter     | Default                         | Description                                                              |
| ------------- | ------------------------------- | ------------------------------------------------------------------------ |
| `api_key`     | _required_                      | Your ModelPricing.ai API key (also reads `MODELPRICING_API_KEY` env var) |
| `base_url`    | `"https://api.modelpricing.ai"` | API base URL (also reads `MODELPRICING_BASE_URL` env var)                |
| `timeout`     | `30.0`                          | Request timeout in seconds                                               |
| `max_retries` | `3`                             | Maximum retry attempts for transient errors                              |
| `session`     | `None`                          | Optional `requests.Session` (sync) or `aiohttp.ClientSession` (async)    |

Parameters are resolved in order: constructor argument > environment variable > default.

```python
client = ModelPricingClient(
    api_key="YOUR_API_KEY",
    base_url="https://api.modelpricing.ai",
    timeout=30.0,
    max_retries=3,
)
```

## Error Handling

The client raises typed exceptions for different failure modes. All of them inherit from `ModelPricingError` and carry a `status_code` attribute:

| Exception           | HTTP Status | When                                                             |
| ------------------- | ----------- | ---------------------------------------------------------------- |
| `Unauthorized`      | 401         | Invalid or missing API key                                       |
| `NotFound`          | 404         | Unknown endpoint                                                 |
| `ValidationError`   | 422         | Invalid model name or metrics                                    |
| `ServerError`       | 5xx         | Server-side failures (retried automatically)                     |
| `ModelPricingError` | other       | Base class — raised on unexpected status codes or malformed 200s |

```python
from modelpricing_ai.errors import Unauthorized, ValidationError, ServerError

try:
    estimate = client.estimate(model="claude-haiku-4-5-20251001", tokens_in=1000, tokens_out=500)
except Unauthorized:
    print("Check your API key")
except ValidationError as e:
    print(f"Bad request: {e}")
except ServerError:
    print("Server error — will be retried automatically")
```

## Retry Behavior

The client automatically retries on transient errors with exponential backoff:

- **Retries**: 5xx server errors and network/connection errors
- **No retry**: 4xx client errors (401, 404, 422)
- **Default**: 3 retries with exponential backoff (0.1 s initial, 2 s max)

```python
# Increase retries for unreliable networks
client = ModelPricingClient(api_key="YOUR_API_KEY", max_retries=5)

# Disable retries (no retry attempts)
client = ModelPricingClient(api_key="YOUR_API_KEY", max_retries=0)
```

## License

MIT

## Credits

Made with ❤️ by [Humanspeak](https://humanspeak.com)
