👋 欢迎阅读

🏠个人主页:愿旖旎 📘专栏传送门:算法专栏 💻当前学习内容:LangChain
📑 目录
-
一、前置讲解
-
二、提示词模板
-
三、示例选择器(Example Selectors)
-
四、复盘(附答案)
一、前置讲解
在进入正文前,先花 10 秒了解本篇会反复用到的核心概念,带着印象去读正文,学习效率更高。
🧠 前置知识一:PromptTemplate
字符串提示词模板,用占位符动态生成提示词。
🗨️ 前置知识二:ChatPromptTemplate
聊天消息模板,可组合 System/User/AI 消息。
📍 前置知识三:MessagesPlaceholder
消息占位符,在指定位置插入一整段对话历史。
🔤 前置知识四:少样本提示(Few-shot)
给模型看几个示例再提问,比写长篇指令更有效。
🎯 前置知识五:示例选择器
从大量示例中按策略挑最合适的几个:长度/语义相似/MMR/NGram。
二、提示词模板
2.1 认识提示词模板
提示词模板(Prompt Template) 是 LangChain 的核心抽象之一,被广泛应用于构建大语言模型(LLM) 应用的各个环节。简单来说,只要是需要动态、批量或有结构地向大语言模型发送请求的地方,几乎都会用到提示词模板。
一个简单的例子:假设我们想根据城市名询问 LLM 其历史,按之前做法,需定义 HumanMessage("请介绍上海的历史")、HumanMessage("请介绍西安的历史") 等,每次都会重复"请介绍 xxx 的历史"。在 LangChain 中,可定义模板:
-
固定文本(模板):"请介绍{city}的历史。"
-
输入变量:["city"]
| 北京 | "北京" | 请介绍北京的历史。 |
| 上海 | "上海" | 请介绍上海的历史。 |

提示词模板就是一个可复用的提示词蓝图,允许我们动态地生成提示词,而不是每次都手动编写完整的提示词。它类似于编程中的字符串格式化功能:创建一个带有占位符的模板,然后在运行时用具体的值(变量)填充这些占位符,从而生成最终发送给 LLM 的完整提示词。
提示词模板解决了以下几个核心问题:
| 可复用性 | 只需定义一个模板,即可用于无数个类似的查询 |
| 关注点分离 | 将提示词的结构和逻辑与具体内容分离:提示工程师优化模板,应用程序提供变量值 |
| 一致性 | 确保发送给 LLM 的提示词结构统一,获得更稳定、可预测的输出 |
| 可维护性 | 修改提示词风格或结构时,只需改一个模板文件,无需在代码多处修改 |
2.2 用法
2.2.1 字符串模板(PromptTemplate)
LangChain 提供了 PromptTemplate 类来轻松实现这一功能。PromptTemplate 实现了标准的 Runnable 接口。
代码示例:
from langchain_core.prompts import PromptTemplate
# 1. 定义模板({language} 是变量占位符)
prompt_template = PromptTemplate.from_template("把下面的内容翻译成{language}")
# 2. 实例化模板(传入变量值,{language} 被替换成实际内容)
print(prompt_template.invoke({"language": "中文"}))
运行结果:
text='把下面的内容翻译成中文'
PromptTemplate 类的参数与方法:
| template | 提示模板 |
| input_variables | 需要其值作为提示输入的变量名称列表 |
| from_template() | 从模板定义提示模板,返回一个 PromptTemplate 实例 |
因此,除了通过 PromptTemplate.from_template 定义提示模板的方式外,下面这种方式也可以直接初始化模板:
代码示例:
prompt_template = PromptTemplate(
input_variables=["language"],
template="Translate the following into {language}",
)
2.2.2 聊天消息模板(ChatPromptTemplate)
ChatPromptTemplate 模板:专为 LangChain 聊天模型设计。可以方便地构建包含 SystemMessage、HumanMessage、AIMessage 的消息模板。
代码示例:
from langchain_core.prompts import ChatPromptTemplate
# 1. 设置模板(两段消息:system 定义角色/规则,user 是用户内容)
# {language}、{text} 是变量占位符
prompt_template = ChatPromptTemplate(
[
("system", "把下面的内容翻译成{language}。"),
("user", "{text}"),
]
)
# 说明:
# 在 0.2.24 版本后可以直接使用 ChatPromptTemplate() 来初始化模板
# 在 0.2.24 版本前,需要使用 ChatPromptTemplate.from_messages() 来初始化模板
# 2. 实例化模板,获取消息实例(把变量值填进去)
messages_value = prompt_template.invoke(
{
"language": "中文",
"text": "你叫什么名字?",
}
)
# to_messages():把 PromptValue 转成消息列表
messages = messages_value.to_messages()
print(messages)
运行结果:
[SystemMessage(content='把下面的内容翻译成中文。', …), HumanMessage(content='你叫什么名字?', …)]
ChatPromptTemplate 同样实现了标准的 Runnable 接口,因此我们还可以通过链来完成调用:
代码示例:
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 定义大模型
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
# 1. 设置模板(system 定义规则,user 是用户内容)
prompt_template = ChatPromptTemplate(
[
("system", "把下面的内容翻译成{language}。"),
("user", "{text}"),
]
)
# 2. 定义输出解析器(把 AIMessage 转成纯字符串)
parser = StrOutputParser()
# 3. 定义链:模板填变量 → 调模型 → 解析成字符串
chain = prompt_template | model | parser
# 流式输出:模型生成一点就吐一点
for token in chain.stream(
{
"language": "English",
"text": "你好,我叫斯蒂芬,很高兴认识你",
}
):
print(token, end="|")
运行结果:
|Hello|,| my| name| is| Stephen|.| Nice| to| meet| you|.|||
2.2.3 消息占位符(MessagesPlaceholder)
在上面的 ChatPromptTemplate 中,我们看到了如何格式化两条消息,每条消息都是字符串。但如果希望将消息插入特定位置怎么办?使用 MessagesPlaceholder——它负责在特定位置添加消息列表。
代码示例:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
# 设置模板:system 消息 + 一个"消息占位符"
# MessagesPlaceholder("msgs") = 留一个位置,运行时把一整段对话消息塞进来
prompt_template = ChatPromptTemplate(
[
("system", "你是一个聊天助手"),
MessagesPlaceholder("msgs"), # 消息占位符
]
)
# 要传入占位符的消息列表(多轮对话历史)
messages_to_pass = [
HumanMessage(content="中国首都是哪里?"),
AIMessage(content="中国首都是北京。"),
HumanMessage(content="那法国呢?"),
]
# 实例化模板:把消息列表填进 msgs 占位符
formatted_prompt = prompt_template.invoke({"msgs": messages_to_pass})
print(formatted_prompt)

在不显式使用 MessagesPlaceholder 类也可以完成该能力:

2.3 使用 LangChain Hub 的提示词模板
LangChain Hub 是一个用于上传、浏览、拉取和管理提示词(Prompt) 的地方。随着 LLM 的发展,提示词变得越来越重要。LangChain 正在打造一个类似于 GitHub 的平台——GitHub 长期以来一直是共享和协作代码的首选平台,于是推出了 LangChain Hub。
LangChain Hub 创建一个分享和发现 Prompt 的平台,使开发者更容易发现新用例和精炼提示词。这一举措使提示工程师更容易合作、复用现有提示词,并对其进行微调以实现特定结果,从而加速对话代理和其他基于语言的应用的开发与部署。
LangChain Hub 官网地址:LangSmith
目前收藏最高的提示词模板是:hardkothari/prompt-maker。我们就以它为例,演示如何使用 LangChain Hub 上的提示词。Prompt Maker 模板是一个提示生成器,可以自动化优化提示过程,从而提高语言模型在各种应用中的质量和效果。
要使用该功能,需先申请并配置 LangSmith 环境变量:LANGSMITH_API_KEY="你的 LangSmith API Key"。然后从 hub 拉取相应提示并使用。
代码示例:
import os
from langchain_openai import ChatOpenAI
from langsmith import Client
# 从 LangSmith Hub 拉取 "hardkothari/prompt-maker" 提示词模板
# dangerously_pull_public_prompt=True:确认信任该公共模板(新版 SDK 的安全要求)
client = Client()
prompt = client.pull_prompt(
"hardkothari/prompt-maker",
include_model=True,
dangerously_pull_public_prompt=True,
)
# 定义模型(适配 DeepSeek)
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
# 定义链:Hub 模板 → 模型
chain = prompt | model
# 交互式问答循环
while True:
task = input("\\n你的任务是什么?(输入 quit 退出聊天)\\n")
if task == "quit":
break
lazy_prompt = input("\\n你当前的提示是什么?(输入 quit 退出聊天)\\n")
if lazy_prompt == "quit":
break
print("\\n Response:")
chain.invoke({"lazy_prompt": lazy_prompt, "task": task}).pretty_print()

通过使用这个模板,可以大幅减少手动调整提示词所需的工作量,从而节省时间和资源。Prompt Maker 通过分析初始提示词的结构和内容,然后应用一组预定规则或算法来优化提示词,以提高响应质量、清晰度和相关性。这在提示词质量对模型输出影响较大的场景中特别有用,例如客服机器人、对话代理或数据分析任务。
2.4 少样本提示(Few-shot)
少样本提示是一种通过向 LLM 提供少量具体示例或样本,来教会它如何执行某项特定任务的技术。提高模型性能的最有效方法之一是给出一个模型示例,指导大模型你想做什么、怎么做。
零样本 vs 少样本:我们直接给模型一道考题,不给任何例子——例如输入 "What is 2 🦜 9?",此时模型的解法有千百种,因此没什么效果;但如果给上样本,它就有了固定意义。例如以下样例,模型通过示例学习后可以发现 🦜 与 ➕ 含义相似,并按照此规则得出 What is 2 🦜 9? 的结果是 11:
代码示例(示例集):
examples = [
{"input": "2 🦜 2", "output": "4"},
{"input": "2 🦜 3", "output": "5"},
]
少样本提示主要解决以下核心问题:
| 强制输出格式 | 要求模型以特定格式(JSON/XML/列表)输出,样例作为格式样板,避免自由格式导致解析困难 |
| 指令难以描述的任务 | 如"用莎士比亚风格写作",提供几个示例比写长篇指令更有效 |
| 多步推理场景 | 示例展示思考链(Chain of Thought),引导模型遵循类似推理路径,减少逻辑错误 |
实现少样本提示(FewShotChatMessagePromptTemplate)
要让大模型"看懂"示例,需要将示例集转换为聊天模型可识别的消息格式。在 LangChain 中,通过 FewShotChatMessagePromptTemplate 对象来实现这一目的——它是一个提示词模板,专门用于将示例集实例化为聊天消息。
代码示例:
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import (
ChatPromptTemplate,
FewShotChatMessagePromptTemplate,
)
# 单个示例的格式化模板(一个示例 = 一问一答)
example_prompt = ChatPromptTemplate(
[
("human", "{input}"),
("ai", "{output}"),
]
)
# 样本示例:教模型"🦜"表示加法
examples = [
{"input": "2 🦜 2", "output": "4"},
{"input": "2 🦜 3", "output": "5"},
]
# 少样本提示词模板:把多个示例拼进提示词,让模型照着范例学习
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt, # 用于格式化单个示例
examples=examples, # 样本示例
)
# 最终完整提示词:system 角色 + 示例 + 用户真实提问
final_prompt = ChatPromptTemplate(
[
("system", "你是一个神奇的数学奇才。"),
few_shot_prompt,
("human", "{input}"),
]
)
# 定义模型
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
# 链:最终模板 → 模型
chain = final_prompt | model
chain.invoke({"input": "2 🦜 9 等于多少?"}).pretty_print()
运行结果:
根据我们之前的规律(🦜 代表加法),2 🦜 9 = 2 + 9 = 11。
先来看看 FewShotChatMessagePromptTemplate,它也实现了标准的 Runnable 接口:
| examples | 静态示例列表,与 example_selector 二选一 |
| example_selector | 动态示例选择器 |
| example_prompt | 用于格式化每个示例的模板 |
| prefix | 示例前的提示消息(可选) |
| suffix | 示例后的提示消息(可选) |
| input_variables | 模板输入变量列表(可自动推断) |
| example_separator | 连接各部分的分隔符(默认 "\\n") |
// 返回值: // FewShotChatMessagePromptTemplate:配置好的少样本聊天消息提示模板对象。
生成的提示结构为:前缀消息 + 示例消息 + 后缀消息;支持静态示例列表或基于输入的动态示例选择。
使用案例 1:推理引导(FewShotPromptTemplate)
代码示例:
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
# 创建字符串模板:一个示例 = "Question: 问题\\n答案"
example_prompt = PromptTemplate.from_template("Question: {question}\\n{answer}")
# 创建示例集(教模型:复杂问题要拆成一步一步推理,最后给出最终答案)
examples = [
{
"question": "李白和杜甫,谁更长寿?",
"answer": """
是否需要后续问题:是的。
后续问题:李白享年多少岁?
中间答案:李白享年61岁。
后续问题:杜甫享年多少岁?
中间答案:杜甫享年58岁。
所以最终答案是:李白
""",
},
{
"question": "腾讯的创始人什么时候出生?",
"answer": """
是否需要后续问题:是的。
后续问题:腾讯的创始人是谁?
中间答案:腾讯由马化腾创立。
后续问题:马化腾什么时候出生?
中间答案:马化腾出生于1971年10月29日。
所以最终答案是:1971年10月29日
""",
},
]
# 少样本模板:示例 + 后缀(用户真正的问题)
prompt = FewShotPromptTemplate(
examples=examples, # 示例集
example_prompt=example_prompt, # 每个示例怎么格式化
suffix="Question: {input}", # 示例后面的用户问题模板
input_variables=["input"], # 后缀里的变量
)
# 生成 [HumanMessage] 消息列表
prompt_messages = prompt.invoke(
{"input": "阿狸和亚索是否来自同一个地区?"}
).to_messages()
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
model.invoke(prompt_messages).pretty_print()
运行结果:
阿狸和亚索并非来自同一个地区。
阿狸是艾欧尼亚的瓦斯塔亚狐狸,而亚索是艾欧尼亚的战士,虽然他们都与艾欧尼亚有关,但阿狸的故乡是艾欧尼亚的某片幽静森林,而亚索则来自艾欧尼亚的某个部族(通常被提及为诺克萨斯边境附近的村庄)。严格来说,他们都属于艾欧尼亚,但背景设定中并未明确说他们来自同一具体地区。如果按“地区”广义理解为同一大陆,那么确实都是艾欧尼亚的;如果按具体出生地,则不是同一个地方。所以最终答案取决于“地区”的划定——若指艾欧尼亚,则是;若指具体地点,则不是。
FewShotPromptTemplate 相关方法:
| .invoke() | 输入一个字典给提示模板,返回完整的提示内容 PromptValue |
| PromptValue.to_string() | 将提示值作为字符串返回 |
| PromptValue.to_messages() | 将提示作为消息列表返回 |
使用案例 2:增强信息提取能力
代码示例:
import os
from langchain_openai import ChatOpenAI
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
# 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[float] = 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. 定义提示词模板(system 定规则 + 示例占位 + 用户真问题)
prompt_template = ChatPromptTemplate(
[
SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。"
"如果您不知道要提取的属性的值,属性值返回null"),
MessagesPlaceholder("example_messages"),
("user", "{new_message}"),
]
)
# 4. 获取样例消息列表(把每个示例转成模型能理解的聊天消息格式)
example_messages = []
for txt, tool_call in examples:
ai_response = "检测到人" if tool_call.people else "未检测到人"
example_messages.extend(
tool_example_to_messages(txt, [tool_call], ai_response=ai_response)
)
# 5. 调用
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
structured_model = model.with_structured_output(schema=Data, method="function_calling")
# 定义链:示例+问题 → 结构化提取
chain = prompt_template | structured_model
result = chain.invoke({
"example_messages": example_messages,
"new_message": "篮球场上,身高两米的中锋王伟默契地将球传给一米七的后卫挚友李明,完成一记绝杀。",
})
print(result)
# 额外案例:没有人名的文本
result_no_people = chain.invoke({
"example_messages": example_messages,
"new_message": "海边的日出很美,金色的阳光洒在平静的海面上。",
})
print(result_no_people)
运行结果:

三、示例选择器(Example Selectors)
3.1 概念
一旦我们有了示例数据集,就需要考虑提示中应该有多少个示例。关键的权衡是:更多的示例通常会提高性能,但更大的提示会增加成本和延迟;超过某个阈值,太多示例可能会开始混淆模型。
找到正确数量的示例在很大程度上取决于模型、任务、示例质量以及成本和延迟限制。有趣的是,模型越好,它需要的精准示例就越少。但其实,最佳方法是使用不同数量的示例进行一些实验。
若此时我们有大量的示例数据集,对于大模型来说,没必要全部使用作为参考。我们需要一种方法,根据给定输入从数据集中选择示例。在 LangChain 中,示例选择器可以帮助我们从一组示例集合中,根据具体策略选择正确的示例子集,构建少样本提示。

选择策略:
| Length | 根据特定长度内可容纳的数量选择示例 |
| Similarity | 使用输入和示例之间的语义相似性决定选择哪些示例 |
| MMR | 使用输入和示例之间的最大边际相关性决定选择哪些示例 |
| Ngram | 使用输入和示例之间的 n-gram 重叠决定选择哪些示例 |
3.2 按长度选择示例(Length)
概念
当我们担心构造提示时会超过上下文窗口长度时,可根据特定长度内可容纳的数量选择示例——对于较长的输入,选择更少的示例;对于较短的输入,选择更多的示例。实现按长度选择示例的选择器是:LengthBasedExampleSelector。
常见参数:
| examples | 可用的示例列表,每个示例为字典 |
| example_prompt | 用于格式化每个示例的 PromptTemplate 对象 |
| max_length | 提示词允许的最大长度(默认 2048),超出则截断 |
| get_text_length | 自定义计算文本长度的函数(默认按单词计数) |
// 返回值: // LengthBasedExampleSelector:配置好的示例选择器对象。
内置方法:
-
add_example:将新示例添加到列表中。
SemanticSimilarityExampleSelector::add_example(example: dict[str, str]);
# 功能:
# 向当前的示例选择器中动态添加一个新示例。该示例会被向量化并存储到底层的向量数据库中,
# 以便后续的 `select_examples` 查询能够基于语义相似度检索到它。
# 适用于需要在线更新示例库的场景(如用户反馈或增量学习)。# 参数说明:
# example: 一个字典,键为示例中的变量名(如 "input"、"output"),值为对应的文本内容。
# 该字典的结构应与创建时传入的示例结构完全一致。# 返回值:
# 无(void) -
select_examples:根据输入选择要使用的示例。
SemanticSimilarityExampleSelector.select_examples(
input_variables: dict
) -> List[dict]:# 功能:
# 根据输入的变量字典,从示例存储中选择与输入语义最相似的 k 个示例(k 在构造函数中指定)。
# 该方法会自动将输入变量组合成文本,计算其嵌入向量,然后在向量库中检索最相似的示例。# 参数说明:
# input_variables: 包含当前输入变量的字典,例如 {"input": "large"}。
# 字典中的键值对会被用于构建检索文本,以匹配示例的语义。# 返回值:
# List[dict]:选中的示例列表,每个示例为字典,包含所有示例字段(如 input、output 等)。
代码示例:
import os
from langchain_openai import ChatOpenAI
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,
max_length=15,
)
# 用选择器创建动态模板
dynamic_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="给出每个输入的反义词",
suffix="Input: {adjective}\\nOutput:",
input_variables=["adjective"],
)
#只看组装好的提示词(不调模型)
print(dynamic_prompt.invoke({"adjective": "happy"}).to_messages()[0].content)
运行结果:
=== 提示词内容 ===
给出每个输入的反义词
Input: happy
Output: sad
Input: tall
Output: short
Input: energetic
Output: lethargic
Input: happy
Output:
可见:5 个反义词示例只塞进了前 3 个(长度达到 max_length 就停止)。
3.3 按语义相似性选择示例(Similarity)
概念
语义相似是衡量文本在含义上的接近程度。例如:
| "我喜欢猫" / "我讨厌狗" | 表面相似度低 | 相似:都表达对动物的态度 |
| "苹果很甜" / "苹果市值创新高" | "苹果"一词相同 | 不相似:一个指水果、一个指公司 |
LangChain 能根据输入和示例之间的语义相似性决定选择哪些示例,它通过查找与输入具有最大余弦相似性的嵌入示例来实现。实现该策略的选择器是:SemanticSimilarityExampleSelector。
常见参数:
| vectorstore | 已初始化的向量存储实例(如 Chroma、FAISS) |
| k | 要选择的示例数量(默认 4) |
| example_keys | 过滤示例中需要返回的键 |
| input_keys | 限制用于搜索的输入变量 |
内置方法 from_examples:
| examples | 示例列表 |
| embeddings | 嵌入模型实例 |
| vectorstore_cls | 向量存储类(默认 Chroma) |
| k | 要选择的示例数量 |
其他方法:add_example(添加新示例)、select_examples(根据输入选择示例)。
代码示例:
import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
os.environ["HF_HUB_DISABLE_XET"] = "1"
from langchain_openai import ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_core.embeddings import Embeddings
from fastembed import TextEmbedding
# 嵌入封装:DeepSeek 没有 embedding 接口,这里用 fastembed(本地轻量,免 torch)
class FastEmbed(Embeddings):
def __init__(self, model="BAAI/bge-small-en-v1.5"):
self.m = TextEmbedding(model)
def embed_documents(self, texts):
return [v.tolist() for v in self.m.embed(list(texts))]
def embed_query(self, text):
return next(self.m.embed([text])).tolist()
# 反义词示例集合
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,
FastEmbed(), # 嵌入类用于生成语义相似度向量
Chroma, # 向量库,存储嵌入并做相似度搜索
k=1, # 选几个示例
)
# 用选择器实例化模板
similar_prompt = FewShotPromptTemplate(
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)
运行结果:
=== 提示词内容(worried【担心】语义最接近哪个示例)===
给出每个输入的反义词
Input: happy
Output: sad
Input: worried
Output:
可见:worried(担心)在语义上最接近 happy(开心),所以只选中了它。
3.4 按最大边际相关性选择示例(MMR)
概念
最大边际相关性(MMR,Maximum Marginal Relevance) 是一种重排序算法,它利用语义相似性作为基础工具,从候选集中选出一组既能代表查询主题又彼此多样化的结果。
它与语义相似性的区别可类比为:
| 语义相似性 | 面试官衡量每个应聘者与职位要求的匹配度,逐个打分 | 只看单点相关性 |
| 最大边际相关性(MMR) | 团队经理组建团队:既要技术达标,又要技能互补(前端/后端/算法/测试) | 相关性 + 多样性/新颖性 |
典型使用场景:
| 语义相似性 | 搜索引擎基础排序、重复检测、聚类、语义搜索 |
| MMR | 推荐系统(相关但不同类型,避免"信息茧房")、文档摘要(覆盖主旨且不重复)、RAG(检索后去重+多样化筛选,提升答案质量、减少幻觉) |
实现该策略的选择器是:MaxMarginalRelevanceExampleSelector。
内置方法:from_examples、add_example、select_examples。
代码示例:
import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
os.environ["HF_HUB_DISABLE_XET"] = "1"
from langchain_openai import ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.example_selectors import MaxMarginalRelevanceExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_core.embeddings import Embeddings
from fastembed import TextEmbedding
# 嵌入封装:DeepSeek 没有 embedding 接口,这里用 fastembed(本地轻量,免 torch)
class FastEmbed(Embeddings):
def __init__(self, model="BAAI/bge-small-en-v1.5"):
self.m = TextEmbedding(model)
def embed_documents(self, texts):
return [v.tolist() for v in self.m.embed(list(texts))]
def embed_query(self, text):
return next(self.m.embed([text])).tolist()
# 反义词示例集合
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,
FastEmbed(), # 嵌入类
Chroma, # 向量库
k=2, # 选 2 个(MMR 特点:相似的 + 补充多样性的)
)
# 用选择器实例化模板
similar_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="给出每个输入的反义词",
suffix="Input: {adjective}\\nOutput:",
input_variables=["adjective"],
)
#只看组装好的提示词(MMR 选了哪 2 个示例)
print(similar_prompt.invoke({"adjective": "worried"}).to_messages()[0].content)
运行结果:
=== 提示词内容(MMR 选中的 2 个示例)===
给出每个输入的反义词
Input: happy
Output: sad
Input: windy
Output: calm
Input: worried
Output:
可见:worried → 选了最像的 happy + 补充多样性的 windy。
3.5 按 n-gram 重叠选择示例(NGram)
概念
n-gram 指一个文本序列中连续的 n 个词(word)或字符(character)。n-gram 重叠 是一种通过计算两段文本之间共同拥有的 n-gram 数量来衡量相似度的方法。
| "苹果手机很好用" / "这款手机很好用" | 高(重复词多) | 相似 |
| "苹果手机很好用" / "iPhone 非常不错" | 0(无相同词) | 相似但表面不匹配 |
因此,传统 n-gram 重叠 是一种表面形式匹配,只关心词是否完全一样,无法处理同义词。
语义 n-gram 重叠:它不再比较词本身,而是比较词背后的语义向量(Embedding)——计算它们在语义空间中的向量是否相似,若相似度超过阈值就认为它们"重叠"。例如 "苹果" vs "iPhone" 向量相似度 0.95 → 视为重叠;"好用" vs "不错" 相似度 0.82 → 视为重叠。常用于剽窃检测,能发现"改换了词汇但保留了核心思想"的"智能"剽窃。
LangChain 实现该策略的选择器是:NGramOverlapExampleSelector,其参数如下:
| example_prompt | PromptTemplate,用于格式化示例的提示模板 |
| examples | 模板所需的示例列表 |
| threshold | 算法停止的阈值,默认 -1.0(见下表) |
threshold 取值行为:
| 负值 | 按重叠分数排序,不排除任何示例 |
| 0.0 | 按重叠分数排序,排除与输入没有 n-gram 重叠的示例 |
| > 1.0 | 排除所有示例,返回空列表 |
内置方法:add_example(example)、select_examples(input_variables) → list[dict]。
代码示例:
import os
from langchain_openai import ChatOpenAI
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 示例选择器:按 n-gram 重叠度来挑和用户问题最像的示例
example_selector = NGramOverlapExampleSelector(
examples=examples,
example_prompt=example_prompt,
threshold=0, # 按重叠分数排序,并排除与输入没有 n-gram 重叠的示例
)
# 用选择器实例化模板
dynamic_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="给出每个输入的中文翻译",
suffix="Input: {sentence}\\nOutput:",
input_variables=["sentence"],
)
# 定义大模型(适配 DeepSeek)
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
# 方式一:只看组装好的提示词(选了哪些示例)
print(dynamic_prompt.invoke({"sentence": "Spot can run fast."}).to_messages()[0].content)
运行结果:
=== 提示词内容(NGram 选中的示例)===
给出每个输入的中文翻译
Input: Spot can run.
Output: Spot可以跑。
Input: See Spot run.
Output: 看见Spot跑。
Input: Spot can run fast.
Output:
可见:Spot can run fast. 与 Spot can run.、See Spot run. 的 n-gram 重叠最多,被优先选中。
3.6 总结对比
| LengthBased(按长度) | 从前往后塞,长度超 max_length 就停,跟问题无关 | 5 个反义词 → 只塞前 3 个(长度到 13 停) |
| SemanticSimilarity(语义相似) | 用向量算"示例和问题多像",选最像的 k 个 | worried 最像 happy → 只选它 |
| MMR(相似+多样) | 既要和问题相关,又要示例之间不重复 | worried → 选 happy(最像)+ windy(补充多样) |
| NGramOverlap(n-gram 重叠) | 数示例和问题相同的单词/词组,按重叠度挑 | Spot can run fast. 和 Spot can run. 重叠多 |
四、复盘(附答案)
💡 思考题
提示词模板解决了哪些核心问题?PromptTemplate 和 ChatPromptTemplate 有什么区别?
MessagesPlaceholder 是干什么的?适合什么场景?
什么是少样本提示?它主要解决哪三类问题?
示例选择器的四种策略(Length / Similarity / MMR / NGram)分别是怎么挑示例的?
语义相似性选择和 MMR 有什么区别?为什么 RAG 场景常用 MMR?
📝 答案
解决四大问题:可复用性(一个模板无限复用)、关注点分离(结构与内容分离)、一致性(提示结构统一、输出稳定)、可维护性(改一处模板即可)。区别:PromptTemplate 生成字符串提示,适合纯文本 LLM;ChatPromptTemplate 生成消息列表(可组合 system/user/ai 角色),专为聊天模型设计。
MessagesPlaceholder 在模板的特定位置插入一整段消息列表(多轮对话历史),适合需要在 system 消息后动态拼接历史的场景,如带上下文的聊天机器人。
少样本提示:给模型提供少量"输入-输出"示例,让它照葫芦画瓢。解决三类问题:强制输出格式(JSON/XML 等格式样板)、指令难以描述的任务(如风格仿写,几个示例比长篇指令更有效)、多步推理场景(示例展示思考链 CoT,引导模型按同样路径推理)。
Length:按长度从前往后塞示例,超 max_length 就停,与问题无关;Similarity:用向量算语义相似度,选最像的 k 个;MMR:既要相关又要彼此多样(相关 + 新颖性);NGram:数示例与问题相同的 n-gram 重叠度来挑选。
Similarity 只看"和问题最像",可能选出一堆同质示例;MMR 在相关性基础上增加多样性约束——先选最相关的,再选"相关且与已选不重复"的。RAG 检索常返回大量高度相似的文档,用 MMR 去重 + 多样化筛选后再交给 LLM,能提升答案质量、减少幻觉。
🎯 闭幕

从"提示词模板"到"少样本提示",从"LangChain Hub"到"四大示例选择器"——这一篇帮你把 LangChain 的提示工程走了一遍。
如果本文对你有帮助,欢迎:
👍 点赞 | ⭐ 收藏 | 👤 关注作者 | 💬 留言交流你的疑问或补充
你的每一次互动都是我继续更新的动力,我们下一篇见!🚀



