AI System Design: Quality, Cost, Latency, and Safety Tradeoffs

Prepare the learner for architecture work and system design interviews with realistic AI tradeoffs.

AI system design is not about picking the most impressive model - it is about making the right engineering tradeoffs for the problem's constraints. A 70B parameter model that answers correctly 95% of the time is not always better than a 7B model at 88% accuracy if the 7B model runs 10x faster and costs 20x less. The best AI engineers navigate quality, cost, latency, and safety tradeoffs explicitly rather than defaulting to "use the best available model."

The Tradeoff Space

Every AI system design decision lives in a four-dimensional space:

DimensionWhat mattersHow to measure
QualityTask accuracy, user satisfactionEval harness, human ratings, business metrics
CostCompute cost per request$/1K tokens (API), GPU-hours (self-hosted)
Latencyp50, p99 response timems from request to response complete
SafetyHarmful output rate, reliability, privacyAdversarial eval, audit, incident rate

These dimensions trade off. Better quality usually means larger models (higher cost, higher latency). Stronger safety usually means more filtering and validation (higher latency). Understanding the constraints lets you make defensible design decisions.

Architecture Patterns and When to Use Each

Pattern 1: Direct Prompting

Use when: general-purpose tasks, low-volume features, prototyping.

User → Prompt → LLM API → Response

Cost: high per-request (frontier models). Latency: 500ms–2s. Quality: high out-of-the-box.

Suitable for: internal tools, low-frequency features, tasks with high variance that need general reasoning.

Pattern 2: RAG (Retrieval-Augmented Generation)

Use when: knowledge is domain-specific, frequently updated, or too large for context.

User → Retrieve(knowledge base) → Prompt + Context → LLM → Response

Cost: retrieval is cheap; LLM call is reduced by using smaller model with better context. Latency: +50–200ms for retrieval.

Suitable for: Q&A over private docs, customer support, knowledge bases. Default choice over fine-tuning for factual grounding.

Pattern 3: Fine-Tuned Smaller Model

Use when: high-volume, narrow task, consistent format.

User → Prompt → Fine-tuned 7B model → Response

Cost: low per-request after upfront training cost. Latency: 50–200ms on GPU.

Suitable for: classification, extraction, structured generation at scale. Break-even vs. API typically at ~1M requests.

Pattern 4: Classifier + LLM Routing

Use when: most requests are simple but some require complex reasoning.

User → Fast Classifier → Simple: small model
                       → Complex: large model
python
def route_request(query: str) -> str: complexity = classify_complexity(query) # Fast, cheap classifier if complexity == "simple": return call_llm("gpt-4o-mini", query, max_tokens=256) else: return call_llm("gpt-4o", query, max_tokens=1024) # Typical result: 70% of queries go to cheap model # Cost reduction: 60-80% with < 5% quality degradation on aggregate

Pattern 5: Cached Responses

Use when: many users ask semantically similar questions.

python
from sentence_transformers import SentenceTransformer import numpy as np embedding_model = SentenceTransformer("bge-small-en-v1.5") cache = {} # In production: Redis + FAISS index over cached queries def semantic_cache_lookup(query: str, threshold: float = 0.95) -> str | None: query_emb = embedding_model.encode([query], normalize_embeddings=True)[0] for cached_query, (cached_emb, cached_response) in cache.items(): similarity = np.dot(query_emb, cached_emb) if similarity >= threshold: return cached_response return None

Semantic caching (not just exact-match) can achieve 30-50% cache hit rates on FAQ-style use cases, reducing LLM calls proportionally.

Safety and Reliability Design

Input Validation

python
def validate_input(user_message: str) -> tuple[bool, str]: # Length limit if len(user_message) > 10_000: return False, "Message too long. Please be more concise." # Basic injection patterns injection_patterns = [ "ignore all previous instructions", "you are now", "system: ", ] lower = user_message.lower() for pattern in injection_patterns: if pattern in lower: return False, "Input contains disallowed patterns." return True, ""

Output Guardrails

python
from pydantic import BaseModel, validator from typing import Optional import re class SafeResponse(BaseModel): content: str blocked: bool = False block_reason: Optional[str] = None @validator("content") def check_pii(cls, v): # Detect and redact common PII patterns v = re.sub(r'\b\d{3}-\d{2}-\d{4}\b', '[SSN REDACTED]', v) v = re.sub(r'\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b', '[CARD REDACTED]', v) return v def apply_guardrails(response: str) -> SafeResponse: # Content moderation API call # In production: OpenAI Moderation API, Azure Content Safety, or custom classifier is_safe = check_content_safety(response) if not is_safe: return SafeResponse(content="", blocked=True, block_reason="Content policy violation") return SafeResponse(content=response)

Graceful Degradation

python
def answer_with_fallback(query: str) -> dict: try: # Tier 1: full RAG + large model return {"answer": rag_answer(query), "tier": "full"} except Exception as e: log_error("rag_failed", e) try: # Tier 2: direct LLM without retrieval return {"answer": direct_llm(query), "tier": "no_rag"} except Exception as e2: log_error("llm_failed", e2) # Tier 3: static fallback return { "answer": "I am unable to process your request right now. Please try again later.", "tier": "fallback" }

Cost Modeling

Before committing to an architecture, model the cost at your expected usage:

python
def estimate_monthly_cost( monthly_requests: int, avg_input_tokens: int, avg_output_tokens: int, model: str = "gpt-4o-mini", ) -> dict: # Example rates - always check current provider pricing pricing = { "gpt-4o-mini": {"input": 0.15, "output": 0.60}, # per 1M tokens "gpt-4o": {"input": 2.50, "output": 10.00}, "claude-3-5-haiku": {"input": 0.80, "output": 4.00}, } rates = pricing.get(model, {"input": 2.0, "output": 8.0}) input_cost = (monthly_requests * avg_input_tokens / 1_000_000) * rates["input"] output_cost = (monthly_requests * avg_output_tokens / 1_000_000) * rates["output"] total = input_cost + output_cost return { "model": model, "monthly_requests": monthly_requests, "monthly_cost_usd": round(total, 2), "cost_per_request_cents": round(total / monthly_requests * 100, 4), } # Compare architectures for req_count in [10_000, 100_000, 1_000_000]: estimate = estimate_monthly_cost(req_count, avg_input_tokens=500, avg_output_tokens=200) print(estimate)

At 1M requests/month with gpt-4o-mini: ~225/month.At1Mrequestswithgpt4o: 225/month. At 1M requests with gpt-4o: ~3,250/month. The routing pattern (70% mini, 30% 4o) costs ~$1,130. Cost modeling informs whether fine-tuning a self-hosted model is worth the engineering investment.

Common Mistakes and Bad Instincts

Defaulting to the most capable model for everything. GPT-4o is not always better than GPT-4o-mini for structured extraction tasks where the format is well-defined. Benchmark both before assuming the larger model is worth 10x the cost.

Not defining a fallback path before launch. "We'll add a fallback if it breaks" becomes "we're down because we have no fallback." Design the degradation path as part of the feature, not as a future ticket.

Treating safety as a post-launch concern. Output safety issues are much harder to fix in production than in design. Run red-team testing and apply guardrails before launch, not after the first incident.

Building a complex multi-model pipeline when a single model with better prompting would work. Complexity compounds failure rates. Each additional LLM call is a new failure point and a new cost. Start with the simplest architecture that achieves your quality target.

Where to Go Next

  • portfolio-conversion-turning-engineering-work-into-ml-evidence: articulate these system design decisions as portfolio evidence for ML engineering roles
  • interview-readiness-for-ml-ai-engineering-roles: practice system design interview questions using this framework

Module 31 of 34 · Software Engineer to ML/AI Engineer

Related Posts

More posts

Open-Weight and Small Models in 2026: When to Self-Host

Open-weight models are competitive, small models run on a phone, and the API-for-everything default is no longer obviously right. Here is a decision framework for self-hosting versus API, where small models win, what mixture-of-experts changes about the parameter count, and the hybrid most teams end up with.

#open-weight#slm#on-device#model-routing#serving#mlops

ML Model to Production: A Complete Walkthrough

Most ML models die in notebooks. Walk through the full path from trained model to live API endpoint serving real traffic - packaging, containerizing, deploying, and monitoring.

#deployment#mlops#serving

Model Versioning with MLflow: Practical Guide

Without model versioning, you cannot reproduce results, roll back broken deployments, or compare experiments. MLflow gives you a practical registry - here is how to use it well.

#mlops#experiment-tracking#deployment