# AIRS Python SDK

**Turn the framework into running code.**

The AIRS Python SDK implements the three-layer security architecture as a drop-in library for Python AI applications. Instead of reading about controls, you can `pip install` them. Like the framework itself, the SDK is designed to be risk-proportionate: use the components you need for your risk tier, leave out the ones you do not.

## Bring Your Own Model

The SDK is **model-agnostic**. It does not call AI models for you. It wraps security controls *around* your existing model calls. You continue using whatever model, provider, or framework you already have:

```python
# 1. AIRS checks the input
input_result = await pipeline.evaluate_input(request)

# 2. You call YOUR model - OpenAI, Anthropic, Bedrock, Ollama, local, anything
ai_output = await your_model(request.input_text)

# 3. AIRS checks the output
output_result = await pipeline.evaluate_output(request, response)
```

AIRS only sees the text going in and the text coming out. It doesn't know or care which model you're using, how you're calling it, or what framework it runs on. This means it works with:

- **Any model provider**: OpenAI, Anthropic, Google, Mistral, Cohere, local models
- **Any framework**: LangChain, LlamaIndex, Haystack, raw API calls
- **Any architecture**: single model, RAG, agents, multi-agent orchestration

The one exception is the optional **LLM-as-Judge**, which does call an OpenAI-compatible API to *evaluate* outputs. This is the judge model, a separate, independent model used for security evaluation, not your production model.

## What It Provides

| Component | What It Does |
|-----------|-------------|
| **Guardrails** | Regex-based prompt injection detection, PII filtering, content policy (extensible) |
| **LLM-as-Judge** | Rule-based (no API key) or LLM-based (OpenAI-compatible) output evaluation |
| **Circuit Breaker** | Sliding-window failure tracking, auto-trip, manual emergency stop, recovery |
| **PACE Controller** | State machine for structured degradation (Primary → Alternate → Contingency → Emergency) |
| **Security Pipeline** | Orchestrates all layers with configurable behavior per PACE state |
| **Agent Security** | Identity propagation, delegation depth enforcement, cycle detection, scope narrowing |
| **Tool Policy Engine** | Tool-call → policy → allow/deny. Deny-list, allow-list, per-agent-type restrictions |
| **Telemetry & Audit** | Structured security events with correlation IDs, pluggable audit sinks for SOC integration |
| **CLI Assessment** | Interactive tool that classifies your deployment and recommends controls |
| **FastAPI Middleware** | Drop-in middleware that protects AI endpoints automatically |

## Install

```bash
# Clone the repository
git clone https://github.com/JonathanCGill/airuntimesecurity.io.git
cd airuntimesecurity.io

# Install core (guardrails, circuit breaker, PACE, CLI)
pip install .

# With FastAPI middleware
pip install ".[fastapi]"

# With LLM-as-Judge (requires OpenAI-compatible API)
pip install ".[judge]"

# Everything
pip install ".[all]"
```

!!! tip "No external dependencies for core"
    The core SDK (guardrails, circuit breaker, PACE) requires only `pydantic`, `rich`, and `typer`. No API keys or external services needed to get started.

## 5-Minute Quick Start

```python
import asyncio
from airs.core.models import AIRequest, AIResponse
from airs.runtime import (
    SecurityPipeline, GuardrailChain, RegexGuardrail,
    CircuitBreaker, PACEController,
)

async def main():
    # Build the pipeline
    pipeline = SecurityPipeline(
        guardrails=GuardrailChain([RegexGuardrail()]),
        circuit_breaker=CircuitBreaker(),
        pace=PACEController(),
    )

    # Evaluate input before calling your model
    request = AIRequest(input_text="What is the capital of France?")
    input_result = await pipeline.evaluate_input(request)

    if not input_result.allowed:
        print(f"Blocked: {input_result.blocked_by}")
        return

    # Call your AI model (replace with your actual model call)
    ai_output = "The capital of France is Paris."

    # Evaluate output before returning to user
    response = AIResponse(request_id=request.request_id, output_text=ai_output)
    output_result = await pipeline.evaluate_output(request, response)

    if output_result.allowed:
        print(ai_output)
    else:
        print(f"Response blocked: {output_result.blocked_by}")

asyncio.run(main())
```

## Model Integration Examples

The quick start above uses a hardcoded string in place of a real model call. Here's how to wire AIRS into real model providers.

### Anthropic (Claude)

```bash
pip install anthropic
```

```python
import asyncio
import anthropic
from airs.core.models import AIRequest, AIResponse
from airs.runtime import (
    SecurityPipeline, GuardrailChain, RegexGuardrail,
    CircuitBreaker, PACEController,
)

client = anthropic.AsyncAnthropic()  # uses ANTHROPIC_API_KEY env var

async def ask_claude(user_input: str) -> str:
    # Build the security pipeline
    pipeline = SecurityPipeline(
        guardrails=GuardrailChain([RegexGuardrail()]),
        circuit_breaker=CircuitBreaker(),
        pace=PACEController(),
    )

    # --- Step 1: AIRS checks the input ---
    request = AIRequest(input_text=user_input, model="claude-sonnet-4-6")
    input_result = await pipeline.evaluate_input(request)

    if not input_result.allowed:
        return f"Blocked: {input_result.blocked_by}"

    # --- Step 2: Call Claude ---
    message = await client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{"role": "user", "content": user_input}],
    )
    ai_output = message.content[0].text

    # --- Step 3: AIRS checks the output ---
    response = AIResponse(request_id=request.request_id, output_text=ai_output)
    output_result = await pipeline.evaluate_output(request, response)

    if not output_result.allowed:
        return f"Response blocked: {output_result.blocked_by}"

    return ai_output

asyncio.run(ask_claude("What is the capital of France?"))
```

### OpenAI (GPT)

```bash
pip install openai
```

```python
import asyncio
from openai import AsyncOpenAI
from airs.core.models import AIRequest, AIResponse
from airs.runtime import (
    SecurityPipeline, GuardrailChain, RegexGuardrail,
    CircuitBreaker, PACEController,
)

client = AsyncOpenAI()  # uses OPENAI_API_KEY env var

async def ask_gpt(user_input: str) -> str:
    pipeline = SecurityPipeline(
        guardrails=GuardrailChain([RegexGuardrail()]),
        circuit_breaker=CircuitBreaker(),
        pace=PACEController(),
    )

    # --- Step 1: AIRS checks the input ---
    request = AIRequest(input_text=user_input, model="gpt-4o")
    input_result = await pipeline.evaluate_input(request)

    if not input_result.allowed:
        return f"Blocked: {input_result.blocked_by}"

    # --- Step 2: Call OpenAI ---
    completion = await client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": user_input}],
    )
    ai_output = completion.choices[0].message.content

    # --- Step 3: AIRS checks the output ---
    response = AIResponse(request_id=request.request_id, output_text=ai_output)
    output_result = await pipeline.evaluate_output(request, response)

    if not output_result.allowed:
        return f"Response blocked: {output_result.blocked_by}"

    return ai_output

asyncio.run(ask_gpt("What is the capital of France?"))
```

### Pattern: Reusable Helper

In practice, you'd build the pipeline once and reuse it. Here's a minimal helper that works with any async model function:

```python
from typing import Callable, Awaitable

async def secured_call(
    pipeline: SecurityPipeline,
    user_input: str,
    model_fn: Callable[[str], Awaitable[str]],
    model_name: str = "",
) -> str:
    """Wrap any async model call with AIRS security checks."""
    request = AIRequest(input_text=user_input, model=model_name)
    input_result = await pipeline.evaluate_input(request)
    if not input_result.allowed:
        raise ValueError(f"Input blocked: {input_result.blocked_by}")

    ai_output = await model_fn(user_input)

    response = AIResponse(request_id=request.request_id, output_text=ai_output)
    output_result = await pipeline.evaluate_output(request, response)
    if not output_result.allowed:
        raise ValueError(f"Output blocked: {output_result.blocked_by}")

    return ai_output
```

Usage:

```python
# With Anthropic
result = await secured_call(
    pipeline, "Hello",
    model_fn=lambda text: call_claude(text),
    model_name="claude-sonnet-4-6",
)

# With OpenAI
result = await secured_call(
    pipeline, "Hello",
    model_fn=lambda text: call_gpt(text),
    model_name="gpt-4o",
)
```

## CLI Assessment

Classify your deployment and get a prioritized implementation plan:

```bash
airs assess
```

The tool asks about your deployment context (audience, data sensitivity, autonomy, architecture) and outputs:

- **Risk tier** (LOW / MEDIUM / HIGH / CRITICAL)
- **Risk factors and mitigations** specific to your deployment
- **PACE resilience posture**: what happens at each degradation level
- **Prioritized control list**: what to implement first
- **Code snippet** to get started

For machine-readable output:

```bash
airs assess --json
```

## What's Next

| Guide | Description |
|-------|-------------|
| [Guardrails](guardrails.md) | Layer 1: input/output filtering, custom guardrails, chaining |
| [Judge](judge.md) | Layer 2: rule-based and LLM-based evaluation |
| [Circuit Breaker & PACE](resilience.md) | Emergency stop and structured degradation |
| [Pipeline](pipeline.md) | Full pipeline orchestration and configuration |
| [Agent Security](agents.md) | Identity propagation, delegation enforcement, tool access control |
| [Telemetry & Audit](telemetry.md) | Structured security events, audit sinks, SOC integration |
| [FastAPI Integration](fastapi.md) | Drop-in middleware for FastAPI apps |
| [Examples](examples.md) | Complete working examples |

## Architecture

![AIRS Security Pipeline](../images/sdk-pipeline-architecture.svg)

## Project Structure

```
src/airs/
├── agents/
│   ├── identity.py          # AgentIdentity, AgentContext, chain propagation
│   └── delegation.py        # DelegationPolicy, DelegationEnforcer
├── cli/
│   ├── __init__.py          # CLI app setup
│   └── assess.py            # Interactive assessment
├── core/
│   ├── models.py            # RiskTier, PACEState, AIRequest, LayerResult
│   ├── controls.py          # 25+ controls in queryable registry
│   └── risk.py              # Risk classification engine
├── runtime/
│   ├── guardrail.py         # Layer 1: Regex, ContentPolicy, Chain
│   ├── judge.py             # Layer 2: RuleBased, LLM
│   ├── circuit_breaker.py   # Emergency stop
│   ├── pace.py              # PACE state machine
│   ├── pipeline.py          # Full pipeline orchestrator
│   └── tool_policy.py       # Tool access control (allow/deny)
├── telemetry/
│   ├── events.py            # AISecurityEvent schema, EventType, emit()
│   └── audit.py             # AuditSink, LogSink, BufferSink, CallbackSink
└── integrations/
    └── fastapi.py           # Drop-in middleware

tests/                       # 100 tests
examples/
├── quickstart.py            # Minimal working example
└── fastapi_app.py           # Complete FastAPI app
```

## Running Tests

```bash
pip install ".[dev]"
pytest tests/ -v
```
