Metadata-Version: 2.4
Name: psiguard
Version: 1.0.0
Summary: Official Python client for the PsiGuard guarded-chat API.
Author: PsiCo, LLC
License: Proprietary
Project-URL: Homepage, https://psiguard.net
Project-URL: Documentation, https://psiguard.net/docs
Keywords: psiguard,llm,ai-safety,guardrails,monitoring
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.25

# PsiGuard Python SDK

The official Python client for the [PsiGuard](https://psiguard.net) guarded-chat API.

PsiGuard wraps a language model with a live structural monitor. You send it a
prompt; it runs the model, watches the generation as it happens, and hands you
back a clean answer plus one coarse signal telling you whether it had to step
in. Everything about *how* it decides stays on PsiGuard's servers — this client
only ever sees the answer and that one signal.

## Install

```bash
pip install psiguard
```

The only dependency is `requests`. The whole client is a single module, so you
can also just drop `psiguard.py` into your project and import it.

## Quick start

```python
from psiguard import Client

client = Client(api_key="psg_live_your_key_here")

result = client.guard("What is your return policy?")
print(result.answer)        # the reply to show your user
print(result.protection)    # "safe" | "caution" | "intervened"
```

Set `PSIGUARD_API_KEY` in your environment and you can skip passing the key:

```python
client = Client()           # reads PSIGUARD_API_KEY
```

Get a key from your dashboard at **psiguard.net/dashboard/keys**.

## Give your assistant a personality

```python
result = client.guard(
    "Do you ship to Canada?",
    system_prompt="You are a friendly support agent for Acme Tools.",
    conversation_id="cust-8472",
)
```

## Multi-turn conversations

Reuse one thread so monitoring keeps context across the chat. The
`Conversation` helper manages the id and transcript for you:

```python
chat = client.conversation(system_prompt="You are Acme's support assistant.")

chat.send("Hi, my order hasn't arrived.")
reply = chat.send("It's order 1234.")   # remembers the turn before it

print(reply.answer)
```

## Reading the protection signal

Every turn comes back with one of three values. A simple integration can
ignore it and just show `answer`.

| value | meaning |
|---|---|
| `safe` | Clean run. PsiGuard didn't need to act. |
| `caution` | PsiGuard steered the response back on course. `answer` is the corrected one. |
| `intervened` | PsiGuard withheld the response. `answer` is a neutral refusal, safe to show as-is. |

```python
result = client.guard(prompt)
if result.intervened:
    log.info("PsiGuard stepped in on this turn")
show_to_user(result.answer)
```

## Handling errors

Everything the client raises inherits from `PsiGuardError`, so you can catch
broadly or narrowly:

```python
from psiguard import (
    Client, UsageLimitError, RateLimitError,
    AuthenticationError, PsiGuardError,
)

try:
    result = client.guard(prompt)
except AuthenticationError:
    ...   # key missing, invalid, or revoked
except RateLimitError:
    ...   # too many requests — slow down and retry shortly
except UsageLimitError as e:
    ...   # monthly cap or provider budget reached — a stop sign, not a retry
          # e.scope, e.plan, e.provider, e.used, e.limit, e.month
except PsiGuardError as e:
    ...   # anything else
```

The client automatically retries the failures that are safe to retry
(connection errors and 5xx), and never retries the ones that aren't (4xx and
usage caps). Tune it with `Client(..., max_retries=2, timeout=60)`.

## Verify a key

```python
who = client.whoami()       # runs no model, isn't metered
print(who.account)
```

## Configuration reference

| Parameter | Default | Notes |
|---|---|---|
| `api_key` | `$PSIGUARD_API_KEY` | Your `psg_live_…` key. |
| `base_url` | `$PSIGUARD_BASE_URL` or `https://psiguard.net` | Your PsiGuard host. |
| `timeout` | `60` | Per-request timeout in seconds. |
| `max_retries` | `2` | Extra attempts for retryable failures only. |

The client keeps a connection pool open; reuse one `Client` for the life of
your process, and use it as a context manager to close the pool when done:

```python
with Client() as client:
    client.guard("hello")
```

## A note on what's inside

This client is a courier, not the engine. It contains none of PsiGuard's
internal workings — no metrics, no thresholds, no scoring. It speaks only the
public contract: a prompt goes out, an answer and a protection signal come
back. PsiGuard's monitoring framework is proprietary and confidential,
protected as a trade secret of PsiCo, LLC.

---

© PsiCo, LLC. Licensed for use through the PsiGuard API.
