微信扫码
添加专属顾问
我要投稿
用LangGraph构建的临床问诊助手ClinicAssist,解决了传统患者信息录入的痛点,通过智能对话提升效率与准确性。核心内容: 1. 医疗场景下患者信息录入的痛点与现有方案局限 2. ClinicAssist的三层技术架构与核心组件设计 3. LLM工作流的分阶段实现与症状收集阶段详解
点击“蓝字” 关注我们
在诊所或医院的日常运营中,“患者信息录入”(Patient Intake)是必不可少的环节,但现有方案始终存在局限:
尽管我并非临床专家,也缺乏医院场景的深度经验,但通过调研发现:当前LLM(大语言模型)若经过合理“编排”,完全有能力解决这些问题——核心是让助手能“有逻辑地引导对话”,同时“准确提取临床信息”(避免幻觉)。基于这个想法,我最终决定用LangGraph构建临床问诊助手ClinicAssist。
整个系统分为三大核心组件,各层职责明确且相互衔接: | 组件 | 技术栈 | 核心作用 | |---------------|--------------|--------------------------------------------------------------------------| | LLM工作流 | LangGraph | 主导对话逻辑与流程编排,控制“什么时候问什么问题”“是否需要追问”“何时进入下一阶段” | | 后端 | FastAPI | 作为前后端桥梁,提供RESTful API(会话管理、消息处理、结构化响应生成),保障数据完整性 | | 前端 | Next.js | 极简用户界面,负责展示对话流程、信息收集进度,以及已提取的临床结构化数据 |
其中,LangGraph驱动的LLM工作流是系统“大脑”——整个问诊流程被拆分为4个递进阶段,每个阶段的逻辑可复用,只需微调细节即可适配不同信息收集目标。
四个阶段遵循统一逻辑:提问→等待患者输入→提取结构化信息→判断是否足够→循环/进入下一阶段。下面以最关键、最复杂的“症状收集阶段”(Phase 2)为例,拆解具体实现。
在设计任何提示或流程前,首先要明确“最终需要什么结构化数据”——这是避免LLM输出混乱的核心。基于临床文档规范,我用Pydantic定义了“症状信息Schema”:
from pydantic import BaseModel, Field
from typing import Optional, List
class SymptomsPartial(BaseModel):
main_symptoms: Optional[List[str]] = Field(description="患者主要症状,如‘头痛’‘咳嗽’")
symptom_onset: Optional[str] = Field(description="症状发作时间,如‘3天前’‘今天早上’")
associated_symptoms: Optional[List[str]] = Field(description="伴随症状,如‘发烧’‘乏力’")
additional_symptom_info: Optional[List[str]] = Field(description="其他关键细节:严重程度、诱发因素、缓解方式等")
这个Schema的价值在于:让LLM的输出被“约束”在固定格式中,后续可直接用于医生参考或系统存储,无需额外处理非结构化文本。
LangGraph以“图”的方式组织流程,每个“节点”对应一个动作,“边”对应节点间的流转关系:
SymptomsPartial格式返回结果:# 让LLM仅输出符合SymptomsPartial格式的内容
symptoms_llm = llm.with_structured_output(SymptomsPartial)
这是整个阶段最需要思考的部分——既要避免信息缺失影响后续诊疗,又要防止过度追问打扰患者。最终采用“半结构化判断”方案:
class SymptomSufficiencyCheck(BaseModel):
is_sufficient: bool = Field(description="信息是否足够医生初步评估")
reason: Optional[str] = Field(description="需补充信息的原因,足够则为None")
路由函数实现:根据判断结果决定流转方向:
def route_after_symptoms(state: AgentState) -> str:
# 1. 硬条件检查:核心字段是否存在
has_main = state.get("main_symptoms") and len(state["main_symptoms"]) > 0
has_onset = state.get("symptom_onset") is not None
if not (has_main and has_onset):
return "ask_symptoms" # 核心字段缺失,循环追问
# 2. LLM辅助判断
check = check_symptom_sufficiency(state)
return "ask_medhist" if check.is_sufficient else "ask_symptoms"
需要注意:这种“LLM作为判断者”的方案虽灵活,但存在“非确定性”(不同次调用可能出不同结果)。需通过以下方式优化:
四个阶段(患者基本信息→症状→病史→分诊总结)的逻辑相似,只需复用“提问→提取→判断”框架,再通过LangGraph的StateGraph串联:
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
def build_clinical_assistant_graph():
# 1. 初始化状态图(AgentState为自定义状态类,存储对话历史和提取的信息)
graph_builder = StateGraph(AgentState)
# 2. 添加各阶段节点(仅展示关键节点)
# 阶段1:患者基本信息
graph_builder.add_node("ask_patient_info", ask_patient_info) # 提问
graph_builder.add_node("extract_patient_info", extract_patient_info) # 提取
graph_builder.add_node("human_patient_node", human_patient_node) # 等待输入
# 阶段2-4:症状、病史、分诊总结(节点定义类似,略)
# 3. 定义流转关系(边)
# 阶段1流转:提问→等输入→提取→判断是否进入阶段2
graph_builder.add_edge(START, "ask_patient_info")
graph_builder.add_edge("ask_patient_info", "human_patient_node")
graph_builder.add_edge("human_patient_node", "extract_patient_info")
# 条件边:提取后判断“循环”还是“进入阶段2”
graph_builder.add_conditional_edges(
"extract_patient_info",
route_after_patient_info, # 路由函数
{"ask_patient_info": "ask_patient_info", "ask_symptoms": "ask_symptoms"}
)
# 阶段2-4流转(逻辑类似,略)
# 最终:分诊总结→告知患者→结束
graph_builder.add_edge("triage_summary", "acknowledgement")
graph_builder.add_edge("acknowledgement", END)
# 4. 启用检查点(保存会话状态,支持中断后恢复)
checkpointer = InMemorySaver()
return graph_builder.compile(checkpointer=checkpointer)
LangGraph借鉴了NetworkX的图论思想,让流程编排更直观:
ClinicAssist目前仍处于早期阶段,但其核心价值在于:用LangGraph解决了“LLM对话流程可控性”问题——通过分阶段、结构化目标设计,让LLM既能灵活引导对话,又能稳定输出可用的临床信息。
未来优化方向可包括:
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2025-10-26
「基于智能体的企业级平台工程」建设完美指南
2025-10-26
DeepSeek-OCR:让 AI"一眼看懂" 文字的黑科技来了!
2025-10-26
马斯克 Grok imagine 完整使用指南:工具、案例、提示词,看这一篇就够了!
2025-10-25
LLM稳定JSON输出,终于摸清了
2025-10-25
涌现节点|AI安全的“皇帝新衣”:你的千亿级模型投资,正建立在一场集体幻觉之上
2025-10-25
当AI学会遗忘:浙大团队用"睡眠机制"破解大模型记忆难题
2025-10-25
CodeFlicker:快手推出的 AI 原生 IDE,让代码开发更高效!
2025-10-25
用Claude/Cursor写代码?小心这10个致命漏洞!
2025-08-21
2025-08-21
2025-08-19
2025-09-16
2025-07-29
2025-09-08
2025-10-02
2025-09-17
2025-08-19
2025-09-29
2025-10-25
2025-10-23
2025-10-23
2025-10-22
2025-10-22
2025-10-20
2025-10-20
2025-10-19