电子病历系统里常见的问题是:主诉、现病史、既往史、处置记录等文本分散在多个输入框或历史文档中,医生和病案人员需要反复复制、整理、校对。本文只讨论技术架构和工程流程示例,不提供诊断、治疗、分诊或用药建议;所有字段规则、风险提示和审核策略都应由医疗专业人员及机构规范确认。
问题背景:Agent 在电子病历里到底做哪几件事
在电子病历场景中,Agent 不适合被设计成“自动替医生下结论”的系统,更合理的定位是文书处理助手。它接收多段自由文本,完成病史归并、字段候选抽取、摘要草稿生成,并把不确定内容交给人工审核。
一个可落地的目标可以拆成四个任务:
- 从门诊记录、入院记录、随访记录中解析出文本片段
- 将主诉、现病史、既往史、过敏史、家族史等字段生成候选值
- 根据机构模板生成一版病历摘要草稿
- 在审核工作台展示来源证据、置信度和修改记录
这里的关键边界是:系统只生成“候选内容”,最终写入电子病历前必须经过授权人员确认。
技术目标与约束
本文示例技术栈为 Python、FastAPI、PostgreSQL、vector store 和 LLM API。适合做成电子病历系统旁路服务,而不是直接改造核心 HIS/EMR 数据库。
核心约束建议如下:
- 不直接覆盖原始病历,所有回填都进入 pending 状态
- 每个结构化字段必须保留来源片段和生成时间
- LLM 输出必须经过 JSON Schema 校验
- 审核人修改后的版本要进入审计日志
- 脱敏、权限、日志留存策略按机构要求实现
总体架构:解析、抽取、回填、审核四段式
一个简化架构如下:
Agent 编排器不需要一开始就做得很复杂。第一版可以使用固定流程:清洗文本、检索相关片段、调用字段抽取、调用摘要生成、落库待审。后续再加入任务重试、人工反馈学习、模板版本管理。
数据建模:不要只存 LLM 的最终答案
很多病历结构化项目失败在“只存结果不存依据”。建议将字段候选值、来源片段、模型版本、提示词版本和审核状态分开存储。
示例表结构可以这样设计:
CREATE TABLE emr_extract_task (
id UUID PRIMARY KEY,
patient_visit_id TEXT NOT NULL,
status TEXT NOT NULL,
model_name TEXT,
prompt_version TEXT,
created_at TIMESTAMP DEFAULT now()
);
CREATE TABLE emr_pending_field (
id UUID PRIMARY KEY,
task_id UUID REFERENCES emr_extract_task(id),
field_name TEXT NOT NULL,
field_value TEXT NOT NULL,
source_text TEXT NOT NULL,
confidence NUMERIC,
review_status TEXT DEFAULT 'pending',
reviewer_note TEXT,
updated_at TIMESTAMP DEFAULT now()
);
confidence 只是工程侧的排序和提示字段,不应被解释为医疗判断依据。真实系统中,哪些字段允许自动候选、哪些字段必须人工录入,应按机构规则配置。
核心实现:FastAPI 封装一个结构化抽取接口
下面代码演示一个最小可运行的接口骨架。为了聚焦流程,LLM 调用部分使用 call_llm_json 占位函数,真实项目中需要替换为内部模型网关或合规的 LLM API,并加入超时、重试和审计日志。
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import List, Optional
import uuid
import json
app = FastAPI(title="EMR Agent Demo")
class NoteInput(BaseModel):
patient_visit_id: str
notes: List[str] = Field(..., description="多段病历自由文本")
class ExtractedField(BaseModel):
field_name: str
field_value: str
source_text: str
confidence: Optional[float] = None
class ExtractResult(BaseModel):
task_id: str
fields: List[ExtractedField]
summary: str
review_required: bool = True
def clean_text(text: str) -> str:
return " ".join(text.replace("\n", " ").split())
def build_prompt(chunks: List[str]) -> str:
joined = "\n".join([f"[片段{i+1}] {c}" for i, c in enumerate(chunks)])
return f"""
你是病历文书结构化助手,只做信息整理,不做诊断或治疗建议。
请从以下文本中抽取候选字段:主诉、现病史、既往史、过敏史、个人史。
要求返回 JSON,字段包含 field_name、field_value、source_text、confidence。
如果文本没有明确依据,不要编造。
{joined}
"""
def call_llm_json(prompt: str) -> dict:
# 真实项目中替换为 LLM API 调用,并记录 model、prompt_version、request_id
demo = {
"fields": [
{
"field_name": "主诉",
"field_value": "示例:反复咳嗽 3 天",
"source_text": "患者自述反复咳嗽 3 天",
"confidence": 0.86
}
],
"summary": "示例摘要:患者因反复咳嗽 3 天就诊,既往信息需结合原始记录人工确认。"
}
return demo
@app.post("/emr/extract", response_model=ExtractResult)
def extract_emr(payload: NoteInput):
chunks = [clean_text(n) for n in payload.notes if n.strip()]
prompt = build_prompt(chunks)
raw = call_llm_json(prompt)
fields = [ExtractedField(**item) for item in raw.get("fields", [])]
task_id = str(uuid.uuid4())
# 这里应写入 PostgreSQL:task、pending fields、summary、source mapping
return ExtractResult(
task_id=task_id,
fields=fields,
summary=raw.get("summary", ""),
review_required=True
)
接口返回后,前端不要直接把 field_value 写入正式病历,而应进入审核工作台。审核人需要看到字段候选值、来源文本和“采纳/修改/驳回”操作。
Agent 编排:把工具边界设计清楚
在这个场景中,Agent 可以调用的工具建议保持少而清晰:
retrieve_visit_context:按就诊号检索相关文本片段extract_emr_fields:输出结构化字段候选generate_record_summary:按模板生成摘要草稿validate_schema:校验 JSON 和字段枚举submit_for_review:写入待审核表
如果引入 vector store,优先用于“找依据”,而不是让模型凭上下文自由发挥。比如生成“既往史”时,只检索包含既往、手术、过敏、长期用药等关键词或语义相近片段,再把片段交给 LLM 抽取。
审核工作台:人工校对边界怎么保留
审核工作台至少要展示三类信息:候选字段、来源证据、变更记录。对于没有来源片段的内容,应默认不允许一键采纳。
推荐交互流程:
- 系统生成候选字段,状态为 pending
- 审核人逐项确认、修改或驳回
- 修改后的内容保存 reviewer_note 和 diff
- 通过审核后再调用 EMR 回填接口
- 回填失败时保留任务状态,允许重试
示例规则可以写成配置,例如“低于某个可配置置信度时必须二次审核”。这里的阈值不能写死为行业标准,应由项目团队与医疗专业人员共同确认。
踩坑与调试建议
第一,提示词里必须明确“无依据不生成”。电子病历自由文本常有省略和口语化描述,如果模型补全过度,会给审核带来额外负担。
第二,字段枚举要稳定。比如“过敏史”不要有时叫 allergy,有时叫 allergy_history,否则后续回填和统计会变复杂。
第三,摘要生成要绑定模板版本。不同科室、不同机构的文书模板差异较大,建议把模板 ID、版本号和生成结果一起落库。
第四,日志要区分业务日志和模型日志。业务日志记录任务状态,模型日志记录请求 ID、模型版本、耗时、错误类型,避免把敏感原文写入不受控日志系统。
性能与扩展性分析
单次病历整理的耗时通常由 LLM 调用和上下文长度决定。工程上可以先做三件事:文本分段去重、只检索相关片段、字段抽取和摘要生成分开调用。这样便于缓存字段抽取结果,也方便失败重试。
PostgreSQL 适合保存任务、字段和审核记录;vector store 适合保存片段索引。对于高并发场景,可以把 /emr/extract 改成异步任务,前端轮询任务状态或通过 WebSocket 接收更新。
成本方面,不建议把整份历史病历无差别塞进上下文。更稳妥的方式是先用规则和向量检索筛选片段,再让 LLM 在较小上下文中完成抽取。
总结与下一步
Agent 落地电子病历整理,重点不是让模型替代专业判断,而是把自由文本处理、结构化候选、摘要草稿和人工审核串成可追踪流程。本文示例覆盖了解析、抽取、回填前审核和数据建模的最小闭环。
下一步可以继续完善三块能力:接入真实 PostgreSQL 持久化、增加前端审核工作台、为不同病历模板配置字段 Schema。上线前还需要完成权限控制、脱敏策略、审计留痕和机构级规则确认。
本文文献检索、文献挖掘以及文献翻译采用的是【超能文献| AI文献检索|AI文档翻译】。
1590

被折叠的 条评论
为什么被折叠?



