夜雨聆风学习资料网

ARTICLE · 1326061

急诊科 AI 极客实战教程 · 第 8 章

急诊科 AI 极客实战教程 · 第 8 章

本地 RAG 知识库:让 AI 带着指南开卷考试

搭一个只基于你的指南和文献回答的循证秘书。预计用时 120-150 分钟。

本章目标

把“凭记忆回答”的 AI,改造成“只基于你的资料回答、说不出就敢说不知道”的助手。

前置条件:完成第 2、5 章,会运行 Python,准备若干 PDF 或 TXT 格式的指南与文献。本章可交付成果:一个本地知识库 + 一个可查询的 rag_qa.py + 一份 20 题测试报告。

一、开场病例:一条推荐剂量,查了半小时

夜班,一个创伤性凝血病(TIC)的患者,血压在往下掉。你记得氨甲环酸(TXA)有用,但不确定:推荐剂量是多少?伤后多长时间内给药?哪些指南支持?肾功能不全要不要减量?

你打开手机,搜到一堆互相矛盾的内容:有的说 1 g,有的说 1 g 加 1 g;有的说 3 小时内,有的说 8 小时内。你打开一个网页版 AI,它很自信地给你一段“指南推荐”,还附了一个看起来很像真的参考文献。你差点就信了。

问题是:AI 不是在查指南,它是在“接龙”。它生成的是“最像答案的文本”,不是“最真实的文本”。

这一章要做的,就是给 AI 配一个本地开卷考试的书包:你提问,系统先在你的指南和文献里检索相关原文,把原文和问题一起交给 AI,再要求它“只根据原文回答,并标注出处”。这就是 RAG(Retrieval-Augmented Generation,检索增强生成)。

二、核心概念:六个词,一张表

概念
一句话解释
急诊类比
RAG
先检索资料,再让模型基于资料回答
开卷考试
Chunking(分块)
把长文档切成小段
把一本指南拆成一张张卡片
Embedding(向量化)
把文字变成一串数字,表示语义
给每张卡片编一个语义指纹
向量数据库
存储向量并支持相似度搜索
按“意思”而不是“关键词”找卡片
Retrieval(检索)
根据问题找出最相关的片段
抢救时快速翻到那一页
Rerank(重排)
对初步检索结果再排序
分诊后二次评估,把最重的排前面

1. RAG 的基本流程

# 离线阶段

PDF/TXT -> 解析文本 -> 分块 -> 向量化 -> 存入向量库

# 在线阶段

用户问题 -> 向量化 -> 相似度检索 -> 取 Top-K 片段

 → 拼成上下文 -> 交给 LLM -> 带引用回答

关键区别:普通 LLM 是“闭卷考试”,靠参数里的记忆;RAG 是“开卷考试”,靠你给它的资料。开卷考试也会答错,但至少你知道它看的是哪一页。

2. 为什么大模型会“幻觉”

第 1 章讲过:LLM 的训练目标是“预测下一个 token”,不是“保证事实正确”。具体到医学指南,有四条路径通向幻觉:知识可能过时,模型的训练有截止日期,新指南可能没学过;知识可能混淆,不同指南、不同年份、不同病种的推荐容易串;上下文里没有答案时它会“补全”,为了完成任务编一个看起来合理的答案;它无法区分“我记得”和“我猜的”,语气都很自信。

RAG 的作用,是给它一个可核查的证据来源。但它不能把幻觉降到零:检索可能没找到正确片段;找到了,模型可能忽略;模型可能错误引用;资料本身也可能过时或出错。

正确期待:RAG 把“凭空编造”变成“有据可查”,把幻觉率显著降低,但仍然需要人工核对引用。

3. Chunking:分块的艺术

策略
做法
适合
固定长度
每 500 到 1000 字一段,重叠 100 到 200 字
通用
按段落
以空行或标题分段
指南、教科书
按章节
以“推荐意见”“剂量”“禁忌”等标题分
结构化指南
语义分块
用模型判断句子边界
高质量要求

分块太大,检索到一大段,模型要自己找重点,上下文浪费,还容易把不相关内容混进来。分块太小,上下文丢失,一个推荐被切成几段检索不全,指代关系也断了(“该药”“上述剂量”)。急诊指南还有个特殊性:推荐剂量、时间窗、禁忌证往往集中在一个小节,分块时最好保留章节标题和页码,否则检索到“1 g”却不知道是哪个药。

4. Embedding:文字怎么变成向量

Embedding(嵌入)把一段文字映射成一个高维向量,比如 384 维或 768 维,语义相近的文字,向量也相近。你可以把它想成“语义指纹”:“氨甲环酸”和“TXA”向量很近;“氨甲环酸”和“对乙酰氨基酚”向量较远;“推荐剂量”和“用法用量”向量较近。

similarity = (A · B) / (|A| × |B|)

余弦相似度等于 1 表示方向完全相同,等于 0 表示无关,等于负 1 表示方向相反。注意:Embedding 不是“理解”,它捕获的是训练语料中的共现模式。缩写、否定、单位、数字,它可能处理不好,所以检索结果必须人工看。

5. 向量数据库:按“意思”搜索

传统搜索按关键词匹配:你搜“TXA”,找不到只写“氨甲环酸”的段落;你搜“肾功能不全”,找不到“eGFR 小于 30”的段落。向量搜索按语义相似度:你搜“TXA 剂量”,能找到“氨甲环酸 1 g 静脉注射”;你搜“肾不好能不能用”,能找到“严重肾功能不全患者慎用”。

工具
特点
适合
FAISS
Facebook 开源,快,轻量
个人本地知识库
Chroma
易用,自带持久化
快速原型
LanceDB
现代,支持多种数据类型
中等规模
Milvus
企业级,部署复杂
大规模、团队

本书用 FAISS 加 sentence-transformers,因为依赖少、可离线、适合个人电脑。

6. 检索之后:Prompt 决定成败

你是循证医学助手。请只根据下面提供的资料回答问题。

要求:

 1. 每个关键结论后标注来源,格式:[文件名, 第X页]

 2. 如果资料中没有明确答案,回答“库中无证据”,不要推断。

 3. 如果资料之间有冲突,列出冲突并说明各自来源。

 4. 不要使用资料之外的知识。

没有这几句话,模型很可能“看了资料,但继续自由发挥”。

三、底层原理:RAG 为什么会失败

知道失败模式,才知道怎么调。

失败模式
表现
原因
对策
检索不到
答案明明在库里却没被找到
分块过大过小、embedding 不匹配、top-k 太小
调分块、换模型、加关键词检索
检索到但不相关
找了一堆无关段落
语义相似但不回答问题的伪相关
加 rerank、加元数据过滤
模型忽略资料
资料里有,模型还是编
prompt 不够强、上下文太长
强化“只基于资料”指令
引用错误
标了页码,但原文对不上
模型编造引用
引用必须可点击、可核对
资料本身错
指南过时、版本错
知识库未更新
版本管理、定期更新
OCR 质量差
PDF 是扫描件,文本乱码
没有做 OCR
用 OCR 工具预处理
表格丢失
剂量表变成一串乱码
PDF 表格解析失败
手动录入或换专用解析器

RAG 的质量上限,取决于知识库的质量。垃圾进,垃圾出。

1. 混合检索:向量 + 关键词

纯向量检索对缩写、数字、专有名词可能不敏感。实践中常用混合检索:向量检索负责语义相似,关键词检索(BM25)负责精确匹配“TXA”“1 g”“3 h”,两者合并结果之后再做 rerank。本书的最小实现只做向量检索;如果你要做生产级知识库,建议加 BM25。

2. 元数据过滤

给每个 chunk 加元数据:文件名、页码、章节、指南版本或年份、病种、证据等级。检索时可以过滤,比如“只查 2024 年以后的指南”“只查创伤相关章节”。这能显著减少无关结果。

四、实操:搭一个最小可用的本地 RAG

步骤 1 安装依赖

conda activate ai-ed

pip install pypdf sentence-transformers faiss-cpu openai numpy pandas

Windows 提示:faiss-cpu 如果安装失败,可以尝试 pip install faiss-cpu --only-binary=:all:,或者改用 chromadb。本书以 FAISS 为例,概念是通用的。如果安装 sentence-transformers 后 import torch 报 shm.dll 或 c10.dll 找不到,通常是缺少最新的 Microsoft Visual C++ Redistributable(x64),安装后重启终端即可,详见附录 C.5.4。

步骤 2 准备资料

在 D:\AI急诊实验室\07_知识库\docs 下放入三类文件:公开版本的指南 PDF(例如 AHA、SSC、ATLS)、综述 PDF、你自己整理的指南摘要 TXT 或 MD。

安全提醒

不要放入含患者信息的文档;

不要放入未经授权的内部文件;

如果文档受版权保护,仅限个人学习使用。

步骤 3 运行建库脚本

python "...\ch08_rag_minimal.py" build ^

 --docs "D:\AI急诊实验室\07_知识库\docs" ^

 --index "D:\AI急诊实验室\07_知识库\index"

PowerShell 的续行符是反引号,不是 ^。上面的 ^ 是 CMD 写法。在 PowerShell 里写成一行,或者用反引号续行。

脚本会读取 .pdf、.txt、.md,按页提取文本,分块(默认 800 字,重叠 120 字),用 BAAI/bge-small-zh-v1.5 生成向量,建立 FAISS 索引,最后保存 index.faiss 和 metadata.json。第一次运行会下载 embedding 模型(几百 MB),需要网络。

步骤 4 查询

python "...\ch08_rag_minimal.py" query `

 --index "D:\AI急诊实验室\07_知识库\index" `

 --question "创伤性凝血病使用氨甲环酸的推荐剂量和时间窗是什么?"

脚本会把问题向量化,检索 Top-K(默认 5)片段,打印片段来源(文件名、页码、相似度);如果设置了 DEEPSEEK_API_KEY,就调用 DeepSeek 生成带引用的回答;如果没有 API Key,只打印检索结果,方便你人工阅读。

$env:DEEPSEEK_API_KEY = "sk-你的密钥"

不要把密钥写进脚本。用环境变量,或者 .env 加 python-dotenv,并把 .env 加入 .gitignore。

步骤 5 测试一个“库中没有”的问题

python "...\ch08_rag_minimal.py" query `

 --index "D:\AI急诊实验室\07_知识库\index" `

 --question "急诊成人咯血使用氨甲环酸的剂量是多少?"

如果库里只有创伤指南、没有咯血指南,正确行为就是回答“库中无证据”。如果它还是给了你一个剂量,说明 prompt 或检索出了问题。

“敢于说不知道”是 RAG 最重要的能力。一个什么都敢答的医疗 AI,比一个答不出来的危险得多。

步骤 6 评估你的知识库

准备 20 个问题:10 个“库中明确有答案”,5 个“需要跨文档综合”,5 个“库中没有答案”。然后按四个指标打分:

指标
定义
目标
检索命中率
正确片段是否出现在 Top-K
不低于 80%
引用准确率
回答中的引用是否真的支持该结论
不低于 90%
拒答正确率
库中无答案时是否回答“无证据”
不低于 90%
答案完整度
是否覆盖问题的主要要点
人工评分 1 到 5

把结果存到 07_知识库\RAG测试报告.md。这一步最容易被跳过,但它恰恰是判断“你的知识库能不能用”的唯一依据。

五、代码结构:你不需要从零写

配套脚本 ch08_rag_minimal.py 包含五个函数:load_documents() 读取 PDF、TXT、MD,split_sentences() 按句子切分,chunk_text() 分块并做重叠,build_index() 向量化并建 FAISS 索引,query() 负责检索与生成。

核心逻辑只有两段,建库和查询:

# 1. 建库

chunks = []

for doc in documents:

 for chunk in chunk_text(doc["text"]):

  chunks.append({"text": chunk, "file": doc["file"], "page": doc["page"]})

vectors = model.encode(

 [c["text"] for c in chunks], normalize_embeddings=True)

index = faiss.IndexFlatIP(vectors.shape[1]); index.add(vectors)

# 2. 查询

q_vec = model.encode([question], normalize_embeddings=True)

scores, ids = index.search(q_vec, top_k)

context = "\n\n".join(

 f"[{i+1}] 文件:{chunks[idx]['file']} 第{chunks[idx]['page']}页\n{chunks[idx]['text']}"

 for i, idx in enumerate(ids[0]))

为什么用 IndexFlatIP?因为 embedding 已经归一化,内积就等于余弦相似度。数据量小(几万条)时 Flat 索引足够快,数据量大了再考虑 IVF 或 HNSW。

六、把 RAG 接进 Claude Code 的工作流

用法
适合
A 独立脚本
批量查询、生成测试报告、知识库维护
B 让 Claude Code 调用脚本
一边查一边整理,保留可追溯性

用法 B 的操作很简单,在 Claude Code 里直接说:

请运行 D:\AI急诊实验室\07_知识库\rag_qa.py,

查询“脓毒症 1 小时 bundle 的推荐内容”,

把检索到的原文和回答一起给我,并标注文件名和页码。

# 进阶:封装成 MCP 工具(第 13 章),像调用本地文件一样调用知识库

Claude Code 会执行脚本、读取输出,再帮你整理。这样你既保留了 RAG 的可追溯性,又利用了 Agent 的整理能力。

七、提示词模板:带引用、敢拒答

【角色】你是一名循证医学助手,擅长从指南原文中提取推荐意见。

【背景】下面是从本地知识库检索到的资料片段,每段都有文件名和页码。

【任务】请只根据这些资料回答用户问题。

【边界】

 1. 每个关键结论后标注来源,格式:[文件名, 第X页]。

 2. 如果资料中没有明确答案,回答“库中无证据”,不要推断。

 3. 如果不同资料冲突,列出冲突点及各自来源。

 4. 不要使用资料之外的知识。

 5. 不要编造页码、指南名称或推荐等级。

【格式】

 - 先给出一句话结论;

 - 再列出证据要点(每条带引用);

 - 最后列出“不确定、冲突、缺失”的部分。

一个“防幻觉”的追问

回答生成之后,再加一句追问:请把你回答中的每一条引用逐条对照原文,确认原文是否真的包含该结论、页码是否准确、是否有断章取义;如果某条引用无法核实,请删除该结论并说明。

这一步会让模型自己删掉几条结论。别嫌它变得“保守”——被删掉的,本来就是你最不该相信的部分。

八、安全与合规

1. 数据不出院

数据类型
处理原则
指南、教科书、公开文献
可以本地建库
患者数据、内部制度、未发表数据
必须本地处理,且符合医院信息安全制度
云端 LLM 调用
只发送检索到的片段,不发送整个数据库
片段含患者信息
先脱敏,或改用本地模型

2. 版权

指南 PDF 可能受版权保护,个人学习使用通常可以,但不能公开分发;不要把整本教科书上传到公开网盘;商用知识库需要获得授权。

3. 版本管理

每份文档记录版本或年份;指南更新后重建索引;在回答中标注指南版本,避免“用旧指南回答新问题”。

九、可交付成果与过关自检

07_知识库/

 ├─ docs/ # 你的指南与文献

 ├─ index/ # index.faiss + metadata.json

 ├─ rag_qa.py # 查询脚本

 └─ RAG测试报告.md # 20 题评估

验收标准:能成功建库、索引文件存在;查询时能返回文件名、页码、相似度;回答中的引用能回到原文核对;库中无答案时能正确回答“无证据”;20 题测试的检索命中率不低于 80%、拒答正确率不低于 90%;没有把患者数据或密钥写进知识库和代码。

过关自检七项:能用自己的话解释 RAG 的开卷考试流程;能说出 RAG 不能把幻觉降到零的三个原因;知道分块太大和太小分别有什么问题;能解释 embedding 和余弦相似度的直觉;知道检索失败的常见原因;能写出“只基于资料回答、无证据就拒答”的 prompt;知道如何评估检索命中率和引用准确率。

延伸练习:把 chunk_size 从 500 改到 1500,观察检索结果的变化;加入 BM25 关键词检索,比较纯向量与混合检索的命中率;给文档加“年份、病种”元数据,只检索 2024 年以后的指南;故意问一个库中没有的罕见病剂量,观察模型是否拒答;随机抽 10 条引用逐条回到原文,计算引用准确率。

本章速查卡

RAG 等于检索 + 增强 + 生成,本质是开卷考试

流程:文档 → 分块 → 向量化 → 向量库 → 检索 → 拼上下文 → LLM

分块:500 到 1000 字,重叠 100 到 200 字,保留文件名、页码、章节

向量:语义指纹;余弦相似度;归一化后用内积

Prompt:只基于资料;每条带引用;无证据就拒答

评估:检索命中率、引用准确率、拒答正确率

安全:患者数据不出院;密钥不进代码;指南注意版权

下一章预告:你有了干净数据、统计图表、循证知识库。接下来要把这些素材变成论文和标书——IMRAD 的底层逻辑、如何写出没有 AI 味的 Results,以及国自然标书“痛点、空白、假说、路线”的四段结构。

本文为《急诊科 AI 极客实战教程》第 8 章排版版。代码请以配套仓库的完整脚本为准,文中代码为讲解节选。

我是老陆,热衷于分享医生 × AI 的实战干货。

如果这篇对你有用,欢迎点赞、在看、转发三连,我们下篇见。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料