Metadata-Version: 2.4
Name: kostlens
Version: 0.2.0
Summary: KostLens SDK - track every AI/LLM call's cost, tokens and latency with 3 lines
Project-URL: Homepage, https://kostlens.com
Project-URL: Documentation, https://kostlens.com/docs/
Project-URL: Source, https://github.com/kostlens/sdks
Project-URL: Issues, https://github.com/kostlens/sdks/issues
Author: KostLens
License: MIT License
        
        Copyright (c) 2026 KostLens (kostlens.com)
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: ai-cost,anthropic,gemini,llm,observability,openai,tokens
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# kostlens

Track every AI call your app makes: tokens, cost, latency, and the tags that
matter (feature, end user), with wrappers designed so they can't break your app.

Part of [KostLens](https://kostlens.com), the financial layer for your AI usage.

## Install

```bash
pip install kostlens
```

## Quickstart (2 lines)

```python
from openai import OpenAI
from kostlens import KostLens, wrap_openai

kl = KostLens(os.environ["KOSTLENS_KEY"])
client = wrap_openai(OpenAI(), kl, tags={"feature": "chat"})

# Use `client` exactly as before. Streaming included.
```

Also available: `wrap_anthropic` and `wrap_google_genai`. Any OpenAI-compatible
API (Groq, OpenRouter, DeepSeek, Kimi...) works through `wrap_openai`. Pass
`provider="deepseek"` so pricing uses the honest vendor label.

Async clients (`AsyncOpenAI`, `AsyncAnthropic`) are supported too. Wrap them
the same way and `await` as usual.

## Tag what matters

Tag keys travel verbatim: use `endUserId`, **not** `end_user_id`. A snake_case
key is stored as a plain custom tag and the Customers page stays empty.

`endUserId` powers cost-per-customer; `feature` powers cost-per-feature.

`feature` rarely changes, so it goes on the client, wrapped once per module:

```python
# module level, once
client = wrap_openai(OpenAI(), kl, tags={"feature": "summarize"})
```

`endUserId` changes on every request, so it goes on the request:

```python
from kostlens import with_tags

# in the handler, where you know who the caller is
with with_tags({"endUserId": user.id}):
    client.chat.completions.create(...)
```

Per-request tags merge OVER the wrap-site ones and win on a name clash, so the
`feature` above still applies. They nest, inner over outer. They do not outlive
the block, so a request that raises cannot leak its customer id into the next
one. It is built on `contextvars`, so threads and `asyncio` tasks never see
each other's tags.

This works for background work too, which is where per-customer cost usually
goes wrong. A nightly job has no request to hang tags on, so its spend lands
with no customer and the accounts that cost the most read as the cheapest:

```python
for account in accounts:
    with with_tags({"endUserId": account.id}):
        summarise(account)
```

**Do not wrap the same client twice to change its tags.** Wrapping replaces
`client.chat.completions.create` in place and marks the client, so handing the
same client over again returns it untouched with its original tags. The mark is
what stops a second wrap from reporting every call once per layer. Before
`with_tags` existed, working around it meant a client per request.

## The sacred rule

The SDK is designed so it can never break the host app. `track()` is
non-blocking (the HTTP call happens on a background daemon thread), network
failures and KostLens outages degrade to "some events are lost", never to an
exception in your request path. Events are batched, retried with backoff on
transient failures, and dropped (with an `on_error` callback) when permanently
rejected.

## Options

```python
KostLens(
    "kl_live_...",
    "https://ingest.kostlens.com",  # default; override for self-hosted
    flush_interval=5.0,
    max_batch_size=100,
    max_buffer_size=5000,
    on_error=lambda err: logging.warning("kostlens: %s", err),
)
```

Call `kl.shutdown()` before a short-lived process exits to flush what is
buffered (it is also registered with `atexit`).

## Docs

Full documentation: [kostlens.com/docs](https://kostlens.com/docs/)

MIT © KostLens
