Metadata-Version: 2.4
Name: promptguard-sdk
Version: 1.9.0
Summary: Drop-in security for AI applications - AI Firewall SDK
Project-URL: Homepage, https://promptguard.co
Project-URL: Documentation, https://docs.promptguard.co
Project-URL: Repository, https://github.com/acebot712/promptguard-python
Project-URL: Issues, https://github.com/acebot712/promptguard-python/issues
Author-email: PromptGuard <support@promptguard.co>
License-Expression: MIT
License-File: LICENSE
Keywords: ai,ai-firewall,ai-security,anthropic,auto-instrumentation,crewai,genai-security,langchain,llamaindex,llm,llm-security,openai,pii-detection,prompt-injection,security
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Security
Requires-Python: >=3.10
Requires-Dist: httpx>=0.24.0
Provides-Extra: all
Requires-Dist: crewai>=0.28.0; extra == 'all'
Requires-Dist: langchain-core>=0.1.0; extra == 'all'
Requires-Dist: llama-index-core>=0.10.0; extra == 'all'
Provides-Extra: crewai
Requires-Dist: crewai>=0.28.0; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff==0.15.11; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.10.0; extra == 'llamaindex'
Description-Content-Type: text/markdown

[![PyPI version](https://img.shields.io/pypi/v/promptguard-sdk)](https://pypi.org/project/promptguard-sdk/)
[![CI](https://github.com/acebot712/promptguard-python/actions/workflows/ci.yml/badge.svg)](https://github.com/acebot712/promptguard-python/actions/workflows/ci.yml)
[![License](https://img.shields.io/github/license/acebot712/promptguard-python)](https://github.com/acebot712/promptguard-python/blob/main/LICENSE)
[![Python](https://img.shields.io/pypi/pyversions/promptguard-sdk)](https://pypi.org/project/promptguard-sdk/)

# PromptGuard Python SDK

Drop-in security for AI applications. No code changes required.

## Installation

```bash
pip install promptguard-sdk
```

> **Package name ≠ import name.** Install **`promptguard-sdk`**, but import **`promptguard`**:
> ```python
> import promptguard
> from promptguard import PromptGuard
> ```

Get a free API key at [app.promptguard.co](https://app.promptguard.co).

> **The SDK reads `PROMPTGUARD_API_KEY` from the environment; it does not auto-load `.env`.** Use [python-dotenv](https://pypi.org/project/python-dotenv/) (call `load_dotenv()` before constructing the client) if you keep secrets in a `.env` file.

> **PromptGuard fails open by default** — if the Guard API is unavailable, calls proceed *unscanned* so your app stays up. Set `fail_open=False` to block (fail closed) on a Guard outage instead.

## Two Ways to Secure Your App

### Option 1: Auto-Instrumentation (Recommended for Frameworks)

One line secures **every LLM call** in your application, regardless of which framework you use (LangChain, CrewAI, AutoGen, LlamaIndex, Haystack, Semantic Kernel, or direct SDK usage):

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

# That's it. Every LLM call is now secured.
# Works with ANY framework built on openai, anthropic, google-generativeai, cohere, or boto3.

from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
    model="gpt-5-nano",
    messages=[{"role": "user", "content": "Hello!"}]
)
# ^^ Scanned by PromptGuard before reaching OpenAI
```

**Supported SDKs** (auto-detected and patched):

| SDK | Frameworks Covered |
|-----|-------------------|
| `openai` | LangChain, CrewAI, AutoGen, Semantic Kernel, direct usage |
| `anthropic` | LangChain (ChatAnthropic), direct usage |
| `google-generativeai` | LangChain, LlamaIndex, direct usage |
| `cohere` | Haystack, LangChain, direct usage |
| `boto3` (Bedrock) | AWS-native apps (Claude, Titan, Llama on Bedrock) |

**Modes:**

```python
# Enforce mode (default) - blocks threats
promptguard.init(api_key="pg_live_xxx", mode="enforce")

# Monitor mode - logs threats without blocking (shadow mode)
promptguard.init(api_key="pg_live_xxx", mode="monitor")

# Scan responses too
promptguard.init(api_key="pg_live_xxx", scan_responses=True)

# Fail-closed (block if Guard API is unreachable)
promptguard.init(api_key="pg_live_xxx", fail_open=False)
```

**Shutdown:**

```python
promptguard.shutdown()  # Removes all patches, closes connections
```

### Option 2: Proxy Mode (Drop-in Replacement)

If you prefer the proxy approach, just swap your client:

```python
# Before
from openai import OpenAI
client = OpenAI()

# After
from promptguard import PromptGuard
client = PromptGuard(api_key="pg_live_xxx")

# Your existing code works unchanged!
```

## Framework-Specific Integrations

For deeper integration with richer context (chain names, tool calls, agent steps), use framework-specific callbacks alongside or instead of auto-instrumentation:

### LangChain

```python
from promptguard.integrations.langchain import PromptGuardCallbackHandler

handler = PromptGuardCallbackHandler(api_key="pg_live_xxx")

# Attach to an LLM
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-5-nano", callbacks=[handler])

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

The handler scans:
- `on_llm_start` / `on_chat_model_start` - prompts before the LLM call
- `on_llm_end` - responses after the LLM call
- `on_tool_start` - tool inputs for injection attempts
- `on_chain_start/end` - tracks chain context

### CrewAI

```python
from crewai import Crew, Agent, Task
from promptguard.integrations.crewai import PromptGuardGuardrail

pg = PromptGuardGuardrail(api_key="pg_live_xxx")

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

crew.kickoff(inputs={"topic": "AI safety"})
```

You can also wrap individual tools:

```python
from promptguard.integrations.crewai import secure_tool
from crewai.tools import BaseTool

@secure_tool(api_key="pg_live_xxx")
class SearchTool(BaseTool):
    name = "search"
    description = "Search the web"

    def _run(self, query: str) -> str:
        ...
```

### LlamaIndex

```python
from promptguard.integrations.llamaindex import PromptGuardCallbackHandler
from llama_index.core.callbacks import CallbackManager
from llama_index.core import Settings

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

# All LlamaIndex queries are now scanned
```

## Standalone Guard API

For any language or framework, call the Guard API directly:

```python
from promptguard import GuardClient

guard = GuardClient(api_key="pg_live_xxx")

# Scan before sending to LLM
decision = guard.scan(
    messages=[{"role": "user", "content": "Hello!"}],
    direction="input",
    model="gpt-5-nano",
)

if decision.blocked:
    print(f"Blocked: {decision.threat_type}")
elif decision.redacted:
    # Use decision.redacted_messages instead of original
    print("Content was redacted")
else:
    # Safe to proceed
    pass
```

Or via HTTP directly (any language):

```bash
curl -X POST https://api.promptguard.co/api/v1/guard \
  -H "X-API-Key: pg_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role": "user", "content": "Hello!"}],
    "direction": "input",
    "model": "gpt-5-nano"
  }'
```

> Authenticate with the `X-API-Key` header — this is the canonical header used by the Guard API and every SDK. There is no `Authorization: Bearer` scheme.

## Security Scanning

```python
from promptguard import PromptGuard

pg = PromptGuard(api_key="pg_live_xxx")

# Scan content for threats
result = pg.security.scan("Ignore previous instructions...")
if result["blocked"]:
    print(f"Threat detected: {result['reason']}")
```

## PII Redaction

```python
result = pg.security.redact(
    "My email is john@example.com and SSN is 123-45-6789"
)
print(result["redacted"])
# Output: "My email is [EMAIL] and SSN is [SSN]"
```

## Red Team Testing

```python
from promptguard import PromptGuard

pg = PromptGuard(api_key="pg_live_xxx")

# Run the autonomous red team agent (LLM-powered mutation)
report = pg.redteam.run_autonomous(
    budget=200,
    target_preset="support_bot:strict",
)
print(f"Grade: {report['grade']}, Bypass rate: {report['bypass_rate']:.0%}")

# Get Attack Intelligence stats
stats = pg.redteam.intelligence_stats()
print(f"Total patterns: {stats['total_patterns']}")
```

The async client mirrors the same methods:

```python
async with PromptGuardAsync(api_key="pg_live_xxx") as pg:
    report = await pg.redteam.run_autonomous(budget=200)
    stats = await pg.redteam.intelligence_stats()
```

## Async Support

The `PromptGuardAsync` client provides a fully asynchronous interface for non-blocking usage in async applications:

```python
from promptguard import PromptGuardAsync

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

    # Async security scanning
    result = await pg.security.scan("Check this content")

    # Async PII redaction
    redacted = await pg.security.redact("My email is john@example.com")
```

The async client mirrors the synchronous API - every method available on `PromptGuard` has an `await`-able counterpart on `PromptGuardAsync`.

## Retry Logic

Both `PromptGuard` and `PromptGuardAsync` support configurable retry behavior for transient failures:

```python
from promptguard import PromptGuard

pg = PromptGuard(
    api_key="pg_live_xxx",
    max_retries=3,        # Number of retry attempts (default: 3)
    retry_delay=0.5,      # Base delay in seconds between retries (default: 1.0)
)
```

Retries use exponential backoff starting from `retry_delay`. Only transient errors (network timeouts, 5xx responses) are retried; client errors (4xx) fail immediately.

## Embeddings

Scan and secure embedding requests through the proxy:

```python
from promptguard import PromptGuard

pg = PromptGuard(api_key="pg_live_xxx")

response = pg.embeddings.create(
    model="text-embedding-3-small",
    input="The quick brown fox jumps over the lazy dog",
)
# Proxy responses are returned as plain dicts (OpenAI-compatible JSON shape).
print(response["data"][0]["embedding"][:5])
```

Batch embedding requests are also supported:

```python
response = pg.embeddings.create(
    model="text-embedding-3-small",
    input=["First document", "Second document", "Third document"],
)
for item in response["data"]:
    print(f"Index {item['index']}: {len(item['embedding'])} dimensions")
```

## Configuration

```python
from promptguard import PromptGuard, Config

config = Config(
    api_key="pg_live_xxx",
    base_url="https://api.promptguard.co/api/v1/proxy",
    timeout=30.0,
)

pg = PromptGuard(config=config)
```

## Environment Variables

```bash
export PROMPTGUARD_API_KEY="pg_live_xxx"
# Optional — only override if pointing at a self-hosted/staging deployment.
# Leave unset to use the default (https://api.promptguard.co/api/v1/proxy).
export PROMPTGUARD_BASE_URL="https://api.promptguard.co/api/v1"
```

> The proxy client (`PromptGuard`) talks to the `/api/v1/proxy` endpoints. If you set `PROMPTGUARD_BASE_URL` to `.../api/v1` (without `/proxy`), the SDK appends the `/proxy` suffix for you, so requests still land on the proxy. Setting it explicitly to `.../api/v1/proxy` also works.
>
> **Security:** the SDK sends your API key (and, in proxy mode, your prompt content) to whatever `PROMPTGUARD_BASE_URL` points at. Self-hosting is supported, so only point it at a host you trust.

## Error Handling

```python
from promptguard import PromptGuard, PromptGuardBlockedError

# Auto-instrumentation
import promptguard
promptguard.init(api_key="pg_live_xxx")

try:
    response = client.chat.completions.create(...)
except PromptGuardBlockedError as e:
    print(f"Blocked: {e.decision.threat_type}")
    print(f"Event ID: {e.decision.event_id}")
```

## Links

- [Documentation](https://docs.promptguard.co)
- [SDK Reference](https://docs.promptguard.co/sdks/python)
- [Support](mailto:support@promptguard.co)

## License

MIT
