Metadata-Version: 2.4
Name: tripwire-litellm
Version: 0.1.1
Summary: Per-client budget hard stop and cost logging for LiteLLM proxies, by Tripwire
Project-URL: Homepage, https://www.tripwire.agency
Project-URL: Walkthrough video, https://www.youtube.com/watch?v=d6WsQY1OMD4
Keywords: litellm,llm,budget,cost,openai,agency,hard-stop,token-usage
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Topic :: Internet :: Proxy Servers
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-asyncio; extra == "test"

# tripwire-litellm

Per-client budget hard stop and cost logging for a LiteLLM proxy. Before each call through your LiteLLM proxy, the plugin checks that client's budget in [Tripwire](https://www.tripwire.agency) and denies the call with HTTP 402 once the budget is used up. After each call, it logs the cost to Tripwire per client and project, so you can see what each client actually costs you.

Built for AI agencies that run client work through one shared LiteLLM proxy and need to know, and cap, what each client spends.

You need a Tripwire account. There's a free 14-day trial, no card required, at https://www.tripwire.agency. The hard stop applies on the Agency and Scale plans and during an active trial. On Starter (or an expired trial) calls are always allowed and cost is still logged.

Walkthrough video: https://www.youtube.com/watch?v=d6WsQY1OMD4

## Install

Python 3.9 or newer, on the machine that runs your LiteLLM proxy (LiteLLM proxy 1.65.0 or newer).

```bash
pip install tripwire-litellm
```

## Config

Point `callbacks` at the handler instance in your proxy config:

```yaml
litellm_settings:
  callbacks: ["tripwire_litellm.handler.proxy_handler_instance"]
```

Then set the environment variables (defaults shown):

```bash
TRIPWIRE_INGEST_TOKEN=tw_live_...          # required, never log it
TRIPWIRE_BASE_URL=https://www.tripwire.agency
TRIPWIRE_FAIL_MODE=open                    # or closed
TRIPWIRE_CHECK_TIMEOUT_MS=800
TRIPWIRE_DENY_STATUS=402                   # not 429; SDKs retry 429
TRIPWIRE_RESERVE_USD=0
TRIPWIRE_HARD_STOP=on                      # off = ingest only
TRIPWIRE_CLIENT_ID=
TRIPWIRE_PROJECT_ID=
```

Calls are attributed to a client and project from, in order: request metadata (`tripwire_client_id`, `tripwire_project_id`), the virtual key's metadata, the team's metadata, then `TRIPWIRE_CLIENT_ID` / `TRIPWIRE_PROJECT_ID`.

## Limits

- **Fail-open by default.** If Tripwire can't be reached (timeout, network error, 5xx, 401), the call is allowed. Set `TRIPWIRE_FAIL_MODE=closed` to return 503 instead. After 5 failures in a row, checks are skipped for 30 seconds.
- **Spend is known after the call.** Tripwire learns the cost when a call finishes and is ingested, so calls already running when the budget is crossed still complete. Worst-case overshoot is about 2 × concurrency × average call cost, and never less than your single largest call.
- **Fixed reserve.** `TRIPWIRE_RESERVE_USD` sends the same estimated cost with every check. There is no per-request sizing from tokens. Lower concurrency on a key, a reserve, or fail-closed mode reduce overshoot.

## For developers

Hooks (checked against the LiteLLM proxy call-hook docs, 2026-10-05):

- `async_pre_call_hook(self, user_api_key_dict, cache, data, call_type)`: returns `data` unchanged on allow; on deny raises `fastapi.HTTPException` 402 with `error.code` `tripwire_budget_exceeded`.
- `async_log_success_event(self, kwargs, response_obj, start_time, end_time)`: call id from `kwargs["litellm_call_id"]`, then `kwargs["litellm_params"]["litellm_call_id"]`, then `response_obj.id`; cost and provider from `kwargs["response_cost"]`, `kwargs["litellm_params"]["custom_llm_provider"]`, `kwargs["model"]`.

Unit tests stub `litellm.integrations.custom_logger` when LiteLLM isn't installed.
