Metadata-Version: 2.5
Name: verexa
Version: 0.1.0
Summary: Check every prompt and model response against your Verexa policy
Project-URL: Homepage, https://verexa.dev
Project-URL: Documentation, https://verexa.dev/docs
License: Apache-2.0
License-File: LICENSE
Keywords: guardrails,llm,openai,prompt-injection,security
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# verexa (Python)

Checks every prompt and model response against your Verexa policy before it reaches the model or the user. The Python counterpart of [`@verexa/sdk`](https://www.npmjs.com/package/@verexa/sdk), with the same wire format and failure behaviour.

Python 3.10+. No runtime dependencies.

```bash
pip install verexa
```

Create a key in the dashboard under **Settings → API keys** and set it in the environment:

```bash
VEREXA_API_KEY=vx_live_...
```

`VEREXA_BASE_URL` is optional and defaults to `https://api.verexa.dev`. Set it to use a local or self-hosted verdict-api.

## OpenAI

```python
from openai import OpenAI
from verexa import wrap_openai

client = wrap_openai(OpenAI())
```

Works the same with `AsyncOpenAI`. Every `chat.completions.create` and `responses.create` call is now checked on both sides of the turn. A blocked input raises `GuardBlockedError` before the provider is called; a redacted input reaches the model with the sensitive span removed.

## Manual

```python
from verexa import create_guard

guard = create_guard()

verdict = guard.check_input(user_message, trace_id=trace_id, profile="balanced")
if verdict.action == "block":
    return

reply = model(verdict.text if verdict.action == "redact" else user_message)

checked = guard.check_output(reply, trace_id=trace_id, system_prompt=SYSTEM_PROMPT)
if checked.action == "block":
    return

send(checked.text if checked.action == "redact" else reply)
```

In async code use `await guard.check_input_async(...)` and `await guard.check_output_async(...)`.

## Behaviour worth knowing

- **Fails open by default.** If the service is unreachable the SDK returns `allow` with `degraded=True`, and a circuit breaker stops hammering it. Pass `fail_mode="closed"` to block instead.
- **One trace id per turn.** The input check and the output check share it, so the dashboard shows them as one trace.
- **Streaming is checked after the fact.** Chunks have already reached the caller by the time the reply is complete, so a blocking verdict raises once the stream ends. It is a retraction signal, not a gate.
- **Profiles.** `deterministic` (pattern detectors only, no model calls), `balanced` (adds the tier-2 classifier, and the tier-3 judge on anything they do not call clean), `audit` (the judge reviews every check; pass a larger `timeout_ms`, e.g. `15000`). A check that names no profile uses the project's default from the dashboard, and that is what `wrap_openai` sends.
- **Only `create` is wrapped.** The `stream()` and `parse()` helpers and `with_raw_response` bypass the guard; check those turns with `check_input` / `check_output`.

## Development

```bash
cd packages/sdk-python
uv sync
uv run ruff check . && uv run ruff format --check . && uv run mypy src tests
uv run pytest

# against a real verdict-api
LIVE_VEREXA_API_KEY=<key> LIVE_VEREXA_BASE_URL=http://localhost:8080 uv run pytest -m live
```
