Python-Powered API cost anomaly Guardrail with Oracle APEX PDF Engine

Introduction / Issue

In cloud computing and serverless systems, there may be unexpected bursts of use, causing hidden cost overheads which are untracked. Guardrails are put in place to run every day to parse through API and cloud usage logs and detect any anomaly in costs and generate a PDF report.

However, creating a PDF file is not all that needs to be done. It is imperative to have a web portal for non-tech people and management teams to access those reports, filter them, and download them. The web portal must be able to do this without accessing cloud storage buckets and databases.

 

Why We Need to Do / Cause of the Issue

The scripts provide useful diagnostic reports, but storing such reports locally on file systems or isolated object storage buckets results in several operational drawbacks:

  • Access: Non-technical individuals find it difficult to locate or retrieve the log files or PDF files stored in the backend system.
  • Audit Trail and Logging: Without a proper logging system using a database, there is no way to know when the report was created and if there were any issues with its creation.
  • User-Friendly Access: Manual downloads of the report via server and database access is a cumbersome process.Through automation that includes creating the PDF using Python, loading the BLOB directly into Oracle Database, and creating a friendly download process via Oracle APEX, a user-friendly dashboard for cloud costs reports is established.

By pairing a real-time FastAPI API Proxy Guardrail with Playwright automated browser monitoring, Oracle Database BLOB storage, and Oracle APEX, we build an automated pipeline that detects cost spikes, logs performance metrics, and securely publishes downloadable PDF reports.

 

How Do We Solve

This solution is further split into the following three main implementation phases:Python scripting: Execute the cost anomaly detection guardrail and upload the PDF file as Binary Large Objects (BLOBs) to the Oracle Database.

Oracle SQL Developer configuration: Schema creation, privileges on tables/synonyms, and checking objects access (DASHBOARD_PDF_LOGS).

Oracle APEX development: Create the Interactive Report page with the native BLOB download column.

 

Real-Time Scenario & Step-by-Step Implementation

 

Step 1: Python Implementation (Proxy, Guardrail, Dashboard & Automation)

Prerequisites & Environment Setup

Install the necessary Python packages and set up your environment variables:

 

pip install fastapi uvicorn httpx pydantic numpy pandas streamlit plotly playwright oracledb python-dotenv tiktoken openai

playwright install

 

Step 2: Create a .env file in your root folder:

 OPENAI_API_KEY= “YOUR API KEY

 

Step 3: Real time cost guardrail Proxy (proxy.py):

 The proxy intercepts incoming OpenAI API requests, estimates prompt token counts using tiktoken, evaluates the cost $Z$-score against historical baseline data, blocks anomalous requests (http status 429), and logs clean transactions to a local SQLite database (guardrail.db).

import os

import sqlite3

import time

from datetime import datetime

from collections import defaultdict

import numpy as np

import httpx

import tiktoken

from dotenv import load_dotenv  # Added for reading .env

from fastapi import FastAPI, HTTPException, Request, Header

from fastapi.responses import JSONResponse

# Automatically load environment variables from .env file

load_dotenv()

 

app = FastAPI(title=”Real-Time API Cost Guardrail Gateway”)

 

# Configuration

WINDOW_SECONDS = 300

ANOMALY_Z_THRESHOLD = 2.0

UPSTREAM_API_URL = “https://api.openai.com/v1/chat/completions”

 

# Price per 1,000 tokens (GPT-4o-mini rates)

INPUT_COST_PER_1K = 0.00015

OUTPUT_COST_PER_1K = 0.00060

 

usage_history = defaultdict(list)

 

def init_db():

conn = sqlite3.connect(“guardrail.db”)

cursor = conn.cursor()

cursor.execute(“””

CREATE TABLE IF NOT EXISTS request_logs (

id INTEGER PRIMARY KEY AUTOINCREMENT,

timestamp DATETIME,

tenant_id TEXT,

estimated_tokens INTEGER,

estimated_cost_usd REAL,

status TEXT,

z_score REAL

)

“””)

conn.commit()

conn.close()

 

init_db()

 

def log_request(tenant_id: str, tokens: int, cost: float, status: str, z_score: float):

conn = sqlite3.connect(“guardrail.db”)

cursor = conn.cursor()

cursor.execute(

“INSERT INTO request_logs (timestamp, tenant_id, estimated_tokens, estimated_cost_usd, status, z_score) VALUES (?, ?, ?, ?, ?, ?)”,

(datetime.now(), tenant_id, tokens, cost, status, round(z_score, 2))

)

conn.commit()

conn.close()

 

def estimate_prompt_tokens(messages: list, model: str = “gpt-4o-mini”) -> int:

“””Accurately calculates token counts using tiktoken tokenizer.”””

try:

encoding = tiktoken.encoding_for_model(model)

except KeyError:

encoding = tiktoken.get_encoding(“cl100k_base”)

 

total_tokens = 0

for msg in messages:

total_tokens += len(encoding.encode(msg.get(“content”, “”)))

return total_tokens

 

def evaluate_cost_anomaly(tenant_id: str, estimated_cost: float):

now = time.time()

usage_history[tenant_id] = [

(ts, c) for ts, c in usage_history[tenant_id] if now – ts <= WINDOW_SECONDS

]

recent_costs = [c for _, c in usage_history[tenant_id]]

usage_history[tenant_id].append((now, estimated_cost))

 

if len(recent_costs) < 5:

return False, 0.0

 

mean_cost = np.mean(recent_costs)

std_cost = np.std(recent_costs)

 

if std_cost == 0:

is_anomaly = estimated_cost > (mean_cost * 2.5)

return is_anomaly, 3.0 if is_anomaly else 0.0

 

z_score = (estimated_cost – mean_cost) / std_cost

return z_score > ANOMALY_Z_THRESHOLD, float(z_score)

 

@app.post(“/v1/chat/completions”)

async def proxy_live_request(request: Request, x_tenant_id: str = Header(default=”production_client”)):

“””

Real-Time Proxy: Intercepts real payloads, evaluates costs, forwards to OpenAI,

and returns live responses back to caller.

“””

body = await request.json()

messages = body.get(“messages”, [])

model = body.get(“model”, “gpt-4o-mini”)

 

# 1. Estimate incoming prompt tokens & cost

prompt_tokens = estimate_prompt_tokens(messages, model)

est_prompt_cost = (prompt_tokens / 1000) * INPUT_COST_PER_1K

 

# 2. Pre-execution Cost Anomaly Guardrail

is_anomaly, z_score = evaluate_cost_anomaly(x_tenant_id, est_prompt_cost)

 

if is_anomaly:

log_request(x_tenant_id, prompt_tokens, est_prompt_cost, “BLOCKED_ANOMALY”, z_score)

raise HTTPException(

status_code=429,

detail={

“error”: “Cost Anomaly Guardrail Triggered”,

“tenant_id”: x_tenant_id,

“cost_usd”: round(est_prompt_cost, 5),

“z_score”: round(z_score, 2),

“message”: “Real-time request blocked due to sudden token usage spike.”

}

)

 

# 3. Request cleared guardrail: Forward request to Upstream Provider

api_key = os.getenv(“OPENAI_API_KEY”)

headers = {

“Authorization”: f”Bearer {api_key}”,

“Content-Type”: “application/json”

}

 

async with httpx.AsyncClient() as client:

try:

upstream_response = await client.post(

UPSTREAM_API_URL,

json=body,

headers=headers,

timeout=30.0

)

except httpx.RequestError as exc:

raise HTTPException(status_code=502, detail=f”Upstream provider connection error: {exc}”)

 

response_data = upstream_response.json()

 

# 4. Extract actual post-call billing data returned from live provider

if upstream_response.status_code == 200 and “usage” in response_data:

actual_total_tokens = response_data[“usage”].get(“total_tokens”, prompt_tokens)

prompt_t = response_data[“usage”].get(“prompt_tokens”, 0)

completion_t = response_data[“usage”].get(“completion_tokens”, 0)

 

actual_cost = ((prompt_t / 1000) * INPUT_COST_PER_1K) + ((completion_t / 1000) * OUTPUT_COST_PER_1K)

log_request(x_tenant_id, actual_total_tokens, actual_cost, “PASSED”, z_score)

else:

log_request(x_tenant_id, prompt_tokens, est_prompt_cost, “FAILED_UPSTREAM”, z_score)

 

return JSONResponse(status_code=upstream_response.status_code, content=response_data)

 

if __name__ == “__main__”:

import uvicorn

uvicorn.run(“proxy:app”, host=”127.0.0.1″, port=8000, reload=True)

 

Step 3: Start the Proxy server:

 

Command : Python proxy.py

 

Step 4: Client Application Test Script

import os

from dotenv import load_dotenv

from openai import OpenAI

 

# Load API key from .env file

load_dotenv()

 

# Initialize the OpenAI client pointing to YOUR PROXY instead of OpenAI

client = OpenAI(

base_url=”http://127.0.0.1:8000/v1″,  # Routes through proxy.py

api_key=os.getenv(“OPENAI_API_KEY”, “dummy_key_if_handled_by_proxy”)

)

 

def make_real_request():

print(“Sending real prompt through proxy…”)

 

try:

response = client.chat.completions.create(

model=”gpt-4o-mini”,

messages=[

{“role”: “system”, “content”: “You are a helpful technical assistant.”},

{“role”: “user”, “content”: “Explain vector databases in two concise sentences.”}

],

# Custom header to track which team/user sent the request

extra_headers={“X-Tenant-ID”: “finance_team”}

)

 

print(“\n— Response Received —“)

print(response.choices[0].message.content)

 

except Exception as e:

print(f”\n Request Error / Blocked by Guardrail: {e}”)

 

if __name__ == “__main__”:

make_real_request()

 

Step 5: To test request flow:

 

Command : Python client_app.py

 

Step 6: Real -Time Visual Analytics Dashboard with playwright PDF snapsjot engine

Launch the streamlit visualization interface to track spend velocity, request Anomalies and transaction distributions in real time.

 

Command : streamlit run dashboard.py

import sqlite3

import pandas as pd

import plotly.express as px

import streamlit as st

 

# Page configuration

st.set_page_config(page_title=”API Cost & Anomaly Guardrail”, layout=”wide”)

 

st.title(“🛡️ API Cost Guardrail & Anomaly Monitor”)

st.caption(“Real-Time Token Usage, Spend Velocity & Circuit Breaker Tracking”)

 

def load_data():

conn = sqlite3.connect(“guardrail.db”)

cursor = conn.cursor()

 

# Ensure table exists before querying to avoid missing table errors

cursor.execute(“””

CREATE TABLE IF NOT EXISTS request_logs (

id INTEGER PRIMARY KEY AUTOINCREMENT,

timestamp DATETIME,

tenant_id TEXT,

estimated_tokens INTEGER,

estimated_cost_usd REAL,

status TEXT,

z_score REAL

)

“””)

conn.commit()

 

# Read data into DataFrame

df = pd.read_sql_query(“SELECT * FROM request_logs ORDER BY timestamp DESC”, conn)

conn.close()

 

if not df.empty:

df[“timestamp”] = pd.to_datetime(df[“timestamp”])

return df

 

# Sidebar settings

st.sidebar.header(“Dashboard Settings”)

auto_refresh = st.sidebar.checkbox(“Auto-refresh data”, value=False)

if auto_refresh:

# Refresh every 3 seconds

st.empty()

time_delay = 3

 

df = load_data()

 

# Top Banner Metrics

col1, col2, col3, col4 = st.columns(4)

 

if not df.empty:

total_spend = df[“estimated_cost_usd”].sum()

total_requests = len(df)

blocked_requests = len(df[df[“status”] == “BLOCKED_ANOMALY”])

total_tokens = df[“estimated_tokens”].sum()

 

col1.metric(“Total Estimated Spend”, f”${total_spend:.4f}”)

col2.metric(“Total Requests Intercepted”, f”{total_requests:,}”)

col3.metric(

“Anomalies Blocked”, f”{blocked_requests:,}”, delta_color=”inverse”

)

col4.metric(“Total Tokens Processed”, f”{total_tokens:,}”)

 

st.markdown(“—“)

 

# Layout: Chart + Blocked Logs

left_col, right_col = st.columns([2, 1])

 

with left_col:

st.subheader(“Spend Velocity Over Time”)

fig = px.strip(

df,

x=”timestamp”,

y=”estimated_cost_usd”,

color=”status”,

hover_data=[“tenant_id”, “estimated_tokens”, “z_score”],

color_discrete_map={

“PASSED”: “#2ECC71”,

“BLOCKED_ANOMALY”: “#E74C3C”,

},

title=”Request Cost Distribution ($ USD)”,

)

st.plotly_chart(fig, use_container_width=True)

 

with right_col:

st.subheader(“Recent Anomaly Alerts”)

blocked_df = df[df[“status”] == “BLOCKED_ANOMALY”][

[“timestamp”, “tenant_id”, “estimated_cost_usd”, “z_score”]

].head(10)

 

if not blocked_df.empty:

st.dataframe(blocked_df, hide_index=True, use_container_width=True)

else:

st.success(“No anomalies detected recently.”)

 

st.subheader(“Raw Transaction Logs”)

st.dataframe(df.head(20), use_container_width=True)

 

else:

st.info(

“Database connected successfully! No request logs recorded yet. Start `proxy.py` and run `test_traffic.py` to stream live traffic into the dashboard.”

)

 

# Manual Refresh Button

if st.button(“Refresh Data”):

st.rerun()

 

 

Step 7: PDF snapshot engine

 

Command : Python dashboard_pdf_automation.py

import asyncio

import time

import oracledb

from playwright.async_api import async_playwright

 

# ——————————————————————-

# 1. Oracle Database Connection Configuration

# ——————————————————————-

DB_USER = “username”

DB_PASSWORD = “password”

DB_DSN = “DSN details”

 

def save_pdf_to_oracle(pdf_bytes, filename):

“””Inserts the binary PDF data (BLOB) into Oracle Database.”””

try:

connection = oracledb.connect(

user=DB_USER,

password=DB_PASSWORD,

dsn=DB_DSN

)

cursor = connection.cursor()

 

sql = “””

INSERT INTO DASHBOARD_PDF_LOGS (DASHBOARD_NAME, PDF_CONTENT, FILE_NAME)

VALUES (:1, :2, :3)

“””

cursor.execute(sql, [“LLM Anomaly Dashboard”, pdf_bytes, filename])

connection.commit()

print(f” Successfully saved {filename} to Oracle DB!”)

 

except Exception as e:

print(f”❌ Database error: {e}”)

finally:

if ‘cursor’ in locals():

cursor.close()

if ‘connection’ in locals():

connection.close()

 

# ——————————————————————-

# 2. Browser Automation & PDF Generation

# ——————————————————————-

async def monitor_and_capture_dashboard(dashboard_url: str, poll_interval: int = 10):

“””

Monitors the dashboard. Captures PDF on initial load and whenever

it detects a page refresh/navigation event.

“””

async with async_playwright() as p:

browser = await p.chromium.launch(headless=True)

context = await browser.new_context()

page = await context.new_page()

 

async def capture_pdf():

“””Helper function to render PDF and save to DB.”””

timestamp = time.strftime(“%Y%m%d_%H%M%S”)

filename = f”dashboard_snap_{timestamp}.pdf”

print(f”📸 Generating PDF snapshot: {filename}…”)

 

# Generate PDF in memory (bytes)

pdf_bytes = await page.pdf(

format=”A4″,

print_background=True,

margin={“top”: “20px”, “right”: “20px”, “bottom”: “20px”, “left”: “20px”}

)

 

# Optional: Save locally in VS Code workspace

with open(filename, “wb”) as f:

f.write(pdf_bytes)

 

# Push directly to Oracle DB

save_pdf_to_oracle(pdf_bytes, filename)

 

# Event listener: Triggers capture automatically whenever the page reloads/refreshes

async def on_framenavigated(frame):

if frame == page.main_frame:

print(“\n🔄 Page refresh/navigation detected!”)

# Wait briefly for charts/CSS to fully render post-refresh

await asyncio.sleep(2)

await capture_pdf()

 

# Attach page reload listener

page.on(“framenavigated”, on_framenavigated)

 

print(f”🚀 Navigating to dashboard: {dashboard_url}”)

await page.goto(dashboard_url, wait_until=”networkidle”)

 

print(“👀 Monitoring page refreshes. Press Ctrl+C in VS Code terminal to stop.”)

 

# Keep the script running in an event loop

try:

while True:

await asyncio.sleep(1)

except KeyboardInterrupt:

print(“Stopping monitor…”)

finally:

await browser.close()

 

# ——————————————————————-

# 3. Execution Entry Point

# ——————————————————————-

if __name__ == “__main__”:

# Replace with your local or hosted dashboard URL

DASHBOARD_URL = “http://localhost:8501”  # e.g., Streamlit, React, or local web app

 

asyncio.run(monitor_and_capture_dashboard(DASHBOARD_URL))

 

 

 

Step 8: SQL developer Database Connection

 

CREATE TABLE DASHBOARD_PDF_LOGS (

PDF_ID          NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,

CREATED_AT      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

DASHBOARD_NAME  VARCHAR2(200),

PDF_CONTENT     BLOB,

FILE_NAME       VARCHAR2(250)

);

Note: Connect to the same database connection in the pdf generation engine.

 

 

Now comes the APEX Integration!!

 

 Step 1: Create an Oracle APEX Application
  1. Log in to your Oracle APEX workspace (for example, apex.oracle.com).
  2. Navigate to App Builder.
  3. Click Create.
  4. Select New Application.
  5. Enter Dashboard PDF Manageras the application name.
  6. Click Create Application.

 

Step 2: Create an Interactive Report Page
  1. Open your newly created application.
  2. Click Create Page.
  3. Select Interactive Report.
  4. Enter PDF Downloadsas the Page Name.
  5. Set Source Typeto SQL Query.
  6. Enter the following SQL query:

 

 

 

Step 3: Configure the BLOB Download Column
  1. Open the Page Designerfor the Interactive Report page.
  2. In the Renderingtree, expand Regions.
  3. Expand the PDF Downloads
  4. Expand Columns.
  5. Select the PDF_CONTENT
  6. In the Property Editor, set Typeto Download BLOB.
  7. Set the Headingto Download PDF.
  8. Scroll down to the BLOB Attributes
  9. Set Table Nameto DASHBOARD_PDF_LOGS.
  10. Set BLOB Columnto PDF_CONTENT.
  11. Set Primary Key Column 1to PDF_ID.

 

 

Step 4: Save and Run the Application
  1. Click the Save
  2. Click RunPage.
  3. The Interactive Report will display all records from the DASHBOARD_PDF_LOGS
  4. Locate the required PDF record.
  5. Click the Download
  6. Oracle APEX will download the selected PDF stored in the database to your local machine.

 

 

 

Conclusion

With the combination of FastAPI proxy guardrails, Playwright automation, Oracle Database BLOB storage, and Oracle APEX, we have created a full-fledged LLM cost monitoring system:

Proactive Budget Safeguarding: On-the-fly $Z$-score calculations prevent abnormal usage peaks before incurring any cost via API calls.

Automated Logging: The Playwright logs Streamlit dashboard renders in Oracle Database without requiring any manual effort.

Enterprise Accessibility: The non-technical users can easily review and download PDF reports from Oracle APEX interface.

Recent Posts