AI Agent白手起家30: 动态示例选择器之根据长度选择 Few Shot 示例

纲要

  • 静态示例与动态示例
    • 静态示例:示例固定在模板中,无法随任务或上下文长度调整
    • 动态示例:根据任务需求和上下文窗口动态选择最合适的示例子集
  • 示例选择器核心概念
    • LengthBasedExampleSelector:基于长度动态选择示例,控制最终提示词的总长度
    • 工作原理:根据 max_length 和已占用的提示词长度,自动截取示例列表
  • 关键组件
    • examples:示例列表(输入-输出对)
    • example_prompt:示例格式化模板
    • FewShotPromptTemplate:组合前缀、示例和后缀的完整提示词模板
  • 示例:Python 脚本,展示长短输入下示例数量的自适应变化

引言

在上一篇关于 Few Shot 提示词工程的文章中,我们介绍了如何通过静态示例让模型学会自定义规则。然而在实际应用中,大模型的上下文窗口是有限且宝贵的资源。如果示例过多或输入过长,就可能超出窗口限制,甚至引入与当前任务无关的噪声。

LangChain 提供了示例选择器(Example Selector)来解决这一问题,其中 LengthBasedExampleSelector 是最简单直接的一种:它根据最终提示词的长度,动态决定使用哪些示例以及使用多少示例,确保提示词始终控制在合理长度内。

静态示例 vs 动态示例

在传统 Few Shot 中,示例是"写死"在模板里的:无论输入什么任务,所有示例都会被完整地拼接到提示词中。这种方式存在两个问题:

  1. 浪费上下文窗口:大量示例可能占用过多 token,挤压实际任务的空间。
  2. 引入无关信息:示例组中包含与当前任务不相关的例子,反而干扰模型输出。

动态示例选择器则允许我们准备一个大而全的示例池,在运行时根据输入长度和上下文窗口,只挑选合适的示例子集注入提示词。

特性 静态示例 动态示例
示例调整 不可变 根据输入长度动态截取
上下文利用 可能浪费或溢出 精确控制在窗口内
实现复杂度 中(使用示例选择器)
适用场景 示例少且固定 示例多、输入长度变化大

根据长度动态选择示例的工作原理

LengthBasedExampleSelector 的核心逻辑是:在格式化提示词时,先计算前缀(系统指令)和后缀(用户问题)的长度,然后用剩余的 max_length 空间,从示例列表中从头开始依次添加示例,直到下一个示例会导致总长度超出限制为止。

这样,较长的输入会自动压缩示例数量,避免超出上下文窗口。

其处理流程可用下图表示:
#mermaid-svg-mqLU5ogBegc36RaW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mqLU5ogBegc36RaW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mqLU5ogBegc36RaW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mqLU5ogBegc36RaW .error-icon{fill:#552222;}#mermaid-svg-mqLU5ogBegc36RaW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mqLU5ogBegc36RaW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mqLU5ogBegc36RaW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mqLU5ogBegc36RaW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mqLU5ogBegc36RaW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mqLU5ogBegc36RaW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mqLU5ogBegc36RaW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mqLU5ogBegc36RaW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mqLU5ogBegc36RaW .marker.cross{stroke:#333333;}#mermaid-svg-mqLU5ogBegc36RaW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mqLU5ogBegc36RaW p{margin:0;}#mermaid-svg-mqLU5ogBegc36RaW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mqLU5ogBegc36RaW .cluster-label text{fill:#333;}#mermaid-svg-mqLU5ogBegc36RaW .cluster-label span{color:#333;}#mermaid-svg-mqLU5ogBegc36RaW .cluster-label span p{background-color:transparent;}#mermaid-svg-mqLU5ogBegc36RaW .label text,#mermaid-svg-mqLU5ogBegc36RaW span{fill:#333;color:#333;}#mermaid-svg-mqLU5ogBegc36RaW .node rect,#mermaid-svg-mqLU5ogBegc36RaW .node circle,#mermaid-svg-mqLU5ogBegc36RaW .node ellipse,#mermaid-svg-mqLU5ogBegc36RaW .node polygon,#mermaid-svg-mqLU5ogBegc36RaW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mqLU5ogBegc36RaW .rough-node .label text,#mermaid-svg-mqLU5ogBegc36RaW .node .label text,#mermaid-svg-mqLU5ogBegc36RaW .image-shape .label,#mermaid-svg-mqLU5ogBegc36RaW .icon-shape .label{text-anchor:middle;}#mermaid-svg-mqLU5ogBegc36RaW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mqLU5ogBegc36RaW .rough-node .label,#mermaid-svg-mqLU5ogBegc36RaW .node .label,#mermaid-svg-mqLU5ogBegc36RaW .image-shape .label,#mermaid-svg-mqLU5ogBegc36RaW .icon-shape .label{text-align:center;}#mermaid-svg-mqLU5ogBegc36RaW .node.clickable{cursor:pointer;}#mermaid-svg-mqLU5ogBegc36RaW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mqLU5ogBegc36RaW .arrowheadPath{fill:#333333;}#mermaid-svg-mqLU5ogBegc36RaW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mqLU5ogBegc36RaW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mqLU5ogBegc36RaW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mqLU5ogBegc36RaW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mqLU5ogBegc36RaW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mqLU5ogBegc36RaW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mqLU5ogBegc36RaW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mqLU5ogBegc36RaW .cluster text{fill:#333;}#mermaid-svg-mqLU5ogBegc36RaW .cluster span{color:#333;}#mermaid-svg-mqLU5ogBegc36RaW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mqLU5ogBegc36RaW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mqLU5ogBegc36RaW rect.text{fill:none;stroke-width:0;}#mermaid-svg-mqLU5ogBegc36RaW .icon-shape,#mermaid-svg-mqLU5ogBegc36RaW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mqLU5ogBegc36RaW .icon-shape p,#mermaid-svg-mqLU5ogBegc36RaW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mqLU5ogBegc36RaW .icon-shape .label rect,#mermaid-svg-mqLU5ogBegc36RaW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mqLU5ogBegc36RaW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mqLU5ogBegc36RaW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mqLU5ogBegc36RaW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

准备完整示例列表
计算前缀与后缀长度
剩余长度 = max_length - 前缀长度 - 后缀长度
从示例列表头部开始逐个添加
添加后总长度 <= max_length?
保留该示例
跳过该示例及后续所有示例
继续下一示例
输出最终选择的示例子集

由于示例是按顺序添加的,因此将最重要、最通用的示例放在列表前面是一个最佳实践,可以保证它们在大部分情况下都会被选中。

示例代码

以下代码基于 LangChain 实现了一个反义词示例的 LengthBasedExampleSelector,并演示了输入较短和较长时,最终提示词中示例数量的动态变化。

首先安装依赖:

bash 复制代码
pip install langchain langchain-core

完整代码:

python 复制代码
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_core.example_selectors import LengthBasedExampleSelector

# 1. 准备反义词示例池
examples = [
    {"input": "happy", "output": "sad"},
    {"input": "高兴", "output": "悲伤"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "up", "output": "down"},
    {"input": "大", "output": "小"},
]

# 2. 定义示例格式化模板
example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="原词: {input}\n反义词: {output}"
)

# 3. 创建长度选择器
#    max_length 控制整个提示词的最大字符数(用于演示,设得较小)
example_selector = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=100,  # 限制最终提示词不超过100个字符
)

# 4. 构建动态 Few Shot 提示词模板
dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="给出每个输入词的反义词:",
    suffix="原词: {input}\n反义词:",
    input_variables=["input"],
)

# 5. 测试:输入较短时,可以容纳更多示例
short_input = "hot"
print("=== 短输入(hot)===")
print(dynamic_prompt.format(input=short_input))
print()

# 6. 测试:输入非常长时,示例会被自动压缩
long_input = "非常" * 30  # 构造一个很长的输入
print("=== 长输入(" + "非常"*30 + ")===")
print(dynamic_prompt.format(input=long_input))

运行后会观察到,短输入时,最终提示词中包含了多个示例(如 happy、高兴、sunny 等);而长输入时,由于用户问题本身就占用了大量长度,示例会被压缩到只剩一两个甚至完全没有,确保总长度不超过 max_length

使用建议与注意事项

  • 示例排序很重要:选择器从前往后挑选示例,因此将典型、通用的示例排在前面,能保证在截断时优先保留高质量示例。
  • 合理设置 max_length :该值应略小于模型的实际上下文窗口长度(以字符或 token 计),为回答预留一定空间。若使用 token 分词器,可以自定义 get_text_length 函数以 token 数计算长度。
  • 配合其他选择器 :当仅按长度选择不够智能时,LangChain 还提供了 SemanticSimilarityExampleSelector 等基于语义相似度的选择器,可以实现"选择与输入最相关的示例",后续文章将详细介绍。

总结

动态示例选择器是 Few Shot 提示工程走向实用化的关键一环。LengthBasedExampleSelector 以其简单高效的机制,保证了提示词长度始终处于可控范围内,非常适合输入长度波动较大或示例池丰富的场景。

结合良好的示例排序策略,可以在不增加额外计算开销的前提下,显著提升大模型应用的稳定性和响应质量。

相关推荐
oyguyteggytrrwwwrt2 小时前
自制交叉线路识别算法
算法
手写码匠2 小时前
华为云Flexus+DeepSeek征文|Dify 多智能体协同编排实战:R1 规划 + V3 执行,构建企业 Agent 团队
人工智能·深度学习·算法·aigc
晓天衡宇•评测社区3 小时前
FSR-Bench 前沿科学推理榜单发布:GPT-5.5 居首,“会推理”未必“能答对”
算法
程序喵大人4 小时前
【C++进阶】STL算法与函数对象 - 02 sort为什么需要随机访问迭代器
开发语言·c++·算法
hanlin035 小时前
动态规划专练:力扣第121、122题
笔记·算法·leetcode
To_OC5 小时前
LC 74 搜索二维矩阵:换皮的二分查找,我居然一开始没看出来
javascript·算法·leetcode
文心快码BaiduComate5 小时前
文心快码荣获“中国优秀软件产品”,实力再获国家级认可
算法·代码规范
玖玥拾5 小时前
LeetCode 189 轮转数组
算法·leetcode
ZhangShao06075 小时前
题解:CF2249B
c++·算法