创新实训博客记录 | 5.GraphDx 接入与 AI 问诊流式输出
本阶段继续围绕智能问诊主链路进行完善。前几次已经完成了用户模块、基础资源模块、问诊会话和医生复核的雏形,这次重点放在 GraphDx 多智能体诊断服务接入 和 SSE 流式输出 上,让后端不仅能保存一轮问诊消息,还能把 AI 推理、候选疾病、建议检查和医生分流结果逐步返回给前端。
整体目标是:患者发送一条消息后,后端先保存消息,再调用 GraphDx 进行医学推理,然后将结构化结果写入数据库,并通过 SSE 事件推送给前端。
1. 本阶段开发目标
本次主要完成以下几部分:
- 接入
GraphDx/目录中的诊断智能体。 - 将 GraphDx 原始动作统一转换为后端可处理的
ask / test / diagnose / escalate。 - 保存每一轮 AI 推理中的观察项、候选疾病、检查建议和 trace 信息。
- 调用改写服务,将结构化结果改写成患者侧和医生侧都能理解的中文文案。
- 使用 SSE 返回阶段事件、候选快照、最终消息和完成状态。
问诊流式接口目前仍然是核心入口:
POST /api/v1/consult-sessions/{session_id}/messages/stream
X-Session-Token: <session_token>
Content-Type: application/json
{
"message_text": "最近三天头晕,站起来时更明显",
"client_request_id": "client-uuid"
}
2. GraphDx 接入方式
项目中后端业务代码主要维护在 server/ 目录下,GraphDx 作为诊断推理依赖放在独立目录中。这样做可以避免把业务接口、用户鉴权、数据库事务和多智能体内部逻辑耦合在一起。
目前接入逻辑主要放在:
server/src/services/consult_ai_service.py
整体调用流程如下:
GraphDx 原始返回中最重要的是 action_type 和 action_content。后端会先做动作归一化,只允许以下几类动作进入后续流程:
@staticmethod
def _normalize_action_type(action_type: str) -> str:
normalized = str(action_type).strip().lower()
if normalized in {"ask", "test", "diagnose", "escalate"}:
return normalized
raise ServiceError(f"unsupported GraphDx action_type: {normalized}", 503)
其中:
ask:信息不足,继续追问患者。test:建议进入检查流程。diagnose:候选诊断较明确,需要医生确认。escalate:风险较高,需要转医生复核。
这样后端服务层就不需要直接理解 GraphDx 内部日志,而是只处理统一后的业务动作。
3. AI 结果结构化与中文改写
GraphDx 输出更偏结构化和推理过程,直接展示给患者并不友好。因此本阶段在 GraphDx 后面增加了一层改写服务:
GraphDx 原始推理结果 -> 后端结构化字段 -> AiRewriteService 中文改写
改写服务会固定要求返回四个字段:
{
"assistant_text_cn": "面向患者的中文回复",
"doctor_summary_cn": "面向医生的临床摘要",
"patient_question_cn": "需要继续追问患者的问题",
"risk_notice_cn": "风险提示"
}
这一层的设计重点是 只改写表达,不改变医学事实。也就是说,候选疾病、检查建议和风险判断都来自 GraphDx,改写模型只负责把内容变成更清晰的中文说明。
当前一轮 AI 结果会被拆分保存到多张表中:
这样做的好处是前端不必只依赖一段文本。后续页面可以单独展示候选疾病快照、建议检查、分诊结果和完整时间线。
4. SSE 流式返回设计
普通 HTTP 接口只能等全部计算结束后一次性返回。但问诊场景中,AI 推理和中文改写都可能耗时,如果前端长时间没有反馈,体验会比较差。
所以这里继续使用 SSE:
Content-Type: text/event-stream
当前流式接口会先立即返回分析阶段,再持续返回心跳或最终事件。典型事件如下:
stage
message_saved
heartbeat
rewrite_completed
candidate_snapshot
department_assigned
doctor_review_required
exam_suggested
assistant_message
done
实际事件流可以理解为:
其中 heartbeat 的作用是告诉前端:后端还在处理中,连接没有断开。
5. 状态与异常处理
流式问诊容易出现两个问题:
- AI 调用时间较长,前端不知道是否还在运行。
- 后端进程中断后,会话可能一直停留在处理中。
因此本阶段对状态也做了处理。患者发送消息后,会话会进入:
AI_PROCESSING
AI 完成后,根据动作进入不同状态:
同时后端维护当前活跃的 trace_id。如果查询会话时发现某个会话长期停留在 AI_PROCESSING,并且对应 trace 已经不在活跃任务中,就会释放回信息收集状态,避免页面一直卡死。
示例代码结构如下:
def recover_orphan_processing_session(self, session_id: int, active_trace_ids: set[str]) -> None:
session = self.repo.get_session(session_id)
if session is None or session.session_status != self.PROCESSING_STATUS:
return
latest = self.repo.get_latest_status_history(session_id)
if latest is None or latest.trace_id in active_trace_ids:
return
# 超过处理 TTL 后释放回 COLLECTING_INFO
6. 总结
本阶段的重点是把“能调用 AI”推进到“能稳定接入问诊业务”。目前后端已经可以完成:
- 患者消息持久化。
- GraphDx 多智能体推理。
- 候选疾病、观察项、检查建议保存。
- 中文改写结果生成。
- SSE 流式事件推送。
- AI 处理中断后的状态恢复。
相比前一阶段,本次实现更接近真实使用场景。下一步主要继续完善医生端复核、检查单确认、设备时隙预约和检查结果回流,让 AI 问诊真正闭环到院内资源调度。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)