RAG入门(一)用Qdrant和ollama做一个简单的医学问答软件

目录

[1. 下载医学答问库](#1. 下载医学答问库)

[📊 主要中文医疗数据集对比](#📊 主要中文医疗数据集对比)

[2. 安装ollama](#2. 安装ollama)

[💡 安装时留意一下](#💡 安装时留意一下)

[2.1 ollama + 关键字搜索 + 整理](#2.1 ollama + 关键字搜索 + 整理)

[3. 下载向量计算模型](#3. 下载向量计算模型)

[下载脚本(推荐用 ModelScope)](#下载脚本(推荐用 ModelScope))

[备选:用 Hugging Face 镜像站下载](#备选:用 Hugging Face 镜像站下载)

[4. 下载Qdrant](#4. 下载Qdrant)

[🛠️ 使用方法(解压即用)](#🛠️ 使用方法(解压即用))

[5. 导入数据](#5. 导入数据)

[6. 测试问答](#6. 测试问答)

[7. 总结](#7. 总结)

[RAG 的标准骨架](#RAG 的标准骨架)

[RAG 各环节的通用叫法](#RAG 各环节的通用叫法)

[但 RAG 不止这一种形态](#但 RAG 不止这一种形态)

关于重排

可选方案对比

[为什么推荐 BGE-Reranker](#为什么推荐 BGE-Reranker)

重排的实际工作方式


1. 下载医学答问库

📊 主要中文医疗数据集对比

下面的表格整理了几个当前主流且各有侧重的数据集,可以根据自己的需求来判断。

数据集 规模 特点与适用场景 数据来源/质量
Huatuo-26M 2600万 QA对 规模最大 的中文医疗问答数据集。适合用于预训练或需要海量知识注入的场景。 整合了在线问诊记录、医学百科和知识库,覆盖面广但质量可能参差不齐。
MedicalQA (MedArk-KI) 约146万 高质量、结构化的SFT数据集 。适合监督微调(SFT) 或作为RAG的检索库,数据经过清洗和审核。 包含多个子集,其中"DX"子集来自丁香医生,由医生撰写并交叉审核,质量很高。
TCM-Ladder 5.2万+ 首个中医多模态评估基准 。适合评估中医大模型的能力,覆盖文本、图像、视频。 涵盖中医核心学科,包含选择题、填空、诊断对话等多种任务类型,用于评测而非训练。
JMED 1千个问题 高难度、贴近真实的临床推理数据集 。每个问题有21个回答选项 ,适合评估模型的鉴别诊断和复杂推理能力。 源自京东大医院的真实会诊数据,更能反映患者描述的模糊性和临床诊断的动态过程。
CMB / CMExam 28万+/6.8万 中文医学考试基准。适合评估模型在标准化医学知识上的掌握程度。 基于中国执业医师/护士等资格考试题目构建,是衡量模型基础医学知识的权威标准之一。
  • 如果想构建RAG的检索库 :MedicalQA 是一个很好的起点。它的数据质量高,结构清晰,非常适合作为知识库来检索。

  • 如果目标是微调一个医疗对话模型 :Huatuo-26M 提供了海量数据用于预训练,之后可以再用 MedicalQA 的高质量数据进行指令微调,这是一个常见的"预训练+微调"组合。

  • 如果想专门评估中医或临床推理能力 :那么 TCM-Ladder 或 JMED 这类专门设计的基准测试,比通用问答数据集更有针对性。

这里使用一个脚本来下载模型:

复制代码
 from huggingface_hub import snapshot_download
 snapshot_download(
     repo_id="Bolin97/MedicalQA",
     repo_type="dataset",
     local_dir="./MedicalQA",
     endpoint="https://hf-mirror.com"
 )
 ​

下载后,里面是一组JSON文件。

2. 安装ollama

Ollama 的官方下载地址是:Download Ollama on macOS 。

打开后它会自动识别你的系统,直接下载 Windows 版的 OllamaSetup.exe 即可 。

💡 安装时留意一下

安装包默认会装在 C 盘用户目录下,软件本身不大(约 4GB 空间),但后续下载的模型文件会很大(几 GB 到几十 GB)。

如果你 C 盘空间紧张,可以在安装时用命令行指定路径,比如装在 D 盘:

复制代码
 OllamaSetup.exe /DIR="D:\Ollama"

或者装完后,把模型存储位置改到别的盘(通过设置 OLLAMA_MODELS 环境变量)。

在chat页面输入

复制代码
 qwen2.5:7b

下载模型后就能聊天了。

2.1 ollama + 关键字搜索 + 整理

最简单的方式是让模型针对用户的提问转为关键字,用关键字搜索,喂给模型,让模型给出合适的答案:

复制代码
 import json
 import pandas as pd
 import requests
 import os
 ​
 # ============ 配置 ============
 OLLAMA_URL = "http://localhost:11434/api/chat"
 MODEL = "qwen2.5:7b"
 DATA_DIR = "./MedicalQA"
 ​
 # 选择加载哪些子集,HT 太大,默认先关掉
 LOAD_DX = True
 LOAD_TCM = True
 LOAD_MB = True
 LOAD_HT = False        # 想吃 HT 就改 True,但很占内存
 ​
 MAX_ANSWER_LEN = 1500
 MAX_CONTEXT_ROWS = 20   # 喂给模型的上限,防止上下文爆掉
 MAX_KEYWORDS = 8        # 关键词最多几个
 ​
 ​
 # ============ 1. 加载数据 ============
 frames = []
 if LOAD_DX:
     with open(os.path.join(DATA_DIR, "DX.json"), "r", encoding="utf-8") as f:
         frames.append(pd.DataFrame(json.load(f)))
 if LOAD_TCM:
     with open(os.path.join(DATA_DIR, "TCM.json"), "r", encoding="utf-8") as f:
         frames.append(pd.DataFrame(json.load(f)))
 if LOAD_MB:
     with open(os.path.join(DATA_DIR, "MB.json"), "r", encoding="utf-8") as f:
         frames.append(pd.DataFrame(json.load(f)))
 if LOAD_HT:
     with open(os.path.join(DATA_DIR, "HT.json"), "r", encoding="utf-8") as f:
         frames.append(pd.DataFrame(json.load(f)))
 ​
 df = pd.concat(frames, ignore_index=True)
 print(f"共加载 {len(df)} 条记录")
 ​
 # 统一列名(HT 的字段可能不同,需要时再适配)
 print("列名:", df.columns.tolist())
 ​
 ​
 # ============ 2. 调用 Ollama ============
 def call_ollama(system_prompt, user_prompt):
     payload = {
         "model": MODEL,
         "messages": [
             {"role": "system", "content": system_prompt},
             {"role": "user", "content": user_prompt},
         ],
         "stream": False,
     }
     resp = requests.post(OLLAMA_URL, json=payload)
     resp.raise_for_status()
     return resp.json()["message"]["content"]
 ​
 ​
 # ============ 3. 抽取关键词 ============
 KEYWORD_SYSTEM = """你是一个医疗检索助手。用户会用口语描述症状,你需要把它转成适合在医学问答库里检索的关键词。
 ​
 要求:
 1. 提取出最可能的疾病名、症状名、部位、诱因等关键词。
 2. 输出 3-8 个关键词,用中文顿号「、」分隔。
 3. 只输出关键词本身,不要解释,不要加任何其他文字。
 4. 如果涉及多种可能,把可能的病名也列出来。
 ​
 示例:
 输入:我被猫抓了,长了一个疙瘩,医生说不是HPV,是瘢痕
 输出:猫抓、疙瘩、瘢痕、疤痕、HPV"""
 ​
 ​
 def extract_keywords(question):
     raw = call_ollama(KEYWORD_SYSTEM, question).strip()
     keywords = [k.strip() for k in raw.replace(",", "、").split("、") if k.strip()]
     return keywords[:MAX_KEYWORDS]
 ​
 ​
 # ============ 4. 多关键词检索(返回全部命中) ============
 # def search_by_keywords(keywords):
 #     hits = []
 #     seen_ids = set()
 ​
 #     for kw in keywords:
 #         mask = (
 #             df["question"].str.contains(kw, na=False, regex=False)
 #             | df["answer"].str.contains(kw, na=False, regex=False)
 #             | df["name"].str.contains(kw, na=False, regex=False)
 #         )
 #         for _, row in df[mask].iterrows():
 #             if row["id"] not in seen_ids:
 #                 seen_ids.add(row["id"])
 #                 hits.append(row)
 ​
 #     return pd.DataFrame(hits)
 ​
 PER_KEYWORD_TOP = 10   # 每个关键词取前几条
 ​
 def search_by_keywords(keywords, per_keyword=PER_KEYWORD_TOP):
     all_rows = []
     seen_ids = set()
 ​
     for kw in keywords:
         mask = (
             df["question"].str.contains(kw, na=False, regex=False)
             #| df["answer"].str.contains(kw, na=False, regex=False)
             #| df["name"].str.contains(kw, na=False, regex=False)
         )
         matched = df[mask]
         print(f"  「{kw}」命中 {len(matched)} 条")
 ​
         # 每个关键词只取前 N 条
         for _, row in matched.head(per_keyword).iterrows():
             if row["id"] not in seen_ids:
                 seen_ids.add(row["id"])
                 all_rows.append(row)
 ​
     return pd.DataFrame(all_rows)
 ​
 ​
 # ============ 5. 生成回答 ============
 ANSWER_SYSTEM = """你是一个严谨的医疗信息助手。请基于提供的「参考资料」回答用户问题。
 ​
 规则:
 1. 只使用参考资料中的信息,不要引入资料之外的知识。
 2. 如果资料不足以回答,明确说明「根据现有资料无法回答」,不要猜测。
 3. 如果用户的描述和资料中的疾病不一致,可以指出差异,但不要下诊断结论。
 4. 不要给具体用药剂量,涉及治疗提醒用户咨询医生。
 5. 最后注明:以上信息仅供参考,不能替代专业医疗诊断。"""
 ​
 ​
 def build_context(rows, limit=MAX_CONTEXT_ROWS):
     rows = rows.head(limit)
     blocks = []
     for _, row in rows.iterrows():
         ans = str(row["answer"])[:MAX_ANSWER_LEN]
         blocks.append(
             f"【病名】{row.get('name', '')}  【科室】{row.get('department', '')}\n"
             f"问:{row['question']}\n"
             f"答:{ans}"
         )
     return "\n\n".join(blocks)
 ​
 ​
 def answer_question(question, rows):
     context = build_context(rows)
     user_prompt = f"""参考资料:
 {context}
 ​
 用户问题:{question}"""
     return call_ollama(ANSWER_SYSTEM, user_prompt)
 ​
 ​
 # ============ 6. 主流程 ============
 if __name__ == "__main__":
     question = input("请输入你的问题:")
 ​
     print("\n[1] 抽取关键词...")
     keywords = extract_keywords(question)
     print("关键词:", "、".join(keywords))
 ​
     print("\n[2] 检索...")
     rows = search_by_keywords(keywords)
     print(f"命中 {len(rows)} 条")
     if not rows.empty:
         print(rows[["id", "name", "question"]].head(30).to_string(index=False))
 ​
     if rows.empty:
         print("\n没检索到相关资料。")
     else:
         print(f"\n[3] 从 {len(rows)} 条中取前 {MAX_CONTEXT_ROWS} 条喂给模型...\n")
         print("=" * 50)
         print(answer_question(question, rows))
         print("=" * 50)

但是经常会关键字会搜索到不相关的东西,主要是关键字无法代表语义。

那么我需要向量方式识别用户输入的含义。

3. 下载向量计算模型

复制代码
 pip install modelscope

国内网络连不上 Hugging Face,最直接的解决办法是走国内镜像站下载。

下载脚本(推荐用 ModelScope)

ModelScope 是阿里达摩院的国内模型社区,CDN 速度很稳定,而且 BAAI/bge-small-zh-v1.5 在 ModelScope 上也有官方同步的版本。

复制代码
 # download_model.py
 import os
 os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"  # 关键:必须在 import 之前设置
 ​
 from modelscope import snapshot_download
 ​
 model_dir = snapshot_download(
     "BAAI/bge-small-zh-v1.5",
     cache_dir="./models",      # 下载到这里
 )
 print("模型已下载到:", model_dir)

先安装依赖:

复制代码
 pip install modelscope

然后运行 python download_model.py。下载完成后,模型文件会出现在 ./models/BAAI/bge-small-zh-v1.5 目录下。

备选:用 Hugging Face 镜像站下载

如果更习惯 Hugging Face 生态,也可以用 hf-mirror.com。

关键是环境变量必须在 sentence_transformers 被导入之前设置,否则库在导入阶段就已经初始化了官方地址,后面再改就晚了。

复制代码
 # download_model_hf.py
 import os
 os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"  # 必须在所有 HF 相关 import 之前
 ​
 from huggingface_hub import snapshot_download
 ​
 model_dir = snapshot_download(
     repo_id="BAAI/bge-small-zh-v1.5",
     local_dir="./models/bge-small-zh-v1.5",
     local_dir_use_symlinks=False,
 )
 print("模型已下载到:", model_dir)

4. 下载Qdrant

Qdrant 官方为 Windows 提供了预编译的压缩包,在 GitHub Releases 页面下载即可。

  • 官方发布页 :https://github.com/qdrant/qdrant/releases

  • 具体文件 :在该页面的 Assets 区域,找到并下载 qdrant-x86_64-pc-windows-msvc.zip。这就是 Windows 64 位版本的免安装包。

🛠️ 使用方法(解压即用)

下载后不需要安装,解压就能跑。

1. 解压与启动 将下载的 zip 压缩包解压到一个你喜欢的目录(比如 D:\ProgramData\Qdrant)。然后在这个文件夹里打开命令行(PowerShell 或 CMD),运行:

复制代码
 ./qdrant.exe

程序启动后,会默认在当前目录下创建一个 storage 文件夹来存放你的向量数据。

2. 验证与访问 启动成功后,打开浏览器访问 http://localhost:6333/dashboard,就能看到 Qdrant 的管理界面了。它的 HTTP API 默认监听 6333 端口,gRPC API 监听 6334 端口。

5. 导入数据

qdrant是可以本地使用的,这里使用服务方式,更接近真实的用法:

复制代码
 import json
 import os
 import pandas as pd
 from sentence_transformers import SentenceTransformer
 from qdrant_client import QdrantClient
 from qdrant_client.models import Distance, VectorParams, PointStruct
 ​
 # ============ 配置 ============
 DATA_DIR = "./MedicalQA"
 COLLECTION = "medical_qa"
 ​
 VECTOR_DIM = 512
 BATCH_SIZE = 64           # CPU 上 batch 太大反而慢,64 比较合适
 ​
 # EMBED_MODEL = "BAAI/bge-small-zh-v1.5"
 EMBED_MODEL = r"E:\2026work\models\BAAI--bge-small-zh-v1.5\snapshots\master"
 ​
 ​
 FILES = {
     "DX.json": True,  # 53554 条
     "TCM.json": True, # 38294 条
     # "MB.json": True, # 14543 条
     # "HT.json": True,   #  1358093 条 # 1.9GB,内存够再开
 }
 ​
 # ============ 1. 加载数据 ============
 frames = []
 for fname, enabled in FILES.items():
     if not enabled:
         continue
     path = os.path.join(DATA_DIR, fname)
     if not os.path.exists(path):
         print(f"跳过(不存在): {fname}")
         continue
     with open(path, "r", encoding="utf-8") as f:
         data = json.load(f)
     df_part = pd.DataFrame(data)
     df_part["source_file"] = fname
     frames.append(df_part)
     print(f"已加载 {fname}: {len(df_part)} 条")
 ​
 df = pd.concat(frames, ignore_index=True)
 print(f"\n合计 {len(df)} 条")
 print("列名:", df.columns.tolist())
 ​
 # ============ 2. 加载 embedding 模型(CPU) ============
 # model = SentenceTransformer(EMBED_MODEL, device="cpu")
 model = SentenceTransformer(EMBED_MODEL, device="cpu")
 print(f"embedding 模型已加载: {EMBED_MODEL}, 维度 {VECTOR_DIM}, 设备: CPU")
 ​
 # ============ 3. 初始化 Qdrant(连接本机服务) ============
 client = QdrantClient(host="localhost", port=6333)
 ​
 try:
     client.get_collections()
     print("已连接本机 Qdrant")
 except Exception as e:
     print(f"无法连接 Qdrant,请确认 qdrant.exe 已启动: {e}")
     raise
 ​
 existing = [c.name for c in client.get_collections().collections]
 if COLLECTION in existing:
     print(f"集合 {COLLECTION} 已存在,删除重建...")
     client.delete_collection(COLLECTION)
 ​
 client.create_collection(
     collection_name=COLLECTION,
     vectors_config=VectorParams(size=VECTOR_DIM, distance=Distance.COSINE),
 )
 print(f"集合 {COLLECTION} 已创建")
 ​
 # ============ 4. 拼接用于 embed 的文本 ============
 def build_text(row):
     return f"{row['question']}"
 ​
 texts = df.apply(build_text, axis=1).tolist()
 ​
 # ============ 5. 分批 embed + 写入 ============
 total = len(texts)
 point_id = 0
 ​
 for start in range(0, total, BATCH_SIZE):
     end = min(start + BATCH_SIZE, total)
     batch_texts = texts[start:end]
     batch_rows = df.iloc[start:end]
 ​
     embeddings = model.encode(
         batch_texts,
         normalize_embeddings=True,
         show_progress_bar=False,
         batch_size=BATCH_SIZE,
     )
 ​
     points = []
     for i, (_, row) in enumerate(batch_rows.iterrows()):
         points.append(
             PointStruct(
                 id=point_id,
                 vector=embeddings[i].tolist(),
                 payload={
                     "qa_id": str(row.get("id", "")),
                     "name": str(row.get("name", "")),
                     "department": str(row.get("department", "")),
                     "question": str(row["question"]),
                     "answer": str(row["answer"]),
                     "source_file": str(row.get("source_file", "")),
                 },
             )
         )
         point_id += 1
 ​
     client.upsert(collection_name=COLLECTION, points=points)
     print(f"已写入 {end}/{total}")
 ​
 print("\n全部完成")
 print(f"集合 {COLLECTION} 共 {client.count(COLLECTION).count} 条")
 ​
 ​

用CPU计算比较慢

复制代码
 全部完成
 集合 medical_qa 共 91848 条
 (qlib312) PS E:\2026work> & C:\ProgramData\miniconda3\envs\qlib312\python.exe e:/2026work/query_qdrant.py
 Loading weights: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████| 71/71 [00:00<00:00, 2354.45it/s]
 集合 medical_qa 共 91848 条

6. 测试问答

写一个问答测试,这里不使用ollama了

复制代码
 # query_qdrant.py
 from sentence_transformers import SentenceTransformer
 from qdrant_client import QdrantClient
 ​
 # ============ 配置 ============
 EMBED_MODEL = r"E:\2026work\models\BAAI--bge-small-zh-v1.5\snapshots\master"
 COLLECTION = "medical_qa"
 TOP_K = 10
 ​
 # ============ 加载模型和客户端 ============
 model = SentenceTransformer(EMBED_MODEL, device="cpu")
 client = QdrantClient(host="localhost", port=6333)
 ​
 print(f"集合 {COLLECTION} 共 {client.count(COLLECTION).count} 条")
 print("=" * 60)
 ​
 # ============ 循环查询 ============
 while True:
     question = input("\n请输入问题(输入 q 退出):").strip()
     if question.lower() == "q":
         break
     if not question:
         continue
 ​
     # 生成查询向量
     q_vec = model.encode(
         [question],
         normalize_embeddings=True,
     )[0].tolist()
 ​
     # 检索
     hits = client.query_points(
         collection_name=COLLECTION,
         query=q_vec,
         limit=TOP_K,
     ).points
 ​
     print(f"\n检索到 {len(hits)} 条:\n")
     for i, h in enumerate(hits, 1):
         p = h.payload
         print(f"[{i}] 相似度 {h.score:.4f} | 病名:{p.get('name', '')} | 科室:{p.get('department', '')}")
         print(f"    问:{p.get('question', '')}")
         print(f"    答:{p.get('answer', '')[:150]}...")
         print()
 ​
 # ============ 可选:把检索结果拼成上下文喂给 Ollama ============
 def build_context(hits, max_rows=5, max_len=1500):
     blocks = []
     for h in hits[:max_rows]:
         p = h.payload
         ans = str(p.get("answer", ""))[:max_len]
         blocks.append(
             f"【病名】{p.get('name', '')}  【科室】{p.get('department', '')}\n"
             f"问:{p.get('question', '')}\n"
             f"答:{ans}"
         )
     return "\n\n".join(blocks)

测试一下:

复制代码
 请输入问题(输入 q 退出):我被猫抓了,长了一个疙瘩,医生说不是HPV,是瘢痕
 ​
 检索到 10 条:
 ​
 [1] 相似度 0.6848 | 病名:瘢痕疙瘩 | 科室:皮肤科
     问:瘢痕疙瘩怎么诊断?需要做检查吗?
     答:根据疾病典型皮疹的发展特点及临床表现即可诊断,若有明确创伤及其他炎症病史对诊断更有帮助,一般不需做特殊检查。对难以确诊的病例、怀疑有恶变可能性的病例可 以做皮肤组织病理检查。在早期,肥大性瘢痕与瘢痕疙瘩无法区别,2~3 个月后疙瘩的进一步表现即可说明其今后的病程,若是瘢痕疙瘩,则容易受激惹且过度敏感,...
 ​
 [2] 相似度 0.6848 | 病名:HPV感染 | 科室:妇科
     问:HPV 感染时,能彻底根除 HPV 吗?
     答:目前没有药物可以清除 HPV。但只要感染者没有免疫系统缺陷,在感染 HPV 的 2~3 年内即可自行清除 HPV。...
 ​
 [3] 相似度 0.6826 | 病名:猫癣 | 科室:皮肤科
     问:人得了猫癣后会留疤痕吗?
     答:一般来说不会留下疤痕,具体还是要根据病情分析。绝大多数能在早期积极治疗的人群不会留下疤痕,因为导致猫癣的致病真菌通常都是寄生在皮肤的角质层,不会侵犯到 真皮层,因此通过早期干预,留疤的可能性较小。但若是长期不进行治疗,在瘙痒时抓破了皮肤可能会继发细菌感染,侵犯到真皮层后就有可能留疤了。...
 ​
 [4] 相似度 0.6812 | 病名:瘢痕 | 科室:美容整形科
     问:瘢痕和瘢痕疙瘩是同一种病吗?
     答:不完全一样。瘢痕有生理性瘢痕和病理性瘢痕之分,瘢痕疙瘩是继发于皮肤外伤或自发形成和过度生长的病理性瘢痕。可见瘢痕和瘢痕疙瘩不完全是一回事,瘢痕是一个更 宽泛的称谓。...
 ​
 [5] 相似度 0.6811 | 病名:瘢痕疙瘩 | 科室:皮肤科
     问:瘢痕疙瘩就说明是疤痕体质吗?
     答:是的。只有「瘢痕体质」的人,在受到外伤或某些刺激下,在特定部位,如胸、背、脖子等处,皮肤过度自我修复、愈合形成瘢痕疙瘩。...
 ​
 [6] 相似度 0.6766 | 病名:HPV感染 | 科室:妇科
     问:HPV 是什么?
     答:HPV 又称「人乳头瘤病毒」,是一种双链 DNA 病毒,可致人类皮肤和黏膜异常增生。感染该病毒后可出现寻常疣、尖锐湿疣等良性皮肤病损,也可能引起恶性肿瘤,如宫颈癌、外阴癌、肛门癌等,为涉及到多学科的疾病。但也有部分人感染了该病毒,没有出现任何症状和不适的感觉。目前发现,HPV 有 200 多种类型(...
 ​
 [7] 相似度 0.6735 | 病名:猫癣 | 科室:皮肤科
     问:怀疑得了猫癣,需要做检查确诊吗?
     答:需要。无论是人还是猫,由于猫癣的症状和其他较多的皮肤病相类似,所以还需要依靠相应的检查手段才能确诊。...
 ​
 [8] 相似度 0.6703 | 病名:HPV感染 | 科室:妇科
     问:HPV 感染是怎么发生的?
     答:HPV感染部位为人的皮肤和黏膜,病毒通过皮肤黏膜的接触感染人体,其中性接触传播为 HPV 的主要传播方式。HPV 不入血,所以血液传播不是 HPV 的传播途径。...     
 ​
 [9] 相似度 0.6675 | 病名:HPV感染 | 科室:妇科
     问:HPV 感染是不是另一半做了对不起自己的事?
     答:不一定。HPV 感染后,不同的亚型会导致不同的疾病,如尖锐湿疣、扁平疣等。我们通常更关注的是以尖锐湿疣为主的性传播疾病,性生活复杂的人(比如同时有多个性伴 侣)感染 HPV 的概率会大大增加。但仅凭检查出 HPV 阳性不足以证明对方隐瞒了复杂的性生活史,因为除性生活外,直接与感染者皮肤进行密切接触、或...
 ​
 [10] 相似度 0.6663 | 病名:HPV感染 | 科室:妇科
     问:HPV感染如何诊断?
     答:医生根据患者的临床症状,询问患者是否存在高危性生活史、有无与 HPV 感染者的直接、间接接触途径,结合相关检查(细胞学检查、阴道镜检查、组织病理学检查)结果以诊断是否感染 HPV。...

数据没有做清洗,就直接用了。

这里看到搜素的准确性还可以;

直接喂给AI就可以得到结果了。

7. 总结

典型的 RAG(Retrieval-Augmented Generation,检索增强生成)。

RAG 的标准骨架

复制代码
 用户问题
   → embedding 模型编码成向量
   → 向量数据库检索 top-k 相关文档
   → 把检索结果拼成上下文,连同问题一起喂给大模型
   → 大模型基于上下文生成回答

这四个步骤,就是 RAG 的核心。

今天要做的和"教科书版 RAG"略有不同的地方是:就是用本地 Ollama 代替了 OpenAI API,用本地 Qdrant 代替了云端向量库------但流程本身完全一样。

RAG 各环节的通用叫法

我们做的 术语 作用
把库里的问答转成向量存进 Qdrant Indexing(索引构建) 离线阶段,把知识库变成可检索的向量
用户问题转成向量去查 Retrieval(检索) 在线阶段,找到最相关的 top-k 文档
把检索结果拼进提示词 Augmentation(增强) 给大模型提供"外部知识"
Ollama 基于上下文生成回答 Generation(生成) 让模型基于证据回答,而非仅靠记忆

但 RAG 不止这一种形态

目前是最基础、最经典的 Naive RAG。实际生产中的 RAG 会在这个骨架上加东西:

  • Query Rewriting:用户问题太口语化时,先让模型改写成更规范的检索语句

  • Reranking:先召回 50 条,再用一个更精细的模型重排,取前 5 条

  • Hybrid Search:向量检索 + 关键词检索(BM25)结合,兼顾语义和字面,Qdrant单机版本不支持反向索引;

  • Multi-hop Retrieval:复杂的一个问题需要多轮检索才能凑齐答案

  • Agentic RAG:用 LangGraph 这类框架编排"检索→判断→重试→再检索"的循环

这些都是在这套基础流程上叠加的优化层,不是必须的。

关于重排

结论:用 BGE-Reranker 系列,这是中文 RAG 场景最成熟、最常用的选择。考虑到我的 3050 只有 8GB 显存,适合 bge-reranker-base 或 bge-reranker-v2-m3。

可选方案对比

模型 参数量 显存需求 特点
bge-reranker-base 278M 约 4GB 轻量快,中英双语,适合你当前的硬件
bge-reranker-v2-m3 约 568M 约 8GB BAAI 最新多语言重排模型,精度更好
bge-reranker-large 560M 约 8GB 精度优于 base,但显存占用更高
Qwen3-Reranker-0.6B 0.6B 约 1.2GB 轻量,但社区反馈有显存管理问题
qwen3-rerank 闭源 API 无需本地 阿里云百炼提供,不适合你本地部署的定位

为什么推荐 BGE-Reranker

中文医疗场景的匹配度最高。 BGE-Reranker 系列由北京智源研究院开源,训练数据包含大量中文语料,在中文语义匹配任务上表现稳定 。有学术研究直接测试了 bge-reranker-base 在专业领域检索中的效果,NDCG 从 0.39 提升到 0.44 。

和当前用的 embedding 模型同源。 bge-small-zh-v1.5 和 bge-reranker 来自同一个系列,搭配使用不会有兼容性问题。

社区验证充分。 在 FastGPT、Dify 等主流 RAG 框架中,BGE-Reranker 都是默认推荐的重排模型 。

重排的实际工作方式

重排模型是 Cross-Encoder 结构:它把「用户问题」和「候选文档」拼在一起输入模型,直接计算两者的相关性分数 。这和 embedding 的 Bi-Encoder (分别编码、算余弦相似度)不同,Cross-Encoder 能看到问题与文档之间的词级交互,所以排序更准,但计算量也更大,只适合对少量候选(50 条)做精排,不适合全库扫描。

相关推荐
@atweiwei1 小时前
训练 AI 大模型系列之 LoRA:不动模型本体,靠一条低秩旁路改专长(附完整手算算例)
人工智能·算法·ai·语言模型·rag
VIP_CQCRE16 小时前
给团队搭一个 AI 工作台:Open WebUI 接入 Ace Data Cloud 实战
docker·ai·rag·open webui·ace data cloud
for_ever_love__1 天前
LangChain Agentic RAG:让 Agent 自己决定要不要检索、检索几次
人工智能·golang·langchain·rag·langgraph·agentic·自适应检索
Guarding and trust1 天前
项目拆解(二):模型输出是‘野‘的——三层兜底+重分析,把AI变成可靠零件
java·人工智能·python·大模型·fastapi·ollama·错误兜底
小易老师AI实战1 天前
AI Agent 完整实战:记忆机制 + 任务拆解 + 自动执行
llm·rag·大模型应用·aiagent·智能体工程化
for_ever_love__1 天前
LangChain Text-to-SQL:让不会写 SQL 的人查数据库
数据库·sql·langchain·大模型·agent·rag·问答
流浪0012 天前
大模型技术全景(二十):RAG 文本分块策略与语义完整性
llm·rag·文本分块·语义完整性
小易老师AI实战2 天前
企业生产级RAG知识库搭建实战:解决大模型幻觉与检索失效问题
rag·检索增强生成·大模型工程化·知识库系统·企业级大模型落地
智码看视界3 天前
向量数据库选型实战:Milvus vs Chroma vs Qdrant 谁更适合 RAG
embedding·milvus·向量数据库·chroma·rag·语义检索·选型对比