少样本学习(Few-Shot Learning)是一种机器学习范式,旨在让模型在仅有极少量标注样本(通常为 1-5 个)的情况下,能够快速理解新任务并做出准确预测。少样本提示词就是少样本学习在大模型领域的具体落地手段。如果说“少样本学习”是让模型具备举一反三的能力,那么“少样本提示词”就是你实现这种能力的“说明书”。LangChain提供了一组针对性的少样本提示词模板。

Alternative Text

1. FewShotPromptTemplate

FewShotPromptTemplate是LangChain实现少样本提示词的核心工程化方案。它通过将 “例子” 与 “主模板” 解耦,解决了手动拼接字符串容易出错且难以维护的问题。我们先来演示一下针对它的使用方式。这是一个基于FewShotPromptTemplate的经典 “起名大师” 示例。它展示了如何通过3个例子教会模型按特定风格(字数对齐、意境深远)起名字。

from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import ChatOpenAI

examples = [
    {"word": "科技", "brand_name": "灵境先驱"},
    {"word": "咖啡", "brand_name": "醇厚余韵"},
    {"word": "自然", "brand_name": "森语呼吸"}
]

template = """
行业: {word}
推荐名称: {brand_name}
"""
prompt = FewShotPromptTemplate(
    examples=examples, 
    example_prompt=PromptTemplate.from_template(template=template), 
    prefix="你是一位极简主义品牌策划专家,请根据行业提供一个四字中文名:",
    suffix="行业: {input}\n推荐名称:", 
    input_variables=["input"],
    example_separator="\n"
)

llm = ChatOpenAI(
    model="gpt-5.2-chat",
    base_url="…",
    api_key="…",
)

chain = prompt | llm
print(prompt.format(input="新能源汽车"))
print(chain.invoke(input= {"input": "新能源汽车"}).content)

如上面的代码片段所示,我们创建了一个FewShotPromptTemplate对象,并利用examples参数以字典列表的形式指定了三个七名案例。参数example_prompt用于指定针对单个实例的提示词模板,我们给定的是一个PromptTemplate对象。除了这两个核心参数,我们还通过相应参数指定的提示词的前缀和后缀,以及多个针对实例的提示词之间的分隔符。我们将“新能源汽车”作为参数格式此FewShotPromptTemplate对象,看看最终生成的提示词是什么样子。为了验证模型能否给出一个希望的名称,我们创建了一个ChatOpenAI对象与之构成一个LCEL链,并以相同的参数调用它。提示词的内容和OpenAI起的名字如下所示:

你是一位极简主义品牌策划专家,请根据行业提供一个四字中文名:

行业: 科技
推荐名称: 灵境先驱

行业: 咖啡
推荐名称: 醇厚余韵

行业: 自然
推荐名称: 森语呼吸

行业: 新能源汽车
推荐名称:
--------------------------------------------------
行业:新能源汽车  
推荐名称:**驭能未来**

FewShotPromptTemplate通过混入一个名为_FewShotPromptTemplateMixin的内部类型的方式继承StringPromptTemplate。私有混入类型_FewShotPromptTemplateMixin的引入旨在解决如何从候选实例中选择与当前任务最匹配的一个或者多个实例。这又涉及到如下这个作为实例选择器基类的BaseExampleSelector类型。这个抽象类提供了add_exampleselect_examples两个抽象方法,前者用于接收候选的实例,后者实施筛选。aadd_example/aselect_examples最终会以异步的方式调用这两个方法。

class BaseExampleSelector(ABC):
    @abstractmethod
    def add_example(self, example: dict[str, str]) -> Any:

    async def aadd_example(self, example: dict[str, str]) -> Any:
        return await run_in_executor(None, self.add_example, example)

    @abstractmethod
    def select_examples(self, input_variables: dict[str, str]) -> list[dict]:

    async def aselect_examples(self, input_variables: dict[str, str]) -> list[dict]:
        return await run_in_executor(None, self.select_examples, input_variables)

混入类_FewShotPromptTemplateMixin继承自pydantic.Model,其_get_examples/_aget_examples提取实例的逻辑很简单:如果通过examples显式指定了实例列表,就返回它,否则就使用example_selector字段的表示的BaseExampleSelector对象来选择返回的实例。Pydantic验证方法check_examples_and_selector会确保两者又能设置其一。

class _FewShotPromptTemplateMixin(BaseModel):
    examples: list[dict] | None = None
    example_selector: BaseExampleSelector | None = None

    @model_validator(mode="before")
    @classmethod
    def check_examples_and_selector(cls, values: dict) -> Any:
        examples = values.get("examples")
        example_selector = values.get("example_selector")
        if examples and example_selector:
            msg = "Only one of 'examples' and 'example_selector' should be provided"
            raise ValueError(msg)

        if examples is None and example_selector is None:
            msg = "One of 'examples' and 'example_selector' should be provided"
            raise ValueError(msg)

        return values

    def _get_examples(self, **kwargs: Any) -> list[dict]:
        if self.examples is not None:
            return self.examples
        if self.example_selector is not None:
            return self.example_selector.select_examples(kwargs)
        msg = "One of 'examples' and 'example_selector' should be provided"
        raise ValueError(msg)

    async def _aget_examples(self, **kwargs: Any) -> list[dict]:
        if self.examples is not None:
            return self.examples
        if self.example_selector is not None:
            return await self.example_selector.aselect_examples(kwargs)
        msg = "One of 'examples' and 'example_selector' should be provided"
        raise ValueError(msg)

由于_FewShotPromptTemplateMixin是基类,所以当我们调用构造函数的时候以参数的形式初始化定义在FewShotPromptTemplate类型中的这些字段外,还需要初始化基类中examples或者example_selector字段。通过template_format字段的定义可看出它支持f-stringjinja2这两种模板格式。实现的format方法逻辑很简单:它会调用_get_examples方法提取实例列表,然后利用example_prompt字段返回的PromptTemplate对每个实例进行格式化,得到的格式化字符串通过example_separator字段返回的分隔符进行拼接。然后再添加前缀和后缀后的到一个完整的模板字符串,再利用与template_format匹配的模板引擎对其再次实施格式化。FewShotPromptTemplateaformat方法也进行了类似的重写。

class FewShotPromptTemplate(_FewShotPromptTemplateMixin, StringPromptTemplate):
    validate_template: bool = False
    example_prompt: PromptTemplate
    suffix: str
    example_separator: str = "\n\n"
    prefix: str = ""
    template_format: Literal["f-string", "jinja2"] = "f-string"

    def __init__(self, **kwargs: Any) -> None:
        if "input_variables" not in kwargs and "example_prompt" in kwargs:
            kwargs["input_variables"] = kwargs["example_prompt"].input_variables
        super().__init__(**kwargs)

    def format(self, **kwargs: Any) -> str:
        examples = self._get_examples(**kwargs)
        examples = [
            {k: e[k] for k in self.example_prompt.input_variables} for e in examples
        ]
        example_strings = [
            self.example_prompt.format(**example) for example in examples
        ]
        pieces = [self.prefix, *example_strings, self.suffix]
        template = self.example_separator.join([piece for piece in pieces if piece])
        return DEFAULT_FORMATTER_MAPPING[self.template_format](template, **kwargs)

    async def aformat(self, **kwargs: Any) -> str  

2. FewShotChatMessagePromptTemplate

FewShotChatMessagePromptTemplate是专门为Chat模型设计的少样本模板。如果说FewShotPromptTemplate是在拼凑一段“长文章” ,那么这个类就是在拼凑一段 “聊天记录” 。在下面的演示程序中,我们利用格式化FewShotChatMessagePromptTemplate生成的提示词来指导模型生成SQL语句。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate

examples = [
    {"input": "查询所有员工的姓名和入职日期", "output": "SELECT name, hire_date FROM employees;"},
    {"input": "找出销售部薪资大于 5000 的人", "output": "SELECT * FROM employees WHERE department = 'Sales' AND salary > 5000;"},
    {"input": "统计每个部门的人数", "output": "SELECT department, COUNT(*) FROM employees GROUP BY department;"}
]

example_prompt = ChatPromptTemplate.from_messages(
    [
        ("human", "{input}"),
        ("ai", "{output}")
    ]
)

few_shot_prompt = FewShotChatMessagePromptTemplate(
    example_prompt=example_prompt,
    examples=examples,
)

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个严谨的 SQL 专家。请直接输出 SQL 语句,不要解释,不要多言。"),
        few_shot_prompt,
        ("human", "{input}"),
    ]
)

messages = prompt.format_messages(input="查询入职时间在2023年之后的研发部员工")
for msg in messages:
    print(f"[{type(msg).__name__}]{msg.content}")

llm = model = ChatOpenAI(
    model="gpt-5.2-chat",
    base_url="…",
    api_key="…",
)

query = "查询入职时间在2023年之后的研发部员工"
chain = prompt | llm
result = chain.invoke(input={"input": query})
print(f"{query}:{result.content}")

如代码片段所示,我们创建了一个ChatPromptTemplate对象作为针对实例的提示词模板,该模板会生成的两个消息用于模拟一个对QA。我们基于这个ChatPromptTemplate和提供的实例将FewShotChatMessagePromptTemplate创建出来。但是最终的提示词模板是一个ChatPromptTemplate对象,它包含标准少样本提示词应该拥有的三个部分:指令(Instructions)、实例(Examples)和输入(Input),其中实例部分就是通过FewShotChatMessagePromptTemplate格式化的结果。

为了验证模型能生成合法的SQL,我们创建了ChatOpenAI与之组成LCEL链。我们将查询需求(“查询入职时间在2023年之后的研发部员工”)作为输入对提示词模板进行格式化,它会生成如下的提示词内容,可以看出它由三部分内容组成:作为指令的SystemMessage;作为实例的HumanMessage + AIMessage和作为输入的HumanMessage。我们传入相同的输入调用LCEL链,它确实能够输出我们希望的SQL语句。

[SystemMessage]你是一个严谨的 SQL 专家。请直接输出 SQL 语句,不要解释,不要多言。
[HumanMessage]查询所有员工的姓名和入职日期
[AIMessage]SELECT name, hire_date FROM employees;
[HumanMessage]找出销售部薪资大于 5000 的人
[AIMessage]SELECT * FROM employees WHERE department = 'Sales' AND salary > 5000;
[HumanMessage]统计每个部门的人数
[AIMessage]SELECT department, COUNT(*) FROM employees GROUP BY department;
[HumanMessage]查询入职时间在2023年之后的研发部员工
查询入职时间在2023年之后的研发部员工:SELECT * FROM employees WHERE department = '研发部' AND hire_date > '2023-12-31';

继承自BaseChatPromptTemplateFewShotChatMessagePromptTemplateFewShotPromptTemplate的定义还是不太一样,它旨生成“实例部分”的提示词,所以它并没有定义前缀和后缀这种构建完整提示词的字段。它实现的format_messages方法的逻辑很简单:通过调用基类(_FewShotPromptTemplateMixin)的_get_examples方法将实例提取出来后,它会利用example_prompt字段提供的BaseMessagePromptTemplate或者BaseChatPromptTemplate针对每个实例生成对应的消息列表,所有消息汇总生成的列表就是返回值。至于实现的format方法返回的字符串,则是将消息列表作为参数调用get_buffer_string函数的结果。它的aformat_messagesaformat方法也做了类似的重写。

class FewShotChatMessagePromptTemplate(
    BaseChatPromptTemplate, _FewShotPromptTemplateMixin
):
    input_variables: list[str] = Field(default_factory=list)
    example_prompt: BaseMessagePromptTemplate | BaseChatPromptTemplate

    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
        examples = self._get_examples(**kwargs)
        examples = [
            {k: e[k] for k in self.example_prompt.input_variables} for e in examples
        ]
        return [
            message
            for example in examples
            for message in self.example_prompt.format_messages(**example)
        ]    

    def format(self, **kwargs: Any) -> str:
        messages = self.format_messages(**kwargs)
        return get_buffer_string(messages)

到目前为止,我们基本上把LangChain提示词模板相关的涉及和实现介绍完了,这其中涉及到很多的类型,它们之间的关系可以使用如下这个UML类型来展示。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐