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

# Python SDK reference

> Every public class, function and method in promptguard-sdk 2.4.2, generated from the published package

> [!NOTE]
>
> Generated from **promptguard-sdk 2.4.2** as published on PyPI (sha256 `0fae2f792594…`). Signatures, parameters and descriptions are read from the package itself, so this page changes only when the pinned SDK version does. For a walkthrough, start with the [guide](https://docs.promptguard.co/guides/python-sdk.md).

```bash theme={"system"}
pip install promptguard-sdk==2.4.2
```

## `promptguard`

PromptGuard Python SDK

```text theme={"system"}
Drop-in security for AI applications.
Just change your base URL and add an API key.

Usage (proxy mode - existing):

    from promptguard import PromptGuard

    pg = PromptGuard(api_key="pg_live_xxx")
    response = pg.chat.completions.create(
        model="gpt-5-nano",
        messages=[{"role": "user", "content": "Hello!"}]
    )

Usage (auto-instrumentation - new):

    import promptguard
    promptguard.init(api_key="pg_live_xxx")

    # Now ALL LLM calls are secured, regardless of framework.
    from openai import OpenAI
    client = OpenAI()
    client.chat.completions.create(...)  # scanned by PromptGuard
```

### `Config`

Class · `from promptguard import Config`

```python theme={"system"}
@dataclass
class Config
```

Configuration for the proxy `PromptGuard` client.

```text theme={"system"}
``timeout`` defaults to ``30.0`` seconds here (vs ``10.0`` for the
standalone Guard scan path / ``GuardClient`` / ``init()``): the proxy fronts
the full upstream LLM call, which is inherently slower than an isolated
Guard scan, so it needs a longer ceiling.
```

**Fields**

* `api_key: str = field(repr=False)`
* `base_url: str = 'https://api.promptguard.co/api/v1/proxy'`
* `max_retries: int = 3`
* `retry_delay: float = 1.0`
* `timeout: float = 30.0`

### `GuardApiError`

Class · `from promptguard import GuardApiError`

```python theme={"system"}
class GuardApiError(Exception)
```

Raised when the Guard API is unreachable or returns an error.

```text theme={"system"}
Only surfaced when ``fail_open=False``.  When ``fail_open=True``
(the default), callers catch this and return an allow decision.
```

#### `GuardApiError(...)`

```python theme={"system"}
GuardApiError(message: str, status_code: int | None = None)
```

| Parameter | Type | Default |
| - | - | - |
| `message` | `str` | required |
| `status_code` | `int \| None` | `None` |

### `GuardClient`

Class · `from promptguard import GuardClient`

```python theme={"system"}
class GuardClient
```

HTTP client for the PromptGuard Guard API.

```text theme={"system"}
Provides both sync and async methods.  The client itself never
swallows errors; callers (patches, integrations) decide whether
to fail open or closed based on configuration.

``timeout`` defaults to ``10.0`` seconds — the Guard scan is a fast,
standalone call.  (The proxy client / ``Config`` default is ``30.0`` because
it fronts the full upstream LLM call.)  ``api_key`` / ``base_url`` fall back
to ``PROMPTGUARD_API_KEY`` / ``PROMPTGUARD_BASE_URL`` when omitted.
```

#### `GuardClient(...)`

```python theme={"system"}
GuardClient(
    api_key: str | None = None,
    base_url: str | None = None,
    timeout: float = 10.0,
)
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `timeout` | `float` | `10.0` |

#### `GuardClient.scan`

```python theme={"system"}
def scan(
    messages: list[dict[str, str]],
    direction: str = 'input',
    model: str | None = None,
    context: dict[str, Any] | None = None,
) -> GuardDecision
```

Synchronous scan via the guard API.

| Parameter | Type | Default |
| - | - | - |
| `messages` | `list[dict[str, str]]` | required |
| `direction` | `str` | `'input'` |
| `model` | `str \| None` | `None` |
| `context` | `dict[str, Any] \| None` | `None` |

#### `GuardClient.scan_async`

```python theme={"system"}
async def scan_async(
    messages: list[dict[str, str]],
    direction: str = 'input',
    model: str | None = None,
    context: dict[str, Any] | None = None,
) -> GuardDecision
```

Asynchronous scan via the guard API.

| Parameter | Type | Default |
| - | - | - |
| `messages` | `list[dict[str, str]]` | required |
| `direction` | `str` | `'input'` |
| `model` | `str \| None` | `None` |
| `context` | `dict[str, Any] \| None` | `None` |

#### `GuardClient.close`

```python theme={"system"}
def close()
```

Close the sync client and best-effort close all async clients.

```text theme={"system"}
Safe to call from sync code (e.g. ``init()`` re-init and
``shutdown()``): each async client is closed on the loop it belongs
to if that loop is still running, otherwise via a throwaway loop, and
any failure is swallowed.
```

#### `GuardClient.aclose`

```python theme={"system"}
async def aclose()
```

Async close: awaits the current loop's client, best-effort for others.

### `GuardDecision`

Class · `from promptguard import GuardDecision`

```python theme={"system"}
class GuardDecision
```

Result of a guard API call.

**Properties**

* `blocked: bool`
* `redacted: bool`
* `allowed: bool`

#### `GuardDecision(...)`

```python theme={"system"}
GuardDecision(data: dict[str, Any])
```

| Parameter | Type | Default |
| - | - | - |
| `data` | `dict[str, Any]` | required |

### `PromptGuard`

Class · `from promptguard import PromptGuard`

```python theme={"system"}
class PromptGuard
```

PromptGuard client - Drop-in security for AI applications.

```text theme={"system"}
Usage::

    from promptguard import PromptGuard

    pg = PromptGuard(api_key="pg_live_xxx")
    response = pg.chat.completions.create(
        model="gpt-5-nano",
        messages=[{"role": "user", "content": "Hello!"}],
    )
    scan_result = pg.security.scan("Check this content")
```

#### `PromptGuard(...)`

```python theme={"system"}
PromptGuard(
    api_key: str | None = None,
    base_url: str | None = None,
    config: Config | None = None,
    timeout: float | None = None,
    max_retries: int | None = None,
    retry_delay: float | None = None,
)
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `config` | `Config \| None` | `None` |
| `timeout` | `float \| None` | `None` |
| `max_retries` | `int \| None` | `None` |
| `retry_delay` | `float \| None` | `None` |

#### `PromptGuard.close`

```python theme={"system"}
def close()
```

### `PromptGuard.chat`

Reached as `PromptGuard.chat` on an instance (a `Chat`; not constructed directly).

```python theme={"system"}
class Chat
```

### `PromptGuard.chat.completions`

Reached as `PromptGuard.chat.completions` on an instance (a `ChatCompletions`; not constructed directly).

```python theme={"system"}
class ChatCompletions
```

#### `PromptGuard.chat.completions.create`

```python theme={"system"}
def create(
    model: str,
    messages: list[dict[str, str]],
    temperature: float = 1.0,
    max_tokens: int | None = None,
    stream: bool = False,
    **kwargs,
) -> dict[str, Any] | Iterator[dict[str, Any]]
```

| Parameter | Type | Default |
| - | - | - |
| `model` | `str` | required |
| `messages` | `list[dict[str, str]]` | required |
| `temperature` | `float` | `1.0` |
| `max_tokens` | `int \| None` | `None` |
| `stream` | `bool` | `False` |
| `**kwargs` | | |

### `PromptGuard.security`

Reached as `PromptGuard.security` on an instance (a `Security`; not constructed directly).

```python theme={"system"}
class Security
```

#### `PromptGuard.security.scan`

```python theme={"system"}
def scan(content: str, content_type: str = 'prompt') -> 'SecurityScanResult'
```

| Parameter | Type | Default |
| - | - | - |
| `content` | `str` | required |
| `content_type` | `str` | `'prompt'` |

#### `PromptGuard.security.redact`

```python theme={"system"}
def redact(content: str, pii_types: list[str] | None = None) -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `content` | `str` | required |
| `pii_types` | `list[str] \| None` | `None` |

### `PromptGuard.agent`

Reached as `PromptGuard.agent` on an instance (a `Agent`; not constructed directly).

```python theme={"system"}
class Agent
```

#### `PromptGuard.agent.validate_tool`

```python theme={"system"}
def validate_tool(
    agent_id: str,
    tool_name: str,
    arguments: dict[str, Any],
    session_id: str | None = None,
) -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `agent_id` | `str` | required |
| `tool_name` | `str` | required |
| `arguments` | `dict[str, Any]` | required |
| `session_id` | `str \| None` | `None` |

#### `PromptGuard.agent.stats`

```python theme={"system"}
def stats(agent_id: str) -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `agent_id` | `str` | required |

### `PromptGuard.redteam`

Reached as `PromptGuard.redteam` on an instance (a `RedTeam`; not constructed directly).

```python theme={"system"}
class RedTeam
```

Run the adversarial corpus against your own policy configuration.

```text theme={"system"}
Every method here used to target ``/internal/redteam``, which requires
platform-admin auth -- so all of them returned 401 for every customer who
called them. The customer-facing plane is ``/api/v1/security-testing``,
reachable with an ordinary API key (``proxy`` scope).

Two methods were removed rather than repointed, because neither has a
customer-facing equivalent:

* ``run_autonomous`` -- the LLM-powered mutation loop. It takes a
  caller-supplied budget and costs real money per run, so it stays
  back-office rather than being exposed to any API key.
* ``intelligence_stats`` -- called ``/internal/redteam/intelligence/stats``,
  which exists on no plane at all. It was a 404 for admins too.
```

#### `PromptGuard.redteam.list_tests`

```python theme={"system"}
def list_tests() -> dict[str, Any]
```

#### `PromptGuard.redteam.run_test`

```python theme={"system"}
def run_test(test_name: str, target_preset: str = 'default') -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `test_name` | `str` | required |
| `target_preset` | `str` | `'default'` |

#### `PromptGuard.redteam.run_all`

```python theme={"system"}
def run_all(target_preset: str = 'default') -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `target_preset` | `str` | `'default'` |

#### `PromptGuard.redteam.run_custom`

```python theme={"system"}
def run_custom(prompt: str, target_preset: str = 'default') -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `prompt` | `str` | required |
| `target_preset` | `str` | `'default'` |

### `PromptGuardAsync`

Class · `from promptguard import PromptGuardAsync`

```python theme={"system"}
class PromptGuardAsync
```

Async PromptGuard client -- full parity with the sync client.

```text theme={"system"}
Usage::

    async with PromptGuardAsync(api_key="pg_live_xxx") as pg:
        resp = await pg.chat.completions.create(
            model="gpt-5-nano",
            messages=[{"role": "user", "content": "Hi"}],
        )
```

#### `PromptGuardAsync(...)`

```python theme={"system"}
PromptGuardAsync(
    api_key: str | None = None,
    base_url: str | None = None,
    config: Config | None = None,
    timeout: float | None = None,
    max_retries: int | None = None,
    retry_delay: float | None = None,
)
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `config` | `Config \| None` | `None` |
| `timeout` | `float \| None` | `None` |
| `max_retries` | `int \| None` | `None` |
| `retry_delay` | `float \| None` | `None` |

#### `PromptGuardAsync.close`

```python theme={"system"}
async def close()
```

### `PromptGuardAsync.chat`

Reached as `PromptGuardAsync.chat` on an instance (a `AsyncChat`; not constructed directly).

```python theme={"system"}
class AsyncChat
```

### `PromptGuardAsync.chat.completions`

Reached as `PromptGuardAsync.chat.completions` on an instance (a `AsyncChatCompletions`; not constructed directly).

```python theme={"system"}
class AsyncChatCompletions
```

#### `PromptGuardAsync.chat.completions.create`

```python theme={"system"}
async def create(
    model: str,
    messages: list[dict[str, str]],
    temperature: float = 1.0,
    max_tokens: int | None = None,
    stream: bool = False,
    **kwargs,
) -> dict[str, Any] | AsyncIterator[dict[str, Any]]
```

| Parameter | Type | Default |
| - | - | - |
| `model` | `str` | required |
| `messages` | `list[dict[str, str]]` | required |
| `temperature` | `float` | `1.0` |
| `max_tokens` | `int \| None` | `None` |
| `stream` | `bool` | `False` |
| `**kwargs` | | |

### `PromptGuardAsync.security`

Reached as `PromptGuardAsync.security` on an instance (a `AsyncSecurity`; not constructed directly).

```python theme={"system"}
class AsyncSecurity
```

#### `PromptGuardAsync.security.scan`

```python theme={"system"}
async def scan(content: str, content_type: str = 'prompt') -> 'SecurityScanResult'
```

| Parameter | Type | Default |
| - | - | - |
| `content` | `str` | required |
| `content_type` | `str` | `'prompt'` |

#### `PromptGuardAsync.security.redact`

```python theme={"system"}
async def redact(content: str, pii_types: list[str] | None = None) -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `content` | `str` | required |
| `pii_types` | `list[str] \| None` | `None` |

### `PromptGuardAsync.agent`

Reached as `PromptGuardAsync.agent` on an instance (a `AsyncAgent`; not constructed directly).

```python theme={"system"}
class AsyncAgent
```

#### `PromptGuardAsync.agent.validate_tool`

```python theme={"system"}
async def validate_tool(
    agent_id: str,
    tool_name: str,
    arguments: dict[str, Any],
    session_id: str | None = None,
) -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `agent_id` | `str` | required |
| `tool_name` | `str` | required |
| `arguments` | `dict[str, Any]` | required |
| `session_id` | `str \| None` | `None` |

#### `PromptGuardAsync.agent.stats`

```python theme={"system"}
async def stats(agent_id: str) -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `agent_id` | `str` | required |

### `PromptGuardAsync.redteam`

Reached as `PromptGuardAsync.redteam` on an instance (a `AsyncRedTeam`; not constructed directly).

```python theme={"system"}
class AsyncRedTeam
```

Async counterpart of :class:`RedTeam`; see it for why the paths moved and why `run_autonomous` / `intelligence_stats` are gone.

#### `PromptGuardAsync.redteam.list_tests`

```python theme={"system"}
async def list_tests() -> dict[str, Any]
```

#### `PromptGuardAsync.redteam.run_test`

```python theme={"system"}
async def run_test(test_name: str, target_preset: str = 'default') -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `test_name` | `str` | required |
| `target_preset` | `str` | `'default'` |

#### `PromptGuardAsync.redteam.run_all`

```python theme={"system"}
async def run_all(target_preset: str = 'default') -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `target_preset` | `str` | `'default'` |

#### `PromptGuardAsync.redteam.run_custom`

```python theme={"system"}
async def run_custom(prompt: str, target_preset: str = 'default') -> dict[str, Any]
```

| Parameter | Type | Default |
| - | - | - |
| `prompt` | `str` | required |
| `target_preset` | `str` | `'default'` |

### `PromptGuardBlockedError`

Class · `from promptguard import PromptGuardBlockedError`

```python theme={"system"}
class PromptGuardBlockedError(Exception)
```

Raised when PromptGuard blocks a request in enforce mode.

#### `PromptGuardBlockedError(...)`

```python theme={"system"}
PromptGuardBlockedError(decision: GuardDecision)
```

| Parameter | Type | Default |
| - | - | - |
| `decision` | `GuardDecision` | required |

### `PromptGuardError`

Class · `from promptguard import PromptGuardError`

```python theme={"system"}
class PromptGuardError(Exception)
```

Error from PromptGuard API

#### `PromptGuardError(...)`

```python theme={"system"}
PromptGuardError(
    message: str,
    code: str,
    status_code: int,
    *,
    error_type: str | None = None,
    upgrade_url: str | None = None,
    current_plan: str | None = None,
    requests_used: int | None = None,
    requests_limit: int | None = None,
)
```

| Parameter | Type | Default |
| - | - | - |
| `message` | `str` | required |
| `code` | `str` | required |
| `status_code` | `int` | required |
| `error_type` | `str \| None` | `None` |
| `upgrade_url` | `str \| None` | `None` |
| `current_plan` | `str \| None` | `None` |
| `requests_used` | `int \| None` | `None` |
| `requests_limit` | `int \| None` | `None` |

### `SecurityScanResult`

Class · `from promptguard import SecurityScanResult`

```python theme={"system"}
class SecurityScanResult(TypedDict)
```

Result of a `security.scan` call.

```text theme={"system"}
Mirrors the Node SDK's ``SecurityScanResult`` so the documented keys are
discoverable.  Declared ``total=False`` because the API only includes
threat metadata when something is detected.
```

**Fields**

* `blocked: bool`
* `decision: str`
* `reason: str`
* `threat_type: str`
* `confidence: float`

### `__version__`

Constant · `from promptguard import __version__`

```python theme={"system"}
__version__ = '2.4.2'
```

### `init`

Function · `from promptguard import init`

```python theme={"system"}
def init(
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    fail_open: bool = True,
    scan_responses: bool = False,
    timeout: float = 10.0,
) -> None
```

Initialise PromptGuard auto-instrumentation.

```text theme={"system"}
Prefer calling this **once at application startup**, before spawning worker
threads or issuing LLM calls. Re-initialising at runtime is supported and
thread-safe (the new client is swapped in before the old one is closed), but
an in-flight request already holding the previous client may see its
connection closed underneath it.

Parameters
----------
api_key:
    PromptGuard API key.  Falls back to ``PROMPTGUARD_API_KEY`` env var.
base_url:
    PromptGuard API base URL.  Falls back to ``PROMPTGUARD_BASE_URL``
    env var, then ``https://api.promptguard.co/api/v1``.
mode:
    ``"enforce"`` to block policy violations, ``"monitor"`` to log only.
fail_open:
    If ``True`` (default), allow LLM calls when the Guard API is
    unreachable.  Set to ``False`` to fail-closed.
scan_responses:
    If ``True``, also scan LLM responses through the Guard API with
    ``direction="output"``.  Defaults to ``False`` here (the zero-config
    drop-in) to avoid doubling Guard API round-trips per LLM call; the
    framework callback handlers default this to ``True`` because they
    already receive response events for free.
timeout:
    HTTP timeout (seconds) for Guard API calls.  Defaults to ``10.0``: the
    Guard scan is a fast, standalone call in the request path.  (The proxy
    client's default is ``30.0`` because it fronts the full upstream LLM
    call, which is inherently slower.)
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `fail_open` | `bool` | `True` |
| `scan_responses` | `bool` | `False` |
| `timeout` | `float` | `10.0` |

### `instrumentation_report`

Function · `from promptguard import instrumentation_report`

```python theme={"system"}
def instrumentation_report() -> dict
```

What is and is not instrumented, as data rather than a log line.

```text theme={"system"}
A startup warning is only read by someone watching the logs at startup.
This is the same information in a form a customer can assert in their own
CI::

    assert promptguard.instrumentation_report()["detected_unpatched"] == []

which turns "we told you" into "you cannot ship without knowing".

``middleware`` names the request-edge middleware guarding this process
(``"fastapi"`` for :class:`promptguard.integrations.fastapi.PromptGuardMiddleware`)
once it is wrapping an app, which with Starlette is from startup (or the
first request when lifespan is off). It is not a patch, so it never
appears in ``patched``.
```

### `is_active`

Function · `from promptguard import is_active`

```python theme={"system"}
def is_active() -> bool
```

Return `True` when auto-instrumentation is initialised (a guard client is installed). Note this is independent of whether any SDK was patchable — use :func:`patched_sdks` to confirm specific SDKs are instrumented.

### `patched_sdks`

Function · `from promptguard import patched_sdks`

```python theme={"system"}
def patched_sdks() -> list[str]
```

Return the names of the SDKs actually patched by the last `init()`.

```text theme={"system"}
Useful in tests/health checks to assert instrumentation is live, e.g.::

    promptguard.init(api_key=...)
    assert "openai" in promptguard.patched_sdks()

Returns an empty list before ``init()`` or after ``shutdown()``, or when no
supported SDK is importable in the current environment.
```

### `shutdown`

Function · `from promptguard import shutdown`

```python theme={"system"}
def shutdown() -> None
```

Remove all patches and close the guard client.

### `verify`

Function · `from promptguard import verify`

```python theme={"system"}
def verify(
    api_key: str | None = None,
    base_url: str | None = None,
    timeout: float | None = None,
    max_retries: int = 1,
    retry_delay: float = 0.25,
) -> dict[str, Any]
```

Run the integration checks and return what happened.

```text theme={"system"}
Credentials resolve exactly as they do everywhere else — explicit argument,
then ``PROMPTGUARD_API_KEY`` / ``PROMPTGUARD_BASE_URL`` from the
environment.

``max_retries`` defaults to 1 rather than the client's 3, and
``retry_delay`` to 0.25s rather than 1s: this is a diagnostic, so answering
fast beats riding out a blip. On the client's defaults a dead host takes
about seven seconds of backoff to report, which is a long time to stare at a
pre-flight check. Raise them if you would rather tolerate a flaky network
than get a false "unreachable".

Never raises for a failed check: a check that raised would be useless in the
place this is most needed, which is a CI step or a pre-flight script that
wants to report *all* the problems at once.  A missing API key is the one
exception — that is a caller error, not a finding, and it raises
``ValueError`` like the rest of the SDK.

Returns a dict with:

``ok``
    ``True`` when no check failed.  Warnings do not clear it.
``checks``
    One entry per check: ``name``, ``status`` (``pass``/``warn``/``fail``)
    and a human-readable ``detail``.
``checks_passed`` / ``checks_failed`` / ``checks_warned``
    Counts, mirroring the CLI's JSON output.
``instrumentation``
    The full :func:`promptguard.instrumentation_report`.
``base_url``, ``sdk_version``
    What was actually checked, so a report pasted into an issue is
    self-describing.

A **warn** means the request worked but the answer was not what a protected
setup should give — an injection that came back allowed, or a PII sample
with nothing detected.  That is a policy or configuration question rather
than a broken integration, which is why it does not clear ``ok``; it is
still the thing to look at before trusting the setup.
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `timeout` | `float \| None` | `None` |
| `max_retries` | `int` | `1` |
| `retry_delay` | `float` | `0.25` |

## `promptguard.integrations.langchain`

LangChain integration - PromptGuardCallbackHandler.

```text theme={"system"}
Implements LangChain's ``BaseCallbackHandler`` to scan prompts before
LLM calls and responses after, with rich context about chains, tools,
and agent steps.

Usage::

    from promptguard.integrations.langchain import PromptGuardCallbackHandler

    handler = PromptGuardCallbackHandler(api_key="pg_live_xxx")

    # Attach to a single LLM
    llm = ChatOpenAI(model="gpt-5-nano", callbacks=[handler])

    # Or use globally with any chain / agent
    chain.invoke({"input": "..."}, config={"callbacks": [handler]})

Every scan names its Conversation in ``session_id`` with the call's own thread
id: the ``thread_id`` LangGraph puts in every callback's metadata, or a
``thread_id`` / ``session_id`` / ``conversation_id`` set in
``config["metadata"]``. A call that names no thread sends no session id, unless
the handler was given a ``session_id`` of its own.
```

### `LangChainCallbackHandler`

Alias · `from promptguard.integrations.langchain import LangChainCallbackHandler`

```python theme={"system"}
LangChainCallbackHandler = PromptGuardCallbackHandler
```

Alias of `PromptGuardCallbackHandler`.

### `PromptGuardCallbackHandler`

Class · `from promptguard.integrations.langchain import PromptGuardCallbackHandler`

```python theme={"system"}
class PromptGuardCallbackHandler(_BaseCallbackHandler)
```

LangChain callback handler that scans LLM I/O via the Guard API.

```text theme={"system"}
A ``langchain_core`` ``BaseCallbackHandler`` when LangChain is installed;
the SDK itself still has no hard dependency on it.

``serialized`` is typed optional on every ``*_start`` callback: runnables,
and so every LangGraph graph and node, pass ``None`` there.
```

#### `PromptGuardCallbackHandler(...)`

```python theme={"system"}
PromptGuardCallbackHandler(
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    scan_responses: bool = True,
    fail_open: bool = True,
    timeout: float = 10.0,
    session_id: str | None = None,
)
```

Construct the handler.

```text theme={"system"}
Parameters
----------
mode:
    ``"enforce"`` to block policy violations, ``"monitor"`` to log only.
session_id:
    The Conversation to name when a call carries no thread id of its
    own -- for a handler that serves one conversation. A call's
    ``thread_id`` (or ``session_id`` / ``conversation_id``) in its
    metadata always wins. Without either, no session id is sent.
scan_responses:
    Defaults to ``True`` here — unlike ``promptguard.init()`` which
    defaults to ``False``. The callback already receives ``on_llm_end``
    events for free, so scanning responses adds no extra call surface
    and richer coverage is the expected behaviour when a user opts into
    a framework integration. ``init()`` stays conservative (off) because
    it is the zero-config drop-in and response scanning doubles the
    Guard API round-trips per LLM call.
timeout:
    HTTP timeout (seconds) for Guard API calls (default 10s, matching
    the Guard scan path; the proxy client uses 30s because it fronts the
    full upstream LLM call).
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `scan_responses` | `bool` | `True` |
| `fail_open` | `bool` | `True` |
| `timeout` | `float` | `10.0` |
| `session_id` | `str \| None` | `None` |

#### `PromptGuardCallbackHandler.on_llm_start`

```python theme={"system"}
def on_llm_start(
    serialized: dict[str, Any] | None,
    prompts: list[str],
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    tags: list[str] | None = None,
    metadata: dict[str, Any] | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `serialized` | `dict[str, Any] \| None` | required |
| `prompts` | `list[str]` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `tags` | `list[str] \| None` | `None` |
| `metadata` | `dict[str, Any] \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_chat_model_start`

```python theme={"system"}
def on_chat_model_start(
    serialized: dict[str, Any] | None,
    messages: list[list[Any]],
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    tags: list[str] | None = None,
    metadata: dict[str, Any] | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `serialized` | `dict[str, Any] \| None` | required |
| `messages` | `list[list[Any]]` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `tags` | `list[str] \| None` | `None` |
| `metadata` | `dict[str, Any] \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_llm_end`

```python theme={"system"}
def on_llm_end(
    response: Any,
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `response` | `Any` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_llm_error`

```python theme={"system"}
def on_llm_error(
    error: BaseException,
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `error` | `BaseException` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_chain_start`

```python theme={"system"}
def on_chain_start(
    serialized: dict[str, Any] | None,
    inputs: dict[str, Any],
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    tags: list[str] | None = None,
    metadata: dict[str, Any] | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `serialized` | `dict[str, Any] \| None` | required |
| `inputs` | `dict[str, Any]` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `tags` | `list[str] \| None` | `None` |
| `metadata` | `dict[str, Any] \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_chain_end`

```python theme={"system"}
def on_chain_end(
    outputs: dict[str, Any],
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `outputs` | `dict[str, Any]` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_chain_error`

```python theme={"system"}
def on_chain_error(
    error: BaseException,
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `error` | `BaseException` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_tool_start`

```python theme={"system"}
def on_tool_start(
    serialized: dict[str, Any] | None,
    input_str: str,
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    tags: list[str] | None = None,
    metadata: dict[str, Any] | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `serialized` | `dict[str, Any] \| None` | required |
| `input_str` | `str` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `tags` | `list[str] \| None` | `None` |
| `metadata` | `dict[str, Any] \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_tool_end`

```python theme={"system"}
def on_tool_end(
    output: Any,
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `output` | `Any` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_tool_error`

```python theme={"system"}
def on_tool_error(
    error: BaseException,
    *,
    run_id: UUID | None = None,
    parent_run_id: UUID | None = None,
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `error` | `BaseException` | required |
| `run_id` | `UUID \| None` | `None` |
| `parent_run_id` | `UUID \| None` | `None` |
| `**kwargs` | `Any` | |

### `PromptGuardRunnable`

Class · `from promptguard.integrations.langchain import PromptGuardRunnable`

```python theme={"system"}
class PromptGuardRunnable
```

Inline LCEL guard that scans (and can redact) content mid-chain.

```text theme={"system"}
Unlike :class:`PromptGuardCallbackHandler`, which only *observes* LLM I/O
and therefore can block but never rewrite it, this guard sits **in** the
data flow. That lets it honor a ``redact`` decision by returning the
redacted value, in addition to raising :class:`PromptGuardBlockedError` on a
``block`` decision. Compose it into any LCEL chain with ``|``::

    from promptguard.integrations.langchain import PromptGuardRunnable

    guard = PromptGuardRunnable(api_key="pg_live_xxx").as_runnable()
    chain = guard | prompt | llm            # scan the user input
    chain.invoke("user question")

Use ``direction="output"`` to guard an LLM/parser result at the tail of a
chain::

    output_guard = PromptGuardRunnable(direction="output").as_runnable()
    chain = prompt | llm | StrOutputParser() | output_guard

Redaction is applied in place for ``str``, ``dict`` (the first recognized
text field), and ``list[dict]`` message inputs. For any other value type a
``redact`` decision cannot be safely rewritten, so — mirroring the callback
posture — enforce mode blocks and monitor mode warns and passes through.
```

#### `PromptGuardRunnable(...)`

```python theme={"system"}
PromptGuardRunnable(
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    direction: str = 'input',
    fail_open: bool = True,
    timeout: float = 10.0,
    session_id: str | None = None,
)
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `direction` | `str` | `'input'` |
| `fail_open` | `bool` | `True` |
| `timeout` | `float` | `10.0` |
| `session_id` | `str \| None` | `None` |

#### `PromptGuardRunnable.guard_value`

```python theme={"system"}
def guard_value(value: Any, config: dict[str, Any] | None = None) -> Any
```

Scan `value`; return it (possibly redacted) or raise on a block.

```text theme={"system"}
This is the plain callable behind :meth:`as_runnable` — usable directly
(e.g. in a ``RunnableLambda`` you build yourself) without importing
LangChain. ``config`` is the call's ``RunnableConfig``, which
``RunnableLambda`` passes in: its ``configurable["thread_id"]`` -- or a
``thread_id`` / ``session_id`` / ``conversation_id`` in its
``metadata`` -- names the Conversation, before the guard's own
``session_id``.
```

| Parameter | Type | Default |
| - | - | - |
| `value` | `Any` | required |
| `config` | `dict[str, Any] \| None` | `None` |

#### `PromptGuardRunnable.as_runnable`

```python theme={"system"}
def as_runnable() -> Any
```

Wrap :meth:`guard_value` in a `langchain_core` `RunnableLambda`.

## `promptguard.integrations.llamaindex`

LlamaIndex integration - PromptGuardCallbackHandler for LlamaIndex.

```text theme={"system"}
Hooks into LlamaIndex's callback system to scan LLM prompts and responses
with rich pipeline context.

Usage::

    from promptguard.integrations.llamaindex import PromptGuardCallbackHandler
    from llama_index.core.callbacks import CallbackManager

    pg_handler = PromptGuardCallbackHandler(api_key="pg_live_xxx")
    callback_manager = CallbackManager([pg_handler])

    from llama_index.core import Settings
    Settings.callback_manager = callback_manager

LlamaIndex's callback events name no conversation -- each carries only its own
event id -- so no session id is sent unless one is passed: give a chat
engine's handler (or query guard) the ``session_id`` of the conversation it
serves, and every scan it makes names that Conversation.
```

### `LlamaIndexCallbackHandler`

Alias · `from promptguard.integrations.llamaindex import LlamaIndexCallbackHandler`

```python theme={"system"}
LlamaIndexCallbackHandler = PromptGuardCallbackHandler
```

Alias of `PromptGuardCallbackHandler`.

### `PromptGuardCallbackHandler`

Class · `from promptguard.integrations.llamaindex import PromptGuardCallbackHandler`

```python theme={"system"}
class PromptGuardCallbackHandler
```

LlamaIndex callback handler that scans LLM I/O via the Guard API.

```text theme={"system"}
Implements the LlamaIndex ``BaseCallbackHandler`` interface without
importing LlamaIndex at module level.
```

#### `PromptGuardCallbackHandler(...)`

```python theme={"system"}
PromptGuardCallbackHandler(
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    scan_responses: bool = True,
    fail_open: bool = True,
    timeout: float = 10.0,
    event_starts_to_ignore: list[str] | None = None,
    event_ends_to_ignore: list[str] | None = None,
    session_id: str | None = None,
)
```

Construct the handler.

```text theme={"system"}
Parameters
----------
mode:
    ``"enforce"`` to block policy violations, ``"monitor"`` to log only.
session_id:
    The Conversation this handler's scans belong to. LlamaIndex events
    name none, so without it no session id is sent.
scan_responses:
    Defaults to ``True`` here — unlike ``promptguard.init()`` which
    defaults to ``False``. The callback already receives ``on_event_end``
    events for free, so scanning responses adds no extra call surface and
    richer coverage is the expected behaviour when opting into a
    framework integration. ``init()`` stays conservative (off) because it
    is the zero-config drop-in and response scanning doubles the Guard
    API round-trips per LLM call.
timeout:
    HTTP timeout (seconds) for Guard API calls (default 10s, matching the
    Guard scan path; the proxy client uses 30s because it fronts the full
    upstream LLM call).
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `scan_responses` | `bool` | `True` |
| `fail_open` | `bool` | `True` |
| `timeout` | `float` | `10.0` |
| `event_starts_to_ignore` | `list[str] \| None` | `None` |
| `event_ends_to_ignore` | `list[str] \| None` | `None` |
| `session_id` | `str \| None` | `None` |

#### `PromptGuardCallbackHandler.on_event_start`

```python theme={"system"}
def on_event_start(
    event_type: str,
    payload: dict[str, Any] | None = None,
    event_id: str = '',
    parent_id: str = '',
    **kwargs: Any,
) -> str
```

| Parameter | Type | Default |
| - | - | - |
| `event_type` | `str` | required |
| `payload` | `dict[str, Any] \| None` | `None` |
| `event_id` | `str` | `''` |
| `parent_id` | `str` | `''` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.on_event_end`

```python theme={"system"}
def on_event_end(
    event_type: str,
    payload: dict[str, Any] | None = None,
    event_id: str = '',
    **kwargs: Any,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `event_type` | `str` | required |
| `payload` | `dict[str, Any] \| None` | `None` |
| `event_id` | `str` | `''` |
| `**kwargs` | `Any` | |

#### `PromptGuardCallbackHandler.start_trace`

```python theme={"system"}
def start_trace(trace_id: str | None = None) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `trace_id` | `str \| None` | `None` |

#### `PromptGuardCallbackHandler.end_trace`

```python theme={"system"}
def end_trace(
    trace_id: str | None = None,
    trace_map: dict[str, list[str]] | None = None,
) -> None
```

| Parameter | Type | Default |
| - | - | - |
| `trace_id` | `str \| None` | `None` |
| `trace_map` | `dict[str, list[str]] \| None` | `None` |

### `PromptGuardQueryGuard`

Class · `from promptguard.integrations.llamaindex import PromptGuardQueryGuard`

```python theme={"system"}
class PromptGuardQueryGuard
```

Inline LlamaIndex guard that scans (and can redact) a query string.

```text theme={"system"}
Unlike :class:`PromptGuardCallbackHandler`, which only *observes* events and
can therefore block but never rewrite them, this guard is a pipeline
component that sits **in** the data flow. It scans the query before
retrieval/synthesis, raising :class:`PromptGuardBlockedError` on a ``block``
decision and returning the redacted query on a ``redact`` decision. Drop it
at the head of a ``QueryPipeline``::

    from promptguard.integrations.llamaindex import PromptGuardQueryGuard
    from llama_index.core.query_pipeline import QueryPipeline

    guard = PromptGuardQueryGuard(api_key="pg_live_xxx")
    pipeline = QueryPipeline(chain=[guard.as_query_component(), retriever])
    pipeline.run(input="user question")

``guard_query`` is also usable directly as a plain preprocessor function
(no LlamaIndex import required) if you are not building a ``QueryPipeline``.
```

#### `PromptGuardQueryGuard(...)`

```python theme={"system"}
PromptGuardQueryGuard(
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    fail_open: bool = True,
    timeout: float = 10.0,
    session_id: str | None = None,
)
```

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `fail_open` | `bool` | `True` |
| `timeout` | `float` | `10.0` |
| `session_id` | `str \| None` | `None` |

#### `PromptGuardQueryGuard.guard_query`

```python theme={"system"}
def guard_query(query_str: str) -> str
```

Scan `query_str`; return it (possibly redacted) or raise on a block.

| Parameter | Type | Default |
| - | - | - |
| `query_str` | `str` | required |

#### `PromptGuardQueryGuard.as_query_component`

```python theme={"system"}
def as_query_component() -> Any
```

Wrap :meth:`guard_query` in a LlamaIndex `FnComponent`.

## `promptguard.integrations.crewai`

CrewAI integration - guardrail function and tool wrapper.

```text theme={"system"}
Usage::

    from crewai import Crew
    from promptguard.integrations.crewai import PromptGuardGuardrail

    pg = PromptGuardGuardrail(api_key="pg_live_xxx")

    crew = Crew(
        agents=[...],
        tasks=[...],
        before_kickoff_callbacks=[pg.before_kickoff],
        after_kickoff_callbacks=[pg.after_kickoff],
    )

    # The fields are lists. ``Crew`` ignores unknown keyword arguments, so
    # ``before_kickoff=...`` would be accepted and register nothing.

    # Or wrap individual tools:
    from promptguard.integrations.crewai import secure_tool

    @secure_tool(api_key="pg_live_xxx")
    class MyTool(BaseTool):
        ...
```

### `PromptGuardGuardrail`

Class · `from promptguard.integrations.crewai import PromptGuardGuardrail`

```python theme={"system"}
class PromptGuardGuardrail
```

CrewAI guardrail that scans inputs/outputs via the Guard API.

#### `PromptGuardGuardrail(...)`

```python theme={"system"}
PromptGuardGuardrail(
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    fail_open: bool = True,
    timeout: float = 10.0,
    session_id: str | None = None,
)
```

`session_id` names the Conversation the crew's scans belong to. CrewAI has no conversation id of its own, so without one none is sent.

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `fail_open` | `bool` | `True` |
| `timeout` | `float` | `10.0` |
| `session_id` | `str \| None` | `None` |

#### `PromptGuardGuardrail.before_kickoff`

```python theme={"system"}
def before_kickoff(inputs: dict[str, Any]) -> dict[str, Any]
```

Scan crew inputs before kickoff.

| Parameter | Type | Default |
| - | - | - |
| `inputs` | `dict[str, Any]` | required |

#### `PromptGuardGuardrail.after_kickoff`

```python theme={"system"}
def after_kickoff(result: Any) -> Any
```

Scan crew output after kickoff.

| Parameter | Type | Default |
| - | - | - |
| `result` | `Any` | required |

#### `PromptGuardGuardrail.scan_task_output`

```python theme={"system"}
def scan_task_output(output: str, task_name: str = 'unknown') -> str
```

Scan an individual task's output.

| Parameter | Type | Default |
| - | - | - |
| `output` | `str` | required |
| `task_name` | `str` | `'unknown'` |

### `secure_tool`

Function · `from promptguard.integrations.crewai import secure_tool`

```python theme={"system"}
def secure_tool(
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    fail_open: bool = True,
) -> Callable
```

Decorator to wrap a CrewAI tool's `_run` method with PromptGuard.

| Parameter | Type | Default |
| - | - | - |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `fail_open` | `bool` | `True` |

## `promptguard.integrations.fastapi`

ASGI middleware - scans prompt-shaped request bodies at the app's edge.

```text theme={"system"}
Works with FastAPI, Starlette, or any ASGI app. It imports neither framework.

Usage::

    from fastapi import FastAPI
    from promptguard.integrations.fastapi import PromptGuardMiddleware

    app = FastAPI()
    app.add_middleware(
        PromptGuardMiddleware,
        api_key="pg_live_xxx",          # or PROMPTGUARD_API_KEY
        paths=["/v1/chat"],             # omit to scan every path
        exclude_paths=["/v1/chat/feedback"],
    )

For each request with a body on a selected path, the middleware buffers the
body, pulls out its prompt-shaped fields (see :data:`DEFAULT_PROMPT_FIELDS`),
scans them with one ``direction="input"`` Guard API call, and either answers
before the app runs or replays the original bytes to the app unchanged.

This is the choice for an app that **accepts** prompts but calls the LLM
somewhere auto-instrumentation does not patch. With ``promptguard.init()`` also
on, the same text is scanned twice — once here, once at the provider call.

The scan is the async Guard client's ``scan_async``, and a body larger than
:data:`OFF_LOOP_BODY_BYTES` is decoded and parsed in a worker thread, so
nothing here holds the event loop while a request is inspected.
```

### `extract_scan`

Function · `from promptguard.integrations.fastapi import extract_scan`

```python theme={"system"}
def extract_scan(body: Any, fields: Sequence[str]) -> _Scan
```

What to scan in a parsed body (an object, or a top-level array).

| Parameter | Type | Default |
| - | - | - |
| `body` | `Any` | required |
| `fields` | `Sequence[str]` | required |

### `PromptGuardMiddleware`

Class · `from promptguard.integrations.fastapi import PromptGuardMiddleware`

```python theme={"system"}
class PromptGuardMiddleware
```

Raw ASGI middleware that scans prompt-shaped request bodies.

```text theme={"system"}
Parameters
----------
app:
    The ASGI app to wrap. ``app.add_middleware(PromptGuardMiddleware, ...)``
    passes it for you.
api_key, base_url:
    Fall back to ``PROMPTGUARD_API_KEY`` / ``PROMPTGUARD_BASE_URL``.
mode:
    ``"enforce"`` (default) answers a block with ``403``; ``"monitor"`` logs
    it and lets the request through.
fail_open:
    Governs a Guard API that cannot answer: a transport error, a timeout, a
    5xx or a 429 (and a 401/403, which is also logged at ``ERROR`` once).
    ``True`` (default, ADR-0002) lets the request through unscanned and logs
    a ``WARNING`` with ``extra={"promptguard_event":
    "guard_unavailable_fail_open"}``; ``False`` answers ``503``. A Guard
    ``400``/``413``/``422`` is caused by the request itself and is refused
    in every mode: ``413`` for a size error, ``400`` otherwise.
timeout:
    Guard API timeout in seconds (default ``10.0``).
paths:
    Path prefixes to scan; ``None`` (default) scans every path, and an empty
    list is refused. Matching is on whole segments of the app's route path
    (``scope["path"]`` less ``root_path``, as Starlette routes it):
    ``"/v1/chat"`` covers ``/v1/chat`` and ``/v1/chat/completions`` but not
    ``/v1/chatter``.
exclude_paths:
    Path prefixes never scanned, matched the same way. An exclusion wins
    over ``paths``. An excluded request's body is not even buffered.
methods:
    HTTP methods to scan; ``None`` (default) scans any method that carries
    a body, ``GET`` and ``DELETE`` included.
prompt_fields:
    Top-level fields to scan; see :data:`DEFAULT_PROMPT_FIELDS`.
max_body_bytes:
    The largest body buffered for a scan, raw or decompressed; see
    :data:`DEFAULT_MAX_BODY_BYTES`. Over it the request is answered
    ``413`` and neither scanned nor passed to the app.
session_id:
    A function of the ASGI ``scope`` returning the Conversation the request
    is a turn of, or ``None``. Read it from the authenticated session your
    auth middleware put in the scope (``scope["user"]``,
    ``scope["session"]``, ``scope["state"]``), not from anything the
    client controls. A function that raises is logged and the request is
    scanned without a session id.
session_id_header:
    Shorthand for a ``session_id`` that reads one request header, e.g.
    ``"X-Conversation-Id"``. Name only a header your own auth layer or a
    trusted proxy sets and strips from client requests: a value a browser
    can choose lets a client pick which Conversation its turns land in.
    Not both at once.

A request is not a conversation by itself, so with neither (the default),
or when they name none, no session id is sent. An id over 255 characters
is dropped.

Bodies are read by ``Content-Type``: JSON (in its declared charset),
``text/plain`` (scanned whole, as one user message) and
``application/x-www-form-urlencoded`` (its fields). ``gzip`` and
``deflate`` bodies are decompressed first. ``multipart/form-data`` and
other media are passed on unscanned. In enforce mode a body that cannot
be read — an unknown ``Content-Encoding`` (``415``) or charset (``415``),
undecodable text or invalid JSON (``400``) — is refused; monitor mode logs
it and passes it on.

A redact decision cannot be applied to an arbitrary body, so enforce mode
refuses it with ``403`` and monitor mode logs it. That is the rule ADR-0003
sets for a patched call auto-instrumentation cannot rewrite, applied here
by analogy.
```

#### `PromptGuardMiddleware(...)`

```python theme={"system"}
PromptGuardMiddleware(
    app: ASGIApp,
    *,
    api_key: str | None = None,
    base_url: str | None = None,
    mode: str = 'enforce',
    fail_open: bool = True,
    timeout: float = 10.0,
    paths: Iterable[str] | None = None,
    exclude_paths: Iterable[str] | None = None,
    methods: Iterable[str] | None = None,
    prompt_fields: Sequence[str] = DEFAULT_PROMPT_FIELDS,
    max_body_bytes: int = DEFAULT_MAX_BODY_BYTES,
    session_id: Callable[[Scope], str | None] | None = None,
    session_id_header: str | None = None,
)
```

| Parameter | Type | Default |
| - | - | - |
| `app` | `ASGIApp` | required |
| `api_key` | `str \| None` | `None` |
| `base_url` | `str \| None` | `None` |
| `mode` | `str` | `'enforce'` |
| `fail_open` | `bool` | `True` |
| `timeout` | `float` | `10.0` |
| `paths` | `Iterable[str] \| None` | `None` |
| `exclude_paths` | `Iterable[str] \| None` | `None` |
| `methods` | `Iterable[str] \| None` | `None` |
| `prompt_fields` | `Sequence[str]` | `DEFAULT_PROMPT_FIELDS` |
| `max_body_bytes` | `int` | `DEFAULT_MAX_BODY_BYTES` |
| `session_id` | `Callable[[Scope], str \| None] \| None` | `None` |
| `session_id_header` | `str \| None` | `None` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.