Fork me on GitHub

构建智能医疗诊断助手:基于 LangGraph 和 DeepSeek 的实践指南

在医疗领域,诊断过程是一个复杂、多步骤且需要高度专业知识的流程。随着人工智能技术的发展,我们能够构建智能系统来辅助医生进行诊断,提高效率和准确性。本文将详细介绍如何使用 LangGraph 和 DeepSeek 模型构建一个智能医疗诊断助手,从设计理念到代码实现,为您提供一个完整的实践指南。完整项目见:

1. 核心理念:为什么选择 LangGraph?

传统的 AI 应用通常采用线性流程(如 LangChain 中的链式结构),但医疗诊断过程远非线性。医生需要根据患者信息进行初步评估,决定是否需要进一步检查,根据检查结果调整诊断,制定治疗方案,甚至可能需要回溯和重新评估。这种复杂决策过程更适合用图结构来表示,而不是简单的链。

LangGraph 正是为此设计的框架,它允许我们:

  1. 定义复杂状态:维护整个诊断过程中的所有信息
  2. 实现条件路由:根据中间结果决定下一步流程
  3. 支持循环和回溯:模拟医生可能需要重新评估的情况
  4. 提供检查点:保存中间状态,便于恢复和调试

我们的智能医疗诊断助手将模拟真实医生的诊断流程,从初步评估到最终报告生成,每个步骤都是一个图中的节点,节点之间的连接则代表了诊断决策路径。

2. 系统架构:智能医疗诊断工作流

2.1 状态定义:系统的记忆

在 LangGraph 中,状态是贯穿整个工作流的核心数据结构。我们使用 TypedDict 定义了一个 MedicalDiagnosisState,它包含了诊断过程中需要的所有信息:

class MedicalDiagnosisState(TypedDict):
    """医疗诊断工作流的状态结构"""
    messages: Annotated[List, add_messages]  # 对话历史
    patient_info: Dict[str, Any]  # 患者基本信息
    symptoms: List[Dict[str, Any]]  # 症状列表
    vital_signs: Dict[str, Any]  # 生命体征
    lab_results: List[Dict[str, Any]]  # 检查结果
    preliminary_diagnosis: List[str]  # 初步诊断
    recommended_tests: List[str]  # 推荐检查
    treatment_plan: Dict[str, Any]  # 治疗方案
    follow_up_plan: Dict[str, Any]  # 随访计划
    current_stage: str  # 当前阶段
    urgency_level: str  # 紧急程度
    doctor_approval: Dict[str, bool]  # 医生审批状态
    cycle_count: int  # 循环计数
    max_cycles: int  # 最大循环次数
    final_report: str  # 最终报告
    error: Optional[str]  # 错误信息
    session_id: str  # 会话ID

这个状态结构就像是医生的病历本,记录了从患者入院到诊断完成的所有信息,每个节点都可以读取和更新这些信息。

2.2 节点设计:诊断步骤

我们的诊断流程被分解为多个节点,每个节点代表诊断过程中的一个特定步骤:

  1. 初步评估节点 (initial_assessment_node):评估患者紧急程度,识别可能的紧急情况
  2. 检查策略决策节点 (decide_testing_strategy_node):根据紧急程度决定检查策略
  3. 开具检查单节点 (order_tests_node):生成并执行检查
  4. 诊断分析节点 (make_diagnosis_node):基于所有信息进行诊断
  5. 制定治疗方案节点 (create_treatment_plan_node):根据诊断结果制定治疗方案
  6. 医生审批节点 (doctor_approval_node):模拟医生审批过程
  7. 随访计划节点 (follow_up_planning_node):制定患者随访计划
  8. 生成报告节点 (generate_final_report_node):生成最终诊断报告

让我们深入分析几个关键节点的实现:

初步评估节点

def initial_assessment_node(state: MedicalDiagnosisState) -> Dict[str, Any]:
    """步骤1:初步评估和分诊"""
    try:
        patient_info = state["patient_info"]
        symptoms = state["symptoms"]
        vital_signs = state["vital_signs"]

        assessment_prompt = f"""
        你是一个经验丰富的急诊科医生。请根据以下信息进行初步评估:

        患者信息:
        {json.dumps(patient_info, ensure_ascii=False, indent=2)}

        症状:
        {json.dumps(symptoms, ensure_ascii=False, indent=2)}

        生命体征:
        {json.dumps(vital_signs, ensure_ascii=False, indent=2)}

        请完成以下任务:
        1. 评估紧急程度(低/中/高/紧急)
        2. 识别可能的紧急情况
        3. 推荐立即需要的检查
        4. 给出初步印象

        请以JSON格式返回:
        {{
            "urgency_level": "紧急程度",
            "emergency_signs": ["紧急征象1", "紧急征象2"],
            "immediate_actions": ["立即行动1", "立即行动2"],
            "recommended_tests": ["推荐检查1", "推荐检查2"],
            "preliminary_impression": "初步印象"
        }}
        """

        response = llm.invoke([SystemMessage(content=assessment_prompt)])

        # 解析JSON响应
        try:
            response_text = response.content.strip()
            if response_text.startswith("```json"):
                response_text = response_text[7:-3]

            assessment_result = json.loads(response_text)
            urgency_level = assessment_result.get("urgency_level", "medium")
            recommended_tests = assessment_result.get("recommended_tests", [])
        except json.JSONDecodeError:
            urgency_level = "medium"
            recommended_tests = ["血常规", "体温检查"]

        # 翻译紧急程度为中文
        urgency_map = {
            "low": "低",
            "medium": "中",
            "high": "高",
            "emergency": "紧急"
        }
        urgency_cn = urgency_map.get(urgency_level, "中")

        return {
            "urgency_level": urgency_level,
            "recommended_tests": recommended_tests,
            "current_stage": "assessed",
            "messages": [AIMessage(content=f"🏥 初步评估完成,紧急程度:{urgency_cn}")]
        }

    except Exception as e:
        return {
            "error": f"初步评估失败: {str(e)}",
            "current_stage": "assessment_failed",
            "messages": [AIMessage(content=f"❌ 初步评估遇到问题: {str(e)}")]
        }

这个节点首先从状态中提取患者信息、症状和生命体征,然后构建一个详细的提示词发送给 DeepSeek 模型。模型会分析这些信息并返回一个 JSON 格式的评估结果,包括紧急程度和推荐的检查。节点解析这个结果,更新状态,并返回一个包含更新信息的字典。

诊断分析节点

def make_diagnosis_node(state: MedicalDiagnosisState) -> Dict[str, Any]:
    """步骤4:诊断分析"""
    try:
        patient_info = state["patient_info"]
        symptoms = state["symptoms"]
        vital_signs = state["vital_signs"]
        lab_results = state["lab_results"]

        diagnosis_prompt = f"""
        你是一个专业的诊断医生。请根据以下信息进行诊断分析:

        患者信息:
        {json.dumps(patient_info, ensure_ascii=False, indent=2)}

        症状:
        {json.dumps(symptoms, ensure_ascii=False, indent=2)}

        生命体征:
        {json.dumps(vital_signs, ensure_ascii=False, indent=2)}

        检查结果:
        {json.dumps(lab_results, ensure_ascii=False, indent=2)}

        请提供:
        1. 主要诊断(可能多个)
        2. 鉴别诊断
        3. 诊断依据
        4. 严重程度评估(轻度/中度/重度)
        5. 是否需要会诊

        请以JSON格式返回:
        {{
            "main_diagnosis": ["诊断1", "诊断2"],
            "differential_diagnosis": ["鉴别诊断1", "鉴别诊断2"],
            "diagnostic_basis": "诊断依据",
            "severity": "mild/moderate/severe",
            "needs_consultation": true/false,
            "consultation_specialty": "会诊科室"
        }}
        """

        response = llm.invoke([SystemMessage(content=diagnosis_prompt)])

        # 解析JSON响应
        try:
            response_text = response.content.strip()
            if response_text.startswith("```json"):
                response_text = response_text[7:-3]

            diagnosis_result = json.loads(response_text)
            main_diagnosis = diagnosis_result.get("main_diagnosis", [])
            needs_consultation = diagnosis_result.get("needs_consultation", False)
        except json.JSONDecodeError:
            main_diagnosis = ["待查"]
            needs_consultation = False

        return {
            "preliminary_diagnosis": main_diagnosis,
            "current_stage": "diagnosed",
            "needs_consultation": needs_consultation,
            "messages": [AIMessage(content=f"📋 诊断完成:{', '.join(main_diagnosis)}")]
        }

    except Exception as e:
        return {
            "error": f"诊断失败: {str(e)}",
            "current_stage": "diagnosis_failed",
            "messages": [AIMessage(content=f"❌ 诊断过程遇到问题: {str(e)}")]
        }

这个节点整合了所有可用信息(患者信息、症状、生命体征和检查结果),并请求 DeepSeek 模型进行全面的诊断分析。返回的结果包括主要诊断、鉴别诊断、诊断依据等,这些信息将用于后续的治疗方案制定。

2.3 边设计:连接决策路径

在 LangGraph 中,边定义了节点之间的连接关系。我们使用两种类型的边:

  1. 普通边:表示固定的流程顺序
  2. 条件边:根据状态决定下一步流向哪个节点

条件边是实现智能决策的关键。例如,在初步评估后,系统需要根据紧急程度决定下一步流程:

def route_after_assessment(state: MedicalDiagnosisState) -> Literal[
    "decide_testing", "order_tests", "make_diagnosis", "generate_report"]:
    """初步评估后的路由决策"""
    if state.get("error") or "failed" in state.get("current_stage", ""):
        return "generate_report"
    urgency = state.get("urgency_level", "")
    if urgency == "emergency":
        return "order_tests"  # 紧急情况直接检查
    return "decide_testing"  # 其他情况先决定检查策略

这个路由函数检查状态中的紧急程度,如果是"紧急"情况,则直接进入检查流程;否则,先决定检查策略。这种条件路由使系统能够根据实际情况灵活调整流程,模拟真实医生的决策过程。

3. 工作流整合:构建诊断图

现在,让我们看看如何将这些节点和边组合成一个完整的工作流:

def create_medical_diagnosis_assistant():
    """创建医疗诊断工作流"""
    workflow = StateGraph(MedicalDiagnosisState)

    # 添加节点
    workflow.add_node("initial_assessment", initial_assessment_node)
    workflow.add_node("decide_testing", decide_testing_strategy_node)
    workflow.add_node("order_tests", order_tests_node)
    workflow.add_node("make_diagnosis", make_diagnosis_node)
    workflow.add_node("create_treatment", create_treatment_plan_node)
    workflow.add_node("doctor_approval", doctor_approval_node)
    workflow.add_node("follow_up_planning", follow_up_planning_node)
    workflow.add_node("generate_report", generate_final_report_node)

    # 设置流程
    workflow.add_edge(START, "initial_assessment")

    # 添加条件边
    workflow.add_conditional_edges(
        "initial_assessment",
        route_after_assessment,
        {
            "decide_testing": "decide_testing",
            "order_tests": "order_tests",
            "make_diagnosis": "make_diagnosis",
            "generate_report": "generate_report"
        }
    )

    workflow.add_conditional_edges(
        "decide_testing",
        route_after_testing_decision,
        {
            "order_tests": "order_tests",
            "make_diagnosis": "make_diagnosis"
        }
    )

    workflow.add_edge("order_tests", "make_diagnosis")

    workflow.add_conditional_edges(
        "make_diagnosis",
        route_after_diagnosis,
        {
            "create_treatment": "create_treatment",
            "doctor_approval": "doctor_approval",
            "follow_up_planning": "follow_up_planning"
        }
    )

    workflow.add_edge("create_treatment", "doctor_approval")

    workflow.add_conditional_edges(
        "doctor_approval",
        route_after_approval,
        {
            "follow_up_planning": "follow_up_planning",
            "make_diagnosis": "make_diagnosis",
            "create_treatment": "create_treatment"
        }
    )

    workflow.add_edge("follow_up_planning", "generate_report")
    workflow.add_edge("generate_report", END)

    # 编译图
    memory = InMemorySaver()
    app = workflow.compile(checkpointer=memory)
    return app

这段代码首先创建了一个 StateGraph 实例,然后添加了所有节点。接着,它定义了节点之间的连接关系,包括起点和终点。特别值得注意的是条件边的使用,它们使工作流能够根据中间结果动态调整路径。

最后,我们使用 InMemorySaver 作为检查点,并编译图得到可执行的应用程序。这个检查点允许我们保存和恢复状态,对于长时间运行的诊断过程非常有用。

4. 流程图:智能诊断工作流可视化

为了更直观地理解这个诊断工作流,下面是一个大致的流程图:

 

这个流程图展示了诊断工作流的主要决策点和路径。从初步评估开始,系统根据紧急程度决定是否需要立即检查。检查完成后,进行诊断分析,然后根据诊断结果制定治疗方案。医生审批环节可能会根据审批结果导致流程回溯,重新进行诊断或调整治疗方案。最后,制定随访计划并生成最终报告。

5. 用户界面:通过 Streamlit 实现交互

为了使我们的智能诊断助手更加用户友好,我们使用 Streamlit 构建了一个 Web 界面。界面分为几个主要部分:

  1. 患者信息录入:在侧边栏收集患者基本信息、病史、过敏史等
  2. 症状描述:允许用户动态添加多个症状,包括症状名称、持续时间、严重程度等
  3. 生命体征:收集血压、心率、体温等关键指标
  4. 诊断过程展示:使用进度条和展开式面板显示诊断过程的每个步骤
  5. 结果展示:清晰展示初步诊断、治疗方案、随访计划和最终报告
# 诊断按钮
st.markdown("---")
col_btn1, col_btn2, col_btn3 = st.columns([1, 2, 1])
with col_btn2:
    diagnose_button = st.button("🔍 开始智能诊断", type="primary", use_container_width=True)

# 显示诊断结果
if diagnose_button:
    # ... 准备输入数据 ...

    # 运行诊断流程
    with st.spinner("正在进行智能诊断,请稍候..."):
        try:
            # 使用流式运行以显示进度
            stream_results = medical_diagnosis(patient_info, symptoms_list, vital_signs, stream=True)

            # 显示每个步骤的结果
            step = 0
            final_result = {}
            with message_container:
                for event in stream_results:
                    step += 1
                    progress = min(step / 8.0, 1.0)
                    progress_bar.progress(progress)

                    for node_name, node_data in event.items():
                        if node_name != "__end__":
                            # 更新状态文本
                            step_name_cn = step_names.get(node_name, node_name)
                            status_text.markdown(f"**当前步骤:{step_name_cn}**")

                            # 显示步骤结果
                            with st.expander(f"步骤 {step}: {step_name_cn}", expanded=True):
                                for msg in node_data.get("messages", []):
                                    if isinstance(msg, AIMessage):
                                        st.markdown(f'<p class="success-message">{msg.content}</p>',
                                                    unsafe_allow_html=True)

            # 显示最终报告
            # ... 展示诊断结果 ...
        except Exception as e:
            st.markdown(f'<p class="error-message">诊断过程中出现异常: {str(e)}</p>', unsafe_allow_html=True)

这段代码展示了如何使用 Streamlit 的界面元素收集用户输入,并调用我们的诊断工作流。特别值得注意的是,我们使用了流式运行(stream=True)来实时显示诊断进度,这大大提升了用户体验。

6. 总结与展望

通过本文的介绍,我们了解了如何使用 LangGraph 和 DeepSeek 构建一个智能医疗诊断助手。这个系统展示了 LangGraph 在构建复杂、有状态的工作流方面的强大能力,特别是在需要条件路由和状态管理的场景中。

我们的智能诊断助手具有以下特点:

  1. 模块化设计:每个诊断步骤都是一个独立的节点,便于维护和扩展
  2. 智能决策:通过条件边实现基于状态的动态路由
  3. 状态管理:集中管理诊断过程中的所有信息
  4. 用户友好:通过 Streamlit 提供直观的界面和实时反馈

未来,我们可以从以下几个方面进一步改进这个系统:

  1. 集成真实数据:连接实际的医疗数据库和检查设备
  2. 增加专业节点:添加更多专科诊断节点,如心脏科、神经科等
  3. 实现人工审核:在关键决策点引入人工审核机制
  4. 优化提示工程:进一步优化提示词,提高诊断准确性
  5. 多模态支持:添加对医学影像、音频等多模态数据的支持

LangGraph 为构建复杂的 AI 应用提供了强大的框架,而 DeepSeek 等大语言模型则为这些应用提供了智能核心。结合这两者,我们能够构建出更加智能、实用的 AI 系统,为各行各业带来变革。

posted @ 2025-11-07 09:23  石头木  阅读(24)  评论(0)    收藏  举报