凌晨两点,米其林三星后厨里,主厨老周接到了今晚最后一位VIP客人的订单。他扫了一眼客人的忌口单——海鲜过敏、偏爱川湘、今天想吃点暖胃的,转身对助手说:“去,把刘师傅叫来,他上周那道酸汤肥牛客人反馈最好。”助手没多问,径直走向后厨最里排的冰柜,翻出三样食材、两份调味汁,交给老周。三分钟后,一道为这位客人量身定制的菜出锅上桌。 你可能会问:这和 AI 有什么关系?关系大了。大模型就像一个“学菜谱”的学徒,你给它看的“示例”就是决定它这顿饭能做多好的“试吃样品”。同样是让模型写一段回复、做一次分类、生成一份总结,你用哪些示例教它,结果可能天差地别——这就是 In-context 示例选择(In-context Example Selection)要解决的问题。本文会用“米其林后厨给 VIP 上菜”这个比喻,带你完整理解示例选择的最优策略。

岗位关联

对于 AI 从业者而言,掌握示例选择策略意味着你能在不动模型权重的前提下显著提升 ICL 性能——这是 Prompt 工程师、AI 应用开发者和研究员都必须具备的核心技能,也是构建高质量 LLM 应用性价比最高的杠杆之一。

目录
  1. 可视化演示
  2. 示例决定上限
  3. 语义相似检索
  4. KNN 近邻选择
  5. 多样性最大化
  6. 动态对决静态
  7. 工程落地要点
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示数据在神经网络各层间的传播过程,节点激活与连接权重实时动画呈现。

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

神经网络前向传播 青色节点表示当前激活的神经元 橙色连线表示活跃的数据流路径 a^(l) = σ(W^(l)·a^(l-1) + b^(l)) 数据逐层传播,每层激活值由上层加权求和后经激活函数得到

示例决定上限

老周的后厨有个不成文的规矩:每个新来的学徒,第一周不允许碰锅铲,只能站在灶台边看老师傅做菜。看三道,记住火候;看五道,记住颠勺的节奏;看十道,自己上手就能做出八分像。老周总说:“你给徒弟看什么菜,他就学会做什么菜——你只给他看食堂大锅饭,他永远做不出宴席的水准。”

这句话翻译到 AI 世界,就是 In-context 学习(ICL)的第一性原理:大模型就像一个“看菜学菜”的学徒,你在 Prompt 里给它的示例(demonstrations)就是它“看”到的那些菜。示例的数量、质量和排列顺序,共同决定了模型本次输出的“天花板”。

研究发现,当示例数量从 0 增加到 1、再到 4、8 个时,模型在分类、生成任务上的准确率几乎呈单调上升趋势;但超过某个范围后收益递减。更重要的是,示例的质量远比比数量重要——一个与任务高度相关的示例,胜过十个不相干的凑数示例。此外,示例的排列顺序也有微妙影响:把与查询最相似的示例放在最后(靠近生成位置),通常比放在开头效果更好。

回到老周的厨房:为什么他给 VIP 配菜前要亲自翻看那摞厚厚的“顾客反馈本”?因为他知道,菜谱上的字是死的,但哪道菜对哪位客人有效,是活的。示例选择,就是你为大模型这位“学徒”精心挑选“试吃样品”的过程——挑对了,它以最小成本做出满汉全席;挑错了,山珍海味也能做成黑暗料理。

ICL_Accuracy≈f(示例数量,示例质量,排列顺序)\displaystyle \text{ICL\_Accuracy} \approx f(\text{示例数量}, \text{示例质量}, \text{排列顺序})
这个公式说明:ICL 的效果不是一个单一因素决定的,而是数量、质量、顺序三个“厨师调料”共同作用的结果——其中质量占比最大。
  1. 函数关系ICL 准确率可以看作一个函数 \( f \),它的输入是三个变量——示例数量、示例质量、排列顺序。
    f(示例数量,示例质量,排列顺序)\displaystyle f(\text{示例数量}, \text{示例质量}, \text{排列顺序})
  2. 数量因子示例数量决定了模型“看了几道菜”,数量太少学不到规律,太多则注意力分散、收益递减。
    示例数量→收益先增后减\displaystyle \text{示例数量} \rightarrow \text{收益先增后减}
  3. 质量因子示例质量是“菜的品质”,一道正宗的酸汤肥牛比三道寡淡的清汤寡水更能教会学徒什么叫酸辣鲜香。
    示例质量∝ICL_Accuracy\displaystyle \text{示例质量} \propto \text{ICL\_Accuracy}
  4. 顺序因子排列顺序影响模型对每道“菜”的记忆权重,越靠近生成位置的示例影响越大。
    排列顺序→末尾示例权重更高\displaystyle \text{排列顺序} \rightarrow \text{末尾示例权重更高}

语义相似检索

老周给 VIP 配菜的第一步是什么?不是翻菜谱,而是先看客人的“偏好画像”——之前点过什么、忌口什么、偏爱什么口味。然后他才会去记忆库(后厨那本厚重的笔记)里找“最像这位客人”的历史订单。这个过程,在 AI 里叫做基于语义相似度的示例检索。

语义相似度检索的思路非常直观:把每个候选示例和当前的查询(query)都用向量(一串数字)表示出来,然后计算它们之间的距离。距离越近,说明语义越相似,这个示例就越值得被选中。关键在于,这里的“相似”是语义层面的,而不是字面层面的——“今天心情不好”和“emo 了”字面完全不同,但语义几乎一致。

具体做法是:用一个嵌入模型(如 text-embedding 系列)把示例库中的所有样本编码为向量,存入向量数据库。当新的查询到来时,同样编码为向量,然后计算它与所有候选向量的余弦相似度(Cosine Similarity),按分数从高到低排序,取前 Top-K 个作为候选示例。余弦相似度关注的是方向而非长度,因此对文本长度差异不敏感,非常适合文本匹配。

老周挑示例的秘诀,就是他的“偏好画像”笔记——语义检索本质上是把后厨的经验数字化,让机器也能像老周一样快速找到“这道菜最能对上客人口味”的过往案例。你给模型看的示例和用户的真实问题越“对味”,模型的输出就越精准。

sim(q,ei)=q⋅ei∥q∥∥ei∥=∑j=1dqjeij∑j=1dqj2∑j=1deij2\displaystyle \text{sim}(\mathbf{q}, \mathbf{e}_i) = \frac{\mathbf{q} \cdot \mathbf{e}_i}{\|\mathbf{q}\| \|\mathbf{e}_i\|} = \frac{\sum_{j=1}^{d} q_j e_{ij}}{\sqrt{\sum_{j=1}^{d} q_j^2} \sqrt{\sum_{j=1}^{d} e_{ij}^2}}
这就是余弦相似度的数学定义:两个向量的点积除以它们长度的乘积,结果越接近 1,说明两个文本的语义方向越一致。
  1. 向量点积分子是查询向量 \(\mathbf{q}\) 和示例向量 \(\mathbf{e}_i\) 的点积,计算的是两个向量在各维度上的乘积之和,反映它们在每个“语义方向”上的重合程度。
    q⋅ei=∑j=1dqjeij\displaystyle \mathbf{q} \cdot \mathbf{e}_i = \sum_{j=1}^{d} q_j e_{ij}
  2. 向量长度分母是两个向量各自长度(L2 范数)的乘积,作用是“归一化”,让相似度不受文本长度影响。
    ∥q∥=∑j=1dqj2,∥ei∥=∑j=1deij2\displaystyle \|\mathbf{q}\| = \sqrt{\sum_{j=1}^{d} q_j^2}, \quad \|\mathbf{e}_i\| = \sqrt{\sum_{j=1}^{d} e_{ij}^2}
  3. 整体相似度分子除以分母得到 \(-1\) 到 \(1\) 之间的相似度分数,1 表示完全同向,0 表示正交无关,-1 表示完全相反。
    sim∈[−1,1]\displaystyle \text{sim} \in [-1, 1]

KNN 近邻选择

有了相似度打分,下一步就是“选谁”。老周的做法是:从反馈本里挑出与这位客人最像的前五位订单,仔细研读。这个思路,对应机器学习中最经典的K 近邻(KNN)示例选择法。

KNN 示例选择的核心很简单:不是只选相似度最高的那一个,而是选相似度最高的 K 个。这样做的好处是“兼听则明”——单个最相似的示例可能有噪声或偏差,而 K 个邻居共同构成的“参考集”更稳健。举例来说,如果查询是“写一封辞职信”,只选最像的那一封可能来自某个极端案例,但选最相似的 3 封,就能覆盖“礼貌离职”“因待遇离职”“创业离职”等多种角度。

K 的取值是 KNN 的核心超参数。K 太小,模型看到的“参考样例”太少,容易被单个噪声带偏;K 太大,选进来的示例可能已经和查询不够相关,稀释了“针对性”。实践中,K 通常取 2 到 8 之间,具体数值需要通过验证集调优。更高级的做法是距离加权 KNN:让更近的邻居在示例排序中排得更靠后(更靠近生成位置),因为它们对输出的影响更大。

KNN 让老周“按图索骥”的效率更高了。他不再凭感觉翻笔记,而是有了一套系统化的“圈人”方法:先圈出最像的 K 个客人,再根据“像的程度”决定每道菜(示例)在餐桌(Prompt)上的位置——像的放中间主推,不太像的放旁边点缀。

Score(ei∣q)=sim(q,ei)∑ej∈TopKsim(q,ej)且ei∈TopK\displaystyle \text{Score}(\mathbf{e}_i | \mathbf{q}) = \frac{\text{sim}(\mathbf{q}, \mathbf{e}_i)}{\sum_{\mathbf{e}_j \in \text{TopK}} \text{sim}(\mathbf{q}, \mathbf{e}_j)} \quad \text{且} \quad \mathbf{e}_i \in \text{TopK}
这个公式说明:KNN 选择就是先在所有候选中筛出相似度最高的 K 个,再对这 K 个示例按相似度比例确定它们在 Prompt 中的相对权重或顺序。
  1. TopK 筛选\(\mathbf{e}_i \in \text{TopK}\) 表示只有相似度排名前 K 的示例有资格被选中,其余全部出局——就像老周只翻前五位最像客人的订单,后面的笔记翻都不翻。
    ei∈TopK\displaystyle \mathbf{e}_i \in \text{TopK}
  2. 归一化权重每个入选示例的得分等于它的相似度除以所有入选者相似度之和,实现“归一化”,使得最相似的示例获得最大的展示权重。
    sim(q,ei)∑ej∈TopKsim(q,ej)\displaystyle \frac{\text{sim}(\mathbf{q}, \mathbf{e}_i)}{\sum_{\mathbf{e}_j \in \text{TopK}} \text{sim}(\mathbf{q}, \mathbf{e}_j)}

多样性最大化

假设老周面对一位爱吃辣的 VIP,他该选哪三份历史订单作参考?如果三份全是“特辣毛血旺”,客人可能觉得老周只会做辣,忽略了毛血旺本身其实是有配菜讲究的。更聪明的做法是:一份毛血旺(特辣)、一份剁椒鱼头(鲜辣)、一份辣子鸡(干辣)——三道菜都辣,但辣的维度各不相同。这就是示例多样性最大化。

多样性最大化的核心动机是:如果选出的 K 个示例彼此太相似(比如都是同一个类别的样本),模型看到的“规律”就太单一,容易产生偏见。举个具体例子:在情感分类任务中,如果查询是“这家店的菜真难吃”,只用负面餐厅评论做示例会让模型把所有负面情绪都归因为“餐厅差评”,但如果同时加入负面电影评论、负面天气评论,模型就能学到“这是一种通用的负面情绪表达方式”,泛化能力更强。

实现多样性的常见方法有三种。第一是类别平衡采样:先对示例库按标签分层,再从每个类别里各取若干,保证覆盖。第二是基于嵌入的聚类:把候选示例向量化后做聚类,然后从每个聚类中选一个代表,避免“扎堆”。第三是贪心挑选(MMR):每选一个新示例,不仅要求它对查询相似,还要求它与已经选中的示例足够不相似,两者加权取最优。

老周的配菜哲学在这一节体现得淋漓尽致:“一道菜是解馋,一桌菜是待客。”多样性最大化就是确保你给模型看的示例不只是一个厨师的拿手菜,而是一桌风格多元、搭配均衡的宴席——这样模型学到的“规律”才完整而不偏科。

MMR=arg⁡max⁡ei∈C∖S[λ sim(ei,q)−(1−λ)max⁡ej∈Ssim(ei,ej)]\displaystyle \text{MMR} = \arg\max_{e_i \in C \setminus S} \left[ \lambda \, \text{sim}(e_i, q) - (1 - \lambda) \max_{e_j \in S} \text{sim}(e_i, e_j) \right]
MMR(最大边际相关)公式的本质:选下一个示例时,既要它与查询相关(第一项),又要它与已选集合尽量不同(第二项),\(\lambda\) 是两者之间的天平。
  1. 候选减已选\(\arg\max_{e_i \in C \setminus S}\) 表示从“还没被选中的候选集合”里挑一个最优者——已经上桌的菜不会重复上。
    ei∈C∖S\displaystyle e_i \in C \setminus S
  2. 相关性第一项 \(\lambda \, \text{sim}(e_i, q)\) 衡量候选示例与查询的相关性,\(\lambda\) 越大越重视相关性——菜还是得对口。
    λ sim(ei,q)\displaystyle \lambda \, \text{sim}(e_i, q)
  3. 差异性第二项 \((1 - \lambda) \max_{e_j \in S} \text{sim}(e_i, e_j)\) 衡量候选示例与已选集合的最大相似度,减去它是为了惩罚“重复”。
    (1−λ)max⁡ej∈Ssim(ei,ej)\displaystyle (1 - \lambda) \max_{e_j \in S} \text{sim}(e_i, e_j)

动态对决静态

接下来是后厨派系之争。二厨主张“把最受欢迎的十道菜印成固定菜单,所有客人来了都上这套”,老周却坚持“每位 VIP 客人必须临时翻反馈本现配”。谁对?实验数据站在老周这边。动态示例选择(Dynamic)在绝大多数任务上优于静态示例选择(Static)。

所谓静态示例选择,是指不管你问什么问题,Prompt 里的示例都是预先固定好的那一批——通常是从示例库里挑出代表性最强、效果最好的几个,写死在系统提示词里。它的优势在于确定性:每次请求的 Prompt 完全相同,输出风格稳定,便于缓存和调试。缺点是“以不变应万变”很容易失灵——同一个示例对“写年终总结”有效,对“写请假条”可能毫无帮助。

动态示例选择则是“每来一个新查询,就重新检索一次最合适的示例”——这正是本文前两节讲的内容:查询进来 → 语义检索 → KNN 选出 Top-K → 构造 Prompt。研究发现,在分类、抽取、对话等任务上,动态选择通常比静态选择带来 5% 到 20% 的相对准确率提升。代价是每次请求都要多一次向量检索和排序计算,增加了时延和成本。工程上的折衷方案是:对高频查询做缓存,命中缓存直接用静态示例;未命中才走全量动态检索。

如果把静态示例比作“快餐店的固定套餐”,动态示例就是“米其林主厨的当日定制”。老周之所以愿意多花那几分钟翻反馈本,是因为 VIP 客人值得这份定制——而在 AI 应用里,“每位用户都值得一份定制”,前提是你有足够的算力和工程优化来支付这份时延账单。

Accuracydynamic>Accuracystatic(在多数任务上),Latencydynamic∝Ncandidates\displaystyle \text{Accuracy}_{\text{dynamic}} > \text{Accuracy}_{\text{static}} \quad \text{(在多数任务上)},\quad \text{Latency}_{\text{dynamic}} \propto N_{\text{candidates}}
这个不等式说明:动态选择的准确率通常优于静态选择,但其延迟与候选示例库的大小成正比——这是两者权衡的核心。
  1. 准确率对比\(\text{Accuracy}_{\text{dynamic}} > \text{Accuracy}_{\text{static}}\) 表示多数情况下动态选择准确率更高——“现配的菜”总比“标准套餐”更合口味。
    Accuracydynamic>Accuracystatic\displaystyle \text{Accuracy}_{\text{dynamic}} > \text{Accuracy}_{\text{static}}
  2. 延迟代价\(\text{Latency}_{\text{dynamic}} \propto N_{\text{candidates}}\) 说明动态选择的响应时间与候选示例数量线性相关——反馈本越厚,翻找越久。
    Latencydynamic∝Ncandidates\displaystyle \text{Latency}_{\text{dynamic}} \propto N_{\text{candidates}}

工程落地要点

理论说了一堆,老周的经验告诉我们:再好的理念,落地不了都是空谈。示例选择要做进真实系统,需要解决三个工程痛点:检索速度、缓存策略、评估反馈。

第一,向量检索加速。 当示例库规模达到百万级,线性扫描全部示例计算余弦相似度就不现实了。工程上使用向量数据库(如 FAISS、Milvus、Qdrant)建立 ANN(近似最近邻)索引,把检索时间从毫秒级 O(N) 降到亚毫秒级 O(log N)。你可以把 FAISS 想象成后厨的“快速定位标签”——以前要翻整本反馈本才找到一条记录,现在按标签一抽就是。

第二,缓存与时延控制。 动态检索虽然效果好,但每次请求都做一次向量编码和数据库查询会增加几十到几百毫秒延迟。实践中,可以对查询做归一化(如对相似 query 聚类打 key),命中缓存的直接返回预置示例;同时把高频 Top-K 示例的结果缓存到内存。老周的冰箱里永远备着提前切好的配菜——用“半成品”换时间。

第三,评估集回测与失败反馈。 示例选择策略不是一次性调好就完事。正确的做法是构建一个带标准答案的评估集,离线比较不同选择策略的效果差异;上线后记录失败样本,定期把“模型答错但用户修正后的结果”作为新示例加入示例库。这相当于老周的“顾客反馈本”是动态更新的——每次客人说“咸了”或“不够辣”,他都会记上一笔,下次配菜就更准一分。

老周的后厨之所以能在凌晨三点依然出菜又快又稳,靠的是二十年的反馈本、满满的备菜冰箱、和一套“边做边学”的规矩。你的 ICL 系统也一样:检索策略是“找得准”,缓存是“上得快”,反馈机制是“记得牢”——三管齐下,示例选择才算真正落地。

ICL_Pipeline=Embed(Query)→ANN Retrieve(TopK)→Rerank(MMR)→Build Prompt→LLM Infer→Feedback\displaystyle \text{ICL\_Pipeline} = \text{Embed}(\text{Query}) \rightarrow \text{ANN Retrieve}(\text{TopK}) \rightarrow \text{Rerank}(\text{MMR}) \rightarrow \text{Build Prompt} \rightarrow \text{LLM Infer} \rightarrow \text{Feedback}
一条完整的智能 ICL 流水线:嵌入查询 → 近邻检索 → 重排序 → 构造 Prompt → 模型推理 → 反馈回流,每一环都是示例选择工程化的关键节点。
  1. 查询嵌入\(\text{Embed}(\text{Query})\) 把用户的原始文本编码为向量——先把客人的口味数字化,才有后续一切。
    Embed(Query)\displaystyle \text{Embed}(\text{Query})
  2. 近似检索\(\text{ANN Retrieve}(\text{TopK})\) 用 ANN 索引快速召回最相似的 K 个示例——按标签抽反馈本,而不是逐页翻。
    ANN Retrieve(TopK)\displaystyle \text{ANN Retrieve}(\text{TopK})
  3. 重排序\(\text{Rerank}(\text{MMR})\) 在召回结果上做多样性重排——保证选出的不是一桌同质菜。
    Rerank(MMR)\displaystyle \text{Rerank}(\text{MMR})
  4. 反馈闭环\(\text{Feedback}\) 记录输出质量,新样本回流示例库——反馈本越用越厚,配菜越来越准。
    Feedback→示例库更新\displaystyle \text{Feedback} \rightarrow \text{示例库更新}

AI 应用场景

智能客服意图识别:动态配菜一分钟教会新客服上岗

阶段:Prompt 工程 / Inference 阶段

解决的问题:传统意图识别需要为每个新业务领域微调分类模型,每次迭代要标注数千条数据、训练数小时。通过 ICL 动态示例选择,只需准备一个示例库和检索器,即可零训练地为新场景完成意图分类——相当于老周不用培训新学徒,只给他看两位老师傅的示范,他就能照着做菜。

具体案例:某电商平台客服系统接入 ICL 示例选择后,用户消息“我在直播间抢的券怎么用不了”被编码为查询向量,从 2 万条历史客服工单中检索出最相似的 5 条作为示例,其中包含优惠券使用失败、支付失败、活动限制三类覆盖。大模型以这 5 条为参考,准确判断用户意图为“优惠券使用咨询”,并自动附上对应解决方案。对比固定示例方案,准确率从 71% 提升到 88%,线上延迟仅增加 45ms。

企业知识库问答:示例检索让每份回答都有据可依

阶段:RAG 流水线 / 检索增强生成

解决的问题:企业知识库问答中,通用 RAG 只检索文档片段,但模型输出格式不稳定。通过将“高质量问答对”作为示例库,每次用户提问时用语义检索挑选与问题最接近的 2-3 个问答对作为 In-context 示例,模型不仅输出的内容更准确,回答格式也统一为“结论 + 依据 + 操作建议”三段式。

具体案例:某大型制造企业的内部 FAQ 系统,基于 5000 条标准问答对构建示例库。员工提问“设备报错 3057 怎么办”,系统检索到最相似的“设备报错 3058 处理流程”和“设备报错 3 系通用排查”两个问答对作为示例,模型输出时直接套用其结构,回答被员工采纳率从 62% 提升至 85%。

文本分类自动化:多类别示例覆盖让模型不再偏科

阶段:少样本学习 / 零训练分类

解决的问题:在“投诉类型自动分单”场景中,如果只选相似度最高的示例,很容易全选成“物流投诉”,导致其他类别识别失效。通过 MMR 多样性重排,候选示例自动覆盖物流、质量、售后、价格四个类别,模型分类 F1 分数比单纯相似度检索提升 12%。

具体案例:某 B2C 平台的工单系统,每天 1.2 万条客诉需要人工分单。接入多样性感知的 ICL 示例选择后,每条工单自动携带 4 个覆盖不同投诉类型的示例进入 Prompt,自动分单准确率达到 91%,人工二次复核量减少 60%。

Python 演示

python83 行
# -*- coding: utf-8 -*-"""基于语义相似度与MMR多样性的In-context示例选择器"""import numpy as npfrom sklearn.metrics.pairwise import cosine_similarity class ICLExampleSelector:    def __init__(self, example_vectors, example_texts, labels=None):        self.example_vectors = np.array(example_vectors)        self.example_texts = example_texts        self.labels = labels        self.selected_idx_list = []     def knn_select(self, query_vector, k=3):        """基于KNN选择最相似的k个示例"""        sims = cosine_similarity([query_vector], self.example_vectors)[0]        top_k_idx = np.argsort(sims)[::-1][:k]        return top_k_idx, sims[top_k_idx]     def mmr_select(self, query_vector, k=3, lam=0.7):        """基于MMR选择既相关又多样的k个示例"""        sim_q = cosine_similarity([query_vector], self.example_vectors)[0]        selected = []        remaining = list(range(len(self.example_vectors)))         for _ in range(k):            best_score = -1.0            best_idx = -1            for i in remaining:                if len(selected) == 0:                    mmr_score = lam * sim_q[i]                else:                    sim_selected = max(                        cosine_similarity([self.example_vectors[i]],                                          [self.example_vectors[j]])[0][0]                        for j in selected                    )                    mmr_score = lam * sim_q[i] - (1 - lam) * sim_selected                if mmr_score > best_score:                    best_score = mmr_score                    best_idx = i            selected.append(best_idx)            remaining.remove(best_idx)        return np.array(selected)     def build_prompt(self, query_text, k=3, method="mmr"):        """构造带示例的Prompt"""        if method == "mmr":            idx = self.mmr_select(self._dummy_embed(query_text), k)        else:            idx, _ = self.knn_select(self._dummy_embed(query_text), k)         self.selected_idx_list = idx.tolist()        examples = [self.example_texts[i] for i in idx]        prompt = "参考以下示例进行回答:\n"        for i, ex in enumerate(examples):            prompt += f"示例{i + 1}: {ex}\n"        prompt += f"新问题: {query_text}\n回答: "        return prompt     @staticmethod    def _dummy_embed(text):        """简化版:用随机向量模拟真实嵌入(实际应用请替换为text-embedding模型)"""        np.random.seed(len(text) % 100)        return np.random.rand(16)  if __name__ == "__main__":    # 示例库:4条问答对    corpus = [        "示例: 如何申请发票?回答: 在订单页点击申请发票。",        "示例: 如何退货?回答: 在订单页申请退货并选择原因。",        "示例: 如何修改收货地址?回答: 在订单详情页修改。",        "示例: 如何查询物流?回答: 在订单页点击查看物流。",    ]    embeds = [np.random.rand(16) for _ in range(4)]    selector = ICLExampleSelector(embeds, corpus)     query = "如何开发票"    final_prompt = selector.build_prompt(query, k=2, method="knn")    print("=== 选中示例索引 ===")    print(selector.selected_idx_list)    print("=== 构造的Prompt ===")    print(final_prompt)
目的:实现一个完整的 ICL 示例选择器,支持 KNN 相似度检索和 MMR 多样性重排两种策略,并自动构造带示例的 Prompt——这是生产环境 ICL 系统的核心骨架。
  1. class ICLExampleSelector定义示例选择器类,初始化时接收示例向量、示例文本和标签;相当于老周的后厨反馈本,预先录入所有“历史订单”。
  2. def knn_select(self, query_vector, k=3)用余弦相似度计算查询与每个示例的分数,取前 k 个最近邻;对应“按相似度圈出最像的 K 位客人”。
  3. sims = cosine_similarity([query_vector], self.example_vectors)[0]一次批量计算查询与所有示例的相似度矩阵,numpy 向量化操作避免手写循环。
  4. def mmr_select(self, query_vector, k=3, lam=0.7)MMR 贪心选择既相关又多样的示例;重点是每次选新示例时惩罚与已选示例相似度过高的候选。
  5. mmr_score = lam * sim_q[i] - (1 - lam) * sim_selectedMMR 核心公式:相关性加分、重复性减分,\(\lambda\) 控制天平倾斜方向。
  6. def build_prompt(self, query_text, k=3, method="mmr")根据查询文本自动检索示例并构造完整的 Prompt 字符串;这是业务侧唯一需要调用的接口。
  7. self.selected_idx_list = idx.tolist()记录选中的示例索引,方便后续调试和可视化;注意列表属性用 _list 后缀命名,避免与方法名冲突。
  8. @staticmethod _dummy_embed(text)用随机向量模拟真实嵌入,实际项目中替换为 text-embedding 模型输出即可;注释中标注了替换点。

常见误区

误区我听说 ICL 就是“给模型看例子”,那自然是给的例子越多模型学得越好。我直接把示例库里的 50 条全塞进 Prompt,结果模型输出反而开始胡言乱语,是不是模型太小了?

正解示例数量与效果是“先增后减”的倒 U 型曲线。示例太少模型学不到规律,太多则冲淡关键信号、超出模型的上下文窗口,还可能引入噪声。就像你给学徒一次看 50 道菜,他看完只记得最后两道——真正的高手一次只看 3-5 道精品。示例的选择比数量重要,精选 3-5 条高质量示例通常优于堆砌 20 条平庸示例。

为何常见:直觉上“数据越多越好”是机器学习的基本信条,但 ICL 的特殊性在于它不更新权重,所有信息都要塞进有限的上下文窗口里,数量有物理上限。

误区我用 Jaccard 相似度或者直接字符匹配来找“最相似”的示例,结果检索出来的示例里全是字面一样的问题,模型反而越看越糊涂。字面对上了还不算对?

正解ICL 示例选择必须使用语义相似度(如嵌入模型的余弦相似度),而不是字面匹配。“我今天心情很 down”和“我emo了”字面完全不同,但语义几乎一致。字面匹配会错过语义相关的示例,也会召回字面相同但意图不同的“假朋友”。实际应用中应始终使用预训练嵌入模型编码后再比较向量距离。

为何常见:小白的文本匹配经验大多来自 Ctrl+F 或正则表达式,天然认为“字一样就是同一个意思”。语义是抽象的,需要向量才能捕捉。

误区既然动态选择准确率更高,我就把所有场景都改成动态选择,结果系统响应时间从 200ms 变成了 800ms,用户投诉激增。动态选择是银弹吗?

正解动态选择在准确率上有优势,但代价是每次请求都要做向量化和检索。对延迟敏感的高频场景,静态示例配合输出缓存可能体验更好;对准确率敏感的低频场景(如金融报告生成),动态选择才值得。工程上更推荐“混合策略”:高频 query 走缓存静态示例,长尾 query 走动态检索——老周的冰箱里永远有备好的半成品菜。

为何常见:看到 benchmark 数字就冲动,忽略了真实系统是准确率、延迟、成本三者的平衡。学术指标与工程约束之间存在鸿沟。

误区我把示例库和检索参数调试好之后,线上跑了一个月效果就开始下滑。示例库里的数据都是高手的标注,怎么会越用越差?

正解现实中用户的 query 分布会漂移,新的问题形态不断出现,旧示例库会逐渐“过期”。你必须建立反馈闭环:定期收集模型失败案例,把“纠正后正确的结果”作为新示例回流到库中;周期性在评估集上回测选择策略,监控检索命中率。老周的反馈本之所以值钱,是因为他二十年来每天都在往里面加新笔记。

为何常见:把示例库当成静态资产,忽略了它本质上是需要持续运营的“活数据”——数据漂移是 AI 系统的常态。

面试题

Q1. In-context 学习中,示例选择对模型输出的影响主要体现在哪方面?

单选题 · easy

  • A.改变模型内部权重参数
  • B.决定上下文学习的输出上限← 正确答案
  • C.增加模型的参数量
  • D.提升模型的训练速度

答案:B。ICL 不更新模型权重,示例选择通过在有限上下文窗口中提供高质量示范来决定输出的“天花板”。选项 A 是微调的特性,C 是模型架构改变,D 与示例选择无关。就像主厨选什么菜给学徒看,决定的是学徒今天能做出什么水准的菜。

Q2. 在语义检索示例时,文本相似度通常使用什么方式衡量?

单选题 · easy

  • A.纯字符完全相同比例
  • B.文本长度绝对差值
  • C.嵌入向量余弦相似度← 正确答案
  • D.文本出现顺序一致性

答案:C。语义检索用嵌入模型把文本编码成向量,再用余弦相似度衡量方向一致性,捕捉语义而非字面。A 是字面匹配,B 是长度比较,D 是顺序比较,三者都无法反映语义。相当于老周看客人的“口味画像”,而不是看客人点了几个字。

Q3. KNN 示例选择中,K 值过大会导致什么问题?

单选题 · mid

  • A.检索速度显著变慢
  • B.选入与查询不相关的示例← 正确答案
  • C.示例向量维度增加
  • D.嵌入模型失效

答案:B。K 太大时,排名靠后的示例与查询语义距离已较远,选入它们会稀释示例的针对性,让模型“参考”到无关内容。A 中速度影响有但不主要,C 与 K 无关,D 错误。好比老周圈定了 30 位“相似客人”,但后 20 位其实口味差异很大。

Q4. MMR(最大边际相关)公式中,第二项 \((1-\lambda)\max \text{sim}(e_i, e_j)\) 的作用是什么?

单选题 · mid

  • A.提高示例与查询的相关性
  • B.惩罚与已选示例过于相似的候选← 正确答案
  • C.增加示例的文本长度
  • D.调整嵌入向量的维度

答案:B。第二项计算候选示例与已选集合的最大相似度并做减法,目的是惩罚“重复”,促进多样性。A 是第一项的作用,C 和 D 与 MMR 无关。就像老周配菜时会避开已经在桌上的同类菜,确保每道菜都有独特看点。

Q5. 关于动态示例选择与静态示例选择的对比,下列哪项说法最准确?

单选题 · mid

  • A.静态选择准确率一定高于动态
  • B.动态选择准确率通常更高但延迟更大← 正确答案
  • C.两者准确率和延迟完全相同
  • D.动态选择不需要向量检索

答案:B。动态选择按查询实时检索,通常准确率更高,但每次请求多一次检索计算导致延迟增加;静态选择反之。A、C、D 均错误。如同老周给 VIP 现配凉菜比统一套餐更对口味,但翻反馈本需要时间。

Q6. 在向量检索示例时,工程上为支持百万级示例库常用什么方案?

单选题 · hard

  • A.全量暴力扫描计算相似度
  • B.ANN 近似最近邻索引← 正确答案
  • C.将所有示例存入模型参数
  • D.每次请求重训嵌入模型

答案:B。百万级库全量线性扫描延迟不可接受,ANN(如 FAISS、HNSW)通过索引结构把 O(N) 降到亚毫秒级。A 是朴素做法,C 和 D 概念错误。相当于后厨用标签抽屉整理反馈本,而不是每次翻完整本子。

Q7. 构建 ICL 示例选择流水线时,以下正确的工程顺序是?

单选题 · hard

  • A.嵌入查询 → 检索 TopK → MMR 重排 → 构造 Prompt → 模型推理← 正确答案
  • B.检索 TopK → 嵌入查询 → 模型推理 → 构造 Prompt
  • C.模型推理 → 检索 TopK → 嵌入查询 → 构造 Prompt
  • D.构造 Prompt → 模型推理 → 嵌入查询 → 检索 TopK

答案:A。正确流水线必须先嵌入查询,再检索候选,接着做多样性重排,最后构造 Prompt 交给大模型。B 顺序颠倒,C 和 D 逻辑混乱。这和后厨流程一致:先确认客人画像,再翻反馈本,最后配菜上桌。

Q8. 以下哪些属于提高示例多样性的具体方法?

多选题 · mid

  • A.按标签分层做类别平衡采样← 正确答案
  • B.对示例向量做聚类后取代表← 正确答案
  • C.选择最相似的单一样例
  • D.用 MMR 贪心算法挑选← 正确答案

答案:ABD。类别平衡、嵌入聚类、MMR 都是多样性最大化的具体手段。C 恰好相反——只选最相似的单一示例必然导致同质化。A 保证类别覆盖,B 避免扎堆,D 在相关性与多样性之间取平衡。

Q9. 动态示例选择相比静态示例选择的优劣包括哪些?

多选题 · mid

  • A.在多数任务上准确率更高← 正确答案
  • B.为每个查询定制最合适的示例← 正确答案
  • C.完全不需要缓存机制
  • D.增加每次请求的检索时延← 正确答案

答案:ABD。动态选择每查询现检索,准确率更高且针对性强,代价是增加时延;但它依然需要缓存机制来控制高频场景的开销,所以 C 错误。就像老周只为 VIP 现配菜,普通客人还是吃标准套餐。

Q10. 关于 In-context 示例选择,下列哪些说法是正确的?

多选题 · hard

  • A.示例质量和排列顺序共同影响效果← 正确答案
  • B.相同数量的示例下高质量示例效果更好← 正确答案
  • C.示例数量越多模型效果一定越好
  • D.排列顺序中靠近生成位置的示例影响更大← 正确答案

答案:ABD。示例效果由数量、质量、顺序共同决定,质量权重更高;数量超过阈值后收益递减甚至下降,故 C 错误。靠近生成位置的 token 在注意力机制中权重更高,顺序效应真实存在。老周配菜时会把最拿手的硬菜放在最关键的位置上桌。