Metadata-Version: 2.4
Name: hong-lab-ai-cost
Version: 0.1.6
Summary: Lightweight API cost tracker for research labs
Author-email: kyle <bian17888nz@gmail.com>
License-Expression: MIT
Keywords: llm,cost-tracking,openai,gemini,anthropic
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.31
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == "openai"
Provides-Extra: gemini
Requires-Dist: google-genai>=1.0; extra == "gemini"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.30; extra == "anthropic"
Provides-Extra: all
Requires-Dist: openai>=1.0; extra == "all"
Requires-Dist: google-genai>=1.0; extra == "all"
Requires-Dist: anthropic>=0.30; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-mock; extra == "dev"
Dynamic: license-file

# Hong Lab AI Cost Tracker

A lightweight Python SDK that transparently tracks LLM API costs. Wrap your existing client with `tracker.wrap()` — costs are logged automatically.

Supports **OpenAI**, **Google Gemini**, **Anthropic**, and **third-party proxies** (e.g. apiyihe.org).

## Installation

```bash
pip install hong-lab-ai-cost

# With specific provider support
pip install "hong-lab-ai-cost[openai]"
pip install "hong-lab-ai-cost[all]"       # OpenAI + Gemini + Anthropic
```

## API Keys (Recommended: Use Environment Variables)

You do **NOT** need to hardcode API keys in your code. Each provider's SDK automatically reads from environment variables — just export them in your shell or `.env` file:

```bash
# OpenAI (including third-party proxies)
export OPENAI_API_KEY="sk-..."

# Google Gemini
export GEMINI_API_KEY="..."

# Anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
```

This way, your code stays clean and your keys are never exposed in source files. The `hong-lab-ai-cost` SDK does not handle API keys at all — it only wraps the client for cost tracking. Key management is entirely handled by each provider's own SDK.

> **💡 Tip:** Add these exports to your `~/.bashrc`, `~/.zshrc`, or use a `.env` file with [python-dotenv](https://pypi.org/project/python-dotenv/) to load them automatically.

## Usage

### OpenAI

```python
from openai import OpenAI
from hong_lab_ai_cost import CostTracker

tracker = CostTracker(project="MyProject", user="kyle", email="kyle@aucklanduni.ac.nz")
client = tracker.wrap(OpenAI())  # Reads OPENAI_API_KEY from environment

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

print(tracker.summary())
```

### Google Gemini

```python
from google import genai
from hong_lab_ai_cost import CostTracker

tracker = CostTracker(project="MyProject", user="kyle", email="kyle@aucklanduni.ac.nz")
client = tracker.wrap(genai.Client())  # Reads GEMINI_API_KEY from environment

response = client.models.generate_content(model="gemini-2.5-flash", contents="Hello!")
```

### Anthropic

```python
import anthropic
from hong_lab_ai_cost import CostTracker

tracker = CostTracker(project="MyProject", user="kyle", email="kyle@aucklanduni.ac.nz")
client = tracker.wrap(anthropic.Anthropic())  # Reads ANTHROPIC_API_KEY from environment

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello!"}],
)
```

### Third-party Proxy

```python
from openai import OpenAI
from hong_lab_ai_cost import CostTracker

tracker = CostTracker(project="MyProject", user="kyle", email="kyle@aucklanduni.ac.nz")
# OPENAI_API_KEY is read from environment; only base_url needs to be specified
client = tracker.wrap(OpenAI(base_url="https://z.apiyihe.org/v1"))

response = client.chat.completions.create(model="gpt-4o-mini", messages=[...])
```

### Manual Recording

For unsupported providers, record usage manually:

```python
tracker.record(model="llama-3-8b", prompt_tokens=1000, completion_tokens=500)
```

## Configuration

All settings can be provided via **constructor arguments**, **environment variables**, or a **`.cost-tracker.yaml`** file (priority: constructor > env > yaml > defaults).

| Setting | Constructor | Env Variable | Default |
|---------|-------------|--------------|---------|
| Project name | `project=` | `COST_TRACKER_PROJECT` | `"default"` |
| User name | `user=` | `COST_TRACKER_USER` | `None` |
| Email | `email=` | `COST_TRACKER_EMAIL` | `None` |
| Remote API | `remote_url=` | `COST_TRACKER_REMOTE_URL` | `https://api.honglab.dev` |
| Storage dir | `storage_dir=` | — | `.cost-tracker/` |

Example `.cost-tracker.yaml`:

```yaml
project: DentalVLM
user: kyle
email: kyle@aucklanduni.ac.nz
```

## Remote Sync

Remote sync is **enabled by default** — all usage records are automatically uploaded to `https://api.honglab.dev`. You do **not** need to configure anything extra.

Records are uploaded to `POST {remote_url}/api/v1/usage/batch` with `X-Lab-User` and `X-Lab-Email` headers. The server validates these against a whitelist.

If the upload fails (network error, server down), the record is kept locally and retried on the next `tracker.flush()` or at process exit.

To **disable** remote sync (local-only mode), explicitly set `remote_url` to an empty string:

```python
tracker = CostTracker(project="MyProject", user="kyle", email="kyle@aucklanduni.ac.nz", remote_url="")
```

Or via environment variable:
```bash
export COST_TRACKER_REMOTE_URL=""
```

## Pricing

The SDK includes a built-in pricing catalog (`pricing_catalog.yaml`) covering **170+ paid API models** from OpenAI, Google Gemini, and Anthropic.

Prices are sourced from [LiteLLM's community-maintained database](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json) (GitHub 60k+ stars, actively maintained) as a **baseline**. The SDK uses these prices to automatically calculate costs based on token usage — no manual lookup needed.

### Override or Add Custom Pricing

You can override any model's pricing or add new models not in the catalog:

```python
from hong_lab_ai_cost import CostTracker

tracker = CostTracker(project="MyProject", user="kyle", email="kyle@aucklanduni.ac.nz")

# Override an existing model's pricing (USD per 1M tokens)
tracker.pricing.set("gpt-4o", "openai", input_per_1m=2.50, output_per_1m=10.00)

# Add a model not in the catalog (e.g., a self-hosted or proxy model)
tracker.pricing.set("my-local-llama", "custom", input_per_1m=0.00, output_per_1m=0.00)
tracker.pricing.set("deepseek-v3", "deepseek", input_per_1m=0.27, output_per_1m=1.10)
```

Overrides take effect immediately and apply for the lifetime of the `CostTracker` instance.

### Unknown Models

If you use a model that is **not** in the catalog and has not been manually configured, the SDK will:

1. Log a warning (once per model, not spammed):
   ```
   ⚠️  Unknown model 'my-new-model' — cost recorded as $0.00.
      To fix, add pricing via tracker.pricing.set('my-new-model', 'provider', input_per_1m, output_per_1m)
      or update pricing_catalog.yaml.
   ```
2. Record the usage with `cost = $0.00` — token counts are still tracked, so you can retroactively calculate costs.

### Update the Pricing Catalog

To pull the latest model prices from LiteLLM and update the bundled catalog:

```bash
python -m hong_lab_ai_cost.sync_pricing
```

This merges new prices into `pricing_catalog.yaml`. Any custom entries you added to the YAML file are preserved.

> **Note**: This updates the YAML file inside the installed package. After syncing, you should rebuild and republish the SDK to distribute the updated prices to all users.

## License

MIT
