设想你要去远方旅行一周。面前是半面墙大的衣柜,里面有几百件衣服、配饰和鞋,而你手里只有一个 20 寸的行李箱——没有人会把整个衣柜塞进去。你会快速扫视,先抓出几件“好像合适”的候选(检索召回),再一件件在镜子前比划(相关性打分),不合适的挂回去(动态过滤),合适的卷起来塞进压缩袋(上下文压缩),最后按行李箱分区科学摆放(Token 预算)。整个旅程是否从容,不取决于你搬空衣柜的决心,而取决于“剪裁”的智慧。大模型也面临同样的难题:它的上下文窗口是那只行李箱,知识库是巨大的衣柜,而外部记忆与 RAG 动态过滤,就是那双会挑拣、会取舍、会打包的手。这篇内容用“收拾行李”的完整流程,带零基础的你搞懂这套系统。

岗位关联

掌握上下文智能裁剪,是 LLM 应用从“能用”走向“好用”的分水岭——RAG 应用工程师、AI 应用开发者都需要靠这套方法,让有限的上下文窗口发挥最大价值。

目录
  1. 可视化演示
  2. 外部记忆概览
  3. 向量检索召回
  4. 相关性打分
  5. 动态过滤机制
  6. 上下文压缩
  7. Token预算管理
  8. 落地应用与调优
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示混合检索RRF融合排序、MMR相关性打分与动态阈值过滤、Token预算分配与上下文压缩三大核心机制,通过模式切换理解RAG上下文智能裁剪的完整流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

混合检索与RRF融合排序 BM25稀疏检索精确匹配关键词,稠密向量检索捕获语义相似性 RRF融合两路检索结果,按倒数排名合并提升召回率与精确率 RRF(d) = Σ 1/(k + rankᵢ(d)), k=60 混合检索兼顾精确匹配与语义理解

外部记忆概览

旅行打包的第一个问题不是“怎么装”,而是“我家里到底有什么”。如果所有衣服胡乱堆在床上,你不仅找不到目标,还会被淹没。聪明的做法是把衣柜分区:当季常穿的挂外面,过季的收上层,配饰按颜色排列。大模型天生没有这个衣柜——它的“肌肉记忆”藏在训练参数里,而大量、新鲜、私有的知识,必须存放在模型之外的某个地方,这就是外部记忆。

外部记忆指的是存放于模型参数之外、按需读取的知识载体。它像一座智能衣柜:衣服不必随身携带,但你要知道每件放在哪、如何快速取出。在系统层面,外部记忆通常分为两层:短期记忆是当前会话的对话记录,相当于随身小包里放的今天要用的东西;长期记忆是向量数据库,相当于按标签分类的家庭大衣柜。

向量数据库为什么能当“智能衣柜”?因为每个文档片段在存入时都会被转成一个高维向量——可以理解为衣服上挂的电子标签,写着“颜色、季节、场合、风格”。查询时,系统把问题也转成向量,去衣柜里找“标签最接近”的片段。整个上下文窗口的构成,可以用一个结构公式来表达。

衣柜清点完毕,接下来就该伸手去抓候选衣服了——不过先记住:外部记忆只是解决“有地方放知识”,真正决定行李箱效率的,是后面一整套的取舍手艺。

上下文窗口=指令+对话记忆+注入片段+生成预留\text{上下文窗口} = \text{指令} + \text{对话记忆} + \text{注入片段} + \text{生成预留}
上下文窗口不是一个黑箱,而是一间需要精心分配的房间:指令是房间的“使用须知”,对话记忆是之前的聊天记录,注入片段是从外部记忆取来的“参考材料”,生成预留则是留给模型写答案的空间。
  1. 指令(系统提示)告诉模型“你是谁、该怎么回答”,像旅行前贴的行程表,字数不多但必须保留。
    指令=角色设定+任务描述+输出格式要求\text{指令} = \text{角色设定} + \text{任务描述} + \text{输出格式要求}
  2. 对话记忆(短期记忆)当前会话的历史消息,决定了模型是否记得你三分钟前提过的要求。
    对话记忆=∑t=1n消息t\text{对话记忆} = \sum_{t=1}^{n} \text{消息}_t
  3. 注入片段(外部记忆)从向量数据库检索并过滤后拼进提示词的参考文本,是“临时借来的衣服”。
    注入片段={doci∣scorei≥θ}\text{注入片段} = \{ \text{doc}_i \mid \text{score}_i \geq \theta \}
  4. 生成预留(输出空间)必须给模型留出生成答案的 token,否则行李被塞满,人反而坐不下。
    生成预留=窗口上限−(指令+对话记忆+注入片段)\text{生成预留} = \text{窗口上限} - (\text{指令} + \text{对话记忆} + \text{注入片段})

向量检索召回

打包的第二步是快速浏览衣柜,把“一看就合适”的衣服先抓出来。你不可能把几百件衣服都试一遍,而是先凭标签筛选:目的地是海边,就直奔泳衣、T 恤、短裤那一排;目标是登山,就只看冲锋衣和速干层。这个过程在 RAG 里叫作召回(Retrieval),目标不是求全,而是用尽量低的代价,把最可能有用的候选捞出来。

要把“凭标签筛选”变成计算机能执行的动作,得先给文本做嵌入(Embedding):用一个神经网络把文本转换成一个高维向量,让语义相近的文本在向量空间里彼此靠近。接着计算查询向量与每个文档向量的余弦相似度——你可以把它理解为“两件衣服的契合分数”。最后做 Top-K 召回,也就是按分数从高到低,只取前 K 个片段。

召回通道还可以不止一条。除了语义向量检索,还可以同时按关键词、按元数据分类做多路召回:语义通道负责抓住“意思相近但用词不同”的相关文本,关键词通道负责精确命中专业术语,元数据通道负责限定时间范围或业务线。多路召回就像既看“颜色风格”又看“面料成分”还看“品牌货架”来同时找衣服,各自带回几件,再合并去重。

抓出来的候选可能有一大摞,但“觉得像”不等于“真合适”。沙滩裙和泳衣都算“海边的衣服”,可要是去的是冲浪营,沙滩裙就该被放回去——下一步是逐件审视,用更挑剔的眼光打分。

sim(q,d)=q⋅d∥q∥ ∥d∥\displaystyle \mathrm{sim}(\mathbf{q}, \mathbf{d}) = \frac{\mathbf{q} \cdot \mathbf{d}}{\|\mathbf{q}\| \, \|\mathbf{d}\|}
余弦相似度衡量的是两个向量的方向是否一致,而不是长度是否接近。就像判断两个人是否合拍,看的是兴趣爱好方向,而不是各自体重多少斤。
  1. 点积(分子)计算两个向量对应位置乘积之和,方向越一致,结果越大。
    q⋅d=∑i=1nqidi\mathbf{q} \cdot \mathbf{d} = \sum_{i=1}^{n} q_i d_i
  2. 模长乘积(分母)两个向量各自长度的乘积,用来消除文本长短带来的偏差。
    ∥q∥=∑i=1nqi2,∥d∥=∑i=1ndi2\|\mathbf{q}\| = \sqrt{\sum_{i=1}^{n} q_i^2}, \quad \|\mathbf{d}\| = \sqrt{\sum_{i=1}^{n} d_i^2}
  3. 归一化比值把“原始重叠程度”压到 $[-1, 1]$,越接近 1 表示方向越一致、语义越相关。
    sim∈[−1,1]越接近 1 越相关\mathrm{sim} \in [-1, 1] \quad \text{越接近 } 1 \text{ 越相关}

相关性打分

从衣柜里抓出一摞候选后,你得一件件在镜子前比划:这件适合晚宴吗?那件能应对山里昼夜温差吗?粗看合适的衣服,细看可能经不起推敲。RAG 里对应的动作是相关性打分:召回只是“初筛”,每个候选片段到底值不值得放进上下文,要经过更精细的评估。

第一轮打分来自向量相似度,但它的视角比较“粗”。于是出现了重排序模型(Reranker):一个更强的排序模型,把查询和每个片段拼在一起做深度语义匹配,输出一个更精确的相关性分数。你可以把它理解为叫上一位眼光毒辣的旅伴,帮你在镜子前重新比划每一件衣服。实际工程中,两路分数常常要融合:

及格线定多少,得看行李箱还剩多大空间:箱子空着的时候你可以宽容一点,快到上限时就得铁面无私。这个随环境变化的“挑剔程度”,正是下一节要聊的动态过滤机制。

scorefinal=α×scoreretrieval+(1−α)×scorererank\displaystyle \mathrm{score}_{\mathrm{final}} = \alpha \times \mathrm{score}_{\mathrm{retrieval}} + (1-\alpha) \times \mathrm{score}_{\mathrm{rerank}}
这是一个加权平均:$\alpha$ 是信任初筛的权重,$1-\alpha$ 是信任精排的权重。拿不稳的时候就五五开,精排模型够强就把 $\alpha$ 调小。
  1. 初筛分数(检索分)向量召回阶段算出的相似度,覆盖范围广、计算快,但精度一般。
    scoreretrieval=sim(q,d)\mathrm{score}_{\mathrm{retrieval}} = \mathrm{sim}(\mathbf{q}, \mathbf{d})
  2. 精排分数(重排分)重排序模型输出的深度匹配分,更准但更慢,所以只对少量候选使用。
    scorererank=Reranker(q,d)\mathrm{score}_{\mathrm{rerank}} = \mathrm{Reranker}(\mathbf{q}, \mathbf{d})
  3. 加权求和$\alpha$ 控制信任比例,当 $\alpha = 1$ 时只看初筛,当 $\alpha = 0$ 时只看精排。
    scorefinal=α⋅sr+(1−α)⋅sp\mathrm{score}_{\mathrm{final}} = \alpha \cdot s_r + (1 - \alpha) \cdot s_p
  4. 阈值判定最终分数与及格线 $\theta$ 比较,决定片段能否进入上下文。
    保留片段={d∣scorefinal(d)≥θ}\text{保留片段} = \{ d \mid \mathrm{score}_{\mathrm{final}}(d) \geq \theta \}

动态过滤机制

收拾行李时每个人都会经历同样的心理变化:箱子空的时候什么都想带,打算“反正还有地方”;一旦装到半满,就开始挑剔——同一件衣服,箱满时会想都不想地放回去。这个“挑剔程度”再也不是一个固定值,而会随着剩余空间不断变化。对应到 RAG 里,就是阈值的动态化。

动态过滤机制的核心思想,就是让阈值随上下文预算余量、查询难度和用户偏好实时浮动。预算宽裕时降低阈值,多放一些候选,宁可多不可漏;预算紧张时提高阈值,只放最精的内容。这种思路还可以拓展成多级过滤:第一级用快速相似度粗筛,剔除明显无关的;第二级用重排序精筛,剩下候选再按预算做最后裁剪。每一级的“挑剔程度”都可以独立调节。

更高级的做法是引入反馈调整:如果用户追问“再详细一点”,说明上一轮过滤得太狠,下一轮主动降低阈值;如果用户说“别扯太远”,则提高阈值收紧范围。这就像老练的旅伴根据前几天的旅行体验,动态调整后续行李的打包标准——不是一刀切,而是越走越懂你。

过滤后的衣服还有可能太多,尤其是冬天的大衣和羽绒服——直接带太占地。怎么办?聪明人都知道用压缩袋。下一节我们聊聊如何在保命和保空间之间找到那个平衡点。

θdynamic=θbase+β×(1−budgetremainingbudgettotal)\displaystyle \theta_{\mathrm{dynamic}} = \theta_{\mathrm{base}} + \beta \times \left(1 - \frac{\mathrm{budget}_{\mathrm{remaining}}}{\mathrm{budget}_{\mathrm{total}}}\right)
当剩余预算越来越少时,$\frac{\mathrm{budget}_{\mathrm{remaining}}}{\mathrm{budget}_{\mathrm{total}}}$ 越来越小,$1$ 减它之后越来越大,阈值被推高,过滤变严。这正应了那句老话:箱子越满,手越挑。
  1. 基准阈值一个经验初始值,相当于旅人的“默认挑剔度”,没有压力时按这个标准执行。
    θbase=0.5\theta_{\mathrm{base}} = 0.5
  2. 预算压力项反映当前剩多少空间,剩下越少,该项越大,表示越需要挑剔。
    budgetremainingbudgettotal∈[0,1]\frac{\mathrm{budget}_{\mathrm{remaining}}}{\mathrm{budget}_{\mathrm{total}}} \in [0, 1]
  3. 调节系数$\beta$ 控制“压力感”的强弱,$\beta$ 大则阈值对预算变化更敏感。
    β×(1−budgetremainingbudgettotal)\beta \times \left(1 - \frac{\mathrm{budget}_{\mathrm{remaining}}}{\mathrm{budget}_{\mathrm{total}}}\right)
  4. 合成阈值基准加上压力项,得到随预算余量变化的最终及格线。
    θdynamic≥θbase\theta_{\mathrm{dynamic}} \geq \theta_{\mathrm{base}}

上下文压缩

冬天出门旅行,羽绒服不带上会冻着,带上又占掉半个箱子。压缩袋解决了这个矛盾:把衣服卷紧、抽成真空,体积能缩小一半以上。上下文也面临同样的矛盾:有些文档片段信息密度低,却动辄几千 token。上下文压缩技术,就是那只“语义压缩袋”。

压缩的核心手段是摘要生成:用一个模型把长文档提炼成几百字的要点。这个动作也常被叫作信息蒸馏——像蒸馏酒那样,把一缸发酵液加热蒸发,只留下高浓度的精华。但需要注意:蒸馏会损失“口感”,压缩也会损失细节。关键数字、否定语气、日期地点这些重要信息一旦被摘掉,模型就可能答错。所以成熟的方案会做“分层处理”:冗余的背景段落生成摘要,关键证据段落保留原文,让两者在上下文中互相配合。

工程上,压缩率是衡量收益的重要指标,它告诉我们到底省下了多少空间:

压缩完的衣服,最终要一件不落地摆进行李箱——有人负责放底层,有人负责塞侧袋。上下文也一样:哪些内容占多少位置、优先级谁高谁低,需要一套 Token 预算管理策略。

ratio=1−tokenscompressedtokensoriginal\displaystyle \mathrm{ratio} = 1 - \frac{\mathrm{tokens}_{\mathrm{compressed}}}{\mathrm{tokens}_{\mathrm{original}}}
压缩率越接近 1,说明压得越狠、省得越多;越接近 0 说明基本没压缩。一个 60% 压缩率的片段,等于行李箱里那件被抽掉一半空气的羽绒服。
  1. 压缩后的 token 数摘要文本按分词器切出的长度,代表“压缩袋打包后的体积”。
    tokenscompressed=Tokenize(摘要)\mathrm{tokens}_{\mathrm{compressed}} = \text{Tokenize(摘要)}
  2. 原始 token 数未压缩文档片的长度,代表“羽绒服在衣柜里的原始体积”。
    tokensoriginal=Tokenize(原文)\mathrm{tokens}_{\mathrm{original}} = \text{Tokenize(原文)}
  3. 压缩率计算$1$ 减去两者比值,就是被省掉的空间占比。
    ratio=1−tokenscompressedtokensoriginal\mathrm{ratio} = 1 - \frac{\mathrm{tokens}_{\mathrm{compressed}}}{\mathrm{tokens}_{\mathrm{original}}}
  4. 临界判断当压缩率超过一定值(如 0.7)时,要警惕信息损失是否已经影响答案质量。
    若 ratio>0.7,需检查关键信息是否被蒸馏掉\text{若 } \mathrm{ratio} > 0.7 \text{,需检查关键信息是否被蒸馏掉}

Token预算管理

行李箱的每一寸空间都应该有它的主人:底部放重物,侧袋放手提,外层放当天要用的轻薄衣物。如果随心所欲乱塞,最后要么撑坏拉链,要么到了目的地发现必需品没带。上下文窗口也一样——指令、对话历史、检索片段、生成空间,每一类内容都要按权重分配预算。

优先级排序是预算管理的第一原则:系统指令是“身份证”不能丢,生成空间是“座位”必须有,然后才是对话历史和检索片段。权重高的内容先占位,权重低的让位。上限控制是第二原则:不管多有意思的内容,总 token 数硬性封顶,就像行李箱的物理容量不可突破。第三原则是动态调整:对话进行到中段,模型可以主动判断是否需要追加一轮检索,并按现有剩余空间重新分配——比如把冗长的历史对话折叠成摘要,腾出位置给新检索的片段。

每个模块能分到多少预算,可以统一用权重比例来计算:

行李箱终于装好了。但真正的考试不在家里,而在旅程中——带多了是累赘,带少了是遗憾。怎么知道这套打包方法好不好用?答案藏在一次次旅途复盘里。

budgeti=budgettotal×wi∑j=1nwj\displaystyle \mathrm{budget}_i = \mathrm{budget}_{\mathrm{total}} \times \frac{w_i}{\sum_{j=1}^{n} w_j}
权重 $w_i$ 就像一个物品的“占用系数”:系统指令的权重可能高达 1.0,平均一句对话的权重只有 0.15。按权重占比切分总预算,自然就得到了各模块的空间配额。
  1. 模块权重每个内容模块(指令、对话、检索片段等)都有一个人为设定的权重,代表它的重要程度。
    wi=模块 i 的重要度权重w_i = \text{模块 } i \text{ 的重要度权重}
  2. 权重总和所有模块权重加在一起,作为归一化的分母。
    ∑j=1nwj=w1+w2+⋯+wn\sum_{j=1}^{n} w_j = w_1 + w_2 + \cdots + w_n
  3. 按比例切分总预算乘以该模块权重占总权重的比例,得到它的预留空间。
    budgeti=budgettotal×wi∑wj\mathrm{budget}_i = \mathrm{budget}_{\mathrm{total}} \times \frac{w_i}{\sum w_j}
  4. 硬上限约束所有模块分配之和不得超过窗口物理上限,否则触发压缩或过滤。
    ∑i=1nbudgeti≤budgettotal\sum_{i=1}^{n} \mathrm{budget}_i \leq \mathrm{budget}_{\mathrm{total}}

落地应用与调优

旅行回来后,你会打开行李箱复盘:那件针织衫穿了三次确实值得,那把多功能军刀一次没用纯属浪费。下次出行,你就会把军刀留在家里,把空间留给更需要的东西。RAG 系统的调优就是同样的复盘循环——用真实数据和反馈,不断修正“打包策略”。

评估指标是复盘的量化工具。最常用的是命中率,衡量“该找的相关片段是否真的被检索到了”;再看最终答案质量,因为哪怕检索全对,模型也可能答错;还要盯住 token 浪费率,看有多少预算被注入了却根本没被模型用上的噪声片段。调优动作包括:调整 Top-K 大小、动态阈值的基准值和敏感系数、压缩策略中压缩率与保留原文的分界线,以及各模块的预算权重。

工具链方面,LangChain、LlamaIndex 提供了开箱即用的 RAG 编排框架,Milvus、FAISS、pgvector 等向量库负责存储与检索底座。实际落地时,常配合观测工具记录每一轮的检索分数、阈值变化与 token 消耗,形成调优闭环。

说到底,这和收拾行李一样,没有一劳永逸的标准答案——去三亚和去拉萨,最佳打包方案完全不同。但只要掌握了“外部记忆存放 → 召回 → 打分 → 动态过滤 → 压缩 → 预算分配 → 复盘调优”这套完整手艺,面对任何知识场景,你都能从容地把最值得的上下文,装进那只有限的箱子。

hit_rate=relevantretrievedrelevanttotal\displaystyle \mathrm{hit\_rate} = \frac{\mathrm{relevant}_{\mathrm{retrieved}}}{\mathrm{relevant}_{\mathrm{total}}}
命中率就是“该带的衣服真的带上了多少”。假设这次旅行本该带 10 件必需品,你只带了 7 件,命中率就是 70%。它是评估检索系统最直观的 KPI。
  1. 检索到的相关片段被召回且通过过滤、最终进入上下文的、与问题真正相关的文档数。
    relevantretrieved=∣{d∈注入集合∣d 相关}∣\mathrm{relevant}_{\mathrm{retrieved}} = |\{ d \in \text{注入集合} \mid d \text{ 相关} \}|
  2. 总相关片段理论上知识库中所有与当前问题相关的片段数,是评估的“标准答案集合”。
    relevanttotal=∣知识库中的全部相关片段∣\mathrm{relevant}_{\mathrm{total}} = |\text{知识库中的全部相关片段}|
  3. 命中率两者之比,越接近 1 说明检索与过滤越精准。
    hit_rate∈[0,1]\mathrm{hit\_rate} \in [0, 1]

AI 应用场景

企业智能客服:从产品手册大海捞针

阶段:检索召回 + 动态过滤 + 预算分配

解决的问题:企业客服要面对几万页的产品手册,传统规则引擎只能按关键词硬匹配,用户问“我的设备连不上网,怎么办”这种模糊问题时经常答非所问。AI 助手需要从海量手册中动态检索相关章节,过滤无关内容,再注入有限的上下文窗口。

具体案例:某路由器厂商的智能客服接入了包含 1.2 万段产品文档的向量知识库。用户提问后,系统召回了 20 个候选段落,按相关性打分后只有 5 段超过动态阈值(预算充足时阈值为 0.4,对话变长后自动上调到 0.6),再对其中两段长文档做摘要压缩,最终只花 680 token 就把核心排障步骤送进了上下文,回答准确率从 61% 提升到 89%。

代码智能助手:仓库级代码的精准注入

阶段:多路召回 + 重排序 + 反馈调整

解决的问题:给 AI 编程助手喂整个代码仓库不现实,需要按当前编辑文件、符号引用和提交历史多路召回相关代码片段,再用重排序挑选最相关的,把上下文预算优先分配给正在修改的函数及其依赖。

具体案例:某团队自研的 IDE 插件,在开发者修改 user_service.py 时,通过符号索引召回被调用的 db.py 函数、相关测试用例和最近三次提交,reranker 打分后只保留 8 段核心代码注入上下文,开发者补全代码的采纳率提升了 34%。

金融研报分析师:长文档分层压缩

阶段:上下文压缩 + Token 预算管理

解决的问题:一份 IPO 招股书动辄几百页、几十万 token,直接塞进上下文必然爆窗。需要分层压缩:目录章节先摘要,核心财务数据保留原文,再按预算分配权重,让模型带着“全貌摘要 + 关键数据原文”回答问题。

具体案例:某券商内部研报助手对 300 页招股书先做章节级摘要,把“风险因素”和“财务指标”两章标注为高权重保留原文,压缩率整体达到 62%,分析师查询“应收账款周转天数变化”时,模型能够给出精确数字并标注出处,复核时间从 2 小时缩短到 15 分钟。

Python 演示

python53 行
# -*- coding: utf-8 -*-"""外部记忆与RAG动态过滤演示:检索-打分-过滤-组装Prompt"""import math KNOWLEDGE_BASE = [    {"id": 1, "tokens": 40, "text": "B+树索引能显著加快等值查询速度,但索引过多会拖慢写入性能"},    {"id": 2, "tokens": 35, "text": "Redis缓存可缓解热点数据并发读压力,注意设置过期时间避免雪崩"},    {"id": 3, "tokens": 38, "text": "慢查询日志配合EXPLAIN可定位全表扫描与索引失效问题"},    {"id": 4, "tokens": 42, "text": "数据库连接池复用连接减少握手开销,提升高并发吞吐"},    {"id": 5, "tokens": 45, "text": "分库分表解决单表数据量过大的问题,但跨库事务复杂度上升"},    {"id": 6, "tokens": 36, "text": "定期备份是数据安全底线,策略需按恢复时间目标RTO设计"},] QUERY = "MySQL慢查询优化使用哪些手段" def tokenize(text):    text = text.lower().replace(" ", "")    return {text[i:i + 2] for i in range(len(text) - 1)} def cos_sim(a, b):    if not a or not b:        return 0.0    inter = len(a & b)    return inter / math.sqrt(len(a) * len(b)) def retrieve(query, top_k=5):    q = tokenize(query)    scored = []    for doc in KNOWLEDGE_BASE:        d = tokenize(doc["text"])        scored.append((cos_sim(q, d), doc))    scored.sort(key=lambda t: t[0], reverse=True)    return scored[:top_k] def dynamic_filter(scored, threshold=0.18, budget=90):    chosen, used = [], 0    for score, doc in scored:        if score >= threshold and used + doc["tokens"] <= budget:            chosen.append((score, doc))            used += doc["tokens"]    return chosen, used def build_prompt(query, chosen):    context = "\n".join("[%.2f] %s" % (s, d["text"]) for s, d in chosen)    return ("背景知识:\n%s\n\n问题:%s\n请基于背景知识简洁回答。"            % (context, query)) if __name__ == "__main__":    candidates = retrieve(QUERY)    chosen, used_tokens = dynamic_filter(candidates, threshold=0.18, budget=90)    print(build_prompt(QUERY, chosen))    print("召回 %d 段,过滤后注入 %d 段,占用 %d tokens"          % (len(candidates), len(chosen), used_tokens))
目的:展示一个最小可运行的 RAG 动态过滤链路——把用户问题转成“字符二元组”集合进行余弦相似度召回,再用阈值加预算双条件过滤片段,最终组装成带背景知识的 Prompt。
  1. tokenize()简易分词:把文本切成相邻两字一组的集合,作为向量的廉价替代品,语义距离近似可用。
  2. cos_sim(a, b)余弦相似度:交集大小除以两个集合模长的几何平均,值越高表示越相关。
  3. retrieve(query, top_k=5)Top-K 召回:遍历知识库算相似度,排序后只保留前 5 名候选。
  4. dynamic_filter(scored, threshold=0.18, budget=90)动态过滤核心:分数过线且预算够才注入,两个条件同时满足。
  5. build_prompt(query, chosen)Prompt 组装:把过滤后的片段按分数标注拼成背景知识块,引导模型参考回答。
  6. if __name__ == "__main__":主入口:串起“召回 → 过滤 → 组装”三步,并把注入情况打印出来验证效果。

常见误区

误区“多塞点片段总没错。K 设大一点,上下文更丰富,模型答案肯定更准。”很多初学者看到检索结果觉得都挺相关,于是把 Top-K 从 5 调到 20。

正解Top-K 只是“初筛名额”,不是“录取名单”。K 越大,噪声片段越多,不仅浪费 token 预算,还会稀释真正有用的信息。就像旅行箱里塞的衣服越多,找到你要穿的那件越难。正确的路子是“宽召回、精过滤”:K 可以适当大,但后面必须有重排序和动态过滤把关。

为何常见:召回结果按分数排列后看起来“都有点关系”,让人误以为多比少好,忽略了噪声对模型的干扰。

误区“相关性阈值设个 0.5,永远不变,简单省事又稳定。”开发者调好一个离线测试集上的数值后,就再也不想动它。

正解不同查询难度、不同剩余预算下,固定阈值会过严或过松。简单问题宽容度该高可它却一视同仁,上下文快塞满时又不知收敛。动态阈值按预算余量、查询复杂度和用户反馈实时调整,才能兼顾“别漏关键信息”和“别塞一堆废话”。

为何常见:固定数值便于理解和汇报,人们容易忽略上下文余量是随对话不断变化的动态变量。

误区“只要 RAG 搜对了片段,模型自然就会参考它。”很多人跑通 RAG 后,看到检索结果里有正确答案,就以为任务已经完成。

正解检索到只是第一步,片段还要被放在合适的位置、控制好数量、去掉冗余,模型才真正“看得见”。把检索片段堆在 Prompt 最后、中间夹杂大量无关文本,模型照样忽略。就像你明明把护照放进了行李箱,但塞在最底层夹缝里,过海关时一样是灾难。

为何常见:把“找到了”误当成“用好了”,高估了模型对噪声和混乱布局的鲁棒性。

误区“摘要嘛,越短越好,压到一两百字最能省 token。”看到压缩率漂亮就觉得万事大吉。

正解过度压缩会丢失关键数字、否定语气、日期等细节,导致模型一本正经地胡说八道。就像把羽绒服压成砖头虽然省空间,但完全失去了保暖功能。正确做法是对冗余背景做摘要,对关键证据保留原文,并对压缩率设红线——超过 70% 就要人工审计信息完整性。

为何常见:token 预算压力和压缩率指标都很诱人,数字越好越容易让人忽视“信息完整性”这个无法用单一指标衡量的底线。

面试题

Q1. 外部记忆的核心作用是什么?

单选题 · easy

  • A.扩大模型本身的参数规模
  • B.给有限上下文补充相关知识← 正确答案
  • C.替代大规模预训练过程
  • D.直接提升模型推理速度

答案:B。外部记忆把知识放在模型参数之外,按需检索注入上下文,解决的是“模型不知道私有/新鲜知识”的问题。它不改变参数规模和推理速度,也不替代预训练。就像衣柜里的衣服不会改变你的身体,但出门前能让你借到合适的行头。

Q2. 向量检索中余弦相似度衡量的是什么?

单选题 · easy

  • A.两个向量的整体长度差
  • B.两个向量的方向一致性← 正确答案
  • C.两个向量的特征维数
  • D.两个向量的存储空间

答案:B。余弦相似度只关心向量方向是否一致,通过点积除以模长乘积实现,结果范围在 -1 到 1 之间。它消除长度影响,所以文本长短不会干扰相似度评估。就像判断两个人是否投缘,看的是三观方向,而不是音量大小。

Q3. 动态过滤中“阈值”直接决定什么?

单选题 · easy

  • A.模型生成答案的最大长度
  • B.检索片段能否被注入上下文← 正确答案
  • C.向量数据库的索引规模
  • D.知识库文档的更新频率

答案:B。阈值是相关性及格线,分数高于阈值的片段被保留并注入上下文,低于阈值的被过滤掉。它不控制生成长度、索引规模或知识更新。就像旅行打包时的“挑剔度”,决定了哪件衣服能进箱子、哪件要被挂回衣柜。

Q4. 多路召回的主要优势是什么?

单选题 · mid

  • A.减少召回总数来降低延迟
  • B.多角度检索提升召回覆盖率← 正确答案
  • C.完全取代重排序精排流程
  • D.保证所有候选百分百相关

答案:B。多路召回同时用语义向量、关键词、元数据等多条通道检索,能覆盖不同维度的相关性,提高召回率,但也因此候选总量更大,后续仍需精排过滤。它不保证相关、不减少总数,更不能取代重排序。就像既按颜色又按面料还按品牌找衣服,找到的候选更多,但最终还得一件件试。

Q5. 为什么不能只依赖摘要压缩上下文?

单选题 · mid

  • A.摘要生成速度一定更慢
  • B.摘要可能丢失关键证据细节← 正确答案
  • C.摘要会显著增加 token 用量
  • D.摘要无法被模型所理解

答案:B。摘要本质是信息蒸馏,会损失精确数字、否定语气和细节。只留摘要可能导致模型答案缺乏事实依据甚至出错。成熟的策略是“背景摘要 + 关键证据保留原文”。就像压缩羽绒服省了空间,但如果把填充物全抽掉,它就不再保暖。

Q6. Token 预算分配的正确做法是?

单选题 · mid

  • A.全部预算都给检索片段
  • B.指令与系统提示可直接丢弃
  • C.按任务优先级动态分配← 正确答案
  • D.预算耗尽时直接丢弃用户问题

答案:C。预算分配应按重要性权重动态切分:系统指令、生成预留通常优先级最高,对话历史和检索片段按情况调整,必要时压缩历史来腾空间。全部给检索、丢弃指令或用户问题都是错误做法。就像行李箱不能全装衣服而把洗漱包扔了。

Q7. 重排序模型主要解决什么问题?

单选题 · mid

  • A.向量库存储容量不足问题
  • B.初召回排序粗糙精度不足问题← 正确答案
  • C.生成阶段推理速度过慢问题
  • D.上下文窗口溢出溢满问题

答案:B。向量召回的相似度是“粗匹配”,重排序模型用深度语义匹配对少量候选重新精确打分,解决初筛排序不够准的问题。它不负责存储、加速生成或管理窗口。就像粗筛一遍拉出的衣服,还得靠眼光毒辣的朋友帮你重新排序。

Q8. 动态阈值相比固定阈值的优势有哪些?

多选题 · hard

  • A.能适应查询难度波动变化← 正确答案
  • B.可关联剩余预算做调节← 正确答案
  • C.保证检索结果永远正确
  • D.平衡相关性与上下文丰富度← 正确答案

答案:ABD。动态阈值结合了查询难度和预算余量:问题难、预算多时放宽,问题简单、预算紧时收紧,从而在相关性和丰富度之间取得平衡。它并不能“永远正确”——任何过滤策略都无法保证百分之百不出错。就像老练的旅伴会根据路程远近调整挑剔度,但也可能看走眼。

Q9. 有效的上下文压缩策略包括哪些?

多选题 · hard

  • A.长文档分区块逐层摘要← 正确答案
  • B.关键证据片段保留原文← 正确答案
  • C.一律截取开头若干字符
  • D.按结构抽取要点并精简← 正确答案

答案:ABD。有效压缩应该对长文档分层做摘要、对关键证据保留原文、按文档结构抽取要点。一味截取开头字符会丢失后文关键信息,属于简单粗暴的截断而非压缩。就像整理行李箱该用压缩袋分类收纳,而不是把衣服直接剪掉一半。

Q10. 评估 RAG 动态过滤效果时应关注哪些指标?

多选题 · extreme

  • A.相关片段命中率与召回率← 正确答案
  • B.最终生成答案的正确率← 正确答案
  • C.无效 token 的浪费比率← 正确答案
  • D.向量嵌入的维度大小

答案:ABC。命中率衡量检索过滤是否找全该找的,最终答案正确率验证整条链路的产品效果,token 浪费率反映注入内容是否被有效利用。向量维度是模型选择阶段的超参数,不是过滤效果的评估指标。就像复盘旅行要看“该带的带没带、玩得开不开心、有没有白占地方”,而不是纠结行李箱的颜色。