门诊入口的常见问题不是“让AI替医生判断”,而是如何把患者主诉结构化、把明显需要人工介入的情况及时升级,并把系统输出限制在流程建议范围内。本文只讨论技术架构和工程流程示例,不提供诊断、治疗、分诊或用药建议;所有风险分层、阈值和升级规则均为示例,真实项目必须由医疗专业人员和机构规范确认。

问题背景:AI Agent在门诊入口最容易越界在哪里

辅助分诊Agent通常接在挂号、互联网医院入口、院内导诊屏或客服系统前面。它要处理自然语言主诉,例如“胸口不舒服”“孩子发热”“复诊想开检查单”,再追问关键字段,最后给出“建议人工护士确认”“建议选择某类门诊咨询”等流程结果。

工程上最容易出问题的点有三个:

  • 症状采集不完整:只问主诉,缺少持续时间、严重程度、伴随表现、特殊人群等字段。
  • LLM直接输出结论:模型可能生成看似专业的诊断词或用药建议。
  • 升级链路不明确:识别到高风险表达后,仍继续聊天,没有进入人工处理队列。

所以系统设计的重点不是让Agent“更会看病”,而是让它更稳定地完成信息收集、规则触发、边界过滤和人工转接。

技术目标与约束

本文示例技术栈为 Python、FastAPI、decision tree、LLM API、PostgreSQL。目标链路如下:

用户输入主诉

LLM抽取结构化字段

规则树风险检查

是否触发升级

人工护士/客服队列

继续追问缺失字段

输出流程建议

边界审查与日志入库

需要满足几个工程约束:

  • LLM只做“理解和追问生成”,不作为最终风险判定唯一来源。
  • 风险分层采用可审计规则树,规则版本入库。
  • 输出只允许包含流程类文本,例如“建议转人工确认”“建议选择对应门诊咨询”。
  • 会话、抽取字段、规则命中、人工转接原因都要记录,便于复盘。

数据模型:先把主诉变成可判断的字段

一个可落地的最小字段集可以这样设计,字段名称服务于流程控制,不表达医学结论:

from pydantic import BaseModel
from typing import Optional, List

class SymptomIntake(BaseModel):
    chief_complaint: str
    duration: Optional[str] = None
    severity: Optional[int] = None  # 示例:1-10,由机构确认是否使用
    age_group: Optional[str] = None
    special_status: Optional[List[str]] = None
    associated_signals: Optional[List[str]] = None
    user_intent: Optional[str] = None  # 挂号、咨询、复诊、报告解读等

这里不要把字段设计成“疾病名称”。Agent的工作是采集信息和识别流程风险,不是归因。比如 associated_signals 只存用户原始表达归一化后的信号词,具体信号词库需要由机构维护。

PostgreSQL侧建议至少保留三类表:

  • triage_session:会话、渠道、用户匿名标识、状态。
  • triage_message:原始输入、Agent回复、时间戳。
  • triage_decision_log:结构化字段、规则版本、命中规则、转人工原因。

实现核心:LLM抽取 + 规则树判定 + 输出护栏

下面是一个简化 FastAPI 示例。代码中的规则仅用于演示工程结构,不代表任何真实分诊标准。

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional, Dict

app = FastAPI()

class ChatRequest(BaseModel):
    session_id: str
    text: str

class Intake(BaseModel):
    chief_complaint: str
    duration: Optional[str] = None
    severity: Optional[int] = None
    age_group: Optional[str] = None
    special_status: List[str] = []
    associated_signals: List[str] = []
    user_intent: Optional[str] = None

EXAMPLE_ESCALATION_RULES = [
    {
        "id": "RISK_SIGNAL_001",
        "if_any_signal": ["严重不适", "意识异常", "呼吸困难"],
        "action": "TRANSFER_HUMAN",
        "reason": "命中示例高优先级表达,需人工确认"
    },
    {
        "id": "SPECIAL_GROUP_001",
        "if_any_status": ["婴幼儿", "孕期", "高龄"],
        "action": "TRANSFER_HUMAN",
        "reason": "命中特殊人群示例规则,需人工确认"
    }
]

def mock_llm_extract(text: str) -> Intake:
    # 真实项目中可调用LLM API,并要求返回JSON Schema
    signals = []
    for word in ["严重不适", "意识异常", "呼吸困难"]:
        if word in text:
            signals.append(word)

    status = []
    for word in ["婴幼儿", "孕期", "高龄"]:
        if word in text:
            status.append(word)

    return Intake(
        chief_complaint=text[:80],
        associated_signals=signals,
        special_status=status,
        user_intent="门诊咨询"
    )

def apply_decision_tree(intake: Intake) -> Dict:
    for rule in EXAMPLE_ESCALATION_RULES:
        if "if_any_signal" in rule:
            if set(rule["if_any_signal"]) & set(intake.associated_signals):
                return {"action": rule["action"], "rule_id": rule["id"], "reason": rule["reason"]}

        if "if_any_status" in rule:
            if set(rule["if_any_status"]) & set(intake.special_status):
                return {"action": rule["action"], "rule_id": rule["id"], "reason": rule["reason"]}

    missing = []
    if not intake.duration:
        missing.append("持续时间")
    if intake.severity is None:
        missing.append("不适程度")

    if missing:
        return {"action": "ASK_MORE", "missing_fields": missing}

    return {"action": "FLOW_SUGGESTION", "suggestion": "可继续由人工或院内规则确认适合的咨询入口"}

def safe_response(decision: Dict) -> str:
    if decision["action"] == "TRANSFER_HUMAN":
        return f"已为你转接人工确认。原因:{decision['reason']}。"
    if decision["action"] == "ASK_MORE":
        fields = "、".join(decision["missing_fields"])
        return f"为了更好完成导诊信息登记,请补充:{fields}。"
    return "已完成基础信息登记,后续入口建议需按机构流程由人工或系统规则确认。"

@app.post("/agent/triage")
def triage(req: ChatRequest):
    intake = mock_llm_extract(req.text)
    decision = apply_decision_tree(intake)
    reply = safe_response(decision)

    return {
        "session_id": req.session_id,
        "intake": intake.model_dump(),
        "decision": decision,
        "reply": reply
    }

这段代码的关键不是规则本身,而是职责拆分:LLM抽取字段,规则树做可解释决策,safe_response 控制输出边界。

常见故障与修复思路

第一个故障是“模型开始解释病因”。修复方式是在系统提示词、JSON Schema和输出后处理三层限制:抽取阶段只返回字段;回复阶段只允许模板化流程话术;对诊断词、用药词、确定性判断做拦截或改写。

第二个故障是“用户描述很短,系统却直接给建议”。解决办法是配置必填字段门槛,例如主诉、持续时间、不适程度、年龄段至少满足若干项后才进入下一步。缺失字段时只追问,不输出结论。

第三个故障是“高风险表达没有及时升级”。不要只依赖LLM分类,应维护机构确认的触发词、正则、同义词表和规则版本。LLM可以做归一化,但规则命中应可审计、可回放。

第四个故障是“日志里看不到为什么转人工”。每次决策都要记录 rule_id、规则版本、输入字段快照和最终动作。线上排查时,能复现比“模型觉得需要转”更重要。

边界控制:把Agent限定为流程助手

在医疗健康场景中,Agent回复建议采用白名单模板,而不是让模型自由生成整段话。可以把输出类型限制为:

  • 补充信息:询问缺失字段。
  • 人工转接:说明进入人工确认流程。
  • 入口提示:说明后续按机构流程确认。
  • 安全声明:提示系统不提供诊断、治疗、分诊或用药建议。

真实项目还需要加入权限控制、脱敏存储、审计日志、数据保留策略和异常告警。对于未成年人、特殊状态、表达强烈不适等情况,示例规则应交给机构人员确认后配置,开发侧不要自行定义医学阈值。

总结与下一步

AI Agent辅助门诊分诊的工程重点,是把自然语言主诉转成结构化信息,并用可审计规则控制人工升级和回复边界。LLM适合做抽取、归一化和追问生成,规则树适合做稳定、可回放的流程判定。

下一步可以继续完善三件事:接入PostgreSQL保存决策日志;把示例规则改成可配置后台;增加离线回放脚本,用历史脱敏会话测试规则命中率和误触发情况。真实上线前,务必由医疗专业人员、合规团队和机构流程负责人共同确认规则与话术。

本文文献检索、文献挖掘以及文献翻译采用的是【超能文献| AI文献检索|AI文档翻译】

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐