Mục lục
- Mục tiêu bài học
- Bối cảnh: expose model thành HTTP API
- Async-first: ASGI vs WSGI
- Type hints và Pydantic v2
- OpenAPI / Swagger tự động
- Performance: TechEmpower benchmark
- Dependency injection với Depends()
- Khi nào FastAPI là lựa chọn phù hợp
- Khi nào nên cân nhắc Flask hoặc Django
- Giới hạn của FastAPI
- Tóm tắt so sánh
- Bài tiếp theo
Mục tiêu bài học
Sau bài này bạn sẽ:
- Hiểu sự khác biệt kỹ thuật giữa FastAPI, Flask và Django REST Framework khi serve AI model.
- Biết tại sao async-first quan trọng với AI inference workload.
- Biết FastAPI sinh OpenAPI schema và docs UI như thế nào.
- Nhận ra giới hạn của FastAPI và khi nào Flask / Django là lựa chọn hợp lý hơn.
Bối cảnh: expose model thành HTTP API
Khi một AI Engineer hoàn thành training hoặc lấy model từ Hugging Face, bước tiếp theo thường là cho phép các service khác — frontend, mobile app, hoặc pipeline nội bộ — gọi đến model qua HTTP. Nhiệm vụ đó thuộc về một web framework.
Có ba framework Python phổ biến nhất cho nhiệm vụ này:
- Flask — micro-framework, ra đời 2010, WSGI-based, cộng đồng lớn.
- Django + Django REST Framework (DRF) — full-stack framework, batteries included, ORM, admin, auth sẵn có.
- FastAPI — ra đời 2018, ASGI-based, type hint native, Pydantic v2, OpenAPI tự động.
Không framework nào là "dành riêng cho AI". Sự khác biệt nằm ở thiết kế kỹ thuật và trade-off mà mỗi framework đưa ra — phần còn lại của bài sẽ phân tích từng điểm.
Async-first: ASGI vs WSGI
WSGI và vấn đề blocking I/O
WSGI (Web Server Gateway Interface, PEP 3333) là giao thức đồng bộ: server gọi application callable, application trả về response, rồi server mới xử lý request tiếp theo (hoặc dùng thread/process pool để chạy song song).
Flask chạy trên WSGI. Flask 2.x có hỗ trợ async def view, nhưng về bản chất vẫn chạy trong thread pool thông qua asyncio.run() — không phải event loop thực sự. Mỗi async view vẫn chiếm 1 thread trong worker pool.
Django hỗ trợ ASGI từ phiên bản 3.0 (2019), nhưng phần lớn middleware, ORM (django.db), và third-party app vẫn là sync. Gọi ORM bên trong async def view cần sync_to_async() wrapper để tránh deadlock, gây ra overhead thêm.
FastAPI và ASGI
FastAPI build trên Starlette — một ASGI framework — và chạy trực tiếp trên Python asyncio event loop (Python 3.7+). Đây là native async, không phải thread pool giả lập.
# FastAPI: async def thực sự chạy trên asyncio event loop
from fastapi import FastAPI
app = FastAPI()
@app.post("/predict")
async def predict(payload: dict):
# I/O-bound: gọi LLM API, vector DB → await không block event loop
result = await call_llm_api(payload["text"])
return {"result": result}
Tại sao async quan trọng với AI workload
AI application thường có nhiều I/O-bound operation:
- Gọi OpenAI / Anthropic / Gemini API qua HTTP — latency 200ms đến vài giây.
- Query vector database (Pinecone, Qdrant, Weaviate) qua mạng.
- Đọc file lớn (embedding batch, document chunk).
- Gọi database để lưu lịch sử conversation.
Với ASGI async, trong khoảng thời gian chờ I/O, event loop có thể xử lý request khác mà không cần thêm thread. Với Flask WSGI, request đó chiếm thread trong suốt thời gian chờ.
Lưu ý: nếu inference chạy locally trên CPU/GPU (ví dụ model.predict() là PyTorch synchronous call), thao tác đó vẫn là CPU-bound và block event loop. Xử lý trường hợp này sẽ được đề cập chi tiết ở Bài 4 (async endpoint).
Type hints và Pydantic v2
FastAPI đọc type annotation trực tiếp từ function signature để tự sinh validation và schema. Không cần viết serializer tách rời.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class PredictRequest(BaseModel):
text: str
max_tokens: int = 256
class PredictResponse(BaseModel):
label: str
confidence: float
@app.post("/predict", response_model=PredictResponse)
async def predict(body: PredictRequest) -> PredictResponse:
# body đã được validate tự động
# nếu client gửi text=123 (int), FastAPI trả 422 Unprocessable Entity
...
FastAPI dùng Pydantic v2 (từ FastAPI 0.100.0, tháng 7/2023). Pydantic v2 rewrite lõi bằng Rust, validation nhanh hơn Pydantic v1 khoảng 5–50 lần tuỳ kiểu dữ liệu (theo benchmark chính thức của Pydantic).
So sánh với Flask và Django
Flask: không có validation tích hợp. Phải thêm thư viện ngoài như marshmallow, flask-pydantic, hoặc tự viết kiểm tra thủ công:
# Flask: phải tự validate
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/predict", methods=["POST"])
def predict():
data = request.get_json()
if not data or "text" not in data:
return jsonify({"error": "missing text"}), 400
text = data["text"]
if not isinstance(text, str):
return jsonify({"error": "text must be string"}), 400
...
Django REST Framework: có Serializer với validation đầy đủ, nhưng phải khai báo tường minh hơn và verbose hơn so với Pydantic type annotation:
# DRF: serializer riêng, khai báo lại từng field
from rest_framework import serializers, views, response
class PredictSerializer(serializers.Serializer):
text = serializers.CharField()
max_tokens = serializers.IntegerField(default=256)
class PredictView(views.APIView):
def post(self, request):
s = PredictSerializer(data=request.data)
s.is_valid(raise_exception=True)
...
OpenAPI / Swagger tự động
FastAPI sinh OpenAPI 3.x schema từ code và cung cấp hai UI mặc định:
/docs— Swagger UI, giao diện test endpoint trực tiếp trên trình duyệt./redoc— ReDoc, tài liệu dạng đọc./openapi.json— raw JSON schema, dùng để generate client SDK hoặc import vào Postman.
Không cần cài thêm gì. Schema được cập nhật tự động khi thêm hoặc sửa endpoint.
Với Flask, để có Swagger UI phải cài thêm flasgger hoặc flask-smorest và viết docstring theo định dạng riêng. Với Django, drf-spectacular hoặc drf-yasg là phổ biến nhưng yêu cầu cấu hình và annotation thêm.
Đối với AI API, OpenAPI tự động có giá trị thực tế: team frontend có thể test endpoint inference mà không cần hỏi backend, QA có thể viết test case từ schema, và client SDK có thể sinh từ /openapi.json.
Performance: TechEmpower benchmark
TechEmpower Web Framework Benchmarks là bộ benchmark độc lập, đo throughput của framework qua nhiều test type. Từ Round 22 trở đi, FastAPI / Starlette nằm trong nhóm trên của các framework Python, đạt throughput tương đương Node.js Express ở các bài test JSON serialization và plaintext response.
Flask và Django chậm hơn khoảng 2–5 lần so với FastAPI/Starlette cho cùng workload JSON serialization trong benchmark này.
Quan trọng: con số TechEmpower đo framework overhead trong điều kiện lý tưởng (không có business logic). Trong AI application thực tế, bottleneck gần như luôn là inference time — thời gian model chạy — không phải framework. Một request gọi GPT-4o tốn 500ms–2s; framework overhead ở mức vài millisecond không phải yếu tố quyết định throughput.
Benchmark framework có giá trị hơn khi bạn chạy nhiều request nhỏ đồng thời và cần event loop xử lý I/O hiệu quả — trường hợp AI API gateway gom nhiều call về LLM.
Dependency injection với Depends()
FastAPI có hệ thống dependency injection tích hợp qua Depends(). Dependency được khai báo là function bình thường (hoặc async), và FastAPI tự resolve khi endpoint được gọi:
from fastapi import FastAPI, Depends, Header, HTTPException
app = FastAPI()
async def verify_api_key(x_api_key: str = Header(...)):
if x_api_key != "secret":
raise HTTPException(status_code=403, detail="Invalid API key")
return x_api_key
@app.post("/predict")
async def predict(
body: dict,
api_key: str = Depends(verify_api_key), # inject dependency
):
...
Dependency có thể lồng nhau (một dependency gọi dependency khác), và FastAPI cache kết quả trong scope của request. Điều này phù hợp để inject shared resource như database session, HTTP client pool, hoặc model instance đã load.
Flask không có dependency injection tích hợp — phải tự implement hoặc dùng thư viện như flask-injector. Django có DI ở mức nhất định qua middleware và class-based view, nhưng không linh hoạt bằng Depends() của FastAPI.
Khi nào FastAPI là lựa chọn phù hợp
FastAPI phù hợp khi:
- Serve model PyTorch / Transformers / scikit-learn qua HTTP — bắt đầu từ API không có code legacy, không cần ORM phức tạp, không cần admin UI.
- Streaming response cho LLM — FastAPI hỗ trợ
StreamingResponsevới async generator, phù hợp để stream token-by-token từ LLM API về client. - Tích hợp với async libraries —
httpx(async HTTP client),asyncpg(PostgreSQL),motor(MongoDB async),redis-pyasync mode — tất cả đều work tốt trong FastAPI async context. - Team cần tài liệu API tự động — Swagger UI tại
/docskhông cần cấu hình thêm. - Project mới hoàn toàn — không bị ràng buộc bởi codebase cũ.
Khi nào nên cân nhắc Flask hoặc Django
FastAPI không phải lựa chọn duy nhất và đôi khi không phải lựa chọn tốt nhất:
Cân nhắc Django + DRF khi
- Codebase Django đã có sẵn với auth, admin, ORM, permissions — thêm endpoint AI bằng DRF nhanh hơn nhiều so với migrate toàn bộ sang FastAPI.
- Cần Django admin để quản lý dữ liệu training, label, feedback — không framework nào thay thế được Django admin sẵn có.
- Team đã thạo Django và không có đủ thời gian học FastAPI + SQLAlchemy.
Cân nhắc Flask khi
- Cần prototype nhanh với logic đơn giản, không cần validation phức tạp.
- Team đã có Flask codebase ổn định và AI chỉ là một endpoint nhỏ thêm vào.
- Tích hợp với hệ sinh thái Flask extension đặc thù (ví dụ một số extension Flask chưa có port sang FastAPI).
Ba framework đều deploy được trên cùng infrastructure (Docker, Kubernetes, cloud platform). Quyết định nên dựa trên context codebase và team, không phải trên lý thuyết "framework nào tốt hơn cho AI".
Giới hạn của FastAPI
FastAPI không giải quyết tất cả:
Không có ORM tích hợp
FastAPI không đi kèm ORM. Bạn phải tự chọn: SQLAlchemy (async với asyncpg), Tortoise ORM, hoặc SQLModel (do tác giả FastAPI viết, dùng Pydantic + SQLAlchemy). Điều này cho phép linh hoạt hơn, nhưng cũng có nghĩa là phải tự thiết lập.
Không có admin UI
Không có gì tương đương Django admin sẵn có. Có thư viện bên thứ ba như sqladmin (SQLAlchemy-based), nhưng phải cài đặt và cấu hình riêng.
Ecosystem plugin nhỏ hơn
Flask và Django có hàng nghìn extension tích hợp sẵn (rate limiting, OAuth, session, cache). FastAPI có middleware Starlette và một số package bên thứ ba, nhưng lựa chọn ít hơn và một số chưa có documentation tốt.
Async code dễ deadlock khi lẫn sync blocking
Đây là gotcha thường gặp nhất. Nếu gọi một hàm blocking (ví dụ model.predict() là synchronous PyTorch call, hoặc time.sleep()) trực tiếp bên trong async def endpoint mà không dùng asyncio.run_in_executor(), event loop sẽ bị block và các request khác phải chờ. FastAPI có run_in_threadpool utility để xử lý trường hợp này — sẽ được đề cập ở Bài 4.
# WRONG: blocking call trực tiếp trong async def
@app.post("/predict")
async def predict(body: PredictRequest):
result = model.predict(body.text) # sync blocking, block event loop!
return result
# CORRECT: chạy blocking code trong thread pool
import asyncio
from fastapi.concurrency import run_in_threadpool
@app.post("/predict")
async def predict(body: PredictRequest):
result = await run_in_threadpool(model.predict, body.text)
return result
Tóm tắt so sánh
| Tiêu chí | FastAPI | Flask | Django + DRF |
|---|---|---|---|
| Server protocol | ASGI (asyncio native) | WSGI (async qua thread pool) | WSGI + ASGI từ 3.0 (ORM vẫn sync) |
| Validation input | Pydantic v2, tự động từ type hint | Marshmallow / thủ công | DRF Serializer (verbose hơn) |
| OpenAPI schema | Tự sinh, /docs và /redoc sẵn | Cần flasgger / flask-smorest | Cần drf-spectacular / drf-yasg |
| Dependency injection | Depends(), tích hợp sẵn | Không có, tự implement | Middleware, CBV (ít linh hoạt hơn) |
| ORM | Không có, tự chọn | Không có, tự chọn | Django ORM sẵn có |
| Admin UI | Không có, cần sqladmin | Flask-Admin (bên thứ ba) | Django Admin sẵn có |
| Streaming response | StreamingResponse + async generator | Generator response, không native async | StreamingHttpResponse, không native async |
| Ecosystem | Starlette middleware, đang tăng | Lớn, trưởng thành | Rất lớn, batteries included |
Bài tiếp theo
Bài 2: Cài đặt FastAPI và endpoint đầu tiên — cài đặt môi trường, viết và chạy endpoint inference đầu tiên với Uvicorn.
