愿旖旎头像
关注
【LangChain实战】LangChain 学习笔记(三):提示词模板与示例选择器封面图

【LangChain实战】LangChain 学习笔记(三):提示词模板与示例选择器

👋 欢迎阅读

🏠个人主页:愿旖旎
📘专栏传送门:算法专栏
💻当前学习内容:LangChain

📑 目录


一、前置讲解

在进入正文前,先花 10 秒了解本篇会反复用到的核心概念,带着印象去读正文,学习效率更高。

🧠 前置知识一:PromptTemplate

字符串提示词模板,用占位符动态生成提示词

🗨️ 前置知识二:ChatPromptTemplate

聊天消息模板,可组合 System/User/AI 消息

📍 前置知识三:MessagesPlaceholder

消息占位符,在指定位置插入一整段对话历史

🔤 前置知识四:少样本提示(Few-shot)

给模型看几个示例再提问,比写长篇指令更有效

🎯 前置知识五:示例选择器

从大量示例中按策略挑最合适的几个:长度/语义相似/MMR/NGram。


二、提示词模板

2.1 认识提示词模板

提示词模板(Prompt Template)LangChain 的核心抽象之一,被广泛应用于构建大语言模型(LLM) 应用的各个环节。简单来说,只要是需要动态批量或有结构地向大语言模型发送请求的地方,几乎都会用到提示词模板

一个简单的例子:假设我们想根据城市名询问 LLM 其历史,按之前做法,需定义 HumanMessage("请介绍上海的历史")HumanMessage("请介绍西安的历史") 等,每次都会重复"请介绍 xxx 的历史"。在 LangChain 中,可定义模板:

  • 固定文本(模板)"请介绍{city}的历史。"

  • 输入变量["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 聊天模型设计。可以方便地构建包含 SystemMessageHumanMessageAIMessage 的消息模板。

代码示例:

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_examplesadd_exampleselect_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 数量来衡量相似度的方法。

文本对n-gram 重叠语义
"苹果手机很好用" / "这款手机很好用"(重复词多)相似
"苹果手机很好用" / "iPhone 非常不错"0(无相同词)相似但表面不匹配

因此,传统 n-gram 重叠 是一种表面形式匹配,只关心词是否完全一样无法处理同义词

语义 n-gram 重叠:它不再比较词本身,而是比较词背后的语义向量(Embedding)——计算它们在语义空间中的向量是否相似,若相似度超过阈值就认为它们"重叠"。例如 "苹果" vs "iPhone" 向量相似度 0.95 → 视为重叠;"好用" vs "不错" 相似度 0.82 → 视为重叠。常用于剽窃检测,能发现"改换了词汇但保留了核心思想"的"智能"剽窃。

LangChain 实现该策略的选择器是:NGramOverlapExampleSelector,其参数如下:

参数说明
example_promptPromptTemplate,用于格式化示例的提示模板
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. 重叠多

四、复盘(附答案)

💡 思考题

  1. 提示词模板解决了哪些核心问题?PromptTemplate 和 ChatPromptTemplate 有什么区别?

  2. MessagesPlaceholder 是干什么的?适合什么场景?

  3. 什么是少样本提示?它主要解决哪三类问题?

  4. 示例选择器的四种策略(Length / Similarity / MMR / NGram)分别是怎么挑示例的?

  5. 语义相似性选择和 MMR 有什么区别?为什么 RAG 场景常用 MMR?

📝 答案

  1. 解决四大问题:可复用性(一个模板无限复用)、关注点分离(结构与内容分离)、一致性(提示结构统一、输出稳定)、可维护性(改一处模板即可)。区别:PromptTemplate 生成字符串提示,适合纯文本 LLM;ChatPromptTemplate 生成消息列表(可组合 system/user/ai 角色),专为聊天模型设计。

  2. MessagesPlaceholder 在模板的特定位置插入一整段消息列表(多轮对话历史),适合需要在 system 消息后动态拼接历史的场景,如带上下文的聊天机器人。

  3. 少样本提示:给模型提供少量"输入-输出"示例,让它照葫芦画瓢。解决三类问题:强制输出格式(JSON/XML 等格式样板)、指令难以描述的任务(如风格仿写,几个示例比长篇指令更有效)、多步推理场景(示例展示思考链 CoT,引导模型按同样路径推理)。

  4. Length:按长度从前往后塞示例,超 max_length 就停,与问题无关;Similarity:用向量算语义相似度,选最像的 k 个;MMR:既要相关又要彼此多样(相关 + 新颖性);NGram:数示例与问题相同的 n-gram 重叠度来挑选。

  5. Similarity 只看"和问题最像",可能选出一堆同质示例;MMR 在相关性基础上增加多样性约束——先选最相关的,再选"相关且与已选不重复"的。RAG 检索常返回大量高度相似的文档,用 MMR 去重 + 多样化筛选后再交给 LLM,能提升答案质量、减少幻觉。


 🎯 闭幕

从"提示词模板"到"少样本提示",从"LangChain Hub"到"四大示例选择器"——这一篇帮你把 LangChain 的提示工程走了一遍。

如果本文对你有帮助,欢迎:

👍 点赞 | ⭐ 收藏 | 👤 关注作者 | 💬 留言交流你的疑问或补充

你的每一次互动都是我继续更新的动力,我们下一篇见!🚀

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2601_96587588/article/details/164376478

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--