lc:152
今天来总结学习rag项目,先看config。 在开头配置好数据与索引的落盘,避免绝对路径写法。 主语言模型用ollama配置为Qwen2.5-7B,免去细节,反正RAG项目本质上是优化prompt模板和embedding及相似度计算,对主模型直接ollama部署完调接口也省事。 embedding 选择BAAI/bge-m3,默认支持多语言,不仅可以在同一种语言内部进行语义匹配,还能处理不同语言之间的语义对齐(如中文查询匹配英文文档,在此处就是中文疾病名称查询RAG信息的英文原始信息)。支持 100+ 语言,并具备跨语言语义匹配能力,适合作为RAG/检索系统的基础质检模型。输出维度1024必须与 Embedding 模型的输出向量维度一致否则 FAISS 建索引/检索会报错或结果错乱。batch size根据显存适当调整.
FAISS index 选择IVFFLAT,近似检索,需要训练聚类中心。FAISS_NLIST = 100 聚类中心数(粗分桶)。越大一般越准但训练更慢、索引更大。查询时探测多少个桶。越大召回越高但更慢。METADATA_PATH:通常存 doc_id -> 原文/标题/来源等映射,检索后回填文本用
数据量小一般用flat,大chunk用ivflat。NPROBE 是主要调参旋钮
chunking,文本切分配置 CHUNK_SIZE = 512 CHUNK_OVERLAP = 64 重叠,防止关键信息被切断 SEPARATORS:优先用更“自然”的边界分割(段落/句号/分号/逗号/空格) 医疗文本通常句子长、术语密:适当 overlap 很重要。
检索配置多管齐下,RETRIEVER_TOP_K = 5,RETRIEVER_SCORE_THRESHOLD = 0.5,取二者的min候选 控制“先粗搜、后精选”。
RERANKER:二阶段重排(Cross-Encoder 类)更准但更慢RERANKER_TOP_K=3:重排后只保留 3 条给 LLM BM25和向量检索加权融合(稀疏 + 稠密融合)
进行多路检索配置 BGE-M3 Multi-Route:Dense + Sparse + ColBERT
- Dense:句向量相似度
- Sparse:稀疏表示(可用 BM25 或 bge-m3 学出来的 sparse)
- ColBERT:token 级别的 late interaction,常在细粒度匹配上更强 三路权重归一化为 1.0
Prompt模板,即给出明确的输入端的系统约束和QA模板,比如
- 必须基于参考资料
- 没资料就明确说不能回答
- 要引用来源
[来源: 文档名称] - 诊疗方案提醒咨询医生
- 条理化输出
- 当资料互相冲突时说明冲突并给出最保守表述(医疗场景常见)
gradio web地址配置 本地/服务器启动可视化界面。
API 配置(FastAPI) 提供程序化调用入口(比如前端/其他服务调用 RAG)
src/data_loader.py
RAG 的典型流水线:
原始文档 →(加载/清洗)→ Document 列表 →(切分)→ chunk 列表 →(向量化)→ 检索 → 生成
这个模块覆盖了
- MedicalDocumentLoader:把各种来源的内容变成 LangChain 的
Document(page_content, metadata)标准格式 - ChineseTextSplitter:把长文本切成适合检索的 chunk,并保留必要的上下文(overlap)
load_json_documents
[
{“text”: “…”, “metadata”: {…}},
…
]
每条 item → 一个 Document
metadata 缺省时给 {}(避免 KeyError)
当前规模尚可可以用json格式,更大规模适合jsonl(逐行读)
load_txt_documents(filepath)
读整个文件内容作为一个 Document - metadata 里加了:
"source": 文件名"type": "text"这很重要:后面检索结果展示或调试时,你能知道 chunk 从哪里来。 同样适用于.md(在load_directory里就是这么复用的)。.md直接按纯文本读(不解析 Markdown 结构)
暂时不添加pdf支持,后续需要使用了再加。 load_cmedqa_data(cmedqa_dir=None) 用途:把问答对变成可检索的“知识文档”
中文切分器 ChineseTextSplitter:核心机制拆解
length_function=len:中文按字符长度算很直观(不是 token 数)is_separator_regex=False:分隔符按字面匹配,不当正则用(更安全)
chunks = self.splitter.split_documents(documents) LangChain 会把每个 chunk 仍然包装成 Document,并继承原 metadata(非常关键!)
chunk_size:便于统计和调参- 打印 avg chunk length,能快速判断“切太碎/切太粗”
便捷函数 load_and_split_documents:一键跑通流程
无overlap切分:Chunk1: 糖尿病的典型症状包括多饮、多尿、多食
Chunk2: 和体重减轻。早期可能没有明显症状。
加overlap: Chunk1: 糖尿病的典型症状包括多饮、多尿、多食
Chunk2: 多尿、多食和体重减轻。早期可能没有明显症状。
直观来看就是: [ A B C ] [ C D E ] [ E F G ]
回头来看一下build_index.py,把处理过的数据转换成向量。 把医疗文档:
- 切成 chunk(小段)
- 用嵌入模型编码成向量(dense,必要时还会拿到 sparse/colbert)
load_and_split_documents():读取“已预处理好的医疗文档”,并切 chunkget_embeddings():根据参数返回不同嵌入模型对象(bge-m3 或 simple)FAISSVectorStore:你们封装过的向量库(内部会创建/保存/搜索 FAISS)
主函数 build_index
embedding_type:决定用哪种 embedding(bge-m3 支持三路;simple 多半只支持 dense)index_type:FAISS 索引类型(Flat / IVFFlat / HNSW)routes:决定构建哪几路索引:"dense":只建 FAISS(dense)"dense+sparse":FAISS + sparse"all":FAISS + sparse + colbert 基础步骤至少有 4 个(加载切分、初始化模型、编码、建 FAISS、保存等)
FAISS 的聚类实质是“空间分区”,让搜索变成局部搜索,而不是全局搜索。
chunk
embeddings = get_embeddings(embedding_type) is_flag_model = getattr(embeddings, “_is_flag_model”, False) use_multi = (build_sparse or build_colbert) and is_flag_model
getattr(..., "_is_flag_model", False):用一个“暗号属性”判断当前模型是否支持“三路编码”- 如果是 BGE-M3 那类 FlagEmbedding 封装,可能提供
encode_multi / encode_query_multi use_multi:只有当你要建 sparse/colbert 且模型支持 multi 才走“三路编码”分支 否则就只做 dense 编码。
Dense-only and Multi-route
构建 FAISS Dense 索引(核心检索)
| |
IVFFlat:需要训练(train),更适合大数据
sparse检索 colbert检索(二者可选)
`SparseVectorStore.build_from_weights
- 建倒排结构 / 稀疏向量矩阵
- 支持 query 的稀疏权重打分
| |
ColBERT 检索不是全量暴力匹配,而是:
- 先用 sparse 或 dense 召回候选
- 再对候选用 token-level 相似度做 rerank(更准)
保存
构建后验证模块(Sanity Check) 做一次小规模试检索,检查数据
- 能正常加载
- 能正常检索
- 各路(dense / sparse / colbert)都能跑通
- 返回结果看起来“像是对的”