Metadata-Version: 2.4
Name: tokenwise-sdk
Version: 0.1.1
Summary: Metadata-only usage tracking for Anthropic and OpenAI — swap one import line.
Project-URL: Homepage, https://tokenwise.io
Project-URL: Documentation, https://docs.tokenwise.io
Author: Tokenwise
License: MIT
Keywords: anthropic,cost,llm,observability,openai,tokens,usage
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Requires-Dist: httpx>=0.23
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: anthropic>=0.40; extra == 'dev'
Requires-Dist: openai>=1.40; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: openai
Requires-Dist: openai>=1.40; extra == 'openai'
Description-Content-Type: text/markdown

# Tokenwise Python SDK

Metadata-only usage tracking for Anthropic and OpenAI. Swap **one import line**
and every API call's token counts and latency flow to your Tokenwise dashboard
— with **zero access to your prompts or responses**.

```diff
- from anthropic import Anthropic
+ from tokenwise import Anthropic
```

Your code is otherwise unchanged: the wrapper exposes the identical interface,
forwards every call to the official SDK, and returns its response untouched.

## Why it's safe

- **Metadata only.** The SDK reads exactly: `model`, `input_tokens`,
  `output_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`,
  `latency_ms`, `timestamp`, `endpoint`. It never reads or transmits prompt
  text, response text, system prompts, or tool definitions. (Contrast with
  proxy-based tools, which see all your traffic.)
- **Non-blocking.** Events are queued and sent on a background daemon thread.
  If Tokenwise is slow or down, your AI calls complete normally.
- **Fail-silent + bounded.** Up to 1,000 events buffer when offline; the oldest
  drop silently if the buffer fills. Capture never raises, never waits.

## Install

```bash
pip install tokenwise-sdk[anthropic]   # if you use Anthropic
pip install tokenwise-sdk[openai]      # if you use OpenAI
pip install tokenwise-sdk[anthropic,openai]
```

`anthropic` and `openai` are optional extras — install only what you use.

## Configure

Set your Tokenwise key (from the dashboard, looks like `tw_...`):

```bash
export TOKENWISE_API_KEY=tw_your_key
# optional:
export TOKENWISE_API_URL=https://tokenwise-production-aa59.up.railway.app   # default
export TOKENWISE_DISABLED=true                       # emergency kill switch
```

Precedence for every setting: constructor argument > environment variable >
default. If no key is configured the SDK runs disabled and your AI calls behave
exactly as the official SDK.

## Usage

```python
# Pattern 1 — key from environment
import os
os.environ["TOKENWISE_API_KEY"] = "tw_abc123"
from tokenwise import Anthropic
client = Anthropic(api_key="sk-ant-...")
msg = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    messages=[{"role": "user", "content": "Hello"}],
)

# Pattern 2 — key passed explicitly
from tokenwise import Anthropic
client = Anthropic(api_key="sk-ant-...", tokenwise_key="tw_abc123")

# Pattern 3 — OpenAI
from tokenwise import OpenAI
client = OpenAI(api_key="sk-...", tokenwise_key="tw_abc123")
client.chat.completions.create(
    model="gpt-5.4",
    messages=[{"role": "user", "content": "Hello"}],
)
```

Streaming and async work the same way:

```python
# Streaming (sync) — usage captured on stream completion
with client.messages.create(..., stream=True) as stream:
    for event in stream:
        ...

# Async
from tokenwise import AsyncAnthropic
client = AsyncAnthropic(api_key="sk-ant-...", tokenwise_key="tw_abc123")
msg = await client.messages.create(...)
```

## What's instrumented (v1)

| Provider | Method | Streaming |
|----------|--------|-----------|
| Anthropic | `messages.create` | ✅ usage read from the event stream (request unchanged) |
| OpenAI | `chat.completions.create` | ✅ see note below |

Other methods pass through and work, but aren't yet recorded. (OpenAI Responses
API and legacy completions are planned.)

### Note on OpenAI streaming

OpenAI only returns token usage on a streamed response when the request includes
`stream_options={"include_usage": True}`. When you stream **without** supplying
your own `stream_options`, Tokenwise injects it for you so usage can be captured.
This adds one final usage-only chunk (with an empty `choices` list) to the
stream. If you already pass `stream_options`, Tokenwise respects yours and does
not modify the request (in that case usage is captured only if you enabled it).

### Latency semantics

For non-streaming calls, `latency_ms` is the wall-clock time of the call. For
streaming calls it is the **total stream duration** (until the last chunk is
consumed), which includes time your code spends between chunks — events from
streaming calls carry `streamed: true` so this is distinguishable.

## License

MIT
