Qwen3-8B 健康自诊「追问官」(follow-up conjecturer) LoRA
用于健康自诊 / 电话分诊场景的追问模型。给定患者自诊的多轮对话上下文,在患者每轮最新回复后,生成 1-3 个最可能、最有鉴别价值的开放式追问问题,用于缩小诊断范围、评估风险(危险信号/red flag),并避免重复询问患者已告知的信息。
- 基础模型:
Qwen/Qwen3-8B (使用 unsloth 4-bit 量化微调)
- 训练方式:LoRA (r=16, rslora, QLoRA 4bit) + SFT,约 2 epochs,约 3016 steps
- 语言:简体中文
能力特性
- 多轮对话输入:接受任意长度的 患者(user)/猜想官(assistant) 累积多轮对话,输出下一轮追问。
- 开放式自然语言追问:每次 1-3 个具体、可回答的临床问题(如“疼痛是钝痛还是刺痛?是否伴随发热?”)。
- 惜字如金的线上用户:专门加入了短/口语化的患者回复风格数据,模型能从容应对“疼”“两天了”“会”这类极简输入。
- 跨领域整合:含跨知识单元(multi-hop)样本,支持在多系统/多病史叠加场景下综合追问。
- 不问重复:根据多轮上下文,不重复已问或已回答的信息。
训练数据
共 约 99.5k 条多轮健康自诊追问样本(train 96.5k / eval 3k),由三个子集混合:
| 子集 |
规模 |
说明 |
| 全句式 full |
~64k |
标准多轮自诊对话,患者回复较完整 |
| 短句式 short |
~20.7k |
患者回复极简短(模拟线上打字少),锻炼稀疏输入处理 |
| 跨单元 multi-hop |
~14.8k |
患者情况同时涉及两段相关医学知识,需综合两个领域追问 |
知识来源(用于 teacher 蒸馏,不直接发布其原始文本):
- 西氏内科学精要(中文,task2)
- UpToDate 临床主题与患者教育内容(英文,task3,老师模型将其蒸馏成中文自诊对话)
数据由本地教师大模型(vLLM minimax)根据上述医学知识文本、按统一 schema 蒸馏生成,覆盖 起病/诱因、部位、性质、持续时间、加重缓解因素、伴随症状、危险信号、既往史、用药史、年龄性别、家族史 等多样鉴别维度。
生成脚本:scripts/gen_dialogues.py(全句式)、scripts/gen_short.py(短句式)、scripts/gen_multihop.py(跨单元)、scripts/build_dataset.py(组装训练集)。
避免遗忘 / 支撑 RAG 的设计
- 多轮深度可变:训练样本对话长度 2~14 条消息,强制模型记住并利用早前的患者信息,防止遗忘上文。
- multi-hop 跨单元样本:要求综合两个相关领域的知识,避免模型只盯住某一窄知识点、抛掉其他信息。
- RAG 知识库(FAISS):
rag/ 内提供对统一临床语料的稠密检索索引(BAAI/bge-small-zh-v1.5,512 维,8,079 个临床单元)。服务端可先用该索引检索当前患者陈述对应的鉴别要点/危险信号,用于提示并校验追问方向。
- 使用时建议结合 RAG 召回的相关医学知识(position/character/duration/red-flag 等)共同决定追问。
安装与推理
环境:uv pip install unsloth trl;GPU(量化推理)。
from unsloth import FastLanguageModel
BASE = "unsloth/qwen3-8b"
LORA = "chenhaodev/qwen3-8b-followup-lora"
model, tokenizer = FastLanguageModel.from_pretrained(
BASE, max_seq_length=2048, dtype=None, load_in_4bit=True)
model.load_adapter(LORA) # LoRA 权重
def generate_followups(history):
# history: list[{"role":"user"|"assistant","content":...}] 累积多轮对话
text = tokenizer.apply_chat_template(
history, tokenize=False, add_generation_prompt=True, enable_thinking=False)
enc = tokenizer(text, return_tensors="pt").to("cuda")
out = model.generate(input_ids=enc.input_ids, attention_mask=enc.attention_mask,
max_new_tokens=220, temperature=0.7, top_p=0.9, top_k=40)
return tokenizer.decode(out[0][enc.input_ids.shape[1]:], skip_special_tokens=True)
history = [
{"role": "user", "content": "我父亲心梗去世过了,我最近走路快了就胸口闷,有点发紧。"},
{"role": "assistant", "content": "这种发紧感一般持续多久?休息后能完全消失吗?"},
{"role": "user", "content": "大概两三分钟,停下歇会就好了。"},
]
print(generate_followups(history))
# 期望输出示例:是否往左肩、后背放射?有没有出冷汗、恶心或呕吐?
RAG 检索(支撑追问)
双语检索设计:知识来源混合中文(task2 西氏内科)与英文(task3 UpToDate)。中文患者主诉若直接在英文原文上检索,task3 内容(占语料约 70%)几乎无法命中。故索引采用中文检索表示:
- index.faiss + docs_retrieval.json:中文检索向量/文本(task2=原中文章节;task3=由该单元蒸馏出的中文自诊对话作为检索表示),用于与中文 query 匹配。
- docs.json:原文/支撑文本(中文或英文原文),供展示与追问的 grounding。
- doc_meta.json / meta.json:条目元数据与模型信息。
- 四个文件按同一顺序对齐(search_rag.py 启动时校验其数量一致性)。
uv pip install faiss-cpu sentence-transformers
# 默认展示“原文/支撑”,用于引导追问
python scripts/search_rag.py "胸口发紧,走路快了就闷,父亲有心梗史" -k 5 --ragdir rag
# 查看命中单元的中文检索表示
python scripts/search_rag.py "心慌,手抖,怕热" -k 3 --ragdir rag --show retrieval
返回与当前主诉最相关的知识单元(含部位/性质/持续时间/危险信号等鉴别要点),供追问官结合上下文生成更有针对性的问题。CLI 支持 -k(返回条数)、--ragdir、--show {grounding,retrieval,both}。
llama.cpp + RAG 联动
精简版(详见 LMPC_RAG.md、merge_to_gguf.py、rag_llm_query.py):
# 1) LoRA -> GGUF
uv run python scripts/merge_to_gguf.py # 合并成 HF bf16
cd <llama.cpp>
python3 convert_hf_to_gguf.py output/merged_zhuìwèn --outfile qwen3-8b-zhuìwèn-f16.gguf
./llama-quantize qwen3-8b-zhuìwèn-f16.gguf qwen3-8b-zhuìwèn-Q4_K_M.gguf Q4_K_M
# 2) 启动 OpenAI 兼容服务
./llama-server -m qwen3-8b-zhuìwèn-Q4_K_M.gguf --host 127.0.0.1 --port 8080 --jinja -c 4096
# 3) RAG 检索 + 组装 prompt + 追问
uv run python scripts/rag_llm_query.py "胸口发紧,走路快了就闷,父亲有心梗史" -k 5
核心:llama.cpp 只跑模型;RAG 是生成前的检索环节,把中文检索索引命中的临床知识(部位/性质/时长/危险信号)与多轮对话历史拼进 prompt,再请求 llama-server 出 1-3 个开放式追问。
免责声明
本模型仅用于辅助健康信息参考与自诊询问方向,不构成医疗诊断或治疗建议。请勿将其输出作为最终临床判断;出现胸痛、呼吸困难、意识障碍、大出血等危险信号应及时就医。开发者与发布者对基于模型输出所作的任何医疗决策不承担责任。
版权说明
- 上传内容:LoRA 权重、训练/推理脚本、RAG 索引、模型卡。
- 未上传:从受版权保护的书籍(西氏内科)与 UpToDate 原文直接派生的训练数据(遵循其许可要求)。训练数据仅用于本地微调,不对外发布。