Metadata-Version: 2.4
Name: tokentifyai-usagemeter
Version: 0.4.2
Summary: Out-of-band AI API usage metering SDK
Author: Tokentify
License-Expression: MIT
Project-URL: Homepage, https://github.com/tokentifyai/tokentify-sdk-python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Requires-Dist: requests>=2.28.0
Requires-Dist: urllib3>=1.26
Requires-Dist: python-dotenv>=1.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"

# Tokentify UsageMeter (Python)

Out-of-band AI API usage metering for Python. After setup, use **httpx** for provider calls; the SDK captures usage automatically.

## Install

```bash
pip install tokentifyai-usagemeter
```

## Quick start (v0.4.0)

**Integrator checklist:** install → `.env` → `um.load_env()` → `um.setup()` → httpx.

```bash
cp .env.example .env
# Set USAGEMETER_API_KEY and USAGEMETER_BUCKET
```

```python
import httpx
import tokentifyai_usagemeter as um

um.load_env()   # step 1 — load .env
um.setup()      # step 2 — verify collector, install hooks, read bucket from .env

with httpx.Client() as client:
    client.post(
        "https://api.openai.com/v1/chat/completions",
        json={"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "hi"}]},
        headers={"Authorization": "Bearer YOUR_OPENAI_KEY"},
    )
# No flush(), sleep(), tag(), or track_tool() required — SDK batches and flushes on exit.
```

Optional bucket override: `um.setup("staging-openai")`.

### What you do NOT need in your app

On the golden path you do **not** call: `flush()`, `sleep()`, `tag()`, `track_tool()`, `init()`, `verify=` flags, or hand-rolled trace/span UUIDs. The SDK owns background emit, shutdown flush, session attribution, and agent tool metering.

### Streamlit / Jupyter hot reload

```python
um.ensure_setup()  # idempotent load_env() + setup()
```

Set `USAGEMETER_VERIFY=false` in `.env` for local dev when the collector is intermittently unavailable.

## Agent apps (`@um.tool`)

Same two startup calls. Define plain tool functions — the SDK meters each execution.

```python
um.load_env()
um.setup()

@um.tool
def get_weather(city: str) -> dict:
    return {"temp_c": 22, "city": city}
```

Or use a context manager:

```python
with um.agent_run() as run:
    result = run.call("get_weather", {"city": "Paris"}, fn=lambda: fetch_weather("Paris"))
```

## Chat / sessions (`um.session`)

Attach `session_id` to every event in a block — no per-turn `tag(session_id=...)`.

```python
um.load_env()
um.setup()

with um.session("conv_abc123"):
    client.post(...)
```

## Multi-provider buckets

Route each provider to its own dashboard bucket without per-call `tag(bucket_name=...)`:

```python
um.load_env()
um.setup(provider_buckets={
    "openai": "openai",
    "anthropic": "anthropic",
    "google": "google",
})
```

Or set `USAGEMETER_PROVIDER_BUCKETS=openai:openai,anthropic:anthropic` in `.env`.

## Environment variables

| Variable | Required | Default | Notes |
|----------|----------|---------|-------|
| `USAGEMETER_API_KEY` | Yes | — | Also accepts `TOKENTIFY_API_KEY` aliases |
| `USAGEMETER_BUCKET` | Yes* | — | *Optional if passed to `setup("bucket")` or using `provider_buckets` |
| `USAGEMETER_COLLECTOR_URL` | No | hosted default | Local / self-hosted collector |
| `USAGEMETER_VERIFY` | No | `true` | Set `false` for Streamlit local dev |
| `USAGEMETER_LOAD_DOTENV` | No | `false` | Legacy: load `.env` inside `setup()` |
| `USAGEMETER_APP_NAME` | No | — | Optional app label |
| `USAGEMETER_ENVIRONMENT` | No | `production` | |
| `USAGEMETER_DEBUG` | No | `false` | SDK debug logging |

## Speech & audio APIs

After `setup()`, httpx calls to OpenAI speech endpoints are metered automatically.

See prior sections in **[INTEGRATION.md](./INTEGRATION.md)** for manual `track_speech()`, token breakdown, and custom metrics metadata.

## Appendix (advanced)

- **`init()`** — lower-level init with explicit `verify_connection` / `verify_api_key`
- **`track_tool()`** — manual agent tool events (prefer `@um.tool` / `agent_run()`)
- **`tag()`** — thread-local overrides (multi-bucket per request)
- **`flush()`** — tests and `os._exit` scenarios only

```python
# Not supported — will raise TypeError:
# um.setup(api_key="secret")
```

Call `init()` / `setup()` **before** creating httpx clients.

## Agent tool tracking (v0.3.4)

After your app executes a tool (any provider / agent framework), call
`track_tool()` once. See **[INTEGRATION.md](./INTEGRATION.md)** for a full loop
example.

Provider APIs expose tool-call data in different places (response content vs
client-side MCP loops; never in billing `usage`). See the
[provider research matrix](https://github.com/infinistackai/tokentify-backend/blob/main/docs/AfterDemo/04-tool-usage-tracking.md#provider-research--where-tool-call-data-lives)
before adding auto-capture.

```python
import tokentifyai_usagemeter as um

um.setup()
um.track_tool(
    trace_id="tr_abc",
    span_id="sp_1",
    tool_name="get_weather",
    tool_input={"city": "Paris"},
    tool_output={"temp_c": 22},
    status="success",
    duration_ms=42,
)
```

## Speech & audio APIs (v0.3.0)

After `setup()`, httpx calls to OpenAI speech endpoints are metered automatically:

| Endpoint | Billable unit | Source |
|----------|---------------|--------|
| `/v1/audio/transcriptions` | seconds | `usage.seconds` in JSON response |
| `/v1/audio/translations` | seconds | same as transcriptions |
| `/v1/audio/speech` | characters | `len(request.input)` |

Create buckets with **tracking mode: custom** and set rates in the Tokentify dashboard.
The SDK sends raw billable quantities in event `metadata` (e.g. `audio_seconds`,
`input_characters`, `total_tokens`) — the collector discovers fields and computes
`cost_usd` server-side. No integrator field-key alignment is required for standard
token and speech signals.

```python
import tokentifyai_usagemeter as um

um.setup()
# Whisper via httpx → auto-emits speech event to speech-to-text bucket

# Manual / non-httpx:
usage = um.track_speech(
    provider="openai",
    model="whisper-1",
    kind="stt",
    billable_unit="seconds",
    billable_quantity=12.5,
    endpoint="audio/transcriptions",
)

# Flat per-request pricing (api_request):
um.track_speech(
    provider="openai",
    model="whisper-1",
    kind="stt",
    billable_unit="request",
    endpoint="audio/transcriptions",
)
```

## Token breakdown (v0.2.0)

The SDK extracts **input**, **output**, **cache read**, and **cache write** tokens
from provider responses. Counts are mirrored into event `metadata` for custom-metrics
field discovery and feed weighted token pricing on the Tokentify collector.

### Automatic (httpx hook)

When you use httpx after `setup()`, usage is captured automatically — including
Anthropic cache fields and OpenAI `prompt_tokens_details.cached_tokens`.

### Manual / out-of-band

Use `track_from_response()` when your app calls the provider directly:

```python
import httpx
import tokentifyai_usagemeter as um

um.setup()

response = httpx.post(
    "https://api.anthropic.com/v1/messages",
    json={"model": "claude-haiku-4-5-20251001", "messages": [...]},
    headers={"x-api-key": "...", "anthropic-version": "2023-06-01"},
)

usage = um.track_from_response(
    {
        "provider": "anthropic",
        "model": "claude-haiku-4-5-20251001",
        "endpoint": "messages",
        "http_status": response.status_code,
        "status": "success" if response.is_success else "error",
    },
    response.text,
)

# Return breakdown to your frontend
return {"reply": assistant_text, "usage": usage.to_dict()}
```

Or pass explicit counts with `track_llm()`:

```python
usage = um.track_llm(
    provider="anthropic",
    model="claude-haiku-4.5",
    input_tokens=1000,
    output_tokens=200,
    cache_read_tokens=500,
    cache_write_tokens=80,
)
```

### `TokenUsage` fields

| Field | Ingest key | Source (Anthropic) | Source (OpenAI) |
|-------|------------|----------------------|-------------------|
| `input_tokens` | `input_tokens` | `usage.input_tokens` | `usage.prompt_tokens` |
| `output_tokens` | `output_tokens` | `usage.output_tokens` | `usage.completion_tokens` |
| `cache_read_tokens` | `cache_read_tokens` | `usage.cache_read_input_tokens` | `usage.prompt_tokens_details.cached_tokens` |
| `cache_write_tokens` | `cache_write_tokens` | `usage.cache_creation_input_tokens` | — |

Vendor alias keys (`prompt_tokens`, `cache_read_input_tokens`, etc.) are accepted
on `track_llm()` and override parsed values on `track_from_response()`.

### 3. Launch (v0.2.0)

`.env` (`USAGEMETER_API_KEY` + `USAGEMETER_BUCKET`):

```bash
USAGEMETER_API_KEY=your_ingest_api_key
USAGEMETER_BUCKET=your_bucket_name
```

`app.py`:

```python
import httpx
import tokentifyai_usagemeter as um

um.setup()  # loads .env; reads USAGEMETER_API_KEY and USAGEMETER_BUCKET

client = httpx.Client()
response = client.post(
    "https://api.openai.com/v1/chat/completions",
    json={"model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}]},
    headers={"Authorization": "Bearer YOUR_OPENAI_KEY"},
)
print(response.status_code)

um.flush()
```

```bash
python app.py
```

### 4. Develop from this repo

```bash
cd tokentify-sdk-python
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
python3 -m pytest tests/ -v
```
