简介:以医疗NLP实战为主题,这份PDF聚焦三甲医院如何利用DeepSeek构建病历分析私有化系统,面向医院信息化人员、NLP开发者和医疗AI产品经理。资源为单个PDF文件,共33页,压缩包大小2.14MB,已有83人学习浏览。文档从医疗NLP重要性、三甲医院病历分析现状与挑战切入,覆盖业务、功能、性能与安全需求分析,并系统讲解DeepSeek的架构原理、自注意力机制、预训练与微调方法,以及软硬件环境搭建、病历数据清洗与标注、模型选型与定制、系统集成测试等环节。症状提取、疾病诊断、治疗方案推荐等核心功能模块的开发思路逐一展开,同时涉及私有化部署、性能优化、数据加密、访问控制、医疗法规遵循和真实医院应用效果评估,末章还对技术趋势与行业影响进行了展望。读者可获得从需求规划到落地验证的完整路径,作为医疗NLP项目选型、系统设计和开发实施的重要参考。
1. 为什么三甲医院要把病历分析做成私有化:一份出院小结的流转困境
我接过不少医院信息科的需求,最典型的不是“我要上大模型”,而是“我要让病历能查、能统计、能给科研用”。三甲医院的病案室和临床科室手里有海量的出院小结、入院记录、病程和检验报告,里面全是自由文本。过去做统计靠人工翻病历,一个课题要三个人抽两周。现在大家知道大模型能抽结构化数据,但文件夹里那几十万份病历,一份都出不了内网——传公有云,伦理和合规这关就过不去,科室也不敢拿患者数据做赌注。于是“DeepSeek本地部署到院内,做病历分析私有化系统”这个方向就自然冒出来了。
DeepSeek 这类开源模型在医院落地,真正解决的痛点不是“AI比医生强”,而是“让病历数据变成结构化资产,且不出医院大门”。方案本身不神:一台带大显存GPU的服务器,把模型文件放到内网,用推理框架起服务,再写一套抽取流水线把病历文本变成结构化字段,喂给科研统计和质控。能做到什么程度?院长问“过去一年全院心衰患者出院带药里,β受体阻滞剂使用率是多少”,让系统跑一遍,半小时出结果,这就是它的价值。
这套东西适合谁?临床科研团队、病案室、信息科运维、做医疗数据服务的公司。新手能照着步骤把服务拉起来看见输出,熟手能在这里面看到参数边界和坑。下面我会按“选型逻辑 → 数据处理 → 部署参数 → 踩坑 → 验证”的顺序,把我做这套系统的方案完整拆开。
2. 私有化病历分析的技术底座:为什么选DeepSeek而不是通用大模型
2.1 医疗信息科的选型逻辑:参数、显存与内网合规
医院不是互联网公司,采购一台GPU服务器要走流程,预算批下来不容易,所以选型第一条不是“效果最好”,而是“现有硬件能跑起来”。满血版671B的DeepSeek需要8卡A100/H100,绝大多数三甲医院没有这个条件。我一般建议看DeepSeek-R1的蒸馏系列,14B、32B这类可以在单张A100/A800的40GB显存上跑INT4/INT8量化,效果在医疗术语抽取这个任务上已经完全够用。
第二条是数据合规。病案首页、出院小结、检查报告都带患者身份信息,这些数据进不了公有云API。DeepSeek开源权重意味着可以完全离线部署,网络可以断开,只在内网提供服务。整个闭环里没有一个请求出医院边界,这是那些只能调API的模型做不到的。
第三条是可控性。用开源模型可以自己微调、可以做提示词层面的约束、可以兜底。你不希望模型某天突然在一个病历里抽出一个医生看不懂的字段,然后没有人能解释为什么。私有化部署之后,模型是固定的、参数是固定的、输出格式是通过校验逻辑卡死的,出了问题能复现、能回滚,这对医疗场景非常重要。
2.2 私有化落地的三种路径:vLLM、Ollama 与 LMDeploy 怎么选
我做过对比测试,三条路各有适用场景:
| 路径 | 适用场景 | 推荐度 |
|---|---|---|
| vLLM + OpenAI兼容接口 | 生产环境,高并发抽取,需要PagedAttention省显存 | 主力推荐 |
| Ollama | 单机测试、快速验证模型效果 | 验证友好,生产不推荐 |
| LMDeploy | 国产卡适配、贪心优化显存 | 备选,硬卡兼容时可考虑 |
Ollama 的优势是上手极快,一条命令把模型下下来就能跑,适合先验证“这个模型在病历上效果怎么样”。但问题在于并发控制不够精细,而且自定义采样参数不如 vLLM 直接。真到了每天要抽几千份病历的时候,Ollama 会明显吃力。
我用 vLLM 做生产服务,原因是它支持连续批处理(continuous batching),多份病历并发推理时吞吐量高很多,显存利用率也更好。LMDeploy 我只有在遇到国产加速卡(比如昇腾、寒武纪)的时候才会切过去,因为 vLLM 对国产卡的适配不如 LMDeploy 完整。
2.3 部署前要确认的三张清单
第一张是硬件清单。病历抽取这个场景,实际上不需要训练,只做推理。一张40GB或80GB显存的卡即可。如果并发要求高,两张卡做张量并行更好。CPU至少16核,内存64GB,硬盘留出模型和病历库的空间——量化后的14B模型约10GB,但日志、临时文件、向量库都会膨胀,建议预留500GB以上。
第二张是软件清单。操作系统建议Ubuntu 20.04/22.04 LTS;CUDA版本和驱动要匹配,别在驱动上省时间;Docker 是必选项,因为院内可能没有外网环境,包管理不方便,Docker镜像可以在有网的机器上提前打好再导入。
第三张是数据清单。要明确病历从哪来——HIS导出、病案系统导出、还是手工整理的Excel?格式是TXT、PDF还是需要OCR的扫描件?这个决定你要花多少精力做预处理。PDF解析是所有坑里最大的一个,我后面会专门讲。
提示:采购前先问清楚科室要抽哪些字段。字段清单直接影响提示词设计和抽取的复杂度,不要等模型买回来了才讨论需求。
3. 把病历变成结构化数据:从自由文本到可查询的字段
3.1 病历预处理的三个步骤:分页、去重、分块
病历文本和普通文本不一样,它格式松散、包含大量重复信息。一份出院小结可能包含“入院情况”“出院诊断”“出院医嘱”“医师签名”等多个语义段落,段与段之间没有固定标记,有的医院用空格隔开,有的用换行符号,有的干脆连在一起。
我一般会把预处理拆成三步:
第一步,统一编码和格式。从医院系统导出的文件经常是GBK编码,或者带BOM的UTF-8,先统一转成UTF-8无BOM。
import chardet from pathlib import Path def normalize_file(input_path: str, output_path: str) -> None: raw = Path(input_path).read_bytes() # 先用 chardet 探测编码,再按探测结果解码 detected = chardet.detect(raw) encoding = detected.get("encoding", "utf-8") or "utf-8" text = raw.decode(encoding, errors="ignore") # 统一换行符,把 \r\n 和 \r 都转成 \n text = text.replace("\r\n", "\n").replace("\r", "\n") # 去掉 BOM 和各种不可见控制字符 text = text.lstrip("\ufeff") text = "".join(ch for ch in text if ch >= "\x20" or ch == "\n") Path(output_path).write_text(text, encoding="utf-8") # 用法:normalize_file("入院记录_原始.txt", "入院记录_utf8.txt")逻辑说明:chardet 做编码探测在中文病历场景命中率足够,但遇到很短的文件会出现误判,错误时用 errors="ignore" 兜底,宁可丢一个字符也不中断整批任务。统一换行符是为了后续的正则匹配和语义分块不受Windows和Linux差异影响。
第二步,按患者和就诊事件去重。同一个患者多次住院会产生多份病历,如果按“入院记录+出院小结”为单位,就要以“住院号+入院时间”为唯一键合并。
import pandas as pd def dedup_by_admission(df: pd.DataFrame) -> pd.DataFrame: # 一份病历必须要有住院号和入院日期,否则说明原表有缺漏 df = df.dropna(subset=["住院号", "入院日期"]) # 同一住院号+入院日期下,保留最早更新的那份文件 df = df.sort_values("更新时间", ascending=False) df = df.drop_duplicates(subset=["住院号", "入院日期"], keep="first") return df # 假设 df 是从 HIS 导出的文件清单表 # df = pd.read_excel("病历文件索引.xlsx") # df = dedup_by_admission(df)参数说明:sort_values 降序 + drop_duplicates keep="first",取的是同一个住院事件下最新导出的文件,避免旧版病历被重复抽取。这个去重逻辑若不处理,最后的统计分析会出现同一个病例被重复计数,医生拿到结果是没法用的。
第三步,分块并保留语义边界。病历过长时容易在Single-turn里失真,我习惯以“段落标记”或“关键词行”切块:像“入院情况:”“入院诊断:”“诊疗经过:”“出院医嘱:”这种词就是天然的语义边界,先用关键词切,再按字符长度做后手补刀。
import re BOUNDARY_PATTERN = re.compile( r"(?=\n?\s*(入院情况|既往史|个人史|家族史|" r"体格检查|辅助检查|初步诊断|出院诊断|" r"诊疗经过|出院医嘱|出院带药|医师签名)[::])" ) def split_chart(text: str, max_block_chars: int = 1500) -> list[str]: # 先按关键段标题切,保证不把“出院带药”和“出院医嘱”切到两块 sections = BOUNDARY_PATTERN.split(text) sections = [s for s in sections if s and s.strip()] blocks = [] for sec in sections: if len(sec) <= max_block_chars: blocks.append(sec.strip()) else: # 超长段落做硬切,但从上一句结束处断开,避免切在一句话中间 parts = [sec[i:i+max_block_chars] for i in range(0, len(sec), max_block_chars)] blocks.extend([p.strip() for p in parts]) return blocks # text = Path("出院小结_utf8.txt").read_text(encoding="utf-8") # blocks = split_chart(text)逻辑说明:正则里的(?=...)是零宽断言,不会吃掉段标题,只作为切分位置;::同时兼容中英文冒号。硬切策略在超长段落里按1500字切,这个阈值可以根据模型最大上下文长度调整——14B蒸馏版最大32K,实际每份病历切完后加提示词大约2K token,剩余空间留来保证长病历也能一次放入。
3.2 实体抽取的提示词设计与JSON输出约束
病历的结构化抽取本质是让模型做一个“文本→JSON”的变换。提示词设计得好不好,直接决定输出质量和后续解析成本。我常用的策略是:给足例子,不给模型自由发挥的空间。
SYSTEM_PROMPT = """你是一名病案编码员。请从给定的出院小结文本中提取指定字段,输出严格 JSON 对象。 规则: 1. 只输出 JSON,不要输出任何解释。 2. 数值字段保留原始单位和数值,如 "50/30mmHg"。 3. 缺失字段使用空字符串 "",禁止自行推测或编造。 4. 诊断字段保留医生原始诊断词,不要改写。 输出格式: { "住院号": str, "出院诊断_主诊断": str, "出院诊断_其他诊断": list[str], "手术操作": list[str], "住院天数": str, "出院带药": list[{"药品名": str, "用法用量": str}], "出院去向": str }"""出格式里标注了缺失用空字符串、禁止编造,这一条在做医疗数据时最重要。“禁止自行推测”能明显减少幻觉,但加了这一条,抽取结果里会出现大量空字段,需要配合后续的规则兜底——比如“住院天数”缺失时,用“出院日期-入院日期+1”算出来。
调用侧我直接在提示词里要求“只输出JSON”,再用vLLM服务端的response_format={"type": "json_object"}做格式约束,这样99%的返回可以直接解析。
import requests import json def extract_chart(block_text: str, api_url: str = "http://内网IP:8000/v1/chat/completions") -> dict: payload = { "model": "deepseek-r1-distill-14b", "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": block_text[:8000]} ], "temperature": 0.1, "max_tokens": 1024, "response_format": {"type": "json_object"} } resp = requests.post(api_url, json=payload, timeout=60) resp.raise_for_status() content = resp.json()["choices"][0]["message"]["content"] # 模型偶尔会在 JSON 外套一层 ```json ... ```,剥掉再解析 content = content.strip() if content.startswith("```"): content = content.strip("`") content = content.removeprefix("json").strip() return json.loads(content) # result = extract_chart(open("出院小结_utf8.txt").read())参数说明:temperature 设置 0.1 而不是 0,是因为部分 vLLM 版本 temperature=0 时采样会走 greedy,但某些后端聚合算子有浮点误差,设 0.1 可以规避偶发的重复输出,效果上几乎无差别。response_format是 vLLM 兼容 OpenAI 的 JSON 模式,开发时用这个能省掉一半的解析踩坑。
3.3 抽取结果落库与二次校验
得到JSON之后不能直接入库,还要过一遍校验。我通常会写一个校验函数,检查关键字段是否有值、药品字段是否列表、用量是否包含单位。
import pydantic from typing import Optional class DrugItem(BaseModel): 药品名: str 用法用量: str class ChartOutcome(BaseModel): 住院号: Optional[str] = "" 出院诊断_主诊断: Optional[str] = "" 出院诊断_其他诊断: list[str] = [] 手术操作: list[str] = [] 住院天数: Optional[str] = "" 出院带药: list[DrugItem] = [] 出院去向: Optional[str] = "" def validate_and_save(raw_json: dict, output_path: str) -> None: # pydantic 负责把关,缺字段或类型不对直接抛错,便于定位是哪份病历 parsed = ChartOutcome.model_validate(raw_json) # 这里做业务规则:主诊断为空就要记进异常日志 if not parsed.出院诊断_主诊断: print("WARN 缺主诊断: ", parsed.住院号) all_data.append(parsed.model_dump()) # 按行追加到 JSONL 文件,方便断点续跑 with open(output_path, "a", encoding="utf-8") as f: f.write(json.dumps(parsed.model_dump(), ensure_ascii=False) + "\n")逻辑说明:pydantic 在这里做的是结构校验,不依赖模型输出是否合法。类型不对的直接报错,能立刻发现提示词把字段结构改坏了。逐行追加写入 JSONL 而不是一次性写大盘,是因为几千份病历抽取时间很长,中途崩掉时可以跳过已处理文件继续跑。
注意:写库之前把患者姓名、身份证号、手机号这类个人信息剥离或替换为脱敏编号。病历分析系统的内网不等于都合法,能少留存敏感字段就少留存。
4. vLLM 部署 DeepSeek 的完整流程与关键参数
4.1 模型文件准备与环境搭建
私有化环境通常没外网,所有离线依赖要在有网的机器上准备好。步骤看起来琐碎,但少一步后面都难补。
# 在能联网的机器上拉取模型(huggingface-cli 可以断点续传) huggingface-cli download deepseek-ai/DeepSeek-R1-Distill-14B --local-dir ./models/deepseek-r1-distill-14b # 导出 vLLM 官方 Docker 镜像,内网用 docker load 导入 docker pull vllm/vllm-openai:latest docker save vllm/vllm-openai:latest -o vllm-image.tar这个模型镜像大约 5GB 左右,用 U 盘拷入医院内网后执行docker load -i vllm-image.tar。模型权重如果大于镜像,同样打包拷进去。我吃过一次亏:只拷了镜像忘了拷模型文件,在机房对着一个空目录折腾半天。
4.2 启动 vLLM 服务的关键参数解读
vLLM 的启动参数决定了服务的吞吐上限、并发能力和能容纳的最大文本长度。直接给一个适合病历抽取场景的启动脚本:
docker run --gpus all --shm-size=32g \ -v /data/models:/models \ -v /data/logs:/logs \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model /models/deepseek-r1-distill-14b \ --served-model-name deepseek-r1-distill-14b \ --max-model-len 8192 \ --gpu-memory-utilization 0.92 \ --tensor-parallel-size 1 \ --dtype auto \ --enforce-eager \ --disable-log-requests \ --api-key sk-internal-001参数说明:
--max-model-len 8192:最大上下文长度。14B 模型原生支持 32K,但病历场景里单次请求很少超过 4K token,设 8192 可以给并发批处理省显存。如果你处理的是超长病程记录,可以改成 16384,但显存占用会同步上涨。--gpu-memory-utilization 0.92:允许 vLLM 占用 92% 显存做 KV cache,剩余保留给 CUDA context。设太高(0.98)在并发时会显存溢出,设太低吞吐折扣严重。--tensor-parallel-size 1:单卡推理不用切分。双卡时改 2 并加--distributed-executor-backend mp。这里补充一点,多卡时 NCCL 通信在院内千兆网下会严重拖慢速度,建议至少用两张卡做张量并行并且之间走 NVLink。--enforce-eager:关闭 CUDA Graph 捕获。老显卡驱动版本下 CUDA Graph 可能失败,这个参数牺牲一点性能换稳定性,内网环境没有重启容错空间,稳定优先。--disable-log-requests:关闭请求内容日志。病历文本不能进服务日志,这是合规底线,一定要加。--api-key sk-internal-001:给服务加访问凭证。内网不等于无人,其他科室的终端也会扫到 8000 端口,加一层认证能免掉很多尴尬。
4.3 用 OpenAI 兼容接口调用:从开发机到 Web 服务的接入方式
vLLM 启动后默认兼容 OpenAI 的/v1/chat/completions接口,所以代码可以用 OpenAI SDK 或直接 requests 调用。我这里讲两种接入方式。
第一种是 Python SDK 方式,适合写抽取流水线:
from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8000/v1", api_key="sk-internal-001" ) response = client.chat.completions.create( model="deepseek-r1-distill-14b", messages=[{"role": "user", "content": "请从以下病历中提取:\n" + chart_text}], temperature=0.1, max_tokens=1024 ) print(response.choices[0].message.content)第二种是标准化 Web API,封装给医院内网其他系统调用,比如科室上报数据或者质控程序。这里顺手提一嘴,很多团队用 FastAPI 把抽取逻辑再包一层,对内只暴露结构化接口,避免每个科室直接拼 prompt。这样做的好处是后续换模型时不用改接入方。
from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() class ChartRequest(BaseModel): chart_text: str chart_id: str class ChartResponse(BaseModel): chart_id: str outcome: dict @app.post("/api/chart/extract", response_model=ChartResponse) def extract_endpoint(req: ChartRequest): try: outcome = extract_chart(req.chart_text) return ChartResponse(chart_id=req.chart_id, outcome=outcome) except Exception as e: raise HTTPException(status_code=500, detail=f"extraction failed: {e}")提示:FastAPI 这一层可以在模型不稳定或提示词需要调整时做兼容,下层模型换了版本,上层接口的输出结构保持不变,减少对接科室的返工量。
5. 病历抽取的五个典型翻车现场与排查路径
5.1 模型反复输出相同字段的错乱内容
现象:多份病历传到同一个请求里,模型把上一份病历的诊断抄到这一份里,或者某一份病历的抽取结果和源文本完全不相关。
原因:并发到达时 vLLM 显存不够,批处理把不同请求的 KV cache 截断,导致上下文错位。还有一种情况是--max-model-len设得太短,超长文本被尾部截断,原有的语义关键信息被切掉,模型找不着北就只能瞎填。
解决:先看 vLLM 的启动日志里有没有显存溢出告警,再把--max-model-len提到 16384 试试;如果已经是 16K 还出错,就在预处理阶段强制按 1200 字切块,保证单请求的长度和训练分布一致。还有个小细节,请求里不要拼多个病历文本,一次只抽一份。
5.2 JSON 输出被 Markdown 代码块包裹导致解析崩溃
现象:日志里json.loads直接抛错,打印返回内容发现模型在 JSON 前后加了```json标记。
原因:微调后的 DeepSeek 在部分系统提示词下会模仿训练数据中的代码回复风格,在 JSON 外套一层 Markdown 代码块。response_format虽然约束了解码,但并不能 100% 保证不加壳。
解决:解析前先做剥离——strip 掉首尾的三反引号,再做json.loads。更稳的方案是解析失败后自动重发一次请求,并附加一句“直接输出 JSON,不要用 Markdown 代码块包裹”。
def safe_json_parse(raw: str, retry_func=None, max_retries: int = 2) -> dict: text = raw.strip() for _ in range(max_retries + 1): try: if text.startswith("```"): text = text.strip("`") text = text.removeprefix("json").strip() return json.loads(text) except json.JSONDecodeError: if retry_func is not None: text = retry_func() # 重发一次请求,带“不要代码块”的补充说明 else: raise raise ValueError("JSON parse failed after retries")逻辑说明:重试函数在重构和原文本中重新拼一轮 prompt,再把返回内容传进来。实测重试一次的成功率大于 90%,两次基本全过。
5.3 同样的病历,今天抽和明天抽结果不一致
现象:用同一份文本、同样的提示词,昨天抽取结果是“高血压2级”,今天变成“高血压病2级”,字段内容多了或少了一两个字。
原因:这类不稳定主要来自采样参数。如果设置的temperature偏高,模型每次输出的词概率分布略有差异,自然会产生字面上的漂移。另一个隐蔽原因是模型服务被重启过,加载了不同的量化权重或执行了不同的dtype转换。
解决:生产环境把temperature固定到 0.1,并在 vLLM 启动参数里锁定--dtype和--quantization;另外把主要字段的归一化规则写进后处理——比如“高血压病”和“高血压”做一次术语映射,这样即使模型输出和教科书写法有出入,统计口径也能统一。
5.4 PDF 扫描件识别出来全是乱码,抽什么都白搭
现象:从病案系统导出的早期病历是扫描版 PDF,OCR 后输出的文本有大量无意义字符,实体抽取的准确率掉到不堪入目。
原因:这是数据链路的问题,不是模型问题。扫描件里医生的手写体本身 OCR 难度就极高,再叠加表格线、印章、模糊背景,通用 OCR 引擎基本无能为力。
解决:病案室能提供结构化数据的优先用结构化数据;只能拿 PDF 的,优先走电子病历系统的文本导出接口,别和扫描件死磕。实在躲不开扫描件,把 OCR 步骤独立出来人工抽检,评估清楚这个批次的文本质量再决定是否送进抽取流水线。把烂数据送给模型,模型不会魔法般变好。
5.5 并发请求一多,服务就报连接超时或 tool call 错误
现象:当科室并发提交大量病历,vLLM 服务端偶尔出现超时,客户端看到messages tool calls need immediate results或者显存溢出错误,部分请求直接被丢弃。
原因:vLLM 的连续批处理在高并发下有队列积压,每个请求等待时间变长;--max-num-seqs控制并行规模,设太多会让每次推理的 batch 膨胀,单次 decode 变慢;tool calling 场景还要求服务端及时响应中间工具结果,内网千兆环境下大文件传输会阻塞。
解决:给客户端加超时重试机制,服务端把--max-num-seqs从默认值调小一半,比如设为 4 或 8,单卡压力会更平稳。tool call 报错如果是本地开发工具(如 claude code 或 vscode 插件)连本地服务产生的,多半是模型没配工具调用能力,不要硬撑,直接用普通 chat 接口即可。
注意:内网环境下建议用进程守护脚本盯住 vLLM 服务,一旦崩溃自动重启,否则半夜跑批量病历抽取时挂了没人发现,早晨白跑一宿。
6. 验证病历抽取质量:用“比较分”给自己的私有化系统打分
很多团队部署完模型就问“效果怎么样”,这是个没法用一条命令回答的问题。我自己的做法是通过双路径比较逼近一个可信的质量数字。
取最近 30 天出院的 100 份病历,由病案室人工抽取关键字段作为金标准。系统跑一遍自动抽取,按字段逐项比对,三个指标的算术平均就是抽取准确率。这套比较法虽然土,但科室认。比“大模型效果好”这种话可信得多。
更细的验证技巧是“首尾抽审法”:把自动抽取结果按住院号排序,每隔 10 份抽一份人工复核,重点看主诊断和出院带药两个核心字段。不要随机抽,排序抽能覆盖到按时间分布的病历类型变化,比如不同科室的书写惯用词差异。
如果你所在的团队想做更自动化的质控流水线,可以考虑把抽取和复核串成多步骤编排。社区里有叫 deepseek harness 的工具,适合做这种多智能体流水线编排——拆成“抽取”和“复核”两个独立的 agent,第一个负责抽字段,第二个负责对照原文找可疑结果并打回重抽。我自己在病历质量场景里试过类似思路,复核 agent 不需要太强模型,小模型就够用,关键是把“复核什么”定义清楚。
我自己习惯的动作是:每月跑一次全量病历抽取,并把结果和上月的抽审样本对齐,观察主诊断字段的漂移率。如果这个月比上个月明显下降,多数不是因为模型变好了,而是病历文本的书写风格变了。这时候就该通知病案室去查一下最近电子病历模板是否有改动。
另外开发调试阶段,把 vscode 或 codex 这类编码工具接上本地 DeepSeek 服务来写抽取规则是可行的,但千万别让它们直接处理病历文件内容,开发工具的数据流向不受你控制,涉及真实病历一律走上面封装好的内网接口。
整套系统走到这个阶段,真正的价值已经不在“模型跑得有多好”,而在于“有没有把病历变成可验证、可追溯的结构化数据”。这套验证方法是我从多次被临床科室追问“准不准”的血泪经验里总结出来的,算是给未来接手的人留了一本账。希望帮到你。
本文还有配套的精品资源,点击获取