实时视频问诊中的医疗AI:多模态引擎与工程落地
这两年医疗 AI 的讨论热度一直很高,但大多数演示还停留在“上传一张 CT 图片,模型输出一段诊断建议”这种离线场景。真正落到线上问诊系统时,输入从静态图片变成了一路连续的视频流,模型要一边听患者说话、一边观察面色和肢体动作、一边检索医学知识,还要在医生和患者都在等待的几秒内给出有参考价值的提示,工程复杂度完全不是一个量级。
本文围绕“Towards Expert-level Medical AI for Real-time Video Consultations”这个方向,从工程落地视角拆解实时视频问诊中的 Medical AI 系统:先讲清楚它解决的问题和技术边界,再给出一个可运行的辅助问诊原型,包含语音识别、视频帧分析、医学推理和知识检索的完整实现思路,最后整理实时场景下的延迟排查、评测方法和合规建议。适合正在做医疗 AI 应用、语音视频系统集成或大模型落地的开发者参考。
1. 实时视频问诊中的 Medical AI 是什么
1.1 从离线诊断到实时视频问诊
在传统的 AI 辅助诊断流程里,系统通常接收一张医学影像或一段文本主诉,经过模型推理后输出结论。这种模式有几个明显特点:输入是静态的、任务边界清晰、推理时间可以放宽到秒级甚至分钟级。
而实时视频问诊完全不同。医生和患者通过音视频连线沟通,AI 系统需要同时处理三路信息:
- 音频信息:患者的主诉、病史、用药情况,以及语气、语速等非语言信息;
- 视频信息:患者的面色、精神状态、皮肤表现、呼吸频率等可见体征;
- 文本信息:医生可能同时查看患者的历史病历、检查报告、过敏史等结构化数据。
这意味着 Medical AI 不再是“单模型单输入”,而是一套多模态实时计算引擎。视频流从摄像头到 AI 服务端的延迟、语音转写的实时性、大模型推理的响应时间、知识检索的命中率,都会直接影响线上体验。这也是“专家级”这个目标背后真正的难点。
1.2 Medical AI 的定位:辅助还是替代
这里必须明确一个边界:当前医疗 AI 产品的定位是“辅助决策”,而不是“替代医生”。尤其在实时视频问诊场景中,AI 的价值更多体现在四个方面:
- 信息结构化:自动把医患对话整理成主诉、现病史、既往史等结构化字段;
- 实时提醒:在医生可能遗漏关键问题、或患者描述与常见病程不匹配时给出提醒;
- 知识增强:基于患者当前描述,实时检索药品说明、指南要点、相似病例;
- 文书自动生成:问诊结束后自动生成病历草稿和随访计划,减少医生打字负担。
系统输出的是“参考信息”,最终决策权和法律责任都在医生身上。这个定位不仅决定产品设计,也决定技术实现,比如提示词约束、权限控制、日志审计都需要围绕“辅助”来设计。
1.3 本文的讨论范围
本文不讨论如何从零训练一个医学大模型,而是聚焦在工程集成层:把语音识别、视频帧理解、医学大模型推理、知识检索这些成熟组件串成一套实时视频问诊辅助系统。换句话说,假设底层 ASR 模型、LLM 接口已经具备基础能力,重要的是如何让它们在真实问诊链路中稳定、低延迟、安全地协同工作。
2. 系统整体架构与核心模块
2.1 一条完整的实时视频问诊链路
先看端到端的数据流。以浏览器问诊为例,前端采集摄像头和麦克风数据,通过 WebRTC 推流到媒体服务器;媒体服务器一方面把音视频转发给医生端,另一方面把音频和视频帧旁路给 AI 分析服务。AI 分析的中间结果通过 WebSocket 推送给医生端页面,以提示卡片的形式呈现。
这条链路可以拆成五个环节:
- 采集端:浏览器或 App,负责音视频采集、编解码、弱网处理;
- 传输层:WebRTC/SFU,负责低延迟推流、转发、录制;
- AI 接入层:把音视频流转成 ASR 和视频帧分析能处理的格式;
- 医学推理层:结合转写文本、视频描述、历史病历和知识检索结果,生成辅助结论;
- 反馈与审计层:把结果推送给医生端,并记录完整的分析过程和依据。
这五个环节中,AI 接入层和医学推理层是文章重点,因为大多数实时问诊工程问题都出现在这里。
2.2 各模块职责拆分
音视频接入模块:接收 WebRTC 轨道或拉流地址,音频送入 ASR,视频按策略抽帧。这个模块要保证低延迟和资源可控,不能为了一帧画面等太久。
语音识别模块(ASR):实时把医生和患者的对话转成文字,同时输出说话人标记和时间戳。中文医疗场景中,专业术语多、口语化明显,ASR 需要加载医学词汇表。
视频理解模块:对面部、皮肤、精神状态进行基础分析。注意,这里不建议直接让模型做复杂诊断,而是做“可见体征提取”,例如面色是否苍白、是否出汗、精神状态是否萎靡,再把这些观察作为文本提供给推理层。
医学推理模块:核心是 LLM 调用,输入包括转写文本、视频观察结果、RAG 检索到的医学知识,输出结构化建议,例如鉴别诊断方向、需要补充的追问、风险预警等。
知识检索模块(RAG):为 LLM 提供外部知识,解决模型知识陈旧和幻觉问题。常见做法是把药品说明书、临床指南、疾病科普语料向量化后存入向量数据库,推理前做相似度检索。
会话管理模块:维护问诊上下文,包括历史对话、患者基本信息、医生标记信息,控制进入 LLM 的上下文长度;同时负责把中间结果缓存起来,避免重复转写和重复检索。
2.3 实时性与准确性的矛盾
实时视频问诊对延迟的敏感度远高于普通聊天机器人。医生问完一个问题,如果 AI 提示要 10 秒后才出现,体验就很差。但医学场景又要求准确,不能为了快而输出没有检索依据的结论。
工程上权衡的策略是分层处理。把“实时性要求高、计算简单”的任务放在前面,比如 ASR;把“准确性要求高、计算量大”的任务放在后面,并且使用异步推送。例如转写完成后立即把文本推给医生端展示,同时后台并行完成知识检索和 LLM 推理,推理结果出来后再推送第二张卡片,这样用户感知到的延迟会被摊薄。
3. 环境准备与项目结构
3.1 运行环境
本文示例在以下环境中验证通过,供参考:
- 操作系统:Ubuntu 22.04 / macOS 13+
- Python:3.10 或以上
- 依赖管理:pip + virtualenv
- ASR 模型:faster-whisper,使用 small 或 medium 模型
- LLM 接口:OpenAI 兼容接口,也可换成其他兼容服务
- 其他:ffmpeg(用于音频格式处理)、Redis(可选,用于会话缓存)
如果你的服务器没有 GPU,ASR 和向量模型可以切到 CPU 推理,速度会慢一些,建议把模型量化为 int8,或者只跑 small 版本。
3.2 Python 依赖
pip install fastapi uvicorn websockets faster-whisper openai numpy opencv-python sentence-transformers faiss-cpu python-multipartfastapi和uvicorn用来搭建 WebSocket 服务和 HTTP 接口;faster-whisper负责语音实时转写;openai用来调用大模型接口;opencv-python负责视频帧读取和基础图像处理;sentence-transformers和faiss-cpu用来做医学知识检索;numpy作为向量计算基础库。
3.3 项目目录结构
medical-ai-video/ ├── main.py # FastAPI 入口,WebSocket 服务 ├── asr_service.py # 语音识别模块 ├── video_analyzer.py # 视频帧分析模块 ├── medical_engine.py # 医学推理模块 ├── rag_service.py # 知识检索模块 ├── prompt_templates.py # 提示词和 JSON 输出格式定义 ├── requirements.txt ├── data/ │ └── medical_kb/ # 医学知识文本 └── logs/这里把功能拆成独立文件,方便后续扩展。如果团队更大,按服务拆分部署更合理,这个稍后在工程化章节展开。
4. 核心能力拆解
4.1 实时语音识别:ASR 模块设计
实时语音识别的关键不是“转写准”,而是“边说话边出结果”。一次性把整段音频送过去识别,延迟无法接受。更常见的做法是分片转写:把音频流切成 2-5 秒的片段,配合语音活动检测(VAD)判断说话开始和结束,有语音才识别,没语音就丢掉。
下面是一个基于 faster-whisper 的代码示例:
# 文件路径:asr_service.py from faster_whisper import WhisperModel import numpy as np class ASRService: def __init__(self, model_size: str = "small", device: str = "auto"): # 使用 int8 量化可以显著降低显存占用和推理延迟 self.model = WhisperModel( model_size, device=device, compute_type="int8" ) def transcribe(self, audio_bytes: bytes, language: str = "zh"): """ 输入 PCM 音频字节,返回转写文本和片段信息 """ # faster-whisper 支持直接读取二进制缓冲区 segments, info = self.model.transcribe( audio_bytes, language=language, vad_filter=True, # 过滤静音,减少无效识别 beam_size=1, # 实时场景用 beam=1 降低延迟 without_timestamps=False ) text_parts = [] ts_parts = [] for segment in segments: text_parts.append(segment.text) ts_parts.append({ "start": segment.start, "end": segment.end, "text": segment.text }) return { "text": "".join(text_parts), "segments": ts_parts }代码里有两个关键参数需要说明。
vad_filter=True会过滤掉没有语音的片段,在问诊场景中特别有用。医生和患者不会一直说话,中间会有停顿,如果没有 VAD,whisper 会把停顿误识别成无意义的语气词,甚至产生幻觉文本。
beam_size=1是实时识别的常用策略。beam size 越大,识别越准,但解码时间越长。在实时链路里通常先用 beam=1 快速出结果,后续如果有需要,再对答案做二次校验。医疗场景中如果对准确率要求较高,可以改成 beam=5,但要做好延迟增加的心理准备。
4.2 视频帧理解:从抽帧到可见体征提取
实时视频流不能每帧都送进模型,会造成极大的计算浪费,同时也没有必要。工程上通常按固定间隔抽帧,例如每 3-5 秒抽一帧,再用轻量模型做分析。
# 文件路径:video_analyzer.py import cv2 import base64 import numpy as np class VideoAnalyzer: def __init__(self): # 这里用 opencv 做人脸检测,实际项目可换成更轻量的模型 self.face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) def analyze_frame(self, frame_bgr: np.ndarray): """ 输入一帧 BGR 图像,输出基础观察结果 """ gray = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2GRAY) faces = self.face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(80, 80) ) result = { "has_face": len(faces) > 0, "face_count": len(faces), "image_quality_ok": True, "luminance": float(np.mean(gray)), "observation": None } if not result["has_face"]: result["image_quality_ok"] = False result["observation"] = "未检测到人脸,请提醒患者正对摄像头" return result avg_lum = result["luminance"] if avg_lum < 50: result["image_quality_ok"] = False result["observation"] = "画面偏暗,可见体征难以判断" elif avg_lum > 200: result["image_quality_ok"] = False result["observation"] = "画面过亮,可能存在过度曝光" # 简化示例:真正生产环境可以用多模态模型生成文本描述 result["observation"] = result["observation"] or "视频画面质量正常,可进一步分析" return result视频模块的核心思路是“质量判断”和“体征提取”分离。先判断当前帧是否适合分析,比如有没有人脸、曝光是否正常、是否模糊,只有质量合格的帧才进入下一步。
这里的“下一步”建议接入多模态模型,把图像转成文字描述,例如“面色苍白,精神状态不佳,嘴唇干燥”。因为后续医学推理层的 LLM 主要处理文本,统一转成文本能让推理层保持简单。多模态模型可以直接用 OpenAI 兼容的图像输入接口,也可以委托给专门的视觉模型服务。
def frame_to_base64(frame_bgr: np.ndarray) -> str: _, buffer = cv2.imencode(".jpg", frame_bgr, [cv2.IMWRITE_JPEG_QUALITY, 80]) return base64.b64encode(buffer).decode("utf-8")这一段代码把帧压缩成 JPEG 再转 base64,是为了传给多模态模型接口时节省带宽。编码质量设为 80 左右比较合适,太低会影响模型判断,太高会增加传输耗时。
4.3 医学推理引擎:提示词与结构化输出
医学推理引擎是整套系统的“大脑”。它负责把 ASR 转写文本、视频观察结果、RAG 检索到的医学知识拼装成提示词,调用 LLM 生成结构化输出。
核心设计要点有两个:任务边界清晰、输出格式固定。
任务边界清晰指的是让模型做“信息整理和参考建议”,而不是让它给出确定性诊断。比如提示词里明确要求“输出鉴别诊断方向”而不是“确诊疾病”。输出格式固定则通过 JSON Schema 约束,方便系统下游解析和展示。
下面是一个简化版的医学推理引擎:
# 文件路径:medical_engine.py import json from openai import OpenAI class MedicalEngine: def __init__(self, api_key: str, base_url: str = None, model: str = "gpt-4o-mini"): self.client = OpenAI(api_key=api_key, base_url=base_url) self.model = model def generate_suggestions( self, dialogue_text: str, video_observation: str, knowledge_texts: list[str], patient_info: dict | None = None ) -> dict: # 拼装系统提示词 system_prompt = """ 你是一名资深全科医生的 AI 助理,在实时视频问诊中为医生提供参考信息。 你的任务包括: 1. 提取患者的主诉、现病史、既往史、用药史; 2. 总结视频观察到的可见体征; 3. 基于检索到的医学知识,给出鉴别诊断方向; 4. 标记需要医生重点关注的风险信息。 注意: - 你只提供辅助参考,不能做出最终诊断; - 如果信息不足,明确列出需要补充的问题; - 所有输出必须使用 JSON 格式。 """ user_content = { "dialogue": dialogue_text, "video_observation": video_observation, "knowledge": knowledge_texts, "patient_info": patient_info or {} } response = self.client.chat.completions.create( model=self.model, response_format={"type": "json_object"}, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": json.dumps(user_content, ensure_ascii=False)} ], temperature=0.2, max_tokens=1024 ) raw = response.choices[0].message.content return json.loads(raw)温度设置为 0.2,目的是减少随机性,让模型在医学场景下更稳定。如果在实际测试中发现输出仍然不稳定,可以进一步使用结构化输出功能,甚至把候选答案限定在预设枚举值里。
要注意的是,这个模块是容易出问题的地方。LLM 返回的 JSON 偶尔会有格式错误、多出注释、或者字段缺失。生产环境一定要加一层解析兜底,解析失败时标记为“暂不展示 AI 建议”,而不是让异常直接抛到前端。
4.4 RAG 知识检索:让模型有据可依
医学 LLM 单独使用会有两个问题:专业知识更新不及时,以及可能对不熟悉的药物或疾病产生幻觉。RAG(检索增强生成)是对抗幻觉的常用手段。
思路很简单:提前把权威的药品说明书、医学指南等文本切块,计算向量后存入 FAISS 索引;推理前用当前对话内容去检索最相关的文本块,把这些文本块作为参考注入 LLM 提示词。
# 文件路径:rag_service.py from sentence_transformers import SentenceTransformer import faiss import numpy as np import os class RAGService: def __init__(self, kb_dir: str = "data/medical_kb"): self.encoder = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") self.kb_dir = kb_dir self.chunks = [] self.index = None self._build_index() def _build_index(self): """ 遍历知识库目录中的文本文件,切块并建立向量索引 """ texts = [] for root, _, files in os.walk(self.kb_dir): for fname in files: if not fname.endswith(".txt"): continue path = os.path.join(root, fname) with open(path, "r", encoding="utf-8") as f: content = f.read() # 简单按段落切块,生产环境可以按语义切块 for para in content.split("\n\n"): para = para.strip() if len(para) >= 20: texts.append(para) if not texts: return self.chunks = texts embeddings = self.encoder.encode(texts, normalize_embeddings=True) dim = embeddings.shape[1] self.index = faiss.IndexFlatIP(dim) self.index.add(np.asarray(embeddings, dtype=np.float32)) def search(self, query: str, top_k: int = 3): if not self.index: return [] q_vec = self.encoder.encode([query], normalize_embeddings=True) scores, indices = self.index.search(np.asarray(q_vec, dtype=np.float32), top_k) results = [] for score, idx in zip(scores[0], indices[0]): if idx < len(self.chunks): results.append({ "text": self.chunks[idx], "score": float(score) }) return results示例中使用了多语言向量模型,因为问诊对话可能是中英文混合。知识库文件切块时,不能切得太碎,否则语义会不完整;也不能一整个文件作为一条记录,否则检索精度会下降,而且会把大量无关信息塞进 LLM 上下文。通常控制在 200-500 字之间。
检索结果里带上相似度分数,方便判断检索质量。如果分数低于阈值,说明知识库没有相关内容,可以考虑不注入检索结果,直接让推理引擎基于对话内容输出提示,并在前端标注“未检索到相关指南”。
5. 实战案例:搭建实时视频问诊辅助原型
5.1 设计思路
下面用 FastAPI 搭建一个可运行的简化原型。考虑到完整 WebRTC 信令和媒体服务器实现比较复杂,原型采用“浏览器采集音视频 → 通过 WebSocket 推流 → 服务端做 AI 分析”的方式,重点演示 AI 侧的处理逻辑。
浏览器端每 3 秒推送一次音频字节和视频帧,服务端分别调用 ASR 和视频分析模块,再把结果送入医学推理引擎,最终返回 JSON 提示卡片。
# 文件路径:main.py import asyncio import json import base64 import numpy as np import cv2 from fastapi import FastAPI, WebSocket from asr_service import ASRService from video_analyzer import VideoAnalyzer from medical_engine import MedicalEngine from rag_service import RAGService app = FastAPI() asr = ASRService(model_size="small", device="auto") video_analyzer = VideoAnalyzer() rag = RAGService() engine = MedicalEngine( api_key="your-api-key", base_url=None, # 本地或自建服务请填写实际地址 model="your-model-name" ) @app.websocket("/ws/analyze") async def websocket_analyze(ws: WebSocket): await ws.accept() dialogue_buffer = [] try: while True: message = await ws.receive_text() data = json.loads(message) if data["type"] == "audio": # 音频是 base64 编码的 PCM 字节 audio_bytes = base64.b64decode(data["data"]) loop = asyncio.get_event_loop() asr_result = await loop.run_in_executor( None, asr.transcribe, audio_bytes ) if asr_result["text"]: dialogue_buffer.append(asr_result["text"]) await ws.send_text(json.dumps({ "type": "transcript", "data": asr_result["text"] }, ensure_ascii=False)) elif data["type"] == "video": # 视频是 base64 编码的 JPEG 帧 frame_bytes = base64.b64decode(data["data"]) frame = cv2.imdecode( np.frombuffer(frame_bytes, dtype=np.uint8), cv2.IMREAD_COLOR ) v_result = video_analyzer.analyze_frame(frame) # 如果画面质量合格,才做多模态分析 if v_result["image_quality_ok"]: # 这里省略多模态接口调用,可以传入 frame 给视觉模型 pass elif data["type"] == "trigger_ai": # 前端通知可以生成阶段性建议 dialogue_text = "".join(dialogue_buffer[-20:]) knowledge = rag.search(dialogue_text, top_k=3) video_obs = "视频画面正常,无特殊发现" suggestions = engine.generate_suggestions( dialogue_text=dialogue_text, video_observation=video_obs, knowledge_texts=[item["text"] for item in knowledge] ) await ws.send_text(json.dumps({ "type": "suggestion", "data": suggestions }, ensure_ascii=False)) except Exception as e: print(f"WebSocket 连接异常: {e}") finally: await ws.close()5.2 前端推送脚本
为了让原型可以直接验证,我也写了一个简单的 Python 客户端,模拟浏览器推送音频和视频数据。实际项目中这段逻辑会放在浏览器里,采集麦克风和摄像头数据后推送到服务端。
# 文件路径:client_simulator.py import asyncio import json import base64 import cv2 import soundfile as sf import numpy as np import websockets async def send_samples(uri: str): # 读取一段预录音频 audio_data, sr = sf.read("sample.wav", dtype="int16") # 读取视频帧 cap = cv2.VideoCapture("sample_video.mp4") async with websockets.connect(uri) as ws: # 发送音频片段,模拟实时语音流 chunk_size = sr * 3 # 3 秒一个片段 for i in range(0, len(audio_data), chunk_size): chunk = audio_data[i:i + chunk_size] payload = base64.b64encode(chunk.tobytes()).decode("utf-8") await ws.send(json.dumps({"type": "audio", "data": payload})) # 发送视频帧 while True: ret, frame = cap.read() if not ret: break _, frame_enc = cv2.imencode(".jpg", frame) payload = base64.b64encode(frame_enc.tobytes()).decode("utf-8") await ws.send(json.dumps({"type": "video", "data": payload})) # 触发 AI 推理 await ws.send(json.dumps({"type": "trigger_ai", "data": ""})) # 接收返回结果 async for response in ws: print(response) asyncio.run(send_samples("ws://localhost:8000/ws/analyze"))5.3 启动与验证
在项目根目录启动服务:
uvicorn main:app --host 0.0.0.0 --port 8000然后运行客户端模拟器:
python client_simulator.py预期你会看到三类输出:
transcript:ASR 转写出来的文本;suggestion:医学推理引擎给出的 JSON 建议;- 视频质量相关提示。
如果 ASR 返回空文本,说明音频格式或采样率不对,需要检查客户端发送的 PCM 数据是否与服务端模型期望的采样率一致。faster-whisper 支持 16kHz 单声道音频,如果采集到的是 48kHz,需要先重采样。
5.4 结果说明
这个原型虽然简化了 WebRTC 传输,但 AI 侧的核心链路已经完整:音频转写、视频帧质量判断、知识检索、LLM 推理、WebSocket 返回。把它迁移到真实视频问诊系统时,只需要把“WebSocket 接收音视频”替换成“从媒体服务器读取音视频轨道”,AI 逻辑可以完整复用。
6. 从“能用”到“专家级”:评测与优化
6.1 离线医学基准评测
评估一个医疗 AI 系统是否“专家级”,不能只看演示效果,需要回到标准数据集上对比。
常见的医学基准包括 MedQA、CMB 等,用于衡量模型的医学知识水平。工业界在推进医疗大模型时,通常会做一批科室级评测集,覆盖内科、外科、儿科、皮肤科等。针对实时视频问诊,还应该单独构建多模态评测集:给出一段视频片段和对话转写序列,要求系统输出主诉、现病史、鉴别方向、风险提示,然后由医生标注对比。
评测指标上,推荐使用字段级 F1,而不是只看整体准确率。因为系统输出是结构化 JSON,主诉提取对不对、鉴别诊断方向是否合理、风险标记是否命中,这些字段要分开评估。这样定位问题也更清楚:是 ASR 出错了,还是检索不相关,还是推理逻辑有问题。
6.2 在线实时指标
离线评测解决“模型能力”问题,在线指标解决“体验质量”问题。在实时视频问诊场景中,建议至少监控四类指标:
- ASR 转写时延:从音频输入到文本输出的时间,目标小于 1.5 秒;
- 端到端提示时延:从医生触发 AI 到前端展示建议卡片的时间,目标小于 5 秒;
- AI 建议采纳率:医生在问诊中实际采纳或引用了多少条 AI 提示;
- 用户满意度:患者和医生对问诊过程的主观评分。
其中“建议采纳率”特别重要。它直接反映 AI 是否有用,如果一个系统产出的提示医生从来不用,准确率再高也是自嗨。
6.3 延迟预算拆解
端到端 5 秒的目标不是随便定的。把它拆开看,每部分都要卡得很紧:
| 环节 | 延迟预算 |
|---|---|
| 音频分片与传输 | 500ms |
| ASR 转写 | 1.5s |
| 触发推理与上下文组装 | 300ms |
| RAG 检索 | 300ms |
| LLM 推理 | 1.5s - 2.5s |
| 结果回传与渲染 | 200ms |
如果 LLM 推理是主要瓶颈,可以考虑流式输出,先让前端展示文本片段,最后再回填结构化 JSON。如果检索耗时长,则要检查向量索引规模、编码模型推理速度,必要时改成服务端预计算加缓存。
7. 常见问题与排查思路
7.1 ASR 转写文本乱码或为空
现象:客户端推入音频后,返回的文本是乱码或空。
常见原因:
- 音频格式不对,whisper 期望 16kHz 单声道 PCM,而客户端发的是 48kHz 或双声道;
- 音频字节没有去除 WAV 文件头;
- VAD 过滤掉了所有声音,可能由于音频本身太轻或噪音太大。
解决思路:
- 先用 ffprobe 查看音频采样率和通道数;
- 统一在客户端重采样到 16kHz,单声道;
- 临时关闭
vad_filter测试,确认是识别问题还是 VAD 误过滤。
ffprobe -show_streams sample.wav | grep -E "sample_rate|channels"7.2 LLM 输出 JSON 解析失败
现象:json.loads抛异常,推荐结果为null。
常见原因:
- 大模型返回了 Markdown 代码块包裹的 JSON;
- 返回内容中夹杂了解释性文字;
- 输出长度超过
max_tokens,被截断。
解决思路:
- 解析前先做内容清洗,例如去除 ```json 标记;
- 设置
response_format={"type": "json_object"}; - 解析失败时进入兜底分支,直接展示原始文本,不要中断问诊流程。
7.3 视频帧模糊或无法提取特征
现象:视频分析模块频繁提示“未检测到人脸”或“画面质量差”。
常见原因:
- 网络分辨率过低,实际帧尺寸只有 320x240;
- 光线太暗或背光;
- 抽帧时机正好在画面切换过渡帧。
解决思路:
- 在客户端限制最小分辨率;
- 抽帧前做基础质量评估,丢弃清晰度低的帧;
- 连续多帧失败时,提示患者调整摄像头或光线。
7.4 音频和视频不同步
现象:转写文本已经出来,但对应的视频观察结果滞后。
常见原因:
- 音频和视频走了两条独立的处理链路,没有共享时间戳;
- 网络拥塞导致视频帧排队。
解决思路:
- 在推流端给每一段音频和每一帧视频打上同步时钟戳;
- 服务端按时间戳对齐后再送 AI 分析;
- 如果无法对齐,宁可丢弃过期的视频帧,也不能让推理等待太久。
7.5 合规顾虑
现象:项目准备上线,但合规评审提出多个无法回答的问题。
常见原因:
- 不清楚音视频数据存储在哪里、保存多久、谁能访问;
- 没有告知患者 AI 正在参与辅助分析;
- 缺少完整的审计日志,无法追溯每一次 AI 建议的依据。
解决思路:
- 数据加密存储,按最小权限原则设置访问控制;
- 在问诊前增加 AI 辅助说明和用户授权;
- 使用脱敏后的数据来训练或评测模型,避免直接用真实问诊数据;
- 记录每次 AI 建议的输入、输出、检索来源和模型版本,方便事后审计。
8. 工程化与合规建议
8.1 服务拆分与部署
原型把多个 AI 模块放在同一个进程里,方便演示。生产环境建议按职责拆分部署,至少拆成三组服务:
- 网关与媒体服务:负责 WebRTC、音视频转发、录制;
- AI 分析服务:负责 ASR、视频分析、RAG 检索,这些模块计算密集,需要独立扩缩容;
- 推理服务:负责大模型调用,需要根据请求量做 QPS 控制。
不同服务使用不同的资源规格。ASR 需要 GPU 或者较强的 CPU,视频分析可以混部,向量检索用内存型实例,大模型推理则要看采用的部署方式。不要把所有服务塞进一个容器,否则一个模块的高负载会影响整条链路。
8.2 敏感信息处理
医疗音视频属于个人敏感信息,工程上必须遵守“最小收集”原则。建议做到:
- 视频流默认不落盘,只在需要质控时经过授权后录制;
- 音频转写文本存储前先做去标识化,例如去掉姓名、身份证号等;
- 模型调用日志只保留脱敏文本,不保留原始音视频;
- 外部 LLM API 调用前对文本做脱敏处理,避免把患者隐私发送到外部服务。
如果业务涉及数据传输和存储,还需要考虑本地化部署方案,把数据留在受控区域内,并做访问审批留痕。
8.3 医生兜底与人工复核机制
不管 AI 建议看起来多准确,最终必须保留医生确认环节。产品设计上建议做到:
- AI 建议卡片不直接写“诊断结果”,而写“参考方向”;
- 医生端有“采纳”“忽略”“标记错误”三个操作,用于反馈 AI 质量;
- 对高危风险提示,UI 上使用不同颜色强调,并提醒医生确认,但不能阻断问诊流程;
- 定期抽检 AI 建议和医生最终病历的一致性,发现系统性问题后回炉优化模型或知识库。
把医生标记为“错误”的样本收集起来,是最有价值的评测数据。这类数据反映了模型在真实场景中的短板,比任何公开数据集都更能指导迭代。
8.4 日志与审计
医疗 AI 系统的审计要求比普通系统严格。每次 AI 推理建议都建议记录以下字段:
{ "session_id": "问诊会话ID", "request_time": "2025-01-01T10:00:00Z", "input": { "dialogue_text": "脱敏后的对话文本", "video_observation": "视频观察结果", "kbs": ["知识库条目ID列表"] }, "output": "模型输出的 JSON 提示", "model_version": "v1.2.0", "latency_ms": 3210 }审计日志不能只记录最后输出,还要记录检索用到了哪些知识库条目,方便追溯 AI 建议的依据。模型版本也要记录,因为大模型服务端经常更新,同一个问题在不同版本下可能有不同回答,没有版本信息,问题排查会非常困难。
9. 总结与下一步实践方向
从技术角度看,“专家级医疗 AI 实时视频问诊”本质上不是一个单一模型问题,而是一个多模态实时系统工程。本文拆解了音视频接入、语音识别、视频帧理解、知识检索、医学推理、结果反馈这六层链路,并给出了一个可直接运行的辅助问诊原型。核心代码可以迁移到真实 WebRTC 问诊系统,重点不在代码本身,而在链路协作方式:ASR 实时出文本、RAG 提供依据、LLM 做结构化推理、医生做最终决策。
如果要在真实项目中继续深入,建议按这个顺序推进:先上线“转写 + 结构化病历摘要”,让医生减少打字负担;再加入“RAG 知识提示”,提升回答的权威性;最后再逐步加入多模态视频体征分析,并且每一步都建立医生反馈闭环。风险上优先关注数据合规、模型幻觉和延迟体验,这三块不过关,医学准确率再高也很难真正落地。
下一阶段值得关注的方向是端侧小模型与云端大模型的协同,例如用端侧 ASR 做低延迟转写、云端 LLM 做深度推理;以及基于视频连续帧的行为分析,而不只是逐帧图片判断。希望这篇文章能帮你在实时医疗 AI 的工程落地上少踩一些坑。如果对你有帮助,可以收藏备用,实战中遇到具体问题也欢迎在评论区交流。
