CtrlK
BlogDocsLog inGet started
Tessl Logo

serving-openmed-rest-api

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

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

SKILL.md
Quality
Evals
Security

Serving OpenMed over REST

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.

When to use this skill

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.

Quick start

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"
}'

Endpoints (confirmed in openmed/service/app.py)

Method & pathPurposeRequest schema
GET /healthliveness + version + active profile
GET /models/loadedcache/keep-alive status of resident models
POST /models/unloadunload one model or all inactive modelsModelUnloadRequest (model_name or all=true)
POST /analyzeclinical NERAnalyzeRequest
POST /pii/extractdetect PII/PHI spansPIIExtractRequest
POST /pii/deidentifymask/remove/replace/hash/shift-dates PHIPIIDeidentifyRequest

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.

Configuring the runtime (env vars)

ServiceRuntime.from_env() reads the process environment at startup (openmed/service/runtime.py):

Env varEffect
OPENMED_PROFILEconfig profile (prod default)
OPENMED_SERVICE_PRELOAD_MODELScomma list of models to warm at startup
OPENMED_SERVICE_KEEP_ALIVEdefault idle keep-alive before unload
OPENMED_SERVICE_MAX_RESIDENT_MODELScap resident models (warm pool)
OPENMED_SERVICE_BATCHING_ENABLEDenable dynamic request batching
OPENMED_SERVICE_BATCH_MAX_SIZEmax dynamic batch size (default 8)
OPENMED_SERVICE_BATCH_MAX_WAIT_MSbatch-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.

Workflow

  1. Install + launch. pip install "openmed[service]", then run uvicorn openmed.service.app:app (or build with create_app()).
  2. Configure the runtime via env vars before start: set OPENMED_PROFILE, preload your hot models, and decide keep-alive / max resident / batching to fit the box.
  3. Front it with auth/TLS. Place a reverse proxy or gateway (API keys/mTLS, CORS allow-list) ahead of the app — it has none built in.
  4. Health-check + warm. Poll GET /health; preloaded models warm during the lifespan startup so the first real request isn't cold.
  5. Call the endpoints (/analyze, /pii/extract, /pii/deidentify) with the strict JSON schemas; handle the {"error": {...}} envelope.
  6. Manage memory with GET /models/loaded and POST /models/unload as traffic shifts between models.

Containerizing

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.

Hand-off to / from OpenMed

  • Same engine, different surface: /analyzeopenmed.analyze_text, /pii/extractopenmed.extract_pii, /pii/deidentifyopenmed.deidentify. Results match the library exactly.
  • Agents/tools: for Claude Code / Codex / chat clients, expose the same capabilities as MCP tools instead (deploying-openmed-mcp).
  • Bulk: for corpora, call batch-processing-clinical-text in a worker, not per-request HTTP.

Edge cases & gotchas

  • No built-in auth/CORS/TLS. The app ships hardened input validation but no authentication. Put it behind your own reverse proxy / API gateway (mTLS, API keys, CORS allow-list) before any real traffic. Bind 127.0.0.1 for local use; only expose 0.0.0.0 behind that proxy.
  • No-PHI logging. Don't add request/response body logging — that's PHI. The error envelope is designed to avoid echoing input; keep it that way. Log status codes, timings, and model names only.
  • Strict schemas. Unknown JSON fields are rejected (extra="forbid"); a bad lang/method/model_name returns 422/400 with a field-level reason.
  • Cold start vs memory. Preloading + a high MAX_RESIDENT_MODELS trades RAM for latency; tune to the box.
  • Timeouts return 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.

Standards & references

  • FastAPI: https://fastapi.tiangolo.com/
  • Uvicorn (ASGI server): https://www.uvicorn.org/
  • OpenAPI (the service auto-serves /docs and /openapi.json): https://www.openapis.org/
  • HIPAA de-identification, 45 CFR 164.514(b): https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/index.html
  • OpenMed source: openmed/service/app.py (routes), openmed/service/runtime.py (ServiceRuntime), openmed/service/schemas.py (request models).
Repository
maziyarpanahi/openmed
Last updated
First committed

Is this your skill?

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.