> ## 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](/ai-gateway/configuration/guardrails#python-guardrail) that calls the service with `requests`. [System Guardrails](/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](/ai-gateway/configuration/guardrail-rules)                                                                  | Per guardrail in the rule: **Input**, **Output**, or **Both** |
| Agents         | Attach the Python Evaluator under [Configure Evaluators and Guardrails](/ai-studio/ai-engineering/build-agents#configure-evaluators-and-guardrails) | Per entry: **Input** or **Output**                            |
| Deployments    | Attach the Python Evaluator under [Evaluators and Guardrails](/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](/ai-gateway/configuration/guardrails#python-guardrail). Output checks, including external ones, do not run on streaming responses. See [Execution behavior](/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: `400` on the Responses endpoint and on the OpenAI-compatible chat completions endpoint, and `422` on the Anthropic- and Google-compatible endpoints and on the Agents and Deployments surfaces. A guardrail that times out returns `408` on the **AI Gateway** endpoints and on Deployments; on the Agents surface a guardrail that cannot run, including a timeout, returns `502 guardrail_execution_failed`. See [Guardrail Error Response](/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 guardrail reference in the request, up to 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. See [Guardrail timeout](/ai-gateway/configuration/guardrails#timeout).                            |
| 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.

### Named providers

Each framework runs as a service, and the check reaches it over HTTP. Expand a framework for what runs where, the endpoint the proxy must expose, the guardrail code, and the pass condition to configure.

<AccordionGroup>
  <Accordion title="Guardrails AI, self-hosted with its validation server" icon="https://mintcdn.com/orqai/l3RwwT_YmAjTyPs0/images/logos/guardrails-ai.svg?fit=max&auto=format&n=l3RwwT_YmAjTyPs0&q=85&s=754c629cea7492d8938f6af0defd7df6" width="460" height="460" data-path="images/logos/guardrails-ai.svg">
    **What runs where.** The framework validates text against a Guard, and the Guardrails server hosts guards behind an HTTP API. Run that server, or reuse one already deployed, wherever the **Orq.ai** sandbox can reach it, and the guardrail calls it.

    **What the proxy must expose.** The server's own validate endpoint, with the guard name in the path and the text in `llmOutput`. The server answers with `validationPassed`, a boolean:

    | Element         | Value                               |
    | --------------- | ----------------------------------- |
    | Method and path | `POST /guards/{guardName}/validate` |
    | Request body    | `{ "llmOutput": "<text>" }`         |
    | Response field  | `validationPassed`                  |

    The proxy holds the server's credential, so the guardrail sends none. See the [Guardrails server REST API](https://guardrailsai.com/docs/guardrails_server_api) for the full schema.

    **Guardrail code.** Set the pass condition to **Boolean** with **True**.

    ```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://guardrails-proxy.example/guards/orq_guardrail/validate",
                json={"llmOutput": text},
                timeout=2,
            )
            response.raise_for_status()
            passed = response.json()["validationPassed"]  # the guard's outcome for the text
            if not isinstance(passed, bool):
                return False  # an outcome outside the expected shape blocks
        except (requests.RequestException, KeyError, ValueError, TypeError):
            return False  # block when the guard cannot be reached or answers unexpectedly

        return passed
    ```

    **Failure modes.** A guard that cannot be reached, a response missing `validationPassed`, and a `validationPassed` that is not a boolean all return `False`. The guard name is part of the URL, so a renamed guard turns every call into a blocking failure rather than a silent pass.
  </Accordion>

  <Accordion title="Lakera, hosted Guard API" icon="https://mintcdn.com/orqai/l3RwwT_YmAjTyPs0/images/logos/lakera.svg?fit=max&auto=format&n=l3RwwT_YmAjTyPs0&q=85&s=3ef12bd27bbe02e080f9aaafdfb8759b" width="20" height="20" data-path="images/logos/lakera.svg">
    **What runs where.** Nothing to deploy: the Guard API is hosted, and the proxy in front of it holds the API key. The project the request names selects the policy that screens it.

    **Before the example runs.** Two project settings decide whether the guardrail can block at all. With the `detect` action, Lakera reports detections in `breakdown` and returns `flagged` as `false` for every request, so no guardrail can act on it. The project must screen with the `enforce` action. The role on the message decides which detectors apply, so model output is sent as `assistant` and input as `user`.

    | Element         | Value                                                                              |
    | --------------- | ---------------------------------------------------------------------------------- |
    | Method and path | `POST /v2/guard`                                                                   |
    | Request body    | `messages: [{ "role": "assistant" \| "user", "content": "<text>" }]`, `project_id` |
    | Response fields | `flagged`, `action`                                                                |

    See the [Guard API reference](https://docs.lakera.ai) for the full schema and the project settings.

    **Guardrail code.** Set the pass condition to **Boolean** with **True**.

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

        try:
            text = log["output"] or log["input"]
            role = "assistant" if log["output"] else "user"  # screen model output as output
            response = requests.post(
                "https://lakera-proxy.example/v2/guard",
                json={
                    "messages": [{"role": role, "content": text}],
                    "project_id": "project_01J...",  # the project whose policy screens this traffic
                },
                timeout=2,
            )
            response.raise_for_status()
            verdict = response.json()
            if verdict.get("action") != "enforce" or not isinstance(verdict["flagged"], bool):
                return False  # a project in detect mode never flags, so it cannot block
        except (requests.RequestException, KeyError, ValueError, TypeError):
            return False  # block when the provider cannot be reached or answers unexpectedly

        return not verdict["flagged"]
    ```

    **Failure modes.** A project that is not in `enforce` mode, a missing `flagged` field, a non-boolean `flagged`, and an unreachable proxy all block. The check on `action` is what keeps the guardrail honest: without it, a project switched to `detect` would silently allow everything the provider detects.
  </Accordion>
</AccordionGroup>

## 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](/ai-studio/observability/traces).
