Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
lint-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: make sync

- name: Lint with ruff
run: make lint

# TODO: enable this once all the tests pass
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the tests still fail, so I will come up with a separate PR for enabling this

# - name: Run tests
# run: make tests
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ For full details, advanced usage, and API reference, see here: [OpenAI Guardrail
2. **Install dependencies**
- **Install from this repo:**
```bash
pip install -e .[presidio]
pip install -e '.[presidio]'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be removed soon, but I noticed it does not work zsh + macOs for me

```
- **Eventually this will be:**
```bash
Expand Down
8 changes: 5 additions & 3 deletions examples/basic/azure_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

import asyncio
import os

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this kind of changes are done by ``uv run ruff check --unsafe-fixes --fix`

from dotenv import load_dotenv
from openai import BadRequestError

from guardrails import (
GuardrailsAsyncAzureOpenAI,
GuardrailTripwireTriggered,
)
from dotenv import load_dotenv

load_dotenv()

Expand Down Expand Up @@ -72,14 +74,14 @@ async def process_input(
except GuardrailTripwireTriggered as e:
# Extract information from the triggered guardrail
triggered_result = e.guardrail_result
print(f" Input blocked. Please try a different message.")
print(" Input blocked. Please try a different message.")
print(f" Full result: {triggered_result}")
raise
except BadRequestError as e:
# Handle Azure's built-in content filter errors
# Will be triggered not when the guardrail is tripped, but when the LLM is filtered by Azure.
if "content_filter" in str(e):
print(f"\n🚨 Third party content filter triggered during LLM call.")
print("\n🚨 Third party content filter triggered during LLM call.")
print(f" Error: {e}")
raise
else:
Expand Down
1 change: 0 additions & 1 deletion examples/basic/custom_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered
from guardrails.context import GuardrailsContext, set_context


# Pipeline config with an LLM-based guardrail using Gemma3 via Ollama
PIPELINE_CONFIG = {
"version": 1,
Expand Down
3 changes: 2 additions & 1 deletion examples/basic/hello_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
from contextlib import suppress

from rich.console import Console
from rich.panel import Panel

Expand Down Expand Up @@ -55,7 +56,7 @@ async def process_input(

return response.llm_response.id

except GuardrailTripwireTriggered as exc:
except GuardrailTripwireTriggered:
raise


Expand Down
6 changes: 3 additions & 3 deletions examples/basic/local_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import asyncio
from contextlib import suppress
from rich.console import Console
from rich.panel import Panel

from openai.types.chat import ChatCompletionMessageParam
from rich.console import Console
from rich.panel import Panel

from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered

Expand Down Expand Up @@ -55,7 +55,7 @@ async def process_input(
input_data.append({"role": "user", "content": user_input})
input_data.append({"role": "assistant", "content": response_content})

except GuardrailTripwireTriggered as exc:
except GuardrailTripwireTriggered:
# Handle guardrail violations
raise

Expand Down
3 changes: 2 additions & 1 deletion examples/basic/multi_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from rich.console import Console
from rich.live import Live
from rich.panel import Panel

from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered

console = Console()
Expand Down Expand Up @@ -79,7 +80,7 @@ async def process_input(

return response_id_to_return

except GuardrailTripwireTriggered as exc:
except GuardrailTripwireTriggered:
# Clear the live display when output guardrail is triggered
live.update("")
console.clear()
Expand Down
8 changes: 4 additions & 4 deletions examples/basic/multiturn_chat_with_alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
from __future__ import annotations

import argparse
import json
from typing import Iterable
import asyncio
import json
from collections.abc import Iterable

from rich.console import Console
from rich.panel import Panel
Expand Down Expand Up @@ -177,10 +177,10 @@ def _stage_lines(stage_name: str, stage_results: Iterable) -> list[str]:
# Add interpretation
if r.tripwire_triggered:
lines.append(
f" ⚠️ PROMPT INJECTION DETECTED: Action does not serve user's goal!"
" ⚠️ PROMPT INJECTION DETECTED: Action does not serve user's goal!"
)
else:
lines.append(f" ✨ ALIGNED: Action serves user's goal")
lines.append(" ✨ ALIGNED: Action serves user's goal")
else:
# Other guardrails - show basic info
for key, value in info.items():
Expand Down
1 change: 1 addition & 0 deletions examples/basic/pii_mask_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import asyncio
from contextlib import suppress

from rich.console import Console
from rich.panel import Panel

Expand Down
9 changes: 5 additions & 4 deletions examples/basic/structured_outputs_example.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Simple example demonstrating structured outputs with GuardrailsClient."""

import asyncio

from pydantic import BaseModel, Field

from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered
Expand Down Expand Up @@ -37,13 +38,13 @@ async def extract_user_info(guardrails_client: GuardrailsAsyncOpenAI, text: str)
model="gpt-4.1-nano",
text_format=UserInfo
)

# Access the parsed structured output
user_info = response.llm_response.output_parsed
print(f"✅ Successfully extracted: {user_info.name}, {user_info.age}, {user_info.email}")

return user_info

except GuardrailTripwireTriggered as exc:
print(f"❌ Guardrail triggered: {exc}")
raise
Expand Down Expand Up @@ -75,4 +76,4 @@ async def main() -> None:


if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
1 change: 1 addition & 0 deletions examples/basic/suppress_tripwire.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
from contextlib import suppress
from typing import Any

from rich.console import Console
from rich.panel import Panel

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import asyncio
from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered

from rich.console import Console
from rich.panel import Panel

from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered

# Initialize Rich console
console = Console()

Expand Down Expand Up @@ -43,14 +45,14 @@ async def main():
messages=[{"role": "user", "content": candidate}],
model="gpt-4.1-mini",
)

console.print(Panel(
f"[bold green]Tripwire not triggered[/bold green]\n\n"
f"Response: {response.llm_response.choices[0].message.content}",
title="✅ Guardrail Check Passed",
border_style="green"
))

except GuardrailTripwireTriggered as exc:
# Make the guardrail triggered message stand out with Rich
console.print(Panel(
Expand Down
8 changes: 4 additions & 4 deletions examples/implementation_code/blocking/blocking_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,28 @@
OutputGuardrailTripwireTriggered,
Runner,
)
from agents.run import RunConfig

from guardrails import GuardrailAgent


async def main():
# Create agent with guardrails configured from config file
agent = GuardrailAgent(
config=Path("guardrails_config.json"),
name="Customer support agent",
instructions="You are a customer support agent. You help customers with their questions.",
)

while True:
try:
prompt = input("\nEnter a message: ")
result = await Runner.run(
agent,
prompt
)

print(f"\nAssistant: {result.final_output}")

except (EOFError, KeyboardInterrupt):
break
except (InputGuardrailTripwireTriggered, OutputGuardrailTripwireTriggered) as exc:
Expand Down
12 changes: 7 additions & 5 deletions examples/implementation_code/blocking/blocking_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

# Please integrate openai-guardrails into my LLM calls.

# Use "guardrails_config.json" as the config file name,
# Use "guardrails_config.json" as the config file name,
# and follow the example below that illustrates a sample installation.

import asyncio
from pathlib import Path

from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered


async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: str) -> None:
"""Process user input with complete response validation using the new GuardrailsClient."""
try:
Expand All @@ -18,17 +20,17 @@ async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: st
messages=[{"role": "user", "content": user_input}],
model="gpt-4.1-nano",
)

print(f"\nAssistant: {response.llm_response.choices[0].message.content}")
except GuardrailTripwireTriggered as exc:

except GuardrailTripwireTriggered:
# GuardrailsClient automatically handles tripwire exceptions
raise

async def main():
# Initialize GuardrailsAsyncOpenAI with the config file
guardrails_client = GuardrailsAsyncOpenAI(config=Path("guardrails_config.json"))

while True:
try:
prompt = input("\nEnter a message: ")
Expand Down
16 changes: 9 additions & 7 deletions examples/implementation_code/blocking/blocking_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

# Please integrate openai-guardrails into my LLM calls.

# Use "guardrails_config.json" as the config file name,
# Use "guardrails_config.json" as the config file name,
# and follow the example below that illustrates a sample installation.

import asyncio
from pathlib import Path

from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered


async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: str, response_id: str | None = None) -> str | None:
"""Process user input with complete response validation using the new GuardrailsClient."""
try:
Expand All @@ -19,21 +21,21 @@ async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: st
model="gpt-4.1-nano",
previous_response_id=response_id
)

print(f"\nAssistant: {response.llm_response.output_text}")

return response.llm_response.id
except GuardrailTripwireTriggered as exc:

except GuardrailTripwireTriggered:
# GuardrailsClient automatically handles tripwire exceptions
raise

async def main():
# Initialize GuardrailsAsyncOpenAI with the config file
guardrails_client = GuardrailsAsyncOpenAI(config=Path("guardrails_config.json"))

response_id: str | None = None

while True:
try:
prompt = input("\nEnter a message: ")
Expand Down
12 changes: 7 additions & 5 deletions examples/implementation_code/streaming/streaming_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

# Please integrate openai-guardrails into my LLM calls.

# Use "guardrails_config.json" as the config file name,
# Use "guardrails_config.json" as the config file name,
# and follow the example below that illustrates a sample installation.

import asyncio
import os
from pathlib import Path

from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered


async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: str) -> str:
"""Process user input with streaming output and guardrails using the GuardrailsClient."""
try:
Expand All @@ -20,20 +22,20 @@ async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: st
model="gpt-4.1-nano",
stream=True,
)

# Stream with output guardrail checks
async for chunk in stream:
if chunk.llm_response.choices[0].delta.content:
print(chunk.llm_response.choices[0].delta.content, end="", flush=True)
return "Stream completed successfully"
except GuardrailTripwireTriggered as exc:

except GuardrailTripwireTriggered:
raise

async def main():
# Initialize GuardrailsAsyncOpenAI with the config file
guardrails_client = GuardrailsAsyncOpenAI(config=Path("guardrails_config.json"))

while True:
try:
prompt = input("\nEnter a message: ")
Expand Down
Loading
Loading