openmed.service is a hardened FastAPI app exposing OpenMed's NER, PII
extraction, and de-identification over HTTP. It is built to be self-hosted:
models run on-device, there's no telemetry, and the request schemas reject raw
PHI from spilling into errors. Use it when callers need request/response
inference; use batch-processing-clinical-text for corpora.
To put OpenMed behind an HTTP endpoint your own apps call — an internal de-id
microservice, an NER backend, a containerized inference tier. For agent/tool
integration prefer the MCP server (deploying-openmed-mcp); for offline bulk
work use batch processing.
pip install "openmed[service]" # FastAPI + uvicorn + pydantic
# Launch the ASGI app (factory create_app, or the module-level `app`)
uvicorn openmed.service.app:app --host 127.0.0.1 --port 8000
# Or build it in-process (e.g. to mount under a parent app / add middleware):
from openmed.service import create_app
app = create_app()
curl -s localhost:8000/health
# {"status":"ok","service":"openmed-rest","version":"...","profile":"prod"}
curl -s localhost:8000/analyze -H 'content-type: application/json' -d '{
"text": "Patient received 75mg clopidogrel for NSTEMI.",
"model_name": "disease_detection_superclinical"
}'
curl -s localhost:8000/pii/deidentify -H 'content-type: application/json' -d '{
"text": "John Doe called 555-123-4567 on 01/15/2020.",
"method": "mask"
}'
openmed/service/app.py)| Method & path | Purpose | Request schema |
|---|---|---|
GET /health |
liveness + version + active profile | — |
GET /models/loaded |
cache/keep-alive status of resident models | — |
POST /models/unload |
unload one model or all inactive models | ModelUnloadRequest (model_name or all=true) |
POST /analyze |
clinical NER | AnalyzeRequest |
POST /pii/extract |
detect PII/PHI spans | PIIExtractRequest |
POST /pii/deidentify |
mask/remove/replace/hash/shift-dates PHI | PIIDeidentifyRequest |
Request fields (from openmed/service/schemas.py, strict — unknown fields are
rejected):
AnalyzeRequest: text (required), model_name
("disease_detection_superclinical"), confidence_threshold (0.0),
group_entities, aggregation_strategy (simple|first|average|max),
sentence_detection, sentence_language, sentence_clean,
use_fast_tokenizer, keep_alive.PIIExtractRequest: text, model_name
(default OpenMed/OpenMed-PII-SuperClinical-Small-44M-v1),
confidence_threshold (0.5), use_smart_merging, lang
(en/fr/de/it/es/nl/hi/te/pt/ar/ja/tr), normalize_accents, keep_alive.PIIDeidentifyRequest: same base plus method
(mask|remove|replace|hash|shift_dates, default mask),
confidence_threshold (0.7), keep_year, shift_dates, date_shift_days,
keep_mapping, policy, use_smart_merging, use_safety_sweep.Responses are the OpenMed result to_dict() (e.g. {text, entities[...], ...}).
Errors use a stable envelope: {"error": {"code", "message", "details"}} with
422 validation_error, 400 bad_request, 504 timeout, 500 internal_error.
ServiceRuntime.from_env() reads the process environment at startup
(openmed/service/runtime.py):
| Env var | Effect |
|---|---|
OPENMED_PROFILE |
config profile (prod default) |
OPENMED_SERVICE_PRELOAD_MODELS |
comma list of models to warm at startup |
OPENMED_SERVICE_KEEP_ALIVE |
default idle keep-alive before unload |
OPENMED_SERVICE_MAX_RESIDENT_MODELS |
cap resident models (warm pool) |
OPENMED_SERVICE_BATCHING_ENABLED |
enable dynamic request batching |
OPENMED_SERVICE_BATCH_MAX_SIZE |
max dynamic batch size (default 8) |
OPENMED_SERVICE_BATCH_MAX_WAIT_MS |
batch-collection window (default 5ms) |
OPENMED_SERVICE_PRELOAD_MODELS="disease_detection_superclinical" \
OPENMED_SERVICE_BATCHING_ENABLED=true \
uvicorn openmed.service.app:app --host 0.0.0.0 --port 8000
Preloading avoids first-request latency; the warm pool keeps hot models resident
and idle-unloads the rest. /analyze and /pii/extract coalesce concurrent
requests when batching is enabled.
pip install "openmed[service]", then run
uvicorn openmed.service.app:app (or build with create_app()).OPENMED_PROFILE, preload your hot models, and decide keep-alive / max
resident / batching to fit the box.GET /health; preloaded models warm during
the lifespan startup so the first real request isn't cold./analyze, /pii/extract, /pii/deidentify) with
the strict JSON schemas; handle the {"error": {...}} envelope.GET /models/loaded and POST /models/unload as
traffic shifts between models.FROM python:3.11-slim
RUN pip install --no-cache-dir "openmed[service]"
ENV OPENMED_SERVICE_PRELOAD_MODELS="disease_detection_superclinical"
EXPOSE 8000
CMD ["uvicorn", "openmed.service.app:app", "--host", "0.0.0.0", "--port", "8000"]
Bake/mount the model cache so containers don't re-download on every start; the service runs offline after that.
/analyze → openmed.analyze_text,
/pii/extract → openmed.extract_pii, /pii/deidentify →
openmed.deidentify. Results match the library exactly.deploying-openmed-mcp).batch-processing-clinical-text in a worker, not
per-request HTTP.127.0.0.1 for
local use; only expose 0.0.0.0 behind that proxy.extra="forbid"); a bad
lang/method/model_name returns 422/400 with a field-level reason.MAX_RESIDENT_MODELS trades RAM
for latency; tune to the box.504 per the profile's configured timeout; long inputs
may need a larger profile or pre-chunking.keep_mapping/policy outputs are sensitive. A de-id response with a
mapping re-identifies patients — only enable it for trusted callers and store
the mapping securely, never in service logs./docs and /openapi.json):
https://www.openapis.org/
openmed/service/app.py (routes), openmed/service/runtime.py
(ServiceRuntime), openmed/service/schemas.py (request models).