Metadata-Version: 2.4
Name: guardtrellis
Version: 0.1.7
Summary: Provider-independent local checks for model inputs, outputs, retrieval, and tool calls
Project-URL: Source, https://github.com/sahilmathur254/guardtrellis
Project-URL: Issues, https://github.com/sahilmathur254/guardtrellis/issues
Project-URL: Documentation, https://github.com/sahilmathur254/guardtrellis/blob/main/docs/api.md
Project-URL: Changelog, https://github.com/sahilmathur254/guardtrellis/blob/main/CHANGELOG.md
Project-URL: Roadmap, https://github.com/users/sahilmathur254/projects/3
Author: Sahil Mathur
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: <3.15,>=3.11
Requires-Dist: jsonschema<5,>=4.23
Requires-Dist: referencing<0.38,>=0.35
Provides-Extra: fastapi
Requires-Dist: fastapi<1,>=0.115; extra == 'fastapi'
Requires-Dist: httpx<1,>=0.27; extra == 'fastapi'
Requires-Dist: uvicorn<1,>=0.30; extra == 'fastapi'
Provides-Extra: gemini
Requires-Dist: google-genai<3,>=2.25; extra == 'gemini'
Requires-Dist: httpx<1,>=0.28.1; extra == 'gemini'
Provides-Extra: langgraph
Requires-Dist: langgraph<2,>=1; extra == 'langgraph'
Requires-Dist: langsmith<1,>=0.3; extra == 'langgraph'
Provides-Extra: openai
Requires-Dist: httpx2<3,>=2.12; extra == 'openai'
Requires-Dist: openai<4,>=3; extra == 'openai'
Description-Content-Type: text/markdown

# GuardTrellis

<p align="center">
  <img src="https://raw.githubusercontent.com/sahilmathur254/guardtrellis/main/docs/assets/guardtrellis-logo.png" alt="GuardTrellis logo: a woven shield with a green check mark" width="360">
</p>

A small Python SDK for local checks around model inputs, complete outputs, retrieved text,
and proposed tool calls. Policies are explicit, composable, and independent of model providers.

[![PyPI Downloads](https://static.pepy.tech/personalized-badge/guardtrellis?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/guardtrellis)

The documented public API is supported across `0.1.x` under the
[compatibility policy](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/compatibility.md).
Supported Python: 3.11–3.14. Apache-2.0 licensed.

<!-- guardtrellis-release:start -->
**Version: `0.1.7`.** See [PyPI](https://pypi.org/project/guardtrellis/0.1.7/)
for availability and [release notes](https://github.com/sahilmathur254/guardtrellis/releases).
A source checkout may describe a candidate that has not been published yet.

Install this version in an activated virtual environment:

```sh
python -m pip install 'guardtrellis==0.1.7'
```

An explicit version opts into a prerelease when applicable. Optional integrations:
`'guardtrellis[fastapi]==0.1.7'`,
`'guardtrellis[langgraph]==0.1.7'`,
`'guardtrellis[openai]==0.1.7'`,
`'guardtrellis[gemini]==0.1.7'`.
<!-- guardtrellis-release:end -->

## Included capabilities

- A [policy and operations recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/policy_operations.md)
  with distinct stage policies, checked-data handoff, metadata-only diagnostics, and host resource controls.
- A separate [policy-combination evaluation](https://github.com/sahilmathur254/guardtrellis/blob/main/evaluation/policy_combinations/README.md)
  measuring ordered checks, callback/tool delivery, and privacy surfaces against explicit synthetic expectations.
- A [retrieval and tool-call recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/retrieval_tools.md)
  that carries checked text and validated arguments through both boundaries using core dependencies.
- A [decoded-query recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/decoded_query.md)
  that checks one decoded `search.query` field before passing freshly validated arguments to a local callback.
- A [decoded-contact recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/decoded_contact.md)
  with a fixed two-field policy, whole-call budgets, and checked authorization and delivery.
  It uses existing public APIs and leaves SDK detector coverage unchanged.
- An explicit [formatted-phone option](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/formatted_phone.md)
  for 8–15 ASCII digits with literal `+`, single spaces/hyphens, and one separated
  parenthesized group. Existing callers retain contiguous-phone detection.
- A [tool-result handoff recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/tool_result_handoff.md)
  that checks returned tool text before inserting it into agent state or passing it to another consumer.
- A separate [40-case Unicode and PII development set](https://github.com/sahilmathur254/guardtrellis/blob/main/evaluation/challenges/README.md)
  with synthetic provenance, multilingual context, benign cases, and retained misses and false positives.
  The original 72-case smoke corpus and its historical results remain available.
- OpenAI SDK 3 compatibility for the optional OpenAI and Azure examples, using HTTPX2.
  See the [migration notes](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/openai.md#sdk-3-compatibility)
  for custom clients and certificate trust changes.

Synthetic results measure the recorded cases and do not establish broader detection accuracy.
The [control-coverage matrix](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/coverage.md)
links configured behavior to source, regression cases, retained evidence and required host controls.

## Quickstart

```python
from guardtrellis import Guard, PIIScanner, SecretScanner

guard = Guard(
    input_scanners=[SecretScanner(), PIIScanner()],
    output_scanners=[SecretScanner(), PIIScanner()],
)

# Deterministic local demonstration, not a real model integration.
result = guard.run("Contact casey@example.org", lambda text: f"Received: {text}")
print(result.require_text())  # Received: Contact [PII]
print(result.diagnostics())  # Metadata only; no matched strings or prompt text.
```

`run` calls your callback only after accepted input checks, then checks the **complete**
returned string before exposing it. Use `await guard.arun(text, callback)` for async work;
both sync and async callbacks work there. Provider SDKs are not required by the core.

| Check | Default | Scope |
| --- | --- | --- |
| `PIIScanner` | redact | ASCII email, contiguous international phone by default, opt-in formatted phone, US SSN shapes |
| `SecretScanner` | block | Selected GitHub tokens, AWS access-key IDs, PEM private keys |
| `InvisibleScanner` | warn | Unicode control/format characters except tab and newlines |
| `LiteralScanner([...])` | block | Configured literal substrings; optional ASCII case folding |
| `JSONScanner(schema)` | block invalid data | Strict JSON and constrained Draft 2020-12 schemas |
| `ToolGuard({name: schema})` | block invalid calls | Exact tool names and object argument schemas |

No scanners are installed implicitly: `Guard()` permits bounded text. Choose policies for each
boundary. Heuristic matches are signals, not probabilities or comprehensive protection.

Select `PIIScanner(formats=["phone"], phone_mode="formatted")` to redact supported
forms such as `+1 (202) 555-0123` as one complete span. Malformed or overlong candidates
produce no phone finding in this mode; they are not automatically blocked. The
[formatted-phone guide](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/formatted_phone.md)
defines the grammar, offsets, exclusions, and decoded-query wiring. It does not validate
country codes, number allocation, ownership, or arbitrary obfuscation.

```python
from guardtrellis import Action, Guard, JSONScanner, LiteralScanner, Stage, ToolGuard

guard = Guard(
    retrieval_scanners=[LiteralScanner(["confidential"], action=Action.BLOCK)],
    output_scanners=[JSONScanner({"type": "object", "required": ["answer"]})],
)
retrieval = guard.scan("A public reference", stage=Stage.RETRIEVAL)
# In async code: await guard.ascan("A public reference", stage=Stage.RETRIEVAL)

tools = ToolGuard(
    {
        "search": {
            "type": "object",
            "properties": {"query": {"type": "string", "maxLength": 200}},
            "required": ["query"],
            "additionalProperties": False,
        },
    }
)
call = tools.validate("search", '{"query":"public documentation"}')
name, arguments = call.require_call()
# Your application must still enforce identity, permissions, and execution limits.
```

`ALLOW`, `WARN`, and `REDACT` expose `result.text`; `BLOCK` and `ERROR` expose `None`.
`require_text()` / `require_call()` raise a payload-free `Rejected` exception on rejection.
WARN deliberately permits delivery. Scanner/callback exceptions are errors, never successful
checks. The first block/error stops its stage. Input rejection prevents the callback entirely.

Stages have separate scanner lists: `input_scanners`, `output_scanners`, `retrieval_scanners`,
and `tool_scanners`. Use `scan(text, stage=Stage.INPUT/OUTPUT/RETRIEVAL/TOOL)` or `ascan`;
the default stage is `Stage.INPUT`. Retrieval/tool checks must be invoked explicitly;
`run` does not discover or intercept these operations. `ToolGuard` adds name/schema validation.

Scanners run in configuration order. Each sees the text produced by preceding redactions.
Findings record half-open character offsets, scanner index, and the **input revision** they
refer to; every redacting scanner increments the revision. Offsets from different revisions
must not be applied to the original string. No originals or rehydration maps are retained.

Defaults: 100,000 characters per stage, 256 findings per scanner, JSON nesting at most 32,
and a 5-second **per-operation async wait**. Synchronous calls have no execution deadline.
Async cancellation cannot terminate a running thread or preempt a coroutine that blocks
the event loop. Timed-out work may continue and have side effects. Use process isolation
and application concurrency limits when execution must be forcibly bounded.

## Examples and development

Clone the source to run the examples or contribute:

```sh
git clone https://github.com/sahilmathur254/guardtrellis.git
cd guardtrellis
uv sync --all-extras --group dev
uv run python examples/plain_callable.py
uv run python examples/retrieval_tools.py   # Checked retrieval through a proposed tool call
uv run python examples/policy_operations.py # Stage policies and metadata-only operations
uv run python examples/decoded_query.py     # Checked decoded search query, including JSON escapes
uv run python examples/decoded_contact.py   # Fixed email/phone fields with whole-call limits
uv run python examples/tool_result_handoff.py # Checked tool return before the next consumer
uv run python examples/fastapi_app.py       # Local TestClient demo, no server needed
uv run python examples/langgraph_app.py     # Local graph, no provider credentials
uv run python examples/openai_app.py        # Real SDK, in-memory HTTP mock by default
uv run python examples/azure_openai_app.py  # Azure SDK, in-memory HTTP mock by default
uv run python examples/gemini_app.py        # Gemini SDK, in-memory HTTP mock by default
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run python evaluation/run.py --report-dir /tmp/guardtrellis-eval
uv run python evaluation/policy_combinations/run.py --report-dir /tmp/guardtrellis-policies
uv build
uv run python scripts/smoke_dist.py
```

The [retrieval/tool-call recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/retrieval_tools.md)
uses only core dependencies and shows checked data crossing both boundaries, rejected callbacks,
and host authorization.

The [policy and operations recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/policy_operations.md)
extends this with stage-specific decisions, scanner ordering, callback failures, safe logging,
and application concurrency/deadline guidance. It also uses only core dependencies.

The [decoded-query recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/decoded_query.md)
uses the existing public APIs to check a single decoded `search.query` string. It rejects
invalid original arguments before scanning, serializes a fresh checked object, validates it
again, and authorizes the final call before local execution. It uses only core dependencies;
it does not recursively scan arbitrary structured data or expand detector coverage.

The [decoded-contact recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/decoded_contact.md)
checks only decoded `contact.email` and `contact.phone` strings. It runs secret checks across
both fields before either reaches PII redaction, shares work/findings budgets across the call,
and keeps decoded findings separate from final serialized validation. It is a synchronous,
core-only application example. Its fixed policy retains contiguous-phone detection;
adopting the formatted-phone option is an explicit application decision.

The [tool-result handoff recipe](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/tool_result_handoff.md)
validates and authorizes a call, executes it, then checks its returned text at `Stage.RETRIEVAL`
before the next consumer receives it. It uses core dependencies and the current public APIs.
The host must route every raw-return path through this check; it cannot undo execution side effects.

FastAPI and LangGraph are optional extras (`.[fastapi]`, `.[langgraph]`). LangGraph input
checks run **before** text enters graph state. The example also checks model output before
returning it to graph state. Instrumentation around callbacks may still capture raw data.

The optional provider examples (`.[openai]`) use direct OpenAI Responses or Azure OpenAI
Chat Completions through guarded callbacks. Their default modes and ordinary tests make
no provider calls. See the [OpenAI guide](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/openai.md)
and [Azure guide](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/azure_openai.md)
for setup, privacy/failure boundaries, and separately approved one-request live smokes.
The `openai` extra now requires `openai>=3,<4` and `httpx2>=2.12,<3`; custom clients
must follow the SDK 3 migration notes above. Live verification of both paths is pending.

The separate [Gemini example](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/gemini.md)
uses the optional `.[gemini]` extra and also defaults to mocked HTTP. Its one-request live smoke can use an approved Gemini Free Tier
project with the documented model and limits. A maintainer-approved live smoke passed on
2026-09-26; the guide records the exact source, versions, token counts, and evidence limits.

See [API details](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/api.md),
[limitations and trust boundaries](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/limitations.md),
[evaluation methodology](https://github.com/sahilmathur254/guardtrellis/blob/main/evaluation/README.md),
[measured smoke results](https://github.com/sahilmathur254/guardtrellis/blob/main/evaluation/REPORT.md),
[Unicode and PII challenges](https://github.com/sahilmathur254/guardtrellis/blob/main/evaluation/challenges/README.md),
[resource measurements and host controls](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/resource_limits.md),
and [contribution guidance](https://github.com/sahilmathur254/guardtrellis/blob/main/CONTRIBUTING.md).
These checks do not provide comprehensive
prompt-injection prevention, factual verification, regulatory compliance, or a tool sandbox.

## Roadmap and contributions

Use the public [GitHub Project](https://github.com/users/sahilmathur254/projects/3) to find
ready work and track progress. The [roadmap](https://github.com/sahilmathur254/guardtrellis/blob/main/ROADMAP.md) explains the release milestones,
dependencies, and longer-term proposals. Start with a
[good first issue](https://github.com/sahilmathur254/guardtrellis/labels/good%20first%20issue)
or a [help wanted issue](https://github.com/sahilmathur254/guardtrellis/labels/help%20wanted),
then follow the [contributor workflow](https://github.com/sahilmathur254/guardtrellis/blob/main/CONTRIBUTING.md).

See the [changelog](https://github.com/sahilmathur254/guardtrellis/blob/main/CHANGELOG.md)
for version-specific behavior and the [release guide](https://github.com/sahilmathur254/guardtrellis/blob/main/docs/releasing.md)
for packaging checks, TestPyPI rehearsal, and maintainer-approved publication.
