Faisalaffan Blog
Faisalaffan Blog
Engineering27 min read

Modern AI Architecture: End-to-End

From the first user query to the response stored in the database. A complete guide to layers, tools, request flow, learning roadmap, and real-world production implementation โ€” including the newly released Google ADK.

Faisal Affan
Faisal Affan
Published
Gallery
1 / 3

Modern AI Architecture: End-to-End

From the first user query to the response stored in the database. A complete guide to layers, tools, request flow, learning roadmap, and real-world production implementation โ€” including the newly released Google ADK.

CORE PRINCIPLE

What makes an AI system production-grade isn't the model quality โ€” it's the quality of the architecture surrounding the model. Models can be swapped anytime. Good architecture remains solid.


Why You Need to Understand This

LLMs can only do one thing: text in โ†’ text out. They cannot remember previous conversations, access databases, execute code, or make multi-step decisions. All the "sophistication" you see in production AI isn't because the model is smart on its own โ€” it's because there's an architecture around the model coordinating all those capabilities.

Understanding this architecture gives you the ability to: choose the right tool for the right problem, design AI systems that are maintainable, and debug when something doesn't work as expected.


Layer Stack โ€” 8 Architecture Layers

A modern AI system consists of 8 layers working together. Click each layer below to see detailed explanations and their tools.

๐Ÿ‘ค Layer 1 โ€” User / Consumer (Entry Point)

How users interact with the AI system. There are three paths: via API (developer), UI (end user), or CLI (developer tooling). All of them boil down to the same HTTP request โ€” only the presentation layer differs.

NOTE

All these paths ultimately send the same thing: a messages[] array to the LLM. The only difference is how the stream is handled and rendered to the user.

๐Ÿ”Œ Layer 2 โ€” Model Gateway (Routing & Fallback)

Abstraction layer above all LLM providers. One interface, swap models without changing code. Handles retry, fallback to other providers when down, centralized cost tracking, and load balancing across API keys.

Python
# Without gateway โ€” different code per provider
client.chat.completions.create(model="gpt-4o", ...)      # OpenAI
client.messages.create(model="claude-sonnet-4-6", ...) # Anthropic

# With LiteLLM โ€” one interface for all providers
litellm.completion(model="gpt-4o", messages=[...])
litellm.completion(model="claude-sonnet-4-6", messages=[...])
litellm.completion(model="gemini/gemini-2.0-flash", messages=[...])

# Automatic fallback when provider is down
litellm.completion(
  model="gpt-4o",
  fallbacks=["claude-sonnet-4-6", "gemini/gemini-2.0-flash"]
)
๐Ÿ”— Layer 3 โ€” Orchestration & Agent (Core Logic)

The brain of the AI system. Coordinates tools, memory, RAG, and multi-step reasoning. This is the layer you need to study most. The fundamental pattern is the ReAct Loop: Reason โ†’ Act โ†’ Observe โ†’ repeat until done.

๐Ÿ“„ Layer 4 โ€” Data & Knowledge / RAG (RAG Pipeline)

Ingest documents โ†’ parse โ†’ chunk โ†’ embed โ†’ store โ†’ retrieve. The foundation of RAG. This pipeline runs offline (indexing) and online (retrieval per request). Quality at this step determines the quality of AI answers.

๐Ÿงฐ Layer 5 โ€” Tools & External Systems (Agent Capabilities)

Tools the agent can call: search the web, read/write databases, execute code, access external APIs. The more high-quality tools available, the more powerful the agent. Tool calling isn't magic โ€” the LLM outputs a JSON descriptor, the orchestration layer executes the function.

Python
# Tool calling: not magic, but a simple mechanism

# 1. Register tool with the agent
@tool
def check_inventory(product_id: str) -> dict:
    """Check product stock from database"""
    return db.query("SELECT stock FROM products WHERE id = ?", product_id)

# 2. LLM OUTPUT (not direct execution):
# { "tool_call": { "name": "check_inventory", "args": {"product_id": "A"} } }

# 3. Orchestration layer executes the function, injects result into context
# 4. LLM is called again with the enriched context
๐Ÿงฉ Layer 6 โ€” Memory & State (Context Persistence)

LLMs are stateless by default โ€” each request stands alone. The memory layer gives the agent the ability to "remember" across sessions. There are 4 types: in-context (conversation history), external DB (Redis), episodic (what happened), semantic (what is known).

๐Ÿ“Š Layer 7 โ€” Eval & Observability (Production Quality)

Without observability, you're blind in production. Trace every LLM call, measure latency/cost/output quality, detect prompt regressions. LangFuse is mandatory for every AI feature going to production.

๐Ÿš€ Layer 8 โ€” Deployment & Infra (Production Serving)

Serve your own model (vLLM) or use managed APIs. K8s for container orchestration. FastAPI as the API layer. LGTM stack for infra observability. LangFuse for AI-layer observability.


Request Flow โ€” The Journey of a Single Query

Below is the complete journey of a single user query from start to response received. No step happens by magic โ€” everything is explicit and traceable.

๐Ÿ‘ค User query REST / UI / Voice / CLI ๐Ÿ”Œ Model Gateway LiteLLM โ€” routing, fallback, cost tracking ๐Ÿ”— Orchestration / Agent LangGraph ยท LlamaIndex ยท Google ADK ยท CrewAI ๐Ÿ“„ RAG Pipeline Retrieve โ†’ Rerank โ†’ Inject ๐Ÿงฉ Memory Mem0 ยท Zep ยท Redis ๐Ÿงฐ Tools Search ยท SQL ยท Browser ยท API โšก LLM Inference Context: query + docs + memory + tool results ๐Ÿ“Š LangFuse trace + eval ๐ŸŽฏ Structured Output Instructor + Pydantic validation + retry โœ… Response JSON / stream / markdown โ‘  ENTRY โ‘ก ROUTING โ‘ข ORCHESTRATION โ‘ฃ INFERENCE โ‘ค OUTPUT
End-to-end request flow โ€” every step can be traced via LangFuse

Fundamental Concepts

Before diving into specific frameworks, these are 6 concepts you must thoroughly understand. All frameworks โ€” LangGraph, Google ADK, CrewAI โ€” are built on top of these concepts.

4.1 ReAct Loop โ€” The Fundamental Pattern of All Agents

ReAct (Reason + Act) is the fundamental pattern used by all agentic frameworks. The agent loop repeats until the agent is confident the answer is sufficient.

User query enters the agent loop โ‘  Reason LLM thinks about next step Done? Enough info? Yes โ†’ Final Response generate answer for user No โ‘ก Act Call tool / query DB / search web Tool execution โ‘ข Observe inject into context loop back
ReAct loop โ€” all agentic frameworks implement this pattern
KEY POINT

Tool calling is not magic. The LLM outputs a JSON descriptor {"name": "check_stock", "args": {...}}. The orchestration layer executes the Python function, injects the result back into the context, then the LLM is called again. That's one ReAct cycle.

4.2 Context Window โ€” What the LLM Sees

An LLM is a mathematical function: f(tokens_in)โ†’probability_distribution(tokens_out)f(\text{tokens\_in}) \rightarrow \text{probability\_distribution}(\text{tokens\_out}). No memory, no state. Every call, it reads the entire context from scratch. All of the LLM's "knowledge" in a single request lives within this context window.

Component Content Token Budget Source
System prompt Identity, rules, output format, constraints 200โ€“2,000 hardcoded by developer
Retrieved docs Relevant chunks from vector DB 500โ€“4,000 RAG pipeline
Memory Facts from previous sessions 100โ€“500 Mem0 / Redis
Tool schema JSON descriptors of all available tools 200โ€“800 tool registry
History Previous conversation turns 500โ€“8,000 conversation state
User query Current query 50โ€“200 user input
Total โ€” ~4kโ€“15k โ€”
LOST-IN-THE-MIDDLE PROBLEM

LLMs pay more "attention" to the beginning and end of the context. Place important instructions in the system prompt (beginning) and the user query right before generation (end). Retrieved chunks in the middle are prone to being ignored if there are too many.

4.3 RAG Pipeline โ€” How It Works in Detail

RAG consists of two separate phases: Indexing (offline, one-time) and Retrieval (online, per request).

INDEXING (Offline โ€” One-Time Run)

๐Ÿ“ฅ Load documents โ†’\rightarrow ๐Ÿ” Parse (Docling) โ†’\rightarrow โœ‚๏ธ Chunk (~512 token, 50 overlap) โ†’\rightarrow ๐Ÿงฎ Embed (OpenAI / Jina) โ†’\rightarrow ๐Ÿ’พ Store (pgvector / Qdrant)


RETRIEVAL (Online โ€” Every Request)

๐Ÿ—ฃ๏ธ User query โ†’\rightarrow ๐Ÿงฎ Embed query โ†’\rightarrow ๐Ÿ” Similarity search (top-100) โ†’\rightarrow ๐Ÿ“Š Rerank (Cohere) โ†’\rightarrow top-5 โ†’\rightarrow ๐Ÿ’‰ Inject into context

Situation Use RAG? Reason
Q&A over internal documents (SOPs, catalogs) Yes (Green) Data not in LLM training set
Real-time data (prices, current stock) Tool call (Yellow) Need direct DB query, not RAG
General questions (how to use Python) No (Gray) LLM already knows
Compliance & audit trail Yes (Green) Must be able to cite specific sources
Inventory forecasting + live data Hybrid (Blue) RAG for historical docs, tool call for live data

4.4 Tool Calling โ€” Complete Mechanism

This is what enables LLMs to interact with the real world. Not magic โ€” it's an explicit, deterministic sequence from the orchestration side.

Python
# Step 1: Define tool as a regular Python function
@tool
def check_inventory(product_id: str) -> dict:
    """Check product stock from database. Args: product_id (str)"""
    return db.query("SELECT * FROM products WHERE id = %s", product_id)

# Step 2: LLM receives tool schema in context
# {"name": "check_inventory", "description": "Check stock...", "parameters": {...}}

# Step 3: LLM output (not execution!) โ€” JSON tool call
# {"type": "tool_call", "name": "check_inventory", "args": {"product_id": "A"}}

# Step 4: Orchestration layer executes the Python function
result = check_inventory(product_id="A")  # {"stock": 142, "safety_stock": 200}

# Step 5: Inject result as tool_result into context
# {"type": "tool_result", "content": {"stock": 142, "safety_stock": 200}}

# Step 6: LLM is called AGAIN with complete context + tool result
# This time the LLM can generate the final answer based on real data

4.5 Structured Output โ€” From Text to Data

Without structured output, the LLM is a text source. With structured output, the LLM becomes a callable function like an API โ€” its output can be directly processed by code without manual parsing.

Python
import instructor
from pydantic import BaseModel, Field
from typing import Literal

# 1. Define the desired schema
class InventoryAlert(BaseModel):
    product_id: str
    current_stock: int = Field(ge=0)
    status: Literal["ok", "low", "critical", "stockout"]
    reorder_suggested: bool
    suggested_quantity: int | None = None
    reason: str = Field(max_length=200)

# 2. Instructor wraps the LLM client
client = instructor.from_anthropic(Anthropic())

# 3. Response is directly a Python object โ€” not a string
alert = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=500,
    response_model=InventoryAlert,  # inject schema into tool calling
    messages=[{"role": "user", "content": context}]
)

# alert.status == "low" โ€” type-safe, use directly
if alert.reorder_suggested:
    trigger_purchase_order(alert.product_id, alert.suggested_quantity)
WHY INSTRUCTOR, NOT JSON MODE?

JSON mode only guarantees the output is valid JSON โ€” schema fields can still be missing or have wrong types. Instructor injects the Pydantic schema into the tool calling mechanism, validates the output, and auto-retries 3x if the output doesn't match the schema. That's what makes it reliable in production.

4.6 Observability with LangFuse

LangFuse is a dedicated AI observability layer โ€” analogous to Grafana for infra, but for LLM calls. Its data hierarchy: Trace (one end-to-end request) โ†’\rightarrow Span (unit of work) โ†’\rightarrow Generation (one LLM call) โ†’\rightarrow Score (evaluation).

Python
# Simplest approach: LiteLLM integration (1 line)
import os
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
litellm.success_callback = ["langfuse"]  # all LLM calls auto-traced

# Or via decorator for custom spans
from langfuse.decorators import observe

@observe(name="inventory-analysis")
def analyze_inventory(query: str) -> str:
    chunks = retrieve_docs(query)     # becomes a child span
    answer = generate_answer(query, chunks)  # becomes a child span
    return answer

# What gets captured per generation:
# latency, input/output tokens, cost, faithfulness score, user feedback

Google ADK โ€” New Multi-Agent Framework

GOOGLE AGENT DEVELOPMENT KIT

Open-source framework from Google to build, test, and deploy AI agents. Native Gemini, but supports other models. Designed for multi-agent production use cases with the A2A (Agent-to-Agent) protocol.

Component Function LangGraph Analogy
Agent Basic unit. LlmAgent (reasoning) or WorkflowAgent (deterministic) Node in a graph
Tools Python function callable by the agent. Auto-converts to function calling schema Tool in LangGraph
Runner Engine that executes the agent loop graph.invoke()
Session Per-user conversation state, persistent Checkpointer
A2A Protocol Agent-to-Agent communication. Sub-agent can be called as a tool Subgraph
Deployment Local dev, Vertex AI (managed), or self-hosted ADK server LangGraph Cloud / self-host
Python
from google.adk.agents import LlmAgent
from google.adk.tools import tool
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService

# 1. Define tool
@tool
def check_stock(product_id: str) -> dict:
    """Check product stock from inventory database"""
    return db.get_stock(product_id)

# 2. Create agent
inventory_agent = LlmAgent(
    name="inventory-analyst",
    model="gemini-2.0-flash",
    tools=[check_stock],
    instruction="You are an inventory analyst. Help users understand stock conditions."
)

# 3. Run
session_service = InMemorySessionService()
runner = Runner(agent=inventory_agent, session_service=session_service)

response = runner.run(
    user_id="user_123",
    session_id="session_abc",
    message="What is the current stock of product A?"
)

ADK vs Other Frameworks

Framework Strengths Trade-offs Choose when
Google ADK Native multi-agent, Vertex AI, A2A protocol Python only, small community Need production-grade multi-agent on Google Cloud
LangGraph Mature, graph state machine, checkpointing Thick abstraction, LangChain dep Complex stateful agent, human-in-the-loop
CrewAI Intuitive, role-based, fast prototyping Less flexible for complex flows Quick prototyping, workshops
Pydantic AI Type-safe, minimal, Pythonic Limited ecosystem Simple agent with strict type safety

AI Response Format in Production

A good response format isn't about aesthetics โ€” it's about predictability, parsability, and a contract that doesn't change when the model is updated.

For data pipelines, background jobs, document analysis. Fully type-safe via Instructor + Pydantic.

Python
class InventoryInsight(BaseModel):
    product_id: str
    action: Literal["reorder", "hold", "liquidate"]
    urgency: Literal["immediate", "this_week", "this_month"]
    confidence: float = Field(ge=0.0, le=1.0)
    suggested_quantity: int | None = None

insight = client.messages.create(
    response_model=InventoryInsight,
    messages=[{"role": "user", "content": context}]
)
# insight.action == "reorder" โ€” use directly, no parsing

Learning Roadmap โ€” 8 Weeks

The optimal learning sequence based on concept dependencies. No shortcuts โ€” each week builds the foundation for the next.

WEEK 1: Fundamentals: LLM API + Structured Output

Understand how to call LLMs via API, model differences, token pricing. Master Instructor + Pydantic โ€” this is the foundation of all other AI tools. Without this, you won't be able to debug issues at the layers above.

WEEK 2: FastAPI + LiteLLM: Serve AI as a Service

Wrap LLM calls in a REST API using FastAPI. Add LiteLLM for multi-provider routing. Implement a streaming endpoint. Deploy via Docker. This skill goes straight onto your resume and can be used in real projects immediately.

WEEK 3: RAG Pipeline: pgvector + LlamaIndex

Build a document Q&A system. Ingest PDF โ†’\rightarrow chunk โ†’\rightarrow embed โ†’\rightarrow store in pgvector โ†’\rightarrow retrieve โ†’\rightarrow rerank โ†’\rightarrow inject into prompt. LlamaIndex handles the orchestration pipeline. This is the most sought-after skill in AI job postings.

WEEK 4: Agentic Loop: LangGraph

Build an agent that can reason, call tools (search, SQL, API), and loop until the task is complete. LangGraph is a graph state machine for complex agent workflows. Learn checkpointing and human-in-the-loop patterns.

WEEK 5: Observability: LangFuse

Integrate LangFuse into all LLM calls. Track latency, token cost, and output quality per request. Set up RAGAS for RAG pipeline evaluation. Without this, you're blind in production. Self-hosted for free, great UI, integrates with K8s.

WEEK 6: Multi-Agent: Google ADK

Learn Google ADK for multi-agent orchestration on top of Gemini. Concepts: sub-agent, tool registry, session management, A2A protocol. Great for your portfolio since it's the newest and increasingly sought after by employers using Google Cloud.

WEEKS 7โ€“8: Demo Project: End-to-End AI Feature

Combine all concepts into a single demo-ready project. Example: inventory Q&A agent โ€” user asks in natural language โ†’\rightarrow agent RAG + query DB + generate insight โ†’\rightarrow stream response. Deploy to K8s with LangFuse tracing active.

V1 PRIORITY STACK

FastAPI + LiteLLM + LlamaIndex + LangGraph + pgvector + LangFuse + Instructor. This covers 90% of mid-level AI engineer job requirements. Master these 7 tools to production depth before expanding to others.


Production Readiness Checklist

Use this before deploying any AI feature to production. Every missed item is a potential incident.

SCHEMA

  • All fields typed, no dict or Any
  • Nullable fields marked explicit Optional / None
  • Numeric fields have constraints (ge=0, le=1.0, etc.)
  • Literal or Enum for fields with limited values

DELIVERY

  • Batch endpoint for data pipelines (JSON + Pydantic)
  • Streaming endpoint for chat interfaces (SSE)
  • SSE headers correct: Cache-Control: no-cache, X-Accel-Buffering: no
  • Timeout set (not infinite wait)
  • Max tokens set explicitly on every LLM call

ERROR HANDLING

  • Validation errors caught before entering DB
  • Retry logic in place (Instructor 3x default, or manual)
  • Error response has retryable flag
  • request_id present in every response for tracing
  • Fallback provider configured in LiteLLM

OBSERVABILITY

  • trace_id linking to LangFuse in every response
  • latency_ms logged per request
  • Input/output tokens logged for cost monitoring
  • context_sources (chunk IDs from RAG) logged for audit trail
  • PII masking configured in LangFuse before go-live

SECURITY

  • Prompt injection protection โ€” validate user input before it reaches the system prompt
  • Rate limiting per user / per endpoint
  • API keys not hardcoded โ€” use env vars or secret manager
  • Output validation โ€” AI output entering DB must go through Pydantic

Related stories

Engineering
Engineering
Integer to Roman: The Greedy Mapping That Always Shows Up in Interviews

Integer to Roman: The Greedy Mapping That Always Shows Up in Interviews

Breaking down the integer to Roman numeral conversion problem โ€” from basic Roman numeral rules, the subtractive notation pitfalls (IV, IX, XL...), to a clean, interview-ready greedy O(1) solution. Complete with mapping table, step-by-step visualization, and why this approach is optimal given the 1-3999 constraint.
Faisal Affan
Faisal Affan
Published
Engineering
Best Time to Buy and Sell Stock: From Brute Force to One-Pass O(n)

Best Time to Buy and Sell Stock: From Brute Force to One-Pass O(n)

Master the classic coding interview problem 'Best Time to Buy and Sell Stock' with step-by-step explanation โ€” from the naive brute force approach, recognizing patterns, to the optimal one-pass O(n) solution using sliding window / two-pointer technique. Complete with array visualization, complexity analysis, and the intuition behind every decision.
Faisal Affan
Faisal Affan
Published
Engineering
Services I've Handled โ€” Full-Stack Engineering, Architecture & Product Strategy

Services I've Handled โ€” Full-Stack Engineering, Architecture & Product Strategy

A comprehensive list of professional services I've delivered across multiple industries: IoT fleet management, fintech core banking integration, cyber insurance platforms, education technology, and enterprise digital transformation. Each service backed by real production systems still running today.
Faisal Affan
Faisal Affan
Published
Engineering
Portfolio Strategy by Career Level โ€” Junior to CTO

Portfolio Strategy by Career Level โ€” Junior to CTO

Your portfolio should reflect your seniority level, not just list every project you've touched. Junior engineers sell potential and fundamentals. Senior engineers sell depth and architecture. Staff engineers sell leverage. CTOs sell business outcomes. Here's exactly what to put in your portfolio at each level โ€” with concrete examples and a universal format you can use today.
Faisal Affan
Faisal Affan
Published