A sentiment analysis API built with FastAPI following the 3-layer architecture pattern.
This project implements the Dispatcher β Controller β Manager pattern:
βββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β - Dispatcher (Front Controller) β β Routing
β - Controller (Request Handler) β β HTTP concerns
βββββββββββββββββββ¬ββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββ
β Business Logic Layer β
β - Manager (Service Layer) β β Business rules
βββββββββββββββββββ¬ββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββ
β Data Access Layer β
β - Service (Infrastructure) β β Data/Model operations
βββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Layer 1: Dispatcher (app/api/dispatcher.py) β
β - Route registration β
β - Controller instantiation β
β - Request delegation β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Layer 2: Controller (tinybert_api_controller.py) β
β - HTTP request/response handling β
β - Data validation β
β - Decorated with @put_in_envelope() β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Layer 3: Manager (tinybert_model_manager.py) β
β - Business logic (inference, label mapping) β
β - Calls Model Service β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Layer 4: Service (model_service.py) β
β - Model loading from S3/disk β
β - Low-level infrastructure β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
tinybert_service_refactored/
βββ app/
β βββ __init__.py
β βββ main.py # FastAPI app initialization
β βββ config.py # Configuration
β βββ schemas.py # Pydantic models
β β
β βββ api/
β β βββ __init__.py
β β βββ base_controller.py # Base controller class
β β βββ response_envelope.py # Response wrapper
β β βββ api_decorators.py # @put_in_envelope decorator
β β βββ dispatcher.py # Route registration
β β β
β β βββ v1/
β β βββ __init__.py
β β βββ inference/
β β βββ __init__.py
β β βββ tinybert_api_controller.py # TinyBERT controller
β β
β βββ managers/
β β βββ __init__.py
β β βββ tinybert_model_manager.py # Business logic & inference
β β
β βββ services/
β βββ __init__.py
β βββ model_service.py # Model loading & S3 operations
β
βββ tests/
β βββ test_api.py
β
βββ Dockerfile
βββ requirements.txt
βββ README.md
Wraps all API responses in a consistent format:
{
"status": "success",
"code": 200,
"message": "OK",
"data": {
"predictions": [...]
}
}Automatically wraps controller method responses:
@put_in_envelope
def predict(self, request: InferenceRequest):
predictions = self.manager.predict_with_labels(request.texts)
return {"predictions": predictions} # Raw data - decorator wraps itRegisters routes and delegates to controllers (similar to Flask Blueprint):
@api_v1_router.post("/predict")
def predict(request: InferenceRequest):
controller = TinyBERTApiController(manager)
return controller.predict(request)Handles HTTP concerns:
class TinyBERTApiController(BaseController):
@put_in_envelope
def predict(self, request: InferenceRequest):
# Validate input
if not request.texts:
raise ValueError("Input texts cannot be empty")
# Call manager
predictions = self.manager.predict_with_labels(request.texts)
# Return raw data
return {"predictions": predictions}Implements business logic:
class TinyBERTModelManager:
def predict_with_labels(self, texts: List[str]):
probabilities = self.predict_probabilities(texts)
return self.map_labels(probabilities)Handles infrastructure:
class ModelService:
def load_model(self, device: str = "cpu"):
self.tokenizer = AutoTokenizer.from_pretrained(self.model_dir)
self.model = AutoModelForSequenceClassification.from_pretrained(self.model_dir)GET /api/v1/healthResponse:
{
"status": "success",
"code": 200,
"message": "OK",
"data": {
"status": "healthy",
"service": "TinyBERT Inference API",
"model_loaded": true
}
}POST /api/v1/predict
Content-Type: application/json
{
"texts": ["I love this product!", "This is terrible"]
}Response:
{
"status": "success",
"code": 200,
"message": "OK",
"data": {
"predictions": [
{"negative": 0.05, "positive": 0.95},
{"negative": 0.92, "positive": 0.08}
]
}
}POST /api/v1/predict/top
Content-Type: application/json
{
"texts": ["Amazing experience!"]
}Response:
{
"status": "success",
"code": 200,
"message": "OK",
"data": {
"predictions": [
{"label": "positive", "confidence": 0.97}
]
}
}POST /api/v1/predict/batch
Content-Type: application/json
{
"texts": ["text1", "text2", ..., "text1000"]
}# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install CPU-only PyTorch
pip install torch --index-url https://download.pytorch.org/whl/cpu
# Install other dependencies
pip install fastapi uvicorn pydantic transformers boto3 pytest httpx
# Run the server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000# Build image
docker build -t tinybert-service .
# Run container
docker run -d -p 8000:8000 --name tinybert tinybert-service
# View logs
docker logs -f tinybert
# Stop container
docker stop tinybert# Health check
curl http://localhost:8000/api/v1/health
# Predict sentiment
curl -X POST http://localhost:8000/api/v1/predict \
-H "Content-Type: application/json" \
-d '{"texts": ["I love this!", "This is bad"]}'
# Get top prediction
curl -X POST http://localhost:8000/api/v1/predict/top \
-H "Content-Type: application/json" \
-d '{"texts": ["Amazing product!"]}'import requests
# Predict sentiment
response = requests.post(
"http://localhost:8000/api/v1/predict",
json={"texts": ["I love this!", "This is terrible"]}
)
data = response.json()
print(data["data"]["predictions"])
# Output: [{'negative': 0.05, 'positive': 0.95}, {'negative': 0.92, 'positive': 0.08}]1. HTTP Request
POST /api/v1/predict
β
βΌ
2. FastAPI Router (dispatcher.py)
@api_v1_router.post("/predict")
β
βΌ
3. Dispatcher Function
controller = TinyBERTApiController(manager)
return controller.predict(request)
β
βΌ
4. Controller Method (@put_in_envelope)
ββ Validate input
ββ Call manager.predict_with_labels()
ββ Return raw data: {"predictions": [...]}
β
βΌ
5. Manager (Business Logic)
ββ predict_probabilities()
β ββ Get tokenizer & model from Service
β ββ Tokenize texts
β ββ Run inference
β ββ Return probabilities
ββ map_labels(probabilities)
β
βΌ
6. @put_in_envelope Decorator
ββ Catches returned data
ββ Wraps in ResponseEnvelope.success()
ββ Returns JSONResponse
β
βΌ
7. Response to Client
{
"status": "success",
"code": 200,
"message": "OK",
"data": {"predictions": [...]}
}
- Separation of Concerns: HTTP, business logic, and infrastructure are separate
- Consistent Responses: All endpoints return the same format via decorator
- Error Handling: Centralized exception handling in decorator
- Testability: Each layer can be tested independently
- Scalability: Easy to add new endpoints or models
- Maintainability: Clear structure makes code easier to understand
- Type Safety: Pydantic models throughout
- Auto Documentation: FastAPI generates OpenAPI docs at
/docs
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- OpenAPI Schema: http://localhost:8000/openapi.json
MIT License
Mahya Kashani