我的笔记散落在几百个 Markdown 文件里,想找某个结论时,全文搜索经常搜不到——因为我记不清当时用的是哪个词。于是我决定用 RAG(检索增强生成)给自己做一个知识库问答。
整体流程
RAG 的思路其实很朴素:先从资料里找出相关片段,再把片段和问题一起交给大模型回答。拆开来是四步:
- 切分:把长文档切成小段;
- 向量化:用 Embedding 模型把每段文字变成向量;
- 检索:把问题也变成向量,找出最相近的几段;
- 生成:把这几段作为上下文,让模型基于它们回答。
分块:最容易被忽视的一步
一开始我按固定 500 字硬切,结果经常把一个完整的论述切成两半,检索出来的片段前言不搭后语。后来改成按标题层级切分,每段再带上它所属的标题路径,效果立刻好了很多。
type Chunk = { id: string; heading: string; text: string };
function splitByHeading(doc: string, file: string): Chunk[] {
const sections = doc.split(/\n(?=#{1,3} )/);
return sections.map((section, i) => ({
id: `${file}#${i}`,
heading: section.match(/^#{1,3} (.*)/)?.[1] ?? file,
text: section.trim(),
}));
}
经验是:让每个片段能独立读懂,比追求固定长度更重要。
检索:先求召回,再求精准
只取最相似的 3 段时,经常漏掉关键信息。我现在的做法是先多召回一些(比如 20 段),再用一个更精细的排序步骤挑出最相关的 5 段。另外把关键词检索和向量检索结合起来,专有名词、版本号这类内容会更稳。
生成:让模型知道“不知道”
在 Prompt 里明确要求:只根据提供的资料回答,资料里没有就直说“没找到”,并标出引用了哪篇笔记。这一条规则,让幻觉少了很多,也让我能快速回到原文核对。
小结
做完之后最大的感受是:RAG 的效果,七分靠数据处理,三分靠模型。与其换更大的模型,不如先把分块和检索打磨好。
评论
评论区即将开放,欢迎先通过侧边栏的方式联系我。