3.6 KiB
API ML Service Migration Design
Summary
Migrate apps/api image classification from in-process TensorFlow.js inference to the separate FastAPI ML service at apps/ml-service. The public API behavior and database writes stay the same; only the model inference boundary changes.
Goals
- Remove TensorFlow.js model loading and image decoding from the Bun/Elysia API runtime.
- Keep existing route behavior for image classifications and diagnoses.
- Use
apps/ml-serviceas the single runtime owner for Keras/TensorFlow inference. - Preserve the internal
classifyImage(file)API shape used by existing routes.
Non-goals
- Do not change frontend behavior.
- Do not change database schema.
- Do not add a fallback to TFJS inference.
- Do not migrate upload storage or diagnosis workflows beyond the inference call.
Architecture
apps/api/src/lib/image-model.ts remains the API-side classification boundary. Its implementation changes from local TFJS inference to an HTTP client for the ML service.
classifyImage(file) will:
- Build multipart
FormDatawith field namefile. - POST it to
${env.mlServiceUrl}/predict. - Validate the ML service response.
- Map returned Indonesian labels to API disease slugs.
- Return the existing
ClassificationResultshape:predictedDiseaseSlugconfidenceprobabilities
Existing route files continue to call classifyImage(file), so route and database logic remain stable.
Runtime configuration
Add mlServiceUrl to apps/api/src/config/env.ts.
Default local value:
http://127.0.0.1:8001
Environment variable:
ML_SERVICE_URL=http://127.0.0.1:8001
The API should trim trailing slashes before appending /predict so both http://127.0.0.1:8001 and http://127.0.0.1:8001/ work.
Label mapping
The ML service returns labels. The API maps labels to catalog slugs:
Bercak Daun→bercak-daunDaun Sehat→daun-sehatKarat Daun→karat-daunHawar Daun→hawar-daun
This mapping follows the trained model output order handled by the ML service while preserving API/catalog slugs.
Error handling
classifyImage(file) throws Error when:
- The input file MIME type is not
image/jpegorimage/png. - The ML service cannot be reached.
- The ML service returns a non-2xx response.
- The ML service response is malformed.
- The ML service returns a label that does not map to a known disease slug.
Existing routes keep their current behavior:
POST /api/v1/classifications/imagecatches the error and returns service unavailable withModel service error: ....POST /api/v1/diagnosescatches the error and stores a failed diagnosis record.
Dependency cleanup
Remove API dependencies that only supported local TFJS inference:
@tensorflow/tfjsjpeg-jspngjs
Keep dependencies needed by routes, database, auth, and upload unchanged.
Verification
Required verification after implementation:
- Run API typecheck with
bun run typecheckfrom the repository root. - Confirm no
@tensorflow/tfjs,jpeg-js, orpngjsimports remain underapps/api/src. - Start
apps/ml-serviceand confirmGET /healthreturnsmodel_loaded: truewhen the Keras model exists. - Exercise
classifyImage(file)against the running ML service with a local image file or synthetic image and confirm it returnspredictedDiseaseSlug,confidence, and sorted probabilities.
If full API route testing is blocked by external database or upload service requirements, report that explicitly and include the lower-level verification evidence.
Catatan (2026-08-02): port produksi sekarang API 4006, nginx 4011, ML 4012; deploy Nix+systemd+Caddy.