Metadata-Version: 2.4
Name: fin-badboy-sdk
Version: 0.2.1
Summary: Python SDK for fin (badboy) - track LLM usage events; import as fin_sdk
Author: fin
License-Expression: LicenseRef-FinProprietary
Project-URL: Homepage, https://github.com/yaronbbb/fin
Project-URL: Repository, https://github.com/yaronbbb/fin
Project-URL: Documentation, https://github.com/yaronbbb/fin/tree/main/sdks/python
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: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-mock>=3.10; extra == "dev"
Requires-Dist: responses>=0.23; extra == "dev"
Dynamic: license-file

# fin-badboy-sdk — Python SDK

Track AI/LLM usage events from Python applications. **PyPI:** `fin-badboy-sdk` (import package remains `fin_sdk`).

## Install

From PyPI (package name `fin-badboy-sdk`).

```bash
pip install fin-badboy-sdk
```

## Quick Start

```python
import time
from openai import OpenAI
from fin_sdk import FinClient

client = OpenAI()
fin = FinClient()

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

fin.track_response(
    response,
    provider="openai",
    feature_id="chat",
    latency_ms=(time.time() - start) * 1000,
)
```

**Product `environment`** (`development` | `production`) is **not** passed into `track_response`—it is set on the server from your **API key** ([Epic 21](../../ENVIRONMENTS.md#deployment-vs-product-environments)). Use a key that matches the fin deployment URL.

## Configuration

Set environment variables (read at construction time):

```bash
export FIN_INGEST_URL=https://events.badboy.dev
export FIN_API_KEY=sk-your-api-key
```

For experiment resolution (`resolve_model` / `experiment`), also set `FIN_API_URL` (api-nest base URL), for example `https://api.badboy.dev`.

Or pass explicitly:

```python
fin = FinClient(
    ingest_url="https://events.badboy.dev",
    api_key="sk-your-api-key",
)
```

For local development, set `ingest_url` to your local events service base URL (no trailing slash).

## API

### `track_response(response=None, *, provider, feature_id, ...)`

Build and POST a usage event from a provider response. `response` is positional-or-keyword; all other parameters are keyword-only.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `response` | `Any` | No | Raw LLM response; SDK auto-extracts `usage` + `model` |
| `provider` | `str` | Yes | Use a key from **Supported Providers** below (aligned with Fin’s global pricing model for server-side cost), or any string with explicit `usage` / `model` |
| `feature_id` | `str` | Yes | Workflow/feature name |
| `model` | `str` | No | Override or fallback |
| `usage` | `dict` | No | `{"input_tokens": N, "output_tokens": N, "total_tokens": N}` (snake_case; converted to camelCase internally) |
| `latency_ms` | `float` | No | End-to-end latency in milliseconds |
| `error` | `bool` | No | `True` on error path (zeroes usage) |
| `tenant_id` | `str` | No | Your end-user/customer ID |
| `plan_tier` | `str` | No | e.g. `"free"`, `"pro"` |
| `outcome` | `str` | No | e.g. `"success"`, `"refusal"` |
| `cost` | `float` | No | Client-supplied cost in USD |
| `idempotency_key` | `str` | No | De-duplicate retries |

**Cost:** pass `cost` when you trust your own figure (e.g. provider billing). Omit it to let Fin derive an estimate from its **global pricing model** using `provider`, `model`, usage, and event time. The same applies to `track()` when you build the full camelCase payload.

### `track(payload, *, idempotency_key=None)`

Post a pre-built event dict. All keys must be **camelCase**. Missing required fields are logged and the event is silently skipped.

**Epic 21:** `track_response` does **not** send `environment`; the events service sets it from the API key. Required fields for `track()` match the public ingest schema (`featureId`, `provider`, `model`, `usage`, `timestamp`, etc.) — **not** `environment`.

## Supported Providers

These keys align with **Fin’s global pricing model** (same provider ids as server-side cost lookup).

| Provider key | Extraction |
|--------------|------------|
| `"openai"` | `usage.prompt_tokens` → `inputTokens`, `usage.completion_tokens` → `outputTokens`, `usage.total_tokens` → `totalTokens`; `model` |
| `"groq"` | Same as `"openai"` |
| `"azure_openai"` | Same as `"openai"` |
| `"mistral"` | Same as `"openai"` (Mistral chat API is OpenAI-compatible) |
| `"anthropic"` | `usage.input_tokens` → `inputTokens`, `usage.output_tokens` → `outputTokens`; `totalTokens` derived as input + output; `model` |
| `"google"` | `usageMetadata.promptTokenCount`, `.candidatesTokenCount`, `.totalTokenCount` (snake_case variants supported); `model` from `model`, `modelVersion`, or `model_version` |

Other provider strings still work: pass `usage` and `model` explicitly on `track_response`, or use `track()` with a full camelCase payload.

## Error Handling

The SDK never raises on ingest failure. HTTP errors and network issues are logged via `logging.getLogger("fin_sdk")` with a `[fin-sdk] ingest failed:` prefix. Your application flow is never disrupted.

## Full Documentation

See [docs/getting-started.md](../../docs/getting-started.md) for the complete integration guide.
