尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

开源大模型医疗问答落地:基于Qwen2.5与RAG构建知识库助手

开源大模型医疗问答落地:基于Qwen2.5与RAG构建知识库助手 这次我们来看一个把开源大模型用到医疗知识问答场景的完整落地案例基于 Qwen2.5-14B-Instruct 构建通义医疗大模型问答助手先把病理学、诊疗指南这类垂直语料做成向量知识库再通过 RAG 检索增强生成方式接进大模型最后用 FastAPI 把能力暴露成 HTTP 接口方便批量测试和后续集成。这事的重点不在于“跑通一个模型”而在于把开源模型变成真正可用的医疗知识问答工具什么时候该搜知识库什么时候让模型直接回答检索结果怎么拼进 Prompt接口怎么设计才能同时支持单条问答和批量评估。下面直接给一套可复用的技术方案。1. 核心能力速览能力项说明模型选择Qwen2.5-14B-Instruct中文医学问答场景下效果好于同量级通用模型部署方式Ollama 拉取模型 Python 服务端也可替换为 Transformers 本地推理知识库构建医学教材、诊疗指南、病理学分类等语料切分后写入 ChromaDB 向量库问答链路RAG向量检索 Top-K LLM 生成 引用来源拼接接口服务FastAPI 提供/ask、/health、/batch三个接口硬件要求建议 24GB 显存运行量化版 14B 模型CPU 模式也能跑但速度明显下降批量任务支持从 JSONL 文件读取问题批量评测结果导出为 CSV使用边界仅供医学信息检索与知识辅助不能替代执业医师诊断从部署到验证全套流程都在本地闭环不依赖任何云端推理服务材料、代码、模型全部可控。2. 适用场景与合规边界先说明适合什么场景。医疗大模型问答助手不是给患者“看病”而是给医学内容生产者、医学生、临床科研人员做知识整理辅助。比如把病理学讲义整理成结构化问答对把某类疾病诊疗指南检索出来并做摘要把 WHO 分类标准和 ICD 编码之间的对应关系做成查询接口。这些场景下大模型加 RAG 的价值是真实存在的。不适合的场景也讲清楚不要面向公众提供未审核的诊断建议不要用模型输出直接替代检验报告解读不要拿它做药物剂量计算。原因很简单开源模型存在幻觉垂直知识库只能降低幻觉概率不能消除幻觉。做医疗相关系统时知识库内容必须经过医学专业人员审核输出必须带引用来源系统必须明确标识“AI 辅助信息仅供参考”。版权和隐私边界同样重要。医学教材、诊疗指南、数据库内容往往有版权保护自建知识库时应优先使用已授权语料、公开的脱敏病例、开源医学数据集。患者数据一律不允许进入本地知识库和模型上下文如果系统最终要接入医院业务必须走完整的等保、伦理审批和信息安全评估流程。合规不是文章末尾补一句免责声明就结束而是在架构设计阶段就要考虑。3. 环境准备与模型选择3.1 硬件和系统要求14B 模型的开源版本推理最稳妥的是 24GB 显存显卡加载 4bit 量化后在 16GB 显存附近可跑但长上下文和并发请求会明显吃紧。CPU 推理可用但速度要低一个数量级适合只想验证流程的场景。磁盘方面基础依赖加模型文件建议预留 60GB 以上空间。系统使用 Linux 服务器最省事本文命令基于 Ubuntu 22.04 Conda 给出。macOS 也可以跑但 Ollama 对 Apple Silicon 支持比较成熟整体流程差异不大。3.2 安装依赖先创建 Python 环境并安装核心依赖。conda create -n medical_qa python3.10 -y conda activate medical_qa pip install --upgrade pip pip install fastapi uvicorn langchain langchain-community chromadb sentence-transformers pandas openpyxl requests pydantic依赖项说明langchain负责 RAG 流程编排chromadb是向量存储sentence-transformers做文本向量化fastapi提供接口服务。版本建议使用较新的稳定版避免老版本 API 不兼容问题。3.3 拉取模型Ollama 方式最直接一条命令拉取模型并常驻为本地服务。ollama pull qwen2.5:14b-instruct-q4_K_M ollama serveOllama 默认监听 11434 端口后续 Python 代码通过http://127.0.0.1:11434访问模型接口。如果要用 Transformers 做更精细的采样控制和 LoRA 微调则用transformers结合accelerate加载模型本文以 Ollama 接入为主但接口调用层留了抽象替换成本不大。# 测试 Ollama 服务是否可用 import requests resp requests.post( http://127.0.0.1:11434/api/generate, json{model: qwen2.5:14b-instruct-q4_K_M, prompt: 你好, stream: False} ) print(resp.json().get(response, )[:200])能看到正常回复说明模型服务没问题可以进入知识库构建阶段。4. 本地医学知识库构建4.1 语料整理知识库质量决定问答质量模型再强也救不了混乱的语料。我把参考语料分为三类结构化权威内容比如诊疗指南、药品说明书、病理学分类标准教材章节比如病理学各论问答对比如从题库整理出的“问题-标准答案”。语料整理成统一格式每一条记录包含content和source两个字段source是来源标注后续答案会带着来源一起返回。documents [ { content: WHO中枢神经系统肿瘤分类将脑膜瘤分为WHO 1级、2级和3级其中大多数脑膜瘤为WHO 1级属于良性肿瘤。, source: WHO CNS Tumor Classification (2021) }, { content: 肺腺癌的常用免疫组化标志物包括TTF-1、Napsin A、CK7其中TTF-1在肺腺癌中阳性率较高。, source: 病理学教材第12章 } ]4.2 文本切分长文本必须切分。切分策略影响检索效果太短导致语义不完整太长导致向量表示被无关内容稀释。推荐先按段落切再用滑窗补充上下文。from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, separators[\n\n, \n, 。, ] ) all_chunks [] for doc in documents: chunks splitter.split_text(doc[content]) for chunk in chunks: all_chunks.append({text: chunk, source: doc[source]}) print(f切分后文本块数量: {len(all_chunks)})医学文本里句子比较长分隔符要放。和只按换行切会把大段内容切碎。4.3 向量化并写入 ChromaDB向量化模型选择很关键。通用向量模型对医学专业术语支持有限优先选择在中文语料上训练且对领域词汇友好的模型比如BAAI/bge-m3。如果确实没有条件下载 embedding 模型也可以用text2vec-large-chinese这类通用中文模型做兜底但要意识到医学实体召回会有损失。from chromadb import PersistentClient from chromadb.utils import embedding_functions client PersistentClient(path./medical_kb_chroma) embedding_fn embedding_functions.SentenceTransformerEmbeddingFunction( model_nameBAAI/bge-m3 ) collection client.get_or_create_collection( namemedical_kb, embedding_functionembedding_fn ) ids [fchunk_{i} for i in range(len(all_chunks))] documents [c[text] for c in all_chunks] metadatas [{source: c[source]} for c in all_chunks] # 如果集合已有数据先清空再写入避免重复 collection.delete(where{}) collection.add( idsids, documentsdocuments, metadatasmetadatas ) print(f向量库已写入 {len(ids)} 条记录)写入完成后可以跑一次检索验证召回效果。query 肺腺癌的免疫组化标志物有哪些 results collection.query(query_texts[query], n_results3) for idx, doc in enumerate(results[documents][0]): meta results[metadatas][0][idx] print(f--- Top {idx1} ---) print(f来源: {meta[source]}) print(doc[:150]) print()如果检索出的内容和问题明显不相关优先检查切分粒度、embedding 模型和语料质量而不是先调 Prompt。5. RAG 问答流程实现5.1 基础 Prompt 设计医疗场景的 Prompt 要同时约束三个点只根据检索内容回答、不编造、引用来源。基础模板如下。SYSTEM_PROMPT 你是一名严谨的医学知识助手。你的任务是基于提供的医学参考资料回答用户问题。 规则 1. 优先使用参考资料中的信息作答如果资料不完整必须说明信息不足。 2. 禁止编造诊断、药物剂量、治疗建议。 3. 输出结尾需要列出引用来源。 4. 如果问题不在你的知识范围内明确回答“暂无法基于现有知识库回答”。 参考资料 {context} 用户问题 {question} 5.2 检索增强生成问答函数先检索向量库再把结果拼进 Prompt最后调用 Ollama。import requests OLLAMA_URL http://127.0.0.1:11434/api/generate MODEL_NAME qwen2.5:14b-instruct-q4_K_M def build_context(query: str, top_k: int 3): results collection.query(query_texts[query], n_resultstop_k) chunks [] sources [] for doc, meta in zip(results[documents][0], results[metadatas][0]): chunks.append(doc) if meta.get(source) not in sources: sources.append(meta[source]) context \n\n.join([f[{i1}] {c} for i, c in enumerate(chunks)]) return context, sources def ask_medical_llm(question: str, top_k: int 3): context, sources build_context(question, top_k) prompt SYSTEM_PROMPT.format(contextcontext, questionquestion) resp requests.post( OLLAMA_URL, json{ model: MODEL_NAME, prompt: prompt, stream: False, options: {temperature: 0.2, max_tokens: 1024} }, timeout180 ) answer resp.json().get(response, ).strip() return { question: question, answer: answer, sources: sources }温度设置为 0.2 是为了压低随机性。医疗问答和创意写作不同输出稳定性优先。5.3 无知识库兜底本地知识库不可能覆盖所有问题。对知识库检索不到相关内容的查询直接把问题交给模型但模型要用更保守的系统提示词。FALLBACK_SYSTEM_PROMPT 你是一名医学信息整理助手。你可以基于常识回答通识性医学问题但必须注明“此为AI生成信息仅供参考”。 涉及具体诊断、用药、治疗方案时必须建议用户咨询执业医师。 def ask_fallback_llm(question: str): prompt f{FALLBACK_SYSTEM_PROMPT}\n\n用户问题{question} resp requests.post( OLLAMA_URL, json{model: MODEL_NAME, prompt: prompt, stream: False}, timeout180 ) return resp.json().get(response, ).strip()判断是否走兜底逻辑可以在检索阶段设定相似度阈值比如 Top-1 分数低于 0.45 就视为无相关材料。阈值以实际语料表现为准没有统一的绝对标准。6. 部署为 FastAPI 服务6.1 接口设计服务端提供三个接口健康检查、单条问答、批量问答。批量接口直接读取 JSONL 文件避免一次请求塞太多文本导致超时。from fastapi import FastAPI, HTTPException from pydantic import BaseModel import uvicorn app FastAPI(titleMedical QA API) class QueryRequest(BaseModel): question: str top_k: int 3 use_rag: bool True app.get(/health) def health(): return {status: ok, model: MODEL_NAME} app.post(/ask) def ask(request: QueryRequest): if not request.question.strip(): raise HTTPException(status_code400, detailquestion 不能为空) if request.use_rag: result ask_medical_llm(request.question, request.top_k) else: answer ask_fallback_llm(request.question) result {question: request.question, answer: answer, sources: []} return result if __name__ __main__: uvicorn.run(app, host127.0.0.1, port8000)6.2 启动服务conda activate medical_qa python api_server.py服务启动后通过http://127.0.0.1:8000/docs可以直接打开 Swagger 调试页面这个很适合第一次验证接口参数格式。6.3 调用示例用 curl 或 Python requests 都能测试。curl -X POST http://127.0.0.1:8000/ask \ -H Content-Type: application/json \ -d {question: 脑膜瘤WHO分级有哪几级, top_k: 3}import requests resp requests.post( http://127.0.0.1:8000/ask, json{question: 肺腺癌常用免疫组化标志物有哪些, top_k: 3}, timeout300 ) data resp.json() print(答案:, data[answer]) print(来源:, data[sources])返回值里带sources字段这是医疗问答重要的可信度来源。没有引用来源的答案无法审计不符合医疗辅助场景要求。7. 批量评测与效果优化7.1 批量评测流程医疗问答系统上线前必须做批量评测。准备一个 JSONL 格式的测试集每行包含question和reference_answer。{question: 肾透明细胞癌最常累及的基因是什么, reference_answer: VHL基因} {question: 胃镜活检病理提示印戒细胞癌最常见好发部位是, reference_answer: 胃窦}批量评测代码逐个读取题目调用本地服务输出结果保存为 CSV。import json import csv import requests API_URL http://127.0.0.1:8000/ask def read_test_set(path: str): items [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if line: items.append(json.loads(line)) return items def run_batch(input_path: str, output_path: str): items read_test_set(input_path) results [] for idx, item in enumerate(items): print(f处理进度: {idx1}/{len(items)}) try: resp requests.post(API_URL, json{question: item[question]}, timeout300) data resp.json() results.append({ question: item[question], reference_answer: item[reference_answer], model_answer: data[answer].replace(\n, ), sources: ; .join(data[sources]) }) except Exception as e: print(f第 {idx1} 条失败: {e}) results.append({ question: item[question], reference_answer: item[reference_answer], model_answer: fERROR: {e}, sources: }) with open(output_path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[question, reference_answer, model_answer, sources]) writer.writeheader() writer.writerows(results) print(f批量评测完成结果保存至 {output_path}) if __name__ __main__: run_batch(./test_set.jsonl, ./eval_result.csv)输出 CSV 用utf-8-sig编码是为了直接 Office 打开不乱码。7.2 评估和优化方向客观题可以做关键词匹配或语义相似度打分主观题建议人工抽检。重点看三类错误知识库有对应内容但模型没检索到说明分块或 embedding 有问题检索到了但答案偏离说明 Prompt 约束不足知识库没有但模型硬答需要加强拒答逻辑。优化方向按性价比排序增加高质量语料覆盖高频问题。调整chunk_size和chunk_overlap让切块更贴合语义单元。换更强的 embedding 模型提升专业术语召回。增加对比示例 Few-shot让模型学会标准回答格式。如果预算充足用评测集在 Qwen2.5-14B 上做 LoRA 微调。RAG 系统先保证检索对再优化生成。检索不到正确答案时换 Prompt 换多少次都没用。8. 常见问题与排查方法问题现象可能原因排查方式解决方案Ollama 拉模型失败网络不通或镜像源问题检查ollama pull报错更换网络环境或手动下载模型文件导入11434 端口被占用已有 Ollama 实例在运行lsof -i:11434关闭旧进程或改用其他端口启动 FastAPI 后/docs打不开服务未启动或端口被占用查看服务日志更换端口重新启动检索结果明显不相关语料质量差或 embedding 模型不匹配打印检索出的 Top-K 内容清洗语料更换 embedding 模型模型回答没有引用来源Prompt 约束不足或模型忽略指令检查答案尾部是否包含来源在 Prompt 中新增引用来源强制要求批量任务卡死单条推理时间过长或并发过大查看服务日志和 GPU 占用降低并发数加超时重试显存不足导致服务崩溃模型量化级别和上下文长度设置过大nvidia-smi观察显存换小模型或降低上下文长度答案内容准确但格式乱模型输出未按预期结构组织打印原始输出增加输出格式约束解析逻辑单条请求超时后建议在客户端做指数退避重试不要频繁重发。批量任务里如果前几条已经超时多半不是单条问题而是模型服务整体不可用此时应停掉整个批量任务排查服务状态。9. 最佳实践与后续扩展先用最小用例验证三条链路Ollama 模型调用通不通、ChromaDB 检索到不到内容、FastAPI 接口返回格式对不对。链路通了再扩大语料和生产化改造。工程化时有几点值得留意模型和知识库分开目录管理模型文件与知识库都做版本号标记知识库更新后要重建向量集合不能原地增量写入。日志要记录question、retrieved_chunks、model_answer和response_time后续做质量分析才有数据。接口服务必须加访问限制医疗数据接口不要直接暴露到公网用内网部署加 API Key 认证。知识库内容变更后要跑一遍回归测试集确认新增语料没有让旧问题的回答质量下降。评估结果要人审尤其是主观题和涉及具体治疗建议的问题。后续可以做几个方向的扩展接语音识别做成医学语音问诊记录助手用 LoRA 对模型做医学指令微调把单机服务改成多卡部署并发推理。但每一步扩展都要重新评估合规边界特别是涉及真实患者信息时必须重新设计数据流和访问控制。这套架构的价值在于它能用开源模型和开源工具在可控预算内搭建一个领域知识问答系统。先跑通再评估最后再谈优化这是最务实的路径。建议收藏备用直接按文中流程在自己机器上验证一遍。
返回列表