Skip to content

Deploying as an API

In this section, we will see how you can deploy your pipeline as a REST API using the power of FastAPI.

The NLP pipeline

Let's create a simple NLP model, that can:

  • match synonyms of COVID19
  • check for negation, speculation and reported speech.

You know the drill:

pipeline.py
import edsnlp, edsnlp.pipes as eds

nlp = edsnlp.blank('eds')
nlp.add_pipe(eds.sentences())
nlp.add_pipe(
   eds.matcher(
    regex=dict(
        covid=[
            "covid",
            r"covid[-\s]?19",
            r"sars[-\s]?cov[-\s]?2",
            r"corona[-\s]?virus",
        ],
    ),
    attr="LOWER",
   ),
)
nlp.add_pipe(eds.negation())
nlp.add_pipe(eds.family())
nlp.add_pipe(eds.hypothesis())
nlp.add_pipe(eds.rspeech())

Creating the FastAPI app

FastAPI is a incredibly efficient framework, based on Python type hints from the ground up, with the help of Pydantic (another great library for building modern Python). We won't go into too much detail about FastAPI in this tutorial. For further information on how the framework operates, go to its excellent documentation!

We'll need to create two things:

  1. A module containing the models for inputs and outputs.
  2. The script that defines the application itself.
models.py
from typing import List

from pydantic import BaseModel


class Entity(BaseModel):  # (1)

    # OMOP-style attributes
    start: int
    end: int
    label: str
    lexical_variant: str
    normalized_variant: str

    # Qualifiers
    negated: bool
    hypothesis: bool
    family: bool
    reported_speech: bool


class Document(BaseModel):  # (2)
    text: str
    ents: List[Entity]
  1. The Entity model contains attributes that define a matched entity, as well as variables that contain the output of the qualifier components.
  2. The Document model contains the input text, and a list of detected entities

Having defined the output models and the pipeline, we can move on to creating the application itself:

app.py
from typing import List

from fastapi import FastAPI

from pipeline import nlp
from models import Entity, Document


app = FastAPI(title="EDS-NLP", version=edsnlp.__version__)


@app.post("/covid", response_model=List[Document])  # (1)
async def process(
    notes: List[str],  # (2)
):

    documents = []

    for doc in nlp.pipe(notes):
        entities = []

        for ent in doc.ents:
            entity = Entity(
                start=ent.start_char,
                end=ent.end_char,
                label=ent.label_,
                lexical_variant=ent.text,
                normalized_variant=ent._.normalized_variant,
                negated=ent._.negation,
                hypothesis=ent._.hypothesis,
                family=ent._.family,
                reported_speech=ent._.reported_speech,
            )
            entities.append(entity)

        documents.append(
            Document(
                text=doc.text,
                ents=entities,
            )
        )

    return documents
  1. By telling FastAPI what output format is expected, you get automatic data validation.
  2. In FastAPI, input and output schemas are defined through Python type hinting. Here, we tell FastAPI to expect a list of strings in the POST request body. As a bonus, you get data validation for free.

Running the API

Our simple API is ready to launch! We'll just need to install FastAPI along with a ASGI server to run it. This can be done in one go:

$ pip install 'fastapi[uvicorn]'
---> 100%
color:green Successfully installed fastapi

Launching the API is trivial:

$ uvicorn app:app --reload

Go to localhost:8000/docs to admire the automatically generated documentation!

Using the API

You can try the API directly from the documentation. Otherwise, you may use the requests package:

import requests

notes = [
    "Le père du patient n'est pas atteint de la covid.",
    "Probable coronavirus.",
]

r = requests.post(
    "http://localhost:8000/covid",
    json=notes,
)

r.json()

You should get something like:

[
  {
    "text": "Le père du patient n'est pas atteint de la covid.",
    "ents": [
      {
        "start": 43,
        "end": 48,
        "label": "covid",
        "lexical_variant": "covid",
        "normalized_variant": "covid",
        "negated": true,
        "hypothesis": false,
        "family": true,
        "reported_speech": false
      }
    ]
  },
  {
    "text": "Probable coronavirus.",
    "ents": [
      {
        "start": 9,
        "end": 20,
        "label": "covid",
        "lexical_variant": "coronavirus",
        "normalized_variant": "coronavirus",
        "negated": false,
        "hypothesis": true,
        "family": false,
        "reported_speech": false
      }
    ]
  }
]