Tutorial

Deploying BioBERT as a Microservice with FastAPI and Docker

Difficulty: Intermediate Time: 15 min read

Introduction

Training a specialized clinical NLP model like BioBERT is only half the battle. To actually use it in a healthcare application, you need to serve it efficiently via an API. In this tutorial, we will wrap a Hugging Face BioBERT model in a high-performance FastAPI web server, and then containerize the entire application using Docker so it can be deployed anywhere.

Architecture Overview


graph LR
    Client(EHR System / Client App) -- POST /analyze --> Docker[Docker Container]
    
    subgraph Docker Container
        Uvicorn[Uvicorn ASGI Server] --> FastAPI[FastAPI App]
        FastAPI --> Pipeline[HuggingFace NER Pipeline]
        Pipeline --> Model[(BioBERT Model Weights)]
    end
    
    Pipeline -- JSON Entities --> FastAPI
    FastAPI -- JSON Response --> Client
        

Prerequisites

  • Python 3.9+
  • Docker installed on your system
  • Basic knowledge of REST APIs

Step 1: Write the FastAPI Application

First, we create a file named app.py. We will use the pipeline from Hugging Face Transformers to load a BioBERT model pre-trained for Named Entity Recognition (NER), specifically for extracting diseases and symptoms. We will implement both a single-text endpoint and a batch endpoint for higher throughput.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
from transformers import pipeline

# Initialize FastAPI app
app = FastAPI(title="BioBERT Clinical NER API")

# Load the BioBERT model at startup
# We use a model fine-tuned on the BC5CDR dataset for Disease/Chemical extraction
print("Loading model...")
ner_pipeline = pipeline("ner", model="alvaroalon2/biobert_diseases_ner", aggregation_strategy="simple")
print("Model loaded successfully.")

# Define Request and Response schemas
class ClinicalNoteRequest(BaseModel):
    text: str

class BatchClinicalNoteRequest(BaseModel):
    texts: List[str]

class EntityResponse(BaseModel):
    entity_group: str
    word: str
    score: float
    start: int
    end: int

# Utility function to clean pipeline output
def clean_entities(entities):
    for ent in entities:
        ent['score'] = float(ent['score']) # Convert float32 to float for JSON
    return entities

@app.post("/analyze", response_model=List[EntityResponse])
async def analyze_note(request: ClinicalNoteRequest):
    if not request.text:
        raise HTTPException(status_code=400, detail="Text cannot be empty.")
    
    try:
        entities = ner_pipeline(request.text)
        return clean_entities(entities)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/analyze/batch", response_model=List[List[EntityResponse]])
async def analyze_batch(request: BatchClinicalNoteRequest):
    if not request.texts:
        raise HTTPException(status_code=400, detail="Texts list cannot be empty.")
    
    try:
        # The Hugging Face pipeline can accept a list of strings directly
        batch_entities = ner_pipeline(request.texts)
        # If batch size is 1, the pipeline returns a flat list instead of a list of lists. We must handle this.
        if len(request.texts) == 1:
            batch_entities = [batch_entities]
            
        return [clean_entities(entities) for entities in batch_entities]
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

Step 2: Create the Requirements File

Create a requirements.txt file to specify the exact dependencies.

fastapi==0.103.1
uvicorn==0.23.2
transformers==4.33.1
torch==2.0.1
pydantic==2.3.0

Step 3: Write the Dockerfile

We need a Dockerfile that uses an official Python image, installs the dependencies, and runs the Uvicorn server. Importantly, we want to download the model during the image build process so the container starts instantly in production, rather than downloading the model at runtime.

# Use official Python runtime as a parent image
FROM python:3.9-slim

# Set working directory
WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Download the model during the build phase to bake it into the image
RUN python -c "from transformers import pipeline; pipeline('ner', model='alvaroalon2/biobert_diseases_ner')"

# Copy the application code
COPY app.py .

# Expose the port the app runs on
EXPOSE 8000

# Command to run the application using Uvicorn
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Step 4: Build and Run the Docker Container

Now, run the following commands in your terminal to build the image and spin up the container.

# Build the image (this will take a few minutes as it downloads PyTorch and the model)
docker build -t biobert-api .

# Run the container, mapping port 8000 on your host to port 8000 in the container
docker run -p 8000:8000 biobert-api

Step 5: Test the API

You can test your new clinical NER microservice using curl or by navigating to http://localhost:8000/docs to use the interactive Swagger UI provided by FastAPI.

curl -X 'POST' \
  'http://localhost:8000/analyze' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "text": "The patient presented with severe chest pain and shortness of breath, suspected to be a myocardial infarction."
}'

Conclusion

By coupling Hugging Face Transformers with FastAPI and Docker, we've created a scalable, self-contained microservice capable of both single-inference and batch-processing. This stateless container can now be deployed to AWS ECS, Google Cloud Run, or a Kubernetes cluster. Because the model weights are baked directly into the Docker image, the container can auto-scale horizontally in seconds to handle massive spikes in clinical note processing without any "cold start" model-download delays.