
少样本提示(few-shotting)
概念
少样本提示是一种通过向 LLM 提供少量具体示例或样本,来教会它如何执行某项特定任务的技术。提高模型性能的最有效方法之一是给出一个【模型示例】,指导大模型你想做什么、怎么做。下面用一个例子解释少样本提示的作用。
参考之前学习的内容,我们可以将其想象为"零样本提示":直接给模型一道考题,不给任何例子。模型会凭借已有的知识直接回答。例如我们输入 "What is 2 🦜 9?",答案可能是:
python
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:
python
examples = [
{"input": "2 🦜 2", "output": "4"},
{"input": "2 🦜 3", "output": "5"},
]
这能解决什么问题? LLM 虽然知识渊博,但有时我们需要它以非常特定的格式、风格或逻辑来回答问题。提供正确的示例可以减少模型 "胡说八道" 或犯低级错误的概率,将其输出约束在你提供的范例范围内。举个例子更能理解:
-
强制要求模型以特定的格式(如 JSON、XML、特定的列表样式)输出结果。样例可以当作格式样板。
-
有些任务很难用文字指令清晰描述(例如:"请用莎士比亚的风格写作")。提供几个例子比写长篇大论的指令更有效。
-
对于需要多步推理的复杂任务,示例可以展示出思考链,引导模型遵循类似的推理路径。
实现少样本提示
实现少样本提示的第一步也是最重要的一步,是提出一个好的示例数据集。好的示例应该与运行时相关、清晰、信息丰富,并提供模型尚不知道的信息。如我们给出的例子,就能很好地提示大模型 🦜 与 + 含义相似:
python
examples = [
{"input": "2 🦜 2", "output": "4"},
{"input": "2 🦜 3", "output": "5"},
]
如何让大模型看懂这份示例?
之前我们说过,聊天模型读的是聊天消息。 因此,接下来我们需要将示例集实例化成聊天模型可以读懂的聊天消息。对于 LangChain,就需要创建一个 FewShotChatMessagePromptTemplate 对象来实例化示例集。
FewShotChatMessagePromptTemplate 是一个提示词模板,专门用来将示例集实例化为聊天消息,用法如下所示:
python
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
# 定义单个示例的格式模板
example_prompt = ChatPromptTemplate(
[
("human", "{input}"),
("ai", "{output}"),
]
)
# 创建少样本提示模板
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt, # ChatPromptTemplate,用于格式化单个示例
examples=examples, # 样本示例列表
)
# 查看转换后的聊天消息列表
print(few_shot_prompt.invoke({}).to_messages())
运行后,可以得到示例集转化的聊天消息列表,结果如下:
python
[
HumanMessage(content='2 🦜 2', additional_kwargs={}, response_metadata={}),
AIMessage(content='4', additional_kwargs={}, response_metadata={}),
HumanMessage(content='2 🦜 3', additional_kwargs={}, response_metadata={}),
AIMessage(content='5', additional_kwargs={}, response_metadata={})
]
FewShotChatMessagePromptTemplate 核心说明
先来看看类 langchain_core.prompts.few_shot.FewShotChatMessagePromptTemplate,它也实现了标准的 Runnable 接口。
类初始化参数:
- examples:样本示例列表。
- example_prompt:ChatPromptTemplate,用于格式化单个示例。
.invoke() 方法:与其他 Runnable 实例的 .invoke() 方法类似。输入一个字典给它,返回完整的提示内容 PromptValue:
- PromptValue 的 to_string() 方法可以将提示值作为【字符串】返回。
- PromptValue 的 to_messages() 方法可以将提示作为【消息列表】返回。
组合提示词并发起请求
最后,得到示例集消息列表后,就可以带上它一起发起请求:
python
# 构建最终的提示词模板
final_prompt = ChatPromptTemplate(
[
("system", "你是一个神奇的数学奇才。"),
few_shot_prompt, # 少样本示例消息
("human", "{input}"),
]
)
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")
# 构建链并调用
chain = final_prompt | model
chain.invoke({"input": "What is 2 🦜 9?"}).pretty_print()
运行结果如下:
python
==================================== Ai Message ====================================
11
计算成功!✅
使用案例
案例一:推理引导
我们希望输入:
《教父》和《星球大战》的导演来自同一个国家吗?
让聊天模型可以先分析再得出结论,而不是直接给出结论。分析过程需要展示出来,示例如下:
python
from langchain_core.prompts import PromptTemplate
# 创建字符串模板
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())
运行后,输出结果如下:
python
Question: 李白和杜甫,谁更长寿?
是否需要后续问题:是的。
后续问题:李白享年多少岁?
中间答案:李白享年61岁。
后续问题:杜甫享年多少岁?
中间答案:杜甫享年58岁。
所以最终答案是:李白
接下来我们需要格式化完整的样本提示。此时可以创建一个 FewShotPromptTemplate 对象来初始化少样本提示模板。其接收少量示例和少量示例的格式化程序。如下所示:
python
from langchain_core.prompts import FewShotPromptTemplate
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt, # PromptTemplate,用于格式化单个示例
suffix="Question: {input}", # 放在示例之后的提示模板字符串
input_variables=["input"], # 变量的名称列表,这些变量的值需要作为提示词的输入
)
print(
prompt.invoke({"input": "《教父》和《星球大战》的导演来自同一个国家吗?"}).to_string()
)
输出结果如下:
python
Question: 李白和杜甫,谁更长寿?
是否需要后续问题:是的。
后续问题:李白享年多少岁?
中间答案:李白享年61岁。
后续问题:杜甫享年多少岁?
中间答案:杜甫享年58岁。
所以最终答案是:李白
Question: 腾讯的创始人什么时候出生?
是否需要后续问题:是的。
后续问题:腾讯的创始人是谁?
中间答案:腾讯由马化腾创立。
后续问题:马化腾什么时候出生?
中间答案:马化腾出生于1971年10月29日。
所以最终答案是:1971年10月29日
Question: 孙中山的外祖父是谁?
是否需要后续问题:是的。
后续问题:孙中山的母亲是谁?
中间答案:孙中山的母亲是杨太夫人。
后续问题:杨太夫人的父亲是谁?
中间答案:杨太夫人的父亲是杨胜辉。
所以最终答案是:杨胜辉
Question: 电影《红高粱》和《霸王别姬》的导演来自同一个国家吗?
是否需要后续问题:是的。
后续问题:《红高粱》的导演是谁?
中间答案:《红高粱》的导演是张艺谋。
后续问题:张艺谋来自哪里?
中间答案:中国。
后续问题:《霸王别姬》的导演是谁?
中间答案:《霸王别姬》的导演是陈凯歌。
后续问题:陈凯歌来自哪里?
中间答案:中国。
所以最终答案是:是
Question: 《教父》和《星球大战》的导演来自同一个国家吗?
FewShotPromptTemplate 核心说明
来看下 class langchain_core.prompts.few_shot.FewShotPromptTemplate,它也实现了标准的 Runnable 接口。
类初始化参数说明:
examples:样本示例。example_prompt:PromptTemplate,用于格式化单个示例。prefix:放在示例前面的提示模板字符串。suffix:放在示例之后的提示模板字符串。input_variables:变量的名称列表,这些变量的值需要作为提示词的输入。
类方法说明:
.invoke() 方法:此方法与其他 Runnable 实例的 .invoke() 方法类似。输入一个字典给提示模板,返回完整的提示内容 PromptValue:
PromptValue的to_string()方法可以将提示值作为【字符串】返回。PromptValue的to_messages()方法可以将提示作为【消息列表】返回。
接下来让我们调用聊天模型看看结果,完整代码如下:
python
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_openai import ChatOpenAI
# 创建字符串模板
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": """
是否需要后续问题:是的。
后续问题:《红高粱》的导演是谁?
中间答案:《红高粱》的导演是张艺谋。
后续问题:张艺谋来自哪里?
中间答案:中国。
后续问题:《霸王别姬》的导演是谁?
中间答案:《霸王别姬》的导演是陈凯歌。
后续问题:陈凯歌来自哪里?
中间答案:中国。
所以最终答案是:是
"""
}
]
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="Question: {input}",
input_variables=["input"],
)
# 生成提示消息列表
prompt_messages = prompt.invoke({"input": "《教父》和《星球大战》的导演来自同一个国家吗?"}).to_messages()
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 调用模型
model.invoke(prompt_messages).pretty_print()
模型返回的结果如下:
python
==================================== Ai Message ====================================
是否需要后续问题:是的。
后续问题:《教父》的导演是谁?
中间答案:弗朗西斯·福特·科波拉。
后续问题:弗朗西斯·福特·科波拉来自哪里?
中间答案:美国。
后续问题:《星球大战》的导演是谁?
中间答案:乔治·卢卡斯。
后续问题:乔治·卢卡斯来自哪里?
中间答案:美国。
所以最终答案是:是
案例二:使用示例数据增强 LangChain 信息提取能力
在我们学习**【结构化输出】** 小节时,其中有一个使用场景是使用结构化输出进行信息提取 。当时说明了可以结合少样本提示来实现。于是在这里,我们来实现一个基于 LangChain 的结构化信息提取系统,专门从文本中提取人物相关信息文本。
例如我们希望,对于输入以下文本:
"篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老队友用十年配合弥补了身高的差距。"
代码会提取出结构化数据,如下所示:
python
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)
]
实现步骤
第一步:定义结构化返回对象
python
from typing import List, Optional
from pydantic import BaseModel, Field
# 定义结构化输出
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]
第二步:定义两个关键示例
每个示例中包含【文本】和【希望输出】:
- 无人物场景:文本中没有人名,期望返回空列表
- 部分信息场景:只有名字,其他字段缺失
python
# 定义2个示例
examples = [
(
"海洋是广阔而蓝色的。它有两万多英尺深。",
Data(people=[]), # 没有人物信息的情况
),
(
"小强从中国远行到美国。",
Data(people=[
Person(name="小强", height_in_meters=None, skin_color=None, hair_color=None),
]), # 部分信息缺失的情况
),
]
第三步:定义提示词模板
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, SystemMessage
# 定义提示词模板
prompt_template = ChatPromptTemplate(
[
SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null"),
MessagesPlaceholder("example_messages"),
("user", "{new_message}"),
]
)
第四步:构造请求的消息列表
使用 tool_example_to_messages 转换格式,将每个示例转换为模型可理解的消息格式:
python
from langchain_core.utils.function_calling import tool_example_to_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
)
)
tool_example_to_messages 方法说明:
此功能处于测试阶段,API 可能会发生变化。
它可以将单个示例转换为聊天模型可以识别的消息列表。
参数:
input: 输入字符串tool_calls:list[BaseModel],表示为 Pydantic BaseModel 的工具调用列表ai_response: 可选,最终 AIMessage 的内容
返回值:list[BaseMessage] 消息列表
测试提示词模板
python
# 实例化提示词
formatted_prompt = prompt_template.invoke({
"example_messages": example_messages,
"new_message": "篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老队友用十年配合弥补了身高的差距。"
})
# 打印
for message in formatted_prompt.to_messages():
message.pretty_print()
第五步:初始化模型并绑定结构化输出
python
from langchain_openai import ChatOpenAI
# 定义结构化输出模型,自动解析为 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(result)
输出结果:
people=[
Person(name='王伟', hair_color=None, skin_color=None, height_in_meters='2'),
Person(name='李明', hair_color=None, skin_color=None, height_in_meters='1.7')
]
完整代码
python
from typing import List, Optional
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.utils.function_calling import tool_example_to_messages
# 1. 定义结构化输出
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. 定义2个示例
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. 获取样例消息列表
example_messages = []
for txt, tool_call in examples:
if tool_call.people:
ai_response = "检测到人"
else:
ai_response = "未检测到人"
example_messages.extend(
tool_example_to_messages(
txt, [tool_call], ai_response=ai_response
)
)
# 5. 调用结构化模型
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(result)
针对比较高版本的 Python,我们可能需要:
python
from pydantic import BaseModel
from typing import Optional
class Person(BaseModel):
name: Optional[str] = None
"""这个人的名字"""
age: Optional[int] = None
"""年龄"""
height_in_meters: Optional[str] = None
"""以米为单位的高度"""
总结
该案例展示了 LangChain 在结构化信息提取中的核心能力:
- 通过 Pydantic 定义输出模式,保证数据格式的一致性。
- 使用 少样本提示 ,让模型理解提取规则和边界情况。
- 利用
tool_example_to_messages工具,将示例转换为模型可识别的消息格式。 - 链式调用简化流程,最终实现从非结构化文本中精准提取结构化数据。
示例选择器(Example selectors)
概念
一旦我们有了示例数据集,就需要考虑提示中应该有多少个示例。关键的权衡是:
- 更多的示例通常会提高模型性能,但更大的提示会增加成本和延迟;
- 超过某个阈值后,过多示例反而可能混淆模型,降低效果。
找到合适数量的示例,很大程度上取决于模型能力、任务类型、示例质量,以及成本和延迟限制。
- 模型越强,需要的示例越少;
- 最佳实践是通过实验,测试不同示例数量的效果。
当示例数据集很大 时,没必要把所有示例都塞进提示里。我们需要一种方法,能根据用户的输入,从数据集中智能筛选出最相关的示例,来构建少样本提示。
在 LangChain 中,示例选择器(Example selectors) 就是为此设计的工具:它可以从一组示例集合中,根据具体策略,选择最合适的示例子集,用于构建少样本提示。

选择策略有:
Length:根据特定【长度】内可以容纳的数量选择示例。Similarity:使用输入和示例之间的【语义相似性:按含义 匹配,选最像的,不管重复】来决定选择哪些示例。MMR:使用输入和示例之间的【最大边际相关性:先按含义选,再强制多样化、不重复 ,精选不冗余】来决定要选择哪些示例。Ngram:使用输入和示例之间的【ngram 重叠:只按字面连续词语重合度 匹配,不懂语义 ,只看有没有大段文字撞车】来决定要选择哪些示例。
这些其实都是自然语言处理(NLP)里的相似性衡量问题。
按长度选择示例(Length)
当我们担心构造提示时,将超过上下文窗口长度,根据特定长度内可以容纳的数量选择示例。对于较长的输入,它将选择更少的示例来包含;而对于较短的输入,它将选择更多示例。
实现按长度选择示例的示例选择器是:class langchain_core.example_selectors.length_based.LengthBasedExampleSelector 类,其参数如下:
example_prompt:PromptTemplate,用于格式化示例的提示模板。examples:模板所需的示例列表。max_length:提示的最大长度,超过该长度将剪切示例。get_text_length:测量提示长度的方法。默认为字数统计。
内置方法:
add_example(example: dict[str, str]):将新示例添加到列表中。
- 输入:一个字典,其中键作为输入变量,值作为其值。
select_examples(input_variables: dict[str, str]) → list[dict]:根据输入长度选择要使用的示例。
- 输入:一个字典,其中键作为输入变量,值作为其值。
- 输出:要包含在提示中的示例列表。
接下来,演示一下如何使用长度示例选择器,让我们:
- 给一个示例集,输入和输出互为反义词
- 定义
PromptTemplate字符串模板,包含输入和输出两个 "占位符" - 定义
LengthBasedExampleSelector长度示例选择器,设置初始示例集与最大长度 - 定义一个
FewShotPromptTemplate模板对象,用于实例化示例,将示例转化为聊天消息 - 打印消息结果
代码如下:
python
from langchain_core.example_selectors import LengthBasedExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
# 反义词示例集合
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,
# 格式化示例的最大长度。
# 长度由下面的get_text_length函数测量。
max_length=25,
# 用于获取字符串长度的函数,用于确定包含哪些示例。
# 如果没有指定,它是作为默认值提供的。
# 该函数返回一个整数,表示字符串中由换行符或空格分隔的"单词"数量
# get_text_length: Callable[[str], int] = lambda x: len(re.split("\n| ", x))
)
# 用于实例化示例的模板
dynamic_prompt = FewShotPromptTemplate(
# 提供了一个ExampleSelector而不是examples。
example_selector=example_selector,
example_prompt=example_prompt,
prefix="给出每个输入的反义词",
suffix="Input: {adjective}\nOutput:",
input_variables=["adjective"],
)
print(
dynamic_prompt.invoke({"adjective": "big"}).to_messages()[0].content
)
结果如下:
python
给出每个输入的反义词
Input: happy
Output: sad
Input: tall
Output: short
Input: energetic
Output: lethargic
Input: sunny
Output: gloomy
Input: windy
Output: calm
Input: big
Output:
可以看到,示例选择器将所有的示例都选择了,因为还没有超过设置的示例最大长度,长度计算是根据字符串中由换行符或空格分隔的 "单词" 数量得到的(默认)。
接下来让我们加入长输入,验证长度选择器筛选示例。代码如下:
python
# 一个长的示例输入,所有示例超出最大长度
long_string = "非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 大,比其他任何东西都要大得多"
print(
dynamic_prompt.invoke({"adjective": long_string}).to_messages()[0].content
)
结果如下:
给出每个输入的反义词
Input: happy
Output: sad
Input: tall
Output: short
Input: 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 大,比其他任何东西都要大得多
Output:
按语义相似性选择示例(Similarity)
什么是语义相似?它是衡量文本在【含义上】的接近程度。例如下述两段文本:
bash
text1 = "我喜欢猫"
text2 = "我讨厌狗"
这两段文本表面相似度低,但语义上都是表达对动物的态度。
再例如:
bash
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]:根据输入选择要使用的示例。
输入:一个字典,其中键作为输入变量,值作为其值。
输出:要包含在提示中的示例列表。
接下来,演示一下如何使用语义相似示例选择器,让我们:
- 给一个示例集,输入和输出互为反义词
- 定义
PromptTemplate字符串模板,包含输入和输出两个 "占位符" - 定义
SemanticSimilarityExampleSelector语义相似示例选择器,设置初始示例集与嵌入 API 接口 - 定义一个
FewShotPromptTemplate模板对象,用于实例化示例,将示例转化为聊天消息 - 打印消息结果
代码如下:
注意:运行前先安装
langchain-chroma库:pip install -U langchain-chroma
python
from langchain_chroma import Chroma
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import OpenAIEmbeddings
# 反义词示例集合
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
)
结果如下:
python
给出每个输入的反义词
Input: happy
Output: sad
Input: worried
Output:
我们输入的内容和最后选择的示例都表示心情,在语义上最相似!
按最大边际相关性选择示例(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 示例选择器,让我们:
- 给一个示例集,输入和输出互为反义词
- 定义
PromptTemplate字符串模板,包含输入和输出两个 "占位符" - 定义
MaxMarginalRelevanceExampleSelectorMMR 示例选择器,设置初始示例集与嵌入 API 接口 - 定义一个
FewShotPromptTemplate模板对象,用于实例化示例,将示例转化为聊天消息 - 打印消息结果
代码如下:
python
from langchain_chroma import Chroma
from langchain_core.example_selectors import MaxMarginalRelevanceExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import OpenAIEmbeddings
# 反义词示例集合
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,
)
# 用于实例化示例的模板
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
)
结果如下:
python
给出每个输入的反义词
Input: happy
Output: sad
Input: windy
Output: calm
Input: worried
Output:
通过 ngram 重叠选择示例(Ngram)
什么是【ngram】?ngram 指一个文本序列中连续的 n 个词(word)或字符(character)。
什么是【ngram 重叠】?通过计算它们之间共同拥有的 ngram 数量来一种衡量两段文本相似度的方法。
例如下述两段文本:
bash
text1 = "苹果手机很好用" (分词后: 苹果 手机 很 好用)
text2 = "这款手机很好用" (分词后: 这款 手机 很 好用)
这两段文本单词重复度很高,连续三个词的相同的情况也存在,因此 ngram 重叠高。
再看个例子:
bash
text1 = "苹果手机很好用" (分词后: 苹果 手机 很 好用)
text2 = "iPhone 非常不错" (分词后: iPhone 非常 不错)
这两段文本在含义上非常相似,但它们的 ngram 重叠度为 0。
因此,传统 ngram 重叠是一种表面形式的匹配。它只关心词是否完全一样,但对于同义词却无法处理。
什么是【语义 ngram 重叠】? 不再比较词本身,而是比较词背后的语义向量(Embedding)。也就是说,它不是看两个词「苹果」和「iPhone」的字面是否相同,而是计算它们在语义空间中的向量是否相似。如果相似度超过某个阈值,就认为它们 "重叠" 了。还是看这个例子:
bash
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]:返回根据输入得到的重叠分数排序的降序示例列表。
输入:一个字典,其中键作为输入变量,值作为其值。
输出:要包含在提示中的示例列表。
接下来,演示一下如何使用语义相似示例选择器,让我们:
- 给一个示例集,设置相关文本
- 定义
PromptTemplate字符串模板,包含输入和输出两个 "占位符" - 定义
NGramOverlapExampleSelector示例选择器,设置初始示例集与阈值(-1),表示对示例进行排序,但不排除任何示例。 - 定义一个
FewShotPromptTemplate模板对象,用于实例化示例,将示例转化为聊天消息 - 打印消息排序结果
代码如下:
注意:
- 运行前先安装
nltk库:pip install nltk。它是自然语言处理工具包,在 NLP 领域中最常使用的一个 Python 库。- 运行前先安装
langchain_community库:pip install -U langchain_community
python
from langchain_community.example_selectors import NGramOverlapExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
# 翻译示例
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)
结果如下:
python
给出每个输入的中文翻译
Input: Spot can run.
Output: Spot可以跑。
Input: See Spot run.
Output: 看见Spot跑。
Input: My dog barks.
Output: 我的狗叫。
Input: Spot can run fast.
Output:
可以看到结果集是按照 ngram 重叠分数进行了排序。
可以分别设置 threshold=0.0 和 1.0 进行测试,打印结果如下:
python
# threshold=0.0(表示排除不相关的示例)
给出每个输入的中文翻译
Input: Spot can run.
Output: Spot可以跑。
Input: See Spot run.
Output: 看见Spot跑。
Input: Spot can run fast.
Output:
python
# threshold=1.0(排除所有示例)
给出每个输入的中文翻译
Input: Spot can run fast.
Output: