AI engineering is the fastest-growing tech role of 2025. Unlike data scientists who focus on research, or ML engineers who train models, AI engineers build production systems on top of foundation models — LLM-powered apps, RAG pipelines, agents, and AI APIs. This roadmap shows you exactly what to learn, in what order, with realistic timelines.
At a glance
| Phase |
Topics |
Time estimate |
| 1 |
Programming & CS fundamentals |
4–6 weeks |
| 2 |
Python for AI |
3–4 weeks |
| 3 |
Machine learning fundamentals |
6–8 weeks |
| 4 |
Deep learning & neural networks |
6–8 weeks |
| 5 |
Large language models (LLMs) |
4–6 weeks |
| 6 |
RAG & embeddings |
4–6 weeks |
| 7 |
AI agents & orchestration |
4–6 weeks |
| 8 |
MLOps & production |
4–6 weeks |
| 9 |
Evaluation & safety |
2–3 weeks |
| 10 |
Specialisation & portfolio |
Ongoing |
Total: 12–18 months from beginner to job-ready
AI engineer vs related roles
| Role |
Focus |
Models |
Coding |
Math |
| AI Engineer |
Build products on LLMs/foundation models |
Uses pre-trained |
High |
Medium |
| ML Engineer |
Train & deploy custom models |
Trains from scratch |
High |
High |
| Data Scientist |
Analyse data, build ML models |
Trains/fine-tunes |
Medium |
High |
| Data Engineer |
Build data pipelines |
No modelling |
High |
Low |
| AI Researcher |
Advance the field |
Creates new architectures |
Medium |
Very high |
| Prompt Engineer |
Optimise prompts |
Uses pre-trained |
Low |
Low |
2025 reality: Most companies hiring "AI engineers" want someone who can integrate GPT-4, Claude, or Gemini into products — not train models from scratch. This roadmap focuses on that production-first track.
Phase 1: Programming & CS fundamentals (4–6 weeks)
You need solid programming foundations before anything AI-specific.
Python (mandatory)
# Essential Python for AI engineers
import os
from pathlib import Path
from dataclasses import dataclass
from typing import Optional, Generator
@dataclass
class Document:
content: str
metadata: dict
embedding: Optional[list[float]] = None
def chunk_text(text: str, chunk_size: int = 512, overlap: int = 50) -> Generator[str, None, None]:
"""Split text into overlapping chunks for RAG."""
words = text.split()
for i in range(0, len(words), chunk_size - overlap):
yield " ".join(words[i:i + chunk_size])
CS concepts you must know
| Concept |
Why it matters for AI |
| Data structures (list, dict, set, queue) |
Token storage, caching, deduplication |
| Algorithms & Big O |
Understanding why RAG retrieval matters |
| REST APIs & HTTP |
Every LLM provider uses REST |
| Async programming (asyncio, await) |
Streaming LLM responses, concurrent calls |
| Git & version control |
Tracking prompts, configs, model versions |
| CLI & shell basics |
Running scripts, Docker, SSH to GPUs |
| Environment variables |
Securing API keys (NEVER hardcode) |
Resources
- Python: Official docs + "Fluent Python" (Ramalho)
- CS fundamentals: CS50 (free, Harvard)
- APIs: Build a simple REST client with
httpx or requests
Phase 2: Python for AI (3–4 weeks)
These libraries are the daily tools of every AI engineer.
NumPy — the foundation of everything
import numpy as np
# Vectors and matrices underpin all ML
embeddings = np.random.rand(1000, 1536) # 1000 docs, 1536-dim vectors
# Cosine similarity (manual)
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Batch similarity search
query = np.random.rand(1536)
similarities = embeddings @ query / (
np.linalg.norm(embeddings, axis=1) * np.linalg.norm(query)
)
top_k = np.argsort(similarities)[-5:][::-1]
Pandas — data wrangling
import pandas as pd
# Clean a dataset for fine-tuning
df = pd.read_csv("training_data.csv")
df = df.dropna(subset=["prompt", "response"])
df = df[df["response"].str.len() > 50]
df["token_count"] = df["response"].str.split().str.len()
df = df[df["token_count"] < 512]
# Export to JSONL for OpenAI fine-tuning format
records = df[["prompt", "response"]].rename(
columns={"prompt": "input", "response": "output"}
).to_dict("records")
Python AI/ML library overview
| Library |
Purpose |
When you use it |
numpy |
Numerical computing, arrays |
Embeddings, similarity |
pandas |
Data manipulation |
Dataset prep, evaluation |
matplotlib / seaborn |
Visualisation |
Eval metrics, loss curves |
scikit-learn |
Classical ML, metrics |
Baselines, evaluation |
torch |
Deep learning |
Fine-tuning, custom models |
transformers |
HuggingFace models |
Local models, fine-tuning |
openai |
OpenAI API client |
GPT-4, embeddings |
anthropic |
Claude API client |
Claude models |
langchain |
LLM orchestration |
Chains, agents, RAG |
llama-index |
RAG framework |
Document indexing |
pydantic |
Data validation |
Structured outputs |
fastapi |
API server |
Serving AI endpoints |
Phase 3: Machine learning fundamentals (6–8 weeks)
You don't need to derive backprop by hand, but you must understand how models learn.
Core concepts
| Concept |
What to understand |
| Supervised vs unsupervised learning |
When to use which approach |
| Train / val / test splits |
Avoiding data leakage |
| Overfitting & underfitting |
Bias-variance trade-off |
| Loss functions |
MSE, cross-entropy, contrastive |
| Gradient descent |
How models update weights |
| Regularisation (L1, L2, dropout) |
Preventing memorisation |
| Evaluation metrics |
Accuracy, F1, BLEU, ROUGE, exact match |
| Hyperparameter tuning |
Learning rate, batch size, epochs |
Classical ML you should know
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
# Even as an AI engineer, classifiers help you build evaluation pipelines
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
print(classification_report(y_test, model.predict(X_test)))
Why classical ML matters for AI engineers
- Intent classification: Route user queries to the right agent
- Reranking: Use a small classifier to rerank RAG results
- Safety filtering: Classify toxic/off-topic inputs cheaply
- A/B evaluation: Statistical tests on LLM output quality
Phase 4: Deep learning & neural networks (6–8 weeks)
You need to understand the architecture that powers every foundation model.
Key concepts
| Concept |
Why it matters |
| Neural network basics (layers, activations, weights) |
Foundation of all LLMs |
| Backpropagation |
How models learn |
| CNNs |
Vision models, multimodal AI |
| RNNs / LSTMs |
Historical context (pre-Transformer) |
| Attention mechanism |
The core of every LLM |
| Transformer architecture |
GPT, BERT, Claude, Gemini all use this |
| Tokenisation (BPE, WordPiece) |
How text becomes numbers |
| Embeddings |
Semantic representations of text |
The Transformer in 30 lines
import torch
import torch.nn as nn
class MultiHeadAttention(nn.Module):
def __init__(self, d_model: int, num_heads: int):
super().__init__()
self.num_heads = num_heads
self.d_k = d_model // num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def forward(self, x: torch.Tensor) -> torch.Tensor:
B, T, D = x.shape
Q = self.W_q(x).view(B, T, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(x).view(B, T, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(x).view(B, T, self.num_heads, self.d_k).transpose(1, 2)
scores = (Q @ K.transpose(-2, -1)) / (self.d_k ** 0.5)
attn = torch.softmax(scores, dim=-1)
out = (attn @ V).transpose(1, 2).contiguous().view(B, T, D)
return self.W_o(out)
Learning resources
- Fast.ai: Practical Deep Learning (free)
- Andrej Karpathy: "Let's build GPT" on YouTube (must-watch)
- 3Blue1Brown: Neural networks video series
Phase 5: Large language models (LLMs) (4–6 weeks)
This is the core of AI engineering in 2025.
LLM fundamentals
| Concept |
What to know |
| Pre-training vs fine-tuning vs RLHF |
How models are built and aligned |
| Context window |
Token limits, KV cache cost |
| Temperature & sampling (top-p, top-k) |
Controlling randomness |
| System prompts |
Shaping model behaviour |
| Few-shot prompting |
In-context learning |
| Chain-of-thought (CoT) |
Getting reasoning steps |
| Structured output (JSON mode) |
Reliable machine-readable responses |
| Function calling / tool use |
LLMs that invoke external APIs |
| Streaming |
Incremental token delivery |
Calling your first LLM API
from anthropic import Anthropic
from openai import OpenAI
# Anthropic (Claude)
client = Anthropic()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="You are a helpful coding assistant.",
messages=[{"role": "user", "content": "Explain async/await in Python."}]
)
print(message.content[0].text)
# OpenAI (GPT-4)
oai = OpenAI()
response = oai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": "Explain async/await in Python."}
]
)
print(response.choices[0].message.content)
Structured output with Pydantic
from pydantic import BaseModel
from openai import OpenAI
class BugReport(BaseModel):
severity: str # "critical" | "high" | "medium" | "low"
root_cause: str
suggested_fix: str
affected_components: list[str]
client = OpenAI()
response = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[{"role": "user", "content": f"Analyse this error: {error_log}"}],
response_format=BugReport,
)
bug = response.choices[0].message.parsed
print(f"Severity: {bug.severity}")
print(f"Fix: {bug.suggested_fix}")
Model comparison (2025)
| Model |
Provider |
Context |
Best for |
| Claude Opus 4.6 |
Anthropic |
200k |
Complex reasoning, long documents |
| Claude Sonnet 4.6 |
Anthropic |
200k |
Coding, everyday tasks (best balance) |
| GPT-4o |
OpenAI |
128k |
Multimodal, function calling |
| Gemini 1.5 Pro |
Google |
1M |
Extremely long context |
| Llama 3.1 405B |
Meta |
128k |
Open-weight, self-hosted |
| Mistral Large |
Mistral |
128k |
European privacy, multilingual |
| Qwen2.5 72B |
Alibaba |
128k |
Open-weight alternative |
Phase 6: RAG & embeddings (4–6 weeks)
Retrieval-Augmented Generation (RAG) is the #1 pattern AI engineers build.
How RAG works
User query
│
▼
Embed query ──────────────────────────────────┐
│ │
▼ ▼
Vector DB search Your knowledge base
│ (PDFs, docs, DB)
▼ │
Top-K chunks ◄────────────────────────────────┘
│
▼
LLM prompt = system + context chunks + user query
│
▼
Grounded answer
Building a minimal RAG pipeline
from openai import OpenAI
import numpy as np
client = OpenAI()
def embed(text: str) -> list[float]:
return client.embeddings.create(
model="text-embedding-3-small",
input=text
).data[0].embedding
def cosine_sim(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
# Index your docs
docs = ["Python is a high-level language.", "FastAPI is async by default.", "..."]
doc_embeddings = [embed(d) for d in docs]
def rag_answer(query: str, top_k: int = 3) -> str:
q_emb = embed(query)
scores = [cosine_sim(q_emb, d_emb) for d_emb in doc_embeddings]
top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k]
context = "\n\n".join(docs[i] for i in top_indices)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"Answer using this context:\n{context}"},
{"role": "user", "content": query}
]
)
return response.choices[0].message.content
Chunking strategies
| Strategy |
How |
Best for |
| Fixed-size |
Split every N tokens |
Simple docs, code |
| Sentence-aware |
Split on sentence boundaries |
Prose, articles |
| Semantic |
Cluster by topic shift |
Long mixed documents |
| Recursive |
Try paragraph → sentence → word |
General purpose |
| Agentic |
LLM decides chunk boundaries |
Complex documents |
Vector databases comparison
| Database |
Type |
Hosting |
Best for |
| Pinecone |
Managed |
Cloud |
Production, no ops |
| Weaviate |
Open-source |
Self/Cloud |
Hybrid search |
| Qdrant |
Open-source |
Self/Cloud |
Performance, filtering |
| Chroma |
Open-source |
Self |
Local dev, prototyping |
| pgvector |
Postgres extension |
Self |
Already using Postgres |
| Milvus |
Open-source |
Self/Cloud |
Billion-scale |
| Supabase pgvector |
Managed Postgres |
Cloud |
Full-stack apps |
RAG failure modes to avoid
| Problem |
Symptom |
Fix |
| Poor chunking |
Context cut mid-sentence |
Use sentence-aware splitter |
| Wrong embedding model |
Low retrieval accuracy |
Match embedding to language/domain |
| Too many chunks |
LLM ignores most context |
Reduce top-k, add reranking |
| No reranking |
Irrelevant chunks retrieved |
Add cross-encoder reranker |
| Hallucination outside context |
LLM makes things up |
Add citation checking |
| Stale index |
Old data returned |
Implement incremental updates |
Phase 7: AI agents & orchestration (4–6 weeks)
Agents are LLMs that use tools to take actions in the world.
Agent architecture
User
│
▼
Agent (LLM + tool loop)
├── Web search tool
├── Code execution tool
├── Database query tool
├── Email/calendar tool
└── Other agents (multi-agent)
│
▼
Final answer
Building a simple tool-calling agent
from anthropic import Anthropic
import json
client = Anthropic()
tools = [
{
"name": "search_web",
"description": "Search the web for current information",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"]
}
},
{
"name": "run_python",
"description": "Execute Python code and return output",
"input_schema": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "Python code to execute"}
},
"required": ["code"]
}
}
]
def run_tool(name: str, inputs: dict) -> str:
if name == "search_web":
return f"[Search results for '{inputs['query']}': ...]"
if name == "run_python":
# WARNING: sandbox this in production!
exec_globals: dict = {}
exec(inputs["code"], exec_globals)
return str(exec_globals.get("result", "No output"))
return "Unknown tool"
def agent_loop(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=messages
)
if response.stop_reason == "end_turn":
return response.content[0].text
# Process tool calls
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
messages.append({"role": "user", "content": tool_results})
AI agent frameworks
| Framework |
Language |
Style |
Best for |
| LangChain |
Python/JS |
Chains & agents |
Rapid prototyping |
| LlamaIndex |
Python |
RAG-first |
Document Q&A |
| LangGraph |
Python |
Graph-based |
Complex workflows |
| CrewAI |
Python |
Role-based |
Multi-agent teams |
| AutoGen |
Python |
Conversational |
Research agents |
| Claude Agent SDK |
Python |
Production |
Anthropic-native |
| Semantic Kernel |
C#/Python |
Enterprise |
Microsoft stack |
| Haystack |
Python |
Pipelines |
Search + RAG |
Memory types for agents
| Type |
Storage |
Scope |
Example |
| In-context |
Prompt tokens |
Current session |
Chat history |
| External (short-term) |
Redis/DB |
Session |
User preferences |
| External (long-term) |
Vector DB + summary |
All sessions |
User knowledge base |
| Procedural |
Fine-tuned weights |
Permanent |
Task-specific behaviour |
Phase 8: MLOps & production AI (4–6 weeks)
Getting AI into production is 80% of the job.
Production AI stack
┌─────────────────────────────────────────────────┐
│ User Interface (Web/Mobile/API) │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ AI Gateway (rate limiting, auth, routing) │
│ Tools: LiteLLM, Kong, Portkey │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ Orchestration layer (chains, agents) │
│ Tools: LangChain, LangGraph, custom │
└────┬────────────┬──────────────┬────────────────┘
│ │ │
▼ ▼ ▼
LLM APIs Vector DB Other tools
(OpenAI/ (Pinecone/ (Search/Code/DB)
Anthropic) pgvector)
│
▼
┌─────────────────────────────────────────────────┐
│ Observability (traces, evals, costs) │
│ Tools: LangFuse, Braintrust, Arize, W&B │
└─────────────────────────────────────────────────┘
LLM observability with LangFuse
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context
langfuse = Langfuse()
@observe()
def classify_intent(user_message: str) -> str:
langfuse_context.update_current_observation(
input=user_message,
metadata={"model": "gpt-4o-mini"}
)
# ... call LLM
result = "question"
langfuse_context.update_current_observation(output=result)
return result
Key MLOps skills
| Skill |
Tool |
Why |
| Containerisation |
Docker |
Reproducible environments |
| CI/CD |
GitHub Actions |
Automated tests + deployments |
| Secrets management |
Vault, AWS Secrets Manager |
Never commit API keys |
| Prompt versioning |
LangFuse, Helicone, DSPy |
Track prompt changes |
| A/B testing |
Posthog, custom |
Measure LLM improvements |
| Cost monitoring |
LiteLLM, Helicone |
Prevent surprise bills |
| Rate limiting |
Redis, API gateway |
Prevent abuse |
| Caching |
Redis, semantic cache |
Reduce costs & latency |
| Load balancing |
LiteLLM, multiple providers |
Resilience |
Serving a FastAPI AI endpoint
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from anthropic import Anthropic
import asyncio
app = FastAPI()
client = Anthropic()
class ChatRequest(BaseModel):
message: str
session_id: str
class ChatResponse(BaseModel):
response: str
tokens_used: int
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest) -> ChatResponse:
if not request.message.strip():
raise HTTPException(status_code=400, detail="Message cannot be empty")
result = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": request.message}]
)
return ChatResponse(
response=result.content[0].text,
tokens_used=result.usage.input_tokens + result.usage.output_tokens
)
Phase 9: Evaluation & safety (2–3 weeks)
AI systems fail in subtle ways. Evaluation is non-negotiable.
Evaluation framework
| Type |
What |
How |
| Unit evals |
Single-turn correctness |
Exact match, regex, LLM-as-judge |
| Integration evals |
Full pipeline quality |
End-to-end test cases |
| Human evals |
Subjective quality |
Annotation platforms |
| Online evals |
Production monitoring |
Thumbs up/down, implicit signals |
| Adversarial evals |
Safety, robustness |
Red-teaming, jailbreak testing |
LLM-as-judge eval
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class EvalResult(BaseModel):
score: int # 1-5
reasoning: str
passed: bool
def llm_judge(question: str, answer: str, ground_truth: str) -> EvalResult:
prompt = f"""Rate this answer from 1 to 5 for correctness and completeness.
Question: {question}
Ground truth: {ground_truth}
Model answer: {answer}
Score criteria:
5 = Correct, complete, well-explained
4 = Correct but incomplete
3 = Partially correct
2 = Mostly wrong
1 = Completely wrong"""
result = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
response_format=EvalResult
)
return result.choices[0].message.parsed
AI safety basics every engineer must know
| Risk |
Description |
Mitigation |
| Prompt injection |
User manipulates system prompt |
Separate system/user context, validate |
| Jailbreaking |
User bypasses guardrails |
Content moderation layer, input validation |
| Hallucination |
Model makes up facts |
RAG + citation checking, low temperature |
| Data leakage |
PII in training/prompts |
PII scrubbing, output filtering |
| Indirect injection |
Malicious content in retrieved docs |
Sanitise retrieved chunks |
| Over-reliance |
Users trust AI blindly |
Show confidence, cite sources |
| Bias |
Model exhibits harmful stereotypes |
Diverse test sets, bias evals |
Phase 10: Specialisation (ongoing)
After the foundations, choose one or two specialisations.
| Specialisation |
Focus |
Key technologies |
Salary range (US) |
| LLM product engineering |
User-facing AI features |
OpenAI, Anthropic, LangChain |
$150k–$220k |
| RAG & search |
Enterprise knowledge systems |
pgvector, Qdrant, LlamaIndex |
$140k–$210k |
| AI agents & automation |
Autonomous AI workflows |
LangGraph, CrewAI, Claude SDK |
$160k–$240k |
| Fine-tuning & alignment |
Customising models |
LoRA, QLoRA, DPO, TRL |
$150k–$250k |
| Multimodal AI |
Vision, audio, video |
GPT-4V, Gemini, CLIP |
$160k–$250k |
| Voice & audio |
Speech AI |
Whisper, ElevenLabs, TTS |
$130k–$200k |
| AI infrastructure |
GPU clusters, serving |
vLLM, TGI, Triton |
$180k–$280k |
| AI safety & red-teaming |
Model evaluation, safety |
Constitutional AI, evals |
$160k–$250k |
Technology map
AI Engineer 2025
├── Language & runtime
│ ├── Python 3.11+ (primary)
│ ├── TypeScript/Node.js (front-end AI)
│ └── Bash/CLI (automation)
│
├── LLM providers
│ ├── Anthropic (Claude) ← highly recommended
│ ├── OpenAI (GPT-4o)
│ ├── Google (Gemini)
│ └── Open-weight (Llama, Mistral, Qwen)
│
├── Frameworks & orchestration
│ ├── LangChain / LangGraph
│ ├── LlamaIndex
│ ├── Pydantic AI
│ └── CrewAI / AutoGen
│
├── Embedding & RAG
│ ├── OpenAI text-embedding-3
│ ├── Cohere reranker
│ ├── Pinecone / Qdrant / pgvector
│ └── Chroma (local dev)
│
├── Fine-tuning
│ ├── HuggingFace Transformers
│ ├── PEFT / LoRA / QLoRA
│ ├── Axolotl / Unsloth
│ └── OpenAI fine-tuning API
│
├── Observability
│ ├── LangFuse
│ ├── Braintrust
│ ├── Arize AI
│ └── Weights & Biases
│
├── Serving & infrastructure
│ ├── FastAPI (API layer)
│ ├── vLLM / TGI (model serving)
│ ├── LiteLLM (LLM gateway)
│ └── Docker / Kubernetes
│
└── Evaluation
├── Custom eval harness
├── RAGAS (RAG evaluation)
├── DeepEval
└── LLM-as-judge patterns
Realistic timeline (18 months to job-ready)
| Month |
Milestones |
| 1–2 |
Python fluent, NumPy/Pandas basics, REST APIs |
| 3–4 |
ML fundamentals, scikit-learn, first Kaggle |
| 5–6 |
Deep learning, PyTorch, Transformer intuition |
| 7–8 |
LLM APIs (OpenAI/Anthropic), prompt engineering, structured output |
| 9–10 |
RAG pipeline end-to-end, vector DB, chunking strategies |
| 11–12 |
Agents with tool use, LangGraph workflow |
| 13–14 |
FastAPI AI service, Docker, basic CI/CD |
| 15–16 |
LangFuse observability, eval harness, safety basics |
| 17–18 |
Specialisation deep-dive, portfolio polish, job applications |
Shortcut if you already code: If you're a software engineer, skip Phase 1 and cut 3–4 months.
Portfolio projects (build these)
| Project |
Skills demonstrated |
Difficulty |
| Document Q&A chatbot |
RAG, embeddings, vector DB |
Beginner |
| AI coding assistant (CLI) |
LLM API, streaming, tool use |
Beginner |
| Multi-agent research assistant |
Agents, orchestration, web search |
Intermediate |
| Fine-tuned task-specific model |
Fine-tuning, evaluation, PEFT |
Intermediate |
| Production AI API with evals |
FastAPI, observability, A/B testing |
Advanced |
| Multimodal app (image + text) |
GPT-4V/Claude, multimodal RAG |
Advanced |
AI engineer roles & salaries (2025)
| Role |
Level |
US base |
Total comp (with equity) |
| AI Engineer I |
Junior (0–2 yr) |
$120k–$150k |
$140k–$200k |
| AI Engineer II |
Mid (2–4 yr) |
$150k–$185k |
$180k–$260k |
| Senior AI Engineer |
Senior (4–7 yr) |
$180k–$230k |
$230k–$350k |
| Staff AI Engineer |
Staff (7+ yr) |
$220k–$280k |
$300k–$450k |
| AI Tech Lead |
Lead (7+ yr) |
$200k–$260k |
$280k–$420k |
| Head of AI / AI Director |
Director |
$250k–$350k |
$400k–$700k+ |
EU/UK: Typically 40–60% of US base, but roles are plentiful in London, Berlin, Amsterdam, Paris.
Common mistakes
| Mistake |
Why it hurts |
What to do instead |
| Skipping ML fundamentals |
Can't debug model failures |
Spend 6–8 weeks on core ML |
| Only knowing one LLM provider |
Gets stuck when API changes |
Learn 2+ providers (OpenAI + Anthropic) |
| Hardcoding API keys |
Security breach, ban |
Always use environment variables |
| No evaluation harness |
Can't prove improvements |
Build evals before scaling |
| Ignoring costs |
Surprise $10k bill |
Monitor tokens from day 1 |
| Over-engineering with frameworks |
Abstraction hides bugs |
Build simple chains first, then add frameworks |
| No observability in production |
Blind to failures |
Add LangFuse/Braintrust from the start |
| Treating hallucination as fixed |
Users lose trust |
Implement retrieval + citation checking |
AI engineer vs ML engineer vs data scientist
| Dimension |
AI Engineer |
ML Engineer |
Data Scientist |
| Primary focus |
Build AI-powered products |
Train & deploy models |
Analyse data + insights |
| Models |
Uses foundation models |
Trains custom models |
Trains smaller models |
| Coding |
Heavy (backend + infra) |
Heavy (ML systems) |
Medium (analysis) |
| Math |
Medium (understand, not derive) |
High (linear algebra, stats) |
High (statistics) |
| Business involvement |
High |
Medium |
High |
| Key skill (2025) |
LLM integration, RAG, agents |
PyTorch, distributed training |
Statistical analysis, SQL |
| Demand growth (2025) |
🚀 Explosive |
📈 Strong |
📊 Steady |
| Average US salary |
$160k–$220k |
$155k–$230k |
$120k–$180k |
Frequently asked questions
Do I need a maths degree to become an AI engineer?
No. You need linear algebra intuition (vectors, dot products, matrix multiplication) and basic probability, but you don't need to derive backpropagation. Focus on building things, and fill in math gaps as you encounter them.
Should I learn PyTorch or just use LLM APIs?
Learn both. You'll use APIs 80% of the time, but understanding PyTorch helps you debug embeddings, implement custom eval metrics, and run fine-tuning when needed.
Is LangChain worth learning in 2025?
Yes, as a starting point — but also learn to build without it. Many production teams move off LangChain once they understand the patterns, because raw API calls give more control. Understand the abstraction before depending on it.
How important is fine-tuning vs RAG vs prompting?
Always try prompting first. If it doesn't work, try RAG (cheaper, faster, updatable). Fine-tuning is expensive and only worth it for domain-specific style, tone, or tasks that need sub-100ms latency with no retrieval.
What's the fastest path if I'm already a software engineer?
Build the Document Q&A chatbot (1 week), then the multi-agent assistant (2 weeks), then a production API with observability (1 week). That's ~4 weeks to a portfolio that can land interviews. Start applying immediately.
Do I need GPU hardware?
Not for most AI engineering work. You'll use LLM APIs (no GPU needed) and cloud services (Google Colab, Modal, RunPod) for fine-tuning. Buy GPUs only if you need fast iteration on local models at scale.