高级 11 分钟健康

档案摘要 医疗领域

直接回答

使用本地 LLM 总结病历时,先通过代码提取事实(从 FHIR 数据包或 CDA R2 文档中提取),为这些事实编号,再让模型仅依据这些事实撰写摘要,并在每一行注明所用事实的引用编号。通过程序检查每个引用编号和每个数值是否都存在于源数据中,最后交由医生审核。本地运行可以保护隐私,却不能防止幻觉:2025 年的一项研究发现,1.47% 的句子存在幻觉。

健康档案中的信息准确,但分散在各处;语言模型生成的摘要虽然流畅,却可能包含严重错误。本指南介绍了一条在本地运行的处理流水线:代码提取事实,模型据此撰写摘要并引用来源,随后先通过程序校验,再由医生确认结果。指南也提醒您核查相关框架:医疗保密义务、健康数据托管,以及工具的用途。

作者: Mohamed Meguedmi·更新于 2026-09-30·已在 Windows、macOS 和 Linux 上测试

#问题:病历内容准确,却难以读懂

接手患者的医护人员需要查阅诊疗报告、实验室检查结果、处方和信函,这些资料通常由多个软件生成。本地模型可以为接手病历准备一页摘要。在医疗场景中,可靠的方法始终相同:通过确定性代码提取结构化数据,让模型仅根据这些事实撰写摘要,要求每一行都引用原始事实的标识符,通过程序检查摘要,最后交由医生审核确认。

一个词汇点可避免常见混淆。在法国,通过共享医疗档案(如共享医疗记录)交换和共享的医疗文件,遵循数字健康机构(Agence du numérique en santé)制定的健康信息系统的互操作性框架(CI-SIS),其具体规范描述了CDA R2格式的文档,这是一种基于XML的HL7标准。HL7版本2是另一个标准,采用以竖线分隔的文本消息形式,用于传输。最后,FHIR以JSON格式出现,是最新且最易处理的交换标准,主要应用于应用程序之间的交互。因此,您的首要任务是明确您的软件提供了什么功能。

i
访问共享病历
脚本不能随意连接 DMP:必须通过供医疗专业人员使用的软件或经授权的中介平台访问。CI-SIS 的 CDA-R2 文档传输规范部分明确说明,业务软件可以将与 DMP 和 MSSanté 消息系统的交互委托给中介平台。因此,本指南以已从您的软件中导出的文档为起点。

#框架:医疗保密、数据托管与责任

本地 AI 套件

只需 1 小时,即可在您的电脑上拥有专属的免费 ChatGPT — LM Studio、Ollama、Open WebUI、您的文档,无需云端。

  • 在线空间,终身可用
  • PDF + 文件
  • 终身更新

在编写任何代码之前,需要考虑三个问题。第一个是医疗保密和健康数据保护,这要求在受控的基础设施上处理这些数据:本地部署是这一要求的具体落实方式,但还必须加密磁盘、限制访问并记录日志。第二个是健康数据托管。《公共卫生法典》第L. 1111-8条规定,任何以数字介质托管个人健康数据的主体,在代表数据处理负责人或患者进行托管时,都必须获得相应认证。

这一义务的适用方式并不总是如人们所想。据一家专业保险机构援引的法国数字健康署(Agence du numérique en santé)所述,当医疗机构内部所有系统仅存储自身患者的资料时,托管认证并非一项法规强制要求,除非该机构为第三方提供托管服务。换句话说,一个在自家环境中为本机构患者使用工具的诊所,并不等同于一个向其他诊所提供此类服务的软件开发商。请务必咨询您的法律顾问或数据保护负责人以确认具体情形。

第三个问题是预期用途。生成用于辅助医疗决策的信息的软件,可能属于医疗器械法规的适用范围,具体取决于其声明的用途。用于重新接手病历的摘要,如果被定位为阅读辅助并经医生核验,其作用范围就不同于建议治疗方案的工具。本指南限定在前一种定位之内。

#本地运行堆栈

三个组件就足够了。首先,用 Python 读取文档:json 模块用于处理 FHIR 批次,lxml 用于处理 CDA 文档,hl7 库用于处理可能遇到的第 2 版消息,该库自称为 HL7 v2.x 消息解析器。其次,用 Ollama 提供模型服务:Qwen 3.5 的 90 亿参数版本大小为 6.6 GB,宣称支持 256 000 个 token 的上下文;Mistral Small 24B 大小为 14 GB,宣称支持 32 000 个 token 的上下文。最后,使用 Ollama 的 JSON Schema,强制模型以可检查的格式回答。

这些模型都未经过其发布方的临床用途验证。它们处理医学法语的能力,需要使用您自己的病历,按照下文的评估流程来衡量。一张 8 GB 显存的显卡足以运行第一个模型;第二个模型需要更多显存,尤其是在上下文较长时。

#读取文档:一批 FHIR 数据和一份 CDA 文档

对于 FHIR 资源包,也就是容纳一组资源的容器,最简单的方法是直接读取原始 JSON,并按类型查找资源。这样可以避免依赖一个所支持的 FHIR 版本可能不同的库。每个保留的事实都会获得一个简短标识符(F1、F2……)和一段可读文本:这样之后就能追溯摘要中每句话的来源。

从 FHIR(R4)批次数据中提取事实
import json

def libelle(cc):
    if not cc:
        return None
    if cc.get('text'):
        return cc['text']
    c = (cc.get('coding') or [{}])[0]
    return c.get('display') or c.get('code')

def faits_fhir(chemin):
    lot = json.load(open(chemin, encoding='utf-8'))
    faits = []
    for e in lot.get('entry', []):
        r = e.get('resource', {})
        t = r.get('resourceType')
        if t == 'Condition':
            texte = f"Diagnostic : {libelle(r.get('code'))} (début {r.get('onsetDateTime', 'date inconnue')})"
        elif t == 'MedicationStatement':
            dose = (r.get('dosage') or [{}])[0].get('text', 'posologie non précisée')
            texte = f"Traitement : {libelle(r.get('medicationCodeableConcept'))}, {dose}"
        elif t == 'Observation':
            v = r.get('valueQuantity', {})
            texte = f"Résultat : {libelle(r.get('code'))} = {v.get('value')} {v.get('unit', '')} ({r.get('effectiveDateTime', 'date inconnue')})"
        elif t == 'AllergyIntolerance':
            texte = f"Allergie : {libelle(r.get('code'))}"
        elif t == 'Procedure':
            texte = f"Acte : {libelle(r.get('code'))} ({r.get('performedDateTime', 'date inconnue')})"
        else:
            continue
        faits.append({'ref': f'F{len(faits) + 1}', 'type': t, 'texte': texte})
    return faits

对于CDA文档,有用的文本位于结构化正文的各个章节中:每个章节都有标题、编码和一块叙述性文本。下面的代码会提取这些章节及其标题,不包含带有患者身份信息的文档头部。CDA使用的命名空间是HL7第3版的命名空间。

提取 CDA R2 文档中的各个章节
from lxml import etree

NS = {'h': 'urn:hl7-org:v3'}

def sections_cda(chemin):
    racine = etree.parse(chemin).getroot()
    for s in racine.iterfind('.//h:structuredBody//h:section', NS):
        texte = s.find('h:text', NS)
        if texte is None:
            continue
        code = s.find('h:code', NS)
        yield {
            'titre': (s.findtext('h:title', default='', namespaces=NS) or '').strip(),
            'code': code.get('code') if code is not None else None,
            'texte': ' '.join(''.join(texte.itertext()).split()),
        }
→
总结前先提取信息
绝不要将原始文档直接交给模型。XML 文档很大一部分由标签和标识符组成,会占用上下文并淹没有用信息。处理流程首先将档案转换为一份带编号的事实列表,然后对这份列表进行摘要。

#应保留哪些事实,以及需要多谨慎

用于知识总结的FHIR资源
资源其中可读取的信息常见误区
Condition诊断、开始日期、状态已解决或有误的诊断仍可能留在历史记录中
MedicationStatement药品、给药剂量与用法、用药时间段已停止的治疗可能并未被标记为已停止
Observation检验或测量结果、单位、日期比较数值时忽略其单位和参考值
AllergyIntolerance物质、反应、严重程度未录入过敏信息,并不代表没有过敏
Procedure已实施的医疗操作及其日期日期不精确或缺失
DocumentReference附件通常为报告内容经过编码,或以链接形式提供,需要另行获取

最后一列比其他列更重要。模型会读取您提供的事实,却不知道缺少了什么:如果一项已经停止的治疗没有被明确标注为已停止,它就会在摘要中被呈现为仍在进行。因此,代码必须包含状态和日期,提示词则必须要求指出没有日期或状态不确定的事实。缺少信息并不等于掌握了某种信息:摘要必须明确说明这一点。

#撰写包含来源引用的摘要,然后进行审核

提示词提供带编号的事实,并要求摘要的每一行都以所用事实的引用标记结尾,例如 [F3][F7]。提示词中不包含患者姓名:年龄和性别就足够,身份信息保留在业务软件中。回答的形式可以灵活安排,但需分节组织,便于在一页内阅读。

基于编号事实的综合说明
import requests

SYSTEM = open('system_med.txt', encoding='utf-8').read()

def synthese(faits, age, sexe, modele='qwen3.5:9b'):
    liste = '\n'.join(f"[{f['ref']}] {f['texte']}" for f in faits)
    r = requests.post('http://localhost:11434/api/chat', json={
        'model': modele, 'stream': False,
        'messages': [{'role': 'system', 'content': SYSTEM},
                     {'role': 'user', 'content': f'Patient : {age} ans, {sexe}.\n\nFAITS :\n{liste}'}],
        'options': {'temperature': 0.1, 'num_ctx': 16384}})
    return r.json()['message']['content']

接下来用代码进行检查。两项检查可以发现大多数严重错误:每个引用的来源都必须存在于事实列表中,摘要中的每个数值也必须出现在事实中。凭空出现的数字通常意味着内容是编造的,或抄录时出了错,尤其是生物学指标或用药剂量。

对摘要进行程序化检查
import re

def verifier(synthese, faits):
    refs_ok = {f['ref'] for f in faits}
    citees = set(re.findall(r'\[(F\d+)\]', synthese))
    def nombres(t):
        t = re.sub(r'\[F\d+\]', ' ', t).replace(',', '.')
        return set(re.findall(r'\d+(?:\.\d+)?', t))
    source = nombres(' '.join(f['texte'] for f in faits))
    return {
        'references_inconnues': sorted(citees - refs_ok),
        'nombres_absents_de_la_source': sorted(nombres(synthese) - source),
        'lignes_sans_reference': [l for l in synthese.split('\n') if l.strip().startswith('-') and not re.search(r'\[F\d+\]', l)],
    }

#系统提示

system_med.txt
Tu prépares une synthèse de reprise de dossier pour un médecin.
Tu ne poses AUCUN diagnostic, tu ne proposes AUCUN traitement ni modification de traitement.
Tu utilises UNIQUEMENT les faits fournis. Tu n'ajoutes aucune donnée.
Chaque ligne de la synthèse se termine par les références des faits utilisés, par exemple [F3][F7].
Si un fait n'a pas de date, ou si un statut est incertain, tu l'écris explicitement.
Si des faits semblent contradictoires, tu les signales sans trancher.
Si une information attendue est absente, écris : non renseigné.

SECTIONS : Pathologies, Traitements en cours, Allergies, Derniers résultats,
Actes et antécédents, Points à vérifier.

“需核实事项”部分在实践中最有用。它将含糊之处(没有结束日期的治疗、没有单位的结果、两个相互矛盾的数值)转化为向医生提出的问题,而不是用一句流畅的话把这些问题掩盖过去。

#使用前务必评估可靠性

信任不能靠一句话就建立起来。2025 年发表于 npj Digital Medicine 的一项研究测量了语言模型生成临床记录时的错误:在由临床专业人员标注的 12,999 个句子中,研究发现 1.47% 的句子存在幻觉,3.45% 存在遗漏,其中 44% 的幻觉被认为是重大幻觉,也就是说,如果不加以纠正,可能影响诊断或诊疗照护。这些数字来自另一项任务,涉及其他模型,并不是您自己的结果。它们表明,即使单句错误率很低,在整份文档的尺度上仍然令人担忧。

假设每行错误率为 1.5%,且各行错误相互独立,一份三十行的摘要在约三分之一的情况下至少包含一个错误。这是用于教学的数量级估算,而非预测。因此采用以下流程,取代“50 份病历零错误”的标准:该标准证明不了多少,因为在 95% 置信水平下,50 份病历中观察到零错误,与约 6% 的实际错误率仍然相容(三法则,3 除以 50)。

  1. 01
    整理一组匿名化病历样本
    选取多样化的病历:患有多种疾病的患者、接受多项治疗的患者,以及包含较早检查结果的病历。在任何诊疗范围之外的使用前,去除身份信息和不必要的数据。
  2. 02
    请由医生进行标注
    摘要中的每句话都被归类为:正确、不够精确、遗漏、错误。请单独区分出那些可能改变临床决策的错误。
  3. 03
    按类别和严重程度统计
    每份病历中的重大错误数量比平均错误率更重要。开始前,请与负责的医生共同设定阈值。
  4. 04
    也要评估遗漏情况
    一份遗漏过敏或治疗信息的摘要,比一句措辞不当的话更危险。
  5. 05
    每次变更都重新运行
    模型、提示词或导出格式:每次修改都需要重新评估。
!
禁止辅助开具处方
切勿使用此工具提出治疗方案、用药剂量或调整建议。摘要仅用于辅助阅读,审核确认该摘要的医生仍对其负责。

#生产部署:日志、访问权限和边界范围

记录每次生成过程
请将日期、模型版本、提供的事实列表以及生成的摘要保存在受保护的空间中:这正是能够还原医生所读内容的关键。
访问控制
推理服务器不得从外部访问;磁盘已加密;每个账户均归属于特定个人。
显示来源
界面显示摘要,并为其中每一行列出对应的原始事实。医生如果能一键核查,就真的会去核查。
限定使用范围
在任何扩展之前,请先在诊所或机构内部使用,并将用户数量控制在较小范围。向其他机构提供服务会改变您在数据托管方面的身份。
安排人工审核确认,并明确标示审核结果
摘要须经临床执业人员签署或确认后才能录入病历,否则不得录入。
FAQ
能否用本地 LLM 总结病历?+
是的,技术上可行,先通过代码提取结构化数据,再仅基于这些事实生成综合摘要。同时需通过程序校验摘要,由医生审核,并在标注病历上评估系统性能。本地部署保障隐私,不保证可靠性。
DMP是否以FHIR格式提供文档?+
法国互操作性框架的各部分说明了基于 XML 的 CDA R2 格式医疗文档,以及这些文档向 DMP 或 MSSanté 的传输方式。FHIR 主要用于应用程序之间的数据交换。请检查您的软件导出的内容,因为您的处理流程取决于导出格式,而不是数据的原始来源。
诊所内部使用的工具是否需要经 HDS 认证的托管服务商?+
不一定:根据Relyens引用的ANS所述,当机构仅存储自身患者的资料时,认证并非强制要求;若为第三方提供托管服务,则需进行认证。请咨询法律顾问或您的数据保护官以确认具体情形。
处理医学文本时,该选择哪个本地模型?+
两个起点:在配备 8 GB 显存的显卡上运行 Qwen 3.5 9B(6.6 GB,宣称支持 256,000 个 token),或在内存更充裕的情况下运行 Mistral Small 24B(14 GB)。两者均未经过临床验证。选择时,应比较它们对您的病历生成、并由医生标注的摘要,尤其要跟踪遗漏和重大错误。
LLM 会编造医学检验结果吗?+
是的:关于生成临床记录的研究发现了幻觉,其中相当一部分被判定为严重问题。因此,这条处理流程要求每行都有引用依据,检查摘要中的每个数字是否都出现在原始事实中,并向医生指出仍有疑问的内容。人工审核仍然必不可少。
这种工具属于医疗器械吗?+
这取决于其声明的预期用途。经医生确认的病历阅读辅助工具,与提出诊断或治疗建议的工具不同,法规的适用方式也不同。在任何超出内部使用范围的部署之前,请先由法规专家确定您的工具在监管上的分类。
这份指南对您有帮助吗?

有反馈、发现了错误,或想补充说明?请告诉我们,让这份指南对每个人都更有帮助。