tmpqfi_vx12
AI Agent辅助门诊分诊:症状采集、初步分流和边界控制怎么设计
门诊分诊类 Agent 的核心难点不是“让模型多回答几句”,而是如何把症状采集、示例规则判断、人工转接和边界控制串成可审计的工程链路。本文只讨论技术架构和工程流程示例,不提供诊断、治疗、分诊或用药建议;文中所有阈值、风险分层和升级规则均为示例,真实项目必须由医疗专业人员和机构规范确认。
问题背景:分诊 Agent 最容易踩的三个坑
在门诊前置服务里,用户通常输入的是自然语言,例如“胸口不舒服”“孩子发热”“头晕两天”。如果直接把这类描述交给 LLM 生成结论,系统很容易越界到诊断建议,甚至给出不应由系统自动输出的处理方案。
工程上常见问题有三个:
- 症状采集不完整:缺少持续时间、严重程度、伴随表现、特殊人群信息。
- 规则链路不可审计:模型说了什么、为什么升级人工、命中了哪些规则没有记录。
- 输出边界失控:从“建议联系人工服务”滑向“判断为某疾病”。
因此,比较稳妥的设计是:LLM 负责结构化采集和表达整理,示例决策树负责风险标签,服务端策略层负责拦截越界内容和触发人工转接。
技术目标与约束
本文示例技术栈:
- FastAPI:提供问答和状态推进接口
- Python:实现规则引擎与 Agent 编排
- PostgreSQL:保存会话、结构化症状、规则命中记录
- LLM API:用于意图识别、追问生成和用户话术改写
- decision tree:实现可配置的示例风险分层
需要明确几个边界:
- Agent 不输出诊断结论。
- Agent 不推荐药物、剂量和治疗方案。
- Agent 只做信息采集、示例风险提示、人工转接建议。
- 所有风险规则必须可配置、可回溯、可由机构审核。
总体架构:把 LLM 放在“采集层”,不要放在“裁决层”
一个可控的流程可以拆成 5 个节点:
这里的关键设计是:LLM 不直接决定“去哪一科”或“是否紧急”,它只将用户输入整理成结构化字段。真正的风险分层由规则层完成,并且每条规则都能在数据库里留下命中记录。
数据模型:先把“症状事实”和“系统判断”分开
建议把用户原始输入、结构化症状、规则命中、最终回复分表保存。这样做便于回放、质检和追责。
CREATE TABLE triage_session (
id UUID PRIMARY KEY,
user_id TEXT,
status TEXT NOT NULL,
created_at TIMESTAMP DEFAULT now()
);
CREATE TABLE symptom_record (
id UUID PRIMARY KEY,
session_id UUID REFERENCES triage_session(id),
chief_complaint TEXT,
duration TEXT,
severity TEXT,
associated_symptoms JSONB,
special_population JSONB,
raw_text TEXT,
created_at TIMESTAMP DEFAULT now()
);
CREATE TABLE rule_hit_log (
id UUID PRIMARY KEY,
session_id UUID REFERENCES triage_session(id),
rule_code TEXT,
risk_level TEXT,
reason TEXT,
created_at TIMESTAMP DEFAULT now()
);
注意不要把“模型猜测”混入症状事实字段。比如用户没有说“持续时间”,就应该标记为 unknown,而不是让模型补一个看似合理的时间。
核心实现:示例规则引擎和边界过滤
下面代码是一个最小可运行的规则层示例。规则内容仅用于说明工程结构,不代表真实医疗分诊标准。
from enum import Enum
from typing import Dict, List
from pydantic import BaseModel
class RiskLevel(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
HANDOFF = "handoff"
class SymptomInput(BaseModel):
chief_complaint: str
duration: str | None = None
severity: int | None = None
associated_symptoms: List[str] = []
age_group: str | None = None
is_pregnant: bool | None = None
class RuleHit(BaseModel):
rule_code: str
risk_level: RiskLevel
reason: str
def evaluate_rules(symptom: SymptomInput) -> List[RuleHit]:
hits: List[RuleHit] = []
# 示例规则:严重程度较高时进入人工复核
if symptom.severity is not None and symptom.severity >= 8:
hits.append(RuleHit(
rule_code="EXAMPLE_SEVERITY_HIGH",
risk_level=RiskLevel.HANDOFF,
reason="用户自述严重程度较高,示例规则要求人工复核"
))
# 示例规则:特殊人群触发人工复核
if symptom.age_group in ["infant", "elderly"] or symptom.is_pregnant is True:
hits.append(RuleHit(
rule_code="EXAMPLE_SPECIAL_POPULATION",
risk_level=RiskLevel.HANDOFF,
reason="特殊人群信息命中示例升级规则"
))
# 示例规则:存在未采集关键字段,继续追问
if not symptom.duration:
hits.append(RuleHit(
rule_code="EXAMPLE_MISSING_DURATION",
risk_level=RiskLevel.MEDIUM,
reason="缺少持续时间字段,需要继续采集"
))
if not hits:
hits.append(RuleHit(
rule_code="EXAMPLE_DEFAULT_LOW",
risk_level=RiskLevel.LOW,
reason="未命中示例升级规则,仅提供非诊断性引导"
))
return hits
FORBIDDEN_PATTERNS = ["确诊", "诊断为", "建议服用", "用药剂量", "治疗方案"]
def guardrail_response(text: str) -> str:
if any(p in text for p in FORBIDDEN_PATTERNS):
return "我无法提供诊断、治疗或用药建议。可以继续帮你整理症状信息,并为你转接人工服务或提示联系正规医疗机构。"
return text
这段代码体现两个原则:
- 风险升级由显式规则触发,而不是由 LLM 自由判断。
- 输出前必须经过边界过滤,避免出现诊断、治疗、用药类表述。
FastAPI 编排:会话状态要可恢复
接口层不要只做一次性问答,而要维护会话状态。一次分诊采集可能包含多轮追问:主诉、持续时间、严重程度、伴随表现、特殊人群信息等。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ChatRequest(BaseModel):
session_id: str
message: str
class ChatResponse(BaseModel):
reply: str
action: str
risk_level: str
@app.post("/triage/chat", response_model=ChatResponse)
def triage_chat(req: ChatRequest):
# 示例:真实项目中应从LLM抽取结构化字段,并与历史会话合并
symptom = SymptomInput(
chief_complaint=req.message,
duration=None,
severity=5,
associated_symptoms=[],
age_group=None,
is_pregnant=None
)
hits = evaluate_rules(symptom)
max_hit = sorted(hits, key=lambda x: ["low", "medium", "high", "handoff"].index(x.risk_level))[0]
if any(h.risk_level == RiskLevel.HANDOFF for h in hits):
reply = "根据当前采集到的信息,系统将为你转接人工服务。此处不提供诊断或治疗建议。"
action = "handoff"
level = "handoff"
elif any(h.rule_code == "EXAMPLE_MISSING_DURATION" for h in hits):
reply = "为了更准确地整理信息,请补充:这个不适大约持续了多久?"
action = "ask_more"
level = "medium"
else:
reply = "已记录你的症状信息。你可以继续补充变化情况,或选择联系人工服务。"
action = "continue"
level = "low"
return ChatResponse(
reply=guardrail_response(reply),
action=action,
risk_level=level
)
这里的 action 比自然语言回复更重要。前端应根据 handoff、ask_more、continue 等动作切换界面,而不是解析回复文本来决定流程。
调试重点:如何发现 Agent 越界
上线前建议准备一组红队测试用例,覆盖以下输入:
- 用户直接要求“帮我诊断是什么病”
- 用户要求“推荐药和剂量”
- 用户只输入一个模糊症状
- 用户反复追问是否严重
- 用户输入特殊人群信息
- 用户描述强烈不适但字段不完整
每条测试用例至少检查 4 件事:
- 是否拒绝诊断、治疗、用药建议
- 是否继续采集缺失字段
- 是否命中预期示例规则
- 是否留下完整审计日志
如果发现 LLM 经常输出越界内容,可以把提示词拆成两段:一段只做结构化抽取,一段只做礼貌话术改写。不要让同一个 Prompt 同时承担抽取、判断和建议。
规则配置与人工转接
真实项目中,规则不应写死在代码里。更推荐把规则配置化,例如:
- 字段条件:severity、duration、age_group、special_population
- 动作:ask_more、handoff、continue
- 文案模板:只允许使用经过审核的非诊断性表达
- 版本号:支持按机构、科室、时间回滚
人工转接也要设计失败兜底。如果当前没有在线人员,系统应提示用户联系机构规定渠道,而不是继续让模型“补位”。
结论
AI Agent 辅助门诊分诊的工程重点,是把自然语言输入变成可审计、可配置、可人工接管的流程。LLM 适合做症状整理和追问生成,示例风险分层应放在规则引擎中,输出层必须设置诊断、治疗、用药边界。下一步可以继续完善 PostgreSQL 审计表、规则后台、红队测试集和人工坐席系统对接,让整个链路从 Demo 走向可治理的生产形态。
本文文献检索、文献挖掘以及文献翻译采用的是【超能文献| AI文献检索|AI文档翻译】。
更多推荐


所有评论(0)