Metadata-Version: 2.4
Name: unifyapps-guardrails
Version: 0.1.0
Summary: Thin client for UnifyApps platform guardrails (config-driven controls/policies), traced to UnifyApps observability.
Author: UnifyApps
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/unify-apps/unifyapps-guardrails
Project-URL: Repository, https://github.com/unify-apps/unifyapps-guardrails
Keywords: unifyapps,guardrails,ai,governance,opentelemetry,llm
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: opentelemetry-api>=1.20.0
Dynamic: license-file

# unifyapps-guardrails

A thin client for **UnifyApps platform guardrails**. Checks are *not* run in this
process — they are config-driven `controls` (Denied Topics, Content Filter, PII,
Prompt Injection, Custom terms, Regex, Credentials, Blocked Tools, Custom LLM)
grouped into `policies` applied to an agent. This client calls the exported
`Governance | Inline Evaluate` endpoint, which resolves the agent's controls for a
given surface, runs only those, writes incidents itself, and returns one decision.

Add or remove a control in the UI and the same code enforces it — nothing about the
checks is hardcoded here. Every call emits an OpenTelemetry span so it nests inside
your existing UnifyApps trace.

## Install

```bash
pip install unifyapps-guardrails
```

Or, from a clone (editable):

```bash
pip install -e .
```

Stdlib + `opentelemetry-api` only — no models to download, no heavy deps.

## Configure

| Env var | Purpose |
|---|---|
| `UNIFYAPPS_GUARDRAILS_URL` | URL of the exported inline-evaluate callable, with the **agent id as its last path segment** (like the OTLP endpoint) |
| `UNIFYAPPS_GUARDRAILS_API_KEY` | optional; sent as `Authorization: Bearer <key>` |

## Usage

```python
from unifyapps.guardrails import GovernanceGuard

# agent id comes from UNIFYAPPS_GUARDRAILS_URL's path — nothing to pass here
guard = GovernanceGuard()

# Before the LLM sees the user's message
d = guard.check_user_input(user_text)
if d.block:
    return d.content          # refusal message — don't call the LLM
user_text = d.content         # masked/clean text to send to the LLM

# After the LLM answers, before the user sees it
d = guard.check_agent_output(reply)
return d.content              # block -> refusal; else masked/clean text
```

### The decision

`evaluate` / `check_user_input` / `check_agent_output` return a `GovernanceDecision`:

| Field | Meaning |
|---|---|
| `block` | `True` if the content must be withheld |
| `content` | the text to **use**: masked/clean when `block=False`, the refusal message when `block=True` |
| `raw` | the full endpoint payload |

Only `user_input` and `agent_output` are enforced (see `Surface`); tool surfaces are
out of scope. The guard **fails open** — on timeout or endpoint error it returns the
original text with `block=False`, so a guardrail outage never drops a turn.

## Endpoint contract

```
POST <UNIFYAPPS_GUARDRAILS_URL>          # .../<agent-id>  (id is the last path segment)
body  -> { content, inputType, caseId }
200   -> { block: bool, content: str }
```

The **agent id is the last path segment of the URL**, not a body field — same shape as
the OTLP collector endpoint, so it is never passed twice. It is both the policy lookup
key and the incident scope; the exported callable maps that path segment to its
`agentId` input. `inputType` is the surface (`user_input` / `agent_output`). `caseId`
is any stable per-conversation id (the client defaults to a random uuid) so the
resolver takes the chat branch.

## Examples

Full input + output turns, one per framework, in [`examples/`](examples/):

| Framework | File |
|---|---|
| LangChain | [`examples/langchain_unify_guardrails.py`](examples/langchain_unify_guardrails.py) |
| LangGraph | [`examples/langgraph_unify_guardrails.py`](examples/langgraph_unify_guardrails.py) |
| OpenAI Agents SDK | [`examples/openai_agents_unify_guardrails.py`](examples/openai_agents_unify_guardrails.py) |

## License

[Apache 2.0](LICENSE).
