问诊状态 Schema

问诊过程中独立维护的结构化状态对象,包含主诉、症状、持续时间、诱因、危险信号、缺失字段等。比原始聊天记录更重要,是模型的工作记忆。

#type / concept #status / evergreen #tech / ai

[!info] related notes

问诊状态 Schema

问诊状态(ConsultationState)是问诊过程中独立维护的结构化状态对象,包含主诉、症状、持续时间、诱因、危险信号、缺失字段等。它比原始聊天记录更重要——messages 是证据,structured state 是工作记忆。

为什么需要独立状态

不要让模型每轮都从聊天记录里重新推理这些字段。原始 messages 有三个问题:

  1. 信息分散 — “膝盖下坠感”在第 1 轮,“一周了”在第 3 轮,“走路明显”在第 5 轮
  2. 格式不稳定 — 用户可能说”没有疼”也可能说”不疼”,模型需要每轮重新解析
  3. 无法驱动逻辑 — 代码无法从 messages 里可靠判断”是否已收集疼痛信息”

独立的 consultation_state 让:

  • 模型直接读取已收集的信息,不重复推理
  • 代码根据 missing_slots 决定下一步动作
  • UI 渲染结构化看板
  • 测试可以验证状态是否正确

Schema 定义

{
  "stage": "collecting_details",
  "chief_complaint": "膝盖下坠感",
  "body_parts": ["膝盖"],
  "symptoms": ["下坠感"],
  "duration": "一周",
  "aggravating_factors": ["走路"],
  "relieving_factors": [],
  "severity": null,
  "pain_level": null,
  "swelling": null,
  "trauma": null,
  "instability": null,
  "red_flags": [],
  "missing_slots": ["pain_level", "swelling", "trauma", "instability"],
  "last_question": "是否伴随疼痛或肿胀?",
  "pending_answer_for": ["pain", "swelling"],
  "confidence": {
    "body_part": 0.95,
    "duration": 0.80,
    "pain_level": null
  },
  "updated_at": "2026-06-30T10:30:00Z"
}

字段说明

核心字段

字段类型说明
stageenum当前问诊阶段
chief_complaintstring主诉(用户最初的问题)
body_partsstring[]涉及的身体部位
symptomsstring[]已确认的症状列表
durationstring持续时间
severitystring严重程度

诱因和缓解因素

字段类型说明
aggravating_factorsstring[]加重因素(如”走路""久坐”)
relieving_factorsstring[]缓解因素(如”休息""热敷”)

细节字段

字段类型说明
pain_levelstring/null疼痛程度(none/mild/moderate/severe)
swellingbool/null是否肿胀
traumabool/null是否有外伤史
instabilitybool/null是否有不稳定感(打软腿、卡顿)

流程控制字段

字段类型说明
red_flagsstring[]已触发的危险信号
missing_slotsstring[]尚未收集的关键字段
last_questionstring上一轮 AI 问了什么
pending_answer_forstring[]用户当前回答对应的字段

元数据字段

字段类型说明
confidenceobject各字段的置信度(0~1)
updated_atdatetime最后更新时间

问诊阶段枚举

阶段说明进入条件
start初始状态新会话
collect_chief_complaint收集主诉用户描述了问题
collect_details收集细节主诉已确认
red_flag_screening排查危险信号基本信息已收集
preliminary_assessment初步判断信息足够分析
rehab_recommendation康复建议初步判断完成
follow_up_plan观察和复查建议方案已给出

状态更新机制

更新来源

状态由两个来源更新:

  1. LLM tool callextract_symptom_info 抽取候选信息
  2. Deterministic merge:后端做字段合并、置信度判断、冲突处理

不要完全相信模型的 tool call 结果。 更优雅的方式是:

LLM 负责抽取候选信息
后端负责合并、校验、版本化

冲突处理

用户可能在不同轮次给出矛盾信息:

第 1 轮:"没有疼"
第 4 轮:"走久了有点疼"

状态不应该简单覆盖,而应该保留历史:

{
  "pain_level": {
    "current": "走久后轻微疼痛",
    "source_turn_id": "turn_004",
    "confidence": 0.86,
    "history": [
      {"value": "无明显疼痛", "turn_id": "turn_001"},
      {"value": "走久后轻微疼痛", "turn_id": "turn_004"}
    ]
  }
}

missing_slots 驱动问诊

missing_slots 是驱动问诊流程的核心字段。当它不为空时,Agent 优先追问最关键的 1~2 个问题:

def decide_next_action(state):
    if state.red_flags:
        return "safety_advice"

    if state.missing_slots:
        return "ask_followup"

    if state.stage == "collecting_details":
        return "preliminary_assessment"

    return "general_response"

详见 咨询 Agent 工作流设计

与 Function Calling 的关系

LLM 通过 extract_symptom_info tool call 提取信息:

SYMPTOM_TOOL = {
    "name": "extract_symptom_info",
    "parameters": {
        "type": "object",
        "properties": {
            "body_part": {"type": "string"},
            "symptom_type": {"type": "string"},
            "duration": {"type": "string"},
            "trigger": {"type": "string"},
            "severity": {"type": "string"},
            "pain_level": {"type": "string"},
            "swelling": {"type": "boolean"},
        },
        "required": ["body_part"],
    },
}

但提取结果只是候选信息,后端 merge 逻辑负责:

  1. 去重(同一字段相同值不重复记录)
  2. 冲突检测(新值与旧值矛盾时保留历史)
  3. 置信度更新
  4. missing_slots 重新计算

与 messages 的关系

messages = 证据(原始对话记录)
consultation_state = 工作记忆(结构化快照)
  • messages 告诉你”用户说了什么”
  • consultation_state 告诉你”我们已经知道了什么、还缺什么”

两者互补,不可替代。

数据库存储

建议 consultation_state 存在会话级别,每轮更新:

ALTER TABLE consultation_sessions
ADD COLUMN consultation_state JSONB DEFAULT '{}';

CREATE INDEX idx_consultation_state_stage
ON consultation_sessions ((consultation_state->>'stage'));
创建于 2026/6/30 更新于 2026/7/15