Stand up OpenMed's FastAPI REST service for clinical NER, PII extraction, and de-identification, with health checks, model keep-alive/unload, optional dynamic batching, and no-PHI logging. Use when the user wants to serve OpenMed over HTTP, deploy a de-id/NER REST API, run an inference endpoint for clinical text, add a /analyze or /pii/deidentify route, or containerize OpenMed as a service. Covers the service extra, launching create_app with uvicorn, the real endpoints (/health, /analyze, /pii/extract, /pii/deidentify, /models/loaded, /models/unload), request/response shapes, ServiceRuntime env-var configuration, and self-hosted auth/CORS/TLS notes.
74
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
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 8000Preloading 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).80da98c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.