针对门诊病历自动化处理场景,封装 3 个串联 Skill

  1. PDF 病历解析 Skill:提取 PDF 格式门诊记录的文本内容
  2. 医疗术语结构化 Skill:将非结构化文本转为标准化字段(症状、诊断、用药、医嘱)
  3. 诊断编码映射 Skill:将诊断结果映射为 ICD-10 国际医疗编码
    通过 LangChain Agent 实现全自动流程调度,无需人工干预。

前置依赖安装

pip install langchain langchain-openai python-dotenv pypdf2 pydantic

完整代码实现

1. 环境配置与基础导入

import os
import re
from dotenv import load_dotenv
from pypdf2 import PdfReader
from pydantic import BaseModel, Field
from typing import Type, List, Optional

# LangChain 核心组件
from langchain_openai import ChatOpenAI
from langchain.tools import BaseTool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import create_openai_tools_agent, AgentExecutor

# 加载环境变量(配置 OpenAI API Key)
load_dotenv()
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")

2. 封装 3 个医疗专属 Skill

Skill 1:PDF 病历解析 Skill(提取文本)
# 定义输入输出 Schema(符合 Skills 范式的标准化约束)
class PDFMedicalRecordInput(BaseModel):
    pdf_path: str = Field(description="PDF 格式门诊病历的本地文件路径,例如:./patient_record.pdf")

class PDFMedicalRecordOutput(BaseModel):
    text_content: str = Field(description="从 PDF 中提取的完整门诊病历文本")
    page_count: int = Field(description="PDF 文件的总页数")

class PDFMedicalRecordParserSkill(BaseTool):
    name = "pdf_medical_record_parser_skill"
    description = "专门用于解析 PDF 格式的医疗门诊病历,提取其中的文本内容,输入为本地 PDF 文件路径"
    args_schema: Type[BaseModel] = PDFMedicalRecordInput
    return_direct: bool = False

    def _run(self, pdf_path: str) -> str:
        """核心执行逻辑:读取 PDF 并提取文本"""
        try:
            reader = PdfReader(pdf_path)
            page_count = len(reader.pages)
            text_content = ""
            for page in reader.pages:
                text_content += page.extract_text() + "\n"
            
            # 按 Schema 格式返回结果
            result = PDFMedicalRecordOutput(
                text_content=text_content.strip(),
                page_count=page_count
            )
            return result.model_dump_json(indent=2)
        except FileNotFoundError:
            return f"错误:未找到 PDF 文件 {pdf_path}"
        except Exception as e:
            return f"PDF 解析失败:{str(e)}"
    
    def _arun(self, pdf_path: str):
        raise NotImplementedError("异步解析暂未支持")
Skill 2:医疗术语结构化 Skill(标准化字段提取)
class MedicalTextStructuringInput(BaseModel):
    raw_text: str = Field(description="从 PDF 提取的原始门诊病历文本,包含症状、诊断、用药、医嘱等信息")
    output_format: str = Field(default="json", description="输出格式,支持 json 或 markdown")

class MedicalStructuredData(BaseModel):
    patient_symptom: str = Field(description="患者症状描述,如发热、咳嗽、乏力")
    diagnosis_result: str = Field(description="医生的诊断结论,如急性上呼吸道感染")
    medication_list: List[str] = Field(description="用药清单,如阿莫西林胶囊、布洛芬混悬液")
    doctor_advice: str = Field(description="医嘱内容,如多喝水、休息 3 天")

class MedicalTextStructuringSkill(BaseTool):
    name = "medical_text_structuring_skill"
    description = "将非结构化的门诊病历文本转换为标准化字段,包含症状、诊断、用药、医嘱,支持指定输出格式"
    args_schema: Type[BaseModel] = MedicalTextStructuringInput
    return_direct: bool = False

    def _run(self, raw_text: str, output_format: str = "json") -> str:
        """核心执行逻辑:基于正则和规则提取标准化字段"""
        # 模拟医疗文本提取逻辑(真实场景可替换为医疗 NLP 模型)
        symptom_pattern = r"症状[::](.*?)(?=诊断|$)"
        diagnosis_pattern = r"诊断[::](.*?)(?=用药|医嘱|$)"
        medication_pattern = r"用药[::](.*?)(?=医嘱|$)"
        advice_pattern = r"医嘱[::](.*)"

        symptom = re.search(symptom_pattern, raw_text, re.DOTALL)
        diagnosis = re.search(diagnosis_pattern, raw_text, re.DOTALL)
        medication = re.search(medication_pattern, raw_text, re.DOTALL)
        advice = re.search(advice_pattern, raw_text, re.DOTALL)

        structured_data = MedicalStructuredData(
            patient_symptom=symptom.group(1).strip() if symptom else "未提取到症状",
            diagnosis_result=diagnosis.group(1).strip() if diagnosis else "未提取到诊断",
            medication_list=medication.group(1).strip().split("、") if medication else [],
            doctor_advice=advice.group(1).strip() if advice else "未提取到医嘱"
        )

        # 按指定格式输出
        if output_format == "json":
            return structured_data.model_dump_json(indent=2)
        else:
            return f"""# 结构化门诊病历
- 症状:{structured_data.patient_symptom}
- 诊断:{structured_data.diagnosis_result}
- 用药:{', '.join(structured_data.medication_list)}
- 医嘱:{structured_data.doctor_advice}"""
    
    def _arun(self, raw_text: str, output_format: str = "json"):
        raise NotImplementedError("异步结构化暂未支持")
Skill 3:诊断编码映射 Skill(ICD-10 匹配)
class MedicalCodeMappingInput(BaseModel):
    diagnosis: str = Field(description="标准化的诊断结论,如急性支气管炎、高血压")

class MedicalCodeOutput(BaseModel):
    diagnosis: str = Field(description="输入的诊断结论")
    icd10_code: str = Field(description="对应的 ICD-10 编码")
    code_description: str = Field(description="ICD-10 编码的官方描述")

# 模拟 ICD-10 编码库(真实场景可对接医保编码数据库)
ICD10_DATABASE = {
    "急性上呼吸道感染": ("J06.900", "急性上呼吸道感染,未特指"),
    "急性支气管炎": ("J20.900", "急性支气管炎,未特指"),
    "高血压": ("I10.x00", "原发性高血压"),
    "糖尿病": ("E11.900", "2型糖尿病,未特指并发症")
}

class MedicalCodeMappingSkill(BaseTool):
    name = "medical_code_mapping_skill"
    description = "将医疗诊断结论映射为 ICD-10 国际标准编码,输入为标准化诊断结果"
    args_schema: Type[BaseModel] = MedicalCodeMappingInput
    return_direct: bool = False

    def _run(self, diagnosis: str) -> str:
        """核心执行逻辑:匹配 ICD-10 编码"""
        diagnosis = diagnosis.strip()
        if diagnosis in ICD10_DATABASE:
            code, desc = ICD10_DATABASE[diagnosis]
            result = MedicalCodeOutput(
                diagnosis=diagnosis,
                icd10_code=code,
                code_description=desc
            )
            return result.model_dump_json(indent=2)
        else:
            return f"未找到【{diagnosis}】对应的 ICD-10 编码,请检查诊断名称是否准确"
    
    def _arun(self, diagnosis: str):
        raise NotImplementedError("异步编码映射暂未支持")

3. 组合 Skill 并创建 Agent 调度器

def create_medical_agent():
    # 1. 初始化大模型(支持工具调用)
    llm = ChatOpenAI(
        model="gpt-3.5-turbo",
        temperature=0,  # 固定输出,适合工具调用场景
        max_tokens=1024
    )

    # 2. 组装医疗 Skill 工具箱
    medical_skills = [
        PDFMedicalRecordParserSkill(),
        MedicalTextStructuringSkill(),
        MedicalCodeMappingSkill()
    ]

    # 3. 定义 Agent Prompt:明确 Skill 调用逻辑和顺序
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是医疗信息化智能助手,负责处理门诊病历的自动化解析任务,可调用以下 3 个技能,需按顺序执行:
        1. 第一步:调用 PDF 病历解析技能,提取 PDF 文本内容;
        2. 第二步:调用医疗术语结构化技能,将提取的文本转为标准化字段;
        3. 第三步:调用诊断编码映射技能,将诊断结果转为 ICD-10 编码。
        请严格按照步骤执行,每一步的输出作为下一步的输入,最终返回完整的处理报告。"""),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad")  # 存储 Agent 思考过程
    ])

    # 4. 创建 Agent 并绑定 Skill
    agent = create_openai_tools_agent(llm, medical_skills, prompt)
    agent_executor = AgentExecutor(
        agent=agent,
        tools=medical_skills,
        verbose=True,  # 开启详细日志,查看 Skill 调用过程
        handle_parsing_errors="请检查输入格式,确保提供正确的 PDF 文件路径"
    )

    return agent_executor

# 初始化医疗 Agent
medical_agent = create_medical_agent()

4. 测试多 Skill 组合调用

if __name__ == "__main__":
    # 测试输入:指定 PDF 病历路径
    test_input = "帮我处理这份门诊病历 PDF:./patient_record.pdf,要求完成结构化和 ICD-10 编码映射"
    
    # 执行 Agent 调度
    result = medical_agent.invoke({"input": test_input})
    
    # 输出最终结果
    print("\n===== 最终处理报告 =====")
    print(result["output"])

关键执行逻辑说明

  1. Skill 调用顺序:Agent 严格按照 Prompt 要求,先调用 PDFMedicalRecordParserSkill 提取文本 → 再用 MedicalTextStructuringSkill 结构化 → 最后用 MedicalCodeMappingSkill 生成编码,实现流水线式处理
  2. 数据流转:前一个 Skill 的输出自动作为后一个 Skill 的输入,无需手动传递参数,完全符合通用 Skills 的组合调用范式。
  3. 日志可视化:开启 verbose=True 后,可在控制台看到每个 Skill 的调用过程、参数传递和结果返回,便于调试。

医疗场景适配优化建议

  1. 替换真实医疗 NLP 模型:将示例中的正则提取逻辑,替换为医疗领域预训练模型(如 BERT-医疗版),提升结构化准确率。
  2. 对接医保数据库:将 ICD10_DATABASE 替换为医院/医保局的官方编码接口,实现实时编码查询。
  3. 添加权限控制:在 Skill 中加入用户鉴权逻辑,仅允许授权人员访问病历数据,符合医疗数据隐私规范。
Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐