TLDR;
Developers write code. They call models. They build agents and give them tools. They do this without a proxy. The keys leak. The bills run high. The data goes out and does not come back. Old gateways do not understand tokens. They do not know prompts. You need an AI gateway.
The Chaos
It is the wild west. Developers hardcode API keys. They send customer data to external servers. They give autonomous agents direct access to databases and shell environments.
graph TD
App1[App] -->|Raw Prompts & Secrets| OpenAI[OpenAI API]
App2[Agent] -->|Direct Raw SQL & Shell| DB[(Company Database)]
App2 -->|Raw Prompts| Gemini[Gemini API]
App3[App] -->|Hardcoded Keys| Anthropic[Anthropic API]
A standard gateway does not help. It knows HTTP and REST. It does not know tokens. It cannot read prompts. It does not understand tool calls.
The Gateway
An AI gateway is a clean proxy. It sits in the middle. It sees all the communication.
graph TD
classDef gw fill:#fff9c4,stroke:#fbc02d,stroke-width:2px;
App[AI App] --> GW[AI Gateway]
Agent[Agent] --> GW
subgraph Target ["Target Systems"]
Models[LLM APIs
Northbound]
Tools[Databases & APIs
Southbound]
end
GW -->|Redacted & Cached Request| Models
GW -->|Validated Parameters| Tools
Governance requires three distinct proxies inside the gateway.
1. The Model Proxy (Northbound)
This proxy faces the model providers. The client talks to the gateway. The gateway talks to Gemini, Anthropic, or OpenAI.
sequenceDiagram
App->>Gateway: User Prompt (includes raw email & secret key)
Note over Gateway: Scans content for secrets & email
Note over Gateway: Redacts sensitive data
Note over Gateway: Injects real model key from vault
Gateway->>LLM: Clean Prompt + Secure Key
LLM->>Gateway: Response
Note over Gateway: Logs token usage
Gateway->>App: Response
- Secrets: The gateway holds the keys. The developer gets an internal token. The real API key never leaves the vault.
- Privacy: The gateway scans the prompt. It redacts emails, passwords, and credit cards before they go to the cloud.
- Cost: It caches responses. If two users ask the same question, the second user gets the cached answer. It is fast, and it costs nothing.
2. The Tool Proxy (Southbound)
Agents use tools. Tools are functions that query databases, read files, or call internal APIs. An agent with unmonitored tools is dangerous.
sequenceDiagram
Agent->>Gateway: Execute Tool: delete_user(id=99)
Note over Gateway: Gateway checks Client Identity
Note over Gateway: Verifies authorization for 'delete_user'
Gateway--xTools: Blocked (Unauthorized)
Gateway-->>Agent: Error: Tool execution rejected
The tool proxy stops the damage:
- Validation: The gateway checks the parameters. It ensures the model is not passing malicious SQL or shell commands.
- Authentication: The gateway verifies if this specific client is allowed to run this tool.
- Human Gate: If a tool changes state—like deleting a user or sending money—the gateway pauses. It asks a human for approval. The agent waits.
3. The Agent Proxy (East-West)
Agents talk to other agents. They delegate tasks. This proxy coordinates the mesh:
- Integrity: It signs messages. An unauthorized agent cannot inject instructions.
- Loop Control: Agents can get stuck in infinite discussions. The proxy counts the messages. It stops the loop before the tokens burn your budget.
A Simple Gateway in Code
This is how you start. Here is a simple middleware written in Python. It redacts data, logs tokens, and proxies the call.
import os
import re
import httpx
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
import logging
app = FastAPI()
logging.basicConfig(level=logging.INFO)
# Hemingway-style: Simple, concrete rules.
SENSITIVE_PATTERNS = [
r"(?i)password\s*=\s*['\"][^'\"]+['\"]",
r"(?i)api[-_]key\s*=\s*['\"][^'\"]+['\"]",
r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
]
def clean_prompt(prompt: str) -> str:
cleaned = prompt
for pattern in SENSITIVE_PATTERNS:
cleaned = re.sub(pattern, "[REDACTED]", cleaned)
return cleaned
@app.post("/v1/chat/completions")
async def proxy(request: Request):
body = await request.json()
# Clean the input
messages = body.get("messages", [])
for msg in messages:
if "content" in msg and isinstance(msg["content"], str):
msg["content"] = clean_prompt(msg["content"])
headers = {
"Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}",
"Content-Type": "application/json"
}
async with httpx.AsyncClient() as client:
try:
response = await client.post(
"https://api.openai.com/v1/chat/completions",
json=body,
headers=headers,
timeout=30.0
)
response.raise_for_status()
data = response.json()
# Log the cost
usage = data.get("usage", {})
logging.info(
f"Client: {request.client.host} | "
f"Tokens: {usage.get('total_tokens', 0)}"
)
return JSONResponse(content=data)
except Exception as e:
logging.error(f"Failed: {str(e)}")
raise HTTPException(status_code=500, detail="Gateway Error")Deployment
Deploy the gateway close to your code. Use a sidecar. Store your cache in Redis. It must be fast. Enforce schemas on your tools.
If you build with AI, stop calling models directly. Use a gateway. It is the only safe way.