Avatar

Qianqiu

Master in PKU Research Direction: VLM, RLHF, MLsys Hobbies: Game, Web novel, Anime

  1. WeChat
  1. Home
  2. Diary
  3. Research
  4. Entertain
  5. Search
  6. Archives
  7. About
    1. Dark Mode Light Mode
Diary

学习day28:

Feb 17, 2026

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():读取“已预处理好的医疗文档”,并切 chunk get_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 索引(核心检索)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import faiss
vector_store = FAISSVectorStore(embeddings=embeddings, index_type=index_type)

vector_store.dimension = dense_vecs.shape[1]
faiss.normalize_L2(dense_vecs)
vector_store.index = vector_store._create_index(num_vectors=len(chunks))

if index_type == "IVFFlat":
    vector_store.index.train(dense_vecs)

vector_store.index.add(dense_vecs)
vector_store.documents = chunks
vector_store.doc_count = len(chunks)

IVFFlat:需要训练(train),更适合大数据

sparse检索 colbert检索(二者可选)

`SparseVectorStore.build_from_weights

  • 建倒排结构 / 稀疏向量矩阵
  • 支持 query 的稀疏权重打分
1
2
3
4
if build_colbert and all_colbert is not None:
    from src.colbert_store import ColBERTStore
    colbert_store = ColBERTStore()
    colbert_store.build_from_vecs(all_colbert)

ColBERT 检索不是全量暴力匹配,而是:

  • 先用 sparse 或 dense 召回候选
  • 再对候选用 token-level 相似度做 rerank(更准)

保存

构建后验证模块(Sanity Check) 做一次小规模试检索,检查数据

  • 能正常加载
  • 能正常检索
  • 各路(dense / sparse / colbert)都能跑通
  • 返回结果看起来“像是对的”

Related content

学习day27:

学习day26:

学习day25:

未学习day24:计划赶不上变化,又摆一天

转码Day 23:

© 2025 - 2026 Qianqiu
Built with Hugo
Theme Stack designed by Jimmy