image

20250914-03: Langchain概念:提示模板+少样本提示

聊天模型 + 消息 + 提示 + 结构化输出

🎯 学习目标

掌握如何“喂给模型正确的输入”并“解析出想要的输出”。

🔗 核心概念

  • 聊天模型(ChatModel)
  • 消息(Message)
  • 聊天历史(Chat History)
  • 提示模板(PromptTemplate)
  • 少样本提示(Few-shot Prompting)
  • 示例选择器(ExampleSelector)
  • 结构化输出(Structured Output)
  • 输出解析器(OutputParser)

提示模板(PromptTemplate)

提示模板有助于将用户输入和参数转换为语言模型的指令。这可以用于指导模型的响应,帮助其理解上下文生成相关且连贯的基于语言的输出。

提示模板接收字典作为输入,其中每个键代表提示模板中要填充的变量

提示模板输出一个 PromptValue。这个 PromptValue 可以传递给 LLM 或 ChatModel,也可以转换为字符串消息列表

PromptValue 存在的原因是为了方便在字符串和消息之间切换。

提示模板有几种不同类型

字符串提示模板

这些提示模板用于格式化单个字符串,通常用于更简单的输入。例如,构建和使用 PromptTemplate 的常见方法如下

from langchain_core.prompts import PromptTemplate
# String
prompt_template = PromptTemplate.from_template("Tell me a joke about {topic}")

prompt_template.invoke({"topic": "cats"})

聊天提示模板 ChatPromptTemplates

这些提示模板用于格式化消息列表。这些“模板”本身由一个模板列表组成。例如,构建和使用 ChatPromptTemplate 的常见方法如下

from langchain_core.prompts import ChatPromptTemplate

prompt_template = ChatPromptTemplate([
    ("system", "You are a helpful assistant"),
    ("user", "Tell me a joke about {topic}")

])

prompt_template.invoke({"topic": "cats"})

**API 参考:**​ChatPromptTemplate

在上面的示例中,当调用此 ChatPromptTemplate 时,它将构建两条消息。第一条是系统消息,没有要格式化的变量。第二条是 HumanMessage,它将由用户传入的 topic 变量进行格式化。

消息占位符 MessagesPlaceholder

场景: 将消息列表插入特定位置非常有用

此提示模板负责在特定位置添加消息列表。在上面的 ChatPromptTemplate 中,我们看到了如何格式化两条消息,每条都是一个字符串。但如果我们希望用户传入一个消息列表,并将其放入特定位置,该怎么办?这就是你使用 MessagesPlaceholder 的方式。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage

prompt_template = ChatPromptTemplate([
    ("system", "You are a helpful assistant"),
    MessagesPlaceholder("msgs")
])

# Simple example with one message
prompt_template.invoke({"msgs": [HumanMessage(content="hi!")]})

# More complex example with conversation history
messages_to_pass = [
    HumanMessage(content="What's the capital of France?"),
    AIMessage(content="The capital of France is Paris."),
    HumanMessage(content="And what about Germany?")
]

formatted_prompt = prompt_template.invoke({"msgs": messages_to_pass})
print(formatted_prompt)

**API 参考:**​ChatPromptTemplate | MessagesPlaceholder | HumanMessage

这将生成一个包含两条消息的列表,第一条是系统消息,第二条是我们传入的 HumanMessage。

如果我们传入了 5 条消息,那么它总共会生成 6 条消息(系统消息加上传入的 5 条消息)。这对于将消息列表插入特定位置非常有用

实现相同目标的另一种方法,不显式使用 MessagesPlaceholder 类,如下所示

prompt_template = ChatPromptTemplate([

    ("system", "You are a helpful assistant"),

    ("placeholder", "{msgs}") # <-- This is the changed part

])

有关如何使用提示模板的详细信息,请参阅此处的相关操作指南

少量提示few-shot prompting

提高模型性能最有效的方法之一是向模型提供您希望它执行操作的示例。向模型提示添加示例输入和预期输出的技术称为“小样本提示”(few-shot prompting)。该技术基于 《语言模型是小样本学习器》论文。在进行小样本提示时,有几点需要考虑:

  1. 如何生成示例?
  2. 每个提示中包含多少示例?
  3. 运行时如何选择示例?
  4. 示例在提示中如何格式化?

1. 生成示例

小样本提示的第一步也是最重要的一步是准备一套好的示例数据集。好的示例应该在运行时相关、清晰、信息丰富,并提供模型先前未知的信息。

从高层次来看,生成示例的基本方法有:

  • 手动:一个人/多个人生成他们认为有用的示例。
  • 更好的模型:使用更好(可能更昂贵/更慢)的模型的响应作为较差(可能更便宜/更快)模型的示例。
  • 用户反馈:用户(或标注者)对应用程序的交互留下反馈,并根据该反馈生成示例(例如,所有带有积极反馈的交互都可以转化为示例)。
  • LLM 反馈:与用户反馈相同,但该过程通过让模型自我评估来自动化。

哪种方法最佳取决于您的任务。

对于需要透彻理解少数核心原则的任务,手工制作一些真正好的示例会很有价值。

对于正确行为空间更广泛、更细致入微的任务,以更自动化的方式生成大量示例会很有用,这样在运行时有更高可能性存在高度相关的示例

单轮对话与多轮对话示例

生成示例时需要考虑的另一个维度是示例实际展示的内容。

最简单的示例类型只包含用户输入和预期的模型输出。这些是单轮对话示例。

一种更复杂的示例类型是示例是一个完整的对话,通常是模型最初响应不正确,然后用户告诉模型如何纠正其答案。这称为多轮对话示例。多轮对话示例对于更细致入微的任务很有用,在这些任务中,展示常见错误并精确说明其错误原因以及应如何纠正会很有帮助。

2. 示例数量

一旦我们有了示例数据集,就需要考虑每个提示中应该包含多少示例。

关键的权衡是:更多示例通常能提高性能,但更大的提示会增加成本和延迟

而且,超过某个阈值,过多示例反而会开始使模型感到困惑。找到合适数量的示例高度依赖于模型、任务、示例的质量以及您的成本和延迟限制。

根据经验,模型越好,它需要的示例越少,并且在添加更多示例时回报递减的速度越快。但是,可靠地回答这个问题最佳/唯一的方法是使用不同数量的示例进行一些实验

3. 选择示例

假设我们不会将整个示例数据集添加到每个提示中,我们需要一种根据给定输入从数据集中选择示例的方法。我们可以通过以下方式进行:

  • 随机选择
  • 根据输入的(语义或关键词)相似性选择
  • 基于其他约束,如令牌大小

LangChain 提供了许多 ExampleSelectors,可以轻松使用这些技术中的任何一种。

通常,通过语义相似性选择会带来最佳的模型性能。但这有多重要又取决于模型和任务,并且值得进行实验。

4. 格式化示例

目前大多数最先进的模型都是聊天模型,因此我们将重点关注如何为它们 格式化示例 。我们的基本选项是将示例插入:

  • 在系统提示中作为字符串
  • 作为它们自己的消息

如果我们将示例作为字符串插入到系统提示中,我们需要确保模型清楚每个示例的开始位置以及哪些部分是输入,哪些部分是输出。不同的模型对不同的语法响应更好,例如 ChatML、XML、TypeScript 等。

如果我们将示例作为消息插入,其中每个示例都表示为一系列人类、AI 消息,我们可能还需要为消息分配名称,例如 "example_user""example_assistant",以表明这些消息与最新输入消息的参与者不同。

格式化工具调用示例

将示例格式化为消息时,当示例输出包含工具调用时,可能会变得棘手。这是因为不同的模型对生成任何工具调用时允许的消息序列类型有不同的约束。

  • 有些模型要求任何带有工具调用的 AIMessage 必须紧跟每个工具调用的 ToolMessages,
  • 有些模型还要求任何 ToolMessages 必须在下一个 HumanMessage 之前紧跟一个 AIMessage,
  • 有些模型要求如果聊天历史中有任何工具调用/ToolMessages,则必须将工具传递给模型。

这些要求是模型特有的,应针对您正在使用的模型进行检查。如果您的模型在工具调用后需要 ToolMessages 和/或在 ToolMessages 后需要 AIMessages,并且您的示例只包含预期的工具调用而不是实际的工具输出,您可以尝试在每个示例的末尾添加带有通用内容的虚拟 ToolMessages / AIMessages,以满足 API 约束。在这些情况下,尤其值得尝试将示例作为字符串而不是消息插入,因为虚拟消息可能会对某些模型产生不利影响。

深入理解

“单轮对话”和“多轮对话”示例的区别是什么?

好的,我们来把“单轮对话”和“多轮对话”示例的区别讲清楚。这是一个非常重要的概念,尤其在教导大模型时。

核心比喻:教小孩做题

想象一下,你正在教一个非常聪明但没有经验的小孩(大语言模型)如何解题。


1. 单轮对话示例

  • 怎么教?

    • 你直接给他看一道题目(用户输入) 和对应的标准答案(预期的模型输出)

    • 示例

      • 输入(用户) : “法国的首都是哪里?”
      • 输出(模型) : “法国的首都是巴黎。”
  • 特点

    • 简单直接:就像一张闪卡或备忘录,告诉模型“遇到这个问题,就回答这个”。
    • 单次交互:只有一问一答,没有后续的交流。
    • 适用场景:适用于事实性定义明确没有歧义的问题。比如询问事实、翻译句子、总结一段固定文本等。
  • 局限性

    • 如果问题很复杂,或者模型第一次答错了,你没有教它如何纠正
    • 就像只给小孩看了答案,没给他讲解题思路和易错点。

2. 多轮对话示例

  • 怎么教?

    • 你给小孩展示一个完整的教学对话。这个对话通常包含模型一开始犯的一个典型错误,然后用户(老师)进行纠正,最后模型给出正确的回应

    • 示例

      • 第1轮(用户) : “帮我写一首关于猫的诗。”
      • 第1轮(模型 - 错误示范) : “猫儿喵喵叫,爱吃鱼和肉。(这首诗过于简单和幼稚)”
      • 第2轮(用户 - 纠正) : “这首诗太简单了,请写得更有文学性一些,用上比喻手法。”
      • 第2轮(模型 - 正确回应) : “夜色中的精灵,绒毯般的身躯蜷缩如神秘的毛线团,一双琥珀是窥探月亮的透镜…”
  • 特点

    • 展示错误与纠正:它不仅展示了“应该做什么”,还展示了“不应该做什么”以及“如何从错误中改正”。

    • 多轮交互:包含多轮一问一答,形成了一个完整的对话上下文。

    • 适用场景:适用于复杂主观容易出错的任务。例如:

      • 风格调整:教模型如何根据反馈调整写作风格(如上例)。
      • 复杂推理:展示模型推理过程中的错误,并教它如何一步步正确思考。
      • 拒绝回答:教模型如何识别并礼貌拒绝不恰当或有害的请求。
      • 交互式任务:需要多次澄清和确认的任务(如订票、编程)。
  • 优势

    • 更加细致入微:能教会模型更复杂的指令和更精细的偏好。
    • 预防常见错误:提前演示常见错误及其纠正方法,让模型学会“避坑”。
    • 理解对话流:教会模型如何在一个连续的对话中理解和响应用户的反馈。

总结与对比

为了帮你更好地理解,我们把两者放在一个表格里:

方面 单轮对话示例 多轮对话示例
形式 简单的 Q-A 对(一问一答) 完整的对话历史(多问多答)
内容 只展示正确的最终答案 展示错误 -> 反馈 -> 纠正的全过程
好比 闪卡/备忘录:只记答案 教学视频/案例研究:分析错题,讲解思路
教学目标 教会模型“是什么 教会模型“为什么”以及“如何改进
复杂度
适用任务 事实问答、翻译、简单总结 风格写作、复杂推理、安全拒绝、交互式任务

简单来说:

  • 如果你想让模型学会回答简单明了的问题,就用单轮示例。像喂给它一对对的(问题,答案)。
  • 如果你想教模型完成一件复杂的、容易出错的事情,就给它讲个小故事(多轮示例)。这个故事里要包含一个常见的错误情节,以及如何修正这个错误的情节。这样模型就能从故事中学到更深刻的东西。

选择哪种方式,取决于你的任务有多复杂。对于高级应用,混合使用这两种示例往往能取得最好的效果。


如何给模型提供例题集?格式化示例?

好的,我们来把“格式化示例”这个概念讲清楚。这就像是教你如何给大模型准备“学习资料”,不同的准备方法效果不同。

核心比喻:如何给模型提供例题集?

想象一下,你是一位老师,要给一个非常聪明的学生(聊天模型)一本练习册,里面包含一些例题和答案(示例),教它如何解决特定类型的问题。

现在关键问题是:这本练习册该怎么编写? 主要有两种方法:


方法一:把例题写在“说明书”里(作为字符串插入系统提示)

  • 怎么做?

    • 系统提示(System Prompt)就像是交给模型的一份总说明书,告诉它它的角色和任务。

    • 这种方法就是把所有的例题和答案,都写成一大段文字,塞进这份说明书里。

    • 例如:

      “你是一个乐于助人的助手。以下是你要学习的例子:
      例子1:
      用户问:法国的首都是什么?
      你应该答:巴黎。

      例子2:
      用户问:你好吗?
      你应该答:我是一个AI,没有感觉,但我很好!今天能帮你什么?

      现在,请开始回答用户的真实问题:”

  • 面临的挑战(需要确保模型清楚)

    • 你必须用非常清晰的标记(一种语法)来告诉模型:哪里是一个例子的开始?哪部分是用户的问题(输入)?哪部分是它应该给出的答案(输出)?
    • 这就好比你在说明书里写例题,必须用不同的字体、颜色或者标签把题目和答案区分开,不然学生就看懵了。
  • 常用的“标记语法”(文中提到的)

    • ChatML:OpenAI模型常用的一种格式,用 <|im_start|>, <|im_end|> 等特殊标签来划分角色和内容。
    • XML标签:用 <user>问题</user><assistant>答案</assistant> 这样的标签来区分。
    • 其他格式:如仿照TypeScript等编程语言的格式。
    • 关键点:不同的学生(模型)习惯不同的标记语法。你需要根据你正在使用的那个模型,选择它最熟悉、效果最好的那种语法。

方法二:把例题做成“标准答题卡”(作为它们自己的消息)

  • 怎么做?

    • 聊天模型本身的理解方式就是基于消息序列的(比如:用户消息1 -> AI消息1 -> 用户消息2 -> AI消息2…)。

    • 这种方法就是把每一个例子都模拟成一段完整的对话历史消息,直接交给模型。

    • 例如,你提供给模型的消息历史会是这样的:

      1. {"role": "user", "content": "法国的首都是什么?"}
      2. {"role": "assistant", "content": "巴黎。"}
      3. {"role": "user", "content": "你好吗?"}
      4. {"role": "assistant", "content": "我是一个AI,没有感觉,但我很好!今天能帮你什么?"}
      5. {"role": "user", "content": "(用户现在的真实问题)"} <-- 从这里开始才是真正的任务
  • 面临的挑战(需要区分例题和真实对话)

    • 如果直接这么干,模型可能会搞不清楚:前面那几轮对话(例子)到底是之前真实的聊天历史,还是你给它看的例题

    • 解决方案:给这些“例题消息”打上特殊的标签名字

    • 例如,你可以这样做:

      • {"role": "user", "name": "example_user", "content": "法国的首都是什么?"}
      • {"role": "assistant", "name": "example_assistant", "content": "巴黎。"}
    • 通过给角色(role)再加上一个名字(name),比如 example_user,你就明确地告诉模型:“注意啦,这条消息是一个示例,不是真正的对话历史!”这样模型就能更好地理解你的意图。


总结与对比

为了帮你更好地理解,我们把两者放在一个表格里:

方面 方法一:作为字符串放入系统提示 方法二:作为独立消息
比喻 把例题写在任务说明书 把例题做成标准的答题卡样本
实现方式 将示例拼接成一个大字符串,放入system消息 将每个示例拆分为多条userassistant消息
关键挑战 需要一种清晰的语法(如XML)来区分输入和输出 需要一种方式(如name字段)来区分示例和真实对话
优点 结构紧凑,所有指令和示例都在一起 更符合模型处理对话的原生方式,通常效果更好、更可靠
缺点 格式容易出错,且最佳语法因模型而异 会消耗更多的Token(字数限制)

简单来说:

  • 方法一(字符串) :像是在给模型下指令时,把例子口头给它念一遍。你需要说得特别清楚,不然它会误解。
  • 方法二(消息) :像是直接给它看几张标准的“答题卡”样板。这种方式更直观,模型更容易模仿,是更推荐、更现代的做法。但记得给这些样板答题卡贴上“这是样例”的标签(使用name字段),避免模型混淆。

在实际应用中,方法二(作为独立消息) 通常更受青睐,因为它更稳定,能更清晰地传达示例的结构。

Logo

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

更多推荐