当前位置: 首页 > news >正文

Few-shot 示例的动态选择:根据 query 语义自动匹配最相关的 prompt 示例

Few-shot 示例的动态选择:根据 query 语义自动匹配最相关的 prompt 示例

一、深度引言与场景痛点

大家好,我是赵咕咕。

你给模型写了一个 Few-shot prompt,里面放了3个示例——一个关于代码审查、一个关于文档生成、一个关于数据分析。结果用户问了一个"写周报"的问题,你的示例一个都用不上。

这就是静态 Few-shot 的痛点:你准备的示例是固定的,但用户的问题是千变万化的。示例不对口,Few-shot 不但帮不上忙,反而可能引导模型往错误方向跑。

解决思路很简单:根据用户 query 的语义,从示例库中动态选出最相关的几个示例。这篇文章我们就来实现这个方案,让你的 Prompt 示例"活"起来。

二、底层机制与原理深度剖析

动态 Few-shot 选择的核心流程是:把每个示例做向量化存储,用户 query 来了也做向量化,然后用向量相似度匹配选出最相关的 K 个示例。

这里有个关键设计:示例的特征向量不应该基于示例原文,而应该基于"这个示例适合解决什么问题"。比如一个代码审查的示例,它的匹配向量应该和"代码审查、代码质量、code review"这些概念靠近,而不是和示例中具体的变量名靠近。

所以我们实际上需要两套向量:

  1. 匹配向量:用于和 user query 做相似度计算的"问题特征向量"
  2. 示例内容:真正注入 prompt 的完整示例文本

架构流程如下:

flowchart TB A[用户 Query] --> B[Query 向量化<br/>Embedding Model] B --> C[向量相似度检索<br/>与示例库匹配向量对比] D[(示例库<br/>Example Store)] --> E[匹配向量索引<br/>Match Vector Index] D --> F[示例内容存储<br/>Example Content] C --> G[Top-K 相似示例] G --> H[示例去重与多样性过滤<br/>MMR 算法] H --> I[按匹配分数排序] I --> J[注入 Prompt<br/>Few-shot Examples] K[Prompt 模板] --> L[最终 Prompt] J --> L A --> L style B fill:#e8f5e9 style C fill:#fff3e0 style H fill:#f3e5f5

除了相似度匹配,还需要考虑示例间的多样性。如果前 5 个最相似的示例都是同一个类型,后两个再相关的示例也没机会展示。这就需要 MMR(Maximal Marginal Relevance)算法来平衡相关性和多样性。

三、生产级代码实现

下面是完整的动态 Few-shot 选择实现:

from __future__ import annotations import asyncio import numpy as np from dataclasses import dataclass, field from typing import Optional import hashlib import json @dataclass class FewShotExample: """单个 Few-shot 示例""" example_id: str description: str # 示例适用场景的描述 user_input: str # 示例中的用户输入 assistant_output: str # 示例中的期望输出 match_vector: Optional[list[float]] = None # 匹配向量 tags: list[str] = field(default_factory=list) difficulty: str = "medium" # easy / medium / hard def to_prompt_str(self) -> str: """转为 prompt 中的格式""" return ( f"用户: {self.user_input}\n" f"助手: {self.assistant_output}\n" ) @property def similarity_key(self) -> str: """用于匹配的文本""" return f"{self.description} {' '.join(self.tags)}" class DynamicFewShotSelector: """动态 Few-shot 示例选择器""" def __init__(self, embedding_model): """ embedding_model: 需要实现 embed(text) -> list[float] """ self._embed = embedding_model self._examples: list[FewShotExample] = [] self._vectors: Optional[np.ndarray] = None # shape: (N, D) async def add_example(self, example: FewShotExample) -> None: """添加示例并计算匹配向量""" if example.match_vector is None: match_text = example.similarity_key example.match_vector = await self._embed(match_text) self._examples.append(example) self._rebuild_index() def _rebuild_index(self) -> None: """重构向量索引矩阵""" if not self._examples: self._vectors = None return vectors = [] for ex in self._examples: if ex.match_vector: vectors.append(ex.match_vector) if vectors: self._vectors = np.array(vectors, dtype=np.float32) async def select( self, query: str, k: int = 3, diversity_lambda: float = 0.5, ) -> list[FewShotExample]: """ 为 query 选择最佳示例 diversity_lambda: MMR 多样性权重 (0=纯多样性, 1=纯相关性) """ if not self._examples or self._vectors is None: return [] # 1. 向量化 query query_vec = np.array( await self._embed(query), dtype=np.float32 ) # 2. 计算余弦相似度 scores = self._cosine_similarity(query_vec, self._vectors) # 3. MMR 多样性选择 selected_indices = self._mmr_select( query_vec, scores, k, diversity_lambda ) return [self._examples[i] for i in selected_indices] def _cosine_similarity( self, query: np.ndarray, candidates: np.ndarray ) -> np.ndarray: """计算余弦相似度""" query_norm = query / (np.linalg.norm(query) + 1e-8) candidates_norm = candidates / ( np.linalg.norm(candidates, axis=1, keepdims=True) + 1e-8 ) return np.dot(candidates_norm, query_norm) def _mmr_select( self, query_vec: np.ndarray, scores: np.ndarray, k: int, lam: float, ) -> list[int]: """ MMR (Maximal Marginal Relevance) 算法 平衡相关性和多样性地选择 Top-K """ n = len(scores) if n <= k: return list(range(n)) selected = [] remaining = set(range(n)) # 第一个选择:最高相关性 first = int(np.argmax(scores)) selected.append(first) remaining.remove(first) while len(selected) < k and remaining: best_idx = -1 best_mmr = -float("inf") for i in remaining: # 相关性项 relevance = lam * scores[i] # 多样性项:与已选示例的最大相似度 max_sim_to_selected = 0.0 if selected: for j in selected: sim = self._cosine_similarity( self._vectors[i], self._vectors[j].reshape(1, -1) ) max_sim_to_selected = max( max_sim_to_selected, sim ) diversity = (1 - lam) * (1 - max_sim_to_selected) mmr = relevance + diversity if mmr > best_mmr: best_mmr = mmr best_idx = i if best_idx >= 0: selected.append(best_idx) remaining.remove(best_idx) return selected def to_prompt_examples(self, examples: list[FewShotExample]) -> str: """将选中的示例转为 prompt 片段""" if not examples: return "" lines = ["## 参考示例\n"] for i, ex in enumerate(examples, 1): lines.append(f"### 示例 {i} ({ex.description})\n") lines.append(ex.to_prompt_str()) return "\n".join(lines) @property def example_count(self) -> int: return len(self._examples) class MockEmbedding: """模拟 Embedding(实际使用时替换为真实模型)""" async def embed(self, text: str) -> list[float]: # 用 hash 模拟向量,实际请使用 text-embedding-3 等模型 h = hashlib.md5(text.encode()).digest() return list(h) + [0.0] * (256 - 16) # 使用示例 async def main(): embedder = MockEmbedding() selector = DynamicFewShotSelector(embedder) # 添加示例库 examples = [ FewShotExample( example_id="ex1", description="代码审查与 Bug 分析", user_input="帮我 review 这段 Python 代码,找出潜在 bug", assistant_output="发现了 3 个问题:1. 未处理的 None...", tags=["code", "review", "python"], difficulty="medium", ), FewShotExample( example_id="ex2", description="技术文档生成", user_input="为这份 API 生成使用文档", assistant_output="## API 文档\n### 接口说明\n...", tags=["document", "api", "writing"], difficulty="easy", ), FewShotExample( example_id="ex3", description="性能优化分析", user_input="这段代码的数据库查询很慢,帮我优化", assistant_output="建议:1. 为 user_id 添加索引...", tags=["performance", "database", "optimization"], difficulty="hard", ), FewShotExample( example_id="ex4", description="周报与工作总结生成", user_input="我是后端工程师,帮我生成本周工作周报", assistant_output="本周工作总结:\n1. 完成了订单模块重构...", tags=["report", "writing", "weekly"], difficulty="easy", ), ] for ex in examples: await selector.add_example(ex) print(f"示例库共有 {selector.example_count} 个示例\n") # 测试1:代码相关 query query1 = "这段 Python 代码有没有安全问题?" selected1 = await selector.select(query1, k=2) print(f"Query: {query1}") print("选中示例:") for ex in selected1: print(f" - {ex.description}") # 测试2:文档相关 query query2 = "写一份项目周报总结" selected2 = await selector.select(query2, k=2) print(f"\nQuery: {query2}") print("选中示例:") for ex in selected2: print(f" - {ex.description}") # 生成 prompt 示例 prompt = selector.to_prompt_examples(selected2) print(f"\n生成的 Prompt 示例:\n{prompt}") asyncio.run(main())

四、边界分析与架构权衡

动态 Few-shot 选择有几个工程边界:

匹配向量的质量问题。如果示例的描述文本写得不好,匹配精度会大幅下降。建议为每个示例额外编写"搜索关键词"字段,而非完全依赖原始描述。同时,定期评估检索命中率,对低命中率的示例做描述优化。

冷启动问题。示例库刚建立时覆盖的场景可能不够广,导致某些 query 找不到合适的示例。解决方案是设置一个"兜底示例集",当相似度分数普遍低于阈值时,使用兜底示例。兜底示例应该是覆盖范围最广的通用示例。

多样性 vs 相关性的平衡。MMR 的diversity_lambda默认设为 0.5 是经验值,实际需要根据示例库的分布调优。示例库中相同类型的示例多时,应降低 lambda(增加多样性权重);示例类型已经足够多样时,可提高 lambda。

Token 预算控制。Few-shot 示例会占用 prompt token。如果一个示例平均 500 token,选 5 个就是 2500 token。需要根据模型的上下文窗口做修剪:示例过长时做摘要,或者根据 token 预算动态调整 k 值。

效果评估的难度。如何衡量动态选择的 Few-shot 确实比静态的好?可以用人工标注的测试集做 A/B 对比,比较两种方案下模型输出的质量和一致性。这本身又是一个小型评测工程。

五、总结

动态 Few-shot 让 Prompt Engineering 从"写死示例"升级到"数据驱动选择"。核心思路是:把示例库向量化,用 query 语义匹配最相关示例,并通过 MMR 算法保证多样性。

关键要点:

  1. 每个示例需要一个独立的"匹配向量",而非直接用示例原文
  2. 用 MMR 算法平衡相关性和多样性
  3. 设置兜底策略应对冷启动
  4. 注意 token 预算,示例过多反而有害

好的 Few-shot 不是写出来的,是选出来的。

http://www.jsqmd.com/news/1150228/

相关文章:

  • Cockpit 与 3 款传统 CLI 工具对比:系统日志、存储与网络管理的效率实测
  • 终极解决方案:如何在Windows系统中轻松访问Linux磁盘分区
  • Prometheus 无人机仿真平台 Ubuntu 18.04 部署:3个关键编译报错与解决方案
  • Win10 默认浏览器锁死 IE 排查:3 类第三方软件(管家/卫士)的锁定与解除
  • rsync --exclude 高级用法:5个真实场景下的文件同步策略
  • REPENTOGON深度解析:如何为《以撒的结合:悔改》带来革命性脚本扩展
  • Linux 0.11 源码解读:从 5 个关键函数看进程创建与切换的实现
  • Boss直聘批量投递终极指南:8分钟完成100份简历投递的自动化方案
  • Linux 服务器解压实战:tar/zip/rar 3种格式 10个核心命令速查表
  • DnCNN深度学习图像去噪完整指南:从理论到高性能实现
  • PhpStorm 打开加载卡死
  • Cockpit 项目 363 版本:CentOS 8/Ubuntu 22.04 双平台部署与 5 大核心模块配置
  • 3种车牌定位方案对比:OpenCV 颜色分割 vs 边缘检测 vs 形态学操作
  • Linux 到 Windows 10 系统迁移实战:3 种启动盘制作与 5 个常见安装报错解决
  • Seedance 2.5本地部署AI生图视频工具全解析
  • 英雄联盟玩家必备:League-Toolkit 高效游戏助手完全指南
  • WinSCP 6.5.6 与 OpenSSH 客户端:Windows 双工具文件传输效率对比
  • Linux cp/rsync 3种排除方案对比:1000文件场景下性能差异超50%
  • Windows 10 磁盘100%占用排查:5种常见进程分析与针对性优化方案
  • Windows 11 访问 Samba 共享失败:5步定位法从网络到 SELinux
  • Seedance2.5本地部署指南:AI生图与视频生成的完整实践
  • donau-arv-gpu-extension开发者手册:CUDA初始化与内存转换最佳实践
  • Linux history 命令配置实战:3种方法设置 HISTTIMEFORMAT 显示精确时间戳
  • Ubuntu 22.04 安装 yum 实战:3步解决依赖冲突与GPG密钥错误
  • Windows批处理if语句3种多条件组合实战:AND/OR逻辑与延迟变量扩展
  • 本地部署AI生图与视频生成工具:免费无限制的完整实践指南
  • Windows OpenSSH Server 配置:3种防火墙规则与公钥认证权限修复
  • Linux nohup 与 命令实战:SSH断开后进程存活率100%的3个关键参数
  • AI多模型统一接入方案:标准化接口设计与工程实践
  • Volatility 2.6 内存取证实战:Windows 10 镜像中提取 5 类关键进程与网络数据