Files
zeavis-edu/docs/superpowers/specs/2026-05-22-api-ml-service-migration-design.md
T
2026-05-22 19:46:48 +00:00

3.5 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-service as 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:

  1. Build multipart FormData with field name file.
  2. POST it to ${env.mlServiceUrl}/predict.
  3. Validate the ML service response.
  4. Map returned Indonesian labels to API disease slugs.
  5. Return the existing ClassificationResult shape:
    • predictedDiseaseSlug
    • confidence
    • probabilities

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 Daunbercak-daun
  • Daun Sehatdaun-sehat
  • Karat Daunkarat-daun
  • Hawar Daunhawar-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/jpeg or image/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/image catches the error and returns service unavailable with Model service error: ....
  • POST /api/v1/diagnoses catches the error and stores a failed diagnosis record.

Dependency cleanup

Remove API dependencies that only supported local TFJS inference:

  • @tensorflow/tfjs
  • jpeg-js
  • pngjs

Keep dependencies needed by routes, database, auth, and upload unchanged.

Verification

Required verification after implementation:

  • Run API typecheck with bun run typecheck from the repository root.
  • Confirm no @tensorflow/tfjs, jpeg-js, or pngjs imports remain under apps/api/src.
  • Start apps/ml-service and confirm GET /health returns model_loaded: true when the Keras model exists.
  • Exercise classifyImage(file) against the running ML service with a local image file or synthetic image and confirm it returns predictedDiseaseSlug, 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.