纲要

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

引言

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

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

静态示例 vs 动态示例

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

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

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

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

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

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

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

其处理流程可用下图表示:

准备完整示例列表

计算前缀与后缀长度

剩余长度 = max_length - 前缀长度 - 后缀长度

从示例列表头部开始逐个添加

添加后总长度 <= max_length?

保留该示例

跳过该示例及后续所有示例

继续下一示例

输出最终选择的示例子集

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

示例代码

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

首先安装依赖:

pip install langchain langchain-core

完整代码:

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 以其简单高效的机制,保证了提示词长度始终处于可控范围内,非常适合输入长度波动较大或示例池丰富的场景。

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

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐