Building AI-powered applications that are reliable, cost-effective, and maintainable in production requires more than just calling an AI model. You need fallback strategies, error handling, cost optimization, monitoring, and the ability to switch models without rewriting code. A unified AI API simplifies all of this by providing a single interface to multiple models.
This guide covers architecture patterns, production code examples, and best practices for building AI applications with a unified API gateway like Bring AI.
Why Unified APIs Simplify Development
A unified AI API provides a single endpoint that accepts requests in a standard format (like OpenAI’s Chat Completions API) and routes them to any supported model. This eliminates several development pain points:
1. Single Integration Point
Without a unified API, accessing GPT, Claude, and Gemini requires three separate SDKs, three API keys, three authentication methods, and three different request/response formats. With a unified API, you integrate once and access 200+ models.
# Without unified API — three SDKs, three formats
import openai
import anthropic
import google.generativeai
# Three different clients, three different formats
openai_client = openai.OpenAI(api_key="sk-...")
anthropic_client = anthropic.Anthropic(api_key="sk-ant-...")
gemini_client = google.generativeai.GenerativeModel("gemini-2.0-flash")
# Three different calling conventions
gpt_resp = openai_client.chat.completions.create(model="gpt-4o", messages=[...])
claude_resp = anthropic_client.messages.create(model="claude-3-5-sonnet", max_tokens=1024, messages=[...])
gemini_resp = gemini_client.generate_content("Hello")
# With Bring AI — one SDK, one format, 200+ models
from openai import OpenAI
client = OpenAI(
base_url="https://bring.buatjalan.xyz/v1",
api_key="bring-your-key",
)
# Same format for every model
gpt_resp = client.chat.completions.create(model="gpt-4o", messages=[...])
claude_resp = client.chat.completions.create(model="claude-3-5-sonnet", messages=[...])
gemini_resp = client.chat.completions.create(model="gemini-2.0-flash", messages=[...])
2. Simplified Dependency Management
One SDK dependency instead of three. One API key to manage. One billing relationship. One set of rate limits to understand. One place to monitor usage.
3. Model Portability
When a new model is released (e.g., GPT-5 or Claude 4), you don’t need to add a new SDK or rewrite integration code. Just change the model parameter. This makes your application future-proof.
4. Cost Optimization
With access to all models through one API, you can dynamically select the cheapest model that meets your quality requirements. Route simple queries to Gemini Flash ($0.10/$0.40 per million tokens) and reserve GPT-4o ($2.50/$10.00) for complex tasks.
Architecture Patterns
Pattern 1: Model Fallback
The fallback pattern tries a primary model and falls back to alternatives if it fails (rate limit, timeout, or error). This ensures your application remains available even when a specific model provider experiences issues.
User Request
│
▼
┌─────────┐ Success ┌──────────┐
│ Primary │───────────────▶│ Response │
│ Model │ └──────────┘
└────┬────┘
│ Failure
▼
┌─────────┐ Success ┌──────────┐
│Fallback1│───────────────▶│ Response │
│ Model │ └──────────┘
└────┬────┘
│ Failure
▼
┌─────────┐ Success ┌──────────┐
│Fallback2│───────────────▶│ Response │
│ Model │ └──────────┘
└─────────┘
Pattern 2: Load Balancing
Distribute requests across multiple models to avoid rate limits and optimize costs:
┌──────────────────┐
│ Load Balancer │
└────────┬─────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ GPT-4o │ │ Claude │ │ Gemini │
│ (40%) │ │ (30%) │ │ (30%) │
└─────────┘ └─────────┘ └─────────┘
Pattern 3: Cost Optimization (Model Routing)
Route requests to the cheapest model capable of handling the task:
User Request
│
▼
┌──────────────┐
│ Classifier │
└──────┬───────┘
│
┌────┼────┬────────┐
▼ ▼ ▼ ▼
Simple Complex Long Vision
│ │ │ │
▼ ▼ ▼ ▼
Flash Sonnet Pro GPT-4o
$0.10 $3.00 $1.25 $2.50
Pattern 4: A/B Testing
Test different models on a subset of traffic to compare quality and cost:
User Request
│
▼
┌──────────────┐
│ A/B Router │
└──┬───────┬───┘
│ │
▼ ▼
90% 10%
GPT-4o Claude
│ │
▼ ▼
┌─────────────┐
│ Compare │
│ Quality │
│ & Cost │
└─────────────┘
Code Examples
Python Chat App with Model Fallback
Here’s a complete Python application that implements the fallback pattern with cost tracking:
import time
import logging
from dataclasses import dataclass
from typing import Optional
from openai import OpenAI
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("ai-app")
@dataclass
class ModelConfig:
"""Configuration for a model in the fallback chain."""
name: str
max_tokens: int = 1024
temperature: float = 0.7
timeout: int = 30
@dataclass
class ChatResult:
"""Result of a chat completion."""
content: str
model: str
input_tokens: int
output_tokens: int
cost_usd: float
latency_ms: int
class ProductionChatApp:
"""Production-ready chat application with model fallback."""
# Model pricing (per million tokens)
PRICING = {
"gpt-4o": {"input": 2.50, "output": 10.00},
"claude-3-5-sonnet": {"input": 3.00, "output": 15.00},
"gemini-2.0-flash": {"input": 0.10, "output": 0.40},
"deepseek-v3": {"input": 0.27, "output": 1.10},
}
def __init__(self, api_key: str):
self.client = OpenAI(
base_url="https://bring.buatjalan.xyz/v1",
api_key=api_key,
)
# Fallback chain: try these models in order
self.fallback_chain = [
ModelConfig("gpt-4o"),
ModelConfig("claude-3-5-sonnet"),
ModelConfig("gemini-2.0-flash"),
]
def _calculate_cost(self, model: str, input_tokens: int, output_tokens: int) -> float:
"""Calculate the cost of a request."""
pricing = self.PRICING.get(model, {"input": 0, "output": 0})
cost = (input_tokens / 1_000_000 * pricing["input"]) + \
(output_tokens / 1_000_000 * pricing["output"])
return round(cost, 6)
def chat(
self,
message: str,
system_prompt: str = "You are a helpful assistant.",
conversation_history: Optional[list] = None,
) -> ChatResult:
"""Send a chat message with automatic model fallback."""
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
if conversation_history:
messages.extend(conversation_history)
messages.append({"role": "user", "content": message})
errors = []
for model_config in self.fallback_chain:
try:
start_time = time.time()
response = self.client.chat.completions.create(
model=model_config.name,
messages=messages,
max_tokens=model_config.max_tokens,
temperature=model_config.temperature,
timeout=model_config.timeout,
)
latency_ms = int((time.time() - start_time) * 1000)
usage = response.usage
result = ChatResult(
content=response.choices[0].message.content,
model=model_config.name,
input_tokens=usage.prompt_tokens if usage else 0,
output_tokens=usage.completion_tokens if usage else 0,
cost_usd=self._calculate_cost(
model_config.name,
usage.prompt_tokens if usage else 0,
usage.completion_tokens if usage else 0,
),
latency_ms=latency_ms,
)
logger.info(
f"Success with {model_config.name}: "
f"{result.input_tokens}+{result.output_tokens} tokens, "
f"${result.cost_usd:.4f}, {result.latency_ms}ms"
)
return result
except Exception as e:
logger.warning(f"{model_config.name} failed: {e}")
errors.append(f"{model_config.name}: {str(e)}")
continue
# All models failed
raise RuntimeError(f"All models failed: {errors}")
def chat_with_retry(
self,
message: str,
max_retries: int = 3,
**kwargs,
) -> ChatResult:
"""Chat with exponential backoff retry."""
last_error = None
for attempt in range(max_retries):
try:
return self.chat(message, **kwargs)
except RuntimeError as e:
last_error = e
wait_time = 2 ** attempt # Exponential backoff
logger.info(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
time.sleep(wait_time)
raise last_error
# Usage
app = ProductionChatApp(api_key="bring-your-key")
result = app.chat(
message="Explain the difference between TCP and UDP.",
system_prompt="You are a networking expert. Explain concepts clearly.",
)
print(f"Response: {result.content}")
print(f"Model: {result.model}")
print(f"Cost: ${result.cost_usd:.4f}")
print(f"Latency: {result.latency_ms}ms")
Node.js App with Streaming
Here’s a Node.js application that implements streaming with error handling and cost tracking:
import OpenAI from "openai";
class ProductionChatApp {
constructor(apiKey) {
this.client = new OpenAI({
baseURL: "https://bring.buatjalan.xyz/v1",
apiKey: apiKey,
});
this.fallbackChain = [
"gpt-4o",
"claude-3-5-sonnet",
"gemini-2.0-flash",
];
this.PRICING = {
"gpt-4o": { input: 2.5, output: 10.0 },
"claude-3-5-sonnet": { input: 3.0, output: 15.0 },
"gemini-2.0-flash": { input: 0.1, output: 0.4 },
};
this.totalCost = 0;
this.requestCount = 0;
}
/**
* Stream a chat completion with automatic fallback
*/
async streamChat(message, options = {}) {
const {
systemPrompt = "You are a helpful assistant.",
onToken = (token) => process.stdout.write(token),
onError = (error) => console.error(error),
} = options;
const messages = [
{ role: "system", content: systemPrompt },
{ role: "user", content: message },
];
const errors = [];
for (const model of this.fallbackChain) {
try {
const startTime = Date.now();
const stream = await this.client.chat.completions.create({
model: model,
messages: messages,
stream: true,
stream_options: { include_usage: true },
});
let outputContent = "";
let usage = null;
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) {
outputContent += delta;
onToken(delta);
}
// Capture usage from the final chunk
if (chunk.usage) {
usage = chunk.usage;
}
}
const latencyMs = Date.now() - startTime;
const cost = this.calculateCost(model, usage);
this.totalCost += cost;
this.requestCount++;
console.log(
`\n[Model: ${model}, Tokens: ${usage?.prompt_tokens}+${usage?.completion_tokens}, ` +
`Cost: $${cost.toFixed(4)}, Latency: ${latencyMs}ms]`
);
return {
content: outputContent,
model: model,
inputTokens: usage?.prompt_tokens || 0,
outputTokens: usage?.completion_tokens || 0,
cost: cost,
latencyMs: latencyMs,
};
} catch (error) {
console.error(`Model ${model} failed: ${error.message}`);
errors.push(`${model}: ${error.message}`);
continue;
}
}
throw new Error(`All models failed: ${errors.join(", ")}`);
}
/**
* Calculate cost for a request
*/
calculateCost(model, usage) {
const pricing = this.PRICING[model] || { input: 0, output: 0 };
const inputCost = (usage?.prompt_tokens || 0) / 1_000_000 * pricing.input;
const outputCost = (usage?.completion_tokens || 0) / 1_000_000 * pricing.output;
return Math.round((inputCost + outputCost) * 1_000_000) / 1_000_000;
}
/**
* Chat with retry and exponential backoff
*/
async chatWithRetry(message, maxRetries = 3, options = {}) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await this.streamChat(message, options);
} catch (error) {
lastError = error;
const waitTime = Math.pow(2, attempt) * 1000;
console.log(`Attempt ${attempt + 1} failed, retrying in ${waitTime}ms...`);
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
}
throw lastError;
}
/**
* Get usage statistics
*/
getStats() {
return {
requestCount: this.requestCount,
totalCost: this.totalCost,
avgCostPerRequest: this.requestCount > 0
? this.totalCost / this.requestCount
: 0,
};
}
}
// Usage
const app = new ProductionChatApp("bring-your-api-key");
const result = await app.streamChat(
"Write a TypeScript function that debounces async functions.",
{
systemPrompt: "You are a senior TypeScript developer.",
onToken: (token) => process.stdout.write(token),
}
);
console.log("\n--- Stats ---");
console.log(app.getStats());
Production Considerations
Error Handling
Production AI applications must handle various failure modes gracefully:
from openai import (
APITimeoutError,
RateLimitError,
APIConnectionError,
InternalServerError,
)
def robust_chat(client, message, model="gpt-4o"):
"""Chat with comprehensive error handling."""
try:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": message}],
)
return response.choices[0].message.content
except RateLimitError:
# Rate limited — retry with backoff or switch model
logger.warning(f"Rate limited on {model}, falling back...")
return robust_chat(client, message, model="claude-3-5-sonnet")
except APITimeoutError:
# Request timed out — retry or fall back
logger.error(f"Timeout on {model}")
raise
except APIConnectionError:
# Network issue — retry with backoff
logger.error(f"Connection error to {model}")
time.sleep(5)
return robust_chat(client, message, model)
except InternalServerError:
# Provider error — fall back to different provider
logger.error(f"Server error on {model}")
return robust_chat(client, message, model="gemini-2.0-flash")
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise
Retries with Exponential Backoff
Implement retry logic with exponential backoff and jitter:
import random
import time
def retry_with_backoff(
func,
max_retries=3,
base_delay=1.0,
max_delay=60.0,
exceptions=(Exception,),
):
"""Retry a function with exponential backoff and jitter."""
for attempt in range(max_retries):
try:
return func()
except exceptions as e:
if attempt == max_retries - 1:
raise
# Exponential backoff with jitter
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
logger.info(f"Attempt {attempt + 1} failed: {e}, retrying in {delay:.1f}s")
time.sleep(delay)
# Usage
response = retry_with_backoff(
lambda: client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
),
max_retries=3,
exceptions=(APITimeoutError, APIConnectionError, InternalServerError),
)
Monitoring and Observability
Monitor your AI API usage to detect issues early and optimize costs:
import time
from collections import defaultdict
from dataclasses import dataclass, field
@dataclass
class MetricsCollector:
"""Collect and report AI API metrics."""
request_count: int = 0
error_count: int = 0
total_cost: float = 0.0
total_input_tokens: int = 0
total_output_tokens: int = 0
model_usage: dict = field(default_factory=lambda: defaultdict(lambda: {
"requests": 0, "errors": 0, "cost": 0.0, "tokens": 0
}))
latency_samples: list = field(default_factory=list)
def record(self, model, input_tokens, output_tokens, cost, latency_ms, success=True):
self.request_count += 1
self.total_cost += cost
self.total_input_tokens += input_tokens
self.total_output_tokens += output_tokens
self.latency_samples.append(latency_ms)
stats = self.model_usage[model]
stats["requests"] += 1
stats["cost"] += cost
stats["tokens"] += input_tokens + output_tokens
if not success:
self.error_count += 1
stats["errors"] += 1
def get_summary(self):
avg_latency = sum(self.latency_samples) / len(self.latency_samples) if self.latency_samples else 0
error_rate = self.error_count / self.request_count if self.request_count else 0
return {
"total_requests": self.request_count,
"error_rate": f"{error_rate:.2%}",
"total_cost": f"${self.total_cost:.2f}",
"avg_cost_per_request": f"${self.total_cost / self.request_count:.4f}" if self.request_count else "$0",
"total_tokens": self.total_input_tokens + self.total_output_tokens,
"avg_latency_ms": f"{avg_latency:.0f}ms",
"by_model": dict(self.model_usage),
}
# Usage
metrics = MetricsCollector()
# After each API call
metrics.record(
model="gpt-4o",
input_tokens=500,
output_tokens=200,
cost=0.00725,
latency_ms=850,
)
# Get summary
print(metrics.get_summary())
Cost Monitoring Dashboard
Implement a simple cost monitoring endpoint:
from flask import Flask, jsonify
app = Flask(__name__)
metrics = MetricsCollector()
@app.route("/api/chat", methods=["POST"])
def chat():
# Your AI API call here
# Record metrics after each call
metrics.record(model="gpt-4o", ...)
return jsonify({"response": "..."})
@app.route("/metrics")
def get_metrics():
"""Endpoint for monitoring dashboard."""
return jsonify(metrics.get_summary())
@app.route("/health")
def health():
"""Health check endpoint."""
return jsonify({"status": "healthy", "models_available": 200})
Caching
Implement response caching to reduce costs for repeated queries:
import hashlib
import json
import redis # or use in-memory cache
cache = redis.Redis(host="localhost", port=6379)
def cached_chat(client, message, model="gpt-4o", ttl_seconds=3600):
"""Cache AI responses for repeated queries."""
# Create cache key from request
cache_key = hashlib.sha256(
json.dumps({"model": model, "message": message}).encode()
).hexdigest()
# Check cache
cached = cache.get(f"ai:{cache_key}")
if cached:
return json.loads(cached)
# Make API call
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": message}],
)
result = response.choices[0].message.content
# Cache the result
cache.setex(f"ai:{cache_key}", ttl_seconds, json.dumps(result))
return result
Connection Management
Reuse HTTP connections for better performance:
import httpx
from openai import OpenAI
# Use a shared HTTP client with connection pooling
http_client = httpx.Client(
limits=httpx.Limits(
max_connections=100,
max_keepalive_connections=20,
keepalive_expiry=30,
),
timeout=httpx.Timeout(30.0, connect=5.0),
)
client = OpenAI(
base_url="https://bring.buatjalan.xyz/v1",
api_key="bring-your-key",
http_client=http_client, # Reuse connections
)
FAQ
What is a unified AI API?
A unified AI API is a single endpoint that accepts requests in a standard format (like OpenAI’s Chat Completions API) and routes them to multiple AI model providers. It lets you use one SDK and one API key to access GPT, Claude, Gemini, DeepSeek, and 200+ other models. Learn more in our AI API gateway guide.
How do I implement model fallback in my AI application?
Implement a fallback chain that tries models in order. If the primary model fails (timeout, rate limit, error), try the next model. Bring AI makes this easy — just change the model parameter in your API call. See the Python and Node.js examples above for complete implementations.
How can I reduce my AI API costs in production?
Key strategies: (1) Use model routing to send simple queries to cheap models like Gemini Flash ($0.10/$0.40 per million tokens), (2) implement response caching for repeated queries, (3) set spending caps on API keys, (4) monitor usage with metrics collection, (5) use prompt caching for repeated system prompts. See our cheapest AI API comparison for pricing details.
How do I handle AI API rate limits?
Implement exponential backoff with jitter for retries. Use a unified API like Bring AI that provides built-in rate limiting. Distribute load across multiple models using the load balancing pattern. Monitor your rate limit headers to avoid hitting limits. Set up alerts for when you approach your limits.
Should I use streaming or batch processing?
Use streaming for real-time applications (chatbots, interactive tools) where users need immediate feedback. Use batch processing for non-urgent workloads (document summarization, data enrichment) to take advantage of batch discounts (often 50% off). Bring AI supports both streaming (with <200ms overhead) and batch processing.
How do I monitor my AI API usage?
Implement a metrics collection system that tracks: request count, error rate, cost per request, tokens used, latency, and per-model breakdowns. Bring AI provides built-in usage dashboards. You can also use the metrics endpoint pattern shown above to build custom monitoring. Set up alerts for unusual usage patterns or cost spikes.
What’s the best architecture for a production AI application?
The best architecture depends on your use case, but common patterns include: (1) Model fallback for reliability, (2) Model routing for cost optimization, (3) Caching for reducing repeated costs, (4) Streaming for responsive UX, (5) Monitoring for observability, and (6) Error handling with retries for robustness. See the code examples above for implementations of each pattern.
Can I use different models for different features in my app?
Yes — and you should. Use cheap models (Gemini Flash, DeepSeek V3) for high-volume simple tasks, and premium models (GPT-4o, Claude Sonnet) for complex reasoning. With Bring AI, switching models is as simple as changing the model parameter. See our models page for all available options and pricing for cost details.
Conclusion
Building production AI applications requires more than just calling an API — you need fallback strategies, error handling, cost optimization, caching, and monitoring. A unified AI API like Bring AI simplifies all of this by providing a single integration point to 200+ models with sub-200ms routing overhead.
The architecture patterns and code examples in this guide give you a starting point for building robust, cost-effective AI applications. Start with the fallback pattern for reliability, add model routing for cost optimization, implement caching for repeated queries, and set up monitoring for observability.
Ready to build? Sign up at bring.buatjalan.xyz, review the documentation, and start building production AI applications today.
Related articles: