How to Add AI Guardrails in Python
Add AI guardrails in Python — REST calls to Unified Guard, environment-based API keys, error handling, and pipeline placement.
Add AI guardrails in Python by calling IdenticAPI Unified Guard with httpx or requests from FastAPI routes, Django views, Celery workers, or standalone scripts. Run prompt_injection, pii_secrets, and output_safety at the correct pipeline stages, authenticate with Bearer idapi_test_ keys, and route on aggregated decision values.
For PII-only Python patterns, see PII Detection in Python. For output moderation, see AI Output Moderation in Python.
Prerequisites
- Python 3.10+
httpx(recommended) orrequests- IdenticAPI API key in environment variables
- Server-side LLM integration (OpenAI, Anthropic, local models)
Unified Guard request schema
Validated fields:
| Field | Type | Notes |
|---|---|---|
text | str (optional) | Assembled prompt or model completion |
checks | list[str] (required) | 1–4 of: prompt_injection, pii_secrets, output_safety, agent_action |
redact | bool (optional) | PII redaction when pii_secrets is in checks |
agent_action | dict (optional) | tool_name, action, optional arguments, context, policy_id |
Endpoint: POST https://www.identicapi.com/api/v1/guard
Maximum text length: 32,000 characters.
Environment setup
export IDENTICAPI_API_KEY="idapi_test_your_synthetic_key_here"
export IDENTICAPI_BASE_URL="https://www.identicapi.com"
Load with os.environ or pydantic-settings. Never embed keys in source or Jupyter notebooks committed to git.
Guard client module
guard/unified_guard.py:
import os
from typing import Any, Literal, TypedDict
import httpx
GuardCheck = Literal["prompt_injection", "pii_secrets", "output_safety", "agent_action"]
GuardDecision = Literal["allow", "review", "block"]
BASE_URL = os.environ.get("IDENTICAPI_BASE_URL", "https://www.identicapi.com")
API_KEY = os.environ.get("IDENTICAPI_API_KEY")
class UnifiedGuardResponse(TypedDict):
request_id: str
api: str
decision: GuardDecision
checks: list[dict[str, Any]]
usage_units: int
processing_time_ms: int
detector_version: str
FALLBACK_MESSAGE = "I can't process that request. Please try again."
_client: httpx.Client | None = None
def get_client() -> httpx.Client:
global _client
if _client is None:
_client = httpx.Client(base_url=BASE_URL, timeout=10.0)
return _client
def run_unified_guard(
*,
text: str | None = None,
checks: list[GuardCheck],
redact: bool | None = None,
agent_action: dict[str, Any] | None = None,
) -> UnifiedGuardResponse:
if not API_KEY:
raise RuntimeError("IDENTICAPI_API_KEY is not configured")
payload: dict[str, Any] = {"checks": checks}
if text is not None:
payload["text"] = text
if redact is not None:
payload["redact"] = redact
if agent_action is not None:
payload["agent_action"] = agent_action
response = get_client().post(
"/api/v1/guard",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json=payload,
)
response.raise_for_status()
return response.json()
Reuse one httpx.Client per worker for connection pooling.
FastAPI chat endpoint
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from guard.unified_guard import run_unified_guard, FALLBACK_MESSAGE
app = FastAPI()
class ChatRequest(BaseModel):
message: str = Field(..., min_length=1, max_length=8000)
history: list[dict[str, str]] = []
def assemble_prompt(message: str, history: list[dict[str, str]]) -> str:
lines = [f"{m['role']}: {m['content']}" for m in history]
lines.append(f"user: {message}")
return "\n".join(lines)
async def call_llm(prompt: str) -> str:
# Replace with your async provider client
return f"Assistant reply for: {prompt[:80]}"
@app.post("/v1/chat")
async def chat(req: ChatRequest):
prompt = assemble_prompt(req.message, req.history)
try:
input_guard = run_unified_guard(
text=prompt,
checks=["prompt_injection", "pii_secrets"],
redact=True,
)
except Exception:
return {"content": FALLBACK_MESSAGE, "decision": "unknown"}
if input_guard["decision"] != "allow":
return {
"content": FALLBACK_MESSAGE,
"decision": input_guard["decision"],
"request_id": input_guard["request_id"],
}
try:
completion = await call_llm(prompt)
except Exception as exc:
raise HTTPException(status_code=502, detail="LLM unavailable") from exc
try:
output_guard = run_unified_guard(
text=completion,
checks=["output_safety", "pii_secrets"],
)
except Exception:
return {"content": FALLBACK_MESSAGE, "decision": "unknown"}
if output_guard["decision"] != "allow":
return {
"content": FALLBACK_MESSAGE,
"decision": output_guard["decision"],
"request_id": output_guard["request_id"],
}
return {
"content": completion,
"decision": output_guard["decision"],
"request_id": output_guard["request_id"],
}
Django view pattern
Wrap the same run_unified_guard calls in a class-based or function view. Keep guard logic in a service module imported by views and Celery tasks — not duplicated across endpoints.
Celery workers
When workers generate user-visible text (email drafts, ticket summaries), run output guard before marking jobs complete:
@celery_app.task
def summarize_ticket(ticket_id: str) -> None:
raw = generate_summary(ticket_id)
guard = run_unified_guard(
text=raw,
checks=["output_safety", "pii_secrets"],
)
if guard["decision"] != "allow":
queue_for_review(ticket_id, guard["request_id"])
return
publish_summary(ticket_id, raw)
RAG ingestion
Scan chunks before embedding:
def ingest_chunk(chunk: str) -> str | None:
result = run_unified_guard(
text=chunk,
checks=["prompt_injection", "pii_secrets"],
redact=True,
)
if result["decision"] == "block":
return None # quarantine
return chunk # or redacted variant from pii check findings
See Secure RAG Retrieved Documents.
curl reference
curl -X POST https://www.identicapi.com/api/v1/guard \
-H "Authorization: Bearer idapi_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"text": "User pasted: api_key=sk_test_DO_NOT_FORWARD",
"checks": ["prompt_injection", "pii_secrets"],
"redact": true
}'
Response includes decision, per-check verdict values, findings, and request_id. Aggregation: block > review > allow.
Decision routing
decision | Typical Python action |
|---|---|
allow | Continue pipeline |
review | queue_for_review(); return fallback to user |
block | Return fallback; do not call LLM or deliver text |
Map per-check results when you need finer policy — e.g., block on pii_secrets block but review on prompt_injection review.
Error handling
Distinguish HTTP errors from policy blocks:
try:
guard = run_unified_guard(text=prompt, checks=["prompt_injection"])
except httpx.HTTPStatusError as exc:
logger.error("guard_http_error", extra={"status": exc.response.status_code})
# fail-closed: return FALLBACK_MESSAGE
Document fail-open vs fail-closed — Fail Open vs Fail Closed.
Testing with pytest
import pytest
from unittest.mock import patch
def test_blocks_on_input_guard(monkeypatch):
def mock_guard(**kwargs):
return {"decision": "block", "request_id": "req_test", "checks": []}
monkeypatch.setattr("guard.unified_guard.run_unified_guard", mock_guard)
# call endpoint; assert LLM mock not invoked
Never use real credentials in tests. Use synthetic values from PII and Secrets Leakage Checklist.
LangChain and other frameworks
If you use LangChain, place guard calls in your application service layer — not inside prompt templates. See LangChain Security Guardrails for durable architecture guidance.
Observability
Log request_id, decision, processing_time_ms, and usage_units. Avoid logging full text payloads in production. Correlate guard calls with LLM provider request_id fields when available.
Related resources
Summary
Python guardrails are REST calls to POST /api/v1/guard with stage-appropriate checks, environment-based API keys, and explicit decision routing. Split input and output stages, fail closed on transport errors, and scan the full assembled prompt — not just the latest user message. Call Unified Guard from Python · Documentation
Frequently asked questions
How do I call Unified Guard from Python?
POST to https://www.identicapi.com/api/v1/guard with httpx or requests, passing checks (required) and optional text, redact, and agent_action in JSON. Authenticate with Authorization Bearer and your API key.
Where should guardrails run in FastAPI or Django?
In the view or service layer that assembles the LLM prompt — before OpenAI, Anthropic, or local model clients — and again on completions before returning responses or writing to databases.
Should Celery workers run output guardrails?
Yes, whenever workers produce user-visible text such as email drafts or summaries. Do not mark jobs complete or publish to queues consumed by users before output checks pass.
How do I scan RAG chunks in Python?
Call Unified Guard with prompt_injection and pii_secrets on each chunk before embedding. Quarantine or skip chunks that return block decisions instead of indexing secrets into your vector store.
What is the maximum text length for Unified Guard?
Text is limited to 32,000 characters per request. Validate and truncate inbound payloads in your application before calling the API.
Related reading
- PII Detection in Python
Call IdenticAPI PII detection from Python backends — authentication, request payloads, redaction, and error handling for…
- Prompt Injection Detection with Python
Call a prompt injection detection API from Python — request flow, environment variables, response handling, and integrat…
- How to Add AI Output Moderation in Python
Integrate AI output moderation in Python services — API authentication, verdict handling, and placement in RAG or chat b…