核心组件——少样本提示few-shotting、示例选择器

3.1 概念

少样本提示是一种通过向LLM提供少量具体样本,来教会它如何执行某项特定任务的技术。提高模型性能的最有效方法之一是给出一个【模型示例】指导大模型你想做什么、怎么做。

参考之前学习的内容,我们可以将其想象为"零样本提示":直接给模型一道考题,不给任何例子。模型会凭借已有的知识直接回答。

复制代码
from langchain_openai import ChatopenAI
model = ChatopenAI (model="gpt-4o-mini")model.invoke("What is 2 🦜 9?").pretty_print()

# 输出:
==== Ai Message =====
表达式"2 🦜 9"不是一个标准的数学运算或方程。它似乎是数字2和🦜的组合,后面跟着数字9
它没有特定的数学意义。

少样本提示则是在给出考题前,先给它看几道类似的、附有正确答案的例题。

添加示例输入和预期输出的技术给到模型提示,让模型通过例题来理解任务应该怎么做。

例如以下样例,模型通过样例后可以发现,🦜与加号含义相似,便可以按照此规则得出What is 2🦜9?的结果是11

复制代码
examples = [
    {"input": "2 🦜 2", "output": "4"},
    {"input": "2 🦜 3", "output": "5"},
]

这能解决什么问题?

LLM虽然知识渊博,但有时我们需要它以非常特定的格式、风格或逻辑来回答问题。提供正确的示例,可以减少模型"胡说八道"或犯低级错误的概率,将其输出约束在你提供的范例范围内。

举个例子更能理解:

1.强制要求模型以特定的格式(如JSON、XML、特定的列表样式)输出结果。样例可以当作格式样板。

2.有些任务很难用文字指令清晰描述(例如:"请用莎士比亚的风格写作")。提供几个例子比写长

篇大论的指令更有效。

3.对于需要多步推理的复杂任务,示例可以展示出思考链,引导模型遵循类似的推理路径。

3.2 实现少样本提示

实现少样本提示的第一步也是最重要的一步是提出一个好的示例数据集。好的示例应该在运行时相关、清晰、信息丰富,并提供模型尚不知道的信息。

如何让大模型看懂这份示例呢?之前我们说过聊天模型读的是聊天消息。因此,接下来我们需要将示例集实例化成聊天模型可以读懂的聊天消息。

对于LangChain就需要创建一个FewShotChatMessagePromptTemplate对象来实例化示例集。

FewShotChatMessagePromptTemplate也是一个提示词模板,专门用来将示例集实例化为聊天消

息。

复制代码
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate

# 1. 定义少样本样例
examples = [
    {"text": "hi, what is your name?", "output": "你好,你叫什么名字?"},
    {"text": "hi, what is your age?", "output": "你好,你多大了?"},
]

# 2. 定义消息模板
examples_prompt_template = ChatPromptTemplate(
    [
        ("user", "{text}"),
        ("ai", "{output}"),
    ]
)

# 3. 把样例组装为消息模板
few_shot_template = FewShotChatMessagePromptTemplate(
    examples=examples,
    example_prompt=examples_prompt_template
)

# 4. 定义聊天模板
chat_prompt_template = ChatPromptTemplate.from_messages([
    ("system", "将文本从{language_from}翻译为{language_to}"),
    few_shot_template,
    ("user", "{text}"),
])

# 渲染prompt,传入变量
final_prompt = chat_prompt_template.invoke({
    "language_from": "英文",
    "language_to": "中文",
    "text": "where are you from?"
})

print(final_prompt.to_messages())

# 输出
#[
# SystemMessage(content='将文本从英文翻译为中文', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='hi, what is your name?', additional_kwargs={}, response_metadata={}),
# AIMessage(content='你好,你叫什么名字?', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='hi, what is your age?', additional_kwargs={}, response_metadata={}),
# AIMessage(content='你好,你多大了?', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='where are you from?', additional_kwargs={}, response_metadata={})
# ]

class langchain_core.prompts.few_shot.FewShotChatMessagePromptTemplate,它也实现了标准的 Runnable 接口。

类初始化参数说明:

  • examples:样本示例
  • example_prompt:ChatPromptTemplate,用于格式化单个示例

类方法说明:

  • .invoke() 方法:此方法与其他 Runnable 实例的 .invoke() 方法类似。输入一个字典给它,返回完整的提示内容 PromptValue:
    • PromptValue 的 to_string() 方法可以将提示值作为【字符串】返回。
    • PromptValue 的 to_messages() 方法可以将提示作为【消息列表】返回。

最后,得到示例集消息列表后,就可以带上一起发起请求:

复制代码
final_prompt = ChatPromptTemplate(
    [
        ("system", "你是一个神奇的数学奇才。"),
        few_shot_prompt,
        ("human", "{input}"),
    ]
)

chain = final_prompt | model
chain.invoke({"input": "What is 2 🦜 9?"}).pretty_print()

# 输出
================================== Ai Message==================================
11

3.3 使用案例

3.3.1 案例一:推理引导

我们希望输入: 《教父》和《星球大战》的导演来自同一个国家吗?

让聊天模型可以先分析再得出结论,而不是直接得出结论。分析过程需要展示出来,示例如下:

复制代码
# 创建字符串模板
example_prompt = PromptTemplate.from_template("Question: {question}\n{answer}")

# 创建示例集
examples = [
    {
        "question": "李白和杜甫,谁更长寿?",
        "answer": """是否需要后续问题: 是的。
        后续问题: 李白享年多少岁?
        中间答案: 李白享年61岁。
        后续问题: 杜甫享年多少岁?
        中间答案: 杜甫享年58岁。
        所以最终答案是: 李白"""
    },
    {
        "question": "腾讯的创始人什么时候出生?",
        "answer": """是否需要后续问题: 是的。
        后续问题: 腾讯的创始人是谁?
        中间答案: 腾讯由马化腾创立。
        后续问题: 马化腾什么时候出生?
        中间答案: 马化腾出生于1971年10月29日。
        所以最终答案是: 1971年10月29日"""
    },
    {
        "question": "孙中山的外祖父是谁?",
        "answer": """是否需要后续问题: 是的。
        后续问题: 孙中山的母亲是谁?
        中间答案: 孙中山的母亲是杨太夫人。
        后续问题: 杨太夫人的父亲是谁?
        中间答案: 杨太夫人的父亲是杨胜辉。
        所以最终答案是: 杨胜辉"""
    },
    {
        "question": "电影《红高粱》和《霸王别姬》的导演来自同一个国家吗?",
        "answer": """是否需要后续问题: 是的。
        后续问题: 《红高粱》的导演是谁?
        中间答案: 《红高粱》的导演是张艺谋。
        后续问题: 张艺谋来自哪里?
        中间答案: 中国。
        后续问题: 《霸王别姬》的导演是谁?
        中间答案: 《霸王别姬》的导演是陈凯歌。
        后续问题: 陈凯歌来自哪里?
        中间答案: 中国。
        所以最终答案是: 是"""
    }
]

# 测试: 实例化第一个示例
print(example_prompt.invoke(examples[0]).to_string())

# 输出
Question:李白和杜甫,谁更长寿?
是否需要后续问题:是的。
后续问题:李白享年多少岁?
中间答案:李白享年61岁。
后续问题:杜甫享年多少岁?
中间答案:杜甫享年58岁。
所以最终答案是:李白

接下来我们需要格式化完整的样本提示。

此时可以创建一个FewShotPromptTemplate对象来初始化少样本提示模板。其接收少量示例和少量示例的格式化程序。如下所示:

复制代码
prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,  # PromptTemplate,用于格式化单个示例
    suffix="Question: {input}",      # 放在示例之后的提示模板字符串。
    input_variables=["input"],       # 变量的名称列表,这些变量的值需要作为提示词的输入。
)

print(
    prompt.invoke({"input": "《教父》和《星球大战》的导演来自同一个国家吗?"}).to_string()
)

# 输出
Question: 李⽩和杜甫,谁更⻓寿?
是否需要后续问题:是的。
后续问题:李⽩享年多少岁?
中间答案:李⽩享年61岁。
后续问题:杜甫享年多少岁?
中间答案:杜甫享年58岁。
所以最终答案是:李⽩
Question: 腾讯的创始⼈什么时候出⽣?
是否需要后续问题:是的。
后续问题:腾讯的创始⼈是谁?
中间答案:腾讯由⻢化腾创⽴。
后续问题:⻢化腾什么时候出⽣?
中间答案:⻢化腾出⽣于1971年10⽉29⽇。
所以最终答案是:1971年10⽉29⽇
Question: 孙中⼭的外祖⽗是谁?
是否需要后续问题:是的。
后续问题:孙中⼭的⺟亲是谁?
中间答案:孙中⼭的⺟亲是杨太夫⼈。
后续问题:杨太夫⼈的⽗亲是谁?
中间答案:杨太夫⼈的⽗亲是杨胜辉。
所以最终答案是:杨胜辉
Question: 电影《红⾼粱》和《霸王别姬》的导演来⾃同⼀个国家吗?
是否需要后续问题:是的。
后续问题:《红⾼粱》的导演是谁?
中间答案:《红⾼粱》的导演是张艺谋。
后续问题:张艺来⾃哪⾥?
中间答案:中国。
后续问题:《霸王别姬》的导演是谁?
中间答案:《霸王别姬》的导演是陈凯歌。
后续问题:陈凯歌来⾃哪⾥?
中间答案:中国。
所以最终答案是:是
Question:《教父》和《星球大战》的导演来自同一个国家吗?

suffix="Question: {input}" 所有示例全部渲染完毕后,追加在最后面的文本 。 {input}是占位符,运行时会替换成用户新问题。

还有一个配套可选参数叫prefix,放在所有示例最前面,这里省略没写。

input_variables=["input"] 声明模板用到的动态变量名。 告诉 LangChain:调用这个模板的时候,必须传入input这个 key,否则会报错。

内部执行流程:

① 遍历examples,用example_prompt渲染每一条样本,拼接在一起

② 渲染suffix,把{input}替换成《教父》和《星球大战》的导演来自同一个国家吗?,追加在所有样本末尾

③ 返回PromptValue对象
接下来让我们调用聊天模型看看结果

复制代码
# ⽣成 [HumanMessage] 消息列表
prompt_messages = prompt.invoke({"input": "《教⽗》和《星球⼤战》的导演来⾃同⼀个国家吗?"}).to_messages()

# 定义⼤模型
model = ChatOpenAI(model="gpt-4o-mini")
model.invoke(prompt_messages).pretty_print()

# 输出
================================== Ai Message ==================================
是否需要后续问题:是的。
后续问题:《教⽗》的导演是谁?
中间答案:《教⽗》的导演是弗朗西斯·福特·科波拉。
后续问题:弗朗西斯·福特·科波拉来⾃哪⾥?
中间答案:弗朗西斯·福特·科波拉来⾃美国。
后续问题:《星球⼤战》的导演是谁?
中间答案:《星球⼤战》的导演是乔治·卢卡斯。
后续问题:乔治·卢卡斯来⾃哪⾥?
中间答案:乔治·卢卡斯也来⾃美国。
所以最终答案是:是

3.3.2 案例二:使用示例数据增强LangChain信息提取能力

例如我们希望,对于输入以下文本:

"篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老友用十年配合弥补了身高的差距。"

代码会提取出结构化数据,如下所示:

复制代码
people=[
    Person(name="王伟", height_in_meters="2", skin_color=None, hair_color=None),
    Person(name="李明", height_in_meters="1.7", skin_color=None,hair_color=None)
]

实现如下:
第一步:定义结构化返回对象。

复制代码
class Person(BaseModel):
    """一个人的信息。"""
    name: Optional[str] = Field(default=None, description="这个人的名字")
    hair_color: Optional[str] = Field(default=None, description="如果知道这个人头发的颜色")
    skin_color: Optional[str] = Field(default=None, description="如果知道这个人的肤色")
    height_in_meters: Optional[str] = Field(default=None, description="以米为单位的高度")

class Data(BaseModel):
    """提取关于人的数据。"""
    people: List[Person]

第二步:定义两个关键示例,每个示例中包含【文本】和【希望输出】:

  1. 无人物场景 :文本中没有人名,期望返回空列表
    • 规则:原文完全不存在人物,people = [](空列表,不要创建 Person 对象)
  2. 部分信息场景 :只有名字,其他字段缺失
  • 规则:识别到人,但身高、发色、肤色原文没写,这些字段赋值None,不能编造内容

    定义2个示例

    examples = [
    (
    "海洋是广阔而蓝色的。它有两万多英尺深。",
    Data(people=[]), # 没有人物信息的情况
    ),
    (
    "小强从中国远行到美国。",
    Data(people=[
    Person(name="小强", height_in_meters=None, skin_color=None, hair_color=None),
    ]), # 部分信息缺失的情况
    ),
    ]

第三步:定义提示词模板

复制代码
# 定义提示词模板
prompt_template = ChatPromptTemplate(
    [
        SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null"),
        MessagesPlaceholder("example_messages"),
        ("user", "{new_message}"),
    ]
)

第四步:构造请求的消息列表。

处理逻辑:

  • 遍历每个示例对:文本、期望输出

  • 根据 是否有人员信息 生成自然语言响应:"检测到人" or "未检测到人"

  • 使用 tool_example_to_messages 转换格式,将每个示例转换为模型可理解的消息格式

    样例消息列表,用来存放全部few-shot对话样例

    example_messages = []

    遍历 (输入文本txt, 期望抽取结果tool_call) 的示例对

    for txt, tool_call in examples:
    # 根据抽取结果,生成AI的文字回复
    if tool_call.people:
    ai_response = "检测到人"
    else:
    ai_response = "未检测到人"

    复制代码
      # 将一组样例(用户输入 + 预期结构化输出)转为消息列表
      message_group = tool_example_to_messages(
          txt,           # 示例的输入
          [tool_call],   # 准确的参考标准,就是前面写的 `Data(BaseModel)` 的实例
          ai_response=ai_response  # 让LLM强制返回ai_response 
      )
      # message_group 是 list[BaseMessage],例如 [HumanMessage, AIMessage, ToolMessage]
      
      # 把message_group里面每一条消息,逐个追加进example_messages
      example_messages.extend(message_group)

tool_example_to_messages 方法说明:

  • 此功能处于测试阶段。它正在积极开发中,因此 API 可能会发生变化。
  • 此方法是一个工具方法,它可以将单个示例转换为聊天模型可以识别的消息列表。
  • 参数说明:
    • input:输入字符串
    • tool_calls:listBaseModel,表示为 Pydantic BaseModel 的工具调用列表
    • ai_response:可选的。如果提供,将是最终 AIMessage 的内容。
  • 返回值:listBaseMessage 消息列表
复制代码
# 实例化提示词
formatted_prompt = prompt_template.invoke({
    "example_messages": example_messages,
    "new_message": "篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。 这对老友用十年配合弥补了身高的差距。"
})

# 打印
for message in formatted_prompt.to_messages():
    message.pretty_print()

# 输出
============================== System Message ============================== 
你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回 null 
============================= Human Message ============================== 
海洋是广阔而蓝色的。它有两万多英尺深。 
============================== Ai Message ============================== 
Tool Calls:   Data (f81f0c81-108d-4c06-88dc-9e5f5289749e)  Call ID: f81f0c81-108d-4c06-88dc-9e5f5289749e  Args:   people: [] 
============================== Tool Message ============================== 
You have correctly called this tool.
============================== Ai Message ============================== 
未检测到人 
============================= Human Message ============================== 
小强从中国远行到美国。 
============================== Ai Message ============================== 
Tool Calls:   Data (ace46c82-07e0-455c-aaef-c13da7328e45)  Call ID: ace46c82-07e0-455c-aaef-c13da7328e45  Args:   people: [{'name': '小强', 'hair_color': None, 'skin_color': None, 'height_in_meters': None}] 
============================== Tool Message ==============================  
You have correctly called this tool. 
============================== Ai Message ==============================  
检测到人 
============================== Human Message ==============================  
篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老友用十年配合弥补了身高的差距。 

消息已经被我们构造成功!可以看到,一个示例经过转换构建出了4条消息:HumanMessage、AiMessage(tool)、ToolMessage、AiMessage。

第五步:初始化模型并绑定结构化输出模式,最后构件链,并调用。

复制代码
# 定义结构化输出模型,自动解析为 Pydantic 对象
model = ChatOpenAI(model="gpt-4o-mini")
structured_model = model.with_structured_output(schema=Data)

chain = prompt_template | structured_model
result = chain.invoke({
    "example_messages": example_messages,
    "new_message": "篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老友用十年配合弥补了身高的差距。"
})

print("\n")
print(result)

# 输出
people=[
    Person(name='王伟', hair_color=None, skin_color=None,height_in_meters='2.0'),
    Person(name='李明', hair_color=None, skin_color=None,height_in_meters='1.7')
]

完整代码

复制代码
# ===================== 1. 定义结构化输出 Pydantic 模型 =====================
class Person(BaseModel):
    """一个人的信息。"""
    name: Optional[str] = Field(default=None, description="这个人的名字")
    hair_color: Optional[str] = Field(default=None, description="如果知道这个人头发的颜色")
    skin_color: Optional[str] = Field(default=None, description="如果知道这个人的肤色")
    height_in_meters: Optional[str] = Field(default=None, description="以米为单位的高度")

class Data(BaseModel):
    """提取关于人的数据。"""
    people: List[Person]


# ===================== 2. 定义 few-shot 示例 =====================
examples = [
    (
        "海洋是广阔而蓝色的。它有两万多英尺深。",
        Data(people=[]),  # 没有人物信息的情况
    ),
    (
        "小强从中国远行到美国。",
        Data(people=[
            Person(name="小强", height_in_meters=None, skin_color=None, hair_color=None),
        ]),  # 部分信息缺失的情况
    ),
]


# ===================== 3. 提示词模板 =====================
prompt_template = ChatPromptTemplate([
    SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null"),
    MessagesPlaceholder("example_messages"),
    ("user", "{new_message}"),
])


# ===================== 4. 把示例转换成对话消息列表 =====================
# 工具:把(文本, Data对象)转为4条消息 Human/Ai(tool)/ToolMessage/Ai
def tool_example_to_messages(input_txt, tool_call_list, ai_response):
    from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
    messages = []
    messages.append(HumanMessage(content=input_txt))
    # 把pydantic转成tool call
    ai_msg = AIMessage(
        tool_calls=[
            {
                "name": "Data",
                "args": tc.model_dump(),
                "id": f"fake-id-{i}"
            }
            for i, tc in enumerate(tool_call_list)
        ]
    )
    messages.append(ai_msg)
    for tool_call in ai_msg.tool_calls:
        tool_msg = ToolMessage(content="You have correctly called this tool.", tool_call_id=tool_call["id"])
        messages.append(tool_msg)
    messages.append(AIMessage(content=ai_response))
    return messages

example_messages = []
# 遍历示例对,将每个示例构造成聊天消息
for txt, tool_call in examples:
    # 根据提取结果生成AI响应文本
    if tool_call.people:
        ai_response = "检测到人"
    else:
        ai_response = "未检测到人"

    # 将示例转换为模型可理解的消息格式
    example_messages.extend(
        tool_example_to_messages(txt, [tool_call], ai_response=ai_response)
    )


# ===================== 5. 初始化模型、构建链并调用 =====================
# 定义结构化输出模型,自动解析为 Pydantic 对象
model = ChatOpenAI(model="gpt-4o-mini")
structured_model = model.with_structured_output(schema=Data)

# 定义链
chain1 = prompt_template | structured_model
chain2 = prompt_template | model
result = chain1.invoke({
    "example_messages": example_messages,
    "new_message": "篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老友用十年配合弥补了身高的差距。"
})

print("\n")
print(result)

总结:该案例展示了 LangChain 在结构化信息提取中的核心能力:通过 Pydantic 定义输出模式、少样本学习引导模型行为、提示词模板动态组装上下文,以及链式调用简化流程。最终实现从非结构化文本中精准提取结构化数据。

3.4 示例选择器(Example selectors)

3.4.1 概念

一旦我们有了示例数据集,就需要考虑提示中应该有多少个示例。关键的权衡是,更多的示例通常会提高性能,但更大的提示会增加成本和延迟。超过某个阈值,太多示例可能会开始混淆模型。找到正确数量的示例在很大程度上取决于模型、任务、示例的质量以及成本和延迟限制。

有趣的是,模型越好,它需要精准的示例就越少。但其实,最佳的方法是使用不同数量的示例进行一些实验。若此时我们有【大量】的示例数据集。对于大模型来说,就没必要全部使用与参考。我们需要有一种方法可以根据给定的输入,从数据集中选择示例。

在LangChain中,示例选择器就可以帮我们从一组【示例的集合】中根据具体策略选择正确的【示例子集】构建少样本提示。

选择策略有:

  1. Length:根据特定【长度】内可以容纳的数量选择示例。
  2. Similarity:使用输入和示例之间的【语义相似性】来决定选择哪些示例。
  3. MMR:使用输入和示例之间的【最大边际相关性】来决定要选择哪些示例。
  4. Ngram:使用输入和示例之间的【ngram重叠】来决定要选择哪些示例。

这些其实都是自然语言处理(NLP)里的相似性衡量问题。

3.4.2 按长度选择示例 (Length)

当我们担心构造提示时,将超过上下文窗口长度,根据特定长度内可以容纳的数量选择示例。对于较长的输入,它将选择更少的示例来包含;而对于较短的输入,它将选择更多示例。

长度统计单位:默认是单词数量,不是字符、不是 token。

实现按长度选择示例的示例选择器是:class langchain_core.example_selectors. length_based. LengthBasedExampleselector类

参数如下:

  • example_prompt:PromptTemplate,用于格式化示例的提示模板。
  • examples:模板所需的示例列表。
  • max_length:提示的最大长度,超过该长度将剪切示例。
  • get_text_length:测量提示长度的方法。默认为字数统计。

内置方法:

  • add_example(example:dictstr,str):将新示例添加到列表中。

输入:一个字典,其中键作为输入变量,值作为其值。

  • select_examples(input_variables: dictstr, str) -> listdict : 根据输入长度选择要使用的示例。

输入:一个字典,其中键作为输入变量,值作为其值。

输出:要包含在提示中的示例列表。


接下来,演示一下如何使用长度示例选择器,让我们:

1.给一个示例集,输入和输出互为反义词

2.定义PromptTemplate字符串模板,包含输入和输出两个"占位符"

3.定义LengthBasedExampleSelector 长度示例选择器,设置初始示例集与最大长度

4.定义一个FewShotPromptTemplate模板对象,用于实例化示例,将示例转化为聊天消息

5.打印消息结果

复制代码
# 反义词示例集合
examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
]

# 字符串模板:定义单个样例的渲染格式
example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
)

# 长度示例选择器:根据总文本长度动态挑选样例
example_selector = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=25,  # 所有选中样例拼接后的最大单词数上限
)

# 动态少样本提示模板:使用选择器,而不是固定写死examples
dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="给出每个输入的反义词",
    suffix="Input: {adjective}\nOutput:",
    input_variables=["adjective"],
)

# 渲染prompt,输入单词big,打印最终完整提示词
print(
    dynamic_prompt.invoke({"adjective": "big"}).to_messages()[0].content
)

# 输出 
给出每个输入的反义词
Input: happy
Output: sad
Input: tall
Output: short
Input: sunny
Output: gloomy
Input: windy
Output: calm
Input: big
Output:

.to_messages():是PromptValue 提供的方法:把纯文本 Prompt 包装成消息对象列表

[0]:取消息列表中下标为 0 的元素,也就是那唯一一条 HumanMessage 对象

可以看到,示例选择器将所有的示例都选择了,因为还没有超过设置的示例最大长度,长度计算是根据字符串中由换行符或空格分隔的"单词"数量得到的(默认)。

接下来让我们加入长输入,验证长度选择器筛选示例。代码如下:

复制代码
#一个长的示例输入,所有示例超出最大长度
long_string= "非常非常非常非常 非常非常非常非常非常非常非常非常非常非常非常 非常大,比其他任何东西都要大得多"
print( dynamic_prompt.invoke({"adjective": long_string}).to_messages()[0].content )

# 输出
给出每个输⼊的反义词
Input: happy
Output: sad
Input: tall
Output: short
Input: ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常 ⾮常
⼤,⽐其他任何东西都要⼤得多
Output:

长输入占用了大量单词额度,但还有一点点余量,足够塞下前 2 条样例,再加第三条就超限,因此只保留 happy、tall。 不是一次性全部删掉,而是从前往后依次加入样例,累加单词,到上限就停止。

样例选取策略:按 examples 列表顺序,贪心向前取,直到总长度触达上限。不做语义匹配。

3.4.3 按语义相似性选择示例(Similarity)

什么是语义相似?

它是衡量文本在【含义上】的接近程度。

例如:text1 = "我喜欢猫" text2 = "我讨厌狗"

这两段文本表面相似度低,但语义上都是表达对动物的态度。

再例如:text1 = "苹果很甜" text2 = "苹果市值创新高"

"苹果"可以指水果或公司,语义相似可以解决一词多义问题,因此这两段文本语义上不相似。

LangChain 能根据输入和示例之间的语义相似性来决定选择哪些示例,它通过查找与输入具有最大余弦相似性的嵌入示例来实现这一点。

实现按语义相似性选择示例的示例选择器是:class langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector类

内置方法:

from_examples():根据示例集生成语义相似示例选择器

  • 输入:
    • examples:示例列表
    • embeddings:初始化的嵌入 API 接口,如 OpenAIEmbeddings()
    • vectorstore_cls:向量存储数据库接口类。
    • k:最终要选择的示例的数量。默认值为 4。
  • 输出:语义相似示例选择器

add_example(example: dict[str, str]):将新示例添加到列表中。

  • 输入:一个字典,其中键作为输入变量,值作为其值。

select_examples(input_variables: dict[str, str]) -> list[dict]:根据输入选择要使用的示例。

  • 输入:一个字典,其中键作为输入变量,值作为其值。
  • 输出:要包含在提示中的示例列表。

接下来,演示一下如何使用语义相似示例选择器,让我们:

1.给一个示例集,输入和输出互为反义词

2.定义PromptTemplate字符串模板,包含输入和输出两个"占位符"

入API接口

3.定义SemanticSimilarityExampleSelector 语义相似示例选择器,设置初始示例集与嵌

定义一个FewShotPromptTemplate模板对象,用于实例化示例,将示例转化为聊天消息

5.打印消息结果

复制代码
# 反义词示例集合
examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
]

# 字符串模板
example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
)

# 语义相似示例选择器
example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,
    # 嵌入类用于生成用于度量语义相似度的嵌入。
    OpenAIEmbeddings(),
    # VectorStore类,用于存储嵌入并对其进行相似性搜索。
    Chroma,
    # 生成示例的数量。
    k=1,
)

# 用于实例化示例的模板
similar_prompt = FewShotPromptTemplate(
    # 提供了一个ExampleSelector而不是examples。
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="给出每个输入的反义词",
    suffix="Input: {adjective}\nOutput:",
    input_variables=["adjective"],
)

print(
    similar_prompt.invoke({"adjective": "worried"}).to_messages()[0].content
)

# 输出
给出每个输⼊的反义词
Input: happy
Output: sad
Input: worried
Output:

3.4.4 按最大边际相关性选择示例(MMR)

什么是最大边际相关性?它是一种重新排序算法,它使用语义相似性作为基础工具,从一个候选集中挑选出一组既能代表查询主题又彼此多样化的结果。

听起来好像和语义相似性类似,用一个例子看下两者的区别:

【语义相似性】就像面试官衡量每个应聘者与职位要求的匹配度。他会给每个应聘者打一个分数。【最大边际相关性】就像团队经理(MMR算法)要组建一个团队。目标是选出一组"精华"结果,而不是一个单一结果:

  • 每个成员都要满足基本职位要求(满足相关性)。
  • 但经理不希望团队里全是只会一种技能的程序员。他需要前端、后端、算法、测试等不同专长的人,以确保团队能力全面、减少冗余(新颖性/多样性)。
  • 经理的策略是:先招一个最匹配的技术大牛(第一步),然后接下来招的人,既要技术达标,又要和已招的人技能互补(迭代过程)。

了解下使用最大边际相关性的场景,更能让我们理解其概念:

  • 语义相似性使用场景:搜索引擎的基础排序、重复检测、聚类、语义搜索。

MMR使用场景:

。推荐系统:推荐与用户兴趣相关但又不同类型的物品,避免"信息茧房"。

。文档摘要:从长文档中选择能代表主旨又包含不同信息的句子,避免摘要内容重复。

。RAG(检索增强生成):在从知识库检索完一堆相关文档后,使用MMR进行去重和多样化筛选,再交给LLM生成答案,能有效提升答案质量和减少幻觉。


LangChain 提供了按最大边际相关性选择示例的能力,该示例选择器是: class langchain_core.example_selectors.semantic_similarity.MaxMarginalRelevanceExampleSelector类

内置方法:

from_examples():根据示例集生成 MMR 示例选择器

  • 输入:
    • examples:示例列表
    • embeddings:初始化的嵌入 API 接口,如 OpenAIEmbeddings()
    • vectorstore_cls:向量存储数据库接口类。
    • k:最终要选择的示例的数量。默认值为 4。
  • 输出:MMR 示例选择器

add_example(example: dict[str, str]):将新示例添加到列表中。

  • 输入:一个字典,其中键作为输入变量,值作为其值。

select_examples(input_variables: dict[str, str]) -> list[dict]:根据输入选择要使用的示例。

  • 输入:一个字典,其中键作为输入变量,值作为其值。
  • 输出:要包含在提示中的示例列表。

接下来,演示一下如何使用 MMR 示例选择器,让我们:

  1. 给一个示例集,输入和输出互为反义词

  2. 定义 PromptTemplate 字符串模板,包含输入和输出两个"占位符"

  3. 定义 MaxMarginalRelevanceExampleSelector MMR 示例选择器,设置初始示例集与嵌入 API 接口

  4. 定义一个 FewShotPromptTemplate 模板对象,用于实例化示例,将示例转化为聊天消息

  5. 打印消息结果

    反义词示例集合

    examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
    ]

    单个样例模板

    example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
    )

    MMR最大边际相关性选择器

    example_selector = MaxMarginalRelevanceExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    Chroma,
    k=2
    )

    FewShot模板

    mmr_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="给出每个输入的反义词",
    suffix="Input: {adjective}\nOutput:",
    input_variables=["adjective"],
    )

    print(
    mmr_prompt.invoke({"adjective": "worried"}).to_messages()[0].content
    )

    输出

    给出每个输⼊的反义词
    Input: happy
    Output: sad
    Input: windy
    Output: calm
    Input: worried
    Output:

MMR最大边际相关性:在保证和query相关 的前提下,最大化选中样例之间的差异,提升样例多样性,减少冗余。


3.4.5 通过ngram重叠选择示例(Ngram)

什么是【ngram】?ngram 指一个文本序列中连续的 n 个词(word)或字符(character)。

什么是【ngram 重叠】?通过计算它们之间共同拥有的 ngram 数量来一种衡量两段文本相似度的方法。

text1 = "苹果手机很好用" (分词后: 苹果 手机 很 好用)

text2 = "这款手机很好用" (分词后: 这款 手机 很 好用)

这两段文本单词重复度很高,连续三个词的相同的情况也存在,因此 ngram 重叠高。

再看个例子:

text1 = "苹果手机很好用" (分词后: 苹果 手机 很 好用)

text2 = "iPhone 非常不错" (分词后: iPhone 非常 不错)

这两段文本在含义上非常相似,但它们的 ngram 重叠度为 0。

因此,传统 ngram 重叠是一种表面形式的匹配,它只关心词是否完全一样,但对于同义词却无法处理。

什么是【语义ngram重叠】?不再比较词本身,而是比较词背后的语义向量(Embedding)。也就是说,它不是看两个词 苹果 和 iPhone 的字面是否相同,而是计算它们在语义空间中的向量是否相似。如果相似度超过某个阈值,就认为它们"重叠"了。还是看这个例子:

text1 = "苹果手机很好用" (分词后: 苹果 手机 很 好用)

text2 = "iPhone 非常不错" (分词后: iPhone 非常 不错)

  • 计算 苹果 和 iPhone 的向量相似度 → 得分 0.95 (很高,视为重叠)
  • 计算 手机 和 iPhone 的向量相似度 → 得分 0.88 (很高,但可能不会同时计分,取决于算法设计,避免重复计算)
  • 计算 很 和 非常 的向量相似度 → 得分 0.90 (很高,视为重叠)
  • 计算 好用 和 不错 的向量相似度 → 得分 0.82 (很高,视为重叠)

最终,语义上的 unigram 重叠度可能为3或4(非常相似!)

那么语义ngram重叠的使用场景是什么?语义ngram重叠常用于需要更精准语义评估的场景,例如剽窃检测,能够发现那些改换了词汇但保留了核心思想的"智能"剽窃。


LangChain 实现按语义 ngram 重叠选择示例的示例选择器是: class langchain_community.example_selectors.ngram_overlap.NGramOverlapExampleSelector类,其参数如下:

  • example_prompt:PromptTemplate,用于格式化示例的提示模板。
  • examples:模板所需的示例列表。
  • threshold:算法停止的阈值。默认设置为 -1.0。
    • 对于负阈值:按重叠分数对示例进行排序,但不排除任何示例。
    • 对于等于0.0的阈值:按重叠分数进行排序,并排除与输入没有ngram重叠的示例。
    • 对于大于1.0的阈值:排除所有示例,并返回一个空列表。

内置方法:

  • add_example(example: dict[str, str]):将新示例添加到列表中。
    • 输入:一个字典,其中键作为输入变量,值作为其值。
  • select_examples(input_variables: dict[str, str]) -> list[dict]:返回根据输入得到的重叠分数排序的降序示例列表。
    • 输入:一个字典,其中键作为输入变量,值作为其值。
    • 输出:要包含在提示中的示例列表。

接下来,演示一下如何使用语义相似示例选择器,让我们:

  1. 给一个示例集,设置相关文本

  2. 定义 PromptTemplate 字符串模板,包含输入和输出两个"占位符"

  3. 定义 NGramOverlapExampleSelector 示例选择器,设置初始示例集与阈值(-1),表示对示例进行排序,但不排除任何示例。

  4. 定义一个 FewShotPromptTemplate 模板对象,用于实例化示例,将示例转化为聊天消息

  5. 打印消息排序结果

    翻译示例

    examples = [
    {"input": "See Spot run.", "output": "看见Spot跑。"},
    {"input": "My dog barks.", "output": "我的狗叫。"},
    {"input": "Spot can run.", "output": "Spot可以跑。"},
    ]

    字符串模板

    example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
    )

    NGram 示例选择器

    example_selector = NGramOverlapExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    threshold=-1.0,
    )

    用于实例化示例的模板

    dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="给出每个输入的中文翻译",
    suffix="Input: {sentence}\nOutput:",
    input_variables=["sentence"],
    )

    按照重叠分数排序

    print(
    dynamic_prompt.invoke({"sentence": "Spot can run fast."}).to_messages()[0].content
    )

    输出

    给出每个输入的中文翻译
    Input: Spot can run.
    Output: Spot可以跑。

    Input: See Spot run.
    Output: 看见Spot跑。

    Input: My dog barks.
    Output: 我的狗叫。

    Input: Spot can run fast.
    Output:

输入句子:Spot can run fast.

  1. Spot can run.:同时包含 Spot、can、run,ngram 字面重叠最多,分数最高,排在第一位

  2. See Spot run.:包含 Spot、run,重叠分数次之,排第二位

  3. My dog barks.:没有任何共享连续字符片段,重叠分数为 0,排在最后

    拓展测试:

    把threshold=0.0,重叠分数 = 0 的样例会被删掉,输出就只剩下前两条:

    给出每个输入的中文翻译
    Input: Spot can run.
    Output: Spot可以跑。

    Input: See Spot run.
    Output: 看见Spot跑。

    Input: Spot can run fast.
    Output:

    threshold=1.0 输出

    大于 1 的阈值,所有示例直接全部丢弃,不放入 prompt

    给出每个输入的中文翻译
    Input: Spot can run fast.
    Output:

一句话记忆口诀

  • threshold < 0:全保留,只排序
  • threshold = 0:去掉零重叠样例
  • threshold > 1:全部样例都删掉

LangChain 四大 Few-Shot 示例选择器对比表

选择器类名 核心原理 优点 缺点 适用场景
LengthBased ExampleSelector 根据字符 / Token 长度动态挑选样例。 ✅ 自动控制 prompt 总长度,避免超出 LLM 上下文上限✅ 不需要 Embedding 与向量库,轻量、低成本✅ 动态调整样例数量 ❌ 完全不评估样例与当前输入的相关性,只看长度 ❌ 可能选出无关示例 样例库庞大、上下文窗口紧张;首要目标是防止 prompt 超限,对示例相关性要求较低
SemanticSimilarity ExampleSelector 选出和输入语义最接近的 Top-K 样例 ✅ 基于语义匹配,不是字面字符,同义词也能匹配✅ 挑选的示例和当前任务语义相关性高 ❌ 选出的样例容易语义重复,多样性差❌ 需要 Embedding 模型 + 向量存储,资源开销更大 问答、文本分类、常规 few-shot 推理;优先追求样例语义相关性,对多样性要求不高
MaxMarginalRelevance ExampleSelector (MMR ) 最大边际相关性 兼顾相关性和样例多样性 ✅ 兼顾和输入的语义相似度 + 样例之间的多样性✅ 减少示例冗余,prompt 信息覆盖面更广 ❌ 检索速度略慢❌ 依赖 Embedding + 向量库,计算开销高于单纯语义相似度选择器 复杂推理、多视角问答;需要多角度参考样例,避免示例同质化的场景
NGramOverlap ExampleSelector 计算输入与样例之间连续字符 n-gram 重叠分数 ✅ 无需 Embedding、向量库,轻量、速度快✅ 阈值可控,灵活过滤样例 ❌ 只看字符字面重合,不理解语义❌ 同义词语义相近但文字不同时无法匹配,拼写大小写会干扰打分 短文本翻译、关键词匹配、固定词汇类任务;不适合依赖语义理解的任务
相关推荐
程序猿阿森44 分钟前
深入解析 NLI:RAG 与大模型防幻觉的“最后一道防线”
前端·人工智能·算法
2601_965448871 小时前
让AI写开源项目的产品介绍+宣传片脚本,实测效果
人工智能
FITA阿泽要努力1 小时前
Sub Agent:上下文管理的艺术
人工智能·python·agent
新新学长搞科研1 小时前
【广州大学主办/会议评优】第六届人工智能与智能制造国际研讨会(AIIM 2026)
人工智能·制造
l1t1 小时前
DeepSeek总结的本地优先的分析不需要数据库服务器
运维·服务器·数据库·人工智能·duckdb
tq10861 小时前
从翻译到对齐:智能转导的一个核心视角
人工智能
zhilai11 小时前
2026 企业 AI 办公工具选型指南:企业团队 AI 平台评估与场景适配
大数据·人工智能
科技看点说1 小时前
翼图资本:低空经济的产业韧性与延展性
大数据·人工智能