> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orq.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# External Guardrails

> Call OPA, other policy engines, and third-party guardrail providers from a Python guardrail or evaluator.

An external guardrail blocks a generation on a decision made outside **Orq.ai**: a policy engine (OPA, Cedar, OpenFGA), a third-party moderation API, or an internal compliance service. The decision runs as a [Python Guardrail](/docs/ai-gateway/configuration/guardrails#python-guardrail) that calls the service with `requests`. [System Guardrails](/docs/ai-gateway/configuration/guardrail-rules#system-guardrails) cover PII and secret detection without an external service.

## Create and attach

| Type             | Where                                                                                                                                                                       |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Python Guardrail | **AI Gateway** > **Guardrails** > <kbd className="key">+ Guardrail</kbd> > **Python**                                                                                       |
| Python Evaluator | **Evaluators** > <kbd className="key">+ Evaluator</kbd> > **Python**, or a project or folder > <kbd className="key"><Icon icon="plus" /></kbd> > **Evaluator** > **Python** |

The code defines a function named `evaluate` that receives `log` and returns the verdict; the runner calls `evaluate(log)` by name, so the entry point cannot be renamed. Helper functions can be defined anywhere in the code.

Attach the check:

| Surface        | Attachment                                                                                                                                               | Stage                                                         |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| **AI Gateway** | Add the guardrail to a [Guardrail Rule](/docs/ai-gateway/configuration/guardrail-rules)                                                                  | Per guardrail in the rule: **Input**, **Output**, or **Both** |
| Agents         | Attach the Python Evaluator under [Configure Evaluators and Guardrails](/docs/ai-studio/ai-engineering/build-agents#configure-evaluators-and-guardrails) | Per entry: **Input** or **Output**                            |
| Deployments    | Attach the Python Evaluator under [Evaluators and Guardrails](/docs/ai-studio/ai-engineering/deployments#evaluators-and-guardrails)                      | Per entry: **Input Guardrail** or **Output Guardrail**        |

| `log` field     | Content                                             |
| --------------- | --------------------------------------------------- |
| `log["input"]`  | Last message sent to the model, on an input check   |
| `log["output"]` | Response generated by the model, on an output check |

Full object: [Python Guardrail](/docs/ai-gateway/configuration/guardrails#python-guardrail). Output checks, including external ones, do not run on streaming responses. See [Execution behavior](/docs/ai-gateway/configuration/guardrails#execution-behavior).

## Pass condition

The verdict is compared against the pass condition configured with the guardrail:

| Return value                       | Pass condition                         |
| ---------------------------------- | -------------------------------------- |
| `True` when the traffic is allowed | **Boolean** with **True**              |
| Score, higher is safer             | **Number** with a threshold above zero |

A failing guardrail blocks the request. The status depends on the surface: `422` on the Responses endpoint and on the Agents and Deployments surfaces, and `400` on the OpenAI-compatible chat completions endpoint. On the **AI Gateway** endpoints a guardrail that times out returns `408`; on the Agents surface a guardrail that cannot run, including a timeout, returns `502 guardrail_execution_failed`. See [Guardrail Error Response](/docs/ai-studio/optimize/evaluators#guardrail-error-response).

<Warning>
  **A guardrail that fails to execute blocks the request.** Code that raises an exception, times out, or cannot reach the network ends the run with an execution error instead of passing the traffic. Catch request errors in the code and return a denial, so the caller sees the guardrail's reason rather than a failed execution.
</Warning>

## Requirements

| Requirement  | Detail                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fail closed  | Deny when the check cannot run. Treat a request error, a malformed response, or a missing field as a denial; an exception that escapes the code is an execution error, which blocks the request.                                                                                                                                                                                                                            |
| Timeout      | Set an explicit `timeout` well inside the execution budget, which defaults to 60 seconds and is configurable per evaluator, capped at 10 minutes. A call that outlives the budget is killed, and a killed guardrail is an execution error: the request is blocked and the in-code denial never runs.                                                                                                                        |
| Reachability | The code runs in an **Orq.ai** sandbox, not on the machine that calls the API. Expose the decision service where that sandbox can reach it, behind a proxy that accepts sandbox traffic only. A server bound to `localhost` is not reachable.                                                                                                                                                                               |
| Credentials  | Guardrail code has no separate secret store, so a key in the code is readable by anyone with access to the evaluator. Put keys in a proxy in front of each decision service, and call the proxy from the code. The proxy holds the key and must reject every other caller; because the code cannot send a credential, that restriction has to be network-level, such as allowing only the addresses the sandbox calls from. |
| Publish      | Code changes stay in the working draft until **Publish**. Attach the guardrail after publishing.                                                                                                                                                                                                                                                                                                                            |

## Open Policy Agent

Open Policy Agent (OPA) is a general-purpose policy engine: rules are written in Rego, and the server returns a decision for the input it receives over HTTP. See the [OPA documentation](https://www.openpolicyagent.org/docs) for installation and policy authoring.

Start the server and load the policy. The data API listens on port 8181 by default:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
opa run --server orq/guardrail.rego
```

`opa run --server` accepts unauthenticated writes to `/v1/policies` and `/v1/data`, so a policy engine the sandbox can reach directly can also be rewritten by anyone who can reach it, including other guardrail authors, whose code runs in the same sandbox. Run it behind a proxy that accepts sandbox traffic only and forwards just the decision query (`POST /v1/data/orq/guardrail/allowed`), rejecting every other path and verb, in particular writes to `/v1/policies` and `/v1/data`, as described in [Requirements](#requirements); the guardrail calls the proxy, which holds the credential.

The policy denies the text the guardrail sends when it matches an injection pattern. `is_string` makes a query without text a denial, and `default` keeps the query answerable when the rule body is undefined.

```text orq/guardrail.rego theme={"theme":{"light":"github-light","dark":"github-dark"}}
package orq.guardrail

default allowed = false

allowed if {
    is_string(input.text)
    not contains(lower(input.text), "ignore previous instructions")
}
```

* Package and rule names map to the query path: `data.orq.guardrail.allowed` is served at `/v1/data/orq/guardrail/allowed`.
* The rule uses the OPA 1.0 syntax. On an earlier version, add `import future.keywords.if`.

Guardrail code, calling the policy engine through the authenticated proxy:

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def evaluate(log):
    import requests

    try:
        text = log["output"] or log["input"]  # output on an output guardrail, input otherwise
        response = requests.post(
            "https://opa-proxy.example/v1/data/orq/guardrail/allowed",
            json={"input": {"text": text}},
            timeout=2,
        )
        response.raise_for_status()
        return response.json()["result"] is True
    except (requests.RequestException, KeyError, ValueError, TypeError):
        return False  # block when the policy cannot be reached
```

Pass condition: **Boolean** with **True**. The generation is blocked when the policy returns `False`.

Any engine that answers over HTTP follows the same shape. Swap the URL, the request body, and the field the verdict is read from.

## Third-party guardrail providers

Map the provider response onto the pass condition. A number guardrail passes at or above the threshold, so a risk score, where a higher value means riskier, needs normalizing to a 0 to 1 range and then inverting; a safety score, where a higher value means safer, needs only normalizing. Check the provider's score direction before deciding whether to invert. Call the provider through the proxy described in [Requirements](#requirements), and swap the proxy host, the request body, and the score field for the provider's own.

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def evaluate(log):
    import requests

    try:
        text = log["output"] or log["input"]
        response = requests.post(
            "https://guard-proxy.example/v1/guard",
            json={"text": text, "model": "guard-standard"},
            timeout=2,
        )
        response.raise_for_status()
        score = response.json()["results"][0]["score"]  # this provider scores 0 to 100
        if isinstance(score, bool) or not isinstance(score, (int, float)) or not 0 <= score <= 100:
            return -1.0  # a score outside the provider contract blocks
    except (requests.RequestException, KeyError, ValueError, IndexError, TypeError):
        return -1.0  # below any threshold above zero, so an unreachable provider blocks

    return 1.0 - score / 100.0
```

The proxy attaches the provider's API key, so no credential lives in the guardrail code.

Pass condition: **Number** with a threshold above zero, so the `-1.0` failure value blocks when the provider cannot be reached. A provider that returns `True` or `False` in its own field can be returned directly with a **Boolean** pass condition.

## Test

1. Test the code in the **Playground** panel of the guardrail.
2. Click **Publish**.
3. Attach the guardrail on the target surface.
4. Send traffic and inspect the results in [Traces](/docs/ai-studio/observability/traces).
