
OpenMed REST API Recipes 实战指南从 curl 到类型化客户端的一站式调用手册【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed本篇技术指南以 OpenMed REST 服务的任务导向配方recipes为主线覆盖从本地启动服务、健康检查、临床 NER 文本分析、PII 提取与去标识化到模型热池管理与统一错误处理、类型化 Python 客户端的完整调用链路。读完本文你将能够在不依赖任何图形界面的情况下用curl或 Pythonrequests快速打通 OpenMed REST 服务的每一个核心接口并在真实部署中正确配置主机白名单、超时与模型生命周期策略。本文是 REST Service 端点参考手册的实操伴侣参考手册负责逐字段的契约与配置旋钮本文负责以最快的方式调通一次请求。所有示例均只使用合成数据。请勿将真实患者文本粘贴到共享终端、shell 历史或 issue 跟踪器中。模型支撑的响应可能回显请求文本与检测到的实体文本在真实部署中请将请求与响应负载都视为 PHI不要写入应用日志、遥测、缓存或未加密的工件。以下示例之所以打印字段仅仅因为其中每个值都是合成的。启动服务三种运行方式方式一本地 Uvicorn推荐用于开发与联调安装服务依赖并启动 Uvicornuv pip install -e .[hf,service] uvicorn openmed.service.app:app --host 127.0.0.1 --port 8080openmed.service.app:app是 app.py 中create_app()构造的 FastAPI 应用。下面的独立 Python 片段使用requests而requests有意不作为服务端依赖需要在客户端环境显式安装uv pip install requests第一个模型支撑请求会下载模型权重除非已经缓存。下载完成后推理完全在本地进行。对于离线/气隙部署可预先填充缓存并遵循 local-only offline mode。方式二Docker Compose使用仓库根目录下已检查入库的 docker-compose.yml 一键构建并启动映射端口8080通过命名卷持久化 Hugging Face 缓存docker compose up -d实际 Compose 文件内容节选确认了端口与缓存配置services: app: build: context: . ports: - 8080:8080 environment: OPENMED_PROFILE: ${OPENMED_PROFILE:-prod} OPENMED_CACHE_DIR: ${OPENMED_CACHE_DIR:-/root/.cache/huggingface/openmed} OPENMED_SERVICE_PRELOAD_MODELS: ${OPENMED_SERVICE_PRELOAD_MODELS:-} OPENMED_SERVICE_MAX_RESIDENT_MODELS: ${OPENMED_SERVICE_MAX_RESIDENT_MODELS:-} HF_TOKEN: ${HF_TOKEN:-} volumes: - hf-cache:/root/.cache/huggingface注意已检查入库的 Compose 端口映射会在所有主机接口上发布8080。仅本地使用时优先使用上面绑定 loopback 的 Uvicorn 命令或用主机防火墙 / Compose override 将发布端口限制到 loopback。在开放远程访问之前必须在反向代理处终止 TLS、启用 REST authentication 并收紧 trusted-host 白名单。CORS 是浏览器策略不是认证或传输安全。统一的基础 URL下面的示例假定服务在http://127.0.0.1:8080可达。先导出一次让curl片段保持简短export OPENMED_URLhttp://127.0.0.1:8080Python 片段共享同一个基础 URLimport requests BASE_URL http://127.0.0.1:8080主机与 CORS 白名单默认情况下服务只信任 loopbackHost头localhost,127.0.0.1,[::1]CORS 关闭。如果从其他主机调用配置OPENMED_SERVICE_TRUSTED_HOSTS如果前端是浏览器还需配置OPENMED_SERVICE_CORS_ORIGINS——详见 Browser and Host Allowlists。这两个白名单不加密也不认证流量远程部署仍需要 HTTPS 与认证。从源码看白名单解析位于 security_headers.py 的parse_cors_origins/parse_trusted_hosts两个变量都接受逗号分隔列表CORS 源必须是精确的 scheme/host/port 且不支持通配符。健康检查 —GET /health这是确认服务存活的最廉价调用永远不会加载模型curl --max-time 10 $OPENMED_URL/healthresponse requests.get(f{BASE_URL}/health, timeout10) response.raise_for_status() print(response.json())响应{ status: ok, service: openmed-rest, version: 2.3.0, profile: prod }GET /health是向后兼容的健康别名。对编排器请使用GET /livez进程存活和GET /readyz启动就绪。在启动完成之前/readyz返回503且error.code为not_ready。这三个路由都由 app.py 中的health/livez/readyz处理函数提供其中/readyz依赖_readiness_middleware在启动预加载完成前翻转状态。分析临床文本 —POST /analyze在自由文本上运行医学 NER 模型。model_name默认为disease_detection_superclinicalconfidence_threshold默认为0.0。curl -sS --max-time 310 -X POST $OPENMED_URL/analyze \ -H Content-Type: application/json \ -d { text: Patient started imatinib for CML., model_name: disease_detection_superclinical, confidence_threshold: 0.5 }payload { text: Patient started imatinib for CML., model_name: disease_detection_superclinical, confidence_threshold: 0.5, } response requests.post(f{BASE_URL}/analyze, jsonpayload, timeout310) response.raise_for_status() result response.json() for entity in result[entities]: print(entity[label], entity[text], round(entity[confidence], 3))代表性响应与analyze_text(..., output_formatdict)形状一致分数与耗时随硬件变化{ text: Patient started imatinib for CML., entities: [ { text: CML, label: DISEASE, confidence: 0.957, start: 29, end: 32, metadata: { sentence_index: 0, sentence_text: Patient started imatinib for CML., sentence_start: 0, sentence_end: 33, span_valid: true } } ], model_name: disease_detection_superclinical, timestamp: 2026-07-11T16:58:55.987165, processing_time: 1.527, metadata: { sentence_detection: true, sentence_count: 1, sentence_language: en, medical_tokenizer: true, max_length: 512 } }从源码看/analyze的处理函数对应 app.py 中的analyze(payload, request)底层调用 openmed/init.py 中定义的analyze_text()顶层函数其完整签名还支持aggregation_strategysimple/first/average/max、group_entities、sentence_detection、sentence_language、use_fast_tokenizer与请求级keep_alive。除text外所有字段都有默认值最小可用请求体其实只有{text: ...}。提取 PII —POST /pii/extract检测个人身份信息。除非显式设置model_name否则 OpenMed 会为lang选择推荐的 PII 模型。36 个受支持的 PII 语言代码am、ar、as、bn、cs、da、de、el、en、es、fa、fr、he、hi、id、it、ja、ko、mr、nl、no、or、pt、ro、ru、sv、sw、ta、te、th、tr、uk、vi、xh、zh、zu。俄语当前使用一个有文档说明的多语言默认模型占位符。API 还接受四条可选印度语种路由gu、kn、ml、pa前提是配置了OPENMED_INDIC_NER_MODEL或显式模型阿萨姆语、孟加拉语、印地语、马拉地语、奥里亚语、泰米尔语和泰卢固语也可使用该适配器。confidence_threshold默认为0.5。语言校验的底层实现在 openmed/utils/gateway.py 的validate_language()它把核心 PII 目录SUPPORTED_LANGUAGES | INDIC_NER_LANGUAGES | USER_SUPPLIED_MODEL_LANGUAGES必要时并入NATIONAL_ID_ONLY_LANGUAGES合并为允许集合小写化并去除空白后校验非法代码抛出InputValidationErrorcodelanguage_required/language_type/unsupported。同时 client.py 中的PIILanguage类型标注也完整登记了这些代码。curl -sS --max-time 310 -X POST $OPENMED_URL/pii/extract \ -H Content-Type: application/json \ -d { text: Patient Jordan Ramirez, MRN 4482910, called from 555-0147., lang: en, use_smart_merging: true }payload { text: Patient Jordan Ramirez, MRN 4482910, called from 555-0147., lang: en, use_smart_merging: True, } response requests.post(f{BASE_URL}/pii/extract, jsonpayload, timeout310) response.raise_for_status() for entity in response.json()[entities]: print(entity[label], entity[start], entity[end], entity[text])代表性响应与extract_pii(...).to_dict()形状一致分数与耗时随硬件变化{ text: Patient Jordan Ramirez, MRN 4482910, called from 555-0147., entities: [ { text: Jordan, label: first_name, confidence: 0.999, start: 8, end: 14, metadata: {span_valid: true} }, { text: Ramirez, label: last_name, confidence: 0.999, start: 15, end: 22, metadata: {span_valid: true} }, { text: MRN 4482910, label: medical_record_number, confidence: 0.708, start: 24, end: 35, metadata: {span_valid: true} }, { text: 555-0147, label: phone_number, confidence: 0.992, start: 49, end: 57, metadata: {span_valid: true} } ], model_name: OpenMed/OpenMed-PII-SuperClinical-Small-44M-v1, timestamp: 2026-07-11T16:58:02.718948, processing_time: 1.772, metadata: { sentence_detection: true, sentence_count: 1, sentence_language: en, medical_tokenizer: true, max_length: 512, clinical_protection: { source: openmed/core/data/clinical_protect_terms.txt, version: clinical-protect-terms-v1, protected_term_count: 71, checked_spans: 2, suppressed_spans: 0, enabled: true } } }注意响应元数据中的clinical_protection块服务在返回实体前对检测跨度运行临床保护词过滤来源openmed/core/data/clinical_protect_terms.txt当前版本clinical-protect-terms-v1含 71 个受保护术语防止把临床术语误报为 PII。核心的extract_pii()实现在 openmed/core/pii.py支持use_smart_merging等开关。去标识化文本 —POST /pii/deidentify对检测到的 PII 进行脱敏。method取值mask、remove、replace、hash或shift_dates默认mask。keep_mapping默认为false此处有意省略启用它会在响应中返回占位符到原文的映射应仅保留给受控的可逆工作流。confidence_threshold默认为0.7。curl -sS --max-time 310 -X POST $OPENMED_URL/pii/deidentify \ -H Content-Type: application/json \ -d { text: Call 555-0147 to confirm the appointment., method: mask, lang: en }payload { text: Call 555-0147 to confirm the appointment., method: mask, lang: en, } response requests.post(f{BASE_URL}/pii/deidentify, jsonpayload, timeout310) response.raise_for_status() result response.json() print(result[deidentified_text]) print(redacted:, result[num_entities_redacted])代表性简化响应deidentify(...).to_dict()分数、耗时与嵌套 provenance 元数据随运行环境变化{ original_text: Call 555-0147 to confirm the appointment., deidentified_text: Call [phone_number] to confirm the appointment., pii_entities: [ { text: 555-0147, label: phone_number, entity_type: phone_number, start: 5, end: 13, confidence: 0.986, redacted_text: [phone_number], canonical_label: PHONE, sources: [ml], evidence: { raw_label: phone_number, language: en, model_id: OpenMed/OpenMed-PII-SuperClinical-Small-44M-v1 }, threshold: 0.7, action: mask, surrogate: [phone_number], metadata: {span_valid: true} } ], method: mask, timestamp: 2026-07-11T16:58:43.445519, num_entities_redacted: 1, metadata: { sentence_detection: true, sentence_count: 1, sentence_language: en, medical_tokenizer: true, max_length: 512, safety_sweep: { source: safety_sweep, patterns_version: safety-sweep-v1, spans_added: 0 } }, audit_report: null }响应中的safety_sweep元数据表示服务在模型输出之外还运行了基于模式的兜底扫描当前模式版本safety-sweep-v1本次未新增跨度。每个实体都携带threshold本次请求生效的置信度阈值 0.7、action脱敏动作与surrogate占位符方便调用方直接消费而不必自行重新解析。若要将日期平移而非脱敏使用method: shift_dates并可选的date_shift_dayscurl -sS --max-time 310 -X POST $OPENMED_URL/pii/deidentify \ -H Content-Type: application/json \ -d { text: Patient Jordan Ramirez was admitted on 2026-01-02., method: shift_dates, date_shift_days: 30, lang: en }已弃用的布尔shift_dates: true仍被接受为method: shift_dates的别名schemas.py 中的_normalize_shift_dates_payload负责这一兼容转换。核心deidentify()实现在 openmed/core/pii.pyDeidentificationMethod字面量类型与各方法默认值定义在 client.py。检查已加载模型 —GET /models/loaded报告热池warm-pool缓存、常驻模型与空闲卸载倒计时。此调用不会加载任何模型。curl --max-time 10 $OPENMED_URL/models/loadedresponse requests.get(f{BASE_URL}/models/loaded, timeout10) response.raise_for_status() state response.json() print(warm:, state[warm_models]) print(resident cap:, state[max_resident_models])未配置的本地服务刚启动时的响应{ default_keep_alive_seconds: null, max_resident_models: null, memory_budget_bytes: null, resident_memory_bytes: 0, pending_memory_bytes: 0, memory_admission_wait_seconds: 0.05, warm_models: [], models: {} }在模型支撑请求之后models为每个已解析模型各含一个条目报告其缓存资源、活跃请求数、常驻状态、空闲卸载倒计时与内存占用warm_models只列出已配置的预加载集合。default_keep_alive_seconds、max_resident_models、memory_budget_bytes在对应可选环境变量未设置时为null。从源码看这些状态来自 warm_pool.py 的WarmPool.loaded_models()与ServiceRuntime.loaded_models()runtime.py。max_resident_models由OPENMED_SERVICE_MAX_RESIDENT_MODELS控制超限时按 LRU 卸载最久未用的空闲模型default_keep_alive_seconds由OPENMED_SERVICE_KEEP_ALIVE控制接受秒数或时长字符串30s、5m、1h30m、1d省略则无限期缓存0表示请求后即卸载。卸载模型 —POST /models/unload释放一个非活跃模型或全部释放。若模型仍有活跃请求服务会保留它并报告活跃请求数。卸载单个模型curl -sS --max-time 30 -X POST $OPENMED_URL/models/unload \ -H Content-Type: application/json \ -d {model_name: disease_detection_superclinical}payload {model_name: disease_detection_superclinical} response requests.post(f{BASE_URL}/models/unload, jsonpayload, timeout30) response.raise_for_status() print(response.json())代表性响应在 analyze 配方已加载默认疾病模型之后释放的资源计数随后端变化{ unloaded: true, model_name: OpenMed/OpenMed-NER-DiseaseDetect-SuperClinical-434M, active_requests: 0, loading: false, released: {models: 0, tokenizers: 0, pipelines: 1} }卸载所有非活跃模型curl -sS --max-time 30 -X POST $OPENMED_URL/models/unload \ -H Content-Type: application/json \ -d {all: true}response requests.post( f{BASE_URL}/models/unload, json{all: True}, timeout30 ) response.raise_for_status() print(response.json())代表性响应分析与 PII 两条 pipeline 均被缓存之后释放的资源计数随后端变化{ unloaded: true, released: {models: 0, tokenizers: 0, pipelines: 2}, active_models: {} }发送model_name卸载一个模型或发送all: true卸载全部非活跃模型。两者都不发送会返回下文描述的参数校验错误信封。不要同时发送两者当前 schema 在两个字段都存在时把all: true视为全量卸载操作对应 schemas.py 中的ModelUnloadRequest与 warm_pool.py 的unload_model/unload_all_models。错误处理统一的 JSON 信封在请求通过已配置的主机与 CORS 中间件之后应用端点的错误使用一个 JSON 信封因此单个处理器即可覆盖参数校验、bad-request、超时与内部错误{ error: { code: validation_error, message: Request validation failed, details: [ { field: body.text, message: Value error, Text must not be blank, type: value_error } ], request_id: recipe-validation-error } }常见的error.code值包括validation_error、bad_request、timeout、not_ready、rate_limited、backpressure、service_busy、circuit_breaker_open与internal_error。认证与隐私网关功能会追加各自的文档化代码。details可以是列表参数校验错误、对象例如{timeout_seconds: 300}或null。每个 HTTP 响应都携带X-Request-ID头应用生成的错误信封也会在error.request_id中回显它以便关联日志而中间件拒绝可能只提供响应头。复现参数校验信封发送空textcurl -sS --max-time 60 -X POST $OPENMED_URL/analyze \ -H Content-Type: application/json \ -H X-Request-ID: recipe-validation-error \ -d {text: }def analyze(text: str) - dict: response requests.post( f{BASE_URL}/analyze, json{text: text}, headers{X-Request-ID: recipe-validation-error}, timeout60, ) if response.status_code 400: error response.json()[error] request_id response.headers.get(X-Request-ID) raise RuntimeError( f{response.status_code} {error[code]}: {error[message]} f(request_id{request_id}) ) return response.json() analyze( ) # raises RuntimeError with code validation_error从源码看错误信封的构造集中在 app.py 的_error_response()与一系列异常处理器_request_validation_handler、_timeout_handler、_circuit_breaker_handler等。关于错误码到 HTTP 状态码的完整映射与兼容性保证可参考 Structured public errors请求 schema 校验失败继续使用 HTTP 422 与validation_error公共 Python 异常分类输入/配置/策略类 → 400能力/预算类 → 503内部/推理类 → 500。类型化 Python 客户端跳过手工状态检查serviceextra 附带一个基于httpx构建的类型化同步客户端openmed.service.client.OpenMedClient实现位于 client.py。它将分析、PII、隐私网关与模型缓存端点映射为方法并在任何非 2xx 响应时抛出OpenMedAPIError因此无需再手工检查状态码from openmed.service.client import OpenMedAPIError, OpenMedClient with OpenMedClient(http://127.0.0.1:8080, timeout310.0) as client: analysis client.analyze( Patient started imatinib for CML., model_namedisease_detection_superclinical, confidence_threshold0.5, ) pii client.extract_pii( Patient Jordan Ramirez, MRN 4482910, called from 555-0147., langen, ) redacted client.deidentify( Patient Jordan Ramirez was admitted on 2026-01-02., methodmask, ) loaded client.loaded_models() try: client.unload_model(disease_detection_superclinical) except OpenMedAPIError as exc: print(exc.status_code, exc.code, exc.message, exc.request_id)OpenMedAPIErrorclient.py暴露服务错误code、message、可选details、HTTPstatus_code以及来自响应头的request_id响应没有时取外发客户端请求 ID。使用client.unload_all_models()一次释放全部非活跃模型。客户端还内置了与 OpenAPI 契约对齐的端点元数据表CLIENT_ENDPOINTS可据此做规范级验证。客户端默认超时30.0秒上面示例特意使用310秒——它刚好高于生产 profile 默认的300秒服务端截止时间使客户端能收到服务的结构化超时信封。如果你的部署使用更长的服务端截止时间请同时调高这两个值。请求级keep_alive如5m、10m也可以在analyze/extract_pii/deidentify/privacy_gateway上按请求覆盖服务端默认缓存策略。进一步阅读REST Service — 完整端点参考、配置环境变量、白名单以及 Docker/Compose 运行路径REST Authentication — 在这些端点之前启用可选的 API-key 与 bearer-token 校验Async REST Jobs Webhooks — 适用于不应长时间占用客户端连接的大型去标识化批次Structured public errors — 错误码与 HTTP 状态码的完整映射及兼容性保证说明文档评估为可成文。原文档docs/rest-recipes.md内容完整、技术实质充分——包含启动命令、六大端点配方、完整响应示例、错误信封与类型化客户端我已完整继承其全部配置示例、命令与响应结构并结合仓库源码 client.py、app.py、warm_pool.py、gateway.py、core/pii.py、docker-compose.yml 等对默认参数、语言目录、热池语义与错误码映射做了纵深扩充文档内部相对链接已全部转换为以仓库根目录为起点的相对路径未引用与主题无关图片。【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考