LangChain 核心组件 [ 3 ]

少样本提示(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_promptPromptTemplate,用于格式化单个示例。
  • prefix:放在示例前面的提示模板字符串。
  • suffix:放在示例之后的提示模板字符串。
  • input_variables:变量的名称列表,这些变量的值需要作为提示词的输入。

类方法说明:

.invoke() 方法:此方法与其他 Runnable 实例的 .invoke() 方法类似。输入一个字典给提示模板,返回完整的提示内容 PromptValue

  • PromptValueto_string() 方法可以将提示值作为【字符串】返回。
  • PromptValueto_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]

第二步:定义两个关键示例

每个示例中包含【文本】和【希望输出】:

  1. 无人物场景:文本中没有人名,期望返回空列表
  2. 部分信息场景:只有名字,其他字段缺失
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_promptPromptTemplate,用于格式化示例的提示模板。
  • examples:模板所需的示例列表。
  • max_length:提示的最大长度,超过该长度将剪切示例。
  • get_text_length:测量提示长度的方法。默认为字数统计。

内置方法:

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

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

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

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

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

  1. 给一个示例集,输入和输出互为反义词
  2. 定义 PromptTemplate 字符串模板,包含输入和输出两个 "占位符"
  3. 定义 LengthBasedExampleSelector 长度示例选择器,设置初始示例集与最大长度
  4. 定义一个 FewShotPromptTemplate 模板对象,用于实例化示例,将示例转化为聊天消息
  5. 打印消息结果

代码如下:

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]:根据输入选择要使用的示例。

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

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


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

  1. 给一个示例集,输入和输出互为反义词
  2. 定义 PromptTemplate 字符串模板,包含输入和输出两个 "占位符"
  3. 定义 SemanticSimilarityExampleSelector 语义相似示例选择器,设置初始示例集与嵌入 API 接口
  4. 定义一个 FewShotPromptTemplate 模板对象,用于实例化示例,将示例转化为聊天消息
  5. 打印消息结果

代码如下:

注意:运行前先安装 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 示例选择器,让我们:

  1. 给一个示例集,输入和输出互为反义词
  2. 定义 PromptTemplate 字符串模板,包含输入和输出两个 "占位符"
  3. 定义 MaxMarginalRelevanceExampleSelector MMR 示例选择器,设置初始示例集与嵌入 API 接口
  4. 定义一个 FewShotPromptTemplate 模板对象,用于实例化示例,将示例转化为聊天消息
  5. 打印消息结果

代码如下:

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_promptPromptTemplate,用于格式化示例的提示模板。

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. 打印消息排序结果

代码如下:

注意:

  • 运行前先安装 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.01.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:
相关推荐
Joseph Cooper2 小时前
AI Agent 框架选型:LangChain、LlamaIndex、Anthropic SDK 和 Codex/Claude Code 怎么选
人工智能·langchain·llamaindex·claudecode·anthropic sdk·codex sdk
深海鱼在掘金3 小时前
深入浅出 LangChain —— 第六章:记忆与状态管理
人工智能·langchain·agent
qq_283720053 小时前
Python+LangChain 调用大模型全方案深度实战:原生调用、统一接口、流式输出、异步、自定义模型全解析
人工智能·langchain·agent·rag
深海鱼在掘金4 小时前
深入浅出 LangChain —— 第七章:Agent 架构深度解析与LangGraph 核心概念
人工智能·langchain·agent
洛_尘4 小时前
AI 2:大语言模型+嵌入模型
langchain
风落无尘17 小时前
LangChain 完全入门指南:从基础到实战(附面试题)
人工智能·langchain
深海鱼在掘金1 天前
深入浅出 LangChain —— 第五章:工具系统
人工智能·langchain·agent
深海鱼在掘金1 天前
深入浅出 LangChain —— 第四章:提示词工程
人工智能·langchain·agent
技术钱1 天前
LangChain简介
python·langchain