Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
42 changes: 42 additions & 0 deletions .github/scripts/build-demo-gif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from pathlib import Path

from PIL import Image

FRAME_DIR = Path("assets/demo-frames")
OUTPUT = Path("assets/demo/safe-agent-demo.gif")
TARGET_WIDTH = 640
DURATIONS_MS = [900, 1150, 1150, 1400, 1400]

frame_paths = sorted(FRAME_DIR.glob("frame-*.png"))
if len(frame_paths) != 5:
raise SystemExit(f"expected 5 demo frames, found {len(frame_paths)}")

frames: list[Image.Image] = []
for frame_path in frame_paths:
with Image.open(frame_path) as source:
image = source.convert("RGB")
height = round(image.height * TARGET_WIDTH / image.width)
image = image.resize((TARGET_WIDTH, height), Image.Resampling.LANCZOS)
frames.append(
image.convert(
"P",
palette=Image.Palette.ADAPTIVE,
colors=64,
)
)

OUTPUT.parent.mkdir(parents=True, exist_ok=True)
frames[0].save(
OUTPUT,
save_all=True,
append_images=frames[1:],
duration=DURATIONS_MS,
loop=0,
optimize=True,
disposal=2,
)

if OUTPUT.stat().st_size == 0:
raise SystemExit("generated GIF is empty")

print(f"DEMO_GIF={OUTPUT} bytes={OUTPUT.stat().st_size}")
56 changes: 56 additions & 0 deletions .github/scripts/capture-demo.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { chromium } from 'playwright';
import fs from 'node:fs';
import path from 'node:path';

const baseUrl = process.env.DEMO_BASE_URL ?? 'http://127.0.0.1:8000';
const outputDir = path.resolve('assets/demo');
const frameDir = path.resolve('assets/demo-frames');
fs.mkdirSync(outputDir, { recursive: true });
fs.mkdirSync(frameDir, { recursive: true });

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } });
const page = await context.newPage();

await page.addInitScript(() => {
Object.defineProperty(Crypto.prototype, 'randomUUID', {
configurable: true,
value: () => '00000000-0000-4000-8000-000000000001',
});
});

await page.goto(baseUrl, { waitUntil: 'networkidle' });
await page.locator('#healthStatus').filter({ hasText: 'API online' }).waitFor();

async function captureFrame(index) {
await page.screenshot({
path: path.join(frameDir, `frame-${String(index).padStart(2, '0')}.png`),
fullPage: true,
});
}

await page.screenshot({
path: path.join(outputDir, 'safe-agent-playground.png'),
fullPage: true,
});
await captureFrame(1);

const scenarios = [
['Cross-tenant deny', 'tenant_mismatch'],
['Needs approval', 'human_approval_required'],
['Approved action', 'policy_allowed'],
['Blocked delete', 'destructive_tool_disabled_in_demo'],
];

let frame = 2;
for (const [buttonName, reason] of scenarios) {
await page.getByRole('button', { name: buttonName }).click();
await page.locator('#reason').filter({ hasText: reason }).waitFor();
await page.waitForTimeout(250);
await captureFrame(frame++);
}

await context.close();
await browser.close();

console.log(`DEMO_FRAMES=${frameDir}`);
101 changes: 101 additions & 0 deletions .github/workflows/demo-assets.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Verified Demo Assets

on:
pull_request:
paths:
- "examples/safe-agent-api/**"
- ".github/scripts/capture-demo.mjs"
- ".github/scripts/build-demo-gif.py"
- ".github/workflows/demo-assets.yml"
workflow_dispatch:

permissions:
contents: write

jobs:
capture:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout source branch
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref || github.ref_name }}

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: examples/safe-agent-api/pyproject.toml

- name: Install Safe Agent API and image tools
run: |
python -m pip install -e "examples/safe-agent-api[dev]"
python -m pip install Pillow==11.3.0

- name: Start Safe Agent API
run: |
cd examples/safe-agent-api
fastapi run app/main.py --host 127.0.0.1 --port 8000 > /tmp/safe-agent-api.log 2>&1 &
echo $! > /tmp/safe-agent-api.pid
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:8000/health >/dev/null; then
exit 0
fi
sleep 1
done
cat /tmp/safe-agent-api.log
exit 1

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install Playwright Chromium
run: |
npm install --no-save playwright@1.55.0
npx playwright install --with-deps chromium

- name: Capture real Playground frames
run: node .github/scripts/capture-demo.mjs | tee /tmp/demo-capture.log

- name: Build optimized GIF from verified frames
run: python .github/scripts/build-demo-gif.py

- name: Validate and report asset sizes
run: |
test -s assets/demo/safe-agent-playground.png
test -s assets/demo/safe-agent-demo.gif
ls -lh assets/demo/safe-agent-playground.png assets/demo/safe-agent-demo.gif

- name: Upload verified assets
uses: actions/upload-artifact@v4
with:
name: safe-agent-demo-assets
path: |
assets/demo/safe-agent-playground.png
assets/demo/safe-agent-demo.gif
if-no-files-found: error
retention-days: 14

- name: Commit verified assets to source branch
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.actor != 'github-actions[bot]'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add assets/demo/safe-agent-playground.png assets/demo/safe-agent-demo.gif
if git diff --cached --quiet; then
echo "Verified assets are unchanged."
exit 0
fi
git commit -m "docs: refresh verified demo assets"
git push origin "HEAD:${GITHUB_HEAD_REF}"

- name: Show API logs on failure
if: failure()
run: cat /tmp/safe-agent-api.log || true
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<p align="center">
<a href="https://github.com/Videirafo/AI-Agent-Production-Checklist/actions"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/Videirafo/AI-Agent-Production-Checklist/example-safe-agent.yml?branch=main&label=tests"></a>
<a href="https://github.com/Videirafo/AI-Agent-Production-Checklist/actions/workflows/demo-assets.yml"><img alt="Verified Demo Assets" src="https://img.shields.io/github/actions/workflow/status/Videirafo/AI-Agent-Production-Checklist/demo-assets.yml?branch=main&label=verified%20demo"></a>
<a href="./LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
<img alt="GitHub stars" src="https://img.shields.io/github/stars/Videirafo/AI-Agent-Production-Checklist?style=social">
</p>
Expand All @@ -12,10 +13,24 @@

| Status | Projeto executável | Qualidade |
|---|---|---|
| `v0.5` | **Safe Agent Playground + API** | GitHub Actions · pytest · CodeQL · Docker · Codespaces · Vercel-ready |
| `v0.5` | **Safe Agent Playground + API** | GitHub Actions · pytest · CodeQL · Docker · Codespaces · verified browser demo |

`agentic-ai` · `guardrails` · `tool-calling` · `RAG` · `MCP` · `evals` · `observability` · `security`

## Veja o Playground em segundos

<p align="center">
<img src="./assets/demo/safe-agent-demo.gif" alt="Safe Agent Playground executing verified policy scenarios" width="760" />
</p>

O GIF acima não é mockup: o workflow **Verified Demo Assets** inicia a FastAPI real, abre o Playground em Chromium com Playwright, executa cenários de autorização e gera os frames usados na animação.

<details>
<summary><strong>Abrir screenshot completo verificado</strong></summary>
<br />
<p align="center"><img src="./assets/demo/safe-agent-playground.png" alt="Safe Agent Playground full screenshot" width="100%" /></p>
</details>

## Use agora

### 1 clique: GitHub Codespaces
Expand Down
Binary file added assets/demo/safe-agent-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/demo/safe-agent-playground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading