门诊入口的常见工程问题不是“让 AI 替医生判断”,而是如何把用户主诉结构化、识别需要人工介入的风险信号,并把会话安全地交给导诊台或在线人工客服。本文只讨论技术架构和工程流程示例,不提供诊断、治疗、分诊或用药建议;文中的风险分层和阈值均为示例规则,真实项目必须由医疗专业人员和机构规范确认。

问题背景:AI Agent 容易在哪些地方越界

在门诊分诊辅助场景里,Agent 通常承担三个任务:采集症状、补充关键问题、生成前置分流建议。最容易出问题的是第三步:模型把“建议去哪个科室咨询”写成“你可能患有某病”,或者在缺少信息时继续追问不该由系统处理的细节。

工程上需要把 Agent 从“自由问答机器人”改造成“受控流程执行器”。LLM 负责自然语言理解和追问表达,规则引擎负责边界判断,审计模块负责留痕,人工转接负责兜底。

技术目标和约束

本文示例技术栈为 Python、FastAPI、decision tree、LLM API、PostgreSQL。目标不是构建自动分诊系统,而是实现一个辅助链路:

  • 将用户输入转换为结构化字段,例如主诉、持续时间、严重程度、伴随情况。
  • 使用可配置规则识别需要人工转接的场景。
  • 输出“就医路径提示”而不是诊断结论。
  • 保存会话、规则命中和模型输出,便于复核。
  • 对高风险、低置信度、未成年人、表达不清等情况优先升级人工。

方案概览:LLM 只做抽取和表达,规则做决策

一个相对稳妥的架构是“LLM + 规则树 + 人工转接”三段式。

缺字段

字段足够

命中升级规则

未命中

用户描述症状

LLM结构化抽取

字段完整性检查

生成下一轮追问

示例规则树评估

人工转接

生成科室咨询提示

记录审计日志

核心原则是:LLM 不直接决定风险等级,只把自然语言转成 JSON,并根据系统允许的模板生成回复。风险分层、转接条件、禁用话术应放在配置或数据库中,由业务和医疗专业人员共同维护。

数据模型:先定义 Agent 能看懂什么

症状采集不要一开始就追求复杂医学知识库,先把门诊导诊常用字段抽象出来。PostgreSQL 可以保存原始消息、结构化结果和规则命中记录。

CREATE TABLE triage_session (
    id UUID PRIMARY KEY,
    user_id TEXT,
    status TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT now()
);

CREATE TABLE triage_observation (
    id UUID PRIMARY KEY,
    session_id UUID REFERENCES triage_session(id),
    chief_complaint TEXT,
    duration_text TEXT,
    severity_level INT,
    age_group TEXT,
    has_red_flag BOOLEAN,
    extracted_json JSONB,
    created_at TIMESTAMP DEFAULT now()
);

CREATE TABLE triage_audit_log (
    id UUID PRIMARY KEY,
    session_id UUID REFERENCES triage_session(id),
    event_type TEXT,
    event_payload JSONB,
    created_at TIMESTAMP DEFAULT now()
);

这里的 severity_level 只是用户自述严重程度的结构化结果,不代表医学判断。has_red_flag 也只是“示例规则是否命中需人工处理的信号”,不能作为诊断依据。

FastAPI 实现:把每轮会话拆成可审计步骤

下面是一个最小可运行风格的接口示例。为了便于展示,LLM 调用用函数占位,真实项目应接入供应商 SDK,并增加超时、重试、限流和敏感信息处理。

from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional, Literal
import uuid

app = FastAPI()

class UserMessage(BaseModel):
    session_id: Optional[str] = None
    text: str = Field(min_length=1, max_length=500)

class Observation(BaseModel):
    chief_complaint: Optional[str] = None
    duration_text: Optional[str] = None
    severity_level: Optional[int] = None
    age_group: Optional[Literal["child", "adult", "older_adult"]] = None
    has_red_flag_text: Optional[bool] = None

def extract_observation_by_llm(text: str) -> Observation:
    """
    示例:真实实现中应要求 LLM 只返回 JSON。
    Prompt 中需要明确禁止输出诊断、治疗、用药建议。
    """
    return Observation(
        chief_complaint=text[:50],
        duration_text=None,
        severity_level=None,
        age_group="adult",
        has_red_flag_text=False
    )

def evaluate_rules(obs: Observation) -> dict:
    """
    示例规则:真实项目应由医疗专业人员和机构流程确认。
    """
    missing = []
    if not obs.duration_text:
        missing.append("duration_text")
    if obs.severity_level is None:
        missing.append("severity_level")

    if obs.has_red_flag_text:
        return {
            "action": "handoff",
            "reason": "matched_configured_red_flag",
            "message": "根据当前描述,建议转接人工服务进一步确认。"
        }

    if missing:
        return {
            "action": "ask_more",
            "missing": missing,
            "message": "为了更好地帮助登记,请补充症状持续时间和自感严重程度。"
        }

    return {
        "action": "guidance",
        "message": "已记录您的描述,可根据机构规则提示合适的咨询入口;本系统不提供诊断结论。"
    }

@app.post("/triage/chat")
def triage_chat(msg: UserMessage):
    session_id = msg.session_id or str(uuid.uuid4())
    obs = extract_observation_by_llm(msg.text)
    decision = evaluate_rules(obs)

    return {
        "session_id": session_id,
        "observation": obs.model_dump(),
        "decision": decision,
        "safety_notice": "本功能仅为技术流程示例,不提供诊断、治疗、分诊或用药建议。"
    }

这个接口的关键点不是代码复杂度,而是职责隔离:抽取、规则、回复、审计应可单独测试。不要让一个 Prompt 同时完成“理解、判断、安抚、推荐科室、生成结论”,否则很难定位错误来源。

症状问答设计:少问、问准、可退出

症状采集建议采用“必填字段 + 条件追问”的方式。第一轮只收集主诉和用户原文,第二轮再补持续时间、严重程度、年龄段等字段。每次追问最多 1 到 2 个问题,避免把门诊入口做成冗长问卷。

可配置字段示例:

  • chief_complaint:用户主要不适描述。
  • duration_text:持续时间,保留原文,不强行标准化。
  • severity_level:用户自评等级,展示为可选项。
  • age_group:粗粒度年龄段,用于流程路由。
  • red_flag_text:用户原文中命中的需人工确认信号。

界面上必须提供“跳过”“转人工”“重新描述”等入口。对无法理解、连续多轮缺失关键字段、用户表达明显焦虑或不确定的情况,应走人工兜底,而不是继续让模型猜测。

边界控制:用黑名单不够,要做输出契约

仅靠敏感词过滤很脆弱。更可靠的做法是定义输出契约:Agent 只能返回固定动作,例如 ask_morehandoffguidance,不能返回疾病名称、处置方案或用药建议。

可以在系统 Prompt 中写清楚:

  • 只抽取用户提供的信息,不补充事实。
  • 不输出诊断结论。
  • 不比较疾病可能性。
  • 不给治疗、检查、用药建议。
  • 遇到不确定、高风险、规则命中时转人工。
  • 所有提示必须包含“以机构人工确认为准”。

同时要在服务端做二次校验。即使 LLM 返回了越界内容,也应该被模板化响应覆盖,而不是直接展示给用户。

常见故障和排查方法

第一类问题是“模型追问过多”。通常是 Prompt 给了模型过大自由度,建议把追问问题从模型生成改为模板生成,模型只负责判断缺哪个字段。

第二类问题是“规则命中不可解释”。解决方式是在每次决策中记录 rule_id、输入字段、规则版本和动作结果。上线后排查问题时,不能只看最终回复。

第三类问题是“人工转接过晚”。这通常不是模型问题,而是升级规则太激进地追求自动完成率。门诊入口更适合保守策略:信息不完整、用户描述不清、规则版本未覆盖时,都应进入人工确认。

性能和扩展建议

LLM 抽取可以异步化,但首轮响应不宜过慢。实践中可先返回“已收到,正在整理信息”,再通过流式或轮询返回下一步。对相同会话上下文要缓存结构化结果,避免每轮都把完整历史发送给模型。

规则树建议版本化,例如 triage_rule_version=2026-06-29-a。当医疗机构调整流程时,可以回放历史匿名样本,检查新旧规则对转人工比例、缺字段比例、越界输出拦截次数的影响。

总结

AI Agent 辅助门诊分诊的工程重点不是让模型“更像医生”,而是让它在受控边界内完成信息采集和流程路由。推荐把 LLM 限定在结构化抽取和自然语言表达,把风险分层交给可审计、可配置、可回滚的规则系统。真实项目中,所有示例规则、阈值和升级策略都应由医疗专业人员与机构规范确认,并持续通过日志审计和人工复核迭代。

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

Logo

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

更多推荐