LangChain之核心组件(少样本提示词)

3. 少样本提示(few-shotting)

LangChain 少样本提示核心类和方法速览

前置知识:涉及的核心类和方法

在写代码之前,先把涉及的"零件"搞清楚。

1. PromptTemplate --- 字符串模板
python 复制代码
from langchain_core.prompts import PromptTemplate

作用: 把一个带 {占位符} 的字符串模板,填上实际值后生成最终文本 。它是 LangChain 中最基础的文本构建块,所有更复杂的模板都基于它扩展。

以下是初始化 PromptTemplate 对象时需要传入的参数,掌握它们是后续灵活构建提示的基础:

初始化参数:

参数 类型 说明
template str 模板字符串,用 {变量名} 做占位符
input_variables list[str] 有哪些变量需要填入

在实际开发中,通常先用 from_template 类方法自动提取变量,或者手动指定 input_variables 以保证数据安全。

示例:

python 复制代码
template = PromptTemplate(
    template="你好,我叫{name},今年{age}岁",
    input_variables=["name", "age"],
)

result = template.invoke({"name": "小明", "age": 25}).to_string()
# 输出:"你好,我叫小明,今年25岁"

说明: 它实现了 Runnable 接口,所以可以 .invoke(dict) 调用,返回 PromptValue 对象。PromptValue 有两个关键方法:

方法 返回值 说明
.to_string() str 转成普通字符串
.to_messages() list[BaseMessage] 转成聊天消息列表

2. ChatPromptTemplate --- 聊天消息模板
python 复制代码
from langchain_core.prompts import ChatPromptTemplate

作用: PromptTemplate 只能生成字符串,但聊天模型需要的是消息列表SystemMessageHumanMessageAIMessage)。ChatPromptTemplate 就是生成消息列表的模板,它让开发者可以精确控制每条消息的角色和内容。

示例:

python 复制代码
prompt = ChatPromptTemplate([
    ("system", "你是一个{role}"),       # 系统消息模板
    ("human", "{user_input}"),          # 用户消息模板
])

messages = prompt.invoke({"role": "数学老师", "user_input": "1+1等于几?"}).to_messages()
# 输出:[SystemMessage("你是一个数学老师"), HumanMessage("1+1等于几?")]

消息元组格式: ("角色", "模板内容"),角色可以是:

  • "system" → 系统消息(设定对话背景)

  • "human" → 用户消息(实际提问)

  • "ai" → AI 消息(历史回复,用于情境延续)

使用中可根据需要添加多个元组,角色也可省略而直接传入 SystemMessage 等对象来构建


3. FewShotPromptTemplate --- 少样本提示模板(字符串版)
python 复制代码
from langchain_core.prompts import FewShotPromptTemplate

作用: 这是核心类。接收一堆示例 + 一个格式化模板自动把所有示例拼成一段文本,然后附加你的实际问题。适合纯文本 LLM 或需要整体控制提示布局的场景。

下面列出它所有初始化参数,理解每个参数的用途能帮助你自由设计提示结构:

参数 类型 说明
examples list[dict] 示例列表,每个示例是一个字典,键名需与 example_prompt 中的变量名对应
example_prompt PromptTemplate 用于格式化单个示例的模板,决定每个示例的呈现形式
prefix str(可选) 放在所有示例前面的文字,通常用来下达整体指令或背景说明
suffix str(可选) 放在所有示例后面的文字,通常用于放置用户的实际提问,内部可以包含变量
input_variables list[str] suffix 中用到的变量名列表
example_selector BaseExampleSelector(可选) 示例选择器,examples 二选一。当示例数量动态变化时用于智能筛选

内部工作流程:

  1. 遍历 examples 列表中的每个示例字典

  2. example_prompt 逐一格式化每个示例

  3. prefix + 所有格式化后的示例 + suffix 拼接起来

  4. 通过 .invoke() 返回完整的 PromptValue

使用时请注意,若 examples 为固定集合,直接传入即可;若需根据输入长度动态选择示例,则应使用 example_selector


4. FewShotChatMessagePromptTemplate --- 少样本提示模板(聊天消息版)
python 复制代码
from langchain_core.prompts import FewShotChatMessagePromptTemplate

作用:FewShotPromptTemplate 类似,但输出的是聊天消息列表 ,而不是字符串。专为聊天模型设计,可与 ChatPromptTemplate 无缝配合,保留每条示例消息的角色信息。

初始化参数非常简洁,但关键在于搭配的 example_prompt 类型必须匹配:

参数 类型 说明
examples list[dict] 示例列表,每个字典对应一条示例,键名需与消息模板中的变量一致
example_prompt ChatPromptTemplate 用于格式化单个示例的聊天消息模板,决定每个示例生成哪些消息

关键区别: example_prompt 必须是 ChatPromptTemplate(生成消息),而不是 PromptTemplate(生成字符串)。这保证了示例能够以多轮对话的形式展现给模型。


5. LengthBasedExampleSelector --- 按长度选择示例
python 复制代码
from langchain_core.example_selectors import LengthBasedExampleSelector

作用: 当示例太多时,根据格式化后的总长度自动选择一部分示例,防止超过 LLM 的上下文窗口。它属于最简单的示例选择器,适合示例数量不多但需控制 token 数的场景。

需要设置的参数如下:

参数 说明
examples 可供选择的全部示例列表
example_prompt 格式化单个示例的模板,用于计算每个示例格式化后的长度
max_length 格式化后所有选中示例的总长度上限(超过上限的示例将被舍弃)
get_text_length 测量长度的函数,默认按空格/换行分词后统计词数,也可自定义

它提供两个主要方法,方便在运行时维护和筛选示例:

方法 说明
add_example(example: dict) 动态添加新示例到候选列表
select_examples(input_variables: dict) 根据当前输入(常与 suffix 结合)计算剩余空间,并选择合适数量的示例,返回 list[dict]

6. tool_example_to_messages --- 工具调用示例转换器
python 复制代码
from langchain_core.utils.function_calling import tool_example_to_messages

注意:此功能处于测试阶段,API 可能变化。

作用: 把"输入文本 + 期望的结构化输出"转换成标准的 HumanMessage / AIMessage / ToolMessage 格式,这样聊天模型就能直接看懂示例,并模仿类似的工具调用行为。

调用时需传入如下参数:

参数 类型 说明
input str 用户的原始输入文本
tool_calls list[BaseModel] Pydantic 模型实例列表,表示期望的结构化输出内容
ai_response str(可选) 模型对应该示例的简短文本回复,通常用于区分是否有信息被提取

返回值: list[BaseMessage] --- 转换后的消息列表,可直接嵌入 MessagesPlaceholder 作为示例对话历史。


7. MessagesPlaceholder --- 消息占位符
python 复制代码
from langchain_core.prompts import MessagesPlaceholder

作用:ChatPromptTemplate 中预留一个"消息插槽",后续 invoke 时用实际的消息列表替换它。这使得模板可以动态插入历史对话、示例消息等可变消息序列。

python 复制代码
prompt = ChatPromptTemplate([
    SystemMessage(content="你是一个助手"),
    MessagesPlaceholder("history"),          # 这里会被替换
    ("human", "{user_input}"),
])

# invoke 时传入 history
prompt.invoke({
    "history": [HumanMessage("你好"), AIMessage("你好!")],
    "user_input": "我叫小明",
})

MessagesPlaceholder 的参数只是占位符名称,模板在调用时会从输入字典中查找同名键对应的消息列表。若要插入固定消息,也可以直接在模板中声明。

3.1 概念

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

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

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

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

篇大论的指令更有效。

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

3.2 实现少样本提示

examples (4个dict)


example_prompt_template (ChatPromptTemplate)

│ 逐个格式化,每个dict → HumanMessage + AIMessage

few_shot_prompt.invoke({})

│ 生成 8 条消息(4问4答)

ChatPromptTemplate 组装:

SystemMessage, ...8条示例..., HumanMessage(真实问题)

chain.invoke({"text": "hi, what is your favourite food?"})

AIMessage("你好,你最喜欢的食物是什么?")

先准备示例数据 → 定义示例如何成对出现 → 包装成少样本模板 → 把系统消息、少样本模板、新用户输入拼成最终提示 → 丢给模型。

实现少样本提示的第一步 也是最重要的一步是提出一个好的示例数据集 。好的示例应该在运行时相

关、清晰、信息丰富,并提供模型尚不知道的信息。

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate, PromptTemplate
from langchain_deepseek import ChatDeepSeek

#少样本提示词
#向LLM提供少量示例或者样本,教会她如何执行某些特定任务技术

#1、先准备示例集
examples = [
    {"text": "hi, what is your name?",    "output": "你好,你叫什么名字?"},
    {"text": "hi, what is your age?",     "output": "你好,你多大了?"},
    {"text": "where are you from?",       "output": "你来自哪里?"},
    {"text": "what do you like to do?",   "output": "你喜欢做什么?"},
]

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

对于 LangChain 就需要创建一个FewShotChatMessagePromptTemplate 对象来实例化示例集。
FewShotChatMessagePromptTemplate 是一个提示词模板,专门用来将示例集实例化为聊天消

python 复制代码
#2、定义单个实例的格式化模板
#ChatPromptTemplate 接收一个列表,列表里每个元组代表一条消息:("角色", "模板内容")
#{text} 和 {output} 是占位符,对应 examples 里每个字典的 key。调用时会被替换成实际值。
example_prompt_template=ChatPromptTemplate([
    ("user", "{text}"), #→   HumanMessage(content="hi, what is your name?")
    ("ai", "{output}"), #→   AIMessage(content="你好,你叫什么名字?")
])

#3、创建少样本提示词模板
#FewShotChatMessagePromptTemplate --- 少样本提示模板(聊天消息版)
# 与 FewShotPromptTemplate(少样本提示模板(字符串版))类似,但输出的是聊天消息列表,而不是字符串。
# 用于和 ChatPromptTemplate 配合。
few_shot_prompt = FewShotChatMessagePromptTemplate(
    examples=examples, #示例数据
    example_prompt=example_prompt_template, #每个示例怎么格式化成消息:"按什么格式把每个字典转成消息"
)

#打印看看示例生成了什么
# few_shot_prompt.invoke({}) --- 调用模板。
# 因为示例里没有需要外部传入的变量(所有占位符的值都来自 examples 本身),所以传空字典 {}
# .to_messages() --- 把结果转为消息列表
# msg.type --- 消息的类型字符串("human"、"ai"、"system")
# msg.content --- 消息的文本内容
print("===少样本模板生成的消息序列===")
for msg in few_shot_prompt.invoke({}).to_messages():
    print(f"{msg.type}: {msg.content}")

#构建最终的完整提示
#ChatPromptTemplate --- 聊天消息模板
# PromptTemplate 只能生成字符串,
# 但聊天模型需要的是消息列表(SystemMessage、HumanMessage、AIMessage)。
# ChatPromptTemplate 就是生成消息列表的模板。
chat_prompt_template=ChatPromptTemplate([
    ("system","你的任务只有一件事,把用户输入的英文翻译成中文,不要回答其他的问题,只输出对应中文翻译即可"),
    few_shot_prompt,
    ("user","{text}"),
])
#验证最终提示长什么样
filled = chat_prompt_template.invoke(
    {
        "language_from": "英文",
        "language_to": "中文",
        "text": "hi, what is your favourite food?",
    }
)
for msg in filled.to_messages():
    print(f"{msg.type}: {msg.content}")

#构建链并调用模型
model=ChatDeepSeek(model="deepseek-v4-flash")
chain=chat_prompt_template | model
response=chain.invoke({
    "language_from": "英文",
    "language_to": "中文",
    "text": "hi, what is your favourite food?",
})
print("===模型回答===")
response.pretty_print()
步骤 做了什么 为什么这样做
1. 准备示例 定义 examples 列表,每个示例包含 "text"(输入)和 "output"(期望输出)。 少样本学习的核心:向模型展示几个输入-输出的配对,让模型"学会"任务的模式。
2. 定义单个示例的格式 创建 examples_prompt_template,将每个示例映射为一条 HumanMessage + 一条 AIMessage 聊天模型需要消息格式的示例,不能直接读字典。这个模板规定了如何把一条原始数据转成消息对。
3. 创建少样本模板 FewShotChatMessagePromptTemplate 包装示例列表和单个示例模板。 这个组件负责将整个示例列表自动转换成一组连续的消息(例如:人→机→人→机...)。它实现了 Runnable 接口,可以像函数一样调用。
4. 构建最终提示 创建一个 ChatPromptTemplate,依次放入:系统消息、少样本模板、用户新输入。 系统消息定义全局任务,少样本模板提供示范,最后放上真正要处理的问题。这样模型在看到新输入前,先看到了几个"正确答案"的例子。
5. 调用模型 将最终提示与模型组合成链,并 invoke 传入变量。 链式调用会自动完成:变量填充 → 生成完整消息列表 → 发送给 LLM → 返回回答。

少样本提示 总结

第一步:准备示例集(教材)

先想清楚:我要让模型学会什么任务? 然后用一个列表,把"输入→输出"的例子写出来。

python

复制代码
examples = [
    {"input": "用户会说的话", "output": "模型应该回答的话"},
    {"input": "...", "output": "..."},
]

为什么先做这一步?

因为示例是少样本提示的灵魂。你的示例质量,直接决定了模型学得好不好。花时间打磨示例,比改代码更重要。


第二步:定义单个示例的排版格式

用一个 ChatPromptTemplate,告诉 LangChain:每个示例应该按什么格式转换成聊天消息?

python

复制代码
from langchain_core.prompts import ChatPromptTemplate

example_prompt = ChatPromptTemplate([
    ("user", "{input}"),    # 用户角色,取示例中的 "input" 字段
    ("ai", "{output}"),     # AI角色,取示例中的 "output" 字段
])

注意{input}{output} 必须和你示例字典里的键名保持一致


第三步:用少样本模板"批量翻译"示例

这一步是"自动化工厂"。FewShotChatMessagePromptTemplate 拿你前两步准备好的"教材"和"排版格式",自动把所有示例变成一长串聊天消息。

python

复制代码
from langchain_core.prompts import FewShotChatMessagePromptTemplate

few_shot_prompt = FewShotChatMessagePromptTemplate(
    examples=examples,              # 第一步的示例列表
    example_prompt=example_prompt,  # 第二步的排版模板
)

到这里,你已经有了一组"可插入的对话历史"。


第四步:组装最终提示模板

用一个大号的 ChatPromptTemplate,把系统指令 + 示例对话 + 新问题串成一条完整的消息序列。

python

复制代码
final_prompt = ChatPromptTemplate([
    ("system", "这里是给模型的全局指令,告诉它要做什么"),  # 系统消息
    few_shot_prompt,                                     # 第三步生成的示例对话
    ("user", "{input}"),                                 # 真正要处理的新问题
])

为什么是这个顺序?

因为聊天模型是按顺序读消息的。系统消息定基调 → 示例对话给范例 → 最后的问题让它照葫芦画瓢。


第五步:串联模型,执行调用

最后,把模板和模型用管道符 | 串成链,调用 invoke 传入变量。

python

复制代码
from langchain_deepseek import ChatDeepSeek

model = ChatDeepSeek(model="deepseek-v4-flash")  # 或 deepseek-chat
chain = final_prompt | model

response = chain.invoke({
    "input": "你想问的新问题",
})

复制代码
第一步:准备示例(字典列表)
         ↓
第二步:定义单示例排版格式(ChatPromptTemplate)
         ↓
第三步:批量翻译成消息(FewShotChatMessagePromptTemplate)
         ↓
第四步:组装最终提示(ChatPromptTemplate:系统 + 示例 + 新问题)
         ↓
第五步:串联模型,invoke 调用

3.3 使用案例

3.3.1 案例一:推理引导

我们希望输入:

《教父》和《星球大战》的导演来自同一个国家吗?

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

python 复制代码
# ==================== 1. 导入所需模块 ====================
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_deepseek import ChatDeepSeek

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

# ==================== 3. 定义"单个示例"的格式化模板 ====================
# 这个是纯字符串模板,每个示例会被格式化成:Question: ... \n answer ...
example_prompt = PromptTemplate.from_template("Question: {question}\n{answer}")

# ==================== 4. 创建"少样本提示模板"(字符串版) ====================
# 注意:这里用的是 FewShotPromptTemplate,而不是 FewShotChatMessagePromptTemplate
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    # suffix 是示例之后的模板字符串,{input} 是真正要问的新问题
    suffix="Question: {input}",
    input_variables=["input"],
)

# ==================== 5. 生成最终的提示字符串并打印(调试用) ====================
final_prompt_str = few_shot_prompt.invoke({"input": "《教父》和《星球大战》的导演来自同一个国家吗?"})
print("=== 最终发给模型的提示字符串 ===")
print(final_prompt_str)

# ==================== 6. 调用 DeepSeek 模型 ====================
model = ChatDeepSeek(model="deepseek-v4-flash")
response = model.invoke(final_prompt_str)  # 直接传入字符串,模型会自动包装成 HumanMessage

print("\n=== 模型回答 ===")
response.pretty_print()

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

在结构化输出那一块笔记中有一个使用场景是使用结构化输出进行信息提取 。当时说明

可以结合少样本提示来实现。于是在这里,我们来实现一个基于 LangChain 的结构化信息提取系统,专门从文本中提取人物相关信息。例如我们希望,对于输入以下文本:"篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。这对老友用十年配合弥补了身高的差距。"代码会提取出结构化数据

说白了,就是先造好一个名叫Data的模具,再定义几个文本描述和标注数据作为样板,接着用一个翻译官把样板变成模型能看懂的工具调用消息格式,再把这些消息和模板通通塞给模型

  1. tool_example_to_messages (工具函数)

    • 它是一个翻译官 。我们定义了一对示例:"输入文本"和"期望的结构化对象"。但这个"结构化对象"模型看不太明白,得由它翻译成模型能秒懂的消息序列(即 HumanMessage → AIMessage(带工具调用) → ToolMessage → AIMessage)。

    • 它的核心参数

      • input_str:示例中的原始文本

      • tool_calls:我们希望模型调用的工具列表(这里是包含我们定义的 Data 对象的列表)

      • ai_response:最后的确认消息,比如"检测到人"或"未检测到人"

    • 为什么需要它?

      因为我们希望模型学会调用工具来输出结构化结果。这个函数把"教它调用工具"的过程完全自动化了,不用我们手动去拼那四条消息。

  2. DataPerson (Pydantic BaseModel)

    • 它们是干什么的?

      它们是数据模具 。我们用它来定义最终想要的那个结构化数据的"形状"。Person 定义了单个人的信息,Data 定义了一组 Person 的列表。Field(description="...") 是模具上的标签,告诉模型每个字段是什么意思。

    • 它的核心参数

      • Field(...) 表示该字段是必填的。

      • Field(default=None) 表示该字段是可选的。

    • 为什么需要它?

      之前我们让模型输出纯文本,现在要它输出一个结构化的"对象"。Pydantic 类能精确描述这个对象的样子,并能在模型输出后进行验证,确保格式正确。它也是我们定制的工具

  3. with_structured_output(schema)

    • 它是干什么的?

      这是一个模式绑定器 。它能把我们刚才定义的那个"数据模具(Data 类)"绑定到模型上,告诉模型:"你接下来输出的内容,必须严格按照这个模具来"

    • 为什么需要它?

      这是开启模型"结构化输出"能力的核心开关。没有它,模型只能返回给我们人类看的一堆字。

示例代码:

定义数据模具

Data

└── people: List[Person] ← 一个 Data 里包含多个人

└── Person ← 每个人有自己的属性

├── name

├── hair_color

├── skin_color

└── height_in_meters

python 复制代码
# ==================== 0. 导入所需模块 ====================
from typing import List, Optional
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage
from langchain_core.utils.function_calling import tool_example_to_messages
from langchain_deepseek import ChatDeepSeek

# ==================== 1. 定义数据模具(结构化输出 Schema)====================
# 为什么要定义这两个类?这就叫"结构化输出的 Schema"。
# 想一想:你让大模型"从一段文字里提取人名和身高",它默认只会返回一段自然语言文字,
# 比如"我发现了王伟,身高两米..."。但你的程序需要的是 Python 对象,方便后续存入数据库、转 JSON、做逻辑判断。
# Pydantic 的作用就是: 定义一个"模具",告诉模型:你的输出必须长成这个样子,缺字段填 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="以米为单位的高度")

# 再定义一个总"模具",里面装着多个 Person
class Data(BaseModel):
    """提取关于人的数据。"""
    people: List[Person]
准备两个关键示例

这两个示例的设计非常讲究,不是随便选的,两个示例覆盖了最核心的两种边界情况。这比给 10 个"完美信息"的示例更有效。

示例 输入特征 期望输出 教会模型什么
示例1 文本里没有任何名字 Data(people=[]),空列表 不要无中生有。没人就是没人,别捏造
示例2 有人名但其他信息全缺 Person(name="小强", height=None, ...) 不知道就填 None。别因为不知道身高就瞎编一个
python 复制代码
# ==================== 2. 准备两个关键示例(教材) ====================
# 示例1(无人物信息):把文本和期望的 Data 对象配成对
example_1_text = "海洋是广阔而蓝色的。它有两万多英尺深。"
example_1_output = Data(people=[])  # 期望:没提取到人

# 示例2(部分信息缺失):给模型看,信息不全的也要正常提取
example_2_text = "小强从中国远行到美国。"
example_2_output = Data(people=[
    Person(name="小强", height_in_meters=None, skin_color=None, hair_color=None),
])

# 把示例放在一个列表里,方便之后处理
examples = [
    (example_1_text, example_1_output),
    (example_2_text, example_2_output),
]
把示例变成模型能读的消息
python 复制代码
# ==================== 3. 把示例变成模型爱吃的"粮草"(消息序列)====================
#准备一个用来装所有示例消息的大箱子等会儿 MessagesPlaceholder 会用这个列表替换掉自己。
example_messages = []

for text, tool_call in examples:
    # 根据提取结果,生成一个简单的AI确认语
    if tool_call.people:
        ai_response = "检测到人"
    else:
        ai_response = "未检测到人"

    # 关键一步:调用"翻译官" tool_example_to_messages
    # 它会把这个示例对,翻译成【HumanMessage, AIMessage(调用工具), ToolMessage, AIMessage("检测到人")】
    # 并加到 example_messages 这个大箱子里
    example_messages.extend(
        tool_example_to_messages(
            text,           # 原始文本
            [tool_call],    # 要调用的工具列表,这里就是把示例中的 Data 对象包一层
            ai_response=ai_response  # 最终的AI确认消息
        )
    )

遍历每一个示例,每次循环拿到:

变量 第一次循环的值 第二次循环的值
text "海洋是广阔而蓝色的..." "小强从中国远行到美国。"
tool_call Data(people=[]) Data(people=[Person(name="小强", ...)])
ai_response "未检测到人" "检测到人"

tool_call.people 的判断逻辑:

空列表 [] 在 Python 里是 falsy 的 → "未检测到人"

有元素的列表是 truthy 的 → "检测到人"

tool_example_to_messages 内部做了什么?以示例 2("小强从中国远行到美国")为例,它会生成如下消息序列:

顺序 消息类型 内容 为什么
1 HumanMessage "小强从中国远行到美国。" 模拟用户发来的待提取文本
2 AIMessage tool_calls=[{"name": "Data", "args": {"people": [{"name": "小强", ...}]}}] 模拟 AI 决定调用 Data 工具,传入提取结果
3 ToolMessage 工具调用结果的回执 模拟工具返回了结果
4 AIMessage "检测到人" AI 最后的确认语(你传的 ai_response)

这就是一整套"人机交互记录"。 模型看到这段记录后,会学习到:收到类似文本时,我应该调用 Data 工具,参数长这样。

extend 和 append 的区别:extend 把列表展开后逐个加进去,不是把整个列表当一个元素加进去。因为 tool_example_to_messages 返回的是一个列表(可能 3-4 条消息),要展开。

搭建最终提示模板

.from_messages() 是 ChatPromptTemplate 的一个类方法,和直接 ChatPromptTemplate([...]) 效果完全一样,写法不同而已。

python 复制代码
# ==================== 4. 搭建最终提示模板(总装车间)====================
final_prompt = ChatPromptTemplate.from_messages([
    SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性值,属性值返回null"),
    MessagesPlaceholder("example_messages"),  # 占位符,这里会放入示例消息列表
    ("user", "{new_message}"),               # 真正要提取的新文本
])

MessagesPlaceholder 工作原理:

模板定义时 ------ 这里是个坑

python 复制代码
MessagesPlaceholder("example_messages")

invoke 调用时 ------ 用实际列表填坑

python 复制代码
final_prompt.invoke({
    "example_messages": example_messages,  # ← 替换那个坑
    "new_message": "篮球场上,身高两米的..."
})

填完之后,最终消息序列就是:

**[0] SystemMessage: "你是一个提取信息的专家..."

1\] HumanMessage: "海洋是广阔而蓝色的..." ← 示例1开始 \[2\] AIMessage: (调用 Data,空列表) \[3\] ToolMessage: (工具回执) \[4\] AIMessage: "未检测到人" ← 示例1结束 \[5\] HumanMessage: "小强从中国远行到美国。" ← 示例2开始 \[6\] AIMessage: (调用 Data,Person对象) \[7\] ToolMessage: (工具回执) \[8\] AIMessage: "检测到人" ← 示例2结束 \[9\] HumanMessage: "篮球场上,身高两米的..." ← 真实问题** ##### 绑定模具,组装链,执行 ```python # ==================== 5. 绑定模具、组装成链、执行(开工!)==================== # 初始化模型,并绑定我们的"数据模具",让模型学会结构化输出 model = ChatDeepSeek(model="deepseek-chat").with_structured_output(schema=Data) # 用管道符 | 把提示模板和处理好的模型串成链 chain = final_prompt | model # 准备我们要提取新信息的文本 new_text = ("篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。" "这对老友用十年配合弥补了身高的差距。") # 执行链,传入示例消息和新文本 result = chain.invoke({ "example_messages": example_messages, "new_message": new_text, }) ``` **.with_structured_output(schema=Data)** 是关键。 它告诉模型:你的输出不要给我自然语言,直接给我一个 Data 对象。LangChain 会自动: 把 Data 这个 Pydantic 类转成模型的 tool/function calling 定义 模型返回后,自动把 JSON 解析成 Data 对象 你拿到的 result 直接就是 Data 类型,可以直接 .people 取列表 ##### 打印结果 ```python 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')] ``` ![](https://i-blog.csdnimg.cn/direct/78771d9f6ec74bba822e0efb183b2852.png) ##### 数据流图 ![](https://i-blog.csdnimg.cn/direct/aec7fe6eb23841c4ae0413a54234d714.png) ### 3.4 示例选择器(Example selectors) **3.4.1 概念** 一旦我们有了示例数据集,就需要考虑提示中应该有多少个示例。关键的权衡是,更多的示例通常会提高性能,但更大的提示会增加成本和延迟。超过某个阈值,太多示例可能会开始混淆模型。 找到正确数量的示例在很大程度上取决于模型、任务、示例的质量以及成本和延迟限制。 若此时我们有【大量】的示例数据集。对于大模型来说,就没必要全部使用与参考。我们需要有种 方法可以**根据给定的输入,从数据集中选择示例** 。在 LangChain 中,示例选择器就可以帮我们**从一组【示例的集合】中根据具体策略选择正确的【示例子集】构建少样本提示。** **示例选择器就是智能筛选:只挑最相关的几个示例。** ![](https://i-blog.csdnimg.cn/direct/c41103aed5a14d56b4783a653be7a387.png) 选择策略有: • Length :根据特定【长度】内可以容纳的数量选择示例。 • Similarity :使用输入和示例之间的【语义相似性】来决定选择哪些示例。 • MMR :使用输入和示例之间的【最大边际相关性】来决定要选择哪些示例。 • Ngram :使用输入和示例之间的【ngram 重叠】来决定要选择哪些示例。 这些其实都是自然语言处理(NLP)里的相似性衡量问题。 四种选择策略 选择合适的示例选择器需要根据示例库规模和对相关性、多样性的要求综合判断,下表对比了四种常用策略: | 选择器 | 筛选依据与原理 | 适用场景 | |---------------------------------------|-----------------------------------------------------|------------------------------------| | `LengthBasedExampleSelector` | 长度(字符数 按字符/词数限制,确保格式化后的示例总长度不超过设定上限 | 上下文窗口有限,简单直接,适合示例较少且长度差异不大的情况 | | `SemanticSimilarityExampleSelector` | 语义向量相似度 用 Embedding 算用户输入与示例输入的语义相似度,选择最相似的 top_k 个 | 示例库大,需要强相关性,常用于客服、知识检索类任务 | | `MaxMarginalRelevanceExampleSelector` | 语义相似 + 多样性 结合相似度与多样性平衡(MMR 算法),避免选出的示例内容重复 | 既要相关,又要避免重复覆盖不同模式,提升泛化能力 | | `NGramOverlapExampleSelector` | n-gram 重合数量 通过 n-gram 重叠率计算文本表面相似度 | 简单文本相似度匹配,适用于缺少 Embedding 引擎的轻量级环境 | #### 3.4.2 按长度选择 --- LengthBasedExampleSelector 当我们担心构造提示时超过上下文窗口长度,我们可以根据特定长度内可以容纳的数量选择示例。对于较长的输入,它将选择更少的示例来包含;而对于较短的输入,它将选择更多示例。 | 类别 | 项目 | 详细说明 | |------------|------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **类基本信息**​ | **完整路径**​ | `langchain_core.example_selectors.length_based.LengthBasedExampleSelector` | | | **核心作用**​ | 基于**长度限制** 智能选择示例,确保选中的示例与用户输入组合后,**不超过模型的最大上下文长度(token限制)**,防止提示被截断。 | | | **适用场景**​ | • 少量示例但文本较长,容易超限的场景• 需要动态裁剪示例,而非简单限制数量• 对 token 使用效率要求高的应用 | | **初始化参数**​ | `example_prompt` | **类型** :`PromptTemplate`**作用**:定义如何将单个示例字典格式化为文本字符串。决定了每个示例在提示中占用的空间大小。 | | | `examples` | **类型** :`list[dict[str, str]]`**作用**:候选示例池。包含所有可供选择的原始示例数据,每个示例是一个键值对字典。 | | | `max_length` | **类型** :`int`**作用** :**硬性长度上限**。选择器会累加示例长度,确保总和不超过此值。超过时停止添加更多示例。 | | | `get_text_length` | **类型** :`Callable[[str], int]`**默认值** :字数统计(或字符数统计)**作用** :测量文本长度的函数。可根据需要替换为 **tiktoken 的 token 计数函数**(推荐),以更精确匹配模型的实际 token 消耗。 | | **核心方法**​ | `add_example(example)` | **功能** :向候选示例列表中动态添加新的示例**输入** :`dict[str, str]`(键为变量名,值为具体内容)**注意**:添加后会影响后续的选择结果 | | | `select_examples(input_variables)` | **功能** :根据输入变量的长度,从候选池中挑选最合适的示例子集**输入** :`dict[str, str]`(当前用户的输入,用于评估所需空间)**输出** :`list[dict]`(要包含在最终提示中的示例列表,按顺序排列)**算法逻辑** :通常计算 `input_length + sum(selected_example_lengths)`,直到接近但不超过 `max_length` | | | | | | **关键优势**​ | **动态适应**​ | 不固定示例数量,而是根据实际占用空间动态选择,更灵活高效 | | | **防超限**​ | 有效避免因为示例过多导致模型报错或截断关键信息 | | | **可定制**​ | 支持自定义长度计算方式,兼容不同模型的 token 计算规则 | 接下来,演示一下如何使用长度示例选择器: 1. 给一个示例集,输入和输出互为反义词 ```python from langchain_core.example_selectors import LengthBasedExampleSelector from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate #1、准备示例集 examples=[ {"input":"happy", "output":"sad"}, {"input": "tall", "output": "short"}, {"input": "energetic", "output": "lethargic"}, {"input": "sunny", "output": "gloomy"}, {"input": "windy", "output": "calm"}, ] ``` 2. 定义PromptTemplate 字符串模板,包含输入和输出两个"占位符" ```python #2、定义单个示例的格式化模板(字符串版) example_prompt=PromptTemplate( input_variables=["input","output"], template="Input:{input}\nOutput:{output}", ) # 格式化后一条示例长这样: # Input: happy # Output: sad # 约 4 个"单词"(Input: 算 1,happy 算 1,Output: 算 1,sad 算 1) ``` 3. 定义LengthBasedExampleSelector 长度示例选择器,设置初始示例集与最大长度 ```python #3、创建长度选择器(关键) example_selector=LengthBasedExampleSelector( examples=examples, #所有示例 example_prompt=example_prompt, #按什么格式计算长度 max_length=25, #最大允许25个单词 ) ``` 4. 定义一个FewShotPromptTemplate 模板对象,用于实例化示例,将示例转化为聊天消息 ```python #4、用选择器代替examples传给模板 dynamic_prompt=FewShotPromptTemplate( example_selector=example_selector, example_prompt=example_prompt, prefix="给出每个输入的反义词", suffix="Input:{adjective}\nOutput:", input_variables=["adjective"], ) ``` 5. 打印消息结果 ```python #5、看看选择器挑了哪些示例(短输入------全保留) prompt_text = dynamic_prompt.invoke({"adjective": "big"}).to_messages()[0].content print("=== 短输入:构造出的提示词 ===") print(prompt_text) #6、看看长输入时选择器裁掉了哪些(长输入------部分被裁) # long_input = "非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常 非常大" # print("\n=== 长输入:构造出的提示词 ===") # print(dynamic_prompt.invoke({"adjective": long_input}).to_messages()[0].content) #7、真正调用模型,让 LLM 给出反义词 from langchain_deepseek import ChatDeepSeek model = ChatDeepSeek(model="deepseek-v4-flash") chain = dynamic_prompt | model response = chain.invoke({"adjective": "big"}) print("\n=== 模型回答(big 的反义词)===") print(response.content) ``` ![](https://i-blog.csdnimg.cn/direct/22e8b406d75c412db2808dba4706920e.png) ![](https://i-blog.csdnimg.cn/direct/deaa5d65b02d4ad59db544f8cc7b09de.png) #### 3.4.3 按语义相似度选择 --- SemanticSimilarityExampleSelector 什么是语义相似?它是衡量文本在【含义上】的接近程度。例如下述两段文本: **text1 = "我喜欢猫" text2 = "我讨厌狗"** 这两段文本表面相似度低,但**语义上都是表达对动物的态度。** 再例如: **text1 = "苹果很甜" text2 = "苹果市值创新高"** "苹果"可以指水果或公司,**语义相似可以解决一词多义问题,因此这两段文本语义上不相似。** LangChain 能根据**输入和示例之间的语义相似性来决定选择哪些示例** ,它**通过查找与输入具有最大余弦相似性的嵌入示例来实现这一点。** 相关方法介绍: | 类别 | 项目 | 详细说明 | |---------------|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| | **类基本信息**​ | **完整路径**​ | `langchain_core.example_selectors.semantic_similarity.SemanticSimilarityExampleSelector` | | | **核心作用**​ | 基于**语义相似度**选择最相关的示例。利用向量嵌入(Embeddings)将文本转换为向量,通过计算余弦相似度,找出与用户输入在语义上最接近的示例。 | | | **适用场景**​ | • 示例库庞大,需要智能筛选最相关的几个示例• 文本含义相近但表述不同(如同义词、语境相似)• 需要让模型参考与当前问题最类似的真实案例 | | **初始化/工厂方法**​ | `from_examples()` | **类型** :类方法(工厂函数)**作用**:快速构建语义相似度选择器,自动处理向量化和存储逻辑。 | | | **参数:`examples`**​ | **类型** :`list[dict[str, str]]`**说明**:候选示例列表。作为构建向量数据库的源数据集。 | | | **参数:`embeddings`**​ | **类型** :`Embeddings`实例(如 `OpenAIEmbeddings()`)**说明**:用于将文本转换为向量的嵌入模型接口。决定了语义理解的准确度。 | | | **参数:`vectorstore_cls`**​ | **类型** :向量存储数据库类(如 `FAISS`, `Chroma`等)**说明**:指定用来存储和检索向量的数据库类型。 | | | **参数:`k`**​ | **类型** :`int`**默认值** :`4`**说明** :**Top-K 选择** 。最终返回与输入最相似的 `k`个示例。 | | **核心方法**​ | `add_example(example)` | **功能** :向内部的候选示例列表中添加新示例**输入** :`dict[str, str]`(键为变量名,值为具体内容)**注意**:添加后通常会重新构建索引,以便后续能被检索到 | | | `select_examples(input_variables)` | **功能** :根据输入变量的语义,检索最匹配的示例**输入** :`dict[str, str]`(当前用户输入)**输出** :`list[dict]`(最相似的示例列表)**原理**:将输入转为向量 -\> 在向量库中做近似最近邻搜索(ANN) -\> 返回 Top-K 结果 | 案例代码: 注意:运行前先安装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 PromptTemplate, FewShotPromptTemplate from langchain_openai import OpenAIEmbeddings from langchain_deepseek import ChatDeepSeek #1、准备示例集 examples = [ {"input": "happy", "output": "sad"}, {"input": "tall", "output": "short"}, {"input": "energetic", "output": "lethargic"}, {"input": "sunny", "output": "gloomy"}, {"input": "windy", "output": "calm"}, ] #2、定义单示例的格式化模板 example_prompt=PromptTemplate( input_variables=["input", "output"], template="Input:{input}\nOutput:{output}", ) #3、创建语义相似度选择器(关键 example_selector=SemanticSimilarityExampleSelector.from_examples( examples, #所有示例 #OpenAIEmbeddings(), #谁来把文字转换成向量 #这里我们用OllamaEmbeddings(一个免费本地嵌入模型)替代OpenAIEmbeddings() #因为我用的是deepseek,没订阅openai的api OllamaEmbeddings(model="nomic-embed-text"), #谁来把文字转换成向量 Chroma, #向量存在哪个数据库里(Chroma 是本地向量库) k=1, #只挑最相似的一个 ) #4、组装模板 similar_prompt=FewShotPromptTemplate( example_selector=example_selector, example_prompt=example_prompt, prefix="给出每个输入的反义词", suffix="Input: {adjective}\nOutput:", input_variables=["adjective"], ) #5、看看选择器挑了哪个示例(仅打印提示,不调模型) prompt_text = similar_prompt.invoke({"adjective": "worried"}).to_messages()[0].content print("=== 构造出的提示词 ===") print(prompt_text) #6、真正调用模型,让 LLM 给出反义词 model = ChatDeepSeek(model="deepseek-v4-flash") chain = similar_prompt | model response = chain.invoke({"adjective": "worried"}) print("\n=== 模型回答 ===") print(response.content) ``` 输出结果: ![](https://i-blog.csdnimg.cn/direct/9e583a5ed7ed43c7884a37d610ede367.png) **为什么选 happy 而不是 tall? Embedding 模型把每个词转成向量后,在向量空间里:** **worried ──────────────────── happy (担忧) 距离 0.3 (开心) │ │ 距离 1.2 │ │ tall (高) --- 语义不相干,离得很远** **worried 和 happy 的向量夹角最小,余弦相似度最高,所以被选中。tall(身高)和 worried(情绪)语义不沾边,排在最后。** #### SemanticSimilarityExampleSelector和LengthBased对比 ![](https://i-blog.csdnimg.cn/direct/1bf59715dd644c11b09e58514b3d93b2.png) #### 3.4.4 按最大边际相关性选择 --- MaxMarginalRelevanceExampleSelector (MMR) 什么是最大边际相关性?它是**一种重新排序算法** ,它**使用语义相似性作为基础工具** ,从一个候选集中挑选出一组**既能代表查询主题又彼此多样化的结果**。听起来好像和语义相似性类似,用一个例子看下两者的区别: **• 【语义相似性】**就像面试官衡量每个应聘者与职位要求的匹配度。他会给每个应聘者打一个分数。 **• 【最大边际相关性】** 就像团队经理(MMR算法)要组建一个团队。目标是选出一组"精华"结果, 而不是一个单一结果: ◦ 每个成员都要满足基本职位要求(满足相关性)。 ◦ 但经理不希望团队里全是只会一种技能的程序员。他需要前端、后端、算法、测试等不同专长 的人,以确保团队能力全面、减少冗余(新颖性/多样性)。 ◦ 经理的策略是:先招一个最匹配的技术大牛(第一步),然后接下来招的人,既要技术达标, 又要和已招的人技能互补(迭代过程)。 了解下使用最大边际相关性的场景,更能让我们理解其概念: **• 语义相似性使用场景:**搜索引擎的基础排序、重复检测、聚类、语义搜索。 **• MMR 使用场景:** ◦ 推荐系统:推荐与用户兴趣相关但又不同类型的物品,避免"信息茧房"。 ◦ 文档摘要:从长文档中选择能代表主旨又包含不同信息的句子,避免摘要内容重复。 ◦ RAG (检索增强生成):在从知识库检索完一堆相关文档后,使用 MMR 进行去重和多样化筛选, 再交给LLM生成答案,能有效提升答案质量和减少幻觉。 **思路** MRC 在语义相似的基础上加了一条约束:挑出来的示例之间不能太像。 举个例子,示例库有: happy → sad (情绪) energetic → lethargic (情绪) tall → short (身高) sunny → gloomy (天气) windy → calm (天气) 输入是 worried(情绪词),如果只用语义相似度,k=2 会选出 happy 和 energetic,全是情绪类。但 MMR 会选 happy(情绪,最相关) + windy(天气,不重复但有价值)。 相关方法: | 类别 | 项目 | 详细说明 | |---------------|------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| | **类基本信息**​ | **完整路径**​ | `langchain_core.example_selectors.semantic_similarity.MaxMarginalRelevanceExampleSelector` | | | **核心作用**​ | 基于 **MMR(Maximal Marginal Relevance,最大边际相关性)** ​ 算法选择示例。它在**相关性** (与查询相似)和**多样性**(示例之间不重复)之间取得平衡,避免选出的示例过于同质化。 | | | **适用场景**​ | • 需要示例既相关又多样化,覆盖不同角度• 避免所有示例都集中在同一个子话题上• 处理复杂任务时,希望模型看到多种不同的处理方式 | | **核心原理**​ | **MMR 算法**​ | 1. 首先选择与查询最相关的示例(高相关性)2. 随后迭代选择:与查询足够相关,**同时**与已选示例差异最大的示例(高多样性)3. 通过调节 λ参数(通常在实现中)平衡相关与多样 | | **初始化/工厂方法**​ | `from_examples()` | **类型** :类方法(工厂函数)**作用**:快速构建 MMR 选择器,自动处理向量化和存储逻辑。 | | | **参数:`examples`**​ | **类型** :`list[dict[str, str]]`**说明**:候选示例列表。作为构建向量数据库的源数据集。 | | | **参数:`embeddings`**​ | **类型** :`Embeddings`实例(如 `OpenAIEmbeddings()`)**说明**:用于将文本转换为向量的嵌入模型接口。 | | | **参数:`vectorstore_cls`**​ | **类型** :向量存储数据库类(如 `FAISS`, `Chroma`等)**说明**:指定用来存储和检索向量的数据库类型。 | | | **参数:`k`**​ | **类型** :`int`**默认值** :`4`**说明** :**Top-K 选择** 。最终返回经过 MMR 算法筛选后的 `k`个示例。 | | | **隐式参数:`fetch_k`**​ | **类型** :`int`**说明** :*(注:通常该工厂方法还接受 `fetch_k`参数,默认常为 20)* 表示从向量库中先检索出 `fetch_k`个最相关的候选,再从中通过 MMR 精选出 `k`个。这是为了在多样性选择前有足够的候选池。 | | **核心方法**​ | `add_example(example)` | **功能** :向内部的候选示例列表中添加新示例**输入** :`dict[str, str]`(键为变量名,值为具体内容)**注意**:添加后通常会重新构建索引,以便后续能被检索到 | | | `select_examples(input_variables)` | **功能** :根据输入变量,使用 MMR 算法选择要使用的示例**输入** :`dict[str, str]`(当前用户的输入)**输出** :`list[dict]`,包含最终选中的示例列表(已平衡相关性与多样性) | 案例代码,和语义选择器几乎一模一样,唯一的区别就是类名 ```python from langchain_chroma import Chroma from langchain_core.example_selectors import MaxMarginalRelevanceExampleSelector from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate from langchain_openai import OpenAIEmbeddings #1、准备示例集 examples = [ {"input": "happy", "output": "sad"}, {"input": "tall", "output": "short"}, {"input": "energetic", "output": "lethargic"}, {"input": "sunny", "output": "gloomy"}, {"input": "windy", "output": "calm"}, ] #2、定义单示例的格式化模板 example_prompt=PromptTemplate( input_variables=["input", "output"], template="Input:{input}\nOutput:{output}", ) #3、创建语义相似度选择器(关键 # 和语义相似度选择器几乎一模一样,唯一的区别是类名 example_selector = MaxMarginalRelevanceExampleSelector.from_examples( examples, #OpenAIEmbeddings(), #谁来把文字转换成向量 #这里我们用OllamaEmbeddings(一个免费本地嵌入模型)替代OpenAIEmbeddings() #因为我用的是deepseek,没订阅openai的api OllamaEmbeddings(model="nomic-embed-text"), #谁来把文字转换成向量 Chroma, k=2, # 挑 2 个:一个最相关 + 一个不重复 ) #4、组装模板 similar_prompt=FewShotPromptTemplate( example_selector=example_selector, example_prompt=example_prompt, prefix="给出每个输入的反义词", suffix="Input: {adjective}\nOutput:", input_variables=["adjective"], ) #5、看看选择器挑了哪些示例(仅打印提示,不调模型) prompt_text = similar_prompt.invoke({"adjective": "worried"}).to_messages()[0].content print("=== 构造出的提示词 ===") print(prompt_text) #6、真正调用模型,让 LLM 给出反义词 model = ChatDeepSeek(model="deepseek-v4-flash") chain = similar_prompt | model response = chain.invoke({"adjective": "worried"}) print("\n=== 模型回答 ===") print(response.content) ``` 输出结果: ![](https://i-blog.csdnimg.cn/direct/a30e41187db4458cbfee49c48f30389a.png) #### SemanticSimilarity与MMR对比 ![](https://i-blog.csdnimg.cn/direct/156deb5eea4e44148c850326dd2440c5.png) #### 3.4.5 按 n-gram 重叠选择 --- NGramOverlapExampleSelector 什么是【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_community.example_selectors.ngram_overlap.NGramOverlapExampleSelector` | | | **核心作用**​ | 基于 **n-gram 重叠分数** (文本片段重合度)选择示例。通过统计输入文本与候选示例之间的共同短语片段(n-gram)数量,衡量文本间的**表面相似度**。 | | | **适用场景**​ | • 需要匹配特定的关键词、短语或句式结构• 文本相似度主要关注字面重叠(如代码片段、专业术语、固定搭配)• 过滤掉与输入完全无关、没有共同词汇的候选示例 | | | **所在模块**​ | `langchain_community`(社区贡献模块,非核心模块) | | **初始化参数**​ | `example_prompt` | **类型** :`PromptTemplate`**作用**:用于格式化单个示例的模板。定义了如何将候选示例字典转换为字符串,以便计算其与输入的 n-gram 重叠。 | | | `examples` | **类型** :`list[dict[str, str]]`**作用**:候选示例列表。包含所有可供选择的原始示例数据。 | | | `threshold` | **类型** :`float`**默认值** :`-1.0`**作用** :**过滤阈值** ,控制示例的保留与排除逻辑:• **负值(如 -1.0)** :仅按重叠分数**降序排序** ,**不排除** 任何示例(适合查看所有相似度排名)• **0.0** :按分数排序,但**完全排除** 与输入无 n-gram 重叠的示例(严格过滤)• **大于 1.0** :**排除所有**示例,返回空列表(极端情况,相当于禁用) | | **核心方法**​ | `add_example(example)` | **功能** :向内部的候选示例列表中动态添加新示例**输入** :`dict[str, str]`(键为变量名,值为具体内容)**注意**:添加后会影响后续的重叠分数计算和排序结果 | | | `select_examples(input_variables)` | **功能** :计算输入与各示例的 n-gram 重叠分数,并返回按分数排序的示例列表**输入** :`dict[str, str]`(当前用户输入,通常为单条文本)**输出** :`list[dict]`(按重叠分数**降序排列**的示例列表,已应用 threshold 过滤逻辑) | | **算法特性**​ | **n-gram 粒度**​ | 默认通常使用 **字符级 n-gram**(如 3-gram/三元组),也可根据实现调整为词级。粒度越细,对拼写错误越鲁棒;粒度越粗,对短语匹配越敏感。 | | | **分数计算**​ | 常见公式:`重叠分数 = (共同 n-gram 数量) / (输入 n-gram 总数 + 候选 n-gram 总数 - 共同 n-gram 数量)`,取值范围通常在 \[0, 1\] 之间,1 表示完全重合。 | | | **与语义相似度区别**​ | 不同于嵌入向量的**语义相似度** (能识别同义词),ngram 重叠是**字面匹配**(lexical overlap),更关注表层文本的重合程度。 | 接下来,演示一下如何使用语义相似示例选择器: 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 PromptTemplate, FewShotPromptTemplate #1、准备翻译示例(英译中) examples = [ {"input": "See Spot run.", "output": "看见Spot跑。"}, {"input": "My dog barks.", "output": "我的狗叫。"}, {"input": "Spot can run.", "output": "Spot可以跑。"}, ] #2、单示例格式化模板 example_prompt = PromptTemplate( input_variables=["input", "output"], template="Input: {input}\nOutput: {output}", ) #3、创建 NGram 选择器 example_selector = NGramOverlapExampleSelector( examples=examples, example_prompt=example_prompt, threshold=-1.0, # 负数:按重叠分数降序排列,不排除任何示例 ) #4、组装模板 dynamic_prompt = FewShotPromptTemplate( example_selector=example_selector, example_prompt=example_prompt, prefix="给出每个输入的中文翻译", suffix="Input: {sentence}\nOutput:", input_variables=["sentence"], ) #5、看看选择器按什么顺序排列了示例 prompt_text = dynamic_prompt.invoke({"sentence": "Spot can run fast."}).to_messages()[0].content print("=== 构造出的提示词 ===") print(prompt_text) #6、真正调用模型,让 LLM 给出翻译 from langchain_deepseek import ChatDeepSeek model = ChatDeepSeek(model="deepseek-v4-flash") chain = dynamic_prompt | model response = chain.invoke({"sentence": "Spot can run fast."}) print("\n=== 模型回答 ===") print(response.content) ``` 运行结果: ![](https://i-blog.csdnimg.cn/direct/0115be08a9a84a3fbdbd3be56333866a.png) ![](https://i-blog.csdnimg.cn/direct/06cf746b15224d0da359a30ce734700c.png) ### 四种选择器对比 ![](https://i-blog.csdnimg.cn/direct/eb2084241676481993226749ed10604a.png) 你的场景: ├─ 上下文窗口紧,怕超长 → LengthBasedExampleSelector ├─ 示例库巨大,要精准匹配 → SemanticSimilarityExampleSelector ├─ 要匹配 + 要多样性(如 RAG) → MaxMarginalRelevanceExampleSelector └─ 纯文本匹配就够了,不想花钱调 API → NGramOverlapExampleSelector ok,LangChain之核心组件(少样本提示词)就先讲这么多。

相关推荐
多年小白4 小时前
2026年5月5日
大数据·人工智能·深度学习·microsoft·机器学习·ai·自动驾驶
AI绘画哇哒哒4 小时前
RAG 系统中文档切分策略:如何选择合适的 chunk size?| 收藏这份实用指南,小白也能轻松上手大模型学习
人工智能·学习·ai·程序员·大模型·产品经理·转行
2601_957964874 小时前
48V系统轻型巡检机器人锂电池完整设计方案要求(工业级智能巡检平台标准)【浩博电池】
人工智能·机器人
Jinkxs4 小时前
深度评测 GLM-5:AtomGit 首发模型的代码生成实战体验
人工智能·深度学习·大模型·atomgit·glm-5
石逸凡4 小时前
AI时代企业数据架构转型趋势二:面向Agent的数据架构设计
人工智能·系统架构
OneBlock Community4 小时前
PBA 官宣 2026 新路线:更聚焦 Parity 人才与产品导向
人工智能
zhuiyisuifeng4 小时前
AI商用合规:GPT-Image-2的许可与版权边界
人工智能·gpt
YANZ2224 小时前
亚马逊绿标(CPF):从环保认证到跨境流量新引擎
java·大数据·人工智能·搜索引擎·百度
酷酷的崽7984 小时前
我把 AI Agent 丢到云端跑了 24 小时,见证如何自主创建和优化技能
人工智能