智能体 · 检索

RAG 流程

嵌入 → 检索 → 阅读

段落
92 本站的术语表和图例
嵌入模型
all-MiniLM-L6-v2 384 维
检索
按余弦取前 3
模型
Qwen3-1.7B

全部步骤

  1. 把知识切成段落

    可供取用的知识是本站自己的文本(英文版):85 条术语表定义和 7 行图例,共 92 个段落。在别处训练的模型从没读过它们。

    92 个段落 · 术语表 85 + 图例 7

  2. 文本变成向量

    嵌入模型把每个段落读一遍,输出一个向量:all-MiniLM-L6-v2 是一个 6 层的 BERT,把最后一层在各词元上取平均,再把结果缩放到长度 1。问题也以同样方式嵌入。

    文本 → 384 个数,长度 1

  3. 意思相近,空间上也相近

    讲相关事物的段落得到相似的向量。压到两维(它们的前两个主成分)后,相关的术语聚在一起,问题落在回答它的那行图例附近。两维只保留了部分信息,所以其他相近的段落可能看起来很远。

    384 维中的 2 维,仅供目测

  4. 按余弦相似度排序

    检索就是点积:问题的向量与每个段落的向量逐一相乘,从高到低排。保留前 3 个。第二个问题的答案只排在第 2 位;词语重叠让 “Few-shot prompting” 排到了第一。

    分数 = q · d(两者长度都是 1)· 保留前 3 个

  5. 段落放进提示词

    程序把排在最前的段落贴进提示词,放在问题上方,并附上只根据它们作答的指令。模型看待检索到的文本,和看待上下文中其他任何东西完全一样。

    211 个词元:指令 + 3 个段落 + 问题

  6. 有检索与无检索

    没有段落时,Qwen3-1.7B 猜的是斜线格子在随便什么地方可能表示什么。有了段落,它按本站的图例来回答。检索没有改变模型;它改变的是摆在模型面前的东西。

    同一个模型 · 有无段落

  7. 检索失手时

    检索会悄无声息地失手。“Which word came first” 与讲位置的段落没有共同的词,一个都没检索回来。问题也可能找到了对的段落,而那个段落恰好没有答案。被告知只用上下文时,模型会如实说明,而它自己作答时则编了一个名字。

    段落不对,回答就弱

代码

embedder = SentenceTransformer("all-MiniLM-L6-v2")
P = embedder.encode(passages, normalize_embeddings=True)       # once, ahead of time: (84, 384)
q = embedder.encode(question, normalize_embeddings=True)       # (384,)
scores = P @ q                                                  # cosine similarity, since both have length 1
top = scores.argsort()[::-1][:3]
context = '\n'.join(f'[{i + 1}] {passages[j]}' for i, j in enumerate(top))
answer = llm(f'Answer using only the context below.\n\nContext:\n{context}\n\nQuestion: {question}')

延伸阅读