Microservices & Architecture
System Overview
The system is composed of four independently deployable services (three FastAPI backends + one nginx frontend) orchestrated as 4 Deployments behind an Ingress in AKS. All services are stateless; persistent state lives in Databricks (MLflow + Delta Lake).
/refresh Β· /me
JWT Β· RBAC Β· bcrypt
ST-GCN + Random Forest
Loads from MLflow at boot
Gloss Assembler agent
Translator agent
Delta β events, feedback
SQL Dashboard
Service Responsibilities
1. authService (Port 8001)
Role: JWT token generation and validation, user registration, RBAC claim issuance.
Endpoints:
| Method | Path | Auth | Request | Response |
|---|---|---|---|---|
| POST | /register |
none | {username: str, password: str, role: str} |
{userId: str} |
| POST | /login |
none | {username: str, password: str} |
{accessToken: str, refreshToken: str} |
| POST | /refresh |
none | {refreshToken: str} |
{accessToken: str} |
| GET | /me |
Bearer | β | {userId: str, role: str} |
| GET | /health |
none | β | {status: str, service: str} |
Key Implementation:
- Tokens: HS256 (symmetric, shared secret across all services)
- Token payload:
{userId, role, exp, iat} - Access tokens: 60-minute expiry; refresh tokens: 7 days
- Roles:
user(demo access),analyst(stats endpoint access) - Password: bcrypt hashing, no plaintext storage
Example JWT Payload:
{
"userId": "demo-user-001",
"role": "user",
"exp": 1717287600,
"iat": 1717284000
}
Environment Variables:
JWT_SECRET_KEY β openssl rand -hex 32 (shared with other services)
JWT_ALGORITHM β HS256
JWT_EXPIRY_MINUTESβ 60
2. inferenceService (Port 8002)
Role: Load ML models (pretrained ST-GCN and trained Random Forest), run inference, write predictions to Delta Lake, serve model metadata.
Endpoints:
| Method | Path | Auth | Query / Body | Response |
|---|---|---|---|---|
| POST | /predict |
Bearer | {landmarks: float[][], modelName: str} |
{sign: str, confidence: float, latencyMs: int, eventId: str, modelVersion: str} |
| GET | /models |
Bearer | β | [{name: str, version: str, stage: str, accuracy: float}] |
| GET | /stats |
Bearer (analyst+) | β | {predictionsLastHour: int, latencyP95: float, topSigns: dict} |
| GET | /health |
none | β | {status: str, service: str} |
Model Loading:
- At startup, pulls both
aslStgcnandaslRandomForestfrom MLflow (Databricks hosted) - ST-GCN: PyTorch model, 30 MB, inference on CPU
- RF: sklearn pickle, ~50 MB (100 trees, depth 15)
Landmark Preprocessing (app/preprocessing.py):
def validate_landmarks(payload):
"""
Expects: { landmarks: [ [27, 2], [27, 2], ... ] }
- Max 200 frames
- Each frame exactly 54 floats (27 keypoints Γ 2 coords)
- Body size β€ 1 MB
"""
Inference Flow:
- Receive landmarks
(T, 27, 2) - Load model from memory (preloaded at startup)
- For ST-GCN:
- Forward pass β 2,731 logits
- Logit-mask to 100 in-vocab signs (set non-vocab logits to -β)
- Softmax β probabilities (now summing to 1 over 100 signs only)
- Return top-3:
[(label, confidence), ...]
- For Random Forest:
- Extract per-clip features (mean/std/velocity/trajectory per keypoint)
predict_proba()β class probabilities- Return top-3
- Write one row to
asl.inferenceEvents:{timestamp, modelName, sign, confidence, latencyMs, eventId} - Return response
Event Schema (asl.inferenceEvents Delta table):
{
"timestamp": "2026-06-01T22:30:00Z",
"modelName": "aslStgcn",
"sign": "HELLO",
"confidence": 0.87,
"latencyMs": 145,
"eventId": "evt-001",
"modelVersion": "1.2.3"
}
Environment Variables:
JWT_SECRET_KEY β (shared)
DATABRICKS_HOST β https://your-workspace.cloud.databricks.com
DATABRICKS_TOKEN β personal access token
MLFLOW_TRACKING_URI β https://your-workspace.cloud.databricks.com/api/2.0/mlflow
3. agentService (Port 8003)
Role: Two-agent pipeline (Gloss Assembler β Translator) for interpretation and correction. Runs on OpenAI gpt-4o-mini with structured outputs.
Endpoints:
| Method | Path | Auth | Request | Response |
|---|---|---|---|---|
| POST | /translate |
Bearer | {signs: [{label: str, confidence: float, timestamp: str}, ...]} |
{gloss: str, english: str, trace: dict, corrections: list} |
| GET | /health |
none | β | {status: str, service: str} |
Agent Workflow:
-
Input: ordered sequence of recognized signs (each with confidence + top-3 alternatives from inferenceService)
[ {"label": "HELLO", "confidence": 0.92, "alternatives": ["WAVE", "HI"]}, {"label": "WORLD", "confidence": 0.41, "alternatives": ["EARTH", "LIFE"]}, {"label": "I", "confidence": 0.88, "alternatives": ["ME", "YOU"]} ] - Gloss Assembler agent (Pydantic schema + gpt-4o-mini):
- Drops signs with confidence < 0.40
- Flags signs with 0.40 β€ confidence < 0.65
- Contextual correction: if top-1 is implausible given surrounding signs, swap with an alternative and log the reason
Output schema:
{ "cleaned_gloss": ["HELLO", "I"], "flags": [{"index": 1, "label": "WORLD", "reason": "below threshold"}], "corrections": [ {"original": "HELLO", "chosen": "WAVE", "reason": "context: greeting expected"} ] } - Translator agent (Pydantic schema + gpt-4o-mini):
- Converts ASL-ordered gloss to grammatical English
- Handles ASL grammar quirks: topic-comment, time markers up front, no articles/copulas
-
Inserts missing English function words (copulas, articles, prepositions)
- Input:
["TOMORROW", "STORE", "I", "GO"] - Output:
"I'm going to the store tomorrow."
- Response includes:
{ "gloss": "HELLO I", "english": "I said hello.", "trace": {"gloss_assembler_output": {}, "translator_output": {}}, "corrections": [] }
Prompts (app/prompts.py):
Gloss Assembler System Prompt (abbreviated):
You are an ASL gloss correction expert. You receive a time-ordered sequence
of recognized signs. Each sign carries confidence and alternatives.
YOUR TASKS:
1. Drop signs with confidence < 0.40.
2. Flag (keep but mark) signs with 0.40 β€ confidence < 0.65.
3. Contextual correction: if top-1 is implausible given surrounding signs,
swap it with an alternative and explain why.
Return a JSON object with:
- cleaned_gloss: list of corrected labels
- flags: list of low-confidence items
- corrections: list of {original, chosen, reason}
Translator System Prompt (abbreviated):
You are an ASL-to-English translator. You receive an ASL-ordered gloss list.
ASL grammar is topic-comment; time markers appear at sentence start.
English is SVO; you must insert copulas and articles as needed.
Examples:
- ASL gloss: ["TOMORROW", "STORE", "I", "GO"]
- English: "I'm going to the store tomorrow."
- ASL gloss: ["RAIN", "OUTSIDE"]
- English: "It's raining outside."
Return JSON with:
- english: the translated sentence (one sentence only)
- explanation: brief notes on grammar transformations applied
Environment Variables:
JWT_SECRET_KEY β (shared)
OPENAI_API_KEY β sk-...
OPENAI_MODEL β gpt-4o-mini
4. Frontend (Port 8080)
Role: Single-page application served by nginx β branded as SignStream. Runs MediaPipe Holistic in the browser, preprocesses landmarks, handles user interaction, displays predictions and corrections.
Pages:
/β Main SPA: status indicators, record button, sign display, alternatives dropdown, feedback chips
JavaScript Modules (frontend/js/):
mediapipe.jsβ MediaPipe Holistic setup, frame capturekeypointPreprocessing.jsβ Subset to 27 keypoints, drop z coords, shoulder-normalizeapi.jsβ HTTP calls to auth, inference, agent services (with JWT refresh)ui.jsβ Render predictions, alternatives, feedback buttons, translation display
User Flow (see Frontend & UI for screenshots):
- Page load β Check health of all three services (3 status dots)
- Start signing β User clicks record; MediaPipe captures frames
- Sign β Frames collected; sliced clips appear as buttons
- Predict β Each clip β
/predictβ top-3 candidates - Display β Render prediction + top-3 alternatives in UI
- Translate β Batch collected signs β
/translate(agentService) - Final English β Display English caption + correction log
- Feedback loop β User thumbs-up/down or swap alternative
End-to-End Request Flow
(JWT HS256, 60 min)
Communication Security
- All traffic: HTTPS (Ingress TLS)
- Authentication: Bearer token (JWT in Authorization header)
- Token validation: Middleware on each service checks signature + expiration
- Cross-origin: CORS configured to allow browser origin only
- Rate limiting: slowapi on
/predictendpoint
Scaling Considerations
- Stateless services β Each service can be scaled horizontally (replicas in K8s)
- Database layer β Databricks MLflow and Delta are managed; no scaling concern on client side
- Inference latency β P95 around 165β180 ms; CPU-only means no GPU contention
- Concurrent users β Limited by AKS pod count and Databricks connection pool